From 0f63fb221c20e7da2085532b7d5aece86b5de270 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 26 Jan 2022 20:53:09 +0100 Subject: [PATCH 0001/1098] Bump version to 2022.3.0dev0 (#64996) --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 1c89dae325654a..63dc76be271f97 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -6,7 +6,7 @@ from .backports.enum import StrEnum MAJOR_VERSION: Final = 2022 -MINOR_VERSION: Final = 2 +MINOR_VERSION: Final = 3 PATCH_VERSION: Final = "0.dev0" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" From 4925cf5afffc331e1494408639fbd4948585138e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 26 Jan 2022 23:05:01 +0100 Subject: [PATCH 0002/1098] Handle Tuya sendings strings instead of numeric values (#65009) --- homeassistant/components/tuya/base.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/tuya/base.py b/homeassistant/components/tuya/base.py index b51fa36112183c..ac9a9c83be2891 100644 --- a/homeassistant/components/tuya/base.py +++ b/homeassistant/components/tuya/base.py @@ -74,7 +74,16 @@ def remap_value_from( @classmethod def from_json(cls, dpcode: DPCode, data: str) -> IntegerTypeData: """Load JSON string and return a IntegerTypeData object.""" - return cls(dpcode, **json.loads(data)) + parsed = json.loads(data) + return cls( + dpcode, + min=int(parsed["min"]), + max=int(parsed["max"]), + scale=float(parsed["scale"]), + step=float(parsed["step"]), + unit=parsed.get("unit"), + type=parsed.get("type"), + ) @dataclass From cb2f5b5ed55d96e434b93af410d80accc1843519 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 27 Jan 2022 11:35:41 +1300 Subject: [PATCH 0003/1098] Add diagnostics download to ESPHome (#65008) --- .../components/esphome/diagnostics.py | 32 +++++++++++++++++ tests/components/esphome/conftest.py | 36 ++++++++++++++++++- tests/components/esphome/test_diagnostics.py | 26 ++++++++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/esphome/diagnostics.py create mode 100644 tests/components/esphome/test_diagnostics.py diff --git a/homeassistant/components/esphome/diagnostics.py b/homeassistant/components/esphome/diagnostics.py new file mode 100644 index 00000000000000..4d9b769791c3e5 --- /dev/null +++ b/homeassistant/components/esphome/diagnostics.py @@ -0,0 +1,32 @@ +"""Diahgnostics support for ESPHome.""" +from __future__ import annotations + +from typing import Any, cast + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_PASSWORD +from homeassistant.core import HomeAssistant + +from . import CONF_NOISE_PSK, DomainData + +CONF_MAC_ADDRESS = "mac_address" + +REDACT_KEYS = {CONF_NOISE_PSK, CONF_PASSWORD, CONF_MAC_ADDRESS} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + diag: dict[str, Any] = {} + + diag["config"] = config_entry.as_dict() + + entry_data = DomainData.get(hass).get_entry_data(config_entry) + + if (storage_data := await entry_data.store.async_load()) is not None: + storage_data = cast("dict[str, Any]", storage_data) + diag["storage_data"] = storage_data + + return async_redact_data(diag, REDACT_KEYS) diff --git a/tests/components/esphome/conftest.py b/tests/components/esphome/conftest.py index 91368044723cd0..7cf25f13015a00 100644 --- a/tests/components/esphome/conftest.py +++ b/tests/components/esphome/conftest.py @@ -1,8 +1,42 @@ """esphome session fixtures.""" - import pytest +from homeassistant.components.esphome import CONF_NOISE_PSK, DOMAIN +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + @pytest.fixture(autouse=True) def esphome_mock_async_zeroconf(mock_async_zeroconf): """Auto mock zeroconf.""" + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Return the default mocked config entry.""" + return MockConfigEntry( + title="ESPHome Device", + domain=DOMAIN, + data={ + CONF_HOST: "192.168.1.2", + CONF_PORT: 6053, + CONF_PASSWORD: "", + CONF_NOISE_PSK: "12345678123456781234567812345678", + }, + unique_id="esphome-device", + ) + + +@pytest.fixture +async def init_integration( + hass: HomeAssistant, mock_config_entry: MockConfigEntry +) -> MockConfigEntry: + """Set up the ESPHome integration for testing.""" + mock_config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + return mock_config_entry diff --git a/tests/components/esphome/test_diagnostics.py b/tests/components/esphome/test_diagnostics.py new file mode 100644 index 00000000000000..319bc2602e1bc9 --- /dev/null +++ b/tests/components/esphome/test_diagnostics.py @@ -0,0 +1,26 @@ +"""Tests for the diagnostics data provided by the ESPHome integration.""" + +from aiohttp import ClientSession + +from homeassistant.components.esphome import CONF_NOISE_PSK +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_diagnostics( + hass: HomeAssistant, hass_client: ClientSession, init_integration: MockConfigEntry +): + """Test diagnostics for config entry.""" + result = await get_diagnostics_for_config_entry(hass, hass_client, init_integration) + + assert isinstance(result, dict) + assert result["config"]["data"] == { + CONF_HOST: "192.168.1.2", + CONF_PORT: 6053, + CONF_PASSWORD: "**REDACTED**", + CONF_NOISE_PSK: "**REDACTED**", + } + assert result["config"]["unique_id"] == "esphome-device" From dd4e5bb9c555f63002b85f4665501d1df41b4cfd Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 27 Jan 2022 00:14:02 +0000 Subject: [PATCH 0004/1098] [ci skip] Translation update --- .../components/abode/translations/el.json | 9 +++- .../components/adax/translations/el.json | 11 ++++ .../components/agent_dvr/translations/el.json | 9 ++++ .../components/airthings/translations/el.json | 13 +++++ .../components/airtouch4/translations/el.json | 3 ++ .../components/airvisual/translations/el.json | 18 +++++++ .../airvisual/translations/sensor.el.json | 16 ++++++ .../alarm_control_panel/translations/el.json | 3 ++ .../ambiclimate/translations/el.json | 17 +++++++ .../components/atag/translations/el.json | 5 ++ .../aussie_broadband/translations/nl.json | 15 ++++++ .../aussie_broadband/translations/sv.json | 50 +++++++++++++++++++ .../components/bsblan/translations/el.json | 6 +++ .../components/bsblan/translations/nl.json | 3 +- .../components/bsblan/translations/sv.json | 7 +++ .../climacell/translations/sensor.nl.json | 12 ++++- .../components/co2signal/translations/el.json | 23 +++++++++ .../components/coinbase/translations/el.json | 7 +++ .../components/coinbase/translations/it.json | 2 + .../components/coinbase/translations/sv.json | 8 +++ .../coronavirus/translations/el.json | 3 +- .../components/cpuspeed/translations/bg.json | 8 ++- .../crownstone/translations/el.json | 49 ++++++++++++++++++ .../devolo_home_control/translations/el.json | 11 ++++ .../dialogflow/translations/ca.json | 1 + .../dialogflow/translations/en.json | 1 + .../dialogflow/translations/it.json | 1 + .../dialogflow/translations/tr.json | 1 + .../components/directv/translations/el.json | 10 ++++ .../components/dlna_dmr/translations/el.json | 16 ++++++ .../components/dnsip/translations/el.json | 19 +++++++ .../components/doorbird/translations/el.json | 4 ++ .../components/eafm/translations/el.json | 7 +++ .../components/elkm1/translations/el.json | 6 +++ .../components/esphome/translations/sv.json | 3 +- .../fireservicerota/translations/el.json | 3 ++ .../components/flume/translations/el.json | 4 ++ .../flunearyou/translations/el.json | 9 ++++ .../forked_daapd/translations/el.json | 21 ++++++++ .../components/freebox/translations/el.json | 13 +++++ .../components/fritzbox/translations/el.json | 6 +++ .../components/gdacs/translations/el.json | 3 +- .../components/geofency/translations/ca.json | 1 + .../components/geofency/translations/en.json | 1 + .../components/geofency/translations/it.json | 1 + .../components/geofency/translations/tr.json | 1 + .../components/gpslogger/translations/ca.json | 1 + .../components/gpslogger/translations/en.json | 1 + .../components/gpslogger/translations/it.json | 1 + .../components/gpslogger/translations/tr.json | 1 + .../components/hangouts/translations/el.json | 3 ++ .../components/harmony/translations/el.json | 20 +++++++- .../components/heos/translations/el.json | 10 ++++ .../homeassistant/translations/el.json | 1 + .../components/homekit/translations/el.json | 29 ++++++++++- .../homekit_controller/translations/el.json | 4 ++ .../homewizard/translations/bg.json | 4 ++ .../components/honeywell/translations/el.json | 10 ++++ .../huawei_lte/translations/el.json | 3 +- .../translations/el.json | 13 +++++ .../components/icloud/translations/el.json | 3 ++ .../components/ifttt/translations/ca.json | 1 + .../components/ifttt/translations/en.json | 1 + .../components/ifttt/translations/it.json | 1 + .../components/ifttt/translations/tr.json | 1 + .../components/insteon/translations/el.json | 41 +++++++++++++++ .../intellifire/translations/sv.json | 18 +++++++ .../components/ipp/translations/el.json | 8 ++- .../components/iqvia/translations/el.json | 16 ++++++ .../components/isy994/translations/el.json | 27 +++++++++- .../components/knx/translations/ca.json | 6 ++- .../components/knx/translations/de.json | 6 ++- .../components/knx/translations/el.json | 6 ++- .../components/knx/translations/en.json | 10 ++-- .../components/knx/translations/et.json | 6 ++- .../components/knx/translations/it.json | 6 ++- .../components/knx/translations/ru.json | 6 ++- .../components/knx/translations/sv.json | 20 ++++++++ .../components/knx/translations/tr.json | 6 ++- .../components/knx/translations/zh-Hant.json | 6 ++- .../components/konnected/translations/el.json | 14 +++++- .../components/locative/translations/ca.json | 1 + .../components/locative/translations/en.json | 1 + .../components/locative/translations/it.json | 1 + .../components/locative/translations/tr.json | 1 + .../logi_circle/translations/el.json | 24 +++++++++ .../components/mailgun/translations/ca.json | 1 + .../components/mailgun/translations/en.json | 1 + .../components/mailgun/translations/it.json | 1 + .../components/mailgun/translations/tr.json | 1 + .../components/melcloud/translations/el.json | 7 +++ .../components/mikrotik/translations/el.json | 4 +- .../minecraft_server/translations/el.json | 6 +++ .../modem_callerid/translations/el.json | 14 ++++++ .../components/monoprice/translations/el.json | 17 +++++++ .../components/mqtt/translations/el.json | 6 ++- .../components/myq/translations/el.json | 3 ++ .../components/nanoleaf/translations/sv.json | 7 +++ .../components/netgear/translations/el.json | 15 ++++++ .../components/nexia/translations/el.json | 9 ++++ .../components/notion/translations/el.json | 9 ++++ .../components/nuheat/translations/el.json | 9 ++++ .../components/nut/translations/el.json | 7 +++ .../onboarding/translations/el.json | 7 +++ .../components/overkiz/translations/bg.json | 3 +- .../components/overkiz/translations/ca.json | 4 +- .../components/overkiz/translations/de.json | 4 +- .../components/overkiz/translations/el.json | 3 ++ .../components/overkiz/translations/et.json | 4 +- .../components/overkiz/translations/it.json | 4 +- .../components/overkiz/translations/ru.json | 4 +- .../overkiz/translations/sensor.bg.json | 4 +- .../overkiz/translations/sensor.nl.json | 3 +- .../components/overkiz/translations/sv.json | 8 +++ .../components/overkiz/translations/tr.json | 4 +- .../overkiz/translations/zh-Hant.json | 4 +- .../components/owntracks/translations/ca.json | 1 + .../components/owntracks/translations/en.json | 1 + .../components/owntracks/translations/it.json | 1 + .../components/owntracks/translations/tr.json | 1 + .../components/plaato/translations/ca.json | 1 + .../components/plaato/translations/en.json | 1 + .../components/plaato/translations/it.json | 1 + .../components/plaato/translations/tr.json | 1 + .../components/plex/translations/el.json | 6 +++ .../components/powerwall/translations/el.json | 3 ++ .../components/prosegur/translations/el.json | 5 ++ .../components/ps4/translations/el.json | 1 + .../pvpc_hourly_pricing/translations/el.json | 14 ++++++ .../components/renault/translations/el.json | 15 ++++++ .../components/senseme/translations/bg.json | 3 +- .../components/senseme/translations/ca.json | 3 +- .../components/senseme/translations/de.json | 3 +- .../components/senseme/translations/it.json | 3 +- .../components/senseme/translations/nl.json | 3 +- .../components/senseme/translations/ru.json | 3 +- .../components/senseme/translations/sv.json | 7 +++ .../components/senseme/translations/tr.json | 3 +- .../senseme/translations/zh-Hant.json | 3 +- .../components/sensor/translations/el.json | 3 ++ .../components/shelly/translations/el.json | 3 +- .../shopping_list/translations/el.json | 6 ++- .../components/solax/translations/bg.json | 17 +++++++ .../components/solax/translations/ca.json | 17 +++++++ .../components/solax/translations/de.json | 17 +++++++ .../components/solax/translations/et.json | 17 +++++++ .../components/solax/translations/it.json | 17 +++++++ .../components/solax/translations/ru.json | 17 +++++++ .../components/solax/translations/sv.json | 17 +++++++ .../components/solax/translations/tr.json | 17 +++++++ .../solax/translations/zh-Hant.json | 17 +++++++ .../components/sonos/translations/el.json | 3 ++ .../components/steamist/translations/bg.json | 3 ++ .../components/switchbot/translations/el.json | 28 +++++++++++ .../synology_dsm/translations/el.json | 4 ++ .../synology_dsm/translations/sv.json | 1 + .../system_health/translations/it.json | 2 +- .../components/tplink/translations/el.json | 12 +++++ .../components/traccar/translations/ca.json | 1 + .../components/traccar/translations/en.json | 1 + .../components/traccar/translations/it.json | 1 + .../components/traccar/translations/tr.json | 1 + .../tuya/translations/select.bg.json | 2 + .../tuya/translations/select.ca.json | 5 ++ .../tuya/translations/select.en.json | 5 ++ .../tuya/translations/select.it.json | 5 ++ .../tuya/translations/select.tr.json | 5 ++ .../components/twilio/translations/ca.json | 1 + .../components/twilio/translations/en.json | 1 + .../components/twilio/translations/it.json | 1 + .../components/twilio/translations/tr.json | 1 + .../components/unifi/translations/el.json | 17 +++++-- .../unifiprotect/translations/sv.json | 12 +++++ .../components/upb/translations/el.json | 18 +++++++ .../components/upnp/translations/el.json | 16 ++++++ .../uptimerobot/translations/sensor.ca.json | 11 ++++ .../uptimerobot/translations/sensor.it.json | 11 ++++ .../uptimerobot/translations/sensor.tr.json | 11 ++++ .../components/vera/translations/el.json | 22 ++++++++ .../components/vilfo/translations/el.json | 10 ++++ .../components/vizio/translations/el.json | 9 +++- .../components/watttime/translations/el.json | 22 ++++++++ .../components/webostv/translations/nl.json | 3 +- .../components/wiffi/translations/el.json | 9 +++- .../components/wled/translations/sv.json | 3 +- .../xiaomi_miio/translations/el.json | 14 ++++++ .../xiaomi_miio/translations/select.el.json | 9 ++++ .../yale_smart_alarm/translations/nl.json | 3 +- .../components/zwave_js/translations/el.json | 9 ++++ 189 files changed, 1425 insertions(+), 66 deletions(-) create mode 100644 homeassistant/components/adax/translations/el.json create mode 100644 homeassistant/components/agent_dvr/translations/el.json create mode 100644 homeassistant/components/airthings/translations/el.json create mode 100644 homeassistant/components/airvisual/translations/sensor.el.json create mode 100644 homeassistant/components/ambiclimate/translations/el.json create mode 100644 homeassistant/components/aussie_broadband/translations/sv.json create mode 100644 homeassistant/components/bsblan/translations/sv.json create mode 100644 homeassistant/components/co2signal/translations/el.json create mode 100644 homeassistant/components/coinbase/translations/sv.json create mode 100644 homeassistant/components/crownstone/translations/el.json create mode 100644 homeassistant/components/devolo_home_control/translations/el.json create mode 100644 homeassistant/components/directv/translations/el.json create mode 100644 homeassistant/components/dlna_dmr/translations/el.json create mode 100644 homeassistant/components/eafm/translations/el.json create mode 100644 homeassistant/components/flunearyou/translations/el.json create mode 100644 homeassistant/components/freebox/translations/el.json create mode 100644 homeassistant/components/heos/translations/el.json create mode 100644 homeassistant/components/honeywell/translations/el.json create mode 100644 homeassistant/components/hunterdouglas_powerview/translations/el.json create mode 100644 homeassistant/components/intellifire/translations/sv.json create mode 100644 homeassistant/components/iqvia/translations/el.json create mode 100644 homeassistant/components/knx/translations/sv.json create mode 100644 homeassistant/components/logi_circle/translations/el.json create mode 100644 homeassistant/components/melcloud/translations/el.json create mode 100644 homeassistant/components/modem_callerid/translations/el.json create mode 100644 homeassistant/components/monoprice/translations/el.json create mode 100644 homeassistant/components/nanoleaf/translations/sv.json create mode 100644 homeassistant/components/netgear/translations/el.json create mode 100644 homeassistant/components/nexia/translations/el.json create mode 100644 homeassistant/components/notion/translations/el.json create mode 100644 homeassistant/components/onboarding/translations/el.json create mode 100644 homeassistant/components/overkiz/translations/sv.json create mode 100644 homeassistant/components/pvpc_hourly_pricing/translations/el.json create mode 100644 homeassistant/components/senseme/translations/sv.json create mode 100644 homeassistant/components/solax/translations/bg.json create mode 100644 homeassistant/components/solax/translations/ca.json create mode 100644 homeassistant/components/solax/translations/de.json create mode 100644 homeassistant/components/solax/translations/et.json create mode 100644 homeassistant/components/solax/translations/it.json create mode 100644 homeassistant/components/solax/translations/ru.json create mode 100644 homeassistant/components/solax/translations/sv.json create mode 100644 homeassistant/components/solax/translations/tr.json create mode 100644 homeassistant/components/solax/translations/zh-Hant.json create mode 100644 homeassistant/components/switchbot/translations/el.json create mode 100644 homeassistant/components/unifiprotect/translations/sv.json create mode 100644 homeassistant/components/upb/translations/el.json create mode 100644 homeassistant/components/upnp/translations/el.json create mode 100644 homeassistant/components/uptimerobot/translations/sensor.ca.json create mode 100644 homeassistant/components/uptimerobot/translations/sensor.it.json create mode 100644 homeassistant/components/uptimerobot/translations/sensor.tr.json create mode 100644 homeassistant/components/vera/translations/el.json create mode 100644 homeassistant/components/vilfo/translations/el.json create mode 100644 homeassistant/components/watttime/translations/el.json create mode 100644 homeassistant/components/xiaomi_miio/translations/select.el.json diff --git a/homeassistant/components/abode/translations/el.json b/homeassistant/components/abode/translations/el.json index caa58ca58e290a..a0501a41959578 100644 --- a/homeassistant/components/abode/translations/el.json +++ b/homeassistant/components/abode/translations/el.json @@ -2,9 +2,16 @@ "config": { "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", - "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03c5\u03b8\u03b5\u03bd\u03c4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7" + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03c5\u03b8\u03b5\u03bd\u03c4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", + "invalid_mfa_code": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 MFA" }, "step": { + "mfa": { + "data": { + "mfa_code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 MFA (6 \u03c8\u03b7\u03c6\u03af\u03b1)" + }, + "title": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc MFA \u03b3\u03b9\u03b1 \u03c4\u03bf Abode" + }, "reauth_confirm": { "title": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Abode" } diff --git a/homeassistant/components/adax/translations/el.json b/homeassistant/components/adax/translations/el.json new file mode 100644 index 00000000000000..14a2f350f88d4b --- /dev/null +++ b/homeassistant/components/adax/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "account_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/agent_dvr/translations/el.json b/homeassistant/components/agent_dvr/translations/el.json new file mode 100644 index 00000000000000..7613c5617597c7 --- /dev/null +++ b/homeassistant/components/agent_dvr/translations/el.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Agent DVR" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airthings/translations/el.json b/homeassistant/components/airthings/translations/el.json new file mode 100644 index 00000000000000..63bf11b8c07f0a --- /dev/null +++ b/homeassistant/components/airthings/translations/el.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 {url} \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b2\u03c1\u03b5\u03af\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2", + "id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc", + "secret": "\u039c\u03c5\u03c3\u03c4\u03b9\u03ba\u03cc" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airtouch4/translations/el.json b/homeassistant/components/airtouch4/translations/el.json index 004cb1a268f05f..a54eab486e6668 100644 --- a/homeassistant/components/airtouch4/translations/el.json +++ b/homeassistant/components/airtouch4/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "no_units": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03ba\u03b1\u03bc\u03af\u03b1 \u03bf\u03bc\u03ac\u03b4\u03b1 AirTouch 4." + }, "step": { "user": { "title": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 {intergration}." diff --git a/homeassistant/components/airvisual/translations/el.json b/homeassistant/components/airvisual/translations/el.json index cee03bf2677e5c..9c9a715e5b1f21 100644 --- a/homeassistant/components/airvisual/translations/el.json +++ b/homeassistant/components/airvisual/translations/el.json @@ -1,13 +1,31 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af \u03ae \u03c4\u03bf Node/Pro ID \u03b5\u03af\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03ba\u03b1\u03c4\u03b1\u03c7\u03c9\u03c1\u03b7\u03bc\u03ad\u03bd\u03bf." + }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "step": { + "node_pro": { + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c9\u03c0\u03b9\u03ba\u03ae \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 AirVisual. \u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b1\u03bd\u03b1\u03ba\u03c4\u03b7\u03b8\u03b5\u03af \u03b1\u03c0\u03cc \u03c4\u03bf UI \u03c4\u03b7\u03c2 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1\u03c2.", + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03bd\u03cc\u03c2 \u03ba\u03cc\u03bc\u03b2\u03bf\u03c5 AirVisual Node/Pro" + }, "reauth_confirm": { "title": "\u0395\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 AirVisual" }, "user": { + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c4\u03cd\u03c0\u03bf \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd AirVisual \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03b5\u03af\u03c4\u03b5.", + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 AirVisual" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7\u03c2 \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03af\u03b1\u03c2 \u03c3\u03c4\u03bf\u03bd \u03c7\u03ac\u03c1\u03c4\u03b7" + }, "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 AirVisual" } } diff --git a/homeassistant/components/airvisual/translations/sensor.el.json b/homeassistant/components/airvisual/translations/sensor.el.json new file mode 100644 index 00000000000000..4bb3fc0fc0962e --- /dev/null +++ b/homeassistant/components/airvisual/translations/sensor.el.json @@ -0,0 +1,16 @@ +{ + "state": { + "airvisual__pollutant_label": { + "co": "\u039c\u03bf\u03bd\u03bf\u03be\u03b5\u03af\u03b4\u03b9\u03bf \u03c4\u03bf\u03c5 \u03ac\u03bd\u03b8\u03c1\u03b1\u03ba\u03b1", + "n2": "\u0394\u03b9\u03bf\u03be\u03b5\u03af\u03b4\u03b9\u03bf \u03c4\u03bf\u03c5 \u03b1\u03b6\u03ce\u03c4\u03bf\u03c5", + "o3": "\u038c\u03b6\u03bf\u03bd", + "p1": "PM10", + "p2": "PM2.5", + "s2": "\u0394\u03b9\u03bf\u03be\u03b5\u03af\u03b4\u03b9\u03bf \u03c4\u03bf\u03c5 \u03b8\u03b5\u03af\u03bf\u03c5" + }, + "airvisual__pollutant_level": { + "good": "\u039a\u03b1\u03bb\u03cc", + "very_unhealthy": "\u03a0\u03bf\u03bb\u03cd \u03b1\u03bd\u03b8\u03c5\u03b3\u03b9\u03b5\u03b9\u03bd\u03cc" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarm_control_panel/translations/el.json b/homeassistant/components/alarm_control_panel/translations/el.json index 450d01ff8a510d..ed19402786f32e 100644 --- a/homeassistant/components/alarm_control_panel/translations/el.json +++ b/homeassistant/components/alarm_control_panel/translations/el.json @@ -7,6 +7,9 @@ "disarm": "\u0391\u03c6\u03bf\u03c0\u03bb\u03b9\u03c3\u03bc\u03cc\u03c2 {entity_name}", "trigger": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 {entity_name}" }, + "condition_type": { + "is_triggered": "{entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5" + }, "trigger_type": { "armed_away": "{entity_name} \u03bf\u03c0\u03bb\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03b3\u03b9\u03b1 \u03b5\u03ba\u03c4\u03cc\u03c2 \u03c3\u03c0\u03b9\u03c4\u03b9\u03bf\u03cd", "armed_home": "{entity_name} \u03bf\u03c0\u03bb\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03b3\u03b9\u03b1 \u03c3\u03c0\u03af\u03c4\u03b9", diff --git a/homeassistant/components/ambiclimate/translations/el.json b/homeassistant/components/ambiclimate/translations/el.json new file mode 100644 index 00000000000000..f77e38ce6fa8c4 --- /dev/null +++ b/homeassistant/components/ambiclimate/translations/el.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "access_token": "\u0386\u03b3\u03bd\u03c9\u03c3\u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b5\u03bd\u03cc\u03c2 \u03c3\u03c5\u03bc\u03b2\u03cc\u03bb\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2." + }, + "error": { + "follow_link": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf \u03ba\u03b1\u03b9 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af\u03c4\u03b5 \u03c0\u03c1\u03b9\u03bd \u03c0\u03b1\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03a5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae", + "no_token": "\u0394\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Ambiclimate" + }, + "step": { + "auth": { + "description": "\u0391\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd [\u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf]({authorization_url}) \u03ba\u03b1\u03b9 **\u0395\u03c0\u03b9\u03c4\u03c1\u03ad\u03c8\u03c4\u03b5** \u03c4\u03b7\u03bd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7 \u03c3\u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf \u0391\u03bc\u03c6\u03b9\u03ba\u03bb\u03b9\u03bc\u03b1\u03c4\u03b9\u03ba\u03cc \u03ba\u03b1\u03b9, \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03b5\u03c0\u03b9\u03c3\u03c4\u03c1\u03ad\u03c8\u03c4\u03b5 \u03ba\u03b1\u03b9 \u03c0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 **\u03a5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae** \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9.\n(\u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03b7 \u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03b5\u03c0\u03b9\u03c3\u03c4\u03c1\u03bf\u03c6\u03ae\u03c2 \u03ba\u03bb\u03ae\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 {cb_url})", + "title": "\u0388\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 Ambiclimate" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/atag/translations/el.json b/homeassistant/components/atag/translations/el.json index e00e4c23097d4b..60fd251d0a55ff 100644 --- a/homeassistant/components/atag/translations/el.json +++ b/homeassistant/components/atag/translations/el.json @@ -2,6 +2,11 @@ "config": { "error": { "unauthorized": "\u0397 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7 \u03b1\u03c0\u03bf\u03c1\u03c1\u03af\u03c6\u03b8\u03b7\u03ba\u03b5, \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b3\u03b9\u03b1 \u03b1\u03af\u03c4\u03b7\u03bc\u03b1 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2" + }, + "step": { + "user": { + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" + } } } } \ No newline at end of file diff --git a/homeassistant/components/aussie_broadband/translations/nl.json b/homeassistant/components/aussie_broadband/translations/nl.json index b7340913f78017..f4a924d02dbe32 100644 --- a/homeassistant/components/aussie_broadband/translations/nl.json +++ b/homeassistant/components/aussie_broadband/translations/nl.json @@ -3,18 +3,33 @@ "abort": { "no_services_found": "Er zijn geen services gevonden voor dit account" }, + "error": { + "cannot_connect": "Kan geen verbinding maken" + }, "step": { "reauth": { + "data": { + "password": "Wachtwoord" + }, "description": "Update wachtwoord voor {username}" }, "service": { "data": { "services": "Services" } + }, + "user": { + "data": { + "password": "Wachtwoord" + } } } }, "options": { + "abort": { + "cannot_connect": "Kan geen verbinding maken", + "unknown": "Onverwachte fout" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/aussie_broadband/translations/sv.json b/homeassistant/components/aussie_broadband/translations/sv.json new file mode 100644 index 00000000000000..a82ed912c19db1 --- /dev/null +++ b/homeassistant/components/aussie_broadband/translations/sv.json @@ -0,0 +1,50 @@ +{ + "config": { + "abort": { + "already_configured": "Konto har redan konfigurerats", + "no_services_found": "Inga tj\u00e4nster hittades f\u00f6r detta konto", + "reauth_successful": "\u00c5terautentisering lyckades" + }, + "error": { + "cannot_connect": "Det gick inte att ansluta.", + "invalid_auth": "Ogiltig autentisering", + "unknown": "Ov\u00e4ntat fel" + }, + "step": { + "reauth": { + "data": { + "password": "L\u00f6senord" + }, + "description": "Uppdatera l\u00f6senord f\u00f6r {username}", + "title": "\u00c5terautentisera integration" + }, + "service": { + "data": { + "services": "Tj\u00e4nster" + }, + "title": "Valda Tj\u00e4nster" + }, + "user": { + "data": { + "password": "L\u00f6senord", + "username": "Anv\u00e4ndarnamn" + } + } + } + }, + "options": { + "abort": { + "cannot_connect": "Det gick inte att ansluta.", + "invalid_auth": "Ogiltig autentisering", + "unknown": "Ov\u00e4ntat fel" + }, + "step": { + "init": { + "data": { + "services": "Tj\u00e4nster" + }, + "title": "Valda Tj\u00e4nster" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bsblan/translations/el.json b/homeassistant/components/bsblan/translations/el.json index 04b238a916d221..02c3aa97f4636b 100644 --- a/homeassistant/components/bsblan/translations/el.json +++ b/homeassistant/components/bsblan/translations/el.json @@ -2,6 +2,12 @@ "config": { "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "step": { + "user": { + "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae BSB-Lan \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Home Assistant.", + "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae BSB-Lan" + } } } } \ No newline at end of file diff --git a/homeassistant/components/bsblan/translations/nl.json b/homeassistant/components/bsblan/translations/nl.json index e07f6bc25f0493..742aaa9f842a9a 100644 --- a/homeassistant/components/bsblan/translations/nl.json +++ b/homeassistant/components/bsblan/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd", + "cannot_connect": "Kan geen verbinding maken" }, "error": { "cannot_connect": "Kan geen verbinding maken" diff --git a/homeassistant/components/bsblan/translations/sv.json b/homeassistant/components/bsblan/translations/sv.json new file mode 100644 index 00000000000000..46631acc69a79d --- /dev/null +++ b/homeassistant/components/bsblan/translations/sv.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cannot_connect": "Det gick inte att ansluta." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/sensor.nl.json b/homeassistant/components/climacell/translations/sensor.nl.json index bd47b20aa7d23e..b3b09105941b63 100644 --- a/homeassistant/components/climacell/translations/sensor.nl.json +++ b/homeassistant/components/climacell/translations/sensor.nl.json @@ -2,12 +2,22 @@ "state": { "climacell__health_concern": { "hazardous": "Gevaarlijk", + "moderate": "Gematigd", "unhealthy": "Ongezond", "unhealthy_for_sensitive_groups": "Ongezond voor gevoelige groepen", "very_unhealthy": "Heel ongezond" }, "climacell__pollen_index": { - "none": "Geen" + "none": "Geen", + "very_high": "Zeer Hoog", + "very_low": "Zeer Laag" + }, + "climacell__precipitation_type": { + "freezing_rain": "IJzel", + "ice_pellets": "Hagel", + "none": "Geen", + "rain": "Regen", + "snow": "Sneeuw" } } } \ No newline at end of file diff --git a/homeassistant/components/co2signal/translations/el.json b/homeassistant/components/co2signal/translations/el.json new file mode 100644 index 00000000000000..7defcb9708ea38 --- /dev/null +++ b/homeassistant/components/co2signal/translations/el.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "api_ratelimit": "\u03a5\u03c0\u03ad\u03c1\u03b2\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03bf\u03c1\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 API" + }, + "error": { + "api_ratelimit": "\u03a5\u03c0\u03ad\u03c1\u03b2\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03bf\u03c1\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 API" + }, + "step": { + "country": { + "data": { + "country_code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c7\u03ce\u03c1\u03b1\u03c2" + } + }, + "user": { + "data": { + "location": "\u039b\u03ae\u03c8\u03b7 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd \u03b3\u03b9\u03b1" + }, + "description": "\u0395\u03c0\u03b9\u03c3\u03ba\u03b5\u03c6\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://co2signal.com/ \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b6\u03b7\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/coinbase/translations/el.json b/homeassistant/components/coinbase/translations/el.json index c7a90c580ea5f3..312ef7d430d857 100644 --- a/homeassistant/components/coinbase/translations/el.json +++ b/homeassistant/components/coinbase/translations/el.json @@ -8,6 +8,13 @@ "error": { "currency_unavailable": "\u0388\u03bd\u03b1 \u03ae \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b1 \u03b6\u03b7\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b1 \u03c5\u03c0\u03cc\u03bb\u03bf\u03b9\u03c0\u03b1 \u03bd\u03bf\u03bc\u03b9\u03c3\u03bc\u03ac\u03c4\u03c9\u03bd \u03b4\u03b5\u03bd \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf API \u03c4\u03b7\u03c2 Coinbase.", "exchange_rate_unavailable": "\u039c\u03af\u03b1 \u03ae \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b9\u03c2 \u03b6\u03b7\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b5\u03c2 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03bc\u03b1\u03c4\u03b9\u03ba\u03ad\u03c2 \u03b9\u03c3\u03bf\u03c4\u03b9\u03bc\u03af\u03b5\u03c2 \u03b4\u03b5\u03bd \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Coinbase." + }, + "step": { + "init": { + "data": { + "exchange_base": "\u0392\u03b1\u03c3\u03b9\u03ba\u03cc \u03bd\u03cc\u03bc\u03b9\u03c3\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03c4\u03bf\u03c5\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b5\u03c2 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03bc\u03b1\u03c4\u03b9\u03ba\u03ce\u03bd \u03b9\u03c3\u03bf\u03c4\u03b9\u03bc\u03b9\u03ce\u03bd." + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/coinbase/translations/it.json b/homeassistant/components/coinbase/translations/it.json index e0bd3a56842166..ecc0d93e747cb4 100644 --- a/homeassistant/components/coinbase/translations/it.json +++ b/homeassistant/components/coinbase/translations/it.json @@ -25,7 +25,9 @@ }, "options": { "error": { + "currency_unavailable": "Uno o pi\u00f9 dei saldi in valuta richiesti non sono forniti dalla tua API Coinbase.", "currency_unavaliable": "Uno o pi\u00f9 saldi in valuta richiesti non sono forniti dalla tua API Coinbase.", + "exchange_rate_unavailable": "Uno o pi\u00f9 dei tassi di cambio richiesti non sono forniti da Coinbase.", "exchange_rate_unavaliable": "Uno o pi\u00f9 dei tassi di cambio richiesti non sono forniti da Coinbase.", "unknown": "Errore imprevisto" }, diff --git a/homeassistant/components/coinbase/translations/sv.json b/homeassistant/components/coinbase/translations/sv.json new file mode 100644 index 00000000000000..5610f0c79fd654 --- /dev/null +++ b/homeassistant/components/coinbase/translations/sv.json @@ -0,0 +1,8 @@ +{ + "options": { + "error": { + "currency_unavailable": "En eller flera av de beg\u00e4rda valutasaldona tillhandah\u00e5lls inte av ditt Coinbase API.", + "exchange_rate_unavailable": "En eller flera av de beg\u00e4rda v\u00e4xelkurserna tillhandah\u00e5lls inte av Coinbase." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/coronavirus/translations/el.json b/homeassistant/components/coronavirus/translations/el.json index c68ccd9e006a74..f7ef9b4844957d 100644 --- a/homeassistant/components/coronavirus/translations/el.json +++ b/homeassistant/components/coronavirus/translations/el.json @@ -4,7 +4,8 @@ "user": { "data": { "country": "\u03a7\u03ce\u03c1\u03b1" - } + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c7\u03ce\u03c1\u03b1 \u03b3\u03b9\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7" } } } diff --git a/homeassistant/components/cpuspeed/translations/bg.json b/homeassistant/components/cpuspeed/translations/bg.json index 500891c40a68db..fe7f44123e9f71 100644 --- a/homeassistant/components/cpuspeed/translations/bg.json +++ b/homeassistant/components/cpuspeed/translations/bg.json @@ -3,6 +3,12 @@ "abort": { "alread_configured": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f.", "already_configured": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." + }, + "step": { + "user": { + "title": "\u0421\u043a\u043e\u0440\u043e\u0441\u0442 \u043d\u0430 CPU" + } } - } + }, + "title": "\u0421\u043a\u043e\u0440\u043e\u0441\u0442 \u043d\u0430 CPU" } \ No newline at end of file diff --git a/homeassistant/components/crownstone/translations/el.json b/homeassistant/components/crownstone/translations/el.json new file mode 100644 index 00000000000000..35033cd1e59080 --- /dev/null +++ b/homeassistant/components/crownstone/translations/el.json @@ -0,0 +1,49 @@ +{ + "config": { + "abort": { + "usb_setup_complete": "\u039f\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03b7 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 Crownstone USB.", + "usb_setup_unsuccessful": "\u0397 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 Crownstone USB \u03ae\u03c4\u03b1\u03bd \u03b1\u03bd\u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2." + }, + "error": { + "account_not_verified": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03c5\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03bc\u03ad\u03c3\u03c9 \u03c4\u03bf\u03c5 email \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Crownstone." + }, + "step": { + "usb_config": { + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1 \u03c4\u03bf\u03c5 dongle USB \u03c4\u03bf\u03c5 Crownstone \u03ae \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \"Don't use USB\" (\u039c\u03b7 \u03c7\u03c1\u03ae\u03c3\u03b7 USB), \u03b1\u03bd \u03b4\u03b5\u03bd \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03b3\u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 dongle USB.\n\n\u0391\u03bd\u03b1\u03b6\u03b7\u03c4\u03ae\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bc\u03b5 VID 10C4 \u03ba\u03b1\u03b9 PID EA60.", + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Crownstone USB dongle" + }, + "usb_manual_config": { + "description": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae\u03c2 \u03b5\u03bd\u03cc\u03c2 dongle USB Crownstone.", + "title": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae\u03c2 USB dongle Crownstone" + }, + "usb_sphere_config": { + "data": { + "usb_sphere": "Crownstone Sphere" + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 Crownstone Sphere \u03cc\u03c0\u03bf\u03c5 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03c4\u03bf USB.", + "title": "Crownstone USB Sphere" + }, + "user": { + "title": "\u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 Crownstone" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "usb_sphere_option": "Crownstone Sphere \u03cc\u03c0\u03bf\u03c5 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03c4\u03bf USB", + "use_usb_option": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03ad\u03bd\u03b1 Crownstone USB dongle \u03b3\u03b9\u03b1 \u03c4\u03bf\u03c0\u03b9\u03ba\u03ae \u03bc\u03b5\u03c4\u03ac\u03b4\u03bf\u03c3\u03b7 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd" + } + }, + "usb_config": { + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1 \u03c4\u03bf\u03c5 Crownstone USB dongle.\n\n\u0391\u03bd\u03b1\u03b6\u03b7\u03c4\u03ae\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bc\u03b5 VID 10C4 \u03ba\u03b1\u03b9 PID EA60.", + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Crownstone USB dongle" + }, + "usb_config_option": { + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1 \u03c4\u03bf\u03c5 Crownstone USB dongle.\n\n\u0391\u03bd\u03b1\u03b6\u03b7\u03c4\u03ae\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bc\u03b5 VID 10C4 \u03ba\u03b1\u03b9 PID EA60.", + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Crownstone USB dongle" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/devolo_home_control/translations/el.json b/homeassistant/components/devolo_home_control/translations/el.json new file mode 100644 index 00000000000000..6ab4acc7e59db3 --- /dev/null +++ b/homeassistant/components/devolo_home_control/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "mydevolo_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c4\u03bf\u03c5 mydevolo" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/translations/ca.json b/homeassistant/components/dialogflow/translations/ca.json index dad18be0f6a0f5..c59076ec57dae2 100644 --- a/homeassistant/components/dialogflow/translations/ca.json +++ b/homeassistant/components/dialogflow/translations/ca.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "No connectat a Home Assistant Cloud.", "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3.", "webhook_not_internet_accessible": "La teva inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per poder rebre missatges webhook." }, diff --git a/homeassistant/components/dialogflow/translations/en.json b/homeassistant/components/dialogflow/translations/en.json index 31b7f1f8880bbc..9c8157ce06db3a 100644 --- a/homeassistant/components/dialogflow/translations/en.json +++ b/homeassistant/components/dialogflow/translations/en.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Not connected to Home Assistant Cloud.", "single_instance_allowed": "Already configured. Only a single configuration possible.", "webhook_not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive webhook messages." }, diff --git a/homeassistant/components/dialogflow/translations/it.json b/homeassistant/components/dialogflow/translations/it.json index b7b04c78863908..5f8c44d358b170 100644 --- a/homeassistant/components/dialogflow/translations/it.json +++ b/homeassistant/components/dialogflow/translations/it.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Non connesso a Home Assistant Cloud.", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione.", "webhook_not_internet_accessible": "L'istanza di Home Assistant deve essere accessibile da Internet per ricevere messaggi webhook." }, diff --git a/homeassistant/components/dialogflow/translations/tr.json b/homeassistant/components/dialogflow/translations/tr.json index 520424e434fe27..27378ab3284226 100644 --- a/homeassistant/components/dialogflow/translations/tr.json +++ b/homeassistant/components/dialogflow/translations/tr.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Home Assistant Cloud'a ba\u011fl\u0131 de\u011fil.", "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", "webhook_not_internet_accessible": "Webhook mesajlar\u0131n\u0131 alabilmek i\u00e7in Home Assistant \u00f6rne\u011finize internetten eri\u015filebilmelidir." }, diff --git a/homeassistant/components/directv/translations/el.json b/homeassistant/components/directv/translations/el.json new file mode 100644 index 00000000000000..6c5446fe9f8833 --- /dev/null +++ b/homeassistant/components/directv/translations/el.json @@ -0,0 +1,10 @@ +{ + "config": { + "flow_title": "{name}", + "step": { + "ssdp_confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name};" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dlna_dmr/translations/el.json b/homeassistant/components/dlna_dmr/translations/el.json new file mode 100644 index 00000000000000..9d11ff838f4a3f --- /dev/null +++ b/homeassistant/components/dlna_dmr/translations/el.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "could_not_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03bc\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae DLNA", + "discovery_error": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7\u03c2 \u03bc\u03b9\u03b1\u03c2 \u03b1\u03bd\u03c4\u03af\u03c3\u03c4\u03bf\u03b9\u03c7\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 DLNA", + "incomplete_config": "\u0391\u03c0\u03cc \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03bb\u03b5\u03af\u03c0\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7 \u03bc\u03b5\u03c4\u03b1\u03b2\u03bb\u03b7\u03c4\u03ae", + "non_unique_id": "\u0392\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ad\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03bc\u03b5 \u03c4\u03bf \u03af\u03b4\u03b9\u03bf \u03bc\u03bf\u03bd\u03b1\u03b4\u03b9\u03ba\u03cc \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc", + "not_dmr": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03bd\u03b1\u03c2 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03bf\u03c2 \u03c8\u03b7\u03c6\u03b9\u03b1\u03ba\u03cc\u03c2 \u03b1\u03bd\u03b1\u03bc\u03b5\u03c4\u03b1\u03b4\u03cc\u03c4\u03b7\u03c2 \u03c0\u03bf\u03bb\u03c5\u03bc\u03ad\u03c3\u03c9\u03bd" + }, + "error": { + "could_not_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03bc\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae DLNA", + "not_dmr": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03bd\u03b1\u03c2 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03bf\u03c2 \u03c8\u03b7\u03c6\u03b9\u03b1\u03ba\u03cc\u03c2 \u03b1\u03bd\u03b1\u03bc\u03b5\u03c4\u03b1\u03b4\u03cc\u03c4\u03b7\u03c2 \u03c0\u03bf\u03bb\u03c5\u03bc\u03ad\u03c3\u03c9\u03bd" + }, + "flow_title": "{name}" + } +} \ No newline at end of file diff --git a/homeassistant/components/dnsip/translations/el.json b/homeassistant/components/dnsip/translations/el.json index 7cdf7f885e7881..0e90341ef03be7 100644 --- a/homeassistant/components/dnsip/translations/el.json +++ b/homeassistant/components/dnsip/translations/el.json @@ -2,6 +2,25 @@ "config": { "error": { "invalid_hostname": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae" + }, + "step": { + "user": { + "data": { + "hostname": "\u03a4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03b3\u03b9\u03b1 \u03c4\u03bf \u03bf\u03c0\u03bf\u03af\u03bf \u03b8\u03b1 \u03b5\u03ba\u03c4\u03b5\u03bb\u03b5\u03c3\u03c4\u03b5\u03af \u03c4\u03bf \u03b5\u03c1\u03ce\u03c4\u03b7\u03bc\u03b1 DNS" + } + } + } + }, + "options": { + "error": { + "invalid_resolver": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03b5\u03c0\u03b9\u03bb\u03cd\u03c4\u03b7" + }, + "step": { + "init": { + "data": { + "resolver": "\u0395\u03c0\u03b9\u03bb\u03cd\u03c4\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7 IPV4" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/doorbird/translations/el.json b/homeassistant/components/doorbird/translations/el.json index b01bd3d8e8ed02..edb649341c8aee 100644 --- a/homeassistant/components/doorbird/translations/el.json +++ b/homeassistant/components/doorbird/translations/el.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "link_local_address": "\u039f\u03b9 \u03c4\u03bf\u03c0\u03b9\u03ba\u03ad\u03c2 \u03b4\u03b9\u03b5\u03c5\u03b8\u03cd\u03bd\u03c3\u03b5\u03b9\u03c2 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03bc\u03bf\u03c5 \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03bf\u03bd\u03c4\u03b1\u03b9", + "not_doorbird_device": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 DoorBird" + }, "step": { "user": { "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf DoorBird" diff --git a/homeassistant/components/eafm/translations/el.json b/homeassistant/components/eafm/translations/el.json new file mode 100644 index 00000000000000..9bae4cf98277d5 --- /dev/null +++ b/homeassistant/components/eafm/translations/el.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "no_stations": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03af \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7\u03c2 \u03c0\u03bb\u03b7\u03bc\u03bc\u03c5\u03c1\u03ce\u03bd." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elkm1/translations/el.json b/homeassistant/components/elkm1/translations/el.json index 4ae9a2d228af96..5bb2846243a125 100644 --- a/homeassistant/components/elkm1/translations/el.json +++ b/homeassistant/components/elkm1/translations/el.json @@ -2,6 +2,12 @@ "config": { "step": { "user": { + "data": { + "address": "\u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03ae \u03bf \u03c4\u03bf\u03bc\u03ad\u03b1\u03c2 \u03ae \u03b7 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1 \u03b5\u03ac\u03bd \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03b3\u03af\u03bd\u03b5\u03c4\u03b1\u03b9 \u03bc\u03ad\u03c3\u03c9 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2.", + "prefix": "\u0388\u03bd\u03b1 \u03bc\u03bf\u03bd\u03b1\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b1\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03bc\u03cc\u03bd\u03bf \u03ad\u03bd\u03b1 ElkM1).", + "protocol": "\u03a0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf", + "temperature_unit": "\u0397 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03c4\u03bf ElkM1." + }, "description": "\u0397 \u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae 'address[:port]' \u03b3\u03b9\u03b1 'secure' \u03ba\u03b1\u03b9 'non-secure'. \u03a0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3\u03bc\u03b1: '192.168.1.1'. \u0397 \u03b8\u03cd\u03c1\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ae \u03ba\u03b1\u03b9 \u03b7 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03c4\u03b9\u03bc\u03ae \u03b5\u03af\u03bd\u03b1\u03b9 2101 \u03b3\u03b9\u03b1 '\u03bc\u03b7 \u03b1\u03c3\u03c6\u03b1\u03bb\u03ae' \u03ba\u03b1\u03b9 2601 \u03b3\u03b9\u03b1 '\u03b1\u03c3\u03c6\u03b1\u03bb\u03ae'. \u0393\u03b9\u03b1 \u03c4\u03bf \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc \u03c0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf, \u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae 'tty[:baud]'. \u03a0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3\u03bc\u03b1: '/dev/ttyS1'. \u03a4\u03bf baud \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03ba\u03b1\u03b9 \u03b7 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03b5\u03af\u03bd\u03b1\u03b9 115200.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf Elk-M1 Control" } diff --git a/homeassistant/components/esphome/translations/sv.json b/homeassistant/components/esphome/translations/sv.json index d979ff4821c0e4..40fbbf86bc627f 100644 --- a/homeassistant/components/esphome/translations/sv.json +++ b/homeassistant/components/esphome/translations/sv.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "ESP \u00e4r redan konfigurerad" + "already_configured": "ESP \u00e4r redan konfigurerad", + "reauth_successful": "\u00c5terautentisering lyckades" }, "error": { "connection_error": "Kan inte ansluta till ESP. Se till att din YAML-fil inneh\u00e5ller en 'api:' line.", diff --git a/homeassistant/components/fireservicerota/translations/el.json b/homeassistant/components/fireservicerota/translations/el.json index bf11ee5dd0017c..64eaa4c7733f50 100644 --- a/homeassistant/components/fireservicerota/translations/el.json +++ b/homeassistant/components/fireservicerota/translations/el.json @@ -1,6 +1,9 @@ { "config": { "step": { + "reauth": { + "description": "\u03a4\u03b1 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03ac \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ad\u03b3\u03b9\u03bd\u03b1\u03bd \u03ac\u03ba\u03c5\u03c1\u03b1, \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c4\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac." + }, "user": { "data": { "url": "\u0399\u03c3\u03c4\u03bf\u03c3\u03b5\u03bb\u03af\u03b4\u03b1" diff --git a/homeassistant/components/flume/translations/el.json b/homeassistant/components/flume/translations/el.json index fca38446c23a28..301be92c3b1dd6 100644 --- a/homeassistant/components/flume/translations/el.json +++ b/homeassistant/components/flume/translations/el.json @@ -6,6 +6,10 @@ "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Flume" }, "user": { + "data": { + "client_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03bb\u03ac\u03c4\u03b7", + "client_secret": "\u039c\u03c5\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03bb\u03ac\u03c4\u03b7" + }, "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03c0\u03bf\u03ba\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7 \u03c3\u03c4\u03bf \u03c0\u03c1\u03bf\u03c3\u03c9\u03c0\u03b9\u03ba\u03cc API \u03c4\u03bf\u03c5 Flume, \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b6\u03b7\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 'Client ID' \u03ba\u03b1\u03b9 \u03ad\u03bd\u03b1 'Client Secret' \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://portal.flumetech.com/settings#token.", "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 Flume" } diff --git a/homeassistant/components/flunearyou/translations/el.json b/homeassistant/components/flunearyou/translations/el.json new file mode 100644 index 00000000000000..a9437c09ba4e5f --- /dev/null +++ b/homeassistant/components/flunearyou/translations/el.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ce\u03bd \u03b2\u03ac\u03c3\u03b5\u03b9 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 CDC \u03b3\u03b9\u03b1 \u03ad\u03bd\u03b1 \u03b6\u03b5\u03cd\u03b3\u03bf\u03c2 \u03c3\u03c5\u03bd\u03c4\u03b5\u03c4\u03b1\u03b3\u03bc\u03ad\u03bd\u03c9\u03bd." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/el.json b/homeassistant/components/forked_daapd/translations/el.json index a1e23fa41459e6..43b3557af3ac96 100644 --- a/homeassistant/components/forked_daapd/translations/el.json +++ b/homeassistant/components/forked_daapd/translations/el.json @@ -1,4 +1,25 @@ { + "config": { + "abort": { + "not_forked_daapd": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 forked-daapd." + }, + "error": { + "websocket_not_enabled": "\u039f forked-daapd \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 websocket \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf\u03c2.", + "wrong_host_or_port": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03b8\u03cd\u03c1\u03b1.", + "wrong_password": "\u0395\u03c3\u03c6\u03b1\u03bb\u03bc\u03ad\u03bd\u03bf\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2.", + "wrong_server_type": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 forked-daapd \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03ad\u03bd\u03b1 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae forked-daapd \u03bc\u03b5 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 >= 27.0." + }, + "flow_title": "{name} ({host})", + "step": { + "user": { + "data": { + "name": "\u03a6\u03b9\u03bb\u03b9\u03ba\u03cc \u03cc\u03bd\u03bf\u03bc\u03b1", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 API (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03cc \u03b5\u03ac\u03bd \u03b4\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2)", + "port": "\u0398\u03cd\u03c1\u03b1 API" + } + } + } + }, "options": { "step": { "init": { diff --git a/homeassistant/components/freebox/translations/el.json b/homeassistant/components/freebox/translations/el.json new file mode 100644 index 00000000000000..42fc274e0a816e --- /dev/null +++ b/homeassistant/components/freebox/translations/el.json @@ -0,0 +1,13 @@ +{ + "config": { + "error": { + "register_failed": "\u0397 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac" + }, + "step": { + "link": { + "description": "\u039a\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \"\u03a5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae\" \u03ba\u03b1\u03b9, \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03b1\u03b3\u03b3\u03af\u03be\u03c4\u03b5 \u03c4\u03bf \u03b4\u03b5\u03be\u03af \u03b2\u03ad\u03bb\u03bf\u03c2 \u03c3\u03c4\u03bf \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c7\u03c9\u03c1\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Freebox \u03c3\u03c4\u03bf Home Assistant.\n\n![\u0398\u03ad\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03ba\u03bf\u03c5\u03bc\u03c0\u03b9\u03bf\u03cd \u03c3\u03c4\u03bf \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae](/static/images/config_freebox.png)", + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae Freebox" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/el.json b/homeassistant/components/fritzbox/translations/el.json index c5524f10790437..f3bd4c2d4c0006 100644 --- a/homeassistant/components/fritzbox/translations/el.json +++ b/homeassistant/components/fritzbox/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "not_supported": "\u03a3\u03c5\u03bd\u03b4\u03ad\u03b8\u03b7\u03ba\u03b5 \u03c3\u03c4\u03bf AVM FRITZ!Box \u03b1\u03bb\u03bb\u03ac \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b5\u03bb\u03ad\u03b3\u03be\u03b5\u03b9 \u03c4\u03b9\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 Smart Home." + }, "flow_title": "{name}", "step": { "confirm": { @@ -7,6 +10,9 @@ }, "reauth_confirm": { "description": "\u0395\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {name}." + }, + "user": { + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf AVM FRITZ!Box." } } } diff --git a/homeassistant/components/gdacs/translations/el.json b/homeassistant/components/gdacs/translations/el.json index 44917a2ca182f7..215801cc7c5887 100644 --- a/homeassistant/components/gdacs/translations/el.json +++ b/homeassistant/components/gdacs/translations/el.json @@ -4,7 +4,8 @@ "user": { "data": { "radius": "\u0391\u03ba\u03c4\u03af\u03bd\u03b1" - } + }, + "title": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c4\u03bf\u03c5 \u03c6\u03af\u03bb\u03c4\u03c1\u03bf\u03c5 \u03c3\u03b1\u03c2." } } } diff --git a/homeassistant/components/geofency/translations/ca.json b/homeassistant/components/geofency/translations/ca.json index a956bf5ce62aa5..d2bb9582a60ac1 100644 --- a/homeassistant/components/geofency/translations/ca.json +++ b/homeassistant/components/geofency/translations/ca.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "No connectat a Home Assistant Cloud.", "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3.", "webhook_not_internet_accessible": "La teva inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per poder rebre missatges webhook." }, diff --git a/homeassistant/components/geofency/translations/en.json b/homeassistant/components/geofency/translations/en.json index db7a9e684f933c..5b97afb3bcc2b3 100644 --- a/homeassistant/components/geofency/translations/en.json +++ b/homeassistant/components/geofency/translations/en.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Not connected to Home Assistant Cloud.", "single_instance_allowed": "Already configured. Only a single configuration possible.", "webhook_not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive webhook messages." }, diff --git a/homeassistant/components/geofency/translations/it.json b/homeassistant/components/geofency/translations/it.json index a72adf25c5017a..07647dc6df321e 100644 --- a/homeassistant/components/geofency/translations/it.json +++ b/homeassistant/components/geofency/translations/it.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Non connesso a Home Assistant Cloud.", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione.", "webhook_not_internet_accessible": "L'istanza di Home Assistant deve essere accessibile da Internet per ricevere messaggi webhook." }, diff --git a/homeassistant/components/geofency/translations/tr.json b/homeassistant/components/geofency/translations/tr.json index 4cd04c64d7b3e0..8cd04ad16c79f9 100644 --- a/homeassistant/components/geofency/translations/tr.json +++ b/homeassistant/components/geofency/translations/tr.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Home Assistant Cloud'a ba\u011fl\u0131 de\u011fil.", "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", "webhook_not_internet_accessible": "Webhook mesajlar\u0131n\u0131 alabilmek i\u00e7in Home Assistant \u00f6rne\u011finize internetten eri\u015filebilmelidir." }, diff --git a/homeassistant/components/gpslogger/translations/ca.json b/homeassistant/components/gpslogger/translations/ca.json index 49878948880b21..e095a8bdb14292 100644 --- a/homeassistant/components/gpslogger/translations/ca.json +++ b/homeassistant/components/gpslogger/translations/ca.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "No connectat a Home Assistant Cloud.", "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3.", "webhook_not_internet_accessible": "La teva inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per poder rebre missatges webhook." }, diff --git a/homeassistant/components/gpslogger/translations/en.json b/homeassistant/components/gpslogger/translations/en.json index 6d280a4c0383d7..24949d0933a0f7 100644 --- a/homeassistant/components/gpslogger/translations/en.json +++ b/homeassistant/components/gpslogger/translations/en.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Not connected to Home Assistant Cloud.", "single_instance_allowed": "Already configured. Only a single configuration possible.", "webhook_not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive webhook messages." }, diff --git a/homeassistant/components/gpslogger/translations/it.json b/homeassistant/components/gpslogger/translations/it.json index 7db05dfb8ee264..235a66c8993fdf 100644 --- a/homeassistant/components/gpslogger/translations/it.json +++ b/homeassistant/components/gpslogger/translations/it.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Non connesso a Home Assistant Cloud.", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione.", "webhook_not_internet_accessible": "L'istanza di Home Assistant deve essere accessibile da Internet per ricevere messaggi webhook." }, diff --git a/homeassistant/components/gpslogger/translations/tr.json b/homeassistant/components/gpslogger/translations/tr.json index ef10b98c5df0b3..dc14b0d4011b45 100644 --- a/homeassistant/components/gpslogger/translations/tr.json +++ b/homeassistant/components/gpslogger/translations/tr.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Home Assistant Cloud'a ba\u011fl\u0131 de\u011fil.", "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", "webhook_not_internet_accessible": "Webhook mesajlar\u0131n\u0131 alabilmek i\u00e7in Home Assistant \u00f6rne\u011finize internetten eri\u015filebilmelidir." }, diff --git a/homeassistant/components/hangouts/translations/el.json b/homeassistant/components/hangouts/translations/el.json index 63c8a6ebc43060..b53f8bd127dcf8 100644 --- a/homeassistant/components/hangouts/translations/el.json +++ b/homeassistant/components/hangouts/translations/el.json @@ -13,6 +13,9 @@ "title": "\u03a0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 2 \u03c0\u03b1\u03c1\u03b1\u03b3\u03cc\u03bd\u03c4\u03c9\u03bd" }, "user": { + "data": { + "authorization_code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2 (\u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03bf \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2)" + }, "description": "\u039a\u03b5\u03bd\u03cc", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 Google Hangouts" } diff --git a/homeassistant/components/harmony/translations/el.json b/homeassistant/components/harmony/translations/el.json index 9b31f1615f77ac..d17c5f3a43c0cf 100644 --- a/homeassistant/components/harmony/translations/el.json +++ b/homeassistant/components/harmony/translations/el.json @@ -3,7 +3,25 @@ "flow_title": "{name}", "step": { "link": { - "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ({host});" + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ({host});", + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Logitech Harmony Hub" + }, + "user": { + "data": { + "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03ba\u03cc\u03bc\u03b2\u03bf\u03c5" + }, + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Logitech Harmony Hub" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "activity": "\u0397 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03b4\u03c1\u03b1\u03c3\u03c4\u03b7\u03c1\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03b5\u03ba\u03c4\u03b5\u03bb\u03b5\u03c3\u03c4\u03b5\u03af \u03cc\u03c4\u03b1\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03ba\u03b1\u03bc\u03af\u03b1.", + "delay_secs": "\u0397 \u03ba\u03b1\u03b8\u03c5\u03c3\u03c4\u03ad\u03c1\u03b7\u03c3\u03b7 \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03c4\u03b7\u03c2 \u03b1\u03c0\u03bf\u03c3\u03c4\u03bf\u03bb\u03ae\u03c2 \u03b5\u03bd\u03c4\u03bf\u03bb\u03ce\u03bd." + }, + "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03c9\u03bd \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd \u03c4\u03bf\u03c5 Harmony Hub" } } } diff --git a/homeassistant/components/heos/translations/el.json b/homeassistant/components/heos/translations/el.json new file mode 100644 index 00000000000000..e6c5379e30ed1f --- /dev/null +++ b/homeassistant/components/heos/translations/el.json @@ -0,0 +1,10 @@ +{ + "config": { + "step": { + "user": { + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03bc\u03b9\u03b1\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 Heos (\u03ba\u03b1\u03c4\u03ac \u03c0\u03c1\u03bf\u03c4\u03af\u03bc\u03b7\u03c3\u03b7 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03b7\u03c2 \u03bc\u03b5 \u03ba\u03b1\u03bb\u03ce\u03b4\u03b9\u03bf \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf).", + "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf Heos" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homeassistant/translations/el.json b/homeassistant/components/homeassistant/translations/el.json index 5f2110e4509360..616ad96a86719c 100644 --- a/homeassistant/components/homeassistant/translations/el.json +++ b/homeassistant/components/homeassistant/translations/el.json @@ -10,6 +10,7 @@ "os_version": "\u0388\u03ba\u03b4\u03bf\u03c3\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03b9\u03ba\u03bf\u03cd \u03c3\u03c5\u03c3\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2", "python_version": "\u0388\u03ba\u03b4\u03bf\u03c3\u03b7 Python", "timezone": "\u0396\u03ce\u03bd\u03b7 \u03ce\u03c1\u03b1\u03c2", + "user": "\u03a7\u03c1\u03ae\u03c3\u03c4\u03b7\u03c2", "version": "\u0388\u03ba\u03b4\u03bf\u03c3\u03b7", "virtualenv": "\u0395\u03b9\u03ba\u03bf\u03bd\u03b9\u03ba\u03cc \u03c0\u03b5\u03c1\u03b9\u03b2\u03ac\u03bb\u03bb\u03bf\u03bd" } diff --git a/homeassistant/components/homekit/translations/el.json b/homeassistant/components/homekit/translations/el.json index 24052b026bdde0..add7d39e39a7e0 100644 --- a/homeassistant/components/homekit/translations/el.json +++ b/homeassistant/components/homekit/translations/el.json @@ -1,4 +1,22 @@ { + "config": { + "abort": { + "port_name_in_use": "\u0388\u03bd\u03b1 \u03b5\u03be\u03ac\u03c1\u03c4\u03b7\u03bc\u03b1 \u03ae \u03bc\u03b9\u03b1 \u03b3\u03ad\u03c6\u03c5\u03c1\u03b1 \u03bc\u03b5 \u03c4\u03bf \u03af\u03b4\u03b9\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ae \u03b8\u03cd\u03c1\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af." + }, + "step": { + "pairing": { + "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03ce\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ce\u03bd\u03c4\u03b1\u03c2 \u03c4\u03b9\u03c2 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2 \u03c3\u03c4\u03b9\u03c2 \"\u0395\u03b9\u03b4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9\u03c2\" \u03c3\u03c4\u03b7\u03bd \u03b5\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \"\u03a3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7 HomeKit\".", + "title": "\u03a3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7 HomeKit" + }, + "user": { + "data": { + "include_domains": "\u03a4\u03bf\u03bc\u03b5\u03af\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd" + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf\u03c5\u03c2 \u03c4\u03bf\u03bc\u03b5\u03af\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd. \u0398\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd \u03cc\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03b5\u03c2 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03c3\u03c4\u03bf\u03bd \u03c4\u03bf\u03bc\u03ad\u03b1, \u03b5\u03ba\u03c4\u03cc\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b9\u03c2 \u03ba\u03b1\u03c4\u03b7\u03b3\u03bf\u03c1\u03b9\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b5\u03c2 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2. \u0398\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03b7\u03b8\u03b5\u03af \u03bc\u03b9\u03b1 \u03be\u03b5\u03c7\u03c9\u03c1\u03b9\u03c3\u03c4\u03ae \u03c0\u03b5\u03c1\u03af\u03c0\u03c4\u03c9\u03c3\u03b7 HomeKit \u03c3\u03b5 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b1\u03be\u03b5\u03c3\u03bf\u03c5\u03ac\u03c1 \u03b3\u03b9\u03b1 \u03ba\u03ac\u03b8\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2 \u03c0\u03bf\u03bb\u03c5\u03bc\u03ad\u03c3\u03c9\u03bd tv, \u03c4\u03b7\u03bb\u03b5\u03c7\u03b5\u03b9\u03c1\u03b9\u03c3\u03c4\u03ae\u03c1\u03b9\u03bf \u03bc\u03b5 \u03b2\u03ac\u03c3\u03b7 \u03c4\u03b7 \u03b4\u03c1\u03b1\u03c3\u03c4\u03b7\u03c1\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1, \u03ba\u03bb\u03b5\u03b9\u03b4\u03b1\u03c1\u03b9\u03ac \u03ba\u03b1\u03b9 \u03ba\u03ac\u03bc\u03b5\u03c1\u03b1.", + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf\u03bc\u03b5\u03af\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd" + } + } + }, "options": { "step": { "accessory": { @@ -16,6 +34,9 @@ "title": "\u03a0\u03c1\u03bf\u03b7\u03b3\u03bc\u03ad\u03bd\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7" }, "cameras": { + "data": { + "camera_audio": "\u039a\u03ac\u03bc\u03b5\u03c1\u03b5\u03c2 \u03c0\u03bf\u03c5 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03bf\u03c5\u03bd \u03ae\u03c7\u03bf" + }, "description": "\u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03cc\u03bb\u03b5\u03c2 \u03c4\u03b9\u03c2 \u03ba\u03ac\u03bc\u03b5\u03c1\u03b5\u03c2 \u03c0\u03bf\u03c5 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03bf\u03c5\u03bd \u03b5\u03b3\u03b3\u03b5\u03bd\u03b5\u03af\u03c2 \u03c1\u03bf\u03ad\u03c2 H.264. \u0395\u03ac\u03bd \u03b7 \u03ba\u03ac\u03bc\u03b5\u03c1\u03b1 \u03b4\u03b5\u03bd \u03c0\u03b1\u03c1\u03ac\u03b3\u03b5\u03b9 \u03c1\u03bf\u03ae H.264, \u03c4\u03bf \u03c3\u03cd\u03c3\u03c4\u03b7\u03bc\u03b1 \u03b8\u03b1 \u03bc\u03b5\u03c4\u03b1\u03c3\u03c7\u03b7\u03bc\u03b1\u03c4\u03af\u03c3\u03b5\u03b9 \u03c4\u03bf \u03b2\u03af\u03bd\u03c4\u03b5\u03bf \u03c3\u03b5 H.264 \u03b3\u03b9\u03b1 \u03c4\u03bf HomeKit. \u0397 \u03bc\u03b5\u03c4\u03b1\u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03bc\u03b9\u03b1 \u03b1\u03c0\u03bf\u03b4\u03bf\u03c4\u03b9\u03ba\u03ae CPU \u03ba\u03b1\u03b9 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c0\u03af\u03b8\u03b1\u03bd\u03bf \u03bd\u03b1 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03b9 \u03c3\u03b5 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ad\u03c2 \u03bc\u03bf\u03bd\u03ae\u03c2 \u03c0\u03bb\u03b1\u03ba\u03ad\u03c4\u03b1\u03c2.", "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03ba\u03ac\u03bc\u03b5\u03c1\u03b1\u03c2" }, @@ -41,7 +62,13 @@ "data": { "include_exclude_mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03af\u03bb\u03b7\u03c8\u03b7\u03c2", "mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 HomeKit" - } + }, + "description": "\u03a4\u03bf HomeKit \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af \u03ce\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03ba\u03b8\u03ad\u03c3\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03b3\u03ad\u03c6\u03c5\u03c1\u03b1 \u03ae \u03ad\u03bd\u03b1 \u03bc\u03b5\u03bc\u03bf\u03bd\u03c9\u03bc\u03ad\u03bd\u03bf \u03b1\u03be\u03b5\u03c3\u03bf\u03c5\u03ac\u03c1. \u03a3\u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b1\u03be\u03b5\u03c3\u03bf\u03c5\u03ac\u03c1, \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03bc\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1. \u0397 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b1\u03be\u03b5\u03c3\u03bf\u03c5\u03ac\u03c1 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03bf\u03cd\u03bd \u03c3\u03c9\u03c3\u03c4\u03ac \u03bf\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2 \u03c0\u03bf\u03bb\u03c5\u03bc\u03ad\u03c3\u03c9\u03bd \u03bc\u03b5 \u03c4\u03b7\u03bd \u03ba\u03bb\u03ac\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 TV. \u039f\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03c3\u03c4\u03bf \"\u03a4\u03bf\u03bc\u03b5\u03af\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd\" \u03b8\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd \u03c3\u03c4\u03bf HomeKit. \u0398\u03b1 \u03bc\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03b5\u03c4\u03b5 \u03c0\u03bf\u03b9\u03b5\u03c2 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03b8\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03ae \u03b8\u03b1 \u03b5\u03be\u03b1\u03b9\u03c1\u03ad\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c0\u03cc \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7 \u03bb\u03af\u03c3\u03c4\u03b1 \u03c3\u03c4\u03b7\u03bd \u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03b7 \u03bf\u03b8\u03cc\u03bd\u03b7.", + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bc\u03b5\u03af\u03c2." + }, + "yaml": { + "description": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03b5\u03bb\u03ad\u03b3\u03c7\u03b5\u03c4\u03b1\u03b9 \u03bc\u03ad\u03c3\u03c9 YAML", + "title": "\u03a0\u03c1\u03bf\u03c3\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd HomeKit" } } } diff --git a/homeassistant/components/homekit_controller/translations/el.json b/homeassistant/components/homekit_controller/translations/el.json index 55a2f055db0e13..694cb41cf4494d 100644 --- a/homeassistant/components/homekit_controller/translations/el.json +++ b/homeassistant/components/homekit_controller/translations/el.json @@ -1,13 +1,17 @@ { "config": { "abort": { + "accessory_not_found_error": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03b6\u03b5\u03cd\u03be\u03b7\u03c2 \u03ba\u03b1\u03b8\u03ce\u03c2 \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03c0\u03bb\u03ad\u03bf\u03bd \u03bd\u03b1 \u03b2\u03c1\u03b5\u03b8\u03b5\u03af.", "invalid_properties": "\u0391\u03bd\u03b1\u03ba\u03bf\u03b9\u03bd\u03ce\u03b8\u03b7\u03ba\u03b1\u03bd \u03bc\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b5\u03c2 \u03b9\u03b4\u03b9\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03b1\u03c0\u03cc \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae." }, "error": { "insecure_setup_code": "\u039f \u03b6\u03b7\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03bf\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03bd\u03b1\u03c3\u03c6\u03b1\u03bb\u03ae\u03c2 \u03bb\u03cc\u03b3\u03c9 \u03c4\u03b7\u03c2 \u03b1\u03c3\u03ae\u03bc\u03b1\u03bd\u03c4\u03b7\u03c2 \u03c6\u03cd\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5. \u0391\u03c5\u03c4\u03cc \u03c4\u03bf \u03b1\u03be\u03b5\u03c3\u03bf\u03c5\u03ac\u03c1 \u03b4\u03b5\u03bd \u03c0\u03bb\u03b7\u03c1\u03bf\u03af \u03c4\u03b9\u03c2 \u03b2\u03b1\u03c3\u03b9\u03ba\u03ad\u03c2 \u03b1\u03c0\u03b1\u03b9\u03c4\u03ae\u03c3\u03b5\u03b9\u03c2 \u03b1\u03c3\u03c6\u03b1\u03bb\u03b5\u03af\u03b1\u03c2.", + "max_peers_error": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b1\u03c1\u03bd\u03ae\u03b8\u03b7\u03ba\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03b9 \u03b1\u03bd\u03c4\u03b9\u03c3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7 \u03ba\u03b1\u03b8\u03ce\u03c2 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b5\u03bb\u03b5\u03cd\u03b8\u03b5\u03c1\u03bf \u03c7\u03ce\u03c1\u03bf \u03b1\u03c0\u03bf\u03b8\u03ae\u03ba\u03b5\u03c5\u03c3\u03b7\u03c2 \u03b1\u03bd\u03c4\u03b9\u03c3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7\u03c2.", + "pairing_failed": "\u03a0\u03c1\u03bf\u03ad\u03ba\u03c5\u03c8\u03b5 \u03ad\u03bd\u03b1 \u03bc\u03b7 \u03b4\u03b9\u03b1\u03c7\u03b5\u03b9\u03c1\u03af\u03c3\u03b9\u03bc\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03c0\u03ac\u03b8\u03b5\u03b9\u03b1 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7\u03c2 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae. \u039c\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c0\u03c1\u03cc\u03ba\u03b5\u03b9\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c9\u03c1\u03b9\u03bd\u03ae \u03b1\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c3\u03b1\u03c2 \u03bd\u03b1 \u03bc\u03b7\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03b5\u03c0\u03af \u03c4\u03bf\u03c5 \u03c0\u03b1\u03c1\u03cc\u03bd\u03c4\u03bf\u03c2.", "unable_to_pair": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", "unknown_error": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b1\u03bd\u03ad\u03c6\u03b5\u03c1\u03b5 \u03ac\u03b3\u03bd\u03c9\u03c3\u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1. \u0397 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5." }, + "flow_title": "{name}", "step": { "busy_error": { "description": "\u039c\u03b1\u03c4\u03b1\u03b9\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03b6\u03b5\u03cd\u03be\u03b7 \u03c3\u03b5 \u03cc\u03bb\u03bf\u03c5\u03c2 \u03c4\u03bf\u03c5\u03c2 \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ad\u03c2 \u03ae \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ba\u03b1\u03b9, \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7." diff --git a/homeassistant/components/homewizard/translations/bg.json b/homeassistant/components/homewizard/translations/bg.json index 8c2031843f43fe..9ecd510ae99a19 100644 --- a/homeassistant/components/homewizard/translations/bg.json +++ b/homeassistant/components/homewizard/translations/bg.json @@ -2,9 +2,13 @@ "config": { "abort": { "already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e", + "device_not_supported": "\u0422\u043e\u0432\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 \u0441\u0435 \u043f\u043e\u0434\u0434\u044a\u0440\u0436\u0430", "unknown_error": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" }, "step": { + "discovery_confirm": { + "title": "\u041f\u043e\u0442\u0432\u044a\u0440\u0434\u0435\u0442\u0435" + }, "user": { "data": { "ip_address": "IP \u0430\u0434\u0440\u0435\u0441" diff --git a/homeassistant/components/honeywell/translations/el.json b/homeassistant/components/honeywell/translations/el.json new file mode 100644 index 00000000000000..06ba7d17aca639 --- /dev/null +++ b/homeassistant/components/honeywell/translations/el.json @@ -0,0 +1,10 @@ +{ + "config": { + "step": { + "user": { + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b1\u03c4\u03b5 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf mytotalconnectcomfort.com.", + "title": "Honeywell Total Connect Comfort (\u0397\u03a0\u0391)" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huawei_lte/translations/el.json b/homeassistant/components/huawei_lte/translations/el.json index f5e6f8763c9924..55ab5b1d17dcec 100644 --- a/homeassistant/components/huawei_lte/translations/el.json +++ b/homeassistant/components/huawei_lte/translations/el.json @@ -24,7 +24,8 @@ "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1\u03c2 \u03b5\u03b9\u03b4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2 (\u03b7 \u03b1\u03bb\u03bb\u03b1\u03b3\u03ae \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7)", "recipient": "\u03a0\u03b1\u03c1\u03b1\u03bb\u03ae\u03c0\u03c4\u03b5\u03c2 \u03b5\u03b9\u03b4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c9\u03bd SMS", "track_new_devices": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03bd\u03ad\u03c9\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd", - "track_wired_clients": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03c0\u03b5\u03bb\u03b1\u03c4\u03ce\u03bd \u03b5\u03bd\u03c3\u03cd\u03c1\u03bc\u03b1\u03c4\u03bf\u03c5 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5" + "track_wired_clients": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03c0\u03b5\u03bb\u03b1\u03c4\u03ce\u03bd \u03b5\u03bd\u03c3\u03cd\u03c1\u03bc\u03b1\u03c4\u03bf\u03c5 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5", + "unauthenticated_mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c7\u03c9\u03c1\u03af\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 (\u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b1\u03bb\u03bb\u03b1\u03b3\u03ae \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b5\u03c0\u03b1\u03bd\u03b1\u03c6\u03cc\u03c1\u03c4\u03c9\u03c3\u03b7)" } } } diff --git a/homeassistant/components/hunterdouglas_powerview/translations/el.json b/homeassistant/components/hunterdouglas_powerview/translations/el.json new file mode 100644 index 00000000000000..b239b4b143e132 --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/el.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "link": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ({host});", + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf PowerView Hub" + }, + "user": { + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf PowerView Hub" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/icloud/translations/el.json b/homeassistant/components/icloud/translations/el.json index 1fa59992bc3aa5..2783621ab09085 100644 --- a/homeassistant/components/icloud/translations/el.json +++ b/homeassistant/components/icloud/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "no_device": "\u039a\u03b1\u03bc\u03af\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b9\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03b1\u03c2 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 \u03c4\u03b7\u03bd \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \"Find my iPhone\"." + }, "step": { "reauth": { "description": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c0\u03bf\u03c5 \u03b5\u03af\u03c7\u03b1\u03c4\u03b5 \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03b9 \u03c0\u03c1\u03bf\u03b7\u03b3\u03bf\u03c5\u03bc\u03ad\u03bd\u03c9\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username} \u03b4\u03b5\u03bd \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03b5\u03af \u03c0\u03bb\u03ad\u03bf\u03bd. \u0395\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7." diff --git a/homeassistant/components/ifttt/translations/ca.json b/homeassistant/components/ifttt/translations/ca.json index 0c8bb89ba02fbf..17e7fe938d075e 100644 --- a/homeassistant/components/ifttt/translations/ca.json +++ b/homeassistant/components/ifttt/translations/ca.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "No connectat a Home Assistant Cloud.", "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3.", "webhook_not_internet_accessible": "La teva inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per poder rebre missatges webhook." }, diff --git a/homeassistant/components/ifttt/translations/en.json b/homeassistant/components/ifttt/translations/en.json index 68999eba2b70ef..d7b79b282eb787 100644 --- a/homeassistant/components/ifttt/translations/en.json +++ b/homeassistant/components/ifttt/translations/en.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Not connected to Home Assistant Cloud.", "single_instance_allowed": "Already configured. Only a single configuration possible.", "webhook_not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive webhook messages." }, diff --git a/homeassistant/components/ifttt/translations/it.json b/homeassistant/components/ifttt/translations/it.json index 6b1d1dd4c53c7d..93ef3f7dc31c5a 100644 --- a/homeassistant/components/ifttt/translations/it.json +++ b/homeassistant/components/ifttt/translations/it.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Non connesso a Home Assistant Cloud.", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione.", "webhook_not_internet_accessible": "L'istanza di Home Assistant deve essere accessibile da Internet per ricevere messaggi webhook." }, diff --git a/homeassistant/components/ifttt/translations/tr.json b/homeassistant/components/ifttt/translations/tr.json index b42268fa889fca..6585c0245e2880 100644 --- a/homeassistant/components/ifttt/translations/tr.json +++ b/homeassistant/components/ifttt/translations/tr.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Home Assistant Cloud'a ba\u011fl\u0131 de\u011fil.", "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", "webhook_not_internet_accessible": "Webhook mesajlar\u0131n\u0131 alabilmek i\u00e7in Home Assistant \u00f6rne\u011finize internetten eri\u015filebilmelidir." }, diff --git a/homeassistant/components/insteon/translations/el.json b/homeassistant/components/insteon/translations/el.json index f35d5bad3434fd..b0cadc8c8fd0bb 100644 --- a/homeassistant/components/insteon/translations/el.json +++ b/homeassistant/components/insteon/translations/el.json @@ -27,8 +27,49 @@ } }, "options": { + "error": { + "input_error": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b5\u03c2 \u03ba\u03b1\u03c4\u03b1\u03c7\u03c9\u03c1\u03ae\u03c3\u03b5\u03b9\u03c2, \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c4\u03b9\u03bc\u03ad\u03c2 \u03c3\u03b1\u03c2.", + "select_single": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae." + }, "step": { + "add_override": { + "data": { + "address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 (\u03c0.\u03c7. 1a2b3c)", + "cat": "\u039a\u03b1\u03c4\u03b7\u03b3\u03bf\u03c1\u03af\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 (\u03c0.\u03c7. 0x10)", + "subcat": "\u03a5\u03c0\u03bf\u03ba\u03b1\u03c4\u03b7\u03b3\u03bf\u03c1\u03af\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 (\u03c0.\u03c7. 0x0a)" + }, + "description": "\u03a0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03b1\u03c1\u03ac\u03ba\u03b1\u03bc\u03c8\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2.", + "title": "Insteon" + }, + "add_x10": { + "data": { + "housecode": "\u039f\u03b9\u03ba\u03b9\u03b1\u03ba\u03cc\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 (a - p)", + "platform": "\u03a0\u03bb\u03b1\u03c4\u03c6\u03cc\u03c1\u03bc\u03b1", + "steps": "\u0392\u03ae\u03bc\u03b1\u03c4\u03b1 \u03c1\u03bf\u03bf\u03c3\u03c4\u03ac\u03c4\u03b7 (\u03bc\u03cc\u03bd\u03bf \u03b3\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c6\u03c9\u03c4\u03b9\u03c3\u03bc\u03bf\u03cd, \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae 22)", + "unitcode": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1\u03c2 (1 - 16)" + }, + "description": "\u0391\u03bb\u03bb\u03ac\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 Insteon Hub.", + "title": "Insteon" + }, + "change_hub_config": { + "description": "\u0391\u03bb\u03bb\u03ac\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 Insteon Hub. \u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Home Assistant \u03bc\u03b5\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03b1\u03b3\u03bc\u03b1\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b1\u03c5\u03c4\u03ae\u03c2 \u03c4\u03b7\u03c2 \u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2. \u0391\u03c5\u03c4\u03cc \u03b4\u03b5\u03bd \u03b1\u03bb\u03bb\u03ac\u03b6\u03b5\u03b9 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03af\u03b4\u03b9\u03bf\u03c5 \u03c4\u03bf\u03c5 Hub. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03bb\u03bb\u03ac\u03be\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c3\u03c4\u03bf Hub \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae Hub.", + "title": "Insteon" + }, + "init": { + "data": { + "add_override": "\u03a0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03b1\u03c1\u03ac\u03ba\u03b1\u03bc\u03c8\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2.", + "add_x10": "\u03a0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae X10.", + "change_hub_config": "\u0391\u03bb\u03bb\u03ac\u03be\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Hub.", + "remove_override": "\u039a\u03b1\u03c4\u03ac\u03c1\u03b3\u03b7\u03c3\u03b7 \u03bc\u03b9\u03b1\u03c2 \u03c0\u03b1\u03c1\u03ac\u03ba\u03b1\u03bc\u03c8\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2.", + "remove_x10": "\u0391\u03c6\u03b1\u03b9\u03c1\u03ad\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae X10." + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03b3\u03b9\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7.", + "title": "Insteon" + }, "remove_override": { + "data": { + "address": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03b3\u03b9\u03b1 \u03ba\u03b1\u03c4\u03ac\u03c1\u03b3\u03b7\u03c3\u03b7" + }, "description": "\u039a\u03b1\u03c4\u03ac\u03c1\u03b3\u03b7\u03c3\u03b7 \u03c0\u03b1\u03c1\u03ac\u03ba\u03b1\u03bc\u03c8\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", "title": "Insteon" }, diff --git a/homeassistant/components/intellifire/translations/sv.json b/homeassistant/components/intellifire/translations/sv.json new file mode 100644 index 00000000000000..f341a6314eeb0d --- /dev/null +++ b/homeassistant/components/intellifire/translations/sv.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten \u00e4r redan konfigurerad" + }, + "error": { + "cannot_connect": "Det gick inte att ansluta.", + "unknown": "Ov\u00e4ntat fel" + }, + "step": { + "user": { + "data": { + "host": "V\u00e4rd" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ipp/translations/el.json b/homeassistant/components/ipp/translations/el.json index 0c3b36ba0afaa9..b758a17357f7f2 100644 --- a/homeassistant/components/ipp/translations/el.json +++ b/homeassistant/components/ipp/translations/el.json @@ -12,7 +12,13 @@ "user": { "data": { "base_path": "\u03a3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ae \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae" - } + }, + "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae \u03c3\u03b1\u03c2 \u03bc\u03ad\u03c3\u03c9 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03c9\u03c4\u03bf\u03ba\u03cc\u03bb\u03bb\u03bf\u03c5 \u03b5\u03ba\u03c4\u03cd\u03c0\u03c9\u03c3\u03b7\u03c2 Internet (IPP) \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Home Assistant.", + "title": "\u03a3\u03c5\u03bd\u03b4\u03ad\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae \u03c3\u03b1\u03c2" + }, + "zeroconf_confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name};", + "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae\u03c2" } } } diff --git a/homeassistant/components/iqvia/translations/el.json b/homeassistant/components/iqvia/translations/el.json new file mode 100644 index 00000000000000..baf8fbcbba4f3e --- /dev/null +++ b/homeassistant/components/iqvia/translations/el.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "invalid_zip_code": "\u039f \u03c4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b9\u03ba\u03cc\u03c2 \u03ba\u03ce\u03b4\u03b9\u03ba\u03b1\u03c2 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2" + }, + "step": { + "user": { + "data": { + "zip_code": "\u03a4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b9\u03ba\u03cc\u03c2 \u03ba\u03ce\u03b4\u03b9\u03ba\u03b1\u03c2" + }, + "description": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b9\u03ba\u03cc \u03ba\u03ce\u03b4\u03b9\u03ba\u03b1 \u03c4\u03c9\u03bd \u0397\u03a0\u0391 \u03ae \u03c4\u03bf\u03c5 \u039a\u03b1\u03bd\u03b1\u03b4\u03ac.", + "title": "IQVIA" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/isy994/translations/el.json b/homeassistant/components/isy994/translations/el.json index eaec481025c38e..61c22e7e6ed023 100644 --- a/homeassistant/components/isy994/translations/el.json +++ b/homeassistant/components/isy994/translations/el.json @@ -1,5 +1,30 @@ { "config": { - "flow_title": "{name} ({host})" + "error": { + "invalid_host": "\u0397 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03b4\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03c3\u03b5 \u03c0\u03bb\u03ae\u03c1\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae URL, \u03c0.\u03c7. http://192.168.10.100:80" + }, + "flow_title": "{name} ({host})", + "step": { + "user": { + "data": { + "tls": "\u0397 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 TLS \u03c4\u03bf\u03c5 \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ae ISY." + }, + "description": "\u0397 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03b5 \u03c0\u03bb\u03ae\u03c1\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae URL, \u03c0.\u03c7. http://192.168.10.100:80", + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf ISY994" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ignore_string": "\u03a0\u03b1\u03c1\u03ac\u03b2\u03bb\u03b5\u03c8\u03b7 \u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac\u03c2", + "restore_light_state": "\u0395\u03c0\u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac \u03c6\u03c9\u03c4\u03b5\u03b9\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03c6\u03c9\u03c4\u03cc\u03c2", + "sensor_string": "\u03a3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03ba\u03cc\u03bc\u03b2\u03bf\u03c5", + "variable_sensor_string": "\u039c\u03b5\u03c4\u03b1\u03b2\u03bb\u03b7\u03c4\u03ae \u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1" + }, + "description": "\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 ISY: \n - \u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03ba\u03cc\u03bc\u03b2\u03bf\u03c5: \u039a\u03ac\u03b8\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ae \u03c6\u03ac\u03ba\u03b5\u03bb\u03bf\u03c2 \u03c0\u03bf\u03c5 \u03c0\u03b5\u03c1\u03b9\u03ad\u03c7\u03b5\u03b9 'Node Sensor String' \u03c3\u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03b8\u03b1 \u03b1\u03bd\u03c4\u03b9\u03bc\u03b5\u03c4\u03c9\u03c0\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c9\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03ae \u03b4\u03c5\u03b1\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2. \n - Ignore String (\u0391\u03b3\u03bd\u03bf\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac): \u039f\u03c0\u03bf\u03b9\u03b1\u03b4\u03ae\u03c0\u03bf\u03c4\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bc\u03b5 \u03c4\u03bf 'Ignore String' \u03c3\u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03b8\u03b1 \u03b1\u03b3\u03bd\u03bf\u03b5\u03af\u03c4\u03b1\u03b9. \n - \u039c\u03b5\u03c4\u03b1\u03b2\u03bb\u03b7\u03c4\u03ae \u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1: \u039a\u03ac\u03b8\u03b5 \u03bc\u03b5\u03c4\u03b1\u03b2\u03bb\u03b7\u03c4\u03ae \u03c0\u03bf\u03c5 \u03c0\u03b5\u03c1\u03b9\u03ad\u03c7\u03b5\u03b9 \u03c4\u03bf 'Variable Sensor String' \u03b8\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c4\u03b5\u03b8\u03b5\u03af \u03c9\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2. \n - \u0395\u03c0\u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac \u03c6\u03c9\u03c4\u03b5\u03b9\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03c6\u03c9\u03c4\u03cc\u03c2: \u0395\u03ac\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7, \u03b7 \u03c0\u03c1\u03bf\u03b7\u03b3\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7 \u03c6\u03c9\u03c4\u03b5\u03b9\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b8\u03b1 \u03b1\u03c0\u03bf\u03ba\u03b1\u03b8\u03af\u03c3\u03c4\u03b1\u03c4\u03b1\u03b9 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03bd\u03cc\u03c2 \u03c6\u03c9\u03c4\u03cc\u03c2 \u03b1\u03bd\u03c4\u03af \u03b3\u03b9\u03b1 \u03c4\u03bf \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03bc\u03ad\u03bd\u03bf \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/knx/translations/ca.json b/homeassistant/components/knx/translations/ca.json index 6778b3539a02c4..dd553f293be637 100644 --- a/homeassistant/components/knx/translations/ca.json +++ b/homeassistant/components/knx/translations/ca.json @@ -14,7 +14,8 @@ "individual_address": "Adre\u00e7a individual de la connexi\u00f3", "local_ip": "IP local de Home Assistant (deixa-ho en blanc si no n'est\u00e0s segur/a)", "port": "Port", - "route_back": "Encaminament de retorn / Mode NAT" + "route_back": "Encaminament de retorn / Mode NAT", + "tunneling_type": "Tipus de t\u00fanel KNX" }, "description": "Introdueix la informaci\u00f3 de connexi\u00f3 del dispositiu de t\u00fanel." }, @@ -59,7 +60,8 @@ "host": "Amfitri\u00f3", "local_ip": "IP local (deixa-ho en blanc si no n'est\u00e0s segur/a)", "port": "Port", - "route_back": "Encaminament de retorn / Mode NAT" + "route_back": "Encaminament de retorn / Mode NAT", + "tunneling_type": "Tipus de t\u00fanel KNX" } } } diff --git a/homeassistant/components/knx/translations/de.json b/homeassistant/components/knx/translations/de.json index cc6af948b5b74c..6716b5c8a3776c 100644 --- a/homeassistant/components/knx/translations/de.json +++ b/homeassistant/components/knx/translations/de.json @@ -14,7 +14,8 @@ "individual_address": "Individuelle Adresse f\u00fcr die Verbindung", "local_ip": "Lokale IP des Home Assistant (f\u00fcr automatische Erkennung leer lassen)", "port": "Port", - "route_back": "Route Back / NAT-Modus" + "route_back": "Route Back / NAT-Modus", + "tunneling_type": "KNX Tunneling Typ" }, "description": "Bitte gib die Verbindungsinformationen deines Tunnelger\u00e4ts ein." }, @@ -59,7 +60,8 @@ "host": "Host", "local_ip": "Lokale IP (leer lassen, wenn unsicher)", "port": "Port", - "route_back": "Route Back / NAT-Modus" + "route_back": "Route Back / NAT-Modus", + "tunneling_type": "KNX Tunneling Typ" } } } diff --git a/homeassistant/components/knx/translations/el.json b/homeassistant/components/knx/translations/el.json index 19ee8d73aa4050..66d47c2a376d4e 100644 --- a/homeassistant/components/knx/translations/el.json +++ b/homeassistant/components/knx/translations/el.json @@ -5,7 +5,8 @@ "data": { "individual_address": "\u0391\u03c4\u03bf\u03bc\u03b9\u03ba\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7", "local_ip": "\u03a4\u03bf\u03c0\u03b9\u03ba\u03ae IP \u03c4\u03bf\u03c5 Home Assistant (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03b5\u03bd\u03ae \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7)", - "route_back": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 Route Back / NAT" + "route_back": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 Route Back / NAT", + "tunneling_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03ae\u03c1\u03b1\u03b3\u03b3\u03b1\u03c2 KNX" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03c3\u03ae\u03c1\u03b1\u03b3\u03b3\u03ac\u03c2 \u03c3\u03b1\u03c2." }, @@ -42,7 +43,8 @@ }, "tunnel": { "data": { - "local_ip": "\u03a4\u03bf\u03c0\u03b9\u03ba\u03ae IP (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03b5\u03bd\u03ae \u03b1\u03bd \u03b4\u03b5\u03bd \u03b5\u03af\u03c3\u03c4\u03b5 \u03c3\u03af\u03b3\u03bf\u03c5\u03c1\u03bf\u03b9)" + "local_ip": "\u03a4\u03bf\u03c0\u03b9\u03ba\u03ae IP (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03b5\u03bd\u03ae \u03b1\u03bd \u03b4\u03b5\u03bd \u03b5\u03af\u03c3\u03c4\u03b5 \u03c3\u03af\u03b3\u03bf\u03c5\u03c1\u03bf\u03b9)", + "tunneling_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03ae\u03c1\u03b1\u03b3\u03b3\u03b1\u03c2 KNX" } } } diff --git a/homeassistant/components/knx/translations/en.json b/homeassistant/components/knx/translations/en.json index 93ba7c006f047b..b8b8cf1250e417 100644 --- a/homeassistant/components/knx/translations/en.json +++ b/homeassistant/components/knx/translations/en.json @@ -10,11 +10,12 @@ "step": { "manual_tunnel": { "data": { - "tunneling_type": "KNX Tunneling Type", "host": "Host", "individual_address": "Individual address for the connection", "local_ip": "Local IP of Home Assistant (leave empty for automatic detection)", - "port": "Port" + "port": "Port", + "route_back": "Route Back / NAT Mode", + "tunneling_type": "KNX Tunneling Type" }, "description": "Please enter the connection information of your tunneling device." }, @@ -56,10 +57,11 @@ }, "tunnel": { "data": { - "tunneling_type": "KNX Tunneling Type", "host": "Host", "local_ip": "Local IP (leave empty if unsure)", - "port": "Port" + "port": "Port", + "route_back": "Route Back / NAT Mode", + "tunneling_type": "KNX Tunneling Type" } } } diff --git a/homeassistant/components/knx/translations/et.json b/homeassistant/components/knx/translations/et.json index 712015e6c91ee6..e9417cdac37ee2 100644 --- a/homeassistant/components/knx/translations/et.json +++ b/homeassistant/components/knx/translations/et.json @@ -14,7 +14,8 @@ "individual_address": "\u00dchenduse individuaalne aadress", "local_ip": "Home Assistanti kohalik IP (automaatseks tuvastuseks j\u00e4ta t\u00fchjaks)", "port": "Port", - "route_back": "Marsruudi tagasitee / NAT-re\u017eiim" + "route_back": "Marsruudi tagasitee / NAT-re\u017eiim", + "tunneling_type": "KNX tunneli t\u00fc\u00fcp" }, "description": "Sisesta tunneldamisseadme \u00fchenduse teave." }, @@ -59,7 +60,8 @@ "host": "Host", "local_ip": "Kohalik IP (j\u00e4ta t\u00fchjaks, kui ei ole kindel)", "port": "Port", - "route_back": "Marsruudi tagasitee / NAT-re\u017eiim" + "route_back": "Marsruudi tagasitee / NAT-re\u017eiim", + "tunneling_type": "KNX tunneli t\u00fc\u00fcp" } } } diff --git a/homeassistant/components/knx/translations/it.json b/homeassistant/components/knx/translations/it.json index 2c6d11e073c88e..ad4e9f34610538 100644 --- a/homeassistant/components/knx/translations/it.json +++ b/homeassistant/components/knx/translations/it.json @@ -14,7 +14,8 @@ "individual_address": "Indirizzo individuale per la connessione", "local_ip": "IP locale di Home Assistant (lascia vuoto per il rilevamento automatico)", "port": "Porta", - "route_back": "Torna indietro / Modalit\u00e0 NAT" + "route_back": "Torna indietro / Modalit\u00e0 NAT", + "tunneling_type": "Tipo tunnel KNX" }, "description": "Inserisci le informazioni di connessione del tuo dispositivo di tunneling." }, @@ -59,7 +60,8 @@ "host": "Host", "local_ip": "IP locale (lascia vuoto se non sei sicuro)", "port": "Porta", - "route_back": "Torna indietro / Modalit\u00e0 NAT" + "route_back": "Torna indietro / Modalit\u00e0 NAT", + "tunneling_type": "Tipo tunnel KNX" } } } diff --git a/homeassistant/components/knx/translations/ru.json b/homeassistant/components/knx/translations/ru.json index 0955b54fda8720..6c1a41dac914da 100644 --- a/homeassistant/components/knx/translations/ru.json +++ b/homeassistant/components/knx/translations/ru.json @@ -14,7 +14,8 @@ "individual_address": "\u0418\u043d\u0434\u0438\u0432\u0438\u0434\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u0430\u0434\u0440\u0435\u0441 \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f", "local_ip": "\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 IP-\u0430\u0434\u0440\u0435\u0441 Home Assistant (\u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u043e\u043b\u0435 \u043f\u0443\u0441\u0442\u044b\u043c \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f)", "port": "\u041f\u043e\u0440\u0442", - "route_back": "\u041e\u0431\u0440\u0430\u0442\u043d\u044b\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 / \u0440\u0435\u0436\u0438\u043c NAT" + "route_back": "\u041e\u0431\u0440\u0430\u0442\u043d\u044b\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 / \u0440\u0435\u0436\u0438\u043c NAT", + "tunneling_type": "\u0422\u0438\u043f \u0442\u0443\u043d\u043d\u0435\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f KNX" }, "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438." }, @@ -59,7 +60,8 @@ "host": "\u0425\u043e\u0441\u0442", "local_ip": "\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 IP-\u0430\u0434\u0440\u0435\u0441 (\u0435\u0441\u043b\u0438 \u043d\u0435 \u0437\u043d\u0430\u0435\u0442\u0435, \u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u043e\u043b\u0435 \u043f\u0443\u0441\u0442\u044b\u043c)", "port": "\u041f\u043e\u0440\u0442", - "route_back": "\u041e\u0431\u0440\u0430\u0442\u043d\u044b\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 / \u0440\u0435\u0436\u0438\u043c NAT" + "route_back": "\u041e\u0431\u0440\u0430\u0442\u043d\u044b\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 / \u0440\u0435\u0436\u0438\u043c NAT", + "tunneling_type": "\u0422\u0438\u043f \u0442\u0443\u043d\u043d\u0435\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f KNX" } } } diff --git a/homeassistant/components/knx/translations/sv.json b/homeassistant/components/knx/translations/sv.json new file mode 100644 index 00000000000000..b1be9557565ea4 --- /dev/null +++ b/homeassistant/components/knx/translations/sv.json @@ -0,0 +1,20 @@ +{ + "config": { + "step": { + "manual_tunnel": { + "data": { + "tunneling_type": "KNX tunneltyp" + } + } + } + }, + "options": { + "step": { + "tunnel": { + "data": { + "tunneling_type": "KNX tunneltyp" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/knx/translations/tr.json b/homeassistant/components/knx/translations/tr.json index fc476a776a4170..6267038a6e023c 100644 --- a/homeassistant/components/knx/translations/tr.json +++ b/homeassistant/components/knx/translations/tr.json @@ -14,7 +14,8 @@ "individual_address": "Ba\u011flant\u0131 i\u00e7in bireysel adres", "local_ip": "Home Assistant Yerel IP'si (otomatik alg\u0131lama i\u00e7in bo\u015f b\u0131rak\u0131n)", "port": "Port", - "route_back": "Geri Y\u00f6nlendirme / NAT Modu" + "route_back": "Geri Y\u00f6nlendirme / NAT Modu", + "tunneling_type": "KNX T\u00fcnel Tipi" }, "description": "L\u00fctfen t\u00fcnel cihaz\u0131n\u0131z\u0131n ba\u011flant\u0131 bilgilerini girin." }, @@ -59,7 +60,8 @@ "host": "Sunucu", "local_ip": "Yerel IP (emin de\u011filseniz bo\u015f b\u0131rak\u0131n)", "port": "Port", - "route_back": "Geri Y\u00f6nlendirme / NAT Modu" + "route_back": "Geri Y\u00f6nlendirme / NAT Modu", + "tunneling_type": "KNX T\u00fcnel Tipi" } } } diff --git a/homeassistant/components/knx/translations/zh-Hant.json b/homeassistant/components/knx/translations/zh-Hant.json index cd7322fc9e6361..39794940fef9d4 100644 --- a/homeassistant/components/knx/translations/zh-Hant.json +++ b/homeassistant/components/knx/translations/zh-Hant.json @@ -14,7 +14,8 @@ "individual_address": "\u9023\u7dda\u500b\u5225\u4f4d\u5740", "local_ip": "Home Assistant \u672c\u5730\u7aef IP\uff08\u4fdd\u7559\u7a7a\u767d\u4ee5\u81ea\u52d5\u5075\u6e2c\uff09", "port": "\u901a\u8a0a\u57e0", - "route_back": "\u8def\u7531\u8fd4\u56de / NAT \u6a21\u5f0f" + "route_back": "\u8def\u7531\u8fd4\u56de / NAT \u6a21\u5f0f", + "tunneling_type": "KNX \u901a\u9053\u985e\u578b" }, "description": "\u8acb\u8f38\u5165\u901a\u9053\u88dd\u7f6e\u7684\u9023\u7dda\u8cc7\u8a0a\u3002" }, @@ -59,7 +60,8 @@ "host": "\u4e3b\u6a5f\u7aef", "local_ip": "\u672c\u5730\u7aef IP\uff08\u5047\u5982\u4e0d\u78ba\u5b9a\uff0c\u4fdd\u7559\u7a7a\u767d\uff09", "port": "\u901a\u8a0a\u57e0", - "route_back": "\u8def\u7531\u8fd4\u56de / NAT \u6a21\u5f0f" + "route_back": "\u8def\u7531\u8fd4\u56de / NAT \u6a21\u5f0f", + "tunneling_type": "KNX \u901a\u9053\u985e\u578b" } } } diff --git a/homeassistant/components/konnected/translations/el.json b/homeassistant/components/konnected/translations/el.json index 0ce9aa09de5c03..a0e57d47083a91 100644 --- a/homeassistant/components/konnected/translations/el.json +++ b/homeassistant/components/konnected/translations/el.json @@ -11,6 +11,9 @@ "abort": { "not_konn_panel": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Konnected.io" }, + "error": { + "bad_host": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03b1\u03bd\u03c4\u03b9\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2 API" + }, "step": { "options_binary": { "data": { @@ -59,15 +62,22 @@ "options_misc": { "data": { "api_host": "\u03a0\u03b1\u03c1\u03ac\u03ba\u03b1\u03bc\u03c8\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae API (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", + "blink": "\u0397 \u03bb\u03c5\u03c7\u03bd\u03af\u03b1 LED \u03c4\u03bf\u03c5 \u03c0\u03af\u03bd\u03b1\u03ba\u03b1 \u03b1\u03bd\u03b1\u03b2\u03bf\u03c3\u03b2\u03ae\u03bd\u03b5\u03b9 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b1\u03c0\u03bf\u03c3\u03c4\u03bf\u03bb\u03ae \u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2", "override_api_host": "\u03a0\u03b1\u03c1\u03ac\u03ba\u03b1\u03bc\u03c8\u03b7 \u03c4\u03b7\u03c2 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03c4\u03bf\u03c5 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c0\u03af\u03bd\u03b1\u03ba\u03b1 \u03c4\u03bf\u03c5 Home Assistant API" }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c0\u03b9\u03b8\u03c5\u03bc\u03b7\u03c4\u03ae \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03c6\u03bf\u03c1\u03ac \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03c0\u03af\u03bd\u03b1\u03ba\u03b1 \u03c3\u03b1\u03c2", "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b4\u03b9\u03ac\u03c6\u03bf\u03c1\u03c9\u03bd" }, "options_switch": { "data": { "activation": "\u0388\u03be\u03bf\u03b4\u03bf\u03c2 \u03cc\u03c4\u03b1\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf", - "more_states": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03c9\u03bd \u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ac\u03c3\u03b5\u03c9\u03bd \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7 \u03b6\u03ce\u03bd\u03b7" - } + "momentary": "\u0394\u03b9\u03ac\u03c1\u03ba\u03b5\u03b9\u03b1 \u03c0\u03b1\u03bb\u03bc\u03bf\u03cd (ms) (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", + "more_states": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03c9\u03bd \u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ac\u03c3\u03b5\u03c9\u03bd \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7 \u03b6\u03ce\u03bd\u03b7", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1 (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", + "pause": "\u03a0\u03b1\u03cd\u03c3\u03b7 \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03c0\u03b1\u03bb\u03bc\u03ce\u03bd (ms) (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", + "repeat": "\u03a6\u03bf\u03c1\u03ad\u03c2 \u03b5\u03c0\u03b1\u03bd\u03ac\u03bb\u03b7\u03c8\u03b7\u03c2 (-1=\u03ac\u03c0\u03b5\u03b9\u03c1\u03b5\u03c2) (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)" + }, + "description": "{zone} : \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 {state}" } } } diff --git a/homeassistant/components/locative/translations/ca.json b/homeassistant/components/locative/translations/ca.json index 637b937a568a55..b6ec5bd1111495 100644 --- a/homeassistant/components/locative/translations/ca.json +++ b/homeassistant/components/locative/translations/ca.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "No connectat a Home Assistant Cloud.", "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3.", "webhook_not_internet_accessible": "La teva inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per poder rebre missatges webhook." }, diff --git a/homeassistant/components/locative/translations/en.json b/homeassistant/components/locative/translations/en.json index 760835c8ea8dc5..91710293751368 100644 --- a/homeassistant/components/locative/translations/en.json +++ b/homeassistant/components/locative/translations/en.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Not connected to Home Assistant Cloud.", "single_instance_allowed": "Already configured. Only a single configuration possible.", "webhook_not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive webhook messages." }, diff --git a/homeassistant/components/locative/translations/it.json b/homeassistant/components/locative/translations/it.json index a9215ea6553cd6..7cdb9b2170d3e2 100644 --- a/homeassistant/components/locative/translations/it.json +++ b/homeassistant/components/locative/translations/it.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Non connesso a Home Assistant Cloud.", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione.", "webhook_not_internet_accessible": "L'istanza di Home Assistant deve essere accessibile da Internet per ricevere messaggi webhook." }, diff --git a/homeassistant/components/locative/translations/tr.json b/homeassistant/components/locative/translations/tr.json index 906abd1b2e55df..e48ff5d9b56e96 100644 --- a/homeassistant/components/locative/translations/tr.json +++ b/homeassistant/components/locative/translations/tr.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Home Assistant Cloud'a ba\u011fl\u0131 de\u011fil.", "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", "webhook_not_internet_accessible": "Webhook mesajlar\u0131n\u0131 alabilmek i\u00e7in Home Assistant \u00f6rne\u011finize internetten eri\u015filebilmelidir." }, diff --git a/homeassistant/components/logi_circle/translations/el.json b/homeassistant/components/logi_circle/translations/el.json new file mode 100644 index 00000000000000..3f9ee36865e476 --- /dev/null +++ b/homeassistant/components/logi_circle/translations/el.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "external_error": "\u03a0\u03c1\u03bf\u03ad\u03ba\u03c5\u03c8\u03b5 \u03b5\u03be\u03b1\u03af\u03c1\u03b5\u03c3\u03b7 \u03b1\u03c0\u03cc \u03ac\u03bb\u03bb\u03b7 \u03c1\u03bf\u03ae.", + "external_setup": "\u03a4\u03bf Logi Circle \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03b8\u03b7\u03ba\u03b5 \u03bc\u03b5 \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03af\u03b1 \u03b1\u03c0\u03cc \u03ac\u03bb\u03bb\u03b7 \u03c1\u03bf\u03ae." + }, + "error": { + "follow_link": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf \u03ba\u03b1\u03b9 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af\u03c4\u03b5 \u03c0\u03c1\u03b9\u03bd \u03c0\u03b1\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03a5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae." + }, + "step": { + "auth": { + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf \u03ba\u03b1\u03b9 **\u0391\u03c0\u03bf\u03b4\u03b5\u03c7\u03c4\u03b5\u03af\u03c4\u03b5** \u03c4\u03b7\u03bd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7 \u03c3\u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 Logi Circle, \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1 \u03b5\u03c0\u03b9\u03c3\u03c4\u03c1\u03ad\u03c8\u03c4\u03b5 \u03ba\u03b1\u03b9 \u03c0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 **\u03a5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae** \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9.\n\n[\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf\u03c2]({authorization_url})", + "title": "\u03a0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03c4\u03bf Logi Circle" + }, + "user": { + "data": { + "flow_impl": "\u03a0\u03ac\u03c1\u03bf\u03c7\u03bf\u03c2" + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03ad\u03c3\u03c9 \u03c0\u03bf\u03b9\u03bf\u03c5 \u03c0\u03b1\u03c1\u03cc\u03c7\u03bf\u03c5 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03b1\u03b3\u03bc\u03b1\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03c3\u03c4\u03bf Logi Circle.", + "title": "\u03a0\u03ac\u03c1\u03bf\u03c7\u03bf\u03c2 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/translations/ca.json b/homeassistant/components/mailgun/translations/ca.json index 6584b15b3ad499..5959fce55c2bed 100644 --- a/homeassistant/components/mailgun/translations/ca.json +++ b/homeassistant/components/mailgun/translations/ca.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "No connectat a Home Assistant Cloud.", "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3.", "webhook_not_internet_accessible": "La teva inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per poder rebre missatges webhook." }, diff --git a/homeassistant/components/mailgun/translations/en.json b/homeassistant/components/mailgun/translations/en.json index ec6732304bd86d..928ab40e1af7ef 100644 --- a/homeassistant/components/mailgun/translations/en.json +++ b/homeassistant/components/mailgun/translations/en.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Not connected to Home Assistant Cloud.", "single_instance_allowed": "Already configured. Only a single configuration possible.", "webhook_not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive webhook messages." }, diff --git a/homeassistant/components/mailgun/translations/it.json b/homeassistant/components/mailgun/translations/it.json index fdefe8992e883a..0131b39c22b060 100644 --- a/homeassistant/components/mailgun/translations/it.json +++ b/homeassistant/components/mailgun/translations/it.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Non connesso a Home Assistant Cloud.", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione.", "webhook_not_internet_accessible": "L'istanza di Home Assistant deve essere accessibile da Internet per ricevere messaggi webhook." }, diff --git a/homeassistant/components/mailgun/translations/tr.json b/homeassistant/components/mailgun/translations/tr.json index 3918614af2e936..6f7efc7d8b3987 100644 --- a/homeassistant/components/mailgun/translations/tr.json +++ b/homeassistant/components/mailgun/translations/tr.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Home Assistant Cloud'a ba\u011fl\u0131 de\u011fil.", "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", "webhook_not_internet_accessible": "Webhook mesajlar\u0131n\u0131 alabilmek i\u00e7in Home Assistant \u00f6rne\u011finize internetten eri\u015filebilmelidir." }, diff --git a/homeassistant/components/melcloud/translations/el.json b/homeassistant/components/melcloud/translations/el.json new file mode 100644 index 00000000000000..835523c59d749d --- /dev/null +++ b/homeassistant/components/melcloud/translations/el.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 MELCloud \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf email. \u03a4\u03bf \u03ba\u03bf\u03c5\u03c0\u03cc\u03bd\u03b9 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03b1\u03bd\u03b1\u03bd\u03b5\u03c9\u03b8\u03b5\u03af." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mikrotik/translations/el.json b/homeassistant/components/mikrotik/translations/el.json index 588841a7be8129..83aba4ae98a9ea 100644 --- a/homeassistant/components/mikrotik/translations/el.json +++ b/homeassistant/components/mikrotik/translations/el.json @@ -7,7 +7,8 @@ "user": { "data": { "verify_ssl": "\u03a7\u03c1\u03ae\u03c3\u03b7 ssl" - } + }, + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae Mikrotik" } } }, @@ -15,6 +16,7 @@ "step": { "device_tracker": { "data": { + "arp_ping": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 ARP ping", "detection_time": "\u0395\u03be\u03b5\u03c4\u03ac\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03b4\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03c3\u03c4\u03bf \u03c3\u03c0\u03af\u03c4\u03b9", "force_dhcp": "\u0391\u03bd\u03b1\u03b3\u03ba\u03b1\u03c3\u03c4\u03b9\u03ba\u03ae \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7 \u03bc\u03b5 \u03c7\u03c1\u03ae\u03c3\u03b7 DHCP" } diff --git a/homeassistant/components/minecraft_server/translations/el.json b/homeassistant/components/minecraft_server/translations/el.json index d69c70c4845f0e..e5dc88173d40ec 100644 --- a/homeassistant/components/minecraft_server/translations/el.json +++ b/homeassistant/components/minecraft_server/translations/el.json @@ -4,6 +4,12 @@ "cannot_connect": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03c3\u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03b8\u03cd\u03c1\u03b1 \u03ba\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac. \u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03b5\u03c0\u03af\u03c3\u03b7\u03c2 \u03cc\u03c4\u03b9 \u03b5\u03ba\u03c4\u03b5\u03bb\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf\u03c5\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf\u03bd \u03c4\u03b7\u03bd \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 1.7 \u03c4\u03bf\u03c5 Minecraft \u03c3\u03c4\u03bf\u03bd \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae \u03c3\u03b1\u03c2.", "invalid_ip": "\u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03b5\u03af\u03bd\u03b1\u03b9 \u03ac\u03ba\u03c5\u03c1\u03b7 (\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 MAC \u03b4\u03b5\u03bd \u03bc\u03c0\u03cc\u03c1\u03b5\u03c3\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b4\u03b9\u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af). \u0394\u03b9\u03bf\u03c1\u03b8\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", "invalid_port": "\u0397 \u03b8\u03cd\u03c1\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ba\u03c5\u03bc\u03b1\u03af\u03bd\u03b5\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc 1024 \u03ad\u03c9\u03c2 65535. \u0394\u03b9\u03bf\u03c1\u03b8\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac." + }, + "step": { + "user": { + "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae Minecraft \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03b5\u03c4\u03b1\u03b9 \u03b7 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7.", + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae Minecraft" + } } } } \ No newline at end of file diff --git a/homeassistant/components/modem_callerid/translations/el.json b/homeassistant/components/modem_callerid/translations/el.json new file mode 100644 index 00000000000000..8c54ea9fefcc5c --- /dev/null +++ b/homeassistant/components/modem_callerid/translations/el.json @@ -0,0 +1,14 @@ +{ + "config": { + "step": { + "usb_confirm": { + "description": "\u03a0\u03c1\u03cc\u03ba\u03b5\u03b9\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03ba\u03bb\u03ae\u03c3\u03b5\u03b9\u03c2 \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae\u03c2 \u03c4\u03b7\u03bb\u03b5\u03c6\u03c9\u03bd\u03af\u03b1\u03c2 \u03bc\u03b5 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c6\u03c9\u03bd\u03b7\u03c4\u03b9\u03ba\u03bf\u03cd \u03bc\u03cc\u03bd\u03c4\u03b5\u03bc CX93001. \u0391\u03c5\u03c4\u03cc \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b1\u03bd\u03b1\u03ba\u03c4\u03ae\u03c3\u03b5\u03b9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ba\u03b1\u03bb\u03bf\u03cd\u03bd\u03c4\u03bf\u03c2 \u03bc\u03b5 \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b1\u03c0\u03cc\u03c1\u03c1\u03b9\u03c8\u03b7\u03c2 \u03bc\u03b9\u03b1\u03c2 \u03b5\u03b9\u03c3\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03b7\u03c2 \u03ba\u03bb\u03ae\u03c3\u03b7\u03c2.", + "title": "\u039c\u03cc\u03bd\u03c4\u03b5\u03bc \u03c4\u03b7\u03bb\u03b5\u03c6\u03ce\u03bd\u03bf\u03c5" + }, + "user": { + "description": "\u03a0\u03c1\u03cc\u03ba\u03b5\u03b9\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03ba\u03bb\u03ae\u03c3\u03b5\u03b9\u03c2 \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae\u03c2 \u03c4\u03b7\u03bb\u03b5\u03c6\u03c9\u03bd\u03af\u03b1\u03c2 \u03bc\u03b5 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c6\u03c9\u03bd\u03b7\u03c4\u03b9\u03ba\u03bf\u03cd \u03bc\u03cc\u03bd\u03c4\u03b5\u03bc CX93001. \u0391\u03c5\u03c4\u03cc \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b1\u03bd\u03b1\u03ba\u03c4\u03ae\u03c3\u03b5\u03b9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ba\u03b1\u03bb\u03bf\u03cd\u03bd\u03c4\u03bf\u03c2 \u03bc\u03b5 \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b1\u03c0\u03cc\u03c1\u03c1\u03b9\u03c8\u03b7\u03c2 \u03bc\u03b9\u03b1\u03c2 \u03b5\u03b9\u03c3\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03b7\u03c2 \u03ba\u03bb\u03ae\u03c3\u03b7\u03c2.", + "title": "\u039c\u03cc\u03bd\u03c4\u03b5\u03bc \u03c4\u03b7\u03bb\u03b5\u03c6\u03ce\u03bd\u03bf\u03c5" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/monoprice/translations/el.json b/homeassistant/components/monoprice/translations/el.json new file mode 100644 index 00000000000000..d72413d8e86f4f --- /dev/null +++ b/homeassistant/components/monoprice/translations/el.json @@ -0,0 +1,17 @@ +{ + "config": { + "step": { + "user": { + "data": { + "source_1": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b7\u03b3\u03ae\u03c2 #1", + "source_2": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b7\u03b3\u03ae\u03c2 #2", + "source_3": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b7\u03b3\u03ae\u03c2 #3", + "source_4": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b7\u03b3\u03ae\u03c2 #4", + "source_5": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b7\u03b3\u03ae\u03c2 #5", + "source_6": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b7\u03b3\u03ae\u03c2 #6" + }, + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mqtt/translations/el.json b/homeassistant/components/mqtt/translations/el.json index 44f835c57d993c..668a292e39b90e 100644 --- a/homeassistant/components/mqtt/translations/el.json +++ b/homeassistant/components/mqtt/translations/el.json @@ -30,8 +30,12 @@ "turn_on": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7" }, "trigger_type": { + "button_double_press": "\u0394\u03b9\u03c0\u03bb\u03cc \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf \"{subtype}\"", + "button_quadruple_press": "\u03a4\u03b5\u03c4\u03c1\u03b1\u03c0\u03bb\u03cc \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf \"{subtype}\"", + "button_quintuple_press": "\u03a0\u03b5\u03bd\u03c4\u03b1\u03c0\u03bb\u03cc \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf \"{subtype}\"", "button_short_press": "\u03a0\u03b1\u03c4\u03ae\u03b8\u03b7\u03ba\u03b5 \u03c4\u03bf \"{subtype}\"", - "button_short_release": "\u0391\u03c0\u03b5\u03bb\u03b5\u03c5\u03b8\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03c4\u03bf \"{subtype}\"" + "button_short_release": "\u0391\u03c0\u03b5\u03bb\u03b5\u03c5\u03b8\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03c4\u03bf \"{subtype}\"", + "button_triple_press": "\u03a4\u03c1\u03b9\u03c0\u03bb\u03cc \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf \"{subtype}\"" } }, "options": { diff --git a/homeassistant/components/myq/translations/el.json b/homeassistant/components/myq/translations/el.json index db9aadc0b60a3c..24b945708d3b2d 100644 --- a/homeassistant/components/myq/translations/el.json +++ b/homeassistant/components/myq/translations/el.json @@ -4,6 +4,9 @@ "reauth_confirm": { "description": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username} \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03bb\u03ad\u03bf\u03bd \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2.", "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03c3\u03b1\u03c2 MyQ" + }, + "user": { + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b7\u03bd \u03c0\u03cd\u03bb\u03b7 MyQ" } } } diff --git a/homeassistant/components/nanoleaf/translations/sv.json b/homeassistant/components/nanoleaf/translations/sv.json new file mode 100644 index 00000000000000..4ca6ad5c3de6d0 --- /dev/null +++ b/homeassistant/components/nanoleaf/translations/sv.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "reauth_successful": "\u00c5terautentisering lyckades" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netgear/translations/el.json b/homeassistant/components/netgear/translations/el.json new file mode 100644 index 00000000000000..2b5744077e18a4 --- /dev/null +++ b/homeassistant/components/netgear/translations/el.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "config": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03ae \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2: \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03ae \u03c3\u03b1\u03c2" + }, + "step": { + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2 (\u03a0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", + "port": "\u0398\u03cd\u03c1\u03b1 (\u03a0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nexia/translations/el.json b/homeassistant/components/nexia/translations/el.json new file mode 100644 index 00000000000000..095042b54b0709 --- /dev/null +++ b/homeassistant/components/nexia/translations/el.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 mynexia.com" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/notion/translations/el.json b/homeassistant/components/notion/translations/el.json new file mode 100644 index 00000000000000..756523974064f6 --- /dev/null +++ b/homeassistant/components/notion/translations/el.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "reauth_confirm": { + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username}." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuheat/translations/el.json b/homeassistant/components/nuheat/translations/el.json index 1d2ee7844f9f7a..d398d5895137aa 100644 --- a/homeassistant/components/nuheat/translations/el.json +++ b/homeassistant/components/nuheat/translations/el.json @@ -2,6 +2,15 @@ "config": { "error": { "invalid_thermostat": "\u039f \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03c4\u03bf\u03c5 \u03b8\u03b5\u03c1\u03bc\u03bf\u03c3\u03c4\u03ac\u03c4\u03b7 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2." + }, + "step": { + "user": { + "data": { + "serial_number": "\u03a3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03c4\u03bf\u03c5 \u03b8\u03b5\u03c1\u03bc\u03bf\u03c3\u03c4\u03ac\u03c4\u03b7." + }, + "description": "\u0398\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03b7\u03c4\u03b9\u03ba\u03cc \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03ae \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03bf\u03c5 \u03b8\u03b5\u03c1\u03bc\u03bf\u03c3\u03c4\u03ac\u03c4\u03b7 \u03c3\u03b1\u03c2, \u03c3\u03c5\u03bd\u03b4\u03b5\u03cc\u03bc\u03b5\u03bd\u03bf\u03b9 \u03c3\u03c4\u03bf https://MyNuHeat.com \u03ba\u03b1\u03b9 \u03b5\u03c0\u03b9\u03bb\u03ad\u03b3\u03bf\u03bd\u03c4\u03b1\u03c2 \u03c4\u03bf\u03bd/\u03c4\u03bf\u03c5\u03c2 \u03b8\u03b5\u03c1\u03bc\u03bf\u03c3\u03c4\u03ac\u03c4\u03b5\u03c2 \u03c3\u03b1\u03c2.", + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf NuHeat" + } } } } \ No newline at end of file diff --git a/homeassistant/components/nut/translations/el.json b/homeassistant/components/nut/translations/el.json index f606163c1fa0c8..67273a20482984 100644 --- a/homeassistant/components/nut/translations/el.json +++ b/homeassistant/components/nut/translations/el.json @@ -7,6 +7,13 @@ }, "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf\u03c5\u03c2 \u03c0\u03cc\u03c1\u03bf\u03c5\u03c2 \u03b3\u03b9\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7" }, + "ups": { + "data": { + "alias": "\u03a8\u03b5\u03c5\u03b4\u03ce\u03bd\u03c5\u03bc\u03bf", + "resources": "\u03a0\u03cc\u03c1\u03bf\u03b9" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf UPS \u03b3\u03b9\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7" + }, "user": { "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae NUT" } diff --git a/homeassistant/components/onboarding/translations/el.json b/homeassistant/components/onboarding/translations/el.json new file mode 100644 index 00000000000000..29d0c1225388e1 --- /dev/null +++ b/homeassistant/components/onboarding/translations/el.json @@ -0,0 +1,7 @@ +{ + "area": { + "bedroom": "\u03a5\u03c0\u03bd\u03bf\u03b4\u03c9\u03bc\u03ac\u03c4\u03b9\u03bf", + "kitchen": "\u039a\u03bf\u03c5\u03b6\u03af\u03bd\u03b1", + "living_room": "\u03a3\u03b1\u03bb\u03cc\u03bd\u03b9" + } +} \ No newline at end of file diff --git a/homeassistant/components/overkiz/translations/bg.json b/homeassistant/components/overkiz/translations/bg.json index aaee76bbb8d5ba..25ad61ac70f186 100644 --- a/homeassistant/components/overkiz/translations/bg.json +++ b/homeassistant/components/overkiz/translations/bg.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u0410\u043a\u0430\u0443\u043d\u0442\u044a\u0442 \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d" + "already_configured": "\u0410\u043a\u0430\u0443\u043d\u0442\u044a\u0442 \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d", + "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0442\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435 \u0431\u0435\u0448\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e" }, "error": { "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", diff --git a/homeassistant/components/overkiz/translations/ca.json b/homeassistant/components/overkiz/translations/ca.json index ae269e11867671..2c604126b3ca48 100644 --- a/homeassistant/components/overkiz/translations/ca.json +++ b/homeassistant/components/overkiz/translations/ca.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "already_configured": "El compte ja est\u00e0 configurat" + "already_configured": "El compte ja est\u00e0 configurat", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament", + "reauth_wrong_account": "Nom\u00e9s pots tornar a autenticar aquesta entrada amb el mateix compte i hub d'Overkiz" }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3", diff --git a/homeassistant/components/overkiz/translations/de.json b/homeassistant/components/overkiz/translations/de.json index 27719b662fdf14..38e69556faaa08 100644 --- a/homeassistant/components/overkiz/translations/de.json +++ b/homeassistant/components/overkiz/translations/de.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "already_configured": "Konto wurde bereits konfiguriert" + "already_configured": "Konto wurde bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich", + "reauth_wrong_account": "Du kannst diesen Eintrag nur mit demselben Overkiz-Konto und -Hub erneut authentifizieren" }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", diff --git a/homeassistant/components/overkiz/translations/el.json b/homeassistant/components/overkiz/translations/el.json index 3e8b72613ce1dd..b1a57f0ead26f2 100644 --- a/homeassistant/components/overkiz/translations/el.json +++ b/homeassistant/components/overkiz/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "reauth_wrong_account": "\u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03bc\u03cc\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf\u03bd \u03af\u03b4\u03b9\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03af\u03b4\u03b9\u03bf \u03ba\u03cc\u03bc\u03b2\u03bf Overkiz." + }, "error": { "server_in_maintenance": "\u039f \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03ba\u03c4\u03cc\u03c2 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c3\u03c5\u03bd\u03c4\u03ae\u03c1\u03b7\u03c3\u03b7", "too_many_requests": "\u03a0\u03ac\u03c1\u03b1 \u03c0\u03bf\u03bb\u03bb\u03ac \u03b1\u03b9\u03c4\u03ae\u03bc\u03b1\u03c4\u03b1, \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1." diff --git a/homeassistant/components/overkiz/translations/et.json b/homeassistant/components/overkiz/translations/et.json index 53e41c8d7be982..c1c00bc1df61cd 100644 --- a/homeassistant/components/overkiz/translations/et.json +++ b/homeassistant/components/overkiz/translations/et.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "already_configured": "Konto on juba h\u00e4\u00e4lestatud" + "already_configured": "Konto on juba h\u00e4\u00e4lestatud", + "reauth_successful": "Taastuvastamine \u00f5nnestus", + "reauth_wrong_account": "Seda kirjet saab uuesti autentida ainult sama Overkiz'i konto ja keskuse abil." }, "error": { "cannot_connect": "\u00dchendamine nurjus", diff --git a/homeassistant/components/overkiz/translations/it.json b/homeassistant/components/overkiz/translations/it.json index 2f9c288aaac4ba..a228f0c05062bd 100644 --- a/homeassistant/components/overkiz/translations/it.json +++ b/homeassistant/components/overkiz/translations/it.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "already_configured": "L'account \u00e8 gi\u00e0 configurato" + "already_configured": "L'account \u00e8 gi\u00e0 configurato", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente", + "reauth_wrong_account": "Puoi riautenticare questa voce solo con lo stesso account e hub Overkiz" }, "error": { "cannot_connect": "Impossibile connettersi", diff --git a/homeassistant/components/overkiz/translations/ru.json b/homeassistant/components/overkiz/translations/ru.json index 3373a23e407309..d351645cebd809 100644 --- a/homeassistant/components/overkiz/translations/ru.json +++ b/homeassistant/components/overkiz/translations/ru.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "already_configured": "\u042d\u0442\u0430 \u0443\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant." + "already_configured": "\u042d\u0442\u0430 \u0443\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant.", + "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e.", + "reauth_wrong_account": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0441 \u0442\u043e\u0439 \u0436\u0435 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u044c\u044e Overkiz \u0438 \u043a\u043e\u043d\u0446\u0435\u043d\u0442\u0440\u0430\u0442\u043e\u0440\u043e\u043c." }, "error": { "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", diff --git a/homeassistant/components/overkiz/translations/sensor.bg.json b/homeassistant/components/overkiz/translations/sensor.bg.json index 40e3aad4dc4efd..cb5016cc23005e 100644 --- a/homeassistant/components/overkiz/translations/sensor.bg.json +++ b/homeassistant/components/overkiz/translations/sensor.bg.json @@ -1,11 +1,13 @@ { "state": { "overkiz__priority_lock_originator": { + "local_user": "\u041b\u043e\u043a\u0430\u043b\u0435\u043d \u043f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b", "lsc": "LSC", "saac": "SAAC", "sfc": "SFC", "temperature": "\u0422\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430", - "ups": "UPS" + "ups": "UPS", + "user": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b" } } } \ No newline at end of file diff --git a/homeassistant/components/overkiz/translations/sensor.nl.json b/homeassistant/components/overkiz/translations/sensor.nl.json index 628ea98dc7b7ae..f1471f4f5b64cf 100644 --- a/homeassistant/components/overkiz/translations/sensor.nl.json +++ b/homeassistant/components/overkiz/translations/sensor.nl.json @@ -9,7 +9,8 @@ "overkiz__discrete_rssi_level": { "good": "Goed", "low": "Laag", - "normal": "Normaal" + "normal": "Normaal", + "verylow": "Zeer laag" }, "overkiz__priority_lock_originator": { "local_user": "Lokale gebruiker", diff --git a/homeassistant/components/overkiz/translations/sv.json b/homeassistant/components/overkiz/translations/sv.json new file mode 100644 index 00000000000000..5ba4512fb3595b --- /dev/null +++ b/homeassistant/components/overkiz/translations/sv.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "reauth_successful": "\u00c5terautentisering lyckades", + "reauth_wrong_account": "Du kan bara \u00e5terautentisera denna post med samma Overkiz-konto och hub" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/overkiz/translations/tr.json b/homeassistant/components/overkiz/translations/tr.json index 42fe10e6a5152b..166b4dc48af44a 100644 --- a/homeassistant/components/overkiz/translations/tr.json +++ b/homeassistant/components/overkiz/translations/tr.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu", + "reauth_wrong_account": "Bu giri\u015fi yaln\u0131zca ayn\u0131 Overkiz hesab\u0131 ve hub ile yeniden do\u011frulayabilirsiniz." }, "error": { "cannot_connect": "Ba\u011flanma hatas\u0131", diff --git a/homeassistant/components/overkiz/translations/zh-Hant.json b/homeassistant/components/overkiz/translations/zh-Hant.json index f20636f9d18826..371f1ce022ef09 100644 --- a/homeassistant/components/overkiz/translations/zh-Hant.json +++ b/homeassistant/components/overkiz/translations/zh-Hant.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f", + "reauth_wrong_account": "\u50c5\u80fd\u4f7f\u7528\u76f8\u540c\u7684 Overkiz. \u5e33\u865f\u8207\u96c6\u7dda\u5668\u91cd\u65b0\u8a8d\u8b49\u6b64\u5be6\u9ad4" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", diff --git a/homeassistant/components/owntracks/translations/ca.json b/homeassistant/components/owntracks/translations/ca.json index 236614d06198ba..80013ccb55458c 100644 --- a/homeassistant/components/owntracks/translations/ca.json +++ b/homeassistant/components/owntracks/translations/ca.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "No connectat a Home Assistant Cloud.", "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "create_entry": { diff --git a/homeassistant/components/owntracks/translations/en.json b/homeassistant/components/owntracks/translations/en.json index 870b7cdbe5ce47..bf75bfe8190048 100644 --- a/homeassistant/components/owntracks/translations/en.json +++ b/homeassistant/components/owntracks/translations/en.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Not connected to Home Assistant Cloud.", "single_instance_allowed": "Already configured. Only a single configuration possible." }, "create_entry": { diff --git a/homeassistant/components/owntracks/translations/it.json b/homeassistant/components/owntracks/translations/it.json index 50d6c30777c37f..6448d4d9576717 100644 --- a/homeassistant/components/owntracks/translations/it.json +++ b/homeassistant/components/owntracks/translations/it.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Non connesso a Home Assistant Cloud.", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "create_entry": { diff --git a/homeassistant/components/owntracks/translations/tr.json b/homeassistant/components/owntracks/translations/tr.json index 944cd176580156..9f19fa47b7b12e 100644 --- a/homeassistant/components/owntracks/translations/tr.json +++ b/homeassistant/components/owntracks/translations/tr.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Home Assistant Cloud'a ba\u011fl\u0131 de\u011fil.", "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." }, "create_entry": { diff --git a/homeassistant/components/plaato/translations/ca.json b/homeassistant/components/plaato/translations/ca.json index 06aa27e5b3721b..4c2959bafa2425 100644 --- a/homeassistant/components/plaato/translations/ca.json +++ b/homeassistant/components/plaato/translations/ca.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "El compte ja est\u00e0 configurat", + "cloud_not_connected": "No connectat a Home Assistant Cloud.", "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3.", "webhook_not_internet_accessible": "La teva inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per poder rebre missatges webhook." }, diff --git a/homeassistant/components/plaato/translations/en.json b/homeassistant/components/plaato/translations/en.json index 1217eb53d6eac8..0eba3a9431023a 100644 --- a/homeassistant/components/plaato/translations/en.json +++ b/homeassistant/components/plaato/translations/en.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Account is already configured", + "cloud_not_connected": "Not connected to Home Assistant Cloud.", "single_instance_allowed": "Already configured. Only a single configuration possible.", "webhook_not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive webhook messages." }, diff --git a/homeassistant/components/plaato/translations/it.json b/homeassistant/components/plaato/translations/it.json index 722d1c5c34c463..26fe409bfc2d64 100644 --- a/homeassistant/components/plaato/translations/it.json +++ b/homeassistant/components/plaato/translations/it.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "L'account \u00e8 gi\u00e0 configurato", + "cloud_not_connected": "Non connesso a Home Assistant Cloud.", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione.", "webhook_not_internet_accessible": "L'istanza di Home Assistant deve essere accessibile da Internet per ricevere messaggi webhook." }, diff --git a/homeassistant/components/plaato/translations/tr.json b/homeassistant/components/plaato/translations/tr.json index 579617127ac804..21f2bddcc353c3 100644 --- a/homeassistant/components/plaato/translations/tr.json +++ b/homeassistant/components/plaato/translations/tr.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cloud_not_connected": "Home Assistant Cloud'a ba\u011fl\u0131 de\u011fil.", "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", "webhook_not_internet_accessible": "Webhook mesajlar\u0131n\u0131 alabilmek i\u00e7in Home Assistant \u00f6rne\u011finize internetten eri\u015filebilmelidir." }, diff --git a/homeassistant/components/plex/translations/el.json b/homeassistant/components/plex/translations/el.json index 721823efbc7425..a4e412f7d01751 100644 --- a/homeassistant/components/plex/translations/el.json +++ b/homeassistant/components/plex/translations/el.json @@ -7,12 +7,17 @@ }, "error": { "faulty_credentials": "\u0397 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5, \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf Token", + "host_or_token": "\u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03c4\u03bf\u03c5\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf\u03bd \u03ad\u03bd\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b1 \u03b5\u03be\u03ae\u03c2: Host \u03ae Token", "no_servers": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03bd \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ad\u03c2 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf\u03b9 \u03bc\u03b5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc Plex", "not_found": "\u039f \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 Plex \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5", "ssl_error": "\u0396\u03ae\u03c4\u03b7\u03bc\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03bf\u03cd SSL" }, + "flow_title": "{name} ({host})", "step": { "manual_setup": { + "data": { + "token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)" + }, "title": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Plex" }, "select_server": { @@ -37,6 +42,7 @@ "plex_mp_settings": { "data": { "ignore_new_shared_users": "\u0391\u03b3\u03bd\u03bf\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03c5\u03c2 \u03bd\u03ad\u03bf\u03c5\u03c2 \u03b4\u03b9\u03b1\u03c7\u03b5\u03b9\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03bf\u03c5\u03c2/\u03ba\u03bf\u03b9\u03bd\u03cc\u03c7\u03c1\u03b7\u03c3\u03c4\u03bf\u03c5\u03c2 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b5\u03c2", + "ignore_plex_web_clients": "\u0391\u03b3\u03bd\u03bf\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03c5\u03c2 \u03c0\u03b5\u03bb\u03ac\u03c4\u03b5\u03c2 Web Plex", "monitored_users": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03bf\u03cd\u03bc\u03b5\u03bd\u03bf\u03b9 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b5\u03c2" }, "description": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2 \u03c0\u03bf\u03bb\u03c5\u03bc\u03ad\u03c3\u03c9\u03bd Plex" diff --git a/homeassistant/components/powerwall/translations/el.json b/homeassistant/components/powerwall/translations/el.json index 79e3178f46f377..b3943953621eb6 100644 --- a/homeassistant/components/powerwall/translations/el.json +++ b/homeassistant/components/powerwall/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "wrong_version": "\u03a4\u03bf powerwall \u03c3\u03b1\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b9\u03c3\u03bc\u03b9\u03ba\u03bf\u03cd \u03c0\u03bf\u03c5 \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9. \u03a3\u03ba\u03b5\u03c6\u03c4\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b1\u03b2\u03b1\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03ae \u03bd\u03b1 \u03b1\u03bd\u03b1\u03c6\u03ad\u03c1\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c0\u03c1\u03cc\u03b2\u03bb\u03b7\u03bc\u03b1 \u03ce\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03c0\u03b9\u03bb\u03c5\u03b8\u03b5\u03af." + }, "flow_title": "{ip_address}", "step": { "user": { diff --git a/homeassistant/components/prosegur/translations/el.json b/homeassistant/components/prosegur/translations/el.json index c5dee661aa25a7..221f35ebba6d01 100644 --- a/homeassistant/components/prosegur/translations/el.json +++ b/homeassistant/components/prosegur/translations/el.json @@ -5,6 +5,11 @@ "data": { "description": "\u0395\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc Prosegur." } + }, + "user": { + "data": { + "country": "\u03a7\u03ce\u03c1\u03b1" + } } } } diff --git a/homeassistant/components/ps4/translations/el.json b/homeassistant/components/ps4/translations/el.json index 1f047f44ab4758..6d682ff545b4b6 100644 --- a/homeassistant/components/ps4/translations/el.json +++ b/homeassistant/components/ps4/translations/el.json @@ -6,6 +6,7 @@ "port_997_bind_error": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03b7 \u03b8\u03cd\u03c1\u03b1 997. \u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7](https://www.home-assistant.io/components/ps4/) \u03b3\u03b9\u03b1 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2." }, "error": { + "credential_timeout": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03b4\u03b9\u03b1\u03c0\u03af\u03c3\u03c4\u03b5\u03c5\u03c3\u03b7\u03c2 \u03c4\u03b5\u03c1\u03bc\u03ac\u03c4\u03b9\u03c3\u03b5 \u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c4\u03b7\u03c2. \u03a0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 submit \u03b3\u03b9\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7.", "no_ipaddress": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03bf\u03c5 PlayStation 4 \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03b5\u03c4\u03b5." }, "step": { diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/el.json b/homeassistant/components/pvpc_hourly_pricing/translations/el.json new file mode 100644 index 00000000000000..1f842e20cf6e78 --- /dev/null +++ b/homeassistant/components/pvpc_hourly_pricing/translations/el.json @@ -0,0 +1,14 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2", + "tariff": "\u0399\u03c3\u03c7\u03cd\u03bf\u03bd \u03c4\u03b9\u03bc\u03bf\u03bb\u03cc\u03b3\u03b9\u03bf \u03b1\u03bd\u03ac \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03ae \u03b6\u03ce\u03bd\u03b7" + }, + "description": "\u0391\u03c5\u03c4\u03cc\u03c2 \u03bf \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03c4\u03bf \u03b5\u03c0\u03af\u03c3\u03b7\u03bc\u03bf API \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03ac\u03b2\u03b5\u03b9 [\u03c9\u03c1\u03b9\u03b1\u03af\u03b1 \u03c4\u03b9\u03bc\u03bf\u03bb\u03cc\u03b3\u03b7\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b7\u03bb\u03b5\u03ba\u03c4\u03c1\u03b9\u03ba\u03ae\u03c2 \u03b5\u03bd\u03ad\u03c1\u03b3\u03b5\u03b9\u03b1\u03c2 (PVPC)](https://www.esios.ree.es/es/pvpc) \u03c3\u03c4\u03b7\u03bd \u0399\u03c3\u03c0\u03b1\u03bd\u03af\u03b1.\n\u0393\u03b9\u03b1 \u03c0\u03b9\u03bf \u03b1\u03ba\u03c1\u03b9\u03b2\u03b5\u03af\u03c2 \u03b5\u03be\u03b7\u03b3\u03ae\u03c3\u03b5\u03b9\u03c2 \u03b5\u03c0\u03b9\u03c3\u03ba\u03b5\u03c6\u03b8\u03b5\u03af\u03c4\u03b5 \u03c4\u03b1 [integration docs](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/renault/translations/el.json b/homeassistant/components/renault/translations/el.json index 4f29e856865940..23a73311f71034 100644 --- a/homeassistant/components/renault/translations/el.json +++ b/homeassistant/components/renault/translations/el.json @@ -1,8 +1,23 @@ { "config": { + "abort": { + "kamereon_no_account": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7\u03c2 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd Kamereon" + }, "step": { + "kamereon": { + "data": { + "kamereon_account_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd Kamereon" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd Kamereon" + }, "reauth_confirm": { "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username}" + }, + "user": { + "data": { + "locale": "\u03a4\u03bf\u03c0\u03b9\u03ba\u03ae \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1" + }, + "title": "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03b7\u03c1\u03af\u03c9\u03bd Renault" } } } diff --git a/homeassistant/components/senseme/translations/bg.json b/homeassistant/components/senseme/translations/bg.json index a01a685bf0cb48..e7125511ec3912 100644 --- a/homeassistant/components/senseme/translations/bg.json +++ b/homeassistant/components/senseme/translations/bg.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e" + "already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e", + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" }, "error": { "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", diff --git a/homeassistant/components/senseme/translations/ca.json b/homeassistant/components/senseme/translations/ca.json index ed36968e61568d..6eccc55fa52816 100644 --- a/homeassistant/components/senseme/translations/ca.json +++ b/homeassistant/components/senseme/translations/ca.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "El dispositiu ja est\u00e0 configurat" + "already_configured": "El dispositiu ja est\u00e0 configurat", + "cannot_connect": "Ha fallat la connexi\u00f3" }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3", diff --git a/homeassistant/components/senseme/translations/de.json b/homeassistant/components/senseme/translations/de.json index afacd480ce122d..01463118ec09ed 100644 --- a/homeassistant/components/senseme/translations/de.json +++ b/homeassistant/components/senseme/translations/de.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Ger\u00e4t ist bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen" }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", diff --git a/homeassistant/components/senseme/translations/it.json b/homeassistant/components/senseme/translations/it.json index 5378ff71ef131a..dcdb8bcd728623 100644 --- a/homeassistant/components/senseme/translations/it.json +++ b/homeassistant/components/senseme/translations/it.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "cannot_connect": "Impossibile connettersi" }, "error": { "cannot_connect": "Impossibile connettersi", diff --git a/homeassistant/components/senseme/translations/nl.json b/homeassistant/components/senseme/translations/nl.json index bc058d00b60d49..5ff28250076b1f 100644 --- a/homeassistant/components/senseme/translations/nl.json +++ b/homeassistant/components/senseme/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd", + "cannot_connect": "Kan geen verbinding maken" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/senseme/translations/ru.json b/homeassistant/components/senseme/translations/ru.json index 725169c668a347..8debd33481f537 100644 --- a/homeassistant/components/senseme/translations/ru.json +++ b/homeassistant/components/senseme/translations/ru.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant." + "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." }, "error": { "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", diff --git a/homeassistant/components/senseme/translations/sv.json b/homeassistant/components/senseme/translations/sv.json new file mode 100644 index 00000000000000..46631acc69a79d --- /dev/null +++ b/homeassistant/components/senseme/translations/sv.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cannot_connect": "Det gick inte att ansluta." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senseme/translations/tr.json b/homeassistant/components/senseme/translations/tr.json index 6d316999cb51d8..87c43a08326297 100644 --- a/homeassistant/components/senseme/translations/tr.json +++ b/homeassistant/components/senseme/translations/tr.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131" }, "error": { "cannot_connect": "Ba\u011flanma hatas\u0131", diff --git a/homeassistant/components/senseme/translations/zh-Hant.json b/homeassistant/components/senseme/translations/zh-Hant.json index 56af531b9d54d2..9875ffa97c241f 100644 --- a/homeassistant/components/senseme/translations/zh-Hant.json +++ b/homeassistant/components/senseme/translations/zh-Hant.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "cannot_connect": "\u9023\u7dda\u5931\u6557" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", diff --git a/homeassistant/components/sensor/translations/el.json b/homeassistant/components/sensor/translations/el.json index a9ede7713f386c..9838417fb498e1 100644 --- a/homeassistant/components/sensor/translations/el.json +++ b/homeassistant/components/sensor/translations/el.json @@ -3,6 +3,9 @@ "condition_type": { "is_frequency": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03c3\u03c5\u03c7\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 {entity_name}", "is_gas": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b1\u03ad\u03c1\u03b9\u03bf {entity_name}", + "is_ozone": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03cc\u03b6\u03bf\u03bd\u03c4\u03bf\u03c2 {entity_name}", + "is_pm1": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 PM1 {entity_name}", + "is_pm10": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 PM10 {entity_name}", "is_pm25": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 {entity_name} PM2.5", "is_sulphur_dioxide": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03b4\u03b9\u03bf\u03be\u03b5\u03b9\u03b4\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 \u03b8\u03b5\u03af\u03bf\u03c5 {entity_name}", "is_volatile_organic_compounds": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03c0\u03c4\u03b7\u03c4\u03b9\u03ba\u03ce\u03bd \u03bf\u03c1\u03b3\u03b1\u03bd\u03b9\u03ba\u03ce\u03bd \u03b5\u03bd\u03ce\u03c3\u03b5\u03c9\u03bd {entity_name}" diff --git a/homeassistant/components/shelly/translations/el.json b/homeassistant/components/shelly/translations/el.json index 1d727ded5d9f7e..9a899257addf44 100644 --- a/homeassistant/components/shelly/translations/el.json +++ b/homeassistant/components/shelly/translations/el.json @@ -18,7 +18,8 @@ "button": "\u039a\u03bf\u03c5\u03bc\u03c0\u03af", "button1": "\u03a0\u03c1\u03ce\u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af", "button2": "\u0394\u03b5\u03cd\u03c4\u03b5\u03c1\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af", - "button3": "\u03a4\u03c1\u03af\u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af" + "button3": "\u03a4\u03c1\u03af\u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af", + "button4": "\u03a4\u03ad\u03c4\u03b1\u03c1\u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af" } } } \ No newline at end of file diff --git a/homeassistant/components/shopping_list/translations/el.json b/homeassistant/components/shopping_list/translations/el.json index f5b11bd9d4d24c..e4ad51b4d022c3 100644 --- a/homeassistant/components/shopping_list/translations/el.json +++ b/homeassistant/components/shopping_list/translations/el.json @@ -2,8 +2,10 @@ "config": { "step": { "user": { - "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03bb\u03af\u03c3\u03c4\u03b1 \u03b1\u03b3\u03bf\u03c1\u03ce\u03bd;" + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03bb\u03af\u03c3\u03c4\u03b1 \u03b1\u03b3\u03bf\u03c1\u03ce\u03bd;", + "title": "\u039b\u03af\u03c3\u03c4\u03b1 \u03b1\u03b3\u03bf\u03c1\u03ce\u03bd" } } - } + }, + "title": "\u039b\u03af\u03c3\u03c4\u03b1 \u03b1\u03b3\u03bf\u03c1\u03ce\u03bd" } \ No newline at end of file diff --git a/homeassistant/components/solax/translations/bg.json b/homeassistant/components/solax/translations/bg.json new file mode 100644 index 00000000000000..d6778a65bc7e10 --- /dev/null +++ b/homeassistant/components/solax/translations/bg.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "user": { + "data": { + "ip_address": "IP \u0430\u0434\u0440\u0435\u0441", + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "port": "\u041f\u043e\u0440\u0442" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solax/translations/ca.json b/homeassistant/components/solax/translations/ca.json new file mode 100644 index 00000000000000..7f7ce67da3997c --- /dev/null +++ b/homeassistant/components/solax/translations/ca.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "ip_address": "Adre\u00e7a IP", + "password": "Contrasenya", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solax/translations/de.json b/homeassistant/components/solax/translations/de.json new file mode 100644 index 00000000000000..45c80923777003 --- /dev/null +++ b/homeassistant/components/solax/translations/de.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "ip_address": "IP-Adresse", + "password": "Passwort", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solax/translations/et.json b/homeassistant/components/solax/translations/et.json new file mode 100644 index 00000000000000..e89f90a61191ce --- /dev/null +++ b/homeassistant/components/solax/translations/et.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "ip_address": "IP aadress", + "password": "Salas\u00f5na", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solax/translations/it.json b/homeassistant/components/solax/translations/it.json new file mode 100644 index 00000000000000..e4fa82ddf46770 --- /dev/null +++ b/homeassistant/components/solax/translations/it.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "Impossibile connettersi", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "ip_address": "Indirizzo IP", + "password": "Password", + "port": "Porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solax/translations/ru.json b/homeassistant/components/solax/translations/ru.json new file mode 100644 index 00000000000000..c05b4b56bc4f21 --- /dev/null +++ b/homeassistant/components/solax/translations/ru.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "user": { + "data": { + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solax/translations/sv.json b/homeassistant/components/solax/translations/sv.json new file mode 100644 index 00000000000000..a43a8312e99a2e --- /dev/null +++ b/homeassistant/components/solax/translations/sv.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "Det gick inte att ansluta.", + "unknown": "Ov\u00e4ntat fel" + }, + "step": { + "user": { + "data": { + "ip_address": "IP-adress", + "password": "L\u00f6senord", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solax/translations/tr.json b/homeassistant/components/solax/translations/tr.json new file mode 100644 index 00000000000000..54b4b67c5e7cbc --- /dev/null +++ b/homeassistant/components/solax/translations/tr.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "ip_address": "IP Adresi", + "password": "Parola", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solax/translations/zh-Hant.json b/homeassistant/components/solax/translations/zh-Hant.json new file mode 100644 index 00000000000000..7896b5796edcb6 --- /dev/null +++ b/homeassistant/components/solax/translations/zh-Hant.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "ip_address": "IP \u4f4d\u5740", + "password": "\u5bc6\u78bc", + "port": "\u901a\u8a0a\u57e0" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonos/translations/el.json b/homeassistant/components/sonos/translations/el.json index fa7c08d30dc61e..808556d2a30590 100644 --- a/homeassistant/components/sonos/translations/el.json +++ b/homeassistant/components/sonos/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "not_sonos_device": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Sonos" + }, "step": { "confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03b3\u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Sonos;" diff --git a/homeassistant/components/steamist/translations/bg.json b/homeassistant/components/steamist/translations/bg.json index dfe2f3cef74630..10f6abeb604e7a 100644 --- a/homeassistant/components/steamist/translations/bg.json +++ b/homeassistant/components/steamist/translations/bg.json @@ -10,6 +10,9 @@ }, "flow_title": "{name} ({ipaddress})", "step": { + "discovery_confirm": { + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 {name} ({ipaddress})?" + }, "pick_device": { "data": { "device": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" diff --git a/homeassistant/components/switchbot/translations/el.json b/homeassistant/components/switchbot/translations/el.json new file mode 100644 index 00000000000000..9f795e36734f03 --- /dev/null +++ b/homeassistant/components/switchbot/translations/el.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "no_unconfigured_devices": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03bc\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2." + }, + "flow_title": "{name}", + "step": { + "user": { + "data": { + "mac": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 MAC \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" + }, + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 Switchbot" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "retry_count": "\u0391\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03b5\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03ce\u03bd \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03b5\u03b9\u03ce\u03bd", + "retry_timeout": "\u03a7\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03cc\u03c1\u03b9\u03bf \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03b5\u03c0\u03b1\u03bd\u03b1\u03bb\u03ae\u03c8\u03b5\u03c9\u03bd", + "scan_timeout": "\u03a0\u03cc\u03c3\u03bf\u03c2 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 \u03b4\u03b9\u03b1\u03c6\u03ae\u03bc\u03b9\u03c3\u03b7\u03c2", + "update_time": "\u03a7\u03c1\u03cc\u03bd\u03bf\u03c2 \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03b5\u03c9\u03bd (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/el.json b/homeassistant/components/synology_dsm/translations/el.json index 18cd08b5507ac5..d4cb349c5463a5 100644 --- a/homeassistant/components/synology_dsm/translations/el.json +++ b/homeassistant/components/synology_dsm/translations/el.json @@ -7,6 +7,7 @@ "missing_data": "\u039b\u03b5\u03af\u03c0\u03bf\u03c5\u03bd \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1: \u03b5\u03c0\u03b1\u03bd\u03b1\u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1 \u03ae \u03ac\u03bb\u03bb\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7", "otp_failed": "\u039f \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b4\u03cd\u03bf \u03b2\u03b7\u03bc\u03ac\u03c4\u03c9\u03bd \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5, \u03b5\u03c0\u03b1\u03bd\u03b1\u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03bc\u03b5 \u03bd\u03ad\u03bf \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, + "flow_title": "{name} ({host})", "step": { "2sa": { "data": { @@ -18,6 +19,9 @@ "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ({host});", "title": "Synology DSM" }, + "reauth_confirm": { + "title": "Synology DSM " + }, "user": { "title": "Synology DSM" } diff --git a/homeassistant/components/synology_dsm/translations/sv.json b/homeassistant/components/synology_dsm/translations/sv.json index 3a0f66f1245de2..a6f5c496f2233b 100644 --- a/homeassistant/components/synology_dsm/translations/sv.json +++ b/homeassistant/components/synology_dsm/translations/sv.json @@ -27,6 +27,7 @@ "init": { "data": { "scan_interval": "Minuter mellan skanningar", + "snap_profile_type": "Kvalitetsniv\u00e5 p\u00e5 kamerabilder (0:h\u00f6g 1:medel 2:l\u00e5g)", "timeout": "Timeout (sekunder)" } } diff --git a/homeassistant/components/system_health/translations/it.json b/homeassistant/components/system_health/translations/it.json index b270bd0f2e91d1..a7a583f3f4545d 100644 --- a/homeassistant/components/system_health/translations/it.json +++ b/homeassistant/components/system_health/translations/it.json @@ -1,3 +1,3 @@ { - "title": "Integrit\u00e0 del Sistema" + "title": "Integrit\u00e0 del sistema" } \ No newline at end of file diff --git a/homeassistant/components/tplink/translations/el.json b/homeassistant/components/tplink/translations/el.json index 78f4ac3095d440..ccbc6049794f6f 100644 --- a/homeassistant/components/tplink/translations/el.json +++ b/homeassistant/components/tplink/translations/el.json @@ -1,8 +1,20 @@ { "config": { + "flow_title": "{name} {model} ({host})", "step": { "confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03ad\u03be\u03c5\u03c0\u03bd\u03b5\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 TP-Link;" + }, + "discovery_confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} {model} ({host});" + }, + "pick_device": { + "data": { + "device": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" + } + }, + "user": { + "description": "\u0395\u03ac\u03bd \u03b1\u03c6\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ba\u03b5\u03bd\u03cc, \u03bf \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03bc\u03cc\u03c2 \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd." } } } diff --git a/homeassistant/components/traccar/translations/ca.json b/homeassistant/components/traccar/translations/ca.json index 62c15e0ca20096..3f8a1b423b3c1b 100644 --- a/homeassistant/components/traccar/translations/ca.json +++ b/homeassistant/components/traccar/translations/ca.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "No connectat a Home Assistant Cloud.", "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3.", "webhook_not_internet_accessible": "La teva inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per poder rebre missatges webhook." }, diff --git a/homeassistant/components/traccar/translations/en.json b/homeassistant/components/traccar/translations/en.json index c6d7f0f189245e..5a6d8eb2ccadea 100644 --- a/homeassistant/components/traccar/translations/en.json +++ b/homeassistant/components/traccar/translations/en.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Not connected to Home Assistant Cloud.", "single_instance_allowed": "Already configured. Only a single configuration possible.", "webhook_not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive webhook messages." }, diff --git a/homeassistant/components/traccar/translations/it.json b/homeassistant/components/traccar/translations/it.json index cee10e5f9fe46e..c70dff73bea941 100644 --- a/homeassistant/components/traccar/translations/it.json +++ b/homeassistant/components/traccar/translations/it.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Non connesso a Home Assistant Cloud.", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione.", "webhook_not_internet_accessible": "L'istanza di Home Assistant deve essere accessibile da Internet per ricevere messaggi webhook." }, diff --git a/homeassistant/components/traccar/translations/tr.json b/homeassistant/components/traccar/translations/tr.json index c16dc8c09d2de7..e0657d5fddf2a6 100644 --- a/homeassistant/components/traccar/translations/tr.json +++ b/homeassistant/components/traccar/translations/tr.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Home Assistant Cloud'a ba\u011fl\u0131 de\u011fil.", "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", "webhook_not_internet_accessible": "Webhook mesajlar\u0131n\u0131 alabilmek i\u00e7in Home Assistant \u00f6rne\u011finize internetten eri\u015filebilmelidir." }, diff --git a/homeassistant/components/tuya/translations/select.bg.json b/homeassistant/components/tuya/translations/select.bg.json index 4f702344029935..e0c55131f3129e 100644 --- a/homeassistant/components/tuya/translations/select.bg.json +++ b/homeassistant/components/tuya/translations/select.bg.json @@ -35,6 +35,8 @@ "power_on": "\u0412\u043a\u043b\u044e\u0447\u0435\u043d\u043e" }, "tuya__vacuum_mode": { + "point": "\u0422\u043e\u0447\u043a\u0430", + "pose": "\u041f\u043e\u0437\u0430", "zone": "\u0417\u043e\u043d\u0430" } } diff --git a/homeassistant/components/tuya/translations/select.ca.json b/homeassistant/components/tuya/translations/select.ca.json index d3de5c908684a0..b8511d68338d02 100644 --- a/homeassistant/components/tuya/translations/select.ca.json +++ b/homeassistant/components/tuya/translations/select.ca.json @@ -14,6 +14,11 @@ "0": "Sensibilitat baixa", "1": "Sensibilitat alta" }, + "tuya__fan_angle": { + "30": "30\u00b0", + "60": "60\u00b0", + "90": "90\u00b0" + }, "tuya__fingerbot_mode": { "click": "Polsador", "switch": "Interruptor" diff --git a/homeassistant/components/tuya/translations/select.en.json b/homeassistant/components/tuya/translations/select.en.json index 08756130a79ef3..8e51dd6b352281 100644 --- a/homeassistant/components/tuya/translations/select.en.json +++ b/homeassistant/components/tuya/translations/select.en.json @@ -14,6 +14,11 @@ "0": "Low sensitivity", "1": "High sensitivity" }, + "tuya__fan_angle": { + "30": "30\u00b0", + "60": "60\u00b0", + "90": "90\u00b0" + }, "tuya__fingerbot_mode": { "click": "Push", "switch": "Switch" diff --git a/homeassistant/components/tuya/translations/select.it.json b/homeassistant/components/tuya/translations/select.it.json index 80b86ec495c7f4..575a28b2dc0491 100644 --- a/homeassistant/components/tuya/translations/select.it.json +++ b/homeassistant/components/tuya/translations/select.it.json @@ -14,6 +14,11 @@ "0": "Bassa sensibilit\u00e0", "1": "Alta sensibilit\u00e0" }, + "tuya__fan_angle": { + "30": "30\u00b0", + "60": "60\u00b0", + "90": "90\u00b0" + }, "tuya__fingerbot_mode": { "click": "Spingere", "switch": "Interruttore" diff --git a/homeassistant/components/tuya/translations/select.tr.json b/homeassistant/components/tuya/translations/select.tr.json index ff9cd09fe09e26..52d7dcff5d168e 100644 --- a/homeassistant/components/tuya/translations/select.tr.json +++ b/homeassistant/components/tuya/translations/select.tr.json @@ -14,6 +14,11 @@ "0": "D\u00fc\u015f\u00fck hassasiyet", "1": "Y\u00fcksek hassasiyet" }, + "tuya__fan_angle": { + "30": "30\u00b0", + "60": "60\u00b0", + "90": "90\u00b0" + }, "tuya__fingerbot_mode": { "click": "Bildirim", "switch": "Anahtar" diff --git a/homeassistant/components/twilio/translations/ca.json b/homeassistant/components/twilio/translations/ca.json index 22c5e00a8e7939..c8c1056a81ce0b 100644 --- a/homeassistant/components/twilio/translations/ca.json +++ b/homeassistant/components/twilio/translations/ca.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "No connectat a Home Assistant Cloud.", "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3.", "webhook_not_internet_accessible": "La teva inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per poder rebre missatges webhook." }, diff --git a/homeassistant/components/twilio/translations/en.json b/homeassistant/components/twilio/translations/en.json index 953b807c081d8c..2bf509d5ca1b8e 100644 --- a/homeassistant/components/twilio/translations/en.json +++ b/homeassistant/components/twilio/translations/en.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Not connected to Home Assistant Cloud.", "single_instance_allowed": "Already configured. Only a single configuration possible.", "webhook_not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive webhook messages." }, diff --git a/homeassistant/components/twilio/translations/it.json b/homeassistant/components/twilio/translations/it.json index 591618bfb60997..8fe5f6316f884f 100644 --- a/homeassistant/components/twilio/translations/it.json +++ b/homeassistant/components/twilio/translations/it.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Non connesso a Home Assistant Cloud.", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione.", "webhook_not_internet_accessible": "L'istanza di Home Assistant deve essere accessibile da Internet per ricevere messaggi webhook." }, diff --git a/homeassistant/components/twilio/translations/tr.json b/homeassistant/components/twilio/translations/tr.json index ef684cbc92c480..fa92c795f25db6 100644 --- a/homeassistant/components/twilio/translations/tr.json +++ b/homeassistant/components/twilio/translations/tr.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Home Assistant Cloud'a ba\u011fl\u0131 de\u011fil.", "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", "webhook_not_internet_accessible": "Webhook mesajlar\u0131n\u0131 alabilmek i\u00e7in Home Assistant \u00f6rne\u011finize internetten eri\u015filebilmelidir." }, diff --git a/homeassistant/components/unifi/translations/el.json b/homeassistant/components/unifi/translations/el.json index a0711b5b506ef2..99ca69f9724490 100644 --- a/homeassistant/components/unifi/translations/el.json +++ b/homeassistant/components/unifi/translations/el.json @@ -4,6 +4,9 @@ "already_configured": "\u039f \u03b9\u03c3\u03c4\u03cc\u03c4\u03bf\u03c0\u03bf\u03c2 \u03c4\u03bf\u03c5 UniFi Network \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "configuration_updated": "\u0397 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5." }, + "error": { + "unknown_client_mac": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03bf\u03c2 \u03c0\u03b5\u03bb\u03ac\u03c4\u03b7\u03c2 \u03c3\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 MAC" + }, "flow_title": "{site} ({host})", "step": { "user": { @@ -15,22 +18,30 @@ "step": { "client_control": { "data": { + "block_client": "\u03a0\u03b5\u03bb\u03ac\u03c4\u03b5\u03c2 \u03bc\u03b5 \u03b5\u03bb\u03b5\u03b3\u03c7\u03cc\u03bc\u03b5\u03bd\u03b7 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", "dpi_restrictions": "\u039d\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03b5\u03c4\u03b1\u03b9 \u03bf \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03c9\u03bd \u03bf\u03bc\u03ac\u03b4\u03c9\u03bd \u03c0\u03b5\u03c1\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd DPI", "poe_clients": "\u039d\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03b5\u03c4\u03b1\u03b9 \u03bf \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 POE \u03c4\u03c9\u03bd \u03c0\u03b5\u03bb\u03b1\u03c4\u03ce\u03bd" - } + }, + "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03c9\u03bd \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03c9\u03bd \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03bf\u03c5 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae-\u03c0\u03b5\u03bb\u03ac\u03c4\u03b7\n\n\u0394\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03c4\u03b5 \u03b4\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03bf\u03cd\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03bf\u03cd\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf\u03c5\u03c2 \u03bf\u03c0\u03bf\u03af\u03bf\u03c5\u03c2 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03bb\u03ad\u03b3\u03be\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf.", + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 UniFi 2/3" }, "device_tracker": { "data": { "ignore_wired_bug": "\u0391\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03bb\u03bf\u03b3\u03b9\u03ba\u03ae\u03c2 \u03b5\u03bd\u03c3\u03cd\u03c1\u03bc\u03b1\u03c4\u03c9\u03bd \u03c3\u03c6\u03b1\u03bb\u03bc\u03ac\u03c4\u03c9\u03bd \u03c4\u03bf\u03c5 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 UniFi", "ssid_filter": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 SSID \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03b5\u03af\u03c4\u03b5 \u03b1\u03c3\u03cd\u03c1\u03bc\u03b1\u03c4\u03b1 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ad\u03c2-\u03c0\u03b5\u03bb\u03ac\u03c4\u03b5\u03c2" }, - "description": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03bc\u03bf\u03cd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" + "description": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03bc\u03bf\u03cd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 UniFi 1/3" + }, + "simple_options": { + "description": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 UniFi" }, "statistics_sensors": { "data": { "allow_uptime_sensors": "\u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b5\u03c2 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c5 \u03c3\u03c5\u03bd\u03b5\u03c7\u03bf\u03cd\u03c2 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c0\u03b5\u03bb\u03ac\u03c4\u03b5\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5" }, - "description": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03c9\u03bd \u03c3\u03c4\u03b1\u03c4\u03b9\u03c3\u03c4\u03b9\u03ba\u03ce\u03bd \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03c9\u03bd" + "description": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03c9\u03bd \u03c3\u03c4\u03b1\u03c4\u03b9\u03c3\u03c4\u03b9\u03ba\u03ce\u03bd \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03c9\u03bd", + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 UniFi 3/3" } } } diff --git a/homeassistant/components/unifiprotect/translations/sv.json b/homeassistant/components/unifiprotect/translations/sv.json new file mode 100644 index 00000000000000..e2bfaa9118cc41 --- /dev/null +++ b/homeassistant/components/unifiprotect/translations/sv.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "discovery_confirm": { + "title": "UniFi Protect uppt\u00e4ckt" + }, + "user": { + "description": "Du beh\u00f6ver en lokal anv\u00e4ndare skapad i din UniFi OS-konsol f\u00f6r att logga in med. Ubiquiti Cloud-anv\u00e4ndare kommer inte att fungera. F\u00f6r mer information: {local_user_documentation_url}" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upb/translations/el.json b/homeassistant/components/upb/translations/el.json new file mode 100644 index 00000000000000..27cedabdefac6f --- /dev/null +++ b/homeassistant/components/upb/translations/el.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "invalid_upb_file": "\u039b\u03b5\u03af\u03c0\u03b5\u03b9 \u03ae \u03b5\u03af\u03bd\u03b1\u03b9 \u03ac\u03ba\u03c5\u03c1\u03bf \u03c4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03b5\u03be\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2 UPB UPStart, \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c4\u03bf\u03c5 \u03b1\u03c1\u03c7\u03b5\u03af\u03bf\u03c5." + }, + "step": { + "user": { + "data": { + "address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 (\u03b2\u03bb\u03ad\u03c0\u03b5 \u03c0\u03b5\u03c1\u03b9\u03b3\u03c1\u03b1\u03c6\u03ae \u03c0\u03b1\u03c1\u03b1\u03c0\u03ac\u03bd\u03c9)", + "file_path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03ba\u03b1\u03b9 \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c4\u03bf\u03c5 \u03b1\u03c1\u03c7\u03b5\u03af\u03bf\u03c5 \u03b5\u03be\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2 UPStart UPB.", + "protocol": "\u03a0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf" + }, + "description": "\u03a3\u03c5\u03bd\u03b4\u03ad\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03b4\u03b9\u03b1\u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 Universal Powerline Bus Powerline Interface Module (UPB PIM). \u0397 \u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03b4\u03b9\u03b5\u03c5\u03b8\u03cd\u03bd\u03c3\u03b5\u03c9\u03bd \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae 'address[:port]' \u03b3\u03b9\u03b1 'tcp'. \u0397 \u03b8\u03cd\u03c1\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ae \u03ba\u03b1\u03b9 \u03b7 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03c4\u03b9\u03bc\u03ae \u03b5\u03af\u03bd\u03b1\u03b9 2101. \u03a0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3\u03bc\u03b1: '192.168.1.42'. \u0393\u03b9\u03b1 \u03c4\u03bf \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc \u03c0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf, \u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae 'tty[:baud]'. \u03a4\u03bf baud \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03ba\u03b1\u03b9 \u03b7 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03b5\u03af\u03bd\u03b1\u03b9 4800. \u03a0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3\u03bc\u03b1: '/dev/ttyS1'.", + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf UPB PIM" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/el.json b/homeassistant/components/upnp/translations/el.json new file mode 100644 index 00000000000000..ba35bb730828fa --- /dev/null +++ b/homeassistant/components/upnp/translations/el.json @@ -0,0 +1,16 @@ +{ + "config": { + "flow_title": "{name}", + "step": { + "ssdp_confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae UPnP/IGD;" + }, + "user": { + "data": { + "scan_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7\u03c2 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1, \u03b5\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf 30)", + "usn": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/uptimerobot/translations/sensor.ca.json b/homeassistant/components/uptimerobot/translations/sensor.ca.json new file mode 100644 index 00000000000000..448590b7c0f874 --- /dev/null +++ b/homeassistant/components/uptimerobot/translations/sensor.ca.json @@ -0,0 +1,11 @@ +{ + "state": { + "uptimerobot__monitor_status": { + "down": "Caigut", + "not_checked_yet": "No comprovat", + "pause": "En pausa", + "seems_down": "Sembla caigut", + "up": "Funcionant" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/uptimerobot/translations/sensor.it.json b/homeassistant/components/uptimerobot/translations/sensor.it.json new file mode 100644 index 00000000000000..260ba6fd7a1d5b --- /dev/null +++ b/homeassistant/components/uptimerobot/translations/sensor.it.json @@ -0,0 +1,11 @@ +{ + "state": { + "uptimerobot__monitor_status": { + "down": "Gi\u00f9", + "not_checked_yet": "Non ancora controllato", + "pause": "Pausa", + "seems_down": "Sembra non funzionante", + "up": "Su" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/uptimerobot/translations/sensor.tr.json b/homeassistant/components/uptimerobot/translations/sensor.tr.json new file mode 100644 index 00000000000000..b5ee9cfd01f922 --- /dev/null +++ b/homeassistant/components/uptimerobot/translations/sensor.tr.json @@ -0,0 +1,11 @@ +{ + "state": { + "uptimerobot__monitor_status": { + "down": "A\u015fa\u011f\u0131", + "not_checked_yet": "Hen\u00fcz kontrol edilmedi", + "pause": "Duraklat", + "seems_down": "A\u015fa\u011f\u0131 g\u00f6r\u00fcn\u00fcyor", + "up": "Yukar\u0131" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vera/translations/el.json b/homeassistant/components/vera/translations/el.json new file mode 100644 index 00000000000000..43dcd322834f80 --- /dev/null +++ b/homeassistant/components/vera/translations/el.json @@ -0,0 +1,22 @@ +{ + "config": { + "step": { + "user": { + "description": "\u0394\u03ce\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c4\u03bf\u03c5 \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ae Vera \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9. \u0398\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03bc\u03bf\u03b9\u03ac\u03b6\u03b5\u03b9 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc: http://192.168.1.161:3480.", + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ae Vera" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "exclude": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03ac \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd Vera \u03c0\u03c1\u03bf\u03c2 \u03b5\u03be\u03b1\u03af\u03c1\u03b5\u03c3\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf Home Assistant.", + "lights": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03ac \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd \u03b4\u03b9\u03b1\u03ba\u03bf\u03c0\u03c4\u03ce\u03bd Vera \u03b3\u03b9\u03b1 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c9\u03c2 \u03c6\u03ce\u03c4\u03b1 \u03c3\u03c4\u03bf Home Assistant." + }, + "description": "\u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7 vera \u03b3\u03b9\u03b1 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2 \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ad\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03bf\u03c5\u03c2: https://www.home-assistant.io/integrations/vera/. \u03a3\u03b7\u03bc\u03b5\u03af\u03c9\u03c3\u03b7: \u039f\u03c0\u03bf\u03b9\u03b1\u03b4\u03ae\u03c0\u03bf\u03c4\u03b5 \u03b1\u03bb\u03bb\u03b1\u03b3\u03ae \u03b5\u03b4\u03ce \u03b8\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 home assistant server. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c8\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c4\u03b9\u03bc\u03ad\u03c2, \u03b4\u03ce\u03c3\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03ba\u03b5\u03bd\u03cc.", + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ae Vera" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vilfo/translations/el.json b/homeassistant/components/vilfo/translations/el.json new file mode 100644 index 00000000000000..d92df8c0341ec1 --- /dev/null +++ b/homeassistant/components/vilfo/translations/el.json @@ -0,0 +1,10 @@ +{ + "config": { + "step": { + "user": { + "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae Vilfo. \u03a7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c3\u03c4\u03b5 \u03c4\u03bf hostname/IP \u03c4\u03bf\u03c5 Vilfo Router \u03ba\u03b1\u03b9 \u03ad\u03bd\u03b1 \u03ba\u03bf\u03c5\u03c0\u03cc\u03bd\u03b9 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 API. \u0393\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03c0\u03ce\u03c2 \u03bd\u03b1 \u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ad\u03c2 \u03c4\u03b9\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2, \u03b5\u03c0\u03b9\u03c3\u03ba\u03b5\u03c6\u03b8\u03b5\u03af\u03c4\u03b5: https://www.home-assistant.io/integrations/vilfo", + "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae Vilfo" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vizio/translations/el.json b/homeassistant/components/vizio/translations/el.json index dbc2c9b5ed35ec..13b26b44b76797 100644 --- a/homeassistant/components/vizio/translations/el.json +++ b/homeassistant/components/vizio/translations/el.json @@ -3,14 +3,21 @@ "abort": { "updated_entry": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af, \u03b1\u03bb\u03bb\u03ac \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1, \u03bf\u03b9 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ad\u03c2 \u03ae/\u03ba\u03b1\u03b9 \u03bf\u03b9 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03c0\u03bf\u03c5 \u03bf\u03c1\u03af\u03b6\u03bf\u03bd\u03c4\u03b1\u03b9 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b4\u03b5\u03bd \u03c4\u03b1\u03b9\u03c1\u03b9\u03ac\u03b6\u03bf\u03c5\u03bd \u03bc\u03b5 \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03b7\u03b3\u03bf\u03c5\u03bc\u03ad\u03bd\u03c9\u03c2 \u03b5\u03b9\u03c3\u03b1\u03c7\u03b8\u03b5\u03af\u03c3\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7, \u03bf\u03c0\u03cc\u03c4\u03b5 \u03b7 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03c9\u03b8\u03b5\u03af \u03b1\u03bd\u03b1\u03bb\u03cc\u03b3\u03c9\u03c2." }, + "error": { + "complete_pairing_failed": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03ae\u03c1\u03c9\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03b1\u03bd\u03c4\u03b9\u03c3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7\u03c2. \u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03bf PIN \u03c0\u03bf\u03c5 \u03b4\u03ce\u03c3\u03b1\u03c4\u03b5 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c9\u03c3\u03c4\u03cc\u03c2 \u03ba\u03b1\u03b9 \u03cc\u03c4\u03b9 \u03b7 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7 \u03b5\u03be\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03b5\u03af \u03bd\u03b1 \u03c4\u03c1\u03bf\u03c6\u03bf\u03b4\u03bf\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03c1\u03b5\u03cd\u03bc\u03b1 \u03ba\u03b1\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03b7 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf \u03c0\u03c1\u03b9\u03bd \u03c4\u03b7\u03bd \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae." + }, "step": { "pair_tv": { "description": "\u0397 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae \u03c3\u03b1\u03c2 \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03b9 \u03ad\u03bd\u03b1\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc. \u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c3\u03c4\u03b7 \u03c6\u03cc\u03c1\u03bc\u03b1 \u03ba\u03b1\u03b9, \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03bf \u03b2\u03ae\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03ce\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7." }, + "pairing_complete_import": { + "title": "\u039f\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7" + }, "user": { "data": { "device_class": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" - } + }, + "description": "\u0388\u03bd\u03b1 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03bc\u03cc\u03bd\u03bf \u03b3\u03b9\u03b1 \u03c4\u03b9\u03c2 \u03c4\u03b7\u03bb\u03b5\u03bf\u03c1\u03ac\u03c3\u03b5\u03b9\u03c2. \u0395\u03ac\u03bd \u03c1\u03c5\u03b8\u03bc\u03af\u03b6\u03b5\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03b1\u03ba\u03cc\u03bc\u03b7 \u03ad\u03bd\u03b1 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2, \u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03b5\u03c1\u03ac\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c0\u03cc \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b1\u03b4\u03b9\u03ba\u03b1\u03c3\u03af\u03b1 \u03b1\u03bd\u03c4\u03b9\u03c3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7\u03c2." } } } diff --git a/homeassistant/components/watttime/translations/el.json b/homeassistant/components/watttime/translations/el.json new file mode 100644 index 00000000000000..5c7ba4adb9de92 --- /dev/null +++ b/homeassistant/components/watttime/translations/el.json @@ -0,0 +1,22 @@ +{ + "config": { + "error": { + "unknown_coordinates": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03bd \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 \u03b3\u03b9\u03b1 \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2/\u03bc\u03ae\u03ba\u03bf\u03c2" + }, + "step": { + "coordinates": { + "data": { + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2" + }, + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2 \u03ba\u03b1\u03b9 \u03bc\u03ae\u03ba\u03bf\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03b5\u03af\u03c4\u03b5:" + }, + "location": { + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03b3\u03b9\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7:" + }, + "user": { + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2:" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/webostv/translations/nl.json b/homeassistant/components/webostv/translations/nl.json index f7325c02cff85c..2a9fb59e1c1214 100644 --- a/homeassistant/components/webostv/translations/nl.json +++ b/homeassistant/components/webostv/translations/nl.json @@ -39,7 +39,8 @@ "data": { "sources": "Bronnenlijst" }, - "description": "Selecteer ingeschakelde bronnen" + "description": "Selecteer ingeschakelde bronnen", + "title": "Opties voor WebOS Smart TV" } } } diff --git a/homeassistant/components/wiffi/translations/el.json b/homeassistant/components/wiffi/translations/el.json index ae15bb06d19607..23e59bbc57f2d2 100644 --- a/homeassistant/components/wiffi/translations/el.json +++ b/homeassistant/components/wiffi/translations/el.json @@ -1,7 +1,14 @@ { "config": { "abort": { - "already_configured": "\u0397 \u03b8\u03cd\u03c1\u03b1 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af." + "addr_in_use": "\u0397 \u03b8\u03cd\u03c1\u03b1 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7.", + "already_configured": "\u0397 \u03b8\u03cd\u03c1\u03b1 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af.", + "start_server_failed": "\u0397 \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5." + }, + "step": { + "user": { + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae TCP \u03b3\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 WIFFI" + } } }, "options": { diff --git a/homeassistant/components/wled/translations/sv.json b/homeassistant/components/wled/translations/sv.json index aea858c5bfcbbb..a795bd523591f3 100644 --- a/homeassistant/components/wled/translations/sv.json +++ b/homeassistant/components/wled/translations/sv.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Enheten har redan konfigurerats" + "already_configured": "Enheten har redan konfigurerats", + "cct_unsupported": "Denna WLED-enhet anv\u00e4nder CCT-kanaler, vilket inte st\u00f6ds av denna integration" }, "flow_title": "WLED: {name}", "step": { diff --git a/homeassistant/components/xiaomi_miio/translations/el.json b/homeassistant/components/xiaomi_miio/translations/el.json index c5c8d79c281d18..23994c6296f37b 100644 --- a/homeassistant/components/xiaomi_miio/translations/el.json +++ b/homeassistant/components/xiaomi_miio/translations/el.json @@ -12,6 +12,20 @@ }, "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf\u03bd 32 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b5\u03c2 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API, \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \u03b3\u03b9\u03b1 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2. \u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03b1\u03c0\u03cc \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Xiaomi Aqara.\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf\u03bd 32 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b5\u03c2 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API, \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \u03b3\u03b9\u03b1 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2. \u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03b1\u03c0\u03cc \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Xiaomi Aqara.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Xiaomi Miio \u03ae \u03c0\u03cd\u03bb\u03b7 Xiaomi" + }, + "gateway": { + "data": { + "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c4\u03b7\u03c2 \u03c0\u03cd\u03bb\u03b7\u03c2" + }, + "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf\u03c5\u03c2 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b5\u03c2 32 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API , \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u03b3\u03b9\u03b1 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2. \u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03b1\u03c0\u03cc \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Xiaomi Aqara.", + "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03cd\u03bb\u03b7 Xiaomi" + }, + "user": { + "data": { + "gateway": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03cd\u03bb\u03b7 Xiaomi" + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c3\u03b5 \u03c0\u03bf\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5.", + "title": "Xiaomi Miio" } } } diff --git a/homeassistant/components/xiaomi_miio/translations/select.el.json b/homeassistant/components/xiaomi_miio/translations/select.el.json new file mode 100644 index 00000000000000..24c8a0376764aa --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/select.el.json @@ -0,0 +1,9 @@ +{ + "state": { + "xiaomi_miio__led_brightness": { + "bright": "\u03a6\u03c9\u03c4\u03b5\u03b9\u03bd\u03cc", + "dim": "\u03a7\u03b1\u03bc\u03b7\u03bb\u03cc", + "off": "\u0391\u03bd\u03b5\u03bd\u03b5\u03c1\u03b3\u03cc" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yale_smart_alarm/translations/nl.json b/homeassistant/components/yale_smart_alarm/translations/nl.json index cdde7efe151676..04dbe9245c1922 100644 --- a/homeassistant/components/yale_smart_alarm/translations/nl.json +++ b/homeassistant/components/yale_smart_alarm/translations/nl.json @@ -34,7 +34,8 @@ "step": { "init": { "data": { - "code": "Standaardcode voor sloten, gebruikt als er geen is opgegeven" + "code": "Standaardcode voor sloten, gebruikt als er geen is opgegeven", + "lock_code_digits": "Aantal cijfers in PIN code voor sloten" } } } diff --git a/homeassistant/components/zwave_js/translations/el.json b/homeassistant/components/zwave_js/translations/el.json index 56683e692c9e3e..43f004625156b6 100644 --- a/homeassistant/components/zwave_js/translations/el.json +++ b/homeassistant/components/zwave_js/translations/el.json @@ -19,7 +19,16 @@ } }, "device_automation": { + "condition_type": { + "value": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03c4\u03b9\u03bc\u03ae \u03bc\u03b9\u03b1\u03c2 \u03c4\u03b9\u03bc\u03ae\u03c2 Z-Wave" + }, "trigger_type": { + "event.notification.entry_control": "\u0391\u03c0\u03bf\u03c3\u03c4\u03bf\u03bb\u03ae \u03b5\u03b9\u03b4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5", + "event.notification.notification": "\u0391\u03c0\u03bf\u03c3\u03c4\u03bf\u03bb\u03ae \u03b5\u03b9\u03b4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2", + "event.value_notification.basic": "\u0392\u03b1\u03c3\u03b9\u03ba\u03cc \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd CC \u03c3\u03c4\u03bf {subtype}", + "event.value_notification.central_scene": "\u0394\u03c1\u03ac\u03c3\u03b7 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03ae\u03c2 \u03c3\u03ba\u03b7\u03bd\u03ae\u03c2 \u03c3\u03c4\u03bf {subtype}", + "event.value_notification.scene_activation": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c3\u03ba\u03b7\u03bd\u03ae\u03c2 \u03c3\u03c4\u03bf {subtype}", + "state.node_status": "\u0397 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03ba\u03cc\u03bc\u03b2\u03bf\u03c5 \u03ac\u03bb\u03bb\u03b1\u03be\u03b5", "zwave_js.value_updated.config_parameter": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ae \u03c4\u03b9\u03bc\u03ae\u03c2 \u03c3\u03c4\u03b7\u03bd \u03c0\u03b1\u03c1\u03ac\u03bc\u03b5\u03c4\u03c1\u03bf config {subtype}", "zwave_js.value_updated.value": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ae \u03c4\u03b9\u03bc\u03ae\u03c2 \u03c3\u03b5 \u03c4\u03b9\u03bc\u03ae Z-Wave JS" } From dc1db463fc7fe44e8f642b000d5c51253671cbe7 Mon Sep 17 00:00:00 2001 From: Brandon Rothweiler Date: Wed, 26 Jan 2022 22:40:53 -0500 Subject: [PATCH 0005/1098] Bump pymazda to 0.3.2 (#65025) --- homeassistant/components/mazda/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/mazda/manifest.json b/homeassistant/components/mazda/manifest.json index 158bed0466d229..e00049101f9088 100644 --- a/homeassistant/components/mazda/manifest.json +++ b/homeassistant/components/mazda/manifest.json @@ -3,7 +3,7 @@ "name": "Mazda Connected Services", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/mazda", - "requirements": ["pymazda==0.3.1"], + "requirements": ["pymazda==0.3.2"], "codeowners": ["@bdr99"], "quality_scale": "platinum", "iot_class": "cloud_polling" diff --git a/requirements_all.txt b/requirements_all.txt index b834a9fcb9d0be..b007468e11c3ca 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1660,7 +1660,7 @@ pymailgunner==1.4 pymata-express==1.19 # homeassistant.components.mazda -pymazda==0.3.1 +pymazda==0.3.2 # homeassistant.components.mediaroom pymediaroom==0.6.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5c26847aacc1d3..183692bb465e0a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1041,7 +1041,7 @@ pymailgunner==1.4 pymata-express==1.19 # homeassistant.components.mazda -pymazda==0.3.1 +pymazda==0.3.2 # homeassistant.components.melcloud pymelcloud==2.5.6 From 2325cacb0436279c00ee058934b12f46f0afc450 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 26 Jan 2022 22:50:20 -0600 Subject: [PATCH 0006/1098] Bump flux_led to fix push updates on newer devices (#65011) --- homeassistant/components/flux_led/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/flux_led/manifest.json b/homeassistant/components/flux_led/manifest.json index f23daf5d05315c..ac324431ba6dbe 100644 --- a/homeassistant/components/flux_led/manifest.json +++ b/homeassistant/components/flux_led/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "dependencies": ["network"], "documentation": "https://www.home-assistant.io/integrations/flux_led", - "requirements": ["flux_led==0.28.11"], + "requirements": ["flux_led==0.28.17"], "quality_scale": "platinum", "codeowners": ["@icemanch", "@bdraco"], "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index b007468e11c3ca..c1973e5989a717 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -681,7 +681,7 @@ fjaraskupan==1.0.2 flipr-api==1.4.1 # homeassistant.components.flux_led -flux_led==0.28.11 +flux_led==0.28.17 # homeassistant.components.homekit fnvhash==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 183692bb465e0a..1e929340cdd718 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -427,7 +427,7 @@ fjaraskupan==1.0.2 flipr-api==1.4.1 # homeassistant.components.flux_led -flux_led==0.28.11 +flux_led==0.28.17 # homeassistant.components.homekit fnvhash==0.1.0 From f6c679699f950073092ec403d8d832f253876cdc Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 27 Jan 2022 05:52:09 +0100 Subject: [PATCH 0007/1098] Add plugin option [hassfest] (#65024) --- mypy.ini | 2 +- script/hassfest/__main__.py | 32 +++++++++++++++++++++++++++++++- script/hassfest/model.py | 1 + script/hassfest/mypy_config.py | 2 +- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/mypy.ini b/mypy.ini index 886b0fce2ceda4..f41ebd8b1ce3d5 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,6 +1,6 @@ # Automatically generated by hassfest. # -# To update, run python3 -m script.hassfest +# To update, run python3 -m script.hassfest -p mypy_config [mypy] python_version = 3.9 diff --git a/script/hassfest/__main__.py b/script/hassfest/__main__.py index d4935196cc738d..8a8e1155ab9550 100644 --- a/script/hassfest/__main__.py +++ b/script/hassfest/__main__.py @@ -43,6 +43,11 @@ mypy_config, ] +ALL_PLUGIN_NAMES = [ + plugin.__name__.rsplit(".", maxsplit=1)[-1] + for plugin in (*INTEGRATION_PLUGINS, *HASS_PLUGINS) +] + def valid_integration_path(integration_path): """Test if it's a valid integration.""" @@ -53,6 +58,17 @@ def valid_integration_path(integration_path): return path +def validate_plugins(plugin_names: str) -> list[str]: + """Split and validate plugin names.""" + all_plugin_names = set(ALL_PLUGIN_NAMES) + plugins = plugin_names.split(",") + for plugin in plugins: + if plugin not in all_plugin_names: + raise argparse.ArgumentTypeError(f"{plugin} is not a valid plugin name") + + return plugins + + def get_config() -> Config: """Return config.""" parser = argparse.ArgumentParser(description="Hassfest") @@ -70,6 +86,13 @@ def get_config() -> Config: action="store_true", help="Validate requirements", ) + parser.add_argument( + "-p", + "--plugins", + type=validate_plugins, + default=ALL_PLUGIN_NAMES, + help="Comma-separate list of plugins to run. Valid plugin names: %(default)s", + ) parsed = parser.parse_args() if parsed.action is None: @@ -91,6 +114,7 @@ def get_config() -> Config: specific_integrations=parsed.integration_path, action=parsed.action, requirements=parsed.requirements, + plugins=set(parsed.plugins), ) @@ -117,9 +141,12 @@ def main(): plugins += HASS_PLUGINS for plugin in plugins: + plugin_name = plugin.__name__.rsplit(".", maxsplit=1)[-1] + if plugin_name not in config.plugins: + continue try: start = monotonic() - print(f"Validating {plugin.__name__.split('.')[-1]}...", end="", flush=True) + print(f"Validating {plugin_name}...", end="", flush=True) if ( plugin is requirements and config.requirements @@ -161,6 +188,9 @@ def main(): if config.action == "generate": for plugin in plugins: + plugin_name = plugin.__name__.rsplit(".", maxsplit=1)[-1] + if plugin_name not in config.plugins: + continue if hasattr(plugin, "generate"): plugin.generate(integrations, config) return 0 diff --git a/script/hassfest/model.py b/script/hassfest/model.py index 69810686cc16df..7006c1e6032604 100644 --- a/script/hassfest/model.py +++ b/script/hassfest/model.py @@ -32,6 +32,7 @@ class Config: requirements: bool = attr.ib() errors: list[Error] = attr.ib(factory=list) cache: dict[str, Any] = attr.ib(factory=dict) + plugins: set[str] = attr.ib(factory=set) def add_error(self, *args: Any, **kwargs: Any) -> None: """Add an error.""" diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index d2bd437c2d9d5f..06c1353ce73a20 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -90,7 +90,7 @@ HEADER: Final = """ # Automatically generated by hassfest. # -# To update, run python3 -m script.hassfest +# To update, run python3 -m script.hassfest -p mypy_config """.lstrip() From 0cabf3e5770d69ccb6e246bca678c477f31f7a3d Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Thu, 27 Jan 2022 08:41:27 +0100 Subject: [PATCH 0008/1098] Fix MQTT climate action null warnings (#64658) --- homeassistant/components/mqtt/climate.py | 8 ++++++++ tests/components/mqtt/test_climate.py | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index 5ac43d5131694b..2e19a345bc39e4 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -125,6 +125,8 @@ CONF_TEMP_MIN = "min_temp" CONF_TEMP_STEP = "temp_step" +PAYLOAD_NONE = "None" + MQTT_CLIMATE_ATTRIBUTES_BLOCKED = frozenset( { climate.ATTR_AUX_HEAT, @@ -441,6 +443,12 @@ def handle_action_received(msg): if payload in CURRENT_HVAC_ACTIONS: self._action = payload self.async_write_ha_state() + elif not payload or payload == PAYLOAD_NONE: + _LOGGER.debug( + "Invalid %s action: %s, ignoring", + CURRENT_HVAC_ACTIONS, + payload, + ) else: _LOGGER.warning( "Invalid %s action: %s", diff --git a/tests/components/mqtt/test_climate.py b/tests/components/mqtt/test_climate.py index 0f4f9b209c67fa..3b2da69f94b835 100644 --- a/tests/components/mqtt/test_climate.py +++ b/tests/components/mqtt/test_climate.py @@ -900,6 +900,15 @@ async def test_get_with_templates(hass, mqtt_mock, caplog): state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("hvac_action") == "cooling" + # Test ignoring null values + async_fire_mqtt_message(hass, "action", "null") + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("hvac_action") == "cooling" + assert ( + "Invalid ['off', 'heating', 'cooling', 'drying', 'idle', 'fan'] action: None, ignoring" + in caplog.text + ) + async def test_set_with_templates(hass, mqtt_mock, caplog): """Test setting various attributes with templates.""" From b086c8d89847288670f105a932026dfcf538c414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 27 Jan 2022 10:03:45 +0100 Subject: [PATCH 0009/1098] Set ping data to None instead of False (#65013) --- homeassistant/components/ping/binary_sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/ping/binary_sensor.py b/homeassistant/components/ping/binary_sensor.py index 97e2aff7bffc2e..ff88412a6ef8d4 100644 --- a/homeassistant/components/ping/binary_sensor.py +++ b/homeassistant/components/ping/binary_sensor.py @@ -140,7 +140,7 @@ async def async_added_to_hass(self): self._available = True if last_state is None or last_state.state != STATE_ON: - self._ping.data = False + self._ping.data = None return attributes = last_state.attributes From fa62e7e142369d56da2f859068a1a5c8739aad55 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Thu, 27 Jan 2022 12:00:38 +0200 Subject: [PATCH 0010/1098] Fix Shelly battery powered devices unavailable (#65031) --- homeassistant/components/shelly/entity.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index a8546cb43dd6dd..12f82016a1c084 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -562,6 +562,11 @@ def __init__( self.block: Block | None = block # type: ignore[assignment] self.entity_description = description + self._attr_should_poll = False + self._attr_device_info = DeviceInfo( + connections={(device_registry.CONNECTION_NETWORK_MAC, wrapper.mac)} + ) + if block is not None: self._attr_unique_id = f"{self.wrapper.mac}-{block.description}-{attribute}" self._attr_name = get_block_entity_name( From 3daaed10565e7527e7aed3427159ca2021c502b0 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 27 Jan 2022 02:02:27 -0800 Subject: [PATCH 0011/1098] Catch connection reset error (#65027) --- homeassistant/components/hassio/ingress.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/hassio/ingress.py b/homeassistant/components/hassio/ingress.py index 91aca4859174f0..284ba42b3c11e9 100644 --- a/homeassistant/components/hassio/ingress.py +++ b/homeassistant/components/hassio/ingress.py @@ -154,7 +154,11 @@ async def _handle_request( async for data in result.content.iter_chunked(4096): await response.write(data) - except (aiohttp.ClientError, aiohttp.ClientPayloadError) as err: + except ( + aiohttp.ClientError, + aiohttp.ClientPayloadError, + ConnectionResetError, + ) as err: _LOGGER.debug("Stream error %s / %s: %s", token, path, err) return response From 9d404b749a0aa0d0527e145c911559df5ecf2afd Mon Sep 17 00:00:00 2001 From: Patrik Lindgren <21142447+ggravlingen@users.noreply.github.com> Date: Thu, 27 Jan 2022 11:12:52 +0100 Subject: [PATCH 0012/1098] Implement coordinator class for Tradfri integration (#64166) * Initial commit coordinator * More coordinator implementation * More coordinator implementation * Allow integration reload * Move API calls to try/catch block * Move back fixture * Remove coordinator test file * Ensure unchanged file * Ensure unchanged conftest.py file * Remove coordinator key check * Apply suggestions from code review Co-authored-by: Martin Hjelmare * Import RequestError * Move async_setup_platforms to end of setup_entry * Remove centralised handling of device data and device controllers * Remove platform_type argument * Remove exception * Remove the correct exception * Refactor coordinator error handling * Apply suggestions from code review Co-authored-by: Martin Hjelmare * Remove platform type from base class * Remove timeout context manager * Refactor exception callback * Simplify starting device observation * Update test * Move observe start into update method * Remove await self.coordinator.async_request_refresh() * Refactor cover.py * Uncomment const.py * Add back extra_state_attributes * Update homeassistant/components/tradfri/coordinator.py Co-authored-by: Martin Hjelmare * Refactor switch platform * Expose switch state * Refactor sensor platform * Put back accidentally deleted code * Add set_hub_available * Apply suggestions from code review Co-authored-by: Martin Hjelmare * Fix tests for fan platform * Update homeassistant/components/tradfri/base_class.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/tradfri/base_class.py Co-authored-by: Martin Hjelmare * Fix non-working tests * Refresh sensor state * Remove commented line * Add group coordinator * Add groups during setup * Refactor light platform * Fix tests * Move outside of try...except * Remove error handler * Remove unneeded methods * Update sensor * Update .coveragerc * Move signal * Add signals for groups * Fix signal Co-authored-by: Martin Hjelmare --- .coveragerc | 1 + homeassistant/components/tradfri/__init__.py | 76 +++++++-- .../components/tradfri/base_class.py | 134 ++++------------ homeassistant/components/tradfri/const.py | 6 + .../components/tradfri/coordinator.py | 145 ++++++++++++++++++ homeassistant/components/tradfri/cover.py | 54 ++++--- homeassistant/components/tradfri/fan.py | 47 +++--- homeassistant/components/tradfri/light.py | 110 +++++++------ homeassistant/components/tradfri/sensor.py | 54 ++++--- homeassistant/components/tradfri/switch.py | 42 +++-- tests/components/tradfri/common.py | 2 + tests/components/tradfri/test_fan.py | 1 - tests/components/tradfri/test_light.py | 23 ++- 13 files changed, 448 insertions(+), 247 deletions(-) create mode 100644 homeassistant/components/tradfri/coordinator.py diff --git a/.coveragerc b/.coveragerc index c301d4e30d3fae..78ef0996c53daf 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1193,6 +1193,7 @@ omit = homeassistant/components/tradfri/__init__.py homeassistant/components/tradfri/base_class.py homeassistant/components/tradfri/config_flow.py + homeassistant/components/tradfri/coordinator.py homeassistant/components/tradfri/cover.py homeassistant/components/tradfri/fan.py homeassistant/components/tradfri/light.py diff --git a/homeassistant/components/tradfri/__init__.py b/homeassistant/components/tradfri/__init__.py index 952ee54a0d8c1d..6dd3236ea973f9 100644 --- a/homeassistant/components/tradfri/__init__.py +++ b/homeassistant/components/tradfri/__init__.py @@ -7,6 +7,9 @@ from pytradfri import Gateway, PytradfriError, RequestError from pytradfri.api.aiocoap_api import APIFactory +from pytradfri.command import Command +from pytradfri.device import Device +from pytradfri.group import Group import voluptuous as vol from homeassistant import config_entries @@ -15,7 +18,10 @@ from homeassistant.core import Event, HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType @@ -28,15 +34,20 @@ CONF_IDENTITY, CONF_IMPORT_GROUPS, CONF_KEY, + COORDINATOR, + COORDINATOR_LIST, DEFAULT_ALLOW_TRADFRI_GROUPS, - DEVICES, DOMAIN, - GROUPS, + GROUPS_LIST, KEY_API, PLATFORMS, SIGNAL_GW, TIMEOUT_API, ) +from .coordinator import ( + TradfriDeviceDataUpdateCoordinator, + TradfriGroupDataUpdateCoordinator, +) _LOGGER = logging.getLogger(__name__) @@ -84,9 +95,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: return True -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, +) -> bool: """Create a gateway.""" - # host, identity, key, allow_tradfri_groups tradfri_data: dict[str, Any] = {} hass.data.setdefault(DOMAIN, {})[entry.entry_id] = tradfri_data listeners = tradfri_data[LISTENERS] = [] @@ -96,11 +109,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: psk_id=entry.data[CONF_IDENTITY], psk=entry.data[CONF_KEY], ) + tradfri_data[FACTORY] = factory # Used for async_unload_entry async def on_hass_stop(event: Event) -> None: """Close connection when hass stops.""" await factory.shutdown() + # Setup listeners listeners.append(hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop)) api = factory.request @@ -108,19 +123,17 @@ async def on_hass_stop(event: Event) -> None: try: gateway_info = await api(gateway.get_gateway_info(), timeout=TIMEOUT_API) - devices_commands = await api(gateway.get_devices(), timeout=TIMEOUT_API) - devices = await api(devices_commands, timeout=TIMEOUT_API) - groups_commands = await api(gateway.get_groups(), timeout=TIMEOUT_API) - groups = await api(groups_commands, timeout=TIMEOUT_API) + devices_commands: Command = await api( + gateway.get_devices(), timeout=TIMEOUT_API + ) + devices: list[Device] = await api(devices_commands, timeout=TIMEOUT_API) + groups_commands: Command = await api(gateway.get_groups(), timeout=TIMEOUT_API) + groups: list[Group] = await api(groups_commands, timeout=TIMEOUT_API) + except PytradfriError as exc: await factory.shutdown() raise ConfigEntryNotReady from exc - tradfri_data[KEY_API] = api - tradfri_data[FACTORY] = factory - tradfri_data[DEVICES] = devices - tradfri_data[GROUPS] = groups - dev_reg = await hass.helpers.device_registry.async_get_registry() dev_reg.async_get_or_create( config_entry_id=entry.entry_id, @@ -133,7 +146,38 @@ async def on_hass_stop(event: Event) -> None: sw_version=gateway_info.firmware_version, ) - hass.config_entries.async_setup_platforms(entry, PLATFORMS) + # Setup the device coordinators + coordinator_data = { + CONF_GATEWAY_ID: gateway, + KEY_API: api, + COORDINATOR_LIST: [], + GROUPS_LIST: [], + } + + for device in devices: + coordinator = TradfriDeviceDataUpdateCoordinator( + hass=hass, api=api, device=device + ) + await coordinator.async_config_entry_first_refresh() + + entry.async_on_unload( + async_dispatcher_connect(hass, SIGNAL_GW, coordinator.set_hub_available) + ) + coordinator_data[COORDINATOR_LIST].append(coordinator) + + for group in groups: + group_coordinator = TradfriGroupDataUpdateCoordinator( + hass=hass, api=api, group=group + ) + await group_coordinator.async_config_entry_first_refresh() + entry.async_on_unload( + async_dispatcher_connect( + hass, SIGNAL_GW, group_coordinator.set_hub_available + ) + ) + coordinator_data[GROUPS_LIST].append(group_coordinator) + + tradfri_data[COORDINATOR] = coordinator_data async def async_keep_alive(now: datetime) -> None: if hass.is_stopping: @@ -152,6 +196,8 @@ async def async_keep_alive(now: datetime) -> None: async_track_time_interval(hass, async_keep_alive, timedelta(seconds=60)) ) + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + return True diff --git a/homeassistant/components/tradfri/base_class.py b/homeassistant/components/tradfri/base_class.py index 34ad7b792b9c2c..af923538fb2743 100644 --- a/homeassistant/components/tradfri/base_class.py +++ b/homeassistant/components/tradfri/base_class.py @@ -1,29 +1,22 @@ """Base class for IKEA TRADFRI.""" from __future__ import annotations +from abc import abstractmethod from collections.abc import Callable from functools import wraps import logging -from typing import Any +from typing import Any, cast from pytradfri.command import Command from pytradfri.device import Device -from pytradfri.device.air_purifier import AirPurifier -from pytradfri.device.air_purifier_control import AirPurifierControl -from pytradfri.device.blind import Blind -from pytradfri.device.blind_control import BlindControl -from pytradfri.device.light import Light -from pytradfri.device.light_control import LightControl -from pytradfri.device.signal_repeater_control import SignalRepeaterControl -from pytradfri.device.socket import Socket -from pytradfri.device.socket_control import SocketControl from pytradfri.error import PytradfriError from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import DeviceInfo, Entity +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN, SIGNAL_GW +from .const import DOMAIN +from .coordinator import TradfriDeviceDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) @@ -44,102 +37,44 @@ async def wrapper(command: Command | list[Command]) -> None: return wrapper -class TradfriBaseClass(Entity): - """Base class for IKEA TRADFRI. +class TradfriBaseEntity(CoordinatorEntity): + """Base Tradfri device.""" - All devices and groups should ultimately inherit from this class. - """ - - _attr_should_poll = False + coordinator: TradfriDeviceDataUpdateCoordinator def __init__( self, - device: Device, - api: Callable[[Command | list[Command]], Any], + device_coordinator: TradfriDeviceDataUpdateCoordinator, gateway_id: str, + api: Callable[[Command | list[Command]], Any], ) -> None: """Initialize a device.""" - self._api = handle_error(api) - self._attr_name = device.name - self._device: Device = device - self._device_control: BlindControl | LightControl | SocketControl | SignalRepeaterControl | AirPurifierControl | None = ( - None - ) - self._device_data: Socket | Light | Blind | AirPurifier | None = None + super().__init__(device_coordinator) + self._gateway_id = gateway_id - async def _async_run_observe(self, cmd: Command) -> None: - """Run observe in a coroutine.""" - try: - await self._api(cmd) - except PytradfriError as err: - self._attr_available = False - self.async_write_ha_state() - _LOGGER.warning("Observation failed, trying again", exc_info=err) - self._async_start_observe() + self._device: Device = device_coordinator.data - @callback - def _async_start_observe(self, exc: Exception | None = None) -> None: - """Start observation of device.""" - if exc: - self._attr_available = False - self.async_write_ha_state() - _LOGGER.warning("Observation failed for %s", self._attr_name, exc_info=exc) - cmd = self._device.observe( - callback=self._observe_update, - err_callback=self._async_start_observe, - duration=0, - ) - self.hass.async_create_task(self._async_run_observe(cmd)) + self._device_id = self._device.id + self._api = handle_error(api) + self._attr_name = self._device.name - async def async_added_to_hass(self) -> None: - """Start thread when added to hass.""" - self._async_start_observe() + self._attr_unique_id = f"{self._gateway_id}-{self._device.id}" + @abstractmethod @callback - def _observe_update(self, device: Device) -> None: - """Receive new state data for this device.""" - self._refresh(device) - - def _refresh(self, device: Device, write_ha: bool = True) -> None: - """Refresh the device data.""" - self._device = device - self._attr_name = device.name - if write_ha: - self.async_write_ha_state() - - -class TradfriBaseDevice(TradfriBaseClass): - """Base class for a TRADFRI device. - - All devices should inherit from this class. - """ - - def __init__( - self, - device: Device, - api: Callable[[Command | list[Command]], Any], - gateway_id: str, - ) -> None: - """Initialize a device.""" - self._attr_available = device.reachable - self._hub_available = True - super().__init__(device, api, gateway_id) - - async def async_added_to_hass(self) -> None: - """Start thread when added to hass.""" - # Only devices shall receive SIGNAL_GW - self.async_on_remove( - async_dispatcher_connect(self.hass, SIGNAL_GW, self.set_hub_available) - ) - await super().async_added_to_hass() + def _refresh(self) -> None: + """Refresh device data.""" @callback - def set_hub_available(self, available: bool) -> None: - """Set status of hub.""" - if available != self._hub_available: - self._hub_available = available - self._refresh(self._device) + def _handle_coordinator_update(self) -> None: + """ + Handle updated data from the coordinator. + + Tests fails without this method. + """ + self._refresh() + super()._handle_coordinator_update() @property def device_info(self) -> DeviceInfo: @@ -154,10 +89,7 @@ def device_info(self) -> DeviceInfo: via_device=(DOMAIN, self._gateway_id), ) - def _refresh(self, device: Device, write_ha: bool = True) -> None: - """Refresh the device data.""" - # The base class _refresh cannot be used, because - # there are devices (group) that do not have .reachable - # so set _attr_available here and let the base class do the rest. - self._attr_available = device.reachable and self._hub_available - super()._refresh(device, write_ha) + @property + def available(self) -> bool: + """Return if entity is available.""" + return cast(bool, self._device.reachable) and super().available diff --git a/homeassistant/components/tradfri/const.py b/homeassistant/components/tradfri/const.py index 487eb0dae13872..c87d2097929416 100644 --- a/homeassistant/components/tradfri/const.py +++ b/homeassistant/components/tradfri/const.py @@ -37,3 +37,9 @@ ] TIMEOUT_API = 30 ATTR_MAX_FAN_STEPS = 49 + +SCAN_INTERVAL = 60 # Interval for updating the coordinator + +COORDINATOR = "coordinator" +COORDINATOR_LIST = "coordinator_list" +GROUPS_LIST = "groups_list" diff --git a/homeassistant/components/tradfri/coordinator.py b/homeassistant/components/tradfri/coordinator.py new file mode 100644 index 00000000000000..1395478b6e906a --- /dev/null +++ b/homeassistant/components/tradfri/coordinator.py @@ -0,0 +1,145 @@ +"""Tradfri DataUpdateCoordinator.""" +from __future__ import annotations + +from collections.abc import Callable +from datetime import timedelta +import logging +from typing import Any + +from pytradfri.command import Command +from pytradfri.device import Device +from pytradfri.error import RequestError +from pytradfri.group import Group + +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import SCAN_INTERVAL + +_LOGGER = logging.getLogger(__name__) + + +class TradfriDeviceDataUpdateCoordinator(DataUpdateCoordinator[Device]): + """Coordinator to manage data for a specific Tradfri device.""" + + def __init__( + self, + hass: HomeAssistant, + *, + api: Callable[[Command | list[Command]], Any], + device: Device, + ) -> None: + """Initialize device coordinator.""" + self.api = api + self.device = device + self._exception: Exception | None = None + + super().__init__( + hass, + _LOGGER, + name=f"Update coordinator for {device}", + update_interval=timedelta(seconds=SCAN_INTERVAL), + ) + + async def set_hub_available(self, available: bool) -> None: + """Set status of hub.""" + if available != self.last_update_success: + if not available: + self.last_update_success = False + await self.async_request_refresh() + + @callback + def _observe_update(self, device: Device) -> None: + """Update the coordinator for a device when a change is detected.""" + self.update_interval = timedelta(seconds=SCAN_INTERVAL) # Reset update interval + + self.async_set_updated_data(data=device) + + @callback + def _exception_callback(self, device: Device, exc: Exception | None = None) -> None: + """Schedule handling exception..""" + self.hass.async_create_task(self._handle_exception(device=device, exc=exc)) + + async def _handle_exception( + self, device: Device, exc: Exception | None = None + ) -> None: + """Handle observe exceptions in a coroutine.""" + self._exception = ( + exc # Store exception so that it gets raised in _async_update_data + ) + + _LOGGER.debug("Observation failed for %s, trying again", device, exc_info=exc) + self.update_interval = timedelta( + seconds=5 + ) # Change interval so we get a swift refresh + await self.async_request_refresh() + + async def _async_update_data(self) -> Device: + """Fetch data from the gateway for a specific device.""" + try: + if self._exception: + exc = self._exception + self._exception = None # Clear stored exception + raise exc # pylint: disable-msg=raising-bad-type + except RequestError as err: + raise UpdateFailed( + f"Error communicating with API: {err}. Try unplugging and replugging your " + f"IKEA gateway." + ) from err + + if not self.data or not self.last_update_success: # Start subscription + try: + cmd = self.device.observe( + callback=self._observe_update, + err_callback=self._exception_callback, + duration=0, + ) + await self.api(cmd) + except RequestError as exc: + await self._handle_exception(device=self.device, exc=exc) + + return self.device + + +class TradfriGroupDataUpdateCoordinator(DataUpdateCoordinator[Group]): + """Coordinator to manage data for a specific Tradfri group.""" + + def __init__( + self, + hass: HomeAssistant, + *, + api: Callable[[Command | list[Command]], Any], + group: Group, + ) -> None: + """Initialize group coordinator.""" + self.api = api + self.group = group + self._exception: Exception | None = None + + super().__init__( + hass, + _LOGGER, + name=f"Update coordinator for {group}", + update_interval=timedelta(seconds=SCAN_INTERVAL), + ) + + async def set_hub_available(self, available: bool) -> None: + """Set status of hub.""" + if available != self.last_update_success: + if not available: + self.last_update_success = False + await self.async_request_refresh() + + async def _async_update_data(self) -> Group: + """Fetch data from the gateway for a specific group.""" + self.update_interval = timedelta(seconds=SCAN_INTERVAL) # Reset update interval + cmd = self.group.update() + try: + await self.api(cmd) + except RequestError as exc: + self.update_interval = timedelta( + seconds=5 + ) # Change interval so we get a swift refresh + raise UpdateFailed("Unable to update group coordinator") from exc + + return self.group diff --git a/homeassistant/components/tradfri/cover.py b/homeassistant/components/tradfri/cover.py index 554650f9005475..d4c3063f35d0a9 100644 --- a/homeassistant/components/tradfri/cover.py +++ b/homeassistant/components/tradfri/cover.py @@ -11,8 +11,16 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .base_class import TradfriBaseDevice -from .const import ATTR_MODEL, CONF_GATEWAY_ID, DEVICES, DOMAIN, KEY_API +from .base_class import TradfriBaseEntity +from .const import ( + ATTR_MODEL, + CONF_GATEWAY_ID, + COORDINATOR, + COORDINATOR_LIST, + DOMAIN, + KEY_API, +) +from .coordinator import TradfriDeviceDataUpdateCoordinator async def async_setup_entry( @@ -22,28 +30,42 @@ async def async_setup_entry( ) -> None: """Load Tradfri covers based on a config entry.""" gateway_id = config_entry.data[CONF_GATEWAY_ID] - tradfri_data = hass.data[DOMAIN][config_entry.entry_id] - api = tradfri_data[KEY_API] - devices = tradfri_data[DEVICES] + coordinator_data = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] + api = coordinator_data[KEY_API] async_add_entities( - TradfriCover(dev, api, gateway_id) for dev in devices if dev.has_blind_control + TradfriCover( + device_coordinator, + api, + gateway_id, + ) + for device_coordinator in coordinator_data[COORDINATOR_LIST] + if device_coordinator.device.has_blind_control ) -class TradfriCover(TradfriBaseDevice, CoverEntity): +class TradfriCover(TradfriBaseEntity, CoverEntity): """The platform class required by Home Assistant.""" def __init__( self, - device: Command, + device_coordinator: TradfriDeviceDataUpdateCoordinator, api: Callable[[Command | list[Command]], Any], gateway_id: str, ) -> None: - """Initialize a cover.""" - self._attr_unique_id = f"{gateway_id}-{device.id}" - super().__init__(device, api, gateway_id) - self._refresh(device, write_ha=False) + """Initialize a switch.""" + super().__init__( + device_coordinator=device_coordinator, + api=api, + gateway_id=gateway_id, + ) + + self._device_control = self._device.blind_control + self._device_data = self._device_control.blinds[0] + + def _refresh(self) -> None: + """Refresh the device.""" + self._device_data = self.coordinator.data.blind_control.blinds[0] @property def extra_state_attributes(self) -> dict[str, str] | None: @@ -88,11 +110,3 @@ async def async_stop_cover(self, **kwargs: Any) -> None: def is_closed(self) -> bool: """Return if the cover is closed or not.""" return self.current_cover_position == 0 - - def _refresh(self, device: Command, write_ha: bool = True) -> None: - """Refresh the cover data.""" - # Caching of BlindControl and cover object - self._device = device - self._device_control = device.blind_control - self._device_data = device.blind_control.blinds[0] - super()._refresh(device, write_ha=write_ha) diff --git a/homeassistant/components/tradfri/fan.py b/homeassistant/components/tradfri/fan.py index 7b64e883c44a5c..36e1c8b08ad888 100644 --- a/homeassistant/components/tradfri/fan.py +++ b/homeassistant/components/tradfri/fan.py @@ -16,15 +16,17 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .base_class import TradfriBaseDevice +from .base_class import TradfriBaseEntity from .const import ( ATTR_AUTO, ATTR_MAX_FAN_STEPS, CONF_GATEWAY_ID, - DEVICES, + COORDINATOR, + COORDINATOR_LIST, DOMAIN, KEY_API, ) +from .coordinator import TradfriDeviceDataUpdateCoordinator def _from_fan_percentage(percentage: int) -> int: @@ -44,30 +46,42 @@ async def async_setup_entry( ) -> None: """Load Tradfri switches based on a config entry.""" gateway_id = config_entry.data[CONF_GATEWAY_ID] - tradfri_data = hass.data[DOMAIN][config_entry.entry_id] - api = tradfri_data[KEY_API] - devices = tradfri_data[DEVICES] + coordinator_data = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] + api = coordinator_data[KEY_API] async_add_entities( - TradfriAirPurifierFan(dev, api, gateway_id) - for dev in devices - if dev.has_air_purifier_control + TradfriAirPurifierFan( + device_coordinator, + api, + gateway_id, + ) + for device_coordinator in coordinator_data[COORDINATOR_LIST] + if device_coordinator.device.has_air_purifier_control ) -class TradfriAirPurifierFan(TradfriBaseDevice, FanEntity): +class TradfriAirPurifierFan(TradfriBaseEntity, FanEntity): """The platform class required by Home Assistant.""" def __init__( self, - device: Command, + device_coordinator: TradfriDeviceDataUpdateCoordinator, api: Callable[[Command | list[Command]], Any], gateway_id: str, ) -> None: """Initialize a switch.""" - super().__init__(device, api, gateway_id) - self._attr_unique_id = f"{gateway_id}-{device.id}" - self._refresh(device, write_ha=False) + super().__init__( + device_coordinator=device_coordinator, + api=api, + gateway_id=gateway_id, + ) + + self._device_control = self._device.air_purifier_control + self._device_data = self._device_control.air_purifiers[0] + + def _refresh(self) -> None: + """Refresh the device.""" + self._device_data = self.coordinator.data.air_purifier_control.air_purifiers[0] @property def supported_features(self) -> int: @@ -168,10 +182,3 @@ async def async_turn_off(self, **kwargs: Any) -> None: if not self._device_control: return await self._api(self._device_control.turn_off()) - - def _refresh(self, device: Command, write_ha: bool = True) -> None: - """Refresh the purifier data.""" - # Caching of air purifier control and purifier object - self._device_control = device.air_purifier_control - self._device_data = device.air_purifier_control.air_purifiers[0] - super()._refresh(device, write_ha=write_ha) diff --git a/homeassistant/components/tradfri/light.py b/homeassistant/components/tradfri/light.py index ca078a37e813bf..9b6ad3e9f06764 100644 --- a/homeassistant/components/tradfri/light.py +++ b/homeassistant/components/tradfri/light.py @@ -5,6 +5,7 @@ from typing import Any, cast from pytradfri.command import Command +from pytradfri.group import Group from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -19,9 +20,10 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity import homeassistant.util.color as color_util -from .base_class import TradfriBaseClass, TradfriBaseDevice +from .base_class import TradfriBaseEntity from .const import ( ATTR_DIMMER, ATTR_HUE, @@ -29,13 +31,18 @@ ATTR_TRANSITION_TIME, CONF_GATEWAY_ID, CONF_IMPORT_GROUPS, - DEVICES, + COORDINATOR, + COORDINATOR_LIST, DOMAIN, - GROUPS, + GROUPS_LIST, KEY_API, SUPPORTED_GROUP_FEATURES, SUPPORTED_LIGHT_FEATURES, ) +from .coordinator import ( + TradfriDeviceDataUpdateCoordinator, + TradfriGroupDataUpdateCoordinator, +) async def async_setup_entry( @@ -45,56 +52,66 @@ async def async_setup_entry( ) -> None: """Load Tradfri lights based on a config entry.""" gateway_id = config_entry.data[CONF_GATEWAY_ID] - tradfri_data = hass.data[DOMAIN][config_entry.entry_id] - api = tradfri_data[KEY_API] - devices = tradfri_data[DEVICES] - - entities: list[TradfriBaseClass] = [ - TradfriLight(dev, api, gateway_id) for dev in devices if dev.has_light_control + coordinator_data = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] + api = coordinator_data[KEY_API] + + entities: list = [ + TradfriLight( + device_coordinator, + api, + gateway_id, + ) + for device_coordinator in coordinator_data[COORDINATOR_LIST] + if device_coordinator.device.has_light_control ] - if config_entry.data[CONF_IMPORT_GROUPS] and (groups := tradfri_data[GROUPS]): - entities.extend([TradfriGroup(group, api, gateway_id) for group in groups]) + + if config_entry.data[CONF_IMPORT_GROUPS] and ( + group_coordinators := coordinator_data[GROUPS_LIST] + ): + entities.extend( + [ + TradfriGroup(group_coordinator, api, gateway_id) + for group_coordinator in group_coordinators + ] + ) + async_add_entities(entities) -class TradfriGroup(TradfriBaseClass, LightEntity): +class TradfriGroup(CoordinatorEntity, LightEntity): """The platform class for light groups required by hass.""" _attr_supported_features = SUPPORTED_GROUP_FEATURES def __init__( self, - device: Command, + group_coordinator: TradfriGroupDataUpdateCoordinator, api: Callable[[Command | list[Command]], Any], gateway_id: str, ) -> None: """Initialize a Group.""" - super().__init__(device, api, gateway_id) - - self._attr_unique_id = f"group-{gateway_id}-{device.id}" - self._attr_should_poll = True - self._refresh(device, write_ha=False) + super().__init__(coordinator=group_coordinator) - async def async_update(self) -> None: - """Fetch new state data for the group. + self._group: Group = self.coordinator.data - This method is required for groups to update properly. - """ - await self._api(self._device.update()) + self._api = api + self._attr_unique_id = f"group-{gateway_id}-{self._group.id}" @property def is_on(self) -> bool: """Return true if group lights are on.""" - return cast(bool, self._device.state) + return cast(bool, self._group.state) @property def brightness(self) -> int | None: """Return the brightness of the group lights.""" - return cast(int, self._device.dimmer) + return cast(int, self._group.dimmer) async def async_turn_off(self, **kwargs: Any) -> None: """Instruct the group lights to turn off.""" - await self._api(self._device.set_state(0)) + await self._api(self._group.set_state(0)) + + await self.coordinator.async_request_refresh() async def async_turn_on(self, **kwargs: Any) -> None: """Instruct the group lights to turn on, or dim.""" @@ -106,39 +123,53 @@ async def async_turn_on(self, **kwargs: Any) -> None: if kwargs[ATTR_BRIGHTNESS] == 255: kwargs[ATTR_BRIGHTNESS] = 254 - await self._api(self._device.set_dimmer(kwargs[ATTR_BRIGHTNESS], **keys)) + await self._api(self._group.set_dimmer(kwargs[ATTR_BRIGHTNESS], **keys)) else: - await self._api(self._device.set_state(1)) + await self._api(self._group.set_state(1)) + await self.coordinator.async_request_refresh() -class TradfriLight(TradfriBaseDevice, LightEntity): + +class TradfriLight(TradfriBaseEntity, LightEntity): """The platform class required by Home Assistant.""" def __init__( self, - device: Command, + device_coordinator: TradfriDeviceDataUpdateCoordinator, api: Callable[[Command | list[Command]], Any], gateway_id: str, ) -> None: """Initialize a Light.""" - super().__init__(device, api, gateway_id) - self._attr_unique_id = f"light-{gateway_id}-{device.id}" + super().__init__( + device_coordinator=device_coordinator, + api=api, + gateway_id=gateway_id, + ) + + self._device_control = self._device.light_control + self._device_data = self._device_control.lights[0] + + self._attr_unique_id = f"light-{gateway_id}-{self._device_id}" self._hs_color = None # Calculate supported features _features = SUPPORTED_LIGHT_FEATURES - if device.light_control.can_set_dimmer: + if self._device.light_control.can_set_dimmer: _features |= SUPPORT_BRIGHTNESS - if device.light_control.can_set_color: + if self._device.light_control.can_set_color: _features |= SUPPORT_COLOR | SUPPORT_COLOR_TEMP - if device.light_control.can_set_temp: + if self._device.light_control.can_set_temp: _features |= SUPPORT_COLOR_TEMP self._attr_supported_features = _features - self._refresh(device, write_ha=False) + if self._device_control: self._attr_min_mireds = self._device_control.min_mireds self._attr_max_mireds = self._device_control.max_mireds + def _refresh(self) -> None: + """Refresh the device.""" + self._device_data = self.coordinator.data.light_control.lights[0] + @property def is_on(self) -> bool: """Return true if light is on.""" @@ -268,10 +299,3 @@ async def async_turn_on(self, **kwargs: Any) -> None: await self._api(temp_command) if command is not None: await self._api(command) - - def _refresh(self, device: Command, write_ha: bool = True) -> None: - """Refresh the light data.""" - # Caching of LightControl and light object - self._device_control = device.light_control - self._device_data = device.light_control.lights[0] - super()._refresh(device, write_ha=write_ha) diff --git a/homeassistant/components/tradfri/sensor.py b/homeassistant/components/tradfri/sensor.py index 5da3179c507d22..3d042fa6417b0c 100644 --- a/homeassistant/components/tradfri/sensor.py +++ b/homeassistant/components/tradfri/sensor.py @@ -2,7 +2,7 @@ from __future__ import annotations from collections.abc import Callable -from typing import Any, cast +from typing import Any from pytradfri.command import Command @@ -12,8 +12,9 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .base_class import TradfriBaseDevice -from .const import CONF_GATEWAY_ID, DEVICES, DOMAIN, KEY_API +from .base_class import TradfriBaseEntity +from .const import CONF_GATEWAY_ID, COORDINATOR, COORDINATOR_LIST, DOMAIN, KEY_API +from .coordinator import TradfriDeviceDataUpdateCoordinator async def async_setup_entry( @@ -23,24 +24,27 @@ async def async_setup_entry( ) -> None: """Set up a Tradfri config entry.""" gateway_id = config_entry.data[CONF_GATEWAY_ID] - tradfri_data = hass.data[DOMAIN][config_entry.entry_id] - api = tradfri_data[KEY_API] - devices = tradfri_data[DEVICES] + coordinator_data = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] + api = coordinator_data[KEY_API] async_add_entities( - TradfriSensor(dev, api, gateway_id) - for dev in devices + TradfriSensor( + device_coordinator, + api, + gateway_id, + ) + for device_coordinator in coordinator_data[COORDINATOR_LIST] if ( - not dev.has_light_control - and not dev.has_socket_control - and not dev.has_blind_control - and not dev.has_signal_repeater_control - and not dev.has_air_purifier_control + not device_coordinator.device.has_light_control + and not device_coordinator.device.has_socket_control + and not device_coordinator.device.has_blind_control + and not device_coordinator.device.has_signal_repeater_control + and not device_coordinator.device.has_air_purifier_control ) ) -class TradfriSensor(TradfriBaseDevice, SensorEntity): +class TradfriSensor(TradfriBaseEntity, SensorEntity): """The platform class required by Home Assistant.""" _attr_device_class = SensorDeviceClass.BATTERY @@ -48,17 +52,19 @@ class TradfriSensor(TradfriBaseDevice, SensorEntity): def __init__( self, - device: Command, + device_coordinator: TradfriDeviceDataUpdateCoordinator, api: Callable[[Command | list[Command]], Any], gateway_id: str, ) -> None: - """Initialize the device.""" - super().__init__(device, api, gateway_id) - self._attr_unique_id = f"{gateway_id}-{device.id}" + """Initialize a switch.""" + super().__init__( + device_coordinator=device_coordinator, + api=api, + gateway_id=gateway_id, + ) + + self._refresh() # Set initial state - @property - def native_value(self) -> int | None: - """Return the current state of the device.""" - if not self._device: - return None - return cast(int, self._device.device_info.battery_level) + def _refresh(self) -> None: + """Refresh the device.""" + self._attr_native_value = self.coordinator.data.device_info.battery_level diff --git a/homeassistant/components/tradfri/switch.py b/homeassistant/components/tradfri/switch.py index f8950d247204f0..e0e2467ca4bf37 100644 --- a/homeassistant/components/tradfri/switch.py +++ b/homeassistant/components/tradfri/switch.py @@ -11,8 +11,9 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .base_class import TradfriBaseDevice -from .const import CONF_GATEWAY_ID, DEVICES, DOMAIN, KEY_API +from .base_class import TradfriBaseEntity +from .const import CONF_GATEWAY_ID, COORDINATOR, COORDINATOR_LIST, DOMAIN, KEY_API +from .coordinator import TradfriDeviceDataUpdateCoordinator async def async_setup_entry( @@ -22,35 +23,42 @@ async def async_setup_entry( ) -> None: """Load Tradfri switches based on a config entry.""" gateway_id = config_entry.data[CONF_GATEWAY_ID] - tradfri_data = hass.data[DOMAIN][config_entry.entry_id] - api = tradfri_data[KEY_API] - devices = tradfri_data[DEVICES] + coordinator_data = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] + api = coordinator_data[KEY_API] async_add_entities( - TradfriSwitch(dev, api, gateway_id) for dev in devices if dev.has_socket_control + TradfriSwitch( + device_coordinator, + api, + gateway_id, + ) + for device_coordinator in coordinator_data[COORDINATOR_LIST] + if device_coordinator.device.has_socket_control ) -class TradfriSwitch(TradfriBaseDevice, SwitchEntity): +class TradfriSwitch(TradfriBaseEntity, SwitchEntity): """The platform class required by Home Assistant.""" def __init__( self, - device: Command, + device_coordinator: TradfriDeviceDataUpdateCoordinator, api: Callable[[Command | list[Command]], Any], gateway_id: str, ) -> None: """Initialize a switch.""" - super().__init__(device, api, gateway_id) - self._attr_unique_id = f"{gateway_id}-{device.id}" - self._refresh(device, write_ha=False) + super().__init__( + device_coordinator=device_coordinator, + api=api, + gateway_id=gateway_id, + ) - def _refresh(self, device: Command, write_ha: bool = True) -> None: - """Refresh the switch data.""" - # Caching of switch control and switch object - self._device_control = device.socket_control - self._device_data = device.socket_control.sockets[0] - super()._refresh(device, write_ha=write_ha) + self._device_control = self._device.socket_control + self._device_data = self._device_control.sockets[0] + + def _refresh(self) -> None: + """Refresh the device.""" + self._device_data = self.coordinator.data.socket_control.sockets[0] @property def is_on(self) -> bool: diff --git a/tests/components/tradfri/common.py b/tests/components/tradfri/common.py index 5e28bdcd55c4e3..feeb60ab7c9547 100644 --- a/tests/components/tradfri/common.py +++ b/tests/components/tradfri/common.py @@ -22,3 +22,5 @@ async def setup_integration(hass): entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() + + return entry diff --git a/tests/components/tradfri/test_fan.py b/tests/components/tradfri/test_fan.py index 13b7e59e10348d..4aa99f5778a146 100644 --- a/tests/components/tradfri/test_fan.py +++ b/tests/components/tradfri/test_fan.py @@ -121,7 +121,6 @@ async def test_set_percentage( """Test setting speed of a fan.""" # Note pytradfri style, not hass. Values not really important. initial_state = {"percentage": 10, "fan_speed": 3} - # Setup the gateway with a mock fan. fan = mock_fan(test_state=initial_state, device_number=0) mock_gateway.mock_devices.append(fan) diff --git a/tests/components/tradfri/test_light.py b/tests/components/tradfri/test_light.py index 7de2c4dcb37fb4..1ed24d7b080f4d 100644 --- a/tests/components/tradfri/test_light.py +++ b/tests/components/tradfri/test_light.py @@ -317,6 +317,7 @@ def mock_group(test_state=None, group_number=0): _mock_group = Mock(member_ids=[], observe=Mock(), **state) _mock_group.name = f"tradfri_group_{group_number}" + _mock_group.id = group_number return _mock_group @@ -327,11 +328,11 @@ async def test_group(hass, mock_gateway, mock_api_factory): mock_gateway.mock_groups.append(mock_group(state, 1)) await setup_integration(hass) - group = hass.states.get("light.tradfri_group_0") + group = hass.states.get("light.tradfri_group_mock_gateway_id_0") assert group is not None assert group.state == "off" - group = hass.states.get("light.tradfri_group_1") + group = hass.states.get("light.tradfri_group_mock_gateway_id_1") assert group is not None assert group.state == "on" assert group.attributes["brightness"] == 100 @@ -349,18 +350,25 @@ async def test_group_turn_on(hass, mock_gateway, mock_api_factory): # Use the turn_off service call to change the light state. await hass.services.async_call( - "light", "turn_on", {"entity_id": "light.tradfri_group_0"}, blocking=True + "light", + "turn_on", + {"entity_id": "light.tradfri_group_mock_gateway_id_0"}, + blocking=True, ) await hass.services.async_call( "light", "turn_on", - {"entity_id": "light.tradfri_group_1", "brightness": 100}, + {"entity_id": "light.tradfri_group_mock_gateway_id_1", "brightness": 100}, blocking=True, ) await hass.services.async_call( "light", "turn_on", - {"entity_id": "light.tradfri_group_2", "brightness": 100, "transition": 1}, + { + "entity_id": "light.tradfri_group_mock_gateway_id_2", + "brightness": 100, + "transition": 1, + }, blocking=True, ) await hass.async_block_till_done() @@ -378,7 +386,10 @@ async def test_group_turn_off(hass, mock_gateway, mock_api_factory): # Use the turn_off service call to change the light state. await hass.services.async_call( - "light", "turn_off", {"entity_id": "light.tradfri_group_0"}, blocking=True + "light", + "turn_off", + {"entity_id": "light.tradfri_group_mock_gateway_id_0"}, + blocking=True, ) await hass.async_block_till_done() From f4ed28ab09605d79be84062bcd4fed433b564ca9 Mon Sep 17 00:00:00 2001 From: Arjan van Balken Date: Thu, 27 Jan 2022 11:48:37 +0100 Subject: [PATCH 0013/1098] Update Arris TG2492LG dependency to 1.2.1 (#64999) --- homeassistant/components/arris_tg2492lg/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/arris_tg2492lg/manifest.json b/homeassistant/components/arris_tg2492lg/manifest.json index 8ed5c39882edf4..01da8b8af3ca80 100644 --- a/homeassistant/components/arris_tg2492lg/manifest.json +++ b/homeassistant/components/arris_tg2492lg/manifest.json @@ -2,7 +2,7 @@ "domain": "arris_tg2492lg", "name": "Arris TG2492LG", "documentation": "https://www.home-assistant.io/integrations/arris_tg2492lg", - "requirements": ["arris-tg2492lg==1.1.0"], + "requirements": ["arris-tg2492lg==1.2.1"], "codeowners": ["@vanbalken"], "iot_class": "local_polling" } diff --git a/requirements_all.txt b/requirements_all.txt index c1973e5989a717..1ad6372a6a5f8e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -338,7 +338,7 @@ aqualogic==2.6 arcam-fmj==0.12.0 # homeassistant.components.arris_tg2492lg -arris-tg2492lg==1.1.0 +arris-tg2492lg==1.2.1 # homeassistant.components.ampio asmog==0.0.6 From 4f3ce275600ad3fb46c6f50136732d75c84d5d9a Mon Sep 17 00:00:00 2001 From: Duco Sebel <74970928+DCSBL@users.noreply.github.com> Date: Thu, 27 Jan 2022 14:30:31 +0100 Subject: [PATCH 0014/1098] Add `flow_title` for HomeWizard Energy (#65047) --- homeassistant/components/homewizard/config_flow.py | 4 ++++ homeassistant/components/homewizard/strings.json | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homewizard/config_flow.py b/homeassistant/components/homewizard/config_flow.py index 1b3211c4765866..17f87680c62043 100644 --- a/homeassistant/components/homewizard/config_flow.py +++ b/homeassistant/components/homewizard/config_flow.py @@ -127,6 +127,10 @@ async def async_step_discovery_confirm( self._set_confirm_only() + self.context["title_placeholders"] = { + "name": f"{self.config[CONF_PRODUCT_NAME]} ({self.config[CONF_SERIAL]})" + } + return self.async_show_form( step_id="discovery_confirm", description_placeholders={ diff --git a/homeassistant/components/homewizard/strings.json b/homeassistant/components/homewizard/strings.json index c3798b35678c9a..b34c6906cc196b 100644 --- a/homeassistant/components/homewizard/strings.json +++ b/homeassistant/components/homewizard/strings.json @@ -15,10 +15,10 @@ }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", - "invalid_discovery_parameters": "unsupported_api_version", + "invalid_discovery_parameters": "Detected unsupported API version", "api_not_enabled": "The API is not enabled. Enable API in the HomeWizard Energy App under settings", "device_not_supported": "This device is not supported", "unknown_error": "[%key:common::config_flow::error::unknown%]" } } -} \ No newline at end of file +} From ca469750ac85506bae907e4577c1c0a4fce23e4b Mon Sep 17 00:00:00 2001 From: alim4r <7687869+alim4r@users.noreply.github.com> Date: Thu, 27 Jan 2022 15:56:12 +0100 Subject: [PATCH 0015/1098] Prometheus tests simulate entities (#64916) --- tests/components/prometheus/test_init.py | 1088 +++++++++++++--------- 1 file changed, 666 insertions(+), 422 deletions(-) diff --git a/tests/components/prometheus/test_init.py b/tests/components/prometheus/test_init.py index d1b0c7c2130ce7..096f1405168e61 100644 --- a/tests/components/prometheus/test_init.py +++ b/tests/components/prometheus/test_init.py @@ -2,24 +2,56 @@ from dataclasses import dataclass import datetime from http import HTTPStatus -import unittest.mock as mock +from unittest import mock import prometheus_client import pytest -from homeassistant.components import climate, counter, humidifier, lock, sensor -from homeassistant.components.demo.binary_sensor import DemoBinarySensor -from homeassistant.components.demo.light import DemoLight -from homeassistant.components.demo.number import DemoNumber -from homeassistant.components.demo.sensor import DemoSensor -import homeassistant.components.prometheus as prometheus +from homeassistant.components import ( + binary_sensor, + climate, + counter, + device_tracker, + humidifier, + input_boolean, + input_number, + light, + lock, + person, + prometheus, + sensor, + switch, +) +from homeassistant.components.climate.const import ( + ATTR_CURRENT_TEMPERATURE, + ATTR_HUMIDITY, + ATTR_HVAC_ACTION, + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + CURRENT_HVAC_COOL, + CURRENT_HVAC_HEAT, +) +from homeassistant.components.humidifier.const import ATTR_AVAILABLE_MODES from homeassistant.components.sensor import SensorDeviceClass from homeassistant.const import ( + ATTR_BATTERY_LEVEL, + ATTR_DEVICE_CLASS, + ATTR_FRIENDLY_NAME, + ATTR_MODE, + ATTR_TEMPERATURE, + ATTR_UNIT_OF_MEASUREMENT, CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONTENT_TYPE_TEXT_PLAIN, DEGREE, ENERGY_KILO_WATT_HOUR, EVENT_STATE_CHANGED, + PERCENTAGE, + STATE_HOME, + STATE_LOCKED, + STATE_NOT_HOME, + STATE_OFF, + STATE_ON, + STATE_UNLOCKED, TEMP_CELSIUS, TEMP_FAHRENHEIT, ) @@ -39,6 +71,7 @@ class FilterTest: should_pass: bool +@pytest.fixture(name="client") async def setup_prometheus_client(hass, hass_client, namespace): """Initialize an hass_client with Prometheus component.""" # Reset registry @@ -71,28 +104,9 @@ async def generate_latest_metrics(client): return body -async def test_view_empty_namespace(hass, hass_client): +@pytest.mark.parametrize("namespace", [""]) +async def test_view_empty_namespace(client, sensor_entities): """Test prometheus metrics view.""" - client = await setup_prometheus_client(hass, hass_client, "") - - sensor2 = DemoSensor( - None, - "Radio Energy", - 14, - SensorDeviceClass.POWER, - None, - ENERGY_KILO_WATT_HOUR, - None, - ) - sensor2.hass = hass - sensor2.entity_id = "sensor.radio_energy" - with mock.patch( - "homeassistant.util.dt.utcnow", - return_value=datetime.datetime(1970, 1, 2, tzinfo=dt_util.UTC), - ): - await sensor2.async_update_ha_state() - - await hass.async_block_till_done() body = await generate_latest_metrics(client) assert "# HELP python_info Python platform information" in body @@ -114,21 +128,9 @@ async def test_view_empty_namespace(hass, hass_client): ) -async def test_view_default_namespace(hass, hass_client): +@pytest.mark.parametrize("namespace", [None]) +async def test_view_default_namespace(client, sensor_entities): """Test prometheus metrics view.""" - assert await async_setup_component( - hass, - "conversation", - {}, - ) - - client = await setup_prometheus_client(hass, hass_client, None) - - assert await async_setup_component( - hass, sensor.DOMAIN, {"sensor": [{"platform": "demo"}]} - ) - await hass.async_block_till_done() - body = await generate_latest_metrics(client) assert "# HELP python_info Python platform information" in body @@ -144,73 +146,9 @@ async def test_view_default_namespace(hass, hass_client): ) -async def test_sensor_unit(hass, hass_client): +@pytest.mark.parametrize("namespace", [""]) +async def test_sensor_unit(client, sensor_entities): """Test prometheus metrics for sensors with a unit.""" - client = await setup_prometheus_client(hass, hass_client, "") - - sensor1 = DemoSensor( - None, "Television Energy", 74, None, None, ENERGY_KILO_WATT_HOUR, None - ) - sensor1.hass = hass - sensor1.entity_id = "sensor.television_energy" - await sensor1.async_update_ha_state() - - sensor2 = DemoSensor( - None, - "Radio Energy", - 14, - SensorDeviceClass.POWER, - None, - ENERGY_KILO_WATT_HOUR, - None, - ) - sensor2.hass = hass - sensor2.entity_id = "sensor.radio_energy" - with mock.patch( - "homeassistant.util.dt.utcnow", - return_value=datetime.datetime(1970, 1, 2, tzinfo=dt_util.UTC), - ): - await sensor2.async_update_ha_state() - - sensor3 = DemoSensor( - None, - "Electricity price", - 0.123, - None, - None, - f"SEK/{ENERGY_KILO_WATT_HOUR}", - None, - ) - sensor3.hass = hass - sensor3.entity_id = "sensor.electricity_price" - await sensor3.async_update_ha_state() - - sensor4 = DemoSensor(None, "Wind Direction", 25, None, None, DEGREE, None) - sensor4.hass = hass - sensor4.entity_id = "sensor.wind_direction" - await sensor4.async_update_ha_state() - - sensor5 = DemoSensor( - None, - "SPS30 PM <1µm Weight concentration", - 3.7069, - None, - None, - CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - None, - ) - sensor5.hass = hass - sensor5.entity_id = "sensor.sps30_pm_1um_weight_concentration" - await sensor5.async_update_ha_state() - - sensor6 = DemoSensor( - None, "Target temperature", 22.7, None, None, TEMP_CELSIUS, None - ) - sensor6.hass = hass - sensor6.entity_id = "input_number.target_temperature" - await sensor6.async_update_ha_state() - - await hass.async_block_till_done() body = await generate_latest_metrics(client) assert ( @@ -237,32 +175,10 @@ async def test_sensor_unit(hass, hass_client): 'friendly_name="SPS30 PM <1µm Weight concentration"} 3.7069' in body ) - assert ( - 'input_number_state_celsius{domain="input_number",' - 'entity="input_number.target_temperature",' - 'friendly_name="Target temperature"} 22.7' in body - ) - -async def test_sensor_without_unit(hass, hass_client): +@pytest.mark.parametrize("namespace", [""]) +async def test_sensor_without_unit(client, sensor_entities): """Test prometheus metrics for sensors without a unit.""" - client = await setup_prometheus_client(hass, hass_client, "") - - sensor6 = DemoSensor(None, "Trend Gradient", 0.002, None, None, None, None) - sensor6.hass = hass - sensor6.entity_id = "sensor.trend_gradient" - await sensor6.async_update_ha_state() - - sensor7 = DemoSensor(None, "Text", "should_not_work", None, None, None, None) - sensor7.hass = hass - sensor7.entity_id = "sensor.text" - await sensor7.async_update_ha_state() - - sensor8 = DemoSensor(None, "Text Unit", "should_not_work", None, None, "Text", None) - sensor8.hass = hass - sensor8.entity_id = "sensor.text_unit" - await sensor8.async_update_ha_state() - body = await generate_latest_metrics(client) assert ( @@ -284,50 +200,9 @@ async def test_sensor_without_unit(hass, hass_client): ) -async def test_sensor_device_class(hass, hass_client): +@pytest.mark.parametrize("namespace", [""]) +async def test_sensor_device_class(client, sensor_entities): """Test prometheus metrics for sensor with a device_class.""" - assert await async_setup_component( - hass, - "conversation", - {}, - ) - - client = await setup_prometheus_client(hass, hass_client, "") - - await async_setup_component(hass, sensor.DOMAIN, {"sensor": [{"platform": "demo"}]}) - await hass.async_block_till_done() - - sensor1 = DemoSensor( - None, - "Fahrenheit", - 50, - SensorDeviceClass.TEMPERATURE, - None, - TEMP_FAHRENHEIT, - None, - ) - sensor1.hass = hass - sensor1.entity_id = "sensor.fahrenheit" - await sensor1.async_update_ha_state() - - sensor2 = DemoSensor( - None, - "Radio Energy", - 14, - SensorDeviceClass.POWER, - None, - ENERGY_KILO_WATT_HOUR, - None, - ) - sensor2.hass = hass - sensor2.entity_id = "sensor.radio_energy" - with mock.patch( - "homeassistant.util.dt.utcnow", - return_value=datetime.datetime(1970, 1, 2, tzinfo=dt_util.UTC), - ): - await sensor2.async_update_ha_state() - - await hass.async_block_till_done() body = await generate_latest_metrics(client) assert ( @@ -355,27 +230,9 @@ async def test_sensor_device_class(hass, hass_client): ) -async def test_input_number(hass, hass_client): +@pytest.mark.parametrize("namespace", [""]) +async def test_input_number(client, input_number_entities): """Test prometheus metrics for input_number.""" - client = await setup_prometheus_client(hass, hass_client, "") - - number1 = DemoNumber(None, "Threshold", 5.2, None, False, 0, 10, 0.1) - number1.hass = hass - number1.entity_id = "input_number.threshold" - await number1.async_update_ha_state() - - number2 = DemoNumber(None, None, 60, None, False, 0, 100) - number2.hass = hass - number2.entity_id = "input_number.brightness" - number2._attr_name = None - await number2.async_update_ha_state() - - number3 = DemoSensor(None, "Retry count", 5, None, None, None, None) - number3.hass = hass - number3.entity_id = "input_number.retry_count" - await number3.async_update_ha_state() - - await hass.async_block_till_done() body = await generate_latest_metrics(client) assert ( @@ -391,25 +248,15 @@ async def test_input_number(hass, hass_client): ) assert ( - 'input_number_state{domain="input_number",' - 'entity="input_number.retry_count",' - 'friendly_name="Retry count"} 5.0' in body + 'input_number_state_celsius{domain="input_number",' + 'entity="input_number.target_temperature",' + 'friendly_name="Target temperature"} 22.7' in body ) -async def test_battery(hass, hass_client): +@pytest.mark.parametrize("namespace", [""]) +async def test_battery(client, sensor_entities): """Test prometheus metrics for battery.""" - assert await async_setup_component( - hass, - "conversation", - {}, - ) - - client = await setup_prometheus_client(hass, hass_client, "") - - await async_setup_component(hass, sensor.DOMAIN, {"sensor": [{"platform": "demo"}]}) - await hass.async_block_till_done() - body = await generate_latest_metrics(client) assert ( @@ -419,21 +266,9 @@ async def test_battery(hass, hass_client): ) -async def test_climate(hass, hass_client): - """Test prometheus metrics for climate.""" - assert await async_setup_component( - hass, - "conversation", - {}, - ) - - client = await setup_prometheus_client(hass, hass_client, "") - - await async_setup_component( - hass, climate.DOMAIN, {"climate": [{"platform": "demo"}]} - ) - - await hass.async_block_till_done() +@pytest.mark.parametrize("namespace", [""]) +async def test_climate(client, climate_entities): + """Test prometheus metrics for climate entities.""" body = await generate_latest_metrics(client) assert ( @@ -460,36 +295,10 @@ async def test_climate(hass, hass_client): 'friendly_name="Ecobee"} 24.0' in body ) - assert ( - 'climate_mode{domain="climate",' - 'entity="climate.heatpump",' - 'friendly_name="HeatPump",' - 'mode="heat"} 1.0' in body - ) - assert ( - 'climate_mode{domain="climate",' - 'entity="climate.heatpump",' - 'friendly_name="HeatPump",' - 'mode="off"} 0.0' in body - ) - - -async def test_humidifier(hass, hass_client): - """Test prometheus metrics for battery.""" - assert await async_setup_component( - hass, - "conversation", - {}, - ) - - client = await setup_prometheus_client(hass, hass_client, "") - - await async_setup_component( - hass, humidifier.DOMAIN, {"humidifier": [{"platform": "demo"}]} - ) - - await hass.async_block_till_done() +@pytest.mark.parametrize("namespace", [""]) +async def test_humidifier(client, humidifier_entities): + """Test prometheus metrics for humidifier entities.""" body = await generate_latest_metrics(client) assert ( @@ -518,29 +327,15 @@ async def test_humidifier(hass, hass_client): ) -async def test_attributes(hass, hass_client): +@pytest.mark.parametrize("namespace", [""]) +async def test_attributes(client, switch_entities): """Test prometheus metrics for entity attributes.""" - client = await setup_prometheus_client(hass, hass_client, "") - - switch1 = DemoSensor(None, "Boolean", 74, None, None, None, None) - switch1.hass = hass - switch1.entity_id = "switch.boolean" - switch1._attr_extra_state_attributes = {"boolean": True} - await switch1.async_update_ha_state() - - switch2 = DemoSensor(None, "Number", 42, None, None, None, None) - switch2.hass = hass - switch2.entity_id = "switch.number" - switch2._attr_extra_state_attributes = {"Number": 10.2} - await switch2.async_update_ha_state() - - await hass.async_block_till_done() body = await generate_latest_metrics(client) assert ( 'switch_state{domain="switch",' 'entity="switch.boolean",' - 'friendly_name="Boolean"} 74.0' in body + 'friendly_name="Boolean"} 1.0' in body ) assert ( @@ -552,7 +347,7 @@ async def test_attributes(hass, hass_client): assert ( 'switch_state{domain="switch",' 'entity="switch.number",' - 'friendly_name="Number"} 42.0' in body + 'friendly_name="Number"} 0.0' in body ) assert ( @@ -562,21 +357,9 @@ async def test_attributes(hass, hass_client): ) -async def test_binary_sensor(hass, hass_client): +@pytest.mark.parametrize("namespace", [""]) +async def test_binary_sensor(client, binary_sensor_entities): """Test prometheus metrics for binary_sensor.""" - client = await setup_prometheus_client(hass, hass_client, "") - - binary_sensor1 = DemoBinarySensor(None, "Door", True, None) - binary_sensor1.hass = hass - binary_sensor1.entity_id = "binary_sensor.door" - await binary_sensor1.async_update_ha_state() - - binary_sensor1 = DemoBinarySensor(None, "Window", False, None) - binary_sensor1.hass = hass - binary_sensor1.entity_id = "binary_sensor.window" - await binary_sensor1.async_update_ha_state() - - await hass.async_block_till_done() body = await generate_latest_metrics(client) assert ( @@ -592,21 +375,9 @@ async def test_binary_sensor(hass, hass_client): ) -async def test_input_boolean(hass, hass_client): +@pytest.mark.parametrize("namespace", [""]) +async def test_input_boolean(client, input_boolean_entities): """Test prometheus metrics for input_boolean.""" - client = await setup_prometheus_client(hass, hass_client, "") - - input_boolean1 = DemoSensor(None, "Test", 1, None, None, None, None) - input_boolean1.hass = hass - input_boolean1.entity_id = "input_boolean.test" - await input_boolean1.async_update_ha_state() - - input_boolean2 = DemoSensor(None, "Helper", 0, None, None, None, None) - input_boolean2.hass = hass - input_boolean2.entity_id = "input_boolean.helper" - await input_boolean2.async_update_ha_state() - - await hass.async_block_till_done() body = await generate_latest_metrics(client) assert ( @@ -622,31 +393,9 @@ async def test_input_boolean(hass, hass_client): ) -async def test_light(hass, hass_client): +@pytest.mark.parametrize("namespace", [""]) +async def test_light(client, light_entities): """Test prometheus metrics for lights.""" - client = await setup_prometheus_client(hass, hass_client, "") - - light1 = DemoSensor(None, "Desk", 1, None, None, None, None) - light1.hass = hass - light1.entity_id = "light.desk" - await light1.async_update_ha_state() - - light2 = DemoSensor(None, "Wall", 0, None, None, None, None) - light2.hass = hass - light2.entity_id = "light.wall" - await light2.async_update_ha_state() - - light3 = DemoLight(None, "TV", True, True, 255, None, None) - light3.hass = hass - light3.entity_id = "light.tv" - await light3.async_update_ha_state() - - light4 = DemoLight(None, "PC", True, True, 180, None, None) - light4.hass = hass - light4.entity_id = "light.pc" - await light4.async_update_ha_state() - - await hass.async_block_till_done() body = await generate_latest_metrics(client) assert ( @@ -674,19 +423,9 @@ async def test_light(hass, hass_client): ) -async def test_lock(hass, hass_client): +@pytest.mark.parametrize("namespace", [""]) +async def test_lock(client, lock_entities): """Test prometheus metrics for lock.""" - assert await async_setup_component( - hass, - "conversation", - {}, - ) - - client = await setup_prometheus_client(hass, hass_client, "") - - await async_setup_component(hass, lock.DOMAIN, {"lock": [{"platform": "demo"}]}) - - await hass.async_block_till_done() body = await generate_latest_metrics(client) assert ( @@ -702,22 +441,9 @@ async def test_lock(hass, hass_client): ) -async def test_counter(hass, hass_client): +@pytest.mark.parametrize("namespace", [""]) +async def test_counter(client, counter_entities): """Test prometheus metrics for counter.""" - assert await async_setup_component( - hass, - "conversation", - {}, - ) - - client = await setup_prometheus_client(hass, hass_client, "") - - await async_setup_component( - hass, counter.DOMAIN, {"counter": {"counter": {"initial": "2"}}} - ) - - await hass.async_block_till_done() - body = await generate_latest_metrics(client) assert ( @@ -727,24 +453,12 @@ async def test_counter(hass, hass_client): ) -async def test_renaming_entity_name(hass, hass_client): +@pytest.mark.parametrize("namespace", [""]) +async def test_renaming_entity_name( + hass, registry, client, sensor_entities, climate_entities +): """Test renaming entity name.""" - assert await async_setup_component( - hass, - "conversation", - {}, - ) - client = await setup_prometheus_client(hass, hass_client, "") - - assert await async_setup_component( - hass, climate.DOMAIN, {"climate": [{"platform": "demo"}]} - ) - - assert await async_setup_component( - hass, sensor.DOMAIN, {"sensor": [{"platform": "demo"}]} - ) - - await hass.async_block_till_done() + data = {**sensor_entities, **climate_entities} body = await generate_latest_metrics(client) assert ( @@ -785,17 +499,29 @@ async def test_renaming_entity_name(hass, hass_client): 'friendly_name="HeatPump"} 0.0' in body ) - registry = entity_registry.async_get(hass) assert "sensor.outside_temperature" in registry.entities assert "climate.heatpump" in registry.entities registry.async_update_entity( - entity_id="sensor.outside_temperature", + entity_id=data["sensor_1"].entity_id, name="Outside Temperature Renamed", ) + set_state_with_entry( + hass, + data["sensor_1"], + 15.6, + {ATTR_FRIENDLY_NAME: "Outside Temperature Renamed"}, + ) registry.async_update_entity( - entity_id="climate.heatpump", + entity_id=data["climate_1"].entity_id, name="HeatPump Renamed", ) + data["climate_1_attributes"] = { + **data["climate_1_attributes"], + ATTR_FRIENDLY_NAME: "HeatPump Renamed", + } + set_state_with_entry( + hass, data["climate_1"], CURRENT_HVAC_HEAT, data["climate_1_attributes"] + ) await hass.async_block_till_done() body = await generate_latest_metrics(client) @@ -846,20 +572,12 @@ async def test_renaming_entity_name(hass, hass_client): ) -async def test_renaming_entity_id(hass, hass_client): +@pytest.mark.parametrize("namespace", [""]) +async def test_renaming_entity_id( + hass, registry, client, sensor_entities, climate_entities +): """Test renaming entity id.""" - assert await async_setup_component( - hass, - "conversation", - {}, - ) - client = await setup_prometheus_client(hass, hass_client, "") - - assert await async_setup_component( - hass, sensor.DOMAIN, {"sensor": [{"platform": "demo"}]} - ) - - await hass.async_block_till_done() + data = {**sensor_entities, **climate_entities} body = await generate_latest_metrics(client) assert ( @@ -886,12 +604,15 @@ async def test_renaming_entity_id(hass, hass_client): 'friendly_name="Outside Humidity"} 1.0' in body ) - registry = entity_registry.async_get(hass) assert "sensor.outside_temperature" in registry.entities + assert "climate.heatpump" in registry.entities registry.async_update_entity( entity_id="sensor.outside_temperature", new_entity_id="sensor.outside_temperature_renamed", ) + set_state_with_entry( + hass, data["sensor_1"], 15.6, None, "sensor.outside_temperature_renamed" + ) await hass.async_block_till_done() body = await generate_latest_metrics(client) @@ -927,24 +648,12 @@ async def test_renaming_entity_id(hass, hass_client): ) -async def test_deleting_entity(hass, hass_client): +@pytest.mark.parametrize("namespace", [""]) +async def test_deleting_entity( + hass, registry, client, sensor_entities, climate_entities +): """Test deleting a entity.""" - assert await async_setup_component( - hass, - "conversation", - {}, - ) - client = await setup_prometheus_client(hass, hass_client, "") - - await async_setup_component( - hass, climate.DOMAIN, {"climate": [{"platform": "demo"}]} - ) - - assert await async_setup_component( - hass, sensor.DOMAIN, {"sensor": [{"platform": "demo"}]} - ) - - await hass.async_block_till_done() + data = {**sensor_entities, **climate_entities} body = await generate_latest_metrics(client) assert ( @@ -985,11 +694,10 @@ async def test_deleting_entity(hass, hass_client): 'friendly_name="HeatPump"} 0.0' in body ) - registry = entity_registry.async_get(hass) assert "sensor.outside_temperature" in registry.entities assert "climate.heatpump" in registry.entities - registry.async_remove("sensor.outside_temperature") - registry.async_remove("climate.heatpump") + registry.async_remove(data["sensor_1"].entity_id) + registry.async_remove(data["climate_1"].entity_id) await hass.async_block_till_done() body = await generate_latest_metrics(client) @@ -1015,22 +723,12 @@ async def test_deleting_entity(hass, hass_client): ) -async def test_disabling_entity(hass, hass_client): +@pytest.mark.parametrize("namespace", [""]) +async def test_disabling_entity( + hass, registry, client, sensor_entities, climate_entities +): """Test disabling a entity.""" - assert await async_setup_component( - hass, - "conversation", - {}, - ) - client = await setup_prometheus_client(hass, hass_client, "") - - await async_setup_component( - hass, climate.DOMAIN, {"climate": [{"platform": "demo"}]} - ) - - assert await async_setup_component( - hass, sensor.DOMAIN, {"sensor": [{"platform": "demo"}]} - ) + data = {**sensor_entities, **climate_entities} await hass.async_block_till_done() body = await generate_latest_metrics(client) @@ -1080,11 +778,10 @@ async def test_disabling_entity(hass, hass_client): 'friendly_name="HeatPump"} 0.0' in body ) - registry = entity_registry.async_get(hass) assert "sensor.outside_temperature" in registry.entities assert "climate.heatpump" in registry.entities registry.async_update_entity( - entity_id="sensor.outside_temperature", + entity_id=data["sensor_1"].entity_id, disabled_by="user", ) registry.async_update_entity(entity_id="climate.heatpump", disabled_by="user") @@ -1113,6 +810,553 @@ async def test_disabling_entity(hass, hass_client): ) +@pytest.fixture(name="registry") +def entity_registry_fixture(hass): + """Provide entity registry.""" + return entity_registry.async_get(hass) + + +@pytest.fixture(name="sensor_entities") +async def sensor_fixture(hass, registry): + """Simulate sensor entities.""" + data = {} + sensor_1 = registry.async_get_or_create( + domain=sensor.DOMAIN, + platform="test", + unique_id="sensor_1", + unit_of_measurement=TEMP_CELSIUS, + original_device_class=SensorDeviceClass.TEMPERATURE, + suggested_object_id="outside_temperature", + original_name="Outside Temperature", + ) + sensor_1_attributes = {ATTR_BATTERY_LEVEL: 12} + set_state_with_entry(hass, sensor_1, 15.6, sensor_1_attributes) + data["sensor_1"] = sensor_1 + data["sensor_1_attributes"] = sensor_1_attributes + + sensor_2 = registry.async_get_or_create( + domain=sensor.DOMAIN, + platform="test", + unique_id="sensor_2", + unit_of_measurement=PERCENTAGE, + original_device_class=SensorDeviceClass.HUMIDITY, + suggested_object_id="outside_humidity", + original_name="Outside Humidity", + ) + set_state_with_entry(hass, sensor_2, 54.0) + data["sensor_2"] = sensor_2 + + sensor_3 = registry.async_get_or_create( + domain=sensor.DOMAIN, + platform="test", + unique_id="sensor_3", + unit_of_measurement=ENERGY_KILO_WATT_HOUR, + original_device_class=SensorDeviceClass.POWER, + suggested_object_id="radio_energy", + original_name="Radio Energy", + ) + with mock.patch( + "homeassistant.util.dt.utcnow", + return_value=datetime.datetime(1970, 1, 2, tzinfo=dt_util.UTC), + ): + set_state_with_entry(hass, sensor_3, 14) + data["sensor_3"] = sensor_3 + + sensor_4 = registry.async_get_or_create( + domain=sensor.DOMAIN, + platform="test", + unique_id="sensor_4", + unit_of_measurement=ENERGY_KILO_WATT_HOUR, + suggested_object_id="television_energy", + original_name="Television Energy", + ) + set_state_with_entry(hass, sensor_4, 74) + data["sensor_4"] = sensor_4 + + sensor_5 = registry.async_get_or_create( + domain=sensor.DOMAIN, + platform="test", + unique_id="sensor_5", + unit_of_measurement=f"SEK/{ENERGY_KILO_WATT_HOUR}", + suggested_object_id="electricity_price", + original_name="Electricity price", + ) + set_state_with_entry(hass, sensor_5, 0.123) + data["sensor_5"] = sensor_5 + + sensor_6 = registry.async_get_or_create( + domain=sensor.DOMAIN, + platform="test", + unique_id="sensor_6", + unit_of_measurement=DEGREE, + suggested_object_id="wind_direction", + original_name="Wind Direction", + ) + set_state_with_entry(hass, sensor_6, 25) + data["sensor_6"] = sensor_6 + + sensor_7 = registry.async_get_or_create( + domain=sensor.DOMAIN, + platform="test", + unique_id="sensor_7", + unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + suggested_object_id="sps30_pm_1um_weight_concentration", + original_name="SPS30 PM <1µm Weight concentration", + ) + set_state_with_entry(hass, sensor_7, 3.7069) + data["sensor_7"] = sensor_7 + + sensor_8 = registry.async_get_or_create( + domain=sensor.DOMAIN, + platform="test", + unique_id="sensor_8", + suggested_object_id="trend_gradient", + original_name="Trend Gradient", + ) + set_state_with_entry(hass, sensor_8, 0.002) + data["sensor_8"] = sensor_8 + + sensor_9 = registry.async_get_or_create( + domain=sensor.DOMAIN, + platform="test", + unique_id="sensor_9", + suggested_object_id="text", + original_name="Text", + ) + set_state_with_entry(hass, sensor_9, "should_not_work") + data["sensor_9"] = sensor_9 + + sensor_10 = registry.async_get_or_create( + domain=sensor.DOMAIN, + platform="test", + unique_id="sensor_10", + unit_of_measurement="Text", + suggested_object_id="text_unit", + original_name="Text Unit", + ) + set_state_with_entry(hass, sensor_10, "should_not_work") + data["sensor_10"] = sensor_10 + + sensor_11 = registry.async_get_or_create( + domain=sensor.DOMAIN, + platform="test", + unique_id="sensor_11", + unit_of_measurement=TEMP_FAHRENHEIT, + original_device_class=SensorDeviceClass.TEMPERATURE, + suggested_object_id="fahrenheit", + original_name="Fahrenheit", + ) + set_state_with_entry(hass, sensor_11, 50) + data["sensor_11"] = sensor_11 + + await hass.async_block_till_done() + return data + + +@pytest.fixture(name="climate_entities") +async def climate_fixture(hass, registry): + """Simulate climate entities.""" + data = {} + climate_1 = registry.async_get_or_create( + domain=climate.DOMAIN, + platform="test", + unique_id="climate_1", + unit_of_measurement=TEMP_CELSIUS, + suggested_object_id="heatpump", + original_name="HeatPump", + ) + climate_1_attributes = { + ATTR_TEMPERATURE: 20, + ATTR_CURRENT_TEMPERATURE: 25, + ATTR_HVAC_ACTION: CURRENT_HVAC_HEAT, + } + set_state_with_entry(hass, climate_1, CURRENT_HVAC_HEAT, climate_1_attributes) + data["climate_1"] = climate_1 + data["climate_1_attributes"] = climate_1_attributes + + climate_2 = registry.async_get_or_create( + domain=climate.DOMAIN, + platform="test", + unique_id="climate_2", + unit_of_measurement=TEMP_CELSIUS, + suggested_object_id="ecobee", + original_name="Ecobee", + ) + climate_2_attributes = { + ATTR_TEMPERATURE: 21, + ATTR_CURRENT_TEMPERATURE: 22, + ATTR_TARGET_TEMP_LOW: 21, + ATTR_TARGET_TEMP_HIGH: 24, + ATTR_HVAC_ACTION: CURRENT_HVAC_COOL, + } + set_state_with_entry(hass, climate_2, CURRENT_HVAC_HEAT, climate_2_attributes) + data["climate_2"] = climate_2 + data["climate_2_attributes"] = climate_2_attributes + + await hass.async_block_till_done() + return data + + +@pytest.fixture(name="humidifier_entities") +async def humidifier_fixture(hass, registry): + """Simulate humidifier entities.""" + data = {} + humidifier_1 = registry.async_get_or_create( + domain=humidifier.DOMAIN, + platform="test", + unique_id="humidifier_1", + original_device_class=humidifier.HumidifierDeviceClass.HUMIDIFIER, + suggested_object_id="humidifier", + original_name="Humidifier", + ) + humidifier_1_attributes = { + ATTR_HUMIDITY: 68, + } + set_state_with_entry(hass, humidifier_1, STATE_ON, humidifier_1_attributes) + data["humidifier_1"] = humidifier_1 + data["humidifier_1_attributes"] = humidifier_1_attributes + + humidifier_2 = registry.async_get_or_create( + domain=humidifier.DOMAIN, + platform="test", + unique_id="humidifier_2", + original_device_class=humidifier.HumidifierDeviceClass.DEHUMIDIFIER, + suggested_object_id="dehumidifier", + original_name="Dehumidifier", + ) + humidifier_2_attributes = { + ATTR_HUMIDITY: 54, + } + set_state_with_entry(hass, humidifier_2, STATE_ON, humidifier_2_attributes) + data["humidifier_2"] = humidifier_2 + data["humidifier_2_attributes"] = humidifier_2_attributes + + humidifier_3 = registry.async_get_or_create( + domain=humidifier.DOMAIN, + platform="test", + unique_id="humidifier_3", + suggested_object_id="hygrostat", + original_name="Hygrostat", + ) + humidifier_3_attributes = { + ATTR_HUMIDITY: 50, + ATTR_MODE: "home", + ATTR_AVAILABLE_MODES: ["home", "eco"], + } + set_state_with_entry(hass, humidifier_3, STATE_ON, humidifier_3_attributes) + data["humidifier_3"] = humidifier_3 + data["humidifier_3_attributes"] = humidifier_3_attributes + + await hass.async_block_till_done() + return data + + +@pytest.fixture(name="lock_entities") +async def lock_fixture(hass, registry): + """Simulate lock entities.""" + data = {} + lock_1 = registry.async_get_or_create( + domain=lock.DOMAIN, + platform="test", + unique_id="lock_1", + suggested_object_id="front_door", + original_name="Front Door", + ) + set_state_with_entry(hass, lock_1, STATE_LOCKED) + data["lock_1"] = lock_1 + + lock_2 = registry.async_get_or_create( + domain=lock.DOMAIN, + platform="test", + unique_id="lock_2", + suggested_object_id="kitchen_door", + original_name="Kitchen Door", + ) + set_state_with_entry(hass, lock_2, STATE_UNLOCKED) + data["lock_2"] = lock_2 + + await hass.async_block_till_done() + return data + + +@pytest.fixture(name="input_number_entities") +async def input_number_fixture(hass, registry): + """Simulate input_number entities.""" + data = {} + input_number_1 = registry.async_get_or_create( + domain=input_number.DOMAIN, + platform="test", + unique_id="input_number_1", + suggested_object_id="threshold", + original_name="Threshold", + ) + set_state_with_entry(hass, input_number_1, 5.2) + data["input_number_1"] = input_number_1 + + input_number_2 = registry.async_get_or_create( + domain=input_number.DOMAIN, + platform="test", + unique_id="input_number_2", + suggested_object_id="brightness", + ) + set_state_with_entry(hass, input_number_2, 60) + data["input_number_2"] = input_number_2 + + input_number_3 = registry.async_get_or_create( + domain=input_number.DOMAIN, + platform="test", + unique_id="input_number_3", + suggested_object_id="target_temperature", + original_name="Target temperature", + unit_of_measurement=TEMP_CELSIUS, + ) + set_state_with_entry(hass, input_number_3, 22.7) + data["input_number_3"] = input_number_3 + + await hass.async_block_till_done() + return data + + +@pytest.fixture(name="input_boolean_entities") +async def input_boolean_fixture(hass, registry): + """Simulate input_boolean entities.""" + data = {} + input_boolean_1 = registry.async_get_or_create( + domain=input_boolean.DOMAIN, + platform="test", + unique_id="input_boolean_1", + suggested_object_id="test", + original_name="Test", + ) + set_state_with_entry(hass, input_boolean_1, STATE_ON) + data["input_boolean_1"] = input_boolean_1 + + input_boolean_2 = registry.async_get_or_create( + domain=input_boolean.DOMAIN, + platform="test", + unique_id="input_boolean_2", + suggested_object_id="helper", + original_name="Helper", + ) + set_state_with_entry(hass, input_boolean_2, STATE_OFF) + data["input_boolean_2"] = input_boolean_2 + + await hass.async_block_till_done() + return data + + +@pytest.fixture(name="binary_sensor_entities") +async def binary_sensor_fixture(hass, registry): + """Simulate binary_sensor entities.""" + data = {} + binary_sensor_1 = registry.async_get_or_create( + domain=binary_sensor.DOMAIN, + platform="test", + unique_id="binary_sensor_1", + suggested_object_id="door", + original_name="Door", + ) + set_state_with_entry(hass, binary_sensor_1, STATE_ON) + data["binary_sensor_1"] = binary_sensor_1 + + binary_sensor_2 = registry.async_get_or_create( + domain=binary_sensor.DOMAIN, + platform="test", + unique_id="binary_sensor_2", + suggested_object_id="window", + original_name="Window", + ) + set_state_with_entry(hass, binary_sensor_2, STATE_OFF) + data["binary_sensor_2"] = binary_sensor_2 + + await hass.async_block_till_done() + return data + + +@pytest.fixture(name="light_entities") +async def light_fixture(hass, registry): + """Simulate light entities.""" + data = {} + light_1 = registry.async_get_or_create( + domain=light.DOMAIN, + platform="test", + unique_id="light_1", + suggested_object_id="desk", + original_name="Desk", + ) + set_state_with_entry(hass, light_1, STATE_ON) + data["light_1"] = light_1 + + light_2 = registry.async_get_or_create( + domain=light.DOMAIN, + platform="test", + unique_id="light_2", + suggested_object_id="wall", + original_name="Wall", + ) + set_state_with_entry(hass, light_2, STATE_OFF) + data["light_2"] = light_2 + + light_3 = registry.async_get_or_create( + domain=light.DOMAIN, + platform="test", + unique_id="light_3", + suggested_object_id="tv", + original_name="TV", + ) + light_3_attributes = {light.ATTR_BRIGHTNESS: 255} + set_state_with_entry(hass, light_3, STATE_ON, light_3_attributes) + data["light_3"] = light_3 + data["light_3_attributes"] = light_3_attributes + + light_4 = registry.async_get_or_create( + domain=light.DOMAIN, + platform="test", + unique_id="light_4", + suggested_object_id="pc", + original_name="PC", + ) + light_4_attributes = {light.ATTR_BRIGHTNESS: 180} + set_state_with_entry(hass, light_4, STATE_ON, light_4_attributes) + data["light_4"] = light_4 + data["light_4_attributes"] = light_4_attributes + + await hass.async_block_till_done() + return data + + +@pytest.fixture(name="switch_entities") +async def switch_fixture(hass, registry): + """Simulate switch entities.""" + data = {} + switch_1 = registry.async_get_or_create( + domain=switch.DOMAIN, + platform="test", + unique_id="switch_1", + suggested_object_id="boolean", + original_name="Boolean", + ) + switch_1_attributes = {"boolean": True} + set_state_with_entry(hass, switch_1, STATE_ON, switch_1_attributes) + data["switch_1"] = switch_1 + data["switch_1_attributes"] = switch_1_attributes + + switch_2 = registry.async_get_or_create( + domain=switch.DOMAIN, + platform="test", + unique_id="switch_2", + suggested_object_id="number", + original_name="Number", + ) + switch_2_attributes = {"Number": 10.2} + set_state_with_entry(hass, switch_2, STATE_OFF, switch_2_attributes) + data["switch_2"] = switch_2 + data["switch_2_attributes"] = switch_2_attributes + + await hass.async_block_till_done() + return data + + +@pytest.fixture(name="person_entities") +async def person_fixture(hass, registry): + """Simulate person entities.""" + data = {} + person_1 = registry.async_get_or_create( + domain=person.DOMAIN, + platform="test", + unique_id="person_1", + suggested_object_id="bob", + original_name="Bob", + ) + set_state_with_entry(hass, person_1, STATE_HOME) + data["person_1"] = person_1 + + person_2 = registry.async_get_or_create( + domain=person.DOMAIN, + platform="test", + unique_id="person_2", + suggested_object_id="alice", + original_name="Alice", + ) + set_state_with_entry(hass, person_2, STATE_NOT_HOME) + data["person_2"] = person_2 + + await hass.async_block_till_done() + return data + + +@pytest.fixture(name="device_tracker_entities") +async def device_tracker_fixture(hass, registry): + """Simulate device_tracker entities.""" + data = {} + device_tracker_1 = registry.async_get_or_create( + domain=device_tracker.DOMAIN, + platform="test", + unique_id="device_tracker_1", + suggested_object_id="phone", + original_name="Phone", + ) + set_state_with_entry(hass, device_tracker_1, STATE_HOME) + data["device_tracker_1"] = device_tracker_1 + + device_tracker_2 = registry.async_get_or_create( + domain=device_tracker.DOMAIN, + platform="test", + unique_id="device_tracker_2", + suggested_object_id="watch", + original_name="Watch", + ) + set_state_with_entry(hass, device_tracker_2, STATE_NOT_HOME) + data["device_tracker_2"] = device_tracker_2 + + await hass.async_block_till_done() + return data + + +@pytest.fixture(name="counter_entities") +async def counter_fixture(hass, registry): + """Simulate counter entities.""" + data = {} + counter_1 = registry.async_get_or_create( + domain=counter.DOMAIN, + platform="test", + unique_id="counter_1", + suggested_object_id="counter", + ) + set_state_with_entry(hass, counter_1, 2) + data["counter_1"] = counter_1 + + await hass.async_block_till_done() + return data + + +def set_state_with_entry( + hass, + entry: entity_registry.RegistryEntry, + state, + additional_attributes=None, + new_entity_id=None, +): + """Set the state of an entity with an Entity Registry entry.""" + attributes = {} + + if entry.original_name: + attributes[ATTR_FRIENDLY_NAME] = entry.original_name + if entry.unit_of_measurement: + attributes[ATTR_UNIT_OF_MEASUREMENT] = entry.unit_of_measurement + if entry.original_device_class: + attributes[ATTR_DEVICE_CLASS] = entry.original_device_class + + if additional_attributes: + attributes = {**attributes, **additional_attributes} + + hass.states.async_set( + entity_id=new_entity_id if new_entity_id else entry.entity_id, + new_state=state, + attributes=attributes, + ) + + @pytest.fixture(name="mock_client") def mock_client_fixture(): """Mock the prometheus client.""" From c3967dec10ab972bdae03830a169957e29edab53 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 27 Jan 2022 17:00:30 +0100 Subject: [PATCH 0016/1098] Fix vera typing (#65051) --- homeassistant/components/vera/common.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/vera/common.py b/homeassistant/components/vera/common.py index b9df7a807e639d..658ed7904f4bb4 100644 --- a/homeassistant/components/vera/common.py +++ b/homeassistant/components/vera/common.py @@ -2,13 +2,14 @@ from __future__ import annotations from collections import defaultdict +from datetime import datetime from typing import NamedTuple import pyvera as pv from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform -from homeassistant.core import HomeAssistant +from homeassistant.core import CALLBACK_TYPE, HomeAssistant from homeassistant.helpers.event import call_later from .const import DOMAIN @@ -56,7 +57,7 @@ def __init__(self, hass: HomeAssistant) -> None: """Initialize the object.""" super().__init__() self._hass = hass - self._cancel_poll = None + self._cancel_poll: CALLBACK_TYPE | None = None def start(self) -> None: """Start polling for data.""" @@ -72,7 +73,7 @@ def stop(self) -> None: def _schedule_poll(self, delay: float) -> None: self._cancel_poll = call_later(self._hass, delay, self._run_poll_server) - def _run_poll_server(self, now) -> None: + def _run_poll_server(self, now: datetime) -> None: delay = 1 # Long poll for changes. The downstream API instructs the endpoint to wait a From 3a45168b979013079a97095befd5543ec4eb6d4b Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 27 Jan 2022 17:05:08 +0100 Subject: [PATCH 0017/1098] Improve proximity typing (#65053) Co-authored-by: Martin Hjelmare --- homeassistant/components/proximity/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/proximity/__init__.py b/homeassistant/components/proximity/__init__.py index da4392ccc09b89..8eec3e2f0383ad 100644 --- a/homeassistant/components/proximity/__init__.py +++ b/homeassistant/components/proximity/__init__.py @@ -70,14 +70,14 @@ def setup_proximity_component( hass: HomeAssistant, name: str, config: ConfigType ) -> bool: """Set up the individual proximity component.""" - ignored_zones = config.get(CONF_IGNORED_ZONES) - proximity_devices = config.get(CONF_DEVICES) - tolerance = config.get(CONF_TOLERANCE) + ignored_zones: list[str] = config[CONF_IGNORED_ZONES] + proximity_devices: list[str] = config[CONF_DEVICES] + tolerance: int = config[CONF_TOLERANCE] proximity_zone = name - unit_of_measurement = config.get( + unit_of_measurement: str = config.get( CONF_UNIT_OF_MEASUREMENT, hass.config.units.length_unit ) - zone_id = f"zone.{config.get(CONF_ZONE)}" + zone_id = f"zone.{config[CONF_ZONE]}" proximity = Proximity( # type:ignore[no-untyped-call] hass, From 9799965c623fc14ef670724607f267d69d2b1d90 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Thu, 27 Jan 2022 16:08:26 +0000 Subject: [PATCH 0018/1098] Better names for energy related homekit_controller sensors (#65055) --- .../components/homekit_controller/sensor.py | 14 +++++++------- .../specific_devices/test_connectsense.py | 16 ++++++++-------- .../specific_devices/test_eve_energy.py | 4 ++-- .../specific_devices/test_koogeek_p1eu.py | 4 ++-- .../specific_devices/test_koogeek_sw2.py | 4 ++-- .../specific_devices/test_vocolinc_vp3.py | 4 ++-- .../components/homekit_controller/test_sensor.py | 4 ++-- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/homekit_controller/sensor.py b/homeassistant/components/homekit_controller/sensor.py index eb408834bcabcc..0cec354e1ab4a0 100644 --- a/homeassistant/components/homekit_controller/sensor.py +++ b/homeassistant/components/homekit_controller/sensor.py @@ -44,21 +44,21 @@ class HomeKitSensorEntityDescription(SensorEntityDescription): SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = { CharacteristicsTypes.Vendor.CONNECTSENSE_ENERGY_WATT: HomeKitSensorEntityDescription( key=CharacteristicsTypes.Vendor.CONNECTSENSE_ENERGY_WATT, - name="Real Time Energy", + name="Power", device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=POWER_WATT, ), CharacteristicsTypes.Vendor.CONNECTSENSE_ENERGY_AMPS: HomeKitSensorEntityDescription( key=CharacteristicsTypes.Vendor.CONNECTSENSE_ENERGY_AMPS, - name="Real Time Current", + name="Current", device_class=SensorDeviceClass.CURRENT, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE, ), CharacteristicsTypes.Vendor.CONNECTSENSE_ENERGY_AMPS_20: HomeKitSensorEntityDescription( key=CharacteristicsTypes.Vendor.CONNECTSENSE_ENERGY_AMPS_20, - name="Real Time Current", + name="Current", device_class=SensorDeviceClass.CURRENT, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE, @@ -72,7 +72,7 @@ class HomeKitSensorEntityDescription(SensorEntityDescription): ), CharacteristicsTypes.Vendor.EVE_ENERGY_WATT: HomeKitSensorEntityDescription( key=CharacteristicsTypes.Vendor.EVE_ENERGY_WATT, - name="Real Time Energy", + name="Power", device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=POWER_WATT, @@ -100,14 +100,14 @@ class HomeKitSensorEntityDescription(SensorEntityDescription): ), CharacteristicsTypes.Vendor.KOOGEEK_REALTIME_ENERGY: HomeKitSensorEntityDescription( key=CharacteristicsTypes.Vendor.KOOGEEK_REALTIME_ENERGY, - name="Real Time Energy", + name="Power", device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=POWER_WATT, ), CharacteristicsTypes.Vendor.KOOGEEK_REALTIME_ENERGY_2: HomeKitSensorEntityDescription( key=CharacteristicsTypes.Vendor.KOOGEEK_REALTIME_ENERGY_2, - name="Real Time Energy", + name="Power", device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=POWER_WATT, @@ -121,7 +121,7 @@ class HomeKitSensorEntityDescription(SensorEntityDescription): ), CharacteristicsTypes.Vendor.VOCOLINC_OUTLET_ENERGY: HomeKitSensorEntityDescription( key=CharacteristicsTypes.Vendor.VOCOLINC_OUTLET_ENERGY, - name="Real Time Energy", + name="Power", device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=POWER_WATT, diff --git a/tests/components/homekit_controller/specific_devices/test_connectsense.py b/tests/components/homekit_controller/specific_devices/test_connectsense.py index 371ee360adce0b..2cbdf924319622 100644 --- a/tests/components/homekit_controller/specific_devices/test_connectsense.py +++ b/tests/components/homekit_controller/specific_devices/test_connectsense.py @@ -35,16 +35,16 @@ async def test_connectsense_setup(hass): devices=[], entities=[ EntityTestInfo( - entity_id="sensor.inwall_outlet_0394de_real_time_current", - friendly_name="InWall Outlet-0394DE Real Time Current", + entity_id="sensor.inwall_outlet_0394de_current", + friendly_name="InWall Outlet-0394DE Current", unique_id="homekit-1020301376-aid:1-sid:13-cid:18", capabilities={"state_class": SensorStateClass.MEASUREMENT}, unit_of_measurement=ELECTRIC_CURRENT_AMPERE, state="0.03", ), EntityTestInfo( - entity_id="sensor.inwall_outlet_0394de_real_time_energy", - friendly_name="InWall Outlet-0394DE Real Time Energy", + entity_id="sensor.inwall_outlet_0394de_power", + friendly_name="InWall Outlet-0394DE Power", unique_id="homekit-1020301376-aid:1-sid:13-cid:19", capabilities={"state_class": SensorStateClass.MEASUREMENT}, unit_of_measurement=POWER_WATT, @@ -65,16 +65,16 @@ async def test_connectsense_setup(hass): state="on", ), EntityTestInfo( - entity_id="sensor.inwall_outlet_0394de_real_time_current_2", - friendly_name="InWall Outlet-0394DE Real Time Current", + entity_id="sensor.inwall_outlet_0394de_current_2", + friendly_name="InWall Outlet-0394DE Current", unique_id="homekit-1020301376-aid:1-sid:25-cid:30", capabilities={"state_class": SensorStateClass.MEASUREMENT}, unit_of_measurement=ELECTRIC_CURRENT_AMPERE, state="0.05", ), EntityTestInfo( - entity_id="sensor.inwall_outlet_0394de_real_time_energy_2", - friendly_name="InWall Outlet-0394DE Real Time Energy", + entity_id="sensor.inwall_outlet_0394de_power_2", + friendly_name="InWall Outlet-0394DE Power", unique_id="homekit-1020301376-aid:1-sid:25-cid:31", capabilities={"state_class": SensorStateClass.MEASUREMENT}, unit_of_measurement=POWER_WATT, diff --git a/tests/components/homekit_controller/specific_devices/test_eve_energy.py b/tests/components/homekit_controller/specific_devices/test_eve_energy.py index e6cfa1eaa5ec03..0ba9b0bee250f6 100644 --- a/tests/components/homekit_controller/specific_devices/test_eve_energy.py +++ b/tests/components/homekit_controller/specific_devices/test_eve_energy.py @@ -59,9 +59,9 @@ async def test_eve_degree_setup(hass): state="0.400000005960464", ), EntityTestInfo( - entity_id="sensor.eve_energy_50ff_real_time_energy", + entity_id="sensor.eve_energy_50ff_power", unique_id="homekit-AA00A0A00000-aid:1-sid:28-cid:34", - friendly_name="Eve Energy 50FF Real Time Energy", + friendly_name="Eve Energy 50FF Power", unit_of_measurement=POWER_WATT, capabilities={"state_class": SensorStateClass.MEASUREMENT}, state="0", diff --git a/tests/components/homekit_controller/specific_devices/test_koogeek_p1eu.py b/tests/components/homekit_controller/specific_devices/test_koogeek_p1eu.py index 78d3efb64bb615..f93adc732ba5f4 100644 --- a/tests/components/homekit_controller/specific_devices/test_koogeek_p1eu.py +++ b/tests/components/homekit_controller/specific_devices/test_koogeek_p1eu.py @@ -37,8 +37,8 @@ async def test_koogeek_p1eu_setup(hass): state="off", ), EntityTestInfo( - entity_id="sensor.koogeek_p1_a00aa0_real_time_energy", - friendly_name="Koogeek-P1-A00AA0 Real Time Energy", + entity_id="sensor.koogeek_p1_a00aa0_power", + friendly_name="Koogeek-P1-A00AA0 Power", unique_id="homekit-EUCP03190xxxxx48-aid:1-sid:21-cid:22", unit_of_measurement=POWER_WATT, capabilities={"state_class": SensorStateClass.MEASUREMENT}, diff --git a/tests/components/homekit_controller/specific_devices/test_koogeek_sw2.py b/tests/components/homekit_controller/specific_devices/test_koogeek_sw2.py index 7c7688be4ee0dd..ed940cb637645f 100644 --- a/tests/components/homekit_controller/specific_devices/test_koogeek_sw2.py +++ b/tests/components/homekit_controller/specific_devices/test_koogeek_sw2.py @@ -43,8 +43,8 @@ async def test_koogeek_sw2_setup(hass): state="off", ), EntityTestInfo( - entity_id="sensor.koogeek_sw2_187a91_real_time_energy", - friendly_name="Koogeek-SW2-187A91 Real Time Energy", + entity_id="sensor.koogeek_sw2_187a91_power", + friendly_name="Koogeek-SW2-187A91 Power", unique_id="homekit-CNNT061751001372-aid:1-sid:14-cid:18", unit_of_measurement=POWER_WATT, capabilities={"state_class": SensorStateClass.MEASUREMENT}, diff --git a/tests/components/homekit_controller/specific_devices/test_vocolinc_vp3.py b/tests/components/homekit_controller/specific_devices/test_vocolinc_vp3.py index a082683cf21cb9..da69b7fe3093f4 100644 --- a/tests/components/homekit_controller/specific_devices/test_vocolinc_vp3.py +++ b/tests/components/homekit_controller/specific_devices/test_vocolinc_vp3.py @@ -37,8 +37,8 @@ async def test_vocolinc_vp3_setup(hass): state="on", ), EntityTestInfo( - entity_id="sensor.vocolinc_vp3_123456_real_time_energy", - friendly_name="VOCOlinc-VP3-123456 Real Time Energy", + entity_id="sensor.vocolinc_vp3_123456_power", + friendly_name="VOCOlinc-VP3-123456 Power", unique_id="homekit-EU0121203xxxxx07-aid:1-sid:48-cid:97", unit_of_measurement=POWER_WATT, capabilities={"state_class": SensorStateClass.MEASUREMENT}, diff --git a/tests/components/homekit_controller/test_sensor.py b/tests/components/homekit_controller/test_sensor.py index c50e23bac13c0c..4c57d94b2b8e3c 100644 --- a/tests/components/homekit_controller/test_sensor.py +++ b/tests/components/homekit_controller/test_sensor.py @@ -218,7 +218,7 @@ async def test_switch_with_sensor(hass, utcnow): # Helper will be for the primary entity, which is the outlet. Make a helper for the sensor. energy_helper = Helper( hass, - "sensor.testdevice_real_time_energy", + "sensor.testdevice_power", helper.pairing, helper.accessory, helper.config_entry, @@ -248,7 +248,7 @@ async def test_sensor_unavailable(hass, utcnow): # Helper will be for the primary entity, which is the outlet. Make a helper for the sensor. energy_helper = Helper( hass, - "sensor.testdevice_real_time_energy", + "sensor.testdevice_power", helper.pairing, helper.accessory, helper.config_entry, From 3d461e9e1f0ef9bb7ca56140d72322fe0d483ef1 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 27 Jan 2022 17:37:40 +0100 Subject: [PATCH 0019/1098] Fix notify leaving zone blueprint (#65056) --- .../components/automation/blueprints/notify_leaving_zone.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml b/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml index 71abf8f865cd63..1dc8a0eddf8fe0 100644 --- a/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml +++ b/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml @@ -34,7 +34,9 @@ variables: condition: condition: template - value_template: "{{ trigger.from_state.state == zone_state and trigger.to_state.state != zone_state }}" + # The first case handles leaving the Home zone which has a special state when zoning called 'home'. + # The second case handles leaving all other zones. + value_template: "{{ zone_entity == 'zone.home' and trigger.from_state.state == 'home' and trigger.to_state.state != 'home' or trigger.from_state.state == zone_state and trigger.to_state.state != zone_state }}" action: - alias: "Notify that a person has left the zone" From 603d0fb068fbba6bf41fc60bfc65301db00bfeb0 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Thu, 27 Jan 2022 16:41:53 +0000 Subject: [PATCH 0020/1098] Allow homekit_controller to customise Ecobee home/sleep/away thresholds (#65036) --- .../components/homekit_controller/const.py | 6 ++ .../components/homekit_controller/number.py | 36 +++++++++ .../specific_devices/test_ecobee3.py | 80 +++++++++++++++++++ 3 files changed, 122 insertions(+) diff --git a/homeassistant/components/homekit_controller/const.py b/homeassistant/components/homekit_controller/const.py index d0dfa9bad4f01e..db9c534f708c51 100644 --- a/homeassistant/components/homekit_controller/const.py +++ b/homeassistant/components/homekit_controller/const.py @@ -56,6 +56,12 @@ CharacteristicsTypes.Vendor.AQARA_E1_GATEWAY_VOLUME: "number", CharacteristicsTypes.Vendor.AQARA_PAIRING_MODE: "switch", CharacteristicsTypes.Vendor.AQARA_E1_PAIRING_MODE: "switch", + CharacteristicsTypes.Vendor.ECOBEE_HOME_TARGET_COOL: "number", + CharacteristicsTypes.Vendor.ECOBEE_HOME_TARGET_HEAT: "number", + CharacteristicsTypes.Vendor.ECOBEE_SLEEP_TARGET_COOL: "number", + CharacteristicsTypes.Vendor.ECOBEE_SLEEP_TARGET_HEAT: "number", + CharacteristicsTypes.Vendor.ECOBEE_AWAY_TARGET_COOL: "number", + CharacteristicsTypes.Vendor.ECOBEE_AWAY_TARGET_HEAT: "number", CharacteristicsTypes.Vendor.EVE_ENERGY_WATT: "sensor", CharacteristicsTypes.Vendor.EVE_DEGREE_AIR_PRESSURE: "sensor", CharacteristicsTypes.Vendor.EVE_DEGREE_ELEVATION: "number", diff --git a/homeassistant/components/homekit_controller/number.py b/homeassistant/components/homekit_controller/number.py index c2b3dc6d7b3679..9c76adf52a942c 100644 --- a/homeassistant/components/homekit_controller/number.py +++ b/homeassistant/components/homekit_controller/number.py @@ -41,6 +41,42 @@ icon="mdi:volume-high", entity_category=EntityCategory.CONFIG, ), + CharacteristicsTypes.Vendor.ECOBEE_HOME_TARGET_COOL: NumberEntityDescription( + key=CharacteristicsTypes.Vendor.ECOBEE_HOME_TARGET_COOL, + name="Home Cool Target", + icon="mdi:thermometer-minus", + entity_category=EntityCategory.CONFIG, + ), + CharacteristicsTypes.Vendor.ECOBEE_HOME_TARGET_HEAT: NumberEntityDescription( + key=CharacteristicsTypes.Vendor.ECOBEE_HOME_TARGET_HEAT, + name="Home Heat Target", + icon="mdi:thermometer-plus", + entity_category=EntityCategory.CONFIG, + ), + CharacteristicsTypes.Vendor.ECOBEE_SLEEP_TARGET_COOL: NumberEntityDescription( + key=CharacteristicsTypes.Vendor.ECOBEE_SLEEP_TARGET_COOL, + name="Sleep Cool Target", + icon="mdi:thermometer-minus", + entity_category=EntityCategory.CONFIG, + ), + CharacteristicsTypes.Vendor.ECOBEE_SLEEP_TARGET_HEAT: NumberEntityDescription( + key=CharacteristicsTypes.Vendor.ECOBEE_SLEEP_TARGET_HEAT, + name="Sleep Heat Target", + icon="mdi:thermometer-plus", + entity_category=EntityCategory.CONFIG, + ), + CharacteristicsTypes.Vendor.ECOBEE_AWAY_TARGET_COOL: NumberEntityDescription( + key=CharacteristicsTypes.Vendor.ECOBEE_AWAY_TARGET_COOL, + name="Away Cool Target", + icon="mdi:thermometer-minus", + entity_category=EntityCategory.CONFIG, + ), + CharacteristicsTypes.Vendor.ECOBEE_AWAY_TARGET_HEAT: NumberEntityDescription( + key=CharacteristicsTypes.Vendor.ECOBEE_AWAY_TARGET_HEAT, + name="Away Heat Target", + icon="mdi:thermometer-plus", + entity_category=EntityCategory.CONFIG, + ), } diff --git a/tests/components/homekit_controller/specific_devices/test_ecobee3.py b/tests/components/homekit_controller/specific_devices/test_ecobee3.py index 2d540f31850c3d..b2b85c70d6e8b7 100644 --- a/tests/components/homekit_controller/specific_devices/test_ecobee3.py +++ b/tests/components/homekit_controller/specific_devices/test_ecobee3.py @@ -14,10 +14,12 @@ SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, ) +from homeassistant.components.number import NumberMode from homeassistant.components.sensor import SensorStateClass from homeassistant.config_entries import ConfigEntryState from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers import entity_registry as er +from homeassistant.helpers.entity import EntityCategory from tests.components.homekit_controller.common import ( HUB_TEST_ACCESSORY_ID, @@ -121,6 +123,84 @@ async def test_ecobee3_setup(hass): }, state="heat", ), + EntityTestInfo( + entity_id="number.homew_home_cool_target", + friendly_name="HomeW Home Cool Target", + unique_id="homekit-123456789012-aid:1-sid:16-cid:35", + entity_category=EntityCategory.CONFIG, + capabilities={ + "max": 33.3, + "min": 18.3, + "mode": NumberMode.AUTO, + "step": 0.1, + }, + state="24.4", + ), + EntityTestInfo( + entity_id="number.homew_home_heat_target", + friendly_name="HomeW Home Heat Target", + unique_id="homekit-123456789012-aid:1-sid:16-cid:34", + entity_category=EntityCategory.CONFIG, + capabilities={ + "max": 26.1, + "min": 7.2, + "mode": NumberMode.AUTO, + "step": 0.1, + }, + state="22.2", + ), + EntityTestInfo( + entity_id="number.homew_sleep_cool_target", + friendly_name="HomeW Sleep Cool Target", + unique_id="homekit-123456789012-aid:1-sid:16-cid:37", + entity_category=EntityCategory.CONFIG, + capabilities={ + "max": 33.3, + "min": 18.3, + "mode": NumberMode.AUTO, + "step": 0.1, + }, + state="27.8", + ), + EntityTestInfo( + entity_id="number.homew_sleep_heat_target", + friendly_name="HomeW Sleep Heat Target", + unique_id="homekit-123456789012-aid:1-sid:16-cid:36", + entity_category=EntityCategory.CONFIG, + capabilities={ + "max": 26.1, + "min": 7.2, + "mode": NumberMode.AUTO, + "step": 0.1, + }, + state="17.8", + ), + EntityTestInfo( + entity_id="number.homew_away_cool_target", + friendly_name="HomeW Away Cool Target", + unique_id="homekit-123456789012-aid:1-sid:16-cid:39", + entity_category=EntityCategory.CONFIG, + capabilities={ + "max": 33.3, + "min": 18.3, + "mode": NumberMode.AUTO, + "step": 0.1, + }, + state="26.7", + ), + EntityTestInfo( + entity_id="number.homew_away_heat_target", + friendly_name="HomeW Away Heat Target", + unique_id="homekit-123456789012-aid:1-sid:16-cid:38", + entity_category=EntityCategory.CONFIG, + capabilities={ + "max": 26.1, + "min": 7.2, + "mode": NumberMode.AUTO, + "step": 0.1, + }, + state="18.9", + ), EntityTestInfo( entity_id="sensor.homew_current_temperature", friendly_name="HomeW Current Temperature", From e0d970c73922e462145ec991f34b02fb1b667794 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Thu, 27 Jan 2022 10:43:23 -0600 Subject: [PATCH 0021/1098] Update rokuecp to 0.12.0 (#65030) --- homeassistant/components/roku/manifest.json | 2 +- homeassistant/components/roku/media_player.py | 4 ++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/roku/test_media_player.py | 14 +++++++------- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/roku/manifest.json b/homeassistant/components/roku/manifest.json index 7c712c81e1ff14..7dd5974589c3ed 100644 --- a/homeassistant/components/roku/manifest.json +++ b/homeassistant/components/roku/manifest.json @@ -2,7 +2,7 @@ "domain": "roku", "name": "Roku", "documentation": "https://www.home-assistant.io/integrations/roku", - "requirements": ["rokuecp==0.11.0"], + "requirements": ["rokuecp==0.12.0"], "homekit": { "models": ["3810X", "4660X", "7820X", "C105X", "C135X"] }, diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index 4d064d5d326633..67abae262d5307 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -408,13 +408,13 @@ async def async_play_media(self, media_type: str, media_id: str, **kwargs) -> No if attr in extra } - await self.coordinator.roku.play_video(media_id, params) + await self.coordinator.roku.play_on_roku(media_id, params) elif media_type == FORMAT_CONTENT_TYPE[HLS_PROVIDER]: params = { "MediaType": "hls", } - await self.coordinator.roku.play_video(media_id, params) + await self.coordinator.roku.play_on_roku(media_id, params) await self.coordinator.async_request_refresh() diff --git a/requirements_all.txt b/requirements_all.txt index 1ad6372a6a5f8e..58aea66c491a38 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2111,7 +2111,7 @@ rjpl==0.3.6 rocketchat-API==0.6.1 # homeassistant.components.roku -rokuecp==0.11.0 +rokuecp==0.12.0 # homeassistant.components.roomba roombapy==1.6.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1e929340cdd718..b2785535014f21 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1294,7 +1294,7 @@ rflink==0.0.62 ring_doorbell==0.7.2 # homeassistant.components.roku -rokuecp==0.11.0 +rokuecp==0.12.0 # homeassistant.components.roomba roombapy==1.6.5 diff --git a/tests/components/roku/test_media_player.py b/tests/components/roku/test_media_player.py index d42e06aceb8c79..a039b313702acf 100644 --- a/tests/components/roku/test_media_player.py +++ b/tests/components/roku/test_media_player.py @@ -476,8 +476,8 @@ async def test_services( blocking=True, ) - assert mock_roku.play_video.call_count == 1 - mock_roku.play_video.assert_called_with( + assert mock_roku.play_on_roku.call_count == 1 + mock_roku.play_on_roku.assert_called_with( "https://awesome.tld/media.mp4", { "videoName": "Sent from HA", @@ -496,8 +496,8 @@ async def test_services( blocking=True, ) - assert mock_roku.play_video.call_count == 2 - mock_roku.play_video.assert_called_with( + assert mock_roku.play_on_roku.call_count == 2 + mock_roku.play_on_roku.assert_called_with( "https://awesome.tld/api/hls/api_token/master_playlist.m3u8", { "MediaType": "hls", @@ -551,9 +551,9 @@ async def test_services_play_media_local_source( blocking=True, ) - assert mock_roku.play_video.call_count == 1 - assert mock_roku.play_video.call_args - call_args = mock_roku.play_video.call_args.args + assert mock_roku.play_on_roku.call_count == 1 + assert mock_roku.play_on_roku.call_args + call_args = mock_roku.play_on_roku.call_args.args assert "/media/local/Epic%20Sax%20Guy%2010%20Hours.mp4?authSig=" in call_args[0] From 70321ed795086f385dd521f04156a26ff0948ffd Mon Sep 17 00:00:00 2001 From: Patrik Lindgren <21142447+ggravlingen@users.noreply.github.com> Date: Thu, 27 Jan 2022 17:47:47 +0100 Subject: [PATCH 0022/1098] Add battery sensor for Tradfri blinds (#65067) --- homeassistant/components/tradfri/sensor.py | 5 ++- tests/components/tradfri/test_sensor.py | 42 ++++++++++++++++------ 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/tradfri/sensor.py b/homeassistant/components/tradfri/sensor.py index 3d042fa6417b0c..3f8fa96857ddf3 100644 --- a/homeassistant/components/tradfri/sensor.py +++ b/homeassistant/components/tradfri/sensor.py @@ -28,7 +28,7 @@ async def async_setup_entry( api = coordinator_data[KEY_API] async_add_entities( - TradfriSensor( + TradfriBatterySensor( device_coordinator, api, gateway_id, @@ -37,14 +37,13 @@ async def async_setup_entry( if ( not device_coordinator.device.has_light_control and not device_coordinator.device.has_socket_control - and not device_coordinator.device.has_blind_control and not device_coordinator.device.has_signal_repeater_control and not device_coordinator.device.has_air_purifier_control ) ) -class TradfriSensor(TradfriBaseEntity, SensorEntity): +class TradfriBatterySensor(TradfriBaseEntity, SensorEntity): """The platform class required by Home Assistant.""" _attr_device_class = SensorDeviceClass.BATTERY diff --git a/tests/components/tradfri/test_sensor.py b/tests/components/tradfri/test_sensor.py index 1e9ae718285340..63d4b6f84e53ab 100644 --- a/tests/components/tradfri/test_sensor.py +++ b/tests/components/tradfri/test_sensor.py @@ -5,16 +5,13 @@ from .common import setup_integration -def mock_sensor(state_name: str, state_value: str, device_number=0): +def mock_sensor(test_state: list, device_number=0): """Mock a tradfri sensor.""" dev_info_mock = MagicMock() dev_info_mock.manufacturer = "manufacturer" dev_info_mock.model_number = "model" dev_info_mock.firmware_version = "1.2.3" - # Set state value, eg battery_level = 50 - setattr(dev_info_mock, state_name, state_value) - _mock_sensor = Mock( id=f"mock-sensor-id-{device_number}", reachable=True, @@ -26,6 +23,11 @@ def mock_sensor(state_name: str, state_value: str, device_number=0): has_signal_repeater_control=False, has_air_purifier_control=False, ) + + # Set state value, eg battery_level = 50, or has_air_purifier_control=True + for state in test_state: + setattr(dev_info_mock, state["attribute"], state["value"]) + _mock_sensor.name = f"tradfri_sensor_{device_number}" return _mock_sensor @@ -34,7 +36,7 @@ def mock_sensor(state_name: str, state_value: str, device_number=0): async def test_battery_sensor(hass, mock_gateway, mock_api_factory): """Test that a battery sensor is correctly added.""" mock_gateway.mock_devices.append( - mock_sensor(state_name="battery_level", state_value=60) + mock_sensor(test_state=[{"attribute": "battery_level", "value": 60}]) ) await setup_integration(hass) @@ -45,10 +47,27 @@ async def test_battery_sensor(hass, mock_gateway, mock_api_factory): assert sensor_1.attributes["device_class"] == "battery" +async def test_cover_battery_sensor(hass, mock_gateway, mock_api_factory): + """Test that a battery sensor is correctly added for a cover (blind).""" + mock_gateway.mock_devices.append( + mock_sensor( + test_state=[ + {"attribute": "battery_level", "value": 42, "has_blind_control": True} + ] + ) + ) + await setup_integration(hass) + + sensor_1 = hass.states.get("sensor.tradfri_sensor_0") + assert sensor_1 is not None + assert sensor_1.state == "42" + assert sensor_1.attributes["unit_of_measurement"] == "%" + assert sensor_1.attributes["device_class"] == "battery" + + async def test_sensor_observed(hass, mock_gateway, mock_api_factory): """Test that sensors are correctly observed.""" - - sensor = mock_sensor(state_name="battery_level", state_value=60) + sensor = mock_sensor(test_state=[{"attribute": "battery_level", "value": 60}]) mock_gateway.mock_devices.append(sensor) await setup_integration(hass) assert len(sensor.observe.mock_calls) > 0 @@ -56,11 +75,14 @@ async def test_sensor_observed(hass, mock_gateway, mock_api_factory): async def test_sensor_available(hass, mock_gateway, mock_api_factory): """Test sensor available property.""" - - sensor = mock_sensor(state_name="battery_level", state_value=60, device_number=1) + sensor = mock_sensor( + test_state=[{"attribute": "battery_level", "value": 60}], device_number=1 + ) sensor.reachable = True - sensor2 = mock_sensor(state_name="battery_level", state_value=60, device_number=2) + sensor2 = mock_sensor( + test_state=[{"attribute": "battery_level", "value": 60}], device_number=2 + ) sensor2.reachable = False mock_gateway.mock_devices.append(sensor) From a65694457a8c9357d890877144be69f3179bc632 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Thu, 27 Jan 2022 17:02:38 +0000 Subject: [PATCH 0023/1098] Allow homekit_controller to set Ecobee's mode (#65032) --- .../components/homekit_controller/const.py | 7 ++ .../components/homekit_controller/select.py | 71 +++++++++++++++ .../homekit_controller/strings.select.json | 9 ++ .../specific_devices/test_ecobee3.py | 7 ++ .../homekit_controller/test_select.py | 90 +++++++++++++++++++ 5 files changed, 184 insertions(+) create mode 100644 homeassistant/components/homekit_controller/select.py create mode 100644 homeassistant/components/homekit_controller/strings.select.json create mode 100644 tests/components/homekit_controller/test_select.py diff --git a/homeassistant/components/homekit_controller/const.py b/homeassistant/components/homekit_controller/const.py index db9c534f708c51..8c20afcd06f712 100644 --- a/homeassistant/components/homekit_controller/const.py +++ b/homeassistant/components/homekit_controller/const.py @@ -1,4 +1,6 @@ """Constants for the homekit_controller component.""" +from typing import Final + from aiohomekit.model.characteristics import CharacteristicsTypes DOMAIN = "homekit_controller" @@ -62,6 +64,7 @@ CharacteristicsTypes.Vendor.ECOBEE_SLEEP_TARGET_HEAT: "number", CharacteristicsTypes.Vendor.ECOBEE_AWAY_TARGET_COOL: "number", CharacteristicsTypes.Vendor.ECOBEE_AWAY_TARGET_HEAT: "number", + CharacteristicsTypes.Vendor.ECOBEE_CURRENT_MODE: "select", CharacteristicsTypes.Vendor.EVE_ENERGY_WATT: "sensor", CharacteristicsTypes.Vendor.EVE_DEGREE_AIR_PRESSURE: "sensor", CharacteristicsTypes.Vendor.EVE_DEGREE_ELEVATION: "number", @@ -92,3 +95,7 @@ for k, v in list(CHARACTERISTIC_PLATFORMS.items()): value = CHARACTERISTIC_PLATFORMS.pop(k) CHARACTERISTIC_PLATFORMS[CharacteristicsTypes.get_uuid(k)] = value + + +# Device classes +DEVICE_CLASS_ECOBEE_MODE: Final = "homekit_controller__ecobee_mode" diff --git a/homeassistant/components/homekit_controller/select.py b/homeassistant/components/homekit_controller/select.py new file mode 100644 index 00000000000000..55c12c77820f2f --- /dev/null +++ b/homeassistant/components/homekit_controller/select.py @@ -0,0 +1,71 @@ +"""Support for Homekit select entities.""" +from __future__ import annotations + +from aiohomekit.model.characteristics import Characteristic, CharacteristicsTypes + +from homeassistant.components.select import SelectEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import KNOWN_DEVICES, CharacteristicEntity +from .const import DEVICE_CLASS_ECOBEE_MODE + +_ECOBEE_MODE_TO_TEXT = { + 0: "home", + 1: "sleep", + 2: "away", +} +_ECOBEE_MODE_TO_NUMBERS = {v: k for (k, v) in _ECOBEE_MODE_TO_TEXT.items()} + + +class EcobeeModeSelect(CharacteristicEntity, SelectEntity): + """Represents a ecobee mode select entity.""" + + _attr_options = ["home", "sleep", "away"] + _attr_device_class = DEVICE_CLASS_ECOBEE_MODE + + @property + def name(self) -> str: + """Return the name of the device if any.""" + if name := super().name: + return f"{name} Current Mode" + return "Current Mode" + + def get_characteristic_types(self): + """Define the homekit characteristics the entity cares about.""" + return [ + CharacteristicsTypes.Vendor.ECOBEE_CURRENT_MODE, + ] + + @property + def current_option(self) -> str | None: + """Return the current selected option.""" + return _ECOBEE_MODE_TO_TEXT.get(self._char.value) + + async def async_select_option(self, option: str) -> None: + """Set the current mode.""" + option_int = _ECOBEE_MODE_TO_NUMBERS[option] + await self.async_put_characteristics( + {CharacteristicsTypes.Vendor.ECOBEE_SET_HOLD_SCHEDULE: option_int} + ) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Homekit select entities.""" + hkid = config_entry.data["AccessoryPairingID"] + conn = hass.data[KNOWN_DEVICES][hkid] + + @callback + def async_add_characteristic(char: Characteristic): + if char.type == CharacteristicsTypes.Vendor.ECOBEE_CURRENT_MODE: + info = {"aid": char.service.accessory.aid, "iid": char.service.iid} + async_add_entities([EcobeeModeSelect(conn, info, char)]) + return True + return False + + conn.add_char_factory(async_add_characteristic) diff --git a/homeassistant/components/homekit_controller/strings.select.json b/homeassistant/components/homekit_controller/strings.select.json new file mode 100644 index 00000000000000..83f83e56ec2591 --- /dev/null +++ b/homeassistant/components/homekit_controller/strings.select.json @@ -0,0 +1,9 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "home": "Home", + "sleep": "Sleep", + "away": "Away" + } + } +} \ No newline at end of file diff --git a/tests/components/homekit_controller/specific_devices/test_ecobee3.py b/tests/components/homekit_controller/specific_devices/test_ecobee3.py index b2b85c70d6e8b7..83378650b97b67 100644 --- a/tests/components/homekit_controller/specific_devices/test_ecobee3.py +++ b/tests/components/homekit_controller/specific_devices/test_ecobee3.py @@ -209,6 +209,13 @@ async def test_ecobee3_setup(hass): unit_of_measurement=TEMP_CELSIUS, state="21.8", ), + EntityTestInfo( + entity_id="select.homew_current_mode", + friendly_name="HomeW Current Mode", + unique_id="homekit-123456789012-aid:1-sid:16-cid:33", + capabilities={"options": ["home", "sleep", "away"]}, + state="home", + ), ], ), ) diff --git a/tests/components/homekit_controller/test_select.py b/tests/components/homekit_controller/test_select.py new file mode 100644 index 00000000000000..ea22bf68c540ea --- /dev/null +++ b/tests/components/homekit_controller/test_select.py @@ -0,0 +1,90 @@ +"""Basic checks for HomeKit select entities.""" +from aiohomekit.model import Accessory +from aiohomekit.model.characteristics import CharacteristicsTypes +from aiohomekit.model.services import ServicesTypes + +from tests.components.homekit_controller.common import Helper, setup_test_component + + +def create_service_with_ecobee_mode(accessory: Accessory): + """Define a thermostat with ecobee mode characteristics.""" + service = accessory.add_service(ServicesTypes.THERMOSTAT, add_required=True) + + current_mode = service.add_char(CharacteristicsTypes.Vendor.ECOBEE_CURRENT_MODE) + current_mode.value = 0 + + service.add_char(CharacteristicsTypes.Vendor.ECOBEE_SET_HOLD_SCHEDULE) + + return service + + +async def test_read_current_mode(hass, utcnow): + """Test that Ecobee mode can be correctly read and show as human readable text.""" + helper = await setup_test_component(hass, create_service_with_ecobee_mode) + service = helper.accessory.services.first(service_type=ServicesTypes.THERMOSTAT) + + # Helper will be for the primary entity, which is the service. Make a helper for the sensor. + energy_helper = Helper( + hass, + "select.testdevice_current_mode", + helper.pairing, + helper.accessory, + helper.config_entry, + ) + + mode = service[CharacteristicsTypes.Vendor.ECOBEE_CURRENT_MODE] + + state = await energy_helper.poll_and_get_state() + assert state.state == "home" + + mode.value = 1 + state = await energy_helper.poll_and_get_state() + assert state.state == "sleep" + + mode.value = 2 + state = await energy_helper.poll_and_get_state() + assert state.state == "away" + + +async def test_write_current_mode(hass, utcnow): + """Test can set a specific mode.""" + helper = await setup_test_component(hass, create_service_with_ecobee_mode) + service = helper.accessory.services.first(service_type=ServicesTypes.THERMOSTAT) + + # Helper will be for the primary entity, which is the service. Make a helper for the sensor. + energy_helper = Helper( + hass, + "select.testdevice_current_mode", + helper.pairing, + helper.accessory, + helper.config_entry, + ) + + service = energy_helper.accessory.services.first( + service_type=ServicesTypes.THERMOSTAT + ) + mode = service[CharacteristicsTypes.Vendor.ECOBEE_SET_HOLD_SCHEDULE] + + await hass.services.async_call( + "select", + "select_option", + {"entity_id": "select.testdevice_current_mode", "option": "home"}, + blocking=True, + ) + assert mode.value == 0 + + await hass.services.async_call( + "select", + "select_option", + {"entity_id": "select.testdevice_current_mode", "option": "sleep"}, + blocking=True, + ) + assert mode.value == 1 + + await hass.services.async_call( + "select", + "select_option", + {"entity_id": "select.testdevice_current_mode", "option": "away"}, + blocking=True, + ) + assert mode.value == 2 From d8f167bbacd36d09dcf0116f63d78c9561448319 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 27 Jan 2022 18:59:27 +0100 Subject: [PATCH 0024/1098] Remove `backports.zoneinfo` dependency (#65069) --- homeassistant/package_constraints.txt | 1 - homeassistant/util/dt.py | 12 +++--------- requirements.txt | 1 - setup.py | 1 - 4 files changed, 3 insertions(+), 12 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 7759c2fe3601d6..ff317e213967a2 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -9,7 +9,6 @@ async_timeout==4.0.2 atomicwrites==1.4.0 attrs==21.2.0 awesomeversion==22.1.0 -backports.zoneinfo;python_version<"3.9" bcrypt==3.1.7 certifi>=2021.5.30 ciso8601==2.2.0 diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index 0c8a1cd9aadced..4b4b798a2d8113 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -5,16 +5,11 @@ from contextlib import suppress import datetime as dt import re -import sys -from typing import Any, cast +from typing import Any +import zoneinfo import ciso8601 -if sys.version_info[:2] >= (3, 9): - import zoneinfo -else: - from backports import zoneinfo - DATE_STR_FORMAT = "%Y-%m-%d" UTC = dt.timezone.utc DEFAULT_TIME_ZONE: dt.tzinfo = dt.timezone.utc @@ -48,8 +43,7 @@ def get_time_zone(time_zone_str: str) -> dt.tzinfo | None: Async friendly. """ try: - # Cast can be removed when mypy is switched to Python 3.9. - return cast(dt.tzinfo, zoneinfo.ZoneInfo(time_zone_str)) + return zoneinfo.ZoneInfo(time_zone_str) except zoneinfo.ZoneInfoNotFoundError: return None diff --git a/requirements.txt b/requirements.txt index 54d4d1b1d19248..c8ee1d91368ac7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,6 @@ async_timeout==4.0.2 attrs==21.2.0 atomicwrites==1.4.0 awesomeversion==22.1.0 -backports.zoneinfo;python_version<"3.9" bcrypt==3.1.7 certifi>=2021.5.30 ciso8601==2.2.0 diff --git a/setup.py b/setup.py index 26ad28428fae0b..efcf61b85fce79 100755 --- a/setup.py +++ b/setup.py @@ -38,7 +38,6 @@ "attrs==21.2.0", "atomicwrites==1.4.0", "awesomeversion==22.1.0", - 'backports.zoneinfo;python_version<"3.9"', "bcrypt==3.1.7", "certifi>=2021.5.30", "ciso8601==2.2.0", From 176eae701a9a6f534bed87be4341b03ecd9a4cf9 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 27 Jan 2022 18:59:58 +0100 Subject: [PATCH 0025/1098] Unset Alexa authorized flag in additional case (#65044) --- homeassistant/components/cloud/alexa_config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/cloud/alexa_config.py b/homeassistant/components/cloud/alexa_config.py index 8a845077dc9d29..56f49307662248 100644 --- a/homeassistant/components/cloud/alexa_config.py +++ b/homeassistant/components/cloud/alexa_config.py @@ -192,10 +192,10 @@ async def _async_prefs_updated(self, prefs): if self.should_report_state != self.is_reporting_states: if self.should_report_state: - with suppress( - alexa_errors.NoTokenAvailable, alexa_errors.RequireRelink - ): + try: await self.async_enable_proactive_mode() + except (alexa_errors.NoTokenAvailable, alexa_errors.RequireRelink): + await self.set_authorized(False) else: await self.async_disable_proactive_mode() From 30fd9027642bc541c004e921a5a2858b80141982 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 27 Jan 2022 19:01:09 +0100 Subject: [PATCH 0026/1098] Correct zone state (#65040) Co-authored-by: Franck Nijhof --- .../components/device_tracker/config_entry.py | 1 + homeassistant/components/zone/__init__.py | 35 ++++-- tests/components/zone/test_init.py | 103 ++++++++---------- 3 files changed, 72 insertions(+), 67 deletions(-) diff --git a/homeassistant/components/device_tracker/config_entry.py b/homeassistant/components/device_tracker/config_entry.py index 096268c8fed156..18d769df07f09e 100644 --- a/homeassistant/components/device_tracker/config_entry.py +++ b/homeassistant/components/device_tracker/config_entry.py @@ -224,6 +224,7 @@ def state_attributes(self) -> dict[str, StateType]: """Return the device state attributes.""" attr: dict[str, StateType] = {} attr.update(super().state_attributes) + if self.latitude is not None and self.longitude is not None: attr[ATTR_LATITUDE] = self.latitude attr[ATTR_LONGITUDE] = self.longitude diff --git a/homeassistant/components/zone/__init__.py b/homeassistant/components/zone/__init__.py index 21f7363695e74d..41fdd8c32d39bc 100644 --- a/homeassistant/components/zone/__init__.py +++ b/homeassistant/components/zone/__init__.py @@ -10,6 +10,7 @@ from homeassistant import config_entries from homeassistant.const import ( ATTR_EDITABLE, + ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_ICON, @@ -22,14 +23,7 @@ SERVICE_RELOAD, STATE_UNAVAILABLE, ) -from homeassistant.core import ( - Event, - HomeAssistant, - ServiceCall, - State, - callback, - split_entity_id, -) +from homeassistant.core import Event, HomeAssistant, ServiceCall, State, callback from homeassistant.helpers import ( collection, config_validation as cv, @@ -346,10 +340,20 @@ async def async_update_config(self, config: dict) -> None: @callback def _person_state_change_listener(self, evt: Event) -> None: - object_id = split_entity_id(self.entity_id)[1] person_entity_id = evt.data["entity_id"] cur_count = len(self._persons_in_zone) - if evt.data["new_state"] and evt.data["new_state"].state == object_id: + if ( + (state := evt.data["new_state"]) + and (latitude := state.attributes.get(ATTR_LATITUDE)) is not None + and (longitude := state.attributes.get(ATTR_LONGITUDE)) is not None + and (accuracy := state.attributes.get(ATTR_GPS_ACCURACY)) is not None + and ( + zone_state := async_active_zone( + self.hass, latitude, longitude, accuracy + ) + ) + and zone_state.entity_id == self.entity_id + ): self._persons_in_zone.add(person_entity_id) elif person_entity_id in self._persons_in_zone: self._persons_in_zone.remove(person_entity_id) @@ -362,10 +366,17 @@ async def async_added_to_hass(self) -> None: await super().async_added_to_hass() person_domain = "person" # avoid circular import persons = self.hass.states.async_entity_ids(person_domain) - object_id = split_entity_id(self.entity_id)[1] for person in persons: state = self.hass.states.get(person) - if state and state.state == object_id: + if ( + state is None + or (latitude := state.attributes.get(ATTR_LATITUDE)) is None + or (longitude := state.attributes.get(ATTR_LONGITUDE)) is None + or (accuracy := state.attributes.get(ATTR_GPS_ACCURACY)) is None + ): + continue + zone_state = async_active_zone(self.hass, latitude, longitude, accuracy) + if zone_state is not None and zone_state.entity_id == self.entity_id: self._persons_in_zone.add(person) self.async_on_remove( diff --git a/tests/components/zone/test_init.py b/tests/components/zone/test_init.py index 399afd480c7410..54cb87aa772cbc 100644 --- a/tests/components/zone/test_init.py +++ b/tests/components/zone/test_init.py @@ -512,7 +512,7 @@ async def test_state(hass): "latitude": 32.880837, "longitude": -117.237561, "radius": 250, - "passive": True, + "passive": False, } assert await setup.async_setup_component(hass, zone.DOMAIN, {"zone": info}) @@ -521,28 +521,40 @@ async def test_state(hass): assert state.state == "0" # Person entity enters zone - hass.states.async_set("person.person1", "test_zone") + hass.states.async_set( + "person.person1", + "Test Zone", + {"latitude": 32.880837, "longitude": -117.237561, "gps_accuracy": 0}, + ) await hass.async_block_till_done() - state = hass.states.get("zone.test_zone") - assert state.state == "1" + assert hass.states.get("zone.test_zone").state == "1" + assert hass.states.get("zone.home").state == "0" # Person entity enters zone - hass.states.async_set("person.person2", "test_zone") + hass.states.async_set( + "person.person2", + "Test Zone", + {"latitude": 32.880837, "longitude": -117.237561, "gps_accuracy": 0}, + ) await hass.async_block_till_done() - state = hass.states.get("zone.test_zone") - assert state.state == "2" + assert hass.states.get("zone.test_zone").state == "2" + assert hass.states.get("zone.home").state == "0" # Person entity enters another zone - hass.states.async_set("person.person1", "home") + hass.states.async_set( + "person.person1", + "home", + {"latitude": 32.87336, "longitude": -117.22743, "gps_accuracy": 0}, + ) await hass.async_block_till_done() - state = hass.states.get("zone.test_zone") - assert state.state == "1" + assert hass.states.get("zone.test_zone").state == "1" + assert hass.states.get("zone.home").state == "1" # Person entity removed hass.states.async_remove("person.person2") await hass.async_block_till_done() - state = hass.states.get("zone.test_zone") - assert state.state == "0" + assert hass.states.get("zone.test_zone").state == "0" + assert hass.states.get("zone.home").state == "1" async def test_state_2(hass): @@ -555,7 +567,7 @@ async def test_state_2(hass): "latitude": 32.880837, "longitude": -117.237561, "radius": 250, - "passive": True, + "passive": False, } assert await setup.async_setup_component(hass, zone.DOMAIN, {"zone": info}) @@ -564,56 +576,37 @@ async def test_state_2(hass): assert state.state == "0" # Person entity enters zone - hass.states.async_set("person.person1", "test_zone") + hass.states.async_set( + "person.person1", + "Test Zone", + {"latitude": 32.880837, "longitude": -117.237561, "gps_accuracy": 0}, + ) await hass.async_block_till_done() - state = hass.states.get("zone.test_zone") - assert state.state == "1" + assert hass.states.get("zone.test_zone").state == "1" + assert hass.states.get("zone.home").state == "0" # Person entity enters zone - hass.states.async_set("person.person2", "test_zone") + hass.states.async_set( + "person.person2", + "Test Zone", + {"latitude": 32.880837, "longitude": -117.237561, "gps_accuracy": 0}, + ) await hass.async_block_till_done() - state = hass.states.get("zone.test_zone") - assert state.state == "2" + assert hass.states.get("zone.test_zone").state == "2" + assert hass.states.get("zone.home").state == "0" # Person entity enters another zone - hass.states.async_set("person.person1", "home") - await hass.async_block_till_done() - state = hass.states.get("zone.test_zone") - assert state.state == "1" - - # Person entity removed - hass.states.async_remove("person.person2") - await hass.async_block_till_done() - state = hass.states.get("zone.test_zone") - assert state.state == "0" - - -async def test_state_3(hass): - """Test the state of a zone.""" - hass.states.async_set("person.person1", "test_zone") - hass.states.async_set("person.person2", "test_zone") - - info = { - "name": "Test Zone", - "latitude": 32.880837, - "longitude": -117.237561, - "radius": 250, - "passive": True, - } - assert await setup.async_setup_component(hass, zone.DOMAIN, {"zone": info}) - - assert len(hass.states.async_entity_ids("zone")) == 2 - state = hass.states.get("zone.test_zone") - assert state.state == "2" - - # Person entity enters another zone - hass.states.async_set("person.person1", "home") + hass.states.async_set( + "person.person1", + "home", + {"latitude": 32.87336, "longitude": -117.22743, "gps_accuracy": 0}, + ) await hass.async_block_till_done() - state = hass.states.get("zone.test_zone") - assert state.state == "1" + assert hass.states.get("zone.test_zone").state == "1" + assert hass.states.get("zone.home").state == "1" # Person entity removed hass.states.async_remove("person.person2") await hass.async_block_till_done() - state = hass.states.get("zone.test_zone") - assert state.state == "0" + assert hass.states.get("zone.test_zone").state == "0" + assert hass.states.get("zone.home").state == "1" From c5787a5422834d2f46166544e34b60724149d500 Mon Sep 17 00:00:00 2001 From: Simon Hansen <67142049+DurgNomis-drol@users.noreply.github.com> Date: Thu, 27 Jan 2022 19:02:10 +0100 Subject: [PATCH 0027/1098] Fix typo in entity name for launchlibrary (#65048) --- homeassistant/components/launch_library/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/launch_library/sensor.py b/homeassistant/components/launch_library/sensor.py index b656f92fec775c..d468c3a653f06e 100644 --- a/homeassistant/components/launch_library/sensor.py +++ b/homeassistant/components/launch_library/sensor.py @@ -85,7 +85,7 @@ class LaunchLibrarySensorEntityDescription( LaunchLibrarySensorEntityDescription( key="launch_probability", icon="mdi:dice-multiple", - name="Launch Probability", + name="Launch probability", native_unit_of_measurement=PERCENTAGE, value_fn=lambda nl: None if nl.probability == -1 else nl.probability, attributes_fn=lambda nl: None, From f8f82629635eda6b920fdfad2337d9d2c42af4e8 Mon Sep 17 00:00:00 2001 From: Hans Oischinger Date: Thu, 27 Jan 2022 19:41:50 +0100 Subject: [PATCH 0028/1098] Update PyVicare to 2.16.1 (#65073) --- homeassistant/components/vicare/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/vicare/manifest.json b/homeassistant/components/vicare/manifest.json index 98c9786f131d2b..98a7eb4c07c1d2 100644 --- a/homeassistant/components/vicare/manifest.json +++ b/homeassistant/components/vicare/manifest.json @@ -3,7 +3,7 @@ "name": "Viessmann ViCare", "documentation": "https://www.home-assistant.io/integrations/vicare", "codeowners": ["@oischinger"], - "requirements": ["PyViCare==2.15.0"], + "requirements": ["PyViCare==2.16.1"], "iot_class": "cloud_polling", "config_flow": true, "dhcp": [ diff --git a/requirements_all.txt b/requirements_all.txt index 58aea66c491a38..295d659f8169b9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -56,7 +56,7 @@ PyTransportNSW==0.1.1 PyTurboJPEG==1.6.5 # homeassistant.components.vicare -PyViCare==2.15.0 +PyViCare==2.16.1 # homeassistant.components.xiaomi_aqara PyXiaomiGateway==0.13.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b2785535014f21..3aa2af3c05aa2c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -37,7 +37,7 @@ PyTransportNSW==0.1.1 PyTurboJPEG==1.6.5 # homeassistant.components.vicare -PyViCare==2.15.0 +PyViCare==2.16.1 # homeassistant.components.xiaomi_aqara PyXiaomiGateway==0.13.4 From 5a4eeaed56572216e8081714a5ff3bd9dfd4b834 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 27 Jan 2022 13:10:19 -0600 Subject: [PATCH 0029/1098] Guard browsing Spotify if setup failed (#65074) Co-authored-by: Franck Nijhof --- homeassistant/components/spotify/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/spotify/__init__.py b/homeassistant/components/spotify/__init__.py index 37f2d7f005770b..5c36a0c71c3b37 100644 --- a/homeassistant/components/spotify/__init__.py +++ b/homeassistant/components/spotify/__init__.py @@ -4,6 +4,7 @@ from spotipy import Spotify, SpotifyException import voluptuous as vol +from homeassistant.components.media_player import BrowseError from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_CREDENTIALS, @@ -60,7 +61,8 @@ async def async_browse_media( hass, media_content_type, media_content_id, *, can_play_artist=True ): """Browse Spotify media.""" - info = list(hass.data[DOMAIN].values())[0] + if not (info := next(iter(hass.data[DOMAIN].values()), None)): + raise BrowseError("No Spotify accounts available") return await async_browse_media_internal( hass, info[DATA_SPOTIFY_CLIENT], From d706a7bbde16184136be39161e012b179f484846 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 27 Jan 2022 20:19:28 +0100 Subject: [PATCH 0030/1098] Update Renault to 0.1.7 (#65076) * Update Renault to 0.1.7 * Adjust tests accordingly Co-authored-by: epenet --- homeassistant/components/renault/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/renault/const.py | 11 ++++++++++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/renault/manifest.json b/homeassistant/components/renault/manifest.json index 118848ad6dd798..9442ea8160b296 100644 --- a/homeassistant/components/renault/manifest.json +++ b/homeassistant/components/renault/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/renault", "requirements": [ - "renault-api==0.1.4" + "renault-api==0.1.7" ], "codeowners": [ "@epenet" diff --git a/requirements_all.txt b/requirements_all.txt index 295d659f8169b9..dbfd56379f2980 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2087,7 +2087,7 @@ raspyrfm-client==1.2.8 regenmaschine==2022.01.0 # homeassistant.components.renault -renault-api==0.1.4 +renault-api==0.1.7 # homeassistant.components.python_script restrictedpython==5.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3aa2af3c05aa2c..08085b62fe993e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1282,7 +1282,7 @@ rachiopy==1.0.3 regenmaschine==2022.01.0 # homeassistant.components.renault -renault-api==0.1.4 +renault-api==0.1.7 # homeassistant.components.python_script restrictedpython==5.2 diff --git a/tests/components/renault/const.py b/tests/components/renault/const.py index e1d7a3fc28c474..91704a59b5178f 100644 --- a/tests/components/renault/const.py +++ b/tests/components/renault/const.py @@ -228,7 +228,7 @@ }, "endpoints_available": [ True, # cockpit - False, # hvac-status + True, # hvac-status True, # location True, # battery-status True, # charge-mode @@ -237,6 +237,7 @@ "battery_status": "battery_status_not_charging.json", "charge_mode": "charge_mode_schedule.json", "cockpit": "cockpit_ev.json", + "hvac_status": "hvac_status.json", "location": "location.json", }, Platform.BINARY_SENSOR: [ @@ -356,6 +357,14 @@ ATTR_UNIQUE_ID: "vf1aaaaa555777999_mileage", ATTR_UNIT_OF_MEASUREMENT: LENGTH_KILOMETERS, }, + { + ATTR_DEVICE_CLASS: SensorDeviceClass.TEMPERATURE, + ATTR_ENTITY_ID: "sensor.reg_number_outside_temperature", + ATTR_STATE: "8.0", + ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT, + ATTR_UNIQUE_ID: "vf1aaaaa555777999_outside_temperature", + ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, + }, { ATTR_DEVICE_CLASS: DEVICE_CLASS_PLUG_STATE, ATTR_ENTITY_ID: "sensor.reg_number_plug_state", From 8cf1109775433b1e704a875107a1c7a5d18902f8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 27 Jan 2022 11:22:53 -0800 Subject: [PATCH 0031/1098] Bump frontend to 20220127.0 (#65075) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index a29752188a2aa8..1eef7aff083b56 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20220126.0" + "home-assistant-frontend==20220127.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index ff317e213967a2..44486677384ca8 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ ciso8601==2.2.0 cryptography==35.0.0 emoji==1.6.3 hass-nabucasa==0.52.0 -home-assistant-frontend==20220126.0 +home-assistant-frontend==20220127.0 httpx==0.21.3 ifaddr==0.1.7 jinja2==3.0.3 diff --git a/requirements_all.txt b/requirements_all.txt index dbfd56379f2980..69f4a2de845636 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -842,7 +842,7 @@ hole==0.7.0 holidays==0.12 # homeassistant.components.frontend -home-assistant-frontend==20220126.0 +home-assistant-frontend==20220127.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 08085b62fe993e..0100ee79044b90 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -543,7 +543,7 @@ hole==0.7.0 holidays==0.12 # homeassistant.components.frontend -home-assistant-frontend==20220126.0 +home-assistant-frontend==20220127.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 696b930b1c11d934af047bde3351f6ac17bf1208 Mon Sep 17 00:00:00 2001 From: dougiteixeira <31328123+dougiteixeira@users.noreply.github.com> Date: Thu, 27 Jan 2022 16:41:13 -0300 Subject: [PATCH 0032/1098] Complementing the Tuya Curtain (cl) category (#65023) Co-authored-by: Franck Nijhof --- homeassistant/components/tuya/const.py | 7 +++++++ homeassistant/components/tuya/select.py | 17 +++++++++++++++++ homeassistant/components/tuya/sensor.py | 10 ++++++++++ .../components/tuya/strings.select.json | 8 ++++++++ homeassistant/components/tuya/switch.py | 16 ++++++++++++++++ 5 files changed, 58 insertions(+) diff --git a/homeassistant/components/tuya/const.py b/homeassistant/components/tuya/const.py index 5093745a0bfd23..79b01140875877 100644 --- a/homeassistant/components/tuya/const.py +++ b/homeassistant/components/tuya/const.py @@ -87,6 +87,8 @@ class TuyaDeviceClass(StrEnum): """Tuya specific device classes, used for translations.""" + CURTAIN_MODE = "tuya__curtain_mode" + CURTAIN_MOTOR_MODE = "tuya__curtain_motor_mode" BASIC_ANTI_FLICKR = "tuya__basic_anti_flickr" BASIC_NIGHTVISION = "tuya__basic_nightvision" DECIBEL_SENSITIVITY = "tuya__decibel_sensitivity" @@ -187,7 +189,10 @@ class DPCode(StrEnum): CONTROL = "control" CONTROL_2 = "control_2" CONTROL_3 = "control_3" + CONTROL_BACK = "control_back" + CONTROL_BACK_MODE = "control_back_mode" COUNTDOWN = "countdown" # Countdown + COUNTDOWN_LEFT = "countdown_left" COUNTDOWN_SET = "countdown_set" # Countdown setting CRY_DETECTION_SWITCH = "cry_detection_switch" CUP_NUMBER = "cup_number" # NUmber of cups @@ -249,6 +254,7 @@ class DPCode(StrEnum): MOVEMENT_DETECT_PIC = "movement_detect_pic" MUFFLING = "muffling" # Muffling NEAR_DETECTION = "near_detection" + OPPOSITE = "opposite" PAUSE = "pause" PERCENT_CONTROL = "percent_control" PERCENT_CONTROL_2 = "percent_control_2" @@ -341,6 +347,7 @@ class DPCode(StrEnum): TEMP_VALUE = "temp_value" # Color temperature TEMP_VALUE_V2 = "temp_value_v2" TEMPER_ALARM = "temper_alarm" # Tamper alarm + TIME_TOTAL = "time_total" TOTAL_CLEAN_AREA = "total_clean_area" TOTAL_CLEAN_COUNT = "total_clean_count" TOTAL_CLEAN_TIME = "total_clean_time" diff --git a/homeassistant/components/tuya/select.py b/homeassistant/components/tuya/select.py index ce89000acd2e6c..5154afb9ab5b38 100644 --- a/homeassistant/components/tuya/select.py +++ b/homeassistant/components/tuya/select.py @@ -257,6 +257,23 @@ icon="mdi:timer-cog-outline", ), ), + # Curtain + # https://developer.tuya.com/en/docs/iot/f?id=K9gf46o5mtfyc + "cl": ( + SelectEntityDescription( + key=DPCode.CONTROL_BACK_MODE, + name="Motor Mode", + device_class=TuyaDeviceClass.CURTAIN_MOTOR_MODE, + entity_category=EntityCategory.CONFIG, + icon="mdi:swap-horizontal", + ), + SelectEntityDescription( + key=DPCode.MODE, + name="Mode", + device_class=TuyaDeviceClass.CURTAIN_MODE, + entity_category=EntityCategory.CONFIG, + ), + ), } # Socket (duplicate of `kg`) diff --git a/homeassistant/components/tuya/sensor.py b/homeassistant/components/tuya/sensor.py index 2799d2b82fcf63..95ed463ee813cb 100644 --- a/homeassistant/components/tuya/sensor.py +++ b/homeassistant/components/tuya/sensor.py @@ -729,6 +729,16 @@ class TuyaSensorEntityDescription(SensorEntityDescription): state_class=SensorStateClass.MEASUREMENT, ), ), + # Curtain + # https://developer.tuya.com/en/docs/iot/s?id=K9gf48qy7wkre + "cl": ( + TuyaSensorEntityDescription( + key=DPCode.TIME_TOTAL, + name="Last Operation Duration", + entity_category=EntityCategory.DIAGNOSTIC, + icon="mdi:progress-clock", + ), + ), } # Socket (duplicate of `kg`) diff --git a/homeassistant/components/tuya/strings.select.json b/homeassistant/components/tuya/strings.select.json index 48bf653979edab..c1171f81fcbe7f 100644 --- a/homeassistant/components/tuya/strings.select.json +++ b/homeassistant/components/tuya/strings.select.json @@ -85,6 +85,14 @@ "30": "30°", "60": "60°", "90": "90°" + }, + "tuya__curtain_mode": { + "morning": "Morning", + "night": "Night" + }, + "tuya__curtain_motor_mode": { + "forward": "Forward", + "back": "Back" } } } diff --git a/homeassistant/components/tuya/switch.py b/homeassistant/components/tuya/switch.py index 4e36aec5ee5192..7041b22ebe6dc7 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -514,6 +514,22 @@ entity_category=EntityCategory.CONFIG, ), ), + # Curtain + # https://developer.tuya.com/en/docs/iot/f?id=K9gf46o5mtfyc + "cl": ( + SwitchEntityDescription( + key=DPCode.CONTROL_BACK, + name="Reverse", + icon="mdi:swap-horizontal", + entity_category=EntityCategory.CONFIG, + ), + SwitchEntityDescription( + key=DPCode.OPPOSITE, + name="Reverse", + icon="mdi:swap-horizontal", + entity_category=EntityCategory.CONFIG, + ), + ), } # Socket (duplicate of `pc`) From e92078cf507089908d103b358acf12230ec4312d Mon Sep 17 00:00:00 2001 From: G Johansson Date: Thu, 27 Jan 2022 21:01:30 +0100 Subject: [PATCH 0033/1098] Fix Yale optionsflow (#65072) --- .../components/yale_smart_alarm/config_flow.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/yale_smart_alarm/config_flow.py b/homeassistant/components/yale_smart_alarm/config_flow.py index 8994d0b2fbd27a..1567f22be4471c 100644 --- a/homeassistant/components/yale_smart_alarm/config_flow.py +++ b/homeassistant/components/yale_smart_alarm/config_flow.py @@ -161,7 +161,10 @@ async def async_step_init( errors = {} if user_input: - if len(user_input[CONF_CODE]) not in [0, user_input[CONF_LOCK_CODE_DIGITS]]: + if len(user_input.get(CONF_CODE, "")) not in [ + 0, + user_input[CONF_LOCK_CODE_DIGITS], + ]: errors["base"] = "code_format_mismatch" else: return self.async_create_entry(title="", data=user_input) @@ -171,7 +174,10 @@ async def async_step_init( data_schema=vol.Schema( { vol.Optional( - CONF_CODE, default=self.entry.options.get(CONF_CODE) + CONF_CODE, + description={ + "suggested_value": self.entry.options.get(CONF_CODE) + }, ): str, vol.Optional( CONF_LOCK_CODE_DIGITS, From f49cfe866a6e0b41dc7cdb183c6dece55fc99129 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Thu, 27 Jan 2022 22:02:30 +0000 Subject: [PATCH 0034/1098] Support unpairing homekit accessories from homekit_controller (#65065) --- .../components/homekit_controller/__init__.py | 20 +++++++++++++++ .../homekit_controller/test_init.py | 24 ++++++++++++++++++ .../homekit_controller/test_storage.py | 25 ------------------- 3 files changed, 44 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index a26978f537ae31..0f231fa93038ca 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +import logging from typing import Any import aiohomekit @@ -26,6 +27,8 @@ from .const import CONTROLLER, ENTITY_MAP, KNOWN_DEVICES, TRIGGERS from .storage import EntityMapStorage +_LOGGER = logging.getLogger(__name__) + def escape_characteristic_name(char_name): """Escape any dash or dots in a characteristics name.""" @@ -248,4 +251,21 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: """Cleanup caches before removing config entry.""" hkid = entry.data["AccessoryPairingID"] + + # Remove cached type data from .storage/homekit_controller-entity-map hass.data[ENTITY_MAP].async_delete_map(hkid) + + # Remove the pairing on the device, making the device discoverable again. + # Don't reuse any objects in hass.data as they are already unloaded + async_zeroconf_instance = await zeroconf.async_get_async_instance(hass) + controller = aiohomekit.Controller(async_zeroconf_instance=async_zeroconf_instance) + controller.load_pairing(hkid, dict(entry.data)) + try: + await controller.remove_pairing(hkid) + except aiohomekit.AccessoryDisconnectedError: + _LOGGER.warning( + "Accessory %s was removed from HomeAssistant but was not reachable " + "to properly unpair. It may need resetting before you can use it with " + "HomeKit again", + entry.title, + ) diff --git a/tests/components/homekit_controller/test_init.py b/tests/components/homekit_controller/test_init.py index cd5662d73c92d8..d1b133468d5a95 100644 --- a/tests/components/homekit_controller/test_init.py +++ b/tests/components/homekit_controller/test_init.py @@ -4,8 +4,11 @@ from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes +from aiohomekit.testing import FakeController +from homeassistant.components.homekit_controller.const import ENTITY_MAP from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.core import HomeAssistant from tests.components.homekit_controller.common import setup_test_component @@ -27,3 +30,24 @@ async def test_unload_on_stop(hass, utcnow): await hass.async_block_till_done() assert async_unlock_mock.called + + +async def test_async_remove_entry(hass: HomeAssistant): + """Test unpairing a component.""" + helper = await setup_test_component(hass, create_motion_sensor_service) + + hkid = "00:00:00:00:00:00" + + with patch("aiohomekit.Controller") as controller_cls: + # Setup a fake controller with 1 pairing + controller = controller_cls.return_value = FakeController() + await controller.add_paired_device([helper.accessory], hkid) + assert len(controller.pairings) == 1 + + assert hkid in hass.data[ENTITY_MAP].storage_data + + # Remove it via config entry and number of pairings should go down + await helper.config_entry.async_remove(hass) + assert len(controller.pairings) == 0 + + assert hkid not in hass.data[ENTITY_MAP].storage_data diff --git a/tests/components/homekit_controller/test_storage.py b/tests/components/homekit_controller/test_storage.py index aa0a5e55057655..b4ed617f9015e5 100644 --- a/tests/components/homekit_controller/test_storage.py +++ b/tests/components/homekit_controller/test_storage.py @@ -2,8 +2,6 @@ from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes -from homeassistant import config_entries -from homeassistant.components.homekit_controller import async_remove_entry from homeassistant.components.homekit_controller.const import ENTITY_MAP from tests.common import flush_store @@ -79,26 +77,3 @@ async def test_storage_is_updated_on_add(hass, hass_storage, utcnow): # Is saved out to store? await flush_store(entity_map.store) assert hkid in hass_storage[ENTITY_MAP]["data"]["pairings"] - - -async def test_storage_is_removed_on_config_entry_removal(hass, utcnow): - """Test entity map storage is cleaned up on config entry removal.""" - await setup_test_component(hass, create_lightbulb_service) - - hkid = "00:00:00:00:00:00" - - pairing_data = {"AccessoryPairingID": hkid} - - entry = config_entries.ConfigEntry( - 1, - "homekit_controller", - "TestData", - pairing_data, - "test", - ) - - assert hkid in hass.data[ENTITY_MAP].storage_data - - await async_remove_entry(hass, entry) - - assert hkid not in hass.data[ENTITY_MAP].storage_data From 62584b481331f4007a0e3b8f0e59ad04e4ab3c77 Mon Sep 17 00:00:00 2001 From: Marvin Wichmann Date: Thu, 27 Jan 2022 23:03:20 +0100 Subject: [PATCH 0035/1098] Add tests for KNX diagnostic and expose (#64938) * Add test for KNX diagnostic * Add test for KNX expose * Apply review suggestions --- .coveragerc | 5 -- homeassistant/components/knx/expose.py | 2 - tests/components/knx/conftest.py | 7 ++- tests/components/knx/test_diagnostic.py | 67 +++++++++++++++++++++++++ tests/components/knx/test_expose.py | 30 ++++++++++- 5 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 tests/components/knx/test_diagnostic.py diff --git a/.coveragerc b/.coveragerc index 78ef0996c53daf..e7f99d9982ee2e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -560,12 +560,7 @@ omit = homeassistant/components/knx/__init__.py homeassistant/components/knx/climate.py homeassistant/components/knx/cover.py - homeassistant/components/knx/diagnostics.py - homeassistant/components/knx/expose.py - homeassistant/components/knx/knx_entity.py - homeassistant/components/knx/light.py homeassistant/components/knx/notify.py - homeassistant/components/knx/schema.py homeassistant/components/kodi/__init__.py homeassistant/components/kodi/browse_media.py homeassistant/components/kodi/const.py diff --git a/homeassistant/components/knx/expose.py b/homeassistant/components/knx/expose.py index 6fa5a3ba728c16..34949b8c0b89f5 100644 --- a/homeassistant/components/knx/expose.py +++ b/homeassistant/components/knx/expose.py @@ -148,8 +148,6 @@ async def _async_entity_changed(self, event: Event) -> None: async def _async_set_knx_value(self, value: StateType) -> None: """Set new value on xknx ExposeSensor.""" - if value is None: - return await self.device.set(value) diff --git a/tests/components/knx/conftest.py b/tests/components/knx/conftest.py index 71a86f1e397fb0..cd15d77629cdf1 100644 --- a/tests/components/knx/conftest.py +++ b/tests/components/knx/conftest.py @@ -13,7 +13,7 @@ from xknx.telegram.address import GroupAddress, IndividualAddress from xknx.telegram.apci import APCI, GroupValueRead, GroupValueResponse, GroupValueWrite -from homeassistant.components.knx import ConnectionSchema +from homeassistant.components.knx import ConnectionSchema, KNXModule from homeassistant.components.knx.const import ( CONF_KNX_AUTOMATIC, CONF_KNX_CONNECTION_TYPE, @@ -40,6 +40,11 @@ def __init__(self, hass: HomeAssistant, mock_config_entry: MockConfigEntry): # telegrams to an InternalGroupAddress won't be queued here self._outgoing_telegrams: asyncio.Queue = asyncio.Queue() + @property + def knx_module(self) -> KNXModule: + """Get the KNX module.""" + return self.hass.data[KNX_DOMAIN] + def assert_state(self, entity_id: str, state: str, **attributes) -> None: """Assert the state of an entity.""" test_state = self.hass.states.get(entity_id) diff --git a/tests/components/knx/test_diagnostic.py b/tests/components/knx/test_diagnostic.py new file mode 100644 index 00000000000000..697ee45ac0777f --- /dev/null +++ b/tests/components/knx/test_diagnostic.py @@ -0,0 +1,67 @@ +"""Tests for the diagnostics data provided by the KNX integration.""" +from unittest.mock import patch + +from aiohttp import ClientSession + +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry +from tests.components.diagnostics import get_diagnostics_for_config_entry +from tests.components.knx.conftest import KNXTestKit + + +async def test_diagnostics( + hass: HomeAssistant, + hass_client: ClientSession, + mock_config_entry: MockConfigEntry, + knx: KNXTestKit, +): + """Test diagnostics.""" + await knx.setup_integration({}) + + with patch("homeassistant.config.async_hass_config_yaml", return_value={}): + # Overwrite the version for this test since we don't want to change this with every library bump + knx.xknx.version = "1.0.0" + assert await get_diagnostics_for_config_entry( + hass, hass_client, mock_config_entry + ) == { + "config_entry_data": { + "connection_type": "automatic", + "individual_address": "15.15.250", + "multicast_group": "224.0.23.12", + "multicast_port": 3671, + }, + "configuration_error": None, + "configuration_yaml": None, + "xknx": {"current_address": "0.0.0", "version": "1.0.0"}, + } + + +async def test_diagnostic_config_error( + hass: HomeAssistant, + hass_client: ClientSession, + mock_config_entry: MockConfigEntry, + knx: KNXTestKit, +): + """Test diagnostics.""" + await knx.setup_integration({}) + + with patch( + "homeassistant.config.async_hass_config_yaml", + return_value={"knx": {"wrong_key": {}}}, + ): + # Overwrite the version for this test since we don't want to change this with every library bump + knx.xknx.version = "1.0.0" + assert await get_diagnostics_for_config_entry( + hass, hass_client, mock_config_entry + ) == { + "config_entry_data": { + "connection_type": "automatic", + "individual_address": "15.15.250", + "multicast_group": "224.0.23.12", + "multicast_port": 3671, + }, + "configuration_error": "extra keys not allowed @ data['knx']['wrong_key']", + "configuration_yaml": {"wrong_key": {}}, + "xknx": {"current_address": "0.0.0", "version": "1.0.0"}, + } diff --git a/tests/components/knx/test_expose.py b/tests/components/knx/test_expose.py index 25ec0f926049c7..cbe174127c412a 100644 --- a/tests/components/knx/test_expose.py +++ b/tests/components/knx/test_expose.py @@ -1,5 +1,8 @@ """Test KNX expose.""" -from homeassistant.components.knx import CONF_KNX_EXPOSE, KNX_ADDRESS +import time +from unittest.mock import patch + +from homeassistant.components.knx import CONF_KNX_EXPOSE, DOMAIN, KNX_ADDRESS from homeassistant.components.knx.schema import ExposeSchema from homeassistant.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_TYPE from homeassistant.core import HomeAssistant @@ -123,3 +126,28 @@ async def test_expose_attribute_with_default(hass: HomeAssistant, knx: KNXTestKi # Change state to "off"; no attribute hass.states.async_set(entity_id, "off", {}) await knx.assert_write("1/1/8", (0,)) + + +@patch("time.localtime") +async def test_expose_with_date(localtime, hass: HomeAssistant, knx: KNXTestKit): + """Test an expose with a date.""" + localtime.return_value = time.struct_time([2022, 1, 7, 9, 13, 14, 6, 0, 0]) + await knx.setup_integration( + { + CONF_KNX_EXPOSE: { + CONF_TYPE: "datetime", + KNX_ADDRESS: "1/1/8", + } + }, + ) + assert not hass.states.async_all() + + await knx.assert_write("1/1/8", (0x7A, 0x1, 0x7, 0xE9, 0xD, 0xE, 0x20, 0x80)) + + await knx.receive_read("1/1/8") + await knx.assert_response("1/1/8", (0x7A, 0x1, 0x7, 0xE9, 0xD, 0xE, 0x20, 0x80)) + + entries = hass.config_entries.async_entries(DOMAIN) + assert len(entries) == 1 + + assert await hass.config_entries.async_unload(entries[0].entry_id) From e591393f0110364d7c25f00f07d46f8db12be089 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 28 Jan 2022 00:14:53 +0000 Subject: [PATCH 0036/1098] [ci skip] Translation update --- .../components/arcam_fmj/translations/el.json | 13 +++++++ .../components/axis/translations/el.json | 3 ++ .../binary_sensor/translations/el.json | 2 ++ .../components/blink/translations/el.json | 11 ++++++ .../components/bsblan/translations/no.json | 5 +-- .../cert_expiry/translations/el.json | 15 ++++++-- .../components/coinbase/translations/no.json | 2 ++ .../dialogflow/translations/bg.json | 1 + .../dialogflow/translations/de.json | 1 + .../dialogflow/translations/et.json | 1 + .../dialogflow/translations/no.json | 1 + .../dialogflow/translations/ru.json | 1 + .../dialogflow/translations/zh-Hant.json | 1 + .../components/ecobee/translations/el.json | 4 ++- .../components/geofency/translations/bg.json | 1 + .../components/geofency/translations/de.json | 1 + .../components/geofency/translations/et.json | 1 + .../components/geofency/translations/no.json | 1 + .../components/geofency/translations/ru.json | 1 + .../geofency/translations/zh-Hant.json | 1 + .../geonetnz_quakes/translations/el.json | 1 + .../components/gpslogger/translations/bg.json | 1 + .../components/gpslogger/translations/de.json | 1 + .../components/gpslogger/translations/et.json | 1 + .../components/gpslogger/translations/no.json | 1 + .../components/gpslogger/translations/ru.json | 1 + .../gpslogger/translations/zh-Hant.json | 1 + .../components/hassio/translations/bg.json | 6 +++- .../translations/select.en.json | 9 +++++ .../homewizard/translations/ca.json | 2 +- .../homewizard/translations/de.json | 2 +- .../homewizard/translations/en.json | 2 +- .../homewizard/translations/et.json | 2 +- .../homewizard/translations/zh-Hant.json | 2 +- .../hvv_departures/translations/el.json | 35 +++++++++++++++++++ .../components/iaqualink/translations/el.json | 10 ++++++ .../components/ifttt/translations/bg.json | 1 + .../components/ifttt/translations/de.json | 1 + .../components/ifttt/translations/et.json | 1 + .../components/ifttt/translations/no.json | 1 + .../components/ifttt/translations/ru.json | 1 + .../ifttt/translations/zh-Hant.json | 1 + .../components/knx/translations/no.json | 6 ++-- .../components/locative/translations/bg.json | 1 + .../components/locative/translations/de.json | 1 + .../components/locative/translations/et.json | 1 + .../components/locative/translations/no.json | 1 + .../components/locative/translations/ru.json | 1 + .../locative/translations/zh-Hant.json | 1 + .../components/lovelace/translations/bg.json | 1 + .../components/mailgun/translations/bg.json | 1 + .../components/mailgun/translations/de.json | 1 + .../components/mailgun/translations/et.json | 1 + .../components/mailgun/translations/no.json | 1 + .../components/mailgun/translations/ru.json | 1 + .../mailgun/translations/zh-Hant.json | 1 + .../components/nest/translations/el.json | 3 ++ .../nightscout/translations/el.json | 1 + .../components/overkiz/translations/no.json | 4 ++- .../ovo_energy/translations/el.json | 1 + .../components/owntracks/translations/bg.json | 1 + .../components/owntracks/translations/de.json | 1 + .../components/owntracks/translations/et.json | 1 + .../components/owntracks/translations/no.json | 1 + .../components/owntracks/translations/ru.json | 1 + .../owntracks/translations/zh-Hant.json | 1 + .../components/plaato/translations/bg.json | 1 + .../components/plaato/translations/de.json | 1 + .../components/plaato/translations/el.json | 3 ++ .../components/plaato/translations/et.json | 1 + .../components/plaato/translations/no.json | 1 + .../components/plaato/translations/ru.json | 1 + .../plaato/translations/zh-Hant.json | 1 + .../components/plex/translations/el.json | 7 ++-- .../components/rdw/translations/el.json | 14 ++++++++ .../components/senseme/translations/no.json | 3 +- .../components/smappee/translations/el.json | 24 +++++++++++++ .../components/solaredge/translations/el.json | 4 +++ .../components/solax/translations/no.json | 17 +++++++++ .../speedtestdotnet/translations/el.json | 7 ++-- .../components/spider/translations/el.json | 9 +++++ .../srp_energy/translations/el.json | 11 +++++- .../synology_dsm/translations/no.json | 1 + .../components/tile/translations/el.json | 12 +++++++ .../components/traccar/translations/bg.json | 1 + .../components/traccar/translations/de.json | 1 + .../components/traccar/translations/et.json | 1 + .../components/traccar/translations/no.json | 1 + .../components/traccar/translations/ru.json | 1 + .../traccar/translations/zh-Hant.json | 1 + .../tuya/translations/select.bg.json | 5 +++ .../tuya/translations/select.de.json | 5 +++ .../tuya/translations/select.el.json | 5 +++ .../tuya/translations/select.en.json | 8 +++++ .../tuya/translations/select.et.json | 5 +++ .../tuya/translations/select.no.json | 5 +++ .../tuya/translations/select.ru.json | 5 +++ .../tuya/translations/select.zh-Hant.json | 5 +++ .../twentemilieu/translations/el.json | 17 +++++++++ .../components/twilio/translations/de.json | 1 + .../components/twilio/translations/et.json | 1 + .../components/twilio/translations/no.json | 1 + .../components/twilio/translations/ru.json | 1 + .../twilio/translations/zh-Hant.json | 1 + .../components/unifi/translations/el.json | 6 +++- .../unifiprotect/translations/no.json | 4 ++- .../uptimerobot/translations/sensor.de.json | 11 ++++++ .../uptimerobot/translations/sensor.et.json | 11 ++++++ .../uptimerobot/translations/sensor.no.json | 11 ++++++ .../uptimerobot/translations/sensor.ru.json | 11 ++++++ .../translations/sensor.zh-Hant.json | 11 ++++++ .../components/velbus/translations/el.json | 9 +++++ .../components/wemo/translations/el.json | 9 +++++ .../xiaomi_miio/translations/el.json | 1 + .../components/zha/translations/el.json | 1 + 115 files changed, 436 insertions(+), 22 deletions(-) create mode 100644 homeassistant/components/arcam_fmj/translations/el.json create mode 100644 homeassistant/components/homekit_controller/translations/select.en.json create mode 100644 homeassistant/components/hvv_departures/translations/el.json create mode 100644 homeassistant/components/iaqualink/translations/el.json create mode 100644 homeassistant/components/rdw/translations/el.json create mode 100644 homeassistant/components/smappee/translations/el.json create mode 100644 homeassistant/components/solax/translations/no.json create mode 100644 homeassistant/components/spider/translations/el.json create mode 100644 homeassistant/components/tile/translations/el.json create mode 100644 homeassistant/components/twentemilieu/translations/el.json create mode 100644 homeassistant/components/uptimerobot/translations/sensor.de.json create mode 100644 homeassistant/components/uptimerobot/translations/sensor.et.json create mode 100644 homeassistant/components/uptimerobot/translations/sensor.no.json create mode 100644 homeassistant/components/uptimerobot/translations/sensor.ru.json create mode 100644 homeassistant/components/uptimerobot/translations/sensor.zh-Hant.json create mode 100644 homeassistant/components/wemo/translations/el.json diff --git a/homeassistant/components/arcam_fmj/translations/el.json b/homeassistant/components/arcam_fmj/translations/el.json new file mode 100644 index 00000000000000..350f7f9865cbbb --- /dev/null +++ b/homeassistant/components/arcam_fmj/translations/el.json @@ -0,0 +1,13 @@ +{ + "config": { + "flow_title": "{host}", + "step": { + "confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Arcam FMJ \u03c3\u03c4\u03bf `{host}` \u03c3\u03c4\u03bf Home Assistant;" + }, + "user": { + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/translations/el.json b/homeassistant/components/axis/translations/el.json index 9bdaac69386df0..4ad186fd8b46a9 100644 --- a/homeassistant/components/axis/translations/el.json +++ b/homeassistant/components/axis/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "not_axis_device": "\u0397 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03c5\u03c6\u03b8\u03b5\u03af\u03c3\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Axis" + }, "flow_title": "{name} ({host})", "step": { "user": { diff --git a/homeassistant/components/binary_sensor/translations/el.json b/homeassistant/components/binary_sensor/translations/el.json index 63469b85c0cb16..294fca09be5913 100644 --- a/homeassistant/components/binary_sensor/translations/el.json +++ b/homeassistant/components/binary_sensor/translations/el.json @@ -51,6 +51,8 @@ "moist": "{entity_name} \u03ad\u03b3\u03b9\u03bd\u03b5 \u03c5\u03b3\u03c1\u03cc", "no_update": "{entity_name} \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5", "not_opened": "{entity_name} \u03ad\u03ba\u03bb\u03b5\u03b9\u03c3\u03b5", + "turned_off": "{entity_name} \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", + "turned_on": "{entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", "update": "{entity_name} \u03ad\u03bb\u03b1\u03b2\u03b5 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7" } }, diff --git a/homeassistant/components/blink/translations/el.json b/homeassistant/components/blink/translations/el.json index b6981eeb66adfb..b439c443e124c1 100644 --- a/homeassistant/components/blink/translations/el.json +++ b/homeassistant/components/blink/translations/el.json @@ -12,5 +12,16 @@ "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc Blink" } } + }, + "options": { + "step": { + "simple_options": { + "data": { + "scan_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7\u03c2 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)" + }, + "description": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 Blink", + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 Blink" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/bsblan/translations/no.json b/homeassistant/components/bsblan/translations/no.json index 043477ed3f92ed..64012b146377dd 100644 --- a/homeassistant/components/bsblan/translations/no.json +++ b/homeassistant/components/bsblan/translations/no.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Enheten er allerede konfigurert" + "already_configured": "Enheten er allerede konfigurert", + "cannot_connect": "Tilkobling mislyktes" }, "error": { "cannot_connect": "Tilkobling mislyktes" @@ -16,7 +17,7 @@ "port": "Port", "username": "Brukernavn" }, - "description": "Konfigurer din BSB-Lan-enhet for \u00e5 integrere med Home Assistant.", + "description": "Konfigurer BSB-Lan-enheten din for \u00e5 integreres med Home Assistant.", "title": "Koble til BSB-Lan-enheten" } } diff --git a/homeassistant/components/cert_expiry/translations/el.json b/homeassistant/components/cert_expiry/translations/el.json index 12b612a8c10381..728fd2c3db746b 100644 --- a/homeassistant/components/cert_expiry/translations/el.json +++ b/homeassistant/components/cert_expiry/translations/el.json @@ -4,7 +4,18 @@ "import_failed": "\u0397 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03b1\u03c0\u03cc \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5" }, "error": { - "connection_refused": "\u0397 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03b1\u03c0\u03bf\u03c1\u03c1\u03af\u03c6\u03b8\u03b7\u03ba\u03b5 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae" + "connection_refused": "\u0397 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03b1\u03c0\u03bf\u03c1\u03c1\u03af\u03c6\u03b8\u03b7\u03ba\u03b5 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae", + "connection_timeout": "\u03a7\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03cc\u03c1\u03b9\u03bf \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae", + "resolve_failed": "\u0391\u03c5\u03c4\u03cc\u03c2 \u03bf \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2 \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b5\u03c0\u03b9\u03bb\u03c5\u03b8\u03b5\u03af" + }, + "step": { + "user": { + "data": { + "name": "\u03a4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c4\u03bf\u03c5 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03bf\u03cd" + }, + "title": "\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03bf\u03c2 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ae" + } } - } + }, + "title": "\u039b\u03ae\u03be\u03b7 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03bf\u03cd" } \ No newline at end of file diff --git a/homeassistant/components/coinbase/translations/no.json b/homeassistant/components/coinbase/translations/no.json index ad1cb5087f1498..9a24d984c32e3a 100644 --- a/homeassistant/components/coinbase/translations/no.json +++ b/homeassistant/components/coinbase/translations/no.json @@ -25,7 +25,9 @@ }, "options": { "error": { + "currency_unavailable": "En eller flere av de forespurte valutasaldoene leveres ikke av Coinbase API.", "currency_unavaliable": "En eller flere av de forespurte valutasaldoene leveres ikke av Coinbase API.", + "exchange_rate_unavailable": "En eller flere av de forespurte valutakursene er ikke levert av Coinbase.", "exchange_rate_unavaliable": "En eller flere av de forespurte valutakursene leveres ikke av Coinbase.", "unknown": "Uventet feil" }, diff --git a/homeassistant/components/dialogflow/translations/bg.json b/homeassistant/components/dialogflow/translations/bg.json index d27bddfcd05dbf..2de3c1a479b640 100644 --- a/homeassistant/components/dialogflow/translations/bg.json +++ b/homeassistant/components/dialogflow/translations/bg.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u041d\u0435 \u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d \u0441 Home Assistant Cloud.", "single_instance_allowed": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." }, "create_entry": { diff --git a/homeassistant/components/dialogflow/translations/de.json b/homeassistant/components/dialogflow/translations/de.json index 2035b818b44d10..bf6a4aca128991 100644 --- a/homeassistant/components/dialogflow/translations/de.json +++ b/homeassistant/components/dialogflow/translations/de.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Nicht mit der Home Assistant Cloud verbunden.", "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", "webhook_not_internet_accessible": "Deine Home Assistant-Instanz muss \u00fcber das Internet erreichbar sein, um Webhook-Nachrichten empfangen zu k\u00f6nnen." }, diff --git a/homeassistant/components/dialogflow/translations/et.json b/homeassistant/components/dialogflow/translations/et.json index 8ffe23497efe89..09cee8a724a34e 100644 --- a/homeassistant/components/dialogflow/translations/et.json +++ b/homeassistant/components/dialogflow/translations/et.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Pilve\u00fchendus puudub", "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine.", "webhook_not_internet_accessible": "Veebikonksu s\u00f5numite vastuv\u00f5tmiseks peab Home Assistant olema Interneti kaudu juurdep\u00e4\u00e4setav." }, diff --git a/homeassistant/components/dialogflow/translations/no.json b/homeassistant/components/dialogflow/translations/no.json index af11f5c1c63712..cceb7dd033d93e 100644 --- a/homeassistant/components/dialogflow/translations/no.json +++ b/homeassistant/components/dialogflow/translations/no.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Ikke koblet til Home Assistant Cloud.", "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig.", "webhook_not_internet_accessible": "Home Assistant forekomsten din m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta webhook meldinger" }, diff --git a/homeassistant/components/dialogflow/translations/ru.json b/homeassistant/components/dialogflow/translations/ru.json index 55768c4f567198..ee8bfebadf97d3 100644 --- a/homeassistant/components/dialogflow/translations/ru.json +++ b/homeassistant/components/dialogflow/translations/ru.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u041d\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a Home Assistant Cloud.", "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e.", "webhook_not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f Webhook-\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439." }, diff --git a/homeassistant/components/dialogflow/translations/zh-Hant.json b/homeassistant/components/dialogflow/translations/zh-Hant.json index 4584a3831363cd..6cbd589e17a4ba 100644 --- a/homeassistant/components/dialogflow/translations/zh-Hant.json +++ b/homeassistant/components/dialogflow/translations/zh-Hant.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u672a\u9023\u7dda\u81f3 Home Assistant \u96f2\u670d\u52d9\u3002", "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "webhook_not_internet_accessible": "Home Assistant \u5be6\u9ad4\u5fc5\u9808\u8981\u80fd\u5f9e\u7db2\u969b\u7db2\u8def\u5b58\u53d6\u65b9\u80fd\u63a5\u6536 Webhook \u8a0a\u606f\u3002" }, diff --git a/homeassistant/components/ecobee/translations/el.json b/homeassistant/components/ecobee/translations/el.json index 341317af7fde27..2596a9c81a16ff 100644 --- a/homeassistant/components/ecobee/translations/el.json +++ b/homeassistant/components/ecobee/translations/el.json @@ -6,9 +6,11 @@ }, "step": { "authorize": { - "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03ae\u03c3\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u03c3\u03c4\u03bf https://www.ecobee.com/consumerportal/index.html \u03bc\u03b5 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc PIN:\n\n{pin}\n\n\u03a3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 \u03a5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae." + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03ae\u03c3\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u03c3\u03c4\u03bf https://www.ecobee.com/consumerportal/index.html \u03bc\u03b5 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc PIN:\n\n{pin}\n\n\u03a3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 \u03a5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae.", + "title": "\u0395\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u03c3\u03c4\u03bf ecobee.com" }, "user": { + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03c0\u03bf\u03c5 \u03ad\u03c7\u03b5\u03c4\u03b5 \u03bb\u03ac\u03b2\u03b5\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd ecobee.com.", "title": "\u03ba\u03bb\u03b5\u03b9\u03b4\u03af API ecobee" } } diff --git a/homeassistant/components/geofency/translations/bg.json b/homeassistant/components/geofency/translations/bg.json index 916336f37a40b8..eaecf88293ddf7 100644 --- a/homeassistant/components/geofency/translations/bg.json +++ b/homeassistant/components/geofency/translations/bg.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u041d\u0435 \u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d \u0441 Home Assistant Cloud.", "single_instance_allowed": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." }, "create_entry": { diff --git a/homeassistant/components/geofency/translations/de.json b/homeassistant/components/geofency/translations/de.json index 9c3fd3ea1b0de7..b9b06cfd020161 100644 --- a/homeassistant/components/geofency/translations/de.json +++ b/homeassistant/components/geofency/translations/de.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Nicht mit der Home Assistant Cloud verbunden.", "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", "webhook_not_internet_accessible": "Deine Home Assistant-Instanz muss \u00fcber das Internet erreichbar sein, um Webhook-Nachrichten empfangen zu k\u00f6nnen." }, diff --git a/homeassistant/components/geofency/translations/et.json b/homeassistant/components/geofency/translations/et.json index fa87d80871fdf2..4283bcb83b1b04 100644 --- a/homeassistant/components/geofency/translations/et.json +++ b/homeassistant/components/geofency/translations/et.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Pilve\u00fchendus puudub", "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine.", "webhook_not_internet_accessible": "Veebikonksu s\u00f5numite vastuv\u00f5tmiseks peab Home Assistant olema Interneti kaudu juurdep\u00e4\u00e4setav." }, diff --git a/homeassistant/components/geofency/translations/no.json b/homeassistant/components/geofency/translations/no.json index 51fa75f66b4424..1ccd77230b60e1 100644 --- a/homeassistant/components/geofency/translations/no.json +++ b/homeassistant/components/geofency/translations/no.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Ikke koblet til Home Assistant Cloud.", "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig.", "webhook_not_internet_accessible": "Home Assistant forekomsten din m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta webhook meldinger" }, diff --git a/homeassistant/components/geofency/translations/ru.json b/homeassistant/components/geofency/translations/ru.json index c4ac5e7928275c..a0a79f1ae3a2a8 100644 --- a/homeassistant/components/geofency/translations/ru.json +++ b/homeassistant/components/geofency/translations/ru.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u041d\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a Home Assistant Cloud.", "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e.", "webhook_not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f Webhook-\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439." }, diff --git a/homeassistant/components/geofency/translations/zh-Hant.json b/homeassistant/components/geofency/translations/zh-Hant.json index 4ffe673045388c..accbab5f1d39ad 100644 --- a/homeassistant/components/geofency/translations/zh-Hant.json +++ b/homeassistant/components/geofency/translations/zh-Hant.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u672a\u9023\u7dda\u81f3 Home Assistant \u96f2\u670d\u52d9\u3002", "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "webhook_not_internet_accessible": "Home Assistant \u5be6\u9ad4\u5fc5\u9808\u8981\u80fd\u5f9e\u7db2\u969b\u7db2\u8def\u5b58\u53d6\u65b9\u80fd\u63a5\u6536 Webhook \u8a0a\u606f\u3002" }, diff --git a/homeassistant/components/geonetnz_quakes/translations/el.json b/homeassistant/components/geonetnz_quakes/translations/el.json index 215801cc7c5887..a859f6fd3df199 100644 --- a/homeassistant/components/geonetnz_quakes/translations/el.json +++ b/homeassistant/components/geonetnz_quakes/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "mmi": "MMI", "radius": "\u0391\u03ba\u03c4\u03af\u03bd\u03b1" }, "title": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c4\u03bf\u03c5 \u03c6\u03af\u03bb\u03c4\u03c1\u03bf\u03c5 \u03c3\u03b1\u03c2." diff --git a/homeassistant/components/gpslogger/translations/bg.json b/homeassistant/components/gpslogger/translations/bg.json index 8e1049d859e122..e396e08cfafad8 100644 --- a/homeassistant/components/gpslogger/translations/bg.json +++ b/homeassistant/components/gpslogger/translations/bg.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u041d\u0435 \u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d \u0441 Home Assistant Cloud.", "single_instance_allowed": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." }, "create_entry": { diff --git a/homeassistant/components/gpslogger/translations/de.json b/homeassistant/components/gpslogger/translations/de.json index 7215f0c458f623..a6ac2e228e2318 100644 --- a/homeassistant/components/gpslogger/translations/de.json +++ b/homeassistant/components/gpslogger/translations/de.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Nicht mit der Home Assistant Cloud verbunden.", "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", "webhook_not_internet_accessible": "Deine Home Assistant-Instanz muss \u00fcber das Internet erreichbar sein, um Webhook-Nachrichten empfangen zu k\u00f6nnen." }, diff --git a/homeassistant/components/gpslogger/translations/et.json b/homeassistant/components/gpslogger/translations/et.json index a84a69a3d0ded4..641b4ba9f4aed0 100644 --- a/homeassistant/components/gpslogger/translations/et.json +++ b/homeassistant/components/gpslogger/translations/et.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Pilve\u00fchendus puudub", "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine.", "webhook_not_internet_accessible": "Veebikonksu s\u00f5numite vastuv\u00f5tmiseks peab Home Assistant olema Interneti kaudu juurdep\u00e4\u00e4setav." }, diff --git a/homeassistant/components/gpslogger/translations/no.json b/homeassistant/components/gpslogger/translations/no.json index 655cfa38418625..a46042abe87dc1 100644 --- a/homeassistant/components/gpslogger/translations/no.json +++ b/homeassistant/components/gpslogger/translations/no.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Ikke koblet til Home Assistant Cloud.", "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig.", "webhook_not_internet_accessible": "Home Assistant forekomsten din m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta webhook meldinger" }, diff --git a/homeassistant/components/gpslogger/translations/ru.json b/homeassistant/components/gpslogger/translations/ru.json index 999949d5dd0775..a7c9fc032f1b9d 100644 --- a/homeassistant/components/gpslogger/translations/ru.json +++ b/homeassistant/components/gpslogger/translations/ru.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u041d\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a Home Assistant Cloud.", "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e.", "webhook_not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f Webhook-\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439." }, diff --git a/homeassistant/components/gpslogger/translations/zh-Hant.json b/homeassistant/components/gpslogger/translations/zh-Hant.json index 9c5448266e66a2..d1e2c7433010ed 100644 --- a/homeassistant/components/gpslogger/translations/zh-Hant.json +++ b/homeassistant/components/gpslogger/translations/zh-Hant.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u672a\u9023\u7dda\u81f3 Home Assistant \u96f2\u670d\u52d9\u3002", "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "webhook_not_internet_accessible": "Home Assistant \u5be6\u9ad4\u5fc5\u9808\u8981\u80fd\u5f9e\u7db2\u969b\u7db2\u8def\u5b58\u53d6\u65b9\u80fd\u63a5\u6536 Webhook \u8a0a\u606f\u3002" }, diff --git a/homeassistant/components/hassio/translations/bg.json b/homeassistant/components/hassio/translations/bg.json index 960dc53b5eaa20..941c3601bea4b6 100644 --- a/homeassistant/components/hassio/translations/bg.json +++ b/homeassistant/components/hassio/translations/bg.json @@ -2,7 +2,11 @@ "system_health": { "info": { "disk_total": "\u0414\u0438\u0441\u043a \u043e\u0431\u0449\u043e", - "disk_used": "\u0418\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u043d \u0434\u0438\u0441\u043a" + "disk_used": "\u0418\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u043d \u0434\u0438\u0441\u043a", + "docker_version": "\u0412\u0435\u0440\u0441\u0438\u044f \u043d\u0430 Docker", + "installed_addons": "\u0418\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u0438 \u0434\u043e\u0431\u0430\u0432\u043a\u0438", + "supervisor_version": "\u0412\u0435\u0440\u0441\u0438\u044f \u043d\u0430 Supervisor", + "update_channel": "\u041a\u0430\u043d\u0430\u043b \u0437\u0430 \u0430\u043a\u0442\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u0430\u043d\u0435" } } } \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/select.en.json b/homeassistant/components/homekit_controller/translations/select.en.json new file mode 100644 index 00000000000000..1468d652d21e6f --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/select.en.json @@ -0,0 +1,9 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "away": "Away", + "home": "Home", + "sleep": "Sleep" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homewizard/translations/ca.json b/homeassistant/components/homewizard/translations/ca.json index c32926cf57b3a0..4e855e3bc071ca 100644 --- a/homeassistant/components/homewizard/translations/ca.json +++ b/homeassistant/components/homewizard/translations/ca.json @@ -4,7 +4,7 @@ "already_configured": "El dispositiu ja est\u00e0 configurat", "api_not_enabled": "L'API no est\u00e0 activada. Activa-la a la configuraci\u00f3 de l'aplicaci\u00f3 HomeWizard Energy", "device_not_supported": "Aquest dispositiu no \u00e9s compatible", - "invalid_discovery_parameters": "unsupported_api_version", + "invalid_discovery_parameters": "Versi\u00f3 d'API no compatible detectada", "unknown_error": "Error inesperat" }, "step": { diff --git a/homeassistant/components/homewizard/translations/de.json b/homeassistant/components/homewizard/translations/de.json index 5a08a12d970462..782ac2bf6feaf9 100644 --- a/homeassistant/components/homewizard/translations/de.json +++ b/homeassistant/components/homewizard/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert", "api_not_enabled": "Die API ist nicht aktiviert. Aktiviere die API in der HomeWizard Energy App unter Einstellungen", "device_not_supported": "Dieses Ger\u00e4t wird nicht unterst\u00fctzt", - "invalid_discovery_parameters": "unsupported_api_version", + "invalid_discovery_parameters": "Nicht unterst\u00fctzte API-Version erkannt", "unknown_error": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/homewizard/translations/en.json b/homeassistant/components/homewizard/translations/en.json index 1aa1ff0a611418..b2ef3c16b1e119 100644 --- a/homeassistant/components/homewizard/translations/en.json +++ b/homeassistant/components/homewizard/translations/en.json @@ -4,7 +4,7 @@ "already_configured": "Device is already configured", "api_not_enabled": "The API is not enabled. Enable API in the HomeWizard Energy App under settings", "device_not_supported": "This device is not supported", - "invalid_discovery_parameters": "unsupported_api_version", + "invalid_discovery_parameters": "Detected unsupported API version", "unknown_error": "Unexpected error" }, "step": { diff --git a/homeassistant/components/homewizard/translations/et.json b/homeassistant/components/homewizard/translations/et.json index 8d8fefd6e75b28..360010b4d573b0 100644 --- a/homeassistant/components/homewizard/translations/et.json +++ b/homeassistant/components/homewizard/translations/et.json @@ -4,7 +4,7 @@ "already_configured": "Seade on juba h\u00e4\u00e4lestatud", "api_not_enabled": "API pole lubatud. Luba API HomeWizard Energy rakenduse seadete all", "device_not_supported": "Seda seadet ei toetata", - "invalid_discovery_parameters": "Toetuseta API versioon", + "invalid_discovery_parameters": "Leiti toetuseta API versioon", "unknown_error": "Ootamatu t\u00f5rge" }, "step": { diff --git a/homeassistant/components/homewizard/translations/zh-Hant.json b/homeassistant/components/homewizard/translations/zh-Hant.json index 3b16abc78f92d6..d68bd74668cc8f 100644 --- a/homeassistant/components/homewizard/translations/zh-Hant.json +++ b/homeassistant/components/homewizard/translations/zh-Hant.json @@ -4,7 +4,7 @@ "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "api_not_enabled": "API \u672a\u958b\u555f\u3002\u8acb\u65bc HomeWizard Energy App \u8a2d\u5b9a\u5167\u555f\u7528 API", "device_not_supported": "\u4e0d\u652f\u63f4\u6b64\u88dd\u7f6e", - "invalid_discovery_parameters": "unsupported_api_version", + "invalid_discovery_parameters": "\u5075\u6e2c\u5230\u4e0d\u652f\u63f4 API \u7248\u672c", "unknown_error": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { diff --git a/homeassistant/components/hvv_departures/translations/el.json b/homeassistant/components/hvv_departures/translations/el.json new file mode 100644 index 00000000000000..916b438c046ddb --- /dev/null +++ b/homeassistant/components/hvv_departures/translations/el.json @@ -0,0 +1,35 @@ +{ + "config": { + "error": { + "no_results": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03bd \u03b1\u03c0\u03bf\u03c4\u03b5\u03bb\u03ad\u03c3\u03bc\u03b1\u03c4\u03b1. \u0394\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03bc\u03b5 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03c3\u03c4\u03b1\u03b8\u03bc\u03cc/\u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7" + }, + "step": { + "station": { + "data": { + "station": "\u03a3\u03c4\u03b1\u03b8\u03bc\u03cc\u03c2/\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7" + }, + "title": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c3\u03c4\u03b1\u03b8\u03bc\u03cc/\u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7" + }, + "station_select": { + "data": { + "station": "\u03a3\u03c4\u03b1\u03b8\u03bc\u03cc\u03c2/\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b1\u03b8\u03bc\u03cc/\u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7" + }, + "user": { + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf HVV API" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "filter": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03b3\u03c1\u03b1\u03bc\u03bc\u03ad\u03c2", + "offset": "\u039c\u03b5\u03c4\u03b1\u03c4\u03cc\u03c0\u03b9\u03c3\u03b7 (\u03bb\u03b5\u03c0\u03c4\u03ac)", + "real_time": "\u03a7\u03c1\u03ae\u03c3\u03b7 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd \u03c3\u03b5 \u03c0\u03c1\u03b1\u03b3\u03bc\u03b1\u03c4\u03b9\u03ba\u03cc \u03c7\u03c1\u03cc\u03bd\u03bf" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iaqualink/translations/el.json b/homeassistant/components/iaqualink/translations/el.json new file mode 100644 index 00000000000000..d6deb0e84c655d --- /dev/null +++ b/homeassistant/components/iaqualink/translations/el.json @@ -0,0 +1,10 @@ +{ + "config": { + "step": { + "user": { + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf iAqualink.", + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf iAqualink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ifttt/translations/bg.json b/homeassistant/components/ifttt/translations/bg.json index 3a23d735f760bd..54d0a83ffcfc75 100644 --- a/homeassistant/components/ifttt/translations/bg.json +++ b/homeassistant/components/ifttt/translations/bg.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u041d\u0435 \u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d \u0441 Home Assistant Cloud.", "single_instance_allowed": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." }, "create_entry": { diff --git a/homeassistant/components/ifttt/translations/de.json b/homeassistant/components/ifttt/translations/de.json index 216511c62f5348..905dbbe420dea4 100644 --- a/homeassistant/components/ifttt/translations/de.json +++ b/homeassistant/components/ifttt/translations/de.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Nicht mit der Home Assistant Cloud verbunden.", "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", "webhook_not_internet_accessible": "Deine Home Assistant-Instanz muss \u00fcber das Internet erreichbar sein, um Webhook-Nachrichten empfangen zu k\u00f6nnen." }, diff --git a/homeassistant/components/ifttt/translations/et.json b/homeassistant/components/ifttt/translations/et.json index e4d2c8f14886bc..caa82dddd41c81 100644 --- a/homeassistant/components/ifttt/translations/et.json +++ b/homeassistant/components/ifttt/translations/et.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Pilve\u00fchendus puudub", "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine.", "webhook_not_internet_accessible": "Veebikonksu s\u00f5numite vastuv\u00f5tmiseks peab Home Assistant olema Interneti kaudu juurdep\u00e4\u00e4setav." }, diff --git a/homeassistant/components/ifttt/translations/no.json b/homeassistant/components/ifttt/translations/no.json index 98fec09e773b20..dc10e256864283 100644 --- a/homeassistant/components/ifttt/translations/no.json +++ b/homeassistant/components/ifttt/translations/no.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Ikke koblet til Home Assistant Cloud.", "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig.", "webhook_not_internet_accessible": "Home Assistant forekomsten din m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta webhook meldinger" }, diff --git a/homeassistant/components/ifttt/translations/ru.json b/homeassistant/components/ifttt/translations/ru.json index 8cf34ace24efe2..8d5fc315beb38b 100644 --- a/homeassistant/components/ifttt/translations/ru.json +++ b/homeassistant/components/ifttt/translations/ru.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u041d\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a Home Assistant Cloud.", "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e.", "webhook_not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f Webhook-\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439." }, diff --git a/homeassistant/components/ifttt/translations/zh-Hant.json b/homeassistant/components/ifttt/translations/zh-Hant.json index fe5b80f72f12ba..3f149896b64196 100644 --- a/homeassistant/components/ifttt/translations/zh-Hant.json +++ b/homeassistant/components/ifttt/translations/zh-Hant.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u672a\u9023\u7dda\u81f3 Home Assistant \u96f2\u670d\u52d9\u3002", "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "webhook_not_internet_accessible": "Home Assistant \u5be6\u9ad4\u5fc5\u9808\u8981\u80fd\u5f9e\u7db2\u969b\u7db2\u8def\u5b58\u53d6\u65b9\u80fd\u63a5\u6536 Webhook \u8a0a\u606f\u3002" }, diff --git a/homeassistant/components/knx/translations/no.json b/homeassistant/components/knx/translations/no.json index 4f0d8008d254f0..231945d52336a3 100644 --- a/homeassistant/components/knx/translations/no.json +++ b/homeassistant/components/knx/translations/no.json @@ -14,7 +14,8 @@ "individual_address": "Individuell adresse for tilkoblingen", "local_ip": "Lokal IP for Home Assistant (la st\u00e5 tomt for automatisk gjenkjenning)", "port": "Port", - "route_back": "Rute tilbake / NAT-modus" + "route_back": "Rute tilbake / NAT-modus", + "tunneling_type": "KNX tunneltype" }, "description": "Vennligst skriv inn tilkoblingsinformasjonen til tunnelenheten din." }, @@ -59,7 +60,8 @@ "host": "Vert", "local_ip": "Lokal IP (la st\u00e5 tomt hvis du er usikker)", "port": "Port", - "route_back": "Rute tilbake / NAT-modus" + "route_back": "Rute tilbake / NAT-modus", + "tunneling_type": "KNX tunneltype" } } } diff --git a/homeassistant/components/locative/translations/bg.json b/homeassistant/components/locative/translations/bg.json index 9c79f86d4f76d5..2daf1d17c80016 100644 --- a/homeassistant/components/locative/translations/bg.json +++ b/homeassistant/components/locative/translations/bg.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u041d\u0435 \u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d \u0441 Home Assistant Cloud.", "single_instance_allowed": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." }, "create_entry": { diff --git a/homeassistant/components/locative/translations/de.json b/homeassistant/components/locative/translations/de.json index 5ca0036347618e..801ec910745dde 100644 --- a/homeassistant/components/locative/translations/de.json +++ b/homeassistant/components/locative/translations/de.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Nicht mit der Home Assistant Cloud verbunden.", "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", "webhook_not_internet_accessible": "Deine Home Assistant-Instanz muss \u00fcber das Internet erreichbar sein, um Webhook-Nachrichten empfangen zu k\u00f6nnen." }, diff --git a/homeassistant/components/locative/translations/et.json b/homeassistant/components/locative/translations/et.json index e73ad4da420504..fb1a65ced8e956 100644 --- a/homeassistant/components/locative/translations/et.json +++ b/homeassistant/components/locative/translations/et.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Pilve\u00fchendus puudub", "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine.", "webhook_not_internet_accessible": "Veebikonksu s\u00f5numite vastuv\u00f5tmiseks peab Home Assistant olema Interneti kaudu juurdep\u00e4\u00e4setav." }, diff --git a/homeassistant/components/locative/translations/no.json b/homeassistant/components/locative/translations/no.json index 7eca10016eaff1..0982c62752654a 100644 --- a/homeassistant/components/locative/translations/no.json +++ b/homeassistant/components/locative/translations/no.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Ikke koblet til Home Assistant Cloud.", "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig.", "webhook_not_internet_accessible": "Home Assistant forekomsten din m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta webhook meldinger" }, diff --git a/homeassistant/components/locative/translations/ru.json b/homeassistant/components/locative/translations/ru.json index dd353c25117dac..90a27fa6e51c1a 100644 --- a/homeassistant/components/locative/translations/ru.json +++ b/homeassistant/components/locative/translations/ru.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u041d\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a Home Assistant Cloud.", "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e.", "webhook_not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f Webhook-\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439." }, diff --git a/homeassistant/components/locative/translations/zh-Hant.json b/homeassistant/components/locative/translations/zh-Hant.json index 8c2dcdb53ed177..34c5fbdbaad1ea 100644 --- a/homeassistant/components/locative/translations/zh-Hant.json +++ b/homeassistant/components/locative/translations/zh-Hant.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u672a\u9023\u7dda\u81f3 Home Assistant \u96f2\u670d\u52d9\u3002", "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "webhook_not_internet_accessible": "Home Assistant \u5be6\u9ad4\u5fc5\u9808\u8981\u80fd\u5f9e\u7db2\u969b\u7db2\u8def\u5b58\u53d6\u65b9\u80fd\u63a5\u6536 Webhook \u8a0a\u606f\u3002" }, diff --git a/homeassistant/components/lovelace/translations/bg.json b/homeassistant/components/lovelace/translations/bg.json index 3a9370b5486206..4a755519aefb04 100644 --- a/homeassistant/components/lovelace/translations/bg.json +++ b/homeassistant/components/lovelace/translations/bg.json @@ -1,6 +1,7 @@ { "system_health": { "info": { + "dashboards": "\u0422\u0430\u0431\u043b\u0430", "mode": "\u0420\u0435\u0436\u0438\u043c", "resources": "\u0420\u0435\u0441\u0443\u0440\u0441\u0438", "views": "\u0418\u0437\u0433\u043b\u0435\u0434\u0438" diff --git a/homeassistant/components/mailgun/translations/bg.json b/homeassistant/components/mailgun/translations/bg.json index d9bcdd38dfd855..0a3782dc2c3da0 100644 --- a/homeassistant/components/mailgun/translations/bg.json +++ b/homeassistant/components/mailgun/translations/bg.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u041d\u0435 \u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d \u0441 Home Assistant Cloud.", "single_instance_allowed": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." }, "create_entry": { diff --git a/homeassistant/components/mailgun/translations/de.json b/homeassistant/components/mailgun/translations/de.json index 118192b65160fe..34c251770cc33f 100644 --- a/homeassistant/components/mailgun/translations/de.json +++ b/homeassistant/components/mailgun/translations/de.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Nicht mit der Home Assistant Cloud verbunden.", "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", "webhook_not_internet_accessible": "Deine Home Assistant-Instanz muss \u00fcber das Internet erreichbar sein, um Webhook-Nachrichten empfangen zu k\u00f6nnen." }, diff --git a/homeassistant/components/mailgun/translations/et.json b/homeassistant/components/mailgun/translations/et.json index 4f3469d13577ed..7e659cfe8ac723 100644 --- a/homeassistant/components/mailgun/translations/et.json +++ b/homeassistant/components/mailgun/translations/et.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Pilve\u00fchendus puudub", "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine.", "webhook_not_internet_accessible": "Veebikonksu s\u00f5numite vastuv\u00f5tmiseks peab Home Assistant olema Interneti kaudu juurdep\u00e4\u00e4setav." }, diff --git a/homeassistant/components/mailgun/translations/no.json b/homeassistant/components/mailgun/translations/no.json index 4cc080805ac576..77576133365d33 100644 --- a/homeassistant/components/mailgun/translations/no.json +++ b/homeassistant/components/mailgun/translations/no.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Ikke koblet til Home Assistant Cloud.", "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig.", "webhook_not_internet_accessible": "Home Assistant forekomsten din m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta webhook meldinger" }, diff --git a/homeassistant/components/mailgun/translations/ru.json b/homeassistant/components/mailgun/translations/ru.json index 2e79776f89a083..00aa509c958229 100644 --- a/homeassistant/components/mailgun/translations/ru.json +++ b/homeassistant/components/mailgun/translations/ru.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u041d\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a Home Assistant Cloud.", "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e.", "webhook_not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f Webhook-\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439." }, diff --git a/homeassistant/components/mailgun/translations/zh-Hant.json b/homeassistant/components/mailgun/translations/zh-Hant.json index 508a652ce9b5ad..5f65978596e5da 100644 --- a/homeassistant/components/mailgun/translations/zh-Hant.json +++ b/homeassistant/components/mailgun/translations/zh-Hant.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u672a\u9023\u7dda\u81f3 Home Assistant \u96f2\u670d\u52d9\u3002", "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "webhook_not_internet_accessible": "Home Assistant \u5be6\u9ad4\u5fc5\u9808\u8981\u80fd\u5f9e\u7db2\u969b\u7db2\u8def\u5b58\u53d6\u65b9\u80fd\u63a5\u6536 Webhook \u8a0a\u606f\u3002" }, diff --git a/homeassistant/components/nest/translations/el.json b/homeassistant/components/nest/translations/el.json index 4fc37801fa0759..236cd0072a939e 100644 --- a/homeassistant/components/nest/translations/el.json +++ b/homeassistant/components/nest/translations/el.json @@ -1,6 +1,9 @@ { "config": { "step": { + "auth": { + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd Google" + }, "init": { "title": "\u03a0\u03ac\u03c1\u03bf\u03c7\u03bf\u03c2 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, diff --git a/homeassistant/components/nightscout/translations/el.json b/homeassistant/components/nightscout/translations/el.json index 42762bfddce8c9..b5b12c25c03696 100644 --- a/homeassistant/components/nightscout/translations/el.json +++ b/homeassistant/components/nightscout/translations/el.json @@ -1,5 +1,6 @@ { "config": { + "flow_title": "Nightscout", "step": { "user": { "data": { diff --git a/homeassistant/components/overkiz/translations/no.json b/homeassistant/components/overkiz/translations/no.json index fe0b10996c93ad..201ecbf3779407 100644 --- a/homeassistant/components/overkiz/translations/no.json +++ b/homeassistant/components/overkiz/translations/no.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "already_configured": "Kontoen er allerede konfigurert" + "already_configured": "Kontoen er allerede konfigurert", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket", + "reauth_wrong_account": "Du kan bare autentisere denne oppf\u00f8ringen p\u00e5 nytt med samme Overkiz-konto og hub" }, "error": { "cannot_connect": "Tilkobling mislyktes", diff --git a/homeassistant/components/ovo_energy/translations/el.json b/homeassistant/components/ovo_energy/translations/el.json index daedfe647f902e..8e60f589fc5975 100644 --- a/homeassistant/components/ovo_energy/translations/el.json +++ b/homeassistant/components/ovo_energy/translations/el.json @@ -1,5 +1,6 @@ { "config": { + "flow_title": "{username}", "step": { "user": { "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1 OVO Energy \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03c0\u03bf\u03ba\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03b5\u03bd\u03ad\u03c1\u03b3\u03b5\u03b9\u03b1\u03c2.", diff --git a/homeassistant/components/owntracks/translations/bg.json b/homeassistant/components/owntracks/translations/bg.json index b69f7ada2a2f52..10bfdae7ae74f5 100644 --- a/homeassistant/components/owntracks/translations/bg.json +++ b/homeassistant/components/owntracks/translations/bg.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u041d\u0435 \u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d \u0441 Home Assistant Cloud.", "single_instance_allowed": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." }, "create_entry": { diff --git a/homeassistant/components/owntracks/translations/de.json b/homeassistant/components/owntracks/translations/de.json index 737b92c642a487..46ca14c81acb62 100644 --- a/homeassistant/components/owntracks/translations/de.json +++ b/homeassistant/components/owntracks/translations/de.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Nicht mit der Home Assistant Cloud verbunden.", "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "create_entry": { diff --git a/homeassistant/components/owntracks/translations/et.json b/homeassistant/components/owntracks/translations/et.json index 2ee171365a4ce2..1d8a85aba4a212 100644 --- a/homeassistant/components/owntracks/translations/et.json +++ b/homeassistant/components/owntracks/translations/et.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Pilve\u00fchendus puudub", "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." }, "create_entry": { diff --git a/homeassistant/components/owntracks/translations/no.json b/homeassistant/components/owntracks/translations/no.json index db992a56305822..7774753a16ee27 100644 --- a/homeassistant/components/owntracks/translations/no.json +++ b/homeassistant/components/owntracks/translations/no.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Ikke koblet til Home Assistant Cloud.", "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "create_entry": { diff --git a/homeassistant/components/owntracks/translations/ru.json b/homeassistant/components/owntracks/translations/ru.json index 09fdba77266834..83e374cdf6cf01 100644 --- a/homeassistant/components/owntracks/translations/ru.json +++ b/homeassistant/components/owntracks/translations/ru.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u041d\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a Home Assistant Cloud.", "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." }, "create_entry": { diff --git a/homeassistant/components/owntracks/translations/zh-Hant.json b/homeassistant/components/owntracks/translations/zh-Hant.json index 2803182629a7b4..09aae548c6209b 100644 --- a/homeassistant/components/owntracks/translations/zh-Hant.json +++ b/homeassistant/components/owntracks/translations/zh-Hant.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u672a\u9023\u7dda\u81f3 Home Assistant \u96f2\u670d\u52d9\u3002", "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "create_entry": { diff --git a/homeassistant/components/plaato/translations/bg.json b/homeassistant/components/plaato/translations/bg.json index d9dd41dbb804c1..c31f1f8a14a780 100644 --- a/homeassistant/components/plaato/translations/bg.json +++ b/homeassistant/components/plaato/translations/bg.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u041d\u0435 \u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d \u0441 Home Assistant Cloud.", "single_instance_allowed": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." }, "create_entry": { diff --git a/homeassistant/components/plaato/translations/de.json b/homeassistant/components/plaato/translations/de.json index 29e3ebe9790854..d3e39738b02289 100644 --- a/homeassistant/components/plaato/translations/de.json +++ b/homeassistant/components/plaato/translations/de.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Konto wurde bereits konfiguriert", + "cloud_not_connected": "Nicht mit der Home Assistant Cloud verbunden.", "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", "webhook_not_internet_accessible": "Deine Home Assistant-Instanz muss \u00fcber das Internet erreichbar sein, um Webhook-Nachrichten empfangen zu k\u00f6nnen." }, diff --git a/homeassistant/components/plaato/translations/el.json b/homeassistant/components/plaato/translations/el.json index 9be7164e051deb..bc23114f6720e1 100644 --- a/homeassistant/components/plaato/translations/el.json +++ b/homeassistant/components/plaato/translations/el.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, + "create_entry": { + "default": "\u03a4\u03bf Plaato {device_type} \u03bc\u03b5 \u03cc\u03bd\u03bf\u03bc\u03b1 **{device_name}** \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03bc\u03b5 \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03af\u03b1!" + }, "error": { "invalid_webhook_device": "\u0388\u03c7\u03b5\u03c4\u03b5 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c0\u03bf\u03c5 \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03b9 \u03c4\u03b7\u03bd \u03b1\u03c0\u03bf\u03c3\u03c4\u03bf\u03bb\u03ae \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd \u03c3\u03b5 \u03ad\u03bd\u03b1 webhook. \u0395\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03bc\u03cc\u03bd\u03bf \u03b3\u03b9\u03b1 \u03c4\u03bf Airlock", "no_api_method": "\u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03ae \u03bd\u03b1 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03b5\u03c4\u03b5 webhook", diff --git a/homeassistant/components/plaato/translations/et.json b/homeassistant/components/plaato/translations/et.json index fb4325581dd37e..f563646753ecff 100644 --- a/homeassistant/components/plaato/translations/et.json +++ b/homeassistant/components/plaato/translations/et.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Kasutaja on juba seadistatud", + "cloud_not_connected": "Pilve\u00fchendus puudub", "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine.", "webhook_not_internet_accessible": "Veebikonksu s\u00f5numite vastuv\u00f5tmiseks peab Home Assistant olema Interneti kaudu juurdep\u00e4\u00e4setav." }, diff --git a/homeassistant/components/plaato/translations/no.json b/homeassistant/components/plaato/translations/no.json index 7039662468ef2b..8065c4fb471afe 100644 --- a/homeassistant/components/plaato/translations/no.json +++ b/homeassistant/components/plaato/translations/no.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Kontoen er allerede konfigurert", + "cloud_not_connected": "Ikke koblet til Home Assistant Cloud.", "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig.", "webhook_not_internet_accessible": "Home Assistant forekomsten din m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta webhook meldinger" }, diff --git a/homeassistant/components/plaato/translations/ru.json b/homeassistant/components/plaato/translations/ru.json index 9ff1977dc53d35..f0963321cab1b6 100644 --- a/homeassistant/components/plaato/translations/ru.json +++ b/homeassistant/components/plaato/translations/ru.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\u042d\u0442\u0430 \u0443\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant.", + "cloud_not_connected": "\u041d\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a Home Assistant Cloud.", "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e.", "webhook_not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f Webhook-\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439." }, diff --git a/homeassistant/components/plaato/translations/zh-Hant.json b/homeassistant/components/plaato/translations/zh-Hant.json index 26d7b728771b72..bb9a58d81274d9 100644 --- a/homeassistant/components/plaato/translations/zh-Hant.json +++ b/homeassistant/components/plaato/translations/zh-Hant.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "cloud_not_connected": "\u672a\u9023\u7dda\u81f3 Home Assistant \u96f2\u670d\u52d9\u3002", "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "webhook_not_internet_accessible": "Home Assistant \u5be6\u9ad4\u5fc5\u9808\u8981\u80fd\u5f9e\u7db2\u969b\u7db2\u8def\u5b58\u53d6\u65b9\u80fd\u63a5\u6536 Webhook \u8a0a\u606f\u3002" }, diff --git a/homeassistant/components/plex/translations/el.json b/homeassistant/components/plex/translations/el.json index a4e412f7d01751..033f78316a9654 100644 --- a/homeassistant/components/plex/translations/el.json +++ b/homeassistant/components/plex/translations/el.json @@ -23,7 +23,9 @@ "select_server": { "data": { "server": "\u0394\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2" - } + }, + "description": "\u0394\u03b9\u03b1\u03c4\u03af\u03b8\u03b5\u03bd\u03c4\u03b1\u03b9 \u03c0\u03bf\u03bb\u03bb\u03bf\u03af \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ad\u03c2, \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd:", + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae Plex" }, "user": { "description": "\u03a3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03c4\u03b5 \u03c3\u03c4\u03bf [plex.tv](https://plex.tv) \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae Plex.", @@ -43,7 +45,8 @@ "data": { "ignore_new_shared_users": "\u0391\u03b3\u03bd\u03bf\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03c5\u03c2 \u03bd\u03ad\u03bf\u03c5\u03c2 \u03b4\u03b9\u03b1\u03c7\u03b5\u03b9\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03bf\u03c5\u03c2/\u03ba\u03bf\u03b9\u03bd\u03cc\u03c7\u03c1\u03b7\u03c3\u03c4\u03bf\u03c5\u03c2 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b5\u03c2", "ignore_plex_web_clients": "\u0391\u03b3\u03bd\u03bf\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03c5\u03c2 \u03c0\u03b5\u03bb\u03ac\u03c4\u03b5\u03c2 Web Plex", - "monitored_users": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03bf\u03cd\u03bc\u03b5\u03bd\u03bf\u03b9 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b5\u03c2" + "monitored_users": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03bf\u03cd\u03bc\u03b5\u03bd\u03bf\u03b9 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b5\u03c2", + "use_episode_art": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03b5\u03be\u03ce\u03c6\u03c5\u03bb\u03bb\u03bf \u03b5\u03c0\u03b5\u03b9\u03c3\u03bf\u03b4\u03af\u03c9\u03bd" }, "description": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2 \u03c0\u03bf\u03bb\u03c5\u03bc\u03ad\u03c3\u03c9\u03bd Plex" } diff --git a/homeassistant/components/rdw/translations/el.json b/homeassistant/components/rdw/translations/el.json new file mode 100644 index 00000000000000..f301eb5bb8c356 --- /dev/null +++ b/homeassistant/components/rdw/translations/el.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "unknown_license_plate": "\u0386\u03b3\u03bd\u03c9\u03c3\u03c4\u03b7 \u03c0\u03b9\u03bd\u03b1\u03ba\u03af\u03b4\u03b1 \u03ba\u03c5\u03ba\u03bb\u03bf\u03c6\u03bf\u03c1\u03af\u03b1\u03c2" + }, + "step": { + "user": { + "data": { + "license_plate": "\u03a0\u03b9\u03bd\u03b1\u03ba\u03af\u03b4\u03b1 \u03ba\u03c5\u03ba\u03bb\u03bf\u03c6\u03bf\u03c1\u03af\u03b1\u03c2" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senseme/translations/no.json b/homeassistant/components/senseme/translations/no.json index 6895aeb247a411..47e330100c91b5 100644 --- a/homeassistant/components/senseme/translations/no.json +++ b/homeassistant/components/senseme/translations/no.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Enheten er allerede konfigurert" + "already_configured": "Enheten er allerede konfigurert", + "cannot_connect": "Tilkobling mislyktes" }, "error": { "cannot_connect": "Tilkobling mislyktes", diff --git a/homeassistant/components/smappee/translations/el.json b/homeassistant/components/smappee/translations/el.json new file mode 100644 index 00000000000000..67a4de8faf859a --- /dev/null +++ b/homeassistant/components/smappee/translations/el.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured_local_device": "\u0397 \u03c4\u03bf\u03c0\u03b9\u03ba\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae(\u03b5\u03c2) \u03b5\u03af\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7(\u03b5\u03c2). \u0391\u03c6\u03b1\u03b9\u03c1\u03ad\u03c3\u03c4\u03b5 \u03c0\u03c1\u03ce\u03c4\u03b1 \u03b1\u03c5\u03c4\u03ad\u03c2 \u03c0\u03c1\u03b9\u03bd \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03b5\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae cloud.", + "invalid_mdns": "\u039c\u03b7 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 Smappee." + }, + "flow_title": "{name}", + "step": { + "environment": { + "data": { + "environment": "\u03a0\u03b5\u03c1\u03b9\u03b2\u03ac\u03bb\u03bb\u03bf\u03bd" + }, + "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf Smappee \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Home Assistant." + }, + "local": { + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03b9 \u03b7 \u03c4\u03bf\u03c0\u03b9\u03ba\u03ae \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Smappee" + }, + "zeroconf_confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Smappee \u03bc\u03b5 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c3\u03b5\u03b9\u03c1\u03ac\u03c2 `{serialnumber}` \u03c3\u03c4\u03bf Home Assistant;", + "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Smappee" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solaredge/translations/el.json b/homeassistant/components/solaredge/translations/el.json index d8fe133bff448b..de1c6594003abc 100644 --- a/homeassistant/components/solaredge/translations/el.json +++ b/homeassistant/components/solaredge/translations/el.json @@ -2,6 +2,10 @@ "config": { "step": { "user": { + "data": { + "name": "\u03a4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03b1\u03c5\u03c4\u03ae\u03c2 \u03c4\u03b7\u03c2 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2", + "site_id": "\u03a4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2 SolarEdge" + }, "title": "\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03bf\u03c5\u03c2 API \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7" } } diff --git a/homeassistant/components/solax/translations/no.json b/homeassistant/components/solax/translations/no.json new file mode 100644 index 00000000000000..8e82b60bce1ea5 --- /dev/null +++ b/homeassistant/components/solax/translations/no.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "Tilkobling mislyktes", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "ip_address": "IP adresse", + "password": "Passord", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/speedtestdotnet/translations/el.json b/homeassistant/components/speedtestdotnet/translations/el.json index a8ac5c9292451a..2ffe32a5feffe0 100644 --- a/homeassistant/components/speedtestdotnet/translations/el.json +++ b/homeassistant/components/speedtestdotnet/translations/el.json @@ -1,14 +1,17 @@ { "config": { "abort": { - "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", + "wrong_server_id": "\u03a4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf" } }, "options": { "step": { "init": { "data": { - "manual": "\u0391\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7\u03c2 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7\u03c2" + "manual": "\u0391\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7\u03c2 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7\u03c2", + "scan_interval": "\u03a3\u03c5\u03c7\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7\u03c2 (\u03bb\u03b5\u03c0\u03c4\u03ac)", + "server_name": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae \u03b4\u03bf\u03ba\u03b9\u03bc\u03ae\u03c2" } } } diff --git a/homeassistant/components/spider/translations/el.json b/homeassistant/components/spider/translations/el.json new file mode 100644 index 00000000000000..a2e96925a5bf0f --- /dev/null +++ b/homeassistant/components/spider/translations/el.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc mijn.ithodaalderop.nl" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/el.json b/homeassistant/components/srp_energy/translations/el.json index aaf69256d36225..e392cd4e4ffe7e 100644 --- a/homeassistant/components/srp_energy/translations/el.json +++ b/homeassistant/components/srp_energy/translations/el.json @@ -2,6 +2,15 @@ "config": { "error": { "invalid_account": "\u03a4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03bd\u03b1\u03c2 9\u03c8\u03ae\u03c6\u03b9\u03bf\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2" + }, + "step": { + "user": { + "data": { + "id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd", + "is_tou": "\u0395\u03af\u03bd\u03b1\u03b9 \u03bf \u03c7\u03c1\u03cc\u03bd\u03bf\u03c2 \u03c7\u03c1\u03ae\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03c3\u03c7\u03b5\u03b4\u03af\u03bf\u03c5" + } + } } - } + }, + "title": "SRP Energy" } \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/no.json b/homeassistant/components/synology_dsm/translations/no.json index 121ffb0c6bfa60..41f56c135aab61 100644 --- a/homeassistant/components/synology_dsm/translations/no.json +++ b/homeassistant/components/synology_dsm/translations/no.json @@ -64,6 +64,7 @@ "init": { "data": { "scan_interval": "Minutter mellom skanninger", + "snap_profile_type": "Kvalitetsniv\u00e5et p\u00e5 kameraets \u00f8yeblikksbilder (0:h\u00f8y 1:middels 2:lav)", "timeout": "Tidsavbrudd (sekunder)" } } diff --git a/homeassistant/components/tile/translations/el.json b/homeassistant/components/tile/translations/el.json new file mode 100644 index 00000000000000..805c15c252a6ef --- /dev/null +++ b/homeassistant/components/tile/translations/el.json @@ -0,0 +1,12 @@ +{ + "options": { + "step": { + "init": { + "data": { + "show_inactive": "\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03b1\u03bd\u03b5\u03bd\u03b5\u03c1\u03b3\u03ce\u03bd Tiles" + }, + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Tile" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/traccar/translations/bg.json b/homeassistant/components/traccar/translations/bg.json index 3859cbda430800..3444f6c0fdd14a 100644 --- a/homeassistant/components/traccar/translations/bg.json +++ b/homeassistant/components/traccar/translations/bg.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u041d\u0435 \u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d \u0441 Home Assistant Cloud.", "single_instance_allowed": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." }, "create_entry": { diff --git a/homeassistant/components/traccar/translations/de.json b/homeassistant/components/traccar/translations/de.json index 3e94aaeb4c5aee..0d372da549f6bf 100644 --- a/homeassistant/components/traccar/translations/de.json +++ b/homeassistant/components/traccar/translations/de.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Nicht mit der Home Assistant Cloud verbunden.", "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", "webhook_not_internet_accessible": "Deine Home Assistant-Instanz muss \u00fcber das Internet erreichbar sein, um Webhook-Nachrichten empfangen zu k\u00f6nnen." }, diff --git a/homeassistant/components/traccar/translations/et.json b/homeassistant/components/traccar/translations/et.json index e2c4ad68a8d85b..beace8108c73ac 100644 --- a/homeassistant/components/traccar/translations/et.json +++ b/homeassistant/components/traccar/translations/et.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Pilve\u00fchendus puudub", "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine.", "webhook_not_internet_accessible": "Veebikonksu s\u00f5numite vastuv\u00f5tmiseks peab Home Assistant olema Interneti kaudu juurdep\u00e4\u00e4setav." }, diff --git a/homeassistant/components/traccar/translations/no.json b/homeassistant/components/traccar/translations/no.json index e2051be22b6262..1e16d41880e265 100644 --- a/homeassistant/components/traccar/translations/no.json +++ b/homeassistant/components/traccar/translations/no.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Ikke koblet til Home Assistant Cloud.", "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig.", "webhook_not_internet_accessible": "Home Assistant forekomsten din m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta webhook meldinger" }, diff --git a/homeassistant/components/traccar/translations/ru.json b/homeassistant/components/traccar/translations/ru.json index 7db0d6f01cf263..5c1a058e5146e1 100644 --- a/homeassistant/components/traccar/translations/ru.json +++ b/homeassistant/components/traccar/translations/ru.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u041d\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a Home Assistant Cloud.", "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e.", "webhook_not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f Webhook-\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439." }, diff --git a/homeassistant/components/traccar/translations/zh-Hant.json b/homeassistant/components/traccar/translations/zh-Hant.json index ee7c75d84086ff..7a4e9b8a02b714 100644 --- a/homeassistant/components/traccar/translations/zh-Hant.json +++ b/homeassistant/components/traccar/translations/zh-Hant.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u672a\u9023\u7dda\u81f3 Home Assistant \u96f2\u670d\u52d9\u3002", "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "webhook_not_internet_accessible": "Home Assistant \u5be6\u9ad4\u5fc5\u9808\u8981\u80fd\u5f9e\u7db2\u969b\u7db2\u8def\u5b58\u53d6\u65b9\u80fd\u63a5\u6536 Webhook \u8a0a\u606f\u3002" }, diff --git a/homeassistant/components/tuya/translations/select.bg.json b/homeassistant/components/tuya/translations/select.bg.json index e0c55131f3129e..3105feec5f7773 100644 --- a/homeassistant/components/tuya/translations/select.bg.json +++ b/homeassistant/components/tuya/translations/select.bg.json @@ -13,6 +13,11 @@ "0": "\u041d\u0438\u0441\u043a\u0430 \u0447\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u043d\u043e\u0441\u0442", "1": "\u0412\u0438\u0441\u043e\u043a\u0430 \u0447\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u043d\u043e\u0441\u0442" }, + "tuya__fan_angle": { + "30": "30\u00b0", + "60": "60\u00b0", + "90": "90\u00b0" + }, "tuya__led_type": { "halogen": "\u0425\u0430\u043b\u043e\u0433\u0435\u043d\u043d\u0438", "incandescent": "\u0421 \u043d\u0430\u0436\u0435\u0436\u0430\u0435\u043c\u0430 \u0436\u0438\u0447\u043a\u0430", diff --git a/homeassistant/components/tuya/translations/select.de.json b/homeassistant/components/tuya/translations/select.de.json index 1577872391e586..23dbb00b4919b8 100644 --- a/homeassistant/components/tuya/translations/select.de.json +++ b/homeassistant/components/tuya/translations/select.de.json @@ -14,6 +14,11 @@ "0": "Geringe Empfindlichkeit", "1": "Hohe Empfindlichkeit" }, + "tuya__fan_angle": { + "30": "30\u00b0", + "60": "60\u00b0", + "90": "90\u00b0" + }, "tuya__fingerbot_mode": { "click": "Dr\u00fccken", "switch": "Schalter" diff --git a/homeassistant/components/tuya/translations/select.el.json b/homeassistant/components/tuya/translations/select.el.json index 90b677d331f5aa..9c773ec34a0b4a 100644 --- a/homeassistant/components/tuya/translations/select.el.json +++ b/homeassistant/components/tuya/translations/select.el.json @@ -8,6 +8,11 @@ "tuya__basic_nightvision": { "0": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03bf" }, + "tuya__fan_angle": { + "30": "30\u00b0", + "60": "60\u00b0", + "90": "90\u00b0" + }, "tuya__fingerbot_mode": { "click": "\u03a0\u03af\u03b5\u03c3\u03b5", "switch": "\u0394\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b7\u03c2" diff --git a/homeassistant/components/tuya/translations/select.en.json b/homeassistant/components/tuya/translations/select.en.json index 8e51dd6b352281..29c3400008991a 100644 --- a/homeassistant/components/tuya/translations/select.en.json +++ b/homeassistant/components/tuya/translations/select.en.json @@ -10,6 +10,14 @@ "1": "Off", "2": "On" }, + "tuya__curtain_mode": { + "morning": "Morning", + "night": "Night" + }, + "tuya__curtain_motor_mode": { + "back": "Back", + "forward": "Forward" + }, "tuya__decibel_sensitivity": { "0": "Low sensitivity", "1": "High sensitivity" diff --git a/homeassistant/components/tuya/translations/select.et.json b/homeassistant/components/tuya/translations/select.et.json index a2559065018fa2..3b97624676943d 100644 --- a/homeassistant/components/tuya/translations/select.et.json +++ b/homeassistant/components/tuya/translations/select.et.json @@ -14,6 +14,11 @@ "0": "Madal tundlikkus", "1": "K\u00f5rge tundlikkus" }, + "tuya__fan_angle": { + "30": "30\u00b0", + "60": "60\u00b0", + "90": "90\u00b0" + }, "tuya__fingerbot_mode": { "click": "Vajutus", "switch": "L\u00fcliti" diff --git a/homeassistant/components/tuya/translations/select.no.json b/homeassistant/components/tuya/translations/select.no.json index 03137f3e02804e..09b02ba8cb32a3 100644 --- a/homeassistant/components/tuya/translations/select.no.json +++ b/homeassistant/components/tuya/translations/select.no.json @@ -14,6 +14,11 @@ "0": "Lav f\u00f8lsomhet", "1": "H\u00f8y f\u00f8lsomhet" }, + "tuya__fan_angle": { + "30": "30\u00b0", + "60": "60\u00b0", + "90": "90\u00b0" + }, "tuya__fingerbot_mode": { "click": "Trykk", "switch": "Bryter" diff --git a/homeassistant/components/tuya/translations/select.ru.json b/homeassistant/components/tuya/translations/select.ru.json index b3ab03143d63bf..34ddcfcd7a6579 100644 --- a/homeassistant/components/tuya/translations/select.ru.json +++ b/homeassistant/components/tuya/translations/select.ru.json @@ -14,6 +14,11 @@ "0": "\u041d\u0438\u0437\u043a\u0430\u044f \u0447\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c", "1": "\u0412\u044b\u0441\u043e\u043a\u0430\u044f \u0447\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c" }, + "tuya__fan_angle": { + "30": "30\u00b0", + "60": "60\u00b0", + "90": "90\u00b0" + }, "tuya__fingerbot_mode": { "click": "\u041a\u043d\u043e\u043f\u043a\u0430", "switch": "\u0412\u044b\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u044c" diff --git a/homeassistant/components/tuya/translations/select.zh-Hant.json b/homeassistant/components/tuya/translations/select.zh-Hant.json index 8a39fe53b07855..a9ee45002cfeb7 100644 --- a/homeassistant/components/tuya/translations/select.zh-Hant.json +++ b/homeassistant/components/tuya/translations/select.zh-Hant.json @@ -14,6 +14,11 @@ "0": "\u4f4e\u654f\u611f\u5ea6", "1": "\u9ad8\u654f\u611f\u5ea6" }, + "tuya__fan_angle": { + "30": "30\u00b0", + "60": "60\u00b0", + "90": "90\u00b0" + }, "tuya__fingerbot_mode": { "click": "\u63a8", "switch": "\u958b\u95dc" diff --git a/homeassistant/components/twentemilieu/translations/el.json b/homeassistant/components/twentemilieu/translations/el.json new file mode 100644 index 00000000000000..27344f27bc865c --- /dev/null +++ b/homeassistant/components/twentemilieu/translations/el.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "invalid_address": "\u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03c3\u03c4\u03b7\u03bd \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03b9\u03ce\u03bd Twente Milieu." + }, + "step": { + "user": { + "data": { + "house_number": "\u0391\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03c3\u03c0\u03b9\u03c4\u03b9\u03bf\u03cd", + "post_code": "\u03a4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b9\u03ba\u03cc\u03c2 \u03ba\u03ce\u03b4\u03b9\u03ba\u03b1\u03c2" + }, + "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf Twente Milieu \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c3\u03c5\u03bb\u03bb\u03bf\u03b3\u03ae \u03b1\u03c0\u03bf\u03b2\u03bb\u03ae\u03c4\u03c9\u03bd \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03ae \u03c3\u03b1\u03c2.", + "title": "Twente Milieu" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twilio/translations/de.json b/homeassistant/components/twilio/translations/de.json index 73a3f244f96038..97c04631aa5f86 100644 --- a/homeassistant/components/twilio/translations/de.json +++ b/homeassistant/components/twilio/translations/de.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Nicht mit der Home Assistant Cloud verbunden.", "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", "webhook_not_internet_accessible": "Deine Home Assistant-Instanz muss \u00fcber das Internet erreichbar sein, um Webhook-Nachrichten empfangen zu k\u00f6nnen." }, diff --git a/homeassistant/components/twilio/translations/et.json b/homeassistant/components/twilio/translations/et.json index 3d06e7f1db03d0..a8d5ab0ea671ce 100644 --- a/homeassistant/components/twilio/translations/et.json +++ b/homeassistant/components/twilio/translations/et.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Pilve\u00fchendus puudub", "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine.", "webhook_not_internet_accessible": "Veebikonksu s\u00f5numite vastuv\u00f5tmiseks peab Home Assistant olema Interneti kaudu juurdep\u00e4\u00e4setav." }, diff --git a/homeassistant/components/twilio/translations/no.json b/homeassistant/components/twilio/translations/no.json index 81c5c35e430c81..6e2b57ae8231b0 100644 --- a/homeassistant/components/twilio/translations/no.json +++ b/homeassistant/components/twilio/translations/no.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Ikke koblet til Home Assistant Cloud.", "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig.", "webhook_not_internet_accessible": "Home Assistant forekomsten din m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta webhook meldinger" }, diff --git a/homeassistant/components/twilio/translations/ru.json b/homeassistant/components/twilio/translations/ru.json index 8d255d492a7d32..ae72742d5605b9 100644 --- a/homeassistant/components/twilio/translations/ru.json +++ b/homeassistant/components/twilio/translations/ru.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u041d\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a Home Assistant Cloud.", "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e.", "webhook_not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f Webhook-\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439." }, diff --git a/homeassistant/components/twilio/translations/zh-Hant.json b/homeassistant/components/twilio/translations/zh-Hant.json index 0776d7cb0e5d81..59a8b4cb52f481 100644 --- a/homeassistant/components/twilio/translations/zh-Hant.json +++ b/homeassistant/components/twilio/translations/zh-Hant.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u672a\u9023\u7dda\u81f3 Home Assistant \u96f2\u670d\u52d9\u3002", "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "webhook_not_internet_accessible": "Home Assistant \u5be6\u9ad4\u5fc5\u9808\u8981\u80fd\u5f9e\u7db2\u969b\u7db2\u8def\u5b58\u53d6\u65b9\u80fd\u63a5\u6536 Webhook \u8a0a\u606f\u3002" }, diff --git a/homeassistant/components/unifi/translations/el.json b/homeassistant/components/unifi/translations/el.json index 99ca69f9724490..cfc6a2fc8b4c34 100644 --- a/homeassistant/components/unifi/translations/el.json +++ b/homeassistant/components/unifi/translations/el.json @@ -27,8 +27,12 @@ }, "device_tracker": { "data": { + "detection_time": "\u03a7\u03c1\u03cc\u03bd\u03bf\u03c2 \u03c3\u03b5 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03c4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03b1 \u03c6\u03bf\u03c1\u03ac \u03c0\u03bf\u03c5 \u03b5\u03b8\u03b5\u03ac\u03b8\u03b7 \u03ad\u03c9\u03c2 \u03cc\u03c4\u03bf\u03c5 \u03b8\u03b5\u03c9\u03c1\u03b7\u03b8\u03b5\u03af \u03b1\u03c0\u03bf\u03bc\u03b1\u03ba\u03c1\u03c5\u03c3\u03bc\u03ad\u03bd\u03b7", "ignore_wired_bug": "\u0391\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03bb\u03bf\u03b3\u03b9\u03ba\u03ae\u03c2 \u03b5\u03bd\u03c3\u03cd\u03c1\u03bc\u03b1\u03c4\u03c9\u03bd \u03c3\u03c6\u03b1\u03bb\u03bc\u03ac\u03c4\u03c9\u03bd \u03c4\u03bf\u03c5 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 UniFi", - "ssid_filter": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 SSID \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03b5\u03af\u03c4\u03b5 \u03b1\u03c3\u03cd\u03c1\u03bc\u03b1\u03c4\u03b1 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ad\u03c2-\u03c0\u03b5\u03bb\u03ac\u03c4\u03b5\u03c2" + "ssid_filter": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 SSID \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03b5\u03af\u03c4\u03b5 \u03b1\u03c3\u03cd\u03c1\u03bc\u03b1\u03c4\u03b1 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ad\u03c2-\u03c0\u03b5\u03bb\u03ac\u03c4\u03b5\u03c2", + "track_clients": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03c0\u03b5\u03bb\u03b1\u03c4\u03ce\u03bd \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5", + "track_devices": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 (\u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 Ubiquiti)", + "track_wired_clients": "\u03a3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c0\u03b5\u03bb\u03ac\u03c4\u03b5\u03c2 \u03b5\u03bd\u03c3\u03cd\u03c1\u03bc\u03b1\u03c4\u03bf\u03c5 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5" }, "description": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03bc\u03bf\u03cd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 UniFi 1/3" diff --git a/homeassistant/components/unifiprotect/translations/no.json b/homeassistant/components/unifiprotect/translations/no.json index 9e93a791a3559e..9b45080b8f1bce 100644 --- a/homeassistant/components/unifiprotect/translations/no.json +++ b/homeassistant/components/unifiprotect/translations/no.json @@ -18,7 +18,8 @@ "username": "Brukernavn", "verify_ssl": "Verifisere SSL-sertifikat" }, - "description": "Vil du konfigurere {name} ( {ip_address} )?" + "description": "Vil du konfigurere {name} ( {ip_address} )? Du trenger en lokal bruker opprettet i UniFi OS-konsollen for \u00e5 logge p\u00e5. Ubiquiti Cloud-brukere vil ikke fungere. For mer informasjon: {local_user_documentation_url}", + "title": "UniFi Protect oppdaget" }, "reauth_confirm": { "data": { @@ -37,6 +38,7 @@ "username": "Brukernavn", "verify_ssl": "Verifisere SSL-sertifikat" }, + "description": "Du trenger en lokal bruker opprettet i UniFi OS-konsollen for \u00e5 logge p\u00e5. Ubiquiti Cloud-brukere vil ikke fungere. For mer informasjon: {local_user_documentation_url}", "title": "UniFi Protect-oppsett" } } diff --git a/homeassistant/components/uptimerobot/translations/sensor.de.json b/homeassistant/components/uptimerobot/translations/sensor.de.json new file mode 100644 index 00000000000000..032e59b51977fb --- /dev/null +++ b/homeassistant/components/uptimerobot/translations/sensor.de.json @@ -0,0 +1,11 @@ +{ + "state": { + "uptimerobot__monitor_status": { + "down": "Herab", + "not_checked_yet": "Noch nicht gepr\u00fcft", + "pause": "Pause", + "seems_down": "Scheint unten zu sein", + "up": "Nach oben" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/uptimerobot/translations/sensor.et.json b/homeassistant/components/uptimerobot/translations/sensor.et.json new file mode 100644 index 00000000000000..75ff376daa5994 --- /dev/null +++ b/homeassistant/components/uptimerobot/translations/sensor.et.json @@ -0,0 +1,11 @@ +{ + "state": { + "uptimerobot__monitor_status": { + "down": "\u00dchenduseta", + "not_checked_yet": "Pole veel kontrollitud", + "pause": "Paus", + "seems_down": "Tundub kadunud olevat", + "up": "\u00dchendatud" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/uptimerobot/translations/sensor.no.json b/homeassistant/components/uptimerobot/translations/sensor.no.json new file mode 100644 index 00000000000000..183ba3306d2c8a --- /dev/null +++ b/homeassistant/components/uptimerobot/translations/sensor.no.json @@ -0,0 +1,11 @@ +{ + "state": { + "uptimerobot__monitor_status": { + "down": "Ned", + "not_checked_yet": "Ikke sjekket enn\u00e5", + "pause": "Pause", + "seems_down": "Virker nede", + "up": "Opp" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/uptimerobot/translations/sensor.ru.json b/homeassistant/components/uptimerobot/translations/sensor.ru.json new file mode 100644 index 00000000000000..72f6abfd12a62e --- /dev/null +++ b/homeassistant/components/uptimerobot/translations/sensor.ru.json @@ -0,0 +1,11 @@ +{ + "state": { + "uptimerobot__monitor_status": { + "down": "\u041d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442", + "not_checked_yet": "\u0415\u0449\u0451 \u043d\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043e", + "pause": "\u041f\u0430\u0443\u0437\u0430", + "seems_down": "\u041f\u043e\u0445\u043e\u0436\u0435, \u0447\u0442\u043e \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442", + "up": "\u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/uptimerobot/translations/sensor.zh-Hant.json b/homeassistant/components/uptimerobot/translations/sensor.zh-Hant.json new file mode 100644 index 00000000000000..5ca514829f59cf --- /dev/null +++ b/homeassistant/components/uptimerobot/translations/sensor.zh-Hant.json @@ -0,0 +1,11 @@ +{ + "state": { + "uptimerobot__monitor_status": { + "down": "\u96e2\u7dda", + "not_checked_yet": "\u5c1a\u672a\u6aa2\u67e5", + "pause": "\u66ab\u505c", + "seems_down": "\u4f3c\u4e4e\u96e2\u7dda", + "up": "\u7dda\u4e0a" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/velbus/translations/el.json b/homeassistant/components/velbus/translations/el.json index 04b238a916d221..a6b86e99d42cb9 100644 --- a/homeassistant/components/velbus/translations/el.json +++ b/homeassistant/components/velbus/translations/el.json @@ -2,6 +2,15 @@ "config": { "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "step": { + "user": { + "data": { + "name": "\u03a4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03b1\u03c5\u03c4\u03ae\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 velbus", + "port": "\u03a3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "title": "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03c4\u03bf\u03c5 \u03c4\u03cd\u03c0\u03bf\u03c5 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 velbus" + } } } } \ No newline at end of file diff --git a/homeassistant/components/wemo/translations/el.json b/homeassistant/components/wemo/translations/el.json new file mode 100644 index 00000000000000..6767d5d4d68031 --- /dev/null +++ b/homeassistant/components/wemo/translations/el.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Wemo;" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/el.json b/homeassistant/components/xiaomi_miio/translations/el.json index 23994c6296f37b..c2e966aee731c5 100644 --- a/homeassistant/components/xiaomi_miio/translations/el.json +++ b/homeassistant/components/xiaomi_miio/translations/el.json @@ -4,6 +4,7 @@ "no_device_selected": "\u0394\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03b5\u03af \u03ba\u03b1\u03bc\u03af\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae, \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae.", "unknown_device": "\u03a4\u03bf \u03bc\u03bf\u03bd\u03c4\u03ad\u03bb\u03bf \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b3\u03bd\u03c9\u03c3\u03c4\u03cc, \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03bc\u03b5 \u03c4\u03b7 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c1\u03bf\u03ae\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c9\u03bd." }, + "flow_title": "{name}", "step": { "device": { "data": { diff --git a/homeassistant/components/zha/translations/el.json b/homeassistant/components/zha/translations/el.json index 2a58de56c32e1d..f4104fe78a505d 100644 --- a/homeassistant/components/zha/translations/el.json +++ b/homeassistant/components/zha/translations/el.json @@ -76,6 +76,7 @@ }, "trigger_type": { "device_dropped": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c0\u03b5\u03c3\u03b5", + "device_offline": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03ba\u03c4\u03cc\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "device_rotated": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c0\u03b5\u03c1\u03b9\u03c3\u03c4\u03c1\u03ac\u03c6\u03b7\u03ba\u03b5 \"{subtype}\"", "device_shaken": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b1\u03bd\u03b1\u03ba\u03b9\u03bd\u03ae\u03b8\u03b7\u03ba\u03b5", "remote_button_alt_double_press": "\u03a4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \"{subtype}\" \u03c0\u03b1\u03c4\u03ae\u03b8\u03b7\u03ba\u03b5 \u03b4\u03b9\u03c0\u03bb\u03ac (\u0395\u03bd\u03b1\u03bb\u03bb\u03b1\u03ba\u03c4\u03b9\u03ba\u03ae \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1)", From 631c4bf10fab9894de8f49f772f53419cbe5ce74 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Fri, 28 Jan 2022 08:33:12 +0200 Subject: [PATCH 0037/1098] Fix Shelly 1/1PM external temperature sensor unavailable (#65096) --- homeassistant/components/shelly/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index efb7d3a35797e9..ce9c57f588966a 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -223,7 +223,7 @@ class RestSensorDescription(RestEntityDescription, SensorEntityDescription): device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, available=lambda block: cast(int, block.extTemp) != 999 - and not block.sensorError, + and not getattr(block, "sensorError", False), ), ("sensor", "humidity"): BlockSensorDescription( key="sensor|humidity", @@ -233,7 +233,7 @@ class RestSensorDescription(RestEntityDescription, SensorEntityDescription): device_class=SensorDeviceClass.HUMIDITY, state_class=SensorStateClass.MEASUREMENT, available=lambda block: cast(int, block.humidity) != 999 - and not block.sensorError, + and not getattr(block, "sensorError", False), ), ("sensor", "luminosity"): BlockSensorDescription( key="sensor|luminosity", From 86ed720335507ac39bb53353377b948380d53704 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 28 Jan 2022 07:34:18 +0100 Subject: [PATCH 0038/1098] Move `install_requires` to `setup.cfg` (#65095) --- .pre-commit-config.yaml | 2 +- script/gen_requirements_all.py | 8 ++++---- setup.cfg | 28 ++++++++++++++++++++++++++++ setup.py | 29 ----------------------------- 4 files changed, 33 insertions(+), 34 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 17575ebe375660..256a0d7d155237 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -107,7 +107,7 @@ repos: pass_filenames: false language: script types: [text] - files: ^(homeassistant/.+/manifest\.json|setup\.py|\.pre-commit-config\.yaml|script/gen_requirements_all\.py)$ + files: ^(homeassistant/.+/manifest\.json|setup\.cfg|\.pre-commit-config\.yaml|script/gen_requirements_all\.py)$ - id: hassfest name: hassfest entry: script/run-in-env.sh python3 -m script.hassfest diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index ce2178288a0ed5..872f2d0c7a8ec3 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 """Generate an updated requirements_all.txt.""" +import configparser import difflib import importlib import os @@ -167,10 +168,9 @@ def explore_module(package, explore_children): def core_requirements(): """Gather core requirements out of setup.py.""" - reqs_raw = re.search( - r"REQUIRES = \[(.*?)\]", Path("setup.py").read_text(), re.S - ).group(1) - return [x[1] for x in re.findall(r"(['\"])(.*?)\1", reqs_raw)] + parser = configparser.ConfigParser() + parser.read("setup.cfg") + return parser["options"]["install_requires"].strip().split("\n") def gather_recursive_requirements(domain, seen=None): diff --git a/setup.cfg b/setup.cfg index f285902985c0e0..4b226b4402cb58 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,6 +14,34 @@ classifier = Programming Language :: Python :: 3.9 Topic :: Home Automation +[options] +install_requires = + aiohttp==3.8.1 + astral==2.2 + async_timeout==4.0.2 + attrs==21.2.0 + atomicwrites==1.4.0 + awesomeversion==22.1.0 + bcrypt==3.1.7 + certifi>=2021.5.30 + ciso8601==2.2.0 + # When bumping httpx, please check the version pins of + # httpcore, anyio, and h11 in gen_requirements_all + httpx==0.21.3 + ifaddr==0.1.7 + jinja2==3.0.3 + PyJWT==2.1.0 + # PyJWT has loose dependency. We want the latest one. + cryptography==35.0.0 + pip>=8.0.3,<20.3 + python-slugify==4.0.1 + pyyaml==6.0 + requests==2.27.1 + typing-extensions>=3.10.0.2,<5.0 + voluptuous==0.12.2 + voluptuous-serialize==2.5.0 + yarl==1.7.2 + [flake8] exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build max-complexity = 25 diff --git a/setup.py b/setup.py index efcf61b85fce79..c6f3f1fb02fde4 100755 --- a/setup.py +++ b/setup.py @@ -31,34 +31,6 @@ PACKAGES = find_packages(exclude=["tests", "tests.*"]) -REQUIRES = [ - "aiohttp==3.8.1", - "astral==2.2", - "async_timeout==4.0.2", - "attrs==21.2.0", - "atomicwrites==1.4.0", - "awesomeversion==22.1.0", - "bcrypt==3.1.7", - "certifi>=2021.5.30", - "ciso8601==2.2.0", - # When bumping httpx, please check the version pins of - # httpcore, anyio, and h11 in gen_requirements_all - "httpx==0.21.3", - "ifaddr==0.1.7", - "jinja2==3.0.3", - "PyJWT==2.1.0", - # PyJWT has loose dependency. We want the latest one. - "cryptography==35.0.0", - "pip>=8.0.3,<20.3", - "python-slugify==4.0.1", - "pyyaml==6.0", - "requests==2.27.1", - "typing-extensions>=3.10.0.2,<5.0", - "voluptuous==0.12.2", - "voluptuous-serialize==2.5.0", - "yarl==1.7.2", -] - MIN_PY_VERSION = ".".join(map(str, hass_const.REQUIRED_PYTHON_VER)) setup( @@ -72,7 +44,6 @@ packages=PACKAGES, include_package_data=True, zip_safe=False, - install_requires=REQUIRES, python_requires=f">={MIN_PY_VERSION}", test_suite="tests", entry_points={"console_scripts": ["hass = homeassistant.__main__:main"]}, From de7f1e793abf6091e14db18d79036a2521a11edc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 28 Jan 2022 02:38:13 -0600 Subject: [PATCH 0039/1098] Downgrade homekit linked humidity sensor error to debug (#65098) Fixes #65015 --- homeassistant/components/homekit/type_humidifiers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/homekit/type_humidifiers.py b/homeassistant/components/homekit/type_humidifiers.py index 42115132420de8..09cfc02dcce310 100644 --- a/homeassistant/components/homekit/type_humidifiers.py +++ b/homeassistant/components/homekit/type_humidifiers.py @@ -190,7 +190,7 @@ def _async_update_current_humidity(self, new_state): ) self.char_current_humidity.set_value(current_humidity) except ValueError as ex: - _LOGGER.error( + _LOGGER.debug( "%s: Unable to update from linked humidity sensor %s: %s", self.entity_id, self.linked_humidity_sensor, From 0a2f57e4f8b87cfb7f8c1e839163a33e108a1517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Fri, 28 Jan 2022 10:51:32 +0100 Subject: [PATCH 0040/1098] Move netatmo dataclass registrations (#65052) --- homeassistant/components/netatmo/camera.py | 3 --- homeassistant/components/netatmo/climate.py | 3 --- homeassistant/components/netatmo/data_handler.py | 16 ++++++++++++++-- homeassistant/components/netatmo/light.py | 4 ---- homeassistant/components/netatmo/select.py | 3 --- homeassistant/components/netatmo/sensor.py | 1 - tests/components/netatmo/test_camera.py | 6 +++--- tests/components/netatmo/test_init.py | 4 ++-- tests/components/netatmo/test_light.py | 10 ++++++++-- 9 files changed, 27 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/netatmo/camera.py b/homeassistant/components/netatmo/camera.py index 380571aba70a29..7fa9fe02956e0c 100644 --- a/homeassistant/components/netatmo/camera.py +++ b/homeassistant/components/netatmo/camera.py @@ -55,9 +55,6 @@ async def async_setup_entry( """Set up the Netatmo camera platform.""" data_handler = hass.data[DOMAIN][entry.entry_id][DATA_HANDLER] - await data_handler.register_data_class( - CAMERA_DATA_CLASS_NAME, CAMERA_DATA_CLASS_NAME, None - ) data_class = data_handler.data.get(CAMERA_DATA_CLASS_NAME) if not data_class or not data_class.raw_data: diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index c8b5e01e5dbb22..623b7d0573a5c0 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -124,9 +124,6 @@ async def async_setup_entry( """Set up the Netatmo energy platform.""" data_handler = hass.data[DOMAIN][entry.entry_id][DATA_HANDLER] - await data_handler.register_data_class( - CLIMATE_TOPOLOGY_CLASS_NAME, CLIMATE_TOPOLOGY_CLASS_NAME, None - ) climate_topology = data_handler.data.get(CLIMATE_TOPOLOGY_CLASS_NAME) if not climate_topology or climate_topology.raw_data == {}: diff --git a/homeassistant/components/netatmo/data_handler.py b/homeassistant/components/netatmo/data_handler.py index ace5934adbd3e9..1d6345506c1e9b 100644 --- a/homeassistant/components/netatmo/data_handler.py +++ b/homeassistant/components/netatmo/data_handler.py @@ -74,7 +74,7 @@ class NetatmoDataClass: name: str interval: int next_scan: float - subscriptions: list[CALLBACK_TYPE] + subscriptions: list[CALLBACK_TYPE | None] class NetatmoDataHandler: @@ -105,6 +105,18 @@ async def async_setup(self) -> None: ) ) + await asyncio.gather( + *[ + self.register_data_class(data_class, data_class, None) + for data_class in ( + CLIMATE_TOPOLOGY_CLASS_NAME, + CAMERA_DATA_CLASS_NAME, + WEATHERSTATION_DATA_CLASS_NAME, + HOMECOACH_DATA_CLASS_NAME, + ) + ] + ) + async def async_update(self, event_time: datetime) -> None: """ Update device. @@ -172,7 +184,7 @@ async def register_data_class( self, data_class_name: str, data_class_entry: str, - update_callback: CALLBACK_TYPE, + update_callback: CALLBACK_TYPE | None, **kwargs: Any, ) -> None: """Register data class.""" diff --git a/homeassistant/components/netatmo/light.py b/homeassistant/components/netatmo/light.py index 9d83aa0297709f..58b1a0d4f43bc0 100644 --- a/homeassistant/components/netatmo/light.py +++ b/homeassistant/components/netatmo/light.py @@ -34,10 +34,6 @@ async def async_setup_entry( ) -> None: """Set up the Netatmo camera light platform.""" data_handler = hass.data[DOMAIN][entry.entry_id][DATA_HANDLER] - - await data_handler.register_data_class( - CAMERA_DATA_CLASS_NAME, CAMERA_DATA_CLASS_NAME, None - ) data_class = data_handler.data.get(CAMERA_DATA_CLASS_NAME) if not data_class or data_class.raw_data == {}: diff --git a/homeassistant/components/netatmo/select.py b/homeassistant/components/netatmo/select.py index 6c5f3eef00ad2a..56f33b04432b10 100644 --- a/homeassistant/components/netatmo/select.py +++ b/homeassistant/components/netatmo/select.py @@ -37,9 +37,6 @@ async def async_setup_entry( """Set up the Netatmo energy platform schedule selector.""" data_handler = hass.data[DOMAIN][entry.entry_id][DATA_HANDLER] - await data_handler.register_data_class( - CLIMATE_TOPOLOGY_CLASS_NAME, CLIMATE_TOPOLOGY_CLASS_NAME, None - ) climate_topology = data_handler.data.get(CLIMATE_TOPOLOGY_CLASS_NAME) if not climate_topology or climate_topology.raw_data == {}: diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index defcd757d0a7c0..7c600f6b44218a 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -386,7 +386,6 @@ async def find_entities(data_class_name: str) -> list: WEATHERSTATION_DATA_CLASS_NAME, HOMECOACH_DATA_CLASS_NAME, ): - await data_handler.register_data_class(data_class_name, data_class_name, None) data_class = data_handler.data.get(data_class_name) if data_class and data_class.raw_data: diff --git a/tests/components/netatmo/test_camera.py b/tests/components/netatmo/test_camera.py index b1cee88ee2ff08..2eaf713e8ee0c5 100644 --- a/tests/components/netatmo/test_camera.py +++ b/tests/components/netatmo/test_camera.py @@ -364,7 +364,7 @@ async def fake_post(*args, **kwargs): await simulate_webhook(hass, webhook_id, response) await hass.async_block_till_done() - assert fake_post_hits == 5 + assert fake_post_hits == 8 calls = fake_post_hits @@ -446,7 +446,7 @@ async def fake_post_no_data(*args, **kwargs): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - assert fake_post_hits == 1 + assert fake_post_hits == 4 async def test_camera_image_raises_exception(hass, config_entry, requests_mock): @@ -491,4 +491,4 @@ async def fake_post(*args, **kwargs): await camera.async_get_image(hass, camera_entity_indoor) assert excinfo.value.args == ("Unable to get image",) - assert fake_post_hits == 6 + assert fake_post_hits == 9 diff --git a/tests/components/netatmo/test_init.py b/tests/components/netatmo/test_init.py index 950a45f1e4a1aa..ffa68d75011c75 100644 --- a/tests/components/netatmo/test_init.py +++ b/tests/components/netatmo/test_init.py @@ -112,7 +112,7 @@ async def fake_post(*args, **kwargs): await hass.async_block_till_done() - assert fake_post_hits == 3 + assert fake_post_hits == 9 mock_impl.assert_called_once() mock_webhook.assert_called_once() @@ -354,7 +354,7 @@ async def test_setup_component_with_delay(hass, config_entry): await hass.async_block_till_done() - assert mock_post_request.call_count == 5 + assert mock_post_request.call_count == 8 mock_impl.assert_called_once() mock_webhook.assert_not_called() diff --git a/tests/components/netatmo/test_light.py b/tests/components/netatmo/test_light.py index d28df01beccdf4..a0992e7ea2c892 100644 --- a/tests/components/netatmo/test_light.py +++ b/tests/components/netatmo/test_light.py @@ -11,6 +11,8 @@ from .common import FAKE_WEBHOOK_ACTIVATION, selected_platforms, simulate_webhook +from tests.test_util.aiohttp import AiohttpClientMockResponse + async def test_light_setup_and_services(hass, config_entry, netatmo_auth): """Test setup and services.""" @@ -89,7 +91,11 @@ async def fake_post_request_no_data(*args, **kwargs): """Fake error during requesting backend data.""" nonlocal fake_post_hits fake_post_hits += 1 - return "{}" + return AiohttpClientMockResponse( + method="POST", + url=kwargs["url"], + json={}, + ) with patch( "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth" @@ -115,7 +121,7 @@ async def fake_post_request_no_data(*args, **kwargs): ) await hass.async_block_till_done() - assert fake_post_hits == 1 + assert fake_post_hits == 4 assert hass.config_entries.async_entries(DOMAIN) assert len(hass.states.async_all()) == 0 From 65fb6f26f1314a249b574e6f7c1cd226c602aba1 Mon Sep 17 00:00:00 2001 From: Thibaut Date: Fri, 28 Jan 2022 10:58:42 +0100 Subject: [PATCH 0041/1098] Check explicitly for None value in Overkiz integration (#65045) --- homeassistant/components/overkiz/cover_entities/awning.py | 3 ++- .../components/overkiz/cover_entities/generic_cover.py | 5 +++-- homeassistant/components/overkiz/light.py | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/overkiz/cover_entities/awning.py b/homeassistant/components/overkiz/cover_entities/awning.py index bb5b0e52186877..bbce2c985edd12 100644 --- a/homeassistant/components/overkiz/cover_entities/awning.py +++ b/homeassistant/components/overkiz/cover_entities/awning.py @@ -48,7 +48,8 @@ def current_cover_position(self) -> int | None: None is unknown, 0 is closed, 100 is fully open. """ - if current_position := self.executor.select_state(OverkizState.CORE_DEPLOYMENT): + current_position = self.executor.select_state(OverkizState.CORE_DEPLOYMENT) + if current_position is not None: return cast(int, current_position) return None diff --git a/homeassistant/components/overkiz/cover_entities/generic_cover.py b/homeassistant/components/overkiz/cover_entities/generic_cover.py index 476ed23ae4daf8..60484620df1727 100644 --- a/homeassistant/components/overkiz/cover_entities/generic_cover.py +++ b/homeassistant/components/overkiz/cover_entities/generic_cover.py @@ -51,9 +51,10 @@ def current_cover_tilt_position(self) -> int | None: None is unknown, 0 is closed, 100 is fully open. """ - if position := self.executor.select_state( + position = self.executor.select_state( OverkizState.CORE_SLATS_ORIENTATION, OverkizState.CORE_SLATE_ORIENTATION - ): + ) + if position is not None: return 100 - cast(int, position) return None diff --git a/homeassistant/components/overkiz/light.py b/homeassistant/components/overkiz/light.py index 6075267b8e6f32..b640a184f5831b 100644 --- a/homeassistant/components/overkiz/light.py +++ b/homeassistant/components/overkiz/light.py @@ -79,8 +79,9 @@ def rgb_color(self) -> tuple[int, int, int] | None: @property def brightness(self) -> int | None: """Return the brightness of this light (0-255).""" - if brightness := self.executor.select_state(OverkizState.CORE_LIGHT_INTENSITY): - return round(cast(int, brightness) * 255 / 100) + value = self.executor.select_state(OverkizState.CORE_LIGHT_INTENSITY) + if value is not None: + return round(cast(int, value) * 255 / 100) return None From c470733febb64df6b86a13b0dd93f93a96996adf Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 28 Jan 2022 11:38:09 +0100 Subject: [PATCH 0042/1098] Fix cast support for browsing local media source (#65115) --- homeassistant/components/cast/media_player.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index f96279d6d7f896..e966e98c3f13b8 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -471,9 +471,16 @@ async def async_browse_media(self, media_content_type=None, media_content_id=Non "audio/" ) - if plex.is_plex_media_id(media_content_id): - return await plex.async_browse_media( - self.hass, media_content_type, media_content_id, platform=CAST_DOMAIN + if media_content_id is not None: + if plex.is_plex_media_id(media_content_id): + return await plex.async_browse_media( + self.hass, + media_content_type, + media_content_id, + platform=CAST_DOMAIN, + ) + return await media_source.async_browse_media( + self.hass, media_content_id, **kwargs ) if media_content_type == "plex": From a9cc35d6b6c5d5791df49672aeaf69650541ba50 Mon Sep 17 00:00:00 2001 From: Hans Oischinger Date: Fri, 28 Jan 2022 12:06:05 +0100 Subject: [PATCH 0043/1098] Handle vicare I/O in executor (#65105) Co-authored-by: Martin Hjelmare --- homeassistant/components/vicare/climate.py | 43 +++++++++++-------- .../components/vicare/water_heater.py | 42 ++++++++++-------- 2 files changed, 50 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/vicare/climate.py b/homeassistant/components/vicare/climate.py index a528fd63614123..451ea70edab574 100644 --- a/homeassistant/components/vicare/climate.py +++ b/homeassistant/components/vicare/climate.py @@ -101,6 +101,15 @@ def _build_entity(name, vicare_api, circuit, device_config, heating_type): return ViCareClimate(name, vicare_api, device_config, circuit, heating_type) +def _get_circuits(vicare_api): + """Return the list of circuits.""" + try: + return vicare_api.circuits + except PyViCareNotSupportedFeatureError: + _LOGGER.info("No circuits found") + return [] + + async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, @@ -108,25 +117,23 @@ async def async_setup_entry( ) -> None: """Set up the ViCare climate platform.""" name = VICARE_NAME - entities = [] - - try: - for circuit in hass.data[DOMAIN][config_entry.entry_id][VICARE_API].circuits: - suffix = "" - if len(hass.data[DOMAIN][config_entry.entry_id][VICARE_API].circuits) > 1: - suffix = f" {circuit.id}" - entity = _build_entity( - f"{name} Heating{suffix}", - hass.data[DOMAIN][config_entry.entry_id][VICARE_API], - hass.data[DOMAIN][config_entry.entry_id][VICARE_DEVICE_CONFIG], - circuit, - config_entry.data[CONF_HEATING_TYPE], - ) - if entity is not None: - entities.append(entity) - except PyViCareNotSupportedFeatureError: - _LOGGER.info("No circuits found") + api = hass.data[DOMAIN][config_entry.entry_id][VICARE_API] + circuits = await hass.async_add_executor_job(_get_circuits, api) + + for circuit in circuits: + suffix = "" + if len(circuits) > 1: + suffix = f" {circuit.id}" + + entity = _build_entity( + f"{name} Heating{suffix}", + api, + hass.data[DOMAIN][config_entry.entry_id][VICARE_DEVICE_CONFIG], + circuit, + config_entry.data[CONF_HEATING_TYPE], + ) + entities.append(entity) platform = entity_platform.async_get_current_platform() diff --git a/homeassistant/components/vicare/water_heater.py b/homeassistant/components/vicare/water_heater.py index 5912287a95332d..0107ff8fe4cede 100644 --- a/homeassistant/components/vicare/water_heater.py +++ b/homeassistant/components/vicare/water_heater.py @@ -68,6 +68,15 @@ def _build_entity(name, vicare_api, circuit, device_config, heating_type): ) +def _get_circuits(vicare_api): + """Return the list of circuits.""" + try: + return vicare_api.circuits + except PyViCareNotSupportedFeatureError: + _LOGGER.info("No circuits found") + return [] + + async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, @@ -75,24 +84,23 @@ async def async_setup_entry( ) -> None: """Set up the ViCare climate platform.""" name = VICARE_NAME - entities = [] - try: - for circuit in hass.data[DOMAIN][config_entry.entry_id][VICARE_API].circuits: - suffix = "" - if len(hass.data[DOMAIN][config_entry.entry_id][VICARE_API].circuits) > 1: - suffix = f" {circuit.id}" - entity = _build_entity( - f"{name} Water{suffix}", - hass.data[DOMAIN][config_entry.entry_id][VICARE_API], - circuit, - hass.data[DOMAIN][config_entry.entry_id][VICARE_DEVICE_CONFIG], - config_entry.data[CONF_HEATING_TYPE], - ) - if entity is not None: - entities.append(entity) - except PyViCareNotSupportedFeatureError: - _LOGGER.info("No circuits found") + api = hass.data[DOMAIN][config_entry.entry_id][VICARE_API] + circuits = await hass.async_add_executor_job(_get_circuits, api) + + for circuit in circuits: + suffix = "" + if len(circuits) > 1: + suffix = f" {circuit.id}" + + entity = _build_entity( + f"{name} Water{suffix}", + api, + circuit, + hass.data[DOMAIN][config_entry.entry_id][VICARE_DEVICE_CONFIG], + config_entry.data[CONF_HEATING_TYPE], + ) + entities.append(entity) async_add_entities(entities) From 75f39f9ca2add4ed3b172223d2a81eedc422b313 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 28 Jan 2022 13:36:20 +0100 Subject: [PATCH 0044/1098] Move version metadata key to setup.cfg (#65091) * Move version to setup.cfg * Move python_requires to setup.cfg * Add script to validate project metadata * Add dedicated pre-commit hook --- .pre-commit-config.yaml | 7 +++++++ script/hassfest/__main__.py | 2 ++ script/hassfest/metadata.py | 31 +++++++++++++++++++++++++++++++ script/version_bump.py | 14 +++++++++++++- setup.cfg | 2 ++ setup.py | 4 ---- 6 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 script/hassfest/metadata.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 256a0d7d155237..87c7d9e9102c7c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -115,3 +115,10 @@ repos: language: script types: [text] files: ^(homeassistant/.+/(manifest|strings)\.json|\.coveragerc|\.strict-typing|homeassistant/.+/services\.yaml|script/hassfest/.+\.py)$ + - id: hassfest-metadata + name: hassfest-metadata + entry: script/run-in-env.sh python3 -m script.hassfest -p metadata + pass_filenames: false + language: script + types: [text] + files: ^(script/hassfest/.+\.py|homeassistant/const\.py$|setup\.cfg)$ diff --git a/script/hassfest/__main__.py b/script/hassfest/__main__.py index 8a8e1155ab9550..c6a9799a502c66 100644 --- a/script/hassfest/__main__.py +++ b/script/hassfest/__main__.py @@ -12,6 +12,7 @@ dhcp, json, manifest, + metadata, mqtt, mypy_config, requirements, @@ -41,6 +42,7 @@ HASS_PLUGINS = [ coverage, mypy_config, + metadata, ] ALL_PLUGIN_NAMES = [ diff --git a/script/hassfest/metadata.py b/script/hassfest/metadata.py new file mode 100644 index 00000000000000..ab5ba3f036dd14 --- /dev/null +++ b/script/hassfest/metadata.py @@ -0,0 +1,31 @@ +"""Package metadata validation.""" +import configparser + +from homeassistant.const import REQUIRED_PYTHON_VER, __version__ + +from .model import Config, Integration + + +def validate(integrations: dict[str, Integration], config: Config) -> None: + """Validate project metadata keys.""" + metadata_path = config.root / "setup.cfg" + parser = configparser.ConfigParser() + parser.read(metadata_path) + + try: + if parser["metadata"]["version"] != __version__: + config.add_error( + "metadata", f"'metadata.version' value does not match '{__version__}'" + ) + except KeyError: + config.add_error("metadata", "No 'metadata.version' key found!") + + required_py_version = f">={'.'.join(map(str, REQUIRED_PYTHON_VER))}" + try: + if parser["options"]["python_requires"] != required_py_version: + config.add_error( + "metadata", + f"'options.python_requires' value doesn't match '{required_py_version}", + ) + except KeyError: + config.add_error("metadata", "No 'options.python_requires' key found!") diff --git a/script/version_bump.py b/script/version_bump.py index 5f1988f3c26b57..6044cdb277c408 100755 --- a/script/version_bump.py +++ b/script/version_bump.py @@ -117,7 +117,18 @@ def write_version(version): ) with open("homeassistant/const.py", "wt") as fil: - content = fil.write(content) + fil.write(content) + + +def write_version_metadata(version: Version) -> None: + """Update setup.cfg file with new version.""" + with open("setup.cfg") as fp: + content = fp.read() + + content = re.sub(r"(version\W+=\W).+\n", f"\\g<1>{version}\n", content, count=1) + + with open("setup.cfg", "w") as fp: + fp.write(content) def main(): @@ -142,6 +153,7 @@ def main(): assert bumped > current, "BUG! New version is not newer than old version" write_version(bumped) + write_version_metadata(bumped) if not arguments.commit: return diff --git a/setup.cfg b/setup.cfg index 4b226b4402cb58..0625178d78f7fd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,4 +1,5 @@ [metadata] +version = 2022.3.0.dev0 license = Apache-2.0 license_file = LICENSE.md platforms = any @@ -15,6 +16,7 @@ classifier = Topic :: Home Automation [options] +python_requires = >=3.9.0 install_requires = aiohttp==3.8.1 astral==2.2 diff --git a/setup.py b/setup.py index c6f3f1fb02fde4..403ba9f0a33678 100755 --- a/setup.py +++ b/setup.py @@ -31,11 +31,8 @@ PACKAGES = find_packages(exclude=["tests", "tests.*"]) -MIN_PY_VERSION = ".".join(map(str, hass_const.REQUIRED_PYTHON_VER)) - setup( name=PROJECT_PACKAGE_NAME, - version=hass_const.__version__, url=PROJECT_URL, download_url=DOWNLOAD_URL, project_urls=PROJECT_URLS, @@ -44,7 +41,6 @@ packages=PACKAGES, include_package_data=True, zip_safe=False, - python_requires=f">={MIN_PY_VERSION}", test_suite="tests", entry_points={"console_scripts": ["hass = homeassistant.__main__:main"]}, ) From 0b02870489053982a4e0934dc8a80f950cde993f Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Fri, 28 Jan 2022 15:54:19 +0100 Subject: [PATCH 0045/1098] Goodwe - fix value errors (#65121) --- homeassistant/components/goodwe/number.py | 2 +- homeassistant/components/goodwe/select.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/goodwe/number.py b/homeassistant/components/goodwe/number.py index 614cab3d90a3f7..06a31a4e10ab44 100644 --- a/homeassistant/components/goodwe/number.py +++ b/homeassistant/components/goodwe/number.py @@ -76,7 +76,7 @@ async def async_setup_entry( for description in NUMBERS: try: current_value = await description.getter(inverter) - except InverterError: + except (InverterError, ValueError): # Inverter model does not support this setting _LOGGER.debug("Could not read inverter setting %s", description.key) continue diff --git a/homeassistant/components/goodwe/select.py b/homeassistant/components/goodwe/select.py index b8ff5c91a3ca2a..985c799110d933 100644 --- a/homeassistant/components/goodwe/select.py +++ b/homeassistant/components/goodwe/select.py @@ -42,7 +42,7 @@ async def async_setup_entry( # read current operating mode from the inverter try: active_mode = await inverter.get_operation_mode() - except InverterError: + except (InverterError, ValueError): # Inverter model does not support this setting _LOGGER.debug("Could not read inverter operation mode") else: From dccc4eba7691218146a971c7128c4902468d6b0d Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 28 Jan 2022 16:00:01 +0100 Subject: [PATCH 0046/1098] Remove artifacts from black formatting (#65113) * Remove artifacts from black formatting * Tweak --- tests/components/device_tracker/test_init.py | 2 +- .../manual_mqtt/test_alarm_control_panel.py | 66 +++++++++---------- tests/components/media_player/test_init.py | 4 +- tests/components/microsoft_face/test_init.py | 2 +- tests/components/mqtt/test_legacy_vacuum.py | 4 +- tests/helpers/test_template.py | 4 +- 6 files changed, 41 insertions(+), 41 deletions(-) diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index bf72cb3411976d..0953fc67b0a12a 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -298,7 +298,7 @@ async def test_entity_attributes( assert picture == attrs.get(ATTR_ENTITY_PICTURE) -@patch("homeassistant.components.device_tracker.legacy." "DeviceTracker.async_see") +@patch("homeassistant.components.device_tracker.legacy.DeviceTracker.async_see") async def test_see_service(mock_see, hass, enable_custom_integrations): """Test the see service with a unicode dev_id and NO MAC.""" with assert_setup_component(1, device_tracker.DOMAIN): diff --git a/tests/components/manual_mqtt/test_alarm_control_panel.py b/tests/components/manual_mqtt/test_alarm_control_panel.py index 05033bb3347422..18ed447ec53110 100644 --- a/tests/components/manual_mqtt/test_alarm_control_panel.py +++ b/tests/components/manual_mqtt/test_alarm_control_panel.py @@ -147,7 +147,7 @@ async def test_arm_home_with_pending(hass, mqtt_mock): future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -307,7 +307,7 @@ async def test_arm_away_with_pending(hass, mqtt_mock): future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -437,7 +437,7 @@ async def test_arm_night_with_pending(hass, mqtt_mock): future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -510,7 +510,7 @@ async def test_trigger_no_pending(hass, mqtt_mock): future = dt_util.utcnow() + timedelta(seconds=60) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -557,7 +557,7 @@ async def test_trigger_with_delay(hass, mqtt_mock): future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -658,7 +658,7 @@ async def test_trigger_with_pending(hass, mqtt_mock): future = dt_util.utcnow() + timedelta(seconds=2) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -668,7 +668,7 @@ async def test_trigger_with_pending(hass, mqtt_mock): future = dt_util.utcnow() + timedelta(seconds=5) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -707,7 +707,7 @@ async def test_trigger_with_disarm_after_trigger(hass, mqtt_mock): future = dt_util.utcnow() + timedelta(seconds=5) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -777,7 +777,7 @@ async def test_trigger_with_unused_zero_specific_trigger_time(hass, mqtt_mock): future = dt_util.utcnow() + timedelta(seconds=5) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -816,7 +816,7 @@ async def test_trigger_with_specific_trigger_time(hass, mqtt_mock): future = dt_util.utcnow() + timedelta(seconds=5) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -860,7 +860,7 @@ async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass, mqtt_mock future = dt_util.utcnow() + timedelta(seconds=5) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -875,7 +875,7 @@ async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass, mqtt_mock future = dt_util.utcnow() + timedelta(seconds=5) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -918,7 +918,7 @@ async def test_disarm_while_pending_trigger(hass, mqtt_mock): future = dt_util.utcnow() + timedelta(seconds=5) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -962,7 +962,7 @@ async def test_disarm_during_trigger_with_invalid_code(hass, mqtt_mock): future = dt_util.utcnow() + timedelta(seconds=5) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1010,7 +1010,7 @@ async def test_trigger_with_unused_specific_delay(hass, mqtt_mock): future = dt_util.utcnow() + timedelta(seconds=5) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1059,7 +1059,7 @@ async def test_trigger_with_specific_delay(hass, mqtt_mock): future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1108,7 +1108,7 @@ async def test_trigger_with_pending_and_delay(hass, mqtt_mock): future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1120,7 +1120,7 @@ async def test_trigger_with_pending_and_delay(hass, mqtt_mock): future += timedelta(seconds=1) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1170,7 +1170,7 @@ async def test_trigger_with_pending_and_specific_delay(hass, mqtt_mock): future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1182,7 +1182,7 @@ async def test_trigger_with_pending_and_specific_delay(hass, mqtt_mock): future += timedelta(seconds=1) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1219,7 +1219,7 @@ async def test_armed_home_with_specific_pending(hass, mqtt_mock): future = dt_util.utcnow() + timedelta(seconds=2) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1255,7 +1255,7 @@ async def test_armed_away_with_specific_pending(hass, mqtt_mock): future = dt_util.utcnow() + timedelta(seconds=2) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1291,7 +1291,7 @@ async def test_armed_night_with_specific_pending(hass, mqtt_mock): future = dt_util.utcnow() + timedelta(seconds=2) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1329,7 +1329,7 @@ async def test_trigger_with_specific_pending(hass, mqtt_mock): future = dt_util.utcnow() + timedelta(seconds=2) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1339,7 +1339,7 @@ async def test_trigger_with_specific_pending(hass, mqtt_mock): future = dt_util.utcnow() + timedelta(seconds=5) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1392,7 +1392,7 @@ async def test_arm_away_after_disabled_disarmed(hass, legacy_patchable_time, mqt future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1411,7 +1411,7 @@ async def test_arm_away_after_disabled_disarmed(hass, legacy_patchable_time, mqt future += timedelta(seconds=1) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1493,7 +1493,7 @@ async def test_arm_home_via_command_topic(hass, mqtt_mock): # Fast-forward a little bit future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1532,7 +1532,7 @@ async def test_arm_away_via_command_topic(hass, mqtt_mock): # Fast-forward a little bit future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1571,7 +1571,7 @@ async def test_arm_night_via_command_topic(hass, mqtt_mock): # Fast-forward a little bit future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1649,7 +1649,7 @@ async def test_state_changes_are_published_to_mqtt(hass, mqtt_mock): # Fast-forward a little bit future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1669,7 +1669,7 @@ async def test_state_changes_are_published_to_mqtt(hass, mqtt_mock): # Fast-forward a little bit future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) @@ -1689,7 +1689,7 @@ async def test_state_changes_are_published_to_mqtt(hass, mqtt_mock): # Fast-forward a little bit future = dt_util.utcnow() + timedelta(seconds=1) with patch( - ("homeassistant.components.manual_mqtt.alarm_control_panel." "dt_util.utcnow"), + ("homeassistant.components.manual_mqtt.alarm_control_panel.dt_util.utcnow"), return_value=future, ): async_fire_time_changed(hass, future) diff --git a/tests/components/media_player/test_init.py b/tests/components/media_player/test_init.py index 3f0efcd45aa3bf..c1fcd510c23a2e 100644 --- a/tests/components/media_player/test_init.py +++ b/tests/components/media_player/test_init.py @@ -165,7 +165,7 @@ async def test_media_browse(hass, hass_ws_client): "homeassistant.components.demo.media_player.YOUTUBE_PLAYER_SUPPORT", media_player.SUPPORT_BROWSE_MEDIA, ), patch( - "homeassistant.components.media_player.MediaPlayerEntity." "async_browse_media", + "homeassistant.components.media_player.MediaPlayerEntity.async_browse_media", return_value={"bla": "yo"}, ) as mock_browse_media: await client.send_json( @@ -190,7 +190,7 @@ async def test_media_browse(hass, hass_ws_client): "homeassistant.components.demo.media_player.YOUTUBE_PLAYER_SUPPORT", media_player.SUPPORT_BROWSE_MEDIA, ), patch( - "homeassistant.components.media_player.MediaPlayerEntity." "async_browse_media", + "homeassistant.components.media_player.MediaPlayerEntity.async_browse_media", return_value={"bla": "yo"}, ): await client.send_json( diff --git a/tests/components/microsoft_face/test_init.py b/tests/components/microsoft_face/test_init.py index 30f6f88bd297ee..24dcb5928d865e 100644 --- a/tests/components/microsoft_face/test_init.py +++ b/tests/components/microsoft_face/test_init.py @@ -210,7 +210,7 @@ async def test_service_person(hass, aioclient_mock): ) aioclient_mock.delete( ENDPOINT_URL.format( - "persongroups/test_group1/persons/" "25985303-c537-4467-b41d-bdb45cd95ca1" + "persongroups/test_group1/persons/25985303-c537-4467-b41d-bdb45cd95ca1" ), status=200, text="{}", diff --git a/tests/components/mqtt/test_legacy_vacuum.py b/tests/components/mqtt/test_legacy_vacuum.py index 59263037e657f6..808212014c7585 100644 --- a/tests/components/mqtt/test_legacy_vacuum.py +++ b/tests/components/mqtt/test_legacy_vacuum.py @@ -661,8 +661,8 @@ async def test_discovery_removal_vacuum(hass, mqtt_mock, caplog): async def test_discovery_update_vacuum(hass, mqtt_mock, caplog): """Test update of discovered vacuum.""" - config1 = {"name": "Beer", " " "command_topic": "test_topic"} - config2 = {"name": "Milk", " " "command_topic": "test_topic"} + config1 = {"name": "Beer", "command_topic": "test_topic"} + config2 = {"name": "Milk", "command_topic": "test_topic"} await help_test_discovery_update( hass, mqtt_mock, caplog, vacuum.DOMAIN, config1, config2 ) diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 42834b3c149634..d70837bd08834b 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -3182,12 +3182,12 @@ def test_render_complex_handling_non_template_values(hass): def test_urlencode(hass): """Test the urlencode method.""" tpl = template.Template( - ("{% set dict = {'foo': 'x&y', 'bar': 42} %}" "{{ dict | urlencode }}"), + ("{% set dict = {'foo': 'x&y', 'bar': 42} %}{{ dict | urlencode }}"), hass, ) assert tpl.async_render() == "foo=x%26y&bar=42" tpl = template.Template( - ("{% set string = 'the quick brown fox = true' %}" "{{ string | urlencode }}"), + ("{% set string = 'the quick brown fox = true' %}{{ string | urlencode }}"), hass, ) assert tpl.async_render() == "the%20quick%20brown%20fox%20%3D%20true" From 090a8a94390587170611c17e05cb847679df046e Mon Sep 17 00:00:00 2001 From: Klaas Schoute Date: Fri, 28 Jan 2022 16:08:29 +0100 Subject: [PATCH 0047/1098] Add diagnostics support to P1 Monitor (#65060) * Add diagnostics to P1 Monitor * Add test for diagnostics --- .../components/p1_monitor/diagnostics.py | 35 +++++++++++ .../components/p1_monitor/test_diagnostics.py | 59 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 homeassistant/components/p1_monitor/diagnostics.py create mode 100644 tests/components/p1_monitor/test_diagnostics.py diff --git a/homeassistant/components/p1_monitor/diagnostics.py b/homeassistant/components/p1_monitor/diagnostics.py new file mode 100644 index 00000000000000..627d0df767deda --- /dev/null +++ b/homeassistant/components/p1_monitor/diagnostics.py @@ -0,0 +1,35 @@ +"""Diagnostics support for P1 Monitor.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_HOST +from homeassistant.core import HomeAssistant + +from . import P1MonitorDataUpdateCoordinator +from .const import DOMAIN, SERVICE_PHASES, SERVICE_SETTINGS, SERVICE_SMARTMETER + +TO_REDACT = { + CONF_HOST, +} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + coordinator: P1MonitorDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + return { + "entry": { + "title": entry.title, + "data": async_redact_data(entry.data, TO_REDACT), + }, + "data": { + "smartmeter": coordinator.data[SERVICE_SMARTMETER].__dict__, + "phases": coordinator.data[SERVICE_PHASES].__dict__, + "settings": coordinator.data[SERVICE_SETTINGS].__dict__, + }, + } diff --git a/tests/components/p1_monitor/test_diagnostics.py b/tests/components/p1_monitor/test_diagnostics.py new file mode 100644 index 00000000000000..6b97107c353be1 --- /dev/null +++ b/tests/components/p1_monitor/test_diagnostics.py @@ -0,0 +1,59 @@ +"""Tests for the diagnostics data provided by the P1 Monitor integration.""" +from aiohttp import ClientSession + +from homeassistant.components.diagnostics import REDACTED +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_diagnostics( + hass: HomeAssistant, + hass_client: ClientSession, + init_integration: MockConfigEntry, +): + """Test diagnostics.""" + assert await get_diagnostics_for_config_entry( + hass, hass_client, init_integration + ) == { + "entry": { + "title": "monitor", + "data": { + "host": REDACTED, + }, + }, + "data": { + "smartmeter": { + "gas_consumption": 2273.447, + "energy_tariff_period": "high", + "power_consumption": 877, + "energy_consumption_high": 2770.133, + "energy_consumption_low": 4988.071, + "power_production": 0, + "energy_production_high": 3971.604, + "energy_production_low": 1432.279, + }, + "phases": { + "voltage_phase_l1": "233.6", + "voltage_phase_l2": "0.0", + "voltage_phase_l3": "233.0", + "current_phase_l1": "1.6", + "current_phase_l2": "4.44", + "current_phase_l3": "3.51", + "power_consumed_phase_l1": 315, + "power_consumed_phase_l2": 0, + "power_consumed_phase_l3": 624, + "power_produced_phase_l1": 0, + "power_produced_phase_l2": 0, + "power_produced_phase_l3": 0, + }, + "settings": { + "gas_consumption_price": "0.64", + "energy_consumption_price_high": "0.20522", + "energy_consumption_price_low": "0.20522", + "energy_production_price_high": "0.20522", + "energy_production_price_low": "0.20522", + }, + }, + } From 444a6817295a01ba1c68c9808d515b891b3e90c4 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Fri, 28 Jan 2022 08:09:08 -0800 Subject: [PATCH 0048/1098] Bump google-nest-sdm to 1.6.0 (diagnostics) (#65135) --- homeassistant/components/nest/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/nest/manifest.json b/homeassistant/components/nest/manifest.json index 9c686362d9877f..478e608700c362 100644 --- a/homeassistant/components/nest/manifest.json +++ b/homeassistant/components/nest/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "dependencies": ["ffmpeg", "http", "media_source"], "documentation": "https://www.home-assistant.io/integrations/nest", - "requirements": ["python-nest==4.1.0", "google-nest-sdm==1.5.1"], + "requirements": ["python-nest==4.1.0", "google-nest-sdm==1.6.0"], "codeowners": ["@allenporter"], "quality_scale": "platinum", "dhcp": [ diff --git a/requirements_all.txt b/requirements_all.txt index 69f4a2de845636..3a3ae91fe2b1b5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -764,7 +764,7 @@ google-cloud-pubsub==2.9.0 google-cloud-texttospeech==0.4.0 # homeassistant.components.nest -google-nest-sdm==1.5.1 +google-nest-sdm==1.6.0 # homeassistant.components.google_travel_time googlemaps==2.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0100ee79044b90..83086ebb78635f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -492,7 +492,7 @@ google-api-python-client==1.6.4 google-cloud-pubsub==2.9.0 # homeassistant.components.nest -google-nest-sdm==1.5.1 +google-nest-sdm==1.6.0 # homeassistant.components.google_travel_time googlemaps==2.5.1 From 44572ff35455a27010c2eccd150417acaaa4aaad Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 28 Jan 2022 17:11:46 +0100 Subject: [PATCH 0049/1098] Move `project_urls` to `setup.cfg` (#65129) --- setup.cfg | 7 +++++++ setup.py | 21 --------------------- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/setup.cfg b/setup.cfg index 0625178d78f7fd..274e1ac362a548 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,6 +6,13 @@ platforms = any description = Open-source home automation platform running on Python 3. long_description = file: README.rst keywords = home, automation +url = https://www.home-assistant.io/ +project_urls = + Source Code = https://github.com/home-assistant/core + Bug Reports = https://github.com/home-assistant/core/issues + Docs: Dev = https://developers.home-assistant.io/ + Discord = https://discordapp.com/invite/c5DvZ4e + Forum = https://community.home-assistant.io/ classifier = Development Status :: 4 - Beta Intended Audience :: End Users/Desktop diff --git a/setup.py b/setup.py index 403ba9f0a33678..febaab62be037b 100755 --- a/setup.py +++ b/setup.py @@ -4,38 +4,17 @@ from setuptools import find_packages, setup -import homeassistant.const as hass_const - PROJECT_NAME = "Home Assistant" PROJECT_PACKAGE_NAME = "homeassistant" PROJECT_LICENSE = "Apache License 2.0" PROJECT_AUTHOR = "The Home Assistant Authors" PROJECT_COPYRIGHT = f" 2013-{dt.now().year}, {PROJECT_AUTHOR}" -PROJECT_URL = "https://www.home-assistant.io/" PROJECT_EMAIL = "hello@home-assistant.io" -PROJECT_GITHUB_USERNAME = "home-assistant" -PROJECT_GITHUB_REPOSITORY = "core" - -PYPI_URL = f"https://pypi.python.org/pypi/{PROJECT_PACKAGE_NAME}" -GITHUB_PATH = f"{PROJECT_GITHUB_USERNAME}/{PROJECT_GITHUB_REPOSITORY}" -GITHUB_URL = f"https://github.com/{GITHUB_PATH}" - -DOWNLOAD_URL = f"{GITHUB_URL}/archive/{hass_const.__version__}.zip" -PROJECT_URLS = { - "Bug Reports": f"{GITHUB_URL}/issues", - "Dev Docs": "https://developers.home-assistant.io/", - "Discord": "https://discordapp.com/invite/c5DvZ4e", - "Forum": "https://community.home-assistant.io/", -} - PACKAGES = find_packages(exclude=["tests", "tests.*"]) setup( name=PROJECT_PACKAGE_NAME, - url=PROJECT_URL, - download_url=DOWNLOAD_URL, - project_urls=PROJECT_URLS, author=PROJECT_AUTHOR, author_email=PROJECT_EMAIL, packages=PACKAGES, From cf6b3fc81059c9ef426502ff64249a877c578999 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 28 Jan 2022 08:16:28 -0800 Subject: [PATCH 0050/1098] Add support for proxy-selected intent (#65094) --- .../components/google_assistant/smart_home.py | 9 ++++++ .../google_assistant/test_smart_home.py | 31 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/homeassistant/components/google_assistant/smart_home.py b/homeassistant/components/google_assistant/smart_home.py index 430169ea97db9b..31fd02544ff199 100644 --- a/homeassistant/components/google_assistant/smart_home.py +++ b/homeassistant/components/google_assistant/smart_home.py @@ -294,6 +294,15 @@ async def async_devices_reachable(hass, data: RequestData, payload): } +@HANDLERS.register("action.devices.PROXY_SELECTED") +async def async_devices_proxy_selected(hass, data: RequestData, payload): + """Handle action.devices.PROXY_SELECTED request. + + When selected for local SDK. + """ + return {} + + def turned_off_response(message): """Return a device turned off response.""" return { diff --git a/tests/components/google_assistant/test_smart_home.py b/tests/components/google_assistant/test_smart_home.py index 5ec43b37550f5f..3398fdca926d3e 100644 --- a/tests/components/google_assistant/test_smart_home.py +++ b/tests/components/google_assistant/test_smart_home.py @@ -1514,3 +1514,34 @@ async def test_query_recover(hass, caplog): } }, } + + +async def test_proxy_selected(hass, caplog): + """Test that we handle proxy selected.""" + + result = await sh.async_handle_message( + hass, + BASIC_CONFIG, + "test-agent", + { + "requestId": REQ_ID, + "inputs": [ + { + "intent": "action.devices.PROXY_SELECTED", + "payload": { + "device": { + "id": "abcdefg", + "customData": {}, + }, + "structureData": {}, + }, + } + ], + }, + const.SOURCE_LOCAL, + ) + + assert result == { + "requestId": REQ_ID, + "payload": {}, + } From efbbef5922269e74677babbc1bb70aa1cd952d95 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Fri, 28 Jan 2022 18:30:44 +0200 Subject: [PATCH 0051/1098] Fix Shelly detached switches automation triggers (#65059) --- homeassistant/components/shelly/utils.py | 15 +++-- .../components/shelly/test_device_trigger.py | 57 +++++++++++++------ 2 files changed, 51 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index a824488327b04c..a01b5de133a4bb 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -125,15 +125,22 @@ def get_block_channel_name(device: BlockDevice, block: Block | None) -> str: return f"{entity_name} channel {chr(int(block.channel)+base)}" -def is_block_momentary_input(settings: dict[str, Any], block: Block) -> bool: +def is_block_momentary_input( + settings: dict[str, Any], block: Block, include_detached: bool = False +) -> bool: """Return true if block input button settings is set to a momentary type.""" + momentary_types = ["momentary", "momentary_on_release"] + + if include_detached: + momentary_types.append("detached") + # Shelly Button type is fixed to momentary and no btn_type if settings["device"]["type"] in SHBTN_MODELS: return True if settings.get("mode") == "roller": button_type = settings["rollers"][0]["button_type"] - return button_type in ["momentary", "momentary_on_release"] + return button_type in momentary_types button = settings.get("relays") or settings.get("lights") or settings.get("inputs") if button is None: @@ -148,7 +155,7 @@ def is_block_momentary_input(settings: dict[str, Any], block: Block) -> bool: channel = min(int(block.channel or 0), len(button) - 1) button_type = button[channel].get("btn_type") - return button_type in ["momentary", "momentary_on_release"] + return button_type in momentary_types def get_device_uptime(uptime: float, last_uptime: datetime | None) -> datetime: @@ -171,7 +178,7 @@ def get_block_input_triggers( if "inputEvent" not in block.sensor_ids or "inputEventCnt" not in block.sensor_ids: return [] - if not is_block_momentary_input(device.settings, block): + if not is_block_momentary_input(device.settings, block, True): return [] triggers = [] diff --git a/tests/components/shelly/test_device_trigger.py b/tests/components/shelly/test_device_trigger.py index aee171eb46e910..0532fa5c82ca1d 100644 --- a/tests/components/shelly/test_device_trigger.py +++ b/tests/components/shelly/test_device_trigger.py @@ -29,25 +29,48 @@ ) -async def test_get_triggers_block_device(hass, coap_wrapper): +@pytest.mark.parametrize( + "button_type, is_valid", + [ + ("momentary", True), + ("momentary_on_release", True), + ("detached", True), + ("toggle", False), + ], +) +async def test_get_triggers_block_device( + hass, coap_wrapper, monkeypatch, button_type, is_valid +): """Test we get the expected triggers from a shelly block device.""" assert coap_wrapper - expected_triggers = [ - { - CONF_PLATFORM: "device", - CONF_DEVICE_ID: coap_wrapper.device_id, - CONF_DOMAIN: DOMAIN, - CONF_TYPE: "single", - CONF_SUBTYPE: "button1", - }, - { - CONF_PLATFORM: "device", - CONF_DEVICE_ID: coap_wrapper.device_id, - CONF_DOMAIN: DOMAIN, - CONF_TYPE: "long", - CONF_SUBTYPE: "button1", - }, - ] + + monkeypatch.setitem( + coap_wrapper.device.settings, + "relays", + [ + {"btn_type": button_type}, + {"btn_type": "toggle"}, + ], + ) + + expected_triggers = [] + if is_valid: + expected_triggers = [ + { + CONF_PLATFORM: "device", + CONF_DEVICE_ID: coap_wrapper.device_id, + CONF_DOMAIN: DOMAIN, + CONF_TYPE: "single", + CONF_SUBTYPE: "button1", + }, + { + CONF_PLATFORM: "device", + CONF_DEVICE_ID: coap_wrapper.device_id, + CONF_DOMAIN: DOMAIN, + CONF_TYPE: "long", + CONF_SUBTYPE: "button1", + }, + ] triggers = await async_get_device_automations( hass, DeviceAutomationType.TRIGGER, coap_wrapper.device_id From f369cef32f896d970668d5411f5977508028a620 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Fri, 28 Jan 2022 17:32:46 +0100 Subject: [PATCH 0052/1098] Handle FritzInternalError exception for Fritz (#65124) --- homeassistant/components/fritz/common.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index 37dfdc0e554b1a..b86a435e758f4d 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -14,6 +14,7 @@ FritzActionError, FritzActionFailedError, FritzConnectionException, + FritzInternalError, FritzLookUpError, FritzSecurityError, FritzServiceError, @@ -523,6 +524,7 @@ def _service_call_action( except ( FritzActionError, FritzActionFailedError, + FritzInternalError, FritzServiceError, FritzLookUpError, ): From da355438aa17f4e0dfe0d9ba8e5f3d25d099574b Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 28 Jan 2022 17:33:31 +0100 Subject: [PATCH 0053/1098] Reconnect client service tried to connect even if device didn't exist (#65082) --- homeassistant/components/unifi/services.py | 3 ++ tests/components/unifi/test_services.py | 38 +++++++++------------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/unifi/services.py b/homeassistant/components/unifi/services.py index c0498395a10a9d..fcde48528b3c83 100644 --- a/homeassistant/components/unifi/services.py +++ b/homeassistant/components/unifi/services.py @@ -57,6 +57,9 @@ async def async_reconnect_client(hass, data) -> None: device_registry = dr.async_get(hass) device_entry = device_registry.async_get(data[ATTR_DEVICE_ID]) + if device_entry is None: + return + mac = "" for connection in device_entry.connections: if connection[0] == CONNECTION_NETWORK_MAC: diff --git a/tests/components/unifi/test_services.py b/tests/components/unifi/test_services.py index b483e789f96e85..27e4ddea930345 100644 --- a/tests/components/unifi/test_services.py +++ b/tests/components/unifi/test_services.py @@ -77,15 +77,26 @@ async def test_reconnect_client(hass, aioclient_mock): assert aioclient_mock.call_count == 1 +async def test_reconnect_non_existant_device(hass, aioclient_mock): + """Verify no call is made if device does not exist.""" + await setup_unifi_integration(hass, aioclient_mock) + + aioclient_mock.clear_requests() + + await hass.services.async_call( + UNIFI_DOMAIN, + SERVICE_RECONNECT_CLIENT, + service_data={ATTR_DEVICE_ID: "device_entry.id"}, + blocking=True, + ) + assert aioclient_mock.call_count == 0 + + async def test_reconnect_device_without_mac(hass, aioclient_mock): """Verify no call is made if device does not have a known mac.""" config_entry = await setup_unifi_integration(hass, aioclient_mock) - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] aioclient_mock.clear_requests() - aioclient_mock.post( - f"https://{controller.host}:1234/api/s/{controller.site}/cmd/stamgr", - ) device_registry = await hass.helpers.device_registry.async_get_registry() device_entry = device_registry.async_get_or_create( @@ -139,12 +150,8 @@ async def test_reconnect_client_controller_unavailable(hass, aioclient_mock): async def test_reconnect_client_unknown_mac(hass, aioclient_mock): """Verify no call is made if trying to reconnect a mac unknown to controller.""" config_entry = await setup_unifi_integration(hass, aioclient_mock) - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] aioclient_mock.clear_requests() - aioclient_mock.post( - f"https://{controller.host}:1234/api/s/{controller.site}/cmd/stamgr", - ) device_registry = await hass.helpers.device_registry.async_get_registry() device_entry = device_registry.async_get_or_create( @@ -172,12 +179,8 @@ async def test_reconnect_wired_client(hass, aioclient_mock): config_entry = await setup_unifi_integration( hass, aioclient_mock, clients_response=clients ) - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] aioclient_mock.clear_requests() - aioclient_mock.post( - f"https://{controller.host}:1234/api/s/{controller.site}/cmd/stamgr", - ) device_registry = await hass.helpers.device_registry.async_get_registry() device_entry = device_registry.async_get_or_create( @@ -264,9 +267,6 @@ async def test_remove_clients_controller_unavailable(hass, aioclient_mock): controller.available = False aioclient_mock.clear_requests() - aioclient_mock.post( - f"https://{controller.host}:1234/api/s/{controller.site}/cmd/stamgr", - ) await hass.services.async_call(UNIFI_DOMAIN, SERVICE_REMOVE_CLIENTS, blocking=True) assert aioclient_mock.call_count == 0 @@ -281,15 +281,9 @@ async def test_remove_clients_no_call_on_empty_list(hass, aioclient_mock): "mac": "00:00:00:00:00:01", } ] - config_entry = await setup_unifi_integration( - hass, aioclient_mock, clients_all_response=clients - ) - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + await setup_unifi_integration(hass, aioclient_mock, clients_all_response=clients) aioclient_mock.clear_requests() - aioclient_mock.post( - f"https://{controller.host}:1234/api/s/{controller.site}/cmd/stamgr", - ) await hass.services.async_call(UNIFI_DOMAIN, SERVICE_REMOVE_CLIENTS, blocking=True) assert aioclient_mock.call_count == 0 From 1a878b40246fa1580f7e8b6082d132cc8158cefb Mon Sep 17 00:00:00 2001 From: Nenad Bogojevic Date: Fri, 28 Jan 2022 17:48:16 +0100 Subject: [PATCH 0054/1098] Use new withings oauth2 refresh token endpoint (#65134) --- homeassistant/components/withings/__init__.py | 4 +- homeassistant/components/withings/common.py | 43 +++++++++++++++++++ homeassistant/components/withings/sensor.py | 1 - tests/components/withings/common.py | 14 +++--- tests/components/withings/test_config_flow.py | 14 +++--- 5 files changed, 61 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/withings/__init__.py b/homeassistant/components/withings/__init__.py index 800f8b654bbd3d..701694e40e903a 100644 --- a/homeassistant/components/withings/__init__.py +++ b/homeassistant/components/withings/__init__.py @@ -9,7 +9,7 @@ from aiohttp.web import Request, Response import voluptuous as vol -from withings_api import WithingsAuth +from withings_api import AbstractWithingsApi, WithingsAuth from withings_api.common import NotifyAppli from homeassistant.components import webhook @@ -84,7 +84,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: conf[CONF_CLIENT_ID], conf[CONF_CLIENT_SECRET], f"{WithingsAuth.URL}/oauth2_user/authorize2", - f"{WithingsAuth.URL}/oauth2/token", + f"{AbstractWithingsApi.URL}/v2/oauth2", ), ) diff --git a/homeassistant/components/withings/common.py b/homeassistant/components/withings/common.py index 8da67a0b77aa7c..56f7f7cdf913cd 100644 --- a/homeassistant/components/withings/common.py +++ b/homeassistant/components/withings/common.py @@ -1111,3 +1111,46 @@ def redirect_uri(self) -> str: """Return the redirect uri.""" url = get_url(self.hass, allow_internal=False, prefer_cloud=True) return f"{url}{AUTH_CALLBACK_PATH}" + + async def _token_request(self, data: dict) -> dict: + """Make a token request and adapt Withings API reply.""" + new_token = await super()._token_request(data) + # Withings API returns habitual token data under json key "body": + # { + # "status": [{integer} Withings API response status], + # "body": { + # "access_token": [{string} Your new access_token], + # "expires_in": [{integer} Access token expiry delay in seconds], + # "token_type": [{string] HTTP Authorization Header format: Bearer], + # "scope": [{string} Scopes the user accepted], + # "refresh_token": [{string} Your new refresh_token], + # "userid": [{string} The Withings ID of the user] + # } + # } + # so we copy that to token root. + if body := new_token.pop("body", None): + new_token.update(body) + return new_token + + async def async_resolve_external_data(self, external_data: Any) -> dict: + """Resolve the authorization code to tokens.""" + return await self._token_request( + { + "action": "requesttoken", + "grant_type": "authorization_code", + "code": external_data["code"], + "redirect_uri": external_data["state"]["redirect_uri"], + } + ) + + async def _async_refresh_token(self, token: dict) -> dict: + """Refresh tokens.""" + new_token = await self._token_request( + { + "action": "requesttoken", + "grant_type": "refresh_token", + "client_id": self.client_id, + "refresh_token": token["refresh_token"], + } + ) + return {**token, **new_token} diff --git a/homeassistant/components/withings/sensor.py b/homeassistant/components/withings/sensor.py index 0ca40d28440d40..f8753739519676 100644 --- a/homeassistant/components/withings/sensor.py +++ b/homeassistant/components/withings/sensor.py @@ -15,7 +15,6 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the sensor config entry.""" - entities = await async_create_entities( hass, entry, diff --git a/tests/components/withings/common.py b/tests/components/withings/common.py index 71eb350410b931..b90a004ed0bf79 100644 --- a/tests/components/withings/common.py +++ b/tests/components/withings/common.py @@ -216,13 +216,15 @@ async def setup_profile(self, user_id: int) -> ConfigEntryWithingsApi: self._aioclient_mock.clear_requests() self._aioclient_mock.post( - "https://account.withings.com/oauth2/token", + "https://wbsapi.withings.net/v2/oauth2", json={ - "refresh_token": "mock-refresh-token", - "access_token": "mock-access-token", - "type": "Bearer", - "expires_in": 60, - "userid": profile_config.user_id, + "body": { + "refresh_token": "mock-refresh-token", + "access_token": "mock-access-token", + "type": "Bearer", + "expires_in": 60, + "userid": profile_config.user_id, + }, }, ) diff --git a/tests/components/withings/test_config_flow.py b/tests/components/withings/test_config_flow.py index 210d1f669e915b..2643ac18c24ece 100644 --- a/tests/components/withings/test_config_flow.py +++ b/tests/components/withings/test_config_flow.py @@ -90,13 +90,15 @@ async def test_config_reauth_profile( aioclient_mock.clear_requests() aioclient_mock.post( - "https://account.withings.com/oauth2/token", + "https://wbsapi.withings.net/v2/oauth2", json={ - "refresh_token": "mock-refresh-token", - "access_token": "mock-access-token", - "type": "Bearer", - "expires_in": 60, - "userid": "0", + "body": { + "refresh_token": "mock-refresh-token", + "access_token": "mock-access-token", + "type": "Bearer", + "expires_in": 60, + "userid": "0", + }, }, ) From d0d55db93640efdebea563ca1dc3a856451e860b Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Fri, 28 Jan 2022 18:00:47 +0100 Subject: [PATCH 0055/1098] Add diagnostics support to onewire (#65131) * Add diagnostics support to onewire * Add tests Co-authored-by: epenet --- .../components/onewire/diagnostics.py | 33 ++++++++++ tests/components/onewire/test_diagnostics.py | 61 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 homeassistant/components/onewire/diagnostics.py create mode 100644 tests/components/onewire/test_diagnostics.py diff --git a/homeassistant/components/onewire/diagnostics.py b/homeassistant/components/onewire/diagnostics.py new file mode 100644 index 00000000000000..a02ff2d8e474f7 --- /dev/null +++ b/homeassistant/components/onewire/diagnostics.py @@ -0,0 +1,33 @@ +"""Diagnostics support for 1-Wire.""" +from __future__ import annotations + +from dataclasses import asdict +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_HOST +from homeassistant.core import HomeAssistant + +from .const import DOMAIN +from .onewirehub import OneWireHub + +TO_REDACT = {CONF_HOST} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + onewirehub: OneWireHub = hass.data[DOMAIN][entry.entry_id] + + return { + "entry": { + "title": entry.title, + "data": async_redact_data(entry.data, TO_REDACT), + "options": {**entry.options}, + }, + "devices": [asdict(device_details) for device_details in onewirehub.devices] + if onewirehub.devices + else [], + } diff --git a/tests/components/onewire/test_diagnostics.py b/tests/components/onewire/test_diagnostics.py new file mode 100644 index 00000000000000..bc164a9b1387d3 --- /dev/null +++ b/tests/components/onewire/test_diagnostics.py @@ -0,0 +1,61 @@ +"""Test 1-Wire diagnostics.""" +from unittest.mock import MagicMock, patch + +import pytest + +from homeassistant.components.diagnostics import REDACTED +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant + +from . import setup_owproxy_mock_devices + +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +@pytest.fixture(autouse=True) +def override_platforms(): + """Override PLATFORMS.""" + with patch("homeassistant.components.onewire.PLATFORMS", [Platform.SWITCH]): + yield + + +DEVICE_DETAILS = { + "device_info": { + "identifiers": [["onewire", "EF.111111111113"]], + "manufacturer": "Hobby Boards", + "model": "HB_HUB", + "name": "EF.111111111113", + }, + "family": "EF", + "id": "EF.111111111113", + "path": "/EF.111111111113/", + "type": "HB_HUB", +} + + +@pytest.mark.parametrize("device_id", ["EF.111111111113"], indirect=True) +async def test_entry_diagnostics( + hass: HomeAssistant, + config_entry: ConfigEntry, + hass_client, + owproxy: MagicMock, + device_id: str, +): + """Test config entry diagnostics.""" + setup_owproxy_mock_devices(owproxy, Platform.SENSOR, [device_id]) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { + "entry": { + "data": { + "host": REDACTED, + "port": 1234, + "type": "OWServer", + }, + "options": {}, + "title": "Mock Title", + }, + "devices": [DEVICE_DETAILS], + } From 0c9be604c237cee34e1005d1708d421bfd33fe4c Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Fri, 28 Jan 2022 09:07:41 -0800 Subject: [PATCH 0056/1098] Add diagnostics for rtsp_to_webrtc (#65138) --- .../components/rtsp_to_webrtc/diagnostics.py | 17 +++ tests/components/rtsp_to_webrtc/conftest.py | 98 +++++++++++++++ .../rtsp_to_webrtc/test_diagnostics.py | 27 ++++ tests/components/rtsp_to_webrtc/test_init.py | 118 ++++-------------- 4 files changed, 168 insertions(+), 92 deletions(-) create mode 100644 homeassistant/components/rtsp_to_webrtc/diagnostics.py create mode 100644 tests/components/rtsp_to_webrtc/conftest.py create mode 100644 tests/components/rtsp_to_webrtc/test_diagnostics.py diff --git a/homeassistant/components/rtsp_to_webrtc/diagnostics.py b/homeassistant/components/rtsp_to_webrtc/diagnostics.py new file mode 100644 index 00000000000000..ab13e0a64ee770 --- /dev/null +++ b/homeassistant/components/rtsp_to_webrtc/diagnostics.py @@ -0,0 +1,17 @@ +"""Diagnostics support for Nest.""" + +from __future__ import annotations + +from typing import Any + +from rtsp_to_webrtc import client + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + return dict(client.get_diagnostics()) diff --git a/tests/components/rtsp_to_webrtc/conftest.py b/tests/components/rtsp_to_webrtc/conftest.py new file mode 100644 index 00000000000000..7148896e45401a --- /dev/null +++ b/tests/components/rtsp_to_webrtc/conftest.py @@ -0,0 +1,98 @@ +"""Tests for RTSPtoWebRTC inititalization.""" + +from __future__ import annotations + +from collections.abc import AsyncGenerator, Awaitable, Callable, Generator +from typing import Any, TypeVar +from unittest.mock import patch + +import pytest +import rtsp_to_webrtc + +from homeassistant.components import camera +from homeassistant.components.rtsp_to_webrtc import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from tests.common import MockConfigEntry + +STREAM_SOURCE = "rtsp://example.com" +SERVER_URL = "http://127.0.0.1:8083" + +CONFIG_ENTRY_DATA = {"server_url": SERVER_URL} + +# Typing helpers +ComponentSetup = Callable[[], Awaitable[None]] +T = TypeVar("T") +YieldFixture = Generator[T, None, None] + + +@pytest.fixture(autouse=True) +async def webrtc_server() -> None: + """Patch client library to force usage of RTSPtoWebRTC server.""" + with patch( + "rtsp_to_webrtc.client.WebClient.heartbeat", + side_effect=rtsp_to_webrtc.exceptions.ResponseError(), + ): + yield + + +@pytest.fixture +async def mock_camera(hass) -> AsyncGenerator[None, None]: + """Initialize a demo camera platform.""" + assert await async_setup_component( + hass, "camera", {camera.DOMAIN: {"platform": "demo"}} + ) + await hass.async_block_till_done() + with patch( + "homeassistant.components.demo.camera.Path.read_bytes", + return_value=b"Test", + ), patch( + "homeassistant.components.camera.Camera.stream_source", + return_value=STREAM_SOURCE, + ), patch( + "homeassistant.components.camera.Camera.supported_features", + return_value=camera.SUPPORT_STREAM, + ): + yield + + +@pytest.fixture +async def config_entry_data() -> dict[str, Any]: + """Fixture for MockConfigEntry data.""" + return CONFIG_ENTRY_DATA + + +@pytest.fixture +async def config_entry(config_entry_data: dict[str, Any]) -> MockConfigEntry: + """Fixture for MockConfigEntry.""" + return MockConfigEntry(domain=DOMAIN, data=config_entry_data) + + +@pytest.fixture +async def rtsp_to_webrtc_client() -> None: + """Fixture for mock rtsp_to_webrtc client.""" + with patch("rtsp_to_webrtc.client.Client.heartbeat"): + yield + + +@pytest.fixture +async def setup_integration( + hass: HomeAssistant, config_entry: MockConfigEntry +) -> YieldFixture[ComponentSetup]: + """Fixture for setting up the component.""" + config_entry.add_to_hass(hass) + + async def func() -> None: + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + + yield func + entries = hass.config_entries.async_entries(DOMAIN) + assert len(entries) == 1 + await hass.config_entries.async_unload(entries[0].entry_id) + await hass.async_block_till_done() + + assert not hass.data.get(DOMAIN) + assert entries[0].state is ConfigEntryState.NOT_LOADED diff --git a/tests/components/rtsp_to_webrtc/test_diagnostics.py b/tests/components/rtsp_to_webrtc/test_diagnostics.py new file mode 100644 index 00000000000000..27b801a71ed41a --- /dev/null +++ b/tests/components/rtsp_to_webrtc/test_diagnostics.py @@ -0,0 +1,27 @@ +"""Test nest diagnostics.""" + +from typing import Any + +from .conftest import ComponentSetup + +from tests.common import MockConfigEntry +from tests.components.diagnostics import get_diagnostics_for_config_entry + +THERMOSTAT_TYPE = "sdm.devices.types.THERMOSTAT" + + +async def test_entry_diagnostics( + hass, + hass_client, + config_entry: MockConfigEntry, + rtsp_to_webrtc_client: Any, + setup_integration: ComponentSetup, +): + """Test config entry diagnostics.""" + await setup_integration() + + assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { + "discovery": {"attempt": 1, "web.failure": 1, "webrtc.success": 1}, + "web": {}, + "webrtc": {}, + } diff --git a/tests/components/rtsp_to_webrtc/test_init.py b/tests/components/rtsp_to_webrtc/test_init.py index 94ec3529836d8e..759fea7c813141 100644 --- a/tests/components/rtsp_to_webrtc/test_init.py +++ b/tests/components/rtsp_to_webrtc/test_init.py @@ -3,7 +3,7 @@ from __future__ import annotations import base64 -from collections.abc import AsyncGenerator, Awaitable, Callable +from collections.abc import Awaitable, Callable from typing import Any from unittest.mock import patch @@ -11,147 +11,84 @@ import pytest import rtsp_to_webrtc -from homeassistant.components import camera from homeassistant.components.rtsp_to_webrtc import DOMAIN from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant -from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry +from .conftest import SERVER_URL, STREAM_SOURCE, ComponentSetup + from tests.test_util.aiohttp import AiohttpClientMocker -STREAM_SOURCE = "rtsp://example.com" # The webrtc component does not inspect the details of the offer and answer, # and is only a pass through. OFFER_SDP = "v=0\r\no=carol 28908764872 28908764872 IN IP4 100.3.6.6\r\n..." ANSWER_SDP = "v=0\r\no=bob 2890844730 2890844730 IN IP4 host.example.com\r\n..." -SERVER_URL = "http://127.0.0.1:8083" - -CONFIG_ENTRY_DATA = {"server_url": SERVER_URL} - - -@pytest.fixture(autouse=True) -async def webrtc_server() -> None: - """Patch client library to force usage of RTSPtoWebRTC server.""" - with patch( - "rtsp_to_webrtc.client.WebClient.heartbeat", - side_effect=rtsp_to_webrtc.exceptions.ResponseError(), - ): - yield - - -@pytest.fixture -async def mock_camera(hass) -> AsyncGenerator[None, None]: - """Initialize a demo camera platform.""" - assert await async_setup_component( - hass, "camera", {camera.DOMAIN: {"platform": "demo"}} - ) - await hass.async_block_till_done() - with patch( - "homeassistant.components.demo.camera.Path.read_bytes", - return_value=b"Test", - ), patch( - "homeassistant.components.camera.Camera.stream_source", - return_value=STREAM_SOURCE, - ), patch( - "homeassistant.components.camera.Camera.supported_features", - return_value=camera.SUPPORT_STREAM, - ): - yield - - -async def async_setup_rtsp_to_webrtc(hass: HomeAssistant) -> None: - """Set up the component.""" - return await async_setup_component(hass, DOMAIN, {}) - -async def test_setup_success(hass: HomeAssistant) -> None: +async def test_setup_success( + hass: HomeAssistant, rtsp_to_webrtc_client: Any, setup_integration: ComponentSetup +) -> None: """Test successful setup and unload.""" - config_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG_ENTRY_DATA) - config_entry.add_to_hass(hass) - - with patch("rtsp_to_webrtc.client.Client.heartbeat"): - assert await async_setup_rtsp_to_webrtc(hass) - await hass.async_block_till_done() + await setup_integration() entries = hass.config_entries.async_entries(DOMAIN) assert len(entries) == 1 assert entries[0].state is ConfigEntryState.LOADED - await hass.config_entries.async_unload(config_entry.entry_id) - await hass.async_block_till_done() - - assert not hass.data.get(DOMAIN) - assert config_entry.state is ConfigEntryState.NOT_LOADED - -async def test_invalid_config_entry(hass: HomeAssistant) -> None: +@pytest.mark.parametrize("config_entry_data", [{}]) +async def test_invalid_config_entry( + hass: HomeAssistant, rtsp_to_webrtc_client: Any, setup_integration: ComponentSetup +) -> None: """Test a config entry with missing required fields.""" - config_entry = MockConfigEntry(domain=DOMAIN, data={}) - config_entry.add_to_hass(hass) - - assert await async_setup_rtsp_to_webrtc(hass) + await setup_integration() entries = hass.config_entries.async_entries(DOMAIN) assert len(entries) == 1 assert entries[0].state is ConfigEntryState.SETUP_ERROR -async def test_setup_server_failure(hass: HomeAssistant) -> None: +async def test_setup_server_failure( + hass: HomeAssistant, setup_integration: ComponentSetup +) -> None: """Test server responds with a failure on startup.""" - config_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG_ENTRY_DATA) - config_entry.add_to_hass(hass) - with patch( "rtsp_to_webrtc.client.Client.heartbeat", side_effect=rtsp_to_webrtc.exceptions.ResponseError(), ): - assert await async_setup_rtsp_to_webrtc(hass) - await hass.async_block_till_done() + await setup_integration() entries = hass.config_entries.async_entries(DOMAIN) assert len(entries) == 1 assert entries[0].state is ConfigEntryState.SETUP_RETRY - await hass.config_entries.async_unload(config_entry.entry_id) - await hass.async_block_till_done() - -async def test_setup_communication_failure(hass: HomeAssistant) -> None: +async def test_setup_communication_failure( + hass: HomeAssistant, setup_integration: ComponentSetup +) -> None: """Test unable to talk to server on startup.""" - config_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG_ENTRY_DATA) - config_entry.add_to_hass(hass) - with patch( "rtsp_to_webrtc.client.Client.heartbeat", side_effect=rtsp_to_webrtc.exceptions.ClientError(), ): - assert await async_setup_rtsp_to_webrtc(hass) - await hass.async_block_till_done() + await setup_integration() entries = hass.config_entries.async_entries(DOMAIN) assert len(entries) == 1 assert entries[0].state is ConfigEntryState.SETUP_RETRY - await hass.config_entries.async_unload(config_entry.entry_id) - await hass.async_block_till_done() - async def test_offer_for_stream_source( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, hass_ws_client: Callable[[...], Awaitable[aiohttp.ClientWebSocketResponse]], mock_camera: Any, + rtsp_to_webrtc_client: Any, + setup_integration: ComponentSetup, ) -> None: """Test successful response from RTSPtoWebRTC server.""" - config_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG_ENTRY_DATA) - config_entry.add_to_hass(hass) - - with patch("rtsp_to_webrtc.client.Client.heartbeat"): - assert await async_setup_rtsp_to_webrtc(hass) - await hass.async_block_till_done() + await setup_integration() aioclient_mock.post( f"{SERVER_URL}/stream", @@ -188,14 +125,11 @@ async def test_offer_failure( aioclient_mock: AiohttpClientMocker, hass_ws_client: Callable[[...], Awaitable[aiohttp.ClientWebSocketResponse]], mock_camera: Any, + rtsp_to_webrtc_client: Any, + setup_integration: ComponentSetup, ) -> None: """Test a transient failure talking to RTSPtoWebRTC server.""" - config_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG_ENTRY_DATA) - config_entry.add_to_hass(hass) - - with patch("rtsp_to_webrtc.client.Client.heartbeat"): - assert await async_setup_rtsp_to_webrtc(hass) - await hass.async_block_till_done() + await setup_integration() aioclient_mock.post( f"{SERVER_URL}/stream", From 7d949a766511240df610c7a6c88ad32146e2461b Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 28 Jan 2022 19:46:17 +0100 Subject: [PATCH 0057/1098] Add dedicated pre-commit hook for mypy_config [hassfest] (#65092) --- .pre-commit-config.yaml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 87c7d9e9102c7c..952729caf09afd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -114,11 +114,18 @@ repos: pass_filenames: false language: script types: [text] - files: ^(homeassistant/.+/(manifest|strings)\.json|\.coveragerc|\.strict-typing|homeassistant/.+/services\.yaml|script/hassfest/.+\.py)$ + files: ^(homeassistant/.+/(manifest|strings)\.json|\.coveragerc|homeassistant/.+/services\.yaml|script/hassfest/(?!metadata|mypy_config).+\.py)$ - id: hassfest-metadata name: hassfest-metadata entry: script/run-in-env.sh python3 -m script.hassfest -p metadata pass_filenames: false language: script types: [text] - files: ^(script/hassfest/.+\.py|homeassistant/const\.py$|setup\.cfg)$ + files: ^(script/hassfest/metadata\.py|homeassistant/const\.py$|setup\.cfg)$ + - id: hassfest-mypy-config + name: hassfest-mypy-config + entry: script/run-in-env.sh python3 -m script.hassfest -p mypy_config + pass_filenames: false + language: script + types: [text] + files: ^(script/hassfest/mypy_config\.py|\.strict-typing|mypy\.ini)$ From bf910229b6327d47ecd64152659acc9ca61cd51a Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Fri, 28 Jan 2022 20:46:57 +0000 Subject: [PATCH 0058/1098] Add test: warn entity_category assigned as str (#65142) --- tests/helpers/test_entity_registry.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index f299177a08ecf6..5f49889b6c6af7 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -9,6 +9,7 @@ from homeassistant.core import CoreState, callback, valid_entity_id from homeassistant.exceptions import MaxLengthExceeded from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers.entity import EntityCategory from tests.common import ( MockConfigEntry, @@ -77,7 +78,7 @@ def test_get_or_create_updates_data(registry): config_entry=orig_config_entry, device_id="mock-dev-id", disabled_by=er.RegistryEntryDisabler.HASS, - entity_category="config", + entity_category=EntityCategory.CONFIG, original_device_class="mock-device-class", original_icon="initial-original_icon", original_name="initial-original_name", @@ -95,7 +96,7 @@ def test_get_or_create_updates_data(registry): device_class=None, device_id="mock-dev-id", disabled_by=er.RegistryEntryDisabler.HASS, - entity_category="config", + entity_category=EntityCategory.CONFIG, icon=None, id=orig_entry.id, name=None, @@ -135,7 +136,7 @@ def test_get_or_create_updates_data(registry): device_class=None, device_id="new-mock-dev-id", disabled_by=er.RegistryEntryDisabler.HASS, # Should not be updated - entity_category="config", + entity_category=EntityCategory.CONFIG, icon=None, id=orig_entry.id, name=None, @@ -189,7 +190,7 @@ async def test_loading_saving_data(hass, registry): config_entry=mock_config, device_id="mock-dev-id", disabled_by=er.RegistryEntryDisabler.HASS, - entity_category="config", + entity_category=EntityCategory.CONFIG, original_device_class="mock-device-class", original_icon="hass:original-icon", original_name="Original Name", @@ -1124,3 +1125,16 @@ async def test_deprecated_disabled_by_str(hass, registry, caplog): assert entry.disabled_by is er.RegistryEntryDisabler.USER assert " str for entity registry disabled_by. This is deprecated " in caplog.text + + +async def test_deprecated_entity_category_str(hass, registry, caplog): + """Test deprecated str use of entity_category converts to enum and logs a warning.""" + entry = er.RegistryEntry( + "light", + "hue", + "5678", + entity_category="diagnostic", + ) + + assert entry.entity_category is EntityCategory.DIAGNOSTIC + assert " should be updated to use EntityCategory" in caplog.text From 956ceb6c684ae16334395a6fc6e5488ea75313fa Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Fri, 28 Jan 2022 12:50:38 -0800 Subject: [PATCH 0059/1098] Update nest diagnostics (#65141) --- homeassistant/components/nest/diagnostics.py | 17 +++++------------ tests/components/nest/conftest.py | 9 +++++++++ tests/components/nest/test_diagnostics.py | 20 ++++++++++++++------ 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/nest/diagnostics.py b/homeassistant/components/nest/diagnostics.py index b60889358fd6ce..0b6cfff6bae00f 100644 --- a/homeassistant/components/nest/diagnostics.py +++ b/homeassistant/components/nest/diagnostics.py @@ -4,6 +4,7 @@ from typing import Any +from google_nest_sdm import diagnostics from google_nest_sdm.device import Device from google_nest_sdm.device_traits import InfoTrait from google_nest_sdm.exceptions import ApiException @@ -30,22 +31,14 @@ async def async_get_config_entry_diagnostics( return {"error": str(err)} return { + **diagnostics.get_diagnostics(), "devices": [ get_device_data(device) for device in device_manager.devices.values() - ] + ], } def get_device_data(device: Device) -> dict[str, Any]: """Return diagnostic information about a device.""" - # Return a simplified view of the API object, but skipping any id fields or - # traits that include unique identifiers or personally identifiable information. - # See https://developers.google.com/nest/device-access/traits for API details - return { - "type": device.type, - "traits": { - trait: data - for trait, data in device.raw_data.get("traits", {}).items() - if trait not in REDACT_DEVICE_TRAITS - }, - } + # Library performs its own redaction for device data + return device.get_diagnostics() diff --git a/tests/components/nest/conftest.py b/tests/components/nest/conftest.py index b13dbf662b9690..9b060d38fbed7e 100644 --- a/tests/components/nest/conftest.py +++ b/tests/components/nest/conftest.py @@ -1,6 +1,7 @@ """Common libraries for test setup.""" from __future__ import annotations +from collections.abc import Generator import copy import shutil from typing import Any @@ -8,6 +9,7 @@ import uuid import aiohttp +from google_nest_sdm import diagnostics from google_nest_sdm.auth import AbstractAuth from google_nest_sdm.device_manager import DeviceManager import pytest @@ -234,3 +236,10 @@ async def setup_platform( ) -> PlatformSetup: """Fixture to setup the integration platform and subscriber.""" return setup_base_platform + + +@pytest.fixture(autouse=True) +def reset_diagnostics() -> Generator[None, None, None]: + """Fixture to reset client library diagnostic counters.""" + yield + diagnostics.reset() diff --git a/tests/components/nest/test_diagnostics.py b/tests/components/nest/test_diagnostics.py index 09930c18501a83..b603019da815c8 100644 --- a/tests/components/nest/test_diagnostics.py +++ b/tests/components/nest/test_diagnostics.py @@ -56,13 +56,21 @@ async def test_entry_diagnostics(hass, hass_client): assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { "devices": [ { - "traits": { - "sdm.devices.traits.Humidity": {"ambientHumidityPercent": 35.0}, - "sdm.devices.traits.Temperature": { - "ambientTemperatureCelsius": 25.1 + "data": { + "assignee": "**REDACTED**", + "name": "**REDACTED**", + "parentRelations": [ + {"displayName": "**REDACTED**", "parent": "**REDACTED**"} + ], + "traits": { + "sdm.devices.traits.Info": {"customName": "**REDACTED**"}, + "sdm.devices.traits.Humidity": {"ambientHumidityPercent": 35.0}, + "sdm.devices.traits.Temperature": { + "ambientTemperatureCelsius": 25.1 + }, }, - }, - "type": "sdm.devices.types.THERMOSTAT", + "type": "sdm.devices.types.THERMOSTAT", + } } ], } From 5b755b74fb04c9fe839d40498df3200cccf63919 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 28 Jan 2022 15:37:53 -0600 Subject: [PATCH 0060/1098] Add loggers to integration manifest.json (#65083) --- homeassistant/components/abode/manifest.json | 3 ++- .../components/accuweather/manifest.json | 3 ++- homeassistant/components/acmeda/manifest.json | 3 ++- homeassistant/components/adax/manifest.json | 3 ++- .../components/adguard/manifest.json | 3 ++- homeassistant/components/ads/manifest.json | 3 ++- .../components/advantage_air/manifest.json | 3 ++- homeassistant/components/aemet/manifest.json | 3 ++- .../components/aftership/manifest.json | 2 +- .../components/agent_dvr/manifest.json | 3 ++- homeassistant/components/airly/manifest.json | 3 ++- homeassistant/components/airnow/manifest.json | 3 ++- .../components/airthings/manifest.json | 3 ++- .../components/airtouch4/manifest.json | 3 ++- .../components/airvisual/manifest.json | 3 ++- .../components/aladdin_connect/manifest.json | 3 ++- .../components/alarmdecoder/manifest.json | 3 ++- homeassistant/components/almond/manifest.json | 3 ++- .../components/alpha_vantage/manifest.json | 3 ++- .../components/amazon_polly/manifest.json | 3 ++- .../components/amberelectric/manifest.json | 3 ++- .../components/ambiclimate/manifest.json | 3 ++- .../components/ambient_station/manifest.json | 3 ++- .../components/amcrest/manifest.json | 3 ++- homeassistant/components/ampio/manifest.json | 3 ++- .../components/androidtv/manifest.json | 3 ++- .../components/anel_pwrctrl/manifest.json | 3 ++- .../components/anthemav/manifest.json | 3 ++- .../components/apache_kafka/manifest.json | 3 ++- .../components/apcupsd/manifest.json | 3 ++- homeassistant/components/apns/manifest.json | 3 ++- .../components/apple_tv/manifest.json | 3 ++- .../components/apprise/manifest.json | 3 ++- homeassistant/components/aprs/manifest.json | 3 ++- .../components/aqualogic/manifest.json | 3 ++- .../components/aquostv/manifest.json | 3 ++- .../components/arcam_fmj/manifest.json | 3 ++- homeassistant/components/arlo/manifest.json | 3 ++- .../components/arris_tg2492lg/manifest.json | 3 ++- homeassistant/components/aruba/manifest.json | 3 ++- .../components/aseko_pool_live/manifest.json | 3 ++- .../components/asterisk_mbox/manifest.json | 3 ++- .../components/asuswrt/manifest.json | 3 ++- homeassistant/components/atag/manifest.json | 3 ++- homeassistant/components/atome/manifest.json | 3 ++- homeassistant/components/august/manifest.json | 3 ++- homeassistant/components/aurora/manifest.json | 3 ++- .../aurora_abb_powerone/manifest.json | 3 ++- .../components/aussie_broadband/manifest.json | 3 ++- homeassistant/components/avea/manifest.json | 3 ++- homeassistant/components/awair/manifest.json | 3 ++- homeassistant/components/aws/manifest.json | 3 ++- homeassistant/components/axis/manifest.json | 3 ++- .../components/azure_devops/manifest.json | 3 ++- .../components/azure_event_hub/manifest.json | 3 ++- .../azure_service_bus/manifest.json | 3 ++- homeassistant/components/baidu/manifest.json | 3 ++- homeassistant/components/balboa/manifest.json | 3 ++- .../components/bbb_gpio/manifest.json | 3 ++- homeassistant/components/bbox/manifest.json | 3 ++- .../components/beewi_smartclim/manifest.json | 3 ++- homeassistant/components/bh1750/manifest.json | 3 ++- .../components/bitcoin/manifest.json | 3 ++- .../components/bizkaibus/manifest.json | 3 ++- .../components/blackbird/manifest.json | 3 ++- homeassistant/components/blebox/manifest.json | 3 ++- homeassistant/components/blink/manifest.json | 3 ++- .../components/blinksticklight/manifest.json | 3 ++- .../components/blockchain/manifest.json | 3 ++- .../bluetooth_le_tracker/manifest.json | 3 ++- .../bluetooth_tracker/manifest.json | 3 ++- homeassistant/components/bme280/manifest.json | 3 ++- homeassistant/components/bme680/manifest.json | 3 ++- .../bmw_connected_drive/manifest.json | 3 ++- homeassistant/components/bond/manifest.json | 3 ++- .../components/bosch_shc/manifest.json | 3 ++- .../components/braviatv/manifest.json | 3 ++- .../components/broadlink/manifest.json | 3 ++- .../components/brother/manifest.json | 3 ++- .../brottsplatskartan/manifest.json | 3 ++- homeassistant/components/brunt/manifest.json | 3 ++- homeassistant/components/bsblan/manifest.json | 3 ++- .../components/bt_home_hub_5/manifest.json | 3 ++- .../components/bt_smarthub/manifest.json | 3 ++- .../components/buienradar/manifest.json | 3 ++- homeassistant/components/caldav/manifest.json | 3 ++- homeassistant/components/canary/manifest.json | 3 ++- homeassistant/components/cast/manifest.json | 3 ++- .../components/channels/manifest.json | 3 ++- .../components/circuit/manifest.json | 3 ++- .../components/cisco_ios/manifest.json | 3 ++- .../cisco_mobility_express/manifest.json | 3 ++- .../cisco_webex_teams/manifest.json | 3 ++- .../components/clementine/manifest.json | 3 ++- .../components/climacell/manifest.json | 3 ++- homeassistant/components/cloud/manifest.json | 3 ++- .../components/cloudflare/manifest.json | 3 ++- homeassistant/components/cmus/manifest.json | 3 ++- .../components/co2signal/manifest.json | 3 ++- .../components/coinbase/manifest.json | 3 ++- .../components/comfoconnect/manifest.json | 3 ++- .../components/concord232/manifest.json | 3 ++- .../components/control4/manifest.json | 3 ++- .../components/coolmaster/manifest.json | 3 ++- .../components/coronavirus/manifest.json | 3 ++- .../components/crownstone/manifest.json | 3 ++- homeassistant/components/daikin/manifest.json | 3 ++- .../components/danfoss_air/manifest.json | 3 ++- .../components/darksky/manifest.json | 3 ++- .../components/datadog/manifest.json | 3 ++- homeassistant/components/deconz/manifest.json | 3 ++- homeassistant/components/decora/manifest.json | 3 ++- .../components/decora_wifi/manifest.json | 3 ++- homeassistant/components/delijn/manifest.json | 3 ++- homeassistant/components/deluge/manifest.json | 3 ++- .../components/denonavr/manifest.json | 3 ++- .../components/deutsche_bahn/manifest.json | 3 ++- .../devolo_home_control/manifest.json | 3 ++- .../devolo_home_network/manifest.json | 3 ++- homeassistant/components/dexcom/manifest.json | 3 ++- homeassistant/components/dhcp/manifest.json | 3 ++- .../components/digital_ocean/manifest.json | 3 ++- .../components/digitalloggers/manifest.json | 3 ++- .../components/directv/manifest.json | 3 ++- .../components/discogs/manifest.json | 3 ++- .../components/discord/manifest.json | 3 ++- .../components/discovery/manifest.json | 3 ++- .../components/dlib_face_detect/manifest.json | 3 ++- .../dlib_face_identify/manifest.json | 3 ++- homeassistant/components/dlink/manifest.json | 3 ++- .../components/dlna_dmr/manifest.json | 3 ++- .../components/dominos/manifest.json | 3 ++- homeassistant/components/doods/manifest.json | 3 ++- .../components/doorbird/manifest.json | 3 ++- homeassistant/components/dsmr/manifest.json | 3 ++- homeassistant/components/dunehd/manifest.json | 3 ++- .../dwd_weather_warnings/manifest.json | 3 ++- homeassistant/components/dweet/manifest.json | 3 ++- .../components/dynalite/manifest.json | 3 ++- homeassistant/components/eafm/manifest.json | 3 ++- homeassistant/components/ebox/manifest.json | 3 ++- homeassistant/components/ebusd/manifest.json | 3 ++- .../components/ecoal_boiler/manifest.json | 3 ++- homeassistant/components/ecobee/manifest.json | 3 ++- homeassistant/components/econet/manifest.json | 3 ++- .../components/ecovacs/manifest.json | 3 ++- .../eddystone_temperature/manifest.json | 3 ++- homeassistant/components/edimax/manifest.json | 3 ++- homeassistant/components/edl21/manifest.json | 3 ++- homeassistant/components/efergy/manifest.json | 3 ++- .../components/egardia/manifest.json | 3 ++- .../components/eight_sleep/manifest.json | 3 ++- homeassistant/components/elkm1/manifest.json | 3 ++- homeassistant/components/elmax/manifest.json | 3 ++- homeassistant/components/elv/manifest.json | 3 ++- homeassistant/components/emby/manifest.json | 3 ++- .../components/emonitor/manifest.json | 3 ++- .../components/emulated_kasa/manifest.json | 3 ++- .../components/emulated_roku/manifest.json | 3 ++- .../components/enigma2/manifest.json | 3 ++- .../components/enocean/manifest.json | 3 ++- .../components/enphase_envoy/manifest.json | 3 ++- .../entur_public_transport/manifest.json | 3 ++- .../environment_canada/manifest.json | 3 ++- .../components/envisalink/manifest.json | 3 ++- .../components/ephember/manifest.json | 3 ++- homeassistant/components/epson/manifest.json | 3 ++- .../components/epsonworkforce/manifest.json | 3 ++- .../components/eq3btsmart/manifest.json | 3 ++- .../components/esphome/manifest.json | 3 ++- .../components/etherscan/manifest.json | 3 ++- homeassistant/components/eufy/manifest.json | 3 ++- .../components/everlights/manifest.json | 3 ++- .../components/evohome/manifest.json | 3 ++- homeassistant/components/ezviz/manifest.json | 3 ++- .../components/faa_delays/manifest.json | 3 ++- .../components/familyhub/manifest.json | 3 ++- .../components/fastdotcom/manifest.json | 3 ++- .../components/feedreader/manifest.json | 3 ++- homeassistant/components/fibaro/manifest.json | 3 ++- homeassistant/components/fido/manifest.json | 3 ++- homeassistant/components/fints/manifest.json | 3 ++- .../components/fireservicerota/manifest.json | 3 ++- .../components/firmata/manifest.json | 3 ++- homeassistant/components/fitbit/manifest.json | 3 ++- homeassistant/components/fixer/manifest.json | 3 ++- .../components/fjaraskupan/manifest.json | 3 ++- .../components/fleetgo/manifest.json | 3 ++- homeassistant/components/flic/manifest.json | 3 ++- .../components/flick_electric/manifest.json | 3 ++- homeassistant/components/flipr/manifest.json | 3 ++- homeassistant/components/flo/manifest.json | 3 ++- homeassistant/components/flume/manifest.json | 3 ++- .../components/flunearyou/manifest.json | 3 ++- .../components/flux_led/manifest.json | 3 ++- .../components/folder_watcher/manifest.json | 3 ++- homeassistant/components/foobot/manifest.json | 3 ++- .../components/forked_daapd/manifest.json | 3 ++- .../components/fortios/manifest.json | 3 ++- homeassistant/components/foscam/manifest.json | 3 ++- .../components/free_mobile/manifest.json | 3 ++- .../components/freebox/manifest.json | 3 ++- .../components/freedompro/manifest.json | 3 ++- homeassistant/components/fritz/manifest.json | 3 ++- .../components/fritzbox/manifest.json | 3 ++- .../fritzbox_callmonitor/manifest.json | 3 ++- .../components/fronius/manifest.json | 3 ++- .../components/geniushub/manifest.json | 3 ++- .../components/geo_json_events/manifest.json | 3 ++- .../components/geo_rss_events/manifest.json | 3 ++- .../components/geonetnz_quakes/manifest.json | 3 ++- .../components/geonetnz_volcano/manifest.json | 3 ++- homeassistant/components/gios/manifest.json | 3 ++- homeassistant/components/github/manifest.json | 3 ++- .../components/gitlab_ci/manifest.json | 3 ++- homeassistant/components/gitter/manifest.json | 3 ++- .../components/glances/manifest.json | 3 ++- homeassistant/components/gntp/manifest.json | 3 ++- .../components/goalfeed/manifest.json | 3 ++- .../components/goalzero/manifest.json | 3 ++- .../components/gogogate2/manifest.json | 3 ++- homeassistant/components/goodwe/manifest.json | 3 ++- homeassistant/components/google/manifest.json | 3 ++- .../components/google_maps/manifest.json | 3 ++- .../components/google_translate/manifest.json | 3 ++- .../google_travel_time/manifest.json | 3 ++- homeassistant/components/gpsd/manifest.json | 3 ++- homeassistant/components/gree/manifest.json | 3 ++- .../components/greeneye_monitor/manifest.json | 3 ++- .../components/greenwave/manifest.json | 3 ++- .../components/growatt_server/manifest.json | 3 ++- .../components/gstreamer/manifest.json | 3 ++- homeassistant/components/gtfs/manifest.json | 3 ++- .../components/guardian/manifest.json | 3 ++- .../components/habitica/manifest.json | 3 ++- .../components/hangouts/manifest.json | 3 ++- .../harman_kardon_avr/manifest.json | 3 ++- .../components/harmony/manifest.json | 3 ++- .../components/hdmi_cec/manifest.json | 3 ++- .../components/heatmiser/manifest.json | 3 ++- homeassistant/components/heos/manifest.json | 3 ++- .../components/here_travel_time/manifest.json | 3 ++- .../components/hikvision/manifest.json | 3 ++- .../components/hikvisioncam/manifest.json | 3 ++- .../components/hisense_aehw4a1/manifest.json | 3 ++- homeassistant/components/hive/manifest.json | 3 ++- .../components/hlk_sw16/manifest.json | 3 ++- .../components/home_connect/manifest.json | 3 ++- .../home_plus_control/manifest.json | 3 ++- .../components/homekit/manifest.json | 3 ++- .../homekit_controller/manifest.json | 3 ++- .../components/homematic/manifest.json | 3 ++- .../homematicip_cloud/manifest.json | 3 ++- .../components/homewizard/manifest.json | 3 ++- .../components/homeworks/manifest.json | 3 ++- .../components/honeywell/manifest.json | 3 ++- .../components/horizon/manifest.json | 3 ++- homeassistant/components/html5/manifest.json | 3 ++- homeassistant/components/htu21d/manifest.json | 3 ++- .../components/huawei_lte/manifest.json | 3 ++- homeassistant/components/hue/manifest.json | 3 ++- .../components/huisbaasje/manifest.json | 3 ++- .../hunterdouglas_powerview/manifest.json | 3 ++- .../components/hvv_departures/manifest.json | 3 ++- .../components/hydrawise/manifest.json | 3 ++- .../components/hyperion/manifest.json | 3 ++- homeassistant/components/ialarm/manifest.json | 3 ++- .../components/iammeter/manifest.json | 3 ++- .../components/iaqualink/manifest.json | 3 ++- homeassistant/components/icloud/manifest.json | 3 ++- .../components/idteck_prox/manifest.json | 3 ++- homeassistant/components/ifttt/manifest.json | 3 ++- homeassistant/components/iglo/manifest.json | 3 ++- .../components/ign_sismologia/manifest.json | 3 ++- homeassistant/components/ihc/manifest.json | 3 ++- homeassistant/components/imap/manifest.json | 3 ++- .../components/incomfort/manifest.json | 3 ++- .../components/influxdb/manifest.json | 3 ++- .../components/insteon/manifest.json | 3 ++- .../components/intellifire/manifest.json | 3 ++- .../components/intesishome/manifest.json | 3 ++- .../components/iotawatt/manifest.json | 3 ++- homeassistant/components/iperf3/manifest.json | 3 ++- homeassistant/components/ipma/manifest.json | 3 ++- homeassistant/components/ipp/manifest.json | 3 ++- homeassistant/components/iqvia/manifest.json | 3 ++- .../irish_rail_transport/manifest.json | 3 ++- .../islamic_prayer_times/manifest.json | 3 ++- homeassistant/components/iss/manifest.json | 3 ++- homeassistant/components/isy994/manifest.json | 3 ++- homeassistant/components/izone/manifest.json | 3 ++- .../components/jellyfin/manifest.json | 3 ++- .../components/jewish_calendar/manifest.json | 3 ++- .../components/joaoapps_join/manifest.json | 3 ++- .../components/juicenet/manifest.json | 3 ++- .../components/kaiterra/manifest.json | 3 ++- homeassistant/components/keba/manifest.json | 3 ++- .../components/keenetic_ndms2/manifest.json | 3 ++- homeassistant/components/kef/manifest.json | 3 ++- .../components/keyboard/manifest.json | 3 ++- .../components/keyboard_remote/manifest.json | 3 ++- homeassistant/components/kira/manifest.json | 3 ++- homeassistant/components/kiwi/manifest.json | 3 ++- .../components/kmtronic/manifest.json | 3 ++- homeassistant/components/knx/manifest.json | 5 +++-- homeassistant/components/kodi/manifest.json | 3 ++- .../components/konnected/manifest.json | 3 ++- .../kostal_plenticore/manifest.json | 3 ++- homeassistant/components/kraken/manifest.json | 3 ++- .../components/kulersky/manifest.json | 3 ++- homeassistant/components/kwb/manifest.json | 3 ++- .../components/lacrosse/manifest.json | 3 ++- .../components/lametric/manifest.json | 3 ++- homeassistant/components/lastfm/manifest.json | 3 ++- homeassistant/components/lcn/manifest.json | 3 ++- .../components/lg_netcast/manifest.json | 3 ++- .../components/lg_soundbar/manifest.json | 3 ++- .../components/life360/manifest.json | 3 ++- homeassistant/components/lifx/manifest.json | 3 ++- .../components/lightwave/manifest.json | 3 ++- .../components/limitlessled/manifest.json | 3 ++- homeassistant/components/linode/manifest.json | 3 ++- .../components/linux_battery/manifest.json | 3 ++- homeassistant/components/lirc/manifest.json | 3 ++- .../components/litejet/manifest.json | 3 ++- .../components/litterrobot/manifest.json | 3 ++- .../components/logi_circle/manifest.json | 3 ++- .../london_underground/manifest.json | 3 ++- homeassistant/components/lookin/manifest.json | 3 ++- homeassistant/components/luci/manifest.json | 3 ++- .../components/luftdaten/manifest.json | 3 ++- .../components/lupusec/manifest.json | 3 ++- homeassistant/components/lutron/manifest.json | 3 ++- .../components/lutron_caseta/manifest.json | 3 ++- homeassistant/components/lyric/manifest.json | 3 ++- .../components/magicseaweed/manifest.json | 3 ++- .../components/mailgun/manifest.json | 3 ++- .../components/marytts/manifest.json | 3 ++- .../components/mastodon/manifest.json | 3 ++- homeassistant/components/matrix/manifest.json | 3 ++- .../components/maxcube/manifest.json | 3 ++- homeassistant/components/mazda/manifest.json | 3 ++- .../components/mcp23017/manifest.json | 3 ++- .../components/media_extractor/manifest.json | 3 ++- .../components/mediaroom/manifest.json | 3 ++- .../components/melcloud/manifest.json | 3 ++- .../components/melissa/manifest.json | 3 ++- .../components/message_bird/manifest.json | 3 ++- homeassistant/components/met/manifest.json | 3 ++- .../components/met_eireann/manifest.json | 3 ++- .../components/meteo_france/manifest.json | 3 ++- .../components/meteoalarm/manifest.json | 3 ++- .../components/meteoclimatic/manifest.json | 3 ++- .../components/metoffice/manifest.json | 3 ++- homeassistant/components/mfi/manifest.json | 3 ++- homeassistant/components/mhz19/manifest.json | 3 ++- .../components/microsoft/manifest.json | 3 ++- .../components/miflora/manifest.json | 3 ++- .../components/mikrotik/manifest.json | 3 ++- homeassistant/components/mill/manifest.json | 3 ++- .../components/minecraft_server/manifest.json | 3 ++- homeassistant/components/minio/manifest.json | 3 ++- .../components/mitemp_bt/manifest.json | 3 ++- .../components/mobile_app/manifest.json | 3 ++- homeassistant/components/mochad/manifest.json | 3 ++- homeassistant/components/modbus/manifest.json | 3 ++- .../components/modem_callerid/manifest.json | 3 ++- .../components/modern_forms/manifest.json | 3 ++- .../components/monoprice/manifest.json | 3 ++- .../components/motion_blinds/manifest.json | 3 ++- .../components/motioneye/manifest.json | 3 ++- homeassistant/components/mpd/manifest.json | 3 ++- .../components/msteams/manifest.json | 3 ++- .../components/mutesync/manifest.json | 3 ++- .../components/mvglive/manifest.json | 3 ++- .../components/mycroft/manifest.json | 3 ++- homeassistant/components/myq/manifest.json | 3 ++- .../components/mysensors/manifest.json | 3 ++- .../components/mystrom/manifest.json | 3 ++- .../components/mythicbeastsdns/manifest.json | 3 ++- homeassistant/components/nad/manifest.json | 3 ++- homeassistant/components/nam/manifest.json | 3 ++- .../components/nanoleaf/manifest.json | 3 ++- homeassistant/components/neato/manifest.json | 3 ++- .../components/ness_alarm/manifest.json | 3 ++- homeassistant/components/nest/manifest.json | 3 ++- .../components/netatmo/manifest.json | 3 ++- .../components/netdata/manifest.json | 3 ++- .../components/netgear/manifest.json | 3 ++- .../components/netgear_lte/manifest.json | 3 ++- .../components/neurio_energy/manifest.json | 3 ++- homeassistant/components/nexia/manifest.json | 3 ++- .../components/nextbus/manifest.json | 3 ++- .../components/nfandroidtv/manifest.json | 3 ++- .../components/nightscout/manifest.json | 3 ++- .../niko_home_control/manifest.json | 3 ++- homeassistant/components/nilu/manifest.json | 3 ++- homeassistant/components/nina/manifest.json | 3 ++- .../components/nissan_leaf/manifest.json | 3 ++- .../components/nmap_tracker/manifest.json | 3 ++- homeassistant/components/nmbs/manifest.json | 3 ++- .../components/noaa_tides/manifest.json | 3 ++- .../components/norway_air/manifest.json | 3 ++- .../components/notify_events/manifest.json | 3 ++- homeassistant/components/notion/manifest.json | 3 ++- .../components/nsw_fuel_station/manifest.json | 3 ++- .../nsw_rural_fire_service_feed/manifest.json | 3 ++- homeassistant/components/nuheat/manifest.json | 3 ++- homeassistant/components/nuki/manifest.json | 3 ++- homeassistant/components/numato/manifest.json | 3 ++- homeassistant/components/nut/manifest.json | 3 ++- homeassistant/components/nws/manifest.json | 3 ++- homeassistant/components/nx584/manifest.json | 3 ++- homeassistant/components/nzbget/manifest.json | 3 ++- .../components/oasa_telematics/manifest.json | 3 ++- homeassistant/components/obihai/manifest.json | 3 ++- .../components/octoprint/manifest.json | 3 ++- homeassistant/components/oem/manifest.json | 3 ++- .../components/omnilogic/manifest.json | 3 ++- homeassistant/components/oncue/manifest.json | 3 ++- .../components/ondilo_ico/manifest.json | 3 ++- .../components/onewire/manifest.json | 3 ++- homeassistant/components/onkyo/manifest.json | 3 ++- homeassistant/components/onvif/manifest.json | 3 ++- .../components/openerz/manifest.json | 3 ++- .../components/openevse/manifest.json | 3 ++- .../components/opengarage/manifest.json | 3 ++- .../components/openhome/manifest.json | 3 ++- .../components/opensensemap/manifest.json | 3 ++- .../components/opentherm_gw/manifest.json | 3 ++- homeassistant/components/openuv/manifest.json | 3 ++- .../components/openweathermap/manifest.json | 3 ++- .../components/opnsense/manifest.json | 3 ++- homeassistant/components/opple/manifest.json | 3 ++- .../components/orangepi_gpio/manifest.json | 3 ++- homeassistant/components/oru/manifest.json | 3 ++- homeassistant/components/orvibo/manifest.json | 3 ++- .../components/osramlightify/manifest.json | 3 ++- homeassistant/components/otp/manifest.json | 3 ++- .../components/overkiz/manifest.json | 3 ++- .../components/ovo_energy/manifest.json | 3 ++- .../components/owntracks/manifest.json | 3 ++- homeassistant/components/ozw/manifest.json | 3 ++- .../components/p1_monitor/manifest.json | 3 ++- .../components/panasonic_bluray/manifest.json | 3 ++- .../components/panasonic_viera/manifest.json | 3 ++- .../components/pandora/manifest.json | 3 ++- .../components/pcal9535a/manifest.json | 3 ++- homeassistant/components/pencom/manifest.json | 3 ++- .../components/philips_js/manifest.json | 3 ++- .../components/pi4ioe5v9xxxx/manifest.json | 3 ++- .../components/pi_hole/manifest.json | 3 ++- homeassistant/components/picnic/manifest.json | 3 ++- .../components/pilight/manifest.json | 3 ++- homeassistant/components/ping/manifest.json | 3 ++- homeassistant/components/pjlink/manifest.json | 3 ++- homeassistant/components/plaato/manifest.json | 3 ++- homeassistant/components/plex/manifest.json | 3 ++- .../components/plugwise/manifest.json | 3 ++- .../components/plum_lightpad/manifest.json | 3 ++- .../components/pocketcasts/manifest.json | 3 ++- homeassistant/components/point/manifest.json | 3 ++- .../components/poolsense/manifest.json | 3 ++- .../components/powerwall/manifest.json | 3 ++- .../components/progettihwsw/manifest.json | 3 ++- .../components/proliphix/manifest.json | 3 ++- .../components/prometheus/manifest.json | 3 ++- .../components/prosegur/manifest.json | 3 ++- .../components/proxmoxve/manifest.json | 3 ++- homeassistant/components/ps4/manifest.json | 3 ++- .../components/pushbullet/manifest.json | 3 ++- .../components/pushover/manifest.json | 3 ++- .../pvpc_hourly_pricing/manifest.json | 3 ++- .../components/python_script/manifest.json | 3 ++- .../components/qbittorrent/manifest.json | 3 ++- .../components/qld_bushfire/manifest.json | 3 ++- homeassistant/components/qnap/manifest.json | 3 ++- homeassistant/components/qrcode/manifest.json | 3 ++- .../components/qvr_pro/manifest.json | 3 ++- .../components/qwikswitch/manifest.json | 3 ++- homeassistant/components/rachio/manifest.json | 3 ++- .../components/radiotherm/manifest.json | 3 ++- .../components/rainbird/manifest.json | 3 ++- .../components/raincloud/manifest.json | 3 ++- .../components/rainforest_eagle/manifest.json | 3 ++- .../components/rainmachine/manifest.json | 3 ++- .../components/raspyrfm/manifest.json | 3 ++- .../components/recollect_waste/manifest.json | 3 ++- .../components/recswitch/manifest.json | 3 ++- homeassistant/components/reddit/manifest.json | 3 ++- .../components/rejseplanen/manifest.json | 3 ++- .../remember_the_milk/manifest.json | 3 ++- .../components/remote_rpi_gpio/manifest.json | 3 ++- .../components/renault/manifest.json | 3 ++- .../components/repetier/manifest.json | 3 ++- homeassistant/components/rflink/manifest.json | 3 ++- homeassistant/components/rfxtrx/manifest.json | 3 ++- .../components/ridwell/manifest.json | 3 ++- homeassistant/components/ring/manifest.json | 3 ++- homeassistant/components/ripple/manifest.json | 3 ++- homeassistant/components/risco/manifest.json | 3 ++- .../rituals_perfume_genie/manifest.json | 3 ++- .../components/rmvtransport/manifest.json | 3 ++- .../components/rocketchat/manifest.json | 3 ++- homeassistant/components/roku/manifest.json | 3 ++- homeassistant/components/roomba/manifest.json | 3 ++- homeassistant/components/roon/manifest.json | 3 ++- .../components/route53/manifest.json | 3 ++- homeassistant/components/rova/manifest.json | 3 ++- .../components/rpi_gpio/manifest.json | 3 ++- .../components/rpi_gpio_pwm/manifest.json | 3 ++- .../components/rpi_pfio/manifest.json | 3 ++- .../components/rpi_power/manifest.json | 3 ++- .../components/rtsp_to_webrtc/manifest.json | 3 ++- .../components/ruckus_unleashed/manifest.json | 3 ++- .../components/russound_rio/manifest.json | 3 ++- .../components/russound_rnet/manifest.json | 3 ++- .../components/sabnzbd/manifest.json | 3 ++- homeassistant/components/saj/manifest.json | 3 ++- .../components/samsungtv/manifest.json | 3 ++- .../components/satel_integra/manifest.json | 3 ++- .../components/schluter/manifest.json | 3 ++- .../components/screenlogic/manifest.json | 3 ++- .../components/scsgate/manifest.json | 3 ++- homeassistant/components/season/manifest.json | 3 ++- .../components/sendgrid/manifest.json | 3 ++- homeassistant/components/sense/manifest.json | 3 ++- .../components/sensehat/manifest.json | 3 ++- .../components/senseme/manifest.json | 3 ++- .../components/sensibo/manifest.json | 3 ++- .../components/serial_pm/manifest.json | 3 ++- homeassistant/components/sesame/manifest.json | 3 ++- .../components/seventeentrack/manifest.json | 3 ++- .../components/sharkiq/manifest.json | 3 ++- homeassistant/components/shelly/manifest.json | 3 ++- homeassistant/components/shiftr/manifest.json | 3 ++- homeassistant/components/shodan/manifest.json | 3 ++- homeassistant/components/sia/manifest.json | 3 ++- .../components/sighthound/manifest.json | 3 ++- .../components/signal_messenger/manifest.json | 3 ++- .../components/simplepush/manifest.json | 3 ++- .../components/simplisafe/manifest.json | 3 ++- homeassistant/components/sinch/manifest.json | 3 ++- .../components/sisyphus/manifest.json | 3 ++- .../components/sky_hub/manifest.json | 3 ++- .../components/skybeacon/manifest.json | 3 ++- .../components/skybell/manifest.json | 3 ++- homeassistant/components/slack/manifest.json | 3 ++- .../components/sleepiq/manifest.json | 3 ++- homeassistant/components/slide/manifest.json | 3 ++- homeassistant/components/sma/manifest.json | 3 ++- .../components/smappee/manifest.json | 3 ++- .../smart_meter_texas/manifest.json | 3 ++- .../components/smarthab/manifest.json | 3 ++- .../components/smartthings/manifest.json | 3 ++- .../components/smarttub/manifest.json | 3 ++- homeassistant/components/smarty/manifest.json | 3 ++- homeassistant/components/smhi/manifest.json | 3 ++- homeassistant/components/sms/manifest.json | 3 ++- .../components/snapcast/manifest.json | 3 ++- homeassistant/components/snmp/manifest.json | 3 ++- .../components/sochain/manifest.json | 3 ++- .../components/solaredge/manifest.json | 3 ++- .../components/solaredge_local/manifest.json | 3 ++- .../components/solarlog/manifest.json | 3 ++- homeassistant/components/solax/manifest.json | 3 ++- homeassistant/components/soma/manifest.json | 3 ++- homeassistant/components/somfy/manifest.json | 3 ++- .../components/somfy_mylink/manifest.json | 3 ++- homeassistant/components/sonarr/manifest.json | 3 ++- .../components/songpal/manifest.json | 3 ++- homeassistant/components/sonos/manifest.json | 3 ++- .../components/sony_projector/manifest.json | 3 ++- .../components/soundtouch/manifest.json | 3 ++- homeassistant/components/spc/manifest.json | 3 ++- homeassistant/components/spider/manifest.json | 3 ++- homeassistant/components/splunk/manifest.json | 3 ++- .../components/spotify/manifest.json | 3 ++- .../components/squeezebox/manifest.json | 3 ++- .../components/srp_energy/manifest.json | 3 ++- homeassistant/components/ssdp/manifest.json | 3 ++- .../components/starline/manifest.json | 3 ++- .../components/starlingbank/manifest.json | 3 ++- homeassistant/components/statsd/manifest.json | 3 ++- .../components/steam_online/manifest.json | 3 ++- .../components/steamist/manifest.json | 3 ++- .../components/stiebel_eltron/manifest.json | 3 ++- homeassistant/components/stream/manifest.json | 3 ++- .../components/streamlabswater/manifest.json | 3 ++- homeassistant/components/subaru/manifest.json | 3 ++- .../components/suez_water/manifest.json | 3 ++- homeassistant/components/supla/manifest.json | 3 ++- .../components/surepetcare/manifest.json | 3 ++- .../swiss_hydrological_data/manifest.json | 3 ++- .../swiss_public_transport/manifest.json | 3 ++- .../components/switchbot/manifest.json | 3 ++- .../components/switcher_kis/manifest.json | 3 ++- .../components/switchmate/manifest.json | 3 ++- .../components/syncthing/manifest.json | 3 ++- .../components/syncthru/manifest.json | 3 ++- .../components/synology_dsm/manifest.json | 3 ++- .../components/synology_srm/manifest.json | 3 ++- .../components/system_bridge/manifest.json | 3 ++- .../components/systemmonitor/manifest.json | 3 ++- homeassistant/components/tado/manifest.json | 3 ++- .../components/tank_utility/manifest.json | 3 ++- .../components/tankerkoenig/manifest.json | 3 ++- .../components/tapsaff/manifest.json | 3 ++- .../components/tasmota/manifest.json | 3 ++- .../components/tautulli/manifest.json | 3 ++- .../components/telegram_bot/manifest.json | 3 ++- .../components/tellstick/manifest.json | 3 ++- homeassistant/components/temper/manifest.json | 3 ++- .../components/tensorflow/manifest.json | 3 ++- .../tesla_wall_connector/manifest.json | 3 ++- .../thermoworks_smoke/manifest.json | 3 ++- .../components/thingspeak/manifest.json | 3 ++- .../components/thinkingcleaner/manifest.json | 3 ++- homeassistant/components/tibber/manifest.json | 3 ++- .../components/tikteck/manifest.json | 3 ++- homeassistant/components/tile/manifest.json | 3 ++- homeassistant/components/tmb/manifest.json | 3 ++- .../components/todoist/manifest.json | 3 ++- homeassistant/components/tof/manifest.json | 3 ++- homeassistant/components/tolo/manifest.json | 3 ++- homeassistant/components/toon/manifest.json | 3 ++- .../components/totalconnect/manifest.json | 3 ++- .../components/touchline/manifest.json | 3 ++- homeassistant/components/tplink/manifest.json | 3 ++- .../components/tplink_lte/manifest.json | 3 ++- .../components/traccar/manifest.json | 3 ++- .../components/tractive/manifest.json | 3 ++- .../components/tradfri/manifest.json | 3 ++- .../trafikverket_train/manifest.json | 3 ++- .../trafikverket_weatherstation/manifest.json | 3 ++- .../components/transmission/manifest.json | 3 ++- .../components/transport_nsw/manifest.json | 3 ++- .../components/travisci/manifest.json | 3 ++- homeassistant/components/tts/manifest.json | 3 ++- homeassistant/components/tuya/manifest.json | 3 ++- .../components/twentemilieu/manifest.json | 3 ++- homeassistant/components/twilio/manifest.json | 3 ++- .../components/twilio_call/manifest.json | 3 ++- .../components/twinkly/manifest.json | 3 ++- homeassistant/components/twitch/manifest.json | 3 ++- .../components/twitter/manifest.json | 3 ++- homeassistant/components/ubus/manifest.json | 3 ++- homeassistant/components/unifi/manifest.json | 3 ++- .../components/unifi_direct/manifest.json | 3 ++- .../components/unifiled/manifest.json | 3 ++- .../components/unifiprotect/manifest.json | 3 ++- homeassistant/components/upb/manifest.json | 3 ++- .../components/upc_connect/manifest.json | 3 ++- .../components/upcloud/manifest.json | 3 ++- homeassistant/components/upnp/manifest.json | 3 ++- .../components/uptimerobot/manifest.json | 3 ++- homeassistant/components/uscis/manifest.json | 3 ++- .../usgs_earthquakes_feed/manifest.json | 3 ++- .../components/utility_meter/manifest.json | 3 ++- homeassistant/components/uvc/manifest.json | 3 ++- homeassistant/components/vallox/manifest.json | 3 ++- .../components/vasttrafik/manifest.json | 3 ++- homeassistant/components/velbus/manifest.json | 3 ++- homeassistant/components/velux/manifest.json | 3 ++- .../components/venstar/manifest.json | 3 ++- homeassistant/components/vera/manifest.json | 3 ++- .../components/verisure/manifest.json | 3 ++- .../components/versasense/manifest.json | 3 ++- .../components/version/manifest.json | 3 ++- homeassistant/components/vesync/manifest.json | 3 ++- homeassistant/components/vicare/manifest.json | 3 ++- homeassistant/components/vilfo/manifest.json | 3 ++- .../components/vivotek/manifest.json | 3 ++- homeassistant/components/vizio/manifest.json | 3 ++- .../components/vlc_telnet/manifest.json | 3 ++- .../components/volkszaehler/manifest.json | 3 ++- .../components/volumio/manifest.json | 3 ++- .../components/volvooncall/manifest.json | 3 ++- homeassistant/components/vultr/manifest.json | 3 ++- .../components/w800rf32/manifest.json | 3 ++- .../components/wallbox/manifest.json | 3 ++- homeassistant/components/waqi/manifest.json | 3 ++- .../components/waterfurnace/manifest.json | 3 ++- .../components/watson_iot/manifest.json | 3 ++- .../components/watson_tts/manifest.json | 3 ++- .../components/watttime/manifest.json | 3 ++- .../components/waze_travel_time/manifest.json | 3 ++- .../components/webostv/manifest.json | 3 ++- homeassistant/components/wemo/manifest.json | 3 ++- .../components/whirlpool/manifest.json | 3 ++- homeassistant/components/whois/manifest.json | 3 ++- homeassistant/components/wiffi/manifest.json | 3 ++- .../components/wilight/manifest.json | 3 ++- .../components/wirelesstag/manifest.json | 3 ++- .../components/withings/manifest.json | 3 ++- .../components/wolflink/manifest.json | 3 ++- .../components/workday/manifest.json | 3 ++- homeassistant/components/xbee/manifest.json | 3 ++- .../components/xbox_live/manifest.json | 3 ++- homeassistant/components/xeoma/manifest.json | 3 ++- .../components/xiaomi_aqara/manifest.json | 3 ++- .../components/xiaomi_miio/manifest.json | 3 ++- .../components/xiaomi_tv/manifest.json | 3 ++- homeassistant/components/xmpp/manifest.json | 3 ++- homeassistant/components/xs1/manifest.json | 3 ++- .../components/yale_smart_alarm/manifest.json | 3 ++- homeassistant/components/yamaha/manifest.json | 3 ++- .../components/yamaha_musiccast/manifest.json | 3 ++- .../components/yeelight/manifest.json | 3 ++- .../yeelightsunflower/manifest.json | 3 ++- homeassistant/components/yi/manifest.json | 3 ++- .../components/youless/manifest.json | 3 ++- homeassistant/components/zabbix/manifest.json | 3 ++- homeassistant/components/zengge/manifest.json | 3 ++- .../components/zerproc/manifest.json | 3 ++- homeassistant/components/zha/manifest.json | 3 ++- .../components/zhong_hong/manifest.json | 3 ++- .../components/zoneminder/manifest.json | 3 ++- .../components/zwave_js/manifest.json | 3 ++- homeassistant/loader.py | 6 ++++++ script/hassfest/manifest.py | 1 + tests/test_loader.py | 19 +++++++++++++++++++ 722 files changed, 1464 insertions(+), 720 deletions(-) diff --git a/homeassistant/components/abode/manifest.json b/homeassistant/components/abode/manifest.json index c9353c31bab32f..07fcfe6cb74b92 100644 --- a/homeassistant/components/abode/manifest.json +++ b/homeassistant/components/abode/manifest.json @@ -8,5 +8,6 @@ "homekit": { "models": ["Abode", "Iota"] }, - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["abodepy", "lomond"] } diff --git a/homeassistant/components/accuweather/manifest.json b/homeassistant/components/accuweather/manifest.json index fd391a81bad244..cbb74585778d4e 100644 --- a/homeassistant/components/accuweather/manifest.json +++ b/homeassistant/components/accuweather/manifest.json @@ -6,5 +6,6 @@ "codeowners": ["@bieniu"], "config_flow": true, "quality_scale": "platinum", - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["accuweather"] } diff --git a/homeassistant/components/acmeda/manifest.json b/homeassistant/components/acmeda/manifest.json index 6313b177f4765e..c47a2831246563 100644 --- a/homeassistant/components/acmeda/manifest.json +++ b/homeassistant/components/acmeda/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/acmeda", "requirements": ["aiopulse==0.4.3"], "codeowners": ["@atmurray"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["aiopulse"] } diff --git a/homeassistant/components/adax/manifest.json b/homeassistant/components/adax/manifest.json index 70b0eff18e35ff..73ab948ecb4b7c 100644 --- a/homeassistant/components/adax/manifest.json +++ b/homeassistant/components/adax/manifest.json @@ -9,5 +9,6 @@ "codeowners": [ "@danielhiversen" ], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["adax", "adax_local"] } diff --git a/homeassistant/components/adguard/manifest.json b/homeassistant/components/adguard/manifest.json index bd311dd3d3574d..506ddfcfce0f3b 100644 --- a/homeassistant/components/adguard/manifest.json +++ b/homeassistant/components/adguard/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/adguard", "requirements": ["adguardhome==0.5.0"], "codeowners": ["@frenck"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["adguardhome"] } diff --git a/homeassistant/components/ads/manifest.json b/homeassistant/components/ads/manifest.json index 9e4f8384404600..06e11f9ae8b248 100644 --- a/homeassistant/components/ads/manifest.json +++ b/homeassistant/components/ads/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/ads", "requirements": ["pyads==3.2.2"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pyads"] } diff --git a/homeassistant/components/advantage_air/manifest.json b/homeassistant/components/advantage_air/manifest.json index 6390ccea39c3d9..73de35987ec071 100644 --- a/homeassistant/components/advantage_air/manifest.json +++ b/homeassistant/components/advantage_air/manifest.json @@ -10,5 +10,6 @@ "advantage_air==0.2.5" ], "quality_scale": "platinum", - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["advantage_air"] } \ No newline at end of file diff --git a/homeassistant/components/aemet/manifest.json b/homeassistant/components/aemet/manifest.json index e69980ad1220a5..087d5c38820446 100644 --- a/homeassistant/components/aemet/manifest.json +++ b/homeassistant/components/aemet/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/aemet", "requirements": ["AEMET-OpenData==0.2.1"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["aemet_opendata"] } diff --git a/homeassistant/components/aftership/manifest.json b/homeassistant/components/aftership/manifest.json index 311674359c7ffa..6b74c771b038f5 100644 --- a/homeassistant/components/aftership/manifest.json +++ b/homeassistant/components/aftership/manifest.json @@ -7,4 +7,4 @@ ], "codeowners": [], "iot_class": "cloud_polling" -} \ No newline at end of file +} diff --git a/homeassistant/components/agent_dvr/manifest.json b/homeassistant/components/agent_dvr/manifest.json index 7d740bbe7310db..c7ac3e14022a09 100644 --- a/homeassistant/components/agent_dvr/manifest.json +++ b/homeassistant/components/agent_dvr/manifest.json @@ -5,5 +5,6 @@ "requirements": ["agent-py==0.0.23"], "config_flow": true, "codeowners": ["@ispysoftware"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["agent"] } diff --git a/homeassistant/components/airly/manifest.json b/homeassistant/components/airly/manifest.json index 430e51c6e9e074..56dd205de6899c 100644 --- a/homeassistant/components/airly/manifest.json +++ b/homeassistant/components/airly/manifest.json @@ -6,5 +6,6 @@ "requirements": ["airly==1.1.0"], "config_flow": true, "quality_scale": "platinum", - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["airly"] } diff --git a/homeassistant/components/airnow/manifest.json b/homeassistant/components/airnow/manifest.json index d4e7bc71937b91..583e23611efcf8 100644 --- a/homeassistant/components/airnow/manifest.json +++ b/homeassistant/components/airnow/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/airnow", "requirements": ["pyairnow==1.1.0"], "codeowners": ["@asymworks"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyairnow"] } diff --git a/homeassistant/components/airthings/manifest.json b/homeassistant/components/airthings/manifest.json index 24585804b45f17..f3aba33ce80f67 100644 --- a/homeassistant/components/airthings/manifest.json +++ b/homeassistant/components/airthings/manifest.json @@ -7,5 +7,6 @@ "codeowners": [ "@danielhiversen" ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["airthings"] } \ No newline at end of file diff --git a/homeassistant/components/airtouch4/manifest.json b/homeassistant/components/airtouch4/manifest.json index 8297081ae9dff9..3e15f62710df23 100644 --- a/homeassistant/components/airtouch4/manifest.json +++ b/homeassistant/components/airtouch4/manifest.json @@ -9,5 +9,6 @@ "codeowners": [ "@LonePurpleWolf" ], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["airtouch4pyapi"] } \ No newline at end of file diff --git a/homeassistant/components/airvisual/manifest.json b/homeassistant/components/airvisual/manifest.json index 5d6a221dbbe0be..ed803a3e6a1cd2 100644 --- a/homeassistant/components/airvisual/manifest.json +++ b/homeassistant/components/airvisual/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/airvisual", "requirements": ["pyairvisual==5.0.9"], "codeowners": ["@bachya"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyairvisual", "pysmb"] } diff --git a/homeassistant/components/aladdin_connect/manifest.json b/homeassistant/components/aladdin_connect/manifest.json index b2cc5f6d32c733..ed568aa8a46ed5 100644 --- a/homeassistant/components/aladdin_connect/manifest.json +++ b/homeassistant/components/aladdin_connect/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/aladdin_connect", "requirements": ["aladdin_connect==0.3"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["aladdin_connect"] } diff --git a/homeassistant/components/alarmdecoder/manifest.json b/homeassistant/components/alarmdecoder/manifest.json index a762d698545f1b..0acb39801b541c 100644 --- a/homeassistant/components/alarmdecoder/manifest.json +++ b/homeassistant/components/alarmdecoder/manifest.json @@ -5,5 +5,6 @@ "requirements": ["adext==0.4.2"], "codeowners": ["@ajschmidt8"], "config_flow": true, - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["adext", "alarmdecoder"] } diff --git a/homeassistant/components/almond/manifest.json b/homeassistant/components/almond/manifest.json index cd045f25715c12..94203b46752f2e 100644 --- a/homeassistant/components/almond/manifest.json +++ b/homeassistant/components/almond/manifest.json @@ -6,5 +6,6 @@ "dependencies": ["http", "conversation"], "codeowners": ["@gcampax", "@balloob"], "requirements": ["pyalmond==0.0.2"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyalmond"] } diff --git a/homeassistant/components/alpha_vantage/manifest.json b/homeassistant/components/alpha_vantage/manifest.json index bfa41b3eeb15c0..b608d18bb7a008 100644 --- a/homeassistant/components/alpha_vantage/manifest.json +++ b/homeassistant/components/alpha_vantage/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/alpha_vantage", "requirements": ["alpha_vantage==2.3.1"], "codeowners": ["@fabaff"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["alpha_vantage"] } diff --git a/homeassistant/components/amazon_polly/manifest.json b/homeassistant/components/amazon_polly/manifest.json index b54fc9d918c1ca..b8befe292eb438 100644 --- a/homeassistant/components/amazon_polly/manifest.json +++ b/homeassistant/components/amazon_polly/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/amazon_polly", "requirements": ["boto3==1.20.24"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["boto3", "botocore", "s3transfer"] } diff --git a/homeassistant/components/amberelectric/manifest.json b/homeassistant/components/amberelectric/manifest.json index 6dc79513e55299..a4fd72f5bdbe1d 100644 --- a/homeassistant/components/amberelectric/manifest.json +++ b/homeassistant/components/amberelectric/manifest.json @@ -9,5 +9,6 @@ "requirements": [ "amberelectric==1.0.3" ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["amberelectric"] } \ No newline at end of file diff --git a/homeassistant/components/ambiclimate/manifest.json b/homeassistant/components/ambiclimate/manifest.json index 9441cdb86bc30a..6e83f747bb1ac3 100644 --- a/homeassistant/components/ambiclimate/manifest.json +++ b/homeassistant/components/ambiclimate/manifest.json @@ -6,5 +6,6 @@ "requirements": ["ambiclimate==0.2.1"], "dependencies": ["http"], "codeowners": ["@danielhiversen"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["ambiclimate"] } diff --git a/homeassistant/components/ambient_station/manifest.json b/homeassistant/components/ambient_station/manifest.json index 33cb84706ff2f9..21f7e25126946e 100644 --- a/homeassistant/components/ambient_station/manifest.json +++ b/homeassistant/components/ambient_station/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/ambient_station", "requirements": ["aioambient==2021.11.0"], "codeowners": ["@bachya"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["aioambient"] } diff --git a/homeassistant/components/amcrest/manifest.json b/homeassistant/components/amcrest/manifest.json index 0d6c1380c20095..6f590d410fd757 100644 --- a/homeassistant/components/amcrest/manifest.json +++ b/homeassistant/components/amcrest/manifest.json @@ -5,5 +5,6 @@ "requirements": ["amcrest==1.9.3"], "dependencies": ["ffmpeg"], "codeowners": ["@flacjacket"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["amcrest"] } diff --git a/homeassistant/components/ampio/manifest.json b/homeassistant/components/ampio/manifest.json index b47f84f2fe5160..6c3978460e45aa 100644 --- a/homeassistant/components/ampio/manifest.json +++ b/homeassistant/components/ampio/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/ampio", "requirements": ["asmog==0.0.6"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["asmog"] } diff --git a/homeassistant/components/androidtv/manifest.json b/homeassistant/components/androidtv/manifest.json index f50876e66296cb..735b4b0a4313a4 100644 --- a/homeassistant/components/androidtv/manifest.json +++ b/homeassistant/components/androidtv/manifest.json @@ -9,5 +9,6 @@ ], "codeowners": ["@JeffLIrion", "@ollo69"], "config_flow": true, - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["adb_shell", "androidtv", "pure_python_adb"] } diff --git a/homeassistant/components/anel_pwrctrl/manifest.json b/homeassistant/components/anel_pwrctrl/manifest.json index 926549f768d82f..49c7f3985e50ab 100644 --- a/homeassistant/components/anel_pwrctrl/manifest.json +++ b/homeassistant/components/anel_pwrctrl/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/anel_pwrctrl", "requirements": ["anel_pwrctrl-homeassistant==0.0.1.dev2"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["anel_pwrctrl"] } diff --git a/homeassistant/components/anthemav/manifest.json b/homeassistant/components/anthemav/manifest.json index 078ecaae0da1e8..c43b976416d91f 100644 --- a/homeassistant/components/anthemav/manifest.json +++ b/homeassistant/components/anthemav/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/anthemav", "requirements": ["anthemav==1.2.0"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["anthemav"] } diff --git a/homeassistant/components/apache_kafka/manifest.json b/homeassistant/components/apache_kafka/manifest.json index 688c7c9fb3d7a8..3b290146a09410 100644 --- a/homeassistant/components/apache_kafka/manifest.json +++ b/homeassistant/components/apache_kafka/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/apache_kafka", "requirements": ["aiokafka==0.6.0"], "codeowners": ["@bachya"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["aiokafka", "kafka_python"] } diff --git a/homeassistant/components/apcupsd/manifest.json b/homeassistant/components/apcupsd/manifest.json index ac9352bae449bf..13a08685c68af3 100644 --- a/homeassistant/components/apcupsd/manifest.json +++ b/homeassistant/components/apcupsd/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/apcupsd", "requirements": ["apcaccess==0.0.13"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["apcaccess"] } diff --git a/homeassistant/components/apns/manifest.json b/homeassistant/components/apns/manifest.json index 73136a2ff2982f..2ea4e495a2b245 100644 --- a/homeassistant/components/apns/manifest.json +++ b/homeassistant/components/apns/manifest.json @@ -5,5 +5,6 @@ "requirements": ["apns2==0.3.0"], "after_dependencies": ["device_tracker"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["apns2", "hyper"] } diff --git a/homeassistant/components/apple_tv/manifest.json b/homeassistant/components/apple_tv/manifest.json index b3af0413bf1e4f..03d662b97217bb 100644 --- a/homeassistant/components/apple_tv/manifest.json +++ b/homeassistant/components/apple_tv/manifest.json @@ -21,5 +21,6 @@ {"type":"_raop._tcp.local.", "properties": {"am":"airport*"}} ], "codeowners": ["@postlund"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pyatv", "srptools"] } diff --git a/homeassistant/components/apprise/manifest.json b/homeassistant/components/apprise/manifest.json index 4e0209cc337db8..f060bf8d8c61c9 100644 --- a/homeassistant/components/apprise/manifest.json +++ b/homeassistant/components/apprise/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/apprise", "requirements": ["apprise==0.9.6"], "codeowners": ["@caronc"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["apprise"] } diff --git a/homeassistant/components/aprs/manifest.json b/homeassistant/components/aprs/manifest.json index dc29ff6fff5420..6979eab4516d43 100644 --- a/homeassistant/components/aprs/manifest.json +++ b/homeassistant/components/aprs/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/aprs", "codeowners": ["@PhilRW"], "requirements": ["aprslib==0.7.0", "geopy==2.1.0"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["aprslib", "geographiclib", "geopy"] } diff --git a/homeassistant/components/aqualogic/manifest.json b/homeassistant/components/aqualogic/manifest.json index acae105b54d6f9..91811189000018 100644 --- a/homeassistant/components/aqualogic/manifest.json +++ b/homeassistant/components/aqualogic/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/aqualogic", "requirements": ["aqualogic==2.6"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["aqualogic"] } diff --git a/homeassistant/components/aquostv/manifest.json b/homeassistant/components/aquostv/manifest.json index a28c852d8dbcb9..b0da88a845063e 100644 --- a/homeassistant/components/aquostv/manifest.json +++ b/homeassistant/components/aquostv/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/aquostv", "requirements": ["sharp_aquos_rc==0.3.2"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["sharp_aquos_rc"] } diff --git a/homeassistant/components/arcam_fmj/manifest.json b/homeassistant/components/arcam_fmj/manifest.json index 08545f4c5b0578..c91c92922b494d 100644 --- a/homeassistant/components/arcam_fmj/manifest.json +++ b/homeassistant/components/arcam_fmj/manifest.json @@ -11,5 +11,6 @@ } ], "codeowners": ["@elupus"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["arcam"] } diff --git a/homeassistant/components/arlo/manifest.json b/homeassistant/components/arlo/manifest.json index 7b4978b56c1cd2..5ba5180b914101 100644 --- a/homeassistant/components/arlo/manifest.json +++ b/homeassistant/components/arlo/manifest.json @@ -5,5 +5,6 @@ "requirements": ["pyarlo==0.2.4"], "dependencies": ["ffmpeg"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyarlo", "sseclient_py"] } diff --git a/homeassistant/components/arris_tg2492lg/manifest.json b/homeassistant/components/arris_tg2492lg/manifest.json index 01da8b8af3ca80..63d292d54accbd 100644 --- a/homeassistant/components/arris_tg2492lg/manifest.json +++ b/homeassistant/components/arris_tg2492lg/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/arris_tg2492lg", "requirements": ["arris-tg2492lg==1.2.1"], "codeowners": ["@vanbalken"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["arris_tg2492lg"] } diff --git a/homeassistant/components/aruba/manifest.json b/homeassistant/components/aruba/manifest.json index 660ba9f06f13c8..4b72a12aa2681c 100644 --- a/homeassistant/components/aruba/manifest.json +++ b/homeassistant/components/aruba/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/aruba", "requirements": ["pexpect==4.6.0"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pexpect", "ptyprocess"] } diff --git a/homeassistant/components/aseko_pool_live/manifest.json b/homeassistant/components/aseko_pool_live/manifest.json index f6323b493547f9..90c2c81e552b58 100644 --- a/homeassistant/components/aseko_pool_live/manifest.json +++ b/homeassistant/components/aseko_pool_live/manifest.json @@ -7,5 +7,6 @@ "codeowners": [ "@milanmeu" ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["aioaseko"] } \ No newline at end of file diff --git a/homeassistant/components/asterisk_mbox/manifest.json b/homeassistant/components/asterisk_mbox/manifest.json index 068da7d64f44c5..d42233ffa2d6c9 100644 --- a/homeassistant/components/asterisk_mbox/manifest.json +++ b/homeassistant/components/asterisk_mbox/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/asterisk_mbox", "requirements": ["asterisk_mbox==0.5.0"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["asterisk_mbox"] } diff --git a/homeassistant/components/asuswrt/manifest.json b/homeassistant/components/asuswrt/manifest.json index 1470c075b0438b..c1d67fa9e5768c 100644 --- a/homeassistant/components/asuswrt/manifest.json +++ b/homeassistant/components/asuswrt/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/asuswrt", "requirements": ["aioasuswrt==1.4.0"], "codeowners": ["@kennedyshead", "@ollo69"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["aioasuswrt", "asyncssh"] } diff --git a/homeassistant/components/atag/manifest.json b/homeassistant/components/atag/manifest.json index eb9dc54ecd2899..39e483721677b9 100644 --- a/homeassistant/components/atag/manifest.json +++ b/homeassistant/components/atag/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/atag/", "requirements": ["pyatag==0.3.5.3"], "codeowners": ["@MatsNL"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyatag"] } diff --git a/homeassistant/components/atome/manifest.json b/homeassistant/components/atome/manifest.json index 975e7f1ac31654..415cb900dc2b6e 100644 --- a/homeassistant/components/atome/manifest.json +++ b/homeassistant/components/atome/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/atome", "codeowners": ["@baqs"], "requirements": ["pyatome==0.1.1"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyatome"] } diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index db537287b05d99..dc05ee9b9296af 100644 --- a/homeassistant/components/august/manifest.json +++ b/homeassistant/components/august/manifest.json @@ -23,5 +23,6 @@ } ], "config_flow": true, - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["pubnub", "yalexs"] } diff --git a/homeassistant/components/aurora/manifest.json b/homeassistant/components/aurora/manifest.json index 466bf938cb511f..54500f5c95aa14 100644 --- a/homeassistant/components/aurora/manifest.json +++ b/homeassistant/components/aurora/manifest.json @@ -5,5 +5,6 @@ "config_flow": true, "codeowners": ["@djtimca"], "requirements": ["auroranoaa==0.0.2"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["auroranoaa"] } diff --git a/homeassistant/components/aurora_abb_powerone/manifest.json b/homeassistant/components/aurora_abb_powerone/manifest.json index 9849c0d84ee7be..d3ab0022a705df 100644 --- a/homeassistant/components/aurora_abb_powerone/manifest.json +++ b/homeassistant/components/aurora_abb_powerone/manifest.json @@ -7,5 +7,6 @@ "codeowners": [ "@davet2001" ], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["aurorapy"] } diff --git a/homeassistant/components/aussie_broadband/manifest.json b/homeassistant/components/aussie_broadband/manifest.json index fb7ce82832460b..fcec645127f751 100644 --- a/homeassistant/components/aussie_broadband/manifest.json +++ b/homeassistant/components/aussie_broadband/manifest.json @@ -10,5 +10,6 @@ "@nickw444", "@Bre77" ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["aussiebb"] } \ No newline at end of file diff --git a/homeassistant/components/avea/manifest.json b/homeassistant/components/avea/manifest.json index 223ceba7685f2c..de6581c377264f 100644 --- a/homeassistant/components/avea/manifest.json +++ b/homeassistant/components/avea/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/avea", "codeowners": ["@pattyland"], "requirements": ["avea==1.5.1"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["avea"] } diff --git a/homeassistant/components/awair/manifest.json b/homeassistant/components/awair/manifest.json index c1a3fbd59a761f..085f2573a21785 100644 --- a/homeassistant/components/awair/manifest.json +++ b/homeassistant/components/awair/manifest.json @@ -5,5 +5,6 @@ "requirements": ["python_awair==0.2.1"], "codeowners": ["@ahayworth", "@danielsjf"], "config_flow": true, - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["python_awair"] } diff --git a/homeassistant/components/aws/manifest.json b/homeassistant/components/aws/manifest.json index 761328ba3ded43..41dcb9b2b0b9cd 100644 --- a/homeassistant/components/aws/manifest.json +++ b/homeassistant/components/aws/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/aws", "requirements": ["aiobotocore==2.1.0"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["aiobotocore", "botocore"] } diff --git a/homeassistant/components/axis/manifest.json b/homeassistant/components/axis/manifest.json index 59e723411507d6..41580aa39d01cb 100644 --- a/homeassistant/components/axis/manifest.json +++ b/homeassistant/components/axis/manifest.json @@ -40,5 +40,6 @@ "after_dependencies": ["mqtt"], "codeowners": ["@Kane610"], "quality_scale": "platinum", - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["axis"] } diff --git a/homeassistant/components/azure_devops/manifest.json b/homeassistant/components/azure_devops/manifest.json index 1dd0475329396c..0500a5856192df 100644 --- a/homeassistant/components/azure_devops/manifest.json +++ b/homeassistant/components/azure_devops/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/azure_devops", "requirements": ["aioazuredevops==1.3.5"], "codeowners": ["@timmo001"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["aioazuredevops"] } diff --git a/homeassistant/components/azure_event_hub/manifest.json b/homeassistant/components/azure_event_hub/manifest.json index 52125b5a79c536..59c589931f42f7 100644 --- a/homeassistant/components/azure_event_hub/manifest.json +++ b/homeassistant/components/azure_event_hub/manifest.json @@ -5,5 +5,6 @@ "requirements": ["azure-eventhub==5.5.0"], "codeowners": ["@eavanvalkenburg"], "iot_class": "cloud_push", - "config_flow": true + "config_flow": true, + "loggers": ["azure"] } diff --git a/homeassistant/components/azure_service_bus/manifest.json b/homeassistant/components/azure_service_bus/manifest.json index 5de15056b08742..6cf5e2bf406abe 100644 --- a/homeassistant/components/azure_service_bus/manifest.json +++ b/homeassistant/components/azure_service_bus/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/azure_service_bus", "requirements": ["azure-servicebus==0.50.3"], "codeowners": ["@hfurubotten"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["azure"] } diff --git a/homeassistant/components/baidu/manifest.json b/homeassistant/components/baidu/manifest.json index e808da427281e5..446551ec3a111f 100644 --- a/homeassistant/components/baidu/manifest.json +++ b/homeassistant/components/baidu/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/baidu", "requirements": ["baidu-aip==1.6.6"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["aip"] } diff --git a/homeassistant/components/balboa/manifest.json b/homeassistant/components/balboa/manifest.json index aa52bee230d7f0..d6ef4094b07f58 100644 --- a/homeassistant/components/balboa/manifest.json +++ b/homeassistant/components/balboa/manifest.json @@ -9,5 +9,6 @@ "codeowners": [ "@garbled1" ], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pybalboa"] } diff --git a/homeassistant/components/bbb_gpio/manifest.json b/homeassistant/components/bbb_gpio/manifest.json index add067ab0ccfb4..c57530a9bf8562 100644 --- a/homeassistant/components/bbb_gpio/manifest.json +++ b/homeassistant/components/bbb_gpio/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/bbb_gpio", "requirements": ["Adafruit_BBIO==1.1.1"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["Adafruit_BBIO"] } diff --git a/homeassistant/components/bbox/manifest.json b/homeassistant/components/bbox/manifest.json index a59023bb3f5241..4f298b2b5e9ae0 100644 --- a/homeassistant/components/bbox/manifest.json +++ b/homeassistant/components/bbox/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/bbox", "requirements": ["pybbox==0.0.5-alpha"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pybbox"] } diff --git a/homeassistant/components/beewi_smartclim/manifest.json b/homeassistant/components/beewi_smartclim/manifest.json index 941faf1b598c0a..b334ab36b36f9c 100644 --- a/homeassistant/components/beewi_smartclim/manifest.json +++ b/homeassistant/components/beewi_smartclim/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/beewi_smartclim", "requirements": ["beewi_smartclim==0.0.10"], "codeowners": ["@alemuro"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["beewi_smartclim"] } diff --git a/homeassistant/components/bh1750/manifest.json b/homeassistant/components/bh1750/manifest.json index f784b029a01d83..807f7a9e05f1a2 100644 --- a/homeassistant/components/bh1750/manifest.json +++ b/homeassistant/components/bh1750/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/bh1750", "requirements": ["i2csense==0.0.4", "smbus-cffi==0.5.1"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["i2csense", "smbus"] } diff --git a/homeassistant/components/bitcoin/manifest.json b/homeassistant/components/bitcoin/manifest.json index 0a8abfa6500b43..2cd9453f4b8316 100644 --- a/homeassistant/components/bitcoin/manifest.json +++ b/homeassistant/components/bitcoin/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/bitcoin", "requirements": ["blockchain==1.4.4"], "codeowners": ["@fabaff"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["blockchain"] } diff --git a/homeassistant/components/bizkaibus/manifest.json b/homeassistant/components/bizkaibus/manifest.json index c8923f3d541fe4..c18bd8b5de213c 100644 --- a/homeassistant/components/bizkaibus/manifest.json +++ b/homeassistant/components/bizkaibus/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/bizkaibus", "codeowners": ["@UgaitzEtxebarria"], "requirements": ["bizkaibus==0.1.1"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["bizkaibus"] } diff --git a/homeassistant/components/blackbird/manifest.json b/homeassistant/components/blackbird/manifest.json index 04bde4b4617fed..44645397c2dd60 100644 --- a/homeassistant/components/blackbird/manifest.json +++ b/homeassistant/components/blackbird/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/blackbird", "requirements": ["pyblackbird==0.5"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyblackbird"] } diff --git a/homeassistant/components/blebox/manifest.json b/homeassistant/components/blebox/manifest.json index 39c0d37e2e3c3c..d9c0481fff65c9 100644 --- a/homeassistant/components/blebox/manifest.json +++ b/homeassistant/components/blebox/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/blebox", "requirements": ["blebox_uniapi==1.3.3"], "codeowners": ["@bbx-a", "@bbx-jp"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["blebox_uniapi"] } diff --git a/homeassistant/components/blink/manifest.json b/homeassistant/components/blink/manifest.json index b90e7e845cfc28..c4bc116b4a63b6 100644 --- a/homeassistant/components/blink/manifest.json +++ b/homeassistant/components/blink/manifest.json @@ -11,5 +11,6 @@ } ], "config_flow": true, - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["blinkpy"] } diff --git a/homeassistant/components/blinksticklight/manifest.json b/homeassistant/components/blinksticklight/manifest.json index 05f8fe65fb3c7f..b7058494e5cc0c 100644 --- a/homeassistant/components/blinksticklight/manifest.json +++ b/homeassistant/components/blinksticklight/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/blinksticklight", "requirements": ["blinkstick==1.2.0"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["blinkstick"] } diff --git a/homeassistant/components/blockchain/manifest.json b/homeassistant/components/blockchain/manifest.json index c7c37c9bd0dd88..712f90a0f26a69 100644 --- a/homeassistant/components/blockchain/manifest.json +++ b/homeassistant/components/blockchain/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/blockchain", "requirements": ["python-blockchain-api==0.0.2"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyblockchain"] } diff --git a/homeassistant/components/bluetooth_le_tracker/manifest.json b/homeassistant/components/bluetooth_le_tracker/manifest.json index 564aef45f8442f..7552c024d62193 100644 --- a/homeassistant/components/bluetooth_le_tracker/manifest.json +++ b/homeassistant/components/bluetooth_le_tracker/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/bluetooth_le_tracker", "requirements": ["pygatt[GATTTOOL]==4.0.5"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pygatt"] } diff --git a/homeassistant/components/bluetooth_tracker/manifest.json b/homeassistant/components/bluetooth_tracker/manifest.json index ccf48a9b8c37e2..ad8ee782592c75 100644 --- a/homeassistant/components/bluetooth_tracker/manifest.json +++ b/homeassistant/components/bluetooth_tracker/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/bluetooth_tracker", "requirements": ["bt_proximity==0.2.1", "pybluez==0.22"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["bluetooth", "bt_proximity"] } diff --git a/homeassistant/components/bme280/manifest.json b/homeassistant/components/bme280/manifest.json index 4c997152b5a87e..8a283b40f5fabe 100644 --- a/homeassistant/components/bme280/manifest.json +++ b/homeassistant/components/bme280/manifest.json @@ -8,5 +8,6 @@ "bme280spi==0.2.0" ], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["bme280spi", "i2csense", "smbus"] } diff --git a/homeassistant/components/bme680/manifest.json b/homeassistant/components/bme680/manifest.json index 16e841b942f5cf..c4db1d640de92f 100644 --- a/homeassistant/components/bme680/manifest.json +++ b/homeassistant/components/bme680/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/bme680", "requirements": ["bme680==1.0.5", "smbus-cffi==0.5.1"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["bme680", "smbus"] } diff --git a/homeassistant/components/bmw_connected_drive/manifest.json b/homeassistant/components/bmw_connected_drive/manifest.json index 9698679a6d6285..3e437f9932f62a 100644 --- a/homeassistant/components/bmw_connected_drive/manifest.json +++ b/homeassistant/components/bmw_connected_drive/manifest.json @@ -5,5 +5,6 @@ "requirements": ["bimmer_connected==0.8.10"], "codeowners": ["@gerard33", "@rikroe"], "config_flow": true, - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["bimmer_connected"] } diff --git a/homeassistant/components/bond/manifest.json b/homeassistant/components/bond/manifest.json index 5e782c70868d73..e5f8b004502fba 100644 --- a/homeassistant/components/bond/manifest.json +++ b/homeassistant/components/bond/manifest.json @@ -7,5 +7,6 @@ "zeroconf": ["_bond._tcp.local."], "codeowners": ["@bdraco", "@prystupa", "@joshs85"], "quality_scale": "platinum", - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["bond_api"] } diff --git a/homeassistant/components/bosch_shc/manifest.json b/homeassistant/components/bosch_shc/manifest.json index 2ed89c0bf5b4f1..ecc4e13e54e850 100644 --- a/homeassistant/components/bosch_shc/manifest.json +++ b/homeassistant/components/bosch_shc/manifest.json @@ -7,5 +7,6 @@ "zeroconf": [{ "type": "_http._tcp.local.", "name": "bosch shc*" }], "iot_class": "local_push", "codeowners": ["@tschamm"], - "after_dependencies": ["zeroconf"] + "after_dependencies": ["zeroconf"], + "loggers": ["boschshcpy"] } diff --git a/homeassistant/components/braviatv/manifest.json b/homeassistant/components/braviatv/manifest.json index 18285ebec00145..4ce465abc36733 100644 --- a/homeassistant/components/braviatv/manifest.json +++ b/homeassistant/components/braviatv/manifest.json @@ -5,5 +5,6 @@ "requirements": ["bravia-tv==1.0.11"], "codeowners": ["@bieniu", "@Drafteed"], "config_flow": true, - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["bravia_tv"] } diff --git a/homeassistant/components/broadlink/manifest.json b/homeassistant/components/broadlink/manifest.json index 1a6e94003ca24a..63f09e3dfb3c8e 100644 --- a/homeassistant/components/broadlink/manifest.json +++ b/homeassistant/components/broadlink/manifest.json @@ -19,5 +19,6 @@ "macaddress": "B4430D*" } ], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["broadlink"] } diff --git a/homeassistant/components/brother/manifest.json b/homeassistant/components/brother/manifest.json index 77a84c70de84d4..aaf1af72db9efb 100644 --- a/homeassistant/components/brother/manifest.json +++ b/homeassistant/components/brother/manifest.json @@ -12,5 +12,6 @@ ], "config_flow": true, "quality_scale": "platinum", - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["brother", "pyasn1", "pysmi", "pysnmp"] } diff --git a/homeassistant/components/brottsplatskartan/manifest.json b/homeassistant/components/brottsplatskartan/manifest.json index cb91446e4762f5..693d6ab465ceba 100644 --- a/homeassistant/components/brottsplatskartan/manifest.json +++ b/homeassistant/components/brottsplatskartan/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/brottsplatskartan", "requirements": ["brottsplatskartan==0.0.1"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["brottsplatskartan"] } diff --git a/homeassistant/components/brunt/manifest.json b/homeassistant/components/brunt/manifest.json index fce775d4b7d89b..72277a820e4d84 100644 --- a/homeassistant/components/brunt/manifest.json +++ b/homeassistant/components/brunt/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/brunt", "requirements": ["brunt==1.1.1"], "codeowners": ["@eavanvalkenburg"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["brunt"] } diff --git a/homeassistant/components/bsblan/manifest.json b/homeassistant/components/bsblan/manifest.json index b1762f7fea319e..88eefb7f9c023c 100644 --- a/homeassistant/components/bsblan/manifest.json +++ b/homeassistant/components/bsblan/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/bsblan", "requirements": ["bsblan==0.5.0"], "codeowners": ["@liudger"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["bsblan"] } diff --git a/homeassistant/components/bt_home_hub_5/manifest.json b/homeassistant/components/bt_home_hub_5/manifest.json index dfd61b1b9a8496..e0edcd934e6bdc 100644 --- a/homeassistant/components/bt_home_hub_5/manifest.json +++ b/homeassistant/components/bt_home_hub_5/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/bt_home_hub_5", "requirements": ["bthomehub5-devicelist==0.1.1"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["bthomehub5_devicelist"] } diff --git a/homeassistant/components/bt_smarthub/manifest.json b/homeassistant/components/bt_smarthub/manifest.json index 33fab430453a21..6a0453752e9f6e 100644 --- a/homeassistant/components/bt_smarthub/manifest.json +++ b/homeassistant/components/bt_smarthub/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/bt_smarthub", "requirements": ["btsmarthub_devicelist==0.2.0"], "codeowners": ["@jxwolstenholme"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["btsmarthub_devicelist"] } diff --git a/homeassistant/components/buienradar/manifest.json b/homeassistant/components/buienradar/manifest.json index f88bfb83ddf266..68011bb7bb2236 100644 --- a/homeassistant/components/buienradar/manifest.json +++ b/homeassistant/components/buienradar/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/buienradar", "requirements": ["buienradar==1.0.5"], "codeowners": ["@mjj4791", "@ties", "@Robbie1221"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["buienradar", "vincenty"] } diff --git a/homeassistant/components/caldav/manifest.json b/homeassistant/components/caldav/manifest.json index 06e90d31942b54..91f563107ed495 100644 --- a/homeassistant/components/caldav/manifest.json +++ b/homeassistant/components/caldav/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/caldav", "requirements": ["caldav==0.8.2"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["caldav", "vobject"] } diff --git a/homeassistant/components/canary/manifest.json b/homeassistant/components/canary/manifest.json index c9a75b063f6113..12b4d54b391569 100644 --- a/homeassistant/components/canary/manifest.json +++ b/homeassistant/components/canary/manifest.json @@ -6,5 +6,6 @@ "dependencies": ["ffmpeg"], "codeowners": [], "config_flow": true, - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["canary"] } diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index b1a4cd0b35874f..2316a884b73490 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -14,5 +14,6 @@ ], "zeroconf": ["_googlecast._tcp.local."], "codeowners": ["@emontnemery"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["casttube", "pychromecast"] } diff --git a/homeassistant/components/channels/manifest.json b/homeassistant/components/channels/manifest.json index 1113699cdcac97..d167d6b420788b 100644 --- a/homeassistant/components/channels/manifest.json +++ b/homeassistant/components/channels/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/channels", "requirements": ["pychannels==1.0.0"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pychannels"] } diff --git a/homeassistant/components/circuit/manifest.json b/homeassistant/components/circuit/manifest.json index 6c10e7ff29980f..da820ccb91fdc9 100644 --- a/homeassistant/components/circuit/manifest.json +++ b/homeassistant/components/circuit/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/circuit", "codeowners": ["@braam"], "requirements": ["circuit-webhook==1.0.1"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["circuit_webhook"] } diff --git a/homeassistant/components/cisco_ios/manifest.json b/homeassistant/components/cisco_ios/manifest.json index 25e07086efe928..651d5eda1af99b 100644 --- a/homeassistant/components/cisco_ios/manifest.json +++ b/homeassistant/components/cisco_ios/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/cisco_ios", "requirements": ["pexpect==4.6.0"], "codeowners": ["@fbradyirl"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pexpect", "ptyprocess"] } diff --git a/homeassistant/components/cisco_mobility_express/manifest.json b/homeassistant/components/cisco_mobility_express/manifest.json index e1bdaeb3144903..5948bb1f94e98d 100644 --- a/homeassistant/components/cisco_mobility_express/manifest.json +++ b/homeassistant/components/cisco_mobility_express/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/cisco_mobility_express", "requirements": ["ciscomobilityexpress==0.3.9"], "codeowners": ["@fbradyirl"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["ciscomobilityexpress"] } diff --git a/homeassistant/components/cisco_webex_teams/manifest.json b/homeassistant/components/cisco_webex_teams/manifest.json index ba20014fdcffd9..571e7708bc6b39 100644 --- a/homeassistant/components/cisco_webex_teams/manifest.json +++ b/homeassistant/components/cisco_webex_teams/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/cisco_webex_teams", "requirements": ["webexteamssdk==1.1.1"], "codeowners": ["@fbradyirl"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["webexteamssdk"] } diff --git a/homeassistant/components/clementine/manifest.json b/homeassistant/components/clementine/manifest.json index 4f0b72a2be84ed..d003c693dd0be7 100644 --- a/homeassistant/components/clementine/manifest.json +++ b/homeassistant/components/clementine/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/clementine", "requirements": ["python-clementine-remote==1.0.1"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["clementineremote"] } diff --git a/homeassistant/components/climacell/manifest.json b/homeassistant/components/climacell/manifest.json index bb7dea841e49a6..4928d92447e151 100644 --- a/homeassistant/components/climacell/manifest.json +++ b/homeassistant/components/climacell/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/climacell", "requirements": ["pyclimacell==0.18.2"], "codeowners": ["@raman325"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyclimacell"] } diff --git a/homeassistant/components/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index b83c4c4cca9b69..3e55f6359c6db1 100644 --- a/homeassistant/components/cloud/manifest.json +++ b/homeassistant/components/cloud/manifest.json @@ -6,5 +6,6 @@ "dependencies": ["http", "webhook"], "after_dependencies": ["google_assistant", "alexa"], "codeowners": ["@home-assistant/cloud"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["hass_nabucasa"] } diff --git a/homeassistant/components/cloudflare/manifest.json b/homeassistant/components/cloudflare/manifest.json index ebb9e4b5f62853..73b83c24cce2db 100644 --- a/homeassistant/components/cloudflare/manifest.json +++ b/homeassistant/components/cloudflare/manifest.json @@ -5,5 +5,6 @@ "requirements": ["pycfdns==1.2.2"], "codeowners": ["@ludeeus", "@ctalkington"], "config_flow": true, - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["pycfdns"] } diff --git a/homeassistant/components/cmus/manifest.json b/homeassistant/components/cmus/manifest.json index 7e785af57c1652..bf2bb9290fc991 100644 --- a/homeassistant/components/cmus/manifest.json +++ b/homeassistant/components/cmus/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/cmus", "requirements": ["pycmus==0.1.1"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pbr", "pycmus"] } diff --git a/homeassistant/components/co2signal/manifest.json b/homeassistant/components/co2signal/manifest.json index 1921ae4f575239..2af5c8bcb2ffa2 100644 --- a/homeassistant/components/co2signal/manifest.json +++ b/homeassistant/components/co2signal/manifest.json @@ -7,5 +7,6 @@ ], "codeowners": [], "iot_class": "cloud_polling", - "config_flow": true + "config_flow": true, + "loggers": ["CO2Signal"] } \ No newline at end of file diff --git a/homeassistant/components/coinbase/manifest.json b/homeassistant/components/coinbase/manifest.json index aa056409786a0d..add24a8fd41107 100644 --- a/homeassistant/components/coinbase/manifest.json +++ b/homeassistant/components/coinbase/manifest.json @@ -9,5 +9,6 @@ "@tombrien" ], "config_flow": true, - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["coinbase"] } \ No newline at end of file diff --git a/homeassistant/components/comfoconnect/manifest.json b/homeassistant/components/comfoconnect/manifest.json index d02c10682e1742..907211dbae6b0f 100644 --- a/homeassistant/components/comfoconnect/manifest.json +++ b/homeassistant/components/comfoconnect/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/comfoconnect", "requirements": ["pycomfoconnect==0.4"], "codeowners": ["@michaelarnauts"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pycomfoconnect"] } diff --git a/homeassistant/components/concord232/manifest.json b/homeassistant/components/concord232/manifest.json index cfcd7fe8d68575..dc7bfae38303cb 100644 --- a/homeassistant/components/concord232/manifest.json +++ b/homeassistant/components/concord232/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/concord232", "requirements": ["concord232==0.15"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["concord232", "stevedore"] } diff --git a/homeassistant/components/control4/manifest.json b/homeassistant/components/control4/manifest.json index 656dd5bc93cf62..b00eef2067f10b 100644 --- a/homeassistant/components/control4/manifest.json +++ b/homeassistant/components/control4/manifest.json @@ -10,5 +10,6 @@ } ], "codeowners": ["@lawtancool"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyControl4"] } diff --git a/homeassistant/components/coolmaster/manifest.json b/homeassistant/components/coolmaster/manifest.json index c032c2620ce6a8..a56a97f272efa0 100644 --- a/homeassistant/components/coolmaster/manifest.json +++ b/homeassistant/components/coolmaster/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/coolmaster", "requirements": ["pycoolmasternet-async==0.1.2"], "codeowners": ["@OnFreund"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pycoolmasternet_async"] } diff --git a/homeassistant/components/coronavirus/manifest.json b/homeassistant/components/coronavirus/manifest.json index 87410d8b5727bb..3e7fc508719276 100644 --- a/homeassistant/components/coronavirus/manifest.json +++ b/homeassistant/components/coronavirus/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/coronavirus", "requirements": ["coronavirus==1.1.1"], "codeowners": ["@home-assistant/core"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["coronavirus"] } diff --git a/homeassistant/components/crownstone/manifest.json b/homeassistant/components/crownstone/manifest.json index 758721d5f71bf3..786f54ad6363ac 100644 --- a/homeassistant/components/crownstone/manifest.json +++ b/homeassistant/components/crownstone/manifest.json @@ -11,5 +11,6 @@ ], "codeowners": ["@Crownstone", "@RicArch97"], "after_dependencies": ["usb"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["crownstone_cloud", "crownstone_core", "crownstone_sse", "crownstone_uart"] } diff --git a/homeassistant/components/daikin/manifest.json b/homeassistant/components/daikin/manifest.json index 30fecb9c6de406..28bfec1476012a 100644 --- a/homeassistant/components/daikin/manifest.json +++ b/homeassistant/components/daikin/manifest.json @@ -7,5 +7,6 @@ "codeowners": ["@fredrike"], "zeroconf": ["_dkapi._tcp.local."], "quality_scale": "platinum", - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pydaikin"] } diff --git a/homeassistant/components/danfoss_air/manifest.json b/homeassistant/components/danfoss_air/manifest.json index 6468eea0a273f0..29c49b68df57af 100644 --- a/homeassistant/components/danfoss_air/manifest.json +++ b/homeassistant/components/danfoss_air/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/danfoss_air", "requirements": ["pydanfossair==0.1.0"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pydanfossair"] } diff --git a/homeassistant/components/darksky/manifest.json b/homeassistant/components/darksky/manifest.json index deefcaeb906e7c..7afd3002fcc97c 100644 --- a/homeassistant/components/darksky/manifest.json +++ b/homeassistant/components/darksky/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/darksky", "requirements": ["python-forecastio==1.4.0"], "codeowners": ["@fabaff"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["forecastio"] } diff --git a/homeassistant/components/datadog/manifest.json b/homeassistant/components/datadog/manifest.json index bd2349798fda3f..1397285a6fed6c 100644 --- a/homeassistant/components/datadog/manifest.json +++ b/homeassistant/components/datadog/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/datadog", "requirements": ["datadog==0.15.0"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["datadog"] } diff --git a/homeassistant/components/deconz/manifest.json b/homeassistant/components/deconz/manifest.json index 94356f95eaffb2..6fb6bbce87a1c8 100644 --- a/homeassistant/components/deconz/manifest.json +++ b/homeassistant/components/deconz/manifest.json @@ -15,5 +15,6 @@ "@Kane610" ], "quality_scale": "platinum", - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pydeconz"] } \ No newline at end of file diff --git a/homeassistant/components/decora/manifest.json b/homeassistant/components/decora/manifest.json index b631467e5e31e1..3734339a34bcb5 100644 --- a/homeassistant/components/decora/manifest.json +++ b/homeassistant/components/decora/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/decora", "requirements": ["bluepy==1.3.0", "decora==0.6"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["bluepy", "decora"] } diff --git a/homeassistant/components/decora_wifi/manifest.json b/homeassistant/components/decora_wifi/manifest.json index 1fd2b1737ad537..35af18a8c30752 100644 --- a/homeassistant/components/decora_wifi/manifest.json +++ b/homeassistant/components/decora_wifi/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/decora_wifi", "requirements": ["decora_wifi==1.4"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["decora_wifi"] } diff --git a/homeassistant/components/delijn/manifest.json b/homeassistant/components/delijn/manifest.json index 317ee21a9b04c9..1209dff749546e 100644 --- a/homeassistant/components/delijn/manifest.json +++ b/homeassistant/components/delijn/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/delijn", "codeowners": ["@bollewolle", "@Emilv2"], "requirements": ["pydelijn==0.6.1"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pydelijn"] } diff --git a/homeassistant/components/deluge/manifest.json b/homeassistant/components/deluge/manifest.json index 8539a69e560d43..5bf4651096c634 100644 --- a/homeassistant/components/deluge/manifest.json +++ b/homeassistant/components/deluge/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/deluge", "requirements": ["deluge-client==1.7.1"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["deluge_client"] } diff --git a/homeassistant/components/denonavr/manifest.json b/homeassistant/components/denonavr/manifest.json index c8d0e8a4d9d43e..5675e573bc1eff 100644 --- a/homeassistant/components/denonavr/manifest.json +++ b/homeassistant/components/denonavr/manifest.json @@ -55,5 +55,6 @@ "deviceType": "urn:schemas-denon-com:device:AiosDevice:1" } ], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["denonavr"] } diff --git a/homeassistant/components/deutsche_bahn/manifest.json b/homeassistant/components/deutsche_bahn/manifest.json index c8cbc5ba11e4ce..1eeb2241db52db 100644 --- a/homeassistant/components/deutsche_bahn/manifest.json +++ b/homeassistant/components/deutsche_bahn/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/deutsche_bahn", "requirements": ["schiene==0.23"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["schiene"] } diff --git a/homeassistant/components/devolo_home_control/manifest.json b/homeassistant/components/devolo_home_control/manifest.json index 9621a49157a902..e9076e3d3daa6f 100644 --- a/homeassistant/components/devolo_home_control/manifest.json +++ b/homeassistant/components/devolo_home_control/manifest.json @@ -8,5 +8,6 @@ "codeowners": ["@2Fake", "@Shutgun"], "quality_scale": "silver", "iot_class": "local_push", - "zeroconf": ["_dvl-deviceapi._tcp.local."] + "zeroconf": ["_dvl-deviceapi._tcp.local."], + "loggers": ["devolo_home_control_api"] } diff --git a/homeassistant/components/devolo_home_network/manifest.json b/homeassistant/components/devolo_home_network/manifest.json index 85f4e1caf9b750..a514606a322250 100644 --- a/homeassistant/components/devolo_home_network/manifest.json +++ b/homeassistant/components/devolo_home_network/manifest.json @@ -7,5 +7,6 @@ "zeroconf": ["_dvl-deviceapi._tcp.local."], "codeowners": ["@2Fake", "@Shutgun"], "quality_scale": "platinum", - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["devolo_plc_api"] } diff --git a/homeassistant/components/dexcom/manifest.json b/homeassistant/components/dexcom/manifest.json index 6133a67bcf16eb..b60ea3a576cdf1 100644 --- a/homeassistant/components/dexcom/manifest.json +++ b/homeassistant/components/dexcom/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/dexcom", "requirements": ["pydexcom==0.2.2"], "codeowners": ["@gagebenne"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pydexcom"] } diff --git a/homeassistant/components/dhcp/manifest.json b/homeassistant/components/dhcp/manifest.json index c79d63dda16a3c..c61b4b24a3099f 100644 --- a/homeassistant/components/dhcp/manifest.json +++ b/homeassistant/components/dhcp/manifest.json @@ -5,5 +5,6 @@ "requirements": ["scapy==2.4.5", "aiodiscover==1.4.7"], "codeowners": ["@bdraco"], "quality_scale": "internal", - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["aiodiscover", "dnspython", "pyroute2", "scapy"] } diff --git a/homeassistant/components/digital_ocean/manifest.json b/homeassistant/components/digital_ocean/manifest.json index eba3626a95085b..93c962f2d6c5e5 100644 --- a/homeassistant/components/digital_ocean/manifest.json +++ b/homeassistant/components/digital_ocean/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/digital_ocean", "requirements": ["python-digitalocean==1.13.2"], "codeowners": ["@fabaff"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["digitalocean"] } diff --git a/homeassistant/components/digitalloggers/manifest.json b/homeassistant/components/digitalloggers/manifest.json index 35cc1413bdfaab..51d5982a595eca 100644 --- a/homeassistant/components/digitalloggers/manifest.json +++ b/homeassistant/components/digitalloggers/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/digitalloggers", "requirements": ["dlipower==0.7.165"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["dlipower"] } diff --git a/homeassistant/components/directv/manifest.json b/homeassistant/components/directv/manifest.json index 3fba13121f1b64..d6fc946ab79a6a 100644 --- a/homeassistant/components/directv/manifest.json +++ b/homeassistant/components/directv/manifest.json @@ -12,5 +12,6 @@ "deviceType": "urn:schemas-upnp-org:device:MediaServer:1" } ], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["directv"] } diff --git a/homeassistant/components/discogs/manifest.json b/homeassistant/components/discogs/manifest.json index 5cc2d900229f75..4073cb273d8f8b 100644 --- a/homeassistant/components/discogs/manifest.json +++ b/homeassistant/components/discogs/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/discogs", "requirements": ["discogs_client==2.3.0"], "codeowners": ["@thibmaek"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["discogs_client"] } diff --git a/homeassistant/components/discord/manifest.json b/homeassistant/components/discord/manifest.json index 0da186e7924511..40c176c5f82a58 100644 --- a/homeassistant/components/discord/manifest.json +++ b/homeassistant/components/discord/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/discord", "requirements": ["discord.py==1.7.3"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["discord"] } diff --git a/homeassistant/components/discovery/manifest.json b/homeassistant/components/discovery/manifest.json index 1b7d51c1716e5f..3e7d31fcb1cf0b 100644 --- a/homeassistant/components/discovery/manifest.json +++ b/homeassistant/components/discovery/manifest.json @@ -5,5 +5,6 @@ "requirements": ["netdisco==3.0.0"], "after_dependencies": ["zeroconf"], "codeowners": [], - "quality_scale": "internal" + "quality_scale": "internal", + "loggers": ["netdisco"] } diff --git a/homeassistant/components/dlib_face_detect/manifest.json b/homeassistant/components/dlib_face_detect/manifest.json index 792486c7a875d6..8a0eb4304033d9 100644 --- a/homeassistant/components/dlib_face_detect/manifest.json +++ b/homeassistant/components/dlib_face_detect/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/dlib_face_detect", "requirements": ["face_recognition==1.2.3"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["face_recognition"] } diff --git a/homeassistant/components/dlib_face_identify/manifest.json b/homeassistant/components/dlib_face_identify/manifest.json index b8ac5bce5fa81f..3932df60631e96 100644 --- a/homeassistant/components/dlib_face_identify/manifest.json +++ b/homeassistant/components/dlib_face_identify/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/dlib_face_identify", "requirements": ["face_recognition==1.2.3"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["face_recognition"] } diff --git a/homeassistant/components/dlink/manifest.json b/homeassistant/components/dlink/manifest.json index 48a36a908c3c9d..9319eb8dd0f309 100644 --- a/homeassistant/components/dlink/manifest.json +++ b/homeassistant/components/dlink/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/dlink", "requirements": ["pyW215==0.7.0"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyW215"] } diff --git a/homeassistant/components/dlna_dmr/manifest.json b/homeassistant/components/dlna_dmr/manifest.json index dfe4e8c1b96a36..d7109aa65ee327 100644 --- a/homeassistant/components/dlna_dmr/manifest.json +++ b/homeassistant/components/dlna_dmr/manifest.json @@ -20,5 +20,6 @@ } ], "codeowners": ["@StevenLooman", "@chishm"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["async_upnp_client"] } diff --git a/homeassistant/components/dominos/manifest.json b/homeassistant/components/dominos/manifest.json index d7d366befd4b42..48b02cb97951a8 100644 --- a/homeassistant/components/dominos/manifest.json +++ b/homeassistant/components/dominos/manifest.json @@ -5,5 +5,6 @@ "requirements": ["pizzapi==0.0.3"], "dependencies": ["http"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pizzapi"] } diff --git a/homeassistant/components/doods/manifest.json b/homeassistant/components/doods/manifest.json index 3957b257364992..fe451db44b861b 100644 --- a/homeassistant/components/doods/manifest.json +++ b/homeassistant/components/doods/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/doods", "requirements": ["pydoods==1.0.2", "pillow==9.0.0"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pydoods"] } diff --git a/homeassistant/components/doorbird/manifest.json b/homeassistant/components/doorbird/manifest.json index 08c77f048a0bb3..6fc29343d04082 100644 --- a/homeassistant/components/doorbird/manifest.json +++ b/homeassistant/components/doorbird/manifest.json @@ -12,5 +12,6 @@ ], "codeowners": ["@oblogic7", "@bdraco", "@flacjacket"], "config_flow": true, - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["doorbirdpy"] } diff --git a/homeassistant/components/dsmr/manifest.json b/homeassistant/components/dsmr/manifest.json index d89c5a74db13e9..e15a7c3b80a2b8 100644 --- a/homeassistant/components/dsmr/manifest.json +++ b/homeassistant/components/dsmr/manifest.json @@ -5,5 +5,6 @@ "requirements": ["dsmr_parser==0.32"], "codeowners": ["@Robbie1221", "@frenck"], "config_flow": true, - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["dsmr_parser"] } diff --git a/homeassistant/components/dunehd/manifest.json b/homeassistant/components/dunehd/manifest.json index bf5fd347888068..09d8090f4fc7e9 100644 --- a/homeassistant/components/dunehd/manifest.json +++ b/homeassistant/components/dunehd/manifest.json @@ -5,5 +5,6 @@ "requirements": ["pdunehd==1.3.2"], "codeowners": ["@bieniu"], "config_flow": true, - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pdunehd"] } diff --git a/homeassistant/components/dwd_weather_warnings/manifest.json b/homeassistant/components/dwd_weather_warnings/manifest.json index 4fd54a7a3c9ba0..8b4576f312ad77 100644 --- a/homeassistant/components/dwd_weather_warnings/manifest.json +++ b/homeassistant/components/dwd_weather_warnings/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/dwd_weather_warnings", "codeowners": ["@runningman84", "@stephan192", "@Hummel95"], "requirements": ["dwdwfsapi==1.0.5"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["dwdwfsapi"] } diff --git a/homeassistant/components/dweet/manifest.json b/homeassistant/components/dweet/manifest.json index 46edd2bacfa5ff..078ea0ed21187a 100644 --- a/homeassistant/components/dweet/manifest.json +++ b/homeassistant/components/dweet/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/dweet", "requirements": ["dweepy==0.3.0"], "codeowners": ["@fabaff"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["dweepy"] } diff --git a/homeassistant/components/dynalite/manifest.json b/homeassistant/components/dynalite/manifest.json index 1ae50233b1a3c1..d403291a081449 100644 --- a/homeassistant/components/dynalite/manifest.json +++ b/homeassistant/components/dynalite/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/dynalite", "codeowners": ["@ziv1234"], "requirements": ["dynalite_devices==0.1.46"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["dynalite_devices_lib"] } diff --git a/homeassistant/components/eafm/manifest.json b/homeassistant/components/eafm/manifest.json index a4250e33a60b64..e3c1455b454d04 100644 --- a/homeassistant/components/eafm/manifest.json +++ b/homeassistant/components/eafm/manifest.json @@ -5,5 +5,6 @@ "config_flow": true, "codeowners": ["@Jc2k"], "requirements": ["aioeafm==0.1.2"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["aioeafm"] } diff --git a/homeassistant/components/ebox/manifest.json b/homeassistant/components/ebox/manifest.json index 6e4aca44ad6afb..3632b23123b8da 100644 --- a/homeassistant/components/ebox/manifest.json +++ b/homeassistant/components/ebox/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/ebox", "requirements": ["pyebox==1.1.4"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyebox"] } diff --git a/homeassistant/components/ebusd/manifest.json b/homeassistant/components/ebusd/manifest.json index 390e8efe7d5f48..fcb963f345d366 100644 --- a/homeassistant/components/ebusd/manifest.json +++ b/homeassistant/components/ebusd/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/ebusd", "requirements": ["ebusdpy==0.0.17"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["ebusdpy"] } diff --git a/homeassistant/components/ecoal_boiler/manifest.json b/homeassistant/components/ecoal_boiler/manifest.json index 83a9e7dbf6bdd2..8c643555fe774a 100644 --- a/homeassistant/components/ecoal_boiler/manifest.json +++ b/homeassistant/components/ecoal_boiler/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/ecoal_boiler", "requirements": ["ecoaliface==0.4.0"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["ecoaliface"] } diff --git a/homeassistant/components/ecobee/manifest.json b/homeassistant/components/ecobee/manifest.json index a22ec48da90c1f..c9fe52b7e133db 100644 --- a/homeassistant/components/ecobee/manifest.json +++ b/homeassistant/components/ecobee/manifest.json @@ -16,5 +16,6 @@ {"type":"_sideplay._tcp.local.", "properties": {"mdl":"eb-*"}}, {"type":"_sideplay._tcp.local.", "properties": {"mdl":"ecobee*"}} ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyecobee"] } \ No newline at end of file diff --git a/homeassistant/components/econet/manifest.json b/homeassistant/components/econet/manifest.json index 99a021de73a394..8a494d193b7260 100644 --- a/homeassistant/components/econet/manifest.json +++ b/homeassistant/components/econet/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/econet", "requirements": ["pyeconet==0.1.14"], "codeowners": ["@vangorra", "@w1ll1am23"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["paho_mqtt", "pyeconet"] } diff --git a/homeassistant/components/ecovacs/manifest.json b/homeassistant/components/ecovacs/manifest.json index ad442b0621a728..1712cea1578e56 100644 --- a/homeassistant/components/ecovacs/manifest.json +++ b/homeassistant/components/ecovacs/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/ecovacs", "requirements": ["sucks==0.9.4"], "codeowners": ["@OverloadUT"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["sleekxmppfs", "sucks"] } diff --git a/homeassistant/components/eddystone_temperature/manifest.json b/homeassistant/components/eddystone_temperature/manifest.json index 92ab636b87f9fb..64ec4bca3a7218 100644 --- a/homeassistant/components/eddystone_temperature/manifest.json +++ b/homeassistant/components/eddystone_temperature/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/eddystone_temperature", "requirements": ["beacontools[scan]==1.2.3", "construct==2.10.56"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["beacontools"] } diff --git a/homeassistant/components/edimax/manifest.json b/homeassistant/components/edimax/manifest.json index 6226968b5d3136..da89298c8739cc 100644 --- a/homeassistant/components/edimax/manifest.json +++ b/homeassistant/components/edimax/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/edimax", "requirements": ["pyedimax==0.2.1"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyedimax"] } diff --git a/homeassistant/components/edl21/manifest.json b/homeassistant/components/edl21/manifest.json index 7505f5e2438865..4cffabe87fc392 100644 --- a/homeassistant/components/edl21/manifest.json +++ b/homeassistant/components/edl21/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/edl21", "requirements": ["pysml==0.0.7"], "codeowners": ["@mtdcr"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["sml"] } diff --git a/homeassistant/components/efergy/manifest.json b/homeassistant/components/efergy/manifest.json index 966df3ed85875b..d3482738450d70 100644 --- a/homeassistant/components/efergy/manifest.json +++ b/homeassistant/components/efergy/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/efergy", "requirements": ["pyefergy==0.1.5"], "codeowners": ["@tkdrob"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["iso4217", "pyefergy"] } diff --git a/homeassistant/components/egardia/manifest.json b/homeassistant/components/egardia/manifest.json index 78e32a4d749652..7ea598e266cfa5 100644 --- a/homeassistant/components/egardia/manifest.json +++ b/homeassistant/components/egardia/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/egardia", "requirements": ["pythonegardia==1.0.40"], "codeowners": ["@jeroenterheerdt"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pythonegardia"] } diff --git a/homeassistant/components/eight_sleep/manifest.json b/homeassistant/components/eight_sleep/manifest.json index 06af3defac3acd..e4c5a1e0029527 100644 --- a/homeassistant/components/eight_sleep/manifest.json +++ b/homeassistant/components/eight_sleep/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/eight_sleep", "requirements": ["pyeight==0.2.0"], "codeowners": ["@mezz64", "@raman325"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyeight"] } diff --git a/homeassistant/components/elkm1/manifest.json b/homeassistant/components/elkm1/manifest.json index 3b341d90669cdc..2d84604d53a181 100644 --- a/homeassistant/components/elkm1/manifest.json +++ b/homeassistant/components/elkm1/manifest.json @@ -5,5 +5,6 @@ "requirements": ["elkm1-lib==1.0.0"], "codeowners": ["@gwww", "@bdraco"], "config_flow": true, - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["elkm1_lib"] } diff --git a/homeassistant/components/elmax/manifest.json b/homeassistant/components/elmax/manifest.json index b89ca55ce3db6a..8e230dcab38dc7 100644 --- a/homeassistant/components/elmax/manifest.json +++ b/homeassistant/components/elmax/manifest.json @@ -7,5 +7,6 @@ "codeowners": [ "@albertogeniola" ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["elmax_api"] } \ No newline at end of file diff --git a/homeassistant/components/elv/manifest.json b/homeassistant/components/elv/manifest.json index a5eb96e137681b..2ee922442e698f 100644 --- a/homeassistant/components/elv/manifest.json +++ b/homeassistant/components/elv/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/pca", "codeowners": ["@majuss"], "requirements": ["pypca==0.0.7"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pypca"] } diff --git a/homeassistant/components/emby/manifest.json b/homeassistant/components/emby/manifest.json index 00c05702db706e..f626ee165be373 100644 --- a/homeassistant/components/emby/manifest.json +++ b/homeassistant/components/emby/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/emby", "requirements": ["pyemby==1.8"], "codeowners": ["@mezz64"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pyemby"] } diff --git a/homeassistant/components/emonitor/manifest.json b/homeassistant/components/emonitor/manifest.json index 331597225f005b..c8ebdc415ecc2e 100644 --- a/homeassistant/components/emonitor/manifest.json +++ b/homeassistant/components/emonitor/manifest.json @@ -6,5 +6,6 @@ "requirements": ["aioemonitor==1.0.5"], "dhcp": [{ "hostname": "emonitor*", "macaddress": "0090C2*" }], "codeowners": ["@bdraco"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["aioemonitor"] } diff --git a/homeassistant/components/emulated_kasa/manifest.json b/homeassistant/components/emulated_kasa/manifest.json index d8d0596989376a..8506ad75e3f6df 100644 --- a/homeassistant/components/emulated_kasa/manifest.json +++ b/homeassistant/components/emulated_kasa/manifest.json @@ -5,5 +5,6 @@ "requirements": ["sense_energy==0.9.6"], "codeowners": ["@kbickar"], "quality_scale": "internal", - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["sense_energy"] } diff --git a/homeassistant/components/emulated_roku/manifest.json b/homeassistant/components/emulated_roku/manifest.json index 36a86137e8725e..c006a627f2f16f 100644 --- a/homeassistant/components/emulated_roku/manifest.json +++ b/homeassistant/components/emulated_roku/manifest.json @@ -6,5 +6,6 @@ "requirements": ["emulated_roku==0.2.1"], "dependencies": ["network"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["emulated_roku"] } diff --git a/homeassistant/components/enigma2/manifest.json b/homeassistant/components/enigma2/manifest.json index 37ed8a5c6bbc20..06bf8c7c0c843b 100644 --- a/homeassistant/components/enigma2/manifest.json +++ b/homeassistant/components/enigma2/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/enigma2", "requirements": ["openwebifpy==3.2.7"], "codeowners": ["@fbradyirl"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["openwebif"] } diff --git a/homeassistant/components/enocean/manifest.json b/homeassistant/components/enocean/manifest.json index 86db950ccc5d60..0fb4e9a9d3344c 100644 --- a/homeassistant/components/enocean/manifest.json +++ b/homeassistant/components/enocean/manifest.json @@ -5,5 +5,6 @@ "requirements": ["enocean==0.50"], "codeowners": ["@bdurrer"], "config_flow": true, - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["enocean"] } diff --git a/homeassistant/components/enphase_envoy/manifest.json b/homeassistant/components/enphase_envoy/manifest.json index d7ad10ca06223b..a27b5a6bc79e3d 100644 --- a/homeassistant/components/enphase_envoy/manifest.json +++ b/homeassistant/components/enphase_envoy/manifest.json @@ -14,5 +14,6 @@ "type": "_enphase-envoy._tcp.local." } ], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["envoy_reader"] } diff --git a/homeassistant/components/entur_public_transport/manifest.json b/homeassistant/components/entur_public_transport/manifest.json index 6f22689b9ca370..c7f4fbeef53df4 100644 --- a/homeassistant/components/entur_public_transport/manifest.json +++ b/homeassistant/components/entur_public_transport/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/entur_public_transport", "requirements": ["enturclient==0.2.3"], "codeowners": ["@hfurubotten"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["enturclient"] } diff --git a/homeassistant/components/environment_canada/manifest.json b/homeassistant/components/environment_canada/manifest.json index 868e62f07c34c1..4d1f1ecdff0e66 100644 --- a/homeassistant/components/environment_canada/manifest.json +++ b/homeassistant/components/environment_canada/manifest.json @@ -5,5 +5,6 @@ "requirements": ["env_canada==0.5.20"], "codeowners": ["@gwww", "@michaeldavie"], "config_flow": true, - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["env_canada"] } diff --git a/homeassistant/components/envisalink/manifest.json b/homeassistant/components/envisalink/manifest.json index 25290a5d431cc4..a7e6a29bfe8c5a 100644 --- a/homeassistant/components/envisalink/manifest.json +++ b/homeassistant/components/envisalink/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/envisalink", "requirements": ["pyenvisalink==4.3"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pyenvisalink"] } diff --git a/homeassistant/components/ephember/manifest.json b/homeassistant/components/ephember/manifest.json index 5abbc7b252a987..9d3047e442d3af 100644 --- a/homeassistant/components/ephember/manifest.json +++ b/homeassistant/components/ephember/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/ephember", "requirements": ["pyephember==0.3.1"], "codeowners": ["@ttroy50"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyephember"] } diff --git a/homeassistant/components/epson/manifest.json b/homeassistant/components/epson/manifest.json index 069956bdc9a19e..310b66c0d37a85 100644 --- a/homeassistant/components/epson/manifest.json +++ b/homeassistant/components/epson/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/epson", "requirements": ["epson-projector==0.4.2"], "codeowners": ["@pszafer"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["epson_projector"] } diff --git a/homeassistant/components/epsonworkforce/manifest.json b/homeassistant/components/epsonworkforce/manifest.json index 3fb7f1d598712e..f16299ae47422f 100644 --- a/homeassistant/components/epsonworkforce/manifest.json +++ b/homeassistant/components/epsonworkforce/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/epsonworkforce", "codeowners": ["@ThaStealth"], "requirements": ["epsonprinter==0.0.9"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["epsonprinter_pkg"] } diff --git a/homeassistant/components/eq3btsmart/manifest.json b/homeassistant/components/eq3btsmart/manifest.json index a644ff394e0353..4ad8d08adf552b 100644 --- a/homeassistant/components/eq3btsmart/manifest.json +++ b/homeassistant/components/eq3btsmart/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/eq3btsmart", "requirements": ["construct==2.10.56", "python-eq3bt==0.1.11"], "codeowners": ["@rytilahti"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["bluepy", "eq3bt"] } diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json index 042bf930d0ee02..81c85a93056827 100644 --- a/homeassistant/components/esphome/manifest.json +++ b/homeassistant/components/esphome/manifest.json @@ -7,5 +7,6 @@ "zeroconf": ["_esphomelib._tcp.local."], "codeowners": ["@OttoWinter", "@jesserockz"], "after_dependencies": ["zeroconf", "tag"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["aioesphomeapi", "noiseprotocol"] } diff --git a/homeassistant/components/etherscan/manifest.json b/homeassistant/components/etherscan/manifest.json index 7df8bb8d4f3a41..b5435201c23c00 100644 --- a/homeassistant/components/etherscan/manifest.json +++ b/homeassistant/components/etherscan/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/etherscan", "requirements": ["python-etherscan-api==0.0.3"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyetherscan"] } diff --git a/homeassistant/components/eufy/manifest.json b/homeassistant/components/eufy/manifest.json index 525283359c9f14..29b0f89cd4ba75 100644 --- a/homeassistant/components/eufy/manifest.json +++ b/homeassistant/components/eufy/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/eufy", "requirements": ["lakeside==0.12"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["lakeside"] } diff --git a/homeassistant/components/everlights/manifest.json b/homeassistant/components/everlights/manifest.json index bbb5e09c446ab1..f9a3af200590c0 100644 --- a/homeassistant/components/everlights/manifest.json +++ b/homeassistant/components/everlights/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/everlights", "requirements": ["pyeverlights==0.1.0"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyeverlights"] } diff --git a/homeassistant/components/evohome/manifest.json b/homeassistant/components/evohome/manifest.json index 09f9cf81cd1634..c2d8f98d40b449 100644 --- a/homeassistant/components/evohome/manifest.json +++ b/homeassistant/components/evohome/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/evohome", "requirements": ["evohome-async==0.3.15"], "codeowners": ["@zxdavb"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["evohomeasync", "evohomeasync2"] } diff --git a/homeassistant/components/ezviz/manifest.json b/homeassistant/components/ezviz/manifest.json index 5ce509bfc3c166..211e500cc7d262 100644 --- a/homeassistant/components/ezviz/manifest.json +++ b/homeassistant/components/ezviz/manifest.json @@ -6,5 +6,6 @@ "codeowners": ["@RenierM26", "@baqs"], "requirements": ["pyezviz==0.2.0.6"], "config_flow": true, - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["paho_mqtt", "pyezviz"] } diff --git a/homeassistant/components/faa_delays/manifest.json b/homeassistant/components/faa_delays/manifest.json index caa6c3bb33a9bf..d337ce72f862c3 100644 --- a/homeassistant/components/faa_delays/manifest.json +++ b/homeassistant/components/faa_delays/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/faa_delays", "requirements": ["faadelays==0.0.7"], "codeowners": ["@ntilley905"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["faadelays"] } diff --git a/homeassistant/components/familyhub/manifest.json b/homeassistant/components/familyhub/manifest.json index ecdafb22b56b3c..fbddcb4c0e6b26 100644 --- a/homeassistant/components/familyhub/manifest.json +++ b/homeassistant/components/familyhub/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/familyhub", "requirements": ["python-family-hub-local==0.0.2"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyfamilyhublocal"] } diff --git a/homeassistant/components/fastdotcom/manifest.json b/homeassistant/components/fastdotcom/manifest.json index af68bbf29932ec..ae953e4271562b 100644 --- a/homeassistant/components/fastdotcom/manifest.json +++ b/homeassistant/components/fastdotcom/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/fastdotcom", "requirements": ["fastdotcom==0.0.3"], "codeowners": ["@rohankapoorcom"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["fastdotcom"] } diff --git a/homeassistant/components/feedreader/manifest.json b/homeassistant/components/feedreader/manifest.json index 66874f760ffb22..1a9bb05e140d18 100644 --- a/homeassistant/components/feedreader/manifest.json +++ b/homeassistant/components/feedreader/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/feedreader", "requirements": ["feedparser==6.0.2"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["feedparser", "sgmllib3k"] } diff --git a/homeassistant/components/fibaro/manifest.json b/homeassistant/components/fibaro/manifest.json index b3a37cf9e5703d..7bc7d5a0e49d0b 100644 --- a/homeassistant/components/fibaro/manifest.json +++ b/homeassistant/components/fibaro/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/fibaro", "requirements": ["fiblary3==0.1.8"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["fiblary3"] } diff --git a/homeassistant/components/fido/manifest.json b/homeassistant/components/fido/manifest.json index 7de047114faaa9..b9cdd74baa8b20 100644 --- a/homeassistant/components/fido/manifest.json +++ b/homeassistant/components/fido/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/fido", "requirements": ["pyfido==2.1.1"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyfido"] } diff --git a/homeassistant/components/fints/manifest.json b/homeassistant/components/fints/manifest.json index 854f3a2f195f1a..ede1025a6db0c0 100644 --- a/homeassistant/components/fints/manifest.json +++ b/homeassistant/components/fints/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/fints", "requirements": ["fints==1.0.1"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["fints", "mt_940", "sepaxml"] } diff --git a/homeassistant/components/fireservicerota/manifest.json b/homeassistant/components/fireservicerota/manifest.json index 1eea9fbfbf1885..317f72dbae98a3 100644 --- a/homeassistant/components/fireservicerota/manifest.json +++ b/homeassistant/components/fireservicerota/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/fireservicerota", "requirements": ["pyfireservicerota==0.0.43"], "codeowners": ["@cyberjunky"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyfireservicerota"] } diff --git a/homeassistant/components/firmata/manifest.json b/homeassistant/components/firmata/manifest.json index 7af4624669bb05..ccfce906047afa 100644 --- a/homeassistant/components/firmata/manifest.json +++ b/homeassistant/components/firmata/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/firmata", "requirements": ["pymata-express==1.19"], "codeowners": ["@DaAwesomeP"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pymata_express"] } diff --git a/homeassistant/components/fitbit/manifest.json b/homeassistant/components/fitbit/manifest.json index b848a344f1f3c9..39bfa2c8e37f72 100644 --- a/homeassistant/components/fitbit/manifest.json +++ b/homeassistant/components/fitbit/manifest.json @@ -5,5 +5,6 @@ "requirements": ["fitbit==0.3.1"], "dependencies": ["configurator", "http"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["fitbit"] } diff --git a/homeassistant/components/fixer/manifest.json b/homeassistant/components/fixer/manifest.json index fa85a0283d86a5..87f2370aace16f 100644 --- a/homeassistant/components/fixer/manifest.json +++ b/homeassistant/components/fixer/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/fixer", "requirements": ["fixerio==1.0.0a0"], "codeowners": ["@fabaff"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["fixerio"] } diff --git a/homeassistant/components/fjaraskupan/manifest.json b/homeassistant/components/fjaraskupan/manifest.json index fb27d8b803f58a..d01995bd28b865 100644 --- a/homeassistant/components/fjaraskupan/manifest.json +++ b/homeassistant/components/fjaraskupan/manifest.json @@ -9,5 +9,6 @@ "codeowners": [ "@elupus" ], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["bleak", "fjaraskupan"] } \ No newline at end of file diff --git a/homeassistant/components/fleetgo/manifest.json b/homeassistant/components/fleetgo/manifest.json index 4e4d1200e56fbd..9f66c7e1cd71da 100644 --- a/homeassistant/components/fleetgo/manifest.json +++ b/homeassistant/components/fleetgo/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/fleetgo", "requirements": ["ritassist==0.9.2"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["geopy", "ritassist"] } diff --git a/homeassistant/components/flic/manifest.json b/homeassistant/components/flic/manifest.json index 7480257fcaa06d..bfbd919c05135a 100644 --- a/homeassistant/components/flic/manifest.json +++ b/homeassistant/components/flic/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/flic", "requirements": ["pyflic==2.0.3"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pyflic"] } diff --git a/homeassistant/components/flick_electric/manifest.json b/homeassistant/components/flick_electric/manifest.json index 75511aba4a1d91..0a79bff792ae9b 100644 --- a/homeassistant/components/flick_electric/manifest.json +++ b/homeassistant/components/flick_electric/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/flick_electric/", "requirements": ["PyFlick==0.0.2"], "codeowners": ["@ZephireNZ"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyflick"] } diff --git a/homeassistant/components/flipr/manifest.json b/homeassistant/components/flipr/manifest.json index 330fea7de8b967..357b5aeb160ca8 100644 --- a/homeassistant/components/flipr/manifest.json +++ b/homeassistant/components/flipr/manifest.json @@ -8,5 +8,6 @@ "codeowners": [ "@cnico" ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["flipr_api"] } diff --git a/homeassistant/components/flo/manifest.json b/homeassistant/components/flo/manifest.json index 6d1e002012c4eb..c93cd2bc6dd46e 100644 --- a/homeassistant/components/flo/manifest.json +++ b/homeassistant/components/flo/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/flo", "requirements": ["aioflo==2021.11.0"], "codeowners": ["@dmulcahey"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["aioflo"] } diff --git a/homeassistant/components/flume/manifest.json b/homeassistant/components/flume/manifest.json index cdad0dd3f0c395..05b0a4bf19aefc 100644 --- a/homeassistant/components/flume/manifest.json +++ b/homeassistant/components/flume/manifest.json @@ -10,5 +10,6 @@ "hostname": "flume-gw-*" } ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyflume"] } diff --git a/homeassistant/components/flunearyou/manifest.json b/homeassistant/components/flunearyou/manifest.json index 5fd3eb6638fe44..ee69961d1b0ca7 100644 --- a/homeassistant/components/flunearyou/manifest.json +++ b/homeassistant/components/flunearyou/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/flunearyou", "requirements": ["pyflunearyou==2.0.2"], "codeowners": ["@bachya"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyflunearyou"] } diff --git a/homeassistant/components/flux_led/manifest.json b/homeassistant/components/flux_led/manifest.json index ac324431ba6dbe..7eb75f54a5586a 100644 --- a/homeassistant/components/flux_led/manifest.json +++ b/homeassistant/components/flux_led/manifest.json @@ -44,6 +44,7 @@ "macaddress": "C82E47*", "hostname": "sta*" } - ] + ], + "loggers": ["flux_led"] } diff --git a/homeassistant/components/folder_watcher/manifest.json b/homeassistant/components/folder_watcher/manifest.json index c243c0d45c82ac..fb9a9ea5d63e4f 100644 --- a/homeassistant/components/folder_watcher/manifest.json +++ b/homeassistant/components/folder_watcher/manifest.json @@ -5,5 +5,6 @@ "requirements": ["watchdog==2.1.6"], "codeowners": [], "quality_scale": "internal", - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["watchdog"] } diff --git a/homeassistant/components/foobot/manifest.json b/homeassistant/components/foobot/manifest.json index b32ff6b4c8aab2..4bef77aee8a26d 100644 --- a/homeassistant/components/foobot/manifest.json +++ b/homeassistant/components/foobot/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/foobot", "requirements": ["foobot_async==1.0.0"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["foobot_async"] } diff --git a/homeassistant/components/forked_daapd/manifest.json b/homeassistant/components/forked_daapd/manifest.json index b802eac13c8d26..9a0372a193e607 100644 --- a/homeassistant/components/forked_daapd/manifest.json +++ b/homeassistant/components/forked_daapd/manifest.json @@ -6,5 +6,6 @@ "requirements": ["pyforked-daapd==0.1.11", "pylibrespot-java==0.1.0"], "config_flow": true, "zeroconf": ["_daap._tcp.local."], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pyforked_daapd", "pylibrespot_java"] } diff --git a/homeassistant/components/fortios/manifest.json b/homeassistant/components/fortios/manifest.json index cc351441cdd568..c7084d4cab470f 100644 --- a/homeassistant/components/fortios/manifest.json +++ b/homeassistant/components/fortios/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/fortios/", "requirements": ["fortiosapi==1.0.5"], "codeowners": ["@kimfrellsen"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["fortiosapi", "paramiko"] } diff --git a/homeassistant/components/foscam/manifest.json b/homeassistant/components/foscam/manifest.json index e2d9e5e501daa0..39103e3ea3ee55 100644 --- a/homeassistant/components/foscam/manifest.json +++ b/homeassistant/components/foscam/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/foscam", "requirements": ["libpyfoscam==1.0"], "codeowners": ["@skgsergio"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["libpyfoscam"] } diff --git a/homeassistant/components/free_mobile/manifest.json b/homeassistant/components/free_mobile/manifest.json index 7fb7f9986432d6..db3144e83e86ca 100644 --- a/homeassistant/components/free_mobile/manifest.json +++ b/homeassistant/components/free_mobile/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/free_mobile", "requirements": ["freesms==0.2.0"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["freesms"] } diff --git a/homeassistant/components/freebox/manifest.json b/homeassistant/components/freebox/manifest.json index 254be7b6857662..846bff5f8ce43a 100644 --- a/homeassistant/components/freebox/manifest.json +++ b/homeassistant/components/freebox/manifest.json @@ -6,5 +6,6 @@ "requirements": ["freebox-api==0.0.10"], "zeroconf": ["_fbx-api._tcp.local."], "codeowners": ["@hacf-fr", "@Quentame"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["freebox_api"] } diff --git a/homeassistant/components/freedompro/manifest.json b/homeassistant/components/freedompro/manifest.json index 94d57b37cae3f4..174862712688f8 100644 --- a/homeassistant/components/freedompro/manifest.json +++ b/homeassistant/components/freedompro/manifest.json @@ -7,5 +7,6 @@ "@stefano055415" ], "requirements": ["pyfreedompro==1.1.0"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyfreedompro"] } diff --git a/homeassistant/components/fritz/manifest.json b/homeassistant/components/fritz/manifest.json index 9e553abcd623f5..b278bd8d1961f2 100644 --- a/homeassistant/components/fritz/manifest.json +++ b/homeassistant/components/fritz/manifest.json @@ -19,5 +19,6 @@ "st": "urn:schemas-upnp-org:device:fritzbox:1" } ], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["fritzconnection"] } diff --git a/homeassistant/components/fritzbox/manifest.json b/homeassistant/components/fritzbox/manifest.json index 98c02d0166ed22..26dc9f65bc2c45 100644 --- a/homeassistant/components/fritzbox/manifest.json +++ b/homeassistant/components/fritzbox/manifest.json @@ -10,5 +10,6 @@ ], "codeowners": ["@mib1185", "@flabbamann"], "config_flow": true, - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyfritzhome"] } diff --git a/homeassistant/components/fritzbox_callmonitor/manifest.json b/homeassistant/components/fritzbox_callmonitor/manifest.json index b28d76a71cc85d..a33e01153b7cfa 100644 --- a/homeassistant/components/fritzbox_callmonitor/manifest.json +++ b/homeassistant/components/fritzbox_callmonitor/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/fritzbox_callmonitor", "requirements": ["fritzconnection==1.8.0"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["fritzconnection"] } diff --git a/homeassistant/components/fronius/manifest.json b/homeassistant/components/fronius/manifest.json index d2f3fc2e0f3511..f78489a2ea1c67 100644 --- a/homeassistant/components/fronius/manifest.json +++ b/homeassistant/components/fronius/manifest.json @@ -11,5 +11,6 @@ "iot_class": "local_polling", "name": "Fronius", "quality_scale": "platinum", - "requirements": ["pyfronius==0.7.1"] + "requirements": ["pyfronius==0.7.1"], + "loggers": ["pyfronius"] } diff --git a/homeassistant/components/geniushub/manifest.json b/homeassistant/components/geniushub/manifest.json index 698da72c3f4612..9d5bc7f9328854 100644 --- a/homeassistant/components/geniushub/manifest.json +++ b/homeassistant/components/geniushub/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/geniushub", "requirements": ["geniushub-client==0.6.30"], "codeowners": ["@zxdavb"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["geniushubclient"] } diff --git a/homeassistant/components/geo_json_events/manifest.json b/homeassistant/components/geo_json_events/manifest.json index aba5abff67c3a3..8f54c816649237 100644 --- a/homeassistant/components/geo_json_events/manifest.json +++ b/homeassistant/components/geo_json_events/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/geo_json_events", "requirements": ["geojson_client==0.6"], "codeowners": ["@exxamalte"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["geojson_client"] } diff --git a/homeassistant/components/geo_rss_events/manifest.json b/homeassistant/components/geo_rss_events/manifest.json index 6a470e1ddbdbb1..30dd4c5af50b3b 100644 --- a/homeassistant/components/geo_rss_events/manifest.json +++ b/homeassistant/components/geo_rss_events/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/geo_rss_events", "requirements": ["georss_generic_client==0.6"], "codeowners": ["@exxamalte"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["georss_client", "georss_generic_client"] } diff --git a/homeassistant/components/geonetnz_quakes/manifest.json b/homeassistant/components/geonetnz_quakes/manifest.json index 5668cd6cb3f066..ba8eecc4ae9202 100644 --- a/homeassistant/components/geonetnz_quakes/manifest.json +++ b/homeassistant/components/geonetnz_quakes/manifest.json @@ -6,5 +6,6 @@ "requirements": ["aio_geojson_geonetnz_quakes==0.13"], "codeowners": ["@exxamalte"], "quality_scale": "platinum", - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["aio_geojson_geonetnz_quakes"] } diff --git a/homeassistant/components/geonetnz_volcano/manifest.json b/homeassistant/components/geonetnz_volcano/manifest.json index dbd793c49b336e..a365237561afe0 100644 --- a/homeassistant/components/geonetnz_volcano/manifest.json +++ b/homeassistant/components/geonetnz_volcano/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/geonetnz_volcano", "requirements": ["aio_geojson_geonetnz_volcano==0.6"], "codeowners": ["@exxamalte"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["aio_geojson_geonetnz_volcano"] } diff --git a/homeassistant/components/gios/manifest.json b/homeassistant/components/gios/manifest.json index 0e7227797d2b25..20ad912d40cce1 100644 --- a/homeassistant/components/gios/manifest.json +++ b/homeassistant/components/gios/manifest.json @@ -6,5 +6,6 @@ "requirements": ["gios==2.1.0"], "config_flow": true, "quality_scale": "platinum", - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["dacite", "gios"] } diff --git a/homeassistant/components/github/manifest.json b/homeassistant/components/github/manifest.json index 474d08c4b0c274..7a23156759d239 100644 --- a/homeassistant/components/github/manifest.json +++ b/homeassistant/components/github/manifest.json @@ -10,5 +10,6 @@ "@ludeeus" ], "iot_class": "cloud_polling", - "config_flow": true + "config_flow": true, + "loggers": ["aiogithubapi"] } \ No newline at end of file diff --git a/homeassistant/components/gitlab_ci/manifest.json b/homeassistant/components/gitlab_ci/manifest.json index 77852e6d9828d7..07619623756039 100644 --- a/homeassistant/components/gitlab_ci/manifest.json +++ b/homeassistant/components/gitlab_ci/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/gitlab_ci", "requirements": ["python-gitlab==1.6.0"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["gitlab"] } diff --git a/homeassistant/components/gitter/manifest.json b/homeassistant/components/gitter/manifest.json index bbf02d1ec9eac5..efd4ff3d28bbd8 100644 --- a/homeassistant/components/gitter/manifest.json +++ b/homeassistant/components/gitter/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/gitter", "requirements": ["gitterpy==0.1.7"], "codeowners": ["@fabaff"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["gitterpy"] } diff --git a/homeassistant/components/glances/manifest.json b/homeassistant/components/glances/manifest.json index 2b0b0f1a4eea23..cce95957ff6128 100644 --- a/homeassistant/components/glances/manifest.json +++ b/homeassistant/components/glances/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/glances", "requirements": ["glances_api==0.3.4"], "codeowners": ["@fabaff", "@engrbm87"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["glances_api"] } diff --git a/homeassistant/components/gntp/manifest.json b/homeassistant/components/gntp/manifest.json index ebef78f9e7fd1a..3a5f4fb8daab97 100644 --- a/homeassistant/components/gntp/manifest.json +++ b/homeassistant/components/gntp/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/gntp", "requirements": ["gntp==1.0.3"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["gntp"] } diff --git a/homeassistant/components/goalfeed/manifest.json b/homeassistant/components/goalfeed/manifest.json index 5b064551cf9229..a8d90d87ac3392 100644 --- a/homeassistant/components/goalfeed/manifest.json +++ b/homeassistant/components/goalfeed/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/goalfeed", "requirements": ["pysher==1.0.1"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["pysher"] } diff --git a/homeassistant/components/goalzero/manifest.json b/homeassistant/components/goalzero/manifest.json index f46401d2a6be43..04bd538322e1a3 100644 --- a/homeassistant/components/goalzero/manifest.json +++ b/homeassistant/components/goalzero/manifest.json @@ -9,5 +9,6 @@ ], "codeowners": ["@tkdrob"], "quality_scale": "silver", - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["goalzero"] } diff --git a/homeassistant/components/gogogate2/manifest.json b/homeassistant/components/gogogate2/manifest.json index 90d50bdda4335c..b438174c256099 100644 --- a/homeassistant/components/gogogate2/manifest.json +++ b/homeassistant/components/gogogate2/manifest.json @@ -13,5 +13,6 @@ "hostname": "ismartgate*" } ], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["ismartgate"] } diff --git a/homeassistant/components/goodwe/manifest.json b/homeassistant/components/goodwe/manifest.json index 1116451e15f5e5..102cd0ac2eb796 100644 --- a/homeassistant/components/goodwe/manifest.json +++ b/homeassistant/components/goodwe/manifest.json @@ -8,5 +8,6 @@ ], "requirements": ["goodwe==0.2.15"], "config_flow": true, - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["goodwe"] } \ No newline at end of file diff --git a/homeassistant/components/google/manifest.json b/homeassistant/components/google/manifest.json index 00d76a9c1d0505..1695c7d0d843de 100644 --- a/homeassistant/components/google/manifest.json +++ b/homeassistant/components/google/manifest.json @@ -8,5 +8,6 @@ "oauth2client==4.1.3" ], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["googleapiclient"] } diff --git a/homeassistant/components/google_maps/manifest.json b/homeassistant/components/google_maps/manifest.json index f0f403912a6308..e8c3af23398169 100644 --- a/homeassistant/components/google_maps/manifest.json +++ b/homeassistant/components/google_maps/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/google_maps", "requirements": ["locationsharinglib==4.1.5"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["locationsharinglib"] } diff --git a/homeassistant/components/google_translate/manifest.json b/homeassistant/components/google_translate/manifest.json index b566f3447f470d..70f5e129950d16 100644 --- a/homeassistant/components/google_translate/manifest.json +++ b/homeassistant/components/google_translate/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/google_translate", "requirements": ["gTTS==2.2.3"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["gtts"] } diff --git a/homeassistant/components/google_travel_time/manifest.json b/homeassistant/components/google_travel_time/manifest.json index 8800b4ef4b8a40..5b353141215490 100644 --- a/homeassistant/components/google_travel_time/manifest.json +++ b/homeassistant/components/google_travel_time/manifest.json @@ -5,5 +5,6 @@ "requirements": ["googlemaps==2.5.1"], "codeowners": [], "config_flow": true, - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["googlemaps"] } diff --git a/homeassistant/components/gpsd/manifest.json b/homeassistant/components/gpsd/manifest.json index 9053bb7ddfcdc5..b69ec09bbe7d82 100644 --- a/homeassistant/components/gpsd/manifest.json +++ b/homeassistant/components/gpsd/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/gpsd", "requirements": ["gps3==0.33.3"], "codeowners": ["@fabaff"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["gps3"] } diff --git a/homeassistant/components/gree/manifest.json b/homeassistant/components/gree/manifest.json index a828789daea1b5..c3b4f1f028a6ec 100644 --- a/homeassistant/components/gree/manifest.json +++ b/homeassistant/components/gree/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/gree", "requirements": ["greeclimate==1.0.2"], "codeowners": ["@cmroche"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["greeclimate"] } diff --git a/homeassistant/components/greeneye_monitor/manifest.json b/homeassistant/components/greeneye_monitor/manifest.json index a243d767d9931e..0f4bef2b38f897 100644 --- a/homeassistant/components/greeneye_monitor/manifest.json +++ b/homeassistant/components/greeneye_monitor/manifest.json @@ -8,5 +8,6 @@ "codeowners": [ "@jkeljo" ], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["greeneye"] } \ No newline at end of file diff --git a/homeassistant/components/greenwave/manifest.json b/homeassistant/components/greenwave/manifest.json index 3d9aca1a0f90de..503719c425be85 100644 --- a/homeassistant/components/greenwave/manifest.json +++ b/homeassistant/components/greenwave/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/greenwave", "requirements": ["greenwavereality==0.5.1"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["greenwavereality"] } diff --git a/homeassistant/components/growatt_server/manifest.json b/homeassistant/components/growatt_server/manifest.json index 79472359ab912a..c8a71d426e724e 100644 --- a/homeassistant/components/growatt_server/manifest.json +++ b/homeassistant/components/growatt_server/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/growatt_server/", "requirements": ["growattServer==1.1.0"], "codeowners": ["@indykoning", "@muppet3000", "@JasperPlant"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["growattServer"] } diff --git a/homeassistant/components/gstreamer/manifest.json b/homeassistant/components/gstreamer/manifest.json index 9957e4602bd23c..1efdc685a2411d 100644 --- a/homeassistant/components/gstreamer/manifest.json +++ b/homeassistant/components/gstreamer/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/gstreamer", "requirements": ["gstreamer-player==1.1.2"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["gsp"] } diff --git a/homeassistant/components/gtfs/manifest.json b/homeassistant/components/gtfs/manifest.json index 4de42e3190afa8..8dfb37ad551708 100644 --- a/homeassistant/components/gtfs/manifest.json +++ b/homeassistant/components/gtfs/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/gtfs", "requirements": ["pygtfs==0.1.6"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pygtfs"] } diff --git a/homeassistant/components/guardian/manifest.json b/homeassistant/components/guardian/manifest.json index 90e33a82452a82..7ba3e1971f4060 100644 --- a/homeassistant/components/guardian/manifest.json +++ b/homeassistant/components/guardian/manifest.json @@ -20,5 +20,6 @@ "hostname": "guardian*", "macaddress": "30AEA4*" } - ] + ], + "loggers": ["aioguardian"] } diff --git a/homeassistant/components/habitica/manifest.json b/homeassistant/components/habitica/manifest.json index 4967a6e87ba81c..fdf170e2ede5fe 100644 --- a/homeassistant/components/habitica/manifest.json +++ b/homeassistant/components/habitica/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/habitica", "requirements": ["habitipy==0.2.0"], "codeowners": ["@ASMfreaK", "@leikoilja"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["habitipy", "plumbum"] } diff --git a/homeassistant/components/hangouts/manifest.json b/homeassistant/components/hangouts/manifest.json index a4c338aa632a75..983dc60414a647 100644 --- a/homeassistant/components/hangouts/manifest.json +++ b/homeassistant/components/hangouts/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/hangouts", "requirements": ["hangups==0.4.17"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["hangups", "urwid"] } diff --git a/homeassistant/components/harman_kardon_avr/manifest.json b/homeassistant/components/harman_kardon_avr/manifest.json index a7f4fffa4d682a..8a029ae63392ea 100644 --- a/homeassistant/components/harman_kardon_avr/manifest.json +++ b/homeassistant/components/harman_kardon_avr/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/harman_kardon_avr", "requirements": ["hkavr==0.0.5"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["hkavr"] } diff --git a/homeassistant/components/harmony/manifest.json b/homeassistant/components/harmony/manifest.json index 948178901609db..ab5848a1fb7660 100644 --- a/homeassistant/components/harmony/manifest.json +++ b/homeassistant/components/harmony/manifest.json @@ -18,5 +18,6 @@ ], "dependencies": ["remote", "switch"], "config_flow": true, - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["aioharmony", "slixmpp"] } diff --git a/homeassistant/components/hdmi_cec/manifest.json b/homeassistant/components/hdmi_cec/manifest.json index 08797541eed58f..ff2411db35afe0 100644 --- a/homeassistant/components/hdmi_cec/manifest.json +++ b/homeassistant/components/hdmi_cec/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/hdmi_cec", "requirements": ["pyCEC==0.5.1"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pycec"] } diff --git a/homeassistant/components/heatmiser/manifest.json b/homeassistant/components/heatmiser/manifest.json index 772171660529be..8b783e40758eeb 100644 --- a/homeassistant/components/heatmiser/manifest.json +++ b/homeassistant/components/heatmiser/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/heatmiser", "requirements": ["heatmiserV3==1.1.18"], "codeowners": ["@andylockran"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["heatmiserV3"] } diff --git a/homeassistant/components/heos/manifest.json b/homeassistant/components/heos/manifest.json index 94794bf536d842..ba7f2e3664cb62 100644 --- a/homeassistant/components/heos/manifest.json +++ b/homeassistant/components/heos/manifest.json @@ -10,5 +10,6 @@ } ], "codeowners": ["@andrewsayre"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pyheos"] } diff --git a/homeassistant/components/here_travel_time/manifest.json b/homeassistant/components/here_travel_time/manifest.json index 9a3e8bd482717b..b620153bba7b05 100644 --- a/homeassistant/components/here_travel_time/manifest.json +++ b/homeassistant/components/here_travel_time/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/here_travel_time", "requirements": ["herepy==2.0.0"], "codeowners": ["@eifinger"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["herepy"] } diff --git a/homeassistant/components/hikvision/manifest.json b/homeassistant/components/hikvision/manifest.json index a8f8940114823c..7209ee00024074 100644 --- a/homeassistant/components/hikvision/manifest.json +++ b/homeassistant/components/hikvision/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/hikvision", "requirements": ["pyhik==0.3.0"], "codeowners": ["@mezz64"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pyhik"] } diff --git a/homeassistant/components/hikvisioncam/manifest.json b/homeassistant/components/hikvisioncam/manifest.json index 61c629655cecf5..84f7f4e28e1212 100644 --- a/homeassistant/components/hikvisioncam/manifest.json +++ b/homeassistant/components/hikvisioncam/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/hikvisioncam", "requirements": ["hikvision==0.4"], "codeowners": ["@fbradyirl"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["hikvision"] } diff --git a/homeassistant/components/hisense_aehw4a1/manifest.json b/homeassistant/components/hisense_aehw4a1/manifest.json index 514ee712710b81..d0e669783d710d 100644 --- a/homeassistant/components/hisense_aehw4a1/manifest.json +++ b/homeassistant/components/hisense_aehw4a1/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/hisense_aehw4a1", "requirements": ["pyaehw4a1==0.3.9"], "codeowners": ["@bannhead"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyaehw4a1"] } diff --git a/homeassistant/components/hive/manifest.json b/homeassistant/components/hive/manifest.json index 5f23eef642b12f..2ce097fb8cc040 100644 --- a/homeassistant/components/hive/manifest.json +++ b/homeassistant/components/hive/manifest.json @@ -10,5 +10,6 @@ "@Rendili", "@KJonline" ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["apyhiveapi"] } \ No newline at end of file diff --git a/homeassistant/components/hlk_sw16/manifest.json b/homeassistant/components/hlk_sw16/manifest.json index 1bd0a73b7ab978..12638679f5a7c9 100644 --- a/homeassistant/components/hlk_sw16/manifest.json +++ b/homeassistant/components/hlk_sw16/manifest.json @@ -5,5 +5,6 @@ "requirements": ["hlk-sw16==0.0.9"], "codeowners": ["@jameshilliard"], "config_flow": true, - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["hlk_sw16"] } diff --git a/homeassistant/components/home_connect/manifest.json b/homeassistant/components/home_connect/manifest.json index b9a4f8e6ddb63a..5667d5399021ea 100644 --- a/homeassistant/components/home_connect/manifest.json +++ b/homeassistant/components/home_connect/manifest.json @@ -6,5 +6,6 @@ "codeowners": ["@DavidMStraub"], "requirements": ["homeconnect==0.6.3"], "config_flow": true, - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["homeconnect"] } diff --git a/homeassistant/components/home_plus_control/manifest.json b/homeassistant/components/home_plus_control/manifest.json index edbf0147e145a3..30ef34b6b34449 100644 --- a/homeassistant/components/home_plus_control/manifest.json +++ b/homeassistant/components/home_plus_control/manifest.json @@ -6,5 +6,6 @@ "requirements": ["homepluscontrol==0.0.5"], "dependencies": ["http"], "codeowners": ["@chemaaa"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["homepluscontrol"] } diff --git a/homeassistant/components/homekit/manifest.json b/homeassistant/components/homekit/manifest.json index 4b54468e092b60..9981b3a1109e20 100644 --- a/homeassistant/components/homekit/manifest.json +++ b/homeassistant/components/homekit/manifest.json @@ -13,5 +13,6 @@ "codeowners": ["@bdraco"], "zeroconf": ["_homekit._tcp.local."], "config_flow": true, - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pyhap"] } diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index 7133871da42dca..cdae867ca85971 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -7,5 +7,6 @@ "zeroconf": ["_hap._tcp.local."], "after_dependencies": ["zeroconf"], "codeowners": ["@Jc2k", "@bdraco"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["aiohomekit", "commentjson"] } diff --git a/homeassistant/components/homematic/manifest.json b/homeassistant/components/homematic/manifest.json index 6482db7ae60278..f6ba16b1c5a2d1 100644 --- a/homeassistant/components/homematic/manifest.json +++ b/homeassistant/components/homematic/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/homematic", "requirements": ["pyhomematic==0.1.77"], "codeowners": ["@pvizeli", "@danielperna84"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pyhomematic"] } diff --git a/homeassistant/components/homematicip_cloud/manifest.json b/homeassistant/components/homematicip_cloud/manifest.json index b41c7b06c74da9..f5fc8bc61cc379 100644 --- a/homeassistant/components/homematicip_cloud/manifest.json +++ b/homeassistant/components/homematicip_cloud/manifest.json @@ -6,5 +6,6 @@ "requirements": ["homematicip==1.0.1"], "codeowners": [], "quality_scale": "platinum", - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["homematicip"] } diff --git a/homeassistant/components/homewizard/manifest.json b/homeassistant/components/homewizard/manifest.json index 641bfca520eac4..870b52446ba651 100644 --- a/homeassistant/components/homewizard/manifest.json +++ b/homeassistant/components/homewizard/manifest.json @@ -9,5 +9,6 @@ ], "zeroconf": ["_hwenergy._tcp.local."], "config_flow": true, - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["aiohwenergy"] } diff --git a/homeassistant/components/homeworks/manifest.json b/homeassistant/components/homeworks/manifest.json index 7dc7c602b9804f..70723fc3676993 100644 --- a/homeassistant/components/homeworks/manifest.json +++ b/homeassistant/components/homeworks/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/homeworks", "requirements": ["pyhomeworks==0.0.6"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pyhomeworks"] } diff --git a/homeassistant/components/honeywell/manifest.json b/homeassistant/components/honeywell/manifest.json index 9bf4932a95362d..7ea878f074e673 100644 --- a/homeassistant/components/honeywell/manifest.json +++ b/homeassistant/components/honeywell/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/honeywell", "requirements": ["somecomfort==0.8.0"], "codeowners": ["@rdfurman"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["somecomfort"] } diff --git a/homeassistant/components/horizon/manifest.json b/homeassistant/components/horizon/manifest.json index 09e6066e573728..7a3a2ced5f7b55 100644 --- a/homeassistant/components/horizon/manifest.json +++ b/homeassistant/components/horizon/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/horizon", "requirements": ["horimote==0.4.1"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["horimote"] } diff --git a/homeassistant/components/html5/manifest.json b/homeassistant/components/html5/manifest.json index 49f44634bcb01d..66d3c84452ac49 100644 --- a/homeassistant/components/html5/manifest.json +++ b/homeassistant/components/html5/manifest.json @@ -5,5 +5,6 @@ "requirements": ["pywebpush==1.9.2"], "dependencies": ["http"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["http_ece", "py_vapid", "pywebpush"] } diff --git a/homeassistant/components/htu21d/manifest.json b/homeassistant/components/htu21d/manifest.json index 6f7ff77efb78c2..c554c775079bd6 100644 --- a/homeassistant/components/htu21d/manifest.json +++ b/homeassistant/components/htu21d/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/htu21d", "requirements": ["i2csense==0.0.4", "smbus-cffi==0.5.1"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["i2csense", "smbus"] } diff --git a/homeassistant/components/huawei_lte/manifest.json b/homeassistant/components/huawei_lte/manifest.json index 9cfc008921bf19..3e7ebf24b16373 100644 --- a/homeassistant/components/huawei_lte/manifest.json +++ b/homeassistant/components/huawei_lte/manifest.json @@ -15,5 +15,6 @@ } ], "codeowners": ["@scop", "@fphammerle"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["huawei_lte_api"] } diff --git a/homeassistant/components/hue/manifest.json b/homeassistant/components/hue/manifest.json index 832592f3f1bf56..b9ffea7d3dfc28 100644 --- a/homeassistant/components/hue/manifest.json +++ b/homeassistant/components/hue/manifest.json @@ -24,5 +24,6 @@ "zeroconf": ["_hue._tcp.local."], "codeowners": ["@balloob", "@marcelveldt"], "quality_scale": "platinum", - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["aiohue"] } diff --git a/homeassistant/components/huisbaasje/manifest.json b/homeassistant/components/huisbaasje/manifest.json index 6b9981fee2379a..8640f126ae41af 100644 --- a/homeassistant/components/huisbaasje/manifest.json +++ b/homeassistant/components/huisbaasje/manifest.json @@ -9,5 +9,6 @@ "codeowners": [ "@dennisschroer" ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["huisbaasje"] } \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/manifest.json b/homeassistant/components/hunterdouglas_powerview/manifest.json index ade3b25f31c883..29b260c2fa3da6 100644 --- a/homeassistant/components/hunterdouglas_powerview/manifest.json +++ b/homeassistant/components/hunterdouglas_powerview/manifest.json @@ -15,5 +15,6 @@ } ], "zeroconf": ["_powerview._tcp.local."], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["aiopvapi"] } diff --git a/homeassistant/components/hvv_departures/manifest.json b/homeassistant/components/hvv_departures/manifest.json index 71a6abdfbdd54c..f0334b5af92064 100644 --- a/homeassistant/components/hvv_departures/manifest.json +++ b/homeassistant/components/hvv_departures/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/hvv_departures", "requirements": ["pygti==0.9.2"], "codeowners": ["@vigonotion"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pygti"] } diff --git a/homeassistant/components/hydrawise/manifest.json b/homeassistant/components/hydrawise/manifest.json index e9656b69eb81bb..8db827a8c35c45 100644 --- a/homeassistant/components/hydrawise/manifest.json +++ b/homeassistant/components/hydrawise/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/hydrawise", "requirements": ["hydrawiser==0.2"], "codeowners": ["@ptcryan"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["hydrawiser"] } diff --git a/homeassistant/components/hyperion/manifest.json b/homeassistant/components/hyperion/manifest.json index 4f247b3e937c23..8a886053361d78 100644 --- a/homeassistant/components/hyperion/manifest.json +++ b/homeassistant/components/hyperion/manifest.json @@ -12,5 +12,6 @@ "st": "urn:hyperion-project.org:device:basic:1" } ], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["hyperion"] } diff --git a/homeassistant/components/ialarm/manifest.json b/homeassistant/components/ialarm/manifest.json index 751faec56c717a..60ecb9da74ab86 100644 --- a/homeassistant/components/ialarm/manifest.json +++ b/homeassistant/components/ialarm/manifest.json @@ -5,5 +5,6 @@ "requirements": ["pyialarm==1.9.0"], "codeowners": ["@RyuzakiKK"], "config_flow": true, - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyialarm"] } diff --git a/homeassistant/components/iammeter/manifest.json b/homeassistant/components/iammeter/manifest.json index e0e0b68bcf4547..2263b583dddad8 100644 --- a/homeassistant/components/iammeter/manifest.json +++ b/homeassistant/components/iammeter/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/iammeter", "codeowners": ["@lewei50"], "requirements": ["iammeter==0.1.7"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["iammeter"] } diff --git a/homeassistant/components/iaqualink/manifest.json b/homeassistant/components/iaqualink/manifest.json index 8061163943daf6..7c57744fd3b0a9 100644 --- a/homeassistant/components/iaqualink/manifest.json +++ b/homeassistant/components/iaqualink/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/iaqualink/", "codeowners": ["@flz"], "requirements": ["iaqualink==0.4.1"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["iaqualink"] } diff --git a/homeassistant/components/icloud/manifest.json b/homeassistant/components/icloud/manifest.json index 6c40ef6bf03b24..4b1d89e59b3cf2 100644 --- a/homeassistant/components/icloud/manifest.json +++ b/homeassistant/components/icloud/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/icloud", "requirements": ["pyicloud==0.10.2"], "codeowners": ["@Quentame", "@nzapponi"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["keyrings.alt", "pyicloud"] } diff --git a/homeassistant/components/idteck_prox/manifest.json b/homeassistant/components/idteck_prox/manifest.json index aa18ead9b6e411..005307b24e18a1 100644 --- a/homeassistant/components/idteck_prox/manifest.json +++ b/homeassistant/components/idteck_prox/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/idteck_prox", "requirements": ["rfk101py==0.0.1"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["rfk101py"] } diff --git a/homeassistant/components/ifttt/manifest.json b/homeassistant/components/ifttt/manifest.json index a4699853b01747..35daf519769c85 100644 --- a/homeassistant/components/ifttt/manifest.json +++ b/homeassistant/components/ifttt/manifest.json @@ -6,5 +6,6 @@ "requirements": ["pyfttt==0.3"], "dependencies": ["webhook"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["pyfttt"] } diff --git a/homeassistant/components/iglo/manifest.json b/homeassistant/components/iglo/manifest.json index b96769af932a0f..5184bc8c1051f8 100644 --- a/homeassistant/components/iglo/manifest.json +++ b/homeassistant/components/iglo/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/iglo", "requirements": ["iglo==1.2.7"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["iglo"] } diff --git a/homeassistant/components/ign_sismologia/manifest.json b/homeassistant/components/ign_sismologia/manifest.json index e80e3a4eeec120..97836e7f1451e4 100644 --- a/homeassistant/components/ign_sismologia/manifest.json +++ b/homeassistant/components/ign_sismologia/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/ign_sismologia", "requirements": ["georss_ign_sismologia_client==0.3"], "codeowners": ["@exxamalte"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["georss_ign_sismologia_client"] } diff --git a/homeassistant/components/ihc/manifest.json b/homeassistant/components/ihc/manifest.json index d6b90f13f8a049..e899a794e070f4 100644 --- a/homeassistant/components/ihc/manifest.json +++ b/homeassistant/components/ihc/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/ihc", "requirements": ["defusedxml==0.7.1", "ihcsdk==2.7.6"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["ihcsdk"] } diff --git a/homeassistant/components/imap/manifest.json b/homeassistant/components/imap/manifest.json index c18234597455d3..655590005bf0d8 100644 --- a/homeassistant/components/imap/manifest.json +++ b/homeassistant/components/imap/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/imap", "requirements": ["aioimaplib==0.9.0"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["aioimaplib"] } diff --git a/homeassistant/components/incomfort/manifest.json b/homeassistant/components/incomfort/manifest.json index 7e8a00aee72a4f..11946e6238debe 100644 --- a/homeassistant/components/incomfort/manifest.json +++ b/homeassistant/components/incomfort/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/incomfort", "requirements": ["incomfort-client==0.4.4"], "codeowners": ["@zxdavb"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["incomfortclient"] } diff --git a/homeassistant/components/influxdb/manifest.json b/homeassistant/components/influxdb/manifest.json index 2c537c7b35a4e5..df2feab5146c0b 100644 --- a/homeassistant/components/influxdb/manifest.json +++ b/homeassistant/components/influxdb/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/influxdb", "requirements": ["influxdb==5.3.1", "influxdb-client==1.24.0"], "codeowners": ["@fabaff", "@mdegat01"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["influxdb", "influxdb_client"] } diff --git a/homeassistant/components/insteon/manifest.json b/homeassistant/components/insteon/manifest.json index e00a85a98235a6..595afd061cc16a 100644 --- a/homeassistant/components/insteon/manifest.json +++ b/homeassistant/components/insteon/manifest.json @@ -9,5 +9,6 @@ "@teharris1" ], "config_flow": true, - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pyinsteon", "pypubsub"] } diff --git a/homeassistant/components/intellifire/manifest.json b/homeassistant/components/intellifire/manifest.json index 42edf00ad25af3..5009e25cb60e04 100644 --- a/homeassistant/components/intellifire/manifest.json +++ b/homeassistant/components/intellifire/manifest.json @@ -6,5 +6,6 @@ "requirements": ["intellifire4py==0.5"], "dependencies": [], "codeowners": ["@jeeftor"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["intellifire4py"] } diff --git a/homeassistant/components/intesishome/manifest.json b/homeassistant/components/intesishome/manifest.json index 44d4d4ca582496..6b84f735c12d4a 100644 --- a/homeassistant/components/intesishome/manifest.json +++ b/homeassistant/components/intesishome/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/intesishome", "codeowners": ["@jnimmo"], "requirements": ["pyintesishome==1.7.6"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["pyintesishome"] } diff --git a/homeassistant/components/iotawatt/manifest.json b/homeassistant/components/iotawatt/manifest.json index 42e1e074c8e108..5addb8699947d4 100644 --- a/homeassistant/components/iotawatt/manifest.json +++ b/homeassistant/components/iotawatt/manifest.json @@ -10,5 +10,6 @@ "@gtdiehl", "@jyavenard" ], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["iotawattpy"] } \ No newline at end of file diff --git a/homeassistant/components/iperf3/manifest.json b/homeassistant/components/iperf3/manifest.json index 6cebb34bc63474..463f921f03bc48 100644 --- a/homeassistant/components/iperf3/manifest.json +++ b/homeassistant/components/iperf3/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/iperf3", "requirements": ["iperf3==0.1.11"], "codeowners": ["@rohankapoorcom"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["iperf3"] } diff --git a/homeassistant/components/ipma/manifest.json b/homeassistant/components/ipma/manifest.json index 06079bf0b5c488..902a03b6c83b78 100644 --- a/homeassistant/components/ipma/manifest.json +++ b/homeassistant/components/ipma/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/ipma", "requirements": ["pyipma==2.0.5"], "codeowners": ["@dgomes", "@abmantis"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["geopy", "pyipma"] } diff --git a/homeassistant/components/ipp/manifest.json b/homeassistant/components/ipp/manifest.json index 18bfc3abc54808..39e798f99bff7d 100644 --- a/homeassistant/components/ipp/manifest.json +++ b/homeassistant/components/ipp/manifest.json @@ -7,5 +7,6 @@ "config_flow": true, "quality_scale": "platinum", "zeroconf": ["_ipps._tcp.local.", "_ipp._tcp.local."], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["deepmerge", "pyipp"] } diff --git a/homeassistant/components/iqvia/manifest.json b/homeassistant/components/iqvia/manifest.json index f78ca1e258cd49..dc91ede5461e6e 100644 --- a/homeassistant/components/iqvia/manifest.json +++ b/homeassistant/components/iqvia/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/iqvia", "requirements": ["numpy==1.21.4", "pyiqvia==2021.11.0"], "codeowners": ["@bachya"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyiqvia"] } diff --git a/homeassistant/components/irish_rail_transport/manifest.json b/homeassistant/components/irish_rail_transport/manifest.json index 4263d5288ff1e2..d6938916c9ad2e 100644 --- a/homeassistant/components/irish_rail_transport/manifest.json +++ b/homeassistant/components/irish_rail_transport/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/irish_rail_transport", "requirements": ["pyirishrail==0.0.2"], "codeowners": ["@ttroy50"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyirishrail"] } diff --git a/homeassistant/components/islamic_prayer_times/manifest.json b/homeassistant/components/islamic_prayer_times/manifest.json index e72eb0a6da73ce..455f3bab675c27 100644 --- a/homeassistant/components/islamic_prayer_times/manifest.json +++ b/homeassistant/components/islamic_prayer_times/manifest.json @@ -5,5 +5,6 @@ "requirements": ["prayer_times_calculator==0.0.5"], "codeowners": ["@engrbm87"], "config_flow": true, - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["prayer_times_calculator"] } diff --git a/homeassistant/components/iss/manifest.json b/homeassistant/components/iss/manifest.json index be34babeeae5ac..740dbbb9ff43ef 100644 --- a/homeassistant/components/iss/manifest.json +++ b/homeassistant/components/iss/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/iss", "requirements": ["pyiss==1.0.1"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyiss"] } diff --git a/homeassistant/components/isy994/manifest.json b/homeassistant/components/isy994/manifest.json index 792629f801c21a..23a2f07b970425 100644 --- a/homeassistant/components/isy994/manifest.json +++ b/homeassistant/components/isy994/manifest.json @@ -12,5 +12,6 @@ } ], "dhcp": [{ "hostname": "isy*", "macaddress": "0021B9*" }], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pyisy"] } diff --git a/homeassistant/components/izone/manifest.json b/homeassistant/components/izone/manifest.json index 9cdf30ad42b9ae..b86e86e2b58998 100644 --- a/homeassistant/components/izone/manifest.json +++ b/homeassistant/components/izone/manifest.json @@ -8,5 +8,6 @@ "homekit": { "models": ["iZone"] }, - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pizone"] } diff --git a/homeassistant/components/jellyfin/manifest.json b/homeassistant/components/jellyfin/manifest.json index 345cecc2eb66e6..ce00edfc10838a 100644 --- a/homeassistant/components/jellyfin/manifest.json +++ b/homeassistant/components/jellyfin/manifest.json @@ -9,5 +9,6 @@ "iot_class": "local_polling", "codeowners": [ "@j-stienstra" - ] + ], + "loggers": ["jellyfin_apiclient_python"] } \ No newline at end of file diff --git a/homeassistant/components/jewish_calendar/manifest.json b/homeassistant/components/jewish_calendar/manifest.json index ef77dc045806d8..9077fef50fdaf6 100644 --- a/homeassistant/components/jewish_calendar/manifest.json +++ b/homeassistant/components/jewish_calendar/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/jewish_calendar", "requirements": ["hdate==0.10.4"], "codeowners": ["@tsvi"], - "iot_class": "calculated" + "iot_class": "calculated", + "loggers": ["hdate"] } diff --git a/homeassistant/components/joaoapps_join/manifest.json b/homeassistant/components/joaoapps_join/manifest.json index a9d67e915fa49c..b56f4a091f0853 100644 --- a/homeassistant/components/joaoapps_join/manifest.json +++ b/homeassistant/components/joaoapps_join/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/joaoapps_join", "requirements": ["python-join-api==0.0.6"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["pyjoin"] } diff --git a/homeassistant/components/juicenet/manifest.json b/homeassistant/components/juicenet/manifest.json index d56977dc9df477..35e9414a1e60a7 100644 --- a/homeassistant/components/juicenet/manifest.json +++ b/homeassistant/components/juicenet/manifest.json @@ -5,5 +5,6 @@ "requirements": ["python-juicenet==1.0.2"], "codeowners": ["@jesserockz"], "config_flow": true, - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyjuicenet"] } diff --git a/homeassistant/components/kaiterra/manifest.json b/homeassistant/components/kaiterra/manifest.json index 1bdcd7670e64ee..9f2a4c0013f540 100644 --- a/homeassistant/components/kaiterra/manifest.json +++ b/homeassistant/components/kaiterra/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/kaiterra", "requirements": ["kaiterra-async-client==0.0.2"], "codeowners": ["@Michsior14"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["kaiterra_async_client"] } diff --git a/homeassistant/components/keba/manifest.json b/homeassistant/components/keba/manifest.json index 7e148be103b0ab..e1685cd47c320e 100644 --- a/homeassistant/components/keba/manifest.json +++ b/homeassistant/components/keba/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/keba", "requirements": ["keba-kecontact==1.1.0"], "codeowners": ["@dannerph"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["keba_kecontact"] } diff --git a/homeassistant/components/keenetic_ndms2/manifest.json b/homeassistant/components/keenetic_ndms2/manifest.json index 3f01c9091c7e73..be1ffd1f0b2243 100644 --- a/homeassistant/components/keenetic_ndms2/manifest.json +++ b/homeassistant/components/keenetic_ndms2/manifest.json @@ -15,5 +15,6 @@ } ], "codeowners": ["@foxel"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["ndms2_client"] } diff --git a/homeassistant/components/kef/manifest.json b/homeassistant/components/kef/manifest.json index 1b0c0b190e6035..40365aa860cc24 100644 --- a/homeassistant/components/kef/manifest.json +++ b/homeassistant/components/kef/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/kef", "codeowners": ["@basnijholt"], "requirements": ["aiokef==0.2.16", "getmac==0.8.2"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["aiokef", "tenacity"] } diff --git a/homeassistant/components/keyboard/manifest.json b/homeassistant/components/keyboard/manifest.json index b53d44ff18838a..8e8d982d216283 100644 --- a/homeassistant/components/keyboard/manifest.json +++ b/homeassistant/components/keyboard/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/keyboard", "requirements": ["pyuserinput==0.1.11"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pykeyboard"] } diff --git a/homeassistant/components/keyboard_remote/manifest.json b/homeassistant/components/keyboard_remote/manifest.json index 1fc34f4700042e..76ab1d7cf5c835 100644 --- a/homeassistant/components/keyboard_remote/manifest.json +++ b/homeassistant/components/keyboard_remote/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/keyboard_remote", "requirements": ["evdev==1.4.0", "aionotify==0.2.0"], "codeowners": ["@bendavid", "@lanrat"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["aionotify", "evdev"] } diff --git a/homeassistant/components/kira/manifest.json b/homeassistant/components/kira/manifest.json index 09514d01cb51b0..a65af141e15984 100644 --- a/homeassistant/components/kira/manifest.json +++ b/homeassistant/components/kira/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/kira", "requirements": ["pykira==0.1.1"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pykira"] } diff --git a/homeassistant/components/kiwi/manifest.json b/homeassistant/components/kiwi/manifest.json index 7b5093eb86b5d9..8185c3000536ef 100644 --- a/homeassistant/components/kiwi/manifest.json +++ b/homeassistant/components/kiwi/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/kiwi", "requirements": ["kiwiki-client==0.1.1"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["kiwiki"] } diff --git a/homeassistant/components/kmtronic/manifest.json b/homeassistant/components/kmtronic/manifest.json index 1c17ee0fd3cfda..0fab41e103ecab 100644 --- a/homeassistant/components/kmtronic/manifest.json +++ b/homeassistant/components/kmtronic/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/kmtronic", "requirements": ["pykmtronic==0.3.0"], "codeowners": ["@dgomes"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pykmtronic"] } diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index 6e229edb893e52..a97265ca244475 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -12,5 +12,6 @@ "@marvin-w" ], "quality_scale": "silver", - "iot_class": "local_push" -} \ No newline at end of file + "iot_class": "local_push", + "loggers": ["xknx"] +} diff --git a/homeassistant/components/kodi/manifest.json b/homeassistant/components/kodi/manifest.json index 6e46b0883d9046..3a39a7870a3276 100644 --- a/homeassistant/components/kodi/manifest.json +++ b/homeassistant/components/kodi/manifest.json @@ -6,5 +6,6 @@ "codeowners": ["@OnFreund", "@cgtobi"], "zeroconf": ["_xbmc-jsonrpc-h._tcp.local."], "config_flow": true, - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["jsonrpc_async", "jsonrpc_base", "jsonrpc_websocket", "pykodi"] } diff --git a/homeassistant/components/konnected/manifest.json b/homeassistant/components/konnected/manifest.json index c4ba720bc6ac2d..93df24c850979d 100644 --- a/homeassistant/components/konnected/manifest.json +++ b/homeassistant/components/konnected/manifest.json @@ -11,5 +11,6 @@ ], "dependencies": ["http"], "codeowners": ["@heythisisnate"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["konnected"] } diff --git a/homeassistant/components/kostal_plenticore/manifest.json b/homeassistant/components/kostal_plenticore/manifest.json index 9e6d4353259fb8..71f71cae993fb8 100644 --- a/homeassistant/components/kostal_plenticore/manifest.json +++ b/homeassistant/components/kostal_plenticore/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/kostal_plenticore", "requirements": ["kostal_plenticore==0.2.0"], "codeowners": ["@stegm"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["kostal"] } diff --git a/homeassistant/components/kraken/manifest.json b/homeassistant/components/kraken/manifest.json index c7d1ca4d0ed8b4..8cbc29f52bd35c 100644 --- a/homeassistant/components/kraken/manifest.json +++ b/homeassistant/components/kraken/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/kraken", "requirements": ["krakenex==2.1.0", "pykrakenapi==0.1.8"], "codeowners": ["@eifinger"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["krakenex", "pykrakenapi"] } diff --git a/homeassistant/components/kulersky/manifest.json b/homeassistant/components/kulersky/manifest.json index 24091ec65c809a..581fe53424bfb6 100644 --- a/homeassistant/components/kulersky/manifest.json +++ b/homeassistant/components/kulersky/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/kulersky", "requirements": ["pykulersky==0.5.2"], "codeowners": ["@emlove"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["bleak", "pykulersky"] } diff --git a/homeassistant/components/kwb/manifest.json b/homeassistant/components/kwb/manifest.json index b84d36131e5f34..b5229f7a0fefa5 100644 --- a/homeassistant/components/kwb/manifest.json +++ b/homeassistant/components/kwb/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/kwb", "requirements": ["pykwb==0.0.8"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pykwb"] } diff --git a/homeassistant/components/lacrosse/manifest.json b/homeassistant/components/lacrosse/manifest.json index 922c0e9d17361f..c377d29d2a0f47 100644 --- a/homeassistant/components/lacrosse/manifest.json +++ b/homeassistant/components/lacrosse/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/lacrosse", "requirements": ["pylacrosse==0.4"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pylacrosse"] } diff --git a/homeassistant/components/lametric/manifest.json b/homeassistant/components/lametric/manifest.json index a27ab3a48d9ad0..a2c0aecb58dc8f 100644 --- a/homeassistant/components/lametric/manifest.json +++ b/homeassistant/components/lametric/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/lametric", "requirements": ["lmnotify==0.0.4"], "codeowners": ["@robbiet480", "@frenck"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["lmnotify"] } diff --git a/homeassistant/components/lastfm/manifest.json b/homeassistant/components/lastfm/manifest.json index f850b39a6204d7..3c8aef9f67393d 100644 --- a/homeassistant/components/lastfm/manifest.json +++ b/homeassistant/components/lastfm/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/lastfm", "requirements": ["pylast==4.2.1"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pylast"] } diff --git a/homeassistant/components/lcn/manifest.json b/homeassistant/components/lcn/manifest.json index 87624a4e4aef6f..2bb9111b269d44 100644 --- a/homeassistant/components/lcn/manifest.json +++ b/homeassistant/components/lcn/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/lcn", "requirements": ["pypck==0.7.13"], "codeowners": ["@alengwenus"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pypck"] } diff --git a/homeassistant/components/lg_netcast/manifest.json b/homeassistant/components/lg_netcast/manifest.json index 18f296e1c53cd2..5006b88a407287 100644 --- a/homeassistant/components/lg_netcast/manifest.json +++ b/homeassistant/components/lg_netcast/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/lg_netcast", "requirements": ["pylgnetcast==0.3.7"], "codeowners": ["@Drafteed"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pylgnetcast"] } diff --git a/homeassistant/components/lg_soundbar/manifest.json b/homeassistant/components/lg_soundbar/manifest.json index 671b1d2ca57351..f40ad1d194cbb0 100644 --- a/homeassistant/components/lg_soundbar/manifest.json +++ b/homeassistant/components/lg_soundbar/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/lg_soundbar", "requirements": ["temescal==0.3"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["temescal"] } diff --git a/homeassistant/components/life360/manifest.json b/homeassistant/components/life360/manifest.json index 54919088262b79..23fdad892d25f5 100644 --- a/homeassistant/components/life360/manifest.json +++ b/homeassistant/components/life360/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/life360", "codeowners": ["@pnbruckner"], "requirements": ["life360==4.1.1"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["life360"] } diff --git a/homeassistant/components/lifx/manifest.json b/homeassistant/components/lifx/manifest.json index 2dc46615f3a1e8..b034745ee31643 100644 --- a/homeassistant/components/lifx/manifest.json +++ b/homeassistant/components/lifx/manifest.json @@ -8,5 +8,6 @@ "models": ["LIFX"] }, "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["aiolifx", "aiolifx_effects", "bitstring"] } diff --git a/homeassistant/components/lightwave/manifest.json b/homeassistant/components/lightwave/manifest.json index d77075a0c564ab..746d702b68973f 100644 --- a/homeassistant/components/lightwave/manifest.json +++ b/homeassistant/components/lightwave/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/lightwave", "requirements": ["lightwave==0.20"], "codeowners": [], - "iot_class": "assumed_state" + "iot_class": "assumed_state", + "loggers": ["lightwave"] } diff --git a/homeassistant/components/limitlessled/manifest.json b/homeassistant/components/limitlessled/manifest.json index f0a8888214a7a0..bf6f00d66ad403 100644 --- a/homeassistant/components/limitlessled/manifest.json +++ b/homeassistant/components/limitlessled/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/limitlessled", "requirements": ["limitlessled==1.1.3"], "codeowners": [], - "iot_class": "assumed_state" + "iot_class": "assumed_state", + "loggers": ["limitlessled"] } diff --git a/homeassistant/components/linode/manifest.json b/homeassistant/components/linode/manifest.json index 27325354553642..df600e357aa94b 100644 --- a/homeassistant/components/linode/manifest.json +++ b/homeassistant/components/linode/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/linode", "requirements": ["linode-api==4.1.9b1"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["linode"] } diff --git a/homeassistant/components/linux_battery/manifest.json b/homeassistant/components/linux_battery/manifest.json index 4502bd039f4075..a35f77525622c5 100644 --- a/homeassistant/components/linux_battery/manifest.json +++ b/homeassistant/components/linux_battery/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/linux_battery", "requirements": ["batinfo==0.4.2"], "codeowners": ["@fabaff"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["batinfo"] } diff --git a/homeassistant/components/lirc/manifest.json b/homeassistant/components/lirc/manifest.json index 3e688bdef6fd0a..e497927180a345 100644 --- a/homeassistant/components/lirc/manifest.json +++ b/homeassistant/components/lirc/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/lirc", "requirements": ["python-lirc==1.2.3"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["lirc"] } diff --git a/homeassistant/components/litejet/manifest.json b/homeassistant/components/litejet/manifest.json index 7481cabb655396..c6e958d3a10772 100644 --- a/homeassistant/components/litejet/manifest.json +++ b/homeassistant/components/litejet/manifest.json @@ -5,5 +5,6 @@ "requirements": ["pylitejet==0.3.0"], "codeowners": ["@joncar"], "config_flow": true, - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pylitejet"] } diff --git a/homeassistant/components/litterrobot/manifest.json b/homeassistant/components/litterrobot/manifest.json index ab05ab111f01cb..b404762fbf37dc 100644 --- a/homeassistant/components/litterrobot/manifest.json +++ b/homeassistant/components/litterrobot/manifest.json @@ -9,5 +9,6 @@ "codeowners": [ "@natekspencer" ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pylitterbot"] } \ No newline at end of file diff --git a/homeassistant/components/logi_circle/manifest.json b/homeassistant/components/logi_circle/manifest.json index b89950061694d0..94c040f3b75c57 100644 --- a/homeassistant/components/logi_circle/manifest.json +++ b/homeassistant/components/logi_circle/manifest.json @@ -6,5 +6,6 @@ "requirements": ["logi_circle==0.2.2"], "dependencies": ["ffmpeg", "http"], "codeowners": ["@evanjd"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["logi_circle"] } diff --git a/homeassistant/components/london_underground/manifest.json b/homeassistant/components/london_underground/manifest.json index 329c9fa504d917..eed2ec45dd7923 100644 --- a/homeassistant/components/london_underground/manifest.json +++ b/homeassistant/components/london_underground/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/london_underground", "requirements": ["london-tube-status==0.2"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["london_tube_status"] } diff --git a/homeassistant/components/lookin/manifest.json b/homeassistant/components/lookin/manifest.json index d63961b5cfa6ba..7cf705403729b3 100644 --- a/homeassistant/components/lookin/manifest.json +++ b/homeassistant/components/lookin/manifest.json @@ -6,5 +6,6 @@ "requirements": ["aiolookin==0.1.0"], "zeroconf": ["_lookin._tcp.local."], "config_flow": true, - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["aiolookin"] } diff --git a/homeassistant/components/luci/manifest.json b/homeassistant/components/luci/manifest.json index 705bb7ecb4b9a2..2d61852689a8cc 100644 --- a/homeassistant/components/luci/manifest.json +++ b/homeassistant/components/luci/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/luci", "requirements": ["openwrt-luci-rpc==1.1.11"], "codeowners": ["@mzdrale"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["openwrt_luci_rpc"] } diff --git a/homeassistant/components/luftdaten/manifest.json b/homeassistant/components/luftdaten/manifest.json index ec3da32a76db82..255dc8c52eabec 100644 --- a/homeassistant/components/luftdaten/manifest.json +++ b/homeassistant/components/luftdaten/manifest.json @@ -6,5 +6,6 @@ "requirements": ["luftdaten==0.7.2"], "codeowners": ["@fabaff", "@frenck"], "quality_scale": "gold", - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["luftdaten"] } diff --git a/homeassistant/components/lupusec/manifest.json b/homeassistant/components/lupusec/manifest.json index 126fa407a37927..53ab1e6af4702d 100644 --- a/homeassistant/components/lupusec/manifest.json +++ b/homeassistant/components/lupusec/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/lupusec", "requirements": ["lupupy==0.0.24"], "codeowners": ["@majuss"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["lupupy"] } diff --git a/homeassistant/components/lutron/manifest.json b/homeassistant/components/lutron/manifest.json index 83c4ee7234573a..7c3e66c71275c7 100644 --- a/homeassistant/components/lutron/manifest.json +++ b/homeassistant/components/lutron/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/lutron", "requirements": ["pylutron==0.2.8"], "codeowners": ["@JonGilmore"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pylutron"] } diff --git a/homeassistant/components/lutron_caseta/manifest.json b/homeassistant/components/lutron_caseta/manifest.json index b6d3eb51f7a1c5..f703f991f6ff29 100644 --- a/homeassistant/components/lutron_caseta/manifest.json +++ b/homeassistant/components/lutron_caseta/manifest.json @@ -9,5 +9,6 @@ "models": ["Smart Bridge"] }, "codeowners": ["@swails", "@bdraco"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pylutron_caseta"] } diff --git a/homeassistant/components/lyric/manifest.json b/homeassistant/components/lyric/manifest.json index c45d7fb38e98f0..146a33972971e5 100644 --- a/homeassistant/components/lyric/manifest.json +++ b/homeassistant/components/lyric/manifest.json @@ -21,5 +21,6 @@ "macaddress": "00D02D*" } ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["aiolyric"] } diff --git a/homeassistant/components/magicseaweed/manifest.json b/homeassistant/components/magicseaweed/manifest.json index 84a2addc3e1441..57b31e03dc7b45 100644 --- a/homeassistant/components/magicseaweed/manifest.json +++ b/homeassistant/components/magicseaweed/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/magicseaweed", "requirements": ["magicseaweed==1.0.3"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["magicseaweed"] } diff --git a/homeassistant/components/mailgun/manifest.json b/homeassistant/components/mailgun/manifest.json index d8d5182816b8df..2d16786bd39017 100644 --- a/homeassistant/components/mailgun/manifest.json +++ b/homeassistant/components/mailgun/manifest.json @@ -6,5 +6,6 @@ "requirements": ["pymailgunner==1.4"], "dependencies": ["webhook"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["pymailgunner"] } diff --git a/homeassistant/components/marytts/manifest.json b/homeassistant/components/marytts/manifest.json index f53e0deecd70b6..c07f9b2a270a4f 100644 --- a/homeassistant/components/marytts/manifest.json +++ b/homeassistant/components/marytts/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/marytts", "requirements": ["speak2mary==1.4.0"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["speak2mary"] } diff --git a/homeassistant/components/mastodon/manifest.json b/homeassistant/components/mastodon/manifest.json index cd393002e1de35..e4e8ceb53ee0d1 100644 --- a/homeassistant/components/mastodon/manifest.json +++ b/homeassistant/components/mastodon/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/mastodon", "requirements": ["Mastodon.py==1.5.1"], "codeowners": ["@fabaff"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["mastodon"] } diff --git a/homeassistant/components/matrix/manifest.json b/homeassistant/components/matrix/manifest.json index 4e31b99c172b7e..e3d7b275de7c26 100644 --- a/homeassistant/components/matrix/manifest.json +++ b/homeassistant/components/matrix/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/matrix", "requirements": ["matrix-client==0.4.0"], "codeowners": ["@tinloaf"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["matrix_client"] } diff --git a/homeassistant/components/maxcube/manifest.json b/homeassistant/components/maxcube/manifest.json index fa4bcc44cc6203..7b9b402cb8dd66 100644 --- a/homeassistant/components/maxcube/manifest.json +++ b/homeassistant/components/maxcube/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/maxcube", "requirements": ["maxcube-api==0.4.3"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["maxcube"] } diff --git a/homeassistant/components/mazda/manifest.json b/homeassistant/components/mazda/manifest.json index e00049101f9088..a75c7f99e4c507 100644 --- a/homeassistant/components/mazda/manifest.json +++ b/homeassistant/components/mazda/manifest.json @@ -6,5 +6,6 @@ "requirements": ["pymazda==0.3.2"], "codeowners": ["@bdr99"], "quality_scale": "platinum", - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pymazda"] } diff --git a/homeassistant/components/mcp23017/manifest.json b/homeassistant/components/mcp23017/manifest.json index 2fad5acc0ce694..e6f04ad1171c2a 100644 --- a/homeassistant/components/mcp23017/manifest.json +++ b/homeassistant/components/mcp23017/manifest.json @@ -7,5 +7,6 @@ "adafruit-circuitpython-mcp230xx==2.2.2" ], "codeowners": ["@jardiamj"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["adafruit_mcp230xx"] } diff --git a/homeassistant/components/media_extractor/manifest.json b/homeassistant/components/media_extractor/manifest.json index 6444aa17d7dcb4..65efae00277a78 100644 --- a/homeassistant/components/media_extractor/manifest.json +++ b/homeassistant/components/media_extractor/manifest.json @@ -6,5 +6,6 @@ "dependencies": ["media_player"], "codeowners": [], "quality_scale": "internal", - "iot_class": "calculated" + "iot_class": "calculated", + "loggers": ["youtube_dl"] } diff --git a/homeassistant/components/mediaroom/manifest.json b/homeassistant/components/mediaroom/manifest.json index 4171322400a681..63007f88bbbaf9 100644 --- a/homeassistant/components/mediaroom/manifest.json +++ b/homeassistant/components/mediaroom/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/mediaroom", "requirements": ["pymediaroom==0.6.4.1"], "codeowners": ["@dgomes"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pymediaroom"] } diff --git a/homeassistant/components/melcloud/manifest.json b/homeassistant/components/melcloud/manifest.json index 355f4c9058b398..2f209667dafc4f 100644 --- a/homeassistant/components/melcloud/manifest.json +++ b/homeassistant/components/melcloud/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/melcloud", "requirements": ["pymelcloud==2.5.6"], "codeowners": ["@vilppuvuorinen"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pymelcloud"] } diff --git a/homeassistant/components/melissa/manifest.json b/homeassistant/components/melissa/manifest.json index d3b4f95a82ebd6..2839f74a5cd079 100644 --- a/homeassistant/components/melissa/manifest.json +++ b/homeassistant/components/melissa/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/melissa", "requirements": ["py-melissa-climate==2.1.4"], "codeowners": ["@kennedyshead"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["melissa"] } diff --git a/homeassistant/components/message_bird/manifest.json b/homeassistant/components/message_bird/manifest.json index 9e38e9d724e96c..f3278956911f38 100644 --- a/homeassistant/components/message_bird/manifest.json +++ b/homeassistant/components/message_bird/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/message_bird", "requirements": ["messagebird==1.2.0"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["messagebird"] } diff --git a/homeassistant/components/met/manifest.json b/homeassistant/components/met/manifest.json index b6c3e565dc0f6b..1ce70f25ea539a 100644 --- a/homeassistant/components/met/manifest.json +++ b/homeassistant/components/met/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/met", "requirements": ["pyMetno==0.9.0"], "codeowners": ["@danielhiversen", "@thimic"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["metno"] } diff --git a/homeassistant/components/met_eireann/manifest.json b/homeassistant/components/met_eireann/manifest.json index 36cc905eabf45c..ad91ce528cc2b0 100644 --- a/homeassistant/components/met_eireann/manifest.json +++ b/homeassistant/components/met_eireann/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/met_eireann", "requirements": ["pyMetEireann==2021.8.0"], "codeowners": ["@DylanGore"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["meteireann"] } diff --git a/homeassistant/components/meteo_france/manifest.json b/homeassistant/components/meteo_france/manifest.json index e7d1c4bd64a52a..cfdd62933c02bd 100644 --- a/homeassistant/components/meteo_france/manifest.json +++ b/homeassistant/components/meteo_france/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/meteo_france", "requirements": ["meteofrance-api==1.0.2"], "codeowners": ["@hacf-fr", "@oncleben31", "@Quentame"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["meteofrance_api"] } diff --git a/homeassistant/components/meteoalarm/manifest.json b/homeassistant/components/meteoalarm/manifest.json index ffdd7d8f49d2ab..35333f6ea0154d 100644 --- a/homeassistant/components/meteoalarm/manifest.json +++ b/homeassistant/components/meteoalarm/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/meteoalarm", "requirements": ["meteoalertapi==0.2.0"], "codeowners": ["@rolfberkenbosch"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["meteoalertapi"] } diff --git a/homeassistant/components/meteoclimatic/manifest.json b/homeassistant/components/meteoclimatic/manifest.json index 71174f216a4743..6c573b0c0d46e4 100644 --- a/homeassistant/components/meteoclimatic/manifest.json +++ b/homeassistant/components/meteoclimatic/manifest.json @@ -9,5 +9,6 @@ "codeowners": [ "@adrianmo" ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["meteoclimatic"] } diff --git a/homeassistant/components/metoffice/manifest.json b/homeassistant/components/metoffice/manifest.json index db6832b04b4547..d38d2d8cffeb19 100644 --- a/homeassistant/components/metoffice/manifest.json +++ b/homeassistant/components/metoffice/manifest.json @@ -5,5 +5,6 @@ "requirements": ["datapoint==0.9.8"], "codeowners": ["@MrHarcombe"], "config_flow": true, - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["datapoint"] } diff --git a/homeassistant/components/mfi/manifest.json b/homeassistant/components/mfi/manifest.json index 8ac5f3876351dd..7aaea34ea608bf 100644 --- a/homeassistant/components/mfi/manifest.json +++ b/homeassistant/components/mfi/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/mfi", "requirements": ["mficlient==0.3.0"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["mficlient"] } diff --git a/homeassistant/components/mhz19/manifest.json b/homeassistant/components/mhz19/manifest.json index aa2271f2dd4d53..349fba8c7a2158 100644 --- a/homeassistant/components/mhz19/manifest.json +++ b/homeassistant/components/mhz19/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/mhz19", "requirements": ["pmsensor==0.4"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pmsensor"] } diff --git a/homeassistant/components/microsoft/manifest.json b/homeassistant/components/microsoft/manifest.json index 299209e9b97607..ec393125d24f31 100644 --- a/homeassistant/components/microsoft/manifest.json +++ b/homeassistant/components/microsoft/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/microsoft", "requirements": ["pycsspeechtts==1.0.4"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["pycsspeechtts"] } diff --git a/homeassistant/components/miflora/manifest.json b/homeassistant/components/miflora/manifest.json index 9242428ebf7a91..eea4b2b82fe665 100644 --- a/homeassistant/components/miflora/manifest.json +++ b/homeassistant/components/miflora/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/miflora", "requirements": ["bluepy==1.3.0", "miflora==0.7.2"], "codeowners": ["@danielhiversen", "@basnijholt"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["btlewrap", "miflora"] } diff --git a/homeassistant/components/mikrotik/manifest.json b/homeassistant/components/mikrotik/manifest.json index eff9d26103d4f6..769db5898c2a46 100644 --- a/homeassistant/components/mikrotik/manifest.json +++ b/homeassistant/components/mikrotik/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/mikrotik", "requirements": ["librouteros==3.2.0"], "codeowners": ["@engrbm87"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["librouteros"] } diff --git a/homeassistant/components/mill/manifest.json b/homeassistant/components/mill/manifest.json index 7cea7118882adb..c2adebae594edb 100644 --- a/homeassistant/components/mill/manifest.json +++ b/homeassistant/components/mill/manifest.json @@ -5,5 +5,6 @@ "requirements": ["millheater==0.9.0", "mill-local==0.1.1"], "codeowners": ["@danielhiversen"], "config_flow": true, - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["mill", "mill_local"] } diff --git a/homeassistant/components/minecraft_server/manifest.json b/homeassistant/components/minecraft_server/manifest.json index 99a5ff3a463d48..b74b2e2bf2aeb4 100644 --- a/homeassistant/components/minecraft_server/manifest.json +++ b/homeassistant/components/minecraft_server/manifest.json @@ -6,5 +6,6 @@ "requirements": ["aiodns==3.0.0", "getmac==0.8.2", "mcstatus==6.0.0"], "codeowners": ["@elmurato"], "quality_scale": "silver", - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["dnspython", "mcstatus"] } diff --git a/homeassistant/components/minio/manifest.json b/homeassistant/components/minio/manifest.json index ba5ba4cd0a8e3b..f89db2346d98dc 100644 --- a/homeassistant/components/minio/manifest.json +++ b/homeassistant/components/minio/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/minio", "requirements": ["minio==5.0.10"], "codeowners": ["@tkislan"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["minio"] } diff --git a/homeassistant/components/mitemp_bt/manifest.json b/homeassistant/components/mitemp_bt/manifest.json index f0465315cefa0e..07121b3695bbd6 100644 --- a/homeassistant/components/mitemp_bt/manifest.json +++ b/homeassistant/components/mitemp_bt/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/mitemp_bt", "requirements": ["mitemp_bt==0.0.5"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["btlewrap", "mitemp_bt"] } diff --git a/homeassistant/components/mobile_app/manifest.json b/homeassistant/components/mobile_app/manifest.json index 86adfbcfe054c4..4723a2a6fb93d6 100644 --- a/homeassistant/components/mobile_app/manifest.json +++ b/homeassistant/components/mobile_app/manifest.json @@ -8,5 +8,6 @@ "after_dependencies": ["cloud", "camera", "notify"], "codeowners": ["@home-assistant/core"], "quality_scale": "internal", - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["emoji", "nacl"] } diff --git a/homeassistant/components/mochad/manifest.json b/homeassistant/components/mochad/manifest.json index 35a92dbb51b0ae..0d609c87eb5852 100644 --- a/homeassistant/components/mochad/manifest.json +++ b/homeassistant/components/mochad/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/mochad", "requirements": ["pymochad==0.2.0"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pbr", "pymochad"] } diff --git a/homeassistant/components/modbus/manifest.json b/homeassistant/components/modbus/manifest.json index ccf2bf8138496f..96127f39bbd563 100644 --- a/homeassistant/components/modbus/manifest.json +++ b/homeassistant/components/modbus/manifest.json @@ -5,5 +5,6 @@ "requirements": ["pymodbus==2.5.3"], "codeowners": ["@adamchengtkc", "@janiversen", "@vzahradnik"], "quality_scale": "gold", - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pymodbus"] } diff --git a/homeassistant/components/modem_callerid/manifest.json b/homeassistant/components/modem_callerid/manifest.json index 4f4264d7688375..ae66e72bfcb2c5 100644 --- a/homeassistant/components/modem_callerid/manifest.json +++ b/homeassistant/components/modem_callerid/manifest.json @@ -7,5 +7,6 @@ "codeowners": ["@tkdrob"], "dependencies": ["usb"], "iot_class": "local_polling", - "usb": [{"vid":"0572","pid":"1340"}] + "usb": [{"vid":"0572","pid":"1340"}], + "loggers": ["phone_modem"] } diff --git a/homeassistant/components/modern_forms/manifest.json b/homeassistant/components/modern_forms/manifest.json index 1466537259b1be..67a7581e8973e8 100644 --- a/homeassistant/components/modern_forms/manifest.json +++ b/homeassistant/components/modern_forms/manifest.json @@ -12,5 +12,6 @@ "codeowners": [ "@wonderslug" ], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["aiomodernforms"] } diff --git a/homeassistant/components/monoprice/manifest.json b/homeassistant/components/monoprice/manifest.json index 2001531a396eb7..85910b0eb9ad0e 100644 --- a/homeassistant/components/monoprice/manifest.json +++ b/homeassistant/components/monoprice/manifest.json @@ -5,5 +5,6 @@ "requirements": ["pymonoprice==0.3"], "codeowners": ["@etsinko", "@OnFreund"], "config_flow": true, - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pymonoprice"] } diff --git a/homeassistant/components/motion_blinds/manifest.json b/homeassistant/components/motion_blinds/manifest.json index 8636bd6ed94f32..21200789fbcf05 100644 --- a/homeassistant/components/motion_blinds/manifest.json +++ b/homeassistant/components/motion_blinds/manifest.json @@ -6,5 +6,6 @@ "requirements": ["motionblinds==0.5.10"], "dependencies": ["network"], "codeowners": ["@starkillerOG"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["motionblinds"] } diff --git a/homeassistant/components/motioneye/manifest.json b/homeassistant/components/motioneye/manifest.json index e01cae085110d5..0eb4dc57d9dfe0 100644 --- a/homeassistant/components/motioneye/manifest.json +++ b/homeassistant/components/motioneye/manifest.json @@ -14,5 +14,6 @@ "codeowners": [ "@dermotduffy" ], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["motioneye_client"] } diff --git a/homeassistant/components/mpd/manifest.json b/homeassistant/components/mpd/manifest.json index 39b4e45196b769..880d32b587795f 100644 --- a/homeassistant/components/mpd/manifest.json +++ b/homeassistant/components/mpd/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/mpd", "requirements": ["python-mpd2==3.0.4"], "codeowners": ["@fabaff"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["mpd"] } diff --git a/homeassistant/components/msteams/manifest.json b/homeassistant/components/msteams/manifest.json index 3024bfb310ba39..75691e5fc26ef0 100644 --- a/homeassistant/components/msteams/manifest.json +++ b/homeassistant/components/msteams/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/msteams", "requirements": ["pymsteams==0.1.12"], "codeowners": ["@peroyvind"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["pymsteams"] } diff --git a/homeassistant/components/mutesync/manifest.json b/homeassistant/components/mutesync/manifest.json index 74e6d89d9f8549..1498c6955059c9 100644 --- a/homeassistant/components/mutesync/manifest.json +++ b/homeassistant/components/mutesync/manifest.json @@ -7,5 +7,6 @@ "iot_class": "local_polling", "codeowners": [ "@currentoor" - ] + ], + "loggers": ["mutesync"] } diff --git a/homeassistant/components/mvglive/manifest.json b/homeassistant/components/mvglive/manifest.json index 90c4b5a9ec08d0..0abb52a1666119 100644 --- a/homeassistant/components/mvglive/manifest.json +++ b/homeassistant/components/mvglive/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/mvglive", "requirements": ["PyMVGLive==1.1.4"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["MVGLive"] } diff --git a/homeassistant/components/mycroft/manifest.json b/homeassistant/components/mycroft/manifest.json index 21fc51fa9eeed2..da5d4763775be9 100644 --- a/homeassistant/components/mycroft/manifest.json +++ b/homeassistant/components/mycroft/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/mycroft", "requirements": ["mycroftapi==2.0"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["mycroftapi"] } diff --git a/homeassistant/components/myq/manifest.json b/homeassistant/components/myq/manifest.json index c8e9c29e4e7f3e..0506e589d54b87 100644 --- a/homeassistant/components/myq/manifest.json +++ b/homeassistant/components/myq/manifest.json @@ -9,5 +9,6 @@ "models": ["819LMB", "MYQ"] }, "iot_class": "cloud_polling", - "dhcp": [{ "macaddress": "645299*" }] + "dhcp": [{ "macaddress": "645299*" }], + "loggers": ["pkce", "pymyq"] } diff --git a/homeassistant/components/mysensors/manifest.json b/homeassistant/components/mysensors/manifest.json index 6e7a4f9cdedd21..dafdd7c86bcd6f 100644 --- a/homeassistant/components/mysensors/manifest.json +++ b/homeassistant/components/mysensors/manifest.json @@ -6,5 +6,6 @@ "after_dependencies": ["mqtt"], "codeowners": ["@MartinHjelmare", "@functionpointer"], "config_flow": true, - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["mysensors"] } diff --git a/homeassistant/components/mystrom/manifest.json b/homeassistant/components/mystrom/manifest.json index 5becef7fff26aa..ef13ea4d8bf620 100644 --- a/homeassistant/components/mystrom/manifest.json +++ b/homeassistant/components/mystrom/manifest.json @@ -5,5 +5,6 @@ "requirements": ["python-mystrom==1.1.2"], "dependencies": ["http"], "codeowners": ["@fabaff"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pymystrom"] } diff --git a/homeassistant/components/mythicbeastsdns/manifest.json b/homeassistant/components/mythicbeastsdns/manifest.json index 50841f21f3a6ee..3b022c1e43d3ec 100644 --- a/homeassistant/components/mythicbeastsdns/manifest.json +++ b/homeassistant/components/mythicbeastsdns/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/mythicbeastsdns", "requirements": ["mbddns==0.1.2"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["mbddns"] } diff --git a/homeassistant/components/nad/manifest.json b/homeassistant/components/nad/manifest.json index 12c1f84aa37651..1cf66c9d438524 100644 --- a/homeassistant/components/nad/manifest.json +++ b/homeassistant/components/nad/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/nad", "requirements": ["nad_receiver==0.3.0"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["nad_receiver"] } diff --git a/homeassistant/components/nam/manifest.json b/homeassistant/components/nam/manifest.json index 68d5fb5074613d..d8cda2f16c7fb9 100644 --- a/homeassistant/components/nam/manifest.json +++ b/homeassistant/components/nam/manifest.json @@ -16,5 +16,6 @@ ], "config_flow": true, "quality_scale": "platinum", - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["nettigo_air_monitor"] } diff --git a/homeassistant/components/nanoleaf/manifest.json b/homeassistant/components/nanoleaf/manifest.json index 3550b56d3520d3..e5ba3a0594142d 100644 --- a/homeassistant/components/nanoleaf/manifest.json +++ b/homeassistant/components/nanoleaf/manifest.json @@ -25,5 +25,6 @@ } ], "codeowners": ["@milanmeu"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["aionanoleaf"] } \ No newline at end of file diff --git a/homeassistant/components/neato/manifest.json b/homeassistant/components/neato/manifest.json index 1c65ebebdcc59d..b183548222dd7f 100644 --- a/homeassistant/components/neato/manifest.json +++ b/homeassistant/components/neato/manifest.json @@ -6,5 +6,6 @@ "requirements": ["pybotvac==0.0.23"], "codeowners": ["@dshokouhi", "@Santobert"], "dependencies": ["http"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pybotvac"] } diff --git a/homeassistant/components/ness_alarm/manifest.json b/homeassistant/components/ness_alarm/manifest.json index 57c89e52ee86d1..4aa01428d271a8 100644 --- a/homeassistant/components/ness_alarm/manifest.json +++ b/homeassistant/components/ness_alarm/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/ness_alarm", "requirements": ["nessclient==0.9.15"], "codeowners": ["@nickw444"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["nessclient"] } diff --git a/homeassistant/components/nest/manifest.json b/homeassistant/components/nest/manifest.json index 478e608700c362..12ee32d532f2e0 100644 --- a/homeassistant/components/nest/manifest.json +++ b/homeassistant/components/nest/manifest.json @@ -13,5 +13,6 @@ { "macaddress": "D8EB46*" }, { "macaddress": "1C53F9*" } ], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["google_nest_sdm", "nest"] } diff --git a/homeassistant/components/netatmo/manifest.json b/homeassistant/components/netatmo/manifest.json index 581a954df30981..1632c8ba9a36eb 100644 --- a/homeassistant/components/netatmo/manifest.json +++ b/homeassistant/components/netatmo/manifest.json @@ -24,5 +24,6 @@ "Welcome" ] }, - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyatmo"] } \ No newline at end of file diff --git a/homeassistant/components/netdata/manifest.json b/homeassistant/components/netdata/manifest.json index 34fbf45c52999d..5be37a358eda9b 100644 --- a/homeassistant/components/netdata/manifest.json +++ b/homeassistant/components/netdata/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/netdata", "requirements": ["netdata==1.0.1"], "codeowners": ["@fabaff"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["netdata"] } diff --git a/homeassistant/components/netgear/manifest.json b/homeassistant/components/netgear/manifest.json index e8af27cb3b655c..f862ca73e6c1fb 100644 --- a/homeassistant/components/netgear/manifest.json +++ b/homeassistant/components/netgear/manifest.json @@ -11,5 +11,6 @@ "manufacturer": "NETGEAR, Inc.", "deviceType": "urn:schemas-upnp-org:device:InternetGatewayDevice:1" } - ] + ], + "loggers": ["pynetgear"] } diff --git a/homeassistant/components/netgear_lte/manifest.json b/homeassistant/components/netgear_lte/manifest.json index c02393e0f54cf0..9b583739c88080 100644 --- a/homeassistant/components/netgear_lte/manifest.json +++ b/homeassistant/components/netgear_lte/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/netgear_lte", "requirements": ["eternalegypt==0.0.12"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["eternalegypt"] } diff --git a/homeassistant/components/neurio_energy/manifest.json b/homeassistant/components/neurio_energy/manifest.json index a46acb46dc6238..1d49293169e9da 100644 --- a/homeassistant/components/neurio_energy/manifest.json +++ b/homeassistant/components/neurio_energy/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/neurio_energy", "requirements": ["neurio==0.3.1"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["neurio"] } diff --git a/homeassistant/components/nexia/manifest.json b/homeassistant/components/nexia/manifest.json index f605b32528ed20..29b80fb00e90ff 100644 --- a/homeassistant/components/nexia/manifest.json +++ b/homeassistant/components/nexia/manifest.json @@ -11,5 +11,6 @@ "macaddress": "000231*" } ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["nexia"] } diff --git a/homeassistant/components/nextbus/manifest.json b/homeassistant/components/nextbus/manifest.json index 3343e24b277667..c441f37078f3b5 100644 --- a/homeassistant/components/nextbus/manifest.json +++ b/homeassistant/components/nextbus/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/nextbus", "codeowners": ["@vividboarder"], "requirements": ["py_nextbusnext==0.1.5"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["py_nextbus"] } diff --git a/homeassistant/components/nfandroidtv/manifest.json b/homeassistant/components/nfandroidtv/manifest.json index c1dea03aa09904..75163f3a92f77d 100644 --- a/homeassistant/components/nfandroidtv/manifest.json +++ b/homeassistant/components/nfandroidtv/manifest.json @@ -5,5 +5,6 @@ "requirements": ["notifications-android-tv==0.1.3"], "codeowners": ["@tkdrob"], "config_flow": true, - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["notifications_android_tv"] } diff --git a/homeassistant/components/nightscout/manifest.json b/homeassistant/components/nightscout/manifest.json index 49cb077dc7956c..c61b4f0cf9352a 100644 --- a/homeassistant/components/nightscout/manifest.json +++ b/homeassistant/components/nightscout/manifest.json @@ -6,5 +6,6 @@ "requirements": ["py-nightscout==1.2.2"], "codeowners": ["@marciogranzotto"], "quality_scale": "platinum", - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["py_nightscout"] } diff --git a/homeassistant/components/niko_home_control/manifest.json b/homeassistant/components/niko_home_control/manifest.json index bb015a059b9d40..5057013bd50445 100644 --- a/homeassistant/components/niko_home_control/manifest.json +++ b/homeassistant/components/niko_home_control/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/niko_home_control", "requirements": ["niko-home-control==0.2.1"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["nikohomecontrol"] } diff --git a/homeassistant/components/nilu/manifest.json b/homeassistant/components/nilu/manifest.json index bdc92209947980..cbb8db87e32743 100644 --- a/homeassistant/components/nilu/manifest.json +++ b/homeassistant/components/nilu/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/nilu", "requirements": ["niluclient==0.1.2"], "codeowners": ["@hfurubotten"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["niluclient"] } diff --git a/homeassistant/components/nina/manifest.json b/homeassistant/components/nina/manifest.json index 11b1b3e3fdd457..c3a0b43f7defa1 100644 --- a/homeassistant/components/nina/manifest.json +++ b/homeassistant/components/nina/manifest.json @@ -10,5 +10,6 @@ "codeowners": [ "@DeerMaximum" ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pynina"] } \ No newline at end of file diff --git a/homeassistant/components/nissan_leaf/manifest.json b/homeassistant/components/nissan_leaf/manifest.json index 42169105930a87..87c29013544f86 100644 --- a/homeassistant/components/nissan_leaf/manifest.json +++ b/homeassistant/components/nissan_leaf/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/nissan_leaf", "requirements": ["pycarwings2==2.13"], "codeowners": ["@filcole"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pycarwings2"] } diff --git a/homeassistant/components/nmap_tracker/manifest.json b/homeassistant/components/nmap_tracker/manifest.json index e17270a62a0a3d..6e7a9cbee53980 100644 --- a/homeassistant/components/nmap_tracker/manifest.json +++ b/homeassistant/components/nmap_tracker/manifest.json @@ -10,5 +10,6 @@ ], "codeowners": [], "iot_class": "local_polling", - "config_flow": true + "config_flow": true, + "loggers": ["nmap"] } diff --git a/homeassistant/components/nmbs/manifest.json b/homeassistant/components/nmbs/manifest.json index 82723f97924332..0c97b08f6800a4 100644 --- a/homeassistant/components/nmbs/manifest.json +++ b/homeassistant/components/nmbs/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/nmbs", "requirements": ["pyrail==0.0.3"], "codeowners": ["@thibmaek"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyrail"] } diff --git a/homeassistant/components/noaa_tides/manifest.json b/homeassistant/components/noaa_tides/manifest.json index 8ad99c8a5c22e2..618110051b6a49 100644 --- a/homeassistant/components/noaa_tides/manifest.json +++ b/homeassistant/components/noaa_tides/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/noaa_tides", "requirements": ["noaa-coops==0.1.8"], "codeowners": ["@jdelaney72"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["noaa_coops"] } diff --git a/homeassistant/components/norway_air/manifest.json b/homeassistant/components/norway_air/manifest.json index ade1a149590098..81572fe9cb7e87 100644 --- a/homeassistant/components/norway_air/manifest.json +++ b/homeassistant/components/norway_air/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/norway_air", "requirements": ["pyMetno==0.9.0"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["metno"] } diff --git a/homeassistant/components/notify_events/manifest.json b/homeassistant/components/notify_events/manifest.json index 96eda381506ac0..5247e19698825d 100644 --- a/homeassistant/components/notify_events/manifest.json +++ b/homeassistant/components/notify_events/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/notify_events", "codeowners": ["@matrozov", "@papajojo"], "requirements": ["notify-events==1.0.4"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["notify_events"] } diff --git a/homeassistant/components/notion/manifest.json b/homeassistant/components/notion/manifest.json index 378d6442e3157d..fa19ef81c8c4a1 100644 --- a/homeassistant/components/notion/manifest.json +++ b/homeassistant/components/notion/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/notion", "requirements": ["aionotion==3.0.2"], "codeowners": ["@bachya"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["aionotion"] } diff --git a/homeassistant/components/nsw_fuel_station/manifest.json b/homeassistant/components/nsw_fuel_station/manifest.json index dfc6ad62d90754..a9f8f64da06f71 100644 --- a/homeassistant/components/nsw_fuel_station/manifest.json +++ b/homeassistant/components/nsw_fuel_station/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/nsw_fuel_station", "requirements": ["nsw-fuel-api-client==1.1.0"], "codeowners": ["@nickw444"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["nsw_fuel"] } diff --git a/homeassistant/components/nsw_rural_fire_service_feed/manifest.json b/homeassistant/components/nsw_rural_fire_service_feed/manifest.json index ce75e72f5de87d..694089b13965b9 100644 --- a/homeassistant/components/nsw_rural_fire_service_feed/manifest.json +++ b/homeassistant/components/nsw_rural_fire_service_feed/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/nsw_rural_fire_service_feed", "requirements": ["aio_geojson_nsw_rfs_incidents==0.4"], "codeowners": ["@exxamalte"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["aio_geojson_nsw_rfs_incidents"] } diff --git a/homeassistant/components/nuheat/manifest.json b/homeassistant/components/nuheat/manifest.json index d2dbb12ebc5367..aea63a692a5cd7 100644 --- a/homeassistant/components/nuheat/manifest.json +++ b/homeassistant/components/nuheat/manifest.json @@ -11,5 +11,6 @@ "macaddress": "002338*" } ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["nuheat"] } diff --git a/homeassistant/components/nuki/manifest.json b/homeassistant/components/nuki/manifest.json index 8642423fd8d636..8a9b7c506b44ed 100644 --- a/homeassistant/components/nuki/manifest.json +++ b/homeassistant/components/nuki/manifest.json @@ -10,5 +10,6 @@ "hostname": "nuki_bridge_*" } ], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pynuki"] } diff --git a/homeassistant/components/numato/manifest.json b/homeassistant/components/numato/manifest.json index a65c4998554c6f..0f02bd6b8f71c1 100644 --- a/homeassistant/components/numato/manifest.json +++ b/homeassistant/components/numato/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/numato", "requirements": ["numato-gpio==0.10.0"], "codeowners": ["@clssn"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["numato_gpio"] } diff --git a/homeassistant/components/nut/manifest.json b/homeassistant/components/nut/manifest.json index 2489078ebd6d1a..4a07713fa30541 100644 --- a/homeassistant/components/nut/manifest.json +++ b/homeassistant/components/nut/manifest.json @@ -6,5 +6,6 @@ "codeowners": ["@bdraco", "@ollo69"], "config_flow": true, "zeroconf": ["_nut._tcp.local."], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pynut2"] } diff --git a/homeassistant/components/nws/manifest.json b/homeassistant/components/nws/manifest.json index 2e6f58028e0118..091bdaa5736425 100644 --- a/homeassistant/components/nws/manifest.json +++ b/homeassistant/components/nws/manifest.json @@ -6,5 +6,6 @@ "requirements": ["pynws==1.3.2"], "quality_scale": "platinum", "config_flow": true, - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["metar", "pynws"] } diff --git a/homeassistant/components/nx584/manifest.json b/homeassistant/components/nx584/manifest.json index 2aa3df8d167f28..9f826a4c4b2633 100644 --- a/homeassistant/components/nx584/manifest.json +++ b/homeassistant/components/nx584/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/nx584", "requirements": ["pynx584==0.5"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["nx584"] } diff --git a/homeassistant/components/nzbget/manifest.json b/homeassistant/components/nzbget/manifest.json index 951d5237736be6..6d4ea286317b29 100644 --- a/homeassistant/components/nzbget/manifest.json +++ b/homeassistant/components/nzbget/manifest.json @@ -5,5 +5,6 @@ "requirements": ["pynzbgetapi==0.2.0"], "codeowners": ["@chriscla"], "config_flow": true, - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pynzbgetapi"] } diff --git a/homeassistant/components/oasa_telematics/manifest.json b/homeassistant/components/oasa_telematics/manifest.json index a1d672ba595183..b1b203a8a61ea0 100644 --- a/homeassistant/components/oasa_telematics/manifest.json +++ b/homeassistant/components/oasa_telematics/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/oasa_telematics/", "requirements": ["oasatelematics==0.3"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["oasatelematics"] } diff --git a/homeassistant/components/obihai/manifest.json b/homeassistant/components/obihai/manifest.json index 05121c81ac73c7..f908ad161793c9 100644 --- a/homeassistant/components/obihai/manifest.json +++ b/homeassistant/components/obihai/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/obihai", "requirements": ["pyobihai==1.3.1"], "codeowners": ["@dshokouhi"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyobihai"] } diff --git a/homeassistant/components/octoprint/manifest.json b/homeassistant/components/octoprint/manifest.json index e150fbe5c57e86..385ab88428a19b 100644 --- a/homeassistant/components/octoprint/manifest.json +++ b/homeassistant/components/octoprint/manifest.json @@ -12,5 +12,6 @@ "deviceType": "urn:schemas-upnp-org:device:Basic:1" } ], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyoctoprintapi"] } diff --git a/homeassistant/components/oem/manifest.json b/homeassistant/components/oem/manifest.json index 29c2b1e7fa4654..e289e7a2e14022 100644 --- a/homeassistant/components/oem/manifest.json +++ b/homeassistant/components/oem/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/oem", "requirements": ["oemthermostat==1.1.1"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["oemthermostat"] } diff --git a/homeassistant/components/omnilogic/manifest.json b/homeassistant/components/omnilogic/manifest.json index ea2e951d084818..396bbb91a9c20f 100644 --- a/homeassistant/components/omnilogic/manifest.json +++ b/homeassistant/components/omnilogic/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/omnilogic", "requirements": ["omnilogic==0.4.5"], "codeowners": ["@oliver84", "@djtimca", "@gentoosu"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["config", "omnilogic"] } diff --git a/homeassistant/components/oncue/manifest.json b/homeassistant/components/oncue/manifest.json index 1b3548296ee129..cbe517dd98631c 100644 --- a/homeassistant/components/oncue/manifest.json +++ b/homeassistant/components/oncue/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/oncue", "requirements": ["aiooncue==0.3.2"], "codeowners": ["@bdraco"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["aiooncue"] } diff --git a/homeassistant/components/ondilo_ico/manifest.json b/homeassistant/components/ondilo_ico/manifest.json index 4c3ee64779a353..d3d9c7d1376137 100644 --- a/homeassistant/components/ondilo_ico/manifest.json +++ b/homeassistant/components/ondilo_ico/manifest.json @@ -6,5 +6,6 @@ "requirements": ["ondilo==0.2.0"], "dependencies": ["http"], "codeowners": ["@JeromeHXP"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["ondilo"] } diff --git a/homeassistant/components/onewire/manifest.json b/homeassistant/components/onewire/manifest.json index f48236c7f37ea1..d7b301f9c23430 100644 --- a/homeassistant/components/onewire/manifest.json +++ b/homeassistant/components/onewire/manifest.json @@ -5,5 +5,6 @@ "config_flow": true, "requirements": ["pyownet==0.10.0.post1", "pi1wire==0.1.0"], "codeowners": ["@garbled1", "@epenet"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pi1wire", "pyownet"] } diff --git a/homeassistant/components/onkyo/manifest.json b/homeassistant/components/onkyo/manifest.json index 39c1686d03e618..4f2dadde270bf6 100644 --- a/homeassistant/components/onkyo/manifest.json +++ b/homeassistant/components/onkyo/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/onkyo", "requirements": ["onkyo-eiscp==1.2.7"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["eiscp"] } diff --git a/homeassistant/components/onvif/manifest.json b/homeassistant/components/onvif/manifest.json index a7faa60cdcd4c0..cd220500751d45 100644 --- a/homeassistant/components/onvif/manifest.json +++ b/homeassistant/components/onvif/manifest.json @@ -6,5 +6,6 @@ "dependencies": ["ffmpeg"], "codeowners": ["@hunterjm"], "config_flow": true, - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["onvif", "wsdiscovery", "zeep"] } diff --git a/homeassistant/components/openerz/manifest.json b/homeassistant/components/openerz/manifest.json index b1e3b0597b5a2d..9a050154969655 100644 --- a/homeassistant/components/openerz/manifest.json +++ b/homeassistant/components/openerz/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/openerz", "codeowners": ["@misialq"], "requirements": ["openerz-api==0.1.0"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["openerz_api"] } diff --git a/homeassistant/components/openevse/manifest.json b/homeassistant/components/openevse/manifest.json index c4e5a5b7711f66..3a8984af253847 100644 --- a/homeassistant/components/openevse/manifest.json +++ b/homeassistant/components/openevse/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/openevse", "requirements": ["openevsewifi==1.1.0"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["openevsewifi"] } diff --git a/homeassistant/components/opengarage/manifest.json b/homeassistant/components/opengarage/manifest.json index cd7d0f48eec77d..a76c7d16d7473b 100644 --- a/homeassistant/components/opengarage/manifest.json +++ b/homeassistant/components/opengarage/manifest.json @@ -9,5 +9,6 @@ "open-garage==0.2.0" ], "iot_class": "local_polling", - "config_flow": true + "config_flow": true, + "loggers": ["opengarage"] } \ No newline at end of file diff --git a/homeassistant/components/openhome/manifest.json b/homeassistant/components/openhome/manifest.json index c83b135cb8ad36..6b8815f931864a 100644 --- a/homeassistant/components/openhome/manifest.json +++ b/homeassistant/components/openhome/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/openhome", "requirements": ["openhomedevice==2.0.1"], "codeowners": ["@bazwilliams"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["async_upnp_client", "openhomedevice"] } diff --git a/homeassistant/components/opensensemap/manifest.json b/homeassistant/components/opensensemap/manifest.json index df750156d1de98..513cb5ac3da266 100644 --- a/homeassistant/components/opensensemap/manifest.json +++ b/homeassistant/components/opensensemap/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/opensensemap", "requirements": ["opensensemap-api==0.1.5"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["opensensemap_api"] } diff --git a/homeassistant/components/opentherm_gw/manifest.json b/homeassistant/components/opentherm_gw/manifest.json index 463a0aa105289e..7aa19224020e7d 100644 --- a/homeassistant/components/opentherm_gw/manifest.json +++ b/homeassistant/components/opentherm_gw/manifest.json @@ -5,5 +5,6 @@ "requirements": ["pyotgw==1.1b1"], "codeowners": ["@mvn23"], "config_flow": true, - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pyotgw"] } diff --git a/homeassistant/components/openuv/manifest.json b/homeassistant/components/openuv/manifest.json index 6132cda271092b..08299ca5ddbe69 100644 --- a/homeassistant/components/openuv/manifest.json +++ b/homeassistant/components/openuv/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/openuv", "requirements": ["pyopenuv==2021.11.0"], "codeowners": ["@bachya"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyopenuv"] } diff --git a/homeassistant/components/openweathermap/manifest.json b/homeassistant/components/openweathermap/manifest.json index 0b0114328acb6c..8146dad908c442 100644 --- a/homeassistant/components/openweathermap/manifest.json +++ b/homeassistant/components/openweathermap/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/openweathermap", "requirements": ["pyowm==3.2.0"], "codeowners": ["@fabaff", "@freekode", "@nzapponi"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["geojson", "pyowm", "pysocks"] } diff --git a/homeassistant/components/opnsense/manifest.json b/homeassistant/components/opnsense/manifest.json index ed390278969cf9..7e8b933fc17da5 100644 --- a/homeassistant/components/opnsense/manifest.json +++ b/homeassistant/components/opnsense/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/opnsense", "requirements": ["pyopnsense==0.2.0"], "codeowners": ["@mtreinish"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pbr", "pyopnsense"] } diff --git a/homeassistant/components/opple/manifest.json b/homeassistant/components/opple/manifest.json index 1f0360e265a367..61a94ffda30c53 100644 --- a/homeassistant/components/opple/manifest.json +++ b/homeassistant/components/opple/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/opple", "requirements": ["pyoppleio==1.0.5"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyoppleio"] } diff --git a/homeassistant/components/orangepi_gpio/manifest.json b/homeassistant/components/orangepi_gpio/manifest.json index 9b5f567c4200ef..b4cda33ee806df 100644 --- a/homeassistant/components/orangepi_gpio/manifest.json +++ b/homeassistant/components/orangepi_gpio/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/orangepi_gpio", "requirements": ["OPi.GPIO==0.5.2"], "codeowners": ["@pascallj"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["OPi", "nanopi", "orangepi"] } diff --git a/homeassistant/components/oru/manifest.json b/homeassistant/components/oru/manifest.json index 0d023a96ad5b80..bd755f38e7ebdf 100644 --- a/homeassistant/components/oru/manifest.json +++ b/homeassistant/components/oru/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/oru", "codeowners": ["@bvlaicu"], "requirements": ["oru==0.1.11"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["oru"] } diff --git a/homeassistant/components/orvibo/manifest.json b/homeassistant/components/orvibo/manifest.json index 94c7391b6492cd..74685b5637307e 100644 --- a/homeassistant/components/orvibo/manifest.json +++ b/homeassistant/components/orvibo/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/orvibo", "requirements": ["orvibo==1.1.1"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["orvibo"] } diff --git a/homeassistant/components/osramlightify/manifest.json b/homeassistant/components/osramlightify/manifest.json index 0596d4073eba57..6143853f188eca 100644 --- a/homeassistant/components/osramlightify/manifest.json +++ b/homeassistant/components/osramlightify/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/osramlightify", "requirements": ["lightify==1.0.7.3"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["lightify"] } diff --git a/homeassistant/components/otp/manifest.json b/homeassistant/components/otp/manifest.json index 14205b8652de7f..0c16e660aa91ec 100644 --- a/homeassistant/components/otp/manifest.json +++ b/homeassistant/components/otp/manifest.json @@ -5,5 +5,6 @@ "requirements": ["pyotp==2.6.0"], "codeowners": [], "quality_scale": "internal", - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyotp"] } diff --git a/homeassistant/components/overkiz/manifest.json b/homeassistant/components/overkiz/manifest.json index 192b55bec574bc..0d235982462ff7 100644 --- a/homeassistant/components/overkiz/manifest.json +++ b/homeassistant/components/overkiz/manifest.json @@ -23,5 +23,6 @@ "@vlebourl", "@tetienne" ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["boto3", "botocore", "pyhumps", "pyoverkiz", "s3transfer"] } \ No newline at end of file diff --git a/homeassistant/components/ovo_energy/manifest.json b/homeassistant/components/ovo_energy/manifest.json index ba559ffb41d4d6..19e737f51ca966 100644 --- a/homeassistant/components/ovo_energy/manifest.json +++ b/homeassistant/components/ovo_energy/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/ovo_energy", "requirements": ["ovoenergy==1.1.12"], "codeowners": ["@timmo001"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["ovoenergy"] } diff --git a/homeassistant/components/owntracks/manifest.json b/homeassistant/components/owntracks/manifest.json index 40dbb7d569c594..1b502481764bc0 100644 --- a/homeassistant/components/owntracks/manifest.json +++ b/homeassistant/components/owntracks/manifest.json @@ -7,5 +7,6 @@ "dependencies": ["webhook"], "after_dependencies": ["mqtt", "cloud"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["nacl"] } diff --git a/homeassistant/components/ozw/manifest.json b/homeassistant/components/ozw/manifest.json index bf54f217f24ce0..997fbbc5a70b56 100644 --- a/homeassistant/components/ozw/manifest.json +++ b/homeassistant/components/ozw/manifest.json @@ -6,5 +6,6 @@ "requirements": ["python-openzwave-mqtt[mqtt-client]==1.4.0"], "after_dependencies": ["mqtt"], "codeowners": ["@cgarwood", "@marcelveldt", "@MartinHjelmare"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["openzwavemqtt"] } diff --git a/homeassistant/components/p1_monitor/manifest.json b/homeassistant/components/p1_monitor/manifest.json index 1f952d04fc99fb..c94893f61fdf40 100644 --- a/homeassistant/components/p1_monitor/manifest.json +++ b/homeassistant/components/p1_monitor/manifest.json @@ -6,5 +6,6 @@ "requirements": ["p1monitor==1.0.1"], "codeowners": ["@klaasnicolaas"], "quality_scale": "platinum", - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["p1monitor"] } diff --git a/homeassistant/components/panasonic_bluray/manifest.json b/homeassistant/components/panasonic_bluray/manifest.json index a9d6a4ebf76cb2..19ea941cb52e1d 100644 --- a/homeassistant/components/panasonic_bluray/manifest.json +++ b/homeassistant/components/panasonic_bluray/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/panasonic_bluray", "requirements": ["panacotta==0.1"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["panacotta"] } diff --git a/homeassistant/components/panasonic_viera/manifest.json b/homeassistant/components/panasonic_viera/manifest.json index fe365f85f2cea8..5b334f57c98fbc 100644 --- a/homeassistant/components/panasonic_viera/manifest.json +++ b/homeassistant/components/panasonic_viera/manifest.json @@ -5,5 +5,6 @@ "requirements": ["panasonic_viera==0.3.6"], "codeowners": [], "config_flow": true, - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["panasonic_viera"] } diff --git a/homeassistant/components/pandora/manifest.json b/homeassistant/components/pandora/manifest.json index 45f87b36ec16f8..6cbf8a76f4a47e 100644 --- a/homeassistant/components/pandora/manifest.json +++ b/homeassistant/components/pandora/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/pandora", "requirements": ["pexpect==4.6.0"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pexpect", "ptyprocess"] } diff --git a/homeassistant/components/pcal9535a/manifest.json b/homeassistant/components/pcal9535a/manifest.json index 2e685a8625c519..fc8214265422c7 100644 --- a/homeassistant/components/pcal9535a/manifest.json +++ b/homeassistant/components/pcal9535a/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/pcal9535a", "requirements": ["pcal9535a==0.7"], "codeowners": ["@Shulyaka"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pcal9535a", "smbus_cffi"] } diff --git a/homeassistant/components/pencom/manifest.json b/homeassistant/components/pencom/manifest.json index e8b44173fe91d5..a80cfb12876b3c 100644 --- a/homeassistant/components/pencom/manifest.json +++ b/homeassistant/components/pencom/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/pencom", "requirements": ["pencompy==0.0.3"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pencompy"] } diff --git a/homeassistant/components/philips_js/manifest.json b/homeassistant/components/philips_js/manifest.json index 60bc862406d9d0..948ce8703a10dd 100644 --- a/homeassistant/components/philips_js/manifest.json +++ b/homeassistant/components/philips_js/manifest.json @@ -5,5 +5,6 @@ "requirements": ["ha-philipsjs==2.7.6"], "codeowners": ["@elupus"], "config_flow": true, - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["haphilipsjs"] } diff --git a/homeassistant/components/pi4ioe5v9xxxx/manifest.json b/homeassistant/components/pi4ioe5v9xxxx/manifest.json index 4e12fcd009cae2..3ea322a6c635dd 100644 --- a/homeassistant/components/pi4ioe5v9xxxx/manifest.json +++ b/homeassistant/components/pi4ioe5v9xxxx/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/pi4ioe5v9xxxx", "requirements": ["pi4ioe5v9xxxx==0.0.2"], "codeowners": ["@antonverburg"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pi4ioe5v9xxxx", "smbus2"] } diff --git a/homeassistant/components/pi_hole/manifest.json b/homeassistant/components/pi_hole/manifest.json index 28ceb8e6c45bc9..cca92d9bcecd19 100644 --- a/homeassistant/components/pi_hole/manifest.json +++ b/homeassistant/components/pi_hole/manifest.json @@ -5,5 +5,6 @@ "requirements": ["hole==0.7.0"], "codeowners": ["@fabaff", "@johnluetke", "@shenxn"], "config_flow": true, - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["hole"] } diff --git a/homeassistant/components/picnic/manifest.json b/homeassistant/components/picnic/manifest.json index 757f2ef24ad8b2..54bcab8e3fd8a2 100644 --- a/homeassistant/components/picnic/manifest.json +++ b/homeassistant/components/picnic/manifest.json @@ -5,5 +5,6 @@ "iot_class": "cloud_polling", "documentation": "https://www.home-assistant.io/integrations/picnic", "requirements": ["python-picnic-api==1.1.0"], - "codeowners": ["@corneyl"] + "codeowners": ["@corneyl"], + "loggers": ["python_picnic_api"] } \ No newline at end of file diff --git a/homeassistant/components/pilight/manifest.json b/homeassistant/components/pilight/manifest.json index e7173df21d9f2c..e8357caeb64ae1 100644 --- a/homeassistant/components/pilight/manifest.json +++ b/homeassistant/components/pilight/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/pilight", "requirements": ["pilight==0.1.1"], "codeowners": ["@trekky12"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pilight"] } diff --git a/homeassistant/components/ping/manifest.json b/homeassistant/components/ping/manifest.json index d25d0fc731e210..4aec8dbee1ab4c 100644 --- a/homeassistant/components/ping/manifest.json +++ b/homeassistant/components/ping/manifest.json @@ -5,5 +5,6 @@ "codeowners": [], "requirements": ["icmplib==3.0"], "quality_scale": "internal", - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["icmplib"] } diff --git a/homeassistant/components/pjlink/manifest.json b/homeassistant/components/pjlink/manifest.json index ea07cc5d85a0bf..5c9436433ed2be 100644 --- a/homeassistant/components/pjlink/manifest.json +++ b/homeassistant/components/pjlink/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/pjlink", "requirements": ["pypjlink2==1.2.1"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pypjlink"] } diff --git a/homeassistant/components/plaato/manifest.json b/homeassistant/components/plaato/manifest.json index 99453f21d459a7..ddb3d4474a3c59 100644 --- a/homeassistant/components/plaato/manifest.json +++ b/homeassistant/components/plaato/manifest.json @@ -7,5 +7,6 @@ "after_dependencies": ["cloud"], "codeowners": ["@JohNan"], "requirements": ["pyplaato==0.0.15"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["pyplaato"] } diff --git a/homeassistant/components/plex/manifest.json b/homeassistant/components/plex/manifest.json index 5355dd252f8ba5..a3331b5c99152a 100644 --- a/homeassistant/components/plex/manifest.json +++ b/homeassistant/components/plex/manifest.json @@ -11,5 +11,6 @@ "zeroconf": ["_plexmediasvr._tcp.local."], "dependencies": ["http"], "codeowners": ["@jjlawren"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["plexapi", "plexwebsocket"] } diff --git a/homeassistant/components/plugwise/manifest.json b/homeassistant/components/plugwise/manifest.json index f81c2402846218..5006b1e659d135 100644 --- a/homeassistant/components/plugwise/manifest.json +++ b/homeassistant/components/plugwise/manifest.json @@ -6,5 +6,6 @@ "codeowners": ["@CoMPaTech", "@bouwew", "@brefra"], "zeroconf": ["_plugwise._tcp.local."], "config_flow": true, - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["crcmod", "plugwise"] } diff --git a/homeassistant/components/plum_lightpad/manifest.json b/homeassistant/components/plum_lightpad/manifest.json index 366f770ca3b9ed..05eeac20f171af 100644 --- a/homeassistant/components/plum_lightpad/manifest.json +++ b/homeassistant/components/plum_lightpad/manifest.json @@ -5,5 +5,6 @@ "requirements": ["plumlightpad==0.0.11"], "codeowners": ["@ColinHarrington", "@prystupa"], "config_flow": true, - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["plumlightpad"] } diff --git a/homeassistant/components/pocketcasts/manifest.json b/homeassistant/components/pocketcasts/manifest.json index a2070daedd7c18..f74c77ed3a9126 100644 --- a/homeassistant/components/pocketcasts/manifest.json +++ b/homeassistant/components/pocketcasts/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/pocketcasts", "requirements": ["pycketcasts==1.0.0"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pycketcasts"] } diff --git a/homeassistant/components/point/manifest.json b/homeassistant/components/point/manifest.json index 792563d3db8564..c74f5745bfcb3a 100644 --- a/homeassistant/components/point/manifest.json +++ b/homeassistant/components/point/manifest.json @@ -7,5 +7,6 @@ "dependencies": ["webhook", "http"], "codeowners": ["@fredrike"], "quality_scale": "gold", - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pypoint"] } diff --git a/homeassistant/components/poolsense/manifest.json b/homeassistant/components/poolsense/manifest.json index 697afd541063a8..7867df3dbee342 100644 --- a/homeassistant/components/poolsense/manifest.json +++ b/homeassistant/components/poolsense/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/poolsense", "requirements": ["poolsense==0.0.8"], "codeowners": ["@haemishkyd"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["poolsense"] } diff --git a/homeassistant/components/powerwall/manifest.json b/homeassistant/components/powerwall/manifest.json index 5dcccadb681ee9..fd17557abe11ad 100644 --- a/homeassistant/components/powerwall/manifest.json +++ b/homeassistant/components/powerwall/manifest.json @@ -15,5 +15,6 @@ "macaddress": "000145*" } ], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["tesla_powerwall"] } diff --git a/homeassistant/components/progettihwsw/manifest.json b/homeassistant/components/progettihwsw/manifest.json index d1dbb30f2fcb20..ca4ff88c986a54 100644 --- a/homeassistant/components/progettihwsw/manifest.json +++ b/homeassistant/components/progettihwsw/manifest.json @@ -5,5 +5,6 @@ "codeowners": ["@ardaseremet"], "requirements": ["progettihwsw==0.1.1"], "config_flow": true, - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["ProgettiHWSW"] } diff --git a/homeassistant/components/proliphix/manifest.json b/homeassistant/components/proliphix/manifest.json index e5f2fc056dc3ae..0d035d969dc1c5 100644 --- a/homeassistant/components/proliphix/manifest.json +++ b/homeassistant/components/proliphix/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/proliphix", "requirements": ["proliphix==0.4.1"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["proliphix"] } diff --git a/homeassistant/components/prometheus/manifest.json b/homeassistant/components/prometheus/manifest.json index 9315bf308b7a81..0dfdd03e5e571a 100644 --- a/homeassistant/components/prometheus/manifest.json +++ b/homeassistant/components/prometheus/manifest.json @@ -5,5 +5,6 @@ "requirements": ["prometheus_client==0.7.1"], "dependencies": ["http"], "codeowners": ["@knyar"], - "iot_class": "assumed_state" + "iot_class": "assumed_state", + "loggers": ["prometheus_client"] } diff --git a/homeassistant/components/prosegur/manifest.json b/homeassistant/components/prosegur/manifest.json index 853324c9408144..ecb3a9e6c415ad 100644 --- a/homeassistant/components/prosegur/manifest.json +++ b/homeassistant/components/prosegur/manifest.json @@ -9,5 +9,6 @@ "codeowners": [ "@dgomes" ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyprosegur"] } diff --git a/homeassistant/components/proxmoxve/manifest.json b/homeassistant/components/proxmoxve/manifest.json index dfed6d623f4c27..4b600abc930c0b 100644 --- a/homeassistant/components/proxmoxve/manifest.json +++ b/homeassistant/components/proxmoxve/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/proxmoxve", "codeowners": ["@jhollowe", "@Corbeno"], "requirements": ["proxmoxer==1.1.1"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["proxmoxer"] } diff --git a/homeassistant/components/ps4/manifest.json b/homeassistant/components/ps4/manifest.json index 609b7497744118..a63ed8b7e7ba42 100644 --- a/homeassistant/components/ps4/manifest.json +++ b/homeassistant/components/ps4/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/ps4", "requirements": ["pyps4-2ndscreen==1.2.0"], "codeowners": ["@ktnrg45"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyps4_2ndscreen"] } diff --git a/homeassistant/components/pushbullet/manifest.json b/homeassistant/components/pushbullet/manifest.json index 34356e74a56489..7931cca70ccf5d 100644 --- a/homeassistant/components/pushbullet/manifest.json +++ b/homeassistant/components/pushbullet/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/pushbullet", "requirements": ["pushbullet.py==0.11.0"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pushbullet"] } diff --git a/homeassistant/components/pushover/manifest.json b/homeassistant/components/pushover/manifest.json index 56bfac01859193..0752fbc7b7851c 100644 --- a/homeassistant/components/pushover/manifest.json +++ b/homeassistant/components/pushover/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/pushover", "requirements": ["pushover_complete==1.1.1"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["pushover_complete"] } diff --git a/homeassistant/components/pvpc_hourly_pricing/manifest.json b/homeassistant/components/pvpc_hourly_pricing/manifest.json index 5c9c06776b848f..7b44d2cfa95622 100644 --- a/homeassistant/components/pvpc_hourly_pricing/manifest.json +++ b/homeassistant/components/pvpc_hourly_pricing/manifest.json @@ -6,5 +6,6 @@ "requirements": ["aiopvpc==3.0.0"], "codeowners": ["@azogue"], "quality_scale": "platinum", - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["aiopvpc", "holidays"] } diff --git a/homeassistant/components/python_script/manifest.json b/homeassistant/components/python_script/manifest.json index 8db94bb981747c..2bc2763e777667 100644 --- a/homeassistant/components/python_script/manifest.json +++ b/homeassistant/components/python_script/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/python_script", "requirements": ["restrictedpython==5.2"], "codeowners": [], - "quality_scale": "internal" + "quality_scale": "internal", + "loggers": ["RestrictedPython"] } diff --git a/homeassistant/components/qbittorrent/manifest.json b/homeassistant/components/qbittorrent/manifest.json index 241b9a5cff9cf5..8d49a24a3d9fc2 100644 --- a/homeassistant/components/qbittorrent/manifest.json +++ b/homeassistant/components/qbittorrent/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/qbittorrent", "requirements": ["python-qbittorrent==0.4.2"], "codeowners": ["@geoffreylagaisse"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["qbittorrent"] } diff --git a/homeassistant/components/qld_bushfire/manifest.json b/homeassistant/components/qld_bushfire/manifest.json index 5b3de2cf62bf82..366bbdc347983f 100644 --- a/homeassistant/components/qld_bushfire/manifest.json +++ b/homeassistant/components/qld_bushfire/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/qld_bushfire", "requirements": ["georss_qld_bushfire_alert_client==0.5"], "codeowners": ["@exxamalte"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["georss_qld_bushfire_alert_client"] } diff --git a/homeassistant/components/qnap/manifest.json b/homeassistant/components/qnap/manifest.json index 94f8c8b57886a7..15de916201c386 100644 --- a/homeassistant/components/qnap/manifest.json +++ b/homeassistant/components/qnap/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/qnap", "requirements": ["qnapstats==0.4.0"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["qnapstats"] } diff --git a/homeassistant/components/qrcode/manifest.json b/homeassistant/components/qrcode/manifest.json index 63eca334d7b357..cb1f3a176a4242 100644 --- a/homeassistant/components/qrcode/manifest.json +++ b/homeassistant/components/qrcode/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/qrcode", "requirements": ["pillow==9.0.0", "pyzbar==0.1.7"], "codeowners": [], - "iot_class": "calculated" + "iot_class": "calculated", + "loggers": ["pyzbar"] } diff --git a/homeassistant/components/qvr_pro/manifest.json b/homeassistant/components/qvr_pro/manifest.json index eb08be180c6c4d..70ca1046b9d4d0 100644 --- a/homeassistant/components/qvr_pro/manifest.json +++ b/homeassistant/components/qvr_pro/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/qvr_pro", "requirements": ["pyqvrpro==0.52"], "codeowners": ["@oblogic7"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyqvrpro"] } diff --git a/homeassistant/components/qwikswitch/manifest.json b/homeassistant/components/qwikswitch/manifest.json index 851e93dc67d959..eeba565d994ac0 100644 --- a/homeassistant/components/qwikswitch/manifest.json +++ b/homeassistant/components/qwikswitch/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/qwikswitch", "requirements": ["pyqwikswitch==0.93"], "codeowners": ["@kellerza"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pyqwikswitch"] } diff --git a/homeassistant/components/rachio/manifest.json b/homeassistant/components/rachio/manifest.json index 735e2f35bf4aa9..4ce203b24999f3 100644 --- a/homeassistant/components/rachio/manifest.json +++ b/homeassistant/components/rachio/manifest.json @@ -30,5 +30,6 @@ "name": "rachio*" } ], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["rachiopy"] } diff --git a/homeassistant/components/radiotherm/manifest.json b/homeassistant/components/radiotherm/manifest.json index b051ba65b3b0ca..72c2c8eb3004f1 100644 --- a/homeassistant/components/radiotherm/manifest.json +++ b/homeassistant/components/radiotherm/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/radiotherm", "requirements": ["radiotherm==2.1.0"], "codeowners": ["@vinnyfuria"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["radiotherm"] } diff --git a/homeassistant/components/rainbird/manifest.json b/homeassistant/components/rainbird/manifest.json index d7d3c064ad754c..47bb7ce9bd9a67 100644 --- a/homeassistant/components/rainbird/manifest.json +++ b/homeassistant/components/rainbird/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/rainbird", "requirements": ["pyrainbird==0.4.3"], "codeowners": ["@konikvranik"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyrainbird"] } diff --git a/homeassistant/components/raincloud/manifest.json b/homeassistant/components/raincloud/manifest.json index 309dc6bdb51993..ac049f00316cb4 100644 --- a/homeassistant/components/raincloud/manifest.json +++ b/homeassistant/components/raincloud/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/raincloud", "requirements": ["raincloudy==0.0.7"], "codeowners": ["@vanstinator"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["raincloudy"] } diff --git a/homeassistant/components/rainforest_eagle/manifest.json b/homeassistant/components/rainforest_eagle/manifest.json index 10a7dc35ddcf5a..b4fbc78f24154e 100644 --- a/homeassistant/components/rainforest_eagle/manifest.json +++ b/homeassistant/components/rainforest_eagle/manifest.json @@ -10,5 +10,6 @@ { "macaddress": "D8D5B9*" } - ] + ], + "loggers": ["aioeagle", "uEagle"] } diff --git a/homeassistant/components/rainmachine/manifest.json b/homeassistant/components/rainmachine/manifest.json index 4a272ea036442a..331f191d029cb7 100644 --- a/homeassistant/components/rainmachine/manifest.json +++ b/homeassistant/components/rainmachine/manifest.json @@ -14,5 +14,6 @@ "type": "_http._tcp.local.", "name": "rainmachine*" } - ] + ], + "loggers": ["regenmaschine"] } diff --git a/homeassistant/components/raspyrfm/manifest.json b/homeassistant/components/raspyrfm/manifest.json index 6fd4b13dee07b9..56f4855d460c95 100644 --- a/homeassistant/components/raspyrfm/manifest.json +++ b/homeassistant/components/raspyrfm/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/raspyrfm", "requirements": ["raspyrfm-client==1.2.8"], "codeowners": [], - "iot_class": "assumed_state" + "iot_class": "assumed_state", + "loggers": ["raspyrfm_client"] } diff --git a/homeassistant/components/recollect_waste/manifest.json b/homeassistant/components/recollect_waste/manifest.json index 85cb7100a6507c..68fc0e2c309264 100644 --- a/homeassistant/components/recollect_waste/manifest.json +++ b/homeassistant/components/recollect_waste/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/recollect_waste", "requirements": ["aiorecollect==1.0.8"], "codeowners": ["@bachya"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["aiorecollect"] } diff --git a/homeassistant/components/recswitch/manifest.json b/homeassistant/components/recswitch/manifest.json index c8a724471883e1..dfe177b05a8c50 100644 --- a/homeassistant/components/recswitch/manifest.json +++ b/homeassistant/components/recswitch/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/recswitch", "requirements": ["pyrecswitch==1.0.2"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyrecswitch"] } diff --git a/homeassistant/components/reddit/manifest.json b/homeassistant/components/reddit/manifest.json index 631414ad344346..f641bfd7a5788e 100644 --- a/homeassistant/components/reddit/manifest.json +++ b/homeassistant/components/reddit/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/reddit", "requirements": ["praw==7.4.0"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["praw", "prawcore"] } diff --git a/homeassistant/components/rejseplanen/manifest.json b/homeassistant/components/rejseplanen/manifest.json index 58594f1757745d..93f359b4f78c18 100644 --- a/homeassistant/components/rejseplanen/manifest.json +++ b/homeassistant/components/rejseplanen/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/rejseplanen", "requirements": ["rjpl==0.3.6"], "codeowners": ["@DarkFox"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["rjpl"] } diff --git a/homeassistant/components/remember_the_milk/manifest.json b/homeassistant/components/remember_the_milk/manifest.json index c19cc701afce5b..40bfbe1683c21e 100644 --- a/homeassistant/components/remember_the_milk/manifest.json +++ b/homeassistant/components/remember_the_milk/manifest.json @@ -5,5 +5,6 @@ "requirements": ["RtmAPI==0.7.2", "httplib2==0.19.0"], "dependencies": ["configurator"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["rtmapi"] } diff --git a/homeassistant/components/remote_rpi_gpio/manifest.json b/homeassistant/components/remote_rpi_gpio/manifest.json index b2ed060bffaba3..7e42611dedf0a4 100644 --- a/homeassistant/components/remote_rpi_gpio/manifest.json +++ b/homeassistant/components/remote_rpi_gpio/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/remote_rpi_gpio", "requirements": ["gpiozero==1.5.1"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["gpiozero"] } diff --git a/homeassistant/components/renault/manifest.json b/homeassistant/components/renault/manifest.json index 9442ea8160b296..33b719f88c9cea 100644 --- a/homeassistant/components/renault/manifest.json +++ b/homeassistant/components/renault/manifest.json @@ -9,5 +9,6 @@ "codeowners": [ "@epenet" ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["renault_api"] } diff --git a/homeassistant/components/repetier/manifest.json b/homeassistant/components/repetier/manifest.json index 463c42c3a64672..8f7ffc2766a382 100644 --- a/homeassistant/components/repetier/manifest.json +++ b/homeassistant/components/repetier/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/repetier", "requirements": ["pyrepetierng==0.1.0"], "codeowners": ["@MTrab", "@ShadowBr0ther"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyrepetierng"] } diff --git a/homeassistant/components/rflink/manifest.json b/homeassistant/components/rflink/manifest.json index b14f7594d7139e..debc12ae4e0008 100644 --- a/homeassistant/components/rflink/manifest.json +++ b/homeassistant/components/rflink/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/rflink", "requirements": ["rflink==0.0.62"], "codeowners": ["@javicalle"], - "iot_class": "assumed_state" + "iot_class": "assumed_state", + "loggers": ["rflink"] } diff --git a/homeassistant/components/rfxtrx/manifest.json b/homeassistant/components/rfxtrx/manifest.json index 8ba27cb450ecc6..d712551832989f 100644 --- a/homeassistant/components/rfxtrx/manifest.json +++ b/homeassistant/components/rfxtrx/manifest.json @@ -5,5 +5,6 @@ "requirements": ["pyRFXtrx==0.27.1"], "codeowners": ["@danielhiversen", "@elupus", "@RobBie1221"], "config_flow": true, - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["RFXtrx"] } diff --git a/homeassistant/components/ridwell/manifest.json b/homeassistant/components/ridwell/manifest.json index 4aed69a05f3512..e02a0ba65265db 100644 --- a/homeassistant/components/ridwell/manifest.json +++ b/homeassistant/components/ridwell/manifest.json @@ -9,5 +9,6 @@ "codeowners": [ "@bachya" ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["aioridwell"] } diff --git a/homeassistant/components/ring/manifest.json b/homeassistant/components/ring/manifest.json index 3e745dc2d4b810..a64411e610f766 100644 --- a/homeassistant/components/ring/manifest.json +++ b/homeassistant/components/ring/manifest.json @@ -12,5 +12,6 @@ "macaddress": "0CAE7D*" } ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["ring_doorbell"] } diff --git a/homeassistant/components/ripple/manifest.json b/homeassistant/components/ripple/manifest.json index 68adda3edeae1a..eee0f3d6a77636 100644 --- a/homeassistant/components/ripple/manifest.json +++ b/homeassistant/components/ripple/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/ripple", "requirements": ["python-ripple-api==0.0.3"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyripple"] } diff --git a/homeassistant/components/risco/manifest.json b/homeassistant/components/risco/manifest.json index 2da0a5254a4855..736adcf0c35600 100644 --- a/homeassistant/components/risco/manifest.json +++ b/homeassistant/components/risco/manifest.json @@ -6,5 +6,6 @@ "requirements": ["pyrisco==0.3.1"], "codeowners": ["@OnFreund"], "quality_scale": "platinum", - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyrisco"] } diff --git a/homeassistant/components/rituals_perfume_genie/manifest.json b/homeassistant/components/rituals_perfume_genie/manifest.json index 2daa6e43873715..6c66b906ff69fd 100644 --- a/homeassistant/components/rituals_perfume_genie/manifest.json +++ b/homeassistant/components/rituals_perfume_genie/manifest.json @@ -6,5 +6,6 @@ "requirements": ["pyrituals==0.0.6"], "codeowners": ["@milanmeu"], "quality_scale": "silver", - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyrituals"] } diff --git a/homeassistant/components/rmvtransport/manifest.json b/homeassistant/components/rmvtransport/manifest.json index bcbb96c70349c8..db73d5b519bd62 100644 --- a/homeassistant/components/rmvtransport/manifest.json +++ b/homeassistant/components/rmvtransport/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/rmvtransport", "requirements": ["PyRMVtransport==0.3.3"], "codeowners": ["@cgtobi"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["RMVtransport"] } diff --git a/homeassistant/components/rocketchat/manifest.json b/homeassistant/components/rocketchat/manifest.json index 13e6a7bb745a1e..b95eb9e8cca27d 100644 --- a/homeassistant/components/rocketchat/manifest.json +++ b/homeassistant/components/rocketchat/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/rocketchat", "requirements": ["rocketchat-API==0.6.1"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["rocketchat_API"] } diff --git a/homeassistant/components/roku/manifest.json b/homeassistant/components/roku/manifest.json index 7dd5974589c3ed..6d7989e93cbdc8 100644 --- a/homeassistant/components/roku/manifest.json +++ b/homeassistant/components/roku/manifest.json @@ -16,5 +16,6 @@ "codeowners": ["@ctalkington"], "quality_scale": "silver", "config_flow": true, - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["rokuecp"] } diff --git a/homeassistant/components/roomba/manifest.json b/homeassistant/components/roomba/manifest.json index ad5857aa630f0c..0c14c0189a0fc5 100644 --- a/homeassistant/components/roomba/manifest.json +++ b/homeassistant/components/roomba/manifest.json @@ -15,5 +15,6 @@ "macaddress": "80A589*" } ], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["paho_mqtt", "roombapy"] } diff --git a/homeassistant/components/roon/manifest.json b/homeassistant/components/roon/manifest.json index f48645717357e6..a3b22a3c2cc5eb 100644 --- a/homeassistant/components/roon/manifest.json +++ b/homeassistant/components/roon/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/roon", "requirements": ["roonapi==0.0.38"], "codeowners": ["@pavoni"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["roonapi"] } diff --git a/homeassistant/components/route53/manifest.json b/homeassistant/components/route53/manifest.json index 3320f9021681a8..83a8c0250148d3 100644 --- a/homeassistant/components/route53/manifest.json +++ b/homeassistant/components/route53/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/route53", "requirements": ["boto3==1.20.24"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["boto3", "botocore", "s3transfer"] } diff --git a/homeassistant/components/rova/manifest.json b/homeassistant/components/rova/manifest.json index 27421b20936957..01f2e2703e88bb 100644 --- a/homeassistant/components/rova/manifest.json +++ b/homeassistant/components/rova/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/rova", "requirements": ["rova==0.2.1"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["rova"] } diff --git a/homeassistant/components/rpi_gpio/manifest.json b/homeassistant/components/rpi_gpio/manifest.json index d09c21779fe80e..f8db41b1a31ca7 100644 --- a/homeassistant/components/rpi_gpio/manifest.json +++ b/homeassistant/components/rpi_gpio/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/rpi_gpio", "requirements": ["RPi.GPIO==0.7.1a4"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["RPi"] } diff --git a/homeassistant/components/rpi_gpio_pwm/manifest.json b/homeassistant/components/rpi_gpio_pwm/manifest.json index ea0bdbcb0f357f..78ec56799a598a 100644 --- a/homeassistant/components/rpi_gpio_pwm/manifest.json +++ b/homeassistant/components/rpi_gpio_pwm/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/rpi_gpio_pwm", "requirements": ["pwmled==1.6.7"], "codeowners": ["@soldag"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["adafruit_blinka", "adafruit_circuitpython_pca9685", "pwmled"] } diff --git a/homeassistant/components/rpi_pfio/manifest.json b/homeassistant/components/rpi_pfio/manifest.json index 9e8f0a30e87f70..7f72a7ba77d10c 100644 --- a/homeassistant/components/rpi_pfio/manifest.json +++ b/homeassistant/components/rpi_pfio/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/rpi_pfio", "requirements": ["pifacecommon==4.2.2", "pifacedigitalio==3.0.5"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pifacedigitalio"] } diff --git a/homeassistant/components/rpi_power/manifest.json b/homeassistant/components/rpi_power/manifest.json index 34e249ccfc33b6..ef20651843e65d 100644 --- a/homeassistant/components/rpi_power/manifest.json +++ b/homeassistant/components/rpi_power/manifest.json @@ -5,5 +5,6 @@ "codeowners": ["@shenxn", "@swetoast"], "requirements": ["rpi-bad-power==0.1.0"], "config_flow": true, - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["rpi_bad_power"] } diff --git a/homeassistant/components/rtsp_to_webrtc/manifest.json b/homeassistant/components/rtsp_to_webrtc/manifest.json index cf147df4fe6412..d3a56ebdee6bb7 100644 --- a/homeassistant/components/rtsp_to_webrtc/manifest.json +++ b/homeassistant/components/rtsp_to_webrtc/manifest.json @@ -8,5 +8,6 @@ "codeowners": [ "@allenporter" ], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["rtsp_to_webrtc"] } diff --git a/homeassistant/components/ruckus_unleashed/manifest.json b/homeassistant/components/ruckus_unleashed/manifest.json index b8b2ef6e46a807..f010d3401471f2 100644 --- a/homeassistant/components/ruckus_unleashed/manifest.json +++ b/homeassistant/components/ruckus_unleashed/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/ruckus_unleashed", "requirements": ["pyruckus==0.12"], "codeowners": ["@gabe565"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pexpect", "pyruckus"] } diff --git a/homeassistant/components/russound_rio/manifest.json b/homeassistant/components/russound_rio/manifest.json index a12d149550b132..4b9b7a2c8d030e 100644 --- a/homeassistant/components/russound_rio/manifest.json +++ b/homeassistant/components/russound_rio/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/russound_rio", "requirements": ["russound_rio==0.1.7"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["russound_rio"] } diff --git a/homeassistant/components/russound_rnet/manifest.json b/homeassistant/components/russound_rnet/manifest.json index 0e7928fb23b053..f8aea92b0a0fcd 100644 --- a/homeassistant/components/russound_rnet/manifest.json +++ b/homeassistant/components/russound_rnet/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/russound_rnet", "requirements": ["russound==0.1.9"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["russound"] } diff --git a/homeassistant/components/sabnzbd/manifest.json b/homeassistant/components/sabnzbd/manifest.json index 25dfe6788009c1..08fb1388b38e83 100644 --- a/homeassistant/components/sabnzbd/manifest.json +++ b/homeassistant/components/sabnzbd/manifest.json @@ -6,5 +6,6 @@ "dependencies": ["configurator"], "after_dependencies": ["discovery"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pysabnzbd"] } diff --git a/homeassistant/components/saj/manifest.json b/homeassistant/components/saj/manifest.json index 79067e47c731c8..eaa0121f1dd313 100644 --- a/homeassistant/components/saj/manifest.json +++ b/homeassistant/components/saj/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/saj", "requirements": ["pysaj==0.0.16"], "codeowners": ["@fredericvl"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pysaj"] } diff --git a/homeassistant/components/samsungtv/manifest.json b/homeassistant/components/samsungtv/manifest.json index 9123a68b716f08..9621e18bf17a1d 100644 --- a/homeassistant/components/samsungtv/manifest.json +++ b/homeassistant/components/samsungtv/manifest.json @@ -30,5 +30,6 @@ "@chemelli74" ], "config_flow": true, - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["samsungctl", "samsungtvws"] } diff --git a/homeassistant/components/satel_integra/manifest.json b/homeassistant/components/satel_integra/manifest.json index 6aacb3015e1a6d..6c4a391698b27a 100644 --- a/homeassistant/components/satel_integra/manifest.json +++ b/homeassistant/components/satel_integra/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/satel_integra", "requirements": ["satel_integra==0.3.4"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["satel_integra"] } diff --git a/homeassistant/components/schluter/manifest.json b/homeassistant/components/schluter/manifest.json index 86f0974b6d12f0..90e69afed183a1 100644 --- a/homeassistant/components/schluter/manifest.json +++ b/homeassistant/components/schluter/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/schluter", "requirements": ["py-schluter==0.1.7"], "codeowners": ["@prairieapps"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["schluter"] } diff --git a/homeassistant/components/screenlogic/manifest.json b/homeassistant/components/screenlogic/manifest.json index 09313dab0dd195..016ade188f4dd2 100644 --- a/homeassistant/components/screenlogic/manifest.json +++ b/homeassistant/components/screenlogic/manifest.json @@ -11,5 +11,6 @@ "macaddress": "00C033*" } ], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["screenlogicpy"] } diff --git a/homeassistant/components/scsgate/manifest.json b/homeassistant/components/scsgate/manifest.json index 8720dfac8797bd..a9a63ccd9f4920 100644 --- a/homeassistant/components/scsgate/manifest.json +++ b/homeassistant/components/scsgate/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/scsgate", "requirements": ["scsgate==0.1.0"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["scsgate"] } diff --git a/homeassistant/components/season/manifest.json b/homeassistant/components/season/manifest.json index b48a148034bd62..cfe04f9b1f719d 100644 --- a/homeassistant/components/season/manifest.json +++ b/homeassistant/components/season/manifest.json @@ -5,5 +5,6 @@ "requirements": ["ephem==3.7.7.0"], "codeowners": [], "quality_scale": "internal", - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["ephem"] } diff --git a/homeassistant/components/sendgrid/manifest.json b/homeassistant/components/sendgrid/manifest.json index d31feb5a8e4afa..db9a5c9c48a433 100644 --- a/homeassistant/components/sendgrid/manifest.json +++ b/homeassistant/components/sendgrid/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/sendgrid", "requirements": ["sendgrid==6.8.2"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["sendgrid"] } diff --git a/homeassistant/components/sense/manifest.json b/homeassistant/components/sense/manifest.json index 361a58379d34a6..a7ec66d8b83e83 100644 --- a/homeassistant/components/sense/manifest.json +++ b/homeassistant/components/sense/manifest.json @@ -19,5 +19,6 @@ "macaddress": "A4D578*" } ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["sense_energy"] } diff --git a/homeassistant/components/sensehat/manifest.json b/homeassistant/components/sensehat/manifest.json index d8e607ec816c80..78f6e0609bcfac 100644 --- a/homeassistant/components/sensehat/manifest.json +++ b/homeassistant/components/sensehat/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/sensehat", "requirements": ["sense-hat==2.2.0"], "codeowners": [], - "iot_class": "assumed_state" + "iot_class": "assumed_state", + "loggers": ["sense_hat"] } diff --git a/homeassistant/components/senseme/manifest.json b/homeassistant/components/senseme/manifest.json index 7eba9eb4bdad4e..9e2a9363effb47 100644 --- a/homeassistant/components/senseme/manifest.json +++ b/homeassistant/components/senseme/manifest.json @@ -10,5 +10,6 @@ "@mikelawrence", "@bdraco" ], "dhcp": [{"macaddress":"20F85E*"}], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["aiosenseme"] } diff --git a/homeassistant/components/sensibo/manifest.json b/homeassistant/components/sensibo/manifest.json index bf0142628b4b53..bb9d9ad7569eda 100644 --- a/homeassistant/components/sensibo/manifest.json +++ b/homeassistant/components/sensibo/manifest.json @@ -8,5 +8,6 @@ "iot_class": "cloud_polling", "homekit": { "models": ["Sensibo"] - } + }, + "loggers": ["pysensibo"] } diff --git a/homeassistant/components/serial_pm/manifest.json b/homeassistant/components/serial_pm/manifest.json index 3812a5de072d7b..c427a547790250 100644 --- a/homeassistant/components/serial_pm/manifest.json +++ b/homeassistant/components/serial_pm/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/serial_pm", "requirements": ["pmsensor==0.4"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pmsensor"] } diff --git a/homeassistant/components/sesame/manifest.json b/homeassistant/components/sesame/manifest.json index c4a3e3775ae95e..c6c4db1143be90 100644 --- a/homeassistant/components/sesame/manifest.json +++ b/homeassistant/components/sesame/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/sesame", "requirements": ["pysesame2==1.0.1"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pysesame2"] } diff --git a/homeassistant/components/seventeentrack/manifest.json b/homeassistant/components/seventeentrack/manifest.json index 01fdb22395cdfd..227f19d248113c 100644 --- a/homeassistant/components/seventeentrack/manifest.json +++ b/homeassistant/components/seventeentrack/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/seventeentrack", "requirements": ["py17track==2021.12.2"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["py17track"] } diff --git a/homeassistant/components/sharkiq/manifest.json b/homeassistant/components/sharkiq/manifest.json index 3299e052227900..0875609db1e948 100644 --- a/homeassistant/components/sharkiq/manifest.json +++ b/homeassistant/components/sharkiq/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/sharkiq", "requirements": ["sharkiqpy==0.1.8"], "codeowners": ["@ajmarks"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["sharkiqpy"] } diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json index 568f2b878aec2d..30e766b6ec48a3 100644 --- a/homeassistant/components/shelly/manifest.json +++ b/homeassistant/components/shelly/manifest.json @@ -11,5 +11,6 @@ } ], "codeowners": ["@balloob", "@bieniu", "@thecode", "@chemelli74"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["aioshelly"] } diff --git a/homeassistant/components/shiftr/manifest.json b/homeassistant/components/shiftr/manifest.json index fc475c2f48efaf..e3d27b6b4fc1cb 100644 --- a/homeassistant/components/shiftr/manifest.json +++ b/homeassistant/components/shiftr/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/shiftr", "requirements": ["paho-mqtt==1.6.1"], "codeowners": ["@fabaff"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["paho"] } diff --git a/homeassistant/components/shodan/manifest.json b/homeassistant/components/shodan/manifest.json index bf4aed39cc6a0a..49e6a14b715091 100644 --- a/homeassistant/components/shodan/manifest.json +++ b/homeassistant/components/shodan/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/shodan", "requirements": ["shodan==1.26.1"], "codeowners": ["@fabaff"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["shodan"] } diff --git a/homeassistant/components/sia/manifest.json b/homeassistant/components/sia/manifest.json index c6a8e491217cb9..094b04f630658e 100644 --- a/homeassistant/components/sia/manifest.json +++ b/homeassistant/components/sia/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/sia", "requirements": ["pysiaalarm==3.0.2"], "codeowners": ["@eavanvalkenburg"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pysiaalarm"] } diff --git a/homeassistant/components/sighthound/manifest.json b/homeassistant/components/sighthound/manifest.json index def1359b1eedae..817bdaccd3c8f1 100644 --- a/homeassistant/components/sighthound/manifest.json +++ b/homeassistant/components/sighthound/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/sighthound", "requirements": ["pillow==9.0.0", "simplehound==0.3"], "codeowners": ["@robmarkcole"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["simplehound"] } diff --git a/homeassistant/components/signal_messenger/manifest.json b/homeassistant/components/signal_messenger/manifest.json index 0b5d0febbe771d..e95760fc1e0c04 100644 --- a/homeassistant/components/signal_messenger/manifest.json +++ b/homeassistant/components/signal_messenger/manifest.json @@ -8,5 +8,6 @@ "requirements": [ "pysignalclirestapi==0.3.18" ], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["pysignalclirestapi"] } \ No newline at end of file diff --git a/homeassistant/components/simplepush/manifest.json b/homeassistant/components/simplepush/manifest.json index dc711df0e8d3a4..26321d17aefb13 100644 --- a/homeassistant/components/simplepush/manifest.json +++ b/homeassistant/components/simplepush/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/simplepush", "requirements": ["simplepush==1.1.4"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["simplepush"] } diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index 8e494af013a3ec..30ea49a359cbad 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -11,5 +11,6 @@ "hostname": "simplisafe*", "macaddress": "30AEA4*" } - ] + ], + "loggers": ["simplipy"] } diff --git a/homeassistant/components/sinch/manifest.json b/homeassistant/components/sinch/manifest.json index c33babf4913f87..43b9e465f52b28 100644 --- a/homeassistant/components/sinch/manifest.json +++ b/homeassistant/components/sinch/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/sinch", "codeowners": ["@bendikrb"], "requirements": ["clx-sdk-xms==1.0.0"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["clx"] } diff --git a/homeassistant/components/sisyphus/manifest.json b/homeassistant/components/sisyphus/manifest.json index 1e0f1dc5bad461..62cfca125f6a9c 100644 --- a/homeassistant/components/sisyphus/manifest.json +++ b/homeassistant/components/sisyphus/manifest.json @@ -8,5 +8,6 @@ "codeowners": [ "@jkeljo" ], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["sisyphus_control"] } \ No newline at end of file diff --git a/homeassistant/components/sky_hub/manifest.json b/homeassistant/components/sky_hub/manifest.json index dccfdbe285acba..9f5fd18d53158d 100644 --- a/homeassistant/components/sky_hub/manifest.json +++ b/homeassistant/components/sky_hub/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/sky_hub", "requirements": ["pyskyqhub==0.1.4"], "codeowners": ["@rogerselwyn"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyskyqhub"] } diff --git a/homeassistant/components/skybeacon/manifest.json b/homeassistant/components/skybeacon/manifest.json index da7ee08ff59891..bfca03d754f2ec 100644 --- a/homeassistant/components/skybeacon/manifest.json +++ b/homeassistant/components/skybeacon/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/skybeacon", "requirements": ["pygatt[GATTTOOL]==4.0.5"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pygatt"] } diff --git a/homeassistant/components/skybell/manifest.json b/homeassistant/components/skybell/manifest.json index 8b939d1d522eb2..ce16617996977b 100644 --- a/homeassistant/components/skybell/manifest.json +++ b/homeassistant/components/skybell/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/skybell", "requirements": ["skybellpy==0.6.3"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["skybellpy"] } diff --git a/homeassistant/components/slack/manifest.json b/homeassistant/components/slack/manifest.json index 2605ffd2914fe9..d54bb9e0ec6441 100644 --- a/homeassistant/components/slack/manifest.json +++ b/homeassistant/components/slack/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/slack", "requirements": ["slackclient==2.5.0"], "codeowners": ["@bachya"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["slack"] } diff --git a/homeassistant/components/sleepiq/manifest.json b/homeassistant/components/sleepiq/manifest.json index f6d4404884d865..ac734393197634 100644 --- a/homeassistant/components/sleepiq/manifest.json +++ b/homeassistant/components/sleepiq/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/sleepiq", "requirements": ["sleepyq==0.8.1"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["sleepyq"] } diff --git a/homeassistant/components/slide/manifest.json b/homeassistant/components/slide/manifest.json index a360bb7491a655..324900a1d97d47 100644 --- a/homeassistant/components/slide/manifest.json +++ b/homeassistant/components/slide/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/slide", "requirements": ["goslide-api==0.5.1"], "codeowners": ["@ualex73"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["goslideapi"] } diff --git a/homeassistant/components/sma/manifest.json b/homeassistant/components/sma/manifest.json index d667ab7ea3717f..308c11f91a1ff5 100644 --- a/homeassistant/components/sma/manifest.json +++ b/homeassistant/components/sma/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/sma", "requirements": ["pysma==0.6.10"], "codeowners": ["@kellerza", "@rklomp"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pysma"] } diff --git a/homeassistant/components/smappee/manifest.json b/homeassistant/components/smappee/manifest.json index 6a1edaf41ae74a..f27ec29996e809 100644 --- a/homeassistant/components/smappee/manifest.json +++ b/homeassistant/components/smappee/manifest.json @@ -24,5 +24,6 @@ "name": "smappee50*" } ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["paho_mqtt", "pysmappee"] } diff --git a/homeassistant/components/smart_meter_texas/manifest.json b/homeassistant/components/smart_meter_texas/manifest.json index f70cf59b9b9cf0..2a65de9ed115ca 100644 --- a/homeassistant/components/smart_meter_texas/manifest.json +++ b/homeassistant/components/smart_meter_texas/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/smart_meter_texas", "requirements": ["smart-meter-texas==0.4.7"], "codeowners": ["@grahamwetzler"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["smart_meter_texas"] } diff --git a/homeassistant/components/smarthab/manifest.json b/homeassistant/components/smarthab/manifest.json index 054aaca2d76787..7974215de64aaf 100644 --- a/homeassistant/components/smarthab/manifest.json +++ b/homeassistant/components/smarthab/manifest.json @@ -5,5 +5,6 @@ "config_flow": true, "requirements": ["smarthab==0.21"], "codeowners": ["@outadoc"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pysmarthab"] } diff --git a/homeassistant/components/smartthings/manifest.json b/homeassistant/components/smartthings/manifest.json index b67a05d57537d6..b4a043e2f13610 100644 --- a/homeassistant/components/smartthings/manifest.json +++ b/homeassistant/components/smartthings/manifest.json @@ -29,5 +29,6 @@ "hostname": "hub*", "macaddress": "286D97*" } - ] + ], + "loggers": ["httpsig", "pysmartapp", "pysmartthings"] } diff --git a/homeassistant/components/smarttub/manifest.json b/homeassistant/components/smarttub/manifest.json index 7d9a963b26c8c4..9bec5d4a72e7ba 100644 --- a/homeassistant/components/smarttub/manifest.json +++ b/homeassistant/components/smarttub/manifest.json @@ -7,5 +7,6 @@ "codeowners": ["@mdz"], "requirements": ["python-smarttub==0.0.29"], "quality_scale": "platinum", - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["smarttub"] } diff --git a/homeassistant/components/smarty/manifest.json b/homeassistant/components/smarty/manifest.json index cfae1d98a5b96d..734e1a44dcfee4 100644 --- a/homeassistant/components/smarty/manifest.json +++ b/homeassistant/components/smarty/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/smarty", "requirements": ["pysmarty==0.8"], "codeowners": ["@z0mbieprocess"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pymodbus", "pysmarty"] } diff --git a/homeassistant/components/smhi/manifest.json b/homeassistant/components/smhi/manifest.json index 4eedc28d378581..d1030cb7868647 100644 --- a/homeassistant/components/smhi/manifest.json +++ b/homeassistant/components/smhi/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/smhi", "requirements": ["smhi-pkg==1.0.15"], "codeowners": ["@gjohansson-ST"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["smhi"] } diff --git a/homeassistant/components/sms/manifest.json b/homeassistant/components/sms/manifest.json index 6d736ac44e750f..d98304ebf238f7 100644 --- a/homeassistant/components/sms/manifest.json +++ b/homeassistant/components/sms/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/sms", "requirements": ["python-gammu==3.2.3"], "codeowners": ["@ocalvo"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["gammu"] } diff --git a/homeassistant/components/snapcast/manifest.json b/homeassistant/components/snapcast/manifest.json index 2e3249f4551d74..675a60e40969ef 100644 --- a/homeassistant/components/snapcast/manifest.json +++ b/homeassistant/components/snapcast/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/snapcast", "requirements": ["snapcast==2.1.3"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["construct", "snapcast"] } diff --git a/homeassistant/components/snmp/manifest.json b/homeassistant/components/snmp/manifest.json index 19cd258ce6ffc9..76df9e186060f8 100644 --- a/homeassistant/components/snmp/manifest.json +++ b/homeassistant/components/snmp/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/snmp", "requirements": ["pysnmp==4.4.12"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyasn1", "pysmi", "pysnmp"] } diff --git a/homeassistant/components/sochain/manifest.json b/homeassistant/components/sochain/manifest.json index e270e81012230e..6ff42bd480088b 100644 --- a/homeassistant/components/sochain/manifest.json +++ b/homeassistant/components/sochain/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/sochain", "requirements": ["python-sochain-api==0.0.2"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pysochain"] } diff --git a/homeassistant/components/solaredge/manifest.json b/homeassistant/components/solaredge/manifest.json index 84b1e6b9445bd4..e5c9520f96b84e 100644 --- a/homeassistant/components/solaredge/manifest.json +++ b/homeassistant/components/solaredge/manifest.json @@ -11,5 +11,6 @@ "macaddress": "002702*" } ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["solaredge"] } diff --git a/homeassistant/components/solaredge_local/manifest.json b/homeassistant/components/solaredge_local/manifest.json index 56e722174b4f35..02f21c69fea40c 100644 --- a/homeassistant/components/solaredge_local/manifest.json +++ b/homeassistant/components/solaredge_local/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/solaredge_local", "requirements": ["solaredge-local==0.2.0"], "codeowners": ["@drobtravels", "@scheric"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["solaredge_local"] } diff --git a/homeassistant/components/solarlog/manifest.json b/homeassistant/components/solarlog/manifest.json index 5535da860f0bb1..5d67ed6bf89d1b 100644 --- a/homeassistant/components/solarlog/manifest.json +++ b/homeassistant/components/solarlog/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/solarlog", "codeowners": ["@Ernst79"], "requirements": ["sunwatcher==0.2.1"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["sunwatcher"] } diff --git a/homeassistant/components/solax/manifest.json b/homeassistant/components/solax/manifest.json index e8a905ca8bc3f8..17ae6db0232dff 100644 --- a/homeassistant/components/solax/manifest.json +++ b/homeassistant/components/solax/manifest.json @@ -5,5 +5,6 @@ "requirements": ["solax==0.2.9"], "codeowners": ["@squishykid"], "iot_class": "local_polling", - "config_flow": true + "config_flow": true, + "loggers": ["solax"] } diff --git a/homeassistant/components/soma/manifest.json b/homeassistant/components/soma/manifest.json index 1bde431e9d7f55..88d77b775c5bc4 100644 --- a/homeassistant/components/soma/manifest.json +++ b/homeassistant/components/soma/manifest.json @@ -8,5 +8,6 @@ "@sebfortier2288" ], "requirements": ["pysoma==0.0.10"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["api"] } diff --git a/homeassistant/components/somfy/manifest.json b/homeassistant/components/somfy/manifest.json index 1adbab49fb2c4d..144938b18225b0 100644 --- a/homeassistant/components/somfy/manifest.json +++ b/homeassistant/components/somfy/manifest.json @@ -12,5 +12,6 @@ "name": "gateway*" } ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pymfy"] } diff --git a/homeassistant/components/somfy_mylink/manifest.json b/homeassistant/components/somfy_mylink/manifest.json index a376654ede4ae1..26d56416e644dc 100644 --- a/homeassistant/components/somfy_mylink/manifest.json +++ b/homeassistant/components/somfy_mylink/manifest.json @@ -11,5 +11,6 @@ "macaddress": "B8B7F1*" } ], - "iot_class": "assumed_state" + "iot_class": "assumed_state", + "loggers": ["somfy_mylink_synergy"] } diff --git a/homeassistant/components/sonarr/manifest.json b/homeassistant/components/sonarr/manifest.json index 50de11d8209a02..4b1555fa3deb72 100644 --- a/homeassistant/components/sonarr/manifest.json +++ b/homeassistant/components/sonarr/manifest.json @@ -6,5 +6,6 @@ "requirements": ["sonarr==0.3.0"], "config_flow": true, "quality_scale": "silver", - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["sonarr"] } diff --git a/homeassistant/components/songpal/manifest.json b/homeassistant/components/songpal/manifest.json index 4d417aec1a2f01..97647d8710628e 100644 --- a/homeassistant/components/songpal/manifest.json +++ b/homeassistant/components/songpal/manifest.json @@ -12,5 +12,6 @@ } ], "quality_scale": "gold", - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["songpal"] } diff --git a/homeassistant/components/sonos/manifest.json b/homeassistant/components/sonos/manifest.json index b482556f287722..5986cb18c756cf 100644 --- a/homeassistant/components/sonos/manifest.json +++ b/homeassistant/components/sonos/manifest.json @@ -13,5 +13,6 @@ } ], "codeowners": ["@cgtobi", "@jjlawren"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["soco"] } diff --git a/homeassistant/components/sony_projector/manifest.json b/homeassistant/components/sony_projector/manifest.json index 07819b7b639418..721b0e90402f9b 100644 --- a/homeassistant/components/sony_projector/manifest.json +++ b/homeassistant/components/sony_projector/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/sony_projector", "requirements": ["pysdcp==1"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pysdcp"] } diff --git a/homeassistant/components/soundtouch/manifest.json b/homeassistant/components/soundtouch/manifest.json index 2b8c2fb5477a92..15091ec04f71ae 100644 --- a/homeassistant/components/soundtouch/manifest.json +++ b/homeassistant/components/soundtouch/manifest.json @@ -5,5 +5,6 @@ "requirements": ["libsoundtouch==0.8"], "after_dependencies": ["zeroconf"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["libsoundtouch"] } diff --git a/homeassistant/components/spc/manifest.json b/homeassistant/components/spc/manifest.json index 9906a4025a5624..088ddc8dd7bce4 100644 --- a/homeassistant/components/spc/manifest.json +++ b/homeassistant/components/spc/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/spc", "requirements": ["pyspcwebgw==0.4.0"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pyspcwebgw"] } diff --git a/homeassistant/components/spider/manifest.json b/homeassistant/components/spider/manifest.json index b80fa0926cde0e..56cd6876e9fbd3 100644 --- a/homeassistant/components/spider/manifest.json +++ b/homeassistant/components/spider/manifest.json @@ -5,5 +5,6 @@ "requirements": ["spiderpy==1.6.1"], "codeowners": ["@peternijssen"], "config_flow": true, - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["spiderpy"] } \ No newline at end of file diff --git a/homeassistant/components/splunk/manifest.json b/homeassistant/components/splunk/manifest.json index 09a128c9b72fac..7ada3ea2a37757 100644 --- a/homeassistant/components/splunk/manifest.json +++ b/homeassistant/components/splunk/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/splunk", "requirements": ["hass_splunk==0.1.1"], "codeowners": ["@Bre77"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["hass_splunk"] } diff --git a/homeassistant/components/spotify/manifest.json b/homeassistant/components/spotify/manifest.json index 402083aa25d760..9dcf1fee6dc1f2 100644 --- a/homeassistant/components/spotify/manifest.json +++ b/homeassistant/components/spotify/manifest.json @@ -8,5 +8,6 @@ "codeowners": ["@frenck"], "config_flow": true, "quality_scale": "silver", - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["spotipy"] } diff --git a/homeassistant/components/squeezebox/manifest.json b/homeassistant/components/squeezebox/manifest.json index ec3089dc4bef88..f36917f1a01ab2 100644 --- a/homeassistant/components/squeezebox/manifest.json +++ b/homeassistant/components/squeezebox/manifest.json @@ -11,5 +11,6 @@ "macaddress": "000420*" } ], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pysqueezebox"] } diff --git a/homeassistant/components/srp_energy/manifest.json b/homeassistant/components/srp_energy/manifest.json index 73aac879a00278..fc5c8a598ccf81 100644 --- a/homeassistant/components/srp_energy/manifest.json +++ b/homeassistant/components/srp_energy/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/srp_energy", "requirements": ["srpenergy==1.3.2"], "codeowners": ["@briglx"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["srpenergy"] } diff --git a/homeassistant/components/ssdp/manifest.json b/homeassistant/components/ssdp/manifest.json index 44d972ca16abcd..e1a5d20a987387 100644 --- a/homeassistant/components/ssdp/manifest.json +++ b/homeassistant/components/ssdp/manifest.json @@ -7,5 +7,6 @@ "after_dependencies": ["zeroconf"], "codeowners": [], "quality_scale": "internal", - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["async_upnp_client"] } diff --git a/homeassistant/components/starline/manifest.json b/homeassistant/components/starline/manifest.json index e487d8d63f0f79..d565b7aa690838 100644 --- a/homeassistant/components/starline/manifest.json +++ b/homeassistant/components/starline/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/starline", "requirements": ["starline==0.1.5"], "codeowners": ["@anonym-tsk"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["starline"] } diff --git a/homeassistant/components/starlingbank/manifest.json b/homeassistant/components/starlingbank/manifest.json index 8de4b4c24dc115..7f658c4409e8d5 100644 --- a/homeassistant/components/starlingbank/manifest.json +++ b/homeassistant/components/starlingbank/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/starlingbank", "requirements": ["starlingbank==3.2"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["starlingbank"] } diff --git a/homeassistant/components/statsd/manifest.json b/homeassistant/components/statsd/manifest.json index 5e4db0b6770f09..39c69e6052f63f 100644 --- a/homeassistant/components/statsd/manifest.json +++ b/homeassistant/components/statsd/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/statsd", "requirements": ["statsd==3.2.1"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["statsd"] } diff --git a/homeassistant/components/steam_online/manifest.json b/homeassistant/components/steam_online/manifest.json index ca5e4f1da53f69..47f645d7148c23 100644 --- a/homeassistant/components/steam_online/manifest.json +++ b/homeassistant/components/steam_online/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/steam_online", "requirements": ["steamodd==4.21"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["steam"] } diff --git a/homeassistant/components/steamist/manifest.json b/homeassistant/components/steamist/manifest.json index e815b48233019d..2856fe94240048 100644 --- a/homeassistant/components/steamist/manifest.json +++ b/homeassistant/components/steamist/manifest.json @@ -12,5 +12,6 @@ "macaddress": "001E0C*", "hostname": "my[45]50*" } - ] + ], + "loggers": ["aiosteamist", "discovery30303"] } \ No newline at end of file diff --git a/homeassistant/components/stiebel_eltron/manifest.json b/homeassistant/components/stiebel_eltron/manifest.json index 3f83c35ffa9d42..feb9657ef31637 100644 --- a/homeassistant/components/stiebel_eltron/manifest.json +++ b/homeassistant/components/stiebel_eltron/manifest.json @@ -5,5 +5,6 @@ "requirements": ["pystiebeleltron==0.0.1.dev2"], "dependencies": ["modbus"], "codeowners": ["@fucm"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pymodbus", "pystiebeleltron"] } diff --git a/homeassistant/components/stream/manifest.json b/homeassistant/components/stream/manifest.json index 1fe64defe36961..5f6dd4e61aaf87 100644 --- a/homeassistant/components/stream/manifest.json +++ b/homeassistant/components/stream/manifest.json @@ -6,5 +6,6 @@ "dependencies": ["http"], "codeowners": ["@hunterjm", "@uvjustin", "@allenporter"], "quality_scale": "internal", - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["av"] } diff --git a/homeassistant/components/streamlabswater/manifest.json b/homeassistant/components/streamlabswater/manifest.json index cb42752d966d6a..20473b66f2acad 100644 --- a/homeassistant/components/streamlabswater/manifest.json +++ b/homeassistant/components/streamlabswater/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/streamlabswater", "requirements": ["streamlabswater==1.0.1"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["streamlabswater"] } diff --git a/homeassistant/components/subaru/manifest.json b/homeassistant/components/subaru/manifest.json index 2b7af28a916068..b08b2381211571 100644 --- a/homeassistant/components/subaru/manifest.json +++ b/homeassistant/components/subaru/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/subaru", "requirements": ["subarulink==0.3.12"], "codeowners": ["@G-Two"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["stdiomask", "subarulink"] } diff --git a/homeassistant/components/suez_water/manifest.json b/homeassistant/components/suez_water/manifest.json index 20c8ba1dfed768..ddda3caf2ffe66 100644 --- a/homeassistant/components/suez_water/manifest.json +++ b/homeassistant/components/suez_water/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/suez_water", "codeowners": ["@ooii"], "requirements": ["pysuez==0.1.19"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pysuez", "regex"] } diff --git a/homeassistant/components/supla/manifest.json b/homeassistant/components/supla/manifest.json index 6420e39538e9e4..789ac76512cb55 100644 --- a/homeassistant/components/supla/manifest.json +++ b/homeassistant/components/supla/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/supla", "requirements": ["asyncpysupla==0.0.5"], "codeowners": ["@mwegrzynek"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["asyncpysupla"] } diff --git a/homeassistant/components/surepetcare/manifest.json b/homeassistant/components/surepetcare/manifest.json index 13def08280a6d8..8675099c530e9b 100644 --- a/homeassistant/components/surepetcare/manifest.json +++ b/homeassistant/components/surepetcare/manifest.json @@ -10,5 +10,6 @@ "surepy==0.7.2" ], "iot_class": "cloud_polling", - "config_flow": true + "config_flow": true, + "loggers": ["rich", "surepy"] } \ No newline at end of file diff --git a/homeassistant/components/swiss_hydrological_data/manifest.json b/homeassistant/components/swiss_hydrological_data/manifest.json index 7d7280ecc5fa8b..a0400cb543b4e9 100644 --- a/homeassistant/components/swiss_hydrological_data/manifest.json +++ b/homeassistant/components/swiss_hydrological_data/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/swiss_hydrological_data", "requirements": ["swisshydrodata==0.1.0"], "codeowners": ["@fabaff"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["swisshydrodata"] } diff --git a/homeassistant/components/swiss_public_transport/manifest.json b/homeassistant/components/swiss_public_transport/manifest.json index 1a4a90031ba722..1fe5316c78a764 100644 --- a/homeassistant/components/swiss_public_transport/manifest.json +++ b/homeassistant/components/swiss_public_transport/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/swiss_public_transport", "requirements": ["python_opendata_transport==0.3.0"], "codeowners": ["@fabaff"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["opendata_transport"] } diff --git a/homeassistant/components/switchbot/manifest.json b/homeassistant/components/switchbot/manifest.json index 59415d31c1edb1..617698b8b022dd 100644 --- a/homeassistant/components/switchbot/manifest.json +++ b/homeassistant/components/switchbot/manifest.json @@ -5,5 +5,6 @@ "requirements": ["PySwitchbot==0.13.2"], "config_flow": true, "codeowners": ["@danielhiversen", "@RenierM26"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["switchbot"] } diff --git a/homeassistant/components/switcher_kis/manifest.json b/homeassistant/components/switcher_kis/manifest.json index c8ed7ceefdcdb3..9ebf83b4acd59d 100644 --- a/homeassistant/components/switcher_kis/manifest.json +++ b/homeassistant/components/switcher_kis/manifest.json @@ -6,5 +6,6 @@ "requirements": ["aioswitcher==2.0.6"], "quality_scale": "platinum", "iot_class": "local_push", - "config_flow": true + "config_flow": true, + "loggers": ["aioswitcher"] } diff --git a/homeassistant/components/switchmate/manifest.json b/homeassistant/components/switchmate/manifest.json index 042ccd93091fe4..c4a263aca19a36 100644 --- a/homeassistant/components/switchmate/manifest.json +++ b/homeassistant/components/switchmate/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/switchmate", "requirements": ["pySwitchmate==0.4.6"], "codeowners": ["@danielhiversen"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["switchmate"] } diff --git a/homeassistant/components/syncthing/manifest.json b/homeassistant/components/syncthing/manifest.json index cd779e1657b789..9d2897abf66b8f 100644 --- a/homeassistant/components/syncthing/manifest.json +++ b/homeassistant/components/syncthing/manifest.json @@ -8,5 +8,6 @@ "@zhulik" ], "quality_scale": "silver", - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["aiosyncthing"] } diff --git a/homeassistant/components/syncthru/manifest.json b/homeassistant/components/syncthru/manifest.json index 37b7ed311cb588..4536e703ce9f6c 100644 --- a/homeassistant/components/syncthru/manifest.json +++ b/homeassistant/components/syncthru/manifest.json @@ -11,5 +11,6 @@ } ], "codeowners": ["@nielstron"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pysyncthru"] } diff --git a/homeassistant/components/synology_dsm/manifest.json b/homeassistant/components/synology_dsm/manifest.json index c436557cea928b..1efefb62109f28 100644 --- a/homeassistant/components/synology_dsm/manifest.json +++ b/homeassistant/components/synology_dsm/manifest.json @@ -11,5 +11,6 @@ "deviceType": "urn:schemas-upnp-org:device:Basic:1" } ], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["synology_dsm"] } diff --git a/homeassistant/components/synology_srm/manifest.json b/homeassistant/components/synology_srm/manifest.json index b4d96f6f9b1bcb..5ee3e114f1fdde 100644 --- a/homeassistant/components/synology_srm/manifest.json +++ b/homeassistant/components/synology_srm/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/synology_srm", "requirements": ["synology-srm==0.2.0"], "codeowners": ["@aerialls"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["synology_srm"] } diff --git a/homeassistant/components/system_bridge/manifest.json b/homeassistant/components/system_bridge/manifest.json index cd4ee5a51a18e9..31c19e4614f66c 100644 --- a/homeassistant/components/system_bridge/manifest.json +++ b/homeassistant/components/system_bridge/manifest.json @@ -8,5 +8,6 @@ "zeroconf": ["_system-bridge._udp.local."], "after_dependencies": ["zeroconf"], "quality_scale": "silver", - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["systembridge"] } diff --git a/homeassistant/components/systemmonitor/manifest.json b/homeassistant/components/systemmonitor/manifest.json index cc79ed12e1e13a..e8a63c9c40a363 100644 --- a/homeassistant/components/systemmonitor/manifest.json +++ b/homeassistant/components/systemmonitor/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/systemmonitor", "requirements": ["psutil==5.8.0"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["psutil"] } diff --git a/homeassistant/components/tado/manifest.json b/homeassistant/components/tado/manifest.json index 50561042ea765e..529b4bcfb970d6 100644 --- a/homeassistant/components/tado/manifest.json +++ b/homeassistant/components/tado/manifest.json @@ -13,5 +13,6 @@ "hostname": "tado*" } ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["PyTado"] } diff --git a/homeassistant/components/tank_utility/manifest.json b/homeassistant/components/tank_utility/manifest.json index 62a667af5b14e2..a9ebcb546b518c 100644 --- a/homeassistant/components/tank_utility/manifest.json +++ b/homeassistant/components/tank_utility/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/tank_utility", "requirements": ["tank_utility==1.4.0"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["tank_utility"] } diff --git a/homeassistant/components/tankerkoenig/manifest.json b/homeassistant/components/tankerkoenig/manifest.json index d49ee6a1255273..d3ad7fbe2e1bd5 100644 --- a/homeassistant/components/tankerkoenig/manifest.json +++ b/homeassistant/components/tankerkoenig/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/tankerkoenig", "requirements": ["pytankerkoenig==0.0.6"], "codeowners": ["@guillempages"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pytankerkoenig"] } diff --git a/homeassistant/components/tapsaff/manifest.json b/homeassistant/components/tapsaff/manifest.json index f8c4dff154522a..6904f90a402026 100644 --- a/homeassistant/components/tapsaff/manifest.json +++ b/homeassistant/components/tapsaff/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/tapsaff", "requirements": ["tapsaff==0.2.1"], "codeowners": ["@bazwilliams"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["tapsaff"] } diff --git a/homeassistant/components/tasmota/manifest.json b/homeassistant/components/tasmota/manifest.json index bd30231396f999..a1f52517690fbd 100644 --- a/homeassistant/components/tasmota/manifest.json +++ b/homeassistant/components/tasmota/manifest.json @@ -7,5 +7,6 @@ "dependencies": ["mqtt"], "mqtt": ["tasmota/discovery/#"], "codeowners": ["@emontnemery"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["hatasmota"] } diff --git a/homeassistant/components/tautulli/manifest.json b/homeassistant/components/tautulli/manifest.json index 68edea99838512..06c2d4e0c6b818 100644 --- a/homeassistant/components/tautulli/manifest.json +++ b/homeassistant/components/tautulli/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/tautulli", "requirements": ["pytautulli==21.11.0"], "codeowners": ["@ludeeus"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pytautulli"] } diff --git a/homeassistant/components/telegram_bot/manifest.json b/homeassistant/components/telegram_bot/manifest.json index 048762903e1cb3..d5eec8db552113 100644 --- a/homeassistant/components/telegram_bot/manifest.json +++ b/homeassistant/components/telegram_bot/manifest.json @@ -5,5 +5,6 @@ "requirements": ["python-telegram-bot==13.1", "PySocks==1.7.1"], "dependencies": ["http"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["telegram"] } diff --git a/homeassistant/components/tellstick/manifest.json b/homeassistant/components/tellstick/manifest.json index 5d8029ddcf529b..bb0f9d32e3e967 100644 --- a/homeassistant/components/tellstick/manifest.json +++ b/homeassistant/components/tellstick/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/tellstick", "requirements": ["tellcore-net==0.4", "tellcore-py==1.1.2"], "codeowners": [], - "iot_class": "assumed_state" + "iot_class": "assumed_state", + "loggers": ["tellcore"] } diff --git a/homeassistant/components/temper/manifest.json b/homeassistant/components/temper/manifest.json index 0443987a87b55e..b71bbe915632b1 100644 --- a/homeassistant/components/temper/manifest.json +++ b/homeassistant/components/temper/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/temper", "requirements": ["temperusb==1.5.3"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyusb", "temperusb"] } diff --git a/homeassistant/components/tensorflow/manifest.json b/homeassistant/components/tensorflow/manifest.json index 26b7421ef44102..771d6f6fd9dd56 100644 --- a/homeassistant/components/tensorflow/manifest.json +++ b/homeassistant/components/tensorflow/manifest.json @@ -10,5 +10,6 @@ "pillow==9.0.0" ], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["tensorflow"] } diff --git a/homeassistant/components/tesla_wall_connector/manifest.json b/homeassistant/components/tesla_wall_connector/manifest.json index 8e86fa3d2f8891..28c2d222f3a21e 100644 --- a/homeassistant/components/tesla_wall_connector/manifest.json +++ b/homeassistant/components/tesla_wall_connector/manifest.json @@ -21,5 +21,6 @@ "codeowners": [ "@einarhauks" ], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["tesla_wall_connector"] } \ No newline at end of file diff --git a/homeassistant/components/thermoworks_smoke/manifest.json b/homeassistant/components/thermoworks_smoke/manifest.json index aa9a87413907b8..d9f2052bd53007 100644 --- a/homeassistant/components/thermoworks_smoke/manifest.json +++ b/homeassistant/components/thermoworks_smoke/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/thermoworks_smoke", "requirements": ["stringcase==1.2.0", "thermoworks_smoke==0.1.8"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["thermoworks_smoke"] } diff --git a/homeassistant/components/thingspeak/manifest.json b/homeassistant/components/thingspeak/manifest.json index 3ac2e7e4b2523f..f14ea25768be42 100644 --- a/homeassistant/components/thingspeak/manifest.json +++ b/homeassistant/components/thingspeak/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/thingspeak", "requirements": ["thingspeak==1.0.0"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["thingspeak"] } diff --git a/homeassistant/components/thinkingcleaner/manifest.json b/homeassistant/components/thinkingcleaner/manifest.json index cb87c1ea8a3759..33081cb967d945 100644 --- a/homeassistant/components/thinkingcleaner/manifest.json +++ b/homeassistant/components/thinkingcleaner/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/thinkingcleaner", "requirements": ["pythinkingcleaner==0.0.3"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pythinkingcleaner"] } diff --git a/homeassistant/components/tibber/manifest.json b/homeassistant/components/tibber/manifest.json index 2f5927442a2d4c..90a19526c7c7db 100644 --- a/homeassistant/components/tibber/manifest.json +++ b/homeassistant/components/tibber/manifest.json @@ -7,5 +7,6 @@ "codeowners": ["@danielhiversen"], "quality_scale": "silver", "config_flow": true, - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["tibber"] } diff --git a/homeassistant/components/tikteck/manifest.json b/homeassistant/components/tikteck/manifest.json index 8e332df8f625fb..39d4d808a1565b 100644 --- a/homeassistant/components/tikteck/manifest.json +++ b/homeassistant/components/tikteck/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/tikteck", "requirements": ["tikteck==0.4"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["tikteck"] } diff --git a/homeassistant/components/tile/manifest.json b/homeassistant/components/tile/manifest.json index 1b30e0483f704c..4ef1b579e1734a 100644 --- a/homeassistant/components/tile/manifest.json +++ b/homeassistant/components/tile/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/tile", "requirements": ["pytile==2022.01.0"], "codeowners": ["@bachya"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pytile"] } diff --git a/homeassistant/components/tmb/manifest.json b/homeassistant/components/tmb/manifest.json index 4032b7e27d6c98..a9b4da9b2fddbe 100644 --- a/homeassistant/components/tmb/manifest.json +++ b/homeassistant/components/tmb/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/tmb", "requirements": ["tmb==0.0.4"], "codeowners": ["@alemuro"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["tmb"] } diff --git a/homeassistant/components/todoist/manifest.json b/homeassistant/components/todoist/manifest.json index 09cd080b4d7705..a00819638f3ce0 100644 --- a/homeassistant/components/todoist/manifest.json +++ b/homeassistant/components/todoist/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/todoist", "requirements": ["todoist-python==8.0.0"], "codeowners": ["@boralyl"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["todoist"] } diff --git a/homeassistant/components/tof/manifest.json b/homeassistant/components/tof/manifest.json index 83a0ba6fbe342f..e530c67b93002f 100644 --- a/homeassistant/components/tof/manifest.json +++ b/homeassistant/components/tof/manifest.json @@ -5,5 +5,6 @@ "requirements": ["VL53L1X2==0.1.5"], "dependencies": ["rpi_gpio"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["VL53L1X2"] } diff --git a/homeassistant/components/tolo/manifest.json b/homeassistant/components/tolo/manifest.json index 63e87ebf876fe7..aa60958591c893 100644 --- a/homeassistant/components/tolo/manifest.json +++ b/homeassistant/components/tolo/manifest.json @@ -10,5 +10,6 @@ "@MatthiasLohr" ], "iot_class": "local_polling", - "dhcp": [{"hostname": "usr-tcp232-ed2"}] + "dhcp": [{"hostname": "usr-tcp232-ed2"}], + "loggers": ["tololib"] } \ No newline at end of file diff --git a/homeassistant/components/toon/manifest.json b/homeassistant/components/toon/manifest.json index dc32b6bfac5991..f6dc4ae284329a 100644 --- a/homeassistant/components/toon/manifest.json +++ b/homeassistant/components/toon/manifest.json @@ -13,5 +13,6 @@ "macaddress": "74C63B*" } ], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["toonapi"] } diff --git a/homeassistant/components/totalconnect/manifest.json b/homeassistant/components/totalconnect/manifest.json index d3be51f91d264c..3960d21842395a 100644 --- a/homeassistant/components/totalconnect/manifest.json +++ b/homeassistant/components/totalconnect/manifest.json @@ -6,5 +6,6 @@ "dependencies": [], "codeowners": ["@austinmroczek"], "config_flow": true, - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["total_connect_client"] } diff --git a/homeassistant/components/touchline/manifest.json b/homeassistant/components/touchline/manifest.json index 1ea02f29ae2842..5d1ef4cc0dcb8c 100644 --- a/homeassistant/components/touchline/manifest.json +++ b/homeassistant/components/touchline/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/touchline", "requirements": ["pytouchline==0.7"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pytouchline"] } diff --git a/homeassistant/components/tplink/manifest.json b/homeassistant/components/tplink/manifest.json index 1531f96c5458c9..378435b9ec0f37 100644 --- a/homeassistant/components/tplink/manifest.json +++ b/homeassistant/components/tplink/manifest.json @@ -113,5 +113,6 @@ "hostname": "lb*", "macaddress": "B09575*" } - ] + ], + "loggers": ["kasa"] } diff --git a/homeassistant/components/tplink_lte/manifest.json b/homeassistant/components/tplink_lte/manifest.json index c18ccbb61067ae..63e20212005dac 100644 --- a/homeassistant/components/tplink_lte/manifest.json +++ b/homeassistant/components/tplink_lte/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/tplink_lte", "requirements": ["tp-connected==0.0.4"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["tp_connected"] } diff --git a/homeassistant/components/traccar/manifest.json b/homeassistant/components/traccar/manifest.json index 77a8511a671ad0..7f0df1b1f3fbf3 100644 --- a/homeassistant/components/traccar/manifest.json +++ b/homeassistant/components/traccar/manifest.json @@ -6,5 +6,6 @@ "requirements": ["pytraccar==0.10.0", "stringcase==1.2.0"], "dependencies": ["webhook"], "codeowners": ["@ludeeus"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pytraccar"] } diff --git a/homeassistant/components/tractive/manifest.json b/homeassistant/components/tractive/manifest.json index b388703e6bd583..a73c8390ad89f1 100644 --- a/homeassistant/components/tractive/manifest.json +++ b/homeassistant/components/tractive/manifest.json @@ -11,5 +11,6 @@ "@zhulik", "@bieniu" ], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["aiotractive"] } diff --git a/homeassistant/components/tradfri/manifest.json b/homeassistant/components/tradfri/manifest.json index 1ac82d0b84cc63..1950b00a079828 100644 --- a/homeassistant/components/tradfri/manifest.json +++ b/homeassistant/components/tradfri/manifest.json @@ -8,5 +8,6 @@ "models": ["TRADFRI"] }, "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pytradfri"] } diff --git a/homeassistant/components/trafikverket_train/manifest.json b/homeassistant/components/trafikverket_train/manifest.json index 36a1d47623e2b3..da1d4de6c13165 100644 --- a/homeassistant/components/trafikverket_train/manifest.json +++ b/homeassistant/components/trafikverket_train/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/trafikverket_train", "requirements": ["pytrafikverket==0.1.6.2"], "codeowners": ["@endor-force", "@gjohansson-ST"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pytrafikverket"] } diff --git a/homeassistant/components/trafikverket_weatherstation/manifest.json b/homeassistant/components/trafikverket_weatherstation/manifest.json index 6490468dc03fc8..4001856b703cd7 100644 --- a/homeassistant/components/trafikverket_weatherstation/manifest.json +++ b/homeassistant/components/trafikverket_weatherstation/manifest.json @@ -5,5 +5,6 @@ "requirements": ["pytrafikverket==0.1.6.2"], "codeowners": ["@endor-force", "@gjohansson-ST"], "config_flow": true, - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pytrafikverket"] } diff --git a/homeassistant/components/transmission/manifest.json b/homeassistant/components/transmission/manifest.json index 1f5843e5e6c886..8f4fabc529d1b7 100644 --- a/homeassistant/components/transmission/manifest.json +++ b/homeassistant/components/transmission/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/transmission", "requirements": ["transmissionrpc==0.11"], "codeowners": ["@engrbm87", "@JPHutchins"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["transmissionrpc"] } diff --git a/homeassistant/components/transport_nsw/manifest.json b/homeassistant/components/transport_nsw/manifest.json index e6670b0e4f6dd2..994fcde1b297a7 100644 --- a/homeassistant/components/transport_nsw/manifest.json +++ b/homeassistant/components/transport_nsw/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/transport_nsw", "requirements": ["PyTransportNSW==0.1.1"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["TransportNSW"] } diff --git a/homeassistant/components/travisci/manifest.json b/homeassistant/components/travisci/manifest.json index c991eecebb2e22..874563745cfb00 100644 --- a/homeassistant/components/travisci/manifest.json +++ b/homeassistant/components/travisci/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/travisci", "requirements": ["TravisPy==0.3.5"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["travispy"] } diff --git a/homeassistant/components/tts/manifest.json b/homeassistant/components/tts/manifest.json index 8f7d203c215af7..f81d112e825381 100644 --- a/homeassistant/components/tts/manifest.json +++ b/homeassistant/components/tts/manifest.json @@ -6,5 +6,6 @@ "dependencies": ["http"], "after_dependencies": ["media_player"], "codeowners": ["@pvizeli"], - "quality_scale": "internal" + "quality_scale": "internal", + "loggers": ["mutagen"] } diff --git a/homeassistant/components/tuya/manifest.json b/homeassistant/components/tuya/manifest.json index 1b8772a36df9f2..24f9324fe5e396 100644 --- a/homeassistant/components/tuya/manifest.json +++ b/homeassistant/components/tuya/manifest.json @@ -19,5 +19,6 @@ { "macaddress": "84E342*" }, { "macaddress": "D4A651*" }, { "macaddress": "D81F12*" } - ] + ], + "loggers": ["tuya_iot"] } diff --git a/homeassistant/components/twentemilieu/manifest.json b/homeassistant/components/twentemilieu/manifest.json index 2a9a7915e763bb..d0b94efe289c9b 100644 --- a/homeassistant/components/twentemilieu/manifest.json +++ b/homeassistant/components/twentemilieu/manifest.json @@ -6,5 +6,6 @@ "requirements": ["twentemilieu==0.5.0"], "codeowners": ["@frenck"], "quality_scale": "platinum", - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["twentemilieu"] } diff --git a/homeassistant/components/twilio/manifest.json b/homeassistant/components/twilio/manifest.json index f34dc5684c3f98..5c1415bc8fc96a 100644 --- a/homeassistant/components/twilio/manifest.json +++ b/homeassistant/components/twilio/manifest.json @@ -6,5 +6,6 @@ "requirements": ["twilio==6.32.0"], "dependencies": ["webhook"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["twilio"] } diff --git a/homeassistant/components/twilio_call/manifest.json b/homeassistant/components/twilio_call/manifest.json index 1317bd9a558680..318ecb8304e2b2 100644 --- a/homeassistant/components/twilio_call/manifest.json +++ b/homeassistant/components/twilio_call/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/twilio_call", "dependencies": ["twilio"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["twilio"] } diff --git a/homeassistant/components/twinkly/manifest.json b/homeassistant/components/twinkly/manifest.json index c78f5152f13b05..871cd27166dbac 100644 --- a/homeassistant/components/twinkly/manifest.json +++ b/homeassistant/components/twinkly/manifest.json @@ -6,5 +6,6 @@ "codeowners": ["@dr1rrb", "@Robbie1221"], "config_flow": true, "dhcp": [{ "hostname": "twinkly_*" }], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["ttls"] } diff --git a/homeassistant/components/twitch/manifest.json b/homeassistant/components/twitch/manifest.json index 706f2d7ab2cc8a..17f1c8586c012d 100644 --- a/homeassistant/components/twitch/manifest.json +++ b/homeassistant/components/twitch/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/twitch", "requirements": ["python-twitch-client==0.6.0"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["twitch"] } diff --git a/homeassistant/components/twitter/manifest.json b/homeassistant/components/twitter/manifest.json index ffd42b8b0fe6b1..4e80eef60218f3 100644 --- a/homeassistant/components/twitter/manifest.json +++ b/homeassistant/components/twitter/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/twitter", "requirements": ["TwitterAPI==2.7.5"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["TwitterAPI"] } diff --git a/homeassistant/components/ubus/manifest.json b/homeassistant/components/ubus/manifest.json index af19bd68a0617b..83953b81d53ecb 100644 --- a/homeassistant/components/ubus/manifest.json +++ b/homeassistant/components/ubus/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/ubus", "requirements": ["openwrt-ubus-rpc==0.0.2"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["openwrt"] } diff --git a/homeassistant/components/unifi/manifest.json b/homeassistant/components/unifi/manifest.json index 71e546879b02d4..0739138ecc7e3c 100644 --- a/homeassistant/components/unifi/manifest.json +++ b/homeassistant/components/unifi/manifest.json @@ -24,5 +24,6 @@ "modelDescription": "UniFi Dream Machine SE" } ], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["aiounifi"] } \ No newline at end of file diff --git a/homeassistant/components/unifi_direct/manifest.json b/homeassistant/components/unifi_direct/manifest.json index e901d66acbf244..b3ed7d2ef2fc67 100644 --- a/homeassistant/components/unifi_direct/manifest.json +++ b/homeassistant/components/unifi_direct/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/unifi_direct", "requirements": ["pexpect==4.6.0"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pexpect", "ptyprocess"] } diff --git a/homeassistant/components/unifiled/manifest.json b/homeassistant/components/unifiled/manifest.json index 46656e4cb3d10d..d0716dcec3a3d8 100644 --- a/homeassistant/components/unifiled/manifest.json +++ b/homeassistant/components/unifiled/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/unifiled", "codeowners": ["@florisvdk"], "requirements": ["unifiled==0.11"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["unifiled"] } diff --git a/homeassistant/components/unifiprotect/manifest.json b/homeassistant/components/unifiprotect/manifest.json index 22dec33917d012..a4b7064e564c97 100644 --- a/homeassistant/components/unifiprotect/manifest.json +++ b/homeassistant/components/unifiprotect/manifest.json @@ -59,5 +59,6 @@ "manufacturer": "Ubiquiti Networks", "modelDescription": "UniFi Dream Machine SE" } - ] + ], + "loggers": ["pyunifiprotect", "unifi_discovery"] } diff --git a/homeassistant/components/upb/manifest.json b/homeassistant/components/upb/manifest.json index 75b64806dffd0a..fd5d68e577f0f7 100644 --- a/homeassistant/components/upb/manifest.json +++ b/homeassistant/components/upb/manifest.json @@ -5,5 +5,6 @@ "requirements": ["upb_lib==0.4.12"], "codeowners": ["@gwww"], "config_flow": true, - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["upb_lib"] } diff --git a/homeassistant/components/upc_connect/manifest.json b/homeassistant/components/upc_connect/manifest.json index 8d5d2c16fbb489..e4994049452639 100644 --- a/homeassistant/components/upc_connect/manifest.json +++ b/homeassistant/components/upc_connect/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/upc_connect", "requirements": ["connect-box==0.2.8"], "codeowners": ["@pvizeli", "@fabaff"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["connect_box"] } diff --git a/homeassistant/components/upcloud/manifest.json b/homeassistant/components/upcloud/manifest.json index a9e0f74462e0a8..26e1f92ef9ac7c 100644 --- a/homeassistant/components/upcloud/manifest.json +++ b/homeassistant/components/upcloud/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/upcloud", "requirements": ["upcloud-api==2.0.0"], "codeowners": ["@scop"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["upcloud_api"] } diff --git a/homeassistant/components/upnp/manifest.json b/homeassistant/components/upnp/manifest.json index 2ac975ada4aa70..a52c2948557c4e 100644 --- a/homeassistant/components/upnp/manifest.json +++ b/homeassistant/components/upnp/manifest.json @@ -14,5 +14,6 @@ "st": "urn:schemas-upnp-org:device:InternetGatewayDevice:2" } ], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["async_upnp_client"] } diff --git a/homeassistant/components/uptimerobot/manifest.json b/homeassistant/components/uptimerobot/manifest.json index 17241dba19639c..f52f751fc01a89 100644 --- a/homeassistant/components/uptimerobot/manifest.json +++ b/homeassistant/components/uptimerobot/manifest.json @@ -10,5 +10,6 @@ ], "quality_scale": "platinum", "iot_class": "cloud_polling", - "config_flow": true + "config_flow": true, + "loggers": ["pyuptimerobot"] } \ No newline at end of file diff --git a/homeassistant/components/uscis/manifest.json b/homeassistant/components/uscis/manifest.json index 6ae41e340ab135..0680848f70a32b 100644 --- a/homeassistant/components/uscis/manifest.json +++ b/homeassistant/components/uscis/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/uscis", "requirements": ["uscisstatus==0.1.1"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["uscisstatus"] } diff --git a/homeassistant/components/usgs_earthquakes_feed/manifest.json b/homeassistant/components/usgs_earthquakes_feed/manifest.json index d38a5c056b841e..9c1f4566dc3919 100644 --- a/homeassistant/components/usgs_earthquakes_feed/manifest.json +++ b/homeassistant/components/usgs_earthquakes_feed/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/usgs_earthquakes_feed", "requirements": ["geojson_client==0.6"], "codeowners": ["@exxamalte"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["geojson_client"] } diff --git a/homeassistant/components/utility_meter/manifest.json b/homeassistant/components/utility_meter/manifest.json index a1ba3b6d370a99..fb880f567d1e27 100644 --- a/homeassistant/components/utility_meter/manifest.json +++ b/homeassistant/components/utility_meter/manifest.json @@ -5,5 +5,6 @@ "requirements": ["croniter==1.0.6"], "codeowners": ["@dgomes"], "quality_scale": "internal", - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["croniter"] } diff --git a/homeassistant/components/uvc/manifest.json b/homeassistant/components/uvc/manifest.json index 507ee518454a37..99e43c6654ffba 100644 --- a/homeassistant/components/uvc/manifest.json +++ b/homeassistant/components/uvc/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/uvc", "requirements": ["uvcclient==0.11.0"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["uvcclient"] } diff --git a/homeassistant/components/vallox/manifest.json b/homeassistant/components/vallox/manifest.json index 4fb0bd29ac58ee..aed87e9239d973 100644 --- a/homeassistant/components/vallox/manifest.json +++ b/homeassistant/components/vallox/manifest.json @@ -5,5 +5,6 @@ "requirements": ["vallox-websocket-api==2.9.0"], "codeowners": ["@andre-richter", "@slovdahl", "@viiru-"], "config_flow": true, - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["vallox_websocket_api"] } diff --git a/homeassistant/components/vasttrafik/manifest.json b/homeassistant/components/vasttrafik/manifest.json index 965e84435db7df..4f4a6a8b4a8be9 100644 --- a/homeassistant/components/vasttrafik/manifest.json +++ b/homeassistant/components/vasttrafik/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/vasttrafik", "requirements": ["vtjp==0.1.14"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["vasttrafik"] } diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index f52ba0fd99d9f7..2543ef580a9753 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -24,5 +24,6 @@ "vid": "10CF", "pid": "0518" } - ] + ], + "loggers": ["velbusaio"] } diff --git a/homeassistant/components/velux/manifest.json b/homeassistant/components/velux/manifest.json index c72e25d42eb7cc..4a5ea07dc82e7d 100644 --- a/homeassistant/components/velux/manifest.json +++ b/homeassistant/components/velux/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/velux", "requirements": ["pyvlx==0.2.19"], "codeowners": ["@Julius2342"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyvlx"] } diff --git a/homeassistant/components/venstar/manifest.json b/homeassistant/components/venstar/manifest.json index 6fef7bf5d5790a..d9f5b51e0ef8e0 100644 --- a/homeassistant/components/venstar/manifest.json +++ b/homeassistant/components/venstar/manifest.json @@ -7,5 +7,6 @@ "venstarcolortouch==0.15" ], "codeowners": ["@garbled1"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["venstarcolortouch"] } diff --git a/homeassistant/components/vera/manifest.json b/homeassistant/components/vera/manifest.json index 84cf9eac007c24..5a87ae29483af8 100644 --- a/homeassistant/components/vera/manifest.json +++ b/homeassistant/components/vera/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/vera", "requirements": ["pyvera==0.3.13"], "codeowners": ["@pavoni"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyvera"] } diff --git a/homeassistant/components/verisure/manifest.json b/homeassistant/components/verisure/manifest.json index 0bd04961ec72c0..c71be7ee4fcb58 100644 --- a/homeassistant/components/verisure/manifest.json +++ b/homeassistant/components/verisure/manifest.json @@ -10,5 +10,6 @@ "macaddress": "0023C1*" } ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["verisure"] } diff --git a/homeassistant/components/versasense/manifest.json b/homeassistant/components/versasense/manifest.json index 470177997d0406..fee8faeab86eda 100644 --- a/homeassistant/components/versasense/manifest.json +++ b/homeassistant/components/versasense/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/versasense", "codeowners": ["@flamm3blemuff1n"], "requirements": ["pyversasense==0.0.6"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyversasense"] } diff --git a/homeassistant/components/version/manifest.json b/homeassistant/components/version/manifest.json index 5a4cd70f4c7a83..803076e44dc840 100644 --- a/homeassistant/components/version/manifest.json +++ b/homeassistant/components/version/manifest.json @@ -11,5 +11,6 @@ ], "quality_scale": "internal", "iot_class": "local_push", - "config_flow": true + "config_flow": true, + "loggers": ["pyhaversion"] } \ No newline at end of file diff --git a/homeassistant/components/vesync/manifest.json b/homeassistant/components/vesync/manifest.json index 761379f130891c..2637cfaa746389 100644 --- a/homeassistant/components/vesync/manifest.json +++ b/homeassistant/components/vesync/manifest.json @@ -5,5 +5,6 @@ "codeowners": ["@markperdue", "@webdjoe", "@thegardenmonkey"], "requirements": ["pyvesync==1.4.2"], "config_flow": true, - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["pyvesync"] } diff --git a/homeassistant/components/vicare/manifest.json b/homeassistant/components/vicare/manifest.json index 98a7eb4c07c1d2..3b3058058b6af7 100644 --- a/homeassistant/components/vicare/manifest.json +++ b/homeassistant/components/vicare/manifest.json @@ -10,5 +10,6 @@ { "macaddress": "B87424*" } - ] + ], + "loggers": ["PyViCare"] } diff --git a/homeassistant/components/vilfo/manifest.json b/homeassistant/components/vilfo/manifest.json index 568db1afdc04ab..e14dc58cf294be 100644 --- a/homeassistant/components/vilfo/manifest.json +++ b/homeassistant/components/vilfo/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/vilfo", "requirements": ["vilfo-api-client==0.3.2"], "codeowners": ["@ManneW"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["vilfo"] } diff --git a/homeassistant/components/vivotek/manifest.json b/homeassistant/components/vivotek/manifest.json index c3a48b304021d7..ba44a69478df9e 100644 --- a/homeassistant/components/vivotek/manifest.json +++ b/homeassistant/components/vivotek/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/vivotek", "requirements": ["libpyvivotek==0.4.0"], "codeowners": ["@HarlemSquirrel"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["libpyvivotek"] } diff --git a/homeassistant/components/vizio/manifest.json b/homeassistant/components/vizio/manifest.json index f686a6ac1fcee9..5b534f861cc37f 100644 --- a/homeassistant/components/vizio/manifest.json +++ b/homeassistant/components/vizio/manifest.json @@ -7,5 +7,6 @@ "config_flow": true, "zeroconf": ["_viziocast._tcp.local."], "quality_scale": "platinum", - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyvizio"] } diff --git a/homeassistant/components/vlc_telnet/manifest.json b/homeassistant/components/vlc_telnet/manifest.json index aa3721fe6452c8..494cae37b5712d 100644 --- a/homeassistant/components/vlc_telnet/manifest.json +++ b/homeassistant/components/vlc_telnet/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/vlc_telnet", "requirements": ["aiovlc==0.1.0"], "codeowners": ["@rodripf", "@dmcc", "@MartinHjelmare"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["aiovlc"] } diff --git a/homeassistant/components/volkszaehler/manifest.json b/homeassistant/components/volkszaehler/manifest.json index 11624da7f5329e..286e18b0b17cbc 100644 --- a/homeassistant/components/volkszaehler/manifest.json +++ b/homeassistant/components/volkszaehler/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/volkszaehler", "requirements": ["volkszaehler==0.2.1"], "codeowners": ["@fabaff"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["volkszaehler"] } diff --git a/homeassistant/components/volumio/manifest.json b/homeassistant/components/volumio/manifest.json index 818df8c83d9404..3785ed0ecc742e 100644 --- a/homeassistant/components/volumio/manifest.json +++ b/homeassistant/components/volumio/manifest.json @@ -6,5 +6,6 @@ "config_flow": true, "zeroconf": ["_Volumio._tcp.local."], "requirements": ["pyvolumio==0.1.5"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyvolumio"] } diff --git a/homeassistant/components/volvooncall/manifest.json b/homeassistant/components/volvooncall/manifest.json index eac179efa8d802..48caa75a824966 100644 --- a/homeassistant/components/volvooncall/manifest.json +++ b/homeassistant/components/volvooncall/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/volvooncall", "requirements": ["volvooncall==0.9.1"], "codeowners": ["@molobrakos", "@decompil3d"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["geopy", "hbmqtt", "volvooncall"] } diff --git a/homeassistant/components/vultr/manifest.json b/homeassistant/components/vultr/manifest.json index 0fbd4e2ebe4daf..449b9a33e34d3b 100644 --- a/homeassistant/components/vultr/manifest.json +++ b/homeassistant/components/vultr/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/vultr", "requirements": ["vultr==0.1.2"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["vultr"] } diff --git a/homeassistant/components/w800rf32/manifest.json b/homeassistant/components/w800rf32/manifest.json index 6089c00be489ab..1a754351e7b9bf 100644 --- a/homeassistant/components/w800rf32/manifest.json +++ b/homeassistant/components/w800rf32/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/w800rf32", "requirements": ["pyW800rf32==0.1"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["W800rf32"] } diff --git a/homeassistant/components/wallbox/manifest.json b/homeassistant/components/wallbox/manifest.json index aeadf541345a9b..2a4978b1cc104d 100644 --- a/homeassistant/components/wallbox/manifest.json +++ b/homeassistant/components/wallbox/manifest.json @@ -9,5 +9,6 @@ "homekit": {}, "dependencies": [], "codeowners": ["@hesselonline"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["wallbox"] } diff --git a/homeassistant/components/waqi/manifest.json b/homeassistant/components/waqi/manifest.json index 48f812f447a9ec..d4818d44626861 100644 --- a/homeassistant/components/waqi/manifest.json +++ b/homeassistant/components/waqi/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/waqi", "requirements": ["waqiasync==1.0.0"], "codeowners": ["@andrey-git"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["waqiasync"] } diff --git a/homeassistant/components/waterfurnace/manifest.json b/homeassistant/components/waterfurnace/manifest.json index 82f60abbd64e93..8699df289d74f8 100644 --- a/homeassistant/components/waterfurnace/manifest.json +++ b/homeassistant/components/waterfurnace/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/waterfurnace", "requirements": ["waterfurnace==1.1.0"], "codeowners": [], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["waterfurnace"] } diff --git a/homeassistant/components/watson_iot/manifest.json b/homeassistant/components/watson_iot/manifest.json index 95f5b3c7d0a975..7b65b5d0faa11f 100644 --- a/homeassistant/components/watson_iot/manifest.json +++ b/homeassistant/components/watson_iot/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/watson_iot", "requirements": ["ibmiotf==0.3.4"], "codeowners": [], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["ibmiotf", "paho_mqtt"] } diff --git a/homeassistant/components/watson_tts/manifest.json b/homeassistant/components/watson_tts/manifest.json index cf70a8088293e8..f225ac25ae72ba 100644 --- a/homeassistant/components/watson_tts/manifest.json +++ b/homeassistant/components/watson_tts/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/watson_tts", "requirements": ["ibm-watson==5.2.2"], "codeowners": ["@rutkai"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["ibm_cloud_sdk_core", "ibm_watson"] } diff --git a/homeassistant/components/watttime/manifest.json b/homeassistant/components/watttime/manifest.json index 85a32bce3314a4..95c53624069253 100644 --- a/homeassistant/components/watttime/manifest.json +++ b/homeassistant/components/watttime/manifest.json @@ -9,5 +9,6 @@ "codeowners": [ "@bachya" ], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["aiowatttime"] } diff --git a/homeassistant/components/waze_travel_time/manifest.json b/homeassistant/components/waze_travel_time/manifest.json index 7991cbccbb4d77..1a08c5c4703549 100644 --- a/homeassistant/components/waze_travel_time/manifest.json +++ b/homeassistant/components/waze_travel_time/manifest.json @@ -5,5 +5,6 @@ "requirements": ["WazeRouteCalculator==0.14"], "codeowners": [], "config_flow": true, - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["WazeRouteCalculator"] } diff --git a/homeassistant/components/webostv/manifest.json b/homeassistant/components/webostv/manifest.json index 3a2a302527704e..733693720a08a0 100644 --- a/homeassistant/components/webostv/manifest.json +++ b/homeassistant/components/webostv/manifest.json @@ -7,5 +7,6 @@ "codeowners": ["@bendavid", "@thecode"], "ssdp": [{"st": "urn:lge-com:service:webos-second-screen:1"}], "quality_scale": "platinum", - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["aiowebostv"] } \ No newline at end of file diff --git a/homeassistant/components/wemo/manifest.json b/homeassistant/components/wemo/manifest.json index d0643ed51a98b0..d048a59d38c54b 100644 --- a/homeassistant/components/wemo/manifest.json +++ b/homeassistant/components/wemo/manifest.json @@ -13,5 +13,6 @@ "models": ["Socket", "Wemo"] }, "codeowners": ["@esev"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["pywemo"] } diff --git a/homeassistant/components/whirlpool/manifest.json b/homeassistant/components/whirlpool/manifest.json index 9df10f32931e97..ce5c76c72f0497 100644 --- a/homeassistant/components/whirlpool/manifest.json +++ b/homeassistant/components/whirlpool/manifest.json @@ -9,5 +9,6 @@ "codeowners": [ "@abmantis" ], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["whirlpool"] } diff --git a/homeassistant/components/whois/manifest.json b/homeassistant/components/whois/manifest.json index acfb9e2178a77e..8cbb0f6f502a23 100644 --- a/homeassistant/components/whois/manifest.json +++ b/homeassistant/components/whois/manifest.json @@ -5,5 +5,6 @@ "requirements": ["whois==0.9.13"], "config_flow": true, "codeowners": ["@frenck"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["whois"] } diff --git a/homeassistant/components/wiffi/manifest.json b/homeassistant/components/wiffi/manifest.json index 58d0f9778d709d..e28062c74c0757 100644 --- a/homeassistant/components/wiffi/manifest.json +++ b/homeassistant/components/wiffi/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/wiffi", "requirements": ["wiffi==1.1.0"], "codeowners": ["@mampfes"], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["wiffi"] } diff --git a/homeassistant/components/wilight/manifest.json b/homeassistant/components/wilight/manifest.json index fec9fdb6c6a20d..972de72a9c99cb 100644 --- a/homeassistant/components/wilight/manifest.json +++ b/homeassistant/components/wilight/manifest.json @@ -11,5 +11,6 @@ ], "codeowners": ["@leofig-rj"], "quality_scale": "silver", - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pywilight"] } diff --git a/homeassistant/components/wirelesstag/manifest.json b/homeassistant/components/wirelesstag/manifest.json index 6074b64d6649c3..881ac34c93f57c 100644 --- a/homeassistant/components/wirelesstag/manifest.json +++ b/homeassistant/components/wirelesstag/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/wirelesstag", "requirements": ["wirelesstagpy==0.8.1"], "codeowners": ["@sergeymaysak"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["wirelesstagpy"] } diff --git a/homeassistant/components/withings/manifest.json b/homeassistant/components/withings/manifest.json index d1c867cd4e6457..f9ec5321c620ee 100644 --- a/homeassistant/components/withings/manifest.json +++ b/homeassistant/components/withings/manifest.json @@ -6,5 +6,6 @@ "requirements": ["withings-api==2.3.2"], "dependencies": ["http", "webhook"], "codeowners": ["@vangorra"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["withings_api"] } diff --git a/homeassistant/components/wolflink/manifest.json b/homeassistant/components/wolflink/manifest.json index 749f7bbc67c1fd..f597093382ee0f 100644 --- a/homeassistant/components/wolflink/manifest.json +++ b/homeassistant/components/wolflink/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/wolflink", "requirements": ["wolf_smartset==0.1.11"], "codeowners": ["@adamkrol93"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["wolf_smartset"] } diff --git a/homeassistant/components/workday/manifest.json b/homeassistant/components/workday/manifest.json index 6140abf4f2aef8..cdf9fa5567b0ef 100644 --- a/homeassistant/components/workday/manifest.json +++ b/homeassistant/components/workday/manifest.json @@ -5,5 +5,6 @@ "requirements": ["holidays==0.12"], "codeowners": ["@fabaff"], "quality_scale": "internal", - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["convertdate", "hijri_converter", "holidays", "korean_lunar_calendar"] } diff --git a/homeassistant/components/xbee/manifest.json b/homeassistant/components/xbee/manifest.json index fbf9cc925baf44..bd1a0d2a1e153b 100644 --- a/homeassistant/components/xbee/manifest.json +++ b/homeassistant/components/xbee/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/xbee", "requirements": ["xbee-helper==0.0.7"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["xbee_helper"] } diff --git a/homeassistant/components/xbox_live/manifest.json b/homeassistant/components/xbox_live/manifest.json index 94ebef9f24177d..f2dacccb7c3038 100644 --- a/homeassistant/components/xbox_live/manifest.json +++ b/homeassistant/components/xbox_live/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/xbox_live", "requirements": ["xboxapi==2.0.1"], "codeowners": ["@MartinHjelmare"], - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["xboxapi"] } diff --git a/homeassistant/components/xeoma/manifest.json b/homeassistant/components/xeoma/manifest.json index e235d35237f0fe..12958a93825de6 100644 --- a/homeassistant/components/xeoma/manifest.json +++ b/homeassistant/components/xeoma/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/xeoma", "requirements": ["pyxeoma==1.4.1"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyxeoma"] } diff --git a/homeassistant/components/xiaomi_aqara/manifest.json b/homeassistant/components/xiaomi_aqara/manifest.json index 13444c6ad69b11..bcc3eef933e73b 100644 --- a/homeassistant/components/xiaomi_aqara/manifest.json +++ b/homeassistant/components/xiaomi_aqara/manifest.json @@ -7,5 +7,6 @@ "after_dependencies": ["discovery"], "codeowners": ["@danielhiversen", "@syssi"], "zeroconf": ["_miio._udp.local."], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["xiaomi_gateway"] } diff --git a/homeassistant/components/xiaomi_miio/manifest.json b/homeassistant/components/xiaomi_miio/manifest.json index da2b94f5382a08..239e8c289102a9 100644 --- a/homeassistant/components/xiaomi_miio/manifest.json +++ b/homeassistant/components/xiaomi_miio/manifest.json @@ -6,5 +6,6 @@ "requirements": ["construct==2.10.56", "micloud==0.5", "python-miio==0.5.9.2"], "codeowners": ["@rytilahti", "@syssi", "@starkillerOG", "@bieniu"], "zeroconf": ["_miio._udp.local."], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["micloud", "miio"] } diff --git a/homeassistant/components/xiaomi_tv/manifest.json b/homeassistant/components/xiaomi_tv/manifest.json index 85fbbef7928a36..303480c2e7f9ee 100644 --- a/homeassistant/components/xiaomi_tv/manifest.json +++ b/homeassistant/components/xiaomi_tv/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/xiaomi_tv", "requirements": ["pymitv==1.4.3"], "codeowners": ["@simse"], - "iot_class": "assumed_state" + "iot_class": "assumed_state", + "loggers": ["pymitv"] } diff --git a/homeassistant/components/xmpp/manifest.json b/homeassistant/components/xmpp/manifest.json index 55df2587898e9f..840f2cd677d168 100644 --- a/homeassistant/components/xmpp/manifest.json +++ b/homeassistant/components/xmpp/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/xmpp", "requirements": ["slixmpp==1.7.1"], "codeowners": ["@fabaff", "@flowolf"], - "iot_class": "cloud_push" + "iot_class": "cloud_push", + "loggers": ["pyasn1", "slixmpp"] } diff --git a/homeassistant/components/xs1/manifest.json b/homeassistant/components/xs1/manifest.json index 4cb5770bed7c0a..cbc0e147f5b48c 100644 --- a/homeassistant/components/xs1/manifest.json +++ b/homeassistant/components/xs1/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/xs1", "requirements": ["xs1-api-client==3.0.0"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["xs1_api_client"] } diff --git a/homeassistant/components/yale_smart_alarm/manifest.json b/homeassistant/components/yale_smart_alarm/manifest.json index 6bc3846ea67a6f..b2c9cd82f5aabf 100644 --- a/homeassistant/components/yale_smart_alarm/manifest.json +++ b/homeassistant/components/yale_smart_alarm/manifest.json @@ -5,5 +5,6 @@ "requirements": ["yalesmartalarmclient==0.3.7"], "codeowners": ["@gjohansson-ST"], "config_flow": true, - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "loggers": ["yalesmartalarmclient"] } diff --git a/homeassistant/components/yamaha/manifest.json b/homeassistant/components/yamaha/manifest.json index 437e9479ae18a0..7fc86f707b30d1 100644 --- a/homeassistant/components/yamaha/manifest.json +++ b/homeassistant/components/yamaha/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/yamaha", "requirements": ["rxv==0.7.0"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["rxv"] } diff --git a/homeassistant/components/yamaha_musiccast/manifest.json b/homeassistant/components/yamaha_musiccast/manifest.json index 7d07d57fc289bd..a50ef69d57e0f4 100644 --- a/homeassistant/components/yamaha_musiccast/manifest.json +++ b/homeassistant/components/yamaha_musiccast/manifest.json @@ -18,5 +18,6 @@ "codeowners": [ "@vigonotion", "@micha91" - ] + ], + "loggers": ["aiomusiccast"] } \ No newline at end of file diff --git a/homeassistant/components/yeelight/manifest.json b/homeassistant/components/yeelight/manifest.json index 5320b8023e9c52..7820e17a9782e2 100644 --- a/homeassistant/components/yeelight/manifest.json +++ b/homeassistant/components/yeelight/manifest.json @@ -17,5 +17,6 @@ "homekit": { "models": ["YL*"] }, - "after_dependencies": ["ssdp"] + "after_dependencies": ["ssdp"], + "loggers": ["async_upnp_client", "yeelight"] } diff --git a/homeassistant/components/yeelightsunflower/manifest.json b/homeassistant/components/yeelightsunflower/manifest.json index 17156ae3490acd..edae33b75aaa50 100644 --- a/homeassistant/components/yeelightsunflower/manifest.json +++ b/homeassistant/components/yeelightsunflower/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/yeelightsunflower", "requirements": ["yeelightsunflower==0.0.10"], "codeowners": ["@lindsaymarkward"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["yeelightsunflower"] } diff --git a/homeassistant/components/yi/manifest.json b/homeassistant/components/yi/manifest.json index 140b1cf3132ea6..232995427365d1 100644 --- a/homeassistant/components/yi/manifest.json +++ b/homeassistant/components/yi/manifest.json @@ -5,5 +5,6 @@ "requirements": ["aioftp==0.12.0"], "dependencies": ["ffmpeg"], "codeowners": ["@bachya"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["aioftp"] } diff --git a/homeassistant/components/youless/manifest.json b/homeassistant/components/youless/manifest.json index f5713c51680efb..1e952452f46d62 100644 --- a/homeassistant/components/youless/manifest.json +++ b/homeassistant/components/youless/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/youless", "requirements": ["youless-api==0.16"], "codeowners": ["@gjong"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["youless_api"] } diff --git a/homeassistant/components/zabbix/manifest.json b/homeassistant/components/zabbix/manifest.json index 39f8ebae4aeae3..8101fd6bf796ff 100644 --- a/homeassistant/components/zabbix/manifest.json +++ b/homeassistant/components/zabbix/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/zabbix", "requirements": ["py-zabbix==1.1.7"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["pyzabbix"] } diff --git a/homeassistant/components/zengge/manifest.json b/homeassistant/components/zengge/manifest.json index 45cf866f51f252..98f2ab1de21e9c 100644 --- a/homeassistant/components/zengge/manifest.json +++ b/homeassistant/components/zengge/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/zengge", "requirements": ["zengge==0.2"], "codeowners": [], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["zengge"] } diff --git a/homeassistant/components/zerproc/manifest.json b/homeassistant/components/zerproc/manifest.json index dfaf6587d3b187..eb43edc7fec5b0 100644 --- a/homeassistant/components/zerproc/manifest.json +++ b/homeassistant/components/zerproc/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/zerproc", "requirements": ["pyzerproc==0.4.8"], "codeowners": ["@emlove"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["bleak", "pyzerproc"] } diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index dfc1ffba538a78..98c5e277f1708e 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -72,5 +72,6 @@ } ], "after_dependencies": ["usb", "zeroconf"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["aiosqlite", "bellows", "crccheck", "pure_pcapy3", "zhaquirks", "zigpy", "zigpy_deconz", "zigpy_xbee", "zigpy_zigate", "zigpy_znp"] } diff --git a/homeassistant/components/zhong_hong/manifest.json b/homeassistant/components/zhong_hong/manifest.json index c57e23507c97ec..d953675965f9d2 100644 --- a/homeassistant/components/zhong_hong/manifest.json +++ b/homeassistant/components/zhong_hong/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/zhong_hong", "requirements": ["zhong_hong_hvac==1.0.9"], "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["zhong_hong_hvac"] } diff --git a/homeassistant/components/zoneminder/manifest.json b/homeassistant/components/zoneminder/manifest.json index 92324f338b5524..699e2e5b7a4fce 100644 --- a/homeassistant/components/zoneminder/manifest.json +++ b/homeassistant/components/zoneminder/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/zoneminder", "requirements": ["zm-py==0.5.2"], "codeowners": ["@rohankapoorcom"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "loggers": ["zoneminder"] } diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index f56255c736dc40..d6cc9938eba19f 100644 --- a/homeassistant/components/zwave_js/manifest.json +++ b/homeassistant/components/zwave_js/manifest.json @@ -11,5 +11,6 @@ {"vid":"0658","pid":"0200","known_devices":["Aeotec Z-Stick Gen5+", "Z-WaveMe UZB"]}, {"vid":"10C4","pid":"8A2A","description":"*z-wave*","known_devices":["Nortek HUSBZB-1"]}, {"vid":"10C4","pid":"EA60","known_devices":["Aeotec Z-Stick 7", "Silicon Labs UZB-7", "Zooz ZST10 700"]} - ] + ], + "loggers": ["zwave_js_server"] } diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 04ddd8df571fe2..7217fd5940bf15 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -88,6 +88,7 @@ class Manifest(TypedDict, total=False): is_built_in: bool version: str codeowners: list[str] + loggers: list[str] def manifest_from_legacy_module(domain: str, module: ModuleType) -> Manifest: @@ -442,6 +443,11 @@ def issue_tracker(self) -> str | None: """Return issue tracker link.""" return self.manifest.get("issue_tracker") + @property + def loggers(self) -> list[str] | None: + """Return list of loggers used by the integration.""" + return self.manifest.get("loggers") + @property def quality_scale(self) -> str | None: """Return Integration Quality Scale.""" diff --git a/script/hassfest/manifest.py b/script/hassfest/manifest.py index 54d4944cf70340..55cfd44bae9f5c 100644 --- a/script/hassfest/manifest.py +++ b/script/hassfest/manifest.py @@ -244,6 +244,7 @@ def verify_wildcard(value: str): vol.Optional("dependencies"): [str], vol.Optional("after_dependencies"): [str], vol.Required("codeowners"): [str], + vol.Optional("loggers"): [str], vol.Optional("disabled"): str, vol.Optional("iot_class"): vol.In(SUPPORTED_IOT_CLASSES), } diff --git a/tests/test_loader.py b/tests/test_loader.py index 68946a9de0123e..8cc923840c2698 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -615,3 +615,22 @@ async def test_validation(hass): """Test we raise if invalid domain passed in.""" with pytest.raises(ValueError): await loader.async_get_integration(hass, "some.thing") + + +async def test_loggers(hass): + """Test we can fetch the loggers from the integration.""" + name = "dummy" + integration = loader.Integration( + hass, + f"homeassistant.components.{name}", + None, + { + "name": name, + "domain": name, + "config_flow": True, + "dependencies": [], + "requirements": [], + "loggers": ["name1", "name2"], + }, + ) + assert integration.loggers == ["name1", "name2"] From 36427fe76c26023701a3b040b8c5808812aa9297 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Fri, 28 Jan 2022 22:57:12 +0100 Subject: [PATCH 0061/1098] Fix excepton for SamsungTV getting device info (#65151) --- homeassistant/components/samsungtv/bridge.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/samsungtv/bridge.py b/homeassistant/components/samsungtv/bridge.py index 262bf4ce67f09e..d509da91304884 100644 --- a/homeassistant/components/samsungtv/bridge.py +++ b/homeassistant/components/samsungtv/bridge.py @@ -5,6 +5,7 @@ import contextlib from typing import Any +from requests.exceptions import Timeout as RequestsTimeout from samsungctl import Remote from samsungctl.exceptions import AccessDenied, ConnectionClosed, UnhandledResponse from samsungtvws import SamsungTVWS @@ -321,7 +322,7 @@ def try_connect(self) -> str: def device_info(self) -> dict[str, Any] | None: """Try to gather infos of this TV.""" if remote := self._get_remote(avoid_open=True): - with contextlib.suppress(HttpApiError): + with contextlib.suppress(HttpApiError, RequestsTimeout): device_info: dict[str, Any] = remote.rest_device_info() return device_info From 783e26e8e47018061d6388c8d107b44e5ef8fdc6 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 28 Jan 2022 23:09:05 +0100 Subject: [PATCH 0062/1098] Use isolated build environments (#65145) --- .github/workflows/builder.yml | 6 ++++-- pyproject.toml | 4 ++++ script/release | 32 -------------------------------- 3 files changed, 8 insertions(+), 34 deletions(-) delete mode 100755 script/release diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 89c4d02c942cf4..74016d4492cd26 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -76,8 +76,10 @@ jobs: - name: Build package shell: bash run: | - pip install twine wheel - python setup.py sdist bdist_wheel + # Remove dist, build, and homeassistant.egg-info + # when build locally for testing! + pip install twine build + python -m build - name: Upload package shell: bash diff --git a/pyproject.toml b/pyproject.toml index 52b000bd1af4d9..69398645d18303 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,7 @@ +[build-system] +requires = ["setuptools~=60.5", "wheel~=0.37.1"] +build-backend = "setuptools.build_meta" + [tool.black] target-version = ["py38"] exclude = 'generated' diff --git a/script/release b/script/release deleted file mode 100755 index 4dc94eb7f1578b..00000000000000 --- a/script/release +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh -# Pushes a new version to PyPi. - -cd "$(dirname "$0")/.." - -head -n 5 homeassistant/const.py | tail -n 1 | grep PATCH_VERSION > /dev/null - -if [ $? -eq 1 ] -then - echo "Patch version not found on const.py line 5" - exit 1 -fi - -head -n 5 homeassistant/const.py | tail -n 1 | grep dev > /dev/null - -if [ $? -eq 0 ] -then - echo "Release version should not contain dev tag" - exit 1 -fi - -CURRENT_BRANCH=`git rev-parse --abbrev-ref HEAD` - -if [ "$CURRENT_BRANCH" != "master" ] && [ "$CURRENT_BRANCH" != "rc" ] -then - echo "You have to be on the master or rc branch to release." - exit 1 -fi - -rm -rf dist build -python3 setup.py sdist bdist_wheel -python3 -m twine upload dist/* --skip-existing From 5e62ff95b9d616c239bc7db37bfade6f63d70fc0 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sat, 29 Jan 2022 00:13:03 +0000 Subject: [PATCH 0063/1098] [ci skip] Translation update --- .../components/ambee/translations/el.json | 14 ++++++++ .../ambee/translations/sensor.el.json | 10 ++++++ .../components/bsblan/translations/id.json | 3 +- .../climacell/translations/sensor.id.json | 26 ++++++++++++++ .../components/climate/translations/el.json | 3 ++ .../components/coinbase/translations/id.json | 2 ++ .../components/cpuspeed/translations/id.json | 3 +- .../components/demo/translations/el.json | 3 +- .../device_tracker/translations/el.json | 3 ++ .../diagnostics/translations/id.json | 3 ++ .../dialogflow/translations/hu.json | 1 + .../dialogflow/translations/id.json | 1 + .../components/dnsip/translations/id.json | 27 ++++++++++++++ .../components/elgato/translations/el.json | 3 +- .../components/fan/translations/id.json | 2 ++ .../components/geofency/translations/hu.json | 1 + .../components/geofency/translations/id.json | 1 + .../geonetnz_volcano/translations/el.json | 3 ++ .../components/github/translations/id.json | 3 +- .../components/gpslogger/translations/hu.json | 1 + .../components/gpslogger/translations/id.json | 1 + .../hisense_aehw4a1/translations/el.json | 9 +++++ .../components/homekit/translations/id.json | 4 +-- .../translations/select.ca.json | 9 +++++ .../translations/select.de.json | 9 +++++ .../translations/select.el.json | 9 +++++ .../translations/select.et.json | 9 +++++ .../translations/select.hu.json | 9 +++++ .../translations/select.id.json | 9 +++++ .../translations/select.ru.json | 9 +++++ .../translations/select.tr.json | 9 +++++ .../translations/select.zh-Hant.json | 9 +++++ .../homewizard/translations/hu.json | 2 +- .../homewizard/translations/id.json | 11 +++++- .../homewizard/translations/ru.json | 1 + .../homewizard/translations/tr.json | 2 +- .../components/hue/translations/id.json | 3 +- .../humidifier/translations/id.json | 2 ++ .../components/icloud/translations/el.json | 22 +++++++++++- .../components/ifttt/translations/hu.json | 1 + .../components/ifttt/translations/id.json | 1 + .../components/knx/translations/hu.json | 6 ++-- .../components/knx/translations/id.json | 12 ++++--- .../components/light/translations/id.json | 2 ++ .../components/locative/translations/hu.json | 1 + .../components/locative/translations/id.json | 1 + .../components/luftdaten/translations/id.json | 2 +- .../components/mailgun/translations/hu.json | 1 + .../components/mailgun/translations/id.json | 1 + .../media_player/translations/el.json | 7 ++++ .../media_player/translations/id.json | 1 + .../meteoclimatic/translations/el.json | 4 ++- .../modern_forms/translations/el.json | 3 ++ .../components/overkiz/translations/hu.json | 4 ++- .../components/overkiz/translations/id.json | 4 ++- .../overkiz/translations/select.id.json | 7 ++++ .../overkiz/translations/sensor.id.json | 8 +++++ .../components/owntracks/translations/hu.json | 1 + .../components/owntracks/translations/id.json | 1 + .../components/plaato/translations/hu.json | 1 + .../components/plaato/translations/id.json | 1 + .../components/remote/translations/id.json | 2 ++ .../components/senseme/translations/hu.json | 3 +- .../components/senseme/translations/id.json | 3 +- .../components/sia/translations/el.json | 7 ++-- .../components/solax/translations/hu.json | 17 +++++++++ .../components/solax/translations/id.json | 17 +++++++++ .../components/steamist/translations/id.json | 6 ++++ .../components/switch/translations/id.json | 2 ++ .../synology_dsm/translations/el.json | 2 +- .../components/traccar/translations/hu.json | 1 + .../components/traccar/translations/id.json | 1 + .../tuya/translations/select.bg.json | 8 +++++ .../tuya/translations/select.ca.json | 8 +++++ .../tuya/translations/select.de.json | 8 +++++ .../tuya/translations/select.el.json | 8 +++++ .../tuya/translations/select.et.json | 8 +++++ .../tuya/translations/select.hu.json | 13 +++++++ .../tuya/translations/select.ru.json | 8 +++++ .../tuya/translations/select.tr.json | 8 +++++ .../tuya/translations/select.zh-Hant.json | 8 +++++ .../components/twilio/translations/bg.json | 1 + .../components/twilio/translations/hu.json | 1 + .../components/twilio/translations/id.json | 1 + .../unifiprotect/translations/id.json | 1 + .../uptimerobot/translations/sensor.bg.json | 7 ++++ .../uptimerobot/translations/sensor.el.json | 10 ++++++ .../uptimerobot/translations/sensor.hu.json | 11 ++++++ .../components/vallox/translations/id.json | 2 +- .../components/webostv/translations/id.json | 35 +++++++++++++++++-- .../components/whois/translations/id.json | 13 +++++++ .../components/withings/translations/el.json | 3 +- .../components/wled/translations/el.json | 9 +++++ .../components/wled/translations/id.json | 3 +- .../xiaomi_miio/translations/el.json | 3 ++ .../yale_smart_alarm/translations/id.json | 13 +++++++ .../yamaha_musiccast/translations/el.json | 16 +++++++++ 97 files changed, 555 insertions(+), 33 deletions(-) create mode 100644 homeassistant/components/ambee/translations/el.json create mode 100644 homeassistant/components/ambee/translations/sensor.el.json create mode 100644 homeassistant/components/climacell/translations/sensor.id.json create mode 100644 homeassistant/components/diagnostics/translations/id.json create mode 100644 homeassistant/components/dnsip/translations/id.json create mode 100644 homeassistant/components/hisense_aehw4a1/translations/el.json create mode 100644 homeassistant/components/homekit_controller/translations/select.ca.json create mode 100644 homeassistant/components/homekit_controller/translations/select.de.json create mode 100644 homeassistant/components/homekit_controller/translations/select.el.json create mode 100644 homeassistant/components/homekit_controller/translations/select.et.json create mode 100644 homeassistant/components/homekit_controller/translations/select.hu.json create mode 100644 homeassistant/components/homekit_controller/translations/select.id.json create mode 100644 homeassistant/components/homekit_controller/translations/select.ru.json create mode 100644 homeassistant/components/homekit_controller/translations/select.tr.json create mode 100644 homeassistant/components/homekit_controller/translations/select.zh-Hant.json create mode 100644 homeassistant/components/overkiz/translations/select.id.json create mode 100644 homeassistant/components/overkiz/translations/sensor.id.json create mode 100644 homeassistant/components/solax/translations/hu.json create mode 100644 homeassistant/components/solax/translations/id.json create mode 100644 homeassistant/components/uptimerobot/translations/sensor.bg.json create mode 100644 homeassistant/components/uptimerobot/translations/sensor.el.json create mode 100644 homeassistant/components/uptimerobot/translations/sensor.hu.json create mode 100644 homeassistant/components/yamaha_musiccast/translations/el.json diff --git a/homeassistant/components/ambee/translations/el.json b/homeassistant/components/ambee/translations/el.json new file mode 100644 index 00000000000000..a6db38ee103011 --- /dev/null +++ b/homeassistant/components/ambee/translations/el.json @@ -0,0 +1,14 @@ +{ + "config": { + "step": { + "reauth_confirm": { + "data": { + "description": "\u0395\u03c0\u03b1\u03bd\u03b1\u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 Ambee." + } + }, + "user": { + "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf Ambee \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Home Assistant." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambee/translations/sensor.el.json b/homeassistant/components/ambee/translations/sensor.el.json new file mode 100644 index 00000000000000..8e9af2dac056fc --- /dev/null +++ b/homeassistant/components/ambee/translations/sensor.el.json @@ -0,0 +1,10 @@ +{ + "state": { + "ambee__risk": { + "high": "\u03a5\u03c8\u03b7\u03bb\u03cc", + "low": "\u03a7\u03b1\u03bc\u03b7\u03bb\u03cc", + "moderate": "\u039c\u03ad\u03c4\u03c1\u03b9\u03bf", + "very high": "\u03a0\u03bf\u03bb\u03cd \u03c5\u03c8\u03b7\u03bb\u03cc" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bsblan/translations/id.json b/homeassistant/components/bsblan/translations/id.json index 83fdb88aae4ff6..8d30e37749f6fc 100644 --- a/homeassistant/components/bsblan/translations/id.json +++ b/homeassistant/components/bsblan/translations/id.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Perangkat sudah dikonfigurasi" + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung" }, "error": { "cannot_connect": "Gagal terhubung" diff --git a/homeassistant/components/climacell/translations/sensor.id.json b/homeassistant/components/climacell/translations/sensor.id.json new file mode 100644 index 00000000000000..1ee479c51dafb0 --- /dev/null +++ b/homeassistant/components/climacell/translations/sensor.id.json @@ -0,0 +1,26 @@ +{ + "state": { + "climacell__health_concern": { + "good": "Bagus", + "hazardous": "Berbahaya", + "moderate": "Sedang", + "unhealthy": "Tidak Sehat", + "unhealthy_for_sensitive_groups": "Tidak Sehat untuk Kelompok Sensitif", + "very_unhealthy": "Sangat Tidak Sehat" + }, + "climacell__pollen_index": { + "high": "Tinggi", + "low": "Rendah", + "medium": "Sedang", + "none": "Tidak Ada", + "very_high": "Sangat Tinggi", + "very_low": "Sangat Rendah" + }, + "climacell__precipitation_type": { + "freezing_rain": "Hujan Beku", + "none": "Tidak Ada", + "rain": "Hujan", + "snow": "Salju" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/climate/translations/el.json b/homeassistant/components/climate/translations/el.json index 90e25c258f12e0..5f5ef326485efe 100644 --- a/homeassistant/components/climate/translations/el.json +++ b/homeassistant/components/climate/translations/el.json @@ -4,6 +4,9 @@ "set_hvac_mode": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ae \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 HVAC \u03c3\u03c4\u03bf {entity_name}", "set_preset_mode": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ae \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae\u03c2 \u03c3\u03c4\u03bf {entity_name}" }, + "condition_type": { + "is_preset_mode": "{entity_name} \u03ad\u03c7\u03b5\u03b9 \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03c3\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03b3\u03ba\u03b5\u03ba\u03c1\u03b9\u03bc\u03ad\u03bd\u03b7 \u03c0\u03c1\u03bf\u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1" + }, "trigger_type": { "current_humidity_changed": "\u0397 \u03bc\u03b5\u03c4\u03c1\u03b7\u03bc\u03ad\u03bd\u03b7 \u03c5\u03b3\u03c1\u03b1\u03c3\u03af\u03b1 \u03c4\u03bf\u03c5 {entity_name} \u03ac\u03bb\u03bb\u03b1\u03be\u03b5", "current_temperature_changed": "\u0397 \u03bc\u03b5\u03c4\u03c1\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1 \u03c4\u03bf\u03c5 {entity_name} \u03ac\u03bb\u03bb\u03b1\u03be\u03b5", diff --git a/homeassistant/components/coinbase/translations/id.json b/homeassistant/components/coinbase/translations/id.json index 598c1a1c7281ab..1d431ffd2fdd0a 100644 --- a/homeassistant/components/coinbase/translations/id.json +++ b/homeassistant/components/coinbase/translations/id.json @@ -25,7 +25,9 @@ }, "options": { "error": { + "currency_unavailable": "Satu atau beberapa saldo mata uang yang diminta tidak disediakan oleh API Coinbase Anda.", "currency_unavaliable": "Satu atau beberapa saldo mata uang yang diminta tidak disediakan oleh API Coinbase Anda.", + "exchange_rate_unavailable": "Satu atau beberapa nilai tukar yang diminta tidak disediakan oleh Coinbase.", "exchange_rate_unavaliable": "Satu atau beberapa nilai tukar yang diminta tidak disediakan oleh Coinbase.", "unknown": "Kesalahan yang tidak diharapkan" }, diff --git a/homeassistant/components/cpuspeed/translations/id.json b/homeassistant/components/cpuspeed/translations/id.json index 8046606a6557c0..ab3b16da4e903d 100644 --- a/homeassistant/components/cpuspeed/translations/id.json +++ b/homeassistant/components/cpuspeed/translations/id.json @@ -2,7 +2,8 @@ "config": { "abort": { "alread_configured": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", - "already_configured": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + "already_configured": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "not_compatible": "Tidak dapat mendapatkan informasi CPU, integrasi ini tidak kompatibel dengan sistem Anda" }, "step": { "user": { diff --git a/homeassistant/components/demo/translations/el.json b/homeassistant/components/demo/translations/el.json index d617e4a6abedf2..b1bef5448b9481 100644 --- a/homeassistant/components/demo/translations/el.json +++ b/homeassistant/components/demo/translations/el.json @@ -15,5 +15,6 @@ } } } - } + }, + "title": "Demo" } \ No newline at end of file diff --git a/homeassistant/components/device_tracker/translations/el.json b/homeassistant/components/device_tracker/translations/el.json index e7acdc0562e8b6..598c747d87b28e 100644 --- a/homeassistant/components/device_tracker/translations/el.json +++ b/homeassistant/components/device_tracker/translations/el.json @@ -1,5 +1,8 @@ { "device_automation": { + "condition_type": { + "is_home": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c4\u03bf \u03c3\u03c0\u03af\u03c4\u03b9" + }, "trigger_type": { "enters": "{entity_name} \u03b5\u03b9\u03c3\u03ad\u03c1\u03c7\u03b5\u03c4\u03b1\u03b9 \u03c3\u03b5 \u03bc\u03b9\u03b1 \u03b6\u03ce\u03bd\u03b7", "leaves": "{entity_name} \u03b5\u03b3\u03ba\u03b1\u03c4\u03b1\u03bb\u03b5\u03af\u03c0\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03b6\u03ce\u03bd\u03b7" diff --git a/homeassistant/components/diagnostics/translations/id.json b/homeassistant/components/diagnostics/translations/id.json new file mode 100644 index 00000000000000..732e52ee843f42 --- /dev/null +++ b/homeassistant/components/diagnostics/translations/id.json @@ -0,0 +1,3 @@ +{ + "title": "Diagnostik" +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/translations/hu.json b/homeassistant/components/dialogflow/translations/hu.json index 23a6001d77c1c8..69fdaea4d00511 100644 --- a/homeassistant/components/dialogflow/translations/hu.json +++ b/homeassistant/components/dialogflow/translations/hu.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Nincs csatlakoztatva a Home Assistant Cloudhoz.", "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", "webhook_not_internet_accessible": "A Home Assistant p\u00e9ld\u00e1ny\u00e1nak el\u00e9rhet\u0151nek kell lennie az internet fel\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." }, diff --git a/homeassistant/components/dialogflow/translations/id.json b/homeassistant/components/dialogflow/translations/id.json index 046a04b1dc49bf..e6dd34cb64d601 100644 --- a/homeassistant/components/dialogflow/translations/id.json +++ b/homeassistant/components/dialogflow/translations/id.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Tidak terhubung ke Home Assistant Cloud.", "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", "webhook_not_internet_accessible": "Instans Home Assistant Anda harus dapat diakses dari internet untuk menerima pesan webhook." }, diff --git a/homeassistant/components/dnsip/translations/id.json b/homeassistant/components/dnsip/translations/id.json new file mode 100644 index 00000000000000..8e3dc496a295d1 --- /dev/null +++ b/homeassistant/components/dnsip/translations/id.json @@ -0,0 +1,27 @@ +{ + "config": { + "error": { + "invalid_hostname": "Nama host tidak valid" + }, + "step": { + "user": { + "data": { + "hostname": "Nama host untuk melakukan kueri DNS" + } + } + } + }, + "options": { + "error": { + "invalid_resolver": "Alamat IP tidak valid untuk resolver" + }, + "step": { + "init": { + "data": { + "resolver": "Resolver untuk pencarian IPV4", + "resolver_ipv6": "Resolver untuk pencarian IPV6" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elgato/translations/el.json b/homeassistant/components/elgato/translations/el.json index 1f15d6d6eb716b..ae27270b52fd7a 100644 --- a/homeassistant/components/elgato/translations/el.json +++ b/homeassistant/components/elgato/translations/el.json @@ -12,7 +12,8 @@ "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf Elgato Light \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Home Assistant." }, "zeroconf_confirm": { - "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Elgato Light \u03bc\u03b5 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc `{serial_number}` \u03c3\u03c4\u03bf Home Assistant;" + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Elgato Light \u03bc\u03b5 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc `{serial_number}` \u03c3\u03c4\u03bf Home Assistant;", + "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Elgato Light" } } } diff --git a/homeassistant/components/fan/translations/id.json b/homeassistant/components/fan/translations/id.json index 054ec10754c1c7..c21b90a495a31a 100644 --- a/homeassistant/components/fan/translations/id.json +++ b/homeassistant/components/fan/translations/id.json @@ -9,6 +9,8 @@ "is_on": "{entity_name} nyala" }, "trigger_type": { + "changed_states": "{entity_name} diaktifkan atau dinonaktifkan", + "toggled": "{entity_name} diaktifkan atau dinonaktifkan", "turned_off": "{entity_name} dimatikan", "turned_on": "{entity_name} dinyalakan" } diff --git a/homeassistant/components/geofency/translations/hu.json b/homeassistant/components/geofency/translations/hu.json index 1b3f17fe700cb0..9da5f3622b6ee9 100644 --- a/homeassistant/components/geofency/translations/hu.json +++ b/homeassistant/components/geofency/translations/hu.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Nincs csatlakoztatva a Home Assistant Cloudhoz.", "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", "webhook_not_internet_accessible": "A Home Assistant p\u00e9ld\u00e1ny\u00e1nak el\u00e9rhet\u0151nek kell lennie az internet fel\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." }, diff --git a/homeassistant/components/geofency/translations/id.json b/homeassistant/components/geofency/translations/id.json index 0e5163b96cd5c2..9793131eb4c434 100644 --- a/homeassistant/components/geofency/translations/id.json +++ b/homeassistant/components/geofency/translations/id.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Tidak terhubung ke Home Assistant Cloud.", "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", "webhook_not_internet_accessible": "Instans Home Assistant Anda harus dapat diakses dari internet untuk menerima pesan webhook." }, diff --git a/homeassistant/components/geonetnz_volcano/translations/el.json b/homeassistant/components/geonetnz_volcano/translations/el.json index 82e2fb54adce59..215801cc7c5887 100644 --- a/homeassistant/components/geonetnz_volcano/translations/el.json +++ b/homeassistant/components/geonetnz_volcano/translations/el.json @@ -2,6 +2,9 @@ "config": { "step": { "user": { + "data": { + "radius": "\u0391\u03ba\u03c4\u03af\u03bd\u03b1" + }, "title": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c4\u03bf\u03c5 \u03c6\u03af\u03bb\u03c4\u03c1\u03bf\u03c5 \u03c3\u03b1\u03c2." } } diff --git a/homeassistant/components/github/translations/id.json b/homeassistant/components/github/translations/id.json index cd133547d61d6f..33d580d03f8fcc 100644 --- a/homeassistant/components/github/translations/id.json +++ b/homeassistant/components/github/translations/id.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Layanan sudah dikonfigurasi" + "already_configured": "Layanan sudah dikonfigurasi", + "could_not_register": "Tidak dapat mendaftarkan integrasi dengan GitHub" }, "step": { "repositories": { diff --git a/homeassistant/components/gpslogger/translations/hu.json b/homeassistant/components/gpslogger/translations/hu.json index d458e959d0a2bf..96677b262eb7a9 100644 --- a/homeassistant/components/gpslogger/translations/hu.json +++ b/homeassistant/components/gpslogger/translations/hu.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Nincs csatlakoztatva a Home Assistant Cloudhoz.", "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", "webhook_not_internet_accessible": "A Home Assistant p\u00e9ld\u00e1ny\u00e1nak el\u00e9rhet\u0151nek kell lennie az internet fel\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." }, diff --git a/homeassistant/components/gpslogger/translations/id.json b/homeassistant/components/gpslogger/translations/id.json index 3be2d91f1f3641..b4e012bc5f6bfd 100644 --- a/homeassistant/components/gpslogger/translations/id.json +++ b/homeassistant/components/gpslogger/translations/id.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Tidak terhubung ke Home Assistant Cloud.", "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", "webhook_not_internet_accessible": "Instans Home Assistant Anda harus dapat diakses dari internet untuk menerima pesan webhook." }, diff --git a/homeassistant/components/hisense_aehw4a1/translations/el.json b/homeassistant/components/hisense_aehw4a1/translations/el.json new file mode 100644 index 00000000000000..421a29064f71a9 --- /dev/null +++ b/homeassistant/components/hisense_aehw4a1/translations/el.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Hisense AEH-W4A1;" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/id.json b/homeassistant/components/homekit/translations/id.json index 0f9da18f7a8f8f..851cac800e60e1 100644 --- a/homeassistant/components/homekit/translations/id.json +++ b/homeassistant/components/homekit/translations/id.json @@ -68,10 +68,10 @@ "domains": "Domain yang disertakan", "include_domains": "Domain yang disertakan", "include_exclude_mode": "Mode Penyertaan", - "mode": "Mode" + "mode": "Mode HomeKit" }, "description": "HomeKit dapat dikonfigurasi untuk memaparkakan sebuah bridge atau sebuah aksesori. Dalam mode aksesori, hanya satu entitas yang dapat digunakan. Mode aksesori diperlukan agar pemutar media dengan kelas perangkat TV berfungsi dengan baik. Entitas di \"Domain yang akan disertakan\" akan disertakan ke HomeKit. Anda akan dapat memilih entitas mana yang akan disertakan atau dikecualikan dari daftar ini pada layar berikutnya.", - "title": "Pilih domain yang akan disertakan." + "title": "Pilih mode dan domain." }, "yaml": { "description": "Entri ini dikontrol melalui YAML", diff --git a/homeassistant/components/homekit_controller/translations/select.ca.json b/homeassistant/components/homekit_controller/translations/select.ca.json new file mode 100644 index 00000000000000..1c859fa18e329b --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/select.ca.json @@ -0,0 +1,9 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "away": "A fora", + "home": "A casa", + "sleep": "Dormint" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/select.de.json b/homeassistant/components/homekit_controller/translations/select.de.json new file mode 100644 index 00000000000000..9b0aff97897157 --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/select.de.json @@ -0,0 +1,9 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "away": "Abwesend", + "home": "Zu Hause", + "sleep": "Schlafen" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/select.el.json b/homeassistant/components/homekit_controller/translations/select.el.json new file mode 100644 index 00000000000000..3087454c361cad --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/select.el.json @@ -0,0 +1,9 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "away": "\u0395\u03ba\u03c4\u03cc\u03c2 \u03a3\u03c0\u03b9\u03c4\u03b9\u03bf\u03cd", + "home": "\u03a3\u03c0\u03af\u03c4\u03b9", + "sleep": "\u038e\u03c0\u03bd\u03bf\u03c2" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/select.et.json b/homeassistant/components/homekit_controller/translations/select.et.json new file mode 100644 index 00000000000000..7a81bab7b6ae52 --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/select.et.json @@ -0,0 +1,9 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "away": "Eemal", + "home": "Kodus", + "sleep": "Unere\u017eiim" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/select.hu.json b/homeassistant/components/homekit_controller/translations/select.hu.json new file mode 100644 index 00000000000000..a3c80cfbccf2b3 --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/select.hu.json @@ -0,0 +1,9 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "away": "T\u00e1vol", + "home": "Otthon", + "sleep": "Alv\u00e1s" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/select.id.json b/homeassistant/components/homekit_controller/translations/select.id.json new file mode 100644 index 00000000000000..e279ef020b8262 --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/select.id.json @@ -0,0 +1,9 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "away": "Keluar", + "home": "Di Rumah", + "sleep": "Tidur" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/select.ru.json b/homeassistant/components/homekit_controller/translations/select.ru.json new file mode 100644 index 00000000000000..dd6b3fcb286f73 --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/select.ru.json @@ -0,0 +1,9 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "away": "\u041d\u0435 \u0434\u043e\u043c\u0430", + "home": "\u0414\u043e\u043c\u0430", + "sleep": "\u0421\u043e\u043d" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/select.tr.json b/homeassistant/components/homekit_controller/translations/select.tr.json new file mode 100644 index 00000000000000..39fe8e65536391 --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/select.tr.json @@ -0,0 +1,9 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "away": "Uzakta", + "home": "Ev", + "sleep": "Uyku" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/select.zh-Hant.json b/homeassistant/components/homekit_controller/translations/select.zh-Hant.json new file mode 100644 index 00000000000000..f0e1588b48607e --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/select.zh-Hant.json @@ -0,0 +1,9 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "away": "\u5916\u51fa", + "home": "\u5728\u5bb6", + "sleep": "\u7761\u7720" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homewizard/translations/hu.json b/homeassistant/components/homewizard/translations/hu.json index 89386e76717808..060e7e9024895e 100644 --- a/homeassistant/components/homewizard/translations/hu.json +++ b/homeassistant/components/homewizard/translations/hu.json @@ -4,7 +4,7 @@ "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", "api_not_enabled": "Az API nincs enged\u00e9lyezve. Enged\u00e9lyezze az API-t a HomeWizard Energy alkalmaz\u00e1sban a be\u00e1ll\u00edt\u00e1sok k\u00f6z\u00f6tt.", "device_not_supported": "Ez az eszk\u00f6z nem t\u00e1mogatott", - "invalid_discovery_parameters": "Nem t\u00e1mogatott API verzi\u00f3", + "invalid_discovery_parameters": "Nem t\u00e1mogatott API-verzi\u00f3 \u00e9szlel\u00e9se", "unknown_error": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { diff --git a/homeassistant/components/homewizard/translations/id.json b/homeassistant/components/homewizard/translations/id.json index 35ddc764aa0d60..6363e9de21d400 100644 --- a/homeassistant/components/homewizard/translations/id.json +++ b/homeassistant/components/homewizard/translations/id.json @@ -2,13 +2,22 @@ "config": { "abort": { "already_configured": "Perangkat sudah dikonfigurasi", + "api_not_enabled": "API tidak diaktifkan. Aktifkan API di Aplikasi Energi HomeWizard di bawah pengaturan", + "device_not_supported": "Perangkat ini tidak didukung", + "invalid_discovery_parameters": "Terdeteksi versi API yang tidak didukung", "unknown_error": "Kesalahan yang tidak diharapkan" }, "step": { + "discovery_confirm": { + "description": "Ingin menyiapkan {product_type} ({serial}) di {ip_address}?", + "title": "Konfirmasikan" + }, "user": { "data": { "ip_address": "Alamat IP" - } + }, + "description": "Masukkan alamat IP perangkat HomeWizard Energy Anda untuk diintegrasikan dengan Home Assistant.", + "title": "Konfigurasikan perangkat" } } } diff --git a/homeassistant/components/homewizard/translations/ru.json b/homeassistant/components/homewizard/translations/ru.json index 47d4434ed6e228..61f4cfd8feaac1 100644 --- a/homeassistant/components/homewizard/translations/ru.json +++ b/homeassistant/components/homewizard/translations/ru.json @@ -4,6 +4,7 @@ "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.", "api_not_enabled": "\u0410\u043a\u0442\u0438\u0432\u0438\u0440\u0443\u0439\u0442\u0435 API \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f HomeWizard Energy.", "device_not_supported": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f.", + "invalid_discovery_parameters": "\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0430 \u043d\u0435\u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f API.", "unknown_error": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "step": { diff --git a/homeassistant/components/homewizard/translations/tr.json b/homeassistant/components/homewizard/translations/tr.json index e574b74ef9195f..3ed1eaf64883fd 100644 --- a/homeassistant/components/homewizard/translations/tr.json +++ b/homeassistant/components/homewizard/translations/tr.json @@ -4,7 +4,7 @@ "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", "api_not_enabled": "API etkin de\u011fil. Ayarlar alt\u0131nda HomeWizard Energy Uygulamas\u0131nda API'yi etkinle\u015ftirin", "device_not_supported": "Bu cihaz desteklenmiyor", - "invalid_discovery_parameters": "unsupported_api_version", + "invalid_discovery_parameters": "Desteklenmeyen API s\u00fcr\u00fcm\u00fc alg\u0131land\u0131", "unknown_error": "Beklenmeyen hata" }, "step": { diff --git a/homeassistant/components/hue/translations/id.json b/homeassistant/components/hue/translations/id.json index 1084d980ea5d13..d7178f6d135e40 100644 --- a/homeassistant/components/hue/translations/id.json +++ b/homeassistant/components/hue/translations/id.json @@ -69,7 +69,8 @@ "data": { "allow_hue_groups": "Izinkan grup Hue", "allow_hue_scenes": "Izinkan skenario Hue", - "allow_unreachable": "Izinkan bohlam yang tidak dapat dijangkau untuk melaporkan statusnya dengan benar" + "allow_unreachable": "Izinkan bohlam yang tidak dapat dijangkau untuk melaporkan statusnya dengan benar", + "ignore_availability": "Abaikan status konektivitas untuk perangkat yang diberikan" } } } diff --git a/homeassistant/components/humidifier/translations/id.json b/homeassistant/components/humidifier/translations/id.json index b06b2bfee45aaa..1996fd23786b5d 100644 --- a/homeassistant/components/humidifier/translations/id.json +++ b/homeassistant/components/humidifier/translations/id.json @@ -13,7 +13,9 @@ "is_on": "{entity_name} nyala" }, "trigger_type": { + "changed_states": "{entity_name} diaktifkan atau dinonaktifkan", "target_humidity_changed": "Kelembapan target {entity_name} berubah", + "toggled": "{entity_name} diaktifkan atau dinonaktifkan", "turned_off": "{entity_name} dimatikan", "turned_on": "{entity_name} dinyalakan" } diff --git a/homeassistant/components/icloud/translations/el.json b/homeassistant/components/icloud/translations/el.json index 2783621ab09085..a7fb32ce7a0285 100644 --- a/homeassistant/components/icloud/translations/el.json +++ b/homeassistant/components/icloud/translations/el.json @@ -3,14 +3,34 @@ "abort": { "no_device": "\u039a\u03b1\u03bc\u03af\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b9\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03b1\u03c2 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 \u03c4\u03b7\u03bd \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \"Find my iPhone\"." }, + "error": { + "send_verification_code": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03b1\u03c0\u03bf\u03c3\u03c4\u03bf\u03bb\u03ae\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03b5\u03c0\u03b1\u03bb\u03ae\u03b8\u03b5\u03c5\u03c3\u03b7\u03c2", + "validate_verification_code": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b5\u03c0\u03b1\u03bb\u03ae\u03b8\u03b5\u03c5\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03b5\u03c0\u03b1\u03bb\u03ae\u03b8\u03b5\u03c5\u03c3\u03b7\u03c2, \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac" + }, "step": { "reauth": { "description": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c0\u03bf\u03c5 \u03b5\u03af\u03c7\u03b1\u03c4\u03b5 \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03b9 \u03c0\u03c1\u03bf\u03b7\u03b3\u03bf\u03c5\u03bc\u03ad\u03bd\u03c9\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username} \u03b4\u03b5\u03bd \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03b5\u03af \u03c0\u03bb\u03ad\u03bf\u03bd. \u0395\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7." }, + "trusted_device": { + "data": { + "trusted_device": "\u0391\u03be\u03b9\u03cc\u03c0\u03b9\u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b1\u03be\u03b9\u03cc\u03c0\u03b9\u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c3\u03b1\u03c2", + "title": "\u0391\u03be\u03b9\u03cc\u03c0\u03b9\u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae iCloud" + }, "user": { "data": { "with_family": "\u039c\u03b5 \u03c4\u03b7\u03bd \u03bf\u03b9\u03ba\u03bf\u03b3\u03ad\u03bd\u03b5\u03b9\u03b1" - } + }, + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2", + "title": "\u0394\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 iCloud" + }, + "verification_code": { + "data": { + "verification_code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03c0\u03b1\u03bb\u03ae\u03b8\u03b5\u03c5\u03c3\u03b7\u03c2" + }, + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03b5\u03c0\u03b1\u03bb\u03ae\u03b8\u03b5\u03c5\u03c3\u03b7\u03c2 \u03c0\u03bf\u03c5 \u03bc\u03cc\u03bb\u03b9\u03c2 \u03bb\u03ac\u03b2\u03b1\u03c4\u03b5 \u03b1\u03c0\u03cc \u03c4\u03bf iCloud", + "title": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03c0\u03b1\u03bb\u03ae\u03b8\u03b5\u03c5\u03c3\u03b7\u03c2 iCloud" } } } diff --git a/homeassistant/components/ifttt/translations/hu.json b/homeassistant/components/ifttt/translations/hu.json index 2f64056e985f06..21bfa86dcec587 100644 --- a/homeassistant/components/ifttt/translations/hu.json +++ b/homeassistant/components/ifttt/translations/hu.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Nincs csatlakoztatva a Home Assistant Cloudhoz.", "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", "webhook_not_internet_accessible": "A Home Assistant p\u00e9ld\u00e1ny\u00e1nak el\u00e9rhet\u0151nek kell lennie az internet fel\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." }, diff --git a/homeassistant/components/ifttt/translations/id.json b/homeassistant/components/ifttt/translations/id.json index f997f39a54eadc..8ddb7c798c11ba 100644 --- a/homeassistant/components/ifttt/translations/id.json +++ b/homeassistant/components/ifttt/translations/id.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Tidak terhubung ke Home Assistant Cloud.", "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", "webhook_not_internet_accessible": "Instans Home Assistant Anda harus dapat diakses dari internet untuk menerima pesan webhook." }, diff --git a/homeassistant/components/knx/translations/hu.json b/homeassistant/components/knx/translations/hu.json index acc102240a3971..13bd4650378265 100644 --- a/homeassistant/components/knx/translations/hu.json +++ b/homeassistant/components/knx/translations/hu.json @@ -14,7 +14,8 @@ "individual_address": "A kapcsolat egy\u00e9ni c\u00edme", "local_ip": "Helyi IP c\u00edm (hagyja \u00fcresen, ha nem biztos benne)", "port": "Port", - "route_back": "\u00datvonal (route) vissza / NAT m\u00f3d" + "route_back": "\u00datvonal (route) vissza / NAT m\u00f3d", + "tunneling_type": "KNX alag\u00fat t\u00edpusa" }, "description": "Adja meg az alag\u00fatkezel\u0151 (tunneling) eszk\u00f6z csatlakoz\u00e1si adatait." }, @@ -59,7 +60,8 @@ "host": "C\u00edm", "local_ip": "Helyi IP c\u00edm (hagyja \u00fcresen, ha nem biztos benne)", "port": "Port", - "route_back": "\u00datvonal (route) vissza / NAT m\u00f3d" + "route_back": "\u00datvonal (route) vissza / NAT m\u00f3d", + "tunneling_type": "KNX alag\u00fat t\u00edpusa" } } } diff --git a/homeassistant/components/knx/translations/id.json b/homeassistant/components/knx/translations/id.json index e93689f3bec986..1cdb614a3cc286 100644 --- a/homeassistant/components/knx/translations/id.json +++ b/homeassistant/components/knx/translations/id.json @@ -12,16 +12,17 @@ "data": { "host": "Host", "individual_address": "Alamat individu untuk koneksi", - "local_ip": "IP lokal (kosongkan jika tidak yakin)", + "local_ip": "IP lokal Home Assistant (kosongkan jika tidak yakin)", "port": "Port", - "route_back": "Dirutekan Kembali/Mode NAT" + "route_back": "Dirutekan Kembali/Mode NAT", + "tunneling_type": "Jenis Tunnel KNX" }, "description": "Masukkan informasi koneksi untuk perangkat tunneling Anda." }, "routing": { "data": { "individual_address": "Alamat individu untuk koneksi routing", - "local_ip": "IP lokal (kosongkan jika tidak yakin)", + "local_ip": "IP lokal Home Assistant (kosongkan jika tidak yakin)", "multicast_group": "Grup multicast yang digunakan untuk routing", "multicast_port": "Port multicast yang digunakan untuk routing" }, @@ -47,7 +48,7 @@ "data": { "connection_type": "Jenis Koneksi KNX", "individual_address": "Alamat individu default", - "local_ip": "IP lokal (kosongkan jika tidak yakin)", + "local_ip": "IP lokal Home Assistant (gunakan 0.0.0.0 untuk deteksi otomatis)", "multicast_group": "Grup multicast yang digunakan untuk routing dan penemuan", "multicast_port": "Port multicast yang digunakan untuk routing dan penemuan", "rate_limit": "Jumlah maksimal telegram keluar per detik", @@ -59,7 +60,8 @@ "host": "Host", "local_ip": "IP lokal (kosongkan jika tidak yakin)", "port": "Port", - "route_back": "Dirutekan Kembali/Mode NAT" + "route_back": "Dirutekan Kembali/Mode NAT", + "tunneling_type": "Jenis Tunnel KNX" } } } diff --git a/homeassistant/components/light/translations/id.json b/homeassistant/components/light/translations/id.json index 25c636ac1c7e56..334e938d41e9e8 100644 --- a/homeassistant/components/light/translations/id.json +++ b/homeassistant/components/light/translations/id.json @@ -13,6 +13,8 @@ "is_on": "{entity_name} nyala" }, "trigger_type": { + "changed_states": "{entity_name} diaktifkan atau dinonaktifkan", + "toggled": "{entity_name} diaktifkan atau dinonaktifkan", "turned_off": "{entity_name} dimatikan", "turned_on": "{entity_name} dinyalakan" } diff --git a/homeassistant/components/locative/translations/hu.json b/homeassistant/components/locative/translations/hu.json index 893e22f1471266..b74774c3c21eff 100644 --- a/homeassistant/components/locative/translations/hu.json +++ b/homeassistant/components/locative/translations/hu.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Nincs csatlakoztatva a Home Assistant Cloudhoz.", "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", "webhook_not_internet_accessible": "A Home Assistant p\u00e9ld\u00e1ny\u00e1nak el\u00e9rhet\u0151nek kell lennie az internet fel\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." }, diff --git a/homeassistant/components/locative/translations/id.json b/homeassistant/components/locative/translations/id.json index 71aea5cc63cff7..89e61d009e9d51 100644 --- a/homeassistant/components/locative/translations/id.json +++ b/homeassistant/components/locative/translations/id.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Tidak terhubung ke Home Assistant Cloud.", "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", "webhook_not_internet_accessible": "Instans Home Assistant Anda harus dapat diakses dari internet untuk menerima pesan webhook." }, diff --git a/homeassistant/components/luftdaten/translations/id.json b/homeassistant/components/luftdaten/translations/id.json index 96ec6d5f20ff10..11fc29170610af 100644 --- a/homeassistant/components/luftdaten/translations/id.json +++ b/homeassistant/components/luftdaten/translations/id.json @@ -9,7 +9,7 @@ "user": { "data": { "show_on_map": "Tampilkan di peta", - "station_id": "ID Sensor Luftdaten" + "station_id": "ID Sensor" }, "title": "Konfigurasikan Luftdaten" } diff --git a/homeassistant/components/mailgun/translations/hu.json b/homeassistant/components/mailgun/translations/hu.json index b40c4316bba226..17b45578ba3de4 100644 --- a/homeassistant/components/mailgun/translations/hu.json +++ b/homeassistant/components/mailgun/translations/hu.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Nincs csatlakoztatva a Home Assistant Cloudhoz.", "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", "webhook_not_internet_accessible": "A Home Assistant p\u00e9ld\u00e1ny\u00e1nak el\u00e9rhet\u0151nek kell lennie az internet fel\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." }, diff --git a/homeassistant/components/mailgun/translations/id.json b/homeassistant/components/mailgun/translations/id.json index b58deb171bef01..e428cc37213812 100644 --- a/homeassistant/components/mailgun/translations/id.json +++ b/homeassistant/components/mailgun/translations/id.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Tidak terhubung ke Home Assistant Cloud.", "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", "webhook_not_internet_accessible": "Instans Home Assistant Anda harus dapat diakses dari internet untuk menerima pesan webhook." }, diff --git a/homeassistant/components/media_player/translations/el.json b/homeassistant/components/media_player/translations/el.json index 73b20532035a43..e3a300af77dd22 100644 --- a/homeassistant/components/media_player/translations/el.json +++ b/homeassistant/components/media_player/translations/el.json @@ -1,5 +1,12 @@ { "device_automation": { + "condition_type": { + "is_idle": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03b4\u03c1\u03b1\u03bd\u03ad\u03c2", + "is_off": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf", + "is_on": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf", + "is_paused": "{entity_name} \u03c4\u03ad\u03b8\u03b7\u03ba\u03b5 \u03c3\u03b5 \u03c0\u03b1\u03cd\u03c3\u03b7", + "is_playing": "{entity_name} \u03c0\u03b1\u03af\u03b6\u03b5\u03b9" + }, "trigger_type": { "changed_states": "\u03a4\u03bf {entity_name} \u03ac\u03bb\u03bb\u03b1\u03be\u03b5 \u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ac\u03c3\u03b5\u03b9\u03c2", "idle": "{entity_name} \u03b3\u03af\u03bd\u03b5\u03c4\u03b1\u03b9 \u03b1\u03b4\u03c1\u03b1\u03bd\u03ad\u03c2", diff --git a/homeassistant/components/media_player/translations/id.json b/homeassistant/components/media_player/translations/id.json index e759f88a15a36c..9446d1e9e89a3c 100644 --- a/homeassistant/components/media_player/translations/id.json +++ b/homeassistant/components/media_player/translations/id.json @@ -8,6 +8,7 @@ "is_playing": "{entity_name} sedang memutar" }, "trigger_type": { + "changed_states": "{entity_name} mengubah status", "idle": "{entity_name} menjadi siaga", "paused": "{entity_name} dijeda", "playing": "{entity_name} mulai memutar", diff --git a/homeassistant/components/meteoclimatic/translations/el.json b/homeassistant/components/meteoclimatic/translations/el.json index b852b1f619278d..cf201a68523344 100644 --- a/homeassistant/components/meteoclimatic/translations/el.json +++ b/homeassistant/components/meteoclimatic/translations/el.json @@ -4,7 +4,9 @@ "user": { "data": { "code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd" - } + }, + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd Meteoclimatic (\u03c0.\u03c7. ESCAT4300000043206B)", + "title": "Meteoclimatic" } } } diff --git a/homeassistant/components/modern_forms/translations/el.json b/homeassistant/components/modern_forms/translations/el.json index 5cdd0da50b5559..fe686797d776fa 100644 --- a/homeassistant/components/modern_forms/translations/el.json +++ b/homeassistant/components/modern_forms/translations/el.json @@ -2,6 +2,9 @@ "config": { "flow_title": "{name}", "step": { + "user": { + "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b1\u03bd\u03b5\u03bc\u03b9\u03c3\u03c4\u03ae\u03c1\u03b1 Modern Forms \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Home Assistant." + }, "zeroconf_confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b1\u03bd\u03b5\u03bc\u03b9\u03c3\u03c4\u03ae\u03c1\u03b1 Modern Forms \u03bc\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 `{name}` \u03c3\u03c4\u03bf Home Assistant;", "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b1\u03bd\u03b5\u03bc\u03b9\u03c3\u03c4\u03ae\u03c1\u03c9\u03bd Modern Forms" diff --git a/homeassistant/components/overkiz/translations/hu.json b/homeassistant/components/overkiz/translations/hu.json index 2129b81aa10509..e824d89da07353 100644 --- a/homeassistant/components/overkiz/translations/hu.json +++ b/homeassistant/components/overkiz/translations/hu.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt.", + "reauth_wrong_account": "Ezt csak ugyanazzal az Overkiz fi\u00f3kkal \u00e9s hubbal hiteles\u00edtheti \u00fajra." }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", diff --git a/homeassistant/components/overkiz/translations/id.json b/homeassistant/components/overkiz/translations/id.json index f1abd934221545..3bbdb67e6ad7ee 100644 --- a/homeassistant/components/overkiz/translations/id.json +++ b/homeassistant/components/overkiz/translations/id.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "already_configured": "Akun sudah dikonfigurasi" + "already_configured": "Akun sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil", + "reauth_wrong_account": "You can only reauthenticate this entry with the same Overkiz account and hub" }, "error": { "cannot_connect": "Gagal terhubung", diff --git a/homeassistant/components/overkiz/translations/select.id.json b/homeassistant/components/overkiz/translations/select.id.json new file mode 100644 index 00000000000000..a0d7fb1cbfe76c --- /dev/null +++ b/homeassistant/components/overkiz/translations/select.id.json @@ -0,0 +1,7 @@ +{ + "state": { + "overkiz__memorized_simple_volume": { + "highest": "Tertinggi" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/overkiz/translations/sensor.id.json b/homeassistant/components/overkiz/translations/sensor.id.json new file mode 100644 index 00000000000000..dea99c02991bde --- /dev/null +++ b/homeassistant/components/overkiz/translations/sensor.id.json @@ -0,0 +1,8 @@ +{ + "state": { + "overkiz__sensor_room": { + "clean": "Bersih", + "dirty": "Kotor" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/owntracks/translations/hu.json b/homeassistant/components/owntracks/translations/hu.json index e99b11a9e7e641..a403bb921c396f 100644 --- a/homeassistant/components/owntracks/translations/hu.json +++ b/homeassistant/components/owntracks/translations/hu.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Nincs csatlakoztatva a Home Assistant Cloudhoz.", "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "create_entry": { diff --git a/homeassistant/components/owntracks/translations/id.json b/homeassistant/components/owntracks/translations/id.json index 890afaa099cb46..214ea9265cabf1 100644 --- a/homeassistant/components/owntracks/translations/id.json +++ b/homeassistant/components/owntracks/translations/id.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Tidak terhubung ke Home Assistant Cloud.", "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." }, "create_entry": { diff --git a/homeassistant/components/plaato/translations/hu.json b/homeassistant/components/plaato/translations/hu.json index a25c0c356720d7..245b0dc2a0c396 100644 --- a/homeassistant/components/plaato/translations/hu.json +++ b/homeassistant/components/plaato/translations/hu.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "cloud_not_connected": "Nincs csatlakoztatva a Home Assistant Cloudhoz.", "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", "webhook_not_internet_accessible": "A Home Assistant p\u00e9ld\u00e1ny\u00e1nak el\u00e9rhet\u0151nek kell lennie az internet fel\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." }, diff --git a/homeassistant/components/plaato/translations/id.json b/homeassistant/components/plaato/translations/id.json index 989bb38bcaf0f5..99783fd4a630bc 100644 --- a/homeassistant/components/plaato/translations/id.json +++ b/homeassistant/components/plaato/translations/id.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Akun sudah dikonfigurasi", + "cloud_not_connected": "Tidak terhubung ke Home Assistant Cloud.", "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", "webhook_not_internet_accessible": "Instans Home Assistant Anda harus dapat diakses dari internet untuk menerima pesan webhook." }, diff --git a/homeassistant/components/remote/translations/id.json b/homeassistant/components/remote/translations/id.json index 09552be40d49d6..34eaa019be2372 100644 --- a/homeassistant/components/remote/translations/id.json +++ b/homeassistant/components/remote/translations/id.json @@ -10,6 +10,8 @@ "is_on": "{entity_name} nyala" }, "trigger_type": { + "changed_states": "{entity_name} diaktifkan atau dinonaktifkan", + "toggled": "{entity_name} diaktifkan atau dinonaktifkan", "turned_off": "{entity_name} dimatikan", "turned_on": "{entity_name} dinyalakan" } diff --git a/homeassistant/components/senseme/translations/hu.json b/homeassistant/components/senseme/translations/hu.json index 06299000122c9c..1aed50994123ae 100644 --- a/homeassistant/components/senseme/translations/hu.json +++ b/homeassistant/components/senseme/translations/hu.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", diff --git a/homeassistant/components/senseme/translations/id.json b/homeassistant/components/senseme/translations/id.json index f7c3d821e661b5..5d3b29018dd80f 100644 --- a/homeassistant/components/senseme/translations/id.json +++ b/homeassistant/components/senseme/translations/id.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Perangkat sudah dikonfigurasi" + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung" }, "error": { "cannot_connect": "Gagal terhubung", diff --git a/homeassistant/components/sia/translations/el.json b/homeassistant/components/sia/translations/el.json index 10d7ed2757b153..a7663fff6d6813 100644 --- a/homeassistant/components/sia/translations/el.json +++ b/homeassistant/components/sia/translations/el.json @@ -22,8 +22,11 @@ "options": { "data": { "ignore_timestamps": "\u0391\u03b3\u03bd\u03bf\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b7\u03c2 \u03c7\u03c1\u03bf\u03bd\u03bf\u03c3\u03c6\u03c1\u03b1\u03b3\u03af\u03b4\u03b1\u03c2 \u03c4\u03c9\u03bd \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03c9\u03bd SIA" - } + }, + "description": "\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc: {account}", + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 SIA." } } - } + }, + "title": "\u03a3\u03c5\u03c3\u03c4\u03ae\u03bc\u03b1\u03c4\u03b1 \u03a3\u03c5\u03bd\u03b1\u03b3\u03b5\u03c1\u03bc\u03bf\u03cd SIA" } \ No newline at end of file diff --git a/homeassistant/components/solax/translations/hu.json b/homeassistant/components/solax/translations/hu.json new file mode 100644 index 00000000000000..69dfcc5d841f1e --- /dev/null +++ b/homeassistant/components/solax/translations/hu.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "ip_address": "IP c\u00edm", + "password": "Jelsz\u00f3", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solax/translations/id.json b/homeassistant/components/solax/translations/id.json new file mode 100644 index 00000000000000..b1f864ad85ba37 --- /dev/null +++ b/homeassistant/components/solax/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "ip_address": "Alamat IP", + "password": "Kata Sandi", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steamist/translations/id.json b/homeassistant/components/steamist/translations/id.json index 203ba41ae00ea0..3ade230c467d95 100644 --- a/homeassistant/components/steamist/translations/id.json +++ b/homeassistant/components/steamist/translations/id.json @@ -10,7 +10,13 @@ "cannot_connect": "Gagal terhubung", "unknown": "Kesalahan yang tidak diharapkan" }, + "flow_title": "{name} ({ipaddress})", "step": { + "pick_device": { + "data": { + "device": "Perangkat" + } + }, "user": { "data": { "host": "Host" diff --git a/homeassistant/components/switch/translations/id.json b/homeassistant/components/switch/translations/id.json index 070d272aa4344a..ca341378714218 100644 --- a/homeassistant/components/switch/translations/id.json +++ b/homeassistant/components/switch/translations/id.json @@ -10,6 +10,8 @@ "is_on": "{entity_name} nyala" }, "trigger_type": { + "changed_states": "{entity_name} diaktifkan atau dinonaktifkan", + "toggled": "{entity_name} diaktifkan atau dinonaktifkan", "turned_off": "{entity_name} dimatikan", "turned_on": "{entity_name} dinyalakan" } diff --git a/homeassistant/components/synology_dsm/translations/el.json b/homeassistant/components/synology_dsm/translations/el.json index d4cb349c5463a5..b5c2d1f18f771d 100644 --- a/homeassistant/components/synology_dsm/translations/el.json +++ b/homeassistant/components/synology_dsm/translations/el.json @@ -20,7 +20,7 @@ "title": "Synology DSM" }, "reauth_confirm": { - "title": "Synology DSM " + "title": "Synology DSM \u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, "user": { "title": "Synology DSM" diff --git a/homeassistant/components/traccar/translations/hu.json b/homeassistant/components/traccar/translations/hu.json index 902b4ea5231910..6b80f58bc977d9 100644 --- a/homeassistant/components/traccar/translations/hu.json +++ b/homeassistant/components/traccar/translations/hu.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Nincs csatlakoztatva a Home Assistant Cloudhoz.", "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", "webhook_not_internet_accessible": "A Home Assistant p\u00e9ld\u00e1ny\u00e1nak el\u00e9rhet\u0151nek kell lennie az internet fel\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." }, diff --git a/homeassistant/components/traccar/translations/id.json b/homeassistant/components/traccar/translations/id.json index 573b73570c2310..17912370a99c63 100644 --- a/homeassistant/components/traccar/translations/id.json +++ b/homeassistant/components/traccar/translations/id.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Tidak terhubung ke Home Assistant Cloud.", "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", "webhook_not_internet_accessible": "Instans Home Assistant Anda harus dapat diakses dari internet untuk menerima pesan webhook." }, diff --git a/homeassistant/components/tuya/translations/select.bg.json b/homeassistant/components/tuya/translations/select.bg.json index 3105feec5f7773..ce690131579b2e 100644 --- a/homeassistant/components/tuya/translations/select.bg.json +++ b/homeassistant/components/tuya/translations/select.bg.json @@ -9,6 +9,14 @@ "1": "\u0418\u0437\u043a\u043b\u044e\u0447\u0435\u043d\u043e", "2": "\u0412\u043a\u043b\u044e\u0447\u0435\u043d\u043e" }, + "tuya__curtain_mode": { + "morning": "\u0421\u0443\u0442\u0440\u0438\u043d", + "night": "\u041d\u043e\u0449" + }, + "tuya__curtain_motor_mode": { + "back": "\u041d\u0430\u0437\u0430\u0434", + "forward": "\u041d\u0430\u043f\u0440\u0435\u0434" + }, "tuya__decibel_sensitivity": { "0": "\u041d\u0438\u0441\u043a\u0430 \u0447\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u043d\u043e\u0441\u0442", "1": "\u0412\u0438\u0441\u043e\u043a\u0430 \u0447\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u043d\u043e\u0441\u0442" diff --git a/homeassistant/components/tuya/translations/select.ca.json b/homeassistant/components/tuya/translations/select.ca.json index b8511d68338d02..17846d1760de8c 100644 --- a/homeassistant/components/tuya/translations/select.ca.json +++ b/homeassistant/components/tuya/translations/select.ca.json @@ -10,6 +10,14 @@ "1": "OFF", "2": "ON" }, + "tuya__curtain_mode": { + "morning": "Mat\u00ed", + "night": "Nit" + }, + "tuya__curtain_motor_mode": { + "back": "Enrere", + "forward": "Endavant" + }, "tuya__decibel_sensitivity": { "0": "Sensibilitat baixa", "1": "Sensibilitat alta" diff --git a/homeassistant/components/tuya/translations/select.de.json b/homeassistant/components/tuya/translations/select.de.json index 23dbb00b4919b8..0aadad144437db 100644 --- a/homeassistant/components/tuya/translations/select.de.json +++ b/homeassistant/components/tuya/translations/select.de.json @@ -10,6 +10,14 @@ "1": "Aus", "2": "An" }, + "tuya__curtain_mode": { + "morning": "Morgen", + "night": "Nacht" + }, + "tuya__curtain_motor_mode": { + "back": "Zur\u00fcck", + "forward": "Vorw\u00e4rts" + }, "tuya__decibel_sensitivity": { "0": "Geringe Empfindlichkeit", "1": "Hohe Empfindlichkeit" diff --git a/homeassistant/components/tuya/translations/select.el.json b/homeassistant/components/tuya/translations/select.el.json index 9c773ec34a0b4a..f8c23abd578ecb 100644 --- a/homeassistant/components/tuya/translations/select.el.json +++ b/homeassistant/components/tuya/translations/select.el.json @@ -8,6 +8,14 @@ "tuya__basic_nightvision": { "0": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03bf" }, + "tuya__curtain_mode": { + "morning": "\u03a0\u03c1\u03c9\u03af", + "night": "\u039d\u03cd\u03c7\u03c4\u03b1" + }, + "tuya__curtain_motor_mode": { + "back": "\u03a0\u03af\u03c3\u03c9", + "forward": "\u0395\u03bc\u03c0\u03c1\u03cc\u03c2" + }, "tuya__fan_angle": { "30": "30\u00b0", "60": "60\u00b0", diff --git a/homeassistant/components/tuya/translations/select.et.json b/homeassistant/components/tuya/translations/select.et.json index 3b97624676943d..8d52bf30cca4f8 100644 --- a/homeassistant/components/tuya/translations/select.et.json +++ b/homeassistant/components/tuya/translations/select.et.json @@ -10,6 +10,14 @@ "1": "V\u00e4ljas", "2": "Sees" }, + "tuya__curtain_mode": { + "morning": "Hommik", + "night": "\u00d6\u00f6" + }, + "tuya__curtain_motor_mode": { + "back": "Tagasi", + "forward": "Edasi" + }, "tuya__decibel_sensitivity": { "0": "Madal tundlikkus", "1": "K\u00f5rge tundlikkus" diff --git a/homeassistant/components/tuya/translations/select.hu.json b/homeassistant/components/tuya/translations/select.hu.json index f1df3a531e85ef..613ce8cedd28c4 100644 --- a/homeassistant/components/tuya/translations/select.hu.json +++ b/homeassistant/components/tuya/translations/select.hu.json @@ -10,10 +10,23 @@ "1": "Ki", "2": "Be" }, + "tuya__curtain_mode": { + "morning": "Reggel", + "night": "\u00c9jszaka" + }, + "tuya__curtain_motor_mode": { + "back": "Vissza", + "forward": "El\u0151re" + }, "tuya__decibel_sensitivity": { "0": "Alacsony \u00e9rz\u00e9kenys\u00e9g", "1": "Magas \u00e9rz\u00e9kenys\u00e9g" }, + "tuya__fan_angle": { + "30": "30\u00b0", + "60": "60\u00b0", + "90": "90\u00b0" + }, "tuya__fingerbot_mode": { "click": "Lenyom\u00e1s", "switch": "Kapcsol\u00e1s" diff --git a/homeassistant/components/tuya/translations/select.ru.json b/homeassistant/components/tuya/translations/select.ru.json index 34ddcfcd7a6579..9f575f3f5f49d3 100644 --- a/homeassistant/components/tuya/translations/select.ru.json +++ b/homeassistant/components/tuya/translations/select.ru.json @@ -10,6 +10,14 @@ "1": "\u0412\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u043e", "2": "\u0412\u043a\u043b\u044e\u0447\u0435\u043d\u043e" }, + "tuya__curtain_mode": { + "morning": "\u0423\u0442\u0440\u043e", + "night": "\u041d\u043e\u0447\u044c" + }, + "tuya__curtain_motor_mode": { + "back": "\u041d\u0430\u0437\u0430\u0434", + "forward": "\u0412\u043f\u0435\u0440\u0435\u0434" + }, "tuya__decibel_sensitivity": { "0": "\u041d\u0438\u0437\u043a\u0430\u044f \u0447\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c", "1": "\u0412\u044b\u0441\u043e\u043a\u0430\u044f \u0447\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c" diff --git a/homeassistant/components/tuya/translations/select.tr.json b/homeassistant/components/tuya/translations/select.tr.json index 52d7dcff5d168e..009b8f622452b0 100644 --- a/homeassistant/components/tuya/translations/select.tr.json +++ b/homeassistant/components/tuya/translations/select.tr.json @@ -10,6 +10,14 @@ "1": "Kapal\u0131", "2": "A\u00e7\u0131k" }, + "tuya__curtain_mode": { + "morning": "Sabah", + "night": "Gece" + }, + "tuya__curtain_motor_mode": { + "back": "Geri", + "forward": "\u0130leri" + }, "tuya__decibel_sensitivity": { "0": "D\u00fc\u015f\u00fck hassasiyet", "1": "Y\u00fcksek hassasiyet" diff --git a/homeassistant/components/tuya/translations/select.zh-Hant.json b/homeassistant/components/tuya/translations/select.zh-Hant.json index a9ee45002cfeb7..71ce79519ed80c 100644 --- a/homeassistant/components/tuya/translations/select.zh-Hant.json +++ b/homeassistant/components/tuya/translations/select.zh-Hant.json @@ -10,6 +10,14 @@ "1": "\u95dc\u9589", "2": "\u958b\u555f" }, + "tuya__curtain_mode": { + "morning": "\u65e9\u6668", + "night": "\u591c\u9593" + }, + "tuya__curtain_motor_mode": { + "back": "\u5f8c\u9000", + "forward": "\u524d\u9032" + }, "tuya__decibel_sensitivity": { "0": "\u4f4e\u654f\u611f\u5ea6", "1": "\u9ad8\u654f\u611f\u5ea6" diff --git a/homeassistant/components/twilio/translations/bg.json b/homeassistant/components/twilio/translations/bg.json index c3defd52d71ab2..c1ef00b7b3c806 100644 --- a/homeassistant/components/twilio/translations/bg.json +++ b/homeassistant/components/twilio/translations/bg.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u041d\u0435 \u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d \u0441 Home Assistant Cloud.", "single_instance_allowed": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." }, "create_entry": { diff --git a/homeassistant/components/twilio/translations/hu.json b/homeassistant/components/twilio/translations/hu.json index 512296463e4e38..409a9b08a720a0 100644 --- a/homeassistant/components/twilio/translations/hu.json +++ b/homeassistant/components/twilio/translations/hu.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Nincs csatlakoztatva a Home Assistant Cloudhoz.", "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", "webhook_not_internet_accessible": "A Home Assistant p\u00e9ld\u00e1ny\u00e1nak el\u00e9rhet\u0151nek kell lennie az internet fel\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." }, diff --git a/homeassistant/components/twilio/translations/id.json b/homeassistant/components/twilio/translations/id.json index be16b1d4802cdf..06a77bc974eb59 100644 --- a/homeassistant/components/twilio/translations/id.json +++ b/homeassistant/components/twilio/translations/id.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Tidak terhubung ke Home Assistant Cloud.", "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", "webhook_not_internet_accessible": "Instans Home Assistant Anda harus dapat diakses dari internet untuk menerima pesan webhook." }, diff --git a/homeassistant/components/unifiprotect/translations/id.json b/homeassistant/components/unifiprotect/translations/id.json index 4e99ac3e90a2bd..3612367090e6a5 100644 --- a/homeassistant/components/unifiprotect/translations/id.json +++ b/homeassistant/components/unifiprotect/translations/id.json @@ -9,6 +9,7 @@ "protect_version": "Versi minimum yang diperlukan adalah v1.20.0. Tingkatkan UniFi Protect lalu coba lagi.", "unknown": "Kesalahan yang tidak diharapkan" }, + "flow_title": "{name} ({ip_address})", "step": { "discovery_confirm": { "data": { diff --git a/homeassistant/components/uptimerobot/translations/sensor.bg.json b/homeassistant/components/uptimerobot/translations/sensor.bg.json new file mode 100644 index 00000000000000..9b98369a859ae7 --- /dev/null +++ b/homeassistant/components/uptimerobot/translations/sensor.bg.json @@ -0,0 +1,7 @@ +{ + "state": { + "uptimerobot__monitor_status": { + "pause": "\u041f\u0430\u0443\u0437\u0430" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/uptimerobot/translations/sensor.el.json b/homeassistant/components/uptimerobot/translations/sensor.el.json new file mode 100644 index 00000000000000..e9aa45f91f2b74 --- /dev/null +++ b/homeassistant/components/uptimerobot/translations/sensor.el.json @@ -0,0 +1,10 @@ +{ + "state": { + "uptimerobot__monitor_status": { + "down": "\u039a\u03ac\u03c4\u03c9", + "not_checked_yet": "\u0394\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b5\u03bb\u03b5\u03b3\u03c7\u03b8\u03b5\u03af \u03b1\u03ba\u03cc\u03bc\u03b1", + "pause": "\u03a0\u03b1\u03cd\u03c3\u03b7", + "up": "\u03a0\u03ac\u03bd\u03c9" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/uptimerobot/translations/sensor.hu.json b/homeassistant/components/uptimerobot/translations/sensor.hu.json new file mode 100644 index 00000000000000..f4f3c0b2460757 --- /dev/null +++ b/homeassistant/components/uptimerobot/translations/sensor.hu.json @@ -0,0 +1,11 @@ +{ + "state": { + "uptimerobot__monitor_status": { + "down": "Nem el\u00e9rhet\u0151", + "not_checked_yet": "M\u00e9g nincs ellen\u0151rizve", + "pause": "Sz\u00fcnet", + "seems_down": "Nem el\u00e9rhet\u0151nek t\u0171nik", + "up": "El\u00e9rhet\u0151" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vallox/translations/id.json b/homeassistant/components/vallox/translations/id.json index 45c32845701ef2..4f8fbbff6e9774 100644 --- a/homeassistant/components/vallox/translations/id.json +++ b/homeassistant/components/vallox/translations/id.json @@ -17,7 +17,7 @@ "host": "Host", "name": "Nama" }, - "description": "Siapkan integrasi Vallox Jika Anda memiliki masalah dengan konfigurasi, buka: https://www.home-assistant.io/integrations/vallox ", + "description": "Siapkan integrasi Vallox Jika Anda memiliki masalah dengan konfigurasi, buka {integration_docs_url}.", "title": "Vallox" } } diff --git a/homeassistant/components/webostv/translations/id.json b/homeassistant/components/webostv/translations/id.json index 44ee9452fef154..81bc9f86bf696d 100644 --- a/homeassistant/components/webostv/translations/id.json +++ b/homeassistant/components/webostv/translations/id.json @@ -2,14 +2,45 @@ "config": { "abort": { "already_configured": "Perangkat sudah dikonfigurasi", - "already_in_progress": "Alur konfigurasi sedang berlangsung" + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "error_pairing": "Terhubung ke LG webOS TV tetapi tidak dipasangkan" }, + "error": { + "cannot_connect": "Gagal terhubung, nyalakan TV atau periksa alamat IP" + }, + "flow_title": "LG webOS Smart TV", "step": { + "pairing": { + "description": "Klik kirim dan terima permintaan pemasangan di TV Anda. \n\n![Image](/static/images/config_webos.png)", + "title": "Pasangan webOS TV" + }, "user": { "data": { "host": "Host", "name": "Nama" - } + }, + "description": "Nyalakan TV, isi bidang berikut lalu klik kirimkan", + "title": "Hubungkan ke webOS TV" + } + } + }, + "device_automation": { + "trigger_type": { + "webostv.turn_on": "Perangkat diminta untuk dinyalakan" + } + }, + "options": { + "error": { + "cannot_retrieve": "Tidak dapat mengambil daftar sumber. Pastikan perangkat dihidupkan", + "script_not_found": "Skrip tidak ditemukan" + }, + "step": { + "init": { + "data": { + "sources": "Daftar sumber" + }, + "description": "Pilih sumber yang diaktifkan", + "title": "Opsi untuk webOS Smart TV" } } } diff --git a/homeassistant/components/whois/translations/id.json b/homeassistant/components/whois/translations/id.json index fa8cd415378a7e..ae926fd522ffbf 100644 --- a/homeassistant/components/whois/translations/id.json +++ b/homeassistant/components/whois/translations/id.json @@ -2,6 +2,19 @@ "config": { "abort": { "already_configured": "Layanan sudah dikonfigurasi" + }, + "error": { + "unexpected_response": "Respons tak terduga dari server whois", + "unknown_date_format": "Format tanggal tidak diketahui dalam respons server whois", + "unknown_tld": "TLD yang diberikan tidak diketahui atau tidak tersedia untuk integrasi ini", + "whois_command_failed": "Perintah whois gagal: tidak dapat mengambil informasi whois" + }, + "step": { + "user": { + "data": { + "domain": "Nama domain" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/withings/translations/el.json b/homeassistant/components/withings/translations/el.json index 78528a8898b4ca..a7d70b00a21a82 100644 --- a/homeassistant/components/withings/translations/el.json +++ b/homeassistant/components/withings/translations/el.json @@ -9,7 +9,8 @@ "data": { "profile": "\u039f\u03bd\u03bf\u03bc\u03b1 \u03c0\u03c1\u03bf\u03c6\u03af\u03bb" }, - "description": "\u0394\u03ce\u03c3\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03bc\u03bf\u03bd\u03b1\u03b4\u03b9\u03ba\u03cc \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c0\u03c1\u03bf\u03c6\u03af\u03bb \u03b3\u03b9\u03b1 \u03c4\u03b1 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 \u03b1\u03c5\u03c4\u03ac. \u03a3\u03c5\u03bd\u03ae\u03b8\u03c9\u03c2 \u03c0\u03c1\u03cc\u03ba\u03b5\u03b9\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03bf\u03c6\u03af\u03bb \u03c0\u03bf\u03c5 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03b1\u03c4\u03b5 \u03c3\u03c4\u03bf \u03c0\u03c1\u03bf\u03b7\u03b3\u03bf\u03cd\u03bc\u03b5\u03bd\u03bf \u03b2\u03ae\u03bc\u03b1." + "description": "\u0394\u03ce\u03c3\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03bc\u03bf\u03bd\u03b1\u03b4\u03b9\u03ba\u03cc \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c0\u03c1\u03bf\u03c6\u03af\u03bb \u03b3\u03b9\u03b1 \u03c4\u03b1 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 \u03b1\u03c5\u03c4\u03ac. \u03a3\u03c5\u03bd\u03ae\u03b8\u03c9\u03c2 \u03c0\u03c1\u03cc\u03ba\u03b5\u03b9\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03bf\u03c6\u03af\u03bb \u03c0\u03bf\u03c5 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03b1\u03c4\u03b5 \u03c3\u03c4\u03bf \u03c0\u03c1\u03bf\u03b7\u03b3\u03bf\u03cd\u03bc\u03b5\u03bd\u03bf \u03b2\u03ae\u03bc\u03b1.", + "title": "\u03a0\u03c1\u03bf\u03c6\u03af\u03bb \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7." }, "reauth": { "description": "\u03a4\u03bf \u03c0\u03c1\u03bf\u03c6\u03af\u03bb \"{profile}\" \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03b9 \u03bd\u03b1 \u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03b5\u03b9 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 Withings." diff --git a/homeassistant/components/wled/translations/el.json b/homeassistant/components/wled/translations/el.json index d931bc69d70ea0..35de8dfdcdc5f0 100644 --- a/homeassistant/components/wled/translations/el.json +++ b/homeassistant/components/wled/translations/el.json @@ -16,5 +16,14 @@ "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae WLED" } } + }, + "options": { + "step": { + "init": { + "data": { + "keep_master_light": "\u0394\u03b9\u03b1\u03c4\u03b7\u03c1\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03cd\u03c1\u03b9\u03bf \u03c6\u03c9\u03c2, \u03b1\u03ba\u03cc\u03bc\u03b7 \u03ba\u03b1\u03b9 \u03bc\u03b5 1 \u03c4\u03bc\u03ae\u03bc\u03b1 LED." + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/wled/translations/id.json b/homeassistant/components/wled/translations/id.json index 621b11e4af59c0..f52d88f5401bb3 100644 --- a/homeassistant/components/wled/translations/id.json +++ b/homeassistant/components/wled/translations/id.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "Perangkat sudah dikonfigurasi", - "cannot_connect": "Gagal terhubung" + "cannot_connect": "Gagal terhubung", + "cct_unsupported": "Perangkat WLED ini menggunakan saluran CCT, yang tidak didukung oleh integrasi ini" }, "error": { "cannot_connect": "Gagal terhubung" diff --git a/homeassistant/components/xiaomi_miio/translations/el.json b/homeassistant/components/xiaomi_miio/translations/el.json index c2e966aee731c5..47716969d47b48 100644 --- a/homeassistant/components/xiaomi_miio/translations/el.json +++ b/homeassistant/components/xiaomi_miio/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "incomplete_info": "\u0395\u03bb\u03bb\u03b9\u03c0\u03b5\u03af\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2, \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c0\u03b1\u03c1\u03b1\u03c3\u03c7\u03b5\u03b8\u03b5\u03af \u03c5\u03c0\u03bf\u03b4\u03bf\u03c7\u03ad\u03b1\u03c2 \u03ae \u03ba\u03bf\u03c5\u03c0\u03cc\u03bd\u03b9." + }, "error": { "no_device_selected": "\u0394\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03b5\u03af \u03ba\u03b1\u03bc\u03af\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae, \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae.", "unknown_device": "\u03a4\u03bf \u03bc\u03bf\u03bd\u03c4\u03ad\u03bb\u03bf \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b3\u03bd\u03c9\u03c3\u03c4\u03cc, \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03bc\u03b5 \u03c4\u03b7 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c1\u03bf\u03ae\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c9\u03bd." diff --git a/homeassistant/components/yale_smart_alarm/translations/id.json b/homeassistant/components/yale_smart_alarm/translations/id.json index 86e54e111bde80..da01b435335e3e 100644 --- a/homeassistant/components/yale_smart_alarm/translations/id.json +++ b/homeassistant/components/yale_smart_alarm/translations/id.json @@ -26,5 +26,18 @@ } } } + }, + "options": { + "error": { + "code_format_mismatch": "Kode tidak sesuai dengan jumlah digit yang diperlukan" + }, + "step": { + "init": { + "data": { + "code": "Kode bawaan untuk kunci, digunakan jika tidak ada yang diberikan", + "lock_code_digits": "Jumlah digit dalam kode PIN untuk kunci" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/yamaha_musiccast/translations/el.json b/homeassistant/components/yamaha_musiccast/translations/el.json new file mode 100644 index 00000000000000..a5118ffd8492e1 --- /dev/null +++ b/homeassistant/components/yamaha_musiccast/translations/el.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "yxc_control_url_missing": "\u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03b4\u03b5\u03bd \u03b4\u03af\u03bd\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03b7\u03bd \u03c0\u03b5\u03c1\u03b9\u03b3\u03c1\u03b1\u03c6\u03ae \u03c4\u03bf\u03c5 ssdp." + }, + "error": { + "no_musiccast_device": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c6\u03b1\u03af\u03bd\u03b5\u03c4\u03b1\u03b9 \u03bd\u03b1 \u03bc\u03b7\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae MusicCast." + }, + "flow_title": "MusicCast: {name}", + "step": { + "user": { + "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf MusicCast \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Home Assistant." + } + } + } +} \ No newline at end of file From c7cdee258eba1e3e047930d10b2e5ba360ce340c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 29 Jan 2022 05:18:09 +0100 Subject: [PATCH 0064/1098] Move remaining keys to `setup.cfg` (#65154) * Move metadata keys * Move options * Delete setup.py * Remove unused constants * Remove deprecated test_suite key * Improve metadata * Only include homeassistant*, not script* * Add long_desc_content_type * Remove license file (auto-included by setuptools + wheels) * Add setup.py Pip 21.2 doesn't support editable installs without it. --- MANIFEST.in | 1 - setup.cfg | 16 +++++++++++++++- setup.py | 30 ++++++------------------------ 3 files changed, 21 insertions(+), 26 deletions(-) mode change 100755 => 100644 setup.py diff --git a/MANIFEST.in b/MANIFEST.in index 490b550e705e52..780ffd02719dae 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,3 @@ include README.rst -include LICENSE.md graft homeassistant recursive-exclude * *.py[co] diff --git a/setup.cfg b/setup.cfg index 274e1ac362a548..061e0bbc0cbe64 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,10 +1,13 @@ [metadata] +name = homeassistant version = 2022.3.0.dev0 +author = The Home Assistant Authors +author_email = hello@home-assistant.io license = Apache-2.0 -license_file = LICENSE.md platforms = any description = Open-source home automation platform running on Python 3. long_description = file: README.rst +long_description_content_type = text/x-rst keywords = home, automation url = https://www.home-assistant.io/ project_urls = @@ -23,6 +26,9 @@ classifier = Topic :: Home Automation [options] +packages = find: +zip_safe = False +include_package_data = True python_requires = >=3.9.0 install_requires = aiohttp==3.8.1 @@ -51,6 +57,14 @@ install_requires = voluptuous-serialize==2.5.0 yarl==1.7.2 +[options.packages.find] +include = + homeassistant* + +[options.entry_points] +console_scripts = + hass = homeassistant.__main__:main + [flake8] exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build max-complexity = 25 diff --git a/setup.py b/setup.py old mode 100755 new mode 100644 index febaab62be037b..69bf65dd8a4bde --- a/setup.py +++ b/setup.py @@ -1,25 +1,7 @@ -#!/usr/bin/env python3 -"""Home Assistant setup script.""" -from datetime import datetime as dt +""" +Entry point for setuptools. Required for editable installs. +TODO: Remove file after updating to pip 21.3 +""" +from setuptools import setup -from setuptools import find_packages, setup - -PROJECT_NAME = "Home Assistant" -PROJECT_PACKAGE_NAME = "homeassistant" -PROJECT_LICENSE = "Apache License 2.0" -PROJECT_AUTHOR = "The Home Assistant Authors" -PROJECT_COPYRIGHT = f" 2013-{dt.now().year}, {PROJECT_AUTHOR}" -PROJECT_EMAIL = "hello@home-assistant.io" - -PACKAGES = find_packages(exclude=["tests", "tests.*"]) - -setup( - name=PROJECT_PACKAGE_NAME, - author=PROJECT_AUTHOR, - author_email=PROJECT_EMAIL, - packages=PACKAGES, - include_package_data=True, - zip_safe=False, - test_suite="tests", - entry_points={"console_scripts": ["hass = homeassistant.__main__:main"]}, -) +setup() From 0755310258c706bb9daebdf0260ae8ba9f4eaa61 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 28 Jan 2022 22:21:05 -0600 Subject: [PATCH 0065/1098] Add loggers to zeroconf (#65168) - The original PR excluded all zeroconf deps, and I forget to add it back --- homeassistant/components/zeroconf/manifest.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json index 16a8a8ff26e157..d1b43da9e27d93 100644 --- a/homeassistant/components/zeroconf/manifest.json +++ b/homeassistant/components/zeroconf/manifest.json @@ -6,5 +6,6 @@ "dependencies": ["network", "api"], "codeowners": ["@bdraco"], "quality_scale": "internal", - "iot_class": "local_push" + "iot_class": "local_push", + "loggers": ["zeroconf"] } From 16db8e080258b9ea71940587d102f153da4122e0 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 29 Jan 2022 06:05:53 +0100 Subject: [PATCH 0066/1098] Fix setting speed of Tuya fan (#65155) --- homeassistant/components/tuya/fan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/tuya/fan.py b/homeassistant/components/tuya/fan.py index 53de4f392b8cf0..42849d4498d9e3 100644 --- a/homeassistant/components/tuya/fan.py +++ b/homeassistant/components/tuya/fan.py @@ -137,7 +137,7 @@ def set_percentage(self, percentage: int) -> None: [ { "code": self._speed.dpcode, - "value": self._speed.scale_value_back(percentage), + "value": int(self._speed.remap_value_from(percentage, 0, 100)), } ] ) From 3da33679a29cddd3527b7e508d3c2538e389b397 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Sat, 29 Jan 2022 06:06:19 +0100 Subject: [PATCH 0067/1098] Fritz tests cleanup (#65054) --- tests/components/fritz/__init__.py | 126 --------------------- tests/components/fritz/conftest.py | 116 +++++++++++++++++++ tests/components/fritz/const.py | 48 ++++++++ tests/components/fritz/test_config_flow.py | 47 ++------ 4 files changed, 173 insertions(+), 164 deletions(-) create mode 100644 tests/components/fritz/conftest.py create mode 100644 tests/components/fritz/const.py diff --git a/tests/components/fritz/__init__.py b/tests/components/fritz/__init__.py index a1fd1ce42fb1d4..1462ec77b8f1db 100644 --- a/tests/components/fritz/__init__.py +++ b/tests/components/fritz/__init__.py @@ -1,127 +1 @@ """Tests for the AVM Fritz!Box integration.""" -from unittest import mock - -from homeassistant.components.fritz.const import DOMAIN -from homeassistant.const import ( - CONF_DEVICES, - CONF_HOST, - CONF_PASSWORD, - CONF_PORT, - CONF_USERNAME, -) - -MOCK_CONFIG = { - DOMAIN: { - CONF_DEVICES: [ - { - CONF_HOST: "fake_host", - CONF_PORT: "1234", - CONF_PASSWORD: "fake_pass", - CONF_USERNAME: "fake_user", - } - ] - } -} - - -class FritzConnectionMock: # pylint: disable=too-few-public-methods - """FritzConnection mocking.""" - - FRITZBOX_DATA = { - ("WANIPConn:1", "GetStatusInfo"): { - "NewConnectionStatus": "Connected", - "NewUptime": 35307, - }, - ("WANIPConnection:1", "GetStatusInfo"): {}, - ("WANCommonIFC:1", "GetCommonLinkProperties"): { - "NewLayer1DownstreamMaxBitRate": 10087000, - "NewLayer1UpstreamMaxBitRate": 2105000, - "NewPhysicalLinkStatus": "Up", - }, - ("WANCommonIFC:1", "GetAddonInfos"): { - "NewByteSendRate": 3438, - "NewByteReceiveRate": 67649, - "NewTotalBytesSent": 1712232562, - "NewTotalBytesReceived": 5221019883, - }, - ("LANEthernetInterfaceConfig:1", "GetStatistics"): { - "NewBytesSent": 23004321, - "NewBytesReceived": 12045, - }, - ("DeviceInfo:1", "GetInfo"): { - "NewSerialNumber": "abcdefgh", - "NewName": "TheName", - "NewModelName": "FRITZ!Box 7490", - }, - } - - FRITZBOX_DATA_INDEXED = { - ("X_AVM-DE_Homeauto:1", "GetGenericDeviceInfos"): [ - { - "NewSwitchIsValid": "VALID", - "NewMultimeterIsValid": "VALID", - "NewTemperatureIsValid": "VALID", - "NewDeviceId": 16, - "NewAIN": "08761 0114116", - "NewDeviceName": "FRITZ!DECT 200 #1", - "NewTemperatureOffset": "0", - "NewSwitchLock": "0", - "NewProductName": "FRITZ!DECT 200", - "NewPresent": "CONNECTED", - "NewMultimeterPower": 1673, - "NewHkrComfortTemperature": "0", - "NewSwitchMode": "AUTO", - "NewManufacturer": "AVM", - "NewMultimeterIsEnabled": "ENABLED", - "NewHkrIsTemperature": "0", - "NewFunctionBitMask": 2944, - "NewTemperatureIsEnabled": "ENABLED", - "NewSwitchState": "ON", - "NewSwitchIsEnabled": "ENABLED", - "NewFirmwareVersion": "03.87", - "NewHkrSetVentilStatus": "CLOSED", - "NewMultimeterEnergy": 5182, - "NewHkrComfortVentilStatus": "CLOSED", - "NewHkrReduceTemperature": "0", - "NewHkrReduceVentilStatus": "CLOSED", - "NewHkrIsEnabled": "DISABLED", - "NewHkrSetTemperature": "0", - "NewTemperatureCelsius": "225", - "NewHkrIsValid": "INVALID", - }, - {}, - ], - ("Hosts1", "GetGenericHostEntry"): [ - { - "NewSerialNumber": 1234, - "NewName": "TheName", - "NewModelName": "FRITZ!Box 7490", - }, - {}, - ], - } - - MODELNAME = "FRITZ!Box 7490" - - def __init__(self): - """Inint Mocking class.""" - type(self).modelname = mock.PropertyMock(return_value=self.MODELNAME) - self.call_action = mock.Mock(side_effect=self._side_effect_call_action) - type(self).action_names = mock.PropertyMock( - side_effect=self._side_effect_action_names - ) - services = { - srv: None - for srv, _ in list(self.FRITZBOX_DATA) + list(self.FRITZBOX_DATA_INDEXED) - } - type(self).services = mock.PropertyMock(side_effect=[services]) - - def _side_effect_call_action(self, service, action, **kwargs): - if kwargs: - index = next(iter(kwargs.values())) - return self.FRITZBOX_DATA_INDEXED[(service, action)][index] - - return self.FRITZBOX_DATA[(service, action)] - - def _side_effect_action_names(self): - return list(self.FRITZBOX_DATA) + list(self.FRITZBOX_DATA_INDEXED) diff --git a/tests/components/fritz/conftest.py b/tests/components/fritz/conftest.py new file mode 100644 index 00000000000000..6f99ab483e64f6 --- /dev/null +++ b/tests/components/fritz/conftest.py @@ -0,0 +1,116 @@ +"""Common stuff for AVM Fritz!Box tests.""" +from unittest import mock +from unittest.mock import patch + +import pytest + + +@pytest.fixture() +def fc_class_mock(): + """Fixture that sets up a mocked FritzConnection class.""" + with patch("fritzconnection.FritzConnection", autospec=True) as result: + result.return_value = FritzConnectionMock() + yield result + + +class FritzConnectionMock: # pylint: disable=too-few-public-methods + """FritzConnection mocking.""" + + FRITZBOX_DATA = { + ("WANIPConn:1", "GetStatusInfo"): { + "NewConnectionStatus": "Connected", + "NewUptime": 35307, + }, + ("WANIPConnection:1", "GetStatusInfo"): {}, + ("WANCommonIFC:1", "GetCommonLinkProperties"): { + "NewLayer1DownstreamMaxBitRate": 10087000, + "NewLayer1UpstreamMaxBitRate": 2105000, + "NewPhysicalLinkStatus": "Up", + }, + ("WANCommonIFC:1", "GetAddonInfos"): { + "NewByteSendRate": 3438, + "NewByteReceiveRate": 67649, + "NewTotalBytesSent": 1712232562, + "NewTotalBytesReceived": 5221019883, + }, + ("LANEthernetInterfaceConfig:1", "GetStatistics"): { + "NewBytesSent": 23004321, + "NewBytesReceived": 12045, + }, + ("DeviceInfo:1", "GetInfo"): { + "NewSerialNumber": "abcdefgh", + "NewName": "TheName", + "NewModelName": "FRITZ!Box 7490", + }, + } + + FRITZBOX_DATA_INDEXED = { + ("X_AVM-DE_Homeauto:1", "GetGenericDeviceInfos"): [ + { + "NewSwitchIsValid": "VALID", + "NewMultimeterIsValid": "VALID", + "NewTemperatureIsValid": "VALID", + "NewDeviceId": 16, + "NewAIN": "08761 0114116", + "NewDeviceName": "FRITZ!DECT 200 #1", + "NewTemperatureOffset": "0", + "NewSwitchLock": "0", + "NewProductName": "FRITZ!DECT 200", + "NewPresent": "CONNECTED", + "NewMultimeterPower": 1673, + "NewHkrComfortTemperature": "0", + "NewSwitchMode": "AUTO", + "NewManufacturer": "AVM", + "NewMultimeterIsEnabled": "ENABLED", + "NewHkrIsTemperature": "0", + "NewFunctionBitMask": 2944, + "NewTemperatureIsEnabled": "ENABLED", + "NewSwitchState": "ON", + "NewSwitchIsEnabled": "ENABLED", + "NewFirmwareVersion": "03.87", + "NewHkrSetVentilStatus": "CLOSED", + "NewMultimeterEnergy": 5182, + "NewHkrComfortVentilStatus": "CLOSED", + "NewHkrReduceTemperature": "0", + "NewHkrReduceVentilStatus": "CLOSED", + "NewHkrIsEnabled": "DISABLED", + "NewHkrSetTemperature": "0", + "NewTemperatureCelsius": "225", + "NewHkrIsValid": "INVALID", + }, + {}, + ], + ("Hosts1", "GetGenericHostEntry"): [ + { + "NewSerialNumber": 1234, + "NewName": "TheName", + "NewModelName": "FRITZ!Box 7490", + }, + {}, + ], + } + + MODELNAME = "FRITZ!Box 7490" + + def __init__(self): + """Inint Mocking class.""" + type(self).modelname = mock.PropertyMock(return_value=self.MODELNAME) + self.call_action = mock.Mock(side_effect=self._side_effect_call_action) + type(self).action_names = mock.PropertyMock( + side_effect=self._side_effect_action_names + ) + services = { + srv: None + for srv, _ in list(self.FRITZBOX_DATA) + list(self.FRITZBOX_DATA_INDEXED) + } + type(self).services = mock.PropertyMock(side_effect=[services]) + + def _side_effect_call_action(self, service, action, **kwargs): + if kwargs: + index = next(iter(kwargs.values())) + return self.FRITZBOX_DATA_INDEXED[(service, action)][index] + + return self.FRITZBOX_DATA[(service, action)] + + def _side_effect_action_names(self): + return list(self.FRITZBOX_DATA) + list(self.FRITZBOX_DATA_INDEXED) diff --git a/tests/components/fritz/const.py b/tests/components/fritz/const.py new file mode 100644 index 00000000000000..3212794fc85ec9 --- /dev/null +++ b/tests/components/fritz/const.py @@ -0,0 +1,48 @@ +"""Common stuff for AVM Fritz!Box tests.""" +from homeassistant.components import ssdp +from homeassistant.components.fritz.const import DOMAIN +from homeassistant.components.ssdp import ATTR_UPNP_FRIENDLY_NAME, ATTR_UPNP_UDN +from homeassistant.const import ( + CONF_DEVICES, + CONF_HOST, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, +) + +ATTR_HOST = "host" +ATTR_NEW_SERIAL_NUMBER = "NewSerialNumber" + +MOCK_CONFIG = { + DOMAIN: { + CONF_DEVICES: [ + { + CONF_HOST: "fake_host", + CONF_PORT: "1234", + CONF_PASSWORD: "fake_pass", + CONF_USERNAME: "fake_user", + } + ] + } +} +MOCK_HOST = "fake_host" +MOCK_IP = "192.168.178.1" +MOCK_SERIAL_NUMBER = "fake_serial_number" +MOCK_FIRMWARE_INFO = [True, "1.1.1"] + +MOCK_USER_DATA = MOCK_CONFIG[DOMAIN][CONF_DEVICES][0] +MOCK_DEVICE_INFO = { + ATTR_HOST: MOCK_HOST, + ATTR_NEW_SERIAL_NUMBER: MOCK_SERIAL_NUMBER, +} +MOCK_SSDP_DATA = ssdp.SsdpServiceInfo( + ssdp_usn="mock_usn", + ssdp_st="mock_st", + ssdp_location=f"https://{MOCK_IP}:12345/test", + upnp={ + ATTR_UPNP_FRIENDLY_NAME: "fake_name", + ATTR_UPNP_UDN: "uuid:only-a-test", + }, +) + +MOCK_REQUEST = b'xxxxxxxxxxxxxxxxxxxxxxxx0Dial2App2HomeAuto2BoxAdmin2Phone2NAS2FakeFritzUser\n' diff --git a/tests/components/fritz/test_config_flow.py b/tests/components/fritz/test_config_flow.py index edb03c516037a9..6505ee2bcaaf72 100644 --- a/tests/components/fritz/test_config_flow.py +++ b/tests/components/fritz/test_config_flow.py @@ -3,9 +3,7 @@ from unittest.mock import patch from fritzconnection.core.exceptions import FritzConnectionException, FritzSecurityError -import pytest -from homeassistant.components import ssdp from homeassistant.components.device_tracker.const import ( CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME, @@ -16,9 +14,9 @@ ERROR_CANNOT_CONNECT, ERROR_UNKNOWN, ) -from homeassistant.components.ssdp import ATTR_UPNP_FRIENDLY_NAME, ATTR_UPNP_UDN +from homeassistant.components.ssdp import ATTR_UPNP_UDN from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_SSDP, SOURCE_USER -from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import ( RESULT_TYPE_ABORT, @@ -26,42 +24,15 @@ RESULT_TYPE_FORM, ) -from . import MOCK_CONFIG, FritzConnectionMock - -from tests.common import MockConfigEntry - -ATTR_HOST = "host" -ATTR_NEW_SERIAL_NUMBER = "NewSerialNumber" - -MOCK_HOST = "fake_host" -MOCK_IP = "192.168.178.1" -MOCK_SERIAL_NUMBER = "fake_serial_number" -MOCK_FIRMWARE_INFO = [True, "1.1.1"] - -MOCK_USER_DATA = MOCK_CONFIG[DOMAIN][CONF_DEVICES][0] -MOCK_DEVICE_INFO = { - ATTR_HOST: MOCK_HOST, - ATTR_NEW_SERIAL_NUMBER: MOCK_SERIAL_NUMBER, -} -MOCK_SSDP_DATA = ssdp.SsdpServiceInfo( - ssdp_usn="mock_usn", - ssdp_st="mock_st", - ssdp_location=f"https://{MOCK_IP}:12345/test", - upnp={ - ATTR_UPNP_FRIENDLY_NAME: "fake_name", - ATTR_UPNP_UDN: "uuid:only-a-test", - }, +from .const import ( + MOCK_FIRMWARE_INFO, + MOCK_IP, + MOCK_REQUEST, + MOCK_SSDP_DATA, + MOCK_USER_DATA, ) -MOCK_REQUEST = b'xxxxxxxxxxxxxxxxxxxxxxxx0Dial2App2HomeAuto2BoxAdmin2Phone2NAS2FakeFritzUser\n' - - -@pytest.fixture() -def fc_class_mock(): - """Fixture that sets up a mocked FritzConnection class.""" - with patch("fritzconnection.FritzConnection", autospec=True) as result: - result.return_value = FritzConnectionMock() - yield result +from tests.common import MockConfigEntry async def test_user(hass: HomeAssistant, fc_class_mock, mock_get_source_ip): From de36e964815a44922397d2eabca564480e462f6c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 28 Jan 2022 23:13:28 -0600 Subject: [PATCH 0068/1098] Add OUI for KL430 tplink light strip to discovery (#65159) --- homeassistant/components/tplink/manifest.json | 4 ++++ homeassistant/generated/dhcp.py | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/homeassistant/components/tplink/manifest.json b/homeassistant/components/tplink/manifest.json index 378435b9ec0f37..9464305cd169b5 100644 --- a/homeassistant/components/tplink/manifest.json +++ b/homeassistant/components/tplink/manifest.json @@ -25,6 +25,10 @@ "hostname": "k[lp]*", "macaddress": "403F8C*" }, + { + "hostname": "k[lp]*", + "macaddress": "C0C9E3*" + }, { "hostname": "ep*", "macaddress": "E848B8*" diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 6c9e0d874998c0..8875fb15b5b6f8 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -392,6 +392,11 @@ "hostname": "k[lp]*", "macaddress": "403F8C*" }, + { + "domain": "tplink", + "hostname": "k[lp]*", + "macaddress": "C0C9E3*" + }, { "domain": "tplink", "hostname": "ep*", From f585777e5629aeb9f6775500092c4936e09fd6ab Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 28 Jan 2022 23:13:41 -0600 Subject: [PATCH 0069/1098] Add dhcp discovery to oncue (#65160) --- homeassistant/components/oncue/manifest.json | 4 ++++ homeassistant/generated/dhcp.py | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/homeassistant/components/oncue/manifest.json b/homeassistant/components/oncue/manifest.json index cbe517dd98631c..190b5c790cc5a1 100644 --- a/homeassistant/components/oncue/manifest.json +++ b/homeassistant/components/oncue/manifest.json @@ -2,6 +2,10 @@ "domain": "oncue", "name": "Oncue by Kohler", "config_flow": true, + "dhcp": [{ + "hostname": "kohlergen*", + "macaddress": "00146F*" + }], "documentation": "https://www.home-assistant.io/integrations/oncue", "requirements": ["aiooncue==0.3.2"], "codeowners": ["@bdraco"], diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 8875fb15b5b6f8..277bc6169e9732 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -201,6 +201,11 @@ "domain": "nuki", "hostname": "nuki_bridge_*" }, + { + "domain": "oncue", + "hostname": "kohlergen*", + "macaddress": "00146F*" + }, { "domain": "overkiz", "hostname": "gateway*", From 4d0dbeb2b500a2fd9b59a73fb55fa888163d497f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 28 Jan 2022 23:13:54 -0600 Subject: [PATCH 0070/1098] Add additional roomba OUIs to DHCP discovery (#65161) --- homeassistant/components/roomba/manifest.json | 6 +++++- homeassistant/generated/dhcp.py | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/manifest.json b/homeassistant/components/roomba/manifest.json index 0c14c0189a0fc5..70053e8ee40e9a 100644 --- a/homeassistant/components/roomba/manifest.json +++ b/homeassistant/components/roomba/manifest.json @@ -13,7 +13,11 @@ { "hostname": "roomba-*", "macaddress": "80A589*" - } + }, + { + "hostname": "roomba-*", + "macaddress": "DCF505*" + } ], "iot_class": "local_push", "loggers": ["paho_mqtt", "roombapy"] diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 277bc6169e9732..0c7538f72665c2 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -255,6 +255,11 @@ "hostname": "roomba-*", "macaddress": "80A589*" }, + { + "domain": "roomba", + "hostname": "roomba-*", + "macaddress": "DCF505*" + }, { "domain": "samsungtv", "hostname": "tizen*" From 0e6c554b7056558c00a71856279600b7037a29e4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 28 Jan 2022 23:14:08 -0600 Subject: [PATCH 0071/1098] Add additional blink OUIs to DHCP discovery (#65162) --- homeassistant/components/blink/manifest.json | 6 +++++- homeassistant/generated/dhcp.py | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/blink/manifest.json b/homeassistant/components/blink/manifest.json index c4bc116b4a63b6..8282795a8e9873 100644 --- a/homeassistant/components/blink/manifest.json +++ b/homeassistant/components/blink/manifest.json @@ -8,7 +8,11 @@ { "hostname": "blink*", "macaddress": "B85F98*" - } + }, + { + "hostname": "blink*", + "macaddress": "00037F*" + } ], "config_flow": true, "iot_class": "cloud_polling", diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 0c7538f72665c2..f05a7f73e50af2 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -46,6 +46,11 @@ "hostname": "blink*", "macaddress": "B85F98*" }, + { + "domain": "blink", + "hostname": "blink*", + "macaddress": "00037F*" + }, { "domain": "broadlink", "macaddress": "34EA34*" From e85e91bdb02d5c39070b716e241831e75f44f0f0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 28 Jan 2022 23:14:30 -0600 Subject: [PATCH 0072/1098] Fix uncaught exception during isy994 dhcp discovery with ignored entry (#65165) --- .../components/isy994/config_flow.py | 2 ++ tests/components/isy994/test_config_flow.py | 31 ++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/isy994/config_flow.py b/homeassistant/components/isy994/config_flow.py index 7289f7d416e0b3..4e700df24cbdfc 100644 --- a/homeassistant/components/isy994/config_flow.py +++ b/homeassistant/components/isy994/config_flow.py @@ -158,6 +158,8 @@ async def _async_set_unique_id_or_update(self, isy_mac, ip_address, port) -> Non existing_entry = await self.async_set_unique_id(isy_mac) if not existing_entry: return + if existing_entry.source == config_entries.SOURCE_IGNORE: + raise data_entry_flow.AbortFlow("already_configured") parsed_url = urlparse(existing_entry.data[CONF_HOST]) if parsed_url.hostname != ip_address: new_netloc = ip_address diff --git a/tests/components/isy994/test_config_flow.py b/tests/components/isy994/test_config_flow.py index e9a4c5dc4fb3c2..b16f5c0070d570 100644 --- a/tests/components/isy994/test_config_flow.py +++ b/tests/components/isy994/test_config_flow.py @@ -16,7 +16,12 @@ ISY_URL_POSTFIX, UDN_UUID_PREFIX, ) -from homeassistant.config_entries import SOURCE_DHCP, SOURCE_IMPORT, SOURCE_SSDP +from homeassistant.config_entries import ( + SOURCE_DHCP, + SOURCE_IGNORE, + SOURCE_IMPORT, + SOURCE_SSDP, +) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant @@ -595,3 +600,27 @@ async def test_form_dhcp_existing_entry_preserves_port(hass: HomeAssistant): assert result["reason"] == "already_configured" assert entry.data[CONF_HOST] == f"http://1.2.3.4:1443{ISY_URL_POSTFIX}" assert entry.data[CONF_USERNAME] == "bob" + + +async def test_form_dhcp_existing_ignored_entry(hass: HomeAssistant): + """Test we handled an ignored entry from dhcp.""" + + entry = MockConfigEntry( + domain=DOMAIN, data={}, unique_id=MOCK_UUID, source=SOURCE_IGNORE + ) + entry.add_to_hass(hass) + + with patch(PATCH_CONNECTION, return_value=MOCK_CONFIG_RESPONSE): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_DHCP}, + data=dhcp.DhcpServiceInfo( + ip="1.2.3.4", + hostname="isy994-ems", + macaddress=MOCK_MAC, + ), + ) + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" From 3cde472e43642bd7850d606eb1555695f10af009 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Sat, 29 Jan 2022 06:14:51 +0100 Subject: [PATCH 0073/1098] Fix status for Fritz device tracker (#65152) --- homeassistant/components/fritz/common.py | 30 +++++++++++++----------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index b86a435e758f4d..9d1c45438572eb 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -343,14 +343,15 @@ def scan_devices(self, now: datetime | None = None) -> None: for interf in node["node_interfaces"]: dev_mac = interf["mac_address"] + + if dev_mac not in hosts: + continue + + dev_info: Device = hosts[dev_mac] + for link in interf["node_links"]: intf = mesh_intf.get(link["node_interface_1_uid"]) - if ( - intf is not None - and link["state"] == "CONNECTED" - and dev_mac in hosts - ): - dev_info: Device = hosts[dev_mac] + if intf is not None: if intf["op_mode"] != "AP_GUEST": dev_info.wan_access = not self.connection.call_action( "X_AVM-DE_HostFilter:1", @@ -361,14 +362,15 @@ def scan_devices(self, now: datetime | None = None) -> None: dev_info.connected_to = intf["device"] dev_info.connection_type = intf["type"] dev_info.ssid = intf.get("ssid") - - if dev_mac in self._devices: - self._devices[dev_mac].update(dev_info, consider_home) - else: - device = FritzDevice(dev_mac, dev_info.name) - device.update(dev_info, consider_home) - self._devices[dev_mac] = device - new_device = True + _LOGGER.debug("Client dev_info: %s", dev_info) + + if dev_mac in self._devices: + self._devices[dev_mac].update(dev_info, consider_home) + else: + device = FritzDevice(dev_mac, dev_info.name) + device.update(dev_info, consider_home) + self._devices[dev_mac] = device + new_device = True dispatcher_send(self.hass, self.signal_device_update) if new_device: From 049fc8a945a2956d0a056855b297a73ce8a1f4c6 Mon Sep 17 00:00:00 2001 From: Simon Hansen <67142049+DurgNomis-drol@users.noreply.github.com> Date: Sat, 29 Jan 2022 10:41:26 +0100 Subject: [PATCH 0074/1098] Add config flow to ISS integration (#64987) * Initial commit * Wrong flowhandler name * Add config flow tests * Tests for config flow * ... * Add test for no coordinates * ... * Update homeassistant/components/iss/config_flow.py Co-authored-by: Shay Levy * Update homeassistant/components/iss/config_flow.py * Update homeassistant/components/iss/binary_sensor.py Co-authored-by: Shay Levy * Add myself as codeowner Co-authored-by: Shay Levy --- .coveragerc | 1 + CODEOWNERS | 2 + homeassistant/components/iss/__init__.py | 26 +++++++ homeassistant/components/iss/binary_sensor.py | 41 +++++++--- homeassistant/components/iss/config_flow.py | 48 ++++++++++++ homeassistant/components/iss/const.py | 3 + homeassistant/components/iss/manifest.json | 3 +- homeassistant/components/iss/strings.json | 16 ++++ .../components/iss/translations/en.json | 16 ++++ homeassistant/generated/config_flows.py | 1 + requirements_test_all.txt | 3 + tests/components/iss/__init__.py | 1 + tests/components/iss/test_config_flow.py | 78 +++++++++++++++++++ 13 files changed, 228 insertions(+), 11 deletions(-) create mode 100644 homeassistant/components/iss/config_flow.py create mode 100644 homeassistant/components/iss/const.py create mode 100644 homeassistant/components/iss/strings.json create mode 100644 homeassistant/components/iss/translations/en.json create mode 100644 tests/components/iss/__init__.py create mode 100644 tests/components/iss/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index e7f99d9982ee2e..426114c65767da 100644 --- a/.coveragerc +++ b/.coveragerc @@ -520,6 +520,7 @@ omit = homeassistant/components/iperf3/* homeassistant/components/iqvia/* homeassistant/components/irish_rail_transport/sensor.py + homeassistant/components/iss/__init__.py homeassistant/components/iss/binary_sensor.py homeassistant/components/isy994/__init__.py homeassistant/components/isy994/binary_sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index e5b6ed9798a7ff..cb2573eedb824c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -467,6 +467,8 @@ tests/components/iqvia/* @bachya homeassistant/components/irish_rail_transport/* @ttroy50 homeassistant/components/islamic_prayer_times/* @engrbm87 tests/components/islamic_prayer_times/* @engrbm87 +homeassistant/components/iss/* @DurgNomis-drol +tests/components/iss/* @DurgNomis-drol homeassistant/components/isy994/* @bdraco @shbatm tests/components/isy994/* @bdraco @shbatm homeassistant/components/izone/* @Swamp-Ig diff --git a/homeassistant/components/iss/__init__.py b/homeassistant/components/iss/__init__.py index 51487bdfaf2e4f..af44e621a7fb41 100644 --- a/homeassistant/components/iss/__init__.py +++ b/homeassistant/components/iss/__init__.py @@ -1 +1,27 @@ """The iss component.""" +from __future__ import annotations + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant + +from .const import DOMAIN + +PLATFORMS = [Platform.BINARY_SENSOR] + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up this integration using UI.""" + + hass.data.setdefault(DOMAIN, {}) + + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Handle removal of an entry.""" + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + del hass.data[DOMAIN] + return unload_ok diff --git a/homeassistant/components/iss/binary_sensor.py b/homeassistant/components/iss/binary_sensor.py index eab1294ad1023c..5272053a517073 100644 --- a/homeassistant/components/iss/binary_sensor.py +++ b/homeassistant/components/iss/binary_sensor.py @@ -1,4 +1,4 @@ -"""Support for International Space Station binary sensor.""" +"""Support for iss binary sensor.""" from __future__ import annotations from datetime import timedelta @@ -10,6 +10,7 @@ import voluptuous as vol from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( ATTR_LATITUDE, ATTR_LONGITUDE, @@ -22,6 +23,8 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util import Throttle +from .const import DOMAIN + _LOGGER = logging.getLogger(__name__) ATTR_ISS_NEXT_RISE = "next_rise" @@ -46,22 +49,40 @@ def setup_platform( add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up the ISS binary sensor.""" - if None in (hass.config.latitude, hass.config.longitude): - _LOGGER.error("Latitude or longitude not set in Home Assistant config") - return + """Import ISS configuration from yaml.""" + _LOGGER.warning( + "Configuration of the iss platform in YAML is deprecated and will be " + "removed in Home Assistant 2022.5; Your existing configuration " + "has been imported into the UI automatically and can be safely removed " + "from your configuration.yaml file" + ) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, + ) + ) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the sensor platform.""" + + name = entry.data.get(CONF_NAME, DEFAULT_NAME) + show_on_map = entry.data.get(CONF_SHOW_ON_MAP, False) try: iss_data = IssData(hass.config.latitude, hass.config.longitude) - iss_data.update() + await hass.async_add_executor_job(iss_data.update) except HTTPError as error: _LOGGER.error(error) return - name = config.get(CONF_NAME) - show_on_map = config.get(CONF_SHOW_ON_MAP) - - add_entities([IssBinarySensor(iss_data, name, show_on_map)], True) + async_add_entities([IssBinarySensor(iss_data, name, show_on_map)], True) class IssBinarySensor(BinarySensorEntity): diff --git a/homeassistant/components/iss/config_flow.py b/homeassistant/components/iss/config_flow.py new file mode 100644 index 00000000000000..e1703b54acbead --- /dev/null +++ b/homeassistant/components/iss/config_flow.py @@ -0,0 +1,48 @@ +"""Config flow to configure iss component.""" + +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_NAME, CONF_SHOW_ON_MAP +from homeassistant.data_entry_flow import FlowResult + +from .const import DOMAIN + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Config flow for iss component.""" + + VERSION = 1 + + async def async_step_user(self, user_input=None) -> FlowResult: + """Handle a flow initialized by the user.""" + # Check if already configured + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + + # Check if location have been defined. + if not self.hass.config.latitude and not self.hass.config.longitude: + return self.async_abort(reason="latitude_longitude_not_defined") + + if user_input is not None: + return self.async_create_entry( + title="International Space Station", data=user_input + ) + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Optional(CONF_SHOW_ON_MAP, default=False): bool, + } + ), + ) + + async def async_step_import(self, conf: dict) -> FlowResult: + """Import a configuration from configuration.yaml.""" + return await self.async_step_user( + user_input={ + CONF_NAME: conf[CONF_NAME], + CONF_SHOW_ON_MAP: conf[CONF_SHOW_ON_MAP], + } + ) diff --git a/homeassistant/components/iss/const.py b/homeassistant/components/iss/const.py new file mode 100644 index 00000000000000..3d240041b67f83 --- /dev/null +++ b/homeassistant/components/iss/const.py @@ -0,0 +1,3 @@ +"""Constants for iss.""" + +DOMAIN = "iss" diff --git a/homeassistant/components/iss/manifest.json b/homeassistant/components/iss/manifest.json index 740dbbb9ff43ef..bd1aa967f070fb 100644 --- a/homeassistant/components/iss/manifest.json +++ b/homeassistant/components/iss/manifest.json @@ -1,9 +1,10 @@ { "domain": "iss", + "config_flow": true, "name": "International Space Station (ISS)", "documentation": "https://www.home-assistant.io/integrations/iss", "requirements": ["pyiss==1.0.1"], - "codeowners": [], + "codeowners": ["@DurgNomis-drol"], "iot_class": "cloud_polling", "loggers": ["pyiss"] } diff --git a/homeassistant/components/iss/strings.json b/homeassistant/components/iss/strings.json new file mode 100644 index 00000000000000..cdbaecbeba5959 --- /dev/null +++ b/homeassistant/components/iss/strings.json @@ -0,0 +1,16 @@ +{ + "config": { + "step": { + "user": { + "description": "Do you want to configure the Internation Space Station?", + "data": { + "show_on_map": "Show on map?" + } + } + }, + "abort": { + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", + "latitude_longitude_not_defined": "Latitude and longitude is not defind in Home Assistant." + } + } + } \ No newline at end of file diff --git a/homeassistant/components/iss/translations/en.json b/homeassistant/components/iss/translations/en.json new file mode 100644 index 00000000000000..13483418ffa6a2 --- /dev/null +++ b/homeassistant/components/iss/translations/en.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "latitude_longitude_not_defined": "Latitude and longitude is not defind in Home Assistant.", + "single_instance_allowed": "Already configured. Only a single configuration possible." + }, + "step": { + "user": { + "data": { + "show_on_map": "Show on map?" + }, + "description": "Do you want to configure the Internation Space Station?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index bf64bc4a51c2db..365aa903b09bbe 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -159,6 +159,7 @@ "ipp", "iqvia", "islamic_prayer_times", + "iss", "isy994", "izone", "jellyfin", diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 83086ebb78635f..52b93948e43d4d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -998,6 +998,9 @@ pyipp==0.11.0 # homeassistant.components.iqvia pyiqvia==2021.11.0 +# homeassistant.components.iss +pyiss==1.0.1 + # homeassistant.components.isy994 pyisy==3.0.1 diff --git a/tests/components/iss/__init__.py b/tests/components/iss/__init__.py new file mode 100644 index 00000000000000..7fa75a42ccb654 --- /dev/null +++ b/tests/components/iss/__init__.py @@ -0,0 +1 @@ +"""Tests for the iss component.""" diff --git a/tests/components/iss/test_config_flow.py b/tests/components/iss/test_config_flow.py new file mode 100644 index 00000000000000..a20a8729f55387 --- /dev/null +++ b/tests/components/iss/test_config_flow.py @@ -0,0 +1,78 @@ +"""Test iss config flow.""" +from unittest.mock import patch + +from homeassistant import data_entry_flow +from homeassistant.components.iss.binary_sensor import DEFAULT_NAME +from homeassistant.components.iss.const import DOMAIN +from homeassistant.config import async_process_ha_core_config +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from homeassistant.const import CONF_NAME, CONF_SHOW_ON_MAP + +from tests.common import MockConfigEntry + + +async def test_import(hass): + """Test entry will be imported.""" + + imported_config = {CONF_NAME: DEFAULT_NAME, CONF_SHOW_ON_MAP: False} + + with patch("homeassistant.components.iss.async_setup_entry", return_value=True): + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=imported_config + ) + assert result.get("type") == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result.get("result").data == imported_config + + +async def test_create_entry(hass): + """Test we can finish a config flow.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + + assert result.get("type") == data_entry_flow.RESULT_TYPE_FORM + assert result.get("step_id") == SOURCE_USER + + with patch("homeassistant.components.iss.async_setup_entry", return_value=True): + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_SHOW_ON_MAP: True}, + ) + + assert result.get("type") == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result.get("result").data[CONF_SHOW_ON_MAP] is True + + +async def test_integration_already_exists(hass): + """Test we only allow a single config flow.""" + + MockConfigEntry( + domain=DOMAIN, + data={}, + ).add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data={CONF_SHOW_ON_MAP: False} + ) + + assert result.get("type") == data_entry_flow.RESULT_TYPE_ABORT + assert result.get("reason") == "single_instance_allowed" + + +async def test_abort_no_home(hass): + """Test we don't create an entry if no coordinates are set.""" + + await async_process_ha_core_config( + hass, + {"latitude": 0.0, "longitude": 0.0}, + ) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data={CONF_SHOW_ON_MAP: False} + ) + + assert result.get("type") == data_entry_flow.RESULT_TYPE_ABORT + assert result.get("reason") == "latitude_longitude_not_defined" From 8bd7519ea52d6eaf764eaf1daf47e7902b459911 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sat, 29 Jan 2022 14:01:46 +0100 Subject: [PATCH 0075/1098] Aqara restore door sensor state on start (#65128) * restore door sensor state on start * fix import * fix issues * also fix Natgas, WaterLeak and Smoke sensors * remove unnesesary async_schedule_update_ha_state --- .../components/xiaomi_aqara/binary_sensor.py | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/xiaomi_aqara/binary_sensor.py b/homeassistant/components/xiaomi_aqara/binary_sensor.py index 13d65cb21f48d3..ae4059728fe6b6 100644 --- a/homeassistant/components/xiaomi_aqara/binary_sensor.py +++ b/homeassistant/components/xiaomi_aqara/binary_sensor.py @@ -9,6 +9,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_call_later +from homeassistant.helpers.restore_state import RestoreEntity from . import XiaomiDevice from .const import DOMAIN, GATEWAYS_KEY @@ -181,6 +182,11 @@ def extra_state_attributes(self): attrs.update(super().extra_state_attributes) return attrs + async def async_added_to_hass(self) -> None: + """Handle entity which will be added.""" + await super().async_added_to_hass() + self._state = False + def parse_data(self, data, raw_data): """Parse data sent by gateway.""" if DENSITY in data: @@ -232,6 +238,11 @@ def _async_set_no_motion(self, now): self._state = False self.async_write_ha_state() + async def async_added_to_hass(self) -> None: + """Handle entity which will be added.""" + await super().async_added_to_hass() + self._state = False + def parse_data(self, data, raw_data): """Parse data sent by gateway. @@ -293,7 +304,7 @@ def parse_data(self, data, raw_data): return True -class XiaomiDoorSensor(XiaomiBinarySensor): +class XiaomiDoorSensor(XiaomiBinarySensor, RestoreEntity): """Representation of a XiaomiDoorSensor.""" def __init__(self, device, xiaomi_hub, config_entry): @@ -319,6 +330,15 @@ def extra_state_attributes(self): attrs.update(super().extra_state_attributes) return attrs + async def async_added_to_hass(self) -> None: + """Handle entity which will be added.""" + await super().async_added_to_hass() + state = await self.async_get_last_state() + if state is None: + return + + self._state = state.state == "on" + def parse_data(self, data, raw_data): """Parse data sent by gateway.""" self._should_poll = False @@ -362,6 +382,11 @@ def __init__(self, device, xiaomi_hub, config_entry): config_entry, ) + async def async_added_to_hass(self) -> None: + """Handle entity which will be added.""" + await super().async_added_to_hass() + self._state = False + def parse_data(self, data, raw_data): """Parse data sent by gateway.""" self._should_poll = False @@ -400,6 +425,11 @@ def extra_state_attributes(self): attrs.update(super().extra_state_attributes) return attrs + async def async_added_to_hass(self) -> None: + """Handle entity which will be added.""" + await super().async_added_to_hass() + self._state = False + def parse_data(self, data, raw_data): """Parse data sent by gateway.""" if DENSITY in data: From 98aa69fdafcee13db1d99e9083ac862c4b501f00 Mon Sep 17 00:00:00 2001 From: Marvin Wichmann Date: Sat, 29 Jan 2022 14:32:12 +0100 Subject: [PATCH 0076/1098] Fix KNX Expose for strings longer than 14 bytes (#63026) * Fix KNX Expose for too long strings * Fix tests * Catch exception and avoid error during config entry setup for exposures * Properly catch exceptions in knx expose * Fix pylint * Fix CI * Add test for conversion error --- homeassistant/components/knx/expose.py | 23 +++++++-- tests/components/knx/test_expose.py | 69 +++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/knx/expose.py b/homeassistant/components/knx/expose.py index 34949b8c0b89f5..0963ec3be4349a 100644 --- a/homeassistant/components/knx/expose.py +++ b/homeassistant/components/knx/expose.py @@ -2,10 +2,12 @@ from __future__ import annotations from collections.abc import Callable +import logging from xknx import XKNX from xknx.devices import DateTime, ExposeSensor -from xknx.dpt import DPTNumeric +from xknx.dpt import DPTNumeric, DPTString +from xknx.exceptions import ConversionError from xknx.remote_value import RemoteValueSensor from homeassistant.const import ( @@ -22,6 +24,8 @@ from .const import KNX_ADDRESS from .schema import ExposeSchema +_LOGGER = logging.getLogger(__name__) + @callback def create_knx_exposure( @@ -101,7 +105,10 @@ def _init_expose_state(self) -> None: """Initialize state of the exposure.""" init_state = self.hass.states.get(self.entity_id) state_value = self._get_expose_value(init_state) - self.device.sensor_value.value = state_value + try: + self.device.sensor_value.value = state_value + except ConversionError: + _LOGGER.exception("Error during sending of expose sensor value") @callback def shutdown(self) -> None: @@ -132,6 +139,13 @@ def _get_expose_value(self, state: State | None) -> StateType: and issubclass(self.device.sensor_value.dpt_class, DPTNumeric) ): return float(value) + if ( + value is not None + and isinstance(self.device.sensor_value, RemoteValueSensor) + and issubclass(self.device.sensor_value.dpt_class, DPTString) + ): + # DPT 16.000 only allows up to 14 Bytes + return str(value)[:14] return value async def _async_entity_changed(self, event: Event) -> None: @@ -148,7 +162,10 @@ async def _async_entity_changed(self, event: Event) -> None: async def _async_set_knx_value(self, value: StateType) -> None: """Set new value on xknx ExposeSensor.""" - await self.device.set(value) + try: + await self.device.set(value) + except ConversionError: + _LOGGER.exception("Error during sending of expose sensor value") class KNXExposeTime: diff --git a/tests/components/knx/test_expose.py b/tests/components/knx/test_expose.py index cbe174127c412a..e5030eef461751 100644 --- a/tests/components/knx/test_expose.py +++ b/tests/components/knx/test_expose.py @@ -128,6 +128,73 @@ async def test_expose_attribute_with_default(hass: HomeAssistant, knx: KNXTestKi await knx.assert_write("1/1/8", (0,)) +async def test_expose_string(hass: HomeAssistant, knx: KNXTestKit): + """Test an expose to send string values of up to 14 bytes only.""" + + entity_id = "fake.entity" + attribute = "fake_attribute" + await knx.setup_integration( + { + CONF_KNX_EXPOSE: { + CONF_TYPE: "string", + KNX_ADDRESS: "1/1/8", + CONF_ENTITY_ID: entity_id, + CONF_ATTRIBUTE: attribute, + ExposeSchema.CONF_KNX_EXPOSE_DEFAULT: "Test", + } + }, + ) + assert not hass.states.async_all() + + # Before init default value shall be sent as response + await knx.receive_read("1/1/8") + await knx.assert_response( + "1/1/8", (84, 101, 115, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + ) + + # Change attribute; keep state + hass.states.async_set( + entity_id, + "on", + {attribute: "This is a very long string that is larger than 14 bytes"}, + ) + await knx.assert_write( + "1/1/8", (84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 118, 101, 114, 121) + ) + + +async def test_expose_conversion_exception(hass: HomeAssistant, knx: KNXTestKit): + """Test expose throws exception.""" + + entity_id = "fake.entity" + attribute = "fake_attribute" + await knx.setup_integration( + { + CONF_KNX_EXPOSE: { + CONF_TYPE: "percent", + KNX_ADDRESS: "1/1/8", + CONF_ENTITY_ID: entity_id, + CONF_ATTRIBUTE: attribute, + ExposeSchema.CONF_KNX_EXPOSE_DEFAULT: 1, + } + }, + ) + assert not hass.states.async_all() + + # Before init default value shall be sent as response + await knx.receive_read("1/1/8") + await knx.assert_response("1/1/8", (3,)) + + # Change attribute: Expect no exception + hass.states.async_set( + entity_id, + "on", + {attribute: 101}, + ) + + await knx.assert_no_telegram() + + @patch("time.localtime") async def test_expose_with_date(localtime, hass: HomeAssistant, knx: KNXTestKit): """Test an expose with a date.""" @@ -138,7 +205,7 @@ async def test_expose_with_date(localtime, hass: HomeAssistant, knx: KNXTestKit) CONF_TYPE: "datetime", KNX_ADDRESS: "1/1/8", } - }, + } ) assert not hass.states.async_all() From c25431750e17a7426bc913ee6afd37c5018a8d30 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 29 Jan 2022 14:34:14 +0100 Subject: [PATCH 0077/1098] Bump dependency to v31 which makes has_relay more robust (#65180) --- homeassistant/components/unifi/manifest.json | 2 +- homeassistant/components/unifi/switch.py | 1 + requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/unifi/manifest.json b/homeassistant/components/unifi/manifest.json index 0739138ecc7e3c..79c8453431d068 100644 --- a/homeassistant/components/unifi/manifest.json +++ b/homeassistant/components/unifi/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/unifi", "requirements": [ - "aiounifi==30" + "aiounifi==31" ], "codeowners": [ "@Kane610" diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index faa3d3a22f7c77..9151c81543a384 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -205,6 +205,7 @@ def add_outlet_entities(controller, async_add_entities, devices): or not (device := controller.api.devices[mac]).outlet_table ): continue + for outlet in device.outlets.values(): if outlet.has_relay: switches.append(UniFiOutletSwitch(device, controller, outlet.index)) diff --git a/requirements_all.txt b/requirements_all.txt index 3a3ae91fe2b1b5..6cb6cfe7e666eb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -269,7 +269,7 @@ aiosyncthing==0.5.1 aiotractive==0.5.2 # homeassistant.components.unifi -aiounifi==30 +aiounifi==31 # homeassistant.components.vlc_telnet aiovlc==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 52b93948e43d4d..44b8d2849824ed 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -204,7 +204,7 @@ aiosyncthing==0.5.1 aiotractive==0.5.2 # homeassistant.components.unifi -aiounifi==30 +aiounifi==31 # homeassistant.components.vlc_telnet aiovlc==0.1.0 From 4a042e7d73414177dcf44e5d34ee776a903b3f38 Mon Sep 17 00:00:00 2001 From: Robert Hillis Date: Sat, 29 Jan 2022 09:01:00 -0500 Subject: [PATCH 0078/1098] Bump pyefergy to 22.1.1 (#65156) * Bump pyefergy to 22.1.0 * uno mas * uno mas * uno mas --- homeassistant/components/efergy/manifest.json | 2 +- homeassistant/components/efergy/sensor.py | 22 ++++++++++++------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/efergy/__init__.py | 20 ++++++++--------- 5 files changed, 27 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/efergy/manifest.json b/homeassistant/components/efergy/manifest.json index d3482738450d70..fc90591cae639b 100644 --- a/homeassistant/components/efergy/manifest.json +++ b/homeassistant/components/efergy/manifest.json @@ -3,7 +3,7 @@ "name": "Efergy", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/efergy", - "requirements": ["pyefergy==0.1.5"], + "requirements": ["pyefergy==22.1.1"], "codeowners": ["@tkdrob"], "iot_class": "cloud_polling", "loggers": ["iso4217", "pyefergy"] diff --git a/homeassistant/components/efergy/sensor.py b/homeassistant/components/efergy/sensor.py index 21d4002bcdb02d..00a10b713d28f2 100644 --- a/homeassistant/components/efergy/sensor.py +++ b/homeassistant/components/efergy/sensor.py @@ -3,8 +3,10 @@ import logging from re import sub +from typing import cast -from pyefergy import Efergy, exceptions +from pyefergy import Efergy +from pyefergy.exceptions import ConnectError, DataError, ServiceError from homeassistant.components.sensor import ( SensorDeviceClass, @@ -16,6 +18,7 @@ from homeassistant.const import ENERGY_KILO_WATT_HOUR, POWER_WATT from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_platform +from homeassistant.helpers.typing import StateType from . import EfergyEntity from .const import CONF_CURRENT_VALUES, DATA_KEY_API, DOMAIN @@ -123,8 +126,8 @@ async def async_setup_entry( ) ) else: - description.entity_registry_enabled_default = len(api.info["sids"]) > 1 - for sid in api.info["sids"]: + description.entity_registry_enabled_default = len(api.sids) > 1 + for sid in api.sids: sensors.append( EfergySensor( api, @@ -146,14 +149,16 @@ def __init__( server_unique_id: str, period: str | None = None, currency: str | None = None, - sid: str = "", + sid: int | None = None, ) -> None: """Initialize the sensor.""" super().__init__(api, server_unique_id) self.entity_description = description if description.key == CONF_CURRENT_VALUES: - self._attr_name = f"{description.name}_{sid}" - self._attr_unique_id = f"{server_unique_id}/{description.key}_{sid}" + self._attr_name = f"{description.name}_{'' if sid is None else sid}" + self._attr_unique_id = ( + f"{server_unique_id}/{description.key}_{'' if sid is None else sid}" + ) if "cost" in description.key: self._attr_native_unit_of_measurement = currency self.sid = sid @@ -162,10 +167,11 @@ def __init__( async def async_update(self) -> None: """Get the Efergy monitor data from the web service.""" try: - self._attr_native_value = await self.api.async_get_reading( + data = await self.api.async_get_reading( self.entity_description.key, period=self.period, sid=self.sid ) - except (exceptions.DataError, exceptions.ConnectError) as ex: + self._attr_native_value = cast(StateType, data) + except (ConnectError, DataError, ServiceError) as ex: if self._attr_available: self._attr_available = False _LOGGER.error("Error getting data: %s", ex) diff --git a/requirements_all.txt b/requirements_all.txt index 6cb6cfe7e666eb..157acea0f53956 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1491,7 +1491,7 @@ pyeconet==0.1.14 pyedimax==0.2.1 # homeassistant.components.efergy -pyefergy==0.1.5 +pyefergy==22.1.1 # homeassistant.components.eight_sleep pyeight==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 44b8d2849824ed..07ce89ccd053c1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -920,7 +920,7 @@ pydispatcher==2.0.5 pyeconet==0.1.14 # homeassistant.components.efergy -pyefergy==0.1.5 +pyefergy==22.1.1 # homeassistant.components.everlights pyeverlights==0.1.0 diff --git a/tests/components/efergy/__init__.py b/tests/components/efergy/__init__.py index 4c26e25e5f48df..ddddc56f4e4a62 100644 --- a/tests/components/efergy/__init__.py +++ b/tests/components/efergy/__init__.py @@ -57,9 +57,9 @@ async def mock_responses( """Mock responses from Efergy.""" base_url = "https://engage.efergy.com/mobile_proxy/" api = Efergy( - token, session=async_get_clientsession(hass), utc_offset=hass.config.time_zone + token, session=async_get_clientsession(hass), utc_offset="America/New_York" ) - offset = api._utc_offset # pylint: disable=protected-access + assert api._utc_offset == 300 if error: aioclient_mock.get( f"{base_url}getInstant?token={token}", @@ -75,19 +75,19 @@ async def mock_responses( text=load_fixture("efergy/instant.json"), ) aioclient_mock.get( - f"{base_url}getEnergy?token={token}&offset={offset}&period=day", + f"{base_url}getEnergy?period=day", text=load_fixture("efergy/daily_energy.json"), ) aioclient_mock.get( - f"{base_url}getEnergy?token={token}&offset={offset}&period=week", + f"{base_url}getEnergy?period=week", text=load_fixture("efergy/weekly_energy.json"), ) aioclient_mock.get( - f"{base_url}getEnergy?token={token}&offset={offset}&period=month", + f"{base_url}getEnergy?period=month", text=load_fixture("efergy/monthly_energy.json"), ) aioclient_mock.get( - f"{base_url}getEnergy?token={token}&offset={offset}&period=year", + f"{base_url}getEnergy?period=year", text=load_fixture("efergy/yearly_energy.json"), ) aioclient_mock.get( @@ -95,19 +95,19 @@ async def mock_responses( text=load_fixture("efergy/budget.json"), ) aioclient_mock.get( - f"{base_url}getCost?token={token}&offset={offset}&period=day", + f"{base_url}getCost?period=day", text=load_fixture("efergy/daily_cost.json"), ) aioclient_mock.get( - f"{base_url}getCost?token={token}&offset={offset}&period=week", + f"{base_url}getCost?period=week", text=load_fixture("efergy/weekly_cost.json"), ) aioclient_mock.get( - f"{base_url}getCost?token={token}&offset={offset}&period=month", + f"{base_url}getCost?period=month", text=load_fixture("efergy/monthly_cost.json"), ) aioclient_mock.get( - f"{base_url}getCost?token={token}&offset={offset}&period=year", + f"{base_url}getCost?period=year", text=load_fixture("efergy/yearly_cost.json"), ) if token == TOKEN: From d770a548811234f859253b59c87e8446fa6dd02e Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sat, 29 Jan 2022 16:13:59 +0100 Subject: [PATCH 0079/1098] Minor refactoring of cast media_player (#65125) --- homeassistant/components/cast/media_player.py | 87 ++++++++++--------- 1 file changed, 48 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index e966e98c3f13b8..6cca4cfa20b5d6 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -4,7 +4,6 @@ import asyncio from contextlib import suppress from datetime import datetime, timedelta -import functools as ft import json import logging from urllib.parse import quote @@ -461,33 +460,10 @@ def media_seek(self, position): media_controller = self._media_controller() media_controller.seek(position) - async def async_browse_media(self, media_content_type=None, media_content_id=None): - """Implement the websocket media browsing helper.""" - kwargs = {} + async def _async_root_payload(self, content_filter): + """Generate root node.""" children = [] - - if self._chromecast.cast_type == pychromecast.const.CAST_TYPE_AUDIO: - kwargs["content_filter"] = lambda item: item.media_content_type.startswith( - "audio/" - ) - - if media_content_id is not None: - if plex.is_plex_media_id(media_content_id): - return await plex.async_browse_media( - self.hass, - media_content_type, - media_content_id, - platform=CAST_DOMAIN, - ) - return await media_source.async_browse_media( - self.hass, media_content_id, **kwargs - ) - - if media_content_type == "plex": - return await plex.async_browse_media( - self.hass, None, None, platform=CAST_DOMAIN - ) - + # Add external sources if "plex" in self.hass.config.components: children.append( BrowseMedia( @@ -501,15 +477,17 @@ async def async_browse_media(self, media_content_type=None, media_content_id=Non ) ) + # Add local media source try: result = await media_source.async_browse_media( - self.hass, media_content_id, **kwargs + self.hass, None, content_filter=content_filter ) children.append(result) except BrowseError: if not children: raise + # If there's only one media source, resolve it if len(children) == 1: return await self.async_browse_media( children[0].media_content_type, @@ -526,6 +504,34 @@ async def async_browse_media(self, media_content_type=None, media_content_id=Non children=children, ) + async def async_browse_media(self, media_content_type=None, media_content_id=None): + """Implement the websocket media browsing helper.""" + content_filter = None + + if self._chromecast.cast_type == pychromecast.const.CAST_TYPE_AUDIO: + + def audio_content_filter(item): + """Filter non audio content.""" + return item.media_content_type.startswith("audio/") + + content_filter = audio_content_filter + + if media_content_id is None: + return await self._async_root_payload(content_filter) + + if plex.is_plex_media_id(media_content_id): + return await plex.async_browse_media( + self.hass, media_content_type, media_content_id, platform=CAST_DOMAIN + ) + if media_content_type == "plex": + return await plex.async_browse_media( + self.hass, None, None, platform=CAST_DOMAIN + ) + + return await media_source.async_browse_media( + self.hass, media_content_id, content_filter=content_filter + ) + async def async_play_media(self, media_type, media_id, **kwargs): """Play a piece of media.""" # Handle media_source @@ -547,12 +553,6 @@ async def async_play_media(self, media_type, media_id, **kwargs): hass_url = get_url(self.hass, prefer_external=True) media_id = f"{hass_url}{media_id}" - await self.hass.async_add_executor_job( - ft.partial(self.play_media, media_type, media_id, **kwargs) - ) - - def play_media(self, media_type, media_id, **kwargs): - """Play media from a URL.""" extra = kwargs.get(ATTR_MEDIA_EXTRA, {}) metadata = extra.get("metadata") @@ -571,7 +571,9 @@ def play_media(self, media_type, media_id, **kwargs): if "app_id" in app_data: app_id = app_data.pop("app_id") _LOGGER.info("Starting Cast app by ID %s", app_id) - self._chromecast.start_app(app_id) + await self.hass.async_add_executor_job( + self._chromecast.start_app, app_id + ) if app_data: _LOGGER.warning( "Extra keys %s were ignored. Please use app_name to cast media", @@ -581,21 +583,28 @@ def play_media(self, media_type, media_id, **kwargs): app_name = app_data.pop("app_name") try: - quick_play(self._chromecast, app_name, app_data) + await self.hass.async_add_executor_job( + quick_play, self._chromecast, app_name, app_data + ) except NotImplementedError: _LOGGER.error("App %s not supported", app_name) + # Handle plex elif media_id and media_id.startswith(PLEX_URI_SCHEME): media_id = media_id[len(PLEX_URI_SCHEME) :] - media = lookup_plex_media(self.hass, media_type, media_id) + media = await self.hass.async_add_executor_job( + lookup_plex_media, self.hass, media_type, media_id + ) if media is None: return controller = PlexController() self._chromecast.register_handler(controller) - controller.play_media(media) + await self.hass.async_add_executor_job(controller.play_media, media) else: app_data = {"media_id": media_id, "media_type": media_type, **extra} - quick_play(self._chromecast, "default_media_receiver", app_data) + await self.hass.async_add_executor_job( + quick_play, self._chromecast, "default_media_receiver", app_data + ) def _media_status(self): """ From cc6b0cc84394f78a326624eb503bf0517f1bdbf4 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sat, 29 Jan 2022 13:30:15 -0700 Subject: [PATCH 0080/1098] Ensure diagnostics redaction can handle lists of lists (#65170) * Ensure diagnostics redaction can handle lists of lists * Code review * Update homeassistant/components/diagnostics/util.py Co-authored-by: Paulus Schoutsen * Code review * Typing * Revert "Typing" This reverts commit 8a57f772caa5180b609175591d81dfc473769f70. * New typing attempt * Revert "New typing attempt" This reverts commit e26e4aae69f62325fdd6af4d80c8fd1f74846e54. * Fix typing * Fix typing again * Add tests Co-authored-by: Paulus Schoutsen --- homeassistant/components/diagnostics/util.py | 11 +++++-- tests/components/diagnostics/test_util.py | 33 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 tests/components/diagnostics/test_util.py diff --git a/homeassistant/components/diagnostics/util.py b/homeassistant/components/diagnostics/util.py index d849dc052e4579..6154dd14bd21d8 100644 --- a/homeassistant/components/diagnostics/util.py +++ b/homeassistant/components/diagnostics/util.py @@ -2,19 +2,24 @@ from __future__ import annotations from collections.abc import Iterable, Mapping -from typing import Any +from typing import Any, TypeVar, cast from homeassistant.core import callback from .const import REDACTED +T = TypeVar("T") + @callback -def async_redact_data(data: Mapping, to_redact: Iterable[Any]) -> dict[str, Any]: +def async_redact_data(data: T, to_redact: Iterable[Any]) -> T: """Redact sensitive data in a dict.""" if not isinstance(data, (Mapping, list)): return data + if isinstance(data, list): + return cast(T, [async_redact_data(val, to_redact) for val in data]) + redacted = {**data} for key, value in redacted.items(): @@ -25,4 +30,4 @@ def async_redact_data(data: Mapping, to_redact: Iterable[Any]) -> dict[str, Any] elif isinstance(value, list): redacted[key] = [async_redact_data(item, to_redact) for item in value] - return redacted + return cast(T, redacted) diff --git a/tests/components/diagnostics/test_util.py b/tests/components/diagnostics/test_util.py new file mode 100644 index 00000000000000..702b838334fe76 --- /dev/null +++ b/tests/components/diagnostics/test_util.py @@ -0,0 +1,33 @@ +"""Test Diagnostics utils.""" +from homeassistant.components.diagnostics import REDACTED, async_redact_data + + +def test_redact(): + """Test the async_redact_data helper.""" + data = { + "key1": "value1", + "key2": ["value2_a", "value2_b"], + "key3": [["value_3a", "value_3b"], ["value_3c", "value_3d"]], + "key4": { + "key4_1": "value4_1", + "key4_2": ["value4_2a", "value4_2b"], + "key4_3": [["value4_3a", "value4_3b"], ["value4_3c", "value4_3d"]], + }, + } + + to_redact = { + "key1", + "key3", + "key4_1", + } + + assert async_redact_data(data, to_redact) == { + "key1": REDACTED, + "key2": ["value2_a", "value2_b"], + "key3": REDACTED, + "key4": { + "key4_1": REDACTED, + "key4_2": ["value4_2a", "value4_2b"], + "key4_3": [["value4_3a", "value4_3b"], ["value4_3c", "value4_3d"]], + }, + } From be5ff87171dc1c3d1c1af4d1167544899e9c3f49 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:47:40 +0100 Subject: [PATCH 0081/1098] Rewrite pylint init-hook (#65193) --- pyproject.toml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 69398645d18303..c685c991e17f82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,16 @@ ignore = [ # Use a conservative default here; 2 should speed up most setups and not hurt # any too bad. Override on command line as appropriate. jobs = 2 -init-hook='from pylint.config.find_default_config_files import find_default_config_files; from pathlib import Path; import sys; sys.path.append(str(Path(Path(list(find_default_config_files())[0]).parent, "pylint/plugins")))' +init-hook = """\ + from pathlib import Path; \ + import sys; \ + + from pylint.config import find_default_config_files; \ + + sys.path.append( \ + str(Path(next(find_default_config_files())).parent.joinpath('pylint/plugins')) + ) \ + """ load-plugins = [ "pylint.extensions.code_style", "pylint.extensions.typing", From 3ca1b2fc6e3ab817a65227705874cc3dbb01f481 Mon Sep 17 00:00:00 2001 From: Patrik Lindgren <21142447+ggravlingen@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:55:05 +0100 Subject: [PATCH 0082/1098] Add air quality sensor for Tradfri air purifier (#65070) * Add air quality sensor for Tradfri fan platform * Refactor, use entity description * Fix typo * CHange init docstring * Let lambda handle special case * Remove unique id * Apply suggestions from code review Co-authored-by: Martin Hjelmare * Refactor to constants, add mixin * Rename lambda * Update homeassistant/components/tradfri/sensor.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/tradfri/sensor.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/tradfri/sensor.py Co-authored-by: Martin Hjelmare * Replace lambda with function * Refactor device init * Remove fixture scope Co-authored-by: Martin Hjelmare --- homeassistant/components/tradfri/sensor.py | 93 ++++++++++++++++++---- tests/components/tradfri/conftest.py | 19 ++++- tests/components/tradfri/test_fan.py | 31 ++------ tests/components/tradfri/test_sensor.py | 15 ++++ 4 files changed, 116 insertions(+), 42 deletions(-) diff --git a/homeassistant/components/tradfri/sensor.py b/homeassistant/components/tradfri/sensor.py index 3f8fa96857ddf3..693ebeead0082b 100644 --- a/homeassistant/components/tradfri/sensor.py +++ b/homeassistant/components/tradfri/sensor.py @@ -2,13 +2,19 @@ from __future__ import annotations from collections.abc import Callable -from typing import Any +from dataclasses import dataclass +from typing import Any, cast from pytradfri.command import Command +from pytradfri.device import Device -from homeassistant.components.sensor import SensorDeviceClass, SensorEntity +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, +) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import PERCENTAGE +from homeassistant.const import CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, PERCENTAGE from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -17,6 +23,46 @@ from .coordinator import TradfriDeviceDataUpdateCoordinator +@dataclass +class TradfriSensorEntityDescriptionMixin: + """Mixin for required keys.""" + + value: Callable[[Device], Any | None] + + +@dataclass +class TradfriSensorEntityDescription( + SensorEntityDescription, + TradfriSensorEntityDescriptionMixin, +): + """Class describing Tradfri sensor entities.""" + + +def _get_air_quality(device: Device) -> int | None: + """Fetch the air quality value.""" + if ( + device.air_purifier_control.air_purifiers[0].air_quality == 65535 + ): # The sensor returns 65535 if the fan is turned off + return None + + return cast(int, device.air_purifier_control.air_purifiers[0].air_quality) + + +SENSOR_DESCRIPTION_AQI = TradfriSensorEntityDescription( + device_class=SensorDeviceClass.AQI, + native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + key=SensorDeviceClass.AQI, + value=_get_air_quality, +) + +SENSOR_DESCRIPTION_BATTERY = TradfriSensorEntityDescription( + device_class=SensorDeviceClass.BATTERY, + native_unit_of_measurement=PERCENTAGE, + key=SensorDeviceClass.BATTERY, + value=lambda device: cast(int, device.device_info.battery_level), +) + + async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, @@ -27,43 +73,56 @@ async def async_setup_entry( coordinator_data = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] api = coordinator_data[KEY_API] - async_add_entities( - TradfriBatterySensor( - device_coordinator, - api, - gateway_id, - ) - for device_coordinator in coordinator_data[COORDINATOR_LIST] + entities: list[TradfriSensor] = [] + + for device_coordinator in coordinator_data[COORDINATOR_LIST]: + description = None if ( not device_coordinator.device.has_light_control and not device_coordinator.device.has_socket_control and not device_coordinator.device.has_signal_repeater_control and not device_coordinator.device.has_air_purifier_control - ) - ) + ): + description = SENSOR_DESCRIPTION_BATTERY + elif device_coordinator.device.has_air_purifier_control: + description = SENSOR_DESCRIPTION_AQI + + if description: + entities.append( + TradfriSensor( + device_coordinator, + api, + gateway_id, + description=description, + ) + ) + async_add_entities(entities) -class TradfriBatterySensor(TradfriBaseEntity, SensorEntity): + +class TradfriSensor(TradfriBaseEntity, SensorEntity): """The platform class required by Home Assistant.""" - _attr_device_class = SensorDeviceClass.BATTERY - _attr_native_unit_of_measurement = PERCENTAGE + entity_description: TradfriSensorEntityDescription def __init__( self, device_coordinator: TradfriDeviceDataUpdateCoordinator, api: Callable[[Command | list[Command]], Any], gateway_id: str, + description: TradfriSensorEntityDescription, ) -> None: - """Initialize a switch.""" + """Initialize a Tradfri sensor.""" super().__init__( device_coordinator=device_coordinator, api=api, gateway_id=gateway_id, ) + self.entity_description = description + self._refresh() # Set initial state def _refresh(self) -> None: """Refresh the device.""" - self._attr_native_value = self.coordinator.data.device_info.battery_level + self._attr_native_value = self.entity_description.value(self.coordinator.data) diff --git a/tests/components/tradfri/conftest.py b/tests/components/tradfri/conftest.py index f4e871d79e10c0..25f30237d0f555 100644 --- a/tests/components/tradfri/conftest.py +++ b/tests/components/tradfri/conftest.py @@ -1,5 +1,5 @@ """Common tradfri test fixtures.""" -from unittest.mock import Mock, patch +from unittest.mock import Mock, PropertyMock, patch import pytest @@ -76,3 +76,20 @@ def mock_api_factory(mock_api): factory.init.return_value = factory.return_value factory.return_value.request = mock_api yield factory.return_value + + +@pytest.fixture(autouse=True) +def setup(request): + """ + Set up patches for pytradfri methods for the fan platform. + + This is used in test_fan as well as in test_sensor. + """ + with patch( + "pytradfri.device.AirPurifierControl.raw", + new_callable=PropertyMock, + return_value=[{"mock": "mock"}], + ), patch( + "pytradfri.device.AirPurifierControl.air_purifiers", + ): + yield diff --git a/tests/components/tradfri/test_fan.py b/tests/components/tradfri/test_fan.py index 4aa99f5778a146..4db4ed4e58505d 100644 --- a/tests/components/tradfri/test_fan.py +++ b/tests/components/tradfri/test_fan.py @@ -1,6 +1,6 @@ """Tradfri fan (recognised as air purifiers in the IKEA ecosystem) platform tests.""" -from unittest.mock import MagicMock, Mock, PropertyMock, patch +from unittest.mock import MagicMock, Mock import pytest from pytradfri.device import Device @@ -10,19 +10,6 @@ from .common import setup_integration -@pytest.fixture(autouse=True, scope="module") -def setup(request): - """Set up patches for pytradfri methods.""" - with patch( - "pytradfri.device.AirPurifierControl.raw", - new_callable=PropertyMock, - return_value=[{"mock": "mock"}], - ), patch( - "pytradfri.device.AirPurifierControl.air_purifiers", - ): - yield - - def mock_fan(test_features=None, test_state=None, device_number=0): """Mock a tradfri fan/air purifier.""" if test_features is None: @@ -57,9 +44,7 @@ def mock_fan(test_features=None, test_state=None, device_number=0): async def test_fan(hass, mock_gateway, mock_api_factory): """Test that fans are correctly added.""" - state = { - "fan_speed": 10, - } + state = {"fan_speed": 10, "air_quality": 12} mock_gateway.mock_devices.append(mock_fan(test_state=state)) await setup_integration(hass) @@ -74,9 +59,7 @@ async def test_fan(hass, mock_gateway, mock_api_factory): async def test_fan_observed(hass, mock_gateway, mock_api_factory): """Test that fans are correctly observed.""" - state = { - "fan_speed": 10, - } + state = {"fan_speed": 10, "air_quality": 12} fan = mock_fan(test_state=state) mock_gateway.mock_devices.append(fan) @@ -87,10 +70,10 @@ async def test_fan_observed(hass, mock_gateway, mock_api_factory): async def test_fan_available(hass, mock_gateway, mock_api_factory): """Test fan available property.""" - fan = mock_fan(test_state={"fan_speed": 10}, device_number=1) + fan = mock_fan(test_state={"fan_speed": 10, "air_quality": 12}, device_number=1) fan.reachable = True - fan2 = mock_fan(test_state={"fan_speed": 10}, device_number=2) + fan2 = mock_fan(test_state={"fan_speed": 10, "air_quality": 12}, device_number=2) fan2.reachable = False mock_gateway.mock_devices.append(fan) @@ -120,7 +103,7 @@ async def test_set_percentage( ): """Test setting speed of a fan.""" # Note pytradfri style, not hass. Values not really important. - initial_state = {"percentage": 10, "fan_speed": 3} + initial_state = {"percentage": 10, "fan_speed": 3, "air_quality": 12} # Setup the gateway with a mock fan. fan = mock_fan(test_state=initial_state, device_number=0) mock_gateway.mock_devices.append(fan) @@ -147,7 +130,7 @@ async def test_set_percentage( mock_gateway_response = responses[0] # A KeyError is raised if we don't add the 5908 response code - mock_gateway_response["15025"][0].update({"5908": 10}) + mock_gateway_response["15025"][0].update({"5908": 10, "5907": 12}) # Use the callback function to update the fan state. dev = Device(mock_gateway_response) diff --git a/tests/components/tradfri/test_sensor.py b/tests/components/tradfri/test_sensor.py index 63d4b6f84e53ab..04f6534412558b 100644 --- a/tests/components/tradfri/test_sensor.py +++ b/tests/components/tradfri/test_sensor.py @@ -3,6 +3,7 @@ from unittest.mock import MagicMock, Mock from .common import setup_integration +from .test_fan import mock_fan def mock_sensor(test_state: list, device_number=0): @@ -65,6 +66,20 @@ async def test_cover_battery_sensor(hass, mock_gateway, mock_api_factory): assert sensor_1.attributes["device_class"] == "battery" +async def test_air_quality_sensor(hass, mock_gateway, mock_api_factory): + """Test that a battery sensor is correctly added.""" + mock_gateway.mock_devices.append( + mock_fan(test_state={"fan_speed": 10, "air_quality": 42}) + ) + await setup_integration(hass) + + sensor_1 = hass.states.get("sensor.tradfri_fan_0") + assert sensor_1 is not None + assert sensor_1.state == "42" + assert sensor_1.attributes["unit_of_measurement"] == "µg/m³" + assert sensor_1.attributes["device_class"] == "aqi" + + async def test_sensor_observed(hass, mock_gateway, mock_api_factory): """Test that sensors are correctly observed.""" sensor = mock_sensor(test_state=[{"attribute": "battery_level", "value": 60}]) From 30440cd1baa4d5d4c5ca1a31f7cd4d7eef4e5b16 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sun, 30 Jan 2022 00:11:28 +0100 Subject: [PATCH 0083/1098] Add logic to avoid creating the same scene multiple times (#65207) --- homeassistant/components/deconz/scene.py | 11 ++++++-- tests/components/deconz/test_diagnostics.py | 1 + tests/components/deconz/test_scene.py | 29 +++++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/deconz/scene.py b/homeassistant/components/deconz/scene.py index 3d8e1aa27ba3c8..9fcccc523864b9 100644 --- a/homeassistant/components/deconz/scene.py +++ b/homeassistant/components/deconz/scene.py @@ -7,7 +7,7 @@ from pydeconz.group import DeconzScene as PydeconzScene -from homeassistant.components.scene import Scene +from homeassistant.components.scene import DOMAIN, Scene from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -23,6 +23,7 @@ async def async_setup_entry( ) -> None: """Set up scenes for deCONZ component.""" gateway = get_gateway_from_config_entry(hass, config_entry) + gateway.entities[DOMAIN] = set() @callback def async_add_scene( @@ -30,7 +31,11 @@ def async_add_scene( | ValuesView[PydeconzScene] = gateway.api.scenes.values(), ) -> None: """Add scene from deCONZ.""" - entities = [DeconzScene(scene, gateway) for scene in scenes] + entities = [ + DeconzScene(scene, gateway) + for scene in scenes + if scene.deconz_id not in gateway.entities[DOMAIN] + ] if entities: async_add_entities(entities) @@ -59,10 +64,12 @@ def __init__(self, scene: PydeconzScene, gateway: DeconzGateway) -> None: async def async_added_to_hass(self) -> None: """Subscribe to sensors events.""" self.gateway.deconz_ids[self.entity_id] = self._scene.deconz_id + self.gateway.entities[DOMAIN].add(self._scene.deconz_id) async def async_will_remove_from_hass(self) -> None: """Disconnect scene object when removed.""" del self.gateway.deconz_ids[self.entity_id] + self.gateway.entities[DOMAIN].remove(self._scene.deconz_id) self._scene = None async def async_activate(self, **kwargs: Any) -> None: diff --git a/tests/components/deconz/test_diagnostics.py b/tests/components/deconz/test_diagnostics.py index 7d351a333cf9cf..17da9f1141a22c 100644 --- a/tests/components/deconz/test_diagnostics.py +++ b/tests/components/deconz/test_diagnostics.py @@ -55,6 +55,7 @@ async def test_entry_diagnostics( str(Platform.LIGHT): [], str(Platform.LOCK): [], str(Platform.NUMBER): [], + str(Platform.SCENE): [], str(Platform.SENSOR): [], str(Platform.SIREN): [], str(Platform.SWITCH): [], diff --git a/tests/components/deconz/test_scene.py b/tests/components/deconz/test_scene.py index 189eb1e6eb7290..e6f74cd0529f42 100644 --- a/tests/components/deconz/test_scene.py +++ b/tests/components/deconz/test_scene.py @@ -2,8 +2,10 @@ from unittest.mock import patch +from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN, SERVICE_TURN_ON from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.helpers.dispatcher import async_dispatcher_send from .test_gateway import ( DECONZ_WEB_REQUEST, @@ -58,3 +60,30 @@ async def test_scenes(hass, aioclient_mock): await hass.config_entries.async_unload(config_entry.entry_id) assert len(hass.states.async_all()) == 0 + + +async def test_only_new_scenes_are_created(hass, aioclient_mock): + """Test that scenes works.""" + data = { + "groups": { + "1": { + "id": "Light group id", + "name": "Light group", + "type": "LightGroup", + "state": {"all_on": False, "any_on": True}, + "action": {}, + "scenes": [{"id": "1", "name": "Scene"}], + "lights": [], + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) + + assert len(hass.states.async_all()) == 1 + + gateway = get_gateway_from_config_entry(hass, config_entry) + async_dispatcher_send(hass, gateway.signal_new_scene) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1 From caa5578134bb8d31ef71c56ae8c75480dc2275e6 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Sun, 30 Jan 2022 01:15:49 +0200 Subject: [PATCH 0084/1098] Fix webostv configure sources when selected source is missing (#65195) * Fix webostv configure sources when selected source is missing * Add comment for filtering duplicates --- homeassistant/components/webostv/config_flow.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/webostv/config_flow.py b/homeassistant/components/webostv/config_flow.py index 2120635be911a0..3bf4f7c6aebb86 100644 --- a/homeassistant/components/webostv/config_flow.py +++ b/homeassistant/components/webostv/config_flow.py @@ -178,11 +178,14 @@ async def async_step_init(self, user_input: ConfigType | None = None) -> FlowRes options_input = {CONF_SOURCES: user_input[CONF_SOURCES]} return self.async_create_entry(title="", data=options_input) # Get sources - sources = self.options.get(CONF_SOURCES, "") sources_list = await async_get_sources(self.host, self.key) if not sources_list: errors["base"] = "cannot_retrieve" + sources = [s for s in self.options.get(CONF_SOURCES, []) if s in sources_list] + if not sources: + sources = sources_list + options_schema = vol.Schema( { vol.Optional( @@ -204,7 +207,11 @@ async def async_get_sources(host: str, key: str) -> list[str]: except WEBOSTV_EXCEPTIONS: return [] - return [ - *(app["title"] for app in client.apps.values()), - *(app["label"] for app in client.inputs.values()), - ] + return list( + dict.fromkeys( # Preserve order when filtering duplicates + [ + *(app["title"] for app in client.apps.values()), + *(app["label"] for app in client.inputs.values()), + ] + ) + ) From 77ef86faee0c7eac3a418dc5784a0aa9d2c769c7 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 30 Jan 2022 00:14:21 +0000 Subject: [PATCH 0085/1098] [ci skip] Translation update --- .../components/abode/translations/pt-BR.json | 12 ++- .../abode/translations/zh-Hant.json | 2 +- .../accuweather/translations/el.json | 7 +- .../accuweather/translations/pt-BR.json | 10 ++- .../accuweather/translations/zh-Hant.json | 2 +- .../components/acmeda/translations/pt-BR.json | 7 ++ .../components/adax/translations/pt-BR.json | 25 ++++++ .../adguard/translations/pt-BR.json | 10 ++- .../advantage_air/translations/pt-BR.json | 18 ++++ .../components/aemet/translations/pt-BR.json | 19 ++++ .../agent_dvr/translations/pt-BR.json | 8 ++ .../components/airly/translations/pt-BR.json | 20 +++++ .../components/airnow/translations/pt-BR.json | 21 +++++ .../airthings/translations/pt-BR.json | 21 +++++ .../airtouch4/translations/pt-BR.json | 17 ++++ .../components/airvisual/translations/el.json | 7 ++ .../airvisual/translations/pt-BR.json | 27 +++++- .../alarmdecoder/translations/pt-BR.json | 18 ++++ .../components/almond/translations/pt-BR.json | 6 ++ .../almond/translations/zh-Hant.json | 2 +- .../components/ambee/translations/pt-BR.json | 26 ++++++ .../ambiclimate/translations/pt-BR.json | 5 +- .../ambient_station/translations/pt-BR.json | 7 +- .../androidtv/translations/pt-BR.json | 56 +++++++++++- .../components/apple_tv/translations/el.json | 3 +- .../apple_tv/translations/pt-BR.json | 25 ++++++ .../arcam_fmj/translations/pt-BR.json | 15 ++++ .../aseko_pool_live/translations/pt-BR.json | 19 ++++ .../asuswrt/translations/pt-BR.json | 23 +++++ .../asuswrt/translations/zh-Hant.json | 2 +- .../components/atag/translations/pt-BR.json | 13 ++- .../components/august/translations/pt-BR.json | 20 +++++ .../components/aurora/translations/pt-BR.json | 16 ++++ .../translations/pt-BR.json | 10 +++ .../aussie_broadband/translations/pt-BR.json | 50 +++++++++++ .../components/awair/translations/pt-BR.json | 20 ++++- .../components/axis/translations/pt-BR.json | 10 ++- .../azure_devops/translations/el.json | 16 +++- .../azure_devops/translations/pt-BR.json | 6 ++ .../azure_event_hub/translations/pt-BR.json | 12 +++ .../azure_event_hub/translations/zh-Hant.json | 2 +- .../components/balboa/translations/pt-BR.json | 18 ++++ .../binary_sensor/translations/el.json | 2 + .../binary_sensor/translations/pt-BR.json | 89 ++++++++++++++++++- .../components/blebox/translations/pt-BR.json | 7 ++ .../components/blink/translations/pt-BR.json | 6 +- .../translations/pt-BR.json | 19 ++++ .../components/bond/translations/pt-BR.json | 23 ++++- .../bosch_shc/translations/pt-BR.json | 22 +++++ .../braviatv/translations/pt-BR.json | 15 ++++ .../broadlink/translations/pt-BR.json | 47 ++++++++++ .../brother/translations/pt-BR.json | 4 +- .../components/brunt/translations/pt-BR.json | 29 ++++++ .../components/bsblan/translations/pt-BR.json | 13 +++ .../buienradar/translations/pt-BR.json | 18 ++++ .../components/canary/translations/pt-BR.json | 19 ++++ .../canary/translations/zh-Hant.json | 2 +- .../components/cast/translations/pt-BR.json | 4 +- .../components/cast/translations/zh-Hant.json | 2 +- .../cert_expiry/translations/pt-BR.json | 7 +- .../climacell/translations/pt-BR.json | 20 +++++ .../climacell/translations/sensor.pt-BR.json | 27 ++++++ .../cloudflare/translations/el.json | 5 ++ .../cloudflare/translations/pt-BR.json | 25 ++++++ .../cloudflare/translations/zh-Hant.json | 2 +- .../co2signal/translations/pt-BR.json | 25 ++++++ .../components/coinbase/translations/el.json | 22 ++++- .../coinbase/translations/pt-BR.json | 27 ++++++ .../control4/translations/pt-BR.json | 8 +- .../coolmaster/translations/pt-BR.json | 5 +- .../coronavirus/translations/pt-BR.json | 3 +- .../cpuspeed/translations/pt-BR.json | 16 ++++ .../cpuspeed/translations/zh-Hant.json | 4 +- .../crownstone/translations/el.json | 22 +++++ .../crownstone/translations/pt-BR.json | 63 +++++++++++++ .../components/daikin/translations/pt-BR.json | 16 +++- .../components/deconz/translations/pt-BR.json | 42 ++++++++- .../denonavr/translations/pt-BR.json | 15 ++++ .../devolo_home_control/translations/el.json | 3 + .../translations/pt-BR.json | 14 +++ .../components/dexcom/translations/pt-BR.json | 20 +++++ .../dialogflow/translations/pt-BR.json | 4 + .../dialogflow/translations/zh-Hant.json | 2 +- .../directv/translations/pt-BR.json | 12 +++ .../components/dlna_dmr/translations/el.json | 23 ++++- .../dlna_dmr/translations/pt-BR.json | 30 +++++++ .../components/dnsip/translations/pt-BR.json | 27 ++++++ .../doorbird/translations/pt-BR.json | 16 +++- .../components/dsmr/translations/pt-BR.json | 25 ++++++ .../components/dunehd/translations/pt-BR.json | 19 ++++ .../components/eafm/translations/pt-BR.json | 7 ++ .../components/ecobee/translations/pt-BR.json | 6 +- .../ecobee/translations/zh-Hant.json | 2 +- .../components/econet/translations/pt-BR.json | 20 +++++ .../components/efergy/translations/pt-BR.json | 20 +++++ .../components/elgato/translations/pt-BR.json | 8 ++ .../components/elkm1/translations/pt-BR.json | 6 ++ .../components/elmax/translations/pt-BR.json | 34 +++++++ .../emonitor/translations/pt-BR.json | 18 ++++ .../emulated_roku/translations/pt-BR.json | 3 + .../enocean/translations/pt-BR.json | 7 ++ .../enocean/translations/zh-Hant.json | 2 +- .../enphase_envoy/translations/pt-BR.json | 22 +++++ .../translations/pt-BR.json | 16 ++++ .../components/epson/translations/pt-BR.json | 15 ++++ .../esphome/translations/pt-BR.json | 7 +- .../evil_genius_labs/translations/pt-BR.json | 15 ++++ .../components/ezviz/translations/pt-BR.json | 35 ++++++++ .../faa_delays/translations/pt-BR.json | 8 ++ .../components/fan/translations/pt-BR.json | 4 + .../fireservicerota/translations/pt-BR.json | 27 ++++++ .../firmata/translations/pt-BR.json | 7 ++ .../fjaraskupan/translations/pt-BR.json | 8 ++ .../fjaraskupan/translations/zh-Hant.json | 2 +- .../flick_electric/translations/pt-BR.json | 4 +- .../components/flipr/translations/pt-BR.json | 19 ++++ .../components/flo/translations/pt-BR.json | 16 +++- .../components/flume/translations/pt-BR.json | 14 ++- .../flunearyou/translations/pt-BR.json | 18 ++++ .../flux_led/translations/pt-BR.json | 36 ++++++++ .../forecast_solar/translations/el.json | 9 ++ .../forecast_solar/translations/pt-BR.json | 13 +++ .../forked_daapd/translations/pt-BR.json | 6 +- .../components/foscam/translations/pt-BR.json | 22 +++++ .../freebox/translations/pt-BR.json | 19 ++++ .../freedompro/translations/pt-BR.json | 18 ++++ .../components/fritz/translations/pt-BR.json | 46 ++++++++++ .../fritzbox/translations/pt-BR.json | 16 ++++ .../fritzbox_callmonitor/translations/el.json | 20 +++++ .../translations/pt-BR.json | 21 +++++ .../components/fronius/translations/el.json | 4 + .../fronius/translations/pt-BR.json | 23 +++++ .../garages_amsterdam/translations/pt-BR.json | 9 ++ .../components/gdacs/translations/pt-BR.json | 2 +- .../geofency/translations/pt-BR.json | 4 + .../geofency/translations/zh-Hant.json | 2 +- .../geonetnz_quakes/translations/pt-BR.json | 2 +- .../geonetnz_volcano/translations/pt-BR.json | 3 + .../components/gios/translations/pt-BR.json | 9 ++ .../glances/translations/pt-BR.json | 12 ++- .../components/goalzero/translations/el.json | 3 + .../goalzero/translations/pt-BR.json | 22 +++++ .../gogogate2/translations/pt-BR.json | 9 +- .../components/goodwe/translations/pt-BR.json | 20 +++++ .../translations/pt-BR.json | 18 ++++ .../gpslogger/translations/pt-BR.json | 4 + .../gpslogger/translations/zh-Hant.json | 2 +- .../components/gree/translations/pt-BR.json | 13 +++ .../components/gree/translations/zh-Hant.json | 2 +- .../growatt_server/translations/pt-BR.json | 17 ++++ .../guardian/translations/pt-BR.json | 17 ++++ .../habitica/translations/pt-BR.json | 16 ++++ .../hangouts/translations/pt-BR.json | 4 +- .../harmony/translations/pt-BR.json | 5 +- .../components/heos/translations/pt-BR.json | 8 +- .../components/heos/translations/zh-Hant.json | 2 +- .../hisense_aehw4a1/translations/pt-BR.json | 8 ++ .../hisense_aehw4a1/translations/zh-Hant.json | 2 +- .../components/hive/translations/pt-BR.json | 25 ++++++ .../hlk_sw16/translations/pt-BR.json | 21 +++++ .../home_connect/translations/pt-BR.json | 6 +- .../home_plus_control/translations/pt-BR.json | 15 ++++ .../translations/zh-Hant.json | 2 +- .../homekit/translations/pt-BR.json | 18 ++++ .../translations/pt-BR.json | 2 +- .../translations/select.pt-BR.json | 9 ++ .../homematicip_cloud/translations/pt-BR.json | 10 +-- .../homewizard/translations/pt-BR.json | 19 ++++ .../honeywell/translations/pt-BR.json | 15 ++++ .../huawei_lte/translations/pt-BR.json | 8 +- .../components/hue/translations/pt-BR.json | 38 ++++++-- .../huisbaasje/translations/pt-BR.json | 20 +++++ .../humidifier/translations/pt-BR.json | 8 ++ .../translations/pt-BR.json | 7 ++ .../hvv_departures/translations/el.json | 4 +- .../hvv_departures/translations/pt-BR.json | 20 +++++ .../components/hyperion/translations/el.json | 28 +++++- .../hyperion/translations/pt-BR.json | 22 +++++ .../components/ialarm/translations/pt-BR.json | 19 ++++ .../iaqualink/translations/pt-BR.json | 12 ++- .../iaqualink/translations/zh-Hant.json | 2 +- .../components/icloud/translations/pt-BR.json | 10 ++- .../components/ifttt/translations/pt-BR.json | 4 + .../ifttt/translations/zh-Hant.json | 2 +- .../insteon/translations/pt-BR.json | 39 +++++++- .../insteon/translations/zh-Hant.json | 2 +- .../intellifire/translations/pt-BR.json | 18 ++++ .../components/ios/translations/pt-BR.json | 4 +- .../components/ios/translations/zh-Hant.json | 2 +- .../iotawatt/translations/pt-BR.json | 22 +++++ .../components/ipp/translations/pt-BR.json | 8 +- .../components/iqvia/translations/pt-BR.json | 3 + .../translations/pt-BR.json | 7 ++ .../translations/zh-Hant.json | 2 +- .../components/iss/translations/ca.json | 16 ++++ .../components/iss/translations/de.json | 16 ++++ .../components/iss/translations/el.json | 15 ++++ .../components/iss/translations/et.json | 16 ++++ .../components/iss/translations/hu.json | 16 ++++ .../components/iss/translations/pt-BR.json | 16 ++++ .../components/iss/translations/ru.json | 16 ++++ .../components/iss/translations/tr.json | 16 ++++ .../components/iss/translations/zh-Hant.json | 16 ++++ .../components/isy994/translations/pt-BR.json | 11 ++- .../components/izone/translations/pt-BR.json | 13 +++ .../izone/translations/zh-Hant.json | 2 +- .../jellyfin/translations/pt-BR.json | 21 +++++ .../jellyfin/translations/zh-Hant.json | 2 +- .../juicenet/translations/pt-BR.json | 12 ++- .../keenetic_ndms2/translations/pt-BR.json | 20 +++++ .../kmtronic/translations/pt-BR.json | 21 +++++ .../components/knx/translations/el.json | 6 +- .../components/knx/translations/pt-BR.json | 35 ++++++++ .../components/knx/translations/zh-Hant.json | 2 +- .../components/kodi/translations/pt-BR.json | 35 ++++++++ .../konnected/translations/pt-BR.json | 18 ++++ .../kostal_plenticore/translations/pt-BR.json | 20 +++++ .../components/kraken/translations/pt-BR.json | 12 +++ .../kraken/translations/zh-Hant.json | 2 +- .../kulersky/translations/pt-BR.json | 13 +++ .../kulersky/translations/zh-Hant.json | 2 +- .../launch_library/translations/pt-BR.json | 12 +++ .../launch_library/translations/zh-Hant.json | 2 +- .../life360/translations/pt-BR.json | 9 +- .../components/lifx/translations/pt-BR.json | 4 +- .../components/lifx/translations/zh-Hant.json | 2 +- .../components/light/translations/pt-BR.json | 2 + .../litejet/translations/pt-BR.json | 14 +++ .../litejet/translations/zh-Hant.json | 2 +- .../litterrobot/translations/pt-BR.json | 20 +++++ .../local_ip/translations/pt-BR.json | 3 +- .../local_ip/translations/zh-Hant.json | 2 +- .../locative/translations/pt-BR.json | 6 +- .../locative/translations/zh-Hant.json | 2 +- .../logi_circle/translations/pt-BR.json | 8 +- .../components/lookin/translations/pt-BR.json | 31 +++++++ .../luftdaten/translations/pt-BR.json | 2 + .../lutron_caseta/translations/pt-BR.json | 11 ++- .../components/lyric/translations/pt-BR.json | 17 ++++ .../mailgun/translations/pt-BR.json | 4 + .../mailgun/translations/zh-Hant.json | 2 +- .../components/mazda/translations/pt-BR.json | 20 +++++ .../media_player/translations/pt-BR.json | 5 ++ .../melcloud/translations/pt-BR.json | 16 ++++ .../components/met/translations/pt-BR.json | 3 + .../met_eireann/translations/pt-BR.json | 16 ++++ .../meteo_france/translations/pt-BR.json | 4 +- .../meteoclimatic/translations/pt-BR.json | 11 +++ .../metoffice/translations/pt-BR.json | 20 +++++ .../mikrotik/translations/pt-BR.json | 8 +- .../components/mill/translations/pt-BR.json | 32 +++++++ .../minecraft_server/translations/pt-BR.json | 10 ++- .../modem_callerid/translations/el.json | 3 + .../modem_callerid/translations/pt-BR.json | 19 ++++ .../modern_forms/translations/pt-BR.json | 21 +++++ .../monoprice/translations/pt-BR.json | 6 +- .../motion_blinds/translations/pt-BR.json | 13 ++- .../motioneye/translations/pt-BR.json | 14 ++- .../components/mqtt/translations/pt-BR.json | 19 +++- .../components/mqtt/translations/zh-Hant.json | 2 +- .../mullvad/translations/pt-BR.json | 11 +++ .../mutesync/translations/pt-BR.json | 15 ++++ .../components/myq/translations/pt-BR.json | 15 ++++ .../mysensors/translations/pt-BR.json | 16 ++++ .../components/nam/translations/el.json | 3 + .../components/nam/translations/pt-BR.json | 34 +++++++ .../nanoleaf/translations/pt-BR.json | 22 +++++ .../components/neato/translations/pt-BR.json | 19 ++++ .../components/nest/translations/el.json | 4 + .../components/nest/translations/pt-BR.json | 33 ++++++- .../components/nest/translations/zh-Hant.json | 2 +- .../netatmo/translations/pt-BR.json | 25 +++++- .../netatmo/translations/zh-Hant.json | 2 +- .../netgear/translations/pt-BR.json | 6 +- .../components/nexia/translations/pt-BR.json | 9 ++ .../nfandroidtv/translations/pt-BR.json | 19 ++++ .../nightscout/translations/pt-BR.json | 2 + .../components/nina/translations/pt-BR.json | 21 +++++ .../components/nina/translations/zh-Hant.json | 2 +- .../nmap_tracker/translations/el.json | 16 +++- .../nmap_tracker/translations/pt-BR.json | 7 ++ .../components/notion/translations/pt-BR.json | 17 +++- .../components/nuheat/translations/pt-BR.json | 5 +- .../components/nuki/translations/pt-BR.json | 27 ++++++ .../components/number/translations/el.json | 8 ++ .../components/nut/translations/pt-BR.json | 16 +++- .../components/nws/translations/pt-BR.json | 5 +- .../components/nzbget/translations/pt-BR.json | 24 +++++ .../nzbget/translations/zh-Hant.json | 2 +- .../octoprint/translations/pt-BR.json | 30 +++++++ .../omnilogic/translations/pt-BR.json | 20 +++++ .../omnilogic/translations/zh-Hant.json | 2 +- .../components/oncue/translations/pt-BR.json | 20 +++++ .../ondilo_ico/translations/pt-BR.json | 11 +++ .../onewire/translations/pt-BR.json | 18 ++++ .../components/onvif/translations/pt-BR.json | 18 +++- .../opengarage/translations/el.json | 11 +++ .../opengarage/translations/pt-BR.json | 22 +++++ .../opentherm_gw/translations/pt-BR.json | 15 ++++ .../components/openuv/translations/pt-BR.json | 5 +- .../openweathermap/translations/pt-BR.json | 20 +++++ .../overkiz/translations/pt-BR.json | 23 +++++ .../overkiz/translations/select.pt-BR.json | 13 +++ .../overkiz/translations/sensor.pt-BR.json | 19 ++++ .../ovo_energy/translations/pt-BR.json | 22 +++++ .../owntracks/translations/pt-BR.json | 4 + .../owntracks/translations/zh-Hant.json | 2 +- .../components/ozw/translations/pt-BR.json | 16 ++++ .../components/ozw/translations/zh-Hant.json | 2 +- .../p1_monitor/translations/pt-BR.json | 16 ++++ .../panasonic_viera/translations/pt-BR.json | 20 ++++- .../philips_js/translations/el.json | 9 ++ .../philips_js/translations/pt-BR.json | 23 +++++ .../pi_hole/translations/pt-BR.json | 13 ++- .../components/picnic/translations/pt-BR.json | 20 +++++ .../components/plaato/translations/pt-BR.json | 7 +- .../plaato/translations/zh-Hant.json | 2 +- .../components/plex/translations/pt-BR.json | 21 ++++- .../plugwise/translations/pt-BR.json | 11 ++- .../plum_lightpad/translations/pt-BR.json | 17 ++++ .../components/point/translations/pt-BR.json | 12 +-- .../point/translations/zh-Hant.json | 2 +- .../poolsense/translations/pt-BR.json | 18 ++++ .../powerwall/translations/pt-BR.json | 9 +- .../profiler/translations/pt-BR.json | 12 +++ .../profiler/translations/zh-Hant.json | 2 +- .../progettihwsw/translations/pt-BR.json | 19 ++++ .../prosegur/translations/pt-BR.json | 27 ++++++ .../components/ps4/translations/pt-BR.json | 12 +-- .../pvoutput/translations/pt-BR.json | 24 +++++ .../translations/pt-BR.json | 2 +- .../components/rachio/translations/pt-BR.json | 19 ++++ .../rainforest_eagle/translations/pt-BR.json | 19 ++++ .../rainmachine/translations/el.json | 10 +++ .../rainmachine/translations/pt-BR.json | 6 ++ .../components/rdw/translations/pt-BR.json | 14 +++ .../recollect_waste/translations/pt-BR.json | 7 ++ .../components/remote/translations/pt-BR.json | 6 ++ .../renault/translations/pt-BR.json | 24 +++++ .../components/rfxtrx/translations/el.json | 9 ++ .../components/rfxtrx/translations/pt-BR.json | 30 +++++++ .../rfxtrx/translations/zh-Hant.json | 2 +- .../ridwell/translations/pt-BR.json | 8 +- .../components/ring/translations/pt-BR.json | 7 ++ .../components/risco/translations/el.json | 4 +- .../components/risco/translations/pt-BR.json | 31 +++++++ .../translations/pt-BR.json | 19 ++++ .../components/roku/translations/pt-BR.json | 11 +++ .../components/roomba/translations/pt-BR.json | 25 +++++- .../components/roon/translations/pt-BR.json | 6 +- .../rpi_power/translations/pt-BR.json | 12 +++ .../rpi_power/translations/zh-Hant.json | 2 +- .../rtsp_to_webrtc/translations/pt-BR.json | 25 ++++++ .../rtsp_to_webrtc/translations/zh-Hant.json | 2 +- .../ruckus_unleashed/translations/pt-BR.json | 21 +++++ .../components/samsungtv/translations/el.json | 1 + .../samsungtv/translations/pt-BR.json | 19 ++++ .../screenlogic/translations/pt-BR.json | 18 ++++ .../components/sense/translations/pt-BR.json | 5 +- .../senseme/translations/pt-BR.json | 30 +++++++ .../sensibo/translations/pt-BR.json | 18 ++++ .../components/sensor/translations/pt-BR.json | 22 ++++- .../components/sentry/translations/pt-BR.json | 3 + .../sentry/translations/zh-Hant.json | 2 +- .../sharkiq/translations/pt-BR.json | 29 ++++++ .../components/shelly/translations/pl.json | 10 +-- .../components/shelly/translations/pt-BR.json | 25 ++++++ .../shopping_list/translations/pt-BR.json | 2 +- .../components/sia/translations/pt-BR.json | 14 +++ .../simplisafe/translations/el.json | 4 +- .../simplisafe/translations/pt-BR.json | 13 ++- .../components/sma/translations/pt-BR.json | 23 +++++ .../smappee/translations/pt-BR.json | 8 +- .../smart_meter_texas/translations/pt-BR.json | 20 +++++ .../smarthab/translations/pt-BR.json | 9 +- .../smartthings/translations/pt-BR.json | 5 ++ .../smarttub/translations/pt-BR.json | 21 +++++ .../components/smhi/translations/pt-BR.json | 3 + .../components/sms/translations/pt-BR.json | 12 +++ .../components/sms/translations/zh-Hant.json | 2 +- .../solaredge/translations/pt-BR.json | 21 +++++ .../solarlog/translations/pt-BR.json | 18 ++++ .../components/solax/translations/pt-BR.json | 17 ++++ .../components/soma/translations/pt-BR.json | 10 ++- .../components/soma/translations/zh-Hant.json | 2 +- .../components/somfy/translations/pt-BR.json | 8 +- .../somfy/translations/zh-Hant.json | 2 +- .../somfy_mylink/translations/pt-BR.json | 25 ++++++ .../components/sonarr/translations/pt-BR.json | 27 ++++++ .../songpal/translations/pt-BR.json | 3 + .../components/sonos/translations/pt-BR.json | 4 +- .../sonos/translations/zh-Hant.json | 2 +- .../speedtestdotnet/translations/pt-BR.json | 12 +++ .../speedtestdotnet/translations/zh-Hant.json | 2 +- .../components/spider/translations/pt-BR.json | 19 ++++ .../spider/translations/zh-Hant.json | 2 +- .../spotify/translations/pt-BR.json | 12 +++ .../squeezebox/translations/pt-BR.json | 27 ++++++ .../srp_energy/translations/pt-BR.json | 20 +++++ .../srp_energy/translations/zh-Hant.json | 2 +- .../steamist/translations/pt-BR.json | 32 +++++++ .../stookalert/translations/pt-BR.json | 7 ++ .../components/subaru/translations/pt-BR.json | 20 +++++ .../surepetcare/translations/pt-BR.json | 2 +- .../components/switch/translations/pt-BR.json | 17 ++++ .../components/switchbot/translations/el.json | 3 +- .../switchbot/translations/pt-BR.json | 20 +++++ .../switcher_kis/translations/pt-BR.json | 13 +++ .../switcher_kis/translations/zh-Hant.json | 2 +- .../syncthing/translations/pt-BR.json | 19 ++++ .../syncthru/translations/pt-BR.json | 19 ++++ .../synology_dsm/translations/pt-BR.json | 41 ++++++++- .../system_bridge/translations/pt-BR.json | 28 ++++++ .../components/tado/translations/pt-BR.json | 8 +- .../components/tailscale/translations/el.json | 15 ++++ .../tailscale/translations/pt-BR.json | 26 ++++++ .../tasmota/translations/pt-BR.json | 7 ++ .../tasmota/translations/zh-Hant.json | 2 +- .../tellduslive/translations/pt-BR.json | 10 ++- .../translations/pt-BR.json | 25 ++++++ .../components/tibber/translations/pt-BR.json | 18 ++++ .../components/tile/translations/pt-BR.json | 24 +++++ .../components/tolo/translations/el.json | 1 + .../components/tolo/translations/pt-BR.json | 21 +++++ .../components/toon/translations/pt-BR.json | 5 +- .../totalconnect/translations/pt-BR.json | 11 ++- .../components/tplink/translations/pt-BR.json | 17 +++- .../tplink/translations/zh-Hant.json | 2 +- .../traccar/translations/pt-BR.json | 4 + .../traccar/translations/zh-Hant.json | 2 +- .../tractive/translations/pt-BR.json | 19 ++++ .../tractive/translations/sensor.el.json | 10 +++ .../tractive/translations/sensor.pt-BR.json | 10 +++ .../tradfri/translations/pt-BR.json | 8 +- .../translations/el.json | 9 ++ .../translations/pt-BR.json | 23 +++++ .../transmission/translations/pt-BR.json | 9 +- .../components/tuya/translations/el.json | 18 ++++ .../components/tuya/translations/pt-BR.json | 26 +++++- .../tuya/translations/select.pt-BR.json | 75 +++++++++++++++- .../components/tuya/translations/zh-Hant.json | 2 +- .../twentemilieu/translations/pt-BR.json | 4 + .../components/twilio/translations/pt-BR.json | 6 +- .../twilio/translations/zh-Hant.json | 2 +- .../twinkly/translations/pt-BR.json | 17 ++++ .../components/unifi/translations/pt-BR.json | 11 +-- .../unifiprotect/translations/pt-BR.json | 40 ++++++++- .../components/upb/translations/pt-BR.json | 5 +- .../upcloud/translations/pt-BR.json | 16 ++++ .../components/upnp/translations/pt-BR.json | 4 +- .../uptimerobot/translations/pt-BR.json | 27 ++++++ .../translations/sensor.pt-BR.json | 11 +++ .../components/vallox/translations/pt-BR.json | 24 +++++ .../components/velbus/translations/pt-BR.json | 20 +++++ .../venstar/translations/pt-BR.json | 22 +++++ .../components/verisure/translations/el.json | 3 + .../verisure/translations/pt-BR.json | 24 +++++ .../version/translations/pt-BR.json | 12 +++ .../components/vesync/translations/pt-BR.json | 9 +- .../vesync/translations/zh-Hant.json | 2 +- .../components/vicare/translations/pt-BR.json | 20 +++++ .../vicare/translations/zh-Hant.json | 2 +- .../components/vilfo/translations/pt-BR.json | 12 ++- .../components/vizio/translations/pt-BR.json | 21 ++++- .../vlc_telnet/translations/pt-BR.json | 13 +++ .../volumio/translations/pt-BR.json | 19 ++++ .../wallbox/translations/pt-BR.json | 28 ++++++ .../watttime/translations/pt-BR.json | 25 +++++- .../waze_travel_time/translations/pt-BR.json | 17 ++++ .../webostv/translations/pt-BR.json | 47 ++++++++++ .../components/webostv/translations/tr.json | 2 +- .../components/wemo/translations/pt-BR.json | 4 +- .../components/wemo/translations/zh-Hant.json | 2 +- .../components/whois/translations/pt-BR.json | 18 ++++ .../components/wiffi/translations/pt-BR.json | 3 +- .../wilight/translations/pt-BR.json | 7 ++ .../withings/translations/pt-BR.json | 13 +++ .../components/wled/translations/pt-BR.json | 19 ++++ .../wled/translations/select.el.json | 7 ++ .../wolflink/translations/pt-BR.json | 7 +- .../wolflink/translations/sensor.el.json | 8 +- .../components/xbox/translations/pt-BR.json | 12 +++ .../components/xbox/translations/zh-Hant.json | 2 +- .../xiaomi_aqara/translations/pt-BR.json | 23 +++++ .../xiaomi_miio/translations/pt-BR.json | 30 ++++++- .../yale_smart_alarm/translations/pt-BR.json | 36 +++++++- .../yamaha_musiccast/translations/pt-BR.json | 17 ++++ .../translations/select.pt-BR.json | 31 +++++++ .../yeelight/translations/pt-BR.json | 18 ++++ .../youless/translations/pt-BR.json | 15 ++++ .../zerproc/translations/pt-BR.json | 2 +- .../zerproc/translations/zh-Hant.json | 2 +- .../components/zha/translations/pt-BR.json | 44 ++++++++- .../components/zha/translations/zh-Hant.json | 2 +- .../zoneminder/translations/pt-BR.json | 22 +++++ .../components/zwave/translations/pt-BR.json | 5 +- .../zwave/translations/zh-Hant.json | 2 +- .../components/zwave_js/translations/el.json | 20 +++++ .../zwave_js/translations/pt-BR.json | 57 +++++++++++- 499 files changed, 6620 insertions(+), 334 deletions(-) create mode 100644 homeassistant/components/acmeda/translations/pt-BR.json create mode 100644 homeassistant/components/adax/translations/pt-BR.json create mode 100644 homeassistant/components/advantage_air/translations/pt-BR.json create mode 100644 homeassistant/components/aemet/translations/pt-BR.json create mode 100644 homeassistant/components/airly/translations/pt-BR.json create mode 100644 homeassistant/components/airnow/translations/pt-BR.json create mode 100644 homeassistant/components/airthings/translations/pt-BR.json create mode 100644 homeassistant/components/airtouch4/translations/pt-BR.json create mode 100644 homeassistant/components/alarmdecoder/translations/pt-BR.json create mode 100644 homeassistant/components/ambee/translations/pt-BR.json create mode 100644 homeassistant/components/apple_tv/translations/pt-BR.json create mode 100644 homeassistant/components/aseko_pool_live/translations/pt-BR.json create mode 100644 homeassistant/components/asuswrt/translations/pt-BR.json create mode 100644 homeassistant/components/aurora/translations/pt-BR.json create mode 100644 homeassistant/components/aurora_abb_powerone/translations/pt-BR.json create mode 100644 homeassistant/components/aussie_broadband/translations/pt-BR.json create mode 100644 homeassistant/components/azure_event_hub/translations/pt-BR.json create mode 100644 homeassistant/components/balboa/translations/pt-BR.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/pt-BR.json create mode 100644 homeassistant/components/bosch_shc/translations/pt-BR.json create mode 100644 homeassistant/components/broadlink/translations/pt-BR.json create mode 100644 homeassistant/components/brunt/translations/pt-BR.json create mode 100644 homeassistant/components/buienradar/translations/pt-BR.json create mode 100644 homeassistant/components/canary/translations/pt-BR.json create mode 100644 homeassistant/components/climacell/translations/pt-BR.json create mode 100644 homeassistant/components/climacell/translations/sensor.pt-BR.json create mode 100644 homeassistant/components/cloudflare/translations/pt-BR.json create mode 100644 homeassistant/components/co2signal/translations/pt-BR.json create mode 100644 homeassistant/components/coinbase/translations/pt-BR.json create mode 100644 homeassistant/components/cpuspeed/translations/pt-BR.json create mode 100644 homeassistant/components/crownstone/translations/pt-BR.json create mode 100644 homeassistant/components/denonavr/translations/pt-BR.json create mode 100644 homeassistant/components/dexcom/translations/pt-BR.json create mode 100644 homeassistant/components/dlna_dmr/translations/pt-BR.json create mode 100644 homeassistant/components/dnsip/translations/pt-BR.json create mode 100644 homeassistant/components/dsmr/translations/pt-BR.json create mode 100644 homeassistant/components/dunehd/translations/pt-BR.json create mode 100644 homeassistant/components/eafm/translations/pt-BR.json create mode 100644 homeassistant/components/econet/translations/pt-BR.json create mode 100644 homeassistant/components/efergy/translations/pt-BR.json create mode 100644 homeassistant/components/elmax/translations/pt-BR.json create mode 100644 homeassistant/components/emonitor/translations/pt-BR.json create mode 100644 homeassistant/components/enocean/translations/pt-BR.json create mode 100644 homeassistant/components/enphase_envoy/translations/pt-BR.json create mode 100644 homeassistant/components/environment_canada/translations/pt-BR.json create mode 100644 homeassistant/components/epson/translations/pt-BR.json create mode 100644 homeassistant/components/evil_genius_labs/translations/pt-BR.json create mode 100644 homeassistant/components/ezviz/translations/pt-BR.json create mode 100644 homeassistant/components/faa_delays/translations/pt-BR.json create mode 100644 homeassistant/components/fireservicerota/translations/pt-BR.json create mode 100644 homeassistant/components/firmata/translations/pt-BR.json create mode 100644 homeassistant/components/fjaraskupan/translations/pt-BR.json create mode 100644 homeassistant/components/flipr/translations/pt-BR.json create mode 100644 homeassistant/components/flunearyou/translations/pt-BR.json create mode 100644 homeassistant/components/flux_led/translations/pt-BR.json create mode 100644 homeassistant/components/forecast_solar/translations/el.json create mode 100644 homeassistant/components/forecast_solar/translations/pt-BR.json create mode 100644 homeassistant/components/foscam/translations/pt-BR.json create mode 100644 homeassistant/components/freebox/translations/pt-BR.json create mode 100644 homeassistant/components/freedompro/translations/pt-BR.json create mode 100644 homeassistant/components/fritz/translations/pt-BR.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/el.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/pt-BR.json create mode 100644 homeassistant/components/fronius/translations/pt-BR.json create mode 100644 homeassistant/components/garages_amsterdam/translations/pt-BR.json create mode 100644 homeassistant/components/goalzero/translations/pt-BR.json create mode 100644 homeassistant/components/goodwe/translations/pt-BR.json create mode 100644 homeassistant/components/google_travel_time/translations/pt-BR.json create mode 100644 homeassistant/components/gree/translations/pt-BR.json create mode 100644 homeassistant/components/growatt_server/translations/pt-BR.json create mode 100644 homeassistant/components/guardian/translations/pt-BR.json create mode 100644 homeassistant/components/habitica/translations/pt-BR.json create mode 100644 homeassistant/components/hisense_aehw4a1/translations/pt-BR.json create mode 100644 homeassistant/components/hive/translations/pt-BR.json create mode 100644 homeassistant/components/hlk_sw16/translations/pt-BR.json create mode 100644 homeassistant/components/home_plus_control/translations/pt-BR.json create mode 100644 homeassistant/components/homekit_controller/translations/select.pt-BR.json create mode 100644 homeassistant/components/homewizard/translations/pt-BR.json create mode 100644 homeassistant/components/honeywell/translations/pt-BR.json create mode 100644 homeassistant/components/huisbaasje/translations/pt-BR.json create mode 100644 homeassistant/components/humidifier/translations/pt-BR.json create mode 100644 homeassistant/components/hvv_departures/translations/pt-BR.json create mode 100644 homeassistant/components/hyperion/translations/pt-BR.json create mode 100644 homeassistant/components/ialarm/translations/pt-BR.json create mode 100644 homeassistant/components/intellifire/translations/pt-BR.json create mode 100644 homeassistant/components/iotawatt/translations/pt-BR.json create mode 100644 homeassistant/components/islamic_prayer_times/translations/pt-BR.json create mode 100644 homeassistant/components/iss/translations/ca.json create mode 100644 homeassistant/components/iss/translations/de.json create mode 100644 homeassistant/components/iss/translations/el.json create mode 100644 homeassistant/components/iss/translations/et.json create mode 100644 homeassistant/components/iss/translations/hu.json create mode 100644 homeassistant/components/iss/translations/pt-BR.json create mode 100644 homeassistant/components/iss/translations/ru.json create mode 100644 homeassistant/components/iss/translations/tr.json create mode 100644 homeassistant/components/iss/translations/zh-Hant.json create mode 100644 homeassistant/components/izone/translations/pt-BR.json create mode 100644 homeassistant/components/jellyfin/translations/pt-BR.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/pt-BR.json create mode 100644 homeassistant/components/kmtronic/translations/pt-BR.json create mode 100644 homeassistant/components/knx/translations/pt-BR.json create mode 100644 homeassistant/components/kodi/translations/pt-BR.json create mode 100644 homeassistant/components/kostal_plenticore/translations/pt-BR.json create mode 100644 homeassistant/components/kraken/translations/pt-BR.json create mode 100644 homeassistant/components/kulersky/translations/pt-BR.json create mode 100644 homeassistant/components/launch_library/translations/pt-BR.json create mode 100644 homeassistant/components/litejet/translations/pt-BR.json create mode 100644 homeassistant/components/litterrobot/translations/pt-BR.json create mode 100644 homeassistant/components/lookin/translations/pt-BR.json create mode 100644 homeassistant/components/lyric/translations/pt-BR.json create mode 100644 homeassistant/components/mazda/translations/pt-BR.json create mode 100644 homeassistant/components/melcloud/translations/pt-BR.json create mode 100644 homeassistant/components/met_eireann/translations/pt-BR.json create mode 100644 homeassistant/components/meteoclimatic/translations/pt-BR.json create mode 100644 homeassistant/components/metoffice/translations/pt-BR.json create mode 100644 homeassistant/components/mill/translations/pt-BR.json create mode 100644 homeassistant/components/modem_callerid/translations/pt-BR.json create mode 100644 homeassistant/components/modern_forms/translations/pt-BR.json create mode 100644 homeassistant/components/mullvad/translations/pt-BR.json create mode 100644 homeassistant/components/mutesync/translations/pt-BR.json create mode 100644 homeassistant/components/mysensors/translations/pt-BR.json create mode 100644 homeassistant/components/nam/translations/pt-BR.json create mode 100644 homeassistant/components/nanoleaf/translations/pt-BR.json create mode 100644 homeassistant/components/neato/translations/pt-BR.json create mode 100644 homeassistant/components/nfandroidtv/translations/pt-BR.json create mode 100644 homeassistant/components/nina/translations/pt-BR.json create mode 100644 homeassistant/components/nmap_tracker/translations/pt-BR.json create mode 100644 homeassistant/components/nuki/translations/pt-BR.json create mode 100644 homeassistant/components/number/translations/el.json create mode 100644 homeassistant/components/nzbget/translations/pt-BR.json create mode 100644 homeassistant/components/octoprint/translations/pt-BR.json create mode 100644 homeassistant/components/omnilogic/translations/pt-BR.json create mode 100644 homeassistant/components/oncue/translations/pt-BR.json create mode 100644 homeassistant/components/ondilo_ico/translations/pt-BR.json create mode 100644 homeassistant/components/onewire/translations/pt-BR.json create mode 100644 homeassistant/components/opengarage/translations/el.json create mode 100644 homeassistant/components/opengarage/translations/pt-BR.json create mode 100644 homeassistant/components/opentherm_gw/translations/pt-BR.json create mode 100644 homeassistant/components/openweathermap/translations/pt-BR.json create mode 100644 homeassistant/components/overkiz/translations/pt-BR.json create mode 100644 homeassistant/components/overkiz/translations/select.pt-BR.json create mode 100644 homeassistant/components/ovo_energy/translations/pt-BR.json create mode 100644 homeassistant/components/ozw/translations/pt-BR.json create mode 100644 homeassistant/components/p1_monitor/translations/pt-BR.json create mode 100644 homeassistant/components/philips_js/translations/pt-BR.json create mode 100644 homeassistant/components/picnic/translations/pt-BR.json create mode 100644 homeassistant/components/plum_lightpad/translations/pt-BR.json create mode 100644 homeassistant/components/poolsense/translations/pt-BR.json create mode 100644 homeassistant/components/profiler/translations/pt-BR.json create mode 100644 homeassistant/components/progettihwsw/translations/pt-BR.json create mode 100644 homeassistant/components/prosegur/translations/pt-BR.json create mode 100644 homeassistant/components/pvoutput/translations/pt-BR.json create mode 100644 homeassistant/components/rachio/translations/pt-BR.json create mode 100644 homeassistant/components/rainforest_eagle/translations/pt-BR.json create mode 100644 homeassistant/components/rdw/translations/pt-BR.json create mode 100644 homeassistant/components/recollect_waste/translations/pt-BR.json create mode 100644 homeassistant/components/renault/translations/pt-BR.json create mode 100644 homeassistant/components/rfxtrx/translations/pt-BR.json create mode 100644 homeassistant/components/risco/translations/pt-BR.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/pt-BR.json create mode 100644 homeassistant/components/rpi_power/translations/pt-BR.json create mode 100644 homeassistant/components/rtsp_to_webrtc/translations/pt-BR.json create mode 100644 homeassistant/components/ruckus_unleashed/translations/pt-BR.json create mode 100644 homeassistant/components/samsungtv/translations/pt-BR.json create mode 100644 homeassistant/components/screenlogic/translations/pt-BR.json create mode 100644 homeassistant/components/senseme/translations/pt-BR.json create mode 100644 homeassistant/components/sensibo/translations/pt-BR.json create mode 100644 homeassistant/components/sharkiq/translations/pt-BR.json create mode 100644 homeassistant/components/shelly/translations/pt-BR.json create mode 100644 homeassistant/components/sia/translations/pt-BR.json create mode 100644 homeassistant/components/sma/translations/pt-BR.json create mode 100644 homeassistant/components/smart_meter_texas/translations/pt-BR.json create mode 100644 homeassistant/components/smarttub/translations/pt-BR.json create mode 100644 homeassistant/components/sms/translations/pt-BR.json create mode 100644 homeassistant/components/solaredge/translations/pt-BR.json create mode 100644 homeassistant/components/solarlog/translations/pt-BR.json create mode 100644 homeassistant/components/solax/translations/pt-BR.json create mode 100644 homeassistant/components/somfy_mylink/translations/pt-BR.json create mode 100644 homeassistant/components/sonarr/translations/pt-BR.json create mode 100644 homeassistant/components/speedtestdotnet/translations/pt-BR.json create mode 100644 homeassistant/components/spider/translations/pt-BR.json create mode 100644 homeassistant/components/spotify/translations/pt-BR.json create mode 100644 homeassistant/components/squeezebox/translations/pt-BR.json create mode 100644 homeassistant/components/srp_energy/translations/pt-BR.json create mode 100644 homeassistant/components/steamist/translations/pt-BR.json create mode 100644 homeassistant/components/stookalert/translations/pt-BR.json create mode 100644 homeassistant/components/subaru/translations/pt-BR.json create mode 100644 homeassistant/components/switchbot/translations/pt-BR.json create mode 100644 homeassistant/components/switcher_kis/translations/pt-BR.json create mode 100644 homeassistant/components/syncthing/translations/pt-BR.json create mode 100644 homeassistant/components/syncthru/translations/pt-BR.json create mode 100644 homeassistant/components/system_bridge/translations/pt-BR.json create mode 100644 homeassistant/components/tailscale/translations/el.json create mode 100644 homeassistant/components/tailscale/translations/pt-BR.json create mode 100644 homeassistant/components/tasmota/translations/pt-BR.json create mode 100644 homeassistant/components/tesla_wall_connector/translations/pt-BR.json create mode 100644 homeassistant/components/tibber/translations/pt-BR.json create mode 100644 homeassistant/components/tile/translations/pt-BR.json create mode 100644 homeassistant/components/tolo/translations/pt-BR.json create mode 100644 homeassistant/components/tractive/translations/pt-BR.json create mode 100644 homeassistant/components/tractive/translations/sensor.el.json create mode 100644 homeassistant/components/tractive/translations/sensor.pt-BR.json create mode 100644 homeassistant/components/trafikverket_weatherstation/translations/pt-BR.json create mode 100644 homeassistant/components/twinkly/translations/pt-BR.json create mode 100644 homeassistant/components/upcloud/translations/pt-BR.json create mode 100644 homeassistant/components/uptimerobot/translations/pt-BR.json create mode 100644 homeassistant/components/uptimerobot/translations/sensor.pt-BR.json create mode 100644 homeassistant/components/vallox/translations/pt-BR.json create mode 100644 homeassistant/components/velbus/translations/pt-BR.json create mode 100644 homeassistant/components/venstar/translations/pt-BR.json create mode 100644 homeassistant/components/verisure/translations/pt-BR.json create mode 100644 homeassistant/components/version/translations/pt-BR.json create mode 100644 homeassistant/components/vicare/translations/pt-BR.json create mode 100644 homeassistant/components/volumio/translations/pt-BR.json create mode 100644 homeassistant/components/wallbox/translations/pt-BR.json create mode 100644 homeassistant/components/waze_travel_time/translations/pt-BR.json create mode 100644 homeassistant/components/webostv/translations/pt-BR.json create mode 100644 homeassistant/components/whois/translations/pt-BR.json create mode 100644 homeassistant/components/wilight/translations/pt-BR.json create mode 100644 homeassistant/components/wled/translations/pt-BR.json create mode 100644 homeassistant/components/wled/translations/select.el.json create mode 100644 homeassistant/components/xbox/translations/pt-BR.json create mode 100644 homeassistant/components/xiaomi_aqara/translations/pt-BR.json create mode 100644 homeassistant/components/yamaha_musiccast/translations/pt-BR.json create mode 100644 homeassistant/components/yamaha_musiccast/translations/select.pt-BR.json create mode 100644 homeassistant/components/yeelight/translations/pt-BR.json create mode 100644 homeassistant/components/youless/translations/pt-BR.json create mode 100644 homeassistant/components/zoneminder/translations/pt-BR.json diff --git a/homeassistant/components/abode/translations/pt-BR.json b/homeassistant/components/abode/translations/pt-BR.json index 4c61f5d243d9b8..dc83ae8dc5cac0 100644 --- a/homeassistant/components/abode/translations/pt-BR.json +++ b/homeassistant/components/abode/translations/pt-BR.json @@ -1,9 +1,19 @@ { "config": { "abort": { - "single_instance_allowed": "Somente uma \u00fanica configura\u00e7\u00e3o de Abode \u00e9 permitida." + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, "step": { + "reauth_confirm": { + "data": { + "password": "Senha" + } + }, "user": { "data": { "password": "Senha", diff --git a/homeassistant/components/abode/translations/zh-Hant.json b/homeassistant/components/abode/translations/zh-Hant.json index 6725df44451628..9a7136223b962c 100644 --- a/homeassistant/components/abode/translations/zh-Hant.json +++ b/homeassistant/components/abode/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", diff --git a/homeassistant/components/accuweather/translations/el.json b/homeassistant/components/accuweather/translations/el.json index 20c3b56bd60c73..0cca8117080fc0 100644 --- a/homeassistant/components/accuweather/translations/el.json +++ b/homeassistant/components/accuweather/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "requests_exceeded": "\u0388\u03c7\u03b5\u03b9 \u03be\u03b5\u03c0\u03b5\u03c1\u03b1\u03c3\u03c4\u03b5\u03af \u03bf \u03b5\u03c0\u03b9\u03c4\u03c1\u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03bf\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03b1\u03b9\u03c4\u03ae\u03c3\u03b5\u03c9\u03bd \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf API \u03c4\u03bf\u03c5 Accuweather. \u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b5\u03c1\u03b9\u03bc\u03ad\u03bd\u03b5\u03c4\u03b5 \u03ae \u03bd\u03b1 \u03b1\u03bb\u03bb\u03ac\u03be\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API." + }, "step": { "user": { "description": "\u0391\u03bd \u03c7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c3\u03c4\u03b5 \u03b2\u03bf\u03ae\u03b8\u03b5\u03b9\u03b1 \u03bc\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7, \u03c1\u03af\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03bc\u03b1\u03c4\u03b9\u03ac \u03b5\u03b4\u03ce: https://www.home-assistant.io/integrations/accuweather/\n\n\u039f\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03b9 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b5\u03c2 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf\u03b9 \u03b1\u03c0\u03cc \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae. \u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c4\u03bf\u03c5\u03c2 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c3\u03c4\u03bf \u03bc\u03b7\u03c4\u03c1\u03ce\u03bf \u03bf\u03bd\u03c4\u03bf\u03c4\u03ae\u03c4\u03c9\u03bd \u03bc\u03b5\u03c4\u03ac \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2.\n\u0397 \u03c0\u03c1\u03cc\u03b3\u03bd\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 \u03b1\u03c0\u03cc \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae. \u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c4\u03b7\u03bd \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c3\u03c4\u03b9\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2.", @@ -12,7 +15,9 @@ "user": { "data": { "forecast": "\u03a0\u03c1\u03cc\u03b3\u03bd\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd" - } + }, + "description": "\u039b\u03cc\u03b3\u03c9 \u03c4\u03c9\u03bd \u03c0\u03b5\u03c1\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03ce\u03bd \u03c4\u03b7\u03c2 \u03b4\u03c9\u03c1\u03b5\u03ac\u03bd \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd API \u03c4\u03bf\u03c5 AccuWeather, \u03cc\u03c4\u03b1\u03bd \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03c1\u03cc\u03b3\u03bd\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd, \u03bf\u03b9 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03b5\u03b9\u03c2 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd \u03b8\u03b1 \u03c0\u03c1\u03b1\u03b3\u03bc\u03b1\u03c4\u03bf\u03c0\u03bf\u03b9\u03bf\u03cd\u03bd\u03c4\u03b1\u03b9 \u03ba\u03ac\u03b8\u03b5 80 \u03bb\u03b5\u03c0\u03c4\u03ac \u03b1\u03bd\u03c4\u03af \u03b3\u03b9\u03b1 \u03ba\u03ac\u03b8\u03b5 40 \u03bb\u03b5\u03c0\u03c4\u03ac.", + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 AccuWeather" } } }, diff --git a/homeassistant/components/accuweather/translations/pt-BR.json b/homeassistant/components/accuweather/translations/pt-BR.json index 75111f9892daf3..f4ac44d3b60b75 100644 --- a/homeassistant/components/accuweather/translations/pt-BR.json +++ b/homeassistant/components/accuweather/translations/pt-BR.json @@ -1,11 +1,19 @@ { "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_api_key": "Chave de API inv\u00e1lida" + }, "step": { "user": { "data": { "api_key": "Chave API", "latitude": "Latitude", - "longitude": "Longitude" + "longitude": "Longitude", + "name": "Nome" }, "title": "AccuWeather" } diff --git a/homeassistant/components/accuweather/translations/zh-Hant.json b/homeassistant/components/accuweather/translations/zh-Hant.json index 11df415d4c9631..fbf72991e92acb 100644 --- a/homeassistant/components/accuweather/translations/zh-Hant.json +++ b/homeassistant/components/accuweather/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", diff --git a/homeassistant/components/acmeda/translations/pt-BR.json b/homeassistant/components/acmeda/translations/pt-BR.json new file mode 100644 index 00000000000000..aa5c22db175e11 --- /dev/null +++ b/homeassistant/components/acmeda/translations/pt-BR.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/adax/translations/pt-BR.json b/homeassistant/components/adax/translations/pt-BR.json new file mode 100644 index 00000000000000..4bde04e1695745 --- /dev/null +++ b/homeassistant/components/adax/translations/pt-BR.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "cloud": { + "data": { + "password": "Senha" + } + }, + "user": { + "data": { + "host": "Nome do host", + "password": "Senha" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/adguard/translations/pt-BR.json b/homeassistant/components/adguard/translations/pt-BR.json index 5d291f4cadb647..aa33b469b1b1cb 100644 --- a/homeassistant/components/adguard/translations/pt-BR.json +++ b/homeassistant/components/adguard/translations/pt-BR.json @@ -1,8 +1,12 @@ { "config": { "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado", "existing_instance_updated": "Configura\u00e7\u00e3o existente atualizada." }, + "error": { + "cannot_connect": "Falha ao conectar" + }, "step": { "hassio_confirm": { "description": "Deseja configurar o Home Assistant para se conectar ao AdGuard Home fornecido pelo complemento Supervisor: {addon} ?", @@ -10,10 +14,12 @@ }, "user": { "data": { + "host": "Nome do host", "password": "Senha", - "ssl": "O AdGuard Home usa um certificado SSL", + "port": "Porta", + "ssl": "Usar um certificado SSL", "username": "Usu\u00e1rio", - "verify_ssl": "O AdGuard Home usa um certificado apropriado" + "verify_ssl": "Verifique o certificado SSL" }, "description": "Configure sua inst\u00e2ncia do AdGuard Home para permitir o monitoramento e o controle." } diff --git a/homeassistant/components/advantage_air/translations/pt-BR.json b/homeassistant/components/advantage_air/translations/pt-BR.json new file mode 100644 index 00000000000000..dcf9a44ea4a701 --- /dev/null +++ b/homeassistant/components/advantage_air/translations/pt-BR.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "ip_address": "Endere\u00e7o IP", + "port": "Porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aemet/translations/pt-BR.json b/homeassistant/components/aemet/translations/pt-BR.json new file mode 100644 index 00000000000000..51e8d3fd84e1a0 --- /dev/null +++ b/homeassistant/components/aemet/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Localiza\u00e7\u00e3o j\u00e1 est\u00e1 configurada" + }, + "error": { + "invalid_api_key": "Chave de API inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "api_key": "Chave da API", + "latitude": "Latitude", + "longitude": "Longitude" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/agent_dvr/translations/pt-BR.json b/homeassistant/components/agent_dvr/translations/pt-BR.json index 0077ceddd4605c..e4203d7c17737d 100644 --- a/homeassistant/components/agent_dvr/translations/pt-BR.json +++ b/homeassistant/components/agent_dvr/translations/pt-BR.json @@ -1,8 +1,16 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "cannot_connect": "Falha ao conectar" + }, "step": { "user": { "data": { + "host": "Nome do host", "port": "Porta" } } diff --git a/homeassistant/components/airly/translations/pt-BR.json b/homeassistant/components/airly/translations/pt-BR.json new file mode 100644 index 00000000000000..abe96f8cccc5f2 --- /dev/null +++ b/homeassistant/components/airly/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Localiza\u00e7\u00e3o j\u00e1 est\u00e1 configurada" + }, + "error": { + "invalid_api_key": "Chave de API inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "api_key": "Chave da API", + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/pt-BR.json b/homeassistant/components/airnow/translations/pt-BR.json new file mode 100644 index 00000000000000..acc62eaa7d8140 --- /dev/null +++ b/homeassistant/components/airnow/translations/pt-BR.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "api_key": "Chave da API", + "latitude": "Latitude", + "longitude": "Longitude" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airthings/translations/pt-BR.json b/homeassistant/components/airthings/translations/pt-BR.json new file mode 100644 index 00000000000000..88693aaab09507 --- /dev/null +++ b/homeassistant/components/airthings/translations/pt-BR.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "description": "Fa\u00e7a login em {url} para encontrar suas credenciais", + "id": "ID", + "secret": "Segredo" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airtouch4/translations/pt-BR.json b/homeassistant/components/airtouch4/translations/pt-BR.json new file mode 100644 index 00000000000000..fb9b1f4c79ebf5 --- /dev/null +++ b/homeassistant/components/airtouch4/translations/pt-BR.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/el.json b/homeassistant/components/airvisual/translations/el.json index 9c9a715e5b1f21..58431a4d313a20 100644 --- a/homeassistant/components/airvisual/translations/el.json +++ b/homeassistant/components/airvisual/translations/el.json @@ -7,6 +7,13 @@ "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "step": { + "geography_by_name": { + "data": { + "country": "\u03a7\u03ce\u03c1\u03b1", + "state": "\u03ba\u03c1\u03ac\u03c4\u03bf\u03c2" + }, + "description": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf AirVisual cloud API \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03b5\u03af\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03cc\u03bb\u03b7/\u03c0\u03bf\u03bb\u03b9\u03c4\u03b5\u03af\u03b1/\u03c7\u03ce\u03c1\u03b1." + }, "node_pro": { "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c9\u03c0\u03b9\u03ba\u03ae \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 AirVisual. \u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b1\u03bd\u03b1\u03ba\u03c4\u03b7\u03b8\u03b5\u03af \u03b1\u03c0\u03cc \u03c4\u03bf UI \u03c4\u03b7\u03c2 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1\u03c2.", "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03bd\u03cc\u03c2 \u03ba\u03cc\u03bc\u03b2\u03bf\u03c5 AirVisual Node/Pro" diff --git a/homeassistant/components/airvisual/translations/pt-BR.json b/homeassistant/components/airvisual/translations/pt-BR.json index 733411f2465f9b..b9db19d0346602 100644 --- a/homeassistant/components/airvisual/translations/pt-BR.json +++ b/homeassistant/components/airvisual/translations/pt-BR.json @@ -1,14 +1,37 @@ { "config": { + "abort": { + "already_configured": "Localiza\u00e7\u00e3o j\u00e1 est\u00e1 configurada", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, "error": { - "general_error": "Ocorreu um erro desconhecido.", - "invalid_api_key": "Chave de API fornecida \u00e9 inv\u00e1lida." + "cannot_connect": "Falha ao conectar", + "general_error": "Erro inesperado", + "invalid_api_key": "Chave de API inv\u00e1lida" }, "step": { + "geography_by_coords": { + "data": { + "api_key": "Chave da API", + "latitude": "Latitude", + "longitude": "Longitude" + } + }, + "geography_by_name": { + "data": { + "api_key": "Chave da API" + } + }, "node_pro": { "data": { + "ip_address": "Nome do host", "password": "Senha" } + }, + "reauth_confirm": { + "data": { + "api_key": "Chave da API" + } } } } diff --git a/homeassistant/components/alarmdecoder/translations/pt-BR.json b/homeassistant/components/alarmdecoder/translations/pt-BR.json new file mode 100644 index 00000000000000..8caf8d08cece01 --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/pt-BR.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "protocol": { + "data": { + "host": "Nome do host", + "port": "Porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/almond/translations/pt-BR.json b/homeassistant/components/almond/translations/pt-BR.json index 94dfbefb86a22c..d6ddde76595224 100644 --- a/homeassistant/components/almond/translations/pt-BR.json +++ b/homeassistant/components/almond/translations/pt-BR.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "cannot_connect": "Falha ao conectar", + "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", + "no_url_available": "N\u00e3o h\u00e1 URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a se\u00e7\u00e3o de ajuda]({docs_url})", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, "step": { "pick_implementation": { "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" diff --git a/homeassistant/components/almond/translations/zh-Hant.json b/homeassistant/components/almond/translations/zh-Hant.json index 9606a440aab9ae..d5139fcb8b8120 100644 --- a/homeassistant/components/almond/translations/zh-Hant.json +++ b/homeassistant/components/almond/translations/zh-Hant.json @@ -4,7 +4,7 @@ "cannot_connect": "\u9023\u7dda\u5931\u6557", "missing_configuration": "\u5143\u4ef6\u5c1a\u672a\u8a2d\u7f6e\uff0c\u8acb\u53c3\u95b1\u6587\u4ef6\u8aaa\u660e\u3002", "no_url_available": "\u6c92\u6709\u53ef\u7528\u7684\u7db2\u5740\u3002\u95dc\u65bc\u6b64\u932f\u8aa4\u66f4\u8a73\u7d30\u8a0a\u606f\uff0c[\u9ede\u9078\u5354\u52a9\u7ae0\u7bc0]({docs_url})", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "step": { "hassio_confirm": { diff --git a/homeassistant/components/ambee/translations/pt-BR.json b/homeassistant/components/ambee/translations/pt-BR.json new file mode 100644 index 00000000000000..03e813c18ff31a --- /dev/null +++ b/homeassistant/components/ambee/translations/pt-BR.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_api_key": "Chave de API inv\u00e1lida" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Chave da API" + } + }, + "user": { + "data": { + "api_key": "Chave da API", + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambiclimate/translations/pt-BR.json b/homeassistant/components/ambiclimate/translations/pt-BR.json index 466096416ae665..9f6290dc49227e 100644 --- a/homeassistant/components/ambiclimate/translations/pt-BR.json +++ b/homeassistant/components/ambiclimate/translations/pt-BR.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "access_token": "Erro desconhecido ao gerar um token de acesso." + "access_token": "Erro desconhecido ao gerar um token de acesso.", + "already_configured": "A conta j\u00e1 foi configurada" }, "create_entry": { - "default": "Autenticado com sucesso no Ambiclimate" + "default": "Autenticado com sucesso" }, "error": { "follow_link": "Por favor, siga o link e autentique-se antes de pressionar Enviar", diff --git a/homeassistant/components/ambient_station/translations/pt-BR.json b/homeassistant/components/ambient_station/translations/pt-BR.json index d3ac36bf0e2f83..ce7a38d0867472 100644 --- a/homeassistant/components/ambient_station/translations/pt-BR.json +++ b/homeassistant/components/ambient_station/translations/pt-BR.json @@ -1,13 +1,16 @@ { "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" + }, "error": { - "invalid_key": "Chave de API e / ou chave de aplicativo inv\u00e1lidas", + "invalid_key": "Chave de API inv\u00e1lida", "no_devices": "Nenhum dispositivo encontrado na conta" }, "step": { "user": { "data": { - "api_key": "Chave API", + "api_key": "Chave da API", "app_key": "Chave de aplicativo" }, "title": "Preencha suas informa\u00e7\u00f5es" diff --git a/homeassistant/components/androidtv/translations/pt-BR.json b/homeassistant/components/androidtv/translations/pt-BR.json index bccf453eb0b7b1..b5b2f9fc0c289b 100644 --- a/homeassistant/components/androidtv/translations/pt-BR.json +++ b/homeassistant/components/androidtv/translations/pt-BR.json @@ -1,10 +1,62 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido", + "unknown": "Erro inesperado" + }, "step": { "user": { "data": { - "device_class": "O tipo de dispositivo" - } + "adb_server_ip": "Endere\u00e7o IP do servidor ADB (deixe em branco para n\u00e3o usar)", + "adb_server_port": "Porta do servidor ADB", + "adbkey": "Caminho para o arquivo de chave ADB (deixe em branco para gerar automaticamente)", + "device_class": "O tipo de dispositivo", + "host": "Nome do host", + "port": "Porta" + }, + "description": "Defina os par\u00e2metros necess\u00e1rios para se conectar ao seu dispositivo Android TV", + "title": "AndroidTV" + } + } + }, + "options": { + "error": { + "invalid_det_rules": "Regras de detec\u00e7\u00e3o de estado inv\u00e1lidas" + }, + "step": { + "apps": { + "data": { + "app_delete": "Marque para excluir este aplicativo", + "app_id": "ID do aplicativo", + "app_name": "Nome do aplicativo" + }, + "description": "Configurar o ID do aplicativo {app_id}", + "title": "Configurar aplicativos da Android TV" + }, + "init": { + "data": { + "apps": "Configurar lista de aplicativos", + "exclude_unnamed_apps": "Excluir aplicativos com nome desconhecido da lista de fontes", + "get_sources": "Recupere os aplicativos em execu\u00e7\u00e3o como a lista de fontes", + "screencap": "Use a captura de tela para a arte do \u00e1lbum", + "state_detection_rules": "Configurar regras de detec\u00e7\u00e3o de estado", + "turn_off_command": "Comando de desligamento do shell ADB (deixe vazio por padr\u00e3o)", + "turn_on_command": "Comando de ativa\u00e7\u00e3o do shell ADB (deixe vazio por padr\u00e3o)" + }, + "title": "Op\u00e7\u00f5es de TV Android" + }, + "rules": { + "data": { + "rule_delete": "Marque para excluir esta regra", + "rule_id": "ID do aplicativo", + "rule_values": "Lista de regras de detec\u00e7\u00e3o de estado (consulte a documenta\u00e7\u00e3o)" + }, + "description": "Configure a regra de detec\u00e7\u00e3o para o ID do aplicativo {rule_id}", + "title": "Configurar regras de detec\u00e7\u00e3o de estado do Android TV" } } } diff --git a/homeassistant/components/apple_tv/translations/el.json b/homeassistant/components/apple_tv/translations/el.json index 37b07361ad838d..d446d618746b82 100644 --- a/homeassistant/components/apple_tv/translations/el.json +++ b/homeassistant/components/apple_tv/translations/el.json @@ -30,7 +30,8 @@ "title": "\u0391\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "protocol_disabled": { - "description": "\u0391\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7 \u03b3\u03b9\u03b1 \u03c4\u03bf `{protocol}` \u03b1\u03bb\u03bb\u03ac \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03bf\u03c5\u03c2 \u03c0\u03b9\u03b8\u03b1\u03bd\u03bf\u03cd\u03c2 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 (\u03c0.\u03c7. \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c8\u03c4\u03b5 \u03c3\u03b5 \u03cc\u03bb\u03b5\u03c2 \u03c4\u03b9\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03c4\u03bf\u03c0\u03b9\u03ba\u03cc \u03b4\u03af\u03ba\u03c4\u03c5\u03bf \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03bf\u03bd\u03c4\u03b1\u03b9) \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae. \n\n \u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03c4\u03b5 \u03c7\u03c9\u03c1\u03af\u03c2 \u03bd\u03b1 \u03ba\u03ac\u03bd\u03b5\u03c4\u03b5 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7 \u03b1\u03c5\u03c4\u03bf\u03cd \u03c4\u03bf\u03c5 \u03c0\u03c1\u03c9\u03c4\u03bf\u03ba\u03cc\u03bb\u03bb\u03bf\u03c5, \u03b1\u03bb\u03bb\u03ac \u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b5\u03c2 \u03b8\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b5\u03c2." + "description": "\u0391\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7 \u03b3\u03b9\u03b1 \u03c4\u03bf `{protocol}` \u03b1\u03bb\u03bb\u03ac \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03bf\u03c5\u03c2 \u03c0\u03b9\u03b8\u03b1\u03bd\u03bf\u03cd\u03c2 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 (\u03c0.\u03c7. \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c8\u03c4\u03b5 \u03c3\u03b5 \u03cc\u03bb\u03b5\u03c2 \u03c4\u03b9\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03c4\u03bf\u03c0\u03b9\u03ba\u03cc \u03b4\u03af\u03ba\u03c4\u03c5\u03bf \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03bf\u03bd\u03c4\u03b1\u03b9) \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae. \n\n \u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03c4\u03b5 \u03c7\u03c9\u03c1\u03af\u03c2 \u03bd\u03b1 \u03ba\u03ac\u03bd\u03b5\u03c4\u03b5 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7 \u03b1\u03c5\u03c4\u03bf\u03cd \u03c4\u03bf\u03c5 \u03c0\u03c1\u03c9\u03c4\u03bf\u03ba\u03cc\u03bb\u03bb\u03bf\u03c5, \u03b1\u03bb\u03bb\u03ac \u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b5\u03c2 \u03b8\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b5\u03c2.", + "title": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7" }, "reconfigure": { "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b1\u03c6\u03ad\u03c1\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03b9\u03ba\u03cc\u03c4\u03b7\u03c4\u03ac \u03c4\u03b7\u03c2.", diff --git a/homeassistant/components/apple_tv/translations/pt-BR.json b/homeassistant/components/apple_tv/translations/pt-BR.json new file mode 100644 index 00000000000000..5e0497e76c0066 --- /dev/null +++ b/homeassistant/components/apple_tv/translations/pt-BR.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_configured_device": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", + "unknown": "Erro inesperado" + }, + "error": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "unknown": "Erro inesperado" + }, + "step": { + "pair_with_pin": { + "data": { + "pin": "C\u00f3digo PIN" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/pt-BR.json b/homeassistant/components/arcam_fmj/translations/pt-BR.json index 8071efb001f35d..58279f9bff9a15 100644 --- a/homeassistant/components/arcam_fmj/translations/pt-BR.json +++ b/homeassistant/components/arcam_fmj/translations/pt-BR.json @@ -1,4 +1,19 @@ { + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "port": "Porta" + } + } + } + }, "device_automation": { "trigger_type": { "turn_on": "Foi solicitado que {entity_name} ligue" diff --git a/homeassistant/components/aseko_pool_live/translations/pt-BR.json b/homeassistant/components/aseko_pool_live/translations/pt-BR.json new file mode 100644 index 00000000000000..67dcf497bc0c6d --- /dev/null +++ b/homeassistant/components/aseko_pool_live/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Senha" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/pt-BR.json b/homeassistant/components/asuswrt/translations/pt-BR.json new file mode 100644 index 00000000000000..07f99344a4a595 --- /dev/null +++ b/homeassistant/components/asuswrt/translations/pt-BR.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "name": "Nome", + "password": "Senha", + "port": "Porta", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/zh-Hant.json b/homeassistant/components/asuswrt/translations/zh-Hant.json index 7aabf592ee3834..d0997e495c5fa3 100644 --- a/homeassistant/components/asuswrt/translations/zh-Hant.json +++ b/homeassistant/components/asuswrt/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", diff --git a/homeassistant/components/atag/translations/pt-BR.json b/homeassistant/components/atag/translations/pt-BR.json index 5d9d507911090a..2d6380e59be604 100644 --- a/homeassistant/components/atag/translations/pt-BR.json +++ b/homeassistant/components/atag/translations/pt-BR.json @@ -1,7 +1,18 @@ { "config": { "abort": { - "already_configured": "Este dispositivo j\u00e1 foi adicionado ao Home Assistant" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "port": "Porta" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/august/translations/pt-BR.json b/homeassistant/components/august/translations/pt-BR.json index 7186be6216cba8..61185dc14f795d 100644 --- a/homeassistant/components/august/translations/pt-BR.json +++ b/homeassistant/components/august/translations/pt-BR.json @@ -1,6 +1,26 @@ { "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, "step": { + "reauth_validate": { + "data": { + "password": "Senha" + } + }, + "user_validate": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + }, "validation": { "data": { "code": "C\u00f3digo de verifica\u00e7\u00e3o" diff --git a/homeassistant/components/aurora/translations/pt-BR.json b/homeassistant/components/aurora/translations/pt-BR.json new file mode 100644 index 00000000000000..7d9ce5c434dac4 --- /dev/null +++ b/homeassistant/components/aurora/translations/pt-BR.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aurora_abb_powerone/translations/pt-BR.json b/homeassistant/components/aurora_abb_powerone/translations/pt-BR.json new file mode 100644 index 00000000000000..d81a4031129b0c --- /dev/null +++ b/homeassistant/components/aurora_abb_powerone/translations/pt-BR.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "unknown": "Erro inesperado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aussie_broadband/translations/pt-BR.json b/homeassistant/components/aussie_broadband/translations/pt-BR.json new file mode 100644 index 00000000000000..c2b3b7e15689f3 --- /dev/null +++ b/homeassistant/components/aussie_broadband/translations/pt-BR.json @@ -0,0 +1,50 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "no_services_found": "Nenhum servi\u00e7o foi encontrado para esta conta", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "reauth": { + "data": { + "password": "Senha" + }, + "description": "Atualizar senha para {nome de usu\u00e1rio}", + "title": "Reautenticar Integra\u00e7\u00e3o" + }, + "service": { + "data": { + "services": "Servi\u00e7os" + }, + "title": "Selecionar servi\u00e7os" + }, + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + }, + "options": { + "abort": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "init": { + "data": { + "services": "Servi\u00e7os" + }, + "title": "Selecionar servi\u00e7os" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/awair/translations/pt-BR.json b/homeassistant/components/awair/translations/pt-BR.json index 6cec4b5050d658..ad86023f8189c0 100644 --- a/homeassistant/components/awair/translations/pt-BR.json +++ b/homeassistant/components/awair/translations/pt-BR.json @@ -1,7 +1,25 @@ { "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, "error": { - "invalid_access_token": "token de acesso invalido" + "invalid_access_token": "Token de acesso inv\u00e1lido", + "unknown": "Erro inesperado" + }, + "step": { + "reauth": { + "data": { + "access_token": "Token de acesso" + } + }, + "user": { + "data": { + "access_token": "Token de acesso" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/pt-BR.json b/homeassistant/components/axis/translations/pt-BR.json index 86b6d408baa7fc..7c25606016e4b4 100644 --- a/homeassistant/components/axis/translations/pt-BR.json +++ b/homeassistant/components/axis/translations/pt-BR.json @@ -1,19 +1,21 @@ { "config": { "abort": { - "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "link_local_address": "Link de endere\u00e7os locais n\u00e3o s\u00e3o suportados", "not_axis_device": "Dispositivo descoberto n\u00e3o \u00e9 um dispositivo Axis" }, "error": { - "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", - "already_in_progress": "O fluxo de configura\u00e7\u00e3o para o dispositivo j\u00e1 est\u00e1 em andamento." + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, "flow_title": "Eixos do dispositivo: {name} ({host})", "step": { "user": { "data": { - "host": "Host", + "host": "Nome do host", "password": "Senha", "port": "Porta", "username": "Usu\u00e1rio" diff --git a/homeassistant/components/azure_devops/translations/el.json b/homeassistant/components/azure_devops/translations/el.json index bde76bff399e37..5f8926f1a51d54 100644 --- a/homeassistant/components/azure_devops/translations/el.json +++ b/homeassistant/components/azure_devops/translations/el.json @@ -1,13 +1,25 @@ { "config": { + "error": { + "project_error": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03bb\u03ae\u03c8\u03b7 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03b9\u03ce\u03bd \u03ad\u03c1\u03b3\u03bf\u03c5." + }, + "flow_title": "{project_url}", "step": { "reauth": { + "data": { + "personal_access_token": "\u03a0\u03c1\u03bf\u03c3\u03c9\u03c0\u03b9\u03ba\u03cc \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 (\u03a0\u0394\u03a0)" + }, + "description": "\u039f \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b3\u03b9\u03b1 \u03c4\u03bf {project_url}. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c4\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2.", "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7" }, "user": { "data": { - "organization": "\u039f\u03c1\u03b3\u03ac\u03bd\u03c9\u03c3\u03b7" - } + "organization": "\u039f\u03c1\u03b3\u03ac\u03bd\u03c9\u03c3\u03b7", + "personal_access_token": "\u03a0\u03c1\u03bf\u03c3\u03c9\u03c0\u03b9\u03ba\u03cc \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 (\u03a0\u0394\u03a0)", + "project": "\u0395\u03c1\u03b3\u03bf" + }, + "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03b5\u03c1\u03af\u03c0\u03c4\u03c9\u03c3\u03b7 Azure DevOps \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03c0\u03bf\u03ba\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7 \u03c3\u03c4\u03bf \u03ad\u03c1\u03b3\u03bf \u03c3\u03b1\u03c2. \u0388\u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c9\u03c0\u03b9\u03ba\u03cc \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03bc\u03cc\u03bd\u03bf \u03b3\u03b9\u03b1 \u03ad\u03bd\u03b1 \u03b9\u03b4\u03b9\u03c9\u03c4\u03b9\u03ba\u03cc \u03ad\u03c1\u03b3\u03bf.", + "title": "\u03a0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03ad\u03c1\u03b3\u03bf\u03c5 Azure DevOps" } } } diff --git a/homeassistant/components/azure_devops/translations/pt-BR.json b/homeassistant/components/azure_devops/translations/pt-BR.json index 510159829cb20b..859c87db3fb5c1 100644 --- a/homeassistant/components/azure_devops/translations/pt-BR.json +++ b/homeassistant/components/azure_devops/translations/pt-BR.json @@ -1,6 +1,12 @@ { "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "project_error": "N\u00e3o foi poss\u00edvel obter informa\u00e7\u00f5es do projeto." }, "step": { diff --git a/homeassistant/components/azure_event_hub/translations/pt-BR.json b/homeassistant/components/azure_event_hub/translations/pt-BR.json new file mode 100644 index 00000000000000..65502dcaa23a91 --- /dev/null +++ b/homeassistant/components/azure_event_hub/translations/pt-BR.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/azure_event_hub/translations/zh-Hant.json b/homeassistant/components/azure_event_hub/translations/zh-Hant.json index 64f713f5bd84b7..a963a7ab4861b3 100644 --- a/homeassistant/components/azure_event_hub/translations/zh-Hant.json +++ b/homeassistant/components/azure_event_hub/translations/zh-Hant.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "cannot_connect": "\u4f7f\u7528 configuration.yaml \u6240\u5305\u542b\u6191\u8b49\u9023\u7dda\u5931\u6557\uff0c\u8acb\u81ea Yaml \u79fb\u9664\u8a72\u6191\u8b49\u4e26\u4f7f\u7528\u8a2d\u5b9a\u6d41\u7a0b\u65b9\u5f0f\u3002", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "unknown": "\u4f7f\u7528 configuration.yaml \u6240\u5305\u542b\u6191\u8b49\u9023\u7dda\u767c\u751f\u672a\u77e5\u932f\u8aa4\uff0c\u8acb\u81ea Yaml \u79fb\u9664\u8a72\u6191\u8b49\u4e26\u4f7f\u7528\u8a2d\u5b9a\u6d41\u7a0b\u65b9\u5f0f\u3002" }, "error": { diff --git a/homeassistant/components/balboa/translations/pt-BR.json b/homeassistant/components/balboa/translations/pt-BR.json new file mode 100644 index 00000000000000..ff6ede166a9dd6 --- /dev/null +++ b/homeassistant/components/balboa/translations/pt-BR.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/binary_sensor/translations/el.json b/homeassistant/components/binary_sensor/translations/el.json index 294fca09be5913..03a750a186d532 100644 --- a/homeassistant/components/binary_sensor/translations/el.json +++ b/homeassistant/components/binary_sensor/translations/el.json @@ -51,6 +51,8 @@ "moist": "{entity_name} \u03ad\u03b3\u03b9\u03bd\u03b5 \u03c5\u03b3\u03c1\u03cc", "no_update": "{entity_name} \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5", "not_opened": "{entity_name} \u03ad\u03ba\u03bb\u03b5\u03b9\u03c3\u03b5", + "not_tampered": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03b6\u03b5\u03b9 \u03c0\u03b1\u03c1\u03b1\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", + "tampered": "{entity_name} \u03ac\u03c1\u03c7\u03b9\u03c3\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03b6\u03b5\u03b9 \u03c0\u03b1\u03c1\u03b1\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", "turned_off": "{entity_name} \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", "turned_on": "{entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", "update": "{entity_name} \u03ad\u03bb\u03b1\u03b2\u03b5 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7" diff --git a/homeassistant/components/binary_sensor/translations/pt-BR.json b/homeassistant/components/binary_sensor/translations/pt-BR.json index d1bf94170bf86a..4ca2f04550efcf 100644 --- a/homeassistant/components/binary_sensor/translations/pt-BR.json +++ b/homeassistant/components/binary_sensor/translations/pt-BR.json @@ -1,12 +1,96 @@ { "device_automation": { "condition_type": { + "is_bat_low": "{entity_name} a bateria est\u00e1 fraca", + "is_co": "{entity_name} est\u00e1 detectando mon\u00f3xido de carbono", + "is_cold": "{entity_name} \u00e9 frio", + "is_connected": "{entity_name} est\u00e1 conectado", + "is_gas": "{entity_name} est\u00e1 detectando g\u00e1s", + "is_hot": "{entity_name} \u00e9 quente", + "is_light": "{entity_name} est\u00e1 detectando luz", + "is_locked": "{entity_name} est\u00e1 bloqueado", + "is_moist": "{entity_name} est\u00e1 \u00famido", "is_motion": "{entity_name} est\u00e1 detectando movimento", - "is_no_motion": "{entity_name} n\u00e3o est\u00e1 detectando movimento" + "is_moving": "{entity_name} est\u00e1 se movendo", + "is_no_co": "{entity_name} n\u00e3o est\u00e1 detectando mon\u00f3xido de carbono", + "is_no_gas": "{entity_name} n\u00e3o est\u00e1 detectando g\u00e1s", + "is_no_light": "{entity_name} n\u00e3o est\u00e1 detectando luz", + "is_no_motion": "{entity_name} n\u00e3o est\u00e1 detectando movimento", + "is_no_problem": "{entity_name} n\u00e3o est\u00e1 detectando problema", + "is_no_smoke": "{entity_name} n\u00e3o est\u00e1 detectando fuma\u00e7a", + "is_no_sound": "{entity_name} n\u00e3o est\u00e1 detectando som", + "is_no_vibration": "{entity_name} n\u00e3o est\u00e1 detectando vibra\u00e7\u00e3o", + "is_not_bat_low": "{entity_name} bateria normal", + "is_not_cold": "{entity_name} n\u00e3o \u00e9 frio", + "is_not_connected": "{entity_name} est\u00e1 desconectado", + "is_not_hot": "{entity_name} n\u00e3o \u00e9 quente", + "is_not_locked": "{entity_name} est\u00e1 desbloqueado", + "is_not_moist": "{entity_name} est\u00e1 seco", + "is_not_moving": "{entity_name} est\u00e1 parado", + "is_not_occupied": "{entity_name} n\u00e3o est\u00e1 ocupado", + "is_not_open": "{entity_name} est\u00e1 fechado", + "is_not_plugged_in": "{entity_name} est\u00e1 desconectado", + "is_not_powered": "{entity_name} n\u00e3o \u00e9 alimentado", + "is_not_present": "{entity_name} n\u00e3o est\u00e1 presente", + "is_not_unsafe": "{entity_name} \u00e9 seguro", + "is_occupied": "{entity_name} est\u00e1 ocupado", + "is_off": "{entity_name} est\u00e1 desligado", + "is_on": "{entity_name} est\u00e1 ligado", + "is_open": "{entity_name} est\u00e1 aberto", + "is_plugged_in": "{entity_name} est\u00e1 conectado", + "is_powered": "{entity_name} \u00e9 alimentado", + "is_present": "{entity_name} est\u00e1 presente", + "is_problem": "{entity_name} est\u00e1 detectando problema", + "is_smoke": "{entity_name} est\u00e1 detectando fuma\u00e7a", + "is_sound": "{entity_name} est\u00e1 detectando som", + "is_unsafe": "{entity_name} \u00e9 inseguro", + "is_vibration": "{entity_name} est\u00e1 detectando vibra\u00e7\u00e3o" }, "trigger_type": { + "bat_low": "{entity_name} bateria fraca", + "co": "{entity_name} come\u00e7ou a detectar mon\u00f3xido de carbono", + "cold": "{entity_name} frio", + "connected": "{entity_name} conectado", + "gas": "{entity_name} come\u00e7ou a detectar g\u00e1s", + "hot": "{entity_name} tornou-se quente", + "light": "{entity_name} come\u00e7ou a detectar luz", + "locked": "{entity_name} bloqueado", "motion": "{entity_name} come\u00e7ou a detectar movimento", - "no_motion": "{entity_name} parou de detectar movimento" + "moving": "{entity_name} come\u00e7ou a se mover", + "no_co": "{entity_name} parou de detectar mon\u00f3xido de carbono", + "no_gas": "{entity_name} parou de detectar g\u00e1s", + "no_light": "{entity_name} parou de detectar luz", + "no_motion": "{entity_name} parou de detectar movimento", + "no_problem": "{entity_name} parou de detectar problema", + "no_smoke": "{entity_name} parou de detectar fuma\u00e7a", + "no_sound": "{entity_name} parou de detectar som", + "no_vibration": "{entity_name} parou de detectar vibra\u00e7\u00e3o", + "not_bat_low": "{entity_name} bateria normal", + "not_cold": "{entity_name} n\u00e3o frio", + "not_connected": "{entity_name} desconectado", + "not_hot": "{entity_name} n\u00e3o quente", + "not_locked": "{entity_name} desbloqueado", + "not_moist": "{entity_name} secou", + "not_moving": "{entity_name} parado", + "not_occupied": "{entity_name} desocupado", + "not_plugged_in": "{entity_name} desconectado", + "not_powered": "{entity_name} sem alimenta\u00e7\u00e3o", + "not_present": "{entity_name} n\u00e3o est\u00e1 presente", + "not_tampered": "{entity_name} parou de detectar adultera\u00e7\u00e3o", + "not_unsafe": "{entity_name} seguro", + "occupied": "{entity_name} ocupado", + "opened": "{entity_name} aberto", + "plugged_in": "{entity_name} conectado", + "powered": "{entity_name} alimentado", + "present": "{entity_name} presente", + "problem": "{entity_name} come\u00e7ou a detectar problema", + "smoke": "{entity_name} come\u00e7ou a detectar fuma\u00e7a", + "sound": "{entity_name} come\u00e7ou a detectar som", + "tampered": "{entity_name} come\u00e7ou a detectar adultera\u00e7\u00e3o", + "turned_off": "{entity_name} desligado", + "turned_on": "{entity_name} ligado", + "unsafe": "{entity_name} tornou-se inseguro", + "vibration": "{entity_name} come\u00e7ou a detectar vibra\u00e7\u00e3o" } }, "device_class": { @@ -37,6 +121,7 @@ "on": "Carregando" }, "co": { + "off": "Limpo", "on": "Detectado" }, "cold": { diff --git a/homeassistant/components/blebox/translations/pt-BR.json b/homeassistant/components/blebox/translations/pt-BR.json index 972aed55cc493e..3bc015b6b55533 100644 --- a/homeassistant/components/blebox/translations/pt-BR.json +++ b/homeassistant/components/blebox/translations/pt-BR.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/blink/translations/pt-BR.json b/homeassistant/components/blink/translations/pt-BR.json index 70d8b8620c45d4..5624d3765d3c29 100644 --- a/homeassistant/components/blink/translations/pt-BR.json +++ b/homeassistant/components/blink/translations/pt-BR.json @@ -1,9 +1,11 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "error": { + "cannot_connect": "Falha ao conectar", + "invalid_access_token": "Token de acesso inv\u00e1lido", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, @@ -18,7 +20,7 @@ "user": { "data": { "password": "Senha", - "username": "Nome de usu\u00e1rio" + "username": "Usu\u00e1rio" }, "title": "Entrar com a conta Blink" } diff --git a/homeassistant/components/bmw_connected_drive/translations/pt-BR.json b/homeassistant/components/bmw_connected_drive/translations/pt-BR.json new file mode 100644 index 00000000000000..86cf9781d3a90c --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bond/translations/pt-BR.json b/homeassistant/components/bond/translations/pt-BR.json index a58a0045e46051..2e596948dc22d3 100644 --- a/homeassistant/components/bond/translations/pt-BR.json +++ b/homeassistant/components/bond/translations/pt-BR.json @@ -1,5 +1,26 @@ { "config": { - "flow_title": "Bond: {bond_id} ({host})" + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "flow_title": "Bond: {bond_id} ({host})", + "step": { + "confirm": { + "data": { + "access_token": "Token de acesso" + } + }, + "user": { + "data": { + "access_token": "Token de acesso", + "host": "Nome do host" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/pt-BR.json b/homeassistant/components/bosch_shc/translations/pt-BR.json new file mode 100644 index 00000000000000..4916cbdfe49adc --- /dev/null +++ b/homeassistant/components/bosch_shc/translations/pt-BR.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "reauth_confirm": { + "title": "Reautenticar Integra\u00e7\u00e3o" + }, + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/braviatv/translations/pt-BR.json b/homeassistant/components/braviatv/translations/pt-BR.json index 1a0fedff9d026c..7bc600a3644dfa 100644 --- a/homeassistant/components/braviatv/translations/pt-BR.json +++ b/homeassistant/components/braviatv/translations/pt-BR.json @@ -1,7 +1,22 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido" + }, "step": { + "authorize": { + "data": { + "pin": "C\u00f3digo PIN" + } + }, "user": { + "data": { + "host": "Nome do host" + }, "description": "Configure a integra\u00e7\u00e3o do Sony Bravia TV. Se voc\u00ea tiver problemas com a configura\u00e7\u00e3o, acesse: https://www.home-assistant.io/integrations/braviatv \n\n Verifique se a sua TV est\u00e1 ligada.", "title": "Sony Bravia TV" } diff --git a/homeassistant/components/broadlink/translations/pt-BR.json b/homeassistant/components/broadlink/translations/pt-BR.json new file mode 100644 index 00000000000000..0cafe568193f79 --- /dev/null +++ b/homeassistant/components/broadlink/translations/pt-BR.json @@ -0,0 +1,47 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "cannot_connect": "Falha ao conectar", + "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido", + "not_supported": "Dispositivo n\u00e3o suportado", + "unknown": "Erro inesperado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido", + "unknown": "Erro inesperado" + }, + "flow_title": "{nome} ({modelo} em {host})", + "step": { + "auth": { + "title": "Autenticar no dispositivo" + }, + "finish": { + "data": { + "name": "Nome" + }, + "title": "Escolha um nome para o dispositivo" + }, + "reset": { + "description": "{nome} ({modelo} em {host}) est\u00e1 bloqueado. Voc\u00ea precisa desbloquear o dispositivo para autenticar e completar a configura\u00e7\u00e3o. Instru\u00e7\u00f5es:\n1. Abra o aplicativo Broadlink.\n2. Clique no dispositivo.\n3. Clique em '...' no canto superior direito.\n4. Role at\u00e9 a parte inferior da p\u00e1gina.\n5. Desabilite o bloqueio.", + "title": "Desbloqueie o dispositivo" + }, + "unlock": { + "data": { + "unlock": "Sim, fa\u00e7a isso." + }, + "description": "{nome} ({modelo} em {host}) est\u00e1 bloqueado. Isso pode levar a problemas de autentica\u00e7\u00e3o no Home Assistant. Gostaria de desbloque\u00e1-lo?", + "title": "Desbloquear o dispositivo (opcional)" + }, + "user": { + "data": { + "host": "Nome do host", + "timeout": "Tempo esgotado" + }, + "title": "Conectar-se ao dispositivo" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/brother/translations/pt-BR.json b/homeassistant/components/brother/translations/pt-BR.json index e7ee63e6e5bf1e..0306932f1467ed 100644 --- a/homeassistant/components/brother/translations/pt-BR.json +++ b/homeassistant/components/brother/translations/pt-BR.json @@ -1,16 +1,18 @@ { "config": { "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "unsupported_model": "Este modelo de impressora n\u00e3o \u00e9 suportado." }, "error": { + "cannot_connect": "Falha ao conectar", "snmp_error": "Servidor SNMP desligado ou impressora n\u00e3o suportada.", "wrong_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido." }, "step": { "user": { "data": { - "host": "Nome do host ou endere\u00e7o IP da impressora", + "host": "Nome do host", "type": "Tipo de impressora" }, "description": "Configure a integra\u00e7\u00e3o da impressora Brother. Se voc\u00ea tiver problemas com a configura\u00e7\u00e3o, acesse: https://www.home-assistant.io/integrations/brother" diff --git a/homeassistant/components/brunt/translations/pt-BR.json b/homeassistant/components/brunt/translations/pt-BR.json new file mode 100644 index 00000000000000..6368184212aaf5 --- /dev/null +++ b/homeassistant/components/brunt/translations/pt-BR.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "Senha" + }, + "description": "Por favor, reinsira a senha para: {username}", + "title": "Reautenticar Integra\u00e7\u00e3o" + }, + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + }, + "title": "Configure sua integra\u00e7\u00e3o Brunt" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bsblan/translations/pt-BR.json b/homeassistant/components/bsblan/translations/pt-BR.json index 3f8701092d1872..8b09d9885c2baa 100644 --- a/homeassistant/components/bsblan/translations/pt-BR.json +++ b/homeassistant/components/bsblan/translations/pt-BR.json @@ -1,7 +1,20 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, "step": { "user": { + "data": { + "host": "Nome do host", + "password": "Senha", + "port": "Porta", + "username": "Usu\u00e1rio" + }, "description": "Configure o seu dispositivo BSB-Lan para integrar com o Home Assistant.", "title": "Conecte-se ao dispositivo BSB-Lan" } diff --git a/homeassistant/components/buienradar/translations/pt-BR.json b/homeassistant/components/buienradar/translations/pt-BR.json new file mode 100644 index 00000000000000..1ab872ffb71d0b --- /dev/null +++ b/homeassistant/components/buienradar/translations/pt-BR.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Localiza\u00e7\u00e3o j\u00e1 est\u00e1 configurada" + }, + "error": { + "already_configured": "Localiza\u00e7\u00e3o j\u00e1 est\u00e1 configurada" + }, + "step": { + "user": { + "data": { + "latitude": "Latitude", + "longitude": "Longitude" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/pt-BR.json b/homeassistant/components/canary/translations/pt-BR.json new file mode 100644 index 00000000000000..25ded1810fecc0 --- /dev/null +++ b/homeassistant/components/canary/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", + "unknown": "Erro inesperado" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/zh-Hant.json b/homeassistant/components/canary/translations/zh-Hant.json index 6c7dbea4daad77..689ff1c42c5058 100644 --- a/homeassistant/components/canary/translations/zh-Hant.json +++ b/homeassistant/components/canary/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "error": { diff --git a/homeassistant/components/cast/translations/pt-BR.json b/homeassistant/components/cast/translations/pt-BR.json index 8abd2dac5e5f46..369064ba6cb5e1 100644 --- a/homeassistant/components/cast/translations/pt-BR.json +++ b/homeassistant/components/cast/translations/pt-BR.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "Apenas uma \u00fanica configura\u00e7\u00e3o do Google Cast \u00e9 necess\u00e1ria." + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "step": { "confirm": { - "description": "Deseja configurar o Google Cast?" + "description": "Deseja iniciar a configura\u00e7\u00e3o?" } } } diff --git a/homeassistant/components/cast/translations/zh-Hant.json b/homeassistant/components/cast/translations/zh-Hant.json index 1994465c410fde..cc538845603fe9 100644 --- a/homeassistant/components/cast/translations/zh-Hant.json +++ b/homeassistant/components/cast/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "invalid_known_hosts": "\u5df2\u77e5\u4e3b\u6a5f\u5fc5\u9808\u4ee5\u9017\u865f\u5206\u4e3b\u6a5f\u5217\u8868\u3002" diff --git a/homeassistant/components/cert_expiry/translations/pt-BR.json b/homeassistant/components/cert_expiry/translations/pt-BR.json index 6a395059625ce6..db1fff5cd048f3 100644 --- a/homeassistant/components/cert_expiry/translations/pt-BR.json +++ b/homeassistant/components/cert_expiry/translations/pt-BR.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" + }, "error": { "connection_timeout": "Tempo limite ao conectar-se a este host", "resolve_failed": "Este host n\u00e3o pode ser resolvido" @@ -7,9 +10,9 @@ "step": { "user": { "data": { - "host": "O nome do host do certificado", + "host": "Nome do host", "name": "O nome do certificado", - "port": "A porta do certificado" + "port": "Porta" }, "title": "Defina o certificado para testar" } diff --git a/homeassistant/components/climacell/translations/pt-BR.json b/homeassistant/components/climacell/translations/pt-BR.json new file mode 100644 index 00000000000000..687bfdf71f933a --- /dev/null +++ b/homeassistant/components/climacell/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_api_key": "Chave de API inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "api_key": "Chave da API", + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Nome" + }, + "description": "Se Latitude e Longitude n\u00e3o forem fornecidos, os valores padr\u00f5es na configura\u00e7\u00e3o do Home Assistant ser\u00e3o usados. Uma entidade ser\u00e1 criada para cada tipo de previs\u00e3o, mas apenas as selecionadas ser\u00e3o habilitadas por padr\u00e3o." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/sensor.pt-BR.json b/homeassistant/components/climacell/translations/sensor.pt-BR.json new file mode 100644 index 00000000000000..eb3814331b90c7 --- /dev/null +++ b/homeassistant/components/climacell/translations/sensor.pt-BR.json @@ -0,0 +1,27 @@ +{ + "state": { + "climacell__health_concern": { + "good": "Bom", + "hazardous": "Perigosos", + "moderate": "Moderado", + "unhealthy": "Pouco saud\u00e1vel", + "unhealthy_for_sensitive_groups": "Insalubre para grupos sens\u00edveis", + "very_unhealthy": "Muito prejudicial \u00e0 sa\u00fade" + }, + "climacell__pollen_index": { + "high": "Alto", + "low": "Baixo", + "medium": "M\u00e9dio", + "none": "Nenhum", + "very_high": "Muito alto", + "very_low": "Muito baixo" + }, + "climacell__precipitation_type": { + "freezing_rain": "Chuva congelante", + "ice_pellets": "Granizo", + "none": "Nenhum", + "rain": "Chuva", + "snow": "Neve" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloudflare/translations/el.json b/homeassistant/components/cloudflare/translations/el.json index 345aa94303969e..b8f144831749f1 100644 --- a/homeassistant/components/cloudflare/translations/el.json +++ b/homeassistant/components/cloudflare/translations/el.json @@ -5,6 +5,11 @@ }, "flow_title": "{name}", "step": { + "reauth_confirm": { + "data": { + "description": "\u0395\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Cloudflare." + } + }, "records": { "data": { "records": "\u0395\u03b3\u03b3\u03c1\u03b1\u03c6\u03ad\u03c2" diff --git a/homeassistant/components/cloudflare/translations/pt-BR.json b/homeassistant/components/cloudflare/translations/pt-BR.json new file mode 100644 index 00000000000000..8a763643004681 --- /dev/null +++ b/homeassistant/components/cloudflare/translations/pt-BR.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", + "unknown": "Erro inesperado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "reauth_confirm": { + "data": { + "api_token": "Token da API" + } + }, + "user": { + "data": { + "api_token": "Token da API" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloudflare/translations/zh-Hant.json b/homeassistant/components/cloudflare/translations/zh-Hant.json index 3ee29277296eb1..675c2b74d28be2 100644 --- a/homeassistant/components/cloudflare/translations/zh-Hant.json +++ b/homeassistant/components/cloudflare/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "error": { diff --git a/homeassistant/components/co2signal/translations/pt-BR.json b/homeassistant/components/co2signal/translations/pt-BR.json new file mode 100644 index 00000000000000..b0989763567b2d --- /dev/null +++ b/homeassistant/components/co2signal/translations/pt-BR.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "unknown": "Erro inesperado" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "coordinates": { + "data": { + "latitude": "Latitude", + "longitude": "Longitude" + } + }, + "user": { + "data": { + "api_key": "Token de acesso" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/coinbase/translations/el.json b/homeassistant/components/coinbase/translations/el.json index 312ef7d430d857..cb7723d297b5c9 100644 --- a/homeassistant/components/coinbase/translations/el.json +++ b/homeassistant/components/coinbase/translations/el.json @@ -2,18 +2,34 @@ "config": { "error": { "invalid_auth_secret": "\u03a4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 API \u03b1\u03c0\u03bf\u03c1\u03c1\u03af\u03c6\u03b8\u03b7\u03ba\u03b1\u03bd \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Coinbase \u03bb\u03cc\u03b3\u03c9 \u03bc\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c5 \u03bc\u03c5\u03c3\u03c4\u03b9\u03ba\u03bf\u03cd API." + }, + "step": { + "user": { + "data": { + "api_token": "\u039c\u03c5\u03c3\u03c4\u03b9\u03ba\u03cc API", + "currencies": "\u039d\u03bf\u03bc\u03af\u03c3\u03bc\u03b1\u03c4\u03b1 \u03c5\u03c0\u03bf\u03bb\u03bf\u03af\u03c0\u03bf\u03c5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd", + "exchange_rates": "\u03a3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03bc\u03b1\u03c4\u03b9\u03ba\u03ad\u03c2 \u03b9\u03c3\u03bf\u03c4\u03b9\u03bc\u03af\u03b5\u03c2" + }, + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c4\u03bf\u03c5 \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd API \u03c3\u03b1\u03c2, \u03cc\u03c0\u03c9\u03c2 \u03b1\u03c5\u03c4\u03ac \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Coinbase.", + "title": "\u0392\u03b1\u03c3\u03b9\u03ba\u03ad\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2 API \u03c4\u03bf\u03c5 Coinbase" + } } }, "options": { "error": { "currency_unavailable": "\u0388\u03bd\u03b1 \u03ae \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b1 \u03b6\u03b7\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b1 \u03c5\u03c0\u03cc\u03bb\u03bf\u03b9\u03c0\u03b1 \u03bd\u03bf\u03bc\u03b9\u03c3\u03bc\u03ac\u03c4\u03c9\u03bd \u03b4\u03b5\u03bd \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf API \u03c4\u03b7\u03c2 Coinbase.", - "exchange_rate_unavailable": "\u039c\u03af\u03b1 \u03ae \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b9\u03c2 \u03b6\u03b7\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b5\u03c2 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03bc\u03b1\u03c4\u03b9\u03ba\u03ad\u03c2 \u03b9\u03c3\u03bf\u03c4\u03b9\u03bc\u03af\u03b5\u03c2 \u03b4\u03b5\u03bd \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Coinbase." + "currency_unavaliable": "\u0388\u03bd\u03b1 \u03ae \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b1 \u03b6\u03b7\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b1 \u03c5\u03c0\u03cc\u03bb\u03bf\u03b9\u03c0\u03b1 \u03bd\u03bf\u03bc\u03b9\u03c3\u03bc\u03ac\u03c4\u03c9\u03bd \u03b4\u03b5\u03bd \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf Coinbase API \u03c3\u03b1\u03c2.", + "exchange_rate_unavailable": "\u039c\u03af\u03b1 \u03ae \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b9\u03c2 \u03b6\u03b7\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b5\u03c2 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03bc\u03b1\u03c4\u03b9\u03ba\u03ad\u03c2 \u03b9\u03c3\u03bf\u03c4\u03b9\u03bc\u03af\u03b5\u03c2 \u03b4\u03b5\u03bd \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Coinbase.", + "exchange_rate_unavaliable": "\u039c\u03af\u03b1 \u03ae \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b9\u03c2 \u03b6\u03b7\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b5\u03c2 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03bc\u03b1\u03c4\u03b9\u03ba\u03ad\u03c2 \u03b9\u03c3\u03bf\u03c4\u03b9\u03bc\u03af\u03b5\u03c2 \u03b4\u03b5\u03bd \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Coinbase." }, "step": { "init": { "data": { - "exchange_base": "\u0392\u03b1\u03c3\u03b9\u03ba\u03cc \u03bd\u03cc\u03bc\u03b9\u03c3\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03c4\u03bf\u03c5\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b5\u03c2 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03bc\u03b1\u03c4\u03b9\u03ba\u03ce\u03bd \u03b9\u03c3\u03bf\u03c4\u03b9\u03bc\u03b9\u03ce\u03bd." - } + "account_balance_currencies": "\u03a5\u03c0\u03cc\u03bb\u03bf\u03b9\u03c0\u03b1 \u03c0\u03bf\u03c1\u03c4\u03bf\u03c6\u03bf\u03bb\u03b9\u03bf\u03cd \u03c0\u03c1\u03bf\u03c2 \u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac.", + "exchange_base": "\u0392\u03b1\u03c3\u03b9\u03ba\u03cc \u03bd\u03cc\u03bc\u03b9\u03c3\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03c4\u03bf\u03c5\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b5\u03c2 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03bc\u03b1\u03c4\u03b9\u03ba\u03ce\u03bd \u03b9\u03c3\u03bf\u03c4\u03b9\u03bc\u03b9\u03ce\u03bd.", + "exchange_rate_currencies": "\u03a3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03bc\u03b1\u03c4\u03b9\u03ba\u03ad\u03c2 \u03b9\u03c3\u03bf\u03c4\u03b9\u03bc\u03af\u03b5\u03c2 \u03c0\u03c1\u03bf\u03c2 \u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac." + }, + "description": "\u03a0\u03c1\u03bf\u03c3\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd Coinbase" } } } diff --git a/homeassistant/components/coinbase/translations/pt-BR.json b/homeassistant/components/coinbase/translations/pt-BR.json new file mode 100644 index 00000000000000..4c6c42664764e9 --- /dev/null +++ b/homeassistant/components/coinbase/translations/pt-BR.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "invalid_auth_key": "Credenciais de API rejeitadas pela Coinbase devido a uma chave de API inv\u00e1lida.", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "api_key": "Chave da API" + } + } + } + }, + "options": { + "error": { + "currency_unavailable": "Um ou mais dos saldos de moeda solicitados n\u00e3o s\u00e3o fornecidos pela sua API Coinbase.", + "exchange_rate_unavailable": "Uma ou mais taxas de c\u00e2mbio solicitadas n\u00e3o s\u00e3o fornecidas pela Coinbase.", + "unknown": "Erro inesperado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/control4/translations/pt-BR.json b/homeassistant/components/control4/translations/pt-BR.json index 931024c0e9613a..c09810d8a43f50 100644 --- a/homeassistant/components/control4/translations/pt-BR.json +++ b/homeassistant/components/control4/translations/pt-BR.json @@ -1,11 +1,17 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, "error": { - "cannot_connect": "Falha na conex\u00e3o" + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" }, "step": { "user": { "data": { + "host": "Endere\u00e7o IP", "password": "Senha", "username": "Usu\u00e1rio" } diff --git a/homeassistant/components/coolmaster/translations/pt-BR.json b/homeassistant/components/coolmaster/translations/pt-BR.json index bb821341818ed9..d69f8206c465c8 100644 --- a/homeassistant/components/coolmaster/translations/pt-BR.json +++ b/homeassistant/components/coolmaster/translations/pt-BR.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "Falha ao conectar" + }, "step": { "user": { "data": { @@ -8,7 +11,7 @@ "fan_only": "Suporte apenas o modo ventilador", "heat": "Suporta o modo de aquecimento", "heat_cool": "Suporta o modo de aquecimento/resfriamento autom\u00e1tico", - "host": "Host", + "host": "Nome do host", "off": "Pode ser desligado" } } diff --git a/homeassistant/components/coronavirus/translations/pt-BR.json b/homeassistant/components/coronavirus/translations/pt-BR.json index ab4a49048575f8..f20cd39494805b 100644 --- a/homeassistant/components/coronavirus/translations/pt-BR.json +++ b/homeassistant/components/coronavirus/translations/pt-BR.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Este pa\u00eds j\u00e1 est\u00e1 configurado." + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar" }, "step": { "user": { diff --git a/homeassistant/components/cpuspeed/translations/pt-BR.json b/homeassistant/components/cpuspeed/translations/pt-BR.json new file mode 100644 index 00000000000000..f39ce9b4c9ae79 --- /dev/null +++ b/homeassistant/components/cpuspeed/translations/pt-BR.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "alread_configured": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", + "already_configured": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", + "not_compatible": "N\u00e3o \u00e9 poss\u00edvel obter informa\u00e7\u00f5es da CPU, esta integra\u00e7\u00e3o n\u00e3o \u00e9 compat\u00edvel com seu sistema" + }, + "step": { + "user": { + "description": "Deseja iniciar a configura\u00e7\u00e3o?", + "title": "Velocidade da CPU" + } + } + }, + "title": "Velocidade da CPU" +} \ No newline at end of file diff --git a/homeassistant/components/cpuspeed/translations/zh-Hant.json b/homeassistant/components/cpuspeed/translations/zh-Hant.json index 5563dadabc70d3..4885aead70ac82 100644 --- a/homeassistant/components/cpuspeed/translations/zh-Hant.json +++ b/homeassistant/components/cpuspeed/translations/zh-Hant.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "alread_configured": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", - "already_configured": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", + "alread_configured": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", + "already_configured": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "not_compatible": "\u7121\u6cd5\u53d6\u5f97 CPU \u8cc7\u8a0a\uff0c\u9019\u500b\u63d2\u4ef6\u8207\u4f60\u7684\u7cfb\u7d71\u4e0d\u76f8\u5bb9" }, "step": { diff --git a/homeassistant/components/crownstone/translations/el.json b/homeassistant/components/crownstone/translations/el.json index 35033cd1e59080..d2990459f4ff1d 100644 --- a/homeassistant/components/crownstone/translations/el.json +++ b/homeassistant/components/crownstone/translations/el.json @@ -43,6 +43,28 @@ "usb_config_option": { "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1 \u03c4\u03bf\u03c5 Crownstone USB dongle.\n\n\u0391\u03bd\u03b1\u03b6\u03b7\u03c4\u03ae\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bc\u03b5 VID 10C4 \u03ba\u03b1\u03b9 PID EA60.", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Crownstone USB dongle" + }, + "usb_manual_config": { + "description": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae\u03c2 \u03b5\u03bd\u03cc\u03c2 dongle USB Crownstone.", + "title": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae\u03c2 USB dongle Crownstone" + }, + "usb_manual_config_option": { + "description": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae\u03c2 \u03b5\u03bd\u03cc\u03c2 dongle USB Crownstone.", + "title": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae\u03c2 USB dongle Crownstone" + }, + "usb_sphere_config": { + "data": { + "usb_sphere": "Crownstone Sphere" + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 Crownstone Sphere \u03cc\u03c0\u03bf\u03c5 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03c4\u03bf USB.", + "title": "Crownstone USB Sphere" + }, + "usb_sphere_config_option": { + "data": { + "usb_sphere": "Crownstone Sphere" + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 Crownstone Sphere \u03cc\u03c0\u03bf\u03c5 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03c4\u03bf USB.", + "title": "Crownstone USB Sphere" } } } diff --git a/homeassistant/components/crownstone/translations/pt-BR.json b/homeassistant/components/crownstone/translations/pt-BR.json new file mode 100644 index 00000000000000..64f05f0190359d --- /dev/null +++ b/homeassistant/components/crownstone/translations/pt-BR.json @@ -0,0 +1,63 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "usb_config": { + "data": { + "usb_path": "Caminho do Dispositivo USB" + } + }, + "usb_manual_config": { + "data": { + "usb_manual_path": "Caminho do Dispositivo USB" + } + }, + "user": { + "data": { + "password": "Senha" + } + } + } + }, + "options": { + "step": { + "usb_config": { + "data": { + "usb_path": "Caminho do Dispositivo USB" + }, + "description": "Selecione a porta serial do dongle USB Crownstone. \n\n Procure um dispositivo com VID 10C4 e PID EA60.", + "title": "Configura\u00e7\u00e3o do dongle USB Crownstone" + }, + "usb_config_option": { + "data": { + "usb_path": "Caminho do Dispositivo USB" + } + }, + "usb_manual_config": { + "data": { + "usb_manual_path": "Caminho do Dispositivo USB" + }, + "description": "Insira manualmente o caminho de um dongle USB Crownstone.", + "title": "Caminho manual do dongle USB Crownstone" + }, + "usb_manual_config_option": { + "data": { + "usb_manual_path": "Caminho do Dispositivo USB" + } + }, + "usb_sphere_config": { + "data": { + "usb_sphere": "Esfera da Pedra da Coroa" + }, + "description": "Selecione um Crownstone Sphere onde o USB est\u00e1 localizado.", + "title": "Esfera USB Crownstone" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/daikin/translations/pt-BR.json b/homeassistant/components/daikin/translations/pt-BR.json index 11642b57627e37..1489556e10da60 100644 --- a/homeassistant/components/daikin/translations/pt-BR.json +++ b/homeassistant/components/daikin/translations/pt-BR.json @@ -1,15 +1,23 @@ { "config": { "abort": { - "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", - "cannot_connect": "Falha na conex\u00e3o" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar" + }, + "error": { + "api_password": "Autentica\u00e7\u00e3o inv\u00e1lida, use a chave de API ou a senha.", + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" }, "step": { "user": { "data": { - "host": "Host" + "api_key": "Chave da API", + "host": "Nome do host", + "password": "Senha" }, - "description": "Digite o endere\u00e7o IP do seu AC Daikin.", + "description": "Insira Endere\u00e7o IP do seu Daikin AC. \n\nObserve que Chave da API e Senha s\u00e3o usados apenas por dispositivos BRP072Cxx e SKYFi, respectivamente.", "title": "Configurar o AC Daikin" } } diff --git a/homeassistant/components/deconz/translations/pt-BR.json b/homeassistant/components/deconz/translations/pt-BR.json index 450fa7707d15a7..feda280cc3f570 100644 --- a/homeassistant/components/deconz/translations/pt-BR.json +++ b/homeassistant/components/deconz/translations/pt-BR.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "A ponte j\u00e1 est\u00e1 configurada", - "already_in_progress": "Fluxo de configura\u00e7\u00e3o para ponte j\u00e1 est\u00e1 em andamento.", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", "no_bridges": "N\u00e3o h\u00e1 pontes de deCONZ descobertas", "not_deconz_bridge": "N\u00e3o \u00e9 uma ponte deCONZ", "updated_instance": "Atualiza\u00e7\u00e3o da inst\u00e2ncia deCONZ com novo endere\u00e7o de host" @@ -21,6 +21,7 @@ }, "manual_input": { "data": { + "host": "Nome do host", "port": "Porta" } } @@ -28,8 +29,45 @@ }, "device_automation": { "trigger_subtype": { + "both_buttons": "Ambos os bot\u00f5es", "bottom_buttons": "Bot\u00f5es inferiores", - "top_buttons": "Bot\u00f5es superiores" + "button_1": "Primeiro bot\u00e3o", + "button_2": "Segundo bot\u00e3o", + "button_3": "Terceiro bot\u00e3o", + "button_4": "Quarto bot\u00e3o", + "close": "Fechar", + "dim_down": "Diminuir a luminosidade", + "dim_up": "Aumentar a luminosidade", + "left": "Esquerdo", + "open": "Aberto", + "right": "Direito", + "top_buttons": "Bot\u00f5es superiores", + "turn_off": "Desligar", + "turn_on": "Ligar" + }, + "trigger_type": { + "remote_button_double_press": "bot\u00e3o \" {subtype} \" clicado duas vezes", + "remote_button_long_press": "Bot\u00e3o \" {subtype} \" pressionado continuamente", + "remote_button_long_release": "Bot\u00e3o \" {subtype} \" liberado ap\u00f3s press\u00e3o longa", + "remote_button_quadruple_press": "Bot\u00e3o \" {subtype} \" qu\u00e1druplo clicado", + "remote_button_quintuple_press": "Bot\u00e3o \" {subtype} \" qu\u00edntuplo clicado", + "remote_button_rotated": "Bot\u00e3o girado \" {subtype} \"", + "remote_button_rotation_stopped": "A rota\u00e7\u00e3o dos bot\u00f5es \"{subtype}\" parou", + "remote_button_short_press": "Bot\u00e3o \" {subtype} \" pressionado", + "remote_button_short_release": "Bot\u00e3o \" {subtype} \" liberados", + "remote_button_triple_press": "Bot\u00e3o \" {subtype} \" clicado tr\u00eas vezes", + "remote_gyro_activated": "Dispositivo sacudido" + } + }, + "options": { + "step": { + "deconz_devices": { + "data": { + "allow_clip_sensor": "Permitir sensores deCONZ CLIP", + "allow_deconz_groups": "Permitir grupos de luz deCONZ" + }, + "description": "Configure a visibilidade dos tipos de dispositivos deCONZ" + } } } } \ No newline at end of file diff --git a/homeassistant/components/denonavr/translations/pt-BR.json b/homeassistant/components/denonavr/translations/pt-BR.json new file mode 100644 index 00000000000000..a39a263484b1e8 --- /dev/null +++ b/homeassistant/components/denonavr/translations/pt-BR.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento" + }, + "step": { + "user": { + "data": { + "host": "Endere\u00e7o IP" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/devolo_home_control/translations/el.json b/homeassistant/components/devolo_home_control/translations/el.json index 6ab4acc7e59db3..97a0f70ec26e52 100644 --- a/homeassistant/components/devolo_home_control/translations/el.json +++ b/homeassistant/components/devolo_home_control/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "reauth_failed": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03af\u03b4\u03b9\u03bf \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 mydevolo \u03cc\u03c0\u03c9\u03c2 \u03ba\u03b1\u03b9 \u03c0\u03c1\u03b9\u03bd." + }, "step": { "user": { "data": { diff --git a/homeassistant/components/devolo_home_control/translations/pt-BR.json b/homeassistant/components/devolo_home_control/translations/pt-BR.json index 4a9930dd95d355..58d60891613c57 100644 --- a/homeassistant/components/devolo_home_control/translations/pt-BR.json +++ b/homeassistant/components/devolo_home_control/translations/pt-BR.json @@ -1,8 +1,22 @@ { "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, "step": { "user": { "data": { + "mydevolo_url": "mydevolo URL", + "password": "Senha" + } + }, + "zeroconf_confirm": { + "data": { + "mydevolo_url": "mydevolo URL", "password": "Senha" } } diff --git a/homeassistant/components/dexcom/translations/pt-BR.json b/homeassistant/components/dexcom/translations/pt-BR.json new file mode 100644 index 00000000000000..d86aef5d51d73a --- /dev/null +++ b/homeassistant/components/dexcom/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/translations/pt-BR.json b/homeassistant/components/dialogflow/translations/pt-BR.json index 45aadbd1730750..43954a1f032cc9 100644 --- a/homeassistant/components/dialogflow/translations/pt-BR.json +++ b/homeassistant/components/dialogflow/translations/pt-BR.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "cloud_not_connected": "N\u00e3o conectado ao Home Assistant Cloud.", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, "create_entry": { "default": "Para enviar eventos para o Home Assistant, voc\u00ea precisar\u00e1 configurar [Integra\u00e7\u00e3o do webhook da Dialogflow] ( {dialogflow_url} ). \n\n Preencha as seguintes informa\u00e7\u00f5es: \n\n - URL: ` {webhook_url} ` \n - M\u00e9todo: POST \n - Tipo de Conte\u00fado: application / json \n\n Veja [a documenta\u00e7\u00e3o] ( {docs_url} ) para mais detalhes." }, diff --git a/homeassistant/components/dialogflow/translations/zh-Hant.json b/homeassistant/components/dialogflow/translations/zh-Hant.json index 6cbd589e17a4ba..0b667026100521 100644 --- a/homeassistant/components/dialogflow/translations/zh-Hant.json +++ b/homeassistant/components/dialogflow/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "cloud_not_connected": "\u672a\u9023\u7dda\u81f3 Home Assistant \u96f2\u670d\u52d9\u3002", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "webhook_not_internet_accessible": "Home Assistant \u5be6\u9ad4\u5fc5\u9808\u8981\u80fd\u5f9e\u7db2\u969b\u7db2\u8def\u5b58\u53d6\u65b9\u80fd\u63a5\u6536 Webhook \u8a0a\u606f\u3002" }, "create_entry": { diff --git a/homeassistant/components/directv/translations/pt-BR.json b/homeassistant/components/directv/translations/pt-BR.json index 277606b855b03a..f317d16eb4119a 100644 --- a/homeassistant/components/directv/translations/pt-BR.json +++ b/homeassistant/components/directv/translations/pt-BR.json @@ -1,9 +1,21 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "unknown": "Erro inesperado" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, "flow_title": "DirecTV: {name}", "step": { "ssdp_confirm": { "description": "Voc\u00ea quer configurar o {name}?" + }, + "user": { + "data": { + "host": "Nome do host" + } } } } diff --git a/homeassistant/components/dlna_dmr/translations/el.json b/homeassistant/components/dlna_dmr/translations/el.json index 9d11ff838f4a3f..fcbf8246205418 100644 --- a/homeassistant/components/dlna_dmr/translations/el.json +++ b/homeassistant/components/dlna_dmr/translations/el.json @@ -11,6 +11,27 @@ "could_not_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03bc\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae DLNA", "not_dmr": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03bd\u03b1\u03c2 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03bf\u03c2 \u03c8\u03b7\u03c6\u03b9\u03b1\u03ba\u03cc\u03c2 \u03b1\u03bd\u03b1\u03bc\u03b5\u03c4\u03b1\u03b4\u03cc\u03c4\u03b7\u03c2 \u03c0\u03bf\u03bb\u03c5\u03bc\u03ad\u03c3\u03c9\u03bd" }, - "flow_title": "{name}" + "flow_title": "{name}", + "step": { + "user": { + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b3\u03b9\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03ae \u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03b5\u03bd\u03ae \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", + "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 DLNA DMR" + } + } + }, + "options": { + "error": { + "invalid_url": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL" + }, + "step": { + "init": { + "data": { + "callback_url_override": "URL \u03ba\u03bb\u03ae\u03c3\u03b7\u03c2 \u03b1\u03ba\u03c1\u03bf\u03b1\u03c4\u03ae \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03bf\u03c2", + "listen_port": "\u0398\u03cd\u03c1\u03b1 \u03b1\u03ba\u03c1\u03cc\u03b1\u03c3\u03b7\u03c2 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03c9\u03bd (\u03c4\u03c5\u03c7\u03b1\u03af\u03b1 \u03b1\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af)", + "poll_availability": "\u0388\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03b8\u03b5\u03c3\u03b9\u03bc\u03cc\u03c4\u03b7\u03c4\u03b1 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" + }, + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 DLNA Digital Media Renderer" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/dlna_dmr/translations/pt-BR.json b/homeassistant/components/dlna_dmr/translations/pt-BR.json new file mode 100644 index 00000000000000..58ed851546d7ba --- /dev/null +++ b/homeassistant/components/dlna_dmr/translations/pt-BR.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "alternative_integration": "O dispositivo \u00e9 melhor suportado por outra integra\u00e7\u00e3o", + "cannot_connect": "Falha ao conectar" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "confirm": { + "description": "Deseja iniciar a configura\u00e7\u00e3o?" + }, + "manual": { + "data": { + "url": "URL" + }, + "description": "URL para um arquivo XML de descri\u00e7\u00e3o do dispositivo", + "title": "Conex\u00e3o manual do dispositivo DLNA DMR" + }, + "user": { + "data": { + "host": "Nome do host", + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dnsip/translations/pt-BR.json b/homeassistant/components/dnsip/translations/pt-BR.json new file mode 100644 index 00000000000000..fca625597d4cca --- /dev/null +++ b/homeassistant/components/dnsip/translations/pt-BR.json @@ -0,0 +1,27 @@ +{ + "config": { + "error": { + "invalid_hostname": "Nome de host ou endere\u00e7o IP inv\u00e1lido" + }, + "step": { + "user": { + "data": { + "hostname": "O hostname para o qual realizar a consulta DNS" + } + } + } + }, + "options": { + "error": { + "invalid_resolver": "Endere\u00e7o IP inv\u00e1lido para resolver" + }, + "step": { + "init": { + "data": { + "resolver": "Resolver para a busca ipv4", + "resolver_ipv6": "Resolver para a busca IPV6" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/doorbird/translations/pt-BR.json b/homeassistant/components/doorbird/translations/pt-BR.json index 828f6a24e8469e..e34e7593c77b20 100644 --- a/homeassistant/components/doorbird/translations/pt-BR.json +++ b/homeassistant/components/doorbird/translations/pt-BR.json @@ -1,9 +1,23 @@ { "config": { "abort": { - "already_configured": "Este DoorBird j\u00e1 est\u00e1 configurado", + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "link_local_address": "Link de endere\u00e7os locais n\u00e3o s\u00e3o suportados", "not_doorbird_device": "Este dispositivo n\u00e3o \u00e9 um DoorBird" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "password": "Senha", + "username": "Usu\u00e1rio" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/dsmr/translations/pt-BR.json b/homeassistant/components/dsmr/translations/pt-BR.json new file mode 100644 index 00000000000000..95b4ef549a74a0 --- /dev/null +++ b/homeassistant/components/dsmr/translations/pt-BR.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar" + }, + "error": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar" + }, + "step": { + "setup_network": { + "data": { + "host": "Nome do host", + "port": "Porta" + } + }, + "setup_serial_manual_path": { + "data": { + "port": "Caminho do Dispositivo USB" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dunehd/translations/pt-BR.json b/homeassistant/components/dunehd/translations/pt-BR.json new file mode 100644 index 00000000000000..d783704c0a99c0 --- /dev/null +++ b/homeassistant/components/dunehd/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar", + "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido" + }, + "step": { + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/eafm/translations/pt-BR.json b/homeassistant/components/eafm/translations/pt-BR.json new file mode 100644 index 00000000000000..e29d809ebff3dd --- /dev/null +++ b/homeassistant/components/eafm/translations/pt-BR.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ecobee/translations/pt-BR.json b/homeassistant/components/ecobee/translations/pt-BR.json index 921319f55d0275..35f7967ccac6ab 100644 --- a/homeassistant/components/ecobee/translations/pt-BR.json +++ b/homeassistant/components/ecobee/translations/pt-BR.json @@ -1,6 +1,10 @@ { "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, "error": { + "pin_request_failed": "Erro ao solicitar o PIN do ecobee; verifique se a chave da API est\u00e1 correta.", "token_request_failed": "Erro ao solicitar tokens da ecobee; Por favor, tente novamente." }, "step": { @@ -10,7 +14,7 @@ }, "user": { "data": { - "api_key": "Chave API" + "api_key": "Chave da API" }, "description": "Por favor, insira a chave de API obtida em ecobee.com.", "title": "chave da API ecobee" diff --git a/homeassistant/components/ecobee/translations/zh-Hant.json b/homeassistant/components/ecobee/translations/zh-Hant.json index b46042182063ae..21e769013baf75 100644 --- a/homeassistant/components/ecobee/translations/zh-Hant.json +++ b/homeassistant/components/ecobee/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "pin_request_failed": "ecobee \u6240\u9700\u4ee3\u78bc\u932f\u8aa4\uff0c\u8acb\u78ba\u8a8d\u91d1\u9470\u6b63\u78ba\u6027\u3002", diff --git a/homeassistant/components/econet/translations/pt-BR.json b/homeassistant/components/econet/translations/pt-BR.json new file mode 100644 index 00000000000000..55722d53aeb4c8 --- /dev/null +++ b/homeassistant/components/econet/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "password": "Senha" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/efergy/translations/pt-BR.json b/homeassistant/components/efergy/translations/pt-BR.json new file mode 100644 index 00000000000000..065c29ab9ab67b --- /dev/null +++ b/homeassistant/components/efergy/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "api_key": "Chave da API" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elgato/translations/pt-BR.json b/homeassistant/components/elgato/translations/pt-BR.json index 02edb707618968..10441872c5195a 100644 --- a/homeassistant/components/elgato/translations/pt-BR.json +++ b/homeassistant/components/elgato/translations/pt-BR.json @@ -1,8 +1,16 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, "step": { "user": { "data": { + "host": "Nome do host", "port": "Porta" } }, diff --git a/homeassistant/components/elkm1/translations/pt-BR.json b/homeassistant/components/elkm1/translations/pt-BR.json index 932b4b8a72e0ae..efdc82ab438933 100644 --- a/homeassistant/components/elkm1/translations/pt-BR.json +++ b/homeassistant/components/elkm1/translations/pt-BR.json @@ -1,8 +1,14 @@ { "config": { + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, "step": { "user": { "data": { + "password": "Senha", "username": "Usu\u00e1rio" } } diff --git a/homeassistant/components/elmax/translations/pt-BR.json b/homeassistant/components/elmax/translations/pt-BR.json new file mode 100644 index 00000000000000..b2cefe6620692f --- /dev/null +++ b/homeassistant/components/elmax/translations/pt-BR.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "bad_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "invalid_pin": "O C\u00f3digo PIN fornecido \u00e9 inv\u00e1lido", + "network_error": "Ocorreu um erro de rede", + "no_panel_online": "Nenhum painel de controle on-line Elmax foi encontrado.", + "unknown": "Erro inesperado", + "unknown_error": "Erro inesperado" + }, + "step": { + "panels": { + "data": { + "panel_id": "ID do painel", + "panel_name": "Nome do painel", + "panel_pin": "C\u00f3digo PIN" + }, + "description": "Selecione qual painel voc\u00ea gostaria de controlar com esta integra\u00e7\u00e3o. Observe que o painel deve estar LIGADO para ser configurado.", + "title": "Sele\u00e7\u00e3o do painel" + }, + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + }, + "description": "Fa\u00e7a login na nuvem Elmax usando suas credenciais" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/emonitor/translations/pt-BR.json b/homeassistant/components/emonitor/translations/pt-BR.json new file mode 100644 index 00000000000000..ff6ede166a9dd6 --- /dev/null +++ b/homeassistant/components/emonitor/translations/pt-BR.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/translations/pt-BR.json b/homeassistant/components/emulated_roku/translations/pt-BR.json index b04554fd41eb6a..864ae263dbad2b 100644 --- a/homeassistant/components/emulated_roku/translations/pt-BR.json +++ b/homeassistant/components/emulated_roku/translations/pt-BR.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/enocean/translations/pt-BR.json b/homeassistant/components/enocean/translations/pt-BR.json new file mode 100644 index 00000000000000..9ab59f40649f29 --- /dev/null +++ b/homeassistant/components/enocean/translations/pt-BR.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/enocean/translations/zh-Hant.json b/homeassistant/components/enocean/translations/zh-Hant.json index 6000b968e5ec53..021b024c78ff9b 100644 --- a/homeassistant/components/enocean/translations/zh-Hant.json +++ b/homeassistant/components/enocean/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "invalid_dongle_path": "\u88dd\u7f6e\u8def\u5f91\u7121\u6548", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "invalid_dongle_path": "\u6b64\u8def\u5f91\u7121\u6709\u6548\u88dd\u7f6e" diff --git a/homeassistant/components/enphase_envoy/translations/pt-BR.json b/homeassistant/components/enphase_envoy/translations/pt-BR.json new file mode 100644 index 00000000000000..223bb36392f24b --- /dev/null +++ b/homeassistant/components/enphase_envoy/translations/pt-BR.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/environment_canada/translations/pt-BR.json b/homeassistant/components/environment_canada/translations/pt-BR.json new file mode 100644 index 00000000000000..f99ab671b8e327 --- /dev/null +++ b/homeassistant/components/environment_canada/translations/pt-BR.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "latitude": "Latitude", + "longitude": "Longitude" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/epson/translations/pt-BR.json b/homeassistant/components/epson/translations/pt-BR.json new file mode 100644 index 00000000000000..ec60fefab42774 --- /dev/null +++ b/homeassistant/components/epson/translations/pt-BR.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/esphome/translations/pt-BR.json b/homeassistant/components/esphome/translations/pt-BR.json index cb050046d50ee7..6a637c735f7e12 100644 --- a/homeassistant/components/esphome/translations/pt-BR.json +++ b/homeassistant/components/esphome/translations/pt-BR.json @@ -1,10 +1,13 @@ { "config": { "abort": { - "already_configured": "O ESP j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { "connection_error": "N\u00e3o \u00e9 poss\u00edvel conectar-se ao ESP. Por favor, verifique se o seu arquivo YAML cont\u00e9m uma linha 'api:'.", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "resolve_error": "N\u00e3o \u00e9 poss\u00edvel resolver o endere\u00e7o do ESP. Se este erro persistir, por favor, defina um endere\u00e7o IP est\u00e1tico: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" }, "flow_title": "ESPHome: {name}", @@ -21,7 +24,7 @@ }, "user": { "data": { - "host": "Host", + "host": "Nome do host", "port": "Porta" }, "description": "Por favor insira as configura\u00e7\u00f5es de conex\u00e3o de seu n\u00f3 de [ESPHome] (https://esphomelib.com/)." diff --git a/homeassistant/components/evil_genius_labs/translations/pt-BR.json b/homeassistant/components/evil_genius_labs/translations/pt-BR.json new file mode 100644 index 00000000000000..159cd52c341283 --- /dev/null +++ b/homeassistant/components/evil_genius_labs/translations/pt-BR.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ezviz/translations/pt-BR.json b/homeassistant/components/ezviz/translations/pt-BR.json new file mode 100644 index 00000000000000..870003bde5d45e --- /dev/null +++ b/homeassistant/components/ezviz/translations/pt-BR.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured_account": "A conta j\u00e1 foi configurada", + "unknown": "Erro inesperado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido" + }, + "step": { + "confirm": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + }, + "user": { + "data": { + "password": "Senha", + "url": "URL", + "username": "Usu\u00e1rio" + } + }, + "user_custom_url": { + "data": { + "password": "Senha", + "url": "URL", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/pt-BR.json b/homeassistant/components/faa_delays/translations/pt-BR.json new file mode 100644 index 00000000000000..34892b7c476595 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/pt-BR.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fan/translations/pt-BR.json b/homeassistant/components/fan/translations/pt-BR.json index f5e9e2f8629fbe..ef519660db800b 100644 --- a/homeassistant/components/fan/translations/pt-BR.json +++ b/homeassistant/components/fan/translations/pt-BR.json @@ -3,6 +3,10 @@ "condition_type": { "is_off": "{entity_name} est\u00e1 desligado", "is_on": "{entity_name} est\u00e1 ligado" + }, + "trigger_type": { + "changed_states": "{entity_name} ligado ou desligado", + "toggled": "{entity_name} ligado ou desligado" } }, "state": { diff --git a/homeassistant/components/fireservicerota/translations/pt-BR.json b/homeassistant/components/fireservicerota/translations/pt-BR.json new file mode 100644 index 00000000000000..2d9c2ad919c9c6 --- /dev/null +++ b/homeassistant/components/fireservicerota/translations/pt-BR.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "create_entry": { + "default": "Autenticado com sucesso" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "reauth": { + "data": { + "password": "Senha" + } + }, + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/firmata/translations/pt-BR.json b/homeassistant/components/firmata/translations/pt-BR.json new file mode 100644 index 00000000000000..fa50f0901aae9b --- /dev/null +++ b/homeassistant/components/firmata/translations/pt-BR.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cannot_connect": "Falha ao conectar" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fjaraskupan/translations/pt-BR.json b/homeassistant/components/fjaraskupan/translations/pt-BR.json new file mode 100644 index 00000000000000..d529509749c38b --- /dev/null +++ b/homeassistant/components/fjaraskupan/translations/pt-BR.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fjaraskupan/translations/zh-Hant.json b/homeassistant/components/fjaraskupan/translations/zh-Hant.json index 3312cea3576a16..6a7db18da6170e 100644 --- a/homeassistant/components/fjaraskupan/translations/zh-Hant.json +++ b/homeassistant/components/fjaraskupan/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "step": { "confirm": { diff --git a/homeassistant/components/flick_electric/translations/pt-BR.json b/homeassistant/components/flick_electric/translations/pt-BR.json index f23a27c2b73b16..2601b89b2a1225 100644 --- a/homeassistant/components/flick_electric/translations/pt-BR.json +++ b/homeassistant/components/flick_electric/translations/pt-BR.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Essa conta j\u00e1 est\u00e1 configurada" + "already_configured": "A conta j\u00e1 foi configurada" }, "error": { - "cannot_connect": "Falha ao conectar, tente novamente", + "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, diff --git a/homeassistant/components/flipr/translations/pt-BR.json b/homeassistant/components/flipr/translations/pt-BR.json new file mode 100644 index 00000000000000..8722382b01bd53 --- /dev/null +++ b/homeassistant/components/flipr/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Senha" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flo/translations/pt-BR.json b/homeassistant/components/flo/translations/pt-BR.json index 026edf7221c66b..93beddb92a8517 100644 --- a/homeassistant/components/flo/translations/pt-BR.json +++ b/homeassistant/components/flo/translations/pt-BR.json @@ -1,7 +1,21 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "password": "Senha", + "username": "Usu\u00e1rio" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/flume/translations/pt-BR.json b/homeassistant/components/flume/translations/pt-BR.json index 033dc26c856076..17c9f8afe60d1f 100644 --- a/homeassistant/components/flume/translations/pt-BR.json +++ b/homeassistant/components/flume/translations/pt-BR.json @@ -1,18 +1,26 @@ { "config": { "abort": { - "already_configured": "Essa conta j\u00e1 est\u00e1 configurada" + "already_configured": "A conta j\u00e1 foi configurada", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { - "cannot_connect": "Falha ao conectar, tente novamente", + "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, "step": { + "reauth_confirm": { + "data": { + "password": "Senha" + } + }, "user": { "data": { "client_id": "ID do Cliente", - "client_secret": "Segredo do cliente" + "client_secret": "Segredo do cliente", + "password": "Senha", + "username": "Usu\u00e1rio" }, "description": "Para acessar a API pessoal do Flume, voc\u00ea precisar\u00e1 solicitar um 'ID do Cliente' e 'Segredo do Cliente' em https://portal.flumetech.com/settings#token", "title": "Conecte-se \u00e0 sua conta Flume" diff --git a/homeassistant/components/flunearyou/translations/pt-BR.json b/homeassistant/components/flunearyou/translations/pt-BR.json new file mode 100644 index 00000000000000..35950f57d3b40f --- /dev/null +++ b/homeassistant/components/flunearyou/translations/pt-BR.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Localiza\u00e7\u00e3o j\u00e1 est\u00e1 configurada" + }, + "error": { + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "latitude": "Latitude", + "longitude": "Longitude" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flux_led/translations/pt-BR.json b/homeassistant/components/flux_led/translations/pt-BR.json new file mode 100644 index 00000000000000..82b12411670f2e --- /dev/null +++ b/homeassistant/components/flux_led/translations/pt-BR.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "flow_title": "{modelo} {id} ({ipaddr})", + "step": { + "discovery_confirm": { + "description": "Deseja configurar {model} {id} ({ipaddr})?" + }, + "user": { + "data": { + "host": "Nome do host" + }, + "description": "Se voc\u00ea deixar o host vazio, a descoberta ser\u00e1 usada para localizar dispositivos." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "custom_effect_colors": "Efeito personalizado: Lista de 1 a 16 cores [R,G,B]. Exemplo: [255,0,255],[60,128,0]", + "custom_effect_speed_pct": "Efeito personalizado: Velocidade em porcentagens para o efeito que muda de cor.", + "custom_effect_transition": "Efeito Personalizado: Tipo de transi\u00e7\u00e3o entre as cores.", + "mode": "O modo de brilho escolhido." + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forecast_solar/translations/el.json b/homeassistant/components/forecast_solar/translations/el.json new file mode 100644 index 00000000000000..ac82268cef49bb --- /dev/null +++ b/homeassistant/components/forecast_solar/translations/el.json @@ -0,0 +1,9 @@ +{ + "options": { + "step": { + "init": { + "description": "\u0391\u03c5\u03c4\u03ad\u03c2 \u03bf\u03b9 \u03c4\u03b9\u03bc\u03ad\u03c2 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03bf\u03c5\u03bd \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b1\u03c0\u03bf\u03c4\u03b5\u03bb\u03ad\u03c3\u03bc\u03b1\u03c4\u03bf\u03c2 Solar.Forecast. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7 \u03b5\u03ac\u03bd \u03ba\u03ac\u03c0\u03bf\u03b9\u03bf \u03c0\u03b5\u03b4\u03af\u03bf \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03b1\u03c6\u03ad\u03c2." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forecast_solar/translations/pt-BR.json b/homeassistant/components/forecast_solar/translations/pt-BR.json new file mode 100644 index 00000000000000..aad75b3bed08b1 --- /dev/null +++ b/homeassistant/components/forecast_solar/translations/pt-BR.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/pt-BR.json b/homeassistant/components/forked_daapd/translations/pt-BR.json index c45178d6a72199..a40bc1321a17e4 100644 --- a/homeassistant/components/forked_daapd/translations/pt-BR.json +++ b/homeassistant/components/forked_daapd/translations/pt-BR.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado.", + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "not_forked_daapd": "O dispositivo n\u00e3o \u00e9 um servidor forked-daapd." }, "error": { - "unknown_error": "Erro desconhecido.", + "unknown_error": "Erro inesperado", "websocket_not_enabled": "websocket do servidor forked-daapd n\u00e3o ativado.", "wrong_host_or_port": "N\u00e3o foi poss\u00edvel conectar. Por favor, verifique o endere\u00e7o e a porta.", "wrong_password": "Senha incorreta.", @@ -15,7 +15,7 @@ "step": { "user": { "data": { - "host": "Endere\u00e7o (IP)", + "host": "Nome do host", "name": "Nome amig\u00e1vel", "password": "Senha da API (deixe em branco se n\u00e3o houver senha)", "port": "Porta API" diff --git a/homeassistant/components/foscam/translations/pt-BR.json b/homeassistant/components/foscam/translations/pt-BR.json new file mode 100644 index 00000000000000..8037db16e534a4 --- /dev/null +++ b/homeassistant/components/foscam/translations/pt-BR.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "password": "Senha", + "port": "Porta", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/freebox/translations/pt-BR.json b/homeassistant/components/freebox/translations/pt-BR.json new file mode 100644 index 00000000000000..1e898e15ce0558 --- /dev/null +++ b/homeassistant/components/freebox/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "port": "Porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/freedompro/translations/pt-BR.json b/homeassistant/components/freedompro/translations/pt-BR.json new file mode 100644 index 00000000000000..71a36bb83590fe --- /dev/null +++ b/homeassistant/components/freedompro/translations/pt-BR.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "api_key": "Chave da API" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritz/translations/pt-BR.json b/homeassistant/components/fritz/translations/pt-BR.json new file mode 100644 index 00000000000000..9076a635244ce8 --- /dev/null +++ b/homeassistant/components/fritz/translations/pt-BR.json @@ -0,0 +1,46 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "cannot_connect": "Falha ao conectar", + "connection_error": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "confirm": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + }, + "reauth_confirm": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + }, + "start_config": { + "data": { + "host": "Nome do host", + "password": "Senha", + "port": "Porta", + "username": "Usu\u00e1rio" + } + }, + "user": { + "data": { + "host": "Nome do host", + "password": "Senha", + "port": "Porta", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/pt-BR.json b/homeassistant/components/fritzbox/translations/pt-BR.json index 9685e93f927100..7693e15a9ecc6a 100644 --- a/homeassistant/components/fritzbox/translations/pt-BR.json +++ b/homeassistant/components/fritzbox/translations/pt-BR.json @@ -1,5 +1,14 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, "step": { "confirm": { "data": { @@ -8,8 +17,15 @@ }, "description": "Voc\u00ea quer configurar o {name}?" }, + "reauth_confirm": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + }, "user": { "data": { + "host": "Nome do host", "password": "Senha", "username": "Usu\u00e1rio" } diff --git a/homeassistant/components/fritzbox_callmonitor/translations/el.json b/homeassistant/components/fritzbox_callmonitor/translations/el.json new file mode 100644 index 00000000000000..c78531f645faa0 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/el.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "insufficient_permissions": "\u039f \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7\u03c2 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b5\u03c0\u03b1\u03c1\u03ba\u03ae \u03b4\u03b9\u03ba\u03b1\u03b9\u03ce\u03bc\u03b1\u03c4\u03b1 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c3\u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c4\u03bf\u03c5 AVM FRITZ!Box \u03ba\u03b1\u03b9 \u03c3\u03c4\u03bf\u03c5\u03c2 \u03c4\u03b7\u03bb\u03b5\u03c6\u03c9\u03bd\u03b9\u03ba\u03bf\u03cd\u03c2 \u03ba\u03b1\u03c4\u03b1\u03bb\u03cc\u03b3\u03bf\u03c5\u03c2 \u03c4\u03bf\u03c5." + }, + "flow_title": "{name}", + "step": { + "phonebook": { + "data": { + "phonebook": "\u03a4\u03b7\u03bb\u03b5\u03c6\u03c9\u03bd\u03b9\u03ba\u03cc\u03c2 \u03ba\u03b1\u03c4\u03ac\u03bb\u03bf\u03b3\u03bf\u03c2" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "\u03a4\u03b1 \u03c0\u03c1\u03bf\u03b8\u03ad\u03bc\u03b1\u03c4\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03bb\u03b1\u03bd\u03b8\u03b1\u03c3\u03bc\u03ad\u03bd\u03b1, \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae \u03c4\u03bf\u03c5\u03c2." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/pt-BR.json b/homeassistant/components/fritzbox_callmonitor/translations/pt-BR.json new file mode 100644 index 00000000000000..7bfce93a5712e9 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/pt-BR.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "password": "Senha", + "port": "Porta", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fronius/translations/el.json b/homeassistant/components/fronius/translations/el.json index 7e137fac7599b1..1f22f20b3a2ead 100644 --- a/homeassistant/components/fronius/translations/el.json +++ b/homeassistant/components/fronius/translations/el.json @@ -4,6 +4,10 @@ "step": { "confirm_discovery": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {device} \u03c3\u03c4\u03bf Home Assistant;" + }, + "user": { + "description": "\u0394\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03ae \u03c4\u03bf \u03c4\u03bf\u03c0\u03b9\u03ba\u03cc \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 Fronius.", + "title": "Fronius SolarNet" } } } diff --git a/homeassistant/components/fronius/translations/pt-BR.json b/homeassistant/components/fronius/translations/pt-BR.json new file mode 100644 index 00000000000000..70f90c5b5f4267 --- /dev/null +++ b/homeassistant/components/fronius/translations/pt-BR.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "flow_title": "{device}", + "step": { + "confirm_discovery": { + "description": "Deseja adicionar {device} ao Home Assistant?" + }, + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/garages_amsterdam/translations/pt-BR.json b/homeassistant/components/garages_amsterdam/translations/pt-BR.json new file mode 100644 index 00000000000000..4b01a4755c38d0 --- /dev/null +++ b/homeassistant/components/garages_amsterdam/translations/pt-BR.json @@ -0,0 +1,9 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gdacs/translations/pt-BR.json b/homeassistant/components/gdacs/translations/pt-BR.json index 1e866fa8059c3e..53de0312b7384c 100644 --- a/homeassistant/components/gdacs/translations/pt-BR.json +++ b/homeassistant/components/gdacs/translations/pt-BR.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Local j\u00e1 est\u00e1 configurado." + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" }, "step": { "user": { diff --git a/homeassistant/components/geofency/translations/pt-BR.json b/homeassistant/components/geofency/translations/pt-BR.json index d8b430df5f4301..95ac686e86e79c 100644 --- a/homeassistant/components/geofency/translations/pt-BR.json +++ b/homeassistant/components/geofency/translations/pt-BR.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "cloud_not_connected": "N\u00e3o conectado ao Home Assistant Cloud.", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, "create_entry": { "default": "Para enviar eventos para o Home Assistant, voc\u00ea precisar\u00e1 configurar o recurso webhook no Geofency. \n\n Preencha as seguintes informa\u00e7\u00f5es: \n\n - URL: ` {webhook_url} ` \n - M\u00e9todo: POST \n\n Veja [a documenta\u00e7\u00e3o] ( {docs_url} ) para mais detalhes." }, diff --git a/homeassistant/components/geofency/translations/zh-Hant.json b/homeassistant/components/geofency/translations/zh-Hant.json index accbab5f1d39ad..6862ab5208e64f 100644 --- a/homeassistant/components/geofency/translations/zh-Hant.json +++ b/homeassistant/components/geofency/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "cloud_not_connected": "\u672a\u9023\u7dda\u81f3 Home Assistant \u96f2\u670d\u52d9\u3002", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "webhook_not_internet_accessible": "Home Assistant \u5be6\u9ad4\u5fc5\u9808\u8981\u80fd\u5f9e\u7db2\u969b\u7db2\u8def\u5b58\u53d6\u65b9\u80fd\u63a5\u6536 Webhook \u8a0a\u606f\u3002" }, "create_entry": { diff --git a/homeassistant/components/geonetnz_quakes/translations/pt-BR.json b/homeassistant/components/geonetnz_quakes/translations/pt-BR.json index ee705850b03191..60cc25c59ce8ae 100644 --- a/homeassistant/components/geonetnz_quakes/translations/pt-BR.json +++ b/homeassistant/components/geonetnz_quakes/translations/pt-BR.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Local j\u00e1 est\u00e1 configurado." + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" }, "step": { "user": { diff --git a/homeassistant/components/geonetnz_volcano/translations/pt-BR.json b/homeassistant/components/geonetnz_volcano/translations/pt-BR.json index 98180e11248aaa..6f79c4865191e5 100644 --- a/homeassistant/components/geonetnz_volcano/translations/pt-BR.json +++ b/homeassistant/components/geonetnz_volcano/translations/pt-BR.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Localiza\u00e7\u00e3o j\u00e1 est\u00e1 configurada" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/gios/translations/pt-BR.json b/homeassistant/components/gios/translations/pt-BR.json index 83add749e47102..2228a8031ac8ae 100644 --- a/homeassistant/components/gios/translations/pt-BR.json +++ b/homeassistant/components/gios/translations/pt-BR.json @@ -1,7 +1,16 @@ { "config": { + "abort": { + "already_configured": "Localiza\u00e7\u00e3o j\u00e1 est\u00e1 configurada" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, "step": { "user": { + "data": { + "name": "Nome" + }, "title": "GIO\u015a (Inspetor-Chefe Polon\u00eas de Prote\u00e7\u00e3o Ambiental)" } } diff --git a/homeassistant/components/glances/translations/pt-BR.json b/homeassistant/components/glances/translations/pt-BR.json index 7f5535fd04b663..1953aa13e2e859 100644 --- a/homeassistant/components/glances/translations/pt-BR.json +++ b/homeassistant/components/glances/translations/pt-BR.json @@ -1,11 +1,21 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, "step": { "user": { "data": { + "host": "Nome do host", + "name": "Nome", "password": "Senha", "port": "Porta", - "username": "Usu\u00e1rio" + "ssl": "Usar um certificado SSL", + "username": "Usu\u00e1rio", + "verify_ssl": "Verifique o certificado SSL" } } } diff --git a/homeassistant/components/goalzero/translations/el.json b/homeassistant/components/goalzero/translations/el.json index 61936c6ff56080..cf4ccf81af6958 100644 --- a/homeassistant/components/goalzero/translations/el.json +++ b/homeassistant/components/goalzero/translations/el.json @@ -9,6 +9,9 @@ "unknown": "\u0386\u03b3\u03bd\u03c9\u03c3\u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { + "confirm_discovery": { + "title": "Goal Zero Yeti" + }, "user": { "data": { "host": "\u0394\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2", diff --git a/homeassistant/components/goalzero/translations/pt-BR.json b/homeassistant/components/goalzero/translations/pt-BR.json new file mode 100644 index 00000000000000..81137fe0bdca2e --- /dev/null +++ b/homeassistant/components/goalzero/translations/pt-BR.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido", + "unknown": "Erro inesperado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/pt-BR.json b/homeassistant/components/gogogate2/translations/pt-BR.json index 79dc7af8131fd2..fd074fcc0f81ea 100644 --- a/homeassistant/components/gogogate2/translations/pt-BR.json +++ b/homeassistant/components/gogogate2/translations/pt-BR.json @@ -1,11 +1,18 @@ { "config": { + "abort": { + "cannot_connect": "Falha ao conectar" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, "step": { "user": { "data": { "ip_address": "Endere\u00e7o IP", "password": "Senha", - "username": "Nome de usu\u00e1rio" + "username": "Usu\u00e1rio" }, "description": "Forne\u00e7a as informa\u00e7\u00f5es necess\u00e1rias abaixo.", "title": "Configurar GogoGate2" diff --git a/homeassistant/components/goodwe/translations/pt-BR.json b/homeassistant/components/goodwe/translations/pt-BR.json new file mode 100644 index 00000000000000..14b1971e62580b --- /dev/null +++ b/homeassistant/components/goodwe/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento" + }, + "error": { + "connection_error": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "host": "Endere\u00e7o IP" + }, + "description": "Conecte ao inversor", + "title": "Inversor GoodWe" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google_travel_time/translations/pt-BR.json b/homeassistant/components/google_travel_time/translations/pt-BR.json new file mode 100644 index 00000000000000..9365f5ac690cd2 --- /dev/null +++ b/homeassistant/components/google_travel_time/translations/pt-BR.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Localiza\u00e7\u00e3o j\u00e1 est\u00e1 configurada" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "api_key": "Chave da API", + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/translations/pt-BR.json b/homeassistant/components/gpslogger/translations/pt-BR.json index fe07d42744ea28..8ef272f06702d4 100644 --- a/homeassistant/components/gpslogger/translations/pt-BR.json +++ b/homeassistant/components/gpslogger/translations/pt-BR.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "cloud_not_connected": "N\u00e3o conectado ao Home Assistant Cloud.", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, "create_entry": { "default": "Para enviar eventos para o Home Assistant, voc\u00ea precisar\u00e1 configurar o recurso webhook no GPSLogger. \n\n Preencha as seguintes informa\u00e7\u00f5es: \n\n - URL: ` {webhook_url} ` \n - M\u00e9todo: POST \n\n Veja [a documenta\u00e7\u00e3o] ( {docs_url} ) para mais detalhes." }, diff --git a/homeassistant/components/gpslogger/translations/zh-Hant.json b/homeassistant/components/gpslogger/translations/zh-Hant.json index d1e2c7433010ed..7d77525cc8a948 100644 --- a/homeassistant/components/gpslogger/translations/zh-Hant.json +++ b/homeassistant/components/gpslogger/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "cloud_not_connected": "\u672a\u9023\u7dda\u81f3 Home Assistant \u96f2\u670d\u52d9\u3002", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "webhook_not_internet_accessible": "Home Assistant \u5be6\u9ad4\u5fc5\u9808\u8981\u80fd\u5f9e\u7db2\u969b\u7db2\u8def\u5b58\u53d6\u65b9\u80fd\u63a5\u6536 Webhook \u8a0a\u606f\u3002" }, "create_entry": { diff --git a/homeassistant/components/gree/translations/pt-BR.json b/homeassistant/components/gree/translations/pt-BR.json new file mode 100644 index 00000000000000..d5efbb90261324 --- /dev/null +++ b/homeassistant/components/gree/translations/pt-BR.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "step": { + "confirm": { + "description": "Deseja iniciar a configura\u00e7\u00e3o?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gree/translations/zh-Hant.json b/homeassistant/components/gree/translations/zh-Hant.json index 90c98e491dfea4..cfd20d603cba17 100644 --- a/homeassistant/components/gree/translations/zh-Hant.json +++ b/homeassistant/components/gree/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "step": { "confirm": { diff --git a/homeassistant/components/growatt_server/translations/pt-BR.json b/homeassistant/components/growatt_server/translations/pt-BR.json new file mode 100644 index 00000000000000..e956f89d381c6a --- /dev/null +++ b/homeassistant/components/growatt_server/translations/pt-BR.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "name": "Nome", + "password": "Senha", + "url": "URL", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/guardian/translations/pt-BR.json b/homeassistant/components/guardian/translations/pt-BR.json new file mode 100644 index 00000000000000..14615cff00234f --- /dev/null +++ b/homeassistant/components/guardian/translations/pt-BR.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "ip_address": "Endere\u00e7o IP", + "port": "Porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/pt-BR.json b/homeassistant/components/habitica/translations/pt-BR.json new file mode 100644 index 00000000000000..f8ca0b401878b7 --- /dev/null +++ b/homeassistant/components/habitica/translations/pt-BR.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "invalid_credentials": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "api_key": "Chave da API", + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hangouts/translations/pt-BR.json b/homeassistant/components/hangouts/translations/pt-BR.json index 3f8fd23b07c9ce..bcf50b5d3da0a7 100644 --- a/homeassistant/components/hangouts/translations/pt-BR.json +++ b/homeassistant/components/hangouts/translations/pt-BR.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Hangouts do Google j\u00e1 est\u00e1 configurado.", - "unknown": "Ocorreu um erro desconhecido." + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado", + "unknown": "Erro inesperado" }, "error": { "invalid_2fa": "Autentica\u00e7\u00e3o de 2 fatores inv\u00e1lida, por favor, tente novamente.", diff --git a/homeassistant/components/harmony/translations/pt-BR.json b/homeassistant/components/harmony/translations/pt-BR.json index 7fe3f58cad606e..7686665e6f68be 100644 --- a/homeassistant/components/harmony/translations/pt-BR.json +++ b/homeassistant/components/harmony/translations/pt-BR.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "error": { - "cannot_connect": "Falha ao conectar, tente novamente", + "cannot_connect": "Falha ao conectar", "unknown": "Erro inesperado" }, "flow_title": "Logitech Harmony Hub {name}", @@ -15,6 +15,7 @@ }, "user": { "data": { + "host": "Nome do host", "name": "Nome do Hub" }, "title": "Configura\u00e7\u00e3o do Logitech Harmony Hub" diff --git a/homeassistant/components/heos/translations/pt-BR.json b/homeassistant/components/heos/translations/pt-BR.json index 328264d3adfb5a..767cd5d5fb3dc5 100644 --- a/homeassistant/components/heos/translations/pt-BR.json +++ b/homeassistant/components/heos/translations/pt-BR.json @@ -1,9 +1,15 @@ { "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, "step": { "user": { "data": { - "host": "Host" + "host": "Nome do host" }, "description": "Por favor, digite o nome do host ou o endere\u00e7o IP de um dispositivo Heos (de prefer\u00eancia para conex\u00f5es conectadas por cabo \u00e0 sua rede).", "title": "Conecte-se a Heos" diff --git a/homeassistant/components/heos/translations/zh-Hant.json b/homeassistant/components/heos/translations/zh-Hant.json index fe3e8fb7b432f5..8a452d98d03121 100644 --- a/homeassistant/components/heos/translations/zh-Hant.json +++ b/homeassistant/components/heos/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557" diff --git a/homeassistant/components/hisense_aehw4a1/translations/pt-BR.json b/homeassistant/components/hisense_aehw4a1/translations/pt-BR.json new file mode 100644 index 00000000000000..d529509749c38b --- /dev/null +++ b/homeassistant/components/hisense_aehw4a1/translations/pt-BR.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hisense_aehw4a1/translations/zh-Hant.json b/homeassistant/components/hisense_aehw4a1/translations/zh-Hant.json index e08a2c5f6df0c4..49eae73f25e197 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/zh-Hant.json +++ b/homeassistant/components/hisense_aehw4a1/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "step": { "confirm": { diff --git a/homeassistant/components/hive/translations/pt-BR.json b/homeassistant/components/hive/translations/pt-BR.json new file mode 100644 index 00000000000000..ef6f9993b115fa --- /dev/null +++ b/homeassistant/components/hive/translations/pt-BR.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "unknown": "Erro inesperado" + }, + "step": { + "reauth": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + }, + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hlk_sw16/translations/pt-BR.json b/homeassistant/components/hlk_sw16/translations/pt-BR.json new file mode 100644 index 00000000000000..93beddb92a8517 --- /dev/null +++ b/homeassistant/components/hlk_sw16/translations/pt-BR.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/home_connect/translations/pt-BR.json b/homeassistant/components/home_connect/translations/pt-BR.json index ff8e13aed1febf..ea479059ebbf1c 100644 --- a/homeassistant/components/home_connect/translations/pt-BR.json +++ b/homeassistant/components/home_connect/translations/pt-BR.json @@ -1,7 +1,11 @@ { "config": { "abort": { - "missing_configuration": "O componente Home Connect n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o." + "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", + "no_url_available": "N\u00e3o h\u00e1 URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a se\u00e7\u00e3o de ajuda]({docs_url})" + }, + "create_entry": { + "default": "Autenticado com sucesso" } } } \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/pt-BR.json b/homeassistant/components/home_plus_control/translations/pt-BR.json new file mode 100644 index 00000000000000..85e6cfdec61da8 --- /dev/null +++ b/homeassistant/components/home_plus_control/translations/pt-BR.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", + "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", + "no_url_available": "N\u00e3o h\u00e1 URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a se\u00e7\u00e3o de ajuda]({docs_url})", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "create_entry": { + "default": "Autenticado com sucesso" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/zh-Hant.json b/homeassistant/components/home_plus_control/translations/zh-Hant.json index 0faa311028720a..da55be65f04735 100644 --- a/homeassistant/components/home_plus_control/translations/zh-Hant.json +++ b/homeassistant/components/home_plus_control/translations/zh-Hant.json @@ -6,7 +6,7 @@ "authorize_url_timeout": "\u7522\u751f\u8a8d\u8b49 URL \u6642\u903e\u6642\u3002", "missing_configuration": "\u5143\u4ef6\u5c1a\u672a\u8a2d\u7f6e\uff0c\u8acb\u53c3\u95b1\u6587\u4ef6\u8aaa\u660e\u3002", "no_url_available": "\u6c92\u6709\u53ef\u7528\u7684\u7db2\u5740\u3002\u95dc\u65bc\u6b64\u932f\u8aa4\u66f4\u8a73\u7d30\u8a0a\u606f\uff0c[\u9ede\u9078\u5354\u52a9\u7ae0\u7bc0]({docs_url})", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "create_entry": { "default": "\u5df2\u6210\u529f\u8a8d\u8b49" diff --git a/homeassistant/components/homekit/translations/pt-BR.json b/homeassistant/components/homekit/translations/pt-BR.json index 1ef802a92023b9..1c2e3094ec553f 100644 --- a/homeassistant/components/homekit/translations/pt-BR.json +++ b/homeassistant/components/homekit/translations/pt-BR.json @@ -1,11 +1,29 @@ { "options": { "step": { + "accessory": { + "data": { + "entities": "Entidades" + }, + "title": "Selecione a entidade para o acess\u00f3rio" + }, "advanced": { "title": "Configura\u00e7\u00e3o avan\u00e7ada" }, "cameras": { "title": "Selecione o codec de v\u00eddeo da c\u00e2mera." + }, + "exclude": { + "data": { + "entities": "Entidades" + }, + "description": "Todas as {domains} \u201d ser\u00e3o inclu\u00eddas, exceto as entidades exclu\u00eddas e as entidades categorizadas.", + "title": "Selecione as entidades a serem exclu\u00eddas" + }, + "include": { + "data": { + "entities": "Entidades" + } } } } diff --git a/homeassistant/components/homekit_controller/translations/pt-BR.json b/homeassistant/components/homekit_controller/translations/pt-BR.json index 55f4b71f7b2ad5..47ee5b8630a5a5 100644 --- a/homeassistant/components/homekit_controller/translations/pt-BR.json +++ b/homeassistant/components/homekit_controller/translations/pt-BR.json @@ -3,7 +3,7 @@ "abort": { "accessory_not_found_error": "N\u00e3o \u00e9 poss\u00edvel adicionar o emparelhamento, pois o dispositivo n\u00e3o pode mais ser encontrado.", "already_configured": "O acess\u00f3rio j\u00e1 est\u00e1 configurado com este controlador.", - "already_in_progress": "O fluxo de configura\u00e7\u00e3o para o dispositivo j\u00e1 est\u00e1 em andamento.", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", "already_paired": "Este acess\u00f3rio j\u00e1 est\u00e1 pareado com outro dispositivo. Por favor, redefina o acess\u00f3rio e tente novamente.", "ignored_model": "O suporte do HomeKit para este modelo est\u00e1 bloqueado, j\u00e1 que uma integra\u00e7\u00e3o nativa mais completa est\u00e1 dispon\u00edvel.", "invalid_config_entry": "Este dispositivo est\u00e1 mostrando como pronto para parear, mas existe um conflito na configura\u00e7\u00e3o de entrada para ele no Home Assistant que deve ser removida primeiro.", diff --git a/homeassistant/components/homekit_controller/translations/select.pt-BR.json b/homeassistant/components/homekit_controller/translations/select.pt-BR.json new file mode 100644 index 00000000000000..e807b3eacf7c7e --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/select.pt-BR.json @@ -0,0 +1,9 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "away": "Fora", + "home": "Casa", + "sleep": "Dormir" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homematicip_cloud/translations/pt-BR.json b/homeassistant/components/homematicip_cloud/translations/pt-BR.json index c19678ad0c4944..101cdd83c48aa4 100644 --- a/homeassistant/components/homematicip_cloud/translations/pt-BR.json +++ b/homeassistant/components/homematicip_cloud/translations/pt-BR.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "O Accesspoint j\u00e1 est\u00e1 configurado", - "connection_aborted": "N\u00e3o foi poss\u00edvel conectar ao servidor HMIP", - "unknown": "Ocorreu um erro desconhecido." + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "connection_aborted": "Falha ao conectar", + "unknown": "Erro inesperado" }, "error": { - "invalid_sgtin_or_pin": "PIN inv\u00e1lido, por favor tente novamente.", + "invalid_sgtin_or_pin": "C\u00f3digo PIN inv\u00e1lido, por favor tente novamente.", "press_the_button": "Por favor, pressione o bot\u00e3o azul.", "register_failed": "Falha ao registrar, por favor tente novamente.", "timeout_button": "Tempo para pressionar o Bot\u00e3o Azul expirou, por favor tente novamente." @@ -16,7 +16,7 @@ "data": { "hapid": "ID do AccessPoint (SGTIN)", "name": "Nome (opcional, usado como prefixo de nome para todos os dispositivos)", - "pin": "C\u00f3digo PIN (opcional)" + "pin": "C\u00f3digo PIN" }, "title": "Escolha um HomematicIP Accesspoint" }, diff --git a/homeassistant/components/homewizard/translations/pt-BR.json b/homeassistant/components/homewizard/translations/pt-BR.json new file mode 100644 index 00000000000000..2916d86b2428fb --- /dev/null +++ b/homeassistant/components/homewizard/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "unknown_error": "Erro inesperado" + }, + "step": { + "discovery_confirm": { + "title": "Confirmar" + }, + "user": { + "data": { + "ip_address": "Endere\u00e7o IP" + }, + "title": "Configurar dispositivo" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/honeywell/translations/pt-BR.json b/homeassistant/components/honeywell/translations/pt-BR.json new file mode 100644 index 00000000000000..7922f363bdab25 --- /dev/null +++ b/homeassistant/components/honeywell/translations/pt-BR.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huawei_lte/translations/pt-BR.json b/homeassistant/components/huawei_lte/translations/pt-BR.json index c69337fa2bb332..821b2f6e72b00c 100644 --- a/homeassistant/components/huawei_lte/translations/pt-BR.json +++ b/homeassistant/components/huawei_lte/translations/pt-BR.json @@ -1,11 +1,17 @@ { "config": { "abort": { - "already_configured": "Este dispositivo j\u00e1 foi configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" }, "step": { "user": { "data": { + "password": "Senha", "url": "URL", "username": "Usu\u00e1rio" } diff --git a/homeassistant/components/hue/translations/pt-BR.json b/homeassistant/components/hue/translations/pt-BR.json index 9a7e8094b11314..a2cc864aeff072 100644 --- a/homeassistant/components/hue/translations/pt-BR.json +++ b/homeassistant/components/hue/translations/pt-BR.json @@ -2,35 +2,61 @@ "config": { "abort": { "all_configured": "Todas as pontes Philips Hue j\u00e1 est\u00e3o configuradas", - "already_configured": "A ponte j\u00e1 est\u00e1 configurada", - "already_in_progress": "O fluxo de configura\u00e7\u00e3o da ponte j\u00e1 est\u00e1 em andamento.", - "cannot_connect": "N\u00e3o \u00e9 poss\u00edvel conectar-se \u00e0 ponte", + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "cannot_connect": "Falha ao conectar", "discover_timeout": "Incapaz de descobrir pontes Hue", "no_bridges": "N\u00e3o h\u00e1 pontes Philips Hue descobertas", "not_hue_bridge": "N\u00e3o \u00e9 uma ponte Hue", - "unknown": "Ocorreu um erro desconhecido" + "unknown": "Erro inesperado" }, "error": { - "linking": "Ocorreu um erro de liga\u00e7\u00e3o desconhecido.", + "linking": "Erro inesperado", "register_failed": "Falhou ao registrar, por favor tente novamente" }, "step": { "init": { "data": { - "host": "Hospedeiro" + "host": "Nome do host" }, "title": "Escolha a ponte Hue" }, "link": { "description": "Pressione o bot\u00e3o na ponte para registrar o Philips Hue com o Home Assistant. \n\n ![Localiza\u00e7\u00e3o do bot\u00e3o na ponte](/static/images/config_philips_hue.jpg)", "title": "Hub de links" + }, + "manual": { + "data": { + "host": "Nome do host" + } } } }, "device_automation": { "trigger_subtype": { + "1": "Primeiro bot\u00e3o", + "2": "Segundo bot\u00e3o", + "3": "Terceiro bot\u00e3o", + "4": "Quarto bot\u00e3o", "double_buttons_1_3": "Primeiro e terceiro bot\u00f5es", "double_buttons_2_4": "Segundo e quarto bot\u00f5es" + }, + "trigger_type": { + "double_short_release": "Ambos \"{subtype}\" liberados", + "initial_press": "Bot\u00e3o \" {subtype} \" pressionado inicialmente", + "long_release": "Bot\u00e3o \" {subtype} \" liberado ap\u00f3s press\u00e3o longa", + "repeat": "Bot\u00e3o \" {subtype} \" pressionado", + "short_release": "Bot\u00e3o \" {subtype} \" liberado ap\u00f3s pressionamento curto" + } + }, + "options": { + "step": { + "init": { + "data": { + "allow_hue_scenes": "Permitir cenas Hue", + "ignore_availability": "Ignorar o status de conectividade para os dispositivos fornecidos" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/pt-BR.json b/homeassistant/components/huisbaasje/translations/pt-BR.json new file mode 100644 index 00000000000000..66c671f99a3c2e --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/humidifier/translations/pt-BR.json b/homeassistant/components/humidifier/translations/pt-BR.json new file mode 100644 index 00000000000000..2783abe00e6a68 --- /dev/null +++ b/homeassistant/components/humidifier/translations/pt-BR.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "trigger_type": { + "changed_states": "{entity_name} ligado ou desligado", + "toggled": "{entity_name} ligado ou desligado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/pt-BR.json b/homeassistant/components/hunterdouglas_powerview/translations/pt-BR.json index f7dc708a2d6169..b98d170336fbe0 100644 --- a/homeassistant/components/hunterdouglas_powerview/translations/pt-BR.json +++ b/homeassistant/components/hunterdouglas_powerview/translations/pt-BR.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/hvv_departures/translations/el.json b/homeassistant/components/hvv_departures/translations/el.json index 916b438c046ddb..f974693fd2a8cd 100644 --- a/homeassistant/components/hvv_departures/translations/el.json +++ b/homeassistant/components/hvv_departures/translations/el.json @@ -28,7 +28,9 @@ "filter": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03b3\u03c1\u03b1\u03bc\u03bc\u03ad\u03c2", "offset": "\u039c\u03b5\u03c4\u03b1\u03c4\u03cc\u03c0\u03b9\u03c3\u03b7 (\u03bb\u03b5\u03c0\u03c4\u03ac)", "real_time": "\u03a7\u03c1\u03ae\u03c3\u03b7 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd \u03c3\u03b5 \u03c0\u03c1\u03b1\u03b3\u03bc\u03b1\u03c4\u03b9\u03ba\u03cc \u03c7\u03c1\u03cc\u03bd\u03bf" - } + }, + "description": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ae \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03b1\u03bd\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7\u03c2", + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2" } } } diff --git a/homeassistant/components/hvv_departures/translations/pt-BR.json b/homeassistant/components/hvv_departures/translations/pt-BR.json new file mode 100644 index 00000000000000..f10ded2b0b379b --- /dev/null +++ b/homeassistant/components/hvv_departures/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hyperion/translations/el.json b/homeassistant/components/hyperion/translations/el.json index c9e64b96906b92..dab904f0160f57 100644 --- a/homeassistant/components/hyperion/translations/el.json +++ b/homeassistant/components/hyperion/translations/el.json @@ -1,14 +1,38 @@ { "config": { "abort": { - "auth_new_token_not_granted_error": "\u03a4\u03bf \u03c0\u03c1\u03cc\u03c3\u03c6\u03b1\u03c4\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03b7\u03bc\u03ad\u03bd\u03bf token \u03b4\u03b5\u03bd \u03b5\u03b3\u03ba\u03c1\u03af\u03b8\u03b7\u03ba\u03b5 \u03c3\u03c4\u03bf Hyperion UI" + "auth_new_token_not_granted_error": "\u03a4\u03bf \u03c0\u03c1\u03cc\u03c3\u03c6\u03b1\u03c4\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03b7\u03bc\u03ad\u03bd\u03bf token \u03b4\u03b5\u03bd \u03b5\u03b3\u03ba\u03c1\u03af\u03b8\u03b7\u03ba\u03b5 \u03c3\u03c4\u03bf Hyperion UI", + "auth_new_token_not_work_error": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03bf \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03bf\u03cd \u03c0\u03bf\u03c5 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03b8\u03b7\u03ba\u03b5 \u03c0\u03c1\u03cc\u03c3\u03c6\u03b1\u03c4\u03b1", + "auth_required_error": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b4\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd \u03b5\u03ac\u03bd \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7", + "no_id": "\u0397 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1 Hyperion Ambilight \u03b4\u03b5\u03bd \u03b1\u03bd\u03ad\u03c6\u03b5\u03c1\u03b5 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03b7\u03c2" + }, + "step": { + "auth": { + "data": { + "create_token": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03bd\u03ad\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03bf\u03cd", + "token": "\u0389 \u03c0\u03b1\u03c1\u03ad\u03c7\u03b5\u03c4\u03b5 \u03c0\u03c1\u03bf\u03cb\u03c0\u03ac\u03c1\u03c7\u03bf\u03bd \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc" + }, + "description": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2 \u03c3\u03c4\u03bf\u03bd \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae Hyperion Ambilight" + }, + "confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf Hyperion Ambilight \u03c3\u03c4\u03bf Home Assistant; \n\n **\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2:** {host}\n **\u0398\u03cd\u03c1\u03b1:** {port}\n **\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc**: {id}", + "title": "\u0395\u03c0\u03b9\u03b2\u03b5\u03b2\u03b1\u03af\u03c9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1\u03c2 Hyperion Ambilight" + }, + "create_token": { + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 **\u03a5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae** \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b6\u03b7\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03bd\u03ad\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2. \u0398\u03b1 \u03b1\u03bd\u03b1\u03ba\u03b1\u03c4\u03b5\u03c5\u03b8\u03c5\u03bd\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf Hyperion UI \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03b3\u03ba\u03c1\u03af\u03bd\u03b5\u03c4\u03b5 \u03c4\u03bf \u03b1\u03af\u03c4\u03b7\u03bc\u03b1. \u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03bf\u03c5 \u03b5\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03b5\u03af\u03bd\u03b1\u03b9 \" {auth_id} \"", + "title": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03bd\u03ad\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03bf\u03cd \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, + "create_token_external": { + "title": "\u0391\u03c0\u03bf\u03b4\u03bf\u03c7\u03ae \u03bd\u03ad\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03bf\u03cd \u03c3\u03c4\u03bf Hyperion UI" + } } }, "options": { "step": { "init": { "data": { - "effect_show_list": "\u0395\u03c6\u03ad Hyperion \u03b3\u03b9\u03b1 \u03b5\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7" + "effect_show_list": "\u0395\u03c6\u03ad Hyperion \u03b3\u03b9\u03b1 \u03b5\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7", + "priority": "\u03a0\u03c1\u03bf\u03c4\u03b5\u03c1\u03b1\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1 Hyperion \u03b3\u03b9\u03b1 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c7\u03c1\u03c9\u03bc\u03ac\u03c4\u03c9\u03bd \u03ba\u03b1\u03b9 \u03b5\u03c6\u03ad" } } } diff --git a/homeassistant/components/hyperion/translations/pt-BR.json b/homeassistant/components/hyperion/translations/pt-BR.json new file mode 100644 index 00000000000000..7406eb095f200d --- /dev/null +++ b/homeassistant/components/hyperion/translations/pt-BR.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "cannot_connect": "Falha ao conectar", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_access_token": "Token de acesso inv\u00e1lido" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "port": "Porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ialarm/translations/pt-BR.json b/homeassistant/components/ialarm/translations/pt-BR.json new file mode 100644 index 00000000000000..1e898e15ce0558 --- /dev/null +++ b/homeassistant/components/ialarm/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "port": "Porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iaqualink/translations/pt-BR.json b/homeassistant/components/iaqualink/translations/pt-BR.json index 932b4b8a72e0ae..bdf8cc5c5bdd29 100644 --- a/homeassistant/components/iaqualink/translations/pt-BR.json +++ b/homeassistant/components/iaqualink/translations/pt-BR.json @@ -1,10 +1,20 @@ { "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, "step": { "user": { "data": { + "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "description": "Por favor, digite o nome de usu\u00e1rio e senha para sua conta iAqualink.", + "title": "Conecte-se ao iAqualink" } } } diff --git a/homeassistant/components/iaqualink/translations/zh-Hant.json b/homeassistant/components/iaqualink/translations/zh-Hant.json index b6c25038bdb061..13591ed948503a 100644 --- a/homeassistant/components/iaqualink/translations/zh-Hant.json +++ b/homeassistant/components/iaqualink/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", diff --git a/homeassistant/components/icloud/translations/pt-BR.json b/homeassistant/components/icloud/translations/pt-BR.json index 7005c83bf6947b..fe7c4d91253db2 100644 --- a/homeassistant/components/icloud/translations/pt-BR.json +++ b/homeassistant/components/icloud/translations/pt-BR.json @@ -1,13 +1,21 @@ { "config": { "abort": { - "already_configured": "Conta j\u00e1 configurada" + "already_configured": "A conta j\u00e1 foi configurada", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "send_verification_code": "Falha ao enviar c\u00f3digo de verifica\u00e7\u00e3o", "validate_verification_code": "Falha ao verificar seu c\u00f3digo de verifica\u00e7\u00e3o, escolha um dispositivo confi\u00e1vel e inicie a verifica\u00e7\u00e3o novamente" }, "step": { + "reauth": { + "data": { + "password": "Senha" + }, + "title": "Reautenticar Integra\u00e7\u00e3o" + }, "trusted_device": { "data": { "trusted_device": "Dispositivo confi\u00e1vel" diff --git a/homeassistant/components/ifttt/translations/pt-BR.json b/homeassistant/components/ifttt/translations/pt-BR.json index c78da4db0c01ef..32dec17701cbf5 100644 --- a/homeassistant/components/ifttt/translations/pt-BR.json +++ b/homeassistant/components/ifttt/translations/pt-BR.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "cloud_not_connected": "N\u00e3o conectado ao Home Assistant Cloud.", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, "create_entry": { "default": "Para enviar eventos para o Home Assistant, voc\u00ea precisar\u00e1 usar a a\u00e7\u00e3o \"Fazer uma solicita\u00e7\u00e3o Web\" no [applet IFTTT Webhook] ( {applet_url} ). \n\n Preencha as seguintes informa\u00e7\u00f5es: \n\n - URL: ` {webhook_url} ` \n - M\u00e9todo: POST \n - Tipo de Conte\u00fado: application / json \n\n Veja [a documenta\u00e7\u00e3o] ( {docs_url} ) sobre como configurar automa\u00e7\u00f5es para manipular dados de entrada." }, diff --git a/homeassistant/components/ifttt/translations/zh-Hant.json b/homeassistant/components/ifttt/translations/zh-Hant.json index 3f149896b64196..e6c5392a9c6a43 100644 --- a/homeassistant/components/ifttt/translations/zh-Hant.json +++ b/homeassistant/components/ifttt/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "cloud_not_connected": "\u672a\u9023\u7dda\u81f3 Home Assistant \u96f2\u670d\u52d9\u3002", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "webhook_not_internet_accessible": "Home Assistant \u5be6\u9ad4\u5fc5\u9808\u8981\u80fd\u5f9e\u7db2\u969b\u7db2\u8def\u5b58\u53d6\u65b9\u80fd\u63a5\u6536 Webhook \u8a0a\u606f\u3002" }, "create_entry": { diff --git a/homeassistant/components/insteon/translations/pt-BR.json b/homeassistant/components/insteon/translations/pt-BR.json index f888c15874bfcf..c2b366b4d0f656 100644 --- a/homeassistant/components/insteon/translations/pt-BR.json +++ b/homeassistant/components/insteon/translations/pt-BR.json @@ -1,7 +1,37 @@ { + "config": { + "abort": { + "cannot_connect": "Falha ao conectar", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "hubv1": { + "data": { + "host": "Endere\u00e7o IP", + "port": "Porta" + } + }, + "hubv2": { + "data": { + "host": "Endere\u00e7o IP", + "password": "Senha", + "port": "Porta", + "username": "Usu\u00e1rio" + } + }, + "plm": { + "data": { + "device": "Caminho do Dispositivo USB" + } + } + } + }, "options": { "error": { - "cannot_connect": "Falha na conex\u00e3o com o modem Insteon, por favor tente novamente.", + "cannot_connect": "Falha ao conectar", "input_error": "Entradas inv\u00e1lidas, por favor, verifique seus valores.", "select_single": "Selecione uma op\u00e7\u00e3o." }, @@ -23,9 +53,10 @@ }, "change_hub_config": { "data": { - "password": "Nova Senha", - "port": "Novo n\u00famero da porta", - "username": "Novo usu\u00e1rio" + "host": "Endere\u00e7o IP", + "password": "Senha", + "port": "Porta", + "username": "Usu\u00e1rio" } }, "init": { diff --git a/homeassistant/components/insteon/translations/zh-Hant.json b/homeassistant/components/insteon/translations/zh-Hant.json index dd69e0ec7c4d2f..cf090f974f75b6 100644 --- a/homeassistant/components/insteon/translations/zh-Hant.json +++ b/homeassistant/components/insteon/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "cannot_connect": "\u9023\u7dda\u5931\u6557", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", diff --git a/homeassistant/components/intellifire/translations/pt-BR.json b/homeassistant/components/intellifire/translations/pt-BR.json new file mode 100644 index 00000000000000..ff6ede166a9dd6 --- /dev/null +++ b/homeassistant/components/intellifire/translations/pt-BR.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ios/translations/pt-BR.json b/homeassistant/components/ios/translations/pt-BR.json index fffbfae22494a4..369064ba6cb5e1 100644 --- a/homeassistant/components/ios/translations/pt-BR.json +++ b/homeassistant/components/ios/translations/pt-BR.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "Apenas uma configura\u00e7\u00e3o do Home Assistant iOS \u00e9 necess\u00e1ria." + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "step": { "confirm": { - "description": "Deseja configurar o componente iOS do Home Assistant?" + "description": "Deseja iniciar a configura\u00e7\u00e3o?" } } } diff --git a/homeassistant/components/ios/translations/zh-Hant.json b/homeassistant/components/ios/translations/zh-Hant.json index aceb4ea78d5281..649ab1f56e608d 100644 --- a/homeassistant/components/ios/translations/zh-Hant.json +++ b/homeassistant/components/ios/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "step": { "confirm": { diff --git a/homeassistant/components/iotawatt/translations/pt-BR.json b/homeassistant/components/iotawatt/translations/pt-BR.json new file mode 100644 index 00000000000000..79d60b8a2f78b7 --- /dev/null +++ b/homeassistant/components/iotawatt/translations/pt-BR.json @@ -0,0 +1,22 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "auth": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + }, + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ipp/translations/pt-BR.json b/homeassistant/components/ipp/translations/pt-BR.json index 704cc017a9b53a..fd3619849c2256 100644 --- a/homeassistant/components/ipp/translations/pt-BR.json +++ b/homeassistant/components/ipp/translations/pt-BR.json @@ -1,12 +1,15 @@ { "config": { "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar", "connection_upgrade": "Falha ao conectar \u00e0 impressora devido \u00e0 atualiza\u00e7\u00e3o da conex\u00e3o ser necess\u00e1ria.", "ipp_error": "Erro IPP encontrado.", "ipp_version_error": "Vers\u00e3o IPP n\u00e3o suportada pela impressora.", "unique_id_required": "Dispositivo faltando identifica\u00e7\u00e3o \u00fanica necess\u00e1ria para a descoberta." }, "error": { + "cannot_connect": "Falha ao conectar", "connection_upgrade": "Falha ao conectar \u00e0 impressora. Por favor, tente novamente com a op\u00e7\u00e3o SSL/TLS marcada." }, "flow_title": "Impressora: {name}", @@ -14,9 +17,10 @@ "user": { "data": { "base_path": "Caminho relativo para a impressora", + "host": "Nome do host", "port": "Porta", - "ssl": "A impressora suporta comunica\u00e7\u00e3o via SSL/TLS", - "verify_ssl": "A impressora usa um certificado SSL adequado" + "ssl": "Usar um certificado SSL", + "verify_ssl": "Verifique o certificado SSL" }, "description": "Configure sua impressora via IPP (Internet Printing Protocol) para integrar-se ao Home Assistant.", "title": "Vincule sua impressora" diff --git a/homeassistant/components/iqvia/translations/pt-BR.json b/homeassistant/components/iqvia/translations/pt-BR.json index d8ceb8fe934492..a366280ec353e7 100644 --- a/homeassistant/components/iqvia/translations/pt-BR.json +++ b/homeassistant/components/iqvia/translations/pt-BR.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" + }, "error": { "invalid_zip_code": "C\u00f3digo postal inv\u00e1lido" }, diff --git a/homeassistant/components/islamic_prayer_times/translations/pt-BR.json b/homeassistant/components/islamic_prayer_times/translations/pt-BR.json new file mode 100644 index 00000000000000..9ab59f40649f29 --- /dev/null +++ b/homeassistant/components/islamic_prayer_times/translations/pt-BR.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/islamic_prayer_times/translations/zh-Hant.json b/homeassistant/components/islamic_prayer_times/translations/zh-Hant.json index ea7a2c4f9b2d2b..a77fa8136bb5fa 100644 --- a/homeassistant/components/islamic_prayer_times/translations/zh-Hant.json +++ b/homeassistant/components/islamic_prayer_times/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "step": { "user": { diff --git a/homeassistant/components/iss/translations/ca.json b/homeassistant/components/iss/translations/ca.json new file mode 100644 index 00000000000000..218bebc5a98fe2 --- /dev/null +++ b/homeassistant/components/iss/translations/ca.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "latitude_longitude_not_defined": "La latitud i longitud no estan definits a Home Assistant.", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." + }, + "step": { + "user": { + "data": { + "show_on_map": "Mostrar al mapa?" + }, + "description": "Vols configurar Estaci\u00f3 Espacial Internacional?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iss/translations/de.json b/homeassistant/components/iss/translations/de.json new file mode 100644 index 00000000000000..7e1a9be8e795a0 --- /dev/null +++ b/homeassistant/components/iss/translations/de.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "latitude_longitude_not_defined": "Breiten- und L\u00e4ngengrad sind im Home Assistant nicht definiert.", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "step": { + "user": { + "data": { + "show_on_map": "Auf der Karte anzeigen?" + }, + "description": "Willst du die Internationale Raumstation konfigurieren?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iss/translations/el.json b/homeassistant/components/iss/translations/el.json new file mode 100644 index 00000000000000..4049e41ea24082 --- /dev/null +++ b/homeassistant/components/iss/translations/el.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "latitude_longitude_not_defined": "\u03a4\u03bf \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2 \u03ba\u03b1\u03b9 \u03c4\u03bf \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2 \u03b4\u03b5\u03bd \u03bf\u03c1\u03af\u03b6\u03bf\u03bd\u03c4\u03b1\u03b9 \u03c3\u03c4\u03bf Home Assistant." + }, + "step": { + "user": { + "data": { + "show_on_map": "\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03c3\u03c4\u03bf \u03c7\u03ac\u03c1\u03c4\u03b7;" + }, + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u0394\u03b9\u03b5\u03b8\u03bd\u03ae \u0394\u03b9\u03b1\u03c3\u03c4\u03b7\u03bc\u03b9\u03ba\u03cc \u03a3\u03c4\u03b1\u03b8\u03bc\u03cc;" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iss/translations/et.json b/homeassistant/components/iss/translations/et.json new file mode 100644 index 00000000000000..09104143492702 --- /dev/null +++ b/homeassistant/components/iss/translations/et.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "latitude_longitude_not_defined": "Laius- ja pikkuskraad pole Home Assistandis m\u00e4\u00e4ratud.", + "single_instance_allowed": "Juba seadistatud. Lubatud on ainult \u00fcks sidumine." + }, + "step": { + "user": { + "data": { + "show_on_map": "Kas n\u00e4idata kaardil?" + }, + "description": "Kas soovid seadistada rahvusvahelist kosmosejaama?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iss/translations/hu.json b/homeassistant/components/iss/translations/hu.json new file mode 100644 index 00000000000000..23841f8325966b --- /dev/null +++ b/homeassistant/components/iss/translations/hu.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "latitude_longitude_not_defined": "A f\u00f6ldrajzi sz\u00e9less\u00e9g \u00e9s hossz\u00fas\u00e1g nincs megadva Home Assistantban.", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "step": { + "user": { + "data": { + "show_on_map": "Megjelenjen a t\u00e9rk\u00e9pen?" + }, + "description": "Szeretn\u00e9 konfigur\u00e1lni a Nemzetk\u00f6zi \u0170r\u00e1llom\u00e1st?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iss/translations/pt-BR.json b/homeassistant/components/iss/translations/pt-BR.json new file mode 100644 index 00000000000000..34a9644e9d0efe --- /dev/null +++ b/homeassistant/components/iss/translations/pt-BR.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "latitude_longitude_not_defined": "Latitude e longitude est\u00e3o definidos em Home Assistant.", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "step": { + "user": { + "data": { + "show_on_map": "Mostrar no mapa?" + }, + "description": "Deseja configurar a Esta\u00e7\u00e3o Espacial Internacional?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iss/translations/ru.json b/homeassistant/components/iss/translations/ru.json new file mode 100644 index 00000000000000..ffd5861f9cfa75 --- /dev/null +++ b/homeassistant/components/iss/translations/ru.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "latitude_longitude_not_defined": "\u041a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u043d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u044b \u0432 Home Assistant.", + "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." + }, + "step": { + "user": { + "data": { + "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043d\u0430 \u043a\u0430\u0440\u0442\u0435" + }, + "description": "\u041d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 Internation Space Station?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iss/translations/tr.json b/homeassistant/components/iss/translations/tr.json new file mode 100644 index 00000000000000..07f374b8a17c45 --- /dev/null +++ b/homeassistant/components/iss/translations/tr.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "latitude_longitude_not_defined": "Enlem ve boylam Home Assistant'ta tan\u0131ml\u0131 de\u011fil.", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "user": { + "data": { + "show_on_map": "Haritada g\u00f6sterilsin mi?" + }, + "description": "Uluslararas\u0131 Uzay \u0130stasyonunu yap\u0131land\u0131rmak istiyor musunuz?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iss/translations/zh-Hant.json b/homeassistant/components/iss/translations/zh-Hant.json new file mode 100644 index 00000000000000..e59aa3a3be587a --- /dev/null +++ b/homeassistant/components/iss/translations/zh-Hant.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "latitude_longitude_not_defined": "\u5c1a\u672a\u65bc Home Assistant \u8a2d\u5b9a\u7d93\u7def\u5ea6\u3002", + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + }, + "step": { + "user": { + "data": { + "show_on_map": "\u65bc\u5730\u5716\u986f\u793a\uff1f" + }, + "description": "\u662f\u5426\u8981\u8a2d\u5b9a\u570b\u969b\u592a\u7a7a\u7ad9\uff1f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/isy994/translations/pt-BR.json b/homeassistant/components/isy994/translations/pt-BR.json index 2bc8cd2ef5aa2c..d377b19f5862d6 100644 --- a/homeassistant/components/isy994/translations/pt-BR.json +++ b/homeassistant/components/isy994/translations/pt-BR.json @@ -1,15 +1,22 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "invalid_host": "A entrada do host n\u00e3o est\u00e1 no formato de URL completo, por exemplo, http://192.168.10.100:80", - "unknown": "Erro inesperado." + "unknown": "Erro inesperado" }, "flow_title": "Dispositivos universais ISY994 {name} ({host})", "step": { "user": { "data": { "host": "URL", - "tls": "A vers\u00e3o TLS do controlador ISY." + "password": "Senha", + "tls": "A vers\u00e3o TLS do controlador ISY.", + "username": "Usu\u00e1rio" }, "description": "A entrada do endere\u00e7o deve estar no formato de URL completo, por exemplo, http://192.168.10.100:80", "title": "Conecte-se ao seu ISY994" diff --git a/homeassistant/components/izone/translations/pt-BR.json b/homeassistant/components/izone/translations/pt-BR.json new file mode 100644 index 00000000000000..ae7a7293429109 --- /dev/null +++ b/homeassistant/components/izone/translations/pt-BR.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "step": { + "confirm": { + "description": "Deseja configurar o iZone?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/izone/translations/zh-Hant.json b/homeassistant/components/izone/translations/zh-Hant.json index 363e62a1b5ff90..7a35966f6853ef 100644 --- a/homeassistant/components/izone/translations/zh-Hant.json +++ b/homeassistant/components/izone/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "step": { "confirm": { diff --git a/homeassistant/components/jellyfin/translations/pt-BR.json b/homeassistant/components/jellyfin/translations/pt-BR.json new file mode 100644 index 00000000000000..2fda26fe566312 --- /dev/null +++ b/homeassistant/components/jellyfin/translations/pt-BR.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "url": "URL", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/jellyfin/translations/zh-Hant.json b/homeassistant/components/jellyfin/translations/zh-Hant.json index 3f24589c23521e..886d6e3676eabd 100644 --- a/homeassistant/components/jellyfin/translations/zh-Hant.json +++ b/homeassistant/components/jellyfin/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", diff --git a/homeassistant/components/juicenet/translations/pt-BR.json b/homeassistant/components/juicenet/translations/pt-BR.json index 281a9dc89312e1..806cea1df9b232 100644 --- a/homeassistant/components/juicenet/translations/pt-BR.json +++ b/homeassistant/components/juicenet/translations/pt-BR.json @@ -1,9 +1,19 @@ { "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada" + }, "error": { - "cannot_connect": "Falha ao conectar, tente novamente", + "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "api_token": "Token da API" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/keenetic_ndms2/translations/pt-BR.json b/homeassistant/components/keenetic_ndms2/translations/pt-BR.json new file mode 100644 index 00000000000000..937edfdd914dfa --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "password": "Senha", + "port": "Porta", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/pt-BR.json b/homeassistant/components/kmtronic/translations/pt-BR.json new file mode 100644 index 00000000000000..93beddb92a8517 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/pt-BR.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/knx/translations/el.json b/homeassistant/components/knx/translations/el.json index 66d47c2a376d4e..28e9033bc11e68 100644 --- a/homeassistant/components/knx/translations/el.json +++ b/homeassistant/components/knx/translations/el.json @@ -38,12 +38,16 @@ "data": { "connection_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 KNX", "individual_address": "\u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03b1\u03c4\u03bf\u03bc\u03b9\u03ba\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7", - "multicast_group": "\u039f\u03bc\u03ac\u03b4\u03b1 \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ae\u03c2 \u03b4\u03b9\u03b1\u03bd\u03bf\u03bc\u03ae\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03cc\u03b3\u03b7\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7" + "multicast_group": "\u039f\u03bc\u03ac\u03b4\u03b1 \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ae\u03c2 \u03b4\u03b9\u03b1\u03bd\u03bf\u03bc\u03ae\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03cc\u03b3\u03b7\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7", + "multicast_port": "\u0398\u03cd\u03c1\u03b1 \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ae\u03c2 \u03b4\u03b9\u03b1\u03bd\u03bf\u03bc\u03ae\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03cc\u03b3\u03b7\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7", + "rate_limit": "\u039c\u03ad\u03b3\u03b9\u03c3\u03c4\u03b1 \u03b5\u03be\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03b1 \u03c4\u03b7\u03bb\u03b5\u03b3\u03c1\u03b1\u03c6\u03ae\u03bc\u03b1\u03c4\u03b1 \u03b1\u03bd\u03ac \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03bf", + "state_updater": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03ba\u03b1\u03b8\u03bf\u03bb\u03b9\u03ba\u03ac \u03c4\u03b9\u03c2 \u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ac\u03c3\u03b5\u03b9\u03c2 \u03b1\u03bd\u03ac\u03b3\u03bd\u03c9\u03c3\u03b7\u03c2 \u03b1\u03c0\u03cc \u03c4\u03bf KNX Bus" } }, "tunnel": { "data": { "local_ip": "\u03a4\u03bf\u03c0\u03b9\u03ba\u03ae IP (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03b5\u03bd\u03ae \u03b1\u03bd \u03b4\u03b5\u03bd \u03b5\u03af\u03c3\u03c4\u03b5 \u03c3\u03af\u03b3\u03bf\u03c5\u03c1\u03bf\u03b9)", + "route_back": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 Route Back / NAT", "tunneling_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03ae\u03c1\u03b1\u03b3\u03b3\u03b1\u03c2 KNX" } } diff --git a/homeassistant/components/knx/translations/pt-BR.json b/homeassistant/components/knx/translations/pt-BR.json new file mode 100644 index 00000000000000..a5343784fabe85 --- /dev/null +++ b/homeassistant/components/knx/translations/pt-BR.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "manual_tunnel": { + "data": { + "host": "Nome do host", + "individual_address": "Endere\u00e7o individual para a conex\u00e3o", + "local_ip": "IP local do Home Assistant (deixe em branco para detec\u00e7\u00e3o autom\u00e1tica)", + "port": "Porta", + "route_back": "Modo Rota de Retorno / NAT", + "tunneling_type": "Tipo de t\u00fanel KNX" + } + } + } + }, + "options": { + "step": { + "tunnel": { + "data": { + "host": "Nome do host", + "local_ip": "IP local (deixe em branco se n\u00e3o tiver certeza)", + "port": "Porta", + "tunneling_type": "Tipo de t\u00fanel KNX" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/knx/translations/zh-Hant.json b/homeassistant/components/knx/translations/zh-Hant.json index 39794940fef9d4..27b167c6551a54 100644 --- a/homeassistant/components/knx/translations/zh-Hant.json +++ b/homeassistant/components/knx/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557" diff --git a/homeassistant/components/kodi/translations/pt-BR.json b/homeassistant/components/kodi/translations/pt-BR.json new file mode 100644 index 00000000000000..321f7f6ffef6e4 --- /dev/null +++ b/homeassistant/components/kodi/translations/pt-BR.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "credentials": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + }, + "user": { + "data": { + "host": "Nome do host", + "port": "Porta", + "ssl": "Usar um certificado SSL" + } + }, + "ws_port": { + "data": { + "ws_port": "Porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/konnected/translations/pt-BR.json b/homeassistant/components/konnected/translations/pt-BR.json index b31bd6feb8ae57..24ba6ade2c0c4d 100644 --- a/homeassistant/components/konnected/translations/pt-BR.json +++ b/homeassistant/components/konnected/translations/pt-BR.json @@ -1,7 +1,20 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, "step": { "user": { + "data": { + "host": "Endere\u00e7o IP", + "port": "Porta" + }, "description": "Por favor, digite as informa\u00e7\u00f5es do host para o seu Painel Konnected." } } @@ -59,6 +72,11 @@ "api_host": "Substituir URL do host da API (opcional)", "override_api_host": "Substituir o URL padr\u00e3o do painel do host da API do Home Assistant" } + }, + "options_switch": { + "data": { + "name": "Nome (opcional)" + } } } } diff --git a/homeassistant/components/kostal_plenticore/translations/pt-BR.json b/homeassistant/components/kostal_plenticore/translations/pt-BR.json new file mode 100644 index 00000000000000..b829ba6e92b458 --- /dev/null +++ b/homeassistant/components/kostal_plenticore/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "password": "Senha" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kraken/translations/pt-BR.json b/homeassistant/components/kraken/translations/pt-BR.json new file mode 100644 index 00000000000000..9ce2cf2399e04a --- /dev/null +++ b/homeassistant/components/kraken/translations/pt-BR.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "already_configured": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "step": { + "user": { + "description": "Deseja iniciar a configura\u00e7\u00e3o?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kraken/translations/zh-Hant.json b/homeassistant/components/kraken/translations/zh-Hant.json index 8d64c0265793a7..53b8a1fa236776 100644 --- a/homeassistant/components/kraken/translations/zh-Hant.json +++ b/homeassistant/components/kraken/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "already_configured": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "step": { "user": { diff --git a/homeassistant/components/kulersky/translations/pt-BR.json b/homeassistant/components/kulersky/translations/pt-BR.json new file mode 100644 index 00000000000000..d5efbb90261324 --- /dev/null +++ b/homeassistant/components/kulersky/translations/pt-BR.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "step": { + "confirm": { + "description": "Deseja iniciar a configura\u00e7\u00e3o?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kulersky/translations/zh-Hant.json b/homeassistant/components/kulersky/translations/zh-Hant.json index 90c98e491dfea4..cfd20d603cba17 100644 --- a/homeassistant/components/kulersky/translations/zh-Hant.json +++ b/homeassistant/components/kulersky/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "step": { "confirm": { diff --git a/homeassistant/components/launch_library/translations/pt-BR.json b/homeassistant/components/launch_library/translations/pt-BR.json new file mode 100644 index 00000000000000..553d0dd761d1b4 --- /dev/null +++ b/homeassistant/components/launch_library/translations/pt-BR.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "step": { + "user": { + "description": "Deseja configurar a Biblioteca de Lan\u00e7amento?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/launch_library/translations/zh-Hant.json b/homeassistant/components/launch_library/translations/zh-Hant.json index b7fb63e939a5ba..23bf571cc4b5de 100644 --- a/homeassistant/components/launch_library/translations/zh-Hant.json +++ b/homeassistant/components/launch_library/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "step": { "user": { diff --git a/homeassistant/components/life360/translations/pt-BR.json b/homeassistant/components/life360/translations/pt-BR.json index 5894376a065e4f..7753c0f84dcc9e 100644 --- a/homeassistant/components/life360/translations/pt-BR.json +++ b/homeassistant/components/life360/translations/pt-BR.json @@ -1,10 +1,17 @@ { "config": { + "abort": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, "create_entry": { "default": "Para definir op\u00e7\u00f5es avan\u00e7adas, consulte [Documenta\u00e7\u00e3o da Life360] ({docs_url})." }, "error": { - "invalid_username": "Nome de usu\u00e1rio Inv\u00e1lido" + "already_configured": "A conta j\u00e1 foi configurada", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "invalid_username": "Nome de usu\u00e1rio Inv\u00e1lido", + "unknown": "Erro inesperado" }, "step": { "user": { diff --git a/homeassistant/components/lifx/translations/pt-BR.json b/homeassistant/components/lifx/translations/pt-BR.json index cf374894623fe2..83a7518386b64f 100644 --- a/homeassistant/components/lifx/translations/pt-BR.json +++ b/homeassistant/components/lifx/translations/pt-BR.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Nenhum dispositivo LIFX encontrado na rede.", - "single_instance_allowed": "Apenas uma configura\u00e7\u00e3o do LIFX \u00e9 poss\u00edvel." + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "step": { "confirm": { diff --git a/homeassistant/components/lifx/translations/zh-Hant.json b/homeassistant/components/lifx/translations/zh-Hant.json index 154e82ec3011e1..911eaa570d1c64 100644 --- a/homeassistant/components/lifx/translations/zh-Hant.json +++ b/homeassistant/components/lifx/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "step": { "confirm": { diff --git a/homeassistant/components/light/translations/pt-BR.json b/homeassistant/components/light/translations/pt-BR.json index 27b9b46297a22d..919fdb89afb698 100644 --- a/homeassistant/components/light/translations/pt-BR.json +++ b/homeassistant/components/light/translations/pt-BR.json @@ -10,6 +10,8 @@ "is_on": "{entity_name} est\u00e1 ligado" }, "trigger_type": { + "changed_states": "{entity_name} ligado ou desligado", + "toggled": "{entity_name} ligado ou desligado", "turned_off": "{entity_name} desligado", "turned_on": "{entity_name} ligado" } diff --git a/homeassistant/components/litejet/translations/pt-BR.json b/homeassistant/components/litejet/translations/pt-BR.json new file mode 100644 index 00000000000000..fdc79cd04e9cf7 --- /dev/null +++ b/homeassistant/components/litejet/translations/pt-BR.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "step": { + "user": { + "data": { + "port": "Porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/zh-Hant.json b/homeassistant/components/litejet/translations/zh-Hant.json index 3e6886e74a32f7..dc7747a3ddec55 100644 --- a/homeassistant/components/litejet/translations/zh-Hant.json +++ b/homeassistant/components/litejet/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "open_failed": "\u7121\u6cd5\u958b\u555f\u6307\u5b9a\u7684\u5e8f\u5217\u57e0" diff --git a/homeassistant/components/litterrobot/translations/pt-BR.json b/homeassistant/components/litterrobot/translations/pt-BR.json new file mode 100644 index 00000000000000..d86aef5d51d73a --- /dev/null +++ b/homeassistant/components/litterrobot/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/local_ip/translations/pt-BR.json b/homeassistant/components/local_ip/translations/pt-BR.json index 179e720abcaff9..14c377783f4b83 100644 --- a/homeassistant/components/local_ip/translations/pt-BR.json +++ b/homeassistant/components/local_ip/translations/pt-BR.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "Somente uma \u00fanica configura\u00e7\u00e3o do IP local \u00e9 permitida." + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "step": { "user": { + "description": "Deseja iniciar a configura\u00e7\u00e3o?", "title": "Endere\u00e7o IP local" } } diff --git a/homeassistant/components/local_ip/translations/zh-Hant.json b/homeassistant/components/local_ip/translations/zh-Hant.json index d7498843b7537c..d88dbf235d8ea0 100644 --- a/homeassistant/components/local_ip/translations/zh-Hant.json +++ b/homeassistant/components/local_ip/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "step": { "user": { diff --git a/homeassistant/components/locative/translations/pt-BR.json b/homeassistant/components/locative/translations/pt-BR.json index 20bcaaad643127..400750b8fecfc1 100644 --- a/homeassistant/components/locative/translations/pt-BR.json +++ b/homeassistant/components/locative/translations/pt-BR.json @@ -1,11 +1,15 @@ { "config": { + "abort": { + "cloud_not_connected": "N\u00e3o conectado ao Home Assistant Cloud.", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, "create_entry": { "default": "Para enviar locais para o Home Assistant, voc\u00ea precisar\u00e1 configurar o recurso webhook no aplicativo Locative. \n\n Preencha as seguintes informa\u00e7\u00f5es: \n\n - URL: ` {webhook_url} ` \n - M\u00e9todo: POST \n\n Veja [a documenta\u00e7\u00e3o] ( {docs_url} ) para mais detalhes." }, "step": { "user": { - "description": "Tem certeza de que deseja configurar o Locative Webhook?", + "description": "Deseja iniciar a configura\u00e7\u00e3o?", "title": "Configurar o Locative Webhook" } } diff --git a/homeassistant/components/locative/translations/zh-Hant.json b/homeassistant/components/locative/translations/zh-Hant.json index 34c5fbdbaad1ea..b3f18defca05d8 100644 --- a/homeassistant/components/locative/translations/zh-Hant.json +++ b/homeassistant/components/locative/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "cloud_not_connected": "\u672a\u9023\u7dda\u81f3 Home Assistant \u96f2\u670d\u52d9\u3002", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "webhook_not_internet_accessible": "Home Assistant \u5be6\u9ad4\u5fc5\u9808\u8981\u80fd\u5f9e\u7db2\u969b\u7db2\u8def\u5b58\u53d6\u65b9\u80fd\u63a5\u6536 Webhook \u8a0a\u606f\u3002" }, "create_entry": { diff --git a/homeassistant/components/logi_circle/translations/pt-BR.json b/homeassistant/components/logi_circle/translations/pt-BR.json index a319cf0e67ee36..10b985c11c6f47 100644 --- a/homeassistant/components/logi_circle/translations/pt-BR.json +++ b/homeassistant/components/logi_circle/translations/pt-BR.json @@ -1,11 +1,15 @@ { "config": { "abort": { + "already_configured": "A conta j\u00e1 foi configurada", "external_error": "Exce\u00e7\u00e3o ocorreu a partir de outro fluxo.", - "external_setup": "Logi Circle configurado com sucesso a partir de outro fluxo." + "external_setup": "Logi Circle configurado com sucesso a partir de outro fluxo.", + "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o." }, "error": { - "follow_link": "Por favor, siga o link e autentique antes de pressionar Enviar." + "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", + "follow_link": "Por favor, siga o link e autentique antes de pressionar Enviar.", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, "step": { "auth": { diff --git a/homeassistant/components/lookin/translations/pt-BR.json b/homeassistant/components/lookin/translations/pt-BR.json new file mode 100644 index 00000000000000..c6fccf44ba23d8 --- /dev/null +++ b/homeassistant/components/lookin/translations/pt-BR.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "cannot_connect": "Falha ao conectar", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "unknown": "Erro inesperado" + }, + "flow_title": "{name} ({host})", + "step": { + "device_name": { + "data": { + "name": "Nome" + } + }, + "discovery_confirm": { + "description": "Deseja configurar {name} ({host})?" + }, + "user": { + "data": { + "ip_address": "Endere\u00e7o IP" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/luftdaten/translations/pt-BR.json b/homeassistant/components/luftdaten/translations/pt-BR.json index 3884170c2e0146..82b1f09735b2f3 100644 --- a/homeassistant/components/luftdaten/translations/pt-BR.json +++ b/homeassistant/components/luftdaten/translations/pt-BR.json @@ -1,6 +1,8 @@ { "config": { "error": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar", "invalid_sensor": "Sensor n\u00e3o dispon\u00edvel ou inv\u00e1lido" }, "step": { diff --git a/homeassistant/components/lutron_caseta/translations/pt-BR.json b/homeassistant/components/lutron_caseta/translations/pt-BR.json index 091f7990989fbd..e3451a9a058e46 100644 --- a/homeassistant/components/lutron_caseta/translations/pt-BR.json +++ b/homeassistant/components/lutron_caseta/translations/pt-BR.json @@ -1,16 +1,21 @@ { "config": { "abort": { - "already_configured": "Ponte Cas\u00e9ta j\u00e1 configurada.", - "cannot_connect": "Instala\u00e7\u00e3o cancelada da ponte Cas\u00e9ta devido \u00e0 falha na conex\u00e3o." + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar" }, "error": { - "cannot_connect": "Falha ao conectar \u00e0 ponte Cas\u00e9ta; verifique sua configura\u00e7\u00e3o de endere\u00e7o e certificado." + "cannot_connect": "Falha ao conectar" }, "step": { "import_failed": { "description": "N\u00e3o foi poss\u00edvel configurar a ponte (host: {host}) importada do configuration.yaml.", "title": "Falha ao importar a configura\u00e7\u00e3o da ponte Cas\u00e9ta." + }, + "user": { + "data": { + "host": "Nome do host" + } } } } diff --git a/homeassistant/components/lyric/translations/pt-BR.json b/homeassistant/components/lyric/translations/pt-BR.json new file mode 100644 index 00000000000000..1e17e604c38eb1 --- /dev/null +++ b/homeassistant/components/lyric/translations/pt-BR.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", + "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "create_entry": { + "default": "Autenticado com sucesso" + }, + "step": { + "reauth_confirm": { + "title": "Reautenticar Integra\u00e7\u00e3o" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/translations/pt-BR.json b/homeassistant/components/mailgun/translations/pt-BR.json index 36e14f97645c1f..7c0caa689972fd 100644 --- a/homeassistant/components/mailgun/translations/pt-BR.json +++ b/homeassistant/components/mailgun/translations/pt-BR.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "cloud_not_connected": "N\u00e3o conectado ao Home Assistant Cloud.", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, "create_entry": { "default": "Para enviar eventos para o Home Assistant, voc\u00ea precisar\u00e1 configurar [Webhooks com Mailgun]({mailgun_url}). \n\n Preencha as seguintes informa\u00e7\u00f5es: \n\n - URL: `{webhook_url}` \n - M\u00e9todo: POST \n - Tipo de Conte\u00fado: application/json \n\n Veja [a documenta\u00e7\u00e3o] ({docs_url}) sobre como configurar automa\u00e7\u00f5es para manipular dados de entrada." }, diff --git a/homeassistant/components/mailgun/translations/zh-Hant.json b/homeassistant/components/mailgun/translations/zh-Hant.json index 5f65978596e5da..67f0a6dc98756d 100644 --- a/homeassistant/components/mailgun/translations/zh-Hant.json +++ b/homeassistant/components/mailgun/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "cloud_not_connected": "\u672a\u9023\u7dda\u81f3 Home Assistant \u96f2\u670d\u52d9\u3002", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "webhook_not_internet_accessible": "Home Assistant \u5be6\u9ad4\u5fc5\u9808\u8981\u80fd\u5f9e\u7db2\u969b\u7db2\u8def\u5b58\u53d6\u65b9\u80fd\u63a5\u6536 Webhook \u8a0a\u606f\u3002" }, "create_entry": { diff --git a/homeassistant/components/mazda/translations/pt-BR.json b/homeassistant/components/mazda/translations/pt-BR.json new file mode 100644 index 00000000000000..4c13fdf68da39a --- /dev/null +++ b/homeassistant/components/mazda/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Senha" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/media_player/translations/pt-BR.json b/homeassistant/components/media_player/translations/pt-BR.json index f980d5d200431e..2efe036e309c2b 100644 --- a/homeassistant/components/media_player/translations/pt-BR.json +++ b/homeassistant/components/media_player/translations/pt-BR.json @@ -1,4 +1,9 @@ { + "device_automation": { + "trigger_type": { + "changed_states": "{entity_name} ligado ou desligado" + } + }, "state": { "_": { "idle": "Ocioso", diff --git a/homeassistant/components/melcloud/translations/pt-BR.json b/homeassistant/components/melcloud/translations/pt-BR.json new file mode 100644 index 00000000000000..eac56eb486a695 --- /dev/null +++ b/homeassistant/components/melcloud/translations/pt-BR.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Senha" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/met/translations/pt-BR.json b/homeassistant/components/met/translations/pt-BR.json index ac85a893c215db..e0520cc442b56c 100644 --- a/homeassistant/components/met/translations/pt-BR.json +++ b/homeassistant/components/met/translations/pt-BR.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/met_eireann/translations/pt-BR.json b/homeassistant/components/met_eireann/translations/pt-BR.json new file mode 100644 index 00000000000000..ee4ea6b05df82a --- /dev/null +++ b/homeassistant/components/met_eireann/translations/pt-BR.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" + }, + "step": { + "user": { + "data": { + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meteo_france/translations/pt-BR.json b/homeassistant/components/meteo_france/translations/pt-BR.json index f23bdb1379d8cc..2aab8c8f8ec80e 100644 --- a/homeassistant/components/meteo_france/translations/pt-BR.json +++ b/homeassistant/components/meteo_france/translations/pt-BR.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Cidade j\u00e1 configurada", - "unknown": "Erro desconhecido: tente novamente mais tarde" + "already_configured": "Localiza\u00e7\u00e3o j\u00e1 est\u00e1 configurada", + "unknown": "Erro inesperado" }, "step": { "user": { diff --git a/homeassistant/components/meteoclimatic/translations/pt-BR.json b/homeassistant/components/meteoclimatic/translations/pt-BR.json new file mode 100644 index 00000000000000..118cb50d8da864 --- /dev/null +++ b/homeassistant/components/meteoclimatic/translations/pt-BR.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "unknown": "Erro inesperado" + }, + "error": { + "not_found": "[%key:common::config_flow::abort::no_devices_found%]" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/metoffice/translations/pt-BR.json b/homeassistant/components/metoffice/translations/pt-BR.json new file mode 100644 index 00000000000000..29bb6935cf50fb --- /dev/null +++ b/homeassistant/components/metoffice/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "api_key": "Chave da API", + "latitude": "Latitude", + "longitude": "Longitude" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mikrotik/translations/pt-BR.json b/homeassistant/components/mikrotik/translations/pt-BR.json index 2a013ba4772357..ba24f5937fe9db 100644 --- a/homeassistant/components/mikrotik/translations/pt-BR.json +++ b/homeassistant/components/mikrotik/translations/pt-BR.json @@ -1,16 +1,20 @@ { "config": { "abort": { - "already_configured": "Mikrotik j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "error": { - "cannot_connect": "Conex\u00e3o malsucedida", + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "name_exists": "O nome j\u00e1 existe" }, "step": { "user": { "data": { + "host": "Nome do host", "name": "Nome", + "password": "Senha", + "port": "Porta", "username": "Usu\u00e1rio", "verify_ssl": "Usar SSL" }, diff --git a/homeassistant/components/mill/translations/pt-BR.json b/homeassistant/components/mill/translations/pt-BR.json new file mode 100644 index 00000000000000..8d90531191aa69 --- /dev/null +++ b/homeassistant/components/mill/translations/pt-BR.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "cloud": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + }, + "local": { + "data": { + "ip_address": "Endere\u00e7o IP" + }, + "description": "Endere\u00e7o IP local do dispositivo." + }, + "user": { + "data": { + "connection_type": "Selecione o tipo de conex\u00e3o", + "password": "Senha", + "username": "Usu\u00e1rio" + }, + "description": "Selecione o tipo de conex\u00e3o. Local requer aquecedores de 3\u00aa gera\u00e7\u00e3o" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/minecraft_server/translations/pt-BR.json b/homeassistant/components/minecraft_server/translations/pt-BR.json index 5aa2fc3609a4ae..2af6adcd47d531 100644 --- a/homeassistant/components/minecraft_server/translations/pt-BR.json +++ b/homeassistant/components/minecraft_server/translations/pt-BR.json @@ -1,7 +1,15 @@ { "config": { "abort": { - "already_configured": "O host j\u00e1 est\u00e1 configurado." + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "name": "Nome" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/modem_callerid/translations/el.json b/homeassistant/components/modem_callerid/translations/el.json index 8c54ea9fefcc5c..179004c8e437d1 100644 --- a/homeassistant/components/modem_callerid/translations/el.json +++ b/homeassistant/components/modem_callerid/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03ac\u03bb\u03bb\u03b5\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2" + }, "step": { "usb_confirm": { "description": "\u03a0\u03c1\u03cc\u03ba\u03b5\u03b9\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03ba\u03bb\u03ae\u03c3\u03b5\u03b9\u03c2 \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae\u03c2 \u03c4\u03b7\u03bb\u03b5\u03c6\u03c9\u03bd\u03af\u03b1\u03c2 \u03bc\u03b5 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c6\u03c9\u03bd\u03b7\u03c4\u03b9\u03ba\u03bf\u03cd \u03bc\u03cc\u03bd\u03c4\u03b5\u03bc CX93001. \u0391\u03c5\u03c4\u03cc \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b1\u03bd\u03b1\u03ba\u03c4\u03ae\u03c3\u03b5\u03b9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ba\u03b1\u03bb\u03bf\u03cd\u03bd\u03c4\u03bf\u03c2 \u03bc\u03b5 \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b1\u03c0\u03cc\u03c1\u03c1\u03b9\u03c8\u03b7\u03c2 \u03bc\u03b9\u03b1\u03c2 \u03b5\u03b9\u03c3\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03b7\u03c2 \u03ba\u03bb\u03ae\u03c3\u03b7\u03c2.", diff --git a/homeassistant/components/modem_callerid/translations/pt-BR.json b/homeassistant/components/modem_callerid/translations/pt-BR.json new file mode 100644 index 00000000000000..84b9d25418a8f5 --- /dev/null +++ b/homeassistant/components/modem_callerid/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "name": "Nome", + "port": "Porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/modern_forms/translations/pt-BR.json b/homeassistant/components/modern_forms/translations/pt-BR.json new file mode 100644 index 00000000000000..4296c2d05f9437 --- /dev/null +++ b/homeassistant/components/modern_forms/translations/pt-BR.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "confirm": { + "description": "Deseja iniciar a configura\u00e7\u00e3o?" + }, + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/monoprice/translations/pt-BR.json b/homeassistant/components/monoprice/translations/pt-BR.json index 4eb010468f3926..486d16cf25a825 100644 --- a/homeassistant/components/monoprice/translations/pt-BR.json +++ b/homeassistant/components/monoprice/translations/pt-BR.json @@ -1,12 +1,16 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, "error": { - "cannot_connect": "Falha ao conectar, tente novamente", + "cannot_connect": "Falha ao conectar", "unknown": "Erro inesperado" }, "step": { "user": { "data": { + "port": "Porta", "source_1": "Nome da fonte #1", "source_2": "Nome da fonte #2", "source_3": "Nome da fonte #3", diff --git a/homeassistant/components/motion_blinds/translations/pt-BR.json b/homeassistant/components/motion_blinds/translations/pt-BR.json index 0d9e257feba90f..50b2728a93b232 100644 --- a/homeassistant/components/motion_blinds/translations/pt-BR.json +++ b/homeassistant/components/motion_blinds/translations/pt-BR.json @@ -1,17 +1,26 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "connection_error": "Falha ao conectar" }, "step": { "connect": { "data": { + "api_key": "Chave da API", "interface": "A interface de rede a ser utilizada" } }, "select": { "data": { - "select_ip": "Endere\u00e7o de IP" + "select_ip": "Endere\u00e7o IP" + } + }, + "user": { + "data": { + "api_key": "Chave da API", + "host": "Endere\u00e7o IP" } } } diff --git a/homeassistant/components/motioneye/translations/pt-BR.json b/homeassistant/components/motioneye/translations/pt-BR.json index ec20df02074339..d78113d7f9ce00 100644 --- a/homeassistant/components/motioneye/translations/pt-BR.json +++ b/homeassistant/components/motioneye/translations/pt-BR.json @@ -1,12 +1,24 @@ { "config": { "abort": { - "already_configured": "Servi\u00e7o j\u00e1 est\u00e1 configurado" + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "admin_password": "Senha Administrador", + "admin_username": "Usu\u00e1rio", + "surveillance_password": "Senha Vigil\u00e2ncia", + "surveillance_username": "Usu\u00e1rio", + "url": "URL" + } + } } }, "options": { diff --git a/homeassistant/components/mqtt/translations/pt-BR.json b/homeassistant/components/mqtt/translations/pt-BR.json index ef9fad14440547..14768a05340c1d 100644 --- a/homeassistant/components/mqtt/translations/pt-BR.json +++ b/homeassistant/components/mqtt/translations/pt-BR.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "Apenas uma configura\u00e7\u00e3o do MQTT \u00e9 permitida." + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "error": { - "cannot_connect": "N\u00e3o \u00e9 poss\u00edvel conectar-se ao Broker" + "cannot_connect": "Falha ao conectar" }, "step": { "broker": { @@ -37,5 +38,19 @@ "turn_off": "Desligar", "turn_on": "Ligar" } + }, + "options": { + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "broker": { + "data": { + "password": "Senha", + "port": "Porta", + "username": "Usu\u00e1rio" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/mqtt/translations/zh-Hant.json b/homeassistant/components/mqtt/translations/zh-Hant.json index 9b08ba9aee88c8..43d6a5f0b4e0b1 100644 --- a/homeassistant/components/mqtt/translations/zh-Hant.json +++ b/homeassistant/components/mqtt/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557" diff --git a/homeassistant/components/mullvad/translations/pt-BR.json b/homeassistant/components/mullvad/translations/pt-BR.json new file mode 100644 index 00000000000000..0c5be5614acecc --- /dev/null +++ b/homeassistant/components/mullvad/translations/pt-BR.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mutesync/translations/pt-BR.json b/homeassistant/components/mutesync/translations/pt-BR.json new file mode 100644 index 00000000000000..159cd52c341283 --- /dev/null +++ b/homeassistant/components/mutesync/translations/pt-BR.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/myq/translations/pt-BR.json b/homeassistant/components/myq/translations/pt-BR.json index 932b4b8a72e0ae..7a85aed89fbb9e 100644 --- a/homeassistant/components/myq/translations/pt-BR.json +++ b/homeassistant/components/myq/translations/pt-BR.json @@ -1,8 +1,23 @@ { "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, "step": { + "reauth_confirm": { + "data": { + "password": "Senha" + } + }, "user": { "data": { + "password": "Senha", "username": "Usu\u00e1rio" } } diff --git a/homeassistant/components/mysensors/translations/pt-BR.json b/homeassistant/components/mysensors/translations/pt-BR.json new file mode 100644 index 00000000000000..ac4274b1fa39db --- /dev/null +++ b/homeassistant/components/mysensors/translations/pt-BR.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "error": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nam/translations/el.json b/homeassistant/components/nam/translations/el.json index d3694cacab8631..06b4294bae9b3a 100644 --- a/homeassistant/components/nam/translations/el.json +++ b/homeassistant/components/nam/translations/el.json @@ -9,6 +9,9 @@ "confirm_discovery": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Nettigo Air Monitor \u03c3\u03c4\u03bf {host};" }, + "reauth_confirm": { + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03c3\u03c9\u03c3\u03c4\u03cc \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae: {host}" + }, "user": { "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 Nettigo Air Monitor." } diff --git a/homeassistant/components/nam/translations/pt-BR.json b/homeassistant/components/nam/translations/pt-BR.json new file mode 100644 index 00000000000000..7b56c616af1141 --- /dev/null +++ b/homeassistant/components/nam/translations/pt-BR.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", + "reauth_unsuccessful": "A reautentica\u00e7\u00e3o falhou. Remova a integra\u00e7\u00e3o e configure-a novamente." + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "credentials": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + }, + "description": "Por favor, digite o nome de usu\u00e1rio e senha." + }, + "reauth_confirm": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + }, + "description": "Insira o nome de usu\u00e1rio e a senha corretos para o host: {host}" + }, + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nanoleaf/translations/pt-BR.json b/homeassistant/components/nanoleaf/translations/pt-BR.json new file mode 100644 index 00000000000000..b6ce644bac9e06 --- /dev/null +++ b/homeassistant/components/nanoleaf/translations/pt-BR.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar", + "invalid_token": "Token de acesso inv\u00e1lido", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", + "unknown": "Erro inesperado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/neato/translations/pt-BR.json b/homeassistant/components/neato/translations/pt-BR.json new file mode 100644 index 00000000000000..dc4207a45f9387 --- /dev/null +++ b/homeassistant/components/neato/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", + "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", + "no_url_available": "N\u00e3o h\u00e1 URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a se\u00e7\u00e3o de ajuda]({docs_url})", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "create_entry": { + "default": "Autenticado com sucesso" + }, + "step": { + "reauth_confirm": { + "title": "Deseja iniciar a configura\u00e7\u00e3o?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nest/translations/el.json b/homeassistant/components/nest/translations/el.json index 236cd0072a939e..1d75ba96a7e479 100644 --- a/homeassistant/components/nest/translations/el.json +++ b/homeassistant/components/nest/translations/el.json @@ -9,6 +9,10 @@ }, "link": { "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd Nest" + }, + "pubsub": { + "description": "\u0395\u03c0\u03b9\u03c3\u03ba\u03b5\u03c6\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf [Cloud Console]({url}) \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b2\u03c1\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03bf\u03c5 \u03ad\u03c1\u03b3\u03bf\u03c5 \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Google Cloud.", + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Google Cloud" } } }, diff --git a/homeassistant/components/nest/translations/pt-BR.json b/homeassistant/components/nest/translations/pt-BR.json index 6d312fa98c1c97..3db8792484c8bf 100644 --- a/homeassistant/components/nest/translations/pt-BR.json +++ b/homeassistant/components/nest/translations/pt-BR.json @@ -1,14 +1,33 @@ { "config": { "abort": { - "authorize_url_timeout": "Excedido tempo limite de url de autoriza\u00e7\u00e3o" + "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", + "invalid_access_token": "Token de acesso inv\u00e1lido", + "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", + "no_url_available": "N\u00e3o h\u00e1 URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a se\u00e7\u00e3o de ajuda]({docs_url})", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "create_entry": { + "default": "Autenticado com sucesso" }, "error": { + "bad_project_id": "Insira um ID de projeto do Cloud v\u00e1lido (verifique o Console do Cloud)", "internal_error": "Erro interno ao validar o c\u00f3digo", + "invalid_pin": "C\u00f3digo PIN", + "subscriber_error": "Erro de assinante desconhecido, veja os logs", "timeout": "Excedido tempo limite para validar c\u00f3digo", - "unknown": "Erro desconhecido ao validar o c\u00f3digo" + "unknown": "Erro inesperado", + "wrong_project_id": "Insira um ID de projeto do Cloud v\u00e1lido (ID do projeto de acesso ao dispositivo encontrado)" }, "step": { + "auth": { + "data": { + "code": "Token de acesso" + }, + "description": "Para vincular sua conta do Google, [autorize sua conta]( {url} ). \n\n Ap\u00f3s a autoriza\u00e7\u00e3o, copie e cole o c\u00f3digo de token de autentica\u00e7\u00e3o fornecido abaixo.", + "title": "Vincular Conta do Google" + }, "init": { "data": { "flow_impl": "Provedor" @@ -22,6 +41,16 @@ }, "description": "Para vincular sua conta do Nest, [autorize sua conta] ( {url} ). \n\n Ap\u00f3s a autoriza\u00e7\u00e3o, copie e cole o c\u00f3digo PIN fornecido abaixo.", "title": "Link da conta Nest" + }, + "pubsub": { + "data": { + "cloud_project_id": "ID do projeto do Google Cloud" + }, + "description": "Visite o [Cloud Console]( {url} ) para encontrar o ID do projeto do Google Cloud.", + "title": "Configurar o Google Cloud" + }, + "reauth_confirm": { + "title": "Reautenticar Integra\u00e7\u00e3o" } } }, diff --git a/homeassistant/components/nest/translations/zh-Hant.json b/homeassistant/components/nest/translations/zh-Hant.json index afae41f7d7a6ce..c52a22e6970114 100644 --- a/homeassistant/components/nest/translations/zh-Hant.json +++ b/homeassistant/components/nest/translations/zh-Hant.json @@ -6,7 +6,7 @@ "missing_configuration": "\u5143\u4ef6\u5c1a\u672a\u8a2d\u7f6e\uff0c\u8acb\u53c3\u95b1\u6587\u4ef6\u8aaa\u660e\u3002", "no_url_available": "\u6c92\u6709\u53ef\u7528\u7684\u7db2\u5740\u3002\u95dc\u65bc\u6b64\u932f\u8aa4\u66f4\u8a73\u7d30\u8a0a\u606f\uff0c[\u9ede\u9078\u5354\u52a9\u7ae0\u7bc0]({docs_url})", "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "unknown_authorize_url_generation": "\u7522\u751f\u8a8d\u8b49 URL \u6642\u767c\u751f\u672a\u77e5\u932f\u8aa4\u3002" }, "create_entry": { diff --git a/homeassistant/components/netatmo/translations/pt-BR.json b/homeassistant/components/netatmo/translations/pt-BR.json index 77e55a889c4c6b..98d1882d5e04d4 100644 --- a/homeassistant/components/netatmo/translations/pt-BR.json +++ b/homeassistant/components/netatmo/translations/pt-BR.json @@ -1,8 +1,31 @@ { "config": { + "abort": { + "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", + "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", + "no_url_available": "N\u00e3o h\u00e1 URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a se\u00e7\u00e3o de ajuda]({docs_url})", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "create_entry": { + "default": "Autenticado com sucesso" + }, "step": { "reauth_confirm": { - "title": "Reautenticar integra\u00e7\u00e3o" + "description": "A integra\u00e7\u00e3o Netatmo precisa autenticar novamente sua conta", + "title": "Reautenticar Integra\u00e7\u00e3o" + } + } + }, + "options": { + "step": { + "public_weather": { + "data": { + "lat_ne": "Latitude nordeste", + "lat_sw": "Latitude sudoeste", + "lon_ne": "Longitude nordeste", + "lon_sw": "Longitude sudoeste" + } } } } diff --git a/homeassistant/components/netatmo/translations/zh-Hant.json b/homeassistant/components/netatmo/translations/zh-Hant.json index f8d181be5d3c76..84bb2dcffa3a2c 100644 --- a/homeassistant/components/netatmo/translations/zh-Hant.json +++ b/homeassistant/components/netatmo/translations/zh-Hant.json @@ -5,7 +5,7 @@ "missing_configuration": "\u5143\u4ef6\u5c1a\u672a\u8a2d\u7f6e\uff0c\u8acb\u53c3\u95b1\u6587\u4ef6\u8aaa\u660e\u3002", "no_url_available": "\u6c92\u6709\u53ef\u7528\u7684\u7db2\u5740\u3002\u95dc\u65bc\u6b64\u932f\u8aa4\u66f4\u8a73\u7d30\u8a0a\u606f\uff0c[\u9ede\u9078\u5354\u52a9\u7ae0\u7bc0]({docs_url})", "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "create_entry": { "default": "\u5df2\u6210\u529f\u8a8d\u8b49" diff --git a/homeassistant/components/netgear/translations/pt-BR.json b/homeassistant/components/netgear/translations/pt-BR.json index ec18c9a65dff75..82c149c759f929 100644 --- a/homeassistant/components/netgear/translations/pt-BR.json +++ b/homeassistant/components/netgear/translations/pt-BR.json @@ -1,15 +1,15 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "step": { "user": { "data": { - "host": "Host (Opcional)", + "host": "Nome do host (Opcional)", "password": "Senha", "port": "Porta (Opcional)", - "ssl": "Utilize um certificado SSL", + "ssl": "Usar um certificado SSL", "username": "Usu\u00e1rio (Opcional)" }, "description": "Host padr\u00e3o: {host}\n Porta padr\u00e3o: {port}\n Usu\u00e1rio padr\u00e3o: {username}", diff --git a/homeassistant/components/nexia/translations/pt-BR.json b/homeassistant/components/nexia/translations/pt-BR.json index 932b4b8a72e0ae..66c671f99a3c2e 100644 --- a/homeassistant/components/nexia/translations/pt-BR.json +++ b/homeassistant/components/nexia/translations/pt-BR.json @@ -1,8 +1,17 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, "step": { "user": { "data": { + "password": "Senha", "username": "Usu\u00e1rio" } } diff --git a/homeassistant/components/nfandroidtv/translations/pt-BR.json b/homeassistant/components/nfandroidtv/translations/pt-BR.json new file mode 100644 index 00000000000000..467eb83fea35f2 --- /dev/null +++ b/homeassistant/components/nfandroidtv/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nightscout/translations/pt-BR.json b/homeassistant/components/nightscout/translations/pt-BR.json index 68dc0756725ba6..bc2a518b65b902 100644 --- a/homeassistant/components/nightscout/translations/pt-BR.json +++ b/homeassistant/components/nightscout/translations/pt-BR.json @@ -5,11 +5,13 @@ }, "error": { "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, "step": { "user": { "data": { + "api_key": "Chave da API", "url": "URL" } } diff --git a/homeassistant/components/nina/translations/pt-BR.json b/homeassistant/components/nina/translations/pt-BR.json new file mode 100644 index 00000000000000..4116fff076d1c3 --- /dev/null +++ b/homeassistant/components/nina/translations/pt-BR.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "error": { + "cannot_connect": "Falha ao conectar", + "no_selection": "Selecione pelo menos uma cidade/condado", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "_a_to_d": "City/county (A-D)", + "_e_to_h": "City/county (E-H)", + "_i_to_l": "City/county (I-L)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nina/translations/zh-Hant.json b/homeassistant/components/nina/translations/zh-Hant.json index 6ab597dbef1afc..0ba4436722d46b 100644 --- a/homeassistant/components/nina/translations/zh-Hant.json +++ b/homeassistant/components/nina/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", diff --git a/homeassistant/components/nmap_tracker/translations/el.json b/homeassistant/components/nmap_tracker/translations/el.json index 74a0f8b1c9c104..873867c981965d 100644 --- a/homeassistant/components/nmap_tracker/translations/el.json +++ b/homeassistant/components/nmap_tracker/translations/el.json @@ -1,4 +1,17 @@ { + "config": { + "step": { + "user": { + "data": { + "exclude": "\u0394\u03b9\u03b5\u03c5\u03b8\u03cd\u03bd\u03c3\u03b5\u03b9\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 (\u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03bc\u03b5 \u03ba\u03cc\u03bc\u03bc\u03b1) \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03b1\u03c0\u03bf\u03ba\u03bb\u03b5\u03af\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7", + "home_interval": "\u0395\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03bb\u03b5\u03c0\u03c4\u03ce\u03bd \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03c4\u03c9\u03bd \u03c3\u03b1\u03c1\u03ce\u03c3\u03b5\u03c9\u03bd \u03b5\u03bd\u03b5\u03c1\u03b3\u03ce\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd (\u03b4\u03b9\u03b1\u03c4\u03ae\u03c1\u03b7\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03bc\u03c0\u03b1\u03c4\u03b1\u03c1\u03af\u03b1\u03c2)", + "hosts": "\u0394\u03b9\u03b5\u03c5\u03b8\u03cd\u03bd\u03c3\u03b5\u03b9\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 (\u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03bc\u03b5 \u03ba\u03cc\u03bc\u03bc\u03b1) \u03b3\u03b9\u03b1 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7", + "scan_options": "\u0391\u03ba\u03b1\u03c4\u03ad\u03c1\u03b3\u03b1\u03c3\u03c4\u03b5\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b9\u03bc\u03b5\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf Nmap" + }, + "description": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03ce\u03bd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf Nmap. \u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 \u03ba\u03b1\u03b9 \u03bf\u03b9 \u03b5\u03be\u03b1\u03b9\u03c1\u03ad\u03c3\u03b5\u03b9\u03c2 \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u0394\u03b9\u03b5\u03c5\u03b8\u03cd\u03bd\u03c3\u03b5\u03b9\u03c2 IP (192.168.1.1), \u0394\u03af\u03ba\u03c4\u03c5\u03b1 IP (192.168.0.0/24) \u03ae \u0395\u03cd\u03c1\u03bf\u03c2 IP (192.168.1.0-32)." + } + } + }, "options": { "step": { "init": { @@ -7,5 +20,6 @@ } } } - } + }, + "title": "\u0399\u03c7\u03bd\u03b7\u03bb\u03ac\u03c4\u03b7\u03c2 Nmap" } \ No newline at end of file diff --git a/homeassistant/components/nmap_tracker/translations/pt-BR.json b/homeassistant/components/nmap_tracker/translations/pt-BR.json new file mode 100644 index 00000000000000..26eae684761a39 --- /dev/null +++ b/homeassistant/components/nmap_tracker/translations/pt-BR.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Localiza\u00e7\u00e3o j\u00e1 est\u00e1 configurada" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/notion/translations/pt-BR.json b/homeassistant/components/notion/translations/pt-BR.json index 084048a625a205..d778a301ee1e82 100644 --- a/homeassistant/components/notion/translations/pt-BR.json +++ b/homeassistant/components/notion/translations/pt-BR.json @@ -1,13 +1,26 @@ { "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, "error": { - "no_devices": "Nenhum dispositivo encontrado na conta" + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "no_devices": "Nenhum dispositivo encontrado na conta", + "unknown": "Erro inesperado" }, "step": { + "reauth_confirm": { + "data": { + "password": "Senha" + }, + "description": "Por favor, digite novamente a senha para {username}.", + "title": "Reautenticar Integra\u00e7\u00e3o" + }, "user": { "data": { "password": "Senha", - "username": "Usu\u00e1rio/ende\u00e7o de e-mail" + "username": "Usu\u00e1rio" }, "title": "Preencha suas informa\u00e7\u00f5es" } diff --git a/homeassistant/components/nuheat/translations/pt-BR.json b/homeassistant/components/nuheat/translations/pt-BR.json index 7963212e49cafe..e90f8e1cfe9ceb 100644 --- a/homeassistant/components/nuheat/translations/pt-BR.json +++ b/homeassistant/components/nuheat/translations/pt-BR.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "O termostato j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "error": { - "cannot_connect": "Falha ao conectar, tente novamente", + "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "invalid_thermostat": "O n\u00famero de s\u00e9rie do termostato \u00e9 inv\u00e1lido.", "unknown": "Erro inesperado" @@ -12,6 +12,7 @@ "step": { "user": { "data": { + "password": "Senha", "serial_number": "N\u00famero de s\u00e9rie do termostato.", "username": "Usu\u00e1rio" }, diff --git a/homeassistant/components/nuki/translations/pt-BR.json b/homeassistant/components/nuki/translations/pt-BR.json new file mode 100644 index 00000000000000..045720cd332e48 --- /dev/null +++ b/homeassistant/components/nuki/translations/pt-BR.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "reauth_confirm": { + "data": { + "token": "Token de acesso" + }, + "title": "Reautenticar Integra\u00e7\u00e3o" + }, + "user": { + "data": { + "host": "Nome do host", + "port": "Porta", + "token": "Token de acesso" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/el.json b/homeassistant/components/number/translations/el.json new file mode 100644 index 00000000000000..0afb4c73e7df8f --- /dev/null +++ b/homeassistant/components/number/translations/el.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03c4\u03b9\u03bc\u03ae\u03c2 \u03b3\u03b9\u03b1 {entity_name}" + } + }, + "title": "\u0391\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2" +} \ No newline at end of file diff --git a/homeassistant/components/nut/translations/pt-BR.json b/homeassistant/components/nut/translations/pt-BR.json index 8b6b7538ba8afa..5a5ec19d2b24a1 100644 --- a/homeassistant/components/nut/translations/pt-BR.json +++ b/homeassistant/components/nut/translations/pt-BR.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "error": { - "cannot_connect": "Falha ao conectar, tente novamente", + "cannot_connect": "Falha ao conectar", "unknown": "Erro inesperado" }, "step": { @@ -20,10 +20,22 @@ "resources": "Recursos" }, "title": "Escolha o no-break (UPS) para monitorar" + }, + "user": { + "data": { + "host": "Nome do host", + "password": "Senha", + "port": "Porta", + "username": "Usu\u00e1rio" + } } } }, "options": { + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/nws/translations/pt-BR.json b/homeassistant/components/nws/translations/pt-BR.json index 3d168bcce30b60..2e74dcad77e7b3 100644 --- a/homeassistant/components/nws/translations/pt-BR.json +++ b/homeassistant/components/nws/translations/pt-BR.json @@ -1,15 +1,16 @@ { "config": { "abort": { - "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" }, "error": { - "cannot_connect": "Falha ao conectar, tente novamente", + "cannot_connect": "Falha ao conectar", "unknown": "Erro inesperado" }, "step": { "user": { "data": { + "api_key": "Chave da API", "latitude": "Latitude", "longitude": "Longitude", "station": "C\u00f3digo da esta\u00e7\u00e3o METAR" diff --git a/homeassistant/components/nzbget/translations/pt-BR.json b/homeassistant/components/nzbget/translations/pt-BR.json new file mode 100644 index 00000000000000..f7489f07d8ffb8 --- /dev/null +++ b/homeassistant/components/nzbget/translations/pt-BR.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", + "unknown": "Erro inesperado" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "name": "Nome", + "password": "Senha", + "port": "Porta", + "ssl": "Usar um certificado SSL", + "username": "Usu\u00e1rio", + "verify_ssl": "Verifique o certificado SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nzbget/translations/zh-Hant.json b/homeassistant/components/nzbget/translations/zh-Hant.json index 28edec03d67c63..6040b52b670375 100644 --- a/homeassistant/components/nzbget/translations/zh-Hant.json +++ b/homeassistant/components/nzbget/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "error": { diff --git a/homeassistant/components/octoprint/translations/pt-BR.json b/homeassistant/components/octoprint/translations/pt-BR.json new file mode 100644 index 00000000000000..f8af97f752625e --- /dev/null +++ b/homeassistant/components/octoprint/translations/pt-BR.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "auth_failed": "Falha ao recuperar a chave de API do aplicativo", + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "flow_title": "Impressora OctoPrint: {host}", + "progress": { + "get_api_key": "Abra a interface do usu\u00e1rio do OctoPrint e clique em 'Permitir' na solicita\u00e7\u00e3o de acesso para 'Assistente dom\u00e9stico'." + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "path": "Caminho do aplicativo", + "port": "N\u00famero da porta", + "ssl": "Usar SSL", + "username": "Usu\u00e1rio", + "verify_ssl": "Verifique o certificado SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/pt-BR.json b/homeassistant/components/omnilogic/translations/pt-BR.json new file mode 100644 index 00000000000000..790e3e661a3492 --- /dev/null +++ b/homeassistant/components/omnilogic/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/zh-Hant.json b/homeassistant/components/omnilogic/translations/zh-Hant.json index 89e49de710af94..0a25890fd8a26b 100644 --- a/homeassistant/components/omnilogic/translations/zh-Hant.json +++ b/homeassistant/components/omnilogic/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", diff --git a/homeassistant/components/oncue/translations/pt-BR.json b/homeassistant/components/oncue/translations/pt-BR.json new file mode 100644 index 00000000000000..d86aef5d51d73a --- /dev/null +++ b/homeassistant/components/oncue/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/pt-BR.json b/homeassistant/components/ondilo_ico/translations/pt-BR.json new file mode 100644 index 00000000000000..c64994cd0d6d0a --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/pt-BR.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", + "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o." + }, + "create_entry": { + "default": "Autenticado com sucesso" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onewire/translations/pt-BR.json b/homeassistant/components/onewire/translations/pt-BR.json new file mode 100644 index 00000000000000..cfb890a38bfee2 --- /dev/null +++ b/homeassistant/components/onewire/translations/pt-BR.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "owserver": { + "data": { + "host": "Nome do host", + "port": "Porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/pt-BR.json b/homeassistant/components/onvif/translations/pt-BR.json index 3304203c57a76e..488bf6621024cb 100644 --- a/homeassistant/components/onvif/translations/pt-BR.json +++ b/homeassistant/components/onvif/translations/pt-BR.json @@ -1,12 +1,15 @@ { "config": { "abort": { - "already_configured": "O dispositivo ONVIF j\u00e1 est\u00e1 configurado.", - "already_in_progress": "O fluxo de configura\u00e7\u00e3o para dispositivos ONVIF j\u00e1 est\u00e1 em andamento.", + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", "no_h264": "N\u00e3o h\u00e1 fluxos H264 dispon\u00edveis. Verifique a configura\u00e7\u00e3o do perfil no seu dispositivo.", "no_mac": "N\u00e3o foi poss\u00edvel configurar um ID \u00fanico para o dispositivo ONVIF.", "onvif_error": "Erro ao configurar o dispositivo ONVIF. Verifique os logs para obter mais informa\u00e7\u00f5es." }, + "error": { + "cannot_connect": "Falha ao conectar" + }, "step": { "auth": { "data": { @@ -15,6 +18,15 @@ }, "title": "Configurar autentica\u00e7\u00e3o" }, + "configure": { + "data": { + "host": "Nome do host", + "name": "Nome", + "password": "Senha", + "port": "Porta", + "username": "Usu\u00e1rio" + } + }, "configure_profile": { "data": { "include": "Criar entidade c\u00e2mera" @@ -30,7 +42,7 @@ }, "manual_input": { "data": { - "host": "Endere\u00e7o (IP)", + "host": "Nome do host", "name": "Nome", "port": "Porta" }, diff --git a/homeassistant/components/opengarage/translations/el.json b/homeassistant/components/opengarage/translations/el.json new file mode 100644 index 00000000000000..e63383e4d7cac2 --- /dev/null +++ b/homeassistant/components/opengarage/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "device_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/opengarage/translations/pt-BR.json b/homeassistant/components/opengarage/translations/pt-BR.json new file mode 100644 index 00000000000000..dbbd78229d2cac --- /dev/null +++ b/homeassistant/components/opengarage/translations/pt-BR.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "device_key": "Chave do dispositivo", + "host": "Nome do host", + "port": "Porta", + "verify_ssl": "Verifique o certificado SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/opentherm_gw/translations/pt-BR.json b/homeassistant/components/opentherm_gw/translations/pt-BR.json new file mode 100644 index 00000000000000..a677332c3e14ef --- /dev/null +++ b/homeassistant/components/opentherm_gw/translations/pt-BR.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar" + }, + "step": { + "init": { + "data": { + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openuv/translations/pt-BR.json b/homeassistant/components/openuv/translations/pt-BR.json index 01a756a2ebbb23..7be0885bde9c93 100644 --- a/homeassistant/components/openuv/translations/pt-BR.json +++ b/homeassistant/components/openuv/translations/pt-BR.json @@ -1,12 +1,15 @@ { "config": { + "abort": { + "already_configured": "Localiza\u00e7\u00e3o j\u00e1 est\u00e1 configurada" + }, "error": { "invalid_api_key": "Chave de API inv\u00e1lida" }, "step": { "user": { "data": { - "api_key": "Chave de API do OpenUV", + "api_key": "Chave da API", "elevation": "Eleva\u00e7\u00e3o", "latitude": "Latitude", "longitude": "Longitude" diff --git a/homeassistant/components/openweathermap/translations/pt-BR.json b/homeassistant/components/openweathermap/translations/pt-BR.json new file mode 100644 index 00000000000000..5e7c3559d90a71 --- /dev/null +++ b/homeassistant/components/openweathermap/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Localiza\u00e7\u00e3o j\u00e1 est\u00e1 configurada" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_api_key": "Chave de API inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "api_key": "Chave da API", + "latitude": "Latitude", + "longitude": "Longitude" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/overkiz/translations/pt-BR.json b/homeassistant/components/overkiz/translations/pt-BR.json new file mode 100644 index 00000000000000..802aef80752262 --- /dev/null +++ b/homeassistant/components/overkiz/translations/pt-BR.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", + "reauth_wrong_account": "Voc\u00ea s\u00f3 pode reautenticar esta entrada com a mesma conta e hub do Overkiz" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/overkiz/translations/select.pt-BR.json b/homeassistant/components/overkiz/translations/select.pt-BR.json new file mode 100644 index 00000000000000..623378023961bb --- /dev/null +++ b/homeassistant/components/overkiz/translations/select.pt-BR.json @@ -0,0 +1,13 @@ +{ + "state": { + "overkiz__memorized_simple_volume": { + "highest": "Alt\u00edssimo", + "standard": "Padr\u00e3o" + }, + "overkiz__open_closed_pedestrian": { + "closed": "Fechado", + "open": "Aberto", + "pedestrian": "Pedestre" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/overkiz/translations/sensor.pt-BR.json b/homeassistant/components/overkiz/translations/sensor.pt-BR.json index 902bb9167b2dd5..7ba542d0cbe568 100644 --- a/homeassistant/components/overkiz/translations/sensor.pt-BR.json +++ b/homeassistant/components/overkiz/translations/sensor.pt-BR.json @@ -1,6 +1,25 @@ { "state": { + "overkiz__battery": { + "low": "Baixo", + "normal": "Normal", + "verylow": "Muito baixo" + }, + "overkiz__discrete_rssi_level": { + "good": "Bom", + "low": "Baixo", + "normal": "Normal", + "verylow": "Muito baixo" + }, "overkiz__priority_lock_originator": { + "local_user": "Usu\u00e1rio local", + "lsc": "LSC", + "myself": "Eu mesmo", + "rain": "Chuva", + "security": "Seguran\u00e7a", + "temperature": "Temperatura", + "timer": "Temporizador", + "ups": "UPS", "user": "Usu\u00e1rio", "wind": "Vento" }, diff --git a/homeassistant/components/ovo_energy/translations/pt-BR.json b/homeassistant/components/ovo_energy/translations/pt-BR.json new file mode 100644 index 00000000000000..dfd1563d548db7 --- /dev/null +++ b/homeassistant/components/ovo_energy/translations/pt-BR.json @@ -0,0 +1,22 @@ +{ + "config": { + "error": { + "already_configured": "A conta j\u00e1 foi configurada", + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "reauth": { + "data": { + "password": "Senha" + } + }, + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/owntracks/translations/pt-BR.json b/homeassistant/components/owntracks/translations/pt-BR.json index af1c939be36506..4137bf5b9a4eb1 100644 --- a/homeassistant/components/owntracks/translations/pt-BR.json +++ b/homeassistant/components/owntracks/translations/pt-BR.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "cloud_not_connected": "N\u00e3o conectado ao Home Assistant Cloud.", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, "create_entry": { "default": "\n\n No Android, abra [o aplicativo OwnTracks] ( {android_url} ), v\u00e1 para prefer\u00eancias - > conex\u00e3o. Altere as seguintes configura\u00e7\u00f5es: \n - Modo: HTTP privado \n - Anfitri\u00e3o: {webhook_url} \n - Identifica\u00e7\u00e3o: \n - Nome de usu\u00e1rio: ` \n - ID do dispositivo: ` ` \n\n No iOS, abra o aplicativo OwnTracks ( {ios_url} ), toque no \u00edcone (i) no canto superior esquerdo - > configura\u00e7\u00f5es. Altere as seguintes configura\u00e7\u00f5es: \n - Modo: HTTP \n - URL: {webhook_url} \n - Ativar a autentica\u00e7\u00e3o \n - UserID: ` ` \n\n {secret} \n \n Veja [a documenta\u00e7\u00e3o] ( {docs_url} ) para mais informa\u00e7\u00f5es." }, diff --git a/homeassistant/components/owntracks/translations/zh-Hant.json b/homeassistant/components/owntracks/translations/zh-Hant.json index 09aae548c6209b..a9f6016c6c115f 100644 --- a/homeassistant/components/owntracks/translations/zh-Hant.json +++ b/homeassistant/components/owntracks/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "cloud_not_connected": "\u672a\u9023\u7dda\u81f3 Home Assistant \u96f2\u670d\u52d9\u3002", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "create_entry": { "default": "\n\n\u65bc Android \u8a2d\u5099\uff0c\u6253\u958b [OwnTracks app]({android_url})\u3001\u9ede\u9078\u8a2d\u5b9a\uff08preferences\uff09 -> \u9023\u7dda\uff08connection\uff09\u3002\u8b8a\u66f4\u4ee5\u4e0b\u8a2d\u5b9a\uff1a\n - \u6a21\u5f0f\uff08Mode\uff09\uff1aPrivate HTTP\n - \u4e3b\u6a5f\u7aef\uff08Host\uff09\uff1a{webhook_url}\n - Identification\uff1a\n - Username\uff1a `''`\n - Device ID\uff1a`''`\n\n\u65bc iOS \u8a2d\u5099\uff0c\u6253\u958b [OwnTracks app]({ios_url})\u3001\u9ede\u9078\u5de6\u4e0a\u65b9\u7684 (i) \u5716\u793a -> \u8a2d\u5b9a\uff08settings\uff09\u3002\u8b8a\u66f4\u4ee5\u4e0b\u8a2d\u5b9a\uff1a\n - \u6a21\u5f0f\uff08Mode\uff09\uff1aHTTP\n - URL: {webhook_url}\n - \u958b\u555f authentication\n - UserID: `''`\n\n{secret}\n\n\u8acb\u53c3\u95b1 [\u6587\u4ef6]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u8a73\u7d30\u8cc7\u6599\u3002" diff --git a/homeassistant/components/ozw/translations/pt-BR.json b/homeassistant/components/ozw/translations/pt-BR.json new file mode 100644 index 00000000000000..079c311cd0ae42 --- /dev/null +++ b/homeassistant/components/ozw/translations/pt-BR.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "step": { + "start_addon": { + "data": { + "usb_path": "Caminho do Dispositivo USB" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ozw/translations/zh-Hant.json b/homeassistant/components/ozw/translations/zh-Hant.json index 5ad1ca7ff6b94d..0e51da481d7302 100644 --- a/homeassistant/components/ozw/translations/zh-Hant.json +++ b/homeassistant/components/ozw/translations/zh-Hant.json @@ -7,7 +7,7 @@ "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "mqtt_required": "MQTT \u6574\u5408\u5c1a\u672a\u8a2d\u5b9a", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "addon_start_failed": "OpenZWave \u9644\u52a0\u5143\u4ef6\u555f\u52d5\u5931\u6557\uff0c\u8acb\u6aa2\u67e5\u8a2d\u5b9a\u3002" diff --git a/homeassistant/components/p1_monitor/translations/pt-BR.json b/homeassistant/components/p1_monitor/translations/pt-BR.json new file mode 100644 index 00000000000000..b46de9ce8ee891 --- /dev/null +++ b/homeassistant/components/p1_monitor/translations/pt-BR.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/panasonic_viera/translations/pt-BR.json b/homeassistant/components/panasonic_viera/translations/pt-BR.json index ae60c2dcdbaa0e..51f86ab0e828e4 100644 --- a/homeassistant/components/panasonic_viera/translations/pt-BR.json +++ b/homeassistant/components/panasonic_viera/translations/pt-BR.json @@ -1,11 +1,27 @@ { "config": { "abort": { - "already_configured": "Esta TV Panasonic Viera j\u00e1 est\u00e1 configurada." + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_pin_code": "C\u00f3digo PIN" }, "step": { + "pairing": { + "data": { + "pin": "C\u00f3digo PIN" + }, + "description": "C\u00f3digo PIN" + }, "user": { - "description": "Digite o endere\u00e7o IP da sua TV Panasonic Viera", + "data": { + "host": "Endere\u00e7o IP", + "name": "Nome" + }, + "description": "Digite o Endere\u00e7o IP da sua TV Panasonic Viera", "title": "Configure sua TV" } } diff --git a/homeassistant/components/philips_js/translations/el.json b/homeassistant/components/philips_js/translations/el.json index e10c6a8bbcc652..d5432d5d60e6ea 100644 --- a/homeassistant/components/philips_js/translations/el.json +++ b/homeassistant/components/philips_js/translations/el.json @@ -22,5 +22,14 @@ "trigger_type": { "turn_on": "\u0396\u03b7\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b7 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" } + }, + "options": { + "step": { + "init": { + "data": { + "allow_notify": "\u0395\u03c0\u03b9\u03c4\u03c1\u03ad\u03c8\u03c4\u03b5 \u03c4\u03b7 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1\u03c2 \u03b5\u03b9\u03b4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd." + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/pt-BR.json b/homeassistant/components/philips_js/translations/pt-BR.json new file mode 100644 index 00000000000000..f7b0e700c180ee --- /dev/null +++ b/homeassistant/components/philips_js/translations/pt-BR.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "step": { + "pair": { + "data": { + "pin": "C\u00f3digo PIN" + } + }, + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/pt-BR.json b/homeassistant/components/pi_hole/translations/pt-BR.json index 8b7cd1004eaa28..08c70aa431f6df 100644 --- a/homeassistant/components/pi_hole/translations/pt-BR.json +++ b/homeassistant/components/pi_hole/translations/pt-BR.json @@ -1,20 +1,25 @@ { "config": { "abort": { - "already_configured": "Servi\u00e7o j\u00e1 configurado" + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" }, "error": { "cannot_connect": "Falha ao conectar" }, "step": { + "api_key": { + "data": { + "api_key": "Chave da API" + } + }, "user": { "data": { - "api_key": "Chave de API", - "host": "Endere\u00e7o (IP)", + "api_key": "Chave da API", + "host": "Nome do host", "location": "Localiza\u00e7\u00e3o", "name": "Nome", "port": "Porta", - "ssl": "Usar SSL", + "ssl": "Usar um certificado SSL", "verify_ssl": "Verifique o certificado SSL" } } diff --git a/homeassistant/components/picnic/translations/pt-BR.json b/homeassistant/components/picnic/translations/pt-BR.json new file mode 100644 index 00000000000000..66c671f99a3c2e --- /dev/null +++ b/homeassistant/components/picnic/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plaato/translations/pt-BR.json b/homeassistant/components/plaato/translations/pt-BR.json index 01c296e59ac8ff..e8568c1ec157d5 100644 --- a/homeassistant/components/plaato/translations/pt-BR.json +++ b/homeassistant/components/plaato/translations/pt-BR.json @@ -1,11 +1,16 @@ { "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "cloud_not_connected": "N\u00e3o conectado ao Home Assistant Cloud.", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, "create_entry": { "default": "Para enviar eventos para o Home Assistant, voc\u00ea precisar\u00e1 configurar o recurso de webhook na Plaato Airlock.\n\nPreencha as seguintes informa\u00e7\u00f5es:\n\n- URL: `{webhook_url}`\n- M\u00e9todo: POST\n\nVeja [a documenta\u00e7\u00e3o]({docs_url}) para mais detalhes." }, "step": { "user": { - "description": "Tens a certeza que queres montar a Plaato Airlock?", + "description": "Deseja iniciar a configura\u00e7\u00e3o?", "title": "Configurar o Plaato Webhook" } } diff --git a/homeassistant/components/plaato/translations/zh-Hant.json b/homeassistant/components/plaato/translations/zh-Hant.json index bb9a58d81274d9..8ffa238d816fd7 100644 --- a/homeassistant/components/plaato/translations/zh-Hant.json +++ b/homeassistant/components/plaato/translations/zh-Hant.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "cloud_not_connected": "\u672a\u9023\u7dda\u81f3 Home Assistant \u96f2\u670d\u52d9\u3002", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "webhook_not_internet_accessible": "Home Assistant \u5be6\u9ad4\u5fc5\u9808\u8981\u80fd\u5f9e\u7db2\u969b\u7db2\u8def\u5b58\u53d6\u65b9\u80fd\u63a5\u6536 Webhook \u8a0a\u606f\u3002" }, "create_entry": { diff --git a/homeassistant/components/plex/translations/pt-BR.json b/homeassistant/components/plex/translations/pt-BR.json index eac953579c0f08..d300e57c1fab74 100644 --- a/homeassistant/components/plex/translations/pt-BR.json +++ b/homeassistant/components/plex/translations/pt-BR.json @@ -1,19 +1,38 @@ { "config": { + "abort": { + "all_configured": "Todos os servidores vinculados j\u00e1 configurados", + "already_configured": "Este servidor Plex j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", + "token_request_timeout": "Tempo limite de obten\u00e7\u00e3o do token", + "unknown": "Erro inesperado" + }, "error": { + "faulty_credentials": "Falha na autoriza\u00e7\u00e3o, verifique o token", + "no_servers": "Nenhum servidor vinculado \u00e0 conta Plex", + "not_found": "Servidor Plex n\u00e3o encontrado", "ssl_error": "Problema no certificado SSL" }, "flow_title": "{name} ({host})", "step": { "manual_setup": { "data": { + "host": "Nome do host", "port": "Porta", - "ssl": "Usar SSL", + "ssl": "Usar um certificado SSL", "token": "Token (Opcional)", "verify_ssl": "Verifique o certificado SSL" }, "title": "Configura\u00e7\u00e3o manual do Plex" }, + "select_server": { + "data": { + "server": "Servidor" + }, + "description": "V\u00e1rios servidores dispon\u00edveis, selecione um:", + "title": "Selecione servidor Plex" + }, "user_advanced": { "data": { "setup_method": "M\u00e9todo de configura\u00e7\u00e3o" diff --git a/homeassistant/components/plugwise/translations/pt-BR.json b/homeassistant/components/plugwise/translations/pt-BR.json index a375e2c1f67d87..c6a9a255cb7470 100644 --- a/homeassistant/components/plugwise/translations/pt-BR.json +++ b/homeassistant/components/plugwise/translations/pt-BR.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, "step": { "user": { "data": { @@ -8,7 +16,8 @@ }, "user_gateway": { "data": { - "host": "Endere\u00e7o IP" + "host": "Endere\u00e7o IP", + "port": "Porta" } } } diff --git a/homeassistant/components/plum_lightpad/translations/pt-BR.json b/homeassistant/components/plum_lightpad/translations/pt-BR.json new file mode 100644 index 00000000000000..88364743020d71 --- /dev/null +++ b/homeassistant/components/plum_lightpad/translations/pt-BR.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "password": "Senha" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/point/translations/pt-BR.json b/homeassistant/components/point/translations/pt-BR.json index c9384d82c38aeb..ef5fb55e53858a 100644 --- a/homeassistant/components/point/translations/pt-BR.json +++ b/homeassistant/components/point/translations/pt-BR.json @@ -1,17 +1,17 @@ { "config": { "abort": { - "already_setup": "Voc\u00ea s\u00f3 pode configurar uma conta Point.", - "authorize_url_timeout": "Excedido tempo limite gerando a URL de autoriza\u00e7\u00e3o.", + "already_setup": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", + "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", "external_setup": "Point configurado com \u00eaxito a partir de outro fluxo.", - "no_flows": "Voc\u00ea precisa configurar o Point antes de ser capaz de autenticar com ele. [Por favor, leia as instru\u00e7\u00f5es](https://www.home-assistant.io/components/point/)." + "no_flows": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.\nVoc\u00ea precisa configurar o Point antes de ser capaz de autenticar com ele. [Por favor, leia as instru\u00e7\u00f5es](https://www.home-assistant.io/components/point/)." }, "create_entry": { - "default": "Autenticado com sucesso com Minut para seu(s) dispositivo(s) Point" + "default": "Autenticado com sucesso" }, "error": { "follow_link": "Por favor, siga o link e autentique antes de pressionar Enviar", - "no_token": "N\u00e3o autenticado com Minut" + "no_token": "Token de acesso inv\u00e1lido" }, "step": { "auth": { @@ -22,7 +22,7 @@ "data": { "flow_impl": "Provedor" }, - "description": "Escolha atrav\u00e9s de qual provedor de autentica\u00e7\u00e3o voc\u00ea deseja autenticar com Point.", + "description": "Deseja iniciar a configura\u00e7\u00e3o?", "title": "Provedor de Autentica\u00e7\u00e3o" } } diff --git a/homeassistant/components/point/translations/zh-Hant.json b/homeassistant/components/point/translations/zh-Hant.json index 3f9df05d697102..1feba202c0b05a 100644 --- a/homeassistant/components/point/translations/zh-Hant.json +++ b/homeassistant/components/point/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_setup": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", + "already_setup": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "authorize_url_timeout": "\u7522\u751f\u8a8d\u8b49 URL \u6642\u903e\u6642\u3002", "external_setup": "\u5df2\u7531\u5176\u4ed6\u6d41\u7a0b\u6210\u529f\u8a2d\u5b9a Point\u3002", "no_flows": "\u5143\u4ef6\u5c1a\u672a\u8a2d\u7f6e\uff0c\u8acb\u53c3\u95b1\u6587\u4ef6\u8aaa\u660e\u3002", diff --git a/homeassistant/components/poolsense/translations/pt-BR.json b/homeassistant/components/poolsense/translations/pt-BR.json new file mode 100644 index 00000000000000..c1e5cf3ac21bac --- /dev/null +++ b/homeassistant/components/poolsense/translations/pt-BR.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "password": "Senha" + }, + "description": "Deseja iniciar a configura\u00e7\u00e3o?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/pt-BR.json b/homeassistant/components/powerwall/translations/pt-BR.json index e97b93d1e66629..f95b3489f8cdff 100644 --- a/homeassistant/components/powerwall/translations/pt-BR.json +++ b/homeassistant/components/powerwall/translations/pt-BR.json @@ -1,16 +1,19 @@ { "config": { "abort": { - "already_configured": "O powerwall j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { - "cannot_connect": "Falha ao conectar, tente novamente", + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, "step": { "user": { "data": { - "ip_address": "Endere\u00e7o IP" + "ip_address": "Endere\u00e7o IP", + "password": "Senha" }, "title": "Conecte-se ao powerwall" } diff --git a/homeassistant/components/profiler/translations/pt-BR.json b/homeassistant/components/profiler/translations/pt-BR.json new file mode 100644 index 00000000000000..7caf7983714456 --- /dev/null +++ b/homeassistant/components/profiler/translations/pt-BR.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "step": { + "user": { + "description": "Deseja iniciar a configura\u00e7\u00e3o?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/profiler/translations/zh-Hant.json b/homeassistant/components/profiler/translations/zh-Hant.json index c7d73c344d8b3a..e10e609a118ee5 100644 --- a/homeassistant/components/profiler/translations/zh-Hant.json +++ b/homeassistant/components/profiler/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "step": { "user": { diff --git a/homeassistant/components/progettihwsw/translations/pt-BR.json b/homeassistant/components/progettihwsw/translations/pt-BR.json new file mode 100644 index 00000000000000..1e898e15ce0558 --- /dev/null +++ b/homeassistant/components/progettihwsw/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "port": "Porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/prosegur/translations/pt-BR.json b/homeassistant/components/prosegur/translations/pt-BR.json new file mode 100644 index 00000000000000..8df5069e4315fb --- /dev/null +++ b/homeassistant/components/prosegur/translations/pt-BR.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + }, + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ps4/translations/pt-BR.json b/homeassistant/components/ps4/translations/pt-BR.json index e0547d1f06f2e9..06ddc17f942893 100644 --- a/homeassistant/components/ps4/translations/pt-BR.json +++ b/homeassistant/components/ps4/translations/pt-BR.json @@ -1,15 +1,17 @@ { "config": { "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "credential_error": "Erro ao buscar credenciais.", - "no_devices_found": "Nenhum dispositivo PlayStation 4 encontrado na rede.", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", "port_987_bind_error": "N\u00e3o foi poss\u00edvel conectar na porta 987. Consulte a [documenta\u00e7\u00e3o] (https://www.home-assistant.io/components/ps4/) para informa\u00e7\u00f5es adicionais.", "port_997_bind_error": "N\u00e3o foi poss\u00edvel conectar na porta 997. Consulte a [documenta\u00e7\u00e3o] (https://www.home-assistant.io/components/ps4/) para informa\u00e7\u00f5es adicionais." }, "error": { + "cannot_connect": "Falha ao conectar", "credential_timeout": "Servi\u00e7o de credencial expirou. Pressione Submit para reiniciar.", - "login_failed": "N\u00e3o foi poss\u00edvel parear com o PlayStation 4. Verifique se o PIN est\u00e1 correto.", - "no_ipaddress": "Digite o endere\u00e7o IP do PlayStation 4 que voc\u00ea gostaria de configurar." + "login_failed": "N\u00e3o foi poss\u00edvel parear com o PlayStation 4. Verifique se o C\u00f3digo PIN est\u00e1 correto.", + "no_ipaddress": "Digite o Endere\u00e7o IP do PlayStation 4 que voc\u00ea gostaria de configurar." }, "step": { "creds": { @@ -18,12 +20,12 @@ }, "link": { "data": { - "code": "PIN", + "code": "C\u00f3digo PIN", "ip_address": "Endere\u00e7o IP", "name": "Nome", "region": "Regi\u00e3o" }, - "description": "Digite suas informa\u00e7\u00f5es do PlayStation 4. Para 'PIN', navegue at\u00e9 'Configura\u00e7\u00f5es' no seu console PlayStation 4. Em seguida, navegue at\u00e9 \"Configura\u00e7\u00f5es de conex\u00e3o de aplicativos m\u00f3veis\" e selecione \"Adicionar dispositivo\". Digite o PIN exibido. Consulte a [documenta\u00e7\u00e3o] (https://www.home-assistant.io/components/ps4/) para informa\u00e7\u00f5es adicionais.", + "description": "Digite suas informa\u00e7\u00f5es do PlayStation 4. Para C\u00f3digo PIN, navegue at\u00e9 'Configura\u00e7\u00f5es' no seu console PlayStation 4. Em seguida, navegue at\u00e9 \"Configura\u00e7\u00f5es de conex\u00e3o de aplicativos m\u00f3veis\" e selecione \"Adicionar dispositivo\". Digite o C\u00f3digo PIN exibido. Consulte a [documenta\u00e7\u00e3o] (https://www.home-assistant.io/components/ps4/) para informa\u00e7\u00f5es adicionais.", "title": "Playstation 4" }, "mode": { diff --git a/homeassistant/components/pvoutput/translations/pt-BR.json b/homeassistant/components/pvoutput/translations/pt-BR.json new file mode 100644 index 00000000000000..a97b0b3abd418c --- /dev/null +++ b/homeassistant/components/pvoutput/translations/pt-BR.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Chave da API" + } + }, + "user": { + "data": { + "api_key": "Chave da API", + "system_id": "ID do sistema" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/pt-BR.json b/homeassistant/components/pvpc_hourly_pricing/translations/pt-BR.json index efcaeb801d943d..396f9f6ab67c04 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/pt-BR.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/pt-BR.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "A integra\u00e7\u00e3o j\u00e1 est\u00e1 configurada com um sensor existente com essa tarifa" + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" }, "step": { "user": { diff --git a/homeassistant/components/rachio/translations/pt-BR.json b/homeassistant/components/rachio/translations/pt-BR.json new file mode 100644 index 00000000000000..558881465675f0 --- /dev/null +++ b/homeassistant/components/rachio/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "api_key": "Chave da API" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rainforest_eagle/translations/pt-BR.json b/homeassistant/components/rainforest_eagle/translations/pt-BR.json new file mode 100644 index 00000000000000..5082256276cbd6 --- /dev/null +++ b/homeassistant/components/rainforest_eagle/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rainmachine/translations/el.json b/homeassistant/components/rainmachine/translations/el.json index 8c2e276df85e6d..8d986480ff0884 100644 --- a/homeassistant/components/rainmachine/translations/el.json +++ b/homeassistant/components/rainmachine/translations/el.json @@ -6,5 +6,15 @@ "title": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03b1\u03c2" } } + }, + "options": { + "step": { + "init": { + "data": { + "zone_run_time": "\u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf\u03c2 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c2 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b6\u03ce\u03bd\u03b7\u03c2 (\u03c3\u03b5 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)" + }, + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 RainMachine" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/rainmachine/translations/pt-BR.json b/homeassistant/components/rainmachine/translations/pt-BR.json index e876d3675759fc..1acfcef8cd31f1 100644 --- a/homeassistant/components/rainmachine/translations/pt-BR.json +++ b/homeassistant/components/rainmachine/translations/pt-BR.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/rdw/translations/pt-BR.json b/homeassistant/components/rdw/translations/pt-BR.json new file mode 100644 index 00000000000000..d0ed7981642846 --- /dev/null +++ b/homeassistant/components/rdw/translations/pt-BR.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "license_plate": "Placa de carro" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recollect_waste/translations/pt-BR.json b/homeassistant/components/recollect_waste/translations/pt-BR.json new file mode 100644 index 00000000000000..e29d809ebff3dd --- /dev/null +++ b/homeassistant/components/recollect_waste/translations/pt-BR.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/remote/translations/pt-BR.json b/homeassistant/components/remote/translations/pt-BR.json index d658a07f4df45d..e1220006111267 100644 --- a/homeassistant/components/remote/translations/pt-BR.json +++ b/homeassistant/components/remote/translations/pt-BR.json @@ -1,4 +1,10 @@ { + "device_automation": { + "trigger_type": { + "changed_states": "{entity_name} ligado ou desligado", + "toggled": "{entity_name} ligado ou desligado" + } + }, "state": { "_": { "off": "Desligado", diff --git a/homeassistant/components/renault/translations/pt-BR.json b/homeassistant/components/renault/translations/pt-BR.json new file mode 100644 index 00000000000000..a4c2dc620e3a14 --- /dev/null +++ b/homeassistant/components/renault/translations/pt-BR.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "invalid_credentials": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "Senha" + }, + "title": "Reautenticar Integra\u00e7\u00e3o" + }, + "user": { + "data": { + "password": "Senha" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/el.json b/homeassistant/components/rfxtrx/translations/el.json index 1ad4a767b323ff..e6f90beed31d46 100644 --- a/homeassistant/components/rfxtrx/translations/el.json +++ b/homeassistant/components/rfxtrx/translations/el.json @@ -30,6 +30,15 @@ } } }, + "device_automation": { + "action_type": { + "send_command": "\u0391\u03c0\u03bf\u03c3\u03c4\u03bf\u03bb\u03ae \u03b5\u03bd\u03c4\u03bf\u03bb\u03ae\u03c2: {subtype}" + }, + "trigger_type": { + "command": "\u039b\u03ae\u03c6\u03b8\u03b7\u03ba\u03b5 \u03b5\u03bd\u03c4\u03bf\u03bb\u03ae: {subtype}", + "status": "\u039b\u03ae\u03c6\u03b8\u03b7\u03ba\u03b5 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7: {subtype}" + } + }, "options": { "error": { "invalid_event_code": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03bf\u03c2", diff --git a/homeassistant/components/rfxtrx/translations/pt-BR.json b/homeassistant/components/rfxtrx/translations/pt-BR.json new file mode 100644 index 00000000000000..eae5b5e14841b2 --- /dev/null +++ b/homeassistant/components/rfxtrx/translations/pt-BR.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", + "cannot_connect": "Falha ao conectar" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "setup_network": { + "data": { + "host": "Nome do host", + "port": "Porta" + } + }, + "setup_serial_manual_path": { + "data": { + "device": "Caminho do Dispositivo USB" + } + } + } + }, + "options": { + "error": { + "already_configured_device": "Dispositivo j\u00e1 est\u00e1 configurado", + "unknown": "Erro inesperado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/zh-Hant.json b/homeassistant/components/rfxtrx/translations/zh-Hant.json index ec763ece1de895..d66b0b1cf7c769 100644 --- a/homeassistant/components/rfxtrx/translations/zh-Hant.json +++ b/homeassistant/components/rfxtrx/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", + "already_configured": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "cannot_connect": "\u9023\u7dda\u5931\u6557" }, "error": { diff --git a/homeassistant/components/ridwell/translations/pt-BR.json b/homeassistant/components/ridwell/translations/pt-BR.json index befa822057f738..c77ca3a02fe808 100644 --- a/homeassistant/components/ridwell/translations/pt-BR.json +++ b/homeassistant/components/ridwell/translations/pt-BR.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 configurado", - "reauth_successful": "A reautentica\u00e7\u00e3o foi feita com sucesso" + "already_configured": "A conta j\u00e1 foi configurada", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", @@ -14,12 +14,12 @@ "password": "Senha" }, "description": "Por favor, digite novamente a senha para {username}:", - "title": "Reautenticar integra\u00e7\u00e3o" + "title": "Reautenticar Integra\u00e7\u00e3o" }, "user": { "data": { "password": "Senha", - "username": "Nome de usu\u00e1rio" + "username": "Usu\u00e1rio" }, "description": "Digite seu nome de usu\u00e1rio e senha:" } diff --git a/homeassistant/components/ring/translations/pt-BR.json b/homeassistant/components/ring/translations/pt-BR.json index abb894549cc93e..124d9d36c33f0f 100644 --- a/homeassistant/components/ring/translations/pt-BR.json +++ b/homeassistant/components/ring/translations/pt-BR.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/risco/translations/el.json b/homeassistant/components/risco/translations/el.json index c38cfc72cc19d0..f1faffc85e1d27 100644 --- a/homeassistant/components/risco/translations/el.json +++ b/homeassistant/components/risco/translations/el.json @@ -40,7 +40,9 @@ "D": "\u039f\u03bc\u03ac\u03b4\u03b1 \u0394", "arm": "\u039f\u03c0\u03bb\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03b9 (\u0395\u039a\u03a4\u039f\u03a3)", "partial_arm": "\u039c\u03b5\u03c1\u03b9\u03ba\u03ce\u03c2 \u03bf\u03c0\u03bb\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2 (\u0395\u039d\u03a4\u039f\u03a3)" - } + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c0\u03bf\u03b9\u03b1 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03b8\u03b1 \u03b1\u03bd\u03b1\u03c6\u03ad\u03c1\u03b5\u03b9 \u03bf \u03c3\u03c5\u03bd\u03b1\u03b3\u03b5\u03c1\u03bc\u03cc\u03c2 \u03c4\u03bf\u03c5 Home Assistant \u03b3\u03b9\u03b1 \u03ba\u03ac\u03b8\u03b5 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03c6\u03ad\u03c1\u03b5\u03b9 \u03b7 Risco", + "title": "\u0391\u03bd\u03c4\u03b9\u03c3\u03c4\u03bf\u03b9\u03c7\u03af\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ac\u03c3\u03b5\u03b9\u03c2 Risco \u03c3\u03b5 \u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ac\u03c3\u03b5\u03b9\u03c2 Home Assistant" } } } diff --git a/homeassistant/components/risco/translations/pt-BR.json b/homeassistant/components/risco/translations/pt-BR.json new file mode 100644 index 00000000000000..ab7d4be0a4c7d5 --- /dev/null +++ b/homeassistant/components/risco/translations/pt-BR.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "pin": "C\u00f3digo PIN", + "username": "Usu\u00e1rio" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "code_arm_required": "C\u00f3digo PIN", + "code_disarm_required": "C\u00f3digo PIN" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rituals_perfume_genie/translations/pt-BR.json b/homeassistant/components/rituals_perfume_genie/translations/pt-BR.json new file mode 100644 index 00000000000000..8722382b01bd53 --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Senha" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/pt-BR.json b/homeassistant/components/roku/translations/pt-BR.json index d866e4d4ee2d35..f0a9a26bdb84b2 100644 --- a/homeassistant/components/roku/translations/pt-BR.json +++ b/homeassistant/components/roku/translations/pt-BR.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "unknown": "Erro inesperado" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, "flow_title": "Roku: {name}", "step": { "ssdp_confirm": { @@ -7,6 +15,9 @@ "title": "Roku" }, "user": { + "data": { + "host": "Nome do host" + }, "description": "Digite suas informa\u00e7\u00f5es de Roku." } } diff --git a/homeassistant/components/roomba/translations/pt-BR.json b/homeassistant/components/roomba/translations/pt-BR.json index a148d1976adfa1..dbd20196984478 100644 --- a/homeassistant/components/roomba/translations/pt-BR.json +++ b/homeassistant/components/roomba/translations/pt-BR.json @@ -1,14 +1,35 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar" + }, "error": { - "cannot_connect": "Falha ao conectar, tente novamente" + "cannot_connect": "Falha ao conectar" }, "step": { + "init": { + "data": { + "host": "Nome do host" + } + }, + "link_manual": { + "data": { + "password": "Senha" + } + }, + "manual": { + "data": { + "host": "Nome do host" + } + }, "user": { "data": { "blid": "BLID", "continuous": "Cont\u00ednuo", - "delay": "Atraso" + "delay": "Atraso", + "host": "Nome do host", + "password": "Senha" }, "description": "Atualmente, a recupera\u00e7\u00e3o do BLID e da senha \u00e9 um processo manual. Siga as etapas descritas na documenta\u00e7\u00e3o em: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", "title": "Conecte-se ao dispositivo" diff --git a/homeassistant/components/roon/translations/pt-BR.json b/homeassistant/components/roon/translations/pt-BR.json index 39538d2bf5232a..a96222b15f3ebe 100644 --- a/homeassistant/components/roon/translations/pt-BR.json +++ b/homeassistant/components/roon/translations/pt-BR.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "O dispositivo j\u00e1 foi configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "error": { "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", - "unknown": "Ocorreu um erro inexperado" + "unknown": "Erro inesperado" }, "step": { "link": { @@ -14,7 +14,7 @@ }, "user": { "data": { - "host": "Host" + "host": "Nome do host" }, "description": "Por favor, digite seu hostname ou IP do servidor Roon." } diff --git a/homeassistant/components/rpi_power/translations/pt-BR.json b/homeassistant/components/rpi_power/translations/pt-BR.json new file mode 100644 index 00000000000000..369064ba6cb5e1 --- /dev/null +++ b/homeassistant/components/rpi_power/translations/pt-BR.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "step": { + "confirm": { + "description": "Deseja iniciar a configura\u00e7\u00e3o?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/zh-Hant.json b/homeassistant/components/rpi_power/translations/zh-Hant.json index 05cdeb6852b85f..dd2658a56db4d6 100644 --- a/homeassistant/components/rpi_power/translations/zh-Hant.json +++ b/homeassistant/components/rpi_power/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\u627e\u4e0d\u5230\u7cfb\u7d71\u6240\u9700\u7684\u5143\u4ef6\uff0c\u8acb\u78ba\u5b9a Kernel \u70ba\u6700\u65b0\u7248\u672c\u3001\u540c\u6642\u786c\u9ad4\u70ba\u652f\u63f4\u72c0\u614b", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "step": { "confirm": { diff --git a/homeassistant/components/rtsp_to_webrtc/translations/pt-BR.json b/homeassistant/components/rtsp_to_webrtc/translations/pt-BR.json new file mode 100644 index 00000000000000..e6fd29a97df78f --- /dev/null +++ b/homeassistant/components/rtsp_to_webrtc/translations/pt-BR.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "error": { + "invalid_url": "Deve ser um URL de servidor RTSPtoWebRTC v\u00e1lido, por exemplo, https://example.com", + "server_failure": "O servidor RTSPtoWebRTC retornou um erro. Verifique os logs para obter mais informa\u00e7\u00f5es.", + "server_unreachable": "N\u00e3o \u00e9 poss\u00edvel se comunicar com o servidor RTSPtoWebRTC. Verifique os logs para obter mais informa\u00e7\u00f5es." + }, + "step": { + "hassio_confirm": { + "description": "Deseja configurar o Home Assistant para se conectar ao servidor RTSPtoWebRTC fornecido pelo complemento: {addon} ?", + "title": "RTSPtoWebRTC via complemento do Home Assistant" + }, + "user": { + "data": { + "server_url": "URL do servidor RTSPtoWebRTC, por exemplo, https://example.com" + }, + "description": "A integra\u00e7\u00e3o RTSPtoWebRTC requer um servidor para traduzir fluxos RTSP em WebRTC. Insira a URL para o servidor RTSPtoWebRTC.", + "title": "Configurar RTSPtoWebRTC" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rtsp_to_webrtc/translations/zh-Hant.json b/homeassistant/components/rtsp_to_webrtc/translations/zh-Hant.json index 60da2aebd3b4de..ace2e23312b11e 100644 --- a/homeassistant/components/rtsp_to_webrtc/translations/zh-Hant.json +++ b/homeassistant/components/rtsp_to_webrtc/translations/zh-Hant.json @@ -3,7 +3,7 @@ "abort": { "server_failure": "RTSPtoWebRTC \u4f3a\u670d\u5668\u56de\u5831\u932f\u8aa4\uff0c\u8acb\u53c3\u95b1\u65e5\u8a8c\u4ee5\u7372\u5f97\u66f4\u8a73\u7d30\u8cc7\u8a0a\u3002", "server_unreachable": "\u7121\u6cd5\u8207 RTSPtoWebRTC \u4f3a\u670d\u5668\u9032\u884c\u9023\u7dda\uff0c\u8acb\u53c3\u95b1\u65e5\u8a8c\u4ee5\u7372\u5f97\u66f4\u8a73\u7d30\u8cc7\u8a0a\u3002", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "invalid_url": "\u5fc5\u9808\u70ba\u6709\u6548 RTSPtoWebRTC \u4f3a\u670d\u5668 URL\uff0c\u4f8b\u5982\uff1ahttps://example.com", diff --git a/homeassistant/components/ruckus_unleashed/translations/pt-BR.json b/homeassistant/components/ruckus_unleashed/translations/pt-BR.json new file mode 100644 index 00000000000000..93beddb92a8517 --- /dev/null +++ b/homeassistant/components/ruckus_unleashed/translations/pt-BR.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/samsungtv/translations/el.json b/homeassistant/components/samsungtv/translations/el.json index b9ea5d85166c11..40037f5e3eb22f 100644 --- a/homeassistant/components/samsungtv/translations/el.json +++ b/homeassistant/components/samsungtv/translations/el.json @@ -3,6 +3,7 @@ "abort": { "auth_missing": "\u03a4\u03bf Home Assistant \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03b7\u03bc\u03ad\u03bd\u03bf \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03c3\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7 Samsung. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c4\u03b7\u03c2 \u0394\u03b9\u03b1\u03c7\u03b5\u03af\u03c1\u03b9\u03c3\u03b7\u03c2 \u03b5\u03be\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03ce\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd \u03c4\u03b7\u03c2 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Home Assistant.", "id_missing": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Samsung \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 SerialNumber.", + "missing_config_entry": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Samsung \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2.", "not_supported": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Samsung \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7 \u03c3\u03c4\u03b9\u03b3\u03bc\u03ae." }, "flow_title": "{device}", diff --git a/homeassistant/components/samsungtv/translations/pt-BR.json b/homeassistant/components/samsungtv/translations/pt-BR.json new file mode 100644 index 00000000000000..429ae516c9b805 --- /dev/null +++ b/homeassistant/components/samsungtv/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "cannot_connect": "Falha ao conectar", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/translations/pt-BR.json b/homeassistant/components/screenlogic/translations/pt-BR.json new file mode 100644 index 00000000000000..3640d2ac0a7bae --- /dev/null +++ b/homeassistant/components/screenlogic/translations/pt-BR.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "gateway_entry": { + "data": { + "ip_address": "Endere\u00e7o IP", + "port": "Porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sense/translations/pt-BR.json b/homeassistant/components/sense/translations/pt-BR.json index b61651bf441f22..ad7889f7536296 100644 --- a/homeassistant/components/sense/translations/pt-BR.json +++ b/homeassistant/components/sense/translations/pt-BR.json @@ -1,16 +1,17 @@ { "config": { "abort": { - "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "error": { - "cannot_connect": "Falha ao conectar, tente novamente", + "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, "step": { "user": { "data": { + "password": "Senha", "timeout": "Tempo limite" } } diff --git a/homeassistant/components/senseme/translations/pt-BR.json b/homeassistant/components/senseme/translations/pt-BR.json new file mode 100644 index 00000000000000..210b33376d504d --- /dev/null +++ b/homeassistant/components/senseme/translations/pt-BR.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido" + }, + "flow_title": "{name} - {model} ({host})", + "step": { + "discovery_confirm": { + "description": "Deseja configurar {name} - {model} ( {host} )?" + }, + "manual": { + "data": { + "host": "Nome do host" + }, + "description": "Digite um endere\u00e7o IP." + }, + "user": { + "data": { + "device": "Dispositivo" + }, + "description": "Selecione um dispositivo ou escolha 'Endere\u00e7o IP' para inserir manualmente um endere\u00e7o IP." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sensibo/translations/pt-BR.json b/homeassistant/components/sensibo/translations/pt-BR.json new file mode 100644 index 00000000000000..fac1d04755f967 --- /dev/null +++ b/homeassistant/components/sensibo/translations/pt-BR.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "api_key": "Chave da API", + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sensor/translations/pt-BR.json b/homeassistant/components/sensor/translations/pt-BR.json index 337a4d9f3184b7..dfb4321dbc69d7 100644 --- a/homeassistant/components/sensor/translations/pt-BR.json +++ b/homeassistant/components/sensor/translations/pt-BR.json @@ -1,10 +1,30 @@ { "device_automation": { "condition_type": { + "is_apparent_power": "Pot\u00eancia aparente atual de {entity_name}", + "is_battery_level": "N\u00edvel atual da bateria {nome_entidade}", + "is_frequency": "Frequ\u00eancia atual de {entity_name}", "is_humidity": "Humidade atual do(a) {entity_name}", + "is_illuminance": "Luminosidade atual {nome_da_entidade}", + "is_power": "Pot\u00eancia atual {entity_name}", "is_pressure": "Press\u00e3o atual do(a) {entity_name}", + "is_reactive_power": "Pot\u00eancia reativa atual de {entity_name}", "is_signal_strength": "For\u00e7a do sinal atual do(a) {entity_name}", - "is_temperature": "Temperatura atual do(a) {entity_name}" + "is_temperature": "Temperatura atual do(a) {entity_name}", + "is_value": "Valor atual de {entity_name}" + }, + "trigger_type": { + "apparent_power": "Mudan\u00e7as de poder aparentes de {entity_name}", + "battery_level": "{nome_da_entidade} mudan\u00e7as no n\u00edvel da bateria", + "frequency": "Altera\u00e7\u00f5es de frequ\u00eancia de {entity_name}", + "humidity": "{nome_da_entidade} mudan\u00e7as de umidade", + "illuminance": "{nome_da_entidade} mudan\u00e7as de luminosidade", + "power": "{entity_name} mudan\u00e7as de energia", + "pressure": "{entity_name} mudan\u00e7as de press\u00e3o", + "reactive_power": "Altera\u00e7\u00f5es de pot\u00eancia reativa de {entity_name}", + "signal_strength": "{nome_da_entidade} muda a for\u00e7a do sinal", + "temperature": "{entity_name} mudan\u00e7as de temperatura", + "value": "{nome_da_entidade} mudan\u00e7as de valor" } }, "state": { diff --git a/homeassistant/components/sentry/translations/pt-BR.json b/homeassistant/components/sentry/translations/pt-BR.json index b4f025eaf6d63c..cae844d66ee96d 100644 --- a/homeassistant/components/sentry/translations/pt-BR.json +++ b/homeassistant/components/sentry/translations/pt-BR.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, "error": { "bad_dsn": "DSN inv\u00e1lido", "unknown": "Erro inesperado" diff --git a/homeassistant/components/sentry/translations/zh-Hant.json b/homeassistant/components/sentry/translations/zh-Hant.json index aae10144a661f3..04fe4682a42450 100644 --- a/homeassistant/components/sentry/translations/zh-Hant.json +++ b/homeassistant/components/sentry/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "bad_dsn": "DSN \u7121\u6548", diff --git a/homeassistant/components/sharkiq/translations/pt-BR.json b/homeassistant/components/sharkiq/translations/pt-BR.json new file mode 100644 index 00000000000000..f16b424dee5c8e --- /dev/null +++ b/homeassistant/components/sharkiq/translations/pt-BR.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "cannot_connect": "Falha ao conectar", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", + "unknown": "Erro inesperado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "reauth": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + }, + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/pl.json b/homeassistant/components/shelly/translations/pl.json index 062cc73de37f8e..d1c658d7816875 100644 --- a/homeassistant/components/shelly/translations/pl.json +++ b/homeassistant/components/shelly/translations/pl.json @@ -37,16 +37,16 @@ "button4": "Czwarty przycisk" }, "trigger_type": { - "btn_down": "zostanie wci\u015bni\u0119ty przycisk \"w d\u00f3\u0142\" {subtype}", - "btn_up": "zostanie wci\u015bni\u0119ty przycisk \"do g\u00f3ry\" {subtype}", + "btn_down": "przycisk {subtype} zostanie wci\u015bni\u0119ty", + "btn_up": "przycisk {subtype} zostanie puszczony", "double": "przycisk \"{subtype}\" zostanie dwukrotnie naci\u015bni\u0119ty", - "double_push": "przycisk \"{subtype}\" zostanie dwukrotnie naci\u015bni\u0119ty", - "long": "przycisk \"{subtype}\" zostanie d\u0142ugo naci\u015bni\u0119ty", + "double_push": "przycisk {subtype} zostanie dwukrotnie naci\u015bni\u0119ty", + "long": "przycisk {subtype} zostanie d\u0142ugo naci\u015bni\u0119ty", "long_push": "przycisk {subtype} zostanie d\u0142ugo naci\u015bni\u0119ty", "long_single": "przycisk \"{subtype}\" zostanie d\u0142ugo naci\u015bni\u0119ty, a nast\u0119pnie pojedynczo naci\u015bni\u0119ty", "single": "przycisk \"{subtype}\" zostanie pojedynczo naci\u015bni\u0119ty", "single_long": "przycisk \"{subtype}\" pojedynczo naci\u015bni\u0119ty, a nast\u0119pnie d\u0142ugo naci\u015bni\u0119ty", - "single_push": "przycisk \"{subtype}\" zostanie pojedynczo naci\u015bni\u0119ty", + "single_push": "przycisk {subtype} zostanie pojedynczo naci\u015bni\u0119ty", "triple": "przycisk \"{subtype}\" zostanie trzykrotnie naci\u015bni\u0119ty" } } diff --git a/homeassistant/components/shelly/translations/pt-BR.json b/homeassistant/components/shelly/translations/pt-BR.json new file mode 100644 index 00000000000000..47d51f4454720e --- /dev/null +++ b/homeassistant/components/shelly/translations/pt-BR.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "credentials": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + }, + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shopping_list/translations/pt-BR.json b/homeassistant/components/shopping_list/translations/pt-BR.json index 9e8b24efa29c77..bdb2d4041ef323 100644 --- a/homeassistant/components/shopping_list/translations/pt-BR.json +++ b/homeassistant/components/shopping_list/translations/pt-BR.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "A lista de compras j\u00e1 est\u00e1 configurada." + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" }, "step": { "user": { diff --git a/homeassistant/components/sia/translations/pt-BR.json b/homeassistant/components/sia/translations/pt-BR.json new file mode 100644 index 00000000000000..ccc0fc7c4776bf --- /dev/null +++ b/homeassistant/components/sia/translations/pt-BR.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "port": "Porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/simplisafe/translations/el.json b/homeassistant/components/simplisafe/translations/el.json index cd55de41a547c1..776b52e4d70547 100644 --- a/homeassistant/components/simplisafe/translations/el.json +++ b/homeassistant/components/simplisafe/translations/el.json @@ -4,10 +4,12 @@ "already_configured": "\u0391\u03c5\u03c4\u03cc\u03c2 \u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 SimpliSafe \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7." }, "error": { - "identifier_exists": "\u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ae\u03b4\u03b7 \u03ba\u03b1\u03c4\u03b1\u03c7\u03c9\u03c1\u03b7\u03bc\u03ad\u03bd\u03bf\u03c2" + "identifier_exists": "\u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ae\u03b4\u03b7 \u03ba\u03b1\u03c4\u03b1\u03c7\u03c9\u03c1\u03b7\u03bc\u03ad\u03bd\u03bf\u03c2", + "still_awaiting_mfa": "\u0391\u03bd\u03b1\u03bc\u03ad\u03bd\u03b5\u03c4\u03b1\u03b9 \u03b1\u03ba\u03cc\u03bc\u03b7 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf email \u03c4\u03bf\u03c5 \u03a5\u03c0\u03bf\u03c5\u03c1\u03b3\u03b5\u03af\u03bf\u03c5 \u039f\u03b9\u03ba\u03bf\u03bd\u03bf\u03bc\u03b9\u03ba\u03ce\u03bd" }, "step": { "mfa": { + "description": "\u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03bf email \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03ad\u03bd\u03b1\u03bd \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd SimpliSafe. \u0391\u03c6\u03bf\u03cd \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf, \u03b5\u03c0\u03b9\u03c3\u03c4\u03c1\u03ad\u03c8\u03c4\u03b5 \u03b5\u03b4\u03ce \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03ce\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2.", "title": "\u03a0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ce\u03bd \u03c0\u03b1\u03c1\u03b1\u03b3\u03cc\u03bd\u03c4\u03c9\u03bd SimpliSafe" }, "reauth_confirm": { diff --git a/homeassistant/components/simplisafe/translations/pt-BR.json b/homeassistant/components/simplisafe/translations/pt-BR.json index 0e5b2151e20bbf..cdf8d042b28bbb 100644 --- a/homeassistant/components/simplisafe/translations/pt-BR.json +++ b/homeassistant/components/simplisafe/translations/pt-BR.json @@ -1,7 +1,12 @@ { "config": { + "abort": { + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, "error": { - "identifier_exists": "Conta j\u00e1 cadastrada" + "identifier_exists": "Conta j\u00e1 cadastrada", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" }, "step": { "input_auth_code": { @@ -9,6 +14,12 @@ "auth_code": "C\u00f3digo de Autoriza\u00e7\u00e3o" } }, + "reauth_confirm": { + "data": { + "password": "Senha" + }, + "title": "Reautenticar Integra\u00e7\u00e3o" + }, "user": { "data": { "password": "Senha", diff --git a/homeassistant/components/sma/translations/pt-BR.json b/homeassistant/components/sma/translations/pt-BR.json new file mode 100644 index 00000000000000..566418e49a95d7 --- /dev/null +++ b/homeassistant/components/sma/translations/pt-BR.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "password": "Senha", + "ssl": "Usar um certificado SSL", + "verify_ssl": "Verifique o certificado SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smappee/translations/pt-BR.json b/homeassistant/components/smappee/translations/pt-BR.json index cbaae7d2d6a419..a0219eb309f6a8 100644 --- a/homeassistant/components/smappee/translations/pt-BR.json +++ b/homeassistant/components/smappee/translations/pt-BR.json @@ -1,12 +1,16 @@ { "config": { "abort": { - "already_configured_device": "Dispositivo j\u00e1 configurado" + "already_configured_device": "Dispositivo j\u00e1 est\u00e1 configurado", + "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", + "cannot_connect": "Falha ao conectar", + "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", + "no_url_available": "N\u00e3o h\u00e1 URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a se\u00e7\u00e3o de ajuda]({docs_url})" }, "step": { "local": { "data": { - "host": "Host" + "host": "Nome do host" } } } diff --git a/homeassistant/components/smart_meter_texas/translations/pt-BR.json b/homeassistant/components/smart_meter_texas/translations/pt-BR.json new file mode 100644 index 00000000000000..66c671f99a3c2e --- /dev/null +++ b/homeassistant/components/smart_meter_texas/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarthab/translations/pt-BR.json b/homeassistant/components/smarthab/translations/pt-BR.json index e1f66450b015fb..9200a7c2eac1f1 100644 --- a/homeassistant/components/smarthab/translations/pt-BR.json +++ b/homeassistant/components/smarthab/translations/pt-BR.json @@ -1,8 +1,15 @@ { "config": { "error": { - "invalid_auth": "Autentica\u00e7\u00e3o invalida", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Senha" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/smartthings/translations/pt-BR.json b/homeassistant/components/smartthings/translations/pt-BR.json index 5b6329a530fbe8..9b6fe28f84bf15 100644 --- a/homeassistant/components/smartthings/translations/pt-BR.json +++ b/homeassistant/components/smartthings/translations/pt-BR.json @@ -11,6 +11,11 @@ "webhook_error": "O SmartThings n\u00e3o p\u00f4de validar o terminal configurado em `base_url`. Por favor, revise os requisitos do componente." }, "step": { + "pat": { + "data": { + "access_token": "Token de acesso" + } + }, "user": { "description": "Por favor, insira um SmartThings [Personal Access Token] ( {token_url} ) que foi criado de acordo com as [instru\u00e7\u00f5es] ( {component_url} ).", "title": "Digite o token de acesso pessoal" diff --git a/homeassistant/components/smarttub/translations/pt-BR.json b/homeassistant/components/smarttub/translations/pt-BR.json new file mode 100644 index 00000000000000..77da3eac5d8585 --- /dev/null +++ b/homeassistant/components/smarttub/translations/pt-BR.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "reauth_confirm": { + "title": "Reautenticar Integra\u00e7\u00e3o" + }, + "user": { + "data": { + "password": "Senha" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smhi/translations/pt-BR.json b/homeassistant/components/smhi/translations/pt-BR.json index 0bc966fdd6cb9b..235008c7c31a1d 100644 --- a/homeassistant/components/smhi/translations/pt-BR.json +++ b/homeassistant/components/smhi/translations/pt-BR.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada" + }, "error": { "name_exists": "O nome j\u00e1 existe", "wrong_location": "Localiza\u00e7\u00e3o apenas na Su\u00e9cia" diff --git a/homeassistant/components/sms/translations/pt-BR.json b/homeassistant/components/sms/translations/pt-BR.json new file mode 100644 index 00000000000000..9b73cd590b25c6 --- /dev/null +++ b/homeassistant/components/sms/translations/pt-BR.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sms/translations/zh-Hant.json b/homeassistant/components/sms/translations/zh-Hant.json index 12cfbc75384bac..b6e08ffa7ec275 100644 --- a/homeassistant/components/sms/translations/zh-Hant.json +++ b/homeassistant/components/sms/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", diff --git a/homeassistant/components/solaredge/translations/pt-BR.json b/homeassistant/components/solaredge/translations/pt-BR.json new file mode 100644 index 00000000000000..3fdd2b2db643a6 --- /dev/null +++ b/homeassistant/components/solaredge/translations/pt-BR.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "invalid_api_key": "Chave de API inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "api_key": "Chave da API", + "name": "O nome desta instala\u00e7\u00e3o", + "site_id": "O ID do site SolarEdge" + }, + "title": "Defina os par\u00e2metros da API para esta instala\u00e7\u00e3o" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solarlog/translations/pt-BR.json b/homeassistant/components/solarlog/translations/pt-BR.json new file mode 100644 index 00000000000000..30221b1d790b98 --- /dev/null +++ b/homeassistant/components/solarlog/translations/pt-BR.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solax/translations/pt-BR.json b/homeassistant/components/solax/translations/pt-BR.json new file mode 100644 index 00000000000000..b000a3b07c389b --- /dev/null +++ b/homeassistant/components/solax/translations/pt-BR.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "ip_address": "Endere\u00e7o IP", + "password": "Senha", + "port": "Porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/soma/translations/pt-BR.json b/homeassistant/components/soma/translations/pt-BR.json index 3485b5304b466e..bb4ddf9a67c8e5 100644 --- a/homeassistant/components/soma/translations/pt-BR.json +++ b/homeassistant/components/soma/translations/pt-BR.json @@ -1,13 +1,19 @@ { "config": { "abort": { - "connection_error": "Falha na liga\u00e7\u00e3o \u00e0 SOMA Connect.", + "already_setup": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", + "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", + "connection_error": "Falha ao conectar", + "missing_configuration": "O componente Soma n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", "result_error": "SOMA Connect respondeu com status de erro." }, + "create_entry": { + "default": "Autenticado com sucesso" + }, "step": { "user": { "data": { - "host": "Host", + "host": "Nome do host", "port": "Porta" } } diff --git a/homeassistant/components/soma/translations/zh-Hant.json b/homeassistant/components/soma/translations/zh-Hant.json index c4e2796e189e22..c3ad4dc1bf1af8 100644 --- a/homeassistant/components/soma/translations/zh-Hant.json +++ b/homeassistant/components/soma/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_setup": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", + "already_setup": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "authorize_url_timeout": "\u7522\u751f\u8a8d\u8b49 URL \u6642\u903e\u6642\u3002", "connection_error": "\u9023\u7dda\u5931\u6557", "missing_configuration": "Soma \u5143\u4ef6\u5c1a\u672a\u8a2d\u7f6e\uff0c\u8acb\u53c3\u95b1\u6587\u4ef6\u8aaa\u660e\u3002", diff --git a/homeassistant/components/somfy/translations/pt-BR.json b/homeassistant/components/somfy/translations/pt-BR.json index 2f3377428707c9..5e658d36102bc8 100644 --- a/homeassistant/components/somfy/translations/pt-BR.json +++ b/homeassistant/components/somfy/translations/pt-BR.json @@ -1,11 +1,13 @@ { "config": { "abort": { - "authorize_url_timeout": "Excedido tempo limite gerando a URL de autoriza\u00e7\u00e3o.", - "missing_configuration": "O componente Somfy n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o." + "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", + "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", + "no_url_available": "N\u00e3o h\u00e1 URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a se\u00e7\u00e3o de ajuda]({docs_url})", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "create_entry": { - "default": "Autenticado com sucesso pela Somfy." + "default": "Autenticado com sucesso" } } } \ No newline at end of file diff --git a/homeassistant/components/somfy/translations/zh-Hant.json b/homeassistant/components/somfy/translations/zh-Hant.json index 71390930e358a8..8dccd6771cb13e 100644 --- a/homeassistant/components/somfy/translations/zh-Hant.json +++ b/homeassistant/components/somfy/translations/zh-Hant.json @@ -4,7 +4,7 @@ "authorize_url_timeout": "\u7522\u751f\u8a8d\u8b49 URL \u6642\u903e\u6642\u3002", "missing_configuration": "\u5143\u4ef6\u5c1a\u672a\u8a2d\u7f6e\uff0c\u8acb\u53c3\u95b1\u6587\u4ef6\u8aaa\u660e\u3002", "no_url_available": "\u6c92\u6709\u53ef\u7528\u7684\u7db2\u5740\u3002\u95dc\u65bc\u6b64\u932f\u8aa4\u66f4\u8a73\u7d30\u8a0a\u606f\uff0c[\u9ede\u9078\u5354\u52a9\u7ae0\u7bc0]({docs_url})", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "create_entry": { "default": "\u5df2\u6210\u529f\u8a8d\u8b49" diff --git a/homeassistant/components/somfy_mylink/translations/pt-BR.json b/homeassistant/components/somfy_mylink/translations/pt-BR.json new file mode 100644 index 00000000000000..f01e9d01465977 --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/pt-BR.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "port": "Porta" + } + } + } + }, + "options": { + "abort": { + "cannot_connect": "Falha ao conectar" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/pt-BR.json b/homeassistant/components/sonarr/translations/pt-BR.json new file mode 100644 index 00000000000000..db1bf2075f9bce --- /dev/null +++ b/homeassistant/components/sonarr/translations/pt-BR.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", + "unknown": "Erro inesperado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "reauth_confirm": { + "title": "Reautenticar Integra\u00e7\u00e3o" + }, + "user": { + "data": { + "api_key": "Chave da API", + "host": "Nome do host", + "port": "Porta", + "ssl": "Usar um certificado SSL", + "verify_ssl": "Verifique o certificado SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/songpal/translations/pt-BR.json b/homeassistant/components/songpal/translations/pt-BR.json index 110e74131217ca..5b5afff290aa36 100644 --- a/homeassistant/components/songpal/translations/pt-BR.json +++ b/homeassistant/components/songpal/translations/pt-BR.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, "error": { "cannot_connect": "Falha ao conectar" } diff --git a/homeassistant/components/sonos/translations/pt-BR.json b/homeassistant/components/sonos/translations/pt-BR.json index f2467135d35559..78407c85e22c6e 100644 --- a/homeassistant/components/sonos/translations/pt-BR.json +++ b/homeassistant/components/sonos/translations/pt-BR.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Nenhum dispositivo Sonos encontrado na rede.", - "single_instance_allowed": "Apenas uma \u00fanica configura\u00e7\u00e3o do Sonos \u00e9 necess\u00e1ria." + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "step": { "confirm": { diff --git a/homeassistant/components/sonos/translations/zh-Hant.json b/homeassistant/components/sonos/translations/zh-Hant.json index 08434f16c15769..26e802b44c7639 100644 --- a/homeassistant/components/sonos/translations/zh-Hant.json +++ b/homeassistant/components/sonos/translations/zh-Hant.json @@ -3,7 +3,7 @@ "abort": { "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e", "not_sonos_device": "\u6240\u767c\u73fe\u7684\u88dd\u7f6e\u4e26\u975e Sonos \u88dd\u7f6e", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "step": { "confirm": { diff --git a/homeassistant/components/speedtestdotnet/translations/pt-BR.json b/homeassistant/components/speedtestdotnet/translations/pt-BR.json new file mode 100644 index 00000000000000..7caf7983714456 --- /dev/null +++ b/homeassistant/components/speedtestdotnet/translations/pt-BR.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "step": { + "user": { + "description": "Deseja iniciar a configura\u00e7\u00e3o?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/speedtestdotnet/translations/zh-Hant.json b/homeassistant/components/speedtestdotnet/translations/zh-Hant.json index e88b4ec39233d1..49b8b0cfb20974 100644 --- a/homeassistant/components/speedtestdotnet/translations/zh-Hant.json +++ b/homeassistant/components/speedtestdotnet/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "wrong_server_id": "\u4f3a\u670d\u5668 ID \u7121\u6548" }, "step": { diff --git a/homeassistant/components/spider/translations/pt-BR.json b/homeassistant/components/spider/translations/pt-BR.json new file mode 100644 index 00000000000000..70df08020b6b06 --- /dev/null +++ b/homeassistant/components/spider/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/spider/translations/zh-Hant.json b/homeassistant/components/spider/translations/zh-Hant.json index ce15c28f47b777..711ed2f62ff0dd 100644 --- a/homeassistant/components/spider/translations/zh-Hant.json +++ b/homeassistant/components/spider/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", diff --git a/homeassistant/components/spotify/translations/pt-BR.json b/homeassistant/components/spotify/translations/pt-BR.json new file mode 100644 index 00000000000000..6858c6371c0768 --- /dev/null +++ b/homeassistant/components/spotify/translations/pt-BR.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "no_url_available": "N\u00e3o h\u00e1 URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a se\u00e7\u00e3o de ajuda]({docs_url})" + }, + "step": { + "reauth_confirm": { + "title": "Reautenticar Integra\u00e7\u00e3o" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/squeezebox/translations/pt-BR.json b/homeassistant/components/squeezebox/translations/pt-BR.json new file mode 100644 index 00000000000000..5e94ace2864042 --- /dev/null +++ b/homeassistant/components/squeezebox/translations/pt-BR.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "edit": { + "data": { + "host": "Nome do host", + "password": "Senha", + "port": "Porta", + "username": "Usu\u00e1rio" + } + }, + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/pt-BR.json b/homeassistant/components/srp_energy/translations/pt-BR.json new file mode 100644 index 00000000000000..790e3e661a3492 --- /dev/null +++ b/homeassistant/components/srp_energy/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/zh-Hant.json b/homeassistant/components/srp_energy/translations/zh-Hant.json index 87bf347795c98a..adbf635100cb60 100644 --- a/homeassistant/components/srp_energy/translations/zh-Hant.json +++ b/homeassistant/components/srp_energy/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", diff --git a/homeassistant/components/steamist/translations/pt-BR.json b/homeassistant/components/steamist/translations/pt-BR.json new file mode 100644 index 00000000000000..685c6dc5319c47 --- /dev/null +++ b/homeassistant/components/steamist/translations/pt-BR.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "cannot_connect": "Falha ao conectar", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "not_steamist_device": "N\u00e3o \u00e9 um dispositivo de vaporizador" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "flow_title": "{name} ({ipaddress})", + "step": { + "discovery_confirm": { + "description": "Deseja configurar {name} ( {ipaddress} )?" + }, + "pick_device": { + "data": { + "device": "Dispositivo" + } + }, + "user": { + "data": { + "host": "Nome do host" + }, + "description": "Se voc\u00ea deixar o host vazio, a descoberta ser\u00e1 usada para localizar dispositivos." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/stookalert/translations/pt-BR.json b/homeassistant/components/stookalert/translations/pt-BR.json new file mode 100644 index 00000000000000..d252c078a2c31a --- /dev/null +++ b/homeassistant/components/stookalert/translations/pt-BR.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/pt-BR.json b/homeassistant/components/subaru/translations/pt-BR.json new file mode 100644 index 00000000000000..3ccfca2f59f961 --- /dev/null +++ b/homeassistant/components/subaru/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "cannot_connect": "Falha ao conectar" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/surepetcare/translations/pt-BR.json b/homeassistant/components/surepetcare/translations/pt-BR.json index c41610abb323cc..d86aef5d51d73a 100644 --- a/homeassistant/components/surepetcare/translations/pt-BR.json +++ b/homeassistant/components/surepetcare/translations/pt-BR.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 configurado" + "already_configured": "A conta j\u00e1 foi configurada" }, "error": { "cannot_connect": "Falha ao conectar", diff --git a/homeassistant/components/switch/translations/pt-BR.json b/homeassistant/components/switch/translations/pt-BR.json index a3dcc96c80b3ff..d00cce3da9f341 100644 --- a/homeassistant/components/switch/translations/pt-BR.json +++ b/homeassistant/components/switch/translations/pt-BR.json @@ -1,4 +1,21 @@ { + "device_automation": { + "action_type": { + "toggle": "Alternar {nome_da_entidade}", + "turn_off": "Desligar {nome_da_entidade}", + "turn_on": "Ligar {nome_da_entidade}" + }, + "condition_type": { + "is_off": "{entity_name} est\u00e1 desligado", + "is_on": "{entity_name} est\u00e1 ligado" + }, + "trigger_type": { + "changed_states": "{entity_name} ligado ou desligado", + "toggled": "{entity_name} ligado ou desligado", + "turned_off": "{entity_name} desligado", + "turned_on": "{entity_name} ligado" + } + }, "state": { "_": { "off": "Desligado", diff --git a/homeassistant/components/switchbot/translations/el.json b/homeassistant/components/switchbot/translations/el.json index 9f795e36734f03..c74dcf759efa07 100644 --- a/homeassistant/components/switchbot/translations/el.json +++ b/homeassistant/components/switchbot/translations/el.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "no_unconfigured_devices": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03bc\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2." + "no_unconfigured_devices": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03bc\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2.", + "switchbot_unsupported_type": "\u039c\u03b7 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03bf\u03c2 \u03c4\u03cd\u03c0\u03bf\u03c2 Switchbot." }, "flow_title": "{name}", "step": { diff --git a/homeassistant/components/switchbot/translations/pt-BR.json b/homeassistant/components/switchbot/translations/pt-BR.json new file mode 100644 index 00000000000000..c63ab2a1f27e9f --- /dev/null +++ b/homeassistant/components/switchbot/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured_device": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "name": "Nome", + "password": "Senha" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/switcher_kis/translations/pt-BR.json b/homeassistant/components/switcher_kis/translations/pt-BR.json new file mode 100644 index 00000000000000..d5efbb90261324 --- /dev/null +++ b/homeassistant/components/switcher_kis/translations/pt-BR.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "step": { + "confirm": { + "description": "Deseja iniciar a configura\u00e7\u00e3o?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/switcher_kis/translations/zh-Hant.json b/homeassistant/components/switcher_kis/translations/zh-Hant.json index 90c98e491dfea4..cfd20d603cba17 100644 --- a/homeassistant/components/switcher_kis/translations/zh-Hant.json +++ b/homeassistant/components/switcher_kis/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "step": { "confirm": { diff --git a/homeassistant/components/syncthing/translations/pt-BR.json b/homeassistant/components/syncthing/translations/pt-BR.json new file mode 100644 index 00000000000000..451ec4459ebe00 --- /dev/null +++ b/homeassistant/components/syncthing/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "url": "URL", + "verify_ssl": "Verifique o certificado SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/syncthru/translations/pt-BR.json b/homeassistant/components/syncthru/translations/pt-BR.json new file mode 100644 index 00000000000000..7164e5bb0831c3 --- /dev/null +++ b/homeassistant/components/syncthru/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "step": { + "confirm": { + "data": { + "name": "Nome" + } + }, + "user": { + "data": { + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/pt-BR.json b/homeassistant/components/synology_dsm/translations/pt-BR.json index e633eb9128d409..f139d3fa5f5ea5 100644 --- a/homeassistant/components/synology_dsm/translations/pt-BR.json +++ b/homeassistant/components/synology_dsm/translations/pt-BR.json @@ -1,8 +1,14 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "otp_failed": "Falha na autentica\u00e7\u00e3o em duas etapas, tente novamente com um novo c\u00f3digo", - "unknown": "Erro desconhecido: verifique os logs para obter mais detalhes" + "unknown": "Erro inesperado" }, "flow_title": "Synology DSM {name} ({host})", "step": { @@ -13,10 +19,38 @@ }, "link": { "data": { - "ssl": "Use SSL/TLS para conectar-se ao seu NAS" + "password": "Senha", + "port": "Porta", + "ssl": "Usar um certificado SSL", + "username": "Usu\u00e1rio", + "verify_ssl": "Verifique o certificado SSL" }, "description": "Voc\u00ea quer configurar o {name} ({host})?", "title": "Synology DSM" + }, + "reauth": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + }, + "title": "Synology DSM Reautenticar Integra\u00e7\u00e3o" + }, + "reauth_confirm": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + }, + "title": "Synology DSM Reautenticar Integra\u00e7\u00e3o" + }, + "user": { + "data": { + "host": "Nome do host", + "password": "Senha", + "port": "Porta", + "ssl": "Usar um certificado SSL", + "username": "Usu\u00e1rio", + "verify_ssl": "Verifique o certificado SSL" + } } } }, @@ -24,7 +58,8 @@ "step": { "init": { "data": { - "scan_interval": "Minutos entre os escaneamentos" + "scan_interval": "Minutos entre os escaneamentos", + "snap_profile_type": "N\u00edvel de qualidade dos instant\u00e2neos da c\u00e2mera (0: alto 1: m\u00e9dio 2: baixo)" } } } diff --git a/homeassistant/components/system_bridge/translations/pt-BR.json b/homeassistant/components/system_bridge/translations/pt-BR.json new file mode 100644 index 00000000000000..b928fae8a32891 --- /dev/null +++ b/homeassistant/components/system_bridge/translations/pt-BR.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", + "unknown": "Erro inesperado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "authenticate": { + "data": { + "api_key": "Chave da API" + } + }, + "user": { + "data": { + "api_key": "Chave da API", + "host": "Nome do host", + "port": "Porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tado/translations/pt-BR.json b/homeassistant/components/tado/translations/pt-BR.json index af32cb3c3a6178..9038912d008bcc 100644 --- a/homeassistant/components/tado/translations/pt-BR.json +++ b/homeassistant/components/tado/translations/pt-BR.json @@ -1,16 +1,20 @@ { "config": { "abort": { - "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "error": { - "cannot_connect": "Falha ao conectar, tente novamente", + "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "no_homes": "N\u00e3o h\u00e1 casas vinculadas a esta conta Tado.", "unknown": "Erro inesperado" }, "step": { "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + }, "title": "Conecte-se \u00e0 sua conta Tado" } } diff --git a/homeassistant/components/tailscale/translations/el.json b/homeassistant/components/tailscale/translations/el.json new file mode 100644 index 00000000000000..1b4b0047b66ea2 --- /dev/null +++ b/homeassistant/components/tailscale/translations/el.json @@ -0,0 +1,15 @@ +{ + "config": { + "step": { + "reauth_confirm": { + "description": "\u03a4\u03b1 \u03ba\u03bf\u03c5\u03c0\u03cc\u03bd\u03b9\u03b1 API \u03c4\u03b7\u03c2 Tailscale \u03b9\u03c3\u03c7\u03cd\u03bf\u03c5\u03bd \u03b3\u03b9\u03b1 90 \u03b7\u03bc\u03ad\u03c1\u03b5\u03c2. \u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03bd\u03ad\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03c4\u03b7\u03c2 Tailscale \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://login.tailscale.com/admin/settings/authkeys." + }, + "user": { + "data": { + "tailnet": "Tailnet" + }, + "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af\u03c4\u03b5 \u03bc\u03b5 \u03c4\u03b7\u03bd Tailscale \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03c3\u03c4\u03bf https://login.tailscale.com/admin/settings/authkeys.\n\n\u03a4\u03bf Tailnet \u03b5\u03af\u03bd\u03b1\u03b9 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 \u03c3\u03b1\u03c2 Tailscale. \u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c4\u03bf \u03b2\u03c1\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd \u03b5\u03c0\u03ac\u03bd\u03c9 \u03b1\u03c1\u03b9\u03c3\u03c4\u03b5\u03c1\u03ae \u03b3\u03c9\u03bd\u03af\u03b1 \u03c3\u03c4\u03bf\u03bd \u03c0\u03af\u03bd\u03b1\u03ba\u03b1 \u03b4\u03b9\u03b1\u03c7\u03b5\u03af\u03c1\u03b9\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 Tailscale (\u03b4\u03af\u03c0\u03bb\u03b1 \u03c3\u03c4\u03bf \u03bb\u03bf\u03b3\u03cc\u03c4\u03c5\u03c0\u03bf \u03c4\u03bf\u03c5 Tailscale)." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tailscale/translations/pt-BR.json b/homeassistant/components/tailscale/translations/pt-BR.json new file mode 100644 index 00000000000000..ddbdda9d5a6d35 --- /dev/null +++ b/homeassistant/components/tailscale/translations/pt-BR.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Chave da API" + }, + "description": "Os tokens da API Tailscale s\u00e3o v\u00e1lidos por 90 dias. Voc\u00ea pode criar uma nova chave de API Tailscale em https://login.tailscale.com/admin/settings/authkeys." + }, + "user": { + "data": { + "api_key": "Chave da API", + "tailnet": "Tailnet" + }, + "description": "Para autenticar com o Tailscale, voc\u00ea precisar\u00e1 criar uma chave de API em https://login.tailscale.com/admin/settings/authkeys. \n\nTailnet \u00e9 o nome da sua rede Tailscale. Voc\u00ea pode encontr\u00e1-lo no canto superior esquerdo no painel de administra\u00e7\u00e3o do Tailscale (ao lado do logotipo do Tailscale)." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tasmota/translations/pt-BR.json b/homeassistant/components/tasmota/translations/pt-BR.json new file mode 100644 index 00000000000000..9ab59f40649f29 --- /dev/null +++ b/homeassistant/components/tasmota/translations/pt-BR.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tasmota/translations/zh-Hant.json b/homeassistant/components/tasmota/translations/zh-Hant.json index 477eb0ffa9cde0..3a11beed839492 100644 --- a/homeassistant/components/tasmota/translations/zh-Hant.json +++ b/homeassistant/components/tasmota/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "invalid_discovery_topic": "\u63a2\u7d22\u4e3b\u984c prefix \u7121\u6548\u3002" diff --git a/homeassistant/components/tellduslive/translations/pt-BR.json b/homeassistant/components/tellduslive/translations/pt-BR.json index 036af4e1c45a71..7965c3083d28ea 100644 --- a/homeassistant/components/tellduslive/translations/pt-BR.json +++ b/homeassistant/components/tellduslive/translations/pt-BR.json @@ -1,8 +1,12 @@ { "config": { "abort": { - "authorize_url_timeout": "Tempo limite de gera\u00e7\u00e3o de url de autoriza\u00e7\u00e3o.", - "unknown": "Ocorreu um erro desconhecido" + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado", + "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", + "unknown": "Erro inesperado" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, "step": { "auth": { @@ -11,7 +15,7 @@ }, "user": { "data": { - "host": "Host" + "host": "Nome do host" }, "description": "Vazio", "title": "Escolha o ponto final." diff --git a/homeassistant/components/tesla_wall_connector/translations/pt-BR.json b/homeassistant/components/tesla_wall_connector/translations/pt-BR.json new file mode 100644 index 00000000000000..4f05163182cd52 --- /dev/null +++ b/homeassistant/components/tesla_wall_connector/translations/pt-BR.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "Configurar op\u00e7\u00f5es para o Tesla Wall Connector" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tibber/translations/pt-BR.json b/homeassistant/components/tibber/translations/pt-BR.json new file mode 100644 index 00000000000000..2e1206f48075a8 --- /dev/null +++ b/homeassistant/components/tibber/translations/pt-BR.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_access_token": "Token de acesso inv\u00e1lido" + }, + "step": { + "user": { + "data": { + "access_token": "Token de acesso" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tile/translations/pt-BR.json b/homeassistant/components/tile/translations/pt-BR.json new file mode 100644 index 00000000000000..33b2e29d518205 --- /dev/null +++ b/homeassistant/components/tile/translations/pt-BR.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "Senha" + }, + "title": "Re-autenticar o bloco" + }, + "user": { + "data": { + "password": "Senha" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tolo/translations/el.json b/homeassistant/components/tolo/translations/el.json index f214d3d7cfe9c0..26e05764d2e456 100644 --- a/homeassistant/components/tolo/translations/el.json +++ b/homeassistant/components/tolo/translations/el.json @@ -1,5 +1,6 @@ { "config": { + "flow_title": "{name}", "step": { "user": { "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 TOLO Sauna." diff --git a/homeassistant/components/tolo/translations/pt-BR.json b/homeassistant/components/tolo/translations/pt-BR.json new file mode 100644 index 00000000000000..4c7ead4c7d6966 --- /dev/null +++ b/homeassistant/components/tolo/translations/pt-BR.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "confirm": { + "description": "Deseja iniciar a configura\u00e7\u00e3o?" + }, + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/toon/translations/pt-BR.json b/homeassistant/components/toon/translations/pt-BR.json index 2e12ac49a8aae8..347c3d8e88ce7d 100644 --- a/homeassistant/components/toon/translations/pt-BR.json +++ b/homeassistant/components/toon/translations/pt-BR.json @@ -1,7 +1,10 @@ { "config": { "abort": { - "no_agreements": "Esta conta n\u00e3o possui exibi\u00e7\u00f5es Toon." + "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", + "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", + "no_agreements": "Esta conta n\u00e3o possui exibi\u00e7\u00f5es Toon.", + "no_url_available": "N\u00e3o h\u00e1 URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a se\u00e7\u00e3o de ajuda]({docs_url})" } } } \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/pt-BR.json b/homeassistant/components/totalconnect/translations/pt-BR.json index 432a49cacf6751..7a58875b9f2345 100644 --- a/homeassistant/components/totalconnect/translations/pt-BR.json +++ b/homeassistant/components/totalconnect/translations/pt-BR.json @@ -1,11 +1,20 @@ { "config": { "abort": { - "already_configured": "Conta j\u00e1 configurada" + "already_configured": "A conta j\u00e1 foi configurada", + "no_locations": "Nenhum local est\u00e1 dispon\u00edvel para este usu\u00e1rio, verifique as configura\u00e7\u00f5es do TotalConnect", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, "step": { + "reauth_confirm": { + "title": "Reautenticar Integra\u00e7\u00e3o" + }, "user": { "data": { + "password": "Senha", "username": "Usu\u00e1rio" }, "title": "Total Connect" diff --git a/homeassistant/components/tplink/translations/pt-BR.json b/homeassistant/components/tplink/translations/pt-BR.json index f4852405726a1b..1977a43636518f 100644 --- a/homeassistant/components/tplink/translations/pt-BR.json +++ b/homeassistant/components/tplink/translations/pt-BR.json @@ -1,12 +1,25 @@ { "config": { "abort": { - "no_devices_found": "Nenhum dispositivo TP-Link encontrado na rede.", - "single_instance_allowed": "Apenas uma \u00fanica configura\u00e7\u00e3o \u00e9 necess\u00e1ria." + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "flow_title": "{nome} {modelo} ({host})", "step": { "confirm": { "description": "Deseja configurar dispositivos inteligentes TP-Link?" + }, + "discovery_confirm": { + "description": "Deseja configurar {name} {model} ({host})?" + }, + "user": { + "data": { + "host": "Nome do host" + } } } } diff --git a/homeassistant/components/tplink/translations/zh-Hant.json b/homeassistant/components/tplink/translations/zh-Hant.json index 153783b1b9094f..bfca7643b329ca 100644 --- a/homeassistant/components/tplink/translations/zh-Hant.json +++ b/homeassistant/components/tplink/translations/zh-Hant.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557" diff --git a/homeassistant/components/traccar/translations/pt-BR.json b/homeassistant/components/traccar/translations/pt-BR.json index eaaa5717709ffc..827c8e190661a9 100644 --- a/homeassistant/components/traccar/translations/pt-BR.json +++ b/homeassistant/components/traccar/translations/pt-BR.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "cloud_not_connected": "N\u00e3o conectado ao Home Assistant Cloud.", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, "create_entry": { "default": "Para enviar eventos ao Home Assistant, voc\u00ea precisar\u00e1 configurar o recurso de webhook no Traccar. \n\n Use o seguinte URL: ` {webhook_url} ` \n\n Veja [a documenta\u00e7\u00e3o] ({docs_url}) para mais detalhes." }, diff --git a/homeassistant/components/traccar/translations/zh-Hant.json b/homeassistant/components/traccar/translations/zh-Hant.json index 7a4e9b8a02b714..aa4a250041ea04 100644 --- a/homeassistant/components/traccar/translations/zh-Hant.json +++ b/homeassistant/components/traccar/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "cloud_not_connected": "\u672a\u9023\u7dda\u81f3 Home Assistant \u96f2\u670d\u52d9\u3002", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "webhook_not_internet_accessible": "Home Assistant \u5be6\u9ad4\u5fc5\u9808\u8981\u80fd\u5f9e\u7db2\u969b\u7db2\u8def\u5b58\u53d6\u65b9\u80fd\u63a5\u6536 Webhook \u8a0a\u606f\u3002" }, "create_entry": { diff --git a/homeassistant/components/tractive/translations/pt-BR.json b/homeassistant/components/tractive/translations/pt-BR.json new file mode 100644 index 00000000000000..e9a14f4323825c --- /dev/null +++ b/homeassistant/components/tractive/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Senha" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tractive/translations/sensor.el.json b/homeassistant/components/tractive/translations/sensor.el.json new file mode 100644 index 00000000000000..2ba41d15fe813b --- /dev/null +++ b/homeassistant/components/tractive/translations/sensor.el.json @@ -0,0 +1,10 @@ +{ + "state": { + "tractive__tracker_state": { + "not_reporting": "\u03a7\u03c9\u03c1\u03af\u03c2 \u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac", + "operational": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03b9\u03ba\u03cc", + "system_shutdown_user": "\u0391\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c3\u03c5\u03c3\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7\u03c2", + "system_startup": "\u0395\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7 \u03c3\u03c5\u03c3\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tractive/translations/sensor.pt-BR.json b/homeassistant/components/tractive/translations/sensor.pt-BR.json new file mode 100644 index 00000000000000..4d3efb6fa67723 --- /dev/null +++ b/homeassistant/components/tractive/translations/sensor.pt-BR.json @@ -0,0 +1,10 @@ +{ + "state": { + "tractive__tracker_state": { + "not_reporting": "N\u00e3o relatando", + "operational": "Operacional", + "system_shutdown_user": "Usu\u00e1rio de desligamento do sistema", + "system_startup": "Inicializa\u00e7\u00e3o do sistema" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tradfri/translations/pt-BR.json b/homeassistant/components/tradfri/translations/pt-BR.json index b1c853f5f2b016..702aa6e33deeab 100644 --- a/homeassistant/components/tradfri/translations/pt-BR.json +++ b/homeassistant/components/tradfri/translations/pt-BR.json @@ -1,18 +1,18 @@ { "config": { "abort": { - "already_configured": "Bridge j\u00e1 est\u00e1 configurado", - "already_in_progress": "A configura\u00e7\u00e3o de ponte j\u00e1 est\u00e1 em andamento." + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento" }, "error": { - "cannot_connect": "N\u00e3o \u00e9 poss\u00edvel conectar-se ao gateway.", + "cannot_connect": "Falha ao conectar", "invalid_key": "Falha ao registrar-se com a chave fornecida. Se isso continuar acontecendo, tente reiniciar o gateway.", "timeout": "Excedido tempo limite para validar c\u00f3digo" }, "step": { "auth": { "data": { - "host": "Hospedeiro", + "host": "Nome do host", "security_code": "C\u00f3digo de seguran\u00e7a" }, "description": "Voc\u00ea pode encontrar o c\u00f3digo de seguran\u00e7a na parte de tr\u00e1s do seu gateway.", diff --git a/homeassistant/components/trafikverket_weatherstation/translations/el.json b/homeassistant/components/trafikverket_weatherstation/translations/el.json index 28ec7eb54f160a..32688c432c4609 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/el.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/el.json @@ -1,7 +1,16 @@ { "config": { "error": { + "invalid_station": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03bc\u03b5\u03c4\u03b5\u03c9\u03c1\u03bf\u03bb\u03bf\u03b3\u03b9\u03ba\u03cc\u03c2 \u03c3\u03c4\u03b1\u03b8\u03bc\u03cc\u03c2 \u03bc\u03b5 \u03c4\u03bf \u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1", "more_stations": "\u0392\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03bf\u03af \u03bc\u03b5\u03c4\u03b5\u03c9\u03c1\u03bf\u03bb\u03bf\u03b3\u03b9\u03ba\u03bf\u03af \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03af \u03bc\u03b5 \u03c4\u03bf \u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1" + }, + "step": { + "user": { + "data": { + "conditions": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03bf\u03cd\u03bc\u03b5\u03bd\u03b5\u03c2 \u03c3\u03c5\u03bd\u03b8\u03ae\u03ba\u03b5\u03c2", + "station": "\u03a3\u03c4\u03b1\u03b8\u03bc\u03cc\u03c2/\u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/trafikverket_weatherstation/translations/pt-BR.json b/homeassistant/components/trafikverket_weatherstation/translations/pt-BR.json new file mode 100644 index 00000000000000..f73ab8555da711 --- /dev/null +++ b/homeassistant/components/trafikverket_weatherstation/translations/pt-BR.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "invalid_station": "N\u00e3o foi poss\u00edvel encontrar uma esta\u00e7\u00e3o meteorol\u00f3gica com o nome especificado", + "more_stations": "Encontrado v\u00e1rias esta\u00e7\u00f5es meteorol\u00f3gicas com o nome especificado" + }, + "step": { + "user": { + "data": { + "api_key": "Chave da API", + "conditions": "Condi\u00e7\u00f5es monitoradas", + "name": "Usu\u00e1rio", + "station": "Esta\u00e7\u00e3o" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/transmission/translations/pt-BR.json b/homeassistant/components/transmission/translations/pt-BR.json index fdc42bcf303047..e9edf1d94e25b2 100644 --- a/homeassistant/components/transmission/translations/pt-BR.json +++ b/homeassistant/components/transmission/translations/pt-BR.json @@ -1,16 +1,17 @@ { "config": { "abort": { - "already_configured": "O host j\u00e1 est\u00e1 configurado." + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "error": { - "cannot_connect": "N\u00e3o foi poss\u00edvel conectar ao host", - "name_exists": "O nome j\u00e1 existe" + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "name_exists": "O Nome j\u00e1 existe" }, "step": { "user": { "data": { - "host": "Host", + "host": "Nome do host", "name": "Nome", "password": "Senha", "port": "Porta", diff --git a/homeassistant/components/tuya/translations/el.json b/homeassistant/components/tuya/translations/el.json index 2fb4b8cf7204f2..bfb955dca5f76f 100644 --- a/homeassistant/components/tuya/translations/el.json +++ b/homeassistant/components/tuya/translations/el.json @@ -2,6 +2,17 @@ "config": { "flow_title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Tuya", "step": { + "login": { + "data": { + "access_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "access_secret": "\u039c\u03c5\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "country_code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c7\u03ce\u03c1\u03b1\u03c2", + "endpoint": "\u0396\u03ce\u03bd\u03b7 \u03b4\u03b9\u03b1\u03b8\u03b5\u03c3\u03b9\u03bc\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "tuya_app_type": "Mobile App", + "username": "\u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2" + } + }, "user": { "data": { "country_code": "\u03a7\u03ce\u03c1\u03b1", @@ -21,6 +32,13 @@ "temp_step_override": "\u0392\u03ae\u03bc\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03c3\u03c4\u03cc\u03c7\u03bf\u03c5" }, "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 Tuya" + }, + "init": { + "data": { + "query_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03b4\u03b7\u03bc\u03bf\u03c3\u03ba\u03cc\u03c0\u03b7\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03c3\u03b5 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1" + }, + "description": "\u039c\u03b7\u03bd \u03bf\u03c1\u03af\u03b6\u03b5\u03c4\u03b5 \u03c0\u03bf\u03bb\u03cd \u03c7\u03b1\u03bc\u03b7\u03bb\u03ad\u03c2 \u03c4\u03b9\u03bc\u03ad\u03c2 \u03b4\u03b9\u03b1\u03c3\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2 \u03b4\u03b7\u03bc\u03bf\u03c3\u03ba\u03bf\u03c0\u03ae\u03c3\u03b5\u03c9\u03bd, \u03b1\u03bb\u03bb\u03b9\u03ce\u03c2 \u03bf\u03b9 \u03ba\u03bb\u03ae\u03c3\u03b5\u03b9\u03c2 \u03b8\u03b1 \u03b1\u03c0\u03bf\u03c4\u03cd\u03c7\u03bf\u03c5\u03bd \u03ba\u03b1\u03b9 \u03b8\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03b7\u03b8\u03b5\u03af \u03bc\u03ae\u03bd\u03c5\u03bc\u03b1 \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1\u03c4\u03bf\u03c2 \u03c3\u03c4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03ba\u03b1\u03c4\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2.", + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd Tuya" } } } diff --git a/homeassistant/components/tuya/translations/pt-BR.json b/homeassistant/components/tuya/translations/pt-BR.json index d9159ce954da4e..bffe92b154fdf5 100644 --- a/homeassistant/components/tuya/translations/pt-BR.json +++ b/homeassistant/components/tuya/translations/pt-BR.json @@ -1,21 +1,36 @@ { "config": { "abort": { - "cannot_connect": "Falhou ao conectar", - "invalid_auth": "{%component::tuya::config::error::invalid_auth%}", - "single_instance_allowed": "J\u00e1 configurado. S\u00f3 \u00e9 poss\u00edvel uma \u00fanica configura\u00e7\u00e3o." + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "error": { - "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "login_error": "Erro de login ({code}): {msg}" }, "flow_title": "Configura\u00e7\u00e3o Tuya", "step": { + "login": { + "data": { + "access_id": "Access ID", + "access_secret": "Access Secret", + "country_code": "C\u00f3digo do pa\u00eds", + "endpoint": "Zona de disponibilidade", + "password": "Senha", + "tuya_app_type": "Aplicativo m\u00f3vel", + "username": "Conta" + }, + "description": "Insira sua credencial Tuya", + "title": "Tuya" + }, "user": { "data": { "country_code": "Pa\u00eds", "password": "Senha", "platform": "O aplicativo onde sua conta \u00e9 registrada", "region": "Regi\u00e3o", + "tuya_project_type": "Tipo de projeto de nuvem Tuya", "username": "Nome de usu\u00e1rio" }, "description": "Digite sua credencial Tuya.", @@ -24,6 +39,9 @@ } }, "options": { + "abort": { + "cannot_connect": "Falha ao conectar" + }, "error": { "dev_multi_type": "V\u00e1rios dispositivos selecionados para configurar devem ser do mesmo tipo", "dev_not_config": "Tipo de dispositivo n\u00e3o configur\u00e1vel", diff --git a/homeassistant/components/tuya/translations/select.pt-BR.json b/homeassistant/components/tuya/translations/select.pt-BR.json index 7d3df1b46aa4a9..06353ca7846384 100644 --- a/homeassistant/components/tuya/translations/select.pt-BR.json +++ b/homeassistant/components/tuya/translations/select.pt-BR.json @@ -1,11 +1,82 @@ { "state": { + "tuya__basic_anti_flickr": { + "0": "Desativado", + "1": "50Hz", + "2": "60Hz" + }, + "tuya__basic_nightvision": { + "0": "Autom\u00e1tico", + "1": "Desligado", + "2": "Ligado" + }, + "tuya__curtain_mode": { + "morning": "Manh\u00e3", + "night": "Noite" + }, + "tuya__curtain_motor_mode": { + "back": "Para tr\u00e1s", + "forward": "Para frente" + }, + "tuya__decibel_sensitivity": { + "0": "Baixa sensibilidade", + "1": "Alta sensibilidade" + }, + "tuya__fan_angle": { + "30": "30\u00b0", + "60": "60\u00b0", + "90": "90\u00b0" + }, + "tuya__fingerbot_mode": { + "click": "Pulsador", + "switch": "Interruptor" + }, + "tuya__led_type": { + "halogen": "Halog\u00eanio", + "incandescent": "Incandescente", + "led": "LED" + }, + "tuya__light_mode": { + "none": "Desligado", + "pos": "Indique a localiza\u00e7\u00e3o do interruptor", + "relay": "Indicar o estado de ligar/desligar" + }, "tuya__relay_status": { "last": "Lembre-se do \u00faltimo estado", - "memory": "Lembre-se do \u00faltimo estado" + "memory": "Lembre-se do \u00faltimo estado", + "off": "Desligado", + "on": "Ligado", + "power_off": "Desligado", + "power_on": "Ligado" + }, + "tuya__vacuum_cistern": { + "closed": "Fechado", + "high": "Alto", + "low": "Baixo", + "middle": "M\u00e9dio" + }, + "tuya__vacuum_collection": { + "large": "Grande", + "middle": "M\u00e9dio", + "small": "Pequeno" }, "tuya__vacuum_mode": { - "point": "Ponto" + "bow": "Arco", + "chargego": "Voltar para a base", + "left_bow": "Curvar \u00e0 esquerda", + "left_spiral": "Espiral Esquerda", + "mop": "Mop", + "part": "Parte", + "partial_bow": "Curvar Parcialmente", + "pick_zone": "Escolher Zona", + "point": "Ponto", + "pose": "Pose", + "random": "Aleat\u00f3rio", + "right_bow": "Curvar \u00e0 direita", + "right_spiral": "Espiral direita", + "single": "\u00danico", + "standby": "Aguarde", + "zone": "\u00c1rea" } } } \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/zh-Hant.json b/homeassistant/components/tuya/translations/zh-Hant.json index f99a4781fdcb62..b905eb0c1e39e6 100644 --- a/homeassistant/components/tuya/translations/zh-Hant.json +++ b/homeassistant/components/tuya/translations/zh-Hant.json @@ -3,7 +3,7 @@ "abort": { "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", diff --git a/homeassistant/components/twentemilieu/translations/pt-BR.json b/homeassistant/components/twentemilieu/translations/pt-BR.json index cc71ffde0e8a13..943b46849b6a2b 100644 --- a/homeassistant/components/twentemilieu/translations/pt-BR.json +++ b/homeassistant/components/twentemilieu/translations/pt-BR.json @@ -1,6 +1,10 @@ { "config": { + "abort": { + "already_configured": "Localiza\u00e7\u00e3o j\u00e1 est\u00e1 configurada" + }, "error": { + "cannot_connect": "Falha ao conectar", "invalid_address": "Endere\u00e7o n\u00e3o encontrado na \u00e1rea de servi\u00e7o de Twente Milieu." }, "step": { diff --git a/homeassistant/components/twilio/translations/pt-BR.json b/homeassistant/components/twilio/translations/pt-BR.json index 9c474ca31b73bb..68e068c0feeb56 100644 --- a/homeassistant/components/twilio/translations/pt-BR.json +++ b/homeassistant/components/twilio/translations/pt-BR.json @@ -1,11 +1,15 @@ { "config": { + "abort": { + "cloud_not_connected": "N\u00e3o conectado ao Home Assistant Cloud.", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, "create_entry": { "default": "Para enviar eventos para o Home Assistant, voc\u00ea precisar\u00e1 configurar [Webhooks com Twilio] ( {twilio_url} ). \n\n Preencha as seguintes informa\u00e7\u00f5es: \n\n - URL: ` {webhook_url} ` \n - M\u00e9todo: POST \n - Tipo de Conte\u00fado: application / x-www-form-urlencoded \n\n Veja [a documenta\u00e7\u00e3o] ( {docs_url} ) sobre como configurar automa\u00e7\u00f5es para manipular dados de entrada." }, "step": { "user": { - "description": "Tem certeza de que deseja configurar o Twilio?", + "description": "Deseja iniciar a configura\u00e7\u00e3o?", "title": "Configurar o Twilio Webhook" } } diff --git a/homeassistant/components/twilio/translations/zh-Hant.json b/homeassistant/components/twilio/translations/zh-Hant.json index 59a8b4cb52f481..ae5ddf7549e29f 100644 --- a/homeassistant/components/twilio/translations/zh-Hant.json +++ b/homeassistant/components/twilio/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "cloud_not_connected": "\u672a\u9023\u7dda\u81f3 Home Assistant \u96f2\u670d\u52d9\u3002", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "webhook_not_internet_accessible": "Home Assistant \u5be6\u9ad4\u5fc5\u9808\u8981\u80fd\u5f9e\u7db2\u969b\u7db2\u8def\u5b58\u53d6\u65b9\u80fd\u63a5\u6536 Webhook \u8a0a\u606f\u3002" }, "create_entry": { diff --git a/homeassistant/components/twinkly/translations/pt-BR.json b/homeassistant/components/twinkly/translations/pt-BR.json new file mode 100644 index 00000000000000..e35968d4e8978e --- /dev/null +++ b/homeassistant/components/twinkly/translations/pt-BR.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "device_exists": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/translations/pt-BR.json b/homeassistant/components/unifi/translations/pt-BR.json index 67b39f07f66a01..a0bf047d8278ae 100644 --- a/homeassistant/components/unifi/translations/pt-BR.json +++ b/homeassistant/components/unifi/translations/pt-BR.json @@ -1,22 +1,23 @@ { "config": { "abort": { - "already_configured": "O site de controle j\u00e1 est\u00e1 configurado" + "already_configured": "O site de controle j\u00e1 est\u00e1 configurado", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { - "faulty_credentials": "Credenciais do usu\u00e1rio inv\u00e1lidas", - "service_unavailable": "Servi\u00e7o indispon\u00edvel", + "faulty_credentials": "Autentica\u00e7\u00e3o inv\u00e1lida", + "service_unavailable": "Falha ao conectar", "unknown_client_mac": "Nenhum cliente dispon\u00edvel nesse endere\u00e7o MAC" }, "step": { "user": { "data": { - "host": "Host", + "host": "Nome do host", "password": "Senha", "port": "Porta", "site": "ID do site", "username": "Usu\u00e1rio", - "verify_ssl": "Controlador usando certificado apropriado" + "verify_ssl": "Verifique o certificado SSL" }, "title": "Configurar o Controlador UniFi" } diff --git a/homeassistant/components/unifiprotect/translations/pt-BR.json b/homeassistant/components/unifiprotect/translations/pt-BR.json index 26523104070d55..9fe15726de7db3 100644 --- a/homeassistant/components/unifiprotect/translations/pt-BR.json +++ b/homeassistant/components/unifiprotect/translations/pt-BR.json @@ -1,17 +1,53 @@ { "config": { "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "discovery_started": "Descoberta iniciada" }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, "flow_title": "{nome} ({ip_address})", "step": { "discovery_confirm": { "data": { "password": "Senha", "username": "Usu\u00e1rio", - "verify_ssl": "Verificar certificado SSL" + "verify_ssl": "Verifique o certificado SSL" + }, + "description": "Deseja configurar {name} ({ip_address})?", + "title": "Descoberta UniFi Protect" + }, + "reauth_confirm": { + "data": { + "host": "IP/Host do Servidor UniFi Protect", + "password": "Senha", + "port": "Porta", + "username": "Usu\u00e1rio" + }, + "title": "Reautentica\u00e7\u00e3o UniFi Protect" + }, + "user": { + "data": { + "host": "Nome do host", + "password": "Senha", + "port": "Porta", + "username": "Usu\u00e1rio", + "verify_ssl": "Verifique o certificado SSL" }, - "description": "Deseja configurar {name} ({ip_address})?" + "description": "Voc\u00ea precisar\u00e1 de um usu\u00e1rio local criado no console do sistema operacional UniFi para fazer login. Usu\u00e1rios da Ubiquiti Cloud n\u00e3o funcionar\u00e3o. Para mais informa\u00e7\u00f5es: {local_user_documentation_url}", + "title": "Configura\u00e7\u00e3o do UniFi Protect" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "disable_rtsp": "Desativar o fluxo RTSP" + } } } } diff --git a/homeassistant/components/upb/translations/pt-BR.json b/homeassistant/components/upb/translations/pt-BR.json index 093611b233140f..03f403b7936483 100644 --- a/homeassistant/components/upb/translations/pt-BR.json +++ b/homeassistant/components/upb/translations/pt-BR.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "error": { - "unknown": "Erro inesperado." + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" }, "step": { "user": { diff --git a/homeassistant/components/upcloud/translations/pt-BR.json b/homeassistant/components/upcloud/translations/pt-BR.json new file mode 100644 index 00000000000000..d905975f78d5fa --- /dev/null +++ b/homeassistant/components/upcloud/translations/pt-BR.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/pt-BR.json b/homeassistant/components/upnp/translations/pt-BR.json index 325865ba87eab6..3258117a145d8f 100644 --- a/homeassistant/components/upnp/translations/pt-BR.json +++ b/homeassistant/components/upnp/translations/pt-BR.json @@ -1,9 +1,9 @@ { "config": { "abort": { - "already_configured": "UPnP / IGD j\u00e1 est\u00e1 configurado", + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "incomplete_discovery": "Descoberta incompleta", - "no_devices_found": "Nenhum dispositivo UPnP/IGD encontrado na rede." + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" }, "step": { "user": { diff --git a/homeassistant/components/uptimerobot/translations/pt-BR.json b/homeassistant/components/uptimerobot/translations/pt-BR.json new file mode 100644 index 00000000000000..20a7ba268bff02 --- /dev/null +++ b/homeassistant/components/uptimerobot/translations/pt-BR.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", + "unknown": "Erro inesperado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_api_key": "Chave de API inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Chave da API" + }, + "title": "Reautenticar Integra\u00e7\u00e3o" + }, + "user": { + "data": { + "api_key": "Chave da API" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/uptimerobot/translations/sensor.pt-BR.json b/homeassistant/components/uptimerobot/translations/sensor.pt-BR.json new file mode 100644 index 00000000000000..7bf2a85b968eb1 --- /dev/null +++ b/homeassistant/components/uptimerobot/translations/sensor.pt-BR.json @@ -0,0 +1,11 @@ +{ + "state": { + "uptimerobot__monitor_status": { + "down": "Para baixo", + "not_checked_yet": "Ainda n\u00e3o foi verificado", + "pause": "Pausado", + "seems_down": "Parece baixo", + "up": "Para cima" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vallox/translations/pt-BR.json b/homeassistant/components/vallox/translations/pt-BR.json new file mode 100644 index 00000000000000..847cb96c0db846 --- /dev/null +++ b/homeassistant/components/vallox/translations/pt-BR.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar", + "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido", + "unknown": "Erro inesperado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "name": "Nome" + }, + "title": "Vallox" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/velbus/translations/pt-BR.json b/homeassistant/components/velbus/translations/pt-BR.json new file mode 100644 index 00000000000000..6b6142af49f13e --- /dev/null +++ b/homeassistant/components/velbus/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "name": "O nome para esta conex\u00e3o velbus", + "port": "String de conex\u00e3o" + }, + "title": "Defina o tipo de conex\u00e3o velbus" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/venstar/translations/pt-BR.json b/homeassistant/components/venstar/translations/pt-BR.json new file mode 100644 index 00000000000000..27ba000cef7f04 --- /dev/null +++ b/homeassistant/components/venstar/translations/pt-BR.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "password": "Senha", + "pin": "C\u00f3digo PIN", + "ssl": "Usar um certificado SSL", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/el.json b/homeassistant/components/verisure/translations/el.json index c837be37bc345d..9f3915dd39bba2 100644 --- a/homeassistant/components/verisure/translations/el.json +++ b/homeassistant/components/verisure/translations/el.json @@ -2,6 +2,9 @@ "config": { "step": { "installation": { + "data": { + "giid": "\u0395\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7" + }, "description": "\u03a4\u03bf Home Assistant \u03b2\u03c1\u03ae\u03ba\u03b5 \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ad\u03c2 \u03b5\u03b3\u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ac\u03c3\u03b5\u03b9\u03c2 Verisure \u03c3\u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 My Pages. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce, \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c3\u03c4\u03bf Home Assistant." }, "reauth_confirm": { diff --git a/homeassistant/components/verisure/translations/pt-BR.json b/homeassistant/components/verisure/translations/pt-BR.json new file mode 100644 index 00000000000000..21c367c3b870cb --- /dev/null +++ b/homeassistant/components/verisure/translations/pt-BR.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "Senha" + } + }, + "user": { + "data": { + "password": "Senha" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/version/translations/pt-BR.json b/homeassistant/components/version/translations/pt-BR.json new file mode 100644 index 00000000000000..0bb3eabad8cd6a --- /dev/null +++ b/homeassistant/components/version/translations/pt-BR.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "step": { + "version_source": { + "title": "Configurar" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vesync/translations/pt-BR.json b/homeassistant/components/vesync/translations/pt-BR.json index c65686007b5d79..89b76484d0ea5f 100644 --- a/homeassistant/components/vesync/translations/pt-BR.json +++ b/homeassistant/components/vesync/translations/pt-BR.json @@ -1,10 +1,17 @@ { "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, "error": { - "invalid_auth": "Autentica\u00e7\u00e3o invalida" + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, "step": { "user": { + "data": { + "password": "Senha", + "username": "Email" + }, "title": "Digite o nome de usu\u00e1rio e a senha" } } diff --git a/homeassistant/components/vesync/translations/zh-Hant.json b/homeassistant/components/vesync/translations/zh-Hant.json index 264ad237af1310..de32d6c787df33 100644 --- a/homeassistant/components/vesync/translations/zh-Hant.json +++ b/homeassistant/components/vesync/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" diff --git a/homeassistant/components/vicare/translations/pt-BR.json b/homeassistant/components/vicare/translations/pt-BR.json new file mode 100644 index 00000000000000..d7026fd7ef1585 --- /dev/null +++ b/homeassistant/components/vicare/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", + "unknown": "Erro inesperado" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "client_id": "Chave da API", + "name": "Nome", + "password": "Senha" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vicare/translations/zh-Hant.json b/homeassistant/components/vicare/translations/zh-Hant.json index 648acb7e35fc7b..c86ebf3cb342f3 100644 --- a/homeassistant/components/vicare/translations/zh-Hant.json +++ b/homeassistant/components/vicare/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "error": { diff --git a/homeassistant/components/vilfo/translations/pt-BR.json b/homeassistant/components/vilfo/translations/pt-BR.json index 3105455cb8baa5..605caa3e40d40e 100644 --- a/homeassistant/components/vilfo/translations/pt-BR.json +++ b/homeassistant/components/vilfo/translations/pt-BR.json @@ -1,15 +1,19 @@ { "config": { "abort": { - "already_configured": "Este roteador Vilfo j\u00e1 est\u00e1 configurado." + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "error": { - "cannot_connect": "Falha ao conectar. Por favor, verifique as informa\u00e7\u00f5es fornecidas por voc\u00ea e tente novamente.", - "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida. Verifique o token de acesso e tente novamente.", - "unknown": "Ocorreu um erro inesperado ao configurar a integra\u00e7\u00e3o." + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" }, "step": { "user": { + "data": { + "access_token": "Token de acesso", + "host": "Nome do host" + }, "title": "Conecte-se ao roteador Vilfo" } } diff --git a/homeassistant/components/vizio/translations/pt-BR.json b/homeassistant/components/vizio/translations/pt-BR.json index bca1aeeaf3d912..6db9e82783120f 100644 --- a/homeassistant/components/vizio/translations/pt-BR.json +++ b/homeassistant/components/vizio/translations/pt-BR.json @@ -1,13 +1,18 @@ { "config": { + "abort": { + "already_configured_device": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar" + }, "error": { + "cannot_connect": "Falha ao conectar", "complete_pairing_failed": "N\u00e3o foi poss\u00edvel concluir o pareamento. Verifique se o PIN que voc\u00ea forneceu est\u00e1 correto e a TV ainda est\u00e1 ligada e conectada \u00e0 internet antes de reenviar.", - "existing_config_entry_found": "Uma entrada j\u00e1 existente configurada com o mesmo n\u00famero de s\u00e9rie j\u00e1 foi configurada. Voc\u00ea deve apagar a entrada existente para poder configurar esta." + "existing_config_entry_found": "Uma entrada j\u00e1 existente Dispositivo VIZIO SmartCast configurada com o mesmo n\u00famero de s\u00e9rie j\u00e1 foi configurada. Voc\u00ea deve apagar a entrada existente para poder configurar esta." }, "step": { "pair_tv": { "data": { - "pin": "PIN" + "pin": "C\u00f3digo PIN" }, "description": "Sua TV deve estar exibindo um c\u00f3digo. Digite esse c\u00f3digo no formul\u00e1rio e continue na pr\u00f3xima etapa para concluir o pareamento.", "title": "Processo de pareamento completo" @@ -15,10 +20,18 @@ "pairing_complete": { "title": "Pareamento completo" }, + "pairing_complete_import": { + "description": "Seu Dispositivo VIZIO SmartCast agora est\u00e1 conectado ao Home Assistant.\n\nSeu Token de acesso \u00e9 '**{access_token}**'." + }, "user": { "data": { - "device_class": "Tipo de dispositivo" - } + "access_token": "Token de acesso", + "device_class": "Tipo de dispositivo", + "host": "Nome do host", + "name": "Nome" + }, + "description": "Um Token de acesso s\u00f3 \u00e9 necess\u00e1rio para TVs. Se voc\u00ea estiver configurando uma TV e ainda n\u00e3o tiver um Token de acesso , deixe-o em branco para passar pelo processo de pareamento.", + "title": "Dispositivo VIZIO SmartCast" } } } diff --git a/homeassistant/components/vlc_telnet/translations/pt-BR.json b/homeassistant/components/vlc_telnet/translations/pt-BR.json index f9028aae0023ed..146adc6d4c7e66 100644 --- a/homeassistant/components/vlc_telnet/translations/pt-BR.json +++ b/homeassistant/components/vlc_telnet/translations/pt-BR.json @@ -1,5 +1,17 @@ { "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", + "unknown": "Erro inesperado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, "step": { "reauth_confirm": { "data": { @@ -8,6 +20,7 @@ }, "user": { "data": { + "host": "Nome do host", "name": "Nome", "password": "Senha", "port": "Porta" diff --git a/homeassistant/components/volumio/translations/pt-BR.json b/homeassistant/components/volumio/translations/pt-BR.json new file mode 100644 index 00000000000000..1e898e15ce0558 --- /dev/null +++ b/homeassistant/components/volumio/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "port": "Porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/pt-BR.json b/homeassistant/components/wallbox/translations/pt-BR.json new file mode 100644 index 00000000000000..21ab247c000e59 --- /dev/null +++ b/homeassistant/components/wallbox/translations/pt-BR.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "reauth_invalid": "Falha na reautentica\u00e7\u00e3o; N\u00famero de s\u00e9rie n\u00e3o corresponde ao original", + "unknown": "Erro inesperado" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + }, + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/watttime/translations/pt-BR.json b/homeassistant/components/watttime/translations/pt-BR.json index a522da7febd296..2f61296cff5e6f 100644 --- a/homeassistant/components/watttime/translations/pt-BR.json +++ b/homeassistant/components/watttime/translations/pt-BR.json @@ -1,21 +1,40 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado", + "unknown_coordinates": "Sem dados para latitude/longitude" + }, "step": { + "coordinates": { + "data": { + "latitude": "Latitude", + "longitude": "Longitude" + }, + "description": "Insira a latitude e longitude para monitorar:" + }, "location": { "data": { "location_type": "Localiza\u00e7\u00e3o" - } + }, + "description": "Escolha um local para monitorar:" }, "reauth_confirm": { "data": { "password": "Senha" - } + }, + "title": "Reautenticar Integra\u00e7\u00e3o" }, "user": { "data": { "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "description": "Insira seu nome de usu\u00e1rio e senha:" } } } diff --git a/homeassistant/components/waze_travel_time/translations/pt-BR.json b/homeassistant/components/waze_travel_time/translations/pt-BR.json new file mode 100644 index 00000000000000..a8d47f260b6515 --- /dev/null +++ b/homeassistant/components/waze_travel_time/translations/pt-BR.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Localiza\u00e7\u00e3o j\u00e1 est\u00e1 configurada" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/webostv/translations/pt-BR.json b/homeassistant/components/webostv/translations/pt-BR.json new file mode 100644 index 00000000000000..9eddde059a8cb0 --- /dev/null +++ b/homeassistant/components/webostv/translations/pt-BR.json @@ -0,0 +1,47 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "error_pairing": "Conectado \u00e0 LG webOS TV, mas n\u00e3o emparelhado" + }, + "error": { + "cannot_connect": "Falha ao conectar, ligue sua TV ou verifique o endere\u00e7o IP" + }, + "flow_title": "LG webOS Smart TV", + "step": { + "pairing": { + "description": "Clique em enviar e aceitar a solicita\u00e7\u00e3o de emparelhamento em sua TV.\n\n! [Imagem] (/est\u00e1tica/imagens/config_webos.png)", + "title": "Emparelhamento de TV webOS" + }, + "user": { + "data": { + "host": "Nome do host", + "name": "Nome" + }, + "description": "Ligue a TV, preencha os campos a seguir clique em enviar", + "title": "Conecte-se \u00e0 webOS TV" + } + } + }, + "device_automation": { + "trigger_type": { + "webostv.turn_on": "O dispositivo \u00e9 solicitado para ligar" + } + }, + "options": { + "error": { + "cannot_retrieve": "N\u00e3o foi poss\u00edvel recuperar a lista de fontes. Verifique se o dispositivo est\u00e1 ligado", + "script_not_found": "Script n\u00e3o encontrado" + }, + "step": { + "init": { + "data": { + "sources": "Lista de fontes" + }, + "description": "Selecionar fontes habilitadas", + "title": "Op\u00e7\u00f5es para WebOS Smart TV" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/webostv/translations/tr.json b/homeassistant/components/webostv/translations/tr.json index 94e5d3abef3b89..c4f0f5c65c4835 100644 --- a/homeassistant/components/webostv/translations/tr.json +++ b/homeassistant/components/webostv/translations/tr.json @@ -32,7 +32,7 @@ "options": { "error": { "cannot_retrieve": "Kaynak listesi al\u0131namad\u0131. Cihaz\u0131n a\u00e7\u0131k oldu\u011fundan emin olun", - "script_not_found": "Komut dosyas\u0131 bulunamad\u0131" + "script_not_found": "Senaryo bulunamad\u0131" }, "step": { "init": { diff --git a/homeassistant/components/wemo/translations/pt-BR.json b/homeassistant/components/wemo/translations/pt-BR.json index c14cb64bf4e8aa..6000966dc7e6cc 100644 --- a/homeassistant/components/wemo/translations/pt-BR.json +++ b/homeassistant/components/wemo/translations/pt-BR.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Nenhum dispositivo Wemo encontrado na rede.", - "single_instance_allowed": "Somente uma \u00fanica configura\u00e7\u00e3o de Wemo \u00e9 poss\u00edvel." + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "step": { "confirm": { diff --git a/homeassistant/components/wemo/translations/zh-Hant.json b/homeassistant/components/wemo/translations/zh-Hant.json index a9a4a2a8b20c5f..05e66acedcf7cd 100644 --- a/homeassistant/components/wemo/translations/zh-Hant.json +++ b/homeassistant/components/wemo/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "step": { "confirm": { diff --git a/homeassistant/components/whois/translations/pt-BR.json b/homeassistant/components/whois/translations/pt-BR.json new file mode 100644 index 00000000000000..062f816c14a984 --- /dev/null +++ b/homeassistant/components/whois/translations/pt-BR.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" + }, + "error": { + "unknown_tld": "O TLD fornecido \u00e9 desconhecido ou n\u00e3o est\u00e1 dispon\u00edvel para esta integra\u00e7\u00e3o", + "whois_command_failed": "O comando Whois falhou: n\u00e3o foi poss\u00edvel recuperar informa\u00e7\u00f5es whois" + }, + "step": { + "user": { + "data": { + "domain": "Nome do dom\u00ednio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/pt-BR.json b/homeassistant/components/wiffi/translations/pt-BR.json index cbe6c6f78e769f..ab43d965a8f57b 100644 --- a/homeassistant/components/wiffi/translations/pt-BR.json +++ b/homeassistant/components/wiffi/translations/pt-BR.json @@ -2,12 +2,13 @@ "config": { "abort": { "addr_in_use": "Porta do servidor j\u00e1 em uso.", + "already_configured": "A porta do servidor j\u00e1 est\u00e1 configurada.", "start_server_failed": "Falha ao iniciar o servidor." }, "step": { "user": { "data": { - "port": "Porta do servidor" + "port": "Porta" }, "title": "Configurar servidor TCP para dispositivos WIFFI" } diff --git a/homeassistant/components/wilight/translations/pt-BR.json b/homeassistant/components/wilight/translations/pt-BR.json new file mode 100644 index 00000000000000..e29d809ebff3dd --- /dev/null +++ b/homeassistant/components/wilight/translations/pt-BR.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/withings/translations/pt-BR.json b/homeassistant/components/withings/translations/pt-BR.json index f87b8b64576429..9e89d9ff7530b8 100644 --- a/homeassistant/components/withings/translations/pt-BR.json +++ b/homeassistant/components/withings/translations/pt-BR.json @@ -1,7 +1,20 @@ { "config": { + "abort": { + "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", + "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", + "no_url_available": "N\u00e3o h\u00e1 URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a se\u00e7\u00e3o de ajuda]({docs_url})" + }, "create_entry": { "default": "Autenticado com sucesso no Withings." + }, + "error": { + "already_configured": "A conta j\u00e1 foi configurada" + }, + "step": { + "reauth": { + "title": "Reautenticar Integra\u00e7\u00e3o" + } } } } \ No newline at end of file diff --git a/homeassistant/components/wled/translations/pt-BR.json b/homeassistant/components/wled/translations/pt-BR.json new file mode 100644 index 00000000000000..da05a0b6690ea2 --- /dev/null +++ b/homeassistant/components/wled/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar", + "cct_unsupported": "Este dispositivo WLED usa canais CCT, que n\u00e3o s\u00e3o suportados por esta integra\u00e7\u00e3o" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wled/translations/select.el.json b/homeassistant/components/wled/translations/select.el.json new file mode 100644 index 00000000000000..3c3e1875f57ee7 --- /dev/null +++ b/homeassistant/components/wled/translations/select.el.json @@ -0,0 +1,7 @@ +{ + "state": { + "wled__live_override": { + "2": "\u039c\u03ad\u03c7\u03c1\u03b9 \u03bd\u03b1 \u03b3\u03af\u03bd\u03b5\u03b9 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/pt-BR.json b/homeassistant/components/wolflink/translations/pt-BR.json index 43e2720b365112..99728884edb006 100644 --- a/homeassistant/components/wolflink/translations/pt-BR.json +++ b/homeassistant/components/wolflink/translations/pt-BR.json @@ -1,7 +1,12 @@ { "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, "error": { - "cannot_connect": "Falha na conex\u00e3o" + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" }, "step": { "device": { diff --git a/homeassistant/components/wolflink/translations/sensor.el.json b/homeassistant/components/wolflink/translations/sensor.el.json index 75ab523afd2a84..4064892a1fbe2b 100644 --- a/homeassistant/components/wolflink/translations/sensor.el.json +++ b/homeassistant/components/wolflink/translations/sensor.el.json @@ -9,7 +9,13 @@ "gradienten_uberwachung": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03ba\u03bb\u03af\u03c3\u03b7\u03c2", "heizbetrieb": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b8\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7\u03c2", "heizgerat_mit_speicher": "\u039b\u03ad\u03b2\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03ba\u03cd\u03bb\u03b9\u03bd\u03b4\u03c1\u03bf", - "heizung": "\u0398\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7" + "heizung": "\u0398\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7", + "test": "\u0394\u03bf\u03ba\u03b9\u03bc\u03ae", + "tpw": "TPW", + "urlaubsmodus": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b4\u03b9\u03b1\u03ba\u03bf\u03c0\u03ce\u03bd", + "warmwasser": "DHW", + "warmwasservorrang": "\u03a0\u03c1\u03bf\u03c4\u03b5\u03c1\u03b1\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1 DHW", + "zunden": "\u0391\u03bd\u03ac\u03c6\u03bb\u03b5\u03be\u03b7" } } } \ No newline at end of file diff --git a/homeassistant/components/xbox/translations/pt-BR.json b/homeassistant/components/xbox/translations/pt-BR.json new file mode 100644 index 00000000000000..20d831afd2e4e6 --- /dev/null +++ b/homeassistant/components/xbox/translations/pt-BR.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", + "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "create_entry": { + "default": "Autenticado com sucesso" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xbox/translations/zh-Hant.json b/homeassistant/components/xbox/translations/zh-Hant.json index 07fc710408f874..9d348536ec3a9c 100644 --- a/homeassistant/components/xbox/translations/zh-Hant.json +++ b/homeassistant/components/xbox/translations/zh-Hant.json @@ -3,7 +3,7 @@ "abort": { "authorize_url_timeout": "\u7522\u751f\u8a8d\u8b49 URL \u6642\u903e\u6642\u3002", "missing_configuration": "\u5143\u4ef6\u5c1a\u672a\u8a2d\u7f6e\uff0c\u8acb\u53c3\u95b1\u6587\u4ef6\u8aaa\u660e\u3002", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "create_entry": { "default": "\u5df2\u6210\u529f\u8a8d\u8b49" diff --git a/homeassistant/components/xiaomi_aqara/translations/pt-BR.json b/homeassistant/components/xiaomi_aqara/translations/pt-BR.json new file mode 100644 index 00000000000000..4b579f14eaa6d9 --- /dev/null +++ b/homeassistant/components/xiaomi_aqara/translations/pt-BR.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento" + }, + "error": { + "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido" + }, + "step": { + "select": { + "data": { + "select_ip": "Endere\u00e7o IP" + } + }, + "user": { + "data": { + "host": "Endere\u00e7o IP" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/pt-BR.json b/homeassistant/components/xiaomi_miio/translations/pt-BR.json index beeb45b988071b..d57568ed7fc311 100644 --- a/homeassistant/components/xiaomi_miio/translations/pt-BR.json +++ b/homeassistant/components/xiaomi_miio/translations/pt-BR.json @@ -1,13 +1,37 @@ { "config": { "abort": { - "already_in_progress": "O fluxo de configura\u00e7\u00e3o para este dispositivo Xiaomi Miio j\u00e1 est\u00e1 em andamento." + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "cannot_connect": "Falha ao conectar" }, "step": { + "device": { + "data": { + "host": "Endere\u00e7o IP", + "token": "Token da API" + }, + "description": "Voc\u00ea precisar\u00e1 do Token da API com 32 caracteres, consulte https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token para obter instru\u00e7\u00f5es. Observe que o Token da API \u00e9 diferente da chave usada pela integra\u00e7\u00e3o Xiaomi Aqara." + }, "gateway": { "data": { - "host": "Endere\u00e7o IP" - } + "host": "Endere\u00e7o IP", + "token": "Token da API" + }, + "description": "Voc\u00ea precisar\u00e1 do Token da API com 32 caracteres, consulte https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token para obter instru\u00e7\u00f5es. Observe que o Token da API \u00e9 diferente da chave usada pela integra\u00e7\u00e3o Xiaomi Aqara." + }, + "manual": { + "data": { + "host": "Endere\u00e7o IP", + "token": "Token da API" + }, + "description": "Voc\u00ea precisar\u00e1 do Token da API com 32 caracteres, consulte https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token para obter instru\u00e7\u00f5es. Observe que o Token da API \u00e9 diferente da chave usada pela integra\u00e7\u00e3o Xiaomi Aqara." + }, + "reauth_confirm": { + "title": "Reautenticar Integra\u00e7\u00e3o" } } } diff --git a/homeassistant/components/yale_smart_alarm/translations/pt-BR.json b/homeassistant/components/yale_smart_alarm/translations/pt-BR.json index b9580fd103fc93..dae2a14938a57a 100644 --- a/homeassistant/components/yale_smart_alarm/translations/pt-BR.json +++ b/homeassistant/components/yale_smart_alarm/translations/pt-BR.json @@ -1,7 +1,41 @@ { "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, "error": { - "cannot_connect": "Falha na conex\u00e3o" + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "reauth_confirm": { + "data": { + "name": "Nome", + "password": "Senha", + "username": "Usu\u00e1rio" + } + }, + "user": { + "data": { + "name": "Nome", + "password": "Senha", + "username": "Usu\u00e1rio" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "O c\u00f3digo n\u00e3o corresponde ao n\u00famero necess\u00e1rio de d\u00edgitos" + }, + "step": { + "init": { + "data": { + "code": "C\u00f3digo padr\u00e3o para fechaduras, usado se nenhuma for dada", + "lock_code_digits": "N\u00famero de d\u00edgitos no c\u00f3digo PIN para fechaduras" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/yamaha_musiccast/translations/pt-BR.json b/homeassistant/components/yamaha_musiccast/translations/pt-BR.json new file mode 100644 index 00000000000000..378809a995b0b2 --- /dev/null +++ b/homeassistant/components/yamaha_musiccast/translations/pt-BR.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "step": { + "confirm": { + "description": "Deseja iniciar a configura\u00e7\u00e3o?" + }, + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yamaha_musiccast/translations/select.pt-BR.json b/homeassistant/components/yamaha_musiccast/translations/select.pt-BR.json new file mode 100644 index 00000000000000..fa9634e5b9474f --- /dev/null +++ b/homeassistant/components/yamaha_musiccast/translations/select.pt-BR.json @@ -0,0 +1,31 @@ +{ + "state": { + "yamaha_musiccast__zone_link_control": { + "standard": "Padr\u00e3o" + }, + "yamaha_musiccast__zone_sleep": { + "120 min": "120 minutos", + "30 min": "30 minutos", + "60 min": "60 minutos", + "90 min": "90 minutos", + "off": "Desligado" + }, + "yamaha_musiccast__zone_surr_decoder_type": { + "auto": "Autom\u00e1tico", + "dolby_pl": "Dolby ProLogic", + "dolby_pl2x_game": "Dolby ProLogic 2x Game", + "dolby_pl2x_movie": "Dolby ProLogic 2x Movie", + "dolby_pl2x_music": "Dolby ProLogic 2x Music", + "dolby_surround": "Dolby Surround", + "dts_neo6_cinema": "DTS Neo:6 Cinema", + "dts_neo6_music": "DTS Neo:6 Music", + "dts_neural_x": "DTS Neural:X", + "toggle": "Alternar" + }, + "yamaha_musiccast__zone_tone_control_mode": { + "auto": "Autom\u00e1tico", + "bypass": "Bypass", + "manual": "Manual" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yeelight/translations/pt-BR.json b/homeassistant/components/yeelight/translations/pt-BR.json new file mode 100644 index 00000000000000..87327e1e44127c --- /dev/null +++ b/homeassistant/components/yeelight/translations/pt-BR.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "host": "Nome do host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/youless/translations/pt-BR.json b/homeassistant/components/youless/translations/pt-BR.json new file mode 100644 index 00000000000000..ec60fefab42774 --- /dev/null +++ b/homeassistant/components/youless/translations/pt-BR.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zerproc/translations/pt-BR.json b/homeassistant/components/zerproc/translations/pt-BR.json index 4cbb697371ed29..1778d39a7d0829 100644 --- a/homeassistant/components/zerproc/translations/pt-BR.json +++ b/homeassistant/components/zerproc/translations/pt-BR.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "Nenhum dispositivo encontrado na rede", - "single_instance_allowed": "J\u00e1 configurado. Somente uma \u00fanica configura\u00e7\u00e3o poss\u00edvel." + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "step": { "confirm": { diff --git a/homeassistant/components/zerproc/translations/zh-Hant.json b/homeassistant/components/zerproc/translations/zh-Hant.json index 90c98e491dfea4..cfd20d603cba17 100644 --- a/homeassistant/components/zerproc/translations/zh-Hant.json +++ b/homeassistant/components/zerproc/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "step": { "confirm": { diff --git a/homeassistant/components/zha/translations/pt-BR.json b/homeassistant/components/zha/translations/pt-BR.json index e06bff43993c73..c07761309b8287 100644 --- a/homeassistant/components/zha/translations/pt-BR.json +++ b/homeassistant/components/zha/translations/pt-BR.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "Apenas uma configura\u00e7\u00e3o do ZHA \u00e9 permitida." + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "error": { - "cannot_connect": "N\u00e3o \u00e9 poss\u00edvel conectar-se ao dispositivo ZHA." + "cannot_connect": "Falha ao conectar" }, "step": { "pick_radio": { @@ -31,10 +31,46 @@ "warn": "Aviso" }, "trigger_subtype": { - "close": "Fechado" + "both_buttons": "Ambos os bot\u00f5es", + "button_1": "Primeiro bot\u00e3o", + "button_2": "Segundo bot\u00e3o", + "button_3": "Terceiro bot\u00e3o", + "button_4": "Quarto bot\u00e3o", + "button_5": "Quinto bot\u00e3o", + "button_6": "Sexto bot\u00e3o", + "close": "Fechado", + "dim_down": "Diminuir a luminosidade", + "dim_up": "Aumentar a luminosidade", + "face_1": "com face 1 ativada", + "face_2": "com face 2 ativada", + "face_3": "com face 3 ativada", + "face_4": "com face 4 ativada", + "face_5": "com face 5 ativada", + "face_6": "com face 6 ativada", + "face_any": "Com qualquer face(s) especificada(s) ativada(s)", + "left": "Esquerdo", + "open": "Aberto", + "right": "Direito", + "turn_off": "Desligar", + "turn_on": "Ligar" }, "trigger_type": { - "device_offline": "Dispositivo offline" + "device_dropped": "Dispositivo caiu", + "device_flipped": "Dispositivo invertido \" {subtype} \"", + "device_knocked": "Dispositivo batido \" {subtype} \"", + "device_offline": "Dispositivo offline", + "device_rotated": "Dispositivo girado \" {subtype} \"", + "device_shaken": "Dispositivo sacudido", + "device_slid": "Dispositivo deslizou \" {subtype} \"", + "device_tilted": "Dispositivo inclinado", + "remote_button_double_press": "bot\u00e3o \" {subtype} \" clicado duas vezes", + "remote_button_long_press": "Bot\u00e3o \" {subtype} \" pressionado continuamente", + "remote_button_long_release": "Bot\u00e3o \" {subtype} \" liberado ap\u00f3s press\u00e3o longa", + "remote_button_quadruple_press": "Bot\u00e3o \" {subtype} \" qu\u00e1druplo clicado", + "remote_button_quintuple_press": "Bot\u00e3o \" {subtype} \" qu\u00edntuplo clicado", + "remote_button_short_press": "Bot\u00e3o \" {subtype} \" pressionado", + "remote_button_short_release": "Bot\u00e3o \" {subtype} \" liberado", + "remote_button_triple_press": "Bot\u00e3o \" {subtype} \" clicado tr\u00eas vezes" } } } \ No newline at end of file diff --git a/homeassistant/components/zha/translations/zh-Hant.json b/homeassistant/components/zha/translations/zh-Hant.json index 28b8f70a8ddda0..e0904cf06830fd 100644 --- a/homeassistant/components/zha/translations/zh-Hant.json +++ b/homeassistant/components/zha/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "not_zha_device": "\u6240\u767c\u73fe\u7684\u88dd\u7f6e\u4e26\u975e ZHA \u88dd\u7f6e", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "usb_probe_failed": "\u5075\u6e2c USB \u88dd\u7f6e\u5931\u6557" }, "error": { diff --git a/homeassistant/components/zoneminder/translations/pt-BR.json b/homeassistant/components/zoneminder/translations/pt-BR.json new file mode 100644 index 00000000000000..318aba882af85d --- /dev/null +++ b/homeassistant/components/zoneminder/translations/pt-BR.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "ssl": "Usar um certificado SSL", + "username": "Usu\u00e1rio", + "verify_ssl": "Verifique o certificado SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave/translations/pt-BR.json b/homeassistant/components/zwave/translations/pt-BR.json index 8c20db13830853..b4ad9acae3ae1e 100644 --- a/homeassistant/components/zwave/translations/pt-BR.json +++ b/homeassistant/components/zwave/translations/pt-BR.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Z-Wave j\u00e1 est\u00e1 configurado." + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "error": { "option_error": "A valida\u00e7\u00e3o Z-Wave falhou. O caminho para o USB est\u00e1 correto?" @@ -10,7 +11,7 @@ "user": { "data": { "network_key": "Chave de rede (deixe em branco para gerar automaticamente)", - "usb_path": "Caminho do USB" + "usb_path": "Caminho do Dispositivo USB" }, "description": "Consulte https://www.home-assistant.io/docs/z-wave/installation/ para obter informa\u00e7\u00f5es sobre as vari\u00e1veis de configura\u00e7\u00e3o" } diff --git a/homeassistant/components/zwave/translations/zh-Hant.json b/homeassistant/components/zwave/translations/zh-Hant.json index f7979daff9e4f0..9dc8810f4999f6 100644 --- a/homeassistant/components/zwave/translations/zh-Hant.json +++ b/homeassistant/components/zwave/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "option_error": "Z-Wave \u9a57\u8b49\u5931\u6557\uff0c\u8acb\u78ba\u5b9a USB \u96a8\u8eab\u789f\u8def\u5f91\u6b63\u78ba\uff1f" diff --git a/homeassistant/components/zwave_js/translations/el.json b/homeassistant/components/zwave_js/translations/el.json index 43f004625156b6..6617d107471d97 100644 --- a/homeassistant/components/zwave_js/translations/el.json +++ b/homeassistant/components/zwave_js/translations/el.json @@ -10,6 +10,19 @@ "start_addon": "\u03a0\u03b5\u03c1\u03b9\u03bc\u03ad\u03bd\u03b5\u03c4\u03b5 \u03bc\u03ad\u03c7\u03c1\u03b9 \u03bd\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03c9\u03b8\u03b5\u03af \u03b7 \u03ad\u03bd\u03b1\u03c1\u03be\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS. \u0391\u03c5\u03c4\u03cc \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b4\u03b9\u03b1\u03c1\u03ba\u03ad\u03c3\u03b5\u03b9 \u03bc\u03b5\u03c1\u03b9\u03ba\u03ac \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1." }, "step": { + "configure_addon": { + "data": { + "network_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5" + }, + "title": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS" + }, + "on_supervisor": { + "data": { + "use_addon": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf Z-Wave JS Supervisor" + }, + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf Z-Wave JS Supervisor;", + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03ad\u03b8\u03bf\u03b4\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "start_addon": { "title": "\u03a4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf Z-Wave JS \u03be\u03b5\u03ba\u03b9\u03bd\u03ac." }, @@ -19,6 +32,13 @@ } }, "device_automation": { + "action_type": { + "refresh_value": "\u0391\u03bd\u03b1\u03bd\u03b5\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c4\u03b9\u03bc\u03ad\u03c2 \u03b3\u03b9\u03b1 {entity_name}", + "reset_meter": "\u0395\u03c0\u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac \u03bc\u03b5\u03c4\u03c1\u03b7\u03c4\u03ce\u03bd \u03c3\u03c4\u03bf {subtype}", + "set_config_parameter": "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03c4\u03b9\u03bc\u03ae\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03bf\u03c5 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 {subtype}", + "set_lock_usercode": "\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03c3\u03c4\u03bf {entity_name}", + "set_value": "\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b9\u03bc\u03ae \u03bc\u03b9\u03b1\u03c2 \u03c4\u03b9\u03bc\u03ae\u03c2 Z-Wave" + }, "condition_type": { "value": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03c4\u03b9\u03bc\u03ae \u03bc\u03b9\u03b1\u03c2 \u03c4\u03b9\u03bc\u03ae\u03c2 Z-Wave" }, diff --git a/homeassistant/components/zwave_js/translations/pt-BR.json b/homeassistant/components/zwave_js/translations/pt-BR.json index e29d809ebff3dd..7bc8288c2dd23a 100644 --- a/homeassistant/components/zwave_js/translations/pt-BR.json +++ b/homeassistant/components/zwave_js/translations/pt-BR.json @@ -1,7 +1,62 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "cannot_connect": "Falha ao conectar" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "step": { + "configure_addon": { + "data": { + "s0_legacy_key": "Chave S0 (Legado)", + "s2_access_control_key": "Chave de controle de acesso S2", + "s2_authenticated_key": "Chave autenticada S2", + "s2_unauthenticated_key": "Chave n\u00e3o autenticada S2", + "usb_path": "Caminho do Dispositivo USB" + } + }, + "manual": { + "data": { + "url": "URL" + } + } + } + }, + "device_automation": { + "action_type": { + "set_config_parameter": "Definir valor do par\u00e2metro de configura\u00e7\u00e3o {subtype}", + "set_lock_usercode": "Defina um c\u00f3digo de usu\u00e1rio em {entity_name}", + "set_value": "Definir valor de um valor de onda Z" + } + }, + "options": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha ao conectar" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "unknown": "Erro inesperado" + }, + "step": { + "configure_addon": { + "data": { + "s0_legacy_key": "Chave S0 (Legado)", + "s2_access_control_key": "Chave de controle de acesso S2", + "s2_authenticated_key": "Chave autenticada S2", + "s2_unauthenticated_key": "Chave n\u00e3o autenticada S2", + "usb_path": "Caminho do Dispositivo USB" + } + }, + "manual": { + "data": { + "url": "URL" + } + } } } } \ No newline at end of file From 1d5a052df1d313c353db5b990bab53b0ce272574 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 30 Jan 2022 11:08:37 -0600 Subject: [PATCH 0086/1098] Fix debugpy blocking the event loop at startup (#65252) --- homeassistant/components/debugpy/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/debugpy/__init__.py b/homeassistant/components/debugpy/__init__.py index 21cfeb15a808ff..1dc0f525c4dbda 100644 --- a/homeassistant/components/debugpy/__init__.py +++ b/homeassistant/components/debugpy/__init__.py @@ -46,7 +46,9 @@ async def debug_start( """Enable asyncio debugging and start the debugger.""" get_running_loop().set_debug(True) - debugpy.listen((conf[CONF_HOST], conf[CONF_PORT])) + await hass.async_add_executor_job( + debugpy.listen, (conf[CONF_HOST], conf[CONF_PORT]) + ) if conf[CONF_WAIT]: _LOGGER.warning( From dbbd239b8074010817fd8eb0f3780d636f8373f4 Mon Sep 17 00:00:00 2001 From: LJU Date: Sun, 30 Jan 2022 18:54:19 +0100 Subject: [PATCH 0087/1098] =?UTF-8?q?Fix=20typo=E2=80=99s=20ISS=20(#65228)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix typo’s --- homeassistant/components/iss/strings.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/iss/strings.json b/homeassistant/components/iss/strings.json index cdbaecbeba5959..b9dd7c374d08dd 100644 --- a/homeassistant/components/iss/strings.json +++ b/homeassistant/components/iss/strings.json @@ -2,7 +2,7 @@ "config": { "step": { "user": { - "description": "Do you want to configure the Internation Space Station?", + "description": "Do you want to configure the International Space Station?", "data": { "show_on_map": "Show on map?" } @@ -10,7 +10,7 @@ }, "abort": { "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", - "latitude_longitude_not_defined": "Latitude and longitude is not defind in Home Assistant." + "latitude_longitude_not_defined": "Latitude and longitude are not defined in Home Assistant." } } - } \ No newline at end of file + } From 6473edd88a83a6fdbb1b14647fa09c4e8a6ec2ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sun, 30 Jan 2022 20:09:51 +0200 Subject: [PATCH 0088/1098] Fix REQUIRED_NEXT_PYTHON_HA_RELEASE comment placement (#65251) --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 63dc76be271f97..49f7ed18490dc5 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -11,8 +11,8 @@ __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) -# Truthy date string triggers showing related deprecation warning messages. REQUIRED_NEXT_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) +# Truthy date string triggers showing related deprecation warning messages. REQUIRED_NEXT_PYTHON_HA_RELEASE: Final = "" # Format for platform files From 0acfc7bbab406595604a8324d1b2106ae2f7844d Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Sun, 30 Jan 2022 19:26:28 +0000 Subject: [PATCH 0089/1098] Align config flow type hints to scaffold (#65157) --- .../components/canary/config_flow.py | 3 +-- .../devolo_home_network/config_flow.py | 7 ++--- homeassistant/components/hue/config_flow.py | 26 +++++++++++++------ .../components/iaqualink/config_flow.py | 3 +-- homeassistant/components/lcn/config_flow.py | 8 +++--- .../components/notion/config_flow.py | 3 +-- .../components/nzbget/config_flow.py | 3 +-- .../components/plum_lightpad/config_flow.py | 5 ++-- .../components/ridwell/config_flow.py | 3 +-- .../components/simplisafe/config_flow.py | 3 +-- .../components/switcher_kis/config_flow.py | 3 +-- homeassistant/components/tile/config_flow.py | 3 +-- .../components/uptimerobot/config_flow.py | 13 ++++++---- .../components/watttime/config_flow.py | 3 +-- .../components/webostv/config_flow.py | 5 ++-- 15 files changed, 49 insertions(+), 42 deletions(-) diff --git a/homeassistant/components/canary/config_flow.py b/homeassistant/components/canary/config_flow.py index 967273a0f34265..6b3176f6bbd4bd 100644 --- a/homeassistant/components/canary/config_flow.py +++ b/homeassistant/components/canary/config_flow.py @@ -12,7 +12,6 @@ from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import FlowResult -from homeassistant.helpers.typing import ConfigType from .const import ( CONF_FFMPEG_ARGUMENTS, @@ -51,7 +50,7 @@ def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow: return CanaryOptionsFlowHandler(config_entry) async def async_step_import( - self, user_input: ConfigType | None = None + self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle a flow initiated by configuration file.""" return await self.async_step_user(user_input) diff --git a/homeassistant/components/devolo_home_network/config_flow.py b/homeassistant/components/devolo_home_network/config_flow.py index 0c6aeabf648a9f..c96126f43e2af3 100644 --- a/homeassistant/components/devolo_home_network/config_flow.py +++ b/homeassistant/components/devolo_home_network/config_flow.py @@ -13,7 +13,6 @@ from homeassistant.const import CONF_HOST, CONF_IP_ADDRESS, CONF_NAME from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.httpx_client import get_async_client -from homeassistant.helpers.typing import ConfigType from .const import DOMAIN, PRODUCT, SERIAL_NUMBER, TITLE @@ -48,7 +47,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 - async def async_step_user(self, user_input: ConfigType | None = None) -> FlowResult: + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle the initial step.""" errors: dict = {} @@ -92,7 +93,7 @@ async def async_step_zeroconf( return await self.async_step_zeroconf_confirm() async def async_step_zeroconf_confirm( - self, user_input: ConfigType | None = None + self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle a flow initiated by zeroconf.""" title = self.context["title_placeholders"][CONF_NAME] diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index 987afe17012aea..0901d9a1e2c282 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -3,6 +3,7 @@ import asyncio import logging +from typing import Any from urllib.parse import urlparse from aiohue import LinkButtonNotPressed, create_app_key @@ -19,7 +20,6 @@ from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client, device_registry import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.typing import ConfigType from .const import ( CONF_ALLOW_HUE_GROUPS, @@ -59,7 +59,9 @@ def __init__(self) -> None: self.bridge: DiscoveredHueBridge | None = None self.discovered_bridges: dict[str, DiscoveredHueBridge] | None = None - async def async_step_user(self, user_input: ConfigType | None = None) -> FlowResult: + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle a flow initialized by the user.""" # This is for backwards compatibility. return await self.async_step_init(user_input) @@ -76,7 +78,9 @@ async def _get_bridge( assert bridge_id == bridge.id return bridge - async def async_step_init(self, user_input: ConfigType | None = None) -> FlowResult: + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle a flow start.""" # Check if user chooses manual entry if user_input is not None and user_input["id"] == HUE_MANUAL_BRIDGE_ID: @@ -126,7 +130,7 @@ async def async_step_init(self, user_input: ConfigType | None = None) -> FlowRes ) async def async_step_manual( - self, user_input: ConfigType | None = None + self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle manual bridge setup.""" if user_input is None: @@ -139,7 +143,9 @@ async def async_step_manual( self.bridge = await self._get_bridge(user_input[CONF_HOST]) return await self.async_step_link() - async def async_step_link(self, user_input: ConfigType | None = None) -> FlowResult: + async def async_step_link( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Attempt to link with the Hue bridge. Given a configured host, will ask the user to press the link button @@ -268,7 +274,7 @@ async def async_step_homekit( await self._async_handle_discovery_without_unique_id() return await self.async_step_link() - async def async_step_import(self, import_info: ConfigType) -> FlowResult: + async def async_step_import(self, import_info: dict[str, Any]) -> FlowResult: """Import a new bridge as a config entry. This flow is triggered by `async_setup` for both configured and @@ -291,7 +297,9 @@ def __init__(self, config_entry: config_entries.ConfigEntry) -> None: """Initialize Hue options flow.""" self.config_entry = config_entry - async def async_step_init(self, user_input: ConfigType | None = None) -> FlowResult: + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Manage Hue options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) @@ -324,7 +332,9 @@ def __init__(self, config_entry: config_entries.ConfigEntry) -> None: """Initialize Hue options flow.""" self.config_entry = config_entry - async def async_step_init(self, user_input: ConfigType | None = None) -> FlowResult: + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Manage Hue options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/iaqualink/config_flow.py b/homeassistant/components/iaqualink/config_flow.py index a91964ba3bc0e6..921102b85dc07a 100644 --- a/homeassistant/components/iaqualink/config_flow.py +++ b/homeassistant/components/iaqualink/config_flow.py @@ -12,7 +12,6 @@ from homeassistant import config_entries from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.helpers.typing import ConfigType from .const import DOMAIN @@ -56,6 +55,6 @@ async def async_step_user(self, user_input: dict[str, Any] | None = None): errors=errors, ) - async def async_step_import(self, user_input: ConfigType | None = None): + async def async_step_import(self, user_input: dict[str, Any] | None = None): """Occurs when an entry is setup through config.""" return await self.async_step_user(user_input) diff --git a/homeassistant/components/lcn/config_flow.py b/homeassistant/components/lcn/config_flow.py index 9316d4309c9178..924ff5b278cd37 100644 --- a/homeassistant/components/lcn/config_flow.py +++ b/homeassistant/components/lcn/config_flow.py @@ -2,6 +2,7 @@ from __future__ import annotations import logging +from typing import Any import pypck @@ -16,7 +17,6 @@ from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import device_registry as dr, entity_registry as er -from homeassistant.helpers.typing import ConfigType from .const import CONF_DIM_MODE, CONF_SK_NUM_TRIES, DOMAIN @@ -24,7 +24,7 @@ def get_config_entry( - hass: HomeAssistant, data: ConfigType + hass: HomeAssistant, data: dict[str, Any] ) -> config_entries.ConfigEntry | None: """Check config entries for already configured entries based on the ip address/port.""" return next( @@ -38,7 +38,7 @@ def get_config_entry( ) -async def validate_connection(host_name: str, data: ConfigType) -> ConfigType: +async def validate_connection(host_name: str, data: dict[str, Any]) -> dict[str, Any]: """Validate if a connection to LCN can be established.""" host = data[CONF_IP_ADDRESS] port = data[CONF_PORT] @@ -70,7 +70,7 @@ class LcnFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 - async def async_step_import(self, data: ConfigType) -> FlowResult: + async def async_step_import(self, data: dict[str, Any]) -> FlowResult: """Import existing configuration from LCN.""" host_name = data[CONF_HOST] # validate the imported connection parameters diff --git a/homeassistant/components/notion/config_flow.py b/homeassistant/components/notion/config_flow.py index cdaab389dc7a31..c9e59107c1b825 100644 --- a/homeassistant/components/notion/config_flow.py +++ b/homeassistant/components/notion/config_flow.py @@ -11,7 +11,6 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client -from homeassistant.helpers.typing import ConfigType from .const import DOMAIN, LOGGER @@ -74,7 +73,7 @@ async def _async_verify(self, step_id: str, schema: vol.Schema) -> FlowResult: return self.async_create_entry(title=self._username, data=data) - async def async_step_reauth(self, config: ConfigType) -> FlowResult: + async def async_step_reauth(self, config: dict[str, Any]) -> FlowResult: """Handle configuration by re-auth.""" self._username = config[CONF_USERNAME] return await self.async_step_reauth_confirm() diff --git a/homeassistant/components/nzbget/config_flow.py b/homeassistant/components/nzbget/config_flow.py index 8aa18502ba35ef..c7a1699a86cf86 100644 --- a/homeassistant/components/nzbget/config_flow.py +++ b/homeassistant/components/nzbget/config_flow.py @@ -19,7 +19,6 @@ ) from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import FlowResult -from homeassistant.helpers.typing import ConfigType from .const import ( DEFAULT_NAME, @@ -65,7 +64,7 @@ def async_get_options_flow(config_entry): return NZBGetOptionsFlowHandler(config_entry) async def async_step_import( - self, user_input: ConfigType | None = None + self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle a flow initiated by configuration file.""" if CONF_SCAN_INTERVAL in user_input: diff --git a/homeassistant/components/plum_lightpad/config_flow.py b/homeassistant/components/plum_lightpad/config_flow.py index f2cc88538f96d0..b2afb55fc5d497 100644 --- a/homeassistant/components/plum_lightpad/config_flow.py +++ b/homeassistant/components/plum_lightpad/config_flow.py @@ -11,7 +11,6 @@ from homeassistant import config_entries from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.data_entry_flow import FlowResult -from homeassistant.helpers.typing import ConfigType from .const import DOMAIN from .utils import load_plum @@ -60,6 +59,8 @@ async def async_step_user( title=username, data={CONF_USERNAME: username, CONF_PASSWORD: password} ) - async def async_step_import(self, import_config: ConfigType | None) -> FlowResult: + async def async_step_import( + self, import_config: dict[str, Any] | None + ) -> FlowResult: """Import a config entry from configuration.yaml.""" return await self.async_step_user(import_config) diff --git a/homeassistant/components/ridwell/config_flow.py b/homeassistant/components/ridwell/config_flow.py index bcb881f37247c1..405474f5875cc3 100644 --- a/homeassistant/components/ridwell/config_flow.py +++ b/homeassistant/components/ridwell/config_flow.py @@ -11,7 +11,6 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client, config_validation as cv -from homeassistant.helpers.typing import ConfigType from .const import DOMAIN, LOGGER @@ -81,7 +80,7 @@ async def _async_validate( data={CONF_USERNAME: self._username, CONF_PASSWORD: self._password}, ) - async def async_step_reauth(self, config: ConfigType) -> FlowResult: + async def async_step_reauth(self, config: dict[str, Any]) -> FlowResult: """Handle configuration by re-auth.""" self._username = config[CONF_USERNAME] return await self.async_step_reauth_confirm() diff --git a/homeassistant/components/simplisafe/config_flow.py b/homeassistant/components/simplisafe/config_flow.py index 3a3d1963e0e4fd..44244e9c5739f3 100644 --- a/homeassistant/components/simplisafe/config_flow.py +++ b/homeassistant/components/simplisafe/config_flow.py @@ -18,7 +18,6 @@ from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client, config_validation as cv -from homeassistant.helpers.typing import ConfigType from .const import CONF_USER_ID, DOMAIN, LOGGER @@ -85,7 +84,7 @@ def _async_show_form(self, *, errors: dict[str, Any] | None = None) -> FlowResul }, ) - async def async_step_reauth(self, config: ConfigType) -> FlowResult: + async def async_step_reauth(self, config: dict[str, Any]) -> FlowResult: """Handle configuration by re-auth.""" self._username = config.get(CONF_USERNAME) self._reauth = True diff --git a/homeassistant/components/switcher_kis/config_flow.py b/homeassistant/components/switcher_kis/config_flow.py index 3c7587152052e3..d196bae8568ef1 100644 --- a/homeassistant/components/switcher_kis/config_flow.py +++ b/homeassistant/components/switcher_kis/config_flow.py @@ -5,7 +5,6 @@ from homeassistant import config_entries from homeassistant.data_entry_flow import FlowResult -from homeassistant.helpers.typing import ConfigType from .const import DATA_DISCOVERY, DOMAIN from .utils import async_discover_devices @@ -14,7 +13,7 @@ class SwitcherFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle Switcher config flow.""" - async def async_step_import(self, import_config: ConfigType) -> FlowResult: + async def async_step_import(self, import_config: dict[str, Any]) -> FlowResult: """Handle a flow initiated by import.""" if self._async_current_entries(True): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/components/tile/config_flow.py b/homeassistant/components/tile/config_flow.py index 58bb929e44689e..e14244530757ba 100644 --- a/homeassistant/components/tile/config_flow.py +++ b/homeassistant/components/tile/config_flow.py @@ -11,7 +11,6 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client -from homeassistant.helpers.typing import ConfigType from .const import DOMAIN, LOGGER @@ -75,7 +74,7 @@ async def async_step_import(self, import_config: dict[str, Any]) -> FlowResult: """Import a config entry from configuration.yaml.""" return await self.async_step_user(import_config) - async def async_step_reauth(self, config: ConfigType) -> FlowResult: + async def async_step_reauth(self, config: dict[str, Any]) -> FlowResult: """Handle configuration by re-auth.""" self._username = config[CONF_USERNAME] return await self.async_step_reauth_confirm() diff --git a/homeassistant/components/uptimerobot/config_flow.py b/homeassistant/components/uptimerobot/config_flow.py index c91b08ca12f46d..3f08e7e692e51b 100644 --- a/homeassistant/components/uptimerobot/config_flow.py +++ b/homeassistant/components/uptimerobot/config_flow.py @@ -1,6 +1,8 @@ """Config flow for UptimeRobot integration.""" from __future__ import annotations +from typing import Any + from pyuptimerobot import ( UptimeRobot, UptimeRobotAccount, @@ -15,7 +17,6 @@ from homeassistant.const import CONF_API_KEY from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.typing import ConfigType from .const import API_ATTR_OK, DOMAIN, LOGGER @@ -28,7 +29,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 async def _validate_input( - self, data: ConfigType + self, data: dict[str, Any] ) -> tuple[dict[str, str], UptimeRobotAccount | None]: """Validate the user input allows us to connect.""" errors: dict[str, str] = {} @@ -61,7 +62,9 @@ async def _validate_input( return errors, account - async def async_step_user(self, user_input: ConfigType | None = None) -> FlowResult: + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( @@ -79,13 +82,13 @@ async def async_step_user(self, user_input: ConfigType | None = None) -> FlowRes ) async def async_step_reauth( - self, user_input: ConfigType | None = None + self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Return the reauth confirm step.""" return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( - self, user_input: ConfigType | None = None + self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Dialog that informs the user that reauth is required.""" if user_input is None: diff --git a/homeassistant/components/watttime/config_flow.py b/homeassistant/components/watttime/config_flow.py index 415697670b0eb4..993e070ffe88bd 100644 --- a/homeassistant/components/watttime/config_flow.py +++ b/homeassistant/components/watttime/config_flow.py @@ -18,7 +18,6 @@ from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client, config_validation as cv -from homeassistant.helpers.typing import ConfigType from .const import ( CONF_BALANCING_AUTHORITY, @@ -190,7 +189,7 @@ async def async_step_location( ) return await self.async_step_coordinates() - async def async_step_reauth(self, config: ConfigType) -> FlowResult: + async def async_step_reauth(self, config: dict[str, Any]) -> FlowResult: """Handle configuration by re-auth.""" self._data = {**config} return await self.async_step_reauth_confirm() diff --git a/homeassistant/components/webostv/config_flow.py b/homeassistant/components/webostv/config_flow.py index 3bf4f7c6aebb86..5100c2dee8affe 100644 --- a/homeassistant/components/webostv/config_flow.py +++ b/homeassistant/components/webostv/config_flow.py @@ -20,7 +20,6 @@ from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.typing import ConfigType from . import async_control_connect from .const import CONF_SOURCES, DEFAULT_NAME, DOMAIN, WEBOSTV_EXCEPTIONS @@ -171,7 +170,9 @@ def __init__(self, config_entry: config_entries.ConfigEntry) -> None: self.host = config_entry.data[CONF_HOST] self.key = config_entry.data[CONF_CLIENT_SECRET] - async def async_step_init(self, user_input: ConfigType | None = None) -> FlowResult: + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Manage the options.""" errors = {} if user_input is not None: From 75b37bee3d2712cfd4a9957226015a3c79b96433 Mon Sep 17 00:00:00 2001 From: josephnad <50909764+josephnad@users.noreply.github.com> Date: Sun, 30 Jan 2022 16:02:47 -0500 Subject: [PATCH 0090/1098] Add homekit_controller support for ecobee vendor extensions (#60914) Co-authored-by: josephnad <> --- .../components/homekit_controller/button.py | 48 ++++++++++- .../components/homekit_controller/const.py | 3 + .../components/homekit_controller/number.py | 82 ++++++++++++++++++- .../homekit_controller/test_button.py | 42 ++++++++++ .../homekit_controller/test_number.py | 78 ++++++++++++++++++ 5 files changed, 247 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/homekit_controller/button.py b/homeassistant/components/homekit_controller/button.py index efb13fc34968a5..8fe7d8e8c72890 100644 --- a/homeassistant/components/homekit_controller/button.py +++ b/homeassistant/components/homekit_controller/button.py @@ -53,6 +53,7 @@ class HomeKitButtonEntityDescription(ButtonEntityDescription): ), } + # For legacy reasons, "built-in" characteristic types are in their short form # And vendor types don't have a short form # This means long and short forms get mixed up in this dict, and comparisons @@ -74,10 +75,17 @@ async def async_setup_entry( @callback def async_add_characteristic(char: Characteristic): - if not (description := BUTTON_ENTITIES.get(char.type)): - return False + entities = [] info = {"aid": char.service.accessory.aid, "iid": char.service.iid} - async_add_entities([HomeKitButton(conn, info, char, description)], True) + + if description := BUTTON_ENTITIES.get(char.type): + entities.append(HomeKitButton(conn, info, char, description)) + elif entity_type := BUTTON_ENTITY_CLASSES.get(char.type): + entities.append(entity_type(conn, info, char)) + else: + return False + + async_add_entities(entities, True) return True conn.add_char_factory(async_add_characteristic) @@ -115,3 +123,37 @@ async def async_press(self) -> None: key = self.entity_description.key val = self.entity_description.write_value return await self.async_put_characteristics({key: val}) + + +class HomeKitEcobeeClearHoldButton(CharacteristicEntity, ButtonEntity): + """Representation of a Button control for Ecobee clear hold request.""" + + def get_characteristic_types(self): + """Define the homekit characteristics the entity is tracking.""" + return [] + + @property + def name(self) -> str: + """Return the name of the device if any.""" + prefix = "" + if name := super().name: + prefix = name + return f"{prefix} Clear Hold" + + async def async_press(self) -> None: + """Press the button.""" + key = self._char.type + + # If we just send true, the request doesn't always get executed by ecobee. + # Sending false value then true value will ensure that the hold gets cleared + # and schedule resumed. + # Ecobee seems to cache the state and not update it correctly, which + # causes the request to be ignored if it thinks it has no effect. + + for val in (False, True): + await self.async_put_characteristics({key: val}) + + +BUTTON_ENTITY_CLASSES: dict[str, type] = { + CharacteristicsTypes.Vendor.ECOBEE_CLEAR_HOLD: HomeKitEcobeeClearHoldButton, +} diff --git a/homeassistant/components/homekit_controller/const.py b/homeassistant/components/homekit_controller/const.py index 8c20afcd06f712..d88f896af31d41 100644 --- a/homeassistant/components/homekit_controller/const.py +++ b/homeassistant/components/homekit_controller/const.py @@ -74,6 +74,9 @@ CharacteristicsTypes.Vendor.KOOGEEK_REALTIME_ENERGY_2: "sensor", CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL: "number", CharacteristicsTypes.Vendor.VOCOLINC_OUTLET_ENERGY: "sensor", + CharacteristicsTypes.Vendor.ECOBEE_CLEAR_HOLD: "button", + CharacteristicsTypes.Vendor.ECOBEE_FAN_WRITE_SPEED: "number", + CharacteristicsTypes.Vendor.ECOBEE_SET_HOLD_SCHEDULE: "number", CharacteristicsTypes.TEMPERATURE_CURRENT: "sensor", CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT: "sensor", CharacteristicsTypes.AIR_QUALITY: "sensor", diff --git a/homeassistant/components/homekit_controller/number.py b/homeassistant/components/homekit_controller/number.py index 9c76adf52a942c..135124cb207194 100644 --- a/homeassistant/components/homekit_controller/number.py +++ b/homeassistant/components/homekit_controller/number.py @@ -91,10 +91,17 @@ async def async_setup_entry( @callback def async_add_characteristic(char: Characteristic): - if not (description := NUMBER_ENTITIES.get(char.type)): - return False + entities = [] info = {"aid": char.service.accessory.aid, "iid": char.service.iid} - async_add_entities([HomeKitNumber(conn, info, char, description)], True) + + if description := NUMBER_ENTITIES.get(char.type): + entities.append(HomeKitNumber(conn, info, char, description)) + elif entity_type := NUMBER_ENTITY_CLASSES.get(char.type): + entities.append(entity_type(conn, info, char)) + else: + return False + + async_add_entities(entities, True) return True conn.add_char_factory(async_add_characteristic) @@ -152,3 +159,72 @@ async def async_set_value(self, value: float): self._char.type: value, } ) + + +class HomeKitEcobeeFanModeNumber(CharacteristicEntity, NumberEntity): + """Representation of a Number control for Ecobee Fan Mode request.""" + + def get_characteristic_types(self): + """Define the homekit characteristics the entity is tracking.""" + return [self._char.type] + + @property + def name(self) -> str: + """Return the name of the device if any.""" + prefix = "" + if name := super().name: + prefix = name + return f"{prefix} Fan Mode" + + @property + def min_value(self) -> float: + """Return the minimum value.""" + return self._char.minValue + + @property + def max_value(self) -> float: + """Return the maximum value.""" + return self._char.maxValue + + @property + def step(self) -> float: + """Return the increment/decrement step.""" + return self._char.minStep + + @property + def value(self) -> float: + """Return the current characteristic value.""" + return self._char.value + + async def async_set_value(self, value: float): + """Set the characteristic to this value.""" + + # Sending the fan mode request sometimes ends up getting ignored by ecobee + # and this might be because it the older value instead of newer, and ecobee + # thinks there is nothing to do. + # So in order to make sure that the request is executed by ecobee, we need + # to send a different value before sending the target value. + # Fan mode value is a value from 0 to 100. We send a value off by 1 first. + + if value > self.min_value: + other_value = value - 1 + else: + other_value = self.min_value + 1 + + if value != other_value: + await self.async_put_characteristics( + { + self._char.type: other_value, + } + ) + + await self.async_put_characteristics( + { + self._char.type: value, + } + ) + + +NUMBER_ENTITY_CLASSES: dict[str, type] = { + CharacteristicsTypes.Vendor.ECOBEE_FAN_WRITE_SPEED: HomeKitEcobeeFanModeNumber, +} diff --git a/tests/components/homekit_controller/test_button.py b/tests/components/homekit_controller/test_button.py index 020f303ffaa357..c501a9e6fb084c 100644 --- a/tests/components/homekit_controller/test_button.py +++ b/tests/components/homekit_controller/test_button.py @@ -20,6 +20,21 @@ def create_switch_with_setup_button(accessory): return service +def create_switch_with_ecobee_clear_hold_button(accessory): + """Define setup button characteristics.""" + service = accessory.add_service(ServicesTypes.OUTLET) + + setup = service.add_char(CharacteristicsTypes.Vendor.ECOBEE_CLEAR_HOLD) + + setup.value = "" + setup.format = "string" + + cur_state = service.add_char(CharacteristicsTypes.ON) + cur_state.value = True + + return service + + async def test_press_button(hass): """Test a switch service that has a button characteristic is correctly handled.""" helper = await setup_test_component(hass, create_switch_with_setup_button) @@ -43,3 +58,30 @@ async def test_press_button(hass): blocking=True, ) assert setup.value == "#HAA@trcmd" + + +async def test_ecobee_clear_hold_press_button(hass): + """Test ecobee clear hold button characteristic is correctly handled.""" + helper = await setup_test_component( + hass, create_switch_with_ecobee_clear_hold_button + ) + + # Helper will be for the primary entity, which is the outlet. Make a helper for the button. + energy_helper = Helper( + hass, + "button.testdevice_clear_hold", + helper.pairing, + helper.accessory, + helper.config_entry, + ) + + outlet = energy_helper.accessory.services.first(service_type=ServicesTypes.OUTLET) + setup = outlet[CharacteristicsTypes.Vendor.ECOBEE_CLEAR_HOLD] + + await hass.services.async_call( + "button", + "press", + {"entity_id": "button.testdevice_clear_hold"}, + blocking=True, + ) + assert setup.value is True diff --git a/tests/components/homekit_controller/test_number.py b/tests/components/homekit_controller/test_number.py index 8eebcbda8f5d01..d83b4195241c50 100644 --- a/tests/components/homekit_controller/test_number.py +++ b/tests/components/homekit_controller/test_number.py @@ -25,6 +25,26 @@ def create_switch_with_spray_level(accessory): return service +def create_switch_with_ecobee_fan_mode(accessory): + """Define battery level characteristics.""" + service = accessory.add_service(ServicesTypes.OUTLET) + + ecobee_fan_mode = service.add_char( + CharacteristicsTypes.Vendor.ECOBEE_FAN_WRITE_SPEED + ) + + ecobee_fan_mode.value = 0 + ecobee_fan_mode.minStep = 1 + ecobee_fan_mode.minValue = 0 + ecobee_fan_mode.maxValue = 100 + ecobee_fan_mode.format = "float" + + cur_state = service.add_char(CharacteristicsTypes.ON) + cur_state.value = True + + return service + + async def test_read_number(hass, utcnow): """Test a switch service that has a sensor characteristic is correctly handled.""" helper = await setup_test_component(hass, create_switch_with_spray_level) @@ -85,3 +105,61 @@ async def test_write_number(hass, utcnow): blocking=True, ) assert spray_level.value == 3 + + +async def test_write_ecobee_fan_mode_number(hass, utcnow): + """Test a switch service that has a sensor characteristic is correctly handled.""" + helper = await setup_test_component(hass, create_switch_with_ecobee_fan_mode) + outlet = helper.accessory.services.first(service_type=ServicesTypes.OUTLET) + + # Helper will be for the primary entity, which is the outlet. Make a helper for the sensor. + energy_helper = Helper( + hass, + "number.testdevice_fan_mode", + helper.pairing, + helper.accessory, + helper.config_entry, + ) + + outlet = energy_helper.accessory.services.first(service_type=ServicesTypes.OUTLET) + ecobee_fan_mode = outlet[CharacteristicsTypes.Vendor.ECOBEE_FAN_WRITE_SPEED] + + await hass.services.async_call( + "number", + "set_value", + {"entity_id": "number.testdevice_fan_mode", "value": 1}, + blocking=True, + ) + assert ecobee_fan_mode.value == 1 + + await hass.services.async_call( + "number", + "set_value", + {"entity_id": "number.testdevice_fan_mode", "value": 2}, + blocking=True, + ) + assert ecobee_fan_mode.value == 2 + + await hass.services.async_call( + "number", + "set_value", + {"entity_id": "number.testdevice_fan_mode", "value": 99}, + blocking=True, + ) + assert ecobee_fan_mode.value == 99 + + await hass.services.async_call( + "number", + "set_value", + {"entity_id": "number.testdevice_fan_mode", "value": 100}, + blocking=True, + ) + assert ecobee_fan_mode.value == 100 + + await hass.services.async_call( + "number", + "set_value", + {"entity_id": "number.testdevice_fan_mode", "value": 0}, + blocking=True, + ) + assert ecobee_fan_mode.value == 0 From 9825111c8df5051eac1b72f1c8c99d4420e0cdf8 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Sun, 30 Jan 2022 23:05:30 +0200 Subject: [PATCH 0091/1098] Fix webostv live TV source missing when configuring sources (#65243) --- .../components/webostv/config_flow.py | 18 +------- homeassistant/components/webostv/helpers.py | 30 ++++++++++++- tests/components/webostv/__init__.py | 21 +-------- tests/components/webostv/conftest.py | 16 ++----- tests/components/webostv/const.py | 36 +++++++++++++++ tests/components/webostv/test_config_flow.py | 44 ++++++++++++++----- .../components/webostv/test_device_trigger.py | 3 +- tests/components/webostv/test_init.py | 2 +- tests/components/webostv/test_media_player.py | 3 +- tests/components/webostv/test_notify.py | 3 +- tests/components/webostv/test_trigger.py | 3 +- 11 files changed, 113 insertions(+), 66 deletions(-) create mode 100644 tests/components/webostv/const.py diff --git a/homeassistant/components/webostv/config_flow.py b/homeassistant/components/webostv/config_flow.py index 5100c2dee8affe..18338e86f1ac53 100644 --- a/homeassistant/components/webostv/config_flow.py +++ b/homeassistant/components/webostv/config_flow.py @@ -23,6 +23,7 @@ from . import async_control_connect from .const import CONF_SOURCES, DEFAULT_NAME, DOMAIN, WEBOSTV_EXCEPTIONS +from .helpers import async_get_sources DATA_SCHEMA = vol.Schema( { @@ -199,20 +200,3 @@ async def async_step_init( return self.async_show_form( step_id="init", data_schema=options_schema, errors=errors ) - - -async def async_get_sources(host: str, key: str) -> list[str]: - """Construct sources list.""" - try: - client = await async_control_connect(host, key) - except WEBOSTV_EXCEPTIONS: - return [] - - return list( - dict.fromkeys( # Preserve order when filtering duplicates - [ - *(app["title"] for app in client.apps.values()), - *(app["label"] for app in client.inputs.values()), - ] - ) - ) diff --git a/homeassistant/components/webostv/helpers.py b/homeassistant/components/webostv/helpers.py index d3f5fec6826f41..70a253d5cebd84 100644 --- a/homeassistant/components/webostv/helpers.py +++ b/homeassistant/components/webostv/helpers.py @@ -6,8 +6,8 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.device_registry import DeviceEntry -from . import WebOsClientWrapper -from .const import DATA_CONFIG_ENTRY, DOMAIN +from . import WebOsClientWrapper, async_control_connect +from .const import DATA_CONFIG_ENTRY, DOMAIN, LIVE_TV_APP_ID, WEBOSTV_EXCEPTIONS @callback @@ -81,3 +81,29 @@ def async_get_client_wrapper_by_device_entry( ) return wrapper + + +async def async_get_sources(host: str, key: str) -> list[str]: + """Construct sources list.""" + try: + client = await async_control_connect(host, key) + except WEBOSTV_EXCEPTIONS: + return [] + + sources = [] + found_live_tv = False + for app in client.apps.values(): + sources.append(app["title"]) + if app["id"] == LIVE_TV_APP_ID: + found_live_tv = True + + for source in client.inputs.values(): + sources.append(source["label"]) + if source["appId"] == LIVE_TV_APP_ID: + found_live_tv = True + + if not found_live_tv: + sources.append("Live TV") + + # Preserve order when filtering duplicates + return list(dict.fromkeys(sources)) diff --git a/tests/components/webostv/__init__.py b/tests/components/webostv/__init__.py index d6e2505b96c8b2..1cbc72b43fc696 100644 --- a/tests/components/webostv/__init__.py +++ b/tests/components/webostv/__init__.py @@ -11,26 +11,9 @@ from homeassistant.helpers import entity_registry from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry - -FAKE_UUID = "some-fake-uuid" -TV_NAME = "fake_webos" -ENTITY_ID = f"{MP_DOMAIN}.{TV_NAME}" -HOST = "1.2.3.4" -CLIENT_KEY = "some-secret" -MOCK_CLIENT_KEYS = {HOST: CLIENT_KEY} -MOCK_JSON = '{"1.2.3.4": "some-secret"}' +from .const import CLIENT_KEY, FAKE_UUID, HOST, MOCK_CLIENT_KEYS, TV_NAME -CHANNEL_1 = { - "channelNumber": "1", - "channelName": "Channel 1", - "channelId": "ch1id", -} -CHANNEL_2 = { - "channelNumber": "20", - "channelName": "Channel Name 2", - "channelId": "ch2id", -} +from tests.common import MockConfigEntry async def setup_webostv(hass, unique_id=FAKE_UUID): diff --git a/tests/components/webostv/conftest.py b/tests/components/webostv/conftest.py index 23c8687018c97d..05f1be66d00cb9 100644 --- a/tests/components/webostv/conftest.py +++ b/tests/components/webostv/conftest.py @@ -6,7 +6,7 @@ from homeassistant.components.webostv.const import LIVE_TV_APP_ID from homeassistant.helpers import entity_registry -from . import CHANNEL_1, CHANNEL_2, CLIENT_KEY, FAKE_UUID +from .const import CHANNEL_1, CHANNEL_2, CLIENT_KEY, FAKE_UUID, MOCK_APPS, MOCK_INPUTS from tests.common import async_mock_service @@ -28,18 +28,8 @@ def client_fixture(): client.software_info = {"major_ver": "major", "minor_ver": "minor"} client.system_info = {"modelName": "TVFAKE"} client.client_key = CLIENT_KEY - client.apps = { - LIVE_TV_APP_ID: { - "title": "Live TV", - "id": LIVE_TV_APP_ID, - "largeIcon": "large-icon", - "icon": "icon", - }, - } - client.inputs = { - "in1": {"label": "Input01", "id": "in1", "appId": "app0"}, - "in2": {"label": "Input02", "id": "in2", "appId": "app1"}, - } + client.apps = MOCK_APPS + client.inputs = MOCK_INPUTS client.current_app_id = LIVE_TV_APP_ID client.channels = [CHANNEL_1, CHANNEL_2] diff --git a/tests/components/webostv/const.py b/tests/components/webostv/const.py new file mode 100644 index 00000000000000..eca38837d8ef76 --- /dev/null +++ b/tests/components/webostv/const.py @@ -0,0 +1,36 @@ +"""Constants for LG webOS Smart TV tests.""" +from homeassistant.components.media_player import DOMAIN as MP_DOMAIN +from homeassistant.components.webostv.const import LIVE_TV_APP_ID + +FAKE_UUID = "some-fake-uuid" +TV_NAME = "fake_webos" +ENTITY_ID = f"{MP_DOMAIN}.{TV_NAME}" +HOST = "1.2.3.4" +CLIENT_KEY = "some-secret" +MOCK_CLIENT_KEYS = {HOST: CLIENT_KEY} +MOCK_JSON = '{"1.2.3.4": "some-secret"}' + +CHANNEL_1 = { + "channelNumber": "1", + "channelName": "Channel 1", + "channelId": "ch1id", +} +CHANNEL_2 = { + "channelNumber": "20", + "channelName": "Channel Name 2", + "channelId": "ch2id", +} + +MOCK_APPS = { + LIVE_TV_APP_ID: { + "title": "Live TV", + "id": LIVE_TV_APP_ID, + "largeIcon": "large-icon", + "icon": "icon", + }, +} + +MOCK_INPUTS = { + "in1": {"label": "Input01", "id": "in1", "appId": "app0"}, + "in2": {"label": "Input02", "id": "in2", "appId": "app1"}, +} diff --git a/tests/components/webostv/test_config_flow.py b/tests/components/webostv/test_config_flow.py index 34e6d96bfdd981..b2b20677513a42 100644 --- a/tests/components/webostv/test_config_flow.py +++ b/tests/components/webostv/test_config_flow.py @@ -7,7 +7,7 @@ from homeassistant import config_entries from homeassistant.components import ssdp -from homeassistant.components.webostv.const import CONF_SOURCES, DOMAIN +from homeassistant.components.webostv.const import CONF_SOURCES, DOMAIN, LIVE_TV_APP_ID from homeassistant.config_entries import SOURCE_SSDP from homeassistant.const import ( CONF_CLIENT_SECRET, @@ -24,7 +24,8 @@ RESULT_TYPE_FORM, ) -from . import CLIENT_KEY, FAKE_UUID, HOST, TV_NAME, setup_webostv +from . import setup_webostv +from .const import CLIENT_KEY, FAKE_UUID, HOST, MOCK_APPS, MOCK_INPUTS, TV_NAME MOCK_YAML_CONFIG = { CONF_HOST: HOST, @@ -149,8 +150,27 @@ async def test_form(hass, client): assert result["title"] == TV_NAME -async def test_options_flow(hass, client): - """Test options config flow.""" +@pytest.mark.parametrize( + "apps, inputs", + [ + # Live TV in apps (default) + (MOCK_APPS, MOCK_INPUTS), + # Live TV in inputs + ( + {}, + { + **MOCK_INPUTS, + "livetv": {"label": "Live TV", "id": "livetv", "appId": LIVE_TV_APP_ID}, + }, + ), + # Live TV not found + ({}, MOCK_INPUTS), + ], +) +async def test_options_flow_live_tv_in_apps(hass, client, apps, inputs): + """Test options config flow Live TV found in apps.""" + client.apps = apps + client.inputs = inputs entry = await setup_webostv(hass) result = await hass.config_entries.options.async_init(entry.entry_id) @@ -161,20 +181,24 @@ async def test_options_flow(hass, client): result2 = await hass.config_entries.options.async_configure( result["flow_id"], - user_input={CONF_SOURCES: ["Input01", "Input02"]}, + user_input={CONF_SOURCES: ["Live TV", "Input01", "Input02"]}, ) await hass.async_block_till_done() assert result2["type"] == RESULT_TYPE_CREATE_ENTRY - assert result2["data"][CONF_SOURCES] == ["Input01", "Input02"] + assert result2["data"][CONF_SOURCES] == ["Live TV", "Input01", "Input02"] - client.connect = Mock(side_effect=ConnectionRefusedError()) - result3 = await hass.config_entries.options.async_init(entry.entry_id) +async def test_options_flow_cannot_retrieve(hass, client): + """Test options config flow cannot retrieve sources.""" + entry = await setup_webostv(hass) + + client.connect = Mock(side_effect=ConnectionRefusedError()) + result = await hass.config_entries.options.async_init(entry.entry_id) await hass.async_block_till_done() - assert result3["type"] == RESULT_TYPE_FORM - assert result3["errors"] == {"base": "cannot_retrieve"} + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {"base": "cannot_retrieve"} async def test_form_cannot_connect(hass, client): diff --git a/tests/components/webostv/test_device_trigger.py b/tests/components/webostv/test_device_trigger.py index ef7c86327a8248..fb8512d56f13c6 100644 --- a/tests/components/webostv/test_device_trigger.py +++ b/tests/components/webostv/test_device_trigger.py @@ -11,7 +11,8 @@ from homeassistant.helpers.device_registry import async_get as get_dev_reg from homeassistant.setup import async_setup_component -from . import ENTITY_ID, FAKE_UUID, setup_webostv +from . import setup_webostv +from .const import ENTITY_ID, FAKE_UUID from tests.common import MockConfigEntry, async_get_device_automations diff --git a/tests/components/webostv/test_init.py b/tests/components/webostv/test_init.py index eeb1e2fa0ee207..8729576d86957b 100644 --- a/tests/components/webostv/test_init.py +++ b/tests/components/webostv/test_init.py @@ -8,11 +8,11 @@ from homeassistant.components.webostv import DOMAIN from . import ( - MOCK_JSON, create_memory_sqlite_engine, is_entity_unique_id_updated, setup_legacy_component, ) +from .const import MOCK_JSON async def test_missing_keys_file_abort(hass, client, caplog): diff --git a/tests/components/webostv/test_media_player.py b/tests/components/webostv/test_media_player.py index a4071c741e59fd..c249b491d9a8f7 100644 --- a/tests/components/webostv/test_media_player.py +++ b/tests/components/webostv/test_media_player.py @@ -64,7 +64,8 @@ from homeassistant.setup import async_setup_component from homeassistant.util import dt -from . import CHANNEL_2, ENTITY_ID, TV_NAME, setup_webostv +from . import setup_webostv +from .const import CHANNEL_2, ENTITY_ID, TV_NAME from tests.common import async_fire_time_changed diff --git a/tests/components/webostv/test_notify.py b/tests/components/webostv/test_notify.py index 315ae6ac9199a4..a518854573762c 100644 --- a/tests/components/webostv/test_notify.py +++ b/tests/components/webostv/test_notify.py @@ -9,7 +9,8 @@ from homeassistant.const import CONF_ICON, CONF_SERVICE_DATA from homeassistant.setup import async_setup_component -from . import TV_NAME, setup_webostv +from . import setup_webostv +from .const import TV_NAME ICON_PATH = "/some/path" MESSAGE = "one, two, testing, testing" diff --git a/tests/components/webostv/test_trigger.py b/tests/components/webostv/test_trigger.py index f2fabe58edea63..cbc72638ad98b3 100644 --- a/tests/components/webostv/test_trigger.py +++ b/tests/components/webostv/test_trigger.py @@ -7,7 +7,8 @@ from homeassistant.helpers.device_registry import async_get as get_dev_reg from homeassistant.setup import async_setup_component -from . import ENTITY_ID, FAKE_UUID, setup_webostv +from . import setup_webostv +from .const import ENTITY_ID, FAKE_UUID from tests.common import MockEntity, MockEntityPlatform From 872bc456a997bff64a96d5fdd5f0a1d5953c61ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sun, 30 Jan 2022 23:07:07 +0200 Subject: [PATCH 0092/1098] Clean up no longer needed Python 3.8 support code (#65231) Co-authored-by: Martin Hjelmare --- .../components/systemmonitor/sensor.py | 16 +++++------ homeassistant/runner.py | 27 +++---------------- 2 files changed, 10 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/systemmonitor/sensor.py b/homeassistant/components/systemmonitor/sensor.py index 79e5e3f9feae58..bceda1b453a683 100644 --- a/homeassistant/components/systemmonitor/sensor.py +++ b/homeassistant/components/systemmonitor/sensor.py @@ -4,7 +4,7 @@ import asyncio from dataclasses import dataclass from datetime import datetime, timedelta -from functools import lru_cache +from functools import cache import logging import os import socket @@ -561,34 +561,32 @@ def _update( # noqa: C901 return state, value, update_time -# When we drop python 3.8 support these can be switched to -# @cache https://docs.python.org/3.9/library/functools.html#functools.cache -@lru_cache(maxsize=None) +@cache def _disk_usage(path: str) -> Any: return psutil.disk_usage(path) -@lru_cache(maxsize=None) +@cache def _swap_memory() -> Any: return psutil.swap_memory() -@lru_cache(maxsize=None) +@cache def _virtual_memory() -> Any: return psutil.virtual_memory() -@lru_cache(maxsize=None) +@cache def _net_io_counters() -> Any: return psutil.net_io_counters(pernic=True) -@lru_cache(maxsize=None) +@cache def _net_if_addrs() -> Any: return psutil.net_if_addrs() -@lru_cache(maxsize=None) +@cache def _getloadavg() -> tuple[float, float, float]: return os.getloadavg() diff --git a/homeassistant/runner.py b/homeassistant/runner.py index 571111d1077282..6ee0b8fefe1923 100644 --- a/homeassistant/runner.py +++ b/homeassistant/runner.py @@ -15,8 +15,8 @@ from .util.thread import deadlock_safe_shutdown # -# Python 3.8 has significantly less workers by default -# than Python 3.7. In order to be consistent between +# Some Python versions may have different number of workers by default +# than others. In order to be consistent between # supported versions, we need to set max_workers. # # In most cases the workers are not I/O bound, as they @@ -121,9 +121,7 @@ def run(runtime_config: RuntimeConfig) -> int: try: _cancel_all_tasks_with_timeout(loop, TASK_CANCELATION_TIMEOUT) loop.run_until_complete(loop.shutdown_asyncgens()) - # Once cpython 3.8 is no longer supported we can use the - # the built-in loop.shutdown_default_executor - loop.run_until_complete(_shutdown_default_executor(loop)) + loop.run_until_complete(loop.shutdown_default_executor()) finally: asyncio.set_event_loop(None) loop.close() @@ -159,22 +157,3 @@ def _cancel_all_tasks_with_timeout( "task": task, } ) - - -async def _shutdown_default_executor(loop: asyncio.AbstractEventLoop) -> None: - """Backport of cpython 3.9 schedule the shutdown of the default executor.""" - future = loop.create_future() - - def _do_shutdown() -> None: - try: - loop._default_executor.shutdown(wait=True) # type: ignore # pylint: disable=protected-access - loop.call_soon_threadsafe(future.set_result, None) - except Exception as ex: # pylint: disable=broad-except - loop.call_soon_threadsafe(future.set_exception, ex) - - thread = threading.Thread(target=_do_shutdown) - thread.start() - try: - await future - finally: - thread.join() From 11ad1589faf0916dd726f3ea2673e40e914b3ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Sun, 30 Jan 2022 22:09:36 +0100 Subject: [PATCH 0093/1098] Use .json.txt for diagnostics download filetype (#65236) --- homeassistant/components/diagnostics/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/diagnostics/__init__.py b/homeassistant/components/diagnostics/__init__.py index a0f161b23aeca9..f8a38971a95be8 100644 --- a/homeassistant/components/diagnostics/__init__.py +++ b/homeassistant/components/diagnostics/__init__.py @@ -170,7 +170,7 @@ async def _async_get_json_file_response( return web.Response( body=json_data, content_type="application/json", - headers={"Content-Disposition": f'attachment; filename="{filename}.json"'}, + headers={"Content-Disposition": f'attachment; filename="{filename}.json.txt"'}, ) From 78ad49325d89e9634e5cb40a5f15c964363ad5b4 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sun, 30 Jan 2022 14:12:01 -0700 Subject: [PATCH 0094/1098] Clean up SimpliSafe config flow tests (#65167) * Clean up SimpliSafe config flow tests * Cleanup --- tests/components/simplisafe/conftest.py | 81 +++++++ .../components/simplisafe/test_config_flow.py | 223 +++++------------- 2 files changed, 145 insertions(+), 159 deletions(-) create mode 100644 tests/components/simplisafe/conftest.py diff --git a/tests/components/simplisafe/conftest.py b/tests/components/simplisafe/conftest.py new file mode 100644 index 00000000000000..b793ee8656e77e --- /dev/null +++ b/tests/components/simplisafe/conftest.py @@ -0,0 +1,81 @@ +"""Define test fixtures for SimpliSafe.""" +from unittest.mock import AsyncMock, Mock, patch + +import pytest + +from homeassistant.components.simplisafe.config_flow import CONF_AUTH_CODE +from homeassistant.components.simplisafe.const import CONF_USER_ID, DOMAIN +from homeassistant.const import CONF_TOKEN +from homeassistant.setup import async_setup_component + +from tests.common import MockConfigEntry + +REFRESH_TOKEN = "token123" +USER_ID = "12345" + + +@pytest.fixture(name="api") +def api_fixture(websocket): + """Define a fixture for a simplisafe-python API object.""" + return Mock( + async_get_systems=AsyncMock(), + refresh_token=REFRESH_TOKEN, + user_id=USER_ID, + websocket=websocket, + ) + + +@pytest.fixture(name="config_entry") +def config_entry_fixture(hass, config): + """Define a config entry fixture.""" + entry = MockConfigEntry(domain=DOMAIN, unique_id=USER_ID, data=config) + entry.add_to_hass(hass) + return entry + + +@pytest.fixture(name="config") +def config_fixture(hass): + """Define a config entry data fixture.""" + return { + CONF_USER_ID: USER_ID, + CONF_TOKEN: REFRESH_TOKEN, + } + + +@pytest.fixture(name="config_code") +def config_code_fixture(hass): + """Define a authorization code.""" + return { + CONF_AUTH_CODE: "code123", + } + + +@pytest.fixture(name="setup_simplisafe") +async def setup_simplisafe_fixture(hass, api, config): + """Define a fixture to set up SimpliSafe.""" + with patch( + "homeassistant.components.simplisafe.API.async_from_auth", return_value=api + ), patch( + "homeassistant.components.simplisafe.API.async_from_refresh_token", + return_value=api, + ), patch( + "homeassistant.components.simplisafe.SimpliSafe.async_init" + ), patch( + "homeassistant.components.simplisafe.config_flow.API.async_from_auth", + return_value=api, + ), patch( + "homeassistant.components.simplisafe.PLATFORMS", [] + ): + assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() + yield + + +@pytest.fixture(name="websocket") +def websocket_fixture(): + """Define a fixture for a simplisafe-python websocket object.""" + return Mock( + async_connect=AsyncMock(), + async_disconnect=AsyncMock(), + async_listen=AsyncMock(), + ) diff --git a/tests/components/simplisafe/test_config_flow.py b/tests/components/simplisafe/test_config_flow.py index 0597ad377cf67f..2e8fe309ff2c6f 100644 --- a/tests/components/simplisafe/test_config_flow.py +++ b/tests/components/simplisafe/test_config_flow.py @@ -1,51 +1,39 @@ """Define tests for the SimpliSafe config flow.""" -from unittest.mock import AsyncMock, Mock, patch +from unittest.mock import patch import pytest from simplipy.errors import InvalidCredentialsError, SimplipyError from homeassistant import data_entry_flow from homeassistant.components.simplisafe import DOMAIN -from homeassistant.components.simplisafe.config_flow import CONF_AUTH_CODE -from homeassistant.components.simplisafe.const import CONF_USER_ID from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER -from homeassistant.const import CONF_CODE, CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME +from homeassistant.const import CONF_CODE -from tests.common import MockConfigEntry +async def test_duplicate_error(hass, config_entry, config_code, setup_simplisafe): + """Test that errors are shown when duplicates are added.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert result["step_id"] == "user" + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM -@pytest.fixture(name="api") -def api_fixture(): - """Define a fixture for simplisafe-python API object.""" - api = Mock() - api.refresh_token = "token123" - api.user_id = "12345" - return api - - -@pytest.fixture(name="mock_async_from_auth") -def mock_async_from_auth_fixture(api): - """Define a fixture for simplipy.API.async_from_auth.""" - with patch( - "homeassistant.components.simplisafe.config_flow.API.async_from_auth", - ) as mock_async_from_auth: - mock_async_from_auth.side_effect = AsyncMock(return_value=api) - yield mock_async_from_auth - + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=config_code + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" -async def test_duplicate_error(hass, mock_async_from_auth): - """Test that errors are shown when duplicates are added.""" - MockConfigEntry( - domain=DOMAIN, - unique_id="12345", - data={ - CONF_USER_ID: "12345", - CONF_TOKEN: "token123", - }, - ).add_to_hass(hass) +@pytest.mark.parametrize( + "exc,error_string", + [(InvalidCredentialsError, "invalid_auth"), (SimplipyError, "unknown")], +) +async def test_errors(hass, config_code, exc, error_string): + """Test that exceptions show the appropriate error.""" with patch( - "homeassistant.components.simplisafe.async_setup_entry", return_value=True + "homeassistant.components.simplisafe.API.async_from_auth", + side_effect=exc, ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} @@ -54,135 +42,75 @@ async def test_duplicate_error(hass, mock_async_from_auth): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_AUTH_CODE: "code123"} + result["flow_id"], user_input=config_code ) - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "already_configured" - - -async def test_invalid_credentials(hass, mock_async_from_auth): - """Test that invalid credentials show the correct error.""" - mock_async_from_auth.side_effect = AsyncMock(side_effect=InvalidCredentialsError) - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER} - ) - assert result["step_id"] == "user" - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_AUTH_CODE: "code123"} - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "invalid_auth"} + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": error_string} -async def test_options_flow(hass): +async def test_options_flow(hass, config_entry): """Test config flow options.""" - entry = MockConfigEntry( - domain=DOMAIN, - unique_id="abcde12345", - data={CONF_USER_ID: "12345", CONF_TOKEN: "token456"}, - options={CONF_CODE: "1234"}, - ) - entry.add_to_hass(hass) - with patch( "homeassistant.components.simplisafe.async_setup_entry", return_value=True ): - await hass.config_entries.async_setup(entry.entry_id) - result = await hass.config_entries.options.async_init(entry.entry_id) - + await hass.config_entries.async_setup(config_entry.entry_id) + result = await hass.config_entries.options.async_init(config_entry.entry_id) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "init" result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={CONF_CODE: "4321"} ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert entry.options == {CONF_CODE: "4321"} + assert config_entry.options == {CONF_CODE: "4321"} -async def test_step_reauth_old_format(hass, mock_async_from_auth): +async def test_step_reauth_old_format( + hass, config, config_code, config_entry, setup_simplisafe +): """Test the re-auth step with "old" config entries (those with user IDs).""" - MockConfigEntry( - domain=DOMAIN, - unique_id="user@email.com", - data={ - CONF_USERNAME: "user@email.com", - CONF_PASSWORD: "password", - }, - ).add_to_hass(hass) - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_REAUTH}, - data={CONF_USERNAME: "user@email.com", CONF_PASSWORD: "password"}, + DOMAIN, context={"source": SOURCE_REAUTH}, data=config ) assert result["step_id"] == "user" - with patch( - "homeassistant.components.simplisafe.async_setup_entry", return_value=True - ), patch("homeassistant.config_entries.ConfigEntries.async_reload"): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_AUTH_CODE: "code123"} - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "reauth_successful" + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=config_code + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "reauth_successful" assert len(hass.config_entries.async_entries()) == 1 [config_entry] = hass.config_entries.async_entries(DOMAIN) - assert config_entry.data == {CONF_USER_ID: "12345", CONF_TOKEN: "token123"} + assert config_entry.data == config -async def test_step_reauth_new_format(hass, mock_async_from_auth): +async def test_step_reauth_new_format( + hass, config, config_code, config_entry, setup_simplisafe +): """Test the re-auth step with "new" config entries (those with user IDs).""" - MockConfigEntry( - domain=DOMAIN, - unique_id="12345", - data={ - CONF_USER_ID: "12345", - CONF_TOKEN: "token123", - }, - ).add_to_hass(hass) - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_REAUTH}, - data={CONF_USER_ID: "12345", CONF_TOKEN: "token123"}, + DOMAIN, context={"source": SOURCE_REAUTH}, data=config ) assert result["step_id"] == "user" - with patch( - "homeassistant.components.simplisafe.async_setup_entry", return_value=True - ), patch("homeassistant.config_entries.ConfigEntries.async_reload"): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_AUTH_CODE: "code123"} - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "reauth_successful" + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=config_code + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "reauth_successful" assert len(hass.config_entries.async_entries()) == 1 [config_entry] = hass.config_entries.async_entries(DOMAIN) - assert config_entry.data == {CONF_USER_ID: "12345", CONF_TOKEN: "token123"} + assert config_entry.data == config -async def test_step_reauth_wrong_account(hass, api, mock_async_from_auth): +async def test_step_reauth_wrong_account( + hass, api, config, config_code, config_entry, setup_simplisafe +): """Test the re-auth step returning a different account from this one.""" - MockConfigEntry( - domain=DOMAIN, - unique_id="12345", - data={ - CONF_USER_ID: "12345", - CONF_TOKEN: "token123", - }, - ).add_to_hass(hass) - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_REAUTH}, - data={CONF_USER_ID: "12345", CONF_TOKEN: "token123"}, + DOMAIN, context={"source": SOURCE_REAUTH}, data=config ) assert result["step_id"] == "user" @@ -190,52 +118,29 @@ async def test_step_reauth_wrong_account(hass, api, mock_async_from_auth): # identified as this entry's unique ID: api.user_id = "67890" - with patch( - "homeassistant.components.simplisafe.async_setup_entry", return_value=True - ), patch("homeassistant.config_entries.ConfigEntries.async_reload"): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_AUTH_CODE: "code123"} - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "wrong_account" + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=config_code + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "wrong_account" assert len(hass.config_entries.async_entries()) == 1 [config_entry] = hass.config_entries.async_entries(DOMAIN) assert config_entry.unique_id == "12345" -async def test_step_user(hass, mock_async_from_auth): +async def test_step_user(hass, config, config_code, setup_simplisafe): """Test the user step.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} ) assert result["step_id"] == "user" - with patch( - "homeassistant.components.simplisafe.async_setup_entry", return_value=True - ), patch("homeassistant.config_entries.ConfigEntries.async_reload"): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_AUTH_CODE: "code123"} - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=config_code + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert len(hass.config_entries.async_entries()) == 1 [config_entry] = hass.config_entries.async_entries(DOMAIN) - assert config_entry.data == {CONF_USER_ID: "12345", CONF_TOKEN: "token123"} - - -async def test_unknown_error(hass, mock_async_from_auth): - """Test that an unknown error shows ohe correct error.""" - mock_async_from_auth.side_effect = AsyncMock(side_effect=SimplipyError) - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER} - ) - assert result["step_id"] == "user" - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_AUTH_CODE: "code123"} - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "unknown"} + assert config_entry.data == config From 21299729042e2e166f0971caf49c5f81aaa685c5 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Sun, 30 Jan 2022 15:15:51 -0600 Subject: [PATCH 0095/1098] Add activity statistics to Sonos diagnostics (#65214) --- homeassistant/components/sonos/__init__.py | 1 + homeassistant/components/sonos/diagnostics.py | 1 + homeassistant/components/sonos/speaker.py | 4 +- homeassistant/components/sonos/statistics.py | 50 +++++++++++++++---- 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py index 27b9d720b0f913..673e0ac4dfe460 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -193,6 +193,7 @@ def _create_soco(self, ip_address: str, source: SoCoCreationSource) -> SoCo | No async def _async_stop_event_listener(self, event: Event | None = None) -> None: for speaker in self.data.discovered.values(): + speaker.activity_stats.log_report() speaker.event_stats.log_report() await asyncio.gather( *(speaker.async_offline() for speaker in self.data.discovered.values()) diff --git a/homeassistant/components/sonos/diagnostics.py b/homeassistant/components/sonos/diagnostics.py index 007348a66bb3ac..707449e4002768 100644 --- a/homeassistant/components/sonos/diagnostics.py +++ b/homeassistant/components/sonos/diagnostics.py @@ -130,5 +130,6 @@ def get_contents(item): if s is speaker } payload["media"] = await async_generate_media_info(hass, speaker) + payload["activity_stats"] = speaker.activity_stats.report() payload["event_stats"] = speaker.event_stats.report() return payload diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index 4de0638d10c617..5f06fe976ac17c 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -62,7 +62,7 @@ ) from .favorites import SonosFavorites from .helpers import soco_error -from .statistics import EventStatistics +from .statistics import ActivityStatistics, EventStatistics NEVER_TIME = -1200.0 EVENT_CHARGING = { @@ -177,6 +177,7 @@ def __init__( self._event_dispatchers: dict[str, Callable] = {} self._last_activity: float = NEVER_TIME self._last_event_cache: dict[str, Any] = {} + self.activity_stats: ActivityStatistics = ActivityStatistics(self.zone_name) self.event_stats: EventStatistics = EventStatistics(self.zone_name) # Scheduled callback handles @@ -528,6 +529,7 @@ def speaker_activity(self, source): """Track the last activity on this speaker, set availability and resubscribe.""" _LOGGER.debug("Activity on %s from %s", self.zone_name, source) self._last_activity = time.monotonic() + self.activity_stats.activity(source, self._last_activity) was_available = self.available self.available = True if not was_available: diff --git a/homeassistant/components/sonos/statistics.py b/homeassistant/components/sonos/statistics.py index 8cade9b0aa5d62..a850e5a8caf614 100644 --- a/homeassistant/components/sonos/statistics.py +++ b/homeassistant/components/sonos/statistics.py @@ -9,13 +9,49 @@ _LOGGER = logging.getLogger(__name__) -class EventStatistics: +class SonosStatistics: + """Base class of Sonos statistics.""" + + def __init__(self, zone_name: str, kind: str) -> None: + """Initialize SonosStatistics.""" + self._stats = {} + self._stat_type = kind + self.zone_name = zone_name + + def report(self) -> dict: + """Generate a report for use in diagnostics.""" + return self._stats.copy() + + def log_report(self) -> None: + """Log statistics for this speaker.""" + _LOGGER.debug( + "%s statistics for %s: %s", + self._stat_type, + self.zone_name, + self.report(), + ) + + +class ActivityStatistics(SonosStatistics): + """Representation of Sonos activity statistics.""" + + def __init__(self, zone_name: str) -> None: + """Initialize ActivityStatistics.""" + super().__init__(zone_name, "Activity") + + def activity(self, source: str, timestamp: float) -> None: + """Track an activity occurrence.""" + activity_entry = self._stats.setdefault(source, {"count": 0}) + activity_entry["count"] += 1 + activity_entry["last_seen"] = timestamp + + +class EventStatistics(SonosStatistics): """Representation of Sonos event statistics.""" def __init__(self, zone_name: str) -> None: """Initialize EventStatistics.""" - self._stats = {} - self.zone_name = zone_name + super().__init__(zone_name, "Event") def receive(self, event: SonosEvent) -> None: """Mark a received event by subscription type.""" @@ -38,11 +74,3 @@ def report(self) -> dict: payload["soco:from_didl_string"] = from_didl_string.cache_info() payload["soco:parse_event_xml"] = parse_event_xml.cache_info() return payload - - def log_report(self) -> None: - """Log event statistics for this speaker.""" - _LOGGER.debug( - "Event statistics for %s: %s", - self.zone_name, - self.report(), - ) From 473abb1793f56abbba917b027e1b0d3c415fb6ad Mon Sep 17 00:00:00 2001 From: Patrik Lindgren <21142447+ggravlingen@users.noreply.github.com> Date: Sun, 30 Jan 2022 22:16:51 +0100 Subject: [PATCH 0096/1098] Flag Tradfri groups and YAML as deprecated (#65226) --- homeassistant/components/tradfri/__init__.py | 34 +++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/tradfri/__init__.py b/homeassistant/components/tradfri/__init__.py index 6dd3236ea973f9..61e2b50d7aabb5 100644 --- a/homeassistant/components/tradfri/__init__.py +++ b/homeassistant/components/tradfri/__init__.py @@ -57,12 +57,18 @@ CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( - { - vol.Optional(CONF_HOST): cv.string, - vol.Optional( - CONF_ALLOW_TRADFRI_GROUPS, default=DEFAULT_ALLOW_TRADFRI_GROUPS - ): cv.boolean, - } + vol.All( + cv.deprecated(CONF_HOST), + cv.deprecated( + CONF_ALLOW_TRADFRI_GROUPS, + ), + { + vol.Optional(CONF_HOST): cv.string, + vol.Optional( + CONF_ALLOW_TRADFRI_GROUPS, default=DEFAULT_ALLOW_TRADFRI_GROUPS + ): cv.boolean, + }, + ), ) }, extra=vol.ALLOW_EXTRA, @@ -120,6 +126,7 @@ async def on_hass_stop(event: Event) -> None: api = factory.request gateway = Gateway() + groups: list[Group] = [] try: gateway_info = await api(gateway.get_gateway_info(), timeout=TIMEOUT_API) @@ -127,8 +134,19 @@ async def on_hass_stop(event: Event) -> None: gateway.get_devices(), timeout=TIMEOUT_API ) devices: list[Device] = await api(devices_commands, timeout=TIMEOUT_API) - groups_commands: Command = await api(gateway.get_groups(), timeout=TIMEOUT_API) - groups: list[Group] = await api(groups_commands, timeout=TIMEOUT_API) + + if entry.data[CONF_IMPORT_GROUPS]: + # Note: we should update this page when deprecating: + # https://www.home-assistant.io/integrations/tradfri/ + _LOGGER.warning( + "Importing of Tradfri groups has been deprecated due to stability issues " + "and will be removed in Home Assistant core 2022.4" + ) + # No need to load groups if the user hasn't requested it + groups_commands: Command = await api( + gateway.get_groups(), timeout=TIMEOUT_API + ) + groups = await api(groups_commands, timeout=TIMEOUT_API) except PytradfriError as exc: await factory.shutdown() From 62fd31a1e711b27d3bcd0f8895b99dfba3bb61d7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 30 Jan 2022 15:19:04 -0600 Subject: [PATCH 0097/1098] Handle missing attrs in whois results (#65254) * Handle missing attrs in whois results - Some attrs are not set depending on where the domain is registered - Fixes #65164 * Set to unknown instead of do not create * no multi-line lambda --- homeassistant/components/whois/sensor.py | 16 ++++++--- tests/components/whois/conftest.py | 44 +++++++++++++++++++++++- tests/components/whois/test_init.py | 4 +-- tests/components/whois/test_sensor.py | 26 ++++++++++++++ 4 files changed, 82 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/whois/sensor.py b/homeassistant/components/whois/sensor.py index 2cbae147a78080..0459651e693c26 100644 --- a/homeassistant/components/whois/sensor.py +++ b/homeassistant/components/whois/sensor.py @@ -80,6 +80,13 @@ def _ensure_timezone(timestamp: datetime | None) -> datetime | None: return timestamp +def _fetch_attr_if_exists(domain: Domain, attr: str) -> str | None: + """Fetch an attribute if it exists and is truthy or return None.""" + if hasattr(domain, attr) and (value := getattr(domain, attr)): + return cast(str, value) + return None + + SENSORS: tuple[WhoisSensorEntityDescription, ...] = ( WhoisSensorEntityDescription( key="admin", @@ -87,7 +94,7 @@ def _ensure_timezone(timestamp: datetime | None) -> datetime | None: icon="mdi:account-star", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, - value_fn=lambda domain: domain.admin if domain.admin else None, + value_fn=lambda domain: _fetch_attr_if_exists(domain, "admin"), ), WhoisSensorEntityDescription( key="creation_date", @@ -123,7 +130,7 @@ def _ensure_timezone(timestamp: datetime | None) -> datetime | None: icon="mdi:account", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, - value_fn=lambda domain: domain.owner if domain.owner else None, + value_fn=lambda domain: _fetch_attr_if_exists(domain, "owner"), ), WhoisSensorEntityDescription( key="registrant", @@ -131,7 +138,7 @@ def _ensure_timezone(timestamp: datetime | None) -> datetime | None: icon="mdi:account-edit", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, - value_fn=lambda domain: domain.registrant if domain.registrant else None, + value_fn=lambda domain: _fetch_attr_if_exists(domain, "registrant"), ), WhoisSensorEntityDescription( key="registrar", @@ -147,7 +154,7 @@ def _ensure_timezone(timestamp: datetime | None) -> datetime | None: icon="mdi:store", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, - value_fn=lambda domain: domain.reseller if domain.reseller else None, + value_fn=lambda domain: _fetch_attr_if_exists(domain, "reseller"), ), ) @@ -190,7 +197,6 @@ async def async_setup_entry( ) for description in SENSORS ], - update_before_add=True, ) diff --git a/tests/components/whois/conftest.py b/tests/components/whois/conftest.py index bbda3b101f5243..ef14750356e784 100644 --- a/tests/components/whois/conftest.py +++ b/tests/components/whois/conftest.py @@ -3,7 +3,7 @@ from collections.abc import Generator from datetime import datetime -from unittest.mock import AsyncMock, MagicMock, patch +from unittest.mock import AsyncMock, MagicMock, Mock, patch import pytest @@ -71,6 +71,33 @@ def mock_whois() -> Generator[MagicMock, None, None]: yield whois_mock +@pytest.fixture +def mock_whois_missing_some_attrs() -> Generator[Mock, None, None]: + """Return a mocked query that only sets admin.""" + + class LimitedWhoisMock: + """A limited mock of whois_query.""" + + def __init__(self, *args, **kwargs): + """Mock only attributes the library always sets being available.""" + self.creation_date = datetime(2019, 1, 1, 0, 0, 0) + self.dnssec = True + self.expiration_date = datetime(2023, 1, 1, 0, 0, 0) + self.last_updated = datetime( + 2022, 1, 1, 0, 0, 0, tzinfo=dt_util.get_time_zone("Europe/Amsterdam") + ) + self.name = "home-assistant.io" + self.name_servers = ["ns1.example.com", "ns2.example.com"] + self.registrar = "My Registrar" + self.status = "OK" + self.statuses = ["OK"] + + with patch( + "homeassistant.components.whois.whois_query", LimitedWhoisMock + ) as whois_mock: + yield whois_mock + + @pytest.fixture async def init_integration( hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_whois: MagicMock @@ -84,6 +111,21 @@ async def init_integration( return mock_config_entry +@pytest.fixture +async def init_integration_missing_some_attrs( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_whois_missing_some_attrs: MagicMock, +) -> MockConfigEntry: + """Set up thewhois integration for testing.""" + mock_config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + return mock_config_entry + + @pytest.fixture def enable_all_entities() -> Generator[AsyncMock, None, None]: """Test fixture that ensures all entities are enabled in the registry.""" diff --git a/tests/components/whois/test_init.py b/tests/components/whois/test_init.py index 6701d8ed20cf96..3cd9efc801dd65 100644 --- a/tests/components/whois/test_init.py +++ b/tests/components/whois/test_init.py @@ -30,7 +30,7 @@ async def test_load_unload_config_entry( await hass.async_block_till_done() assert mock_config_entry.state is ConfigEntryState.LOADED - assert len(mock_whois.mock_calls) == 2 + assert len(mock_whois.mock_calls) == 1 await hass.config_entries.async_unload(mock_config_entry.entry_id) await hass.async_block_till_done() @@ -76,5 +76,5 @@ async def test_import_config( await hass.async_block_till_done() assert len(hass.config_entries.async_entries(DOMAIN)) == 1 - assert len(mock_whois.mock_calls) == 2 + assert len(mock_whois.mock_calls) == 1 assert "the Whois platform in YAML is deprecated" in caplog.text diff --git a/tests/components/whois/test_sensor.py b/tests/components/whois/test_sensor.py index b0e9862eb64ccb..e824522ed09318 100644 --- a/tests/components/whois/test_sensor.py +++ b/tests/components/whois/test_sensor.py @@ -143,6 +143,32 @@ async def test_whois_sensors( assert device_entry.sw_version is None +@pytest.mark.freeze_time("2022-01-01 12:00:00", tz_offset=0) +async def test_whois_sensors_missing_some_attrs( + hass: HomeAssistant, + enable_all_entities: AsyncMock, + init_integration_missing_some_attrs: MockConfigEntry, +) -> None: + """Test the Whois sensors with owner and reseller missing.""" + entity_registry = er.async_get(hass) + + state = hass.states.get("sensor.home_assistant_io_last_updated") + entry = entity_registry.async_get("sensor.home_assistant_io_last_updated") + assert entry + assert state + assert entry.unique_id == "home-assistant.io_last_updated" + assert entry.entity_category == EntityCategory.DIAGNOSTIC + assert state.state == "2021-12-31T23:00:00+00:00" + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "home-assistant.io Last Updated" + assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TIMESTAMP + assert ATTR_ICON not in state.attributes + + assert hass.states.get("sensor.home_assistant_io_owner").state == STATE_UNKNOWN + assert hass.states.get("sensor.home_assistant_io_reseller").state == STATE_UNKNOWN + assert hass.states.get("sensor.home_assistant_io_registrant").state == STATE_UNKNOWN + assert hass.states.get("sensor.home_assistant_io_admin").state == STATE_UNKNOWN + + @pytest.mark.parametrize( "entity_id", ( From ac1b30a78d4d52ea8f8ff0f410d63f0eb98af053 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sun, 30 Jan 2022 22:20:59 +0100 Subject: [PATCH 0098/1098] Better manage of nested lists (#65176) --- homeassistant/components/unifi/diagnostics.py | 48 +++++++++++++------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/unifi/diagnostics.py b/homeassistant/components/unifi/diagnostics.py index 298457754092e2..4f27ff4ff4fd3d 100644 --- a/homeassistant/components/unifi/diagnostics.py +++ b/homeassistant/components/unifi/diagnostics.py @@ -31,24 +31,42 @@ @callback -def async_replace_data(data: Mapping, to_replace: dict[str, str]) -> dict[str, Any]: - """Replace sensitive data in a dict.""" - if not isinstance(data, (Mapping, list, set, tuple)): - return to_replace.get(data, data) - +def async_replace_dict_data( + data: Mapping, to_replace: dict[str, str] +) -> dict[str, Any]: + """Redact sensitive data in a dict.""" redacted = {**data} - - for key, value in redacted.items(): + for key, value in data.items(): if isinstance(value, dict): - redacted[key] = async_replace_data(value, to_replace) + redacted[key] = async_replace_dict_data(value, to_replace) elif isinstance(value, (list, set, tuple)): - redacted[key] = [async_replace_data(item, to_replace) for item in value] + redacted[key] = async_replace_list_data(value, to_replace) elif isinstance(value, str): if value in to_replace: redacted[key] = to_replace[value] elif value.count(":") == 5: redacted[key] = REDACTED + return redacted + +@callback +def async_replace_list_data( + data: list | set | tuple, to_replace: dict[str, str] +) -> list[Any]: + """Redact sensitive data in a list.""" + redacted = [] + for item in data: + new_value = None + if isinstance(item, (list, set, tuple)): + new_value = async_replace_list_data(item, to_replace) + elif isinstance(item, Mapping): + new_value = async_replace_dict_data(item, to_replace) + elif isinstance(item, str): + if item in to_replace: + new_value = to_replace[item] + elif item.count(":") == 5: + new_value = REDACTED + redacted.append(new_value or item) return redacted @@ -73,26 +91,28 @@ async def async_get_config_entry_diagnostics( counter += 1 diag["config"] = async_redact_data( - async_replace_data(config_entry.as_dict(), macs_to_redact), REDACT_CONFIG + async_replace_dict_data(config_entry.as_dict(), macs_to_redact), REDACT_CONFIG ) diag["site_role"] = controller.site_role - diag["entities"] = async_replace_data(controller.entities, macs_to_redact) + diag["entities"] = async_replace_dict_data(controller.entities, macs_to_redact) diag["clients"] = { macs_to_redact[k]: async_redact_data( - async_replace_data(v.raw, macs_to_redact), REDACT_CLIENTS + async_replace_dict_data(v.raw, macs_to_redact), REDACT_CLIENTS ) for k, v in controller.api.clients.items() } diag["devices"] = { macs_to_redact[k]: async_redact_data( - async_replace_data(v.raw, macs_to_redact), REDACT_DEVICES + async_replace_dict_data(v.raw, macs_to_redact), REDACT_DEVICES ) for k, v in controller.api.devices.items() } diag["dpi_apps"] = {k: v.raw for k, v in controller.api.dpi_apps.items()} diag["dpi_groups"] = {k: v.raw for k, v in controller.api.dpi_groups.items()} diag["wlans"] = { - k: async_redact_data(async_replace_data(v.raw, macs_to_redact), REDACT_WLANS) + k: async_redact_data( + async_replace_dict_data(v.raw, macs_to_redact), REDACT_WLANS + ) for k, v in controller.api.wlans.items() } From ef74dab352193503c9bb18a4bad62387e3aaec3e Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sun, 30 Jan 2022 22:22:32 +0100 Subject: [PATCH 0099/1098] Fix "internet access" switch for Fritz connected device without known IP address (#65190) * fix get wan access * small improvement - default wan_access to None - test if dev_info.ip_address is not empty --- homeassistant/components/fritz/common.py | 26 +++++++++++++++--------- homeassistant/components/fritz/switch.py | 9 +++++++- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index 9d1c45438572eb..70485ac0c5f4a7 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -107,7 +107,7 @@ class Device: ip_address: str name: str ssid: str | None - wan_access: bool = True + wan_access: bool | None = None class Interface(TypedDict): @@ -277,6 +277,14 @@ def _update_device_info(self) -> tuple[bool, str | None]: ) return bool(version), version + def _get_wan_access(self, ip_address: str) -> bool | None: + """Get WAN access rule for given IP address.""" + return not self.connection.call_action( + "X_AVM-DE_HostFilter:1", + "GetWANAccessByIP", + NewIPv4Address=ip_address, + ).get("NewDisallow") + async def async_scan_devices(self, now: datetime | None = None) -> None: """Wrap up FritzboxTools class scan.""" await self.hass.async_add_executor_job(self.scan_devices, now) @@ -315,7 +323,7 @@ def scan_devices(self, now: datetime | None = None) -> None: connection_type="", ip_address=host["ip"], ssid=None, - wan_access=False, + wan_access=None, ) mesh_intf = {} @@ -352,12 +360,10 @@ def scan_devices(self, now: datetime | None = None) -> None: for link in interf["node_links"]: intf = mesh_intf.get(link["node_interface_1_uid"]) if intf is not None: - if intf["op_mode"] != "AP_GUEST": - dev_info.wan_access = not self.connection.call_action( - "X_AVM-DE_HostFilter:1", - "GetWANAccessByIP", - NewIPv4Address=dev_info.ip_address, - ).get("NewDisallow") + if intf["op_mode"] != "AP_GUEST" and dev_info.ip_address: + dev_info.wan_access = self._get_wan_access( + dev_info.ip_address + ) dev_info.connected_to = intf["device"] dev_info.connection_type = intf["type"] @@ -762,7 +768,7 @@ def __init__(self, mac: str, name: str) -> None: self._mac = mac self._name = name self._ssid: str | None = None - self._wan_access = False + self._wan_access: bool | None = False def update(self, dev_info: Device, consider_home: float) -> None: """Update device info.""" @@ -830,7 +836,7 @@ def ssid(self) -> str | None: return self._ssid @property - def wan_access(self) -> bool: + def wan_access(self) -> bool | None: """Return device wan access.""" return self._wan_access diff --git a/homeassistant/components/fritz/switch.py b/homeassistant/components/fritz/switch.py index 0726741845ac75..fec760fbe7aacc 100644 --- a/homeassistant/components/fritz/switch.py +++ b/homeassistant/components/fritz/switch.py @@ -477,10 +477,17 @@ def __init__(self, avm_wrapper: AvmWrapper, device: FritzDevice) -> None: self._attr_entity_category = EntityCategory.CONFIG @property - def is_on(self) -> bool: + def is_on(self) -> bool | None: """Switch status.""" return self._avm_wrapper.devices[self._mac].wan_access + @property + def available(self) -> bool: + """Return availability of the switch.""" + if self._avm_wrapper.devices[self._mac].wan_access is None: + return False + return super().available + @property def device_info(self) -> DeviceInfo: """Return the device information.""" From 8b5e76b8988f4c50aba480c71568f8a09f5e047a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 31 Jan 2022 10:53:46 +1300 Subject: [PATCH 0100/1098] Fix comment typo in ESPHome diagnostics (#65268) --- homeassistant/components/esphome/diagnostics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/esphome/diagnostics.py b/homeassistant/components/esphome/diagnostics.py index 4d9b769791c3e5..cc82fd536e76ff 100644 --- a/homeassistant/components/esphome/diagnostics.py +++ b/homeassistant/components/esphome/diagnostics.py @@ -1,4 +1,4 @@ -"""Diahgnostics support for ESPHome.""" +"""Diagnostics support for ESPHome.""" from __future__ import annotations from typing import Any, cast From a4904bd9bc3bcd80e5f688e4cd017fb25594e397 Mon Sep 17 00:00:00 2001 From: Stephan Uhle Date: Sun, 30 Jan 2022 23:01:20 +0100 Subject: [PATCH 0101/1098] Add Edl21 unit of measurement mapping (#64926) --- homeassistant/components/edl21/sensor.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/edl21/sensor.py b/homeassistant/components/edl21/sensor.py index f52cc0c17d46e6..f96f9d828bb175 100644 --- a/homeassistant/components/edl21/sensor.py +++ b/homeassistant/components/edl21/sensor.py @@ -15,7 +15,14 @@ SensorEntityDescription, SensorStateClass, ) -from homeassistant.const import CONF_NAME +from homeassistant.const import ( + CONF_NAME, + ELECTRIC_CURRENT_AMPERE, + ELECTRIC_POTENTIAL_VOLT, + ENERGY_KILO_WATT_HOUR, + ENERGY_WATT_HOUR, + POWER_WATT, +) from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import ( @@ -238,6 +245,14 @@ SENSORS = {desc.key: desc for desc in SENSOR_TYPES} +SENSOR_UNIT_MAPPING = { + "Wh": ENERGY_WATT_HOUR, + "kWh": ENERGY_KILO_WATT_HOUR, + "W": POWER_WATT, + "A": ELECTRIC_CURRENT_AMPERE, + "V": ELECTRIC_POTENTIAL_VOLT, +} + async def async_setup_platform( hass: HomeAssistant, @@ -435,4 +450,9 @@ def extra_state_attributes(self): @property def native_unit_of_measurement(self): """Return the unit of measurement.""" - return self._telegram.get("unit") + unit = self._telegram.get("unit") + + if unit is None: + return None + + return SENSOR_UNIT_MAPPING[unit] From 5999d08d72e4c4669b629ad1fb886926839a7927 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Mon, 31 Jan 2022 00:04:00 +0200 Subject: [PATCH 0102/1098] Bump aiowebostv to 0.1.2 (#65267) --- homeassistant/components/webostv/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/webostv/manifest.json b/homeassistant/components/webostv/manifest.json index 733693720a08a0..afba19c82d9f62 100644 --- a/homeassistant/components/webostv/manifest.json +++ b/homeassistant/components/webostv/manifest.json @@ -3,7 +3,7 @@ "name": "LG webOS Smart TV", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/webostv", - "requirements": ["aiowebostv==0.1.1", "sqlalchemy==1.4.27"], + "requirements": ["aiowebostv==0.1.2", "sqlalchemy==1.4.27"], "codeowners": ["@bendavid", "@thecode"], "ssdp": [{"st": "urn:lge-com:service:webos-second-screen:1"}], "quality_scale": "platinum", diff --git a/requirements_all.txt b/requirements_all.txt index 157acea0f53956..2a2b8567f27d4b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -278,7 +278,7 @@ aiovlc==0.1.0 aiowatttime==0.1.1 # homeassistant.components.webostv -aiowebostv==0.1.1 +aiowebostv==0.1.2 # homeassistant.components.yandex_transport aioymaps==1.2.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 07ce89ccd053c1..3daa7a710bdf44 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -213,7 +213,7 @@ aiovlc==0.1.0 aiowatttime==0.1.1 # homeassistant.components.webostv -aiowebostv==0.1.1 +aiowebostv==0.1.2 # homeassistant.components.yandex_transport aioymaps==1.2.2 From cc94af2872945667d80f8f76512260ae6205d739 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Sun, 30 Jan 2022 22:20:19 +0000 Subject: [PATCH 0103/1098] Remove deprecated helper functions from homekit_controller pairing flow (#65270) --- .../homekit_controller/config_flow.py | 9 +++-- .../homekit_controller/connection.py | 40 ------------------- .../homekit_controller/test_config_flow.py | 1 + 3 files changed, 7 insertions(+), 43 deletions(-) diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index 26055b964f8dec..18f19265f5969c 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -4,6 +4,7 @@ import aiohomekit from aiohomekit.exceptions import AuthenticationError +from aiohomekit.model import Accessories, CharacteristicsTypes, ServicesTypes import voluptuous as vol from homeassistant import config_entries @@ -15,7 +16,6 @@ async_get_registry as async_get_device_registry, ) -from .connection import get_accessory_name, get_bridge_information from .const import DOMAIN, KNOWN_DEVICES HOMEKIT_DIR = ".homekit" @@ -489,8 +489,11 @@ async def _entry_from_accessory(self, pairing): if not (accessories := pairing_data.pop("accessories", None)): accessories = await pairing.list_accessories_and_characteristics() - bridge_info = get_bridge_information(accessories) - name = get_accessory_name(bridge_info) + parsed = Accessories.from_list(accessories) + accessory_info = parsed.aid(1).services.first( + service_type=ServicesTypes.ACCESSORY_INFORMATION + ) + name = accessory_info.value(CharacteristicsTypes.NAME, "") return self.async_create_entry(title=name, data=pairing_data) diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index 1e011eaf15dfd8..88706e6fbd0fe5 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -48,36 +48,6 @@ def valid_serial_number(serial): return True -def get_accessory_information(accessory): - """Obtain the accessory information service of a HomeKit device.""" - result = {} - for service in accessory["services"]: - stype = service["type"].upper() - if ServicesTypes.get_short(stype) != "accessory-information": - continue - for characteristic in service["characteristics"]: - ctype = CharacteristicsTypes.get_short(characteristic["type"]) - if "value" in characteristic: - result[ctype] = characteristic["value"] - return result - - -def get_bridge_information(accessories): - """Return the accessory info for the bridge.""" - for accessory in accessories: - if accessory["aid"] == 1: - return get_accessory_information(accessory) - return get_accessory_information(accessories[0]) - - -def get_accessory_name(accessory_info): - """Return the name field of an accessory.""" - for field in ("name", "model", "manufacturer"): - if field in accessory_info: - return accessory_info[field] - return None - - class HKDevice: """HomeKit device.""" @@ -642,13 +612,3 @@ def unique_id(self): This id is random and will change if a device undergoes a hard reset. """ return self.pairing_data["AccessoryPairingID"] - - @property - def connection_info(self): - """Return accessory information for the main accessory.""" - return get_bridge_information(self.accessories) - - @property - def name(self): - """Name of the bridge accessory.""" - return get_accessory_name(self.connection_info) or self.unique_id diff --git a/tests/components/homekit_controller/test_config_flow.py b/tests/components/homekit_controller/test_config_flow.py index 33b5b15698d409..c9354297a43467 100644 --- a/tests/components/homekit_controller/test_config_flow.py +++ b/tests/components/homekit_controller/test_config_flow.py @@ -180,6 +180,7 @@ def setup_mock_accessory(controller): serial_number="12345", firmware_revision="1.1", ) + accessory.aid = 1 service = accessory.add_service(ServicesTypes.LIGHTBULB) on_char = service.add_char(CharacteristicsTypes.ON) From 58f624a3dac1ad57ccb58ba227677f201c9bdf11 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sun, 30 Jan 2022 15:37:56 -0700 Subject: [PATCH 0104/1098] Add diagnostics to SimpliSafe (#65171) * Add diagnostics to SimpliSafe * Bump * Cleanup --- .../components/simplisafe/diagnostics.py | 40 ++ .../components/simplisafe/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/simplisafe/conftest.py | 52 ++- .../fixtures/latest_event_data.json | 21 + .../simplisafe/fixtures/sensor_data.json | 75 ++++ .../simplisafe/fixtures/settings_data.json | 69 ++++ .../fixtures/subscription_data.json | 374 ++++++++++++++++++ .../components/simplisafe/test_diagnostics.py | 226 +++++++++++ 10 files changed, 853 insertions(+), 10 deletions(-) create mode 100644 homeassistant/components/simplisafe/diagnostics.py create mode 100644 tests/components/simplisafe/fixtures/latest_event_data.json create mode 100644 tests/components/simplisafe/fixtures/sensor_data.json create mode 100644 tests/components/simplisafe/fixtures/settings_data.json create mode 100644 tests/components/simplisafe/fixtures/subscription_data.json create mode 100644 tests/components/simplisafe/test_diagnostics.py diff --git a/homeassistant/components/simplisafe/diagnostics.py b/homeassistant/components/simplisafe/diagnostics.py new file mode 100644 index 00000000000000..bc0dddef47c7f8 --- /dev/null +++ b/homeassistant/components/simplisafe/diagnostics.py @@ -0,0 +1,40 @@ +"""Diagnostics support for SimpliSafe.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_ADDRESS +from homeassistant.core import HomeAssistant + +from . import SimpliSafe +from .const import DOMAIN + +CONF_SERIAL = "serial" +CONF_SYSTEM_ID = "system_id" +CONF_WIFI_SSID = "wifi_ssid" + +TO_REDACT = { + CONF_ADDRESS, + CONF_SERIAL, + CONF_SYSTEM_ID, + CONF_WIFI_SSID, +} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + simplisafe: SimpliSafe = hass.data[DOMAIN][entry.entry_id] + + return async_redact_data( + { + "entry": { + "options": dict(entry.options), + }, + "systems": [system.as_dict() for system in simplisafe.systems.values()], + }, + TO_REDACT, + ) diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index 30ea49a359cbad..6465b542f36ab0 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,7 +3,7 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==2021.12.2"], + "requirements": ["simplisafe-python==2022.01.0"], "codeowners": ["@bachya"], "iot_class": "cloud_polling", "dhcp": [ diff --git a/requirements_all.txt b/requirements_all.txt index 2a2b8567f27d4b..7a1cc0e98d34c1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2190,7 +2190,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==2021.12.2 +simplisafe-python==2022.01.0 # homeassistant.components.sisyphus sisyphus-control==3.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3daa7a710bdf44..5b6c25c2a7b372 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1340,7 +1340,7 @@ sharkiqpy==0.1.8 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==2021.12.2 +simplisafe-python==2022.01.0 # homeassistant.components.slack slackclient==2.5.0 diff --git a/tests/components/simplisafe/conftest.py b/tests/components/simplisafe/conftest.py index b793ee8656e77e..d9e6d46c2eb1a0 100644 --- a/tests/components/simplisafe/conftest.py +++ b/tests/components/simplisafe/conftest.py @@ -1,24 +1,27 @@ """Define test fixtures for SimpliSafe.""" +import json from unittest.mock import AsyncMock, Mock, patch import pytest +from simplipy.system.v3 import SystemV3 from homeassistant.components.simplisafe.config_flow import CONF_AUTH_CODE from homeassistant.components.simplisafe.const import CONF_USER_ID, DOMAIN from homeassistant.const import CONF_TOKEN from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, load_fixture REFRESH_TOKEN = "token123" +SYSTEM_ID = "system_123" USER_ID = "12345" @pytest.fixture(name="api") -def api_fixture(websocket): +def api_fixture(system_v3, websocket): """Define a fixture for a simplisafe-python API object.""" return Mock( - async_get_systems=AsyncMock(), + async_get_systems=AsyncMock(return_value={SYSTEM_ID: system_v3}), refresh_token=REFRESH_TOKEN, user_id=USER_ID, websocket=websocket, @@ -50,19 +53,43 @@ def config_code_fixture(hass): } +@pytest.fixture(name="data_latest_event", scope="session") +def data_latest_event_fixture(): + """Define latest event data.""" + return json.loads(load_fixture("latest_event_data.json", "simplisafe")) + + +@pytest.fixture(name="data_sensor", scope="session") +def data_sensor_fixture(): + """Define sensor data.""" + return json.loads(load_fixture("sensor_data.json", "simplisafe")) + + +@pytest.fixture(name="data_settings", scope="session") +def data_settings_fixture(): + """Define settings data.""" + return json.loads(load_fixture("settings_data.json", "simplisafe")) + + +@pytest.fixture(name="data_subscription", scope="session") +def data_subscription_fixture(): + """Define subscription data.""" + return json.loads(load_fixture("subscription_data.json", "simplisafe")) + + @pytest.fixture(name="setup_simplisafe") async def setup_simplisafe_fixture(hass, api, config): """Define a fixture to set up SimpliSafe.""" with patch( + "homeassistant.components.simplisafe.config_flow.API.async_from_auth", + return_value=api, + ), patch( "homeassistant.components.simplisafe.API.async_from_auth", return_value=api ), patch( "homeassistant.components.simplisafe.API.async_from_refresh_token", return_value=api, ), patch( - "homeassistant.components.simplisafe.SimpliSafe.async_init" - ), patch( - "homeassistant.components.simplisafe.config_flow.API.async_from_auth", - return_value=api, + "homeassistant.components.simplisafe.SimpliSafe._async_start_websocket_loop" ), patch( "homeassistant.components.simplisafe.PLATFORMS", [] ): @@ -71,6 +98,17 @@ async def setup_simplisafe_fixture(hass, api, config): yield +@pytest.fixture(name="system_v3") +def system_v3_fixture(data_latest_event, data_sensor, data_settings, data_subscription): + """Define a fixture for a simplisafe-python V3 System object.""" + system = SystemV3(Mock(subscription_data=data_subscription), SYSTEM_ID) + system.async_get_latest_event = AsyncMock(return_value=data_latest_event) + system.sensor_data = data_sensor + system.settings_data = data_settings + system.generate_device_objects() + return system + + @pytest.fixture(name="websocket") def websocket_fixture(): """Define a fixture for a simplisafe-python websocket object.""" diff --git a/tests/components/simplisafe/fixtures/latest_event_data.json b/tests/components/simplisafe/fixtures/latest_event_data.json new file mode 100644 index 00000000000000..ca44c0674f11b5 --- /dev/null +++ b/tests/components/simplisafe/fixtures/latest_event_data.json @@ -0,0 +1,21 @@ +{ + "eventId": 1234567890, + "eventTimestamp": 1564018073, + "eventCid": 1400, + "zoneCid": "2", + "sensorType": 1, + "sensorSerial": "01010101", + "account": "00011122", + "userId": 12345, + "sid": "system_123", + "info": "System Disarmed by PIN 2", + "pinName": "", + "sensorName": "Kitchen", + "messageSubject": "SimpliSafe System Disarmed", + "messageBody": "System Disarmed: Your SimpliSafe security system was ...", + "eventType": "activity", + "timezone": 2, + "locationOffset": -360, + "videoStartedBy": "", + "video": {} +} diff --git a/tests/components/simplisafe/fixtures/sensor_data.json b/tests/components/simplisafe/fixtures/sensor_data.json new file mode 100644 index 00000000000000..073d51b0538eff --- /dev/null +++ b/tests/components/simplisafe/fixtures/sensor_data.json @@ -0,0 +1,75 @@ +{ + "825": { + "type": 5, + "serial": "825", + "name": "Fire Door", + "setting": { + "instantTrigger": false, + "away2": 1, + "away": 1, + "home2": 1, + "home": 1, + "off": 0 + }, + "status": { + "triggered": false + }, + "flags": { + "swingerShutdown": false, + "lowBattery": false, + "offline": false + } + }, + "14": { + "type": 12, + "serial": "14", + "name": "Front Door", + "setting": { + "instantTrigger": false, + "away2": 1, + "away": 1, + "home2": 1, + "home": 1, + "off": 0 + }, + "status": { + "triggered": false + }, + "flags": { + "swingerShutdown": false, + "lowBattery": false, + "offline": false + } + }, + "987": { + "serial": "987", + "type": 16, + "status": { + "pinPadState": 0, + "lockState": 1, + "pinPadOffline": false, + "pinPadLowBattery": false, + "lockDisabled": false, + "lockLowBattery": false, + "calibrationErrDelta": 0, + "calibrationErrZero": 0, + "lockJamState": 0 + }, + "name": "Front Door", + "deviceGroupID": 1, + "firmwareVersion": "1.0.0", + "bootVersion": "1.0.0", + "setting": { + "autoLock": 3, + "away": 1, + "home": 1, + "awayToOff": 0, + "homeToOff": 1 + }, + "flags": { + "swingerShutdown": false, + "lowBattery": false, + "offline": false + } + } +} diff --git a/tests/components/simplisafe/fixtures/settings_data.json b/tests/components/simplisafe/fixtures/settings_data.json new file mode 100644 index 00000000000000..2b617bb86634d8 --- /dev/null +++ b/tests/components/simplisafe/fixtures/settings_data.json @@ -0,0 +1,69 @@ +{ + "account": "12345012", + "settings": { + "normal": { + "wifiSSID": "MY_WIFI", + "alarmDuration": 240, + "alarmVolume": 3, + "doorChime": 2, + "entryDelayAway": 30, + "entryDelayAway2": 30, + "entryDelayHome": 30, + "entryDelayHome2": 30, + "exitDelayAway": 60, + "exitDelayAway2": 60, + "exitDelayHome": 0, + "exitDelayHome2": 0, + "lastUpdated": "2019-07-03T03:24:20.999Z", + "light": true, + "voicePrompts": 2, + "_id": "1197192618725121765212" + }, + "pins": { + "lastUpdated": "2019-07-04T20:47:44.016Z", + "_id": "asd6281526381253123", + "users": [ + { + "_id": "1271279d966212121124c7", + "pin": "3456", + "name": "Test 1" + }, + { + "_id": "1271279d966212121124c6", + "pin": "5423", + "name": "Test 2" + }, + { + "_id": "1271279d966212121124c5", + "pin": "", + "name": "" + }, + { + "_id": "1271279d966212121124c4", + "pin": "", + "name": "" + } + ], + "duress": { + "pin": "9876" + }, + "master": { + "pin": "1234" + } + } + }, + "basestationStatus": { + "lastUpdated": "2019-07-15T15:28:22.961Z", + "rfJamming": false, + "ethernetStatus": 4, + "gsmRssi": -73, + "gsmStatus": 3, + "backupBattery": 5293, + "wallPower": 5933, + "wifiRssi": -49, + "wifiStatus": 1, + "_id": "6128153715231t237123", + "encryptionErrors": [] + }, + "lastUpdated": 1562273264 +} diff --git a/tests/components/simplisafe/fixtures/subscription_data.json b/tests/components/simplisafe/fixtures/subscription_data.json new file mode 100644 index 00000000000000..56731307e427af --- /dev/null +++ b/tests/components/simplisafe/fixtures/subscription_data.json @@ -0,0 +1,374 @@ +{ + "system_123": { + "uid": 12345, + "sid": "system_123", + "sStatus": 20, + "activated": 1445034752, + "planSku": "SSEDSM2", + "planName": "Interactive Monitoring", + "price": 24.99, + "currency": "USD", + "country": "US", + "expires": 1602887552, + "canceled": 0, + "extraTime": 0, + "creditCard": { + "lastFour": "", + "type": "", + "ppid": "ABCDE12345", + "uid": 12345 + }, + "time": 2628000, + "paymentProfileId": "ABCDE12345", + "features": { + "monitoring": true, + "alerts": true, + "online": true, + "hazard": true, + "video": true, + "cameras": 10, + "dispatch": true, + "proInstall": false, + "discount": 0, + "vipCS": false, + "medical": true, + "careVisit": false, + "storageDays": 30 + }, + "status": { + "hasBaseStation": true, + "isActive": true, + "monitoring": "Active" + }, + "subscriptionFeatures": { + "monitoredSensorsTypes": [ + "Entry", + "Motion", + "GlassBreak", + "Smoke", + "CO", + "Freeze", + "Water" + ], + "monitoredPanicConditions": [ + "Fire", + "Medical", + "Duress" + ], + "dispatchTypes": [ + "Police", + "Fire", + "Medical", + "Guard" + ], + "remoteControl": [ + "ArmDisarm", + "LockUnlock", + "ViewSettings", + "ConfigureSettings" + ], + "cameraFeatures": { + "liveView": true, + "maxRecordingCameras": 10, + "recordingStorageDays": 30, + "videoVerification": true + }, + "support": { + "level": "Basic", + "annualVisit": false, + "professionalInstall": false + }, + "cellCommunicationBackup": true, + "alertChannels": [ + "Push", + "SMS", + "Email" + ], + "alertTypes": [ + "Alarm", + "Error", + "Activity", + "Camera" + ], + "alarmModes": [ + "Alarm", + "SecretAlert", + "Disabled" + ], + "supportedIntegrations": [ + "GoogleAssistant", + "AmazonAlexa", + "AugustLock" + ], + "timeline": {} + }, + "dispatcher": "cops", + "dcid": 0, + "location": { + "sid": 12345, + "uid": 12345, + "lStatus": 10, + "account": "1234ABCD", + "street1": "1234 Main Street", + "street2": "", + "locationName": "", + "city": "Atlantis", + "county": "SEA", + "state": "UW", + "zip": "12345", + "country": "US", + "crossStreet": "River 1 and River 2", + "notes": "", + "residenceType": 2, + "numAdults": 2, + "numChildren": 0, + "locationOffset": -360, + "safeWord": "TRITON", + "signature": "Atlantis Citizen 1", + "timeZone": 2, + "primaryContacts": [ + { + "name": "John Doe", + "phone": "1234567890" + } + ], + "secondaryContacts": [ + { + "name": "Jane Doe", + "phone": "9876543210" + } + ], + "copsOptIn": false, + "certificateUri": "https://simplisafe.com/account2/12345/alarm-certificate/12345", + "nestStructureId": "", + "system": { + "serial": "1234ABCD", + "alarmState": "OFF", + "alarmStateTimestamp": 0, + "isAlarming": false, + "version": 3, + "capabilities": { + "setWifiOverCell": true, + "setDoorbellChimeVolume": true, + "outdoorBattCamera": true + }, + "temperature": 67, + "exitDelayRemaining": 60, + "cameras": [ + { + "staleSettingsTypes": [], + "upgradeWhitelisted": false, + "model": "SS001", + "uuid": "1234567890", + "uid": 12345, + "sid": 12345, + "cameraSettings": { + "cameraName": "Camera", + "pictureQuality": "720p", + "nightVision": "auto", + "statusLight": "off", + "micSensitivity": 100, + "micEnable": true, + "speakerVolume": 75, + "motionSensitivity": 0, + "shutterHome": "closedAlarmOnly", + "shutterAway": "open", + "shutterOff": "closedAlarmOnly", + "wifiSsid": "", + "canStream": false, + "canRecord": false, + "pirEnable": true, + "vaEnable": true, + "notificationsEnable": false, + "enableDoorbellNotification": true, + "doorbellChimeVolume": "off", + "privacyEnable": false, + "hdr": false, + "vaZoningEnable": false, + "vaZoningRows": 0, + "vaZoningCols": 0, + "vaZoningMask": [], + "maxDigitalZoom": 10, + "supportedResolutions": [ + "480p", + "720p" + ], + "admin": { + "IRLED": 0, + "pirSens": 0, + "statusLEDState": 1, + "lux": "lowLux", + "motionDetectionEnabled": false, + "motionThresholdZero": 0, + "motionThresholdOne": 10000, + "levelChangeDelayZero": 30, + "levelChangeDelayOne": 10, + "audioDetectionEnabled": false, + "audioChannelNum": 2, + "audioSampleRate": 16000, + "audioChunkBytes": 2048, + "audioSampleFormat": 3, + "audioSensitivity": 50, + "audioThreshold": 50, + "audioDirection": 0, + "bitRate": 284, + "longPress": 2000, + "kframe": 1, + "gopLength": 40, + "idr": 1, + "fps": 20, + "firmwareVersion": "2.6.1.107", + "netConfigVersion": "", + "camAgentVersion": "", + "lastLogin": 1600639997, + "lastLogout": 1600639944, + "pirSampleRateMs": 800, + "pirHysteresisHigh": 2, + "pirHysteresisLow": 10, + "pirFilterCoefficient": 1, + "logEnabled": true, + "logLevel": 3, + "logQDepth": 20, + "firmwareGroup": "public", + "irOpenThreshold": 445, + "irCloseThreshold": 840, + "irOpenDelay": 3, + "irCloseDelay": 3, + "irThreshold1x": 388, + "irThreshold2x": 335, + "irThreshold3x": 260, + "rssi": [ + [ + 1600935204, + -43 + ] + ], + "battery": [], + "dbm": 0, + "vmUse": 161592, + "resSet": 10540, + "uptime": 810043.74, + "wifiDisconnects": 1, + "wifiDriverReloads": 1, + "statsPeriod": 3600000, + "sarlaccDebugLogTypes": 0, + "odProcessingFps": 8, + "odObjectMinWidthPercent": 6, + "odObjectMinHeightPercent": 24, + "odEnableObjectDetection": true, + "odClassificationMask": 2, + "odClassificationConfidenceThreshold": 0.95, + "odEnableOverlay": false, + "odAnalyticsLib": 2, + "odSensitivity": 85, + "odEventObjectMask": 2, + "odLuxThreshold": 445, + "odLuxHysteresisHigh": 4, + "odLuxHysteresisLow": 4, + "odLuxSamplingFrequency": 30, + "odFGExtractorMode": 2, + "odVideoScaleFactor": 1, + "odSceneType": 1, + "odCameraView": 3, + "odCameraFOV": 2, + "odBackgroundLearnStationary": true, + "odBackgroundLearnStationarySpeed": 15, + "odClassifierQualityProfile": 1, + "odEnableVideoAnalyticsWhileStreaming": false, + "wlanMac": "XX:XX:XX:XX:XX:XX", + "region": "us-east-1", + "enableWifiAnalyticsLib": false, + "ivLicense": "" + }, + "pirLevel": "medium", + "odLevel": "medium" + }, + "__v": 0, + "cameraStatus": { + "firmwareVersion": "2.6.1.107", + "netConfigVersion": "", + "camAgentVersion": "", + "lastLogin": 1600639997, + "lastLogout": 1600639944, + "wlanMac": "XX:XX:XX:XX:XX:XX", + "fwDownloadVersion": "", + "fwDownloadPercentage": 0, + "recovered": false, + "recoveredFromVersion": "", + "_id": "1234567890", + "initErrors": [], + "speedTestTokenCreated": 1600235629 + }, + "supportedFeatures": { + "providers": { + "webrtc": "none", + "recording": "simplisafe", + "live": "simplisafe" + }, + "audioEncodings": [ + "speex" + ], + "resolutions": [ + "480p", + "720p" + ], + "_id": "1234567890", + "pir": true, + "videoAnalytics": false, + "privacyShutter": true, + "microphone": true, + "fullDuplexAudio": false, + "wired": true, + "networkSpeedTest": false, + "videoEncoding": "h264" + }, + "subscription": { + "enabled": true, + "freeTrialActive": false, + "freeTrialUsed": true, + "freeTrialEnds": 0, + "freeTrialExpires": 0, + "planSku": "SSVM1", + "price": 0, + "expires": 0, + "storageDays": 30, + "trialUsed": true, + "trialActive": false, + "trialExpires": 0 + }, + "status": "online" + } + ], + "connType": "wifi", + "stateUpdated": 1601502948, + "messages": [ + { + "_id": "xxxxxxxxxxxxxxxxxxxxxxxx", + "id": "xxxxxxxxxxxxxxxxxxxxxxxx", + "textTemplate": "Power Outage - Backup battery in use.", + "data": { + "time": "2020-02-16T03:20:28+00:00" + }, + "text": "Power Outage - Backup battery in use.", + "code": "2000", + "filters": [], + "link": "http://link.to.info", + "linkLabel": "More Info", + "expiration": 0, + "category": "error", + "timestamp": 1581823228 + } + ], + "powerOutage": false, + "lastPowerOutage": 1581991064, + "lastSuccessfulWifiTS": 1601424776, + "isOffline": false + } + }, + "pinUnlocked": true, + "billDate": 1602887552, + "billInterval": 2628000, + "pinUnlockedBy": "pin", + "autoActivation": null + } +} diff --git a/tests/components/simplisafe/test_diagnostics.py b/tests/components/simplisafe/test_diagnostics.py new file mode 100644 index 00000000000000..d2c2866bf5b790 --- /dev/null +++ b/tests/components/simplisafe/test_diagnostics.py @@ -0,0 +1,226 @@ +"""Test SimpliSafe diagnostics.""" +from homeassistant.components.diagnostics import REDACTED + +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_entry_diagnostics(hass, config_entry, hass_client, setup_simplisafe): + """Test config entry diagnostics.""" + assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { + "entry": {"options": {}}, + "systems": [ + { + "address": REDACTED, + "alarm_going_off": False, + "connection_type": "wifi", + "notifications": [], + "serial": REDACTED, + "state": 99, + "system_id": REDACTED, + "temperature": 67, + "version": 3, + "sensors": [ + { + "name": "Fire Door", + "serial": REDACTED, + "type": 5, + "error": False, + "low_battery": False, + "offline": False, + "settings": { + "instantTrigger": False, + "away2": 1, + "away": 1, + "home2": 1, + "home": 1, + "off": 0, + }, + "trigger_instantly": False, + "triggered": False, + }, + { + "name": "Front Door", + "serial": REDACTED, + "type": 12, + "error": False, + "low_battery": False, + "offline": False, + "settings": { + "instantTrigger": False, + "away2": 1, + "away": 1, + "home2": 1, + "home": 1, + "off": 0, + }, + "trigger_instantly": False, + "triggered": False, + }, + ], + "alarm_duration": 240, + "alarm_volume": 3, + "battery_backup_power_level": 5293, + "cameras": [ + { + "camera_settings": { + "cameraName": "Camera", + "pictureQuality": "720p", + "nightVision": "auto", + "statusLight": "off", + "micSensitivity": 100, + "micEnable": True, + "speakerVolume": 75, + "motionSensitivity": 0, + "shutterHome": "closedAlarmOnly", + "shutterAway": "open", + "shutterOff": "closedAlarmOnly", + "wifiSsid": "", + "canStream": False, + "canRecord": False, + "pirEnable": True, + "vaEnable": True, + "notificationsEnable": False, + "enableDoorbellNotification": True, + "doorbellChimeVolume": "off", + "privacyEnable": False, + "hdr": False, + "vaZoningEnable": False, + "vaZoningRows": 0, + "vaZoningCols": 0, + "vaZoningMask": [], + "maxDigitalZoom": 10, + "supportedResolutions": ["480p", "720p"], + "admin": { + "IRLED": 0, + "pirSens": 0, + "statusLEDState": 1, + "lux": "lowLux", + "motionDetectionEnabled": False, + "motionThresholdZero": 0, + "motionThresholdOne": 10000, + "levelChangeDelayZero": 30, + "levelChangeDelayOne": 10, + "audioDetectionEnabled": False, + "audioChannelNum": 2, + "audioSampleRate": 16000, + "audioChunkBytes": 2048, + "audioSampleFormat": 3, + "audioSensitivity": 50, + "audioThreshold": 50, + "audioDirection": 0, + "bitRate": 284, + "longPress": 2000, + "kframe": 1, + "gopLength": 40, + "idr": 1, + "fps": 20, + "firmwareVersion": "2.6.1.107", + "netConfigVersion": "", + "camAgentVersion": "", + "lastLogin": 1600639997, + "lastLogout": 1600639944, + "pirSampleRateMs": 800, + "pirHysteresisHigh": 2, + "pirHysteresisLow": 10, + "pirFilterCoefficient": 1, + "logEnabled": True, + "logLevel": 3, + "logQDepth": 20, + "firmwareGroup": "public", + "irOpenThreshold": 445, + "irCloseThreshold": 840, + "irOpenDelay": 3, + "irCloseDelay": 3, + "irThreshold1x": 388, + "irThreshold2x": 335, + "irThreshold3x": 260, + "rssi": [[1600935204, -43]], + "battery": [], + "dbm": 0, + "vmUse": 161592, + "resSet": 10540, + "uptime": 810043.74, + "wifiDisconnects": 1, + "wifiDriverReloads": 1, + "statsPeriod": 3600000, + "sarlaccDebugLogTypes": 0, + "odProcessingFps": 8, + "odObjectMinWidthPercent": 6, + "odObjectMinHeightPercent": 24, + "odEnableObjectDetection": True, + "odClassificationMask": 2, + "odClassificationConfidenceThreshold": 0.95, + "odEnableOverlay": False, + "odAnalyticsLib": 2, + "odSensitivity": 85, + "odEventObjectMask": 2, + "odLuxThreshold": 445, + "odLuxHysteresisHigh": 4, + "odLuxHysteresisLow": 4, + "odLuxSamplingFrequency": 30, + "odFGExtractorMode": 2, + "odVideoScaleFactor": 1, + "odSceneType": 1, + "odCameraView": 3, + "odCameraFOV": 2, + "odBackgroundLearnStationary": True, + "odBackgroundLearnStationarySpeed": 15, + "odClassifierQualityProfile": 1, + "odEnableVideoAnalyticsWhileStreaming": False, + "wlanMac": "XX:XX:XX:XX:XX:XX", + "region": "us-east-1", + "enableWifiAnalyticsLib": False, + "ivLicense": "", + }, + "pirLevel": "medium", + "odLevel": "medium", + }, + "camera_type": 0, + "name": "Camera", + "serial": REDACTED, + "shutter_open_when_away": True, + "shutter_open_when_home": False, + "shutter_open_when_off": False, + "status": "online", + "subscription_enabled": True, + }, + ], + "chime_volume": 2, + "entry_delay_away": 30, + "entry_delay_home": 30, + "exit_delay_away": 60, + "exit_delay_home": 0, + "gsm_strength": -73, + "light": True, + "locks": [ + { + "name": "Front Door", + "serial": REDACTED, + "type": 16, + "error": False, + "low_battery": False, + "offline": False, + "settings": { + "autoLock": 3, + "away": 1, + "home": 1, + "awayToOff": 0, + "homeToOff": 1, + }, + "disabled": False, + "lock_low_battery": False, + "pin_pad_low_battery": False, + "pin_pad_offline": False, + "state": 1, + } + ], + "offline": False, + "power_outage": False, + "rf_jamming": False, + "voice_prompt_volume": 2, + "wall_power_level": 5933, + "wifi_ssid": REDACTED, + "wifi_strength": -49, + } + ], + } From eb94fe1ca7e0871478a54ec832125670e1a614e2 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Sun, 30 Jan 2022 22:59:01 +0000 Subject: [PATCH 0105/1098] Use upstream constants when defining homekit service to platform mapping (#65272) --- .../homekit_controller/connection.py | 6 +- .../components/homekit_controller/const.py | 55 ++++++++++--------- tests/components/homekit_controller/common.py | 2 +- 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index 88706e6fbd0fe5..08b3e9823e3b54 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -494,7 +494,11 @@ async def async_load_platforms(self): tasks = [] for accessory in self.accessories: for service in accessory["services"]: - stype = ServicesTypes.get_short(service["type"].upper()) + try: + stype = ServicesTypes.get_short_uuid(service["type"].upper()) + except KeyError: + stype = service["type"].upper() + if stype in HOMEKIT_ACCESSORY_DISPATCH: platform = HOMEKIT_ACCESSORY_DISPATCH[stype] if platform not in self.platforms: diff --git a/homeassistant/components/homekit_controller/const.py b/homeassistant/components/homekit_controller/const.py index d88f896af31d41..9ec991c0d88646 100644 --- a/homeassistant/components/homekit_controller/const.py +++ b/homeassistant/components/homekit_controller/const.py @@ -2,6 +2,7 @@ from typing import Final from aiohomekit.model.characteristics import CharacteristicsTypes +from aiohomekit.model.services import ServicesTypes DOMAIN = "homekit_controller" @@ -20,33 +21,33 @@ # Mapping from Homekit type to component. HOMEKIT_ACCESSORY_DISPATCH = { - "lightbulb": "light", - "outlet": "switch", - "switch": "switch", - "thermostat": "climate", - "heater-cooler": "climate", - "security-system": "alarm_control_panel", - "garage-door-opener": "cover", - "window": "cover", - "window-covering": "cover", - "lock-mechanism": "lock", - "contact": "binary_sensor", - "motion": "binary_sensor", - "carbon-dioxide": "sensor", - "humidity": "sensor", - "humidifier-dehumidifier": "humidifier", - "light": "sensor", - "temperature": "sensor", - "battery": "sensor", - "smoke": "binary_sensor", - "carbon-monoxide": "binary_sensor", - "leak": "binary_sensor", - "fan": "fan", - "fanv2": "fan", - "occupancy": "binary_sensor", - "television": "media_player", - "valve": "switch", - "camera-rtp-stream-management": "camera", + ServicesTypes.LIGHTBULB: "light", + ServicesTypes.OUTLET: "switch", + ServicesTypes.SWITCH: "switch", + ServicesTypes.THERMOSTAT: "climate", + ServicesTypes.HEATER_COOLER: "climate", + ServicesTypes.SECURITY_SYSTEM: "alarm_control_panel", + ServicesTypes.GARAGE_DOOR_OPENER: "cover", + ServicesTypes.WINDOW: "cover", + ServicesTypes.WINDOW_COVERING: "cover", + ServicesTypes.LOCK_MECHANISM: "lock", + ServicesTypes.CONTACT_SENSOR: "binary_sensor", + ServicesTypes.MOTION_SENSOR: "binary_sensor", + ServicesTypes.CARBON_DIOXIDE_SENSOR: "sensor", + ServicesTypes.HUMIDITY_SENSOR: "sensor", + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER: "humidifier", + ServicesTypes.LIGHT_SENSOR: "sensor", + ServicesTypes.TEMPERATURE_SENSOR: "sensor", + ServicesTypes.BATTERY_SERVICE: "sensor", + ServicesTypes.SMOKE_SENSOR: "binary_sensor", + ServicesTypes.CARBON_MONOXIDE_SENSOR: "binary_sensor", + ServicesTypes.LEAK_SENSOR: "binary_sensor", + ServicesTypes.FAN: "fan", + ServicesTypes.FAN_V2: "fan", + ServicesTypes.OCCUPANCY_SENSOR: "binary_sensor", + ServicesTypes.TELEVISION: "media_player", + ServicesTypes.VALVE: "switch", + ServicesTypes.CAMERA_RTP_STREAM_MANAGEMENT: "camera", } CHARACTERISTIC_PLATFORMS = { diff --git a/tests/components/homekit_controller/common.py b/tests/components/homekit_controller/common.py index ef77d5bc65246e..802c150b981755 100644 --- a/tests/components/homekit_controller/common.py +++ b/tests/components/homekit_controller/common.py @@ -230,7 +230,7 @@ async def setup_test_component(hass, setup_accessory, capitalize=False, suffix=N domain = None for service in accessory.services: - service_name = ServicesTypes.get_short(service.type) + service_name = ServicesTypes.get_short_uuid(service.type) if service_name in HOMEKIT_ACCESSORY_DISPATCH: domain = HOMEKIT_ACCESSORY_DISPATCH[service_name] break From 58b8c30221a6f6e5acbbe98b7e3298b03fb741f5 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Sun, 30 Jan 2022 22:59:39 +0000 Subject: [PATCH 0106/1098] Improve homekit_controller tests (#65266) --- tests/components/homekit_controller/common.py | 59 ++- .../specific_devices/test_koogeek_ls1.py | 20 +- .../test_alarm_control_panel.py | 56 ++- .../homekit_controller/test_binary_sensor.py | 57 ++- .../homekit_controller/test_button.py | 24 +- .../homekit_controller/test_climate.py | 395 ++++++++++++------ .../homekit_controller/test_cover.py | 121 ++++-- .../components/homekit_controller/test_fan.py | 385 ++++++++++++----- .../homekit_controller/test_humidifier.py | 280 +++++++++---- .../homekit_controller/test_light.py | 106 +++-- .../homekit_controller/test_lock.py | 73 +++- .../homekit_controller/test_media_player.py | 198 ++++++--- .../homekit_controller/test_number.py | 62 +-- .../homekit_controller/test_select.py | 53 ++- .../homekit_controller/test_sensor.py | 147 ++++--- .../homekit_controller/test_switch.py | 101 +++-- 16 files changed, 1487 insertions(+), 650 deletions(-) diff --git a/tests/components/homekit_controller/common.py b/tests/components/homekit_controller/common.py index 802c150b981755..0891d6209b7921 100644 --- a/tests/components/homekit_controller/common.py +++ b/tests/components/homekit_controller/common.py @@ -10,9 +10,8 @@ from unittest import mock from aiohomekit.model import Accessories, Accessory -from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes -from aiohomekit.testing import FakeController +from aiohomekit.testing import FakeController, FakePairing from homeassistant.components import zeroconf from homeassistant.components.device_automation import DeviceAutomationType @@ -24,7 +23,8 @@ IDENTIFIER_ACCESSORY_ID, IDENTIFIER_SERIAL_NUMBER, ) -from homeassistant.core import HomeAssistant +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, State, callback from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.entity import EntityCategory from homeassistant.setup import async_setup_component @@ -94,7 +94,14 @@ class DeviceTestInfo: class Helper: """Helper methods for interacting with HomeKit fakes.""" - def __init__(self, hass, entity_id, pairing, accessory, config_entry): + def __init__( + self, + hass: HomeAssistant, + entity_id: str, + pairing: FakePairing, + accessory: Accessory, + config_entry: ConfigEntry, + ) -> None: """Create a helper for a given accessory/entity.""" self.hass = hass self.entity_id = entity_id @@ -102,19 +109,43 @@ def __init__(self, hass, entity_id, pairing, accessory, config_entry): self.accessory = accessory self.config_entry = config_entry - self.characteristics = {} - for service in self.accessory.services: - service_name = ServicesTypes.get_short(service.type) - for char in service.characteristics: - char_name = CharacteristicsTypes.get_short(char.type) - self.characteristics[(service_name, char_name)] = char + async def async_update( + self, service: str, characteristics: dict[str, Any] + ) -> State: + """Set the characteristics on this service.""" + changes = [] + + service = self.accessory.services.first(service_type=service) + aid = service.accessory.aid + + for ctype, value in characteristics.items(): + char = service.characteristics.first(char_types=[ctype]) + changes.append((aid, char.iid, value)) + + self.pairing.testing.update_aid_iid(changes) + + if not self.pairing.testing.events_enabled: + # If events aren't enabled, explicitly do a poll + # If they are enabled, then HA will pick up the changes next time + # we yield control + await time_changed(self.hass, 60) - async def update_named_service(self, service, characteristics): - """Update a service.""" - self.pairing.testing.update_named_service(service, characteristics) await self.hass.async_block_till_done() - async def poll_and_get_state(self): + state = self.hass.states.get(self.entity_id) + assert state is not None + return state + + @callback + def async_assert_service_values( + self, service: str, characteristics: dict[str, Any] + ) -> None: + """Assert a service has characteristics with these values.""" + service = self.accessory.services.first(service_type=service) + for ctype, value in characteristics.items(): + assert service.value(ctype) == value + + async def poll_and_get_state(self) -> State: """Trigger a time based poll and return the current entity state.""" await time_changed(self.hass, 60) diff --git a/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py b/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py index 9591eb27b6ffa7..33a1ebdbafefb2 100644 --- a/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py +++ b/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py @@ -4,6 +4,7 @@ from unittest import mock from aiohomekit.exceptions import AccessoryDisconnectedError, EncryptionError +from aiohomekit.model import CharacteristicsTypes, ServicesTypes from aiohomekit.testing import FakePairing import pytest @@ -72,26 +73,29 @@ async def test_recover_from_failure(hass, utcnow, failure_cls): accessories = await setup_accessories_from_file(hass, "koogeek_ls1.json") config_entry, pairing = await setup_test_accessories(hass, accessories) + pairing.testing.events_enabled = False + helper = Helper( hass, "light.koogeek_ls1_20833f", pairing, accessories[0], config_entry ) # Set light state on fake device to off - helper.characteristics[LIGHT_ON].set_value(False) + state = await helper.async_update( + ServicesTypes.LIGHTBULB, {CharacteristicsTypes.ON: False} + ) # Test that entity starts off in a known state - state = await helper.poll_and_get_state() assert state.state == "off" - # Set light state on fake device to on - helper.characteristics[LIGHT_ON].set_value(True) - # Test that entity remains in the same state if there is a network error next_update = dt_util.utcnow() + timedelta(seconds=60) with mock.patch.object(FakePairing, "get_characteristics") as get_char: get_char.side_effect = failure_cls("Disconnected") - state = await helper.poll_and_get_state() + # Set light state on fake device to on + state = await helper.async_update( + ServicesTypes.LIGHTBULB, {CharacteristicsTypes.ON: True} + ) assert state.state == "off" chars = get_char.call_args[0][0] @@ -102,5 +106,7 @@ async def test_recover_from_failure(hass, utcnow, failure_cls): async_fire_time_changed(hass, next_update) await hass.async_block_till_done() - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.LIGHTBULB, {CharacteristicsTypes.ON: True} + ) assert state.state == "on" diff --git a/tests/components/homekit_controller/test_alarm_control_panel.py b/tests/components/homekit_controller/test_alarm_control_panel.py index 5694be5f955fad..2804ffff824895 100644 --- a/tests/components/homekit_controller/test_alarm_control_panel.py +++ b/tests/components/homekit_controller/test_alarm_control_panel.py @@ -4,9 +4,6 @@ from tests.components.homekit_controller.common import setup_test_component -CURRENT_STATE = ("security-system", "security-system-state.current") -TARGET_STATE = ("security-system", "security-system-state.target") - def create_security_system_service(accessory): """Define a security-system characteristics as per page 219 of HAP spec.""" @@ -36,7 +33,12 @@ async def test_switch_change_alarm_state(hass, utcnow): {"entity_id": "alarm_control_panel.testdevice"}, blocking=True, ) - assert helper.characteristics[TARGET_STATE].value == 0 + helper.async_assert_service_values( + ServicesTypes.SECURITY_SYSTEM, + { + CharacteristicsTypes.SECURITY_SYSTEM_STATE_TARGET: 0, + }, + ) await hass.services.async_call( "alarm_control_panel", @@ -44,7 +46,12 @@ async def test_switch_change_alarm_state(hass, utcnow): {"entity_id": "alarm_control_panel.testdevice"}, blocking=True, ) - assert helper.characteristics[TARGET_STATE].value == 1 + helper.async_assert_service_values( + ServicesTypes.SECURITY_SYSTEM, + { + CharacteristicsTypes.SECURITY_SYSTEM_STATE_TARGET: 1, + }, + ) await hass.services.async_call( "alarm_control_panel", @@ -52,7 +59,12 @@ async def test_switch_change_alarm_state(hass, utcnow): {"entity_id": "alarm_control_panel.testdevice"}, blocking=True, ) - assert helper.characteristics[TARGET_STATE].value == 2 + helper.async_assert_service_values( + ServicesTypes.SECURITY_SYSTEM, + { + CharacteristicsTypes.SECURITY_SYSTEM_STATE_TARGET: 2, + }, + ) await hass.services.async_call( "alarm_control_panel", @@ -60,30 +72,50 @@ async def test_switch_change_alarm_state(hass, utcnow): {"entity_id": "alarm_control_panel.testdevice"}, blocking=True, ) - assert helper.characteristics[TARGET_STATE].value == 3 + helper.async_assert_service_values( + ServicesTypes.SECURITY_SYSTEM, + { + CharacteristicsTypes.SECURITY_SYSTEM_STATE_TARGET: 3, + }, + ) async def test_switch_read_alarm_state(hass, utcnow): """Test that we can read the state of a HomeKit alarm accessory.""" helper = await setup_test_component(hass, create_security_system_service) - helper.characteristics[CURRENT_STATE].value = 0 + await helper.async_update( + ServicesTypes.SECURITY_SYSTEM, + {CharacteristicsTypes.SECURITY_SYSTEM_STATE_CURRENT: 0}, + ) state = await helper.poll_and_get_state() assert state.state == "armed_home" assert state.attributes["battery_level"] == 50 - helper.characteristics[CURRENT_STATE].value = 1 + await helper.async_update( + ServicesTypes.SECURITY_SYSTEM, + {CharacteristicsTypes.SECURITY_SYSTEM_STATE_CURRENT: 1}, + ) state = await helper.poll_and_get_state() assert state.state == "armed_away" - helper.characteristics[CURRENT_STATE].value = 2 + await helper.async_update( + ServicesTypes.SECURITY_SYSTEM, + {CharacteristicsTypes.SECURITY_SYSTEM_STATE_CURRENT: 2}, + ) state = await helper.poll_and_get_state() assert state.state == "armed_night" - helper.characteristics[CURRENT_STATE].value = 3 + await helper.async_update( + ServicesTypes.SECURITY_SYSTEM, + {CharacteristicsTypes.SECURITY_SYSTEM_STATE_CURRENT: 3}, + ) state = await helper.poll_and_get_state() assert state.state == "disarmed" - helper.characteristics[CURRENT_STATE].value = 4 + await helper.async_update( + ServicesTypes.SECURITY_SYSTEM, + {CharacteristicsTypes.SECURITY_SYSTEM_STATE_CURRENT: 4}, + ) state = await helper.poll_and_get_state() assert state.state == "triggered" diff --git a/tests/components/homekit_controller/test_binary_sensor.py b/tests/components/homekit_controller/test_binary_sensor.py index e0b23775c4d5ec..d83beb07df3699 100644 --- a/tests/components/homekit_controller/test_binary_sensor.py +++ b/tests/components/homekit_controller/test_binary_sensor.py @@ -6,13 +6,6 @@ from tests.components.homekit_controller.common import setup_test_component -MOTION_DETECTED = ("motion", "motion-detected") -CONTACT_STATE = ("contact", "contact-state") -SMOKE_DETECTED = ("smoke", "smoke-detected") -CARBON_MONOXIDE_DETECTED = ("carbon-monoxide", "carbon-monoxide.detected") -OCCUPANCY_DETECTED = ("occupancy", "occupancy-detected") -LEAK_DETECTED = ("leak", "leak-detected") - def create_motion_sensor_service(accessory): """Define motion characteristics as per page 225 of HAP spec.""" @@ -26,11 +19,15 @@ async def test_motion_sensor_read_state(hass, utcnow): """Test that we can read the state of a HomeKit motion sensor accessory.""" helper = await setup_test_component(hass, create_motion_sensor_service) - helper.characteristics[MOTION_DETECTED].value = False + await helper.async_update( + ServicesTypes.MOTION_SENSOR, {CharacteristicsTypes.MOTION_DETECTED: False} + ) state = await helper.poll_and_get_state() assert state.state == "off" - helper.characteristics[MOTION_DETECTED].value = True + await helper.async_update( + ServicesTypes.MOTION_SENSOR, {CharacteristicsTypes.MOTION_DETECTED: True} + ) state = await helper.poll_and_get_state() assert state.state == "on" @@ -49,11 +46,15 @@ async def test_contact_sensor_read_state(hass, utcnow): """Test that we can read the state of a HomeKit contact accessory.""" helper = await setup_test_component(hass, create_contact_sensor_service) - helper.characteristics[CONTACT_STATE].value = 0 + await helper.async_update( + ServicesTypes.CONTACT_SENSOR, {CharacteristicsTypes.CONTACT_STATE: 0} + ) state = await helper.poll_and_get_state() assert state.state == "off" - helper.characteristics[CONTACT_STATE].value = 1 + await helper.async_update( + ServicesTypes.CONTACT_SENSOR, {CharacteristicsTypes.CONTACT_STATE: 1} + ) state = await helper.poll_and_get_state() assert state.state == "on" @@ -72,11 +73,15 @@ async def test_smoke_sensor_read_state(hass, utcnow): """Test that we can read the state of a HomeKit contact accessory.""" helper = await setup_test_component(hass, create_smoke_sensor_service) - helper.characteristics[SMOKE_DETECTED].value = 0 + await helper.async_update( + ServicesTypes.SMOKE_SENSOR, {CharacteristicsTypes.SMOKE_DETECTED: 0} + ) state = await helper.poll_and_get_state() assert state.state == "off" - helper.characteristics[SMOKE_DETECTED].value = 1 + await helper.async_update( + ServicesTypes.SMOKE_SENSOR, {CharacteristicsTypes.SMOKE_DETECTED: 1} + ) state = await helper.poll_and_get_state() assert state.state == "on" @@ -95,11 +100,17 @@ async def test_carbon_monoxide_sensor_read_state(hass, utcnow): """Test that we can read the state of a HomeKit contact accessory.""" helper = await setup_test_component(hass, create_carbon_monoxide_sensor_service) - helper.characteristics[CARBON_MONOXIDE_DETECTED].value = 0 + await helper.async_update( + ServicesTypes.CARBON_MONOXIDE_SENSOR, + {CharacteristicsTypes.CARBON_MONOXIDE_DETECTED: 0}, + ) state = await helper.poll_and_get_state() assert state.state == "off" - helper.characteristics[CARBON_MONOXIDE_DETECTED].value = 1 + await helper.async_update( + ServicesTypes.CARBON_MONOXIDE_SENSOR, + {CharacteristicsTypes.CARBON_MONOXIDE_DETECTED: 1}, + ) state = await helper.poll_and_get_state() assert state.state == "on" @@ -118,11 +129,15 @@ async def test_occupancy_sensor_read_state(hass, utcnow): """Test that we can read the state of a HomeKit occupancy sensor accessory.""" helper = await setup_test_component(hass, create_occupancy_sensor_service) - helper.characteristics[OCCUPANCY_DETECTED].value = False + await helper.async_update( + ServicesTypes.OCCUPANCY_SENSOR, {CharacteristicsTypes.OCCUPANCY_DETECTED: False} + ) state = await helper.poll_and_get_state() assert state.state == "off" - helper.characteristics[OCCUPANCY_DETECTED].value = True + await helper.async_update( + ServicesTypes.OCCUPANCY_SENSOR, {CharacteristicsTypes.OCCUPANCY_DETECTED: True} + ) state = await helper.poll_and_get_state() assert state.state == "on" @@ -141,11 +156,15 @@ async def test_leak_sensor_read_state(hass, utcnow): """Test that we can read the state of a HomeKit leak sensor accessory.""" helper = await setup_test_component(hass, create_leak_sensor_service) - helper.characteristics[LEAK_DETECTED].value = 0 + await helper.async_update( + ServicesTypes.LEAK_SENSOR, {CharacteristicsTypes.LEAK_DETECTED: 0} + ) state = await helper.poll_and_get_state() assert state.state == "off" - helper.characteristics[LEAK_DETECTED].value = 1 + await helper.async_update( + ServicesTypes.LEAK_SENSOR, {CharacteristicsTypes.LEAK_DETECTED: 1} + ) state = await helper.poll_and_get_state() assert state.state == "on" diff --git a/tests/components/homekit_controller/test_button.py b/tests/components/homekit_controller/test_button.py index c501a9e6fb084c..131fed572f752a 100644 --- a/tests/components/homekit_controller/test_button.py +++ b/tests/components/homekit_controller/test_button.py @@ -40,7 +40,7 @@ async def test_press_button(hass): helper = await setup_test_component(hass, create_switch_with_setup_button) # Helper will be for the primary entity, which is the outlet. Make a helper for the button. - energy_helper = Helper( + button = Helper( hass, "button.testdevice_setup", helper.pairing, @@ -48,16 +48,18 @@ async def test_press_button(hass): helper.config_entry, ) - outlet = energy_helper.accessory.services.first(service_type=ServicesTypes.OUTLET) - setup = outlet[CharacteristicsTypes.Vendor.HAA_SETUP] - await hass.services.async_call( "button", "press", {"entity_id": "button.testdevice_setup"}, blocking=True, ) - assert setup.value == "#HAA@trcmd" + button.async_assert_service_values( + ServicesTypes.OUTLET, + { + CharacteristicsTypes.Vendor.HAA_SETUP: "#HAA@trcmd", + }, + ) async def test_ecobee_clear_hold_press_button(hass): @@ -67,7 +69,7 @@ async def test_ecobee_clear_hold_press_button(hass): ) # Helper will be for the primary entity, which is the outlet. Make a helper for the button. - energy_helper = Helper( + clear_hold = Helper( hass, "button.testdevice_clear_hold", helper.pairing, @@ -75,13 +77,15 @@ async def test_ecobee_clear_hold_press_button(hass): helper.config_entry, ) - outlet = energy_helper.accessory.services.first(service_type=ServicesTypes.OUTLET) - setup = outlet[CharacteristicsTypes.Vendor.ECOBEE_CLEAR_HOLD] - await hass.services.async_call( "button", "press", {"entity_id": "button.testdevice_clear_hold"}, blocking=True, ) - assert setup.value is True + clear_hold.async_assert_service_values( + ServicesTypes.OUTLET, + { + CharacteristicsTypes.Vendor.ECOBEE_CLEAR_HOLD: True, + }, + ) diff --git a/tests/components/homekit_controller/test_climate.py b/tests/components/homekit_controller/test_climate.py index 07a5025ac88acd..9ca45fd53ac190 100644 --- a/tests/components/homekit_controller/test_climate.py +++ b/tests/components/homekit_controller/test_climate.py @@ -22,21 +22,6 @@ from tests.components.homekit_controller.common import setup_test_component -HEATING_COOLING_TARGET = ("thermostat", "heating-cooling.target") -HEATING_COOLING_CURRENT = ("thermostat", "heating-cooling.current") -THERMOSTAT_TEMPERATURE_COOLING_THRESHOLD = ( - "thermostat", - "temperature.cooling-threshold", -) -THERMOSTAT_TEMPERATURE_HEATING_THRESHOLD = ( - "thermostat", - "temperature.heating-threshold", -) -TEMPERATURE_TARGET = ("thermostat", "temperature.target") -TEMPERATURE_CURRENT = ("thermostat", "temperature.current") -HUMIDITY_TARGET = ("thermostat", "relative-humidity.target") -HUMIDITY_CURRENT = ("thermostat", "relative-humidity.current") - # Test thermostat devices @@ -116,8 +101,12 @@ async def test_climate_change_thermostat_state(hass, utcnow): {"entity_id": "climate.testdevice", "hvac_mode": HVAC_MODE_HEAT}, blocking=True, ) - - assert helper.characteristics[HEATING_COOLING_TARGET].value == 1 + helper.async_assert_service_values( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.HEATING_COOLING_TARGET: 1, + }, + ) await hass.services.async_call( DOMAIN, @@ -125,7 +114,12 @@ async def test_climate_change_thermostat_state(hass, utcnow): {"entity_id": "climate.testdevice", "hvac_mode": HVAC_MODE_COOL}, blocking=True, ) - assert helper.characteristics[HEATING_COOLING_TARGET].value == 2 + helper.async_assert_service_values( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.HEATING_COOLING_TARGET: 2, + }, + ) await hass.services.async_call( DOMAIN, @@ -133,7 +127,12 @@ async def test_climate_change_thermostat_state(hass, utcnow): {"entity_id": "climate.testdevice", "hvac_mode": HVAC_MODE_HEAT_COOL}, blocking=True, ) - assert helper.characteristics[HEATING_COOLING_TARGET].value == 3 + helper.async_assert_service_values( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.HEATING_COOLING_TARGET: 3, + }, + ) await hass.services.async_call( DOMAIN, @@ -141,7 +140,12 @@ async def test_climate_change_thermostat_state(hass, utcnow): {"entity_id": "climate.testdevice", "hvac_mode": HVAC_MODE_OFF}, blocking=True, ) - assert helper.characteristics[HEATING_COOLING_TARGET].value == 0 + helper.async_assert_service_values( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.HEATING_COOLING_TARGET: 0, + }, + ) async def test_climate_check_min_max_values_per_mode(hass, utcnow): @@ -189,7 +193,12 @@ async def test_climate_change_thermostat_temperature(hass, utcnow): {"entity_id": "climate.testdevice", "temperature": 21}, blocking=True, ) - assert helper.characteristics[TEMPERATURE_TARGET].value == 21 + helper.async_assert_service_values( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.TEMPERATURE_TARGET: 21, + }, + ) await hass.services.async_call( DOMAIN, @@ -197,7 +206,12 @@ async def test_climate_change_thermostat_temperature(hass, utcnow): {"entity_id": "climate.testdevice", "temperature": 25}, blocking=True, ) - assert helper.characteristics[TEMPERATURE_TARGET].value == 25 + helper.async_assert_service_values( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.TEMPERATURE_TARGET: 25, + }, + ) async def test_climate_change_thermostat_temperature_range(hass, utcnow): @@ -222,9 +236,15 @@ async def test_climate_change_thermostat_temperature_range(hass, utcnow): }, blocking=True, ) - assert helper.characteristics[TEMPERATURE_TARGET].value == 22.5 - assert helper.characteristics[THERMOSTAT_TEMPERATURE_HEATING_THRESHOLD].value == 20 - assert helper.characteristics[THERMOSTAT_TEMPERATURE_COOLING_THRESHOLD].value == 25 + + helper.async_assert_service_values( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.TEMPERATURE_TARGET: 22.5, + CharacteristicsTypes.TEMPERATURE_HEATING_THRESHOLD: 20, + CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD: 25, + }, + ) async def test_climate_change_thermostat_temperature_range_iphone(hass, utcnow): @@ -250,9 +270,14 @@ async def test_climate_change_thermostat_temperature_range_iphone(hass, utcnow): }, blocking=True, ) - assert helper.characteristics[TEMPERATURE_TARGET].value == 22 - assert helper.characteristics[THERMOSTAT_TEMPERATURE_HEATING_THRESHOLD].value == 20 - assert helper.characteristics[THERMOSTAT_TEMPERATURE_COOLING_THRESHOLD].value == 24 + helper.async_assert_service_values( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.TEMPERATURE_TARGET: 22, + CharacteristicsTypes.TEMPERATURE_HEATING_THRESHOLD: 20, + CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD: 24, + }, + ) async def test_climate_cannot_set_thermostat_temp_range_in_wrong_mode(hass, utcnow): @@ -277,9 +302,14 @@ async def test_climate_cannot_set_thermostat_temp_range_in_wrong_mode(hass, utcn }, blocking=True, ) - assert helper.characteristics[TEMPERATURE_TARGET].value == 22 - assert helper.characteristics[THERMOSTAT_TEMPERATURE_HEATING_THRESHOLD].value == 0 - assert helper.characteristics[THERMOSTAT_TEMPERATURE_COOLING_THRESHOLD].value == 0 + helper.async_assert_service_values( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.TEMPERATURE_TARGET: 22, + CharacteristicsTypes.TEMPERATURE_HEATING_THRESHOLD: 0, + CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD: 0, + }, + ) def create_thermostat_single_set_point_auto(accessory): @@ -359,7 +389,12 @@ async def test_climate_set_thermostat_temp_on_sspa_device(hass, utcnow): {"entity_id": "climate.testdevice", "temperature": 21}, blocking=True, ) - assert helper.characteristics[TEMPERATURE_TARGET].value == 21 + helper.async_assert_service_values( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.TEMPERATURE_TARGET: 21, + }, + ) await hass.services.async_call( DOMAIN, @@ -367,7 +402,12 @@ async def test_climate_set_thermostat_temp_on_sspa_device(hass, utcnow): {"entity_id": "climate.testdevice", "hvac_mode": HVAC_MODE_HEAT_COOL}, blocking=True, ) - assert helper.characteristics[TEMPERATURE_TARGET].value == 21 + helper.async_assert_service_values( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.TEMPERATURE_TARGET: 21, + }, + ) await hass.services.async_call( DOMAIN, @@ -378,7 +418,12 @@ async def test_climate_set_thermostat_temp_on_sspa_device(hass, utcnow): }, blocking=True, ) - assert helper.characteristics[TEMPERATURE_TARGET].value == 22 + helper.async_assert_service_values( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.TEMPERATURE_TARGET: 22, + }, + ) async def test_climate_set_mode_via_temp(hass, utcnow): @@ -395,8 +440,13 @@ async def test_climate_set_mode_via_temp(hass, utcnow): }, blocking=True, ) - assert helper.characteristics[TEMPERATURE_TARGET].value == 21 - assert helper.characteristics[HEATING_COOLING_TARGET].value == 1 + helper.async_assert_service_values( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.TEMPERATURE_TARGET: 21, + CharacteristicsTypes.HEATING_COOLING_TARGET: 1, + }, + ) await hass.services.async_call( DOMAIN, @@ -408,8 +458,13 @@ async def test_climate_set_mode_via_temp(hass, utcnow): }, blocking=True, ) - assert helper.characteristics[TEMPERATURE_TARGET].value == 22 - assert helper.characteristics[HEATING_COOLING_TARGET].value == 3 + helper.async_assert_service_values( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.TEMPERATURE_TARGET: 22, + CharacteristicsTypes.HEATING_COOLING_TARGET: 3, + }, + ) async def test_climate_change_thermostat_humidity(hass, utcnow): @@ -422,7 +477,12 @@ async def test_climate_change_thermostat_humidity(hass, utcnow): {"entity_id": "climate.testdevice", "humidity": 50}, blocking=True, ) - assert helper.characteristics[HUMIDITY_TARGET].value == 50 + helper.async_assert_service_values( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET: 50, + }, + ) await hass.services.async_call( DOMAIN, @@ -430,7 +490,12 @@ async def test_climate_change_thermostat_humidity(hass, utcnow): {"entity_id": "climate.testdevice", "humidity": 45}, blocking=True, ) - assert helper.characteristics[HUMIDITY_TARGET].value == 45 + helper.async_assert_service_values( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET: 45, + }, + ) async def test_climate_read_thermostat_state(hass, utcnow): @@ -438,12 +503,17 @@ async def test_climate_read_thermostat_state(hass, utcnow): helper = await setup_test_component(hass, create_thermostat_service) # Simulate that heating is on - helper.characteristics[TEMPERATURE_CURRENT].value = 19 - helper.characteristics[TEMPERATURE_TARGET].value = 21 - helper.characteristics[HEATING_COOLING_CURRENT].value = 1 - helper.characteristics[HEATING_COOLING_TARGET].value = 1 - helper.characteristics[HUMIDITY_CURRENT].value = 50 - helper.characteristics[HUMIDITY_TARGET].value = 45 + await helper.async_update( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.TEMPERATURE_CURRENT: 19, + CharacteristicsTypes.TEMPERATURE_TARGET: 21, + CharacteristicsTypes.HEATING_COOLING_CURRENT: 1, + CharacteristicsTypes.HEATING_COOLING_TARGET: 1, + CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT: 50, + CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET: 45, + }, + ) state = await helper.poll_and_get_state() assert state.state == HVAC_MODE_HEAT @@ -453,12 +523,17 @@ async def test_climate_read_thermostat_state(hass, utcnow): assert state.attributes["max_temp"] == 35 # Simulate that cooling is on - helper.characteristics[TEMPERATURE_CURRENT].value = 21 - helper.characteristics[TEMPERATURE_TARGET].value = 19 - helper.characteristics[HEATING_COOLING_CURRENT].value = 2 - helper.characteristics[HEATING_COOLING_TARGET].value = 2 - helper.characteristics[HUMIDITY_CURRENT].value = 45 - helper.characteristics[HUMIDITY_TARGET].value = 45 + await helper.async_update( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.TEMPERATURE_CURRENT: 21, + CharacteristicsTypes.TEMPERATURE_TARGET: 19, + CharacteristicsTypes.HEATING_COOLING_CURRENT: 2, + CharacteristicsTypes.HEATING_COOLING_TARGET: 2, + CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT: 45, + CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET: 45, + }, + ) state = await helper.poll_and_get_state() assert state.state == HVAC_MODE_COOL @@ -466,10 +541,15 @@ async def test_climate_read_thermostat_state(hass, utcnow): assert state.attributes["current_humidity"] == 45 # Simulate that we are in heat/cool mode - helper.characteristics[TEMPERATURE_CURRENT].value = 21 - helper.characteristics[TEMPERATURE_TARGET].value = 21 - helper.characteristics[HEATING_COOLING_CURRENT].value = 0 - helper.characteristics[HEATING_COOLING_TARGET].value = 3 + await helper.async_update( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.TEMPERATURE_CURRENT: 21, + CharacteristicsTypes.TEMPERATURE_TARGET: 21, + CharacteristicsTypes.HEATING_COOLING_CURRENT: 0, + CharacteristicsTypes.HEATING_COOLING_TARGET: 3, + }, + ) state = await helper.poll_and_get_state() assert state.state == HVAC_MODE_HEAT_COOL @@ -481,12 +561,17 @@ async def test_hvac_mode_vs_hvac_action(hass, utcnow): # Simulate that current temperature is above target temp # Heating might be on, but hvac_action currently 'off' - helper.characteristics[TEMPERATURE_CURRENT].value = 22 - helper.characteristics[TEMPERATURE_TARGET].value = 21 - helper.characteristics[HEATING_COOLING_CURRENT].value = 0 - helper.characteristics[HEATING_COOLING_TARGET].value = 1 - helper.characteristics[HUMIDITY_CURRENT].value = 50 - helper.characteristics[HUMIDITY_TARGET].value = 45 + await helper.async_update( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.TEMPERATURE_CURRENT: 22, + CharacteristicsTypes.TEMPERATURE_TARGET: 21, + CharacteristicsTypes.HEATING_COOLING_CURRENT: 0, + CharacteristicsTypes.HEATING_COOLING_TARGET: 1, + CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT: 50, + CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET: 45, + }, + ) state = await helper.poll_and_get_state() assert state.state == "heat" @@ -494,23 +579,19 @@ async def test_hvac_mode_vs_hvac_action(hass, utcnow): # Simulate that current temperature is below target temp # Heating might be on and hvac_action currently 'heat' - helper.characteristics[TEMPERATURE_CURRENT].value = 19 - helper.characteristics[HEATING_COOLING_CURRENT].value = 1 + await helper.async_update( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.TEMPERATURE_CURRENT: 19, + CharacteristicsTypes.HEATING_COOLING_CURRENT: 1, + }, + ) state = await helper.poll_and_get_state() assert state.state == "heat" assert state.attributes["hvac_action"] == "heating" -TARGET_HEATER_COOLER_STATE = ("heater-cooler", "heater-cooler.state.target") -CURRENT_HEATER_COOLER_STATE = ("heater-cooler", "heater-cooler.state.current") -HEATER_COOLER_ACTIVE = ("heater-cooler", "active") -HEATER_COOLER_TEMPERATURE_CURRENT = ("heater-cooler", "temperature.current") -TEMPERATURE_COOLING_THRESHOLD = ("heater-cooler", "temperature.cooling-threshold") -TEMPERATURE_HEATING_THRESHOLD = ("heater-cooler", "temperature.heating-threshold") -SWING_MODE = ("heater-cooler", "swing-mode") - - def create_heater_cooler_service(accessory): """Define thermostat characteristics.""" service = accessory.add_service(ServicesTypes.HEATER_COOLER) @@ -583,10 +664,11 @@ async def test_heater_cooler_change_thermostat_state(hass, utcnow): {"entity_id": "climate.testdevice", "hvac_mode": HVAC_MODE_HEAT}, blocking=True, ) - - assert ( - helper.characteristics[TARGET_HEATER_COOLER_STATE].value - == TargetHeaterCoolerStateValues.HEAT + helper.async_assert_service_values( + ServicesTypes.HEATER_COOLER, + { + CharacteristicsTypes.TARGET_HEATER_COOLER_STATE: TargetHeaterCoolerStateValues.HEAT, + }, ) await hass.services.async_call( @@ -595,9 +677,11 @@ async def test_heater_cooler_change_thermostat_state(hass, utcnow): {"entity_id": "climate.testdevice", "hvac_mode": HVAC_MODE_COOL}, blocking=True, ) - assert ( - helper.characteristics[TARGET_HEATER_COOLER_STATE].value - == TargetHeaterCoolerStateValues.COOL + helper.async_assert_service_values( + ServicesTypes.HEATER_COOLER, + { + CharacteristicsTypes.TARGET_HEATER_COOLER_STATE: TargetHeaterCoolerStateValues.COOL, + }, ) await hass.services.async_call( @@ -606,9 +690,11 @@ async def test_heater_cooler_change_thermostat_state(hass, utcnow): {"entity_id": "climate.testdevice", "hvac_mode": HVAC_MODE_HEAT_COOL}, blocking=True, ) - assert ( - helper.characteristics[TARGET_HEATER_COOLER_STATE].value - == TargetHeaterCoolerStateValues.AUTOMATIC + helper.async_assert_service_values( + ServicesTypes.HEATER_COOLER, + { + CharacteristicsTypes.TARGET_HEATER_COOLER_STATE: TargetHeaterCoolerStateValues.AUTOMATIC, + }, ) await hass.services.async_call( @@ -617,9 +703,11 @@ async def test_heater_cooler_change_thermostat_state(hass, utcnow): {"entity_id": "climate.testdevice", "hvac_mode": HVAC_MODE_OFF}, blocking=True, ) - assert ( - helper.characteristics[HEATER_COOLER_ACTIVE].value - == ActivationStateValues.INACTIVE + helper.async_assert_service_values( + ServicesTypes.HEATER_COOLER, + { + CharacteristicsTypes.ACTIVE: ActivationStateValues.INACTIVE, + }, ) @@ -639,7 +727,12 @@ async def test_heater_cooler_change_thermostat_temperature(hass, utcnow): {"entity_id": "climate.testdevice", "temperature": 20}, blocking=True, ) - assert helper.characteristics[TEMPERATURE_HEATING_THRESHOLD].value == 20 + helper.async_assert_service_values( + ServicesTypes.HEATER_COOLER, + { + CharacteristicsTypes.TEMPERATURE_HEATING_THRESHOLD: 20, + }, + ) await hass.services.async_call( DOMAIN, @@ -653,7 +746,12 @@ async def test_heater_cooler_change_thermostat_temperature(hass, utcnow): {"entity_id": "climate.testdevice", "temperature": 26}, blocking=True, ) - assert helper.characteristics[TEMPERATURE_COOLING_THRESHOLD].value == 26 + helper.async_assert_service_values( + ServicesTypes.HEATER_COOLER, + { + CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD: 26, + }, + ) async def test_heater_cooler_read_thermostat_state(hass, utcnow): @@ -661,15 +759,16 @@ async def test_heater_cooler_read_thermostat_state(hass, utcnow): helper = await setup_test_component(hass, create_heater_cooler_service) # Simulate that heating is on - helper.characteristics[HEATER_COOLER_TEMPERATURE_CURRENT].value = 19 - helper.characteristics[TEMPERATURE_HEATING_THRESHOLD].value = 20 - helper.characteristics[ - CURRENT_HEATER_COOLER_STATE - ].value = CurrentHeaterCoolerStateValues.HEATING - helper.characteristics[ - TARGET_HEATER_COOLER_STATE - ].value = TargetHeaterCoolerStateValues.HEAT - helper.characteristics[SWING_MODE].value = SwingModeValues.DISABLED + await helper.async_update( + ServicesTypes.HEATER_COOLER, + { + CharacteristicsTypes.TEMPERATURE_CURRENT: 19, + CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD: 21, + CharacteristicsTypes.CURRENT_HEATER_COOLER_STATE: CurrentHeaterCoolerStateValues.HEATING, + CharacteristicsTypes.TARGET_HEATER_COOLER_STATE: TargetHeaterCoolerStateValues.HEAT, + CharacteristicsTypes.SWING_MODE: SwingModeValues.DISABLED, + }, + ) state = await helper.poll_and_get_state() assert state.state == HVAC_MODE_HEAT @@ -678,30 +777,32 @@ async def test_heater_cooler_read_thermostat_state(hass, utcnow): assert state.attributes["max_temp"] == 35 # Simulate that cooling is on - helper.characteristics[HEATER_COOLER_TEMPERATURE_CURRENT].value = 21 - helper.characteristics[TEMPERATURE_COOLING_THRESHOLD].value = 19 - helper.characteristics[ - CURRENT_HEATER_COOLER_STATE - ].value = CurrentHeaterCoolerStateValues.COOLING - helper.characteristics[ - TARGET_HEATER_COOLER_STATE - ].value = TargetHeaterCoolerStateValues.COOL - helper.characteristics[SWING_MODE].value = SwingModeValues.DISABLED + await helper.async_update( + ServicesTypes.HEATER_COOLER, + { + CharacteristicsTypes.TEMPERATURE_CURRENT: 21, + CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD: 19, + CharacteristicsTypes.CURRENT_HEATER_COOLER_STATE: CurrentHeaterCoolerStateValues.COOLING, + CharacteristicsTypes.TARGET_HEATER_COOLER_STATE: TargetHeaterCoolerStateValues.COOL, + CharacteristicsTypes.SWING_MODE: SwingModeValues.DISABLED, + }, + ) state = await helper.poll_and_get_state() assert state.state == HVAC_MODE_COOL assert state.attributes["current_temperature"] == 21 # Simulate that we are in auto mode - helper.characteristics[HEATER_COOLER_TEMPERATURE_CURRENT].value = 21 - helper.characteristics[TEMPERATURE_COOLING_THRESHOLD].value = 21 - helper.characteristics[ - CURRENT_HEATER_COOLER_STATE - ].value = CurrentHeaterCoolerStateValues.COOLING - helper.characteristics[ - TARGET_HEATER_COOLER_STATE - ].value = TargetHeaterCoolerStateValues.AUTOMATIC - helper.characteristics[SWING_MODE].value = SwingModeValues.DISABLED + await helper.async_update( + ServicesTypes.HEATER_COOLER, + { + CharacteristicsTypes.TEMPERATURE_CURRENT: 21, + CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD: 21, + CharacteristicsTypes.CURRENT_HEATER_COOLER_STATE: CurrentHeaterCoolerStateValues.COOLING, + CharacteristicsTypes.TARGET_HEATER_COOLER_STATE: TargetHeaterCoolerStateValues.AUTOMATIC, + CharacteristicsTypes.SWING_MODE: SwingModeValues.DISABLED, + }, + ) state = await helper.poll_and_get_state() assert state.state == HVAC_MODE_HEAT_COOL @@ -713,15 +814,16 @@ async def test_heater_cooler_hvac_mode_vs_hvac_action(hass, utcnow): # Simulate that current temperature is above target temp # Heating might be on, but hvac_action currently 'off' - helper.characteristics[HEATER_COOLER_TEMPERATURE_CURRENT].value = 22 - helper.characteristics[TEMPERATURE_HEATING_THRESHOLD].value = 21 - helper.characteristics[ - CURRENT_HEATER_COOLER_STATE - ].value = CurrentHeaterCoolerStateValues.IDLE - helper.characteristics[ - TARGET_HEATER_COOLER_STATE - ].value = TargetHeaterCoolerStateValues.HEAT - helper.characteristics[SWING_MODE].value = SwingModeValues.DISABLED + await helper.async_update( + ServicesTypes.HEATER_COOLER, + { + CharacteristicsTypes.TEMPERATURE_CURRENT: 22, + CharacteristicsTypes.TEMPERATURE_HEATING_THRESHOLD: 21, + CharacteristicsTypes.CURRENT_HEATER_COOLER_STATE: CurrentHeaterCoolerStateValues.IDLE, + CharacteristicsTypes.TARGET_HEATER_COOLER_STATE: TargetHeaterCoolerStateValues.HEAT, + CharacteristicsTypes.SWING_MODE: SwingModeValues.DISABLED, + }, + ) state = await helper.poll_and_get_state() assert state.state == "heat" @@ -729,10 +831,16 @@ async def test_heater_cooler_hvac_mode_vs_hvac_action(hass, utcnow): # Simulate that current temperature is below target temp # Heating might be on and hvac_action currently 'heat' - helper.characteristics[HEATER_COOLER_TEMPERATURE_CURRENT].value = 19 - helper.characteristics[ - CURRENT_HEATER_COOLER_STATE - ].value = CurrentHeaterCoolerStateValues.HEATING + await helper.async_update( + ServicesTypes.HEATER_COOLER, + { + CharacteristicsTypes.TEMPERATURE_CURRENT: 19, + CharacteristicsTypes.TEMPERATURE_HEATING_THRESHOLD: 21, + CharacteristicsTypes.CURRENT_HEATER_COOLER_STATE: CurrentHeaterCoolerStateValues.HEATING, + CharacteristicsTypes.TARGET_HEATER_COOLER_STATE: TargetHeaterCoolerStateValues.HEAT, + CharacteristicsTypes.SWING_MODE: SwingModeValues.DISABLED, + }, + ) state = await helper.poll_and_get_state() assert state.state == "heat" @@ -749,7 +857,12 @@ async def test_heater_cooler_change_swing_mode(hass, utcnow): {"entity_id": "climate.testdevice", "swing_mode": "vertical"}, blocking=True, ) - assert helper.characteristics[SWING_MODE].value == SwingModeValues.ENABLED + helper.async_assert_service_values( + ServicesTypes.HEATER_COOLER, + { + CharacteristicsTypes.SWING_MODE: SwingModeValues.ENABLED, + }, + ) await hass.services.async_call( DOMAIN, @@ -757,20 +870,28 @@ async def test_heater_cooler_change_swing_mode(hass, utcnow): {"entity_id": "climate.testdevice", "swing_mode": "off"}, blocking=True, ) - assert helper.characteristics[SWING_MODE].value == SwingModeValues.DISABLED + helper.async_assert_service_values( + ServicesTypes.HEATER_COOLER, + { + CharacteristicsTypes.SWING_MODE: SwingModeValues.DISABLED, + }, + ) async def test_heater_cooler_turn_off(hass, utcnow): """Test that both hvac_action and hvac_mode return "off" when turned off.""" helper = await setup_test_component(hass, create_heater_cooler_service) + # Simulate that the device is turned off but CURRENT_HEATER_COOLER_STATE still returns HEATING/COOLING - helper.characteristics[HEATER_COOLER_ACTIVE].value = ActivationStateValues.INACTIVE - helper.characteristics[ - CURRENT_HEATER_COOLER_STATE - ].value = CurrentHeaterCoolerStateValues.HEATING - helper.characteristics[ - TARGET_HEATER_COOLER_STATE - ].value = TargetHeaterCoolerStateValues.HEAT + await helper.async_update( + ServicesTypes.HEATER_COOLER, + { + CharacteristicsTypes.ACTIVE: ActivationStateValues.INACTIVE, + CharacteristicsTypes.CURRENT_HEATER_COOLER_STATE: CurrentHeaterCoolerStateValues.HEATING, + CharacteristicsTypes.TARGET_HEATER_COOLER_STATE: TargetHeaterCoolerStateValues.HEAT, + }, + ) + state = await helper.poll_and_get_state() assert state.state == "off" assert state.attributes["hvac_action"] == "off" diff --git a/tests/components/homekit_controller/test_cover.py b/tests/components/homekit_controller/test_cover.py index 45514b291222db..35e6933f2cd85e 100644 --- a/tests/components/homekit_controller/test_cover.py +++ b/tests/components/homekit_controller/test_cover.py @@ -4,23 +4,6 @@ from tests.components.homekit_controller.common import setup_test_component -POSITION_STATE = ("window-covering", "position.state") -POSITION_CURRENT = ("window-covering", "position.current") -POSITION_TARGET = ("window-covering", "position.target") -POSITION_HOLD = ("window-covering", "position.hold") - -H_TILT_CURRENT = ("window-covering", "horizontal-tilt.current") -H_TILT_TARGET = ("window-covering", "horizontal-tilt.target") - -V_TILT_CURRENT = ("window-covering", "vertical-tilt.current") -V_TILT_TARGET = ("window-covering", "vertical-tilt.target") - -WINDOW_OBSTRUCTION = ("window-covering", "obstruction-detected") - -DOOR_CURRENT = ("garage-door-opener", "door-state.current") -DOOR_TARGET = ("garage-door-opener", "door-state.target") -DOOR_OBSTRUCTION = ("garage-door-opener", "obstruction-detected") - def create_window_covering_service(accessory): """Define a window-covering characteristics as per page 219 of HAP spec.""" @@ -76,31 +59,53 @@ async def test_change_window_cover_state(hass, utcnow): await hass.services.async_call( "cover", "open_cover", {"entity_id": helper.entity_id}, blocking=True ) - assert helper.characteristics[POSITION_TARGET].value == 100 + helper.async_assert_service_values( + ServicesTypes.WINDOW_COVERING, + { + CharacteristicsTypes.POSITION_TARGET: 100, + }, + ) await hass.services.async_call( "cover", "close_cover", {"entity_id": helper.entity_id}, blocking=True ) - assert helper.characteristics[POSITION_TARGET].value == 0 + helper.async_assert_service_values( + ServicesTypes.WINDOW_COVERING, + { + CharacteristicsTypes.POSITION_TARGET: 0, + }, + ) async def test_read_window_cover_state(hass, utcnow): """Test that we can read the state of a HomeKit alarm accessory.""" helper = await setup_test_component(hass, create_window_covering_service) - helper.characteristics[POSITION_STATE].value = 0 + await helper.async_update( + ServicesTypes.WINDOW_COVERING, + {CharacteristicsTypes.POSITION_STATE: 0}, + ) state = await helper.poll_and_get_state() assert state.state == "closing" - helper.characteristics[POSITION_STATE].value = 1 + await helper.async_update( + ServicesTypes.WINDOW_COVERING, + {CharacteristicsTypes.POSITION_STATE: 1}, + ) state = await helper.poll_and_get_state() assert state.state == "opening" - helper.characteristics[POSITION_STATE].value = 2 + await helper.async_update( + ServicesTypes.WINDOW_COVERING, + {CharacteristicsTypes.POSITION_STATE: 2}, + ) state = await helper.poll_and_get_state() assert state.state == "closed" - helper.characteristics[WINDOW_OBSTRUCTION].value = True + await helper.async_update( + ServicesTypes.WINDOW_COVERING, + {CharacteristicsTypes.OBSTRUCTION_DETECTED: True}, + ) state = await helper.poll_and_get_state() assert state.attributes["obstruction-detected"] is True @@ -111,7 +116,10 @@ async def test_read_window_cover_tilt_horizontal(hass, utcnow): hass, create_window_covering_service_with_h_tilt ) - helper.characteristics[H_TILT_CURRENT].value = 75 + await helper.async_update( + ServicesTypes.WINDOW_COVERING, + {CharacteristicsTypes.HORIZONTAL_TILT_CURRENT: 75}, + ) state = await helper.poll_and_get_state() assert state.attributes["current_tilt_position"] == 75 @@ -122,7 +130,10 @@ async def test_read_window_cover_tilt_vertical(hass, utcnow): hass, create_window_covering_service_with_v_tilt ) - helper.characteristics[V_TILT_CURRENT].value = 75 + await helper.async_update( + ServicesTypes.WINDOW_COVERING, + {CharacteristicsTypes.VERTICAL_TILT_CURRENT: 75}, + ) state = await helper.poll_and_get_state() assert state.attributes["current_tilt_position"] == 75 @@ -139,7 +150,12 @@ async def test_write_window_cover_tilt_horizontal(hass, utcnow): {"entity_id": helper.entity_id, "tilt_position": 90}, blocking=True, ) - assert helper.characteristics[H_TILT_TARGET].value == 90 + helper.async_assert_service_values( + ServicesTypes.WINDOW_COVERING, + { + CharacteristicsTypes.HORIZONTAL_TILT_TARGET: 90, + }, + ) async def test_write_window_cover_tilt_vertical(hass, utcnow): @@ -154,7 +170,12 @@ async def test_write_window_cover_tilt_vertical(hass, utcnow): {"entity_id": helper.entity_id, "tilt_position": 90}, blocking=True, ) - assert helper.characteristics[V_TILT_TARGET].value == 90 + helper.async_assert_service_values( + ServicesTypes.WINDOW_COVERING, + { + CharacteristicsTypes.VERTICAL_TILT_TARGET: 90, + }, + ) async def test_window_cover_stop(hass, utcnow): @@ -166,7 +187,12 @@ async def test_window_cover_stop(hass, utcnow): await hass.services.async_call( "cover", "stop_cover", {"entity_id": helper.entity_id}, blocking=True ) - assert helper.characteristics[POSITION_HOLD].value == 1 + helper.async_assert_service_values( + ServicesTypes.WINDOW_COVERING, + { + CharacteristicsTypes.POSITION_HOLD: True, + }, + ) def create_garage_door_opener_service(accessory): @@ -195,34 +221,59 @@ async def test_change_door_state(hass, utcnow): await hass.services.async_call( "cover", "open_cover", {"entity_id": helper.entity_id}, blocking=True ) - assert helper.characteristics[DOOR_TARGET].value == 0 + helper.async_assert_service_values( + ServicesTypes.GARAGE_DOOR_OPENER, + { + CharacteristicsTypes.DOOR_STATE_TARGET: 0, + }, + ) await hass.services.async_call( "cover", "close_cover", {"entity_id": helper.entity_id}, blocking=True ) - assert helper.characteristics[DOOR_TARGET].value == 1 + helper.async_assert_service_values( + ServicesTypes.GARAGE_DOOR_OPENER, + { + CharacteristicsTypes.DOOR_STATE_TARGET: 1, + }, + ) async def test_read_door_state(hass, utcnow): """Test that we can read the state of a HomeKit garage door.""" helper = await setup_test_component(hass, create_garage_door_opener_service) - helper.characteristics[DOOR_CURRENT].value = 0 + await helper.async_update( + ServicesTypes.GARAGE_DOOR_OPENER, + {CharacteristicsTypes.DOOR_STATE_CURRENT: 0}, + ) state = await helper.poll_and_get_state() assert state.state == "open" - helper.characteristics[DOOR_CURRENT].value = 1 + await helper.async_update( + ServicesTypes.GARAGE_DOOR_OPENER, + {CharacteristicsTypes.DOOR_STATE_CURRENT: 1}, + ) state = await helper.poll_and_get_state() assert state.state == "closed" - helper.characteristics[DOOR_CURRENT].value = 2 + await helper.async_update( + ServicesTypes.GARAGE_DOOR_OPENER, + {CharacteristicsTypes.DOOR_STATE_CURRENT: 2}, + ) state = await helper.poll_and_get_state() assert state.state == "opening" - helper.characteristics[DOOR_CURRENT].value = 3 + await helper.async_update( + ServicesTypes.GARAGE_DOOR_OPENER, + {CharacteristicsTypes.DOOR_STATE_CURRENT: 3}, + ) state = await helper.poll_and_get_state() assert state.state == "closing" - helper.characteristics[DOOR_OBSTRUCTION].value = True + await helper.async_update( + ServicesTypes.GARAGE_DOOR_OPENER, + {CharacteristicsTypes.OBSTRUCTION_DETECTED: True}, + ) state = await helper.poll_and_get_state() assert state.attributes["obstruction-detected"] is True diff --git a/tests/components/homekit_controller/test_fan.py b/tests/components/homekit_controller/test_fan.py index d66ce81d5349f7..252e7f87bed7e9 100644 --- a/tests/components/homekit_controller/test_fan.py +++ b/tests/components/homekit_controller/test_fan.py @@ -4,15 +4,6 @@ from tests.components.homekit_controller.common import setup_test_component -V1_ON = ("fan", "on") -V1_ROTATION_DIRECTION = ("fan", "rotation.direction") -V1_ROTATION_SPEED = ("fan", "rotation.speed") - -V2_ACTIVE = ("fanv2", "active") -V2_ROTATION_DIRECTION = ("fanv2", "rotation.direction") -V2_ROTATION_SPEED = ("fanv2", "rotation.speed") -V2_SWING_MODE = ("fanv2", "swing-mode") - def create_fan_service(accessory): """ @@ -86,12 +77,14 @@ async def test_fan_read_state(hass, utcnow): """Test that we can read the state of a HomeKit fan accessory.""" helper = await setup_test_component(hass, create_fan_service) - helper.characteristics[V1_ON].value = False - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.FAN, {CharacteristicsTypes.ON: False} + ) assert state.state == "off" - helper.characteristics[V1_ON].value = True - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.FAN, {CharacteristicsTypes.ON: True} + ) assert state.state == "on" @@ -105,8 +98,13 @@ async def test_turn_on(hass, utcnow): {"entity_id": "fan.testdevice", "speed": "high"}, blocking=True, ) - assert helper.characteristics[V1_ON].value == 1 - assert helper.characteristics[V1_ROTATION_SPEED].value == 100 + helper.async_assert_service_values( + ServicesTypes.FAN, + { + CharacteristicsTypes.ON: 1, + CharacteristicsTypes.ROTATION_SPEED: 100, + }, + ) await hass.services.async_call( "fan", @@ -114,8 +112,13 @@ async def test_turn_on(hass, utcnow): {"entity_id": "fan.testdevice", "speed": "medium"}, blocking=True, ) - assert helper.characteristics[V1_ON].value == 1 - assert helper.characteristics[V1_ROTATION_SPEED].value == 66.0 + helper.async_assert_service_values( + ServicesTypes.FAN, + { + CharacteristicsTypes.ON: 1, + CharacteristicsTypes.ROTATION_SPEED: 66.0, + }, + ) await hass.services.async_call( "fan", @@ -123,8 +126,13 @@ async def test_turn_on(hass, utcnow): {"entity_id": "fan.testdevice", "speed": "low"}, blocking=True, ) - assert helper.characteristics[V1_ON].value == 1 - assert helper.characteristics[V1_ROTATION_SPEED].value == 33.0 + helper.async_assert_service_values( + ServicesTypes.FAN, + { + CharacteristicsTypes.ON: 1, + CharacteristicsTypes.ROTATION_SPEED: 33.0, + }, + ) async def test_turn_on_off_without_rotation_speed(hass, utcnow): @@ -139,7 +147,12 @@ async def test_turn_on_off_without_rotation_speed(hass, utcnow): {"entity_id": "fan.testdevice"}, blocking=True, ) - assert helper.characteristics[V2_ACTIVE].value == 1 + helper.async_assert_service_values( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.ACTIVE: 1, + }, + ) await hass.services.async_call( "fan", @@ -147,14 +160,19 @@ async def test_turn_on_off_without_rotation_speed(hass, utcnow): {"entity_id": "fan.testdevice"}, blocking=True, ) - assert helper.characteristics[V2_ACTIVE].value == 0 + helper.async_assert_service_values( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.ACTIVE: 0, + }, + ) async def test_turn_off(hass, utcnow): """Test that we can turn a fan off.""" helper = await setup_test_component(hass, create_fan_service) - helper.characteristics[V1_ON].value = 1 + await helper.async_update(ServicesTypes.FAN, {CharacteristicsTypes.ON: 1}) await hass.services.async_call( "fan", @@ -162,14 +180,19 @@ async def test_turn_off(hass, utcnow): {"entity_id": "fan.testdevice"}, blocking=True, ) - assert helper.characteristics[V1_ON].value == 0 + helper.async_assert_service_values( + ServicesTypes.FAN, + { + CharacteristicsTypes.ON: 0, + }, + ) async def test_set_speed(hass, utcnow): """Test that we set fan speed.""" helper = await setup_test_component(hass, create_fan_service) - helper.characteristics[V1_ON].value = 1 + await helper.async_update(ServicesTypes.FAN, {CharacteristicsTypes.ON: 1}) await hass.services.async_call( "fan", @@ -177,7 +200,12 @@ async def test_set_speed(hass, utcnow): {"entity_id": "fan.testdevice", "speed": "high"}, blocking=True, ) - assert helper.characteristics[V1_ROTATION_SPEED].value == 100 + helper.async_assert_service_values( + ServicesTypes.FAN, + { + CharacteristicsTypes.ROTATION_SPEED: 100.0, + }, + ) await hass.services.async_call( "fan", @@ -185,7 +213,12 @@ async def test_set_speed(hass, utcnow): {"entity_id": "fan.testdevice", "speed": "medium"}, blocking=True, ) - assert helper.characteristics[V1_ROTATION_SPEED].value == 66.0 + helper.async_assert_service_values( + ServicesTypes.FAN, + { + CharacteristicsTypes.ROTATION_SPEED: 66.0, + }, + ) await hass.services.async_call( "fan", @@ -193,7 +226,12 @@ async def test_set_speed(hass, utcnow): {"entity_id": "fan.testdevice", "speed": "low"}, blocking=True, ) - assert helper.characteristics[V1_ROTATION_SPEED].value == 33.0 + helper.async_assert_service_values( + ServicesTypes.FAN, + { + CharacteristicsTypes.ROTATION_SPEED: 33.0, + }, + ) await hass.services.async_call( "fan", @@ -201,14 +239,19 @@ async def test_set_speed(hass, utcnow): {"entity_id": "fan.testdevice", "speed": "off"}, blocking=True, ) - assert helper.characteristics[V1_ON].value == 0 + helper.async_assert_service_values( + ServicesTypes.FAN, + { + CharacteristicsTypes.ON: 0, + }, + ) async def test_set_percentage(hass, utcnow): """Test that we set fan speed by percentage.""" helper = await setup_test_component(hass, create_fan_service) - helper.characteristics[V1_ON].value = 1 + await helper.async_update(ServicesTypes.FAN, {CharacteristicsTypes.ON: 1}) await hass.services.async_call( "fan", @@ -216,7 +259,12 @@ async def test_set_percentage(hass, utcnow): {"entity_id": "fan.testdevice", "percentage": 66}, blocking=True, ) - assert helper.characteristics[V1_ROTATION_SPEED].value == 66 + helper.async_assert_service_values( + ServicesTypes.FAN, + { + CharacteristicsTypes.ROTATION_SPEED: 66, + }, + ) await hass.services.async_call( "fan", @@ -224,33 +272,54 @@ async def test_set_percentage(hass, utcnow): {"entity_id": "fan.testdevice", "percentage": 0}, blocking=True, ) - assert helper.characteristics[V1_ON].value == 0 + helper.async_assert_service_values( + ServicesTypes.FAN, + { + CharacteristicsTypes.ON: 0, + }, + ) async def test_speed_read(hass, utcnow): """Test that we can read a fans oscillation.""" helper = await setup_test_component(hass, create_fan_service) - helper.characteristics[V1_ON].value = 1 - helper.characteristics[V1_ROTATION_SPEED].value = 100 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.FAN, + { + CharacteristicsTypes.ON: 1, + CharacteristicsTypes.ROTATION_SPEED: 100, + }, + ) assert state.attributes["speed"] == "high" assert state.attributes["percentage"] == 100 assert state.attributes["percentage_step"] == 1.0 - helper.characteristics[V1_ROTATION_SPEED].value = 50 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.FAN, + { + CharacteristicsTypes.ROTATION_SPEED: 50, + }, + ) assert state.attributes["speed"] == "medium" assert state.attributes["percentage"] == 50 - helper.characteristics[V1_ROTATION_SPEED].value = 25 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.FAN, + { + CharacteristicsTypes.ROTATION_SPEED: 25, + }, + ) assert state.attributes["speed"] == "low" assert state.attributes["percentage"] == 25 - helper.characteristics[V1_ON].value = 0 - helper.characteristics[V1_ROTATION_SPEED].value = 0 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.FAN, + { + CharacteristicsTypes.ON: 0, + CharacteristicsTypes.ROTATION_SPEED: 0, + }, + ) assert state.attributes["speed"] == "off" assert state.attributes["percentage"] == 0 @@ -265,7 +334,12 @@ async def test_set_direction(hass, utcnow): {"entity_id": "fan.testdevice", "direction": "reverse"}, blocking=True, ) - assert helper.characteristics[V1_ROTATION_DIRECTION].value == 1 + helper.async_assert_service_values( + ServicesTypes.FAN, + { + CharacteristicsTypes.ROTATION_DIRECTION: 1, + }, + ) await hass.services.async_call( "fan", @@ -273,19 +347,26 @@ async def test_set_direction(hass, utcnow): {"entity_id": "fan.testdevice", "direction": "forward"}, blocking=True, ) - assert helper.characteristics[V1_ROTATION_DIRECTION].value == 0 + helper.async_assert_service_values( + ServicesTypes.FAN, + { + CharacteristicsTypes.ROTATION_DIRECTION: 0, + }, + ) async def test_direction_read(hass, utcnow): """Test that we can read a fans oscillation.""" helper = await setup_test_component(hass, create_fan_service) - helper.characteristics[V1_ROTATION_DIRECTION].value = 0 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.FAN, {CharacteristicsTypes.ROTATION_DIRECTION: 0} + ) assert state.attributes["direction"] == "forward" - helper.characteristics[V1_ROTATION_DIRECTION].value = 1 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.FAN, {CharacteristicsTypes.ROTATION_DIRECTION: 1} + ) assert state.attributes["direction"] == "reverse" @@ -293,12 +374,14 @@ async def test_fanv2_read_state(hass, utcnow): """Test that we can read the state of a HomeKit fan accessory.""" helper = await setup_test_component(hass, create_fanv2_service) - helper.characteristics[V2_ACTIVE].value = False - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.FAN_V2, {CharacteristicsTypes.ACTIVE: False} + ) assert state.state == "off" - helper.characteristics[V2_ACTIVE].value = True - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.FAN_V2, {CharacteristicsTypes.ACTIVE: True} + ) assert state.state == "on" @@ -312,8 +395,13 @@ async def test_v2_turn_on(hass, utcnow): {"entity_id": "fan.testdevice", "speed": "high"}, blocking=True, ) - assert helper.characteristics[V2_ACTIVE].value == 1 - assert helper.characteristics[V2_ROTATION_SPEED].value == 100 + helper.async_assert_service_values( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.ACTIVE: 1, + CharacteristicsTypes.ROTATION_SPEED: 100, + }, + ) await hass.services.async_call( "fan", @@ -321,8 +409,13 @@ async def test_v2_turn_on(hass, utcnow): {"entity_id": "fan.testdevice", "speed": "medium"}, blocking=True, ) - assert helper.characteristics[V2_ACTIVE].value == 1 - assert helper.characteristics[V2_ROTATION_SPEED].value == 66.0 + helper.async_assert_service_values( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.ACTIVE: 1, + CharacteristicsTypes.ROTATION_SPEED: 66, + }, + ) await hass.services.async_call( "fan", @@ -330,8 +423,13 @@ async def test_v2_turn_on(hass, utcnow): {"entity_id": "fan.testdevice", "speed": "low"}, blocking=True, ) - assert helper.characteristics[V2_ACTIVE].value == 1 - assert helper.characteristics[V2_ROTATION_SPEED].value == 33.0 + helper.async_assert_service_values( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.ACTIVE: 1, + CharacteristicsTypes.ROTATION_SPEED: 33, + }, + ) await hass.services.async_call( "fan", @@ -339,8 +437,13 @@ async def test_v2_turn_on(hass, utcnow): {"entity_id": "fan.testdevice"}, blocking=True, ) - assert helper.characteristics[V2_ACTIVE].value == 0 - assert helper.characteristics[V2_ROTATION_SPEED].value == 33.0 + helper.async_assert_service_values( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.ACTIVE: 0, + CharacteristicsTypes.ROTATION_SPEED: 33, + }, + ) await hass.services.async_call( "fan", @@ -348,15 +451,20 @@ async def test_v2_turn_on(hass, utcnow): {"entity_id": "fan.testdevice"}, blocking=True, ) - assert helper.characteristics[V2_ACTIVE].value == 1 - assert helper.characteristics[V2_ROTATION_SPEED].value == 33.0 + helper.async_assert_service_values( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.ACTIVE: 1, + CharacteristicsTypes.ROTATION_SPEED: 33, + }, + ) async def test_v2_turn_off(hass, utcnow): """Test that we can turn a fan off.""" helper = await setup_test_component(hass, create_fanv2_service) - helper.characteristics[V2_ACTIVE].value = 1 + await helper.async_update(ServicesTypes.FAN_V2, {CharacteristicsTypes.ACTIVE: 1}) await hass.services.async_call( "fan", @@ -364,14 +472,19 @@ async def test_v2_turn_off(hass, utcnow): {"entity_id": "fan.testdevice"}, blocking=True, ) - assert helper.characteristics[V2_ACTIVE].value == 0 + helper.async_assert_service_values( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.ACTIVE: 0, + }, + ) async def test_v2_set_speed(hass, utcnow): """Test that we set fan speed.""" helper = await setup_test_component(hass, create_fanv2_service) - helper.characteristics[V2_ACTIVE].value = 1 + await helper.async_update(ServicesTypes.FAN_V2, {CharacteristicsTypes.ACTIVE: 1}) await hass.services.async_call( "fan", @@ -379,7 +492,12 @@ async def test_v2_set_speed(hass, utcnow): {"entity_id": "fan.testdevice", "speed": "high"}, blocking=True, ) - assert helper.characteristics[V2_ROTATION_SPEED].value == 100 + helper.async_assert_service_values( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.ROTATION_SPEED: 100, + }, + ) await hass.services.async_call( "fan", @@ -387,7 +505,12 @@ async def test_v2_set_speed(hass, utcnow): {"entity_id": "fan.testdevice", "speed": "medium"}, blocking=True, ) - assert helper.characteristics[V2_ROTATION_SPEED].value == 66 + helper.async_assert_service_values( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.ROTATION_SPEED: 66, + }, + ) await hass.services.async_call( "fan", @@ -395,7 +518,12 @@ async def test_v2_set_speed(hass, utcnow): {"entity_id": "fan.testdevice", "speed": "low"}, blocking=True, ) - assert helper.characteristics[V2_ROTATION_SPEED].value == 33 + helper.async_assert_service_values( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.ROTATION_SPEED: 33, + }, + ) await hass.services.async_call( "fan", @@ -403,14 +531,19 @@ async def test_v2_set_speed(hass, utcnow): {"entity_id": "fan.testdevice", "speed": "off"}, blocking=True, ) - assert helper.characteristics[V2_ACTIVE].value == 0 + helper.async_assert_service_values( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.ACTIVE: 0, + }, + ) async def test_v2_set_percentage(hass, utcnow): """Test that we set fan speed by percentage.""" helper = await setup_test_component(hass, create_fanv2_service) - helper.characteristics[V2_ACTIVE].value = 1 + await helper.async_update(ServicesTypes.FAN_V2, {CharacteristicsTypes.ACTIVE: 1}) await hass.services.async_call( "fan", @@ -418,7 +551,12 @@ async def test_v2_set_percentage(hass, utcnow): {"entity_id": "fan.testdevice", "percentage": 66}, blocking=True, ) - assert helper.characteristics[V2_ROTATION_SPEED].value == 66 + helper.async_assert_service_values( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.ROTATION_SPEED: 66, + }, + ) await hass.services.async_call( "fan", @@ -426,14 +564,19 @@ async def test_v2_set_percentage(hass, utcnow): {"entity_id": "fan.testdevice", "percentage": 0}, blocking=True, ) - assert helper.characteristics[V2_ACTIVE].value == 0 + helper.async_assert_service_values( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.ACTIVE: 0, + }, + ) async def test_v2_set_percentage_with_min_step(hass, utcnow): """Test that we set fan speed by percentage.""" helper = await setup_test_component(hass, create_fanv2_service_with_min_step) - helper.characteristics[V2_ACTIVE].value = 1 + await helper.async_update(ServicesTypes.FAN_V2, {CharacteristicsTypes.ACTIVE: 1}) await hass.services.async_call( "fan", @@ -441,7 +584,12 @@ async def test_v2_set_percentage_with_min_step(hass, utcnow): {"entity_id": "fan.testdevice", "percentage": 66}, blocking=True, ) - assert helper.characteristics[V2_ROTATION_SPEED].value == 75 + helper.async_assert_service_values( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.ROTATION_SPEED: 75, + }, + ) await hass.services.async_call( "fan", @@ -449,32 +597,53 @@ async def test_v2_set_percentage_with_min_step(hass, utcnow): {"entity_id": "fan.testdevice", "percentage": 0}, blocking=True, ) - assert helper.characteristics[V2_ACTIVE].value == 0 + helper.async_assert_service_values( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.ACTIVE: 0, + }, + ) async def test_v2_speed_read(hass, utcnow): """Test that we can read a fans oscillation.""" helper = await setup_test_component(hass, create_fanv2_service) - helper.characteristics[V2_ACTIVE].value = 1 - helper.characteristics[V2_ROTATION_SPEED].value = 100 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.ACTIVE: 1, + CharacteristicsTypes.ROTATION_SPEED: 100, + }, + ) assert state.attributes["speed"] == "high" assert state.attributes["percentage"] == 100 - helper.characteristics[V2_ROTATION_SPEED].value = 50 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.ROTATION_SPEED: 50, + }, + ) assert state.attributes["speed"] == "medium" assert state.attributes["percentage"] == 50 - helper.characteristics[V2_ROTATION_SPEED].value = 25 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.ROTATION_SPEED: 25, + }, + ) assert state.attributes["speed"] == "low" assert state.attributes["percentage"] == 25 - helper.characteristics[V2_ACTIVE].value = 0 - helper.characteristics[V2_ROTATION_SPEED].value = 0 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.ACTIVE: 0, + CharacteristicsTypes.ROTATION_SPEED: 0, + }, + ) assert state.attributes["speed"] == "off" assert state.attributes["percentage"] == 0 @@ -489,7 +658,12 @@ async def test_v2_set_direction(hass, utcnow): {"entity_id": "fan.testdevice", "direction": "reverse"}, blocking=True, ) - assert helper.characteristics[V2_ROTATION_DIRECTION].value == 1 + helper.async_assert_service_values( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.ROTATION_DIRECTION: 1, + }, + ) await hass.services.async_call( "fan", @@ -497,19 +671,26 @@ async def test_v2_set_direction(hass, utcnow): {"entity_id": "fan.testdevice", "direction": "forward"}, blocking=True, ) - assert helper.characteristics[V2_ROTATION_DIRECTION].value == 0 + helper.async_assert_service_values( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.ROTATION_DIRECTION: 0, + }, + ) async def test_v2_direction_read(hass, utcnow): """Test that we can read a fans oscillation.""" helper = await setup_test_component(hass, create_fanv2_service) - helper.characteristics[V2_ROTATION_DIRECTION].value = 0 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.FAN_V2, {CharacteristicsTypes.ROTATION_DIRECTION: 0} + ) assert state.attributes["direction"] == "forward" - helper.characteristics[V2_ROTATION_DIRECTION].value = 1 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.FAN_V2, {CharacteristicsTypes.ROTATION_DIRECTION: 1} + ) assert state.attributes["direction"] == "reverse" @@ -523,7 +704,12 @@ async def test_v2_oscillate(hass, utcnow): {"entity_id": "fan.testdevice", "oscillating": True}, blocking=True, ) - assert helper.characteristics[V2_SWING_MODE].value == 1 + helper.async_assert_service_values( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.SWING_MODE: 1, + }, + ) await hass.services.async_call( "fan", @@ -531,17 +717,24 @@ async def test_v2_oscillate(hass, utcnow): {"entity_id": "fan.testdevice", "oscillating": False}, blocking=True, ) - assert helper.characteristics[V2_SWING_MODE].value == 0 + helper.async_assert_service_values( + ServicesTypes.FAN_V2, + { + CharacteristicsTypes.SWING_MODE: 0, + }, + ) async def test_v2_oscillate_read(hass, utcnow): """Test that we can read a fans oscillation.""" helper = await setup_test_component(hass, create_fanv2_service) - helper.characteristics[V2_SWING_MODE].value = 0 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.FAN_V2, {CharacteristicsTypes.SWING_MODE: 0} + ) assert state.attributes["oscillating"] is False - helper.characteristics[V2_SWING_MODE].value = 1 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.FAN_V2, {CharacteristicsTypes.SWING_MODE: 1} + ) assert state.attributes["oscillating"] is True diff --git a/tests/components/homekit_controller/test_humidifier.py b/tests/components/homekit_controller/test_humidifier.py index 0af795e2ce9416..ea32b1931c2c94 100644 --- a/tests/components/homekit_controller/test_humidifier.py +++ b/tests/components/homekit_controller/test_humidifier.py @@ -7,25 +7,6 @@ from tests.components.homekit_controller.common import setup_test_component -ACTIVE = ("humidifier-dehumidifier", "active") -CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE = ( - "humidifier-dehumidifier", - "humidifier-dehumidifier.state.current", -) -TARGET_HUMIDIFIER_DEHUMIDIFIER_STATE = ( - "humidifier-dehumidifier", - "humidifier-dehumidifier.state.target", -) -RELATIVE_HUMIDITY_CURRENT = ("humidifier-dehumidifier", "relative-humidity.current") -RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD = ( - "humidifier-dehumidifier", - "relative-humidity.humidifier-threshold", -) -RELATIVE_HUMIDITY_DEHUMIDIFIER_THRESHOLD = ( - "humidifier-dehumidifier", - "relative-humidity.dehumidifier-threshold", -) - def create_humidifier_service(accessory): """Define a humidifier characteristics as per page 219 of HAP spec.""" @@ -89,13 +70,19 @@ async def test_humidifier_active_state(hass, utcnow): DOMAIN, "turn_on", {"entity_id": helper.entity_id}, blocking=True ) - assert helper.characteristics[ACTIVE].value == 1 + helper.async_assert_service_values( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + {CharacteristicsTypes.ACTIVE: 1}, + ) await hass.services.async_call( DOMAIN, "turn_off", {"entity_id": helper.entity_id}, blocking=True ) - assert helper.characteristics[ACTIVE].value == 0 + helper.async_assert_service_values( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + {CharacteristicsTypes.ACTIVE: 0}, + ) async def test_dehumidifier_active_state(hass, utcnow): @@ -106,54 +93,85 @@ async def test_dehumidifier_active_state(hass, utcnow): DOMAIN, "turn_on", {"entity_id": helper.entity_id}, blocking=True ) - assert helper.characteristics[ACTIVE].value == 1 + helper.async_assert_service_values( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + {CharacteristicsTypes.ACTIVE: 1}, + ) await hass.services.async_call( DOMAIN, "turn_off", {"entity_id": helper.entity_id}, blocking=True ) - assert helper.characteristics[ACTIVE].value == 0 + helper.async_assert_service_values( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + {CharacteristicsTypes.ACTIVE: 0}, + ) async def test_humidifier_read_humidity(hass, utcnow): """Test that we can read the state of a HomeKit humidifier accessory.""" helper = await setup_test_component(hass, create_humidifier_service) - helper.characteristics[ACTIVE].value = True - helper.characteristics[RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD].value = 75 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.ACTIVE: True, + CharacteristicsTypes.RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD: 75, + }, + ) assert state.state == "on" assert state.attributes["humidity"] == 75 - helper.characteristics[ACTIVE].value = False - helper.characteristics[RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD].value = 10 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.ACTIVE: False, + CharacteristicsTypes.RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD: 10, + }, + ) assert state.state == "off" assert state.attributes["humidity"] == 10 - helper.characteristics[CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE].value = 3 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE: 3, + }, + ) assert state.attributes["humidity"] == 10 + assert state.state == "off" async def test_dehumidifier_read_humidity(hass, utcnow): """Test that we can read the state of a HomeKit dehumidifier accessory.""" helper = await setup_test_component(hass, create_dehumidifier_service) - helper.characteristics[ACTIVE].value = True - helper.characteristics[RELATIVE_HUMIDITY_DEHUMIDIFIER_THRESHOLD].value = 75 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.ACTIVE: True, + CharacteristicsTypes.RELATIVE_HUMIDITY_DEHUMIDIFIER_THRESHOLD: 75, + }, + ) assert state.state == "on" assert state.attributes["humidity"] == 75 - helper.characteristics[ACTIVE].value = False - helper.characteristics[RELATIVE_HUMIDITY_DEHUMIDIFIER_THRESHOLD].value = 40 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.ACTIVE: False, + CharacteristicsTypes.RELATIVE_HUMIDITY_DEHUMIDIFIER_THRESHOLD: 40, + }, + ) assert state.state == "off" assert state.attributes["humidity"] == 40 - helper.characteristics[CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE].value = 2 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE: 2, + }, + ) assert state.attributes["humidity"] == 40 @@ -167,7 +185,10 @@ async def test_humidifier_set_humidity(hass, utcnow): {"entity_id": helper.entity_id, "humidity": 20}, blocking=True, ) - assert helper.characteristics[RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD].value == 20 + helper.async_assert_service_values( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + {CharacteristicsTypes.RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD: 20}, + ) async def test_dehumidifier_set_humidity(hass, utcnow): @@ -180,7 +201,10 @@ async def test_dehumidifier_set_humidity(hass, utcnow): {"entity_id": helper.entity_id, "humidity": 20}, blocking=True, ) - assert helper.characteristics[RELATIVE_HUMIDITY_DEHUMIDIFIER_THRESHOLD].value == 20 + helper.async_assert_service_values( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + {CharacteristicsTypes.RELATIVE_HUMIDITY_DEHUMIDIFIER_THRESHOLD: 20}, + ) async def test_humidifier_set_mode(hass, utcnow): @@ -193,8 +217,13 @@ async def test_humidifier_set_mode(hass, utcnow): {"entity_id": helper.entity_id, "mode": MODE_AUTO}, blocking=True, ) - assert helper.characteristics[TARGET_HUMIDIFIER_DEHUMIDIFIER_STATE].value == 0 - assert helper.characteristics[ACTIVE].value == 1 + helper.async_assert_service_values( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.ACTIVE: 1, + CharacteristicsTypes.TARGET_HUMIDIFIER_DEHUMIDIFIER_STATE: 0, + }, + ) await hass.services.async_call( DOMAIN, @@ -202,8 +231,13 @@ async def test_humidifier_set_mode(hass, utcnow): {"entity_id": helper.entity_id, "mode": MODE_NORMAL}, blocking=True, ) - assert helper.characteristics[TARGET_HUMIDIFIER_DEHUMIDIFIER_STATE].value == 1 - assert helper.characteristics[ACTIVE].value == 1 + helper.async_assert_service_values( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.ACTIVE: 1, + CharacteristicsTypes.TARGET_HUMIDIFIER_DEHUMIDIFIER_STATE: 1, + }, + ) async def test_dehumidifier_set_mode(hass, utcnow): @@ -216,8 +250,13 @@ async def test_dehumidifier_set_mode(hass, utcnow): {"entity_id": helper.entity_id, "mode": MODE_AUTO}, blocking=True, ) - assert helper.characteristics[TARGET_HUMIDIFIER_DEHUMIDIFIER_STATE].value == 0 - assert helper.characteristics[ACTIVE].value == 1 + helper.async_assert_service_values( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.ACTIVE: 1, + CharacteristicsTypes.TARGET_HUMIDIFIER_DEHUMIDIFIER_STATE: 0, + }, + ) await hass.services.async_call( DOMAIN, @@ -225,8 +264,13 @@ async def test_dehumidifier_set_mode(hass, utcnow): {"entity_id": helper.entity_id, "mode": MODE_NORMAL}, blocking=True, ) - assert helper.characteristics[TARGET_HUMIDIFIER_DEHUMIDIFIER_STATE].value == 2 - assert helper.characteristics[ACTIVE].value == 1 + helper.async_assert_service_values( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.ACTIVE: 1, + CharacteristicsTypes.TARGET_HUMIDIFIER_DEHUMIDIFIER_STATE: 2, + }, + ) async def test_humidifier_read_only_mode(hass, utcnow): @@ -236,20 +280,36 @@ async def test_humidifier_read_only_mode(hass, utcnow): state = await helper.poll_and_get_state() assert state.attributes["mode"] == "normal" - helper.characteristics[CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE].value = 0 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE: 0, + }, + ) assert state.attributes["mode"] == "normal" - helper.characteristics[CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE].value = 1 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE: 1, + }, + ) assert state.attributes["mode"] == "auto" - helper.characteristics[CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE].value = 2 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE: 2, + }, + ) assert state.attributes["mode"] == "normal" - helper.characteristics[CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE].value = 3 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE: 3, + }, + ) assert state.attributes["mode"] == "normal" @@ -260,20 +320,36 @@ async def test_dehumidifier_read_only_mode(hass, utcnow): state = await helper.poll_and_get_state() assert state.attributes["mode"] == "normal" - helper.characteristics[CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE].value = 0 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE: 0, + }, + ) assert state.attributes["mode"] == "normal" - helper.characteristics[CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE].value = 1 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE: 1, + }, + ) assert state.attributes["mode"] == "auto" - helper.characteristics[CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE].value = 2 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE: 2, + }, + ) assert state.attributes["mode"] == "normal" - helper.characteristics[CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE].value = 3 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE: 3, + }, + ) assert state.attributes["mode"] == "normal" @@ -281,26 +357,41 @@ async def test_humidifier_target_humidity_modes(hass, utcnow): """Test that we can read the state of a HomeKit humidifier accessory.""" helper = await setup_test_component(hass, create_humidifier_service) - helper.characteristics[RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD].value = 37 - helper.characteristics[RELATIVE_HUMIDITY_CURRENT].value = 51 - helper.characteristics[CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE].value = 1 - - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD: 37, + CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT: 51, + CharacteristicsTypes.CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE: 1, + }, + ) assert state.attributes["mode"] == "auto" assert state.attributes["humidity"] == 37 - helper.characteristics[CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE].value = 3 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE: 3, + }, + ) assert state.attributes["mode"] == "normal" assert state.attributes["humidity"] == 37 - helper.characteristics[CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE].value = 2 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE: 2, + }, + ) assert state.attributes["mode"] == "normal" assert state.attributes["humidity"] == 37 - helper.characteristics[CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE].value = 0 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE: 0, + }, + ) assert state.attributes["mode"] == "normal" assert state.attributes["humidity"] == 37 @@ -309,25 +400,40 @@ async def test_dehumidifier_target_humidity_modes(hass, utcnow): """Test that we can read the state of a HomeKit dehumidifier accessory.""" helper = await setup_test_component(hass, create_dehumidifier_service) - helper.characteristics[RELATIVE_HUMIDITY_DEHUMIDIFIER_THRESHOLD].value = 73 - helper.characteristics[RELATIVE_HUMIDITY_CURRENT].value = 51 - helper.characteristics[CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE].value = 1 - - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.RELATIVE_HUMIDITY_DEHUMIDIFIER_THRESHOLD: 73, + CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT: 51, + CharacteristicsTypes.CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE: 1, + }, + ) assert state.attributes["mode"] == "auto" assert state.attributes["humidity"] == 73 - helper.characteristics[CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE].value = 3 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE: 3, + }, + ) assert state.attributes["mode"] == "normal" assert state.attributes["humidity"] == 73 - helper.characteristics[CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE].value = 2 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE: 2, + }, + ) assert state.attributes["mode"] == "normal" assert state.attributes["humidity"] == 73 - helper.characteristics[CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE].value = 0 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDIFIER_DEHUMIDIFIER, + { + CharacteristicsTypes.CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE: 0, + }, + ) assert state.attributes["mode"] == "normal" assert state.attributes["humidity"] == 73 diff --git a/tests/components/homekit_controller/test_light.py b/tests/components/homekit_controller/test_light.py index f4950512063332..8d094c4eb7095d 100644 --- a/tests/components/homekit_controller/test_light.py +++ b/tests/components/homekit_controller/test_light.py @@ -10,12 +10,6 @@ LIGHT_BULB_NAME = "Light Bulb" LIGHT_BULB_ENTITY_ID = "light.testdevice" -LIGHT_ON = ("lightbulb", "on") -LIGHT_BRIGHTNESS = ("lightbulb", "brightness") -LIGHT_HUE = ("lightbulb", "hue") -LIGHT_SATURATION = ("lightbulb", "saturation") -LIGHT_COLOR_TEMP = ("lightbulb", "color-temperature") - def create_lightbulb_service(accessory): """Define lightbulb characteristics.""" @@ -63,16 +57,25 @@ async def test_switch_change_light_state(hass, utcnow): {"entity_id": "light.testdevice", "brightness": 255, "hs_color": [4, 5]}, blocking=True, ) - - assert helper.characteristics[LIGHT_ON].value == 1 - assert helper.characteristics[LIGHT_BRIGHTNESS].value == 100 - assert helper.characteristics[LIGHT_HUE].value == 4 - assert helper.characteristics[LIGHT_SATURATION].value == 5 + helper.async_assert_service_values( + ServicesTypes.LIGHTBULB, + { + CharacteristicsTypes.ON: True, + CharacteristicsTypes.BRIGHTNESS: 100, + CharacteristicsTypes.HUE: 4, + CharacteristicsTypes.SATURATION: 5, + }, + ) await hass.services.async_call( "light", "turn_off", {"entity_id": "light.testdevice"}, blocking=True ) - assert helper.characteristics[LIGHT_ON].value == 0 + helper.async_assert_service_values( + ServicesTypes.LIGHTBULB, + { + CharacteristicsTypes.ON: False, + }, + ) async def test_switch_change_light_state_color_temp(hass, utcnow): @@ -85,9 +88,14 @@ async def test_switch_change_light_state_color_temp(hass, utcnow): {"entity_id": "light.testdevice", "brightness": 255, "color_temp": 400}, blocking=True, ) - assert helper.characteristics[LIGHT_ON].value == 1 - assert helper.characteristics[LIGHT_BRIGHTNESS].value == 100 - assert helper.characteristics[LIGHT_COLOR_TEMP].value == 400 + helper.async_assert_service_values( + ServicesTypes.LIGHTBULB, + { + CharacteristicsTypes.ON: True, + CharacteristicsTypes.BRIGHTNESS: 100, + CharacteristicsTypes.COLOR_TEMPERATURE: 400, + }, + ) async def test_switch_read_light_state(hass, utcnow): @@ -99,18 +107,26 @@ async def test_switch_read_light_state(hass, utcnow): assert state.state == "off" # Simulate that someone switched on the device in the real world not via HA - helper.characteristics[LIGHT_ON].set_value(True) - helper.characteristics[LIGHT_BRIGHTNESS].value = 100 - helper.characteristics[LIGHT_HUE].value = 4 - helper.characteristics[LIGHT_SATURATION].value = 5 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.LIGHTBULB, + { + CharacteristicsTypes.ON: True, + CharacteristicsTypes.BRIGHTNESS: 100, + CharacteristicsTypes.HUE: 4, + CharacteristicsTypes.SATURATION: 5, + }, + ) assert state.state == "on" assert state.attributes["brightness"] == 255 assert state.attributes["hs_color"] == (4, 5) # Simulate that device switched off in the real world not via HA - helper.characteristics[LIGHT_ON].set_value(False) - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.LIGHTBULB, + { + CharacteristicsTypes.ON: False, + }, + ) assert state.state == "off" @@ -122,8 +138,8 @@ async def test_switch_push_light_state(hass, utcnow): state = hass.states.get(LIGHT_BULB_ENTITY_ID) assert state.state == "off" - await helper.update_named_service( - LIGHT_BULB_NAME, + state = await helper.async_update( + ServicesTypes.LIGHTBULB, { CharacteristicsTypes.ON: True, CharacteristicsTypes.BRIGHTNESS: 100, @@ -131,15 +147,17 @@ async def test_switch_push_light_state(hass, utcnow): CharacteristicsTypes.SATURATION: 5, }, ) - - state = hass.states.get(LIGHT_BULB_ENTITY_ID) assert state.state == "on" assert state.attributes["brightness"] == 255 assert state.attributes["hs_color"] == (4, 5) # Simulate that device switched off in the real world not via HA - await helper.update_named_service(LIGHT_BULB_NAME, {CharacteristicsTypes.ON: False}) - state = hass.states.get(LIGHT_BULB_ENTITY_ID) + state = await helper.async_update( + ServicesTypes.LIGHTBULB, + { + CharacteristicsTypes.ON: False, + }, + ) assert state.state == "off" @@ -152,11 +170,14 @@ async def test_switch_read_light_state_color_temp(hass, utcnow): assert state.state == "off" # Simulate that someone switched on the device in the real world not via HA - helper.characteristics[LIGHT_ON].set_value(True) - helper.characteristics[LIGHT_BRIGHTNESS].value = 100 - helper.characteristics[LIGHT_COLOR_TEMP].value = 400 - - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.LIGHTBULB, + { + CharacteristicsTypes.ON: True, + CharacteristicsTypes.BRIGHTNESS: 100, + CharacteristicsTypes.COLOR_TEMPERATURE: 400, + }, + ) assert state.state == "on" assert state.attributes["brightness"] == 255 assert state.attributes["color_temp"] == 400 @@ -170,16 +191,14 @@ async def test_switch_push_light_state_color_temp(hass, utcnow): state = hass.states.get(LIGHT_BULB_ENTITY_ID) assert state.state == "off" - await helper.update_named_service( - LIGHT_BULB_NAME, + state = await helper.async_update( + ServicesTypes.LIGHTBULB, { CharacteristicsTypes.ON: True, CharacteristicsTypes.BRIGHTNESS: 100, CharacteristicsTypes.COLOR_TEMPERATURE: 400, }, ) - - state = hass.states.get(LIGHT_BULB_ENTITY_ID) assert state.state == "on" assert state.attributes["brightness"] == 255 assert state.attributes["color_temp"] == 400 @@ -199,12 +218,15 @@ async def test_light_becomes_unavailable_but_recovers(hass, utcnow): assert state.state == "unavailable" # Simulate that someone switched on the device in the real world not via HA - helper.characteristics[LIGHT_ON].set_value(True) - helper.characteristics[LIGHT_BRIGHTNESS].value = 100 - helper.characteristics[LIGHT_COLOR_TEMP].value = 400 helper.pairing.available = True - - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.LIGHTBULB, + { + CharacteristicsTypes.ON: True, + CharacteristicsTypes.BRIGHTNESS: 100, + CharacteristicsTypes.COLOR_TEMPERATURE: 400, + }, + ) assert state.state == "on" assert state.attributes["brightness"] == 255 assert state.attributes["color_temp"] == 400 diff --git a/tests/components/homekit_controller/test_lock.py b/tests/components/homekit_controller/test_lock.py index 15e645bf181832..8f996cea8e3f4e 100644 --- a/tests/components/homekit_controller/test_lock.py +++ b/tests/components/homekit_controller/test_lock.py @@ -4,9 +4,6 @@ from tests.components.homekit_controller.common import setup_test_component -LOCK_CURRENT_STATE = ("lock-mechanism", "lock-mechanism.current-state") -LOCK_TARGET_STATE = ("lock-mechanism", "lock-mechanism.target-state") - def create_lock_service(accessory): """Define a lock characteristics as per page 219 of HAP spec.""" @@ -35,45 +32,83 @@ async def test_switch_change_lock_state(hass, utcnow): await hass.services.async_call( "lock", "lock", {"entity_id": "lock.testdevice"}, blocking=True ) - assert helper.characteristics[LOCK_TARGET_STATE].value == 1 + helper.async_assert_service_values( + ServicesTypes.LOCK_MECHANISM, + { + CharacteristicsTypes.LOCK_MECHANISM_TARGET_STATE: 1, + }, + ) await hass.services.async_call( "lock", "unlock", {"entity_id": "lock.testdevice"}, blocking=True ) - assert helper.characteristics[LOCK_TARGET_STATE].value == 0 + helper.async_assert_service_values( + ServicesTypes.LOCK_MECHANISM, + { + CharacteristicsTypes.LOCK_MECHANISM_TARGET_STATE: 0, + }, + ) async def test_switch_read_lock_state(hass, utcnow): """Test that we can read the state of a HomeKit lock accessory.""" helper = await setup_test_component(hass, create_lock_service) - helper.characteristics[LOCK_CURRENT_STATE].value = 0 - helper.characteristics[LOCK_TARGET_STATE].value = 0 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.LOCK_MECHANISM, + { + CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE: 0, + CharacteristicsTypes.LOCK_MECHANISM_TARGET_STATE: 0, + }, + ) assert state.state == "unlocked" assert state.attributes["battery_level"] == 50 - helper.characteristics[LOCK_CURRENT_STATE].value = 1 - helper.characteristics[LOCK_TARGET_STATE].value = 1 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.LOCK_MECHANISM, + { + CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE: 1, + CharacteristicsTypes.LOCK_MECHANISM_TARGET_STATE: 1, + }, + ) assert state.state == "locked" - helper.characteristics[LOCK_CURRENT_STATE].value = 2 - helper.characteristics[LOCK_TARGET_STATE].value = 1 + await helper.async_update( + ServicesTypes.LOCK_MECHANISM, + { + CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE: 2, + CharacteristicsTypes.LOCK_MECHANISM_TARGET_STATE: 1, + }, + ) state = await helper.poll_and_get_state() assert state.state == "jammed" - helper.characteristics[LOCK_CURRENT_STATE].value = 3 - helper.characteristics[LOCK_TARGET_STATE].value = 1 + await helper.async_update( + ServicesTypes.LOCK_MECHANISM, + { + CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE: 3, + CharacteristicsTypes.LOCK_MECHANISM_TARGET_STATE: 1, + }, + ) state = await helper.poll_and_get_state() assert state.state == "unknown" - helper.characteristics[LOCK_CURRENT_STATE].value = 0 - helper.characteristics[LOCK_TARGET_STATE].value = 1 + await helper.async_update( + ServicesTypes.LOCK_MECHANISM, + { + CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE: 0, + CharacteristicsTypes.LOCK_MECHANISM_TARGET_STATE: 1, + }, + ) state = await helper.poll_and_get_state() assert state.state == "locking" - helper.characteristics[LOCK_CURRENT_STATE].value = 1 - helper.characteristics[LOCK_TARGET_STATE].value = 0 + await helper.async_update( + ServicesTypes.LOCK_MECHANISM, + { + CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE: 1, + CharacteristicsTypes.LOCK_MECHANISM_TARGET_STATE: 0, + }, + ) state = await helper.poll_and_get_state() assert state.state == "unlocking" diff --git a/tests/components/homekit_controller/test_media_player.py b/tests/components/homekit_controller/test_media_player.py index 44c53af02daa03..d45e785f31564d 100644 --- a/tests/components/homekit_controller/test_media_player.py +++ b/tests/components/homekit_controller/test_media_player.py @@ -8,11 +8,6 @@ from tests.components.homekit_controller.common import setup_test_component -CURRENT_MEDIA_STATE = ("television", "current-media-state") -TARGET_MEDIA_STATE = ("television", "target-media-state") -REMOTE_KEY = ("television", "remote-key") -ACTIVE_IDENTIFIER = ("television", "active-identifier") - def create_tv_service(accessory): """ @@ -26,10 +21,12 @@ def create_tv_service(accessory): cur_state = tv_service.add_char(CharacteristicsTypes.CURRENT_MEDIA_STATE) cur_state.value = 0 + cur_state.perms.append(CharacteristicPermissions.events) remote = tv_service.add_char(CharacteristicsTypes.REMOTE_KEY) remote.value = None remote.perms.append(CharacteristicPermissions.paired_write) + remote.perms.append(CharacteristicPermissions.events) # Add a HDMI 1 channel input_source_1 = accessory.add_service(ServicesTypes.INPUT_SOURCE) @@ -66,16 +63,28 @@ async def test_tv_read_state(hass, utcnow): """Test that we can read the state of a HomeKit fan accessory.""" helper = await setup_test_component(hass, create_tv_service) - helper.characteristics[CURRENT_MEDIA_STATE].value = 0 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.CURRENT_MEDIA_STATE: 0, + }, + ) assert state.state == "playing" - helper.characteristics[CURRENT_MEDIA_STATE].value = 1 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.CURRENT_MEDIA_STATE: 1, + }, + ) assert state.state == "paused" - helper.characteristics[CURRENT_MEDIA_STATE].value = 2 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.CURRENT_MEDIA_STATE: 2, + }, + ) assert state.state == "idle" @@ -92,8 +101,12 @@ async def test_play_remote_key(hass, utcnow): """Test that we can play media on a media player.""" helper = await setup_test_component(hass, create_tv_service) - helper.characteristics[CURRENT_MEDIA_STATE].value = 1 - await helper.poll_and_get_state() + await helper.async_update( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.CURRENT_MEDIA_STATE: 1, + }, + ) await hass.services.async_call( "media_player", @@ -101,28 +114,46 @@ async def test_play_remote_key(hass, utcnow): {"entity_id": "media_player.testdevice"}, blocking=True, ) - assert helper.characteristics[REMOTE_KEY].value == 11 + helper.async_assert_service_values( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.REMOTE_KEY: 11, + }, + ) # Second time should be a no-op - helper.characteristics[CURRENT_MEDIA_STATE].value = 0 - await helper.poll_and_get_state() + await helper.async_update( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.CURRENT_MEDIA_STATE: 0, + CharacteristicsTypes.REMOTE_KEY: None, + }, + ) - helper.characteristics[REMOTE_KEY].value = None await hass.services.async_call( "media_player", "media_play", {"entity_id": "media_player.testdevice"}, blocking=True, ) - assert helper.characteristics[REMOTE_KEY].value is None + helper.async_assert_service_values( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.REMOTE_KEY: None, + }, + ) async def test_pause_remote_key(hass, utcnow): """Test that we can pause a media player.""" helper = await setup_test_component(hass, create_tv_service) - helper.characteristics[CURRENT_MEDIA_STATE].value = 0 - await helper.poll_and_get_state() + await helper.async_update( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.CURRENT_MEDIA_STATE: 0, + }, + ) await hass.services.async_call( "media_player", @@ -130,28 +161,46 @@ async def test_pause_remote_key(hass, utcnow): {"entity_id": "media_player.testdevice"}, blocking=True, ) - assert helper.characteristics[REMOTE_KEY].value == 11 + helper.async_assert_service_values( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.REMOTE_KEY: 11, + }, + ) # Second time should be a no-op - helper.characteristics[CURRENT_MEDIA_STATE].value = 1 - await helper.poll_and_get_state() + await helper.async_update( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.CURRENT_MEDIA_STATE: 1, + CharacteristicsTypes.REMOTE_KEY: None, + }, + ) - helper.characteristics[REMOTE_KEY].value = None await hass.services.async_call( "media_player", "media_pause", {"entity_id": "media_player.testdevice"}, blocking=True, ) - assert helper.characteristics[REMOTE_KEY].value is None + helper.async_assert_service_values( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.REMOTE_KEY: None, + }, + ) async def test_play(hass, utcnow): """Test that we can play media on a media player.""" helper = await setup_test_component(hass, create_tv_service_with_target_media_state) - helper.characteristics[CURRENT_MEDIA_STATE].value = 1 - await helper.poll_and_get_state() + await helper.async_update( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.CURRENT_MEDIA_STATE: 1, + }, + ) await hass.services.async_call( "media_player", @@ -159,30 +208,48 @@ async def test_play(hass, utcnow): {"entity_id": "media_player.testdevice"}, blocking=True, ) - assert helper.characteristics[REMOTE_KEY].value is None - assert helper.characteristics[TARGET_MEDIA_STATE].value == 0 + helper.async_assert_service_values( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.REMOTE_KEY: None, + CharacteristicsTypes.TARGET_MEDIA_STATE: 0, + }, + ) # Second time should be a no-op - helper.characteristics[CURRENT_MEDIA_STATE].value = 0 - await helper.poll_and_get_state() + await helper.async_update( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.CURRENT_MEDIA_STATE: 0, + CharacteristicsTypes.TARGET_MEDIA_STATE: None, + }, + ) - helper.characteristics[TARGET_MEDIA_STATE].value = None await hass.services.async_call( "media_player", "media_play", {"entity_id": "media_player.testdevice"}, blocking=True, ) - assert helper.characteristics[REMOTE_KEY].value is None - assert helper.characteristics[TARGET_MEDIA_STATE].value is None + helper.async_assert_service_values( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.REMOTE_KEY: None, + CharacteristicsTypes.TARGET_MEDIA_STATE: None, + }, + ) async def test_pause(hass, utcnow): """Test that we can turn pause a media player.""" helper = await setup_test_component(hass, create_tv_service_with_target_media_state) - helper.characteristics[CURRENT_MEDIA_STATE].value = 0 - await helper.poll_and_get_state() + await helper.async_update( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.CURRENT_MEDIA_STATE: 0, + }, + ) await hass.services.async_call( "media_player", @@ -190,21 +257,35 @@ async def test_pause(hass, utcnow): {"entity_id": "media_player.testdevice"}, blocking=True, ) - assert helper.characteristics[REMOTE_KEY].value is None - assert helper.characteristics[TARGET_MEDIA_STATE].value == 1 + helper.async_assert_service_values( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.REMOTE_KEY: None, + CharacteristicsTypes.TARGET_MEDIA_STATE: 1, + }, + ) # Second time should be a no-op - helper.characteristics[CURRENT_MEDIA_STATE].value = 1 - await helper.poll_and_get_state() + await helper.async_update( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.CURRENT_MEDIA_STATE: 1, + CharacteristicsTypes.REMOTE_KEY: None, + }, + ) - helper.characteristics[REMOTE_KEY].value = None await hass.services.async_call( "media_player", "media_pause", {"entity_id": "media_player.testdevice"}, blocking=True, ) - assert helper.characteristics[REMOTE_KEY].value is None + helper.async_assert_service_values( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.REMOTE_KEY: None, + }, + ) async def test_stop(hass, utcnow): @@ -217,21 +298,35 @@ async def test_stop(hass, utcnow): {"entity_id": "media_player.testdevice"}, blocking=True, ) - assert helper.characteristics[TARGET_MEDIA_STATE].value == 2 + helper.async_assert_service_values( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.TARGET_MEDIA_STATE: 2, + }, + ) # Second time should be a no-op - helper.characteristics[CURRENT_MEDIA_STATE].value = 2 - await helper.poll_and_get_state() + await helper.async_update( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.CURRENT_MEDIA_STATE: 2, + CharacteristicsTypes.TARGET_MEDIA_STATE: None, + }, + ) - helper.characteristics[TARGET_MEDIA_STATE].value = None await hass.services.async_call( "media_player", "media_stop", {"entity_id": "media_player.testdevice"}, blocking=True, ) - assert helper.characteristics[REMOTE_KEY].value is None - assert helper.characteristics[TARGET_MEDIA_STATE].value is None + helper.async_assert_service_values( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.REMOTE_KEY: None, + CharacteristicsTypes.TARGET_MEDIA_STATE: None, + }, + ) async def test_tv_set_source(hass, utcnow): @@ -244,7 +339,12 @@ async def test_tv_set_source(hass, utcnow): {"entity_id": "media_player.testdevice", "source": "HDMI 2"}, blocking=True, ) - assert helper.characteristics[ACTIVE_IDENTIFIER].value == 2 + helper.async_assert_service_values( + ServicesTypes.TELEVISION, + { + CharacteristicsTypes.ACTIVE_IDENTIFIER: 2, + }, + ) state = await helper.poll_and_get_state() assert state.attributes["source"] == "HDMI 2" diff --git a/tests/components/homekit_controller/test_number.py b/tests/components/homekit_controller/test_number.py index d83b4195241c50..a8a40bfa7320c5 100644 --- a/tests/components/homekit_controller/test_number.py +++ b/tests/components/homekit_controller/test_number.py @@ -13,6 +13,7 @@ def create_switch_with_spray_level(accessory): CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL ) + spray_level.perms.append("ev") spray_level.value = 1 spray_level.minStep = 1 spray_level.minValue = 1 @@ -48,10 +49,9 @@ def create_switch_with_ecobee_fan_mode(accessory): async def test_read_number(hass, utcnow): """Test a switch service that has a sensor characteristic is correctly handled.""" helper = await setup_test_component(hass, create_switch_with_spray_level) - outlet = helper.accessory.services.first(service_type=ServicesTypes.OUTLET) # Helper will be for the primary entity, which is the outlet. Make a helper for the sensor. - energy_helper = Helper( + spray_level = Helper( hass, "number.testdevice_spray_quantity", helper.pairing, @@ -59,27 +59,25 @@ async def test_read_number(hass, utcnow): helper.config_entry, ) - outlet = energy_helper.accessory.services.first(service_type=ServicesTypes.OUTLET) - spray_level = outlet[CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL] - - state = await energy_helper.poll_and_get_state() + state = await spray_level.poll_and_get_state() assert state.state == "1" assert state.attributes["step"] == 1 assert state.attributes["min"] == 1 assert state.attributes["max"] == 5 - spray_level.value = 5 - state = await energy_helper.poll_and_get_state() + state = await spray_level.async_update( + ServicesTypes.OUTLET, + {CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL: 5}, + ) assert state.state == "5" async def test_write_number(hass, utcnow): """Test a switch service that has a sensor characteristic is correctly handled.""" helper = await setup_test_component(hass, create_switch_with_spray_level) - outlet = helper.accessory.services.first(service_type=ServicesTypes.OUTLET) # Helper will be for the primary entity, which is the outlet. Make a helper for the sensor. - energy_helper = Helper( + spray_level = Helper( hass, "number.testdevice_spray_quantity", helper.pairing, @@ -87,16 +85,16 @@ async def test_write_number(hass, utcnow): helper.config_entry, ) - outlet = energy_helper.accessory.services.first(service_type=ServicesTypes.OUTLET) - spray_level = outlet[CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL] - await hass.services.async_call( "number", "set_value", {"entity_id": "number.testdevice_spray_quantity", "value": 5}, blocking=True, ) - assert spray_level.value == 5 + spray_level.async_assert_service_values( + ServicesTypes.OUTLET, + {CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL: 5}, + ) await hass.services.async_call( "number", @@ -104,16 +102,18 @@ async def test_write_number(hass, utcnow): {"entity_id": "number.testdevice_spray_quantity", "value": 3}, blocking=True, ) - assert spray_level.value == 3 + spray_level.async_assert_service_values( + ServicesTypes.OUTLET, + {CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL: 3}, + ) async def test_write_ecobee_fan_mode_number(hass, utcnow): """Test a switch service that has a sensor characteristic is correctly handled.""" helper = await setup_test_component(hass, create_switch_with_ecobee_fan_mode) - outlet = helper.accessory.services.first(service_type=ServicesTypes.OUTLET) # Helper will be for the primary entity, which is the outlet. Make a helper for the sensor. - energy_helper = Helper( + fan_mode = Helper( hass, "number.testdevice_fan_mode", helper.pairing, @@ -121,16 +121,16 @@ async def test_write_ecobee_fan_mode_number(hass, utcnow): helper.config_entry, ) - outlet = energy_helper.accessory.services.first(service_type=ServicesTypes.OUTLET) - ecobee_fan_mode = outlet[CharacteristicsTypes.Vendor.ECOBEE_FAN_WRITE_SPEED] - await hass.services.async_call( "number", "set_value", {"entity_id": "number.testdevice_fan_mode", "value": 1}, blocking=True, ) - assert ecobee_fan_mode.value == 1 + fan_mode.async_assert_service_values( + ServicesTypes.OUTLET, + {CharacteristicsTypes.Vendor.ECOBEE_FAN_WRITE_SPEED: 1}, + ) await hass.services.async_call( "number", @@ -138,7 +138,10 @@ async def test_write_ecobee_fan_mode_number(hass, utcnow): {"entity_id": "number.testdevice_fan_mode", "value": 2}, blocking=True, ) - assert ecobee_fan_mode.value == 2 + fan_mode.async_assert_service_values( + ServicesTypes.OUTLET, + {CharacteristicsTypes.Vendor.ECOBEE_FAN_WRITE_SPEED: 2}, + ) await hass.services.async_call( "number", @@ -146,7 +149,10 @@ async def test_write_ecobee_fan_mode_number(hass, utcnow): {"entity_id": "number.testdevice_fan_mode", "value": 99}, blocking=True, ) - assert ecobee_fan_mode.value == 99 + fan_mode.async_assert_service_values( + ServicesTypes.OUTLET, + {CharacteristicsTypes.Vendor.ECOBEE_FAN_WRITE_SPEED: 99}, + ) await hass.services.async_call( "number", @@ -154,7 +160,10 @@ async def test_write_ecobee_fan_mode_number(hass, utcnow): {"entity_id": "number.testdevice_fan_mode", "value": 100}, blocking=True, ) - assert ecobee_fan_mode.value == 100 + fan_mode.async_assert_service_values( + ServicesTypes.OUTLET, + {CharacteristicsTypes.Vendor.ECOBEE_FAN_WRITE_SPEED: 100}, + ) await hass.services.async_call( "number", @@ -162,4 +171,7 @@ async def test_write_ecobee_fan_mode_number(hass, utcnow): {"entity_id": "number.testdevice_fan_mode", "value": 0}, blocking=True, ) - assert ecobee_fan_mode.value == 0 + fan_mode.async_assert_service_values( + ServicesTypes.OUTLET, + {CharacteristicsTypes.Vendor.ECOBEE_FAN_WRITE_SPEED: 0}, + ) diff --git a/tests/components/homekit_controller/test_select.py b/tests/components/homekit_controller/test_select.py index ea22bf68c540ea..6cc7f5336b45e2 100644 --- a/tests/components/homekit_controller/test_select.py +++ b/tests/components/homekit_controller/test_select.py @@ -12,6 +12,7 @@ def create_service_with_ecobee_mode(accessory: Accessory): current_mode = service.add_char(CharacteristicsTypes.Vendor.ECOBEE_CURRENT_MODE) current_mode.value = 0 + current_mode.perms.append("ev") service.add_char(CharacteristicsTypes.Vendor.ECOBEE_SET_HOLD_SCHEDULE) @@ -21,10 +22,9 @@ def create_service_with_ecobee_mode(accessory: Accessory): async def test_read_current_mode(hass, utcnow): """Test that Ecobee mode can be correctly read and show as human readable text.""" helper = await setup_test_component(hass, create_service_with_ecobee_mode) - service = helper.accessory.services.first(service_type=ServicesTypes.THERMOSTAT) # Helper will be for the primary entity, which is the service. Make a helper for the sensor. - energy_helper = Helper( + ecobee_mode = Helper( hass, "select.testdevice_current_mode", helper.pairing, @@ -32,27 +32,38 @@ async def test_read_current_mode(hass, utcnow): helper.config_entry, ) - mode = service[CharacteristicsTypes.Vendor.ECOBEE_CURRENT_MODE] - - state = await energy_helper.poll_and_get_state() + state = await ecobee_mode.async_update( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.Vendor.ECOBEE_CURRENT_MODE: 0, + }, + ) assert state.state == "home" - mode.value = 1 - state = await energy_helper.poll_and_get_state() + state = await ecobee_mode.async_update( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.Vendor.ECOBEE_CURRENT_MODE: 1, + }, + ) assert state.state == "sleep" - mode.value = 2 - state = await energy_helper.poll_and_get_state() + state = await ecobee_mode.async_update( + ServicesTypes.THERMOSTAT, + { + CharacteristicsTypes.Vendor.ECOBEE_CURRENT_MODE: 2, + }, + ) assert state.state == "away" async def test_write_current_mode(hass, utcnow): """Test can set a specific mode.""" helper = await setup_test_component(hass, create_service_with_ecobee_mode) - service = helper.accessory.services.first(service_type=ServicesTypes.THERMOSTAT) + helper.accessory.services.first(service_type=ServicesTypes.THERMOSTAT) # Helper will be for the primary entity, which is the service. Make a helper for the sensor. - energy_helper = Helper( + current_mode = Helper( hass, "select.testdevice_current_mode", helper.pairing, @@ -60,18 +71,16 @@ async def test_write_current_mode(hass, utcnow): helper.config_entry, ) - service = energy_helper.accessory.services.first( - service_type=ServicesTypes.THERMOSTAT - ) - mode = service[CharacteristicsTypes.Vendor.ECOBEE_SET_HOLD_SCHEDULE] - await hass.services.async_call( "select", "select_option", {"entity_id": "select.testdevice_current_mode", "option": "home"}, blocking=True, ) - assert mode.value == 0 + current_mode.async_assert_service_values( + ServicesTypes.THERMOSTAT, + {CharacteristicsTypes.Vendor.ECOBEE_SET_HOLD_SCHEDULE: 0}, + ) await hass.services.async_call( "select", @@ -79,7 +88,10 @@ async def test_write_current_mode(hass, utcnow): {"entity_id": "select.testdevice_current_mode", "option": "sleep"}, blocking=True, ) - assert mode.value == 1 + current_mode.async_assert_service_values( + ServicesTypes.THERMOSTAT, + {CharacteristicsTypes.Vendor.ECOBEE_SET_HOLD_SCHEDULE: 1}, + ) await hass.services.async_call( "select", @@ -87,4 +99,7 @@ async def test_write_current_mode(hass, utcnow): {"entity_id": "select.testdevice_current_mode", "option": "away"}, blocking=True, ) - assert mode.value == 2 + current_mode.async_assert_service_values( + ServicesTypes.THERMOSTAT, + {CharacteristicsTypes.Vendor.ECOBEE_SET_HOLD_SCHEDULE: 2}, + ) diff --git a/tests/components/homekit_controller/test_sensor.py b/tests/components/homekit_controller/test_sensor.py index 4c57d94b2b8e3c..fc1a48f0f5d019 100644 --- a/tests/components/homekit_controller/test_sensor.py +++ b/tests/components/homekit_controller/test_sensor.py @@ -7,15 +7,6 @@ from tests.components.homekit_controller.common import Helper, setup_test_component -TEMPERATURE = ("temperature", "temperature.current") -HUMIDITY = ("humidity", "relative-humidity.current") -LIGHT_LEVEL = ("light", "light-level.current") -CARBON_DIOXIDE_LEVEL = ("carbon-dioxide", "carbon-dioxide.level") -BATTERY_LEVEL = ("battery", "battery-level") -CHARGING_STATE = ("battery", "charging-state") -LO_BATT = ("battery", "status-lo-batt") -ON = ("outlet", "on") - def create_temperature_sensor_service(accessory): """Define temperature characteristics.""" @@ -71,12 +62,20 @@ async def test_temperature_sensor_read_state(hass, utcnow): hass, create_temperature_sensor_service, suffix="temperature" ) - helper.characteristics[TEMPERATURE].value = 10 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.TEMPERATURE_SENSOR, + { + CharacteristicsTypes.TEMPERATURE_CURRENT: 10, + }, + ) assert state.state == "10" - helper.characteristics[TEMPERATURE].value = 20 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.TEMPERATURE_SENSOR, + { + CharacteristicsTypes.TEMPERATURE_CURRENT: 20, + }, + ) assert state.state == "20" assert state.attributes["device_class"] == SensorDeviceClass.TEMPERATURE @@ -100,12 +99,20 @@ async def test_humidity_sensor_read_state(hass, utcnow): hass, create_humidity_sensor_service, suffix="humidity" ) - helper.characteristics[HUMIDITY].value = 10 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDITY_SENSOR, + { + CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT: 10, + }, + ) assert state.state == "10" - helper.characteristics[HUMIDITY].value = 20 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.HUMIDITY_SENSOR, + { + CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT: 20, + }, + ) assert state.state == "20" assert state.attributes["device_class"] == SensorDeviceClass.HUMIDITY @@ -117,12 +124,20 @@ async def test_light_level_sensor_read_state(hass, utcnow): hass, create_light_level_sensor_service, suffix="light_level" ) - helper.characteristics[LIGHT_LEVEL].value = 10 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.LIGHT_SENSOR, + { + CharacteristicsTypes.LIGHT_LEVEL_CURRENT: 10, + }, + ) assert state.state == "10" - helper.characteristics[LIGHT_LEVEL].value = 20 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.LIGHT_SENSOR, + { + CharacteristicsTypes.LIGHT_LEVEL_CURRENT: 20, + }, + ) assert state.state == "20" assert state.attributes["device_class"] == SensorDeviceClass.ILLUMINANCE @@ -134,12 +149,20 @@ async def test_carbon_dioxide_level_sensor_read_state(hass, utcnow): hass, create_carbon_dioxide_level_sensor_service, suffix="co2" ) - helper.characteristics[CARBON_DIOXIDE_LEVEL].value = 10 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.CARBON_DIOXIDE_SENSOR, + { + CharacteristicsTypes.CARBON_DIOXIDE_LEVEL: 10, + }, + ) assert state.state == "10" - helper.characteristics[CARBON_DIOXIDE_LEVEL].value = 20 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.CARBON_DIOXIDE_SENSOR, + { + CharacteristicsTypes.CARBON_DIOXIDE_LEVEL: 20, + }, + ) assert state.state == "20" @@ -149,13 +172,21 @@ async def test_battery_level_sensor(hass, utcnow): hass, create_battery_level_sensor, suffix="battery" ) - helper.characteristics[BATTERY_LEVEL].value = 100 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.BATTERY_SERVICE, + { + CharacteristicsTypes.BATTERY_LEVEL: 100, + }, + ) assert state.state == "100" assert state.attributes["icon"] == "mdi:battery" - helper.characteristics[BATTERY_LEVEL].value = 20 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.BATTERY_SERVICE, + { + CharacteristicsTypes.BATTERY_LEVEL: 20, + }, + ) assert state.state == "20" assert state.attributes["icon"] == "mdi:battery-20" @@ -168,13 +199,21 @@ async def test_battery_charging(hass, utcnow): hass, create_battery_level_sensor, suffix="battery" ) - helper.characteristics[BATTERY_LEVEL].value = 0 - helper.characteristics[CHARGING_STATE].value = 1 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.BATTERY_SERVICE, + { + CharacteristicsTypes.BATTERY_LEVEL: 0, + CharacteristicsTypes.CHARGING_STATE: 1, + }, + ) assert state.attributes["icon"] == "mdi:battery-outline" - helper.characteristics[BATTERY_LEVEL].value = 20 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.BATTERY_SERVICE, + { + CharacteristicsTypes.BATTERY_LEVEL: 20, + }, + ) assert state.attributes["icon"] == "mdi:battery-charging-20" @@ -184,13 +223,22 @@ async def test_battery_low(hass, utcnow): hass, create_battery_level_sensor, suffix="battery" ) - helper.characteristics[LO_BATT].value = 0 - helper.characteristics[BATTERY_LEVEL].value = 1 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.BATTERY_SERVICE, + { + CharacteristicsTypes.BATTERY_LEVEL: 1, + CharacteristicsTypes.STATUS_LO_BATT: 0, + }, + ) assert state.attributes["icon"] == "mdi:battery-10" - helper.characteristics[LO_BATT].value = 1 - state = await helper.poll_and_get_state() + state = await helper.async_update( + ServicesTypes.BATTERY_SERVICE, + { + CharacteristicsTypes.BATTERY_LEVEL: 1, + CharacteristicsTypes.STATUS_LO_BATT: 1, + }, + ) assert state.attributes["icon"] == "mdi:battery-alert" @@ -203,6 +251,7 @@ def create_switch_with_sensor(accessory): ) realtime_energy.value = 0 realtime_energy.format = "float" + realtime_energy.perms.append("ev") cur_state = service.add_char(CharacteristicsTypes.ON) cur_state.value = True @@ -213,7 +262,6 @@ def create_switch_with_sensor(accessory): async def test_switch_with_sensor(hass, utcnow): """Test a switch service that has a sensor characteristic is correctly handled.""" helper = await setup_test_component(hass, create_switch_with_sensor) - outlet = helper.accessory.services.first(service_type=ServicesTypes.OUTLET) # Helper will be for the primary entity, which is the outlet. Make a helper for the sensor. energy_helper = Helper( @@ -224,15 +272,20 @@ async def test_switch_with_sensor(hass, utcnow): helper.config_entry, ) - outlet = energy_helper.accessory.services.first(service_type=ServicesTypes.OUTLET) - realtime_energy = outlet[CharacteristicsTypes.Vendor.KOOGEEK_REALTIME_ENERGY] - - realtime_energy.value = 1 - state = await energy_helper.poll_and_get_state() + state = await energy_helper.async_update( + ServicesTypes.OUTLET, + { + CharacteristicsTypes.Vendor.KOOGEEK_REALTIME_ENERGY: 1, + }, + ) assert state.state == "1" - realtime_energy.value = 50 - state = await energy_helper.poll_and_get_state() + state = await energy_helper.async_update( + ServicesTypes.OUTLET, + { + CharacteristicsTypes.Vendor.KOOGEEK_REALTIME_ENERGY: 50, + }, + ) assert state.state == "50" diff --git a/tests/components/homekit_controller/test_switch.py b/tests/components/homekit_controller/test_switch.py index 5c737e63edc94e..fbea04171cb34b 100644 --- a/tests/components/homekit_controller/test_switch.py +++ b/tests/components/homekit_controller/test_switch.py @@ -43,6 +43,7 @@ def create_char_switch_service(accessory): service = accessory.add_service(ServicesTypes.OUTLET) on_char = service.add_char(CharacteristicsTypes.Vendor.AQARA_PAIRING_MODE) + on_char.perms.append("ev") on_char.value = False @@ -53,12 +54,22 @@ async def test_switch_change_outlet_state(hass, utcnow): await hass.services.async_call( "switch", "turn_on", {"entity_id": "switch.testdevice"}, blocking=True ) - assert helper.characteristics[("outlet", "on")].value == 1 + helper.async_assert_service_values( + ServicesTypes.OUTLET, + { + CharacteristicsTypes.ON: 1, + }, + ) await hass.services.async_call( "switch", "turn_off", {"entity_id": "switch.testdevice"}, blocking=True ) - assert helper.characteristics[("outlet", "on")].value == 0 + helper.async_assert_service_values( + ServicesTypes.OUTLET, + { + CharacteristicsTypes.ON: 0, + }, + ) async def test_switch_read_outlet_state(hass, utcnow): @@ -71,19 +82,25 @@ async def test_switch_read_outlet_state(hass, utcnow): assert switch_1.attributes["outlet_in_use"] is False # Simulate that someone switched on the device in the real world not via HA - helper.characteristics[("outlet", "on")].set_value(True) - switch_1 = await helper.poll_and_get_state() + switch_1 = await helper.async_update( + ServicesTypes.OUTLET, + {CharacteristicsTypes.ON: True}, + ) assert switch_1.state == "on" assert switch_1.attributes["outlet_in_use"] is False # Simulate that device switched off in the real world not via HA - helper.characteristics[("outlet", "on")].set_value(False) - switch_1 = await helper.poll_and_get_state() + switch_1 = await helper.async_update( + ServicesTypes.OUTLET, + {CharacteristicsTypes.ON: False}, + ) assert switch_1.state == "off" # Simulate that someone plugged something into the device - helper.characteristics[("outlet", "outlet-in-use")].value = True - switch_1 = await helper.poll_and_get_state() + switch_1 = await helper.async_update( + ServicesTypes.OUTLET, + {CharacteristicsTypes.OUTLET_IN_USE: True}, + ) assert switch_1.state == "off" assert switch_1.attributes["outlet_in_use"] is True @@ -95,12 +112,22 @@ async def test_valve_change_active_state(hass, utcnow): await hass.services.async_call( "switch", "turn_on", {"entity_id": "switch.testdevice"}, blocking=True ) - assert helper.characteristics[("valve", "active")].value == 1 + helper.async_assert_service_values( + ServicesTypes.VALVE, + { + CharacteristicsTypes.ACTIVE: 1, + }, + ) await hass.services.async_call( "switch", "turn_off", {"entity_id": "switch.testdevice"}, blocking=True ) - assert helper.characteristics[("valve", "active")].value == 0 + helper.async_assert_service_values( + ServicesTypes.VALVE, + { + CharacteristicsTypes.ACTIVE: 0, + }, + ) async def test_valve_read_state(hass, utcnow): @@ -115,20 +142,24 @@ async def test_valve_read_state(hass, utcnow): assert switch_1.attributes["remaining_duration"] == 99 # Simulate that someone switched on the device in the real world not via HA - helper.characteristics[("valve", "active")].set_value(True) - switch_1 = await helper.poll_and_get_state() + switch_1 = await helper.async_update( + ServicesTypes.VALVE, + {CharacteristicsTypes.ACTIVE: True}, + ) assert switch_1.state == "on" # Simulate that someone configured the device in the real world not via HA - helper.characteristics[ - ("valve", "is-configured") - ].value = IsConfiguredValues.NOT_CONFIGURED - switch_1 = await helper.poll_and_get_state() + switch_1 = await helper.async_update( + ServicesTypes.VALVE, + {CharacteristicsTypes.IS_CONFIGURED: IsConfiguredValues.NOT_CONFIGURED}, + ) assert switch_1.attributes["is_configured"] is False # Simulate that someone using the device in the real world not via HA - helper.characteristics[("valve", "in-use")].value = InUseValues.NOT_IN_USE - switch_1 = await helper.poll_and_get_state() + switch_1 = await helper.async_update( + ServicesTypes.VALVE, + {CharacteristicsTypes.IN_USE: InUseValues.NOT_IN_USE}, + ) assert switch_1.attributes["in_use"] is False @@ -137,8 +168,6 @@ async def test_char_switch_change_state(hass, utcnow): helper = await setup_test_component( hass, create_char_switch_service, suffix="pairing_mode" ) - svc = helper.accessory.services.first(service_type=ServicesTypes.OUTLET) - pairing_mode = svc[CharacteristicsTypes.Vendor.AQARA_PAIRING_MODE] await hass.services.async_call( "switch", @@ -146,7 +175,12 @@ async def test_char_switch_change_state(hass, utcnow): {"entity_id": "switch.testdevice_pairing_mode"}, blocking=True, ) - assert pairing_mode.value is True + helper.async_assert_service_values( + ServicesTypes.OUTLET, + { + CharacteristicsTypes.Vendor.AQARA_PAIRING_MODE: True, + }, + ) await hass.services.async_call( "switch", @@ -154,7 +188,12 @@ async def test_char_switch_change_state(hass, utcnow): {"entity_id": "switch.testdevice_pairing_mode"}, blocking=True, ) - assert pairing_mode.value is False + helper.async_assert_service_values( + ServicesTypes.OUTLET, + { + CharacteristicsTypes.Vendor.AQARA_PAIRING_MODE: False, + }, + ) async def test_char_switch_read_state(hass, utcnow): @@ -162,19 +201,17 @@ async def test_char_switch_read_state(hass, utcnow): helper = await setup_test_component( hass, create_char_switch_service, suffix="pairing_mode" ) - svc = helper.accessory.services.first(service_type=ServicesTypes.OUTLET) - pairing_mode = svc[CharacteristicsTypes.Vendor.AQARA_PAIRING_MODE] - - # Initial state is that the switch is off - switch_1 = await helper.poll_and_get_state() - assert switch_1.state == "off" # Simulate that someone switched on the device in the real world not via HA - pairing_mode.set_value(True) - switch_1 = await helper.poll_and_get_state() + switch_1 = await helper.async_update( + ServicesTypes.OUTLET, + {CharacteristicsTypes.Vendor.AQARA_PAIRING_MODE: True}, + ) assert switch_1.state == "on" # Simulate that device switched off in the real world not via HA - pairing_mode.set_value(False) - switch_1 = await helper.poll_and_get_state() + switch_1 = await helper.async_update( + ServicesTypes.OUTLET, + {CharacteristicsTypes.Vendor.AQARA_PAIRING_MODE: False}, + ) assert switch_1.state == "off" From ca7d4234e1fc8f094506ce59912e39a9a7235dd1 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 31 Jan 2022 00:14:28 +0000 Subject: [PATCH 0107/1098] [ci skip] Translation update --- .../components/abode/translations/el.json | 3 + .../components/abode/translations/pt-BR.json | 20 +++- .../components/abode/translations/uk.json | 2 +- .../accuweather/translations/pt-BR.json | 15 ++- .../translations/sensor.pt-BR.json | 9 ++ .../accuweather/translations/uk.json | 2 +- .../components/acmeda/translations/pt-BR.json | 10 +- .../components/adax/translations/pt-BR.json | 15 ++- .../components/adguard/translations/el.json | 3 + .../adguard/translations/pt-BR.json | 4 +- .../advantage_air/translations/pt-BR.json | 4 +- .../components/aemet/translations/pt-BR.json | 14 ++- .../agent_dvr/translations/pt-BR.json | 3 +- .../components/airly/translations/el.json | 6 ++ .../components/airly/translations/pt-BR.json | 14 ++- .../components/airnow/translations/pt-BR.json | 11 ++- .../airtouch4/translations/pt-BR.json | 6 +- .../airvisual/translations/pt-BR.json | 37 ++++++-- .../airvisual/translations/sensor.pt-BR.json | 20 ++++ .../alarm_control_panel/translations/el.json | 4 + .../translations/pt-BR.json | 10 +- .../alarmdecoder/translations/pt-BR.json | 58 ++++++++++- .../components/almond/translations/pt-BR.json | 4 + .../components/almond/translations/uk.json | 2 +- .../components/ambee/translations/pt-BR.json | 6 +- .../ambee/translations/sensor.pt-BR.json | 10 ++ .../amberelectric/translations/pt-BR.json | 22 +++++ .../ambiclimate/translations/pt-BR.json | 5 +- .../androidtv/translations/pt-BR.json | 5 +- .../apple_tv/translations/pt-BR.json | 60 +++++++++++- .../arcam_fmj/translations/pt-BR.json | 7 +- .../aseko_pool_live/translations/pt-BR.json | 1 + .../asuswrt/translations/pt-BR.json | 24 ++++- .../components/atag/translations/pt-BR.json | 6 +- .../components/august/translations/pt-BR.json | 9 +- .../components/aurora/translations/pt-BR.json | 12 ++- .../translations/pt-BR.json | 15 ++- .../aussie_broadband/translations/nl.json | 15 ++- .../aussie_broadband/translations/pt-BR.json | 2 +- .../automation/translations/pt-BR.json | 2 +- .../components/awair/translations/el.json | 3 + .../components/awair/translations/pt-BR.json | 14 ++- .../components/axis/translations/pt-BR.json | 10 ++ .../azure_devops/translations/pt-BR.json | 5 +- .../azure_event_hub/translations/pt-BR.json | 39 +++++++- .../components/balboa/translations/pt-BR.json | 10 ++ .../binary_sensor/translations/el.json | 8 ++ .../binary_sensor/translations/pt-BR.json | 20 +++- .../binary_sensor/translations/uk.json | 2 +- .../components/blebox/translations/pt-BR.json | 9 +- .../components/blink/translations/pt-BR.json | 13 ++- .../translations/pt-BR.json | 11 +++ .../components/bond/translations/pt-BR.json | 6 +- .../bosch_shc/translations/pt-BR.json | 22 ++++- .../braviatv/translations/pt-BR.json | 10 +- .../broadlink/translations/pt-BR.json | 6 +- .../brother/translations/pt-BR.json | 8 ++ .../components/bsblan/translations/pt-BR.json | 2 + .../buienradar/translations/pt-BR.json | 11 +++ .../calendar/translations/pt-BR.json | 4 +- .../components/canary/translations/pt-BR.json | 12 +++ .../components/canary/translations/uk.json | 2 +- .../components/cast/translations/pt-BR.json | 32 +++++++ .../components/cast/translations/uk.json | 2 +- .../cert_expiry/translations/pt-BR.json | 4 +- .../climacell/translations/pt-BR.json | 16 +++- .../climate/translations/pt-BR.json | 17 +++- .../components/cloud/translations/pt-BR.json | 17 ++++ .../cloudflare/translations/pt-BR.json | 23 ++++- .../cloudflare/translations/uk.json | 2 +- .../co2signal/translations/pt-BR.json | 13 ++- .../components/coinbase/translations/nl.json | 2 + .../coinbase/translations/pt-BR.json | 22 ++++- .../control4/translations/pt-BR.json | 10 ++ .../coolmaster/translations/pt-BR.json | 6 +- .../components/cover/translations/pt-BR.json | 27 ++++++ .../crownstone/translations/pt-BR.json | 45 +++++++-- .../components/deconz/translations/pt-BR.json | 46 ++++++++- .../components/demo/translations/pt-BR.json | 1 + .../demo/translations/select.pt-BR.json | 9 ++ .../denonavr/translations/pt-BR.json | 38 +++++++- .../device_tracker/translations/pt-BR.json | 10 ++ .../translations/pt-BR.json | 9 +- .../translations/pt-BR.json | 2 +- .../components/dexcom/translations/pt-BR.json | 12 +++ .../diagnostics/translations/nl.json | 3 + .../dialogflow/translations/ja.json | 1 + .../dialogflow/translations/nl.json | 1 + .../dialogflow/translations/pt-BR.json | 3 +- .../dialogflow/translations/uk.json | 3 +- .../directv/translations/pt-BR.json | 2 +- .../dlna_dmr/translations/pt-BR.json | 34 ++++++- .../components/dnsip/translations/ja.json | 7 ++ .../components/dnsip/translations/nl.json | 13 +++ .../components/doorbird/translations/el.json | 3 + .../doorbird/translations/pt-BR.json | 15 ++- .../components/dsmr/translations/pt-BR.json | 32 ++++++- .../components/dunehd/translations/pt-BR.json | 4 +- .../components/eafm/translations/pt-BR.json | 12 ++- .../components/ecobee/translations/pt-BR.json | 2 +- .../components/ecobee/translations/uk.json | 2 +- .../components/econet/translations/pt-BR.json | 4 +- .../components/efergy/translations/pt-BR.json | 3 +- .../components/elgato/translations/pt-BR.json | 8 +- .../components/elkm1/translations/el.json | 4 + .../components/elkm1/translations/pt-BR.json | 12 ++- .../components/elmax/translations/pt-BR.json | 6 +- .../emonitor/translations/pt-BR.json | 5 + .../emulated_roku/translations/pt-BR.json | 4 +- .../components/energy/translations/pt-BR.json | 3 + .../enocean/translations/pt-BR.json | 18 ++++ .../components/enocean/translations/uk.json | 2 +- .../enphase_envoy/translations/pt-BR.json | 4 +- .../translations/pt-BR.json | 11 ++- .../components/epson/translations/pt-BR.json | 3 +- .../esphome/translations/pt-BR.json | 17 +++- .../evil_genius_labs/translations/pt-BR.json | 1 + .../components/ezviz/translations/pt-BR.json | 21 +++- .../faa_delays/translations/pt-BR.json | 13 +++ .../components/fan/translations/nl.json | 2 + .../components/fan/translations/pt-BR.json | 8 +- .../fireservicerota/translations/pt-BR.json | 4 +- .../fjaraskupan/translations/pt-BR.json | 7 +- .../flick_electric/translations/pt-BR.json | 4 +- .../components/flipr/translations/pt-BR.json | 13 ++- .../components/flume/translations/pt-BR.json | 4 +- .../flunearyou/translations/pt-BR.json | 4 +- .../flux_led/translations/pt-BR.json | 4 +- .../forecast_solar/translations/pt-BR.json | 20 +++- .../forked_daapd/translations/pt-BR.json | 3 +- .../components/foscam/translations/pt-BR.json | 6 +- .../components/freebox/translations/el.json | 3 + .../freebox/translations/pt-BR.json | 8 +- .../freedompro/translations/pt-BR.json | 4 +- .../components/fritz/translations/pt-BR.json | 24 ++++- .../fritzbox/translations/pt-BR.json | 10 +- .../translations/pt-BR.json | 22 ++++- .../fronius/translations/pt-BR.json | 4 +- .../garages_amsterdam/translations/pt-BR.json | 11 ++- .../components/geofency/translations/ja.json | 1 + .../components/geofency/translations/nl.json | 1 + .../geofency/translations/pt-BR.json | 3 +- .../components/geofency/translations/uk.json | 3 +- .../geonetnz_volcano/translations/pt-BR.json | 3 +- .../components/gios/translations/el.json | 4 + .../components/gios/translations/pt-BR.json | 13 ++- .../components/github/translations/nl.json | 6 +- .../components/glances/translations/el.json | 7 ++ .../glances/translations/pt-BR.json | 12 ++- .../goalzero/translations/pt-BR.json | 8 +- .../gogogate2/translations/pt-BR.json | 3 +- .../components/goodwe/translations/nl.json | 1 + .../translations/pt-BR.json | 27 +++++- .../components/gpslogger/translations/ja.json | 1 + .../components/gpslogger/translations/nl.json | 1 + .../gpslogger/translations/pt-BR.json | 3 +- .../components/gpslogger/translations/uk.json | 3 +- .../components/gree/translations/pt-BR.json | 2 +- .../components/gree/translations/uk.json | 2 +- .../growatt_server/translations/pt-BR.json | 15 ++- .../guardian/translations/pt-BR.json | 9 +- .../habitica/translations/pt-BR.json | 8 +- .../hangouts/translations/pt-BR.json | 4 +- .../harmony/translations/pt-BR.json | 5 +- .../components/hassio/translations/pt-BR.json | 18 ++++ .../components/heos/translations/uk.json | 2 +- .../hisense_aehw4a1/translations/pt-BR.json | 7 +- .../hisense_aehw4a1/translations/uk.json | 2 +- .../components/hive/translations/pt-BR.json | 34 ++++++- .../home_connect/translations/pt-BR.json | 5 + .../home_plus_control/translations/pt-BR.json | 8 +- .../homeassistant/translations/pt-BR.json | 18 ++++ .../components/homekit/translations/nl.json | 1 + .../homekit/translations/pt-BR.json | 56 ++++++++++- .../translations/pt-BR.json | 41 ++++++-- .../translations/select.it.json | 9 ++ .../translations/select.ja.json | 9 ++ .../translations/select.nl.json | 9 ++ .../translations/select.uk.json | 9 ++ .../homematicip_cloud/translations/pt-BR.json | 2 +- .../homewizard/translations/it.json | 2 +- .../homewizard/translations/pt-BR.json | 5 + .../honeywell/translations/pt-BR.json | 4 +- .../huawei_lte/translations/pt-BR.json | 25 ++++- .../components/hue/translations/pt-BR.json | 20 +++- .../humidifier/translations/nl.json | 2 + .../humidifier/translations/pt-BR.json | 28 +++++- .../translations/pt-BR.json | 8 +- .../hvv_departures/translations/pt-BR.json | 31 +++++- .../hyperion/translations/pt-BR.json | 32 +++++++ .../components/iaqualink/translations/uk.json | 2 +- .../components/icloud/translations/pt-BR.json | 7 +- .../components/ifttt/translations/ja.json | 1 + .../components/ifttt/translations/nl.json | 1 + .../components/ifttt/translations/pt-BR.json | 3 +- .../components/ifttt/translations/uk.json | 3 +- .../insteon/translations/pt-BR.json | 58 ++++++++--- .../components/insteon/translations/uk.json | 2 +- .../components/ios/translations/uk.json | 2 +- .../iotawatt/translations/pt-BR.json | 3 +- .../components/ipma/translations/pt-BR.json | 5 + .../components/ipp/translations/pt-BR.json | 4 +- .../translations/pt-BR.json | 18 +++- .../islamic_prayer_times/translations/uk.json | 2 +- .../components/iss/translations/en.json | 4 +- .../components/iss/translations/it.json | 16 ++++ .../components/iss/translations/ja.json | 16 ++++ .../components/iss/translations/nl.json | 15 +++ .../components/iss/translations/pt-BR.json | 2 +- .../components/iss/translations/uk.json | 16 ++++ .../components/isy994/translations/pt-BR.json | 11 ++- .../components/isy994/translations/uk.json | 4 +- .../components/izone/translations/pt-BR.json | 2 +- .../components/izone/translations/uk.json | 2 +- .../juicenet/translations/pt-BR.json | 4 +- .../keenetic_ndms2/translations/pt-BR.json | 20 +++- .../kmtronic/translations/pt-BR.json | 9 ++ .../components/knx/translations/ja.json | 6 +- .../components/knx/translations/nl.json | 6 +- .../components/knx/translations/pt-BR.json | 36 ++++++- .../components/kodi/translations/pt-BR.json | 21 +++- .../konnected/translations/pt-BR.json | 38 ++++++-- .../components/konnected/translations/uk.json | 2 +- .../kostal_plenticore/translations/pt-BR.json | 3 +- .../components/kraken/translations/pt-BR.json | 10 ++ .../kulersky/translations/pt-BR.json | 2 +- .../components/kulersky/translations/uk.json | 2 +- .../components/lcn/translations/pt-BR.json | 10 ++ .../components/lifx/translations/pt-BR.json | 2 +- .../components/lifx/translations/uk.json | 2 +- .../components/light/translations/nl.json | 2 + .../components/light/translations/pt-BR.json | 15 +-- .../litejet/translations/pt-BR.json | 17 +++- .../components/local_ip/translations/uk.json | 2 +- .../components/locative/translations/ja.json | 1 + .../components/locative/translations/nl.json | 1 + .../locative/translations/pt-BR.json | 3 +- .../components/locative/translations/uk.json | 3 +- .../components/lock/translations/pt-BR.json | 8 ++ .../logi_circle/translations/pt-BR.json | 2 +- .../components/lookin/translations/pt-BR.json | 4 +- .../lovelace/translations/pt-BR.json | 10 ++ .../lutron_caseta/translations/pt-BR.json | 58 ++++++++++- .../components/lyric/translations/pt-BR.json | 4 + .../components/mailgun/translations/ja.json | 1 + .../components/mailgun/translations/nl.json | 1 + .../mailgun/translations/pt-BR.json | 3 +- .../components/mailgun/translations/uk.json | 3 +- .../components/mazda/translations/pt-BR.json | 12 ++- .../media_player/translations/nl.json | 1 + .../media_player/translations/pt-BR.json | 18 +++- .../melcloud/translations/pt-BR.json | 10 +- .../components/met/translations/el.json | 5 + .../components/met/translations/pt-BR.json | 3 + .../met_eireann/translations/pt-BR.json | 5 +- .../meteo_france/translations/pt-BR.json | 21 ++++ .../meteoclimatic/translations/pt-BR.json | 11 ++- .../metoffice/translations/pt-BR.json | 4 +- .../mikrotik/translations/pt-BR.json | 11 +++ .../minecraft_server/translations/pt-BR.json | 9 +- .../mobile_app/translations/pt-BR.json | 8 +- .../modem_callerid/translations/pt-BR.json | 11 ++- .../modern_forms/translations/pt-BR.json | 11 ++- .../components/monoprice/translations/el.json | 15 +++ .../monoprice/translations/pt-BR.json | 15 +++ .../motion_blinds/translations/pt-BR.json | 23 ++++- .../motioneye/translations/pt-BR.json | 9 +- .../components/mqtt/translations/pt-BR.json | 38 +++++++- .../components/mqtt/translations/uk.json | 2 +- .../mullvad/translations/pt-BR.json | 5 + .../mutesync/translations/pt-BR.json | 1 + .../components/myq/translations/pt-BR.json | 7 +- .../mysensors/translations/pt-BR.json | 66 ++++++++++++- .../components/nam/translations/pt-BR.json | 11 ++- .../nanoleaf/translations/pt-BR.json | 6 ++ .../components/neato/translations/pt-BR.json | 6 +- .../components/nest/translations/el.json | 3 + .../components/nest/translations/pt-BR.json | 16 +++- .../components/nest/translations/uk.json | 2 +- .../netatmo/translations/pt-BR.json | 42 +++++++- .../components/netatmo/translations/uk.json | 2 +- .../netgear/translations/pt-BR.json | 8 +- .../components/nexia/translations/pt-BR.json | 4 +- .../nfandroidtv/translations/pt-BR.json | 4 +- .../nightscout/translations/pt-BR.json | 5 +- .../components/nina/translations/pt-BR.json | 10 +- .../nmap_tracker/translations/pt-BR.json | 36 ++++++- .../components/notion/translations/el.json | 6 ++ .../components/nuki/translations/pt-BR.json | 1 + .../components/number/translations/pt-BR.json | 8 ++ .../components/nut/translations/pt-BR.json | 7 +- .../components/nzbget/translations/pt-BR.json | 11 +++ .../components/nzbget/translations/uk.json | 2 +- .../omnilogic/translations/pt-BR.json | 10 ++ .../components/omnilogic/translations/uk.json | 2 +- .../ondilo_ico/translations/pt-BR.json | 5 + .../onewire/translations/pt-BR.json | 12 ++- .../components/onvif/translations/pt-BR.json | 6 +- .../open_meteo/translations/pt-BR.json | 12 +++ .../opentherm_gw/translations/el.json | 18 +++- .../opentherm_gw/translations/pt-BR.json | 21 +++- .../components/openuv/translations/pt-BR.json | 11 +++ .../openweathermap/translations/pt-BR.json | 17 +++- .../components/overkiz/translations/ja.json | 3 +- .../components/overkiz/translations/nl.json | 4 +- .../overkiz/translations/pt-BR.json | 6 +- .../overkiz/translations/select.ru.json | 13 +++ .../overkiz/translations/sensor.nl.json | 6 +- .../overkiz/translations/sensor.pt-BR.json | 4 + .../ovo_energy/translations/pt-BR.json | 9 +- .../components/owntracks/translations/ja.json | 1 + .../components/owntracks/translations/nl.json | 1 + .../owntracks/translations/pt-BR.json | 2 +- .../components/owntracks/translations/uk.json | 3 +- .../components/ozw/translations/el.json | 3 + .../components/ozw/translations/pt-BR.json | 27 +++++- .../components/ozw/translations/uk.json | 2 +- .../p1_monitor/translations/pt-BR.json | 3 +- .../panasonic_viera/translations/pt-BR.json | 3 +- .../philips_js/translations/pt-BR.json | 21 +++- .../pi_hole/translations/pt-BR.json | 1 + .../components/picnic/translations/pt-BR.json | 4 +- .../components/plaato/translations/el.json | 3 +- .../components/plaato/translations/ja.json | 1 + .../components/plaato/translations/nl.json | 1 + .../components/plaato/translations/pt-BR.json | 41 +++++++- .../components/plaato/translations/uk.json | 3 +- .../components/plant/translations/pt-BR.json | 2 +- .../components/plex/translations/pt-BR.json | 11 ++- .../plugwise/translations/pt-BR.json | 23 ++++- .../plum_lightpad/translations/pt-BR.json | 3 +- .../components/point/translations/pt-BR.json | 7 +- .../components/point/translations/uk.json | 2 +- .../poolsense/translations/pt-BR.json | 4 +- .../powerwall/translations/pt-BR.json | 5 +- .../components/profiler/translations/uk.json | 2 +- .../progettihwsw/translations/pt-BR.json | 24 ++++- .../prosegur/translations/pt-BR.json | 2 + .../components/ps4/translations/pt-BR.json | 2 +- .../pvoutput/translations/pt-BR.json | 6 +- .../translations/pt-BR.json | 21 +++- .../components/rachio/translations/el.json | 10 ++ .../components/rachio/translations/pt-BR.json | 11 +++ .../rainforest_eagle/translations/pt-BR.json | 4 +- .../rainmachine/translations/pt-BR.json | 11 +++ .../components/rdw/translations/pt-BR.json | 3 +- .../recollect_waste/translations/pt-BR.json | 21 ++++ .../components/remote/translations/nl.json | 2 + .../components/remote/translations/pt-BR.json | 13 ++- .../renault/translations/pt-BR.json | 15 ++- .../components/rfxtrx/translations/pt-BR.json | 58 ++++++++++- .../components/rfxtrx/translations/uk.json | 2 +- .../components/ring/translations/pt-BR.json | 9 +- .../components/risco/translations/pt-BR.json | 28 +++++- .../translations/pt-BR.json | 4 +- .../components/roku/translations/el.json | 9 ++ .../components/roku/translations/pt-BR.json | 6 +- .../components/roomba/translations/pt-BR.json | 24 ++++- .../rpi_power/translations/pt-BR.json | 4 +- .../components/rpi_power/translations/uk.json | 2 +- .../rtsp_to_webrtc/translations/nl.json | 1 + .../rtsp_to_webrtc/translations/pt-BR.json | 2 + .../samsungtv/translations/pt-BR.json | 18 +++- .../screenlogic/translations/pt-BR.json | 23 ++++- .../components/select/translations/pt-BR.json | 14 +++ .../components/sense/translations/pt-BR.json | 4 +- .../components/senseme/translations/ja.json | 3 +- .../components/sensor/translations/pt-BR.json | 48 ++++++++-- .../components/sentry/translations/pt-BR.json | 22 ++++- .../components/sentry/translations/uk.json | 2 +- .../components/shelly/translations/pt-BR.json | 32 ++++++- .../components/sia/translations/pt-BR.json | 42 +++++++- .../simplisafe/translations/el.json | 10 ++ .../simplisafe/translations/pt-BR.json | 31 +++++- .../components/sma/translations/pt-BR.json | 6 +- .../smappee/translations/pt-BR.json | 19 +++- .../smarthab/translations/pt-BR.json | 6 +- .../smartthings/translations/pt-BR.json | 21 +++- .../smarttub/translations/pt-BR.json | 6 +- .../components/sms/translations/pt-BR.json | 8 ++ .../components/sms/translations/uk.json | 2 +- .../solaredge/translations/pt-BR.json | 4 +- .../solarlog/translations/pt-BR.json | 6 +- .../components/solax/translations/ja.json | 17 ++++ .../components/solax/translations/nl.json | 17 ++++ .../components/solax/translations/uk.json | 17 ++++ .../components/soma/translations/el.json | 14 +++ .../components/soma/translations/pt-BR.json | 4 +- .../components/somfy/translations/pt-BR.json | 5 + .../components/somfy/translations/uk.json | 2 +- .../somfy_mylink/translations/pt-BR.json | 34 ++++++- .../components/sonarr/translations/pt-BR.json | 13 +++ .../songpal/translations/pt-BR.json | 14 ++- .../components/sonos/translations/pt-BR.json | 5 +- .../components/sonos/translations/uk.json | 2 +- .../speedtestdotnet/translations/pt-BR.json | 14 ++- .../speedtestdotnet/translations/uk.json | 2 +- .../components/spider/translations/pt-BR.json | 3 +- .../components/spider/translations/uk.json | 2 +- .../spotify/translations/pt-BR.json | 17 +++- .../squeezebox/translations/pt-BR.json | 8 +- .../srp_energy/translations/pt-BR.json | 6 +- .../srp_energy/translations/uk.json | 2 +- .../starline/translations/pt-BR.json | 11 ++- .../components/steamist/translations/nl.json | 3 +- .../steamist/translations/pt-BR.json | 2 +- .../stookalert/translations/pt-BR.json | 7 ++ .../components/subaru/translations/pt-BR.json | 25 ++++- .../components/switch/translations/nl.json | 1 + .../components/switch/translations/pt-BR.json | 6 +- .../switchbot/translations/pt-BR.json | 17 ++++ .../switcher_kis/translations/pt-BR.json | 2 +- .../syncthing/translations/pt-BR.json | 5 +- .../syncthru/translations/pt-BR.json | 12 ++- .../synology_dsm/translations/nl.json | 1 + .../synology_dsm/translations/pt-BR.json | 16 +++- .../system_bridge/translations/pt-BR.json | 10 +- .../components/tado/translations/pt-BR.json | 1 + .../tasmota/translations/pt-BR.json | 15 +++ .../components/tasmota/translations/uk.json | 2 +- .../tellduslive/translations/pt-BR.json | 3 +- .../translations/pt-BR.json | 7 +- .../components/tibber/translations/el.json | 7 ++ .../components/tibber/translations/pt-BR.json | 7 +- .../components/tile/translations/pt-BR.json | 16 +++- .../components/tolo/translations/pt-BR.json | 6 +- .../tolo/translations/select.pt-BR.json | 8 ++ .../components/toon/translations/pt-BR.json | 16 +++- .../totalconnect/translations/pt-BR.json | 12 ++- .../components/tplink/translations/pt-BR.json | 12 ++- .../components/tplink/translations/uk.json | 2 +- .../components/traccar/translations/ja.json | 1 + .../components/traccar/translations/nl.json | 1 + .../traccar/translations/pt-BR.json | 5 +- .../components/traccar/translations/uk.json | 3 +- .../tractive/translations/pt-BR.json | 2 + .../tradfri/translations/pt-BR.json | 1 + .../transmission/translations/pt-BR.json | 2 + .../components/tuya/translations/pt-BR.json | 38 +++++--- .../tuya/translations/select.it.json | 8 ++ .../tuya/translations/select.ja.json | 13 +++ .../tuya/translations/select.nl.json | 24 +++++ .../tuya/translations/select.pt-BR.json | 44 ++++++--- .../tuya/translations/select.ru.json | 5 + .../tuya/translations/select.uk.json | 17 ++++ .../tuya/translations/sensor.pt-BR.json | 15 +++ .../components/tuya/translations/uk.json | 2 +- .../components/twilio/translations/ja.json | 1 + .../components/twilio/translations/nl.json | 1 + .../components/twilio/translations/pt-BR.json | 3 +- .../components/twilio/translations/uk.json | 2 +- .../twinkly/translations/pt-BR.json | 7 +- .../components/unifi/translations/el.json | 1 + .../components/unifi/translations/pt-BR.json | 33 +++++-- .../unifiprotect/translations/nl.json | 4 +- .../unifiprotect/translations/pt-BR.json | 13 ++- .../components/upb/translations/pt-BR.json | 6 +- .../upcloud/translations/pt-BR.json | 9 ++ .../updater/translations/pt-BR.json | 2 +- .../components/upnp/translations/pt-BR.json | 16 +++- .../uptimerobot/translations/pt-BR.json | 6 +- .../uptimerobot/translations/sensor.ja.json | 11 +++ .../uptimerobot/translations/sensor.nl.json | 11 +++ .../components/vacuum/translations/pt-BR.json | 8 +- .../components/vallox/translations/pt-BR.json | 1 + .../venstar/translations/pt-BR.json | 3 +- .../components/vera/translations/pt-BR.json | 30 ++++++ .../verisure/translations/pt-BR.json | 23 +++++ .../version/translations/pt-BR.json | 14 +++ .../components/vesync/translations/uk.json | 2 +- .../components/vicare/translations/pt-BR.json | 10 +- .../components/vilfo/translations/pt-BR.json | 1 + .../components/vizio/translations/el.json | 11 +++ .../components/vizio/translations/pt-BR.json | 20 +++- .../vlc_telnet/translations/pt-BR.json | 7 +- .../volumio/translations/pt-BR.json | 7 +- .../wallbox/translations/pt-BR.json | 4 +- .../water_heater/translations/pt-BR.json | 19 ++++ .../watttime/translations/pt-BR.json | 11 +++ .../waze_travel_time/translations/pt-BR.json | 28 +++++- .../weather/translations/pt-BR.json | 8 +- .../components/wemo/translations/pt-BR.json | 7 +- .../components/wemo/translations/uk.json | 2 +- .../components/whois/translations/ja.json | 6 ++ .../components/whois/translations/pt-BR.json | 2 + .../components/wiffi/translations/pt-BR.json | 9 ++ .../wilight/translations/pt-BR.json | 11 ++- .../withings/translations/pt-BR.json | 13 +++ .../components/wled/translations/nl.json | 3 +- .../components/wled/translations/pt-BR.json | 15 +++ .../wled/translations/select.pt-BR.json | 9 ++ .../wolflink/translations/pt-BR.json | 6 +- .../wolflink/translations/sensor.pt-BR.json | 71 +++++++++++++- .../components/xbox/translations/pt-BR.json | 5 + .../components/xbox/translations/uk.json | 2 +- .../xiaomi_aqara/translations/pt-BR.json | 30 +++++- .../xiaomi_miio/translations/pt-BR.json | 69 +++++++++++++- .../translations/select.pt-BR.json | 9 ++ .../yale_smart_alarm/translations/pt-BR.json | 2 + .../yamaha_musiccast/translations/pt-BR.json | 10 +- .../translations/select.pt-BR.json | 21 ++++ .../yeelight/translations/pt-BR.json | 28 +++++- .../components/zerproc/translations/uk.json | 2 +- .../components/zha/translations/pt-BR.json | 40 +++++++- .../components/zha/translations/uk.json | 2 +- .../zodiac/translations/sensor.pt-BR.json | 18 ++++ .../zoneminder/translations/pt-BR.json | 14 ++- .../components/zwave/translations/pt-BR.json | 4 +- .../components/zwave/translations/uk.json | 2 +- .../zwave_js/translations/pt-BR.json | 95 ++++++++++++++++++- 510 files changed, 4867 insertions(+), 606 deletions(-) create mode 100644 homeassistant/components/accuweather/translations/sensor.pt-BR.json create mode 100644 homeassistant/components/airvisual/translations/sensor.pt-BR.json create mode 100644 homeassistant/components/ambee/translations/sensor.pt-BR.json create mode 100644 homeassistant/components/amberelectric/translations/pt-BR.json create mode 100644 homeassistant/components/cloud/translations/pt-BR.json create mode 100644 homeassistant/components/demo/translations/select.pt-BR.json create mode 100644 homeassistant/components/diagnostics/translations/nl.json create mode 100644 homeassistant/components/energy/translations/pt-BR.json create mode 100644 homeassistant/components/glances/translations/el.json create mode 100644 homeassistant/components/hassio/translations/pt-BR.json create mode 100644 homeassistant/components/homeassistant/translations/pt-BR.json create mode 100644 homeassistant/components/homekit_controller/translations/select.it.json create mode 100644 homeassistant/components/homekit_controller/translations/select.ja.json create mode 100644 homeassistant/components/homekit_controller/translations/select.nl.json create mode 100644 homeassistant/components/homekit_controller/translations/select.uk.json create mode 100644 homeassistant/components/iss/translations/it.json create mode 100644 homeassistant/components/iss/translations/ja.json create mode 100644 homeassistant/components/iss/translations/nl.json create mode 100644 homeassistant/components/iss/translations/uk.json create mode 100644 homeassistant/components/lcn/translations/pt-BR.json create mode 100644 homeassistant/components/lovelace/translations/pt-BR.json create mode 100644 homeassistant/components/number/translations/pt-BR.json create mode 100644 homeassistant/components/open_meteo/translations/pt-BR.json create mode 100644 homeassistant/components/overkiz/translations/select.ru.json create mode 100644 homeassistant/components/rachio/translations/el.json create mode 100644 homeassistant/components/roku/translations/el.json create mode 100644 homeassistant/components/select/translations/pt-BR.json create mode 100644 homeassistant/components/solax/translations/ja.json create mode 100644 homeassistant/components/solax/translations/nl.json create mode 100644 homeassistant/components/solax/translations/uk.json create mode 100644 homeassistant/components/soma/translations/el.json create mode 100644 homeassistant/components/tibber/translations/el.json create mode 100644 homeassistant/components/tolo/translations/select.pt-BR.json create mode 100644 homeassistant/components/tuya/translations/select.uk.json create mode 100644 homeassistant/components/tuya/translations/sensor.pt-BR.json create mode 100644 homeassistant/components/uptimerobot/translations/sensor.ja.json create mode 100644 homeassistant/components/uptimerobot/translations/sensor.nl.json create mode 100644 homeassistant/components/vera/translations/pt-BR.json create mode 100644 homeassistant/components/water_heater/translations/pt-BR.json create mode 100644 homeassistant/components/wled/translations/select.pt-BR.json create mode 100644 homeassistant/components/xiaomi_miio/translations/select.pt-BR.json create mode 100644 homeassistant/components/zodiac/translations/sensor.pt-BR.json diff --git a/homeassistant/components/abode/translations/el.json b/homeassistant/components/abode/translations/el.json index a0501a41959578..9212c7d424750d 100644 --- a/homeassistant/components/abode/translations/el.json +++ b/homeassistant/components/abode/translations/el.json @@ -14,6 +14,9 @@ }, "reauth_confirm": { "title": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Abode" + }, + "user": { + "title": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Abode" } } } diff --git a/homeassistant/components/abode/translations/pt-BR.json b/homeassistant/components/abode/translations/pt-BR.json index dc83ae8dc5cac0..27f72830f87eeb 100644 --- a/homeassistant/components/abode/translations/pt-BR.json +++ b/homeassistant/components/abode/translations/pt-BR.json @@ -6,19 +6,29 @@ }, "error": { "cannot_connect": "Falha ao conectar", - "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "invalid_mfa_code": "C\u00f3digo MFA inv\u00e1lido" }, "step": { + "mfa": { + "data": { + "mfa_code": "C\u00f3digo MFA (6 d\u00edgitos)" + }, + "title": "Digite seu c\u00f3digo MFA para Abode" + }, "reauth_confirm": { "data": { - "password": "Senha" - } + "password": "Senha", + "username": "Email" + }, + "title": "Preencha as informa\u00e7\u00f5es de login da Abode" }, "user": { "data": { "password": "Senha", - "username": "Endere\u00e7o de e-mail" - } + "username": "Email" + }, + "title": "Preencha suas informa\u00e7\u00f5es de login Abode" } } } diff --git a/homeassistant/components/abode/translations/uk.json b/homeassistant/components/abode/translations/uk.json index 7ad57a0ec68a69..4ff498f98d6259 100644 --- a/homeassistant/components/abode/translations/uk.json +++ b/homeassistant/components/abode/translations/uk.json @@ -2,7 +2,7 @@ "config": { "abort": { "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430 \u0430\u0432\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f \u043f\u0440\u043e\u0439\u0448\u043b\u0430 \u0443\u0441\u043f\u0456\u0448\u043d\u043e", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "error": { "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", diff --git a/homeassistant/components/accuweather/translations/pt-BR.json b/homeassistant/components/accuweather/translations/pt-BR.json index f4ac44d3b60b75..469dd81ff9129b 100644 --- a/homeassistant/components/accuweather/translations/pt-BR.json +++ b/homeassistant/components/accuweather/translations/pt-BR.json @@ -5,16 +5,18 @@ }, "error": { "cannot_connect": "Falha ao conectar", - "invalid_api_key": "Chave de API inv\u00e1lida" + "invalid_api_key": "Chave de API inv\u00e1lida", + "requests_exceeded": "O n\u00famero permitido de solicita\u00e7\u00f5es para a API Accuweather foi excedido. Voc\u00ea precisa esperar ou alterar a chave de API." }, "step": { "user": { "data": { - "api_key": "Chave API", + "api_key": "Chave da API", "latitude": "Latitude", "longitude": "Longitude", "name": "Nome" }, + "description": "Se precisar de ajuda com a configura\u00e7\u00e3o, d\u00ea uma olhada aqui: https://www.home-assistant.io/integrations/accuweather/ \n\nAlguns sensores n\u00e3o s\u00e3o ativados por padr\u00e3o. Voc\u00ea pode habilit\u00e1-los no registro da entidade ap\u00f3s a configura\u00e7\u00e3o da integra\u00e7\u00e3o.\nA previs\u00e3o do tempo n\u00e3o est\u00e1 habilitada por padr\u00e3o. Voc\u00ea pode habilit\u00e1-lo nas op\u00e7\u00f5es de integra\u00e7\u00e3o.", "title": "AccuWeather" } } @@ -25,8 +27,15 @@ "data": { "forecast": "Previs\u00e3o do Tempo" }, - "description": "Devido \u00e0s limita\u00e7\u00f5es da vers\u00e3o gratuita da chave da API AccuWeather, quando voc\u00ea habilita a previs\u00e3o do tempo, as atualiza\u00e7\u00f5es de dados ser\u00e3o realizadas a cada 64 minutos em vez de a cada 32 minutos." + "description": "Devido \u00e0s limita\u00e7\u00f5es da vers\u00e3o gratuita da chave da API AccuWeather, quando voc\u00ea habilita a previs\u00e3o do tempo, as atualiza\u00e7\u00f5es de dados ser\u00e3o realizadas a cada 64 minutos em vez de a cada 32 minutos.", + "title": "Op\u00e7\u00f5es do AccuWeather" } } + }, + "system_health": { + "info": { + "can_reach_server": "Alcance o servidor AccuWeather", + "remaining_requests": "Solicita\u00e7\u00f5es permitidas restantes" + } } } \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/sensor.pt-BR.json b/homeassistant/components/accuweather/translations/sensor.pt-BR.json new file mode 100644 index 00000000000000..1e9cca9b30b5f8 --- /dev/null +++ b/homeassistant/components/accuweather/translations/sensor.pt-BR.json @@ -0,0 +1,9 @@ +{ + "state": { + "accuweather__pressure_tendency": { + "falling": "Queda", + "rising": "Eleva\u00e7\u00e3o", + "steady": "Est\u00e1vel" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/uk.json b/homeassistant/components/accuweather/translations/uk.json index 7432d0df484355..cb02c7e0ad522a 100644 --- a/homeassistant/components/accuweather/translations/uk.json +++ b/homeassistant/components/accuweather/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "error": { "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", diff --git a/homeassistant/components/acmeda/translations/pt-BR.json b/homeassistant/components/acmeda/translations/pt-BR.json index aa5c22db175e11..33d9f11e95e3ab 100644 --- a/homeassistant/components/acmeda/translations/pt-BR.json +++ b/homeassistant/components/acmeda/translations/pt-BR.json @@ -1,7 +1,15 @@ { "config": { "abort": { - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" + "no_devices_found": "Nenhum dispositivo encontrado na rede" + }, + "step": { + "user": { + "data": { + "id": "ID do host" + }, + "title": "Escolha um hub para adicionar" + } } } } \ No newline at end of file diff --git a/homeassistant/components/adax/translations/pt-BR.json b/homeassistant/components/adax/translations/pt-BR.json index 4bde04e1695745..09498932c202bb 100644 --- a/homeassistant/components/adax/translations/pt-BR.json +++ b/homeassistant/components/adax/translations/pt-BR.json @@ -2,6 +2,8 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "heater_not_available": "Aquecedor n\u00e3o dispon\u00edvel. Tente reiniciar o aquecedor pressionando + e OK por alguns segundos.", + "heater_not_found": "Aquecedor n\u00e3o encontrado. Tente aproximar o aquecedor do computador do Home Assistant.", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, "error": { @@ -11,14 +13,25 @@ "step": { "cloud": { "data": { + "account_id": "ID da conta", "password": "Senha" } }, + "local": { + "data": { + "wifi_pswd": "Senha do Wi-Fi", + "wifi_ssid": "Wi-Fi SSID" + }, + "description": "Reinicie o aquecedor pressionando + e OK at\u00e9 que o display mostre 'Reset'. Em seguida, pressione e segure o bot\u00e3o OK no aquecedor at\u00e9 que o led azul comece a piscar antes de pressionar Enviar. A configura\u00e7\u00e3o do aquecedor pode levar alguns minutos." + }, "user": { "data": { + "account_id": "ID da conta", + "connection_type": "Selecione o tipo de conex\u00e3o", "host": "Nome do host", "password": "Senha" - } + }, + "description": "Selecione o tipo de conex\u00e3o. Local requer aquecedores com bluetooth" } } } diff --git a/homeassistant/components/adguard/translations/el.json b/homeassistant/components/adguard/translations/el.json index e7a56e8f73620f..1ab13f21d962ca 100644 --- a/homeassistant/components/adguard/translations/el.json +++ b/homeassistant/components/adguard/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "existing_instance_updated": "\u0395\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03b7 \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03c3\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7." + }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, diff --git a/homeassistant/components/adguard/translations/pt-BR.json b/homeassistant/components/adguard/translations/pt-BR.json index aa33b469b1b1cb..1fd86188063116 100644 --- a/homeassistant/components/adguard/translations/pt-BR.json +++ b/homeassistant/components/adguard/translations/pt-BR.json @@ -9,8 +9,8 @@ }, "step": { "hassio_confirm": { - "description": "Deseja configurar o Home Assistant para se conectar ao AdGuard Home fornecido pelo complemento Supervisor: {addon} ?", - "title": "AdGuard Home via add-on Supervisor" + "description": "Deseja configurar o Home Assistant para se conectar ao AdGuard Home fornecido pelo add-on {addon}?", + "title": "AdGuard Home via add-on" }, "user": { "data": { diff --git a/homeassistant/components/advantage_air/translations/pt-BR.json b/homeassistant/components/advantage_air/translations/pt-BR.json index dcf9a44ea4a701..f2ec82e1a20d13 100644 --- a/homeassistant/components/advantage_air/translations/pt-BR.json +++ b/homeassistant/components/advantage_air/translations/pt-BR.json @@ -11,7 +11,9 @@ "data": { "ip_address": "Endere\u00e7o IP", "port": "Porta" - } + }, + "description": "Conecte-se \u00e0 API do seu tablet Advantage Air montado na parede.", + "title": "Conectar" } } } diff --git a/homeassistant/components/aemet/translations/pt-BR.json b/homeassistant/components/aemet/translations/pt-BR.json index 51e8d3fd84e1a0..6a0c8800b026d7 100644 --- a/homeassistant/components/aemet/translations/pt-BR.json +++ b/homeassistant/components/aemet/translations/pt-BR.json @@ -11,7 +11,19 @@ "data": { "api_key": "Chave da API", "latitude": "Latitude", - "longitude": "Longitude" + "longitude": "Longitude", + "name": "Nome da integra\u00e7\u00e3o" + }, + "description": "Configure a integra\u00e7\u00e3o AEMET OpenData. Para gerar a chave API acesse https://opendata.aemet.es/centrodedescargas/altaUsuario", + "title": "AEMET OpenData" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "station_updates": "Colete dados das esta\u00e7\u00f5es meteorol\u00f3gicas da AEMET" } } } diff --git a/homeassistant/components/agent_dvr/translations/pt-BR.json b/homeassistant/components/agent_dvr/translations/pt-BR.json index e4203d7c17737d..5dab2c95651ba3 100644 --- a/homeassistant/components/agent_dvr/translations/pt-BR.json +++ b/homeassistant/components/agent_dvr/translations/pt-BR.json @@ -12,7 +12,8 @@ "data": { "host": "Nome do host", "port": "Porta" - } + }, + "title": "Configurar agente DVR" } } } diff --git a/homeassistant/components/airly/translations/el.json b/homeassistant/components/airly/translations/el.json index 30677a8504195b..29133caadb8b92 100644 --- a/homeassistant/components/airly/translations/el.json +++ b/homeassistant/components/airly/translations/el.json @@ -2,6 +2,12 @@ "config": { "error": { "invalid_api_key": "\u0386\u03ba\u03c5\u03c1\u03bf API \u03ba\u03bb\u03b5\u03b9\u03b4\u03af" + }, + "step": { + "user": { + "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c0\u03bf\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b1\u03ad\u03c1\u03b1 Airly. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API, \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://developer.airly.eu/register", + "title": "Airly" + } } }, "system_health": { diff --git a/homeassistant/components/airly/translations/pt-BR.json b/homeassistant/components/airly/translations/pt-BR.json index abe96f8cccc5f2..e1f2fe097a6826 100644 --- a/homeassistant/components/airly/translations/pt-BR.json +++ b/homeassistant/components/airly/translations/pt-BR.json @@ -4,7 +4,8 @@ "already_configured": "Localiza\u00e7\u00e3o j\u00e1 est\u00e1 configurada" }, "error": { - "invalid_api_key": "Chave de API inv\u00e1lida" + "invalid_api_key": "Chave de API inv\u00e1lida", + "wrong_location": "N\u00e3o h\u00e1 esta\u00e7\u00f5es de medi\u00e7\u00e3o a\u00e9reas nesta \u00e1rea." }, "step": { "user": { @@ -13,8 +14,17 @@ "latitude": "Latitude", "longitude": "Longitude", "name": "Nome" - } + }, + "description": "Configure a integra\u00e7\u00e3o da qualidade do ar airly. Para gerar a chave de API v\u00e1 para https://developer.airly.eu/register", + "title": "Airly" } } + }, + "system_health": { + "info": { + "can_reach_server": "Alcance o servidor Airly", + "requests_per_day": "Solicita\u00e7\u00f5es permitidas por dia", + "requests_remaining": "Solicita\u00e7\u00f5es permitidas restantes" + } } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/pt-BR.json b/homeassistant/components/airnow/translations/pt-BR.json index acc62eaa7d8140..fa24093b4192ed 100644 --- a/homeassistant/components/airnow/translations/pt-BR.json +++ b/homeassistant/components/airnow/translations/pt-BR.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "invalid_location": "Nenhum resultado encontrado para esse local", "unknown": "Erro inesperado" }, "step": { @@ -13,9 +14,13 @@ "data": { "api_key": "Chave da API", "latitude": "Latitude", - "longitude": "Longitude" - } + "longitude": "Longitude", + "radius": "Raio da Esta\u00e7\u00e3o (milhas; opcional)" + }, + "description": "Configure a integra\u00e7\u00e3o da qualidade do ar AirNow. Para gerar a chave de API, acesse https://docs.airnowapi.org/account/request/", + "title": "AirNow" } } - } + }, + "title": "AirNow" } \ No newline at end of file diff --git a/homeassistant/components/airtouch4/translations/pt-BR.json b/homeassistant/components/airtouch4/translations/pt-BR.json index fb9b1f4c79ebf5..e710bc87da07f9 100644 --- a/homeassistant/components/airtouch4/translations/pt-BR.json +++ b/homeassistant/components/airtouch4/translations/pt-BR.json @@ -4,13 +4,15 @@ "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "error": { - "cannot_connect": "Falha ao conectar" + "cannot_connect": "Falha ao conectar", + "no_units": "N\u00e3o foi poss\u00edvel encontrar nenhum Grupo AirTouch 4." }, "step": { "user": { "data": { "host": "Nome do host" - } + }, + "title": "Configure os detalhes de conex\u00e3o do AirTouch 4." } } } diff --git a/homeassistant/components/airvisual/translations/pt-BR.json b/homeassistant/components/airvisual/translations/pt-BR.json index b9db19d0346602..b1d8b655029ec3 100644 --- a/homeassistant/components/airvisual/translations/pt-BR.json +++ b/homeassistant/components/airvisual/translations/pt-BR.json @@ -7,7 +7,8 @@ "error": { "cannot_connect": "Falha ao conectar", "general_error": "Erro inesperado", - "invalid_api_key": "Chave de API inv\u00e1lida" + "invalid_api_key": "Chave de API inv\u00e1lida", + "location_not_found": "Localiza\u00e7\u00e3o n\u00e3o encontrada" }, "step": { "geography_by_coords": { @@ -15,23 +16,47 @@ "api_key": "Chave da API", "latitude": "Latitude", "longitude": "Longitude" - } + }, + "description": "Use a API de nuvem AirVisual para monitorar uma latitude/longitude.", + "title": "Configurar uma geografia" }, "geography_by_name": { "data": { - "api_key": "Chave da API" - } + "api_key": "Chave da API", + "city": "Cidade", + "country": "Pa\u00eds", + "state": "Estado" + }, + "description": "Use a API de nuvem AirVisual para monitorar uma cidade/estado/pa\u00eds.", + "title": "Configurar uma geografia" }, "node_pro": { "data": { "ip_address": "Nome do host", "password": "Senha" - } + }, + "description": "Monitore uma unidade AirVisual pessoal. A senha pode ser recuperada da interface do usu\u00e1rio da unidade.", + "title": "Configurar um n\u00f3/pro AirVisual" }, "reauth_confirm": { "data": { "api_key": "Chave da API" - } + }, + "title": "Reautenticar o AirVisual" + }, + "user": { + "description": "Escolha que tipo de dados do AirVisual voc\u00ea deseja monitorar.", + "title": "Configurar o Airvisual" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "Mostrar o monitoramento no mapa" + }, + "title": "Configurar o AirVisual" } } } diff --git a/homeassistant/components/airvisual/translations/sensor.pt-BR.json b/homeassistant/components/airvisual/translations/sensor.pt-BR.json new file mode 100644 index 00000000000000..07ea1099cc1e52 --- /dev/null +++ b/homeassistant/components/airvisual/translations/sensor.pt-BR.json @@ -0,0 +1,20 @@ +{ + "state": { + "airvisual__pollutant_label": { + "co": "Mon\u00f3xido de carbono", + "n2": "Di\u00f3xido de nitrog\u00eanio", + "o3": "Oz\u00f4nio", + "p1": "PM10", + "p2": "PM2,5", + "s2": "Di\u00f3xido de enxofre" + }, + "airvisual__pollutant_level": { + "good": "Bom", + "hazardous": "Perigoso", + "moderate": "Moderado", + "unhealthy": "Insalubre", + "unhealthy_sensitive": "Insalubre para grupos sens\u00edveis", + "very_unhealthy": "Muito insalubre" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarm_control_panel/translations/el.json b/homeassistant/components/alarm_control_panel/translations/el.json index ed19402786f32e..b0aeb95fcb9ea3 100644 --- a/homeassistant/components/alarm_control_panel/translations/el.json +++ b/homeassistant/components/alarm_control_panel/translations/el.json @@ -8,6 +8,10 @@ "trigger": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 {entity_name}" }, "condition_type": { + "is_armed_away": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03bf\u03c0\u03bb\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf \u03b5\u03ba\u03c4\u03cc\u03c2", + "is_armed_home": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03bf\u03c0\u03bb\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf \u03c3\u03b5 \u03c3\u03c0\u03af\u03c4\u03b9", + "is_armed_night": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03bf\u03c0\u03bb\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf \u03c3\u03b5 \u03bd\u03cd\u03c7\u03c4\u03b1", + "is_disarmed": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c6\u03bf\u03c0\u03bb\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf", "is_triggered": "{entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5" }, "trigger_type": { diff --git a/homeassistant/components/alarm_control_panel/translations/pt-BR.json b/homeassistant/components/alarm_control_panel/translations/pt-BR.json index 07e005cba03e86..70d7a68ecc4540 100644 --- a/homeassistant/components/alarm_control_panel/translations/pt-BR.json +++ b/homeassistant/components/alarm_control_panel/translations/pt-BR.json @@ -4,13 +4,15 @@ "arm_away": "Armar {entity_name} longe", "arm_home": "Armar {entity_name} casa", "arm_night": "Armar {entity_name} noite", + "arm_vacation": "Armar {entity_name} f\u00e9rias", "disarm": "Desarmar {entity_name}", - "trigger": "Disparar {entidade_nome}" + "trigger": "Disparar {entity_name}" }, "condition_type": { "is_armed_away": "{entity_name} est\u00e1 armado modo longe", "is_armed_home": "{entity_name} est\u00e1 armadado modo casa", "is_armed_night": "{entity_name} est\u00e1 armadado modo noite", + "is_armed_vacation": "{entity_name} est\u00e1 armadado modo f\u00e9rias", "is_disarmed": "{entity_name} est\u00e1 desarmado", "is_triggered": "{entity_name} est\u00e1 acionado" }, @@ -18,6 +20,7 @@ "armed_away": "{entity_name} armado modo longe", "armed_home": "{entity_name} armadado modo casa", "armed_night": "{entity_name} armadado para noite", + "armed_vacation": "{entity_name} armadado para f\u00e9rias", "disarmed": "{entity_name} desarmado", "triggered": "{entity_name} acionado" } @@ -27,8 +30,9 @@ "armed": "Armado", "armed_away": "Armado ausente", "armed_custom_bypass": "Armado em \u00e1reas espec\u00edficas", - "armed_home": "Armado casa", - "armed_night": "Armado noite", + "armed_home": "Armado em casa", + "armed_night": "Armado noturno", + "armed_vacation": "Armado f\u00e9rias", "arming": "Armando", "disarmed": "Desarmado", "disarming": "Desarmando", diff --git a/homeassistant/components/alarmdecoder/translations/pt-BR.json b/homeassistant/components/alarmdecoder/translations/pt-BR.json index 8caf8d08cece01..bdc65371ac7cd7 100644 --- a/homeassistant/components/alarmdecoder/translations/pt-BR.json +++ b/homeassistant/components/alarmdecoder/translations/pt-BR.json @@ -3,15 +3,71 @@ "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, + "create_entry": { + "default": "Conectado com sucesso ao AlarmDecoder." + }, "error": { "cannot_connect": "Falha ao conectar" }, "step": { "protocol": { "data": { + "device_baudrate": "Taxa de transmiss\u00e3o do dispositivo", + "device_path": "Caminho do dispositivo", "host": "Nome do host", "port": "Porta" - } + }, + "title": "Definir as configura\u00e7\u00f5es de conex\u00e3o" + }, + "user": { + "data": { + "protocol": "Protocolo" + }, + "title": "Escolha o protocolo AlarmDecoder" + } + } + }, + "options": { + "error": { + "int": "O campo abaixo deve ser um n\u00famero inteiro.", + "loop_range": "RF Loop deve ser um n\u00famero inteiro entre 1 e 4.", + "loop_rfid": "RF Loop n\u00e3o pode ser usado sem RF Serial.", + "relay_inclusive": "O Relay Address e o Relay Channel s\u00e3o codependentes e devem ser inclu\u00eddos juntos." + }, + "step": { + "arm_settings": { + "data": { + "alt_night_mode": "Modo noturno alternativo", + "auto_bypass": "Auto Bypass ao armar", + "code_arm_required": "C\u00f3digo necess\u00e1rio para armar" + }, + "title": "Configurar AlarmDecoder" + }, + "init": { + "data": { + "edit_select": "Editar" + }, + "description": "O que voc\u00ea gostaria de editar?", + "title": "Configurar AlarmDecoder" + }, + "zone_details": { + "data": { + "zone_loop": "Loop RF", + "zone_name": "Nome da zona", + "zone_relayaddr": "Endere\u00e7o do rel\u00e9", + "zone_relaychan": "Canal do rel\u00e9", + "zone_rfid": "RF Serial", + "zone_type": "Tipo de zona" + }, + "description": "Insira os detalhes da zona {zone_number}. Para excluir a zona {zone_number}, deixe o nome da zona em branco.", + "title": "Configurar AlarmDecoder" + }, + "zone_select": { + "data": { + "zone_number": "N\u00famero da zona" + }, + "description": "Insira o n\u00famero da zona que voc\u00ea deseja adicionar, editar ou remover.", + "title": "Configurar AlarmDecoder" } } } diff --git a/homeassistant/components/almond/translations/pt-BR.json b/homeassistant/components/almond/translations/pt-BR.json index d6ddde76595224..d012b1695f3a35 100644 --- a/homeassistant/components/almond/translations/pt-BR.json +++ b/homeassistant/components/almond/translations/pt-BR.json @@ -7,6 +7,10 @@ "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "step": { + "hassio_confirm": { + "description": "Deseja configurar o Home Assistant para se conectar ao Almond fornecido pelo add-on {addon} ?", + "title": "Almond via add-on" + }, "pick_implementation": { "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" } diff --git a/homeassistant/components/almond/translations/uk.json b/homeassistant/components/almond/translations/uk.json index db96ef3d0a3aec..d1e0d1e1cb623a 100644 --- a/homeassistant/components/almond/translations/uk.json +++ b/homeassistant/components/almond/translations/uk.json @@ -4,7 +4,7 @@ "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", "missing_configuration": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438.", "no_url_available": "URL-\u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430. \u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0456\u0454\u044e] ({docs_url}) \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457 \u043f\u0440\u043e \u0446\u044e \u043f\u043e\u043c\u0438\u043b\u043a\u0443.", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "step": { "hassio_confirm": { diff --git a/homeassistant/components/ambee/translations/pt-BR.json b/homeassistant/components/ambee/translations/pt-BR.json index 03e813c18ff31a..2d960e17df248a 100644 --- a/homeassistant/components/ambee/translations/pt-BR.json +++ b/homeassistant/components/ambee/translations/pt-BR.json @@ -10,7 +10,8 @@ "step": { "reauth_confirm": { "data": { - "api_key": "Chave da API" + "api_key": "Chave da API", + "description": "Re-autentique com sua conta Ambee." } }, "user": { @@ -19,7 +20,8 @@ "latitude": "Latitude", "longitude": "Longitude", "name": "Nome" - } + }, + "description": "Configure o Ambee para integrar com o Home Assistant." } } } diff --git a/homeassistant/components/ambee/translations/sensor.pt-BR.json b/homeassistant/components/ambee/translations/sensor.pt-BR.json new file mode 100644 index 00000000000000..2e0dc187368e20 --- /dev/null +++ b/homeassistant/components/ambee/translations/sensor.pt-BR.json @@ -0,0 +1,10 @@ +{ + "state": { + "ambee__risk": { + "high": "Alto", + "low": "Baixo", + "moderate": "Moderado", + "very high": "Muito alto" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/amberelectric/translations/pt-BR.json b/homeassistant/components/amberelectric/translations/pt-BR.json new file mode 100644 index 00000000000000..6e8eb4d0ac8cec --- /dev/null +++ b/homeassistant/components/amberelectric/translations/pt-BR.json @@ -0,0 +1,22 @@ +{ + "config": { + "step": { + "site": { + "data": { + "site_name": "Nome do site", + "site_nmi": "Site NMI" + }, + "description": "Selecione o NMI do site que voc\u00ea gostaria de adicionar", + "title": "\u00c2mbar el\u00e9trico" + }, + "user": { + "data": { + "api_token": "Token de API", + "site_id": "ID do site" + }, + "description": "V\u00e1 para {api_url} para gerar uma chave de API", + "title": "\u00c2mbar el\u00e9trico" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambiclimate/translations/pt-BR.json b/homeassistant/components/ambiclimate/translations/pt-BR.json index 9f6290dc49227e..19c0bce83cc886 100644 --- a/homeassistant/components/ambiclimate/translations/pt-BR.json +++ b/homeassistant/components/ambiclimate/translations/pt-BR.json @@ -2,7 +2,8 @@ "config": { "abort": { "access_token": "Erro desconhecido ao gerar um token de acesso.", - "already_configured": "A conta j\u00e1 foi configurada" + "already_configured": "A conta j\u00e1 foi configurada", + "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o." }, "create_entry": { "default": "Autenticado com sucesso" @@ -13,7 +14,7 @@ }, "step": { "auth": { - "description": "Por favor, siga este [link]({authorization_url}) e Permitir acesso \u00e0 sua conta Ambiclimate, em seguida, volte e pressione Enviar abaixo. \n (Verifique se a URL de retorno de chamada especificada \u00e9 {cb_url})", + "description": "Por favor, siga este [link]({authorization_url}) e **Permitir** acesso \u00e0 sua conta Ambiclimate, em seguida, volte e pressione **Enviar** abaixo. \n (Verifique se a URL de retorno de chamada especificada \u00e9 {cb_url})", "title": "Autenticar Ambiclimate" } } diff --git a/homeassistant/components/androidtv/translations/pt-BR.json b/homeassistant/components/androidtv/translations/pt-BR.json index b5b2f9fc0c289b..86e9a29dd12d61 100644 --- a/homeassistant/components/androidtv/translations/pt-BR.json +++ b/homeassistant/components/androidtv/translations/pt-BR.json @@ -1,11 +1,14 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "invalid_unique_id": "Imposs\u00edvel determinar um ID exclusivo v\u00e1lido para o dispositivo" }, "error": { + "adbkey_not_file": "Arquivo de chave ADB n\u00e3o encontrado", "cannot_connect": "Falha ao conectar", "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido", + "key_and_server": "Forne\u00e7a apenas chave ADB ou servidor ADB", "unknown": "Erro inesperado" }, "step": { diff --git a/homeassistant/components/apple_tv/translations/pt-BR.json b/homeassistant/components/apple_tv/translations/pt-BR.json index 5e0497e76c0066..79fee5f02ec393 100644 --- a/homeassistant/components/apple_tv/translations/pt-BR.json +++ b/homeassistant/components/apple_tv/translations/pt-BR.json @@ -4,22 +4,74 @@ "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "already_configured_device": "Dispositivo j\u00e1 est\u00e1 configurado", "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "backoff": "O dispositivo n\u00e3o aceita solicita\u00e7\u00f5es de emparelhamento neste momento (voc\u00ea pode ter digitado um c\u00f3digo PIN inv\u00e1lido muitas vezes), tente novamente mais tarde.", + "device_did_not_pair": "Nenhuma tentativa de concluir o processo de emparelhamento foi feita a partir do dispositivo.", + "device_not_found": "O dispositivo n\u00e3o foi encontrado durante a descoberta. Tente adicion\u00e1-lo novamente.", + "inconsistent_device": "Os protocolos esperados n\u00e3o foram encontrados durante a descoberta. Isso normalmente indica um problema com o DNS multicast (Zeroconf). Tente adicionar o dispositivo novamente.", + "invalid_config": "A configura\u00e7\u00e3o deste dispositivo est\u00e1 incompleta. Tente adicion\u00e1-lo novamente.", + "no_devices_found": "Nenhum dispositivo encontrado na rede", "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", + "setup_failed": "Falha ao configurar o dispositivo.", "unknown": "Erro inesperado" }, "error": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "no_devices_found": "Nenhum dispositivo encontrado na rede", + "no_usable_service": "Um dispositivo foi encontrado, mas n\u00e3o foi poss\u00edvel identificar nenhuma maneira de estabelecer uma conex\u00e3o com ele. Se voc\u00ea continuar vendo esta mensagem, tente especificar o endere\u00e7o IP ou reiniciar a Apple TV.", "unknown": "Erro inesperado" }, + "flow_title": "{name} ({type})", "step": { + "confirm": { + "description": "Voc\u00ea est\u00e1 prestes a adicionar `{name}` do tipo `{type}` ao Home Assistant.\n\n** Para completar o processo, voc\u00ea pode ter que inserir v\u00e1rios c\u00f3digos PIN.**\n\nObserve que voc\u00ea *n\u00e3o* poder\u00e1 desligar sua TV Apple com esta integra\u00e7\u00e3o. Somente o reprodutor de m\u00eddia no Home Assistant ser\u00e1 desligado!", + "title": "Confirme a adi\u00e7\u00e3o da Apple TV" + }, + "pair_no_pin": { + "description": "O emparelhamento \u00e9 necess\u00e1rio para o servi\u00e7o `{protocol}`. Digite PIN {pin} no dispositivo para continuar.", + "title": "Emparelhamento" + }, "pair_with_pin": { "data": { "pin": "C\u00f3digo PIN" - } + }, + "description": "O emparelhamento \u00e9 necess\u00e1rio para o protocolo `{protocol}`. Digite o c\u00f3digo PIN exibido na tela. Os zeros principais ser\u00e3o omitidos, ou seja, digite 123 se o c\u00f3digo exibido for 0123.", + "title": "Emparelhamento" + }, + "password": { + "description": "Uma senha \u00e9 exigida por `{protocol}`. Isso ainda n\u00e3o est\u00e1 suportado, por favor desabilitar a senha para continuar.", + "title": "Senha requerida" + }, + "protocol_disabled": { + "description": "O emparelhamento \u00e9 necess\u00e1rio para ` {protocol} ` mas est\u00e1 desabilitado no dispositivo. Revise as poss\u00edveis restri\u00e7\u00f5es de acesso (por exemplo, permitir que todos os dispositivos da rede local se conectem) no dispositivo. \n\n Voc\u00ea pode continuar sem emparelhar este protocolo, mas algumas funcionalidades ser\u00e3o limitadas.", + "title": "N\u00e3o \u00e9 poss\u00edvel emparelhar" + }, + "reconfigure": { + "description": "Reconfigure este dispositivo para restaurar sua funcionalidade.", + "title": "Reconfigura\u00e7\u00e3o do dispositivo" + }, + "service_problem": { + "description": "Ocorreu um problema ao emparelhar o protocolo `{protocol}`. Ser\u00e1 ignorado.", + "title": "Falha ao adicionar servi\u00e7o" + }, + "user": { + "data": { + "device_input": "Dispositivo" + }, + "description": "Comece digitando o nome do dispositivo (por exemplo, Cozinha ou Quarto) ou o endere\u00e7o IP da Apple TV que voc\u00ea deseja adicionar. \n\n Se voc\u00ea n\u00e3o conseguir ver seu dispositivo ou tiver problemas, tente especificar o endere\u00e7o IP do dispositivo.", + "title": "Configurar uma nova Apple TV" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "start_off": "N\u00e3o ligue o dispositivo ao iniciar o Home Assistant" + }, + "description": "Definir as configura\u00e7\u00f5es gerais do dispositivo" } } - } + }, + "title": "Apple TV" } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/pt-BR.json b/homeassistant/components/arcam_fmj/translations/pt-BR.json index 58279f9bff9a15..8df3d821f934f2 100644 --- a/homeassistant/components/arcam_fmj/translations/pt-BR.json +++ b/homeassistant/components/arcam_fmj/translations/pt-BR.json @@ -5,12 +5,17 @@ "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", "cannot_connect": "Falha ao conectar" }, + "flow_title": "{host}", "step": { + "confirm": { + "description": "Deseja adicionar Arcam FMJ em `{host}` ao Home Assistant?" + }, "user": { "data": { "host": "Nome do host", "port": "Porta" - } + }, + "description": "Digite o nome do host ou o endere\u00e7o IP do dispositivo." } } }, diff --git a/homeassistant/components/aseko_pool_live/translations/pt-BR.json b/homeassistant/components/aseko_pool_live/translations/pt-BR.json index 67dcf497bc0c6d..bb8bb1f7819870 100644 --- a/homeassistant/components/aseko_pool_live/translations/pt-BR.json +++ b/homeassistant/components/aseko_pool_live/translations/pt-BR.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "email": "Email", "password": "Senha" } } diff --git a/homeassistant/components/asuswrt/translations/pt-BR.json b/homeassistant/components/asuswrt/translations/pt-BR.json index 07f99344a4a595..06982ade6227b2 100644 --- a/homeassistant/components/asuswrt/translations/pt-BR.json +++ b/homeassistant/components/asuswrt/translations/pt-BR.json @@ -6,17 +6,39 @@ "error": { "cannot_connect": "Falha ao conectar", "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido", + "pwd_and_ssh": "Forne\u00e7a apenas senha ou arquivo de chave SSH", + "pwd_or_ssh": "Forne\u00e7a a senha ou o arquivo de chave SSH", + "ssh_not_file": "Arquivo de chave SSH n\u00e3o encontrado", "unknown": "Erro inesperado" }, "step": { "user": { "data": { "host": "Nome do host", + "mode": "Modo", "name": "Nome", "password": "Senha", "port": "Porta", + "protocol": "Protocolo de comunica\u00e7\u00e3o a ser usado", + "ssh_key": "Caminho para seu arquivo de chave SSH (em vez de senha)", "username": "Usu\u00e1rio" - } + }, + "description": "Defina o par\u00e2metro necess\u00e1rio para se conectar ao seu roteador", + "title": "AsusWRT" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "consider_home": "Segundos para esperar antes de considerar um dispositivo ausente", + "dnsmasq": "A localiza\u00e7\u00e3o dos arquivos dnsmasq.leases no roteador", + "interface": "A interface da qual voc\u00ea deseja estat\u00edsticas (por exemplo, eth0,eth1 etc)", + "require_ip": "Os dispositivos devem ter IP (para o modo de ponto de acesso)", + "track_unknown": "Rastrear dispositivos desconhecidos/sem nome" + }, + "title": "Op\u00e7\u00f5es AsusWRT" } } } diff --git a/homeassistant/components/atag/translations/pt-BR.json b/homeassistant/components/atag/translations/pt-BR.json index 2d6380e59be604..1577ad4436ff50 100644 --- a/homeassistant/components/atag/translations/pt-BR.json +++ b/homeassistant/components/atag/translations/pt-BR.json @@ -4,14 +4,16 @@ "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "error": { - "cannot_connect": "Falha ao conectar" + "cannot_connect": "Falha ao conectar", + "unauthorized": "Emparelhamento negado, verifique o dispositivo para solicita\u00e7\u00e3o de autentica\u00e7\u00e3o" }, "step": { "user": { "data": { "host": "Nome do host", "port": "Porta" - } + }, + "title": "Conecte-se ao dispositivo" } } } diff --git a/homeassistant/components/august/translations/pt-BR.json b/homeassistant/components/august/translations/pt-BR.json index 61185dc14f795d..6582c5b60c4515 100644 --- a/homeassistant/components/august/translations/pt-BR.json +++ b/homeassistant/components/august/translations/pt-BR.json @@ -13,13 +13,18 @@ "reauth_validate": { "data": { "password": "Senha" - } + }, + "description": "Digite a senha para {username}.", + "title": "Reautenticar uma conta August" }, "user_validate": { "data": { + "login_method": "M\u00e9todo de login", "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "description": "Se o m\u00e9todo de login for 'e-mail', o nome de usu\u00e1rio ser\u00e1 o endere\u00e7o de e-mail. Se o m\u00e9todo de login for 'telefone', o nome de usu\u00e1rio ser\u00e1 o n\u00famero de telefone no formato '+NNNNNNNNN'.", + "title": "Configurar uma conta August" }, "validation": { "data": { diff --git a/homeassistant/components/aurora/translations/pt-BR.json b/homeassistant/components/aurora/translations/pt-BR.json index 7d9ce5c434dac4..dcaa594cd13140 100644 --- a/homeassistant/components/aurora/translations/pt-BR.json +++ b/homeassistant/components/aurora/translations/pt-BR.json @@ -12,5 +12,15 @@ } } } - } + }, + "options": { + "step": { + "init": { + "data": { + "threshold": "Limiar (%)" + } + } + } + }, + "title": "Sensor NOAA Aurora" } \ No newline at end of file diff --git a/homeassistant/components/aurora_abb_powerone/translations/pt-BR.json b/homeassistant/components/aurora_abb_powerone/translations/pt-BR.json index d81a4031129b0c..8fb79bcefa2150 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/pt-BR.json +++ b/homeassistant/components/aurora_abb_powerone/translations/pt-BR.json @@ -1,10 +1,23 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "no_serial_ports": "Nenhuma porta de comunica\u00e7\u00e3o encontrada. Precisa de um dispositivo RS485 v\u00e1lido para se comunicar." }, "error": { + "cannot_connect": "N\u00e3o \u00e9 poss\u00edvel conectar, verifique a porta serial, endere\u00e7o, conex\u00e3o el\u00e9trica e se o inversor est\u00e1 ligado (\u00e0 luz do dia)", + "cannot_open_serial_port": "N\u00e3o \u00e9 poss\u00edvel abrir a porta serial, verifique e tente novamente", + "invalid_serial_port": "A porta serial n\u00e3o \u00e9 um dispositivo v\u00e1lido ou n\u00e3o p\u00f4de ser aberta", "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "address": "Endere\u00e7o inversor", + "port": "Porta adaptadora RS485 ou USB-RS485" + }, + "description": "O inversor deve ser conectado atrav\u00e9s de um adaptador RS485, selecione a porta serial e o endere\u00e7o do inversor conforme configurado no painel LCD" + } } } } \ No newline at end of file diff --git a/homeassistant/components/aussie_broadband/translations/nl.json b/homeassistant/components/aussie_broadband/translations/nl.json index f4a924d02dbe32..f895082e233a6a 100644 --- a/homeassistant/components/aussie_broadband/translations/nl.json +++ b/homeassistant/components/aussie_broadband/translations/nl.json @@ -1,17 +1,22 @@ { "config": { "abort": { - "no_services_found": "Er zijn geen services gevonden voor dit account" + "already_configured": "Account is al geconfigureerd", + "no_services_found": "Er zijn geen services gevonden voor dit account", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { - "cannot_connect": "Kan geen verbinding maken" + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" }, "step": { "reauth": { "data": { "password": "Wachtwoord" }, - "description": "Update wachtwoord voor {username}" + "description": "Update wachtwoord voor {username}", + "title": "Verifieer de integratie opnieuw" }, "service": { "data": { @@ -20,7 +25,8 @@ }, "user": { "data": { - "password": "Wachtwoord" + "password": "Wachtwoord", + "username": "Gebruikersnaam" } } } @@ -28,6 +34,7 @@ "options": { "abort": { "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, "step": { diff --git a/homeassistant/components/aussie_broadband/translations/pt-BR.json b/homeassistant/components/aussie_broadband/translations/pt-BR.json index c2b3b7e15689f3..9dbd275d2bcc03 100644 --- a/homeassistant/components/aussie_broadband/translations/pt-BR.json +++ b/homeassistant/components/aussie_broadband/translations/pt-BR.json @@ -15,7 +15,7 @@ "data": { "password": "Senha" }, - "description": "Atualizar senha para {nome de usu\u00e1rio}", + "description": "Atualizar senha para {username}", "title": "Reautenticar Integra\u00e7\u00e3o" }, "service": { diff --git a/homeassistant/components/automation/translations/pt-BR.json b/homeassistant/components/automation/translations/pt-BR.json index 30c78d0a187b69..447658433e5c7b 100644 --- a/homeassistant/components/automation/translations/pt-BR.json +++ b/homeassistant/components/automation/translations/pt-BR.json @@ -2,7 +2,7 @@ "state": { "_": { "off": "Desligado", - "on": "Ativa" + "on": "Ligado" } }, "title": "Automa\u00e7\u00e3o" diff --git a/homeassistant/components/awair/translations/el.json b/homeassistant/components/awair/translations/el.json index 8225c4088f7710..3f3bbcac514e29 100644 --- a/homeassistant/components/awair/translations/el.json +++ b/homeassistant/components/awair/translations/el.json @@ -1,6 +1,9 @@ { "config": { "step": { + "reauth": { + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c0\u03c1\u03bf\u03b3\u03c1\u03b1\u03bc\u03bc\u03b1\u03c4\u03b9\u03c3\u03c4\u03ae Awair." + }, "user": { "description": "\u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03b5\u03af\u03c4\u03b5 \u03b3\u03b9\u03b1 \u03ad\u03bd\u03b1 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c0\u03c1\u03bf\u03b3\u03c1\u03b1\u03bc\u03bc\u03b1\u03c4\u03b9\u03c3\u03c4\u03ae Awair \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7: https://developer.getawair.com/onboard/login" } diff --git a/homeassistant/components/awair/translations/pt-BR.json b/homeassistant/components/awair/translations/pt-BR.json index ad86023f8189c0..635a7373b7533b 100644 --- a/homeassistant/components/awair/translations/pt-BR.json +++ b/homeassistant/components/awair/translations/pt-BR.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "A conta j\u00e1 foi configurada", - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "no_devices_found": "Nenhum dispositivo encontrado na rede", "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { @@ -12,13 +12,17 @@ "step": { "reauth": { "data": { - "access_token": "Token de acesso" - } + "access_token": "Token de acesso", + "email": "Email" + }, + "description": "Insira novamente seu token de acesso de desenvolvedor Awair." }, "user": { "data": { - "access_token": "Token de acesso" - } + "access_token": "Token de acesso", + "email": "Email" + }, + "description": "Voc\u00ea deve se registrar para um token de acesso de desenvolvedor Awair em: https://developer.getawair.com/onboard/login" } } } diff --git a/homeassistant/components/axis/translations/pt-BR.json b/homeassistant/components/axis/translations/pt-BR.json index 7c25606016e4b4..3208a509ec4e4b 100644 --- a/homeassistant/components/axis/translations/pt-BR.json +++ b/homeassistant/components/axis/translations/pt-BR.json @@ -23,5 +23,15 @@ "title": "Configurar o dispositivo Axis" } } + }, + "options": { + "step": { + "configure_stream": { + "data": { + "stream_profile": "Selecione o perfil de stream a ser usado" + }, + "title": "Op\u00e7\u00f5es de transmiss\u00e3o de v\u00eddeo do dispositivo Axis" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/azure_devops/translations/pt-BR.json b/homeassistant/components/azure_devops/translations/pt-BR.json index 859c87db3fb5c1..d8fec75b0f49da 100644 --- a/homeassistant/components/azure_devops/translations/pt-BR.json +++ b/homeassistant/components/azure_devops/translations/pt-BR.json @@ -9,6 +9,7 @@ "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "project_error": "N\u00e3o foi poss\u00edvel obter informa\u00e7\u00f5es do projeto." }, + "flow_title": "{project_url}", "step": { "reauth": { "data": { @@ -22,7 +23,9 @@ "organization": "Organiza\u00e7\u00e3o", "personal_access_token": "Token de acesso pessoal (PAT)", "project": "Projeto" - } + }, + "description": "Configure uma inst\u00e2ncia do Azure DevOps para acessar seu projeto. Um token de acesso pessoal s\u00f3 \u00e9 necess\u00e1rio para um projeto privado.", + "title": "Adicionar o projeto 'Azure DevOps'" } } } diff --git a/homeassistant/components/azure_event_hub/translations/pt-BR.json b/homeassistant/components/azure_event_hub/translations/pt-BR.json index 65502dcaa23a91..853a553cd4281c 100644 --- a/homeassistant/components/azure_event_hub/translations/pt-BR.json +++ b/homeassistant/components/azure_event_hub/translations/pt-BR.json @@ -2,11 +2,48 @@ "config": { "abort": { "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado", - "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + "cannot_connect": "Falha na conex\u00e3o com as credenciais do configuration.yaml. Remova do yaml e use o fluxo de configura\u00e7\u00e3o.", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", + "unknown": "A conex\u00e3o com as credenciais do configuration.yaml falhou com um erro desconhecido. Remova do yaml e use o fluxo de configura\u00e7\u00e3o." }, "error": { "cannot_connect": "Falha ao conectar", "unknown": "Erro inesperado" + }, + "step": { + "conn_string": { + "data": { + "event_hub_connection_string": "Cadeia de Conex\u00e3o do Hub de Eventos" + }, + "description": "Insira a string de conex\u00e3o para: {event_hub_instance_name}", + "title": "M\u00e9todo string de conex\u00e3o" + }, + "sas": { + "data": { + "event_hub_namespace": "Namespace do Hub de Eventos", + "event_hub_sas_key": "Chave SAS do Hub de Eventos", + "event_hub_sas_policy": "Pol\u00edtica SAS do Hub de Eventos" + }, + "description": "Insira as credenciais SAS (assinatura de acesso compartilhado) para: {event_hub_instance_name}", + "title": "M\u00e9todo de credenciais SAS" + }, + "user": { + "data": { + "event_hub_instance_name": "Nome da inst\u00e2ncia do hub de eventos", + "use_connection_string": "Use string de conex\u00e3o" + }, + "title": "Configure sua integra\u00e7\u00e3o do Hub de Eventos do Azure" + } + } + }, + "options": { + "step": { + "options": { + "data": { + "send_interval": "Intervalo entre o envio de comandos para o hub." + }, + "title": "Op\u00e7\u00f5es para o Hub de Eventos do Azure." + } } } } \ No newline at end of file diff --git a/homeassistant/components/balboa/translations/pt-BR.json b/homeassistant/components/balboa/translations/pt-BR.json index ff6ede166a9dd6..cd16bb3cec959f 100644 --- a/homeassistant/components/balboa/translations/pt-BR.json +++ b/homeassistant/components/balboa/translations/pt-BR.json @@ -11,6 +11,16 @@ "user": { "data": { "host": "Nome do host" + }, + "title": "Conecte-se ao dispositivo Wi-Fi Balboa" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "sync_time": "Mantenha o hor\u00e1rio do seu cliente Balboa Spa sincronizado com o Home Assistant" } } } diff --git a/homeassistant/components/binary_sensor/translations/el.json b/homeassistant/components/binary_sensor/translations/el.json index 03a750a186d532..8ad46475a7148e 100644 --- a/homeassistant/components/binary_sensor/translations/el.json +++ b/homeassistant/components/binary_sensor/translations/el.json @@ -49,6 +49,14 @@ "light": "{entity_name} \u03ac\u03c1\u03c7\u03b9\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03c6\u03c9\u03c2", "locked": "{entity_name} \u03ba\u03bb\u03b5\u03b9\u03b4\u03ce\u03b8\u03b7\u03ba\u03b5", "moist": "{entity_name} \u03ad\u03b3\u03b9\u03bd\u03b5 \u03c5\u03b3\u03c1\u03cc", + "motion": "{entity_name} \u03ac\u03c1\u03c7\u03b9\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03ba\u03af\u03bd\u03b7\u03c3\u03b7", + "moving": "{entity_name} \u03ac\u03c1\u03c7\u03b9\u03c3\u03b5 \u03bd\u03b1 \u03ba\u03b9\u03bd\u03b5\u03af\u03c4\u03b1\u03b9", + "no_gas": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03b1\u03ad\u03c1\u03b9\u03bf", + "no_light": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03c6\u03c9\u03c2", + "no_motion": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03ba\u03af\u03bd\u03b7\u03c3\u03b7", + "no_problem": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03b6\u03b5\u03b9 \u03c0\u03c1\u03cc\u03b2\u03bb\u03b7\u03bc\u03b1", + "no_smoke": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03ba\u03b1\u03c0\u03bd\u03cc", + "no_sound": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03ae\u03c7\u03bf", "no_update": "{entity_name} \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5", "not_opened": "{entity_name} \u03ad\u03ba\u03bb\u03b5\u03b9\u03c3\u03b5", "not_tampered": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03b6\u03b5\u03b9 \u03c0\u03b1\u03c1\u03b1\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", diff --git a/homeassistant/components/binary_sensor/translations/pt-BR.json b/homeassistant/components/binary_sensor/translations/pt-BR.json index 4ca2f04550efcf..2e052970d63fba 100644 --- a/homeassistant/components/binary_sensor/translations/pt-BR.json +++ b/homeassistant/components/binary_sensor/translations/pt-BR.json @@ -19,6 +19,7 @@ "is_no_problem": "{entity_name} n\u00e3o est\u00e1 detectando problema", "is_no_smoke": "{entity_name} n\u00e3o est\u00e1 detectando fuma\u00e7a", "is_no_sound": "{entity_name} n\u00e3o est\u00e1 detectando som", + "is_no_update": "{entity_name} est\u00e1 atualizado", "is_no_vibration": "{entity_name} n\u00e3o est\u00e1 detectando vibra\u00e7\u00e3o", "is_not_bat_low": "{entity_name} bateria normal", "is_not_cold": "{entity_name} n\u00e3o \u00e9 frio", @@ -32,6 +33,8 @@ "is_not_plugged_in": "{entity_name} est\u00e1 desconectado", "is_not_powered": "{entity_name} n\u00e3o \u00e9 alimentado", "is_not_present": "{entity_name} n\u00e3o est\u00e1 presente", + "is_not_running": "{entity_name} n\u00e3o est\u00e1 em execu\u00e7\u00e3o", + "is_not_tampered": "{entity_name} n\u00e3o est\u00e1 detectando adultera\u00e7\u00e3o", "is_not_unsafe": "{entity_name} \u00e9 seguro", "is_occupied": "{entity_name} est\u00e1 ocupado", "is_off": "{entity_name} est\u00e1 desligado", @@ -41,9 +44,12 @@ "is_powered": "{entity_name} \u00e9 alimentado", "is_present": "{entity_name} est\u00e1 presente", "is_problem": "{entity_name} est\u00e1 detectando problema", + "is_running": "{entity_name} est\u00e1 em execu\u00e7\u00e3o", "is_smoke": "{entity_name} est\u00e1 detectando fuma\u00e7a", "is_sound": "{entity_name} est\u00e1 detectando som", + "is_tampered": "{entity_name} est\u00e1 detectando adultera\u00e7\u00e3o", "is_unsafe": "{entity_name} \u00e9 inseguro", + "is_update": "{entity_name} tem uma atualiza\u00e7\u00e3o dispon\u00edvel", "is_vibration": "{entity_name} est\u00e1 detectando vibra\u00e7\u00e3o" }, "trigger_type": { @@ -53,8 +59,11 @@ "connected": "{entity_name} conectado", "gas": "{entity_name} come\u00e7ou a detectar g\u00e1s", "hot": "{entity_name} tornou-se quente", + "is_not_tampered": "{entity_name} parar de detectar adultera\u00e7\u00e3o", + "is_tampered": "{entity_name} come\u00e7ar a detectar adultera\u00e7\u00e3o", "light": "{entity_name} come\u00e7ou a detectar luz", "locked": "{entity_name} bloqueado", + "moist": "{entity_name} ficar \u00famido", "motion": "{entity_name} come\u00e7ou a detectar movimento", "moving": "{entity_name} come\u00e7ou a se mover", "no_co": "{entity_name} parou de detectar mon\u00f3xido de carbono", @@ -64,6 +73,7 @@ "no_problem": "{entity_name} parou de detectar problema", "no_smoke": "{entity_name} parou de detectar fuma\u00e7a", "no_sound": "{entity_name} parou de detectar som", + "no_update": "{entity_name} for atualizado", "no_vibration": "{entity_name} parou de detectar vibra\u00e7\u00e3o", "not_bat_low": "{entity_name} bateria normal", "not_cold": "{entity_name} n\u00e3o frio", @@ -73,9 +83,11 @@ "not_moist": "{entity_name} secou", "not_moving": "{entity_name} parado", "not_occupied": "{entity_name} desocupado", + "not_opened": "{entity_name} for fechado", "not_plugged_in": "{entity_name} desconectado", "not_powered": "{entity_name} sem alimenta\u00e7\u00e3o", "not_present": "{entity_name} n\u00e3o est\u00e1 presente", + "not_running": "{entity_name} n\u00e3o estiver mais em execu\u00e7\u00e3o", "not_tampered": "{entity_name} parou de detectar adultera\u00e7\u00e3o", "not_unsafe": "{entity_name} seguro", "occupied": "{entity_name} ocupado", @@ -84,12 +96,14 @@ "powered": "{entity_name} alimentado", "present": "{entity_name} presente", "problem": "{entity_name} come\u00e7ou a detectar problema", + "running": "{entity_name} come\u00e7ar a correr", "smoke": "{entity_name} come\u00e7ou a detectar fuma\u00e7a", "sound": "{entity_name} come\u00e7ou a detectar som", "tampered": "{entity_name} come\u00e7ou a detectar adultera\u00e7\u00e3o", "turned_off": "{entity_name} desligado", "turned_on": "{entity_name} ligado", "unsafe": "{entity_name} tornou-se inseguro", + "update": "{entity_name} tiver uma atualiza\u00e7\u00e3o dispon\u00edvel", "vibration": "{entity_name} come\u00e7ou a detectar vibra\u00e7\u00e3o" } }, @@ -141,7 +155,7 @@ "on": "Aberto" }, "gas": { - "off": "Limpo", + "off": "Normal", "on": "Detectado" }, "heat": { @@ -154,14 +168,14 @@ }, "lock": { "off": "Trancado", - "on": "Desbloqueado" + "on": "Destrancado" }, "moisture": { "off": "Seco", "on": "Molhado" }, "motion": { - "off": "Desligado", + "off": "Sem movimento", "on": "Detectado" }, "moving": { diff --git a/homeassistant/components/binary_sensor/translations/uk.json b/homeassistant/components/binary_sensor/translations/uk.json index 0f8d92749c4cb1..c423002359ee40 100644 --- a/homeassistant/components/binary_sensor/translations/uk.json +++ b/homeassistant/components/binary_sensor/translations/uk.json @@ -187,5 +187,5 @@ "on": "\u0412\u0456\u0434\u0447\u0438\u043d\u0435\u043d\u043e" } }, - "title": "\u0411\u0456\u043d\u0430\u0440\u043d\u0438\u0439 \u0434\u0430\u0442\u0447\u0438\u043a" + "title": "\u0411\u0456\u043d\u0430\u0440\u043d\u0438\u0439 \u0441\u0435\u043d\u0441\u043e\u0440" } \ No newline at end of file diff --git a/homeassistant/components/blebox/translations/pt-BR.json b/homeassistant/components/blebox/translations/pt-BR.json index 3bc015b6b55533..5a672767617020 100644 --- a/homeassistant/components/blebox/translations/pt-BR.json +++ b/homeassistant/components/blebox/translations/pt-BR.json @@ -1,18 +1,23 @@ { "config": { "abort": { + "address_already_configured": "Um dispositivo BleBox j\u00e1 est\u00e1 configurado em {address}.", "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "error": { "cannot_connect": "Falha ao conectar", - "unknown": "Erro inesperado" + "unknown": "Erro inesperado", + "unsupported_version": "O dispositivo BleBox possui firmware desatualizado. Por favor, atualize-o primeiro." }, + "flow_title": "{name} ({host})", "step": { "user": { "data": { "host": "Endere\u00e7o IP", "port": "Porta" - } + }, + "description": "Configure seu BleBox para integrar com o Home Assistant.", + "title": "Configure seu dispositivo BleBox" } } } diff --git a/homeassistant/components/blink/translations/pt-BR.json b/homeassistant/components/blink/translations/pt-BR.json index 5624d3765d3c29..440558cddd7b46 100644 --- a/homeassistant/components/blink/translations/pt-BR.json +++ b/homeassistant/components/blink/translations/pt-BR.json @@ -14,7 +14,7 @@ "data": { "2fa": "C\u00f3digo de dois fatores" }, - "description": "Digite o pin enviado para o seu e-mail. Se o e-mail n\u00e3o contiver um pin, deixe em branco", + "description": "Digite o PIN enviado para o seu e-mail", "title": "Autentica\u00e7\u00e3o de dois fatores" }, "user": { @@ -25,5 +25,16 @@ "title": "Entrar com a conta Blink" } } + }, + "options": { + "step": { + "simple_options": { + "data": { + "scan_interval": "Intervalo de varredura (segundos)" + }, + "description": "Configurar integra\u00e7\u00e3o Blink", + "title": "Op\u00e7\u00f5es Blink" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/pt-BR.json b/homeassistant/components/bmw_connected_drive/translations/pt-BR.json index 86cf9781d3a90c..b4b82c1fe1812c 100644 --- a/homeassistant/components/bmw_connected_drive/translations/pt-BR.json +++ b/homeassistant/components/bmw_connected_drive/translations/pt-BR.json @@ -11,9 +11,20 @@ "user": { "data": { "password": "Senha", + "region": "Regi\u00e3o do ConnectedDrive", "username": "Usu\u00e1rio" } } } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "Somente leitura (somente sensores e notificar, sem execu\u00e7\u00e3o de servi\u00e7os, sem bloqueio)", + "use_location": "Use a localiza\u00e7\u00e3o do Home Assistant para pesquisas de localiza\u00e7\u00e3o de carros (necess\u00e1rio para ve\u00edculos n\u00e3o i3/i8 produzidos antes de 7/2014)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/bond/translations/pt-BR.json b/homeassistant/components/bond/translations/pt-BR.json index 2e596948dc22d3..8300c0db0b372f 100644 --- a/homeassistant/components/bond/translations/pt-BR.json +++ b/homeassistant/components/bond/translations/pt-BR.json @@ -6,14 +6,16 @@ "error": { "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "old_firmware": "Firmware antigo n\u00e3o suportado no dispositivo Bond - atualize antes de continuar", "unknown": "Erro inesperado" }, - "flow_title": "Bond: {bond_id} ({host})", + "flow_title": "{name} ({host})", "step": { "confirm": { "data": { "access_token": "Token de acesso" - } + }, + "description": "Deseja configurar {name}?" }, "user": { "data": { diff --git a/homeassistant/components/bosch_shc/translations/pt-BR.json b/homeassistant/components/bosch_shc/translations/pt-BR.json index 4916cbdfe49adc..f8a74157ab358e 100644 --- a/homeassistant/components/bosch_shc/translations/pt-BR.json +++ b/homeassistant/components/bosch_shc/translations/pt-BR.json @@ -6,17 +6,33 @@ }, "error": { "cannot_connect": "Falha ao conectar", - "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "pairing_failed": "Falha no emparelhamento; verifique se o Bosch Smart Home Controller est\u00e1 no modo de emparelhamento (LED piscando) e se sua senha est\u00e1 correta.", + "session_error": "Erro de sess\u00e3o: API retorna resultado n\u00e3o OK.", + "unknown": "Erro inesperado" }, + "flow_title": "Bosch SHC: {name}", "step": { + "confirm_discovery": { + "description": "Pressione o bot\u00e3o frontal do Bosch Smart Home Controller at\u00e9 que o LED comece a piscar.\n Pronto para continuar a configurar {model} @ {host} com o Home Assistant?" + }, + "credentials": { + "data": { + "password": "Senha do Smart Home Controller" + } + }, "reauth_confirm": { + "description": "A integra\u00e7\u00e3o bosch_shc precisa re-autenticar sua conta", "title": "Reautenticar Integra\u00e7\u00e3o" }, "user": { "data": { "host": "Nome do host" - } + }, + "description": "Configure seu Bosch Smart Home Controller para permitir monitoramento e controle com o Home Assistant.", + "title": "Par\u00e2metros de autentica\u00e7\u00e3o SHC" } } - } + }, + "title": "Bosch SHC" } \ No newline at end of file diff --git a/homeassistant/components/braviatv/translations/pt-BR.json b/homeassistant/components/braviatv/translations/pt-BR.json index 7bc600a3644dfa..bd6d47af018d60 100644 --- a/homeassistant/components/braviatv/translations/pt-BR.json +++ b/homeassistant/components/braviatv/translations/pt-BR.json @@ -1,17 +1,21 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "no_ip_control": "O Controle de IP est\u00e1 desativado em sua TV ou a TV n\u00e3o \u00e9 compat\u00edvel." }, "error": { "cannot_connect": "Falha ao conectar", - "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido" + "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido", + "unsupported_model": "Seu modelo de TV n\u00e3o \u00e9 suportado." }, "step": { "authorize": { "data": { "pin": "C\u00f3digo PIN" - } + }, + "description": "Digite o c\u00f3digo PIN mostrado na TV Sony Bravia.\n\nSe o c\u00f3digo PIN n\u00e3o for mostrado, voc\u00ea deve cancelar o registro do Home Assistant na sua TV, v\u00e1 para: Configura\u00e7\u00f5es -> Rede -> Configura\u00e7\u00f5es do dispositivo remoto -> Cancelar registro do dispositivo remoto.", + "title": "Autorizar a TV Sony Bravia" }, "user": { "data": { diff --git a/homeassistant/components/broadlink/translations/pt-BR.json b/homeassistant/components/broadlink/translations/pt-BR.json index 0cafe568193f79..e5a372176aa12c 100644 --- a/homeassistant/components/broadlink/translations/pt-BR.json +++ b/homeassistant/components/broadlink/translations/pt-BR.json @@ -13,7 +13,7 @@ "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido", "unknown": "Erro inesperado" }, - "flow_title": "{nome} ({modelo} em {host})", + "flow_title": "{name} ({model} at {host})", "step": { "auth": { "title": "Autenticar no dispositivo" @@ -25,14 +25,14 @@ "title": "Escolha um nome para o dispositivo" }, "reset": { - "description": "{nome} ({modelo} em {host}) est\u00e1 bloqueado. Voc\u00ea precisa desbloquear o dispositivo para autenticar e completar a configura\u00e7\u00e3o. Instru\u00e7\u00f5es:\n1. Abra o aplicativo Broadlink.\n2. Clique no dispositivo.\n3. Clique em '...' no canto superior direito.\n4. Role at\u00e9 a parte inferior da p\u00e1gina.\n5. Desabilite o bloqueio.", + "description": "{name} ({model} em {host}) est\u00e1 bloqueado. Voc\u00ea precisa desbloquear o dispositivo para autenticar e completar a configura\u00e7\u00e3o. Instru\u00e7\u00f5es:\n1. Abra o aplicativo Broadlink.\n2. Clique no dispositivo.\n3. Clique em '...' no canto superior direito.\n4. Role at\u00e9 a parte inferior da p\u00e1gina.\n5. Desabilite o bloqueio.", "title": "Desbloqueie o dispositivo" }, "unlock": { "data": { "unlock": "Sim, fa\u00e7a isso." }, - "description": "{nome} ({modelo} em {host}) est\u00e1 bloqueado. Isso pode levar a problemas de autentica\u00e7\u00e3o no Home Assistant. Gostaria de desbloque\u00e1-lo?", + "description": "{name} ({model} em {host}) est\u00e1 bloqueado. Isso pode levar a problemas de autentica\u00e7\u00e3o no Home Assistant. Gostaria de desbloque\u00e1-lo?", "title": "Desbloquear o dispositivo (opcional)" }, "user": { diff --git a/homeassistant/components/brother/translations/pt-BR.json b/homeassistant/components/brother/translations/pt-BR.json index 0306932f1467ed..33113d12881a39 100644 --- a/homeassistant/components/brother/translations/pt-BR.json +++ b/homeassistant/components/brother/translations/pt-BR.json @@ -9,6 +9,7 @@ "snmp_error": "Servidor SNMP desligado ou impressora n\u00e3o suportada.", "wrong_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido." }, + "flow_title": "{model} {serial_number}", "step": { "user": { "data": { @@ -16,6 +17,13 @@ "type": "Tipo de impressora" }, "description": "Configure a integra\u00e7\u00e3o da impressora Brother. Se voc\u00ea tiver problemas com a configura\u00e7\u00e3o, acesse: https://www.home-assistant.io/integrations/brother" + }, + "zeroconf_confirm": { + "data": { + "type": "Tipo de impressora" + }, + "description": "Deseja adicionar a impressora Brother {model} com n\u00famero de s\u00e9rie ` {serial_number} ` ao Home Assistant?", + "title": "Impressora Brother descoberta" } } } diff --git a/homeassistant/components/bsblan/translations/pt-BR.json b/homeassistant/components/bsblan/translations/pt-BR.json index 8b09d9885c2baa..789b09ebefce2f 100644 --- a/homeassistant/components/bsblan/translations/pt-BR.json +++ b/homeassistant/components/bsblan/translations/pt-BR.json @@ -7,10 +7,12 @@ "error": { "cannot_connect": "Falha ao conectar" }, + "flow_title": "{name}", "step": { "user": { "data": { "host": "Nome do host", + "passkey": "Chave da senha", "password": "Senha", "port": "Porta", "username": "Usu\u00e1rio" diff --git a/homeassistant/components/buienradar/translations/pt-BR.json b/homeassistant/components/buienradar/translations/pt-BR.json index 1ab872ffb71d0b..9ce1013644ffc3 100644 --- a/homeassistant/components/buienradar/translations/pt-BR.json +++ b/homeassistant/components/buienradar/translations/pt-BR.json @@ -14,5 +14,16 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "country_code": "C\u00f3digo do pa\u00eds para exibir as imagens da c\u00e2mera.", + "delta": "Intervalo de tempo em segundos entre as atualiza\u00e7\u00f5es da imagem da c\u00e2mera", + "timeframe": "Minutos para antecipar a previs\u00e3o de precipita\u00e7\u00e3o" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/calendar/translations/pt-BR.json b/homeassistant/components/calendar/translations/pt-BR.json index fca0b1a103bd96..0d47e41440b504 100644 --- a/homeassistant/components/calendar/translations/pt-BR.json +++ b/homeassistant/components/calendar/translations/pt-BR.json @@ -1,8 +1,8 @@ { "state": { "_": { - "off": "Inativo", - "on": "Ativo" + "off": "Desligado", + "on": "Ligado" } }, "title": "Calend\u00e1rio" diff --git a/homeassistant/components/canary/translations/pt-BR.json b/homeassistant/components/canary/translations/pt-BR.json index 25ded1810fecc0..3f3d51547574f3 100644 --- a/homeassistant/components/canary/translations/pt-BR.json +++ b/homeassistant/components/canary/translations/pt-BR.json @@ -7,11 +7,23 @@ "error": { "cannot_connect": "Falha ao conectar" }, + "flow_title": "{name}", "step": { "user": { "data": { "password": "Senha", "username": "Usu\u00e1rio" + }, + "title": "Conecte-se ao Canary" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ffmpeg_arguments": "Argumentos passados para ffmpeg para c\u00e2meras", + "timeout": "Tempo limite da solicita\u00e7\u00e3o (segundos)" } } } diff --git a/homeassistant/components/canary/translations/uk.json b/homeassistant/components/canary/translations/uk.json index 74327f3ebd672b..6664c756e16b8e 100644 --- a/homeassistant/components/canary/translations/uk.json +++ b/homeassistant/components/canary/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e.", + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f.", "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" }, "error": { diff --git a/homeassistant/components/cast/translations/pt-BR.json b/homeassistant/components/cast/translations/pt-BR.json index 369064ba6cb5e1..708a5072254339 100644 --- a/homeassistant/components/cast/translations/pt-BR.json +++ b/homeassistant/components/cast/translations/pt-BR.json @@ -3,10 +3,42 @@ "abort": { "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, + "error": { + "invalid_known_hosts": "Os hosts conhecidos devem ser uma lista de hosts separados por v\u00edrgulas." + }, "step": { + "config": { + "data": { + "known_hosts": "Hosts conhecidos" + }, + "description": "Hosts conhecidos - Uma lista separada por v\u00edrgulas de nomes de host ou endere\u00e7os IP de dispositivos de transmiss\u00e3o, use se a descoberta de mDNS n\u00e3o estiver funcionando.", + "title": "Configura\u00e7\u00e3o do Google Cast" + }, "confirm": { "description": "Deseja iniciar a configura\u00e7\u00e3o?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "Os hosts conhecidos devem ser uma lista de hosts separados por v\u00edrgulas." + }, + "step": { + "advanced_options": { + "data": { + "ignore_cec": "Ignorar CEC", + "uuid": "UUIDs permitidos" + }, + "description": "UUIDs permitidos - uma lista separada por v\u00edrgulas de UUIDs de dispositivos Cast para adicionar ao Home Assistant. Use somente se n\u00e3o quiser adicionar todos os dispositivos de transmiss\u00e3o dispon\u00edveis.\n Ignore CEC - Uma lista separada por v\u00edrgulas de Chromecasts que devem ignorar os dados CEC para determinar a entrada ativa. Isso ser\u00e1 passado para pychromecast.IGNORE_CEC.", + "title": "Configura\u00e7\u00e3o avan\u00e7ada do Google Cast" + }, + "basic_options": { + "data": { + "known_hosts": "Anfitri\u00f5es conhecidos" + }, + "description": "Hosts conhecidos - Uma lista separada por v\u00edrgulas de nomes de host ou endere\u00e7os IP de dispositivos de transmiss\u00e3o, use se a descoberta de mDNS n\u00e3o estiver funcionando.", + "title": "Configura\u00e7\u00e3o do Google Cast" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/uk.json b/homeassistant/components/cast/translations/uk.json index 5f8d69f5f29b89..5ee7dbfde346d7 100644 --- a/homeassistant/components/cast/translations/uk.json +++ b/homeassistant/components/cast/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "step": { "confirm": { diff --git a/homeassistant/components/cert_expiry/translations/pt-BR.json b/homeassistant/components/cert_expiry/translations/pt-BR.json index db1fff5cd048f3..6e31e42ed4957e 100644 --- a/homeassistant/components/cert_expiry/translations/pt-BR.json +++ b/homeassistant/components/cert_expiry/translations/pt-BR.json @@ -1,9 +1,11 @@ { "config": { "abort": { - "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado", + "import_failed": "Falha na importa\u00e7\u00e3o da configura\u00e7\u00e3o" }, "error": { + "connection_refused": "Conex\u00e3o recusada ao se conectar ao host", "connection_timeout": "Tempo limite ao conectar-se a este host", "resolve_failed": "Este host n\u00e3o pode ser resolvido" }, diff --git a/homeassistant/components/climacell/translations/pt-BR.json b/homeassistant/components/climacell/translations/pt-BR.json index 687bfdf71f933a..54de15d1f7fd00 100644 --- a/homeassistant/components/climacell/translations/pt-BR.json +++ b/homeassistant/components/climacell/translations/pt-BR.json @@ -3,12 +3,14 @@ "error": { "cannot_connect": "Falha ao conectar", "invalid_api_key": "Chave de API inv\u00e1lida", + "rate_limited": "Taxa atualmente limitada, tente novamente mais tarde.", "unknown": "Erro inesperado" }, "step": { "user": { "data": { "api_key": "Chave da API", + "api_version": "Vers\u00e3o da API", "latitude": "Latitude", "longitude": "Longitude", "name": "Nome" @@ -16,5 +18,17 @@ "description": "Se Latitude e Longitude n\u00e3o forem fornecidos, os valores padr\u00f5es na configura\u00e7\u00e3o do Home Assistant ser\u00e3o usados. Uma entidade ser\u00e1 criada para cada tipo de previs\u00e3o, mas apenas as selecionadas ser\u00e3o habilitadas por padr\u00e3o." } } - } + }, + "options": { + "step": { + "init": { + "data": { + "timestep": "M\u00ednimo entre previs\u00f5es NowCast" + }, + "description": "Se voc\u00ea optar por ativar a entidade de previs\u00e3o `nowcast`, poder\u00e1 configurar o n\u00famero de minutos entre cada previs\u00e3o. O n\u00famero de previs\u00f5es fornecidas depende do n\u00famero de minutos escolhidos entre as previs\u00f5es.", + "title": "Atualizar as op\u00e7\u00f5es do ClimaCell" + } + } + }, + "title": "ClimaCell" } \ No newline at end of file diff --git a/homeassistant/components/climate/translations/pt-BR.json b/homeassistant/components/climate/translations/pt-BR.json index e920caf2a870fe..fc745aaef39623 100644 --- a/homeassistant/components/climate/translations/pt-BR.json +++ b/homeassistant/components/climate/translations/pt-BR.json @@ -1,10 +1,25 @@ { + "device_automation": { + "action_type": { + "set_hvac_mode": "Alterar o modo HVAC em {entity_name}", + "set_preset_mode": "Alterar predefini\u00e7\u00e3o em {entity_name}" + }, + "condition_type": { + "is_hvac_mode": "{entity_name} est\u00e1 definido para um modo HVAC espec\u00edfico", + "is_preset_mode": "{entity_name} est\u00e1 definido para um modo predefinido espec\u00edfico" + }, + "trigger_type": { + "current_humidity_changed": "{entity_name} umidade medida alterada", + "current_temperature_changed": "{entity_name} temperatura medida alterada", + "hvac_mode_changed": "{entity_name} modo HVAC alterado" + } + }, "state": { "_": { "auto": "Autom\u00e1tico", "cool": "Frio", "dry": "Seco", - "fan_only": "Apenas ventilador", + "fan_only": "Apenas ventilar", "heat": "Quente", "heat_cool": "Quente/Frio", "off": "Desligado" diff --git a/homeassistant/components/cloud/translations/pt-BR.json b/homeassistant/components/cloud/translations/pt-BR.json new file mode 100644 index 00000000000000..7e9a1f71c0659f --- /dev/null +++ b/homeassistant/components/cloud/translations/pt-BR.json @@ -0,0 +1,17 @@ +{ + "system_health": { + "info": { + "alexa_enabled": "Alexa habilitada", + "can_reach_cert_server": "Alcance o servidor de certificados", + "can_reach_cloud": "Alcance a nuvem do Home Assistant", + "can_reach_cloud_auth": "Alcance o servidor de autentica\u00e7\u00e3o", + "google_enabled": "Google ativado", + "logged_in": "Logado", + "relayer_connected": "Relayer Conectado", + "remote_connected": "Conectado remotamente", + "remote_enabled": "Conex\u00e3o remota habilitada", + "remote_server": "Servidor remoto", + "subscription_expiration": "Expira\u00e7\u00e3o da assinatura" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloudflare/translations/pt-BR.json b/homeassistant/components/cloudflare/translations/pt-BR.json index 8a763643004681..c591abe2b3be17 100644 --- a/homeassistant/components/cloudflare/translations/pt-BR.json +++ b/homeassistant/components/cloudflare/translations/pt-BR.json @@ -7,18 +7,35 @@ }, "error": { "cannot_connect": "Falha ao conectar", - "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "invalid_zone": "Zona inv\u00e1lida" }, + "flow_title": "{name}", "step": { "reauth_confirm": { "data": { - "api_token": "Token da API" + "api_token": "Token da API", + "description": "Re-autentique com sua conta Cloudflare." } }, + "records": { + "data": { + "records": "Registros" + }, + "title": "Escolha os registros a serem atualizados" + }, "user": { "data": { "api_token": "Token da API" - } + }, + "description": "Essa integra\u00e7\u00e3o requer um token de API criado com as permiss\u00f5es Zone:Zone:Read e Zone:DNS:Edit para todas as zonas em sua conta.", + "title": "Conecte-se \u00e0 Cloudflare" + }, + "zone": { + "data": { + "zone": "Zona" + }, + "title": "Escolha a Zona para Atualizar" } } } diff --git a/homeassistant/components/cloudflare/translations/uk.json b/homeassistant/components/cloudflare/translations/uk.json index 425ec2733b8f66..a8e383dc7b71d8 100644 --- a/homeassistant/components/cloudflare/translations/uk.json +++ b/homeassistant/components/cloudflare/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e.", + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f.", "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" }, "error": { diff --git a/homeassistant/components/co2signal/translations/pt-BR.json b/homeassistant/components/co2signal/translations/pt-BR.json index b0989763567b2d..d40c11d57dc363 100644 --- a/homeassistant/components/co2signal/translations/pt-BR.json +++ b/homeassistant/components/co2signal/translations/pt-BR.json @@ -2,9 +2,11 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "api_ratelimit": "Limite de taxa da API excedido", "unknown": "Erro inesperado" }, "error": { + "api_ratelimit": "Limite de taxa da API excedido", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, @@ -15,10 +17,17 @@ "longitude": "Longitude" } }, - "user": { + "country": { "data": { - "api_key": "Token de acesso" + "country_code": "C\u00f3digo do pa\u00eds" } + }, + "user": { + "data": { + "api_key": "Token de acesso", + "location": "Obter dados para" + }, + "description": "Acesse https://co2signal.com/ para solicitar um token." } } } diff --git a/homeassistant/components/coinbase/translations/nl.json b/homeassistant/components/coinbase/translations/nl.json index 2eebb526015613..fc2068cb01a2ad 100644 --- a/homeassistant/components/coinbase/translations/nl.json +++ b/homeassistant/components/coinbase/translations/nl.json @@ -25,7 +25,9 @@ }, "options": { "error": { + "currency_unavailable": "Een of meer van de gevraagde valutabalansen wordt niet geleverd door uw Coinbase API.", "currency_unavaliable": "Een of meer van de gevraagde valutasaldi worden niet geleverd door uw Coinbase API.", + "exchange_rate_unavailable": "Een of meer van de gevraagde wisselkoersen worden niet door Coinbase geleverd.", "exchange_rate_unavaliable": "Een of meer van de gevraagde wisselkoersen worden niet door Coinbase verstrekt.", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/coinbase/translations/pt-BR.json b/homeassistant/components/coinbase/translations/pt-BR.json index 4c6c42664764e9..6596135208bc47 100644 --- a/homeassistant/components/coinbase/translations/pt-BR.json +++ b/homeassistant/components/coinbase/translations/pt-BR.json @@ -7,21 +7,39 @@ "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "invalid_auth_key": "Credenciais de API rejeitadas pela Coinbase devido a uma chave de API inv\u00e1lida.", + "invalid_auth_secret": "Credenciais de API rejeitadas pela Coinbase devido a um segredo de API inv\u00e1lido.", "unknown": "Erro inesperado" }, "step": { "user": { "data": { - "api_key": "Chave da API" - } + "api_key": "Chave da API", + "api_token": "Segredo da API", + "currencies": "Moedas do saldo da conta", + "exchange_rates": "Taxas de c\u00e2mbio" + }, + "description": "Por favor, insira os detalhes da sua chave de API conforme fornecido pela Coinbase.", + "title": "Detalhes da chave da API Coinbase" } } }, "options": { "error": { "currency_unavailable": "Um ou mais dos saldos de moeda solicitados n\u00e3o s\u00e3o fornecidos pela sua API Coinbase.", + "currency_unavaliable": "Um ou mais dos saldos de moeda solicitados n\u00e3o s\u00e3o fornecidos pela sua API Coinbase.", "exchange_rate_unavailable": "Uma ou mais taxas de c\u00e2mbio solicitadas n\u00e3o s\u00e3o fornecidas pela Coinbase.", + "exchange_rate_unavaliable": "Uma ou mais taxas de c\u00e2mbio solicitadas n\u00e3o s\u00e3o fornecidas pela Coinbase.", "unknown": "Erro inesperado" + }, + "step": { + "init": { + "data": { + "account_balance_currencies": "Saldos da carteira a relatar.", + "exchange_base": "Moeda base para sensores de taxa de c\u00e2mbio.", + "exchange_rate_currencies": "Taxas de c\u00e2mbio a informar." + }, + "description": "Ajustar as op\u00e7\u00f5es da Coinbase" + } } } } \ No newline at end of file diff --git a/homeassistant/components/control4/translations/pt-BR.json b/homeassistant/components/control4/translations/pt-BR.json index c09810d8a43f50..f6fc6c6ef6247b 100644 --- a/homeassistant/components/control4/translations/pt-BR.json +++ b/homeassistant/components/control4/translations/pt-BR.json @@ -14,6 +14,16 @@ "host": "Endere\u00e7o IP", "password": "Senha", "username": "Usu\u00e1rio" + }, + "description": "Por favor, insira os detalhes da sua conta Control4 e o endere\u00e7o IP do seu controlador local." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Segundos entre atualiza\u00e7\u00f5es" } } } diff --git a/homeassistant/components/coolmaster/translations/pt-BR.json b/homeassistant/components/coolmaster/translations/pt-BR.json index d69f8206c465c8..4bb7d51e4640b8 100644 --- a/homeassistant/components/coolmaster/translations/pt-BR.json +++ b/homeassistant/components/coolmaster/translations/pt-BR.json @@ -1,7 +1,8 @@ { "config": { "error": { - "cannot_connect": "Falha ao conectar" + "cannot_connect": "Falha ao conectar", + "no_units": "N\u00e3o foi poss\u00edvel encontrar nenhuma unidade HVAC no host CoolMasterNet." }, "step": { "user": { @@ -13,7 +14,8 @@ "heat_cool": "Suporta o modo de aquecimento/resfriamento autom\u00e1tico", "host": "Nome do host", "off": "Pode ser desligado" - } + }, + "title": "Configure seus detalhes de conex\u00e3o CoolMasterNet." } } } diff --git a/homeassistant/components/cover/translations/pt-BR.json b/homeassistant/components/cover/translations/pt-BR.json index 3403666dfb94c9..81689ea1122f56 100644 --- a/homeassistant/components/cover/translations/pt-BR.json +++ b/homeassistant/components/cover/translations/pt-BR.json @@ -1,4 +1,31 @@ { + "device_automation": { + "action_type": { + "close": "Fechar {entity_name}", + "close_tilt": "Fechar inclina\u00e7\u00e3o de {entity_name}", + "open": "Abra {entity_name}", + "open_tilt": "Abrir inclina\u00e7\u00e3o de {entity_name}", + "set_position": "Definir a posi\u00e7\u00e3o de {entity_name}", + "set_tilt_position": "Definir a posi\u00e7\u00e3o de inclina\u00e7\u00e3o de {entity_name}", + "stop": "Parar {entity_name}" + }, + "condition_type": { + "is_closed": "{entity_name} est\u00e1 fechado", + "is_closing": "{entity_name} est\u00e1 fechando", + "is_open": "{entity_name} est\u00e1 aberto", + "is_opening": "{entity_name} est\u00e1 abrindo", + "is_position": "A posi\u00e7\u00e3o atual de {entity_name}", + "is_tilt_position": "A posi\u00e7\u00e3o de inclina\u00e7\u00e3o atual de {entity_name}" + }, + "trigger_type": { + "closed": "{entity_name} for fechado", + "closing": "{entity_name} estiver fechando", + "opened": "{entity_name} for aberto", + "opening": "{entity_name} estiver abrindo", + "position": "houver mudan\u00e7a de posi\u00e7\u00e3o de {entity_name}", + "tilt_position": "houver mudan\u00e7a na posi\u00e7\u00e3o de inclina\u00e7\u00e3o de {entity_name}" + } + }, "state": { "_": { "closed": "Fechado", diff --git a/homeassistant/components/crownstone/translations/pt-BR.json b/homeassistant/components/crownstone/translations/pt-BR.json index 64f05f0190359d..df4e446837e776 100644 --- a/homeassistant/components/crownstone/translations/pt-BR.json +++ b/homeassistant/components/crownstone/translations/pt-BR.json @@ -1,9 +1,12 @@ { "config": { "abort": { - "already_configured": "A conta j\u00e1 foi configurada" + "already_configured": "A conta j\u00e1 foi configurada", + "usb_setup_complete": "Configura\u00e7\u00e3o completa do Crownstone USB.", + "usb_setup_unsuccessful": "A configura\u00e7\u00e3o do USB Crownstone n\u00e3o foi bem-sucedida." }, "error": { + "account_not_verified": "Conta n\u00e3o verificada. Por favor, ative sua conta atrav\u00e9s do e-mail de ativa\u00e7\u00e3o da Crownstone.", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, @@ -11,22 +14,41 @@ "usb_config": { "data": { "usb_path": "Caminho do Dispositivo USB" - } + }, + "description": "Selecione a porta serial do dongle USB Crownstone ou selecione 'N\u00e3o usar USB' se n\u00e3o quiser configurar um dongle USB. \n\n Procure um dispositivo com VID 10C4 e PID EA60.", + "title": "Configura\u00e7\u00e3o do dongle USB Crownstone" }, "usb_manual_config": { "data": { "usb_manual_path": "Caminho do Dispositivo USB" - } + }, + "description": "Insira manualmente o caminho de um dongle USB Crownstone.", + "title": "Caminho manual do dongle USB Crownstone" + }, + "usb_sphere_config": { + "data": { + "usb_sphere": "Esfera de Crownstone" + }, + "description": "Selecione uma Esfera de Crownstone onde o USB est\u00e1 localizado.", + "title": "Esfera USB Crownstone" }, "user": { "data": { + "email": "Email", "password": "Senha" - } + }, + "title": "Conta Crownstone" } } }, "options": { "step": { + "init": { + "data": { + "usb_sphere_option": "Crownstone Sphere onde o USB est\u00e1 localizado", + "use_usb_option": "Use um dongle USB Crownstone para transmiss\u00e3o de dados local" + } + }, "usb_config": { "data": { "usb_path": "Caminho do Dispositivo USB" @@ -37,7 +59,9 @@ "usb_config_option": { "data": { "usb_path": "Caminho do Dispositivo USB" - } + }, + "description": "Selecione a porta serial do dongle USB Crownstone. \n\n Procure um dispositivo com VID 10C4 e PID EA60.", + "title": "Configura\u00e7\u00e3o do dongle USB Crownstone" }, "usb_manual_config": { "data": { @@ -49,7 +73,9 @@ "usb_manual_config_option": { "data": { "usb_manual_path": "Caminho do Dispositivo USB" - } + }, + "description": "Insira manualmente o caminho de um dongle USB Crownstone.", + "title": "Caminho manual do dongle USB Crownstone" }, "usb_sphere_config": { "data": { @@ -57,6 +83,13 @@ }, "description": "Selecione um Crownstone Sphere onde o USB est\u00e1 localizado.", "title": "Esfera USB Crownstone" + }, + "usb_sphere_config_option": { + "data": { + "usb_sphere": "Esfera de Crownstone" + }, + "description": "Selecione um Crownstone Sphere onde o USB est\u00e1 localizado.", + "title": "Esfera USB Crownstone" } } } diff --git a/homeassistant/components/deconz/translations/pt-BR.json b/homeassistant/components/deconz/translations/pt-BR.json index feda280cc3f570..03004cae304620 100644 --- a/homeassistant/components/deconz/translations/pt-BR.json +++ b/homeassistant/components/deconz/translations/pt-BR.json @@ -4,16 +4,18 @@ "already_configured": "A ponte j\u00e1 est\u00e1 configurada", "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", "no_bridges": "N\u00e3o h\u00e1 pontes de deCONZ descobertas", + "no_hardware_available": "Nenhum hardware de r\u00e1dio conectado ao deCONZ", "not_deconz_bridge": "N\u00e3o \u00e9 uma ponte deCONZ", "updated_instance": "Atualiza\u00e7\u00e3o da inst\u00e2ncia deCONZ com novo endere\u00e7o de host" }, "error": { "no_key": "N\u00e3o foi poss\u00edvel obter uma chave de API" }, + "flow_title": "{host}", "step": { "hassio_confirm": { - "description": "Deseja configurar o Home Assistant para conectar-se ao gateway deCONZ fornecido pelo add-on Supervisor {addon} ?", - "title": "Gateway deCONZ Zigbee via add-on Supervisor" + "description": "Deseja configurar o Home Assistant para conectar-se ao gateway deCONZ fornecido pelo add-on {addon} ?", + "title": "Gateway deCONZ Zigbee via add-on" }, "link": { "description": "Desbloqueie o seu gateway deCONZ para se registar no Home Assistant. \n\n 1. V\u00e1 para as configura\u00e7\u00f5es do sistema deCONZ \n 2. Pressione o bot\u00e3o \"Desbloquear Gateway\"", @@ -24,6 +26,11 @@ "host": "Nome do host", "port": "Porta" } + }, + "user": { + "data": { + "host": "Selecione o gateway deCONZ descoberto" + } } } }, @@ -35,28 +42,55 @@ "button_2": "Segundo bot\u00e3o", "button_3": "Terceiro bot\u00e3o", "button_4": "Quarto bot\u00e3o", + "button_5": "Quinto bot\u00e3o", + "button_6": "Sexto bot\u00e3o", + "button_7": "S\u00e9timo bot\u00e3o", + "button_8": "Oitavo bot\u00e3o", "close": "Fechar", "dim_down": "Diminuir a luminosidade", "dim_up": "Aumentar a luminosidade", "left": "Esquerdo", "open": "Aberto", "right": "Direito", + "side_1": "Lado 1", + "side_2": "Lado 2", + "side_3": "Lado 3", + "side_4": "Lado 4", + "side_5": "Lado 5", + "side_6": "Lado 6", "top_buttons": "Bot\u00f5es superiores", "turn_off": "Desligar", "turn_on": "Ligar" }, "trigger_type": { + "remote_awakened": "Dispositivo for despertado", "remote_button_double_press": "bot\u00e3o \" {subtype} \" clicado duas vezes", "remote_button_long_press": "Bot\u00e3o \" {subtype} \" pressionado continuamente", "remote_button_long_release": "Bot\u00e3o \" {subtype} \" liberado ap\u00f3s press\u00e3o longa", "remote_button_quadruple_press": "Bot\u00e3o \" {subtype} \" qu\u00e1druplo clicado", "remote_button_quintuple_press": "Bot\u00e3o \" {subtype} \" qu\u00edntuplo clicado", "remote_button_rotated": "Bot\u00e3o girado \" {subtype} \"", + "remote_button_rotated_fast": "Bot\u00e3o girado r\u00e1pido \"{subtype}\"", "remote_button_rotation_stopped": "A rota\u00e7\u00e3o dos bot\u00f5es \"{subtype}\" parou", "remote_button_short_press": "Bot\u00e3o \" {subtype} \" pressionado", "remote_button_short_release": "Bot\u00e3o \" {subtype} \" liberados", "remote_button_triple_press": "Bot\u00e3o \" {subtype} \" clicado tr\u00eas vezes", - "remote_gyro_activated": "Dispositivo sacudido" + "remote_double_tap": "Dispositivo \"{subtype}\" tocado duas vezes", + "remote_double_tap_any_side": "Dispositivo tocado duas vezes em qualquer lado", + "remote_falling": "Dispositivo em queda livre", + "remote_flip_180_degrees": "Dispositivo invertido 180 graus", + "remote_flip_90_degrees": "Dispositivo invertido 90 graus", + "remote_gyro_activated": "Dispositivo sacudido", + "remote_moved": "Dispositivo movido com \"{subtype}\" para cima", + "remote_moved_any_side": "Dispositivo movido com qualquer lado para cima", + "remote_rotate_from_side_1": "Dispositivo girado de \"lado 1\" para \"{subtype}\"", + "remote_rotate_from_side_2": "Dispositivo girado de \"lado 2\" para \"{subtype}\"", + "remote_rotate_from_side_3": "Dispositivo girado de \"lado 3\" para \"{subtype}\"", + "remote_rotate_from_side_4": "Dispositivo girado de \"lado 4\" para \"{subtype}\"", + "remote_rotate_from_side_5": "Dispositivo girado de \"lado 5\" para \"{subtype}\"", + "remote_rotate_from_side_6": "Dispositivo girado de \"lado 6\" para \"{subtype}\"", + "remote_turned_clockwise": "Dispositivo girado no sentido hor\u00e1rio", + "remote_turned_counter_clockwise": "Dispositivo girado no sentido anti-hor\u00e1rio" } }, "options": { @@ -64,9 +98,11 @@ "deconz_devices": { "data": { "allow_clip_sensor": "Permitir sensores deCONZ CLIP", - "allow_deconz_groups": "Permitir grupos de luz deCONZ" + "allow_deconz_groups": "Permitir grupos de luz deCONZ", + "allow_new_devices": "Permitir a adi\u00e7\u00e3o autom\u00e1tica de novos dispositivos" }, - "description": "Configure a visibilidade dos tipos de dispositivos deCONZ" + "description": "Configure a visibilidade dos tipos de dispositivos deCONZ", + "title": "Op\u00e7\u00f5es deCONZ" } } } diff --git a/homeassistant/components/demo/translations/pt-BR.json b/homeassistant/components/demo/translations/pt-BR.json index 8364f0bc94be21..49290be4ceb7ee 100644 --- a/homeassistant/components/demo/translations/pt-BR.json +++ b/homeassistant/components/demo/translations/pt-BR.json @@ -4,6 +4,7 @@ "options_1": { "data": { "bool": "Booleano opcional", + "constant": "Constante", "int": "Entrada num\u00e9rica" } }, diff --git a/homeassistant/components/demo/translations/select.pt-BR.json b/homeassistant/components/demo/translations/select.pt-BR.json new file mode 100644 index 00000000000000..2530ff3b4ca53b --- /dev/null +++ b/homeassistant/components/demo/translations/select.pt-BR.json @@ -0,0 +1,9 @@ +{ + "state": { + "demo__speed": { + "light_speed": "Velocidade da luz", + "ludicrous_speed": "Velocidade absurda", + "ridiculous_speed": "Velocidade rid\u00edcula" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/denonavr/translations/pt-BR.json b/homeassistant/components/denonavr/translations/pt-BR.json index a39a263484b1e8..084c7dd3c182d6 100644 --- a/homeassistant/components/denonavr/translations/pt-BR.json +++ b/homeassistant/components/denonavr/translations/pt-BR.json @@ -2,13 +2,47 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", - "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento" + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "cannot_connect": "Falha ao conectar, tente novamente, desconectar os cabos de alimenta\u00e7\u00e3o e ethernet e reconect\u00e1-los pode ajudar", + "not_denonavr_manufacturer": "N\u00e3o \u00e9 um receptor de rede Denon AVR, o fabricante descoberto n\u00e3o corresponde", + "not_denonavr_missing": "N\u00e3o \u00e9 um receptor de rede Denon AVR, as informa\u00e7\u00f5es de descoberta n\u00e3o est\u00e3o completas" }, + "error": { + "discovery_error": "Falha ao descobrir um receptor de rede Denon AVR" + }, + "flow_title": "{name}", "step": { + "confirm": { + "description": "Confirme a adi\u00e7\u00e3o do receptor", + "title": "Receptores de rede Denon AVR" + }, + "select": { + "data": { + "select_host": "Endere\u00e7o IP do receptor" + }, + "description": "Execute a configura\u00e7\u00e3o novamente se desejar conectar receptores adicionais", + "title": "Selecione o receptor que voc\u00ea deseja conectar" + }, "user": { "data": { "host": "Endere\u00e7o IP" - } + }, + "description": "Conecte-se ao seu receptor, se o endere\u00e7o IP n\u00e3o estiver definido, a descoberta autom\u00e1tica ser\u00e1 usada", + "title": "Receptores de rede Denon AVR" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_all_sources": "Mostrar todas as fontes", + "update_audyssey": "Atualizar as configura\u00e7\u00f5es do Audyssey", + "zone2": "Configure a Zona 2", + "zone3": "Configurar a Zona 3" + }, + "description": "Especificar configura\u00e7\u00f5es opcionais", + "title": "Receptores de rede Denon AVR" } } } diff --git a/homeassistant/components/device_tracker/translations/pt-BR.json b/homeassistant/components/device_tracker/translations/pt-BR.json index c20638a4a6131c..762fb96fd05a15 100644 --- a/homeassistant/components/device_tracker/translations/pt-BR.json +++ b/homeassistant/components/device_tracker/translations/pt-BR.json @@ -1,4 +1,14 @@ { + "device_automation": { + "condition_type": { + "is_home": "{entity_name} est\u00e1 em casa", + "is_not_home": "{entity_name} n\u00e3o est\u00e1 em casa" + }, + "trigger_type": { + "enters": "{entity_name} entra em uma zona", + "leaves": "{entity_name} sai de uma zona" + } + }, "state": { "_": { "home": "Em casa", diff --git a/homeassistant/components/devolo_home_control/translations/pt-BR.json b/homeassistant/components/devolo_home_control/translations/pt-BR.json index 58d60891613c57..c2136958ffbc78 100644 --- a/homeassistant/components/devolo_home_control/translations/pt-BR.json +++ b/homeassistant/components/devolo_home_control/translations/pt-BR.json @@ -5,19 +5,22 @@ "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { - "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "reauth_failed": "Por favor, use o mesmo usu\u00e1rio mydevolo de antes." }, "step": { "user": { "data": { "mydevolo_url": "mydevolo URL", - "password": "Senha" + "password": "Senha", + "username": "Email / devolo ID" } }, "zeroconf_confirm": { "data": { "mydevolo_url": "mydevolo URL", - "password": "Senha" + "password": "Senha", + "username": "Email / devolo ID" } } } diff --git a/homeassistant/components/devolo_home_network/translations/pt-BR.json b/homeassistant/components/devolo_home_network/translations/pt-BR.json index edffd23f3afb10..94a1f632d788e1 100644 --- a/homeassistant/components/devolo_home_network/translations/pt-BR.json +++ b/homeassistant/components/devolo_home_network/translations/pt-BR.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 configurado", + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "home_control": "A Unidade Central de Home Control Devolo n\u00e3o funciona com esta integra\u00e7\u00e3o." }, "error": { diff --git a/homeassistant/components/dexcom/translations/pt-BR.json b/homeassistant/components/dexcom/translations/pt-BR.json index d86aef5d51d73a..ce21e4d51c8d98 100644 --- a/homeassistant/components/dexcom/translations/pt-BR.json +++ b/homeassistant/components/dexcom/translations/pt-BR.json @@ -12,7 +12,19 @@ "user": { "data": { "password": "Senha", + "server": "Servidor", "username": "Usu\u00e1rio" + }, + "description": "Insira as credenciais do Dexcom Share", + "title": "Configurar integra\u00e7\u00e3o Dexcom" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "unit_of_measurement": "Unidade de medida" } } } diff --git a/homeassistant/components/diagnostics/translations/nl.json b/homeassistant/components/diagnostics/translations/nl.json new file mode 100644 index 00000000000000..b12cbdbb1db038 --- /dev/null +++ b/homeassistant/components/diagnostics/translations/nl.json @@ -0,0 +1,3 @@ +{ + "title": "Diagnostiek" +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/translations/ja.json b/homeassistant/components/dialogflow/translations/ja.json index 0cb6f57eae2f30..199db0c326c1d7 100644 --- a/homeassistant/components/dialogflow/translations/ja.json +++ b/homeassistant/components/dialogflow/translations/ja.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Home Assistant Cloud\u306b\u63a5\u7d9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002", "single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002", "webhook_not_internet_accessible": "Webhook\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u53d7\u4fe1\u3059\u308b\u306b\u306f\u3001Home Assistant\u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u306b\u3001\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u304b\u3089\u30a2\u30af\u30bb\u30b9\u3067\u304d\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002" }, diff --git a/homeassistant/components/dialogflow/translations/nl.json b/homeassistant/components/dialogflow/translations/nl.json index 82fe7daea00150..3d2617d25e5005 100644 --- a/homeassistant/components/dialogflow/translations/nl.json +++ b/homeassistant/components/dialogflow/translations/nl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Niet verbonden met Home Assistant Cloud.", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." }, diff --git a/homeassistant/components/dialogflow/translations/pt-BR.json b/homeassistant/components/dialogflow/translations/pt-BR.json index 43954a1f032cc9..7b5c5a96464d1a 100644 --- a/homeassistant/components/dialogflow/translations/pt-BR.json +++ b/homeassistant/components/dialogflow/translations/pt-BR.json @@ -2,7 +2,8 @@ "config": { "abort": { "cloud_not_connected": "N\u00e3o conectado ao Home Assistant Cloud.", - "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", + "webhook_not_internet_accessible": "Sua inst\u00e2ncia do Home Assistant precisa estar acess\u00edvel pela Internet para receber mensagens de webhook." }, "create_entry": { "default": "Para enviar eventos para o Home Assistant, voc\u00ea precisar\u00e1 configurar [Integra\u00e7\u00e3o do webhook da Dialogflow] ( {dialogflow_url} ). \n\n Preencha as seguintes informa\u00e7\u00f5es: \n\n - URL: ` {webhook_url} ` \n - M\u00e9todo: POST \n - Tipo de Conte\u00fado: application / json \n\n Veja [a documenta\u00e7\u00e3o] ( {docs_url} ) para mais detalhes." diff --git a/homeassistant/components/dialogflow/translations/uk.json b/homeassistant/components/dialogflow/translations/uk.json index 625d2db78dcb01..5186a1882f14b9 100644 --- a/homeassistant/components/dialogflow/translations/uk.json +++ b/homeassistant/components/dialogflow/translations/uk.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e.", + "cloud_not_connected": "\u041d\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0434\u043e Home Assistant Cloud.", + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f.", "webhook_not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u043f\u043e\u0432\u0438\u043d\u0435\u043d \u0431\u0443\u0442\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0439 \u0437 \u0406\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443 \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f Webhook-\u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u044c." }, "create_entry": { diff --git a/homeassistant/components/directv/translations/pt-BR.json b/homeassistant/components/directv/translations/pt-BR.json index f317d16eb4119a..98fa2d6e3b6b59 100644 --- a/homeassistant/components/directv/translations/pt-BR.json +++ b/homeassistant/components/directv/translations/pt-BR.json @@ -7,7 +7,7 @@ "error": { "cannot_connect": "Falha ao conectar" }, - "flow_title": "DirecTV: {name}", + "flow_title": "{name}", "step": { "ssdp_confirm": { "description": "Voc\u00ea quer configurar o {name}?" diff --git a/homeassistant/components/dlna_dmr/translations/pt-BR.json b/homeassistant/components/dlna_dmr/translations/pt-BR.json index 58ed851546d7ba..0df5a60e565281 100644 --- a/homeassistant/components/dlna_dmr/translations/pt-BR.json +++ b/homeassistant/components/dlna_dmr/translations/pt-BR.json @@ -3,15 +3,26 @@ "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "alternative_integration": "O dispositivo \u00e9 melhor suportado por outra integra\u00e7\u00e3o", - "cannot_connect": "Falha ao conectar" + "cannot_connect": "Falha ao conectar", + "could_not_connect": "Falha ao conectar ao dispositivo DLNA", + "discovery_error": "Falha ao descobrir um dispositivo DLNA correspondente", + "incomplete_config": "A configura\u00e7\u00e3o n\u00e3o tem uma vari\u00e1vel obrigat\u00f3ria", + "non_unique_id": "V\u00e1rios dispositivos encontrados com o mesmo ID exclusivo", + "not_dmr": "O dispositivo n\u00e3o \u00e9 um renderizador de m\u00eddia digital compat\u00edvel" }, "error": { - "cannot_connect": "Falha ao conectar" + "cannot_connect": "Falha ao conectar", + "could_not_connect": "Falha ao conectar-se ao dispositivo DLNA", + "not_dmr": "O dispositivo n\u00e3o \u00e9 um renderizador de m\u00eddia digital compat\u00edvel" }, + "flow_title": "{name}", "step": { "confirm": { "description": "Deseja iniciar a configura\u00e7\u00e3o?" }, + "import_turn_on": { + "description": "Por favor, ligue o dispositivo e clique em enviar para continuar a migra\u00e7\u00e3o" + }, "manual": { "data": { "url": "URL" @@ -23,7 +34,24 @@ "data": { "host": "Nome do host", "url": "URL" - } + }, + "description": "Escolha um dispositivo para configurar ou deixe em branco para inserir um URL", + "title": "Dispositivos DMR DLNA descobertos" + } + } + }, + "options": { + "error": { + "invalid_url": "URL inv\u00e1lida" + }, + "step": { + "init": { + "data": { + "callback_url_override": "URL de retorno do ouvinte de eventos", + "listen_port": "Porta do ouvinte de eventos (aleat\u00f3rio se n\u00e3o estiver definido)", + "poll_availability": "Pesquisa de disponibilidade do dispositivo" + }, + "title": "Configura\u00e7\u00e3o do renderizador de m\u00eddia digital DLNA" } } } diff --git a/homeassistant/components/dnsip/translations/ja.json b/homeassistant/components/dnsip/translations/ja.json index bf252c8ef80e65..4b2e6a1fc6566e 100644 --- a/homeassistant/components/dnsip/translations/ja.json +++ b/homeassistant/components/dnsip/translations/ja.json @@ -2,6 +2,13 @@ "config": { "error": { "invalid_hostname": "\u7121\u52b9\u306a\u30db\u30b9\u30c8\u540d" + }, + "step": { + "user": { + "data": { + "hostname": "DNS\u30af\u30a8\u30ea\u3092\u5b9f\u884c\u3059\u308b\u30db\u30b9\u30c8\u540d" + } + } } }, "options": { diff --git a/homeassistant/components/dnsip/translations/nl.json b/homeassistant/components/dnsip/translations/nl.json index b0aebece7b4393..6528bdb5a61045 100644 --- a/homeassistant/components/dnsip/translations/nl.json +++ b/homeassistant/components/dnsip/translations/nl.json @@ -10,5 +10,18 @@ } } } + }, + "options": { + "error": { + "invalid_resolver": "Ongeldig IP-adres voor resolver" + }, + "step": { + "init": { + "data": { + "resolver": "Resolver voor IPV4 lookup", + "resolver_ipv6": "Resolver voor IPV6 lookup" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/doorbird/translations/el.json b/homeassistant/components/doorbird/translations/el.json index edb649341c8aee..5eaa736cd5c0fe 100644 --- a/homeassistant/components/doorbird/translations/el.json +++ b/homeassistant/components/doorbird/translations/el.json @@ -6,6 +6,9 @@ }, "step": { "user": { + "data": { + "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" + }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf DoorBird" } } diff --git a/homeassistant/components/doorbird/translations/pt-BR.json b/homeassistant/components/doorbird/translations/pt-BR.json index e34e7593c77b20..3f2c479df8f0b2 100644 --- a/homeassistant/components/doorbird/translations/pt-BR.json +++ b/homeassistant/components/doorbird/translations/pt-BR.json @@ -10,13 +10,26 @@ "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, + "flow_title": "{name} ({host})", "step": { "user": { "data": { "host": "Nome do host", + "name": "Nome do dispositivo", "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "title": "Conecte-se ao DoorBird" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "events": "Lista de eventos separados por v\u00edrgulas." + }, + "description": "Adicione um nome de evento separado por v\u00edrgula para cada evento que voc\u00ea deseja rastrear. Depois de inseri-los aqui, use o aplicativo DoorBird para atribu\u00ed-los a um evento espec\u00edfico. Consulte a documenta\u00e7\u00e3o em https://www.home-assistant.io/integrations/doorbird/#events. Exemplo: alguem_pressionou_o_botao movimento" } } } diff --git a/homeassistant/components/dsmr/translations/pt-BR.json b/homeassistant/components/dsmr/translations/pt-BR.json index 95b4ef549a74a0..911a93db1b8c74 100644 --- a/homeassistant/components/dsmr/translations/pt-BR.json +++ b/homeassistant/components/dsmr/translations/pt-BR.json @@ -2,23 +2,51 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_communicate": "Falha ao comunicar", "cannot_connect": "Falha ao conectar" }, "error": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_communicate": "Falha ao comunicar", "cannot_connect": "Falha ao conectar" }, "step": { "setup_network": { "data": { + "dsmr_version": "Selecione a vers\u00e3o do DSMR", "host": "Nome do host", "port": "Porta" - } + }, + "title": "Selecione o endere\u00e7o de conex\u00e3o" + }, + "setup_serial": { + "data": { + "dsmr_version": "Selecione a vers\u00e3o do DSMR", + "port": "Selecionar dispositivo" + }, + "title": "Dispositivo" }, "setup_serial_manual_path": { "data": { "port": "Caminho do Dispositivo USB" - } + }, + "title": "Caminho" + }, + "user": { + "data": { + "type": "Tipo de conex\u00e3o" + }, + "title": "Selecione o tipo de conex\u00e3o" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "time_between_update": "Tempo m\u00ednimo entre atualiza\u00e7\u00f5es de entidade [s]" + }, + "title": "Op\u00e7\u00f5es de DSMR" } } } diff --git a/homeassistant/components/dunehd/translations/pt-BR.json b/homeassistant/components/dunehd/translations/pt-BR.json index d783704c0a99c0..072cf6011ea828 100644 --- a/homeassistant/components/dunehd/translations/pt-BR.json +++ b/homeassistant/components/dunehd/translations/pt-BR.json @@ -12,7 +12,9 @@ "user": { "data": { "host": "Nome do host" - } + }, + "description": "Configure a integra\u00e7\u00e3o Dune HD. Se voc\u00ea tiver problemas com a configura\u00e7\u00e3o, acesse: https://www.home-assistant.io/integrations/dunehd \n\n Certifique-se de que seu player est\u00e1 ligado.", + "title": "Dune HD" } } } diff --git a/homeassistant/components/eafm/translations/pt-BR.json b/homeassistant/components/eafm/translations/pt-BR.json index e29d809ebff3dd..f7dfd4cf08078f 100644 --- a/homeassistant/components/eafm/translations/pt-BR.json +++ b/homeassistant/components/eafm/translations/pt-BR.json @@ -1,7 +1,17 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "no_stations": "Nenhuma esta\u00e7\u00e3o de monitoramento de enchentes encontrada." + }, + "step": { + "user": { + "data": { + "station": "Esta\u00e7\u00e3o" + }, + "description": "Selecione a esta\u00e7\u00e3o que deseja monitorar", + "title": "Rastrear uma esta\u00e7\u00e3o de monitoramento de enchentes" + } } } } \ No newline at end of file diff --git a/homeassistant/components/ecobee/translations/pt-BR.json b/homeassistant/components/ecobee/translations/pt-BR.json index 35f7967ccac6ab..3174c06c802749 100644 --- a/homeassistant/components/ecobee/translations/pt-BR.json +++ b/homeassistant/components/ecobee/translations/pt-BR.json @@ -9,7 +9,7 @@ }, "step": { "authorize": { - "description": "Por favor, autorize este aplicativo em https://www.ecobee.com/consumerportal/index.html com c\u00f3digo PIN:\n\n{pin}\n\nEm seguida, pressione Submit.", + "description": "Por favor, autorize este aplicativo em https://www.ecobee.com/consumerportal/index.html com c\u00f3digo PIN:\n\n{pin}\n\nEm seguida, pressione Enviar.", "title": "Autorizar aplicativo em ecobee.com" }, "user": { diff --git a/homeassistant/components/ecobee/translations/uk.json b/homeassistant/components/ecobee/translations/uk.json index 7cf7df534296f6..0d411f1fdd1059 100644 --- a/homeassistant/components/ecobee/translations/uk.json +++ b/homeassistant/components/ecobee/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "error": { "pin_request_failed": "\u0421\u0442\u0430\u043b\u0430\u0441\u044f \u043f\u043e\u043c\u0438\u043b\u043a\u0430 \u043f\u0456\u0434 \u0447\u0430\u0441 \u0437\u0430\u043f\u0438\u0442\u0443 PIN-\u043a\u043e\u0434\u0443 \u0443 ecobee; \u0431\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0456\u0441\u0442\u044c \u043a\u043b\u044e\u0447\u0430 API.", diff --git a/homeassistant/components/econet/translations/pt-BR.json b/homeassistant/components/econet/translations/pt-BR.json index 55722d53aeb4c8..23469f0fd263f4 100644 --- a/homeassistant/components/econet/translations/pt-BR.json +++ b/homeassistant/components/econet/translations/pt-BR.json @@ -12,8 +12,10 @@ "step": { "user": { "data": { + "email": "Email", "password": "Senha" - } + }, + "title": "Configurar conta Rheem EcoNet" } } } diff --git a/homeassistant/components/efergy/translations/pt-BR.json b/homeassistant/components/efergy/translations/pt-BR.json index 065c29ab9ab67b..8197121b5d57b5 100644 --- a/homeassistant/components/efergy/translations/pt-BR.json +++ b/homeassistant/components/efergy/translations/pt-BR.json @@ -13,7 +13,8 @@ "user": { "data": { "api_key": "Chave da API" - } + }, + "title": "Efergy" } } } diff --git a/homeassistant/components/elgato/translations/pt-BR.json b/homeassistant/components/elgato/translations/pt-BR.json index 10441872c5195a..4cc692371d3b51 100644 --- a/homeassistant/components/elgato/translations/pt-BR.json +++ b/homeassistant/components/elgato/translations/pt-BR.json @@ -7,16 +7,18 @@ "error": { "cannot_connect": "Falha ao conectar" }, + "flow_title": "{serial_number}", "step": { "user": { "data": { "host": "Nome do host", "port": "Porta" - } + }, + "description": "Configure seu Elgato Light para integrar com o Home Assistant." }, "zeroconf_confirm": { - "description": "Deseja adicionar o Elgato Key Light n\u00famero de s\u00e9rie ` {serial_number} ` ao Home Assistant?", - "title": "Dispositivo Elgato Key Light descoberto" + "description": "Deseja adicionar a l\u00e2mpada Elgato com n\u00famero de s\u00e9rie `{serial_number}` ao Home Assistant?", + "title": "Dispositivo Elgato Key descoberto" } } } diff --git a/homeassistant/components/elkm1/translations/el.json b/homeassistant/components/elkm1/translations/el.json index 5bb2846243a125..d86e777877e6b3 100644 --- a/homeassistant/components/elkm1/translations/el.json +++ b/homeassistant/components/elkm1/translations/el.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "address_already_configured": "\u0388\u03bd\u03b1 ElkM1 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_configured": "\u0388\u03bd\u03b1 ElkM1 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/elkm1/translations/pt-BR.json b/homeassistant/components/elkm1/translations/pt-BR.json index efdc82ab438933..ffc9c5ba4ec3ff 100644 --- a/homeassistant/components/elkm1/translations/pt-BR.json +++ b/homeassistant/components/elkm1/translations/pt-BR.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "address_already_configured": "Um ElkM1 com este endere\u00e7o j\u00e1 est\u00e1 configurado", + "already_configured": "Um ElkM1 com este prefixo j\u00e1 est\u00e1 configurado" + }, "error": { "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", @@ -8,9 +12,15 @@ "step": { "user": { "data": { + "address": "O endere\u00e7o IP ou dom\u00ednio ou porta serial se estiver conectando via serial.", "password": "Senha", + "prefix": "Um prefixo exclusivo (deixe em branco se voc\u00ea tiver apenas um ElkM1).", + "protocol": "Protocolo", + "temperature_unit": "A unidade de temperatura que ElkM1 usa.", "username": "Usu\u00e1rio" - } + }, + "description": "A string de endere\u00e7o deve estar no formato 'address[:port]' para 'seguro' e 'n\u00e3o seguro'. Exemplo: '192.168.1.1'. A porta \u00e9 opcional e o padr\u00e3o \u00e9 2101 para 'n\u00e3o seguro' e 2601 para 'seguro'. Para o protocolo serial, o endere\u00e7o deve estar no formato 'tty[:baud]'. Exemplo: '/dev/ttyS1'. O baud \u00e9 opcional e o padr\u00e3o \u00e9 115200.", + "title": "Conecte ao controle Elk-M1" } } } diff --git a/homeassistant/components/elmax/translations/pt-BR.json b/homeassistant/components/elmax/translations/pt-BR.json index b2cefe6620692f..9db13c83fbc77f 100644 --- a/homeassistant/components/elmax/translations/pt-BR.json +++ b/homeassistant/components/elmax/translations/pt-BR.json @@ -27,8 +27,10 @@ "password": "Senha", "username": "Usu\u00e1rio" }, - "description": "Fa\u00e7a login na nuvem Elmax usando suas credenciais" + "description": "Fa\u00e7a login na nuvem Elmax usando suas credenciais", + "title": "Login da conta" } } - } + }, + "title": "Configura\u00e7\u00e3o de nuvem Elmax" } \ No newline at end of file diff --git a/homeassistant/components/emonitor/translations/pt-BR.json b/homeassistant/components/emonitor/translations/pt-BR.json index ff6ede166a9dd6..80e47d1f10cead 100644 --- a/homeassistant/components/emonitor/translations/pt-BR.json +++ b/homeassistant/components/emonitor/translations/pt-BR.json @@ -7,7 +7,12 @@ "cannot_connect": "Falha ao conectar", "unknown": "Erro inesperado" }, + "flow_title": "{name}", "step": { + "confirm": { + "description": "Deseja configurar {name} ({host})?", + "title": "Configura\u00e7\u00e3o SiteSage Emonitor" + }, "user": { "data": { "host": "Nome do host" diff --git a/homeassistant/components/emulated_roku/translations/pt-BR.json b/homeassistant/components/emulated_roku/translations/pt-BR.json index 864ae263dbad2b..139a17577e8bd2 100644 --- a/homeassistant/components/emulated_roku/translations/pt-BR.json +++ b/homeassistant/components/emulated_roku/translations/pt-BR.json @@ -9,7 +9,7 @@ "advertise_ip": "Anunciar IP", "advertise_port": "Anunciar porta", "host_ip": "IP do host", - "listen_port": "Porta de escuta", + "listen_port": "Ouvir Porta", "name": "Nome", "upnp_bind_multicast": "Vincular multicast (Verdadeiro/Falso)" }, @@ -17,5 +17,5 @@ } } }, - "title": "EmulatedRoku" + "title": "Emulated Roku" } \ No newline at end of file diff --git a/homeassistant/components/energy/translations/pt-BR.json b/homeassistant/components/energy/translations/pt-BR.json new file mode 100644 index 00000000000000..c8d85790fdd538 --- /dev/null +++ b/homeassistant/components/energy/translations/pt-BR.json @@ -0,0 +1,3 @@ +{ + "title": "Energia" +} \ No newline at end of file diff --git a/homeassistant/components/enocean/translations/pt-BR.json b/homeassistant/components/enocean/translations/pt-BR.json index 9ab59f40649f29..c0d65a7934fadb 100644 --- a/homeassistant/components/enocean/translations/pt-BR.json +++ b/homeassistant/components/enocean/translations/pt-BR.json @@ -1,7 +1,25 @@ { "config": { "abort": { + "invalid_dongle_path": "Caminho de dongle inv\u00e1lido", "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "error": { + "invalid_dongle_path": "Nenhum dongle v\u00e1lido encontrado para este caminho" + }, + "step": { + "detect": { + "data": { + "path": "Caminho de dongle USB" + }, + "title": "Selecione o caminho para seu dongle ENOcean" + }, + "manual": { + "data": { + "path": "caminho do dongle USB" + }, + "title": "Digite o caminho para voc\u00ea dongle ENOcean" + } } } } \ No newline at end of file diff --git a/homeassistant/components/enocean/translations/uk.json b/homeassistant/components/enocean/translations/uk.json index 5c3e2d6eb6ec97..01f7447c8aeb37 100644 --- a/homeassistant/components/enocean/translations/uk.json +++ b/homeassistant/components/enocean/translations/uk.json @@ -2,7 +2,7 @@ "config": { "abort": { "invalid_dongle_path": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0438\u0439 \u0448\u043b\u044f\u0445 \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e.", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "error": { "invalid_dongle_path": "\u041d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0432\u0430\u043d\u0438\u0445 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 \u0437\u0430 \u0446\u0438\u043c \u0448\u043b\u044f\u0445\u043e\u043c." diff --git a/homeassistant/components/enphase_envoy/translations/pt-BR.json b/homeassistant/components/enphase_envoy/translations/pt-BR.json index 223bb36392f24b..d4e296b6b4b7a0 100644 --- a/homeassistant/components/enphase_envoy/translations/pt-BR.json +++ b/homeassistant/components/enphase_envoy/translations/pt-BR.json @@ -9,13 +9,15 @@ "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, + "flow_title": "{serial} ({host})", "step": { "user": { "data": { "host": "Nome do host", "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "description": "Para modelos mais novos, digite o nome de usu\u00e1rio `envoy` sem uma senha. Para modelos mais antigos, digite o nome de usu\u00e1rio `installer` sem uma senha. Para todos os outros modelos, insira um nome de usu\u00e1rio e senha v\u00e1lidos." } } } diff --git a/homeassistant/components/environment_canada/translations/pt-BR.json b/homeassistant/components/environment_canada/translations/pt-BR.json index f99ab671b8e327..6325a1268237ec 100644 --- a/homeassistant/components/environment_canada/translations/pt-BR.json +++ b/homeassistant/components/environment_canada/translations/pt-BR.json @@ -1,15 +1,22 @@ { "config": { "error": { + "bad_station_id": "A ID da esta\u00e7\u00e3o \u00e9 inv\u00e1lida, ausente ou n\u00e3o encontrada no banco de dados de ID da esta\u00e7\u00e3o", "cannot_connect": "Falha ao conectar", + "error_response": "Resposta do Environment Canada com erro", + "too_many_attempts": "As conex\u00f5es com o Environment Canada s\u00e3o limitadas por tarifas; Tente novamente em 60 segundos", "unknown": "Erro inesperado" }, "step": { "user": { "data": { + "language": "Idioma de informa\u00e7\u00f5es meteorol\u00f3gicas", "latitude": "Latitude", - "longitude": "Longitude" - } + "longitude": "Longitude", + "station": "ID da esta\u00e7\u00e3o meteorol\u00f3gica" + }, + "description": "Um ID de esta\u00e7\u00e3o ou latitude/longitude deve ser especificado. A latitude/longitude padr\u00e3o usada s\u00e3o os valores configurados na instala\u00e7\u00e3o do Home Assistant. A esta\u00e7\u00e3o meteorol\u00f3gica mais pr\u00f3xima das coordenadas ser\u00e1 usada se especificar as coordenadas. Se for usado um c\u00f3digo de esta\u00e7\u00e3o, deve seguir o formato: PP/c\u00f3digo, onde PP \u00e9 a prov\u00edncia de duas letras e c\u00f3digo \u00e9 o ID da esta\u00e7\u00e3o. A lista de IDs de esta\u00e7\u00f5es pode ser encontrada aqui: https://dd.weather.gc.ca/citypage_weather/docs/site_list_towns_en.csv. As informa\u00e7\u00f5es meteorol\u00f3gicas podem ser recuperadas em ingl\u00eas ou franc\u00eas.", + "title": "Environment Canada: localiza\u00e7\u00e3o e idioma do clima" } } } diff --git a/homeassistant/components/epson/translations/pt-BR.json b/homeassistant/components/epson/translations/pt-BR.json index ec60fefab42774..c14278182a5bd5 100644 --- a/homeassistant/components/epson/translations/pt-BR.json +++ b/homeassistant/components/epson/translations/pt-BR.json @@ -1,7 +1,8 @@ { "config": { "error": { - "cannot_connect": "Falha ao conectar" + "cannot_connect": "Falha ao conectar", + "powered_off": "O projetor est\u00e1 ligado? Voc\u00ea precisa ligar o projetor para a configura\u00e7\u00e3o inicial." }, "step": { "user": { diff --git a/homeassistant/components/esphome/translations/pt-BR.json b/homeassistant/components/esphome/translations/pt-BR.json index 6a637c735f7e12..737bc5020af760 100644 --- a/homeassistant/components/esphome/translations/pt-BR.json +++ b/homeassistant/components/esphome/translations/pt-BR.json @@ -8,20 +8,33 @@ "error": { "connection_error": "N\u00e3o \u00e9 poss\u00edvel conectar-se ao ESP. Por favor, verifique se o seu arquivo YAML cont\u00e9m uma linha 'api:'.", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "invalid_psk": "A chave de criptografia de transporte \u00e9 inv\u00e1lida. Certifique-se de que corresponde ao que voc\u00ea tem em sua configura\u00e7\u00e3o", "resolve_error": "N\u00e3o \u00e9 poss\u00edvel resolver o endere\u00e7o do ESP. Se este erro persistir, por favor, defina um endere\u00e7o IP est\u00e1tico: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" }, - "flow_title": "ESPHome: {name}", + "flow_title": "{name}", "step": { "authenticate": { "data": { "password": "Senha" }, - "description": "Por favor, digite a senha que voc\u00ea definiu em sua configura\u00e7\u00e3o." + "description": "Digite a senha definida na configura\u00e7\u00e3o para {name}." }, "discovery_confirm": { "description": "Voc\u00ea quer adicionar o n\u00f3 ESPHome ` {name} ` ao Home Assistant?", "title": "N\u00f3 ESPHome descoberto" }, + "encryption_key": { + "data": { + "noise_psk": "Chave de encripta\u00e7\u00e3o" + }, + "description": "Insira a chave de criptografia que voc\u00ea definiu em sua configura\u00e7\u00e3o para {name} ." + }, + "reauth_confirm": { + "data": { + "noise_psk": "Chave de encripta\u00e7\u00e3o" + }, + "description": "O dispositivo ESPHome {name} ativou a criptografia de transporte ou alterou a chave de criptografia. Insira a chave atualizada." + }, "user": { "data": { "host": "Nome do host", diff --git a/homeassistant/components/evil_genius_labs/translations/pt-BR.json b/homeassistant/components/evil_genius_labs/translations/pt-BR.json index 159cd52c341283..5dda4dc69dc3a7 100644 --- a/homeassistant/components/evil_genius_labs/translations/pt-BR.json +++ b/homeassistant/components/evil_genius_labs/translations/pt-BR.json @@ -2,6 +2,7 @@ "config": { "error": { "cannot_connect": "Falha ao conectar", + "timeout": "Tempo limite para estabelecer conex\u00e3o atingido", "unknown": "Erro inesperado" }, "step": { diff --git a/homeassistant/components/ezviz/translations/pt-BR.json b/homeassistant/components/ezviz/translations/pt-BR.json index 870003bde5d45e..371686bbf98cdf 100644 --- a/homeassistant/components/ezviz/translations/pt-BR.json +++ b/homeassistant/components/ezviz/translations/pt-BR.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured_account": "A conta j\u00e1 foi configurada", + "ezviz_cloud_account_missing": "Conta na nuvem Ezviz ausente. Por favor, reconfigure a conta de nuvem Ezviz", "unknown": "Erro inesperado" }, "error": { @@ -9,25 +10,41 @@ "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido" }, + "flow_title": "{serial}", "step": { "confirm": { "data": { "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "description": "Insira as credenciais RTSP para a c\u00e2mera Ezviz {serial} com IP {ip_address}", + "title": "C\u00e2mera Ezviz descoberta" }, "user": { "data": { "password": "Senha", "url": "URL", "username": "Usu\u00e1rio" - } + }, + "title": "Conecte-se ao Ezviz Cloud" }, "user_custom_url": { "data": { "password": "Senha", "url": "URL", "username": "Usu\u00e1rio" + }, + "description": "Especifique manualmente o URL da sua regi\u00e3o", + "title": "Conecte-se ao URL personalizado do Ezviz" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ffmpeg_arguments": "Argumentos passados para ffmpeg para c\u00e2meras", + "timeout": "Tempo limite da solicita\u00e7\u00e3o (segundos)" } } } diff --git a/homeassistant/components/faa_delays/translations/pt-BR.json b/homeassistant/components/faa_delays/translations/pt-BR.json index 34892b7c476595..87e64d0db17b41 100644 --- a/homeassistant/components/faa_delays/translations/pt-BR.json +++ b/homeassistant/components/faa_delays/translations/pt-BR.json @@ -1,8 +1,21 @@ { "config": { + "abort": { + "already_configured": "Este aeroporto j\u00e1 est\u00e1 configurado." + }, "error": { "cannot_connect": "Falha ao conectar", + "invalid_airport": "O c\u00f3digo do aeroporto n\u00e3o \u00e9 v\u00e1lido", "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "id": "Aeroporto" + }, + "description": "Insira um c\u00f3digo de aeroporto dos EUA no formato IATA", + "title": "Atrasos FAA" + } } } } \ No newline at end of file diff --git a/homeassistant/components/fan/translations/nl.json b/homeassistant/components/fan/translations/nl.json index 07f6bbf8c7b8c0..aa4474a782f31c 100644 --- a/homeassistant/components/fan/translations/nl.json +++ b/homeassistant/components/fan/translations/nl.json @@ -9,6 +9,8 @@ "is_on": "{entity_name} is ingeschakeld" }, "trigger_type": { + "changed_states": "{entity_name} in- of uitgeschakeld", + "toggled": "{entity_name} in- of uitgeschakeld", "turned_off": "{entity_name} uitgeschakeld", "turned_on": "{entity_name} ingeschakeld" } diff --git a/homeassistant/components/fan/translations/pt-BR.json b/homeassistant/components/fan/translations/pt-BR.json index ef519660db800b..97145dd8a60add 100644 --- a/homeassistant/components/fan/translations/pt-BR.json +++ b/homeassistant/components/fan/translations/pt-BR.json @@ -1,12 +1,18 @@ { "device_automation": { + "action_type": { + "turn_off": "Desligar {entity_name}", + "turn_on": "Ligar {entity_name}" + }, "condition_type": { "is_off": "{entity_name} est\u00e1 desligado", "is_on": "{entity_name} est\u00e1 ligado" }, "trigger_type": { "changed_states": "{entity_name} ligado ou desligado", - "toggled": "{entity_name} ligado ou desligado" + "toggled": "{entity_name} ligado ou desligado", + "turned_off": "{entity_name} for desligado", + "turned_on": "{entity_name} for ligado" } }, "state": { diff --git a/homeassistant/components/fireservicerota/translations/pt-BR.json b/homeassistant/components/fireservicerota/translations/pt-BR.json index 2d9c2ad919c9c6..45b55aa5324471 100644 --- a/homeassistant/components/fireservicerota/translations/pt-BR.json +++ b/homeassistant/components/fireservicerota/translations/pt-BR.json @@ -14,11 +14,13 @@ "reauth": { "data": { "password": "Senha" - } + }, + "description": "Os tokens de autentica\u00e7\u00e3o se tornaram inv\u00e1lidos, fa\u00e7a login para recri\u00e1-los." }, "user": { "data": { "password": "Senha", + "url": "Site", "username": "Usu\u00e1rio" } } diff --git a/homeassistant/components/fjaraskupan/translations/pt-BR.json b/homeassistant/components/fjaraskupan/translations/pt-BR.json index d529509749c38b..164c5cde793327 100644 --- a/homeassistant/components/fjaraskupan/translations/pt-BR.json +++ b/homeassistant/components/fjaraskupan/translations/pt-BR.json @@ -1,8 +1,13 @@ { "config": { "abort": { - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "no_devices_found": "Nenhum dispositivo encontrado na rede", "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "step": { + "confirm": { + "description": "Deseja configurar o Fj\u00e4r\u00e5skupan?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/flick_electric/translations/pt-BR.json b/homeassistant/components/flick_electric/translations/pt-BR.json index 2601b89b2a1225..444c72d3d5d3e2 100644 --- a/homeassistant/components/flick_electric/translations/pt-BR.json +++ b/homeassistant/components/flick_electric/translations/pt-BR.json @@ -11,8 +11,8 @@ "step": { "user": { "data": { - "client_id": "ID do cliente (Opcional)", - "client_secret": "Segredo do cliente (Opcional)", + "client_id": "Client ID (Opcional)", + "client_secret": "Client Secret (Opcional)", "password": "Senha", "username": "Usu\u00e1rio" }, diff --git a/homeassistant/components/flipr/translations/pt-BR.json b/homeassistant/components/flipr/translations/pt-BR.json index 8722382b01bd53..e3535fb54ff8ee 100644 --- a/homeassistant/components/flipr/translations/pt-BR.json +++ b/homeassistant/components/flipr/translations/pt-BR.json @@ -6,13 +6,24 @@ "error": { "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "no_flipr_id_found": "Nenhum ID flipr associado \u00e0 sua conta por enquanto. Voc\u00ea deve verificar se est\u00e1 funcionando com o aplicativo m\u00f3vel do Flipr primeiro.", "unknown": "Erro inesperado" }, "step": { + "flipr_id": { + "data": { + "flipr_id": "Flipr ID" + }, + "description": "Escolha seu ID Flipr na lista", + "title": "Escolha seu Flipr" + }, "user": { "data": { + "email": "Email", "password": "Senha" - } + }, + "description": "Conecte-se usando sua conta Flipr.", + "title": "Conecte-se ao Flipr" } } } diff --git a/homeassistant/components/flume/translations/pt-BR.json b/homeassistant/components/flume/translations/pt-BR.json index 17c9f8afe60d1f..a9027c5bfd64d3 100644 --- a/homeassistant/components/flume/translations/pt-BR.json +++ b/homeassistant/components/flume/translations/pt-BR.json @@ -13,7 +13,9 @@ "reauth_confirm": { "data": { "password": "Senha" - } + }, + "description": "A senha para {username} n\u00e3o \u00e9 mais v\u00e1lida.", + "title": "Reautentique sua conta Flume" }, "user": { "data": { diff --git a/homeassistant/components/flunearyou/translations/pt-BR.json b/homeassistant/components/flunearyou/translations/pt-BR.json index 35950f57d3b40f..dc63fa1baf8701 100644 --- a/homeassistant/components/flunearyou/translations/pt-BR.json +++ b/homeassistant/components/flunearyou/translations/pt-BR.json @@ -11,7 +11,9 @@ "data": { "latitude": "Latitude", "longitude": "Longitude" - } + }, + "description": "Monitore relat\u00f3rios baseados em usu\u00e1rio e CDC para um par de coordenadas.", + "title": "Configurar Flue Near You" } } } diff --git a/homeassistant/components/flux_led/translations/pt-BR.json b/homeassistant/components/flux_led/translations/pt-BR.json index 82b12411670f2e..36560315ee7409 100644 --- a/homeassistant/components/flux_led/translations/pt-BR.json +++ b/homeassistant/components/flux_led/translations/pt-BR.json @@ -3,12 +3,12 @@ "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" + "no_devices_found": "Nenhum dispositivo encontrado na rede" }, "error": { "cannot_connect": "Falha ao conectar" }, - "flow_title": "{modelo} {id} ({ipaddr})", + "flow_title": "{model} {id} ({ipaddr})", "step": { "discovery_confirm": { "description": "Deseja configurar {model} {id} ({ipaddr})?" diff --git a/homeassistant/components/forecast_solar/translations/pt-BR.json b/homeassistant/components/forecast_solar/translations/pt-BR.json index aad75b3bed08b1..ad6cca066c4f41 100644 --- a/homeassistant/components/forecast_solar/translations/pt-BR.json +++ b/homeassistant/components/forecast_solar/translations/pt-BR.json @@ -3,10 +3,28 @@ "step": { "user": { "data": { + "azimuth": "Azimute (360\u00b0, 0\u00b0 = Norte, 90\u00b0 = Leste, 180\u00b0 = Sul, 270\u00b0 = Oeste)", + "declination": "Declina\u00e7\u00e3o (0\u00b0 = Horizontal, 90\u00b0 = Vertical)", "latitude": "Latitude", "longitude": "Longitude", + "modules power": "Pot\u00eancia de pico total em Watt de seus m\u00f3dulos solares", "name": "Nome" - } + }, + "description": "Preencha os dados de seus pain\u00e9is solares. Consulte a documenta\u00e7\u00e3o se um campo n\u00e3o estiver claro." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "api_key": "Chave de API Forecast.Solar (opcional)", + "azimuth": "Azimute (360\u00b0, 0\u00b0 = Norte, 90\u00b0 = Leste, 180\u00b0 = Sul, 270\u00b0 = Oeste)", + "damping": "Fator de amortecimento: ajusta os resultados de manh\u00e3 e \u00e0 noite", + "declination": "Declina\u00e7\u00e3o (0\u00b0 = Horizontal, 90\u00b0 = Vertical)", + "modules power": "Pot\u00eancia de pico total em Watt de seus m\u00f3dulos solares" + }, + "description": "Preencha os dados de seus pain\u00e9is solares. Consulte a documenta\u00e7\u00e3o se um campo n\u00e3o estiver claro." } } } diff --git a/homeassistant/components/forked_daapd/translations/pt-BR.json b/homeassistant/components/forked_daapd/translations/pt-BR.json index a40bc1321a17e4..1b768604befe4b 100644 --- a/homeassistant/components/forked_daapd/translations/pt-BR.json +++ b/homeassistant/components/forked_daapd/translations/pt-BR.json @@ -5,13 +5,14 @@ "not_forked_daapd": "O dispositivo n\u00e3o \u00e9 um servidor forked-daapd." }, "error": { + "forbidden": "Incapaz de conectar. Verifique suas permiss\u00f5es de rede forked-daapd.", "unknown_error": "Erro inesperado", "websocket_not_enabled": "websocket do servidor forked-daapd n\u00e3o ativado.", "wrong_host_or_port": "N\u00e3o foi poss\u00edvel conectar. Por favor, verifique o endere\u00e7o e a porta.", "wrong_password": "Senha incorreta.", "wrong_server_type": "A integra\u00e7\u00e3o forked-daapd requer um servidor forked-daapd com vers\u00e3o >= 27.0." }, - "flow_title": "servidor forked-daapd: {name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/foscam/translations/pt-BR.json b/homeassistant/components/foscam/translations/pt-BR.json index 8037db16e534a4..b33dce1e6feac9 100644 --- a/homeassistant/components/foscam/translations/pt-BR.json +++ b/homeassistant/components/foscam/translations/pt-BR.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "invalid_response": "Resposta inv\u00e1lida do dispositivo", "unknown": "Erro inesperado" }, "step": { @@ -14,9 +15,12 @@ "host": "Nome do host", "password": "Senha", "port": "Porta", + "rtsp_port": "porta RTSP", + "stream": "Stream", "username": "Usu\u00e1rio" } } } - } + }, + "title": "Foscam" } \ No newline at end of file diff --git a/homeassistant/components/freebox/translations/el.json b/homeassistant/components/freebox/translations/el.json index 42fc274e0a816e..aa0a9b037371e6 100644 --- a/homeassistant/components/freebox/translations/el.json +++ b/homeassistant/components/freebox/translations/el.json @@ -7,6 +7,9 @@ "link": { "description": "\u039a\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \"\u03a5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae\" \u03ba\u03b1\u03b9, \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03b1\u03b3\u03b3\u03af\u03be\u03c4\u03b5 \u03c4\u03bf \u03b4\u03b5\u03be\u03af \u03b2\u03ad\u03bb\u03bf\u03c2 \u03c3\u03c4\u03bf \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c7\u03c9\u03c1\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Freebox \u03c3\u03c4\u03bf Home Assistant.\n\n![\u0398\u03ad\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03ba\u03bf\u03c5\u03bc\u03c0\u03b9\u03bf\u03cd \u03c3\u03c4\u03bf \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae](/static/images/config_freebox.png)", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae Freebox" + }, + "user": { + "title": "Freebox" } } } diff --git a/homeassistant/components/freebox/translations/pt-BR.json b/homeassistant/components/freebox/translations/pt-BR.json index 1e898e15ce0558..021ab5c902ad58 100644 --- a/homeassistant/components/freebox/translations/pt-BR.json +++ b/homeassistant/components/freebox/translations/pt-BR.json @@ -5,14 +5,20 @@ }, "error": { "cannot_connect": "Falha ao conectar", + "register_failed": "Falha ao registrar, tente novamente", "unknown": "Erro inesperado" }, "step": { + "link": { + "description": "Clique em \"Enviar\" e toque na seta para a direita no roteador para registrar o Freebox com o Home Assistant.\n\n![Localiza\u00e7\u00e3o do bot\u00e3o no roteador](/static/images/config_freebox.png)", + "title": "Link roteador Freebox" + }, "user": { "data": { "host": "Nome do host", "port": "Porta" - } + }, + "title": "Freebox" } } } diff --git a/homeassistant/components/freedompro/translations/pt-BR.json b/homeassistant/components/freedompro/translations/pt-BR.json index 71a36bb83590fe..2e20c6b5da9f73 100644 --- a/homeassistant/components/freedompro/translations/pt-BR.json +++ b/homeassistant/components/freedompro/translations/pt-BR.json @@ -11,7 +11,9 @@ "user": { "data": { "api_key": "Chave da API" - } + }, + "description": "Insira a chave de API obtida em https://home.freedompro.eu", + "title": "Chave da API Freedompro" } } } diff --git a/homeassistant/components/fritz/translations/pt-BR.json b/homeassistant/components/fritz/translations/pt-BR.json index 9076a635244ce8..a28f063ab6d65d 100644 --- a/homeassistant/components/fritz/translations/pt-BR.json +++ b/homeassistant/components/fritz/translations/pt-BR.json @@ -12,18 +12,23 @@ "connection_error": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, + "flow_title": "{name}", "step": { "confirm": { "data": { "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "description": "Descoberto FRITZ!Box: {name} \n\n Configure as Ferramentas do FRITZ!Box para controlar o seu {name}", + "title": "Configurar as Ferramentas do FRITZ!Box" }, "reauth_confirm": { "data": { "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "description": "Atualize as credenciais do FRITZ!Box Tools para: {host} . \n\n O FRITZ!Box Tools n\u00e3o consegue iniciar sess\u00e3o no seu FRITZ!Box.", + "title": "Atualizando as Ferramentas do FRITZ!Box - credenciais" }, "start_config": { "data": { @@ -31,7 +36,9 @@ "password": "Senha", "port": "Porta", "username": "Usu\u00e1rio" - } + }, + "description": "Configure as Ferramentas do FRITZ!Box para controlar o seu FRITZ!Box.\n M\u00ednimo necess\u00e1rio: nome de usu\u00e1rio, senha.", + "title": "Configurar as Ferramentas do FRITZ!Box - obrigat\u00f3rio" }, "user": { "data": { @@ -39,6 +46,17 @@ "password": "Senha", "port": "Porta", "username": "Usu\u00e1rio" + }, + "description": "Configure as Ferramentas do FRITZ!Box para controlar o seu FRITZ!Box.\nM\u00ednimo necess\u00e1rio: nome de usu\u00e1rio, senha.", + "title": "Configurar as Ferramentas do FRITZ!Box" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "consider_home": "Segundos para considerar um dispositivo em 'casa'" } } } diff --git a/homeassistant/components/fritzbox/translations/pt-BR.json b/homeassistant/components/fritzbox/translations/pt-BR.json index 7693e15a9ecc6a..3e3884cbc41cbf 100644 --- a/homeassistant/components/fritzbox/translations/pt-BR.json +++ b/homeassistant/components/fritzbox/translations/pt-BR.json @@ -3,12 +3,14 @@ "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "no_devices_found": "Nenhum dispositivo encontrado na rede", + "not_supported": "Conectado ao AVM FRITZ!Box, mas n\u00e3o consegue controlar os dispositivos Smart Home.", "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, + "flow_title": "{name}", "step": { "confirm": { "data": { @@ -21,14 +23,16 @@ "data": { "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "description": "Atualize suas informa\u00e7\u00f5es de login para {name}." }, "user": { "data": { "host": "Nome do host", "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "description": "Insira as informa\u00e7\u00f5es do seu AVM FRITZ!Box" } } } diff --git a/homeassistant/components/fritzbox_callmonitor/translations/pt-BR.json b/homeassistant/components/fritzbox_callmonitor/translations/pt-BR.json index 7bfce93a5712e9..55919515397381 100644 --- a/homeassistant/components/fritzbox_callmonitor/translations/pt-BR.json +++ b/homeassistant/components/fritzbox_callmonitor/translations/pt-BR.json @@ -2,12 +2,19 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" + "insufficient_permissions": "O usu\u00e1rio n\u00e3o tem permiss\u00f5es suficientes para acessar as configura\u00e7\u00f5es do AVM FRITZ!Box e suas listas telef\u00f4nicas.", + "no_devices_found": "Nenhum dispositivo encontrado na rede" }, "error": { "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, + "flow_title": "{name}", "step": { + "phonebook": { + "data": { + "phonebook": "Lista telef\u00f4nica" + } + }, "user": { "data": { "host": "Nome do host", @@ -17,5 +24,18 @@ } } } + }, + "options": { + "error": { + "malformed_prefixes": "Os prefixos est\u00e3o malformados, verifique sua formata\u00e7\u00e3o." + }, + "step": { + "init": { + "data": { + "prefixes": "Prefixos (lista separada por v\u00edrgulas)" + }, + "title": "Configurar prefixos" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/fronius/translations/pt-BR.json b/homeassistant/components/fronius/translations/pt-BR.json index 70f90c5b5f4267..da83db69451f0d 100644 --- a/homeassistant/components/fronius/translations/pt-BR.json +++ b/homeassistant/components/fronius/translations/pt-BR.json @@ -16,7 +16,9 @@ "user": { "data": { "host": "Nome do host" - } + }, + "description": "Configure o endere\u00e7o IP ou nome de host local do seu dispositivo Fronius.", + "title": "Fronius SolarNet" } } } diff --git a/homeassistant/components/garages_amsterdam/translations/pt-BR.json b/homeassistant/components/garages_amsterdam/translations/pt-BR.json index 4b01a4755c38d0..ea66c718a27f1b 100644 --- a/homeassistant/components/garages_amsterdam/translations/pt-BR.json +++ b/homeassistant/components/garages_amsterdam/translations/pt-BR.json @@ -4,6 +4,15 @@ "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "cannot_connect": "Falha ao conectar", "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "garage_name": "Nome da garagem" + }, + "title": "Escolha uma garagem para monitorar" + } } - } + }, + "title": "Garages Amsterdam" } \ No newline at end of file diff --git a/homeassistant/components/geofency/translations/ja.json b/homeassistant/components/geofency/translations/ja.json index ba80a1979948ad..e653cb99d430c9 100644 --- a/homeassistant/components/geofency/translations/ja.json +++ b/homeassistant/components/geofency/translations/ja.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Home Assistant Cloud\u306b\u63a5\u7d9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002", "single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002", "webhook_not_internet_accessible": "Webhook\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u53d7\u4fe1\u3059\u308b\u306b\u306f\u3001Home Assistant\u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u306b\u3001\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u304b\u3089\u30a2\u30af\u30bb\u30b9\u3067\u304d\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002" }, diff --git a/homeassistant/components/geofency/translations/nl.json b/homeassistant/components/geofency/translations/nl.json index 59ed1cf6b5bf1b..30a2acfdaddc80 100644 --- a/homeassistant/components/geofency/translations/nl.json +++ b/homeassistant/components/geofency/translations/nl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Niet verbonden met Home Assistant Cloud.", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." }, diff --git a/homeassistant/components/geofency/translations/pt-BR.json b/homeassistant/components/geofency/translations/pt-BR.json index 95ac686e86e79c..226c388532e10a 100644 --- a/homeassistant/components/geofency/translations/pt-BR.json +++ b/homeassistant/components/geofency/translations/pt-BR.json @@ -2,7 +2,8 @@ "config": { "abort": { "cloud_not_connected": "N\u00e3o conectado ao Home Assistant Cloud.", - "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", + "webhook_not_internet_accessible": "Sua inst\u00e2ncia do Home Assistant precisa estar acess\u00edvel pela Internet para receber mensagens de webhook." }, "create_entry": { "default": "Para enviar eventos para o Home Assistant, voc\u00ea precisar\u00e1 configurar o recurso webhook no Geofency. \n\n Preencha as seguintes informa\u00e7\u00f5es: \n\n - URL: ` {webhook_url} ` \n - M\u00e9todo: POST \n\n Veja [a documenta\u00e7\u00e3o] ( {docs_url} ) para mais detalhes." diff --git a/homeassistant/components/geofency/translations/uk.json b/homeassistant/components/geofency/translations/uk.json index 54a14afb764d9d..b38d3b66c7cd1c 100644 --- a/homeassistant/components/geofency/translations/uk.json +++ b/homeassistant/components/geofency/translations/uk.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e.", + "cloud_not_connected": "\u041d\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0434\u043e Home Assistant Cloud.", + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f.", "webhook_not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u043f\u043e\u0432\u0438\u043d\u0435\u043d \u0431\u0443\u0442\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0439 \u0437 \u0406\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443 \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f Webhook-\u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u044c." }, "create_entry": { diff --git a/homeassistant/components/geonetnz_volcano/translations/pt-BR.json b/homeassistant/components/geonetnz_volcano/translations/pt-BR.json index 6f79c4865191e5..5faa4ea054a91b 100644 --- a/homeassistant/components/geonetnz_volcano/translations/pt-BR.json +++ b/homeassistant/components/geonetnz_volcano/translations/pt-BR.json @@ -7,7 +7,8 @@ "user": { "data": { "radius": "Raio" - } + }, + "title": "Preencha os dados do filtro." } } } diff --git a/homeassistant/components/gios/translations/el.json b/homeassistant/components/gios/translations/el.json index c6613936401497..b8d85b6960bbb7 100644 --- a/homeassistant/components/gios/translations/el.json +++ b/homeassistant/components/gios/translations/el.json @@ -1,5 +1,9 @@ { "config": { + "error": { + "invalid_sensors_data": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b1 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03c9\u03bd \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03c3\u03c4\u03b1\u03b8\u03bc\u03cc \u03bc\u03ad\u03c4\u03c1\u03b7\u03c3\u03b7\u03c2.", + "wrong_station_id": "\u03a4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03bf\u03c5 \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd \u03bc\u03ad\u03c4\u03c1\u03b7\u03c3\u03b7\u03c2 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c9\u03c3\u03c4\u03cc." + }, "step": { "user": { "data": { diff --git a/homeassistant/components/gios/translations/pt-BR.json b/homeassistant/components/gios/translations/pt-BR.json index 2228a8031ac8ae..39472c3b420098 100644 --- a/homeassistant/components/gios/translations/pt-BR.json +++ b/homeassistant/components/gios/translations/pt-BR.json @@ -4,15 +4,24 @@ "already_configured": "Localiza\u00e7\u00e3o j\u00e1 est\u00e1 configurada" }, "error": { - "cannot_connect": "Falha ao conectar" + "cannot_connect": "Falha ao conectar", + "invalid_sensors_data": "Dados de sensores inv\u00e1lidos para esta esta\u00e7\u00e3o de medi\u00e7\u00e3o.", + "wrong_station_id": "O ID da esta\u00e7\u00e3o de medi\u00e7\u00e3o n\u00e3o est\u00e1 correto." }, "step": { "user": { "data": { - "name": "Nome" + "name": "Nome", + "station_id": "ID da esta\u00e7\u00e3o de medi\u00e7\u00e3o" }, + "description": "Configurar a integra\u00e7\u00e3o da qualidade do ar GIO\u015a (Polish Chief Inspectorate Of Environmental Protection). Se precisar de ajuda com a configura\u00e7\u00e3o, d\u00ea uma olhada em https://www.home-assistant.io/integrations/gios", "title": "GIO\u015a (Inspetor-Chefe Polon\u00eas de Prote\u00e7\u00e3o Ambiental)" } } + }, + "system_health": { + "info": { + "can_reach_server": "Alcance o servidor GIO\u015a" + } } } \ No newline at end of file diff --git a/homeassistant/components/github/translations/nl.json b/homeassistant/components/github/translations/nl.json index 8dbc3289c81ca4..f7cb2ac22e407e 100644 --- a/homeassistant/components/github/translations/nl.json +++ b/homeassistant/components/github/translations/nl.json @@ -1,7 +1,11 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd" + "already_configured": "Service is al geconfigureerd", + "could_not_register": "Kan integratie niet met GitHub registreren" + }, + "progress": { + "wait_for_device": "1. Open {url} \n2.Plak de volgende sleutel om de integratie te autoriseren: \n```\n{code}\n```\n" }, "step": { "repositories": { diff --git a/homeassistant/components/glances/translations/el.json b/homeassistant/components/glances/translations/el.json new file mode 100644 index 00000000000000..fd43f179032fdf --- /dev/null +++ b/homeassistant/components/glances/translations/el.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "wrong_version": "\u0397 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 (\u03bc\u03cc\u03bd\u03bf 2 \u03ae 3)" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/glances/translations/pt-BR.json b/homeassistant/components/glances/translations/pt-BR.json index 1953aa13e2e859..d081c897d3873e 100644 --- a/homeassistant/components/glances/translations/pt-BR.json +++ b/homeassistant/components/glances/translations/pt-BR.json @@ -4,7 +4,8 @@ "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "error": { - "cannot_connect": "Falha ao conectar" + "cannot_connect": "Falha ao conectar", + "wrong_version": "Vers\u00e3o n\u00e3o suportada (somente 2 ou 3)" }, "step": { "user": { @@ -15,8 +16,10 @@ "port": "Porta", "ssl": "Usar um certificado SSL", "username": "Usu\u00e1rio", - "verify_ssl": "Verifique o certificado SSL" - } + "verify_ssl": "Verifique o certificado SSL", + "version": "Vers\u00e3o da API Glances (2 ou 3)" + }, + "title": "Configura\u00e7\u00e3o Glances" } } }, @@ -25,7 +28,8 @@ "init": { "data": { "scan_interval": "Frequ\u00eancia de atualiza\u00e7\u00e3o" - } + }, + "description": "Configure op\u00e7\u00f5es para Glances" } } } diff --git a/homeassistant/components/goalzero/translations/pt-BR.json b/homeassistant/components/goalzero/translations/pt-BR.json index 81137fe0bdca2e..7ecea696702b6f 100644 --- a/homeassistant/components/goalzero/translations/pt-BR.json +++ b/homeassistant/components/goalzero/translations/pt-BR.json @@ -11,11 +11,17 @@ "unknown": "Erro inesperado" }, "step": { + "confirm_discovery": { + "description": "A reserva de DHCP em seu roteador \u00e9 recomendada. Se n\u00e3o estiver configurado, o dispositivo pode ficar indispon\u00edvel at\u00e9 que o Home Assistant detecte o novo endere\u00e7o IP. Consulte o manual do usu\u00e1rio do seu roteador.", + "title": "Gol Zero Yeti" + }, "user": { "data": { "host": "Nome do host", "name": "Nome" - } + }, + "description": "Primeiro, voc\u00ea precisa baixar o aplicativo Goal Zero: https://www.goalzero.com/product-features/yeti-app/ \n\n Siga as instru\u00e7\u00f5es para conectar seu Yeti \u00e0 sua rede Wi-fi. A reserva de DHCP em seu roteador \u00e9 recomendada. Se n\u00e3o estiver configurado, o dispositivo pode ficar indispon\u00edvel at\u00e9 que o Home Assistant detecte o novo endere\u00e7o IP. Consulte o manual do usu\u00e1rio do seu roteador.", + "title": "Gol Zero Yeti" } } } diff --git a/homeassistant/components/gogogate2/translations/pt-BR.json b/homeassistant/components/gogogate2/translations/pt-BR.json index fd074fcc0f81ea..99b63826b307eb 100644 --- a/homeassistant/components/gogogate2/translations/pt-BR.json +++ b/homeassistant/components/gogogate2/translations/pt-BR.json @@ -7,6 +7,7 @@ "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, + "flow_title": "{device} ({ip_address})", "step": { "user": { "data": { @@ -15,7 +16,7 @@ "username": "Usu\u00e1rio" }, "description": "Forne\u00e7a as informa\u00e7\u00f5es necess\u00e1rias abaixo.", - "title": "Configurar GogoGate2" + "title": "Configurar Gogogate2 ou ismartgate" } } } diff --git a/homeassistant/components/goodwe/translations/nl.json b/homeassistant/components/goodwe/translations/nl.json index d648ff4e8e8fd1..8986006fdeb254 100644 --- a/homeassistant/components/goodwe/translations/nl.json +++ b/homeassistant/components/goodwe/translations/nl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom is al aan de gang" }, "error": { diff --git a/homeassistant/components/google_travel_time/translations/pt-BR.json b/homeassistant/components/google_travel_time/translations/pt-BR.json index 9365f5ac690cd2..b08969910975bc 100644 --- a/homeassistant/components/google_travel_time/translations/pt-BR.json +++ b/homeassistant/components/google_travel_time/translations/pt-BR.json @@ -10,9 +10,30 @@ "user": { "data": { "api_key": "Chave da API", - "name": "Nome" - } + "destination": "Destino", + "name": "Nome", + "origin": "Origem" + }, + "description": "Ao especificar a origem e o destino, voc\u00ea pode fornecer um ou mais locais separados pelo caractere de barra vertical, na forma de um endere\u00e7o, coordenadas de latitude/longitude ou um ID de local do Google. Ao especificar o local usando um ID de local do Google, o ID deve ser prefixado com `place_id:`." } } - } + }, + "options": { + "step": { + "init": { + "data": { + "avoid": "Evite", + "language": "Idioma", + "mode": "Modo de viagem", + "time": "Tempo", + "time_type": "Tipo de tempo", + "transit_mode": "Modo de tr\u00e2nsito", + "transit_routing_preference": "Prefer\u00eancia de rota de tr\u00e2nsito", + "units": "Unidades" + }, + "description": "Opcionalmente, voc\u00ea pode especificar um hor\u00e1rio de partida ou um hor\u00e1rio de chegada. Se especificar um hor\u00e1rio de partida, voc\u00ea pode inserir `now`, um timestamp Unix ou uma string de 24 horas como `08:00:00`. Se especificar uma hora de chegada, voc\u00ea pode usar um timestamp Unix ou uma string de 24 horas como `08:00:00`now`" + } + } + }, + "title": "Tempo de viagem do Google Maps" } \ No newline at end of file diff --git a/homeassistant/components/gpslogger/translations/ja.json b/homeassistant/components/gpslogger/translations/ja.json index c04d58020d60a0..4674c074763db9 100644 --- a/homeassistant/components/gpslogger/translations/ja.json +++ b/homeassistant/components/gpslogger/translations/ja.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Home Assistant Cloud\u306b\u63a5\u7d9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002", "single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002", "webhook_not_internet_accessible": "Webhook\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u53d7\u4fe1\u3059\u308b\u306b\u306f\u3001Home Assistant\u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u306b\u3001\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u304b\u3089\u30a2\u30af\u30bb\u30b9\u3067\u304d\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002" }, diff --git a/homeassistant/components/gpslogger/translations/nl.json b/homeassistant/components/gpslogger/translations/nl.json index d90b648760db9d..26bfba4eaea6a5 100644 --- a/homeassistant/components/gpslogger/translations/nl.json +++ b/homeassistant/components/gpslogger/translations/nl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Niet verbonden met Home Assistant Cloud.", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." }, diff --git a/homeassistant/components/gpslogger/translations/pt-BR.json b/homeassistant/components/gpslogger/translations/pt-BR.json index 8ef272f06702d4..a3204d7e5542cd 100644 --- a/homeassistant/components/gpslogger/translations/pt-BR.json +++ b/homeassistant/components/gpslogger/translations/pt-BR.json @@ -2,7 +2,8 @@ "config": { "abort": { "cloud_not_connected": "N\u00e3o conectado ao Home Assistant Cloud.", - "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", + "webhook_not_internet_accessible": "Sua inst\u00e2ncia do Home Assistant precisa estar acess\u00edvel pela Internet para receber mensagens de webhook." }, "create_entry": { "default": "Para enviar eventos para o Home Assistant, voc\u00ea precisar\u00e1 configurar o recurso webhook no GPSLogger. \n\n Preencha as seguintes informa\u00e7\u00f5es: \n\n - URL: ` {webhook_url} ` \n - M\u00e9todo: POST \n\n Veja [a documenta\u00e7\u00e3o] ( {docs_url} ) para mais detalhes." diff --git a/homeassistant/components/gpslogger/translations/uk.json b/homeassistant/components/gpslogger/translations/uk.json index 5b0b6305cdb638..25cae99e3b024f 100644 --- a/homeassistant/components/gpslogger/translations/uk.json +++ b/homeassistant/components/gpslogger/translations/uk.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e.", + "cloud_not_connected": "\u041d\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0434\u043e Home Assistant Cloud.", + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f.", "webhook_not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u043f\u043e\u0432\u0438\u043d\u0435\u043d \u0431\u0443\u0442\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0439 \u0437 \u0406\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443 \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f Webhook-\u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u044c." }, "create_entry": { diff --git a/homeassistant/components/gree/translations/pt-BR.json b/homeassistant/components/gree/translations/pt-BR.json index d5efbb90261324..1778d39a7d0829 100644 --- a/homeassistant/components/gree/translations/pt-BR.json +++ b/homeassistant/components/gree/translations/pt-BR.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "no_devices_found": "Nenhum dispositivo encontrado na rede", "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "step": { diff --git a/homeassistant/components/gree/translations/uk.json b/homeassistant/components/gree/translations/uk.json index 292861e9129dbd..5c2489c2a18ab7 100644 --- a/homeassistant/components/gree/translations/uk.json +++ b/homeassistant/components/gree/translations/uk.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\u041f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456 \u0432 \u043c\u0435\u0440\u0435\u0436\u0456.", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "step": { "confirm": { diff --git a/homeassistant/components/growatt_server/translations/pt-BR.json b/homeassistant/components/growatt_server/translations/pt-BR.json index e956f89d381c6a..29222d32df4a0b 100644 --- a/homeassistant/components/growatt_server/translations/pt-BR.json +++ b/homeassistant/components/growatt_server/translations/pt-BR.json @@ -1,17 +1,28 @@ { "config": { + "abort": { + "no_plants": "Nenhuma planta foi encontrada nesta conta" + }, "error": { "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, "step": { + "plant": { + "data": { + "plant_id": "Planta" + }, + "title": "Selecione sua planta" + }, "user": { "data": { "name": "Nome", "password": "Senha", "url": "URL", "username": "Usu\u00e1rio" - } + }, + "title": "Insira suas informa\u00e7\u00f5es do Growatt" } } - } + }, + "title": "Servidor Growatt" } \ No newline at end of file diff --git a/homeassistant/components/guardian/translations/pt-BR.json b/homeassistant/components/guardian/translations/pt-BR.json index 14615cff00234f..f0996e6b84ec73 100644 --- a/homeassistant/components/guardian/translations/pt-BR.json +++ b/homeassistant/components/guardian/translations/pt-BR.json @@ -6,11 +6,18 @@ "cannot_connect": "Falha ao conectar" }, "step": { + "discovery_confirm": { + "description": "Deseja configurar este dispositivo Guardian?" + }, "user": { "data": { "ip_address": "Endere\u00e7o IP", "port": "Porta" - } + }, + "description": "Configure um dispositivo local Elexa Guardian." + }, + "zeroconf_confirm": { + "description": "Deseja configurar este dispositivo Guardian?" } } } diff --git a/homeassistant/components/habitica/translations/pt-BR.json b/homeassistant/components/habitica/translations/pt-BR.json index f8ca0b401878b7..cbdb6e3453fc3e 100644 --- a/homeassistant/components/habitica/translations/pt-BR.json +++ b/homeassistant/components/habitica/translations/pt-BR.json @@ -8,9 +8,13 @@ "user": { "data": { "api_key": "Chave da API", + "api_user": "ID de usu\u00e1rio da API do Habitica", + "name": "Substitua o nome de usu\u00e1rio do Habitica. Ser\u00e1 usado para chamadas de servi\u00e7o", "url": "URL" - } + }, + "description": "Conecte seu perfil do Habitica para permitir o monitoramento do perfil e das tarefas do seu usu\u00e1rio. Observe que api_id e api_key devem ser obtidos em https://habitica.com/user/settings/api" } } - } + }, + "title": "Habitica" } \ No newline at end of file diff --git a/homeassistant/components/hangouts/translations/pt-BR.json b/homeassistant/components/hangouts/translations/pt-BR.json index bcf50b5d3da0a7..c60d9d8ec47023 100644 --- a/homeassistant/components/hangouts/translations/pt-BR.json +++ b/homeassistant/components/hangouts/translations/pt-BR.json @@ -12,7 +12,7 @@ "step": { "2fa": { "data": { - "2fa": "Pin 2FA" + "2fa": "C\u00f3digo 2FA" }, "description": "Vazio", "title": "Autentica\u00e7\u00e3o de 2 Fatores" @@ -20,7 +20,7 @@ "user": { "data": { "authorization_code": "C\u00f3digo de Autoriza\u00e7\u00e3o (requerido para autentica\u00e7\u00e3o manual)", - "email": "Endere\u00e7o de e-mail", + "email": "Email", "password": "Senha" }, "description": "Vazio", diff --git a/homeassistant/components/harmony/translations/pt-BR.json b/homeassistant/components/harmony/translations/pt-BR.json index 7686665e6f68be..4b3f264ddebab7 100644 --- a/homeassistant/components/harmony/translations/pt-BR.json +++ b/homeassistant/components/harmony/translations/pt-BR.json @@ -7,7 +7,7 @@ "cannot_connect": "Falha ao conectar", "unknown": "Erro inesperado" }, - "flow_title": "Logitech Harmony Hub {name}", + "flow_title": "{name}", "step": { "link": { "description": "Voc\u00ea quer configurar o {name} ({host})?", @@ -28,7 +28,8 @@ "data": { "activity": "A atividade padr\u00e3o a ser executada quando nenhuma for especificada.", "delay_secs": "O atraso entre o envio de comandos." - } + }, + "description": "Ajustar as op\u00e7\u00f5es do Harmony Hub" } } } diff --git a/homeassistant/components/hassio/translations/pt-BR.json b/homeassistant/components/hassio/translations/pt-BR.json new file mode 100644 index 00000000000000..b157725600d5a2 --- /dev/null +++ b/homeassistant/components/hassio/translations/pt-BR.json @@ -0,0 +1,18 @@ +{ + "system_health": { + "info": { + "board": "Borda", + "disk_total": "Total do disco", + "disk_used": "Disco usado", + "docker_version": "Vers\u00e3o do Docker", + "healthy": "Saud\u00e1vel", + "host_os": "Sistema Operacional Host", + "installed_addons": "Add-ons instalados", + "supervisor_api": "API do supervisor", + "supervisor_version": "Vers\u00e3o do Supervisor", + "supported": "Suportado", + "update_channel": "Atualizar canal", + "version_api": "API de vers\u00e3o" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/heos/translations/uk.json b/homeassistant/components/heos/translations/uk.json index c0a5fdf04bf94f..8ea94d7a04504e 100644 --- a/homeassistant/components/heos/translations/uk.json +++ b/homeassistant/components/heos/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "error": { "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" diff --git a/homeassistant/components/hisense_aehw4a1/translations/pt-BR.json b/homeassistant/components/hisense_aehw4a1/translations/pt-BR.json index d529509749c38b..1b98cc41a7ae6d 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/pt-BR.json +++ b/homeassistant/components/hisense_aehw4a1/translations/pt-BR.json @@ -1,8 +1,13 @@ { "config": { "abort": { - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "no_devices_found": "Nenhum dispositivo encontrado na rede", "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "step": { + "confirm": { + "description": "Deseja configurar o Hisense AEH-W4A1?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/hisense_aehw4a1/translations/uk.json b/homeassistant/components/hisense_aehw4a1/translations/uk.json index 900882513d5128..d7da075345aee9 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/uk.json +++ b/homeassistant/components/hisense_aehw4a1/translations/uk.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\u041f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456 \u0432 \u043c\u0435\u0440\u0435\u0436\u0456.", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "step": { "confirm": { diff --git a/homeassistant/components/hive/translations/pt-BR.json b/homeassistant/components/hive/translations/pt-BR.json index ef6f9993b115fa..5f5f7e857c2f43 100644 --- a/homeassistant/components/hive/translations/pt-BR.json +++ b/homeassistant/components/hive/translations/pt-BR.json @@ -2,23 +2,51 @@ "config": { "abort": { "already_configured": "A conta j\u00e1 foi configurada", - "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", + "unknown_entry": "N\u00e3o foi poss\u00edvel encontrar a entrada existente." }, "error": { + "invalid_code": "Falha ao entrar no Hive. Seu c\u00f3digo de autentica\u00e7\u00e3o de dois fatores estava incorreto.", + "invalid_password": "Falha ao entrar no Hive. Senha incorreta. Por favor tente novamente.", + "invalid_username": "Falha ao entrar no Hive. Seu endere\u00e7o de e-mail n\u00e3o \u00e9 reconhecido.", + "no_internet_available": "\u00c9 necess\u00e1ria uma conex\u00e3o com a Internet para se conectar ao Hive.", "unknown": "Erro inesperado" }, "step": { + "2fa": { + "data": { + "2fa": "C\u00f3digo de dois fatores" + }, + "description": "Digite seu c\u00f3digo de autentica\u00e7\u00e3o Hive. \n\n Insira o c\u00f3digo 0000 para solicitar outro c\u00f3digo.", + "title": "Autentica\u00e7\u00e3o de dois fatores do Hive." + }, "reauth": { "data": { "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "description": "Insira novamente suas informa\u00e7\u00f5es de login do Hive.", + "title": "Login do Hive" }, "user": { "data": { "password": "Senha", + "scan_interval": "Intervalo de escaneamento (segundos)", "username": "Usu\u00e1rio" - } + }, + "description": "Insira suas informa\u00e7\u00f5es de login e configura\u00e7\u00e3o do Hive.", + "title": "Login do Hive" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Intervalo de escaneamento (segundos)" + }, + "description": "Atualize o intervalo de varredura para pesquisar dados com mais frequ\u00eancia.", + "title": "Op\u00e7\u00f5es para o Hive" } } } diff --git a/homeassistant/components/home_connect/translations/pt-BR.json b/homeassistant/components/home_connect/translations/pt-BR.json index ea479059ebbf1c..54cd4aece0bb2b 100644 --- a/homeassistant/components/home_connect/translations/pt-BR.json +++ b/homeassistant/components/home_connect/translations/pt-BR.json @@ -6,6 +6,11 @@ }, "create_entry": { "default": "Autenticado com sucesso" + }, + "step": { + "pick_implementation": { + "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" + } } } } \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/pt-BR.json b/homeassistant/components/home_plus_control/translations/pt-BR.json index 85e6cfdec61da8..3b7340be7c7e8a 100644 --- a/homeassistant/components/home_plus_control/translations/pt-BR.json +++ b/homeassistant/components/home_plus_control/translations/pt-BR.json @@ -10,6 +10,12 @@ }, "create_entry": { "default": "Autenticado com sucesso" + }, + "step": { + "pick_implementation": { + "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" + } } - } + }, + "title": "Legrand Home+ Control" } \ No newline at end of file diff --git a/homeassistant/components/homeassistant/translations/pt-BR.json b/homeassistant/components/homeassistant/translations/pt-BR.json new file mode 100644 index 00000000000000..f30a1775e0eb32 --- /dev/null +++ b/homeassistant/components/homeassistant/translations/pt-BR.json @@ -0,0 +1,18 @@ +{ + "system_health": { + "info": { + "arch": "Arquitetura da CPU", + "dev": "Desenvolvimento", + "docker": "Docker", + "hassio": "Supervisor", + "installation_type": "Tipo de instala\u00e7\u00e3o", + "os_name": "Fam\u00edlia de sistemas operacionais", + "os_version": "Vers\u00e3o do sistema operacional", + "python_version": "Vers\u00e3o do Python", + "timezone": "Fuso hor\u00e1rio", + "user": "Usu\u00e1rio", + "version": "Vers\u00e3o", + "virtualenv": "Ambiente Virtual" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/nl.json b/homeassistant/components/homekit/translations/nl.json index 4b0a4a64a71b00..c8b3c037c0e3e2 100644 --- a/homeassistant/components/homekit/translations/nl.json +++ b/homeassistant/components/homekit/translations/nl.json @@ -67,6 +67,7 @@ "data": { "domains": "Domeinen om op te nemen", "include_domains": "Domeinen om op te nemen", + "include_exclude_mode": "Inclusiemodus", "mode": "modus" }, "description": "HomeKit kan worden geconfigureerd om een brug of een enkel accessoire te tonen. In de accessoiremodus kan slechts \u00e9\u00e9n entiteit worden gebruikt. De accessoiremodus is vereist om mediaspelers met de tv-apparaatklasse correct te laten werken. Entiteiten in de \"Op te nemen domeinen\" zullen worden blootgesteld aan HomeKit. U kunt op het volgende scherm selecteren welke entiteiten u wilt opnemen of uitsluiten van deze lijst.", diff --git a/homeassistant/components/homekit/translations/pt-BR.json b/homeassistant/components/homekit/translations/pt-BR.json index 1c2e3094ec553f..ad8aab120f4e2c 100644 --- a/homeassistant/components/homekit/translations/pt-BR.json +++ b/homeassistant/components/homekit/translations/pt-BR.json @@ -1,4 +1,22 @@ { + "config": { + "abort": { + "port_name_in_use": "Um acess\u00f3rio ou ponte com o mesmo nome ou porta j\u00e1 est\u00e1 configurado." + }, + "step": { + "pairing": { + "description": "Para concluir o emparelhamento, siga as instru\u00e7\u00f5es em \"Emparelhamento do HomeKit\" > \"Notifica\u00e7\u00f5es\".", + "title": "Emparelhar HomeKit" + }, + "user": { + "data": { + "include_domains": "Dom\u00ednios para incluir" + }, + "description": "Escolha os dom\u00ednios a serem inclu\u00eddos. Todas as entidades apoiadas no dom\u00ednio ser\u00e3o inclu\u00eddas, exceto para entidades categorizadas. Uma inst\u00e2ncia separada do HomeKit no modo acess\u00f3rio ser\u00e1 criada para cada leitor de m\u00eddia de TV, controle remoto, bloqueio e c\u00e2mera baseados em atividades.", + "title": "Selecione os dom\u00ednios a serem inclu\u00eddos" + } + } + }, "options": { "step": { "accessory": { @@ -8,10 +26,20 @@ "title": "Selecione a entidade para o acess\u00f3rio" }, "advanced": { + "data": { + "auto_start": "Autostart (desabilite se voc\u00ea estiver chamando o servi\u00e7o homekit.start manualmente)", + "devices": "Dispositivos (gatilhos)" + }, + "description": "Os interruptores program\u00e1veis s\u00e3o criados para cada dispositivo selecionado. Quando um dispositivo dispara, o HomeKit pode ser configurado para executar uma automa\u00e7\u00e3o ou cena.", "title": "Configura\u00e7\u00e3o avan\u00e7ada" }, "cameras": { - "title": "Selecione o codec de v\u00eddeo da c\u00e2mera." + "data": { + "camera_audio": "C\u00e2meras que suportam \u00e1udio", + "camera_copy": "C\u00e2meras que suportam streams H.264 nativos" + }, + "description": "Verifique todas as c\u00e2meras que suportam fluxos H.264 nativos. Se a c\u00e2mera n\u00e3o emitir um fluxo H.264, o sistema transcodificar\u00e1 o v\u00eddeo para H.264 para HomeKit. A transcodifica\u00e7\u00e3o requer uma CPU de alto desempenho e \u00e9 improv\u00e1vel que funcione em computadores de placa \u00fanica.", + "title": "Configura\u00e7\u00e3o da c\u00e2mera" }, "exclude": { "data": { @@ -23,7 +51,31 @@ "include": { "data": { "entities": "Entidades" - } + }, + "description": "Todas as entidades \"{domains}\" ser\u00e3o inclu\u00eddas, a menos que entidades espec\u00edficas sejam selecionadas.", + "title": "Selecione as entidades a serem inclu\u00eddas" + }, + "include_exclude": { + "data": { + "entities": "Entidades", + "mode": "Modo" + }, + "description": "Escolha as entidades a serem inclu\u00eddas. No modo acess\u00f3rio, apenas uma \u00fanica entidade est\u00e1 inclu\u00edda. No modo de incluir ponte, todas as entidades do dom\u00ednio ser\u00e3o inclu\u00eddas a menos que entidades espec\u00edficas sejam selecionadas. No modo de exclus\u00e3o da ponte, todas as entidades do dom\u00ednio ser\u00e3o inclu\u00eddas, exceto para as entidades exclu\u00eddas. Para melhor desempenho, um acess\u00f3rio HomeKit separado ser\u00e1 criado para cada leitor de m\u00eddia de TV, controle remoto, bloqueio e c\u00e2mera baseados em atividades.", + "title": "Selecione as entidades a serem inclu\u00eddas" + }, + "init": { + "data": { + "domains": "Dom\u00ednios a serem inclu\u00eddos", + "include_domains": "Dom\u00ednios para incluir", + "include_exclude_mode": "Modo de inclus\u00e3o", + "mode": "Modo HomeKit" + }, + "description": "O HomeKit pode ser configurado para expor uma ponte ou um \u00fanico acess\u00f3rio. No modo acess\u00f3rio, apenas uma \u00fanica entidade pode ser usada. O modo acess\u00f3rio \u00e9 necess\u00e1rio para que os players de m\u00eddia com a classe de dispositivo TV funcionem corretamente. As entidades nos \u201cDom\u00ednios a incluir\u201d ser\u00e3o inclu\u00eddas no HomeKit. Voc\u00ea poder\u00e1 selecionar quais entidades incluir ou excluir desta lista na pr\u00f3xima tela.", + "title": "Selecione o modo e os dom\u00ednios." + }, + "yaml": { + "description": "Esta entrada \u00e9 controlada via YAML", + "title": "Ajustar as op\u00e7\u00f5es do HomeKit" } } } diff --git a/homeassistant/components/homekit_controller/translations/pt-BR.json b/homeassistant/components/homekit_controller/translations/pt-BR.json index 47ee5b8630a5a5..67b89e51b08f03 100644 --- a/homeassistant/components/homekit_controller/translations/pt-BR.json +++ b/homeassistant/components/homekit_controller/translations/pt-BR.json @@ -7,38 +7,67 @@ "already_paired": "Este acess\u00f3rio j\u00e1 est\u00e1 pareado com outro dispositivo. Por favor, redefina o acess\u00f3rio e tente novamente.", "ignored_model": "O suporte do HomeKit para este modelo est\u00e1 bloqueado, j\u00e1 que uma integra\u00e7\u00e3o nativa mais completa est\u00e1 dispon\u00edvel.", "invalid_config_entry": "Este dispositivo est\u00e1 mostrando como pronto para parear, mas existe um conflito na configura\u00e7\u00e3o de entrada para ele no Home Assistant que deve ser removida primeiro.", + "invalid_properties": "Propriedades inv\u00e1lidas anunciadas pelo dispositivo.", "no_devices": "N\u00e3o foi poss\u00edvel encontrar dispositivos n\u00e3o pareados" }, "error": { "authentication_error": "C\u00f3digo HomeKit incorreto. Por favor verifique e tente novamente.", + "insecure_setup_code": "O c\u00f3digo de configura\u00e7\u00e3o solicitado \u00e9 inseguro devido \u00e0 sua natureza trivial. Este acess\u00f3rio n\u00e3o atende aos requisitos b\u00e1sicos de seguran\u00e7a.", "max_peers_error": "O dispositivo recusou-se a adicionar o emparelhamento, pois n\u00e3o tem armazenamento de emparelhamento gratuito.", "pairing_failed": "Ocorreu um erro sem tratamento ao tentar emparelhar com este dispositivo. Isso pode ser uma falha tempor\u00e1ria ou o dispositivo pode n\u00e3o ser suportado no momento.", "unable_to_pair": "N\u00e3o \u00e9 poss\u00edvel parear, tente novamente.", "unknown_error": "O dispositivo relatou um erro desconhecido. O pareamento falhou." }, - "flow_title": "Acess\u00f3rio HomeKit: {name}", + "flow_title": "{name}", "step": { "busy_error": { + "description": "Abortar o emparelhamento em todos os controladores, ou tentar reiniciar o dispositivo, em seguida, continuar a retomar o emparelhamento.", "title": "O dispositivo est\u00e1 pareando com outro controlador" }, "max_tries_error": { + "description": "O dispositivo recebeu mais de 100 tentativas de autentica\u00e7\u00e3o malsucedidas. Tente reiniciar o dispositivo e continue para retomar o emparelhamento.", "title": "Quantidade de tentativas de autentica\u00e7\u00e3o excedido" }, "pair": { "data": { + "allow_insecure_setup_codes": "Permitir o emparelhamento com c\u00f3digos de configura\u00e7\u00e3o inseguros.", "pairing_code": "C\u00f3digo de pareamento" }, - "description": "Digite seu c\u00f3digo de pareamento do HomeKit (no formato XXX-XX-XXX) para usar este acess\u00f3rio", - "title": "Parear com o acess\u00f3rio HomeKit" + "description": "O HomeKit Controller se comunica com {name} sobre a rede local usando uma conex\u00e3o criptografada segura sem um controlador HomeKit separado ou iCloud. Digite seu c\u00f3digo de emparelhamento HomeKit (no formato XXX-XX-XXX) para usar este acess\u00f3rio. Este c\u00f3digo geralmente \u00e9 encontrado no pr\u00f3prio dispositivo ou na embalagem.", + "title": "Emparelhar com um dispositivo atrav\u00e9s do protocolo `HomeKit Accessory`" + }, + "protocol_error": { + "description": "O dispositivo pode n\u00e3o estar no modo de emparelhamento e pode exigir um pressionamento de bot\u00e3o f\u00edsico ou virtual. Certifique-se de que o dispositivo esteja no modo de emparelhamento ou tente reinici\u00e1-lo e continue para retomar o emparelhamento.", + "title": "Erro de comunica\u00e7\u00e3o com o acess\u00f3rio" }, "user": { "data": { "device": "Dispositivo" }, - "description": "Selecione o dispositivo com o qual voc\u00ea deseja parear", - "title": "Parear com o acess\u00f3rio HomeKit" + "description": "O HomeKit Controller se comunica pela rede local usando uma conex\u00e3o criptografada segura sem um controlador HomeKit separado ou iCloud. Selecione o dispositivo com o qual deseja emparelhar:", + "title": "Sele\u00e7\u00e3o de dispositivo" } } }, - "title": "Acess\u00f3rio HomeKit" + "device_automation": { + "trigger_subtype": { + "button1": "Bot\u00e3o 1", + "button10": "Bot\u00e3o 10", + "button2": "Bot\u00e3o 2", + "button3": "Bot\u00e3o 3", + "button4": "Bot\u00e3o 4", + "button5": "Bot\u00e3o 5", + "button6": "Bot\u00e3o 6", + "button7": "Bot\u00e3o 7", + "button8": "Bot\u00e3o 8", + "button9": "Bot\u00e3o 9", + "doorbell": "Campainha" + }, + "trigger_type": { + "double_press": "\"{subtype}\" pressionado duas vezes", + "long_press": "\"{subtype}\" pressionado e mantido", + "single_press": "\"{subtype}\" pressionado" + } + }, + "title": "" } \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/select.it.json b/homeassistant/components/homekit_controller/translations/select.it.json new file mode 100644 index 00000000000000..68fc27ab80ea9a --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/select.it.json @@ -0,0 +1,9 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "away": "Fuori casa", + "home": "Casa", + "sleep": "Notte" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/select.ja.json b/homeassistant/components/homekit_controller/translations/select.ja.json new file mode 100644 index 00000000000000..15be755add3913 --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/select.ja.json @@ -0,0 +1,9 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "away": "\u30a2\u30a6\u30a7\u30a4", + "home": "\u30db\u30fc\u30e0", + "sleep": "\u30b9\u30ea\u30fc\u30d7" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/select.nl.json b/homeassistant/components/homekit_controller/translations/select.nl.json new file mode 100644 index 00000000000000..d330549f208f8c --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/select.nl.json @@ -0,0 +1,9 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "away": "Afwezig", + "home": "Thuis", + "sleep": "Slapen" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/select.uk.json b/homeassistant/components/homekit_controller/translations/select.uk.json new file mode 100644 index 00000000000000..b2bb2e8e3e2564 --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/select.uk.json @@ -0,0 +1,9 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "away": "\u041d\u0435 \u0432\u0434\u043e\u043c\u0430", + "home": "\u0412\u0434\u043e\u043c\u0430", + "sleep": "\u0421\u043e\u043d" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homematicip_cloud/translations/pt-BR.json b/homeassistant/components/homematicip_cloud/translations/pt-BR.json index 101cdd83c48aa4..bff773d58bd064 100644 --- a/homeassistant/components/homematicip_cloud/translations/pt-BR.json +++ b/homeassistant/components/homematicip_cloud/translations/pt-BR.json @@ -22,7 +22,7 @@ }, "link": { "description": "Pressione o bot\u00e3o azul no ponto de acesso e o bot\u00e3o enviar para registrar o HomematicIP com o Home Assistant.\n\n![Location of button on bridge](/static/images/config_flows/config_homematicip_cloud.png)", - "title": "Accesspoint link" + "title": "Link ponto de acesso" } } } diff --git a/homeassistant/components/homewizard/translations/it.json b/homeassistant/components/homewizard/translations/it.json index c0d1be424a530a..61f8a62a6c1f7d 100644 --- a/homeassistant/components/homewizard/translations/it.json +++ b/homeassistant/components/homewizard/translations/it.json @@ -4,7 +4,7 @@ "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "api_not_enabled": "L'API non \u00e8 abilitata. Abilita API nell'applicazione HomeWizard Energy sotto impostazioni", "device_not_supported": "Questo dispositivo non \u00e8 supportato", - "invalid_discovery_parameters": "versione_api_non_supportata", + "invalid_discovery_parameters": "Rilevata versione API non supportata", "unknown_error": "Errore imprevisto" }, "step": { diff --git a/homeassistant/components/homewizard/translations/pt-BR.json b/homeassistant/components/homewizard/translations/pt-BR.json index 2916d86b2428fb..b1abeef992797c 100644 --- a/homeassistant/components/homewizard/translations/pt-BR.json +++ b/homeassistant/components/homewizard/translations/pt-BR.json @@ -2,16 +2,21 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "api_not_enabled": "A API n\u00e3o est\u00e1 habilitada. Ative a API no aplicativo HomeWizard Energy em configura\u00e7\u00f5es", + "device_not_supported": "Este dispositivo n\u00e3o \u00e9 compat\u00edvel", + "invalid_discovery_parameters": "Vers\u00e3o de API n\u00e3o compat\u00edvel detectada", "unknown_error": "Erro inesperado" }, "step": { "discovery_confirm": { + "description": "Deseja configurar {product_type} ( {serial} ) em {ip_address} ?", "title": "Confirmar" }, "user": { "data": { "ip_address": "Endere\u00e7o IP" }, + "description": "Digite o endere\u00e7o IP de seu dispositivo HomeWizard Energy para integrar com o Home Assistant.", "title": "Configurar dispositivo" } } diff --git a/homeassistant/components/honeywell/translations/pt-BR.json b/homeassistant/components/honeywell/translations/pt-BR.json index 7922f363bdab25..f16a6c71637df9 100644 --- a/homeassistant/components/honeywell/translations/pt-BR.json +++ b/homeassistant/components/honeywell/translations/pt-BR.json @@ -8,7 +8,9 @@ "data": { "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "description": "Insira as credenciais usadas para fazer login em mytotalconnectcomfort.com.", + "title": "Honeywell Total Connect Comfort (EUA)" } } } diff --git a/homeassistant/components/huawei_lte/translations/pt-BR.json b/homeassistant/components/huawei_lte/translations/pt-BR.json index 821b2f6e72b00c..7b69fce212d8af 100644 --- a/homeassistant/components/huawei_lte/translations/pt-BR.json +++ b/homeassistant/components/huawei_lte/translations/pt-BR.json @@ -2,18 +2,41 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", - "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento" + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "not_huawei_lte": "N\u00e3o \u00e9 um dispositivo Huawei LTE" }, "error": { + "connection_timeout": "Tempo limite de conex\u00e3o atingido", + "incorrect_password": "Senha incorreta", + "incorrect_username": "Nome de usu\u00e1rio incorreto", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "invalid_url": "URL inv\u00e1lida", + "login_attempts_exceeded": "O m\u00e1ximo de tentativas de login foi excedido. Tente novamente mais tarde", + "response_error": "Erro desconhecido do dispositivo", "unknown": "Erro inesperado" }, + "flow_title": "{name}", "step": { "user": { "data": { "password": "Senha", "url": "URL", "username": "Usu\u00e1rio" + }, + "description": "Digite os detalhes de acesso do dispositivo.", + "title": "Configurar Huawei LTE" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "Nome do servi\u00e7o de notifica\u00e7\u00e3o (a altera\u00e7\u00e3o requer rein\u00edcio)", + "recipient": "Destinat\u00e1rios de notifica\u00e7\u00e3o por SMS", + "track_new_devices": "Rastrear novos dispositivos", + "track_wired_clients": "Rastrear clientes da rede cabeada", + "unauthenticated_mode": "Modo n\u00e3o autenticado (a altera\u00e7\u00e3o requer recarga)" } } } diff --git a/homeassistant/components/hue/translations/pt-BR.json b/homeassistant/components/hue/translations/pt-BR.json index a2cc864aeff072..05dec678318344 100644 --- a/homeassistant/components/hue/translations/pt-BR.json +++ b/homeassistant/components/hue/translations/pt-BR.json @@ -28,7 +28,8 @@ "manual": { "data": { "host": "Nome do host" - } + }, + "title": "Configurar manualmente uma ponte Hue" } } }, @@ -38,13 +39,26 @@ "2": "Segundo bot\u00e3o", "3": "Terceiro bot\u00e3o", "4": "Quarto bot\u00e3o", + "button_1": "Primeiro bot\u00e3o", + "button_2": "Segundo bot\u00e3o", + "button_3": "Terceiro bot\u00e3o", + "button_4": "Quarto bot\u00e3o", + "dim_down": "Diminuir a luminosidade", + "dim_up": "Aumentar a luminosidade", "double_buttons_1_3": "Primeiro e terceiro bot\u00f5es", - "double_buttons_2_4": "Segundo e quarto bot\u00f5es" + "double_buttons_2_4": "Segundo e quarto bot\u00f5es", + "turn_off": "Desligar", + "turn_on": "Ligar" }, "trigger_type": { "double_short_release": "Ambos \"{subtype}\" liberados", "initial_press": "Bot\u00e3o \" {subtype} \" pressionado inicialmente", "long_release": "Bot\u00e3o \" {subtype} \" liberado ap\u00f3s press\u00e3o longa", + "remote_button_long_release": "Bot\u00e3o \"{subtype}\" liberado ap\u00f3s longa press\u00e3o", + "remote_button_short_press": "Bot\u00e3o \"{subtype}\" pressionado", + "remote_button_short_release": "Bot\u00e3o \"{subtype}\" liberado", + "remote_double_button_long_press": "Ambos \"{subtype}\" lan\u00e7ados ap\u00f3s longa imprensa", + "remote_double_button_short_press": "Ambos \"{subtype}\" lan\u00e7ados", "repeat": "Bot\u00e3o \" {subtype} \" pressionado", "short_release": "Bot\u00e3o \" {subtype} \" liberado ap\u00f3s pressionamento curto" } @@ -53,7 +67,9 @@ "step": { "init": { "data": { + "allow_hue_groups": "Permitir grupos Hue", "allow_hue_scenes": "Permitir cenas Hue", + "allow_unreachable": "Permitir que l\u00e2mpadas inacess\u00edveis relatem seu estado corretamente", "ignore_availability": "Ignorar o status de conectividade para os dispositivos fornecidos" } } diff --git a/homeassistant/components/humidifier/translations/nl.json b/homeassistant/components/humidifier/translations/nl.json index 9505a6a0838619..8d96d698209017 100644 --- a/homeassistant/components/humidifier/translations/nl.json +++ b/homeassistant/components/humidifier/translations/nl.json @@ -13,7 +13,9 @@ "is_on": "{entity_name} staat aan" }, "trigger_type": { + "changed_states": "{entity_name} in- of uitgeschakeld", "target_humidity_changed": "{entity_name} doel luchtvochtigheid gewijzigd", + "toggled": "{entity_name} in- of uitgeschakeld", "turned_off": "{entity_name} is uitgeschakeld", "turned_on": "{entity_name} is ingeschakeld" } diff --git a/homeassistant/components/humidifier/translations/pt-BR.json b/homeassistant/components/humidifier/translations/pt-BR.json index 2783abe00e6a68..bb4b6c00177bd5 100644 --- a/homeassistant/components/humidifier/translations/pt-BR.json +++ b/homeassistant/components/humidifier/translations/pt-BR.json @@ -1,8 +1,30 @@ { "device_automation": { + "action_type": { + "set_humidity": "Definir umidade para {entity_name}", + "set_mode": "Alterar modo em {entity_name}", + "toggle": "Alternar {entity_name}", + "turn_off": "Desligar {entity_name}", + "turn_on": "Ligar {entity_name}" + }, + "condition_type": { + "is_mode": "{entity_name} est\u00e1 definido para um modo espec\u00edfico", + "is_off": "{entity_name} est\u00e1 desligado", + "is_on": "{entity_name} est\u00e1 ligado" + }, "trigger_type": { - "changed_states": "{entity_name} ligado ou desligado", - "toggled": "{entity_name} ligado ou desligado" + "changed_states": "{entity_name} for ligado ou desligado", + "target_humidity_changed": "{entity_name} tiver a umidade alvo alterada", + "toggled": "{entity_name} for ligado ou desligado", + "turned_off": "{entity_name} for desligado", + "turned_on": "{entity_name} for ligado" } - } + }, + "state": { + "_": { + "off": "Desligado", + "on": "Ligado" + } + }, + "title": "Umidificador" } \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/pt-BR.json b/homeassistant/components/hunterdouglas_powerview/translations/pt-BR.json index b98d170336fbe0..b170cb598828fd 100644 --- a/homeassistant/components/hunterdouglas_powerview/translations/pt-BR.json +++ b/homeassistant/components/hunterdouglas_powerview/translations/pt-BR.json @@ -7,11 +7,17 @@ "cannot_connect": "Falha ao conectar", "unknown": "Erro inesperado" }, + "flow_title": "{name} ( {host} )", "step": { + "link": { + "description": "Deseja configurar {name} ( {host} )?", + "title": "Conecte-se ao PowerView Hub" + }, "user": { "data": { "host": "Endere\u00e7o IP" - } + }, + "title": "Conecte-se ao PowerView Hub" } } } diff --git a/homeassistant/components/hvv_departures/translations/pt-BR.json b/homeassistant/components/hvv_departures/translations/pt-BR.json index f10ded2b0b379b..325caaa27f69ee 100644 --- a/homeassistant/components/hvv_departures/translations/pt-BR.json +++ b/homeassistant/components/hvv_departures/translations/pt-BR.json @@ -5,15 +5,42 @@ }, "error": { "cannot_connect": "Falha ao conectar", - "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "no_results": "Sem resultados. Tente com uma esta\u00e7\u00e3o/endere\u00e7o diferente" }, "step": { + "station": { + "data": { + "station": "Esta\u00e7\u00e3o/Endere\u00e7o" + }, + "title": "Digite Esta\u00e7\u00e3o/Endere\u00e7o" + }, + "station_select": { + "data": { + "station": "Esta\u00e7\u00e3o/Endere\u00e7o" + }, + "title": "Selecione Esta\u00e7\u00e3o/Endere\u00e7o" + }, "user": { "data": { "host": "Nome do host", "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "title": "Conecte-se \u00e0 API HVV" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "filter": "Selecionar linhas", + "offset": "Deslocamento (minutos)", + "real_time": "Usar dados em tempo real" + }, + "description": "Alterar op\u00e7\u00f5es para este sensor de partida", + "title": "Op\u00e7\u00f5es" } } } diff --git a/homeassistant/components/hyperion/translations/pt-BR.json b/homeassistant/components/hyperion/translations/pt-BR.json index 7406eb095f200d..a0d75722f0d378 100644 --- a/homeassistant/components/hyperion/translations/pt-BR.json +++ b/homeassistant/components/hyperion/translations/pt-BR.json @@ -3,7 +3,11 @@ "abort": { "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado", "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "auth_new_token_not_granted_error": "O token rec\u00e9m-criado n\u00e3o foi aprovado na interface do usu\u00e1rio do Hyperion", + "auth_new_token_not_work_error": "Falha ao autenticar usando o token rec\u00e9m-criado", + "auth_required_error": "Falha ao determinar se a autoriza\u00e7\u00e3o \u00e9 necess\u00e1ria", "cannot_connect": "Falha ao conectar", + "no_id": "A inst\u00e2ncia Hyperion Ambilight n\u00e3o informou seu ID", "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { @@ -11,6 +15,24 @@ "invalid_access_token": "Token de acesso inv\u00e1lido" }, "step": { + "auth": { + "data": { + "create_token": "Criar novo token automaticamente", + "token": "Ou forne\u00e7a um token pr\u00e9-existente" + }, + "description": "Configure a autoriza\u00e7\u00e3o para seu servidor Hyperion Ambilight" + }, + "confirm": { + "description": "Deseja adicionar este Hyperion Ambilight ao Home Assistant?\n\n**Host:** {host}\n**Porta:** {port}\n**ID**: {id}", + "title": "Confirme a adi\u00e7\u00e3o do servi\u00e7o Hyperion Ambilight" + }, + "create_token": { + "description": "Escolha **Enviar** abaixo para solicitar um novo token de autentica\u00e7\u00e3o. Voc\u00ea ser\u00e1 redirecionado para a interface do usu\u00e1rio do Hyperion para aprovar a solicita\u00e7\u00e3o. Verifique se o ID mostrado \u00e9 \" {auth_id} \"", + "title": "Criar automaticamente um novo token de autentica\u00e7\u00e3o" + }, + "create_token_external": { + "title": "Aceitar novo token na interface do usu\u00e1rio do Hyperion" + }, "user": { "data": { "host": "Nome do host", @@ -18,5 +40,15 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "effect_show_list": "Efeitos do Hyperion para mostrar", + "priority": "Prioridade do Hyperion a ser usada para cores e efeitos" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/iaqualink/translations/uk.json b/homeassistant/components/iaqualink/translations/uk.json index b855d75572603c..fc71e748a157d1 100644 --- a/homeassistant/components/iaqualink/translations/uk.json +++ b/homeassistant/components/iaqualink/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "error": { "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" diff --git a/homeassistant/components/icloud/translations/pt-BR.json b/homeassistant/components/icloud/translations/pt-BR.json index fe7c4d91253db2..0c3363a379905c 100644 --- a/homeassistant/components/icloud/translations/pt-BR.json +++ b/homeassistant/components/icloud/translations/pt-BR.json @@ -2,18 +2,20 @@ "config": { "abort": { "already_configured": "A conta j\u00e1 foi configurada", + "no_device": "Nenhum dos seus dispositivos tem \"Encontrar meu iPhone\" ativado", "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "send_verification_code": "Falha ao enviar c\u00f3digo de verifica\u00e7\u00e3o", - "validate_verification_code": "Falha ao verificar seu c\u00f3digo de verifica\u00e7\u00e3o, escolha um dispositivo confi\u00e1vel e inicie a verifica\u00e7\u00e3o novamente" + "validate_verification_code": "Falha ao verificar seu c\u00f3digo de verifica\u00e7\u00e3o, tente novamente" }, "step": { "reauth": { "data": { "password": "Senha" }, + "description": "Sua senha inserida anteriormente para {username} n\u00e3o est\u00e1 mais funcionando. Atualize sua senha para continuar usando esta integra\u00e7\u00e3o.", "title": "Reautenticar Integra\u00e7\u00e3o" }, "trusted_device": { @@ -26,7 +28,8 @@ "user": { "data": { "password": "Senha", - "username": "E-mail" + "username": "Email", + "with_family": "Com a fam\u00edlia" }, "description": "Insira suas credenciais", "title": "credenciais do iCloud" diff --git a/homeassistant/components/ifttt/translations/ja.json b/homeassistant/components/ifttt/translations/ja.json index 81616a31dd724f..a3a63f532cfb0c 100644 --- a/homeassistant/components/ifttt/translations/ja.json +++ b/homeassistant/components/ifttt/translations/ja.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Home Assistant Cloud\u306b\u63a5\u7d9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002", "single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002", "webhook_not_internet_accessible": "Webhook\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u53d7\u4fe1\u3059\u308b\u306b\u306f\u3001Home Assistant\u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u306b\u3001\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u304b\u3089\u30a2\u30af\u30bb\u30b9\u3067\u304d\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002" }, diff --git a/homeassistant/components/ifttt/translations/nl.json b/homeassistant/components/ifttt/translations/nl.json index 82006860db3adc..727b21f43be7b0 100644 --- a/homeassistant/components/ifttt/translations/nl.json +++ b/homeassistant/components/ifttt/translations/nl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Niet verbonden met Home Assistant Cloud.", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." }, diff --git a/homeassistant/components/ifttt/translations/pt-BR.json b/homeassistant/components/ifttt/translations/pt-BR.json index 32dec17701cbf5..239afa3b7e48e1 100644 --- a/homeassistant/components/ifttt/translations/pt-BR.json +++ b/homeassistant/components/ifttt/translations/pt-BR.json @@ -2,7 +2,8 @@ "config": { "abort": { "cloud_not_connected": "N\u00e3o conectado ao Home Assistant Cloud.", - "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", + "webhook_not_internet_accessible": "Sua inst\u00e2ncia do Home Assistant precisa estar acess\u00edvel pela Internet para receber mensagens de webhook." }, "create_entry": { "default": "Para enviar eventos para o Home Assistant, voc\u00ea precisar\u00e1 usar a a\u00e7\u00e3o \"Fazer uma solicita\u00e7\u00e3o Web\" no [applet IFTTT Webhook] ( {applet_url} ). \n\n Preencha as seguintes informa\u00e7\u00f5es: \n\n - URL: ` {webhook_url} ` \n - M\u00e9todo: POST \n - Tipo de Conte\u00fado: application / json \n\n Veja [a documenta\u00e7\u00e3o] ( {docs_url} ) sobre como configurar automa\u00e7\u00f5es para manipular dados de entrada." diff --git a/homeassistant/components/ifttt/translations/uk.json b/homeassistant/components/ifttt/translations/uk.json index 8ea8f2b1970da3..46bc1de9d62d6e 100644 --- a/homeassistant/components/ifttt/translations/uk.json +++ b/homeassistant/components/ifttt/translations/uk.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e.", + "cloud_not_connected": "\u041d\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0434\u043e Home Assistant Cloud.", + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f.", "webhook_not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u043f\u043e\u0432\u0438\u043d\u0435\u043d \u0431\u0443\u0442\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0439 \u0437 \u0406\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443 \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f Webhook-\u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u044c." }, "create_entry": { diff --git a/homeassistant/components/insteon/translations/pt-BR.json b/homeassistant/components/insteon/translations/pt-BR.json index c2b366b4d0f656..409ac9d62834bc 100644 --- a/homeassistant/components/insteon/translations/pt-BR.json +++ b/homeassistant/components/insteon/translations/pt-BR.json @@ -5,14 +5,17 @@ "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "error": { - "cannot_connect": "Falha ao conectar" + "cannot_connect": "Falha ao conectar", + "select_single": "Selecione uma op\u00e7\u00e3o." }, "step": { "hubv1": { "data": { "host": "Endere\u00e7o IP", "port": "Porta" - } + }, + "description": "Configure o Insteon Hub Vers\u00e3o 1 (anterior a 2014).", + "title": "Insteon Hub Vers\u00e3o 1" }, "hubv2": { "data": { @@ -20,12 +23,23 @@ "password": "Senha", "port": "Porta", "username": "Usu\u00e1rio" - } + }, + "description": "Configure o Insteon Hub Vers\u00e3o 2.", + "title": "Insteon Hub Vers\u00e3o 2" }, "plm": { "data": { "device": "Caminho do Dispositivo USB" - } + }, + "description": "Configure o modem Insteon PowerLink (PLM).", + "title": "Insteon PLM" + }, + "user": { + "data": { + "modem_type": "Tipo de modem." + }, + "description": "Selecione o tipo de modem Insteon.", + "title": "Insteon" } } }, @@ -39,17 +53,21 @@ "add_override": { "data": { "address": "Endere\u00e7o do dispositivo (ou seja, 1a2b3c)", - "cat": "Subcategoria de dispositivo (ou seja, 0x0a)", + "cat": "Subcategoria de dispositivo (ou seja, 0x10)", "subcat": "Subcategoria de dispositivo (ou seja, 0x0a)" }, - "description": "Escolha um dispositivo para sobrescrever" + "description": "Escolha um dispositivo para sobrescrever", + "title": "Insteon" }, "add_x10": { "data": { + "housecode": "C\u00f3digo da casa (a - p)", "platform": "Plataforma", - "steps": "Etapas de dimmer (apenas para dispositivos de lux, padr\u00e3o 22)" + "steps": "Etapas de dimmer (apenas para dispositivos de lux, padr\u00e3o 22)", + "unitcode": "C\u00f3digo de unidade (1 - 16)" }, - "description": "Altere a senha do Insteon Hub." + "description": "Altere a senha do Insteon Hub.", + "title": "Insteon" }, "change_hub_config": { "data": { @@ -57,14 +75,32 @@ "password": "Senha", "port": "Porta", "username": "Usu\u00e1rio" - } + }, + "description": "Altere as informa\u00e7\u00f5es de conex\u00e3o do Hub Insteon. Voc\u00ea deve reiniciar o Home Assistant depois de fazer essa altera\u00e7\u00e3o. Isso n\u00e3o altera a configura\u00e7\u00e3o do pr\u00f3prio Hub. Para alterar a configura\u00e7\u00e3o no Hub, use o aplicativo Hub.", + "title": "Insteon" }, "init": { "data": { - "add_x10": "Adicionar um dispositivo X10" - } + "add_override": "Adicione uma substitui\u00e7\u00e3o de dispositivo.", + "add_x10": "Adicionar um dispositivo X10", + "change_hub_config": "Altere a configura\u00e7\u00e3o do Hub.", + "remove_override": "Remova uma substitui\u00e7\u00e3o de dispositivo.", + "remove_x10": "Remova um dispositivo X10." + }, + "description": "Selecione uma op\u00e7\u00e3o para configurar.", + "title": "Insteon" + }, + "remove_override": { + "data": { + "address": "Selecione um endere\u00e7o de dispositivo para remover" + }, + "description": "Remover uma substitui\u00e7\u00e3o de dispositivo", + "title": "Insteon" }, "remove_x10": { + "data": { + "address": "Selecione um endere\u00e7o de dispositivo para remover" + }, "description": "Remover um dispositivo X10", "title": "Insteon" } diff --git a/homeassistant/components/insteon/translations/uk.json b/homeassistant/components/insteon/translations/uk.json index 302d8c3676a00d..747e3a301767cf 100644 --- a/homeassistant/components/insteon/translations/uk.json +++ b/homeassistant/components/insteon/translations/uk.json @@ -2,7 +2,7 @@ "config": { "abort": { "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "error": { "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", diff --git a/homeassistant/components/ios/translations/uk.json b/homeassistant/components/ios/translations/uk.json index 5f8d69f5f29b89..5ee7dbfde346d7 100644 --- a/homeassistant/components/ios/translations/uk.json +++ b/homeassistant/components/ios/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "step": { "confirm": { diff --git a/homeassistant/components/iotawatt/translations/pt-BR.json b/homeassistant/components/iotawatt/translations/pt-BR.json index 79d60b8a2f78b7..9fec74f379fdff 100644 --- a/homeassistant/components/iotawatt/translations/pt-BR.json +++ b/homeassistant/components/iotawatt/translations/pt-BR.json @@ -10,7 +10,8 @@ "data": { "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "description": "O dispositivo IoTawatt requer autentica\u00e7\u00e3o. Por favor, digite o nome de usu\u00e1rio e senha e clique no bot\u00e3o Enviar." }, "user": { "data": { diff --git a/homeassistant/components/ipma/translations/pt-BR.json b/homeassistant/components/ipma/translations/pt-BR.json index f2af40324ebfff..b6022ba812493f 100644 --- a/homeassistant/components/ipma/translations/pt-BR.json +++ b/homeassistant/components/ipma/translations/pt-BR.json @@ -15,5 +15,10 @@ "title": "Localiza\u00e7\u00e3o" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "Endpoint da API IPMA acess\u00edvel" + } } } \ No newline at end of file diff --git a/homeassistant/components/ipp/translations/pt-BR.json b/homeassistant/components/ipp/translations/pt-BR.json index fd3619849c2256..7da66ff568beee 100644 --- a/homeassistant/components/ipp/translations/pt-BR.json +++ b/homeassistant/components/ipp/translations/pt-BR.json @@ -6,13 +6,14 @@ "connection_upgrade": "Falha ao conectar \u00e0 impressora devido \u00e0 atualiza\u00e7\u00e3o da conex\u00e3o ser necess\u00e1ria.", "ipp_error": "Erro IPP encontrado.", "ipp_version_error": "Vers\u00e3o IPP n\u00e3o suportada pela impressora.", + "parse_error": "Falha ao analisar a resposta da impressora.", "unique_id_required": "Dispositivo faltando identifica\u00e7\u00e3o \u00fanica necess\u00e1ria para a descoberta." }, "error": { "cannot_connect": "Falha ao conectar", "connection_upgrade": "Falha ao conectar \u00e0 impressora. Por favor, tente novamente com a op\u00e7\u00e3o SSL/TLS marcada." }, - "flow_title": "Impressora: {name}", + "flow_title": "{name}", "step": { "user": { "data": { @@ -26,6 +27,7 @@ "title": "Vincule sua impressora" }, "zeroconf_confirm": { + "description": "Deseja configurar {name}?", "title": "Impressora descoberta" } } diff --git a/homeassistant/components/islamic_prayer_times/translations/pt-BR.json b/homeassistant/components/islamic_prayer_times/translations/pt-BR.json index 9ab59f40649f29..c55992a3e97928 100644 --- a/homeassistant/components/islamic_prayer_times/translations/pt-BR.json +++ b/homeassistant/components/islamic_prayer_times/translations/pt-BR.json @@ -2,6 +2,22 @@ "config": { "abort": { "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "step": { + "user": { + "description": "Voc\u00ea quer configurar tempo de ora\u00e7\u00e3o Isl\u00e2mico?", + "title": "Estabele\u00e7a hor\u00e1rios de ora\u00e7\u00e3o Isl\u00e2mico" + } } - } + }, + "options": { + "step": { + "init": { + "data": { + "calculation_method": "M\u00e9todo de c\u00e1lculo de ora\u00e7\u00e3o" + } + } + } + }, + "title": "Tempo de ora\u00e7\u00e3o Isl\u00e2mico" } \ No newline at end of file diff --git a/homeassistant/components/islamic_prayer_times/translations/uk.json b/homeassistant/components/islamic_prayer_times/translations/uk.json index 9290114899a4ee..3774d17802596f 100644 --- a/homeassistant/components/islamic_prayer_times/translations/uk.json +++ b/homeassistant/components/islamic_prayer_times/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "step": { "user": { diff --git a/homeassistant/components/iss/translations/en.json b/homeassistant/components/iss/translations/en.json index 13483418ffa6a2..f8ef8d27cd70fe 100644 --- a/homeassistant/components/iss/translations/en.json +++ b/homeassistant/components/iss/translations/en.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "latitude_longitude_not_defined": "Latitude and longitude is not defind in Home Assistant.", + "latitude_longitude_not_defined": "Latitude and longitude are not defined in Home Assistant.", "single_instance_allowed": "Already configured. Only a single configuration possible." }, "step": { @@ -9,7 +9,7 @@ "data": { "show_on_map": "Show on map?" }, - "description": "Do you want to configure the Internation Space Station?" + "description": "Do you want to configure the International Space Station?" } } } diff --git a/homeassistant/components/iss/translations/it.json b/homeassistant/components/iss/translations/it.json new file mode 100644 index 00000000000000..148e4b91c01098 --- /dev/null +++ b/homeassistant/components/iss/translations/it.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "latitude_longitude_not_defined": "Latitudine e longitudine non sono definite in Home Assistant.", + "single_instance_allowed": "Gi\u00e0 configurato. Solo una configurazione \u00e8 ammessa." + }, + "step": { + "user": { + "data": { + "show_on_map": "Mostrare sulla mappa?" + }, + "description": "Vuoi configurare la Stazione Spaziale Internazionale?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iss/translations/ja.json b/homeassistant/components/iss/translations/ja.json new file mode 100644 index 00000000000000..6b76fb0e6bcbcd --- /dev/null +++ b/homeassistant/components/iss/translations/ja.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "latitude_longitude_not_defined": "Home Assistant\u3067\u7def\u5ea6\u3068\u7d4c\u5ea6\u304c\u5b9a\u7fa9\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002", + "single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002" + }, + "step": { + "user": { + "data": { + "show_on_map": "\u5730\u56f3\u306b\u8868\u793a\u3057\u307e\u3059\u304b\uff1f" + }, + "description": "\u56fd\u969b\u5b87\u5b99\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u306e\u8a2d\u5b9a\u3092\u3057\u307e\u3059\u304b\uff1f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iss/translations/nl.json b/homeassistant/components/iss/translations/nl.json new file mode 100644 index 00000000000000..c14e2f838001c2 --- /dev/null +++ b/homeassistant/components/iss/translations/nl.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, + "step": { + "user": { + "data": { + "show_on_map": "Op kaart tonen?" + }, + "description": "Wilt u het International Space Station configureren?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iss/translations/pt-BR.json b/homeassistant/components/iss/translations/pt-BR.json index 34a9644e9d0efe..b4257ea668c53f 100644 --- a/homeassistant/components/iss/translations/pt-BR.json +++ b/homeassistant/components/iss/translations/pt-BR.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "latitude_longitude_not_defined": "Latitude e longitude est\u00e3o definidos em Home Assistant.", + "latitude_longitude_not_defined": "Latitude e longitude n\u00e3o est\u00e3o definidos no Home Assistant.", "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "step": { diff --git a/homeassistant/components/iss/translations/uk.json b/homeassistant/components/iss/translations/uk.json new file mode 100644 index 00000000000000..cfcf3e0c458bb7 --- /dev/null +++ b/homeassistant/components/iss/translations/uk.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "latitude_longitude_not_defined": "\u0428\u0438\u0440\u043e\u0442\u0430 \u0442\u0430 \u0434\u043e\u0432\u0433\u043e\u0442\u0430 \u043d\u0435 \u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u0456 \u0432 Home Assistant.", + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." + }, + "step": { + "user": { + "data": { + "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u0438 \u043d\u0430 \u043c\u0430\u043f\u0456?" + }, + "description": "\u0427\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0432\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u041c\u0456\u0436\u043d\u0430\u0440\u043e\u0434\u043d\u0443 \u041a\u043e\u0441\u043c\u0456\u0447\u043d\u0443 \u0421\u0442\u0430\u043d\u0446\u0456\u044e?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/isy994/translations/pt-BR.json b/homeassistant/components/isy994/translations/pt-BR.json index d377b19f5862d6..b644b6c1bfc00c 100644 --- a/homeassistant/components/isy994/translations/pt-BR.json +++ b/homeassistant/components/isy994/translations/pt-BR.json @@ -9,7 +9,7 @@ "invalid_host": "A entrada do host n\u00e3o est\u00e1 no formato de URL completo, por exemplo, http://192.168.10.100:80", "unknown": "Erro inesperado" }, - "flow_title": "Dispositivos universais ISY994 {name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { @@ -32,8 +32,17 @@ "sensor_string": "Texto do sensor node", "variable_sensor_string": "Texto da vari\u00e1vel do sensor" }, + "description": "Defina as op\u00e7\u00f5es para a Integra\u00e7\u00e3o ISY:\n \u2022 Cadeia de Sensores de N\u00f3: Qualquer dispositivo ou pasta que contenha 'Cadeia de Sensores de N\u00f3' no nome ser\u00e1 tratado como um sensor ou sensor bin\u00e1rio.\n \u2022 Ignore String: Qualquer dispositivo com 'Ignore String' no nome ser\u00e1 ignorado.\n \u2022 Variable Sensor String: Qualquer vari\u00e1vel que contenha 'Variable Sensor String' ser\u00e1 adicionada como sensor.\n \u2022 Restaurar o brilho da luz: Se ativado, o brilho anterior ser\u00e1 restaurado ao acender uma luz em vez do n\u00edvel integrado do dispositivo.", "title": "ISY994 Op\u00e7\u00f5es" } } + }, + "system_health": { + "info": { + "device_connected": "ISY conectado", + "host_reachable": "Alcance do host", + "last_heartbeat": "Hora da \u00faltima pulsa\u00e7\u00e3o", + "websocket_status": "Status do soquete de eventos" + } } } \ No newline at end of file diff --git a/homeassistant/components/isy994/translations/uk.json b/homeassistant/components/isy994/translations/uk.json index c874b8654f58dc..e50bc5cb26b615 100644 --- a/homeassistant/components/isy994/translations/uk.json +++ b/homeassistant/components/isy994/translations/uk.json @@ -29,10 +29,10 @@ "data": { "ignore_string": "\u0406\u0433\u043d\u043e\u0440\u0443\u0432\u0430\u0442\u0438", "restore_light_state": "\u0412\u0456\u0434\u043d\u043e\u0432\u043b\u044e\u0432\u0430\u0442\u0438 \u044f\u0441\u043a\u0440\u0430\u0432\u0456\u0441\u0442\u044c \u0441\u0432\u0456\u0442\u043b\u0430", - "sensor_string": "\u0406\u043c\u043f\u043e\u0440\u0442\u0443\u0432\u0430\u0442\u0438 \u0432\u0443\u0437\u043e\u043b \u044f\u043a \u0441\u0435\u043d\u0441\u043e\u0440", + "sensor_string": "\u0420\u044f\u0434\u043e\u043a \u0441\u0435\u043d\u0441\u043e\u0440\u0443 \u0432\u0443\u0437\u043b\u0430", "variable_sensor_string": "\u0406\u043c\u043f\u043e\u0440\u0442\u0443\u0432\u0430\u0442\u0438 \u0437\u043c\u0456\u043d\u043d\u0443 \u044f\u043a \u0441\u0435\u043d\u0441\u043e\u0440" }, - "description": "\u041e\u043f\u0438\u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0456\u0432:\n \u2022 \u0406\u043c\u043f\u043e\u0440\u0442\u0443\u0432\u0430\u0442\u0438 \u0432\u0443\u0437\u043e\u043b \u044f\u043a \u0441\u0435\u043d\u0441\u043e\u0440: \u0431\u0443\u0434\u044c-\u044f\u043a\u0438\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0430\u0431\u043e \u043f\u0430\u043f\u043a\u0430, \u0432 \u0456\u043c\u0435\u043d\u0456 \u044f\u043a\u043e\u0457 \u043c\u0456\u0441\u0442\u0438\u0442\u044c\u0441\u044f \u0437\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0440\u044f\u0434\u043e\u043a, \u0431\u0443\u0434\u0435 \u0456\u043c\u043f\u043e\u0440\u0442\u043e\u0432\u0430\u043d\u043e \u044f\u043a \u0441\u0435\u043d\u0441\u043e\u0440 \u0430\u0431\u043e \u0431\u0456\u043d\u0430\u0440\u043d\u0438\u0439 \u0441\u0435\u043d\u0441\u043e\u0440.\n \u2022 \u0406\u043c\u043f\u043e\u0440\u0442\u0443\u0432\u0430\u0442\u0438 \u0437\u043c\u0456\u043d\u043d\u0443 \u044f\u043a \u0441\u0435\u043d\u0441\u043e\u0440: \u0431\u0443\u0434\u044c-\u044f\u043a\u0430 \u0437\u043c\u0456\u043d\u043d\u0430, \u044f\u043a\u0430 \u043c\u0456\u0441\u0442\u0438\u0442\u044c \u0437\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0440\u044f\u0434\u043e\u043a, \u0431\u0443\u0434\u0435 \u0456\u043c\u043f\u043e\u0440\u0442\u043e\u0432\u0430\u043d\u0430 \u044f\u043a \u0441\u0435\u043d\u0441\u043e\u0440.\n \u2022 \u0406\u0433\u043d\u043e\u0440\u0443\u0432\u0430\u0442\u0438: \u0431\u0443\u0434\u044c-\u044f\u043a\u0438\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439, \u0432 \u0456\u043c\u0435\u043d\u0456 \u044f\u043a\u043e\u0433\u043e \u043c\u0456\u0441\u0442\u0438\u0442\u044c\u0441\u044f \u0437\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0440\u044f\u0434\u043e\u043a, \u0431\u0443\u0434\u0435 \u0456\u0433\u043d\u043e\u0440\u0443\u0432\u0430\u0442\u0438\u0441\u044f.\n \u2022 \u0412\u0456\u0434\u043d\u043e\u0432\u043b\u044e\u0432\u0430\u0442\u0438 \u044f\u0441\u043a\u0440\u0430\u0432\u0456\u0441\u0442\u044c \u0441\u0432\u0456\u0442\u043b\u0430: \u043f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u0456 \u043e\u0441\u0432\u0456\u0442\u043b\u0435\u043d\u043d\u044f \u0431\u0443\u0434\u0435 \u0432\u0456\u0434\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u044f\u0441\u043a\u0440\u0430\u0432\u043e\u0441\u0442\u0456, \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0435 \u0434\u043e \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f.", + "description": "\u041e\u043f\u0438\u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0456\u0432:\n \u2022 \u0420\u044f\u0434\u043e\u043a \u0441\u0435\u043d\u0441\u043e\u0440\u0443 \u0432\u0443\u0437\u043b\u0430: \u0431\u0443\u0434\u044c-\u044f\u043a\u0438\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0430\u0431\u043e \u0442\u0435\u043a\u0430, \u0449\u043e \u043c\u0456\u0441\u0442\u0438\u0442\u0438\u043c\u0435 \"\u0440\u044f\u0434\u043e\u043a \u0441\u0435\u043d\u0441\u043e\u0440\u0443 \u0432\u0443\u0437\u043b\u0430\" \u0432 \u0456\u043c\u0435\u043d\u0456, \u0431\u0443\u0434\u0435 \u0432\u0438\u0437\u043d\u0430\u043d\u043e \u044f\u043a \u0441\u0435\u043d\u0441\u043e\u0440 \u0430\u0431\u043e \u0431\u0456\u043d\u0430\u0440\u043d\u0438\u0439 \u0441\u0435\u043d\u0441\u043e\u0440.\n \u2022 \u0406\u043c\u043f\u043e\u0440\u0442\u0443\u0432\u0430\u0442\u0438 \u0437\u043c\u0456\u043d\u043d\u0443 \u044f\u043a \u0441\u0435\u043d\u0441\u043e\u0440: \u0431\u0443\u0434\u044c-\u044f\u043a\u0430 \u0437\u043c\u0456\u043d\u043d\u0430, \u044f\u043a\u0430 \u043c\u0456\u0441\u0442\u0438\u0442\u044c \u0437\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0440\u044f\u0434\u043e\u043a, \u0431\u0443\u0434\u0435 \u0456\u043c\u043f\u043e\u0440\u0442\u043e\u0432\u0430\u043d\u0430 \u044f\u043a \u0441\u0435\u043d\u0441\u043e\u0440.\n \u2022 \u0406\u0433\u043d\u043e\u0440\u0443\u0432\u0430\u0442\u0438: \u0431\u0443\u0434\u044c-\u044f\u043a\u0438\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439, \u0432 \u0456\u043c\u0435\u043d\u0456 \u044f\u043a\u043e\u0433\u043e \u043c\u0456\u0441\u0442\u0438\u0442\u044c\u0441\u044f \u0437\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0440\u044f\u0434\u043e\u043a, \u0431\u0443\u0434\u0435 \u0456\u0433\u043d\u043e\u0440\u0443\u0432\u0430\u0442\u0438\u0441\u044f.\n \u2022 \u0412\u0456\u0434\u043d\u043e\u0432\u043b\u044e\u0432\u0430\u0442\u0438 \u044f\u0441\u043a\u0440\u0430\u0432\u0456\u0441\u0442\u044c \u0441\u0432\u0456\u0442\u043b\u0430: \u043f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u0456 \u043e\u0441\u0432\u0456\u0442\u043b\u0435\u043d\u043d\u044f \u0431\u0443\u0434\u0435 \u0432\u0456\u0434\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u044f\u0441\u043a\u0440\u0430\u0432\u043e\u0441\u0442\u0456, \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0435 \u0434\u043e \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f.", "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f ISY994" } } diff --git a/homeassistant/components/izone/translations/pt-BR.json b/homeassistant/components/izone/translations/pt-BR.json index ae7a7293429109..d9055af4b36e01 100644 --- a/homeassistant/components/izone/translations/pt-BR.json +++ b/homeassistant/components/izone/translations/pt-BR.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "no_devices_found": "Nenhum dispositivo encontrado na rede", "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "step": { diff --git a/homeassistant/components/izone/translations/uk.json b/homeassistant/components/izone/translations/uk.json index 8ab6c1e1664374..e29c169a26e097 100644 --- a/homeassistant/components/izone/translations/uk.json +++ b/homeassistant/components/izone/translations/uk.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\u041f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456 \u0432 \u043c\u0435\u0440\u0435\u0436\u0456.", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "step": { "confirm": { diff --git a/homeassistant/components/juicenet/translations/pt-BR.json b/homeassistant/components/juicenet/translations/pt-BR.json index 806cea1df9b232..c5cd5d0dcc91e4 100644 --- a/homeassistant/components/juicenet/translations/pt-BR.json +++ b/homeassistant/components/juicenet/translations/pt-BR.json @@ -12,7 +12,9 @@ "user": { "data": { "api_token": "Token da API" - } + }, + "description": "Voc\u00ea precisar\u00e1 do token de API de https://home.juice.net/Manage.", + "title": "Conecte-se ao JuiceNet" } } } diff --git a/homeassistant/components/keenetic_ndms2/translations/pt-BR.json b/homeassistant/components/keenetic_ndms2/translations/pt-BR.json index 937edfdd914dfa..c143db7cbd1ae9 100644 --- a/homeassistant/components/keenetic_ndms2/translations/pt-BR.json +++ b/homeassistant/components/keenetic_ndms2/translations/pt-BR.json @@ -1,11 +1,14 @@ { "config": { "abort": { - "already_configured": "A conta j\u00e1 foi configurada" + "already_configured": "A conta j\u00e1 foi configurada", + "no_udn": "As informa\u00e7\u00f5es de descoberta SSDP n\u00e3o t\u00eam UDN", + "not_keenetic_ndms2": "O item descoberto n\u00e3o \u00e9 um roteador Keenetic" }, "error": { "cannot_connect": "Falha ao conectar" }, + "flow_title": "{name} ( {host} )", "step": { "user": { "data": { @@ -13,6 +16,21 @@ "password": "Senha", "port": "Porta", "username": "Usu\u00e1rio" + }, + "title": "Configurar o roteador Keenetic NDMS2" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "consider_home": "Considere o intervalo em casa", + "include_arp": "Use dados ARP (ignorados se forem usados dados de hotspot)", + "include_associated": "Use dados de associa\u00e7\u00f5es de AP WiFi (ignorado se forem usados dados de ponto de acesso)", + "interfaces": "Escolha interfaces para escanear", + "scan_interval": "Intervalo de escaneamento", + "try_hotspot": "Use dados 'ip hotspot' (mais precisos)" } } } diff --git a/homeassistant/components/kmtronic/translations/pt-BR.json b/homeassistant/components/kmtronic/translations/pt-BR.json index 93beddb92a8517..2d0b50c0640fa7 100644 --- a/homeassistant/components/kmtronic/translations/pt-BR.json +++ b/homeassistant/components/kmtronic/translations/pt-BR.json @@ -17,5 +17,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "L\u00f3gica de comuta\u00e7\u00e3o reversa (use NC)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/knx/translations/ja.json b/homeassistant/components/knx/translations/ja.json index 83a11ef82fea64..a4744a41a2cbc1 100644 --- a/homeassistant/components/knx/translations/ja.json +++ b/homeassistant/components/knx/translations/ja.json @@ -14,7 +14,8 @@ "individual_address": "\u63a5\u7d9a\u7528\u306e\u500b\u5225\u30a2\u30c9\u30ec\u30b9", "local_ip": "\u30ed\u30fc\u30ab\u30ebIP(\u4e0d\u660e\u306a\u5834\u5408\u306f\u7a7a\u306e\u307e\u307e\u306b\u3057\u3066\u304f\u3060\u3055\u3044)", "port": "\u30dd\u30fc\u30c8", - "route_back": "\u30eb\u30fc\u30c8\u30d0\u30c3\u30af / NAT\u30e2\u30fc\u30c9" + "route_back": "\u30eb\u30fc\u30c8\u30d0\u30c3\u30af / NAT\u30e2\u30fc\u30c9", + "tunneling_type": "KNX\u30c8\u30f3\u30cd\u30ea\u30f3\u30b0\u30bf\u30a4\u30d7" }, "description": "\u30c8\u30f3\u30cd\u30ea\u30f3\u30b0\u30c7\u30d0\u30a4\u30b9\u306e\u63a5\u7d9a\u60c5\u5831\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002" }, @@ -59,7 +60,8 @@ "host": "\u30db\u30b9\u30c8", "local_ip": "\u30ed\u30fc\u30ab\u30ebIP(\u4e0d\u660e\u306a\u5834\u5408\u306f\u7a7a\u306e\u307e\u307e\u306b\u3057\u3066\u304f\u3060\u3055\u3044)", "port": "\u30dd\u30fc\u30c8", - "route_back": "\u30eb\u30fc\u30c8\u30d0\u30c3\u30af / NAT\u30e2\u30fc\u30c9" + "route_back": "\u30eb\u30fc\u30c8\u30d0\u30c3\u30af / NAT\u30e2\u30fc\u30c9", + "tunneling_type": "KNX\u30c8\u30f3\u30cd\u30ea\u30f3\u30b0\u30bf\u30a4\u30d7" } } } diff --git a/homeassistant/components/knx/translations/nl.json b/homeassistant/components/knx/translations/nl.json index 0d0bbffce142da..9b68bd02d2f4ae 100644 --- a/homeassistant/components/knx/translations/nl.json +++ b/homeassistant/components/knx/translations/nl.json @@ -14,7 +14,8 @@ "individual_address": "Individueel adres voor de verbinding", "local_ip": "Lokaal IP van Home Assistant (leeg laten voor automatische detectie)", "port": "Poort", - "route_back": "Route Back / NAT Mode" + "route_back": "Route Back / NAT Mode", + "tunneling_type": "KNX Tunneling Type" }, "description": "Voer de verbindingsinformatie van uw tunneling-apparaat in." }, @@ -59,7 +60,8 @@ "host": "Host", "local_ip": "Lokaal IP (laat leeg indien niet zeker)", "port": "Poort", - "route_back": "Route Back / NAT Mode" + "route_back": "Route Back / NAT Mode", + "tunneling_type": "KNX Tunneling Type" } } } diff --git a/homeassistant/components/knx/translations/pt-BR.json b/homeassistant/components/knx/translations/pt-BR.json index a5343784fabe85..0e8e340296187d 100644 --- a/homeassistant/components/knx/translations/pt-BR.json +++ b/homeassistant/components/knx/translations/pt-BR.json @@ -16,17 +16,51 @@ "port": "Porta", "route_back": "Modo Rota de Retorno / NAT", "tunneling_type": "Tipo de t\u00fanel KNX" - } + }, + "description": "Por favor, digite as informa\u00e7\u00f5es de conex\u00e3o do seu dispositivo de tunelamento." + }, + "routing": { + "data": { + "individual_address": "Endere\u00e7o individual para a conex\u00e3o de roteamento", + "local_ip": "IP local do Home Assistant (deixe vazio para detec\u00e7\u00e3o autom\u00e1tica)", + "multicast_group": "O grupo multicast usado para roteamento", + "multicast_port": "A porta multicast usada para roteamento" + }, + "description": "Por favor, configure as op\u00e7\u00f5es de roteamento." + }, + "tunnel": { + "data": { + "gateway": "Conex\u00e3o do t\u00fanel KNX" + }, + "description": "Selecione um gateway na lista." + }, + "type": { + "data": { + "connection_type": "Tipo de conex\u00e3o KNX" + }, + "description": "Insira o tipo de conex\u00e3o que devemos usar para sua conex\u00e3o KNX.\n AUTOM\u00c1TICO - A integra\u00e7\u00e3o cuida da conectividade ao seu KNX Bus realizando uma varredura de gateway.\n TUNNELING - A integra\u00e7\u00e3o ser\u00e1 conectada ao seu barramento KNX via tunelamento.\n ROUTING - A integra\u00e7\u00e3o ligar-se-\u00e1 ao seu bus KNX atrav\u00e9s de encaminhamento." } } }, "options": { "step": { + "init": { + "data": { + "connection_type": "Tipo de conex\u00e3o KNX", + "individual_address": "Endere\u00e7o individual padr\u00e3o", + "local_ip": "IP local do Home Assistant (use 0.0.0.0 para detec\u00e7\u00e3o autom\u00e1tica)", + "multicast_group": "Grupo multicast usado para roteamento e descoberta", + "multicast_port": "Porta multicast usada para roteamento e descoberta", + "rate_limit": "M\u00e1ximo de telegramas de sa\u00edda por segundo", + "state_updater": "Permitir globalmente estados de leitura a partir do KNX Bus" + } + }, "tunnel": { "data": { "host": "Nome do host", "local_ip": "IP local (deixe em branco se n\u00e3o tiver certeza)", "port": "Porta", + "route_back": "Modo Rota de Retorno / NAT", "tunneling_type": "Tipo de t\u00fanel KNX" } } diff --git a/homeassistant/components/kodi/translations/pt-BR.json b/homeassistant/components/kodi/translations/pt-BR.json index 321f7f6ffef6e4..be8f6cbdfd30cf 100644 --- a/homeassistant/components/kodi/translations/pt-BR.json +++ b/homeassistant/components/kodi/translations/pt-BR.json @@ -4,6 +4,7 @@ "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "no_uuid": "A inst\u00e2ncia Kodi n\u00e3o possui um ID exclusivo. Isso provavelmente se deve a uma vers\u00e3o antiga do Kodi (17.x ou inferior). Voc\u00ea pode configurar a integra\u00e7\u00e3o manualmente ou atualizar para uma vers\u00e3o mais recente do Kodi.", "unknown": "Erro inesperado" }, "error": { @@ -11,25 +12,39 @@ "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, + "flow_title": "{name}", "step": { "credentials": { "data": { "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "description": "Digite seu nome de usu\u00e1rio e senha Kodi. Eles podem ser encontrados em Sistema/Configura\u00e7\u00f5es/Rede/Servi\u00e7os." + }, + "discovery_confirm": { + "description": "Deseja adicionar Kodi (` {name} `) ao Home Assistant?", + "title": "Kodi descoberto" }, "user": { "data": { "host": "Nome do host", "port": "Porta", "ssl": "Usar um certificado SSL" - } + }, + "description": "Informa\u00e7\u00f5es de conex\u00e3o Kodi. Certifique-se de ativar \"Permitir controle do Kodi via HTTP\" em Sistema/Configura\u00e7\u00f5es/Rede/Servi\u00e7os." }, "ws_port": { "data": { "ws_port": "Porta" - } + }, + "description": "A porta WebSocket (\u00e0s vezes chamada de porta TCP no Kodi). Para se conectar pelo WebSocket, voc\u00ea precisa habilitar \"Permitir que programas ... controlem o Kodi\" em Sistema/Configura\u00e7\u00f5es/Rede/Servi\u00e7os. Se o WebSocket n\u00e3o estiver habilitado, remova a porta e deixe em branco." } } + }, + "device_automation": { + "trigger_type": { + "turn_off": "{entity_name} for solicitado para desligar", + "turn_on": "{entity_name} for solicitado para ativar" + } } } \ No newline at end of file diff --git a/homeassistant/components/konnected/translations/pt-BR.json b/homeassistant/components/konnected/translations/pt-BR.json index 24ba6ade2c0c4d..b49b487ab0dfb2 100644 --- a/homeassistant/components/konnected/translations/pt-BR.json +++ b/homeassistant/components/konnected/translations/pt-BR.json @@ -4,12 +4,21 @@ "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", "cannot_connect": "Falha ao conectar", + "not_konn_panel": "N\u00e3o \u00e9 um dispositivo Konnected.io reconhecido", "unknown": "Erro inesperado" }, "error": { "cannot_connect": "Falha ao conectar" }, "step": { + "confirm": { + "description": "Modelo: {model}\nID: {id}\nHost: {host}\nPorta: {port}\n\nVoc\u00ea pode configurar o comportamento do IO e do painel nas configura\u00e7\u00f5es do painel de alarme Konnected.", + "title": "Dispositivo Konnected pronto" + }, + "import_confirm": { + "description": "Um Painel de Alarmes Konnected com ID {id} foi descoberto em configuration.yaml. Esse fluxo permitir\u00e1 que voc\u00ea o importe para uma entrada de configura\u00e7\u00e3o.", + "title": "Importar dispositivo conectado" + }, "user": { "data": { "host": "Endere\u00e7o IP", @@ -24,7 +33,7 @@ "not_konn_panel": "N\u00e3o \u00e9 um dispositivo Konnected.io reconhecido" }, "error": { - "bad_host": "URL de host da API de substitui\u00e7\u00e3o inv\u00e1lido" + "bad_host": "URL substituta para host da API inv\u00e1lido" }, "step": { "options_binary": { @@ -56,6 +65,7 @@ "7": "Zona 7", "out": "SA\u00cdDA" }, + "description": "Descobri um {model} em {host}. Selecione a configura\u00e7\u00e3o base de cada I/O abaixo - dependendo da I/O, pode permitir sensores bin\u00e1rios (contatos abertos/pr\u00f3ximos), sensores digitais (dht e ds18b20) ou sa\u00eddas comutadas. Voc\u00ea poder\u00e1 configurar op\u00e7\u00f5es detalhadas nos pr\u00f3ximos passos.", "title": "Configurar I/O" }, "options_io_ext": { @@ -64,19 +74,35 @@ "11": "Zona 11", "12": "Zona 12", "8": "Zona 8", - "9": "Zona 9" - } + "9": "Zona 9", + "alarm1": "ALARM1", + "alarm2_out2": "OUT2/ALARM2", + "out1": "OUT1" + }, + "description": "Selecione a configura\u00e7\u00e3o da I/O restante abaixo. Voc\u00ea poder\u00e1 configurar op\u00e7\u00f5es detalhadas nos pr\u00f3ximos passos.", + "title": "Configure I/O estendido" }, "options_misc": { "data": { "api_host": "Substituir URL do host da API (opcional)", + "blink": "LED do painel piscando ao enviar mudan\u00e7a de estado", + "discovery": "Responder \u00e0s solicita\u00e7\u00f5es de descoberta em sua rede", "override_api_host": "Substituir o URL padr\u00e3o do painel do host da API do Home Assistant" - } + }, + "description": "Selecione o comportamento desejado para o seu painel", + "title": "Configurar Misc" }, "options_switch": { "data": { - "name": "Nome (opcional)" - } + "activation": "Sa\u00edda quando ligado", + "momentary": "Dura\u00e7\u00e3o do pulso (ms) (opcional)", + "more_states": "Configurar estados adicionais para esta zona", + "name": "Nome (opcional)", + "pause": "Pausa entre pulsos (ms) (opcional)", + "repeat": "Intervalo para repetir (-1=infinito) (opcional)" + }, + "description": "Selecione as op\u00e7\u00f5es para o switch conectado a {zone}: estado {state}", + "title": "Configurar o switch de sa\u00edda" } } } diff --git a/homeassistant/components/konnected/translations/uk.json b/homeassistant/components/konnected/translations/uk.json index 92cd3744d945c2..6a3170ffde733b 100644 --- a/homeassistant/components/konnected/translations/uk.json +++ b/homeassistant/components/konnected/translations/uk.json @@ -64,7 +64,7 @@ "7": "\u0417\u043e\u043d\u0430 7", "out": "\u0412\u0418\u0425\u0406\u0414" }, - "description": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 {model} \u0437 \u0430\u0434\u0440\u0435\u0441\u043e\u044e {host}. \u0417\u0430\u043b\u0435\u0436\u043d\u043e \u0432\u0456\u0434 \u043e\u0431\u0440\u0430\u043d\u043e\u0457 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u0457 \u0432\u0445\u043e\u0434\u0456\u0432 / \u0432\u0438\u0445\u043e\u0434\u0456\u0432, \u0434\u043e \u043f\u0430\u043d\u0435\u043b\u0456 \u043c\u043e\u0436\u0443\u0442\u044c \u0431\u0443\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0456 \u0431\u0456\u043d\u0430\u0440\u043d\u0456 \u0441\u0435\u043d\u0441\u043e\u0440\u0438 (\u0434\u0430\u0442\u0447\u0438\u043a\u0438 \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u0442\u044f / \u0437\u0430\u043a\u0440\u0438\u0442\u0442\u044f), \u0446\u0438\u0444\u0440\u043e\u0432\u0456 \u0441\u0435\u043d\u0441\u043e\u0440\u0438 (dht \u0456 ds18b20) \u0430\u0431\u043e \u043f\u0435\u0440\u0435\u043c\u0438\u043a\u0430\u044e\u0447\u0456 \u0432\u0438\u0445\u043e\u0434\u0438. \u0411\u0456\u043b\u044c\u0448 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0431\u0443\u0434\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0435 \u043d\u0430 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0438\u0445 \u043a\u0440\u043e\u043a\u0430\u0445.", + "description": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e {model} \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043e\u044e {host}. \u0417\u0430\u043b\u0435\u0436\u043d\u043e \u0432\u0456\u0434 \u043e\u0431\u0440\u0430\u043d\u043e\u0457 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u0457 \u0432\u0445\u043e\u0434\u0456\u0432 / \u0432\u0438\u0445\u043e\u0434\u0456\u0432, \u0434\u043e \u043f\u0430\u043d\u0435\u043b\u0456 \u043c\u043e\u0436\u0443\u0442\u044c \u0431\u0443\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0456 \u0431\u0456\u043d\u0430\u0440\u043d\u0456 \u0441\u0435\u043d\u0441\u043e\u0440\u0438 (\u043a\u043e\u043d\u0442\u0430\u043a\u0442\u0438 \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u0442\u044f/\u0437\u0430\u043a\u0440\u0438\u0442\u0442\u044f), \u0446\u0438\u0444\u0440\u043e\u0432\u0456 \u0441\u0435\u043d\u0441\u043e\u0440\u0438 (dht \u0456 ds18b20) \u0430\u0431\u043e \u043f\u0435\u0440\u0435\u043c\u0438\u043a\u0430\u044e\u0447\u0456 \u0432\u0438\u0445\u043e\u0434\u0438. \u0411\u0456\u043b\u044c\u0448 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0431\u0443\u0434\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0435 \u043d\u0430 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0438\u0445 \u043a\u0440\u043e\u043a\u0430\u0445.", "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0445\u043e\u0434\u0456\u0432 / \u0432\u0438\u0445\u043e\u0434\u0456\u0432" }, "options_io_ext": { diff --git a/homeassistant/components/kostal_plenticore/translations/pt-BR.json b/homeassistant/components/kostal_plenticore/translations/pt-BR.json index b829ba6e92b458..a670c5a41be320 100644 --- a/homeassistant/components/kostal_plenticore/translations/pt-BR.json +++ b/homeassistant/components/kostal_plenticore/translations/pt-BR.json @@ -16,5 +16,6 @@ } } } - } + }, + "title": "Inversor Solar Kostal Plenticore" } \ No newline at end of file diff --git a/homeassistant/components/kraken/translations/pt-BR.json b/homeassistant/components/kraken/translations/pt-BR.json index 9ce2cf2399e04a..955386f20982bf 100644 --- a/homeassistant/components/kraken/translations/pt-BR.json +++ b/homeassistant/components/kraken/translations/pt-BR.json @@ -8,5 +8,15 @@ "description": "Deseja iniciar a configura\u00e7\u00e3o?" } } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Intervalo de atualiza\u00e7\u00e3o", + "tracked_asset_pairs": "Pares de ativos rastreados" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/kulersky/translations/pt-BR.json b/homeassistant/components/kulersky/translations/pt-BR.json index d5efbb90261324..1778d39a7d0829 100644 --- a/homeassistant/components/kulersky/translations/pt-BR.json +++ b/homeassistant/components/kulersky/translations/pt-BR.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "no_devices_found": "Nenhum dispositivo encontrado na rede", "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "step": { diff --git a/homeassistant/components/kulersky/translations/uk.json b/homeassistant/components/kulersky/translations/uk.json index 292861e9129dbd..5c2489c2a18ab7 100644 --- a/homeassistant/components/kulersky/translations/uk.json +++ b/homeassistant/components/kulersky/translations/uk.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\u041f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456 \u0432 \u043c\u0435\u0440\u0435\u0436\u0456.", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "step": { "confirm": { diff --git a/homeassistant/components/lcn/translations/pt-BR.json b/homeassistant/components/lcn/translations/pt-BR.json new file mode 100644 index 00000000000000..9898533ea72a68 --- /dev/null +++ b/homeassistant/components/lcn/translations/pt-BR.json @@ -0,0 +1,10 @@ +{ + "device_automation": { + "trigger_type": { + "fingerprint": "c\u00f3digo de impress\u00e3o digital recebido", + "send_keys": "enviar chaves recebidas", + "transmitter": "c\u00f3digo do transmissor recebido", + "transponder": "c\u00f3digo do transponder recebido" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lifx/translations/pt-BR.json b/homeassistant/components/lifx/translations/pt-BR.json index 83a7518386b64f..f67284d8b5d3b9 100644 --- a/homeassistant/components/lifx/translations/pt-BR.json +++ b/homeassistant/components/lifx/translations/pt-BR.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "no_devices_found": "Nenhum dispositivo encontrado na rede", "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "step": { diff --git a/homeassistant/components/lifx/translations/uk.json b/homeassistant/components/lifx/translations/uk.json index 8c32e79533dc00..556729e895b842 100644 --- a/homeassistant/components/lifx/translations/uk.json +++ b/homeassistant/components/lifx/translations/uk.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\u041f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456 \u0432 \u043c\u0435\u0440\u0435\u0436\u0456.", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "step": { "confirm": { diff --git a/homeassistant/components/light/translations/nl.json b/homeassistant/components/light/translations/nl.json index 190ed3f52bdc9b..f70830601c7ae9 100644 --- a/homeassistant/components/light/translations/nl.json +++ b/homeassistant/components/light/translations/nl.json @@ -13,6 +13,8 @@ "is_on": "{entity_name} is ingeschakeld" }, "trigger_type": { + "changed_states": "{entity_name} in- of uitgeschakeld", + "toggled": "{entity_name} in- of uitgeschakeld", "turned_off": "{entity_name} is uitgeschakeld", "turned_on": "{entity_name} is ingeschakeld" } diff --git a/homeassistant/components/light/translations/pt-BR.json b/homeassistant/components/light/translations/pt-BR.json index 919fdb89afb698..f884baf9b3cedd 100644 --- a/homeassistant/components/light/translations/pt-BR.json +++ b/homeassistant/components/light/translations/pt-BR.json @@ -1,19 +1,22 @@ { "device_automation": { "action_type": { + "brightness_decrease": "Diminuir o brilho {entity_name}", + "brightness_increase": "Aumente o brilho {entity_name}", + "flash": "Flash {entity_name}", "toggle": "Alternar {entity_name}", "turn_off": "Desligar {entity_name}", "turn_on": "Ligar {entity_name}" }, "condition_type": { - "is_off": "{entity_name} est\u00e1 desligado", - "is_on": "{entity_name} est\u00e1 ligado" + "is_off": "{entity_name} est\u00e1 desligada", + "is_on": "{entity_name} est\u00e1 ligada" }, "trigger_type": { - "changed_states": "{entity_name} ligado ou desligado", - "toggled": "{entity_name} ligado ou desligado", - "turned_off": "{entity_name} desligado", - "turned_on": "{entity_name} ligado" + "changed_states": "{entity_name} for ligada ou desligada", + "toggled": "{entity_name} for ligada ou desligada", + "turned_off": "{entity_name} for desligada", + "turned_on": "{entity_name} for ligada" } }, "state": { diff --git a/homeassistant/components/litejet/translations/pt-BR.json b/homeassistant/components/litejet/translations/pt-BR.json index fdc79cd04e9cf7..e41fc57329e716 100644 --- a/homeassistant/components/litejet/translations/pt-BR.json +++ b/homeassistant/components/litejet/translations/pt-BR.json @@ -3,11 +3,26 @@ "abort": { "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, + "error": { + "open_failed": "N\u00e3o \u00e9 poss\u00edvel abrir a porta serial especificada." + }, "step": { "user": { "data": { "port": "Porta" - } + }, + "description": "Conecte a porta RS232-2 do LiteJet ao seu computador e digite o caminho para o dispositivo de porta serial. \n\n O LiteJet MCP deve ser configurado para 19,2 K baud, 8 bits de dados, 1 bit de parada, sem paridade e para transmitir um 'CR' ap\u00f3s cada resposta.", + "title": "Conecte-se ao LiteJet" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "default_transition": "Transi\u00e7\u00e3o padr\u00e3o (segundos)" + }, + "title": "Configurar LiteJet" } } } diff --git a/homeassistant/components/local_ip/translations/uk.json b/homeassistant/components/local_ip/translations/uk.json index 52aed47fa20763..d8ec556180f838 100644 --- a/homeassistant/components/local_ip/translations/uk.json +++ b/homeassistant/components/local_ip/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "step": { "user": { diff --git a/homeassistant/components/locative/translations/ja.json b/homeassistant/components/locative/translations/ja.json index 89003e78a9dbee..a4f03bde29f8de 100644 --- a/homeassistant/components/locative/translations/ja.json +++ b/homeassistant/components/locative/translations/ja.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Home Assistant Cloud\u306b\u63a5\u7d9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002", "single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002", "webhook_not_internet_accessible": "Webhook\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u53d7\u4fe1\u3059\u308b\u306b\u306f\u3001Home Assistant\u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u306b\u3001\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u304b\u3089\u30a2\u30af\u30bb\u30b9\u3067\u304d\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002" }, diff --git a/homeassistant/components/locative/translations/nl.json b/homeassistant/components/locative/translations/nl.json index ed39d00430b1d8..0a459e566c56dc 100644 --- a/homeassistant/components/locative/translations/nl.json +++ b/homeassistant/components/locative/translations/nl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Niet verbonden met Home Assistant Cloud.", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." }, diff --git a/homeassistant/components/locative/translations/pt-BR.json b/homeassistant/components/locative/translations/pt-BR.json index 400750b8fecfc1..d134a5113f4420 100644 --- a/homeassistant/components/locative/translations/pt-BR.json +++ b/homeassistant/components/locative/translations/pt-BR.json @@ -2,7 +2,8 @@ "config": { "abort": { "cloud_not_connected": "N\u00e3o conectado ao Home Assistant Cloud.", - "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", + "webhook_not_internet_accessible": "Sua inst\u00e2ncia do Home Assistant precisa estar acess\u00edvel pela Internet para receber mensagens de webhook." }, "create_entry": { "default": "Para enviar locais para o Home Assistant, voc\u00ea precisar\u00e1 configurar o recurso webhook no aplicativo Locative. \n\n Preencha as seguintes informa\u00e7\u00f5es: \n\n - URL: ` {webhook_url} ` \n - M\u00e9todo: POST \n\n Veja [a documenta\u00e7\u00e3o] ( {docs_url} ) para mais detalhes." diff --git a/homeassistant/components/locative/translations/uk.json b/homeassistant/components/locative/translations/uk.json index d9a4713087117f..ccfac69f0a9062 100644 --- a/homeassistant/components/locative/translations/uk.json +++ b/homeassistant/components/locative/translations/uk.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e.", + "cloud_not_connected": "\u041d\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0434\u043e Home Assistant Cloud.", + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f.", "webhook_not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u043f\u043e\u0432\u0438\u043d\u0435\u043d \u0431\u0443\u0442\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0439 \u0437 \u0406\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443 \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f Webhook-\u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u044c." }, "create_entry": { diff --git a/homeassistant/components/lock/translations/pt-BR.json b/homeassistant/components/lock/translations/pt-BR.json index f9c4e12214cc26..02ce765e7d13a5 100644 --- a/homeassistant/components/lock/translations/pt-BR.json +++ b/homeassistant/components/lock/translations/pt-BR.json @@ -4,6 +4,14 @@ "lock": "Bloquear {entity_name}", "open": "Abrir {entity_name}", "unlock": "Desbloquear {entity_name}" + }, + "condition_type": { + "is_locked": "{entity_name} est\u00e1 bloqueado", + "is_unlocked": "{entity_name} est\u00e1 desbloqueado" + }, + "trigger_type": { + "locked": "{entity_name} for bloqueado", + "unlocked": "{entity_name} for desbloqueado" } }, "state": { diff --git a/homeassistant/components/logi_circle/translations/pt-BR.json b/homeassistant/components/logi_circle/translations/pt-BR.json index 10b985c11c6f47..3086d6560f199f 100644 --- a/homeassistant/components/logi_circle/translations/pt-BR.json +++ b/homeassistant/components/logi_circle/translations/pt-BR.json @@ -13,7 +13,7 @@ }, "step": { "auth": { - "description": "Por favor, siga o link abaixo e Aceite o acesso \u00e0 sua conta do Logi Circle, depois volte e pressione Enviar abaixo. \n\n [Link] ( {authorization_url} )", + "description": "Por favor, siga o link abaixo e **Aceite** o acesso \u00e0 sua conta do Logi Circle, depois volte e pressione **Enviar** abaixo. \n\n [Link] ( {authorization_url} )", "title": "Autenticar com o Logi Circle" }, "user": { diff --git a/homeassistant/components/lookin/translations/pt-BR.json b/homeassistant/components/lookin/translations/pt-BR.json index c6fccf44ba23d8..38a6ae113589ec 100644 --- a/homeassistant/components/lookin/translations/pt-BR.json +++ b/homeassistant/components/lookin/translations/pt-BR.json @@ -4,11 +4,11 @@ "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", "cannot_connect": "Falha ao conectar", - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" + "no_devices_found": "Nenhum dispositivo encontrado na rede" }, "error": { "cannot_connect": "Falha ao conectar", - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "no_devices_found": "Nenhum dispositivo encontrado na rede", "unknown": "Erro inesperado" }, "flow_title": "{name} ({host})", diff --git a/homeassistant/components/lovelace/translations/pt-BR.json b/homeassistant/components/lovelace/translations/pt-BR.json new file mode 100644 index 00000000000000..2ff25d17161dd0 --- /dev/null +++ b/homeassistant/components/lovelace/translations/pt-BR.json @@ -0,0 +1,10 @@ +{ + "system_health": { + "info": { + "dashboards": "Pain\u00e9is", + "mode": "Modo", + "resources": "Recursos", + "views": "Visualiza\u00e7\u00f5es" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/pt-BR.json b/homeassistant/components/lutron_caseta/translations/pt-BR.json index e3451a9a058e46..28a85a8820deae 100644 --- a/homeassistant/components/lutron_caseta/translations/pt-BR.json +++ b/homeassistant/components/lutron_caseta/translations/pt-BR.json @@ -2,21 +2,75 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", - "cannot_connect": "Falha ao conectar" + "cannot_connect": "Falha ao conectar", + "not_lutron_device": "O dispositivo descoberto n\u00e3o \u00e9 um dispositivo Lutron" }, "error": { "cannot_connect": "Falha ao conectar" }, + "flow_title": "{name} ( {host} )", "step": { "import_failed": { "description": "N\u00e3o foi poss\u00edvel configurar a ponte (host: {host}) importada do configuration.yaml.", "title": "Falha ao importar a configura\u00e7\u00e3o da ponte Cas\u00e9ta." }, + "link": { + "description": "Para parear com {name} ( {host} ), ap\u00f3s enviar este formul\u00e1rio, pressione o bot\u00e3o preto na parte de tr\u00e1s da ponte.", + "title": "Parear com a ponte" + }, "user": { "data": { "host": "Nome do host" - } + }, + "description": "Digite o endere\u00e7o IP do dispositivo.", + "title": "Conecte-se automaticamente \u00e0 ponte" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Primeiro bot\u00e3o", + "button_2": "Segundo bot\u00e3o", + "button_3": "Terceiro bot\u00e3o", + "button_4": "Quarto bot\u00e3o", + "close_1": "Fechar 1", + "close_2": "Fechar 2", + "close_3": "Fechar 3", + "close_4": "Fechar 4", + "close_all": "Feche tudo", + "group_1_button_1": "Primeiro bot\u00e3o do primeiro grupo", + "group_1_button_2": "Primeiro bot\u00e3o segundo grupo", + "group_2_button_1": "Primeiro bot\u00e3o do segundo grupo", + "group_2_button_2": "Segundo bot\u00e3o do segundo grupo", + "lower": "Abaixar", + "lower_1": "Inferior 1", + "lower_2": "Inferior 2", + "lower_3": "Inferior 3", + "lower_4": "Inferior 4", + "lower_all": "Baixar tudo", + "off": "Desligado", + "on": "Ligado", + "open_1": "Abrir 1", + "open_2": "Abrir 2", + "open_3": "Abrir 3", + "open_4": "Abrir 4", + "open_all": "Abra tudo", + "raise": "Aumentar", + "raise_1": "Aumentar 1", + "raise_2": "Aumentar 2", + "raise_3": "Aumentar 3", + "raise_4": "Aumentar 4", + "raise_all": "Aumentar tudo", + "stop": "Parar (favorito)", + "stop_1": "Parar 1", + "stop_2": "Parar 2", + "stop_3": "Parar 3", + "stop_4": "Parar 4", + "stop_all": "Parar tudo" + }, + "trigger_type": { + "press": "\"{subtype}\" pressionado", + "release": "\"{subtype}\" lan\u00e7ado" + } } } \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/pt-BR.json b/homeassistant/components/lyric/translations/pt-BR.json index 1e17e604c38eb1..907a396d5e2e19 100644 --- a/homeassistant/components/lyric/translations/pt-BR.json +++ b/homeassistant/components/lyric/translations/pt-BR.json @@ -9,7 +9,11 @@ "default": "Autenticado com sucesso" }, "step": { + "pick_implementation": { + "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" + }, "reauth_confirm": { + "description": "A integra\u00e7\u00e3o do Lyric precisa autenticar novamente sua conta.", "title": "Reautenticar Integra\u00e7\u00e3o" } } diff --git a/homeassistant/components/mailgun/translations/ja.json b/homeassistant/components/mailgun/translations/ja.json index cacb7e9250298d..58818dd99de6aa 100644 --- a/homeassistant/components/mailgun/translations/ja.json +++ b/homeassistant/components/mailgun/translations/ja.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Home Assistant Cloud\u306b\u63a5\u7d9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002", "single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002", "webhook_not_internet_accessible": "Webhook\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u53d7\u4fe1\u3059\u308b\u306b\u306f\u3001Home Assistant\u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u306b\u3001\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u304b\u3089\u30a2\u30af\u30bb\u30b9\u3067\u304d\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002" }, diff --git a/homeassistant/components/mailgun/translations/nl.json b/homeassistant/components/mailgun/translations/nl.json index dea33946af51f6..5e84c62a314c33 100644 --- a/homeassistant/components/mailgun/translations/nl.json +++ b/homeassistant/components/mailgun/translations/nl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Niet verbonden met Home Assistant Cloud.", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." }, diff --git a/homeassistant/components/mailgun/translations/pt-BR.json b/homeassistant/components/mailgun/translations/pt-BR.json index 7c0caa689972fd..7985d9ddbfb8f6 100644 --- a/homeassistant/components/mailgun/translations/pt-BR.json +++ b/homeassistant/components/mailgun/translations/pt-BR.json @@ -2,7 +2,8 @@ "config": { "abort": { "cloud_not_connected": "N\u00e3o conectado ao Home Assistant Cloud.", - "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", + "webhook_not_internet_accessible": "Sua inst\u00e2ncia do Home Assistant precisa estar acess\u00edvel pela Internet para receber mensagens de webhook." }, "create_entry": { "default": "Para enviar eventos para o Home Assistant, voc\u00ea precisar\u00e1 configurar [Webhooks com Mailgun]({mailgun_url}). \n\n Preencha as seguintes informa\u00e7\u00f5es: \n\n - URL: `{webhook_url}` \n - M\u00e9todo: POST \n - Tipo de Conte\u00fado: application/json \n\n Veja [a documenta\u00e7\u00e3o] ({docs_url}) sobre como configurar automa\u00e7\u00f5es para manipular dados de entrada." diff --git a/homeassistant/components/mailgun/translations/uk.json b/homeassistant/components/mailgun/translations/uk.json index d999b52085a213..0c31d070bec298 100644 --- a/homeassistant/components/mailgun/translations/uk.json +++ b/homeassistant/components/mailgun/translations/uk.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e.", + "cloud_not_connected": "\u041d\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0434\u043e Home Assistant Cloud.", + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f.", "webhook_not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u043f\u043e\u0432\u0438\u043d\u0435\u043d \u0431\u0443\u0442\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0439 \u0437 \u0406\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443 \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f Webhook-\u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u044c." }, "create_entry": { diff --git a/homeassistant/components/mazda/translations/pt-BR.json b/homeassistant/components/mazda/translations/pt-BR.json index 4c13fdf68da39a..7b28450bfd0930 100644 --- a/homeassistant/components/mazda/translations/pt-BR.json +++ b/homeassistant/components/mazda/translations/pt-BR.json @@ -5,6 +5,7 @@ "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { + "account_locked": "Conta bloqueada. Por favor, tente novamente mais tarde.", "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" @@ -12,9 +13,14 @@ "step": { "user": { "data": { - "password": "Senha" - } + "email": "Email", + "password": "Senha", + "region": "Regi\u00e3o" + }, + "description": "Digite o endere\u00e7o de e-mail e senha que voc\u00ea usa para entrar no aplicativo MyMazda.", + "title": "Mazda Connected Services - Adicionar conta" } } - } + }, + "title": "Servi\u00e7os conectados Mazda" } \ No newline at end of file diff --git a/homeassistant/components/media_player/translations/nl.json b/homeassistant/components/media_player/translations/nl.json index 6ad22742533bf7..5fae215f4f9c85 100644 --- a/homeassistant/components/media_player/translations/nl.json +++ b/homeassistant/components/media_player/translations/nl.json @@ -8,6 +8,7 @@ "is_playing": "{entity_name} wordt afgespeeld" }, "trigger_type": { + "changed_states": "{entity_name} veranderde van status", "idle": "{entity_name} wordt inactief", "paused": "{entity_name} is gepauzeerd", "playing": "{entity_name} begint te spelen", diff --git a/homeassistant/components/media_player/translations/pt-BR.json b/homeassistant/components/media_player/translations/pt-BR.json index 2efe036e309c2b..147f66ec0e72d7 100644 --- a/homeassistant/components/media_player/translations/pt-BR.json +++ b/homeassistant/components/media_player/translations/pt-BR.json @@ -1,7 +1,19 @@ { "device_automation": { + "condition_type": { + "is_idle": "{entity_name} est\u00e1 ocioso", + "is_off": "{entity_name} est\u00e1 desligado", + "is_on": "{entity_name} est\u00e1 ligado", + "is_paused": "{entity_name} est\u00e1 pausado", + "is_playing": "{entity_name} est\u00e1 reproduzindo" + }, "trigger_type": { - "changed_states": "{entity_name} ligado ou desligado" + "changed_states": "{entity_name} ligado ou desligado", + "idle": "{entity_name} ficar ocioso", + "paused": "{entity_name} for pausado", + "playing": "{entity_name} come\u00e7ar a reproduzir", + "turned_off": "{entity_name} for desligado", + "turned_on": "{entity_name} for ligado" } }, "state": { @@ -10,9 +22,9 @@ "off": "Desligado", "on": "Ligado", "paused": "Pausado", - "playing": "Tocando", + "playing": "Reproduzindo", "standby": "Em espera" } }, - "title": "Media player" + "title": "Navegador multim\u00eddia" } \ No newline at end of file diff --git a/homeassistant/components/melcloud/translations/pt-BR.json b/homeassistant/components/melcloud/translations/pt-BR.json index eac56eb486a695..6cd33d9fbb13d6 100644 --- a/homeassistant/components/melcloud/translations/pt-BR.json +++ b/homeassistant/components/melcloud/translations/pt-BR.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Integra\u00e7\u00e3o MELCloud j\u00e1 configurada para este email. O token de acesso foi atualizado." + }, "error": { "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", @@ -8,8 +11,11 @@ "step": { "user": { "data": { - "password": "Senha" - } + "password": "Senha", + "username": "Email" + }, + "description": "Conecte-se usando sua conta MELCloud.", + "title": "Conecte-se ao MELCloud" } } } diff --git a/homeassistant/components/met/translations/el.json b/homeassistant/components/met/translations/el.json index dd50a5ac784424..6759159b1db517 100644 --- a/homeassistant/components/met/translations/el.json +++ b/homeassistant/components/met/translations/el.json @@ -2,6 +2,11 @@ "config": { "abort": { "no_home": "\u0394\u03b5\u03bd \u03ad\u03c7\u03bf\u03c5\u03bd \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03c3\u03c5\u03bd\u03c4\u03b5\u03c4\u03b1\u03b3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03ba\u03b1\u03c4\u03bf\u03b9\u03ba\u03af\u03b1\u03c2 \u03c3\u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd \u03c4\u03bf\u03c5 Home Assistant" + }, + "step": { + "user": { + "description": "Meteorologisk institutt" + } } } } \ No newline at end of file diff --git a/homeassistant/components/met/translations/pt-BR.json b/homeassistant/components/met/translations/pt-BR.json index e0520cc442b56c..d87561136a65f5 100644 --- a/homeassistant/components/met/translations/pt-BR.json +++ b/homeassistant/components/met/translations/pt-BR.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "no_home": "Nenhuma coordenada de casa est\u00e1 definida na configura\u00e7\u00e3o do Home Assistant" + }, "error": { "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" }, diff --git a/homeassistant/components/met_eireann/translations/pt-BR.json b/homeassistant/components/met_eireann/translations/pt-BR.json index ee4ea6b05df82a..d72a8d1ccfd242 100644 --- a/homeassistant/components/met_eireann/translations/pt-BR.json +++ b/homeassistant/components/met_eireann/translations/pt-BR.json @@ -6,10 +6,13 @@ "step": { "user": { "data": { + "elevation": "Eleva\u00e7\u00e3o", "latitude": "Latitude", "longitude": "Longitude", "name": "Nome" - } + }, + "description": "Insira sua localiza\u00e7\u00e3o para usar os dados meteorol\u00f3gicos da API de previs\u00e3o meteorol\u00f3gica p\u00fablica do Met \u00c9ireann", + "title": "Localiza\u00e7\u00e3o" } } } diff --git a/homeassistant/components/meteo_france/translations/pt-BR.json b/homeassistant/components/meteo_france/translations/pt-BR.json index 2aab8c8f8ec80e..456c6eef17cf03 100644 --- a/homeassistant/components/meteo_france/translations/pt-BR.json +++ b/homeassistant/components/meteo_france/translations/pt-BR.json @@ -4,10 +4,31 @@ "already_configured": "Localiza\u00e7\u00e3o j\u00e1 est\u00e1 configurada", "unknown": "Erro inesperado" }, + "error": { + "empty": "Nenhum resultado na pesquisa da cidade: verifique o campo da cidade" + }, "step": { + "cities": { + "data": { + "city": "Cidade" + }, + "description": "Escolha sua cidade na lista", + "title": "M\u00e9t\u00e9o-France" + }, "user": { "data": { "city": "Cidade" + }, + "description": "Insira o c\u00f3digo postal (somente para a Fran\u00e7a, recomendado) ou o nome da cidade", + "title": "M\u00e9t\u00e9o-France" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "mode": "Modo de previs\u00e3o" } } } diff --git a/homeassistant/components/meteoclimatic/translations/pt-BR.json b/homeassistant/components/meteoclimatic/translations/pt-BR.json index 118cb50d8da864..c81109b8939dd2 100644 --- a/homeassistant/components/meteoclimatic/translations/pt-BR.json +++ b/homeassistant/components/meteoclimatic/translations/pt-BR.json @@ -5,7 +5,16 @@ "unknown": "Erro inesperado" }, "error": { - "not_found": "[%key:common::config_flow::abort::no_devices_found%]" + "not_found": "Nenhum dispositivo encontrado na rede" + }, + "step": { + "user": { + "data": { + "code": "C\u00f3digo da esta\u00e7\u00e3o" + }, + "description": "Digite o c\u00f3digo da esta\u00e7\u00e3o Meteoclim\u00e1tica (por exemplo, ESCAT4300000043206B)", + "title": "Meteoclimatic" + } } } } \ No newline at end of file diff --git a/homeassistant/components/metoffice/translations/pt-BR.json b/homeassistant/components/metoffice/translations/pt-BR.json index 29bb6935cf50fb..3e0fc4b79d358f 100644 --- a/homeassistant/components/metoffice/translations/pt-BR.json +++ b/homeassistant/components/metoffice/translations/pt-BR.json @@ -13,7 +13,9 @@ "api_key": "Chave da API", "latitude": "Latitude", "longitude": "Longitude" - } + }, + "description": "A latitude e a longitude ser\u00e3o usadas para encontrar a esta\u00e7\u00e3o meteorol\u00f3gica mais pr\u00f3xima.", + "title": "Conecte-se ao Met Office do Reino Unido" } } } diff --git a/homeassistant/components/mikrotik/translations/pt-BR.json b/homeassistant/components/mikrotik/translations/pt-BR.json index ba24f5937fe9db..0fb66a063bddd7 100644 --- a/homeassistant/components/mikrotik/translations/pt-BR.json +++ b/homeassistant/components/mikrotik/translations/pt-BR.json @@ -21,5 +21,16 @@ "title": "Configurar roteador Mikrotik" } } + }, + "options": { + "step": { + "device_tracker": { + "data": { + "arp_ping": "Habilitar ping ARP", + "detection_time": "Considere o tempo para definir em casa", + "force_dhcp": "For\u00e7ar varredura usando DHCP" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/minecraft_server/translations/pt-BR.json b/homeassistant/components/minecraft_server/translations/pt-BR.json index 2af6adcd47d531..d71651e98e8561 100644 --- a/homeassistant/components/minecraft_server/translations/pt-BR.json +++ b/homeassistant/components/minecraft_server/translations/pt-BR.json @@ -3,12 +3,19 @@ "abort": { "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" }, + "error": { + "cannot_connect": "Falha ao conectar ao servidor. Verifique o host e a porta e tente novamente. Verifique tamb\u00e9m se voc\u00ea est\u00e1 executando pelo menos a vers\u00e3o 1.7 do Minecraft em seu servidor.", + "invalid_ip": "O endere\u00e7o IP \u00e9 inv\u00e1lido (o endere\u00e7o MAC n\u00e3o p\u00f4de ser determinado). Corrija-o e tente novamente.", + "invalid_port": "A porta deve estar no intervalo de 1024 a 65535. Corrija-a e tente novamente." + }, "step": { "user": { "data": { "host": "Nome do host", "name": "Nome" - } + }, + "description": "Configure sua inst\u00e2ncia do Minecraft Server para permitir o monitoramento.", + "title": "Vincule seu servidor Minecraft" } } } diff --git a/homeassistant/components/mobile_app/translations/pt-BR.json b/homeassistant/components/mobile_app/translations/pt-BR.json index 4c211f4bc53926..f108db65cad9f7 100644 --- a/homeassistant/components/mobile_app/translations/pt-BR.json +++ b/homeassistant/components/mobile_app/translations/pt-BR.json @@ -8,5 +8,11 @@ "description": "Deseja configurar o componente do aplicativo m\u00f3vel?" } } - } + }, + "device_automation": { + "action_type": { + "notify": "Enviar uma notifica\u00e7\u00e3o" + } + }, + "title": "Aplicativo mobile" } \ No newline at end of file diff --git a/homeassistant/components/modem_callerid/translations/pt-BR.json b/homeassistant/components/modem_callerid/translations/pt-BR.json index 84b9d25418a8f5..39394d0752e2a2 100644 --- a/homeassistant/components/modem_callerid/translations/pt-BR.json +++ b/homeassistant/components/modem_callerid/translations/pt-BR.json @@ -2,17 +2,24 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", - "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento" + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "no_devices_found": "Nenhum dispositivo restante encontrado" }, "error": { "cannot_connect": "Falha ao conectar" }, "step": { + "usb_confirm": { + "description": "Esta \u00e9 uma integra\u00e7\u00e3o para chamadas fixas usando um modem de voz CX93001. Isso pode recuperar informa\u00e7\u00f5es de identifica\u00e7\u00e3o de chamadas com a op\u00e7\u00e3o de rejeitar uma chamada recebida.", + "title": "Modem do telefone" + }, "user": { "data": { "name": "Nome", "port": "Porta" - } + }, + "description": "Esta \u00e9 uma integra\u00e7\u00e3o para chamadas fixas usando um modem de voz CX93001. Isso pode recuperar informa\u00e7\u00f5es de identifica\u00e7\u00e3o de chamadas com a op\u00e7\u00e3o de rejeitar uma chamada recebida.", + "title": "Modem do telefone" } } } diff --git a/homeassistant/components/modern_forms/translations/pt-BR.json b/homeassistant/components/modern_forms/translations/pt-BR.json index 4296c2d05f9437..07a68e2fb84ded 100644 --- a/homeassistant/components/modern_forms/translations/pt-BR.json +++ b/homeassistant/components/modern_forms/translations/pt-BR.json @@ -7,6 +7,7 @@ "error": { "cannot_connect": "Falha ao conectar" }, + "flow_title": "{name}", "step": { "confirm": { "description": "Deseja iniciar a configura\u00e7\u00e3o?" @@ -14,8 +15,14 @@ "user": { "data": { "host": "Nome do host" - } + }, + "description": "Configure seu ventilador do Modern Forms para integrar com o Home Assistant." + }, + "zeroconf_confirm": { + "description": "Deseja adicionar o f\u00e3 do Modern Forms chamado `{name}` ao Home Assistant?", + "title": "Dispositivo de ventilador do Modern Forms descoberto" } } - } + }, + "title": "Formas modernas" } \ No newline at end of file diff --git a/homeassistant/components/monoprice/translations/el.json b/homeassistant/components/monoprice/translations/el.json index d72413d8e86f4f..28c82949398743 100644 --- a/homeassistant/components/monoprice/translations/el.json +++ b/homeassistant/components/monoprice/translations/el.json @@ -13,5 +13,20 @@ "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" } } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b7\u03b3\u03ae\u03c2 #1", + "source_2": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b7\u03b3\u03ae\u03c2 #2", + "source_3": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b7\u03b3\u03ae\u03c2 #3", + "source_4": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b7\u03b3\u03ae\u03c2 #4", + "source_5": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b7\u03b3\u03ae\u03c2 #5", + "source_6": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b7\u03b3\u03ae\u03c2 #6" + }, + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c0\u03b7\u03b3\u03ce\u03bd" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/monoprice/translations/pt-BR.json b/homeassistant/components/monoprice/translations/pt-BR.json index 486d16cf25a825..2935c6510be22c 100644 --- a/homeassistant/components/monoprice/translations/pt-BR.json +++ b/homeassistant/components/monoprice/translations/pt-BR.json @@ -21,5 +21,20 @@ "title": "Conecte-se ao dispositivo" } } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "Nome da fonte #1", + "source_2": "Nome da fonte #2", + "source_3": "Nome da fonte #3", + "source_4": "Nome da fonte #4", + "source_5": "Nome da fonte #5", + "source_6": "Nome da fonte #6" + }, + "title": "Configurar as fontes" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/motion_blinds/translations/pt-BR.json b/homeassistant/components/motion_blinds/translations/pt-BR.json index 50b2728a93b232..dcabdbd16e5c79 100644 --- a/homeassistant/components/motion_blinds/translations/pt-BR.json +++ b/homeassistant/components/motion_blinds/translations/pt-BR.json @@ -5,30 +5,45 @@ "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", "connection_error": "Falha ao conectar" }, + "error": { + "discovery_error": "Falha ao descobrir um Motion Gateway", + "invalid_interface": "Interface de rede inv\u00e1lida" + }, + "flow_title": "Cortinas de movimento", "step": { "connect": { "data": { "api_key": "Chave da API", "interface": "A interface de rede a ser utilizada" - } + }, + "description": "Voc\u00ea precisar\u00e1 da chave de API de 16 caracteres, consulte https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key para obter instru\u00e7\u00f5es", + "title": "Cortinas de movimento" }, "select": { "data": { "select_ip": "Endere\u00e7o IP" - } + }, + "description": "Execute a configura\u00e7\u00e3o novamente se desejar conectar Motion Gateways adicionais", + "title": "Selecione o Motion Gateway que voc\u00ea deseja conectar" }, "user": { "data": { "api_key": "Chave da API", "host": "Endere\u00e7o IP" - } + }, + "description": "Conecte-se ao seu Motion Gateway, se o endere\u00e7o IP n\u00e3o estiver definido, a descoberta autom\u00e1tica ser\u00e1 usada", + "title": "Cortinas de movimento" } } }, "options": { "step": { "init": { - "description": "Especifique as configura\u00e7\u00f5es opcionais" + "data": { + "wait_for_push": "Aguarde o push multicast na atualiza\u00e7\u00e3o" + }, + "description": "Especifique as configura\u00e7\u00f5es opcionais", + "title": "Cortinas de movimento" } } } diff --git a/homeassistant/components/motioneye/translations/pt-BR.json b/homeassistant/components/motioneye/translations/pt-BR.json index d78113d7f9ce00..2ede694dcbaefb 100644 --- a/homeassistant/components/motioneye/translations/pt-BR.json +++ b/homeassistant/components/motioneye/translations/pt-BR.json @@ -7,9 +7,14 @@ "error": { "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "invalid_url": "URL inv\u00e1lida", "unknown": "Erro inesperado" }, "step": { + "hassio_confirm": { + "description": "Deseja configurar o Home Assistant para se conectar ao servi\u00e7o motionEye fornecido pelo add-on: {addon} ?", + "title": "motionEye via Home Assistant add-on" + }, "user": { "data": { "admin_password": "Senha Administrador", @@ -25,7 +30,9 @@ "step": { "init": { "data": { - "stream_url_template": "Modelo de URL de fluxo" + "stream_url_template": "Modelo de URL de fluxo", + "webhook_set": "Configure os webhooks do motionEye para relatar eventos ao Home Assistant", + "webhook_set_overwrite": "Substituir webhooks n\u00e3o reconhecidos" } } } diff --git a/homeassistant/components/mqtt/translations/pt-BR.json b/homeassistant/components/mqtt/translations/pt-BR.json index 14768a05340c1d..526fe072cf7906 100644 --- a/homeassistant/components/mqtt/translations/pt-BR.json +++ b/homeassistant/components/mqtt/translations/pt-BR.json @@ -22,8 +22,8 @@ "data": { "discovery": "Ativar descoberta" }, - "description": "Deseja configurar o Home Assistant para se conectar ao broker MQTT fornecido pelo complemento Supervisor {addon}?", - "title": "MQTT Broker via add-on Supervisor" + "description": "Deseja configurar o Home Assistant para se conectar ao broker MQTT fornecido pelo add-on {addon}?", + "title": "MQTT Broker via add-on" } } }, @@ -37,19 +37,51 @@ "button_6": "Sexto bot\u00e3o", "turn_off": "Desligar", "turn_on": "Ligar" + }, + "trigger_type": { + "button_double_press": "\"{subtype}\" clicado duas vezes", + "button_long_press": "\"{subtype}\" continuamente pressionado", + "button_long_release": "\"{subtype}\" lan\u00e7ado ap\u00f3s longa prensa", + "button_quadruple_press": "\"{subtype}\" quadruplicado", + "button_quintuple_press": "\"{subtype}\" quintuplo clicado", + "button_short_press": "\"{subtype}\" pressionado", + "button_short_release": "\"{subtype}\" lan\u00e7ados", + "button_triple_press": "\"{subtype}\" triplo clicado" } }, "options": { "error": { + "bad_birth": "T\u00f3pico \u00b4Birth message\u00b4 inv\u00e1lido", + "bad_will": "T\u00f3pico \u00b4Will message\u00b4 inv\u00e1lido", "cannot_connect": "Falha ao conectar" }, "step": { "broker": { "data": { + "broker": "", "password": "Senha", "port": "Porta", "username": "Usu\u00e1rio" - } + }, + "description": "Insira as informa\u00e7\u00f5es de conex\u00e3o do seu broker MQTT.", + "title": "Op\u00e7\u00f5es do broker" + }, + "options": { + "data": { + "birth_enable": "Ativar \u00b4Birth message\u00b4", + "birth_payload": "Payload \u00b4Birth message\u00b4", + "birth_qos": "QoS \u00b4Birth message\u00b4", + "birth_retain": "Retain \u00b4Birth message\u00b4", + "birth_topic": "T\u00f3pico \u00b4Birth message\u00b4", + "discovery": "Ativar descoberta", + "will_enable": "Ativar `Will message`", + "will_payload": "Payload `Will message`", + "will_qos": "QoS `Will message`", + "will_retain": "Retain `Will message`", + "will_topic": "T\u00f3pico `Will message`" + }, + "description": "Descoberta - Se a descoberta estiver habilitada (recomendado), o Home Assistant descobrir\u00e1 automaticamente dispositivos e entidades que publicam suas configura\u00e7\u00f5es no broker MQTT. Se a descoberta estiver desabilitada, toda a configura\u00e7\u00e3o dever\u00e1 ser feita manualmente.\n\u00b4Birth message\u00b4 - Ser\u00e1 enviada sempre que o Home Assistant (re)conectar-se ao broker MQTT.\n`Will message` - Ser\u00e1 enviada sempre que o Home Assistant perder sua conex\u00e3o com o broker, tanto no caso de uma parada programada (por exemplo, o Home Assistant desligando) quanto no caso de uma parada inesperada (por exemplo, o Home Assistant travando ou perdendo sua conex\u00e3o de rede).", + "title": "Op\u00e7\u00f5es de MQTT" } } } diff --git a/homeassistant/components/mqtt/translations/uk.json b/homeassistant/components/mqtt/translations/uk.json index b8cbab32b14fd4..b684595b1704ba 100644 --- a/homeassistant/components/mqtt/translations/uk.json +++ b/homeassistant/components/mqtt/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "error": { "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" diff --git a/homeassistant/components/mullvad/translations/pt-BR.json b/homeassistant/components/mullvad/translations/pt-BR.json index 0c5be5614acecc..341389ea117d70 100644 --- a/homeassistant/components/mullvad/translations/pt-BR.json +++ b/homeassistant/components/mullvad/translations/pt-BR.json @@ -6,6 +6,11 @@ "error": { "cannot_connect": "Falha ao conectar", "unknown": "Erro inesperado" + }, + "step": { + "user": { + "description": "Configurar a integra\u00e7\u00e3o do Mullvad VPN?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/mutesync/translations/pt-BR.json b/homeassistant/components/mutesync/translations/pt-BR.json index 159cd52c341283..fe08f78aa736f7 100644 --- a/homeassistant/components/mutesync/translations/pt-BR.json +++ b/homeassistant/components/mutesync/translations/pt-BR.json @@ -2,6 +2,7 @@ "config": { "error": { "cannot_connect": "Falha ao conectar", + "invalid_auth": "Habilitar autentica\u00e7\u00e3o em Prefer\u00eancias m\u00fctesync > Autentica\u00e7\u00e3o", "unknown": "Erro inesperado" }, "step": { diff --git a/homeassistant/components/myq/translations/pt-BR.json b/homeassistant/components/myq/translations/pt-BR.json index 7a85aed89fbb9e..75d340c7571ef9 100644 --- a/homeassistant/components/myq/translations/pt-BR.json +++ b/homeassistant/components/myq/translations/pt-BR.json @@ -13,13 +13,16 @@ "reauth_confirm": { "data": { "password": "Senha" - } + }, + "description": "A senha para {username} n\u00e3o \u00e9 mais v\u00e1lida.", + "title": "Reautentique sua conta MyQ" }, "user": { "data": { "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "title": "Conecte-se ao Gateway MyQ" } } } diff --git a/homeassistant/components/mysensors/translations/pt-BR.json b/homeassistant/components/mysensors/translations/pt-BR.json index ac4274b1fa39db..468acf70588977 100644 --- a/homeassistant/components/mysensors/translations/pt-BR.json +++ b/homeassistant/components/mysensors/translations/pt-BR.json @@ -3,14 +3,78 @@ "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "cannot_connect": "Falha ao conectar", + "duplicate_persistence_file": "Arquivo de persist\u00eancia j\u00e1 em uso", + "duplicate_topic": "T\u00f3pico j\u00e1 em uso", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "invalid_device": "Dispositivo inv\u00e1lido", + "invalid_ip": "Endere\u00e7o IP inv\u00e1lido", + "invalid_persistence_file": "Arquivo de persist\u00eancia inv\u00e1lido", + "invalid_port": "N\u00famero de porta inv\u00e1lido", + "invalid_publish_topic": "T\u00f3pico de publica\u00e7\u00e3o inv\u00e1lido", + "invalid_serial": "Porta serial inv\u00e1lida", + "invalid_subscribe_topic": "T\u00f3pico de inscri\u00e7\u00e3o inv\u00e1lido", + "invalid_version": "Vers\u00e3o MySensors inv\u00e1lida", + "not_a_number": "Por favor, digite um n\u00famero", + "port_out_of_range": "O n\u00famero da porta deve ser no m\u00ednimo 1 e no m\u00e1ximo 65535", + "same_topic": "Subscrever e publicar t\u00f3picos s\u00e3o os mesmos", "unknown": "Erro inesperado" }, "error": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "cannot_connect": "Falha ao conectar", + "duplicate_persistence_file": "Arquivo de persist\u00eancia j\u00e1 em uso", + "duplicate_topic": "T\u00f3pico j\u00e1 em uso", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "invalid_device": "Dispositivo inv\u00e1lido", + "invalid_ip": "Endere\u00e7o IP inv\u00e1lido", + "invalid_persistence_file": "Arquivo de persist\u00eancia inv\u00e1lido", + "invalid_port": "N\u00famero da porta inv\u00e1lida", + "invalid_publish_topic": "T\u00f3pico de publica\u00e7\u00e3o inv\u00e1lido", + "invalid_serial": "Porta serial inv\u00e1lida", + "invalid_subscribe_topic": "T\u00f3pico de inscri\u00e7\u00e3o inv\u00e1lido", + "invalid_version": "Vers\u00e3o MySensors inv\u00e1lida", + "mqtt_required": "A integra\u00e7\u00e3o do MQTT n\u00e3o est\u00e1 configurada", + "not_a_number": "Por favor, digite um n\u00famero", + "port_out_of_range": "O n\u00famero da porta deve ser no m\u00ednimo 1 e no m\u00e1ximo 65535", + "same_topic": "Subscrever e publicar t\u00f3picos s\u00e3o os mesmos", "unknown": "Erro inesperado" + }, + "step": { + "gw_mqtt": { + "data": { + "persistence_file": "arquivo de persist\u00eancia (deixe em branco para gerar automaticamente)", + "retain": "mqtt retain", + "topic_in_prefix": "prefixo para t\u00f3picos de entrada (topic_in_prefix)", + "topic_out_prefix": "prefixo para t\u00f3picos de sa\u00edda (topic_out_prefix)", + "version": "Vers\u00e3o MySensors" + }, + "description": "Configura\u00e7\u00e3o do gateway MQTT" + }, + "gw_serial": { + "data": { + "baud_rate": "taxa de transmiss\u00e3o", + "device": "Porta serial", + "persistence_file": "arquivo de persist\u00eancia (deixe em branco para gerar automaticamente)", + "version": "Vers\u00e3o MySensors" + }, + "description": "Configura\u00e7\u00e3o do gateway serial" + }, + "gw_tcp": { + "data": { + "device": "Endere\u00e7o IP do gateway", + "persistence_file": "arquivo de persist\u00eancia (deixe em branco para gerar automaticamente)", + "tcp_port": "porta", + "version": "Vers\u00e3o MySensors" + }, + "description": "Configura\u00e7\u00e3o do gateway Ethernet" + }, + "user": { + "data": { + "gateway_type": "Tipo de gateway" + }, + "description": "Escolha o m\u00e9todo de conex\u00e3o com o gateway" + } } - } + }, + "title": "MySensors" } \ No newline at end of file diff --git a/homeassistant/components/nam/translations/pt-BR.json b/homeassistant/components/nam/translations/pt-BR.json index 7b56c616af1141..270e080701c511 100644 --- a/homeassistant/components/nam/translations/pt-BR.json +++ b/homeassistant/components/nam/translations/pt-BR.json @@ -2,14 +2,20 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "device_unsupported": "O dispositivo n\u00e3o \u00e9 compat\u00edvel.", "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", "reauth_unsuccessful": "A reautentica\u00e7\u00e3o falhou. Remova a integra\u00e7\u00e3o e configure-a novamente." }, "error": { "cannot_connect": "Falha ao conectar", - "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" }, + "flow_title": "{host}", "step": { + "confirm_discovery": { + "description": "Deseja configurar o Nettigo Air Monitor em {host} ?" + }, "credentials": { "data": { "password": "Senha", @@ -27,7 +33,8 @@ "user": { "data": { "host": "Nome do host" - } + }, + "description": "Configure a integra\u00e7\u00e3o do Nettigo Air Monitor." } } } diff --git a/homeassistant/components/nanoleaf/translations/pt-BR.json b/homeassistant/components/nanoleaf/translations/pt-BR.json index b6ce644bac9e06..3946a794711257 100644 --- a/homeassistant/components/nanoleaf/translations/pt-BR.json +++ b/homeassistant/components/nanoleaf/translations/pt-BR.json @@ -9,9 +9,15 @@ }, "error": { "cannot_connect": "Falha ao conectar", + "not_allowing_new_tokens": "Nanoleaf n\u00e3o est\u00e1 permitindo novos tokens, siga as instru\u00e7\u00f5es acima.", "unknown": "Erro inesperado" }, + "flow_title": "{name}", "step": { + "link": { + "description": "Pressione e segure o bot\u00e3o liga/desliga em seu Nanoleaf por 5 segundos at\u00e9 que os LEDs do bot\u00e3o comecem a piscar e clique em **ENVIAR** dentro de 30 segundos.", + "title": "Link Nanoleaf" + }, "user": { "data": { "host": "Nome do host" diff --git a/homeassistant/components/neato/translations/pt-BR.json b/homeassistant/components/neato/translations/pt-BR.json index dc4207a45f9387..684f7cdff22128 100644 --- a/homeassistant/components/neato/translations/pt-BR.json +++ b/homeassistant/components/neato/translations/pt-BR.json @@ -11,9 +11,13 @@ "default": "Autenticado com sucesso" }, "step": { + "pick_implementation": { + "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" + }, "reauth_confirm": { "title": "Deseja iniciar a configura\u00e7\u00e3o?" } } - } + }, + "title": "Neato Botvac" } \ No newline at end of file diff --git a/homeassistant/components/nest/translations/el.json b/homeassistant/components/nest/translations/el.json index 1d75ba96a7e479..201cd7c02b541a 100644 --- a/homeassistant/components/nest/translations/el.json +++ b/homeassistant/components/nest/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "invalid_pin": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN" + }, "step": { "auth": { "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd Google" diff --git a/homeassistant/components/nest/translations/pt-BR.json b/homeassistant/components/nest/translations/pt-BR.json index 3db8792484c8bf..54b558493b8540 100644 --- a/homeassistant/components/nest/translations/pt-BR.json +++ b/homeassistant/components/nest/translations/pt-BR.json @@ -6,7 +6,8 @@ "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", "no_url_available": "N\u00e3o h\u00e1 URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a se\u00e7\u00e3o de ajuda]({docs_url})", "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", - "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", + "unknown_authorize_url_generation": "Erro desconhecido ao gerar um URL de autoriza\u00e7\u00e3o." }, "create_entry": { "default": "Autenticado com sucesso" @@ -32,16 +33,19 @@ "data": { "flow_impl": "Provedor" }, - "description": "Escolha atrav\u00e9s de qual provedor de autentica\u00e7\u00e3o voc\u00ea deseja autenticar com o Nest.", + "description": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o", "title": "Provedor de Autentica\u00e7\u00e3o" }, "link": { "data": { "code": "C\u00f3digo PIN" }, - "description": "Para vincular sua conta do Nest, [autorize sua conta] ( {url} ). \n\n Ap\u00f3s a autoriza\u00e7\u00e3o, copie e cole o c\u00f3digo PIN fornecido abaixo.", + "description": "Para vincular sua conta do Nest, [autorize sua conta]({url}). \n\n Ap\u00f3s a autoriza\u00e7\u00e3o, copie e cole o c\u00f3digo PIN fornecido abaixo.", "title": "Link da conta Nest" }, + "pick_implementation": { + "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" + }, "pubsub": { "data": { "cloud_project_id": "ID do projeto do Google Cloud" @@ -50,13 +54,17 @@ "title": "Configurar o Google Cloud" }, "reauth_confirm": { + "description": "A integra\u00e7\u00e3o Nest precisa re-autenticar sua conta", "title": "Reautenticar Integra\u00e7\u00e3o" } } }, "device_automation": { "trigger_type": { - "camera_motion": "Movimento detectado" + "camera_motion": "Movimento detectado", + "camera_person": "Pessoa detectada", + "camera_sound": "Som detectado", + "doorbell_chime": "Campainha pressionada" } } } \ No newline at end of file diff --git a/homeassistant/components/nest/translations/uk.json b/homeassistant/components/nest/translations/uk.json index f2ee64e7fc4b64..0d105c59c05fd3 100644 --- a/homeassistant/components/nest/translations/uk.json +++ b/homeassistant/components/nest/translations/uk.json @@ -5,7 +5,7 @@ "missing_configuration": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438.", "no_url_available": "URL-\u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430. \u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0456\u0454\u044e] ({docs_url}) \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457 \u043f\u0440\u043e \u0446\u044e \u043f\u043e\u043c\u0438\u043b\u043a\u0443.", "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430 \u0430\u0432\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f \u043f\u0440\u043e\u0439\u0448\u043b\u0430 \u0443\u0441\u043f\u0456\u0448\u043d\u043e", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e.", + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f.", "unknown_authorize_url_generation": "\u041d\u0435\u0432\u0456\u0434\u043e\u043c\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430 \u043f\u0440\u0438 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0456\u0457 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457." }, "create_entry": { diff --git a/homeassistant/components/netatmo/translations/pt-BR.json b/homeassistant/components/netatmo/translations/pt-BR.json index 98d1882d5e04d4..b47c0ea36466d4 100644 --- a/homeassistant/components/netatmo/translations/pt-BR.json +++ b/homeassistant/components/netatmo/translations/pt-BR.json @@ -11,21 +11,59 @@ "default": "Autenticado com sucesso" }, "step": { + "pick_implementation": { + "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" + }, "reauth_confirm": { "description": "A integra\u00e7\u00e3o Netatmo precisa autenticar novamente sua conta", "title": "Reautenticar Integra\u00e7\u00e3o" } } }, + "device_automation": { + "trigger_subtype": { + "away": "fora", + "hg": "protetor de geada", + "schedule": "hor\u00e1rio" + }, + "trigger_type": { + "alarm_started": "{entity_name} detectou um alarme", + "animal": "{entity_name} detectou um animal", + "cancel_set_point": "{entity_name} retomou sua programa\u00e7\u00e3o", + "human": "{entity_name} detectou um humano", + "movement": "{entity_name} detectou movimento", + "outdoor": "{entity_name} detectou um evento ao ar livre", + "person": "{entity_name} detectou uma pessoa", + "person_away": "{entity_name} detectou que uma pessoa saiu", + "set_point": "Temperatura alvo {entity_name} definida manualmente", + "therm_mode": "{entity_name} mudou para \" {subtype} \"", + "turned_off": "{entity_name} for desligado", + "turned_on": "{entity_name} for ligado", + "vehicle": "{entity_name} detectou um ve\u00edculo" + } + }, "options": { "step": { "public_weather": { "data": { + "area_name": "Nome da \u00e1rea", "lat_ne": "Latitude nordeste", "lat_sw": "Latitude sudoeste", "lon_ne": "Longitude nordeste", - "lon_sw": "Longitude sudoeste" - } + "lon_sw": "Longitude sudoeste", + "mode": "C\u00e1lculo", + "show_on_map": "Mostrar no mapa" + }, + "description": "Configure um sensor meteorol\u00f3gico p\u00fablico para uma \u00e1rea.", + "title": "Sensor meteorol\u00f3gico p\u00fablico Netatmo" + }, + "public_weather_areas": { + "data": { + "new_area": "Nome da \u00e1rea", + "weather_areas": "\u00c1reas meteorol\u00f3gicas" + }, + "description": "Configurar sensores meteorol\u00f3gicos p\u00fablicos.", + "title": "Sensor meteorol\u00f3gico p\u00fablico Netatmo" } } } diff --git a/homeassistant/components/netatmo/translations/uk.json b/homeassistant/components/netatmo/translations/uk.json index b8c439edfde4fc..9d6065a4841cb0 100644 --- a/homeassistant/components/netatmo/translations/uk.json +++ b/homeassistant/components/netatmo/translations/uk.json @@ -4,7 +4,7 @@ "authorize_url_timeout": "\u041c\u0438\u043d\u0443\u0432 \u0447\u0430\u0441 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0456\u0457 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457.", "missing_configuration": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438.", "no_url_available": "URL-\u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430. \u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0456\u0454\u044e] ({docs_url}) \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457 \u043f\u0440\u043e \u0446\u044e \u043f\u043e\u043c\u0438\u043b\u043a\u0443.", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "create_entry": { "default": "\u0410\u0432\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044e \u0443\u0441\u043f\u0456\u0448\u043d\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e." diff --git a/homeassistant/components/netgear/translations/pt-BR.json b/homeassistant/components/netgear/translations/pt-BR.json index 82c149c759f929..4789dcc042bd9b 100644 --- a/homeassistant/components/netgear/translations/pt-BR.json +++ b/homeassistant/components/netgear/translations/pt-BR.json @@ -3,6 +3,9 @@ "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, + "error": { + "config": "Erro de conex\u00e3o ou de login: verifique sua configura\u00e7\u00e3o" + }, "step": { "user": { "data": { @@ -12,7 +15,7 @@ "ssl": "Usar um certificado SSL", "username": "Usu\u00e1rio (Opcional)" }, - "description": "Host padr\u00e3o: {host}\n Porta padr\u00e3o: {port}\n Usu\u00e1rio padr\u00e3o: {username}", + "description": "Host padr\u00e3o: {host}\nPorta padr\u00e3o: {port}\nUsu\u00e1rio padr\u00e3o: {username}", "title": "Netgear" } } @@ -20,6 +23,9 @@ "options": { "step": { "init": { + "data": { + "consider_home": "Considere o tempo de casa (segundos)" + }, "description": "Especifique configura\u00e7\u00f5es opcionais", "title": "Netgear" } diff --git a/homeassistant/components/nexia/translations/pt-BR.json b/homeassistant/components/nexia/translations/pt-BR.json index 66c671f99a3c2e..e9cce64aef9684 100644 --- a/homeassistant/components/nexia/translations/pt-BR.json +++ b/homeassistant/components/nexia/translations/pt-BR.json @@ -11,9 +11,11 @@ "step": { "user": { "data": { + "brand": "Marca", "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "title": "Conecte-se a mynexia.com" } } } diff --git a/homeassistant/components/nfandroidtv/translations/pt-BR.json b/homeassistant/components/nfandroidtv/translations/pt-BR.json index 467eb83fea35f2..43b7a296c21564 100644 --- a/homeassistant/components/nfandroidtv/translations/pt-BR.json +++ b/homeassistant/components/nfandroidtv/translations/pt-BR.json @@ -12,7 +12,9 @@ "data": { "host": "Nome do host", "name": "Nome" - } + }, + "description": "Essa integra\u00e7\u00e3o requer as Notifica\u00e7\u00f5es para o aplicativo Android TV.\n\nPara Android TV: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\nPara Fire TV: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK\n\nVoc\u00ea deve configurar a reserva DHCP no roteador (consulte o manual do usu\u00e1rio do roteador) ou um endere\u00e7o IP est\u00e1tico no dispositivo. Se n\u00e3o, o dispositivo acabar\u00e1 por ficar indispon\u00edvel.", + "title": "Notifica\u00e7\u00f5es para Android TV / Fire TV" } } } diff --git a/homeassistant/components/nightscout/translations/pt-BR.json b/homeassistant/components/nightscout/translations/pt-BR.json index bc2a518b65b902..4c31da855347ff 100644 --- a/homeassistant/components/nightscout/translations/pt-BR.json +++ b/homeassistant/components/nightscout/translations/pt-BR.json @@ -8,12 +8,15 @@ "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, + "flow_title": "Nightscout", "step": { "user": { "data": { "api_key": "Chave da API", "url": "URL" - } + }, + "description": "- URL: o endere\u00e7o da sua inst\u00e2ncia nightscout. ou seja: https://myhomeassistant.duckdns.org:5423\n- Chave da API (opcional): Use somente se sua inst\u00e2ncia estiver protegida (auth_default_roles != readable).", + "title": "Insira as informa\u00e7\u00f5es do seu servidor Nightscout." } } } diff --git a/homeassistant/components/nina/translations/pt-BR.json b/homeassistant/components/nina/translations/pt-BR.json index 4116fff076d1c3..c22d3e065302ab 100644 --- a/homeassistant/components/nina/translations/pt-BR.json +++ b/homeassistant/components/nina/translations/pt-BR.json @@ -13,8 +13,14 @@ "data": { "_a_to_d": "City/county (A-D)", "_e_to_h": "City/county (E-H)", - "_i_to_l": "City/county (I-L)" - } + "_i_to_l": "City/county (I-L)", + "_m_to_q": "Cidade/munic\u00edpio (M-Q)", + "_r_to_u": "Cidade/munic\u00edpio (R-U)", + "_v_to_z": "Cidade/munic\u00edpio (V-Z)", + "corona_filter": "Remover avisos do corona", + "slots": "M\u00e1ximo de avisos por cidade/munic\u00edpio" + }, + "title": "Selecione a cidade/munic\u00edpio" } } } diff --git a/homeassistant/components/nmap_tracker/translations/pt-BR.json b/homeassistant/components/nmap_tracker/translations/pt-BR.json index 26eae684761a39..bf058495cb9345 100644 --- a/homeassistant/components/nmap_tracker/translations/pt-BR.json +++ b/homeassistant/components/nmap_tracker/translations/pt-BR.json @@ -2,6 +2,40 @@ "config": { "abort": { "already_configured": "Localiza\u00e7\u00e3o j\u00e1 est\u00e1 configurada" + }, + "error": { + "invalid_hosts": "Hosts inv\u00e1lidos" + }, + "step": { + "user": { + "data": { + "exclude": "Endere\u00e7os de rede (separados por v\u00edrgula) para excluir do escaneamento", + "home_interval": "N\u00famero m\u00ednimo de minutos entre escaneamento de dispositivos ativos (preservar bateria)", + "hosts": "Endere\u00e7os de rede (separados por v\u00edrgula) para escanear", + "scan_options": "Op\u00e7\u00f5es de escaneamento bruto configur\u00e1veis para Nmap" + }, + "description": "Configure os hosts a serem verificados pelo Nmap. O endere\u00e7o de rede e as exclus\u00f5es podem ser endere\u00e7os IP (192.168.1.1), redes IP (192.168.0.0/24) ou intervalos de IP (192.168.1.0-32)." + } } - } + }, + "options": { + "error": { + "invalid_hosts": "Hosts inv\u00e1lidos" + }, + "step": { + "init": { + "data": { + "consider_home": "Segundos para esperar at\u00e9 marcar um rastreador de dispositivo como fora de casa depois de n\u00e3o ser visto.", + "exclude": "Endere\u00e7os de rede (separados por v\u00edrgula) para excluir do escaneamento", + "home_interval": "N\u00famero m\u00ednimo de minutos entre escaneamento de dispositivos ativos (preservar bateria)", + "hosts": "Endere\u00e7os de rede (separados por v\u00edrgula) para escanear", + "interval_seconds": "Intervalo de varredura", + "scan_options": "Op\u00e7\u00f5es de varredura configur\u00e1veis brutas para Nmap", + "track_new_devices": "Rastrear novos dispositivos" + }, + "description": "Configure hosts a serem digitalizados pelo Nmap. O endere\u00e7o de rede e exclus\u00f5es podem ser Endere\u00e7os IP (192.168.1.1), Redes IP (192.168.0.0/24) ou Faixas IP (192.168.1.0-32)." + } + } + }, + "title": "Nmap Tracker" } \ No newline at end of file diff --git a/homeassistant/components/notion/translations/el.json b/homeassistant/components/notion/translations/el.json index 756523974064f6..288856ef8a1555 100644 --- a/homeassistant/components/notion/translations/el.json +++ b/homeassistant/components/notion/translations/el.json @@ -1,8 +1,14 @@ { "config": { + "error": { + "no_devices": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc" + }, "step": { "reauth_confirm": { "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username}." + }, + "user": { + "title": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03b1\u03c2" } } } diff --git a/homeassistant/components/nuki/translations/pt-BR.json b/homeassistant/components/nuki/translations/pt-BR.json index 045720cd332e48..e00d14b479e398 100644 --- a/homeassistant/components/nuki/translations/pt-BR.json +++ b/homeassistant/components/nuki/translations/pt-BR.json @@ -13,6 +13,7 @@ "data": { "token": "Token de acesso" }, + "description": "A integra\u00e7\u00e3o Nuki precisa se autenticar novamente com sua ponte.", "title": "Reautenticar Integra\u00e7\u00e3o" }, "user": { diff --git a/homeassistant/components/number/translations/pt-BR.json b/homeassistant/components/number/translations/pt-BR.json new file mode 100644 index 00000000000000..b45277027445a6 --- /dev/null +++ b/homeassistant/components/number/translations/pt-BR.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "Definir valor para {entity_name}" + } + }, + "title": "N\u00famero" +} \ No newline at end of file diff --git a/homeassistant/components/nut/translations/pt-BR.json b/homeassistant/components/nut/translations/pt-BR.json index 5a5ec19d2b24a1..d6be7b4104493d 100644 --- a/homeassistant/components/nut/translations/pt-BR.json +++ b/homeassistant/components/nut/translations/pt-BR.json @@ -27,7 +27,8 @@ "password": "Senha", "port": "Porta", "username": "Usu\u00e1rio" - } + }, + "title": "Conecte-se ao servidor NUT" } } }, @@ -39,8 +40,10 @@ "step": { "init": { "data": { + "resources": "Recursos", "scan_interval": "Intervalo de escaneamento (segundos)" - } + }, + "description": "Escolha os recursos dos sensores." } } } diff --git a/homeassistant/components/nzbget/translations/pt-BR.json b/homeassistant/components/nzbget/translations/pt-BR.json index f7489f07d8ffb8..69c0c575ecaf0d 100644 --- a/homeassistant/components/nzbget/translations/pt-BR.json +++ b/homeassistant/components/nzbget/translations/pt-BR.json @@ -7,6 +7,7 @@ "error": { "cannot_connect": "Falha ao conectar" }, + "flow_title": "{name}", "step": { "user": { "data": { @@ -17,6 +18,16 @@ "ssl": "Usar um certificado SSL", "username": "Usu\u00e1rio", "verify_ssl": "Verifique o certificado SSL" + }, + "title": "Conecte-se ao NZBGet" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Frequ\u00eancia de atualiza\u00e7\u00e3o (segundos)" } } } diff --git a/homeassistant/components/nzbget/translations/uk.json b/homeassistant/components/nzbget/translations/uk.json index eba15cca19c175..a7f7e8b3f07df4 100644 --- a/homeassistant/components/nzbget/translations/uk.json +++ b/homeassistant/components/nzbget/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e.", + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f.", "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" }, "error": { diff --git a/homeassistant/components/omnilogic/translations/pt-BR.json b/homeassistant/components/omnilogic/translations/pt-BR.json index 790e3e661a3492..6a48db65df54bc 100644 --- a/homeassistant/components/omnilogic/translations/pt-BR.json +++ b/homeassistant/components/omnilogic/translations/pt-BR.json @@ -16,5 +16,15 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "ph_offset": "Deslocamento de pH (positivo ou negativo)", + "polling_interval": "Intervalo de sondagem (em segundos)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/uk.json b/homeassistant/components/omnilogic/translations/uk.json index 21ebf6f4fafa26..6c65ff99dbbd34 100644 --- a/homeassistant/components/omnilogic/translations/uk.json +++ b/homeassistant/components/omnilogic/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "error": { "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", diff --git a/homeassistant/components/ondilo_ico/translations/pt-BR.json b/homeassistant/components/ondilo_ico/translations/pt-BR.json index c64994cd0d6d0a..c219c32f8e315e 100644 --- a/homeassistant/components/ondilo_ico/translations/pt-BR.json +++ b/homeassistant/components/ondilo_ico/translations/pt-BR.json @@ -6,6 +6,11 @@ }, "create_entry": { "default": "Autenticado com sucesso" + }, + "step": { + "pick_implementation": { + "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" + } } } } \ No newline at end of file diff --git a/homeassistant/components/onewire/translations/pt-BR.json b/homeassistant/components/onewire/translations/pt-BR.json index cfb890a38bfee2..401452bcaf515b 100644 --- a/homeassistant/components/onewire/translations/pt-BR.json +++ b/homeassistant/components/onewire/translations/pt-BR.json @@ -4,14 +4,22 @@ "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "error": { - "cannot_connect": "Falha ao conectar" + "cannot_connect": "Falha ao conectar", + "invalid_path": "Diret\u00f3rio n\u00e3o encontrado." }, "step": { "owserver": { "data": { "host": "Nome do host", "port": "Porta" - } + }, + "title": "Definir detalhes do servidor" + }, + "user": { + "data": { + "type": "Tipo de conex\u00e3o" + }, + "title": "Configurar 1-Wire" } } } diff --git a/homeassistant/components/onvif/translations/pt-BR.json b/homeassistant/components/onvif/translations/pt-BR.json index 488bf6621024cb..d5586b2dd2b528 100644 --- a/homeassistant/components/onvif/translations/pt-BR.json +++ b/homeassistant/components/onvif/translations/pt-BR.json @@ -25,7 +25,8 @@ "password": "Senha", "port": "Porta", "username": "Usu\u00e1rio" - } + }, + "title": "Configurar dispositivo ONVIF" }, "configure_profile": { "data": { @@ -49,6 +50,9 @@ "title": "Configurar dispositivo ONVIF" }, "user": { + "data": { + "auto": "Pesquisar automaticamente" + }, "description": "Ao clicar em enviar, procuraremos na sua rede por dispositivos ONVIF compat\u00edveis com o Perfil S. \n\nAlguns fabricantes deixam o ONVIF desativado por padr\u00e3o. Verifique se o ONVIF est\u00e1 ativado na configura\u00e7\u00e3o da sua c\u00e2mera.", "title": "Configura\u00e7\u00e3o do dispositivo ONVIF" } diff --git a/homeassistant/components/open_meteo/translations/pt-BR.json b/homeassistant/components/open_meteo/translations/pt-BR.json new file mode 100644 index 00000000000000..44ef054dceac2e --- /dev/null +++ b/homeassistant/components/open_meteo/translations/pt-BR.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "zone": "Zona" + }, + "description": "Selecione o local a ser usado para previs\u00e3o do tempo" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/opentherm_gw/translations/el.json b/homeassistant/components/opentherm_gw/translations/el.json index f15bc7bdc0e25b..11f543797fe13f 100644 --- a/homeassistant/components/opentherm_gw/translations/el.json +++ b/homeassistant/components/opentherm_gw/translations/el.json @@ -1,11 +1,27 @@ { + "config": { + "error": { + "id_exists": "\u03a4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03cd\u03bb\u03b7\u03c2 \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7" + }, + "step": { + "init": { + "data": { + "device": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", + "id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc" + }, + "title": "\u03a0\u03cd\u03bb\u03b7 OpenTherm" + } + } + }, "options": { "step": { "init": { "data": { + "floor_temperature": "\u0398\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1 \u03b4\u03b1\u03c0\u03ad\u03b4\u03bf\u03c5", "read_precision": "\u0394\u03b9\u03ac\u03b2\u03b1\u03c3\u03b5 \u03c4\u03b7\u03bd \u03b1\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1", "set_precision": "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03b1\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1\u03c2" - } + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c0\u03cd\u03bb\u03b7 OpenTherm" } } } diff --git a/homeassistant/components/opentherm_gw/translations/pt-BR.json b/homeassistant/components/opentherm_gw/translations/pt-BR.json index a677332c3e14ef..cf4ed0846d2cc8 100644 --- a/homeassistant/components/opentherm_gw/translations/pt-BR.json +++ b/homeassistant/components/opentherm_gw/translations/pt-BR.json @@ -2,13 +2,30 @@ "config": { "error": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", - "cannot_connect": "Falha ao conectar" + "cannot_connect": "Falha ao conectar", + "id_exists": "ID do gateway j\u00e1 existe" }, "step": { "init": { "data": { + "device": "Caminho ou URL", + "id": "ID", "name": "Nome" - } + }, + "title": "OpenTherm Gateway" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "floor_temperature": "Temperatura do piso", + "read_precision": "Precis\u00e3o de leitura", + "set_precision": "Definir precis\u00e3o", + "temporary_override_mode": "Modo de substitui\u00e7\u00e3o tempor\u00e1ria do ponto de ajuste" + }, + "description": "Op\u00e7\u00f5es para o OpenTherm Gateway" } } } diff --git a/homeassistant/components/openuv/translations/pt-BR.json b/homeassistant/components/openuv/translations/pt-BR.json index 7be0885bde9c93..1fe9216bdad699 100644 --- a/homeassistant/components/openuv/translations/pt-BR.json +++ b/homeassistant/components/openuv/translations/pt-BR.json @@ -17,5 +17,16 @@ "title": "Preencha suas informa\u00e7\u00f5es" } } + }, + "options": { + "step": { + "init": { + "data": { + "from_window": "Iniciar \u00edndice UV para a janela de prote\u00e7\u00e3o", + "to_window": "Fim do \u00edndice UV para a janela de prote\u00e7\u00e3o" + }, + "title": "Configurar OpenUV" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/openweathermap/translations/pt-BR.json b/homeassistant/components/openweathermap/translations/pt-BR.json index 5e7c3559d90a71..dd88767bb61393 100644 --- a/homeassistant/components/openweathermap/translations/pt-BR.json +++ b/homeassistant/components/openweathermap/translations/pt-BR.json @@ -11,8 +11,23 @@ "user": { "data": { "api_key": "Chave da API", + "language": "Idioma", "latitude": "Latitude", - "longitude": "Longitude" + "longitude": "Longitude", + "mode": "Modo", + "name": "Nome da integra\u00e7\u00e3o" + }, + "description": "Configure a integra\u00e7\u00e3o do OpenWeatherMap. Para gerar a chave de API, acesse https://openweathermap.org/appid", + "title": "OpenWeatherMap" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "language": "Idioma", + "mode": "Modo" } } } diff --git a/homeassistant/components/overkiz/translations/ja.json b/homeassistant/components/overkiz/translations/ja.json index 6ff74c5a61eedd..b2bf92f329f623 100644 --- a/homeassistant/components/overkiz/translations/ja.json +++ b/homeassistant/components/overkiz/translations/ja.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u30a2\u30ab\u30a6\u30f3\u30c8\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" + "already_configured": "\u30a2\u30ab\u30a6\u30f3\u30c8\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", + "reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f" }, "error": { "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", diff --git a/homeassistant/components/overkiz/translations/nl.json b/homeassistant/components/overkiz/translations/nl.json index 7c64d862e2f885..e76f534350baec 100644 --- a/homeassistant/components/overkiz/translations/nl.json +++ b/homeassistant/components/overkiz/translations/nl.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "already_configured": "Account is al geconfigureerd" + "already_configured": "Account is al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol", + "reauth_wrong_account": "U kunt deze invoer alleen opnieuw verifi\u00ebren met hetzelfde Overkiz account en hub" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/overkiz/translations/pt-BR.json b/homeassistant/components/overkiz/translations/pt-BR.json index 802aef80752262..545ddf77c8d0a8 100644 --- a/homeassistant/components/overkiz/translations/pt-BR.json +++ b/homeassistant/components/overkiz/translations/pt-BR.json @@ -8,15 +8,19 @@ "error": { "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "server_in_maintenance": "O servidor est\u00e1 fora de servi\u00e7o para manuten\u00e7\u00e3o", + "too_many_requests": "Muitas solicita\u00e7\u00f5es, tente novamente mais tarde", "unknown": "Erro inesperado" }, "step": { "user": { "data": { "host": "Nome do host", + "hub": "Hub", "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "description": "A plataforma Overkiz \u00e9 utilizada por v\u00e1rios fornecedores como Somfy (Connexoon/TaHoma), Hitachi (Hi Kumo), Rexel (Energeasy Connect) e Atlantic (Cozytouch). Insira suas credenciais de aplicativo e selecione seu hub." } } } diff --git a/homeassistant/components/overkiz/translations/select.ru.json b/homeassistant/components/overkiz/translations/select.ru.json new file mode 100644 index 00000000000000..6c4c93ca753231 --- /dev/null +++ b/homeassistant/components/overkiz/translations/select.ru.json @@ -0,0 +1,13 @@ +{ + "state": { + "overkiz__memorized_simple_volume": { + "highest": "\u0421\u0430\u043c\u044b\u0439 \u0432\u044b\u0441\u043e\u043a\u0438\u0439", + "standard": "\u0421\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439" + }, + "overkiz__open_closed_pedestrian": { + "closed": "\u0417\u0430\u043a\u0440\u044b\u0442\u044b\u0439", + "open": "\u041e\u0442\u043a\u0440\u044b\u0442\u044b\u0439", + "pedestrian": "\u041f\u0435\u0448\u0435\u0445\u043e\u0434\u043d\u044b\u0439" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/overkiz/translations/sensor.nl.json b/homeassistant/components/overkiz/translations/sensor.nl.json index f1471f4f5b64cf..aef0b1e0394f5c 100644 --- a/homeassistant/components/overkiz/translations/sensor.nl.json +++ b/homeassistant/components/overkiz/translations/sensor.nl.json @@ -13,6 +13,7 @@ "verylow": "Zeer laag" }, "overkiz__priority_lock_originator": { + "external_gateway": "Externe gateway", "local_user": "Lokale gebruiker", "lsc": "LSC", "myself": "Ikzelf", @@ -27,7 +28,10 @@ "wind": "Wind" }, "overkiz__sensor_defect": { - "dead": "Onbereikbaar" + "dead": "Onbereikbaar", + "low_battery": "Batterij bijna leeg", + "maintenance_required": "Onderhoud vereist", + "no_defect": "Geen defect" }, "overkiz__sensor_room": { "clean": "Schoon", diff --git a/homeassistant/components/overkiz/translations/sensor.pt-BR.json b/homeassistant/components/overkiz/translations/sensor.pt-BR.json index 7ba542d0cbe568..3ec82aa83d554d 100644 --- a/homeassistant/components/overkiz/translations/sensor.pt-BR.json +++ b/homeassistant/components/overkiz/translations/sensor.pt-BR.json @@ -1,6 +1,7 @@ { "state": { "overkiz__battery": { + "full": "Completa", "low": "Baixo", "normal": "Normal", "verylow": "Muito baixo" @@ -12,11 +13,14 @@ "verylow": "Muito baixo" }, "overkiz__priority_lock_originator": { + "external_gateway": "Gateway externo", "local_user": "Usu\u00e1rio local", "lsc": "LSC", "myself": "Eu mesmo", "rain": "Chuva", + "saac": "SAAC", "security": "Seguran\u00e7a", + "sfc": "SFC", "temperature": "Temperatura", "timer": "Temporizador", "ups": "UPS", diff --git a/homeassistant/components/ovo_energy/translations/pt-BR.json b/homeassistant/components/ovo_energy/translations/pt-BR.json index dfd1563d548db7..4b73ecca8b9438 100644 --- a/homeassistant/components/ovo_energy/translations/pt-BR.json +++ b/homeassistant/components/ovo_energy/translations/pt-BR.json @@ -5,17 +5,22 @@ "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, + "flow_title": "{username}", "step": { "reauth": { "data": { "password": "Senha" - } + }, + "description": "Falha na autentica\u00e7\u00e3o para OVO Energy. Por favor, insira suas credenciais atuais.", + "title": "Reautentica\u00e7\u00e3o" }, "user": { "data": { "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "description": "Configure uma inst\u00e2ncia OVO Energy para acessar seu uso de energia.", + "title": "Adicionar conta OVO Energy" } } } diff --git a/homeassistant/components/owntracks/translations/ja.json b/homeassistant/components/owntracks/translations/ja.json index faec1e6977b8bf..998478a9cc8b56 100644 --- a/homeassistant/components/owntracks/translations/ja.json +++ b/homeassistant/components/owntracks/translations/ja.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Home Assistant Cloud\u306b\u63a5\u7d9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002", "single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002" }, "create_entry": { diff --git a/homeassistant/components/owntracks/translations/nl.json b/homeassistant/components/owntracks/translations/nl.json index 65189e6b0be929..74baf0fe1067db 100644 --- a/homeassistant/components/owntracks/translations/nl.json +++ b/homeassistant/components/owntracks/translations/nl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Niet verbonden met Home Assistant Cloud.", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "create_entry": { diff --git a/homeassistant/components/owntracks/translations/pt-BR.json b/homeassistant/components/owntracks/translations/pt-BR.json index 4137bf5b9a4eb1..79c42db20e38dc 100644 --- a/homeassistant/components/owntracks/translations/pt-BR.json +++ b/homeassistant/components/owntracks/translations/pt-BR.json @@ -5,7 +5,7 @@ "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "create_entry": { - "default": "\n\n No Android, abra [o aplicativo OwnTracks] ( {android_url} ), v\u00e1 para prefer\u00eancias - > conex\u00e3o. Altere as seguintes configura\u00e7\u00f5es: \n - Modo: HTTP privado \n - Anfitri\u00e3o: {webhook_url} \n - Identifica\u00e7\u00e3o: \n - Nome de usu\u00e1rio: ` \n - ID do dispositivo: ` ` \n\n No iOS, abra o aplicativo OwnTracks ( {ios_url} ), toque no \u00edcone (i) no canto superior esquerdo - > configura\u00e7\u00f5es. Altere as seguintes configura\u00e7\u00f5es: \n - Modo: HTTP \n - URL: {webhook_url} \n - Ativar a autentica\u00e7\u00e3o \n - UserID: ` ` \n\n {secret} \n \n Veja [a documenta\u00e7\u00e3o] ( {docs_url} ) para mais informa\u00e7\u00f5es." + "default": "\n\nNo Android, abra [o aplicativo OwnTracks]({android_url}), v\u00e1 para prefer\u00eancias -> conex\u00e3o. Altere as seguintes configura\u00e7\u00f5es:\n - Modo: HTTP privado\n - Anfitri\u00e3o: {webhook_url}\n - Identifica\u00e7\u00e3o:\n - Nome de usu\u00e1rio: `''`\n - ID do dispositivo: `''` \n\nNo iOS, abra o aplicativo OwnTracks ({ios_url}), toque no \u00edcone (i) no canto superior esquerdo -> configura\u00e7\u00f5es. Altere as seguintes configura\u00e7\u00f5es:\n - Modo: HTTP\n - URL: {webhook_url}\n - Ativar a autentica\u00e7\u00e3o\n - UserID: `''`\n\n{secret}\n\nVeja [a documenta\u00e7\u00e3o]({docs_url}) para mais informa\u00e7\u00f5es." }, "step": { "user": { diff --git a/homeassistant/components/owntracks/translations/uk.json b/homeassistant/components/owntracks/translations/uk.json index e6a6fc26068e90..04cac3dd6eca96 100644 --- a/homeassistant/components/owntracks/translations/uk.json +++ b/homeassistant/components/owntracks/translations/uk.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "cloud_not_connected": "\u041d\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0434\u043e Home Assistant Cloud.", + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "create_entry": { "default": "\u042f\u043a\u0449\u043e \u0412\u0430\u0448 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0440\u0430\u0446\u044e\u0454 \u043d\u0430 \u043e\u043f\u0435\u0440\u0430\u0446\u0456\u0439\u043d\u0456\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u0456 Android, \u0432\u0456\u0434\u043a\u0440\u0438\u0439\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u043e\u043a [OwnTracks]({android_url}), \u043f\u043e\u0442\u0456\u043c preferences - > connection. \u0417\u043c\u0456\u043d\u0456\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u0442\u0430\u043a, \u044f\u043a \u0437\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043e \u043d\u0438\u0436\u0447\u0435:\n- Mode: Private HTTP\n- Host: {webhook_url}\n- Identification:\n- Username: ``\n- Device ID: `` \n\n\u042f\u043a\u0449\u043e \u0412\u0430\u0448 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0440\u0430\u0446\u044e\u0454 \u043d\u0430 iOS, \u0432\u0456\u0434\u043a\u0440\u0438\u0439\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u043e\u043a [OwnTracks]({ios_url}), \u043d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c \u043d\u0430 \u0437\u043d\u0430\u0447\u043e\u043a (i) \u0432 \u043b\u0456\u0432\u043e\u043c\u0443 \u0432\u0435\u0440\u0445\u043d\u044c\u043e\u043c\u0443 \u043a\u0443\u0442\u043a\u0443 - > settings. \u0417\u043c\u0456\u043d\u0456\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u0442\u0430\u043a, \u044f\u043a \u0437\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043e \u043d\u0438\u0436\u0447\u0435:\n- Mode: HTTP\n- URL: {webhook_url}\n- Turn on authentication\n- UserID: ``\n\n{secret}\n\n\u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 [\u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438]({docs_url}) \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0431\u0456\u043b\u044c\u0448 \u0434\u043e\u043a\u043b\u0430\u0434\u043d\u043e\u0457 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457." diff --git a/homeassistant/components/ozw/translations/el.json b/homeassistant/components/ozw/translations/el.json index d365534110baae..f30b504897dcdf 100644 --- a/homeassistant/components/ozw/translations/el.json +++ b/homeassistant/components/ozw/translations/el.json @@ -4,6 +4,9 @@ "mqtt_required": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 MQTT \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, + "progress": { + "install_addon": "\u03a0\u03b5\u03c1\u03b9\u03bc\u03ad\u03bd\u03b5\u03c4\u03b5 \u03bc\u03ad\u03c7\u03c1\u03b9 \u03bd\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03c9\u03b8\u03b5\u03af \u03b7 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 OpenZWave. \u0391\u03c5\u03c4\u03cc \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b4\u03b9\u03b1\u03c1\u03ba\u03ad\u03c3\u03b5\u03b9 \u03b1\u03c1\u03ba\u03b5\u03c4\u03ac \u03bb\u03b5\u03c0\u03c4\u03ac." + }, "step": { "install_addon": { "title": "\u0397 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 OpenZWave \u03ad\u03c7\u03b5\u03b9 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03b9" diff --git a/homeassistant/components/ozw/translations/pt-BR.json b/homeassistant/components/ozw/translations/pt-BR.json index 079c311cd0ae42..8ec256d1d75f56 100644 --- a/homeassistant/components/ozw/translations/pt-BR.json +++ b/homeassistant/components/ozw/translations/pt-BR.json @@ -1,15 +1,40 @@ { "config": { "abort": { + "addon_info_failed": "Falha ao obter informa\u00e7\u00f5es do add-on OpenZWave.", + "addon_install_failed": "Falha ao instalar o add-on OpenZWave.", + "addon_set_config_failed": "Falha ao definir a configura\u00e7\u00e3o do OpenZWave.", "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "mqtt_required": "A integra\u00e7\u00e3o do MQTT n\u00e3o est\u00e1 configurada", "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, + "error": { + "addon_start_failed": "Falha ao iniciar o add-on OpenZWave. Verifique a configura\u00e7\u00e3o." + }, + "progress": { + "install_addon": "Aguarde enquanto a instala\u00e7\u00e3o do add-on OpenZWave termina. Isso pode levar v\u00e1rios minutos." + }, "step": { + "hassio_confirm": { + "title": "Configure a integra\u00e7\u00e3o do OpenZWave com o add-on OpenZWave" + }, + "install_addon": { + "title": "A instala\u00e7\u00e3o do add-on OpenZWave foi iniciada" + }, + "on_supervisor": { + "data": { + "use_addon": "Use o add-on OpenZWave Supervisor" + }, + "description": "Deseja usar o add-on OpenZWave Supervisor?", + "title": "Selecione o m\u00e9todo de conex\u00e3o" + }, "start_addon": { "data": { + "network_key": "Chave de rede", "usb_path": "Caminho do Dispositivo USB" - } + }, + "title": "Digite a configura\u00e7\u00e3o do add-on OpenZWave" } } } diff --git a/homeassistant/components/ozw/translations/uk.json b/homeassistant/components/ozw/translations/uk.json index f8fb161aa1c4f6..f662bc978aedd1 100644 --- a/homeassistant/components/ozw/translations/uk.json +++ b/homeassistant/components/ozw/translations/uk.json @@ -7,7 +7,7 @@ "already_configured": "\u0426\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", "already_in_progress": "\u041f\u0440\u043e\u0446\u0435\u0441 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0442\u0440\u0438\u0432\u0430\u0454.", "mqtt_required": "\u0406\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f MQTT \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0430.", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "error": { "addon_start_failed": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0438 OpenZWave. \u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." diff --git a/homeassistant/components/p1_monitor/translations/pt-BR.json b/homeassistant/components/p1_monitor/translations/pt-BR.json index b46de9ce8ee891..777a1fc5633cbf 100644 --- a/homeassistant/components/p1_monitor/translations/pt-BR.json +++ b/homeassistant/components/p1_monitor/translations/pt-BR.json @@ -9,7 +9,8 @@ "data": { "host": "Nome do host", "name": "Nome" - } + }, + "description": "Configure o P1 Monitor para integrar com o Home Assistant." } } } diff --git a/homeassistant/components/panasonic_viera/translations/pt-BR.json b/homeassistant/components/panasonic_viera/translations/pt-BR.json index 51f86ab0e828e4..8cc89f6215bb57 100644 --- a/homeassistant/components/panasonic_viera/translations/pt-BR.json +++ b/homeassistant/components/panasonic_viera/translations/pt-BR.json @@ -14,7 +14,8 @@ "data": { "pin": "C\u00f3digo PIN" }, - "description": "C\u00f3digo PIN" + "description": "C\u00f3digo PIN", + "title": "Pareamento" }, "user": { "data": { diff --git a/homeassistant/components/philips_js/translations/pt-BR.json b/homeassistant/components/philips_js/translations/pt-BR.json index f7b0e700c180ee..a4da1d92ed6839 100644 --- a/homeassistant/components/philips_js/translations/pt-BR.json +++ b/homeassistant/components/philips_js/translations/pt-BR.json @@ -5,19 +5,38 @@ }, "error": { "cannot_connect": "Falha ao conectar", + "invalid_pin": "PIN inv\u00e1lido", + "pairing_failure": "N\u00e3o foi poss\u00edvel parear: {error_id}", "unknown": "Erro inesperado" }, "step": { "pair": { "data": { "pin": "C\u00f3digo PIN" - } + }, + "description": "Digite o PIN exibido na sua TV", + "title": "Par" }, "user": { "data": { + "api_version": "Vers\u00e3o da API", "host": "Nome do host" } } } + }, + "device_automation": { + "trigger_type": { + "turn_on": "Dispositivo for solicitado para ligar" + } + }, + "options": { + "step": { + "init": { + "data": { + "allow_notify": "Permitir o uso do servi\u00e7o de notifica\u00e7\u00e3o de dados." + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/pt-BR.json b/homeassistant/components/pi_hole/translations/pt-BR.json index 08c70aa431f6df..3de821afe8d984 100644 --- a/homeassistant/components/pi_hole/translations/pt-BR.json +++ b/homeassistant/components/pi_hole/translations/pt-BR.json @@ -20,6 +20,7 @@ "name": "Nome", "port": "Porta", "ssl": "Usar um certificado SSL", + "statistics_only": "Somente estat\u00edsticas", "verify_ssl": "Verifique o certificado SSL" } } diff --git a/homeassistant/components/picnic/translations/pt-BR.json b/homeassistant/components/picnic/translations/pt-BR.json index 66c671f99a3c2e..c11bc3fa96531f 100644 --- a/homeassistant/components/picnic/translations/pt-BR.json +++ b/homeassistant/components/picnic/translations/pt-BR.json @@ -11,10 +11,12 @@ "step": { "user": { "data": { + "country_code": "C\u00f3digo do pa\u00eds", "password": "Senha", "username": "Usu\u00e1rio" } } } - } + }, + "title": "Picnic" } \ No newline at end of file diff --git a/homeassistant/components/plaato/translations/el.json b/homeassistant/components/plaato/translations/el.json index bc23114f6720e1..85fc79981ce651 100644 --- a/homeassistant/components/plaato/translations/el.json +++ b/homeassistant/components/plaato/translations/el.json @@ -24,7 +24,8 @@ "data": { "device_name": "\u039f\u03bd\u03bf\u03bc\u03ac\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c3\u03b1\u03c2", "device_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 Plaato" - } + }, + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03c9\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd Plaato" }, "webhook": { "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c4\u03b5\u03af\u03bb\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03b1 \u03c3\u03c4\u03bf Home Assistant, \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 webhook \u03c3\u03c4\u03bf Plaato Airlock.\n\n\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b1\u03ba\u03cc\u03bb\u03bf\u03c5\u03b8\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2:\n\n- URL: `{webhook_url}`\n- \u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2: \n\n\u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]({docs_url}) \u03b3\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2." diff --git a/homeassistant/components/plaato/translations/ja.json b/homeassistant/components/plaato/translations/ja.json index 8b3f030b72f45d..0842ca6da74faa 100644 --- a/homeassistant/components/plaato/translations/ja.json +++ b/homeassistant/components/plaato/translations/ja.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\u30a2\u30ab\u30a6\u30f3\u30c8\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", + "cloud_not_connected": "Home Assistant Cloud\u306b\u63a5\u7d9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002", "single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002", "webhook_not_internet_accessible": "Webhook\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u53d7\u4fe1\u3059\u308b\u306b\u306f\u3001Home Assistant\u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u306b\u3001\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u304b\u3089\u30a2\u30af\u30bb\u30b9\u3067\u304d\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002" }, diff --git a/homeassistant/components/plaato/translations/nl.json b/homeassistant/components/plaato/translations/nl.json index 7dc3eaf6fb7e07..83e2874ed7327d 100644 --- a/homeassistant/components/plaato/translations/nl.json +++ b/homeassistant/components/plaato/translations/nl.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Account is al geconfigureerd", + "cloud_not_connected": "Niet verbonden met Home Assistant Cloud.", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." }, diff --git a/homeassistant/components/plaato/translations/pt-BR.json b/homeassistant/components/plaato/translations/pt-BR.json index e8568c1ec157d5..4b57d3f984e3d4 100644 --- a/homeassistant/components/plaato/translations/pt-BR.json +++ b/homeassistant/components/plaato/translations/pt-BR.json @@ -3,15 +3,52 @@ "abort": { "already_configured": "A conta j\u00e1 foi configurada", "cloud_not_connected": "N\u00e3o conectado ao Home Assistant Cloud.", - "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", + "webhook_not_internet_accessible": "Sua inst\u00e2ncia do Home Assistant precisa estar acess\u00edvel pela Internet para receber mensagens de webhook." }, "create_entry": { - "default": "Para enviar eventos para o Home Assistant, voc\u00ea precisar\u00e1 configurar o recurso de webhook na Plaato Airlock.\n\nPreencha as seguintes informa\u00e7\u00f5es:\n\n- URL: `{webhook_url}`\n- M\u00e9todo: POST\n\nVeja [a documenta\u00e7\u00e3o]({docs_url}) para mais detalhes." + "default": "Seu Plaato {device_type} com o nome **{device_name}** foi configurado com sucesso!" + }, + "error": { + "invalid_webhook_device": "Voc\u00ea selecionou um dispositivo que n\u00e3o suporta o envio de dados para um webhook. Est\u00e1 dispon\u00edvel apenas para o Airlock", + "no_api_method": "Voc\u00ea precisa adicionar um token de autentica\u00e7\u00e3o ou selecionar webhook", + "no_auth_token": "Voc\u00ea precisa adicionar um token de autentica\u00e7\u00e3o" }, "step": { + "api_method": { + "data": { + "token": "Cole o token de autentica\u00e7\u00e3o aqui", + "use_webhook": "Usar webhook" + }, + "description": "Para poder consultar a API, \u00e9 necess\u00e1rio um `auth_token`, que pode ser obtido seguindo [estas](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) instru\u00e7\u00f5es \n\n Dispositivo selecionado: ** {device_type} ** \n\n Se voc\u00ea preferir usar o m\u00e9todo de webhook integrado (somente Airlock), marque a caixa abaixo e deixe o token de autentica\u00e7\u00e3o em branco", + "title": "Selecione o m\u00e9todo de API" + }, "user": { + "data": { + "device_name": "D\u00ea um nome ao seu dispositivo", + "device_type": "Tipo de dispositivo Plaato" + }, "description": "Deseja iniciar a configura\u00e7\u00e3o?", "title": "Configurar o Plaato Webhook" + }, + "webhook": { + "description": "Para enviar eventos para o Home Assistant, voc\u00ea precisar\u00e1 configurar o recurso de webhook no Plaato Airlock. \n\nPreencha as seguintes informa\u00e7\u00f5es: \n\n - URL: `{webhook_url}`\n - M\u00e9todo: POST \n\nConsulte [a documenta\u00e7\u00e3o]({docs_url}) para obter mais detalhes.", + "title": "Webhook para usar" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "Intervalo de atualiza\u00e7\u00e3o (minutos)" + }, + "description": "Defina o intervalo de atualiza\u00e7\u00e3o (minutos)", + "title": "Op\u00e7\u00f5es para Plaato" + }, + "webhook": { + "description": "Informa\u00e7\u00f5es do webhook: \n\n- URL: `{webhook_url}`\n- M\u00e9todo: POST \n\n", + "title": "Op\u00e7\u00f5es para a Plaato Airlock" } } } diff --git a/homeassistant/components/plaato/translations/uk.json b/homeassistant/components/plaato/translations/uk.json index a4f7de7c6be4ea..6e740a68cdba7c 100644 --- a/homeassistant/components/plaato/translations/uk.json +++ b/homeassistant/components/plaato/translations/uk.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e.", + "cloud_not_connected": "\u041d\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0434\u043e Home Assistant Cloud.", + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f.", "webhook_not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u043f\u043e\u0432\u0438\u043d\u0435\u043d \u0431\u0443\u0442\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0439 \u0437 \u0406\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443 \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f Webhook-\u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u044c." }, "create_entry": { diff --git a/homeassistant/components/plant/translations/pt-BR.json b/homeassistant/components/plant/translations/pt-BR.json index 09b88d2578b97e..4a720d6cb77913 100644 --- a/homeassistant/components/plant/translations/pt-BR.json +++ b/homeassistant/components/plant/translations/pt-BR.json @@ -5,5 +5,5 @@ "problem": "Problema" } }, - "title": "Planta" + "title": "Monitor de Planta" } \ No newline at end of file diff --git a/homeassistant/components/plex/translations/pt-BR.json b/homeassistant/components/plex/translations/pt-BR.json index d300e57c1fab74..ea74d2b173b7fe 100644 --- a/homeassistant/components/plex/translations/pt-BR.json +++ b/homeassistant/components/plex/translations/pt-BR.json @@ -10,6 +10,7 @@ }, "error": { "faulty_credentials": "Falha na autoriza\u00e7\u00e3o, verifique o token", + "host_or_token": "Deve fornecer pelo menos um Host ou Token", "no_servers": "Nenhum servidor vinculado \u00e0 conta Plex", "not_found": "Servidor Plex n\u00e3o encontrado", "ssl_error": "Problema no certificado SSL" @@ -33,10 +34,15 @@ "description": "V\u00e1rios servidores dispon\u00edveis, selecione um:", "title": "Selecione servidor Plex" }, + "user": { + "description": "Continue para [plex.tv](https://plex.tv) para vincular um servidor Plex.", + "title": "Plex Media Server" + }, "user_advanced": { "data": { "setup_method": "M\u00e9todo de configura\u00e7\u00e3o" - } + }, + "title": "Plex Media Server" } } }, @@ -44,6 +50,9 @@ "step": { "plex_mp_settings": { "data": { + "ignore_new_shared_users": "Ignorar novos usu\u00e1rios gerenciados/compartilhados", + "ignore_plex_web_clients": "Ignorar clientes Web Plex", + "monitored_users": "Usu\u00e1rios monitorados", "use_episode_art": "Usar arte epis\u00f3dio" }, "description": "Op\u00e7\u00f5es para Plex Media Players" diff --git a/homeassistant/components/plugwise/translations/pt-BR.json b/homeassistant/components/plugwise/translations/pt-BR.json index c6a9a255cb7470..f53f6c7c3794f4 100644 --- a/homeassistant/components/plugwise/translations/pt-BR.json +++ b/homeassistant/components/plugwise/translations/pt-BR.json @@ -8,17 +8,34 @@ "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, + "flow_title": "{name}", "step": { "user": { "data": { "flow_type": "Tipo de conex\u00e3o" - } + }, + "description": "Produto:", + "title": "Tipo Plugwise" }, "user_gateway": { "data": { "host": "Endere\u00e7o IP", - "port": "Porta" - } + "password": "ID do Smile", + "port": "Porta", + "username": "Nome de usu\u00e1rio Smile" + }, + "description": "Por favor, insira", + "title": "Conecte-se ao Smile" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Intervalo de escaneamento (segundos)" + }, + "description": "Ajustar as op\u00e7\u00f5es Plugwise" } } } diff --git a/homeassistant/components/plum_lightpad/translations/pt-BR.json b/homeassistant/components/plum_lightpad/translations/pt-BR.json index 88364743020d71..4213b842e462e6 100644 --- a/homeassistant/components/plum_lightpad/translations/pt-BR.json +++ b/homeassistant/components/plum_lightpad/translations/pt-BR.json @@ -9,7 +9,8 @@ "step": { "user": { "data": { - "password": "Senha" + "password": "Senha", + "username": "Email" } } } diff --git a/homeassistant/components/point/translations/pt-BR.json b/homeassistant/components/point/translations/pt-BR.json index ef5fb55e53858a..a940c67daf9624 100644 --- a/homeassistant/components/point/translations/pt-BR.json +++ b/homeassistant/components/point/translations/pt-BR.json @@ -4,7 +4,8 @@ "already_setup": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", "external_setup": "Point configurado com \u00eaxito a partir de outro fluxo.", - "no_flows": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.\nVoc\u00ea precisa configurar o Point antes de ser capaz de autenticar com ele. [Por favor, leia as instru\u00e7\u00f5es](https://www.home-assistant.io/components/point/)." + "no_flows": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.\nVoc\u00ea precisa configurar o Point antes de ser capaz de autenticar com ele. [Por favor, leia as instru\u00e7\u00f5es](https://www.home-assistant.io/components/point/).", + "unknown_authorize_url_generation": "Erro desconhecido ao gerar um URL de autoriza\u00e7\u00e3o." }, "create_entry": { "default": "Autenticado com sucesso" @@ -15,7 +16,7 @@ }, "step": { "auth": { - "description": "Siga o link abaixo e Aceite o acesso \u00e0 sua conta Minut, depois volte e pressione Enviar. \n\n [Link]({authorization_url})", + "description": "Siga o link abaixo e **Aceite** o acesso \u00e0 sua conta Minut, depois volte e pressione **Enviar**. \n\n [Link]({authorization_url})", "title": "Autenticar Ponto" }, "user": { @@ -23,7 +24,7 @@ "flow_impl": "Provedor" }, "description": "Deseja iniciar a configura\u00e7\u00e3o?", - "title": "Provedor de Autentica\u00e7\u00e3o" + "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" } } } diff --git a/homeassistant/components/point/translations/uk.json b/homeassistant/components/point/translations/uk.json index 798f76e4f6b30e..c4d23a6055dfeb 100644 --- a/homeassistant/components/point/translations/uk.json +++ b/homeassistant/components/point/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_setup": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e.", + "already_setup": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f.", "authorize_url_timeout": "\u041c\u0438\u043d\u0443\u0432 \u0447\u0430\u0441 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0456\u0457 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457.", "external_setup": "Point \u0443\u0441\u043f\u0456\u0448\u043d\u043e \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0438\u0439 \u0437 \u0456\u043d\u0448\u043e\u0433\u043e \u043f\u043e\u0442\u043e\u043a\u0443.", "no_flows": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438.", diff --git a/homeassistant/components/poolsense/translations/pt-BR.json b/homeassistant/components/poolsense/translations/pt-BR.json index c1e5cf3ac21bac..8b2e2bddda0989 100644 --- a/homeassistant/components/poolsense/translations/pt-BR.json +++ b/homeassistant/components/poolsense/translations/pt-BR.json @@ -9,9 +9,11 @@ "step": { "user": { "data": { + "email": "Email", "password": "Senha" }, - "description": "Deseja iniciar a configura\u00e7\u00e3o?" + "description": "Deseja iniciar a configura\u00e7\u00e3o?", + "title": "PoolSense" } } } diff --git a/homeassistant/components/powerwall/translations/pt-BR.json b/homeassistant/components/powerwall/translations/pt-BR.json index f95b3489f8cdff..1da49f708d9260 100644 --- a/homeassistant/components/powerwall/translations/pt-BR.json +++ b/homeassistant/components/powerwall/translations/pt-BR.json @@ -7,14 +7,17 @@ "error": { "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", - "unknown": "Erro inesperado" + "unknown": "Erro inesperado", + "wrong_version": "Seu powerwall usa uma vers\u00e3o de software que n\u00e3o \u00e9 compat\u00edvel. Considere atualizar ou relatar este problema para que ele possa ser resolvido." }, + "flow_title": "{ip_address}", "step": { "user": { "data": { "ip_address": "Endere\u00e7o IP", "password": "Senha" }, + "description": "A senha \u00e9 geralmente os \u00faltimos 5 caracteres do n\u00famero de s\u00e9rie do Backup Gateway e pode ser encontrada no aplicativo Tesla ou os \u00faltimos 5 caracteres da senha encontrada dentro da porta do Backup Gateway 2.", "title": "Conecte-se ao powerwall" } } diff --git a/homeassistant/components/profiler/translations/uk.json b/homeassistant/components/profiler/translations/uk.json index 5594895456e987..b1d6150a318d87 100644 --- a/homeassistant/components/profiler/translations/uk.json +++ b/homeassistant/components/profiler/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "step": { "user": { diff --git a/homeassistant/components/progettihwsw/translations/pt-BR.json b/homeassistant/components/progettihwsw/translations/pt-BR.json index 1e898e15ce0558..9d5f4715fd1cf5 100644 --- a/homeassistant/components/progettihwsw/translations/pt-BR.json +++ b/homeassistant/components/progettihwsw/translations/pt-BR.json @@ -8,11 +8,33 @@ "unknown": "Erro inesperado" }, "step": { + "relay_modes": { + "data": { + "relay_1": "Rel\u00e9 1", + "relay_10": "Rel\u00e9 10", + "relay_11": "Rel\u00e9 11", + "relay_12": "Rel\u00e9 12", + "relay_13": "Rel\u00e9 13", + "relay_14": "Rel\u00e9 14", + "relay_15": "Rel\u00e9 15", + "relay_16": "Rel\u00e9 16", + "relay_2": "Rel\u00e9 2", + "relay_3": "Rel\u00e9 3", + "relay_4": "Rel\u00e9 4", + "relay_5": "Rel\u00e9 5", + "relay_6": "Rel\u00e9 6", + "relay_7": "Rel\u00e9 7", + "relay_8": "Rel\u00e9 8", + "relay_9": "Rel\u00e9 9" + }, + "title": "Configurar rel\u00e9s" + }, "user": { "data": { "host": "Nome do host", "port": "Porta" - } + }, + "title": "Placa de configura\u00e7\u00e3o" } } } diff --git a/homeassistant/components/prosegur/translations/pt-BR.json b/homeassistant/components/prosegur/translations/pt-BR.json index 8df5069e4315fb..71d9df7c175718 100644 --- a/homeassistant/components/prosegur/translations/pt-BR.json +++ b/homeassistant/components/prosegur/translations/pt-BR.json @@ -12,12 +12,14 @@ "step": { "reauth_confirm": { "data": { + "description": "Re-autentique com a conta Prosegur.", "password": "Senha", "username": "Usu\u00e1rio" } }, "user": { "data": { + "country": "Pa\u00eds", "password": "Senha", "username": "Usu\u00e1rio" } diff --git a/homeassistant/components/ps4/translations/pt-BR.json b/homeassistant/components/ps4/translations/pt-BR.json index 06ddc17f942893..7938cbb6241d51 100644 --- a/homeassistant/components/ps4/translations/pt-BR.json +++ b/homeassistant/components/ps4/translations/pt-BR.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "credential_error": "Erro ao buscar credenciais.", - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "no_devices_found": "Nenhum dispositivo encontrado na rede", "port_987_bind_error": "N\u00e3o foi poss\u00edvel conectar na porta 987. Consulte a [documenta\u00e7\u00e3o] (https://www.home-assistant.io/components/ps4/) para informa\u00e7\u00f5es adicionais.", "port_997_bind_error": "N\u00e3o foi poss\u00edvel conectar na porta 997. Consulte a [documenta\u00e7\u00e3o] (https://www.home-assistant.io/components/ps4/) para informa\u00e7\u00f5es adicionais." }, diff --git a/homeassistant/components/pvoutput/translations/pt-BR.json b/homeassistant/components/pvoutput/translations/pt-BR.json index a97b0b3abd418c..39bcb9258c665e 100644 --- a/homeassistant/components/pvoutput/translations/pt-BR.json +++ b/homeassistant/components/pvoutput/translations/pt-BR.json @@ -11,13 +11,15 @@ "reauth_confirm": { "data": { "api_key": "Chave da API" - } + }, + "description": "Para re-autenticar com PVOutput, voc\u00ea precisar\u00e1 obter a chave API em {account_url}." }, "user": { "data": { "api_key": "Chave da API", "system_id": "ID do sistema" - } + }, + "description": "Para autenticar com PVOutput, voc\u00ea precisar\u00e1 obter a chave de API em {account_url} . \n\n Os IDs de sistema dos sistemas registrados s\u00e3o listados nessa mesma p\u00e1gina." } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/pt-BR.json b/homeassistant/components/pvpc_hourly_pricing/translations/pt-BR.json index 396f9f6ab67c04..e5754180a7cd6d 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/pt-BR.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/pt-BR.json @@ -7,10 +7,25 @@ "user": { "data": { "name": "Nome do sensor", - "tariff": "Tarifa contratada (1, 2 ou 3 per\u00edodos)" + "power": "Pot\u00eancia contratada (kW)", + "power_p3": "Pot\u00eancia contratada para o per\u00edodo de vale P3 (kW)", + "tariff": "Tarifa aplic\u00e1vel por zona geogr\u00e1fica" }, - "description": "Esse sensor usa a API oficial para obter [pre\u00e7os por hora de eletricidade (PVPC)]](https://www.esios.ree.es/es/pvpc) na Espanha. \nPara uma explica\u00e7\u00e3o mais precisa, visite os [documentos de integra\u00e7\u00e3o](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/). \n\nSelecione a taxa contratada com base no n\u00famero de per\u00edodos de cobran\u00e7a por dia: \n- 1 per\u00edodo: normal \n- 2 per\u00edodos: discrimina\u00e7\u00e3o (taxa noturna) \n- 3 per\u00edodos: carro el\u00e9trico (taxa noturna de 3 per\u00edodos)", - "title": "Sele\u00e7\u00e3o de tarifas" + "description": "Esse sensor usa a API oficial para obter [pre\u00e7os por hora de eletricidade (PVPC)](https://www.esios.ree.es/es/pvpc) na Espanha. \nPara uma explica\u00e7\u00e3o mais precisa, visite os [documentos de integra\u00e7\u00e3o](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/). \n\nSelecione a taxa contratada com base no n\u00famero de per\u00edodos de cobran\u00e7a por dia: \n- 1 per\u00edodo: normal \n- 2 per\u00edodos: discrimina\u00e7\u00e3o (taxa noturna) \n- 3 per\u00edodos: carro el\u00e9trico (taxa noturna de 3 per\u00edodos)", + "title": "Configura\u00e7\u00e3o do sensor" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "power": "Pot\u00eancia contratada (kW)", + "power_p3": "Pot\u00eancia contratada para o per\u00edodo de vale P3 (kW)", + "tariff": "Tarifa aplic\u00e1vel por zona geogr\u00e1fica" + }, + "description": "Este sensor usa a API oficial para obter [pre\u00e7os por hora de eletricidade (PVPC)](https://www.esios.ree.es/es/pvpc) na Espanha.\n Para uma explica\u00e7\u00e3o mais precisa, visite os [documentos de integra\u00e7\u00e3o](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", + "title": "Configura\u00e7\u00e3o do sensor" } } } diff --git a/homeassistant/components/rachio/translations/el.json b/homeassistant/components/rachio/translations/el.json new file mode 100644 index 00000000000000..4e3e9483945721 --- /dev/null +++ b/homeassistant/components/rachio/translations/el.json @@ -0,0 +1,10 @@ +{ + "config": { + "step": { + "user": { + "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03b1\u03c0\u03cc \u03c4\u03bf https://app.rach.io/. \u039c\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b9\u03c2 \u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03ba\u03b1\u03b9 \u03ba\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf 'GET API KEY'.", + "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c3\u03b1\u03c2 Rachio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rachio/translations/pt-BR.json b/homeassistant/components/rachio/translations/pt-BR.json index 558881465675f0..c1c53e065dced2 100644 --- a/homeassistant/components/rachio/translations/pt-BR.json +++ b/homeassistant/components/rachio/translations/pt-BR.json @@ -12,6 +12,17 @@ "user": { "data": { "api_key": "Chave da API" + }, + "description": "Voc\u00ea precisar\u00e1 da chave de API de https://app.rach.io/. V\u00e1 para Configura\u00e7\u00f5es e clique em 'GET API KEY'.", + "title": "Conecte-se ao seu dispositivo Rachio" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "manual_run_mins": "Dura\u00e7\u00e3o em minutos para ser executada ao ativar um interruptor de zona" } } } diff --git a/homeassistant/components/rainforest_eagle/translations/pt-BR.json b/homeassistant/components/rainforest_eagle/translations/pt-BR.json index 5082256276cbd6..e40f41a6152896 100644 --- a/homeassistant/components/rainforest_eagle/translations/pt-BR.json +++ b/homeassistant/components/rainforest_eagle/translations/pt-BR.json @@ -11,7 +11,9 @@ "step": { "user": { "data": { - "host": "Nome do host" + "cloud_id": "Cloud ID", + "host": "Nome do host", + "install_code": "C\u00f3digo de instala\u00e7\u00e3o" } } } diff --git a/homeassistant/components/rainmachine/translations/pt-BR.json b/homeassistant/components/rainmachine/translations/pt-BR.json index 1acfcef8cd31f1..6359b1b6ae9e1d 100644 --- a/homeassistant/components/rainmachine/translations/pt-BR.json +++ b/homeassistant/components/rainmachine/translations/pt-BR.json @@ -6,6 +6,7 @@ "error": { "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, + "flow_title": "{ip}", "step": { "user": { "data": { @@ -16,5 +17,15 @@ "title": "Preencha suas informa\u00e7\u00f5es" } } + }, + "options": { + "step": { + "init": { + "data": { + "zone_run_time": "Tempo de execu\u00e7\u00e3o da zona padr\u00e3o (em segundos)" + }, + "title": "Configurar RainMachine" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/rdw/translations/pt-BR.json b/homeassistant/components/rdw/translations/pt-BR.json index d0ed7981642846..57de88fd6e9d9e 100644 --- a/homeassistant/components/rdw/translations/pt-BR.json +++ b/homeassistant/components/rdw/translations/pt-BR.json @@ -1,7 +1,8 @@ { "config": { "error": { - "cannot_connect": "Falha ao conectar" + "cannot_connect": "Falha ao conectar", + "unknown_license_plate": "Placa desconhecida" }, "step": { "user": { diff --git a/homeassistant/components/recollect_waste/translations/pt-BR.json b/homeassistant/components/recollect_waste/translations/pt-BR.json index e29d809ebff3dd..0df3b63a2f8140 100644 --- a/homeassistant/components/recollect_waste/translations/pt-BR.json +++ b/homeassistant/components/recollect_waste/translations/pt-BR.json @@ -2,6 +2,27 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "invalid_place_or_service_id": "ID de local ou ID de servi\u00e7o inv\u00e1lido" + }, + "step": { + "user": { + "data": { + "place_id": "ID do lugar", + "service_id": "ID de servi\u00e7o" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "friendly_name": "Use nomes amig\u00e1veis para tipos de coleta (quando poss\u00edvel)" + }, + "title": "Configurar Recollect Waste" + } } } } \ No newline at end of file diff --git a/homeassistant/components/remote/translations/nl.json b/homeassistant/components/remote/translations/nl.json index 18d984f5c68f5a..47ba3d7eda740e 100644 --- a/homeassistant/components/remote/translations/nl.json +++ b/homeassistant/components/remote/translations/nl.json @@ -10,6 +10,8 @@ "is_on": "{entity_name} staat aan" }, "trigger_type": { + "changed_states": "{entity_name} in- of uitgeschakeld", + "toggled": "{entity_name} in- of uitgeschakeld", "turned_off": "{entity_name} uitgeschakeld", "turned_on": "{entity_name} ingeschakeld" } diff --git a/homeassistant/components/remote/translations/pt-BR.json b/homeassistant/components/remote/translations/pt-BR.json index e1220006111267..15d9b0d7c76e03 100644 --- a/homeassistant/components/remote/translations/pt-BR.json +++ b/homeassistant/components/remote/translations/pt-BR.json @@ -1,8 +1,19 @@ { "device_automation": { + "action_type": { + "toggle": "Alternar {entity_name}", + "turn_off": "Desligar {entity_name}", + "turn_on": "Ligar {entity_name}" + }, + "condition_type": { + "is_off": "{entity_name} est\u00e1 desligado", + "is_on": "{entity_name} est\u00e1 ligado" + }, "trigger_type": { "changed_states": "{entity_name} ligado ou desligado", - "toggled": "{entity_name} ligado ou desligado" + "toggled": "{entity_name} ligado ou desligado", + "turned_off": "{entity_name} for desligado", + "turned_on": "{entity_name} for ligado" } }, "state": { diff --git a/homeassistant/components/renault/translations/pt-BR.json b/homeassistant/components/renault/translations/pt-BR.json index a4c2dc620e3a14..28054eac1c5889 100644 --- a/homeassistant/components/renault/translations/pt-BR.json +++ b/homeassistant/components/renault/translations/pt-BR.json @@ -2,22 +2,33 @@ "config": { "abort": { "already_configured": "A conta j\u00e1 foi configurada", + "kamereon_no_account": "N\u00e3o foi poss\u00edvel encontrar a conta Kamereon", "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { "invalid_credentials": "Autentica\u00e7\u00e3o inv\u00e1lida" }, "step": { + "kamereon": { + "data": { + "kamereon_account_id": "ID da conta Kamereon" + }, + "title": "Selecione o ID da conta Kamereon" + }, "reauth_confirm": { "data": { "password": "Senha" }, + "description": "Atualize sua senha para {username}", "title": "Reautenticar Integra\u00e7\u00e3o" }, "user": { "data": { - "password": "Senha" - } + "locale": "Localidade", + "password": "Senha", + "username": "Email" + }, + "title": "Definir credenciais Renault" } } } diff --git a/homeassistant/components/rfxtrx/translations/pt-BR.json b/homeassistant/components/rfxtrx/translations/pt-BR.json index eae5b5e14841b2..6f867a22a55175 100644 --- a/homeassistant/components/rfxtrx/translations/pt-BR.json +++ b/homeassistant/components/rfxtrx/translations/pt-BR.json @@ -12,19 +12,73 @@ "data": { "host": "Nome do host", "port": "Porta" - } + }, + "title": "Selecione o endere\u00e7o de conex\u00e3o" + }, + "setup_serial": { + "data": { + "device": "Selecionar dispositivo" + }, + "title": "Dispositivo" }, "setup_serial_manual_path": { "data": { "device": "Caminho do Dispositivo USB" - } + }, + "title": "Caminho" + }, + "user": { + "data": { + "type": "Tipo de conex\u00e3o" + }, + "title": "Selecione o tipo de conex\u00e3o" } } }, + "device_automation": { + "action_type": { + "send_command": "Enviar comando: {subtype}", + "send_status": "Enviar atualiza\u00e7\u00e3o de status: {subtype}" + }, + "trigger_type": { + "command": "Comando recebido: {subtype}", + "status": "Status recebido: {subtype}" + } + }, "options": { "error": { "already_configured_device": "Dispositivo j\u00e1 est\u00e1 configurado", + "invalid_event_code": "C\u00f3digo de evento inv\u00e1lido", + "invalid_input_2262_off": "Entrada inv\u00e1lida para comando desligado", + "invalid_input_2262_on": "Entrada inv\u00e1lida para comando ligado", + "invalid_input_off_delay": "Entrada inv\u00e1lida para atraso de desligamento", "unknown": "Erro inesperado" + }, + "step": { + "prompt_options": { + "data": { + "automatic_add": "Habilitar a adi\u00e7\u00e3o autom\u00e1tica", + "debug": "Habilitar a depura\u00e7\u00e3o", + "device": "Selecione o dispositivo para configurar", + "event_code": "Insira o c\u00f3digo do evento para adicionar", + "remove_device": "Selecione o dispositivo para excluir" + }, + "title": "Op\u00e7\u00f5es de Rfxtrx" + }, + "set_device_options": { + "data": { + "command_off": "Valor de bits de dados para comando desligado", + "command_on": "Valor de bits de dados para comando ligado", + "data_bit": "N\u00famero de bits de dados", + "fire_event": "Ativar evento do dispositivo", + "off_delay": "Atraso de desligamento", + "off_delay_enabled": "Ativar atraso de desligamento", + "replace_device": "Selecione o dispositivo para substituir", + "signal_repetitions": "N\u00famero de repeti\u00e7\u00f5es de sinal", + "venetian_blind_mode": "Modo de persianas" + }, + "title": "Configurar op\u00e7\u00f5es do dispositivo" + } } } } \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/uk.json b/homeassistant/components/rfxtrx/translations/uk.json index 1b0938b8b70887..65cca65679ce78 100644 --- a/homeassistant/components/rfxtrx/translations/uk.json +++ b/homeassistant/components/rfxtrx/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e.", + "already_configured": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f.", "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" }, "error": { diff --git a/homeassistant/components/ring/translations/pt-BR.json b/homeassistant/components/ring/translations/pt-BR.json index 124d9d36c33f0f..e3bbe6cd9d02e1 100644 --- a/homeassistant/components/ring/translations/pt-BR.json +++ b/homeassistant/components/ring/translations/pt-BR.json @@ -8,11 +8,18 @@ "unknown": "Erro inesperado" }, "step": { + "2fa": { + "data": { + "2fa": "C\u00f3digo de verifica\u00e7\u00e3o em duas etapas" + }, + "title": "Autentica\u00e7\u00e3o de duas etapas" + }, "user": { "data": { "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "title": "Entrar com conta Ring" } } } diff --git a/homeassistant/components/risco/translations/pt-BR.json b/homeassistant/components/risco/translations/pt-BR.json index ab7d4be0a4c7d5..53659ab672a668 100644 --- a/homeassistant/components/risco/translations/pt-BR.json +++ b/homeassistant/components/risco/translations/pt-BR.json @@ -20,11 +20,35 @@ }, "options": { "step": { + "ha_to_risco": { + "data": { + "armed_away": "Armado Fora", + "armed_custom_bypass": "Bypass Armado Personalizado", + "armed_home": "Armado (Casa)", + "armed_night": "Armado (Noite)" + }, + "description": "Selecione o estado para definir seu alarme Risco ao armar o alarme do Home Assistant", + "title": "Mapear os estados do Home Assistant para os estados do Risco" + }, "init": { "data": { "code_arm_required": "C\u00f3digo PIN", - "code_disarm_required": "C\u00f3digo PIN" - } + "code_disarm_required": "C\u00f3digo PIN", + "scan_interval": "Quantas vezes pesquisar Risco (em segundos)" + }, + "title": "Configurar op\u00e7\u00f5es" + }, + "risco_to_ha": { + "data": { + "A": "Grupo A", + "B": "Grupo B", + "C": "Grupo C", + "D": "Grupo D", + "arm": "Armado (FORA)", + "partial_arm": "Parcialmente Armado (STAY)" + }, + "description": "Selecione qual estado o alarme do Home Assistant reportar\u00e1 para cada estado reportado pelo Risco", + "title": "Mapear os estados do Risco para os estados do Home Assistant" } } } diff --git a/homeassistant/components/rituals_perfume_genie/translations/pt-BR.json b/homeassistant/components/rituals_perfume_genie/translations/pt-BR.json index 8722382b01bd53..a278ec20ec292a 100644 --- a/homeassistant/components/rituals_perfume_genie/translations/pt-BR.json +++ b/homeassistant/components/rituals_perfume_genie/translations/pt-BR.json @@ -11,8 +11,10 @@ "step": { "user": { "data": { + "email": "Email", "password": "Senha" - } + }, + "title": "Conecte-se \u00e0 sua conta Rituals" } } } diff --git a/homeassistant/components/roku/translations/el.json b/homeassistant/components/roku/translations/el.json new file mode 100644 index 00000000000000..793cd468f33263 --- /dev/null +++ b/homeassistant/components/roku/translations/el.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 Roku \u03c3\u03b1\u03c2." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/pt-BR.json b/homeassistant/components/roku/translations/pt-BR.json index f0a9a26bdb84b2..408bbc915c0490 100644 --- a/homeassistant/components/roku/translations/pt-BR.json +++ b/homeassistant/components/roku/translations/pt-BR.json @@ -8,8 +8,12 @@ "error": { "cannot_connect": "Falha ao conectar" }, - "flow_title": "Roku: {name}", + "flow_title": "{name}", "step": { + "discovery_confirm": { + "description": "Deseja configurar {name}?", + "title": "Roku" + }, "ssdp_confirm": { "description": "Voc\u00ea quer configurar o {name}?", "title": "Roku" diff --git a/homeassistant/components/roomba/translations/pt-BR.json b/homeassistant/components/roomba/translations/pt-BR.json index dbd20196984478..ee0a29eac896df 100644 --- a/homeassistant/components/roomba/translations/pt-BR.json +++ b/homeassistant/components/roomba/translations/pt-BR.json @@ -2,26 +2,40 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", - "cannot_connect": "Falha ao conectar" + "cannot_connect": "Falha ao conectar", + "not_irobot_device": "O dispositivo descoberto n\u00e3o \u00e9 um dispositivo iRobot", + "short_blid": "O BLID foi truncado" }, "error": { "cannot_connect": "Falha ao conectar" }, + "flow_title": "{name} ( {host} )", "step": { "init": { "data": { "host": "Nome do host" - } + }, + "description": "Selecione um Roomba ou Braava.", + "title": "Conecte-se automaticamente ao dispositivo" + }, + "link": { + "description": "Pressione e segure o bot\u00e3o Home em {name} at\u00e9 que o dispositivo gere um som (cerca de dois segundos) e envie em 30 segundos.", + "title": "Recuperar Senha" }, "link_manual": { "data": { "password": "Senha" - } + }, + "description": "A senha do dispositivo n\u00e3o p\u00f4de ser recuperada automaticamente. Siga as etapas descritas na documenta\u00e7\u00e3o em: {auth_help_url}", + "title": "Digite a senha" }, "manual": { "data": { + "blid": "BLID", "host": "Nome do host" - } + }, + "description": "Nenhum Roomba ou Braava foi descoberto em sua rede.", + "title": "Conecte-se manualmente ao dispositivo" }, "user": { "data": { @@ -31,7 +45,7 @@ "host": "Nome do host", "password": "Senha" }, - "description": "Atualmente, a recupera\u00e7\u00e3o do BLID e da senha \u00e9 um processo manual. Siga as etapas descritas na documenta\u00e7\u00e3o em: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", + "description": "Selecione um Roomba ou Braava.", "title": "Conecte-se ao dispositivo" } } diff --git a/homeassistant/components/rpi_power/translations/pt-BR.json b/homeassistant/components/rpi_power/translations/pt-BR.json index 369064ba6cb5e1..f886cab722aea7 100644 --- a/homeassistant/components/rpi_power/translations/pt-BR.json +++ b/homeassistant/components/rpi_power/translations/pt-BR.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "no_devices_found": "N\u00e3o \u00e9 poss\u00edvel encontrar a classe de sistema necess\u00e1ria para este componente, verifique se o kernel \u00e9 recente e se o hardware \u00e9 suportado", "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "step": { @@ -8,5 +9,6 @@ "description": "Deseja iniciar a configura\u00e7\u00e3o?" } } - } + }, + "title": "Verificador de fonte de alimenta\u00e7\u00e3o Raspberry Pi" } \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/uk.json b/homeassistant/components/rpi_power/translations/uk.json index b60160e1c4ec3a..39b0dee9bdb330 100644 --- a/homeassistant/components/rpi_power/translations/uk.json +++ b/homeassistant/components/rpi_power/translations/uk.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u043d\u0430\u0439\u0442\u0438 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u0438\u0439 \u043a\u043b\u0430\u0441, \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u0438\u0439 \u0434\u043b\u044f \u0440\u043e\u0431\u043e\u0442\u0438 \u0446\u044c\u043e\u0433\u043e \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430. \u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u0443 \u0412\u0430\u0441 \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u043d\u0430\u0439\u043d\u043e\u0432\u0456\u0448\u0435 \u044f\u0434\u0440\u043e \u0456 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0432\u0430\u043d\u0435 \u043e\u0431\u043b\u0430\u0434\u043d\u0430\u043d\u043d\u044f.", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "step": { "confirm": { diff --git a/homeassistant/components/rtsp_to_webrtc/translations/nl.json b/homeassistant/components/rtsp_to_webrtc/translations/nl.json index 57d4fd851d2d9a..6b3344f2b6b0bd 100644 --- a/homeassistant/components/rtsp_to_webrtc/translations/nl.json +++ b/homeassistant/components/rtsp_to_webrtc/translations/nl.json @@ -12,6 +12,7 @@ }, "step": { "hassio_confirm": { + "description": "Wilt u Home Assistant configureren om verbinding te maken met de RTSPtoWebRTC-server die wordt geleverd door de add-on: {addon}?", "title": "RTSPtoWebRTC via Home Assistant add-on" }, "user": { diff --git a/homeassistant/components/rtsp_to_webrtc/translations/pt-BR.json b/homeassistant/components/rtsp_to_webrtc/translations/pt-BR.json index e6fd29a97df78f..7856079886272c 100644 --- a/homeassistant/components/rtsp_to_webrtc/translations/pt-BR.json +++ b/homeassistant/components/rtsp_to_webrtc/translations/pt-BR.json @@ -1,6 +1,8 @@ { "config": { "abort": { + "server_failure": "O servidor RTSPtoWebRTC retornou um erro. Verifique os logs para obter mais informa\u00e7\u00f5es.", + "server_unreachable": "N\u00e3o \u00e9 poss\u00edvel se comunicar com o servidor RTSPtoWebRTC. Verifique os logs para obter mais informa\u00e7\u00f5es.", "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "error": { diff --git a/homeassistant/components/samsungtv/translations/pt-BR.json b/homeassistant/components/samsungtv/translations/pt-BR.json index 429ae516c9b805..407e9d94d0a5c6 100644 --- a/homeassistant/components/samsungtv/translations/pt-BR.json +++ b/homeassistant/components/samsungtv/translations/pt-BR.json @@ -3,16 +3,32 @@ "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "auth_missing": "O Home Assistant n\u00e3o est\u00e1 autorizado a se conectar a esta TV Samsung. Verifique as configura\u00e7\u00f5es do Gerenciador de dispositivos externos da sua TV para autorizar o Home Assistant.", "cannot_connect": "Falha ao conectar", + "id_missing": "Este dispositivo Samsung n\u00e3o possui um SerialNumber.", + "missing_config_entry": "Este dispositivo Samsung n\u00e3o tem uma entrada de configura\u00e7\u00e3o.", + "not_supported": "Este dispositivo Samsung n\u00e3o \u00e9 compat\u00edvel no momento.", "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", "unknown": "Erro inesperado" }, + "error": { + "auth_missing": "O Home Assistant n\u00e3o est\u00e1 autorizado a se conectar a esta TV Samsung. Verifique as configura\u00e7\u00f5es do Gerenciador de dispositivos externos da sua TV para autorizar o Home Assistant." + }, + "flow_title": "{device}", "step": { + "confirm": { + "description": "Deseja configurar {device}? Se voc\u00ea nunca conectou o Home Assistant antes, aparecer\u00e1 um pop-up na sua TV pedindo autoriza\u00e7\u00e3o.", + "title": "TV Samsung" + }, + "reauth_confirm": { + "description": "Ap\u00f3s o envio, aceite o pop-up em {device} solicitando autoriza\u00e7\u00e3o em 30 segundos." + }, "user": { "data": { "host": "Nome do host", "name": "Nome" - } + }, + "description": "Insira suas informa\u00e7\u00f5es da Samsung TV. Se voc\u00ea nunca conectou o Home Assistant antes de ver um pop-up na sua TV pedindo autoriza\u00e7\u00e3o." } } } diff --git a/homeassistant/components/screenlogic/translations/pt-BR.json b/homeassistant/components/screenlogic/translations/pt-BR.json index 3640d2ac0a7bae..ee99fa03406d2a 100644 --- a/homeassistant/components/screenlogic/translations/pt-BR.json +++ b/homeassistant/components/screenlogic/translations/pt-BR.json @@ -6,12 +6,33 @@ "error": { "cannot_connect": "Falha ao conectar" }, + "flow_title": "{name}", "step": { "gateway_entry": { "data": { "ip_address": "Endere\u00e7o IP", "port": "Porta" - } + }, + "description": "Insira as informa\u00e7\u00f5es do seu ScreenLogic Gateway.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "Gateway" + }, + "description": "Os seguintes gateways ScreenLogic foram descobertos. Selecione um para configurar ou opte por configurar manualmente um gateway ScreenLogic.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Segundos entre escaneamentos" + }, + "description": "Especifique as configura\u00e7\u00f5es para {gateway_name}", + "title": "ScreenLogic" } } } diff --git a/homeassistant/components/select/translations/pt-BR.json b/homeassistant/components/select/translations/pt-BR.json new file mode 100644 index 00000000000000..1eabd618ce31ab --- /dev/null +++ b/homeassistant/components/select/translations/pt-BR.json @@ -0,0 +1,14 @@ +{ + "device_automation": { + "action_type": { + "select_option": "Alterar op\u00e7\u00e3o de {entity_name}" + }, + "condition_type": { + "selected_option": "Op\u00e7\u00e3o selecionada atual de {entity_name}" + }, + "trigger_type": { + "current_option_changed": "{entity_name} op\u00e7\u00e3o alterada" + } + }, + "title": "Selecionar" +} \ No newline at end of file diff --git a/homeassistant/components/sense/translations/pt-BR.json b/homeassistant/components/sense/translations/pt-BR.json index ad7889f7536296..3544bd22dc33aa 100644 --- a/homeassistant/components/sense/translations/pt-BR.json +++ b/homeassistant/components/sense/translations/pt-BR.json @@ -11,9 +11,11 @@ "step": { "user": { "data": { + "email": "Email", "password": "Senha", "timeout": "Tempo limite" - } + }, + "title": "Conecte-se ao seu monitor de Energia Sense" } } } diff --git a/homeassistant/components/senseme/translations/ja.json b/homeassistant/components/senseme/translations/ja.json index 4971b9f4494dcf..36fd50cfdcc60a 100644 --- a/homeassistant/components/senseme/translations/ja.json +++ b/homeassistant/components/senseme/translations/ja.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" + "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f" }, "error": { "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", diff --git a/homeassistant/components/sensor/translations/pt-BR.json b/homeassistant/components/sensor/translations/pt-BR.json index dfb4321dbc69d7..72e1c7ba023be7 100644 --- a/homeassistant/components/sensor/translations/pt-BR.json +++ b/homeassistant/components/sensor/translations/pt-BR.json @@ -2,29 +2,61 @@ "device_automation": { "condition_type": { "is_apparent_power": "Pot\u00eancia aparente atual de {entity_name}", - "is_battery_level": "N\u00edvel atual da bateria {nome_entidade}", + "is_battery_level": "N\u00edvel atual da bateria {entity_name}", + "is_carbon_dioxide": "N\u00edvel atual de concentra\u00e7\u00e3o de di\u00f3xido de carbono de {entity_name}", + "is_carbon_monoxide": "N\u00edvel de concentra\u00e7\u00e3o de mon\u00f3xido de carbono atual de {entity_name}", + "is_current": "Corrente atual de {entity_name}", + "is_energy": "Energia atual de {entity_name}", "is_frequency": "Frequ\u00eancia atual de {entity_name}", + "is_gas": "G\u00e1s atual de {entity_name}", "is_humidity": "Humidade atual do(a) {entity_name}", - "is_illuminance": "Luminosidade atual {nome_da_entidade}", + "is_illuminance": "Luminosidade atual {entity_name}", + "is_nitrogen_dioxide": "N\u00edvel atual de concentra\u00e7\u00e3o de di\u00f3xido de nitrog\u00eanio de {entity_name}", + "is_nitrogen_monoxide": "N\u00edvel atual de concentra\u00e7\u00e3o de mon\u00f3xido de nitrog\u00eanio de {entity_name}", + "is_nitrous_oxide": "N\u00edvel atual de concentra\u00e7\u00e3o de \u00f3xido nitroso de {entity_name}", + "is_ozone": "N\u00edvel atual de concentra\u00e7\u00e3o de oz\u00f4nio de {entity_name}", + "is_pm1": "N\u00edvel de concentra\u00e7\u00e3o PM1 atual de {entity_name}", + "is_pm10": "N\u00edvel de concentra\u00e7\u00e3o PM10 atual de {entity_name}", + "is_pm25": "N\u00edvel de concentra\u00e7\u00e3o PM2.5 atual de {entity_name}", "is_power": "Pot\u00eancia atual {entity_name}", + "is_power_factor": "Fator de pot\u00eancia atual de {entity_name}", "is_pressure": "Press\u00e3o atual do(a) {entity_name}", "is_reactive_power": "Pot\u00eancia reativa atual de {entity_name}", "is_signal_strength": "For\u00e7a do sinal atual do(a) {entity_name}", + "is_sulphur_dioxide": "N\u00edvel atual de concentra\u00e7\u00e3o de di\u00f3xido de enxofre de {entity_name}", "is_temperature": "Temperatura atual do(a) {entity_name}", - "is_value": "Valor atual de {entity_name}" + "is_value": "Valor atual de {entity_name}", + "is_volatile_organic_compounds": "N\u00edvel atual de concentra\u00e7\u00e3o de compostos org\u00e2nicos vol\u00e1teis de {entity_name}", + "is_voltage": "Tens\u00e3o atual de {entity_name}" }, "trigger_type": { "apparent_power": "Mudan\u00e7as de poder aparentes de {entity_name}", - "battery_level": "{nome_da_entidade} mudan\u00e7as no n\u00edvel da bateria", + "battery_level": "{entity_name} mudan\u00e7as no n\u00edvel da bateria", + "carbon_dioxide": "Mudan\u00e7as na concentra\u00e7\u00e3o de di\u00f3xido de carbono de {entity_name}", + "carbon_monoxide": "Altera\u00e7\u00f5es na concentra\u00e7\u00e3o de mon\u00f3xido de carbono de {entity_name}", + "current": "Mudan\u00e7a na corrente de {entity_name}", + "energy": "Mudan\u00e7as na energia de {entity_name}", "frequency": "Altera\u00e7\u00f5es de frequ\u00eancia de {entity_name}", - "humidity": "{nome_da_entidade} mudan\u00e7as de umidade", - "illuminance": "{nome_da_entidade} mudan\u00e7as de luminosidade", + "gas": "Mudan\u00e7as de g\u00e1s de {entity_name}", + "humidity": "{entity_name} mudan\u00e7as de umidade", + "illuminance": "{entity_name} mudan\u00e7as de luminosidade", + "nitrogen_dioxide": "Mudan\u00e7as na concentra\u00e7\u00e3o de di\u00f3xido de nitrog\u00eanio de {entity_name}", + "nitrogen_monoxide": "Mudan\u00e7as na concentra\u00e7\u00e3o de mon\u00f3xido de nitrog\u00eanio de {entity_name}", + "nitrous_oxide": "Altera\u00e7\u00f5es na concentra\u00e7\u00e3o de \u00f3xido nitroso de {entity_name}", + "ozone": "Mudan\u00e7as na concentra\u00e7\u00e3o de oz\u00f4nio de {entity_name}", + "pm1": "Mudan\u00e7as na concentra\u00e7\u00e3o PM1 de {entity_name}", + "pm10": "Mudan\u00e7as na concentra\u00e7\u00e3o PM10 de {entity_name}", + "pm25": "Altera\u00e7\u00f5es na concentra\u00e7\u00e3o PM2.5 de {entity_name}", "power": "{entity_name} mudan\u00e7as de energia", + "power_factor": "Altera\u00e7\u00f5es do fator de pot\u00eancia de {entity_name}", "pressure": "{entity_name} mudan\u00e7as de press\u00e3o", "reactive_power": "Altera\u00e7\u00f5es de pot\u00eancia reativa de {entity_name}", - "signal_strength": "{nome_da_entidade} muda a for\u00e7a do sinal", + "signal_strength": "{entity_name} muda a for\u00e7a do sinal", + "sulphur_dioxide": "Altera\u00e7\u00f5es na concentra\u00e7\u00e3o de di\u00f3xido de enxofre de {entity_name}", "temperature": "{entity_name} mudan\u00e7as de temperatura", - "value": "{nome_da_entidade} mudan\u00e7as de valor" + "value": "{entity_name} mudan\u00e7as de valor", + "volatile_organic_compounds": "Altera\u00e7\u00f5es na concentra\u00e7\u00e3o de compostos org\u00e2nicos vol\u00e1teis de {entity_name}", + "voltage": "Mudan\u00e7as de voltagem de {entity_name}" } }, "state": { diff --git a/homeassistant/components/sentry/translations/pt-BR.json b/homeassistant/components/sentry/translations/pt-BR.json index cae844d66ee96d..21f1e3ef91a983 100644 --- a/homeassistant/components/sentry/translations/pt-BR.json +++ b/homeassistant/components/sentry/translations/pt-BR.json @@ -9,7 +9,27 @@ }, "step": { "user": { - "description": "Digite seu DSN Sentry" + "data": { + "dsn": "DSN" + }, + "description": "Digite seu DSN Sentry", + "title": "Sentry" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "environment": "Nome opcional do ambiente.", + "event_custom_components": "Enviar eventos de componentes personalizados", + "event_handled": "Enviar eventos tratados", + "event_third_party_packages": "Envie eventos de pacotes de terceiros", + "logging_event_level": "O n\u00edvel de registro Sentry registrar\u00e1 um evento para", + "logging_level": "O n\u00edvel de log Sentry gravar\u00e1 logs como breadcrums para", + "tracing": "Habilitar o rastreamento de desempenho", + "tracing_sample_rate": "Taxa de amostragem de rastreamento; entre 0,0 e 1,0 (1,0 = 100%)" + } } } } diff --git a/homeassistant/components/sentry/translations/uk.json b/homeassistant/components/sentry/translations/uk.json index 01da0308851d37..124ac4543ed899 100644 --- a/homeassistant/components/sentry/translations/uk.json +++ b/homeassistant/components/sentry/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "error": { "bad_dsn": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 DSN.", diff --git a/homeassistant/components/shelly/translations/pt-BR.json b/homeassistant/components/shelly/translations/pt-BR.json index 47d51f4454720e..8b8ec5ea020c9b 100644 --- a/homeassistant/components/shelly/translations/pt-BR.json +++ b/homeassistant/components/shelly/translations/pt-BR.json @@ -1,14 +1,19 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "unsupported_firmware": "O dispositivo est\u00e1 usando uma vers\u00e3o de firmware n\u00e3o compat\u00edvel." }, "error": { "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, + "flow_title": "{name}", "step": { + "confirm_discovery": { + "description": "Deseja configurar o {model} em {host} ? \n\n Os dispositivos alimentados por bateria que s\u00e3o protegidos por senha devem ser ativados antes de continuar com a configura\u00e7\u00e3o.\n Dispositivos alimentados por bateria que n\u00e3o s\u00e3o protegidos por senha ser\u00e3o adicionados quando o dispositivo for ativado, agora voc\u00ea pode ativar manualmente o dispositivo usando um bot\u00e3o ou aguardar a pr\u00f3xima atualiza\u00e7\u00e3o de dados do dispositivo." + }, "credentials": { "data": { "password": "Senha", @@ -18,8 +23,31 @@ "user": { "data": { "host": "Nome do host" - } + }, + "description": "Antes de configurar, os dispositivos alimentados por bateria devem ser ativados, agora voc\u00ea pode ativar o dispositivo usando um bot\u00e3o nele." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "Bot\u00e3o", + "button1": "Primeiro bot\u00e3o", + "button2": "Segundo bot\u00e3o", + "button3": "Terceiro bot\u00e3o", + "button4": "Quarto bot\u00e3o" + }, + "trigger_type": { + "btn_down": "{subtype} bot\u00e3o para baixo", + "btn_up": "{subtype} bot\u00e3o para cima", + "double": "{subtype} clicado duas vezes", + "double_push": "{subtype} empurr\u00e3o duplo", + "long": "{subtype} clicado longo", + "long_push": "{subtype} empurr\u00e3o longo", + "long_single": "{subtype} clicado longo e, em seguida, \u00fanico clicado", + "single": "{subtype} \u00fanico clicado", + "single_long": "{subtype} \u00fanico clicado e, em seguida, clique longo", + "single_push": "{subtype} \u00fanico empurr\u00e3o", + "triple": "{subtype} triplo clicado" + } } } \ No newline at end of file diff --git a/homeassistant/components/sia/translations/pt-BR.json b/homeassistant/components/sia/translations/pt-BR.json index ccc0fc7c4776bf..a113717859fd7f 100644 --- a/homeassistant/components/sia/translations/pt-BR.json +++ b/homeassistant/components/sia/translations/pt-BR.json @@ -1,14 +1,50 @@ { "config": { "error": { + "invalid_account_format": "A conta n\u00e3o \u00e9 um valor hexadecimal, use apenas 0-9 e AF.", + "invalid_account_length": "A conta n\u00e3o tem o tamanho certo, tem que ter entre 3 e 16 caracteres.", + "invalid_key_format": "A chave n\u00e3o \u00e9 um valor hexadecimal, use apenas 0-9 e AF.", + "invalid_key_length": "A chave n\u00e3o tem o tamanho certo, tem que ter 16, 24 ou 32 caracteres hexadecimais.", + "invalid_ping": "O intervalo de ping precisa estar entre 1 e 1440 minutos.", + "invalid_zones": "Deve haver pelo menos 1 zona.", "unknown": "Erro inesperado" }, "step": { + "additional_account": { + "data": { + "account": "ID da conta", + "additional_account": "Contas adicionais", + "encryption_key": "Chave de encripta\u00e7\u00e3o", + "ping_interval": "Intervalo de ping (min)", + "zones": "N\u00famero de zonas para a conta" + }, + "title": "Adicione outra conta \u00e0 porta atual." + }, "user": { "data": { - "port": "Porta" - } + "account": "ID da conta", + "additional_account": "Contas adicionais", + "encryption_key": "Chave de encripta\u00e7\u00e3o", + "ping_interval": "Intervalo de ping (min)", + "port": "Porta", + "protocol": "Protocolo", + "zones": "N\u00famero de zonas para a conta" + }, + "title": "Crie uma conex\u00e3o para sistemas de alarme baseados em SIA." + } + } + }, + "options": { + "step": { + "options": { + "data": { + "ignore_timestamps": "Ignore a verifica\u00e7\u00e3o do timestamp dos eventos do SIA", + "zones": "N\u00famero de zonas para a conta" + }, + "description": "Defina as op\u00e7\u00f5es da conta: {account}", + "title": "Op\u00e7\u00f5es para a configura\u00e7\u00e3o SIA." } } - } + }, + "title": "Sistemas de alarme SIA" } \ No newline at end of file diff --git a/homeassistant/components/simplisafe/translations/el.json b/homeassistant/components/simplisafe/translations/el.json index 776b52e4d70547..35cb5786fd2c1e 100644 --- a/homeassistant/components/simplisafe/translations/el.json +++ b/homeassistant/components/simplisafe/translations/el.json @@ -22,5 +22,15 @@ "title": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03b1\u03c2" } } + }, + "options": { + "step": { + "init": { + "data": { + "code": "\u039a\u03ce\u03b4\u03b9\u03ba\u03b1\u03c2 (\u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03c3\u03c4\u03bf UI \u03c4\u03bf\u03c5 Home Assistant)" + }, + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 SimpliSafe" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/simplisafe/translations/pt-BR.json b/homeassistant/components/simplisafe/translations/pt-BR.json index cdf8d042b28bbb..f64e478a485d2b 100644 --- a/homeassistant/components/simplisafe/translations/pt-BR.json +++ b/homeassistant/components/simplisafe/translations/pt-BR.json @@ -1,31 +1,54 @@ { "config": { "abort": { - "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + "already_configured": "Esta conta SimpliSafe j\u00e1 est\u00e1 em uso.", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", + "wrong_account": "As credenciais de usu\u00e1rio fornecidas n\u00e3o correspondem a esta conta SimpliSafe." }, "error": { "identifier_exists": "Conta j\u00e1 cadastrada", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "still_awaiting_mfa": "Ainda aguardando clique no e-mail da MFA", "unknown": "Erro inesperado" }, "step": { "input_auth_code": { "data": { "auth_code": "C\u00f3digo de Autoriza\u00e7\u00e3o" - } + }, + "description": "Insira o c\u00f3digo de autoriza\u00e7\u00e3o do URL do aplicativo Web SimpliSafe:", + "title": "Concluir autoriza\u00e7\u00e3o" + }, + "mfa": { + "description": "Verifique seu e-mail para obter um link do SimpliSafe. Ap\u00f3s verificar o link, volte aqui para concluir a instala\u00e7\u00e3o da integra\u00e7\u00e3o.", + "title": "Autentica\u00e7\u00e3o SimpliSafe multifator" }, "reauth_confirm": { "data": { "password": "Senha" }, + "description": "Seu acesso expirou ou foi revogado. Digite sua senha para vincular novamente sua conta.", "title": "Reautenticar Integra\u00e7\u00e3o" }, "user": { "data": { + "auth_code": "C\u00f3digo de autoriza\u00e7\u00e3o", + "code": "C\u00f3digo (usado na IU do Home Assistant)", "password": "Senha", - "username": "Endere\u00e7o de e-mail" + "username": "Email" + }, + "description": "O SimpliSafe autentica com o Home Assistant por meio do aplicativo da Web SimpliSafe. Por limita\u00e7\u00f5es t\u00e9cnicas, existe uma etapa manual ao final deste processo; certifique-se de ler a [documenta\u00e7\u00e3o]( {docs_url} ) antes de come\u00e7ar. \n\n 1. Clique [aqui]( {url} ) para abrir o aplicativo da web SimpliSafe e insira suas credenciais. \n\n 2. Quando o processo de login estiver conclu\u00eddo, retorne aqui e insira o c\u00f3digo de autoriza\u00e7\u00e3o abaixo.", + "title": "Preencha suas informa\u00e7\u00f5es." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "code": "C\u00f3digo (usado na IU do Home Assistant)" }, - "title": "Preencha suas informa\u00e7\u00f5es" + "title": "Configurar SimpliSafe" } } } diff --git a/homeassistant/components/sma/translations/pt-BR.json b/homeassistant/components/sma/translations/pt-BR.json index 566418e49a95d7..21017fe6fc3080 100644 --- a/homeassistant/components/sma/translations/pt-BR.json +++ b/homeassistant/components/sma/translations/pt-BR.json @@ -6,17 +6,21 @@ }, "error": { "cannot_connect": "Falha ao conectar", + "cannot_retrieve_device_info": "Conectado com sucesso, mas incapaz de recuperar as informa\u00e7\u00f5es do dispositivo", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, "step": { "user": { "data": { + "group": "Grupo", "host": "Nome do host", "password": "Senha", "ssl": "Usar um certificado SSL", "verify_ssl": "Verifique o certificado SSL" - } + }, + "description": "Insira as informa\u00e7\u00f5es do seu dispositivo SMA.", + "title": "Configurar SMA Solar" } } } diff --git a/homeassistant/components/smappee/translations/pt-BR.json b/homeassistant/components/smappee/translations/pt-BR.json index a0219eb309f6a8..dfd6b31fe2e471 100644 --- a/homeassistant/components/smappee/translations/pt-BR.json +++ b/homeassistant/components/smappee/translations/pt-BR.json @@ -2,16 +2,33 @@ "config": { "abort": { "already_configured_device": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_configured_local_device": "Os dispositivos locais j\u00e1 est\u00e3o configurados. Remova-os primeiro antes de configurar um dispositivo em nuvem.", "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", "cannot_connect": "Falha ao conectar", + "invalid_mdns": "Dispositivo n\u00e3o suportado para a integra\u00e7\u00e3o do Smappee.", "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", "no_url_available": "N\u00e3o h\u00e1 URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a se\u00e7\u00e3o de ajuda]({docs_url})" }, + "flow_title": "{name}", "step": { + "environment": { + "data": { + "environment": "Ambiente" + }, + "description": "Configure seu Smappee para integrar com o Home Assistant." + }, "local": { "data": { "host": "Nome do host" - } + }, + "description": "Digite o host para iniciar a integra\u00e7\u00e3o local do Smappee" + }, + "pick_implementation": { + "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" + }, + "zeroconf_confirm": { + "description": "Deseja adicionar o dispositivo Smappee com n\u00famero de s\u00e9rie ` {serialnumber} ` ao Home Assistant?", + "title": "Dispositivo Smappee descoberto" } } } diff --git a/homeassistant/components/smarthab/translations/pt-BR.json b/homeassistant/components/smarthab/translations/pt-BR.json index 9200a7c2eac1f1..ef205c53827d28 100644 --- a/homeassistant/components/smarthab/translations/pt-BR.json +++ b/homeassistant/components/smarthab/translations/pt-BR.json @@ -2,13 +2,17 @@ "config": { "error": { "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "service": "Erro ao tentar acessar o SmartHab. O servi\u00e7o pode estar inoperante. Verifique sua conex\u00e3o.", "unknown": "Erro inesperado" }, "step": { "user": { "data": { + "email": "Email", "password": "Senha" - } + }, + "description": "Por motivos t\u00e9cnicos, certifique-se de usar uma conta secund\u00e1ria espec\u00edfica para a configura\u00e7\u00e3o do Home Assistant. Voc\u00ea pode criar um a partir do aplicativo SmartHab.", + "title": "Configurar SmartHab" } } } diff --git a/homeassistant/components/smartthings/translations/pt-BR.json b/homeassistant/components/smartthings/translations/pt-BR.json index 9b6fe28f84bf15..a4a80fb29cca46 100644 --- a/homeassistant/components/smartthings/translations/pt-BR.json +++ b/homeassistant/components/smartthings/translations/pt-BR.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "invalid_webhook_url": "O Home Assistant n\u00e3o est\u00e1 configurado corretamente para receber atualiza\u00e7\u00f5es do SmartThings. O URL do webhook \u00e9 inv\u00e1lido:\n> {webhook_url}\n\nAtualize sua configura\u00e7\u00e3o de acordo com as [instru\u00e7\u00f5es]({component_url}), reinicie o Home Assistant e tente novamente.", "no_available_locations": "N\u00e3o h\u00e1 Locais SmartThings dispon\u00edveis para configura\u00e7\u00e3o no Home Assistant." }, "error": { @@ -8,17 +9,29 @@ "token_forbidden": "O token n\u00e3o possui os escopos necess\u00e1rios do OAuth.", "token_invalid_format": "O token deve estar no formato UID / GUID", "token_unauthorized": "O token \u00e9 inv\u00e1lido ou n\u00e3o est\u00e1 mais autorizado.", - "webhook_error": "O SmartThings n\u00e3o p\u00f4de validar o terminal configurado em `base_url`. Por favor, revise os requisitos do componente." + "webhook_error": "SmartThings n\u00e3o p\u00f4de validar a URL do webhook. Por favor, certifique-se de que a URL do webhook seja acess\u00edvel a partir da internet e tente novamente." }, "step": { + "authorize": { + "title": "Autorizar o Home Assistant" + }, "pat": { "data": { "access_token": "Token de acesso" - } + }, + "description": "Insira um [Token de Acesso Pessoal]({token_url}) SmartThings que foi criado de acordo com as [instru\u00e7\u00f5es]({component_url}). Isso ser\u00e1 usado para criar a integra\u00e7\u00e3o do Home Assistant em sua conta SmartThings.", + "title": "Insira o token de acesso pessoal" + }, + "select_location": { + "data": { + "location_id": "Localiza\u00e7\u00e3o" + }, + "description": "Selecione o local do SmartThings que deseja adicionar ao Home Assistant. Em seguida, abriremos uma nova janela e solicitaremos que voc\u00ea fa\u00e7a login e autorize a instala\u00e7\u00e3o da integra\u00e7\u00e3o do Home Assistant no local selecionado.", + "title": "Selecione a localiza\u00e7\u00e3o" }, "user": { - "description": "Por favor, insira um SmartThings [Personal Access Token] ( {token_url} ) que foi criado de acordo com as [instru\u00e7\u00f5es] ( {component_url} ).", - "title": "Digite o token de acesso pessoal" + "description": "SmartThings ser\u00e1 configurado para enviar atualiza\u00e7\u00f5es push para home assistant em:\n> {webhook_url}\n\nSe isso n\u00e3o estiver correto, atualize sua configura\u00e7\u00e3o, reinicie o Home Assistant e tente novamente.", + "title": "Confirmar URL de retorno de chamada" } } } diff --git a/homeassistant/components/smarttub/translations/pt-BR.json b/homeassistant/components/smarttub/translations/pt-BR.json index 77da3eac5d8585..4eedb6d7a3bbe2 100644 --- a/homeassistant/components/smarttub/translations/pt-BR.json +++ b/homeassistant/components/smarttub/translations/pt-BR.json @@ -9,12 +9,16 @@ }, "step": { "reauth_confirm": { + "description": "A integra\u00e7\u00e3o do SmartTub precisa re-autenticar sua conta", "title": "Reautenticar Integra\u00e7\u00e3o" }, "user": { "data": { + "email": "Email", "password": "Senha" - } + }, + "description": "Digite seu endere\u00e7o de e-mail e senha do SmartTub para fazer login", + "title": "Login" } } } diff --git a/homeassistant/components/sms/translations/pt-BR.json b/homeassistant/components/sms/translations/pt-BR.json index 9b73cd590b25c6..05e211760f2518 100644 --- a/homeassistant/components/sms/translations/pt-BR.json +++ b/homeassistant/components/sms/translations/pt-BR.json @@ -7,6 +7,14 @@ "error": { "cannot_connect": "Falha ao conectar", "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "device": "Dispositivo" + }, + "title": "Conectado ao modem" + } } } } \ No newline at end of file diff --git a/homeassistant/components/sms/translations/uk.json b/homeassistant/components/sms/translations/uk.json index be271a2b6e4896..0105742da5fe11 100644 --- a/homeassistant/components/sms/translations/uk.json +++ b/homeassistant/components/sms/translations/uk.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\u0426\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "error": { "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", diff --git a/homeassistant/components/solaredge/translations/pt-BR.json b/homeassistant/components/solaredge/translations/pt-BR.json index 3fdd2b2db643a6..f005c2913ef6a3 100644 --- a/homeassistant/components/solaredge/translations/pt-BR.json +++ b/homeassistant/components/solaredge/translations/pt-BR.json @@ -5,7 +5,9 @@ }, "error": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", - "invalid_api_key": "Chave de API inv\u00e1lida" + "could_not_connect": "N\u00e3o foi poss\u00edvel conectar-se \u00e0 API do solaredge", + "invalid_api_key": "Chave de API inv\u00e1lida", + "site_not_active": "O site n\u00e3o est\u00e1 ativo" }, "step": { "user": { diff --git a/homeassistant/components/solarlog/translations/pt-BR.json b/homeassistant/components/solarlog/translations/pt-BR.json index 30221b1d790b98..b935d245da9e7b 100644 --- a/homeassistant/components/solarlog/translations/pt-BR.json +++ b/homeassistant/components/solarlog/translations/pt-BR.json @@ -10,8 +10,10 @@ "step": { "user": { "data": { - "host": "Nome do host" - } + "host": "Nome do host", + "name": "O prefixo a ser usado para seus sensores Solar-Log" + }, + "title": "Defina sua conex\u00e3o Solar-Log" } } } diff --git a/homeassistant/components/solax/translations/ja.json b/homeassistant/components/solax/translations/ja.json new file mode 100644 index 00000000000000..70aee01ce0f2a9 --- /dev/null +++ b/homeassistant/components/solax/translations/ja.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" + }, + "step": { + "user": { + "data": { + "ip_address": "IP\u30a2\u30c9\u30ec\u30b9", + "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", + "port": "\u30dd\u30fc\u30c8" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solax/translations/nl.json b/homeassistant/components/solax/translations/nl.json new file mode 100644 index 00000000000000..4e0c400148f993 --- /dev/null +++ b/homeassistant/components/solax/translations/nl.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "Kan geen verbinding maken", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "ip_address": "IP-adres", + "password": "Wachtwoord", + "port": "Poort" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solax/translations/uk.json b/homeassistant/components/solax/translations/uk.json new file mode 100644 index 00000000000000..bc7d29b12f9e42 --- /dev/null +++ b/homeassistant/components/solax/translations/uk.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/soma/translations/el.json b/homeassistant/components/soma/translations/el.json new file mode 100644 index 00000000000000..35980b52f406a0 --- /dev/null +++ b/homeassistant/components/soma/translations/el.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "missing_configuration": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf Soma \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7.", + "result_error": "\u03a4\u03bf SOMA Connect \u03b1\u03c0\u03ac\u03bd\u03c4\u03b7\u03c3\u03b5 \u03bc\u03b5 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1\u03c4\u03bf\u03c2." + }, + "step": { + "user": { + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 SOMA Connect.", + "title": "SOMA Connect" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/soma/translations/pt-BR.json b/homeassistant/components/soma/translations/pt-BR.json index bb4ddf9a67c8e5..fb1af9db783f26 100644 --- a/homeassistant/components/soma/translations/pt-BR.json +++ b/homeassistant/components/soma/translations/pt-BR.json @@ -15,7 +15,9 @@ "data": { "host": "Nome do host", "port": "Porta" - } + }, + "description": "Insira as configura\u00e7\u00f5es de conex\u00e3o do seu SOMA Connect.", + "title": "SOMA Connect" } } } diff --git a/homeassistant/components/somfy/translations/pt-BR.json b/homeassistant/components/somfy/translations/pt-BR.json index 5e658d36102bc8..8ad5fac904490d 100644 --- a/homeassistant/components/somfy/translations/pt-BR.json +++ b/homeassistant/components/somfy/translations/pt-BR.json @@ -8,6 +8,11 @@ }, "create_entry": { "default": "Autenticado com sucesso" + }, + "step": { + "pick_implementation": { + "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" + } } } } \ No newline at end of file diff --git a/homeassistant/components/somfy/translations/uk.json b/homeassistant/components/somfy/translations/uk.json index ebf7e41044eef6..207169ad6b0ae1 100644 --- a/homeassistant/components/somfy/translations/uk.json +++ b/homeassistant/components/somfy/translations/uk.json @@ -4,7 +4,7 @@ "authorize_url_timeout": "\u041c\u0438\u043d\u0443\u0432 \u0447\u0430\u0441 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0456\u0457 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457.", "missing_configuration": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438.", "no_url_available": "URL-\u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430. \u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0456\u0454\u044e] ({docs_url}) \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457 \u043f\u0440\u043e \u0446\u044e \u043f\u043e\u043c\u0438\u043b\u043a\u0443.", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "create_entry": { "default": "\u0410\u0432\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044e \u0443\u0441\u043f\u0456\u0448\u043d\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e." diff --git a/homeassistant/components/somfy_mylink/translations/pt-BR.json b/homeassistant/components/somfy_mylink/translations/pt-BR.json index f01e9d01465977..12efd378984f2b 100644 --- a/homeassistant/components/somfy_mylink/translations/pt-BR.json +++ b/homeassistant/components/somfy_mylink/translations/pt-BR.json @@ -8,18 +8,46 @@ "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, + "flow_title": "{mac} ( {ip} )", "step": { "user": { "data": { "host": "Nome do host", - "port": "Porta" - } + "port": "Porta", + "system_id": "ID do sistema" + }, + "description": "A ID do sistema pode ser obtida no aplicativo MyLink em Integra\u00e7\u00e3o selecionando qualquer servi\u00e7o n\u00e3o Cloud." } } }, "options": { "abort": { "cannot_connect": "Falha ao conectar" + }, + "step": { + "entity_config": { + "data": { + "reverse": "A cobertura est\u00e1 invertida" + }, + "description": "Configurar op\u00e7\u00f5es para ` {entity_id} `", + "title": "Configurar entidade" + }, + "init": { + "data": { + "default_reverse": "Status de revers\u00e3o padr\u00e3o para coberturas n\u00e3o configuradas", + "entity_id": "Configure uma entidade espec\u00edfica.", + "target_id": "Configure as op\u00e7\u00f5es para uma cobertura." + }, + "title": "Configurar op\u00e7\u00f5es do MyLink" + }, + "target_config": { + "data": { + "reverse": "A cobertura est\u00e1 invertida" + }, + "description": "Configurar op\u00e7\u00f5es para ` {target_name} `", + "title": "Configurar a MyLink Cover" + } } - } + }, + "title": "Somfy MyLink" } \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/pt-BR.json b/homeassistant/components/sonarr/translations/pt-BR.json index db1bf2075f9bce..f6b8b63781a3d0 100644 --- a/homeassistant/components/sonarr/translations/pt-BR.json +++ b/homeassistant/components/sonarr/translations/pt-BR.json @@ -9,13 +9,16 @@ "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, + "flow_title": "{name}", "step": { "reauth_confirm": { + "description": "A integra\u00e7\u00e3o do Sonarr precisa ser autenticada manualmente novamente com a API do Sonarr hospedada em: {host}", "title": "Reautenticar Integra\u00e7\u00e3o" }, "user": { "data": { "api_key": "Chave da API", + "base_path": "Caminho para a API", "host": "Nome do host", "port": "Porta", "ssl": "Usar um certificado SSL", @@ -23,5 +26,15 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "upcoming_days": "N\u00famero de pr\u00f3ximos dias a serem exibidos", + "wanted_max_items": "N\u00famero m\u00e1ximo de itens desejados para exibir" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/songpal/translations/pt-BR.json b/homeassistant/components/songpal/translations/pt-BR.json index 5b5afff290aa36..0a6fcbb52c2df2 100644 --- a/homeassistant/components/songpal/translations/pt-BR.json +++ b/homeassistant/components/songpal/translations/pt-BR.json @@ -1,10 +1,22 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "not_songpal_device": "N\u00e3o \u00e9 um dispositivo Songpal" }, "error": { "cannot_connect": "Falha ao conectar" + }, + "flow_title": "{name} ({host})", + "step": { + "init": { + "description": "Deseja configurar {name} ( {host} )?" + }, + "user": { + "data": { + "endpoint": "Ponto final" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/sonos/translations/pt-BR.json b/homeassistant/components/sonos/translations/pt-BR.json index 78407c85e22c6e..527d0046b7c073 100644 --- a/homeassistant/components/sonos/translations/pt-BR.json +++ b/homeassistant/components/sonos/translations/pt-BR.json @@ -1,12 +1,13 @@ { "config": { "abort": { - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "no_devices_found": "Nenhum dispositivo encontrado na rede", + "not_sonos_device": "O dispositivo descoberto n\u00e3o \u00e9 um dispositivo Sonos", "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "step": { "confirm": { - "description": "Voc\u00ea quer configurar o Sonos?" + "description": "Deseja configurar o Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/uk.json b/homeassistant/components/sonos/translations/uk.json index aff6c9f59b1799..64d4af145c408d 100644 --- a/homeassistant/components/sonos/translations/uk.json +++ b/homeassistant/components/sonos/translations/uk.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\u041f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456 \u0432 \u043c\u0435\u0440\u0435\u0436\u0456.", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "step": { "confirm": { diff --git a/homeassistant/components/speedtestdotnet/translations/pt-BR.json b/homeassistant/components/speedtestdotnet/translations/pt-BR.json index 7caf7983714456..0498a2fad6de14 100644 --- a/homeassistant/components/speedtestdotnet/translations/pt-BR.json +++ b/homeassistant/components/speedtestdotnet/translations/pt-BR.json @@ -1,12 +1,24 @@ { "config": { "abort": { - "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", + "wrong_server_id": "O ID do servidor n\u00e3o \u00e9 v\u00e1lido" }, "step": { "user": { "description": "Deseja iniciar a configura\u00e7\u00e3o?" } } + }, + "options": { + "step": { + "init": { + "data": { + "manual": "Desativar atualiza\u00e7\u00e3o autom\u00e1tica", + "scan_interval": "Frequ\u00eancia de atualiza\u00e7\u00e3o (minutos)", + "server_name": "Selecione o servidor de teste" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/speedtestdotnet/translations/uk.json b/homeassistant/components/speedtestdotnet/translations/uk.json index 89ef24440d13e4..e54ed1da92ee27 100644 --- a/homeassistant/components/speedtestdotnet/translations/uk.json +++ b/homeassistant/components/speedtestdotnet/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e.", + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f.", "wrong_server_id": "\u041d\u0435\u043f\u0440\u0438\u043f\u0443\u0441\u0442\u0438\u043c\u0438\u0439 \u0456\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u0441\u0435\u0440\u0432\u0435\u0440\u0430." }, "step": { diff --git a/homeassistant/components/spider/translations/pt-BR.json b/homeassistant/components/spider/translations/pt-BR.json index 70df08020b6b06..3c281b4754cc5d 100644 --- a/homeassistant/components/spider/translations/pt-BR.json +++ b/homeassistant/components/spider/translations/pt-BR.json @@ -12,7 +12,8 @@ "data": { "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "title": "Entrar com a conta mijn.ithodaalderop.nl" } } } diff --git a/homeassistant/components/spider/translations/uk.json b/homeassistant/components/spider/translations/uk.json index b8be2a1488791e..52e7158c40ac32 100644 --- a/homeassistant/components/spider/translations/uk.json +++ b/homeassistant/components/spider/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "error": { "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", diff --git a/homeassistant/components/spotify/translations/pt-BR.json b/homeassistant/components/spotify/translations/pt-BR.json index 6858c6371c0768..c49fbfabfa05d7 100644 --- a/homeassistant/components/spotify/translations/pt-BR.json +++ b/homeassistant/components/spotify/translations/pt-BR.json @@ -1,12 +1,27 @@ { "config": { "abort": { - "no_url_available": "N\u00e3o h\u00e1 URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a se\u00e7\u00e3o de ajuda]({docs_url})" + "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", + "missing_configuration": "A integra\u00e7\u00e3o do Spotify n\u00e3o est\u00e1 configurada. Por favor, siga a documenta\u00e7\u00e3o.", + "no_url_available": "N\u00e3o h\u00e1 URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a se\u00e7\u00e3o de ajuda]({docs_url})", + "reauth_account_mismatch": "A conta Spotify autenticada com, n\u00e3o corresponde \u00e0 conta necess\u00e1ria para reautentica\u00e7\u00e3o." + }, + "create_entry": { + "default": "Autenticado com sucesso no Spotify." }, "step": { + "pick_implementation": { + "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" + }, "reauth_confirm": { + "description": "A integra\u00e7\u00e3o do Spotify precisa ser autenticada novamente com o Spotify para a conta: {account}", "title": "Reautenticar Integra\u00e7\u00e3o" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "Endpoint da API do Spotify acess\u00edvel" + } } } \ No newline at end of file diff --git a/homeassistant/components/squeezebox/translations/pt-BR.json b/homeassistant/components/squeezebox/translations/pt-BR.json index 5e94ace2864042..28ca34f0818630 100644 --- a/homeassistant/components/squeezebox/translations/pt-BR.json +++ b/homeassistant/components/squeezebox/translations/pt-BR.json @@ -1,13 +1,16 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "no_server_found": "Nenhum servidor LMS encontrado." }, "error": { "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "no_server_found": "N\u00e3o foi poss\u00edvel descobrir o servidor automaticamente.", "unknown": "Erro inesperado" }, + "flow_title": "{host}", "step": { "edit": { "data": { @@ -15,7 +18,8 @@ "password": "Senha", "port": "Porta", "username": "Usu\u00e1rio" - } + }, + "title": "Editar informa\u00e7\u00f5es de conex\u00e3o" }, "user": { "data": { diff --git a/homeassistant/components/srp_energy/translations/pt-BR.json b/homeassistant/components/srp_energy/translations/pt-BR.json index 790e3e661a3492..b0dbfe9c6d04d7 100644 --- a/homeassistant/components/srp_energy/translations/pt-BR.json +++ b/homeassistant/components/srp_energy/translations/pt-BR.json @@ -5,16 +5,20 @@ }, "error": { "cannot_connect": "Falha ao conectar", + "invalid_account": "O ID da conta deve ser um n\u00famero de 9 d\u00edgitos", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, "step": { "user": { "data": { + "id": "ID da conta", + "is_tou": "Plano de tempo de uso", "password": "Senha", "username": "Usu\u00e1rio" } } } - } + }, + "title": "SRP Energy" } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/uk.json b/homeassistant/components/srp_energy/translations/uk.json index 5267aa2a5757f1..144e40f15bb545 100644 --- a/homeassistant/components/srp_energy/translations/uk.json +++ b/homeassistant/components/srp_energy/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "error": { "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", diff --git a/homeassistant/components/starline/translations/pt-BR.json b/homeassistant/components/starline/translations/pt-BR.json index 3b73793804a309..d9141c8b5cb198 100644 --- a/homeassistant/components/starline/translations/pt-BR.json +++ b/homeassistant/components/starline/translations/pt-BR.json @@ -1,17 +1,24 @@ { "config": { "error": { + "error_auth_app": "C\u00f3digo ou segredo do aplicativo incorreto", "error_auth_mfa": "C\u00f3digo incorreto", "error_auth_user": "Usu\u00e1rio ou senha incorretos" }, "step": { "auth_app": { + "data": { + "app_id": "ID do aplicativo", + "app_secret": "Segredo" + }, + "description": "ID do aplicativo e c\u00f3digo secreto de [conta de desenvolvedor StarLine](https://my.starline.ru/developer)", "title": "Credenciais do aplicativo" }, "auth_captcha": { "data": { "captcha_code": "C\u00f3digo da imagem" }, + "description": "{captcha_img}", "title": "Captcha" }, "auth_mfa": { @@ -25,7 +32,9 @@ "data": { "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "description": "Email e senha da conta StarLine", + "title": "Credenciais do usu\u00e1rio" } } } diff --git a/homeassistant/components/steamist/translations/nl.json b/homeassistant/components/steamist/translations/nl.json index ccd7379c346d8a..926f3cc4b9d09e 100644 --- a/homeassistant/components/steamist/translations/nl.json +++ b/homeassistant/components/steamist/translations/nl.json @@ -19,7 +19,8 @@ "user": { "data": { "host": "Host" - } + }, + "description": "Als u de host leeg laat, zal discovery worden gebruikt om apparaten te vinden." } } } diff --git a/homeassistant/components/steamist/translations/pt-BR.json b/homeassistant/components/steamist/translations/pt-BR.json index 685c6dc5319c47..b099c4b36f7547 100644 --- a/homeassistant/components/steamist/translations/pt-BR.json +++ b/homeassistant/components/steamist/translations/pt-BR.json @@ -4,7 +4,7 @@ "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", "cannot_connect": "Falha ao conectar", - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "no_devices_found": "Nenhum dispositivo encontrado na rede", "not_steamist_device": "N\u00e3o \u00e9 um dispositivo de vaporizador" }, "error": { diff --git a/homeassistant/components/stookalert/translations/pt-BR.json b/homeassistant/components/stookalert/translations/pt-BR.json index d252c078a2c31a..5ad1d9f9a81fb3 100644 --- a/homeassistant/components/stookalert/translations/pt-BR.json +++ b/homeassistant/components/stookalert/translations/pt-BR.json @@ -2,6 +2,13 @@ "config": { "abort": { "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" + }, + "step": { + "user": { + "data": { + "province": "Prov\u00edncia" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/pt-BR.json b/homeassistant/components/subaru/translations/pt-BR.json index 3ccfca2f59f961..e88a9e883c0484 100644 --- a/homeassistant/components/subaru/translations/pt-BR.json +++ b/homeassistant/components/subaru/translations/pt-BR.json @@ -5,15 +5,38 @@ "cannot_connect": "Falha ao conectar" }, "error": { + "bad_pin_format": "O PIN deve ter 4 d\u00edgitos", "cannot_connect": "Falha ao conectar", + "incorrect_pin": "PIN incorreto", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "description": "Por favor, digite seu PIN MySubaru\n NOTA: Todos os ve\u00edculos em conta devem ter o mesmo PIN", + "title": "Configura\u00e7\u00e3o do Subaru Starlink" + }, "user": { "data": { + "country": "Selecione o pa\u00eds", "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "description": "Por favor, insira suas credenciais MySubaru\n NOTA: A configura\u00e7\u00e3o inicial pode demorar at\u00e9 30 segundos", + "title": "Configura\u00e7\u00e3o do Subaru Starlink" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "Habilitar as pesquisas de ve\u00edculos" + }, + "description": "Quando ativado, a pesquisa de ve\u00edculos enviar\u00e1 um comando remoto para seu ve\u00edculo a cada 2 horas para obter novos dados do sensor. Sem a pesquisa de ve\u00edculos, os novos dados do sensor s\u00f3 s\u00e3o recebidos quando o ve\u00edculo enviar\u00e1 automaticamente os dados (normalmente ap\u00f3s o desligamento do motor).", + "title": "Op\u00e7\u00f5es do Subaru Starlink" } } } diff --git a/homeassistant/components/switch/translations/nl.json b/homeassistant/components/switch/translations/nl.json index dbc4dc19f37528..71d3b0f6b8ef87 100644 --- a/homeassistant/components/switch/translations/nl.json +++ b/homeassistant/components/switch/translations/nl.json @@ -10,6 +10,7 @@ "is_on": "{entity_name} is ingeschakeld" }, "trigger_type": { + "changed_states": "{entity_name} in- of uitgeschakeld", "toggled": "{entity_name} in-of uitgeschakeld", "turned_off": "{entity_name} uitgeschakeld", "turned_on": "{entity_name} ingeschakeld" diff --git a/homeassistant/components/switch/translations/pt-BR.json b/homeassistant/components/switch/translations/pt-BR.json index d00cce3da9f341..cb73ce3c5cf683 100644 --- a/homeassistant/components/switch/translations/pt-BR.json +++ b/homeassistant/components/switch/translations/pt-BR.json @@ -1,9 +1,9 @@ { "device_automation": { "action_type": { - "toggle": "Alternar {nome_da_entidade}", - "turn_off": "Desligar {nome_da_entidade}", - "turn_on": "Ligar {nome_da_entidade}" + "toggle": "Alternar {entity_name}", + "turn_off": "Desligar {entity_name}", + "turn_on": "Ligar {entity_name}" }, "condition_type": { "is_off": "{entity_name} est\u00e1 desligado", diff --git a/homeassistant/components/switchbot/translations/pt-BR.json b/homeassistant/components/switchbot/translations/pt-BR.json index c63ab2a1f27e9f..bf9cb746dcbd38 100644 --- a/homeassistant/components/switchbot/translations/pt-BR.json +++ b/homeassistant/components/switchbot/translations/pt-BR.json @@ -3,16 +3,33 @@ "abort": { "already_configured_device": "Dispositivo j\u00e1 est\u00e1 configurado", "cannot_connect": "Falha ao conectar", + "no_unconfigured_devices": "Nenhum dispositivo n\u00e3o configurado foi encontrado.", + "switchbot_unsupported_type": "Tipo de Switchbot sem suporte.", "unknown": "Erro inesperado" }, "error": { "cannot_connect": "Falha ao conectar" }, + "flow_title": "{name}", "step": { "user": { "data": { + "mac": "Endere\u00e7o MAC do dispositivo", "name": "Nome", "password": "Senha" + }, + "title": "Configurar dispositivo Switchbot" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "retry_count": "Contagem de tentativas", + "retry_timeout": "Intervalo entre tentativas", + "scan_timeout": "Quanto tempo para verificar os dados do an\u00fancio", + "update_time": "Tempo entre atualiza\u00e7\u00f5es (segundos)" } } } diff --git a/homeassistant/components/switcher_kis/translations/pt-BR.json b/homeassistant/components/switcher_kis/translations/pt-BR.json index d5efbb90261324..1778d39a7d0829 100644 --- a/homeassistant/components/switcher_kis/translations/pt-BR.json +++ b/homeassistant/components/switcher_kis/translations/pt-BR.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "no_devices_found": "Nenhum dispositivo encontrado na rede", "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "step": { diff --git a/homeassistant/components/syncthing/translations/pt-BR.json b/homeassistant/components/syncthing/translations/pt-BR.json index 451ec4459ebe00..08f66569d933df 100644 --- a/homeassistant/components/syncthing/translations/pt-BR.json +++ b/homeassistant/components/syncthing/translations/pt-BR.json @@ -10,10 +10,13 @@ "step": { "user": { "data": { + "title": "Configurar integra\u00e7\u00e3o do Syncthing", + "token": "Token", "url": "URL", "verify_ssl": "Verifique o certificado SSL" } } } - } + }, + "title": "Sincroniza\u00e7\u00e3o" } \ No newline at end of file diff --git a/homeassistant/components/syncthru/translations/pt-BR.json b/homeassistant/components/syncthru/translations/pt-BR.json index 7164e5bb0831c3..0872de05be9a0c 100644 --- a/homeassistant/components/syncthru/translations/pt-BR.json +++ b/homeassistant/components/syncthru/translations/pt-BR.json @@ -3,15 +3,23 @@ "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, + "error": { + "invalid_url": "URL inv\u00e1lida", + "syncthru_not_supported": "O dispositivo n\u00e3o suporta SyncThru", + "unknown_state": "Estado da impressora desconhecido, verifique o URL e a conectividade de rede" + }, + "flow_title": "{name}", "step": { "confirm": { "data": { - "name": "Nome" + "name": "Nome", + "url": "URL da interface da Web" } }, "user": { "data": { - "name": "Nome" + "name": "Nome", + "url": "URL da interface da Web" } } } diff --git a/homeassistant/components/synology_dsm/translations/nl.json b/homeassistant/components/synology_dsm/translations/nl.json index 8740308faf09e1..801c1d7fe82866 100644 --- a/homeassistant/components/synology_dsm/translations/nl.json +++ b/homeassistant/components/synology_dsm/translations/nl.json @@ -64,6 +64,7 @@ "init": { "data": { "scan_interval": "Minuten tussen scans", + "snap_profile_type": "Kwaliteitsniveau van camera-snapshots (0:hoog 1:gemiddeld 2:laag)", "timeout": "Time-out (seconden)" } } diff --git a/homeassistant/components/synology_dsm/translations/pt-BR.json b/homeassistant/components/synology_dsm/translations/pt-BR.json index f139d3fa5f5ea5..ddde3e1e29db82 100644 --- a/homeassistant/components/synology_dsm/translations/pt-BR.json +++ b/homeassistant/components/synology_dsm/translations/pt-BR.json @@ -2,20 +2,23 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", - "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", + "reconfigure_successful": "A reconfigura\u00e7\u00e3o foi bem-sucedida" }, "error": { "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "missing_data": "Dados ausentes: tente novamente mais tarde ou outra configura\u00e7\u00e3o", "otp_failed": "Falha na autentica\u00e7\u00e3o em duas etapas, tente novamente com um novo c\u00f3digo", "unknown": "Erro inesperado" }, - "flow_title": "Synology DSM {name} ({host})", + "flow_title": "{name} ({host})", "step": { "2sa": { "data": { "otp_code": "C\u00f3digo" - } + }, + "title": "Synology DSM: autentica\u00e7\u00e3o em duas etapas" }, "link": { "data": { @@ -33,6 +36,7 @@ "password": "Senha", "username": "Usu\u00e1rio" }, + "description": "Motivo: {details}", "title": "Synology DSM Reautenticar Integra\u00e7\u00e3o" }, "reauth_confirm": { @@ -50,7 +54,8 @@ "ssl": "Usar um certificado SSL", "username": "Usu\u00e1rio", "verify_ssl": "Verifique o certificado SSL" - } + }, + "title": "Synology DSM" } } }, @@ -59,7 +64,8 @@ "init": { "data": { "scan_interval": "Minutos entre os escaneamentos", - "snap_profile_type": "N\u00edvel de qualidade dos instant\u00e2neos da c\u00e2mera (0: alto 1: m\u00e9dio 2: baixo)" + "snap_profile_type": "N\u00edvel de qualidade dos instant\u00e2neos da c\u00e2mera (0: alto 1: m\u00e9dio 2: baixo)", + "timeout": "Tempo limite (segundos)" } } } diff --git a/homeassistant/components/system_bridge/translations/pt-BR.json b/homeassistant/components/system_bridge/translations/pt-BR.json index b928fae8a32891..ed1bcd01ded0c1 100644 --- a/homeassistant/components/system_bridge/translations/pt-BR.json +++ b/homeassistant/components/system_bridge/translations/pt-BR.json @@ -10,19 +10,23 @@ "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, + "flow_title": "{name}", "step": { "authenticate": { "data": { "api_key": "Chave da API" - } + }, + "description": "Insira a chave de API que voc\u00ea definiu em sua configura\u00e7\u00e3o para {name} ." }, "user": { "data": { "api_key": "Chave da API", "host": "Nome do host", "port": "Porta" - } + }, + "description": "Por favor, insira os detalhes da sua conex\u00e3o." } } - } + }, + "title": "Ponte do sistema" } \ No newline at end of file diff --git a/homeassistant/components/tado/translations/pt-BR.json b/homeassistant/components/tado/translations/pt-BR.json index 9038912d008bcc..68cf24fe5dfefc 100644 --- a/homeassistant/components/tado/translations/pt-BR.json +++ b/homeassistant/components/tado/translations/pt-BR.json @@ -25,6 +25,7 @@ "data": { "fallback": "Ative o modo de fallback." }, + "description": "O modo Fallback mudar\u00e1 para Smart Schedule na pr\u00f3xima troca de agendamento ap\u00f3s ajustar manualmente uma zona.", "title": "Ajuste as op\u00e7\u00f5es do Tado." } } diff --git a/homeassistant/components/tasmota/translations/pt-BR.json b/homeassistant/components/tasmota/translations/pt-BR.json index 9ab59f40649f29..6fa1f064e88999 100644 --- a/homeassistant/components/tasmota/translations/pt-BR.json +++ b/homeassistant/components/tasmota/translations/pt-BR.json @@ -2,6 +2,21 @@ "config": { "abort": { "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "error": { + "invalid_discovery_topic": "Prefixo do t\u00f3pico de descoberta inv\u00e1lido." + }, + "step": { + "config": { + "data": { + "discovery_prefix": "Prefixo do t\u00f3pico de descoberta" + }, + "description": "Por favor, insira a configura\u00e7\u00e3o do Tasmota.", + "title": "Tasmota" + }, + "confirm": { + "description": "Deseja configurar o Tasmota?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/tasmota/translations/uk.json b/homeassistant/components/tasmota/translations/uk.json index 6639a9c9626eb8..5b57f950866b64 100644 --- a/homeassistant/components/tasmota/translations/uk.json +++ b/homeassistant/components/tasmota/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "error": { "invalid_discovery_topic": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043f\u0440\u0435\u0444\u0456\u043a\u0441 \u0442\u0435\u043c\u0438 \u0430\u0432\u0442\u043e\u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f." diff --git a/homeassistant/components/tellduslive/translations/pt-BR.json b/homeassistant/components/tellduslive/translations/pt-BR.json index 7965c3083d28ea..1c94b12cc59798 100644 --- a/homeassistant/components/tellduslive/translations/pt-BR.json +++ b/homeassistant/components/tellduslive/translations/pt-BR.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado", "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", - "unknown": "Erro inesperado" + "unknown": "Erro inesperado", + "unknown_authorize_url_generation": "Erro desconhecido ao gerar um URL de autoriza\u00e7\u00e3o." }, "error": { "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" diff --git a/homeassistant/components/tesla_wall_connector/translations/pt-BR.json b/homeassistant/components/tesla_wall_connector/translations/pt-BR.json index 4f05163182cd52..da2c9d50f34f81 100644 --- a/homeassistant/components/tesla_wall_connector/translations/pt-BR.json +++ b/homeassistant/components/tesla_wall_connector/translations/pt-BR.json @@ -7,17 +7,22 @@ "cannot_connect": "Falha ao conectar", "unknown": "Erro inesperado" }, + "flow_title": "{serial_number} ( {host} )", "step": { "user": { "data": { "host": "Nome do host" - } + }, + "title": "Configurar o conector de parede Tesla" } } }, "options": { "step": { "init": { + "data": { + "scan_interval": "Frequ\u00eancia de atualiza\u00e7\u00e3o" + }, "title": "Configurar op\u00e7\u00f5es para o Tesla Wall Connector" } } diff --git a/homeassistant/components/tibber/translations/el.json b/homeassistant/components/tibber/translations/el.json new file mode 100644 index 00000000000000..bf736e096640fe --- /dev/null +++ b/homeassistant/components/tibber/translations/el.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03c4\u03bf Tibber" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tibber/translations/pt-BR.json b/homeassistant/components/tibber/translations/pt-BR.json index 2e1206f48075a8..7ff663470094c5 100644 --- a/homeassistant/components/tibber/translations/pt-BR.json +++ b/homeassistant/components/tibber/translations/pt-BR.json @@ -5,13 +5,16 @@ }, "error": { "cannot_connect": "Falha ao conectar", - "invalid_access_token": "Token de acesso inv\u00e1lido" + "invalid_access_token": "Token de acesso inv\u00e1lido", + "timeout": "Tempo limite de conex\u00e3o com o Tibber" }, "step": { "user": { "data": { "access_token": "Token de acesso" - } + }, + "description": "Insira seu token de acesso em https://developer.tibber.com/settings/accesstoken", + "title": "Tibber" } } } diff --git a/homeassistant/components/tile/translations/pt-BR.json b/homeassistant/components/tile/translations/pt-BR.json index 33b2e29d518205..e3a4aa67c026f3 100644 --- a/homeassistant/components/tile/translations/pt-BR.json +++ b/homeassistant/components/tile/translations/pt-BR.json @@ -16,8 +16,20 @@ }, "user": { "data": { - "password": "Senha" - } + "password": "Senha", + "username": "Email" + }, + "title": "Configurar bloco" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_inactive": "Mostrar blocos inativos" + }, + "title": "Configurar bloco" } } } diff --git a/homeassistant/components/tolo/translations/pt-BR.json b/homeassistant/components/tolo/translations/pt-BR.json index 4c7ead4c7d6966..c86e10b4e8e943 100644 --- a/homeassistant/components/tolo/translations/pt-BR.json +++ b/homeassistant/components/tolo/translations/pt-BR.json @@ -2,11 +2,12 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" + "no_devices_found": "Nenhum dispositivo encontrado na rede" }, "error": { "cannot_connect": "Falha ao conectar" }, + "flow_title": "{name}", "step": { "confirm": { "description": "Deseja iniciar a configura\u00e7\u00e3o?" @@ -14,7 +15,8 @@ "user": { "data": { "host": "Nome do host" - } + }, + "description": "Digite o nome do host ou o endere\u00e7o IP do seu dispositivo TOLO Sauna." } } } diff --git a/homeassistant/components/tolo/translations/select.pt-BR.json b/homeassistant/components/tolo/translations/select.pt-BR.json new file mode 100644 index 00000000000000..61ee349aa2fada --- /dev/null +++ b/homeassistant/components/tolo/translations/select.pt-BR.json @@ -0,0 +1,8 @@ +{ + "state": { + "tolo__lamp_mode": { + "automatic": "autom\u00e1tico", + "manual": "manual" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/toon/translations/pt-BR.json b/homeassistant/components/toon/translations/pt-BR.json index 347c3d8e88ce7d..982203402059be 100644 --- a/homeassistant/components/toon/translations/pt-BR.json +++ b/homeassistant/components/toon/translations/pt-BR.json @@ -1,10 +1,24 @@ { "config": { "abort": { + "already_configured": "O contrato selecionado j\u00e1 est\u00e1 configurado.", "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", "no_agreements": "Esta conta n\u00e3o possui exibi\u00e7\u00f5es Toon.", - "no_url_available": "N\u00e3o h\u00e1 URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a se\u00e7\u00e3o de ajuda]({docs_url})" + "no_url_available": "N\u00e3o h\u00e1 URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a se\u00e7\u00e3o de ajuda]({docs_url})", + "unknown_authorize_url_generation": "Erro desconhecido ao gerar um URL de autoriza\u00e7\u00e3o." + }, + "step": { + "agreement": { + "data": { + "agreement": "Acordo" + }, + "description": "Selecione o endere\u00e7o do contrato que voc\u00ea deseja adicionar.", + "title": "Selecione seu contrato" + }, + "pick_implementation": { + "title": "Escolha seu locat\u00e1rio para autenticar" + } } } } \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/pt-BR.json b/homeassistant/components/totalconnect/translations/pt-BR.json index 7a58875b9f2345..47dfe5437e0d82 100644 --- a/homeassistant/components/totalconnect/translations/pt-BR.json +++ b/homeassistant/components/totalconnect/translations/pt-BR.json @@ -6,10 +6,20 @@ "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { - "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "usercode": "C\u00f3digo de usu\u00e1rio n\u00e3o \u00e9 v\u00e1lido para este usu\u00e1rio neste local" }, "step": { + "locations": { + "data": { + "location": "Localiza\u00e7\u00e3o", + "usercode": "C\u00f3digo de usu\u00e1rio" + }, + "description": "Insira o c\u00f3digo de usu\u00e1rio para este usu\u00e1rio no local {location_id}", + "title": "C\u00f3digos de usu\u00e1rio de localiza\u00e7\u00e3o" + }, "reauth_confirm": { + "description": "Total Connect precisa re-autenticar sua conta", "title": "Reautenticar Integra\u00e7\u00e3o" }, "user": { diff --git a/homeassistant/components/tplink/translations/pt-BR.json b/homeassistant/components/tplink/translations/pt-BR.json index 1977a43636518f..a034b44b761d6e 100644 --- a/homeassistant/components/tplink/translations/pt-BR.json +++ b/homeassistant/components/tplink/translations/pt-BR.json @@ -2,13 +2,13 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "no_devices_found": "Nenhum dispositivo encontrado na rede", "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "error": { "cannot_connect": "Falha ao conectar" }, - "flow_title": "{nome} {modelo} ({host})", + "flow_title": "{name} {model} ({host})", "step": { "confirm": { "description": "Deseja configurar dispositivos inteligentes TP-Link?" @@ -16,10 +16,16 @@ "discovery_confirm": { "description": "Deseja configurar {name} {model} ({host})?" }, + "pick_device": { + "data": { + "device": "Dispositivo" + } + }, "user": { "data": { "host": "Nome do host" - } + }, + "description": "Se voc\u00ea deixar o host vazio, a descoberta ser\u00e1 usada para encontrar dispositivos." } } } diff --git a/homeassistant/components/tplink/translations/uk.json b/homeassistant/components/tplink/translations/uk.json index cfeaf049675f2a..abbfc076f7e1cf 100644 --- a/homeassistant/components/tplink/translations/uk.json +++ b/homeassistant/components/tplink/translations/uk.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\u041f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456 \u0432 \u043c\u0435\u0440\u0435\u0436\u0456.", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "step": { "confirm": { diff --git a/homeassistant/components/traccar/translations/ja.json b/homeassistant/components/traccar/translations/ja.json index 4338175fda65c9..c635e23fbe0dec 100644 --- a/homeassistant/components/traccar/translations/ja.json +++ b/homeassistant/components/traccar/translations/ja.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Home Assistant Cloud\u306b\u63a5\u7d9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002", "single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002", "webhook_not_internet_accessible": "Webhook\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u53d7\u4fe1\u3059\u308b\u306b\u306f\u3001Home Assistant\u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u306b\u3001\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u304b\u3089\u30a2\u30af\u30bb\u30b9\u3067\u304d\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002" }, diff --git a/homeassistant/components/traccar/translations/nl.json b/homeassistant/components/traccar/translations/nl.json index 0b4563d69fcfa4..45a70a42b435eb 100644 --- a/homeassistant/components/traccar/translations/nl.json +++ b/homeassistant/components/traccar/translations/nl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Niet verbonden met Home Assistant Cloud.", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." }, diff --git a/homeassistant/components/traccar/translations/pt-BR.json b/homeassistant/components/traccar/translations/pt-BR.json index 827c8e190661a9..1fc86514bd1f04 100644 --- a/homeassistant/components/traccar/translations/pt-BR.json +++ b/homeassistant/components/traccar/translations/pt-BR.json @@ -2,10 +2,11 @@ "config": { "abort": { "cloud_not_connected": "N\u00e3o conectado ao Home Assistant Cloud.", - "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", + "webhook_not_internet_accessible": "Sua inst\u00e2ncia do Home Assistant precisa estar acess\u00edvel pela Internet para receber mensagens de webhook." }, "create_entry": { - "default": "Para enviar eventos ao Home Assistant, voc\u00ea precisar\u00e1 configurar o recurso de webhook no Traccar. \n\n Use o seguinte URL: ` {webhook_url} ` \n\n Veja [a documenta\u00e7\u00e3o] ({docs_url}) para mais detalhes." + "default": "Para enviar eventos ao Home Assistant, voc\u00ea precisar\u00e1 configurar o recurso de webhook no Traccar. \n\n Use o seguinte URL: ` {webhook_url} ` \n\n Veja [a documenta\u00e7\u00e3o]({docs_url}) para mais detalhes." }, "step": { "user": { diff --git a/homeassistant/components/traccar/translations/uk.json b/homeassistant/components/traccar/translations/uk.json index 5bfb1714a79256..4d400941016a94 100644 --- a/homeassistant/components/traccar/translations/uk.json +++ b/homeassistant/components/traccar/translations/uk.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e.", + "cloud_not_connected": "\u041d\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0434\u043e Home Assistant Cloud.", + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f.", "webhook_not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u043f\u043e\u0432\u0438\u043d\u0435\u043d \u0431\u0443\u0442\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0439 \u0437 \u0406\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443 \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f Webhook-\u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u044c." }, "create_entry": { diff --git a/homeassistant/components/tractive/translations/pt-BR.json b/homeassistant/components/tractive/translations/pt-BR.json index e9a14f4323825c..d33bdf4f9aa1c6 100644 --- a/homeassistant/components/tractive/translations/pt-BR.json +++ b/homeassistant/components/tractive/translations/pt-BR.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "reauth_failed_existing": "N\u00e3o foi poss\u00edvel atualizar a entrada de configura\u00e7\u00e3o. Remova a integra\u00e7\u00e3o e configure-a novamente.", "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { @@ -11,6 +12,7 @@ "step": { "user": { "data": { + "email": "Email", "password": "Senha" } } diff --git a/homeassistant/components/tradfri/translations/pt-BR.json b/homeassistant/components/tradfri/translations/pt-BR.json index 702aa6e33deeab..5cf3afc282d3b8 100644 --- a/homeassistant/components/tradfri/translations/pt-BR.json +++ b/homeassistant/components/tradfri/translations/pt-BR.json @@ -5,6 +5,7 @@ "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento" }, "error": { + "cannot_authenticate": "N\u00e3o \u00e9 poss\u00edvel autenticar, o Gateway est\u00e1 emparelhado com outro servidor como, por exemplo, Homekit?", "cannot_connect": "Falha ao conectar", "invalid_key": "Falha ao registrar-se com a chave fornecida. Se isso continuar acontecendo, tente reiniciar o gateway.", "timeout": "Excedido tempo limite para validar c\u00f3digo" diff --git a/homeassistant/components/transmission/translations/pt-BR.json b/homeassistant/components/transmission/translations/pt-BR.json index e9edf1d94e25b2..3353884ef33383 100644 --- a/homeassistant/components/transmission/translations/pt-BR.json +++ b/homeassistant/components/transmission/translations/pt-BR.json @@ -25,6 +25,8 @@ "step": { "init": { "data": { + "limit": "Limite", + "order": "Pedido", "scan_interval": "Frequ\u00eancia de atualiza\u00e7\u00e3o" }, "title": "Configurar op\u00e7\u00f5es para Transmission" diff --git a/homeassistant/components/tuya/translations/pt-BR.json b/homeassistant/components/tuya/translations/pt-BR.json index bffe92b154fdf5..242d1e9ee0896f 100644 --- a/homeassistant/components/tuya/translations/pt-BR.json +++ b/homeassistant/components/tuya/translations/pt-BR.json @@ -13,27 +13,29 @@ "step": { "login": { "data": { - "access_id": "Access ID", - "access_secret": "Access Secret", - "country_code": "C\u00f3digo do pa\u00eds", - "endpoint": "Zona de disponibilidade", - "password": "Senha", - "tuya_app_type": "Aplicativo m\u00f3vel", - "username": "Conta" + "access_id": "Tuya IoT Access ID", + "access_secret": "Tuya IoT Access Secret", + "country_code": "Pa\u00eds", + "endpoint": "Regi\u00e3o", + "password": "Senha do Aplicativo", + "tuya_app_type": "O aplicativo onde sua conta \u00e9 registrada", + "username": "Usu\u00e1rio do Aplicativo" }, - "description": "Insira sua credencial Tuya", - "title": "Tuya" + "description": "Digite sua credencial Tuya", + "title": "Integra\u00e7\u00e3o Tuya" }, "user": { "data": { + "access_id": "Tuya IoT Access ID", + "access_secret": "Tuya IoT Access Secret", "country_code": "Pa\u00eds", - "password": "Senha", + "password": "Senha do Aplicativo", "platform": "O aplicativo onde sua conta \u00e9 registrada", "region": "Regi\u00e3o", - "tuya_project_type": "Tipo de projeto de nuvem Tuya", - "username": "Nome de usu\u00e1rio" + "tuya_project_type": "Tipo de projeto de Tuya Cloud", + "username": "Usu\u00e1rio do Aplicativo" }, - "description": "Digite sua credencial Tuya.", + "description": "Digite sua credencial Tuya", "title": "Integra\u00e7\u00e3o Tuya" } } @@ -56,8 +58,10 @@ "max_temp": "Temperatura m\u00e1xima do alvo (use min e max = 0 para padr\u00e3o)", "min_kelvin": "Temperatura m\u00ednima de cor suportada em kelvin", "min_temp": "Temperatura m\u00ednima desejada (use m\u00edn e m\u00e1x = 0 para o padr\u00e3o)", + "set_temp_divided": "Use o valor de temperatura dividido para o comando de temperatura definido", "support_color": "For\u00e7ar suporte de cores", "temp_divider": "Divisor de valores de temperatura (0 = usar padr\u00e3o)", + "temp_step_override": "Etapa de temperatura alvo", "tuya_max_coltemp": "Temperatura m\u00e1xima de cor relatada pelo dispositivo", "unit_of_measurement": "Unidade de temperatura usada pelo dispositivo" }, @@ -67,8 +71,12 @@ "init": { "data": { "discovery_interval": "Intervalo de pesquisa do dispositivo de descoberta em segundos", - "list_devices": "Selecione os dispositivos para configurar ou deixe em branco para salvar a configura\u00e7\u00e3o" - } + "list_devices": "Selecione os dispositivos para configurar ou deixe em branco para salvar a configura\u00e7\u00e3o", + "query_device": "Selecione o dispositivo que usar\u00e1 o m\u00e9todo de consulta para atualiza\u00e7\u00e3o de status mais r\u00e1pida", + "query_interval": "Intervalo de sondagem do dispositivo de consulta em segundos" + }, + "description": "N\u00e3o defina valores de intervalo de sondagens muito baixos ou as chamadas falhar\u00e3o gerando mensagem de erro no log", + "title": "Configurar op\u00e7\u00f5es do Tuya" } } } diff --git a/homeassistant/components/tuya/translations/select.it.json b/homeassistant/components/tuya/translations/select.it.json index 575a28b2dc0491..629e98f698609b 100644 --- a/homeassistant/components/tuya/translations/select.it.json +++ b/homeassistant/components/tuya/translations/select.it.json @@ -10,6 +10,14 @@ "1": "Spento", "2": "Acceso" }, + "tuya__curtain_mode": { + "morning": "Mattina", + "night": "Notte" + }, + "tuya__curtain_motor_mode": { + "back": "Indietro", + "forward": "Avanti" + }, "tuya__decibel_sensitivity": { "0": "Bassa sensibilit\u00e0", "1": "Alta sensibilit\u00e0" diff --git a/homeassistant/components/tuya/translations/select.ja.json b/homeassistant/components/tuya/translations/select.ja.json index 57d4297fd68330..89ea2c39090814 100644 --- a/homeassistant/components/tuya/translations/select.ja.json +++ b/homeassistant/components/tuya/translations/select.ja.json @@ -10,10 +10,23 @@ "1": "\u30aa\u30d5", "2": "\u30aa\u30f3" }, + "tuya__curtain_mode": { + "morning": "\u671d", + "night": "\u591c" + }, + "tuya__curtain_motor_mode": { + "back": "\u623b\u308b", + "forward": "\u9032\u3080" + }, "tuya__decibel_sensitivity": { "0": "\u4f4e\u611f\u5ea6", "1": "\u9ad8\u611f\u5ea6" }, + "tuya__fan_angle": { + "30": "30\u00b0", + "60": "60\u00b0", + "90": "90\u00b0" + }, "tuya__fingerbot_mode": { "click": "\u62bc\u3059", "switch": "\u30b9\u30a4\u30c3\u30c1" diff --git a/homeassistant/components/tuya/translations/select.nl.json b/homeassistant/components/tuya/translations/select.nl.json index 0961f6a29fd1c8..4393efb7859c6a 100644 --- a/homeassistant/components/tuya/translations/select.nl.json +++ b/homeassistant/components/tuya/translations/select.nl.json @@ -10,10 +10,22 @@ "1": "Uit", "2": "Aan" }, + "tuya__curtain_mode": { + "morning": "Ochtend", + "night": "Nacht" + }, + "tuya__curtain_motor_mode": { + "back": "Terug", + "forward": "Vooruit" + }, "tuya__decibel_sensitivity": { "0": "Lage gevoeligheid", "1": "Hoge gevoeligheid" }, + "tuya__fan_angle": { + "60": "60\u00b0", + "90": "90\u00b0" + }, "tuya__fingerbot_mode": { "click": "Duw", "switch": "Schakelaar" @@ -58,8 +70,20 @@ "small": "Klein" }, "tuya__vacuum_mode": { + "bow": "Boog", + "left_bow": "Boog links", + "left_spiral": "Spiraal links", + "part": "Deel", + "partial_bow": "Boog gedeeltelijk", + "pick_zone": "Kies zone", "point": "Punt", "pose": "Houding", + "random": "Willekeurig", + "right_bow": "Boog rechts", + "right_spiral": "Spiraal Rechts", + "single": "Enkel", + "smart": "Smart", + "spiral": "Spiraal", "wall_follow": "Volg muur", "zone": "Zone" } diff --git a/homeassistant/components/tuya/translations/select.pt-BR.json b/homeassistant/components/tuya/translations/select.pt-BR.json index 06353ca7846384..e32b8ebcd3c40f 100644 --- a/homeassistant/components/tuya/translations/select.pt-BR.json +++ b/homeassistant/components/tuya/translations/select.pt-BR.json @@ -31,6 +31,10 @@ "click": "Pulsador", "switch": "Interruptor" }, + "tuya__ipc_work_mode": { + "0": "Modo de baixo consumo", + "1": "Modo de trabalho cont\u00ednuo" + }, "tuya__led_type": { "halogen": "Halog\u00eanio", "incandescent": "Incandescente", @@ -41,6 +45,15 @@ "pos": "Indique a localiza\u00e7\u00e3o do interruptor", "relay": "Indicar o estado de ligar/desligar" }, + "tuya__motion_sensitivity": { + "0": "Sensibilidade baixa", + "1": "Sensibilidade m\u00e9dia", + "2": "Sensibilidade alta" + }, + "tuya__record_mode": { + "1": "Gravar apenas eventos", + "2": "Grava\u00e7\u00e3o cont\u00ednua" + }, "tuya__relay_status": { "last": "Lembre-se do \u00faltimo estado", "memory": "Lembre-se do \u00faltimo estado", @@ -61,22 +74,25 @@ "small": "Pequeno" }, "tuya__vacuum_mode": { - "bow": "Arco", - "chargego": "Voltar para a base", - "left_bow": "Curvar \u00e0 esquerda", - "left_spiral": "Espiral Esquerda", - "mop": "Mop", - "part": "Parte", - "partial_bow": "Curvar Parcialmente", - "pick_zone": "Escolher Zona", + "bow": "", + "chargego": "Retornar para Base", + "left_bow": "", + "left_spiral": "", + "mop": "Esfregar (Mop)", + "part": "Parcial", + "partial_bow": "", + "pick_zone": "C\u00f4modos Selecionados", "point": "Ponto", - "pose": "Pose", + "pose": "Ponto Definido", "random": "Aleat\u00f3rio", - "right_bow": "Curvar \u00e0 direita", - "right_spiral": "Espiral direita", - "single": "\u00danico", - "standby": "Aguarde", - "zone": "\u00c1rea" + "right_bow": "", + "right_spiral": "", + "single": "Simples", + "smart": "Autom\u00e1tica", + "spiral": "Espiral", + "standby": "Em Espera", + "wall_follow": "Limpeza de Cantos", + "zone": "\u00c1rea Selecionada" } } } \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/select.ru.json b/homeassistant/components/tuya/translations/select.ru.json index 9f575f3f5f49d3..c6da7570d5e448 100644 --- a/homeassistant/components/tuya/translations/select.ru.json +++ b/homeassistant/components/tuya/translations/select.ru.json @@ -63,6 +63,7 @@ "power_on": "\u0412\u043a\u043b\u044e\u0447\u0435\u043d\u043e" }, "tuya__vacuum_cistern": { + "closed": "\u0417\u0430\u043a\u0440\u044b\u0442\u043e", "high": "\u0412\u044b\u0441\u043e\u043a\u0438\u0439", "low": "\u041d\u0438\u0437\u043a\u0438\u0439", "middle": "\u0421\u0440\u0435\u0434\u043d\u0438\u0439" @@ -73,18 +74,22 @@ "small": "\u041c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0439" }, "tuya__vacuum_mode": { + "bow": "\u041e\u0433\u0438\u0431\u0430\u0442\u044c", "chargego": "\u0412\u0435\u0440\u043d\u0443\u0442\u044c \u043a \u0434\u043e\u043a-\u0441\u0442\u0430\u043d\u0446\u0438\u0438", + "left_bow": "\u041e\u0433\u0438\u0431\u0430\u0442\u044c \u0441\u043b\u0435\u0432\u0430", "left_spiral": "\u0421\u043f\u0438\u0440\u0430\u043b\u044c \u0432\u043b\u0435\u0432\u043e", "mop": "\u0428\u0432\u0430\u0431\u0440\u0430", "part": "\u0427\u0430\u0441\u0442\u044c", "pick_zone": "\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0437\u043e\u043d\u0443", "point": "\u0422\u043e\u0447\u043a\u0430", "random": "\u0421\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0439", + "right_bow": "\u041e\u0433\u0438\u0431\u0430\u0442\u044c \u0441\u043f\u0440\u0430\u0432\u0430", "right_spiral": "\u0421\u043f\u0438\u0440\u0430\u043b\u044c \u0432\u043f\u0440\u0430\u0432\u043e", "single": "\u041e\u0434\u0438\u043d\u043e\u0447\u043d\u044b\u0439", "smart": "\u0418\u043d\u0442\u0435\u043b\u043b\u0435\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439", "spiral": "\u0421\u043f\u0438\u0440\u0430\u043b\u044c", "standby": "\u041e\u0436\u0438\u0434\u0430\u043d\u0438\u0435", + "wall_follow": "\u0421\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c \u0437\u0430 \u0441\u0442\u0435\u043d\u043e\u0439", "zone": "\u0417\u043e\u043d\u0430" } } diff --git a/homeassistant/components/tuya/translations/select.uk.json b/homeassistant/components/tuya/translations/select.uk.json new file mode 100644 index 00000000000000..3e802b0e97f639 --- /dev/null +++ b/homeassistant/components/tuya/translations/select.uk.json @@ -0,0 +1,17 @@ +{ + "state": { + "tuya__curtain_mode": { + "morning": "\u0420\u0430\u043d\u043e\u043a", + "night": "\u041d\u0456\u0447" + }, + "tuya__curtain_motor_mode": { + "back": "\u041d\u0430\u0437\u0430\u0434", + "forward": "\u0412\u043f\u0435\u0440\u0435\u0434" + }, + "tuya__fan_angle": { + "30": "30\u00b0", + "60": "60\u00b0", + "90": "90\u00b0" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/sensor.pt-BR.json b/homeassistant/components/tuya/translations/sensor.pt-BR.json new file mode 100644 index 00000000000000..b8e8beeb0940b2 --- /dev/null +++ b/homeassistant/components/tuya/translations/sensor.pt-BR.json @@ -0,0 +1,15 @@ +{ + "state": { + "tuya__status": { + "boiling_temp": "Temperatura de ebuli\u00e7\u00e3o", + "cooling": "Resfriamento", + "heating": "Aquecimento", + "heating_temp": "Temperatura de aquecimento", + "reserve_1": "Reserva 1", + "reserve_2": "Reserva 2", + "reserve_3": "Reserva 3", + "standby": "Em espera", + "warm": "Preserva\u00e7\u00e3o do calor" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/uk.json b/homeassistant/components/tuya/translations/uk.json index 1d2709d260a0c6..97616e5f388c9f 100644 --- a/homeassistant/components/tuya/translations/uk.json +++ b/homeassistant/components/tuya/translations/uk.json @@ -3,7 +3,7 @@ "abort": { "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "error": { "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." diff --git a/homeassistant/components/twilio/translations/ja.json b/homeassistant/components/twilio/translations/ja.json index 45930c49e7b10c..84ea72878e7b4c 100644 --- a/homeassistant/components/twilio/translations/ja.json +++ b/homeassistant/components/twilio/translations/ja.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Home Assistant Cloud\u306b\u63a5\u7d9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002", "single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002", "webhook_not_internet_accessible": "Webhook\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u53d7\u4fe1\u3059\u308b\u306b\u306f\u3001Home Assistant\u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u306b\u3001\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u304b\u3089\u30a2\u30af\u30bb\u30b9\u3067\u304d\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002" }, diff --git a/homeassistant/components/twilio/translations/nl.json b/homeassistant/components/twilio/translations/nl.json index 3d31175d2de19c..cf180123c989dd 100644 --- a/homeassistant/components/twilio/translations/nl.json +++ b/homeassistant/components/twilio/translations/nl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Niet verbonden met Home Assistant Cloud.", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." }, diff --git a/homeassistant/components/twilio/translations/pt-BR.json b/homeassistant/components/twilio/translations/pt-BR.json index 68e068c0feeb56..4f6e33f0f7a636 100644 --- a/homeassistant/components/twilio/translations/pt-BR.json +++ b/homeassistant/components/twilio/translations/pt-BR.json @@ -2,7 +2,8 @@ "config": { "abort": { "cloud_not_connected": "N\u00e3o conectado ao Home Assistant Cloud.", - "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", + "webhook_not_internet_accessible": "Sua inst\u00e2ncia do Home Assistant precisa estar acess\u00edvel pela Internet para receber mensagens de webhook." }, "create_entry": { "default": "Para enviar eventos para o Home Assistant, voc\u00ea precisar\u00e1 configurar [Webhooks com Twilio] ( {twilio_url} ). \n\n Preencha as seguintes informa\u00e7\u00f5es: \n\n - URL: ` {webhook_url} ` \n - M\u00e9todo: POST \n - Tipo de Conte\u00fado: application / x-www-form-urlencoded \n\n Veja [a documenta\u00e7\u00e3o] ( {docs_url} ) sobre como configurar automa\u00e7\u00f5es para manipular dados de entrada." diff --git a/homeassistant/components/twilio/translations/uk.json b/homeassistant/components/twilio/translations/uk.json index 8ea0ce86a37a09..0ba735ba9c279e 100644 --- a/homeassistant/components/twilio/translations/uk.json +++ b/homeassistant/components/twilio/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e.", + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f.", "webhook_not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u043f\u043e\u0432\u0438\u043d\u0435\u043d \u0431\u0443\u0442\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0439 \u0437 \u0406\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443 \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f Webhook-\u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u044c." }, "create_entry": { diff --git a/homeassistant/components/twinkly/translations/pt-BR.json b/homeassistant/components/twinkly/translations/pt-BR.json index e35968d4e8978e..3aff4eb867d561 100644 --- a/homeassistant/components/twinkly/translations/pt-BR.json +++ b/homeassistant/components/twinkly/translations/pt-BR.json @@ -7,10 +7,15 @@ "cannot_connect": "Falha ao conectar" }, "step": { + "discovery_confirm": { + "description": "Deseja configurar {name} - {model} ( {host} )?" + }, "user": { "data": { "host": "Nome do host" - } + }, + "description": "Configure sua fita de led Twinkly", + "title": "Twinkly" } } } diff --git a/homeassistant/components/unifi/translations/el.json b/homeassistant/components/unifi/translations/el.json index cfc6a2fc8b4c34..4c3917b4caeef0 100644 --- a/homeassistant/components/unifi/translations/el.json +++ b/homeassistant/components/unifi/translations/el.json @@ -42,6 +42,7 @@ }, "statistics_sensors": { "data": { + "allow_bandwidth_sensors": "\u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b5\u03c2 \u03c7\u03c1\u03ae\u03c3\u03b7\u03c2 \u03b5\u03cd\u03c1\u03bf\u03c5\u03c2 \u03b6\u03ce\u03bd\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c0\u03b5\u03bb\u03ac\u03c4\u03b5\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5", "allow_uptime_sensors": "\u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b5\u03c2 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c5 \u03c3\u03c5\u03bd\u03b5\u03c7\u03bf\u03cd\u03c2 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c0\u03b5\u03bb\u03ac\u03c4\u03b5\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5" }, "description": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03c9\u03bd \u03c3\u03c4\u03b1\u03c4\u03b9\u03c3\u03c4\u03b9\u03ba\u03ce\u03bd \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03c9\u03bd", diff --git a/homeassistant/components/unifi/translations/pt-BR.json b/homeassistant/components/unifi/translations/pt-BR.json index a0bf047d8278ae..2419418479b39e 100644 --- a/homeassistant/components/unifi/translations/pt-BR.json +++ b/homeassistant/components/unifi/translations/pt-BR.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "O site de controle j\u00e1 est\u00e1 configurado", + "configuration_updated": "Configura\u00e7\u00e3o atualizada.", "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { @@ -9,6 +10,7 @@ "service_unavailable": "Falha ao conectar", "unknown_client_mac": "Nenhum cliente dispon\u00edvel nesse endere\u00e7o MAC" }, + "flow_title": "{site} ( {host} )", "step": { "user": { "data": { @@ -27,27 +29,40 @@ "step": { "client_control": { "data": { - "block_client": "Clientes com acesso controlado \u00e0 rede" + "block_client": "Clientes com acesso controlado \u00e0 rede", + "dpi_restrictions": "Permitir o controle de grupos de restri\u00e7\u00e3o de DPI", + "poe_clients": "Permitir o controle POE de clientes" }, "description": "Configurar controles do cliente \n\nCrie comutadores para os n\u00fameros de s\u00e9rie para os quais deseja controlar o acesso \u00e0 rede.", - "title": "UniFi op\u00e7\u00f5es de 2/3" + "title": "Op\u00e7\u00f5es UniFi 2/3" }, "device_tracker": { "data": { "detection_time": "Tempo em segundos desde a \u00faltima vez que foi visto at\u00e9 ser considerado afastado", + "ignore_wired_bug": "Desativar `wired bug logic`", + "ssid_filter": "Selecione SSIDs para rastrear clientes sem fio", "track_clients": "Rastrear clientes da rede", "track_devices": "Rastrear dispositivos de rede (dispositivos Ubiquiti)", "track_wired_clients": "Incluir clientes de rede com fio" - } - }, - "init": { - "data": { - "one": "um", - "other": "uns" - } + }, + "description": "Configurar rastreamento de dispositivo", + "title": "Op\u00e7\u00f5es UniFi 1/3" }, "simple_options": { + "data": { + "block_client": "Clientes com acesso controlado \u00e0 rede", + "track_clients": "Rastrear clientes da rede", + "track_devices": "Rastrear dispositivos de rede (dispositivos Ubiquiti)" + }, "description": "Configurar integra\u00e7\u00e3o UniFi" + }, + "statistics_sensors": { + "data": { + "allow_bandwidth_sensors": "Sensores de uso de largura de banda para clientes de rede", + "allow_uptime_sensors": "Sensores de tempo de atividade para clientes de rede" + }, + "description": "Configurar sensores de estat\u00edsticas", + "title": "Op\u00e7\u00f5es UniFi 3/3" } } } diff --git a/homeassistant/components/unifiprotect/translations/nl.json b/homeassistant/components/unifiprotect/translations/nl.json index a7417a8e50d507..090624f20094ff 100644 --- a/homeassistant/components/unifiprotect/translations/nl.json +++ b/homeassistant/components/unifiprotect/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd", + "discovery_started": "Ontdekking gestart" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -9,6 +10,7 @@ "protect_version": "Minimaal vereiste versie is v1.20.0. Upgrade UniFi Protect en probeer het opnieuw.", "unknown": "Onverwachte fout" }, + "flow_title": "{name} ({ip_address})", "step": { "discovery_confirm": { "data": { diff --git a/homeassistant/components/unifiprotect/translations/pt-BR.json b/homeassistant/components/unifiprotect/translations/pt-BR.json index 9fe15726de7db3..1f26b9529983a8 100644 --- a/homeassistant/components/unifiprotect/translations/pt-BR.json +++ b/homeassistant/components/unifiprotect/translations/pt-BR.json @@ -7,9 +7,10 @@ "error": { "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "protect_version": "A vers\u00e3o m\u00ednima exigida \u00e9 v1.20.0. Atualize o UniFi Protect e tente novamente.", "unknown": "Erro inesperado" }, - "flow_title": "{nome} ({ip_address})", + "flow_title": "{name} ({ip_address})", "step": { "discovery_confirm": { "data": { @@ -17,7 +18,7 @@ "username": "Usu\u00e1rio", "verify_ssl": "Verifique o certificado SSL" }, - "description": "Deseja configurar {name} ({ip_address})?", + "description": "Deseja configurar {name} ({ip_address})?\nVoc\u00ea precisar\u00e1 de um usu\u00e1rio local criado no console do sistema operacional UniFi para fazer login. Usu\u00e1rios da Ubiquiti Cloud n\u00e3o funcionar\u00e3o. Para mais informa\u00e7\u00f5es: {local_user_documentation_url}", "title": "Descoberta UniFi Protect" }, "reauth_confirm": { @@ -46,8 +47,12 @@ "step": { "init": { "data": { - "disable_rtsp": "Desativar o fluxo RTSP" - } + "all_updates": "M\u00e9tricas em tempo real (AVISO: aumenta muito o uso da CPU)", + "disable_rtsp": "Desativar o fluxo RTSP", + "override_connection_host": "Anular o host de conex\u00e3o" + }, + "description": "A op\u00e7\u00e3o de m\u00e9tricas em tempo real s\u00f3 deve ser habilitada se voc\u00ea tiver habilitado os sensores de diagn\u00f3stico e quiser que eles sejam atualizados em tempo real. Se n\u00e3o estiver ativado, eles ser\u00e3o atualizados apenas uma vez a cada 15 minutos.", + "title": "Op\u00e7\u00f5es de prote\u00e7\u00e3o UniFi" } } } diff --git a/homeassistant/components/upb/translations/pt-BR.json b/homeassistant/components/upb/translations/pt-BR.json index 03f403b7936483..402b81140f92b5 100644 --- a/homeassistant/components/upb/translations/pt-BR.json +++ b/homeassistant/components/upb/translations/pt-BR.json @@ -5,14 +5,18 @@ }, "error": { "cannot_connect": "Falha ao conectar", + "invalid_upb_file": "Caminho e nome do arquivo de exporta\u00e7\u00e3o UPSstart UPB.", "unknown": "Erro inesperado" }, "step": { "user": { "data": { "address": "Endere\u00e7o (veja a descri\u00e7\u00e3o acima)", + "file_path": "Caminho e nome do arquivo de exporta\u00e7\u00e3o UPSstart UPB.", "protocol": "Protocolo" - } + }, + "description": "Conecte um M\u00f3dulo de Interface Powerline Universal Powerline Bus (UPB PIM). A string de endere\u00e7o deve estar no formato 'address[:port]' para 'tcp'. A porta \u00e9 opcional e o padr\u00e3o \u00e9 2101. Exemplo: '192.168.1.42'. Para o protocolo serial, o endere\u00e7o deve estar no formato 'tty[:baud]'. O baud \u00e9 opcional e o padr\u00e3o \u00e9 4800. Exemplo: '/dev/ttyS1'.", + "title": "Conecte-se ao UPB PIM" } } } diff --git a/homeassistant/components/upcloud/translations/pt-BR.json b/homeassistant/components/upcloud/translations/pt-BR.json index d905975f78d5fa..0fadb90bf5a8ee 100644 --- a/homeassistant/components/upcloud/translations/pt-BR.json +++ b/homeassistant/components/upcloud/translations/pt-BR.json @@ -12,5 +12,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Intervalo de atualiza\u00e7\u00e3o em segundos, m\u00ednimo 30" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/updater/translations/pt-BR.json b/homeassistant/components/updater/translations/pt-BR.json index 7d07ec8da096be..cc89a22092af2a 100644 --- a/homeassistant/components/updater/translations/pt-BR.json +++ b/homeassistant/components/updater/translations/pt-BR.json @@ -1,3 +1,3 @@ { - "title": "Atualizador" + "title": "Gerenciador de atualiza\u00e7\u00f5es" } \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/pt-BR.json b/homeassistant/components/upnp/translations/pt-BR.json index 3258117a145d8f..a1544981ea91ef 100644 --- a/homeassistant/components/upnp/translations/pt-BR.json +++ b/homeassistant/components/upnp/translations/pt-BR.json @@ -3,15 +3,29 @@ "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "incomplete_discovery": "Descoberta incompleta", - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" + "no_devices_found": "Nenhum dispositivo encontrado na rede" }, + "flow_title": "{name}", "step": { + "ssdp_confirm": { + "description": "Deseja configurar este dispositivo UPnP/IGD?" + }, "user": { "data": { "scan_interval": "Intervalo de atualiza\u00e7\u00e3o (segundos, m\u00ednimo 30)", + "unique_id": "Dispositivo", "usn": "Dispositivo" } } } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Intervalo de atualiza\u00e7\u00e3o (segundos, m\u00ednimo 30)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/uptimerobot/translations/pt-BR.json b/homeassistant/components/uptimerobot/translations/pt-BR.json index 20a7ba268bff02..0d9bea96b121d3 100644 --- a/homeassistant/components/uptimerobot/translations/pt-BR.json +++ b/homeassistant/components/uptimerobot/translations/pt-BR.json @@ -2,12 +2,14 @@ "config": { "abort": { "already_configured": "A conta j\u00e1 foi configurada", + "reauth_failed_existing": "N\u00e3o foi poss\u00edvel atualizar a entrada de configura\u00e7\u00e3o. Remova a integra\u00e7\u00e3o e configure-a novamente.", "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", "unknown": "Erro inesperado" }, "error": { "cannot_connect": "Falha ao conectar", "invalid_api_key": "Chave de API inv\u00e1lida", + "reauth_failed_matching_account": "A chave de API fornecida n\u00e3o corresponde ao ID da conta da configura\u00e7\u00e3o existente.", "unknown": "Erro inesperado" }, "step": { @@ -15,12 +17,14 @@ "data": { "api_key": "Chave da API" }, + "description": "Voc\u00ea precisa fornecer uma nova chave de API somente leitura do UptimeRobot", "title": "Reautenticar Integra\u00e7\u00e3o" }, "user": { "data": { "api_key": "Chave da API" - } + }, + "description": "Voc\u00ea precisa fornecer uma chave de API somente leitura do UptimeRobot" } } } diff --git a/homeassistant/components/uptimerobot/translations/sensor.ja.json b/homeassistant/components/uptimerobot/translations/sensor.ja.json new file mode 100644 index 00000000000000..e9bccabf73682e --- /dev/null +++ b/homeassistant/components/uptimerobot/translations/sensor.ja.json @@ -0,0 +1,11 @@ +{ + "state": { + "uptimerobot__monitor_status": { + "down": "\u4e0b", + "not_checked_yet": "\u307e\u3060\u30c1\u30a7\u30c3\u30af\u3057\u3066\u3044\u307e\u305b\u3093", + "pause": "\u4e00\u6642\u505c\u6b62", + "seems_down": "\u4e0b\u304c\u3063\u3066\u3044\u308b\u3088\u3046\u3067\u3059", + "up": "\u4e0a" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/uptimerobot/translations/sensor.nl.json b/homeassistant/components/uptimerobot/translations/sensor.nl.json new file mode 100644 index 00000000000000..8aad2b8b56f557 --- /dev/null +++ b/homeassistant/components/uptimerobot/translations/sensor.nl.json @@ -0,0 +1,11 @@ +{ + "state": { + "uptimerobot__monitor_status": { + "down": "Offline", + "not_checked_yet": "Nog niet gecontroleerd", + "pause": "Pauzeer", + "seems_down": "Lijkt offline", + "up": "Online" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vacuum/translations/pt-BR.json b/homeassistant/components/vacuum/translations/pt-BR.json index 77a38f3b29863c..e149d944f93388 100644 --- a/homeassistant/components/vacuum/translations/pt-BR.json +++ b/homeassistant/components/vacuum/translations/pt-BR.json @@ -1,5 +1,9 @@ { "device_automation": { + "action_type": { + "clean": "Permitir {entity_name} limpar", + "dock": "Permitir {entity_name} voltar \u00e0 base" + }, "condition_type": { "is_cleaning": "{entity_name} est\u00e1 limpando", "is_docked": "{entity_name} est\u00e1 na base" @@ -13,8 +17,8 @@ "_": { "cleaning": "Limpando", "docked": "Na base", - "error": "Erro", - "idle": "Em espera", + "error": "Falha", + "idle": "Ocioso", "off": "Desligado", "on": "Ligado", "paused": "Pausado", diff --git a/homeassistant/components/vallox/translations/pt-BR.json b/homeassistant/components/vallox/translations/pt-BR.json index 847cb96c0db846..729e5257db8ed5 100644 --- a/homeassistant/components/vallox/translations/pt-BR.json +++ b/homeassistant/components/vallox/translations/pt-BR.json @@ -17,6 +17,7 @@ "host": "Nome do host", "name": "Nome" }, + "description": "Configure a integra\u00e7\u00e3o Vallox. Se voc\u00ea tiver problemas com a configura\u00e7\u00e3o, v\u00e1 para {integration_docs_url} .", "title": "Vallox" } } diff --git a/homeassistant/components/venstar/translations/pt-BR.json b/homeassistant/components/venstar/translations/pt-BR.json index 27ba000cef7f04..c22a92e18089bb 100644 --- a/homeassistant/components/venstar/translations/pt-BR.json +++ b/homeassistant/components/venstar/translations/pt-BR.json @@ -15,7 +15,8 @@ "pin": "C\u00f3digo PIN", "ssl": "Usar um certificado SSL", "username": "Usu\u00e1rio" - } + }, + "title": "Conecte-se ao termostato Venstar" } } } diff --git a/homeassistant/components/vera/translations/pt-BR.json b/homeassistant/components/vera/translations/pt-BR.json new file mode 100644 index 00000000000000..361b0487617c6f --- /dev/null +++ b/homeassistant/components/vera/translations/pt-BR.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "cannot_connect": "N\u00e3o foi poss\u00edvel conectar ao controlador com URL {base_url}" + }, + "step": { + "user": { + "data": { + "exclude": "IDs de dispositivos Vera a serem exclu\u00eddos do Home Assistant.", + "lights": "Vera - alternar os IDs do dispositivo para tratar como luzes no Home Assistant.", + "vera_controller_url": "URL do controlador" + }, + "description": "Forne\u00e7a um URL do controlador Vera abaixo. Deve ficar assim: http://192.168.1.161:3480.", + "title": "Configurar controlador Vera" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "exclude": "IDs de dispositivos Vera a serem exclu\u00eddos do Home Assistant.", + "lights": "Vera alternar os IDs do dispositivo para tratar como luzes no Home Assistant." + }, + "description": "Consulte a documenta\u00e7\u00e3o do vera para obter detalhes sobre par\u00e2metros opcionais: https://www.home-assistant.io/integrations/vera/. Nota: Quaisquer altera\u00e7\u00f5es aqui precisar\u00e3o de uma reinicializa\u00e7\u00e3o no servidor do Home Assistant. Para limpar valores, forne\u00e7a um espa\u00e7o.", + "title": "Op\u00e7\u00f5es do controlador Vera" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/pt-BR.json b/homeassistant/components/verisure/translations/pt-BR.json index 21c367c3b870cb..1fc733bfd5580f 100644 --- a/homeassistant/components/verisure/translations/pt-BR.json +++ b/homeassistant/components/verisure/translations/pt-BR.json @@ -9,16 +9,39 @@ "unknown": "Erro inesperado" }, "step": { + "installation": { + "data": { + "giid": "Instala\u00e7\u00e3o" + }, + "description": "O Home Assistant encontrou v\u00e1rias instala\u00e7\u00f5es da Verisure na sua conta do My Pages. Por favor, selecione a instala\u00e7\u00e3o para adicionar ao Home Assistant." + }, "reauth_confirm": { "data": { + "description": "Re-autentique com sua conta Verisure My Pages.", + "email": "Email", "password": "Senha" } }, "user": { "data": { + "description": "Fa\u00e7a login com sua conta Verisure My Pages.", + "email": "Email", "password": "Senha" } } } + }, + "options": { + "error": { + "code_format_mismatch": "O c\u00f3digo PIN padr\u00e3o n\u00e3o corresponde ao n\u00famero necess\u00e1rio de d\u00edgitos" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "N\u00famero de d\u00edgitos no c\u00f3digo PIN para fechaduras", + "lock_default_code": "C\u00f3digo PIN padr\u00e3o para bloqueios, usado se nenhum for fornecido" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/version/translations/pt-BR.json b/homeassistant/components/version/translations/pt-BR.json index 0bb3eabad8cd6a..2129822ace56a8 100644 --- a/homeassistant/components/version/translations/pt-BR.json +++ b/homeassistant/components/version/translations/pt-BR.json @@ -4,7 +4,21 @@ "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "step": { + "user": { + "data": { + "version_source": "Origem da vers\u00e3o" + }, + "description": "Selecione a fonte da qual voc\u00ea deseja rastrear as vers\u00f5es", + "title": "Selecione o tipo de instala\u00e7\u00e3o" + }, "version_source": { + "data": { + "beta": "Incluir vers\u00f5es beta", + "board": "Qual placa deve ser rastreada", + "channel": "Qual canal deve ser rastreado", + "image": "Qual imagem deve ser rastreada" + }, + "description": "Configurar o acompanhamento de vers\u00e3o {version_source}", "title": "Configurar" } } diff --git a/homeassistant/components/vesync/translations/uk.json b/homeassistant/components/vesync/translations/uk.json index 7f6b3a46b15524..1649357f4ff6a1 100644 --- a/homeassistant/components/vesync/translations/uk.json +++ b/homeassistant/components/vesync/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "error": { "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." diff --git a/homeassistant/components/vicare/translations/pt-BR.json b/homeassistant/components/vicare/translations/pt-BR.json index d7026fd7ef1585..01504272dacc68 100644 --- a/homeassistant/components/vicare/translations/pt-BR.json +++ b/homeassistant/components/vicare/translations/pt-BR.json @@ -7,13 +7,19 @@ "error": { "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, + "flow_title": "{name} ( {host} )", "step": { "user": { "data": { "client_id": "Chave da API", + "heating_type": "Tipo de aquecimento", "name": "Nome", - "password": "Senha" - } + "password": "Senha", + "scan_interval": "Intervalo de varredura (segundos)", + "username": "Email" + }, + "description": "Configure a integra\u00e7\u00e3o do ViCare. Para gerar a chave de API, acesse https://developer.viessmann.com", + "title": "{name}" } } } diff --git a/homeassistant/components/vilfo/translations/pt-BR.json b/homeassistant/components/vilfo/translations/pt-BR.json index 605caa3e40d40e..8766261955f803 100644 --- a/homeassistant/components/vilfo/translations/pt-BR.json +++ b/homeassistant/components/vilfo/translations/pt-BR.json @@ -14,6 +14,7 @@ "access_token": "Token de acesso", "host": "Nome do host" }, + "description": "Configure a integra\u00e7\u00e3o do roteador Vilfo. Voc\u00ea precisa do seu nome de host/IP do roteador Vilfo e um token de acesso \u00e0 API. Para obter informa\u00e7\u00f5es adicionais sobre essa integra\u00e7\u00e3o e como obter esses detalhes, visite: https://www.home-assistant.io/integrations/vilfo", "title": "Conecte-se ao roteador Vilfo" } } diff --git a/homeassistant/components/vizio/translations/el.json b/homeassistant/components/vizio/translations/el.json index 13b26b44b76797..99414ed3b6f138 100644 --- a/homeassistant/components/vizio/translations/el.json +++ b/homeassistant/components/vizio/translations/el.json @@ -20,5 +20,16 @@ "description": "\u0388\u03bd\u03b1 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03bc\u03cc\u03bd\u03bf \u03b3\u03b9\u03b1 \u03c4\u03b9\u03c2 \u03c4\u03b7\u03bb\u03b5\u03bf\u03c1\u03ac\u03c3\u03b5\u03b9\u03c2. \u0395\u03ac\u03bd \u03c1\u03c5\u03b8\u03bc\u03af\u03b6\u03b5\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03b1\u03ba\u03cc\u03bc\u03b7 \u03ad\u03bd\u03b1 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2, \u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03b5\u03c1\u03ac\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c0\u03cc \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b1\u03b4\u03b9\u03ba\u03b1\u03c3\u03af\u03b1 \u03b1\u03bd\u03c4\u03b9\u03c3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7\u03c2." } } + }, + "options": { + "step": { + "init": { + "data": { + "apps_to_include_or_exclude": "\u0395\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03af\u03bb\u03b7\u03c8\u03b7 \u03ae \u03b5\u03be\u03b1\u03af\u03c1\u03b5\u03c3\u03b7", + "include_or_exclude": "\u03a3\u03c5\u03bc\u03c0\u03b5\u03c1\u03af\u03bb\u03b7\u03c8\u03b7 \u03ae \u03b5\u03be\u03b1\u03af\u03c1\u03b5\u03c3\u03b7 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ce\u03bd;" + }, + "description": "\u0395\u03ac\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 Smart TV, \u03bc\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ac \u03bd\u03b1 \u03c6\u03b9\u03bb\u03c4\u03c1\u03ac\u03c1\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03bb\u03af\u03c3\u03c4\u03b1 \u03c0\u03b7\u03b3\u03ce\u03bd \u03c3\u03b1\u03c2 \u03b5\u03c0\u03b9\u03bb\u03ad\u03b3\u03bf\u03bd\u03c4\u03b1\u03c2 \u03c0\u03bf\u03b9\u03b5\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ad\u03c2 \u03b8\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03ae \u03b8\u03b1 \u03b5\u03be\u03b1\u03b9\u03c1\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03bb\u03af\u03c3\u03c4\u03b1 \u03c0\u03b7\u03b3\u03ce\u03bd \u03c3\u03b1\u03c2." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/vizio/translations/pt-BR.json b/homeassistant/components/vizio/translations/pt-BR.json index 6db9e82783120f..785bbd752947a6 100644 --- a/homeassistant/components/vizio/translations/pt-BR.json +++ b/homeassistant/components/vizio/translations/pt-BR.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured_device": "Dispositivo j\u00e1 est\u00e1 configurado", - "cannot_connect": "Falha ao conectar" + "cannot_connect": "Falha ao conectar", + "updated_entry": "Esta entrada j\u00e1 foi configurada, mas o nome, aplicativos e/ou op\u00e7\u00f5es definidas na configura\u00e7\u00e3o n\u00e3o correspondem \u00e0 configura\u00e7\u00e3o anteriormente importada, portanto a entrada de configura\u00e7\u00e3o foi atualizada de acordo." }, "error": { "cannot_connect": "Falha ao conectar", @@ -18,10 +19,12 @@ "title": "Processo de pareamento completo" }, "pairing_complete": { + "description": "Seu Dispositivo VIZIO SmartCast agora est\u00e1 conectado ao Home Assistant.", "title": "Pareamento completo" }, "pairing_complete_import": { - "description": "Seu Dispositivo VIZIO SmartCast agora est\u00e1 conectado ao Home Assistant.\n\nSeu Token de acesso \u00e9 '**{access_token}**'." + "description": "Seu Dispositivo VIZIO SmartCast agora est\u00e1 conectado ao Home Assistant.\n\nSeu Token de acesso \u00e9 '**{access_token}**'.", + "title": "Emparelhamento conclu\u00eddo" }, "user": { "data": { @@ -34,5 +37,18 @@ "title": "Dispositivo VIZIO SmartCast" } } + }, + "options": { + "step": { + "init": { + "data": { + "apps_to_include_or_exclude": "Aplicativos para incluir ou excluir", + "include_or_exclude": "Incluir ou excluir aplicativos?", + "volume_step": "Tamanho do Passo do Volume" + }, + "description": "Se voc\u00ea tiver uma Smart TV, poder\u00e1 filtrar sua lista de fontes opcionalmente escolhendo quais aplicativos incluir ou excluir em sua lista de fontes.", + "title": "Alterar op\u00e7\u00f5es para Dispositivo VIZIO SmartCast" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/vlc_telnet/translations/pt-BR.json b/homeassistant/components/vlc_telnet/translations/pt-BR.json index 146adc6d4c7e66..0a26a6aaf7d75d 100644 --- a/homeassistant/components/vlc_telnet/translations/pt-BR.json +++ b/homeassistant/components/vlc_telnet/translations/pt-BR.json @@ -12,11 +12,16 @@ "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, + "flow_title": "{host}", "step": { + "hassio_confirm": { + "description": "Voc\u00ea quer se conectar para adicionar {addon}?" + }, "reauth_confirm": { "data": { "password": "Senha" - } + }, + "description": "Digite a senha correta para o host: {host}" }, "user": { "data": { diff --git a/homeassistant/components/volumio/translations/pt-BR.json b/homeassistant/components/volumio/translations/pt-BR.json index 1e898e15ce0558..487710baf01a6d 100644 --- a/homeassistant/components/volumio/translations/pt-BR.json +++ b/homeassistant/components/volumio/translations/pt-BR.json @@ -1,13 +1,18 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "N\u00e3o foi poss\u00edvel conectar o Audi\u00f3filo descoberto" }, "error": { "cannot_connect": "Falha ao conectar", "unknown": "Erro inesperado" }, "step": { + "discovery_confirm": { + "description": "Voc\u00ea quer adicionar o audi\u00f3filo {name} ao Home Assistant?", + "title": "O dispositivo foi encontrado" + }, "user": { "data": { "host": "Nome do host", diff --git a/homeassistant/components/wallbox/translations/pt-BR.json b/homeassistant/components/wallbox/translations/pt-BR.json index 21ab247c000e59..3fb6428603a588 100644 --- a/homeassistant/components/wallbox/translations/pt-BR.json +++ b/homeassistant/components/wallbox/translations/pt-BR.json @@ -20,9 +20,11 @@ "user": { "data": { "password": "Senha", + "station": "N\u00famero de s\u00e9rie da esta\u00e7\u00e3o", "username": "Usu\u00e1rio" } } } - } + }, + "title": "Wallbox" } \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/pt-BR.json b/homeassistant/components/water_heater/translations/pt-BR.json new file mode 100644 index 00000000000000..28e234b4d74d77 --- /dev/null +++ b/homeassistant/components/water_heater/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "device_automation": { + "action_type": { + "turn_off": "Desligar {entity_name}", + "turn_on": "Ligar {entity_name}" + } + }, + "state": { + "_": { + "eco": "Eco", + "electric": "El\u00e9trico", + "gas": "G\u00e1s", + "heat_pump": "Bomba de calor", + "high_demand": "Alta demanda", + "off": "Desligado", + "performance": "Desempenho" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/watttime/translations/pt-BR.json b/homeassistant/components/watttime/translations/pt-BR.json index 2f61296cff5e6f..286a714eac4999 100644 --- a/homeassistant/components/watttime/translations/pt-BR.json +++ b/homeassistant/components/watttime/translations/pt-BR.json @@ -27,6 +27,7 @@ "data": { "password": "Senha" }, + "description": "Por favor, digite novamente a senha para {username} :", "title": "Reautenticar Integra\u00e7\u00e3o" }, "user": { @@ -37,5 +38,15 @@ "description": "Insira seu nome de usu\u00e1rio e senha:" } } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "Mostrar localiza\u00e7\u00e3o monitorada no mapa" + }, + "title": "Configurar WattTime" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/waze_travel_time/translations/pt-BR.json b/homeassistant/components/waze_travel_time/translations/pt-BR.json index a8d47f260b6515..54ade45119bdc0 100644 --- a/homeassistant/components/waze_travel_time/translations/pt-BR.json +++ b/homeassistant/components/waze_travel_time/translations/pt-BR.json @@ -9,9 +9,31 @@ "step": { "user": { "data": { - "name": "Nome" - } + "destination": "Destino", + "name": "Nome", + "origin": "Origem", + "region": "Regi\u00e3o" + }, + "description": "Para Origem e Destino, insira o endere\u00e7o ou as coordenadas GPS do local (as coordenadas GPS devem ser separadas por uma v\u00edrgula). Voc\u00ea tamb\u00e9m pode inserir um ID de entidade que forne\u00e7a essas informa\u00e7\u00f5es em seu estado, um ID de entidade com atributos de latitude e longitude ou um nome amig\u00e1vel de zona." } } - } + }, + "options": { + "step": { + "init": { + "data": { + "avoid_ferries": "Evitar balsas?", + "avoid_subscription_roads": "Evitar estradas que precisam de uma vinheta/assinatura?", + "avoid_toll_roads": "Evitar estradas com ped\u00e1gio?", + "excl_filter": "SEM Substring na descri\u00e7\u00e3o da rota selecionada", + "incl_filter": "Substring na descri\u00e7\u00e3o da rota selecionada", + "realtime": "Tempo de viagem em tempo real?", + "units": "Unidades", + "vehicle_type": "Tipo de Ve\u00edculo" + }, + "description": "As entradas `substring` permitir\u00e3o que voc\u00ea force a integra\u00e7\u00e3o a usar uma rota espec\u00edfica ou evite uma rota espec\u00edfica em seu c\u00e1lculo de viagem no tempo." + } + } + }, + "title": "Tempo de viagem do Waze" } \ No newline at end of file diff --git a/homeassistant/components/weather/translations/pt-BR.json b/homeassistant/components/weather/translations/pt-BR.json index 64a81da9b354bc..bf5f96d849176f 100644 --- a/homeassistant/components/weather/translations/pt-BR.json +++ b/homeassistant/components/weather/translations/pt-BR.json @@ -7,15 +7,15 @@ "fog": "Nevoeiro", "hail": "Granizo", "lightning": "Raios", - "lightning-rainy": "Raios, chuvoso", + "lightning-rainy": "Chuvoso com raios", "partlycloudy": "Parcialmente nublado", "pouring": "Torrencial", "rainy": "Chuvoso", "snowy": "Neve", - "snowy-rainy": "Neve, chuva", + "snowy-rainy": "Chuvoso com neve", "sunny": "Ensolarado", - "windy": "Ventoso", - "windy-variant": "Ventoso" + "windy": "Ventania", + "windy-variant": "Ventania" } } } \ No newline at end of file diff --git a/homeassistant/components/wemo/translations/pt-BR.json b/homeassistant/components/wemo/translations/pt-BR.json index 6000966dc7e6cc..59e6bf2e3b08e4 100644 --- a/homeassistant/components/wemo/translations/pt-BR.json +++ b/homeassistant/components/wemo/translations/pt-BR.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "no_devices_found": "Nenhum dispositivo encontrado na rede", "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "step": { @@ -9,5 +9,10 @@ "description": "Voc\u00ea quer configurar o Wemo?" } } + }, + "device_automation": { + "trigger_type": { + "long_press": "O bot\u00e3o Wemo foi pressionado por 2 segundos." + } } } \ No newline at end of file diff --git a/homeassistant/components/wemo/translations/uk.json b/homeassistant/components/wemo/translations/uk.json index 1217d664234241..2a705d370f4d96 100644 --- a/homeassistant/components/wemo/translations/uk.json +++ b/homeassistant/components/wemo/translations/uk.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\u041f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456 \u0432 \u043c\u0435\u0440\u0435\u0436\u0456.", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "step": { "confirm": { diff --git a/homeassistant/components/whois/translations/ja.json b/homeassistant/components/whois/translations/ja.json index 7102e95a25d5f6..66a7868a265971 100644 --- a/homeassistant/components/whois/translations/ja.json +++ b/homeassistant/components/whois/translations/ja.json @@ -3,6 +3,12 @@ "abort": { "already_configured": "\u30b5\u30fc\u30d3\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" }, + "error": { + "unexpected_response": "Whois\u30b5\u30fc\u30d0\u30fc\u304b\u3089\u306e\u4e88\u671f\u3057\u306a\u3044\u5fdc\u7b54", + "unknown_date_format": "Whois\u30b5\u30fc\u30d0\u30fc\u306e\u5fdc\u7b54\u3067\u4e0d\u660e\u306a\u65e5\u4ed8\u30d5\u30a9\u30fc\u30de\u30c3\u30c8", + "unknown_tld": "\u6307\u5b9a\u3055\u308c\u305fTLD\u306f\u4e0d\u660e\u3001\u3082\u3057\u304f\u306f\u3053\u306e\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3067\u306f\u5229\u7528\u3067\u304d\u307e\u305b\u3093", + "whois_command_failed": "Whois\u30b3\u30de\u30f3\u30c9\u304c\u5931\u6557\u3057\u307e\u3057\u305f: whois\u60c5\u5831\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/whois/translations/pt-BR.json b/homeassistant/components/whois/translations/pt-BR.json index 062f816c14a984..cf4b5334c2062b 100644 --- a/homeassistant/components/whois/translations/pt-BR.json +++ b/homeassistant/components/whois/translations/pt-BR.json @@ -4,6 +4,8 @@ "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" }, "error": { + "unexpected_response": "Resposta inesperada do servidor whois", + "unknown_date_format": "Formato de data desconhecido na resposta do servidor whois", "unknown_tld": "O TLD fornecido \u00e9 desconhecido ou n\u00e3o est\u00e1 dispon\u00edvel para esta integra\u00e7\u00e3o", "whois_command_failed": "O comando Whois falhou: n\u00e3o foi poss\u00edvel recuperar informa\u00e7\u00f5es whois" }, diff --git a/homeassistant/components/wiffi/translations/pt-BR.json b/homeassistant/components/wiffi/translations/pt-BR.json index ab43d965a8f57b..226e5399cdd2ca 100644 --- a/homeassistant/components/wiffi/translations/pt-BR.json +++ b/homeassistant/components/wiffi/translations/pt-BR.json @@ -13,5 +13,14 @@ "title": "Configurar servidor TCP para dispositivos WIFFI" } } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Tempo limite (minutos)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/wilight/translations/pt-BR.json b/homeassistant/components/wilight/translations/pt-BR.json index e29d809ebff3dd..5da689b9b74e3a 100644 --- a/homeassistant/components/wilight/translations/pt-BR.json +++ b/homeassistant/components/wilight/translations/pt-BR.json @@ -1,7 +1,16 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "not_supported_device": "Este WiLight n\u00e3o \u00e9 suportado atualmente", + "not_wilight_device": "Este dispositivo n\u00e3o \u00e9 WiLight" + }, + "flow_title": "{name}", + "step": { + "confirm": { + "description": "Voc\u00ea deseja configurar WiLight {name}?\n\nEle suporta: {components}", + "title": "WiLight" + } } } } \ No newline at end of file diff --git a/homeassistant/components/withings/translations/pt-BR.json b/homeassistant/components/withings/translations/pt-BR.json index 9e89d9ff7530b8..6a067498f1e034 100644 --- a/homeassistant/components/withings/translations/pt-BR.json +++ b/homeassistant/components/withings/translations/pt-BR.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "Configura\u00e7\u00e3o atualizada para o perfil.", "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", "no_url_available": "N\u00e3o h\u00e1 URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a se\u00e7\u00e3o de ajuda]({docs_url})" @@ -11,8 +12,20 @@ "error": { "already_configured": "A conta j\u00e1 foi configurada" }, + "flow_title": "{profile}", "step": { + "pick_implementation": { + "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" + }, + "profile": { + "data": { + "profile": "Nome do perfil" + }, + "description": "Forne\u00e7a um nome de perfil exclusivo para esses dados. Normalmente, esse \u00e9 o nome do perfil selecionado na etapa anterior.", + "title": "Perfil de usu\u00e1rio." + }, "reauth": { + "description": "O perfil \"{profile}\" precisa ser autenticado novamente para continuar recebendo dados do Withings", "title": "Reautenticar Integra\u00e7\u00e3o" } } diff --git a/homeassistant/components/wled/translations/nl.json b/homeassistant/components/wled/translations/nl.json index 8423f2d3f48d6c..d1ba5b75ec33d6 100644 --- a/homeassistant/components/wled/translations/nl.json +++ b/homeassistant/components/wled/translations/nl.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "cannot_connect": "Kan geen verbinding maken" + "cannot_connect": "Kan geen verbinding maken", + "cct_unsupported": "Dit WLED-apparaat maakt gebruik van CCT-kanalen, wat niet wordt ondersteund door deze integratie" }, "error": { "cannot_connect": "Kan geen verbinding maken" diff --git a/homeassistant/components/wled/translations/pt-BR.json b/homeassistant/components/wled/translations/pt-BR.json index da05a0b6690ea2..c922f86d776df0 100644 --- a/homeassistant/components/wled/translations/pt-BR.json +++ b/homeassistant/components/wled/translations/pt-BR.json @@ -8,10 +8,25 @@ "error": { "cannot_connect": "Falha ao conectar" }, + "flow_title": "{name}", "step": { "user": { "data": { "host": "Nome do host" + }, + "description": "Configure seu WLED para integra\u00e7\u00e3o com o Home Assistant." + }, + "zeroconf_confirm": { + "description": "Deseja adicionar o WLED chamado `{name}` ao Home Assistant?", + "title": "Dispositivo WLED descoberto" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "keep_master_light": "Mantenha a luz principal, mesmo com 1 segmento de LED." } } } diff --git a/homeassistant/components/wled/translations/select.pt-BR.json b/homeassistant/components/wled/translations/select.pt-BR.json new file mode 100644 index 00000000000000..0323f9f7960d15 --- /dev/null +++ b/homeassistant/components/wled/translations/select.pt-BR.json @@ -0,0 +1,9 @@ +{ + "state": { + "wled__live_override": { + "0": "Desligado", + "1": "Ligado", + "2": "At\u00e9 que o dispositivo seja reiniciado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/pt-BR.json b/homeassistant/components/wolflink/translations/pt-BR.json index 99728884edb006..617a2398ae8134 100644 --- a/homeassistant/components/wolflink/translations/pt-BR.json +++ b/homeassistant/components/wolflink/translations/pt-BR.json @@ -12,13 +12,15 @@ "device": { "data": { "device_name": "Dispositivo" - } + }, + "title": "Selecione o dispositivo WOLF" }, "user": { "data": { "password": "Senha", "username": "Usu\u00e1rio" - } + }, + "title": "Conex\u00e3o WOLF SmartSet" } } } diff --git a/homeassistant/components/wolflink/translations/sensor.pt-BR.json b/homeassistant/components/wolflink/translations/sensor.pt-BR.json index 2af363e8f1c0bd..7c2cdca7bf8fa4 100644 --- a/homeassistant/components/wolflink/translations/sensor.pt-BR.json +++ b/homeassistant/components/wolflink/translations/sensor.pt-BR.json @@ -1,18 +1,87 @@ { "state": { "wolflink__state": { + "1_x_warmwasser": "1 x DHW", + "abgasklappe": "Amortecedor de g\u00e1s de combust\u00e3o", + "absenkbetrieb": "Modo de recuo", + "absenkstop": "Parada de recuo", "aktiviert": "Ativado", + "antilegionellenfunktion": "Fun\u00e7\u00e3o anti-legionela", + "at_abschaltung": "Desligamento OT", + "at_frostschutz": "Prote\u00e7\u00e3o contra geada OT", "aus": "Desativado", + "auto": "Auto", + "auto_off_cool": "AutoOffCool", + "auto_on_cool": "AutoOnCool", + "automatik_aus": "Automatic OFF", + "automatik_ein": "Automatic ON", + "bereit_keine_ladung": "Pronto, n\u00e3o carregando", + "betrieb_ohne_brenner": "Trabalhando sem bico", + "cooling": "Resfriamento", "deaktiviert": "Inativo", + "dhw_prior": "DHWPrior", "eco": "Econ\u00f4mico", "ein": "Habilitado", + "estrichtrocknung": "Secagem da mesa", + "externe_deaktivierung": "Desativa\u00e7\u00e3o externa", + "fernschalter_ein": "Controle remoto ativado", + "frost_heizkreis": "Congelamento do circuito de aquecimento", + "frost_warmwasser": "Geada DHW", + "frostschutz": "Prote\u00e7\u00e3o contra geada", + "gasdruck": "Press\u00e3o do g\u00e1s", + "glt_betrieb": "Modo BMS", + "gradienten_uberwachung": "Monitoramento de gradiente", + "heizbetrieb": "Modo de aquecimento", + "heizgerat_mit_speicher": "Boiler com cilindro", + "heizung": "Aquecimento", + "initialisierung": "Inicializa\u00e7\u00e3o", + "kalibration": "Calibra\u00e7\u00e3o", + "kalibration_heizbetrieb": "Calibra\u00e7\u00e3o do modo de aquecimento", + "kalibration_kombibetrieb": "Calibra\u00e7\u00e3o do modo combinado", + "kalibration_warmwasserbetrieb": "Calibra\u00e7\u00e3o DHW", + "kaskadenbetrieb": "Opera\u00e7\u00e3o em cascata", + "kombibetrieb": "Modo combinado", + "kombigerat": "Boiler combinado", + "kombigerat_mit_solareinbindung": "Boiler combinado com integra\u00e7\u00e3o solar", + "mindest_kombizeit": "Tempo m\u00ednimo combinado", + "nachlauf_heizkreispumpe": "Funcionamento da bomba do circuito de aquecimento", + "nachspulen": "P\u00f3s-lavagem", + "nur_heizgerat": "Apenas Boiler", + "parallelbetrieb": "Modo paralelo", + "partymodus": "Modo festa", + "perm_cooling": "PermCooling", + "permanent": "Permanente", + "permanentbetrieb": "Modo permanente", + "reduzierter_betrieb": "Modo limitado", + "rt_abschaltung": "Desligamento do RT", + "rt_frostschutz": "RT prote\u00e7\u00e3o contra congelamento", + "ruhekontakt": "Descansar contato", + "schornsteinfeger": "Teste de emiss\u00f5es", + "smart_grid": "SmartGrid", + "smart_home": "SmartHome", + "softstart": "In\u00edcio suave", + "solarbetrieb": "Modo solar", + "sparbetrieb": "Modo econ\u00f4mico", + "sparen": "Economia", + "spreizung_hoch": "dT muito largo", + "spreizung_kf": "Espalhe KF", "stabilisierung": "Estabiliza\u00e7\u00e3o", "standby": "Em espera", "start": "Iniciar", "storung": "Falha", + "taktsperre": "Anti-ciclo", + "telefonfernschalter": "Interruptor remoto do telefone", "test": "Teste", + "tpw": "TPW", "urlaubsmodus": "Modo de f\u00e9rias", - "ventilprufung": "Teste de v\u00e1lvula" + "ventilprufung": "Teste de v\u00e1lvula", + "vorspulen": "Enx\u00e1gue de entrada", + "warmwasser": "DHW", + "warmwasser_schnellstart": "in\u00edcio r\u00e1pido DHW", + "warmwasserbetrieb": "Modo DHW", + "warmwassernachlauf": "DHW funcionando", + "warmwasservorrang": "Prioridade de DHW", + "zunden": "Igni\u00e7\u00e3o" } } } \ No newline at end of file diff --git a/homeassistant/components/xbox/translations/pt-BR.json b/homeassistant/components/xbox/translations/pt-BR.json index 20d831afd2e4e6..7f788c1ebb83e7 100644 --- a/homeassistant/components/xbox/translations/pt-BR.json +++ b/homeassistant/components/xbox/translations/pt-BR.json @@ -7,6 +7,11 @@ }, "create_entry": { "default": "Autenticado com sucesso" + }, + "step": { + "pick_implementation": { + "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" + } } } } \ No newline at end of file diff --git a/homeassistant/components/xbox/translations/uk.json b/homeassistant/components/xbox/translations/uk.json index a1b3f8340fc889..1828c0737a6ff4 100644 --- a/homeassistant/components/xbox/translations/uk.json +++ b/homeassistant/components/xbox/translations/uk.json @@ -3,7 +3,7 @@ "abort": { "authorize_url_timeout": "\u041c\u0438\u043d\u0443\u0432 \u0447\u0430\u0441 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0456\u0457 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457.", "missing_configuration": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438.", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "create_entry": { "default": "\u0410\u0432\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044e \u0443\u0441\u043f\u0456\u0448\u043d\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e." diff --git a/homeassistant/components/xiaomi_aqara/translations/pt-BR.json b/homeassistant/components/xiaomi_aqara/translations/pt-BR.json index 4b579f14eaa6d9..5e188e545653d8 100644 --- a/homeassistant/components/xiaomi_aqara/translations/pt-BR.json +++ b/homeassistant/components/xiaomi_aqara/translations/pt-BR.json @@ -2,21 +2,41 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", - "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento" + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "not_xiaomi_aqara": "N\u00e3o \u00e9 um Xiaomi Aqara Gateway, o dispositivo descoberto n\u00e3o corresponde aos gateways conhecidos" }, "error": { - "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido" + "discovery_error": "Falha ao descobrir um Xiaomi Aqara Gateway, tente usar o IP do dispositivo que executa o HomeAssistant como interface", + "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido, veja https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem", + "invalid_interface": "Interface de rede inv\u00e1lida", + "invalid_key": "Chave de API inv\u00e1lida", + "invalid_mac": "Endere\u00e7o Mac inv\u00e1lido" }, + "flow_title": "{name}", "step": { "select": { "data": { "select_ip": "Endere\u00e7o IP" - } + }, + "description": "Execute a configura\u00e7\u00e3o novamente se quiser conectar gateways adicionais", + "title": "Selecione o Xiaomi Aqara Gateway que voc\u00ea deseja conectar" + }, + "settings": { + "data": { + "key": "A chave do seu gateway", + "name": "Nome do Gateway" + }, + "description": "A chave (senha) pode ser recuperada usando este tutorial: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Se a chave n\u00e3o for fornecida, apenas os sensores estar\u00e3o acess\u00edveis", + "title": "Xiaomi Aqara Gateway, configura\u00e7\u00f5es opcionais" }, "user": { "data": { - "host": "Endere\u00e7o IP" - } + "host": "Endere\u00e7o IP", + "interface": "A interface de rede a ser usada", + "mac": "Endere\u00e7o Mac (opcional)" + }, + "description": "Conecte-se ao seu Xiaomi Aqara Gateway, se os endere\u00e7os IP e MAC ficarem vazios, a descoberta autom\u00e1tica \u00e9 usada", + "title": "Gateway Xiaomi Aqara" } } } diff --git a/homeassistant/components/xiaomi_miio/translations/pt-BR.json b/homeassistant/components/xiaomi_miio/translations/pt-BR.json index d57568ed7fc311..9e966a541d574e 100644 --- a/homeassistant/components/xiaomi_miio/translations/pt-BR.json +++ b/homeassistant/components/xiaomi_miio/translations/pt-BR.json @@ -3,35 +3,96 @@ "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "incomplete_info": "Informa\u00e7\u00f5es incompletas para configurar o dispositivo, nenhum host ou token fornecido.", + "not_xiaomi_miio": "O dispositivo (ainda) n\u00e3o \u00e9 suportado pelo Xiaomi Miio.", "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { - "cannot_connect": "Falha ao conectar" + "cannot_connect": "Falha ao conectar", + "cloud_credentials_incomplete": "Credenciais da nuvem incompletas, preencha o nome de usu\u00e1rio, a senha e o pa\u00eds", + "cloud_login_error": "N\u00e3o foi poss\u00edvel fazer login no Xiaomi Miio Cloud, verifique as credenciais.", + "cloud_no_devices": "Nenhum dispositivo encontrado nesta conta de nuvem Xiaomi Miio.", + "no_device_selected": "Nenhum dispositivo selecionado, selecione um dispositivo.", + "unknown_device": "O modelo do dispositivo n\u00e3o \u00e9 conhecido, n\u00e3o \u00e9 poss\u00edvel configurar o dispositivo usando o fluxo de configura\u00e7\u00e3o.", + "wrong_token": "Erro de checksum, token errado" }, + "flow_title": "{name}", "step": { + "cloud": { + "data": { + "cloud_country": "Pa\u00eds do servidor em Cloud", + "cloud_password": "Senha da Cloud", + "cloud_username": "Usu\u00e1rio da Cloud", + "manual": "Configurar manualmente (n\u00e3o recomendado)" + }, + "description": "Fa\u00e7a login na Cloud Xiaomi Miio, consulte https://www.openhab.org/addons/bindings/miio/#country-servers para o servidor em cloud usar.", + "title": "Conecte-se a um dispositivo Xiaomi Miio ou Xiaomi Gateway" + }, + "connect": { + "data": { + "model": "Modelo do dispositivo" + }, + "description": "Selecione manualmente o modelo do dispositivo entre os modelos suportados.", + "title": "Conecte-se a um dispositivo Xiaomi Miio ou Xiaomi Gateway" + }, "device": { "data": { "host": "Endere\u00e7o IP", + "model": "Modelo do dispositivo (opcional)", + "name": "Nome do dispositivo", "token": "Token da API" }, - "description": "Voc\u00ea precisar\u00e1 do Token da API com 32 caracteres, consulte https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token para obter instru\u00e7\u00f5es. Observe que o Token da API \u00e9 diferente da chave usada pela integra\u00e7\u00e3o Xiaomi Aqara." + "description": "Voc\u00ea precisar\u00e1 do Token da API com 32 caracteres, consulte https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token para obter instru\u00e7\u00f5es. Observe que o Token da API \u00e9 diferente da chave usada pela integra\u00e7\u00e3o Xiaomi Aqara.", + "title": "Conecte-se a um dispositivo Xiaomi Miio ou Xiaomi Gateway" }, "gateway": { "data": { "host": "Endere\u00e7o IP", + "name": "Nome do Gateway", "token": "Token da API" }, - "description": "Voc\u00ea precisar\u00e1 do Token da API com 32 caracteres, consulte https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token para obter instru\u00e7\u00f5es. Observe que o Token da API \u00e9 diferente da chave usada pela integra\u00e7\u00e3o Xiaomi Aqara." + "description": "Voc\u00ea precisar\u00e1 do Token da API com 32 caracteres, consulte https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token para obter instru\u00e7\u00f5es. Observe que o Token da API \u00e9 diferente da chave usada pela integra\u00e7\u00e3o Xiaomi Aqara.", + "title": "Conecte-se a um Xiaomi Gateway" }, "manual": { "data": { "host": "Endere\u00e7o IP", "token": "Token da API" }, - "description": "Voc\u00ea precisar\u00e1 do Token da API com 32 caracteres, consulte https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token para obter instru\u00e7\u00f5es. Observe que o Token da API \u00e9 diferente da chave usada pela integra\u00e7\u00e3o Xiaomi Aqara." + "description": "Voc\u00ea precisar\u00e1 do Token da API com 32 caracteres, consulte https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token para obter instru\u00e7\u00f5es. Observe que o Token da API \u00e9 diferente da chave usada pela integra\u00e7\u00e3o Xiaomi Aqara.", + "title": "Conecte-se a um dispositivo Xiaomi Miio ou Xiaomi Gateway" }, "reauth_confirm": { + "description": "A integra\u00e7\u00e3o do Xiaomi Miio precisa autenticar novamente sua conta para atualizar os tokens ou adicionar credenciais de nuvem ausentes.", "title": "Reautenticar Integra\u00e7\u00e3o" + }, + "select": { + "data": { + "select_device": "Dispositivo Miio" + }, + "description": "Selecione o dispositivo Xiaomi Miio para configurar.", + "title": "Conecte-se a um dispositivo Xiaomi Miio ou Xiaomi Gateway" + }, + "user": { + "data": { + "gateway": "Conecte-se a um Xiaomi Gateway" + }, + "description": "Selecione a qual dispositivo voc\u00ea deseja se conectar.", + "title": "Xiaomi Miio" + } + } + }, + "options": { + "error": { + "cloud_credentials_incomplete": "Credenciais da cloud incompletas, preencha o nome de usu\u00e1rio, a senha e o pa\u00eds" + }, + "step": { + "init": { + "data": { + "cloud_subdevices": "Use a cloud para obter subdispositivos conectados" + }, + "description": "Especificar configura\u00e7\u00f5es opcionais", + "title": "Xiaomi Miio" } } } diff --git a/homeassistant/components/xiaomi_miio/translations/select.pt-BR.json b/homeassistant/components/xiaomi_miio/translations/select.pt-BR.json new file mode 100644 index 00000000000000..c4c1735bff3704 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/select.pt-BR.json @@ -0,0 +1,9 @@ +{ + "state": { + "xiaomi_miio__led_brightness": { + "bright": "Brilhante", + "dim": "Escurecido", + "off": "Desligado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yale_smart_alarm/translations/pt-BR.json b/homeassistant/components/yale_smart_alarm/translations/pt-BR.json index dae2a14938a57a..332c91ed447008 100644 --- a/homeassistant/components/yale_smart_alarm/translations/pt-BR.json +++ b/homeassistant/components/yale_smart_alarm/translations/pt-BR.json @@ -11,6 +11,7 @@ "step": { "reauth_confirm": { "data": { + "area_id": "ID da \u00e1rea", "name": "Nome", "password": "Senha", "username": "Usu\u00e1rio" @@ -18,6 +19,7 @@ }, "user": { "data": { + "area_id": "ID da \u00e1rea", "name": "Nome", "password": "Senha", "username": "Usu\u00e1rio" diff --git a/homeassistant/components/yamaha_musiccast/translations/pt-BR.json b/homeassistant/components/yamaha_musiccast/translations/pt-BR.json index 378809a995b0b2..eee52e2182b481 100644 --- a/homeassistant/components/yamaha_musiccast/translations/pt-BR.json +++ b/homeassistant/components/yamaha_musiccast/translations/pt-BR.json @@ -1,8 +1,13 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "yxc_control_url_missing": "A URL de controle n\u00e3o \u00e9 fornecida na descri\u00e7\u00e3o do ssdp." }, + "error": { + "no_musiccast_device": "Este dispositivo parece n\u00e3o ser um dispositivo MusicCast." + }, + "flow_title": "MusicCast: {name}", "step": { "confirm": { "description": "Deseja iniciar a configura\u00e7\u00e3o?" @@ -10,7 +15,8 @@ "user": { "data": { "host": "Nome do host" - } + }, + "description": "Configure o MusicCast para integrar com o Home Assistant." } } } diff --git a/homeassistant/components/yamaha_musiccast/translations/select.pt-BR.json b/homeassistant/components/yamaha_musiccast/translations/select.pt-BR.json index fa9634e5b9474f..dfbd33784b5fec 100644 --- a/homeassistant/components/yamaha_musiccast/translations/select.pt-BR.json +++ b/homeassistant/components/yamaha_musiccast/translations/select.pt-BR.json @@ -1,6 +1,27 @@ { "state": { + "yamaha_musiccast__dimmer": { + "auto": "Auto" + }, + "yamaha_musiccast__zone_equalizer_mode": { + "auto": "Auto", + "bypass": "Contornar", + "manual": "Manual" + }, + "yamaha_musiccast__zone_link_audio_delay": { + "audio_sync": "Sincroniza\u00e7\u00e3o de \u00e1udio", + "audio_sync_off": "Sincroniza\u00e7\u00e3o de \u00e1udio desligada", + "audio_sync_on": "Sincroniza\u00e7\u00e3o de \u00e1udio ligada", + "balanced": "Equilibrado", + "lip_sync": "Sincroniza\u00e7\u00e3o labial" + }, + "yamaha_musiccast__zone_link_audio_quality": { + "compressed": "Comprimido", + "uncompressed": "Descomprimido" + }, "yamaha_musiccast__zone_link_control": { + "speed": "Velocidade", + "stability": "Estabilidade", "standard": "Padr\u00e3o" }, "yamaha_musiccast__zone_sleep": { diff --git a/homeassistant/components/yeelight/translations/pt-BR.json b/homeassistant/components/yeelight/translations/pt-BR.json index 87327e1e44127c..2c54af41b250a9 100644 --- a/homeassistant/components/yeelight/translations/pt-BR.json +++ b/homeassistant/components/yeelight/translations/pt-BR.json @@ -2,16 +2,40 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" + "no_devices_found": "Nenhum dispositivo encontrado na rede" }, "error": { "cannot_connect": "Falha ao conectar" }, + "flow_title": "{model} {id} ({host})", "step": { + "discovery_confirm": { + "description": "Deseja configurar {model} ({host})?" + }, + "pick_device": { + "data": { + "device": "Dispositivo" + } + }, "user": { "data": { "host": "Nome do host" - } + }, + "description": "Se voc\u00ea deixar o host vazio, a descoberta ser\u00e1 usada para encontrar dispositivos." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "model": "Modelo", + "nightlight_switch": "Use o interruptor de luz noturna", + "save_on_change": "Salvar status na altera\u00e7\u00e3o", + "transition": "Tempo de transi\u00e7\u00e3o (ms)", + "use_music_mode": "Ativar o modo de m\u00fasica" + }, + "description": "Se voc\u00ea deixar o modelo vazio, ele ser\u00e1 detectado automaticamente." } } } diff --git a/homeassistant/components/zerproc/translations/uk.json b/homeassistant/components/zerproc/translations/uk.json index 292861e9129dbd..5c2489c2a18ab7 100644 --- a/homeassistant/components/zerproc/translations/uk.json +++ b/homeassistant/components/zerproc/translations/uk.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\u041f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456 \u0432 \u043c\u0435\u0440\u0435\u0436\u0456.", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "step": { "confirm": { diff --git a/homeassistant/components/zha/translations/pt-BR.json b/homeassistant/components/zha/translations/pt-BR.json index c07761309b8287..2e5aec0d1cce58 100644 --- a/homeassistant/components/zha/translations/pt-BR.json +++ b/homeassistant/components/zha/translations/pt-BR.json @@ -1,12 +1,18 @@ { "config": { "abort": { - "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + "not_zha_device": "Este dispositivo n\u00e3o \u00e9 um dispositivo ZHA", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", + "usb_probe_failed": "Falha ao sondar o dispositivo usb" }, "error": { "cannot_connect": "Falha ao conectar" }, + "flow_title": "{name}", "step": { + "confirm": { + "description": "Voc\u00ea deseja configurar {name}?" + }, "pick_radio": { "data": { "radio_type": "Tipo de r\u00e1dio" @@ -16,15 +22,37 @@ }, "port_config": { "data": { - "baudrate": "velocidade da porta" + "baudrate": "velocidade da porta", + "flow_control": "controle de fluxo de dados", + "path": "Caminho do dispositivo serial" }, + "description": "Digite configura\u00e7\u00f5es espec\u00edficas da porta", "title": "Configura\u00e7\u00f5es" }, "user": { + "data": { + "path": "Caminho do dispositivo serial" + }, + "description": "Selecione a porta serial para o r\u00e1dio Zigbee", "title": "ZHA" } } }, + "config_panel": { + "zha_alarm_options": { + "alarm_arm_requires_code": "C\u00f3digo necess\u00e1rio para a\u00e7\u00f5es de armamento", + "alarm_failed_tries": "O n\u00famero de entradas consecutivas de c\u00f3digo com falha para acionar um alarme", + "alarm_master_code": "C\u00f3digo mestre para o(s) painel(es) de controle de alarme", + "title": "Op\u00e7\u00f5es do painel de controle de alarme" + }, + "zha_options": { + "consider_unavailable_battery": "Considerar dispositivos alimentados por bateria indispon\u00edveis ap\u00f3s (segundos)", + "consider_unavailable_mains": "Considerar os dispositivos alimentados pela rede indispon\u00edveis ap\u00f3s (segundos)", + "default_light_transition": "Tempo de transi\u00e7\u00e3o de luz padr\u00e3o (segundos)", + "enable_identify_on_join": "Ativar o efeito de identifica\u00e7\u00e3o quando os dispositivos ingressarem na rede", + "title": "Op\u00e7\u00f5es globais" + } + }, "device_automation": { "action_type": { "squawk": "Squawk", @@ -63,6 +91,14 @@ "device_shaken": "Dispositivo sacudido", "device_slid": "Dispositivo deslizou \" {subtype} \"", "device_tilted": "Dispositivo inclinado", + "remote_button_alt_double_press": "Bot\u00e3o \" {subtype} \" clicado duas vezes (modo alternativo)", + "remote_button_alt_long_press": "Bot\u00e3o \" {subtype} \" pressionado continuamente (modo alternativo)", + "remote_button_alt_long_release": "Bot\u00e3o \" {subtype} \" liberado ap\u00f3s press\u00e3o longa (modo alternativo)", + "remote_button_alt_quadruple_press": "Bot\u00e3o \" {subtype} \" clicado quatro vezes (modo alternativo)", + "remote_button_alt_quintuple_press": "Bot\u00e3o \" {subtype} \" clicado qu\u00edntuplo (modo alternativo)", + "remote_button_alt_short_press": "Bot\u00e3o \" {subtype} \" pressionado (modo alternativo)", + "remote_button_alt_short_release": "Bot\u00e3o \" {subtype} \" liberado (modo alternativo)", + "remote_button_alt_triple_press": "Bot\u00e3o \" {subtype} \" clicado tr\u00eas vezes (modo alternativo)", "remote_button_double_press": "bot\u00e3o \" {subtype} \" clicado duas vezes", "remote_button_long_press": "Bot\u00e3o \" {subtype} \" pressionado continuamente", "remote_button_long_release": "Bot\u00e3o \" {subtype} \" liberado ap\u00f3s press\u00e3o longa", diff --git a/homeassistant/components/zha/translations/uk.json b/homeassistant/components/zha/translations/uk.json index 7bd62cf26e1dca..f7206911534efa 100644 --- a/homeassistant/components/zha/translations/uk.json +++ b/homeassistant/components/zha/translations/uk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "error": { "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" diff --git a/homeassistant/components/zodiac/translations/sensor.pt-BR.json b/homeassistant/components/zodiac/translations/sensor.pt-BR.json new file mode 100644 index 00000000000000..9662e6160e435a --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.pt-BR.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "Aqu\u00e1rio", + "aries": "\u00c1ries", + "cancer": "C\u00e2ncer", + "capricorn": "Capric\u00f3rnio", + "gemini": "G\u00eameos", + "leo": "Le\u00e3o", + "libra": "Libra", + "pisces": "Peixes", + "sagittarius": "Sagit\u00e1rio", + "scorpio": "Escorpi\u00e3o", + "taurus": "Touro", + "virgo": "Virgem" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/pt-BR.json b/homeassistant/components/zoneminder/translations/pt-BR.json index 318aba882af85d..a7fada70a8336f 100644 --- a/homeassistant/components/zoneminder/translations/pt-BR.json +++ b/homeassistant/components/zoneminder/translations/pt-BR.json @@ -1,21 +1,33 @@ { "config": { "abort": { + "auth_fail": "Nome de usu\u00e1rio ou senha est\u00e1 incorreta.", "cannot_connect": "Falha ao conectar", + "connection_error": "Falha ao conectar a um servidor ZoneMinder.", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, + "create_entry": { + "default": "Servidor ZoneMinder adicionado." + }, "error": { + "auth_fail": "Nome de usu\u00e1rio ou senha est\u00e1 incorreta.", "cannot_connect": "Falha ao conectar", + "connection_error": "Falha ao conectar a um servidor ZoneMinder.", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, + "flow_title": "ZoneMinder", "step": { "user": { "data": { + "host": "Host e Porta (ex 10.10.0.4:8010)", "password": "Senha", + "path": "Caminho ZM", + "path_zms": "Caminho ZMS", "ssl": "Usar um certificado SSL", "username": "Usu\u00e1rio", "verify_ssl": "Verifique o certificado SSL" - } + }, + "title": "Adicione o Servidor ZoneMinder." } } } diff --git a/homeassistant/components/zwave/translations/pt-BR.json b/homeassistant/components/zwave/translations/pt-BR.json index b4ad9acae3ae1e..079f1ab85932d5 100644 --- a/homeassistant/components/zwave/translations/pt-BR.json +++ b/homeassistant/components/zwave/translations/pt-BR.json @@ -25,8 +25,8 @@ "sleeping": "Dormindo" }, "query_stage": { - "dead": "Morto ({query_stage})", - "initializing": "Iniciando ( {query_stage} )" + "dead": "Morto", + "initializing": "Iniciando" } } } \ No newline at end of file diff --git a/homeassistant/components/zwave/translations/uk.json b/homeassistant/components/zwave/translations/uk.json index 696c0caccd2ba5..3c5b49681c84a5 100644 --- a/homeassistant/components/zwave/translations/uk.json +++ b/homeassistant/components/zwave/translations/uk.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\u0426\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", - "single_instance_allowed": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u041c\u043e\u0436\u043d\u0430 \u0434\u043e\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "error": { "option_error": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0438 Z-Wave. \u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u0448\u043b\u044f\u0445 \u0434\u043e USB-\u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e." diff --git a/homeassistant/components/zwave_js/translations/pt-BR.json b/homeassistant/components/zwave_js/translations/pt-BR.json index 7bc8288c2dd23a..b8fadd65089242 100644 --- a/homeassistant/components/zwave_js/translations/pt-BR.json +++ b/homeassistant/components/zwave_js/translations/pt-BR.json @@ -1,62 +1,147 @@ { "config": { "abort": { + "addon_get_discovery_info_failed": "Falhou em obter informa\u00e7\u00f5es de descoberta do add-on Z-Wave JS.", + "addon_info_failed": "Falha ao obter informa\u00e7\u00f5es do add-on Z-Wave JS.", + "addon_install_failed": "Falha ao instalar o add-on Z-Wave JS.", + "addon_set_config_failed": "Falha ao definir a configura\u00e7\u00e3o do Z-Wave JS.", + "addon_start_failed": "Falha ao iniciar o add-on Z-Wave JS.", "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", - "cannot_connect": "Falha ao conectar" + "cannot_connect": "Falha ao conectar", + "discovery_requires_supervisor": "A descoberta requer o supervisor.", + "not_zwave_device": "O dispositivo descoberto n\u00e3o \u00e9 um dispositivo Z-Wave." }, "error": { + "addon_start_failed": "Falha ao iniciar o add-on Z-Wave JS. Verifique a configura\u00e7\u00e3o.", "cannot_connect": "Falha ao conectar", + "invalid_ws_url": "URL de websocket inv\u00e1lido", "unknown": "Erro inesperado" }, + "flow_title": "{name}", + "progress": { + "install_addon": "Aguarde enquanto a instala\u00e7\u00e3o do add-on Z-Wave JS termina. Isso pode levar v\u00e1rios minutos.", + "start_addon": "Aguarde enquanto a inicializa\u00e7\u00e3o do add-on Z-Wave JS \u00e9 conclu\u00edda. Isso pode levar alguns segundos." + }, "step": { "configure_addon": { "data": { + "network_key": "Chave de rede", "s0_legacy_key": "Chave S0 (Legado)", "s2_access_control_key": "Chave de controle de acesso S2", "s2_authenticated_key": "Chave autenticada S2", "s2_unauthenticated_key": "Chave n\u00e3o autenticada S2", "usb_path": "Caminho do Dispositivo USB" - } + }, + "description": "O add-on gerar\u00e1 chaves de seguran\u00e7a se esses campos forem deixados em vazios.", + "title": "Digite a configura\u00e7\u00e3o do add-on Z-Wave JS" + }, + "hassio_confirm": { + "title": "Configure a integra\u00e7\u00e3o Z-Wave JS com o add-on Z-Wave JS" + }, + "install_addon": { + "title": "A instala\u00e7\u00e3o do add-on Z-Wave JS foi iniciada" }, "manual": { "data": { "url": "URL" } + }, + "on_supervisor": { + "data": { + "use_addon": "Use o add-on Z-Wave JS Supervisor" + }, + "description": "Deseja usar o add-on Z-Wave JS Supervisor?", + "title": "Selecione o m\u00e9todo de conex\u00e3o" + }, + "start_addon": { + "title": "O add-on Z-Wave JS est\u00e1 iniciando." + }, + "usb_confirm": { + "description": "Deseja configurar o {name} com o add-on Z-Wave JS?" } } }, "device_automation": { "action_type": { + "clear_lock_usercode": "Limpar o c\u00f3digo de usu\u00e1rio em {entity_name}", + "ping": "Ping dispositivo", + "refresh_value": "Atualize os valores para {entity_name}", + "reset_meter": "Redefinir medidores em {subtype}", "set_config_parameter": "Definir valor do par\u00e2metro de configura\u00e7\u00e3o {subtype}", "set_lock_usercode": "Defina um c\u00f3digo de usu\u00e1rio em {entity_name}", "set_value": "Definir valor de um valor de onda Z" + }, + "condition_type": { + "config_parameter": "Valor do par\u00e2metro de configura\u00e7\u00e3o {subtype}", + "node_status": "Status do n\u00f3", + "value": "Valor atual de um Z-Wave" + }, + "trigger_type": { + "event.notification.entry_control": "Enviou uma notifica\u00e7\u00e3o de controle de entrada", + "event.notification.notification": "Enviou uma notifica\u00e7\u00e3o", + "event.value_notification.basic": "Evento CC b\u00e1sico em {subtype}", + "event.value_notification.central_scene": "A\u00e7\u00e3o da cena central em {subtype}", + "event.value_notification.scene_activation": "Ativa\u00e7\u00e3o de cena em {subtype}", + "state.node_status": "Status do n\u00f3 alterado", + "zwave_js.value_updated.config_parameter": "Altera\u00e7\u00e3o de valor no par\u00e2metro de configura\u00e7\u00e3o {subtype}", + "zwave_js.value_updated.value": "Altera\u00e7\u00e3o de valor em um valor Z-Wave JS" } }, "options": { "abort": { + "addon_get_discovery_info_failed": "Falha em obter informa\u00e7\u00f5es sobre a descoberta do add-on Z-Wave JS.", + "addon_info_failed": "Falha ao obter informa\u00e7\u00f5es do add-on Z-Wave JS.", + "addon_install_failed": "Falha ao instalar o add-on Z-Wave JS.", + "addon_set_config_failed": "Falha ao definir a configura\u00e7\u00e3o do Z-Wave JS.", + "addon_start_failed": "Falha ao iniciar o complemento Z-Wave JS.", "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", - "cannot_connect": "Falha ao conectar" + "cannot_connect": "Falha ao conectar", + "different_device": "O dispositivo USB conectado n\u00e3o \u00e9 o mesmo configurado anteriormente para esta entrada de configura\u00e7\u00e3o. Em vez disso, crie uma nova entrada de configura\u00e7\u00e3o para o novo dispositivo." }, "error": { "cannot_connect": "Falha ao conectar", + "invalid_ws_url": "URL de websocket inv\u00e1lido", "unknown": "Erro inesperado" }, + "progress": { + "install_addon": "Aguarde enquanto a instala\u00e7\u00e3o do complemento Z-Wave JS termina. Isso pode levar v\u00e1rios minutos.", + "start_addon": "Aguarde enquanto a inicializa\u00e7\u00e3o do complemento Z-Wave JS \u00e9 conclu\u00edda. Isso pode levar alguns segundos." + }, "step": { "configure_addon": { "data": { + "emulate_hardware": "Emular hardware", + "log_level": "N\u00edvel de registro", + "network_key": "Chave de rede", "s0_legacy_key": "Chave S0 (Legado)", "s2_access_control_key": "Chave de controle de acesso S2", "s2_authenticated_key": "Chave autenticada S2", "s2_unauthenticated_key": "Chave n\u00e3o autenticada S2", "usb_path": "Caminho do Dispositivo USB" - } + }, + "description": "O complemento gerar\u00e1 chaves de seguran\u00e7a se esses campos forem deixados em branco.", + "title": "Digite a configura\u00e7\u00e3o do add-on Z-Wave JS" + }, + "install_addon": { + "title": "A instala\u00e7\u00e3o do add-on Z-Wave JS foi iniciada" }, "manual": { "data": { "url": "URL" } + }, + "on_supervisor": { + "data": { + "use_addon": "Use o add-on Z-Wave JS" + }, + "description": "Deseja usar o add-on Z-Wave JS?", + "title": "Selecione o m\u00e9todo de conex\u00e3o" + }, + "start_addon": { + "title": "O add-on Z-Wave JS est\u00e1 iniciando." } } - } + }, + "title": "Z-Wave JS" } \ No newline at end of file From c985ebb3a7c13e8779dc5f6c5ab67c8d9405a13f Mon Sep 17 00:00:00 2001 From: Teemu R Date: Mon, 31 Jan 2022 03:09:07 +0100 Subject: [PATCH 0108/1098] Bump python-kasa to 0.4.1 for tplink integration (#64123) Co-authored-by: J. Nick Koston --- homeassistant/components/tplink/__init__.py | 2 +- homeassistant/components/tplink/light.py | 9 +++++---- homeassistant/components/tplink/manifest.json | 2 +- homeassistant/components/tplink/switch.py | 9 +++++---- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/tplink/__init__.py b/homeassistant/components/tplink/__init__.py index c2ada4190b3146..e6b4c4aceab775 100644 --- a/homeassistant/components/tplink/__init__.py +++ b/homeassistant/components/tplink/__init__.py @@ -96,7 +96,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: device: SmartDevice = hass_data[entry.entry_id].device if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): hass_data.pop(entry.entry_id) - await device.protocol.close() + await device.protocol.close() # type: ignore return unload_ok diff --git a/homeassistant/components/tplink/light.py b/homeassistant/components/tplink/light.py index ad423e84fa5cba..6efabe537f71da 100644 --- a/homeassistant/components/tplink/light.py +++ b/homeassistant/components/tplink/light.py @@ -2,9 +2,9 @@ from __future__ import annotations import logging -from typing import Any +from typing import Any, cast -from kasa import SmartDevice +from kasa import SmartBulb from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -41,7 +41,7 @@ async def async_setup_entry( ) -> None: """Set up switches.""" coordinator: TPLinkDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] - device = coordinator.device + device = cast(SmartBulb, coordinator.device) if device.is_bulb or device.is_light_strip or device.is_dimmer: async_add_entities([TPLinkSmartBulb(device, coordinator)]) @@ -50,10 +50,11 @@ class TPLinkSmartBulb(CoordinatedTPLinkEntity, LightEntity): """Representation of a TPLink Smart Bulb.""" coordinator: TPLinkDataUpdateCoordinator + device: SmartBulb def __init__( self, - device: SmartDevice, + device: SmartBulb, coordinator: TPLinkDataUpdateCoordinator, ) -> None: """Initialize the switch.""" diff --git a/homeassistant/components/tplink/manifest.json b/homeassistant/components/tplink/manifest.json index 9464305cd169b5..4d4738c39f9f20 100644 --- a/homeassistant/components/tplink/manifest.json +++ b/homeassistant/components/tplink/manifest.json @@ -3,7 +3,7 @@ "name": "TP-Link Kasa Smart", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tplink", - "requirements": ["python-kasa==0.4.0"], + "requirements": ["python-kasa==0.4.1"], "codeowners": ["@rytilahti", "@thegardenmonkey"], "dependencies": ["network"], "quality_scale": "platinum", diff --git a/homeassistant/components/tplink/switch.py b/homeassistant/components/tplink/switch.py index eb1094b7675320..823d37267d6329 100644 --- a/homeassistant/components/tplink/switch.py +++ b/homeassistant/components/tplink/switch.py @@ -2,9 +2,9 @@ from __future__ import annotations import logging -from typing import Any +from typing import Any, cast -from kasa import SmartDevice +from kasa import SmartDevice, SmartPlug from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry @@ -27,7 +27,7 @@ async def async_setup_entry( ) -> None: """Set up switches.""" coordinator: TPLinkDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] - device = coordinator.device + device = cast(SmartPlug, coordinator.device) if not device.is_plug and not device.is_strip: return entities: list = [] @@ -48,11 +48,12 @@ class SmartPlugLedSwitch(CoordinatedTPLinkEntity, SwitchEntity): """Representation of switch for the LED of a TPLink Smart Plug.""" coordinator: TPLinkDataUpdateCoordinator + device: SmartPlug _attr_entity_category = EntityCategory.CONFIG def __init__( - self, device: SmartDevice, coordinator: TPLinkDataUpdateCoordinator + self, device: SmartPlug, coordinator: TPLinkDataUpdateCoordinator ) -> None: """Initialize the LED switch.""" super().__init__(device, coordinator) diff --git a/requirements_all.txt b/requirements_all.txt index 7a1cc0e98d34c1..a706ec5de0d7ff 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1939,7 +1939,7 @@ python-join-api==0.0.6 python-juicenet==1.0.2 # homeassistant.components.tplink -python-kasa==0.4.0 +python-kasa==0.4.1 # homeassistant.components.lirc # python-lirc==1.2.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5b6c25c2a7b372..0f63722cc65e6c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1203,7 +1203,7 @@ python-izone==1.2.3 python-juicenet==1.0.2 # homeassistant.components.tplink -python-kasa==0.4.0 +python-kasa==0.4.1 # homeassistant.components.xiaomi_miio python-miio==0.5.9.2 From 385f1f3dadeeaa333780f82642ab66dfe0fbe58c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 30 Jan 2022 22:09:56 -0600 Subject: [PATCH 0109/1098] Fix powerwall login retry when hitting rate limit (#65245) --- .../components/powerwall/__init__.py | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/powerwall/__init__.py b/homeassistant/components/powerwall/__init__.py index 5fb974fa5c7eed..fccd8979631677 100644 --- a/homeassistant/components/powerwall/__init__.py +++ b/homeassistant/components/powerwall/__init__.py @@ -5,6 +5,7 @@ import requests from tesla_powerwall import ( AccessDeniedError, + APIError, MissingAttributeError, Powerwall, PowerwallUnreachableError, @@ -131,7 +132,28 @@ def _recreate_powerwall_login(): power_wall = Powerwall(ip_address, http_session=http_session) runtime_data[POWERWALL_OBJECT] = power_wall runtime_data[POWERWALL_HTTP_SESSION] = http_session - power_wall.login("", password) + power_wall.login(password) + + async def _async_login_and_retry_update_data(): + """Retry the update after a failed login.""" + nonlocal login_failed_count + # If the session expired, recreate, relogin, and try again + _LOGGER.debug("Retrying login and updating data") + try: + await hass.async_add_executor_job(_recreate_powerwall_login) + data = await _async_update_powerwall_data(hass, entry, power_wall) + except AccessDeniedError as err: + login_failed_count += 1 + if login_failed_count == MAX_LOGIN_FAILURES: + raise ConfigEntryAuthFailed from err + raise UpdateFailed( + f"Login attempt {login_failed_count}/{MAX_LOGIN_FAILURES} failed, will retry: {err}" + ) from err + except APIError as err: + raise UpdateFailed(f"Updated failed due to {err}, will retry") from err + else: + login_failed_count = 0 + return data async def async_update_data(): """Fetch data from API endpoint.""" @@ -147,18 +169,9 @@ async def async_update_data(): except AccessDeniedError as err: if password is None: raise ConfigEntryAuthFailed from err - - # If the session expired, recreate, relogin, and try again - try: - await hass.async_add_executor_job(_recreate_powerwall_login) - return await _async_update_powerwall_data(hass, entry, power_wall) - except AccessDeniedError as ex: - login_failed_count += 1 - if login_failed_count == MAX_LOGIN_FAILURES: - raise ConfigEntryAuthFailed from ex - raise UpdateFailed( - f"Login attempt {login_failed_count}/{MAX_LOGIN_FAILURES} failed, will retry" - ) from ex + return await _async_login_and_retry_update_data() + except APIError as err: + raise UpdateFailed(f"Updated failed due to {err}, will retry") from err else: login_failed_count = 0 return data From d4370395e2b3a5761d5d1558824262cdc64fbbca Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Mon, 31 Jan 2022 05:12:44 +0100 Subject: [PATCH 0110/1098] Update xknx to 0.19.1 (#65275) --- homeassistant/components/knx/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/knx/conftest.py | 7 +------ tests/components/knx/test_config_flow.py | 10 +++++----- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index a97265ca244475..9e4557276ca1f7 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/knx", "requirements": [ - "xknx==0.19.0" + "xknx==0.19.1" ], "codeowners": [ "@Julius2342", diff --git a/requirements_all.txt b/requirements_all.txt index a706ec5de0d7ff..f7260088e0ea34 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2496,7 +2496,7 @@ xbox-webapi==2.0.11 xboxapi==2.0.1 # homeassistant.components.knx -xknx==0.19.0 +xknx==0.19.1 # homeassistant.components.bluesound # homeassistant.components.fritz diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0f63722cc65e6c..08eaf12cf0c635 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1530,7 +1530,7 @@ wolf_smartset==0.1.11 xbox-webapi==2.0.11 # homeassistant.components.knx -xknx==0.19.0 +xknx==0.19.1 # homeassistant.components.bluesound # homeassistant.components.fritz diff --git a/tests/components/knx/conftest.py b/tests/components/knx/conftest.py index cd15d77629cdf1..71a86f1e397fb0 100644 --- a/tests/components/knx/conftest.py +++ b/tests/components/knx/conftest.py @@ -13,7 +13,7 @@ from xknx.telegram.address import GroupAddress, IndividualAddress from xknx.telegram.apci import APCI, GroupValueRead, GroupValueResponse, GroupValueWrite -from homeassistant.components.knx import ConnectionSchema, KNXModule +from homeassistant.components.knx import ConnectionSchema from homeassistant.components.knx.const import ( CONF_KNX_AUTOMATIC, CONF_KNX_CONNECTION_TYPE, @@ -40,11 +40,6 @@ def __init__(self, hass: HomeAssistant, mock_config_entry: MockConfigEntry): # telegrams to an InternalGroupAddress won't be queued here self._outgoing_telegrams: asyncio.Queue = asyncio.Queue() - @property - def knx_module(self) -> KNXModule: - """Get the KNX module.""" - return self.hass.data[KNX_DOMAIN] - def assert_state(self, entity_id: str, state: str, **attributes) -> None: """Assert the state of an entity.""" test_state = self.hass.states.get(entity_id) diff --git a/tests/components/knx/test_config_flow.py b/tests/components/knx/test_config_flow.py index d6c2ec98b20d18..83b5a9988c77f0 100644 --- a/tests/components/knx/test_config_flow.py +++ b/tests/components/knx/test_config_flow.py @@ -39,11 +39,11 @@ def _gateway_descriptor( ) -> GatewayDescriptor: """Get mock gw descriptor.""" return GatewayDescriptor( - "Test", - ip, - port, - "eth0", - "127.0.0.1", + name="Test", + ip_addr=ip, + port=port, + local_interface="eth0", + local_ip="127.0.0.1", supports_routing=True, supports_tunnelling=True, supports_tunnelling_tcp=supports_tunnelling_tcp, From 40d871a1a36925e8d1d7eb0e62f57dae3a8d6c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Mon, 31 Jan 2022 06:15:32 +0200 Subject: [PATCH 0111/1098] Update readthedocs config (#65230) Co-authored-by: Martin Hjelmare --- .readthedocs.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index e8344e0a655c79..1a91abd9a996e2 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,10 +1,14 @@ # .readthedocs.yml +version: 2 + build: - image: latest + os: ubuntu-20.04 + tools: + python: "3.9" python: - version: 3.8 - setup_py_install: true - -requirements_file: requirements_docs.txt + install: + - method: setuptools + path: . + - requirements: requirements_docs.txt From 0673e96401a0c336c3a223b62218e020cd35bcca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Mon, 31 Jan 2022 06:15:50 +0200 Subject: [PATCH 0112/1098] Update black target version to 3.9+ (#65260) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c685c991e17f82..c1a08a3f0c3c4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools~=60.5", "wheel~=0.37.1"] build-backend = "setuptools.build_meta" [tool.black] -target-version = ["py38"] +target-version = ["py39", "py310"] exclude = 'generated' [tool.isort] From 27d5be71dd2e86cc88706611b0da852d491eecd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Mon, 31 Jan 2022 06:16:14 +0200 Subject: [PATCH 0113/1098] Update python-typing-update config to py39plus (#65261) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 952729caf09afd..72716f15dfd761 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -78,7 +78,7 @@ repos: - id: python-typing-update stages: [manual] args: - - --py38-plus + - --py39-plus - --force - --keep-updates files: ^(homeassistant|tests|script)/.+\.py$ From 73bd8db273fd2ad745674f0ec6be1b37f5a5915b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 30 Jan 2022 22:17:19 -0600 Subject: [PATCH 0114/1098] Fix flux_led not generating unique ids when discovery fails (#65250) --- homeassistant/components/flux_led/__init__.py | 29 +++++++- homeassistant/components/flux_led/button.py | 4 +- homeassistant/components/flux_led/entity.py | 51 ++++++++------ homeassistant/components/flux_led/light.py | 6 +- homeassistant/components/flux_led/number.py | 18 ++--- homeassistant/components/flux_led/select.py | 22 +++--- homeassistant/components/flux_led/sensor.py | 2 +- homeassistant/components/flux_led/switch.py | 10 +-- tests/components/flux_led/test_init.py | 51 +++++++++++++- tests/components/flux_led/test_light.py | 6 +- tests/components/flux_led/test_number.py | 19 +++++- tests/components/flux_led/test_select.py | 42 ++++++++++++ tests/components/flux_led/test_switch.py | 68 ++++++++++++++++++- 13 files changed, 268 insertions(+), 60 deletions(-) diff --git a/homeassistant/components/flux_led/__init__.py b/homeassistant/components/flux_led/__init__.py index efab893be42fd9..ff1962aed1bd45 100644 --- a/homeassistant/components/flux_led/__init__.py +++ b/homeassistant/components/flux_led/__init__.py @@ -14,7 +14,7 @@ from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STARTED, Platform from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers import device_registry as dr +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import ( async_track_time_change, @@ -88,6 +88,31 @@ async def _async_discovery(*_: Any) -> None: return True +async def _async_migrate_unique_ids(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Migrate entities when the mac address gets discovered.""" + unique_id = entry.unique_id + if not unique_id: + return + entry_id = entry.entry_id + + @callback + def _async_migrator(entity_entry: er.RegistryEntry) -> dict[str, Any] | None: + # Old format {entry_id}..... + # New format {unique_id}.... + entity_unique_id = entity_entry.unique_id + if not entity_unique_id.startswith(entry_id): + return None + new_unique_id = f"{unique_id}{entity_unique_id[len(entry_id):]}" + _LOGGER.info( + "Migrating unique_id from [%s] to [%s]", + entity_unique_id, + new_unique_id, + ) + return {"new_unique_id": new_unique_id} + + await er.async_migrate_entries(hass, entry.entry_id, _async_migrator) + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Flux LED/MagicLight from a config entry.""" host = entry.data[CONF_HOST] @@ -135,6 +160,8 @@ def _async_state_changed(*_: Any) -> None: # is either missing or we have verified it matches async_update_entry_from_discovery(hass, entry, discovery, device.model_num) + await _async_migrate_unique_ids(hass, entry) + coordinator = FluxLedUpdateCoordinator(hass, device, entry) hass.data[DOMAIN][entry.entry_id] = coordinator platforms = PLATFORMS_BY_TYPE[device.device_type] diff --git a/homeassistant/components/flux_led/button.py b/homeassistant/components/flux_led/button.py index 3f74090f075b4d..fcd4ecc3adcafa 100644 --- a/homeassistant/components/flux_led/button.py +++ b/homeassistant/components/flux_led/button.py @@ -64,8 +64,8 @@ def __init__( self.entity_description = description super().__init__(device, entry) self._attr_name = f"{entry.data[CONF_NAME]} {description.name}" - if entry.unique_id: - self._attr_unique_id = f"{entry.unique_id}_{description.key}" + base_unique_id = entry.unique_id or entry.entry_id + self._attr_unique_id = f"{base_unique_id}_{description.key}" async def async_press(self) -> None: """Send out a command.""" diff --git a/homeassistant/components/flux_led/entity.py b/homeassistant/components/flux_led/entity.py index c06070002d4323..5946ab817de20a 100644 --- a/homeassistant/components/flux_led/entity.py +++ b/homeassistant/components/flux_led/entity.py @@ -7,19 +7,28 @@ from flux_led.aiodevice import AIOWifiLedBulb from homeassistant import config_entries -from homeassistant.const import CONF_NAME +from homeassistant.const import ( + ATTR_CONNECTIONS, + ATTR_HW_VERSION, + ATTR_IDENTIFIERS, + ATTR_MANUFACTURER, + ATTR_MODEL, + ATTR_NAME, + ATTR_SW_VERSION, + CONF_NAME, +) from homeassistant.core import callback from homeassistant.helpers import device_registry as dr from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import CONF_MINOR_VERSION, CONF_MODEL, SIGNAL_STATE_UPDATED +from .const import CONF_MINOR_VERSION, CONF_MODEL, DOMAIN, SIGNAL_STATE_UPDATED from .coordinator import FluxLedUpdateCoordinator def _async_device_info( - unique_id: str, device: AIOWifiLedBulb, entry: config_entries.ConfigEntry + device: AIOWifiLedBulb, entry: config_entries.ConfigEntry ) -> DeviceInfo: version_num = device.version_num if minor_version := entry.data.get(CONF_MINOR_VERSION): @@ -27,14 +36,18 @@ def _async_device_info( sw_version_str = f"{sw_version:0.2f}" else: sw_version_str = str(device.version_num) - return DeviceInfo( - connections={(dr.CONNECTION_NETWORK_MAC, unique_id)}, - manufacturer="Zengge", - model=device.model, - name=entry.data[CONF_NAME], - sw_version=sw_version_str, - hw_version=entry.data.get(CONF_MODEL), - ) + device_info: DeviceInfo = { + ATTR_IDENTIFIERS: {(DOMAIN, entry.entry_id)}, + ATTR_MANUFACTURER: "Zengge", + ATTR_MODEL: device.model, + ATTR_NAME: entry.data[CONF_NAME], + ATTR_SW_VERSION: sw_version_str, + } + if hw_model := entry.data.get(CONF_MODEL): + device_info[ATTR_HW_VERSION] = hw_model + if entry.unique_id: + device_info[ATTR_CONNECTIONS] = {(dr.CONNECTION_NETWORK_MAC, entry.unique_id)} + return device_info class FluxBaseEntity(Entity): @@ -50,10 +63,7 @@ def __init__( """Initialize the light.""" self._device: AIOWifiLedBulb = device self.entry = entry - if entry.unique_id: - self._attr_device_info = _async_device_info( - entry.unique_id, self._device, entry - ) + self._attr_device_info = _async_device_info(self._device, entry) class FluxEntity(CoordinatorEntity): @@ -64,7 +74,7 @@ class FluxEntity(CoordinatorEntity): def __init__( self, coordinator: FluxLedUpdateCoordinator, - unique_id: str | None, + base_unique_id: str, name: str, key: str | None, ) -> None: @@ -74,13 +84,10 @@ def __init__( self._responding = True self._attr_name = name if key: - self._attr_unique_id = f"{unique_id}_{key}" + self._attr_unique_id = f"{base_unique_id}_{key}" else: - self._attr_unique_id = unique_id - if unique_id: - self._attr_device_info = _async_device_info( - unique_id, self._device, coordinator.entry - ) + self._attr_unique_id = base_unique_id + self._attr_device_info = _async_device_info(self._device, coordinator.entry) async def _async_ensure_device_on(self) -> None: """Turn the device on if it needs to be turned on before a command.""" diff --git a/homeassistant/components/flux_led/light.py b/homeassistant/components/flux_led/light.py index 85b74616b32335..4534c45e22824b 100644 --- a/homeassistant/components/flux_led/light.py +++ b/homeassistant/components/flux_led/light.py @@ -177,7 +177,7 @@ async def async_setup_entry( [ FluxLight( coordinator, - entry.unique_id, + entry.unique_id or entry.entry_id, entry.data[CONF_NAME], list(custom_effect_colors), options.get(CONF_CUSTOM_EFFECT_SPEED_PCT, DEFAULT_EFFECT_SPEED), @@ -195,14 +195,14 @@ class FluxLight(FluxOnOffEntity, CoordinatorEntity, LightEntity): def __init__( self, coordinator: FluxLedUpdateCoordinator, - unique_id: str | None, + base_unique_id: str, name: str, custom_effect_colors: list[tuple[int, int, int]], custom_effect_speed_pct: int, custom_effect_transition: str, ) -> None: """Initialize the light.""" - super().__init__(coordinator, unique_id, name, None) + super().__init__(coordinator, base_unique_id, name, None) self._attr_min_mireds = color_temperature_kelvin_to_mired(self._device.max_temp) self._attr_max_mireds = color_temperature_kelvin_to_mired(self._device.min_temp) self._attr_supported_color_modes = _hass_color_modes(self._device) diff --git a/homeassistant/components/flux_led/number.py b/homeassistant/components/flux_led/number.py index 7c607219901d9f..d7fad9cf0e6f4a 100644 --- a/homeassistant/components/flux_led/number.py +++ b/homeassistant/components/flux_led/number.py @@ -51,26 +51,28 @@ async def async_setup_entry( | FluxMusicSegmentsNumber ] = [] name = entry.data[CONF_NAME] - unique_id = entry.unique_id + base_unique_id = entry.unique_id or entry.entry_id if device.pixels_per_segment is not None: entities.append( FluxPixelsPerSegmentNumber( coordinator, - unique_id, + base_unique_id, f"{name} Pixels Per Segment", "pixels_per_segment", ) ) if device.segments is not None: entities.append( - FluxSegmentsNumber(coordinator, unique_id, f"{name} Segments", "segments") + FluxSegmentsNumber( + coordinator, base_unique_id, f"{name} Segments", "segments" + ) ) if device.music_pixels_per_segment is not None: entities.append( FluxMusicPixelsPerSegmentNumber( coordinator, - unique_id, + base_unique_id, f"{name} Music Pixels Per Segment", "music_pixels_per_segment", ) @@ -78,12 +80,12 @@ async def async_setup_entry( if device.music_segments is not None: entities.append( FluxMusicSegmentsNumber( - coordinator, unique_id, f"{name} Music Segments", "music_segments" + coordinator, base_unique_id, f"{name} Music Segments", "music_segments" ) ) if device.effect_list and device.effect_list != [EFFECT_RANDOM]: entities.append( - FluxSpeedNumber(coordinator, unique_id, f"{name} Effect Speed", None) + FluxSpeedNumber(coordinator, base_unique_id, f"{name} Effect Speed", None) ) if entities: @@ -131,12 +133,12 @@ class FluxConfigNumber(FluxEntity, CoordinatorEntity, NumberEntity): def __init__( self, coordinator: FluxLedUpdateCoordinator, - unique_id: str | None, + base_unique_id: str, name: str, key: str | None, ) -> None: """Initialize the flux number.""" - super().__init__(coordinator, unique_id, name, key) + super().__init__(coordinator, base_unique_id, name, key) self._debouncer: Debouncer | None = None self._pending_value: int | None = None diff --git a/homeassistant/components/flux_led/select.py b/homeassistant/components/flux_led/select.py index 701465da03691b..3b78baa782b77f 100644 --- a/homeassistant/components/flux_led/select.py +++ b/homeassistant/components/flux_led/select.py @@ -54,28 +54,28 @@ async def async_setup_entry( | FluxWhiteChannelSelect ] = [] name = entry.data[CONF_NAME] - unique_id = entry.unique_id + base_unique_id = entry.unique_id or entry.entry_id if device.device_type == DeviceType.Switch: entities.append(FluxPowerStateSelect(coordinator.device, entry)) if device.operating_modes: entities.append( FluxOperatingModesSelect( - coordinator, unique_id, f"{name} Operating Mode", "operating_mode" + coordinator, base_unique_id, f"{name} Operating Mode", "operating_mode" ) ) if device.wirings: entities.append( - FluxWiringsSelect(coordinator, unique_id, f"{name} Wiring", "wiring") + FluxWiringsSelect(coordinator, base_unique_id, f"{name} Wiring", "wiring") ) if device.ic_types: entities.append( - FluxICTypeSelect(coordinator, unique_id, f"{name} IC Type", "ic_type") + FluxICTypeSelect(coordinator, base_unique_id, f"{name} IC Type", "ic_type") ) if device.remote_config: entities.append( FluxRemoteConfigSelect( - coordinator, unique_id, f"{name} Remote Config", "remote_config" + coordinator, base_unique_id, f"{name} Remote Config", "remote_config" ) ) if FLUX_COLOR_MODE_RGBW in device.color_modes: @@ -111,8 +111,8 @@ def __init__( """Initialize the power state select.""" super().__init__(device, entry) self._attr_name = f"{entry.data[CONF_NAME]} Power Restored" - if entry.unique_id: - self._attr_unique_id = f"{entry.unique_id}_power_restored" + base_unique_id = entry.unique_id or entry.entry_id + self._attr_unique_id = f"{base_unique_id}_power_restored" self._async_set_current_option_from_device() @callback @@ -201,12 +201,12 @@ class FluxRemoteConfigSelect(FluxConfigSelect): def __init__( self, coordinator: FluxLedUpdateCoordinator, - unique_id: str | None, + base_unique_id: str, name: str, key: str, ) -> None: """Initialize the remote config type select.""" - super().__init__(coordinator, unique_id, name, key) + super().__init__(coordinator, base_unique_id, name, key) assert self._device.remote_config is not None self._name_to_state = { _human_readable_option(option.name): option for option in RemoteConfig @@ -238,8 +238,8 @@ def __init__( """Initialize the white channel select.""" super().__init__(device, entry) self._attr_name = f"{entry.data[CONF_NAME]} White Channel" - if entry.unique_id: - self._attr_unique_id = f"{entry.unique_id}_white_channel" + base_unique_id = entry.unique_id or entry.entry_id + self._attr_unique_id = f"{base_unique_id}_white_channel" @property def current_option(self) -> str | None: diff --git a/homeassistant/components/flux_led/sensor.py b/homeassistant/components/flux_led/sensor.py index 6d67ced1fe2238..18d1aac55067a5 100644 --- a/homeassistant/components/flux_led/sensor.py +++ b/homeassistant/components/flux_led/sensor.py @@ -25,7 +25,7 @@ async def async_setup_entry( [ FluxPairedRemotes( coordinator, - entry.unique_id, + entry.unique_id or entry.entry_id, f"{entry.data[CONF_NAME]} Paired Remotes", "paired_remotes", ) diff --git a/homeassistant/components/flux_led/switch.py b/homeassistant/components/flux_led/switch.py index f8de86d3340335..ee004fc2250ffd 100644 --- a/homeassistant/components/flux_led/switch.py +++ b/homeassistant/components/flux_led/switch.py @@ -34,18 +34,18 @@ async def async_setup_entry( """Set up the Flux lights.""" coordinator: FluxLedUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] entities: list[FluxSwitch | FluxRemoteAccessSwitch | FluxMusicSwitch] = [] - unique_id = entry.unique_id + base_unique_id = entry.unique_id or entry.entry_id name = entry.data[CONF_NAME] if coordinator.device.device_type == DeviceType.Switch: - entities.append(FluxSwitch(coordinator, unique_id, name, None)) + entities.append(FluxSwitch(coordinator, base_unique_id, name, None)) if entry.data.get(CONF_REMOTE_ACCESS_HOST): entities.append(FluxRemoteAccessSwitch(coordinator.device, entry)) if coordinator.device.microphone: entities.append( - FluxMusicSwitch(coordinator, unique_id, f"{name} Music", "music") + FluxMusicSwitch(coordinator, base_unique_id, f"{name} Music", "music") ) if entities: @@ -74,8 +74,8 @@ def __init__( """Initialize the light.""" super().__init__(device, entry) self._attr_name = f"{entry.data[CONF_NAME]} Remote Access" - if entry.unique_id: - self._attr_unique_id = f"{entry.unique_id}_remote_access" + base_unique_id = entry.unique_id or entry.entry_id + self._attr_unique_id = f"{base_unique_id}_remote_access" async def async_turn_on(self, **kwargs: Any) -> None: """Turn the remote access on.""" diff --git a/tests/components/flux_led/test_init.py b/tests/components/flux_led/test_init.py index 7981f2cef11ce7..de655c2e6adb72 100644 --- a/tests/components/flux_led/test_init.py +++ b/tests/components/flux_led/test_init.py @@ -7,10 +7,16 @@ import pytest from homeassistant.components import flux_led -from homeassistant.components.flux_led.const import DOMAIN +from homeassistant.components.flux_led.const import ( + CONF_REMOTE_ACCESS_ENABLED, + CONF_REMOTE_ACCESS_HOST, + CONF_REMOTE_ACCESS_PORT, + DOMAIN, +) from homeassistant.config_entries import ConfigEntryState from homeassistant.const import CONF_HOST, CONF_NAME, EVENT_HOMEASSISTANT_STARTED from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow @@ -156,3 +162,46 @@ async def test_time_sync_startup_and_next_day(hass: HomeAssistant) -> None: async_fire_time_changed(hass, utcnow() + timedelta(hours=24)) await hass.async_block_till_done() assert len(bulb.async_set_time.mock_calls) == 2 + + +async def test_unique_id_migrate_when_mac_discovered(hass: HomeAssistant) -> None: + """Test unique id migrated when mac discovered.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_REMOTE_ACCESS_HOST: "any", + CONF_REMOTE_ACCESS_ENABLED: True, + CONF_REMOTE_ACCESS_PORT: 1234, + CONF_HOST: IP_ADDRESS, + CONF_NAME: DEFAULT_ENTRY_TITLE, + }, + ) + config_entry.add_to_hass(hass) + bulb = _mocked_bulb() + with _patch_discovery(no_device=True), _patch_wifibulb(device=bulb): + await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) + await hass.async_block_till_done() + + assert not config_entry.unique_id + entity_registry = er.async_get(hass) + assert ( + entity_registry.async_get("light.bulb_rgbcw_ddeeff").unique_id + == config_entry.entry_id + ) + assert ( + entity_registry.async_get("switch.bulb_rgbcw_ddeeff_remote_access").unique_id + == f"{config_entry.entry_id}_remote_access" + ) + + with _patch_discovery(), _patch_wifibulb(device=bulb): + await hass.config_entries.async_reload(config_entry.entry_id) + await hass.async_block_till_done() + + assert ( + entity_registry.async_get("light.bulb_rgbcw_ddeeff").unique_id + == config_entry.unique_id + ) + assert ( + entity_registry.async_get("switch.bulb_rgbcw_ddeeff_remote_access").unique_id + == f"{config_entry.unique_id}_remote_access" + ) diff --git a/tests/components/flux_led/test_light.py b/tests/components/flux_led/test_light.py index 83a76311e8a3db..67603544c5d1d5 100644 --- a/tests/components/flux_led/test_light.py +++ b/tests/components/flux_led/test_light.py @@ -137,8 +137,8 @@ async def test_light_goes_unavailable_and_recovers(hass: HomeAssistant) -> None: assert state.state == STATE_ON -async def test_light_no_unique_id(hass: HomeAssistant) -> None: - """Test a light without a unique id.""" +async def test_light_mac_address_not_found(hass: HomeAssistant) -> None: + """Test a light when we cannot discover the mac address.""" config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: IP_ADDRESS, CONF_NAME: DEFAULT_ENTRY_TITLE} ) @@ -150,7 +150,7 @@ async def test_light_no_unique_id(hass: HomeAssistant) -> None: entity_id = "light.bulb_rgbcw_ddeeff" entity_registry = er.async_get(hass) - assert entity_registry.async_get(entity_id) is None + assert entity_registry.async_get(entity_id).unique_id == config_entry.entry_id state = hass.states.get(entity_id) assert state.state == STATE_ON diff --git a/tests/components/flux_led/test_number.py b/tests/components/flux_led/test_number.py index a4b23f47fcc759..d0d71cacbe19ed 100644 --- a/tests/components/flux_led/test_number.py +++ b/tests/components/flux_led/test_number.py @@ -41,7 +41,7 @@ from tests.common import MockConfigEntry -async def test_number_unique_id(hass: HomeAssistant) -> None: +async def test_effects_speed_unique_id(hass: HomeAssistant) -> None: """Test a number unique id.""" config_entry = MockConfigEntry( domain=DOMAIN, @@ -59,6 +59,23 @@ async def test_number_unique_id(hass: HomeAssistant) -> None: assert entity_registry.async_get(entity_id).unique_id == MAC_ADDRESS +async def test_effects_speed_unique_id_no_discovery(hass: HomeAssistant) -> None: + """Test a number unique id.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: IP_ADDRESS, CONF_NAME: DEFAULT_ENTRY_TITLE}, + ) + config_entry.add_to_hass(hass) + bulb = _mocked_bulb() + with _patch_discovery(no_device=True), _patch_wifibulb(device=bulb): + await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) + await hass.async_block_till_done() + + entity_id = "number.bulb_rgbcw_ddeeff_effect_speed" + entity_registry = er.async_get(hass) + assert entity_registry.async_get(entity_id).unique_id == config_entry.entry_id + + async def test_rgb_light_effect_speed(hass: HomeAssistant) -> None: """Test an rgb light with an effect.""" config_entry = MockConfigEntry( diff --git a/tests/components/flux_led/test_select.py b/tests/components/flux_led/test_select.py index 6df276e50119ea..b2a88b00fe06b6 100644 --- a/tests/components/flux_led/test_select.py +++ b/tests/components/flux_led/test_select.py @@ -14,6 +14,7 @@ from homeassistant.components.select import DOMAIN as SELECT_DOMAIN from homeassistant.const import ATTR_ENTITY_ID, ATTR_OPTION, CONF_HOST, CONF_NAME from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from . import ( @@ -67,6 +68,47 @@ async def test_switch_power_restore_state(hass: HomeAssistant) -> None: ) +async def test_power_restored_unique_id(hass: HomeAssistant) -> None: + """Test a select unique id.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: IP_ADDRESS, CONF_NAME: DEFAULT_ENTRY_TITLE}, + unique_id=MAC_ADDRESS, + ) + config_entry.add_to_hass(hass) + switch = _mocked_switch() + with _patch_discovery(), _patch_wifibulb(device=switch): + await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) + await hass.async_block_till_done() + + entity_id = "select.bulb_rgbcw_ddeeff_power_restored" + entity_registry = er.async_get(hass) + assert ( + entity_registry.async_get(entity_id).unique_id + == f"{MAC_ADDRESS}_power_restored" + ) + + +async def test_power_restored_unique_id_no_discovery(hass: HomeAssistant) -> None: + """Test a select unique id.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: IP_ADDRESS, CONF_NAME: DEFAULT_ENTRY_TITLE}, + ) + config_entry.add_to_hass(hass) + switch = _mocked_switch() + with _patch_discovery(no_device=True), _patch_wifibulb(device=switch): + await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) + await hass.async_block_till_done() + + entity_id = "select.bulb_rgbcw_ddeeff_power_restored" + entity_registry = er.async_get(hass) + assert ( + entity_registry.async_get(entity_id).unique_id + == f"{config_entry.entry_id}_power_restored" + ) + + async def test_select_addressable_strip_config(hass: HomeAssistant) -> None: """Test selecting addressable strip configs.""" config_entry = MockConfigEntry( diff --git a/tests/components/flux_led/test_switch.py b/tests/components/flux_led/test_switch.py index ce2855a53ed6cf..cb0034f8d36b19 100644 --- a/tests/components/flux_led/test_switch.py +++ b/tests/components/flux_led/test_switch.py @@ -2,7 +2,12 @@ from flux_led.const import MODE_MUSIC from homeassistant.components import flux_led -from homeassistant.components.flux_led.const import CONF_REMOTE_ACCESS_ENABLED, DOMAIN +from homeassistant.components.flux_led.const import ( + CONF_REMOTE_ACCESS_ENABLED, + CONF_REMOTE_ACCESS_HOST, + CONF_REMOTE_ACCESS_PORT, + DOMAIN, +) from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, @@ -12,6 +17,7 @@ STATE_ON, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from . import ( @@ -65,11 +71,69 @@ async def test_switch_on_off(hass: HomeAssistant) -> None: assert hass.states.get(entity_id).state == STATE_ON +async def test_remote_access_unique_id(hass: HomeAssistant) -> None: + """Test a remote access switch unique id.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_REMOTE_ACCESS_HOST: "any", + CONF_REMOTE_ACCESS_ENABLED: True, + CONF_REMOTE_ACCESS_PORT: 1234, + CONF_HOST: IP_ADDRESS, + CONF_NAME: DEFAULT_ENTRY_TITLE, + }, + unique_id=MAC_ADDRESS, + ) + config_entry.add_to_hass(hass) + bulb = _mocked_bulb() + with _patch_discovery(), _patch_wifibulb(device=bulb): + await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) + await hass.async_block_till_done() + + entity_id = "switch.bulb_rgbcw_ddeeff_remote_access" + entity_registry = er.async_get(hass) + assert ( + entity_registry.async_get(entity_id).unique_id == f"{MAC_ADDRESS}_remote_access" + ) + + +async def test_effects_speed_unique_id_no_discovery(hass: HomeAssistant) -> None: + """Test a remote access switch unique id when discovery fails.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_REMOTE_ACCESS_HOST: "any", + CONF_REMOTE_ACCESS_ENABLED: True, + CONF_REMOTE_ACCESS_PORT: 1234, + CONF_HOST: IP_ADDRESS, + CONF_NAME: DEFAULT_ENTRY_TITLE, + }, + ) + config_entry.add_to_hass(hass) + bulb = _mocked_bulb() + with _patch_discovery(no_device=True), _patch_wifibulb(device=bulb): + await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) + await hass.async_block_till_done() + + entity_id = "switch.bulb_rgbcw_ddeeff_remote_access" + entity_registry = er.async_get(hass) + assert ( + entity_registry.async_get(entity_id).unique_id + == f"{config_entry.entry_id}_remote_access" + ) + + async def test_remote_access_on_off(hass: HomeAssistant) -> None: """Test enable/disable remote access.""" config_entry = MockConfigEntry( domain=DOMAIN, - data={CONF_HOST: IP_ADDRESS, CONF_NAME: DEFAULT_ENTRY_TITLE}, + data={ + CONF_REMOTE_ACCESS_HOST: "any", + CONF_REMOTE_ACCESS_ENABLED: True, + CONF_REMOTE_ACCESS_PORT: 1234, + CONF_HOST: IP_ADDRESS, + CONF_NAME: DEFAULT_ENTRY_TITLE, + }, unique_id=MAC_ADDRESS, ) config_entry.add_to_hass(hass) From 6458e45ef0700faa783a477bd16da8680cef5970 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 30 Jan 2022 22:19:52 -0600 Subject: [PATCH 0115/1098] Simplify whois value_fn (#65265) --- homeassistant/components/whois/sensor.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/whois/sensor.py b/homeassistant/components/whois/sensor.py index 0459651e693c26..6df920f385c3b9 100644 --- a/homeassistant/components/whois/sensor.py +++ b/homeassistant/components/whois/sensor.py @@ -80,13 +80,6 @@ def _ensure_timezone(timestamp: datetime | None) -> datetime | None: return timestamp -def _fetch_attr_if_exists(domain: Domain, attr: str) -> str | None: - """Fetch an attribute if it exists and is truthy or return None.""" - if hasattr(domain, attr) and (value := getattr(domain, attr)): - return cast(str, value) - return None - - SENSORS: tuple[WhoisSensorEntityDescription, ...] = ( WhoisSensorEntityDescription( key="admin", @@ -94,7 +87,7 @@ def _fetch_attr_if_exists(domain: Domain, attr: str) -> str | None: icon="mdi:account-star", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, - value_fn=lambda domain: _fetch_attr_if_exists(domain, "admin"), + value_fn=lambda domain: getattr(domain, "admin", None), ), WhoisSensorEntityDescription( key="creation_date", @@ -130,7 +123,7 @@ def _fetch_attr_if_exists(domain: Domain, attr: str) -> str | None: icon="mdi:account", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, - value_fn=lambda domain: _fetch_attr_if_exists(domain, "owner"), + value_fn=lambda domain: getattr(domain, "owner", None), ), WhoisSensorEntityDescription( key="registrant", @@ -138,7 +131,7 @@ def _fetch_attr_if_exists(domain: Domain, attr: str) -> str | None: icon="mdi:account-edit", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, - value_fn=lambda domain: _fetch_attr_if_exists(domain, "registrant"), + value_fn=lambda domain: getattr(domain, "registrant", None), ), WhoisSensorEntityDescription( key="registrar", @@ -154,7 +147,7 @@ def _fetch_attr_if_exists(domain: Domain, attr: str) -> str | None: icon="mdi:store", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, - value_fn=lambda domain: _fetch_attr_if_exists(domain, "reseller"), + value_fn=lambda domain: getattr(domain, "reseller", None), ), ) From 99f56579a5ac1f9c749679285ea358439b4c4075 Mon Sep 17 00:00:00 2001 From: Brynley McDonald Date: Mon, 31 Jan 2022 17:21:15 +1300 Subject: [PATCH 0116/1098] Fix flick_electric auth failures (#65274) --- .../components/flick_electric/__init__.py | 18 ++++++++++++++---- .../components/flick_electric/const.py | 1 - .../components/flick_electric/sensor.py | 4 ++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/flick_electric/__init__.py b/homeassistant/components/flick_electric/__init__.py index c29a476ca5515d..54eaf5a6917511 100644 --- a/homeassistant/components/flick_electric/__init__.py +++ b/homeassistant/components/flick_electric/__init__.py @@ -1,7 +1,9 @@ """The Flick Electric integration.""" from datetime import datetime as dt +import logging +import jwt from pyflick import FlickAPI from pyflick.authentication import AbstractFlickAuth from pyflick.const import DEFAULT_CLIENT_ID, DEFAULT_CLIENT_SECRET @@ -18,7 +20,9 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import aiohttp_client -from .const import CONF_TOKEN_EXPIRES_IN, CONF_TOKEN_EXPIRY, DOMAIN +from .const import CONF_TOKEN_EXPIRY, DOMAIN + +_LOGGER = logging.getLogger(__name__) CONF_ID_TOKEN = "id_token" @@ -69,6 +73,8 @@ async def _get_entry_token(self): return self._entry.data[CONF_ACCESS_TOKEN] async def _update_token(self): + _LOGGER.debug("Fetching new access token") + token = await self.get_new_token( username=self._entry.data[CONF_USERNAME], password=self._entry.data[CONF_PASSWORD], @@ -78,15 +84,19 @@ async def _update_token(self): ), ) - # Reduce expiry by an hour to avoid API being called after expiry - expiry = dt.now().timestamp() + int(token[CONF_TOKEN_EXPIRES_IN] - 3600) + _LOGGER.debug("New token: %s", token) + + # Flick will send the same token, but expiry is relative - so grab it from the token + token_decoded = jwt.decode( + token[CONF_ID_TOKEN], options={"verify_signature": False} + ) self._hass.config_entries.async_update_entry( self._entry, data={ **self._entry.data, CONF_ACCESS_TOKEN: token, - CONF_TOKEN_EXPIRY: expiry, + CONF_TOKEN_EXPIRY: token_decoded["exp"], }, ) diff --git a/homeassistant/components/flick_electric/const.py b/homeassistant/components/flick_electric/const.py index e8365f37411cf3..de1942096b5b2b 100644 --- a/homeassistant/components/flick_electric/const.py +++ b/homeassistant/components/flick_electric/const.py @@ -2,7 +2,6 @@ DOMAIN = "flick_electric" -CONF_TOKEN_EXPIRES_IN = "expires_in" CONF_TOKEN_EXPIRY = "expires" ATTR_START_AT = "start_at" diff --git a/homeassistant/components/flick_electric/sensor.py b/homeassistant/components/flick_electric/sensor.py index d6a1bd59d8e55c..92bc81b5aa09a2 100644 --- a/homeassistant/components/flick_electric/sensor.py +++ b/homeassistant/components/flick_electric/sensor.py @@ -15,8 +15,6 @@ from .const import ATTR_COMPONENTS, ATTR_END_AT, ATTR_START_AT, DOMAIN _LOGGER = logging.getLogger(__name__) -_AUTH_URL = "https://api.flick.energy/identity/oauth/token" -_RESOURCE = "https://api.flick.energy/customer/mobile_provider/price" SCAN_INTERVAL = timedelta(minutes=5) @@ -71,6 +69,8 @@ async def async_update(self): async with async_timeout.timeout(60): self._price = await self._api.getPricing() + _LOGGER.debug("Pricing data: %s", self._price) + self._attributes[ATTR_START_AT] = self._price.start_at self._attributes[ATTR_END_AT] = self._price.end_at for component in self._price.components: From 7552404f70c61bff02b2f8a4e8f6951950190dfc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 30 Jan 2022 22:21:54 -0600 Subject: [PATCH 0117/1098] Increase the timeout for flux_led directed discovery (#65222) --- homeassistant/components/flux_led/const.py | 1 + homeassistant/components/flux_led/discovery.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/flux_led/const.py b/homeassistant/components/flux_led/const.py index 95121b5685b8a1..9113d52f5c8218 100644 --- a/homeassistant/components/flux_led/const.py +++ b/homeassistant/components/flux_led/const.py @@ -51,6 +51,7 @@ STARTUP_SCAN_TIMEOUT: Final = 5 DISCOVER_SCAN_TIMEOUT: Final = 10 +DIRECTED_DISCOVERY_TIMEOUT: Final = 15 CONF_MODEL: Final = "model" CONF_MODEL_NUM: Final = "model_num" diff --git a/homeassistant/components/flux_led/discovery.py b/homeassistant/components/flux_led/discovery.py index 32b0d0ed9df4b1..0f65c7c17974ad 100644 --- a/homeassistant/components/flux_led/discovery.py +++ b/homeassistant/components/flux_led/discovery.py @@ -38,7 +38,7 @@ CONF_REMOTE_ACCESS_ENABLED, CONF_REMOTE_ACCESS_HOST, CONF_REMOTE_ACCESS_PORT, - DISCOVER_SCAN_TIMEOUT, + DIRECTED_DISCOVERY_TIMEOUT, DOMAIN, FLUX_LED_DISCOVERY, ) @@ -194,7 +194,7 @@ async def async_discover_device( """Direct discovery at a single ip instead of broadcast.""" # If we are missing the unique_id we should be able to fetch it # from the device by doing a directed discovery at the host only - for device in await async_discover_devices(hass, DISCOVER_SCAN_TIMEOUT, host): + for device in await async_discover_devices(hass, DIRECTED_DISCOVERY_TIMEOUT, host): if device[ATTR_IPADDR] == host: return device return None From 2f6bf0816520004f464c06c7ac2a46cedefdb57d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 30 Jan 2022 22:24:42 -0600 Subject: [PATCH 0118/1098] Fix senseme fan lights (#65217) --- homeassistant/components/senseme/light.py | 78 ++++++++++------ tests/components/senseme/__init__.py | 74 ++++++++++------ tests/components/senseme/test_light.py | 103 ++++++++++++++++++++++ 3 files changed, 200 insertions(+), 55 deletions(-) create mode 100644 tests/components/senseme/test_light.py diff --git a/homeassistant/components/senseme/light.py b/homeassistant/components/senseme/light.py index 2a4d82de6d0db2..75d853c4001705 100644 --- a/homeassistant/components/senseme/light.py +++ b/homeassistant/components/senseme/light.py @@ -31,50 +31,30 @@ async def async_setup_entry( ) -> None: """Set up SenseME lights.""" device = hass.data[DOMAIN][entry.entry_id] - if device.has_light: - async_add_entities([HASensemeLight(device)]) + if not device.has_light: + return + if device.is_light: + async_add_entities([HASensemeStandaloneLight(device)]) + else: + async_add_entities([HASensemeFanLight(device)]) class HASensemeLight(SensemeEntity, LightEntity): """Representation of a Big Ass Fans SenseME light.""" - def __init__(self, device: SensemeDevice) -> None: + def __init__(self, device: SensemeDevice, name: str) -> None: """Initialize the entity.""" - self._device = device - if device.is_light: - name = device.name # The device itself is a light - else: - name = f"{device.name} Light" # A fan light super().__init__(device, name) - if device.is_light: - self._attr_supported_color_modes = {COLOR_MODE_COLOR_TEMP} - self._attr_color_mode = COLOR_MODE_COLOR_TEMP - else: - self._attr_supported_color_modes = {COLOR_MODE_BRIGHTNESS} - self._attr_color_mode = COLOR_MODE_BRIGHTNESS - self._attr_unique_id = f"{self._device.uuid}-LIGHT" # for legacy compat - self._attr_min_mireds = color_temperature_kelvin_to_mired( - self._device.light_color_temp_max - ) - self._attr_max_mireds = color_temperature_kelvin_to_mired( - self._device.light_color_temp_min - ) + self._attr_unique_id = f"{device.uuid}-LIGHT" # for legacy compat @callback def _async_update_attrs(self) -> None: """Update attrs from device.""" self._attr_is_on = self._device.light_on self._attr_brightness = int(min(255, self._device.light_brightness * 16)) - self._attr_color_temp = color_temperature_kelvin_to_mired( - self._device.light_color_temp - ) async def async_turn_on(self, **kwargs: Any) -> None: """Turn on the light.""" - if (color_temp := kwargs.get(ATTR_COLOR_TEMP)) is not None: - self._device.light_color_temp = color_temperature_mired_to_kelvin( - color_temp - ) if (brightness := kwargs.get(ATTR_BRIGHTNESS)) is not None: # set the brightness, which will also turn on/off light if brightness == 255: @@ -86,3 +66,45 @@ async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the light.""" self._device.light_on = False + + +class HASensemeFanLight(HASensemeLight): + """Representation of a Big Ass Fans SenseME light on a fan.""" + + def __init__(self, device: SensemeDevice) -> None: + """Init a fan light.""" + super().__init__(device, device.name) + self._attr_supported_color_modes = {COLOR_MODE_BRIGHTNESS} + self._attr_color_mode = COLOR_MODE_BRIGHTNESS + + +class HASensemeStandaloneLight(HASensemeLight): + """Representation of a Big Ass Fans SenseME light.""" + + def __init__(self, device: SensemeDevice) -> None: + """Init a standalone light.""" + super().__init__(device, f"{device.name} Light") + self._attr_supported_color_modes = {COLOR_MODE_COLOR_TEMP} + self._attr_color_mode = COLOR_MODE_COLOR_TEMP + self._attr_min_mireds = color_temperature_kelvin_to_mired( + device.light_color_temp_max + ) + self._attr_max_mireds = color_temperature_kelvin_to_mired( + device.light_color_temp_min + ) + + @callback + def _async_update_attrs(self) -> None: + """Update attrs from device.""" + super()._async_update_attrs() + self._attr_color_temp = color_temperature_kelvin_to_mired( + self._device.light_color_temp + ) + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn on the light.""" + if (color_temp := kwargs.get(ATTR_COLOR_TEMP)) is not None: + self._device.light_color_temp = color_temperature_mired_to_kelvin( + color_temp + ) + await super().async_turn_on(**kwargs) diff --git a/tests/components/senseme/__init__.py b/tests/components/senseme/__init__.py index 5c586d88fd5121..8c9a7669889ddf 100644 --- a/tests/components/senseme/__init__.py +++ b/tests/components/senseme/__init__.py @@ -12,32 +12,38 @@ MOCK_ADDRESS = "127.0.0.1" MOCK_MAC = "20:F8:5E:92:5A:75" -device = MagicMock(auto_spec=SensemeDevice) -device.async_update = AsyncMock() -device.model = "Haiku Fan" -device.fan_speed_max = 7 -device.mac = "aa:bb:cc:dd:ee:ff" -device.fan_dir = "REV" -device.room_name = "Main" -device.room_type = "Main" -device.fw_version = "1" -device.fan_autocomfort = "on" -device.fan_smartmode = "on" -device.fan_whoosh_mode = "on" -device.name = MOCK_NAME -device.uuid = MOCK_UUID -device.address = MOCK_ADDRESS -device.get_device_info = { - "name": MOCK_NAME, - "uuid": MOCK_UUID, - "mac": MOCK_ADDRESS, - "address": MOCK_ADDRESS, - "base_model": "FAN,HAIKU,HSERIES", - "has_light": False, - "has_sensor": True, - "is_fan": True, - "is_light": False, -} + +def _mock_device(): + device = MagicMock(auto_spec=SensemeDevice) + device.async_update = AsyncMock() + device.model = "Haiku Fan" + device.fan_speed_max = 7 + device.mac = "aa:bb:cc:dd:ee:ff" + device.fan_dir = "REV" + device.has_light = True + device.is_light = False + device.light_brightness = 50 + device.room_name = "Main" + device.room_type = "Main" + device.fw_version = "1" + device.fan_autocomfort = "COOLING" + device.fan_smartmode = "OFF" + device.fan_whoosh_mode = "on" + device.name = MOCK_NAME + device.uuid = MOCK_UUID + device.address = MOCK_ADDRESS + device.get_device_info = { + "name": MOCK_NAME, + "uuid": MOCK_UUID, + "mac": MOCK_ADDRESS, + "address": MOCK_ADDRESS, + "base_model": "FAN,HAIKU,HSERIES", + "has_light": False, + "has_sensor": True, + "is_fan": True, + "is_light": False, + } + return device device_alternate_ip = MagicMock(auto_spec=SensemeDevice) @@ -99,7 +105,7 @@ device_no_uuid.uuid = None -MOCK_DEVICE = device +MOCK_DEVICE = _mock_device() MOCK_DEVICE_ALTERNATE_IP = device_alternate_ip MOCK_DEVICE2 = device2 MOCK_DEVICE_NO_UUID = device_no_uuid @@ -121,3 +127,17 @@ def _patcher(): yield return _patcher() + + +def _patch_device(device=None, no_device=False): + async def _device_mocker(*args, **kwargs): + if no_device: + return False, None + if device: + return True, device + return True, _mock_device() + + return patch( + "homeassistant.components.senseme.async_get_device_by_device_info", + new=_device_mocker, + ) diff --git a/tests/components/senseme/test_light.py b/tests/components/senseme/test_light.py new file mode 100644 index 00000000000000..21811452610715 --- /dev/null +++ b/tests/components/senseme/test_light.py @@ -0,0 +1,103 @@ +"""Tests for senseme light platform.""" + + +from aiosenseme import SensemeDevice + +from homeassistant.components import senseme +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + ATTR_COLOR_MODE, + ATTR_COLOR_TEMP, + ATTR_SUPPORTED_COLOR_MODES, + COLOR_MODE_BRIGHTNESS, + COLOR_MODE_COLOR_TEMP, + DOMAIN as LIGHT_DOMAIN, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, +) +from homeassistant.components.senseme.const import DOMAIN +from homeassistant.const import ATTR_ENTITY_ID, STATE_ON +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er +from homeassistant.setup import async_setup_component + +from . import _mock_device, _patch_device, _patch_discovery + +from tests.common import MockConfigEntry + + +async def _setup_mocked_entry(hass: HomeAssistant, device: SensemeDevice) -> None: + """Set up a mocked entry.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={"info": device.get_device_info}, + unique_id=device.uuid, + ) + entry.add_to_hass(hass) + with _patch_discovery(), _patch_device(device=device): + await async_setup_component(hass, senseme.DOMAIN, {senseme.DOMAIN: {}}) + await hass.async_block_till_done() + + +async def test_light_unique_id(hass: HomeAssistant) -> None: + """Test a light unique id.""" + device = _mock_device() + await _setup_mocked_entry(hass, device) + entity_id = "light.haiku_fan" + entity_registry = er.async_get(hass) + assert entity_registry.async_get(entity_id).unique_id == f"{device.uuid}-LIGHT" + state = hass.states.get(entity_id) + assert state.state == STATE_ON + + +async def test_fan_light(hass: HomeAssistant) -> None: + """Test a fan light.""" + device = _mock_device() + await _setup_mocked_entry(hass, device) + entity_id = "light.haiku_fan" + + state = hass.states.get(entity_id) + assert state.state == STATE_ON + attributes = state.attributes + assert attributes[ATTR_BRIGHTNESS] == 255 + assert attributes[ATTR_COLOR_MODE] == COLOR_MODE_BRIGHTNESS + assert attributes[ATTR_SUPPORTED_COLOR_MODES] == [COLOR_MODE_BRIGHTNESS] + + await hass.services.async_call( + LIGHT_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + assert device.light_on is False + + await hass.services.async_call( + LIGHT_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + assert device.light_on is True + + +async def test_standalone_light(hass: HomeAssistant) -> None: + """Test a standalone light.""" + device = _mock_device() + device.is_light = True + device.light_color_temp_max = 6500 + device.light_color_temp_min = 2700 + device.light_color_temp = 4000 + await _setup_mocked_entry(hass, device) + entity_id = "light.haiku_fan_light" + + state = hass.states.get(entity_id) + assert state.state == STATE_ON + attributes = state.attributes + assert attributes[ATTR_BRIGHTNESS] == 255 + assert attributes[ATTR_COLOR_MODE] == COLOR_MODE_COLOR_TEMP + assert attributes[ATTR_SUPPORTED_COLOR_MODES] == [COLOR_MODE_COLOR_TEMP] + assert attributes[ATTR_COLOR_TEMP] == 250 + + await hass.services.async_call( + LIGHT_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + assert device.light_on is False + + await hass.services.async_call( + LIGHT_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + assert device.light_on is True From 8c9bd6e790f975ddf0905eaf0361776f8ff7bf02 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 30 Jan 2022 22:50:49 -0600 Subject: [PATCH 0119/1098] Increase august timeout and make failure to sync at startup non-fatal (#65281) --- homeassistant/components/august/__init__.py | 39 ++++++++++++++----- homeassistant/components/august/const.py | 2 +- homeassistant/components/august/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/august/test_init.py | 20 ++++++++++ 6 files changed, 54 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index d58541e708d287..3fc113c6038c06 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -43,7 +43,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return await async_setup_august(hass, entry, august_gateway) except (RequireValidation, InvalidAuth) as err: raise ConfigEntryAuthFailed from err - except (ClientResponseError, CannotConnect, asyncio.TimeoutError) as err: + except asyncio.TimeoutError as err: + raise ConfigEntryNotReady("Timed out connecting to august api") from err + except (ClientResponseError, CannotConnect) as err: raise ConfigEntryNotReady from err @@ -141,15 +143,34 @@ async def async_setup(self): self._pubnub_unsub = async_create_pubnub(user_data["UserID"], pubnub) if self._locks_by_id: - tasks = [] - for lock_id in self._locks_by_id: - detail = self._device_detail_by_id[lock_id] - tasks.append( - self.async_status_async( - lock_id, bool(detail.bridge and detail.bridge.hyper_bridge) - ) + # Do not prevent setup as the sync can timeout + # but it is not a fatal error as the lock + # will recover automatically when it comes back online. + asyncio.create_task(self._async_initial_sync()) + + async def _async_initial_sync(self): + """Attempt to request an initial sync.""" + # We don't care if this fails because we only want to wake + # locks that are actually online anyways and they will be + # awake when they come back online + for result in await asyncio.gather( + *[ + self.async_status_async( + device_id, bool(detail.bridge and detail.bridge.hyper_bridge) + ) + for device_id, detail in self._device_detail_by_id.items() + if device_id in self._locks_by_id + ], + return_exceptions=True, + ): + if isinstance(result, Exception) and not isinstance( + result, (asyncio.TimeoutError, ClientResponseError, CannotConnect) + ): + _LOGGER.warning( + "Unexpected exception during initial sync: %s", + result, + exc_info=result, ) - await asyncio.gather(*tasks) @callback def async_pubnub_message(self, device_id, date_time, message): diff --git a/homeassistant/components/august/const.py b/homeassistant/components/august/const.py index dfe9cc0f7002a1..ae3fcdf90e3cb6 100644 --- a/homeassistant/components/august/const.py +++ b/homeassistant/components/august/const.py @@ -4,7 +4,7 @@ from homeassistant.const import Platform -DEFAULT_TIMEOUT = 10 +DEFAULT_TIMEOUT = 15 CONF_ACCESS_TOKEN_CACHE_FILE = "access_token_cache_file" CONF_LOGIN_METHOD = "login_method" diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index dc05ee9b9296af..71a8b6c83aa10f 100644 --- a/homeassistant/components/august/manifest.json +++ b/homeassistant/components/august/manifest.json @@ -2,7 +2,7 @@ "domain": "august", "name": "August", "documentation": "https://www.home-assistant.io/integrations/august", - "requirements": ["yalexs==1.1.19"], + "requirements": ["yalexs==1.1.20"], "codeowners": ["@bdraco"], "dhcp": [ { diff --git a/requirements_all.txt b/requirements_all.txt index f7260088e0ea34..5dc97d4f7d89da 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2513,7 +2513,7 @@ xs1-api-client==3.0.0 yalesmartalarmclient==0.3.7 # homeassistant.components.august -yalexs==1.1.19 +yalexs==1.1.20 # homeassistant.components.yeelight yeelight==0.7.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 08eaf12cf0c635..e5c1a696fe8a45 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1544,7 +1544,7 @@ xmltodict==0.12.0 yalesmartalarmclient==0.3.7 # homeassistant.components.august -yalexs==1.1.19 +yalexs==1.1.20 # homeassistant.components.yeelight yeelight==0.7.8 diff --git a/tests/components/august/test_init.py b/tests/components/august/test_init.py index 4d73eec7f1f14e..320461ca6e9729 100644 --- a/tests/components/august/test_init.py +++ b/tests/components/august/test_init.py @@ -30,6 +30,26 @@ ) +async def test_august_api_is_failing(hass): + """Config entry state is SETUP_RETRY when august api is failing.""" + + config_entry = MockConfigEntry( + domain=DOMAIN, + data=_mock_get_config()[DOMAIN], + title="August august", + ) + config_entry.add_to_hass(hass) + + with patch( + "yalexs.authenticator_async.AuthenticatorAsync.async_authenticate", + side_effect=ClientResponseError(None, None, status=500), + ): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state is ConfigEntryState.SETUP_RETRY + + async def test_august_is_offline(hass): """Config entry state is SETUP_RETRY when august is offline.""" From 3536271fceeb3c06e9638e62f60592748f46117e Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Mon, 31 Jan 2022 14:51:39 +1000 Subject: [PATCH 0120/1098] Add diagnostics to Advantage Air (#65006) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Joakim Sørensen --- .coveragerc | 1 + .../components/advantage_air/diagnostics.py | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 homeassistant/components/advantage_air/diagnostics.py diff --git a/.coveragerc b/.coveragerc index 426114c65767da..0ae80a22a47e81 100644 --- a/.coveragerc +++ b/.coveragerc @@ -27,6 +27,7 @@ omit = homeassistant/components/adguard/sensor.py homeassistant/components/adguard/switch.py homeassistant/components/ads/* + homeassistant/components/advantage_air/diagnostics.py homeassistant/components/aemet/weather_update_coordinator.py homeassistant/components/aftership/* homeassistant/components/agent_dvr/alarm_control_panel.py diff --git a/homeassistant/components/advantage_air/diagnostics.py b/homeassistant/components/advantage_air/diagnostics.py new file mode 100644 index 00000000000000..27eaef09b43b6a --- /dev/null +++ b/homeassistant/components/advantage_air/diagnostics.py @@ -0,0 +1,25 @@ +"""Provides diagnostics for Advantage Air.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN + +TO_REDACT = ["dealerPhoneNumber", "latitude", "logoPIN", "longitude", "postCode"] + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + data = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]["coordinator"].data + + # Return only the relevant children + return { + "aircons": data["aircons"], + "system": async_redact_data(data["system"], TO_REDACT), + } From f82183f0e3409edb0815476055202d39aafd645c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jan 2022 09:18:35 +0100 Subject: [PATCH 0121/1098] Bump home-assistant/builder from 2021.12.0 to 2022.01.0 (#65284) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/builder.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 74016d4492cd26..191e8fa97e9b9f 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -135,7 +135,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build base image - uses: home-assistant/builder@2021.12.0 + uses: home-assistant/builder@2022.01.0 with: args: | $BUILD_ARGS \ @@ -200,7 +200,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build base image - uses: home-assistant/builder@2021.12.0 + uses: home-assistant/builder@2022.01.0 with: args: | $BUILD_ARGS \ From f6b0f26783d51288dea69b6e2244c2584e0ae2ee Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Mon, 31 Jan 2022 10:07:50 +0100 Subject: [PATCH 0122/1098] Bump pyatmo to v.6.2.4 (#65285) * Bump pyatmo to v6.2.3 Signed-off-by: cgtobi * Bump pyatmo to v6.2.4 Signed-off-by: cgtobi --- homeassistant/components/netatmo/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/netatmo/manifest.json b/homeassistant/components/netatmo/manifest.json index 1632c8ba9a36eb..e801d941a74add 100644 --- a/homeassistant/components/netatmo/manifest.json +++ b/homeassistant/components/netatmo/manifest.json @@ -3,7 +3,7 @@ "name": "Netatmo", "documentation": "https://www.home-assistant.io/integrations/netatmo", "requirements": [ - "pyatmo==6.2.2" + "pyatmo==6.2.4" ], "after_dependencies": [ "cloud", diff --git a/requirements_all.txt b/requirements_all.txt index 5dc97d4f7d89da..65a6067fef7497 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1395,7 +1395,7 @@ pyarlo==0.2.4 pyatag==0.3.5.3 # homeassistant.components.netatmo -pyatmo==6.2.2 +pyatmo==6.2.4 # homeassistant.components.atome pyatome==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e5c1a696fe8a45..bde0607e8b9a30 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -872,7 +872,7 @@ pyarlo==0.2.4 pyatag==0.3.5.3 # homeassistant.components.netatmo -pyatmo==6.2.2 +pyatmo==6.2.4 # homeassistant.components.apple_tv pyatv==0.10.0 From bfaada34e28318fa13791fa76ca7c4f3bf276c49 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Mon, 31 Jan 2022 10:25:08 +0100 Subject: [PATCH 0123/1098] Allow `unknown` state to be set (#65183) --- homeassistant/components/mqtt/binary_sensor.py | 5 ++++- tests/components/mqtt/test_binary_sensor.py | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index 800db2cad79bed..d9ec64a02c9904 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -49,6 +49,7 @@ DEFAULT_PAYLOAD_ON = "ON" DEFAULT_FORCE_UPDATE = False CONF_EXPIRE_AFTER = "expire_after" +PAYLOAD_NONE = "None" PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( { @@ -174,6 +175,8 @@ def state_message_received(msg): self._state = True elif payload == self._config[CONF_PAYLOAD_OFF]: self._state = False + elif payload == PAYLOAD_NONE: + self._state = None else: # Payload is not for this entity template_info = "" if self._config.get(CONF_VALUE_TEMPLATE) is not None: @@ -221,7 +224,7 @@ def _value_is_expired(self, *_): self.async_write_ha_state() @property - def is_on(self): + def is_on(self) -> bool | None: """Return true if the binary sensor is on.""" return self._state diff --git a/tests/components/mqtt/test_binary_sensor.py b/tests/components/mqtt/test_binary_sensor.py index a13f0781dfb664..1d94dc7ccf7f14 100644 --- a/tests/components/mqtt/test_binary_sensor.py +++ b/tests/components/mqtt/test_binary_sensor.py @@ -269,6 +269,10 @@ async def test_setting_sensor_value_via_mqtt_message(hass, mqtt_mock): state = hass.states.get("binary_sensor.test") assert state.state == STATE_OFF + async_fire_mqtt_message(hass, "test-topic", "None") + state = hass.states.get("binary_sensor.test") + assert state.state == STATE_UNKNOWN + async def test_invalid_sensor_value_via_mqtt_message(hass, mqtt_mock, caplog): """Test the setting of the value via MQTT.""" From 6fdaec0847950ed52736e6a58730d5fda223c181 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Mon, 31 Jan 2022 10:31:57 +0100 Subject: [PATCH 0124/1098] Add MQTT siren platform (#64440) * Add mqtt siren draft * fix tests * Intergrate notify platform * tests and fixes siren platform * Add tests notify platform * config parameters and abbreviations * remove duplicate key * undo move topic abbreviation * Move const CONF_MESSAGE_COMMAND_TEMPLATE * Remove notify service integration * Rework * Update homeassistant/components/mqtt/siren.py Co-authored-by: Erik Montnemery * Publish JSON by default * Allow unknown state - rename value_template Co-authored-by: Erik Montnemery --- homeassistant/components/mqtt/__init__.py | 1 + .../components/mqtt/abbreviations.py | 3 + homeassistant/components/mqtt/const.py | 3 + homeassistant/components/mqtt/discovery.py | 1 + homeassistant/components/mqtt/siren.py | 374 ++++++++ tests/components/mqtt/test_siren.py | 884 ++++++++++++++++++ 6 files changed, 1266 insertions(+) create mode 100644 homeassistant/components/mqtt/siren.py create mode 100644 tests/components/mqtt/test_siren.py diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 39e8d0d55b3f90..9ff389325cee44 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -150,6 +150,7 @@ Platform.SELECT, Platform.SCENE, Platform.SENSOR, + Platform.SIREN, Platform.SWITCH, Platform.VACUUM, ] diff --git a/homeassistant/components/mqtt/abbreviations.py b/homeassistant/components/mqtt/abbreviations.py index c5c70ad33a4bb9..4b4c6fb7af9d4f 100644 --- a/homeassistant/components/mqtt/abbreviations.py +++ b/homeassistant/components/mqtt/abbreviations.py @@ -7,6 +7,7 @@ "aux_cmd_t": "aux_command_topic", "aux_stat_tpl": "aux_state_template", "aux_stat_t": "aux_state_topic", + "av_tones": "available_tones", "avty": "availability", "avty_mode": "availability_mode", "avty_t": "availability_topic", @@ -205,6 +206,8 @@ "stat_val_tpl": "state_value_template", "step": "step", "stype": "subtype", + "sup_dur": "support_duration", + "sup_vol": "support_volume_set", "sup_feat": "supported_features", "sup_clrm": "supported_color_modes", "swing_mode_cmd_tpl": "swing_mode_command_template", diff --git a/homeassistant/components/mqtt/const.py b/homeassistant/components/mqtt/const.py index 4ccd81904b14bf..0feb21b0010047 100644 --- a/homeassistant/components/mqtt/const.py +++ b/homeassistant/components/mqtt/const.py @@ -51,4 +51,7 @@ MQTT_CONNECTED = "mqtt_connected" MQTT_DISCONNECTED = "mqtt_disconnected" +PAYLOAD_EMPTY_JSON = "{}" +PAYLOAD_NONE = "None" + PROTOCOL_311 = "3.1.1" diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index c9b0b816c4efa4..b31d90c76f8ca9 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -50,6 +50,7 @@ "lock", "number", "scene", + "siren", "select", "sensor", "switch", diff --git a/homeassistant/components/mqtt/siren.py b/homeassistant/components/mqtt/siren.py new file mode 100644 index 00000000000000..e83aee2622845b --- /dev/null +++ b/homeassistant/components/mqtt/siren.py @@ -0,0 +1,374 @@ +"""Support for MQTT sirens.""" +from __future__ import annotations + +import copy +import functools +import json +import logging +from typing import Any + +import voluptuous as vol + +from homeassistant.components import siren +from homeassistant.components.siren import ( + TURN_ON_SCHEMA, + SirenEntity, + process_turn_on_params, +) +from homeassistant.components.siren.const import ( + ATTR_AVAILABLE_TONES, + ATTR_DURATION, + ATTR_TONE, + ATTR_VOLUME_LEVEL, + SUPPORT_DURATION, + SUPPORT_TONES, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, + SUPPORT_VOLUME_SET, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + CONF_NAME, + CONF_OPTIMISTIC, + CONF_PAYLOAD_OFF, + CONF_PAYLOAD_ON, +) +from homeassistant.core import HomeAssistant, callback +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.reload import async_setup_reload_service +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType + +from . import PLATFORMS, MqttCommandTemplate, MqttValueTemplate, subscription +from .. import mqtt +from .const import ( + CONF_COMMAND_TEMPLATE, + CONF_COMMAND_TOPIC, + CONF_ENCODING, + CONF_QOS, + CONF_RETAIN, + CONF_STATE_TOPIC, + DOMAIN, + PAYLOAD_EMPTY_JSON, + PAYLOAD_NONE, +) +from .debug_info import log_messages +from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper + +DEFAULT_NAME = "MQTT Siren" +DEFAULT_PAYLOAD_ON = "ON" +DEFAULT_PAYLOAD_OFF = "OFF" +DEFAULT_OPTIMISTIC = False + +ENTITY_ID_FORMAT = siren.DOMAIN + ".{}" + +CONF_AVAILABLE_TONES = "available_tones" +CONF_COMMAND_OFF_TEMPLATE = "command_off_template" +CONF_STATE_ON = "state_on" +CONF_STATE_OFF = "state_off" +CONF_STATE_VALUE_TEMPLATE = "state_value_template" +CONF_SUPPORT_DURATION = "support_duration" +CONF_SUPPORT_VOLUME_SET = "support_volume_set" + +STATE = "state" + +PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_AVAILABLE_TONES): cv.ensure_list, + vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, + vol.Optional(CONF_COMMAND_OFF_TEMPLATE): cv.template, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, + vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, + vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, + vol.Optional(CONF_STATE_OFF): cv.string, + vol.Optional(CONF_STATE_ON): cv.string, + vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_SUPPORT_DURATION, default=True): cv.boolean, + vol.Optional(CONF_SUPPORT_VOLUME_SET, default=True): cv.boolean, + }, +).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) + +DISCOVERY_SCHEMA = vol.All(PLATFORM_SCHEMA.extend({}, extra=vol.REMOVE_EXTRA)) + +MQTT_SIREN_ATTRIBUTES_BLOCKED = frozenset( + { + ATTR_AVAILABLE_TONES, + ATTR_DURATION, + ATTR_TONE, + ATTR_VOLUME_LEVEL, + } +) + +SUPPORTED_BASE = SUPPORT_TURN_OFF | SUPPORT_TURN_ON + +SUPPORTED_ATTRIBUTES = { + ATTR_DURATION: SUPPORT_DURATION, + ATTR_TONE: SUPPORT_TONES, + ATTR_VOLUME_LEVEL: SUPPORT_VOLUME_SET, +} + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_platform( + hass: HomeAssistant, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + discovery_info: DiscoveryInfoType | None = None, +) -> None: + """Set up MQTT siren through configuration.yaml.""" + await async_setup_reload_service(hass, DOMAIN, PLATFORMS) + await _async_setup_entity(hass, async_add_entities, config) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up MQTT siren dynamically through MQTT discovery.""" + setup = functools.partial( + _async_setup_entity, hass, async_add_entities, config_entry=config_entry + ) + await async_setup_entry_helper(hass, siren.DOMAIN, setup, DISCOVERY_SCHEMA) + + +async def _async_setup_entity( + hass, async_add_entities, config, config_entry=None, discovery_data=None +): + """Set up the MQTT siren.""" + async_add_entities([MqttSiren(hass, config, config_entry, discovery_data)]) + + +class MqttSiren(MqttEntity, SirenEntity): + """Representation of a siren that can be controlled using MQTT.""" + + _entity_id_format = ENTITY_ID_FORMAT + _attributes_extra_blocked = MQTT_SIREN_ATTRIBUTES_BLOCKED + + def __init__(self, hass, config, config_entry, discovery_data): + """Initialize the MQTT siren.""" + self._attr_name = config[CONF_NAME] + self._attr_should_poll = False + self._supported_features = SUPPORTED_BASE + self._attr_is_on = None + self._state_on = None + self._state_off = None + self._optimistic = None + + self._attr_extra_state_attributes: dict[str, Any] = {} + + self.target = None + + MqttEntity.__init__(self, hass, config, config_entry, discovery_data) + + @staticmethod + def config_schema(): + """Return the config schema.""" + return DISCOVERY_SCHEMA + + def _setup_from_config(self, config): + """(Re)Setup the entity.""" + + state_on = config.get(CONF_STATE_ON) + self._state_on = state_on if state_on else config[CONF_PAYLOAD_ON] + + state_off = config.get(CONF_STATE_OFF) + self._state_off = state_off if state_off else config[CONF_PAYLOAD_OFF] + + if config[CONF_SUPPORT_DURATION]: + self._supported_features |= SUPPORT_DURATION + self._attr_extra_state_attributes[ATTR_DURATION] = None + + if config.get(CONF_AVAILABLE_TONES): + self._supported_features |= SUPPORT_TONES + self._attr_available_tones = config[CONF_AVAILABLE_TONES] + self._attr_extra_state_attributes[ATTR_TONE] = None + + if config[CONF_SUPPORT_VOLUME_SET]: + self._supported_features |= SUPPORT_VOLUME_SET + self._attr_extra_state_attributes[ATTR_VOLUME_LEVEL] = None + + self._optimistic = config[CONF_OPTIMISTIC] or CONF_STATE_TOPIC not in config + self._attr_is_on = False if self._optimistic else None + + command_template = config.get(CONF_COMMAND_TEMPLATE) + command_off_template = config.get(CONF_COMMAND_OFF_TEMPLATE) or config.get( + CONF_COMMAND_TEMPLATE + ) + self._command_templates = { + CONF_COMMAND_TEMPLATE: MqttCommandTemplate( + command_template, entity=self + ).async_render + if command_template + else None, + CONF_COMMAND_OFF_TEMPLATE: MqttCommandTemplate( + command_off_template, entity=self + ).async_render + if command_off_template + else None, + } + self._value_template = MqttValueTemplate( + config.get(CONF_STATE_VALUE_TEMPLATE), + entity=self, + ).async_render_with_possible_json_value + + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + + @callback + @log_messages(self.hass, self.entity_id) + def state_message_received(msg): + """Handle new MQTT state messages.""" + payload = self._value_template(msg.payload) + if not payload or payload == PAYLOAD_EMPTY_JSON: + _LOGGER.debug( + "Ignoring empty payload '%s' after rendering for topic %s", + payload, + msg.topic, + ) + return + json_payload = {} + if payload in [self._state_on, self._state_off, PAYLOAD_NONE]: + json_payload = {STATE: payload} + else: + try: + json_payload = json.loads(payload) + _LOGGER.debug( + "JSON payload detected after processing payload '%s' on topic %s", + json_payload, + msg.topic, + ) + except json.decoder.JSONDecodeError: + _LOGGER.warning( + "No valid (JSON) payload detected after processing payload '%s' on topic %s", + json_payload, + msg.topic, + ) + return + if STATE in json_payload: + if json_payload[STATE] == self._state_on: + self._attr_is_on = True + if json_payload[STATE] == self._state_off: + self._attr_is_on = False + if json_payload[STATE] == PAYLOAD_NONE: + self._attr_is_on = None + del json_payload[STATE] + + if json_payload: + # process attributes + try: + vol.All(TURN_ON_SCHEMA)(json_payload) + except vol.MultipleInvalid as invalid_siren_parameters: + _LOGGER.warning( + "Unable to update siren state attributes from payload '%s': %s", + json_payload, + invalid_siren_parameters, + ) + return + self._update(process_turn_on_params(self, json_payload)) + self.async_write_ha_state() + + if self._config.get(CONF_STATE_TOPIC) is None: + # Force into optimistic mode. + self._optimistic = True + else: + self._sub_state = await subscription.async_subscribe_topics( + self.hass, + self._sub_state, + { + CONF_STATE_TOPIC: { + "topic": self._config.get(CONF_STATE_TOPIC), + "msg_callback": state_message_received, + "qos": self._config[CONF_QOS], + "encoding": self._config[CONF_ENCODING] or None, + } + }, + ) + + @property + def assumed_state(self): + """Return true if we do optimistic updates.""" + return self._optimistic + + @property + def extra_state_attributes(self) -> dict: + """Return the state attributes.""" + mqtt_attributes = super().extra_state_attributes + attributes = ( + copy.deepcopy(mqtt_attributes) if mqtt_attributes is not None else {} + ) + attributes.update(self._attr_extra_state_attributes) + return attributes + + @property + def supported_features(self) -> int: + """Flag supported features.""" + return self._supported_features + + async def _async_publish( + self, + topic: str, + template: str, + value: Any, + variables: dict[str, Any] | None = None, + ) -> None: + """Publish MQTT payload with optional command template.""" + template_variables = {STATE: value} + if variables is not None: + template_variables.update(variables) + payload = ( + self._command_templates[template](value, template_variables) + if self._command_templates[template] + else json.dumps(template_variables) + ) + if payload and payload not in PAYLOAD_NONE: + await mqtt.async_publish( + self.hass, + self._config[topic], + payload, + self._config[CONF_QOS], + self._config[CONF_RETAIN], + self._config[CONF_ENCODING], + ) + + async def async_turn_on(self, **kwargs) -> None: + """Turn the siren on. + + This method is a coroutine. + """ + await self._async_publish( + CONF_COMMAND_TOPIC, + CONF_COMMAND_TEMPLATE, + self._config[CONF_PAYLOAD_ON], + kwargs, + ) + if self._optimistic: + # Optimistically assume that siren has changed state. + _LOGGER.debug("Writing state attributes %s", kwargs) + self._attr_is_on = True + self._update(kwargs) + self.async_write_ha_state() + + async def async_turn_off(self, **kwargs) -> None: + """Turn the siren off. + + This method is a coroutine. + """ + await self._async_publish( + CONF_COMMAND_TOPIC, + CONF_COMMAND_OFF_TEMPLATE, + self._config[CONF_PAYLOAD_OFF], + ) + + if self._optimistic: + # Optimistically assume that siren has changed state. + self._attr_is_on = False + self.async_write_ha_state() + + def _update(self, data: dict[str, Any]) -> None: + """Update the extra siren state attributes.""" + for attribute, support in SUPPORTED_ATTRIBUTES.items(): + if self._supported_features & support and attribute in data: + self._attr_extra_state_attributes[attribute] = data[attribute] diff --git a/tests/components/mqtt/test_siren.py b/tests/components/mqtt/test_siren.py new file mode 100644 index 00000000000000..e500bdb6ea7034 --- /dev/null +++ b/tests/components/mqtt/test_siren.py @@ -0,0 +1,884 @@ +"""The tests for the MQTT siren platform.""" +import copy +from unittest.mock import patch + +import pytest + +from homeassistant.components import siren +from homeassistant.components.siren.const import ATTR_VOLUME_LEVEL +from homeassistant.const import ( + ATTR_ASSUMED_STATE, + ATTR_ENTITY_ID, + ENTITY_MATCH_ALL, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, + STATE_UNKNOWN, +) +from homeassistant.setup import async_setup_component + +from .test_common import ( + help_test_availability_when_connection_lost, + help_test_availability_without_topic, + help_test_custom_availability_payload, + help_test_default_availability_payload, + help_test_discovery_broken, + help_test_discovery_removal, + help_test_discovery_update, + help_test_discovery_update_attr, + help_test_discovery_update_unchanged, + help_test_encoding_subscribable_topics, + help_test_entity_debug_info_message, + help_test_entity_device_info_remove, + help_test_entity_device_info_update, + help_test_entity_device_info_with_connection, + help_test_entity_device_info_with_identifier, + help_test_entity_id_update_discovery_update, + help_test_entity_id_update_subscriptions, + help_test_publishing_with_custom_encoding, + help_test_reloadable, + help_test_setting_attribute_via_mqtt_json_message, + help_test_setting_attribute_with_template, + help_test_setting_blocked_attribute_via_mqtt_json_message, + help_test_unique_id, + help_test_update_with_json_attrs_bad_JSON, + help_test_update_with_json_attrs_not_dict, +) + +from tests.common import async_fire_mqtt_message + +DEFAULT_CONFIG = { + siren.DOMAIN: {"platform": "mqtt", "name": "test", "command_topic": "test-topic"} +} + + +async def async_turn_on(hass, entity_id=ENTITY_MATCH_ALL, parameters={}) -> None: + """Turn all or specified siren on.""" + data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} + data.update(parameters) + + await hass.services.async_call(siren.DOMAIN, SERVICE_TURN_ON, data, blocking=True) + + +async def async_turn_off(hass, entity_id=ENTITY_MATCH_ALL) -> None: + """Turn all or specified siren off.""" + data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} + + await hass.services.async_call(siren.DOMAIN, SERVICE_TURN_OFF, data, blocking=True) + + +async def test_controlling_state_via_topic(hass, mqtt_mock): + """Test the controlling state via topic.""" + assert await async_setup_component( + hass, + siren.DOMAIN, + { + siren.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + "payload_on": 1, + "payload_off": 0, + } + }, + ) + await hass.async_block_till_done() + + state = hass.states.get("siren.test") + assert state.state == STATE_UNKNOWN + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + async_fire_mqtt_message(hass, "state-topic", "1") + + state = hass.states.get("siren.test") + assert state.state == STATE_ON + + async_fire_mqtt_message(hass, "state-topic", "0") + + state = hass.states.get("siren.test") + assert state.state == STATE_OFF + + +async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): + """Test the sending MQTT commands in optimistic mode.""" + assert await async_setup_component( + hass, + siren.DOMAIN, + { + siren.DOMAIN: { + "platform": "mqtt", + "name": "test", + "command_topic": "command-topic", + "payload_on": "beer on", + "payload_off": "beer off", + "qos": "2", + } + }, + ) + await hass.async_block_till_done() + + state = hass.states.get("siren.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await async_turn_on(hass, entity_id="siren.test") + + mqtt_mock.async_publish.assert_called_once_with( + "command-topic", '{"state": "beer on"}', 2, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("siren.test") + assert state.state == STATE_ON + + await async_turn_off(hass, entity_id="siren.test") + + mqtt_mock.async_publish.assert_called_once_with( + "command-topic", '{"state": "beer off"}', 2, False + ) + state = hass.states.get("siren.test") + assert state.state == STATE_OFF + + +async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock, caplog): + """Test the controlling state via topic and JSON message.""" + assert await async_setup_component( + hass, + siren.DOMAIN, + { + siren.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + "payload_on": "beer on", + "payload_off": "beer off", + "state_value_template": "{{ value_json.val }}", + } + }, + ) + await hass.async_block_till_done() + + state = hass.states.get("siren.test") + assert state.state == STATE_UNKNOWN + + async_fire_mqtt_message(hass, "state-topic", '{"val":"beer on"}') + + state = hass.states.get("siren.test") + assert state.state == STATE_ON + + async_fire_mqtt_message(hass, "state-topic", '{"val": null }') + state = hass.states.get("siren.test") + assert state.state == STATE_UNKNOWN + + async_fire_mqtt_message(hass, "state-topic", '{"val":"beer off"}') + + state = hass.states.get("siren.test") + assert state.state == STATE_OFF + + +async def test_controlling_state_and_attributes_with_json_message_without_template( + hass, mqtt_mock, caplog +): + """Test the controlling state via topic and JSON message without a value template.""" + assert await async_setup_component( + hass, + siren.DOMAIN, + { + siren.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + "payload_on": "beer on", + "payload_off": "beer off", + "available_tones": ["ping", "siren", "bell"], + } + }, + ) + await hass.async_block_till_done() + + state = hass.states.get("siren.test") + assert state.state == STATE_UNKNOWN + assert state.attributes.get(siren.ATTR_TONE) is None + assert state.attributes.get(siren.ATTR_DURATION) is None + assert state.attributes.get(siren.ATTR_VOLUME_LEVEL) is None + + async_fire_mqtt_message( + hass, + "state-topic", + '{"state":"beer on", "tone": "bell", "duration": 10, "volume_level": 0.5 }', + ) + + state = hass.states.get("siren.test") + assert state.state == STATE_ON + assert state.attributes.get(siren.ATTR_TONE) == "bell" + assert state.attributes.get(siren.ATTR_DURATION) == 10 + assert state.attributes.get(siren.ATTR_VOLUME_LEVEL) == 0.5 + + async_fire_mqtt_message( + hass, + "state-topic", + '{"state":"beer off", "duration": 5, "volume_level": 0.6}', + ) + + state = hass.states.get("siren.test") + assert state.state == STATE_OFF + assert state.attributes.get(siren.ATTR_TONE) == "bell" + assert state.attributes.get(siren.ATTR_DURATION) == 5 + assert state.attributes.get(siren.ATTR_VOLUME_LEVEL) == 0.6 + + # Test validation of received attributes, invalid + async_fire_mqtt_message( + hass, + "state-topic", + '{"state":"beer on", "duration": 6, "volume_level": 2 }', + ) + state = hass.states.get("siren.test") + assert ( + "Unable to update siren state attributes from payload '{'duration': 6, 'volume_level': 2}': value must be at most 1 for dictionary value @ data['volume_level']" + in caplog.text + ) + assert state.state == STATE_OFF + assert state.attributes.get(siren.ATTR_TONE) == "bell" + assert state.attributes.get(siren.ATTR_DURATION) == 5 + assert state.attributes.get(siren.ATTR_VOLUME_LEVEL) == 0.6 + + async_fire_mqtt_message( + hass, + "state-topic", + "{}", + ) + assert state.state == STATE_OFF + assert state.attributes.get(siren.ATTR_TONE) == "bell" + assert state.attributes.get(siren.ATTR_DURATION) == 5 + assert state.attributes.get(siren.ATTR_VOLUME_LEVEL) == 0.6 + assert ( + "Ignoring empty payload '{}' after rendering for topic state-topic" + in caplog.text + ) + + +async def test_filtering_not_supported_attributes_optimistic(hass, mqtt_mock): + """Test setting attributes with support flags optimistic.""" + config = { + "platform": "mqtt", + "command_topic": "command-topic", + "available_tones": ["ping", "siren", "bell"], + } + config1 = copy.deepcopy(config) + config1["name"] = "test1" + config1["support_duration"] = False + config2 = copy.deepcopy(config) + config2["name"] = "test2" + config2["support_volume_set"] = False + config3 = copy.deepcopy(config) + config3["name"] = "test3" + del config3["available_tones"] + + assert await async_setup_component( + hass, + siren.DOMAIN, + {siren.DOMAIN: [config1, config2, config3]}, + ) + await hass.async_block_till_done() + + state1 = hass.states.get("siren.test1") + assert state1.state == STATE_OFF + assert siren.ATTR_DURATION not in state1.attributes + assert siren.ATTR_AVAILABLE_TONES in state1.attributes + assert siren.ATTR_TONE in state1.attributes + assert siren.ATTR_VOLUME_LEVEL in state1.attributes + await async_turn_on( + hass, + entity_id="siren.test1", + parameters={ + siren.ATTR_DURATION: 22, + siren.ATTR_TONE: "ping", + ATTR_VOLUME_LEVEL: 0.88, + }, + ) + state1 = hass.states.get("siren.test1") + assert state1.attributes.get(siren.ATTR_TONE) == "ping" + assert state1.attributes.get(siren.ATTR_DURATION) is None + assert state1.attributes.get(siren.ATTR_VOLUME_LEVEL) == 0.88 + + state2 = hass.states.get("siren.test2") + assert siren.ATTR_DURATION in state2.attributes + assert siren.ATTR_AVAILABLE_TONES in state2.attributes + assert siren.ATTR_TONE in state2.attributes + assert siren.ATTR_VOLUME_LEVEL not in state2.attributes + await async_turn_on( + hass, + entity_id="siren.test2", + parameters={ + siren.ATTR_DURATION: 22, + siren.ATTR_TONE: "ping", + ATTR_VOLUME_LEVEL: 0.88, + }, + ) + state2 = hass.states.get("siren.test2") + assert state2.attributes.get(siren.ATTR_TONE) == "ping" + assert state2.attributes.get(siren.ATTR_DURATION) == 22 + assert state2.attributes.get(siren.ATTR_VOLUME_LEVEL) is None + + state3 = hass.states.get("siren.test3") + assert siren.ATTR_DURATION in state3.attributes + assert siren.ATTR_AVAILABLE_TONES not in state3.attributes + assert siren.ATTR_TONE not in state3.attributes + assert siren.ATTR_VOLUME_LEVEL in state3.attributes + await async_turn_on( + hass, + entity_id="siren.test3", + parameters={ + siren.ATTR_DURATION: 22, + siren.ATTR_TONE: "ping", + ATTR_VOLUME_LEVEL: 0.88, + }, + ) + state3 = hass.states.get("siren.test3") + assert state3.attributes.get(siren.ATTR_TONE) is None + assert state3.attributes.get(siren.ATTR_DURATION) == 22 + assert state3.attributes.get(siren.ATTR_VOLUME_LEVEL) == 0.88 + + +async def test_filtering_not_supported_attributes_via_state(hass, mqtt_mock): + """Test setting attributes with support flags via state.""" + config = { + "platform": "mqtt", + "command_topic": "command-topic", + "available_tones": ["ping", "siren", "bell"], + } + config1 = copy.deepcopy(config) + config1["name"] = "test1" + config1["state_topic"] = "state-topic1" + config1["support_duration"] = False + config2 = copy.deepcopy(config) + config2["name"] = "test2" + config2["state_topic"] = "state-topic2" + config2["support_volume_set"] = False + config3 = copy.deepcopy(config) + config3["name"] = "test3" + config3["state_topic"] = "state-topic3" + del config3["available_tones"] + + assert await async_setup_component( + hass, + siren.DOMAIN, + {siren.DOMAIN: [config1, config2, config3]}, + ) + await hass.async_block_till_done() + + state1 = hass.states.get("siren.test1") + assert state1.state == STATE_UNKNOWN + assert siren.ATTR_DURATION not in state1.attributes + assert siren.ATTR_AVAILABLE_TONES in state1.attributes + assert siren.ATTR_TONE in state1.attributes + assert siren.ATTR_VOLUME_LEVEL in state1.attributes + async_fire_mqtt_message( + hass, + "state-topic1", + '{"state":"ON", "duration": 22, "tone": "ping", "volume_level": 0.88}', + ) + await hass.async_block_till_done() + state1 = hass.states.get("siren.test1") + assert state1.attributes.get(siren.ATTR_TONE) == "ping" + assert state1.attributes.get(siren.ATTR_DURATION) is None + assert state1.attributes.get(siren.ATTR_VOLUME_LEVEL) == 0.88 + + state2 = hass.states.get("siren.test2") + assert siren.ATTR_DURATION in state2.attributes + assert siren.ATTR_AVAILABLE_TONES in state2.attributes + assert siren.ATTR_TONE in state2.attributes + assert siren.ATTR_VOLUME_LEVEL not in state2.attributes + async_fire_mqtt_message( + hass, + "state-topic2", + '{"state":"ON", "duration": 22, "tone": "ping", "volume_level": 0.88}', + ) + await hass.async_block_till_done() + state2 = hass.states.get("siren.test2") + assert state2.attributes.get(siren.ATTR_TONE) == "ping" + assert state2.attributes.get(siren.ATTR_DURATION) == 22 + assert state2.attributes.get(siren.ATTR_VOLUME_LEVEL) is None + + state3 = hass.states.get("siren.test3") + assert siren.ATTR_DURATION in state3.attributes + assert siren.ATTR_AVAILABLE_TONES not in state3.attributes + assert siren.ATTR_TONE not in state3.attributes + assert siren.ATTR_VOLUME_LEVEL in state3.attributes + async_fire_mqtt_message( + hass, + "state-topic3", + '{"state":"ON", "duration": 22, "tone": "ping", "volume_level": 0.88}', + ) + await hass.async_block_till_done() + state3 = hass.states.get("siren.test3") + assert state3.attributes.get(siren.ATTR_TONE) is None + assert state3.attributes.get(siren.ATTR_DURATION) == 22 + assert state3.attributes.get(siren.ATTR_VOLUME_LEVEL) == 0.88 + + +async def test_availability_when_connection_lost(hass, mqtt_mock): + """Test availability after MQTT disconnection.""" + await help_test_availability_when_connection_lost( + hass, mqtt_mock, siren.DOMAIN, DEFAULT_CONFIG + ) + + +async def test_availability_without_topic(hass, mqtt_mock): + """Test availability without defined availability topic.""" + await help_test_availability_without_topic( + hass, mqtt_mock, siren.DOMAIN, DEFAULT_CONFIG + ) + + +async def test_default_availability_payload(hass, mqtt_mock): + """Test availability by default payload with defined topic.""" + config = { + siren.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + "payload_on": 1, + "payload_off": 0, + } + } + + await help_test_default_availability_payload( + hass, mqtt_mock, siren.DOMAIN, config, True, "state-topic", "1" + ) + + +async def test_custom_availability_payload(hass, mqtt_mock): + """Test availability by custom payload with defined topic.""" + config = { + siren.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + "payload_on": 1, + "payload_off": 0, + } + } + + await help_test_custom_availability_payload( + hass, mqtt_mock, siren.DOMAIN, config, True, "state-topic", "1" + ) + + +async def test_custom_state_payload(hass, mqtt_mock): + """Test the state payload.""" + assert await async_setup_component( + hass, + siren.DOMAIN, + { + siren.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + "payload_on": 1, + "payload_off": 0, + "state_on": "HIGH", + "state_off": "LOW", + } + }, + ) + await hass.async_block_till_done() + + state = hass.states.get("siren.test") + assert state.state == STATE_UNKNOWN + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + async_fire_mqtt_message(hass, "state-topic", "HIGH") + + state = hass.states.get("siren.test") + assert state.state == STATE_ON + + async_fire_mqtt_message(hass, "state-topic", "LOW") + + state = hass.states.get("siren.test") + assert state.state == STATE_OFF + + +async def test_setting_attribute_via_mqtt_json_message(hass, mqtt_mock): + """Test the setting of attribute via MQTT with JSON payload.""" + await help_test_setting_attribute_via_mqtt_json_message( + hass, mqtt_mock, siren.DOMAIN, DEFAULT_CONFIG + ) + + +async def test_setting_blocked_attribute_via_mqtt_json_message(hass, mqtt_mock): + """Test the setting of attribute via MQTT with JSON payload.""" + await help_test_setting_blocked_attribute_via_mqtt_json_message( + hass, mqtt_mock, siren.DOMAIN, DEFAULT_CONFIG, {} + ) + + +async def test_setting_attribute_with_template(hass, mqtt_mock): + """Test the setting of attribute via MQTT with JSON payload.""" + await help_test_setting_attribute_with_template( + hass, mqtt_mock, siren.DOMAIN, DEFAULT_CONFIG + ) + + +async def test_update_with_json_attrs_not_dict(hass, mqtt_mock, caplog): + """Test attributes get extracted from a JSON result.""" + await help_test_update_with_json_attrs_not_dict( + hass, mqtt_mock, caplog, siren.DOMAIN, DEFAULT_CONFIG + ) + + +async def test_update_with_json_attrs_bad_JSON(hass, mqtt_mock, caplog): + """Test attributes get extracted from a JSON result.""" + await help_test_update_with_json_attrs_bad_JSON( + hass, mqtt_mock, caplog, siren.DOMAIN, DEFAULT_CONFIG + ) + + +async def test_discovery_update_attr(hass, mqtt_mock, caplog): + """Test update of discovered MQTTAttributes.""" + await help_test_discovery_update_attr( + hass, mqtt_mock, caplog, siren.DOMAIN, DEFAULT_CONFIG + ) + + +async def test_unique_id(hass, mqtt_mock): + """Test unique id option only creates one siren per unique_id.""" + config = { + siren.DOMAIN: [ + { + "platform": "mqtt", + "name": "Test 1", + "state_topic": "test-topic", + "command_topic": "command-topic", + "unique_id": "TOTALLY_UNIQUE", + }, + { + "platform": "mqtt", + "name": "Test 2", + "state_topic": "test-topic", + "command_topic": "command-topic", + "unique_id": "TOTALLY_UNIQUE", + }, + ] + } + await help_test_unique_id(hass, mqtt_mock, siren.DOMAIN, config) + + +async def test_discovery_removal_siren(hass, mqtt_mock, caplog): + """Test removal of discovered siren.""" + data = ( + '{ "name": "test",' + ' "state_topic": "test_topic",' + ' "command_topic": "test_topic" }' + ) + await help_test_discovery_removal(hass, mqtt_mock, caplog, siren.DOMAIN, data) + + +async def test_discovery_update_siren_topic_template(hass, mqtt_mock, caplog): + """Test update of discovered siren.""" + config1 = copy.deepcopy(DEFAULT_CONFIG[siren.DOMAIN]) + config2 = copy.deepcopy(DEFAULT_CONFIG[siren.DOMAIN]) + config1["name"] = "Beer" + config2["name"] = "Milk" + config1["state_topic"] = "siren/state1" + config2["state_topic"] = "siren/state2" + config1["state_value_template"] = "{{ value_json.state1.state }}" + config2["state_value_template"] = "{{ value_json.state2.state }}" + + state_data1 = [ + ([("siren/state1", '{"state1":{"state":"ON"}}')], "on", None), + ] + state_data2 = [ + ([("siren/state2", '{"state2":{"state":"OFF"}}')], "off", None), + ([("siren/state2", '{"state2":{"state":"ON"}}')], "on", None), + ([("siren/state1", '{"state1":{"state":"OFF"}}')], "on", None), + ([("siren/state1", '{"state2":{"state":"OFF"}}')], "on", None), + ([("siren/state2", '{"state1":{"state":"OFF"}}')], "on", None), + ([("siren/state2", '{"state2":{"state":"OFF"}}')], "off", None), + ] + + await help_test_discovery_update( + hass, + mqtt_mock, + caplog, + siren.DOMAIN, + config1, + config2, + state_data1=state_data1, + state_data2=state_data2, + ) + + +async def test_discovery_update_siren_template(hass, mqtt_mock, caplog): + """Test update of discovered siren.""" + config1 = copy.deepcopy(DEFAULT_CONFIG[siren.DOMAIN]) + config2 = copy.deepcopy(DEFAULT_CONFIG[siren.DOMAIN]) + config1["name"] = "Beer" + config2["name"] = "Milk" + config1["state_topic"] = "siren/state1" + config2["state_topic"] = "siren/state1" + config1["state_value_template"] = "{{ value_json.state1.state }}" + config2["state_value_template"] = "{{ value_json.state2.state }}" + + state_data1 = [ + ([("siren/state1", '{"state1":{"state":"ON"}}')], "on", None), + ] + state_data2 = [ + ([("siren/state1", '{"state2":{"state":"OFF"}}')], "off", None), + ([("siren/state1", '{"state2":{"state":"ON"}}')], "on", None), + ([("siren/state1", '{"state1":{"state":"OFF"}}')], "on", None), + ([("siren/state1", '{"state2":{"state":"OFF"}}')], "off", None), + ] + + await help_test_discovery_update( + hass, + mqtt_mock, + caplog, + siren.DOMAIN, + config1, + config2, + state_data1=state_data1, + state_data2=state_data2, + ) + + +async def test_command_templates(hass, mqtt_mock, caplog): + """Test siren with command templates optimistic.""" + config1 = copy.deepcopy(DEFAULT_CONFIG[siren.DOMAIN]) + config1["name"] = "Beer" + config1["available_tones"] = ["ping", "chimes"] + config1[ + "command_template" + ] = "CMD: {{ value }}, DURATION: {{ duration }}, TONE: {{ tone }}, VOLUME: {{ volume_level }}" + + config2 = copy.deepcopy(config1) + config2["name"] = "Milk" + config2["command_off_template"] = "CMD_OFF: {{ value }}" + + assert await async_setup_component( + hass, + siren.DOMAIN, + {siren.DOMAIN: [config1, config2]}, + ) + await hass.async_block_till_done() + + state1 = hass.states.get("siren.beer") + assert state1.state == STATE_OFF + assert state1.attributes.get(ATTR_ASSUMED_STATE) + + state2 = hass.states.get("siren.milk") + assert state2.state == STATE_OFF + assert state1.attributes.get(ATTR_ASSUMED_STATE) + + await async_turn_on( + hass, + entity_id="siren.beer", + parameters={ + siren.ATTR_DURATION: 22, + siren.ATTR_TONE: "ping", + ATTR_VOLUME_LEVEL: 0.88, + }, + ) + state1 = hass.states.get("siren.beer") + assert state1.attributes.get(siren.ATTR_TONE) == "ping" + assert state1.attributes.get(siren.ATTR_DURATION) == 22 + assert state1.attributes.get(siren.ATTR_VOLUME_LEVEL) == 0.88 + + mqtt_mock.async_publish.assert_any_call( + "test-topic", "CMD: ON, DURATION: 22, TONE: ping, VOLUME: 0.88", 0, False + ) + mqtt_mock.async_publish.call_count == 1 + mqtt_mock.reset_mock() + await async_turn_off( + hass, + entity_id="siren.beer", + ) + mqtt_mock.async_publish.assert_any_call( + "test-topic", "CMD: OFF, DURATION: , TONE: , VOLUME:", 0, False + ) + mqtt_mock.async_publish.call_count == 1 + mqtt_mock.reset_mock() + + await async_turn_on( + hass, + entity_id="siren.milk", + parameters={ + siren.ATTR_DURATION: 22, + siren.ATTR_TONE: "ping", + ATTR_VOLUME_LEVEL: 0.88, + }, + ) + state2 = hass.states.get("siren.milk") + assert state2.attributes.get(siren.ATTR_TONE) == "ping" + assert state2.attributes.get(siren.ATTR_DURATION) == 22 + assert state2.attributes.get(siren.ATTR_VOLUME_LEVEL) == 0.88 + await async_turn_off( + hass, + entity_id="siren.milk", + ) + mqtt_mock.async_publish.assert_any_call("test-topic", "CMD_OFF: OFF", 0, False) + mqtt_mock.async_publish.call_count == 1 + mqtt_mock.reset_mock() + + +async def test_discovery_update_unchanged_siren(hass, mqtt_mock, caplog): + """Test update of discovered siren.""" + data1 = ( + '{ "name": "Beer",' + ' "device_class": "siren",' + ' "state_topic": "test_topic",' + ' "command_topic": "test_topic" }' + ) + with patch( + "homeassistant.components.mqtt.siren.MqttSiren.discovery_update" + ) as discovery_update: + await help_test_discovery_update_unchanged( + hass, mqtt_mock, caplog, siren.DOMAIN, data1, discovery_update + ) + + +@pytest.mark.no_fail_on_log_exception +async def test_discovery_broken(hass, mqtt_mock, caplog): + """Test handling of bad discovery message.""" + data1 = '{ "name": "Beer" }' + data2 = ( + '{ "name": "Milk",' + ' "state_topic": "test_topic",' + ' "command_topic": "test_topic" }' + ) + await help_test_discovery_broken( + hass, mqtt_mock, caplog, siren.DOMAIN, data1, data2 + ) + + +async def test_entity_device_info_with_connection(hass, mqtt_mock): + """Test MQTT siren device registry integration.""" + await help_test_entity_device_info_with_connection( + hass, mqtt_mock, siren.DOMAIN, DEFAULT_CONFIG + ) + + +async def test_entity_device_info_with_identifier(hass, mqtt_mock): + """Test MQTT siren device registry integration.""" + await help_test_entity_device_info_with_identifier( + hass, mqtt_mock, siren.DOMAIN, DEFAULT_CONFIG + ) + + +async def test_entity_device_info_update(hass, mqtt_mock): + """Test device registry update.""" + await help_test_entity_device_info_update( + hass, mqtt_mock, siren.DOMAIN, DEFAULT_CONFIG + ) + + +async def test_entity_device_info_remove(hass, mqtt_mock): + """Test device registry remove.""" + await help_test_entity_device_info_remove( + hass, mqtt_mock, siren.DOMAIN, DEFAULT_CONFIG + ) + + +async def test_entity_id_update_subscriptions(hass, mqtt_mock): + """Test MQTT subscriptions are managed when entity_id is updated.""" + await help_test_entity_id_update_subscriptions( + hass, mqtt_mock, siren.DOMAIN, DEFAULT_CONFIG + ) + + +async def test_entity_id_update_discovery_update(hass, mqtt_mock): + """Test MQTT discovery update when entity_id is updated.""" + await help_test_entity_id_update_discovery_update( + hass, mqtt_mock, siren.DOMAIN, DEFAULT_CONFIG + ) + + +async def test_entity_debug_info_message(hass, mqtt_mock): + """Test MQTT debug info.""" + await help_test_entity_debug_info_message( + hass, mqtt_mock, siren.DOMAIN, DEFAULT_CONFIG + ) + + +@pytest.mark.parametrize( + "service,topic,parameters,payload,template", + [ + ( + siren.SERVICE_TURN_ON, + "command_topic", + None, + '{"state": "ON"}', + None, + ), + ( + siren.SERVICE_TURN_OFF, + "command_topic", + None, + '{"state": "OFF"}', + None, + ), + ], +) +async def test_publishing_with_custom_encoding( + hass, + mqtt_mock, + caplog, + service, + topic, + parameters, + payload, + template, +): + """Test publishing MQTT payload with command templates and different encoding.""" + domain = siren.DOMAIN + config = copy.deepcopy(DEFAULT_CONFIG[domain]) + config[siren.ATTR_AVAILABLE_TONES] = ["siren", "xylophone"] + + await help_test_publishing_with_custom_encoding( + hass, + mqtt_mock, + caplog, + domain, + config, + service, + topic, + parameters, + payload, + template, + ) + + +async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): + """Test reloading the MQTT platform.""" + domain = siren.DOMAIN + config = DEFAULT_CONFIG[domain] + await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) + + +@pytest.mark.parametrize( + "topic,value,attribute,attribute_value", + [ + ("state_topic", "ON", None, "on"), + ], +) +async def test_encoding_subscribable_topics( + hass, mqtt_mock, caplog, topic, value, attribute, attribute_value +): + """Test handling of incoming encoded payload.""" + await help_test_encoding_subscribable_topics( + hass, + mqtt_mock, + caplog, + siren.DOMAIN, + DEFAULT_CONFIG[siren.DOMAIN], + topic, + value, + attribute, + attribute_value, + ) From b0c36d77292ebcab9810f092c196ef00043ae8b2 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 31 Jan 2022 10:50:05 +0100 Subject: [PATCH 0125/1098] Add cast platform for extending Google Cast media_player (#65149) * Add cast platform for extending Google Cast media_player * Update tests * Refactor according to review comments * Add test for playing using a cast platform * Apply suggestions from code review Co-authored-by: jjlawren * Pass cast type instead of a filter function when browsing * Raise on invalid cast platform * Test media browsing Co-authored-by: jjlawren --- homeassistant/components/cast/__init__.py | 58 +++++ homeassistant/components/cast/media_player.py | 67 ++--- homeassistant/components/plex/cast.py | 75 ++++++ tests/components/cast/conftest.py | 10 - tests/components/cast/test_media_player.py | 241 ++++++++++++++---- 5 files changed, 352 insertions(+), 99 deletions(-) create mode 100644 homeassistant/components/plex/cast.py diff --git a/homeassistant/components/cast/__init__.py b/homeassistant/components/cast/__init__.py index 2623de0933e7af..cb631e17ccdf06 100644 --- a/homeassistant/components/cast/__init__.py +++ b/homeassistant/components/cast/__init__.py @@ -1,12 +1,21 @@ """Component to embed Google Cast.""" +from __future__ import annotations + import logging +from typing import Protocol +from pychromecast import Chromecast import voluptuous as vol +from homeassistant.components.media_player import BrowseMedia from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.integration_platform import ( + async_process_integration_platforms, +) from homeassistant.helpers.typing import ConfigType from . import home_assistant_cast @@ -49,9 +58,58 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Cast from a config entry.""" await home_assistant_cast.async_setup_ha_cast(hass, entry) hass.config_entries.async_setup_platforms(entry, PLATFORMS) + hass.data[DOMAIN] = {} + await async_process_integration_platforms(hass, DOMAIN, _register_cast_platform) return True +class CastProtocol(Protocol): + """Define the format of cast platforms.""" + + async def async_get_media_browser_root_object( + self, cast_type: str + ) -> list[BrowseMedia]: + """Create a list of root objects for media browsing.""" + + async def async_browse_media( + self, + hass: HomeAssistant, + media_content_type: str, + media_content_id: str, + cast_type: str, + ) -> BrowseMedia | None: + """Browse media. + + Return a BrowseMedia object or None if the media does not belong to this platform. + """ + + async def async_play_media( + self, + hass: HomeAssistant, + cast_entity_id: str, + chromecast: Chromecast, + media_type: str, + media_id: str, + ) -> bool: + """Play media. + + Return True if the media is played by the platform, False if not. + """ + + +async def _register_cast_platform( + hass: HomeAssistant, integration_domain: str, platform: CastProtocol +): + """Register a cast platform.""" + if ( + not hasattr(platform, "async_get_media_browser_root_object") + or not hasattr(platform, "async_browse_media") + or not hasattr(platform, "async_play_media") + ): + raise HomeAssistantError(f"Invalid cast platform {platform}") + hass.data[DOMAIN][integration_domain] = platform + + async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: """Remove Home Assistant Cast user.""" await home_assistant_cast.async_remove_user(hass, entry) diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 6cca4cfa20b5d6..0b7bc5e5748ffa 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -11,7 +11,6 @@ import pychromecast from pychromecast.controllers.homeassistant import HomeAssistantController from pychromecast.controllers.multizone import MultizoneManager -from pychromecast.controllers.plex import PlexController from pychromecast.controllers.receiver import VOLUME_CONTROL_TYPE_FIXED from pychromecast.quick_play import quick_play from pychromecast.socket_client import ( @@ -20,7 +19,7 @@ ) import voluptuous as vol -from homeassistant.components import media_source, plex, zeroconf +from homeassistant.components import media_source, zeroconf from homeassistant.components.http.auth import async_sign_path from homeassistant.components.media_player import ( BrowseError, @@ -29,7 +28,6 @@ ) from homeassistant.components.media_player.const import ( ATTR_MEDIA_EXTRA, - MEDIA_CLASS_APP, MEDIA_CLASS_DIRECTORY, MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, @@ -47,8 +45,6 @@ SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, ) -from homeassistant.components.plex.const import PLEX_URI_SCHEME -from homeassistant.components.plex.services import lookup_plex_media from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CAST_APP_ID_HOMEASSISTANT_LOVELACE, @@ -463,21 +459,15 @@ def media_seek(self, position): async def _async_root_payload(self, content_filter): """Generate root node.""" children = [] - # Add external sources - if "plex" in self.hass.config.components: - children.append( - BrowseMedia( - title="Plex", - media_class=MEDIA_CLASS_APP, - media_content_id="", - media_content_type="plex", - thumbnail="https://brands.home-assistant.io/_/plex/logo.png", - can_play=False, - can_expand=True, + # Add media browsers + for platform in self.hass.data[CAST_DOMAIN].values(): + children.extend( + await platform.async_get_media_browser_root_object( + self._chromecast.cast_type ) ) - # Add local media source + # Add media sources try: result = await media_source.async_browse_media( self.hass, None, content_filter=content_filter @@ -519,14 +509,15 @@ def audio_content_filter(item): if media_content_id is None: return await self._async_root_payload(content_filter) - if plex.is_plex_media_id(media_content_id): - return await plex.async_browse_media( - self.hass, media_content_type, media_content_id, platform=CAST_DOMAIN - ) - if media_content_type == "plex": - return await plex.async_browse_media( - self.hass, None, None, platform=CAST_DOMAIN + for platform in self.hass.data[CAST_DOMAIN].values(): + browse_media = await platform.async_browse_media( + self.hass, + media_content_type, + media_content_id, + self._chromecast.cast_type, ) + if browse_media: + return browse_media return await media_source.async_browse_media( self.hass, media_content_id, content_filter=content_filter @@ -556,7 +547,7 @@ async def async_play_media(self, media_type, media_id, **kwargs): extra = kwargs.get(ATTR_MEDIA_EXTRA, {}) metadata = extra.get("metadata") - # We do not want this to be forwarded to a group + # Handle media supported by a known cast app if media_type == CAST_DOMAIN: try: app_data = json.loads(media_id) @@ -588,23 +579,21 @@ async def async_play_media(self, media_type, media_id, **kwargs): ) except NotImplementedError: _LOGGER.error("App %s not supported", app_name) + return - # Handle plex - elif media_id and media_id.startswith(PLEX_URI_SCHEME): - media_id = media_id[len(PLEX_URI_SCHEME) :] - media = await self.hass.async_add_executor_job( - lookup_plex_media, self.hass, media_type, media_id + # Try the cast platforms + for platform in self.hass.data[CAST_DOMAIN].values(): + result = await platform.async_play_media( + self.hass, self.entity_id, self._chromecast, media_type, media_id ) - if media is None: + if result: return - controller = PlexController() - self._chromecast.register_handler(controller) - await self.hass.async_add_executor_job(controller.play_media, media) - else: - app_data = {"media_id": media_id, "media_type": media_type, **extra} - await self.hass.async_add_executor_job( - quick_play, self._chromecast, "default_media_receiver", app_data - ) + + # Default to play with the default media receiver + app_data = {"media_id": media_id, "media_type": media_type, **extra} + await self.hass.async_add_executor_job( + quick_play, self._chromecast, "default_media_receiver", app_data + ) def _media_status(self): """ diff --git a/homeassistant/components/plex/cast.py b/homeassistant/components/plex/cast.py new file mode 100644 index 00000000000000..c2a09ff8810766 --- /dev/null +++ b/homeassistant/components/plex/cast.py @@ -0,0 +1,75 @@ +"""Google Cast support for the Plex component.""" +from __future__ import annotations + +from pychromecast import Chromecast +from pychromecast.controllers.plex import PlexController + +from homeassistant.components.cast.const import DOMAIN as CAST_DOMAIN +from homeassistant.components.media_player import BrowseMedia +from homeassistant.components.media_player.const import MEDIA_CLASS_APP +from homeassistant.core import HomeAssistant + +from . import async_browse_media as async_browse_plex_media, is_plex_media_id +from .const import PLEX_URI_SCHEME +from .services import lookup_plex_media + + +async def async_get_media_browser_root_object(cast_type: str) -> list[BrowseMedia]: + """Create a root object for media browsing.""" + return [ + BrowseMedia( + title="Plex", + media_class=MEDIA_CLASS_APP, + media_content_id="", + media_content_type="plex", + thumbnail="https://brands.home-assistant.io/_/plex/logo.png", + can_play=False, + can_expand=True, + ) + ] + + +async def async_browse_media( + hass: HomeAssistant, + media_content_type: str, + media_content_id: str, + cast_type: str, +) -> BrowseMedia | None: + """Browse media.""" + if is_plex_media_id(media_content_id): + return await async_browse_plex_media( + hass, media_content_type, media_content_id, platform=CAST_DOMAIN + ) + if media_content_type == "plex": + return await async_browse_plex_media(hass, None, None, platform=CAST_DOMAIN) + return None + + +def _play_media( + hass: HomeAssistant, chromecast: Chromecast, media_type: str, media_id: str +) -> None: + """Play media.""" + media_id = media_id[len(PLEX_URI_SCHEME) :] + media = lookup_plex_media(hass, media_type, media_id) + if media is None: + return + controller = PlexController() + chromecast.register_handler(controller) + controller.play_media(media) + + +async def async_play_media( + hass: HomeAssistant, + cast_entity_id: str, + chromecast: Chromecast, + media_type: str, + media_id: str, +) -> bool: + """Play media.""" + if media_id and media_id.startswith(PLEX_URI_SCHEME): + await hass.async_add_executor_job( + _play_media, hass, chromecast, media_type, media_id + ) + return True + + return False diff --git a/tests/components/cast/conftest.py b/tests/components/cast/conftest.py index 2d0114c011080c..3b96f378906e9c 100644 --- a/tests/components/cast/conftest.py +++ b/tests/components/cast/conftest.py @@ -26,12 +26,6 @@ def mz_mock(): return MagicMock(spec_set=pychromecast.controllers.multizone.MultizoneManager) -@pytest.fixture() -def plex_mock(): - """Mock pychromecast PlexController.""" - return MagicMock(spec_set=pychromecast.controllers.plex.PlexController) - - @pytest.fixture() def quick_play_mock(): """Mock pychromecast quick_play.""" @@ -51,7 +45,6 @@ def cast_mock( castbrowser_mock, get_chromecast_mock, get_multizone_status_mock, - plex_mock, ): """Mock pychromecast.""" ignore_cec_orig = list(pychromecast.IGNORE_CEC) @@ -65,9 +58,6 @@ def cast_mock( ), patch( "homeassistant.components.cast.media_player.MultizoneManager", return_value=mz_mock, - ), patch( - "homeassistant.components.cast.media_player.PlexController", - return_value=plex_mock, ), patch( "homeassistant.components.cast.media_player.zeroconf.async_get_instance", AsyncMock(), diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 5e14b5e7bd6f30..584f40016e0c08 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -3,7 +3,7 @@ from __future__ import annotations import json -from unittest.mock import ANY, MagicMock, patch +from unittest.mock import ANY, AsyncMock, MagicMock, Mock, patch from uuid import UUID import attr @@ -14,7 +14,10 @@ from homeassistant.components import media_player, tts from homeassistant.components.cast import media_player as cast from homeassistant.components.cast.media_player import ChromecastInfo +from homeassistant.components.media_player import BrowseMedia from homeassistant.components.media_player.const import ( + MEDIA_CLASS_APP, + MEDIA_CLASS_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, @@ -38,7 +41,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry, assert_setup_component +from tests.common import MockConfigEntry, assert_setup_component, mock_platform from tests.components.media_player import common # pylint: disable=invalid-name @@ -844,54 +847,6 @@ async def test_entity_play_media_cast(hass: HomeAssistant, quick_play_mock): ) -async def test_entity_play_media_plex(hass: HomeAssistant, plex_mock): - """Test playing media.""" - entity_id = "media_player.speaker" - - info = get_fake_chromecast_info() - - chromecast, _ = await async_setup_media_player_cast(hass, info) - _, conn_status_cb, _ = get_status_callbacks(chromecast) - - connection_status = MagicMock() - connection_status.status = "CONNECTED" - conn_status_cb(connection_status) - await hass.async_block_till_done() - - with patch( - "homeassistant.components.cast.media_player.lookup_plex_media", - return_value=None, - ): - await hass.services.async_call( - media_player.DOMAIN, - media_player.SERVICE_PLAY_MEDIA, - { - ATTR_ENTITY_ID: entity_id, - media_player.ATTR_MEDIA_CONTENT_TYPE: "music", - media_player.ATTR_MEDIA_CONTENT_ID: 'plex://{"library_name": "Music", "artist.title": "Not an Artist"}', - }, - blocking=True, - ) - assert not plex_mock.play_media.called - - mock_plex_media = MagicMock() - with patch( - "homeassistant.components.cast.media_player.lookup_plex_media", - return_value=mock_plex_media, - ): - await hass.services.async_call( - media_player.DOMAIN, - media_player.SERVICE_PLAY_MEDIA, - { - ATTR_ENTITY_ID: entity_id, - media_player.ATTR_MEDIA_CONTENT_TYPE: "music", - media_player.ATTR_MEDIA_CONTENT_ID: 'plex://{"library_name": "Music", "artist.title": "Artist"}', - }, - blocking=True, - ) - plex_mock.play_media.assert_called_once_with(mock_plex_media) - - async def test_entity_play_media_cast_invalid(hass, caplog, quick_play_mock): """Test playing media.""" entity_id = "media_player.speaker" @@ -1578,3 +1533,189 @@ async def test_entry_setup_list_config(hass: HomeAssistant): assert set(config_entry.data["uuid"]) == {"bla", "blu"} assert set(config_entry.data["ignore_cec"]) == {"cast1", "cast2", "cast3"} assert set(pychromecast.IGNORE_CEC) == {"cast1", "cast2", "cast3"} + + +async def test_invalid_cast_platform(hass: HomeAssistant, caplog): + """Test we can play media through a cast platform.""" + cast_platform_mock = Mock() + del cast_platform_mock.async_get_media_browser_root_object + del cast_platform_mock.async_browse_media + del cast_platform_mock.async_play_media + mock_platform(hass, "test.cast", cast_platform_mock) + + await async_setup_component(hass, "test", {"test": {}}) + await hass.async_block_till_done() + + info = get_fake_chromecast_info() + await async_setup_media_player_cast(hass, info) + + assert "Invalid cast platform Date: Mon, 31 Jan 2022 11:45:48 +0100 Subject: [PATCH 0126/1098] Correct cast media browse filter for audio groups (#65288) --- homeassistant/components/cast/media_player.py | 5 +- tests/components/cast/test_media_player.py | 105 ++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 0b7bc5e5748ffa..5f3381896dac24 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -498,7 +498,10 @@ async def async_browse_media(self, media_content_type=None, media_content_id=Non """Implement the websocket media browsing helper.""" content_filter = None - if self._chromecast.cast_type == pychromecast.const.CAST_TYPE_AUDIO: + if self._chromecast.cast_type in ( + pychromecast.const.CAST_TYPE_AUDIO, + pychromecast.const.CAST_TYPE_GROUP, + ): def audio_content_filter(item): """Filter non audio content.""" diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 584f40016e0c08..bd22a558314e74 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -759,6 +759,111 @@ async def test_supported_features( assert state.attributes.get("supported_features") == supported_features +async def test_entity_browse_media(hass: HomeAssistant, hass_ws_client): + """Test we can browse media.""" + await async_setup_component(hass, "media_source", {"media_source": {}}) + + info = get_fake_chromecast_info() + + chromecast, _ = await async_setup_media_player_cast(hass, info) + _, conn_status_cb, _ = get_status_callbacks(chromecast) + + connection_status = MagicMock() + connection_status.status = "CONNECTED" + conn_status_cb(connection_status) + await hass.async_block_till_done() + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "media_player/browse_media", + "entity_id": "media_player.speaker", + } + ) + response = await client.receive_json() + assert response["success"] + expected_child_1 = { + "title": "Epic Sax Guy 10 Hours.mp4", + "media_class": "video", + "media_content_type": "video/mp4", + "media_content_id": "media-source://media_source/local/Epic Sax Guy 10 Hours.mp4", + "can_play": True, + "can_expand": False, + "children_media_class": None, + "thumbnail": None, + } + assert expected_child_1 in response["result"]["children"] + + expected_child_2 = { + "title": "test.mp3", + "media_class": "music", + "media_content_type": "audio/mpeg", + "media_content_id": "media-source://media_source/local/test.mp3", + "can_play": True, + "can_expand": False, + "children_media_class": None, + "thumbnail": None, + } + assert expected_child_2 in response["result"]["children"] + + +@pytest.mark.parametrize( + "cast_type", + [pychromecast.const.CAST_TYPE_AUDIO, pychromecast.const.CAST_TYPE_GROUP], +) +async def test_entity_browse_media_audio_only( + hass: HomeAssistant, hass_ws_client, cast_type +): + """Test we can browse media.""" + await async_setup_component(hass, "media_source", {"media_source": {}}) + + info = get_fake_chromecast_info() + + chromecast, _ = await async_setup_media_player_cast(hass, info) + chromecast.cast_type = cast_type + _, conn_status_cb, _ = get_status_callbacks(chromecast) + + connection_status = MagicMock() + connection_status.status = "CONNECTED" + conn_status_cb(connection_status) + await hass.async_block_till_done() + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "media_player/browse_media", + "entity_id": "media_player.speaker", + } + ) + response = await client.receive_json() + assert response["success"] + expected_child_1 = { + "title": "Epic Sax Guy 10 Hours.mp4", + "media_class": "video", + "media_content_type": "video/mp4", + "media_content_id": "media-source://media_source/local/Epic Sax Guy 10 Hours.mp4", + "can_play": True, + "can_expand": False, + "children_media_class": None, + "thumbnail": None, + } + assert expected_child_1 not in response["result"]["children"] + + expected_child_2 = { + "title": "test.mp3", + "media_class": "music", + "media_content_type": "audio/mpeg", + "media_content_id": "media-source://media_source/local/test.mp3", + "can_play": True, + "can_expand": False, + "children_media_class": None, + "thumbnail": None, + } + assert expected_child_2 in response["result"]["children"] + + async def test_entity_play_media(hass: HomeAssistant, quick_play_mock): """Test playing media.""" entity_id = "media_player.speaker" From 9b5757dff5f58c8879db94764daad4ea11f8478a Mon Sep 17 00:00:00 2001 From: fOmey Date: Mon, 31 Jan 2022 22:37:43 +1100 Subject: [PATCH 0127/1098] Tuya fan percentage fix (#65225) --- homeassistant/components/tuya/fan.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tuya/fan.py b/homeassistant/components/tuya/fan.py index 42849d4498d9e3..e2e98e3fd5ccfc 100644 --- a/homeassistant/components/tuya/fan.py +++ b/homeassistant/components/tuya/fan.py @@ -137,7 +137,7 @@ def set_percentage(self, percentage: int) -> None: [ { "code": self._speed.dpcode, - "value": int(self._speed.remap_value_from(percentage, 0, 100)), + "value": int(self._speed.remap_value_from(percentage, 1, 100)), } ] ) @@ -178,7 +178,7 @@ def turn_on( commands.append( { "code": self._speed.dpcode, - "value": int(self._speed.remap_value_from(percentage, 0, 100)), + "value": int(self._speed.remap_value_from(percentage, 1, 100)), } ) return @@ -248,7 +248,7 @@ def percentage(self) -> int | None: if self._speed is not None: if (value := self.device.status.get(self._speed.dpcode)) is None: return None - return int(self._speed.remap_value_to(value, 0, 100)) + return int(self._speed.remap_value_to(value, 1, 100)) if self._speeds is not None: if (value := self.device.status.get(self._speeds.dpcode)) is None: From ade656a333886fd6971afd4817cb6c208cd9365b Mon Sep 17 00:00:00 2001 From: Duco Sebel <74970928+DCSBL@users.noreply.github.com> Date: Mon, 31 Jan 2022 12:49:18 +0100 Subject: [PATCH 0128/1098] Fix HomeWizard unclosed clientsession error when closing Home Assistant (#65296) --- homeassistant/components/homewizard/coordinator.py | 5 ++++- homeassistant/components/homewizard/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/homewizard/coordinator.py b/homeassistant/components/homewizard/coordinator.py index a2612d07464a1a..2cce88cbe36a47 100644 --- a/homeassistant/components/homewizard/coordinator.py +++ b/homeassistant/components/homewizard/coordinator.py @@ -8,6 +8,7 @@ import async_timeout from homeassistant.core import HomeAssistant +from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import DOMAIN, UPDATE_INTERVAL, DeviceResponseEntry @@ -28,7 +29,9 @@ def __init__( """Initialize Update Coordinator.""" super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL) - self.api = aiohwenergy.HomeWizardEnergy(host) + + session = async_get_clientsession(hass) + self.api = aiohwenergy.HomeWizardEnergy(host, clientsession=session) async def _async_update_data(self) -> DeviceResponseEntry: """Fetch all device and sensor data from api.""" diff --git a/homeassistant/components/homewizard/manifest.json b/homeassistant/components/homewizard/manifest.json index 870b52446ba651..0705da4938b720 100644 --- a/homeassistant/components/homewizard/manifest.json +++ b/homeassistant/components/homewizard/manifest.json @@ -5,7 +5,7 @@ "codeowners": ["@DCSBL"], "dependencies": [], "requirements": [ - "aiohwenergy==0.7.0" + "aiohwenergy==0.8.0" ], "zeroconf": ["_hwenergy._tcp.local."], "config_flow": true, diff --git a/requirements_all.txt b/requirements_all.txt index 65a6067fef7497..c43c8e4f475129 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -194,7 +194,7 @@ aiohttp_cors==0.7.0 aiohue==3.0.11 # homeassistant.components.homewizard -aiohwenergy==0.7.0 +aiohwenergy==0.8.0 # homeassistant.components.imap aioimaplib==0.9.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bde0607e8b9a30..852128d2916555 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -144,7 +144,7 @@ aiohttp_cors==0.7.0 aiohue==3.0.11 # homeassistant.components.homewizard -aiohwenergy==0.7.0 +aiohwenergy==0.8.0 # homeassistant.components.apache_kafka aiokafka==0.6.0 From cc14acb178e87bfec8658b9794f7b0b8e2ef99ec Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 31 Jan 2022 13:00:10 +0100 Subject: [PATCH 0129/1098] Bump pynetgear to 0.9.1 (#65290) --- homeassistant/components/netgear/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/netgear/manifest.json b/homeassistant/components/netgear/manifest.json index f862ca73e6c1fb..b2c7ddf6be2b0f 100644 --- a/homeassistant/components/netgear/manifest.json +++ b/homeassistant/components/netgear/manifest.json @@ -2,7 +2,7 @@ "domain": "netgear", "name": "NETGEAR", "documentation": "https://www.home-assistant.io/integrations/netgear", - "requirements": ["pynetgear==0.9.0"], + "requirements": ["pynetgear==0.9.1"], "codeowners": ["@hacf-fr", "@Quentame", "@starkillerOG"], "iot_class": "local_polling", "config_flow": true, diff --git a/requirements_all.txt b/requirements_all.txt index c43c8e4f475129..2be34511a1c1dc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1696,7 +1696,7 @@ pymyq==3.1.4 pymysensors==0.22.1 # homeassistant.components.netgear -pynetgear==0.9.0 +pynetgear==0.9.1 # homeassistant.components.netio pynetio==0.1.9.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 852128d2916555..465f352b1154fe 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1071,7 +1071,7 @@ pymyq==3.1.4 pymysensors==0.22.1 # homeassistant.components.netgear -pynetgear==0.9.0 +pynetgear==0.9.1 # homeassistant.components.nina pynina==0.1.4 From 216ac6547561d33c1c31a9f000074844b19363a9 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Mon, 31 Jan 2022 04:08:43 -0800 Subject: [PATCH 0130/1098] Bump pyoverkiz to 1.3.2 (#65293) --- homeassistant/components/overkiz/manifest.json | 10 ++++++++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/overkiz/manifest.json b/homeassistant/components/overkiz/manifest.json index 0d235982462ff7..47c5c45f2ad7ed 100644 --- a/homeassistant/components/overkiz/manifest.json +++ b/homeassistant/components/overkiz/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/overkiz", "requirements": [ - "pyoverkiz==1.3.1" + "pyoverkiz==1.3.2" ], "zeroconf": [ { @@ -24,5 +24,11 @@ "@tetienne" ], "iot_class": "cloud_polling", - "loggers": ["boto3", "botocore", "pyhumps", "pyoverkiz", "s3transfer"] + "loggers": [ + "boto3", + "botocore", + "pyhumps", + "pyoverkiz", + "s3transfer" + ] } \ No newline at end of file diff --git a/requirements_all.txt b/requirements_all.txt index 2be34511a1c1dc..1d28e4d702531d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1746,7 +1746,7 @@ pyotgw==1.1b1 pyotp==2.6.0 # homeassistant.components.overkiz -pyoverkiz==1.3.1 +pyoverkiz==1.3.2 # homeassistant.components.openweathermap pyowm==3.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 465f352b1154fe..4242cc595b54e9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1109,7 +1109,7 @@ pyotgw==1.1b1 pyotp==2.6.0 # homeassistant.components.overkiz -pyoverkiz==1.3.1 +pyoverkiz==1.3.2 # homeassistant.components.openweathermap pyowm==3.2.0 From 4991f1a693f79a10dacd9bf553e399ea448984cd Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Mon, 31 Jan 2022 13:16:45 +0100 Subject: [PATCH 0131/1098] Use super() in mqtt siren init (#65291) --- homeassistant/components/mqtt/siren.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/mqtt/siren.py b/homeassistant/components/mqtt/siren.py index e83aee2622845b..6a268881593bd3 100644 --- a/homeassistant/components/mqtt/siren.py +++ b/homeassistant/components/mqtt/siren.py @@ -161,7 +161,7 @@ def __init__(self, hass, config, config_entry, discovery_data): self.target = None - MqttEntity.__init__(self, hass, config, config_entry, discovery_data) + super().__init__(hass, config, config_entry, discovery_data) @staticmethod def config_schema(): From a3f5582010e909806f219390f7a9ff073f8945ed Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 31 Jan 2022 16:31:02 +0100 Subject: [PATCH 0132/1098] Update adguard to 0.5.1 (#65305) --- homeassistant/components/adguard/config_flow.py | 4 ++-- homeassistant/components/adguard/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/adguard/config_flow.py b/homeassistant/components/adguard/config_flow.py index aadbed49980288..9afdc4e02b8132 100644 --- a/homeassistant/components/adguard/config_flow.py +++ b/homeassistant/components/adguard/config_flow.py @@ -80,8 +80,8 @@ async def async_step_user( adguard = AdGuardHome( user_input[CONF_HOST], port=user_input[CONF_PORT], - username=username, # type:ignore[arg-type] - password=password, # type:ignore[arg-type] + username=username, + password=password, tls=user_input[CONF_SSL], verify_ssl=user_input[CONF_VERIFY_SSL], session=session, diff --git a/homeassistant/components/adguard/manifest.json b/homeassistant/components/adguard/manifest.json index 506ddfcfce0f3b..cf1210b0884e7e 100644 --- a/homeassistant/components/adguard/manifest.json +++ b/homeassistant/components/adguard/manifest.json @@ -3,7 +3,7 @@ "name": "AdGuard Home", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/adguard", - "requirements": ["adguardhome==0.5.0"], + "requirements": ["adguardhome==0.5.1"], "codeowners": ["@frenck"], "iot_class": "local_polling", "loggers": ["adguardhome"] diff --git a/requirements_all.txt b/requirements_all.txt index 1d28e4d702531d..ef2142f9e66b74 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -111,7 +111,7 @@ adb-shell[async]==0.4.0 adext==0.4.2 # homeassistant.components.adguard -adguardhome==0.5.0 +adguardhome==0.5.1 # homeassistant.components.advantage_air advantage_air==0.2.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4242cc595b54e9..8c6e695181758a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -67,7 +67,7 @@ adb-shell[async]==0.4.0 adext==0.4.2 # homeassistant.components.adguard -adguardhome==0.5.0 +adguardhome==0.5.1 # homeassistant.components.advantage_air advantage_air==0.2.5 From ac32af7004fa8740b50bba3b9a6e704fd9499d8d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 31 Jan 2022 17:08:21 +0100 Subject: [PATCH 0133/1098] Update wled to 0.13.0 (#65312) --- homeassistant/components/wled/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/wled/manifest.json b/homeassistant/components/wled/manifest.json index d99f07a78ae312..eb99b8519a9bc5 100644 --- a/homeassistant/components/wled/manifest.json +++ b/homeassistant/components/wled/manifest.json @@ -3,7 +3,7 @@ "name": "WLED", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/wled", - "requirements": ["wled==0.12.0"], + "requirements": ["wled==0.13.0"], "zeroconf": ["_wled._tcp.local."], "codeowners": ["@frenck"], "quality_scale": "platinum", diff --git a/requirements_all.txt b/requirements_all.txt index ef2142f9e66b74..c89eb1598b8e98 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2481,7 +2481,7 @@ wirelesstagpy==0.8.1 withings-api==2.3.2 # homeassistant.components.wled -wled==0.12.0 +wled==0.13.0 # homeassistant.components.wolflink wolf_smartset==0.1.11 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8c6e695181758a..e30a8be77e0d70 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1521,7 +1521,7 @@ wiffi==1.1.0 withings-api==2.3.2 # homeassistant.components.wled -wled==0.12.0 +wled==0.13.0 # homeassistant.components.wolflink wolf_smartset==0.1.11 From fab9c4aa20b4c2549691d0aa5066798a0259e803 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 31 Jan 2022 10:21:47 -0600 Subject: [PATCH 0134/1098] Improve reliability of august setup with recent api changes (#65314) --- homeassistant/components/august/__init__.py | 28 ++++++++++++++++----- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index 3fc113c6038c06..5d51017bfd6b6a 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -206,12 +206,28 @@ async def _async_refresh(self, time): await self._async_refresh_device_detail_by_ids(self._subscriptions.keys()) async def _async_refresh_device_detail_by_ids(self, device_ids_list): - await asyncio.gather( - *( - self._async_refresh_device_detail_by_id(device_id) - for device_id in device_ids_list - ) - ) + """Refresh each device in sequence. + + This used to be a gather but it was less reliable with august's + recent api changes. + + The august api has been timing out for some devices so + we want the ones that it isn't timing out for to keep working. + """ + for device_id in device_ids_list: + try: + await self._async_refresh_device_detail_by_id(device_id) + except asyncio.TimeoutError: + _LOGGER.warning( + "Timed out calling august api during refresh of device: %s", + device_id, + ) + except (ClientResponseError, CannotConnect) as err: + _LOGGER.warning( + "Error from august api during refresh of device: %s", + device_id, + exc_info=err, + ) async def _async_refresh_device_detail_by_id(self, device_id): if device_id in self._locks_by_id: From 5891f65c7ef541c88e867523f9cbc826aaab55ad Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Mon, 31 Jan 2022 09:09:17 -0800 Subject: [PATCH 0135/1098] Bump androidtv to 0.0.61 (#65315) --- homeassistant/components/androidtv/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/androidtv/manifest.json b/homeassistant/components/androidtv/manifest.json index 735b4b0a4313a4..c5c11b7d3a9502 100644 --- a/homeassistant/components/androidtv/manifest.json +++ b/homeassistant/components/androidtv/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/androidtv", "requirements": [ "adb-shell[async]==0.4.0", - "androidtv[async]==0.0.60", + "androidtv[async]==0.0.61", "pure-python-adb[async]==0.3.0.dev0" ], "codeowners": ["@JeffLIrion", "@ollo69"], diff --git a/requirements_all.txt b/requirements_all.txt index c89eb1598b8e98..c8b82662ceb51f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -311,7 +311,7 @@ ambiclimate==0.2.1 amcrest==1.9.3 # homeassistant.components.androidtv -androidtv[async]==0.0.60 +androidtv[async]==0.0.61 # homeassistant.components.anel_pwrctrl anel_pwrctrl-homeassistant==0.0.1.dev2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e30a8be77e0d70..5ce35d69980795 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -237,7 +237,7 @@ amberelectric==1.0.3 ambiclimate==0.2.1 # homeassistant.components.androidtv -androidtv[async]==0.0.60 +androidtv[async]==0.0.61 # homeassistant.components.apns apns2==0.3.0 From ce6048e705bf196aecf6972fbe34efcf767fc91e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 31 Jan 2022 18:15:13 +0100 Subject: [PATCH 0136/1098] Fix missing expiration data in Whois information (#65313) --- homeassistant/components/whois/sensor.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/whois/sensor.py b/homeassistant/components/whois/sensor.py index 6df920f385c3b9..3d0b25640b3a58 100644 --- a/homeassistant/components/whois/sensor.py +++ b/homeassistant/components/whois/sensor.py @@ -233,17 +233,20 @@ def extra_state_attributes(self) -> dict[str, int | float | None] | None: if self.coordinator.data is None: return None - attrs = { - ATTR_EXPIRES: self.coordinator.data.expiration_date.isoformat(), - } + attrs = {} + if expiration_date := self.coordinator.data.expiration_date: + attrs[ATTR_EXPIRES] = expiration_date.isoformat() - if self.coordinator.data.name_servers: - attrs[ATTR_NAME_SERVERS] = " ".join(self.coordinator.data.name_servers) + if name_servers := self.coordinator.data.name_servers: + attrs[ATTR_NAME_SERVERS] = " ".join(name_servers) - if self.coordinator.data.last_updated: - attrs[ATTR_UPDATED] = self.coordinator.data.last_updated.isoformat() + if last_updated := self.coordinator.data.last_updated: + attrs[ATTR_UPDATED] = last_updated.isoformat() - if self.coordinator.data.registrar: - attrs[ATTR_REGISTRAR] = self.coordinator.data.registrar + if registrar := self.coordinator.data.registrar: + attrs[ATTR_REGISTRAR] = registrar + + if not attrs: + return None return attrs From 441e81c02a8e6272549a48be362928c835d0fc7e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 31 Jan 2022 18:16:33 +0100 Subject: [PATCH 0137/1098] Add diagnostics support to WLED (#65317) --- homeassistant/components/wled/diagnostics.py | 48 +++++ tests/components/wled/test_diagnostics.py | 210 +++++++++++++++++++ 2 files changed, 258 insertions(+) create mode 100644 homeassistant/components/wled/diagnostics.py create mode 100644 tests/components/wled/test_diagnostics.py diff --git a/homeassistant/components/wled/diagnostics.py b/homeassistant/components/wled/diagnostics.py new file mode 100644 index 00000000000000..c2820a7a13a0d7 --- /dev/null +++ b/homeassistant/components/wled/diagnostics.py @@ -0,0 +1,48 @@ +"""Diagnostics support for WLED.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import DOMAIN +from .coordinator import WLEDDataUpdateCoordinator + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + coordinator: WLEDDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + data = { + "info": async_redact_data(coordinator.data.info.__dict__, "wifi"), + "state": coordinator.data.state.__dict__, + "effects": { + effect.effect_id: effect.name for effect in coordinator.data.effects + }, + "palettes": { + palette.palette_id: palette.name for palette in coordinator.data.palettes + }, + "playlists": { + playlist.playlist_id: { + "name": playlist.name, + "repeat": playlist.repeat, + "shuffle": playlist.shuffle, + "end": playlist.end.preset_id if playlist.end else None, + } + for playlist in coordinator.data.playlists + }, + "presets": { + preset.preset_id: { + "name": preset.name, + "quick_label": preset.quick_label, + "on": preset.on, + "transition": preset.transition, + } + for preset in coordinator.data.presets + }, + } + return data diff --git a/tests/components/wled/test_diagnostics.py b/tests/components/wled/test_diagnostics.py new file mode 100644 index 00000000000000..d8782848c92d09 --- /dev/null +++ b/tests/components/wled/test_diagnostics.py @@ -0,0 +1,210 @@ +"""Tests for the diagnostics data provided by the WLED integration.""" +from aiohttp import ClientSession + +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_diagnostics( + hass: HomeAssistant, + hass_client: ClientSession, + init_integration: MockConfigEntry, +): + """Test diagnostics.""" + assert await get_diagnostics_for_config_entry( + hass, hass_client, init_integration + ) == { + "info": { + "architecture": "esp8266", + "arduino_core_version": "2.4.2", + "brand": "WLED", + "build_type": "bin", + "effect_count": 81, + "filesystem": None, + "free_heap": 14600, + "leds": { + "__type": "", + "repr": "Leds(cct=False, count=30, fps=None, max_power=850, max_segments=10, power=470, rgbw=False, wv=True)", + }, + "live_ip": "Unknown", + "live_mode": "Unknown", + "live": False, + "mac_address": "aabbccddeeff", + "name": "WLED RGB Light", + "pallet_count": 50, + "product": "DIY light", + "udp_port": 21324, + "uptime": 32, + "version_id": 1909122, + "version": "0.8.5", + "version_latest_beta": "0.13.0b1", + "version_latest_stable": "0.12.0", + "websocket": None, + "wifi": "**REDACTED**", + }, + "state": { + "brightness": 127, + "nightlight": { + "__type": "", + "repr": "Nightlight(duration=60, fade=True, on=False, mode=, target_brightness=0)", + }, + "on": True, + "playlist": -1, + "preset": -1, + "segments": [ + { + "__type": "", + "repr": "Segment(brightness=127, clones=-1, color_primary=(255, 159, 0), color_secondary=(0, 0, 0), color_tertiary=(0, 0, 0), effect=Effect(effect_id=0, name='Solid'), intensity=128, length=20, on=True, palette=Palette(name='Default', palette_id=0), reverse=False, segment_id=0, selected=True, speed=32, start=0, stop=19)", + }, + { + "__type": "", + "repr": "Segment(brightness=127, clones=-1, color_primary=(0, 255, 123), color_secondary=(0, 0, 0), color_tertiary=(0, 0, 0), effect=Effect(effect_id=1, name='Blink'), intensity=64, length=10, on=True, palette=Palette(name='Random Cycle', palette_id=1), reverse=True, segment_id=1, selected=True, speed=16, start=20, stop=30)", + }, + ], + "sync": { + "__type": "", + "repr": "Sync(receive=True, send=False)", + }, + "transition": 7, + "lor": 0, + }, + "effects": { + "27": "Android", + "68": "BPM", + "1": "Blink", + "26": "Blink Rainbow", + "2": "Breathe", + "13": "Chase", + "28": "Chase", + "31": "Chase Flash", + "32": "Chase Flash Rnd", + "14": "Chase Rainbow", + "30": "Chase Rainbow", + "29": "Chase Random", + "52": "Circus", + "34": "Colorful", + "8": "Colorloop", + "74": "Colortwinkle", + "67": "Colorwaves", + "21": "Dark Sparkle", + "18": "Dissolve", + "19": "Dissolve Rnd", + "11": "Dual Scan", + "60": "Dual Scanner", + "7": "Dynamic", + "12": "Fade", + "69": "Fill Noise", + "66": "Fire 2012", + "45": "Fire Flicker", + "42": "Fireworks", + "46": "Gradient", + "53": "Halloween", + "58": "ICU", + "49": "In In", + "48": "In Out", + "64": "Juggle", + "75": "Lake", + "41": "Lighthouse", + "57": "Lightning", + "47": "Loading", + "25": "Mega Strobe", + "44": "Merry Christmas", + "76": "Meteor", + "59": "Multi Comet", + "70": "Noise 1", + "71": "Noise 2", + "72": "Noise 3", + "73": "Noise 4", + "62": "Oscillate", + "51": "Out In", + "50": "Out Out", + "65": "Palette", + "63": "Pride 2015", + "78": "Railway", + "43": "Rain", + "9": "Rainbow", + "33": "Rainbow Runner", + "5": "Random Colors", + "38": "Red & Blue", + "79": "Ripple", + "15": "Running", + "37": "Running 2", + "16": "Saw", + "10": "Scan", + "40": "Scanner", + "77": "Smooth Meteor", + "0": "Solid", + "20": "Sparkle", + "22": "Sparkle+", + "39": "Stream", + "61": "Stream 2", + "23": "Strobe", + "24": "Strobe Rainbow", + "6": "Sweep", + "36": "Sweep Random", + "35": "Traffic Light", + "54": "Tri Chase", + "56": "Tri Fade", + "55": "Tri Wipe", + "17": "Twinkle", + "80": "Twinklefox", + "3": "Wipe", + "4": "Wipe Random", + }, + "palettes": { + "18": "Analogous", + "46": "April Night", + "39": "Autumn", + "3": "Based on Primary", + "5": "Based on Set", + "26": "Beach", + "22": "Beech", + "15": "Breeze", + "48": "C9", + "7": "Cloud", + "37": "Cyane", + "0": "Default", + "24": "Departure", + "30": "Drywet", + "35": "Fire", + "10": "Forest", + "32": "Grintage", + "28": "Hult", + "29": "Hult 64", + "36": "Icefire", + "31": "Jul", + "25": "Landscape", + "8": "Lava", + "38": "Light Pink", + "40": "Magenta", + "41": "Magred", + "9": "Ocean", + "44": "Orange & Teal", + "47": "Orangery", + "6": "Party", + "20": "Pastel", + "2": "Primary Color", + "11": "Rainbow", + "12": "Rainbow Bands", + "1": "Random Cycle", + "16": "Red & Blue", + "33": "Rewhi", + "14": "Rivendell", + "49": "Sakura", + "4": "Set Colors", + "27": "Sherbet", + "19": "Splash", + "13": "Sunset", + "21": "Sunset 2", + "34": "Tertiary", + "45": "Tiamat", + "23": "Vintage", + "43": "Yelblu", + "17": "Yellowout", + "42": "Yelmag", + }, + "playlists": {}, + "presets": {}, + } From e5b6a58fab9b0d0feee81a4de807e20fca6132d9 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 31 Jan 2022 18:17:35 +0100 Subject: [PATCH 0138/1098] Update tailscale to 0.2.0 (#65318) --- homeassistant/components/tailscale/binary_sensor.py | 6 ++---- homeassistant/components/tailscale/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/tailscale/binary_sensor.py b/homeassistant/components/tailscale/binary_sensor.py index fff4cfbf908e57..2f97d307b158ed 100644 --- a/homeassistant/components/tailscale/binary_sensor.py +++ b/homeassistant/components/tailscale/binary_sensor.py @@ -111,8 +111,6 @@ class TailscaleBinarySensorEntity(TailscaleEntity, BinarySensorEntity): entity_description: TailscaleBinarySensorEntityDescription @property - def is_on(self) -> bool: + def is_on(self) -> bool | None: """Return the state of the sensor.""" - return bool( - self.entity_description.is_on_fn(self.coordinator.data[self.device_id]) - ) + return self.entity_description.is_on_fn(self.coordinator.data[self.device_id]) diff --git a/homeassistant/components/tailscale/manifest.json b/homeassistant/components/tailscale/manifest.json index ac7cbe844593d5..3249705ce7ced6 100644 --- a/homeassistant/components/tailscale/manifest.json +++ b/homeassistant/components/tailscale/manifest.json @@ -3,7 +3,7 @@ "name": "Tailscale", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tailscale", - "requirements": ["tailscale==0.1.6"], + "requirements": ["tailscale==0.2.0"], "codeowners": ["@frenck"], "quality_scale": "platinum", "iot_class": "cloud_polling" diff --git a/requirements_all.txt b/requirements_all.txt index c8b82662ceb51f..8bb86d47bb54aa 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2314,7 +2314,7 @@ synology-srm==0.2.0 systembridge==2.2.3 # homeassistant.components.tailscale -tailscale==0.1.6 +tailscale==0.2.0 # homeassistant.components.tank_utility tank_utility==1.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5ce35d69980795..c5dabcbf94d22a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1423,7 +1423,7 @@ surepy==0.7.2 systembridge==2.2.3 # homeassistant.components.tailscale -tailscale==0.1.6 +tailscale==0.2.0 # homeassistant.components.tellduslive tellduslive==0.10.11 From b7bf302ef8b663a2e4947b4ceeb52465dc081d3d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 31 Jan 2022 18:42:49 +0100 Subject: [PATCH 0139/1098] Ensure PVOutput connection error is logged (#65319) --- homeassistant/components/pvoutput/config_flow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/pvoutput/config_flow.py b/homeassistant/components/pvoutput/config_flow.py index aa197061466878..a1933ff9315f49 100644 --- a/homeassistant/components/pvoutput/config_flow.py +++ b/homeassistant/components/pvoutput/config_flow.py @@ -12,7 +12,7 @@ from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession -from .const import CONF_SYSTEM_ID, DOMAIN +from .const import CONF_SYSTEM_ID, DOMAIN, LOGGER async def validate_input(hass: HomeAssistant, *, api_key: str, system_id: int) -> None: @@ -50,6 +50,7 @@ async def async_step_user( except PVOutputAuthenticationError: errors["base"] = "invalid_auth" except PVOutputError: + LOGGER.exception("Cannot connect to PVOutput") errors["base"] = "cannot_connect" else: await self.async_set_unique_id(str(user_input[CONF_SYSTEM_ID])) From 48ae3bece8a1720b773a64cab3c0285c7eb3ff4b Mon Sep 17 00:00:00 2001 From: jjlawren Date: Mon, 31 Jan 2022 12:21:21 -0600 Subject: [PATCH 0140/1098] Send notification to alert of Sonos networking issues (#65084) * Send notification to alert of Sonos networking issues * Add links to documentation --- homeassistant/components/sonos/entity.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sonos/entity.py b/homeassistant/components/sonos/entity.py index 74d310d40ec4df..53768431a1d1db 100644 --- a/homeassistant/components/sonos/entity.py +++ b/homeassistant/components/sonos/entity.py @@ -9,6 +9,7 @@ from soco.core import SoCo from soco.exceptions import SoCoException +from homeassistant.components import persistent_notification import homeassistant.helpers.device_registry as dr from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo, Entity @@ -22,6 +23,8 @@ ) from .speaker import SonosSpeaker +SUB_FAIL_URL = "https://www.home-assistant.io/integrations/sonos/#network-requirements" + _LOGGER = logging.getLogger(__name__) @@ -70,10 +73,15 @@ async def async_poll(self, now: datetime.datetime) -> None: listener_msg = f"{self.speaker.subscription_address} (advertising as {soco_config.EVENT_ADVERTISE_IP})" else: listener_msg = self.speaker.subscription_address - _LOGGER.warning( - "%s cannot reach %s, falling back to polling, functionality may be limited", - self.speaker.zone_name, - listener_msg, + message = f"{self.speaker.zone_name} cannot reach {listener_msg}, falling back to polling, functionality may be limited" + log_link_msg = f", see {SUB_FAIL_URL} for more details" + notification_link_msg = f'.\n\nSee Sonos documentation for more details.' + _LOGGER.warning(message + log_link_msg) + persistent_notification.async_create( + self.hass, + message + notification_link_msg, + "Sonos networking issue", + "sonos_subscriptions_failed", ) self.speaker.subscriptions_failed = True await self.speaker.async_unsubscribe() From 0cfc7112fa61a4f12c4fbaf3a39cb87c81cb4750 Mon Sep 17 00:00:00 2001 From: Pascal Winters Date: Mon, 31 Jan 2022 19:23:07 +0100 Subject: [PATCH 0141/1098] Bump pyps4-2ndscreen to 1.3.1 (#65320) --- homeassistant/components/ps4/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ps4/manifest.json b/homeassistant/components/ps4/manifest.json index a63ed8b7e7ba42..c7f47333901c75 100644 --- a/homeassistant/components/ps4/manifest.json +++ b/homeassistant/components/ps4/manifest.json @@ -3,7 +3,7 @@ "name": "Sony PlayStation 4", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/ps4", - "requirements": ["pyps4-2ndscreen==1.2.0"], + "requirements": ["pyps4-2ndscreen==1.3.1"], "codeowners": ["@ktnrg45"], "iot_class": "local_polling", "loggers": ["pyps4_2ndscreen"] diff --git a/requirements_all.txt b/requirements_all.txt index 8bb86d47bb54aa..271bb4b583e47e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1776,7 +1776,7 @@ pyprof2calltree==1.4.5 pyprosegur==0.0.5 # homeassistant.components.ps4 -pyps4-2ndscreen==1.2.0 +pyps4-2ndscreen==1.3.1 # homeassistant.components.qvr_pro pyqvrpro==0.52 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c5dabcbf94d22a..d880e4e65ce45a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1133,7 +1133,7 @@ pyprof2calltree==1.4.5 pyprosegur==0.0.5 # homeassistant.components.ps4 -pyps4-2ndscreen==1.2.0 +pyps4-2ndscreen==1.3.1 # homeassistant.components.qwikswitch pyqwikswitch==0.93 From 076faaa4a4f231eb5b7b7c72fa20c239c7cc391c Mon Sep 17 00:00:00 2001 From: w35l3y Date: Mon, 31 Jan 2022 15:23:26 -0300 Subject: [PATCH 0142/1098] Add support to reprompt user (#65256) --- homeassistant/components/alexa/intent.py | 12 ++--- .../components/intent_script/__init__.py | 12 +++++ homeassistant/helpers/intent.py | 16 ++++++- tests/components/alexa/test_intent.py | 43 ++++++++++++++++++ tests/components/intent_script/test_init.py | 45 +++++++++++++++++++ 5 files changed, 121 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/alexa/intent.py b/homeassistant/components/alexa/intent.py index fede7d96810f2e..0b8bf55fcdad2a 100644 --- a/homeassistant/components/alexa/intent.py +++ b/homeassistant/components/alexa/intent.py @@ -166,7 +166,10 @@ async def async_handle_intent(hass, message): alexa_response.add_speech( alexa_speech, intent_response.speech[intent_speech]["speech"] ) - break + if intent_speech in intent_response.reprompt: + alexa_response.add_reprompt( + alexa_speech, intent_response.reprompt[intent_speech]["reprompt"] + ) if "simple" in intent_response.card: alexa_response.add_card( @@ -267,10 +270,9 @@ def add_reprompt(self, speech_type, text): key = "ssml" if speech_type == SpeechType.ssml else "text" - self.reprompt = { - "type": speech_type.value, - key: text.async_render(self.variables, parse_result=False), - } + self.should_end_session = False + + self.reprompt = {"type": speech_type.value, key: text} def as_dict(self): """Return response in an Alexa valid dict.""" diff --git a/homeassistant/components/intent_script/__init__.py b/homeassistant/components/intent_script/__init__.py index 2deca297f34bd7..d14aaf5a68bae8 100644 --- a/homeassistant/components/intent_script/__init__.py +++ b/homeassistant/components/intent_script/__init__.py @@ -12,6 +12,7 @@ CONF_INTENTS = "intents" CONF_SPEECH = "speech" +CONF_REPROMPT = "reprompt" CONF_ACTION = "action" CONF_CARD = "card" @@ -39,6 +40,10 @@ vol.Optional(CONF_TYPE, default="plain"): cv.string, vol.Required(CONF_TEXT): cv.template, }, + vol.Optional(CONF_REPROMPT): { + vol.Optional(CONF_TYPE, default="plain"): cv.string, + vol.Required(CONF_TEXT): cv.template, + }, } } }, @@ -72,6 +77,7 @@ def __init__(self, intent_type, config): async def async_handle(self, intent_obj): """Handle the intent.""" speech = self.config.get(CONF_SPEECH) + reprompt = self.config.get(CONF_REPROMPT) card = self.config.get(CONF_CARD) action = self.config.get(CONF_ACTION) is_async_action = self.config.get(CONF_ASYNC_ACTION) @@ -93,6 +99,12 @@ async def async_handle(self, intent_obj): speech[CONF_TYPE], ) + if reprompt is not None and reprompt[CONF_TEXT].template: + response.async_set_reprompt( + reprompt[CONF_TEXT].async_render(slots, parse_result=False), + reprompt[CONF_TYPE], + ) + if card is not None: response.async_set_card( card[CONF_TITLE].async_render(slots, parse_result=False), diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index ca154d20b75cec..13cc32a35b60a5 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -249,6 +249,7 @@ def __init__(self, intent: Intent | None = None) -> None: """Initialize an IntentResponse.""" self.intent = intent self.speech: dict[str, dict[str, Any]] = {} + self.reprompt: dict[str, dict[str, Any]] = {} self.card: dict[str, dict[str, str]] = {} @callback @@ -258,14 +259,25 @@ def async_set_speech( """Set speech response.""" self.speech[speech_type] = {"speech": speech, "extra_data": extra_data} + @callback + def async_set_reprompt( + self, speech: str, speech_type: str = "plain", extra_data: Any | None = None + ) -> None: + """Set reprompt response.""" + self.reprompt[speech_type] = {"reprompt": speech, "extra_data": extra_data} + @callback def async_set_card( self, title: str, content: str, card_type: str = "simple" ) -> None: - """Set speech response.""" + """Set card response.""" self.card[card_type] = {"title": title, "content": content} @callback def as_dict(self) -> dict[str, dict[str, dict[str, Any]]]: """Return a dictionary representation of an intent response.""" - return {"speech": self.speech, "card": self.card} + return ( + {"speech": self.speech, "reprompt": self.reprompt, "card": self.card} + if self.reprompt + else {"speech": self.speech, "card": self.card} + ) diff --git a/tests/components/alexa/test_intent.py b/tests/components/alexa/test_intent.py index d6c32996330aaf..f15fa860c7b202 100644 --- a/tests/components/alexa/test_intent.py +++ b/tests/components/alexa/test_intent.py @@ -13,6 +13,9 @@ SESSION_ID = "amzn1.echo-api.session.0000000-0000-0000-0000-00000000000" APPLICATION_ID = "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe" +APPLICATION_ID_SESSION_OPEN = ( + "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebf" +) REQUEST_ID = "amzn1.echo-api.request.0000000-0000-0000-0000-00000000000" AUTHORITY_ID = "amzn1.er-authority.000000-d0ed-0000-ad00-000000d00ebe.ZODIAC" BUILTIN_AUTH_ID = "amzn1.er-authority.000000-d0ed-0000-ad00-000000d00ebe.TEST" @@ -102,6 +105,16 @@ def mock_service(call): "text": "LaunchRequest has been received.", } }, + APPLICATION_ID_SESSION_OPEN: { + "speech": { + "type": "plain", + "text": "LaunchRequest has been received.", + }, + "reprompt": { + "type": "plain", + "text": "LaunchRequest has been received.", + }, + }, } }, ) @@ -139,6 +152,36 @@ async def test_intent_launch_request(alexa_client): data = await req.json() text = data.get("response", {}).get("outputSpeech", {}).get("text") assert text == "LaunchRequest has been received." + assert data.get("response", {}).get("shouldEndSession") + + +async def test_intent_launch_request_with_session_open(alexa_client): + """Test the launch of a request.""" + data = { + "version": "1.0", + "session": { + "new": True, + "sessionId": SESSION_ID, + "application": {"applicationId": APPLICATION_ID_SESSION_OPEN}, + "attributes": {}, + "user": {"userId": "amzn1.account.AM3B00000000000000000000000"}, + }, + "request": { + "type": "LaunchRequest", + "requestId": REQUEST_ID, + "timestamp": "2015-05-13T12:34:56Z", + }, + } + req = await _intent_req(alexa_client, data) + assert req.status == HTTPStatus.OK + data = await req.json() + text = data.get("response", {}).get("outputSpeech", {}).get("text") + assert text == "LaunchRequest has been received." + text = ( + data.get("response", {}).get("reprompt", {}).get("outputSpeech", {}).get("text") + ) + assert text == "LaunchRequest has been received." + assert not data.get("response", {}).get("shouldEndSession") async def test_intent_launch_request_not_configured(alexa_client): diff --git a/tests/components/intent_script/test_init.py b/tests/components/intent_script/test_init.py index 95b167caba6d46..6f345522e635b5 100644 --- a/tests/components/intent_script/test_init.py +++ b/tests/components/intent_script/test_init.py @@ -40,3 +40,48 @@ async def test_intent_script(hass): assert response.card["simple"]["title"] == "Hello Paulus" assert response.card["simple"]["content"] == "Content for Paulus" + + +async def test_intent_script_wait_response(hass): + """Test intent scripts work.""" + calls = async_mock_service(hass, "test", "service") + + await async_setup_component( + hass, + "intent_script", + { + "intent_script": { + "HelloWorldWaitResponse": { + "action": { + "service": "test.service", + "data_template": {"hello": "{{ name }}"}, + }, + "card": { + "title": "Hello {{ name }}", + "content": "Content for {{ name }}", + }, + "speech": {"text": "Good morning {{ name }}"}, + "reprompt": { + "text": "I didn't hear you, {{ name }}... I said good morning!" + }, + } + } + }, + ) + + response = await intent.async_handle( + hass, "test", "HelloWorldWaitResponse", {"name": {"value": "Paulus"}} + ) + + assert len(calls) == 1 + assert calls[0].data["hello"] == "Paulus" + + assert response.speech["plain"]["speech"] == "Good morning Paulus" + + assert ( + response.reprompt["plain"]["reprompt"] + == "I didn't hear you, Paulus... I said good morning!" + ) + + assert response.card["simple"]["title"] == "Hello Paulus" + assert response.card["simple"]["content"] == "Content for Paulus" From 17c41f47835d667c630cde85c4069e810160bf76 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Mon, 31 Jan 2022 22:14:59 +0100 Subject: [PATCH 0143/1098] Introduce number platform for Shelly (#64207) * Introduce number platform for Shelly * coverage * Rework based on review comment * Improve logic around channel * Remove unused value * rebase * Removed redundant properties * Update homeassistant/components/shelly/number.py Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> * Remove channel workaround as currently not needed Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> --- .coveragerc | 1 + homeassistant/components/shelly/__init__.py | 1 + homeassistant/components/shelly/number.py | 132 ++++++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 homeassistant/components/shelly/number.py diff --git a/.coveragerc b/.coveragerc index 0ae80a22a47e81..7cca9c005ce588 100644 --- a/.coveragerc +++ b/.coveragerc @@ -985,6 +985,7 @@ omit = homeassistant/components/shelly/climate.py homeassistant/components/shelly/entity.py homeassistant/components/shelly/light.py + homeassistant/components/shelly/number.py homeassistant/components/shelly/sensor.py homeassistant/components/shelly/utils.py homeassistant/components/sht31/sensor.py diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index 3ab87ad9d0e85e..d60a8aabb1a946 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -80,6 +80,7 @@ BLOCK_SLEEPING_PLATFORMS: Final = [ Platform.BINARY_SENSOR, Platform.CLIMATE, + Platform.NUMBER, Platform.SENSOR, ] RPC_PLATFORMS: Final = [ diff --git a/homeassistant/components/shelly/number.py b/homeassistant/components/shelly/number.py new file mode 100644 index 00000000000000..27773c629c0462 --- /dev/null +++ b/homeassistant/components/shelly/number.py @@ -0,0 +1,132 @@ +"""Number for Shelly.""" +from __future__ import annotations + +import asyncio +from dataclasses import dataclass +import logging +from typing import Any, Final, cast + +import async_timeout + +from homeassistant.components.number import ( + NumberEntity, + NumberEntityDescription, + NumberMode, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import PERCENTAGE +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.entity_registry import RegistryEntry + +from .const import AIOSHELLY_DEVICE_TIMEOUT_SEC, CONF_SLEEP_PERIOD +from .entity import ( + BlockEntityDescription, + ShellySleepingBlockAttributeEntity, + async_setup_entry_attribute_entities, +) +from .utils import get_device_entry_gen + +_LOGGER: Final = logging.getLogger(__name__) + + +@dataclass +class BlockNumberDescription(BlockEntityDescription, NumberEntityDescription): + """Class to describe a BLOCK sensor.""" + + mode: NumberMode = NumberMode("slider") + rest_path: str = "" + rest_arg: str = "" + + +NUMBERS: Final = { + ("device", "valvePos"): BlockNumberDescription( + key="device|valvepos", + icon="mdi:pipe-valve", + name="Valve Position", + unit_of_measurement=PERCENTAGE, + available=lambda block: cast(int, block.valveError) != 1, + entity_category=EntityCategory.CONFIG, + min_value=0, + max_value=100, + step=1, + mode=NumberMode("slider"), + rest_path="thermostat/0", + rest_arg="pos", + ), +} + + +def _build_block_description(entry: RegistryEntry) -> BlockNumberDescription: + """Build description when restoring block attribute entities.""" + assert entry.capabilities + return BlockNumberDescription( + key="", + name="", + icon=entry.original_icon, + unit_of_measurement=entry.unit_of_measurement, + device_class=entry.original_device_class, + min_value=cast(float, entry.capabilities.get("min")), + max_value=cast(float, entry.capabilities.get("max")), + step=cast(float, entry.capabilities.get("step")), + mode=cast(NumberMode, entry.capabilities.get("mode")), + ) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up numbers for device.""" + if get_device_entry_gen(config_entry) == 2: + return + + if config_entry.data[CONF_SLEEP_PERIOD]: + await async_setup_entry_attribute_entities( + hass, + config_entry, + async_add_entities, + NUMBERS, + BlockSleepingNumber, + _build_block_description, + ) + + +class BlockSleepingNumber(ShellySleepingBlockAttributeEntity, NumberEntity): + """Represent a block sleeping number.""" + + entity_description: BlockNumberDescription + + @property + def value(self) -> float: + """Return value of number.""" + if self.block is not None: + return cast(float, self.attribute_value) + + return cast(float, self.last_state) + + async def async_set_value(self, value: float) -> None: + """Set value.""" + # Example for Shelly Valve: http://192.168.188.187/thermostat/0?pos=13.0 + await self._set_state_full_path( + self.entity_description.rest_path, + {self.entity_description.rest_arg: value}, + ) + self.async_write_ha_state() + + async def _set_state_full_path(self, path: str, params: Any) -> Any: + """Set block state (HTTP request).""" + + _LOGGER.debug("Setting state for entity %s, state: %s", self.name, params) + try: + async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC): + return await self.wrapper.device.http_request("get", path, params) + except (asyncio.TimeoutError, OSError) as err: + _LOGGER.error( + "Setting state for entity %s failed, state: %s, error: %s", + self.name, + params, + repr(err), + ) From a9af29cbe001277f19cbdc7e89e60ce0482c7da5 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Mon, 31 Jan 2022 23:43:46 +0100 Subject: [PATCH 0144/1098] Add diagnostics support to Fritz (#65334) * Add diagnostics support to Fritz * Temporary remove tests * coveragerc --- .coveragerc | 1 + homeassistant/components/fritz/diagnostics.py | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 homeassistant/components/fritz/diagnostics.py diff --git a/.coveragerc b/.coveragerc index 7cca9c005ce588..049e6a37904b4f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -376,6 +376,7 @@ omit = homeassistant/components/fritz/common.py homeassistant/components/fritz/const.py homeassistant/components/fritz/device_tracker.py + homeassistant/components/fritz/diagnostics.py homeassistant/components/fritz/sensor.py homeassistant/components/fritz/services.py homeassistant/components/fritz/switch.py diff --git a/homeassistant/components/fritz/diagnostics.py b/homeassistant/components/fritz/diagnostics.py new file mode 100644 index 00000000000000..4305ee4d7cb2e5 --- /dev/null +++ b/homeassistant/components/fritz/diagnostics.py @@ -0,0 +1,35 @@ +"""Diagnostics support for AVM FRITZ!Box.""" +from __future__ import annotations + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant + +from .common import AvmWrapper +from .const import DOMAIN + +TO_REDACT = {CONF_USERNAME, CONF_PASSWORD} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict: + """Return diagnostics for a config entry.""" + avm_wrapper: AvmWrapper = hass.data[DOMAIN][entry.entry_id] + + diag_data = { + "entry": async_redact_data(entry.as_dict(), TO_REDACT), + "device_info": { + "model": avm_wrapper.model, + "current_firmware": avm_wrapper.current_firmware, + "latest_firmware": avm_wrapper.latest_firmware, + "update_available": avm_wrapper.update_available, + "is_router": avm_wrapper.device_is_router, + "mesh_role": avm_wrapper.mesh_role, + "last_update success": avm_wrapper.last_update_success, + "last_exception": avm_wrapper.last_exception, + }, + } + + return diag_data From 0f88790303d842aa0a93efd1d529f988c189a090 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Mon, 31 Jan 2022 22:48:16 +0000 Subject: [PATCH 0145/1098] Refactor homekit_controller to prepare for more typing information (#65329) --- .../homekit_controller/alarm_control_panel.py | 2 +- .../homekit_controller/binary_sensor.py | 2 +- .../components/homekit_controller/button.py | 20 ++---- .../components/homekit_controller/climate.py | 2 +- .../homekit_controller/connection.py | 3 +- .../components/homekit_controller/const.py | 64 ++++++++--------- .../components/homekit_controller/cover.py | 2 +- .../homekit_controller/device_trigger.py | 2 +- .../homekit_controller/diagnostics.py | 9 +-- .../components/homekit_controller/fan.py | 2 +- .../homekit_controller/humidifier.py | 2 +- .../components/homekit_controller/light.py | 2 +- .../components/homekit_controller/lock.py | 2 +- .../homekit_controller/manifest.json | 2 +- .../homekit_controller/media_player.py | 2 +- .../components/homekit_controller/number.py | 42 +++++------ .../components/homekit_controller/select.py | 6 +- .../components/homekit_controller/sensor.py | 69 ++++++++----------- .../components/homekit_controller/switch.py | 10 +-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/homekit_controller/common.py | 3 +- .../homekit_controller/test_button.py | 8 +-- .../homekit_controller/test_diagnostics.py | 4 +- .../homekit_controller/test_number.py | 20 +++--- .../homekit_controller/test_select.py | 16 ++--- .../homekit_controller/test_sensor.py | 8 +-- .../homekit_controller/test_switch.py | 10 +-- 28 files changed, 139 insertions(+), 179 deletions(-) diff --git a/homeassistant/components/homekit_controller/alarm_control_panel.py b/homeassistant/components/homekit_controller/alarm_control_panel.py index 25af3064d5d936..e7d9bf11c32266 100644 --- a/homeassistant/components/homekit_controller/alarm_control_panel.py +++ b/homeassistant/components/homekit_controller/alarm_control_panel.py @@ -51,7 +51,7 @@ async def async_setup_entry( @callback def async_add_service(service): - if service.short_type != ServicesTypes.SECURITY_SYSTEM: + if service.type != ServicesTypes.SECURITY_SYSTEM: return False info = {"aid": service.accessory.aid, "iid": service.iid} async_add_entities([HomeKitAlarmControlPanelEntity(conn, info)], True) diff --git a/homeassistant/components/homekit_controller/binary_sensor.py b/homeassistant/components/homekit_controller/binary_sensor.py index 3aadff93a01c5e..09c42bf0547e11 100644 --- a/homeassistant/components/homekit_controller/binary_sensor.py +++ b/homeassistant/components/homekit_controller/binary_sensor.py @@ -124,7 +124,7 @@ async def async_setup_entry( @callback def async_add_service(service): - if not (entity_class := ENTITY_TYPES.get(service.short_type)): + if not (entity_class := ENTITY_TYPES.get(service.type)): return False info = {"aid": service.accessory.aid, "iid": service.iid} async_add_entities([entity_class(conn, info)], True) diff --git a/homeassistant/components/homekit_controller/button.py b/homeassistant/components/homekit_controller/button.py index 8fe7d8e8c72890..8c8b1e616c9347 100644 --- a/homeassistant/components/homekit_controller/button.py +++ b/homeassistant/components/homekit_controller/button.py @@ -31,15 +31,15 @@ class HomeKitButtonEntityDescription(ButtonEntityDescription): BUTTON_ENTITIES: dict[str, HomeKitButtonEntityDescription] = { - CharacteristicsTypes.Vendor.HAA_SETUP: HomeKitButtonEntityDescription( - key=CharacteristicsTypes.Vendor.HAA_SETUP, + CharacteristicsTypes.VENDOR_HAA_SETUP: HomeKitButtonEntityDescription( + key=CharacteristicsTypes.VENDOR_HAA_SETUP, name="Setup", icon="mdi:cog", entity_category=EntityCategory.CONFIG, write_value="#HAA@trcmd", ), - CharacteristicsTypes.Vendor.HAA_UPDATE: HomeKitButtonEntityDescription( - key=CharacteristicsTypes.Vendor.HAA_UPDATE, + CharacteristicsTypes.VENDOR_HAA_UPDATE: HomeKitButtonEntityDescription( + key=CharacteristicsTypes.VENDOR_HAA_UPDATE, name="Update", device_class=ButtonDeviceClass.UPDATE, entity_category=EntityCategory.CONFIG, @@ -54,16 +54,6 @@ class HomeKitButtonEntityDescription(ButtonEntityDescription): } -# For legacy reasons, "built-in" characteristic types are in their short form -# And vendor types don't have a short form -# This means long and short forms get mixed up in this dict, and comparisons -# don't work! -# We call get_uuid on *every* type to normalise them to the long form -# Eventually aiohomekit will use the long form exclusively amd this can be removed. -for k, v in list(BUTTON_ENTITIES.items()): - BUTTON_ENTITIES[CharacteristicsTypes.get_uuid(k)] = BUTTON_ENTITIES.pop(k) - - async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, @@ -155,5 +145,5 @@ async def async_press(self) -> None: BUTTON_ENTITY_CLASSES: dict[str, type] = { - CharacteristicsTypes.Vendor.ECOBEE_CLEAR_HOLD: HomeKitEcobeeClearHoldButton, + CharacteristicsTypes.VENDOR_ECOBEE_CLEAR_HOLD: HomeKitEcobeeClearHoldButton, } diff --git a/homeassistant/components/homekit_controller/climate.py b/homeassistant/components/homekit_controller/climate.py index 4cb3f24bbc45f0..aa807f3e9014f8 100644 --- a/homeassistant/components/homekit_controller/climate.py +++ b/homeassistant/components/homekit_controller/climate.py @@ -95,7 +95,7 @@ async def async_setup_entry( @callback def async_add_service(service): - if not (entity_class := ENTITY_TYPES.get(service.short_type)): + if not (entity_class := ENTITY_TYPES.get(service.type)): return False info = {"aid": service.accessory.aid, "iid": service.iid} async_add_entities([entity_class(conn, info)], True) diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index 08b3e9823e3b54..e31296258e4081 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -11,6 +11,7 @@ from aiohomekit.model import Accessories, Accessory from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes +from aiohomekit.uuid import normalize_uuid from homeassistant.const import ATTR_VIA_DEVICE from homeassistant.core import callback @@ -495,7 +496,7 @@ async def async_load_platforms(self): for accessory in self.accessories: for service in accessory["services"]: try: - stype = ServicesTypes.get_short_uuid(service["type"].upper()) + stype = normalize_uuid(service["type"]) except KeyError: stype = service["type"].upper() diff --git a/homeassistant/components/homekit_controller/const.py b/homeassistant/components/homekit_controller/const.py index 9ec991c0d88646..c27527d363873e 100644 --- a/homeassistant/components/homekit_controller/const.py +++ b/homeassistant/components/homekit_controller/const.py @@ -51,33 +51,33 @@ } CHARACTERISTIC_PLATFORMS = { - CharacteristicsTypes.Vendor.CONNECTSENSE_ENERGY_WATT: "sensor", - CharacteristicsTypes.Vendor.CONNECTSENSE_ENERGY_AMPS: "sensor", - CharacteristicsTypes.Vendor.CONNECTSENSE_ENERGY_AMPS_20: "sensor", - CharacteristicsTypes.Vendor.CONNECTSENSE_ENERGY_KW_HOUR: "sensor", - CharacteristicsTypes.Vendor.AQARA_GATEWAY_VOLUME: "number", - CharacteristicsTypes.Vendor.AQARA_E1_GATEWAY_VOLUME: "number", - CharacteristicsTypes.Vendor.AQARA_PAIRING_MODE: "switch", - CharacteristicsTypes.Vendor.AQARA_E1_PAIRING_MODE: "switch", - CharacteristicsTypes.Vendor.ECOBEE_HOME_TARGET_COOL: "number", - CharacteristicsTypes.Vendor.ECOBEE_HOME_TARGET_HEAT: "number", - CharacteristicsTypes.Vendor.ECOBEE_SLEEP_TARGET_COOL: "number", - CharacteristicsTypes.Vendor.ECOBEE_SLEEP_TARGET_HEAT: "number", - CharacteristicsTypes.Vendor.ECOBEE_AWAY_TARGET_COOL: "number", - CharacteristicsTypes.Vendor.ECOBEE_AWAY_TARGET_HEAT: "number", - CharacteristicsTypes.Vendor.ECOBEE_CURRENT_MODE: "select", - CharacteristicsTypes.Vendor.EVE_ENERGY_WATT: "sensor", - CharacteristicsTypes.Vendor.EVE_DEGREE_AIR_PRESSURE: "sensor", - CharacteristicsTypes.Vendor.EVE_DEGREE_ELEVATION: "number", - CharacteristicsTypes.Vendor.HAA_SETUP: "button", - CharacteristicsTypes.Vendor.HAA_UPDATE: "button", - CharacteristicsTypes.Vendor.KOOGEEK_REALTIME_ENERGY: "sensor", - CharacteristicsTypes.Vendor.KOOGEEK_REALTIME_ENERGY_2: "sensor", - CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL: "number", - CharacteristicsTypes.Vendor.VOCOLINC_OUTLET_ENERGY: "sensor", - CharacteristicsTypes.Vendor.ECOBEE_CLEAR_HOLD: "button", - CharacteristicsTypes.Vendor.ECOBEE_FAN_WRITE_SPEED: "number", - CharacteristicsTypes.Vendor.ECOBEE_SET_HOLD_SCHEDULE: "number", + CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_WATT: "sensor", + CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_AMPS: "sensor", + CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_AMPS_20: "sensor", + CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_KW_HOUR: "sensor", + CharacteristicsTypes.VENDOR_AQARA_GATEWAY_VOLUME: "number", + CharacteristicsTypes.VENDOR_AQARA_E1_GATEWAY_VOLUME: "number", + CharacteristicsTypes.VENDOR_AQARA_PAIRING_MODE: "switch", + CharacteristicsTypes.VENDOR_AQARA_E1_PAIRING_MODE: "switch", + CharacteristicsTypes.VENDOR_ECOBEE_HOME_TARGET_COOL: "number", + CharacteristicsTypes.VENDOR_ECOBEE_HOME_TARGET_HEAT: "number", + CharacteristicsTypes.VENDOR_ECOBEE_SLEEP_TARGET_COOL: "number", + CharacteristicsTypes.VENDOR_ECOBEE_SLEEP_TARGET_HEAT: "number", + CharacteristicsTypes.VENDOR_ECOBEE_AWAY_TARGET_COOL: "number", + CharacteristicsTypes.VENDOR_ECOBEE_AWAY_TARGET_HEAT: "number", + CharacteristicsTypes.VENDOR_ECOBEE_CURRENT_MODE: "select", + CharacteristicsTypes.VENDOR_EVE_ENERGY_WATT: "sensor", + CharacteristicsTypes.VENDOR_EVE_DEGREE_AIR_PRESSURE: "sensor", + CharacteristicsTypes.VENDOR_EVE_DEGREE_ELEVATION: "number", + CharacteristicsTypes.VENDOR_HAA_SETUP: "button", + CharacteristicsTypes.VENDOR_HAA_UPDATE: "button", + CharacteristicsTypes.VENDOR_KOOGEEK_REALTIME_ENERGY: "sensor", + CharacteristicsTypes.VENDOR_KOOGEEK_REALTIME_ENERGY_2: "sensor", + CharacteristicsTypes.VENDOR_VOCOLINC_HUMIDIFIER_SPRAY_LEVEL: "number", + CharacteristicsTypes.VENDOR_VOCOLINC_OUTLET_ENERGY: "sensor", + CharacteristicsTypes.VENDOR_ECOBEE_CLEAR_HOLD: "button", + CharacteristicsTypes.VENDOR_ECOBEE_FAN_WRITE_SPEED: "number", + CharacteristicsTypes.VENDOR_ECOBEE_SET_HOLD_SCHEDULE: "number", CharacteristicsTypes.TEMPERATURE_CURRENT: "sensor", CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT: "sensor", CharacteristicsTypes.AIR_QUALITY: "sensor", @@ -90,16 +90,6 @@ CharacteristicsTypes.IDENTIFY: "button", } -# For legacy reasons, "built-in" characteristic types are in their short form -# And vendor types don't have a short form -# This means long and short forms get mixed up in this dict, and comparisons -# don't work! -# We call get_uuid on *every* type to normalise them to the long form -# Eventually aiohomekit will use the long form exclusively amd this can be removed. -for k, v in list(CHARACTERISTIC_PLATFORMS.items()): - value = CHARACTERISTIC_PLATFORMS.pop(k) - CHARACTERISTIC_PLATFORMS[CharacteristicsTypes.get_uuid(k)] = value - # Device classes DEVICE_CLASS_ECOBEE_MODE: Final = "homekit_controller__ecobee_mode" diff --git a/homeassistant/components/homekit_controller/cover.py b/homeassistant/components/homekit_controller/cover.py index ca24acc777aa26..bb6bab4a495895 100644 --- a/homeassistant/components/homekit_controller/cover.py +++ b/homeassistant/components/homekit_controller/cover.py @@ -47,7 +47,7 @@ async def async_setup_entry( @callback def async_add_service(service): - if not (entity_class := ENTITY_TYPES.get(service.short_type)): + if not (entity_class := ENTITY_TYPES.get(service.type)): return False info = {"aid": service.accessory.aid, "iid": service.iid} async_add_entities([entity_class(conn, info)], True) diff --git a/homeassistant/components/homekit_controller/device_trigger.py b/homeassistant/components/homekit_controller/device_trigger.py index 5bb7d6346265f8..0ad64723478198 100644 --- a/homeassistant/components/homekit_controller/device_trigger.py +++ b/homeassistant/components/homekit_controller/device_trigger.py @@ -197,7 +197,7 @@ async def async_setup_triggers_for_entry(hass: HomeAssistant, config_entry): @callback def async_add_service(service): aid = service.accessory.aid - service_type = service.short_type + service_type = service.type # If not a known service type then we can't handle any stateless events for it if service_type not in TRIGGER_FINDERS: diff --git a/homeassistant/components/homekit_controller/diagnostics.py b/homeassistant/components/homekit_controller/diagnostics.py index bdf19b6d5930f4..e5183b7be319fc 100644 --- a/homeassistant/components/homekit_controller/diagnostics.py +++ b/homeassistant/components/homekit_controller/diagnostics.py @@ -15,7 +15,7 @@ from .const import KNOWN_DEVICES REDACTED_CHARACTERISTICS = [ - CharacteristicsTypes.get_uuid(CharacteristicsTypes.SERIAL_NUMBER), + CharacteristicsTypes.SERIAL_NUMBER, ] REDACTED_CONFIG_ENTRY_KEYS = [ @@ -112,12 +112,7 @@ def _async_get_diagnostics( for accessory in accessories: for service in accessory.get("services", []): for char in service.get("characteristics", []): - try: - normalized = CharacteristicsTypes.get_uuid(char["type"]) - except KeyError: - normalized = char["type"] - - if normalized in REDACTED_CHARACTERISTICS: + if char["type"] in REDACTED_CHARACTERISTICS: char["value"] = REDACTED if device: diff --git a/homeassistant/components/homekit_controller/fan.py b/homeassistant/components/homekit_controller/fan.py index 89b13206e90ca1..0b6f03b18e3722 100644 --- a/homeassistant/components/homekit_controller/fan.py +++ b/homeassistant/components/homekit_controller/fan.py @@ -160,7 +160,7 @@ async def async_setup_entry( @callback def async_add_service(service): - if not (entity_class := ENTITY_TYPES.get(service.short_type)): + if not (entity_class := ENTITY_TYPES.get(service.type)): return False info = {"aid": service.accessory.aid, "iid": service.iid} async_add_entities([entity_class(conn, info)], True) diff --git a/homeassistant/components/homekit_controller/humidifier.py b/homeassistant/components/homekit_controller/humidifier.py index bde16abd23b302..bb46c5b2decde8 100644 --- a/homeassistant/components/homekit_controller/humidifier.py +++ b/homeassistant/components/homekit_controller/humidifier.py @@ -252,7 +252,7 @@ async def async_setup_entry( @callback def async_add_service(service): - if service.short_type != ServicesTypes.HUMIDIFIER_DEHUMIDIFIER: + if service.type != ServicesTypes.HUMIDIFIER_DEHUMIDIFIER: return False info = {"aid": service.accessory.aid, "iid": service.iid} diff --git a/homeassistant/components/homekit_controller/light.py b/homeassistant/components/homekit_controller/light.py index 77d78074255e29..2e2d60750d84a7 100644 --- a/homeassistant/components/homekit_controller/light.py +++ b/homeassistant/components/homekit_controller/light.py @@ -29,7 +29,7 @@ async def async_setup_entry( @callback def async_add_service(service): - if service.short_type != ServicesTypes.LIGHTBULB: + if service.type != ServicesTypes.LIGHTBULB: return False info = {"aid": service.accessory.aid, "iid": service.iid} async_add_entities([HomeKitLight(conn, info)], True) diff --git a/homeassistant/components/homekit_controller/lock.py b/homeassistant/components/homekit_controller/lock.py index fa8601e90ff842..a3248e3fa02d8c 100644 --- a/homeassistant/components/homekit_controller/lock.py +++ b/homeassistant/components/homekit_controller/lock.py @@ -38,7 +38,7 @@ async def async_setup_entry( @callback def async_add_service(service): - if service.short_type != ServicesTypes.LOCK_MECHANISM: + if service.type != ServicesTypes.LOCK_MECHANISM: return False info = {"aid": service.accessory.aid, "iid": service.iid} async_add_entities([HomeKitLock(conn, info)], True) diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index cdae867ca85971..fcb36d3c54a0fa 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit Controller", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homekit_controller", - "requirements": ["aiohomekit==0.6.11"], + "requirements": ["aiohomekit==0.7.0"], "zeroconf": ["_hap._tcp.local."], "after_dependencies": ["zeroconf"], "codeowners": ["@Jc2k", "@bdraco"], diff --git a/homeassistant/components/homekit_controller/media_player.py b/homeassistant/components/homekit_controller/media_player.py index 6d87250f53c9fb..dda763c4ae5ffd 100644 --- a/homeassistant/components/homekit_controller/media_player.py +++ b/homeassistant/components/homekit_controller/media_player.py @@ -54,7 +54,7 @@ async def async_setup_entry( @callback def async_add_service(service): - if service.short_type != ServicesTypes.TELEVISION: + if service.type != ServicesTypes.TELEVISION: return False info = {"aid": service.accessory.aid, "iid": service.iid} async_add_entities([HomeKitTelevision(conn, info)], True) diff --git a/homeassistant/components/homekit_controller/number.py b/homeassistant/components/homekit_controller/number.py index 135124cb207194..3cfe842e85658e 100644 --- a/homeassistant/components/homekit_controller/number.py +++ b/homeassistant/components/homekit_controller/number.py @@ -17,62 +17,62 @@ from . import KNOWN_DEVICES, CharacteristicEntity NUMBER_ENTITIES: dict[str, NumberEntityDescription] = { - CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL: NumberEntityDescription( - key=CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL, + CharacteristicsTypes.VENDOR_VOCOLINC_HUMIDIFIER_SPRAY_LEVEL: NumberEntityDescription( + key=CharacteristicsTypes.VENDOR_VOCOLINC_HUMIDIFIER_SPRAY_LEVEL, name="Spray Quantity", icon="mdi:water", entity_category=EntityCategory.CONFIG, ), - CharacteristicsTypes.Vendor.EVE_DEGREE_ELEVATION: NumberEntityDescription( - key=CharacteristicsTypes.Vendor.EVE_DEGREE_ELEVATION, + CharacteristicsTypes.VENDOR_EVE_DEGREE_ELEVATION: NumberEntityDescription( + key=CharacteristicsTypes.VENDOR_EVE_DEGREE_ELEVATION, name="Elevation", icon="mdi:elevation-rise", entity_category=EntityCategory.CONFIG, ), - CharacteristicsTypes.Vendor.AQARA_GATEWAY_VOLUME: NumberEntityDescription( - key=CharacteristicsTypes.Vendor.AQARA_GATEWAY_VOLUME, + CharacteristicsTypes.VENDOR_AQARA_GATEWAY_VOLUME: NumberEntityDescription( + key=CharacteristicsTypes.VENDOR_AQARA_GATEWAY_VOLUME, name="Volume", icon="mdi:volume-high", entity_category=EntityCategory.CONFIG, ), - CharacteristicsTypes.Vendor.AQARA_E1_GATEWAY_VOLUME: NumberEntityDescription( - key=CharacteristicsTypes.Vendor.AQARA_E1_GATEWAY_VOLUME, + CharacteristicsTypes.VENDOR_AQARA_E1_GATEWAY_VOLUME: NumberEntityDescription( + key=CharacteristicsTypes.VENDOR_AQARA_E1_GATEWAY_VOLUME, name="Volume", icon="mdi:volume-high", entity_category=EntityCategory.CONFIG, ), - CharacteristicsTypes.Vendor.ECOBEE_HOME_TARGET_COOL: NumberEntityDescription( - key=CharacteristicsTypes.Vendor.ECOBEE_HOME_TARGET_COOL, + CharacteristicsTypes.VENDOR_ECOBEE_HOME_TARGET_COOL: NumberEntityDescription( + key=CharacteristicsTypes.VENDOR_ECOBEE_HOME_TARGET_COOL, name="Home Cool Target", icon="mdi:thermometer-minus", entity_category=EntityCategory.CONFIG, ), - CharacteristicsTypes.Vendor.ECOBEE_HOME_TARGET_HEAT: NumberEntityDescription( - key=CharacteristicsTypes.Vendor.ECOBEE_HOME_TARGET_HEAT, + CharacteristicsTypes.VENDOR_ECOBEE_HOME_TARGET_HEAT: NumberEntityDescription( + key=CharacteristicsTypes.VENDOR_ECOBEE_HOME_TARGET_HEAT, name="Home Heat Target", icon="mdi:thermometer-plus", entity_category=EntityCategory.CONFIG, ), - CharacteristicsTypes.Vendor.ECOBEE_SLEEP_TARGET_COOL: NumberEntityDescription( - key=CharacteristicsTypes.Vendor.ECOBEE_SLEEP_TARGET_COOL, + CharacteristicsTypes.VENDOR_ECOBEE_SLEEP_TARGET_COOL: NumberEntityDescription( + key=CharacteristicsTypes.VENDOR_ECOBEE_SLEEP_TARGET_COOL, name="Sleep Cool Target", icon="mdi:thermometer-minus", entity_category=EntityCategory.CONFIG, ), - CharacteristicsTypes.Vendor.ECOBEE_SLEEP_TARGET_HEAT: NumberEntityDescription( - key=CharacteristicsTypes.Vendor.ECOBEE_SLEEP_TARGET_HEAT, + CharacteristicsTypes.VENDOR_ECOBEE_SLEEP_TARGET_HEAT: NumberEntityDescription( + key=CharacteristicsTypes.VENDOR_ECOBEE_SLEEP_TARGET_HEAT, name="Sleep Heat Target", icon="mdi:thermometer-plus", entity_category=EntityCategory.CONFIG, ), - CharacteristicsTypes.Vendor.ECOBEE_AWAY_TARGET_COOL: NumberEntityDescription( - key=CharacteristicsTypes.Vendor.ECOBEE_AWAY_TARGET_COOL, + CharacteristicsTypes.VENDOR_ECOBEE_AWAY_TARGET_COOL: NumberEntityDescription( + key=CharacteristicsTypes.VENDOR_ECOBEE_AWAY_TARGET_COOL, name="Away Cool Target", icon="mdi:thermometer-minus", entity_category=EntityCategory.CONFIG, ), - CharacteristicsTypes.Vendor.ECOBEE_AWAY_TARGET_HEAT: NumberEntityDescription( - key=CharacteristicsTypes.Vendor.ECOBEE_AWAY_TARGET_HEAT, + CharacteristicsTypes.VENDOR_ECOBEE_AWAY_TARGET_HEAT: NumberEntityDescription( + key=CharacteristicsTypes.VENDOR_ECOBEE_AWAY_TARGET_HEAT, name="Away Heat Target", icon="mdi:thermometer-plus", entity_category=EntityCategory.CONFIG, @@ -226,5 +226,5 @@ async def async_set_value(self, value: float): NUMBER_ENTITY_CLASSES: dict[str, type] = { - CharacteristicsTypes.Vendor.ECOBEE_FAN_WRITE_SPEED: HomeKitEcobeeFanModeNumber, + CharacteristicsTypes.VENDOR_ECOBEE_FAN_WRITE_SPEED: HomeKitEcobeeFanModeNumber, } diff --git a/homeassistant/components/homekit_controller/select.py b/homeassistant/components/homekit_controller/select.py index 55c12c77820f2f..82ae3aff691287 100644 --- a/homeassistant/components/homekit_controller/select.py +++ b/homeassistant/components/homekit_controller/select.py @@ -35,7 +35,7 @@ def name(self) -> str: def get_characteristic_types(self): """Define the homekit characteristics the entity cares about.""" return [ - CharacteristicsTypes.Vendor.ECOBEE_CURRENT_MODE, + CharacteristicsTypes.VENDOR_ECOBEE_CURRENT_MODE, ] @property @@ -47,7 +47,7 @@ async def async_select_option(self, option: str) -> None: """Set the current mode.""" option_int = _ECOBEE_MODE_TO_NUMBERS[option] await self.async_put_characteristics( - {CharacteristicsTypes.Vendor.ECOBEE_SET_HOLD_SCHEDULE: option_int} + {CharacteristicsTypes.VENDOR_ECOBEE_SET_HOLD_SCHEDULE: option_int} ) @@ -62,7 +62,7 @@ async def async_setup_entry( @callback def async_add_characteristic(char: Characteristic): - if char.type == CharacteristicsTypes.Vendor.ECOBEE_CURRENT_MODE: + if char.type == CharacteristicsTypes.VENDOR_ECOBEE_CURRENT_MODE: info = {"aid": char.service.accessory.aid, "iid": char.service.iid} async_add_entities([EcobeeModeSelect(conn, info, char)]) return True diff --git a/homeassistant/components/homekit_controller/sensor.py b/homeassistant/components/homekit_controller/sensor.py index 0cec354e1ab4a0..13491c8ee0373a 100644 --- a/homeassistant/components/homekit_controller/sensor.py +++ b/homeassistant/components/homekit_controller/sensor.py @@ -42,85 +42,85 @@ class HomeKitSensorEntityDescription(SensorEntityDescription): SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = { - CharacteristicsTypes.Vendor.CONNECTSENSE_ENERGY_WATT: HomeKitSensorEntityDescription( - key=CharacteristicsTypes.Vendor.CONNECTSENSE_ENERGY_WATT, + CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_WATT: HomeKitSensorEntityDescription( + key=CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_WATT, name="Power", device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=POWER_WATT, ), - CharacteristicsTypes.Vendor.CONNECTSENSE_ENERGY_AMPS: HomeKitSensorEntityDescription( - key=CharacteristicsTypes.Vendor.CONNECTSENSE_ENERGY_AMPS, + CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_AMPS: HomeKitSensorEntityDescription( + key=CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_AMPS, name="Current", device_class=SensorDeviceClass.CURRENT, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE, ), - CharacteristicsTypes.Vendor.CONNECTSENSE_ENERGY_AMPS_20: HomeKitSensorEntityDescription( - key=CharacteristicsTypes.Vendor.CONNECTSENSE_ENERGY_AMPS_20, + CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_AMPS_20: HomeKitSensorEntityDescription( + key=CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_AMPS_20, name="Current", device_class=SensorDeviceClass.CURRENT, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE, ), - CharacteristicsTypes.Vendor.CONNECTSENSE_ENERGY_KW_HOUR: HomeKitSensorEntityDescription( - key=CharacteristicsTypes.Vendor.CONNECTSENSE_ENERGY_KW_HOUR, + CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_KW_HOUR: HomeKitSensorEntityDescription( + key=CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_KW_HOUR, name="Energy kWh", device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, ), - CharacteristicsTypes.Vendor.EVE_ENERGY_WATT: HomeKitSensorEntityDescription( - key=CharacteristicsTypes.Vendor.EVE_ENERGY_WATT, + CharacteristicsTypes.VENDOR_EVE_ENERGY_WATT: HomeKitSensorEntityDescription( + key=CharacteristicsTypes.VENDOR_EVE_ENERGY_WATT, name="Power", device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=POWER_WATT, ), - CharacteristicsTypes.Vendor.EVE_ENERGY_KW_HOUR: HomeKitSensorEntityDescription( - key=CharacteristicsTypes.Vendor.EVE_ENERGY_KW_HOUR, + CharacteristicsTypes.VENDOR_EVE_ENERGY_KW_HOUR: HomeKitSensorEntityDescription( + key=CharacteristicsTypes.VENDOR_EVE_ENERGY_KW_HOUR, name="Energy kWh", device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, ), - CharacteristicsTypes.Vendor.EVE_ENERGY_VOLTAGE: HomeKitSensorEntityDescription( - key=CharacteristicsTypes.Vendor.EVE_ENERGY_VOLTAGE, + CharacteristicsTypes.VENDOR_EVE_ENERGY_VOLTAGE: HomeKitSensorEntityDescription( + key=CharacteristicsTypes.VENDOR_EVE_ENERGY_VOLTAGE, name="Volts", device_class=SensorDeviceClass.VOLTAGE, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT, ), - CharacteristicsTypes.Vendor.EVE_ENERGY_AMPERE: HomeKitSensorEntityDescription( - key=CharacteristicsTypes.Vendor.EVE_ENERGY_AMPERE, + CharacteristicsTypes.VENDOR_EVE_ENERGY_AMPERE: HomeKitSensorEntityDescription( + key=CharacteristicsTypes.VENDOR_EVE_ENERGY_AMPERE, name="Amps", device_class=SensorDeviceClass.CURRENT, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE, ), - CharacteristicsTypes.Vendor.KOOGEEK_REALTIME_ENERGY: HomeKitSensorEntityDescription( - key=CharacteristicsTypes.Vendor.KOOGEEK_REALTIME_ENERGY, + CharacteristicsTypes.VENDOR_KOOGEEK_REALTIME_ENERGY: HomeKitSensorEntityDescription( + key=CharacteristicsTypes.VENDOR_KOOGEEK_REALTIME_ENERGY, name="Power", device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=POWER_WATT, ), - CharacteristicsTypes.Vendor.KOOGEEK_REALTIME_ENERGY_2: HomeKitSensorEntityDescription( - key=CharacteristicsTypes.Vendor.KOOGEEK_REALTIME_ENERGY_2, + CharacteristicsTypes.VENDOR_KOOGEEK_REALTIME_ENERGY_2: HomeKitSensorEntityDescription( + key=CharacteristicsTypes.VENDOR_KOOGEEK_REALTIME_ENERGY_2, name="Power", device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=POWER_WATT, ), - CharacteristicsTypes.Vendor.EVE_DEGREE_AIR_PRESSURE: HomeKitSensorEntityDescription( - key=CharacteristicsTypes.Vendor.EVE_DEGREE_AIR_PRESSURE, + CharacteristicsTypes.VENDOR_EVE_DEGREE_AIR_PRESSURE: HomeKitSensorEntityDescription( + key=CharacteristicsTypes.VENDOR_EVE_DEGREE_AIR_PRESSURE, name="Air Pressure", device_class=SensorDeviceClass.PRESSURE, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=PRESSURE_HPA, ), - CharacteristicsTypes.Vendor.VOCOLINC_OUTLET_ENERGY: HomeKitSensorEntityDescription( - key=CharacteristicsTypes.Vendor.VOCOLINC_OUTLET_ENERGY, + CharacteristicsTypes.VENDOR_VOCOLINC_OUTLET_ENERGY: HomeKitSensorEntityDescription( + key=CharacteristicsTypes.VENDOR_VOCOLINC_OUTLET_ENERGY, name="Power", device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, @@ -134,10 +134,7 @@ class HomeKitSensorEntityDescription(SensorEntityDescription): native_unit_of_measurement=TEMP_CELSIUS, # This sensor is only for temperature characteristics that are not part # of a temperature sensor service. - probe=( - lambda char: char.service.type - != ServicesTypes.get_uuid(ServicesTypes.TEMPERATURE_SENSOR) - ), + probe=(lambda char: char.service.type != ServicesTypes.TEMPERATURE_SENSOR), ), CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT: HomeKitSensorEntityDescription( key=CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT, @@ -147,10 +144,7 @@ class HomeKitSensorEntityDescription(SensorEntityDescription): native_unit_of_measurement=PERCENTAGE, # This sensor is only for humidity characteristics that are not part # of a humidity sensor service. - probe=( - lambda char: char.service.type - != ServicesTypes.get_uuid(ServicesTypes.HUMIDITY_SENSOR) - ), + probe=(lambda char: char.service.type != ServicesTypes.HUMIDITY_SENSOR), ), CharacteristicsTypes.AIR_QUALITY: HomeKitSensorEntityDescription( key=CharacteristicsTypes.AIR_QUALITY, @@ -202,15 +196,6 @@ class HomeKitSensorEntityDescription(SensorEntityDescription): ), } -# For legacy reasons, "built-in" characteristic types are in their short form -# And vendor types don't have a short form -# This means long and short forms get mixed up in this dict, and comparisons -# don't work! -# We call get_uuid on *every* type to normalise them to the long form -# Eventually aiohomekit will use the long form exclusively amd this can be removed. -for k, v in list(SIMPLE_SENSOR.items()): - SIMPLE_SENSOR[CharacteristicsTypes.get_uuid(k)] = SIMPLE_SENSOR.pop(k) - class HomeKitHumiditySensor(HomeKitEntity, SensorEntity): """Representation of a Homekit humidity sensor.""" @@ -415,7 +400,7 @@ async def async_setup_entry( @callback def async_add_service(service): - if not (entity_class := ENTITY_TYPES.get(service.short_type)): + if not (entity_class := ENTITY_TYPES.get(service.type)): return False info = {"aid": service.accessory.aid, "iid": service.iid} async_add_entities([entity_class(conn, info)], True) diff --git a/homeassistant/components/homekit_controller/switch.py b/homeassistant/components/homekit_controller/switch.py index 8228d9546cb632..2645907a962bf1 100644 --- a/homeassistant/components/homekit_controller/switch.py +++ b/homeassistant/components/homekit_controller/switch.py @@ -35,14 +35,14 @@ class DeclarativeSwitchEntityDescription(SwitchEntityDescription): SWITCH_ENTITIES: dict[str, DeclarativeSwitchEntityDescription] = { - CharacteristicsTypes.Vendor.AQARA_PAIRING_MODE: DeclarativeSwitchEntityDescription( - key=CharacteristicsTypes.Vendor.AQARA_PAIRING_MODE, + CharacteristicsTypes.VENDOR_AQARA_PAIRING_MODE: DeclarativeSwitchEntityDescription( + key=CharacteristicsTypes.VENDOR_AQARA_PAIRING_MODE, name="Pairing Mode", icon="mdi:lock-open", entity_category=EntityCategory.CONFIG, ), - CharacteristicsTypes.Vendor.AQARA_E1_PAIRING_MODE: DeclarativeSwitchEntityDescription( - key=CharacteristicsTypes.Vendor.AQARA_E1_PAIRING_MODE, + CharacteristicsTypes.VENDOR_AQARA_E1_PAIRING_MODE: DeclarativeSwitchEntityDescription( + key=CharacteristicsTypes.VENDOR_AQARA_E1_PAIRING_MODE, name="Pairing Mode", icon="mdi:lock-open", entity_category=EntityCategory.CONFIG, @@ -189,7 +189,7 @@ async def async_setup_entry( @callback def async_add_service(service): - if not (entity_class := ENTITY_TYPES.get(service.short_type)): + if not (entity_class := ENTITY_TYPES.get(service.type)): return False info = {"aid": service.accessory.aid, "iid": service.iid} async_add_entities([entity_class(conn, info)], True) diff --git a/requirements_all.txt b/requirements_all.txt index 271bb4b583e47e..f1b59e57f12fc5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -184,7 +184,7 @@ aioguardian==2021.11.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==0.6.11 +aiohomekit==0.7.0 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d880e4e65ce45a..94566810bcca01 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -134,7 +134,7 @@ aioguardian==2021.11.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==0.6.11 +aiohomekit==0.7.0 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/tests/components/homekit_controller/common.py b/tests/components/homekit_controller/common.py index 0891d6209b7921..4fabe504d4b22c 100644 --- a/tests/components/homekit_controller/common.py +++ b/tests/components/homekit_controller/common.py @@ -10,7 +10,6 @@ from unittest import mock from aiohomekit.model import Accessories, Accessory -from aiohomekit.model.services import ServicesTypes from aiohomekit.testing import FakeController, FakePairing from homeassistant.components import zeroconf @@ -261,7 +260,7 @@ async def setup_test_component(hass, setup_accessory, capitalize=False, suffix=N domain = None for service in accessory.services: - service_name = ServicesTypes.get_short_uuid(service.type) + service_name = service.type if service_name in HOMEKIT_ACCESSORY_DISPATCH: domain = HOMEKIT_ACCESSORY_DISPATCH[service_name] break diff --git a/tests/components/homekit_controller/test_button.py b/tests/components/homekit_controller/test_button.py index 131fed572f752a..79dbc59a38a077 100644 --- a/tests/components/homekit_controller/test_button.py +++ b/tests/components/homekit_controller/test_button.py @@ -9,7 +9,7 @@ def create_switch_with_setup_button(accessory): """Define setup button characteristics.""" service = accessory.add_service(ServicesTypes.OUTLET) - setup = service.add_char(CharacteristicsTypes.Vendor.HAA_SETUP) + setup = service.add_char(CharacteristicsTypes.VENDOR_HAA_SETUP) setup.value = "" setup.format = "string" @@ -24,7 +24,7 @@ def create_switch_with_ecobee_clear_hold_button(accessory): """Define setup button characteristics.""" service = accessory.add_service(ServicesTypes.OUTLET) - setup = service.add_char(CharacteristicsTypes.Vendor.ECOBEE_CLEAR_HOLD) + setup = service.add_char(CharacteristicsTypes.VENDOR_ECOBEE_CLEAR_HOLD) setup.value = "" setup.format = "string" @@ -57,7 +57,7 @@ async def test_press_button(hass): button.async_assert_service_values( ServicesTypes.OUTLET, { - CharacteristicsTypes.Vendor.HAA_SETUP: "#HAA@trcmd", + CharacteristicsTypes.VENDOR_HAA_SETUP: "#HAA@trcmd", }, ) @@ -86,6 +86,6 @@ async def test_ecobee_clear_hold_press_button(hass): clear_hold.async_assert_service_values( ServicesTypes.OUTLET, { - CharacteristicsTypes.Vendor.ECOBEE_CLEAR_HOLD: True, + CharacteristicsTypes.VENDOR_ECOBEE_CLEAR_HOLD: True, }, ) diff --git a/tests/components/homekit_controller/test_diagnostics.py b/tests/components/homekit_controller/test_diagnostics.py index bd9aa30f6aebec..8d22f7ff4bc1ac 100644 --- a/tests/components/homekit_controller/test_diagnostics.py +++ b/tests/components/homekit_controller/test_diagnostics.py @@ -151,7 +151,7 @@ async def test_config_entry(hass: HomeAssistant, hass_client: ClientSession, utc }, { "iid": 13, - "type": "4aaaf940-0dec-11e5-b939-0800200c9a66", + "type": "4AAAF940-0DEC-11E5-B939-0800200C9A66", "characteristics": [ { "type": "4AAAF942-0DEC-11E5-B939-0800200C9A66", @@ -422,7 +422,7 @@ async def test_device(hass: HomeAssistant, hass_client: ClientSession, utcnow): }, { "iid": 13, - "type": "4aaaf940-0dec-11e5-b939-0800200c9a66", + "type": "4AAAF940-0DEC-11E5-B939-0800200C9A66", "characteristics": [ { "type": "4AAAF942-0DEC-11E5-B939-0800200C9A66", diff --git a/tests/components/homekit_controller/test_number.py b/tests/components/homekit_controller/test_number.py index a8a40bfa7320c5..78bdb394f0c80d 100644 --- a/tests/components/homekit_controller/test_number.py +++ b/tests/components/homekit_controller/test_number.py @@ -10,7 +10,7 @@ def create_switch_with_spray_level(accessory): service = accessory.add_service(ServicesTypes.OUTLET) spray_level = service.add_char( - CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL + CharacteristicsTypes.VENDOR_VOCOLINC_HUMIDIFIER_SPRAY_LEVEL ) spray_level.perms.append("ev") @@ -31,7 +31,7 @@ def create_switch_with_ecobee_fan_mode(accessory): service = accessory.add_service(ServicesTypes.OUTLET) ecobee_fan_mode = service.add_char( - CharacteristicsTypes.Vendor.ECOBEE_FAN_WRITE_SPEED + CharacteristicsTypes.VENDOR_ECOBEE_FAN_WRITE_SPEED ) ecobee_fan_mode.value = 0 @@ -67,7 +67,7 @@ async def test_read_number(hass, utcnow): state = await spray_level.async_update( ServicesTypes.OUTLET, - {CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL: 5}, + {CharacteristicsTypes.VENDOR_VOCOLINC_HUMIDIFIER_SPRAY_LEVEL: 5}, ) assert state.state == "5" @@ -93,7 +93,7 @@ async def test_write_number(hass, utcnow): ) spray_level.async_assert_service_values( ServicesTypes.OUTLET, - {CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL: 5}, + {CharacteristicsTypes.VENDOR_VOCOLINC_HUMIDIFIER_SPRAY_LEVEL: 5}, ) await hass.services.async_call( @@ -104,7 +104,7 @@ async def test_write_number(hass, utcnow): ) spray_level.async_assert_service_values( ServicesTypes.OUTLET, - {CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL: 3}, + {CharacteristicsTypes.VENDOR_VOCOLINC_HUMIDIFIER_SPRAY_LEVEL: 3}, ) @@ -129,7 +129,7 @@ async def test_write_ecobee_fan_mode_number(hass, utcnow): ) fan_mode.async_assert_service_values( ServicesTypes.OUTLET, - {CharacteristicsTypes.Vendor.ECOBEE_FAN_WRITE_SPEED: 1}, + {CharacteristicsTypes.VENDOR_ECOBEE_FAN_WRITE_SPEED: 1}, ) await hass.services.async_call( @@ -140,7 +140,7 @@ async def test_write_ecobee_fan_mode_number(hass, utcnow): ) fan_mode.async_assert_service_values( ServicesTypes.OUTLET, - {CharacteristicsTypes.Vendor.ECOBEE_FAN_WRITE_SPEED: 2}, + {CharacteristicsTypes.VENDOR_ECOBEE_FAN_WRITE_SPEED: 2}, ) await hass.services.async_call( @@ -151,7 +151,7 @@ async def test_write_ecobee_fan_mode_number(hass, utcnow): ) fan_mode.async_assert_service_values( ServicesTypes.OUTLET, - {CharacteristicsTypes.Vendor.ECOBEE_FAN_WRITE_SPEED: 99}, + {CharacteristicsTypes.VENDOR_ECOBEE_FAN_WRITE_SPEED: 99}, ) await hass.services.async_call( @@ -162,7 +162,7 @@ async def test_write_ecobee_fan_mode_number(hass, utcnow): ) fan_mode.async_assert_service_values( ServicesTypes.OUTLET, - {CharacteristicsTypes.Vendor.ECOBEE_FAN_WRITE_SPEED: 100}, + {CharacteristicsTypes.VENDOR_ECOBEE_FAN_WRITE_SPEED: 100}, ) await hass.services.async_call( @@ -173,5 +173,5 @@ async def test_write_ecobee_fan_mode_number(hass, utcnow): ) fan_mode.async_assert_service_values( ServicesTypes.OUTLET, - {CharacteristicsTypes.Vendor.ECOBEE_FAN_WRITE_SPEED: 0}, + {CharacteristicsTypes.VENDOR_ECOBEE_FAN_WRITE_SPEED: 0}, ) diff --git a/tests/components/homekit_controller/test_select.py b/tests/components/homekit_controller/test_select.py index 6cc7f5336b45e2..55d5b168abea65 100644 --- a/tests/components/homekit_controller/test_select.py +++ b/tests/components/homekit_controller/test_select.py @@ -10,11 +10,11 @@ def create_service_with_ecobee_mode(accessory: Accessory): """Define a thermostat with ecobee mode characteristics.""" service = accessory.add_service(ServicesTypes.THERMOSTAT, add_required=True) - current_mode = service.add_char(CharacteristicsTypes.Vendor.ECOBEE_CURRENT_MODE) + current_mode = service.add_char(CharacteristicsTypes.VENDOR_ECOBEE_CURRENT_MODE) current_mode.value = 0 current_mode.perms.append("ev") - service.add_char(CharacteristicsTypes.Vendor.ECOBEE_SET_HOLD_SCHEDULE) + service.add_char(CharacteristicsTypes.VENDOR_ECOBEE_SET_HOLD_SCHEDULE) return service @@ -35,7 +35,7 @@ async def test_read_current_mode(hass, utcnow): state = await ecobee_mode.async_update( ServicesTypes.THERMOSTAT, { - CharacteristicsTypes.Vendor.ECOBEE_CURRENT_MODE: 0, + CharacteristicsTypes.VENDOR_ECOBEE_CURRENT_MODE: 0, }, ) assert state.state == "home" @@ -43,7 +43,7 @@ async def test_read_current_mode(hass, utcnow): state = await ecobee_mode.async_update( ServicesTypes.THERMOSTAT, { - CharacteristicsTypes.Vendor.ECOBEE_CURRENT_MODE: 1, + CharacteristicsTypes.VENDOR_ECOBEE_CURRENT_MODE: 1, }, ) assert state.state == "sleep" @@ -51,7 +51,7 @@ async def test_read_current_mode(hass, utcnow): state = await ecobee_mode.async_update( ServicesTypes.THERMOSTAT, { - CharacteristicsTypes.Vendor.ECOBEE_CURRENT_MODE: 2, + CharacteristicsTypes.VENDOR_ECOBEE_CURRENT_MODE: 2, }, ) assert state.state == "away" @@ -79,7 +79,7 @@ async def test_write_current_mode(hass, utcnow): ) current_mode.async_assert_service_values( ServicesTypes.THERMOSTAT, - {CharacteristicsTypes.Vendor.ECOBEE_SET_HOLD_SCHEDULE: 0}, + {CharacteristicsTypes.VENDOR_ECOBEE_SET_HOLD_SCHEDULE: 0}, ) await hass.services.async_call( @@ -90,7 +90,7 @@ async def test_write_current_mode(hass, utcnow): ) current_mode.async_assert_service_values( ServicesTypes.THERMOSTAT, - {CharacteristicsTypes.Vendor.ECOBEE_SET_HOLD_SCHEDULE: 1}, + {CharacteristicsTypes.VENDOR_ECOBEE_SET_HOLD_SCHEDULE: 1}, ) await hass.services.async_call( @@ -101,5 +101,5 @@ async def test_write_current_mode(hass, utcnow): ) current_mode.async_assert_service_values( ServicesTypes.THERMOSTAT, - {CharacteristicsTypes.Vendor.ECOBEE_SET_HOLD_SCHEDULE: 2}, + {CharacteristicsTypes.VENDOR_ECOBEE_SET_HOLD_SCHEDULE: 2}, ) diff --git a/tests/components/homekit_controller/test_sensor.py b/tests/components/homekit_controller/test_sensor.py index fc1a48f0f5d019..145d85eeed7fb7 100644 --- a/tests/components/homekit_controller/test_sensor.py +++ b/tests/components/homekit_controller/test_sensor.py @@ -247,7 +247,7 @@ def create_switch_with_sensor(accessory): service = accessory.add_service(ServicesTypes.OUTLET) realtime_energy = service.add_char( - CharacteristicsTypes.Vendor.KOOGEEK_REALTIME_ENERGY + CharacteristicsTypes.VENDOR_KOOGEEK_REALTIME_ENERGY ) realtime_energy.value = 0 realtime_energy.format = "float" @@ -275,7 +275,7 @@ async def test_switch_with_sensor(hass, utcnow): state = await energy_helper.async_update( ServicesTypes.OUTLET, { - CharacteristicsTypes.Vendor.KOOGEEK_REALTIME_ENERGY: 1, + CharacteristicsTypes.VENDOR_KOOGEEK_REALTIME_ENERGY: 1, }, ) assert state.state == "1" @@ -283,7 +283,7 @@ async def test_switch_with_sensor(hass, utcnow): state = await energy_helper.async_update( ServicesTypes.OUTLET, { - CharacteristicsTypes.Vendor.KOOGEEK_REALTIME_ENERGY: 50, + CharacteristicsTypes.VENDOR_KOOGEEK_REALTIME_ENERGY: 50, }, ) assert state.state == "50" @@ -295,7 +295,7 @@ async def test_sensor_unavailable(hass, utcnow): # Find the energy sensor and mark it as offline outlet = helper.accessory.services.first(service_type=ServicesTypes.OUTLET) - realtime_energy = outlet[CharacteristicsTypes.Vendor.KOOGEEK_REALTIME_ENERGY] + realtime_energy = outlet[CharacteristicsTypes.VENDOR_KOOGEEK_REALTIME_ENERGY] realtime_energy.status = HapStatusCode.UNABLE_TO_COMMUNICATE # Helper will be for the primary entity, which is the outlet. Make a helper for the sensor. diff --git a/tests/components/homekit_controller/test_switch.py b/tests/components/homekit_controller/test_switch.py index fbea04171cb34b..9fafa4afada0a1 100644 --- a/tests/components/homekit_controller/test_switch.py +++ b/tests/components/homekit_controller/test_switch.py @@ -42,7 +42,7 @@ def create_char_switch_service(accessory): """Define swtch characteristics.""" service = accessory.add_service(ServicesTypes.OUTLET) - on_char = service.add_char(CharacteristicsTypes.Vendor.AQARA_PAIRING_MODE) + on_char = service.add_char(CharacteristicsTypes.VENDOR_AQARA_PAIRING_MODE) on_char.perms.append("ev") on_char.value = False @@ -178,7 +178,7 @@ async def test_char_switch_change_state(hass, utcnow): helper.async_assert_service_values( ServicesTypes.OUTLET, { - CharacteristicsTypes.Vendor.AQARA_PAIRING_MODE: True, + CharacteristicsTypes.VENDOR_AQARA_PAIRING_MODE: True, }, ) @@ -191,7 +191,7 @@ async def test_char_switch_change_state(hass, utcnow): helper.async_assert_service_values( ServicesTypes.OUTLET, { - CharacteristicsTypes.Vendor.AQARA_PAIRING_MODE: False, + CharacteristicsTypes.VENDOR_AQARA_PAIRING_MODE: False, }, ) @@ -205,13 +205,13 @@ async def test_char_switch_read_state(hass, utcnow): # Simulate that someone switched on the device in the real world not via HA switch_1 = await helper.async_update( ServicesTypes.OUTLET, - {CharacteristicsTypes.Vendor.AQARA_PAIRING_MODE: True}, + {CharacteristicsTypes.VENDOR_AQARA_PAIRING_MODE: True}, ) assert switch_1.state == "on" # Simulate that device switched off in the real world not via HA switch_1 = await helper.async_update( ServicesTypes.OUTLET, - {CharacteristicsTypes.Vendor.AQARA_PAIRING_MODE: False}, + {CharacteristicsTypes.VENDOR_AQARA_PAIRING_MODE: False}, ) assert switch_1.state == "off" From 70da08499add6d3ce7b6320623390842c365a4dd Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Mon, 31 Jan 2022 16:59:18 -0600 Subject: [PATCH 0146/1098] Refactor sonarr tests (#64886) --- tests/components/sonarr/__init__.py | 243 +----------------- tests/components/sonarr/conftest.py | 159 ++++++++++++ tests/components/sonarr/fixtures/app.json | 28 ++ .../sonarr/fixtures/system-status.json | 18 -- tests/components/sonarr/test_config_flow.py | 121 ++++----- tests/components/sonarr/test_init.py | 59 +++-- tests/components/sonarr/test_sensor.py | 39 +-- 7 files changed, 309 insertions(+), 358 deletions(-) create mode 100644 tests/components/sonarr/conftest.py create mode 100644 tests/components/sonarr/fixtures/app.json delete mode 100644 tests/components/sonarr/fixtures/system-status.json diff --git a/tests/components/sonarr/__init__.py b/tests/components/sonarr/__init__.py index 8172cb4e0dd3ea..cd3fb8f795a7d2 100644 --- a/tests/components/sonarr/__init__.py +++ b/tests/components/sonarr/__init__.py @@ -1,244 +1,13 @@ """Tests for the Sonarr component.""" -from http import HTTPStatus -from socket import gaierror as SocketGIAError -from unittest.mock import patch - -from homeassistant.components.sonarr.const import ( - CONF_BASE_PATH, - CONF_UPCOMING_DAYS, - CONF_WANTED_MAX_ITEMS, - DEFAULT_UPCOMING_DAYS, - DEFAULT_WANTED_MAX_ITEMS, - DOMAIN, -) -from homeassistant.const import ( - CONF_API_KEY, - CONF_HOST, - CONF_PORT, - CONF_SSL, - CONF_VERIFY_SSL, - CONTENT_TYPE_JSON, -) -from homeassistant.core import HomeAssistant - -from tests.common import MockConfigEntry, load_fixture -from tests.test_util.aiohttp import AiohttpClientMocker - -HOST = "192.168.1.189" -PORT = 8989 -BASE_PATH = "/api" -API_KEY = "MOCK_API_KEY" +from homeassistant.components.sonarr.const import CONF_BASE_PATH +from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT, CONF_SSL MOCK_REAUTH_INPUT = {CONF_API_KEY: "test-api-key-reauth"} MOCK_USER_INPUT = { - CONF_HOST: HOST, - CONF_PORT: PORT, - CONF_BASE_PATH: BASE_PATH, + CONF_HOST: "192.168.1.189", + CONF_PORT: 8989, + CONF_BASE_PATH: "/api", CONF_SSL: False, - CONF_API_KEY: API_KEY, + CONF_API_KEY: "MOCK_API_KEY", } - - -def mock_connection( - aioclient_mock: AiohttpClientMocker, - host: str = HOST, - port: str = PORT, - base_path: str = BASE_PATH, - error: bool = False, - invalid_auth: bool = False, - server_error: bool = False, -) -> None: - """Mock Sonarr connection.""" - if error: - mock_connection_error( - aioclient_mock, - host=host, - port=port, - base_path=base_path, - ) - return - - if invalid_auth: - mock_connection_invalid_auth( - aioclient_mock, - host=host, - port=port, - base_path=base_path, - ) - return - - if server_error: - mock_connection_server_error( - aioclient_mock, - host=host, - port=port, - base_path=base_path, - ) - return - - sonarr_url = f"http://{host}:{port}{base_path}" - - aioclient_mock.get( - f"{sonarr_url}/system/status", - text=load_fixture("sonarr/system-status.json"), - headers={"Content-Type": CONTENT_TYPE_JSON}, - ) - - aioclient_mock.get( - f"{sonarr_url}/diskspace", - text=load_fixture("sonarr/diskspace.json"), - headers={"Content-Type": CONTENT_TYPE_JSON}, - ) - - aioclient_mock.get( - f"{sonarr_url}/calendar", - text=load_fixture("sonarr/calendar.json"), - headers={"Content-Type": CONTENT_TYPE_JSON}, - ) - - aioclient_mock.get( - f"{sonarr_url}/command", - text=load_fixture("sonarr/command.json"), - headers={"Content-Type": CONTENT_TYPE_JSON}, - ) - - aioclient_mock.get( - f"{sonarr_url}/queue", - text=load_fixture("sonarr/queue.json"), - headers={"Content-Type": CONTENT_TYPE_JSON}, - ) - - aioclient_mock.get( - f"{sonarr_url}/series", - text=load_fixture("sonarr/series.json"), - headers={"Content-Type": CONTENT_TYPE_JSON}, - ) - - aioclient_mock.get( - f"{sonarr_url}/wanted/missing", - text=load_fixture("sonarr/wanted-missing.json"), - headers={"Content-Type": CONTENT_TYPE_JSON}, - ) - - -def mock_connection_error( - aioclient_mock: AiohttpClientMocker, - host: str = HOST, - port: str = PORT, - base_path: str = BASE_PATH, -) -> None: - """Mock Sonarr connection errors.""" - sonarr_url = f"http://{host}:{port}{base_path}" - - aioclient_mock.get(f"{sonarr_url}/system/status", exc=SocketGIAError) - aioclient_mock.get(f"{sonarr_url}/diskspace", exc=SocketGIAError) - aioclient_mock.get(f"{sonarr_url}/calendar", exc=SocketGIAError) - aioclient_mock.get(f"{sonarr_url}/command", exc=SocketGIAError) - aioclient_mock.get(f"{sonarr_url}/queue", exc=SocketGIAError) - aioclient_mock.get(f"{sonarr_url}/series", exc=SocketGIAError) - aioclient_mock.get(f"{sonarr_url}/missing/wanted", exc=SocketGIAError) - - -def mock_connection_invalid_auth( - aioclient_mock: AiohttpClientMocker, - host: str = HOST, - port: str = PORT, - base_path: str = BASE_PATH, -) -> None: - """Mock Sonarr invalid auth errors.""" - sonarr_url = f"http://{host}:{port}{base_path}" - - aioclient_mock.get(f"{sonarr_url}/system/status", status=HTTPStatus.FORBIDDEN) - aioclient_mock.get(f"{sonarr_url}/diskspace", status=HTTPStatus.FORBIDDEN) - aioclient_mock.get(f"{sonarr_url}/calendar", status=HTTPStatus.FORBIDDEN) - aioclient_mock.get(f"{sonarr_url}/command", status=HTTPStatus.FORBIDDEN) - aioclient_mock.get(f"{sonarr_url}/queue", status=HTTPStatus.FORBIDDEN) - aioclient_mock.get(f"{sonarr_url}/series", status=HTTPStatus.FORBIDDEN) - aioclient_mock.get(f"{sonarr_url}/missing/wanted", status=HTTPStatus.FORBIDDEN) - - -def mock_connection_server_error( - aioclient_mock: AiohttpClientMocker, - host: str = HOST, - port: str = PORT, - base_path: str = BASE_PATH, -) -> None: - """Mock Sonarr server errors.""" - sonarr_url = f"http://{host}:{port}{base_path}" - - aioclient_mock.get( - f"{sonarr_url}/system/status", status=HTTPStatus.INTERNAL_SERVER_ERROR - ) - aioclient_mock.get( - f"{sonarr_url}/diskspace", status=HTTPStatus.INTERNAL_SERVER_ERROR - ) - aioclient_mock.get( - f"{sonarr_url}/calendar", status=HTTPStatus.INTERNAL_SERVER_ERROR - ) - aioclient_mock.get(f"{sonarr_url}/command", status=HTTPStatus.INTERNAL_SERVER_ERROR) - aioclient_mock.get(f"{sonarr_url}/queue", status=HTTPStatus.INTERNAL_SERVER_ERROR) - aioclient_mock.get(f"{sonarr_url}/series", status=HTTPStatus.INTERNAL_SERVER_ERROR) - aioclient_mock.get( - f"{sonarr_url}/missing/wanted", status=HTTPStatus.INTERNAL_SERVER_ERROR - ) - - -async def setup_integration( - hass: HomeAssistant, - aioclient_mock: AiohttpClientMocker, - host: str = HOST, - port: str = PORT, - base_path: str = BASE_PATH, - api_key: str = API_KEY, - unique_id: str = None, - skip_entry_setup: bool = False, - connection_error: bool = False, - invalid_auth: bool = False, - server_error: bool = False, -) -> MockConfigEntry: - """Set up the Sonarr integration in Home Assistant.""" - entry = MockConfigEntry( - domain=DOMAIN, - unique_id=unique_id, - data={ - CONF_HOST: host, - CONF_PORT: port, - CONF_BASE_PATH: base_path, - CONF_SSL: False, - CONF_VERIFY_SSL: False, - CONF_API_KEY: api_key, - CONF_UPCOMING_DAYS: DEFAULT_UPCOMING_DAYS, - CONF_WANTED_MAX_ITEMS: DEFAULT_WANTED_MAX_ITEMS, - }, - options={ - CONF_UPCOMING_DAYS: DEFAULT_UPCOMING_DAYS, - CONF_WANTED_MAX_ITEMS: DEFAULT_WANTED_MAX_ITEMS, - }, - ) - - entry.add_to_hass(hass) - - mock_connection( - aioclient_mock, - host=host, - port=port, - base_path=base_path, - error=connection_error, - invalid_auth=invalid_auth, - server_error=server_error, - ) - - if not skip_entry_setup: - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - return entry - - -def _patch_async_setup_entry(return_value=True): - """Patch the async entry setup of sonarr.""" - return patch( - "homeassistant.components.sonarr.async_setup_entry", - return_value=return_value, - ) diff --git a/tests/components/sonarr/conftest.py b/tests/components/sonarr/conftest.py new file mode 100644 index 00000000000000..a03ae7532d43ad --- /dev/null +++ b/tests/components/sonarr/conftest.py @@ -0,0 +1,159 @@ +"""Fixtures for Sonarr integration tests.""" +from collections.abc import Generator +import json +from unittest.mock import MagicMock, patch + +import pytest +from sonarr.models import ( + Application, + CommandItem, + Episode, + QueueItem, + SeriesItem, + WantedResults, +) + +from homeassistant.components.sonarr.const import ( + CONF_BASE_PATH, + CONF_UPCOMING_DAYS, + CONF_WANTED_MAX_ITEMS, + DEFAULT_UPCOMING_DAYS, + DEFAULT_WANTED_MAX_ITEMS, + DOMAIN, +) +from homeassistant.const import ( + CONF_API_KEY, + CONF_HOST, + CONF_PORT, + CONF_SSL, + CONF_VERIFY_SSL, +) +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry, load_fixture + + +def sonarr_calendar(): + """Generate a response for the calendar method.""" + results = json.loads(load_fixture("sonarr/calendar.json")) + return [Episode.from_dict(result) for result in results] + + +def sonarr_commands(): + """Generate a response for the commands method.""" + results = json.loads(load_fixture("sonarr/command.json")) + return [CommandItem.from_dict(result) for result in results] + + +def sonarr_queue(): + """Generate a response for the queue method.""" + results = json.loads(load_fixture("sonarr/queue.json")) + return [QueueItem.from_dict(result) for result in results] + + +def sonarr_series(): + """Generate a response for the series method.""" + results = json.loads(load_fixture("sonarr/series.json")) + return [SeriesItem.from_dict(result) for result in results] + + +def sonarr_wanted(): + """Generate a response for the wanted method.""" + results = json.loads(load_fixture("sonarr/wanted-missing.json")) + return WantedResults.from_dict(results) + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Return the default mocked config entry.""" + return MockConfigEntry( + title="Sonarr", + domain=DOMAIN, + data={ + CONF_HOST: "192.168.1.189", + CONF_PORT: 8989, + CONF_BASE_PATH: "/api", + CONF_SSL: False, + CONF_VERIFY_SSL: False, + CONF_API_KEY: "MOCK_API_KEY", + CONF_UPCOMING_DAYS: DEFAULT_UPCOMING_DAYS, + CONF_WANTED_MAX_ITEMS: DEFAULT_WANTED_MAX_ITEMS, + }, + options={ + CONF_UPCOMING_DAYS: DEFAULT_UPCOMING_DAYS, + CONF_WANTED_MAX_ITEMS: DEFAULT_WANTED_MAX_ITEMS, + }, + unique_id=None, + ) + + +@pytest.fixture +def mock_setup_entry() -> Generator[None, None, None]: + """Mock setting up a config entry.""" + with patch("homeassistant.components.sonarr.async_setup_entry", return_value=True): + yield + + +@pytest.fixture +def mock_sonarr_config_flow( + request: pytest.FixtureRequest, +) -> Generator[None, MagicMock, None]: + """Return a mocked Sonarr client.""" + fixture: str = "sonarr/app.json" + if hasattr(request, "param") and request.param: + fixture = request.param + + app = Application(json.loads(load_fixture(fixture))) + with patch( + "homeassistant.components.sonarr.config_flow.Sonarr", autospec=True + ) as sonarr_mock: + client = sonarr_mock.return_value + client.host = "192.168.1.189" + client.port = 8989 + client.base_path = "/api" + client.tls = False + client.app = app + client.update.return_value = app + client.calendar.return_value = sonarr_calendar() + client.commands.return_value = sonarr_commands() + client.queue.return_value = sonarr_queue() + client.series.return_value = sonarr_series() + client.wanted.return_value = sonarr_wanted() + yield client + + +@pytest.fixture +def mock_sonarr(request: pytest.FixtureRequest) -> Generator[None, MagicMock, None]: + """Return a mocked Sonarr client.""" + fixture: str = "sonarr/app.json" + if hasattr(request, "param") and request.param: + fixture = request.param + + app = Application(json.loads(load_fixture(fixture))) + with patch("homeassistant.components.sonarr.Sonarr", autospec=True) as sonarr_mock: + client = sonarr_mock.return_value + client.host = "192.168.1.189" + client.port = 8989 + client.base_path = "/api" + client.tls = False + client.app = app + client.update.return_value = app + client.calendar.return_value = sonarr_calendar() + client.commands.return_value = sonarr_commands() + client.queue.return_value = sonarr_queue() + client.series.return_value = sonarr_series() + client.wanted.return_value = sonarr_wanted() + yield client + + +@pytest.fixture +async def init_integration( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_sonarr: MagicMock +) -> MockConfigEntry: + """Set up the Sonarr integration for testing.""" + mock_config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + return mock_config_entry diff --git a/tests/components/sonarr/fixtures/app.json b/tests/components/sonarr/fixtures/app.json new file mode 100644 index 00000000000000..e9ce88b233e4ea --- /dev/null +++ b/tests/components/sonarr/fixtures/app.json @@ -0,0 +1,28 @@ +{ + "info": { + "version": "2.0.0.1121", + "buildTime": "2014-02-08T20:49:36.5560392Z", + "isDebug": false, + "isProduction": true, + "isAdmin": true, + "isUserInteractive": false, + "startupPath": "C:\\ProgramData\\NzbDrone\\bin", + "appData": "C:\\ProgramData\\NzbDrone", + "osVersion": "6.2.9200.0", + "isMono": false, + "isLinux": false, + "isWindows": true, + "branch": "develop", + "authentication": false, + "startOfWeek": 0, + "urlBase": "" + }, + "diskspace": [ + { + "path": "C:\\", + "label": "", + "freeSpace": 282500067328, + "totalSpace": 499738734592 + } + ] +} diff --git a/tests/components/sonarr/fixtures/system-status.json b/tests/components/sonarr/fixtures/system-status.json deleted file mode 100644 index c3969df08fe5e0..00000000000000 --- a/tests/components/sonarr/fixtures/system-status.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "version": "2.0.0.1121", - "buildTime": "2014-02-08T20:49:36.5560392Z", - "isDebug": false, - "isProduction": true, - "isAdmin": true, - "isUserInteractive": false, - "startupPath": "C:\\ProgramData\\NzbDrone\\bin", - "appData": "C:\\ProgramData\\NzbDrone", - "osVersion": "6.2.9200.0", - "isMono": false, - "isLinux": false, - "isWindows": true, - "branch": "develop", - "authentication": false, - "startOfWeek": 0, - "urlBase": "" -} diff --git a/tests/components/sonarr/test_config_flow.py b/tests/components/sonarr/test_config_flow.py index 87b38e52742f51..52e7b9b61ca9f4 100644 --- a/tests/components/sonarr/test_config_flow.py +++ b/tests/components/sonarr/test_config_flow.py @@ -1,5 +1,7 @@ """Test the Sonarr config flow.""" -from unittest.mock import patch +from unittest.mock import MagicMock, patch + +from sonarr import SonarrAccessRestricted, SonarrError from homeassistant.components.sonarr.const import ( CONF_UPCOMING_DAYS, @@ -17,17 +19,8 @@ RESULT_TYPE_FORM, ) -from tests.components.sonarr import ( - HOST, - MOCK_REAUTH_INPUT, - MOCK_USER_INPUT, - _patch_async_setup_entry, - mock_connection, - mock_connection_error, - mock_connection_invalid_auth, - setup_integration, -) -from tests.test_util.aiohttp import AiohttpClientMocker +from tests.common import MockConfigEntry +from tests.components.sonarr import MOCK_REAUTH_INPUT, MOCK_USER_INPUT async def test_show_user_form(hass: HomeAssistant) -> None: @@ -42,10 +35,10 @@ async def test_show_user_form(hass: HomeAssistant) -> None: async def test_cannot_connect( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, mock_sonarr_config_flow: MagicMock ) -> None: """Test we show user form on connection error.""" - mock_connection_error(aioclient_mock) + mock_sonarr_config_flow.update.side_effect = SonarrError user_input = MOCK_USER_INPUT.copy() result = await hass.config_entries.flow.async_init( @@ -60,10 +53,10 @@ async def test_cannot_connect( async def test_invalid_auth( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, mock_sonarr_config_flow: MagicMock ) -> None: """Test we show user form on invalid auth.""" - mock_connection_invalid_auth(aioclient_mock) + mock_sonarr_config_flow.update.side_effect = SonarrAccessRestricted user_input = MOCK_USER_INPUT.copy() result = await hass.config_entries.flow.async_init( @@ -78,30 +71,30 @@ async def test_invalid_auth( async def test_unknown_error( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, mock_sonarr_config_flow: MagicMock ) -> None: """Test we show user form on unknown error.""" + mock_sonarr_config_flow.update.side_effect = Exception + user_input = MOCK_USER_INPUT.copy() - with patch( - "homeassistant.components.sonarr.config_flow.Sonarr.update", - side_effect=Exception, - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={CONF_SOURCE: SOURCE_USER}, - data=user_input, - ) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={CONF_SOURCE: SOURCE_USER}, + data=user_input, + ) assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "unknown" async def test_full_reauth_flow_implementation( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + mock_sonarr_config_flow: MagicMock, + mock_setup_entry: None, + init_integration: MockConfigEntry, ) -> None: """Test the manual reauth flow from start to finish.""" - entry = await setup_integration(hass, aioclient_mock, skip_entry_setup=True) - assert entry + entry = init_integration result = await hass.config_entries.flow.async_init( DOMAIN, @@ -124,26 +117,23 @@ async def test_full_reauth_flow_implementation( assert result["step_id"] == "user" user_input = MOCK_REAUTH_INPUT.copy() - with _patch_async_setup_entry() as mock_setup_entry: - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input=user_input - ) - await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=user_input + ) + await hass.async_block_till_done() assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "reauth_successful" assert entry.data[CONF_API_KEY] == "test-api-key-reauth" - mock_setup_entry.assert_called_once() - async def test_full_user_flow_implementation( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + mock_sonarr_config_flow: MagicMock, + mock_setup_entry: None, ) -> None: """Test the full manual user flow from start to finish.""" - mock_connection(aioclient_mock) - result = await hass.config_entries.flow.async_init( DOMAIN, context={CONF_SOURCE: SOURCE_USER}, @@ -154,25 +144,24 @@ async def test_full_user_flow_implementation( user_input = MOCK_USER_INPUT.copy() - with _patch_async_setup_entry(): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input=user_input, - ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input=user_input, + ) assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["title"] == HOST + assert result["title"] == "192.168.1.189" assert result["data"] - assert result["data"][CONF_HOST] == HOST + assert result["data"][CONF_HOST] == "192.168.1.189" async def test_full_user_flow_advanced_options( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + mock_sonarr_config_flow: MagicMock, + mock_setup_entry: None, ) -> None: """Test the full manual user flow with advanced options.""" - mock_connection(aioclient_mock) - result = await hass.config_entries.flow.async_init( DOMAIN, context={CONF_SOURCE: SOURCE_USER, "show_advanced_options": True} ) @@ -185,24 +174,27 @@ async def test_full_user_flow_advanced_options( CONF_VERIFY_SSL: True, } - with _patch_async_setup_entry(): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input=user_input, - ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input=user_input, + ) assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["title"] == HOST + assert result["title"] == "192.168.1.189" assert result["data"] - assert result["data"][CONF_HOST] == HOST + assert result["data"][CONF_HOST] == "192.168.1.189" assert result["data"][CONF_VERIFY_SSL] -async def test_options_flow(hass, aioclient_mock: AiohttpClientMocker): +@patch("homeassistant.components.sonarr.PLATFORMS", []) +async def test_options_flow( + hass: HomeAssistant, + mock_setup_entry: None, + init_integration: MockConfigEntry, +): """Test updating options.""" - with patch("homeassistant.components.sonarr.PLATFORMS", []): - entry = await setup_integration(hass, aioclient_mock) + entry = init_integration assert entry.options[CONF_UPCOMING_DAYS] == DEFAULT_UPCOMING_DAYS assert entry.options[CONF_WANTED_MAX_ITEMS] == DEFAULT_WANTED_MAX_ITEMS @@ -212,12 +204,11 @@ async def test_options_flow(hass, aioclient_mock: AiohttpClientMocker): assert result["type"] == RESULT_TYPE_FORM assert result["step_id"] == "init" - with _patch_async_setup_entry(): - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={CONF_UPCOMING_DAYS: 2, CONF_WANTED_MAX_ITEMS: 100}, - ) - await hass.async_block_till_done() + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={CONF_UPCOMING_DAYS: 2, CONF_WANTED_MAX_ITEMS: 100}, + ) + await hass.async_block_till_done() assert result["type"] == RESULT_TYPE_CREATE_ENTRY assert result["data"][CONF_UPCOMING_DAYS] == 2 diff --git a/tests/components/sonarr/test_init.py b/tests/components/sonarr/test_init.py index 39d9b7fc24ea24..3a59f5d7cca8c0 100644 --- a/tests/components/sonarr/test_init.py +++ b/tests/components/sonarr/test_init.py @@ -1,60 +1,79 @@ """Tests for the Sonsrr integration.""" -from unittest.mock import patch +from unittest.mock import MagicMock, patch + +from sonarr import SonarrAccessRestricted, SonarrError from homeassistant.components.sonarr.const import DOMAIN from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState from homeassistant.const import CONF_SOURCE from homeassistant.core import HomeAssistant -from tests.components.sonarr import setup_integration -from tests.test_util.aiohttp import AiohttpClientMocker +from tests.common import MockConfigEntry async def test_config_entry_not_ready( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_sonarr: MagicMock, ) -> None: """Test the configuration entry not ready.""" - entry = await setup_integration(hass, aioclient_mock, connection_error=True) - assert entry.state is ConfigEntryState.SETUP_RETRY + mock_sonarr.update.side_effect = SonarrError + + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY async def test_config_entry_reauth( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_sonarr: MagicMock, ) -> None: """Test the configuration entry needing to be re-authenticated.""" + mock_sonarr.update.side_effect = SonarrAccessRestricted + with patch.object(hass.config_entries.flow, "async_init") as mock_flow_init: - entry = await setup_integration(hass, aioclient_mock, invalid_auth=True) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() - assert entry.state is ConfigEntryState.SETUP_ERROR + assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR mock_flow_init.assert_called_once_with( DOMAIN, context={ CONF_SOURCE: SOURCE_REAUTH, - "entry_id": entry.entry_id, - "unique_id": entry.unique_id, - "title_placeholders": {"name": entry.title}, + "entry_id": mock_config_entry.entry_id, + "unique_id": mock_config_entry.unique_id, + "title_placeholders": {"name": mock_config_entry.title}, }, - data=entry.data, + data=mock_config_entry.data, ) async def test_unload_config_entry( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_sonarr: MagicMock, ) -> None: """Test the configuration entry unloading.""" + mock_config_entry.add_to_hass(hass) + with patch( "homeassistant.components.sonarr.sensor.async_setup_entry", return_value=True, ): - entry = await setup_integration(hass, aioclient_mock) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() assert hass.data[DOMAIN] - assert entry.entry_id in hass.data[DOMAIN] - assert entry.state is ConfigEntryState.LOADED + assert mock_config_entry.state is ConfigEntryState.LOADED + assert mock_config_entry.entry_id in hass.data[DOMAIN] - await hass.config_entries.async_unload(entry.entry_id) + await hass.config_entries.async_unload(mock_config_entry.entry_id) await hass.async_block_till_done() - assert entry.entry_id not in hass.data[DOMAIN] - assert entry.state is ConfigEntryState.NOT_LOADED + assert mock_config_entry.state is ConfigEntryState.NOT_LOADED + assert mock_config_entry.entry_id not in hass.data[DOMAIN] diff --git a/tests/components/sonarr/test_sensor.py b/tests/components/sonarr/test_sensor.py index f68920e4e4f15a..8acf7d5b2c8293 100644 --- a/tests/components/sonarr/test_sensor.py +++ b/tests/components/sonarr/test_sensor.py @@ -1,8 +1,9 @@ """Tests for the Sonarr sensor platform.""" from datetime import timedelta -from unittest.mock import patch +from unittest.mock import MagicMock, patch import pytest +from sonarr import SonarrError from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.sonarr.const import DOMAIN @@ -16,18 +17,18 @@ from homeassistant.helpers import entity_registry as er from homeassistant.util import dt as dt_util -from tests.common import async_fire_time_changed -from tests.components.sonarr import mock_connection, setup_integration -from tests.test_util.aiohttp import AiohttpClientMocker +from tests.common import MockConfigEntry, async_fire_time_changed UPCOMING_ENTITY_ID = f"{SENSOR_DOMAIN}.sonarr_upcoming" async def test_sensors( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_sonarr: MagicMock, ) -> None: """Test the creation and values of the sensors.""" - entry = await setup_integration(hass, aioclient_mock, skip_entry_setup=True) + entry = mock_config_entry registry = er.async_get(hass) # Pre-create registry entries for disabled by default sensors @@ -48,6 +49,7 @@ async def test_sensors( disabled_by=None, ) + mock_config_entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -104,10 +106,11 @@ async def test_sensors( ), ) async def test_disabled_by_default_sensors( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, entity_id: str + hass: HomeAssistant, + init_integration: MockConfigEntry, + entity_id: str, ) -> None: """Test the disabled by default sensors.""" - await setup_integration(hass, aioclient_mock) registry = er.async_get(hass) state = hass.states.get(entity_id) @@ -120,19 +123,22 @@ async def test_disabled_by_default_sensors( async def test_availability( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_sonarr: MagicMock, ) -> None: """Test entity availability.""" now = dt_util.utcnow() + mock_config_entry.add_to_hass(hass) with patch("homeassistant.util.dt.utcnow", return_value=now): - await setup_integration(hass, aioclient_mock) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() assert hass.states.get(UPCOMING_ENTITY_ID).state == "1" # state to unavailable - aioclient_mock.clear_requests() - mock_connection(aioclient_mock, error=True) + mock_sonarr.calendar.side_effect = SonarrError future = now + timedelta(minutes=1) with patch("homeassistant.util.dt.utcnow", return_value=future): @@ -142,8 +148,7 @@ async def test_availability( assert hass.states.get(UPCOMING_ENTITY_ID).state == STATE_UNAVAILABLE # state to available - aioclient_mock.clear_requests() - mock_connection(aioclient_mock) + mock_sonarr.calendar.side_effect = None future += timedelta(minutes=1) with patch("homeassistant.util.dt.utcnow", return_value=future): @@ -153,8 +158,7 @@ async def test_availability( assert hass.states.get(UPCOMING_ENTITY_ID).state == "1" # state to unavailable - aioclient_mock.clear_requests() - mock_connection(aioclient_mock, invalid_auth=True) + mock_sonarr.calendar.side_effect = SonarrError future += timedelta(minutes=1) with patch("homeassistant.util.dt.utcnow", return_value=future): @@ -164,8 +168,7 @@ async def test_availability( assert hass.states.get(UPCOMING_ENTITY_ID).state == STATE_UNAVAILABLE # state to available - aioclient_mock.clear_requests() - mock_connection(aioclient_mock) + mock_sonarr.calendar.side_effect = None future += timedelta(minutes=1) with patch("homeassistant.util.dt.utcnow", return_value=future): From bf138c4ffbf8751ec22e930d672160bd394c6555 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 31 Jan 2022 15:01:46 -0800 Subject: [PATCH 0147/1098] Alexa to handle brightness and catch exceptions (#65322) --- homeassistant/components/alexa/errors.py | 26 ++++++++++++++++++++ homeassistant/components/alexa/handlers.py | 14 +++-------- homeassistant/components/alexa/smart_home.py | 12 ++++++++- tests/components/alexa/__init__.py | 4 ++- tests/components/alexa/test_capabilities.py | 20 +++++---------- 5 files changed, 50 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/alexa/errors.py b/homeassistant/components/alexa/errors.py index f4c50a242675a6..0ce00f1fe48b43 100644 --- a/homeassistant/components/alexa/errors.py +++ b/homeassistant/components/alexa/errors.py @@ -1,6 +1,8 @@ """Alexa related errors.""" from __future__ import annotations +from typing import Literal + from homeassistant.exceptions import HomeAssistantError from .const import API_TEMP_UNITS @@ -58,6 +60,30 @@ class AlexaInvalidValueError(AlexaError): error_type = "INVALID_VALUE" +class AlexaInteralError(AlexaError): + """Class to represent internal errors.""" + + namespace = "Alexa" + error_type = "INTERNAL_ERROR" + + +class AlexaNotSupportedInCurrentMode(AlexaError): + """The device is not in the correct mode to support this command.""" + + namespace = "Alexa" + error_type = "NOT_SUPPORTED_IN_CURRENT_MODE" + + def __init__( + self, + endpoint_id: str, + current_mode: Literal["COLOR", "ASLEEP", "NOT_PROVISIONED", "OTHER"], + ) -> None: + """Initialize invalid endpoint error.""" + msg = f"Not supported while in {current_mode} mode" + AlexaError.__init__(self, msg, {"currentDeviceMode": current_mode}) + self.endpoint_id = endpoint_id + + class AlexaUnsupportedThermostatModeError(AlexaError): """Class to represent UnsupportedThermostatMode errors.""" diff --git a/homeassistant/components/alexa/handlers.py b/homeassistant/components/alexa/handlers.py index c0b0782f62ec6d..f3f669de3b39cc 100644 --- a/homeassistant/components/alexa/handlers.py +++ b/homeassistant/components/alexa/handlers.py @@ -212,20 +212,14 @@ async def async_api_adjust_brightness(hass, config, directive, context): entity = directive.entity brightness_delta = int(directive.payload["brightnessDelta"]) - # read current state - try: - current = math.floor( - int(entity.attributes.get(light.ATTR_BRIGHTNESS)) / 255 * 100 - ) - except ZeroDivisionError: - current = 0 - # set brightness - brightness = max(0, brightness_delta + current) await hass.services.async_call( entity.domain, SERVICE_TURN_ON, - {ATTR_ENTITY_ID: entity.entity_id, light.ATTR_BRIGHTNESS_PCT: brightness}, + { + ATTR_ENTITY_ID: entity.entity_id, + light.ATTR_BRIGHTNESS_STEP_PCT: brightness_delta, + }, blocking=False, context=context, ) diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index 7d144619bc94f2..242295078774f1 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -48,8 +48,18 @@ async def async_handle_message(hass, config, request, context=None, enabled=True response = directive.error() except AlexaError as err: response = directive.error( - error_type=err.error_type, error_message=err.error_message + error_type=err.error_type, + error_message=err.error_message, + payload=err.payload, ) + except Exception: # pylint: disable=broad-except + _LOGGER.exception( + "Uncaught exception processing Alexa %s/%s request (%s)", + directive.namespace, + directive.name, + directive.entity_id or "-", + ) + response = directive.error(error_message="Unknown error") request_info = {"namespace": directive.namespace, "name": directive.name} diff --git a/tests/components/alexa/__init__.py b/tests/components/alexa/__init__.py index 1d8289b5ec09a1..053100d2e0033a 100644 --- a/tests/components/alexa/__init__.py +++ b/tests/components/alexa/__init__.py @@ -194,7 +194,7 @@ async def assert_scene_controller_works( assert re.search(pattern, response["event"]["payload"]["timestamp"]) -async def reported_properties(hass, endpoint): +async def reported_properties(hass, endpoint, return_full_response=False): """Use ReportState to get properties and return them. The result is a ReportedProperties instance, which has methods to make @@ -203,6 +203,8 @@ async def reported_properties(hass, endpoint): request = get_new_request("Alexa", "ReportState", endpoint) msg = await smart_home.async_handle_message(hass, get_default_config(), request) await hass.async_block_till_done() + if return_full_response: + return msg return ReportedProperties(msg["context"]["properties"]) diff --git a/tests/components/alexa/test_capabilities.py b/tests/components/alexa/test_capabilities.py index 566917d7c39b0a..8a9a40e3217180 100644 --- a/tests/components/alexa/test_capabilities.py +++ b/tests/components/alexa/test_capabilities.py @@ -4,7 +4,6 @@ import pytest from homeassistant.components.alexa import smart_home -from homeassistant.components.alexa.errors import UnsupportedProperty from homeassistant.components.climate import const as climate from homeassistant.components.lock import STATE_JAMMED, STATE_LOCKING, STATE_UNLOCKING from homeassistant.components.media_player.const import ( @@ -39,8 +38,8 @@ from tests.common import async_mock_service -@pytest.mark.parametrize("result,adjust", [(25, "-5"), (35, "5"), (0, "-80")]) -async def test_api_adjust_brightness(hass, result, adjust): +@pytest.mark.parametrize("adjust", ["-5", "5", "-80"]) +async def test_api_adjust_brightness(hass, adjust): """Test api adjust brightness process.""" request = get_new_request( "Alexa.BrightnessController", "AdjustBrightness", "light#test" @@ -64,7 +63,7 @@ async def test_api_adjust_brightness(hass, result, adjust): assert len(call_light) == 1 assert call_light[0].data["entity_id"] == "light.test" - assert call_light[0].data["brightness_pct"] == result + assert call_light[0].data["brightness_step_pct"] == int(adjust) assert msg["header"]["name"] == "Response" @@ -677,16 +676,9 @@ async def test_report_climate_state(hass): ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, }, ) - with pytest.raises(UnsupportedProperty): - properties = await reported_properties(hass, "climate.unsupported") - properties.assert_not_has_property( - "Alexa.ThermostatController", "thermostatMode" - ) - properties.assert_equal( - "Alexa.TemperatureSensor", - "temperature", - {"value": 34.0, "scale": "CELSIUS"}, - ) + msg = await reported_properties(hass, "climate.unsupported", True) + assert msg["event"]["header"]["name"] == "ErrorResponse" + assert msg["event"]["payload"]["type"] == "INTERNAL_ERROR" async def test_temperature_sensor_sensor(hass): From c7eb6764490ae0f6c167864bb3a0bf5ed2d4dabe Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 31 Jan 2022 17:24:55 -0600 Subject: [PATCH 0148/1098] Fix guardian being rediscovered via dhcp (#65332) --- .../components/guardian/config_flow.py | 6 +++ tests/components/guardian/test_config_flow.py | 44 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/homeassistant/components/guardian/config_flow.py b/homeassistant/components/guardian/config_flow.py index ea4589ddd423d7..c027fe8bc20903 100644 --- a/homeassistant/components/guardian/config_flow.py +++ b/homeassistant/components/guardian/config_flow.py @@ -68,6 +68,9 @@ async def _async_set_unique_id(self, pin: str) -> None: self._abort_if_unique_id_configured( updates={CONF_IP_ADDRESS: self.discovery_info[CONF_IP_ADDRESS]} ) + self._async_abort_entries_match( + {CONF_IP_ADDRESS: self.discovery_info[CONF_IP_ADDRESS]} + ) else: self._abort_if_unique_id_configured() @@ -103,6 +106,9 @@ async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowRes CONF_IP_ADDRESS: discovery_info.ip, CONF_PORT: DEFAULT_PORT, } + await self._async_set_unique_id( + async_get_pin_from_uid(discovery_info.macaddress.replace(":", "").upper()) + ) return await self._async_handle_discovery() async def async_step_zeroconf( diff --git a/tests/components/guardian/test_config_flow.py b/tests/components/guardian/test_config_flow.py index b8d8a10752dfaf..fc3157289e9706 100644 --- a/tests/components/guardian/test_config_flow.py +++ b/tests/components/guardian/test_config_flow.py @@ -13,6 +13,8 @@ from homeassistant.config_entries import SOURCE_DHCP, SOURCE_USER, SOURCE_ZEROCONF from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT +from tests.common import MockConfigEntry + async def test_duplicate_error(hass, config, config_entry, setup_guardian): """Test that errors are shown when duplicate entries are added.""" @@ -166,3 +168,45 @@ async def test_step_dhcp_already_in_progress(hass): ) assert result["type"] == "abort" assert result["reason"] == "already_in_progress" + + +async def test_step_dhcp_already_setup_match_mac(hass): + """Test we abort if the device is already setup with matching unique id and discovered via DHCP.""" + entry = MockConfigEntry( + domain=DOMAIN, data={CONF_IP_ADDRESS: "1.2.3.4"}, unique_id="guardian_ABCD" + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_DHCP}, + data=dhcp.DhcpServiceInfo( + ip="192.168.1.100", + hostname="GVC1-ABCD.local.", + macaddress="aa:bb:cc:dd:ab:cd", + ), + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + + +async def test_step_dhcp_already_setup_match_ip(hass): + """Test we abort if the device is already setup with matching ip and discovered via DHCP.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_IP_ADDRESS: "192.168.1.100"}, + unique_id="guardian_0000", + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_DHCP}, + data=dhcp.DhcpServiceInfo( + ip="192.168.1.100", + hostname="GVC1-ABCD.local.", + macaddress="aa:bb:cc:dd:ab:cd", + ), + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" From 18ea3fb85a28765eace292ca1233bb885610475a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 31 Jan 2022 17:27:26 -0600 Subject: [PATCH 0149/1098] Prevent unifiprotect from being rediscovered on UDM-PROs (#65335) --- .../components/unifiprotect/config_flow.py | 53 ++--- .../components/unifiprotect/utils.py | 20 +- tests/components/unifiprotect/__init__.py | 3 +- .../unifiprotect/test_config_flow.py | 209 +++++++++++++++++- 4 files changed, 256 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/unifiprotect/config_flow.py b/homeassistant/components/unifiprotect/config_flow.py index 5b5b20e7175df8..720a46b4659699 100644 --- a/homeassistant/components/unifiprotect/config_flow.py +++ b/homeassistant/components/unifiprotect/config_flow.py @@ -36,7 +36,7 @@ OUTDATED_LOG_MESSAGE, ) from .discovery import async_start_discovery -from .utils import _async_short_mac, _async_unifi_mac_from_hass +from .utils import _async_resolve, _async_short_mac, _async_unifi_mac_from_hass _LOGGER = logging.getLogger(__name__) @@ -88,32 +88,35 @@ async def async_step_discovery( self._discovered_device = discovery_info mac = _async_unifi_mac_from_hass(discovery_info["hw_addr"]) await self.async_set_unique_id(mac) + source_ip = discovery_info["source_ip"] + direct_connect_domain = discovery_info["direct_connect_domain"] for entry in self._async_current_entries(include_ignore=False): - if entry.unique_id != mac: - continue - new_host = None - if ( - _host_is_direct_connect(entry.data[CONF_HOST]) - and discovery_info["direct_connect_domain"] - and entry.data[CONF_HOST] != discovery_info["direct_connect_domain"] + entry_host = entry.data[CONF_HOST] + entry_has_direct_connect = _host_is_direct_connect(entry_host) + if entry.unique_id == mac: + new_host = None + if ( + entry_has_direct_connect + and direct_connect_domain + and entry_host != direct_connect_domain + ): + new_host = direct_connect_domain + elif not entry_has_direct_connect and entry_host != source_ip: + new_host = source_ip + if new_host: + self.hass.config_entries.async_update_entry( + entry, data={**entry.data, CONF_HOST: new_host} + ) + self.hass.async_create_task( + self.hass.config_entries.async_reload(entry.entry_id) + ) + return self.async_abort(reason="already_configured") + if entry_host in (direct_connect_domain, source_ip) or ( + entry_has_direct_connect + and (ip := await _async_resolve(self.hass, entry_host)) + and ip == source_ip ): - new_host = discovery_info["direct_connect_domain"] - elif ( - not _host_is_direct_connect(entry.data[CONF_HOST]) - and entry.data[CONF_HOST] != discovery_info["source_ip"] - ): - new_host = discovery_info["source_ip"] - if new_host: - self.hass.config_entries.async_update_entry( - entry, data={**entry.data, CONF_HOST: new_host} - ) - self.hass.async_create_task( - self.hass.config_entries.async_reload(entry.entry_id) - ) - return self.async_abort(reason="already_configured") - self._abort_if_unique_id_configured( - updates={CONF_HOST: discovery_info["source_ip"]} - ) + return self.async_abort(reason="already_configured") return await self.async_step_discovery_confirm() async def async_step_discovery_confirm( diff --git a/homeassistant/components/unifiprotect/utils.py b/homeassistant/components/unifiprotect/utils.py index 45645d6f06b7ff..559cfd37660572 100644 --- a/homeassistant/components/unifiprotect/utils.py +++ b/homeassistant/components/unifiprotect/utils.py @@ -1,10 +1,12 @@ """UniFi Protect Integration utils.""" from __future__ import annotations +import contextlib from enum import Enum +import socket from typing import Any -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback def get_nested_attr(obj: Any, attr: str) -> Any: @@ -33,3 +35,19 @@ def _async_unifi_mac_from_hass(mac: str) -> str: def _async_short_mac(mac: str) -> str: """Get the short mac address from the full mac.""" return _async_unifi_mac_from_hass(mac)[-6:] + + +async def _async_resolve(hass: HomeAssistant, host: str) -> str | None: + """Resolve a hostname to an ip.""" + with contextlib.suppress(OSError): + return next( + iter( + raw[0] + for family, _, _, _, raw in await hass.loop.getaddrinfo( + host, None, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP + ) + if family == socket.AF_INET + ), + None, + ) + return None diff --git a/tests/components/unifiprotect/__init__.py b/tests/components/unifiprotect/__init__.py index 5fd1b7cc909191..1bbe9fb435dc84 100644 --- a/tests/components/unifiprotect/__init__.py +++ b/tests/components/unifiprotect/__init__.py @@ -8,6 +8,7 @@ DEVICE_HOSTNAME = "unvr" DEVICE_IP_ADDRESS = "127.0.0.1" DEVICE_MAC_ADDRESS = "aa:bb:cc:dd:ee:ff" +DIRECT_CONNECT_DOMAIN = "x.ui.direct" UNIFI_DISCOVERY = UnifiDevice( @@ -16,7 +17,7 @@ platform=DEVICE_HOSTNAME, hostname=DEVICE_HOSTNAME, services={UnifiService.Protect: True}, - direct_connect_domain="x.ui.direct", + direct_connect_domain=DIRECT_CONNECT_DOMAIN, ) diff --git a/tests/components/unifiprotect/test_config_flow.py b/tests/components/unifiprotect/test_config_flow.py index 557eb3d5e791ff..fc5b2b32873199 100644 --- a/tests/components/unifiprotect/test_config_flow.py +++ b/tests/components/unifiprotect/test_config_flow.py @@ -2,6 +2,7 @@ from __future__ import annotations from dataclasses import asdict +import socket from unittest.mock import patch import pytest @@ -29,6 +30,7 @@ DEVICE_HOSTNAME, DEVICE_IP_ADDRESS, DEVICE_MAC_ADDRESS, + DIRECT_CONNECT_DOMAIN, UNIFI_DISCOVERY, UNIFI_DISCOVERY_PARTIAL, _patch_discovery, @@ -334,7 +336,7 @@ async def test_discovered_by_unifi_discovery_direct_connect( assert result2["type"] == RESULT_TYPE_CREATE_ENTRY assert result2["title"] == "UnifiProtect" assert result2["data"] == { - "host": "x.ui.direct", + "host": DIRECT_CONNECT_DOMAIN, "username": "test-username", "password": "test-password", "id": "UnifiProtect", @@ -377,7 +379,7 @@ async def test_discovered_by_unifi_discovery_direct_connect_updated( assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "already_configured" assert len(mock_setup_entry.mock_calls) == 1 - assert mock_config.data[CONF_HOST] == "x.ui.direct" + assert mock_config.data[CONF_HOST] == DIRECT_CONNECT_DOMAIN async def test_discovered_by_unifi_discovery_direct_connect_updated_but_not_using_direct_connect( @@ -518,3 +520,206 @@ async def test_discovered_by_unifi_discovery_partial( "verify_ssl": False, } assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_discovered_by_unifi_discovery_direct_connect_on_different_interface( + hass: HomeAssistant, mock_nvr: NVR +) -> None: + """Test a discovery from unifi-discovery from an alternate interface.""" + mock_config = MockConfigEntry( + domain=DOMAIN, + data={ + "host": DIRECT_CONNECT_DOMAIN, + "username": "test-username", + "password": "test-password", + "id": "UnifiProtect", + "port": 443, + "verify_ssl": True, + }, + unique_id="FFFFFFAAAAAA", + ) + mock_config.add_to_hass(hass) + + with _patch_discovery(): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DISCOVERY}, + data=UNIFI_DISCOVERY_DICT, + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + + +async def test_discovered_by_unifi_discovery_direct_connect_on_different_interface_ip_matches( + hass: HomeAssistant, mock_nvr: NVR +) -> None: + """Test a discovery from unifi-discovery from an alternate interface when the ip matches.""" + mock_config = MockConfigEntry( + domain=DOMAIN, + data={ + "host": "127.0.0.1", + "username": "test-username", + "password": "test-password", + "id": "UnifiProtect", + "port": 443, + "verify_ssl": True, + }, + unique_id="FFFFFFAAAAAA", + ) + mock_config.add_to_hass(hass) + + with _patch_discovery(): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DISCOVERY}, + data=UNIFI_DISCOVERY_DICT, + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + + +async def test_discovered_by_unifi_discovery_direct_connect_on_different_interface_resolver( + hass: HomeAssistant, mock_nvr: NVR +) -> None: + """Test a discovery from unifi-discovery from an alternate interface when direct connect domain resolves to host ip.""" + mock_config = MockConfigEntry( + domain=DOMAIN, + data={ + "host": "y.ui.direct", + "username": "test-username", + "password": "test-password", + "id": "UnifiProtect", + "port": 443, + "verify_ssl": True, + }, + unique_id="FFFFFFAAAAAA", + ) + mock_config.add_to_hass(hass) + + other_ip_dict = UNIFI_DISCOVERY_DICT.copy() + other_ip_dict["source_ip"] = "127.0.0.1" + other_ip_dict["direct_connect_domain"] = "nomatchsameip.ui.direct" + + with _patch_discovery(), patch.object( + hass.loop, + "getaddrinfo", + return_value=[(socket.AF_INET, None, None, None, ("127.0.0.1", 443))], + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DISCOVERY}, + data=other_ip_dict, + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + + +async def test_discovered_by_unifi_discovery_direct_connect_on_different_interface_resolver_fails( + hass: HomeAssistant, mock_nvr: NVR +) -> None: + """Test we can still configure if the resolver fails.""" + mock_config = MockConfigEntry( + domain=DOMAIN, + data={ + "host": "y.ui.direct", + "username": "test-username", + "password": "test-password", + "id": "UnifiProtect", + "port": 443, + "verify_ssl": True, + }, + unique_id="FFFFFFAAAAAA", + ) + mock_config.add_to_hass(hass) + + other_ip_dict = UNIFI_DISCOVERY_DICT.copy() + other_ip_dict["source_ip"] = "127.0.0.2" + other_ip_dict["direct_connect_domain"] = "nomatchsameip.ui.direct" + + with _patch_discovery(), patch.object( + hass.loop, "getaddrinfo", side_effect=OSError + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DISCOVERY}, + data=other_ip_dict, + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "discovery_confirm" + flows = hass.config_entries.flow.async_progress_by_handler(DOMAIN) + assert flows[0]["context"]["title_placeholders"] == { + "ip_address": "127.0.0.2", + "name": "unvr", + } + + assert not result["errors"] + + with patch( + "homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_nvr", + return_value=mock_nvr, + ), patch( + "homeassistant.components.unifiprotect.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "username": "test-username", + "password": "test-password", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == "UnifiProtect" + assert result2["data"] == { + "host": "nomatchsameip.ui.direct", + "username": "test-username", + "password": "test-password", + "id": "UnifiProtect", + "port": 443, + "verify_ssl": True, + } + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_discovered_by_unifi_discovery_direct_connect_on_different_interface_resolver_no_result( + hass: HomeAssistant, mock_nvr: NVR +) -> None: + """Test a discovery from unifi-discovery from an alternate interface when direct connect domain resolve has no result.""" + mock_config = MockConfigEntry( + domain=DOMAIN, + data={ + "host": "y.ui.direct", + "username": "test-username", + "password": "test-password", + "id": "UnifiProtect", + "port": 443, + "verify_ssl": True, + }, + unique_id="FFFFFFAAAAAA", + ) + mock_config.add_to_hass(hass) + + other_ip_dict = UNIFI_DISCOVERY_DICT.copy() + other_ip_dict["source_ip"] = "127.0.0.2" + other_ip_dict["direct_connect_domain"] = "y.ui.direct" + + with _patch_discovery(), patch.object(hass.loop, "getaddrinfo", return_value=[]): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DISCOVERY}, + data=other_ip_dict, + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" From c9f38355f7da4aa171025c8bfb52ab9cbddc2df5 Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Tue, 1 Feb 2022 00:28:11 +0100 Subject: [PATCH 0150/1098] Improve debugging and error handling in Fritz!Tools (#65324) --- homeassistant/components/fritz/__init__.py | 10 +++----- homeassistant/components/fritz/common.py | 29 ++++++++++++++-------- homeassistant/components/fritz/const.py | 16 ++++++++++++ tests/components/fritz/conftest.py | 5 ++-- 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/fritz/__init__.py b/homeassistant/components/fritz/__init__.py index 0db85b12077391..a0e0413366bc93 100644 --- a/homeassistant/components/fritz/__init__.py +++ b/homeassistant/components/fritz/__init__.py @@ -1,11 +1,7 @@ """Support for AVM Fritz!Box functions.""" import logging -from fritzconnection.core.exceptions import ( - FritzConnectionException, - FritzResourceError, - FritzSecurityError, -) +from fritzconnection.core.exceptions import FritzSecurityError from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME @@ -13,7 +9,7 @@ from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from .common import AvmWrapper, FritzData -from .const import DATA_FRITZ, DOMAIN, PLATFORMS +from .const import DATA_FRITZ, DOMAIN, FRITZ_EXCEPTIONS, PLATFORMS from .services import async_setup_services, async_unload_services _LOGGER = logging.getLogger(__name__) @@ -34,7 +30,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await avm_wrapper.async_setup(entry.options) except FritzSecurityError as ex: raise ConfigEntryAuthFailed from ex - except (FritzConnectionException, FritzResourceError) as ex: + except FRITZ_EXCEPTIONS as ex: raise ConfigEntryNotReady from ex hass.data.setdefault(DOMAIN, {}) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index 70485ac0c5f4a7..c9eff204bf278e 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -12,10 +12,7 @@ from fritzconnection import FritzConnection from fritzconnection.core.exceptions import ( FritzActionError, - FritzActionFailedError, FritzConnectionException, - FritzInternalError, - FritzLookUpError, FritzSecurityError, FritzServiceError, ) @@ -46,6 +43,7 @@ DEFAULT_PORT, DEFAULT_USERNAME, DOMAIN, + FRITZ_EXCEPTIONS, SERVICE_CLEANUP, SERVICE_REBOOT, SERVICE_RECONNECT, @@ -188,9 +186,26 @@ def setup(self) -> None: _LOGGER.error("Unable to establish a connection with %s", self.host) return + _LOGGER.debug( + "detected services on %s %s", + self.host, + list(self.connection.services.keys()), + ) + self.fritz_hosts = FritzHosts(fc=self.connection) self.fritz_status = FritzStatus(fc=self.connection) info = self.connection.call_action("DeviceInfo:1", "GetInfo") + + _LOGGER.debug( + "gathered device info of %s %s", + self.host, + { + **info, + "NewDeviceLog": "***omitted***", + "NewSerialNumber": "***omitted***", + }, + ) + if not self._unique_id: self._unique_id = info["NewSerialNumber"] @@ -529,13 +544,7 @@ def _service_call_action( "Authorization Error: Please check the provided credentials and verify that you can log into the web interface", exc_info=True, ) - except ( - FritzActionError, - FritzActionFailedError, - FritzInternalError, - FritzServiceError, - FritzLookUpError, - ): + except FRITZ_EXCEPTIONS: _LOGGER.error( "Service/Action Error: cannot execute service %s with action %s", service_name, diff --git a/homeassistant/components/fritz/const.py b/homeassistant/components/fritz/const.py index ae8ffe83e381e4..59200e07c782b5 100644 --- a/homeassistant/components/fritz/const.py +++ b/homeassistant/components/fritz/const.py @@ -2,6 +2,14 @@ from typing import Literal +from fritzconnection.core.exceptions import ( + FritzActionError, + FritzActionFailedError, + FritzInternalError, + FritzLookUpError, + FritzServiceError, +) + from homeassistant.backports.enum import StrEnum from homeassistant.const import Platform @@ -47,3 +55,11 @@ class MeshRoles(StrEnum): SWITCH_TYPE_WIFINETWORK = "WiFiNetwork" UPTIME_DEVIATION = 5 + +FRITZ_EXCEPTIONS = ( + FritzActionError, + FritzActionFailedError, + FritzInternalError, + FritzServiceError, + FritzLookUpError, +) diff --git a/tests/components/fritz/conftest.py b/tests/components/fritz/conftest.py index 6f99ab483e64f6..1dc60f4a59ee0f 100644 --- a/tests/components/fritz/conftest.py +++ b/tests/components/fritz/conftest.py @@ -94,16 +94,15 @@ class FritzConnectionMock: # pylint: disable=too-few-public-methods def __init__(self): """Inint Mocking class.""" - type(self).modelname = mock.PropertyMock(return_value=self.MODELNAME) + self.modelname = self.MODELNAME self.call_action = mock.Mock(side_effect=self._side_effect_call_action) type(self).action_names = mock.PropertyMock( side_effect=self._side_effect_action_names ) - services = { + self.services = { srv: None for srv, _ in list(self.FRITZBOX_DATA) + list(self.FRITZBOX_DATA_INDEXED) } - type(self).services = mock.PropertyMock(side_effect=[services]) def _side_effect_call_action(self, service, action, **kwargs): if kwargs: From 31709b92ac980a491359c931a12bf4b414b49d76 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 31 Jan 2022 15:52:31 -0800 Subject: [PATCH 0151/1098] Bump version tag on async_timeout warning (#65339) --- homeassistant/async_timeout_backcompat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/async_timeout_backcompat.py b/homeassistant/async_timeout_backcompat.py index 70b38c1870803d..212beddfae3262 100644 --- a/homeassistant/async_timeout_backcompat.py +++ b/homeassistant/async_timeout_backcompat.py @@ -17,7 +17,7 @@ def timeout( loop = asyncio.get_running_loop() else: report( - "called async_timeout.timeout with loop keyword argument. The loop keyword argument is deprecated and calls will fail after Home Assistant 2022.2", + "called async_timeout.timeout with loop keyword argument. The loop keyword argument is deprecated and calls will fail after Home Assistant 2022.3", error_if_core=False, ) if delay is not None: @@ -30,7 +30,7 @@ def timeout( def current_task(loop: asyncio.AbstractEventLoop) -> asyncio.Task[Any] | None: """Backwards compatible current_task.""" report( - "called async_timeout.current_task. The current_task call is deprecated and calls will fail after Home Assistant 2022.2; use asyncio.current_task instead", + "called async_timeout.current_task. The current_task call is deprecated and calls will fail after Home Assistant 2022.3; use asyncio.current_task instead", error_if_core=False, ) return asyncio.current_task() From 47a54115251e6db026a813612abc6481ab32b179 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 31 Jan 2022 15:58:52 -0800 Subject: [PATCH 0152/1098] Bump aiohue to 4.0.1 (#65340) --- homeassistant/components/hue/bridge.py | 7 ++++--- homeassistant/components/hue/manifest.json | 2 +- homeassistant/components/hue/migration.py | 3 +-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/hue/bridge.py b/homeassistant/components/hue/bridge.py index 3a529c15cf39c4..346cc67d23518a 100644 --- a/homeassistant/components/hue/bridge.py +++ b/homeassistant/components/hue/bridge.py @@ -49,11 +49,12 @@ def __init__(self, hass: core.HomeAssistant, config_entry: ConfigEntry) -> None: self.logger = logging.getLogger(__name__) # store actual api connection to bridge as api app_key: str = self.config_entry.data[CONF_API_KEY] - websession = aiohttp_client.async_get_clientsession(hass) if self.api_version == 1: - self.api = HueBridgeV1(self.host, app_key, websession) + self.api = HueBridgeV1( + self.host, app_key, aiohttp_client.async_get_clientsession(hass) + ) else: - self.api = HueBridgeV2(self.host, app_key, websession) + self.api = HueBridgeV2(self.host, app_key) # store (this) bridge object in hass data hass.data.setdefault(DOMAIN, {})[self.config_entry.entry_id] = self diff --git a/homeassistant/components/hue/manifest.json b/homeassistant/components/hue/manifest.json index b9ffea7d3dfc28..231c00e3d24f24 100644 --- a/homeassistant/components/hue/manifest.json +++ b/homeassistant/components/hue/manifest.json @@ -3,7 +3,7 @@ "name": "Philips Hue", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/hue", - "requirements": ["aiohue==3.0.11"], + "requirements": ["aiohue==4.0.1"], "ssdp": [ { "manufacturer": "Royal Philips Electronics", diff --git a/homeassistant/components/hue/migration.py b/homeassistant/components/hue/migration.py index 3dbfef42d16457..f779fccdb3b6ac 100644 --- a/homeassistant/components/hue/migration.py +++ b/homeassistant/components/hue/migration.py @@ -76,7 +76,6 @@ async def handle_v2_migration(hass: core.HomeAssistant, entry: ConfigEntry) -> N """Perform migration of devices and entities to V2 Id's.""" host = entry.data[CONF_HOST] api_key = entry.data[CONF_API_KEY] - websession = aiohttp_client.async_get_clientsession(hass) dev_reg = async_get_device_registry(hass) ent_reg = async_get_entity_registry(hass) LOGGER.info("Start of migration of devices and entities to support API schema 2") @@ -93,7 +92,7 @@ async def handle_v2_migration(hass: core.HomeAssistant, entry: ConfigEntry) -> N dev_ids[normalized_mac] = hass_dev.id # initialize bridge connection just for the migration - async with HueBridgeV2(host, api_key, websession) as api: + async with HueBridgeV2(host, api_key) as api: sensor_class_mapping = { SensorDeviceClass.BATTERY.value: ResourceTypes.DEVICE_POWER, diff --git a/requirements_all.txt b/requirements_all.txt index f1b59e57f12fc5..46de83efe472d7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -191,7 +191,7 @@ aiohomekit==0.7.0 aiohttp_cors==0.7.0 # homeassistant.components.hue -aiohue==3.0.11 +aiohue==4.0.1 # homeassistant.components.homewizard aiohwenergy==0.8.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 94566810bcca01..e59308f3529fde 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -141,7 +141,7 @@ aiohomekit==0.7.0 aiohttp_cors==0.7.0 # homeassistant.components.hue -aiohue==3.0.11 +aiohue==4.0.1 # homeassistant.components.homewizard aiohwenergy==0.8.0 From 3bb8de66d84938deed36271bd7604ef0d55b716d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 31 Jan 2022 18:08:42 -0600 Subject: [PATCH 0153/1098] Bump zeroconf to 0.38.3 (#65341) --- homeassistant/components/zeroconf/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json index d1b43da9e27d93..fa3b8688c47e0b 100644 --- a/homeassistant/components/zeroconf/manifest.json +++ b/homeassistant/components/zeroconf/manifest.json @@ -2,7 +2,7 @@ "domain": "zeroconf", "name": "Zero-configuration networking (zeroconf)", "documentation": "https://www.home-assistant.io/integrations/zeroconf", - "requirements": ["zeroconf==0.38.1"], + "requirements": ["zeroconf==0.38.3"], "dependencies": ["network", "api"], "codeowners": ["@bdraco"], "quality_scale": "internal", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 44486677384ca8..15c1afc1b99cc8 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -33,7 +33,7 @@ typing-extensions>=3.10.0.2,<5.0 voluptuous-serialize==2.5.0 voluptuous==0.12.2 yarl==1.7.2 -zeroconf==0.38.1 +zeroconf==0.38.3 # Constrain pycryptodome to avoid vulnerability # see https://github.com/home-assistant/core/pull/16238 diff --git a/requirements_all.txt b/requirements_all.txt index 46de83efe472d7..2a88989c9006c0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2531,7 +2531,7 @@ youtube_dl==2021.12.17 zengge==0.2 # homeassistant.components.zeroconf -zeroconf==0.38.1 +zeroconf==0.38.3 # homeassistant.components.zha zha-quirks==0.0.66 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e59308f3529fde..b2832777f8a6e5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1553,7 +1553,7 @@ yeelight==0.7.8 youless-api==0.16 # homeassistant.components.zeroconf -zeroconf==0.38.1 +zeroconf==0.38.3 # homeassistant.components.zha zha-quirks==0.0.66 From 103fe9e0ba4d5b602f6173a88055e347303715be Mon Sep 17 00:00:00 2001 From: Duco Sebel <74970928+DCSBL@users.noreply.github.com> Date: Tue, 1 Feb 2022 01:08:58 +0100 Subject: [PATCH 0154/1098] Add HomeWizard diagnostics (#65297) Co-authored-by: Paulus Schoutsen --- .coveragerc | 1 + .../components/homewizard/diagnostics.py | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 homeassistant/components/homewizard/diagnostics.py diff --git a/.coveragerc b/.coveragerc index 049e6a37904b4f..01906614e15e69 100644 --- a/.coveragerc +++ b/.coveragerc @@ -464,6 +464,7 @@ omit = homeassistant/components/homematic/* homeassistant/components/home_plus_control/api.py homeassistant/components/home_plus_control/switch.py + homeassistant/components/homewizard/diagnostics.py homeassistant/components/homeworks/* homeassistant/components/honeywell/__init__.py homeassistant/components/honeywell/climate.py diff --git a/homeassistant/components/homewizard/diagnostics.py b/homeassistant/components/homewizard/diagnostics.py new file mode 100644 index 00000000000000..3dd559332915f3 --- /dev/null +++ b/homeassistant/components/homewizard/diagnostics.py @@ -0,0 +1,34 @@ +"""Diagnostics support for P1 Monitor.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_IP_ADDRESS +from homeassistant.core import HomeAssistant + +from .const import DOMAIN +from .coordinator import HWEnergyDeviceUpdateCoordinator + +TO_REDACT = {CONF_IP_ADDRESS, "serial", "wifi_ssid"} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + coordinator: HWEnergyDeviceUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + meter_data = { + "device": coordinator.api.device.todict(), + "data": coordinator.api.data.todict(), + "state": coordinator.api.state.todict() + if coordinator.api.state is not None + else None, + } + + return { + "entry": async_redact_data(entry.data, TO_REDACT), + "data": async_redact_data(meter_data, TO_REDACT), + } From 86079375b9053768452cc169732b2edd9d415403 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Tue, 1 Feb 2022 01:10:55 +0100 Subject: [PATCH 0155/1098] Add diagnostics for SamsungTV (#65342) --- .coveragerc | 1 + .../components/samsungtv/diagnostics.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 homeassistant/components/samsungtv/diagnostics.py diff --git a/.coveragerc b/.coveragerc index 01906614e15e69..9c1c8733ab4d6d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -948,6 +948,7 @@ omit = homeassistant/components/sabnzbd/* homeassistant/components/saj/sensor.py homeassistant/components/samsungtv/bridge.py + homeassistant/components/samsungtv/diagnostics.py homeassistant/components/satel_integra/* homeassistant/components/schluter/* homeassistant/components/scrape/sensor.py diff --git a/homeassistant/components/samsungtv/diagnostics.py b/homeassistant/components/samsungtv/diagnostics.py new file mode 100644 index 00000000000000..18d2325f38c914 --- /dev/null +++ b/homeassistant/components/samsungtv/diagnostics.py @@ -0,0 +1,18 @@ +"""Diagnostics support for SamsungTV.""" +from __future__ import annotations + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_TOKEN +from homeassistant.core import HomeAssistant + +TO_REDACT = {CONF_TOKEN} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict: + """Return diagnostics for a config entry.""" + diag_data = {"entry": async_redact_data(entry.as_dict(), TO_REDACT)} + + return diag_data From 0be8060b6968e28ae1d69bb37a0488e817781352 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 1 Feb 2022 00:18:49 +0000 Subject: [PATCH 0156/1098] [ci skip] Translation update --- .../accuweather/translations/el.json | 1 + .../components/airnow/translations/el.json | 7 +++++++ .../aussie_broadband/translations/bg.json | 7 +++++-- .../aussie_broadband/translations/nl.json | 3 ++- .../bmw_connected_drive/translations/el.json | 21 +++++++++++++++++++ .../climacell/translations/sensor.nl.json | 4 ++++ .../components/coinbase/translations/bg.json | 2 ++ .../dialogflow/translations/he.json | 1 + .../garages_amsterdam/translations/el.json | 3 ++- .../components/geofency/translations/he.json | 1 + .../components/goalzero/translations/el.json | 1 + .../components/gpslogger/translations/he.json | 1 + .../components/homekit/translations/nl.json | 4 ++-- .../translations/select.bg.json | 9 ++++++++ .../translations/select.no.json | 9 ++++++++ .../homewizard/translations/nl.json | 2 +- .../homewizard/translations/no.json | 2 +- .../translations/el.json | 1 + .../components/ifttt/translations/he.json | 1 + .../components/iss/translations/bg.json | 16 ++++++++++++++ .../components/iss/translations/he.json | 7 +++++++ .../components/iss/translations/nl.json | 1 + .../components/iss/translations/no.json | 16 ++++++++++++++ .../components/iss/translations/ru.json | 2 +- .../components/isy994/translations/el.json | 8 +++++++ .../components/knx/translations/bg.json | 6 ++++-- .../components/kraken/translations/el.json | 11 ++++++++++ .../components/locative/translations/he.json | 1 + .../lutron_caseta/translations/el.json | 1 + .../components/mailgun/translations/he.json | 1 + .../motion_blinds/translations/el.json | 17 +++++++++++++++ .../components/mysensors/translations/el.json | 8 ++++++- .../components/nest/translations/el.json | 3 +++ .../components/nexia/translations/el.json | 3 +++ .../components/overkiz/translations/bg.json | 3 ++- .../components/overkiz/translations/he.json | 3 ++- .../components/owntracks/translations/he.json | 1 + .../components/plaato/translations/he.json | 1 + .../recollect_waste/translations/el.json | 10 +++++++++ .../components/roku/translations/el.json | 4 ++++ .../components/senseme/translations/he.json | 3 ++- .../components/senseme/translations/nl.json | 3 +++ .../components/solax/translations/he.json | 17 +++++++++++++++ .../components/steamist/translations/nl.json | 5 +++++ .../synology_dsm/translations/bg.json | 3 ++- .../components/traccar/translations/he.json | 1 + .../tuya/translations/select.he.json | 5 +++++ .../tuya/translations/select.nl.json | 7 +++++++ .../tuya/translations/select.no.json | 8 +++++++ .../components/twilio/translations/he.json | 1 + .../components/twinkly/translations/nl.json | 2 +- .../unifiprotect/translations/bg.json | 6 ++++-- .../components/upnp/translations/el.json | 10 +++++++++ .../uptimerobot/translations/sensor.bg.json | 6 +++++- .../uptimerobot/translations/sensor.he.json | 7 +++++++ .../components/wled/translations/bg.json | 3 ++- 56 files changed, 269 insertions(+), 21 deletions(-) create mode 100644 homeassistant/components/airnow/translations/el.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/el.json create mode 100644 homeassistant/components/homekit_controller/translations/select.bg.json create mode 100644 homeassistant/components/homekit_controller/translations/select.no.json create mode 100644 homeassistant/components/iss/translations/bg.json create mode 100644 homeassistant/components/iss/translations/he.json create mode 100644 homeassistant/components/iss/translations/no.json create mode 100644 homeassistant/components/kraken/translations/el.json create mode 100644 homeassistant/components/motion_blinds/translations/el.json create mode 100644 homeassistant/components/solax/translations/he.json create mode 100644 homeassistant/components/uptimerobot/translations/sensor.he.json diff --git a/homeassistant/components/accuweather/translations/el.json b/homeassistant/components/accuweather/translations/el.json index 0cca8117080fc0..74756efbf38828 100644 --- a/homeassistant/components/accuweather/translations/el.json +++ b/homeassistant/components/accuweather/translations/el.json @@ -23,6 +23,7 @@ }, "system_health": { "info": { + "can_reach_server": "\u03a0\u03c1\u03bf\u03c3\u03b5\u03b3\u03b3\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae AccuWeather", "remaining_requests": "\u03a5\u03c0\u03bf\u03bb\u03b5\u03b9\u03c0\u03cc\u03bc\u03b5\u03bd\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03b1 \u03b1\u03b9\u03c4\u03ae\u03bc\u03b1\u03c4\u03b1" } } diff --git a/homeassistant/components/airnow/translations/el.json b/homeassistant/components/airnow/translations/el.json new file mode 100644 index 00000000000000..e8968158682e69 --- /dev/null +++ b/homeassistant/components/airnow/translations/el.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_location": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03b1\u03c0\u03bf\u03c4\u03b5\u03bb\u03ad\u03c3\u03bc\u03b1\u03c4\u03b1 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7\u03bd \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aussie_broadband/translations/bg.json b/homeassistant/components/aussie_broadband/translations/bg.json index 5f931933f9b722..508b940a541650 100644 --- a/homeassistant/components/aussie_broadband/translations/bg.json +++ b/homeassistant/components/aussie_broadband/translations/bg.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\u0410\u043a\u0430\u0443\u043d\u0442\u044a\u0442 \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d", + "no_services_found": "\u041d\u0435 \u0431\u044f\u0445\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u0443\u0441\u043b\u0443\u0433\u0438 \u0437\u0430 \u0442\u043e\u0437\u0438 \u0430\u043a\u0430\u0443\u043d\u0442", "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0442\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435 \u0431\u0435\u0448\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e" }, "error": { @@ -20,7 +21,8 @@ "service": { "data": { "services": "\u0423\u0441\u043b\u0443\u0433\u0438" - } + }, + "title": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0423\u0441\u043b\u0443\u0433\u0438" }, "user": { "data": { @@ -40,7 +42,8 @@ "init": { "data": { "services": "\u0423\u0441\u043b\u0443\u0433\u0438" - } + }, + "title": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0423\u0441\u043b\u0443\u0433\u0438" } } } diff --git a/homeassistant/components/aussie_broadband/translations/nl.json b/homeassistant/components/aussie_broadband/translations/nl.json index f895082e233a6a..c1ca5b7717acea 100644 --- a/homeassistant/components/aussie_broadband/translations/nl.json +++ b/homeassistant/components/aussie_broadband/translations/nl.json @@ -21,7 +21,8 @@ "service": { "data": { "services": "Services" - } + }, + "title": "Selecteer Services" }, "user": { "data": { diff --git a/homeassistant/components/bmw_connected_drive/translations/el.json b/homeassistant/components/bmw_connected_drive/translations/el.json new file mode 100644 index 00000000000000..ff1f06ff6f8d2e --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/el.json @@ -0,0 +1,21 @@ +{ + "config": { + "step": { + "user": { + "data": { + "region": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae ConnectedDrive" + } + } + } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "\u039c\u03cc\u03bd\u03bf \u03b3\u03b9\u03b1 \u03b1\u03bd\u03ac\u03b3\u03bd\u03c9\u03c3\u03b7 (\u03bc\u03cc\u03bd\u03bf \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b5\u03c2 \u03ba\u03b1\u03b9 \u03b5\u03b9\u03b4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9\u03c2, \u03cc\u03c7\u03b9 \u03b5\u03ba\u03c4\u03ad\u03bb\u03b5\u03c3\u03b7 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03b9\u03ce\u03bd, \u03cc\u03c7\u03b9 \u03ba\u03bb\u03b5\u03af\u03b4\u03c9\u03bc\u03b1)", + "use_location": "\u03a7\u03c1\u03ae\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2 \u03c4\u03bf\u03c5 Home Assistant \u03b3\u03b9\u03b1 \u03c4\u03b9\u03c2 \u03b4\u03b7\u03bc\u03bf\u03c3\u03ba\u03bf\u03c0\u03ae\u03c3\u03b5\u03b9\u03c2 \u03b8\u03ad\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03b1\u03c5\u03c4\u03bf\u03ba\u03b9\u03bd\u03ae\u03c4\u03bf\u03c5 (\u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03bf\u03c7\u03ae\u03bc\u03b1\u03c4\u03b1 \u03c0\u03bf\u03c5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 i3/i8 \u03ba\u03b1\u03b9 \u03ad\u03c7\u03bf\u03c5\u03bd \u03c0\u03b1\u03c1\u03b1\u03c7\u03b8\u03b5\u03af \u03c0\u03c1\u03b9\u03bd \u03b1\u03c0\u03cc \u03c4\u03b9\u03c2 7/2014)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/sensor.nl.json b/homeassistant/components/climacell/translations/sensor.nl.json index b3b09105941b63..37713ec0634251 100644 --- a/homeassistant/components/climacell/translations/sensor.nl.json +++ b/homeassistant/components/climacell/translations/sensor.nl.json @@ -1,6 +1,7 @@ { "state": { "climacell__health_concern": { + "good": "Goed", "hazardous": "Gevaarlijk", "moderate": "Gematigd", "unhealthy": "Ongezond", @@ -8,6 +9,9 @@ "very_unhealthy": "Heel ongezond" }, "climacell__pollen_index": { + "high": "Hoog", + "low": "Laag", + "medium": "Medium", "none": "Geen", "very_high": "Zeer Hoog", "very_low": "Zeer Laag" diff --git a/homeassistant/components/coinbase/translations/bg.json b/homeassistant/components/coinbase/translations/bg.json index 6888f4ddf354e6..eb72ab1d10d890 100644 --- a/homeassistant/components/coinbase/translations/bg.json +++ b/homeassistant/components/coinbase/translations/bg.json @@ -18,6 +18,8 @@ }, "options": { "error": { + "currency_unavailable": "\u0415\u0434\u043d\u043e \u0438\u043b\u0438 \u043f\u043e\u0432\u0435\u0447\u0435 \u043e\u0442 \u0438\u0441\u043a\u0430\u043d\u0438\u0442\u0435 \u0432\u0430\u043b\u0443\u0442\u043d\u0438 \u0441\u0430\u043b\u0434\u0430 \u043d\u0435 \u0441\u0435 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u044f\u0442 \u043e\u0442 \u0432\u0430\u0448\u0438\u044f Coinbase API.", + "exchange_rate_unavailable": "\u0415\u0434\u0438\u043d \u0438\u043b\u0438 \u043f\u043e\u0432\u0435\u0447\u0435 \u043e\u0442 \u0437\u0430\u044f\u0432\u0435\u043d\u0438\u0442\u0435 \u043e\u0431\u043c\u0435\u043d\u043d\u0438 \u043a\u0443\u0440\u0441\u043e\u0432\u0435 \u043d\u0435 \u0441\u0435 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u044f\u0442 \u043e\u0442 Coinbase.", "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" } } diff --git a/homeassistant/components/dialogflow/translations/he.json b/homeassistant/components/dialogflow/translations/he.json index ebee9aee97649f..55d9377f8d2295 100644 --- a/homeassistant/components/dialogflow/translations/he.json +++ b/homeassistant/components/dialogflow/translations/he.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u05dc\u05d0 \u05de\u05d7\u05d5\u05d1\u05e8 \u05dc\u05e2\u05e0\u05df Home Assistant.", "single_instance_allowed": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea.", "webhook_not_internet_accessible": "\u05de\u05d5\u05e4\u05e2 \u05d4-Home Assistant \u05e9\u05dc\u05da \u05e6\u05e8\u05d9\u05da \u05dc\u05d4\u05d9\u05d5\u05ea \u05e0\u05d2\u05d9\u05e9 \u05de\u05d4\u05d0\u05d9\u05e0\u05d8\u05e8\u05e0\u05d8 \u05db\u05d3\u05d9 \u05dc\u05e7\u05d1\u05dc \u05d4\u05d5\u05d3\u05e2\u05d5\u05ea webhook." } diff --git a/homeassistant/components/garages_amsterdam/translations/el.json b/homeassistant/components/garages_amsterdam/translations/el.json index da926cb5017270..f6e364b044ceb4 100644 --- a/homeassistant/components/garages_amsterdam/translations/el.json +++ b/homeassistant/components/garages_amsterdam/translations/el.json @@ -8,5 +8,6 @@ "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03b3\u03ba\u03b1\u03c1\u03ac\u03b6 \u03b3\u03b9\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7" } } - } + }, + "title": "Garages Amsterdam" } \ No newline at end of file diff --git a/homeassistant/components/geofency/translations/he.json b/homeassistant/components/geofency/translations/he.json index ebee9aee97649f..55d9377f8d2295 100644 --- a/homeassistant/components/geofency/translations/he.json +++ b/homeassistant/components/geofency/translations/he.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u05dc\u05d0 \u05de\u05d7\u05d5\u05d1\u05e8 \u05dc\u05e2\u05e0\u05df Home Assistant.", "single_instance_allowed": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea.", "webhook_not_internet_accessible": "\u05de\u05d5\u05e4\u05e2 \u05d4-Home Assistant \u05e9\u05dc\u05da \u05e6\u05e8\u05d9\u05da \u05dc\u05d4\u05d9\u05d5\u05ea \u05e0\u05d2\u05d9\u05e9 \u05de\u05d4\u05d0\u05d9\u05e0\u05d8\u05e8\u05e0\u05d8 \u05db\u05d3\u05d9 \u05dc\u05e7\u05d1\u05dc \u05d4\u05d5\u05d3\u05e2\u05d5\u05ea webhook." } diff --git a/homeassistant/components/goalzero/translations/el.json b/homeassistant/components/goalzero/translations/el.json index cf4ccf81af6958..4ad673ea7db315 100644 --- a/homeassistant/components/goalzero/translations/el.json +++ b/homeassistant/components/goalzero/translations/el.json @@ -10,6 +10,7 @@ }, "step": { "confirm_discovery": { + "description": "\u03a3\u03c5\u03bd\u03b9\u03c3\u03c4\u03ac\u03c4\u03b1\u03b9 \u03b7 \u03ba\u03c1\u03ac\u03c4\u03b7\u03c3\u03b7 DHCP \u03c3\u03c4\u03bf \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2. \u0395\u03ac\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af, \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03bd\u03b4\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03bd\u03b1 \u03bc\u03b7\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03ad\u03c9\u03c2 \u03cc\u03c4\u03bf\u03c5 \u03c4\u03bf Home Assistant \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03b5\u03b9 \u03c4\u03b7 \u03bd\u03ad\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 ip. \u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b5\u03b3\u03c7\u03b5\u03b9\u03c1\u03af\u03b4\u03b9\u03bf \u03c7\u03c1\u03ae\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2.", "title": "Goal Zero Yeti" }, "user": { diff --git a/homeassistant/components/gpslogger/translations/he.json b/homeassistant/components/gpslogger/translations/he.json index ebee9aee97649f..55d9377f8d2295 100644 --- a/homeassistant/components/gpslogger/translations/he.json +++ b/homeassistant/components/gpslogger/translations/he.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u05dc\u05d0 \u05de\u05d7\u05d5\u05d1\u05e8 \u05dc\u05e2\u05e0\u05df Home Assistant.", "single_instance_allowed": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea.", "webhook_not_internet_accessible": "\u05de\u05d5\u05e4\u05e2 \u05d4-Home Assistant \u05e9\u05dc\u05da \u05e6\u05e8\u05d9\u05da \u05dc\u05d4\u05d9\u05d5\u05ea \u05e0\u05d2\u05d9\u05e9 \u05de\u05d4\u05d0\u05d9\u05e0\u05d8\u05e8\u05e0\u05d8 \u05db\u05d3\u05d9 \u05dc\u05e7\u05d1\u05dc \u05d4\u05d5\u05d3\u05e2\u05d5\u05ea webhook." } diff --git a/homeassistant/components/homekit/translations/nl.json b/homeassistant/components/homekit/translations/nl.json index c8b3c037c0e3e2..9788f4904b6b88 100644 --- a/homeassistant/components/homekit/translations/nl.json +++ b/homeassistant/components/homekit/translations/nl.json @@ -68,10 +68,10 @@ "domains": "Domeinen om op te nemen", "include_domains": "Domeinen om op te nemen", "include_exclude_mode": "Inclusiemodus", - "mode": "modus" + "mode": "HomeKit-modus" }, "description": "HomeKit kan worden geconfigureerd om een brug of een enkel accessoire te tonen. In de accessoiremodus kan slechts \u00e9\u00e9n entiteit worden gebruikt. De accessoiremodus is vereist om mediaspelers met de tv-apparaatklasse correct te laten werken. Entiteiten in de \"Op te nemen domeinen\" zullen worden blootgesteld aan HomeKit. U kunt op het volgende scherm selecteren welke entiteiten u wilt opnemen of uitsluiten van deze lijst.", - "title": "Selecteer domeinen om zichtbaar te maken." + "title": "Selecteer modus en domeinen." }, "yaml": { "description": "Deze invoer wordt beheerd via YAML", diff --git a/homeassistant/components/homekit_controller/translations/select.bg.json b/homeassistant/components/homekit_controller/translations/select.bg.json new file mode 100644 index 00000000000000..5bf7e7a4d1f1ef --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/select.bg.json @@ -0,0 +1,9 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "away": "\u041e\u0442\u0441\u044a\u0441\u0442\u0432\u0430", + "home": "\u0414\u043e\u043c", + "sleep": "\u0417\u0430\u0441\u043f\u0438\u0432\u0430\u043d\u0435" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/select.no.json b/homeassistant/components/homekit_controller/translations/select.no.json new file mode 100644 index 00000000000000..65dabaa328afda --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/select.no.json @@ -0,0 +1,9 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "away": "Borte", + "home": "Hjemme", + "sleep": "Sove" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homewizard/translations/nl.json b/homeassistant/components/homewizard/translations/nl.json index 0cae34cffebb04..bda434b4439eed 100644 --- a/homeassistant/components/homewizard/translations/nl.json +++ b/homeassistant/components/homewizard/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd", "api_not_enabled": "De API is niet ingeschakeld. Activeer API in de HomeWizard Energy App onder instellingen", "device_not_supported": "Dit apparaat wordt niet ondersteund", - "invalid_discovery_parameters": "unsupported_api_version", + "invalid_discovery_parameters": "Niet-ondersteunde API-versie gedetecteerd", "unknown_error": "Onverwachte fout" }, "step": { diff --git a/homeassistant/components/homewizard/translations/no.json b/homeassistant/components/homewizard/translations/no.json index b19184f1a33027..e2e0aadeb5ec5d 100644 --- a/homeassistant/components/homewizard/translations/no.json +++ b/homeassistant/components/homewizard/translations/no.json @@ -4,7 +4,7 @@ "already_configured": "Enheten er allerede konfigurert", "api_not_enabled": "API-en er ikke aktivert. Aktiver API i HomeWizard Energy-appen under innstillinger", "device_not_supported": "Denne enheten st\u00f8ttes ikke", - "invalid_discovery_parameters": "unsupported_api_version", + "invalid_discovery_parameters": "Oppdaget API-versjon som ikke st\u00f8ttes", "unknown_error": "Uventet feil" }, "step": { diff --git a/homeassistant/components/hunterdouglas_powerview/translations/el.json b/homeassistant/components/hunterdouglas_powerview/translations/el.json index b239b4b143e132..12cde31ea6d50c 100644 --- a/homeassistant/components/hunterdouglas_powerview/translations/el.json +++ b/homeassistant/components/hunterdouglas_powerview/translations/el.json @@ -1,5 +1,6 @@ { "config": { + "flow_title": "{name} ({host})", "step": { "link": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ({host});", diff --git a/homeassistant/components/ifttt/translations/he.json b/homeassistant/components/ifttt/translations/he.json index ebee9aee97649f..55d9377f8d2295 100644 --- a/homeassistant/components/ifttt/translations/he.json +++ b/homeassistant/components/ifttt/translations/he.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u05dc\u05d0 \u05de\u05d7\u05d5\u05d1\u05e8 \u05dc\u05e2\u05e0\u05df Home Assistant.", "single_instance_allowed": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea.", "webhook_not_internet_accessible": "\u05de\u05d5\u05e4\u05e2 \u05d4-Home Assistant \u05e9\u05dc\u05da \u05e6\u05e8\u05d9\u05da \u05dc\u05d4\u05d9\u05d5\u05ea \u05e0\u05d2\u05d9\u05e9 \u05de\u05d4\u05d0\u05d9\u05e0\u05d8\u05e8\u05e0\u05d8 \u05db\u05d3\u05d9 \u05dc\u05e7\u05d1\u05dc \u05d4\u05d5\u05d3\u05e2\u05d5\u05ea webhook." } diff --git a/homeassistant/components/iss/translations/bg.json b/homeassistant/components/iss/translations/bg.json new file mode 100644 index 00000000000000..7d004af7b54f33 --- /dev/null +++ b/homeassistant/components/iss/translations/bg.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "latitude_longitude_not_defined": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430\u0442\u0430 \u0448\u0438\u0440\u0438\u043d\u0430 \u0438 \u0433\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430\u0442\u0430 \u0434\u044a\u043b\u0436\u0438\u043d\u0430 \u043d\u0435 \u0441\u0430 \u0434\u0435\u0444\u0438\u043d\u0438\u0440\u0430\u043d\u0438 \u0432 Home Assistant.", + "single_instance_allowed": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." + }, + "step": { + "user": { + "data": { + "show_on_map": "\u0414\u0430 \u0441\u0435 \u043f\u043e\u043a\u0430\u0436\u0435 \u043d\u0430 \u043a\u0430\u0440\u0442\u0430\u0442\u0430?" + }, + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u0442\u0435 \u041c\u0435\u0436\u0434\u0443\u043d\u0430\u0440\u043e\u0434\u043d\u0430\u0442\u0430 \u043a\u043e\u0441\u043c\u0438\u0447\u0435\u0441\u043a\u0430 \u0441\u0442\u0430\u043d\u0446\u0438\u044f?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iss/translations/he.json b/homeassistant/components/iss/translations/he.json new file mode 100644 index 00000000000000..d0c3523da94e2a --- /dev/null +++ b/homeassistant/components/iss/translations/he.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iss/translations/nl.json b/homeassistant/components/iss/translations/nl.json index c14e2f838001c2..e2ed58741bdb4d 100644 --- a/homeassistant/components/iss/translations/nl.json +++ b/homeassistant/components/iss/translations/nl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "latitude_longitude_not_defined": "Breedte- en lengtegraad zijn niet gedefinieerd in Home Assistant.", "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "step": { diff --git a/homeassistant/components/iss/translations/no.json b/homeassistant/components/iss/translations/no.json new file mode 100644 index 00000000000000..aec142c37cfade --- /dev/null +++ b/homeassistant/components/iss/translations/no.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "latitude_longitude_not_defined": "Bredde- og lengdegrad er ikke definert i Home Assistant.", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." + }, + "step": { + "user": { + "data": { + "show_on_map": "Vis p\u00e5 kart?" + }, + "description": "Vil du konfigurere den internasjonale romstasjonen?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iss/translations/ru.json b/homeassistant/components/iss/translations/ru.json index ffd5861f9cfa75..277046d209d28e 100644 --- a/homeassistant/components/iss/translations/ru.json +++ b/homeassistant/components/iss/translations/ru.json @@ -9,7 +9,7 @@ "data": { "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043d\u0430 \u043a\u0430\u0440\u0442\u0435" }, - "description": "\u041d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 Internation Space Station?" + "description": "\u041d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 International Space Station?" } } } diff --git a/homeassistant/components/isy994/translations/el.json b/homeassistant/components/isy994/translations/el.json index 61c22e7e6ed023..40d3a848254927 100644 --- a/homeassistant/components/isy994/translations/el.json +++ b/homeassistant/components/isy994/translations/el.json @@ -26,5 +26,13 @@ "description": "\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 ISY: \n - \u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03ba\u03cc\u03bc\u03b2\u03bf\u03c5: \u039a\u03ac\u03b8\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ae \u03c6\u03ac\u03ba\u03b5\u03bb\u03bf\u03c2 \u03c0\u03bf\u03c5 \u03c0\u03b5\u03c1\u03b9\u03ad\u03c7\u03b5\u03b9 'Node Sensor String' \u03c3\u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03b8\u03b1 \u03b1\u03bd\u03c4\u03b9\u03bc\u03b5\u03c4\u03c9\u03c0\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c9\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03ae \u03b4\u03c5\u03b1\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2. \n - Ignore String (\u0391\u03b3\u03bd\u03bf\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac): \u039f\u03c0\u03bf\u03b9\u03b1\u03b4\u03ae\u03c0\u03bf\u03c4\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bc\u03b5 \u03c4\u03bf 'Ignore String' \u03c3\u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03b8\u03b1 \u03b1\u03b3\u03bd\u03bf\u03b5\u03af\u03c4\u03b1\u03b9. \n - \u039c\u03b5\u03c4\u03b1\u03b2\u03bb\u03b7\u03c4\u03ae \u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1: \u039a\u03ac\u03b8\u03b5 \u03bc\u03b5\u03c4\u03b1\u03b2\u03bb\u03b7\u03c4\u03ae \u03c0\u03bf\u03c5 \u03c0\u03b5\u03c1\u03b9\u03ad\u03c7\u03b5\u03b9 \u03c4\u03bf 'Variable Sensor String' \u03b8\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c4\u03b5\u03b8\u03b5\u03af \u03c9\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2. \n - \u0395\u03c0\u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac \u03c6\u03c9\u03c4\u03b5\u03b9\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03c6\u03c9\u03c4\u03cc\u03c2: \u0395\u03ac\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7, \u03b7 \u03c0\u03c1\u03bf\u03b7\u03b3\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7 \u03c6\u03c9\u03c4\u03b5\u03b9\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b8\u03b1 \u03b1\u03c0\u03bf\u03ba\u03b1\u03b8\u03af\u03c3\u03c4\u03b1\u03c4\u03b1\u03b9 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03bd\u03cc\u03c2 \u03c6\u03c9\u03c4\u03cc\u03c2 \u03b1\u03bd\u03c4\u03af \u03b3\u03b9\u03b1 \u03c4\u03bf \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03bc\u03ad\u03bd\u03bf \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2." } } + }, + "system_health": { + "info": { + "device_connected": "\u03a3\u03c5\u03bd\u03b4\u03ad\u03b8\u03b7\u03ba\u03b5 \u03c4\u03bf ISY", + "host_reachable": "\u039f\u03b9\u03ba\u03bf\u03b4\u03b5\u03c3\u03c0\u03cc\u03c4\u03b7\u03c2 \u03c0\u03c1\u03bf\u03c3\u03b2\u03ac\u03c3\u03b9\u03bc\u03bf\u03c2", + "last_heartbeat": "\u03a4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03bf\u03c2 \u03c7\u03c4\u03cd\u03c0\u03bf\u03c2 \u03c4\u03b7\u03c2 \u03ba\u03b1\u03c1\u03b4\u03b9\u03ac\u03c2", + "websocket_status": "\u039a\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c5\u03c0\u03bf\u03b4\u03bf\u03c7\u03ae\u03c2 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03bf\u03c2" + } } } \ No newline at end of file diff --git a/homeassistant/components/knx/translations/bg.json b/homeassistant/components/knx/translations/bg.json index 07785249026974..43f72f49867878 100644 --- a/homeassistant/components/knx/translations/bg.json +++ b/homeassistant/components/knx/translations/bg.json @@ -12,7 +12,8 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "local_ip": "\u041b\u043e\u043a\u0430\u043b\u0435\u043d IP (\u043e\u0441\u0442\u0430\u0432\u0435\u0442\u0435 \u043f\u0440\u0430\u0437\u043d\u043e, \u0430\u043a\u043e \u043d\u0435 \u0441\u0442\u0435 \u0441\u0438\u0433\u0443\u0440\u043d\u0438)", - "port": "\u041f\u043e\u0440\u0442" + "port": "\u041f\u043e\u0440\u0442", + "tunneling_type": "KNX \u0442\u0443\u043d\u0435\u043b\u0435\u043d \u0442\u0438\u043f" } }, "routing": { @@ -33,7 +34,8 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "local_ip": "\u041b\u043e\u043a\u0430\u043b\u0435\u043d IP (\u043e\u0441\u0442\u0430\u0432\u0435\u0442\u0435 \u043f\u0440\u0430\u0437\u043d\u043e, \u0430\u043a\u043e \u043d\u0435 \u0441\u0442\u0435 \u0441\u0438\u0433\u0443\u0440\u043d\u0438)", - "port": "\u041f\u043e\u0440\u0442" + "port": "\u041f\u043e\u0440\u0442", + "tunneling_type": "KNX \u0442\u0443\u043d\u0435\u043b\u0435\u043d \u0442\u0438\u043f" } } } diff --git a/homeassistant/components/kraken/translations/el.json b/homeassistant/components/kraken/translations/el.json new file mode 100644 index 00000000000000..6cb6a0d4ea0c44 --- /dev/null +++ b/homeassistant/components/kraken/translations/el.json @@ -0,0 +1,11 @@ +{ + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7\u03c2" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/translations/he.json b/homeassistant/components/locative/translations/he.json index 7e155c6bdd7a24..4850982aede2c5 100644 --- a/homeassistant/components/locative/translations/he.json +++ b/homeassistant/components/locative/translations/he.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u05dc\u05d0 \u05de\u05d7\u05d5\u05d1\u05e8 \u05dc\u05e2\u05e0\u05df Home Assistant.", "single_instance_allowed": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea.", "webhook_not_internet_accessible": "\u05de\u05d5\u05e4\u05e2 \u05d4-Home Assistant \u05e9\u05dc\u05da \u05e6\u05e8\u05d9\u05da \u05dc\u05d4\u05d9\u05d5\u05ea \u05e0\u05d2\u05d9\u05e9 \u05de\u05d4\u05d0\u05d9\u05e0\u05d8\u05e8\u05e0\u05d8 \u05db\u05d3\u05d9 \u05dc\u05e7\u05d1\u05dc \u05d4\u05d5\u05d3\u05e2\u05d5\u05ea webhook." }, diff --git a/homeassistant/components/lutron_caseta/translations/el.json b/homeassistant/components/lutron_caseta/translations/el.json index 4285f5fcbd0cbf..4f0bc6628e3695 100644 --- a/homeassistant/components/lutron_caseta/translations/el.json +++ b/homeassistant/components/lutron_caseta/translations/el.json @@ -14,6 +14,7 @@ "title": "\u03a3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7 \u03bc\u03b5 \u03c4\u03b7 \u03b3\u03ad\u03c6\u03c5\u03c1\u03b1" }, "user": { + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2.", "title": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03b3\u03ad\u03c6\u03c5\u03c1\u03b1" } } diff --git a/homeassistant/components/mailgun/translations/he.json b/homeassistant/components/mailgun/translations/he.json index ebee9aee97649f..55d9377f8d2295 100644 --- a/homeassistant/components/mailgun/translations/he.json +++ b/homeassistant/components/mailgun/translations/he.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u05dc\u05d0 \u05de\u05d7\u05d5\u05d1\u05e8 \u05dc\u05e2\u05e0\u05df Home Assistant.", "single_instance_allowed": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea.", "webhook_not_internet_accessible": "\u05de\u05d5\u05e4\u05e2 \u05d4-Home Assistant \u05e9\u05dc\u05da \u05e6\u05e8\u05d9\u05da \u05dc\u05d4\u05d9\u05d5\u05ea \u05e0\u05d2\u05d9\u05e9 \u05de\u05d4\u05d0\u05d9\u05e0\u05d8\u05e8\u05e0\u05d8 \u05db\u05d3\u05d9 \u05dc\u05e7\u05d1\u05dc \u05d4\u05d5\u05d3\u05e2\u05d5\u05ea webhook." } diff --git a/homeassistant/components/motion_blinds/translations/el.json b/homeassistant/components/motion_blinds/translations/el.json new file mode 100644 index 00000000000000..8c6a8c07fdfcba --- /dev/null +++ b/homeassistant/components/motion_blinds/translations/el.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "discovery_error": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03c0\u03cd\u03bb\u03b7 \u03ba\u03af\u03bd\u03b7\u03c3\u03b7\u03c2" + }, + "step": { + "connect": { + "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API 16 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03c9\u03bd, \u03b4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key \u03b3\u03b9\u03b1 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2.", + "title": "Motion Blinds" + }, + "select": { + "description": "\u0395\u03ba\u03c4\u03b5\u03bb\u03ad\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b1\u03bd \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03b5\u03c0\u03b9\u03c0\u03bb\u03ad\u03bf\u03bd \u03c0\u03cd\u03bb\u03b5\u03c2 \u03ba\u03af\u03bd\u03b7\u03c3\u03b7\u03c2.", + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03cd\u03bb\u03b7 \u03ba\u03af\u03bd\u03b7\u03c3\u03b7\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/el.json b/homeassistant/components/mysensors/translations/el.json index ba550fbbe2adb5..1edd6c06fc2e47 100644 --- a/homeassistant/components/mysensors/translations/el.json +++ b/homeassistant/components/mysensors/translations/el.json @@ -21,6 +21,10 @@ "invalid_device": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae", "invalid_ip": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", "invalid_persistence_file": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf persistance", + "invalid_port": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03b8\u03cd\u03c1\u03b1\u03c2", + "invalid_publish_topic": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b8\u03ad\u03bc\u03b1 \u03b4\u03b7\u03bc\u03bf\u03c3\u03af\u03b5\u03c5\u03c3\u03b7\u03c2", + "invalid_serial": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1", + "invalid_subscribe_topic": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b8\u03ad\u03bc\u03b1 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2", "invalid_version": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 MySensors", "mqtt_required": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 MQTT \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", "not_a_number": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc", @@ -49,7 +53,9 @@ "gw_tcp": { "data": { "device": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03b7\u03c2 \u03c0\u03cd\u03bb\u03b7\u03c2", - "persistence_file": "\u03b1\u03c1\u03c7\u03b5\u03af\u03bf persistence (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1)" + "persistence_file": "\u03b1\u03c1\u03c7\u03b5\u03af\u03bf persistence (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1)", + "tcp_port": "\u03b8\u03cd\u03c1\u03b1", + "version": "\u0388\u03ba\u03b4\u03bf\u03c3\u03b7 MySensors" }, "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03cd\u03bb\u03b7\u03c2 Ethernet" }, diff --git a/homeassistant/components/nest/translations/el.json b/homeassistant/components/nest/translations/el.json index 201cd7c02b541a..0cde52b9297f95 100644 --- a/homeassistant/components/nest/translations/el.json +++ b/homeassistant/components/nest/translations/el.json @@ -16,6 +16,9 @@ "pubsub": { "description": "\u0395\u03c0\u03b9\u03c3\u03ba\u03b5\u03c6\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf [Cloud Console]({url}) \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b2\u03c1\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03bf\u03c5 \u03ad\u03c1\u03b3\u03bf\u03c5 \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Google Cloud.", "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Google Cloud" + }, + "reauth_confirm": { + "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 Nest \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03bb\u03ad\u03b3\u03be\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03b7\u03bd \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03c4\u03bf\u03c5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03c3\u03b1\u03c2" } } }, diff --git a/homeassistant/components/nexia/translations/el.json b/homeassistant/components/nexia/translations/el.json index 095042b54b0709..bfd24267091b87 100644 --- a/homeassistant/components/nexia/translations/el.json +++ b/homeassistant/components/nexia/translations/el.json @@ -2,6 +2,9 @@ "config": { "step": { "user": { + "data": { + "brand": "\u039c\u03ac\u03c1\u03ba\u03b1" + }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 mynexia.com" } } diff --git a/homeassistant/components/overkiz/translations/bg.json b/homeassistant/components/overkiz/translations/bg.json index 25ad61ac70f186..bca9ff034712b0 100644 --- a/homeassistant/components/overkiz/translations/bg.json +++ b/homeassistant/components/overkiz/translations/bg.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "\u0410\u043a\u0430\u0443\u043d\u0442\u044a\u0442 \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d", - "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0442\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435 \u0431\u0435\u0448\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e" + "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0442\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435 \u0431\u0435\u0448\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e", + "reauth_wrong_account": "\u041c\u043e\u0436\u0435\u0442\u0435 \u0434\u0430 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u0435 \u0437\u0430\u043f\u0438\u0441\u0430 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0441\u044a\u0441 \u0441\u044a\u0449\u0438\u044f Overkiz \u0430\u043a\u0430\u0443\u043d\u0442 \u0438 \u0445\u044a\u0431 " }, "error": { "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", diff --git a/homeassistant/components/overkiz/translations/he.json b/homeassistant/components/overkiz/translations/he.json index bb0bbef8760bc4..a030b22455e009 100644 --- a/homeassistant/components/overkiz/translations/he.json +++ b/homeassistant/components/overkiz/translations/he.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d7\u05e9\u05d1\u05d5\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4" + "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d7\u05e9\u05d1\u05d5\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4", + "reauth_successful": "\u05d4\u05d0\u05d9\u05de\u05d5\u05ea \u05de\u05d7\u05d3\u05e9 \u05d4\u05e6\u05dc\u05d9\u05d7" }, "error": { "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", diff --git a/homeassistant/components/owntracks/translations/he.json b/homeassistant/components/owntracks/translations/he.json index d0c3523da94e2a..10bd4cb9a4126e 100644 --- a/homeassistant/components/owntracks/translations/he.json +++ b/homeassistant/components/owntracks/translations/he.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u05dc\u05d0 \u05de\u05d7\u05d5\u05d1\u05e8 \u05dc\u05e2\u05e0\u05df Home Assistant.", "single_instance_allowed": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea." } } diff --git a/homeassistant/components/plaato/translations/he.json b/homeassistant/components/plaato/translations/he.json index 014783d743188b..28375b45c2cd56 100644 --- a/homeassistant/components/plaato/translations/he.json +++ b/homeassistant/components/plaato/translations/he.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d7\u05e9\u05d1\u05d5\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4", + "cloud_not_connected": "\u05dc\u05d0 \u05de\u05d7\u05d5\u05d1\u05e8 \u05dc\u05e2\u05e0\u05df Home Assistant.", "single_instance_allowed": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea.", "webhook_not_internet_accessible": "\u05de\u05d5\u05e4\u05e2 \u05d4-Home Assistant \u05e9\u05dc\u05da \u05e6\u05e8\u05d9\u05da \u05dc\u05d4\u05d9\u05d5\u05ea \u05e0\u05d2\u05d9\u05e9 \u05de\u05d4\u05d0\u05d9\u05e0\u05d8\u05e8\u05e0\u05d8 \u05db\u05d3\u05d9 \u05dc\u05e7\u05d1\u05dc \u05d4\u05d5\u05d3\u05e2\u05d5\u05ea webhook." }, diff --git a/homeassistant/components/recollect_waste/translations/el.json b/homeassistant/components/recollect_waste/translations/el.json index 5dbfa18b18c018..2f816f71c3c9a9 100644 --- a/homeassistant/components/recollect_waste/translations/el.json +++ b/homeassistant/components/recollect_waste/translations/el.json @@ -11,5 +11,15 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "friendly_name": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c6\u03b9\u03bb\u03b9\u03ba\u03ac \u03bf\u03bd\u03cc\u03bc\u03b1\u03c4\u03b1 \u03b3\u03b9\u03b1 \u03c4\u03cd\u03c0\u03bf\u03c5\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bb\u03b1\u03b2\u03ae\u03c2 (\u03cc\u03c0\u03bf\u03c5 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc\u03bd)" + }, + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Recollect Waste" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/roku/translations/el.json b/homeassistant/components/roku/translations/el.json index 793cd468f33263..7850ff1c34d53e 100644 --- a/homeassistant/components/roku/translations/el.json +++ b/homeassistant/components/roku/translations/el.json @@ -1,6 +1,10 @@ { "config": { "step": { + "discovery_confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name};", + "title": "Roku" + }, "user": { "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 Roku \u03c3\u03b1\u03c2." } diff --git a/homeassistant/components/senseme/translations/he.json b/homeassistant/components/senseme/translations/he.json index 55eb3a0d660035..3b3e7dea4bfe0a 100644 --- a/homeassistant/components/senseme/translations/he.json +++ b/homeassistant/components/senseme/translations/he.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4" + "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4", + "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4" }, "error": { "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", diff --git a/homeassistant/components/senseme/translations/nl.json b/homeassistant/components/senseme/translations/nl.json index 5ff28250076b1f..6cb73c4c9f2f98 100644 --- a/homeassistant/components/senseme/translations/nl.json +++ b/homeassistant/components/senseme/translations/nl.json @@ -10,6 +10,9 @@ }, "flow_title": "{name} - {model} ({host})", "step": { + "discovery_confirm": { + "description": "Wilt u {name} - {model} ({host})?" + }, "manual": { "data": { "host": "Host" diff --git a/homeassistant/components/solax/translations/he.json b/homeassistant/components/solax/translations/he.json new file mode 100644 index 00000000000000..44e903b1220222 --- /dev/null +++ b/homeassistant/components/solax/translations/he.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", + "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" + }, + "step": { + "user": { + "data": { + "ip_address": "\u05db\u05ea\u05d5\u05d1\u05ea IP", + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "port": "\u05e4\u05ea\u05d7\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steamist/translations/nl.json b/homeassistant/components/steamist/translations/nl.json index 926f3cc4b9d09e..345ec01dce3489 100644 --- a/homeassistant/components/steamist/translations/nl.json +++ b/homeassistant/components/steamist/translations/nl.json @@ -16,6 +16,11 @@ "discovery_confirm": { "description": "Wilt u {name} ({ipaddress}) instellen?" }, + "pick_device": { + "data": { + "device": "Apparaat" + } + }, "user": { "data": { "host": "Host" diff --git a/homeassistant/components/synology_dsm/translations/bg.json b/homeassistant/components/synology_dsm/translations/bg.json index f77c82130a41f9..acf35c4c2a4b4d 100644 --- a/homeassistant/components/synology_dsm/translations/bg.json +++ b/homeassistant/components/synology_dsm/translations/bg.json @@ -53,7 +53,8 @@ "step": { "init": { "data": { - "scan_interval": "\u041c\u0438\u043d\u0443\u0442\u0438 \u043c\u0435\u0436\u0434\u0443 \u0441\u043a\u0430\u043d\u0438\u0440\u0430\u043d\u0438\u044f\u0442\u0430" + "scan_interval": "\u041c\u0438\u043d\u0443\u0442\u0438 \u043c\u0435\u0436\u0434\u0443 \u0441\u043a\u0430\u043d\u0438\u0440\u0430\u043d\u0438\u044f\u0442\u0430", + "snap_profile_type": "\u041d\u0438\u0432\u043e \u043d\u0430 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u043e \u043d\u0430 \u0441\u043d\u0438\u043c\u043a\u0438\u0442\u0435 \u043e\u0442 \u043a\u0430\u043c\u0435\u0440\u0430\u0442\u0430 (0:\u0432\u0438\u0441\u043e\u043a\u043e 1:\u0441\u0440\u0435\u0434\u043d\u043e 2:\u043d\u0438\u0441\u043a\u043e)" } } } diff --git a/homeassistant/components/traccar/translations/he.json b/homeassistant/components/traccar/translations/he.json index ebee9aee97649f..55d9377f8d2295 100644 --- a/homeassistant/components/traccar/translations/he.json +++ b/homeassistant/components/traccar/translations/he.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u05dc\u05d0 \u05de\u05d7\u05d5\u05d1\u05e8 \u05dc\u05e2\u05e0\u05df Home Assistant.", "single_instance_allowed": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea.", "webhook_not_internet_accessible": "\u05de\u05d5\u05e4\u05e2 \u05d4-Home Assistant \u05e9\u05dc\u05da \u05e6\u05e8\u05d9\u05da \u05dc\u05d4\u05d9\u05d5\u05ea \u05e0\u05d2\u05d9\u05e9 \u05de\u05d4\u05d0\u05d9\u05e0\u05d8\u05e8\u05e0\u05d8 \u05db\u05d3\u05d9 \u05dc\u05e7\u05d1\u05dc \u05d4\u05d5\u05d3\u05e2\u05d5\u05ea webhook." } diff --git a/homeassistant/components/tuya/translations/select.he.json b/homeassistant/components/tuya/translations/select.he.json index f3555baadfe84f..2e792646cecaf9 100644 --- a/homeassistant/components/tuya/translations/select.he.json +++ b/homeassistant/components/tuya/translations/select.he.json @@ -8,6 +8,11 @@ "1": "\u05db\u05d1\u05d5\u05d9", "2": "\u05de\u05d5\u05e4\u05e2\u05dc" }, + "tuya__fan_angle": { + "30": "30\u00b0", + "60": "60\u00b0", + "90": "90\u00b0" + }, "tuya__fingerbot_mode": { "click": "\u05d3\u05d7\u05d9\u05e4\u05d4", "switch": "\u05de\u05ea\u05d2" diff --git a/homeassistant/components/tuya/translations/select.nl.json b/homeassistant/components/tuya/translations/select.nl.json index 4393efb7859c6a..979a8d50ee1479 100644 --- a/homeassistant/components/tuya/translations/select.nl.json +++ b/homeassistant/components/tuya/translations/select.nl.json @@ -23,6 +23,7 @@ "1": "Hoge gevoeligheid" }, "tuya__fan_angle": { + "30": "30\u00b0", "60": "60\u00b0", "90": "90\u00b0" }, @@ -62,17 +63,22 @@ "power_on": "Aan" }, "tuya__vacuum_cistern": { + "closed": "Gesloten", + "high": "Hoog", "low": "Laag", "middle": "Midden" }, "tuya__vacuum_collection": { + "large": "Groot", "middle": "Midden", "small": "Klein" }, "tuya__vacuum_mode": { "bow": "Boog", + "chargego": "Keer terug naar dock", "left_bow": "Boog links", "left_spiral": "Spiraal links", + "mop": "Dweil", "part": "Deel", "partial_bow": "Boog gedeeltelijk", "pick_zone": "Kies zone", @@ -84,6 +90,7 @@ "single": "Enkel", "smart": "Smart", "spiral": "Spiraal", + "standby": "Stand-by", "wall_follow": "Volg muur", "zone": "Zone" } diff --git a/homeassistant/components/tuya/translations/select.no.json b/homeassistant/components/tuya/translations/select.no.json index 09b02ba8cb32a3..71b49b9ad2f5d1 100644 --- a/homeassistant/components/tuya/translations/select.no.json +++ b/homeassistant/components/tuya/translations/select.no.json @@ -10,6 +10,14 @@ "1": "Av", "2": "P\u00e5" }, + "tuya__curtain_mode": { + "morning": "Morgen", + "night": "Natt" + }, + "tuya__curtain_motor_mode": { + "back": "Tilbake", + "forward": "Framover" + }, "tuya__decibel_sensitivity": { "0": "Lav f\u00f8lsomhet", "1": "H\u00f8y f\u00f8lsomhet" diff --git a/homeassistant/components/twilio/translations/he.json b/homeassistant/components/twilio/translations/he.json index 7e155c6bdd7a24..4850982aede2c5 100644 --- a/homeassistant/components/twilio/translations/he.json +++ b/homeassistant/components/twilio/translations/he.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u05dc\u05d0 \u05de\u05d7\u05d5\u05d1\u05e8 \u05dc\u05e2\u05e0\u05df Home Assistant.", "single_instance_allowed": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea.", "webhook_not_internet_accessible": "\u05de\u05d5\u05e4\u05e2 \u05d4-Home Assistant \u05e9\u05dc\u05da \u05e6\u05e8\u05d9\u05da \u05dc\u05d4\u05d9\u05d5\u05ea \u05e0\u05d2\u05d9\u05e9 \u05de\u05d4\u05d0\u05d9\u05e0\u05d8\u05e8\u05e0\u05d8 \u05db\u05d3\u05d9 \u05dc\u05e7\u05d1\u05dc \u05d4\u05d5\u05d3\u05e2\u05d5\u05ea webhook." }, diff --git a/homeassistant/components/twinkly/translations/nl.json b/homeassistant/components/twinkly/translations/nl.json index 4efce28f37f8bb..38331177895eb0 100644 --- a/homeassistant/components/twinkly/translations/nl.json +++ b/homeassistant/components/twinkly/translations/nl.json @@ -12,7 +12,7 @@ }, "user": { "data": { - "host": "Hostnaam (of IP-adres van uw Twinkly apparaat" + "host": "Host" }, "description": "Uw Twinkly LED-string instellen", "title": "Twinkly" diff --git a/homeassistant/components/unifiprotect/translations/bg.json b/homeassistant/components/unifiprotect/translations/bg.json index 4e1eaa0c695805..adc8d7ef6996f9 100644 --- a/homeassistant/components/unifiprotect/translations/bg.json +++ b/homeassistant/components/unifiprotect/translations/bg.json @@ -14,7 +14,8 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u0430", "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" - } + }, + "title": "\u041e\u0442\u043a\u0440\u0438\u0442 \u0435 UniFi Protect" }, "reauth_confirm": { "data": { @@ -29,7 +30,8 @@ "password": "\u041f\u0430\u0440\u043e\u043b\u0430", "port": "\u041f\u043e\u0440\u0442", "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" - } + }, + "description": "\u0429\u0435 \u0432\u0438 \u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c \u043b\u043e\u043a\u0430\u043b\u0435\u043d \u043f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b, \u0441\u044a\u0437\u0434\u0430\u0434\u0435\u043d \u0432\u044a\u0432 \u0432\u0430\u0448\u0430\u0442\u0430 UniFi OS Console, \u0437\u0430 \u0434\u0430 \u0432\u043b\u0435\u0437\u0435\u0442\u0435 \u0441 \u043d\u0435\u0433\u043e. \u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u0438\u0442\u0435 \u0430\u043a\u0430\u0443\u043d\u0442\u0438 \u0441\u044a\u0437\u0434\u0430\u0434\u0435\u043d\u0438 \u0432 Ubiquiti Cloud \u043d\u044f\u043c\u0430 \u0434\u0430 \u0440\u0430\u0431\u043e\u0442\u044f\u0442. \u0417\u0430 \u043f\u043e\u0432\u0435\u0447\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f: {local_user_documentation_url}" } } } diff --git a/homeassistant/components/upnp/translations/el.json b/homeassistant/components/upnp/translations/el.json index ba35bb730828fa..5472b660388a88 100644 --- a/homeassistant/components/upnp/translations/el.json +++ b/homeassistant/components/upnp/translations/el.json @@ -8,9 +8,19 @@ "user": { "data": { "scan_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7\u03c2 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1, \u03b5\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf 30)", + "unique_id": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae", "usn": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" } } } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7\u03c2 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1, \u03b5\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf 30)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/uptimerobot/translations/sensor.bg.json b/homeassistant/components/uptimerobot/translations/sensor.bg.json index 9b98369a859ae7..41d3c402ef1f34 100644 --- a/homeassistant/components/uptimerobot/translations/sensor.bg.json +++ b/homeassistant/components/uptimerobot/translations/sensor.bg.json @@ -1,7 +1,11 @@ { "state": { "uptimerobot__monitor_status": { - "pause": "\u041f\u0430\u0443\u0437\u0430" + "down": "\u0414\u043e\u043b\u0443", + "not_checked_yet": "\u041d\u0435\u043f\u043e\u0442\u0432\u044a\u0440\u0434\u0435\u043d\u043e", + "pause": "\u041f\u0430\u0443\u0437\u0430", + "seems_down": "\u041d\u0435\u0434\u043e\u0441\u0442\u044a\u043f\u043d\u043e \u0437\u0430 \u043c\u043e\u043c\u0435\u043d\u0442\u0430", + "up": "\u041d\u0430\u0433\u043e\u0440\u0435" } } } \ No newline at end of file diff --git a/homeassistant/components/uptimerobot/translations/sensor.he.json b/homeassistant/components/uptimerobot/translations/sensor.he.json new file mode 100644 index 00000000000000..6d9e58dc5ded48 --- /dev/null +++ b/homeassistant/components/uptimerobot/translations/sensor.he.json @@ -0,0 +1,7 @@ +{ + "state": { + "uptimerobot__monitor_status": { + "up": "\u05dc\u05de\u05e2\u05dc\u05d4" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wled/translations/bg.json b/homeassistant/components/wled/translations/bg.json index a511aad9e42890..b023b5d6011315 100644 --- a/homeassistant/components/wled/translations/bg.json +++ b/homeassistant/components/wled/translations/bg.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", + "cct_unsupported": "\u0422\u043e\u0432\u0430 WLED \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430 CCT \u043a\u0430\u043d\u0430\u043b\u0438, \u043a\u043e\u0438\u0442\u043e \u043d\u0435 \u0441\u0435 \u043f\u043e\u0434\u0434\u044a\u0440\u0436\u0430\u0442 \u043e\u0442 \u0442\u0430\u0437\u0438 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f" }, "error": { "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" From d0412d65ac0819975ab0ab3d0889c443c7964726 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 1 Feb 2022 01:43:16 +0100 Subject: [PATCH 0157/1098] Remove stale tradfri devices (#65218) --- homeassistant/components/tradfri/__init__.py | 47 +++++++++++++++++++- tests/components/tradfri/test_init.py | 41 +++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/tradfri/__init__.py b/homeassistant/components/tradfri/__init__.py index 61e2b50d7aabb5..c11b9874bae9dc 100644 --- a/homeassistant/components/tradfri/__init__.py +++ b/homeassistant/components/tradfri/__init__.py @@ -15,9 +15,10 @@ from homeassistant import config_entries from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP -from homeassistant.core import Event, HomeAssistant +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv +import homeassistant.helpers.device_registry as dr from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, @@ -164,6 +165,8 @@ async def on_hass_stop(event: Event) -> None: sw_version=gateway_info.firmware_version, ) + remove_stale_devices(hass, entry, devices) + # Setup the device coordinators coordinator_data = { CONF_GATEWAY_ID: gateway, @@ -231,3 +234,45 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: listener() return unload_ok + + +@callback +def remove_stale_devices( + hass: HomeAssistant, config_entry: ConfigEntry, devices: list[Device] +) -> None: + """Remove stale devices from device registry.""" + device_registry = dr.async_get(hass) + device_entries = dr.async_entries_for_config_entry( + device_registry, config_entry.entry_id + ) + all_device_ids = {device.id for device in devices} + + for device_entry in device_entries: + device_id: str | None = None + gateway_id: str | None = None + + for identifier in device_entry.identifiers: + if identifier[0] != DOMAIN: + continue + + _id = identifier[1] + + # Identify gateway device. + if _id == config_entry.data[CONF_GATEWAY_ID]: + gateway_id = _id + break + + device_id = _id + break + + if gateway_id is not None: + # Do not remove gateway device entry. + continue + + if device_id is None or device_id not in all_device_ids: + # If device_id is None an invalid device entry was found for this config entry. + # If the device_id is not in existing device ids it's a stale device entry. + # Remove config entry from this device entry in either case. + device_registry.async_update_device( + device_entry.id, remove_config_entry_id=config_entry.entry_id + ) diff --git a/tests/components/tradfri/test_init.py b/tests/components/tradfri/test_init.py index f96d1c09050d50..2a26391c43f84e 100644 --- a/tests/components/tradfri/test_init.py +++ b/tests/components/tradfri/test_init.py @@ -49,3 +49,44 @@ async def test_entry_setup_unload(hass, mock_api_factory): await hass.async_block_till_done() assert unload.call_count == len(tradfri.PLATFORMS) assert mock_api_factory.shutdown.call_count == 1 + + +async def test_remove_stale_devices(hass, mock_api_factory): + """Test remove stale device registry entries.""" + entry = MockConfigEntry( + domain=tradfri.DOMAIN, + data={ + tradfri.CONF_HOST: "mock-host", + tradfri.CONF_IDENTITY: "mock-identity", + tradfri.CONF_KEY: "mock-key", + tradfri.CONF_IMPORT_GROUPS: True, + tradfri.CONF_GATEWAY_ID: GATEWAY_ID, + }, + ) + + entry.add_to_hass(hass) + dev_reg = dr.async_get(hass) + dev_reg.async_get_or_create( + config_entry_id=entry.entry_id, + identifiers={(tradfri.DOMAIN, "stale_device_id")}, + ) + dev_entries = dr.async_entries_for_config_entry(dev_reg, entry.entry_id) + + assert len(dev_entries) == 1 + dev_entry = dev_entries[0] + assert dev_entry.identifiers == {(tradfri.DOMAIN, "stale_device_id")} + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + dev_entries = dr.async_entries_for_config_entry(dev_reg, entry.entry_id) + + # Check that only the gateway device entry remains. + assert len(dev_entries) == 1 + dev_entry = dev_entries[0] + assert dev_entry.identifiers == { + (tradfri.DOMAIN, entry.data[tradfri.CONF_GATEWAY_ID]) + } + assert dev_entry.manufacturer == tradfri.ATTR_TRADFRI_MANUFACTURER + assert dev_entry.name == tradfri.ATTR_TRADFRI_GATEWAY + assert dev_entry.model == tradfri.ATTR_TRADFRI_GATEWAY_MODEL From 5289935ac1bbae720e1fe73605499e453b9f1641 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 31 Jan 2022 17:04:46 -0800 Subject: [PATCH 0158/1098] I zone, you zone, we zoning (#65344) --- homeassistant/components/zone/__init__.py | 66 +------------ tests/components/zone/test_init.py | 115 +--------------------- 2 files changed, 7 insertions(+), 174 deletions(-) diff --git a/homeassistant/components/zone/__init__.py b/homeassistant/components/zone/__init__.py index 41fdd8c32d39bc..dd327acbf7582d 100644 --- a/homeassistant/components/zone/__init__.py +++ b/homeassistant/components/zone/__init__.py @@ -1,7 +1,6 @@ """Support for the definition of zones.""" from __future__ import annotations -from collections.abc import Callable import logging from typing import Any, cast @@ -10,7 +9,6 @@ from homeassistant import config_entries from homeassistant.const import ( ATTR_EDITABLE, - ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_ICON, @@ -29,7 +27,6 @@ config_validation as cv, entity, entity_component, - event, service, storage, ) @@ -287,10 +284,7 @@ def __init__(self, config: dict) -> None: """Initialize the zone.""" self._config = config self.editable = True - self._attrs: dict | None = None - self._remove_listener: Callable[[], None] | None = None self._generate_attrs() - self._persons_in_zone: set[str] = set() @classmethod def from_yaml(cls, config: dict) -> Zone: @@ -301,9 +295,9 @@ def from_yaml(cls, config: dict) -> Zone: return zone @property - def state(self) -> int: + def state(self) -> str: """Return the state property really does nothing for a zone.""" - return len(self._persons_in_zone) + return "zoning" @property def name(self) -> str: @@ -320,11 +314,6 @@ def icon(self) -> str | None: """Return the icon if any.""" return self._config.get(CONF_ICON) - @property - def extra_state_attributes(self) -> dict | None: - """Return the state attributes of the zone.""" - return self._attrs - @property def should_poll(self) -> bool: """Zone does not poll.""" @@ -338,59 +327,10 @@ async def async_update_config(self, config: dict) -> None: self._generate_attrs() self.async_write_ha_state() - @callback - def _person_state_change_listener(self, evt: Event) -> None: - person_entity_id = evt.data["entity_id"] - cur_count = len(self._persons_in_zone) - if ( - (state := evt.data["new_state"]) - and (latitude := state.attributes.get(ATTR_LATITUDE)) is not None - and (longitude := state.attributes.get(ATTR_LONGITUDE)) is not None - and (accuracy := state.attributes.get(ATTR_GPS_ACCURACY)) is not None - and ( - zone_state := async_active_zone( - self.hass, latitude, longitude, accuracy - ) - ) - and zone_state.entity_id == self.entity_id - ): - self._persons_in_zone.add(person_entity_id) - elif person_entity_id in self._persons_in_zone: - self._persons_in_zone.remove(person_entity_id) - - if len(self._persons_in_zone) != cur_count: - self.async_write_ha_state() - - async def async_added_to_hass(self) -> None: - """Run when entity about to be added to hass.""" - await super().async_added_to_hass() - person_domain = "person" # avoid circular import - persons = self.hass.states.async_entity_ids(person_domain) - for person in persons: - state = self.hass.states.get(person) - if ( - state is None - or (latitude := state.attributes.get(ATTR_LATITUDE)) is None - or (longitude := state.attributes.get(ATTR_LONGITUDE)) is None - or (accuracy := state.attributes.get(ATTR_GPS_ACCURACY)) is None - ): - continue - zone_state = async_active_zone(self.hass, latitude, longitude, accuracy) - if zone_state is not None and zone_state.entity_id == self.entity_id: - self._persons_in_zone.add(person) - - self.async_on_remove( - event.async_track_state_change_filtered( - self.hass, - event.TrackStates(False, set(), {person_domain}), - self._person_state_change_listener, - ).async_remove - ) - @callback def _generate_attrs(self) -> None: """Generate new attrs based on config.""" - self._attrs = { + self._attr_extra_state_attributes = { ATTR_LATITUDE: self._config[CONF_LATITUDE], ATTR_LONGITUDE: self._config[CONF_LONGITUDE], ATTR_RADIUS: self._config[CONF_RADIUS], diff --git a/tests/components/zone/test_init.py b/tests/components/zone/test_init.py index 54cb87aa772cbc..8d0fddb921c87c 100644 --- a/tests/components/zone/test_init.py +++ b/tests/components/zone/test_init.py @@ -316,7 +316,7 @@ async def test_load_from_storage(hass, storage_setup): """Test set up from storage.""" assert await storage_setup() state = hass.states.get(f"{DOMAIN}.from_storage") - assert state.state == "0" + assert state.state == "zoning" assert state.name == "from storage" assert state.attributes.get(ATTR_EDITABLE) @@ -328,12 +328,12 @@ async def test_editable_state_attribute(hass, storage_setup): ) state = hass.states.get(f"{DOMAIN}.from_storage") - assert state.state == "0" + assert state.state == "zoning" assert state.attributes.get(ATTR_FRIENDLY_NAME) == "from storage" assert state.attributes.get(ATTR_EDITABLE) state = hass.states.get(f"{DOMAIN}.yaml_option") - assert state.state == "0" + assert state.state == "zoning" assert not state.attributes.get(ATTR_EDITABLE) @@ -457,7 +457,7 @@ async def test_ws_create(hass, hass_ws_client, storage_setup): assert resp["success"] state = hass.states.get(input_entity_id) - assert state.state == "0" + assert state.state == "zoning" assert state.attributes["latitude"] == 3 assert state.attributes["longitude"] == 4 assert state.attributes["passive"] is True @@ -503,110 +503,3 @@ async def test_unavailable_zone(hass): assert zone.async_active_zone(hass, 0.0, 0.01) is None assert zone.in_zone(hass.states.get("zone.bla"), 0, 0) is False - - -async def test_state(hass): - """Test the state of a zone.""" - info = { - "name": "Test Zone", - "latitude": 32.880837, - "longitude": -117.237561, - "radius": 250, - "passive": False, - } - assert await setup.async_setup_component(hass, zone.DOMAIN, {"zone": info}) - - assert len(hass.states.async_entity_ids("zone")) == 2 - state = hass.states.get("zone.test_zone") - assert state.state == "0" - - # Person entity enters zone - hass.states.async_set( - "person.person1", - "Test Zone", - {"latitude": 32.880837, "longitude": -117.237561, "gps_accuracy": 0}, - ) - await hass.async_block_till_done() - assert hass.states.get("zone.test_zone").state == "1" - assert hass.states.get("zone.home").state == "0" - - # Person entity enters zone - hass.states.async_set( - "person.person2", - "Test Zone", - {"latitude": 32.880837, "longitude": -117.237561, "gps_accuracy": 0}, - ) - await hass.async_block_till_done() - assert hass.states.get("zone.test_zone").state == "2" - assert hass.states.get("zone.home").state == "0" - - # Person entity enters another zone - hass.states.async_set( - "person.person1", - "home", - {"latitude": 32.87336, "longitude": -117.22743, "gps_accuracy": 0}, - ) - await hass.async_block_till_done() - assert hass.states.get("zone.test_zone").state == "1" - assert hass.states.get("zone.home").state == "1" - - # Person entity removed - hass.states.async_remove("person.person2") - await hass.async_block_till_done() - assert hass.states.get("zone.test_zone").state == "0" - assert hass.states.get("zone.home").state == "1" - - -async def test_state_2(hass): - """Test the state of a zone.""" - hass.states.async_set("person.person1", "unknown") - hass.states.async_set("person.person2", "unknown") - - info = { - "name": "Test Zone", - "latitude": 32.880837, - "longitude": -117.237561, - "radius": 250, - "passive": False, - } - assert await setup.async_setup_component(hass, zone.DOMAIN, {"zone": info}) - - assert len(hass.states.async_entity_ids("zone")) == 2 - state = hass.states.get("zone.test_zone") - assert state.state == "0" - - # Person entity enters zone - hass.states.async_set( - "person.person1", - "Test Zone", - {"latitude": 32.880837, "longitude": -117.237561, "gps_accuracy": 0}, - ) - await hass.async_block_till_done() - assert hass.states.get("zone.test_zone").state == "1" - assert hass.states.get("zone.home").state == "0" - - # Person entity enters zone - hass.states.async_set( - "person.person2", - "Test Zone", - {"latitude": 32.880837, "longitude": -117.237561, "gps_accuracy": 0}, - ) - await hass.async_block_till_done() - assert hass.states.get("zone.test_zone").state == "2" - assert hass.states.get("zone.home").state == "0" - - # Person entity enters another zone - hass.states.async_set( - "person.person1", - "home", - {"latitude": 32.87336, "longitude": -117.22743, "gps_accuracy": 0}, - ) - await hass.async_block_till_done() - assert hass.states.get("zone.test_zone").state == "1" - assert hass.states.get("zone.home").state == "1" - - # Person entity removed - hass.states.async_remove("person.person2") - await hass.async_block_till_done() - assert hass.states.get("zone.test_zone").state == "0" - assert hass.states.get("zone.home").state == "1" From 88ed2f3b3ed5a52ad5c94e170d981520940e977e Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 31 Jan 2022 18:14:49 -0800 Subject: [PATCH 0159/1098] Improve google calendar test coverage to 97% (#65223) * Improve google calendar test coverage to 97% * Remove commented out code. * Remove unnecessary (flaky) checks for token file persistence * Remove mock code assertions * Add debug logging to google calendar integration * Increase alarm time to polling code to reduce flakes * Setup every test in their own configuration directory * Mock out filesystem calls to avoid disk dependencies Update scope checking code to use Storage object rather than text file matching * Update tests to check entity states when integration is loaded * Mock out google service in multiple locations * Update homeassistant/components/google/__init__.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/google/__init__.py Co-authored-by: Martin Hjelmare Co-authored-by: Martin Hjelmare --- .coveragerc | 1 - homeassistant/components/google/__init__.py | 23 +- tests/components/google/conftest.py | 56 ++- tests/components/google/test_calendar.py | 17 - tests/components/google/test_init.py | 522 ++++++++++++++++++-- 5 files changed, 542 insertions(+), 77 deletions(-) diff --git a/.coveragerc b/.coveragerc index 9c1c8733ab4d6d..a7baa17e852f53 100644 --- a/.coveragerc +++ b/.coveragerc @@ -407,7 +407,6 @@ omit = homeassistant/components/goodwe/number.py homeassistant/components/goodwe/select.py homeassistant/components/goodwe/sensor.py - homeassistant/components/google/__init__.py homeassistant/components/google_cloud/tts.py homeassistant/components/google_maps/device_tracker.py homeassistant/components/google_pubsub/__init__.py diff --git a/homeassistant/components/google/__init__.py b/homeassistant/components/google/__init__.py index 0dfacb13e740f1..05bfb490cb8a38 100644 --- a/homeassistant/components/google/__init__.py +++ b/homeassistant/components/google/__init__.py @@ -194,6 +194,7 @@ def do_authentication(hass, hass_config, config): def step2_exchange(now): """Keep trying to validate the user_code until it expires.""" + _LOGGER.debug("Attempting to validate user code") # For some reason, oauth.step1_get_device_and_user_codes() returns a datetime # object without tzinfo. For the comparison below to work, it needs one. @@ -208,6 +209,7 @@ def step2_exchange(now): notification_id=NOTIFICATION_ID, ) listener() + return try: credentials = oauth.step2_exchange(device_flow_info=dev_flow) @@ -247,9 +249,11 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: token_file = hass.config.path(TOKEN_FILE) if not os.path.isfile(token_file): + _LOGGER.debug("Token file does not exist, authenticating for first time") do_authentication(hass, config, conf) else: - if not check_correct_scopes(token_file, conf): + if not check_correct_scopes(hass, token_file, conf): + _LOGGER.debug("Existing scopes are not sufficient, re-authenticating") do_authentication(hass, config, conf) else: do_setup(hass, config, conf) @@ -257,17 +261,13 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: return True -def check_correct_scopes(token_file, config): +def check_correct_scopes(hass, token_file, config): """Check for the correct scopes in file.""" - with open(token_file, encoding="utf8") as tokenfile: - contents = tokenfile.read() - - # Check for quoted scope as our scopes can be subsets of other scopes - target_scope = f'"{config.get(CONF_CALENDAR_ACCESS).scope}"' - if target_scope not in contents: - _LOGGER.warning("Please re-authenticate with Google") - return False - return True + creds = Storage(token_file).get() + if not creds or not creds.scopes: + return False + target_scope = config[CONF_CALENDAR_ACCESS].scope + return target_scope in creds.scopes def setup_services( @@ -364,6 +364,7 @@ def _add_event(call: ServiceCall) -> None: def do_setup(hass, hass_config, config): """Run the setup after we have everything configured.""" + _LOGGER.debug("Setting up integration") # Load calendars the user has configured hass.data[DATA_INDEX] = load_config(hass.config.path(YAML_DEVICES)) diff --git a/tests/components/google/conftest.py b/tests/components/google/conftest.py index 20cb13130ec63f..59de9b2cddf9bf 100644 --- a/tests/components/google/conftest.py +++ b/tests/components/google/conftest.py @@ -1,10 +1,20 @@ """Test configuration and mocks for the google integration.""" -from unittest.mock import patch +from collections.abc import Callable +from typing import Any, Generator, TypeVar +from unittest.mock import Mock, patch import pytest +from homeassistant.components.google import GoogleCalendarService + +ApiResult = Callable[[dict[str, Any]], None] +T = TypeVar("T") +YieldFixture = Generator[T, None, None] + + +CALENDAR_ID = "qwertyuiopasdfghjklzxcvbnm@import.calendar.google.com" TEST_CALENDAR = { - "id": "qwertyuiopasdfghjklzxcvbnm@import.calendar.google.com", + "id": CALENDAR_ID, "etag": '"3584134138943410"', "timeZone": "UTC", "accessRole": "reader", @@ -34,3 +44,45 @@ def mock_next_event(): ) with patch_google_cal as google_cal_data: yield google_cal_data + + +@pytest.fixture +def mock_events_list( + google_service: GoogleCalendarService, +) -> Callable[[dict[str, Any]], None]: + """Fixture to construct a fake event list API response.""" + + def _put_result(response: dict[str, Any]) -> None: + google_service.return_value.get.return_value.events.return_value.list.return_value.execute.return_value = ( + response + ) + return + + return _put_result + + +@pytest.fixture +def mock_calendars_list( + google_service: GoogleCalendarService, +) -> ApiResult: + """Fixture to construct a fake calendar list API response.""" + + def _put_result(response: dict[str, Any]) -> None: + google_service.return_value.get.return_value.calendarList.return_value.list.return_value.execute.return_value = ( + response + ) + return + + return _put_result + + +@pytest.fixture +def mock_insert_event( + google_service: GoogleCalendarService, +) -> Mock: + """Fixture to create a mock to capture new events added to the API.""" + insert_mock = Mock() + google_service.return_value.get.return_value.events.return_value.insert = ( + insert_mock + ) + return insert_mock diff --git a/tests/components/google/test_calendar.py b/tests/components/google/test_calendar.py index 01bd179e2ea111..0ee257788dd0c9 100644 --- a/tests/components/google/test_calendar.py +++ b/tests/components/google/test_calendar.py @@ -2,7 +2,6 @@ from __future__ import annotations -from collections.abc import Callable import copy from http import HTTPStatus from typing import Any @@ -22,7 +21,6 @@ CONF_TRACK, DEVICE_SCHEMA, SERVICE_SCAN_CALENDARS, - GoogleCalendarService, do_setup, ) from homeassistant.const import STATE_OFF, STATE_ON @@ -358,21 +356,6 @@ async def test_http_event_api_failure(hass, hass_client, google_service): assert events == [] -@pytest.fixture -def mock_events_list( - google_service: GoogleCalendarService, -) -> Callable[[dict[str, Any]], None]: - """Fixture to construct a fake event list API response.""" - - def _put_result(response: dict[str, Any]) -> None: - google_service.return_value.get.return_value.events.return_value.list.return_value.execute.return_value = ( - response - ) - return - - return _put_result - - async def test_http_api_event(hass, hass_client, google_service, mock_events_list): """Test querying the API and fetching events from the server.""" now = dt_util.now() diff --git a/tests/components/google/test_init.py b/tests/components/google/test_init.py index d90efa29f6c8ae..c3754511b048eb 100644 --- a/tests/components/google/test_init.py +++ b/tests/components/google/test_init.py @@ -1,67 +1,497 @@ """The tests for the Google Calendar component.""" -from unittest.mock import patch +from collections.abc import Awaitable, Callable +import datetime +from typing import Any +from unittest.mock import Mock, call, mock_open, patch +from oauth2client.client import ( + FlowExchangeError, + OAuth2Credentials, + OAuth2DeviceCodeError, +) import pytest +import yaml -import homeassistant.components.google as google -from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET +from homeassistant.components.google import ( + DOMAIN, + SERVICE_ADD_EVENT, + GoogleCalendarService, +) +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, STATE_OFF +from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component +from homeassistant.util.dt import utcnow +from .conftest import CALENDAR_ID, ApiResult, YieldFixture -@pytest.fixture(name="google_setup") -def mock_google_setup(hass): - """Mock the google set up functions.""" - p_auth = patch( - "homeassistant.components.google.do_authentication", side_effect=google.do_setup +from tests.common import async_fire_time_changed + +# Typing helpers +ComponentSetup = Callable[[], Awaitable[bool]] +HassApi = Callable[[], Awaitable[dict[str, Any]]] + +CODE_CHECK_INTERVAL = 1 +CODE_CHECK_ALARM_TIMEDELTA = datetime.timedelta(seconds=CODE_CHECK_INTERVAL * 2) + + +@pytest.fixture +async def code_expiration_delta() -> datetime.timedelta: + """Fixture for code expiration time, defaulting to the future.""" + return datetime.timedelta(minutes=3) + + +@pytest.fixture +async def mock_code_flow( + code_expiration_delta: datetime.timedelta, +) -> YieldFixture[Mock]: + """Fixture for initiating OAuth flow.""" + with patch( + "oauth2client.client.OAuth2WebServerFlow.step1_get_device_and_user_codes", + ) as mock_flow: + mock_flow.return_value.user_code_expiry = utcnow() + code_expiration_delta + mock_flow.return_value.interval = CODE_CHECK_INTERVAL + yield mock_flow + + +@pytest.fixture +async def token_scopes() -> list[str]: + """Fixture for scopes used during test.""" + return ["https://www.googleapis.com/auth/calendar"] + + +@pytest.fixture +async def creds(token_scopes: list[str]) -> OAuth2Credentials: + """Fixture that defines creds used in the test.""" + token_expiry = utcnow() + datetime.timedelta(days=7) + return OAuth2Credentials( + access_token="ACCESS_TOKEN", + client_id="client-id", + client_secret="client-secret", + refresh_token="REFRESH_TOKEN", + token_expiry=token_expiry, + token_uri="http://example.com", + user_agent="n/a", + scopes=token_scopes, ) - p_service = patch("homeassistant.components.google.GoogleCalendarService.get") - p_discovery = patch("homeassistant.components.google.discovery.load_platform") - p_load = patch("homeassistant.components.google.load_config", return_value={}) - p_save = patch("homeassistant.components.google.update_config") - with p_auth, p_load, p_service, p_discovery, p_save: + +@pytest.fixture +async def mock_exchange(creds: OAuth2Credentials) -> YieldFixture[Mock]: + """Fixture for mocking out the exchange for credentials.""" + with patch( + "oauth2client.client.OAuth2WebServerFlow.step2_exchange", return_value=creds + ) as mock: + yield mock + + +@pytest.fixture(autouse=True) +async def mock_token_write(hass: HomeAssistant) -> None: + """Fixture to avoid writing token files to disk.""" + with patch( + "homeassistant.components.google.os.path.isfile", return_value=True + ), patch("homeassistant.components.google.Storage.put"): + yield + + +@pytest.fixture +async def mock_token_read( + hass: HomeAssistant, + creds: OAuth2Credentials, +) -> None: + """Fixture to populate an existing token file.""" + with patch("homeassistant.components.google.Storage.get", return_value=creds): yield -async def test_setup_component(hass, google_setup): - """Test setup component.""" - config = {"google": {CONF_CLIENT_ID: "id", CONF_CLIENT_SECRET: "secret"}} +@pytest.fixture +async def calendars_config() -> list[dict[str, Any]]: + """Fixture for tests to override default calendar configuration.""" + return [ + { + "cal_id": CALENDAR_ID, + "entities": [ + { + "device_id": "backyard_light", + "name": "Backyard Light", + "search": "#Backyard", + "track": True, + } + ], + } + ] + + +@pytest.fixture +async def mock_calendars_yaml( + hass: HomeAssistant, + calendars_config: list[dict[str, Any]], +) -> None: + """Fixture that prepares the calendars.yaml file.""" + mocked_open_function = mock_open(read_data=yaml.dump(calendars_config)) + with patch("homeassistant.components.google.open", mocked_open_function): + yield + + +@pytest.fixture +async def mock_notification() -> YieldFixture[Mock]: + """Fixture for capturing persistent notifications.""" + with patch("homeassistant.components.persistent_notification.create") as mock: + yield mock + + +@pytest.fixture +async def config() -> dict[str, Any]: + """Fixture for overriding component config.""" + return {DOMAIN: {CONF_CLIENT_ID: "client-id", CONF_CLIENT_SECRET: "client-ecret"}} + + +@pytest.fixture +async def component_setup( + hass: HomeAssistant, config: dict[str, Any] +) -> ComponentSetup: + """Fixture for setting up the integration.""" + + async def _setup_func() -> bool: + result = await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() + return result + + return _setup_func + + +@pytest.fixture +async def google_service() -> YieldFixture[GoogleCalendarService]: + """Fixture to capture service calls.""" + with patch("homeassistant.components.google.GoogleCalendarService") as mock, patch( + "homeassistant.components.google.calendar.GoogleCalendarService", mock + ): + yield mock + + +async def fire_alarm(hass, point_in_time): + """Fire an alarm and wait for callbacks to run.""" + with patch("homeassistant.util.dt.utcnow", return_value=point_in_time): + async_fire_time_changed(hass, point_in_time) + await hass.async_block_till_done() + + +@pytest.mark.parametrize("config", [{}]) +async def test_setup_config_empty( + hass: HomeAssistant, + component_setup: ComponentSetup, + mock_notification: Mock, +): + """Test setup component with an empty configuruation.""" + assert await component_setup() + + mock_notification.assert_not_called() + + assert not hass.states.get("calendar.backyard_light") + + +async def test_init_success( + hass: HomeAssistant, + google_service: GoogleCalendarService, + mock_code_flow: Mock, + mock_exchange: Mock, + mock_notification: Mock, + mock_calendars_yaml: None, + component_setup: ComponentSetup, +) -> None: + """Test successful creds setup.""" + assert await component_setup() + + # Run one tick to invoke the credential exchange check + now = utcnow() + await fire_alarm(hass, now + CODE_CHECK_ALARM_TIMEDELTA) + + state = hass.states.get("calendar.backyard_light") + assert state + assert state.name == "Backyard Light" + assert state.state == STATE_OFF + + mock_notification.assert_called() + assert "We are all setup now" in mock_notification.call_args[0][1] + + +async def test_code_error( + hass: HomeAssistant, + mock_code_flow: Mock, + component_setup: ComponentSetup, + mock_notification: Mock, +) -> None: + """Test loading the integration with no existing credentials.""" + + with patch( + "oauth2client.client.OAuth2WebServerFlow.step1_get_device_and_user_codes", + side_effect=OAuth2DeviceCodeError("Test Failure"), + ): + assert await component_setup() + + assert not hass.states.get("calendar.backyard_light") + + mock_notification.assert_called() + assert "Error: Test Failure" in mock_notification.call_args[0][1] - assert await async_setup_component(hass, "google", config) +@pytest.mark.parametrize("code_expiration_delta", [datetime.timedelta(minutes=-5)]) +async def test_expired_after_exchange( + hass: HomeAssistant, + mock_code_flow: Mock, + component_setup: ComponentSetup, + mock_notification: Mock, +) -> None: + """Test loading the integration with no existing credentials.""" -async def test_get_calendar_info(hass, test_calendar): - """Test getting the calendar info.""" - calendar_info = await hass.async_add_executor_job( - google.get_calendar_info, hass, test_calendar + assert await component_setup() + + now = utcnow() + await fire_alarm(hass, now + CODE_CHECK_ALARM_TIMEDELTA) + + assert not hass.states.get("calendar.backyard_light") + + mock_notification.assert_called() + assert ( + "Authentication code expired, please restart Home-Assistant and try again" + in mock_notification.call_args[0][1] ) - assert calendar_info == { - "cal_id": "qwertyuiopasdfghjklzxcvbnm@import.calendar.google.com", - "entities": [ + + +async def test_exchange_error( + hass: HomeAssistant, + mock_code_flow: Mock, + component_setup: ComponentSetup, + mock_notification: Mock, +) -> None: + """Test an error while exchanging the code for credentials.""" + + with patch( + "oauth2client.client.OAuth2WebServerFlow.step2_exchange", + side_effect=FlowExchangeError(), + ): + assert await component_setup() + + now = utcnow() + await fire_alarm(hass, now + CODE_CHECK_ALARM_TIMEDELTA) + + assert not hass.states.get("calendar.backyard_light") + + mock_notification.assert_called() + assert "In order to authorize Home-Assistant" in mock_notification.call_args[0][1] + + +async def test_existing_token( + hass: HomeAssistant, + mock_token_read: None, + component_setup: ComponentSetup, + google_service: GoogleCalendarService, + mock_calendars_yaml: None, + mock_notification: Mock, +) -> None: + """Test setup with an existing token file.""" + assert await component_setup() + + state = hass.states.get("calendar.backyard_light") + assert state + assert state.name == "Backyard Light" + assert state.state == STATE_OFF + + mock_notification.assert_not_called() + + +@pytest.mark.parametrize( + "token_scopes", ["https://www.googleapis.com/auth/calendar.readonly"] +) +async def test_existing_token_missing_scope( + hass: HomeAssistant, + token_scopes: list[str], + mock_token_read: None, + component_setup: ComponentSetup, + google_service: GoogleCalendarService, + mock_calendars_yaml: None, + mock_notification: Mock, + mock_code_flow: Mock, + mock_exchange: Mock, +) -> None: + """Test setup where existing token does not have sufficient scopes.""" + assert await component_setup() + + # Run one tick to invoke the credential exchange check + now = utcnow() + await fire_alarm(hass, now + CODE_CHECK_ALARM_TIMEDELTA) + assert len(mock_exchange.mock_calls) == 1 + + state = hass.states.get("calendar.backyard_light") + assert state + assert state.name == "Backyard Light" + assert state.state == STATE_OFF + + # No notifications on success + mock_notification.assert_called() + assert "We are all setup now" in mock_notification.call_args[0][1] + + +@pytest.mark.parametrize("calendars_config", [[{"cal_id": "invalid-schema"}]]) +async def test_calendar_yaml_missing_required_fields( + hass: HomeAssistant, + mock_token_read: None, + component_setup: ComponentSetup, + google_service: GoogleCalendarService, + calendars_config: list[dict[str, Any]], + mock_calendars_yaml: None, + mock_notification: Mock, +) -> None: + """Test setup with a missing schema fields, ignores the error and continues.""" + assert await component_setup() + + assert not hass.states.get("calendar.backyard_light") + + mock_notification.assert_not_called() + + +@pytest.mark.parametrize("calendars_config", [[{"missing-cal_id": "invalid-schema"}]]) +async def test_invalid_calendar_yaml( + hass: HomeAssistant, + mock_token_read: None, + component_setup: ComponentSetup, + google_service: GoogleCalendarService, + calendars_config: list[dict[str, Any]], + mock_calendars_yaml: None, + mock_notification: Mock, +) -> None: + """Test setup with missing entity id fields fails to setup the integration.""" + + # Integration fails to setup + assert not await component_setup() + + assert not hass.states.get("calendar.backyard_light") + + mock_notification.assert_not_called() + + +async def test_found_calendar_from_api( + hass: HomeAssistant, + mock_token_read: None, + component_setup: ComponentSetup, + google_service: GoogleCalendarService, + mock_calendars_list: ApiResult, + test_calendar: dict[str, Any], +) -> None: + """Test finding a calendar from the API.""" + + mock_calendars_list({"items": [test_calendar]}) + + mocked_open_function = mock_open(read_data=yaml.dump([])) + with patch("homeassistant.components.google.open", mocked_open_function): + assert await component_setup() + + state = hass.states.get("calendar.we_are_we_are_a_test_calendar") + assert state + assert state.name == "We are, we are, a... Test Calendar" + assert state.state == STATE_OFF + + +async def test_add_event( + hass: HomeAssistant, + mock_token_read: None, + component_setup: ComponentSetup, + google_service: GoogleCalendarService, + mock_calendars_list: ApiResult, + test_calendar: dict[str, Any], + mock_insert_event: Mock, +) -> None: + """Test service call that adds an event.""" + + assert await component_setup() + + await hass.services.async_call( + DOMAIN, + SERVICE_ADD_EVENT, + { + "calendar_id": CALENDAR_ID, + "summary": "Summary", + "description": "Description", + }, + blocking=True, + ) + mock_insert_event.assert_called() + assert mock_insert_event.mock_calls[0] == call( + calendarId=CALENDAR_ID, + body={ + "summary": "Summary", + "description": "Description", + "start": {}, + "end": {}, + }, + ) + + +@pytest.mark.parametrize( + "date_fields,start_timedelta,end_timedelta", + [ + ( + {"in": {"days": 3}}, + datetime.timedelta(days=3), + datetime.timedelta(days=4), + ), + ( + {"in": {"weeks": 1}}, + datetime.timedelta(days=7), + datetime.timedelta(days=8), + ), + ( { - "device_id": "we_are_we_are_a_test_calendar", - "name": "We are, we are, a... Test Calendar", - "track": True, - "ignore_availability": True, - } - ], - } - - -async def test_found_calendar(hass, google_setup, mock_next_event, test_calendar): - """Test when a calendar is found.""" - config = { - "google": { - CONF_CLIENT_ID: "id", - CONF_CLIENT_SECRET: "secret", - "track_new_calendar": True, - } - } - assert await async_setup_component(hass, "google", config) - assert hass.data[google.DATA_INDEX] == {} + "start_date": datetime.date.today().isoformat(), + "end_date": ( + datetime.date.today() + datetime.timedelta(days=2) + ).isoformat(), + }, + datetime.timedelta(days=0), + datetime.timedelta(days=2), + ), + ], + ids=["in_days", "in_weeks", "explit_date"], +) +async def test_add_event_date_ranges( + hass: HomeAssistant, + mock_token_read: None, + calendars_config: list[dict[str, Any]], + component_setup: ComponentSetup, + google_service: GoogleCalendarService, + mock_calendars_list: ApiResult, + test_calendar: dict[str, Any], + mock_insert_event: Mock, + date_fields: dict[str, Any], + start_timedelta: datetime.timedelta, + end_timedelta: datetime.timedelta, +) -> None: + """Test service call that adds an event with various time ranges.""" + + assert await component_setup() await hass.services.async_call( - "google", google.SERVICE_FOUND_CALENDARS, test_calendar, blocking=True + DOMAIN, + SERVICE_ADD_EVENT, + { + "calendar_id": CALENDAR_ID, + "summary": "Summary", + "description": "Description", + **date_fields, + }, + blocking=True, ) + mock_insert_event.assert_called() - assert hass.data[google.DATA_INDEX].get(test_calendar["id"]) is not None + now = datetime.datetime.now() + start_date = now + start_timedelta + end_date = now + end_timedelta + + assert mock_insert_event.mock_calls[0] == call( + calendarId=CALENDAR_ID, + body={ + "summary": "Summary", + "description": "Description", + "start": {"date": start_date.date().isoformat()}, + "end": {"date": end_date.date().isoformat()}, + }, + ) From b05b4c4b3836cce84a63de0f5746ed231a1166cf Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 31 Jan 2022 22:22:12 -0800 Subject: [PATCH 0160/1098] Simplify unifi cleanup logic (#65345) --- .../components/unifi/unifi_entity_base.py | 30 +++---------- tests/components/unifi/test_device_tracker.py | 45 +------------------ 2 files changed, 6 insertions(+), 69 deletions(-) diff --git a/homeassistant/components/unifi/unifi_entity_base.py b/homeassistant/components/unifi/unifi_entity_base.py index 1c3251d213c147..c611fc1ee603ba 100644 --- a/homeassistant/components/unifi/unifi_entity_base.py +++ b/homeassistant/components/unifi/unifi_entity_base.py @@ -3,7 +3,7 @@ from typing import Any from homeassistant.core import callback -from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity @@ -78,34 +78,14 @@ async def options_updated(self) -> None: raise NotImplementedError async def remove_item(self, keys: set) -> None: - """Remove entity if key is part of set. - - Remove entity if no entry in entity registry exist. - Remove entity registry entry if no entry in device registry exist. - Remove device registry entry if there is only one linked entity (this entity). - Remove config entry reference from device registry entry if there is more than one config entry. - Remove entity registry entry if there are more than one entity linked to the device registry entry. - """ + """Remove entity if key is part of set.""" if self.key not in keys: return - entity_registry = er.async_get(self.hass) - entity_entry = entity_registry.async_get(self.entity_id) - if not entity_entry: + if self.registry_entry: + er.async_get(self.hass).async_remove(self.entity_id) + else: await self.async_remove(force_remove=True) - return - - device_registry = dr.async_get(self.hass) - device_entry = device_registry.async_get(entity_entry.device_id) - if not device_entry: - entity_registry.async_remove(self.entity_id) - return - - device_registry.async_update_device( - entity_entry.device_id, - remove_config_entry_id=self.controller.config_entry.entry_id, - ) - entity_registry.async_remove(self.entity_id) @property def should_poll(self) -> bool: diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index cf4861980a02b6..b490d43fffdf5f 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -18,7 +18,7 @@ DOMAIN as UNIFI_DOMAIN, ) from homeassistant.const import STATE_HOME, STATE_NOT_HOME, STATE_UNAVAILABLE -from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers import entity_registry as er import homeassistant.util.dt as dt_util from .test_controller import ENTRY_CONFIG, setup_unifi_integration @@ -317,49 +317,6 @@ async def test_remove_clients( assert hass.states.get("device_tracker.client_2") -async def test_remove_client_but_keep_device_entry( - hass, aioclient_mock, mock_unifi_websocket, mock_device_registry -): - """Test that unifi entity base remove config entry id from a multi integration device registry entry.""" - client_1 = { - "essid": "ssid", - "hostname": "client_1", - "is_wired": False, - "last_seen": 1562600145, - "mac": "00:00:00:00:00:01", - } - await setup_unifi_integration(hass, aioclient_mock, clients_response=[client_1]) - - device_registry = dr.async_get(hass) - device_entry = device_registry.async_get_or_create( - config_entry_id="other", - connections={("mac", "00:00:00:00:00:01")}, - ) - - entity_registry = er.async_get(hass) - other_entity = entity_registry.async_get_or_create( - TRACKER_DOMAIN, - "other", - "unique_id", - device_id=device_entry.id, - ) - assert len(device_entry.config_entries) == 3 - - mock_unifi_websocket( - data={ - "meta": {"message": MESSAGE_CLIENT_REMOVED}, - "data": [client_1], - } - ) - await hass.async_block_till_done() - await hass.async_block_till_done() - - assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 0 - - device_entry = device_registry.async_get(other_entity.device_id) - assert len(device_entry.config_entries) == 2 - - async def test_controller_state_change( hass, aioclient_mock, mock_unifi_websocket, mock_device_registry ): From 2a193e1016ccfbb8776a7589ff73711f9e1f3bb9 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Tue, 1 Feb 2022 07:38:42 +0000 Subject: [PATCH 0161/1098] Refactor platform loading in homekit_controller (#65338) --- .../homekit_controller/connection.py | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index e31296258e4081..7b5f7114170152 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -11,7 +11,6 @@ from aiohomekit.model import Accessories, Accessory from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes -from aiohomekit.uuid import normalize_uuid from homeassistant.const import ATTR_VIA_DEVICE from homeassistant.core import callback @@ -493,21 +492,16 @@ async def async_load_platform(self, platform): async def async_load_platforms(self): """Load any platforms needed by this HomeKit device.""" tasks = [] - for accessory in self.accessories: - for service in accessory["services"]: - try: - stype = normalize_uuid(service["type"]) - except KeyError: - stype = service["type"].upper() - - if stype in HOMEKIT_ACCESSORY_DISPATCH: - platform = HOMEKIT_ACCESSORY_DISPATCH[stype] + for accessory in self.entity_map.accessories: + for service in accessory.services: + if service.type in HOMEKIT_ACCESSORY_DISPATCH: + platform = HOMEKIT_ACCESSORY_DISPATCH[service.type] if platform not in self.platforms: tasks.append(self.async_load_platform(platform)) - for char in service["characteristics"]: - if char["type"].upper() in CHARACTERISTIC_PLATFORMS: - platform = CHARACTERISTIC_PLATFORMS[char["type"].upper()] + for char in service.characteristics: + if char.type in CHARACTERISTIC_PLATFORMS: + platform = CHARACTERISTIC_PLATFORMS[char.type] if platform not in self.platforms: tasks.append(self.async_load_platform(platform)) From ab17f8984be55126a267bf6269612e8f64890536 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 1 Feb 2022 10:47:12 +0100 Subject: [PATCH 0162/1098] Improve CastProtocol (#65357) * Improve CastProtocol * Tweak --- homeassistant/components/cast/__init__.py | 2 +- homeassistant/components/cast/media_player.py | 2 +- homeassistant/components/plex/cast.py | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/cast/__init__.py b/homeassistant/components/cast/__init__.py index cb631e17ccdf06..86e5557160cb4f 100644 --- a/homeassistant/components/cast/__init__.py +++ b/homeassistant/components/cast/__init__.py @@ -67,7 +67,7 @@ class CastProtocol(Protocol): """Define the format of cast platforms.""" async def async_get_media_browser_root_object( - self, cast_type: str + self, hass: HomeAssistant, cast_type: str ) -> list[BrowseMedia]: """Create a list of root objects for media browsing.""" diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 5f3381896dac24..975fa3f58362b0 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -463,7 +463,7 @@ async def _async_root_payload(self, content_filter): for platform in self.hass.data[CAST_DOMAIN].values(): children.extend( await platform.async_get_media_browser_root_object( - self._chromecast.cast_type + self.hass, self._chromecast.cast_type ) ) diff --git a/homeassistant/components/plex/cast.py b/homeassistant/components/plex/cast.py index c2a09ff8810766..59f23a681f814a 100644 --- a/homeassistant/components/plex/cast.py +++ b/homeassistant/components/plex/cast.py @@ -14,7 +14,9 @@ from .services import lookup_plex_media -async def async_get_media_browser_root_object(cast_type: str) -> list[BrowseMedia]: +async def async_get_media_browser_root_object( + hass: HomeAssistant, cast_type: str +) -> list[BrowseMedia]: """Create a root object for media browsing.""" return [ BrowseMedia( From dd5bcafab7f68ac726f199af32bd63ed25e37919 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Tue, 1 Feb 2022 11:27:35 +0000 Subject: [PATCH 0163/1098] Enable mypy checks for homekit_controller (#65358) --- .../components/homekit_controller/__init__.py | 10 ++-------- .../components/homekit_controller/config_flow.py | 4 ++++ .../components/homekit_controller/device_trigger.py | 2 +- .../components/homekit_controller/diagnostics.py | 8 +++++--- homeassistant/components/homekit_controller/number.py | 2 +- homeassistant/components/homekit_controller/switch.py | 2 +- mypy.ini | 3 --- script/hassfest/mypy_config.py | 1 - 8 files changed, 14 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index 0f231fa93038ca..ed65a83a1e7b77 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -48,8 +48,6 @@ def __init__(self, accessory: HKDevice, devinfo): self._features = 0 self.setup() - self._signals = [] - super().__init__() @property @@ -71,7 +69,7 @@ def service(self) -> Service: async def async_added_to_hass(self): """Entity added to hass.""" - self._signals.append( + self.async_on_remove( self.hass.helpers.dispatcher.async_dispatcher_connect( self._accessory.signal_state_updated, self.async_write_ha_state ) @@ -85,10 +83,6 @@ async def async_will_remove_from_hass(self): self._accessory.remove_pollable_characteristics(self._aid) self._accessory.remove_watchable_characteristics(self._aid) - for signal_remove in self._signals: - signal_remove() - self._signals.clear() - async def async_put_characteristics(self, characteristics: dict[str, Any]): """ Write characteristics to the device. @@ -145,7 +139,7 @@ def unique_id(self) -> str: return f"homekit-{self._accessory.unique_id}-{self._aid}-{self._iid}" @property - def name(self) -> str: + def name(self) -> str | None: """Return the name of the device if any.""" return self.accessory_info.value(CharacteristicsTypes.NAME) diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index 18f19265f5969c..6a2200b69eb411 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -280,9 +280,13 @@ async def async_step_zeroconf( if self.controller is None: await self._async_setup_controller() + # mypy can't see that self._async_setup_controller() always sets self.controller or throws + assert self.controller + pairing = self.controller.load_pairing( existing.data["AccessoryPairingID"], dict(existing.data) ) + try: await pairing.list_accessories_and_characteristics() except AuthenticationError: diff --git a/homeassistant/components/homekit_controller/device_trigger.py b/homeassistant/components/homekit_controller/device_trigger.py index 0ad64723478198..a69d189ebd5f04 100644 --- a/homeassistant/components/homekit_controller/device_trigger.py +++ b/homeassistant/components/homekit_controller/device_trigger.py @@ -76,7 +76,7 @@ def async_get_triggers(self): async def async_attach_trigger( self, - config: TRIGGER_SCHEMA, + config: ConfigType, action: AutomationActionType, automation_info: AutomationTriggerInfo, ) -> CALLBACK_TYPE: diff --git a/homeassistant/components/homekit_controller/diagnostics.py b/homeassistant/components/homekit_controller/diagnostics.py index e5183b7be319fc..fd404636a220af 100644 --- a/homeassistant/components/homekit_controller/diagnostics.py +++ b/homeassistant/components/homekit_controller/diagnostics.py @@ -44,7 +44,7 @@ async def async_get_device_diagnostics( def _async_get_diagnostics_for_device( hass: HomeAssistant, device: DeviceEntry ) -> dict[str, Any]: - data = {} + data: dict[str, Any] = {} data["name"] = device.name data["model"] = device.model @@ -60,7 +60,7 @@ def _async_get_diagnostics_for_device( include_disabled_entities=True, ) - hass_entities.sort(key=lambda entry: entry.original_name) + hass_entities.sort(key=lambda entry: entry.original_name or "") for entity_entry in hass_entities: state = hass.states.get(entity_entry.entity_id) @@ -95,7 +95,7 @@ def _async_get_diagnostics( hkid = entry.data["AccessoryPairingID"] connection: HKDevice = hass.data[KNOWN_DEVICES][hkid] - data = { + data: dict[str, Any] = { "config-entry": { "title": entry.title, "version": entry.version, @@ -123,6 +123,8 @@ def _async_get_diagnostics( devices = data["devices"] = [] for device_id in connection.devices.values(): device = device_registry.async_get(device_id) + if not device: + continue devices.append(_async_get_diagnostics_for_device(hass, device)) return data diff --git a/homeassistant/components/homekit_controller/number.py b/homeassistant/components/homekit_controller/number.py index 3cfe842e85658e..9c28bc8ebddab9 100644 --- a/homeassistant/components/homekit_controller/number.py +++ b/homeassistant/components/homekit_controller/number.py @@ -122,7 +122,7 @@ def __init__( super().__init__(conn, info, char) @property - def name(self) -> str: + def name(self) -> str | None: """Return the name of the device if any.""" if prefix := super().name: return f"{prefix} {self.entity_description.name}" diff --git a/homeassistant/components/homekit_controller/switch.py b/homeassistant/components/homekit_controller/switch.py index 2645907a962bf1..c681ca4d288c5a 100644 --- a/homeassistant/components/homekit_controller/switch.py +++ b/homeassistant/components/homekit_controller/switch.py @@ -143,7 +143,7 @@ def __init__( super().__init__(conn, info, char) @property - def name(self) -> str: + def name(self) -> str | None: """Return the name of the device if any.""" if prefix := super().name: return f"{prefix} {self.entity_description.name}" diff --git a/mypy.ini b/mypy.ini index f41ebd8b1ce3d5..7e35a4929f8853 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2088,9 +2088,6 @@ ignore_errors = true [mypy-homeassistant.components.homekit.*] ignore_errors = true -[mypy-homeassistant.components.homekit_controller.*] -ignore_errors = true - [mypy-homeassistant.components.honeywell.*] ignore_errors = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index 06c1353ce73a20..bb1cc2a6679544 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -35,7 +35,6 @@ "homeassistant.components.here_travel_time.*", "homeassistant.components.home_plus_control.*", "homeassistant.components.homekit.*", - "homeassistant.components.homekit_controller.*", "homeassistant.components.honeywell.*", "homeassistant.components.icloud.*", "homeassistant.components.influxdb.*", From 75a1f3207cc2a7971732ff9d7471eaef48c05d4f Mon Sep 17 00:00:00 2001 From: Klaas Schoute Date: Tue, 1 Feb 2022 14:44:40 +0100 Subject: [PATCH 0164/1098] Use dataclass asdict to convert to dict (#65365) --- homeassistant/components/p1_monitor/diagnostics.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/p1_monitor/diagnostics.py b/homeassistant/components/p1_monitor/diagnostics.py index 627d0df767deda..b99cc7b86e1bdb 100644 --- a/homeassistant/components/p1_monitor/diagnostics.py +++ b/homeassistant/components/p1_monitor/diagnostics.py @@ -1,6 +1,7 @@ """Diagnostics support for P1 Monitor.""" from __future__ import annotations +from dataclasses import asdict from typing import Any from homeassistant.components.diagnostics import async_redact_data @@ -28,8 +29,8 @@ async def async_get_config_entry_diagnostics( "data": async_redact_data(entry.data, TO_REDACT), }, "data": { - "smartmeter": coordinator.data[SERVICE_SMARTMETER].__dict__, - "phases": coordinator.data[SERVICE_PHASES].__dict__, - "settings": coordinator.data[SERVICE_SETTINGS].__dict__, + "smartmeter": asdict(coordinator.data[SERVICE_SMARTMETER]), + "phases": asdict(coordinator.data[SERVICE_PHASES]), + "settings": asdict(coordinator.data[SERVICE_SETTINGS]), }, } From d24fedbe9762697eea5c293cc198cc245c04e8a9 Mon Sep 17 00:00:00 2001 From: fOmey Date: Wed, 2 Feb 2022 01:50:43 +1100 Subject: [PATCH 0165/1098] Tuya fan natural wind mode (#65343) --- homeassistant/components/tuya/switch.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/homeassistant/components/tuya/switch.py b/homeassistant/components/tuya/switch.py index 7041b22ebe6dc7..619e85cbdd4eae 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -501,6 +501,12 @@ icon="mdi:molecule", entity_category=EntityCategory.CONFIG, ), + SwitchEntityDescription( + key=DPCode.FAN_COOL, + name="Natural Wind", + icon="mdi:weather-windy", + entity_category=EntityCategory.CONFIG, + ), SwitchEntityDescription( key=DPCode.FAN_BEEP, name="Sound", From 390d32c71bd2d041bccce4c99b9b1838e064acb0 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Tue, 1 Feb 2022 16:47:42 +0100 Subject: [PATCH 0166/1098] Fix options for dnsip (#65369) --- homeassistant/components/dnsip/config_flow.py | 6 ++- homeassistant/components/dnsip/sensor.py | 6 +-- tests/components/dnsip/test_config_flow.py | 45 ++++++++++++++----- 3 files changed, 39 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/dnsip/config_flow.py b/homeassistant/components/dnsip/config_flow.py index e47dc67d58df12..bedcc5f821cddb 100644 --- a/homeassistant/components/dnsip/config_flow.py +++ b/homeassistant/components/dnsip/config_flow.py @@ -110,11 +110,13 @@ async def async_step_user( data={ CONF_HOSTNAME: hostname, CONF_NAME: name, - CONF_RESOLVER: resolver, - CONF_RESOLVER_IPV6: resolver_ipv6, CONF_IPV4: validate[CONF_IPV4], CONF_IPV6: validate[CONF_IPV6], }, + options={ + CONF_RESOLVER: resolver, + CONF_RESOLVER_IPV6: resolver_ipv6, + }, ) return self.async_show_form( diff --git a/homeassistant/components/dnsip/sensor.py b/homeassistant/components/dnsip/sensor.py index 9057f3e8c33224..7dfc3aaa544318 100644 --- a/homeassistant/components/dnsip/sensor.py +++ b/homeassistant/components/dnsip/sensor.py @@ -79,10 +79,8 @@ async def async_setup_entry( hostname = entry.data[CONF_HOSTNAME] name = entry.data[CONF_NAME] - resolver_ipv4 = entry.options.get(CONF_RESOLVER, entry.data[CONF_RESOLVER]) - resolver_ipv6 = entry.options.get( - CONF_RESOLVER_IPV6, entry.data[CONF_RESOLVER_IPV6] - ) + resolver_ipv4 = entry.options[CONF_RESOLVER] + resolver_ipv6 = entry.options[CONF_RESOLVER_IPV6] entities = [] if entry.data[CONF_IPV4]: entities.append(WanIpSensor(name, hostname, resolver_ipv4, False)) diff --git a/tests/components/dnsip/test_config_flow.py b/tests/components/dnsip/test_config_flow.py index 3ebbdfe91da234..59dcb81aa94a0a 100644 --- a/tests/components/dnsip/test_config_flow.py +++ b/tests/components/dnsip/test_config_flow.py @@ -69,11 +69,13 @@ async def test_form(hass: HomeAssistant) -> None: assert result2["data"] == { "hostname": "home-assistant.io", "name": "home-assistant.io", - "resolver": "208.67.222.222", - "resolver_ipv6": "2620:0:ccc::2", "ipv4": True, "ipv6": True, } + assert result2["options"] == { + "resolver": "208.67.222.222", + "resolver_ipv6": "2620:0:ccc::2", + } assert len(mock_setup_entry.mock_calls) == 1 @@ -101,34 +103,41 @@ async def test_form_error(hass: HomeAssistant) -> None: @pytest.mark.parametrize( - "p_input,p_output", + "p_input,p_output,p_options", [ ( {CONF_HOSTNAME: "home-assistant.io"}, { "hostname": "home-assistant.io", "name": "home-assistant.io", - "resolver": "208.67.222.222", - "resolver_ipv6": "2620:0:ccc::2", "ipv4": True, "ipv6": True, }, + { + "resolver": "208.67.222.222", + "resolver_ipv6": "2620:0:ccc::2", + }, ), ( {}, { "hostname": "myip.opendns.com", "name": "myip", - "resolver": "208.67.222.222", - "resolver_ipv6": "2620:0:ccc::2", "ipv4": True, "ipv6": True, }, + { + "resolver": "208.67.222.222", + "resolver_ipv6": "2620:0:ccc::2", + }, ), ], ) async def test_import_flow_success( - hass: HomeAssistant, p_input: dict[str, str], p_output: dict[str, str] + hass: HomeAssistant, + p_input: dict[str, str], + p_output: dict[str, str], + p_options: dict[str, str], ) -> None: """Test a successful import of YAML.""" @@ -149,6 +158,7 @@ async def test_import_flow_success( assert result2["type"] == RESULT_TYPE_CREATE_ENTRY assert result2["title"] == p_output["name"] assert result2["data"] == p_output + assert result2["options"] == p_options assert len(mock_setup_entry.mock_calls) == 1 @@ -160,11 +170,13 @@ async def test_flow_already_exist(hass: HomeAssistant) -> None: data={ CONF_HOSTNAME: "home-assistant.io", CONF_NAME: "home-assistant.io", - CONF_RESOLVER: "208.67.222.222", - CONF_RESOLVER_IPV6: "2620:0:ccc::2", CONF_IPV4: True, CONF_IPV6: True, }, + options={ + CONF_RESOLVER: "208.67.222.222", + CONF_RESOLVER_IPV6: "2620:0:ccc::2", + }, unique_id="home-assistant.io", ).add_to_hass(hass) @@ -199,11 +211,13 @@ async def test_options_flow(hass: HomeAssistant) -> None: data={ CONF_HOSTNAME: "home-assistant.io", CONF_NAME: "home-assistant.io", - CONF_RESOLVER: "208.67.222.222", - CONF_RESOLVER_IPV6: "2620:0:ccc::2", CONF_IPV4: True, CONF_IPV6: False, }, + options={ + CONF_RESOLVER: "208.67.222.222", + CONF_RESOLVER_IPV6: "2620:0:ccc::2", + }, ) entry.add_to_hass(hass) @@ -267,6 +281,13 @@ async def test_options_error(hass: HomeAssistant, p_input: dict[str, str]) -> No ) entry.add_to_hass(hass) + with patch( + "homeassistant.components.dnsip.async_setup_entry", + return_value=True, + ): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + result = await hass.config_entries.options.async_init(entry.entry_id) with patch( From d3374ecd8e0b07884713af77e539ebbf36d53881 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Tue, 1 Feb 2022 08:28:32 -0800 Subject: [PATCH 0167/1098] Add type hints for google calendar integration (#65353) Co-authored-by: Martin Hjelmare --- homeassistant/components/google/__init__.py | 69 ++++++++++++--------- homeassistant/components/google/calendar.py | 69 ++++++++++++++------- 2 files changed, 88 insertions(+), 50 deletions(-) diff --git a/homeassistant/components/google/__init__.py b/homeassistant/components/google/__init__.py index 05bfb490cb8a38..9f85d41774beaa 100644 --- a/homeassistant/components/google/__init__.py +++ b/homeassistant/components/google/__init__.py @@ -1,8 +1,10 @@ """Support for Google - Calendar Event Devices.""" +from collections.abc import Mapping from datetime import datetime, timedelta, timezone from enum import Enum import logging import os +from typing import Any from googleapiclient import discovery as google_discovery import httplib2 @@ -158,7 +160,9 @@ def scope(self) -> str: ) -def do_authentication(hass, hass_config, config): +def do_authentication( + hass: HomeAssistant, hass_config: ConfigType, config: ConfigType +) -> bool: """Notify user of actions and authenticate. Notify user of user_code and verification_url then poll @@ -192,7 +196,7 @@ def do_authentication(hass, hass_config, config): notification_id=NOTIFICATION_ID, ) - def step2_exchange(now): + def step2_exchange(now: datetime) -> None: """Keep trying to validate the user_code until it expires.""" _LOGGER.debug("Attempting to validate user code") @@ -261,7 +265,9 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: return True -def check_correct_scopes(hass, token_file, config): +def check_correct_scopes( + hass: HomeAssistant, token_file: str, config: ConfigType +) -> bool: """Check for the correct scopes in file.""" creds = Storage(token_file).get() if not creds or not creds.scopes: @@ -270,9 +276,30 @@ def check_correct_scopes(hass, token_file, config): return target_scope in creds.scopes +class GoogleCalendarService: + """Calendar service interface to Google.""" + + def __init__(self, token_file: str) -> None: + """Init the Google Calendar service.""" + self.token_file = token_file + + def get(self) -> google_discovery.Resource: + """Get the calendar service from the storage file token.""" + credentials = Storage(self.token_file).get() + http = credentials.authorize(httplib2.Http()) + service = google_discovery.build( + "calendar", "v3", http=http, cache_discovery=False + ) + return service + + def setup_services( - hass, hass_config, config, track_new_found_calendars, calendar_service -): + hass: HomeAssistant, + hass_config: ConfigType, + config: ConfigType, + track_new_found_calendars: bool, + calendar_service: GoogleCalendarService, +) -> None: """Set up the service listeners.""" def _found_calendar(call: ServiceCall) -> None: @@ -359,10 +386,9 @@ def _add_event(call: ServiceCall) -> None: hass.services.register( DOMAIN, SERVICE_ADD_EVENT, _add_event, schema=ADD_EVENT_SERVICE_SCHEMA ) - return True -def do_setup(hass, hass_config, config): +def do_setup(hass: HomeAssistant, hass_config: ConfigType, config: ConfigType) -> None: """Run the setup after we have everything configured.""" _LOGGER.debug("Setting up integration") # Load calendars the user has configured @@ -372,6 +398,7 @@ def do_setup(hass, hass_config, config): track_new_found_calendars = convert( config.get(CONF_TRACK_NEW), bool, DEFAULT_CONF_TRACK_NEW ) + assert track_new_found_calendars is not None setup_services( hass, hass_config, config, track_new_found_calendars, calendar_service ) @@ -381,29 +408,13 @@ def do_setup(hass, hass_config, config): # Look for any new calendars hass.services.call(DOMAIN, SERVICE_SCAN_CALENDARS, None) - return True - - -class GoogleCalendarService: - """Calendar service interface to Google.""" - - def __init__(self, token_file): - """Init the Google Calendar service.""" - self.token_file = token_file - - def get(self): - """Get the calendar service from the storage file token.""" - credentials = Storage(self.token_file).get() - http = credentials.authorize(httplib2.Http()) - service = google_discovery.build( - "calendar", "v3", http=http, cache_discovery=False - ) - return service -def get_calendar_info(hass, calendar): +def get_calendar_info( + hass: HomeAssistant, calendar: Mapping[str, Any] +) -> dict[str, Any]: """Convert data from Google into DEVICE_SCHEMA.""" - calendar_info = DEVICE_SCHEMA( + calendar_info: dict[str, Any] = DEVICE_SCHEMA( { CONF_CAL_ID: calendar["id"], CONF_ENTITIES: [ @@ -420,7 +431,7 @@ def get_calendar_info(hass, calendar): return calendar_info -def load_config(path): +def load_config(path: str) -> dict[str, Any]: """Load the google_calendar_devices.yaml.""" calendars = {} try: @@ -439,7 +450,7 @@ def load_config(path): return calendars -def update_config(path, calendar): +def update_config(path: str, calendar: dict[str, Any]) -> None: """Write the google_calendar_devices.yaml.""" with open(path, "a", encoding="utf8") as out: out.write("\n") diff --git a/homeassistant/components/google/calendar.py b/homeassistant/components/google/calendar.py index 6db9c810ad99ba..90b5bbb3d89376 100644 --- a/homeassistant/components/google/calendar.py +++ b/homeassistant/components/google/calendar.py @@ -2,9 +2,11 @@ from __future__ import annotations import copy -from datetime import timedelta +from datetime import datetime, timedelta import logging +from typing import Any +from googleapiclient import discovery as google_discovery from httplib2 import ServerNotFoundError from homeassistant.components.calendar import ( @@ -72,40 +74,48 @@ def setup_platform( class GoogleCalendarEventDevice(CalendarEventDevice): """A calendar event device.""" - def __init__(self, calendar_service, calendar, data, entity_id): + def __init__( + self, + calendar_service: GoogleCalendarService, + calendar_id: str, + data: dict[str, Any], + entity_id: str, + ) -> None: """Create the Calendar event device.""" self.data = GoogleCalendarData( calendar_service, - calendar, + calendar_id, data.get(CONF_SEARCH), - data.get(CONF_IGNORE_AVAILABILITY), + data.get(CONF_IGNORE_AVAILABILITY, False), ) - self._event = None - self._name = data[CONF_NAME] + self._event: dict[str, Any] | None = None + self._name: str = data[CONF_NAME] self._offset = data.get(CONF_OFFSET, DEFAULT_CONF_OFFSET) self._offset_reached = False self.entity_id = entity_id @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, bool]: """Return the device state attributes.""" return {"offset_reached": self._offset_reached} @property - def event(self): + def event(self) -> dict[str, Any] | None: """Return the next upcoming event.""" return self._event @property - def name(self): + def name(self) -> str: """Return the name of the entity.""" return self._name - async def async_get_events(self, hass, start_date, end_date): + async def async_get_events( + self, hass: HomeAssistant, start_date: datetime, end_date: datetime + ) -> list[dict[str, Any]]: """Get all events in a specific time frame.""" return await self.data.async_get_events(hass, start_date, end_date) - def update(self): + def update(self) -> None: """Update event data.""" self.data.update() event = copy.deepcopy(self.data.event) @@ -120,15 +130,23 @@ def update(self): class GoogleCalendarData: """Class to utilize calendar service object to get next event.""" - def __init__(self, calendar_service, calendar_id, search, ignore_availability): + def __init__( + self, + calendar_service: GoogleCalendarService, + calendar_id: str, + search: str | None, + ignore_availability: bool, + ) -> None: """Set up how we are going to search the google calendar.""" self.calendar_service = calendar_service self.calendar_id = calendar_id self.search = search self.ignore_availability = ignore_availability - self.event = None + self.event: dict[str, Any] | None = None - def _prepare_query(self): + def _prepare_query( + self, + ) -> tuple[google_discovery.Resource | None, dict[str, Any] | None]: try: service = self.calendar_service.get() except ServerNotFoundError as err: @@ -143,17 +161,19 @@ def _prepare_query(self): return service, params - async def async_get_events(self, hass, start_date, end_date): + async def async_get_events( + self, hass: HomeAssistant, start_date: datetime, end_date: datetime + ) -> list[dict[str, Any]]: """Get all events in a specific time frame.""" service, params = await hass.async_add_executor_job(self._prepare_query) - if service is None: + if service is None or params is None: return [] params["timeMin"] = start_date.isoformat("T") params["timeMax"] = end_date.isoformat("T") - event_list = [] + event_list: list[dict[str, Any]] = [] events = await hass.async_add_executor_job(service.events) - page_token = None + page_token: str | None = None while True: page_token = await self.async_get_events_page( hass, events, params, page_token, event_list @@ -162,7 +182,14 @@ async def async_get_events(self, hass, start_date, end_date): break return event_list - async def async_get_events_page(self, hass, events, params, page_token, event_list): + async def async_get_events_page( + self, + hass: HomeAssistant, + events: google_discovery.Resource, + params: dict[str, Any], + page_token: str | None, + event_list: list[dict[str, Any]], + ) -> str | None: """Get a page of events in a specific time frame.""" params["pageToken"] = page_token result = await hass.async_add_executor_job(events.list(**params).execute) @@ -177,10 +204,10 @@ async def async_get_events_page(self, hass, events, params, page_token, event_li return result.get("nextPageToken") @Throttle(MIN_TIME_BETWEEN_UPDATES) - def update(self): + def update(self) -> None: """Get the latest data.""" service, params = self._prepare_query() - if service is None: + if service is None or params is None: return params["timeMin"] = dt.now().isoformat("T") From 65ea54927d1a68bc4bd62895fff3c3fb92c9bbd2 Mon Sep 17 00:00:00 2001 From: ZuluWhiskey <35011199+ZuluWhiskey@users.noreply.github.com> Date: Tue, 1 Feb 2022 17:05:50 +0000 Subject: [PATCH 0168/1098] Fix MotionEye config flow (#64360) Co-authored-by: Paulus Schoutsen Co-authored-by: Paulus Schoutsen --- .../components/motioneye/config_flow.py | 20 +++++++++---------- .../components/motioneye/test_config_flow.py | 18 +++++++++++++++++ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/motioneye/config_flow.py b/homeassistant/components/motioneye/config_flow.py index 84a6d0771e6ed5..0361f4562c4eea 100644 --- a/homeassistant/components/motioneye/config_flow.py +++ b/homeassistant/components/motioneye/config_flow.py @@ -222,17 +222,15 @@ async def async_step_init( if self.show_advanced_options: # The input URL is not validated as being a URL, to allow for the possibility - # the template input won't be a valid URL until after it's rendered. - schema.update( - { - vol.Required( - CONF_STREAM_URL_TEMPLATE, - default=self._config_entry.options.get( - CONF_STREAM_URL_TEMPLATE, - "", - ), - ): str + # the template input won't be a valid URL until after it's rendered + stream_kwargs = {} + if CONF_STREAM_URL_TEMPLATE in self._config_entry.options: + stream_kwargs["description"] = { + "suggested_value": self._config_entry.options[ + CONF_STREAM_URL_TEMPLATE + ] } - ) + + schema[vol.Optional(CONF_STREAM_URL_TEMPLATE, **stream_kwargs)] = str return self.async_show_form(step_id="init", data_schema=vol.Schema(schema)) diff --git a/tests/components/motioneye/test_config_flow.py b/tests/components/motioneye/test_config_flow.py index 57def069d59788..9ef0f78874d690 100644 --- a/tests/components/motioneye/test_config_flow.py +++ b/tests/components/motioneye/test_config_flow.py @@ -480,6 +480,24 @@ async def test_advanced_options(hass: HomeAssistant) -> None: ) as mock_setup_entry: await hass.async_block_till_done() + result = await hass.config_entries.options.async_init( + config_entry.entry_id, context={"show_advanced_options": True} + ) + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_WEBHOOK_SET: True, + CONF_WEBHOOK_SET_OVERWRITE: True, + }, + ) + await hass.async_block_till_done() + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"][CONF_WEBHOOK_SET] + assert result["data"][CONF_WEBHOOK_SET_OVERWRITE] + assert CONF_STREAM_URL_TEMPLATE not in result["data"] + assert len(mock_setup.mock_calls) == 0 + assert len(mock_setup_entry.mock_calls) == 0 + result = await hass.config_entries.options.async_init( config_entry.entry_id, context={"show_advanced_options": True} ) From 3697f5611c5001c9f1cce2f73981677f6c38d4a0 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 1 Feb 2022 18:09:51 +0100 Subject: [PATCH 0169/1098] Fix tradfri coordinator error handling (#65204) --- .../components/tradfri/coordinator.py | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/tradfri/coordinator.py b/homeassistant/components/tradfri/coordinator.py index 1395478b6e906a..039ff34c9f74a6 100644 --- a/homeassistant/components/tradfri/coordinator.py +++ b/homeassistant/components/tradfri/coordinator.py @@ -51,27 +51,23 @@ async def set_hub_available(self, available: bool) -> None: @callback def _observe_update(self, device: Device) -> None: """Update the coordinator for a device when a change is detected.""" - self.update_interval = timedelta(seconds=SCAN_INTERVAL) # Reset update interval - self.async_set_updated_data(data=device) @callback - def _exception_callback(self, device: Device, exc: Exception | None = None) -> None: + def _exception_callback(self, exc: Exception) -> None: """Schedule handling exception..""" - self.hass.async_create_task(self._handle_exception(device=device, exc=exc)) + self.hass.async_create_task(self._handle_exception(exc)) - async def _handle_exception( - self, device: Device, exc: Exception | None = None - ) -> None: + async def _handle_exception(self, exc: Exception) -> None: """Handle observe exceptions in a coroutine.""" - self._exception = ( - exc # Store exception so that it gets raised in _async_update_data - ) + # Store exception so that it gets raised in _async_update_data + self._exception = exc - _LOGGER.debug("Observation failed for %s, trying again", device, exc_info=exc) - self.update_interval = timedelta( - seconds=5 - ) # Change interval so we get a swift refresh + _LOGGER.debug( + "Observation failed for %s, trying again", self.device, exc_info=exc + ) + # Change interval so we get a swift refresh + self.update_interval = timedelta(seconds=5) await self.async_request_refresh() async def _async_update_data(self) -> Device: @@ -82,10 +78,7 @@ async def _async_update_data(self) -> Device: self._exception = None # Clear stored exception raise exc # pylint: disable-msg=raising-bad-type except RequestError as err: - raise UpdateFailed( - f"Error communicating with API: {err}. Try unplugging and replugging your " - f"IKEA gateway." - ) from err + raise UpdateFailed(f"Error communicating with API: {err}.") from err if not self.data or not self.last_update_success: # Start subscription try: @@ -95,8 +88,11 @@ async def _async_update_data(self) -> Device: duration=0, ) await self.api(cmd) - except RequestError as exc: - await self._handle_exception(device=self.device, exc=exc) + except RequestError as err: + raise UpdateFailed(f"Error communicating with API: {err}.") from err + + # Reset update interval + self.update_interval = timedelta(seconds=SCAN_INTERVAL) return self.device From 3c0369ed5944af1b82fc8418550861b6211aba03 Mon Sep 17 00:00:00 2001 From: schreyack Date: Tue, 1 Feb 2022 09:11:09 -0800 Subject: [PATCH 0170/1098] Fix honeywell hold mode (#65327) Co-authored-by: Martin Hjelmare --- homeassistant/components/honeywell/climate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/honeywell/climate.py b/homeassistant/components/honeywell/climate.py index 57ce0125c93375..f0e18953402676 100644 --- a/homeassistant/components/honeywell/climate.py +++ b/homeassistant/components/honeywell/climate.py @@ -242,7 +242,7 @@ def _set_temperature(self, **kwargs) -> None: # Get current mode mode = self._device.system_mode # Set hold if this is not the case - if getattr(self._device, f"hold_{mode}") is False: + if getattr(self._device, f"hold_{mode}", None) is False: # Get next period key next_period_key = f"{mode.capitalize()}NextPeriod" # Get next period raw value From c82aa1606a3e818063c74bab9182f19063c1cc1a Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 1 Feb 2022 18:45:08 +0100 Subject: [PATCH 0171/1098] Allow removing keys from automation (#65374) --- homeassistant/components/config/automation.py | 37 ++++++++----------- homeassistant/components/config/scene.py | 2 +- tests/components/config/test_automation.py | 37 +++++++++++++++++++ 3 files changed, 54 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/config/automation.py b/homeassistant/components/config/automation.py index 01e22297c0d42a..f1a2d9aab843d9 100644 --- a/homeassistant/components/config/automation.py +++ b/homeassistant/components/config/automation.py @@ -1,5 +1,4 @@ """Provide configuration end points for Automations.""" -from collections import OrderedDict import uuid from homeassistant.components.automation.config import ( @@ -52,31 +51,27 @@ class EditAutomationConfigView(EditIdBasedConfigView): def _write_value(self, hass, data, config_key, new_value): """Set value.""" - index = None - for index, cur_value in enumerate(data): - # When people copy paste their automations to the config file, - # they sometimes forget to add IDs. Fix it here. - if CONF_ID not in cur_value: - cur_value[CONF_ID] = uuid.uuid4().hex - - elif cur_value[CONF_ID] == config_key: - break - else: - cur_value = OrderedDict() - cur_value[CONF_ID] = config_key - index = len(data) - data.append(cur_value) + updated_value = {CONF_ID: config_key} # Iterate through some keys that we want to have ordered in the output - updated_value = OrderedDict() - for key in ("id", "alias", "description", "trigger", "condition", "action"): - if key in cur_value: - updated_value[key] = cur_value[key] + for key in ("alias", "description", "trigger", "condition", "action"): if key in new_value: updated_value[key] = new_value[key] # We cover all current fields above, but just in case we start # supporting more fields in the future. - updated_value.update(cur_value) updated_value.update(new_value) - data[index] = updated_value + + updated = False + for index, cur_value in enumerate(data): + # When people copy paste their automations to the config file, + # they sometimes forget to add IDs. Fix it here. + if CONF_ID not in cur_value: + cur_value[CONF_ID] = uuid.uuid4().hex + + elif cur_value[CONF_ID] == config_key: + data[index] = updated_value + updated = True + + if not updated: + data.append(updated_value) diff --git a/homeassistant/components/config/scene.py b/homeassistant/components/config/scene.py index 41b8dce09577f2..6523ff84158fa0 100644 --- a/homeassistant/components/config/scene.py +++ b/homeassistant/components/config/scene.py @@ -47,8 +47,8 @@ class EditSceneConfigView(EditIdBasedConfigView): def _write_value(self, hass, data, config_key, new_value): """Set value.""" - # Iterate through some keys that we want to have ordered in the output updated_value = {CONF_ID: config_key} + # Iterate through some keys that we want to have ordered in the output for key in ("name", "entities"): if key in new_value: updated_value[key] = new_value[key] diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index 80ee38350aa85f..6f782fdbbfff22 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -80,6 +80,43 @@ def mock_write(path, data): assert written[0] == orig_data +@pytest.mark.parametrize("automation_config", ({},)) +async def test_update_remove_key_device_config(hass, hass_client, setup_automation): + """Test updating device config while removing a key.""" + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_client() + + orig_data = [{"id": "sun", "key": "value"}, {"id": "moon", "key": "value"}] + + def mock_read(path): + """Mock reading data.""" + return orig_data + + written = [] + + def mock_write(path, data): + """Mock writing data.""" + written.append(data) + + with patch("homeassistant.components.config._read", mock_read), patch( + "homeassistant.components.config._write", mock_write + ), patch("homeassistant.config.async_hass_config_yaml", return_value={}): + resp = await client.post( + "/api/config/automation/config/moon", + data=json.dumps({"trigger": [], "action": [], "condition": []}), + ) + + assert resp.status == HTTPStatus.OK + result = await resp.json() + assert result == {"result": "ok"} + + assert list(orig_data[1]) == ["id", "trigger", "condition", "action"] + assert orig_data[1] == {"id": "moon", "trigger": [], "condition": [], "action": []} + assert written[0] == orig_data + + @pytest.mark.parametrize("automation_config", ({},)) async def test_bad_formatted_automations(hass, hass_client, setup_automation): """Test that we handle automations without ID.""" From 69ac59ce738eaf3706fcf12875a1a87c3b6792e1 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 1 Feb 2022 18:52:56 +0100 Subject: [PATCH 0172/1098] Redact host address in UniFi diagnostics (#65379) --- homeassistant/components/unifi/diagnostics.py | 4 ++-- tests/components/unifi/test_diagnostics.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/unifi/diagnostics.py b/homeassistant/components/unifi/diagnostics.py index 4f27ff4ff4fd3d..ed05985688140d 100644 --- a/homeassistant/components/unifi/diagnostics.py +++ b/homeassistant/components/unifi/diagnostics.py @@ -7,14 +7,14 @@ from homeassistant.components.diagnostics import REDACTED, async_redact_data from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.device_registry import format_mac from .const import CONF_CONTROLLER, DOMAIN as UNIFI_DOMAIN TO_REDACT = {CONF_CONTROLLER, CONF_PASSWORD} -REDACT_CONFIG = {CONF_CONTROLLER, CONF_PASSWORD, CONF_USERNAME} +REDACT_CONFIG = {CONF_CONTROLLER, CONF_HOST, CONF_PASSWORD, CONF_USERNAME} REDACT_CLIENTS = {"bssid", "essid"} REDACT_DEVICES = { "anon_id", diff --git a/tests/components/unifi/test_diagnostics.py b/tests/components/unifi/test_diagnostics.py index 3de9393e5b93eb..ccec7fcb48adb1 100644 --- a/tests/components/unifi/test_diagnostics.py +++ b/tests/components/unifi/test_diagnostics.py @@ -122,7 +122,7 @@ async def test_entry_diagnostics(hass, hass_client, aioclient_mock): "config": { "data": { "controller": REDACTED, - "host": "1.2.3.4", + "host": REDACTED, "password": REDACTED, "port": 1234, "site": "site_id", From 1b8252fa2f0a640090518d478cd3abad3d4166df Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Tue, 1 Feb 2022 18:57:34 +0100 Subject: [PATCH 0173/1098] Fix wan_access switch for disconnected devices in Fritz!Tools (#65378) --- homeassistant/components/fritz/common.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index c9eff204bf278e..dede4b82c02f0a 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -372,13 +372,14 @@ def scan_devices(self, now: datetime | None = None) -> None: dev_info: Device = hosts[dev_mac] + if dev_info.ip_address: + dev_info.wan_access = self._get_wan_access(dev_info.ip_address) + for link in interf["node_links"]: intf = mesh_intf.get(link["node_interface_1_uid"]) if intf is not None: - if intf["op_mode"] != "AP_GUEST" and dev_info.ip_address: - dev_info.wan_access = self._get_wan_access( - dev_info.ip_address - ) + if intf["op_mode"] == "AP_GUEST": + dev_info.wan_access = None dev_info.connected_to = intf["device"] dev_info.connection_type = intf["type"] From aef6f49eff1fa9da9bf34460d5ab24d1e708f04c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 1 Feb 2022 09:58:23 -0800 Subject: [PATCH 0174/1098] Bump frontend to 20220201.0 (#65380) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 1eef7aff083b56..49e49ac2efa054 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20220127.0" + "home-assistant-frontend==20220201.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 15c1afc1b99cc8..30086a676e44c1 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ ciso8601==2.2.0 cryptography==35.0.0 emoji==1.6.3 hass-nabucasa==0.52.0 -home-assistant-frontend==20220127.0 +home-assistant-frontend==20220201.0 httpx==0.21.3 ifaddr==0.1.7 jinja2==3.0.3 diff --git a/requirements_all.txt b/requirements_all.txt index 2a88989c9006c0..8f5ca63a3ec3af 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -842,7 +842,7 @@ hole==0.7.0 holidays==0.12 # homeassistant.components.frontend -home-assistant-frontend==20220127.0 +home-assistant-frontend==20220201.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b2832777f8a6e5..c25db3f6345b88 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -543,7 +543,7 @@ hole==0.7.0 holidays==0.12 # homeassistant.components.frontend -home-assistant-frontend==20220127.0 +home-assistant-frontend==20220201.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 9f5d77e0df957c20a2af574d706140786f0a551a Mon Sep 17 00:00:00 2001 From: Jc2k Date: Tue, 1 Feb 2022 19:30:37 +0000 Subject: [PATCH 0175/1098] Add missing type hints to homekit_controller (#65368) --- .../components/homekit_controller/__init__.py | 33 +++--- .../homekit_controller/alarm_control_panel.py | 26 +++-- .../homekit_controller/binary_sensor.py | 32 +++--- .../components/homekit_controller/button.py | 18 ++-- .../components/homekit_controller/camera.py | 7 +- .../components/homekit_controller/climate.py | 88 ++++++++------- .../homekit_controller/config_flow.py | 10 +- .../homekit_controller/connection.py | 102 ++++++++++-------- .../components/homekit_controller/cover.py | 64 +++++------ .../components/homekit_controller/fan.py | 42 +++++--- .../homekit_controller/humidifier.py | 28 ++--- .../components/homekit_controller/light.py | 24 +++-- .../components/homekit_controller/lock.py | 26 +++-- .../homekit_controller/media_player.py | 32 +++--- .../components/homekit_controller/number.py | 20 ++-- .../components/homekit_controller/select.py | 4 +- .../components/homekit_controller/sensor.py | 56 +++++----- .../components/homekit_controller/storage.py | 41 +++++-- .../components/homekit_controller/switch.py | 48 +++++---- 19 files changed, 389 insertions(+), 312 deletions(-) diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index ed65a83a1e7b77..974cb5e1dfc70c 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -17,7 +17,7 @@ from homeassistant.components import zeroconf from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP -from homeassistant.core import HomeAssistant +from homeassistant.core import Event, HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.typing import ConfigType @@ -30,17 +30,12 @@ _LOGGER = logging.getLogger(__name__) -def escape_characteristic_name(char_name): - """Escape any dash or dots in a characteristics name.""" - return char_name.replace("-", "_").replace(".", "_") - - class HomeKitEntity(Entity): """Representation of a Home Assistant HomeKit device.""" _attr_should_poll = False - def __init__(self, accessory: HKDevice, devinfo): + def __init__(self, accessory: HKDevice, devinfo: ConfigType) -> None: """Initialise a generic HomeKit device.""" self._accessory = accessory self._aid = devinfo["aid"] @@ -67,7 +62,7 @@ def service(self) -> Service: """Return a Service model that this entity is attached to.""" return self.accessory.services.iid(self._iid) - async def async_added_to_hass(self): + async def async_added_to_hass(self) -> None: """Entity added to hass.""" self.async_on_remove( self.hass.helpers.dispatcher.async_dispatcher_connect( @@ -78,12 +73,12 @@ async def async_added_to_hass(self): self._accessory.add_pollable_characteristics(self.pollable_characteristics) self._accessory.add_watchable_characteristics(self.watchable_characteristics) - async def async_will_remove_from_hass(self): + async def async_will_remove_from_hass(self) -> None: """Prepare to be removed from hass.""" self._accessory.remove_pollable_characteristics(self._aid) self._accessory.remove_watchable_characteristics(self._aid) - async def async_put_characteristics(self, characteristics: dict[str, Any]): + async def async_put_characteristics(self, characteristics: dict[str, Any]) -> None: """ Write characteristics to the device. @@ -101,10 +96,10 @@ async def async_put_characteristics(self, characteristics: dict[str, Any]): payload = self.service.build_update(characteristics) return await self._accessory.put_characteristics(payload) - def setup(self): - """Configure an entity baed on its HomeKit characteristics metadata.""" - self.pollable_characteristics = [] - self.watchable_characteristics = [] + def setup(self) -> None: + """Configure an entity based on its HomeKit characteristics metadata.""" + self.pollable_characteristics: list[tuple[int, int]] = [] + self.watchable_characteristics: list[tuple[int, int]] = [] char_types = self.get_characteristic_types() @@ -118,7 +113,7 @@ def setup(self): for char in service.characteristics.filter(char_types=char_types): self._setup_characteristic(char) - def _setup_characteristic(self, char: Characteristic): + def _setup_characteristic(self, char: Characteristic) -> None: """Configure an entity based on a HomeKit characteristics metadata.""" # Build up a list of (aid, iid) tuples to poll on update() if CharacteristicPermissions.paired_read in char.perms: @@ -153,7 +148,7 @@ def device_info(self) -> DeviceInfo: """Return the device info.""" return self._accessory.device_info_for_accessory(self.accessory) - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" raise NotImplementedError @@ -176,7 +171,9 @@ class CharacteristicEntity(HomeKitEntity): the service entity. """ - def __init__(self, accessory, devinfo, char): + def __init__( + self, accessory: HKDevice, devinfo: ConfigType, char: Characteristic + ) -> None: """Initialise a generic single characteristic HomeKit entity.""" self._char = char super().__init__(accessory, devinfo) @@ -218,7 +215,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: hass.data[KNOWN_DEVICES] = {} hass.data[TRIGGERS] = {} - async def _async_stop_homekit_controller(event): + async def _async_stop_homekit_controller(event: Event) -> None: await asyncio.gather( *( connection.async_unload() diff --git a/homeassistant/components/homekit_controller/alarm_control_panel.py b/homeassistant/components/homekit_controller/alarm_control_panel.py index e7d9bf11c32266..0194036db4e318 100644 --- a/homeassistant/components/homekit_controller/alarm_control_panel.py +++ b/homeassistant/components/homekit_controller/alarm_control_panel.py @@ -1,6 +1,10 @@ """Support for Homekit Alarm Control Panel.""" +from __future__ import annotations + +from typing import Any + from aiohomekit.model.characteristics import CharacteristicsTypes -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.services import Service, ServicesTypes from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity from homeassistant.components.alarm_control_panel.const import ( @@ -50,7 +54,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_service(service): + def async_add_service(service: Service) -> bool: if service.type != ServicesTypes.SECURITY_SYSTEM: return False info = {"aid": service.accessory.aid, "iid": service.iid} @@ -63,7 +67,7 @@ def async_add_service(service): class HomeKitAlarmControlPanelEntity(HomeKitEntity, AlarmControlPanelEntity): """Representation of a Homekit Alarm Control Panel.""" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.SECURITY_SYSTEM_STATE_CURRENT, @@ -72,12 +76,12 @@ def get_characteristic_types(self): ] @property - def icon(self): + def icon(self) -> str: """Return icon.""" return ICON @property - def state(self): + def state(self) -> str: """Return the state of the device.""" return CURRENT_STATE_MAP[ self.service.value(CharacteristicsTypes.SECURITY_SYSTEM_STATE_CURRENT) @@ -88,30 +92,30 @@ def supported_features(self) -> int: """Return the list of supported features.""" return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT - async def async_alarm_disarm(self, code=None): + async def async_alarm_disarm(self, code: str | None = None) -> None: """Send disarm command.""" await self.set_alarm_state(STATE_ALARM_DISARMED, code) - async def async_alarm_arm_away(self, code=None): + async def async_alarm_arm_away(self, code: str | None = None) -> None: """Send arm command.""" await self.set_alarm_state(STATE_ALARM_ARMED_AWAY, code) - async def async_alarm_arm_home(self, code=None): + async def async_alarm_arm_home(self, code: str | None = None) -> None: """Send stay command.""" await self.set_alarm_state(STATE_ALARM_ARMED_HOME, code) - async def async_alarm_arm_night(self, code=None): + async def async_alarm_arm_night(self, code: str | None = None) -> None: """Send night command.""" await self.set_alarm_state(STATE_ALARM_ARMED_NIGHT, code) - async def set_alarm_state(self, state, code=None): + async def set_alarm_state(self, state: str, code: str | None = None) -> None: """Send state command.""" await self.async_put_characteristics( {CharacteristicsTypes.SECURITY_SYSTEM_STATE_TARGET: TARGET_STATE_MAP[state]} ) @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the optional state attributes.""" battery_level = self.service.value(CharacteristicsTypes.BATTERY_LEVEL) diff --git a/homeassistant/components/homekit_controller/binary_sensor.py b/homeassistant/components/homekit_controller/binary_sensor.py index 09c42bf0547e11..91c1c47d47ed1d 100644 --- a/homeassistant/components/homekit_controller/binary_sensor.py +++ b/homeassistant/components/homekit_controller/binary_sensor.py @@ -1,6 +1,8 @@ """Support for Homekit motion sensors.""" +from __future__ import annotations + from aiohomekit.model.characteristics import CharacteristicsTypes -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.services import Service, ServicesTypes from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, @@ -18,14 +20,14 @@ class HomeKitMotionSensor(HomeKitEntity, BinarySensorEntity): _attr_device_class = BinarySensorDeviceClass.MOTION - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [CharacteristicsTypes.MOTION_DETECTED] @property - def is_on(self): + def is_on(self) -> bool: """Has motion been detected.""" - return self.service.value(CharacteristicsTypes.MOTION_DETECTED) + return self.service.value(CharacteristicsTypes.MOTION_DETECTED) is True class HomeKitContactSensor(HomeKitEntity, BinarySensorEntity): @@ -33,12 +35,12 @@ class HomeKitContactSensor(HomeKitEntity, BinarySensorEntity): _attr_device_class = BinarySensorDeviceClass.OPENING - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [CharacteristicsTypes.CONTACT_STATE] @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary sensor is on/open.""" return self.service.value(CharacteristicsTypes.CONTACT_STATE) == 1 @@ -48,12 +50,12 @@ class HomeKitSmokeSensor(HomeKitEntity, BinarySensorEntity): _attr_device_class = BinarySensorDeviceClass.SMOKE - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [CharacteristicsTypes.SMOKE_DETECTED] @property - def is_on(self): + def is_on(self) -> bool: """Return true if smoke is currently detected.""" return self.service.value(CharacteristicsTypes.SMOKE_DETECTED) == 1 @@ -63,12 +65,12 @@ class HomeKitCarbonMonoxideSensor(HomeKitEntity, BinarySensorEntity): _attr_device_class = BinarySensorDeviceClass.GAS - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [CharacteristicsTypes.CARBON_MONOXIDE_DETECTED] @property - def is_on(self): + def is_on(self) -> bool: """Return true if CO is currently detected.""" return self.service.value(CharacteristicsTypes.CARBON_MONOXIDE_DETECTED) == 1 @@ -78,12 +80,12 @@ class HomeKitOccupancySensor(HomeKitEntity, BinarySensorEntity): _attr_device_class = BinarySensorDeviceClass.OCCUPANCY - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [CharacteristicsTypes.OCCUPANCY_DETECTED] @property - def is_on(self): + def is_on(self) -> bool: """Return true if occupancy is currently detected.""" return self.service.value(CharacteristicsTypes.OCCUPANCY_DETECTED) == 1 @@ -93,12 +95,12 @@ class HomeKitLeakSensor(HomeKitEntity, BinarySensorEntity): _attr_device_class = BinarySensorDeviceClass.MOISTURE - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [CharacteristicsTypes.LEAK_DETECTED] @property - def is_on(self): + def is_on(self) -> bool: """Return true if a leak is detected from the binary sensor.""" return self.service.value(CharacteristicsTypes.LEAK_DETECTED) == 1 @@ -123,7 +125,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_service(service): + def async_add_service(service: Service) -> bool: if not (entity_class := ENTITY_TYPES.get(service.type)): return False info = {"aid": service.accessory.aid, "iid": service.iid} diff --git a/homeassistant/components/homekit_controller/button.py b/homeassistant/components/homekit_controller/button.py index 8c8b1e616c9347..7d2c737b509c35 100644 --- a/homeassistant/components/homekit_controller/button.py +++ b/homeassistant/components/homekit_controller/button.py @@ -19,8 +19,10 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType from . import KNOWN_DEVICES, CharacteristicEntity +from .connection import HKDevice @dataclass @@ -64,7 +66,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_characteristic(char: Characteristic): + def async_add_characteristic(char: Characteristic) -> bool: entities = [] info = {"aid": char.service.accessory.aid, "iid": char.service.iid} @@ -88,16 +90,16 @@ class HomeKitButton(CharacteristicEntity, ButtonEntity): def __init__( self, - conn, - info, - char, + conn: HKDevice, + info: ConfigType, + char: Characteristic, description: HomeKitButtonEntityDescription, - ): + ) -> None: """Initialise a HomeKit button control.""" self.entity_description = description super().__init__(conn, info, char) - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [self._char.type] @@ -112,13 +114,13 @@ async def async_press(self) -> None: """Press the button.""" key = self.entity_description.key val = self.entity_description.write_value - return await self.async_put_characteristics({key: val}) + await self.async_put_characteristics({key: val}) class HomeKitEcobeeClearHoldButton(CharacteristicEntity, ButtonEntity): """Representation of a Button control for Ecobee clear hold request.""" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [] diff --git a/homeassistant/components/homekit_controller/camera.py b/homeassistant/components/homekit_controller/camera.py index 63b06cce3381d3..0ffa0a22f4d7ab 100644 --- a/homeassistant/components/homekit_controller/camera.py +++ b/homeassistant/components/homekit_controller/camera.py @@ -1,6 +1,7 @@ """Support for Homekit cameras.""" from __future__ import annotations +from aiohomekit.model import Accessory from aiohomekit.model.services import ServicesTypes from homeassistant.components.camera import Camera @@ -16,7 +17,7 @@ class HomeKitCamera(AccessoryEntity, Camera): # content_type = "image/jpeg" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [] @@ -41,12 +42,12 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_accessory(accessory): + def async_add_accessory(accessory: Accessory) -> bool: stream_mgmt = accessory.services.first( service_type=ServicesTypes.CAMERA_RTP_STREAM_MANAGEMENT ) if not stream_mgmt: - return + return False info = {"aid": accessory.aid, "iid": stream_mgmt.iid} async_add_entities([HomeKitCamera(conn, info)], True) diff --git a/homeassistant/components/homekit_controller/climate.py b/homeassistant/components/homekit_controller/climate.py index aa807f3e9014f8..e8179b0bc6bd76 100644 --- a/homeassistant/components/homekit_controller/climate.py +++ b/homeassistant/components/homekit_controller/climate.py @@ -1,5 +1,8 @@ """Support for Homekit climate devices.""" +from __future__ import annotations + import logging +from typing import Any from aiohomekit.model.characteristics import ( ActivationStateValues, @@ -10,7 +13,7 @@ SwingModeValues, TargetHeaterCoolerStateValues, ) -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.services import Service, ServicesTypes from aiohomekit.utils import clamp_enum_to_char from homeassistant.components.climate import ClimateEntity @@ -94,7 +97,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_service(service): + def async_add_service(service: Service) -> bool: if not (entity_class := ENTITY_TYPES.get(service.type)): return False info = {"aid": service.accessory.aid, "iid": service.iid} @@ -107,7 +110,7 @@ def async_add_service(service): class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): """Representation of a Homekit climate device.""" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.ACTIVE, @@ -119,7 +122,7 @@ def get_characteristic_types(self): CharacteristicsTypes.TEMPERATURE_CURRENT, ] - async def async_set_temperature(self, **kwargs): + async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" temp = kwargs.get(ATTR_TEMPERATURE) state = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE) @@ -140,7 +143,7 @@ async def async_set_temperature(self, **kwargs): hvac_mode, ) - async def async_set_hvac_mode(self, hvac_mode): + async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new target operation mode.""" if hvac_mode == HVAC_MODE_OFF: await self.async_put_characteristics( @@ -163,12 +166,12 @@ async def async_set_hvac_mode(self, hvac_mode): ) @property - def current_temperature(self): + def current_temperature(self) -> float: """Return the current temperature.""" return self.service.value(CharacteristicsTypes.TEMPERATURE_CURRENT) @property - def target_temperature(self): + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" state = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE) if state == TargetHeaterCoolerStateValues.COOL: @@ -182,7 +185,7 @@ def target_temperature(self): return None @property - def target_temperature_step(self): + def target_temperature_step(self) -> float | None: """Return the supported step of target temperature.""" state = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE) if state == TargetHeaterCoolerStateValues.COOL and self.service.has( @@ -200,7 +203,7 @@ def target_temperature_step(self): return None @property - def min_temp(self): + def min_temp(self) -> float: """Return the minimum target temp.""" state = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE) if state == TargetHeaterCoolerStateValues.COOL and self.service.has( @@ -218,7 +221,7 @@ def min_temp(self): return super().min_temp @property - def max_temp(self): + def max_temp(self) -> float: """Return the maximum target temp.""" state = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE) if state == TargetHeaterCoolerStateValues.COOL and self.service.has( @@ -236,7 +239,7 @@ def max_temp(self): return super().max_temp @property - def hvac_action(self): + def hvac_action(self) -> str | None: """Return the current running hvac operation.""" # This characteristic describes the current mode of a device, # e.g. a thermostat is "heating" a room to 75 degrees Fahrenheit. @@ -250,7 +253,7 @@ def hvac_action(self): return CURRENT_HEATER_COOLER_STATE_HOMEKIT_TO_HASS.get(value) @property - def hvac_mode(self): + def hvac_mode(self) -> str: """Return hvac operation ie. heat, cool mode.""" # This characteristic describes the target mode # E.g. should the device start heating a room if the temperature @@ -262,10 +265,10 @@ def hvac_mode(self): ): return HVAC_MODE_OFF value = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE) - return TARGET_HEATER_COOLER_STATE_HOMEKIT_TO_HASS.get(value) + return TARGET_HEATER_COOLER_STATE_HOMEKIT_TO_HASS[value] @property - def hvac_modes(self): + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" valid_values = clamp_enum_to_char( TargetHeaterCoolerStateValues, @@ -278,7 +281,7 @@ def hvac_modes(self): return modes @property - def swing_mode(self): + def swing_mode(self) -> str: """Return the swing setting. Requires SUPPORT_SWING_MODE. @@ -287,7 +290,7 @@ def swing_mode(self): return SWING_MODE_HOMEKIT_TO_HASS[value] @property - def swing_modes(self): + def swing_modes(self) -> list[str]: """Return the list of available swing modes. Requires SUPPORT_SWING_MODE. @@ -305,7 +308,7 @@ async def async_set_swing_mode(self, swing_mode: str) -> None: ) @property - def supported_features(self): + def supported_features(self) -> int: """Return the list of supported features.""" features = 0 @@ -321,7 +324,7 @@ def supported_features(self): return features @property - def temperature_unit(self): + def temperature_unit(self) -> str: """Return the unit of measurement.""" return TEMP_CELSIUS @@ -329,7 +332,7 @@ def temperature_unit(self): class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): """Representation of a Homekit climate device.""" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.HEATING_COOLING_CURRENT, @@ -342,12 +345,12 @@ def get_characteristic_types(self): CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET, ] - async def async_set_temperature(self, **kwargs): + async def async_set_temperature(self, **kwargs) -> None: """Set new target temperature.""" chars = {} value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) - mode = MODE_HOMEKIT_TO_HASS.get(value) + mode = MODE_HOMEKIT_TO_HASS[value] if kwargs.get(ATTR_HVAC_MODE, mode) != mode: mode = kwargs[ATTR_HVAC_MODE] @@ -359,8 +362,11 @@ async def async_set_temperature(self, **kwargs): heat_temp = kwargs.get(ATTR_TARGET_TEMP_LOW) cool_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH) - if (mode == HVAC_MODE_HEAT_COOL) and ( - SUPPORT_TARGET_TEMPERATURE_RANGE & self.supported_features + if ( + (mode == HVAC_MODE_HEAT_COOL) + and (SUPPORT_TARGET_TEMPERATURE_RANGE & self.supported_features) + and heat_temp + and cool_temp ): if temp is None: temp = (cool_temp + heat_temp) / 2 @@ -376,13 +382,13 @@ async def async_set_temperature(self, **kwargs): await self.async_put_characteristics(chars) - async def async_set_humidity(self, humidity): + async def async_set_humidity(self, humidity: int) -> None: """Set new target humidity.""" await self.async_put_characteristics( {CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET: humidity} ) - async def async_set_hvac_mode(self, hvac_mode): + async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new target operation mode.""" await self.async_put_characteristics( { @@ -393,12 +399,12 @@ async def async_set_hvac_mode(self, hvac_mode): ) @property - def current_temperature(self): + def current_temperature(self) -> float | None: """Return the current temperature.""" return self.service.value(CharacteristicsTypes.TEMPERATURE_CURRENT) @property - def target_temperature(self): + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT, HVAC_MODE_COOL}) or ( @@ -409,7 +415,7 @@ def target_temperature(self): return None @property - def target_temperature_high(self): + def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach.""" value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and ( @@ -421,7 +427,7 @@ def target_temperature_high(self): return None @property - def target_temperature_low(self): + def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach.""" value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and ( @@ -433,7 +439,7 @@ def target_temperature_low(self): return None @property - def min_temp(self): + def min_temp(self) -> float: """Return the minimum target temp.""" value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and ( @@ -455,7 +461,7 @@ def min_temp(self): return super().min_temp @property - def max_temp(self): + def max_temp(self) -> float: """Return the maximum target temp.""" value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and ( @@ -477,17 +483,17 @@ def max_temp(self): return super().max_temp @property - def current_humidity(self): + def current_humidity(self) -> int: """Return the current humidity.""" return self.service.value(CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT) @property - def target_humidity(self): + def target_humidity(self) -> int: """Return the humidity we try to reach.""" return self.service.value(CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET) @property - def min_humidity(self): + def min_humidity(self) -> int: """Return the minimum humidity.""" min_humidity = self.service[ CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET @@ -497,7 +503,7 @@ def min_humidity(self): return super().min_humidity @property - def max_humidity(self): + def max_humidity(self) -> int: """Return the maximum humidity.""" max_humidity = self.service[ CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET @@ -507,7 +513,7 @@ def max_humidity(self): return super().max_humidity @property - def hvac_action(self): + def hvac_action(self) -> str | None: """Return the current running hvac operation.""" # This characteristic describes the current mode of a device, # e.g. a thermostat is "heating" a room to 75 degrees Fahrenheit. @@ -516,17 +522,17 @@ def hvac_action(self): return CURRENT_MODE_HOMEKIT_TO_HASS.get(value) @property - def hvac_mode(self): + def hvac_mode(self) -> str: """Return hvac operation ie. heat, cool mode.""" # This characteristic describes the target mode # E.g. should the device start heating a room if the temperature # falls below the target temperature. # Can be 0 - 3 (Off, Heat, Cool, Auto) value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) - return MODE_HOMEKIT_TO_HASS.get(value) + return MODE_HOMEKIT_TO_HASS[value] @property - def hvac_modes(self): + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" valid_values = clamp_enum_to_char( HeatingCoolingTargetValues, @@ -535,7 +541,7 @@ def hvac_modes(self): return [MODE_HOMEKIT_TO_HASS[mode] for mode in valid_values] @property - def supported_features(self): + def supported_features(self) -> int: """Return the list of supported features.""" features = 0 @@ -553,7 +559,7 @@ def supported_features(self): return features @property - def temperature_unit(self): + def temperature_unit(self) -> str: """Return the unit of measurement.""" return TEMP_CELSIUS diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index 6a2200b69eb411..54d265a5f4ad5b 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -1,6 +1,9 @@ """Config flow to configure homekit_controller.""" +from __future__ import annotations + import logging import re +from typing import Any import aiohomekit from aiohomekit.exceptions import AuthenticationError @@ -55,20 +58,21 @@ } -def normalize_hkid(hkid): +def normalize_hkid(hkid: str) -> str: """Normalize a hkid so that it is safe to compare with other normalized hkids.""" return hkid.lower() @callback -def find_existing_host(hass, serial): +def find_existing_host(hass, serial: str) -> config_entries.ConfigEntry | None: """Return a set of the configured hosts.""" for entry in hass.config_entries.async_entries(DOMAIN): if entry.data.get("AccessoryPairingID") == serial: return entry + return None -def ensure_pin_format(pin, allow_insecure_setup_codes=None): +def ensure_pin_format(pin: str, allow_insecure_setup_codes: Any = None) -> str: """ Ensure a pin code is correctly formatted. diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index 7b5f7114170152..8bd5351906c413 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -1,7 +1,11 @@ """Helpers for managing a pairing with a HomeKit accessory or bridge.""" +from __future__ import annotations + import asyncio +from collections.abc import Callable import datetime import logging +from typing import Any from aiohomekit.exceptions import ( AccessoryDisconnectedError, @@ -9,11 +13,11 @@ EncryptionError, ) from aiohomekit.model import Accessories, Accessory -from aiohomekit.model.characteristics import CharacteristicsTypes -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.characteristics import Characteristic, CharacteristicsTypes +from aiohomekit.model.services import Service, ServicesTypes from homeassistant.const import ATTR_VIA_DEVICE -from homeassistant.core import callback +from homeassistant.core import CALLBACK_TYPE, callback from homeassistant.helpers import device_registry as dr from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.event import async_track_time_interval @@ -37,6 +41,10 @@ _LOGGER = logging.getLogger(__name__) +AddAccessoryCb = Callable[[Accessory], bool] +AddServiceCb = Callable[[Service], bool] +AddCharacteristicCb = Callable[[Characteristic], bool] + def valid_serial_number(serial): """Return if the serial number appears to be valid.""" @@ -51,7 +59,7 @@ def valid_serial_number(serial): class HKDevice: """HomeKit device.""" - def __init__(self, hass, config_entry, pairing_data): + def __init__(self, hass, config_entry, pairing_data) -> None: """Initialise a generic HomeKit device.""" self.hass = hass @@ -71,28 +79,28 @@ def __init__(self, hass, config_entry, pairing_data): self.entity_map = Accessories() # A list of callbacks that turn HK accessories into entities - self.accessory_factories = [] + self.accessory_factories: list[AddAccessoryCb] = [] # A list of callbacks that turn HK service metadata into entities - self.listeners = [] + self.listeners: list[AddServiceCb] = [] # A list of callbacks that turn HK characteristics into entities - self.char_factories = [] + self.char_factories: list[AddCharacteristicCb] = [] # The platorms we have forwarded the config entry so far. If a new # accessory is added to a bridge we may have to load additional # platforms. We don't want to load all platforms up front if its just # a lightbulb. And we don't want to forward a config entry twice # (triggers a Config entry already set up error) - self.platforms = set() + self.platforms: set[str] = set() # This just tracks aid/iid pairs so we know if a HK service has been # mapped to a HA entity. - self.entities = [] + self.entities: list[tuple[int, int | None, int | None]] = [] # A map of aid -> device_id # Useful when routing events to triggers - self.devices = {} + self.devices: dict[int, str] = {} self.available = False @@ -100,13 +108,13 @@ def __init__(self, hass, config_entry, pairing_data): # Current values of all characteristics homekit_controller is tracking. # Key is a (accessory_id, characteristic_id) tuple. - self.current_state = {} + self.current_state: dict[tuple[int, int], Any] = {} - self.pollable_characteristics = [] + self.pollable_characteristics: list[tuple[int, int]] = [] # If this is set polling is active and can be disabled by calling # this method. - self._polling_interval_remover = None + self._polling_interval_remover: CALLBACK_TYPE | None = None # Never allow concurrent polling of the same accessory or bridge self._polling_lock = asyncio.Lock() @@ -116,33 +124,37 @@ def __init__(self, hass, config_entry, pairing_data): # This is set to True if we can't rely on serial numbers to be unique self.unreliable_serial_numbers = False - self.watchable_characteristics = [] + self.watchable_characteristics: list[tuple[int, int]] = [] self.pairing.dispatcher_connect(self.process_new_events) - def add_pollable_characteristics(self, characteristics): + def add_pollable_characteristics( + self, characteristics: list[tuple[int, int]] + ) -> None: """Add (aid, iid) pairs that we need to poll.""" self.pollable_characteristics.extend(characteristics) - def remove_pollable_characteristics(self, accessory_id): + def remove_pollable_characteristics(self, accessory_id: int) -> None: """Remove all pollable characteristics by accessory id.""" self.pollable_characteristics = [ char for char in self.pollable_characteristics if char[0] != accessory_id ] - def add_watchable_characteristics(self, characteristics): + def add_watchable_characteristics( + self, characteristics: list[tuple[int, int]] + ) -> None: """Add (aid, iid) pairs that we need to poll.""" self.watchable_characteristics.extend(characteristics) self.hass.async_create_task(self.pairing.subscribe(characteristics)) - def remove_watchable_characteristics(self, accessory_id): + def remove_watchable_characteristics(self, accessory_id: int) -> None: """Remove all pollable characteristics by accessory id.""" self.watchable_characteristics = [ char for char in self.watchable_characteristics if char[0] != accessory_id ] @callback - def async_set_available_state(self, available): + def async_set_available_state(self, available: bool) -> None: """Mark state of all entities on this connection when it becomes available or unavailable.""" _LOGGER.debug( "Called async_set_available_state with %s for %s", available, self.unique_id @@ -152,7 +164,7 @@ def async_set_available_state(self, available): self.available = available self.hass.helpers.dispatcher.async_dispatcher_send(self.signal_state_updated) - async def async_setup(self): + async def async_setup(self) -> bool: """Prepare to use a paired HomeKit device in Home Assistant.""" cache = self.hass.data[ENTITY_MAP].get_map(self.unique_id) if not cache: @@ -214,7 +226,7 @@ def device_info_for_accessory(self, accessory: Accessory) -> DeviceInfo: return device_info @callback - def async_migrate_devices(self): + def async_migrate_devices(self) -> None: """Migrate legacy device entries from 3-tuples to 2-tuples.""" _LOGGER.debug( "Migrating device registry entries for pairing %s", self.unique_id @@ -246,7 +258,7 @@ def async_migrate_devices(self): (DOMAIN, IDENTIFIER_LEGACY_SERIAL_NUMBER, serial_number) ) - device = device_registry.async_get_device(identifiers=identifiers) + device = device_registry.async_get_device(identifiers=identifiers) # type: ignore[arg-type] if not device: continue @@ -286,7 +298,7 @@ def async_migrate_devices(self): ) @callback - def async_create_devices(self): + def async_create_devices(self) -> None: """ Build device registry entries for all accessories paired with the bridge. @@ -315,7 +327,7 @@ def async_create_devices(self): self.devices = devices @callback - def async_detect_workarounds(self): + def async_detect_workarounds(self) -> None: """Detect any workarounds that are needed for this pairing.""" unreliable_serial_numbers = False @@ -354,7 +366,7 @@ def async_detect_workarounds(self): self.unreliable_serial_numbers = unreliable_serial_numbers - async def async_process_entity_map(self): + async def async_process_entity_map(self) -> None: """ Process the entity map and load any platforms or entities that need adding. @@ -388,18 +400,18 @@ async def async_process_entity_map(self): await self.async_update() - async def async_unload(self): + async def async_unload(self) -> None: """Stop interacting with device and prepare for removal from hass.""" if self._polling_interval_remover: self._polling_interval_remover() await self.pairing.close() - return await self.hass.config_entries.async_unload_platforms( + await self.hass.config_entries.async_unload_platforms( self.config_entry, self.platforms ) - async def async_refresh_entity_map(self, config_num): + async def async_refresh_entity_map(self, config_num: int) -> bool: """Handle setup of a HomeKit accessory.""" try: self.accessories = await self.pairing.list_accessories_and_characteristics() @@ -419,26 +431,26 @@ async def async_refresh_entity_map(self, config_num): return True - def add_accessory_factory(self, add_entities_cb): + def add_accessory_factory(self, add_entities_cb) -> None: """Add a callback to run when discovering new entities for accessories.""" self.accessory_factories.append(add_entities_cb) self._add_new_entities_for_accessory([add_entities_cb]) - def _add_new_entities_for_accessory(self, handlers): + def _add_new_entities_for_accessory(self, handlers) -> None: for accessory in self.entity_map.accessories: for handler in handlers: - if (accessory.aid, None) in self.entities: + if (accessory.aid, None, None) in self.entities: continue if handler(accessory): - self.entities.append((accessory.aid, None)) + self.entities.append((accessory.aid, None, None)) break - def add_char_factory(self, add_entities_cb): + def add_char_factory(self, add_entities_cb) -> None: """Add a callback to run when discovering new entities for accessories.""" self.char_factories.append(add_entities_cb) self._add_new_entities_for_char([add_entities_cb]) - def _add_new_entities_for_char(self, handlers): + def _add_new_entities_for_char(self, handlers) -> None: for accessory in self.entity_map.accessories: for service in accessory.services: for char in service.characteristics: @@ -449,33 +461,33 @@ def _add_new_entities_for_char(self, handlers): self.entities.append((accessory.aid, service.iid, char.iid)) break - def add_listener(self, add_entities_cb): + def add_listener(self, add_entities_cb) -> None: """Add a callback to run when discovering new entities for services.""" self.listeners.append(add_entities_cb) self._add_new_entities([add_entities_cb]) - def add_entities(self): + def add_entities(self) -> None: """Process the entity map and create HA entities.""" self._add_new_entities(self.listeners) self._add_new_entities_for_accessory(self.accessory_factories) self._add_new_entities_for_char(self.char_factories) - def _add_new_entities(self, callbacks): + def _add_new_entities(self, callbacks) -> None: for accessory in self.entity_map.accessories: aid = accessory.aid for service in accessory.services: iid = service.iid - if (aid, iid) in self.entities: + if (aid, None, iid) in self.entities: # Don't add the same entity again continue for listener in callbacks: if listener(service): - self.entities.append((aid, iid)) + self.entities.append((aid, None, iid)) break - async def async_load_platform(self, platform): + async def async_load_platform(self, platform: str) -> None: """Load a single platform idempotently.""" if platform in self.platforms: return @@ -489,7 +501,7 @@ async def async_load_platform(self, platform): self.platforms.remove(platform) raise - async def async_load_platforms(self): + async def async_load_platforms(self) -> None: """Load any platforms needed by this HomeKit device.""" tasks = [] for accessory in self.entity_map.accessories: @@ -558,7 +570,7 @@ async def async_update(self, now=None): _LOGGER.debug("Finished HomeKit controller update: %s", self.unique_id) - def process_new_events(self, new_values_dict): + def process_new_events(self, new_values_dict) -> None: """Process events from accessory into HA state.""" self.async_set_available_state(True) @@ -575,11 +587,11 @@ def process_new_events(self, new_values_dict): self.hass.helpers.dispatcher.async_dispatcher_send(self.signal_state_updated) - async def get_characteristics(self, *args, **kwargs): + async def get_characteristics(self, *args, **kwargs) -> dict[str, Any]: """Read latest state from homekit accessory.""" return await self.pairing.get_characteristics(*args, **kwargs) - async def put_characteristics(self, characteristics): + async def put_characteristics(self, characteristics) -> None: """Control a HomeKit device state from Home Assistant.""" results = await self.pairing.put_characteristics(characteristics) @@ -604,7 +616,7 @@ async def put_characteristics(self, characteristics): self.process_new_events(new_entity_state) @property - def unique_id(self): + def unique_id(self) -> str: """ Return a unique id for this accessory or bridge. diff --git a/homeassistant/components/homekit_controller/cover.py b/homeassistant/components/homekit_controller/cover.py index bb6bab4a495895..a1aa8dbd8077ef 100644 --- a/homeassistant/components/homekit_controller/cover.py +++ b/homeassistant/components/homekit_controller/cover.py @@ -1,10 +1,15 @@ """Support for Homekit covers.""" +from __future__ import annotations + +from typing import Any + from aiohomekit.model.characteristics import CharacteristicsTypes -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.services import Service, ServicesTypes from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, + DEVICE_CLASS_GARAGE, SUPPORT_CLOSE, SUPPORT_CLOSE_TILT, SUPPORT_OPEN, @@ -46,7 +51,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_service(service): + def async_add_service(service: Service) -> bool: if not (entity_class := ENTITY_TYPES.get(service.type)): return False info = {"aid": service.accessory.aid, "iid": service.iid} @@ -59,12 +64,9 @@ def async_add_service(service): class HomeKitGarageDoorCover(HomeKitEntity, CoverEntity): """Representation of a HomeKit Garage Door.""" - @property - def device_class(self): - """Define this cover as a garage door.""" - return "garage" + _attr_device_class = DEVICE_CLASS_GARAGE - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.DOOR_STATE_CURRENT, @@ -73,47 +75,47 @@ def get_characteristic_types(self): ] @property - def supported_features(self): + def supported_features(self) -> int: """Flag supported features.""" return SUPPORT_OPEN | SUPPORT_CLOSE @property - def _state(self): + def _state(self) -> str: """Return the current state of the garage door.""" value = self.service.value(CharacteristicsTypes.DOOR_STATE_CURRENT) return CURRENT_GARAGE_STATE_MAP[value] @property - def is_closed(self): + def is_closed(self) -> bool: """Return true if cover is closed, else False.""" return self._state == STATE_CLOSED @property - def is_closing(self): + def is_closing(self) -> bool: """Return if the cover is closing or not.""" return self._state == STATE_CLOSING @property - def is_opening(self): + def is_opening(self) -> bool: """Return if the cover is opening or not.""" return self._state == STATE_OPENING - async def async_open_cover(self, **kwargs): + async def async_open_cover(self, **kwargs: Any) -> None: """Send open command.""" await self.set_door_state(STATE_OPEN) - async def async_close_cover(self, **kwargs): + async def async_close_cover(self, **kwargs: Any) -> None: """Send close command.""" await self.set_door_state(STATE_CLOSED) - async def set_door_state(self, state): + async def set_door_state(self, state: str) -> None: """Send state command.""" await self.async_put_characteristics( {CharacteristicsTypes.DOOR_STATE_TARGET: TARGET_GARAGE_STATE_MAP[state]} ) @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any]: """Return the optional state attributes.""" obstruction_detected = self.service.value( CharacteristicsTypes.OBSTRUCTION_DETECTED @@ -124,7 +126,7 @@ def extra_state_attributes(self): class HomeKitWindowCover(HomeKitEntity, CoverEntity): """Representation of a HomeKit Window or Window Covering.""" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.POSITION_STATE, @@ -139,7 +141,7 @@ def get_characteristic_types(self): ] @property - def supported_features(self): + def supported_features(self) -> int: """Flag supported features.""" features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION @@ -161,45 +163,45 @@ def supported_features(self): return features @property - def current_cover_position(self): + def current_cover_position(self) -> int: """Return the current position of cover.""" return self.service.value(CharacteristicsTypes.POSITION_CURRENT) @property - def is_closed(self): + def is_closed(self) -> bool: """Return true if cover is closed, else False.""" return self.current_cover_position == 0 @property - def is_closing(self): + def is_closing(self) -> bool: """Return if the cover is closing or not.""" value = self.service.value(CharacteristicsTypes.POSITION_STATE) state = CURRENT_WINDOW_STATE_MAP[value] return state == STATE_CLOSING @property - def is_opening(self): + def is_opening(self) -> bool: """Return if the cover is opening or not.""" value = self.service.value(CharacteristicsTypes.POSITION_STATE) state = CURRENT_WINDOW_STATE_MAP[value] return state == STATE_OPENING @property - def is_horizontal_tilt(self): + def is_horizontal_tilt(self) -> bool: """Return True if the service has a horizontal tilt characteristic.""" return ( self.service.value(CharacteristicsTypes.HORIZONTAL_TILT_CURRENT) is not None ) @property - def is_vertical_tilt(self): + def is_vertical_tilt(self) -> bool: """Return True if the service has a vertical tilt characteristic.""" return ( self.service.value(CharacteristicsTypes.VERTICAL_TILT_CURRENT) is not None ) @property - def current_cover_tilt_position(self): + def current_cover_tilt_position(self) -> int: """Return current position of cover tilt.""" tilt_position = self.service.value(CharacteristicsTypes.VERTICAL_TILT_CURRENT) if not tilt_position: @@ -208,26 +210,26 @@ def current_cover_tilt_position(self): ) return tilt_position - async def async_stop_cover(self, **kwargs): + async def async_stop_cover(self, **kwargs: Any) -> None: """Send hold command.""" await self.async_put_characteristics({CharacteristicsTypes.POSITION_HOLD: 1}) - async def async_open_cover(self, **kwargs): + async def async_open_cover(self, **kwargs: Any) -> None: """Send open command.""" await self.async_set_cover_position(position=100) - async def async_close_cover(self, **kwargs): + async def async_close_cover(self, **kwargs: Any) -> None: """Send close command.""" await self.async_set_cover_position(position=0) - async def async_set_cover_position(self, **kwargs): + async def async_set_cover_position(self, **kwargs: Any) -> None: """Send position command.""" position = kwargs[ATTR_POSITION] await self.async_put_characteristics( {CharacteristicsTypes.POSITION_TARGET: position} ) - async def async_set_cover_tilt_position(self, **kwargs): + async def async_set_cover_tilt_position(self, **kwargs: Any) -> None: """Move the cover tilt to a specific position.""" tilt_position = kwargs[ATTR_TILT_POSITION] if self.is_vertical_tilt: @@ -240,7 +242,7 @@ async def async_set_cover_tilt_position(self, **kwargs): ) @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any]: """Return the optional state attributes.""" obstruction_detected = self.service.value( CharacteristicsTypes.OBSTRUCTION_DETECTED diff --git a/homeassistant/components/homekit_controller/fan.py b/homeassistant/components/homekit_controller/fan.py index 0b6f03b18e3722..71d71ce469fc22 100644 --- a/homeassistant/components/homekit_controller/fan.py +++ b/homeassistant/components/homekit_controller/fan.py @@ -1,6 +1,10 @@ """Support for Homekit fans.""" +from __future__ import annotations + +from typing import Any + from aiohomekit.model.characteristics import CharacteristicsTypes -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.services import Service, ServicesTypes from homeassistant.components.fan import ( DIRECTION_FORWARD, @@ -30,9 +34,9 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): # This must be set in subclasses to the name of a boolean characteristic # that controls whether the fan is on or off. - on_characteristic = None + on_characteristic: str - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.SWING_MODE, @@ -42,12 +46,12 @@ def get_characteristic_types(self): ] @property - def is_on(self): + def is_on(self) -> bool: """Return true if device is on.""" return self.service.value(self.on_characteristic) == 1 @property - def percentage(self): + def percentage(self) -> int: """Return the current speed percentage.""" if not self.is_on: return 0 @@ -55,19 +59,19 @@ def percentage(self): return self.service.value(CharacteristicsTypes.ROTATION_SPEED) @property - def current_direction(self): + def current_direction(self) -> str: """Return the current direction of the fan.""" direction = self.service.value(CharacteristicsTypes.ROTATION_DIRECTION) return HK_DIRECTION_TO_HA[direction] @property - def oscillating(self): + def oscillating(self) -> bool: """Return whether or not the fan is currently oscillating.""" oscillating = self.service.value(CharacteristicsTypes.SWING_MODE) return oscillating == 1 @property - def supported_features(self): + def supported_features(self) -> int: """Flag supported features.""" features = 0 @@ -83,20 +87,20 @@ def supported_features(self): return features @property - def speed_count(self): + def speed_count(self) -> int: """Speed count for the fan.""" return round( min(self.service[CharacteristicsTypes.ROTATION_SPEED].maxValue or 100, 100) / max(1, self.service[CharacteristicsTypes.ROTATION_SPEED].minStep or 0) ) - async def async_set_direction(self, direction): + async def async_set_direction(self, direction: str) -> None: """Set the direction of the fan.""" await self.async_put_characteristics( {CharacteristicsTypes.ROTATION_DIRECTION: DIRECTION_TO_HK[direction]} ) - async def async_set_percentage(self, percentage): + async def async_set_percentage(self, percentage: int) -> None: """Set the speed of the fan.""" if percentage == 0: return await self.async_turn_off() @@ -105,17 +109,21 @@ async def async_set_percentage(self, percentage): {CharacteristicsTypes.ROTATION_SPEED: percentage} ) - async def async_oscillate(self, oscillating: bool): + async def async_oscillate(self, oscillating: bool) -> None: """Oscillate the fan.""" await self.async_put_characteristics( {CharacteristicsTypes.SWING_MODE: 1 if oscillating else 0} ) async def async_turn_on( - self, speed=None, percentage=None, preset_mode=None, **kwargs - ): + self, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, + **kwargs: Any, + ) -> None: """Turn the specified fan on.""" - characteristics = {} + characteristics: dict[str, Any] = {} if not self.is_on: characteristics[self.on_characteristic] = True @@ -126,7 +134,7 @@ async def async_turn_on( if characteristics: await self.async_put_characteristics(characteristics) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the specified fan off.""" await self.async_put_characteristics({self.on_characteristic: False}) @@ -159,7 +167,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_service(service): + def async_add_service(service: Service) -> bool: if not (entity_class := ENTITY_TYPES.get(service.type)): return False info = {"aid": service.accessory.aid, "iid": service.iid} diff --git a/homeassistant/components/homekit_controller/humidifier.py b/homeassistant/components/homekit_controller/humidifier.py index bb46c5b2decde8..bc922dd4ec11eb 100644 --- a/homeassistant/components/homekit_controller/humidifier.py +++ b/homeassistant/components/homekit_controller/humidifier.py @@ -1,8 +1,10 @@ """Support for HomeKit Controller humidifier.""" from __future__ import annotations +from typing import Any + from aiohomekit.model.characteristics import CharacteristicsTypes -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.services import Service, ServicesTypes from homeassistant.components.humidifier import HumidifierDeviceClass, HumidifierEntity from homeassistant.components.humidifier.const import ( @@ -37,7 +39,7 @@ class HomeKitHumidifier(HomeKitEntity, HumidifierEntity): _attr_device_class = HumidifierDeviceClass.HUMIDIFIER - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.ACTIVE, @@ -47,20 +49,20 @@ def get_characteristic_types(self): ] @property - def supported_features(self): + def supported_features(self) -> int: """Return the list of supported features.""" return SUPPORT_FLAGS | SUPPORT_MODES @property - def is_on(self): + def is_on(self) -> bool: """Return true if device is on.""" return self.service.value(CharacteristicsTypes.ACTIVE) - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the specified valve on.""" await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: True}) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the specified valve off.""" await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: False}) @@ -138,7 +140,7 @@ class HomeKitDehumidifier(HomeKitEntity, HumidifierEntity): _attr_device_class = HumidifierDeviceClass.DEHUMIDIFIER - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.ACTIVE, @@ -149,20 +151,20 @@ def get_characteristic_types(self): ] @property - def supported_features(self): + def supported_features(self) -> int: """Return the list of supported features.""" return SUPPORT_FLAGS | SUPPORT_MODES @property - def is_on(self): + def is_on(self) -> bool: """Return true if device is on.""" return self.service.value(CharacteristicsTypes.ACTIVE) - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the specified valve on.""" await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: True}) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the specified valve off.""" await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: False}) @@ -251,13 +253,13 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_service(service): + def async_add_service(service: Service) -> bool: if service.type != ServicesTypes.HUMIDIFIER_DEHUMIDIFIER: return False info = {"aid": service.accessory.aid, "iid": service.iid} - entities = [] + entities: list[HumidifierEntity] = [] if service.has(CharacteristicsTypes.RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD): entities.append(HomeKitHumidifier(conn, info)) diff --git a/homeassistant/components/homekit_controller/light.py b/homeassistant/components/homekit_controller/light.py index 2e2d60750d84a7..2b82f27022b246 100644 --- a/homeassistant/components/homekit_controller/light.py +++ b/homeassistant/components/homekit_controller/light.py @@ -1,6 +1,10 @@ """Support for Homekit lights.""" +from __future__ import annotations + +from typing import Any + from aiohomekit.model.characteristics import CharacteristicsTypes -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.services import Service, ServicesTypes from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -28,7 +32,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_service(service): + def async_add_service(service: Service) -> bool: if service.type != ServicesTypes.LIGHTBULB: return False info = {"aid": service.accessory.aid, "iid": service.iid} @@ -41,7 +45,7 @@ def async_add_service(service): class HomeKitLight(HomeKitEntity, LightEntity): """Representation of a Homekit light.""" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.ON, @@ -52,17 +56,17 @@ def get_characteristic_types(self): ] @property - def is_on(self): + def is_on(self) -> bool: """Return true if device is on.""" return self.service.value(CharacteristicsTypes.ON) @property - def brightness(self): + def brightness(self) -> int: """Return the brightness of this light between 0..255.""" return self.service.value(CharacteristicsTypes.BRIGHTNESS) * 255 / 100 @property - def hs_color(self): + def hs_color(self) -> tuple[float, float]: """Return the color property.""" return ( self.service.value(CharacteristicsTypes.HUE), @@ -70,12 +74,12 @@ def hs_color(self): ) @property - def color_temp(self): + def color_temp(self) -> int: """Return the color temperature.""" return self.service.value(CharacteristicsTypes.COLOR_TEMPERATURE) @property - def supported_features(self): + def supported_features(self) -> int: """Flag supported features.""" features = 0 @@ -93,7 +97,7 @@ def supported_features(self): return features - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the specified light on.""" hs_color = kwargs.get(ATTR_HS_COLOR) temperature = kwargs.get(ATTR_COLOR_TEMP) @@ -121,6 +125,6 @@ async def async_turn_on(self, **kwargs): await self.async_put_characteristics(characteristics) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the specified light off.""" await self.async_put_characteristics({CharacteristicsTypes.ON: False}) diff --git a/homeassistant/components/homekit_controller/lock.py b/homeassistant/components/homekit_controller/lock.py index a3248e3fa02d8c..248bb93a68f6c8 100644 --- a/homeassistant/components/homekit_controller/lock.py +++ b/homeassistant/components/homekit_controller/lock.py @@ -1,6 +1,10 @@ """Support for HomeKit Controller locks.""" +from __future__ import annotations + +from typing import Any + from aiohomekit.model.characteristics import CharacteristicsTypes -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.services import Service, ServicesTypes from homeassistant.components.lock import STATE_JAMMED, LockEntity from homeassistant.config_entries import ConfigEntry @@ -37,7 +41,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_service(service): + def async_add_service(service: Service) -> bool: if service.type != ServicesTypes.LOCK_MECHANISM: return False info = {"aid": service.accessory.aid, "iid": service.iid} @@ -50,7 +54,7 @@ def async_add_service(service): class HomeKitLock(HomeKitEntity, LockEntity): """Representation of a HomeKit Controller Lock.""" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE, @@ -59,7 +63,7 @@ def get_characteristic_types(self): ] @property - def is_locked(self): + def is_locked(self) -> bool | None: """Return true if device is locked.""" value = self.service.value(CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE) if CURRENT_STATE_MAP[value] == STATE_UNKNOWN: @@ -67,7 +71,7 @@ def is_locked(self): return CURRENT_STATE_MAP[value] == STATE_LOCKED @property - def is_locking(self): + def is_locking(self) -> bool: """Return true if device is locking.""" current_value = self.service.value( CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE @@ -81,7 +85,7 @@ def is_locking(self): ) @property - def is_unlocking(self): + def is_unlocking(self) -> bool: """Return true if device is unlocking.""" current_value = self.service.value( CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE @@ -95,27 +99,27 @@ def is_unlocking(self): ) @property - def is_jammed(self): + def is_jammed(self) -> bool: """Return true if device is jammed.""" value = self.service.value(CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE) return CURRENT_STATE_MAP[value] == STATE_JAMMED - async def async_lock(self, **kwargs): + async def async_lock(self, **kwargs: Any) -> None: """Lock the device.""" await self._set_lock_state(STATE_LOCKED) - async def async_unlock(self, **kwargs): + async def async_unlock(self, **kwargs: Any) -> None: """Unlock the device.""" await self._set_lock_state(STATE_UNLOCKED) - async def _set_lock_state(self, state): + async def _set_lock_state(self, state: str) -> None: """Send state command.""" await self.async_put_characteristics( {CharacteristicsTypes.LOCK_MECHANISM_TARGET_STATE: TARGET_STATE_MAP[state]} ) @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any]: """Return the optional state attributes.""" attributes = {} diff --git a/homeassistant/components/homekit_controller/media_player.py b/homeassistant/components/homekit_controller/media_player.py index dda763c4ae5ffd..7318343b643d97 100644 --- a/homeassistant/components/homekit_controller/media_player.py +++ b/homeassistant/components/homekit_controller/media_player.py @@ -1,4 +1,6 @@ """Support for HomeKit Controller Televisions.""" +from __future__ import annotations + import logging from aiohomekit.model.characteristics import ( @@ -7,7 +9,7 @@ RemoteKeyValues, TargetMediaStateValues, ) -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.services import Service, ServicesTypes from aiohomekit.utils import clamp_enum_to_char from homeassistant.components.media_player import ( @@ -53,7 +55,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_service(service): + def async_add_service(service: Service) -> bool: if service.type != ServicesTypes.TELEVISION: return False info = {"aid": service.accessory.aid, "iid": service.iid} @@ -68,7 +70,7 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): _attr_device_class = MediaPlayerDeviceClass.TV - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.ACTIVE, @@ -82,7 +84,7 @@ def get_characteristic_types(self): ] @property - def supported_features(self): + def supported_features(self) -> int: """Flag media player features that are supported.""" features = 0 @@ -108,10 +110,10 @@ def supported_features(self): return features @property - def supported_media_states(self): + def supported_media_states(self) -> set[TargetMediaStateValues]: """Mediate state flags that are supported.""" if not self.service.has(CharacteristicsTypes.TARGET_MEDIA_STATE): - return frozenset() + return set() return clamp_enum_to_char( TargetMediaStateValues, @@ -119,17 +121,17 @@ def supported_media_states(self): ) @property - def supported_remote_keys(self): + def supported_remote_keys(self) -> set[str]: """Remote key buttons that are supported.""" if not self.service.has(CharacteristicsTypes.REMOTE_KEY): - return frozenset() + return set() return clamp_enum_to_char( RemoteKeyValues, self.service[CharacteristicsTypes.REMOTE_KEY] ) @property - def source_list(self): + def source_list(self) -> list[str]: """List of all input sources for this television.""" sources = [] @@ -147,7 +149,7 @@ def source_list(self): return sources @property - def source(self): + def source(self) -> str | None: """Name of the current input source.""" active_identifier = self.service.value(CharacteristicsTypes.ACTIVE_IDENTIFIER) if not active_identifier: @@ -165,7 +167,7 @@ def source(self): return char.value @property - def state(self): + def state(self) -> str: """State of the tv.""" active = self.service.value(CharacteristicsTypes.ACTIVE) if not active: @@ -177,7 +179,7 @@ def state(self): return STATE_OK - async def async_media_play(self): + async def async_media_play(self) -> None: """Send play command.""" if self.state == STATE_PLAYING: _LOGGER.debug("Cannot play while already playing") @@ -192,7 +194,7 @@ async def async_media_play(self): {CharacteristicsTypes.REMOTE_KEY: RemoteKeyValues.PLAY_PAUSE} ) - async def async_media_pause(self): + async def async_media_pause(self) -> None: """Send pause command.""" if self.state == STATE_PAUSED: _LOGGER.debug("Cannot pause while already paused") @@ -207,7 +209,7 @@ async def async_media_pause(self): {CharacteristicsTypes.REMOTE_KEY: RemoteKeyValues.PLAY_PAUSE} ) - async def async_media_stop(self): + async def async_media_stop(self) -> None: """Send stop command.""" if self.state == STATE_IDLE: _LOGGER.debug("Cannot stop when already idle") @@ -218,7 +220,7 @@ async def async_media_stop(self): {CharacteristicsTypes.TARGET_MEDIA_STATE: TargetMediaStateValues.STOP} ) - async def async_select_source(self, source): + async def async_select_source(self, source: str) -> None: """Switch to a different media source.""" this_accessory = self._accessory.entity_map.aid(self._aid) this_tv = this_accessory.services.iid(self._iid) diff --git a/homeassistant/components/homekit_controller/number.py b/homeassistant/components/homekit_controller/number.py index 9c28bc8ebddab9..a473e30fe6d494 100644 --- a/homeassistant/components/homekit_controller/number.py +++ b/homeassistant/components/homekit_controller/number.py @@ -13,8 +13,10 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType from . import KNOWN_DEVICES, CharacteristicEntity +from .connection import HKDevice NUMBER_ENTITIES: dict[str, NumberEntityDescription] = { CharacteristicsTypes.VENDOR_VOCOLINC_HUMIDIFIER_SPRAY_LEVEL: NumberEntityDescription( @@ -90,7 +92,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_characteristic(char: Characteristic): + def async_add_characteristic(char: Characteristic) -> bool: entities = [] info = {"aid": char.service.accessory.aid, "iid": char.service.iid} @@ -112,11 +114,11 @@ class HomeKitNumber(CharacteristicEntity, NumberEntity): def __init__( self, - conn, - info, - char, + conn: HKDevice, + info: ConfigType, + char: Characteristic, description: NumberEntityDescription, - ): + ) -> None: """Initialise a HomeKit number control.""" self.entity_description = description super().__init__(conn, info, char) @@ -128,7 +130,7 @@ def name(self) -> str | None: return f"{prefix} {self.entity_description.name}" return self.entity_description.name - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [self._char.type] @@ -152,7 +154,7 @@ def value(self) -> float: """Return the current characteristic value.""" return self._char.value - async def async_set_value(self, value: float): + async def async_set_value(self, value: float) -> None: """Set the characteristic to this value.""" await self.async_put_characteristics( { @@ -164,7 +166,7 @@ async def async_set_value(self, value: float): class HomeKitEcobeeFanModeNumber(CharacteristicEntity, NumberEntity): """Representation of a Number control for Ecobee Fan Mode request.""" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [self._char.type] @@ -196,7 +198,7 @@ def value(self) -> float: """Return the current characteristic value.""" return self._char.value - async def async_set_value(self, value: float): + async def async_set_value(self, value: float) -> None: """Set the characteristic to this value.""" # Sending the fan mode request sometimes ends up getting ignored by ecobee diff --git a/homeassistant/components/homekit_controller/select.py b/homeassistant/components/homekit_controller/select.py index 82ae3aff691287..681f24b9ab8e24 100644 --- a/homeassistant/components/homekit_controller/select.py +++ b/homeassistant/components/homekit_controller/select.py @@ -32,7 +32,7 @@ def name(self) -> str: return f"{name} Current Mode" return "Current Mode" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.VENDOR_ECOBEE_CURRENT_MODE, @@ -61,7 +61,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_characteristic(char: Characteristic): + def async_add_characteristic(char: Characteristic) -> bool: if char.type == CharacteristicsTypes.VENDOR_ECOBEE_CURRENT_MODE: info = {"aid": char.service.accessory.aid, "iid": char.service.iid} async_add_entities([EcobeeModeSelect(conn, info, char)]) diff --git a/homeassistant/components/homekit_controller/sensor.py b/homeassistant/components/homekit_controller/sensor.py index 13491c8ee0373a..b7d7b8005ed978 100644 --- a/homeassistant/components/homekit_controller/sensor.py +++ b/homeassistant/components/homekit_controller/sensor.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from aiohomekit.model.characteristics import Characteristic, CharacteristicsTypes -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.services import Service, ServicesTypes from homeassistant.components.sensor import ( SensorDeviceClass, @@ -28,8 +28,10 @@ ) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType from . import KNOWN_DEVICES, CharacteristicEntity, HomeKitEntity +from .connection import HKDevice CO2_ICON = "mdi:molecule-co2" @@ -203,17 +205,17 @@ class HomeKitHumiditySensor(HomeKitEntity, SensorEntity): _attr_device_class = SensorDeviceClass.HUMIDITY _attr_native_unit_of_measurement = PERCENTAGE - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT] @property - def name(self): + def name(self) -> str: """Return the name of the device.""" return f"{super().name} Humidity" @property - def native_value(self): + def native_value(self) -> float: """Return the current humidity.""" return self.service.value(CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT) @@ -224,17 +226,17 @@ class HomeKitTemperatureSensor(HomeKitEntity, SensorEntity): _attr_device_class = SensorDeviceClass.TEMPERATURE _attr_native_unit_of_measurement = TEMP_CELSIUS - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [CharacteristicsTypes.TEMPERATURE_CURRENT] @property - def name(self): + def name(self) -> str: """Return the name of the device.""" return f"{super().name} Temperature" @property - def native_value(self): + def native_value(self) -> float: """Return the current temperature in Celsius.""" return self.service.value(CharacteristicsTypes.TEMPERATURE_CURRENT) @@ -245,17 +247,17 @@ class HomeKitLightSensor(HomeKitEntity, SensorEntity): _attr_device_class = SensorDeviceClass.ILLUMINANCE _attr_native_unit_of_measurement = LIGHT_LUX - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [CharacteristicsTypes.LIGHT_LEVEL_CURRENT] @property - def name(self): + def name(self) -> str: """Return the name of the device.""" return f"{super().name} Light Level" @property - def native_value(self): + def native_value(self) -> int: """Return the current light level in lux.""" return self.service.value(CharacteristicsTypes.LIGHT_LEVEL_CURRENT) @@ -266,17 +268,17 @@ class HomeKitCarbonDioxideSensor(HomeKitEntity, SensorEntity): _attr_icon = CO2_ICON _attr_native_unit_of_measurement = CONCENTRATION_PARTS_PER_MILLION - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [CharacteristicsTypes.CARBON_DIOXIDE_LEVEL] @property - def name(self): + def name(self) -> str: """Return the name of the device.""" return f"{super().name} CO2" @property - def native_value(self): + def native_value(self) -> int: """Return the current CO2 level in ppm.""" return self.service.value(CharacteristicsTypes.CARBON_DIOXIDE_LEVEL) @@ -287,7 +289,7 @@ class HomeKitBatterySensor(HomeKitEntity, SensorEntity): _attr_device_class = SensorDeviceClass.BATTERY _attr_native_unit_of_measurement = PERCENTAGE - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [ CharacteristicsTypes.BATTERY_LEVEL, @@ -296,12 +298,12 @@ def get_characteristic_types(self): ] @property - def name(self): + def name(self) -> str: """Return the name of the device.""" return f"{super().name} Battery" @property - def icon(self): + def icon(self) -> str: """Return the sensor icon.""" if not self.available or self.state is None: return "mdi:battery-unknown" @@ -323,12 +325,12 @@ def icon(self): return icon @property - def is_low_battery(self): + def is_low_battery(self) -> bool: """Return true if battery level is low.""" return self.service.value(CharacteristicsTypes.STATUS_LO_BATT) == 1 @property - def is_charging(self): + def is_charging(self) -> bool: """Return true if currently charing.""" # 0 = not charging # 1 = charging @@ -336,7 +338,7 @@ def is_charging(self): return self.service.value(CharacteristicsTypes.CHARGING_STATE) == 1 @property - def native_value(self): + def native_value(self) -> int: """Return the current battery level percentage.""" return self.service.value(CharacteristicsTypes.BATTERY_LEVEL) @@ -356,16 +358,16 @@ class SimpleSensor(CharacteristicEntity, SensorEntity): def __init__( self, - conn, - info, - char, + conn: HKDevice, + info: ConfigType, + char: Characteristic, description: HomeKitSensorEntityDescription, - ): + ) -> None: """Initialise a secondary HomeKit characteristic sensor.""" self.entity_description = description super().__init__(conn, info, char) - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" return [self._char.type] @@ -375,7 +377,7 @@ def name(self) -> str: return f"{super().name} {self.entity_description.name}" @property - def native_value(self): + def native_value(self) -> str | int | float: """Return the current sensor value.""" return self._char.value @@ -399,7 +401,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_service(service): + def async_add_service(service: Service) -> bool: if not (entity_class := ENTITY_TYPES.get(service.type)): return False info = {"aid": service.accessory.aid, "iid": service.iid} @@ -409,7 +411,7 @@ def async_add_service(service): conn.add_listener(async_add_service) @callback - def async_add_characteristic(char: Characteristic): + def async_add_characteristic(char: Characteristic) -> bool: if not (description := SIMPLE_SENSOR.get(char.type)): return False if description.probe and not description.probe(char): diff --git a/homeassistant/components/homekit_controller/storage.py b/homeassistant/components/homekit_controller/storage.py index 4d512fbbc5ddee..fc6970f078a710 100644 --- a/homeassistant/components/homekit_controller/storage.py +++ b/homeassistant/components/homekit_controller/storage.py @@ -1,6 +1,10 @@ """Helpers for HomeKit data stored in HA storage.""" -from homeassistant.core import callback +from __future__ import annotations + +from typing import Any, TypedDict, cast + +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.storage import Store from .const import DOMAIN @@ -10,6 +14,19 @@ ENTITY_MAP_SAVE_DELAY = 10 +class Pairing(TypedDict): + """A versioned map of entity metadata as presented by aiohomekit.""" + + config_num: int + accessories: list[Any] + + +class StorageLayout(TypedDict): + """Cached pairing metadata needed by aiohomekit.""" + + pairings: dict[str, Pairing] + + class EntityMapStorage: """ Holds a cache of entity structure data from a paired HomeKit device. @@ -26,34 +43,36 @@ class EntityMapStorage: very slow for these devices. """ - def __init__(self, hass): + def __init__(self, hass: HomeAssistant) -> None: """Create a new entity map store.""" self.hass = hass self.store = Store(hass, ENTITY_MAP_STORAGE_VERSION, ENTITY_MAP_STORAGE_KEY) - self.storage_data = {} + self.storage_data: dict[str, Pairing] = {} - async def async_initialize(self): + async def async_initialize(self) -> None: """Get the pairing cache data.""" - if not (raw_storage := await self.store.async_load()): + if not (raw_storage := cast(StorageLayout, await self.store.async_load())): # There is no cached data about HomeKit devices yet return self.storage_data = raw_storage.get("pairings", {}) - def get_map(self, homekit_id): + def get_map(self, homekit_id) -> Pairing | None: """Get a pairing cache item.""" return self.storage_data.get(homekit_id) @callback - def async_create_or_update_map(self, homekit_id, config_num, accessories): + def async_create_or_update_map( + self, homekit_id: str, config_num: int, accessories: list[Any] + ) -> Pairing: """Create a new pairing cache.""" - data = {"config_num": config_num, "accessories": accessories} + data = Pairing(config_num=config_num, accessories=accessories) self.storage_data[homekit_id] = data self._async_schedule_save() return data @callback - def async_delete_map(self, homekit_id): + def async_delete_map(self, homekit_id: str) -> None: """Delete pairing cache.""" if homekit_id not in self.storage_data: return @@ -62,11 +81,11 @@ def async_delete_map(self, homekit_id): self._async_schedule_save() @callback - def _async_schedule_save(self): + def _async_schedule_save(self) -> None: """Schedule saving the entity map cache.""" self.store.async_delay_save(self._data_to_save, ENTITY_MAP_SAVE_DELAY) @callback - def _data_to_save(self): + def _data_to_save(self) -> dict[str, Any]: """Return data of entity map to store in a file.""" return {"pairings": self.storage_data} diff --git a/homeassistant/components/homekit_controller/switch.py b/homeassistant/components/homekit_controller/switch.py index c681ca4d288c5a..07d0e21e59f5bb 100644 --- a/homeassistant/components/homekit_controller/switch.py +++ b/homeassistant/components/homekit_controller/switch.py @@ -2,6 +2,7 @@ from __future__ import annotations from dataclasses import dataclass +from typing import Any from aiohomekit.model.characteristics import ( Characteristic, @@ -9,15 +10,17 @@ InUseValues, IsConfiguredValues, ) -from aiohomekit.model.services import ServicesTypes +from aiohomekit.model.services import Service, ServicesTypes from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType from . import KNOWN_DEVICES, CharacteristicEntity, HomeKitEntity +from .connection import HKDevice OUTLET_IN_USE = "outlet_in_use" @@ -53,35 +56,36 @@ class DeclarativeSwitchEntityDescription(SwitchEntityDescription): class HomeKitSwitch(HomeKitEntity, SwitchEntity): """Representation of a Homekit switch.""" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [CharacteristicsTypes.ON, CharacteristicsTypes.OUTLET_IN_USE] @property - def is_on(self): + def is_on(self) -> bool: """Return true if device is on.""" return self.service.value(CharacteristicsTypes.ON) - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the specified switch on.""" await self.async_put_characteristics({CharacteristicsTypes.ON: True}) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the specified switch off.""" await self.async_put_characteristics({CharacteristicsTypes.ON: False}) @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the optional state attributes.""" outlet_in_use = self.service.value(CharacteristicsTypes.OUTLET_IN_USE) if outlet_in_use is not None: return {OUTLET_IN_USE: outlet_in_use} + return None class HomeKitValve(HomeKitEntity, SwitchEntity): """Represents a valve in an irrigation system.""" - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.ACTIVE, @@ -90,11 +94,11 @@ def get_characteristic_types(self): CharacteristicsTypes.REMAINING_DURATION, ] - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the specified valve on.""" await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: True}) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the specified valve off.""" await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: False}) @@ -104,12 +108,12 @@ def icon(self) -> str: return "mdi:water" @property - def is_on(self): + def is_on(self) -> bool: """Return true if device is on.""" return self.service.value(CharacteristicsTypes.ACTIVE) @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any]: """Return the optional state attributes.""" attrs = {} @@ -133,13 +137,13 @@ class DeclarativeCharacteristicSwitch(CharacteristicEntity, SwitchEntity): def __init__( self, - conn, - info, - char, + conn: HKDevice, + info: ConfigType, + char: Characteristic, description: DeclarativeSwitchEntityDescription, - ): + ) -> None: """Initialise a HomeKit switch.""" - self.entity_description = description + self.entity_description: DeclarativeSwitchEntityDescription = description super().__init__(conn, info, char) @property @@ -149,22 +153,22 @@ def name(self) -> str | None: return f"{prefix} {self.entity_description.name}" return self.entity_description.name - def get_characteristic_types(self): + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [self._char.type] @property - def is_on(self): + def is_on(self) -> bool: """Return true if device is on.""" return self._char.value == self.entity_description.true_value - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the specified switch on.""" await self.async_put_characteristics( {self._char.type: self.entity_description.true_value} ) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the specified switch off.""" await self.async_put_characteristics( {self._char.type: self.entity_description.false_value} @@ -188,7 +192,7 @@ async def async_setup_entry( conn = hass.data[KNOWN_DEVICES][hkid] @callback - def async_add_service(service): + def async_add_service(service: Service) -> bool: if not (entity_class := ENTITY_TYPES.get(service.type)): return False info = {"aid": service.accessory.aid, "iid": service.iid} @@ -198,7 +202,7 @@ def async_add_service(service): conn.add_listener(async_add_service) @callback - def async_add_characteristic(char: Characteristic): + def async_add_characteristic(char: Characteristic) -> bool: if not (description := SWITCH_ENTITIES.get(char.type)): return False From 3718d7fca8d31d60582661238ea6e9ed024a52d7 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Tue, 1 Feb 2022 21:06:03 +0100 Subject: [PATCH 0176/1098] Add Netatmo error logging when no public stations are available (#65298) * Log error if public stations don't provide data Signed-off-by: cgtobi * Only log once Signed-off-by: cgtobi * Update homeassistant/components/netatmo/sensor.py Co-authored-by: Shay Levy Co-authored-by: Shay Levy --- homeassistant/components/netatmo/sensor.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index 7c600f6b44218a..41ae27b2992d5f 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -839,15 +839,16 @@ def async_update_callback(self) -> None: elif self.entity_description.key == "guststrength": data = self._data.get_latest_gust_strengths() - if data is None: - if self.state is None: - return - _LOGGER.debug( - "No station provides %s data in the area %s", - self.entity_description.key, - self._area_name, - ) - self._attr_native_value = None + if not data: + if self.available: + _LOGGER.error( + "No station provides %s data in the area %s", + self.entity_description.key, + self._area_name, + ) + self._attr_native_value = None + + self._attr_available = False return if values := [x for x in data.values() if x is not None]: From 476a694248c6b05be75c788117aacf6a27fd1fc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20St=C3=A5hl?= Date: Tue, 1 Feb 2022 22:30:28 +0100 Subject: [PATCH 0177/1098] Sort Apple TV app list by name (#65386) --- homeassistant/components/apple_tv/media_player.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/apple_tv/media_player.py b/homeassistant/components/apple_tv/media_player.py index 8abab0e0225a16..b62975a97464c7 100644 --- a/homeassistant/components/apple_tv/media_player.py +++ b/homeassistant/components/apple_tv/media_player.py @@ -162,7 +162,10 @@ async def _update_app_list(self): except exceptions.ProtocolError: _LOGGER.exception("Failed to update app list") else: - self._app_list = {app.name: app.identifier for app in apps} + self._app_list = { + app.name: app.identifier + for app in sorted(apps, key=lambda app: app.name.lower()) + } self.async_write_ha_state() @callback From e2935b55ae35a4e8ea077a34857e22331cb6937b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20St=C3=A5hl?= Date: Tue, 1 Feb 2022 22:44:06 +0100 Subject: [PATCH 0178/1098] Fix disconnect bug in Apple TV integration (#65385) --- homeassistant/components/apple_tv/__init__.py | 3 --- homeassistant/components/apple_tv/media_player.py | 3 --- 2 files changed, 6 deletions(-) diff --git a/homeassistant/components/apple_tv/__init__.py b/homeassistant/components/apple_tv/__init__.py index deb65c1f0a8718..bd511d84eb5364 100644 --- a/homeassistant/components/apple_tv/__init__.py +++ b/homeassistant/components/apple_tv/__init__.py @@ -179,7 +179,6 @@ def connection_closed(self): def _handle_disconnect(self): """Handle that the device disconnected and restart connect loop.""" if self.atv: - self.atv.listener = None self.atv.close() self.atv = None self._dispatch_send(SIGNAL_DISCONNECTED) @@ -196,8 +195,6 @@ async def disconnect(self): self._is_on = False try: if self.atv: - self.atv.push_updater.listener = None - self.atv.push_updater.stop() self.atv.close() self.atv = None if self._task: diff --git a/homeassistant/components/apple_tv/media_player.py b/homeassistant/components/apple_tv/media_player.py index b62975a97464c7..1f8cabb1d1487e 100644 --- a/homeassistant/components/apple_tv/media_player.py +++ b/homeassistant/components/apple_tv/media_player.py @@ -171,9 +171,6 @@ async def _update_app_list(self): @callback def async_device_disconnected(self): """Handle when connection was lost to device.""" - self.atv.push_updater.stop() - self.atv.push_updater.listener = None - self.atv.power.listener = None self._attr_supported_features = SUPPORT_APPLE_TV @property From 73189ead1f471ea500b473da13b600fafdfce684 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 1 Feb 2022 16:51:28 -0600 Subject: [PATCH 0179/1098] Handle brightness being None for senseme (#65372) --- homeassistant/components/senseme/light.py | 3 ++- tests/components/senseme/test_light.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/senseme/light.py b/homeassistant/components/senseme/light.py index 75d853c4001705..3036dc1d04dd66 100644 --- a/homeassistant/components/senseme/light.py +++ b/homeassistant/components/senseme/light.py @@ -51,7 +51,8 @@ def __init__(self, device: SensemeDevice, name: str) -> None: def _async_update_attrs(self) -> None: """Update attrs from device.""" self._attr_is_on = self._device.light_on - self._attr_brightness = int(min(255, self._device.light_brightness * 16)) + if self._device.light_brightness is not None: + self._attr_brightness = int(min(255, self._device.light_brightness * 16)) async def async_turn_on(self, **kwargs: Any) -> None: """Turn on the light.""" diff --git a/tests/components/senseme/test_light.py b/tests/components/senseme/test_light.py index 21811452610715..c585cfc31bf423 100644 --- a/tests/components/senseme/test_light.py +++ b/tests/components/senseme/test_light.py @@ -74,6 +74,21 @@ async def test_fan_light(hass: HomeAssistant) -> None: assert device.light_on is True +async def test_fan_light_no_brightness(hass: HomeAssistant) -> None: + """Test a fan light without brightness.""" + device = _mock_device() + device.brightness = None + await _setup_mocked_entry(hass, device) + entity_id = "light.haiku_fan" + + state = hass.states.get(entity_id) + assert state.state == STATE_ON + attributes = state.attributes + assert attributes[ATTR_BRIGHTNESS] == 255 + assert attributes[ATTR_COLOR_MODE] == COLOR_MODE_BRIGHTNESS + assert attributes[ATTR_SUPPORTED_COLOR_MODES] == [COLOR_MODE_BRIGHTNESS] + + async def test_standalone_light(hass: HomeAssistant) -> None: """Test a standalone light.""" device = _mock_device() From ff2f135f553506f2d3a8930bc4d148d439706fdb Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 2 Feb 2022 00:15:13 +0000 Subject: [PATCH 0180/1098] [ci skip] Translation update --- .../components/dialogflow/translations/zh-Hans.json | 3 +++ .../components/geofency/translations/zh-Hans.json | 3 +++ .../components/gpslogger/translations/zh-Hans.json | 3 +++ .../components/homekit/translations/zh-Hans.json | 10 ++++++++-- .../translations/select.zh-Hans.json | 9 +++++++++ .../homekit_controller/translations/zh-Hans.json | 6 +++--- .../components/ifttt/translations/zh-Hans.json | 1 + .../components/locative/translations/zh-Hans.json | 3 +++ .../components/mailgun/translations/zh-Hans.json | 3 +++ .../components/synology_dsm/translations/el.json | 3 +++ .../components/traccar/translations/zh-Hans.json | 7 +++++++ .../components/twilio/translations/zh-Hans.json | 3 +++ 12 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 homeassistant/components/homekit_controller/translations/select.zh-Hans.json create mode 100644 homeassistant/components/traccar/translations/zh-Hans.json diff --git a/homeassistant/components/dialogflow/translations/zh-Hans.json b/homeassistant/components/dialogflow/translations/zh-Hans.json index ae414f99e5540c..a67c1ab76e1c9c 100644 --- a/homeassistant/components/dialogflow/translations/zh-Hans.json +++ b/homeassistant/components/dialogflow/translations/zh-Hans.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "cloud_not_connected": "\u672a\u8fde\u63a5\u81f3 Home Assistant Cloud\u3002" + }, "create_entry": { "default": "\u8981\u5411 Home Assistant \u53d1\u9001\u4e8b\u4ef6\uff0c\u60a8\u9700\u8981\u914d\u7f6e [Dialogflow \u7684 Webhook \u96c6\u6210]({dialogflow_url})\u3002\n\n\u586b\u5199\u4ee5\u4e0b\u4fe1\u606f\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\u8bf7\u53c2\u9605[\u6587\u6863]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u591a\u4fe1\u606f\u3002" }, diff --git a/homeassistant/components/geofency/translations/zh-Hans.json b/homeassistant/components/geofency/translations/zh-Hans.json index d6355fd3809e85..9a04764873cb7e 100644 --- a/homeassistant/components/geofency/translations/zh-Hans.json +++ b/homeassistant/components/geofency/translations/zh-Hans.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "cloud_not_connected": "\u672a\u8fde\u63a5\u81f3 Home Assistant Cloud\u3002" + }, "create_entry": { "default": "\u8981\u5411 Home Assistant \u53d1\u9001\u4e8b\u4ef6\uff0c\u60a8\u9700\u8981\u914d\u7f6e Geofency \u7684 Webhook \u529f\u80fd\u3002\n\n\u586b\u5199\u4ee5\u4e0b\u4fe1\u606f\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u8bf7\u53c2\u9605[\u6587\u6863]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u591a\u4fe1\u606f\u3002" }, diff --git a/homeassistant/components/gpslogger/translations/zh-Hans.json b/homeassistant/components/gpslogger/translations/zh-Hans.json index 343acd4b5d9194..06c91921d408f8 100644 --- a/homeassistant/components/gpslogger/translations/zh-Hans.json +++ b/homeassistant/components/gpslogger/translations/zh-Hans.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "cloud_not_connected": "\u672a\u8fde\u63a5\u81f3 Home Assistant Cloud\u3002" + }, "create_entry": { "default": "\u8981\u5411 Home Assistant \u53d1\u9001\u4e8b\u4ef6\uff0c\u60a8\u9700\u8981\u914d\u7f6e GPSLogger \u7684 Webhook \u529f\u80fd\u3002\n\n\u586b\u5199\u4ee5\u4e0b\u4fe1\u606f\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u8bf7\u53c2\u9605[\u6587\u6863]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u591a\u4fe1\u606f\u3002" }, diff --git a/homeassistant/components/homekit/translations/zh-Hans.json b/homeassistant/components/homekit/translations/zh-Hans.json index 73875ea0423332..5dd3cb32bf4986 100644 --- a/homeassistant/components/homekit/translations/zh-Hans.json +++ b/homeassistant/components/homekit/translations/zh-Hans.json @@ -5,7 +5,7 @@ }, "step": { "pairing": { - "description": "\u4e00\u65e6 {name} \u51c6\u5907\u5c31\u7eea\uff0c\u5c31\u53ef\u4ee5\u5728\u201c\u901a\u77e5\u201d\u627e\u5230\u201cHomeKit \u6865\u63a5\u5668\u914d\u7f6e\u201d\u8fdb\u884c\u914d\u5bf9\u3002", + "description": "\u8bf7\u5728\u201c\u901a\u77e5\u201d\u4e2d\u627e\u5230\u201cHomeKit Pairing\u201d\uff0c\u8ddf\u968f\u6307\u5f15\u5b8c\u6210\u914d\u5bf9\u8fc7\u7a0b\u3002", "title": "\u4e0e HomeKit \u914d\u5bf9" }, "user": { @@ -19,6 +19,12 @@ }, "options": { "step": { + "accessory": { + "data": { + "entities": "\u5b9e\u4f53" + }, + "title": "\u9009\u62e9\u914d\u4ef6\u7684\u5b9e\u4f53" + }, "advanced": { "data": { "auto_start": "\u81ea\u52a8\u542f\u52a8\uff08\u5982\u679c\u60a8\u624b\u52a8\u8c03\u7528 homekit.start \u670d\u52a1\uff0c\u8bf7\u7981\u7528\u6b64\u9879\uff09", @@ -46,7 +52,7 @@ "init": { "data": { "include_domains": "\u8981\u5305\u542b\u7684\u57df", - "mode": "\u6a21\u5f0f" + "mode": "HomeKit \u6a21\u5f0f" }, "description": "HomeKit \u53ef\u4ee5\u88ab\u914d\u7f6e\u4e3a\u5bf9\u5916\u5c55\u793a\u4e00\u4e2a\u6865\u63a5\u5668\u6216\u5355\u4e2a\u914d\u4ef6\u3002\u5728\u914d\u4ef6\u6a21\u5f0f\u4e2d\uff0c\u53ea\u80fd\u4f7f\u7528\u4e00\u4e2a\u5b9e\u4f53\u3002\u8bbe\u5907\u7c7b\u578b\u4e3a\u201c\u7535\u89c6\u201d\u7684\u5a92\u4f53\u64ad\u653e\u5668\u5fc5\u987b\u4f7f\u7528\u914d\u4ef6\u6a21\u5f0f\u624d\u80fd\u6b63\u5e38\u5de5\u4f5c\u3002\u201c\u8981\u5305\u542b\u7684\u57df\u201d\u4e2d\u7684\u5b9e\u4f53\u5c06\u5411 HomeKit \u5f00\u653e\u3002\u5728\u4e0b\u4e00\u9875\u53ef\u4ee5\u9009\u62e9\u8981\u5305\u542b\u6216\u6392\u9664\u5176\u4e2d\u7684\u54ea\u4e9b\u5b9e\u4f53\u3002", "title": "\u9009\u62e9\u8981\u5305\u542b\u7684\u57df\u3002" diff --git a/homeassistant/components/homekit_controller/translations/select.zh-Hans.json b/homeassistant/components/homekit_controller/translations/select.zh-Hans.json new file mode 100644 index 00000000000000..7b8bda72fff2ec --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/select.zh-Hans.json @@ -0,0 +1,9 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "away": "\u79bb\u5f00", + "home": "\u5728\u5bb6", + "sleep": "\u7761\u7720" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/zh-Hans.json b/homeassistant/components/homekit_controller/translations/zh-Hans.json index a5f57e2f57615a..17f42da58053fa 100644 --- a/homeassistant/components/homekit_controller/translations/zh-Hans.json +++ b/homeassistant/components/homekit_controller/translations/zh-Hans.json @@ -33,7 +33,7 @@ "allow_insecure_setup_codes": "\u5141\u8bb8\u4f7f\u7528\u4e0d\u5b89\u5168\u7684\u8bbe\u7f6e\u4ee3\u7801\u914d\u5bf9\u3002", "pairing_code": "\u914d\u5bf9\u4ee3\u7801" }, - "description": "\u8f93\u5165\u60a8\u7684 HomeKit \u914d\u5bf9\u4ee3\u7801\uff08\u683c\u5f0f\u4e3a XXX-XX-XXX\uff09\u4ee5\u4f7f\u7528\u6b64\u914d\u4ef6", + "description": "HomeKit \u63a7\u5236\u5668\u4f7f\u7528\u5b89\u5168\u7684\u52a0\u5bc6\u8fde\u63a5\u901a\u8fc7\u5c40\u57df\u7f51\u4e0e\u914d\u4ef6\u76f4\u63a5\u901a\u4fe1\uff0c\u65e0\u9700\u4f7f\u7528 iCloud \u6216\u5176\u4ed6\u8f6c\u63a5\u5668\u3002\u8bf7\u8f93\u5165\u201c{name}\u201d\u7684 HomeKit \u914d\u5bf9\u4ee3\u7801\u4ee5\u4f7f\u7528\u6b64\u914d\u4ef6\u3002\u6b64\u4ee3\u7801\u4e3a 8 \u4f4d\u6570\u5b57\uff0c\u901a\u5e38\u4f4d\u4e8e\u8bbe\u5907\u672c\u4f53\u6216\u5305\u88c5\u76d2\u4e0a\u3002", "title": "\u4e0e HomeKit \u914d\u4ef6\u914d\u5bf9" }, "protocol_error": { @@ -44,8 +44,8 @@ "data": { "device": "\u8bbe\u5907" }, - "description": "HomeKit \u63a7\u5236\u5668\u4f7f\u7528\u5b89\u5168\u7684\u52a0\u5bc6\u8fde\u63a5\uff0c\u901a\u8fc7\u5c40\u57df\u7f51\u76f4\u63a5\u8fdb\u884c\u901a\u4fe1\uff0c\u65e0\u9700\u5355\u72ec\u7684 HomeKit \u63a7\u5236\u5668\u6216 iCloud\u3002\u8bf7\u9009\u62e9\u8981\u914d\u5bf9\u7684\u8bbe\u5907\uff1a", - "title": "\u4e0e HomeKit \u914d\u4ef6\u914d\u5bf9" + "description": "HomeKit \u63a7\u5236\u5668\u4f7f\u7528\u5b89\u5168\u7684\u52a0\u5bc6\u8fde\u63a5\u901a\u8fc7\u5c40\u57df\u7f51\u4e0e\u914d\u4ef6\u76f4\u63a5\u901a\u4fe1\uff0c\u65e0\u9700\u4f7f\u7528 iCloud \u6216\u5176\u4ed6\u8f6c\u63a5\u5668\u3002\u8bf7\u9009\u62e9\u8981\u914d\u5bf9\u7684\u8bbe\u5907\uff1a", + "title": "\u9009\u62e9\u8bbe\u5907" } } }, diff --git a/homeassistant/components/ifttt/translations/zh-Hans.json b/homeassistant/components/ifttt/translations/zh-Hans.json index 78cbc37a7d9a9b..58ba93cd276866 100644 --- a/homeassistant/components/ifttt/translations/zh-Hans.json +++ b/homeassistant/components/ifttt/translations/zh-Hans.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u672a\u8fde\u63a5\u81f3 Home Assistant Cloud\u3002", "single_instance_allowed": "\u5b9e\u4f8b\u5df2\u914d\u7f6e\uff0c\u4e14\u53ea\u80fd\u5b58\u5728\u5355\u4e2a\u914d\u7f6e\u3002", "webhook_not_internet_accessible": "Home Assistant \u9700\u8981\u7f51\u7edc\u8fde\u63a5\u4ee5\u83b7\u53d6\u76f8\u5173\u63a8\u9001\u4fe1\u606f\u3002" }, diff --git a/homeassistant/components/locative/translations/zh-Hans.json b/homeassistant/components/locative/translations/zh-Hans.json index 00eaf2929ddb3b..a96698dedacd6a 100644 --- a/homeassistant/components/locative/translations/zh-Hans.json +++ b/homeassistant/components/locative/translations/zh-Hans.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "cloud_not_connected": "\u672a\u8fde\u63a5\u81f3 Home Assistant Cloud\u3002" + }, "create_entry": { "default": "\u8981\u5411 Home Assistant \u53d1\u9001\u4e8b\u4ef6\uff0c\u60a8\u9700\u8981\u914d\u7f6e Locative app \u7684 Webhook \u529f\u80fd\u3002\n\n\u586b\u5199\u4ee5\u4e0b\u4fe1\u606f\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u8bf7\u53c2\u9605[\u6587\u6863]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u591a\u4fe1\u606f\u3002" }, diff --git a/homeassistant/components/mailgun/translations/zh-Hans.json b/homeassistant/components/mailgun/translations/zh-Hans.json index 97a827f3d6b906..35de7e89ed193c 100644 --- a/homeassistant/components/mailgun/translations/zh-Hans.json +++ b/homeassistant/components/mailgun/translations/zh-Hans.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "cloud_not_connected": "\u672a\u8fde\u63a5\u81f3 Home Assistant Cloud\u3002" + }, "create_entry": { "default": "\u8981\u5411 Home Assistant \u53d1\u9001\u4e8b\u4ef6\uff0c\u60a8\u9700\u8981\u914d\u7f6e [Mailgun \u7684 Webhook]({mailgun_url})\u3002\n\n\u586b\u5199\u4ee5\u4e0b\u4fe1\u606f\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\u6709\u5173\u5982\u4f55\u914d\u7f6e\u81ea\u52a8\u5316\u4ee5\u5904\u7406\u4f20\u5165\u7684\u6570\u636e\uff0c\u8bf7\u53c2\u9605[\u6587\u6863]({docs_url})\u3002" }, diff --git a/homeassistant/components/synology_dsm/translations/el.json b/homeassistant/components/synology_dsm/translations/el.json index b5c2d1f18f771d..0f69f0e96c9fa9 100644 --- a/homeassistant/components/synology_dsm/translations/el.json +++ b/homeassistant/components/synology_dsm/translations/el.json @@ -19,6 +19,9 @@ "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ({host});", "title": "Synology DSM" }, + "reauth": { + "description": "\u0391\u03b9\u03c4\u03af\u03b1: {details}" + }, "reauth_confirm": { "title": "Synology DSM \u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, diff --git a/homeassistant/components/traccar/translations/zh-Hans.json b/homeassistant/components/traccar/translations/zh-Hans.json new file mode 100644 index 00000000000000..d99562320c867f --- /dev/null +++ b/homeassistant/components/traccar/translations/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cloud_not_connected": "\u672a\u8fde\u63a5\u81f3 Home Assistant Cloud\u3002" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twilio/translations/zh-Hans.json b/homeassistant/components/twilio/translations/zh-Hans.json index f59a63344036ee..f9cf06ceaa65cb 100644 --- a/homeassistant/components/twilio/translations/zh-Hans.json +++ b/homeassistant/components/twilio/translations/zh-Hans.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "cloud_not_connected": "\u672a\u8fde\u63a5\u81f3 Home Assistant Cloud\u3002" + }, "create_entry": { "default": "\u8981\u5411 Home Assistant \u53d1\u9001\u4e8b\u4ef6\uff0c\u60a8\u9700\u8981\u914d\u7f6e [Twilio \u7684 Webhook]({twilio_url})\u3002\n\n\u586b\u5199\u4ee5\u4e0b\u4fe1\u606f\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\u6709\u5173\u5982\u4f55\u914d\u7f6e\u81ea\u52a8\u5316\u4ee5\u5904\u7406\u4f20\u5165\u7684\u6570\u636e\uff0c\u8bf7\u53c2\u9605[\u6587\u6863]({docs_url})\u3002" }, From 3da60355a9ecfaa2b4e43bc30f6c55d25dd172a4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 1 Feb 2022 19:19:24 -0600 Subject: [PATCH 0181/1098] Bump lutron_caseta to 0.13.1 to fix setup when no button devices are present (#65400) --- homeassistant/components/lutron_caseta/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/lutron_caseta/manifest.json b/homeassistant/components/lutron_caseta/manifest.json index f703f991f6ff29..206d8b51233c5c 100644 --- a/homeassistant/components/lutron_caseta/manifest.json +++ b/homeassistant/components/lutron_caseta/manifest.json @@ -2,7 +2,7 @@ "domain": "lutron_caseta", "name": "Lutron Cas\u00e9ta", "documentation": "https://www.home-assistant.io/integrations/lutron_caseta", - "requirements": ["pylutron-caseta==0.13.0"], + "requirements": ["pylutron-caseta==0.13.1"], "config_flow": true, "zeroconf": ["_leap._tcp.local."], "homekit": { diff --git a/requirements_all.txt b/requirements_all.txt index 8f5ca63a3ec3af..2f2e7d9347d958 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1648,7 +1648,7 @@ pylitejet==0.3.0 pylitterbot==2021.12.0 # homeassistant.components.lutron_caseta -pylutron-caseta==0.13.0 +pylutron-caseta==0.13.1 # homeassistant.components.lutron pylutron==0.2.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c25db3f6345b88..34101966b2b2a9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1035,7 +1035,7 @@ pylitejet==0.3.0 pylitterbot==2021.12.0 # homeassistant.components.lutron_caseta -pylutron-caseta==0.13.0 +pylutron-caseta==0.13.1 # homeassistant.components.mailgun pymailgunner==1.4 From c80d6810b3e6cac7730b1d2a9cd8c21e010d1f67 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Tue, 1 Feb 2022 22:10:53 -0600 Subject: [PATCH 0182/1098] Fix Sonos diagnostics with offline device (#65393) Co-authored-by: J. Nick Koston --- homeassistant/components/sonos/diagnostics.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonos/diagnostics.py b/homeassistant/components/sonos/diagnostics.py index 707449e4002768..421a0f7ed6afec 100644 --- a/homeassistant/components/sonos/diagnostics.py +++ b/homeassistant/components/sonos/diagnostics.py @@ -91,9 +91,13 @@ async def async_generate_media_info( payload[attrib] = getattr(speaker.media, attrib) def poll_current_track_info(): - return speaker.soco.avTransport.GetPositionInfo( - [("InstanceID", 0), ("Channel", "Master")] - ) + try: + return speaker.soco.avTransport.GetPositionInfo( + [("InstanceID", 0), ("Channel", "Master")], + timeout=3, + ) + except OSError as ex: + return f"Error retrieving: {ex}" payload["current_track_poll"] = await hass.async_add_executor_job( poll_current_track_info From 479462504893d35ffcd7474f9ef330e0c0c3d4cd Mon Sep 17 00:00:00 2001 From: jjlawren Date: Tue, 1 Feb 2022 22:11:21 -0600 Subject: [PATCH 0183/1098] Detect battery-operated Sonos devices going offline (#65382) --- homeassistant/components/sonos/const.py | 1 + homeassistant/components/sonos/speaker.py | 34 ++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sonos/const.py b/homeassistant/components/sonos/const.py index bbeaceb08cd0a9..abb0696360b7f2 100644 --- a/homeassistant/components/sonos/const.py +++ b/homeassistant/components/sonos/const.py @@ -160,6 +160,7 @@ SONOS_SPEAKER_ADDED = "sonos_speaker_added" SONOS_STATE_UPDATED = "sonos_state_updated" SONOS_REBOOTED = "sonos_rebooted" +SONOS_VANISHED = "sonos_vanished" SOURCE_LINEIN = "Line-in" SOURCE_TV = "TV" diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index 5f06fe976ac17c..7777265a124cc6 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -12,6 +12,7 @@ import urllib.parse import async_timeout +import defusedxml.ElementTree as ET from soco.core import MUSIC_SRC_LINE_IN, MUSIC_SRC_RADIO, MUSIC_SRC_TV, SoCo from soco.data_structures import DidlAudioBroadcast, DidlPlaylistContainer from soco.events_base import Event as SonosEvent, SubscriptionBase @@ -56,6 +57,7 @@ SONOS_STATE_PLAYING, SONOS_STATE_TRANSITIONING, SONOS_STATE_UPDATED, + SONOS_VANISHED, SOURCE_LINEIN, SOURCE_TV, SUBSCRIPTION_TIMEOUT, @@ -225,6 +227,7 @@ async def async_setup_dispatchers(self, entry: ConfigEntry) -> None: (SONOS_SPEAKER_ADDED, self.update_group_for_uid), (f"{SONOS_REBOOTED}-{self.soco.uid}", self.async_rebooted), (f"{SONOS_SPEAKER_ACTIVITY}-{self.soco.uid}", self.speaker_activity), + (f"{SONOS_VANISHED}-{self.soco.uid}", self.async_vanished), ) for (signal, target) in dispatch_pairs: @@ -388,6 +391,8 @@ async def _subscribe( async def async_unsubscribe(self) -> None: """Cancel all subscriptions.""" + if not self._subscriptions: + return _LOGGER.debug("Unsubscribing from events for %s", self.zone_name) results = await asyncio.gather( *(subscription.unsubscribe() for subscription in self._subscriptions), @@ -572,6 +577,15 @@ async def async_offline(self) -> None: self.hass.data[DATA_SONOS].discovery_known.discard(self.soco.uid) self.async_write_entity_states() + async def async_vanished(self, reason: str) -> None: + """Handle removal of speaker when marked as vanished.""" + if not self.available: + return + _LOGGER.debug( + "%s has vanished (%s), marking unavailable", self.zone_name, reason + ) + await self.async_offline() + async def async_rebooted(self, soco: SoCo) -> None: """Handle a detected speaker reboot.""" _LOGGER.warning( @@ -685,7 +699,25 @@ def update_group_for_uid(self, uid: str) -> None: @callback def async_update_groups(self, event: SonosEvent) -> None: """Handle callback for topology change event.""" - if not hasattr(event, "zone_player_uui_ds_in_group"): + if xml := event.variables.get("zone_group_state"): + zgs = ET.fromstring(xml) + for vanished_device in zgs.find("VanishedDevices"): + if (reason := vanished_device.get("Reason")) != "sleeping": + _LOGGER.debug( + "Ignoring %s marked %s as vanished with reason: %s", + self.zone_name, + vanished_device.get("ZoneName"), + reason, + ) + continue + uid = vanished_device.get("UUID") + async_dispatcher_send( + self.hass, + f"{SONOS_VANISHED}-{uid}", + reason, + ) + + if "zone_player_uui_ds_in_group" not in event.variables: return self.event_stats.process(event) self.hass.async_create_task(self.create_update_groups_coro(event)) From 4a55d58d6d127c3773433abb57dd5cfc4f90f968 Mon Sep 17 00:00:00 2001 From: Josh Shoemaker Date: Wed, 2 Feb 2022 03:29:05 -0500 Subject: [PATCH 0184/1098] Bump aladdin_connect to 0.4 to fix integration for some users due to API changes (#65407) --- homeassistant/components/aladdin_connect/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/aladdin_connect/manifest.json b/homeassistant/components/aladdin_connect/manifest.json index ed568aa8a46ed5..e28b2adff42b7c 100644 --- a/homeassistant/components/aladdin_connect/manifest.json +++ b/homeassistant/components/aladdin_connect/manifest.json @@ -2,7 +2,7 @@ "domain": "aladdin_connect", "name": "Aladdin Connect", "documentation": "https://www.home-assistant.io/integrations/aladdin_connect", - "requirements": ["aladdin_connect==0.3"], + "requirements": ["aladdin_connect==0.4"], "codeowners": [], "iot_class": "cloud_polling", "loggers": ["aladdin_connect"] diff --git a/requirements_all.txt b/requirements_all.txt index 2f2e7d9347d958..c8a4924566dfbb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -293,7 +293,7 @@ airthings_cloud==0.1.0 airtouch4pyapi==1.0.5 # homeassistant.components.aladdin_connect -aladdin_connect==0.3 +aladdin_connect==0.4 # homeassistant.components.alpha_vantage alpha_vantage==2.3.1 From 627be815315207633bcad23c0df02b38b09b60ba Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 2 Feb 2022 10:32:11 +0100 Subject: [PATCH 0185/1098] Import registries in MQTT mixins (#65411) --- homeassistant/components/mqtt/mixins.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index a150a0cf49cf26..ff9e70cc9c07d1 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -24,7 +24,11 @@ CONF_VALUE_TEMPLATE, ) from homeassistant.core import callback -from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import ( + config_validation as cv, + device_registry as dr, + entity_registry as er, +) from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, @@ -432,11 +436,11 @@ async def cleanup_device_registry(hass, device_id): # pylint: disable=import-outside-toplevel from . import device_trigger, tag - device_registry = await hass.helpers.device_registry.async_get_registry() - entity_registry = await hass.helpers.entity_registry.async_get_registry() + device_registry = dr.async_get(hass) + entity_registry = er.async_get(hass) if ( device_id - and not hass.helpers.entity_registry.async_entries_for_device( + and not er.async_entries_for_device( entity_registry, device_id, include_disabled_entities=False ) and not await device_trigger.async_get_triggers(hass, device_id) @@ -469,11 +473,8 @@ async def _async_remove_state_and_registry_entry(self) -> None: Remove entity from entity registry if it is registered, this also removes the state. If the entity is not in the entity registry, just remove the state. """ - entity_registry = ( - await self.hass.helpers.entity_registry.async_get_registry() - ) - if entity_registry.async_is_registered(self.entity_id): - entity_entry = entity_registry.async_get(self.entity_id) + entity_registry = er.async_get(self.hass) + if entity_entry := entity_registry.async_get(self.entity_id): entity_registry.async_remove(self.entity_id) await cleanup_device_registry(self.hass, entity_entry.device_id) else: @@ -598,7 +599,7 @@ def __init__(self, device_config: ConfigType | None, config_entry=None) -> None: async def device_info_discovery_update(self, config: dict): """Handle updated discovery message.""" self._device_config = config.get(CONF_DEVICE) - device_registry = await self.hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(self.hass) config_entry_id = self._config_entry.entry_id device_info = self.device_info From a63e5c7ded1acad336224eb2b6fd26475de84aba Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Wed, 2 Feb 2022 10:36:04 +0100 Subject: [PATCH 0186/1098] Add type hints for config entry update listeners (#65412) --- homeassistant/components/asuswrt/__init__.py | 2 +- homeassistant/components/aussie_broadband/__init__.py | 2 +- homeassistant/components/axis/device.py | 5 ++++- homeassistant/components/control4/__init__.py | 2 +- homeassistant/components/deconz/gateway.py | 8 +++++--- homeassistant/components/denonavr/__init__.py | 2 +- homeassistant/components/dexcom/__init__.py | 2 +- homeassistant/components/doorbird/__init__.py | 2 +- homeassistant/components/forked_daapd/media_player.py | 2 +- homeassistant/components/freedompro/__init__.py | 2 +- homeassistant/components/glances/__init__.py | 2 +- homeassistant/components/harmony/__init__.py | 2 +- homeassistant/components/homekit/__init__.py | 2 +- homeassistant/components/honeywell/__init__.py | 2 +- homeassistant/components/islamic_prayer_times/__init__.py | 2 +- homeassistant/components/isy994/__init__.py | 2 +- homeassistant/components/keenetic_ndms2/__init__.py | 2 +- homeassistant/components/konnected/__init__.py | 2 +- homeassistant/components/meteo_france/__init__.py | 2 +- homeassistant/components/monoprice/__init__.py | 2 +- homeassistant/components/nut/__init__.py | 2 +- homeassistant/components/opentherm_gw/__init__.py | 2 +- homeassistant/components/plaato/__init__.py | 2 +- homeassistant/components/plex/__init__.py | 2 +- homeassistant/components/plugwise/gateway.py | 2 +- homeassistant/components/risco/__init__.py | 2 +- homeassistant/components/roomba/__init__.py | 2 +- homeassistant/components/screenlogic/__init__.py | 2 +- homeassistant/components/somfy_mylink/__init__.py | 2 +- homeassistant/components/tado/__init__.py | 2 +- homeassistant/components/tesla_wall_connector/__init__.py | 2 +- homeassistant/components/transmission/__init__.py | 2 +- homeassistant/components/unifi/controller.py | 7 +++++-- homeassistant/components/vera/__init__.py | 2 +- homeassistant/components/wiffi/__init__.py | 2 +- homeassistant/components/yeelight/__init__.py | 2 +- 36 files changed, 47 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/asuswrt/__init__.py b/homeassistant/components/asuswrt/__init__.py index 7d3ea839ebd298..ec0162af915f60 100644 --- a/homeassistant/components/asuswrt/__init__.py +++ b/homeassistant/components/asuswrt/__init__.py @@ -155,7 +155,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -async def update_listener(hass: HomeAssistant, entry: ConfigEntry): +async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Update when config_entry options update.""" router = hass.data[DOMAIN][entry.entry_id][DATA_ASUSWRT] diff --git a/homeassistant/components/aussie_broadband/__init__.py b/homeassistant/components/aussie_broadband/__init__.py index d967450788fe8e..45ae6f90e6d18c 100644 --- a/homeassistant/components/aussie_broadband/__init__.py +++ b/homeassistant/components/aussie_broadband/__init__.py @@ -70,7 +70,7 @@ async def async_update_data(): return True -async def update_listener(hass: HomeAssistant, entry: ConfigEntry): +async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Reload to update options.""" await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/axis/device.py b/homeassistant/components/axis/device.py index a2eceff6870f7d..3a1017534451cd 100644 --- a/homeassistant/components/axis/device.py +++ b/homeassistant/components/axis/device.py @@ -13,6 +13,7 @@ from homeassistant.components import mqtt from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN from homeassistant.components.mqtt.models import ReceiveMessage +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -156,7 +157,9 @@ def async_event_callback(self, action, event_id): async_dispatcher_send(self.hass, self.signal_new_event, event_id) @staticmethod - async def async_new_address_callback(hass, entry): + async def async_new_address_callback( + hass: HomeAssistant, entry: ConfigEntry + ) -> None: """Handle signals of device getting new address. Called when config entry is updated. diff --git a/homeassistant/components/control4/__init__.py b/homeassistant/components/control4/__init__.py index ee2f51303f27d3..48a639e56f650d 100644 --- a/homeassistant/components/control4/__init__.py +++ b/homeassistant/components/control4/__init__.py @@ -115,7 +115,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return True -async def update_listener(hass, config_entry): +async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None: """Update when config_entry options update.""" _LOGGER.debug("Config entry was updated, rerunning setup") await hass.config_entries.async_reload(config_entry.entry_id) diff --git a/homeassistant/components/deconz/gateway.py b/homeassistant/components/deconz/gateway.py index 8fa0c6133dfdd7..d197f27910b572 100644 --- a/homeassistant/components/deconz/gateway.py +++ b/homeassistant/components/deconz/gateway.py @@ -4,9 +4,9 @@ import async_timeout from pydeconz import DeconzSession, errors, group, light, sensor -from homeassistant.config_entries import SOURCE_HASSIO +from homeassistant.config_entries import SOURCE_HASSIO, ConfigEntry from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers import ( aiohttp_client, @@ -193,7 +193,9 @@ async def async_setup(self) -> bool: return True @staticmethod - async def async_config_entry_updated(hass, entry) -> None: + async def async_config_entry_updated( + hass: HomeAssistant, entry: ConfigEntry + ) -> None: """Handle signals of config entry being updated. This is a static method because a class method (bound method), can not be used with weak references. diff --git a/homeassistant/components/denonavr/__init__.py b/homeassistant/components/denonavr/__init__.py index a6678d8b533443..27594703fd23a7 100644 --- a/homeassistant/components/denonavr/__init__.py +++ b/homeassistant/components/denonavr/__init__.py @@ -88,6 +88,6 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> return unload_ok -async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry): +async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None: """Handle options update.""" await hass.config_entries.async_reload(config_entry.entry_id) diff --git a/homeassistant/components/dexcom/__init__.py b/homeassistant/components/dexcom/__init__.py index 8db69b389279ce..7b5ae85bdff6ff 100644 --- a/homeassistant/components/dexcom/__init__.py +++ b/homeassistant/components/dexcom/__init__.py @@ -81,6 +81,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -async def update_listener(hass, entry): +async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/doorbird/__init__.py b/homeassistant/components/doorbird/__init__.py index 1018d23fd8f4ff..06264153af2acc 100644 --- a/homeassistant/components/doorbird/__init__.py +++ b/homeassistant/components/doorbird/__init__.py @@ -184,7 +184,7 @@ async def _async_register_events(hass, doorstation): return True -async def _update_listener(hass: HomeAssistant, entry: ConfigEntry): +async def _update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" config_entry_id = entry.entry_id doorstation = hass.data[DOMAIN][config_entry_id][DOOR_STATION] diff --git a/homeassistant/components/forked_daapd/media_player.py b/homeassistant/components/forked_daapd/media_player.py index e1b21efe541a5a..a68f56e296527d 100644 --- a/homeassistant/components/forked_daapd/media_player.py +++ b/homeassistant/components/forked_daapd/media_player.py @@ -115,7 +115,7 @@ def async_add_zones(api, outputs): ] = forked_daapd_updater -async def update_listener(hass, entry): +async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" async_dispatcher_send( hass, SIGNAL_CONFIG_OPTIONS_UPDATE.format(entry.entry_id), entry.options diff --git a/homeassistant/components/freedompro/__init__.py b/homeassistant/components/freedompro/__init__.py index 327b6314a34540..ec0085c9d329a6 100644 --- a/homeassistant/components/freedompro/__init__.py +++ b/homeassistant/components/freedompro/__init__.py @@ -55,7 +55,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -async def update_listener(hass, config_entry): +async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None: """Update listener.""" await hass.config_entries.async_reload(config_entry.entry_id) diff --git a/homeassistant/components/glances/__init__.py b/homeassistant/components/glances/__init__.py index 22f6ef2ed1df14..6272015e73c766 100644 --- a/homeassistant/components/glances/__init__.py +++ b/homeassistant/components/glances/__init__.py @@ -163,7 +163,7 @@ async def refresh(event_time): ) @staticmethod - async def async_options_updated(hass, entry): + async def async_options_updated(hass: HomeAssistant, entry: ConfigEntry) -> None: """Triggered by config entry options updates.""" hass.data[DOMAIN][entry.entry_id].set_scan_interval( entry.options[CONF_SCAN_INTERVAL] diff --git a/homeassistant/components/harmony/__init__.py b/homeassistant/components/harmony/__init__.py index 4ec610f1f75707..4e109ae95a7f79 100644 --- a/homeassistant/components/harmony/__init__.py +++ b/homeassistant/components/harmony/__init__.py @@ -94,7 +94,7 @@ def _async_import_options_from_data_if_missing(hass: HomeAssistant, entry: Confi hass.config_entries.async_update_entry(entry, options=options) -async def _update_listener(hass: HomeAssistant, entry: ConfigEntry): +async def _update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" async_dispatcher_send( hass, f"{HARMONY_OPTIONS_UPDATE}-{entry.unique_id}", entry.options diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 843cc6ea642c44..4cd0940adb726e 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -313,7 +313,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return True -async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry): +async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" if entry.source == SOURCE_IMPORT: return diff --git a/homeassistant/components/honeywell/__init__.py b/homeassistant/components/honeywell/__init__.py index 68a005c5f72257..bafd4c470db1d0 100644 --- a/homeassistant/components/honeywell/__init__.py +++ b/homeassistant/components/honeywell/__init__.py @@ -55,7 +55,7 @@ async def async_setup_entry(hass: HomeAssistant, config: ConfigEntry) -> bool: return True -async def update_listener(hass, config) -> None: +async def update_listener(hass: HomeAssistant, config: ConfigEntry) -> None: """Update listener.""" await hass.config_entries.async_reload(config.entry_id) diff --git a/homeassistant/components/islamic_prayer_times/__init__.py b/homeassistant/components/islamic_prayer_times/__init__.py index 5aaa243282b14e..c88e26e1c90cc7 100644 --- a/homeassistant/components/islamic_prayer_times/__init__.py +++ b/homeassistant/components/islamic_prayer_times/__init__.py @@ -201,7 +201,7 @@ async def async_add_options(self): ) @staticmethod - async def async_options_updated(hass, entry): + async def async_options_updated(hass: HomeAssistant, entry: ConfigEntry) -> None: """Triggered by config entry options updates.""" if hass.data[DOMAIN].event_unsub: hass.data[DOMAIN].event_unsub() diff --git a/homeassistant/components/isy994/__init__.py b/homeassistant/components/isy994/__init__.py index 9250b567d1b334..b66e6d4676e4c4 100644 --- a/homeassistant/components/isy994/__init__.py +++ b/homeassistant/components/isy994/__init__.py @@ -227,7 +227,7 @@ def _async_stop_auto_update(event) -> None: async def _async_update_listener( hass: HomeAssistant, entry: config_entries.ConfigEntry -): +) -> None: """Handle options update.""" await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/keenetic_ndms2/__init__.py b/homeassistant/components/keenetic_ndms2/__init__.py index 8a494b88d9111f..ecd9ece1bd0a57 100644 --- a/homeassistant/components/keenetic_ndms2/__init__.py +++ b/homeassistant/components/keenetic_ndms2/__init__.py @@ -96,7 +96,7 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> return unload_ok -async def update_listener(hass, entry): +async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/konnected/__init__.py b/homeassistant/components/konnected/__init__.py index f018d1a51335be..98440766334682 100644 --- a/homeassistant/components/konnected/__init__.py +++ b/homeassistant/components/konnected/__init__.py @@ -281,7 +281,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -async def async_entry_updated(hass: HomeAssistant, entry: ConfigEntry): +async def async_entry_updated(hass: HomeAssistant, entry: ConfigEntry) -> None: """Reload the config entry when options change.""" await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/meteo_france/__init__.py b/homeassistant/components/meteo_france/__init__.py index 2f47aee9c02fc6..bbc3b16875da8f 100644 --- a/homeassistant/components/meteo_france/__init__.py +++ b/homeassistant/components/meteo_france/__init__.py @@ -186,6 +186,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry): +async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/monoprice/__init__.py b/homeassistant/components/monoprice/__init__.py index e018ef94f7d98c..91fd353f2e0ae5 100644 --- a/homeassistant/components/monoprice/__init__.py +++ b/homeassistant/components/monoprice/__init__.py @@ -63,6 +63,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -async def _update_listener(hass: HomeAssistant, entry: ConfigEntry): +async def _update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/nut/__init__.py b/homeassistant/components/nut/__init__.py index 6be66fe64c1b91..775c512f9461a6 100644 --- a/homeassistant/components/nut/__init__.py +++ b/homeassistant/components/nut/__init__.py @@ -115,7 +115,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry): +async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/opentherm_gw/__init__.py b/homeassistant/components/opentherm_gw/__init__.py index ba934fefa12516..1ea24ae9709495 100644 --- a/homeassistant/components/opentherm_gw/__init__.py +++ b/homeassistant/components/opentherm_gw/__init__.py @@ -83,7 +83,7 @@ PLATFORMS = [Platform.BINARY_SENSOR, Platform.CLIMATE, Platform.SENSOR] -async def options_updated(hass, entry): +async def options_updated(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" gateway = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][entry.data[CONF_ID]] async_dispatcher_send(hass, gateway.options_update_signal, entry) diff --git a/homeassistant/components/plaato/__init__.py b/homeassistant/components/plaato/__init__.py index 183eb98e21376f..19c935338676ee 100644 --- a/homeassistant/components/plaato/__init__.py +++ b/homeassistant/components/plaato/__init__.py @@ -180,7 +180,7 @@ async def async_unload_platforms(hass: HomeAssistant, entry: ConfigEntry, platfo return unloaded -async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry): +async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index 41b7f0d0afd18e..26a158d240f25f 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -265,7 +265,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -async def async_options_updated(hass, entry): +async def async_options_updated(hass: HomeAssistant, entry: ConfigEntry) -> None: """Triggered by config entry options updates.""" server_id = entry.data[CONF_SERVER_IDENTIFIER] diff --git a/homeassistant/components/plugwise/gateway.py b/homeassistant/components/plugwise/gateway.py index af4472ac7ae76d..67f1895943dccf 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -145,7 +145,7 @@ async def async_update_data(): return True -async def _update_listener(hass: HomeAssistant, entry: ConfigEntry): +async def _update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" coordinator = hass.data[DOMAIN][entry.entry_id][COORDINATOR] update_interval = entry.options.get(CONF_SCAN_INTERVAL) diff --git a/homeassistant/components/risco/__init__.py b/homeassistant/components/risco/__init__.py index 2e37499b10fce8..362fd6157000f3 100644 --- a/homeassistant/components/risco/__init__.py +++ b/homeassistant/components/risco/__init__.py @@ -80,7 +80,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -async def _update_listener(hass: HomeAssistant, entry: ConfigEntry): +async def _update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index dc16e92b23792d..0a58effa481f09 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -111,7 +111,7 @@ async def async_disconnect_or_timeout(hass, roomba): return True -async def async_update_options(hass, config_entry): +async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry) -> None: """Update options.""" await hass.config_entries.async_reload(config_entry.entry_id) diff --git a/homeassistant/components/screenlogic/__init__.py b/homeassistant/components/screenlogic/__init__.py index b5f16574f1a01c..8580ce8f8fcf5d 100644 --- a/homeassistant/components/screenlogic/__init__.py +++ b/homeassistant/components/screenlogic/__init__.py @@ -92,7 +92,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -async def async_update_listener(hass: HomeAssistant, entry: ConfigEntry): +async def async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/somfy_mylink/__init__.py b/homeassistant/components/somfy_mylink/__init__.py index fd036daf385cde..a7fb40eafd49b0 100644 --- a/homeassistant/components/somfy_mylink/__init__.py +++ b/homeassistant/components/somfy_mylink/__init__.py @@ -60,7 +60,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return True -async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry): +async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/tado/__init__.py b/homeassistant/components/tado/__init__.py index f89ad5feedc307..42fc806ab54fb6 100644 --- a/homeassistant/components/tado/__init__.py +++ b/homeassistant/components/tado/__init__.py @@ -103,7 +103,7 @@ def _async_import_options_from_data_if_missing(hass: HomeAssistant, entry: Confi hass.config_entries.async_update_entry(entry, options=options) -async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry): +async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/tesla_wall_connector/__init__.py b/homeassistant/components/tesla_wall_connector/__init__.py index 742b0a34b177db..c4c7c551375cbc 100644 --- a/homeassistant/components/tesla_wall_connector/__init__.py +++ b/homeassistant/components/tesla_wall_connector/__init__.py @@ -107,7 +107,7 @@ def get_poll_interval(entry: ConfigEntry) -> timedelta: ) -async def update_listener(hass, entry): +async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" wall_connector_data: WallConnectorData = hass.data[DOMAIN][entry.entry_id] wall_connector_data.update_coordinator.update_interval = get_poll_interval(entry) diff --git a/homeassistant/components/transmission/__init__.py b/homeassistant/components/transmission/__init__.py index 6b9d8c0aeb1658..a824185b13e354 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -330,7 +330,7 @@ def refresh(event_time): ) @staticmethod - async def async_options_updated(hass, entry): + async def async_options_updated(hass: HomeAssistant, entry: ConfigEntry) -> None: """Triggered by config entry options updates.""" tm_client = hass.data[DOMAIN][entry.entry_id] tm_client.set_scan_interval(entry.options[CONF_SCAN_INTERVAL]) diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index 5ee2023ce93f88..be59a25f69f3d2 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -26,6 +26,7 @@ from aiounifi.websocket import STATE_DISCONNECTED, STATE_RUNNING import async_timeout +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -34,7 +35,7 @@ CONF_VERIFY_SSL, Platform, ) -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers import aiohttp_client, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -397,7 +398,9 @@ def _async_check_for_stale(self, *_) -> None: del self._heartbeat_time[unique_id] @staticmethod - async def async_config_entry_updated(hass, config_entry) -> None: + async def async_config_entry_updated( + hass: HomeAssistant, config_entry: ConfigEntry + ) -> None: """Handle signals of config entry being updated. If config entry is updated due to reauth flow diff --git a/homeassistant/components/vera/__init__.py b/homeassistant/components/vera/__init__.py index 2e708a8b206ad7..ef293c279bedbc 100644 --- a/homeassistant/components/vera/__init__.py +++ b/homeassistant/components/vera/__init__.py @@ -177,7 +177,7 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> return True -async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry): +async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/wiffi/__init__.py b/homeassistant/components/wiffi/__init__.py index ab5bda8dd1d961..f2d11d53862b57 100644 --- a/homeassistant/components/wiffi/__init__.py +++ b/homeassistant/components/wiffi/__init__.py @@ -58,7 +58,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return True -async def async_update_options(hass: HomeAssistant, entry: ConfigEntry): +async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None: """Update options.""" await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/yeelight/__init__.py b/homeassistant/components/yeelight/__init__.py index d1b8e9d4f46156..4d226f233b28ac 100644 --- a/homeassistant/components/yeelight/__init__.py +++ b/homeassistant/components/yeelight/__init__.py @@ -230,7 +230,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) -async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry): +async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" await hass.config_entries.async_reload(entry.entry_id) From 8448462720f1957ed5fc74e8fc5146c02ca05a6e Mon Sep 17 00:00:00 2001 From: G Johansson Date: Wed, 2 Feb 2022 13:09:42 +0100 Subject: [PATCH 0187/1098] Rewrite sensibo integration (#64753) * Rewrite sensibo integration * Fixing CI * coordinator in untested * Fix review comments * Additional review fixes * Fix all conversations * Remove extra state attributes * Restore assumed state service * Fix async_assume_state --- .coveragerc | 1 + homeassistant/components/sensibo/__init__.py | 46 +-- homeassistant/components/sensibo/climate.py | 361 ++++++++++-------- .../components/sensibo/config_flow.py | 4 +- homeassistant/components/sensibo/const.py | 16 +- .../components/sensibo/coordinator.py | 111 ++++++ .../components/sensibo/services.yaml | 7 +- 7 files changed, 324 insertions(+), 222 deletions(-) create mode 100644 homeassistant/components/sensibo/coordinator.py diff --git a/.coveragerc b/.coveragerc index a7baa17e852f53..b592f1c416dd05 100644 --- a/.coveragerc +++ b/.coveragerc @@ -975,6 +975,7 @@ omit = homeassistant/components/senseme/switch.py homeassistant/components/sensibo/__init__.py homeassistant/components/sensibo/climate.py + homeassistant/components/sensibo/coordinator.py homeassistant/components/serial/sensor.py homeassistant/components/serial_pm/sensor.py homeassistant/components/sesame/lock.py diff --git a/homeassistant/components/sensibo/__init__.py b/homeassistant/components/sensibo/__init__.py index 7401a8c2150e8a..b62482b60b5fa3 100644 --- a/homeassistant/components/sensibo/__init__.py +++ b/homeassistant/components/sensibo/__init__.py @@ -1,53 +1,22 @@ """The sensibo component.""" from __future__ import annotations -import asyncio -import logging - -import aiohttp -import async_timeout -import pysensibo - from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_API_KEY from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers.aiohttp_client import async_get_clientsession - -from .const import _INITIAL_FETCH_FIELDS, DOMAIN, PLATFORMS, TIMEOUT -_LOGGER = logging.getLogger(__name__) +from .const import DOMAIN, PLATFORMS +from .coordinator import SensiboDataUpdateCoordinator async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Sensibo from a config entry.""" - client = pysensibo.SensiboClient( - entry.data[CONF_API_KEY], session=async_get_clientsession(hass), timeout=TIMEOUT - ) - devices = [] - try: - async with async_timeout.timeout(TIMEOUT): - for dev in await client.async_get_devices(_INITIAL_FETCH_FIELDS): - devices.append(dev) - except ( - aiohttp.client_exceptions.ClientConnectorError, - asyncio.TimeoutError, - pysensibo.SensiboError, - ) as err: - raise ConfigEntryNotReady( - f"Failed to get devices from Sensibo servers: {err}" - ) from err - - if not devices: - return False - - hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { - "devices": devices, - "client": client, - } + + coordinator = SensiboDataUpdateCoordinator(hass, entry) + await coordinator.async_config_entry_first_refresh() + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator hass.config_entries.async_setup_platforms(entry, PLATFORMS) - _LOGGER.debug("Loaded entry for %s", entry.title) + return True @@ -57,6 +26,5 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: del hass.data[DOMAIN][entry.entry_id] if not hass.data[DOMAIN]: del hass.data[DOMAIN] - _LOGGER.debug("Unloaded entry for %s", entry.title) return True return False diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index 648e05dbe797f9..b711bca5ac3f16 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -2,12 +2,11 @@ from __future__ import annotations import asyncio -import logging from typing import Any -import aiohttp +from aiohttp.client_exceptions import ClientConnectionError import async_timeout -from pysensibo import SensiboClient, SensiboError +from pysensibo import SensiboError import voluptuous as vol from homeassistant.components.climate import ( @@ -32,20 +31,20 @@ ATTR_TEMPERATURE, CONF_API_KEY, CONF_ID, - STATE_ON, TEMP_CELSIUS, TEMP_FAHRENHEIT, ) from homeassistant.core import HomeAssistant, ServiceCall +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.util.temperature import convert as convert_temperature -from .const import _FETCH_FIELDS, ALL, DOMAIN, TIMEOUT - -_LOGGER = logging.getLogger(__name__) +from .const import ALL, DOMAIN, LOGGER, TIMEOUT +from .coordinator import SensiboDataUpdateCoordinator SERVICE_ASSUME_STATE = "assume_state" @@ -72,7 +71,7 @@ "fan": HVAC_MODE_FAN_ONLY, "auto": HVAC_MODE_HEAT_COOL, "dry": HVAC_MODE_DRY, - "": HVAC_MODE_OFF, + "off": HVAC_MODE_OFF, } HA_TO_SENSIBO = {value: key for key, value in SENSIBO_TO_HA.items()} @@ -85,7 +84,7 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up Sensibo devices.""" - _LOGGER.warning( + LOGGER.warning( "Loading Sensibo via platform setup is deprecated; Please remove it from your configuration" ) hass.async_create_task( @@ -102,13 +101,13 @@ async def async_setup_entry( ) -> None: """Set up the Sensibo climate entry.""" - data = hass.data[DOMAIN][entry.entry_id] - client = data["client"] - devices = data["devices"] + coordinator: SensiboDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] entities = [ - SensiboClimate(client, dev, hass.config.units.temperature_unit) - for dev in devices + SensiboClimate(coordinator, device_id, hass.config.units.temperature_unit) + for device_id, device_data in coordinator.data.items() + # Remove none climate devices + if device_data["hvac_modes"] and device_data["temp"] ] async_add_entities(entities) @@ -138,208 +137,234 @@ async def async_assume_state(service: ServiceCall) -> None: ) -class SensiboClimate(ClimateEntity): +class SensiboClimate(CoordinatorEntity, ClimateEntity): """Representation of a Sensibo device.""" - def __init__(self, client: SensiboClient, data: dict[str, Any], units: str) -> None: + coordinator: SensiboDataUpdateCoordinator + + def __init__( + self, + coordinator: SensiboDataUpdateCoordinator, + device_id: str, + temp_unit: str, + ) -> None: """Initiate SensiboClimate.""" - self._client = client - self._id = data["id"] - self._external_state = None - self._units = units - self._failed_update = False - self._attr_available = False - self._attr_unique_id = self._id + super().__init__(coordinator) + self._client = coordinator.client + self._attr_unique_id = device_id + self._attr_name = coordinator.data[device_id]["name"] self._attr_temperature_unit = ( - TEMP_CELSIUS if data["temperatureUnit"] == "C" else TEMP_FAHRENHEIT - ) - self._do_update(data) - self._attr_target_temperature_step = ( - 1 if self.temperature_unit == units else None + TEMP_CELSIUS + if coordinator.data[device_id]["temp_unit"] == "C" + else TEMP_FAHRENHEIT ) + self._attr_supported_features = self.get_features() self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, self._id)}, - name=self._attr_name, + identifiers={(DOMAIN, coordinator.data[device_id]["id"])}, + name=coordinator.data[device_id]["name"], manufacturer="Sensibo", configuration_url="https://home.sensibo.com/", - model=data["productModel"], - sw_version=data["firmwareVersion"], - hw_version=data["firmwareType"], - suggested_area=self._attr_name, + model=coordinator.data[device_id]["model"], + sw_version=coordinator.data[device_id]["fw_ver"], + hw_version=coordinator.data[device_id]["fw_type"], + suggested_area=coordinator.data[device_id]["name"], ) - def _do_update(self, data) -> None: - self._attr_name = data["room"]["name"] - self._ac_states = data["acState"] - self._attr_extra_state_attributes = { - "battery": data["measurements"].get("batteryVoltage") - } - self._attr_current_temperature = convert_temperature( - data["measurements"].get("temperature"), - TEMP_CELSIUS, - self._attr_temperature_unit, + def get_features(self) -> int: + """Get supported features.""" + features = 0 + for key in self.coordinator.data[self.unique_id]["features"]: + if key in FIELD_TO_FLAG: + features |= FIELD_TO_FLAG[key] + return features + + @property + def current_humidity(self) -> int: + """Return the current humidity.""" + return self.coordinator.data[self.unique_id]["humidity"] + + @property + def hvac_mode(self) -> str: + """Return hvac operation.""" + return ( + SENSIBO_TO_HA[self.coordinator.data[self.unique_id]["hvac_mode"]] + if self.coordinator.data[self.unique_id]["on"] + else HVAC_MODE_OFF ) - self._attr_current_humidity = data["measurements"].get("humidity") - - self._attr_target_temperature = self._ac_states.get("targetTemperature") - if self._ac_states["on"]: - self._attr_hvac_mode = SENSIBO_TO_HA.get(self._ac_states["mode"], "") - else: - self._attr_hvac_mode = HVAC_MODE_OFF - self._attr_fan_mode = self._ac_states.get("fanLevel") - self._attr_swing_mode = self._ac_states.get("swing") - self._attr_available = data["connectionStatus"].get("isAlive") - capabilities = data["remoteCapabilities"] - self._attr_hvac_modes = [SENSIBO_TO_HA[mode] for mode in capabilities["modes"]] - self._attr_hvac_modes.append(HVAC_MODE_OFF) - - current_capabilities = capabilities["modes"][self._ac_states.get("mode")] - self._attr_fan_modes = current_capabilities.get("fanLevels") - self._attr_swing_modes = current_capabilities.get("swing") - - temperature_unit_key = data.get("temperatureUnit") or self._ac_states.get( - "temperatureUnit" - ) - if temperature_unit_key: - self._temperature_unit = ( - TEMP_CELSIUS if temperature_unit_key == "C" else TEMP_FAHRENHEIT - ) - self._temperatures_list = ( - current_capabilities["temperatures"] - .get(temperature_unit_key, {}) - .get("values", []) - ) - else: - self._temperature_unit = self._units - self._temperatures_list = [] - self._attr_min_temp = ( - self._temperatures_list[0] if self._temperatures_list else super().min_temp - ) - self._attr_max_temp = ( - self._temperatures_list[-1] if self._temperatures_list else super().max_temp + @property + def hvac_modes(self) -> list[str]: + """Return the list of available hvac operation modes.""" + return [ + SENSIBO_TO_HA[mode] + for mode in self.coordinator.data[self.unique_id]["hvac_modes"] + ] + + @property + def current_temperature(self) -> float: + """Return the current temperature.""" + return convert_temperature( + self.coordinator.data[self.unique_id]["temp"], + TEMP_CELSIUS, + self.temperature_unit, ) - self._attr_temperature_unit = self._temperature_unit - - self._attr_supported_features = 0 - for key in self._ac_states: - if key in FIELD_TO_FLAG: - self._attr_supported_features |= FIELD_TO_FLAG[key] - self._attr_state = self._external_state or super().state + @property + def target_temperature(self) -> float | None: + """Return the temperature we try to reach.""" + return self.coordinator.data[self.unique_id]["target_temp"] + + @property + def target_temperature_step(self) -> float | None: + """Return the supported step of target temperature.""" + return self.coordinator.data[self.unique_id]["temp_step"] + + @property + def fan_mode(self) -> str | None: + """Return the fan setting.""" + return self.coordinator.data[self.unique_id]["fan_mode"] + + @property + def fan_modes(self) -> list[str] | None: + """Return the list of available fan modes.""" + return self.coordinator.data[self.unique_id]["fan_modes"] + + @property + def swing_mode(self) -> str | None: + """Return the swing setting.""" + return self.coordinator.data[self.unique_id]["swing_mode"] + + @property + def swing_modes(self) -> list[str] | None: + """Return the list of available swing modes.""" + return self.coordinator.data[self.unique_id]["swing_modes"] + + @property + def min_temp(self) -> float: + """Return the minimum temperature.""" + return self.coordinator.data[self.unique_id]["temp_list"][0] + + @property + def max_temp(self) -> float: + """Return the maximum temperature.""" + return self.coordinator.data[self.unique_id]["temp_list"][-1] + + @property + def available(self) -> bool: + """Return True if entity is available.""" + return self.coordinator.data[self.unique_id]["available"] and super().available async def async_set_temperature(self, **kwargs) -> None: """Set new target temperature.""" if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None: return - temperature = int(temperature) - if temperature not in self._temperatures_list: + + if temperature == self.target_temperature: + return + + if temperature not in self.coordinator.data[self.unique_id]["temp_list"]: # Requested temperature is not supported. - if temperature == self.target_temperature: - return - index = self._temperatures_list.index(self.target_temperature) - if ( - temperature > self.target_temperature - and index < len(self._temperatures_list) - 1 - ): - temperature = self._temperatures_list[index + 1] - elif temperature < self.target_temperature and index > 0: - temperature = self._temperatures_list[index - 1] + if temperature > self.coordinator.data[self.unique_id]["temp_list"][-1]: + temperature = self.coordinator.data[self.unique_id]["temp_list"][-1] + + elif temperature < self.coordinator.data[self.unique_id]["temp_list"][0]: + temperature = self.coordinator.data[self.unique_id]["temp_list"][0] + else: return - await self._async_set_ac_state_property("targetTemperature", temperature) + result = await self._async_set_ac_state_property( + "targetTemperature", int(temperature) + ) + if result: + self.coordinator.data[self.unique_id]["target_temp"] = int(temperature) + self.async_write_ha_state() - async def async_set_fan_mode(self, fan_mode) -> None: + async def async_set_fan_mode(self, fan_mode: str) -> None: """Set new target fan mode.""" - await self._async_set_ac_state_property("fanLevel", fan_mode) + result = await self._async_set_ac_state_property("fanLevel", fan_mode) + if result: + self.coordinator.data[self.unique_id]["fan_mode"] = fan_mode + self.async_write_ha_state() - async def async_set_hvac_mode(self, hvac_mode) -> None: + async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new target operation mode.""" if hvac_mode == HVAC_MODE_OFF: - await self._async_set_ac_state_property("on", False) + result = await self._async_set_ac_state_property("on", False) + if result: + self.coordinator.data[self.unique_id]["on"] = False + self.async_write_ha_state() return # Turn on if not currently on. - if not self._ac_states["on"]: - await self._async_set_ac_state_property("on", True) + if not self.coordinator.data[self.unique_id]["on"]: + result = await self._async_set_ac_state_property("on", True) + if result: + self.coordinator.data[self.unique_id]["on"] = True - await self._async_set_ac_state_property("mode", HA_TO_SENSIBO[hvac_mode]) + result = await self._async_set_ac_state_property( + "mode", HA_TO_SENSIBO[hvac_mode] + ) + if result: + self.coordinator.data[self.unique_id]["hvac_mode"] = HA_TO_SENSIBO[ + hvac_mode + ] + self.async_write_ha_state() - async def async_set_swing_mode(self, swing_mode) -> None: + async def async_set_swing_mode(self, swing_mode: str) -> None: """Set new target swing operation.""" - await self._async_set_ac_state_property("swing", swing_mode) + result = await self._async_set_ac_state_property("swing", swing_mode) + if result: + self.coordinator.data[self.unique_id]["swing_mode"] = swing_mode + self.async_write_ha_state() async def async_turn_on(self) -> None: """Turn Sensibo unit on.""" - await self._async_set_ac_state_property("on", True) + result = await self._async_set_ac_state_property("on", True) + if result: + self.coordinator.data[self.unique_id]["on"] = True + self.async_write_ha_state() async def async_turn_off(self) -> None: """Turn Sensibo unit on.""" - await self._async_set_ac_state_property("on", False) - - async def async_assume_state(self, state) -> None: - """Set external state.""" - change_needed = (state != HVAC_MODE_OFF and not self._ac_states["on"]) or ( - state == HVAC_MODE_OFF and self._ac_states["on"] - ) - - if change_needed: - await self._async_set_ac_state_property("on", state != HVAC_MODE_OFF, True) - - if state in (STATE_ON, HVAC_MODE_OFF): - self._external_state = None - else: - self._external_state = state - - async def async_update(self) -> None: - """Retrieve latest state.""" - try: - async with async_timeout.timeout(TIMEOUT): - data = await self._client.async_get_device(self._id, _FETCH_FIELDS) - except ( - aiohttp.client_exceptions.ClientError, - asyncio.TimeoutError, - SensiboError, - ) as err: - if self._failed_update: - _LOGGER.warning( - "Failed to update data for device '%s' from Sensibo servers with error %s", - self._attr_name, - err, - ) - self._attr_available = False - self.async_write_ha_state() - return - - _LOGGER.debug("First failed update data for device '%s'", self._attr_name) - self._failed_update = True - return - - if self.temperature_unit == self.hass.config.units.temperature_unit: - self._attr_target_temperature_step = 1 - else: - self._attr_target_temperature_step = None - - self._failed_update = False - self._do_update(data) + result = await self._async_set_ac_state_property("on", False) + if result: + self.coordinator.data[self.unique_id]["on"] = False + self.async_write_ha_state() async def _async_set_ac_state_property( - self, name, value, assumed_state=False - ) -> None: + self, name: str, value: Any, assumed_state: bool = False + ) -> bool: """Set AC state.""" + result = {} try: async with async_timeout.timeout(TIMEOUT): - await self._client.async_set_ac_state_property( - self._id, name, value, self._ac_states, assumed_state + result = await self._client.async_set_ac_state_property( + self.unique_id, + name, + value, + self.coordinator.data[self.unique_id]["ac_states"], + assumed_state, ) except ( - aiohttp.client_exceptions.ClientError, + ClientConnectionError, asyncio.TimeoutError, SensiboError, ) as err: - self._attr_available = False - self.async_write_ha_state() - raise Exception( - f"Failed to set AC state for device {self._attr_name} to Sensibo servers" + raise HomeAssistantError( + f"Failed to set AC state for device {self.name} to Sensibo servers: {err}" ) from err + LOGGER.debug("Result: %s", result) + if result["status"] == "Success": + return True + failure = result["failureReason"] + raise HomeAssistantError( + f"Could not set state for device {self.name} due to reason {failure}" + ) + + async def async_assume_state(self, state) -> None: + """Sync state with api.""" + if state == self.state or (state == "on" and self.state != HVAC_MODE_OFF): + return + await self._async_set_ac_state_property("on", state != HVAC_MODE_OFF, True) + await self.coordinator.async_refresh() diff --git a/homeassistant/components/sensibo/config_flow.py b/homeassistant/components/sensibo/config_flow.py index 77f1049d8d2941..8544972baeed77 100644 --- a/homeassistant/components/sensibo/config_flow.py +++ b/homeassistant/components/sensibo/config_flow.py @@ -16,7 +16,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from .const import _INITIAL_FETCH_FIELDS, DEFAULT_NAME, DOMAIN, TIMEOUT +from .const import DEFAULT_NAME, DOMAIN, TIMEOUT _LOGGER = logging.getLogger(__name__) @@ -37,7 +37,7 @@ async def async_validate_api(hass: HomeAssistant, api_key: str) -> bool: try: async with async_timeout.timeout(TIMEOUT): - if await client.async_get_devices(_INITIAL_FETCH_FIELDS): + if await client.async_get_devices(): return True except ( aiohttp.ClientConnectionError, diff --git a/homeassistant/components/sensibo/const.py b/homeassistant/components/sensibo/const.py index fb387e64a1a236..e6d5bebff426aa 100644 --- a/homeassistant/components/sensibo/const.py +++ b/homeassistant/components/sensibo/const.py @@ -1,20 +1,14 @@ """Constants for Sensibo.""" +import logging + from homeassistant.const import Platform +LOGGER = logging.getLogger(__package__) + +DEFAULT_SCAN_INTERVAL = 60 DOMAIN = "sensibo" PLATFORMS = [Platform.CLIMATE] ALL = ["all"] DEFAULT_NAME = "Sensibo" TIMEOUT = 8 -_FETCH_FIELDS = ",".join( - [ - "room{name}", - "measurements", - "remoteCapabilities", - "acState", - "connectionStatus{isAlive}", - "temperatureUnit", - ] -) -_INITIAL_FETCH_FIELDS = f"id,firmwareVersion,firmwareType,productModel,{_FETCH_FIELDS}" diff --git a/homeassistant/components/sensibo/coordinator.py b/homeassistant/components/sensibo/coordinator.py new file mode 100644 index 00000000000000..4781250874f402 --- /dev/null +++ b/homeassistant/components/sensibo/coordinator.py @@ -0,0 +1,111 @@ +"""DataUpdateCoordinator for the Sensibo integration.""" +from __future__ import annotations + +from datetime import timedelta +from typing import Any + +import pysensibo + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_API_KEY +from homeassistant.core import HomeAssistant +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER, TIMEOUT + + +class SensiboDataUpdateCoordinator(DataUpdateCoordinator): + """A Sensibo Data Update Coordinator.""" + + def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: + """Initialize the Sensibo coordinator.""" + self.client = pysensibo.SensiboClient( + entry.data[CONF_API_KEY], + session=async_get_clientsession(hass), + timeout=TIMEOUT, + ) + super().__init__( + hass, + LOGGER, + name=DOMAIN, + update_interval=timedelta(seconds=DEFAULT_SCAN_INTERVAL), + ) + + async def _async_update_data(self) -> dict[str, dict[str, Any]]: + """Fetch data from Sensibo.""" + + devices = [] + try: + for dev in await self.client.async_get_devices(): + devices.append(dev) + except (pysensibo.SensiboError) as error: + raise UpdateFailed from error + + device_data: dict[str, dict[str, Any]] = {} + for dev in devices: + unique_id = dev["id"] + name = dev["room"]["name"] + temperature = dev["measurements"].get("temperature", 0.0) + humidity = dev["measurements"].get("humidity", 0) + ac_states = dev["acState"] + target_temperature = ac_states.get("targetTemperature") + hvac_mode = ac_states.get("mode") + running = ac_states.get("on") + fan_mode = ac_states.get("fanLevel") + swing_mode = ac_states.get("swing") + available = dev["connectionStatus"].get("isAlive", True) + capabilities = dev["remoteCapabilities"] + hvac_modes = list(capabilities["modes"]) + if hvac_modes: + hvac_modes.append("off") + current_capabilities = capabilities["modes"][ac_states.get("mode")] + fan_modes = current_capabilities.get("fanLevels") + swing_modes = current_capabilities.get("swing") + temperature_unit_key = dev.get("temperatureUnit") or ac_states.get( + "temperatureUnit" + ) + temperatures_list = ( + current_capabilities["temperatures"] + .get(temperature_unit_key, {}) + .get("values", [0]) + ) + if temperatures_list: + temperature_step = temperatures_list[1] - temperatures_list[0] + features = list(ac_states) + state = hvac_mode if hvac_mode else "off" + + fw_ver = dev["firmwareVersion"] + fw_type = dev["firmwareType"] + model = dev["productModel"] + + calibration_temp = dev["sensorsCalibration"].get("temperature", 0.0) + calibration_hum = dev["sensorsCalibration"].get("humidity", 0.0) + + device_data[unique_id] = { + "id": unique_id, + "name": name, + "ac_states": ac_states, + "temp": temperature, + "humidity": humidity, + "target_temp": target_temperature, + "hvac_mode": hvac_mode, + "on": running, + "fan_mode": fan_mode, + "swing_mode": swing_mode, + "available": available, + "hvac_modes": hvac_modes, + "fan_modes": fan_modes, + "swing_modes": swing_modes, + "temp_unit": temperature_unit_key, + "temp_list": temperatures_list, + "temp_step": temperature_step, + "features": features, + "state": state, + "fw_ver": fw_ver, + "fw_type": fw_type, + "model": model, + "calibration_temp": calibration_temp, + "calibration_hum": calibration_hum, + } + return device_data diff --git a/homeassistant/components/sensibo/services.yaml b/homeassistant/components/sensibo/services.yaml index 586ad3b4168f87..6438ea37e189f3 100644 --- a/homeassistant/components/sensibo/services.yaml +++ b/homeassistant/components/sensibo/services.yaml @@ -13,6 +13,9 @@ assume_state: name: State description: State to set. required: true - example: "idle" + example: "on" selector: - text: + select: + options: + - "on" + - "off" From 40c727df6663f6c6a9e6509e1771b3fe19acb079 Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Wed, 2 Feb 2022 14:38:19 +0100 Subject: [PATCH 0188/1098] Bump velbus-aio to 2022.2.1 (#65422) --- homeassistant/components/velbus/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index 2543ef580a9753..e935cf004be7a5 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -2,7 +2,7 @@ "domain": "velbus", "name": "Velbus", "documentation": "https://www.home-assistant.io/integrations/velbus", - "requirements": ["velbus-aio==2021.11.7"], + "requirements": ["velbus-aio==2022.2.1"], "config_flow": true, "codeowners": ["@Cereal2nd", "@brefra"], "dependencies": ["usb"], diff --git a/requirements_all.txt b/requirements_all.txt index c8a4924566dfbb..6441d7421ccb60 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2423,7 +2423,7 @@ vallox-websocket-api==2.9.0 vehicle==0.3.1 # homeassistant.components.velbus -velbus-aio==2021.11.7 +velbus-aio==2022.2.1 # homeassistant.components.venstar venstarcolortouch==0.15 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 34101966b2b2a9..88c456aa8e3d8b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1484,7 +1484,7 @@ vallox-websocket-api==2.9.0 vehicle==0.3.1 # homeassistant.components.velbus -velbus-aio==2021.11.7 +velbus-aio==2022.2.1 # homeassistant.components.venstar venstarcolortouch==0.15 From 0eb2caabcf593bb6c4bd723022fb470f952b3249 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 2 Feb 2022 15:06:27 +0100 Subject: [PATCH 0189/1098] Report unmet dependencies for failing config flows (#65061) * Report unmet dependencies for failing config flows * Apply suggestions from code review Co-authored-by: Martin Hjelmare * Update homeassistant/setup.py Co-authored-by: Martin Hjelmare * Modify error message * Add test Co-authored-by: Martin Hjelmare Co-authored-by: Paulus Schoutsen --- .../components/config/config_entries.py | 11 +++++-- homeassistant/exceptions.py | 12 ++++++++ homeassistant/setup.py | 18 +++++++----- .../components/config/test_config_entries.py | 29 +++++++++++++++++++ 4 files changed, 60 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/config/config_entries.py b/homeassistant/components/config/config_entries.py index 1e26a9c46d4c7a..887c0517d05c36 100644 --- a/homeassistant/components/config/config_entries.py +++ b/homeassistant/components/config/config_entries.py @@ -3,6 +3,7 @@ from http import HTTPStatus +from aiohttp import web import aiohttp.web_exceptions import voluptuous as vol @@ -11,7 +12,7 @@ from homeassistant.components import websocket_api from homeassistant.components.http import HomeAssistantView from homeassistant.core import HomeAssistant, callback -from homeassistant.exceptions import Unauthorized +from homeassistant.exceptions import DependencyError, Unauthorized from homeassistant.helpers.data_entry_flow import ( FlowManagerIndexView, FlowManagerResourceView, @@ -127,7 +128,13 @@ async def post(self, request): raise Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="add") # pylint: disable=no-value-for-parameter - return await super().post(request) + try: + return await super().post(request) + except DependencyError as exc: + return web.Response( + text=f"Failed dependencies {', '.join(exc.failed_dependencies)}", + status=HTTPStatus.BAD_REQUEST, + ) def _prepare_result_json(self, result): """Convert result to JSON.""" diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index ff8fb295cd5bbb..052d3de4768aff 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -199,3 +199,15 @@ def __init__(self, parameter_names: list[str]) -> None: ), ) self.parameter_names = parameter_names + + +class DependencyError(HomeAssistantError): + """Raised when dependencies can not be setup.""" + + def __init__(self, failed_dependencies: list[str]) -> None: + """Initialize error.""" + super().__init__( + self, + f"Could not setup dependencies: {', '.join(failed_dependencies)}", + ) + self.failed_dependencies = failed_dependencies diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 5ff6519f6ec71d..5c56cb55b19900 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -18,7 +18,7 @@ Platform, ) from .core import CALLBACK_TYPE -from .exceptions import HomeAssistantError +from .exceptions import DependencyError, HomeAssistantError from .helpers.typing import ConfigType from .util import dt as dt_util, ensure_unique_string @@ -83,8 +83,11 @@ async def async_setup_component( async def _async_process_dependencies( hass: core.HomeAssistant, config: ConfigType, integration: loader.Integration -) -> bool: - """Ensure all dependencies are set up.""" +) -> list[str]: + """Ensure all dependencies are set up. + + Returns a list of dependencies which failed to set up. + """ dependencies_tasks = { dep: hass.loop.create_task(async_setup_component(hass, dep, config)) for dep in integration.dependencies @@ -104,7 +107,7 @@ async def _async_process_dependencies( ) if not dependencies_tasks and not after_dependencies_tasks: - return True + return [] if dependencies_tasks: _LOGGER.debug( @@ -135,8 +138,7 @@ async def _async_process_dependencies( ", ".join(failed), ) - return False - return True + return failed async def _async_setup_component( @@ -341,8 +343,8 @@ async def async_process_deps_reqs( elif integration.domain in processed: return - if not await _async_process_dependencies(hass, config, integration): - raise HomeAssistantError("Could not set up all dependencies.") + if failed_deps := await _async_process_dependencies(hass, config, integration): + raise DependencyError(failed_deps) if not hass.config.skip_pip and integration.requirements: async with hass.timeout.async_freeze(integration.domain): diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index 7f88d9b482b93a..6608bf3471dc98 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -252,6 +252,35 @@ async def async_step_user(self, user_input=None): } +async def test_initialize_flow_unmet_dependency(hass, client): + """Test unmet dependencies are listed.""" + mock_entity_platform(hass, "config_flow.test", None) + + config_schema = vol.Schema({"comp_conf": {"hello": str}}, required=True) + mock_integration( + hass, MockModule(domain="dependency_1", config_schema=config_schema) + ) + # The test2 config flow should fail because dependency_1 can't be automatically setup + mock_integration( + hass, + MockModule(domain="test2", partial_manifest={"dependencies": ["dependency_1"]}), + ) + + class TestFlow(core_ce.ConfigFlow): + async def async_step_user(self, user_input=None): + pass + + with patch.dict(HANDLERS, {"test2": TestFlow}): + resp = await client.post( + "/api/config/config_entries/flow", + json={"handler": "test2", "show_advanced_options": True}, + ) + + assert resp.status == HTTPStatus.BAD_REQUEST + data = await resp.text() + assert data == "Failed dependencies dependency_1" + + async def test_initialize_flow_unauth(hass, client, hass_admin_user): """Test we can initialize a flow.""" hass_admin_user.groups = [] From 9ce2e9e8f4e9afaefc4a66e788d503ebe817c327 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 2 Feb 2022 16:08:16 +0100 Subject: [PATCH 0190/1098] Update frontend to 20220202.0 (#65432) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 49e49ac2efa054..4f1e6eff032fe2 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20220201.0" + "home-assistant-frontend==20220202.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 30086a676e44c1..438fa0eba8d587 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ ciso8601==2.2.0 cryptography==35.0.0 emoji==1.6.3 hass-nabucasa==0.52.0 -home-assistant-frontend==20220201.0 +home-assistant-frontend==20220202.0 httpx==0.21.3 ifaddr==0.1.7 jinja2==3.0.3 diff --git a/requirements_all.txt b/requirements_all.txt index 6441d7421ccb60..545a758effaf33 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -842,7 +842,7 @@ hole==0.7.0 holidays==0.12 # homeassistant.components.frontend -home-assistant-frontend==20220201.0 +home-assistant-frontend==20220202.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 88c456aa8e3d8b..e542e233324de2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -543,7 +543,7 @@ hole==0.7.0 holidays==0.12 # homeassistant.components.frontend -home-assistant-frontend==20220201.0 +home-assistant-frontend==20220202.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 2c07330794d754272a53e87db96c6c528d7c632b Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Wed, 2 Feb 2022 16:14:52 +0100 Subject: [PATCH 0191/1098] Fix MQTT expire_after effects after reloading (#65359) * Cleanup sensor expire triggers after reload * fix test binary_sensor * Also trigger cleanup parent classes * Restore an expiring state after a reload * correct discovery_update * restore expiring state with remaining time * Update homeassistant/components/mqtt/binary_sensor.py description Co-authored-by: Erik Montnemery * Log remaining time * Move check * check and tests reload * remove self.async_write_ha_state() Co-authored-by: Erik Montnemery --- .../components/mqtt/binary_sensor.py | 41 +++++++- homeassistant/components/mqtt/mixins.py | 5 + homeassistant/components/mqtt/sensor.py | 41 +++++++- tests/components/mqtt/test_binary_sensor.py | 91 +++++++++++++++++- tests/components/mqtt/test_common.py | 36 ++++--- tests/components/mqtt/test_sensor.py | 93 ++++++++++++++++++- 6 files changed, 289 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index d9ec64a02c9904..c500d52dd70c19 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -20,6 +20,8 @@ CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_VALUE_TEMPLATE, + STATE_UNAVAILABLE, + STATE_UNKNOWN, ) from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv @@ -27,6 +29,7 @@ import homeassistant.helpers.event as evt from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.reload import async_setup_reload_service +from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util import dt as dt_util @@ -96,7 +99,7 @@ async def _async_setup_entity( async_add_entities([MqttBinarySensor(hass, config, config_entry, discovery_data)]) -class MqttBinarySensor(MqttEntity, BinarySensorEntity): +class MqttBinarySensor(MqttEntity, BinarySensorEntity, RestoreEntity): """Representation a binary sensor that is updated by MQTT.""" _entity_id_format = binary_sensor.ENTITY_ID_FORMAT @@ -114,6 +117,42 @@ def __init__(self, hass, config, config_entry, discovery_data): MqttEntity.__init__(self, hass, config, config_entry, discovery_data) + async def async_added_to_hass(self) -> None: + """Restore state for entities with expire_after set.""" + await super().async_added_to_hass() + if ( + (expire_after := self._config.get(CONF_EXPIRE_AFTER)) is not None + and expire_after > 0 + and (last_state := await self.async_get_last_state()) is not None + and last_state.state not in [STATE_UNKNOWN, STATE_UNAVAILABLE] + ): + expiration_at = last_state.last_changed + timedelta(seconds=expire_after) + if expiration_at < (time_now := dt_util.utcnow()): + # Skip reactivating the binary_sensor + _LOGGER.debug("Skip state recovery after reload for %s", self.entity_id) + return + self._expired = False + self._state = last_state.state + + self._expiration_trigger = async_track_point_in_utc_time( + self.hass, self._value_is_expired, expiration_at + ) + _LOGGER.debug( + "State recovered after reload for %s, remaining time before expiring %s", + self.entity_id, + expiration_at - time_now, + ) + + async def async_will_remove_from_hass(self) -> None: + """Remove exprire triggers.""" + # Clean up expire triggers + if self._expiration_trigger: + _LOGGER.debug("Clean up expire after trigger for %s", self.entity_id) + self._expiration_trigger() + self._expiration_trigger = None + self._expired = False + await MqttEntity.async_will_remove_from_hass(self) + @staticmethod def config_schema(): """Return the config schema.""" diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index ff9e70cc9c07d1..f49af86360db75 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -524,6 +524,11 @@ async def discovery_callback(payload): async def async_removed_from_registry(self) -> None: """Clear retained discovery topic in broker.""" if not self._removed_from_hass: + # Stop subscribing to discovery updates to not trigger when we clear the + # discovery topic + self._cleanup_discovery_on_remove() + + # Clear the discovery topic so the entity is not rediscovered after a restart discovery_topic = self._discovery_data[ATTR_DISCOVERY_TOPIC] publish(self.hass, discovery_topic, "", retain=True) diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index c457cff5d09462..59f124155d3ebc 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -23,12 +23,15 @@ CONF_NAME, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, + STATE_UNAVAILABLE, + STATE_UNKNOWN, ) from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.reload import async_setup_reload_service +from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util import dt as dt_util @@ -140,7 +143,7 @@ async def _async_setup_entity( async_add_entities([MqttSensor(hass, config, config_entry, discovery_data)]) -class MqttSensor(MqttEntity, SensorEntity): +class MqttSensor(MqttEntity, SensorEntity, RestoreEntity): """Representation of a sensor that can be updated using MQTT.""" _entity_id_format = ENTITY_ID_FORMAT @@ -160,6 +163,42 @@ def __init__(self, hass, config, config_entry, discovery_data): MqttEntity.__init__(self, hass, config, config_entry, discovery_data) + async def async_added_to_hass(self) -> None: + """Restore state for entities with expire_after set.""" + await super().async_added_to_hass() + if ( + (expire_after := self._config.get(CONF_EXPIRE_AFTER)) is not None + and expire_after > 0 + and (last_state := await self.async_get_last_state()) is not None + and last_state.state not in [STATE_UNKNOWN, STATE_UNAVAILABLE] + ): + expiration_at = last_state.last_changed + timedelta(seconds=expire_after) + if expiration_at < (time_now := dt_util.utcnow()): + # Skip reactivating the sensor + _LOGGER.debug("Skip state recovery after reload for %s", self.entity_id) + return + self._expired = False + self._state = last_state.state + + self._expiration_trigger = async_track_point_in_utc_time( + self.hass, self._value_is_expired, expiration_at + ) + _LOGGER.debug( + "State recovered after reload for %s, remaining time before expiring %s", + self.entity_id, + expiration_at - time_now, + ) + + async def async_will_remove_from_hass(self) -> None: + """Remove exprire triggers.""" + # Clean up expire triggers + if self._expiration_trigger: + _LOGGER.debug("Clean up expire after trigger for %s", self.entity_id) + self._expiration_trigger() + self._expiration_trigger = None + self._expired = False + await MqttEntity.async_will_remove_from_hass(self) + @staticmethod def config_schema(): """Return the config schema.""" diff --git a/tests/components/mqtt/test_binary_sensor.py b/tests/components/mqtt/test_binary_sensor.py index 1d94dc7ccf7f14..39685db2afc282 100644 --- a/tests/components/mqtt/test_binary_sensor.py +++ b/tests/components/mqtt/test_binary_sensor.py @@ -36,6 +36,7 @@ help_test_entity_device_info_with_identifier, help_test_entity_id_update_discovery_update, help_test_entity_id_update_subscriptions, + help_test_reload_with_config, help_test_reloadable, help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, @@ -44,7 +45,11 @@ help_test_update_with_json_attrs_not_dict, ) -from tests.common import async_fire_mqtt_message, async_fire_time_changed +from tests.common import ( + assert_setup_component, + async_fire_mqtt_message, + async_fire_time_changed, +) DEFAULT_CONFIG = { binary_sensor.DOMAIN: { @@ -872,3 +877,87 @@ async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): domain = binary_sensor.DOMAIN config = DEFAULT_CONFIG[domain] await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) + + +async def test_cleanup_triggers_and_restoring_state( + hass, mqtt_mock, caplog, tmp_path, freezer +): + """Test cleanup old triggers at reloading and restoring the state.""" + domain = binary_sensor.DOMAIN + config1 = copy.deepcopy(DEFAULT_CONFIG[domain]) + config1["name"] = "test1" + config1["expire_after"] = 30 + config1["state_topic"] = "test-topic1" + config2 = copy.deepcopy(DEFAULT_CONFIG[domain]) + config2["name"] = "test2" + config2["expire_after"] = 5 + config2["state_topic"] = "test-topic2" + + freezer.move_to("2022-02-02 12:01:00+01:00") + + assert await async_setup_component( + hass, + binary_sensor.DOMAIN, + {binary_sensor.DOMAIN: [config1, config2]}, + ) + await hass.async_block_till_done() + async_fire_mqtt_message(hass, "test-topic1", "ON") + state = hass.states.get("binary_sensor.test1") + assert state.state == "on" + + async_fire_mqtt_message(hass, "test-topic2", "ON") + state = hass.states.get("binary_sensor.test2") + assert state.state == "on" + + freezer.move_to("2022-02-02 12:01:10+01:00") + + await help_test_reload_with_config( + hass, caplog, tmp_path, domain, [config1, config2] + ) + assert "Clean up expire after trigger for binary_sensor.test1" in caplog.text + assert "Clean up expire after trigger for binary_sensor.test2" not in caplog.text + assert ( + "State recovered after reload for binary_sensor.test1, remaining time before expiring" + in caplog.text + ) + assert "State recovered after reload for binary_sensor.test2" not in caplog.text + + state = hass.states.get("binary_sensor.test1") + assert state.state == "on" + + state = hass.states.get("binary_sensor.test2") + assert state.state == STATE_UNAVAILABLE + + async_fire_mqtt_message(hass, "test-topic1", "OFF") + state = hass.states.get("binary_sensor.test1") + assert state.state == "off" + + async_fire_mqtt_message(hass, "test-topic2", "OFF") + state = hass.states.get("binary_sensor.test2") + assert state.state == "off" + + +async def test_skip_restoring_state_with_over_due_expire_trigger( + hass, mqtt_mock, caplog, freezer +): + """Test restoring a state with over due expire timer.""" + + freezer.move_to("2022-02-02 12:02:00+01:00") + domain = binary_sensor.DOMAIN + config3 = copy.deepcopy(DEFAULT_CONFIG[domain]) + config3["name"] = "test3" + config3["expire_after"] = 10 + config3["state_topic"] = "test-topic3" + fake_state = ha.State( + "binary_sensor.test3", + "on", + {}, + last_changed=datetime.fromisoformat("2022-02-02 12:01:35+01:00"), + ) + with patch( + "homeassistant.helpers.restore_state.RestoreEntity.async_get_last_state", + return_value=fake_state, + ), assert_setup_component(1, domain): + assert await async_setup_component(hass, domain, {domain: config3}) + await hass.async_block_till_done() + assert "Skip state recovery after reload for binary_sensor.test3" in caplog.text diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index ba2c9e3871a51f..593d08f4c87b9d 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -1525,6 +1525,25 @@ async def help_test_publishing_with_custom_encoding( mqtt_mock.async_publish.reset_mock() +async def help_test_reload_with_config(hass, caplog, tmp_path, domain, config): + """Test reloading with supplied config.""" + new_yaml_config_file = tmp_path / "configuration.yaml" + new_yaml_config = yaml.dump({domain: config}) + new_yaml_config_file.write_text(new_yaml_config) + assert new_yaml_config_file.read_text() == new_yaml_config + + with patch.object(hass_config, "YAML_CONFIG_FILE", new_yaml_config_file): + await hass.services.async_call( + "mqtt", + SERVICE_RELOAD, + {}, + blocking=True, + ) + await hass.async_block_till_done() + + assert "" in caplog.text + + async def help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config): """Test reloading an MQTT platform.""" # Create and test an old config of 2 entities based on the config supplied @@ -1549,21 +1568,10 @@ async def help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config new_config_2["name"] = "test_new_2" new_config_3 = copy.deepcopy(config) new_config_3["name"] = "test_new_3" - new_yaml_config_file = tmp_path / "configuration.yaml" - new_yaml_config = yaml.dump({domain: [new_config_1, new_config_2, new_config_3]}) - new_yaml_config_file.write_text(new_yaml_config) - assert new_yaml_config_file.read_text() == new_yaml_config - with patch.object(hass_config, "YAML_CONFIG_FILE", new_yaml_config_file): - await hass.services.async_call( - "mqtt", - SERVICE_RELOAD, - {}, - blocking=True, - ) - await hass.async_block_till_done() - - assert "" in caplog.text + await help_test_reload_with_config( + hass, caplog, tmp_path, domain, [new_config_1, new_config_2, new_config_3] + ) assert len(hass.states.async_all(domain)) == 3 diff --git a/tests/components/mqtt/test_sensor.py b/tests/components/mqtt/test_sensor.py index a511938f0d1a10..ad43a7b23533c4 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -43,6 +43,7 @@ help_test_entity_disabled_by_default, help_test_entity_id_update_discovery_update, help_test_entity_id_update_subscriptions, + help_test_reload_with_config, help_test_reloadable, help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, @@ -52,7 +53,11 @@ help_test_update_with_json_attrs_not_dict, ) -from tests.common import async_fire_mqtt_message, async_fire_time_changed +from tests.common import ( + assert_setup_component, + async_fire_mqtt_message, + async_fire_time_changed, +) DEFAULT_CONFIG = { sensor.DOMAIN: {"platform": "mqtt", "name": "test", "state_topic": "test-topic"} @@ -935,6 +940,92 @@ async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) +async def test_cleanup_triggers_and_restoring_state( + hass, mqtt_mock, caplog, tmp_path, freezer +): + """Test cleanup old triggers at reloading and restoring the state.""" + domain = sensor.DOMAIN + config1 = copy.deepcopy(DEFAULT_CONFIG[domain]) + config1["name"] = "test1" + config1["expire_after"] = 30 + config1["state_topic"] = "test-topic1" + config2 = copy.deepcopy(DEFAULT_CONFIG[domain]) + config2["name"] = "test2" + config2["expire_after"] = 5 + config2["state_topic"] = "test-topic2" + + freezer.move_to("2022-02-02 12:01:00+01:00") + + assert await async_setup_component( + hass, + domain, + {domain: [config1, config2]}, + ) + await hass.async_block_till_done() + async_fire_mqtt_message(hass, "test-topic1", "100") + state = hass.states.get("sensor.test1") + assert state.state == "100" + + async_fire_mqtt_message(hass, "test-topic2", "200") + state = hass.states.get("sensor.test2") + assert state.state == "200" + + freezer.move_to("2022-02-02 12:01:10+01:00") + + await help_test_reload_with_config( + hass, caplog, tmp_path, domain, [config1, config2] + ) + await hass.async_block_till_done() + + assert "Clean up expire after trigger for sensor.test1" in caplog.text + assert "Clean up expire after trigger for sensor.test2" not in caplog.text + assert ( + "State recovered after reload for sensor.test1, remaining time before expiring" + in caplog.text + ) + assert "State recovered after reload for sensor.test2" not in caplog.text + + state = hass.states.get("sensor.test1") + assert state.state == "100" + + state = hass.states.get("sensor.test2") + assert state.state == STATE_UNAVAILABLE + + async_fire_mqtt_message(hass, "test-topic1", "101") + state = hass.states.get("sensor.test1") + assert state.state == "101" + + async_fire_mqtt_message(hass, "test-topic2", "201") + state = hass.states.get("sensor.test2") + assert state.state == "201" + + +async def test_skip_restoring_state_with_over_due_expire_trigger( + hass, mqtt_mock, caplog, freezer +): + """Test restoring a state with over due expire timer.""" + + freezer.move_to("2022-02-02 12:02:00+01:00") + domain = sensor.DOMAIN + config3 = copy.deepcopy(DEFAULT_CONFIG[domain]) + config3["name"] = "test3" + config3["expire_after"] = 10 + config3["state_topic"] = "test-topic3" + fake_state = ha.State( + "sensor.test3", + "300", + {}, + last_changed=datetime.fromisoformat("2022-02-02 12:01:35+01:00"), + ) + with patch( + "homeassistant.helpers.restore_state.RestoreEntity.async_get_last_state", + return_value=fake_state, + ), assert_setup_component(1, domain): + assert await async_setup_component(hass, domain, {domain: config3}) + await hass.async_block_till_done() + assert "Skip state recovery after reload for sensor.test3" in caplog.text + + @pytest.mark.parametrize( "topic,value,attribute,attribute_value", [ From 530fc8a9af1d2f4b5d93aa460694668efb279957 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 2 Feb 2022 09:16:29 -0600 Subject: [PATCH 0192/1098] Ensure unifiprotect discovery can be ignored (#65406) --- .../components/unifiprotect/config_flow.py | 6 +++++- .../unifiprotect/test_config_flow.py | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/unifiprotect/config_flow.py b/homeassistant/components/unifiprotect/config_flow.py index 720a46b4659699..0890a962e5b4cd 100644 --- a/homeassistant/components/unifiprotect/config_flow.py +++ b/homeassistant/components/unifiprotect/config_flow.py @@ -90,7 +90,11 @@ async def async_step_discovery( await self.async_set_unique_id(mac) source_ip = discovery_info["source_ip"] direct_connect_domain = discovery_info["direct_connect_domain"] - for entry in self._async_current_entries(include_ignore=False): + for entry in self._async_current_entries(): + if entry.source == config_entries.SOURCE_IGNORE: + if entry.unique_id == mac: + return self.async_abort(reason="already_configured") + continue entry_host = entry.data[CONF_HOST] entry_has_direct_connect = _host_is_direct_connect(entry_host) if entry.unique_id == mac: diff --git a/tests/components/unifiprotect/test_config_flow.py b/tests/components/unifiprotect/test_config_flow.py index fc5b2b32873199..a1609984be302e 100644 --- a/tests/components/unifiprotect/test_config_flow.py +++ b/tests/components/unifiprotect/test_config_flow.py @@ -723,3 +723,24 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "already_configured" + + +async def test_discovery_can_be_ignored(hass: HomeAssistant, mock_nvr: NVR) -> None: + """Test a discovery can be ignored.""" + mock_config = MockConfigEntry( + domain=DOMAIN, + data={}, + unique_id=DEVICE_MAC_ADDRESS.upper().replace(":", ""), + source=config_entries.SOURCE_IGNORE, + ) + mock_config.add_to_hass(hass) + with _patch_discovery(): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DISCOVERY}, + data=UNIFI_DISCOVERY_DICT, + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" From c8e64358b1a4016ed114724decb16ca4b26db0f3 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Wed, 2 Feb 2022 17:39:33 +0100 Subject: [PATCH 0193/1098] Set last_reset for integrated entities in IoTaWatt (#65143) * Set last_reset for integrated entities For entities which integrate over time (like energy in watt hours) the iotawattpy library uses the beginning of the year as start date by default (using relative time specification "y", see [1]). Since PR #56974 state class has been changed from TOTAL_INCREASING to TOTAL. However, the relative start date of "y" causes the value to reset to zero at the beginning of the year. This fixes it by setting last_reset properly, which takes such resets into account. While at it, let's set the cycle to one day. This lowers the load on IoTaWatt (fetching with start date beginning of the day seems to response faster than beginning of the year). [1]: https://docs.iotawatt.com/en/master/query.html#relative-time * Update homeassistant/components/iotawatt/sensor.py * Update homeassistant/components/iotawatt/coordinator.py Co-authored-by: Franck Nijhof --- homeassistant/components/iotawatt/coordinator.py | 1 + homeassistant/components/iotawatt/sensor.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/homeassistant/components/iotawatt/coordinator.py b/homeassistant/components/iotawatt/coordinator.py index ada9c9fb346ca0..46a0ac81d90e83 100644 --- a/homeassistant/components/iotawatt/coordinator.py +++ b/homeassistant/components/iotawatt/coordinator.py @@ -51,6 +51,7 @@ async def _async_update_data(self): httpx_client.get_async_client(self.hass), self.entry.data.get(CONF_USERNAME), self.entry.data.get(CONF_PASSWORD), + integratedInterval="d", ) try: is_authenticated = await api.connect() diff --git a/homeassistant/components/iotawatt/sensor.py b/homeassistant/components/iotawatt/sensor.py index 62f65741566873..acc4577fa8a172 100644 --- a/homeassistant/components/iotawatt/sensor.py +++ b/homeassistant/components/iotawatt/sensor.py @@ -8,6 +8,7 @@ from iotawattpy.sensor import Sensor from homeassistant.components.sensor import ( + ATTR_LAST_RESET, SensorDeviceClass, SensorEntity, SensorEntityDescription, @@ -219,6 +220,9 @@ def extra_state_attributes(self) -> dict[str, str]: attrs = {"type": data.getType()} if attrs["type"] == "Input": attrs["channel"] = data.getChannel() + if (begin := data.getBegin()) and (last_reset := dt.parse_datetime(begin)): + attrs[ATTR_LAST_RESET] = last_reset.isoformat() + return attrs @property From 5a34feb7de440e0df748c9db500facc72a4c2646 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 2 Feb 2022 17:58:14 +0100 Subject: [PATCH 0194/1098] Don't warn on time.sleep injected by the debugger (#65420) --- homeassistant/util/async_.py | 17 +++++++++++--- tests/util/test_async.py | 45 +++++++++++++++++++++++++++++++----- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py index 229a8fef366bca..8f9526b6800696 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -88,7 +88,7 @@ def run_callback() -> None: return future -def check_loop(strict: bool = True) -> None: +def check_loop(func: Callable, strict: bool = True) -> None: """Warn if called inside the event loop. Raise if `strict` is True.""" try: get_running_loop() @@ -101,7 +101,18 @@ def check_loop(strict: bool = True) -> None: found_frame = None - for frame in reversed(extract_stack()): + stack = extract_stack() + + if ( + func.__name__ == "sleep" + and len(stack) >= 3 + and stack[-3].filename.endswith("pydevd.py") + ): + # Don't report `time.sleep` injected by the debugger (pydevd.py) + # stack[-1] is us, stack[-2] is protected_loop_func, stack[-3] is the offender + return + + for frame in reversed(stack): for path in ("custom_components/", "homeassistant/components/"): try: index = frame.filename.index(path) @@ -152,7 +163,7 @@ def protect_loop(func: Callable, strict: bool = True) -> Callable: @functools.wraps(func) def protected_loop_func(*args, **kwargs): # type: ignore - check_loop(strict=strict) + check_loop(func, strict=strict) return func(*args, **kwargs) return protected_loop_func diff --git a/tests/util/test_async.py b/tests/util/test_async.py index d272da8fe96ba0..f02d3c03b4b713 100644 --- a/tests/util/test_async.py +++ b/tests/util/test_async.py @@ -5,6 +5,7 @@ import pytest +from homeassistant import block_async_io from homeassistant.util import async_ as hasync @@ -70,10 +71,14 @@ def test_run_callback_threadsafe_from_inside_event_loop(mock_ident, _): assert len(loop.call_soon_threadsafe.mock_calls) == 2 +def banned_function(): + """Mock banned function.""" + + async def test_check_loop_async(): """Test check_loop detects when called from event loop without integration context.""" with pytest.raises(RuntimeError): - hasync.check_loop() + hasync.check_loop(banned_function) async def test_check_loop_async_integration(caplog): @@ -98,7 +103,7 @@ async def test_check_loop_async_integration(caplog): ), ], ): - hasync.check_loop() + hasync.check_loop(banned_function) assert ( "Detected blocking call inside the event loop. This is causing stability issues. " "Please report issue for hue doing blocking calls at " @@ -129,7 +134,7 @@ async def test_check_loop_async_integration_non_strict(caplog): ), ], ): - hasync.check_loop(strict=False) + hasync.check_loop(banned_function, strict=False) assert ( "Detected blocking call inside the event loop. This is causing stability issues. " "Please report issue for hue doing blocking calls at " @@ -160,7 +165,7 @@ async def test_check_loop_async_custom(caplog): ), ], ): - hasync.check_loop() + hasync.check_loop(banned_function) assert ( "Detected blocking call inside the event loop. This is causing stability issues. " "Please report issue to the custom component author for hue doing blocking calls " @@ -170,7 +175,7 @@ async def test_check_loop_async_custom(caplog): def test_check_loop_sync(caplog): """Test check_loop does nothing when called from thread.""" - hasync.check_loop() + hasync.check_loop(banned_function) assert "Detected blocking call inside the event loop" not in caplog.text @@ -179,10 +184,38 @@ def test_protect_loop_sync(): func = Mock() with patch("homeassistant.util.async_.check_loop") as mock_check_loop: hasync.protect_loop(func)(1, test=2) - mock_check_loop.assert_called_once_with(strict=True) + mock_check_loop.assert_called_once_with(func, strict=True) func.assert_called_once_with(1, test=2) +async def test_protect_loop_debugger_sleep(caplog): + """Test time.sleep injected by the debugger is not reported.""" + block_async_io.enable() + + with patch( + "homeassistant.util.async_.extract_stack", + return_value=[ + Mock( + filename="/home/paulus/homeassistant/.venv/blah/pydevd.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/homeassistant/util/async.py", + lineno="123", + line="protected_loop_func", + ), + Mock( + filename="/home/paulus/homeassistant/util/async.py", + lineno="123", + line="check_loop()", + ), + ], + ): + time.sleep(0) + assert "Detected blocking call inside the event loop" not in caplog.text + + async def test_gather_with_concurrency(): """Test gather_with_concurrency limits the number of running tasks.""" From fda0fbd11536019f4cd538ee13ac588cede4f11d Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 2 Feb 2022 18:08:48 +0100 Subject: [PATCH 0195/1098] Stringify MQTT payload in mqtt/debug/info WS response (#65429) --- homeassistant/components/mqtt/debug_info.py | 2 +- tests/components/mqtt/test_common.py | 2 +- tests/components/mqtt/test_init.py | 64 ++++++++++++++++++++- 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/mqtt/debug_info.py b/homeassistant/components/mqtt/debug_info.py index e462d76fa31045..3e32d301b707fe 100644 --- a/homeassistant/components/mqtt/debug_info.py +++ b/homeassistant/components/mqtt/debug_info.py @@ -139,7 +139,7 @@ async def info_for_device(hass, device_id): "topic": topic, "messages": [ { - "payload": msg.payload, + "payload": str(msg.payload), "qos": msg.qos, "retain": msg.retain, "time": msg.timestamp, diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index 593d08f4c87b9d..78c37b1105a4c7 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -1222,7 +1222,7 @@ async def help_test_entity_debug_info_message( "topic": topic, "messages": [ { - "payload": payload, + "payload": str(payload), "qos": 0, "retain": False, "time": start_dt, diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index a71c332b70b3e8..9101b895218f9f 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -3,7 +3,7 @@ from datetime import datetime, timedelta import json import ssl -from unittest.mock import AsyncMock, MagicMock, call, mock_open, patch +from unittest.mock import ANY, AsyncMock, MagicMock, call, mock_open, patch import pytest import voluptuous as vol @@ -1540,6 +1540,68 @@ async def test_mqtt_ws_get_device_debug_info( assert response["result"] == expected_result +async def test_mqtt_ws_get_device_debug_info_binary( + hass, device_reg, hass_ws_client, mqtt_mock +): + """Test MQTT websocket device debug info.""" + config = { + "device": {"identifiers": ["0AFFD2"]}, + "platform": "mqtt", + "topic": "foobar/image", + "unique_id": "unique", + } + data = json.dumps(config) + + async_fire_mqtt_message(hass, "homeassistant/camera/bla/config", data) + await hass.async_block_till_done() + + # Verify device entry is created + device_entry = device_reg.async_get_device({("mqtt", "0AFFD2")}) + assert device_entry is not None + + small_png = ( + b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x04\x00\x00\x00\x04\x08\x06" + b"\x00\x00\x00\xa9\xf1\x9e~\x00\x00\x00\x13IDATx\xdac\xfc\xcf\xc0P\xcf\x80\x04" + b"\x18I\x17\x00\x00\xf2\xae\x05\xfdR\x01\xc2\xde\x00\x00\x00\x00IEND\xaeB`\x82" + ) + async_fire_mqtt_message(hass, "foobar/image", small_png) + await hass.async_block_till_done() + + client = await hass_ws_client(hass) + await client.send_json( + {"id": 5, "type": "mqtt/device/debug_info", "device_id": device_entry.id} + ) + response = await client.receive_json() + assert response["success"] + expected_result = { + "entities": [ + { + "entity_id": "camera.mqtt_camera", + "subscriptions": [ + { + "topic": "foobar/image", + "messages": [ + { + "payload": str(small_png), + "qos": 0, + "retain": False, + "time": ANY, + "topic": "foobar/image", + } + ], + } + ], + "discovery_data": { + "payload": config, + "topic": "homeassistant/camera/bla/config", + }, + } + ], + "triggers": [], + } + assert response["result"] == expected_result + + async def test_debug_info_multiple_devices(hass, mqtt_mock): """Test we get correct debug_info when multiple devices are present.""" devices = [ From 81ad56b8ad9478739179701e6accfffb63771dff Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Wed, 2 Feb 2022 18:11:06 +0100 Subject: [PATCH 0196/1098] Add events on cloud connect and disconnect (#65215) * Add events on cloud connect and disconnect Signed-off-by: cgtobi * Use event capture helper Signed-off-by: cgtobi * Provide listener method instead of public event Signed-off-by: cgtobi * Add test for disconnect notification Signed-off-by: cgtobi * Apply suggestions from code review Co-authored-by: Martin Hjelmare * Use Enum Signed-off-by: cgtobi * Add module level api Signed-off-by: cgtobi * Apply suggestions from code review Co-authored-by: Martin Hjelmare * Clean up dead code Signed-off-by: cgtobi * Flake8 Signed-off-by: cgtobi * Clean up Co-authored-by: Martin Hjelmare --- homeassistant/components/cloud/__init__.py | 34 ++++++++++++++++++++++ tests/components/cloud/test_init.py | 19 ++++++++++++ 2 files changed, 53 insertions(+) diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index 7353ba6fd21879..07c2898f20431a 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -1,5 +1,7 @@ """Component to integrate the Home Assistant cloud.""" import asyncio +from collections.abc import Callable +from enum import Enum from hass_nabucasa import Cloud import voluptuous as vol @@ -18,6 +20,10 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv, entityfilter from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass from homeassistant.util.aiohttp import MockRequest @@ -52,6 +58,8 @@ SERVICE_REMOTE_CONNECT = "remote_connect" SERVICE_REMOTE_DISCONNECT = "remote_disconnect" +SIGNAL_CLOUD_CONNECTION_STATE = "CLOUD_CONNECTION_STATE" + ALEXA_ENTITY_SCHEMA = vol.Schema( { @@ -118,6 +126,13 @@ class CloudNotConnected(CloudNotAvailable): """Raised when an action requires the cloud but it's not connected.""" +class CloudConnectionState(Enum): + """Cloud connection state.""" + + CLOUD_CONNECTED = "cloud_connected" + CLOUD_DISCONNECTED = "cloud_disconnected" + + @bind_hass @callback def async_is_logged_in(hass: HomeAssistant) -> bool: @@ -135,6 +150,14 @@ def async_is_connected(hass: HomeAssistant) -> bool: return DOMAIN in hass.data and hass.data[DOMAIN].iot.connected +@callback +def async_listen_connection_change( + hass: HomeAssistant, target: Callable[[CloudConnectionState], None] +) -> Callable[[], None]: + """Notify on connection state changes.""" + return async_dispatcher_connect(hass, SIGNAL_CLOUD_CONNECTION_STATE, target) + + @bind_hass @callback def async_active_subscription(hass: HomeAssistant) -> bool: @@ -252,11 +275,22 @@ async def _on_connect(): Platform.TTS, DOMAIN, {}, config ) + async_dispatcher_send( + hass, SIGNAL_CLOUD_CONNECTION_STATE, CloudConnectionState.CLOUD_CONNECTED + ) + + async def _on_disconnect(): + """Handle cloud disconnect.""" + async_dispatcher_send( + hass, SIGNAL_CLOUD_CONNECTION_STATE, CloudConnectionState.CLOUD_DISCONNECTED + ) + async def _on_initialized(): """Update preferences.""" await prefs.async_update(remote_domain=cloud.remote.instance_domain) cloud.iot.register_on_connect(_on_connect) + cloud.iot.register_on_disconnect(_on_disconnect) cloud.register_on_initialized(_on_initialized) await cloud.initialize() diff --git a/tests/components/cloud/test_init.py b/tests/components/cloud/test_init.py index 4a513aff1172d6..78a8f83eef689e 100644 --- a/tests/components/cloud/test_init.py +++ b/tests/components/cloud/test_init.py @@ -137,6 +137,14 @@ async def test_on_connect(hass, mock_cloud_fixture): assert len(hass.states.async_entity_ids("binary_sensor")) == 0 + cloud_states = [] + + def handle_state(cloud_state): + nonlocal cloud_states + cloud_states.append(cloud_state) + + cloud.async_listen_connection_change(hass, handle_state) + assert "async_setup" in str(cl.iot._on_connect[-1]) await cl.iot._on_connect[-1]() await hass.async_block_till_done() @@ -149,6 +157,17 @@ async def test_on_connect(hass, mock_cloud_fixture): assert len(mock_load.mock_calls) == 0 + assert len(cloud_states) == 1 + assert cloud_states[-1] == cloud.CloudConnectionState.CLOUD_CONNECTED + + assert len(cl.iot._on_disconnect) == 2 + assert "async_setup" in str(cl.iot._on_disconnect[-1]) + await cl.iot._on_disconnect[-1]() + await hass.async_block_till_done() + + assert len(cloud_states) == 2 + assert cloud_states[-1] == cloud.CloudConnectionState.CLOUD_DISCONNECTED + async def test_remote_ui_url(hass, mock_cloud_fixture): """Test getting remote ui url.""" From 494ef2f9b2e5feeb10dce94a02ee90a9a7430b6a Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Wed, 2 Feb 2022 18:56:34 +0100 Subject: [PATCH 0197/1098] Adjust config_entry UpdateListenerType signature (#65410) Co-authored-by: epenet --- homeassistant/components/alarmdecoder/__init__.py | 2 +- homeassistant/config_entries.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/alarmdecoder/__init__.py b/homeassistant/components/alarmdecoder/__init__.py index fd0b76a5c8a772..3be3d67e32b3bb 100644 --- a/homeassistant/components/alarmdecoder/__init__.py +++ b/homeassistant/components/alarmdecoder/__init__.py @@ -155,7 +155,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return True -async def _update_listener(hass: HomeAssistant, entry: ConfigEntry): +async def _update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" _LOGGER.debug("AlarmDecoder options updated: %s", entry.as_dict()["options"]) await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 32014be77748c9..a5d0ca736fc40a 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -2,7 +2,7 @@ from __future__ import annotations import asyncio -from collections.abc import Callable, Iterable, Mapping +from collections.abc import Awaitable, Callable, Iterable, Mapping from contextvars import ContextVar import dataclasses from enum import Enum @@ -159,7 +159,7 @@ class OperationNotAllowed(ConfigError): """Raised when a config entry operation is not allowed.""" -UpdateListenerType = Callable[[HomeAssistant, "ConfigEntry"], Any] +UpdateListenerType = Callable[[HomeAssistant, "ConfigEntry"], Awaitable[None]] class ConfigEntry: From 83fa4df641ab9a0cea82679683905e22494479b1 Mon Sep 17 00:00:00 2001 From: Colin Robbins Date: Wed, 2 Feb 2022 19:44:16 +0000 Subject: [PATCH 0198/1098] Fix Shodan sensor (#65443) --- homeassistant/components/shodan/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/shodan/sensor.py b/homeassistant/components/shodan/sensor.py index 6fc73f40096c03..bdef681fdd262e 100644 --- a/homeassistant/components/shodan/sensor.py +++ b/homeassistant/components/shodan/sensor.py @@ -67,7 +67,7 @@ def __init__(self, data: ShodanData, name: str) -> None: def update(self) -> None: """Get the latest data and updates the states.""" data = self.data.update() - self._attr_native_value = data.details["total"] + self._attr_native_value = data["total"] class ShodanData: From 256ad084c537f9549d2e03be1fe330d717556d4d Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 3 Feb 2022 00:14:18 +0000 Subject: [PATCH 0199/1098] [ci skip] Translation update --- .../acmeda/translations/es-419.json | 9 ++++ .../components/adax/translations/es-419.json | 18 +++++++ .../airthings/translations/es-419.json | 2 + .../airtouch4/translations/es-419.json | 12 +++++ .../alarmdecoder/translations/es-419.json | 8 ++- .../components/ambee/translations/es-419.json | 3 ++ .../amberelectric/translations/es-419.json | 13 ++++- .../androidtv/translations/es-419.json | 53 +++++++++++++++++++ .../arcam_fmj/translations/es-419.json | 5 ++ .../august/translations/es-419.json | 9 ++++ .../translations/es-419.json | 12 +++++ .../aussie_broadband/translations/es-419.json | 28 ++++++++++ .../azure_event_hub/translations/es-419.json | 43 +++++++++++++++ .../balboa/translations/es-419.json | 18 +++++++ .../binary_sensor/translations/es-419.json | 31 +++++++++++ .../translations/es-419.json | 3 +- .../broadlink/translations/es-419.json | 6 +++ .../components/brunt/translations/es-419.json | 12 +++++ .../buienradar/translations/es-419.json | 13 +++++ .../button/translations/es-419.json | 7 +++ .../canary/translations/es-419.json | 1 + .../climacell/translations/es-419.json | 6 ++- .../climacell/translations/sensor.es-419.json | 27 ++++++++++ .../cloudflare/translations/es-419.json | 3 ++ .../co2signal/translations/es-419.json | 9 +++- .../coinbase/translations/es-419.json | 13 +++++ .../cpuspeed/translations/es-419.json | 13 +++++ .../crownstone/translations/es-419.json | 48 +++++++++++++++++ .../denonavr/translations/es-419.json | 29 ++++++++++ .../dlna_dmr/translations/es-419.json | 25 +++++++++ .../components/eafm/translations/el.json | 9 ++++ .../homekit_controller/translations/el.json | 10 +++- .../components/insteon/translations/el.json | 4 ++ .../components/konnected/translations/el.json | 1 + .../components/netatmo/translations/el.json | 15 ++++++ .../rtsp_to_webrtc/translations/el.json | 3 +- .../components/vizio/translations/el.json | 7 ++- 37 files changed, 516 insertions(+), 12 deletions(-) create mode 100644 homeassistant/components/acmeda/translations/es-419.json create mode 100644 homeassistant/components/airtouch4/translations/es-419.json create mode 100644 homeassistant/components/androidtv/translations/es-419.json create mode 100644 homeassistant/components/aurora_abb_powerone/translations/es-419.json create mode 100644 homeassistant/components/aussie_broadband/translations/es-419.json create mode 100644 homeassistant/components/azure_event_hub/translations/es-419.json create mode 100644 homeassistant/components/balboa/translations/es-419.json create mode 100644 homeassistant/components/brunt/translations/es-419.json create mode 100644 homeassistant/components/buienradar/translations/es-419.json create mode 100644 homeassistant/components/button/translations/es-419.json create mode 100644 homeassistant/components/climacell/translations/sensor.es-419.json create mode 100644 homeassistant/components/cpuspeed/translations/es-419.json create mode 100644 homeassistant/components/crownstone/translations/es-419.json create mode 100644 homeassistant/components/dlna_dmr/translations/es-419.json diff --git a/homeassistant/components/acmeda/translations/es-419.json b/homeassistant/components/acmeda/translations/es-419.json new file mode 100644 index 00000000000000..fff5e8fa56591d --- /dev/null +++ b/homeassistant/components/acmeda/translations/es-419.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "title": "Elija un concentrador para agregar" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/adax/translations/es-419.json b/homeassistant/components/adax/translations/es-419.json index e2a1fdbc9f322e..85df150d6630b2 100644 --- a/homeassistant/components/adax/translations/es-419.json +++ b/homeassistant/components/adax/translations/es-419.json @@ -1,8 +1,26 @@ { "config": { + "abort": { + "heater_not_available": "Calentador no disponible. Intente restablecer el calentador presionando + y OK durante algunos segundos.", + "heater_not_found": "No se encontr\u00f3 el calentador. Intente acercar el calentador a la computadora de Home Assistant." + }, "step": { + "cloud": { + "data": { + "account_id": "ID de cuenta", + "password": "Contrase\u00f1a" + } + }, + "local": { + "data": { + "wifi_pswd": "Contrase\u00f1a de Wi-Fi", + "wifi_ssid": "Wi-Fi SSID" + }, + "description": "Reinicie el calentador presionando + y OK hasta que la pantalla muestre 'Restablecer'. Luego mantenga presionado el bot\u00f3n OK en el calentador hasta que el led azul comience a parpadear antes de presionar Enviar. La configuraci\u00f3n del calentador puede tardar algunos minutos." + }, "user": { "data": { + "account_id": "ID de cuenta", "connection_type": "Seleccione el tipo de conexi\u00f3n" }, "description": "Seleccione el tipo de conexi\u00f3n. Local requiere calentadores con bluetooth" diff --git a/homeassistant/components/airthings/translations/es-419.json b/homeassistant/components/airthings/translations/es-419.json index 952b27937dfcca..bdda51d4093128 100644 --- a/homeassistant/components/airthings/translations/es-419.json +++ b/homeassistant/components/airthings/translations/es-419.json @@ -3,6 +3,8 @@ "step": { "user": { "data": { + "description": "Inicie sesi\u00f3n en {url} para encontrar sus credenciales", + "id": "ID", "secret": "Secreto" } } diff --git a/homeassistant/components/airtouch4/translations/es-419.json b/homeassistant/components/airtouch4/translations/es-419.json new file mode 100644 index 00000000000000..1f4fd90ff08dbf --- /dev/null +++ b/homeassistant/components/airtouch4/translations/es-419.json @@ -0,0 +1,12 @@ +{ + "config": { + "error": { + "no_units": "No se pudo encontrar ning\u00fan grupo de AirTouch 4." + }, + "step": { + "user": { + "title": "Configure los detalles de conexi\u00f3n de su AirTouch 4." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/es-419.json b/homeassistant/components/alarmdecoder/translations/es-419.json index d1f87c83cc8947..d5124e40856e50 100644 --- a/homeassistant/components/alarmdecoder/translations/es-419.json +++ b/homeassistant/components/alarmdecoder/translations/es-419.json @@ -33,20 +33,26 @@ "init": { "data": { "edit_select": "Editar" - } + }, + "description": "\u00bfQu\u00e9 le gustar\u00eda editar?" }, "zone_details": { "data": { + "zone_loop": "Bucle de RF", "zone_name": "Nombre de zona", + "zone_relayaddr": "Direcci\u00f3n de retransmisi\u00f3n", + "zone_relaychan": "Canal de retransmisi\u00f3n", "zone_rfid": "Serie RF", "zone_type": "Tipo de zona" }, + "description": "Introduzca los detalles de la zona {zone_number}. Para eliminar la zona {zone_number}, deje el nombre de la zona en blanco.", "title": "Configurar AlarmDecoder" }, "zone_select": { "data": { "zone_number": "N\u00famero de zona" }, + "description": "Ingrese el n\u00famero de zona que le gustar\u00eda agregar, editar o eliminar.", "title": "Configurar AlarmDecoder" } } diff --git a/homeassistant/components/ambee/translations/es-419.json b/homeassistant/components/ambee/translations/es-419.json index dee7d514b4865b..de5ce971fa0c4d 100644 --- a/homeassistant/components/ambee/translations/es-419.json +++ b/homeassistant/components/ambee/translations/es-419.json @@ -5,6 +5,9 @@ "data": { "description": "Vuelva a autenticarse con su cuenta de Ambee." } + }, + "user": { + "description": "Configure Ambee para que se integre con Home Assistant." } } } diff --git a/homeassistant/components/amberelectric/translations/es-419.json b/homeassistant/components/amberelectric/translations/es-419.json index 7b82a2f08f74a3..6cb3d42656e619 100644 --- a/homeassistant/components/amberelectric/translations/es-419.json +++ b/homeassistant/components/amberelectric/translations/es-419.json @@ -3,8 +3,17 @@ "step": { "site": { "data": { - "site_name": "Nombre del sitio" - } + "site_name": "Nombre del sitio", + "site_nmi": "NMI del sitio" + }, + "description": "Seleccione el NMI del sitio que le gustar\u00eda agregar" + }, + "user": { + "data": { + "api_token": "Token API", + "site_id": "ID del sitio" + }, + "description": "Vaya a {api_url} para generar una clave de API" } } } diff --git a/homeassistant/components/androidtv/translations/es-419.json b/homeassistant/components/androidtv/translations/es-419.json new file mode 100644 index 00000000000000..0e97b8c1265764 --- /dev/null +++ b/homeassistant/components/androidtv/translations/es-419.json @@ -0,0 +1,53 @@ +{ + "config": { + "step": { + "user": { + "data": { + "adb_server_ip": "Direcci\u00f3n IP del servidor ADB (dejar en blanco para no usar)", + "adb_server_port": "Puerto del servidor ADB", + "adbkey": "Ruta a su archivo de clave ADB (d\u00e9jelo en blanco para generarlo autom\u00e1ticamente)", + "device_class": "El tipo de dispositivo" + }, + "description": "Establezca los par\u00e1metros requeridos para conectarse a su dispositivo Android TV", + "title": "Android TV" + } + } + }, + "options": { + "error": { + "invalid_det_rules": "Reglas de detecci\u00f3n de estado no v\u00e1lidas" + }, + "step": { + "apps": { + "data": { + "app_delete": "Marque para eliminar esta aplicaci\u00f3n", + "app_id": "ID de aplicaci\u00f3n", + "app_name": "Nombre de la aplicaci\u00f3n" + }, + "description": "Configurar la identificaci\u00f3n de la aplicaci\u00f3n {app_id}", + "title": "Configurar aplicaciones de Android TV" + }, + "init": { + "data": { + "apps": "Configurar lista de aplicaciones", + "exclude_unnamed_apps": "Excluir aplicaciones con nombre desconocido de la lista de fuentes", + "get_sources": "Recuperar las aplicaciones en ejecuci\u00f3n como la lista de fuentes", + "screencap": "Usar captura de pantalla para la car\u00e1tula del \u00e1lbum", + "state_detection_rules": "Configurar reglas de detecci\u00f3n de estado", + "turn_off_command": "Comando de apagado de shell ADB (d\u00e9jelo vac\u00edo por defecto)", + "turn_on_command": "Comando de activaci\u00f3n de shell ADB (d\u00e9jelo vac\u00edo por defecto)" + }, + "title": "Opciones de Android TV" + }, + "rules": { + "data": { + "rule_delete": "Marque para eliminar esta regla", + "rule_id": "ID de aplicaci\u00f3n", + "rule_values": "Lista de reglas de detecci\u00f3n de estado (ver documentaci\u00f3n)" + }, + "description": "Configure la regla de detecci\u00f3n para la identificaci\u00f3n de la aplicaci\u00f3n {rule_id}", + "title": "Configurar reglas de detecci\u00f3n de estado de Android TV" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/es-419.json b/homeassistant/components/arcam_fmj/translations/es-419.json index a69b353354b744..78655f3ac42f44 100644 --- a/homeassistant/components/arcam_fmj/translations/es-419.json +++ b/homeassistant/components/arcam_fmj/translations/es-419.json @@ -8,5 +8,10 @@ "description": "Ingrese el nombre de host o la direcci\u00f3n IP del dispositivo." } } + }, + "device_automation": { + "trigger_type": { + "turn_on": "{entity_name} ha sido solicitada para encender" + } } } \ No newline at end of file diff --git a/homeassistant/components/august/translations/es-419.json b/homeassistant/components/august/translations/es-419.json index 7e5fe76d3afbcd..efb55133c9c75a 100644 --- a/homeassistant/components/august/translations/es-419.json +++ b/homeassistant/components/august/translations/es-419.json @@ -9,6 +9,15 @@ "unknown": "Error inesperado" }, "step": { + "reauth_validate": { + "description": "Introduzca la contrase\u00f1a para {username} .", + "title": "Volver a autenticar una cuenta de agosto" + }, + "user_validate": { + "data": { + "login_method": "M\u00e9todo de inicio de sesi\u00f3n" + } + }, "validation": { "data": { "code": "C\u00f3digo de verificaci\u00f3n" diff --git a/homeassistant/components/aurora_abb_powerone/translations/es-419.json b/homeassistant/components/aurora_abb_powerone/translations/es-419.json new file mode 100644 index 00000000000000..7efa4d7ff45137 --- /dev/null +++ b/homeassistant/components/aurora_abb_powerone/translations/es-419.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Puerto adaptador RS485 o USB-RS485" + }, + "description": "El inversor debe estar conectado a trav\u00e9s de un adaptador RS485, seleccione el puerto serie y la direcci\u00f3n del inversor seg\u00fan lo configurado en el panel LCD" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aussie_broadband/translations/es-419.json b/homeassistant/components/aussie_broadband/translations/es-419.json new file mode 100644 index 00000000000000..df9322bf01dca9 --- /dev/null +++ b/homeassistant/components/aussie_broadband/translations/es-419.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "no_services_found": "No se encontraron servicios para esta cuenta" + }, + "step": { + "reauth": { + "description": "Actualizar contrase\u00f1a para {username}" + }, + "service": { + "data": { + "services": "Servicios" + }, + "title": "Seleccione Servicios" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "services": "Servicios" + }, + "title": "Seleccione Servicios" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/azure_event_hub/translations/es-419.json b/homeassistant/components/azure_event_hub/translations/es-419.json new file mode 100644 index 00000000000000..0405051cead0f1 --- /dev/null +++ b/homeassistant/components/azure_event_hub/translations/es-419.json @@ -0,0 +1,43 @@ +{ + "config": { + "abort": { + "cannot_connect": "No se pudo conectar con las credenciales de configuration.yaml, elim\u00ednelo de yaml y use el flujo de configuraci\u00f3n.", + "unknown": "La conexi\u00f3n con las credenciales de la configuraci\u00f3n.yaml fall\u00f3 con un error desconocido, elim\u00ednelo de yaml y use el flujo de configuraci\u00f3n." + }, + "step": { + "conn_string": { + "data": { + "event_hub_connection_string": "Cadena de conexi\u00f3n del centro de eventos" + }, + "description": "Ingrese la cadena de conexi\u00f3n para: {event_hub_instance_name}", + "title": "M\u00e9todo de cadena de conexi\u00f3n" + }, + "sas": { + "data": { + "event_hub_namespace": "Espacio de nombres del centro de eventos", + "event_hub_sas_key": "Clave SAS del centro de eventos", + "event_hub_sas_policy": "Pol\u00edtica de SAS del centro de eventos" + }, + "description": "Ingrese las credenciales de SAS (firma de acceso compartido) para: {event_hub_instance_name}", + "title": "M\u00e9todo de credenciales SAS" + }, + "user": { + "data": { + "event_hub_instance_name": "Nombre de la instancia del centro de eventos", + "use_connection_string": "Usar cadena de conexi\u00f3n" + }, + "title": "Configure su integraci\u00f3n de Azure Event Hub" + } + } + }, + "options": { + "step": { + "options": { + "data": { + "send_interval": "Intervalo entre el env\u00edo de lotes al concentrador." + }, + "title": "Opciones para Azure Event Hub." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/balboa/translations/es-419.json b/homeassistant/components/balboa/translations/es-419.json new file mode 100644 index 00000000000000..16aa28fb6b2e52 --- /dev/null +++ b/homeassistant/components/balboa/translations/es-419.json @@ -0,0 +1,18 @@ +{ + "config": { + "step": { + "user": { + "title": "Con\u00e9ctese al dispositivo Wi-Fi de Balboa" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "sync_time": "Mant\u00e9n sincronizada la hora de tu Cliente Balboa Spa con Home Assistant" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/binary_sensor/translations/es-419.json b/homeassistant/components/binary_sensor/translations/es-419.json index dad07f9b771f2a..2951e71b114edf 100644 --- a/homeassistant/components/binary_sensor/translations/es-419.json +++ b/homeassistant/components/binary_sensor/translations/es-419.json @@ -2,6 +2,7 @@ "device_automation": { "condition_type": { "is_bat_low": "{entity_name} la bater\u00eda est\u00e1 baja", + "is_co": "{entity_name} est\u00e1 detectando mon\u00f3xido de carbono", "is_cold": "{entity_name} est\u00e1 fr\u00edo", "is_connected": "{entity_name} est\u00e1 conectado", "is_gas": "{entity_name} est\u00e1 detectando gas", @@ -11,12 +12,14 @@ "is_moist": "{entity_name} est\u00e1 h\u00famedo", "is_motion": "{entity_name} est\u00e1 detectando movimiento", "is_moving": "{entity_name} se est\u00e1 moviendo", + "is_no_co": "{entity_name} no detecta mon\u00f3xido de carbono", "is_no_gas": "{entity_name} no detecta gas", "is_no_light": "{entity_name} no detecta luz", "is_no_motion": "{entity_name} no detecta movimiento", "is_no_problem": "{entity_name} no detecta el problema", "is_no_smoke": "{entity_name} no detecta humo", "is_no_sound": "{entity_name} no detecta sonido", + "is_no_update": "{entity_name} est\u00e1 actualizado", "is_no_vibration": "{entity_name} no detecta vibraciones", "is_not_bat_low": "{entity_name} bater\u00eda est\u00e1 normal", "is_not_cold": "{entity_name} no est\u00e1 fr\u00edo", @@ -30,6 +33,8 @@ "is_not_plugged_in": "{entity_name} est\u00e1 desconectado", "is_not_powered": "{entity_name} no tiene encendido", "is_not_present": "{entity_name} no est\u00e1 presente", + "is_not_running": "{entity_name} no se est\u00e1 ejecutando", + "is_not_tampered": "{entity_name} no detecta la manipulaci\u00f3n", "is_not_unsafe": "{entity_name} es seguro", "is_occupied": "{entity_name} est\u00e1 ocupado", "is_off": "{entity_name} est\u00e1 apagado", @@ -39,6 +44,7 @@ "is_powered": "{entity_name} est\u00e1 encendido", "is_present": "{entity_name} est\u00e1 presente", "is_problem": "{entity_name} est\u00e1 detectando un problema", + "is_running": "{entity_name} se est\u00e1 ejecutando", "is_smoke": "{entity_name} est\u00e1 detectando humo", "is_sound": "{entity_name} est\u00e1 detectando sonido", "is_unsafe": "{entity_name} es inseguro", @@ -89,6 +95,19 @@ "vibration": "{entity_name} comenz\u00f3 a detectar vibraciones" } }, + "device_class": { + "cold": "fr\u00edo", + "gas": "gas", + "heat": "calor", + "moisture": "humedad", + "motion": "movimiento", + "occupancy": "ocupaci\u00f3n", + "power": "energ\u00eda", + "problem": "problema", + "smoke": "humo", + "sound": "sonido", + "vibration": "vibraci\u00f3n" + }, "state": { "_": { "off": "Desactivado", @@ -102,6 +121,10 @@ "off": "No esta cargando", "on": "Cargando" }, + "co": { + "off": "Despejado", + "on": "Detectado" + }, "cold": { "off": "Normal", "on": "Fr\u00edo" @@ -166,6 +189,10 @@ "off": "OK", "on": "Problema" }, + "running": { + "off": "No se est\u00e1 ejecutando", + "on": "Corriendo" + }, "safety": { "off": "Seguro", "on": "Inseguro" @@ -178,6 +205,10 @@ "off": "Despejado", "on": "Detectado" }, + "update": { + "off": "Actualizado", + "on": "Actualizaci\u00f3n disponible" + }, "vibration": { "off": "Despejado", "on": "Detectado" diff --git a/homeassistant/components/bmw_connected_drive/translations/es-419.json b/homeassistant/components/bmw_connected_drive/translations/es-419.json index 0bce46abd97097..bb19124b55c9ca 100644 --- a/homeassistant/components/bmw_connected_drive/translations/es-419.json +++ b/homeassistant/components/bmw_connected_drive/translations/es-419.json @@ -12,7 +12,8 @@ "step": { "account_options": { "data": { - "read_only": "Solo lectura (solo sensores y notificaci\u00f3n, sin ejecuci\u00f3n de servicios, sin bloqueo)" + "read_only": "Solo lectura (solo sensores y notificaci\u00f3n, sin ejecuci\u00f3n de servicios, sin bloqueo)", + "use_location": "Use la ubicaci\u00f3n de Home Assistant para encuestas de ubicaci\u00f3n de autom\u00f3viles (obligatorio para veh\u00edculos que no sean i3/i8 fabricados antes de julio de 2014)" } } } diff --git a/homeassistant/components/broadlink/translations/es-419.json b/homeassistant/components/broadlink/translations/es-419.json index 9c3129a2c6c0cd..1e96b5fee0d08f 100644 --- a/homeassistant/components/broadlink/translations/es-419.json +++ b/homeassistant/components/broadlink/translations/es-419.json @@ -21,6 +21,12 @@ }, "description": "{name} ({model} en {host}) est\u00e1 bloqueado. Esto puede provocar problemas de autenticaci\u00f3n en Home Assistant. \u00bfQuieres desbloquearlo?", "title": "Desbloquear el dispositivo (opcional)" + }, + "user": { + "data": { + "timeout": "Tiempo de espera" + }, + "title": "Conectarse al dispositivo" } } } diff --git a/homeassistant/components/brunt/translations/es-419.json b/homeassistant/components/brunt/translations/es-419.json new file mode 100644 index 00000000000000..a461c335f55456 --- /dev/null +++ b/homeassistant/components/brunt/translations/es-419.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "reauth_confirm": { + "description": "Vuelva a ingresar la contrase\u00f1a para: {username}" + }, + "user": { + "title": "Configura tu integraci\u00f3n Brunt" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/buienradar/translations/es-419.json b/homeassistant/components/buienradar/translations/es-419.json new file mode 100644 index 00000000000000..95968bce45c608 --- /dev/null +++ b/homeassistant/components/buienradar/translations/es-419.json @@ -0,0 +1,13 @@ +{ + "options": { + "step": { + "init": { + "data": { + "country_code": "C\u00f3digo de pa\u00eds del pa\u00eds para mostrar las im\u00e1genes de la c\u00e1mara.", + "delta": "Intervalo de tiempo en segundos entre las actualizaciones de la imagen de la c\u00e1mara", + "timeframe": "Minutos para anticipar el pron\u00f3stico de precipitaci\u00f3n" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/button/translations/es-419.json b/homeassistant/components/button/translations/es-419.json new file mode 100644 index 00000000000000..e3a46094da9ee6 --- /dev/null +++ b/homeassistant/components/button/translations/es-419.json @@ -0,0 +1,7 @@ +{ + "device_automation": { + "action_type": { + "press": "Presiona el bot\u00f3n {entity_name}" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/es-419.json b/homeassistant/components/canary/translations/es-419.json index 8ce6a8fb855a2b..57cc475b383def 100644 --- a/homeassistant/components/canary/translations/es-419.json +++ b/homeassistant/components/canary/translations/es-419.json @@ -1,5 +1,6 @@ { "config": { + "flow_title": "{name}", "step": { "user": { "title": "Conectarse a Canary" diff --git a/homeassistant/components/climacell/translations/es-419.json b/homeassistant/components/climacell/translations/es-419.json index deb60db2004c28..17c63089dbf19e 100644 --- a/homeassistant/components/climacell/translations/es-419.json +++ b/homeassistant/components/climacell/translations/es-419.json @@ -17,8 +17,10 @@ "data": { "timestep": "Min. entre pron\u00f3sticos de NowCast" }, - "description": "Si elige habilitar la entidad de pron\u00f3stico \"nowcast\", puede configurar el n\u00famero de minutos entre cada pron\u00f3stico. El n\u00famero de pron\u00f3sticos proporcionados depende del n\u00famero de minutos elegidos entre los pron\u00f3sticos." + "description": "Si elige habilitar la entidad de pron\u00f3stico \"nowcast\", puede configurar el n\u00famero de minutos entre cada pron\u00f3stico. El n\u00famero de pron\u00f3sticos proporcionados depende del n\u00famero de minutos elegidos entre los pron\u00f3sticos.", + "title": "Actualizar opciones de ClimaCell" } } - } + }, + "title": "ClimaCell" } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/sensor.es-419.json b/homeassistant/components/climacell/translations/sensor.es-419.json new file mode 100644 index 00000000000000..127177e84b443c --- /dev/null +++ b/homeassistant/components/climacell/translations/sensor.es-419.json @@ -0,0 +1,27 @@ +{ + "state": { + "climacell__health_concern": { + "good": "Bueno", + "hazardous": "Peligroso", + "moderate": "Moderado", + "unhealthy": "Insalubre", + "unhealthy_for_sensitive_groups": "Insalubre para grupos sensibles", + "very_unhealthy": "Muy poco saludable" + }, + "climacell__pollen_index": { + "high": "Alto", + "low": "Bajo", + "medium": "Medio", + "none": "Ninguno", + "very_high": "Muy alto", + "very_low": "Muy bajo" + }, + "climacell__precipitation_type": { + "freezing_rain": "Lluvia helada", + "ice_pellets": "Gr\u00e1nulos de hielo", + "none": "Ninguno", + "rain": "Lluvia", + "snow": "Nieve" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloudflare/translations/es-419.json b/homeassistant/components/cloudflare/translations/es-419.json index 03b49267d12649..561c9efa24be4e 100644 --- a/homeassistant/components/cloudflare/translations/es-419.json +++ b/homeassistant/components/cloudflare/translations/es-419.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "invalid_zone": "Zona inv\u00e1lida" + }, "step": { "reauth_confirm": { "data": { diff --git a/homeassistant/components/co2signal/translations/es-419.json b/homeassistant/components/co2signal/translations/es-419.json index 023c867ee9b826..691c4e2a3501d0 100644 --- a/homeassistant/components/co2signal/translations/es-419.json +++ b/homeassistant/components/co2signal/translations/es-419.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "api_ratelimit": "Se excedi\u00f3 el l\u00edmite de tasa de API" + }, + "error": { + "api_ratelimit": "Se excedi\u00f3 el l\u00edmite de tasa de API" + }, "step": { "country": { "data": { @@ -9,7 +15,8 @@ "user": { "data": { "location": "Obtener datos para" - } + }, + "description": "Visite https://co2signal.com/ para solicitar un token." } } } diff --git a/homeassistant/components/coinbase/translations/es-419.json b/homeassistant/components/coinbase/translations/es-419.json index 12acea8a7df5f6..c5bc63ee29ac0e 100644 --- a/homeassistant/components/coinbase/translations/es-419.json +++ b/homeassistant/components/coinbase/translations/es-419.json @@ -1,14 +1,27 @@ { "config": { + "error": { + "invalid_auth_key": "Credenciales de API rechazadas por Coinbase debido a una clave de API no v\u00e1lida.", + "invalid_auth_secret": "Credenciales de API rechazadas por Coinbase debido a un secreto de API no v\u00e1lido." + }, "step": { "user": { "data": { "api_token": "Secreto de la API", + "currencies": "Monedas del saldo de la cuenta", "exchange_rates": "Tipos de cambio" }, "description": "Ingrese los detalles de su clave API proporcionada por Coinbase.", "title": "Detalles clave de la API de Coinbase" } } + }, + "options": { + "error": { + "currency_unavailable": "Su API de Coinbase no proporciona uno o m\u00e1s de los saldos de divisas solicitados.", + "currency_unavaliable": "Su API de Coinbase no proporciona uno o m\u00e1s de los saldos de divisas solicitados.", + "exchange_rate_unavailable": "Coinbase no proporciona uno o m\u00e1s de los tipos de cambio solicitados.", + "exchange_rate_unavaliable": "Coinbase no proporciona uno o m\u00e1s de los tipos de cambio solicitados." + } } } \ No newline at end of file diff --git a/homeassistant/components/cpuspeed/translations/es-419.json b/homeassistant/components/cpuspeed/translations/es-419.json new file mode 100644 index 00000000000000..57c70ac4094cd3 --- /dev/null +++ b/homeassistant/components/cpuspeed/translations/es-419.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "not_compatible": "No se puede obtener informaci\u00f3n de la CPU, esta integraci\u00f3n no es compatible con su sistema" + }, + "step": { + "user": { + "title": "Velocidad de la CPU" + } + } + }, + "title": "Velocidad de la CPU" +} \ No newline at end of file diff --git a/homeassistant/components/crownstone/translations/es-419.json b/homeassistant/components/crownstone/translations/es-419.json new file mode 100644 index 00000000000000..1c90430254174a --- /dev/null +++ b/homeassistant/components/crownstone/translations/es-419.json @@ -0,0 +1,48 @@ +{ + "config": { + "abort": { + "usb_setup_complete": "Configuraci\u00f3n USB de Crownstone completa.", + "usb_setup_unsuccessful": "La configuraci\u00f3n del USB de Crownstone no tuvo \u00e9xito." + }, + "error": { + "account_not_verified": "Cuenta no verificada. Active su cuenta a trav\u00e9s del correo electr\u00f3nico de activaci\u00f3n de Crownstone." + }, + "step": { + "usb_config": { + "description": "Seleccione el puerto serie del dongle USB de Crownstone o seleccione 'No usar USB' si no desea configurar un dongle USB. \n\n Busque un dispositivo con VID 10C4 y PID EA60.", + "title": "Configuraci\u00f3n del dongle USB Crownstone" + }, + "usb_manual_config": { + "description": "Ingrese manualmente la ruta de un dongle USB de Crownstone.", + "title": "Ruta manual del dongle USB Crownstone" + }, + "usb_sphere_config": { + "data": { + "usb_sphere": "Esfera de Crownstone" + }, + "description": "Seleccione una esfera Crownstone donde se encuentra el USB.", + "title": "Esfera USB Crownstone" + }, + "user": { + "title": "Cuenta de Crownstone" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "usb_sphere_option": "Crownstone Esfera donde se encuentra el USB", + "use_usb_option": "Utilice un dongle USB de Crownstone para la transmisi\u00f3n local de datos" + } + }, + "usb_config": { + "description": "Seleccione el puerto serie del dongle USB Crownstone. \n\n Busque un dispositivo con VID 10C4 y PID EA60.", + "title": "Configuraci\u00f3n del dongle USB Crownstone" + }, + "usb_config_option": { + "description": "Seleccione el puerto serie del dongle USB Crownstone. \n\n Busque un dispositivo con VID 10C4 y PID EA60." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/denonavr/translations/es-419.json b/homeassistant/components/denonavr/translations/es-419.json index c506f9f6aac7d8..e22b8feebd131f 100644 --- a/homeassistant/components/denonavr/translations/es-419.json +++ b/homeassistant/components/denonavr/translations/es-419.json @@ -1,8 +1,37 @@ { + "config": { + "abort": { + "cannot_connect": "No se pudo conectar, intente nuevamente, desconectar la alimentaci\u00f3n el\u00e9ctrica y los cables de ethernet y volver a conectarlos puede ayudar", + "not_denonavr_manufacturer": "No es un receptor de red Denon AVR, el fabricante descubierto no coincide", + "not_denonavr_missing": "No es un receptor de red Denon AVR, la informaci\u00f3n de descubrimiento no est\u00e1 completa" + }, + "error": { + "discovery_error": "Error al descubrir un receptor de red Denon AVR" + }, + "flow_title": "{name}", + "step": { + "confirm": { + "description": "Por favor, confirme la adici\u00f3n del receptor", + "title": "Receptores de red Denon AVR" + }, + "select": { + "data": { + "select_host": "Direcci\u00f3n IP del receptor" + }, + "description": "Vuelva a ejecutar la configuraci\u00f3n si desea conectar receptores adicionales", + "title": "Seleccione el receptor que desea conectar" + }, + "user": { + "description": "Con\u00e9ctese a su receptor, si la direcci\u00f3n IP no est\u00e1 configurada, se usa el descubrimiento autom\u00e1tico", + "title": "Receptores de red Denon AVR" + } + } + }, "options": { "step": { "init": { "data": { + "show_all_sources": "Mostrar todas las fuentes", "update_audyssey": "Actualizar la configuraci\u00f3n de Audyssey", "zone2": "Configurar Zona 2", "zone3": "Configurar Zona 3" diff --git a/homeassistant/components/dlna_dmr/translations/es-419.json b/homeassistant/components/dlna_dmr/translations/es-419.json new file mode 100644 index 00000000000000..3dff768512273e --- /dev/null +++ b/homeassistant/components/dlna_dmr/translations/es-419.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "alternative_integration": "El dispositivo es mejor compatible con otra integraci\u00f3n", + "could_not_connect": "No se pudo conectar al dispositivo DLNA", + "discovery_error": "Error al descubrir un dispositivo DLNA coincidente", + "incomplete_config": "A la configuraci\u00f3n le falta una variable requerida", + "non_unique_id": "Varios dispositivos encontrados con la misma ID \u00fanica", + "not_dmr": "El dispositivo no es un renderizador de medios digitales compatible" + }, + "error": { + "could_not_connect": "No se pudo conectar al dispositivo DLNA", + "not_dmr": "El dispositivo no es un renderizador de medios digitales compatible" + }, + "flow_title": "{name}", + "step": { + "import_turn_on": { + "description": "Encienda el dispositivo y haga clic en Enviar para continuar con la migraci\u00f3n." + }, + "manual": { + "description": "URL a un archivo XML de descripci\u00f3n de dispositivo" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/eafm/translations/el.json b/homeassistant/components/eafm/translations/el.json index 9bae4cf98277d5..9745cd934edefd 100644 --- a/homeassistant/components/eafm/translations/el.json +++ b/homeassistant/components/eafm/translations/el.json @@ -2,6 +2,15 @@ "config": { "abort": { "no_stations": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03af \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7\u03c2 \u03c0\u03bb\u03b7\u03bc\u03bc\u03c5\u03c1\u03ce\u03bd." + }, + "step": { + "user": { + "data": { + "station": "\u03a3\u03c4\u03b1\u03b8\u03bc\u03cc\u03c2" + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf \u03c3\u03c4\u03b1\u03b8\u03bc\u03cc \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03b5\u03c4\u03b5", + "title": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7\u03c2 \u03c0\u03bb\u03b7\u03bc\u03bc\u03c5\u03c1\u03ce\u03bd" + } } } } \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/el.json b/homeassistant/components/homekit_controller/translations/el.json index 694cb41cf4494d..5c687ceb5b66a5 100644 --- a/homeassistant/components/homekit_controller/translations/el.json +++ b/homeassistant/components/homekit_controller/translations/el.json @@ -14,10 +14,12 @@ "flow_title": "{name}", "step": { "busy_error": { - "description": "\u039c\u03b1\u03c4\u03b1\u03b9\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03b6\u03b5\u03cd\u03be\u03b7 \u03c3\u03b5 \u03cc\u03bb\u03bf\u03c5\u03c2 \u03c4\u03bf\u03c5\u03c2 \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ad\u03c2 \u03ae \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ba\u03b1\u03b9, \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7." + "description": "\u039c\u03b1\u03c4\u03b1\u03b9\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03b6\u03b5\u03cd\u03be\u03b7 \u03c3\u03b5 \u03cc\u03bb\u03bf\u03c5\u03c2 \u03c4\u03bf\u03c5\u03c2 \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ad\u03c2 \u03ae \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ba\u03b1\u03b9, \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7.", + "title": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03af\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03b7 \u03bc\u03b5 \u03ac\u03bb\u03bb\u03bf \u03c7\u03b5\u03b9\u03c1\u03b9\u03c3\u03c4\u03ae\u03c1\u03b9\u03bf" }, "max_tries_error": { - "description": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03bb\u03ac\u03b2\u03b5\u03b9 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03b1\u03c0\u03cc 100 \u03b1\u03c0\u03bf\u03c4\u03c5\u03c7\u03b7\u03bc\u03ad\u03bd\u03b5\u03c2 \u03c0\u03c1\u03bf\u03c3\u03c0\u03ac\u03b8\u03b5\u03b9\u03b5\u03c2 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2. \u0394\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ba\u03b1\u03b9, \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7." + "description": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03bb\u03ac\u03b2\u03b5\u03b9 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03b1\u03c0\u03cc 100 \u03b1\u03c0\u03bf\u03c4\u03c5\u03c7\u03b7\u03bc\u03ad\u03bd\u03b5\u03c2 \u03c0\u03c1\u03bf\u03c3\u03c0\u03ac\u03b8\u03b5\u03b9\u03b5\u03c2 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2. \u0394\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ba\u03b1\u03b9, \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7.", + "title": "\u03a5\u03c0\u03ad\u03c1\u03b2\u03b1\u03c3\u03b7 \u03c4\u03c9\u03bd \u03bc\u03ad\u03b3\u03b9\u03c3\u03c4\u03c9\u03bd \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03b5\u03b9\u03ce\u03bd \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "pair": { "data": { @@ -27,6 +29,10 @@ "description": "\u03a4\u03bf HomeKit Controller \u03b5\u03c0\u03b9\u03ba\u03bf\u03b9\u03bd\u03c9\u03bd\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf {name} \u03bc\u03ad\u03c3\u03c9 \u03c4\u03bf\u03c5 \u03c4\u03bf\u03c0\u03b9\u03ba\u03bf\u03cd \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ce\u03bd\u03c4\u03b1\u03c2 \u03bc\u03b9\u03b1 \u03b1\u03c3\u03c6\u03b1\u03bb\u03ae \u03ba\u03c1\u03c5\u03c0\u03c4\u03bf\u03b3\u03c1\u03b1\u03c6\u03b7\u03bc\u03ad\u03bd\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c7\u03c9\u03c1\u03af\u03c2 \u03be\u03b5\u03c7\u03c9\u03c1\u03b9\u03c3\u03c4\u03cc \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ae HomeKit \u03ae iCloud. \u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03b1\u03bd\u03c4\u03b9\u03c3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7\u03c2 HomeKit (\u03bc\u03b5 \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae XXX-XX-XXX) \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03b1\u03be\u03b5\u03c3\u03bf\u03c5\u03ac\u03c1. \u0391\u03c5\u03c4\u03cc\u03c2 \u03bf \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c5\u03bd\u03ae\u03b8\u03c9\u03c2 \u03c3\u03c4\u03b7\u03bd \u03af\u03b4\u03b9\u03b1 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ae \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03b1\u03c3\u03af\u03b1.", "title": "\u03a3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7 \u03bc\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bc\u03ad\u03c3\u03c9 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03c9\u03c4\u03bf\u03ba\u03cc\u03bb\u03bb\u03bf\u03c5 \u03b1\u03be\u03b5\u03c3\u03bf\u03c5\u03ac\u03c1 HomeKit" }, + "protocol_error": { + "description": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03bd\u03b4\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03bd\u03b1 \u03bc\u03b7\u03bd \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03c3\u03b5 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b6\u03b5\u03cd\u03be\u03b7\u03c2 \u03ba\u03b1\u03b9 \u03bd\u03b1 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03c4\u03bf \u03c0\u03ac\u03c4\u03b7\u03bc\u03b1 \u03b5\u03bd\u03cc\u03c2 \u03c6\u03c5\u03c3\u03b9\u03ba\u03bf\u03cd \u03ae \u03b5\u03b9\u03ba\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03ba\u03bf\u03c5\u03bc\u03c0\u03b9\u03bf\u03cd. \u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03c3\u03b5 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7\u03c2 \u03ae \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03ba\u03b1\u03b9, \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03c4\u03b5 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b1\u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7.", + "title": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03b5\u03c0\u03b9\u03ba\u03bf\u03b9\u03bd\u03c9\u03bd\u03af\u03b1\u03c2 \u03bc\u03b5 \u03c4\u03bf \u03b5\u03be\u03ac\u03c1\u03c4\u03b7\u03bc\u03b1" + }, "user": { "data": { "device": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" diff --git a/homeassistant/components/insteon/translations/el.json b/homeassistant/components/insteon/translations/el.json index b0cadc8c8fd0bb..54900940d9b686 100644 --- a/homeassistant/components/insteon/translations/el.json +++ b/homeassistant/components/insteon/translations/el.json @@ -17,6 +17,10 @@ "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd \u03c4\u03bf\u03c5 Insteon Hub Version 2.", "title": "Insteon Hub Version 2" }, + "plm": { + "description": "\u0394\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf \u03bc\u03cc\u03bd\u03c4\u03b5\u03bc Insteon PowerLink (PLM).", + "title": "Insteon PLM" + }, "user": { "data": { "modem_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03bc\u03cc\u03bd\u03c4\u03b5\u03bc." diff --git a/homeassistant/components/konnected/translations/el.json b/homeassistant/components/konnected/translations/el.json index a0e57d47083a91..5ac9183347125c 100644 --- a/homeassistant/components/konnected/translations/el.json +++ b/homeassistant/components/konnected/translations/el.json @@ -63,6 +63,7 @@ "data": { "api_host": "\u03a0\u03b1\u03c1\u03ac\u03ba\u03b1\u03bc\u03c8\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae API (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", "blink": "\u0397 \u03bb\u03c5\u03c7\u03bd\u03af\u03b1 LED \u03c4\u03bf\u03c5 \u03c0\u03af\u03bd\u03b1\u03ba\u03b1 \u03b1\u03bd\u03b1\u03b2\u03bf\u03c3\u03b2\u03ae\u03bd\u03b5\u03b9 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b1\u03c0\u03bf\u03c3\u03c4\u03bf\u03bb\u03ae \u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2", + "discovery": "\u0391\u03bd\u03c4\u03b1\u03c0\u03cc\u03ba\u03c1\u03b9\u03c3\u03b7 \u03c3\u03b5 \u03b1\u03b9\u03c4\u03ae\u03bc\u03b1\u03c4\u03b1 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03cc \u03c3\u03b1\u03c2", "override_api_host": "\u03a0\u03b1\u03c1\u03ac\u03ba\u03b1\u03bc\u03c8\u03b7 \u03c4\u03b7\u03c2 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03c4\u03bf\u03c5 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c0\u03af\u03bd\u03b1\u03ba\u03b1 \u03c4\u03bf\u03c5 Home Assistant API" }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c0\u03b9\u03b8\u03c5\u03bc\u03b7\u03c4\u03ae \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03c6\u03bf\u03c1\u03ac \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03c0\u03af\u03bd\u03b1\u03ba\u03b1 \u03c3\u03b1\u03c2", diff --git a/homeassistant/components/netatmo/translations/el.json b/homeassistant/components/netatmo/translations/el.json index 364e390ddc26ed..16e7f3b5d17931 100644 --- a/homeassistant/components/netatmo/translations/el.json +++ b/homeassistant/components/netatmo/translations/el.json @@ -19,5 +19,20 @@ "turned_on": "{entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", "vehicle": "{entity_name} \u03b5\u03bd\u03c4\u03cc\u03c0\u03b9\u03c3\u03b5 \u03ad\u03bd\u03b1 \u03cc\u03c7\u03b7\u03bc\u03b1" } + }, + "options": { + "step": { + "public_weather": { + "description": "\u0394\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03b4\u03b7\u03bc\u03cc\u03c3\u03b9\u03bf \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd \u03b3\u03b9\u03b1 \u03bc\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae.", + "title": "\u0394\u03b7\u03bc\u03cc\u03c3\u03b9\u03bf\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd Netatmo" + }, + "public_weather_areas": { + "data": { + "new_area": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae\u03c2", + "weather_areas": "\u039a\u03b1\u03b9\u03c1\u03b9\u03ba\u03ad\u03c2 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ad\u03c2" + }, + "title": "\u0394\u03b7\u03bc\u03cc\u03c3\u03b9\u03bf\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd Netatmo" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/rtsp_to_webrtc/translations/el.json b/homeassistant/components/rtsp_to_webrtc/translations/el.json index bc8212e318bb77..0e4c6baa287612 100644 --- a/homeassistant/components/rtsp_to_webrtc/translations/el.json +++ b/homeassistant/components/rtsp_to_webrtc/translations/el.json @@ -2,7 +2,8 @@ "config": { "abort": { "server_failure": "\u039f \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 RTSPtoWebRTC \u03b5\u03c0\u03ad\u03c3\u03c4\u03c1\u03b5\u03c8\u03b5 \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b1 \u03b1\u03c1\u03c7\u03b5\u03af\u03b1 \u03ba\u03b1\u03c4\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2 \u03b3\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2.", - "server_unreachable": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03b5\u03c0\u03b9\u03ba\u03bf\u03b9\u03bd\u03c9\u03bd\u03af\u03b1\u03c2 \u03bc\u03b5 \u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae RTSPtoWebRTC. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b1 \u03b1\u03c1\u03c7\u03b5\u03af\u03b1 \u03ba\u03b1\u03c4\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2 \u03b3\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2." + "server_unreachable": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03b5\u03c0\u03b9\u03ba\u03bf\u03b9\u03bd\u03c9\u03bd\u03af\u03b1\u03c2 \u03bc\u03b5 \u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae RTSPtoWebRTC. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b1 \u03b1\u03c1\u03c7\u03b5\u03af\u03b1 \u03ba\u03b1\u03c4\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2 \u03b3\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2.", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, "error": { "invalid_url": "\u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03bc\u03b9\u03b1 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae RTSPtoWebRTC, \u03c0.\u03c7. https://example.com.", diff --git a/homeassistant/components/vizio/translations/el.json b/homeassistant/components/vizio/translations/el.json index 99414ed3b6f138..3cfce894ca25c6 100644 --- a/homeassistant/components/vizio/translations/el.json +++ b/homeassistant/components/vizio/translations/el.json @@ -4,20 +4,23 @@ "updated_entry": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af, \u03b1\u03bb\u03bb\u03ac \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1, \u03bf\u03b9 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ad\u03c2 \u03ae/\u03ba\u03b1\u03b9 \u03bf\u03b9 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03c0\u03bf\u03c5 \u03bf\u03c1\u03af\u03b6\u03bf\u03bd\u03c4\u03b1\u03b9 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b4\u03b5\u03bd \u03c4\u03b1\u03b9\u03c1\u03b9\u03ac\u03b6\u03bf\u03c5\u03bd \u03bc\u03b5 \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03b7\u03b3\u03bf\u03c5\u03bc\u03ad\u03bd\u03c9\u03c2 \u03b5\u03b9\u03c3\u03b1\u03c7\u03b8\u03b5\u03af\u03c3\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7, \u03bf\u03c0\u03cc\u03c4\u03b5 \u03b7 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03c9\u03b8\u03b5\u03af \u03b1\u03bd\u03b1\u03bb\u03cc\u03b3\u03c9\u03c2." }, "error": { - "complete_pairing_failed": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03ae\u03c1\u03c9\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03b1\u03bd\u03c4\u03b9\u03c3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7\u03c2. \u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03bf PIN \u03c0\u03bf\u03c5 \u03b4\u03ce\u03c3\u03b1\u03c4\u03b5 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c9\u03c3\u03c4\u03cc\u03c2 \u03ba\u03b1\u03b9 \u03cc\u03c4\u03b9 \u03b7 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7 \u03b5\u03be\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03b5\u03af \u03bd\u03b1 \u03c4\u03c1\u03bf\u03c6\u03bf\u03b4\u03bf\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03c1\u03b5\u03cd\u03bc\u03b1 \u03ba\u03b1\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03b7 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf \u03c0\u03c1\u03b9\u03bd \u03c4\u03b7\u03bd \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae." + "complete_pairing_failed": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03ae\u03c1\u03c9\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03b1\u03bd\u03c4\u03b9\u03c3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7\u03c2. \u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03bf PIN \u03c0\u03bf\u03c5 \u03b4\u03ce\u03c3\u03b1\u03c4\u03b5 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c9\u03c3\u03c4\u03cc\u03c2 \u03ba\u03b1\u03b9 \u03cc\u03c4\u03b9 \u03b7 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7 \u03b5\u03be\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03b5\u03af \u03bd\u03b1 \u03c4\u03c1\u03bf\u03c6\u03bf\u03b4\u03bf\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03c1\u03b5\u03cd\u03bc\u03b1 \u03ba\u03b1\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03b7 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf \u03c0\u03c1\u03b9\u03bd \u03c4\u03b7\u03bd \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae.", + "existing_config_entry_found": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af \u03bc\u03b9\u03b1 \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03c3\u03b1 VIZIO SmartCast Device \u03bc\u03b5 \u03c4\u03bf\u03bd \u03af\u03b4\u03b9\u03bf \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc. \u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c8\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03c3\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae\u03bd." }, "step": { "pair_tv": { "description": "\u0397 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae \u03c3\u03b1\u03c2 \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03b9 \u03ad\u03bd\u03b1\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc. \u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c3\u03c4\u03b7 \u03c6\u03cc\u03c1\u03bc\u03b1 \u03ba\u03b1\u03b9, \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03bf \u03b2\u03ae\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03ce\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7." }, "pairing_complete_import": { + "description": "\u03a4\u03bf VIZIO SmartCast Device \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03bb\u03ad\u03bf\u03bd \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03c3\u03c4\u03bf Home Assistant. \n\n \u03a4\u03bf \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \"**{access_token}**\".", "title": "\u039f\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7" }, "user": { "data": { "device_class": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" }, - "description": "\u0388\u03bd\u03b1 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03bc\u03cc\u03bd\u03bf \u03b3\u03b9\u03b1 \u03c4\u03b9\u03c2 \u03c4\u03b7\u03bb\u03b5\u03bf\u03c1\u03ac\u03c3\u03b5\u03b9\u03c2. \u0395\u03ac\u03bd \u03c1\u03c5\u03b8\u03bc\u03af\u03b6\u03b5\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03b1\u03ba\u03cc\u03bc\u03b7 \u03ad\u03bd\u03b1 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2, \u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03b5\u03c1\u03ac\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c0\u03cc \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b1\u03b4\u03b9\u03ba\u03b1\u03c3\u03af\u03b1 \u03b1\u03bd\u03c4\u03b9\u03c3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7\u03c2." + "description": "\u0388\u03bd\u03b1 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03bc\u03cc\u03bd\u03bf \u03b3\u03b9\u03b1 \u03c4\u03b9\u03c2 \u03c4\u03b7\u03bb\u03b5\u03bf\u03c1\u03ac\u03c3\u03b5\u03b9\u03c2. \u0395\u03ac\u03bd \u03c1\u03c5\u03b8\u03bc\u03af\u03b6\u03b5\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03b1\u03ba\u03cc\u03bc\u03b7 \u03ad\u03bd\u03b1 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2, \u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03b5\u03c1\u03ac\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c0\u03cc \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b1\u03b4\u03b9\u03ba\u03b1\u03c3\u03af\u03b1 \u03b1\u03bd\u03c4\u03b9\u03c3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7\u03c2.", + "title": "VIZIO SmartCast Device" } } }, From 340146e5fbb370daa9fd018a56c95a5383893250 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 3 Feb 2022 01:25:51 +0100 Subject: [PATCH 0200/1098] Add update listener type hints to coinbase (#65414) Co-authored-by: epenet --- homeassistant/components/coinbase/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/coinbase/__init__.py b/homeassistant/components/coinbase/__init__.py index 238ff1db87d3cc..4ef26a11130216 100644 --- a/homeassistant/components/coinbase/__init__.py +++ b/homeassistant/components/coinbase/__init__.py @@ -99,7 +99,7 @@ def create_and_update_instance(entry: ConfigEntry) -> CoinbaseData: return instance -async def update_listener(hass, config_entry): +async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None: """Handle options update.""" await hass.config_entries.async_reload(config_entry.entry_id) @@ -113,11 +113,11 @@ async def update_listener(hass, config_entry): for entity in entities: currency = entity.unique_id.split("-")[-1] if "xe" in entity.unique_id and currency not in config_entry.options.get( - CONF_EXCHANGE_RATES + CONF_EXCHANGE_RATES, [] ): registry.async_remove(entity.entity_id) elif "wallet" in entity.unique_id and currency not in config_entry.options.get( - CONF_CURRENCIES + CONF_CURRENCIES, [] ): registry.async_remove(entity.entity_id) From 6e36bdb907f4cc85e14451e9010455c95d75fadd Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 3 Feb 2022 14:05:12 +1300 Subject: [PATCH 0201/1098] Expose ESPHome project information in device information (#65466) --- homeassistant/components/esphome/__init__.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index 7e799a83ee2f7a..ca6eca9ea9f14d 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -343,18 +343,30 @@ def _async_setup_device_registry( sw_version = device_info.esphome_version if device_info.compilation_time: sw_version += f" ({device_info.compilation_time})" + configuration_url = None if device_info.webserver_port > 0: configuration_url = f"http://{entry.data['host']}:{device_info.webserver_port}" + + manufacturer = "espressif" + model = device_info.model + hw_version = None + if device_info.project_name: + project_name = device_info.project_name.split(".") + manufacturer = project_name[0] + model = project_name[1] + hw_version = device_info.project_version + device_registry = dr.async_get(hass) device_entry = device_registry.async_get_or_create( config_entry_id=entry.entry_id, configuration_url=configuration_url, connections={(dr.CONNECTION_NETWORK_MAC, device_info.mac_address)}, name=device_info.name, - manufacturer="espressif", - model=device_info.model, + manufacturer=manufacturer, + model=model, sw_version=sw_version, + hw_version=hw_version, ) return device_entry.id From 75e5079df33319a48ea2a310b67f0070051eadb3 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 2 Feb 2022 18:05:40 -0700 Subject: [PATCH 0202/1098] Catch correct error during OpenUV startup (#65459) --- homeassistant/components/openuv/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/openuv/__init__.py b/homeassistant/components/openuv/__init__.py index 2f186af2ffe1e4..774cd05fd9f101 100644 --- a/homeassistant/components/openuv/__init__.py +++ b/homeassistant/components/openuv/__init__.py @@ -70,7 +70,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: try: await openuv.async_update() - except OpenUvError as err: + except HomeAssistantError as err: LOGGER.error("Config entry failed: %s", err) raise ConfigEntryNotReady from err From 7909cff957168101d3d7bddc9097a3bee9148283 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 2 Feb 2022 18:06:24 -0700 Subject: [PATCH 0203/1098] Fix `unknown alarm websocket event` error for restored SimpliSafe connections (#65457) --- .../components/simplisafe/__init__.py | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index a8cdb8537494a4..a133ec6c2dcdf0 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -820,17 +820,6 @@ def _handle_websocket_update(self, event: WebsocketEvent) -> None: ): return - if event.event_type in (EVENT_CONNECTION_LOST, EVENT_POWER_OUTAGE): - self._online = False - elif event.event_type in (EVENT_CONNECTION_RESTORED, EVENT_POWER_RESTORED): - self._online = True - - # It's uncertain whether SimpliSafe events will still propagate down the - # websocket when the base station is offline. Just in case, we guard against - # further action until connection is restored: - if not self._online: - return - sensor_type: str | None if event.sensor_type: sensor_type = event.sensor_type.name @@ -846,6 +835,19 @@ def _handle_websocket_update(self, event: WebsocketEvent) -> None: } ) + # It's unknown whether these events reach the base station (since the connection + # is lost); we include this for completeness and coverage: + if event.event_type in (EVENT_CONNECTION_LOST, EVENT_POWER_OUTAGE): + self._online = False + return + + # If the base station comes back online, set entities to available, but don't + # instruct the entities to update their state (since there won't be anything new + # until the next websocket event or REST API update: + if event.event_type in (EVENT_CONNECTION_RESTORED, EVENT_POWER_RESTORED): + self._online = True + return + self.async_update_from_websocket_event(event) self.async_write_ha_state() From 32be5576dccd4adc17145b8ba31ddbbb6ad269c3 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Thu, 3 Feb 2022 02:07:12 +0100 Subject: [PATCH 0204/1098] Get wind speed unit from AccuWeather data (#65425) --- homeassistant/components/accuweather/weather.py | 3 +++ tests/components/accuweather/test_weather.py | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/accuweather/weather.py b/homeassistant/components/accuweather/weather.py index c97cc44aea3eda..00726f6db3815a 100644 --- a/homeassistant/components/accuweather/weather.py +++ b/homeassistant/components/accuweather/weather.py @@ -62,6 +62,9 @@ def __init__( """Initialize.""" super().__init__(coordinator) self._unit_system = API_METRIC if coordinator.is_metric else API_IMPERIAL + self._attr_wind_speed_unit = self.coordinator.data["Wind"]["Speed"][ + self._unit_system + ]["Unit"] self._attr_name = name self._attr_unique_id = coordinator.location_key self._attr_temperature_unit = ( diff --git a/tests/components/accuweather/test_weather.py b/tests/components/accuweather/test_weather.py index 6c1bc76e9b1f5d..02ace5d3f1d1ab 100644 --- a/tests/components/accuweather/test_weather.py +++ b/tests/components/accuweather/test_weather.py @@ -46,7 +46,7 @@ async def test_weather_without_forecast(hass): assert state.attributes.get(ATTR_WEATHER_TEMPERATURE) == 22.6 assert state.attributes.get(ATTR_WEATHER_VISIBILITY) == 16.1 assert state.attributes.get(ATTR_WEATHER_WIND_BEARING) == 180 - assert state.attributes.get(ATTR_WEATHER_WIND_SPEED) == 14.5 + assert state.attributes.get(ATTR_WEATHER_WIND_SPEED) == 4.03 assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION entry = registry.async_get("weather.home") @@ -68,7 +68,7 @@ async def test_weather_with_forecast(hass): assert state.attributes.get(ATTR_WEATHER_TEMPERATURE) == 22.6 assert state.attributes.get(ATTR_WEATHER_VISIBILITY) == 16.1 assert state.attributes.get(ATTR_WEATHER_WIND_BEARING) == 180 - assert state.attributes.get(ATTR_WEATHER_WIND_SPEED) == 14.5 + assert state.attributes.get(ATTR_WEATHER_WIND_SPEED) == 4.03 assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION forecast = state.attributes.get(ATTR_FORECAST)[0] assert forecast.get(ATTR_FORECAST_CONDITION) == "lightning-rainy" @@ -78,7 +78,7 @@ async def test_weather_with_forecast(hass): assert forecast.get(ATTR_FORECAST_TEMP_LOW) == 15.4 assert forecast.get(ATTR_FORECAST_TIME) == "2020-07-26T05:00:00+00:00" assert forecast.get(ATTR_FORECAST_WIND_BEARING) == 166 - assert forecast.get(ATTR_FORECAST_WIND_SPEED) == 13.0 + assert forecast.get(ATTR_FORECAST_WIND_SPEED) == 3.61 entry = registry.async_get("weather.home") assert entry From 4e7cf19b5fd70fc1429a7178cea4a33c8bed8b62 Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Wed, 2 Feb 2022 17:08:19 -0800 Subject: [PATCH 0205/1098] Bump androidtv to 0.0.62 (#65440) --- homeassistant/components/androidtv/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/androidtv/manifest.json b/homeassistant/components/androidtv/manifest.json index c5c11b7d3a9502..37e0ae485c6f53 100644 --- a/homeassistant/components/androidtv/manifest.json +++ b/homeassistant/components/androidtv/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/androidtv", "requirements": [ "adb-shell[async]==0.4.0", - "androidtv[async]==0.0.61", + "androidtv[async]==0.0.62", "pure-python-adb[async]==0.3.0.dev0" ], "codeowners": ["@JeffLIrion", "@ollo69"], diff --git a/requirements_all.txt b/requirements_all.txt index 545a758effaf33..f8d4c19220b8d3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -311,7 +311,7 @@ ambiclimate==0.2.1 amcrest==1.9.3 # homeassistant.components.androidtv -androidtv[async]==0.0.61 +androidtv[async]==0.0.62 # homeassistant.components.anel_pwrctrl anel_pwrctrl-homeassistant==0.0.1.dev2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e542e233324de2..74c0be71d6914e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -237,7 +237,7 @@ amberelectric==1.0.3 ambiclimate==0.2.1 # homeassistant.components.androidtv -androidtv[async]==0.0.61 +androidtv[async]==0.0.62 # homeassistant.components.apns apns2==0.3.0 From f3a89de71f6fa693016a41397b051bd48c86f841 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 3 Feb 2022 02:12:22 +0100 Subject: [PATCH 0206/1098] Fix race when handling updated MQTT discovery data (#65415) --- .../components/mqtt/alarm_control_panel.py | 8 ++- .../components/mqtt/binary_sensor.py | 8 ++- homeassistant/components/mqtt/button.py | 3 + homeassistant/components/mqtt/camera.py | 8 ++- homeassistant/components/mqtt/climate.py | 13 ++--- homeassistant/components/mqtt/cover.py | 8 ++- .../mqtt/device_tracker/schema_discovery.py | 8 ++- homeassistant/components/mqtt/fan.py | 8 ++- homeassistant/components/mqtt/humidifier.py | 8 ++- .../components/mqtt/light/schema_basic.py | 56 +++++++++--------- .../components/mqtt/light/schema_json.py | 10 +++- .../components/mqtt/light/schema_template.py | 11 ++-- homeassistant/components/mqtt/lock.py | 8 ++- homeassistant/components/mqtt/mixins.py | 58 +++++++++++++++---- homeassistant/components/mqtt/number.py | 8 ++- homeassistant/components/mqtt/select.py | 8 ++- homeassistant/components/mqtt/sensor.py | 8 ++- homeassistant/components/mqtt/siren.py | 8 ++- homeassistant/components/mqtt/subscription.py | 43 ++++++++++---- homeassistant/components/mqtt/switch.py | 8 ++- homeassistant/components/mqtt/tag.py | 5 +- .../components/mqtt/vacuum/schema_legacy.py | 8 ++- .../components/mqtt/vacuum/schema_state.py | 8 ++- homeassistant/components/tasmota/__init__.py | 7 ++- tests/components/mqtt/test_subscription.py | 28 +++++---- 25 files changed, 246 insertions(+), 108 deletions(-) diff --git a/homeassistant/components/mqtt/alarm_control_panel.py b/homeassistant/components/mqtt/alarm_control_panel.py index 63c4a79b96fcad..eaea908e3589e5 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -173,7 +173,7 @@ def _setup_from_config(self, config): self._config[CONF_COMMAND_TEMPLATE], entity=self ).async_render - async def _subscribe_topics(self): + def _prepare_subscribe_topics(self): """(Re)Subscribe to topics.""" @callback @@ -198,7 +198,7 @@ def message_received(msg): self._state = payload self.async_write_ha_state() - self._sub_state = await subscription.async_subscribe_topics( + self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, self._sub_state, { @@ -211,6 +211,10 @@ def message_received(msg): }, ) + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + await subscription.async_subscribe_topics(self.hass, self._sub_state) + @property def state(self): """Return the state of the device.""" diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index c500d52dd70c19..b84ddaad4041d3 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -164,7 +164,7 @@ def _setup_from_config(self, config): entity=self, ).async_render_with_possible_json_value - async def _subscribe_topics(self): + def _prepare_subscribe_topics(self): """(Re)Subscribe to topics.""" @callback @@ -241,7 +241,7 @@ def state_message_received(msg): self.async_write_ha_state() - self._sub_state = await subscription.async_subscribe_topics( + self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, self._sub_state, { @@ -254,6 +254,10 @@ def state_message_received(msg): }, ) + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + await subscription.async_subscribe_topics(self.hass, self._sub_state) + @callback def _value_is_expired(self, *_): """Triggered when value is expired.""" diff --git a/homeassistant/components/mqtt/button.py b/homeassistant/components/mqtt/button.py index 7143b65ed9eda7..993251606ed255 100644 --- a/homeassistant/components/mqtt/button.py +++ b/homeassistant/components/mqtt/button.py @@ -95,6 +95,9 @@ def _setup_from_config(self, config): config.get(CONF_COMMAND_TEMPLATE), entity=self ).async_render + def _prepare_subscribe_topics(self): + """(Re)Subscribe to topics.""" + async def _subscribe_topics(self): """(Re)Subscribe to topics.""" diff --git a/homeassistant/components/mqtt/camera.py b/homeassistant/components/mqtt/camera.py index 5c2b8258f010dc..1c060f7f32a332 100644 --- a/homeassistant/components/mqtt/camera.py +++ b/homeassistant/components/mqtt/camera.py @@ -90,7 +90,7 @@ def config_schema(): """Return the config schema.""" return DISCOVERY_SCHEMA - async def _subscribe_topics(self): + def _prepare_subscribe_topics(self): """(Re)Subscribe to topics.""" @callback @@ -99,7 +99,7 @@ def message_received(msg): """Handle new MQTT messages.""" self._last_image = msg.payload - self._sub_state = await subscription.async_subscribe_topics( + self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, self._sub_state, { @@ -112,6 +112,10 @@ def message_received(msg): }, ) + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + await subscription.async_subscribe_topics(self.hass, self._sub_state) + async def async_camera_image( self, width: int | None = None, height: int | None = None ) -> bytes | None: diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index 2e19a345bc39e4..02d4f267fe86e6 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -358,11 +358,6 @@ def config_schema(): """Return the config schema.""" return DISCOVERY_SCHEMA - async def async_added_to_hass(self): - """Handle being added to Home Assistant.""" - await super().async_added_to_hass() - await self._subscribe_topics() - def _setup_from_config(self, config): """(Re)Setup the entity.""" self._topic = {key: config.get(key) for key in TOPIC_KEYS} @@ -417,7 +412,7 @@ def _setup_from_config(self, config): self._command_templates = command_templates - async def _subscribe_topics(self): # noqa: C901 + def _prepare_subscribe_topics(self): # noqa: C901 """(Re)Subscribe to topics.""" topics = {} qos = self._config[CONF_QOS] @@ -615,10 +610,14 @@ def handle_hold_mode_received(msg): add_subscription(topics, CONF_HOLD_STATE_TOPIC, handle_hold_mode_received) - self._sub_state = await subscription.async_subscribe_topics( + self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, self._sub_state, topics ) + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + await subscription.async_subscribe_topics(self.hass, self._sub_state) + @property def temperature_unit(self): """Return the unit of measurement.""" diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index 95ea6182bf18d4..dfb48fb89e232f 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -335,7 +335,7 @@ def _setup_from_config(self, config): config_attributes=template_config_attributes, ).async_render_with_possible_json_value - async def _subscribe_topics(self): + def _prepare_subscribe_topics(self): """(Re)Subscribe to topics.""" topics = {} @@ -460,10 +460,14 @@ def position_message_received(msg): "encoding": self._config[CONF_ENCODING] or None, } - self._sub_state = await subscription.async_subscribe_topics( + self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, self._sub_state, topics ) + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + await subscription.async_subscribe_topics(self.hass, self._sub_state) + @property def assumed_state(self): """Return true if we do optimistic updates.""" diff --git a/homeassistant/components/mqtt/device_tracker/schema_discovery.py b/homeassistant/components/mqtt/device_tracker/schema_discovery.py index 3ee5f22be902e7..a7b597d06897c3 100644 --- a/homeassistant/components/mqtt/device_tracker/schema_discovery.py +++ b/homeassistant/components/mqtt/device_tracker/schema_discovery.py @@ -77,7 +77,7 @@ def _setup_from_config(self, config): self._config.get(CONF_VALUE_TEMPLATE), entity=self ).async_render_with_possible_json_value - async def _subscribe_topics(self): + def _prepare_subscribe_topics(self): """(Re)Subscribe to topics.""" @callback @@ -94,7 +94,7 @@ def message_received(msg): self.async_write_ha_state() - self._sub_state = await subscription.async_subscribe_topics( + self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, self._sub_state, { @@ -106,6 +106,10 @@ def message_received(msg): }, ) + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + await subscription.async_subscribe_topics(self.hass, self._sub_state) + @property def latitude(self): """Return latitude if provided in extra_state_attributes or None.""" diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index fb6d21c8538178..f6d60ae8cbe7a5 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -351,7 +351,7 @@ def _setup_from_config(self, config): entity=self, ).async_render_with_possible_json_value - async def _subscribe_topics(self): + def _prepare_subscribe_topics(self): """(Re)Subscribe to topics.""" topics = {} @@ -479,10 +479,14 @@ def oscillation_received(msg): } self._oscillation = False - self._sub_state = await subscription.async_subscribe_topics( + self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, self._sub_state, topics ) + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + await subscription.async_subscribe_topics(self.hass, self._sub_state) + @property def assumed_state(self): """Return true if we do optimistic updates.""" diff --git a/homeassistant/components/mqtt/humidifier.py b/homeassistant/components/mqtt/humidifier.py index df1b7667ef751e..b2c4ed4b916046 100644 --- a/homeassistant/components/mqtt/humidifier.py +++ b/homeassistant/components/mqtt/humidifier.py @@ -267,7 +267,7 @@ def _setup_from_config(self, config): entity=self, ).async_render_with_possible_json_value - async def _subscribe_topics(self): + def _prepare_subscribe_topics(self): """(Re)Subscribe to topics.""" topics = {} @@ -373,10 +373,14 @@ def mode_received(msg): } self._mode = None - self._sub_state = await subscription.async_subscribe_topics( + self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, self._sub_state, topics ) + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + await subscription.async_subscribe_topics(self.hass, self._sub_state) + @property def assumed_state(self): """Return true if we do optimistic updates.""" diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index 4f692c8063c762..f164abe52975ad 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -417,12 +417,10 @@ def _is_optimistic(self, attribute): """Return True if the attribute is optimistically updated.""" return getattr(self, f"_optimistic_{attribute}") - async def _subscribe_topics(self): # noqa: C901 + def _prepare_subscribe_topics(self): # noqa: C901 """(Re)Subscribe to topics.""" topics = {} - last_state = await self.async_get_last_state() - def add_topic(topic, msg_callback): """Add a topic.""" if self._topic[topic] is not None: @@ -433,14 +431,6 @@ def add_topic(topic, msg_callback): "encoding": self._config[CONF_ENCODING] or None, } - def restore_state(attribute, condition_attribute=None): - """Restore a state attribute.""" - if condition_attribute is None: - condition_attribute = attribute - optimistic = self._is_optimistic(condition_attribute) - if optimistic and last_state and last_state.attributes.get(attribute): - setattr(self, f"_{attribute}", last_state.attributes[attribute]) - @callback @log_messages(self.hass, self.entity_id) def state_received(msg): @@ -465,8 +455,6 @@ def state_received(msg): "qos": self._config[CONF_QOS], "encoding": self._config[CONF_ENCODING] or None, } - elif self._optimistic and last_state: - self._state = last_state.state == STATE_ON @callback @log_messages(self.hass, self.entity_id) @@ -485,7 +473,6 @@ def brightness_received(msg): self.async_write_ha_state() add_topic(CONF_BRIGHTNESS_STATE_TOPIC, brightness_received) - restore_state(ATTR_BRIGHTNESS) def _rgbx_received(msg, template, color_mode, convert_color): """Handle new MQTT messages for RGBW and RGBWW.""" @@ -520,8 +507,6 @@ def rgb_received(msg): self.async_write_ha_state() add_topic(CONF_RGB_STATE_TOPIC, rgb_received) - restore_state(ATTR_RGB_COLOR) - restore_state(ATTR_HS_COLOR, ATTR_RGB_COLOR) @callback @log_messages(self.hass, self.entity_id) @@ -539,7 +524,6 @@ def rgbw_received(msg): self.async_write_ha_state() add_topic(CONF_RGBW_STATE_TOPIC, rgbw_received) - restore_state(ATTR_RGBW_COLOR) @callback @log_messages(self.hass, self.entity_id) @@ -557,7 +541,6 @@ def rgbww_received(msg): self.async_write_ha_state() add_topic(CONF_RGBWW_STATE_TOPIC, rgbww_received) - restore_state(ATTR_RGBWW_COLOR) @callback @log_messages(self.hass, self.entity_id) @@ -574,7 +557,6 @@ def color_mode_received(msg): self.async_write_ha_state() add_topic(CONF_COLOR_MODE_STATE_TOPIC, color_mode_received) - restore_state(ATTR_COLOR_MODE) @callback @log_messages(self.hass, self.entity_id) @@ -593,7 +575,6 @@ def color_temp_received(msg): self.async_write_ha_state() add_topic(CONF_COLOR_TEMP_STATE_TOPIC, color_temp_received) - restore_state(ATTR_COLOR_TEMP) @callback @log_messages(self.hass, self.entity_id) @@ -610,7 +591,6 @@ def effect_received(msg): self.async_write_ha_state() add_topic(CONF_EFFECT_STATE_TOPIC, effect_received) - restore_state(ATTR_EFFECT) @callback @log_messages(self.hass, self.entity_id) @@ -630,7 +610,6 @@ def hs_received(msg): _LOGGER.debug("Failed to parse hs state update: '%s'", payload) add_topic(CONF_HS_STATE_TOPIC, hs_received) - restore_state(ATTR_HS_COLOR) @callback @log_messages(self.hass, self.entity_id) @@ -649,7 +628,6 @@ def white_value_received(msg): self.async_write_ha_state() add_topic(CONF_WHITE_VALUE_STATE_TOPIC, white_value_received) - restore_state(ATTR_WHITE_VALUE) @callback @log_messages(self.hass, self.entity_id) @@ -670,13 +648,39 @@ def xy_received(msg): self.async_write_ha_state() add_topic(CONF_XY_STATE_TOPIC, xy_received) - restore_state(ATTR_XY_COLOR) - restore_state(ATTR_HS_COLOR, ATTR_XY_COLOR) - self._sub_state = await subscription.async_subscribe_topics( + self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, self._sub_state, topics ) + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + await subscription.async_subscribe_topics(self.hass, self._sub_state) + last_state = await self.async_get_last_state() + + def restore_state(attribute, condition_attribute=None): + """Restore a state attribute.""" + if condition_attribute is None: + condition_attribute = attribute + optimistic = self._is_optimistic(condition_attribute) + if optimistic and last_state and last_state.attributes.get(attribute): + setattr(self, f"_{attribute}", last_state.attributes[attribute]) + + if self._topic[CONF_STATE_TOPIC] is None and self._optimistic and last_state: + self._state = last_state.state == STATE_ON + restore_state(ATTR_BRIGHTNESS) + restore_state(ATTR_RGB_COLOR) + restore_state(ATTR_HS_COLOR, ATTR_RGB_COLOR) + restore_state(ATTR_RGBW_COLOR) + restore_state(ATTR_RGBWW_COLOR) + restore_state(ATTR_COLOR_MODE) + restore_state(ATTR_COLOR_TEMP) + restore_state(ATTR_EFFECT) + restore_state(ATTR_HS_COLOR) + restore_state(ATTR_WHITE_VALUE) + restore_state(ATTR_XY_COLOR) + restore_state(ATTR_HS_COLOR, ATTR_XY_COLOR) + @property def brightness(self): """Return the brightness of this light between 0..255.""" diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index 3adaec38adbb94..2d9b8f6a388ef7 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -304,9 +304,8 @@ def _update_color(self, values): except (KeyError, ValueError): _LOGGER.warning("Invalid or incomplete color value received") - async def _subscribe_topics(self): + def _prepare_subscribe_topics(self): """(Re)Subscribe to topics.""" - last_state = await self.async_get_last_state() @callback @log_messages(self.hass, self.entity_id) @@ -370,7 +369,7 @@ def state_received(msg): self.async_write_ha_state() if self._topic[CONF_STATE_TOPIC] is not None: - self._sub_state = await subscription.async_subscribe_topics( + self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, self._sub_state, { @@ -383,6 +382,11 @@ def state_received(msg): }, ) + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + await subscription.async_subscribe_topics(self.hass, self._sub_state) + + last_state = await self.async_get_last_state() if self._optimistic and last_state: self._state = last_state.state == STATE_ON last_attributes = last_state.attributes diff --git a/homeassistant/components/mqtt/light/schema_template.py b/homeassistant/components/mqtt/light/schema_template.py index 54252ebc0b0920..736ff98f321884 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -156,14 +156,12 @@ def _setup_from_config(self, config): or self._templates[CONF_STATE_TEMPLATE] is None ) - async def _subscribe_topics(self): # noqa: C901 + def _prepare_subscribe_topics(self): """(Re)Subscribe to topics.""" for tpl in self._templates.values(): if tpl is not None: tpl = MqttValueTemplate(tpl, entity=self) - last_state = await self.async_get_last_state() - @callback @log_messages(self.hass, self.entity_id) def state_received(msg): @@ -246,7 +244,7 @@ def state_received(msg): self.async_write_ha_state() if self._topics[CONF_STATE_TOPIC] is not None: - self._sub_state = await subscription.async_subscribe_topics( + self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, self._sub_state, { @@ -259,6 +257,11 @@ def state_received(msg): }, ) + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + await subscription.async_subscribe_topics(self.hass, self._sub_state) + + last_state = await self.async_get_last_state() if self._optimistic and last_state: self._state = last_state.state == STATE_ON if last_state.attributes.get(ATTR_BRIGHTNESS): diff --git a/homeassistant/components/mqtt/lock.py b/homeassistant/components/mqtt/lock.py index 1c280405522f67..89917f4cc5c6ea 100644 --- a/homeassistant/components/mqtt/lock.py +++ b/homeassistant/components/mqtt/lock.py @@ -123,7 +123,7 @@ def _setup_from_config(self, config): entity=self, ).async_render_with_possible_json_value - async def _subscribe_topics(self): + def _prepare_subscribe_topics(self): """(Re)Subscribe to topics.""" @callback @@ -142,7 +142,7 @@ def message_received(msg): # Force into optimistic mode. self._optimistic = True else: - self._sub_state = await subscription.async_subscribe_topics( + self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, self._sub_state, { @@ -155,6 +155,10 @@ def message_received(msg): }, ) + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + await subscription.async_subscribe_topics(self.hass, self._sub_state) + @property def is_locked(self): """Return true if lock is locked.""" diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index f49af86360db75..1f4bbe9d949e72 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -66,7 +66,11 @@ set_discovery_hash, ) from .models import ReceiveMessage -from .subscription import async_subscribe_topics, async_unsubscribe_topics +from .subscription import ( + async_prepare_subscribe_topics, + async_subscribe_topics, + async_unsubscribe_topics, +) from .util import valid_subscribe_topic _LOGGER = logging.getLogger(__name__) @@ -249,14 +253,19 @@ def __init__(self, config: dict) -> None: async def async_added_to_hass(self) -> None: """Subscribe MQTT events.""" await super().async_added_to_hass() + self._attributes_prepare_subscribe_topics() await self._attributes_subscribe_topics() - async def attributes_discovery_update(self, config: dict): + def attributes_prepare_discovery_update(self, config: dict): """Handle updated discovery message.""" self._attributes_config = config + self._attributes_prepare_subscribe_topics() + + async def attributes_discovery_update(self, config: dict): + """Handle updated discovery message.""" await self._attributes_subscribe_topics() - async def _attributes_subscribe_topics(self): + def _attributes_prepare_subscribe_topics(self): """(Re)Subscribe to topics.""" attr_tpl = MqttValueTemplate( self._attributes_config.get(CONF_JSON_ATTRS_TEMPLATE), entity=self @@ -284,7 +293,7 @@ def attributes_message_received(msg: ReceiveMessage) -> None: _LOGGER.warning("Erroneous JSON: %s", payload) self._attributes = None - self._attributes_sub_state = await async_subscribe_topics( + self._attributes_sub_state = async_prepare_subscribe_topics( self.hass, self._attributes_sub_state, { @@ -297,9 +306,13 @@ def attributes_message_received(msg: ReceiveMessage) -> None: }, ) + async def _attributes_subscribe_topics(self): + """(Re)Subscribe to topics.""" + await async_subscribe_topics(self.hass, self._attributes_sub_state) + async def async_will_remove_from_hass(self): """Unsubscribe when removed.""" - self._attributes_sub_state = await async_unsubscribe_topics( + self._attributes_sub_state = async_unsubscribe_topics( self.hass, self._attributes_sub_state ) @@ -322,6 +335,7 @@ def __init__(self, config: dict) -> None: async def async_added_to_hass(self) -> None: """Subscribe MQTT events.""" await super().async_added_to_hass() + self._availability_prepare_subscribe_topics() await self._availability_subscribe_topics() self.async_on_remove( async_dispatcher_connect(self.hass, MQTT_CONNECTED, self.async_mqtt_connect) @@ -332,9 +346,13 @@ async def async_added_to_hass(self) -> None: ) ) - async def availability_discovery_update(self, config: dict): + def availability_prepare_discovery_update(self, config: dict): """Handle updated discovery message.""" self._availability_setup_from_config(config) + self._availability_prepare_subscribe_topics() + + async def availability_discovery_update(self, config: dict): + """Handle updated discovery message.""" await self._availability_subscribe_topics() def _availability_setup_from_config(self, config): @@ -366,7 +384,7 @@ def _availability_setup_from_config(self, config): self._avail_config = config - async def _availability_subscribe_topics(self): + def _availability_prepare_subscribe_topics(self): """(Re)Subscribe to topics.""" @callback @@ -398,12 +416,16 @@ def availability_message_received(msg: ReceiveMessage) -> None: for topic in self._avail_topics } - self._availability_sub_state = await async_subscribe_topics( + self._availability_sub_state = async_prepare_subscribe_topics( self.hass, self._availability_sub_state, topics, ) + async def _availability_subscribe_topics(self): + """(Re)Subscribe to topics.""" + await async_subscribe_topics(self.hass, self._availability_sub_state) + @callback def async_mqtt_connect(self): """Update state on connection/disconnection to MQTT broker.""" @@ -412,7 +434,7 @@ def async_mqtt_connect(self): async def async_will_remove_from_hass(self): """Unsubscribe when removed.""" - self._availability_sub_state = await async_unsubscribe_topics( + self._availability_sub_state = async_unsubscribe_topics( self.hass, self._availability_sub_state ) @@ -601,7 +623,7 @@ def __init__(self, device_config: ConfigType | None, config_entry=None) -> None: self._device_config = device_config self._config_entry = config_entry - async def device_info_discovery_update(self, config: dict): + def device_info_discovery_update(self, config: dict): """Handle updated discovery message.""" self._device_config = config.get(CONF_DEVICE) device_registry = dr.async_get(self.hass) @@ -657,6 +679,7 @@ def _init_entity_id(self): async def async_added_to_hass(self): """Subscribe mqtt events.""" await super().async_added_to_hass() + self._prepare_subscribe_topics() await self._subscribe_topics() async def discovery_update(self, discovery_payload): @@ -664,15 +687,22 @@ async def discovery_update(self, discovery_payload): config = self.config_schema()(discovery_payload) self._config = config self._setup_from_config(self._config) + + # Prepare MQTT subscriptions + self.attributes_prepare_discovery_update(config) + self.availability_prepare_discovery_update(config) + self.device_info_discovery_update(config) + self._prepare_subscribe_topics() + + # Finalize MQTT subscriptions await self.attributes_discovery_update(config) await self.availability_discovery_update(config) - await self.device_info_discovery_update(config) await self._subscribe_topics() self.async_write_ha_state() async def async_will_remove_from_hass(self): """Unsubscribe when removed.""" - self._sub_state = await subscription.async_unsubscribe_topics( + self._sub_state = subscription.async_unsubscribe_topics( self.hass, self._sub_state ) await MqttAttributes.async_will_remove_from_hass(self) @@ -687,6 +717,10 @@ def config_schema(): def _setup_from_config(self, config): """(Re)Setup the entity.""" + @abstractmethod + def _prepare_subscribe_topics(self): + """(Re)Subscribe to topics.""" + @abstractmethod async def _subscribe_topics(self): """(Re)Subscribe to topics.""" diff --git a/homeassistant/components/mqtt/number.py b/homeassistant/components/mqtt/number.py index 0020a020411eeb..511b6e470acbe8 100644 --- a/homeassistant/components/mqtt/number.py +++ b/homeassistant/components/mqtt/number.py @@ -162,7 +162,7 @@ def _setup_from_config(self, config): ).async_render_with_possible_json_value, } - async def _subscribe_topics(self): + def _prepare_subscribe_topics(self): """(Re)Subscribe to topics.""" @callback @@ -200,7 +200,7 @@ def message_received(msg): # Force into optimistic mode. self._optimistic = True else: - self._sub_state = await subscription.async_subscribe_topics( + self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, self._sub_state, { @@ -213,6 +213,10 @@ def message_received(msg): }, ) + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + await subscription.async_subscribe_topics(self.hass, self._sub_state) + if self._optimistic and (last_state := await self.async_get_last_state()): self._current_number = last_state.state diff --git a/homeassistant/components/mqtt/select.py b/homeassistant/components/mqtt/select.py index 810c6126d52b1c..24bed158eda9a1 100644 --- a/homeassistant/components/mqtt/select.py +++ b/homeassistant/components/mqtt/select.py @@ -128,7 +128,7 @@ def _setup_from_config(self, config): ).async_render_with_possible_json_value, } - async def _subscribe_topics(self): + def _prepare_subscribe_topics(self): """(Re)Subscribe to topics.""" @callback @@ -156,7 +156,7 @@ def message_received(msg): # Force into optimistic mode. self._optimistic = True else: - self._sub_state = await subscription.async_subscribe_topics( + self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, self._sub_state, { @@ -169,6 +169,10 @@ def message_received(msg): }, ) + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + await subscription.async_subscribe_topics(self.hass, self._sub_state) + if self._optimistic and (last_state := await self.async_get_last_state()): self._attr_current_option = last_state.state diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index 59f124155d3ebc..6cf72279546968 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -213,7 +213,7 @@ def _setup_from_config(self, config): self._config.get(CONF_LAST_RESET_VALUE_TEMPLATE), entity=self ).async_render_with_possible_json_value - async def _subscribe_topics(self): + def _prepare_subscribe_topics(self): """(Re)Subscribe to topics.""" topics = {} @@ -304,10 +304,14 @@ def last_reset_message_received(msg): "encoding": self._config[CONF_ENCODING] or None, } - self._sub_state = await subscription.async_subscribe_topics( + self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, self._sub_state, topics ) + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + await subscription.async_subscribe_topics(self.hass, self._sub_state) + @callback def _value_is_expired(self, *_): """Triggered when value is expired.""" diff --git a/homeassistant/components/mqtt/siren.py b/homeassistant/components/mqtt/siren.py index 6a268881593bd3..e33a13545b310f 100644 --- a/homeassistant/components/mqtt/siren.py +++ b/homeassistant/components/mqtt/siren.py @@ -214,7 +214,7 @@ def _setup_from_config(self, config): entity=self, ).async_render_with_possible_json_value - async def _subscribe_topics(self): + def _prepare_subscribe_topics(self): """(Re)Subscribe to topics.""" @callback @@ -274,7 +274,7 @@ def state_message_received(msg): # Force into optimistic mode. self._optimistic = True else: - self._sub_state = await subscription.async_subscribe_topics( + self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, self._sub_state, { @@ -287,6 +287,10 @@ def state_message_received(msg): }, ) + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + await subscription.async_subscribe_topics(self.hass, self._sub_state) + @property def assumed_state(self): """Return true if we do optimistic updates.""" diff --git a/homeassistant/components/mqtt/subscription.py b/homeassistant/components/mqtt/subscription.py index 6d132b28a98cfb..d0af533f2947a3 100644 --- a/homeassistant/components/mqtt/subscription.py +++ b/homeassistant/components/mqtt/subscription.py @@ -1,13 +1,12 @@ """Helper to handle a set of topics to subscribe to.""" from __future__ import annotations -from collections.abc import Callable +from collections.abc import Callable, Coroutine from typing import Any import attr from homeassistant.core import HomeAssistant -from homeassistant.loader import bind_hass from . import debug_info from .. import mqtt @@ -22,11 +21,12 @@ class EntitySubscription: hass: HomeAssistant = attr.ib() topic: str = attr.ib() message_callback: MessageCallbackType = attr.ib() + subscribe_task: Coroutine | None = attr.ib() unsubscribe_callback: Callable[[], None] | None = attr.ib() qos: int = attr.ib(default=0) encoding: str = attr.ib(default="utf-8") - async def resubscribe_if_necessary(self, hass, other): + def resubscribe_if_necessary(self, hass, other): """Re-subscribe to the new topic if necessary.""" if not self._should_resubscribe(other): self.unsubscribe_callback = other.unsubscribe_callback @@ -46,33 +46,41 @@ async def resubscribe_if_necessary(self, hass, other): # Prepare debug data debug_info.add_subscription(self.hass, self.message_callback, self.topic) - self.unsubscribe_callback = await mqtt.async_subscribe( + self.subscribe_task = mqtt.async_subscribe( hass, self.topic, self.message_callback, self.qos, self.encoding ) + async def subscribe(self): + """Subscribe to a topic.""" + if not self.subscribe_task: + return + self.unsubscribe_callback = await self.subscribe_task + def _should_resubscribe(self, other): """Check if we should re-subscribe to the topic using the old state.""" if other is None: return True - return (self.topic, self.qos, self.encoding) != ( + return (self.topic, self.qos, self.encoding,) != ( other.topic, other.qos, other.encoding, ) -@bind_hass -async def async_subscribe_topics( +def async_prepare_subscribe_topics( hass: HomeAssistant, new_state: dict[str, EntitySubscription] | None, topics: dict[str, Any], ) -> dict[str, EntitySubscription]: - """(Re)Subscribe to a set of MQTT topics. + """Prepare (re)subscribe to a set of MQTT topics. State is kept in sub_state and a dictionary mapping from the subscription key to the subscription state. + After this function has been called, async_subscribe_topics must be called to + finalize any new subscriptions. + Please note that the sub state must not be shared between multiple sets of topics. Every call to async_subscribe_topics must always contain _all_ the topics the subscription state should manage. @@ -88,10 +96,11 @@ async def async_subscribe_topics( qos=value.get("qos", DEFAULT_QOS), encoding=value.get("encoding", "utf-8"), hass=hass, + subscribe_task=None, ) # Get the current subscription state current = current_subscriptions.pop(key, None) - await requested.resubscribe_if_necessary(hass, current) + requested.resubscribe_if_necessary(hass, current) new_state[key] = requested # Go through all remaining subscriptions and unsubscribe them @@ -106,9 +115,19 @@ async def async_subscribe_topics( return new_state -@bind_hass -async def async_unsubscribe_topics( +async def async_subscribe_topics( + hass: HomeAssistant, + sub_state: dict[str, EntitySubscription] | None, +) -> None: + """(Re)Subscribe to a set of MQTT topics.""" + if sub_state is None: + return + for sub in sub_state.values(): + await sub.subscribe() + + +def async_unsubscribe_topics( hass: HomeAssistant, sub_state: dict[str, EntitySubscription] | None ) -> dict[str, EntitySubscription]: """Unsubscribe from all MQTT topics managed by async_subscribe_topics.""" - return await async_subscribe_topics(hass, sub_state, {}) + return async_prepare_subscribe_topics(hass, sub_state, {}) diff --git a/homeassistant/components/mqtt/switch.py b/homeassistant/components/mqtt/switch.py index 9feba2c1d253ca..6576072e4076f0 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -132,7 +132,7 @@ def _setup_from_config(self, config): self._config.get(CONF_VALUE_TEMPLATE), entity=self ).async_render_with_possible_json_value - async def _subscribe_topics(self): + def _prepare_subscribe_topics(self): """(Re)Subscribe to topics.""" @callback @@ -151,7 +151,7 @@ def state_message_received(msg): # Force into optimistic mode. self._optimistic = True else: - self._sub_state = await subscription.async_subscribe_topics( + self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, self._sub_state, { @@ -164,6 +164,10 @@ def state_message_received(msg): }, ) + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + await subscription.async_subscribe_topics(self.hass, self._sub_state) + if self._optimistic and (last_state := await self.async_get_last_state()): self._state = last_state.state == STATE_ON diff --git a/homeassistant/components/mqtt/tag.py b/homeassistant/components/mqtt/tag.py index b2638f8ac4b0f8..e415225080238e 100644 --- a/homeassistant/components/mqtt/tag.py +++ b/homeassistant/components/mqtt/tag.py @@ -175,7 +175,7 @@ async def tag_scanned(msg): await self.hass.components.tag.async_scan_tag(tag_id, self.device_id) - self._sub_state = await subscription.async_subscribe_topics( + self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, self._sub_state, { @@ -186,6 +186,7 @@ async def tag_scanned(msg): } }, ) + await subscription.async_subscribe_topics(self.hass, self._sub_state) async def device_removed(self, event): """Handle the removal of a device.""" @@ -207,7 +208,7 @@ async def tear_down(self): self._remove_discovery() mqtt.publish(self.hass, discovery_topic, "", retain=True) - self._sub_state = await subscription.async_unsubscribe_topics( + self._sub_state = subscription.async_unsubscribe_topics( self.hass, self._sub_state ) if self.device_id: diff --git a/homeassistant/components/mqtt/vacuum/schema_legacy.py b/homeassistant/components/mqtt/vacuum/schema_legacy.py index 5f85acc75ca6cc..3a764ca9e454a6 100644 --- a/homeassistant/components/mqtt/vacuum/schema_legacy.py +++ b/homeassistant/components/mqtt/vacuum/schema_legacy.py @@ -240,7 +240,7 @@ def _setup_from_config(self, config): ) } - async def _subscribe_topics(self): + def _prepare_subscribe_topics(self): """(Re)Subscribe to topics.""" for tpl in self._templates.values(): if tpl is not None: @@ -325,7 +325,7 @@ def message_received(msg): self.async_write_ha_state() topics_list = {topic for topic in self._state_topics.values() if topic} - self._sub_state = await subscription.async_subscribe_topics( + self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, self._sub_state, { @@ -339,6 +339,10 @@ def message_received(msg): }, ) + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + await subscription.async_subscribe_topics(self.hass, self._sub_state) + @property def is_on(self): """Return true if vacuum is on.""" diff --git a/homeassistant/components/mqtt/vacuum/schema_state.py b/homeassistant/components/mqtt/vacuum/schema_state.py index 872f4d62765a4c..1eb763c76cd18a 100644 --- a/homeassistant/components/mqtt/vacuum/schema_state.py +++ b/homeassistant/components/mqtt/vacuum/schema_state.py @@ -197,7 +197,7 @@ def _setup_from_config(self, config): ) } - async def _subscribe_topics(self): + def _prepare_subscribe_topics(self): """(Re)Subscribe to topics.""" topics = {} @@ -219,10 +219,14 @@ def state_message_received(msg): "qos": self._config[CONF_QOS], "encoding": self._config[CONF_ENCODING] or None, } - self._sub_state = await subscription.async_subscribe_topics( + self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, self._sub_state, topics ) + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + await subscription.async_subscribe_topics(self.hass, self._sub_state) + @property def state(self): """Return state of vacuum.""" diff --git a/homeassistant/components/tasmota/__init__.py b/homeassistant/components/tasmota/__init__.py index f8dcd4035df391..2d664bb46ee232 100644 --- a/homeassistant/components/tasmota/__init__.py +++ b/homeassistant/components/tasmota/__init__.py @@ -19,6 +19,7 @@ from homeassistant.components import mqtt, websocket_api from homeassistant.components.mqtt.subscription import ( + async_prepare_subscribe_topics, async_subscribe_topics, async_unsubscribe_topics, ) @@ -62,10 +63,12 @@ async def _subscribe_topics(sub_state: dict | None, topics: dict) -> dict: for topic in topics.values(): if "msg_callback" in topic and "event_loop_safe" in topic: topic["msg_callback"] = callback(topic["msg_callback"]) - return await async_subscribe_topics(hass, sub_state, topics) + sub_state = async_prepare_subscribe_topics(hass, sub_state, topics) + await async_subscribe_topics(hass, sub_state) + return sub_state async def _unsubscribe_topics(sub_state: dict | None) -> dict: - return await async_unsubscribe_topics(hass, sub_state) + return async_unsubscribe_topics(hass, sub_state) tasmota_mqtt = TasmotaMQTTClient(_publish, _subscribe_topics, _unsubscribe_topics) diff --git a/tests/components/mqtt/test_subscription.py b/tests/components/mqtt/test_subscription.py index 36d8946be0bb24..e2ffc602ddd445 100644 --- a/tests/components/mqtt/test_subscription.py +++ b/tests/components/mqtt/test_subscription.py @@ -2,6 +2,7 @@ from unittest.mock import ANY from homeassistant.components.mqtt.subscription import ( + async_prepare_subscribe_topics, async_subscribe_topics, async_unsubscribe_topics, ) @@ -27,7 +28,7 @@ def record_calls2(*args): calls2.append(args) sub_state = None - sub_state = await async_subscribe_topics( + sub_state = async_prepare_subscribe_topics( hass, sub_state, { @@ -35,6 +36,7 @@ def record_calls2(*args): "test_topic2": {"topic": "test-topic2", "msg_callback": record_calls2}, }, ) + await async_subscribe_topics(hass, sub_state) async_fire_mqtt_message(hass, "test-topic1", "test-payload1") assert len(calls1) == 1 @@ -48,7 +50,7 @@ def record_calls2(*args): assert calls2[0][0].topic == "test-topic2" assert calls2[0][0].payload == "test-payload2" - await async_unsubscribe_topics(hass, sub_state) + async_unsubscribe_topics(hass, sub_state) async_fire_mqtt_message(hass, "test-topic1", "test-payload") async_fire_mqtt_message(hass, "test-topic2", "test-payload") @@ -74,7 +76,7 @@ def record_calls2(*args): calls2.append(args) sub_state = None - sub_state = await async_subscribe_topics( + sub_state = async_prepare_subscribe_topics( hass, sub_state, { @@ -82,6 +84,7 @@ def record_calls2(*args): "test_topic2": {"topic": "test-topic2", "msg_callback": record_calls2}, }, ) + await async_subscribe_topics(hass, sub_state) async_fire_mqtt_message(hass, "test-topic1", "test-payload") assert len(calls1) == 1 @@ -91,11 +94,12 @@ def record_calls2(*args): assert len(calls1) == 1 assert len(calls2) == 1 - sub_state = await async_subscribe_topics( + sub_state = async_prepare_subscribe_topics( hass, sub_state, {"test_topic1": {"topic": "test-topic1_1", "msg_callback": record_calls1}}, ) + await async_subscribe_topics(hass, sub_state) async_fire_mqtt_message(hass, "test-topic1", "test-payload") async_fire_mqtt_message(hass, "test-topic2", "test-payload") @@ -108,7 +112,7 @@ def record_calls2(*args): assert calls1[1][0].payload == "test-payload" assert len(calls2) == 1 - await async_unsubscribe_topics(hass, sub_state) + async_unsubscribe_topics(hass, sub_state) async_fire_mqtt_message(hass, "test-topic1_1", "test-payload") async_fire_mqtt_message(hass, "test-topic2", "test-payload") @@ -126,11 +130,12 @@ def msg_callback(*args): pass sub_state = None - sub_state = await async_subscribe_topics( + sub_state = async_prepare_subscribe_topics( hass, sub_state, {"test_topic1": {"topic": "test-topic1", "msg_callback": msg_callback}}, ) + await async_subscribe_topics(hass, sub_state) mqtt_mock.async_subscribe.assert_called_once_with("test-topic1", ANY, 0, "utf-8") @@ -143,7 +148,7 @@ def msg_callback(*args): pass sub_state = None - sub_state = await async_subscribe_topics( + sub_state = async_prepare_subscribe_topics( hass, sub_state, { @@ -155,6 +160,7 @@ def msg_callback(*args): } }, ) + await async_subscribe_topics(hass, sub_state) mqtt_mock.async_subscribe.assert_called_once_with("test-topic1", ANY, 1, "utf-16") @@ -169,27 +175,29 @@ def record_calls(*args): calls.append(args) sub_state = None - sub_state = await async_subscribe_topics( + sub_state = async_prepare_subscribe_topics( hass, sub_state, {"test_topic1": {"topic": "test-topic1", "msg_callback": record_calls}}, ) + await async_subscribe_topics(hass, sub_state) subscribe_call_count = mqtt_mock.async_subscribe.call_count async_fire_mqtt_message(hass, "test-topic1", "test-payload") assert len(calls) == 1 - sub_state = await async_subscribe_topics( + sub_state = async_prepare_subscribe_topics( hass, sub_state, {"test_topic1": {"topic": "test-topic1", "msg_callback": record_calls}}, ) + await async_subscribe_topics(hass, sub_state) assert subscribe_call_count == mqtt_mock.async_subscribe.call_count async_fire_mqtt_message(hass, "test-topic1", "test-payload") assert len(calls) == 2 - await async_unsubscribe_topics(hass, sub_state) + async_unsubscribe_topics(hass, sub_state) async_fire_mqtt_message(hass, "test-topic1", "test-payload") assert len(calls) == 2 From 8325188ed2284feb4a9d5ebff67b47a6f7823311 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 3 Feb 2022 06:57:44 +0100 Subject: [PATCH 0207/1098] Remove nest legacy from mypy ignored modules (#65421) * Remove nest legacy from mypy ignored modules * Set type-ignore inside the files Co-authored-by: epenet --- homeassistant/components/nest/legacy/__init__.py | 1 + homeassistant/components/nest/legacy/binary_sensor.py | 2 ++ homeassistant/components/nest/legacy/camera.py | 2 ++ homeassistant/components/nest/legacy/climate.py | 2 ++ homeassistant/components/nest/legacy/local_auth.py | 2 ++ homeassistant/components/nest/legacy/sensor.py | 2 ++ mypy.ini | 3 --- script/hassfest/mypy_config.py | 1 - 8 files changed, 11 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/nest/legacy/__init__.py b/homeassistant/components/nest/legacy/__init__.py index ce1fdd0e7acd4c..e0202c63567eb2 100644 --- a/homeassistant/components/nest/legacy/__init__.py +++ b/homeassistant/components/nest/legacy/__init__.py @@ -1,4 +1,5 @@ """Support for Nest devices.""" +# mypy: ignore-errors from datetime import datetime, timedelta import logging diff --git a/homeassistant/components/nest/legacy/binary_sensor.py b/homeassistant/components/nest/legacy/binary_sensor.py index 79e1bf65c86e0e..8b17e7d020ff79 100644 --- a/homeassistant/components/nest/legacy/binary_sensor.py +++ b/homeassistant/components/nest/legacy/binary_sensor.py @@ -1,4 +1,6 @@ """Support for Nest Thermostat binary sensors.""" +# mypy: ignore-errors + from itertools import chain import logging diff --git a/homeassistant/components/nest/legacy/camera.py b/homeassistant/components/nest/legacy/camera.py index 0b6b7a649a6c4d..3bcf1cdee2cc04 100644 --- a/homeassistant/components/nest/legacy/camera.py +++ b/homeassistant/components/nest/legacy/camera.py @@ -1,4 +1,6 @@ """Support for Nest Cameras.""" +# mypy: ignore-errors + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/nest/legacy/climate.py b/homeassistant/components/nest/legacy/climate.py index 3e0eb5ac16b25b..97ed1ee00b91d4 100644 --- a/homeassistant/components/nest/legacy/climate.py +++ b/homeassistant/components/nest/legacy/climate.py @@ -1,4 +1,6 @@ """Legacy Works with Nest climate implementation.""" +# mypy: ignore-errors + import logging from nest.nest import APIError diff --git a/homeassistant/components/nest/legacy/local_auth.py b/homeassistant/components/nest/legacy/local_auth.py index 6c7f10430934cf..a091469cd81e24 100644 --- a/homeassistant/components/nest/legacy/local_auth.py +++ b/homeassistant/components/nest/legacy/local_auth.py @@ -1,4 +1,6 @@ """Local Nest authentication for the legacy api.""" +# mypy: ignore-errors + import asyncio from functools import partial from http import HTTPStatus diff --git a/homeassistant/components/nest/legacy/sensor.py b/homeassistant/components/nest/legacy/sensor.py index c5229177b60ca5..bb09a40b15e83a 100644 --- a/homeassistant/components/nest/legacy/sensor.py +++ b/homeassistant/components/nest/legacy/sensor.py @@ -1,4 +1,6 @@ """Support for Nest Thermostat sensors for the legacy API.""" +# mypy: ignore-errors + import logging from homeassistant.components.sensor import SensorDeviceClass, SensorEntity diff --git a/mypy.ini b/mypy.ini index 7e35a4929f8853..0181d769c2dce8 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2136,9 +2136,6 @@ ignore_errors = true [mypy-homeassistant.components.mobile_app.*] ignore_errors = true -[mypy-homeassistant.components.nest.legacy.*] -ignore_errors = true - [mypy-homeassistant.components.netgear.*] ignore_errors = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index bb1cc2a6679544..00c22abae4a2e1 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -51,7 +51,6 @@ "homeassistant.components.meteo_france.*", "homeassistant.components.minecraft_server.*", "homeassistant.components.mobile_app.*", - "homeassistant.components.nest.legacy.*", "homeassistant.components.netgear.*", "homeassistant.components.nilu.*", "homeassistant.components.nzbget.*", From 913d6f6ea15cdaaec8f5871f943089a82d3fe843 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 3 Feb 2022 06:59:21 +0100 Subject: [PATCH 0208/1098] Remove sonos media_player from strict typing (#65419) Co-authored-by: epenet --- .strict-typing | 1 - mypy.ini | 11 ----------- 2 files changed, 12 deletions(-) diff --git a/.strict-typing b/.strict-typing index 38c9e6812b7af3..00ce5de2b3e964 100644 --- a/.strict-typing +++ b/.strict-typing @@ -155,7 +155,6 @@ homeassistant.components.shelly.* homeassistant.components.simplisafe.* homeassistant.components.slack.* homeassistant.components.smhi.* -homeassistant.components.sonos.media_player homeassistant.components.ssdp.* homeassistant.components.stookalert.* homeassistant.components.statistics.* diff --git a/mypy.ini b/mypy.ini index 0181d769c2dce8..cd03f8dc8b5753 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1522,17 +1522,6 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true -[mypy-homeassistant.components.sonos.media_player] -check_untyped_defs = true -disallow_incomplete_defs = true -disallow_subclassing_any = true -disallow_untyped_calls = true -disallow_untyped_decorators = true -disallow_untyped_defs = true -no_implicit_optional = true -warn_return_any = true -warn_unreachable = true - [mypy-homeassistant.components.ssdp.*] check_untyped_defs = true disallow_incomplete_defs = true From 23ee8adf596e8f238e688d394757e8e90481b376 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 3 Feb 2022 09:23:32 +0100 Subject: [PATCH 0209/1098] Fix flaky homewizard test (#65490) --- tests/components/homewizard/test_sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/components/homewizard/test_sensor.py b/tests/components/homewizard/test_sensor.py index 6f5396f8702853..c1a98c0710823c 100644 --- a/tests/components/homewizard/test_sensor.py +++ b/tests/components/homewizard/test_sensor.py @@ -663,10 +663,10 @@ async def test_sensors_unreachable(hass, mock_config_entry_data, mock_config_ent entry = mock_config_entry entry.data = mock_config_entry_data entry.add_to_hass(hass) - utcnow = dt_util.utcnow() await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() + utcnow = dt_util.utcnow() # Time after the integration is setup assert ( hass.states.get( @@ -717,7 +717,7 @@ async def test_api_disabled(hass, mock_config_entry_data, mock_config_entry): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - utcnow = dt_util.utcnow() + utcnow = dt_util.utcnow() # Time after the integration is setup assert ( hass.states.get( From b5872016548466ea81a9cf84ce4a29f638632d8b Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 3 Feb 2022 10:00:30 +0100 Subject: [PATCH 0210/1098] Add update listener type hints to broadlink (#65413) Co-authored-by: epenet --- homeassistant/components/broadlink/device.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/broadlink/device.py b/homeassistant/components/broadlink/device.py index 951be9b26bb7a5..46582334e2d525 100644 --- a/homeassistant/components/broadlink/device.py +++ b/homeassistant/components/broadlink/device.py @@ -12,8 +12,9 @@ NetworkTimeoutError, ) -from homeassistant.config_entries import SOURCE_REAUTH +from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_TIMEOUT, CONF_TYPE +from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr @@ -64,13 +65,15 @@ def available(self): return self.update_manager.available @staticmethod - async def async_update(hass, entry): + async def async_update(hass: HomeAssistant, entry: ConfigEntry) -> None: """Update the device and related entities. Triggered when the device is renamed on the frontend. """ device_registry = dr.async_get(hass) + assert entry.unique_id device_entry = device_registry.async_get_device({(DOMAIN, entry.unique_id)}) + assert device_entry device_registry.async_update_device(device_entry.id, name=entry.title) await hass.config_entries.async_reload(entry.entry_id) From c8504bd21dbb7b3d873505492bcf24ff46c73acd Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 3 Feb 2022 10:01:02 +0100 Subject: [PATCH 0211/1098] Add tests for pylint plugins (#65436) Co-authored-by: epenet --- tests/pylint/__init__.py | 30 +++++ tests/pylint/conftest.py | 30 +++++ tests/pylint/test_enforce_type_hints.py | 142 ++++++++++++++++++++++-- 3 files changed, 192 insertions(+), 10 deletions(-) create mode 100644 tests/pylint/conftest.py diff --git a/tests/pylint/__init__.py b/tests/pylint/__init__.py index d6bdd6675f0ebd..e03a2d2a118f6d 100644 --- a/tests/pylint/__init__.py +++ b/tests/pylint/__init__.py @@ -1 +1,31 @@ """Tests for pylint.""" +import contextlib + +from pylint.testutils.unittest_linter import UnittestLinter + + +@contextlib.contextmanager +def assert_no_messages(linter: UnittestLinter): + """Assert that no messages are added by the given method.""" + with assert_adds_messages(linter): + yield + + +@contextlib.contextmanager +def assert_adds_messages(linter: UnittestLinter, *messages): + """Assert that exactly the given method adds the given messages. + + The list of messages must exactly match *all* the messages added by the + method. Additionally, we check to see whether the args in each message can + actually be substituted into the message string. + """ + yield + got = linter.release_messages() + no_msg = "No message." + expected = "\n".join(repr(m) for m in messages) or no_msg + got_str = "\n".join(repr(m) for m in got) or no_msg + msg = ( + "Expected messages did not match actual.\n" + f"\nExpected:\n{expected}\n\nGot:\n{got_str}\n" + ) + assert got == list(messages), msg diff --git a/tests/pylint/conftest.py b/tests/pylint/conftest.py new file mode 100644 index 00000000000000..887f50fb628ac0 --- /dev/null +++ b/tests/pylint/conftest.py @@ -0,0 +1,30 @@ +"""Configuration for pylint tests.""" +from importlib.machinery import SourceFileLoader +from types import ModuleType + +from pylint.checkers import BaseChecker +from pylint.testutils.unittest_linter import UnittestLinter +import pytest + + +@pytest.fixture(name="hass_enforce_type_hints") +def hass_enforce_type_hints_fixture() -> ModuleType: + """Fixture to provide a requests mocker.""" + loader = SourceFileLoader( + "hass_enforce_type_hints", "pylint/plugins/hass_enforce_type_hints.py" + ) + return loader.load_module(None) + + +@pytest.fixture(name="linter") +def linter_fixture() -> UnittestLinter: + """Fixture to provide a requests mocker.""" + return UnittestLinter() + + +@pytest.fixture(name="type_hint_checker") +def type_hint_checker_fixture(hass_enforce_type_hints, linter) -> BaseChecker: + """Fixture to provide a requests mocker.""" + type_hint_checker = hass_enforce_type_hints.HassTypeHintChecker(linter) + type_hint_checker.module = "homeassistant.components.pylint_test" + return type_hint_checker diff --git a/tests/pylint/test_enforce_type_hints.py b/tests/pylint/test_enforce_type_hints.py index fe60ed022f48ed..81fdd2fa916bb5 100644 --- a/tests/pylint/test_enforce_type_hints.py +++ b/tests/pylint/test_enforce_type_hints.py @@ -1,16 +1,17 @@ """Tests for pylint hass_enforce_type_hints plugin.""" # pylint:disable=protected-access -from importlib.machinery import SourceFileLoader import re +from types import ModuleType +from unittest.mock import patch +import astroid +from pylint.checkers import BaseChecker +import pylint.testutils +from pylint.testutils.unittest_linter import UnittestLinter import pytest -loader = SourceFileLoader( - "hass_enforce_type_hints", "pylint/plugins/hass_enforce_type_hints.py" -) -hass_enforce_type_hints = loader.load_module(None) -_TYPE_HINT_MATCHERS: dict[str, re.Pattern] = hass_enforce_type_hints._TYPE_HINT_MATCHERS +from . import assert_adds_messages, assert_no_messages @pytest.mark.parametrize( @@ -20,9 +21,17 @@ ("Callable[..., Awaitable[None]]", "Callable", "...", "Awaitable[None]"), ], ) -def test_regex_x_of_y_comma_z(string, expected_x, expected_y, expected_z): +def test_regex_x_of_y_comma_z( + hass_enforce_type_hints: ModuleType, + string: str, + expected_x: str, + expected_y: str, + expected_z: str, +) -> None: """Test x_of_y_comma_z regexes.""" - assert (match := _TYPE_HINT_MATCHERS["x_of_y_comma_z"].match(string)) + matchers: dict[str, re.Pattern] = hass_enforce_type_hints._TYPE_HINT_MATCHERS + + assert (match := matchers["x_of_y_comma_z"].match(string)) assert match.group(0) == string assert match.group(1) == expected_x assert match.group(2) == expected_y @@ -33,9 +42,122 @@ def test_regex_x_of_y_comma_z(string, expected_x, expected_y, expected_z): ("string", "expected_a", "expected_b"), [("DiscoveryInfoType | None", "DiscoveryInfoType", "None")], ) -def test_regex_a_or_b(string, expected_a, expected_b): +def test_regex_a_or_b( + hass_enforce_type_hints: ModuleType, string: str, expected_a: str, expected_b: str +) -> None: """Test a_or_b regexes.""" - assert (match := _TYPE_HINT_MATCHERS["a_or_b"].match(string)) + matchers: dict[str, re.Pattern] = hass_enforce_type_hints._TYPE_HINT_MATCHERS + + assert (match := matchers["a_or_b"].match(string)) assert match.group(0) == string assert match.group(1) == expected_a assert match.group(2) == expected_b + + +@pytest.mark.parametrize( + "code", + [ + """ + async def setup( #@ + arg1, arg2 + ): + pass + """ + ], +) +def test_ignore_not_annotations( + hass_enforce_type_hints: ModuleType, type_hint_checker: BaseChecker, code: str +) -> None: + """Ensure that _is_valid_type is not run if there are no annotations.""" + func_node = astroid.extract_node(code) + + with patch.object( + hass_enforce_type_hints, "_is_valid_type", return_value=True + ) as is_valid_type: + type_hint_checker.visit_asyncfunctiondef(func_node) + is_valid_type.assert_not_called() + + +@pytest.mark.parametrize( + "code", + [ + """ + async def setup( #@ + arg1: ArgHint, arg2 + ): + pass + """, + """ + async def setup( #@ + arg1, arg2 + ) -> ReturnHint: + pass + """, + """ + async def setup( #@ + arg1: ArgHint, arg2: ArgHint + ) -> ReturnHint: + pass + """, + ], +) +def test_dont_ignore_partial_annotations( + hass_enforce_type_hints: ModuleType, type_hint_checker: BaseChecker, code: str +) -> None: + """Ensure that _is_valid_type is run if there is at least one annotation.""" + func_node = astroid.extract_node(code) + + with patch.object( + hass_enforce_type_hints, "_is_valid_type", return_value=True + ) as is_valid_type: + type_hint_checker.visit_asyncfunctiondef(func_node) + is_valid_type.assert_called() + + +def test_invalid_discovery_info( + linter: UnittestLinter, type_hint_checker: BaseChecker +) -> None: + """Ensure invalid hints are rejected for discovery_info.""" + type_hint_checker.module = "homeassistant.components.pylint_test.device_tracker" + func_node, discovery_info_node = astroid.extract_node( + """ + async def async_setup_scanner( #@ + hass: HomeAssistant, + config: ConfigType, + async_see: Callable[..., Awaitable[None]], + discovery_info: dict[str, Any] | None = None, #@ + ) -> bool: + pass + """ + ) + + with assert_adds_messages( + linter, + pylint.testutils.MessageTest( + msg_id="hass-argument-type", + node=discovery_info_node, + args=(4, "DiscoveryInfoType | None"), + ), + ): + type_hint_checker.visit_asyncfunctiondef(func_node) + + +def test_valid_discovery_info( + linter: UnittestLinter, type_hint_checker: BaseChecker +) -> None: + """Ensure valid hints are accepted for discovery_info.""" + type_hint_checker.module = "homeassistant.components.pylint_test.device_tracker" + func_node = astroid.extract_node( + """ + async def async_setup_scanner( #@ + hass: HomeAssistant, + config: ConfigType, + async_see: Callable[..., Awaitable[None]], + discovery_info: DiscoveryInfoType | None = None, + ) -> bool: + pass + """ + ) + + with assert_no_messages(linter): + type_hint_checker.visit_asyncfunctiondef(func_node) From 9fde84ab411bfb8fb56ba398e49833272a4037b9 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 3 Feb 2022 10:01:41 +0100 Subject: [PATCH 0212/1098] Remove freebox from mypy ignore list (#65126) * Add type hints to freebox * Remove freebox from mypy ignore list * Adjust type hints * Refactor FreeboxRouter setup/close * Remove unnecessary assert * Remove unused constant * Rework unload routine * Bring back close method * Suppress NotOpenError * Use async_on_unload on signal_device_new Co-authored-by: epenet --- homeassistant/components/freebox/__init__.py | 32 ++++++--- .../components/freebox/device_tracker.py | 14 ++-- homeassistant/components/freebox/router.py | 65 ++++++------------- homeassistant/components/freebox/sensor.py | 4 +- homeassistant/components/freebox/switch.py | 6 +- mypy.ini | 3 - script/hassfest/mypy_config.py | 1 - 7 files changed, 55 insertions(+), 70 deletions(-) diff --git a/homeassistant/components/freebox/__init__.py b/homeassistant/components/freebox/__init__.py index ee8c2f3097596a..b5deb8517bbb93 100644 --- a/homeassistant/components/freebox/__init__.py +++ b/homeassistant/components/freebox/__init__.py @@ -1,18 +1,19 @@ """Support for Freebox devices (Freebox v6 and Freebox mini 4K).""" -import logging +from datetime import timedelta +from freebox_api.exceptions import HttpRequestError import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP -from homeassistant.core import HomeAssistant, ServiceCall +from homeassistant.core import Event, HomeAssistant, ServiceCall +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType from .const import DOMAIN, PLATFORMS, SERVICE_REBOOT -from .router import FreeboxRouter - -_LOGGER = logging.getLogger(__name__) +from .router import FreeboxRouter, get_api FREEBOX_SCHEMA = vol.Schema( {vol.Required(CONF_HOST): cv.string, vol.Required(CONF_PORT): cv.port} @@ -26,6 +27,8 @@ extra=vol.ALLOW_EXTRA, ) +SCAN_INTERVAL = timedelta(seconds=30) + async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Freebox integration.""" @@ -42,8 +45,19 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Freebox entry.""" - router = FreeboxRouter(hass, entry) - await router.setup() + api = await get_api(hass, entry.data[CONF_HOST]) + try: + await api.open(entry.data[CONF_HOST], entry.data[CONF_PORT]) + except HttpRequestError as err: + raise ConfigEntryNotReady from err + + freebox_config = await api.system.get_config() + + router = FreeboxRouter(hass, entry, api, freebox_config) + await router.update_all() + entry.async_on_unload( + async_track_time_interval(hass, router.update_all, SCAN_INTERVAL) + ) hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.unique_id] = router @@ -57,7 +71,7 @@ async def async_reboot(call: ServiceCall) -> None: hass.services.async_register(DOMAIN, SERVICE_REBOOT, async_reboot) - async def async_close_connection(event): + async def async_close_connection(event: Event) -> None: """Close Freebox connection on HA Stop.""" await router.close() @@ -72,7 +86,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: - router = hass.data[DOMAIN].pop(entry.unique_id) + router: FreeboxRouter = hass.data[DOMAIN].pop(entry.unique_id) await router.close() hass.services.async_remove(DOMAIN, SERVICE_REBOOT) diff --git a/homeassistant/components/freebox/device_tracker.py b/homeassistant/components/freebox/device_tracker.py index d57af2d53c25c6..0fe04fe7eb9795 100644 --- a/homeassistant/components/freebox/device_tracker.py +++ b/homeassistant/components/freebox/device_tracker.py @@ -19,15 +19,15 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up device tracker for Freebox component.""" - router = hass.data[DOMAIN][entry.unique_id] - tracked = set() + router: FreeboxRouter = hass.data[DOMAIN][entry.unique_id] + tracked: set[str] = set() @callback - def update_router(): + def update_router() -> None: """Update the values of the router.""" add_entities(router, async_add_entities, tracked) - router.listeners.append( + entry.async_on_unload( async_dispatcher_connect(hass, router.signal_device_new, update_router) ) @@ -35,7 +35,9 @@ def update_router(): @callback -def add_entities(router, async_add_entities, tracked): +def add_entities( + router: FreeboxRouter, async_add_entities: AddEntitiesCallback, tracked: set[str] +) -> None: """Add new tracker entities from the router.""" new_tracked = [] @@ -61,7 +63,7 @@ def __init__(self, router: FreeboxRouter, device: dict[str, Any]) -> None: self._manufacturer = device["vendor_name"] self._icon = icon_for_freebox_device(device) self._active = False - self._attrs = {} + self._attrs: dict[str, Any] = {} @callback def async_update_state(self) -> None: diff --git a/homeassistant/components/freebox/router.py b/homeassistant/components/freebox/router.py index e352146915e94c..0d20545fdcd908 100644 --- a/homeassistant/components/freebox/router.py +++ b/homeassistant/components/freebox/router.py @@ -1,24 +1,23 @@ """Represent the Freebox router and its devices and sensors.""" from __future__ import annotations -from datetime import datetime, timedelta -import logging +from collections.abc import Mapping +from contextlib import suppress +from datetime import datetime import os from pathlib import Path from typing import Any from freebox_api import Freepybox from freebox_api.api.wifi import Wifi -from freebox_api.exceptions import HttpRequestError +from freebox_api.exceptions import NotOpenError from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.entity import DeviceInfo -from homeassistant.helpers.event import async_track_time_interval from homeassistant.util import slugify from .const import ( @@ -30,10 +29,6 @@ STORAGE_VERSION, ) -_LOGGER = logging.getLogger(__name__) - -SCAN_INTERVAL = timedelta(seconds=30) - async def get_api(hass: HomeAssistant, host: str) -> Freepybox: """Get the Freebox API.""" @@ -50,18 +45,23 @@ async def get_api(hass: HomeAssistant, host: str) -> Freepybox: class FreeboxRouter: """Representation of a Freebox router.""" - def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: + def __init__( + self, + hass: HomeAssistant, + entry: ConfigEntry, + api: Freepybox, + freebox_config: Mapping[str, Any], + ) -> None: """Initialize a Freebox router.""" self.hass = hass - self._entry = entry self._host = entry.data[CONF_HOST] self._port = entry.data[CONF_PORT] - self._api: Freepybox = None - self.name = None - self.mac = None - self._sw_v = None - self._attrs = {} + self._api: Freepybox = api + self.name: str = freebox_config["model_info"]["pretty_name"] + self.mac: str = freebox_config["mac"] + self._sw_v: str = freebox_config["firmware_version"] + self._attrs: dict[str, Any] = {} self.devices: dict[str, dict[str, Any]] = {} self.disks: dict[int, dict[str, Any]] = {} @@ -69,31 +69,6 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: self.sensors_connection: dict[str, float] = {} self.call_list: list[dict[str, Any]] = [] - self._unsub_dispatcher = None - self.listeners = [] - - async def setup(self) -> None: - """Set up a Freebox router.""" - self._api = await get_api(self.hass, self._host) - - try: - await self._api.open(self._host, self._port) - except HttpRequestError: - _LOGGER.exception("Failed to connect to Freebox") - return ConfigEntryNotReady - - # System - fbx_config = await self._api.system.get_config() - self.mac = fbx_config["mac"] - self.name = fbx_config["model_info"]["pretty_name"] - self._sw_v = fbx_config["firmware_version"] - - # Devices & sensors - await self.update_all() - self._unsub_dispatcher = async_track_time_interval( - self.hass, self.update_all, SCAN_INTERVAL - ) - async def update_all(self, now: datetime | None = None) -> None: """Update all Freebox platforms.""" await self.update_device_trackers() @@ -102,7 +77,7 @@ async def update_all(self, now: datetime | None = None) -> None: async def update_device_trackers(self) -> None: """Update Freebox devices.""" new_device = False - fbx_devices: [dict[str, Any]] = await self._api.lan.get_hosts_list() + fbx_devices: list[dict[str, Any]] = await self._api.lan.get_hosts_list() # Adds the Freebox itself fbx_devices.append( @@ -164,7 +139,7 @@ async def update_sensors(self) -> None: async def _update_disks_sensors(self) -> None: """Update Freebox disks.""" # None at first request - fbx_disks: [dict[str, Any]] = await self._api.storage.get_disks() or [] + fbx_disks: list[dict[str, Any]] = await self._api.storage.get_disks() or [] for fbx_disk in fbx_disks: self.disks[fbx_disk["id"]] = fbx_disk @@ -175,10 +150,8 @@ async def reboot(self) -> None: async def close(self) -> None: """Close the connection.""" - if self._api is not None: + with suppress(NotOpenError): await self._api.close() - self._unsub_dispatcher() - self._api = None @property def device_info(self) -> DeviceInfo: diff --git a/homeassistant/components/freebox/sensor.py b/homeassistant/components/freebox/sensor.py index c5852aa22aafc1..46aa9ee8aa0794 100644 --- a/homeassistant/components/freebox/sensor.py +++ b/homeassistant/components/freebox/sensor.py @@ -27,7 +27,7 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the sensors.""" - router = hass.data[DOMAIN][entry.unique_id] + router: FreeboxRouter = hass.data[DOMAIN][entry.unique_id] entities = [] _LOGGER.debug( @@ -120,7 +120,7 @@ def __init__( ) -> None: """Initialize a Freebox call sensor.""" super().__init__(router, description) - self._call_list_for_type = [] + self._call_list_for_type: list[dict[str, Any]] = [] @callback def async_update_state(self) -> None: diff --git a/homeassistant/components/freebox/switch.py b/homeassistant/components/freebox/switch.py index 76b89a17414716..ee59c097f93718 100644 --- a/homeassistant/components/freebox/switch.py +++ b/homeassistant/components/freebox/switch.py @@ -21,7 +21,7 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the switch.""" - router = hass.data[DOMAIN][entry.unique_id] + router: FreeboxRouter = hass.data[DOMAIN][entry.unique_id] async_add_entities([FreeboxWifiSwitch(router)], True) @@ -31,7 +31,7 @@ class FreeboxWifiSwitch(SwitchEntity): def __init__(self, router: FreeboxRouter) -> None: """Initialize the Wifi switch.""" self._name = "Freebox WiFi" - self._state = None + self._state: bool | None = None self._router = router self._unique_id = f"{self._router.mac} {self._name}" @@ -46,7 +46,7 @@ def name(self) -> str: return self._name @property - def is_on(self) -> bool: + def is_on(self) -> bool | None: """Return true if device is on.""" return self._state diff --git a/mypy.ini b/mypy.ini index cd03f8dc8b5753..e947b66161837d 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2050,9 +2050,6 @@ ignore_errors = true [mypy-homeassistant.components.firmata.*] ignore_errors = true -[mypy-homeassistant.components.freebox.*] -ignore_errors = true - [mypy-homeassistant.components.geniushub.*] ignore_errors = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index 00c22abae4a2e1..2ab8739de8909a 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -26,7 +26,6 @@ "homeassistant.components.evohome.*", "homeassistant.components.fireservicerota.*", "homeassistant.components.firmata.*", - "homeassistant.components.freebox.*", "homeassistant.components.geniushub.*", "homeassistant.components.google_assistant.*", "homeassistant.components.gree.*", From cd67ddbe260fd43585f965151ceb54540121a6f5 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 3 Feb 2022 12:16:53 +0100 Subject: [PATCH 0213/1098] Upgrade pwmled to 1.6.9 (#65465) --- homeassistant/components/rpi_gpio_pwm/manifest.json | 2 +- requirements_all.txt | 2 +- script/pip_check | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/rpi_gpio_pwm/manifest.json b/homeassistant/components/rpi_gpio_pwm/manifest.json index 78ec56799a598a..96094e6f75ea6d 100644 --- a/homeassistant/components/rpi_gpio_pwm/manifest.json +++ b/homeassistant/components/rpi_gpio_pwm/manifest.json @@ -2,7 +2,7 @@ "domain": "rpi_gpio_pwm", "name": "pigpio Daemon PWM LED", "documentation": "https://www.home-assistant.io/integrations/rpi_gpio_pwm", - "requirements": ["pwmled==1.6.7"], + "requirements": ["pwmled==1.6.9"], "codeowners": ["@soldag"], "iot_class": "local_push", "loggers": ["adafruit_blinka", "adafruit_circuitpython_pca9685", "pwmled"] diff --git a/requirements_all.txt b/requirements_all.txt index f8d4c19220b8d3..9bb37f7a48a789 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1313,7 +1313,7 @@ pushover_complete==1.1.1 pvo==0.2.0 # homeassistant.components.rpi_gpio_pwm -pwmled==1.6.7 +pwmled==1.6.9 # homeassistant.components.canary py-canary==0.5.1 diff --git a/script/pip_check b/script/pip_check index 1b2be96132120c..14be2d1a7964e4 100755 --- a/script/pip_check +++ b/script/pip_check @@ -3,7 +3,7 @@ PIP_CACHE=$1 # Number of existing dependency conflicts # Update if a PR resolve one! -DEPENDENCY_CONFLICTS=13 +DEPENDENCY_CONFLICTS=12 PIP_CHECK=$(pip check --cache-dir=$PIP_CACHE) LINE_COUNT=$(echo "$PIP_CHECK" | wc -l) From 69a004a2f6a5f026fee519d3375805d6c5ae937b Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 3 Feb 2022 12:28:04 +0100 Subject: [PATCH 0214/1098] Netgear coordinator (#65255) * implement coordinator * fix styling * fix async_uload_entry * use async_config_entry_first_refresh * use const * fix black * use KEY_ROUTER * review comments * fix black * ensure the coordinator keeps updating * fix flake8 * rework setup of entities using coordinator * styling * check for failed get_info call * fix * fix setup of entities * simplify * do not set unique_id and device_info on scanner entity * Update homeassistant/components/netgear/sensor.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/netgear/device_tracker.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/netgear/router.py Co-authored-by: Martin Hjelmare * use entry_id instead of unique_id * unused import Co-authored-by: Martin Hjelmare --- homeassistant/components/netgear/__init__.py | 33 ++++- homeassistant/components/netgear/const.py | 7 +- .../components/netgear/device_tracker.py | 42 ++++-- homeassistant/components/netgear/router.py | 134 ++++++------------ homeassistant/components/netgear/sensor.py | 54 +++++-- 5 files changed, 149 insertions(+), 121 deletions(-) diff --git a/homeassistant/components/netgear/__init__.py b/homeassistant/components/netgear/__init__.py index 26c7f4f1a1a39c..919cf25ae82552 100644 --- a/homeassistant/components/netgear/__init__.py +++ b/homeassistant/components/netgear/__init__.py @@ -1,4 +1,5 @@ """Support for Netgear routers.""" +from datetime import timedelta import logging from homeassistant.config_entries import ConfigEntry @@ -6,19 +7,23 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from .const import DOMAIN, PLATFORMS +from .const import DOMAIN, KEY_COORDINATOR, KEY_ROUTER, PLATFORMS from .errors import CannotLoginException from .router import NetgearRouter _LOGGER = logging.getLogger(__name__) +SCAN_INTERVAL = timedelta(seconds=30) + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Netgear component.""" router = NetgearRouter(hass, entry) try: - await router.async_setup() + if not await router.async_setup(): + raise ConfigEntryNotReady except CannotLoginException as ex: raise ConfigEntryNotReady from ex @@ -37,7 +42,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) hass.data.setdefault(DOMAIN, {}) - hass.data[DOMAIN][entry.unique_id] = router entry.async_on_unload(entry.add_update_listener(update_listener)) @@ -52,6 +56,27 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: configuration_url=f"http://{entry.data[CONF_HOST]}/", ) + async def async_update_data() -> bool: + """Fetch data from the router.""" + data = await router.async_update_device_trackers() + return data + + # Create update coordinator + coordinator = DataUpdateCoordinator( + hass, + _LOGGER, + name=router.device_name, + update_method=async_update_data, + update_interval=SCAN_INTERVAL, + ) + + await coordinator.async_config_entry_first_refresh() + + hass.data[DOMAIN][entry.entry_id] = { + KEY_ROUTER: router, + KEY_COORDINATOR: coordinator, + } + hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True @@ -62,7 +87,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: - hass.data[DOMAIN].pop(entry.unique_id) + hass.data[DOMAIN].pop(entry.entry_id) if not hass.data[DOMAIN]: hass.data.pop(DOMAIN) diff --git a/homeassistant/components/netgear/const.py b/homeassistant/components/netgear/const.py index 5f8944f96d6738..b1d5dd229425dd 100644 --- a/homeassistant/components/netgear/const.py +++ b/homeassistant/components/netgear/const.py @@ -5,10 +5,13 @@ DOMAIN = "netgear" -PLATFORMS = [Platform.DEVICE_TRACKER, Platform.SENSOR] - CONF_CONSIDER_HOME = "consider_home" +KEY_ROUTER = "router" +KEY_COORDINATOR = "coordinator" + +PLATFORMS = [Platform.DEVICE_TRACKER, Platform.SENSOR] + DEFAULT_CONSIDER_HOME = timedelta(seconds=180) DEFAULT_NAME = "Netgear router" diff --git a/homeassistant/components/netgear/device_tracker.py b/homeassistant/components/netgear/device_tracker.py index e3beb005845d7a..72699768f84f90 100644 --- a/homeassistant/components/netgear/device_tracker.py +++ b/homeassistant/components/netgear/device_tracker.py @@ -8,9 +8,10 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from .const import DEVICE_ICONS -from .router import NetgearDeviceEntity, NetgearRouter, async_setup_netgear_entry +from .const import DEVICE_ICONS, DOMAIN, KEY_COORDINATOR, KEY_ROUTER +from .router import NetgearBaseEntity, NetgearRouter _LOGGER = logging.getLogger(__name__) @@ -19,19 +20,42 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up device tracker for Netgear component.""" + router = hass.data[DOMAIN][entry.entry_id][KEY_ROUTER] + coordinator = hass.data[DOMAIN][entry.entry_id][KEY_COORDINATOR] + tracked = set() - def generate_classes(router: NetgearRouter, device: dict): - return [NetgearScannerEntity(router, device)] + @callback + def new_device_callback() -> None: + """Add new devices if needed.""" + if not coordinator.data: + return + + new_entities = [] + + for mac, device in router.devices.items(): + if mac in tracked: + continue + + new_entities.append(NetgearScannerEntity(coordinator, router, device)) + tracked.add(mac) - async_setup_netgear_entry(hass, entry, async_add_entities, generate_classes) + if new_entities: + async_add_entities(new_entities) + entry.async_on_unload(coordinator.async_add_listener(new_device_callback)) -class NetgearScannerEntity(NetgearDeviceEntity, ScannerEntity): + coordinator.data = True + new_device_callback() + + +class NetgearScannerEntity(NetgearBaseEntity, ScannerEntity): """Representation of a device connected to a Netgear router.""" - def __init__(self, router: NetgearRouter, device: dict) -> None: + def __init__( + self, coordinator: DataUpdateCoordinator, router: NetgearRouter, device: dict + ) -> None: """Initialize a Netgear device.""" - super().__init__(router, device) + super().__init__(coordinator, router, device) self._hostname = self.get_hostname() self._icon = DEVICE_ICONS.get(device["device_type"], "mdi:help-network") @@ -49,8 +73,6 @@ def async_update_device(self) -> None: self._active = self._device["active"] self._icon = DEVICE_ICONS.get(self._device["device_type"], "mdi:help-network") - self.async_write_ha_state() - @property def is_connected(self): """Return true if the device is connected to the router.""" diff --git a/homeassistant/components/netgear/router.py b/homeassistant/components/netgear/router.py index 5226218a623821..d275aaf7fb25c3 100644 --- a/homeassistant/components/netgear/router.py +++ b/homeassistant/components/netgear/router.py @@ -2,7 +2,6 @@ from __future__ import annotations from abc import abstractmethod -from collections.abc import Callable from datetime import timedelta import logging @@ -19,13 +18,11 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, format_mac -from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, - async_dispatcher_send, +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, ) -from homeassistant.helpers.entity import DeviceInfo, Entity -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.event import async_track_time_interval from homeassistant.util import dt as dt_util from .const import ( @@ -37,8 +34,6 @@ ) from .errors import CannotLoginException -SCAN_INTERVAL = timedelta(seconds=30) - _LOGGER = logging.getLogger(__name__) @@ -58,47 +53,6 @@ def get_api( return api -@callback -def async_setup_netgear_entry( - hass: HomeAssistant, - entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, - entity_class_generator: Callable[[NetgearRouter, dict], list], -) -> None: - """Set up device tracker for Netgear component.""" - router = hass.data[DOMAIN][entry.unique_id] - tracked = set() - - @callback - def _async_router_updated(): - """Update the values of the router.""" - async_add_new_entities( - router, async_add_entities, tracked, entity_class_generator - ) - - entry.async_on_unload( - async_dispatcher_connect(hass, router.signal_device_new, _async_router_updated) - ) - - _async_router_updated() - - -@callback -def async_add_new_entities(router, async_add_entities, tracked, entity_class_generator): - """Add new tracker entities from the router.""" - new_tracked = [] - - for mac, device in router.devices.items(): - if mac in tracked: - continue - - new_tracked.extend(entity_class_generator(router, device)) - tracked.add(mac) - - if new_tracked: - async_add_entities(new_tracked, True) - - class NetgearRouter: """Representation of a Netgear router.""" @@ -141,6 +95,9 @@ def _setup(self) -> None: ) self._info = self._api.get_info() + if self._info is None: + return False + self.device_name = self._info.get("DeviceName", DEFAULT_NAME) self.model = self._info.get("ModelName") self.firmware_version = self._info.get("Firmwareversion") @@ -157,9 +114,12 @@ def _setup(self) -> None: ) self.method_version = 1 - async def async_setup(self) -> None: + return True + + async def async_setup(self) -> bool: """Set up a Netgear router.""" - await self.hass.async_add_executor_job(self._setup) + if not await self.hass.async_add_executor_job(self._setup): + return False # set already known devices to away instead of unavailable device_registry = dr.async_get(self.hass) @@ -184,14 +144,7 @@ async def async_setup(self) -> None: "conn_ap_mac": None, } - await self.async_update_device_trackers() - self.entry.async_on_unload( - async_track_time_interval( - self.hass, self.async_update_device_trackers, SCAN_INTERVAL - ) - ) - - async_dispatcher_send(self.hass, self.signal_device_new) + return True async def async_get_attached_devices(self) -> list: """Get the devices connected to the router.""" @@ -228,21 +181,10 @@ async def async_update_device_trackers(self, now=None) -> None: for device in self.devices.values(): device["active"] = now - device["last_seen"] <= self._consider_home - async_dispatcher_send(self.hass, self.signal_device_update) - if new_device: _LOGGER.debug("Netgear tracker: new device found") - async_dispatcher_send(self.hass, self.signal_device_new) - @property - def signal_device_new(self) -> str: - """Event specific per Netgear entry to signal new device.""" - return f"{DOMAIN}-{self._host}-device-new" - - @property - def signal_device_update(self) -> str: - """Event specific per Netgear entry to signal updates in devices.""" - return f"{DOMAIN}-{self._host}-device-update" + return new_device @property def port(self) -> int: @@ -255,17 +197,19 @@ def ssl(self) -> bool: return self._api.ssl -class NetgearDeviceEntity(Entity): +class NetgearBaseEntity(CoordinatorEntity): """Base class for a device connected to a Netgear router.""" - def __init__(self, router: NetgearRouter, device: dict) -> None: + def __init__( + self, coordinator: DataUpdateCoordinator, router: NetgearRouter, device: dict + ) -> None: """Initialize a Netgear device.""" + super().__init__(coordinator) self._router = router self._device = device self._mac = device["mac"] self._name = self.get_device_name() self._device_name = self._name - self._unique_id = self._mac self._active = device["active"] def get_device_name(self): @@ -281,16 +225,33 @@ def get_device_name(self): def async_update_device(self) -> None: """Update the Netgear device.""" - @property - def unique_id(self) -> str: - """Return a unique ID.""" - return self._unique_id + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self.async_update_device() + super()._handle_coordinator_update() @property def name(self) -> str: """Return the name.""" return self._name + +class NetgearDeviceEntity(NetgearBaseEntity): + """Base class for a device connected to a Netgear router.""" + + def __init__( + self, coordinator: DataUpdateCoordinator, router: NetgearRouter, device: dict + ) -> None: + """Initialize a Netgear device.""" + super().__init__(coordinator, router, device) + self._unique_id = self._mac + + @property + def unique_id(self) -> str: + """Return a unique ID.""" + return self._unique_id + @property def device_info(self) -> DeviceInfo: """Return the device information.""" @@ -300,18 +261,3 @@ def device_info(self) -> DeviceInfo: default_model=self._device["device_model"], via_device=(DOMAIN, self._router.unique_id), ) - - @property - def should_poll(self) -> bool: - """No polling needed.""" - return False - - async def async_added_to_hass(self): - """Register state update callback.""" - self.async_on_remove( - async_dispatcher_connect( - self.hass, - self._router.signal_device_update, - self.async_update_device, - ) - ) diff --git a/homeassistant/components/netgear/sensor.py b/homeassistant/components/netgear/sensor.py index 227e6e28b092aa..0db0e4f19f4868 100644 --- a/homeassistant/components/netgear/sensor.py +++ b/homeassistant/components/netgear/sensor.py @@ -9,8 +9,10 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from .router import NetgearDeviceEntity, NetgearRouter, async_setup_netgear_entry +from .const import DOMAIN, KEY_COORDINATOR, KEY_ROUTER +from .router import NetgearDeviceEntity, NetgearRouter SENSOR_TYPES = { "type": SensorEntityDescription( @@ -48,15 +50,41 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up device tracker for Netgear component.""" + router = hass.data[DOMAIN][entry.entry_id][KEY_ROUTER] + coordinator = hass.data[DOMAIN][entry.entry_id][KEY_COORDINATOR] + tracked = set() - def generate_sensor_classes(router: NetgearRouter, device: dict): - sensors = ["type", "link_rate", "signal"] - if router.method_version == 2: - sensors.extend(["ssid", "conn_ap_mac"]) + sensors = ["type", "link_rate", "signal"] + if router.method_version == 2: + sensors.extend(["ssid", "conn_ap_mac"]) - return [NetgearSensorEntity(router, device, attribute) for attribute in sensors] + @callback + def new_device_callback() -> None: + """Add new devices if needed.""" + if not coordinator.data: + return + + new_entities = [] + + for mac, device in router.devices.items(): + if mac in tracked: + continue + + new_entities.extend( + [ + NetgearSensorEntity(coordinator, router, device, attribute) + for attribute in sensors + ] + ) + tracked.add(mac) - async_setup_netgear_entry(hass, entry, async_add_entities, generate_sensor_classes) + if new_entities: + async_add_entities(new_entities) + + entry.async_on_unload(coordinator.async_add_listener(new_device_callback)) + + coordinator.data = True + new_device_callback() class NetgearSensorEntity(NetgearDeviceEntity, SensorEntity): @@ -64,9 +92,15 @@ class NetgearSensorEntity(NetgearDeviceEntity, SensorEntity): _attr_entity_registry_enabled_default = False - def __init__(self, router: NetgearRouter, device: dict, attribute: str) -> None: + def __init__( + self, + coordinator: DataUpdateCoordinator, + router: NetgearRouter, + device: dict, + attribute: str, + ) -> None: """Initialize a Netgear device.""" - super().__init__(router, device) + super().__init__(coordinator, router, device) self._attribute = attribute self.entity_description = SENSOR_TYPES[self._attribute] self._name = f"{self.get_device_name()} {self.entity_description.name}" @@ -85,5 +119,3 @@ def async_update_device(self) -> None: self._active = self._device["active"] if self._device.get(self._attribute) is not None: self._state = self._device[self._attribute] - - self.async_write_ha_state() From 770707f48764ebb0e0c80d723052874dbde25db7 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 3 Feb 2022 06:22:34 -0600 Subject: [PATCH 0215/1098] Fix vanished checks on old Sonos firmware (#65477) --- homeassistant/components/sonos/speaker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index 7777265a124cc6..b5ae20e1123742 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -701,7 +701,7 @@ def async_update_groups(self, event: SonosEvent) -> None: """Handle callback for topology change event.""" if xml := event.variables.get("zone_group_state"): zgs = ET.fromstring(xml) - for vanished_device in zgs.find("VanishedDevices"): + for vanished_device in zgs.find("VanishedDevices") or []: if (reason := vanished_device.get("Reason")) != "sleeping": _LOGGER.debug( "Ignoring %s marked %s as vanished with reason: %s", From 3e0856ccac9ac6c779b8c8db189574ff774a9921 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 3 Feb 2022 14:00:03 +0100 Subject: [PATCH 0216/1098] Fix missing windspeed in Tuya climate (#65511) --- homeassistant/components/tuya/climate.py | 4 +++- homeassistant/components/tuya/const.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/tuya/climate.py b/homeassistant/components/tuya/climate.py index cbb778e34b1199..a97a27a7453280 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -223,7 +223,9 @@ def __init__( # Determine fan modes if enum_type := self.find_dpcode( - DPCode.FAN_SPEED_ENUM, dptype=DPType.ENUM, prefer_function=True + (DPCode.FAN_SPEED_ENUM, DPCode.WINDSPEED), + dptype=DPType.ENUM, + prefer_function=True, ): self._attr_supported_features |= SUPPORT_FAN_MODE self._attr_fan_modes = enum_type.range diff --git a/homeassistant/components/tuya/const.py b/homeassistant/components/tuya/const.py index 79b01140875877..7765cd27322b09 100644 --- a/homeassistant/components/tuya/const.py +++ b/homeassistant/components/tuya/const.py @@ -367,6 +367,7 @@ class DPCode(StrEnum): WATER_SET = "water_set" # Water level WATERSENSOR_STATE = "watersensor_state" WET = "wet" # Humidification + WINDSPEED = "windspeed" WIRELESS_BATTERYLOCK = "wireless_batterylock" WIRELESS_ELECTRICITY = "wireless_electricity" WORK_MODE = "work_mode" # Working mode From 63680f0b36722f4dd2e3c2eb4efcd7b123978717 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 3 Feb 2022 14:06:40 +0100 Subject: [PATCH 0217/1098] Make util.async_.protect_loop name names (#65493) --- homeassistant/util/async_.py | 5 +++-- tests/util/test_async.py | 15 ++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py index 8f9526b6800696..b898efe49fe56b 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -127,7 +127,7 @@ def check_loop(func: Callable, strict: bool = True) -> None: # Did not source from integration? Hard error. if found_frame is None: raise RuntimeError( - "Detected blocking call inside the event loop. " + f"Detected blocking call to {func.__name__} inside the event loop. " "This is causing stability issues. Please report issue" ) @@ -142,8 +142,9 @@ def check_loop(func: Callable, strict: bool = True) -> None: extra = "" _LOGGER.warning( - "Detected blocking call inside the event loop. This is causing stability issues. " + "Detected blocking call to %s inside the event loop. This is causing stability issues. " "Please report issue%s for %s doing blocking calls at %s, line %s: %s", + func.__name__, extra, integration, found_frame.filename[index:], diff --git a/tests/util/test_async.py b/tests/util/test_async.py index f02d3c03b4b713..9bae6f5ebea093 100644 --- a/tests/util/test_async.py +++ b/tests/util/test_async.py @@ -105,8 +105,8 @@ async def test_check_loop_async_integration(caplog): ): hasync.check_loop(banned_function) assert ( - "Detected blocking call inside the event loop. This is causing stability issues. " - "Please report issue for hue doing blocking calls at " + "Detected blocking call to banned_function inside the event loop. This is " + "causing stability issues. Please report issue for hue doing blocking calls at " "homeassistant/components/hue/light.py, line 23: self.light.is_on" in caplog.text ) @@ -136,8 +136,8 @@ async def test_check_loop_async_integration_non_strict(caplog): ): hasync.check_loop(banned_function, strict=False) assert ( - "Detected blocking call inside the event loop. This is causing stability issues. " - "Please report issue for hue doing blocking calls at " + "Detected blocking call to banned_function inside the event loop. This is " + "causing stability issues. Please report issue for hue doing blocking calls at " "homeassistant/components/hue/light.py, line 23: self.light.is_on" in caplog.text ) @@ -167,9 +167,10 @@ async def test_check_loop_async_custom(caplog): ): hasync.check_loop(banned_function) assert ( - "Detected blocking call inside the event loop. This is causing stability issues. " - "Please report issue to the custom component author for hue doing blocking calls " - "at custom_components/hue/light.py, line 23: self.light.is_on" in caplog.text + "Detected blocking call to banned_function inside the event loop. This is " + "causing stability issues. Please report issue to the custom component author " + "for hue doing blocking calls at custom_components/hue/light.py, line 23: " + "self.light.is_on" in caplog.text ) From 3f57adf4754999e8d3b01f45f7717df1909d02e2 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Thu, 3 Feb 2022 14:11:53 +0100 Subject: [PATCH 0218/1098] Code quality custom service for sensibo (#65496) --- homeassistant/components/sensibo/climate.py | 38 ++++--------------- .../components/sensibo/services.yaml | 11 ++---- 2 files changed, 12 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index b711bca5ac3f16..16d7f8601c9b95 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -26,7 +26,6 @@ ) from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_STATE, ATTR_TEMPERATURE, CONF_API_KEY, @@ -34,9 +33,9 @@ TEMP_CELSIUS, TEMP_FAHRENHEIT, ) -from homeassistant.core import HomeAssistant, ServiceCall +from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -55,10 +54,6 @@ } ) -ASSUME_STATE_SCHEMA = vol.Schema( - {vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, vol.Required(ATTR_STATE): cv.string} -) - FIELD_TO_FLAG = { "fanLevel": SUPPORT_FAN_MODE, "swing": SUPPORT_SWING_MODE, @@ -112,28 +107,13 @@ async def async_setup_entry( async_add_entities(entities) - async def async_assume_state(service: ServiceCall) -> None: - """Set state according to external service call..""" - if entity_ids := service.data.get(ATTR_ENTITY_ID): - target_climate = [ - entity for entity in entities if entity.entity_id in entity_ids - ] - else: - target_climate = entities - - update_tasks = [] - for climate in target_climate: - await climate.async_assume_state(service.data.get(ATTR_STATE)) - update_tasks.append(climate.async_update_ha_state(True)) - - if update_tasks: - await asyncio.wait(update_tasks) - - hass.services.async_register( - DOMAIN, + platform = entity_platform.async_get_current_platform() + platform.async_register_entity_service( SERVICE_ASSUME_STATE, - async_assume_state, - schema=ASSUME_STATE_SCHEMA, + { + vol.Required(ATTR_STATE): vol.In(["on", "off"]), + }, + "async_assume_state", ) @@ -364,7 +344,5 @@ async def _async_set_ac_state_property( async def async_assume_state(self, state) -> None: """Sync state with api.""" - if state == self.state or (state == "on" and self.state != HVAC_MODE_OFF): - return await self._async_set_ac_state_property("on", state != HVAC_MODE_OFF, True) await self.coordinator.async_refresh() diff --git a/homeassistant/components/sensibo/services.yaml b/homeassistant/components/sensibo/services.yaml index 6438ea37e189f3..bbbdb8611e87b5 100644 --- a/homeassistant/components/sensibo/services.yaml +++ b/homeassistant/components/sensibo/services.yaml @@ -1,14 +1,11 @@ assume_state: name: Assume state description: Set Sensibo device to external state. + target: + entity: + integration: sensibo + domain: climate fields: - entity_id: - name: Entity - description: Name(s) of entities to change. - selector: - entity: - integration: sensibo - domain: climate state: name: State description: State to set. From 8234625d306a3f55b50fbe0ffd177669d84bd2d1 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 3 Feb 2022 14:16:35 +0100 Subject: [PATCH 0219/1098] Use Domain not Platform on test service calls (#65508) * Adjust atag * adjust broadlink * Adjust onewire * Adjust plex * Adjust renault * Adjust tasmota * Adjust zha Co-authored-by: epenet --- tests/components/atag/test_climate.py | 7 +- tests/components/atag/test_water_heater.py | 7 +- tests/components/broadlink/test_remote.py | 11 +-- tests/components/onewire/test_switch.py | 3 +- tests/components/plex/test_media_search.py | 41 +++++------ tests/components/renault/test_button.py | 6 +- tests/components/renault/test_select.py | 8 ++- tests/components/tasmota/test_cover.py | 2 +- .../zha/test_alarm_control_panel.py | 15 ++-- tests/components/zha/test_climate.py | 71 ++++++++++--------- tests/components/zha/test_cover.py | 27 +++---- tests/components/zha/test_fan.py | 3 +- tests/components/zha/test_light.py | 18 +++-- tests/components/zha/test_lock.py | 5 +- tests/components/zha/test_number.py | 3 +- tests/components/zha/test_siren.py | 7 +- tests/components/zha/test_switch.py | 9 +-- 17 files changed, 133 insertions(+), 110 deletions(-) diff --git a/tests/components/atag/test_climate.py b/tests/components/atag/test_climate.py index ba6bc892e40c28..8fb7730a4e4a94 100644 --- a/tests/components/atag/test_climate.py +++ b/tests/components/atag/test_climate.py @@ -6,6 +6,7 @@ ATTR_HVAC_ACTION, ATTR_HVAC_MODE, ATTR_PRESET_MODE, + DOMAIN as CLIMATE_DOMAIN, HVAC_MODE_HEAT, SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE, @@ -49,7 +50,7 @@ async def test_setting_climate( await init_integration(hass, aioclient_mock) with patch("pyatag.entities.Climate.set_temp") as mock_set_temp: await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, {ATTR_ENTITY_ID: CLIMATE_ID, ATTR_TEMPERATURE: 15}, blocking=True, @@ -59,7 +60,7 @@ async def test_setting_climate( with patch("pyatag.entities.Climate.set_preset_mode") as mock_set_preset: await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: CLIMATE_ID, ATTR_PRESET_MODE: PRESET_AWAY}, blocking=True, @@ -69,7 +70,7 @@ async def test_setting_climate( with patch("pyatag.entities.Climate.set_hvac_mode") as mock_set_hvac: await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, {ATTR_ENTITY_ID: CLIMATE_ID, ATTR_HVAC_MODE: HVAC_MODE_HEAT}, blocking=True, diff --git a/tests/components/atag/test_water_heater.py b/tests/components/atag/test_water_heater.py index df83fa6d40be6a..3372e8c69fa5da 100644 --- a/tests/components/atag/test_water_heater.py +++ b/tests/components/atag/test_water_heater.py @@ -2,7 +2,10 @@ from unittest.mock import patch from homeassistant.components.atag import DOMAIN -from homeassistant.components.water_heater import SERVICE_SET_TEMPERATURE +from homeassistant.components.water_heater import ( + DOMAIN as WATER_HEATER_DOMAIN, + SERVICE_SET_TEMPERATURE, +) from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -33,7 +36,7 @@ async def test_setting_target_temperature( await init_integration(hass, aioclient_mock) with patch("pyatag.entities.DHW.set_temp") as mock_set_temp: await hass.services.async_call( - Platform.WATER_HEATER, + WATER_HEATER_DOMAIN, SERVICE_SET_TEMPERATURE, {ATTR_ENTITY_ID: WATER_HEATER_ID, ATTR_TEMPERATURE: 50}, blocking=True, diff --git a/tests/components/broadlink/test_remote.py b/tests/components/broadlink/test_remote.py index 3c97f8ea47a9eb..a3b291efd0021b 100644 --- a/tests/components/broadlink/test_remote.py +++ b/tests/components/broadlink/test_remote.py @@ -4,6 +4,7 @@ from homeassistant.components.broadlink.const import DOMAIN from homeassistant.components.remote import ( + DOMAIN as REMOTE_DOMAIN, SERVICE_SEND_COMMAND, SERVICE_TURN_OFF, SERVICE_TURN_ON, @@ -59,7 +60,7 @@ async def test_remote_send_command(hass): remote = remotes[0] await hass.services.async_call( - Platform.REMOTE, + REMOTE_DOMAIN, SERVICE_SEND_COMMAND, {"entity_id": remote.entity_id, "command": "b64:" + IR_PACKET}, blocking=True, @@ -86,7 +87,7 @@ async def test_remote_turn_off_turn_on(hass): remote = remotes[0] await hass.services.async_call( - Platform.REMOTE, + REMOTE_DOMAIN, SERVICE_TURN_OFF, {"entity_id": remote.entity_id}, blocking=True, @@ -94,7 +95,7 @@ async def test_remote_turn_off_turn_on(hass): assert hass.states.get(remote.entity_id).state == STATE_OFF await hass.services.async_call( - Platform.REMOTE, + REMOTE_DOMAIN, SERVICE_SEND_COMMAND, {"entity_id": remote.entity_id, "command": "b64:" + IR_PACKET}, blocking=True, @@ -102,7 +103,7 @@ async def test_remote_turn_off_turn_on(hass): assert mock_setup.api.send_data.call_count == 0 await hass.services.async_call( - Platform.REMOTE, + REMOTE_DOMAIN, SERVICE_TURN_ON, {"entity_id": remote.entity_id}, blocking=True, @@ -110,7 +111,7 @@ async def test_remote_turn_off_turn_on(hass): assert hass.states.get(remote.entity_id).state == STATE_ON await hass.services.async_call( - Platform.REMOTE, + REMOTE_DOMAIN, SERVICE_SEND_COMMAND, {"entity_id": remote.entity_id, "command": "b64:" + IR_PACKET}, blocking=True, diff --git a/tests/components/onewire/test_switch.py b/tests/components/onewire/test_switch.py index 336dafb15a1fc8..32212f84b34fca 100644 --- a/tests/components/onewire/test_switch.py +++ b/tests/components/onewire/test_switch.py @@ -4,6 +4,7 @@ import pytest +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_ENTITY_ID, @@ -83,7 +84,7 @@ async def test_owserver_switch( expected_entity[ATTR_STATE] = STATE_ON await hass.services.async_call( - Platform.SWITCH, + SWITCH_DOMAIN, SERVICE_TOGGLE, {ATTR_ENTITY_ID: entity_id}, blocking=True, diff --git a/tests/components/plex/test_media_search.py b/tests/components/plex/test_media_search.py index adfdff2d1dcbed..f73fdea2806a96 100644 --- a/tests/components/plex/test_media_search.py +++ b/tests/components/plex/test_media_search.py @@ -7,6 +7,7 @@ from homeassistant.components.media_player.const import ( ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, + DOMAIN as MEDIA_PLAYER_DOMAIN, MEDIA_TYPE_EPISODE, MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, @@ -15,7 +16,7 @@ SERVICE_PLAY_MEDIA, ) from homeassistant.components.plex.const import DOMAIN -from homeassistant.const import ATTR_ENTITY_ID, Platform +from homeassistant.const import ATTR_ENTITY_ID from homeassistant.exceptions import HomeAssistantError @@ -29,7 +30,7 @@ async def test_media_lookups( requests_mock.get("/player/playback/playMedia", status_code=200) assert await hass.services.async_call( - Platform.MEDIA_PLAYER, + MEDIA_PLAYER_DOMAIN, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: media_player_id, @@ -41,7 +42,7 @@ async def test_media_lookups( with pytest.raises(HomeAssistantError) as excinfo: with patch("plexapi.server.PlexServer.fetchItem", side_effect=NotFound): assert await hass.services.async_call( - Platform.MEDIA_PLAYER, + MEDIA_PLAYER_DOMAIN, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: media_player_id, @@ -56,7 +57,7 @@ async def test_media_lookups( with pytest.raises(HomeAssistantError) as excinfo: payload = '{"library_name": "Not a Library", "show_name": "TV Show"}' assert await hass.services.async_call( - Platform.MEDIA_PLAYER, + MEDIA_PLAYER_DOMAIN, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: media_player_id, @@ -69,7 +70,7 @@ async def test_media_lookups( with patch("plexapi.library.LibrarySection.search") as search: assert await hass.services.async_call( - Platform.MEDIA_PLAYER, + MEDIA_PLAYER_DOMAIN, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: media_player_id, @@ -81,7 +82,7 @@ async def test_media_lookups( search.assert_called_with(**{"show.title": "TV Show", "libtype": "show"}) assert await hass.services.async_call( - Platform.MEDIA_PLAYER, + MEDIA_PLAYER_DOMAIN, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: media_player_id, @@ -95,7 +96,7 @@ async def test_media_lookups( ) assert await hass.services.async_call( - Platform.MEDIA_PLAYER, + MEDIA_PLAYER_DOMAIN, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: media_player_id, @@ -109,7 +110,7 @@ async def test_media_lookups( ) assert await hass.services.async_call( - Platform.MEDIA_PLAYER, + MEDIA_PLAYER_DOMAIN, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: media_player_id, @@ -128,7 +129,7 @@ async def test_media_lookups( ) assert await hass.services.async_call( - Platform.MEDIA_PLAYER, + MEDIA_PLAYER_DOMAIN, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: media_player_id, @@ -140,7 +141,7 @@ async def test_media_lookups( search.assert_called_with(**{"artist.title": "Artist", "libtype": "artist"}) assert await hass.services.async_call( - Platform.MEDIA_PLAYER, + MEDIA_PLAYER_DOMAIN, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: media_player_id, @@ -152,7 +153,7 @@ async def test_media_lookups( search.assert_called_with(**{"album.title": "Album", "libtype": "album"}) assert await hass.services.async_call( - Platform.MEDIA_PLAYER, + MEDIA_PLAYER_DOMAIN, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: media_player_id, @@ -166,7 +167,7 @@ async def test_media_lookups( ) assert await hass.services.async_call( - Platform.MEDIA_PLAYER, + MEDIA_PLAYER_DOMAIN, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: media_player_id, @@ -180,7 +181,7 @@ async def test_media_lookups( ) assert await hass.services.async_call( - Platform.MEDIA_PLAYER, + MEDIA_PLAYER_DOMAIN, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: media_player_id, @@ -199,7 +200,7 @@ async def test_media_lookups( ) assert await hass.services.async_call( - Platform.MEDIA_PLAYER, + MEDIA_PLAYER_DOMAIN, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: media_player_id, @@ -219,7 +220,7 @@ async def test_media_lookups( # Movie searches assert await hass.services.async_call( - Platform.MEDIA_PLAYER, + MEDIA_PLAYER_DOMAIN, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: media_player_id, @@ -231,7 +232,7 @@ async def test_media_lookups( search.assert_called_with(**{"movie.title": "Movie 1", "libtype": None}) assert await hass.services.async_call( - Platform.MEDIA_PLAYER, + MEDIA_PLAYER_DOMAIN, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: media_player_id, @@ -247,7 +248,7 @@ async def test_media_lookups( payload = '{"library_name": "Movies", "title": "Not a Movie"}' with patch("plexapi.library.LibrarySection.search", side_effect=BadRequest): assert await hass.services.async_call( - Platform.MEDIA_PLAYER, + MEDIA_PLAYER_DOMAIN, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: media_player_id, @@ -261,7 +262,7 @@ async def test_media_lookups( # Playlist searches assert await hass.services.async_call( - Platform.MEDIA_PLAYER, + MEDIA_PLAYER_DOMAIN, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: media_player_id, @@ -274,7 +275,7 @@ async def test_media_lookups( with pytest.raises(HomeAssistantError) as excinfo: payload = '{"playlist_name": "Not a Playlist"}' assert await hass.services.async_call( - Platform.MEDIA_PLAYER, + MEDIA_PLAYER_DOMAIN, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: media_player_id, @@ -289,7 +290,7 @@ async def test_media_lookups( with pytest.raises(HomeAssistantError) as excinfo: payload = "{}" assert await hass.services.async_call( - Platform.MEDIA_PLAYER, + MEDIA_PLAYER_DOMAIN, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: media_player_id, diff --git a/tests/components/renault/test_button.py b/tests/components/renault/test_button.py index cf6fc1902e911d..6ed50a833f11aa 100644 --- a/tests/components/renault/test_button.py +++ b/tests/components/renault/test_button.py @@ -4,7 +4,7 @@ import pytest from renault_api.kamereon import schemas -from homeassistant.components.button.const import SERVICE_PRESS +from homeassistant.components.button.const import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_UNKNOWN, Platform from homeassistant.core import HomeAssistant @@ -150,7 +150,7 @@ async def test_button_start_charge(hass: HomeAssistant, config_entry: ConfigEntr ), ) as mock_action: await hass.services.async_call( - Platform.BUTTON, SERVICE_PRESS, service_data=data, blocking=True + BUTTON_DOMAIN, SERVICE_PRESS, service_data=data, blocking=True ) assert len(mock_action.mock_calls) == 1 assert mock_action.mock_calls[0][1] == () @@ -178,7 +178,7 @@ async def test_button_start_air_conditioner( ), ) as mock_action: await hass.services.async_call( - Platform.BUTTON, SERVICE_PRESS, service_data=data, blocking=True + BUTTON_DOMAIN, SERVICE_PRESS, service_data=data, blocking=True ) assert len(mock_action.mock_calls) == 1 assert mock_action.mock_calls[0][1] == (21, None) diff --git a/tests/components/renault/test_select.py b/tests/components/renault/test_select.py index e0cb4413a7e3ef..18c2eed8a7c3f1 100644 --- a/tests/components/renault/test_select.py +++ b/tests/components/renault/test_select.py @@ -4,7 +4,11 @@ import pytest from renault_api.kamereon import schemas -from homeassistant.components.select.const import ATTR_OPTION, SERVICE_SELECT_OPTION +from homeassistant.components.select.const import ( + ATTR_OPTION, + DOMAIN as SELECT_DOMAIN, + SERVICE_SELECT_OPTION, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN, Platform from homeassistant.core import HomeAssistant @@ -145,7 +149,7 @@ async def test_select_charge_mode(hass: HomeAssistant, config_entry: ConfigEntry ), ) as mock_action: await hass.services.async_call( - Platform.SELECT, SERVICE_SELECT_OPTION, service_data=data, blocking=True + SELECT_DOMAIN, SERVICE_SELECT_OPTION, service_data=data, blocking=True ) assert len(mock_action.mock_calls) == 1 assert mock_action.mock_calls[0][1] == ("always",) diff --git a/tests/components/tasmota/test_cover.py b/tests/components/tasmota/test_cover.py index 80bf14943a9fd1..e88df08a80c733 100644 --- a/tests/components/tasmota/test_cover.py +++ b/tests/components/tasmota/test_cover.py @@ -392,7 +392,7 @@ async def test_controlling_state_via_mqtt_inverted(hass, mqtt_mock, setup_tasmot async def call_service(hass, entity_id, service, **kwargs): """Call a fan service.""" await hass.services.async_call( - Platform.COVER, + cover.DOMAIN, service, {"entity_id": entity_id, **kwargs}, blocking=True, diff --git a/tests/components/zha/test_alarm_control_panel.py b/tests/components/zha/test_alarm_control_panel.py index 84e66f0833a0d8..e3742a3132f6a8 100644 --- a/tests/components/zha/test_alarm_control_panel.py +++ b/tests/components/zha/test_alarm_control_panel.py @@ -6,6 +6,7 @@ import zigpy.zcl.clusters.security as security import zigpy.zcl.foundation as zcl_f +from homeassistant.components.alarm_control_panel import DOMAIN as ALARM_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, STATE_ALARM_ARMED_AWAY, @@ -62,7 +63,7 @@ async def test_alarm_control_panel(hass, zha_device_joined_restored, zigpy_devic # arm_away from HA cluster.client_command.reset_mock() await hass.services.async_call( - Platform.ALARM_CONTROL_PANEL, + ALARM_DOMAIN, "alarm_arm_away", {ATTR_ENTITY_ID: entity_id}, blocking=True, @@ -85,7 +86,7 @@ async def test_alarm_control_panel(hass, zha_device_joined_restored, zigpy_devic # trip alarm from faulty code entry cluster.client_command.reset_mock() await hass.services.async_call( - Platform.ALARM_CONTROL_PANEL, + ALARM_DOMAIN, "alarm_arm_away", {ATTR_ENTITY_ID: entity_id}, blocking=True, @@ -94,13 +95,13 @@ async def test_alarm_control_panel(hass, zha_device_joined_restored, zigpy_devic assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY cluster.client_command.reset_mock() await hass.services.async_call( - Platform.ALARM_CONTROL_PANEL, + ALARM_DOMAIN, "alarm_disarm", {ATTR_ENTITY_ID: entity_id, "code": "1111"}, blocking=True, ) await hass.services.async_call( - Platform.ALARM_CONTROL_PANEL, + ALARM_DOMAIN, "alarm_disarm", {ATTR_ENTITY_ID: entity_id, "code": "1111"}, blocking=True, @@ -123,7 +124,7 @@ async def test_alarm_control_panel(hass, zha_device_joined_restored, zigpy_devic # arm_home from HA cluster.client_command.reset_mock() await hass.services.async_call( - Platform.ALARM_CONTROL_PANEL, + ALARM_DOMAIN, "alarm_arm_home", {ATTR_ENTITY_ID: entity_id}, blocking=True, @@ -143,7 +144,7 @@ async def test_alarm_control_panel(hass, zha_device_joined_restored, zigpy_devic # arm_night from HA cluster.client_command.reset_mock() await hass.services.async_call( - Platform.ALARM_CONTROL_PANEL, + ALARM_DOMAIN, "alarm_arm_night", {ATTR_ENTITY_ID: entity_id}, blocking=True, @@ -240,7 +241,7 @@ async def reset_alarm_panel(hass, cluster, entity_id): """Reset the state of the alarm panel.""" cluster.client_command.reset_mock() await hass.services.async_call( - Platform.ALARM_CONTROL_PANEL, + ALARM_DOMAIN, "alarm_disarm", {ATTR_ENTITY_ID: entity_id, "code": "4321"}, blocking=True, diff --git a/tests/components/zha/test_climate.py b/tests/components/zha/test_climate.py index 4dc72b092e49eb..8ff787af7bd420 100644 --- a/tests/components/zha/test_climate.py +++ b/tests/components/zha/test_climate.py @@ -25,6 +25,7 @@ CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, CURRENT_HVAC_OFF, + DOMAIN as CLIMATE_DOMAIN, FAN_AUTO, FAN_LOW, FAN_ON, @@ -525,7 +526,7 @@ async def test_target_temperature( entity_id = await find_entity_id(Platform.CLIMATE, device_climate, hass) if preset: await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: preset}, blocking=True, @@ -561,7 +562,7 @@ async def test_target_temperature_high( entity_id = await find_entity_id(Platform.CLIMATE, device_climate, hass) if preset: await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: preset}, blocking=True, @@ -597,7 +598,7 @@ async def test_target_temperature_low( entity_id = await find_entity_id(Platform.CLIMATE, device_climate, hass) if preset: await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: preset}, blocking=True, @@ -628,7 +629,7 @@ async def test_set_hvac_mode(hass, device_climate, hvac_mode, sys_mode): assert state.state == HVAC_MODE_OFF await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_HVAC_MODE: hvac_mode}, blocking=True, @@ -647,7 +648,7 @@ async def test_set_hvac_mode(hass, device_climate, hvac_mode, sys_mode): # turn off thrm_cluster.write_attributes.reset_mock() await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_HVAC_MODE: HVAC_MODE_OFF}, blocking=True, @@ -675,7 +676,7 @@ async def test_preset_setting(hass, device_climate_sinope): ] await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_AWAY}, blocking=True, @@ -692,7 +693,7 @@ async def test_preset_setting(hass, device_climate_sinope): zcl_f.WriteAttributesResponse.deserialize(b"\x00")[0] ] await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_AWAY}, blocking=True, @@ -709,7 +710,7 @@ async def test_preset_setting(hass, device_climate_sinope): zcl_f.WriteAttributesResponse.deserialize(b"\x01\x01\x01")[0] ] await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_NONE}, blocking=True, @@ -726,7 +727,7 @@ async def test_preset_setting(hass, device_climate_sinope): zcl_f.WriteAttributesResponse.deserialize(b"\x00")[0] ] await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_NONE}, blocking=True, @@ -748,7 +749,7 @@ async def test_preset_setting_invalid(hass, device_climate_sinope): assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: "invalid_preset"}, blocking=True, @@ -769,7 +770,7 @@ async def test_set_temperature_hvac_mode(hass, device_climate): assert state.state == HVAC_MODE_OFF await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, { ATTR_ENTITY_ID: entity_id, @@ -809,7 +810,7 @@ async def test_set_temperature_heat_cool(hass, device_climate_mock): assert state.state == HVAC_MODE_HEAT_COOL await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, {ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 21}, blocking=True, @@ -821,7 +822,7 @@ async def test_set_temperature_heat_cool(hass, device_climate_mock): assert thrm_cluster.write_attributes.await_count == 0 await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, { ATTR_ENTITY_ID: entity_id, @@ -843,7 +844,7 @@ async def test_set_temperature_heat_cool(hass, device_climate_mock): } await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_AWAY}, blocking=True, @@ -851,7 +852,7 @@ async def test_set_temperature_heat_cool(hass, device_climate_mock): thrm_cluster.write_attributes.reset_mock() await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, { ATTR_ENTITY_ID: entity_id, @@ -895,7 +896,7 @@ async def test_set_temperature_heat(hass, device_climate_mock): assert state.state == HVAC_MODE_HEAT await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, { ATTR_ENTITY_ID: entity_id, @@ -912,7 +913,7 @@ async def test_set_temperature_heat(hass, device_climate_mock): assert thrm_cluster.write_attributes.await_count == 0 await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, {ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 21}, blocking=True, @@ -928,7 +929,7 @@ async def test_set_temperature_heat(hass, device_climate_mock): } await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_AWAY}, blocking=True, @@ -936,7 +937,7 @@ async def test_set_temperature_heat(hass, device_climate_mock): thrm_cluster.write_attributes.reset_mock() await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, {ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 22}, blocking=True, @@ -974,7 +975,7 @@ async def test_set_temperature_cool(hass, device_climate_mock): assert state.state == HVAC_MODE_COOL await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, { ATTR_ENTITY_ID: entity_id, @@ -991,7 +992,7 @@ async def test_set_temperature_cool(hass, device_climate_mock): assert thrm_cluster.write_attributes.await_count == 0 await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, {ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 21}, blocking=True, @@ -1007,7 +1008,7 @@ async def test_set_temperature_cool(hass, device_climate_mock): } await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_AWAY}, blocking=True, @@ -1015,7 +1016,7 @@ async def test_set_temperature_cool(hass, device_climate_mock): thrm_cluster.write_attributes.reset_mock() await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, {ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 22}, blocking=True, @@ -1057,7 +1058,7 @@ async def test_set_temperature_wrong_mode(hass, device_climate_mock): assert state.state == HVAC_MODE_DRY await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, {ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 24}, blocking=True, @@ -1080,7 +1081,7 @@ async def test_occupancy_reset(hass, device_climate_sinope): assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_AWAY}, blocking=True, @@ -1133,7 +1134,7 @@ async def test_set_fan_mode_not_supported(hass, device_climate_fan): fan_cluster = device_climate_fan.device.endpoints[1].fan await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_FAN_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_FAN_MODE: FAN_LOW}, blocking=True, @@ -1151,7 +1152,7 @@ async def test_set_fan_mode(hass, device_climate_fan): assert state.attributes[ATTR_FAN_MODE] == FAN_AUTO await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_FAN_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_FAN_MODE: FAN_ON}, blocking=True, @@ -1161,7 +1162,7 @@ async def test_set_fan_mode(hass, device_climate_fan): fan_cluster.write_attributes.reset_mock() await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_FAN_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_FAN_MODE: FAN_AUTO}, blocking=True, @@ -1180,7 +1181,7 @@ async def test_set_moes_preset(hass, device_climate_moes): assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_AWAY}, blocking=True, @@ -1193,7 +1194,7 @@ async def test_set_moes_preset(hass, device_climate_moes): thrm_cluster.write_attributes.reset_mock() await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_SCHEDULE}, blocking=True, @@ -1209,7 +1210,7 @@ async def test_set_moes_preset(hass, device_climate_moes): thrm_cluster.write_attributes.reset_mock() await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_COMFORT}, blocking=True, @@ -1225,7 +1226,7 @@ async def test_set_moes_preset(hass, device_climate_moes): thrm_cluster.write_attributes.reset_mock() await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_ECO}, blocking=True, @@ -1241,7 +1242,7 @@ async def test_set_moes_preset(hass, device_climate_moes): thrm_cluster.write_attributes.reset_mock() await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_BOOST}, blocking=True, @@ -1257,7 +1258,7 @@ async def test_set_moes_preset(hass, device_climate_moes): thrm_cluster.write_attributes.reset_mock() await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_COMPLEX}, blocking=True, @@ -1273,7 +1274,7 @@ async def test_set_moes_preset(hass, device_climate_moes): thrm_cluster.write_attributes.reset_mock() await hass.services.async_call( - Platform.CLIMATE, + CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_NONE}, blocking=True, diff --git a/tests/components/zha/test_cover.py b/tests/components/zha/test_cover.py index 45c5928797dcf2..73ab38c27acee5 100644 --- a/tests/components/zha/test_cover.py +++ b/tests/components/zha/test_cover.py @@ -11,6 +11,7 @@ from homeassistant.components.cover import ( ATTR_CURRENT_POSITION, + DOMAIN as COVER_DOMAIN, SERVICE_CLOSE_COVER, SERVICE_OPEN_COVER, SERVICE_SET_COVER_POSITION, @@ -140,7 +141,7 @@ async def test_cover(m1, hass, zha_device_joined_restored, zigpy_cover_device): "zigpy.zcl.Cluster.request", return_value=mock_coro([0x1, zcl_f.Status.SUCCESS]) ): await hass.services.async_call( - Platform.COVER, SERVICE_CLOSE_COVER, {"entity_id": entity_id}, blocking=True + COVER_DOMAIN, SERVICE_CLOSE_COVER, {"entity_id": entity_id}, blocking=True ) assert cluster.request.call_count == 1 assert cluster.request.call_args[0][0] is False @@ -153,7 +154,7 @@ async def test_cover(m1, hass, zha_device_joined_restored, zigpy_cover_device): "zigpy.zcl.Cluster.request", return_value=mock_coro([0x0, zcl_f.Status.SUCCESS]) ): await hass.services.async_call( - Platform.COVER, SERVICE_OPEN_COVER, {"entity_id": entity_id}, blocking=True + COVER_DOMAIN, SERVICE_OPEN_COVER, {"entity_id": entity_id}, blocking=True ) assert cluster.request.call_count == 1 assert cluster.request.call_args[0][0] is False @@ -166,7 +167,7 @@ async def test_cover(m1, hass, zha_device_joined_restored, zigpy_cover_device): "zigpy.zcl.Cluster.request", return_value=mock_coro([0x5, zcl_f.Status.SUCCESS]) ): await hass.services.async_call( - Platform.COVER, + COVER_DOMAIN, SERVICE_SET_COVER_POSITION, {"entity_id": entity_id, "position": 47}, blocking=True, @@ -183,7 +184,7 @@ async def test_cover(m1, hass, zha_device_joined_restored, zigpy_cover_device): "zigpy.zcl.Cluster.request", return_value=mock_coro([0x2, zcl_f.Status.SUCCESS]) ): await hass.services.async_call( - Platform.COVER, SERVICE_STOP_COVER, {"entity_id": entity_id}, blocking=True + COVER_DOMAIN, SERVICE_STOP_COVER, {"entity_id": entity_id}, blocking=True ) assert cluster.request.call_count == 1 assert cluster.request.call_args[0][0] is False @@ -226,7 +227,7 @@ async def test_shade(hass, zha_device_joined_restored, zigpy_shade_device): # close from UI command fails with patch("zigpy.zcl.Cluster.request", side_effect=asyncio.TimeoutError): await hass.services.async_call( - Platform.COVER, SERVICE_CLOSE_COVER, {"entity_id": entity_id}, blocking=True + COVER_DOMAIN, SERVICE_CLOSE_COVER, {"entity_id": entity_id}, blocking=True ) assert cluster_on_off.request.call_count == 1 assert cluster_on_off.request.call_args[0][0] is False @@ -237,7 +238,7 @@ async def test_shade(hass, zha_device_joined_restored, zigpy_shade_device): "zigpy.zcl.Cluster.request", AsyncMock(return_value=[0x1, zcl_f.Status.SUCCESS]) ): await hass.services.async_call( - Platform.COVER, SERVICE_CLOSE_COVER, {"entity_id": entity_id}, blocking=True + COVER_DOMAIN, SERVICE_CLOSE_COVER, {"entity_id": entity_id}, blocking=True ) assert cluster_on_off.request.call_count == 1 assert cluster_on_off.request.call_args[0][0] is False @@ -249,7 +250,7 @@ async def test_shade(hass, zha_device_joined_restored, zigpy_shade_device): await send_attributes_report(hass, cluster_level, {0: 0}) with patch("zigpy.zcl.Cluster.request", side_effect=asyncio.TimeoutError): await hass.services.async_call( - Platform.COVER, SERVICE_OPEN_COVER, {"entity_id": entity_id}, blocking=True + COVER_DOMAIN, SERVICE_OPEN_COVER, {"entity_id": entity_id}, blocking=True ) assert cluster_on_off.request.call_count == 1 assert cluster_on_off.request.call_args[0][0] is False @@ -261,7 +262,7 @@ async def test_shade(hass, zha_device_joined_restored, zigpy_shade_device): "zigpy.zcl.Cluster.request", AsyncMock(return_value=[0x0, zcl_f.Status.SUCCESS]) ): await hass.services.async_call( - Platform.COVER, SERVICE_OPEN_COVER, {"entity_id": entity_id}, blocking=True + COVER_DOMAIN, SERVICE_OPEN_COVER, {"entity_id": entity_id}, blocking=True ) assert cluster_on_off.request.call_count == 1 assert cluster_on_off.request.call_args[0][0] is False @@ -271,7 +272,7 @@ async def test_shade(hass, zha_device_joined_restored, zigpy_shade_device): # set position UI command fails with patch("zigpy.zcl.Cluster.request", side_effect=asyncio.TimeoutError): await hass.services.async_call( - Platform.COVER, + COVER_DOMAIN, SERVICE_SET_COVER_POSITION, {"entity_id": entity_id, "position": 47}, blocking=True, @@ -287,7 +288,7 @@ async def test_shade(hass, zha_device_joined_restored, zigpy_shade_device): "zigpy.zcl.Cluster.request", AsyncMock(return_value=[0x5, zcl_f.Status.SUCCESS]) ): await hass.services.async_call( - Platform.COVER, + COVER_DOMAIN, SERVICE_SET_COVER_POSITION, {"entity_id": entity_id, "position": 47}, blocking=True, @@ -313,7 +314,7 @@ async def test_shade(hass, zha_device_joined_restored, zigpy_shade_device): # test cover stop with patch("zigpy.zcl.Cluster.request", side_effect=asyncio.TimeoutError): await hass.services.async_call( - Platform.COVER, + COVER_DOMAIN, SERVICE_STOP_COVER, {"entity_id": entity_id}, blocking=True, @@ -377,7 +378,7 @@ async def test_keen_vent(hass, zha_device_joined_restored, zigpy_keen_vent): with p1, p2: await hass.services.async_call( - Platform.COVER, SERVICE_OPEN_COVER, {"entity_id": entity_id}, blocking=True + COVER_DOMAIN, SERVICE_OPEN_COVER, {"entity_id": entity_id}, blocking=True ) assert cluster_on_off.request.call_count == 1 assert cluster_on_off.request.call_args[0][0] is False @@ -391,7 +392,7 @@ async def test_keen_vent(hass, zha_device_joined_restored, zigpy_keen_vent): with p1, p2: await hass.services.async_call( - Platform.COVER, SERVICE_OPEN_COVER, {"entity_id": entity_id}, blocking=True + COVER_DOMAIN, SERVICE_OPEN_COVER, {"entity_id": entity_id}, blocking=True ) await asyncio.sleep(0) assert cluster_on_off.request.call_count == 1 diff --git a/tests/components/zha/test_fan.py b/tests/components/zha/test_fan.py index e94c028acd899a..a8f5f6450595f6 100644 --- a/tests/components/zha/test_fan.py +++ b/tests/components/zha/test_fan.py @@ -14,6 +14,7 @@ ATTR_PERCENTAGE_STEP, ATTR_PRESET_MODE, ATTR_SPEED, + DOMAIN as FAN_DOMAIN, SERVICE_SET_PRESET_MODE, SERVICE_SET_SPEED, SPEED_HIGH, @@ -246,7 +247,7 @@ async def async_set_preset_mode(hass, entity_id, preset_mode=None): } await hass.services.async_call( - Platform.FAN, SERVICE_SET_PRESET_MODE, data, blocking=True + FAN_DOMAIN, SERVICE_SET_PRESET_MODE, data, blocking=True ) diff --git a/tests/components/zha/test_light.py b/tests/components/zha/test_light.py index ee437fc63c938a..9c35215c889f9e 100644 --- a/tests/components/zha/test_light.py +++ b/tests/components/zha/test_light.py @@ -9,7 +9,11 @@ import zigpy.zcl.clusters.lighting as lighting import zigpy.zcl.foundation as zcl_f -from homeassistant.components.light import FLASH_LONG, FLASH_SHORT +from homeassistant.components.light import ( + DOMAIN as LIGHT_DOMAIN, + FLASH_LONG, + FLASH_SHORT, +) from homeassistant.components.zha.core.group import GroupMember from homeassistant.components.zha.light import FLASH_EFFECTS from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE, Platform @@ -327,7 +331,7 @@ async def async_test_on_off_from_hass(hass, cluster, entity_id): # turn on via UI cluster.request.reset_mock() await hass.services.async_call( - Platform.LIGHT, "turn_on", {"entity_id": entity_id}, blocking=True + LIGHT_DOMAIN, "turn_on", {"entity_id": entity_id}, blocking=True ) assert cluster.request.call_count == 1 assert cluster.request.await_count == 1 @@ -344,7 +348,7 @@ async def async_test_off_from_hass(hass, cluster, entity_id): # turn off via UI cluster.request.reset_mock() await hass.services.async_call( - Platform.LIGHT, "turn_off", {"entity_id": entity_id}, blocking=True + LIGHT_DOMAIN, "turn_off", {"entity_id": entity_id}, blocking=True ) assert cluster.request.call_count == 1 assert cluster.request.await_count == 1 @@ -362,7 +366,7 @@ async def async_test_level_on_off_from_hass( level_cluster.request.reset_mock() # turn on via UI await hass.services.async_call( - Platform.LIGHT, "turn_on", {"entity_id": entity_id}, blocking=True + LIGHT_DOMAIN, "turn_on", {"entity_id": entity_id}, blocking=True ) assert on_off_cluster.request.call_count == 1 assert on_off_cluster.request.await_count == 1 @@ -375,7 +379,7 @@ async def async_test_level_on_off_from_hass( level_cluster.request.reset_mock() await hass.services.async_call( - Platform.LIGHT, + LIGHT_DOMAIN, "turn_on", {"entity_id": entity_id, "transition": 10}, blocking=True, @@ -402,7 +406,7 @@ async def async_test_level_on_off_from_hass( level_cluster.request.reset_mock() await hass.services.async_call( - Platform.LIGHT, + LIGHT_DOMAIN, "turn_on", {"entity_id": entity_id, "brightness": 10}, blocking=True, @@ -448,7 +452,7 @@ async def async_test_flash_from_hass(hass, cluster, entity_id, flash): # turn on via UI cluster.request.reset_mock() await hass.services.async_call( - Platform.LIGHT, + LIGHT_DOMAIN, "turn_on", {"entity_id": entity_id, "flash": flash}, blocking=True, diff --git a/tests/components/zha/test_lock.py b/tests/components/zha/test_lock.py index c8685996c25cca..0669cebf128a7b 100644 --- a/tests/components/zha/test_lock.py +++ b/tests/components/zha/test_lock.py @@ -7,6 +7,7 @@ import zigpy.zcl.clusters.general as general import zigpy.zcl.foundation as zcl_f +from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.const import ( STATE_LOCKED, STATE_UNAVAILABLE, @@ -96,7 +97,7 @@ async def async_lock(hass, cluster, entity_id): ): # lock via UI await hass.services.async_call( - Platform.LOCK, "lock", {"entity_id": entity_id}, blocking=True + LOCK_DOMAIN, "lock", {"entity_id": entity_id}, blocking=True ) assert cluster.request.call_count == 1 assert cluster.request.call_args[0][0] is False @@ -110,7 +111,7 @@ async def async_unlock(hass, cluster, entity_id): ): # lock via UI await hass.services.async_call( - Platform.LOCK, "unlock", {"entity_id": entity_id}, blocking=True + LOCK_DOMAIN, "unlock", {"entity_id": entity_id}, blocking=True ) assert cluster.request.call_count == 1 assert cluster.request.call_args[0][0] is False diff --git a/tests/components/zha/test_number.py b/tests/components/zha/test_number.py index ac72a00d80235b..336800f9ccbfa5 100644 --- a/tests/components/zha/test_number.py +++ b/tests/components/zha/test_number.py @@ -7,6 +7,7 @@ import zigpy.zcl.clusters.general as general import zigpy.zcl.foundation as zcl_f +from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN from homeassistant.const import STATE_UNAVAILABLE, Platform from homeassistant.setup import async_setup_component @@ -109,7 +110,7 @@ async def test_number(hass, zha_device_joined_restored, zigpy_analog_output_devi ): # set value via UI await hass.services.async_call( - Platform.NUMBER, + NUMBER_DOMAIN, "set_value", {"entity_id": entity_id, "value": 30.0}, blocking=True, diff --git a/tests/components/zha/test_siren.py b/tests/components/zha/test_siren.py index 17e12491f84023..285bc1cd585962 100644 --- a/tests/components/zha/test_siren.py +++ b/tests/components/zha/test_siren.py @@ -13,6 +13,7 @@ ATTR_DURATION, ATTR_TONE, ATTR_VOLUME_LEVEL, + DOMAIN as SIREN_DOMAIN, ) from homeassistant.components.zha.core.const import ( WARNING_DEVICE_MODE_EMERGENCY_PANIC, @@ -72,7 +73,7 @@ async def test_siren(hass, siren): ): # turn on via UI await hass.services.async_call( - Platform.SIREN, "turn_on", {"entity_id": entity_id}, blocking=True + SIREN_DOMAIN, "turn_on", {"entity_id": entity_id}, blocking=True ) assert len(cluster.request.mock_calls) == 1 assert cluster.request.call_args[0][0] is False @@ -92,7 +93,7 @@ async def test_siren(hass, siren): ): # turn off via UI await hass.services.async_call( - Platform.SIREN, "turn_off", {"entity_id": entity_id}, blocking=True + SIREN_DOMAIN, "turn_off", {"entity_id": entity_id}, blocking=True ) assert len(cluster.request.mock_calls) == 1 assert cluster.request.call_args[0][0] is False @@ -112,7 +113,7 @@ async def test_siren(hass, siren): ): # turn on via UI await hass.services.async_call( - Platform.SIREN, + SIREN_DOMAIN, "turn_on", { "entity_id": entity_id, diff --git a/tests/components/zha/test_switch.py b/tests/components/zha/test_switch.py index 879bc26db9f220..c5cdf1a96f1e8a 100644 --- a/tests/components/zha/test_switch.py +++ b/tests/components/zha/test_switch.py @@ -6,6 +6,7 @@ import zigpy.zcl.clusters.general as general import zigpy.zcl.foundation as zcl_f +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.zha.core.group import GroupMember from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE, Platform @@ -136,7 +137,7 @@ async def test_switch(hass, zha_device_joined_restored, zigpy_device): ): # turn on via UI await hass.services.async_call( - Platform.SWITCH, "turn_on", {"entity_id": entity_id}, blocking=True + SWITCH_DOMAIN, "turn_on", {"entity_id": entity_id}, blocking=True ) assert len(cluster.request.mock_calls) == 1 assert cluster.request.call_args == call( @@ -150,7 +151,7 @@ async def test_switch(hass, zha_device_joined_restored, zigpy_device): ): # turn off via UI await hass.services.async_call( - Platform.SWITCH, "turn_off", {"entity_id": entity_id}, blocking=True + SWITCH_DOMAIN, "turn_off", {"entity_id": entity_id}, blocking=True ) assert len(cluster.request.mock_calls) == 1 assert cluster.request.call_args == call( @@ -219,7 +220,7 @@ async def test_zha_group_switch_entity( ): # turn on via UI await hass.services.async_call( - Platform.SWITCH, "turn_on", {"entity_id": entity_id}, blocking=True + SWITCH_DOMAIN, "turn_on", {"entity_id": entity_id}, blocking=True ) assert len(group_cluster_on_off.request.mock_calls) == 1 assert group_cluster_on_off.request.call_args == call( @@ -234,7 +235,7 @@ async def test_zha_group_switch_entity( ): # turn off via UI await hass.services.async_call( - Platform.SWITCH, "turn_off", {"entity_id": entity_id}, blocking=True + SWITCH_DOMAIN, "turn_off", {"entity_id": entity_id}, blocking=True ) assert len(group_cluster_on_off.request.mock_calls) == 1 assert group_cluster_on_off.request.call_args == call( From f95183f6a837350ca4db546e4f46def638a5cdff Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 3 Feb 2022 14:21:06 +0100 Subject: [PATCH 0220/1098] Make mypy IGNORED_MODULES file specific (#65416) * Make mypy IGNORED_MODULES file specific * Adjust sonos type hints * Remove legacy nest from IGNORED_MODULES Co-authored-by: epenet --- mypy.ini | 789 ++++++++++++++++++++++++++++++--- script/hassfest/mypy_config.py | 361 ++++++++++++--- 2 files changed, 1028 insertions(+), 122 deletions(-) diff --git a/mypy.ini b/mypy.ini index e947b66161837d..c48d0221033a34 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2020,182 +2020,851 @@ no_implicit_optional = false warn_return_any = false warn_unreachable = false -[mypy-homeassistant.components.blueprint.*] +[mypy-homeassistant.components.blueprint.importer] ignore_errors = true -[mypy-homeassistant.components.cloud.*] +[mypy-homeassistant.components.blueprint.models] ignore_errors = true -[mypy-homeassistant.components.config.*] +[mypy-homeassistant.components.blueprint.websocket_api] ignore_errors = true -[mypy-homeassistant.components.conversation.*] +[mypy-homeassistant.components.cloud.client] ignore_errors = true -[mypy-homeassistant.components.deconz.*] +[mypy-homeassistant.components.cloud.http_api] ignore_errors = true -[mypy-homeassistant.components.demo.*] +[mypy-homeassistant.components.config.auth] ignore_errors = true -[mypy-homeassistant.components.denonavr.*] +[mypy-homeassistant.components.config.config_entries] ignore_errors = true -[mypy-homeassistant.components.evohome.*] +[mypy-homeassistant.components.config.core] ignore_errors = true -[mypy-homeassistant.components.fireservicerota.*] +[mypy-homeassistant.components.config.entity_registry] ignore_errors = true -[mypy-homeassistant.components.firmata.*] +[mypy-homeassistant.components.conversation] ignore_errors = true -[mypy-homeassistant.components.geniushub.*] +[mypy-homeassistant.components.conversation.default_agent] ignore_errors = true -[mypy-homeassistant.components.google_assistant.*] +[mypy-homeassistant.components.deconz] ignore_errors = true -[mypy-homeassistant.components.gree.*] +[mypy-homeassistant.components.deconz.alarm_control_panel] ignore_errors = true -[mypy-homeassistant.components.harmony.*] +[mypy-homeassistant.components.deconz.binary_sensor] ignore_errors = true -[mypy-homeassistant.components.hassio.*] +[mypy-homeassistant.components.deconz.climate] ignore_errors = true -[mypy-homeassistant.components.here_travel_time.*] +[mypy-homeassistant.components.deconz.cover] ignore_errors = true -[mypy-homeassistant.components.home_plus_control.*] +[mypy-homeassistant.components.deconz.fan] ignore_errors = true -[mypy-homeassistant.components.homekit.*] +[mypy-homeassistant.components.deconz.gateway] ignore_errors = true -[mypy-homeassistant.components.honeywell.*] +[mypy-homeassistant.components.deconz.light] ignore_errors = true -[mypy-homeassistant.components.icloud.*] +[mypy-homeassistant.components.deconz.lock] ignore_errors = true -[mypy-homeassistant.components.influxdb.*] +[mypy-homeassistant.components.deconz.logbook] ignore_errors = true -[mypy-homeassistant.components.input_datetime.*] +[mypy-homeassistant.components.deconz.number] ignore_errors = true -[mypy-homeassistant.components.isy994.*] +[mypy-homeassistant.components.deconz.sensor] ignore_errors = true -[mypy-homeassistant.components.izone.*] +[mypy-homeassistant.components.deconz.services] ignore_errors = true -[mypy-homeassistant.components.konnected.*] +[mypy-homeassistant.components.deconz.siren] ignore_errors = true -[mypy-homeassistant.components.kostal_plenticore.*] +[mypy-homeassistant.components.deconz.switch] ignore_errors = true -[mypy-homeassistant.components.litterrobot.*] +[mypy-homeassistant.components.demo] ignore_errors = true -[mypy-homeassistant.components.lovelace.*] +[mypy-homeassistant.components.demo.fan] ignore_errors = true -[mypy-homeassistant.components.lutron_caseta.*] +[mypy-homeassistant.components.demo.light] ignore_errors = true -[mypy-homeassistant.components.lyric.*] +[mypy-homeassistant.components.demo.number] ignore_errors = true -[mypy-homeassistant.components.melcloud.*] +[mypy-homeassistant.components.demo.remote] ignore_errors = true -[mypy-homeassistant.components.meteo_france.*] +[mypy-homeassistant.components.demo.siren] ignore_errors = true -[mypy-homeassistant.components.minecraft_server.*] +[mypy-homeassistant.components.demo.switch] ignore_errors = true -[mypy-homeassistant.components.mobile_app.*] +[mypy-homeassistant.components.denonavr.config_flow] ignore_errors = true -[mypy-homeassistant.components.netgear.*] +[mypy-homeassistant.components.denonavr.media_player] ignore_errors = true -[mypy-homeassistant.components.nilu.*] +[mypy-homeassistant.components.denonavr.receiver] ignore_errors = true -[mypy-homeassistant.components.nzbget.*] +[mypy-homeassistant.components.evohome] ignore_errors = true -[mypy-homeassistant.components.omnilogic.*] +[mypy-homeassistant.components.evohome.climate] ignore_errors = true -[mypy-homeassistant.components.onvif.*] +[mypy-homeassistant.components.evohome.water_heater] ignore_errors = true -[mypy-homeassistant.components.ozw.*] +[mypy-homeassistant.components.fireservicerota] ignore_errors = true -[mypy-homeassistant.components.philips_js.*] +[mypy-homeassistant.components.fireservicerota.binary_sensor] ignore_errors = true -[mypy-homeassistant.components.plex.*] +[mypy-homeassistant.components.fireservicerota.sensor] ignore_errors = true -[mypy-homeassistant.components.profiler.*] +[mypy-homeassistant.components.fireservicerota.switch] ignore_errors = true -[mypy-homeassistant.components.solaredge.*] +[mypy-homeassistant.components.firmata] ignore_errors = true -[mypy-homeassistant.components.sonos.*] +[mypy-homeassistant.components.firmata.binary_sensor] ignore_errors = true -[mypy-homeassistant.components.spotify.*] +[mypy-homeassistant.components.firmata.board] ignore_errors = true -[mypy-homeassistant.components.system_health.*] +[mypy-homeassistant.components.firmata.entity] ignore_errors = true -[mypy-homeassistant.components.telegram_bot.*] +[mypy-homeassistant.components.firmata.light] ignore_errors = true -[mypy-homeassistant.components.template.*] +[mypy-homeassistant.components.firmata.pin] ignore_errors = true -[mypy-homeassistant.components.toon.*] +[mypy-homeassistant.components.firmata.sensor] ignore_errors = true -[mypy-homeassistant.components.unifi.*] +[mypy-homeassistant.components.firmata.switch] ignore_errors = true -[mypy-homeassistant.components.upnp.*] +[mypy-homeassistant.components.geniushub] ignore_errors = true -[mypy-homeassistant.components.vizio.*] +[mypy-homeassistant.components.geniushub.binary_sensor] ignore_errors = true -[mypy-homeassistant.components.withings.*] +[mypy-homeassistant.components.geniushub.climate] ignore_errors = true -[mypy-homeassistant.components.xbox.*] +[mypy-homeassistant.components.geniushub.sensor] ignore_errors = true -[mypy-homeassistant.components.xiaomi_aqara.*] +[mypy-homeassistant.components.geniushub.water_heater] ignore_errors = true -[mypy-homeassistant.components.xiaomi_miio.*] +[mypy-homeassistant.components.google_assistant.helpers] ignore_errors = true -[mypy-homeassistant.components.yeelight.*] +[mypy-homeassistant.components.google_assistant.http] ignore_errors = true -[mypy-homeassistant.components.zha.*] +[mypy-homeassistant.components.google_assistant.report_state] ignore_errors = true -[mypy-homeassistant.components.zwave.*] +[mypy-homeassistant.components.google_assistant.trait] +ignore_errors = true + +[mypy-homeassistant.components.gree.climate] +ignore_errors = true + +[mypy-homeassistant.components.gree.switch] +ignore_errors = true + +[mypy-homeassistant.components.harmony] +ignore_errors = true + +[mypy-homeassistant.components.harmony.config_flow] +ignore_errors = true + +[mypy-homeassistant.components.harmony.data] +ignore_errors = true + +[mypy-homeassistant.components.hassio] +ignore_errors = true + +[mypy-homeassistant.components.hassio.auth] +ignore_errors = true + +[mypy-homeassistant.components.hassio.binary_sensor] +ignore_errors = true + +[mypy-homeassistant.components.hassio.ingress] +ignore_errors = true + +[mypy-homeassistant.components.hassio.sensor] +ignore_errors = true + +[mypy-homeassistant.components.hassio.system_health] +ignore_errors = true + +[mypy-homeassistant.components.hassio.websocket_api] +ignore_errors = true + +[mypy-homeassistant.components.here_travel_time.sensor] +ignore_errors = true + +[mypy-homeassistant.components.home_plus_control] +ignore_errors = true + +[mypy-homeassistant.components.home_plus_control.api] +ignore_errors = true + +[mypy-homeassistant.components.homekit.aidmanager] +ignore_errors = true + +[mypy-homeassistant.components.homekit.config_flow] +ignore_errors = true + +[mypy-homeassistant.components.homekit.util] +ignore_errors = true + +[mypy-homeassistant.components.honeywell.climate] +ignore_errors = true + +[mypy-homeassistant.components.icloud] +ignore_errors = true + +[mypy-homeassistant.components.icloud.account] +ignore_errors = true + +[mypy-homeassistant.components.icloud.device_tracker] +ignore_errors = true + +[mypy-homeassistant.components.icloud.sensor] +ignore_errors = true + +[mypy-homeassistant.components.influxdb] +ignore_errors = true + +[mypy-homeassistant.components.input_datetime] +ignore_errors = true + +[mypy-homeassistant.components.isy994] +ignore_errors = true + +[mypy-homeassistant.components.isy994.binary_sensor] +ignore_errors = true + +[mypy-homeassistant.components.isy994.climate] +ignore_errors = true + +[mypy-homeassistant.components.isy994.config_flow] +ignore_errors = true + +[mypy-homeassistant.components.isy994.cover] +ignore_errors = true + +[mypy-homeassistant.components.isy994.entity] +ignore_errors = true + +[mypy-homeassistant.components.isy994.fan] +ignore_errors = true + +[mypy-homeassistant.components.isy994.helpers] +ignore_errors = true + +[mypy-homeassistant.components.isy994.light] +ignore_errors = true + +[mypy-homeassistant.components.isy994.lock] +ignore_errors = true + +[mypy-homeassistant.components.isy994.sensor] +ignore_errors = true + +[mypy-homeassistant.components.isy994.services] +ignore_errors = true + +[mypy-homeassistant.components.isy994.switch] +ignore_errors = true + +[mypy-homeassistant.components.izone.climate] +ignore_errors = true + +[mypy-homeassistant.components.konnected] +ignore_errors = true + +[mypy-homeassistant.components.konnected.config_flow] +ignore_errors = true + +[mypy-homeassistant.components.kostal_plenticore.helper] +ignore_errors = true + +[mypy-homeassistant.components.kostal_plenticore.select] +ignore_errors = true + +[mypy-homeassistant.components.kostal_plenticore.sensor] +ignore_errors = true + +[mypy-homeassistant.components.kostal_plenticore.switch] +ignore_errors = true + +[mypy-homeassistant.components.litterrobot] +ignore_errors = true + +[mypy-homeassistant.components.litterrobot.button] +ignore_errors = true + +[mypy-homeassistant.components.litterrobot.entity] +ignore_errors = true + +[mypy-homeassistant.components.litterrobot.hub] +ignore_errors = true + +[mypy-homeassistant.components.litterrobot.select] +ignore_errors = true + +[mypy-homeassistant.components.litterrobot.sensor] +ignore_errors = true + +[mypy-homeassistant.components.litterrobot.switch] +ignore_errors = true + +[mypy-homeassistant.components.litterrobot.vacuum] +ignore_errors = true + +[mypy-homeassistant.components.lovelace] +ignore_errors = true + +[mypy-homeassistant.components.lovelace.dashboard] +ignore_errors = true + +[mypy-homeassistant.components.lovelace.resources] +ignore_errors = true + +[mypy-homeassistant.components.lovelace.websocket] +ignore_errors = true + +[mypy-homeassistant.components.lutron_caseta] +ignore_errors = true + +[mypy-homeassistant.components.lutron_caseta.device_trigger] +ignore_errors = true + +[mypy-homeassistant.components.lutron_caseta.switch] +ignore_errors = true + +[mypy-homeassistant.components.lyric.climate] +ignore_errors = true + +[mypy-homeassistant.components.lyric.config_flow] +ignore_errors = true + +[mypy-homeassistant.components.lyric.sensor] +ignore_errors = true + +[mypy-homeassistant.components.melcloud] +ignore_errors = true + +[mypy-homeassistant.components.melcloud.climate] +ignore_errors = true + +[mypy-homeassistant.components.meteo_france.sensor] +ignore_errors = true + +[mypy-homeassistant.components.meteo_france.weather] +ignore_errors = true + +[mypy-homeassistant.components.minecraft_server] +ignore_errors = true + +[mypy-homeassistant.components.minecraft_server.helpers] +ignore_errors = true + +[mypy-homeassistant.components.minecraft_server.sensor] +ignore_errors = true + +[mypy-homeassistant.components.mobile_app.binary_sensor] +ignore_errors = true + +[mypy-homeassistant.components.mobile_app.device_action] +ignore_errors = true + +[mypy-homeassistant.components.mobile_app.device_tracker] +ignore_errors = true + +[mypy-homeassistant.components.mobile_app.helpers] +ignore_errors = true + +[mypy-homeassistant.components.mobile_app.http_api] +ignore_errors = true + +[mypy-homeassistant.components.mobile_app.push_notification] +ignore_errors = true + +[mypy-homeassistant.components.mobile_app.sensor] +ignore_errors = true + +[mypy-homeassistant.components.netgear] +ignore_errors = true + +[mypy-homeassistant.components.netgear.config_flow] +ignore_errors = true + +[mypy-homeassistant.components.netgear.device_tracker] +ignore_errors = true + +[mypy-homeassistant.components.netgear.router] +ignore_errors = true + +[mypy-homeassistant.components.nilu.air_quality] +ignore_errors = true + +[mypy-homeassistant.components.nzbget] +ignore_errors = true + +[mypy-homeassistant.components.nzbget.config_flow] +ignore_errors = true + +[mypy-homeassistant.components.nzbget.coordinator] +ignore_errors = true + +[mypy-homeassistant.components.nzbget.switch] +ignore_errors = true + +[mypy-homeassistant.components.omnilogic.common] +ignore_errors = true + +[mypy-homeassistant.components.omnilogic.sensor] +ignore_errors = true + +[mypy-homeassistant.components.omnilogic.switch] +ignore_errors = true + +[mypy-homeassistant.components.onvif.base] +ignore_errors = true + +[mypy-homeassistant.components.onvif.binary_sensor] +ignore_errors = true + +[mypy-homeassistant.components.onvif.button] +ignore_errors = true + +[mypy-homeassistant.components.onvif.camera] +ignore_errors = true + +[mypy-homeassistant.components.onvif.config_flow] +ignore_errors = true + +[mypy-homeassistant.components.onvif.device] +ignore_errors = true + +[mypy-homeassistant.components.onvif.event] +ignore_errors = true + +[mypy-homeassistant.components.onvif.models] +ignore_errors = true + +[mypy-homeassistant.components.onvif.parsers] +ignore_errors = true + +[mypy-homeassistant.components.onvif.sensor] +ignore_errors = true + +[mypy-homeassistant.components.ozw] +ignore_errors = true + +[mypy-homeassistant.components.ozw.climate] +ignore_errors = true + +[mypy-homeassistant.components.ozw.entity] +ignore_errors = true + +[mypy-homeassistant.components.philips_js] +ignore_errors = true + +[mypy-homeassistant.components.philips_js.config_flow] +ignore_errors = true + +[mypy-homeassistant.components.philips_js.device_trigger] +ignore_errors = true + +[mypy-homeassistant.components.philips_js.light] +ignore_errors = true + +[mypy-homeassistant.components.philips_js.media_player] +ignore_errors = true + +[mypy-homeassistant.components.plex.media_player] +ignore_errors = true + +[mypy-homeassistant.components.profiler] +ignore_errors = true + +[mypy-homeassistant.components.solaredge.config_flow] +ignore_errors = true + +[mypy-homeassistant.components.solaredge.coordinator] +ignore_errors = true + +[mypy-homeassistant.components.solaredge.sensor] +ignore_errors = true + +[mypy-homeassistant.components.sonos] +ignore_errors = true + +[mypy-homeassistant.components.sonos.alarms] +ignore_errors = true + +[mypy-homeassistant.components.sonos.binary_sensor] +ignore_errors = true + +[mypy-homeassistant.components.sonos.diagnostics] +ignore_errors = true + +[mypy-homeassistant.components.sonos.entity] +ignore_errors = true + +[mypy-homeassistant.components.sonos.favorites] +ignore_errors = true + +[mypy-homeassistant.components.sonos.helpers] +ignore_errors = true + +[mypy-homeassistant.components.sonos.media_browser] +ignore_errors = true + +[mypy-homeassistant.components.sonos.media_player] +ignore_errors = true + +[mypy-homeassistant.components.sonos.number] +ignore_errors = true + +[mypy-homeassistant.components.sonos.sensor] +ignore_errors = true + +[mypy-homeassistant.components.sonos.speaker] +ignore_errors = true + +[mypy-homeassistant.components.sonos.statistics] +ignore_errors = true + +[mypy-homeassistant.components.spotify.config_flow] +ignore_errors = true + +[mypy-homeassistant.components.spotify.media_player] +ignore_errors = true + +[mypy-homeassistant.components.system_health] +ignore_errors = true + +[mypy-homeassistant.components.telegram_bot.polling] +ignore_errors = true + +[mypy-homeassistant.components.template] +ignore_errors = true + +[mypy-homeassistant.components.template.binary_sensor] +ignore_errors = true + +[mypy-homeassistant.components.template.button] +ignore_errors = true + +[mypy-homeassistant.components.template.fan] +ignore_errors = true + +[mypy-homeassistant.components.template.number] +ignore_errors = true + +[mypy-homeassistant.components.template.select] +ignore_errors = true + +[mypy-homeassistant.components.template.sensor] +ignore_errors = true + +[mypy-homeassistant.components.template.template_entity] +ignore_errors = true + +[mypy-homeassistant.components.template.trigger_entity] +ignore_errors = true + +[mypy-homeassistant.components.template.weather] +ignore_errors = true + +[mypy-homeassistant.components.toon] +ignore_errors = true + +[mypy-homeassistant.components.toon.config_flow] +ignore_errors = true + +[mypy-homeassistant.components.toon.models] +ignore_errors = true + +[mypy-homeassistant.components.unifi] +ignore_errors = true + +[mypy-homeassistant.components.unifi.config_flow] +ignore_errors = true + +[mypy-homeassistant.components.unifi.device_tracker] +ignore_errors = true + +[mypy-homeassistant.components.unifi.diagnostics] +ignore_errors = true + +[mypy-homeassistant.components.unifi.unifi_entity_base] +ignore_errors = true + +[mypy-homeassistant.components.upnp] +ignore_errors = true + +[mypy-homeassistant.components.upnp.binary_sensor] +ignore_errors = true + +[mypy-homeassistant.components.upnp.config_flow] +ignore_errors = true + +[mypy-homeassistant.components.upnp.device] +ignore_errors = true + +[mypy-homeassistant.components.upnp.sensor] +ignore_errors = true + +[mypy-homeassistant.components.vizio.config_flow] +ignore_errors = true + +[mypy-homeassistant.components.vizio.media_player] +ignore_errors = true + +[mypy-homeassistant.components.withings] +ignore_errors = true + +[mypy-homeassistant.components.withings.binary_sensor] +ignore_errors = true + +[mypy-homeassistant.components.withings.common] +ignore_errors = true + +[mypy-homeassistant.components.withings.config_flow] +ignore_errors = true + +[mypy-homeassistant.components.xbox] +ignore_errors = true + +[mypy-homeassistant.components.xbox.base_sensor] +ignore_errors = true + +[mypy-homeassistant.components.xbox.binary_sensor] +ignore_errors = true + +[mypy-homeassistant.components.xbox.browse_media] +ignore_errors = true + +[mypy-homeassistant.components.xbox.media_source] +ignore_errors = true + +[mypy-homeassistant.components.xbox.sensor] +ignore_errors = true + +[mypy-homeassistant.components.xiaomi_aqara] +ignore_errors = true + +[mypy-homeassistant.components.xiaomi_aqara.binary_sensor] +ignore_errors = true + +[mypy-homeassistant.components.xiaomi_aqara.lock] +ignore_errors = true + +[mypy-homeassistant.components.xiaomi_aqara.sensor] +ignore_errors = true + +[mypy-homeassistant.components.xiaomi_miio] +ignore_errors = true + +[mypy-homeassistant.components.xiaomi_miio.air_quality] +ignore_errors = true + +[mypy-homeassistant.components.xiaomi_miio.binary_sensor] +ignore_errors = true + +[mypy-homeassistant.components.xiaomi_miio.device] +ignore_errors = true + +[mypy-homeassistant.components.xiaomi_miio.device_tracker] +ignore_errors = true + +[mypy-homeassistant.components.xiaomi_miio.fan] +ignore_errors = true + +[mypy-homeassistant.components.xiaomi_miio.humidifier] +ignore_errors = true + +[mypy-homeassistant.components.xiaomi_miio.light] +ignore_errors = true + +[mypy-homeassistant.components.xiaomi_miio.sensor] +ignore_errors = true + +[mypy-homeassistant.components.xiaomi_miio.switch] +ignore_errors = true + +[mypy-homeassistant.components.yeelight] +ignore_errors = true + +[mypy-homeassistant.components.yeelight.light] +ignore_errors = true + +[mypy-homeassistant.components.yeelight.scanner] +ignore_errors = true + +[mypy-homeassistant.components.zha.alarm_control_panel] +ignore_errors = true + +[mypy-homeassistant.components.zha.api] +ignore_errors = true + +[mypy-homeassistant.components.zha.binary_sensor] +ignore_errors = true + +[mypy-homeassistant.components.zha.button] +ignore_errors = true + +[mypy-homeassistant.components.zha.climate] +ignore_errors = true + +[mypy-homeassistant.components.zha.config_flow] +ignore_errors = true + +[mypy-homeassistant.components.zha.core.channels] +ignore_errors = true + +[mypy-homeassistant.components.zha.core.channels.base] +ignore_errors = true + +[mypy-homeassistant.components.zha.core.channels.closures] +ignore_errors = true + +[mypy-homeassistant.components.zha.core.channels.general] +ignore_errors = true + +[mypy-homeassistant.components.zha.core.channels.homeautomation] +ignore_errors = true + +[mypy-homeassistant.components.zha.core.channels.hvac] +ignore_errors = true + +[mypy-homeassistant.components.zha.core.channels.lighting] +ignore_errors = true + +[mypy-homeassistant.components.zha.core.channels.lightlink] +ignore_errors = true + +[mypy-homeassistant.components.zha.core.channels.manufacturerspecific] +ignore_errors = true + +[mypy-homeassistant.components.zha.core.channels.measurement] +ignore_errors = true + +[mypy-homeassistant.components.zha.core.channels.protocol] +ignore_errors = true + +[mypy-homeassistant.components.zha.core.channels.security] +ignore_errors = true + +[mypy-homeassistant.components.zha.core.channels.smartenergy] +ignore_errors = true + +[mypy-homeassistant.components.zha.core.decorators] +ignore_errors = true + +[mypy-homeassistant.components.zha.core.device] +ignore_errors = true + +[mypy-homeassistant.components.zha.core.discovery] +ignore_errors = true + +[mypy-homeassistant.components.zha.core.gateway] +ignore_errors = true + +[mypy-homeassistant.components.zha.core.group] +ignore_errors = true + +[mypy-homeassistant.components.zha.core.helpers] +ignore_errors = true + +[mypy-homeassistant.components.zha.core.registries] +ignore_errors = true + +[mypy-homeassistant.components.zha.core.store] +ignore_errors = true + +[mypy-homeassistant.components.zha.core.typing] +ignore_errors = true + +[mypy-homeassistant.components.zha.cover] +ignore_errors = true + +[mypy-homeassistant.components.zha.device_action] +ignore_errors = true + +[mypy-homeassistant.components.zha.device_tracker] +ignore_errors = true + +[mypy-homeassistant.components.zha.entity] +ignore_errors = true + +[mypy-homeassistant.components.zha.fan] +ignore_errors = true + +[mypy-homeassistant.components.zha.light] +ignore_errors = true + +[mypy-homeassistant.components.zha.lock] +ignore_errors = true + +[mypy-homeassistant.components.zha.select] +ignore_errors = true + +[mypy-homeassistant.components.zha.sensor] +ignore_errors = true + +[mypy-homeassistant.components.zha.siren] +ignore_errors = true + +[mypy-homeassistant.components.zha.switch] +ignore_errors = true + +[mypy-homeassistant.components.zwave] +ignore_errors = true + +[mypy-homeassistant.components.zwave.migration] +ignore_errors = true + +[mypy-homeassistant.components.zwave.node_entity] ignore_errors = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index 2ab8739de8909a..6697adfa1d1035 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -16,66 +16,289 @@ # remove your component from this list to enable type checks. # Do your best to not add anything new here. IGNORED_MODULES: Final[list[str]] = [ - "homeassistant.components.blueprint.*", - "homeassistant.components.cloud.*", - "homeassistant.components.config.*", - "homeassistant.components.conversation.*", - "homeassistant.components.deconz.*", - "homeassistant.components.demo.*", - "homeassistant.components.denonavr.*", - "homeassistant.components.evohome.*", - "homeassistant.components.fireservicerota.*", - "homeassistant.components.firmata.*", - "homeassistant.components.geniushub.*", - "homeassistant.components.google_assistant.*", - "homeassistant.components.gree.*", - "homeassistant.components.harmony.*", - "homeassistant.components.hassio.*", - "homeassistant.components.here_travel_time.*", - "homeassistant.components.home_plus_control.*", - "homeassistant.components.homekit.*", - "homeassistant.components.honeywell.*", - "homeassistant.components.icloud.*", - "homeassistant.components.influxdb.*", - "homeassistant.components.input_datetime.*", - "homeassistant.components.isy994.*", - "homeassistant.components.izone.*", - "homeassistant.components.konnected.*", - "homeassistant.components.kostal_plenticore.*", - "homeassistant.components.litterrobot.*", - "homeassistant.components.lovelace.*", - "homeassistant.components.lutron_caseta.*", - "homeassistant.components.lyric.*", - "homeassistant.components.melcloud.*", - "homeassistant.components.meteo_france.*", - "homeassistant.components.minecraft_server.*", - "homeassistant.components.mobile_app.*", - "homeassistant.components.netgear.*", - "homeassistant.components.nilu.*", - "homeassistant.components.nzbget.*", - "homeassistant.components.omnilogic.*", - "homeassistant.components.onvif.*", - "homeassistant.components.ozw.*", - "homeassistant.components.philips_js.*", - "homeassistant.components.plex.*", - "homeassistant.components.profiler.*", - "homeassistant.components.solaredge.*", - "homeassistant.components.sonos.*", - "homeassistant.components.spotify.*", - "homeassistant.components.system_health.*", - "homeassistant.components.telegram_bot.*", - "homeassistant.components.template.*", - "homeassistant.components.toon.*", - "homeassistant.components.unifi.*", - "homeassistant.components.upnp.*", - "homeassistant.components.vizio.*", - "homeassistant.components.withings.*", - "homeassistant.components.xbox.*", - "homeassistant.components.xiaomi_aqara.*", - "homeassistant.components.xiaomi_miio.*", - "homeassistant.components.yeelight.*", - "homeassistant.components.zha.*", - "homeassistant.components.zwave.*", + "homeassistant.components.blueprint.importer", + "homeassistant.components.blueprint.models", + "homeassistant.components.blueprint.websocket_api", + "homeassistant.components.cloud.client", + "homeassistant.components.cloud.http_api", + "homeassistant.components.config.auth", + "homeassistant.components.config.config_entries", + "homeassistant.components.config.core", + "homeassistant.components.config.entity_registry", + "homeassistant.components.conversation", + "homeassistant.components.conversation.default_agent", + "homeassistant.components.deconz", + "homeassistant.components.deconz.alarm_control_panel", + "homeassistant.components.deconz.binary_sensor", + "homeassistant.components.deconz.climate", + "homeassistant.components.deconz.cover", + "homeassistant.components.deconz.fan", + "homeassistant.components.deconz.gateway", + "homeassistant.components.deconz.light", + "homeassistant.components.deconz.lock", + "homeassistant.components.deconz.logbook", + "homeassistant.components.deconz.number", + "homeassistant.components.deconz.sensor", + "homeassistant.components.deconz.services", + "homeassistant.components.deconz.siren", + "homeassistant.components.deconz.switch", + "homeassistant.components.demo", + "homeassistant.components.demo.fan", + "homeassistant.components.demo.light", + "homeassistant.components.demo.number", + "homeassistant.components.demo.remote", + "homeassistant.components.demo.siren", + "homeassistant.components.demo.switch", + "homeassistant.components.denonavr.config_flow", + "homeassistant.components.denonavr.media_player", + "homeassistant.components.denonavr.receiver", + "homeassistant.components.evohome", + "homeassistant.components.evohome.climate", + "homeassistant.components.evohome.water_heater", + "homeassistant.components.fireservicerota", + "homeassistant.components.fireservicerota.binary_sensor", + "homeassistant.components.fireservicerota.sensor", + "homeassistant.components.fireservicerota.switch", + "homeassistant.components.firmata", + "homeassistant.components.firmata.binary_sensor", + "homeassistant.components.firmata.board", + "homeassistant.components.firmata.entity", + "homeassistant.components.firmata.light", + "homeassistant.components.firmata.pin", + "homeassistant.components.firmata.sensor", + "homeassistant.components.firmata.switch", + "homeassistant.components.geniushub", + "homeassistant.components.geniushub.binary_sensor", + "homeassistant.components.geniushub.climate", + "homeassistant.components.geniushub.sensor", + "homeassistant.components.geniushub.water_heater", + "homeassistant.components.google_assistant.helpers", + "homeassistant.components.google_assistant.http", + "homeassistant.components.google_assistant.report_state", + "homeassistant.components.google_assistant.trait", + "homeassistant.components.gree.climate", + "homeassistant.components.gree.switch", + "homeassistant.components.harmony", + "homeassistant.components.harmony.config_flow", + "homeassistant.components.harmony.data", + "homeassistant.components.hassio", + "homeassistant.components.hassio.auth", + "homeassistant.components.hassio.binary_sensor", + "homeassistant.components.hassio.ingress", + "homeassistant.components.hassio.sensor", + "homeassistant.components.hassio.system_health", + "homeassistant.components.hassio.websocket_api", + "homeassistant.components.here_travel_time.sensor", + "homeassistant.components.home_plus_control", + "homeassistant.components.home_plus_control.api", + "homeassistant.components.homekit.aidmanager", + "homeassistant.components.homekit.config_flow", + "homeassistant.components.homekit.util", + "homeassistant.components.honeywell.climate", + "homeassistant.components.icloud", + "homeassistant.components.icloud.account", + "homeassistant.components.icloud.device_tracker", + "homeassistant.components.icloud.sensor", + "homeassistant.components.influxdb", + "homeassistant.components.input_datetime", + "homeassistant.components.isy994", + "homeassistant.components.isy994.binary_sensor", + "homeassistant.components.isy994.climate", + "homeassistant.components.isy994.config_flow", + "homeassistant.components.isy994.cover", + "homeassistant.components.isy994.entity", + "homeassistant.components.isy994.fan", + "homeassistant.components.isy994.helpers", + "homeassistant.components.isy994.light", + "homeassistant.components.isy994.lock", + "homeassistant.components.isy994.sensor", + "homeassistant.components.isy994.services", + "homeassistant.components.isy994.switch", + "homeassistant.components.izone.climate", + "homeassistant.components.konnected", + "homeassistant.components.konnected.config_flow", + "homeassistant.components.kostal_plenticore.helper", + "homeassistant.components.kostal_plenticore.select", + "homeassistant.components.kostal_plenticore.sensor", + "homeassistant.components.kostal_plenticore.switch", + "homeassistant.components.litterrobot", + "homeassistant.components.litterrobot.button", + "homeassistant.components.litterrobot.entity", + "homeassistant.components.litterrobot.hub", + "homeassistant.components.litterrobot.select", + "homeassistant.components.litterrobot.sensor", + "homeassistant.components.litterrobot.switch", + "homeassistant.components.litterrobot.vacuum", + "homeassistant.components.lovelace", + "homeassistant.components.lovelace.dashboard", + "homeassistant.components.lovelace.resources", + "homeassistant.components.lovelace.websocket", + "homeassistant.components.lutron_caseta", + "homeassistant.components.lutron_caseta.device_trigger", + "homeassistant.components.lutron_caseta.switch", + "homeassistant.components.lyric.climate", + "homeassistant.components.lyric.config_flow", + "homeassistant.components.lyric.sensor", + "homeassistant.components.melcloud", + "homeassistant.components.melcloud.climate", + "homeassistant.components.meteo_france.sensor", + "homeassistant.components.meteo_france.weather", + "homeassistant.components.minecraft_server", + "homeassistant.components.minecraft_server.helpers", + "homeassistant.components.minecraft_server.sensor", + "homeassistant.components.mobile_app.binary_sensor", + "homeassistant.components.mobile_app.device_action", + "homeassistant.components.mobile_app.device_tracker", + "homeassistant.components.mobile_app.helpers", + "homeassistant.components.mobile_app.http_api", + "homeassistant.components.mobile_app.push_notification", + "homeassistant.components.mobile_app.sensor", + "homeassistant.components.netgear", + "homeassistant.components.netgear.config_flow", + "homeassistant.components.netgear.device_tracker", + "homeassistant.components.netgear.router", + "homeassistant.components.nilu.air_quality", + "homeassistant.components.nzbget", + "homeassistant.components.nzbget.config_flow", + "homeassistant.components.nzbget.coordinator", + "homeassistant.components.nzbget.switch", + "homeassistant.components.omnilogic.common", + "homeassistant.components.omnilogic.sensor", + "homeassistant.components.omnilogic.switch", + "homeassistant.components.onvif.base", + "homeassistant.components.onvif.binary_sensor", + "homeassistant.components.onvif.button", + "homeassistant.components.onvif.camera", + "homeassistant.components.onvif.config_flow", + "homeassistant.components.onvif.device", + "homeassistant.components.onvif.event", + "homeassistant.components.onvif.models", + "homeassistant.components.onvif.parsers", + "homeassistant.components.onvif.sensor", + "homeassistant.components.ozw", + "homeassistant.components.ozw.climate", + "homeassistant.components.ozw.entity", + "homeassistant.components.philips_js", + "homeassistant.components.philips_js.config_flow", + "homeassistant.components.philips_js.device_trigger", + "homeassistant.components.philips_js.light", + "homeassistant.components.philips_js.media_player", + "homeassistant.components.plex.media_player", + "homeassistant.components.profiler", + "homeassistant.components.solaredge.config_flow", + "homeassistant.components.solaredge.coordinator", + "homeassistant.components.solaredge.sensor", + "homeassistant.components.sonos", + "homeassistant.components.sonos.alarms", + "homeassistant.components.sonos.binary_sensor", + "homeassistant.components.sonos.diagnostics", + "homeassistant.components.sonos.entity", + "homeassistant.components.sonos.favorites", + "homeassistant.components.sonos.helpers", + "homeassistant.components.sonos.media_browser", + "homeassistant.components.sonos.media_player", + "homeassistant.components.sonos.number", + "homeassistant.components.sonos.sensor", + "homeassistant.components.sonos.speaker", + "homeassistant.components.sonos.statistics", + "homeassistant.components.spotify.config_flow", + "homeassistant.components.spotify.media_player", + "homeassistant.components.system_health", + "homeassistant.components.telegram_bot.polling", + "homeassistant.components.template", + "homeassistant.components.template.binary_sensor", + "homeassistant.components.template.button", + "homeassistant.components.template.fan", + "homeassistant.components.template.number", + "homeassistant.components.template.select", + "homeassistant.components.template.sensor", + "homeassistant.components.template.template_entity", + "homeassistant.components.template.trigger_entity", + "homeassistant.components.template.weather", + "homeassistant.components.toon", + "homeassistant.components.toon.config_flow", + "homeassistant.components.toon.models", + "homeassistant.components.unifi", + "homeassistant.components.unifi.config_flow", + "homeassistant.components.unifi.device_tracker", + "homeassistant.components.unifi.diagnostics", + "homeassistant.components.unifi.unifi_entity_base", + "homeassistant.components.upnp", + "homeassistant.components.upnp.binary_sensor", + "homeassistant.components.upnp.config_flow", + "homeassistant.components.upnp.device", + "homeassistant.components.upnp.sensor", + "homeassistant.components.vizio.config_flow", + "homeassistant.components.vizio.media_player", + "homeassistant.components.withings", + "homeassistant.components.withings.binary_sensor", + "homeassistant.components.withings.common", + "homeassistant.components.withings.config_flow", + "homeassistant.components.xbox", + "homeassistant.components.xbox.base_sensor", + "homeassistant.components.xbox.binary_sensor", + "homeassistant.components.xbox.browse_media", + "homeassistant.components.xbox.media_source", + "homeassistant.components.xbox.sensor", + "homeassistant.components.xiaomi_aqara", + "homeassistant.components.xiaomi_aqara.binary_sensor", + "homeassistant.components.xiaomi_aqara.lock", + "homeassistant.components.xiaomi_aqara.sensor", + "homeassistant.components.xiaomi_miio", + "homeassistant.components.xiaomi_miio.air_quality", + "homeassistant.components.xiaomi_miio.binary_sensor", + "homeassistant.components.xiaomi_miio.device", + "homeassistant.components.xiaomi_miio.device_tracker", + "homeassistant.components.xiaomi_miio.fan", + "homeassistant.components.xiaomi_miio.humidifier", + "homeassistant.components.xiaomi_miio.light", + "homeassistant.components.xiaomi_miio.sensor", + "homeassistant.components.xiaomi_miio.switch", + "homeassistant.components.yeelight", + "homeassistant.components.yeelight.light", + "homeassistant.components.yeelight.scanner", + "homeassistant.components.zha.alarm_control_panel", + "homeassistant.components.zha.api", + "homeassistant.components.zha.binary_sensor", + "homeassistant.components.zha.button", + "homeassistant.components.zha.climate", + "homeassistant.components.zha.config_flow", + "homeassistant.components.zha.core.channels", + "homeassistant.components.zha.core.channels.base", + "homeassistant.components.zha.core.channels.closures", + "homeassistant.components.zha.core.channels.general", + "homeassistant.components.zha.core.channels.homeautomation", + "homeassistant.components.zha.core.channels.hvac", + "homeassistant.components.zha.core.channels.lighting", + "homeassistant.components.zha.core.channels.lightlink", + "homeassistant.components.zha.core.channels.manufacturerspecific", + "homeassistant.components.zha.core.channels.measurement", + "homeassistant.components.zha.core.channels.protocol", + "homeassistant.components.zha.core.channels.security", + "homeassistant.components.zha.core.channels.smartenergy", + "homeassistant.components.zha.core.decorators", + "homeassistant.components.zha.core.device", + "homeassistant.components.zha.core.discovery", + "homeassistant.components.zha.core.gateway", + "homeassistant.components.zha.core.group", + "homeassistant.components.zha.core.helpers", + "homeassistant.components.zha.core.registries", + "homeassistant.components.zha.core.store", + "homeassistant.components.zha.core.typing", + "homeassistant.components.zha.cover", + "homeassistant.components.zha.device_action", + "homeassistant.components.zha.device_tracker", + "homeassistant.components.zha.entity", + "homeassistant.components.zha.fan", + "homeassistant.components.zha.light", + "homeassistant.components.zha.lock", + "homeassistant.components.zha.select", + "homeassistant.components.zha.sensor", + "homeassistant.components.zha.siren", + "homeassistant.components.zha.switch", + "homeassistant.components.zwave", + "homeassistant.components.zwave.migration", + "homeassistant.components.zwave.node_entity", ] # Component modules which should set no_implicit_reexport = true. @@ -130,6 +353,19 @@ ] +def _strict_module_in_ignore_list( + module: str, ignored_modules_set: set[str] +) -> str | None: + if module in ignored_modules_set: + return module + if module.endswith("*"): + module = module[:-1] + for ignored_module in ignored_modules_set: + if ignored_module.startswith(module): + return ignored_module + return None + + def generate_and_validate(config: Config) -> str: """Validate and generate mypy config.""" @@ -162,9 +398,10 @@ def generate_and_validate(config: Config) -> str: config.add_error( "mypy_config", f"Only components should be added: {module}" ) - if module in ignored_modules_set: + if ignored_module := _strict_module_in_ignore_list(module, ignored_modules_set): config.add_error( - "mypy_config", f"Module '{module}' is in ignored list in mypy_config.py" + "mypy_config", + f"Module '{ignored_module}' is in ignored list in mypy_config.py", ) # Validate that all modules exist. From 4946f271c9c920f5f523c5bf6832dafdd5ed160d Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 3 Feb 2022 06:26:30 -0700 Subject: [PATCH 0221/1098] Bump pytile to 2022.02.0 (#65482) --- homeassistant/components/tile/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tile/manifest.json b/homeassistant/components/tile/manifest.json index 4ef1b579e1734a..dd0e78007f6347 100644 --- a/homeassistant/components/tile/manifest.json +++ b/homeassistant/components/tile/manifest.json @@ -3,7 +3,7 @@ "name": "Tile", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tile", - "requirements": ["pytile==2022.01.0"], + "requirements": ["pytile==2022.02.0"], "codeowners": ["@bachya"], "iot_class": "cloud_polling", "loggers": ["pytile"] diff --git a/requirements_all.txt b/requirements_all.txt index 9bb37f7a48a789..602eeae6b2ceb7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1999,7 +1999,7 @@ python_opendata_transport==0.3.0 pythonegardia==1.0.40 # homeassistant.components.tile -pytile==2022.01.0 +pytile==2022.02.0 # homeassistant.components.touchline pytouchline==0.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 74c0be71d6914e..7e4614d127ba64 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1233,7 +1233,7 @@ python-twitch-client==0.6.0 python_awair==0.2.1 # homeassistant.components.tile -pytile==2022.01.0 +pytile==2022.02.0 # homeassistant.components.traccar pytraccar==0.10.0 From 0ff93759d0f677aa733617eabb7edf8d2548862c Mon Sep 17 00:00:00 2001 From: Eduard van Valkenburg Date: Thu, 3 Feb 2022 14:29:12 +0100 Subject: [PATCH 0222/1098] Fix SIA availability (#65509) --- homeassistant/components/sia/const.py | 1 + homeassistant/components/sia/sia_entity_base.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sia/const.py b/homeassistant/components/sia/const.py index 82ef6ad94293b8..537c106fefa41f 100644 --- a/homeassistant/components/sia/const.py +++ b/homeassistant/components/sia/const.py @@ -38,3 +38,4 @@ KEY_POWER: Final = "power" PREVIOUS_STATE: Final = "previous_state" +AVAILABILITY_EVENT_CODE: Final = "RP" diff --git a/homeassistant/components/sia/sia_entity_base.py b/homeassistant/components/sia/sia_entity_base.py index fee3c0b2262834..311728ad578b7b 100644 --- a/homeassistant/components/sia/sia_entity_base.py +++ b/homeassistant/components/sia/sia_entity_base.py @@ -14,7 +14,7 @@ from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import StateType -from .const import DOMAIN, SIA_EVENT, SIA_HUB_ZONE +from .const import AVAILABILITY_EVENT_CODE, DOMAIN, SIA_EVENT, SIA_HUB_ZONE from .utils import get_attr_from_sia_event, get_unavailability_interval _LOGGER = logging.getLogger(__name__) @@ -105,7 +105,7 @@ def async_handle_event(self, sia_event: SIAEvent) -> None: return self._attr_extra_state_attributes.update(get_attr_from_sia_event(sia_event)) state_changed = self.update_state(sia_event) - if state_changed: + if state_changed or sia_event.code == AVAILABILITY_EVENT_CODE: self.async_reset_availability_cb() self.async_write_ha_state() From 82c4d344b245000290ee5d627254d8fe3aacf7fe Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 3 Feb 2022 06:31:15 -0700 Subject: [PATCH 0223/1098] Allow Flu Near You to re-attempt startup on error (#65481) --- homeassistant/components/flunearyou/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/flunearyou/__init__.py b/homeassistant/components/flunearyou/__init__.py index bb07e9ccc73034..9a2ef2d4465744 100644 --- a/homeassistant/components/flunearyou/__init__.py +++ b/homeassistant/components/flunearyou/__init__.py @@ -59,7 +59,7 @@ async def async_update(api_category: str) -> dict[str, Any]: update_interval=DEFAULT_UPDATE_INTERVAL, update_method=partial(async_update, api_category), ) - data_init_tasks.append(coordinator.async_refresh()) + data_init_tasks.append(coordinator.async_config_entry_first_refresh()) await asyncio.gather(*data_init_tasks) hass.data.setdefault(DOMAIN, {}) From ca1e2956621fe8a81b0993fc24e3cc7d26ce804a Mon Sep 17 00:00:00 2001 From: G Johansson Date: Thu, 3 Feb 2022 15:03:56 +0100 Subject: [PATCH 0224/1098] Implement diagnostics for Sensibo (#65515) --- .coveragerc | 1 + .../components/sensibo/diagnostics.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 homeassistant/components/sensibo/diagnostics.py diff --git a/.coveragerc b/.coveragerc index b592f1c416dd05..03e99959fd4f80 100644 --- a/.coveragerc +++ b/.coveragerc @@ -976,6 +976,7 @@ omit = homeassistant/components/sensibo/__init__.py homeassistant/components/sensibo/climate.py homeassistant/components/sensibo/coordinator.py + homeassistant/components/sensibo/diagnostics.py homeassistant/components/serial/sensor.py homeassistant/components/serial_pm/sensor.py homeassistant/components/sesame/lock.py diff --git a/homeassistant/components/sensibo/diagnostics.py b/homeassistant/components/sensibo/diagnostics.py new file mode 100644 index 00000000000000..d3e2382c7a807a --- /dev/null +++ b/homeassistant/components/sensibo/diagnostics.py @@ -0,0 +1,18 @@ +"""Diagnostics support for Sensibo.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import DOMAIN +from .coordinator import SensiboDataUpdateCoordinator + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + coordinator: SensiboDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + return coordinator.data From 63459feede5ddf9c86551f4b6484dabe509b4fba Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Thu, 3 Feb 2022 16:46:36 +0100 Subject: [PATCH 0225/1098] Return current state if template throws (#65534) --- homeassistant/components/mqtt/sensor.py | 2 +- tests/components/mqtt/test_sensor.py | 32 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index 6cf72279546968..a8cad4b09f896f 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -236,7 +236,7 @@ def _update_state(msg): self.hass, self._value_is_expired, expiration_at ) - payload = self._template(msg.payload) + payload = self._template(msg.payload, default=self._state) if payload is not None and self.device_class in ( SensorDeviceClass.DATE, diff --git a/tests/components/mqtt/test_sensor.py b/tests/components/mqtt/test_sensor.py index ad43a7b23533c4..c758b670b3d37e 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -268,6 +268,38 @@ async def test_setting_sensor_value_via_mqtt_json_message(hass, mqtt_mock): assert state.state == "100" +async def test_setting_sensor_value_via_mqtt_json_message_and_default_current_state( + hass, mqtt_mock +): + """Test the setting of the value via MQTT with fall back to current state.""" + assert await async_setup_component( + hass, + sensor.DOMAIN, + { + sensor.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "test-topic", + "unit_of_measurement": "fav unit", + "value_template": "{{ value_json.val | is_defined }}-{{ value_json.par }}", + } + }, + ) + await hass.async_block_till_done() + + async_fire_mqtt_message( + hass, "test-topic", '{ "val": "valcontent", "par": "parcontent" }' + ) + state = hass.states.get("sensor.test") + + assert state.state == "valcontent-parcontent" + + async_fire_mqtt_message(hass, "test-topic", '{ "par": "invalidcontent" }') + state = hass.states.get("sensor.test") + + assert state.state == "valcontent-parcontent" + + async def test_setting_sensor_last_reset_via_mqtt_message(hass, mqtt_mock, caplog): """Test the setting of the last_reset property via MQTT.""" assert await async_setup_component( From 3d434dffc77ab79c9426d12ea5126b06abd38354 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Thu, 3 Feb 2022 16:47:24 +0100 Subject: [PATCH 0226/1098] Add support Mqtt switch for unkown state (#65294) * Mqtt switch allow unkown state * correct type * Update discovery tests * Optimistic mode if not state_topic is configured. * Default state UNKNOWN in optimistic mode * fix discovery test --- homeassistant/components/mqtt/switch.py | 12 ++++++-- tests/components/mqtt/test_discovery.py | 14 +++++----- tests/components/mqtt/test_switch.py | 37 +++++++++++++++++++++++-- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/mqtt/switch.py b/homeassistant/components/mqtt/switch.py index 6576072e4076f0..cf5422a93eb0e2 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -51,6 +51,8 @@ CONF_STATE_ON = "state_on" CONF_STATE_OFF = "state_off" +PAYLOAD_NONE = "None" + PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, @@ -105,7 +107,7 @@ class MqttSwitch(MqttEntity, SwitchEntity, RestoreEntity): def __init__(self, hass, config, config_entry, discovery_data): """Initialize the MQTT switch.""" - self._state = False + self._state = None self._state_on = None self._state_off = None @@ -126,7 +128,9 @@ def _setup_from_config(self, config): state_off = config.get(CONF_STATE_OFF) self._state_off = state_off if state_off else config[CONF_PAYLOAD_OFF] - self._optimistic = config[CONF_OPTIMISTIC] + self._optimistic = ( + config[CONF_OPTIMISTIC] or config.get(CONF_STATE_TOPIC) is None + ) self._value_template = MqttValueTemplate( self._config.get(CONF_VALUE_TEMPLATE), entity=self @@ -144,6 +148,8 @@ def state_message_received(msg): self._state = True elif payload == self._state_off: self._state = False + elif payload == PAYLOAD_NONE: + self._state = None self.async_write_ha_state() @@ -172,7 +178,7 @@ async def _subscribe_topics(self): self._state = last_state.state == STATE_ON @property - def is_on(self): + def is_on(self) -> bool | None: """Return true if device is on.""" return self._state diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index de9150de1a2239..5d94f349c580e4 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -14,9 +14,9 @@ from homeassistant.components.mqtt.discovery import ALREADY_DISCOVERED, async_start from homeassistant.const import ( EVENT_STATE_CHANGED, - STATE_OFF, STATE_ON, STATE_UNAVAILABLE, + STATE_UNKNOWN, ) import homeassistant.core as ha @@ -649,7 +649,7 @@ async def test_discovery_expansion(hass, mqtt_mock, caplog): assert state is not None assert state.name == "DiscoveryExpansionTest1" assert ("switch", "bla") in hass.data[ALREADY_DISCOVERED] - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN async_fire_mqtt_message(hass, "test_topic/some/base/topic", "ON") @@ -699,7 +699,7 @@ async def test_discovery_expansion_2(hass, mqtt_mock, caplog): assert state is not None assert state.name == "DiscoveryExpansionTest1" assert ("switch", "bla") in hass.data[ALREADY_DISCOVERED] - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN @pytest.mark.no_fail_on_log_exception @@ -773,7 +773,7 @@ async def test_discovery_expansion_without_encoding_and_value_template_1( assert state is not None assert state.name == "DiscoveryExpansionTest1" assert ("switch", "bla") in hass.data[ALREADY_DISCOVERED] - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN async_fire_mqtt_message(hass, "some/base/topic/avail_item1", b"\x00") @@ -819,7 +819,7 @@ async def test_discovery_expansion_without_encoding_and_value_template_2( assert state is not None assert state.name == "DiscoveryExpansionTest1" assert ("switch", "bla") in hass.data[ALREADY_DISCOVERED] - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN async_fire_mqtt_message(hass, "some/base/topic/avail_item1", b"\x00") @@ -895,13 +895,13 @@ async def test_no_implicit_state_topic_switch(hass, mqtt_mock, caplog): assert state is not None assert state.name == "Test1" assert ("switch", "bla") in hass.data[ALREADY_DISCOVERED] - assert state.state == "off" + assert state.state == STATE_UNKNOWN assert state.attributes["assumed_state"] is True async_fire_mqtt_message(hass, "homeassistant/switch/bla/state", "ON") state = hass.states.get("switch.Test1") - assert state.state == "off" + assert state.state == STATE_UNKNOWN @pytest.mark.parametrize( diff --git a/tests/components/mqtt/test_switch.py b/tests/components/mqtt/test_switch.py index 9519d7321ffdd9..3eb998193a07ca 100644 --- a/tests/components/mqtt/test_switch.py +++ b/tests/components/mqtt/test_switch.py @@ -11,6 +11,7 @@ ATTR_DEVICE_CLASS, STATE_OFF, STATE_ON, + STATE_UNKNOWN, ) import homeassistant.core as ha from homeassistant.setup import async_setup_component @@ -71,7 +72,7 @@ async def test_controlling_state_via_topic(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("switch.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get(ATTR_DEVICE_CLASS) == "switch" assert not state.attributes.get(ATTR_ASSUMED_STATE) @@ -85,6 +86,11 @@ async def test_controlling_state_via_topic(hass, mqtt_mock): state = hass.states.get("switch.test") assert state.state == STATE_OFF + async_fire_mqtt_message(hass, "state-topic", "None") + + state = hass.states.get("switch.test") + assert state.state == STATE_UNKNOWN + async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): """Test the sending MQTT commands in optimistic mode.""" @@ -132,6 +138,26 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): assert state.state == STATE_OFF +async def test_sending_inital_state_and_optimistic(hass, mqtt_mock): + """Test the initial state in optimistic mode.""" + assert await async_setup_component( + hass, + switch.DOMAIN, + { + switch.DOMAIN: { + "platform": "mqtt", + "name": "test", + "command_topic": "command-topic", + } + }, + ) + await hass.async_block_till_done() + + state = hass.states.get("switch.test") + assert state.state == STATE_UNKNOWN + assert state.attributes.get(ATTR_ASSUMED_STATE) + + async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock): """Test the controlling state via topic and JSON message.""" assert await async_setup_component( @@ -152,7 +178,7 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("switch.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN async_fire_mqtt_message(hass, "state-topic", '{"val":"beer on"}') @@ -164,6 +190,11 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock): state = hass.states.get("switch.test") assert state.state == STATE_OFF + async_fire_mqtt_message(hass, "state-topic", '{"val": null}') + + state = hass.states.get("switch.test") + assert state.state == STATE_UNKNOWN + async def test_availability_when_connection_lost(hass, mqtt_mock): """Test availability after MQTT disconnection.""" @@ -236,7 +267,7 @@ async def test_custom_state_payload(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("switch.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "state-topic", "HIGH") From 711bd7169e32a4085fa8ad884f9daf9aa716cb9a Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Thu, 3 Feb 2022 16:48:03 +0100 Subject: [PATCH 0227/1098] Add Mqtt Fan unknown state support (#65301) * Add Mqtt Fan unknown state support * Update homeassistant/components/mqtt/fan.py Co-authored-by: Erik Montnemery * Adjust default state in tests Co-authored-by: Erik Montnemery --- homeassistant/components/mqtt/fan.py | 8 ++++++-- tests/components/mqtt/test_fan.py | 29 ++++++++++++++++++---------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index f6d60ae8cbe7a5..f5b347d6b71a20 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -94,6 +94,8 @@ OSCILLATE_ON_PAYLOAD = "oscillate_on" OSCILLATE_OFF_PAYLOAD = "oscillate_off" +PAYLOAD_NONE = "None" + MQTT_FAN_ATTRIBUTES_BLOCKED = frozenset( { fan.ATTR_DIRECTION, @@ -243,7 +245,7 @@ class MqttFan(MqttEntity, FanEntity): def __init__(self, hass, config, config_entry, discovery_data): """Initialize the MQTT fan.""" - self._state = False + self._state = None self._percentage = None self._preset_mode = None self._oscillation = None @@ -367,6 +369,8 @@ def state_received(msg): self._state = True elif payload == self._payload["STATE_OFF"]: self._state = False + elif payload == PAYLOAD_NONE: + self._state = None self.async_write_ha_state() if self._topic[CONF_STATE_TOPIC] is not None: @@ -493,7 +497,7 @@ def assumed_state(self): return self._optimistic @property - def is_on(self): + def is_on(self) -> bool | None: """Return true if device is on.""" return self._state diff --git a/tests/components/mqtt/test_fan.py b/tests/components/mqtt/test_fan.py index 0a3b2499dc2c5a..ecc1f15c2040e1 100644 --- a/tests/components/mqtt/test_fan.py +++ b/tests/components/mqtt/test_fan.py @@ -27,6 +27,7 @@ ATTR_SUPPORTED_FEATURES, STATE_OFF, STATE_ON, + STATE_UNKNOWN, ) from homeassistant.setup import async_setup_component @@ -119,7 +120,7 @@ async def test_controlling_state_via_topic(hass, mqtt_mock, caplog): await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "state-topic", "StAtE_On") @@ -194,6 +195,10 @@ async def test_controlling_state_via_topic(hass, mqtt_mock, caplog): assert state.attributes.get(fan.ATTR_PERCENTAGE) is None assert state.attributes.get(fan.ATTR_SPEED) is None + async_fire_mqtt_message(hass, "state-topic", "None") + state = hass.states.get("fan.test") + assert state.state == STATE_UNKNOWN + async def test_controlling_state_via_topic_with_different_speed_range( hass, mqtt_mock, caplog @@ -285,7 +290,7 @@ async def test_controlling_state_via_topic_no_percentage_topics( await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "preset-mode-state-topic", "smart") @@ -349,13 +354,17 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock, cap await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "state-topic", '{"val":"ON"}') state = hass.states.get("fan.test") assert state.state == STATE_ON + async_fire_mqtt_message(hass, "state-topic", '{"val": null}') + state = hass.states.get("fan.test") + assert state.state == STATE_UNKNOWN + async_fire_mqtt_message(hass, "state-topic", '{"val":"OFF"}') state = hass.states.get("fan.test") assert state.state == STATE_OFF @@ -449,7 +458,7 @@ async def test_controlling_state_via_topic_and_json_message_shared_topic( await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message( @@ -527,7 +536,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock, caplog): await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_turn_on(hass, "fan.test") @@ -748,7 +757,7 @@ async def test_sending_mqtt_commands_and_optimistic_no_legacy(hass, mqtt_mock, c await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_turn_on(hass, "fan.test") @@ -883,7 +892,7 @@ async def test_sending_mqtt_command_templates_(hass, mqtt_mock, caplog): await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_turn_on(hass, "fan.test") @@ -1022,7 +1031,7 @@ async def test_sending_mqtt_commands_and_optimistic_no_percentage_topic( await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_set_preset_mode(hass, "fan.test", "medium") @@ -1086,7 +1095,7 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock, ca await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_turn_on(hass, "fan.test") @@ -1344,7 +1353,7 @@ async def test_attributes(hass, mqtt_mock, caplog): await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on(hass, "fan.test") state = hass.states.get("fan.test") From 2d011821ea0e452c9e86a7107632650c9e018cdd Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Thu, 3 Feb 2022 16:49:09 +0100 Subject: [PATCH 0228/1098] Add MQTT humidifier unknown state support (#65302) * Add MQTT humidifier unknown state support * Update homeassistant/components/mqtt/humidifier.py Co-authored-by: Erik Montnemery * Fix tests for changed default optimistic state Co-authored-by: Erik Montnemery --- homeassistant/components/mqtt/humidifier.py | 8 +++++-- tests/components/mqtt/test_humidifier.py | 23 ++++++++++++++------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/mqtt/humidifier.py b/homeassistant/components/mqtt/humidifier.py index b2c4ed4b916046..e5f4cea6f88b2b 100644 --- a/homeassistant/components/mqtt/humidifier.py +++ b/homeassistant/components/mqtt/humidifier.py @@ -66,6 +66,8 @@ DEFAULT_PAYLOAD_OFF = "OFF" DEFAULT_PAYLOAD_RESET = "None" +PAYLOAD_NONE = "None" + MQTT_HUMIDIFIER_ATTRIBUTES_BLOCKED = frozenset( { humidifier.ATTR_HUMIDITY, @@ -187,7 +189,7 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): def __init__(self, hass, config, config_entry, discovery_data): """Initialize the MQTT humidifier.""" - self._state = False + self._state = None self._target_humidity = None self._mode = None self._supported_features = 0 @@ -283,6 +285,8 @@ def state_received(msg): self._state = True elif payload == self._payload["STATE_OFF"]: self._state = False + elif payload == PAYLOAD_NONE: + self._state = None self.async_write_ha_state() if self._topic[CONF_STATE_TOPIC] is not None: @@ -392,7 +396,7 @@ def available_modes(self) -> list: return self._available_modes @property - def is_on(self): + def is_on(self) -> bool | None: """Return true if device is on.""" return self._state diff --git a/tests/components/mqtt/test_humidifier.py b/tests/components/mqtt/test_humidifier.py index 62d29c12ee82be..48fe5b29a0c18f 100644 --- a/tests/components/mqtt/test_humidifier.py +++ b/tests/components/mqtt/test_humidifier.py @@ -28,6 +28,7 @@ SERVICE_TURN_ON, STATE_OFF, STATE_ON, + STATE_UNKNOWN, ) from homeassistant.setup import async_setup_component @@ -157,7 +158,7 @@ async def test_controlling_state_via_topic(hass, mqtt_mock, caplog): await hass.async_block_till_done() state = hass.states.get("humidifier.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "state-topic", "StAtE_On") @@ -220,6 +221,10 @@ async def test_controlling_state_via_topic(hass, mqtt_mock, caplog): state = hass.states.get("humidifier.test") assert state.attributes.get(humidifier.ATTR_HUMIDITY) is None + async_fire_mqtt_message(hass, "state-topic", "None") + state = hass.states.get("humidifier.test") + assert state.state == STATE_UNKNOWN + async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock, caplog): """Test the controlling state via topic and JSON message.""" @@ -250,7 +255,7 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock, cap await hass.async_block_till_done() state = hass.states.get("humidifier.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "state-topic", '{"val":"ON"}') @@ -301,6 +306,10 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock, cap assert "Ignoring empty mode from" in caplog.text caplog.clear() + async_fire_mqtt_message(hass, "state-topic", '{"val": null}') + state = hass.states.get("humidifier.test") + assert state.state == STATE_UNKNOWN + async def test_controlling_state_via_topic_and_json_message_shared_topic( hass, mqtt_mock, caplog @@ -333,7 +342,7 @@ async def test_controlling_state_via_topic_and_json_message_shared_topic( await hass.async_block_till_done() state = hass.states.get("humidifier.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message( @@ -404,7 +413,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock, caplog): await hass.async_block_till_done() state = hass.states.get("humidifier.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get(ATTR_ASSUMED_STATE) await async_turn_on(hass, "humidifier.test") @@ -498,7 +507,7 @@ async def test_sending_mqtt_command_templates_(hass, mqtt_mock, caplog): await hass.async_block_till_done() state = hass.states.get("humidifier.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get(ATTR_ASSUMED_STATE) await async_turn_on(hass, "humidifier.test") @@ -593,7 +602,7 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock, ca await hass.async_block_till_done() state = hass.states.get("humidifier.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get(ATTR_ASSUMED_STATE) await async_turn_on(hass, "humidifier.test") @@ -731,7 +740,7 @@ async def test_attributes(hass, mqtt_mock, caplog): await hass.async_block_till_done() state = hass.states.get("humidifier.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get(humidifier.ATTR_AVAILABLE_MODES) == [ "eco", "baby", From cf523572294a89d3d44923aaa67d6ac28cbd940e Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Thu, 3 Feb 2022 16:49:57 +0100 Subject: [PATCH 0229/1098] Add MQTT light unknown state support (#65308) * Add MQTT light unknown sate support * Update homeassistant/components/mqtt/light/schema_basic.py Co-authored-by: Erik Montnemery * Update homeassistant/components/mqtt/light/schema_json.py Co-authored-by: Erik Montnemery * Update homeassistant/components/mqtt/light/schema_template.py Co-authored-by: Erik Montnemery * Update tests for default unknown state Co-authored-by: Erik Montnemery --- .../components/mqtt/light/schema_basic.py | 10 ++- .../components/mqtt/light/schema_json.py | 6 +- .../components/mqtt/light/schema_template.py | 6 +- tests/components/mqtt/test_light.py | 76 +++++++++++-------- tests/components/mqtt/test_light_json.py | 39 ++++++---- tests/components/mqtt/test_light_template.py | 42 +++++++--- 6 files changed, 116 insertions(+), 63 deletions(-) diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index f164abe52975ad..d917b379eab779 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -109,6 +109,8 @@ CONF_WHITE_VALUE_TEMPLATE = "white_value_template" CONF_ON_COMMAND_TYPE = "on_command_type" +PAYLOAD_NONE = "None" + MQTT_LIGHT_ATTRIBUTES_BLOCKED = frozenset( { ATTR_COLOR_MODE, @@ -257,7 +259,7 @@ def __init__(self, hass, config, config_entry, discovery_data): self._rgb_color = None self._rgbw_color = None self._rgbww_color = None - self._state = False + self._state = None self._supported_color_modes = None self._white_value = None self._xy_color = None @@ -435,9 +437,7 @@ def add_topic(topic, msg_callback): @log_messages(self.hass, self.entity_id) def state_received(msg): """Handle new MQTT messages.""" - payload = self._value_templates[CONF_STATE_VALUE_TEMPLATE]( - msg.payload, None - ) + payload = self._value_templates[CONF_STATE_VALUE_TEMPLATE](msg.payload) if not payload: _LOGGER.debug("Ignoring empty state message from '%s'", msg.topic) return @@ -446,6 +446,8 @@ def state_received(msg): self._state = True elif payload == self._payload["off"]: self._state = False + elif payload == PAYLOAD_NONE: + self._state = None self.async_write_ha_state() if self._topic[CONF_STATE_TOPIC] is not None: diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index 2d9b8f6a388ef7..32435948b1e19a 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -100,6 +100,8 @@ CONF_MAX_MIREDS = "max_mireds" CONF_MIN_MIREDS = "min_mireds" +PAYLOAD_NONE = "None" + def valid_color_configuration(config): """Test color_mode is not combined with deprecated config.""" @@ -179,7 +181,7 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): def __init__(self, hass, config, config_entry, discovery_data): """Initialize MQTT JSON light.""" - self._state = False + self._state = None self._supported_features = 0 self._topic = None @@ -317,6 +319,8 @@ def state_received(msg): self._state = True elif values["state"] == "OFF": self._state = False + elif values["state"] is None: + self._state = None if self._supported_features and SUPPORT_COLOR and "color" in values: if values["color"] is None: diff --git a/homeassistant/components/mqtt/light/schema_template.py b/homeassistant/components/mqtt/light/schema_template.py index 736ff98f321884..b7f03fd0508457 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -67,6 +67,8 @@ CONF_RED_TEMPLATE = "red_template" CONF_WHITE_VALUE_TEMPLATE = "white_value_template" +PAYLOAD_NONE = "None" + PLATFORM_SCHEMA_TEMPLATE = ( mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( { @@ -109,7 +111,7 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): def __init__(self, hass, config, config_entry, discovery_data): """Initialize a MQTT Template light.""" - self._state = False + self._state = None self._topics = None self._templates = None @@ -173,6 +175,8 @@ def state_received(msg): self._state = True elif state == STATE_OFF: self._state = False + elif state == PAYLOAD_NONE: + self._state = None else: _LOGGER.warning("Invalid state value received") diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index dcff826311bbc6..a1f929244a0a19 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -177,6 +177,7 @@ ATTR_SUPPORTED_FEATURES, STATE_OFF, STATE_ON, + STATE_UNKNOWN, ) import homeassistant.core as ha from homeassistant.setup import async_setup_component @@ -269,7 +270,7 @@ async def test_no_color_brightness_color_temp_hs_white_xy_if_no_topics(hass, mqt await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get("rgb_color") is None assert state.attributes.get("brightness") is None assert state.attributes.get("color_temp") is None @@ -298,6 +299,16 @@ async def test_no_color_brightness_color_temp_hs_white_xy_if_no_topics(hass, mqt assert state.attributes.get(light.ATTR_COLOR_MODE) == "onoff" assert state.attributes.get(light.ATTR_SUPPORTED_COLOR_MODES) == ["onoff"] + async_fire_mqtt_message(hass, "test_light_rgb/status", "OFF") + + state = hass.states.get("light.test") + assert state.state == STATE_OFF + + async_fire_mqtt_message(hass, "test_light_rgb/status", "None") + + state = hass.states.get("light.test") + assert state.state == STATE_UNKNOWN + async def test_legacy_controlling_state_via_topic(hass, mqtt_mock): """Test the controlling of the state via topic for legacy light (white_value).""" @@ -332,7 +343,7 @@ async def test_legacy_controlling_state_via_topic(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get("rgb_color") is None assert state.attributes.get("brightness") is None assert state.attributes.get("color_temp") is None @@ -463,7 +474,7 @@ async def test_controlling_state_via_topic(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get("rgb_color") is None assert state.attributes.get("brightness") is None assert state.attributes.get("color_temp") is None @@ -581,7 +592,7 @@ async def test_legacy_invalid_state_via_topic(hass, mqtt_mock, caplog): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get("rgb_color") is None assert state.attributes.get("brightness") is None assert state.attributes.get("color_temp") is None @@ -700,7 +711,7 @@ async def test_invalid_state_via_topic(hass, mqtt_mock, caplog): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get("rgb_color") is None assert state.attributes.get("rgbw_color") is None assert state.attributes.get("rgbww_color") is None @@ -823,7 +834,7 @@ async def test_brightness_controlling_scale(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get("brightness") is None assert not state.attributes.get(ATTR_ASSUMED_STATE) @@ -869,7 +880,7 @@ async def test_brightness_from_rgb_controlling_scale(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get("brightness") is None assert not state.attributes.get(ATTR_ASSUMED_STATE) @@ -909,7 +920,7 @@ async def test_legacy_white_value_controlling_scale(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get("white_value") is None assert not state.attributes.get(ATTR_ASSUMED_STATE) @@ -969,7 +980,7 @@ async def test_legacy_controlling_state_via_topic_with_templates(hass, mqtt_mock await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get("brightness") is None assert state.attributes.get("rgb_color") is None @@ -1016,6 +1027,10 @@ async def test_legacy_controlling_state_via_topic_with_templates(hass, mqtt_mock state = hass.states.get("light.test") assert state.attributes.get("xy_color") == (0.14, 0.131) + async_fire_mqtt_message(hass, "test_light_rgb/status", '{"hello": null}') + state = hass.states.get("light.test") + assert state.state == STATE_UNKNOWN + async def test_controlling_state_via_topic_with_templates(hass, mqtt_mock): """Test the setting of the state with a template.""" @@ -1058,7 +1073,7 @@ async def test_controlling_state_via_topic_with_templates(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get("brightness") is None assert state.attributes.get("rgb_color") is None @@ -1456,7 +1471,7 @@ async def test_sending_mqtt_rgb_command_with_template(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on(hass, "light.test", rgb_color=[255, 128, 64]) @@ -1493,7 +1508,7 @@ async def test_sending_mqtt_rgbw_command_with_template(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on(hass, "light.test", rgbw_color=[255, 128, 64, 32]) @@ -1530,7 +1545,7 @@ async def test_sending_mqtt_rgbww_command_with_template(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on(hass, "light.test", rgbww_color=[255, 128, 64, 32, 16]) @@ -1566,7 +1581,7 @@ async def test_sending_mqtt_color_temp_command_with_template(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on(hass, "light.test", color_temp=100) @@ -1599,7 +1614,7 @@ async def test_on_command_first(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on(hass, "light.test", brightness=50) @@ -1634,7 +1649,7 @@ async def test_on_command_last(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on(hass, "light.test", brightness=50) @@ -1671,7 +1686,7 @@ async def test_on_command_brightness(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN # Turn on w/ no brightness - should set to max await common.async_turn_on(hass, "light.test") @@ -1727,7 +1742,7 @@ async def test_on_command_brightness_scaled(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN # Turn on w/ no brightness - should set to max await common.async_turn_on(hass, "light.test") @@ -1795,7 +1810,7 @@ async def test_legacy_on_command_rgb(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on(hass, "light.test", brightness=127) @@ -1885,7 +1900,7 @@ async def test_on_command_rgb(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on(hass, "light.test", brightness=127) @@ -1975,7 +1990,7 @@ async def test_on_command_rgbw(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on(hass, "light.test", brightness=127) @@ -2065,7 +2080,7 @@ async def test_on_command_rgbww(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on(hass, "light.test", brightness=127) @@ -2156,7 +2171,7 @@ async def test_on_command_rgb_template(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on(hass, "light.test", brightness=127) @@ -2193,8 +2208,7 @@ async def test_on_command_rgbw_template(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF - + assert state.state == STATE_UNKNOWN await common.async_turn_on(hass, "light.test", brightness=127) # Should get the following MQTT messages. @@ -2230,7 +2244,7 @@ async def test_on_command_rgbww_template(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on(hass, "light.test", brightness=127) @@ -2279,7 +2293,7 @@ async def test_on_command_white(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get("brightness") is None assert state.attributes.get("rgb_color") is None assert state.attributes.get(light.ATTR_COLOR_MODE) is None @@ -2364,7 +2378,7 @@ async def test_explicit_color_mode(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get("rgb_color") is None assert state.attributes.get("brightness") is None assert state.attributes.get("color_temp") is None @@ -2505,7 +2519,7 @@ async def test_explicit_color_mode_templated(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get("brightness") is None assert state.attributes.get("color_temp") is None assert state.attributes.get("hs_color") is None @@ -2591,7 +2605,7 @@ async def test_white_state_update(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get("brightness") is None assert state.attributes.get("rgb_color") is None assert state.attributes.get(light.ATTR_COLOR_MODE) is None @@ -2639,7 +2653,7 @@ async def test_effect(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on(hass, "light.test", effect="rainbow") diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index baad644bf6db28..2811e44618d350 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -102,6 +102,7 @@ ATTR_SUPPORTED_FEATURES, STATE_OFF, STATE_ON, + STATE_UNKNOWN, ) import homeassistant.core as ha from homeassistant.setup import async_setup_component @@ -268,7 +269,7 @@ async def test_no_color_brightness_color_temp_white_val_if_no_topics(hass, mqtt_ await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN expected_features = light.SUPPORT_FLASH | light.SUPPORT_TRANSITION assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features assert state.attributes.get("rgb_color") is None @@ -291,6 +292,16 @@ async def test_no_color_brightness_color_temp_white_val_if_no_topics(hass, mqtt_ assert state.attributes.get("xy_color") is None assert state.attributes.get("hs_color") is None + async_fire_mqtt_message(hass, "test_light_rgb", '{"state":"OFF"}') + + state = hass.states.get("light.test") + assert state.state == STATE_OFF + + async_fire_mqtt_message(hass, "test_light_rgb", '{"state": null}') + + state = hass.states.get("light.test") + assert state.state == STATE_UNKNOWN + async def test_controlling_state_via_topic(hass, mqtt_mock): """Test the controlling of the state via topic.""" @@ -318,7 +329,7 @@ async def test_controlling_state_via_topic(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN expected_features = ( light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR @@ -446,7 +457,7 @@ async def test_controlling_state_via_topic2(hass, mqtt_mock, caplog): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN expected_features = ( light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR @@ -960,7 +971,7 @@ async def test_sending_hs_color(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN mqtt_mock.reset_mock() await common.async_turn_on( @@ -1023,7 +1034,7 @@ async def test_sending_rgb_color_no_brightness(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on( hass, "light.test", brightness=50, xy_color=[0.123, 0.123] @@ -1078,7 +1089,7 @@ async def test_sending_rgb_color_no_brightness2(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on( hass, "light.test", brightness=50, xy_color=[0.123, 0.123] @@ -1155,7 +1166,7 @@ async def test_sending_rgb_color_with_brightness(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on( hass, "light.test", brightness=50, xy_color=[0.123, 0.123] @@ -1226,7 +1237,7 @@ async def test_sending_rgb_color_with_scaled_brightness(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on( hass, "light.test", brightness=50, xy_color=[0.123, 0.123] @@ -1296,7 +1307,7 @@ async def test_sending_xy_color(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on( hass, "light.test", brightness=50, xy_color=[0.123, 0.123] @@ -1359,7 +1370,7 @@ async def test_effect(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN expected_features = ( light.SUPPORT_EFFECT | light.SUPPORT_FLASH | light.SUPPORT_TRANSITION ) @@ -1422,7 +1433,7 @@ async def test_flash_short_and_long(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN expected_features = light.SUPPORT_FLASH | light.SUPPORT_TRANSITION assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features @@ -1481,7 +1492,7 @@ async def test_transition(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN expected_features = light.SUPPORT_FLASH | light.SUPPORT_TRANSITION assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features await common.async_turn_on(hass, "light.test", transition=15) @@ -1529,7 +1540,7 @@ async def test_brightness_scale(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get("brightness") is None assert not state.attributes.get(ATTR_ASSUMED_STATE) @@ -1573,7 +1584,7 @@ async def test_invalid_values(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN expected_features = ( light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR diff --git a/tests/components/mqtt/test_light_template.py b/tests/components/mqtt/test_light_template.py index 3a8ecd357c3287..45977343b95098 100644 --- a/tests/components/mqtt/test_light_template.py +++ b/tests/components/mqtt/test_light_template.py @@ -40,6 +40,7 @@ ATTR_SUPPORTED_FEATURES, STATE_OFF, STATE_ON, + STATE_UNKNOWN, ) import homeassistant.core as ha from homeassistant.setup import async_setup_component @@ -171,6 +172,7 @@ async def test_rgb_light(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") + assert state.state == STATE_UNKNOWN expected_features = ( light.SUPPORT_TRANSITION | light.SUPPORT_COLOR @@ -208,7 +210,7 @@ async def test_state_change_via_topic(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get("rgb_color") is None assert state.attributes.get("brightness") is None assert state.attributes.get("color_temp") is None @@ -224,6 +226,16 @@ async def test_state_change_via_topic(hass, mqtt_mock): assert state.attributes.get("color_temp") is None assert state.attributes.get("white_value") is None + async_fire_mqtt_message(hass, "test_light_rgb", "off") + + state = hass.states.get("light.test") + assert state.state == STATE_OFF + + async_fire_mqtt_message(hass, "test_light_rgb", "None") + + state = hass.states.get("light.test") + assert state.state == STATE_UNKNOWN + async def test_state_brightness_color_effect_temp_white_change_via_topic( hass, mqtt_mock @@ -264,7 +276,7 @@ async def test_state_brightness_color_effect_temp_white_change_via_topic( await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get("rgb_color") is None assert state.attributes.get("brightness") is None assert state.attributes.get("effect") is None @@ -283,6 +295,12 @@ async def test_state_brightness_color_effect_temp_white_change_via_topic( assert state.attributes.get("white_value") == 123 assert state.attributes.get("effect") is None + # make the light state unknown + async_fire_mqtt_message(hass, "test_light_rgb", "None") + + state = hass.states.get("light.test") + assert state.state == STATE_UNKNOWN + # turn the light off async_fire_mqtt_message(hass, "test_light_rgb", "off") @@ -514,7 +532,7 @@ async def test_sending_mqtt_commands_non_optimistic_brightness_template( await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert not state.attributes.get("brightness") assert not state.attributes.get("hs_color") assert not state.attributes.get("effect") @@ -528,7 +546,7 @@ async def test_sending_mqtt_commands_non_optimistic_brightness_template( ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on(hass, "light.test") mqtt_mock.async_publish.assert_called_once_with( @@ -536,7 +554,7 @@ async def test_sending_mqtt_commands_non_optimistic_brightness_template( ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN # Set color_temp await common.async_turn_on(hass, "light.test", color_temp=70) @@ -545,7 +563,7 @@ async def test_sending_mqtt_commands_non_optimistic_brightness_template( ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert not state.attributes.get("color_temp") # Set full brightness @@ -555,7 +573,7 @@ async def test_sending_mqtt_commands_non_optimistic_brightness_template( ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert not state.attributes.get("brightness") # Full brightness - no scaling of RGB values sent over MQTT @@ -567,7 +585,7 @@ async def test_sending_mqtt_commands_non_optimistic_brightness_template( ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert not state.attributes.get("white_value") assert not state.attributes.get("rgb_color") @@ -628,7 +646,7 @@ async def test_effect(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 44 await common.async_turn_on(hass, "light.test") @@ -679,7 +697,7 @@ async def test_flash(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 40 await common.async_turn_on(hass, "light.test") @@ -727,7 +745,7 @@ async def test_transition(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 40 @@ -783,7 +801,7 @@ async def test_invalid_values(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get("rgb_color") is None assert state.attributes.get("brightness") is None assert state.attributes.get("color_temp") is None From 2f0d0998a2200e2b20947fdf4bd79a95a861fa23 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Thu, 3 Feb 2022 16:50:39 +0100 Subject: [PATCH 0230/1098] Add Mqtt vacuum `unknown` state (#65311) * Add Mqtt vacuum `unknown` status * Update tests/components/mqtt/test_state_vacuum.py Co-authored-by: Erik Montnemery Co-authored-by: Erik Montnemery --- homeassistant/components/mqtt/vacuum/schema_state.py | 8 ++++++-- tests/components/mqtt/test_state_vacuum.py | 7 +++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/mqtt/vacuum/schema_state.py b/homeassistant/components/mqtt/vacuum/schema_state.py index 1eb763c76cd18a..494fc60fabd1bd 100644 --- a/homeassistant/components/mqtt/vacuum/schema_state.py +++ b/homeassistant/components/mqtt/vacuum/schema_state.py @@ -206,8 +206,12 @@ def _prepare_subscribe_topics(self): def state_message_received(msg): """Handle state MQTT message.""" payload = json.loads(msg.payload) - if STATE in payload and payload[STATE] in POSSIBLE_STATES: - self._state = POSSIBLE_STATES[payload[STATE]] + if STATE in payload and ( + payload[STATE] in POSSIBLE_STATES or payload[STATE] is None + ): + self._state = ( + POSSIBLE_STATES[payload[STATE]] if payload[STATE] else None + ) del payload[STATE] self._state_attrs.update(payload) self.async_write_ha_state() diff --git a/tests/components/mqtt/test_state_vacuum.py b/tests/components/mqtt/test_state_vacuum.py index 5011f279470ec9..a1b90f52c37206 100644 --- a/tests/components/mqtt/test_state_vacuum.py +++ b/tests/components/mqtt/test_state_vacuum.py @@ -235,6 +235,8 @@ async def test_status(hass, mqtt_mock): assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) await hass.async_block_till_done() + state = hass.states.get("vacuum.mqtttest") + assert state.state == STATE_UNKNOWN message = """{ "battery_level": 54, @@ -262,6 +264,11 @@ async def test_status(hass, mqtt_mock): assert state.attributes.get(ATTR_FAN_SPEED) == "min" assert state.attributes.get(ATTR_FAN_SPEED_LIST) == ["min", "medium", "high", "max"] + message = '{"state":null}' + async_fire_mqtt_message(hass, "vacuum/state", message) + state = hass.states.get("vacuum.mqtttest") + assert state.state == STATE_UNKNOWN + async def test_no_fan_vacuum(hass, mqtt_mock): """Test status updates from the vacuum when fan is not supported.""" From 6c38a6b5697bcf4587e00101771001bf596974f9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 3 Feb 2022 10:02:05 -0600 Subject: [PATCH 0231/1098] Enable strict typing for isy994 (#65439) Co-authored-by: Martin Hjelmare --- .strict-typing | 1 + homeassistant/components/isy994/__init__.py | 15 +- .../components/isy994/binary_sensor.py | 152 +++++++++++------- homeassistant/components/isy994/climate.py | 31 ++-- .../components/isy994/config_flow.py | 37 +++-- homeassistant/components/isy994/const.py | 2 +- homeassistant/components/isy994/cover.py | 33 ++-- homeassistant/components/isy994/entity.py | 64 +++++--- homeassistant/components/isy994/fan.py | 37 ++--- homeassistant/components/isy994/helpers.py | 64 +++++--- homeassistant/components/isy994/light.py | 32 ++-- homeassistant/components/isy994/lock.py | 23 +-- homeassistant/components/isy994/sensor.py | 33 ++-- homeassistant/components/isy994/services.py | 10 +- homeassistant/components/isy994/switch.py | 25 +-- .../components/isy994/system_health.py | 8 +- mypy.ini | 50 ++---- script/hassfest/mypy_config.py | 13 -- 18 files changed, 351 insertions(+), 279 deletions(-) diff --git a/.strict-typing b/.strict-typing index 00ce5de2b3e964..cd74910415c2b5 100644 --- a/.strict-typing +++ b/.strict-typing @@ -95,6 +95,7 @@ homeassistant.components.image_processing.* homeassistant.components.input_button.* homeassistant.components.input_select.* homeassistant.components.integration.* +homeassistant.components.isy994.* homeassistant.components.iqvia.* homeassistant.components.jellyfin.* homeassistant.components.jewish_calendar.* diff --git a/homeassistant/components/isy994/__init__.py b/homeassistant/components/isy994/__init__.py index b66e6d4676e4c4..ba152c5d840181 100644 --- a/homeassistant/components/isy994/__init__.py +++ b/homeassistant/components/isy994/__init__.py @@ -16,7 +16,7 @@ CONF_USERNAME, EVENT_HOMEASSISTANT_STOP, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client, config_validation as cv import homeassistant.helpers.device_registry as dr @@ -98,10 +98,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: @callback -def _async_find_matching_config_entry(hass): +def _async_find_matching_config_entry( + hass: HomeAssistant, +) -> config_entries.ConfigEntry | None: for entry in hass.config_entries.async_entries(DOMAIN): if entry.source == config_entries.SOURCE_IMPORT: return entry + return None async def async_setup_entry( @@ -147,7 +150,7 @@ async def async_setup_entry( https = False port = host.port or 80 session = aiohttp_client.async_create_clientsession( - hass, verify_ssl=None, cookie_jar=CookieJar(unsafe=True) + hass, verify_ssl=False, cookie_jar=CookieJar(unsafe=True) ) elif host.scheme == "https": https = True @@ -206,7 +209,7 @@ async def async_setup_entry( hass.config_entries.async_setup_platforms(entry, PLATFORMS) @callback - def _async_stop_auto_update(event) -> None: + def _async_stop_auto_update(event: Event) -> None: """Stop the isy auto update on Home Assistant Shutdown.""" _LOGGER.debug("ISY Stopping Event Stream and automatic updates") isy.websocket.stop() @@ -235,7 +238,7 @@ async def _async_update_listener( @callback def _async_import_options_from_data_if_missing( hass: HomeAssistant, entry: config_entries.ConfigEntry -): +) -> None: options = dict(entry.options) modified = False for importable_option in ( @@ -261,7 +264,7 @@ def _async_isy_to_configuration_url(isy: ISY) -> str: @callback def _async_get_or_create_isy_device_in_registry( - hass: HomeAssistant, entry: config_entries.ConfigEntry, isy + hass: HomeAssistant, entry: config_entries.ConfigEntry, isy: ISY ) -> None: device_registry = dr.async_get(hass) url = _async_isy_to_configuration_url(isy) diff --git a/homeassistant/components/isy994/binary_sensor.py b/homeassistant/components/isy994/binary_sensor.py index 5cf7d6b2b76594..23e77ba849d51a 100644 --- a/homeassistant/components/isy994/binary_sensor.py +++ b/homeassistant/components/isy994/binary_sensor.py @@ -1,7 +1,8 @@ """Support for ISY994 binary sensors.""" from __future__ import annotations -from datetime import timedelta +from datetime import datetime, timedelta +from typing import Any from pyisy.constants import ( CMD_OFF, @@ -10,6 +11,7 @@ PROTO_INSTEON, PROTO_ZWAVE, ) +from pyisy.helpers import NodeProperty from pyisy.nodes import Group, Node from homeassistant.components.binary_sensor import ( @@ -18,7 +20,7 @@ BinarySensorEntity, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.util import dt as dt_util @@ -55,12 +57,25 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the ISY994 binary sensor platform.""" - devices = [] - devices_by_address = {} - child_nodes = [] + entities: list[ + ISYInsteonBinarySensorEntity + | ISYBinarySensorEntity + | ISYBinarySensorHeartbeat + | ISYBinarySensorProgramEntity + ] = [] + entities_by_address: dict[ + str, + ISYInsteonBinarySensorEntity + | ISYBinarySensorEntity + | ISYBinarySensorHeartbeat + | ISYBinarySensorProgramEntity, + ] = {} + child_nodes: list[tuple[Node, str | None, str | None]] = [] + entity: ISYInsteonBinarySensorEntity | ISYBinarySensorEntity | ISYBinarySensorHeartbeat | ISYBinarySensorProgramEntity hass_isy_data = hass.data[ISY994_DOMAIN][entry.entry_id] for node in hass_isy_data[ISY994_NODES][BINARY_SENSOR]: + assert isinstance(node, Node) device_class, device_type = _detect_device_type_and_class(node) if node.protocol == PROTO_INSTEON: if node.parent_node is not None: @@ -68,38 +83,38 @@ async def async_setup_entry( # nodes have been processed child_nodes.append((node, device_class, device_type)) continue - device = ISYInsteonBinarySensorEntity(node, device_class) + entity = ISYInsteonBinarySensorEntity(node, device_class) else: - device = ISYBinarySensorEntity(node, device_class) - devices.append(device) - devices_by_address[node.address] = device + entity = ISYBinarySensorEntity(node, device_class) + entities.append(entity) + entities_by_address[node.address] = entity # Handle some special child node cases for Insteon Devices for (node, device_class, device_type) in child_nodes: subnode_id = int(node.address.split(" ")[-1], 16) # Handle Insteon Thermostats - if device_type.startswith(TYPE_CATEGORY_CLIMATE): + if device_type is not None and device_type.startswith(TYPE_CATEGORY_CLIMATE): if subnode_id == SUBNODE_CLIMATE_COOL: # Subnode 2 is the "Cool Control" sensor # It never reports its state until first use is # detected after an ISY Restart, so we assume it's off. # As soon as the ISY Event Stream connects if it has a # valid state, it will be set. - device = ISYInsteonBinarySensorEntity( + entity = ISYInsteonBinarySensorEntity( node, BinarySensorDeviceClass.COLD, False ) - devices.append(device) + entities.append(entity) elif subnode_id == SUBNODE_CLIMATE_HEAT: # Subnode 3 is the "Heat Control" sensor - device = ISYInsteonBinarySensorEntity( + entity = ISYInsteonBinarySensorEntity( node, BinarySensorDeviceClass.HEAT, False ) - devices.append(device) + entities.append(entity) continue if device_class in DEVICE_PARENT_REQUIRED: - parent_device = devices_by_address.get(node.parent_node.address) - if not parent_device: + parent_entity = entities_by_address.get(node.parent_node.address) + if not parent_entity: _LOGGER.error( "Node %s has a parent node %s, but no device " "was created for the parent. Skipping", @@ -115,13 +130,15 @@ async def async_setup_entry( # These sensors use an optional "negative" subnode 2 to # snag all state changes if subnode_id == SUBNODE_NEGATIVE: - parent_device.add_negative_node(node) + assert isinstance(parent_entity, ISYInsteonBinarySensorEntity) + parent_entity.add_negative_node(node) elif subnode_id == SUBNODE_HEARTBEAT: + assert isinstance(parent_entity, ISYInsteonBinarySensorEntity) # Subnode 4 is the heartbeat node, which we will # represent as a separate binary_sensor - device = ISYBinarySensorHeartbeat(node, parent_device) - parent_device.add_heartbeat_device(device) - devices.append(device) + entity = ISYBinarySensorHeartbeat(node, parent_entity) + parent_entity.add_heartbeat_device(entity) + entities.append(entity) continue if ( device_class == BinarySensorDeviceClass.MOTION @@ -133,48 +150,49 @@ async def async_setup_entry( # the initial state is forced "OFF"/"NORMAL" if the # parent device has a valid state. This is corrected # upon connection to the ISY event stream if subnode has a valid state. - initial_state = None if parent_device.state is None else False + assert isinstance(parent_entity, ISYInsteonBinarySensorEntity) + initial_state = None if parent_entity.state is None else False if subnode_id == SUBNODE_DUSK_DAWN: # Subnode 2 is the Dusk/Dawn sensor - device = ISYInsteonBinarySensorEntity( + entity = ISYInsteonBinarySensorEntity( node, BinarySensorDeviceClass.LIGHT ) - devices.append(device) + entities.append(entity) continue if subnode_id == SUBNODE_LOW_BATTERY: # Subnode 3 is the low battery node - device = ISYInsteonBinarySensorEntity( + entity = ISYInsteonBinarySensorEntity( node, BinarySensorDeviceClass.BATTERY, initial_state ) - devices.append(device) + entities.append(entity) continue if subnode_id in SUBNODE_TAMPER: # Tamper Sub-node for MS II. Sometimes reported as "A" sometimes # reported as "10", which translate from Hex to 10 and 16 resp. - device = ISYInsteonBinarySensorEntity( + entity = ISYInsteonBinarySensorEntity( node, BinarySensorDeviceClass.PROBLEM, initial_state ) - devices.append(device) + entities.append(entity) continue if subnode_id in SUBNODE_MOTION_DISABLED: # Motion Disabled Sub-node for MS II ("D" or "13") - device = ISYInsteonBinarySensorEntity(node) - devices.append(device) + entity = ISYInsteonBinarySensorEntity(node) + entities.append(entity) continue # We don't yet have any special logic for other sensor # types, so add the nodes as individual devices - device = ISYBinarySensorEntity(node, device_class) - devices.append(device) + entity = ISYBinarySensorEntity(node, device_class) + entities.append(entity) for name, status, _ in hass_isy_data[ISY994_PROGRAMS][BINARY_SENSOR]: - devices.append(ISYBinarySensorProgramEntity(name, status)) + entities.append(ISYBinarySensorProgramEntity(name, status)) - await migrate_old_unique_ids(hass, BINARY_SENSOR, devices) - async_add_entities(devices) + await migrate_old_unique_ids(hass, BINARY_SENSOR, entities) + async_add_entities(entities) -def _detect_device_type_and_class(node: Group | Node) -> (str, str): +def _detect_device_type_and_class(node: Group | Node) -> tuple[str | None, str | None]: try: device_type = node.type except AttributeError: @@ -199,20 +217,25 @@ def _detect_device_type_and_class(node: Group | Node) -> (str, str): class ISYBinarySensorEntity(ISYNodeEntity, BinarySensorEntity): """Representation of a basic ISY994 binary sensor device.""" - def __init__(self, node, force_device_class=None, unknown_state=None) -> None: + def __init__( + self, + node: Node, + force_device_class: str | None = None, + unknown_state: bool | None = None, + ) -> None: """Initialize the ISY994 binary sensor device.""" super().__init__(node) self._device_class = force_device_class @property - def is_on(self) -> bool: + def is_on(self) -> bool | None: """Get whether the ISY994 binary sensor device is on.""" if self._node.status == ISY_VALUE_UNKNOWN: return None return bool(self._node.status) @property - def device_class(self) -> str: + def device_class(self) -> str | None: """Return the class of this device. This was discovered by parsing the device type code during init @@ -229,11 +252,16 @@ class ISYInsteonBinarySensorEntity(ISYBinarySensorEntity): Assistant entity and handles both ways that ISY binary sensors can work. """ - def __init__(self, node, force_device_class=None, unknown_state=None) -> None: + def __init__( + self, + node: Node, + force_device_class: str | None = None, + unknown_state: bool | None = None, + ) -> None: """Initialize the ISY994 binary sensor device.""" super().__init__(node, force_device_class) - self._negative_node = None - self._heartbeat_device = None + self._negative_node: Node | None = None + self._heartbeat_device: ISYBinarySensorHeartbeat | None = None if self._node.status == ISY_VALUE_UNKNOWN: self._computed_state = unknown_state self._status_was_unknown = True @@ -252,21 +280,21 @@ async def async_added_to_hass(self) -> None: self._async_negative_node_control_handler ) - def add_heartbeat_device(self, device) -> None: + def add_heartbeat_device(self, entity: ISYBinarySensorHeartbeat | None) -> None: """Register a heartbeat device for this sensor. The heartbeat node beats on its own, but we can gain a little reliability by considering any node activity for this sensor to be a heartbeat as well. """ - self._heartbeat_device = device + self._heartbeat_device = entity def _async_heartbeat(self) -> None: """Send a heartbeat to our heartbeat device, if we have one.""" if self._heartbeat_device is not None: self._heartbeat_device.async_heartbeat() - def add_negative_node(self, child) -> None: + def add_negative_node(self, child: Node) -> None: """Add a negative node to this binary sensor device. The negative node is a node that can receive the 'off' events @@ -287,7 +315,7 @@ def add_negative_node(self, child) -> None: self._computed_state = None @callback - def _async_negative_node_control_handler(self, event: object) -> None: + def _async_negative_node_control_handler(self, event: NodeProperty) -> None: """Handle an "On" control event from the "negative" node.""" if event.control == CMD_ON: _LOGGER.debug( @@ -299,7 +327,7 @@ def _async_negative_node_control_handler(self, event: object) -> None: self._async_heartbeat() @callback - def _async_positive_node_control_handler(self, event: object) -> None: + def _async_positive_node_control_handler(self, event: NodeProperty) -> None: """Handle On and Off control event coming from the primary node. Depending on device configuration, sometimes only On events @@ -324,7 +352,7 @@ def _async_positive_node_control_handler(self, event: object) -> None: self._async_heartbeat() @callback - def async_on_update(self, event: object) -> None: + def async_on_update(self, event: NodeProperty) -> None: """Primary node status updates. We MOSTLY ignore these updates, as we listen directly to the Control @@ -341,7 +369,7 @@ def async_on_update(self, event: object) -> None: self._async_heartbeat() @property - def is_on(self) -> bool: + def is_on(self) -> bool | None: """Get whether the ISY994 binary sensor device is on. Insteon leak sensors set their primary node to On when the state is @@ -361,7 +389,14 @@ def is_on(self) -> bool: class ISYBinarySensorHeartbeat(ISYNodeEntity, BinarySensorEntity): """Representation of the battery state of an ISY994 sensor.""" - def __init__(self, node, parent_device) -> None: + def __init__( + self, + node: Node, + parent_device: ISYInsteonBinarySensorEntity + | ISYBinarySensorEntity + | ISYBinarySensorHeartbeat + | ISYBinarySensorProgramEntity, + ) -> None: """Initialize the ISY994 binary sensor device. Computed state is set to UNKNOWN unless the ISY provided a valid @@ -372,8 +407,8 @@ def __init__(self, node, parent_device) -> None: """ super().__init__(node) self._parent_device = parent_device - self._heartbeat_timer = None - self._computed_state = None + self._heartbeat_timer: CALLBACK_TYPE | None = None + self._computed_state: bool | None = None if self.state is None: self._computed_state = False @@ -386,7 +421,7 @@ async def async_added_to_hass(self) -> None: # Start the timer on bootup, so we can change from UNKNOWN to OFF self._restart_timer() - def _heartbeat_node_control_handler(self, event: object) -> None: + def _heartbeat_node_control_handler(self, event: NodeProperty) -> None: """Update the heartbeat timestamp when any ON/OFF event is sent. The ISY uses both DON and DOF commands (alternating) for a heartbeat. @@ -395,7 +430,7 @@ def _heartbeat_node_control_handler(self, event: object) -> None: self.async_heartbeat() @callback - def async_heartbeat(self): + def async_heartbeat(self) -> None: """Mark the device as online, and restart the 25 hour timer. This gets called when the heartbeat node beats, but also when the @@ -407,17 +442,14 @@ def async_heartbeat(self): self._restart_timer() self.async_write_ha_state() - def _restart_timer(self): + def _restart_timer(self) -> None: """Restart the 25 hour timer.""" - try: + if self._heartbeat_timer is not None: self._heartbeat_timer() self._heartbeat_timer = None - except TypeError: - # No heartbeat timer is active - pass @callback - def timer_elapsed(now) -> None: + def timer_elapsed(now: datetime) -> None: """Heartbeat missed; set state to ON to indicate dead battery.""" self._computed_state = True self._heartbeat_timer = None @@ -457,7 +489,7 @@ def device_class(self) -> str: return BinarySensorDeviceClass.BATTERY @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any]: """Get the state attributes for the device.""" attr = super().extra_state_attributes attr["parent_entity_id"] = self._parent_device.entity_id diff --git a/homeassistant/components/isy994/climate.py b/homeassistant/components/isy994/climate.py index 98c20bb441ae54..00a02e5c210c74 100644 --- a/homeassistant/components/isy994/climate.py +++ b/homeassistant/components/isy994/climate.py @@ -1,6 +1,8 @@ """Support for Insteon Thermostats via ISY994 Platform.""" from __future__ import annotations +from typing import Any + from pyisy.constants import ( CMD_CLIMATE_FAN_SETTING, CMD_CLIMATE_MODE, @@ -11,6 +13,7 @@ PROP_UOM, PROTO_INSTEON, ) +from pyisy.nodes import Node from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( @@ -18,9 +21,11 @@ ATTR_TARGET_TEMP_LOW, DOMAIN as CLIMATE, FAN_AUTO, + FAN_OFF, FAN_ON, HVAC_MODE_COOL, HVAC_MODE_HEAT, + HVAC_MODE_OFF, SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, @@ -76,16 +81,15 @@ async def async_setup_entry( class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): """Representation of an ISY994 thermostat entity.""" - def __init__(self, node) -> None: + def __init__(self, node: Node) -> None: """Initialize the ISY Thermostat entity.""" super().__init__(node) - self._node = node self._uom = self._node.uom if isinstance(self._uom, list): self._uom = self._node.uom[0] - self._hvac_action = None - self._hvac_mode = None - self._fan_mode = None + self._hvac_action: str | None = None + self._hvac_mode: str | None = None + self._fan_mode: str | None = None self._temp_unit = None self._current_humidity = 0 self._target_temp_low = 0 @@ -97,7 +101,7 @@ def supported_features(self) -> int: return ISY_SUPPORTED_FEATURES @property - def precision(self) -> str: + def precision(self) -> float: """Return the precision of the system.""" return PRECISION_TENTHS @@ -110,6 +114,7 @@ def temperature_unit(self) -> str: return TEMP_CELSIUS if uom.value == UOM_ISY_FAHRENHEIT: return TEMP_FAHRENHEIT + return TEMP_FAHRENHEIT @property def current_humidity(self) -> int | None: @@ -119,10 +124,10 @@ def current_humidity(self) -> int | None: return int(humidity.value) @property - def hvac_mode(self) -> str | None: + def hvac_mode(self) -> str: """Return hvac operation ie. heat, cool mode.""" if not (hvac_mode := self._node.aux_properties.get(CMD_CLIMATE_MODE)): - return None + return HVAC_MODE_OFF # Which state values used depends on the mode property's UOM: uom = hvac_mode.uom @@ -133,7 +138,7 @@ def hvac_mode(self) -> str | None: if self._node.protocol == PROTO_INSTEON else UOM_HVAC_MODE_GENERIC ) - return UOM_TO_STATES[uom].get(hvac_mode.value) + return UOM_TO_STATES[uom].get(hvac_mode.value, HVAC_MODE_OFF) @property def hvac_modes(self) -> list[str]: @@ -186,7 +191,7 @@ def target_temperature_low(self) -> float | None: return convert_isy_value_to_hass(target.value, target.uom, target.prec, 1) @property - def fan_modes(self): + def fan_modes(self) -> list[str]: """Return the list of available fan modes.""" return [FAN_AUTO, FAN_ON] @@ -195,10 +200,10 @@ def fan_mode(self) -> str: """Return the current fan mode ie. auto, on.""" fan_mode = self._node.aux_properties.get(CMD_CLIMATE_FAN_SETTING) if not fan_mode: - return None - return UOM_TO_STATES[UOM_FAN_MODES].get(fan_mode.value) + return FAN_OFF + return UOM_TO_STATES[UOM_FAN_MODES].get(fan_mode.value, FAN_OFF) - async def async_set_temperature(self, **kwargs) -> None: + async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" target_temp = kwargs.get(ATTR_TEMPERATURE) target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW) diff --git a/homeassistant/components/isy994/config_flow.py b/homeassistant/components/isy994/config_flow.py index 4e700df24cbdfc..866ec800402519 100644 --- a/homeassistant/components/isy994/config_flow.py +++ b/homeassistant/components/isy994/config_flow.py @@ -1,5 +1,8 @@ """Config flow for Universal Devices ISY994 integration.""" +from __future__ import annotations + import logging +from typing import Any from urllib.parse import urlparse, urlunparse from aiohttp import CookieJar @@ -38,7 +41,7 @@ _LOGGER = logging.getLogger(__name__) -def _data_schema(schema_input): +def _data_schema(schema_input: dict[str, str]) -> vol.Schema: """Generate schema with defaults.""" return vol.Schema( { @@ -51,7 +54,9 @@ def _data_schema(schema_input): ) -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input( + hass: core.HomeAssistant, data: dict[str, Any] +) -> dict[str, str]: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -65,7 +70,7 @@ async def validate_input(hass: core.HomeAssistant, data): https = False port = host.port or HTTP_PORT session = aiohttp_client.async_create_clientsession( - hass, verify_ssl=None, cookie_jar=CookieJar(unsafe=True) + hass, verify_ssl=False, cookie_jar=CookieJar(unsafe=True) ) elif host.scheme == SCHEME_HTTPS: https = True @@ -113,18 +118,22 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Initialize the isy994 config flow.""" - self.discovered_conf = {} + self.discovered_conf: dict[str, str] = {} @staticmethod @callback - def async_get_options_flow(config_entry): + def async_get_options_flow( + config_entry: config_entries.ConfigEntry, + ) -> config_entries.OptionsFlow: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) - async def async_step_user(self, user_input=None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> data_entry_flow.FlowResult: """Handle the initial step.""" errors = {} - info = None + info: dict[str, str] = {} if user_input is not None: try: info = await validate_input(self.hass, user_input) @@ -149,11 +158,15 @@ async def async_step_user(self, user_input=None): errors=errors, ) - async def async_step_import(self, user_input): + async def async_step_import( + self, user_input: dict[str, Any] + ) -> data_entry_flow.FlowResult: """Handle import.""" return await self.async_step_user(user_input) - async def _async_set_unique_id_or_update(self, isy_mac, ip_address, port) -> None: + async def _async_set_unique_id_or_update( + self, isy_mac: str, ip_address: str, port: int | None + ) -> None: """Abort and update the ip address on change.""" existing_entry = await self.async_set_unique_id(isy_mac) if not existing_entry: @@ -211,6 +224,7 @@ async def async_step_ssdp( """Handle a discovered isy994.""" friendly_name = discovery_info.upnp[ssdp.ATTR_UPNP_FRIENDLY_NAME] url = discovery_info.ssdp_location + assert isinstance(url, str) parsed_url = urlparse(url) mac = discovery_info.upnp[ssdp.ATTR_UPNP_UDN] if mac.startswith(UDN_UUID_PREFIX): @@ -224,6 +238,7 @@ async def async_step_ssdp( elif parsed_url.scheme == SCHEME_HTTPS: port = HTTPS_PORT + assert isinstance(parsed_url.hostname, str) await self._async_set_unique_id_or_update(mac, parsed_url.hostname, port) self.discovered_conf = { @@ -242,7 +257,9 @@ def __init__(self, config_entry: config_entries.ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry - async def async_step_init(self, user_input=None): + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> data_entry_flow.FlowResult: """Handle options flow.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/isy994/const.py b/homeassistant/components/isy994/const.py index 470aaa64c6437c..8ca1ac786f86d2 100644 --- a/homeassistant/components/isy994/const.py +++ b/homeassistant/components/isy994/const.py @@ -204,7 +204,7 @@ # responses, not using them for Home Assistant states # Insteon Types: https://www.universal-devices.com/developers/wsdk/5.0.4/1_fam.xml # Z-Wave Categories: https://www.universal-devices.com/developers/wsdk/5.0.4/4_fam.xml -NODE_FILTERS = { +NODE_FILTERS: dict[Platform, dict[str, list[str]]] = { Platform.BINARY_SENSOR: { FILTER_UOM: [UOM_ON_OFF], FILTER_STATES: [], diff --git a/homeassistant/components/isy994/cover.py b/homeassistant/components/isy994/cover.py index 0fcfb30a775de5..f00128b6d156bc 100644 --- a/homeassistant/components/isy994/cover.py +++ b/homeassistant/components/isy994/cover.py @@ -1,4 +1,7 @@ """Support for ISY994 covers.""" +from __future__ import annotations + +from typing import Any from pyisy.constants import ISY_VALUE_UNKNOWN @@ -31,53 +34,53 @@ async def async_setup_entry( ) -> None: """Set up the ISY994 cover platform.""" hass_isy_data = hass.data[ISY994_DOMAIN][entry.entry_id] - devices = [] + entities: list[ISYCoverEntity | ISYCoverProgramEntity] = [] for node in hass_isy_data[ISY994_NODES][COVER]: - devices.append(ISYCoverEntity(node)) + entities.append(ISYCoverEntity(node)) for name, status, actions in hass_isy_data[ISY994_PROGRAMS][COVER]: - devices.append(ISYCoverProgramEntity(name, status, actions)) + entities.append(ISYCoverProgramEntity(name, status, actions)) - await migrate_old_unique_ids(hass, COVER, devices) - async_add_entities(devices) + await migrate_old_unique_ids(hass, COVER, entities) + async_add_entities(entities) class ISYCoverEntity(ISYNodeEntity, CoverEntity): """Representation of an ISY994 cover device.""" @property - def current_cover_position(self) -> int: + def current_cover_position(self) -> int | None: """Return the current cover position.""" if self._node.status == ISY_VALUE_UNKNOWN: return None if self._node.uom == UOM_8_BIT_RANGE: return round(self._node.status * 100.0 / 255.0) - return sorted((0, self._node.status, 100))[1] + return int(sorted((0, self._node.status, 100))[1]) @property - def is_closed(self) -> bool: + def is_closed(self) -> bool | None: """Get whether the ISY994 cover device is closed.""" if self._node.status == ISY_VALUE_UNKNOWN: return None - return self._node.status == 0 + return bool(self._node.status == 0) @property - def supported_features(self): + def supported_features(self) -> int: """Flag supported features.""" return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION - async def async_open_cover(self, **kwargs) -> None: + async def async_open_cover(self, **kwargs: Any) -> None: """Send the open cover command to the ISY994 cover device.""" val = 100 if self._node.uom == UOM_BARRIER else None if not await self._node.turn_on(val=val): _LOGGER.error("Unable to open the cover") - async def async_close_cover(self, **kwargs) -> None: + async def async_close_cover(self, **kwargs: Any) -> None: """Send the close cover command to the ISY994 cover device.""" if not await self._node.turn_off(): _LOGGER.error("Unable to close the cover") - async def async_set_cover_position(self, **kwargs): + async def async_set_cover_position(self, **kwargs: Any) -> None: """Move the cover to a specific position.""" position = kwargs[ATTR_POSITION] if self._node.uom == UOM_8_BIT_RANGE: @@ -94,12 +97,12 @@ def is_closed(self) -> bool: """Get whether the ISY994 cover program is closed.""" return bool(self._node.status) - async def async_open_cover(self, **kwargs) -> None: + async def async_open_cover(self, **kwargs: Any) -> None: """Send the open cover command to the ISY994 cover program.""" if not await self._actions.run_then(): _LOGGER.error("Unable to open the cover") - async def async_close_cover(self, **kwargs) -> None: + async def async_close_cover(self, **kwargs: Any) -> None: """Send the close cover command to the ISY994 cover program.""" if not await self._actions.run_else(): _LOGGER.error("Unable to close the cover") diff --git a/homeassistant/components/isy994/entity.py b/homeassistant/components/isy994/entity.py index aca0dbf5d3d291..e5db8de58723f7 100644 --- a/homeassistant/components/isy994/entity.py +++ b/homeassistant/components/isy994/entity.py @@ -1,6 +1,8 @@ """Representation of ISYEntity Types.""" from __future__ import annotations +from typing import Any, cast + from pyisy.constants import ( COMMAND_FRIENDLY_NAME, EMPTY_TIME, @@ -8,7 +10,9 @@ PROTO_GROUP, PROTO_ZWAVE, ) -from pyisy.helpers import NodeProperty +from pyisy.helpers import EventListener, NodeProperty +from pyisy.nodes import Node +from pyisy.programs import Program from homeassistant.const import ( ATTR_IDENTIFIERS, @@ -30,14 +34,14 @@ class ISYEntity(Entity): """Representation of an ISY994 device.""" - _name: str = None + _name: str | None = None - def __init__(self, node) -> None: + def __init__(self, node: Node) -> None: """Initialize the insteon device.""" self._node = node - self._attrs = {} - self._change_handler = None - self._control_handler = None + self._attrs: dict[str, Any] = {} + self._change_handler: EventListener | None = None + self._control_handler: EventListener | None = None async def async_added_to_hass(self) -> None: """Subscribe to the node change events.""" @@ -49,7 +53,7 @@ async def async_added_to_hass(self) -> None: ) @callback - def async_on_update(self, event: object) -> None: + def async_on_update(self, event: NodeProperty) -> None: """Handle the update event from the ISY994 Node.""" self.async_write_ha_state() @@ -72,7 +76,7 @@ def async_on_control(self, event: NodeProperty) -> None: self.hass.bus.fire("isy994_control", event_data) @property - def device_info(self) -> DeviceInfo: + def device_info(self) -> DeviceInfo | None: """Return the device_info of the device.""" if hasattr(self._node, "protocol") and self._node.protocol == PROTO_GROUP: # not a device @@ -90,7 +94,6 @@ def device_info(self) -> DeviceInfo: basename = node.name device_info = DeviceInfo( - identifiers={}, manufacturer="Unknown", model="Unknown", name=basename, @@ -99,25 +102,30 @@ def device_info(self) -> DeviceInfo: ) if hasattr(node, "address"): - device_info[ATTR_NAME] += f" ({node.address})" + assert isinstance(node.address, str) + device_info[ATTR_NAME] = f"{basename} ({node.address})" if hasattr(node, "primary_node"): device_info[ATTR_IDENTIFIERS] = {(DOMAIN, f"{uuid}_{node.address}")} # ISYv5 Device Types if hasattr(node, "node_def_id") and node.node_def_id is not None: - device_info[ATTR_MODEL] = node.node_def_id + model: str = str(node.node_def_id) # Numerical Device Type if hasattr(node, "type") and node.type is not None: - device_info[ATTR_MODEL] += f" {node.type}" + model += f" {node.type}" + device_info[ATTR_MODEL] = model if hasattr(node, "protocol"): - device_info[ATTR_MANUFACTURER] = node.protocol + model = str(device_info[ATTR_MODEL]) + manufacturer = str(node.protocol) if node.protocol == PROTO_ZWAVE: # Get extra information for Z-Wave Devices - device_info[ATTR_MANUFACTURER] += f" MfrID:{node.zwave_props.mfr_id}" - device_info[ATTR_MODEL] += ( + manufacturer += f" MfrID:{node.zwave_props.mfr_id}" + model += ( f" Type:{node.zwave_props.devtype_gen} " f"ProductTypeID:{node.zwave_props.prod_type_id} " f"ProductID:{node.zwave_props.product_id}" ) + device_info[ATTR_MANUFACTURER] = manufacturer + device_info[ATTR_MODEL] = model if hasattr(node, "folder") and node.folder is not None: device_info[ATTR_SUGGESTED_AREA] = node.folder # Note: sw_version is not exposed by the ISY for the individual devices. @@ -125,17 +133,17 @@ def device_info(self) -> DeviceInfo: return device_info @property - def unique_id(self) -> str: + def unique_id(self) -> str | None: """Get the unique identifier of the device.""" if hasattr(self._node, "address"): return f"{self._node.isy.configuration['uuid']}_{self._node.address}" return None @property - def old_unique_id(self) -> str: + def old_unique_id(self) -> str | None: """Get the old unique identifier of the device.""" if hasattr(self._node, "address"): - return self._node.address + return cast(str, self._node.address) return None @property @@ -174,7 +182,7 @@ def extra_state_attributes(self) -> dict: self._attrs.update(attr) return self._attrs - async def async_send_node_command(self, command): + async def async_send_node_command(self, command: str) -> None: """Respond to an entity service command call.""" if not hasattr(self._node, command): raise HomeAssistantError( @@ -183,8 +191,12 @@ async def async_send_node_command(self, command): await getattr(self._node, command)() async def async_send_raw_node_command( - self, command, value=None, unit_of_measurement=None, parameters=None - ): + self, + command: str, + value: Any | None = None, + unit_of_measurement: str | None = None, + parameters: Any | None = None, + ) -> None: """Respond to an entity service raw command call.""" if not hasattr(self._node, "send_cmd"): raise HomeAssistantError( @@ -192,7 +204,7 @@ async def async_send_raw_node_command( ) await self._node.send_cmd(command, value, unit_of_measurement, parameters) - async def async_get_zwave_parameter(self, parameter): + async def async_get_zwave_parameter(self, parameter: Any) -> None: """Respond to an entity service command to request a Z-Wave device parameter from the ISY.""" if not hasattr(self._node, "protocol") or self._node.protocol != PROTO_ZWAVE: raise HomeAssistantError( @@ -200,7 +212,9 @@ async def async_get_zwave_parameter(self, parameter): ) await self._node.get_zwave_parameter(parameter) - async def async_set_zwave_parameter(self, parameter, value, size): + async def async_set_zwave_parameter( + self, parameter: Any, value: Any | None, size: int | None + ) -> None: """Respond to an entity service command to set a Z-Wave device parameter via the ISY.""" if not hasattr(self._node, "protocol") or self._node.protocol != PROTO_ZWAVE: raise HomeAssistantError( @@ -209,7 +223,7 @@ async def async_set_zwave_parameter(self, parameter, value, size): await self._node.set_zwave_parameter(parameter, value, size) await self._node.get_zwave_parameter(parameter) - async def async_rename_node(self, name): + async def async_rename_node(self, name: str) -> None: """Respond to an entity service command to rename a node on the ISY.""" await self._node.rename(name) @@ -217,7 +231,7 @@ async def async_rename_node(self, name): class ISYProgramEntity(ISYEntity): """Representation of an ISY994 program base.""" - def __init__(self, name: str, status, actions=None) -> None: + def __init__(self, name: str, status: Any | None, actions: Program = None) -> None: """Initialize the ISY994 program-based entity.""" super().__init__(status) self._name = name diff --git a/homeassistant/components/isy994/fan.py b/homeassistant/components/isy994/fan.py index 28d0675a1849a9..bf4d48ad3e8011 100644 --- a/homeassistant/components/isy994/fan.py +++ b/homeassistant/components/isy994/fan.py @@ -2,6 +2,7 @@ from __future__ import annotations import math +from typing import Any from pyisy.constants import ISY_VALUE_UNKNOWN, PROTO_INSTEON @@ -27,16 +28,16 @@ async def async_setup_entry( ) -> None: """Set up the ISY994 fan platform.""" hass_isy_data = hass.data[ISY994_DOMAIN][entry.entry_id] - devices = [] + entities: list[ISYFanEntity | ISYFanProgramEntity] = [] for node in hass_isy_data[ISY994_NODES][FAN]: - devices.append(ISYFanEntity(node)) + entities.append(ISYFanEntity(node)) for name, status, actions in hass_isy_data[ISY994_PROGRAMS][FAN]: - devices.append(ISYFanProgramEntity(name, status, actions)) + entities.append(ISYFanProgramEntity(name, status, actions)) - await migrate_old_unique_ids(hass, FAN, devices) - async_add_entities(devices) + await migrate_old_unique_ids(hass, FAN, entities) + async_add_entities(entities) class ISYFanEntity(ISYNodeEntity, FanEntity): @@ -57,11 +58,11 @@ def speed_count(self) -> int: return int_states_in_range(SPEED_RANGE) @property - def is_on(self) -> bool: + def is_on(self) -> bool | None: """Get if the fan is on.""" if self._node.status == ISY_VALUE_UNKNOWN: return None - return self._node.status != 0 + return bool(self._node.status != 0) async def async_set_percentage(self, percentage: int) -> None: """Set node to speed percentage for the ISY994 fan device.""" @@ -75,15 +76,15 @@ async def async_set_percentage(self, percentage: int) -> None: async def async_turn_on( self, - speed: str = None, - percentage: int = None, - preset_mode: str = None, - **kwargs, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, + **kwargs: Any, ) -> None: """Send the turn on command to the ISY994 fan device.""" await self.async_set_percentage(percentage or 67) - async def async_turn_off(self, **kwargs) -> None: + async def async_turn_off(self, **kwargs: Any) -> None: """Send the turn off command to the ISY994 fan device.""" await self._node.turn_off() @@ -111,19 +112,19 @@ def speed_count(self) -> int: @property def is_on(self) -> bool: """Get if the fan is on.""" - return self._node.status != 0 + return bool(self._node.status != 0) - async def async_turn_off(self, **kwargs) -> None: + async def async_turn_off(self, **kwargs: Any) -> None: """Send the turn on command to ISY994 fan program.""" if not await self._actions.run_then(): _LOGGER.error("Unable to turn off the fan") async def async_turn_on( self, - speed: str = None, - percentage: int = None, - preset_mode: str = None, - **kwargs, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, + **kwargs: Any, ) -> None: """Send the turn off command to ISY994 fan program.""" if not await self._actions.run_else(): diff --git a/homeassistant/components/isy994/helpers.py b/homeassistant/components/isy994/helpers.py index d1790fcc13cf43..6d0a1d303bb15b 100644 --- a/homeassistant/components/isy994/helpers.py +++ b/homeassistant/components/isy994/helpers.py @@ -1,7 +1,8 @@ """Sorting helpers for ISY994 device classifications.""" from __future__ import annotations -from typing import Any +from collections.abc import Sequence +from typing import TYPE_CHECKING, cast from pyisy.constants import ( ISY_VALUE_UNKNOWN, @@ -21,6 +22,7 @@ from homeassistant.components.light import DOMAIN as LIGHT from homeassistant.components.sensor import DOMAIN as SENSOR from homeassistant.components.switch import DOMAIN as SWITCH +from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_registry import async_get_registry @@ -53,12 +55,15 @@ UOM_ISYV4_DEGREES, ) +if TYPE_CHECKING: + from .entity import ISYEntity + BINARY_SENSOR_UOMS = ["2", "78"] BINARY_SENSOR_ISY_STATES = ["on", "off"] def _check_for_node_def( - hass_isy_data: dict, node: Group | Node, single_platform: str = None + hass_isy_data: dict, node: Group | Node, single_platform: Platform | None = None ) -> bool: """Check if the node matches the node_def_id for any platforms. @@ -81,7 +86,7 @@ def _check_for_node_def( def _check_for_insteon_type( - hass_isy_data: dict, node: Group | Node, single_platform: str = None + hass_isy_data: dict, node: Group | Node, single_platform: Platform | None = None ) -> bool: """Check if the node matches the Insteon type for any platforms. @@ -146,7 +151,7 @@ def _check_for_insteon_type( def _check_for_zwave_cat( - hass_isy_data: dict, node: Group | Node, single_platform: str = None + hass_isy_data: dict, node: Group | Node, single_platform: Platform | None = None ) -> bool: """Check if the node matches the ISY Z-Wave Category for any platforms. @@ -176,8 +181,8 @@ def _check_for_zwave_cat( def _check_for_uom_id( hass_isy_data: dict, node: Group | Node, - single_platform: str = None, - uom_list: list = None, + single_platform: Platform | None = None, + uom_list: list[str] | None = None, ) -> bool: """Check if a node's uom matches any of the platforms uom filter. @@ -211,8 +216,8 @@ def _check_for_uom_id( def _check_for_states_in_uom( hass_isy_data: dict, node: Group | Node, - single_platform: str = None, - states_list: list = None, + single_platform: Platform | None = None, + states_list: list[str] | None = None, ) -> bool: """Check if a list of uoms matches two possible filters. @@ -247,9 +252,11 @@ def _check_for_states_in_uom( def _is_sensor_a_binary_sensor(hass_isy_data: dict, node: Group | Node) -> bool: """Determine if the given sensor node should be a binary_sensor.""" - if _check_for_node_def(hass_isy_data, node, single_platform=BINARY_SENSOR): + if _check_for_node_def(hass_isy_data, node, single_platform=Platform.BINARY_SENSOR): return True - if _check_for_insteon_type(hass_isy_data, node, single_platform=BINARY_SENSOR): + if _check_for_insteon_type( + hass_isy_data, node, single_platform=Platform.BINARY_SENSOR + ): return True # For the next two checks, we're providing our own set of uoms that @@ -257,13 +264,16 @@ def _is_sensor_a_binary_sensor(hass_isy_data: dict, node: Group | Node) -> bool: # checks in the context of already knowing that this is definitely a # sensor device. if _check_for_uom_id( - hass_isy_data, node, single_platform=BINARY_SENSOR, uom_list=BINARY_SENSOR_UOMS + hass_isy_data, + node, + single_platform=Platform.BINARY_SENSOR, + uom_list=BINARY_SENSOR_UOMS, ): return True if _check_for_states_in_uom( hass_isy_data, node, - single_platform=BINARY_SENSOR, + single_platform=Platform.BINARY_SENSOR, states_list=BINARY_SENSOR_ISY_STATES, ): return True @@ -275,7 +285,7 @@ def _categorize_nodes( hass_isy_data: dict, nodes: Nodes, ignore_identifier: str, sensor_identifier: str ) -> None: """Sort the nodes to their proper platforms.""" - for (path, node) in nodes: + for path, node in nodes: ignored = ignore_identifier in path or ignore_identifier in node.name if ignored: # Don't import this node as a device at all @@ -365,43 +375,45 @@ def _categorize_variables( async def migrate_old_unique_ids( - hass: HomeAssistant, platform: str, devices: list[Any] | None + hass: HomeAssistant, platform: str, entities: Sequence[ISYEntity] ) -> None: """Migrate to new controller-specific unique ids.""" registry = await async_get_registry(hass) - for device in devices: + for entity in entities: + if entity.old_unique_id is None or entity.unique_id is None: + continue old_entity_id = registry.async_get_entity_id( - platform, DOMAIN, device.old_unique_id + platform, DOMAIN, entity.old_unique_id ) if old_entity_id is not None: _LOGGER.debug( "Migrating unique_id from [%s] to [%s]", - device.old_unique_id, - device.unique_id, + entity.old_unique_id, + entity.unique_id, ) - registry.async_update_entity(old_entity_id, new_unique_id=device.unique_id) + registry.async_update_entity(old_entity_id, new_unique_id=entity.unique_id) old_entity_id_2 = registry.async_get_entity_id( - platform, DOMAIN, device.unique_id.replace(":", "") + platform, DOMAIN, entity.unique_id.replace(":", "") ) if old_entity_id_2 is not None: _LOGGER.debug( "Migrating unique_id from [%s] to [%s]", - device.unique_id.replace(":", ""), - device.unique_id, + entity.unique_id.replace(":", ""), + entity.unique_id, ) registry.async_update_entity( - old_entity_id_2, new_unique_id=device.unique_id + old_entity_id_2, new_unique_id=entity.unique_id ) def convert_isy_value_to_hass( value: int | float | None, - uom: str, + uom: str | None, precision: int | str, fallback_precision: int | None = None, -) -> float | int: +) -> float | int | None: """Fix ISY Reported Values. ISY provides float values as an integer and precision component. @@ -416,7 +428,7 @@ def convert_isy_value_to_hass( if uom in (UOM_DOUBLE_TEMP, UOM_ISYV4_DEGREES): return round(float(value) / 2.0, 1) if precision not in ("0", 0): - return round(float(value) / 10 ** int(precision), int(precision)) + return cast(float, round(float(value) / 10 ** int(precision), int(precision))) if fallback_precision: return round(float(value), fallback_precision) return value diff --git a/homeassistant/components/isy994/light.py b/homeassistant/components/isy994/light.py index 2fd98b6f177819..640442c3f19d59 100644 --- a/homeassistant/components/isy994/light.py +++ b/homeassistant/components/isy994/light.py @@ -1,7 +1,11 @@ """Support for ISY994 lights.""" from __future__ import annotations +from typing import Any + from pyisy.constants import ISY_VALUE_UNKNOWN +from pyisy.helpers import NodeProperty +from pyisy.nodes import Node from homeassistant.components.light import ( DOMAIN as LIGHT, @@ -35,22 +39,22 @@ async def async_setup_entry( isy_options = entry.options restore_light_state = isy_options.get(CONF_RESTORE_LIGHT_STATE, False) - devices = [] + entities = [] for node in hass_isy_data[ISY994_NODES][LIGHT]: - devices.append(ISYLightEntity(node, restore_light_state)) + entities.append(ISYLightEntity(node, restore_light_state)) - await migrate_old_unique_ids(hass, LIGHT, devices) - async_add_entities(devices) + await migrate_old_unique_ids(hass, LIGHT, entities) + async_add_entities(entities) async_setup_light_services(hass) class ISYLightEntity(ISYNodeEntity, LightEntity, RestoreEntity): """Representation of an ISY994 light device.""" - def __init__(self, node, restore_light_state) -> None: + def __init__(self, node: Node, restore_light_state: bool) -> None: """Initialize the ISY994 light device.""" super().__init__(node) - self._last_brightness = None + self._last_brightness: int | None = None self._restore_light_state = restore_light_state @property @@ -61,7 +65,7 @@ def is_on(self) -> bool: return int(self._node.status) != 0 @property - def brightness(self) -> float: + def brightness(self) -> int | None: """Get the brightness of the ISY994 light.""" if self._node.status == ISY_VALUE_UNKNOWN: return None @@ -70,14 +74,14 @@ def brightness(self) -> float: return round(self._node.status * 255.0 / 100.0) return int(self._node.status) - async def async_turn_off(self, **kwargs) -> None: + async def async_turn_off(self, **kwargs: Any) -> None: """Send the turn off command to the ISY994 light device.""" self._last_brightness = self.brightness if not await self._node.turn_off(): _LOGGER.debug("Unable to turn off light") @callback - def async_on_update(self, event: object) -> None: + def async_on_update(self, event: NodeProperty) -> None: """Save brightness in the update event from the ISY994 Node.""" if self._node.status not in (0, ISY_VALUE_UNKNOWN): self._last_brightness = self._node.status @@ -88,7 +92,7 @@ def async_on_update(self, event: object) -> None: super().async_on_update(event) # pylint: disable=arguments-differ - async def async_turn_on(self, brightness=None, **kwargs) -> None: + async def async_turn_on(self, brightness: int | None = None, **kwargs: Any) -> None: """Send the turn on command to the ISY994 light device.""" if self._restore_light_state and brightness is None and self._last_brightness: brightness = self._last_brightness @@ -99,14 +103,14 @@ async def async_turn_on(self, brightness=None, **kwargs) -> None: _LOGGER.debug("Unable to turn on light") @property - def extra_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict[str, Any]: """Return the light attributes.""" attribs = super().extra_state_attributes attribs[ATTR_LAST_BRIGHTNESS] = self._last_brightness return attribs @property - def supported_features(self): + def supported_features(self) -> int: """Flag supported features.""" return SUPPORT_BRIGHTNESS @@ -124,10 +128,10 @@ async def async_added_to_hass(self) -> None: ): self._last_brightness = last_state.attributes[ATTR_LAST_BRIGHTNESS] - async def async_set_on_level(self, value): + async def async_set_on_level(self, value: int) -> None: """Set the ON Level for a device.""" await self._node.set_on_level(value) - async def async_set_ramp_rate(self, value): + async def async_set_ramp_rate(self, value: int) -> None: """Set the Ramp Rate for a device.""" await self._node.set_ramp_rate(value) diff --git a/homeassistant/components/isy994/lock.py b/homeassistant/components/isy994/lock.py index e2befc574872e6..4de5cdaa05bb48 100644 --- a/homeassistant/components/isy994/lock.py +++ b/homeassistant/components/isy994/lock.py @@ -1,4 +1,7 @@ """Support for ISY994 locks.""" +from __future__ import annotations + +from typing import Any from pyisy.constants import ISY_VALUE_UNKNOWN @@ -19,33 +22,33 @@ async def async_setup_entry( ) -> None: """Set up the ISY994 lock platform.""" hass_isy_data = hass.data[ISY994_DOMAIN][entry.entry_id] - devices = [] + entities: list[ISYLockEntity | ISYLockProgramEntity] = [] for node in hass_isy_data[ISY994_NODES][LOCK]: - devices.append(ISYLockEntity(node)) + entities.append(ISYLockEntity(node)) for name, status, actions in hass_isy_data[ISY994_PROGRAMS][LOCK]: - devices.append(ISYLockProgramEntity(name, status, actions)) + entities.append(ISYLockProgramEntity(name, status, actions)) - await migrate_old_unique_ids(hass, LOCK, devices) - async_add_entities(devices) + await migrate_old_unique_ids(hass, LOCK, entities) + async_add_entities(entities) class ISYLockEntity(ISYNodeEntity, LockEntity): """Representation of an ISY994 lock device.""" @property - def is_locked(self) -> bool: + def is_locked(self) -> bool | None: """Get whether the lock is in locked state.""" if self._node.status == ISY_VALUE_UNKNOWN: return None return VALUE_TO_STATE.get(self._node.status) - async def async_lock(self, **kwargs) -> None: + async def async_lock(self, **kwargs: Any) -> None: """Send the lock command to the ISY994 device.""" if not await self._node.secure_lock(): _LOGGER.error("Unable to lock device") - async def async_unlock(self, **kwargs) -> None: + async def async_unlock(self, **kwargs: Any) -> None: """Send the unlock command to the ISY994 device.""" if not await self._node.secure_unlock(): _LOGGER.error("Unable to lock device") @@ -59,12 +62,12 @@ def is_locked(self) -> bool: """Return true if the device is locked.""" return bool(self._node.status) - async def async_lock(self, **kwargs) -> None: + async def async_lock(self, **kwargs: Any) -> None: """Lock the device.""" if not await self._actions.run_then(): _LOGGER.error("Unable to lock device") - async def async_unlock(self, **kwargs) -> None: + async def async_unlock(self, **kwargs: Any) -> None: """Unlock the device.""" if not await self._actions.run_else(): _LOGGER.error("Unable to unlock device") diff --git a/homeassistant/components/isy994/sensor.py b/homeassistant/components/isy994/sensor.py index 466a7334775920..d9751fd707b07b 100644 --- a/homeassistant/components/isy994/sensor.py +++ b/homeassistant/components/isy994/sensor.py @@ -1,6 +1,8 @@ """Support for ISY994 sensors.""" from __future__ import annotations +from typing import Any, cast + from pyisy.constants import ISY_VALUE_UNKNOWN from homeassistant.components.sensor import DOMAIN as SENSOR, SensorEntity @@ -29,24 +31,24 @@ async def async_setup_entry( ) -> None: """Set up the ISY994 sensor platform.""" hass_isy_data = hass.data[ISY994_DOMAIN][entry.entry_id] - devices = [] + entities: list[ISYSensorEntity | ISYSensorVariableEntity] = [] for node in hass_isy_data[ISY994_NODES][SENSOR]: _LOGGER.debug("Loading %s", node.name) - devices.append(ISYSensorEntity(node)) + entities.append(ISYSensorEntity(node)) for vname, vobj in hass_isy_data[ISY994_VARIABLES]: - devices.append(ISYSensorVariableEntity(vname, vobj)) + entities.append(ISYSensorVariableEntity(vname, vobj)) - await migrate_old_unique_ids(hass, SENSOR, devices) - async_add_entities(devices) + await migrate_old_unique_ids(hass, SENSOR, entities) + async_add_entities(entities) class ISYSensorEntity(ISYNodeEntity, SensorEntity): """Representation of an ISY994 sensor device.""" @property - def raw_unit_of_measurement(self) -> dict | str: + def raw_unit_of_measurement(self) -> dict | str | None: """Get the raw unit of measurement for the ISY994 sensor device.""" uom = self._node.uom @@ -59,12 +61,13 @@ def raw_unit_of_measurement(self) -> dict | str: return isy_states if uom in (UOM_ON_OFF, UOM_INDEX): + assert isinstance(uom, str) return uom return UOM_FRIENDLY_NAME.get(uom) @property - def native_value(self) -> str: + def native_value(self) -> float | int | str | None: """Get the state of the ISY994 sensor device.""" if (value := self._node.status) == ISY_VALUE_UNKNOWN: return None @@ -77,11 +80,11 @@ def native_value(self) -> str: return uom.get(value, value) if uom in (UOM_INDEX, UOM_ON_OFF): - return self._node.formatted + return cast(str, self._node.formatted) # Check if this is an index type and get formatted value if uom == UOM_INDEX and hasattr(self._node, "formatted"): - return self._node.formatted + return cast(str, self._node.formatted) # Handle ISY precision and rounding value = convert_isy_value_to_hass(value, uom, self._node.prec) @@ -90,10 +93,14 @@ def native_value(self) -> str: if uom in (TEMP_CELSIUS, TEMP_FAHRENHEIT): value = self.hass.config.units.temperature(value, uom) + if value is None: + return None + + assert isinstance(value, (int, float)) return value @property - def native_unit_of_measurement(self) -> str: + def native_unit_of_measurement(self) -> str | None: """Get the Home Assistant unit of measurement for the device.""" raw_units = self.raw_unit_of_measurement # Check if this is a known index pair UOM @@ -113,12 +120,12 @@ def __init__(self, vname: str, vobj: object) -> None: self._name = vname @property - def native_value(self): + def native_value(self) -> float | int | None: """Return the state of the variable.""" return convert_isy_value_to_hass(self._node.status, "", self._node.prec) @property - def extra_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict[str, Any]: """Get the state attributes for the device.""" return { "init_value": convert_isy_value_to_hass( @@ -128,6 +135,6 @@ def extra_state_attributes(self) -> dict: } @property - def icon(self): + def icon(self) -> str: """Return the icon.""" return "mdi:counter" diff --git a/homeassistant/components/isy994/services.py b/homeassistant/components/isy994/services.py index a1dff594a1f720..8323394803f62f 100644 --- a/homeassistant/components/isy994/services.py +++ b/homeassistant/components/isy994/services.py @@ -1,4 +1,5 @@ """ISY Services and Commands.""" +from __future__ import annotations from typing import Any @@ -93,6 +94,7 @@ def valid_isy_commands(value: Any) -> str: """Validate the command is valid.""" value = str(value).upper() if value in COMMAND_FRIENDLY_NAME: + assert isinstance(value, str) return value raise vol.Invalid("Invalid ISY Command.") @@ -173,7 +175,7 @@ def valid_isy_commands(value: Any) -> str: @callback -def async_setup_services(hass: HomeAssistant): # noqa: C901 +def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901 """Create and register services for the ISY integration.""" existing_services = hass.services.async_services().get(DOMAIN) if existing_services and any( @@ -234,7 +236,7 @@ async def async_send_program_command_service_handler(service: ServiceCall) -> No """Handle a send program command service call.""" address = service.data.get(CONF_ADDRESS) name = service.data.get(CONF_NAME) - command = service.data.get(CONF_COMMAND) + command = service.data[CONF_COMMAND] isy_name = service.data.get(CONF_ISY) for config_entry_id in hass.data[DOMAIN]: @@ -432,7 +434,7 @@ async def _async_rename_node(call: ServiceCall) -> None: @callback -def async_unload_services(hass: HomeAssistant): +def async_unload_services(hass: HomeAssistant) -> None: """Unload services for the ISY integration.""" if hass.data[DOMAIN]: # There is still another config entry for this domain, don't remove services. @@ -456,7 +458,7 @@ def async_unload_services(hass: HomeAssistant): @callback -def async_setup_light_services(hass: HomeAssistant): +def async_setup_light_services(hass: HomeAssistant) -> None: """Create device-specific services for the ISY Integration.""" platform = entity_platform.async_get_current_platform() diff --git a/homeassistant/components/isy994/switch.py b/homeassistant/components/isy994/switch.py index 3e72dd6f0ecd9e..a92be5d4d23c87 100644 --- a/homeassistant/components/isy994/switch.py +++ b/homeassistant/components/isy994/switch.py @@ -1,4 +1,7 @@ """Support for ISY994 switches.""" +from __future__ import annotations + +from typing import Any from pyisy.constants import ISY_VALUE_UNKNOWN, PROTO_GROUP @@ -17,39 +20,39 @@ async def async_setup_entry( ) -> None: """Set up the ISY994 switch platform.""" hass_isy_data = hass.data[ISY994_DOMAIN][entry.entry_id] - devices = [] + entities: list[ISYSwitchProgramEntity | ISYSwitchEntity] = [] for node in hass_isy_data[ISY994_NODES][SWITCH]: - devices.append(ISYSwitchEntity(node)) + entities.append(ISYSwitchEntity(node)) for name, status, actions in hass_isy_data[ISY994_PROGRAMS][SWITCH]: - devices.append(ISYSwitchProgramEntity(name, status, actions)) + entities.append(ISYSwitchProgramEntity(name, status, actions)) - await migrate_old_unique_ids(hass, SWITCH, devices) - async_add_entities(devices) + await migrate_old_unique_ids(hass, SWITCH, entities) + async_add_entities(entities) class ISYSwitchEntity(ISYNodeEntity, SwitchEntity): """Representation of an ISY994 switch device.""" @property - def is_on(self) -> bool: + def is_on(self) -> bool | None: """Get whether the ISY994 device is in the on state.""" if self._node.status == ISY_VALUE_UNKNOWN: return None return bool(self._node.status) - async def async_turn_off(self, **kwargs) -> None: + async def async_turn_off(self, **kwargs: Any) -> None: """Send the turn off command to the ISY994 switch.""" if not await self._node.turn_off(): _LOGGER.debug("Unable to turn off switch") - async def async_turn_on(self, **kwargs) -> None: + async def async_turn_on(self, **kwargs: Any) -> None: """Send the turn on command to the ISY994 switch.""" if not await self._node.turn_on(): _LOGGER.debug("Unable to turn on switch") @property - def icon(self) -> str: + def icon(self) -> str | None: """Get the icon for groups.""" if hasattr(self._node, "protocol") and self._node.protocol == PROTO_GROUP: return "mdi:google-circles-communities" # Matches isy scene icon @@ -64,12 +67,12 @@ def is_on(self) -> bool: """Get whether the ISY994 switch program is on.""" return bool(self._node.status) - async def async_turn_on(self, **kwargs) -> None: + async def async_turn_on(self, **kwargs: Any) -> None: """Send the turn on command to the ISY994 switch program.""" if not await self._actions.run_then(): _LOGGER.error("Unable to turn on switch") - async def async_turn_off(self, **kwargs) -> None: + async def async_turn_off(self, **kwargs: Any) -> None: """Send the turn off command to the ISY994 switch program.""" if not await self._actions.run_else(): _LOGGER.error("Unable to turn off switch") diff --git a/homeassistant/components/isy994/system_health.py b/homeassistant/components/isy994/system_health.py index f550b8ed07bfd6..a8497ba0b27937 100644 --- a/homeassistant/components/isy994/system_health.py +++ b/homeassistant/components/isy994/system_health.py @@ -1,7 +1,12 @@ """Provide info to system health.""" +from __future__ import annotations + +from typing import Any + from pyisy import ISY from homeassistant.components import system_health +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant, callback @@ -16,7 +21,7 @@ def async_register( register.async_register_info(system_health_info) -async def system_health_info(hass): +async def system_health_info(hass: HomeAssistant) -> dict[str, Any]: """Get info for the info page.""" health_info = {} @@ -26,6 +31,7 @@ async def system_health_info(hass): isy: ISY = hass.data[DOMAIN][config_entry_id][ISY994_ISY] entry = hass.config_entries.async_get_entry(config_entry_id) + assert isinstance(entry, ConfigEntry) health_info["host_reachable"] = await system_health.async_check_can_reach_url( hass, f"{entry.data[CONF_HOST]}{ISY_URL_POSTFIX}" ) diff --git a/mypy.ini b/mypy.ini index c48d0221033a34..cc5d9b0e5e44e3 100644 --- a/mypy.ini +++ b/mypy.ini @@ -862,6 +862,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.isy994.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.iqvia.*] check_untyped_defs = true disallow_incomplete_defs = true @@ -2275,45 +2286,6 @@ ignore_errors = true [mypy-homeassistant.components.input_datetime] ignore_errors = true -[mypy-homeassistant.components.isy994] -ignore_errors = true - -[mypy-homeassistant.components.isy994.binary_sensor] -ignore_errors = true - -[mypy-homeassistant.components.isy994.climate] -ignore_errors = true - -[mypy-homeassistant.components.isy994.config_flow] -ignore_errors = true - -[mypy-homeassistant.components.isy994.cover] -ignore_errors = true - -[mypy-homeassistant.components.isy994.entity] -ignore_errors = true - -[mypy-homeassistant.components.isy994.fan] -ignore_errors = true - -[mypy-homeassistant.components.isy994.helpers] -ignore_errors = true - -[mypy-homeassistant.components.isy994.light] -ignore_errors = true - -[mypy-homeassistant.components.isy994.lock] -ignore_errors = true - -[mypy-homeassistant.components.isy994.sensor] -ignore_errors = true - -[mypy-homeassistant.components.isy994.services] -ignore_errors = true - -[mypy-homeassistant.components.isy994.switch] -ignore_errors = true - [mypy-homeassistant.components.izone.climate] ignore_errors = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index 6697adfa1d1035..6b14803b4995b5 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -101,19 +101,6 @@ "homeassistant.components.icloud.sensor", "homeassistant.components.influxdb", "homeassistant.components.input_datetime", - "homeassistant.components.isy994", - "homeassistant.components.isy994.binary_sensor", - "homeassistant.components.isy994.climate", - "homeassistant.components.isy994.config_flow", - "homeassistant.components.isy994.cover", - "homeassistant.components.isy994.entity", - "homeassistant.components.isy994.fan", - "homeassistant.components.isy994.helpers", - "homeassistant.components.isy994.light", - "homeassistant.components.isy994.lock", - "homeassistant.components.isy994.sensor", - "homeassistant.components.isy994.services", - "homeassistant.components.isy994.switch", "homeassistant.components.izone.climate", "homeassistant.components.konnected", "homeassistant.components.konnected.config_flow", From 714a952d73ec1ee2477977303ce801a9209ee140 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Thu, 3 Feb 2022 16:18:03 +0000 Subject: [PATCH 0232/1098] Enable types from aiohomekit to be used by mypy for homekit_controller (#65433) --- .strict-typing | 7 ++ .../components/homekit_controller/__init__.py | 2 +- .../components/homekit_controller/climate.py | 72 ++++++++++------- .../homekit_controller/connection.py | 55 +++++-------- .../homekit_controller/device_trigger.py | 7 +- .../homekit_controller/humidifier.py | 38 ++++++--- .../homekit_controller/manifest.json | 2 +- .../homekit_controller/media_player.py | 2 +- .../components/homekit_controller/number.py | 17 ++-- .../components/homekit_controller/storage.py | 7 +- mypy.ini | 77 +++++++++++++++++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 13 files changed, 200 insertions(+), 90 deletions(-) diff --git a/.strict-typing b/.strict-typing index cd74910415c2b5..e84e01b180313b 100644 --- a/.strict-typing +++ b/.strict-typing @@ -87,6 +87,13 @@ homeassistant.components.group.* homeassistant.components.guardian.* homeassistant.components.history.* homeassistant.components.homeassistant.triggers.event +homeassistant.components.homekit_controller +homeassistant.components.homekit_controller.alarm_control_panel +homeassistant.components.homekit_controller.button +homeassistant.components.homekit_controller.const +homeassistant.components.homekit_controller.lock +homeassistant.components.homekit_controller.select +homeassistant.components.homekit_controller.storage homeassistant.components.homewizard.* homeassistant.components.http.* homeassistant.components.huawei_lte.* diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index 974cb5e1dfc70c..eeca98167d0cf4 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -136,7 +136,7 @@ def unique_id(self) -> str: @property def name(self) -> str | None: """Return the name of the device if any.""" - return self.accessory_info.value(CharacteristicsTypes.NAME) + return self.accessory.name @property def available(self) -> bool: diff --git a/homeassistant/components/homekit_controller/climate.py b/homeassistant/components/homekit_controller/climate.py index e8179b0bc6bd76..53aee8561e4f00 100644 --- a/homeassistant/components/homekit_controller/climate.py +++ b/homeassistant/components/homekit_controller/climate.py @@ -2,7 +2,7 @@ from __future__ import annotations import logging -from typing import Any +from typing import Any, Final from aiohomekit.model.characteristics import ( ActivationStateValues, @@ -16,7 +16,11 @@ from aiohomekit.model.services import Service, ServicesTypes from aiohomekit.utils import clamp_enum_to_char -from homeassistant.components.climate import ClimateEntity +from homeassistant.components.climate import ( + DEFAULT_MAX_TEMP, + DEFAULT_MIN_TEMP, + ClimateEntity, +) from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, @@ -86,6 +90,8 @@ SWING_MODE_HASS_TO_HOMEKIT = {v: k for k, v in SWING_MODE_HOMEKIT_TO_HASS.items()} +DEFAULT_MIN_STEP: Final = 1.0 + async def async_setup_entry( hass: HomeAssistant, @@ -185,22 +191,24 @@ def target_temperature(self) -> float | None: return None @property - def target_temperature_step(self) -> float | None: + def target_temperature_step(self) -> float: """Return the supported step of target temperature.""" state = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE) if state == TargetHeaterCoolerStateValues.COOL and self.service.has( CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD ): - return self.service[ - CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD - ].minStep + return ( + self.service[CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD].minStep + or DEFAULT_MIN_STEP + ) if state == TargetHeaterCoolerStateValues.HEAT and self.service.has( CharacteristicsTypes.TEMPERATURE_HEATING_THRESHOLD ): - return self.service[ - CharacteristicsTypes.TEMPERATURE_HEATING_THRESHOLD - ].minStep - return None + return ( + self.service[CharacteristicsTypes.TEMPERATURE_HEATING_THRESHOLD].minStep + or DEFAULT_MIN_STEP + ) + return DEFAULT_MIN_STEP @property def min_temp(self) -> float: @@ -209,15 +217,21 @@ def min_temp(self) -> float: if state == TargetHeaterCoolerStateValues.COOL and self.service.has( CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD ): - return self.service[ - CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD - ].minValue + return ( + self.service[ + CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD + ].minValue + or DEFAULT_MIN_TEMP + ) if state == TargetHeaterCoolerStateValues.HEAT and self.service.has( CharacteristicsTypes.TEMPERATURE_HEATING_THRESHOLD ): - return self.service[ - CharacteristicsTypes.TEMPERATURE_HEATING_THRESHOLD - ].minValue + return ( + self.service[ + CharacteristicsTypes.TEMPERATURE_HEATING_THRESHOLD + ].minValue + or DEFAULT_MIN_TEMP + ) return super().min_temp @property @@ -227,15 +241,21 @@ def max_temp(self) -> float: if state == TargetHeaterCoolerStateValues.COOL and self.service.has( CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD ): - return self.service[ - CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD - ].maxValue + return ( + self.service[ + CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD + ].maxValue + or DEFAULT_MAX_TEMP + ) if state == TargetHeaterCoolerStateValues.HEAT and self.service.has( CharacteristicsTypes.TEMPERATURE_HEATING_THRESHOLD ): - return self.service[ - CharacteristicsTypes.TEMPERATURE_HEATING_THRESHOLD - ].maxValue + return ( + self.service[ + CharacteristicsTypes.TEMPERATURE_HEATING_THRESHOLD + ].maxValue + or DEFAULT_MAX_TEMP + ) return super().max_temp @property @@ -345,9 +365,9 @@ def get_characteristic_types(self) -> list[str]: CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET, ] - async def async_set_temperature(self, **kwargs) -> None: + async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" - chars = {} + chars: dict[str, Any] = {} value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) mode = MODE_HOMEKIT_TO_HASS[value] @@ -499,7 +519,7 @@ def min_humidity(self) -> int: CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET ].minValue if min_humidity is not None: - return min_humidity + return int(min_humidity) return super().min_humidity @property @@ -509,7 +529,7 @@ def max_humidity(self) -> int: CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET ].maxValue if max_humidity is not None: - return max_humidity + return int(max_humidity) return super().max_humidity @property diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index 8bd5351906c413..e4ad06f322afaa 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -13,8 +13,8 @@ EncryptionError, ) from aiohomekit.model import Accessories, Accessory -from aiohomekit.model.characteristics import Characteristic, CharacteristicsTypes -from aiohomekit.model.services import Service, ServicesTypes +from aiohomekit.model.characteristics import Characteristic +from aiohomekit.model.services import Service from homeassistant.const import ATTR_VIA_DEVICE from homeassistant.core import CALLBACK_TYPE, callback @@ -46,7 +46,7 @@ AddCharacteristicCb = Callable[[Characteristic], bool] -def valid_serial_number(serial): +def valid_serial_number(serial: str) -> bool: """Return if the serial number appears to be valid.""" if not serial: return False @@ -190,10 +190,6 @@ async def async_setup(self) -> bool: def device_info_for_accessory(self, accessory: Accessory) -> DeviceInfo: """Build a DeviceInfo for a given accessory.""" - info = accessory.services.first( - service_type=ServicesTypes.ACCESSORY_INFORMATION, - ) - identifiers = { ( IDENTIFIER_ACCESSORY_ID, @@ -202,16 +198,15 @@ def device_info_for_accessory(self, accessory: Accessory) -> DeviceInfo: } if not self.unreliable_serial_numbers: - serial_number = info.value(CharacteristicsTypes.SERIAL_NUMBER) - identifiers.add((IDENTIFIER_SERIAL_NUMBER, serial_number)) + identifiers.add((IDENTIFIER_SERIAL_NUMBER, accessory.serial_number)) device_info = DeviceInfo( identifiers=identifiers, - name=info.value(CharacteristicsTypes.NAME), - manufacturer=info.value(CharacteristicsTypes.MANUFACTURER, ""), - model=info.value(CharacteristicsTypes.MODEL, ""), - sw_version=info.value(CharacteristicsTypes.FIRMWARE_REVISION, ""), - hw_version=info.value(CharacteristicsTypes.HARDWARE_REVISION, ""), + name=accessory.name, + manufacturer=accessory.manufacturer, + model=accessory.model, + sw_version=accessory.firmware_revision, + hw_version=accessory.hardware_revision, ) if accessory.aid != 1: @@ -235,10 +230,6 @@ def async_migrate_devices(self) -> None: device_registry = dr.async_get(self.hass) for accessory in self.entity_map.accessories: - info = accessory.services.first( - service_type=ServicesTypes.ACCESSORY_INFORMATION, - ) - identifiers = { ( DOMAIN, @@ -252,10 +243,9 @@ def async_migrate_devices(self) -> None: (DOMAIN, IDENTIFIER_LEGACY_ACCESSORY_ID, self.unique_id) ) - serial_number = info.value(CharacteristicsTypes.SERIAL_NUMBER) - if valid_serial_number(serial_number): + if valid_serial_number(accessory.serial_number): identifiers.add( - (DOMAIN, IDENTIFIER_LEGACY_SERIAL_NUMBER, serial_number) + (DOMAIN, IDENTIFIER_LEGACY_SERIAL_NUMBER, accessory.serial_number) ) device = device_registry.async_get_device(identifiers=identifiers) # type: ignore[arg-type] @@ -284,8 +274,7 @@ def async_migrate_devices(self) -> None: } if not self.unreliable_serial_numbers: - serial_number = info.value(CharacteristicsTypes.SERIAL_NUMBER) - new_identifiers.add((IDENTIFIER_SERIAL_NUMBER, serial_number)) + new_identifiers.add((IDENTIFIER_SERIAL_NUMBER, accessory.serial_number)) else: _LOGGER.debug( "Not migrating serial number identifier for %s:aid:%s (it is wrong, not unique or unreliable)", @@ -334,35 +323,29 @@ def async_detect_workarounds(self) -> None: devices = set() for accessory in self.entity_map.accessories: - info = accessory.services.first( - service_type=ServicesTypes.ACCESSORY_INFORMATION, - ) - - serial_number = info.value(CharacteristicsTypes.SERIAL_NUMBER) - - if not valid_serial_number(serial_number): + if not valid_serial_number(accessory.serial_number): _LOGGER.debug( "Serial number %r is not valid, it cannot be used as a unique identifier", - serial_number, + accessory.serial_number, ) unreliable_serial_numbers = True - elif serial_number in devices: + elif accessory.serial_number in devices: _LOGGER.debug( "Serial number %r is duplicated within this pairing, it cannot be used as a unique identifier", - serial_number, + accessory.serial_number, ) unreliable_serial_numbers = True - elif serial_number == info.value(CharacteristicsTypes.HARDWARE_REVISION): + elif accessory.serial_number == accessory.hardware_revision: # This is a known bug with some devices (e.g. RYSE SmartShades) _LOGGER.debug( "Serial number %r is actually the hardware revision, it cannot be used as a unique identifier", - serial_number, + accessory.serial_number, ) unreliable_serial_numbers = True - devices.add(serial_number) + devices.add(accessory.serial_number) self.unreliable_serial_numbers = unreliable_serial_numbers diff --git a/homeassistant/components/homekit_controller/device_trigger.py b/homeassistant/components/homekit_controller/device_trigger.py index a69d189ebd5f04..aa2765d9be551e 100644 --- a/homeassistant/components/homekit_controller/device_trigger.py +++ b/homeassistant/components/homekit_controller/device_trigger.py @@ -1,7 +1,7 @@ """Provides device automations for homekit devices.""" from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING, Any from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics.const import InputEventValues @@ -20,6 +20,9 @@ from .const import DOMAIN, KNOWN_DEVICES, TRIGGERS +if TYPE_CHECKING: + from .connection import HKDevice + TRIGGER_TYPES = { "doorbell", "button1", @@ -225,7 +228,7 @@ def async_add_service(service): conn.add_listener(async_add_service) -def async_fire_triggers(conn, events): +def async_fire_triggers(conn: HKDevice, events: dict[tuple[int, int], Any]): """Process events generated by a HomeKit accessory into automation triggers.""" for (aid, iid), ev in events.items(): if aid in conn.devices: diff --git a/homeassistant/components/homekit_controller/humidifier.py b/homeassistant/components/homekit_controller/humidifier.py index bc922dd4ec11eb..fcca3e54725dfd 100644 --- a/homeassistant/components/homekit_controller/humidifier.py +++ b/homeassistant/components/homekit_controller/humidifier.py @@ -8,6 +8,8 @@ from homeassistant.components.humidifier import HumidifierDeviceClass, HumidifierEntity from homeassistant.components.humidifier.const import ( + DEFAULT_MAX_HUMIDITY, + DEFAULT_MIN_HUMIDITY, MODE_AUTO, MODE_NORMAL, SUPPORT_MODES, @@ -123,16 +125,22 @@ async def async_set_mode(self, mode: str) -> None: @property def min_humidity(self) -> int: """Return the minimum humidity.""" - return self.service[ - CharacteristicsTypes.RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD - ].minValue + return int( + self.service[ + CharacteristicsTypes.RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD + ].minValue + or DEFAULT_MIN_HUMIDITY + ) @property def max_humidity(self) -> int: """Return the maximum humidity.""" - return self.service[ - CharacteristicsTypes.RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD - ].maxValue + return int( + self.service[ + CharacteristicsTypes.RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD + ].maxValue + or DEFAULT_MAX_HUMIDITY + ) class HomeKitDehumidifier(HomeKitEntity, HumidifierEntity): @@ -225,16 +233,22 @@ async def async_set_mode(self, mode: str) -> None: @property def min_humidity(self) -> int: """Return the minimum humidity.""" - return self.service[ - CharacteristicsTypes.RELATIVE_HUMIDITY_DEHUMIDIFIER_THRESHOLD - ].minValue + return int( + self.service[ + CharacteristicsTypes.RELATIVE_HUMIDITY_DEHUMIDIFIER_THRESHOLD + ].minValue + or DEFAULT_MIN_HUMIDITY + ) @property def max_humidity(self) -> int: """Return the maximum humidity.""" - return self.service[ - CharacteristicsTypes.RELATIVE_HUMIDITY_DEHUMIDIFIER_THRESHOLD - ].maxValue + return int( + self.service[ + CharacteristicsTypes.RELATIVE_HUMIDITY_DEHUMIDIFIER_THRESHOLD + ].maxValue + or DEFAULT_MAX_HUMIDITY + ) @property def unique_id(self) -> str: diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index fcb36d3c54a0fa..8cc6ce2b575f78 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit Controller", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homekit_controller", - "requirements": ["aiohomekit==0.7.0"], + "requirements": ["aiohomekit==0.7.5"], "zeroconf": ["_hap._tcp.local."], "after_dependencies": ["zeroconf"], "codeowners": ["@Jc2k", "@bdraco"], diff --git a/homeassistant/components/homekit_controller/media_player.py b/homeassistant/components/homekit_controller/media_player.py index 7318343b643d97..6314efe9dc4354 100644 --- a/homeassistant/components/homekit_controller/media_player.py +++ b/homeassistant/components/homekit_controller/media_player.py @@ -121,7 +121,7 @@ def supported_media_states(self) -> set[TargetMediaStateValues]: ) @property - def supported_remote_keys(self) -> set[str]: + def supported_remote_keys(self) -> set[int]: """Remote key buttons that are supported.""" if not self.service.has(CharacteristicsTypes.REMOTE_KEY): return set() diff --git a/homeassistant/components/homekit_controller/number.py b/homeassistant/components/homekit_controller/number.py index a473e30fe6d494..5fcb5027640992 100644 --- a/homeassistant/components/homekit_controller/number.py +++ b/homeassistant/components/homekit_controller/number.py @@ -9,6 +9,11 @@ from aiohomekit.model.characteristics import Characteristic, CharacteristicsTypes from homeassistant.components.number import NumberEntity, NumberEntityDescription +from homeassistant.components.number.const import ( + DEFAULT_MAX_VALUE, + DEFAULT_MIN_VALUE, + DEFAULT_STEP, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import EntityCategory @@ -137,17 +142,17 @@ def get_characteristic_types(self) -> list[str]: @property def min_value(self) -> float: """Return the minimum value.""" - return self._char.minValue + return self._char.minValue or DEFAULT_MIN_VALUE @property def max_value(self) -> float: """Return the maximum value.""" - return self._char.maxValue + return self._char.maxValue or DEFAULT_MAX_VALUE @property def step(self) -> float: """Return the increment/decrement step.""" - return self._char.minStep + return self._char.minStep or DEFAULT_STEP @property def value(self) -> float: @@ -181,17 +186,17 @@ def name(self) -> str: @property def min_value(self) -> float: """Return the minimum value.""" - return self._char.minValue + return self._char.minValue or DEFAULT_MIN_VALUE @property def max_value(self) -> float: """Return the maximum value.""" - return self._char.maxValue + return self._char.maxValue or DEFAULT_MAX_VALUE @property def step(self) -> float: """Return the increment/decrement step.""" - return self._char.minStep + return self._char.minStep or DEFAULT_STEP @property def value(self) -> float: diff --git a/homeassistant/components/homekit_controller/storage.py b/homeassistant/components/homekit_controller/storage.py index fc6970f078a710..9372764a88af3e 100644 --- a/homeassistant/components/homekit_controller/storage.py +++ b/homeassistant/components/homekit_controller/storage.py @@ -51,13 +51,14 @@ def __init__(self, hass: HomeAssistant) -> None: async def async_initialize(self) -> None: """Get the pairing cache data.""" - if not (raw_storage := cast(StorageLayout, await self.store.async_load())): + if not (raw_storage := await self.store.async_load()): # There is no cached data about HomeKit devices yet return - self.storage_data = raw_storage.get("pairings", {}) + storage = cast(StorageLayout, raw_storage) + self.storage_data = storage.get("pairings", {}) - def get_map(self, homekit_id) -> Pairing | None: + def get_map(self, homekit_id: str) -> Pairing | None: """Get a pairing cache item.""" return self.storage_data.get(homekit_id) diff --git a/mypy.ini b/mypy.ini index cc5d9b0e5e44e3..32ee9352326944 100644 --- a/mypy.ini +++ b/mypy.ini @@ -774,6 +774,83 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.homekit_controller] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + +[mypy-homeassistant.components.homekit_controller.alarm_control_panel] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + +[mypy-homeassistant.components.homekit_controller.button] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + +[mypy-homeassistant.components.homekit_controller.const] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + +[mypy-homeassistant.components.homekit_controller.lock] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + +[mypy-homeassistant.components.homekit_controller.select] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + +[mypy-homeassistant.components.homekit_controller.storage] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.homewizard.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/requirements_all.txt b/requirements_all.txt index 602eeae6b2ceb7..79407a8df6d660 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -184,7 +184,7 @@ aioguardian==2021.11.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==0.7.0 +aiohomekit==0.7.5 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7e4614d127ba64..d71ccbfadc5080 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -134,7 +134,7 @@ aioguardian==2021.11.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==0.7.0 +aiohomekit==0.7.5 # homeassistant.components.emulated_hue # homeassistant.components.http From 778cc6106a7121e6f570356b061050416ca07dc8 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 3 Feb 2022 09:18:31 -0700 Subject: [PATCH 0233/1098] Remove deprecated SimpliSafe `service_id` service parameter (#65483) --- .../components/simplisafe/__init__.py | 133 +++++++----------- 1 file changed, 50 insertions(+), 83 deletions(-) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index a133ec6c2dcdf0..9a0566531c3ecb 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -150,86 +150,62 @@ SERVICE_NAME_SET_SYSTEM_PROPERTIES, ) -SERVICE_CLEAR_NOTIFICATIONS_SCHEMA = vol.All( - cv.deprecated(ATTR_SYSTEM_ID), - vol.Schema( - { - vol.Optional(ATTR_DEVICE_ID): cv.string, - vol.Optional(ATTR_SYSTEM_ID): cv.string, - } - ), - cv.has_at_least_one_key(ATTR_DEVICE_ID, ATTR_SYSTEM_ID), +SERVICE_CLEAR_NOTIFICATIONS_SCHEMA = vol.Schema( + { + vol.Required(ATTR_DEVICE_ID): cv.string, + }, ) -SERVICE_REMOVE_PIN_SCHEMA = vol.All( - cv.deprecated(ATTR_SYSTEM_ID), - vol.Schema( - { - vol.Optional(ATTR_DEVICE_ID): cv.string, - vol.Optional(ATTR_SYSTEM_ID): cv.string, - vol.Required(ATTR_PIN_LABEL_OR_VALUE): cv.string, - } - ), - cv.has_at_least_one_key(ATTR_DEVICE_ID, ATTR_SYSTEM_ID), +SERVICE_REMOVE_PIN_SCHEMA = vol.Schema( + { + vol.Required(ATTR_DEVICE_ID): cv.string, + vol.Required(ATTR_PIN_LABEL_OR_VALUE): cv.string, + } ) -SERVICE_SET_PIN_SCHEMA = vol.All( - cv.deprecated(ATTR_SYSTEM_ID), - vol.Schema( - { - vol.Optional(ATTR_DEVICE_ID): cv.string, - vol.Optional(ATTR_SYSTEM_ID): cv.string, - vol.Required(ATTR_PIN_LABEL): cv.string, - vol.Required(ATTR_PIN_VALUE): cv.string, - }, - ), - cv.has_at_least_one_key(ATTR_DEVICE_ID, ATTR_SYSTEM_ID), +SERVICE_SET_PIN_SCHEMA = vol.Schema( + { + vol.Required(ATTR_DEVICE_ID): cv.string, + vol.Required(ATTR_PIN_LABEL): cv.string, + vol.Required(ATTR_PIN_VALUE): cv.string, + }, ) -SERVICE_SET_SYSTEM_PROPERTIES_SCHEMA = vol.All( - cv.deprecated(ATTR_SYSTEM_ID), - vol.Schema( - { - vol.Optional(ATTR_DEVICE_ID): cv.string, - vol.Optional(ATTR_SYSTEM_ID): cv.string, - vol.Optional(ATTR_ALARM_DURATION): vol.All( - cv.time_period, - lambda value: value.total_seconds(), - vol.Range(min=MIN_ALARM_DURATION, max=MAX_ALARM_DURATION), - ), - vol.Optional(ATTR_ALARM_VOLUME): vol.All( - vol.In(VOLUME_MAP), VOLUME_MAP.get - ), - vol.Optional(ATTR_CHIME_VOLUME): vol.All( - vol.In(VOLUME_MAP), VOLUME_MAP.get - ), - vol.Optional(ATTR_ENTRY_DELAY_AWAY): vol.All( - cv.time_period, - lambda value: value.total_seconds(), - vol.Range(min=MIN_ENTRY_DELAY_AWAY, max=MAX_ENTRY_DELAY_AWAY), - ), - vol.Optional(ATTR_ENTRY_DELAY_HOME): vol.All( - cv.time_period, - lambda value: value.total_seconds(), - vol.Range(max=MAX_ENTRY_DELAY_HOME), - ), - vol.Optional(ATTR_EXIT_DELAY_AWAY): vol.All( - cv.time_period, - lambda value: value.total_seconds(), - vol.Range(min=MIN_EXIT_DELAY_AWAY, max=MAX_EXIT_DELAY_AWAY), - ), - vol.Optional(ATTR_EXIT_DELAY_HOME): vol.All( - cv.time_period, - lambda value: value.total_seconds(), - vol.Range(max=MAX_EXIT_DELAY_HOME), - ), - vol.Optional(ATTR_LIGHT): cv.boolean, - vol.Optional(ATTR_VOICE_PROMPT_VOLUME): vol.All( - vol.In(VOLUME_MAP), VOLUME_MAP.get - ), - } - ), - cv.has_at_least_one_key(ATTR_DEVICE_ID, ATTR_SYSTEM_ID), +SERVICE_SET_SYSTEM_PROPERTIES_SCHEMA = vol.Schema( + { + vol.Required(ATTR_DEVICE_ID): cv.string, + vol.Optional(ATTR_ALARM_DURATION): vol.All( + cv.time_period, + lambda value: value.total_seconds(), + vol.Range(min=MIN_ALARM_DURATION, max=MAX_ALARM_DURATION), + ), + vol.Optional(ATTR_ALARM_VOLUME): vol.All(vol.In(VOLUME_MAP), VOLUME_MAP.get), + vol.Optional(ATTR_CHIME_VOLUME): vol.All(vol.In(VOLUME_MAP), VOLUME_MAP.get), + vol.Optional(ATTR_ENTRY_DELAY_AWAY): vol.All( + cv.time_period, + lambda value: value.total_seconds(), + vol.Range(min=MIN_ENTRY_DELAY_AWAY, max=MAX_ENTRY_DELAY_AWAY), + ), + vol.Optional(ATTR_ENTRY_DELAY_HOME): vol.All( + cv.time_period, + lambda value: value.total_seconds(), + vol.Range(max=MAX_ENTRY_DELAY_HOME), + ), + vol.Optional(ATTR_EXIT_DELAY_AWAY): vol.All( + cv.time_period, + lambda value: value.total_seconds(), + vol.Range(min=MIN_EXIT_DELAY_AWAY, max=MAX_EXIT_DELAY_AWAY), + ), + vol.Optional(ATTR_EXIT_DELAY_HOME): vol.All( + cv.time_period, + lambda value: value.total_seconds(), + vol.Range(max=MAX_EXIT_DELAY_HOME), + ), + vol.Optional(ATTR_LIGHT): cv.boolean, + vol.Optional(ATTR_VOICE_PROMPT_VOLUME): vol.All( + vol.In(VOLUME_MAP), VOLUME_MAP.get + ), + } ) WEBSOCKET_EVENTS_REQUIRING_SERIAL = [EVENT_LOCK_LOCKED, EVENT_LOCK_UNLOCKED] @@ -251,15 +227,6 @@ def _async_get_system_for_service_call( hass: HomeAssistant, call: ServiceCall ) -> SystemType: """Get the SimpliSafe system related to a service call (by device ID).""" - if ATTR_SYSTEM_ID in call.data: - for entry in hass.config_entries.async_entries(DOMAIN): - simplisafe = hass.data[DOMAIN][entry.entry_id] - if ( - system := simplisafe.systems.get(int(call.data[ATTR_SYSTEM_ID])) - ) is None: - continue - return cast(SystemType, system) - device_id = call.data[ATTR_DEVICE_ID] device_registry = dr.async_get(hass) From efb6fd1569558d3813c201b886b1a72f61a1f953 Mon Sep 17 00:00:00 2001 From: mk-maddin <46523240+mk-maddin@users.noreply.github.com> Date: Thu, 3 Feb 2022 17:18:58 +0100 Subject: [PATCH 0234/1098] Fix script / automation repeat with count 0 fails (#65448) Co-authored-by: Paulus Schoutsen Co-authored-by: Erik Montnemery --- homeassistant/helpers/script.py | 2 +- tests/helpers/test_script.py | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 8e54e294f4bfa0..5a80691fa46152 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -742,7 +742,7 @@ async def async_run_sequence(iteration, extra_msg=""): if saved_repeat_vars: self._variables["repeat"] = saved_repeat_vars else: - del self._variables["repeat"] + self._variables.pop("repeat", None) # Not set if count = 0 async def _async_choose_step(self) -> None: """Choose a sequence.""" diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 2ee4213a688d61..5bb4833a796e26 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -1742,6 +1742,44 @@ async def test_repeat_count(hass, caplog, count): ) +async def test_repeat_count_0(hass, caplog): + """Test repeat action w/ count option.""" + event = "test_event" + events = async_capture_events(hass, event) + count = 0 + + alias = "condition step" + sequence = cv.SCRIPT_SCHEMA( + { + "alias": alias, + "repeat": { + "count": count, + "sequence": { + "event": event, + "event_data_template": { + "first": "{{ repeat.first }}", + "index": "{{ repeat.index }}", + "last": "{{ repeat.last }}", + }, + }, + }, + } + ) + + script_obj = script.Script(hass, sequence, "Test Name", "test_domain") + + await script_obj.async_run(context=Context()) + await hass.async_block_till_done() + + assert len(events) == count + assert caplog.text.count(f"Repeating {alias}") == count + assert_action_trace( + { + "0": [{}], + } + ) + + @pytest.mark.parametrize("condition", ["while", "until"]) async def test_repeat_condition_warning(hass, caplog, condition): """Test warning on repeat conditions.""" From 8d2fac09bb17f825e9447e9ac4817caf75f3148b Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 3 Feb 2022 09:19:06 -0700 Subject: [PATCH 0235/1098] Remove deprecated Guardian `entity_id` service parameter (#65484) --- homeassistant/components/guardian/__init__.py | 64 ++++++------------- 1 file changed, 18 insertions(+), 46 deletions(-) diff --git a/homeassistant/components/guardian/__init__.py b/homeassistant/components/guardian/__init__.py index 55af1619da5979..b971a428a76dff 100644 --- a/homeassistant/components/guardian/__init__.py +++ b/homeassistant/components/guardian/__init__.py @@ -3,7 +3,7 @@ import asyncio from collections.abc import Awaitable, Callable -from typing import TYPE_CHECKING, cast +from typing import cast from aioguardian import Client from aioguardian.errors import GuardianError @@ -12,7 +12,6 @@ from homeassistant.config_entries import ConfigEntry, ConfigEntryState from homeassistant.const import ( ATTR_DEVICE_ID, - ATTR_ENTITY_ID, CONF_DEVICE_ID, CONF_FILENAME, CONF_IP_ADDRESS, @@ -22,11 +21,7 @@ ) from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import ( - config_validation as cv, - device_registry as dr, - entity_registry as er, -) +from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.entity import DeviceInfo, EntityDescription from homeassistant.helpers.update_coordinator import ( @@ -71,41 +66,26 @@ SERVICE_NAME_UPGRADE_FIRMWARE, ) -SERVICE_BASE_SCHEMA = vol.All( - cv.deprecated(ATTR_ENTITY_ID), - vol.Schema( - { - vol.Optional(ATTR_DEVICE_ID): cv.string, - vol.Optional(ATTR_ENTITY_ID): cv.entity_id, - } - ), - cv.has_at_least_one_key(ATTR_DEVICE_ID, ATTR_ENTITY_ID), +SERVICE_BASE_SCHEMA = vol.Schema( + { + vol.Required(ATTR_DEVICE_ID): cv.string, + } ) -SERVICE_PAIR_UNPAIR_SENSOR_SCHEMA = vol.All( - cv.deprecated(ATTR_ENTITY_ID), - vol.Schema( - { - vol.Optional(ATTR_DEVICE_ID): cv.string, - vol.Optional(ATTR_ENTITY_ID): cv.entity_id, - vol.Required(CONF_UID): cv.string, - } - ), - cv.has_at_least_one_key(ATTR_DEVICE_ID, ATTR_ENTITY_ID), +SERVICE_PAIR_UNPAIR_SENSOR_SCHEMA = vol.Schema( + { + vol.Required(ATTR_DEVICE_ID): cv.string, + vol.Required(CONF_UID): cv.string, + } ) -SERVICE_UPGRADE_FIRMWARE_SCHEMA = vol.All( - cv.deprecated(ATTR_ENTITY_ID), - vol.Schema( - { - vol.Optional(ATTR_DEVICE_ID): cv.string, - vol.Optional(ATTR_ENTITY_ID): cv.entity_id, - vol.Optional(CONF_URL): cv.url, - vol.Optional(CONF_PORT): cv.port, - vol.Optional(CONF_FILENAME): cv.string, - }, - ), - cv.has_at_least_one_key(ATTR_DEVICE_ID, ATTR_ENTITY_ID), +SERVICE_UPGRADE_FIRMWARE_SCHEMA = vol.Schema( + { + vol.Required(ATTR_DEVICE_ID): cv.string, + vol.Optional(CONF_URL): cv.url, + vol.Optional(CONF_PORT): cv.port, + vol.Optional(CONF_FILENAME): cv.string, + }, ) @@ -115,14 +95,6 @@ @callback def async_get_entry_id_for_service_call(hass: HomeAssistant, call: ServiceCall) -> str: """Get the entry ID related to a service call (by device ID).""" - if ATTR_ENTITY_ID in call.data: - entity_registry = er.async_get(hass) - entity_registry_entry = entity_registry.async_get(call.data[ATTR_ENTITY_ID]) - if TYPE_CHECKING: - assert entity_registry_entry - assert entity_registry_entry.config_entry_id - return entity_registry_entry.config_entry_id - device_id = call.data[CONF_DEVICE_ID] device_registry = dr.async_get(hass) From 3d8d507ed975c9a4b3d756789c5537d484f1ffda Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 3 Feb 2022 11:39:57 -0600 Subject: [PATCH 0236/1098] Migrate powerwall from using ip address as unique id (#65257) Co-authored-by: Martin Hjelmare --- .../components/powerwall/__init__.py | 17 +- .../components/powerwall/config_flow.py | 155 ++++++++++--- homeassistant/components/powerwall/const.py | 1 + .../components/powerwall/manifest.json | 9 +- .../components/powerwall/strings.json | 14 +- .../components/powerwall/translations/en.json | 14 +- homeassistant/generated/dhcp.py | 8 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/powerwall/mocks.py | 3 + .../components/powerwall/test_config_flow.py | 210 +++++++++++++++--- 11 files changed, 359 insertions(+), 76 deletions(-) diff --git a/homeassistant/components/powerwall/__init__.py b/homeassistant/components/powerwall/__init__.py index fccd8979631677..8d91b984d46724 100644 --- a/homeassistant/components/powerwall/__init__.py +++ b/homeassistant/components/powerwall/__init__.py @@ -1,4 +1,5 @@ """The Tesla Powerwall integration.""" +import contextlib from datetime import timedelta import logging @@ -8,6 +9,7 @@ APIError, MissingAttributeError, Powerwall, + PowerwallError, PowerwallUnreachableError, ) @@ -19,12 +21,14 @@ from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed +from homeassistant.util.network import is_ip_address from .const import ( DOMAIN, POWERWALL_API_CHANGED, POWERWALL_API_CHARGE, POWERWALL_API_DEVICE_TYPE, + POWERWALL_API_GATEWAY_DIN, POWERWALL_API_GRID_SERVICES_ACTIVE, POWERWALL_API_GRID_STATUS, POWERWALL_API_METERS, @@ -117,6 +121,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: raise ConfigEntryAuthFailed from err await _migrate_old_unique_ids(hass, entry_id, powerwall_data) + + gateway_din = powerwall_data[POWERWALL_API_GATEWAY_DIN] + if gateway_din and entry.unique_id is not None and is_ip_address(entry.unique_id): + hass.config_entries.async_update_entry(entry, unique_id=gateway_din) + login_failed_count = 0 runtime_data = hass.data[DOMAIN][entry.entry_id] = { @@ -224,14 +233,16 @@ def _login_and_fetch_base_info(power_wall: Powerwall, password: str): def call_base_info(power_wall): """Wrap powerwall properties to be a callable.""" - serial_numbers = power_wall.get_serial_numbers() # Make sure the serial numbers always have the same order - serial_numbers.sort() + gateway_din = None + with contextlib.suppress((AssertionError, PowerwallError)): + gateway_din = power_wall.get_gateway_din().upper() return { POWERWALL_API_SITE_INFO: power_wall.get_site_info(), POWERWALL_API_STATUS: power_wall.get_status(), POWERWALL_API_DEVICE_TYPE: power_wall.get_device_type(), - POWERWALL_API_SERIAL_NUMBERS: serial_numbers, + POWERWALL_API_SERIAL_NUMBERS: sorted(power_wall.get_serial_numbers()), + POWERWALL_API_GATEWAY_DIN: gateway_din, } diff --git a/homeassistant/components/powerwall/config_flow.py b/homeassistant/components/powerwall/config_flow.py index cb9929a890e142..9814a52d8a02d5 100644 --- a/homeassistant/components/powerwall/config_flow.py +++ b/homeassistant/components/powerwall/config_flow.py @@ -1,5 +1,8 @@ """Config flow for Tesla Powerwall integration.""" +from __future__ import annotations + import logging +from typing import Any from tesla_powerwall import ( AccessDeniedError, @@ -13,6 +16,7 @@ from homeassistant.components import dhcp from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD from homeassistant.data_entry_flow import FlowResult +from homeassistant.util.network import is_ip_address from .const import DOMAIN @@ -24,10 +28,12 @@ def _login_and_fetch_site_info(power_wall: Powerwall, password: str): if password is not None: power_wall.login(password) power_wall.detect_and_pin_version() - return power_wall.get_site_info() + return power_wall.get_site_info(), power_wall.get_gateway_din() -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input( + hass: core.HomeAssistant, data: dict[str, str] +) -> dict[str, str]: """Validate the user input allows us to connect. Data has the keys from schema with values provided by the user. @@ -37,7 +43,7 @@ async def validate_input(hass: core.HomeAssistant, data): password = data[CONF_PASSWORD] try: - site_info = await hass.async_add_executor_job( + site_info, gateway_din = await hass.async_add_executor_job( _login_and_fetch_site_info, power_wall, password ) except MissingAttributeError as err: @@ -46,7 +52,7 @@ async def validate_input(hass: core.HomeAssistant, data): raise WrongVersion from err # Return info that you want to store in the config entry. - return {"title": site_info.site_name} + return {"title": site_info.site_name, "unique_id": gateway_din.upper()} class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -56,41 +62,107 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize the powerwall flow.""" - self.ip_address = None + self.ip_address: str | None = None + self.title: str | None = None + self.reauth_entry: config_entries.ConfigEntry | None = None async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: """Handle dhcp discovery.""" self.ip_address = discovery_info.ip - self._async_abort_entries_match({CONF_IP_ADDRESS: self.ip_address}) - self.context["title_placeholders"] = {CONF_IP_ADDRESS: self.ip_address} - return await self.async_step_user() + gateway_din = discovery_info.hostname.upper() + # The hostname is the gateway_din (unique_id) + await self.async_set_unique_id(gateway_din) + self._abort_if_unique_id_configured(updates={CONF_IP_ADDRESS: self.ip_address}) + for entry in self._async_current_entries(include_ignore=False): + if entry.data[CONF_IP_ADDRESS] == discovery_info.ip: + if entry.unique_id is not None and is_ip_address(entry.unique_id): + if self.hass.config_entries.async_update_entry( + entry, unique_id=gateway_din + ): + self.hass.async_create_task( + self.hass.config_entries.async_reload(entry.entry_id) + ) + return self.async_abort(reason="already_configured") + self.context["title_placeholders"] = { + "name": gateway_din, + "ip_address": self.ip_address, + } + errors, info = await self._async_try_connect( + {CONF_IP_ADDRESS: self.ip_address, CONF_PASSWORD: gateway_din[-5:]} + ) + if errors: + if CONF_PASSWORD in errors: + # The default password is the gateway din last 5 + # if it does not work, we have to ask + return await self.async_step_user() + return self.async_abort(reason="cannot_connect") + assert info is not None + self.title = info["title"] + return await self.async_step_confirm_discovery() + + async def _async_try_connect( + self, user_input + ) -> tuple[dict[str, Any] | None, dict[str, str] | None]: + """Try to connect to the powerwall.""" + info = None + errors: dict[str, str] = {} + try: + info = await validate_input(self.hass, user_input) + except PowerwallUnreachableError: + errors[CONF_IP_ADDRESS] = "cannot_connect" + except WrongVersion: + errors["base"] = "wrong_version" + except AccessDeniedError: + errors[CONF_PASSWORD] = "invalid_auth" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + + return errors, info + + async def async_step_confirm_discovery(self, user_input=None) -> FlowResult: + """Confirm a discovered powerwall.""" + assert self.ip_address is not None + assert self.unique_id is not None + if user_input is not None: + assert self.title is not None + return self.async_create_entry( + title=self.title, + data={ + CONF_IP_ADDRESS: self.ip_address, + CONF_PASSWORD: self.unique_id[-5:], + }, + ) + + self._set_confirm_only() + self.context["title_placeholders"] = { + "name": self.title, + "ip_address": self.ip_address, + } + return self.async_show_form( + step_id="confirm_discovery", + data_schema=vol.Schema({}), + description_placeholders={ + "name": self.title, + "ip_address": self.ip_address, + }, + ) async def async_step_user(self, user_input=None): """Handle the initial step.""" errors = {} if user_input is not None: - try: - info = await validate_input(self.hass, user_input) - except PowerwallUnreachableError: - errors[CONF_IP_ADDRESS] = "cannot_connect" - except WrongVersion: - errors["base"] = "wrong_version" - except AccessDeniedError: - errors[CONF_PASSWORD] = "invalid_auth" - except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" - + errors, info = await self._async_try_connect(user_input) if not errors: - existing_entry = await self.async_set_unique_id( - user_input[CONF_IP_ADDRESS] - ) - if existing_entry: - self.hass.config_entries.async_update_entry( - existing_entry, data=user_input + assert info is not None + if info["unique_id"]: + await self.async_set_unique_id( + info["unique_id"], raise_on_progress=False ) - await self.hass.config_entries.async_reload(existing_entry.entry_id) - return self.async_abort(reason="reauth_successful") + self._abort_if_unique_id_configured( + updates={CONF_IP_ADDRESS: user_input[CONF_IP_ADDRESS]} + ) + self._async_abort_entries_match({CONF_IP_ADDRESS: self.ip_address}) return self.async_create_entry(title=info["title"], data=user_input) return self.async_show_form( @@ -104,10 +176,33 @@ async def async_step_user(self, user_input=None): errors=errors, ) + async def async_step_reauth_confirm(self, user_input=None): + """Handle reauth confirmation.""" + errors = {} + if user_input is not None: + entry_data = self.reauth_entry.data + errors, _ = await self._async_try_connect( + {CONF_IP_ADDRESS: entry_data[CONF_IP_ADDRESS], **user_input} + ) + if not errors: + self.hass.config_entries.async_update_entry( + self.reauth_entry, data={**entry_data, **user_input} + ) + await self.hass.config_entries.async_reload(self.reauth_entry.entry_id) + return self.async_abort(reason="reauth_successful") + + return self.async_show_form( + step_id="reauth_confirm", + data_schema=vol.Schema({vol.Optional(CONF_PASSWORD): str}), + errors=errors, + ) + async def async_step_reauth(self, data): """Handle configuration by re-auth.""" - self.ip_address = data[CONF_IP_ADDRESS] - return await self.async_step_user() + self.reauth_entry = self.hass.config_entries.async_get_entry( + self.context["entry_id"] + ) + return await self.async_step_reauth_confirm() class WrongVersion(exceptions.HomeAssistantError): diff --git a/homeassistant/components/powerwall/const.py b/homeassistant/components/powerwall/const.py index 8cc0cbc27cd944..b2f0e3afe80c23 100644 --- a/homeassistant/components/powerwall/const.py +++ b/homeassistant/components/powerwall/const.py @@ -26,6 +26,7 @@ POWERWALL_API_DEVICE_TYPE = "device_type" POWERWALL_API_SITE_INFO = "site_info" POWERWALL_API_SERIAL_NUMBERS = "serial_numbers" +POWERWALL_API_GATEWAY_DIN = "gateway_din" POWERWALL_HTTP_SESSION = "http_session" diff --git a/homeassistant/components/powerwall/manifest.json b/homeassistant/components/powerwall/manifest.json index fd17557abe11ad..55c7ab41e64bf3 100644 --- a/homeassistant/components/powerwall/manifest.json +++ b/homeassistant/components/powerwall/manifest.json @@ -3,16 +3,11 @@ "name": "Tesla Powerwall", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/powerwall", - "requirements": ["tesla-powerwall==0.3.12"], + "requirements": ["tesla-powerwall==0.3.15"], "codeowners": ["@bdraco", "@jrester"], "dhcp": [ { - "hostname": "1118431-*", - "macaddress": "88DA1A*" - }, - { - "hostname": "1118431-*", - "macaddress": "000145*" + "hostname": "1118431-*" } ], "iot_class": "local_polling", diff --git a/homeassistant/components/powerwall/strings.json b/homeassistant/components/powerwall/strings.json index e1b2f2dbd3bec8..8995a0f956eb52 100644 --- a/homeassistant/components/powerwall/strings.json +++ b/homeassistant/components/powerwall/strings.json @@ -1,6 +1,6 @@ { "config": { - "flow_title": "{ip_address}", + "flow_title": "{name} ({ip_address})", "step": { "user": { "title": "Connect to the powerwall", @@ -9,6 +9,17 @@ "ip_address": "[%key:common::config_flow::data::ip%]", "password": "[%key:common::config_flow::data::password%]" } + }, + "reauth_confim": { + "title": "Reauthenticate the powerwall", + "description": "[%key:component::powerwall::config::step::user::description%]", + "data": { + "password": "[%key:common::config_flow::data::password%]" + } + }, + "confirm_discovery": { + "title": "[%key:component::powerwall::config::step::user::title%]", + "description": "Do you want to setup {name} ({ip_address})?" } }, "error": { @@ -18,6 +29,7 @@ "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" }, "abort": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } diff --git a/homeassistant/components/powerwall/translations/en.json b/homeassistant/components/powerwall/translations/en.json index 3be711d94c5484..04279759888fc0 100644 --- a/homeassistant/components/powerwall/translations/en.json +++ b/homeassistant/components/powerwall/translations/en.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Device is already configured", + "cannot_connect": "Failed to connect", "reauth_successful": "Re-authentication was successful" }, "error": { @@ -10,8 +11,19 @@ "unknown": "Unexpected error", "wrong_version": "Your powerwall uses a software version that is not supported. Please consider upgrading or reporting this issue so it can be resolved." }, - "flow_title": "{ip_address}", + "flow_title": "{name} ({ip_address})", "step": { + "confirm_discovery": { + "description": "Do you want to setup {name} ({ip_address})?", + "title": "Connect to the powerwall" + }, + "reauth_confim": { + "data": { + "password": "Password" + }, + "description": "The password is usually the last 5 characters of the serial number for Backup Gateway and can be found in the Tesla app or the last 5 characters of the password found inside the door for Backup Gateway 2.", + "title": "Reauthenticate the powerwall" + }, "user": { "data": { "ip_address": "IP Address", diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index f05a7f73e50af2..701117cb562ff6 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -218,13 +218,7 @@ }, { "domain": "powerwall", - "hostname": "1118431-*", - "macaddress": "88DA1A*" - }, - { - "domain": "powerwall", - "hostname": "1118431-*", - "macaddress": "000145*" + "hostname": "1118431-*" }, { "domain": "rachio", diff --git a/requirements_all.txt b/requirements_all.txt index 79407a8df6d660..c858f443769111 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2341,7 +2341,7 @@ temperusb==1.5.3 # tensorflow==2.5.0 # homeassistant.components.powerwall -tesla-powerwall==0.3.12 +tesla-powerwall==0.3.15 # homeassistant.components.tesla_wall_connector tesla-wall-connector==1.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d71ccbfadc5080..9f5e857869632c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1429,7 +1429,7 @@ tailscale==0.2.0 tellduslive==0.10.11 # homeassistant.components.powerwall -tesla-powerwall==0.3.12 +tesla-powerwall==0.3.15 # homeassistant.components.tesla_wall_connector tesla-wall-connector==1.0.1 diff --git a/tests/components/powerwall/mocks.py b/tests/components/powerwall/mocks.py index 9d253c3a74b0bd..1eac0319819371 100644 --- a/tests/components/powerwall/mocks.py +++ b/tests/components/powerwall/mocks.py @@ -16,6 +16,8 @@ from tests.common import load_fixture +MOCK_GATEWAY_DIN = "111-0----2-000000000FFA" + async def _mock_powerwall_with_fixtures(hass): """Mock data used to build powerwall state.""" @@ -70,6 +72,7 @@ async def _mock_powerwall_site_name(hass, site_name): # Sets site_info_resp.site_name to return site_name site_info_resp.response["site_name"] = site_name powerwall_mock.get_site_info = Mock(return_value=site_info_resp) + powerwall_mock.get_gateway_din = Mock(return_value=MOCK_GATEWAY_DIN) return powerwall_mock diff --git a/tests/components/powerwall/test_config_flow.py b/tests/components/powerwall/test_config_flow.py index 29a04a7008530c..3ef9e9c0fd1ffd 100644 --- a/tests/components/powerwall/test_config_flow.py +++ b/tests/components/powerwall/test_config_flow.py @@ -12,8 +12,13 @@ from homeassistant.components import dhcp from homeassistant.components.powerwall.const import DOMAIN from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD +from homeassistant.data_entry_flow import RESULT_TYPE_ABORT -from .mocks import _mock_powerwall_side_effect, _mock_powerwall_site_name +from .mocks import ( + MOCK_GATEWAY_DIN, + _mock_powerwall_side_effect, + _mock_powerwall_site_name, +) from tests.common import MockConfigEntry @@ -162,34 +167,63 @@ async def test_already_configured_with_ignored(hass): ) config_entry.add_to_hass(hass) - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_DHCP}, - data=dhcp.DhcpServiceInfo( - ip="1.1.1.1", - macaddress="AA:BB:CC:DD:EE:FF", - hostname="any", - ), - ) + mock_powerwall = await _mock_powerwall_site_name(hass, "Some site") + + with patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DHCP}, + data=dhcp.DhcpServiceInfo( + ip="1.1.1.1", + macaddress="AA:BB:CC:DD:EE:FF", + hostname="00GGX", + ), + ) assert result["type"] == "form" + assert result["errors"] is None + + with patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), patch( + "homeassistant.components.powerwall.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {}, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "Some site" + assert result2["data"] == {"ip_address": "1.1.1.1", "password": "00GGX"} + assert len(mock_setup_entry.mock_calls) == 1 -async def test_dhcp_discovery(hass): - """Test we can process the discovery from dhcp.""" +async def test_dhcp_discovery_manual_configure(hass): + """Test we can process the discovery from dhcp and manually configure.""" + mock_powerwall = await _mock_powerwall_site_name(hass, "Some site") - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_DHCP}, - data=dhcp.DhcpServiceInfo( - ip="1.1.1.1", - macaddress="AA:BB:CC:DD:EE:FF", - hostname="any", - ), - ) + with patch( + "homeassistant.components.powerwall.config_flow.Powerwall.login", + side_effect=AccessDeniedError("xyz"), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DHCP}, + data=dhcp.DhcpServiceInfo( + ip="1.1.1.1", + macaddress="AA:BB:CC:DD:EE:FF", + hostname="any", + ), + ) assert result["type"] == "form" assert result["errors"] == {} - mock_powerwall = await _mock_powerwall_site_name(hass, "Some site") with patch( "homeassistant.components.powerwall.config_flow.Powerwall", return_value=mock_powerwall, @@ -209,18 +243,80 @@ async def test_dhcp_discovery(hass): assert len(mock_setup_entry.mock_calls) == 1 +async def test_dhcp_discovery_auto_configure(hass): + """Test we can process the discovery from dhcp and auto configure.""" + mock_powerwall = await _mock_powerwall_site_name(hass, "Some site") + + with patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DHCP}, + data=dhcp.DhcpServiceInfo( + ip="1.1.1.1", + macaddress="AA:BB:CC:DD:EE:FF", + hostname="00GGX", + ), + ) + assert result["type"] == "form" + assert result["errors"] is None + + with patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), patch( + "homeassistant.components.powerwall.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {}, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "Some site" + assert result2["data"] == {"ip_address": "1.1.1.1", "password": "00GGX"} + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_dhcp_discovery_cannot_connect(hass): + """Test we can process the discovery from dhcp and we cannot connect.""" + mock_powerwall = _mock_powerwall_side_effect(site_info=PowerwallUnreachableError) + + with patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DHCP}, + data=dhcp.DhcpServiceInfo( + ip="1.1.1.1", + macaddress="AA:BB:CC:DD:EE:FF", + hostname="00GGX", + ), + ) + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "cannot_connect" + + async def test_form_reauth(hass): """Test reauthenticate.""" entry = MockConfigEntry( domain=DOMAIN, data=VALID_CONFIG, - unique_id="1.2.3.4", + unique_id=MOCK_GATEWAY_DIN, ) entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data=entry.data + DOMAIN, + context={"source": config_entries.SOURCE_REAUTH, "entry_id": entry.entry_id}, + data=entry.data, ) assert result["type"] == "form" assert result["errors"] == {} @@ -237,7 +333,6 @@ async def test_form_reauth(hass): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { - CONF_IP_ADDRESS: "1.2.3.4", CONF_PASSWORD: "new-test-password", }, ) @@ -246,3 +341,68 @@ async def test_form_reauth(hass): assert result2["type"] == "abort" assert result2["reason"] == "reauth_successful" assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_dhcp_discovery_update_ip_address(hass): + """Test we can update the ip address from dhcp.""" + entry = MockConfigEntry( + domain=DOMAIN, + data=VALID_CONFIG, + unique_id=MOCK_GATEWAY_DIN, + ) + entry.add_to_hass(hass) + mock_powerwall = await _mock_powerwall_site_name(hass, "Some site") + + with patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), patch( + "homeassistant.components.powerwall.async_setup_entry", + return_value=True, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DHCP}, + data=dhcp.DhcpServiceInfo( + ip="1.1.1.1", + macaddress="AA:BB:CC:DD:EE:FF", + hostname=MOCK_GATEWAY_DIN.lower(), + ), + ) + await hass.async_block_till_done() + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + assert entry.data[CONF_IP_ADDRESS] == "1.1.1.1" + + +async def test_dhcp_discovery_updates_unique_id(hass): + """Test we can update the unique id from dhcp.""" + entry = MockConfigEntry( + domain=DOMAIN, + data=VALID_CONFIG, + unique_id="1.2.3.4", + ) + entry.add_to_hass(hass) + mock_powerwall = await _mock_powerwall_site_name(hass, "Some site") + + with patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), patch( + "homeassistant.components.powerwall.async_setup_entry", + return_value=True, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DHCP}, + data=dhcp.DhcpServiceInfo( + ip="1.2.3.4", + macaddress="AA:BB:CC:DD:EE:FF", + hostname=MOCK_GATEWAY_DIN.lower(), + ), + ) + await hass.async_block_till_done() + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + assert entry.data[CONF_IP_ADDRESS] == "1.2.3.4" + assert entry.unique_id == MOCK_GATEWAY_DIN From b97cd3ce93f06baa241629cf147147a614232a4d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 3 Feb 2022 12:14:36 -0600 Subject: [PATCH 0237/1098] Do not update unifiprotect host from discovery if its not an ip (#65548) --- .../components/unifiprotect/config_flow.py | 7 ++++- .../unifiprotect/test_config_flow.py | 31 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/unifiprotect/config_flow.py b/homeassistant/components/unifiprotect/config_flow.py index 0890a962e5b4cd..39e255bb715169 100644 --- a/homeassistant/components/unifiprotect/config_flow.py +++ b/homeassistant/components/unifiprotect/config_flow.py @@ -24,6 +24,7 @@ from homeassistant.helpers.aiohttp_client import async_create_clientsession from homeassistant.helpers.typing import DiscoveryInfoType from homeassistant.loader import async_get_integration +from homeassistant.util.network import is_ip_address from .const import ( CONF_ALL_UPDATES, @@ -105,7 +106,11 @@ async def async_step_discovery( and entry_host != direct_connect_domain ): new_host = direct_connect_domain - elif not entry_has_direct_connect and entry_host != source_ip: + elif ( + not entry_has_direct_connect + and is_ip_address(entry_host) + and entry_host != source_ip + ): new_host = source_ip if new_host: self.hass.config_entries.async_update_entry( diff --git a/tests/components/unifiprotect/test_config_flow.py b/tests/components/unifiprotect/test_config_flow.py index a1609984be302e..68d90ff82eb120 100644 --- a/tests/components/unifiprotect/test_config_flow.py +++ b/tests/components/unifiprotect/test_config_flow.py @@ -418,6 +418,37 @@ async def test_discovered_by_unifi_discovery_direct_connect_updated_but_not_usin assert mock_config.data[CONF_HOST] == "127.0.0.1" +async def test_discovered_host_not_updated_if_existing_is_a_hostname( + hass: HomeAssistant, mock_nvr: NVR +) -> None: + """Test we only update the host if its an ip address from discovery.""" + mock_config = MockConfigEntry( + domain=DOMAIN, + data={ + "host": "a.hostname", + "username": "test-username", + "password": "test-password", + "id": "UnifiProtect", + "port": 443, + "verify_ssl": True, + }, + unique_id=DEVICE_MAC_ADDRESS.upper().replace(":", ""), + ) + mock_config.add_to_hass(hass) + + with _patch_discovery(): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DISCOVERY}, + data=UNIFI_DISCOVERY_DICT, + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + assert mock_config.data[CONF_HOST] == "a.hostname" + + async def test_discovered_by_unifi_discovery( hass: HomeAssistant, mock_nvr: NVR ) -> None: From cc7680b0c3c6e43ba8ab7556d17fdcb8b1b3f95a Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 3 Feb 2022 19:22:43 +0100 Subject: [PATCH 0238/1098] Adjust pylint plugin to enforce diagnostics type hints (#64976) * Adjust pylint plugin to enforce diagnostics type hints * Adjust return_type * Set return_type to UNDEFINED * Use Any for the expected data Co-authored-by: epenet --- .../components/diagnostics/__init__.py | 8 ++--- pylint/plugins/hass_enforce_type_hints.py | 29 +++++++++++++++++-- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/diagnostics/__init__.py b/homeassistant/components/diagnostics/__init__.py index f8a38971a95be8..b08c521537d6c6 100644 --- a/homeassistant/components/diagnostics/__init__.py +++ b/homeassistant/components/diagnostics/__init__.py @@ -4,7 +4,7 @@ from http import HTTPStatus import json import logging -from typing import Protocol +from typing import Any, Protocol from aiohttp import web import voluptuous as vol @@ -51,12 +51,12 @@ class DiagnosticsProtocol(Protocol): async def async_get_config_entry_diagnostics( self, hass: HomeAssistant, config_entry: ConfigEntry - ) -> dict: + ) -> Any: """Return diagnostics for a config entry.""" async def async_get_device_diagnostics( self, hass: HomeAssistant, config_entry: ConfigEntry, device: DeviceEntry - ) -> dict: + ) -> Any: """Return diagnostics for a device.""" @@ -125,7 +125,7 @@ def handle_get( async def _async_get_json_file_response( hass: HomeAssistant, - data: dict | list, + data: Any, filename: str, domain: str, d_type: DiagnosticsType, diff --git a/pylint/plugins/hass_enforce_type_hints.py b/pylint/plugins/hass_enforce_type_hints.py index 288fcc560c3855..0137e26a8a2c9c 100644 --- a/pylint/plugins/hass_enforce_type_hints.py +++ b/pylint/plugins/hass_enforce_type_hints.py @@ -10,6 +10,7 @@ from pylint.lint import PyLinter from homeassistant.const import Platform +from homeassistant.helpers.typing import UNDEFINED @dataclass @@ -39,9 +40,9 @@ class TypeHintMatch: f"^homeassistant\\.components\\.\\w+\\.({'|'.join([platform.value for platform in Platform])})$" ), # device_tracker matches only in the package root (device_tracker.py) - "device_tracker": re.compile( - f"^homeassistant\\.components\\.\\w+\\.({Platform.DEVICE_TRACKER.value})$" - ), + "device_tracker": re.compile(r"^homeassistant\.components\.\w+\.(device_tracker)$"), + # diagnostics matches only in the package root (diagnostics.py) + "diagnostics": re.compile(r"^homeassistant\.components\.\w+\.(diagnostics)$"), } _METHOD_MATCH: list[TypeHintMatch] = [ @@ -171,11 +172,33 @@ class TypeHintMatch: }, return_type=["DeviceScanner", "DeviceScanner | None"], ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["diagnostics"], + function_name="async_get_config_entry_diagnostics", + arg_types={ + 0: "HomeAssistant", + 1: "ConfigEntry", + }, + return_type=UNDEFINED, + ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["diagnostics"], + function_name="async_get_device_diagnostics", + arg_types={ + 0: "HomeAssistant", + 1: "ConfigEntry", + 2: "DeviceEntry", + }, + return_type=UNDEFINED, + ), ] def _is_valid_type(expected_type: list[str] | str | None, node: astroid.NodeNG) -> bool: """Check the argument node against the expected type.""" + if expected_type is UNDEFINED: + return True + if isinstance(expected_type, list): for expected_type_item in expected_type: if _is_valid_type(expected_type_item, node): From a3d2a1a5e0c4caf6cf30b62eec047b41b8c772de Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 3 Feb 2022 20:31:22 +0100 Subject: [PATCH 0239/1098] Add missing Tuya vacuum states (#65567) --- homeassistant/components/tuya/vacuum.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/homeassistant/components/tuya/vacuum.py b/homeassistant/components/tuya/vacuum.py index 832128a8b3ecef..70e1691af92dd6 100644 --- a/homeassistant/components/tuya/vacuum.py +++ b/homeassistant/components/tuya/vacuum.py @@ -37,6 +37,7 @@ TUYA_STATUS_TO_HA = { "charge_done": STATE_DOCKED, "chargecompleted": STATE_DOCKED, + "chargego": STATE_DOCKED, "charging": STATE_DOCKED, "cleaning": STATE_CLEANING, "docking": STATE_RETURNING, @@ -48,11 +49,14 @@ "pick_zone_clean": STATE_CLEANING, "pos_arrived": STATE_CLEANING, "pos_unarrive": STATE_CLEANING, + "random": STATE_CLEANING, "sleep": STATE_IDLE, "smart_clean": STATE_CLEANING, + "smart": STATE_CLEANING, "spot_clean": STATE_CLEANING, "standby": STATE_IDLE, "wall_clean": STATE_CLEANING, + "wall_follow": STATE_CLEANING, "zone_clean": STATE_CLEANING, } From d22fc99294b2980750b155a27406e5ea636da199 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 3 Feb 2022 21:23:30 +0100 Subject: [PATCH 0240/1098] Add device class to ESPHome switches (#64919) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- homeassistant/components/esphome/switch.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/esphome/switch.py b/homeassistant/components/esphome/switch.py index 952cb96fcd836a..fbb22bb7397020 100644 --- a/homeassistant/components/esphome/switch.py +++ b/homeassistant/components/esphome/switch.py @@ -5,7 +5,7 @@ from aioesphomeapi import SwitchInfo, SwitchState -from homeassistant.components.switch import SwitchEntity +from homeassistant.components.switch import DEVICE_CLASSES, SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -45,6 +45,13 @@ def is_on(self) -> bool | None: """Return true if the switch is on.""" return self._state.state + @property + def device_class(self) -> str | None: + """Return the class of this device.""" + if self._static_info.device_class not in DEVICE_CLASSES: + return None + return self._static_info.device_class + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the entity on.""" await self._client.switch_command(self._static_info.key, True) From 97c842750674b06312bc5c4fd2b9ecd795e7ca7b Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 3 Feb 2022 21:32:01 +0100 Subject: [PATCH 0241/1098] Update frontend to 20220203.0 (#65572) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 4f1e6eff032fe2..8a9b13648e962d 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20220202.0" + "home-assistant-frontend==20220203.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 438fa0eba8d587..3f0b9516eadecc 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ ciso8601==2.2.0 cryptography==35.0.0 emoji==1.6.3 hass-nabucasa==0.52.0 -home-assistant-frontend==20220202.0 +home-assistant-frontend==20220203.0 httpx==0.21.3 ifaddr==0.1.7 jinja2==3.0.3 diff --git a/requirements_all.txt b/requirements_all.txt index c858f443769111..3f978e595d73ef 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -842,7 +842,7 @@ hole==0.7.0 holidays==0.12 # homeassistant.components.frontend -home-assistant-frontend==20220202.0 +home-assistant-frontend==20220203.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9f5e857869632c..734a452a85464b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -543,7 +543,7 @@ hole==0.7.0 holidays==0.12 # homeassistant.components.frontend -home-assistant-frontend==20220202.0 +home-assistant-frontend==20220203.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 80102e1e840c67779c19d4512a985dbed0251817 Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Thu, 3 Feb 2022 21:32:36 +0100 Subject: [PATCH 0242/1098] Fix data update when guest client disappears in Fritz!Tools (#65564) Co-authored-by: Simone Chemelli --- homeassistant/components/fritz/common.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index dede4b82c02f0a..fa4096cb4ffa55 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -220,8 +220,8 @@ async def _async_update_data(self) -> None: """Update FritzboxTools data.""" try: await self.async_scan_devices() - except (FritzSecurityError, FritzConnectionException) as ex: - raise update_coordinator.UpdateFailed from ex + except FRITZ_EXCEPTIONS as ex: + raise update_coordinator.UpdateFailed(ex) from ex @property def unique_id(self) -> str: @@ -294,11 +294,19 @@ def _update_device_info(self) -> tuple[bool, str | None]: def _get_wan_access(self, ip_address: str) -> bool | None: """Get WAN access rule for given IP address.""" - return not self.connection.call_action( - "X_AVM-DE_HostFilter:1", - "GetWANAccessByIP", - NewIPv4Address=ip_address, - ).get("NewDisallow") + try: + return not self.connection.call_action( + "X_AVM-DE_HostFilter:1", + "GetWANAccessByIP", + NewIPv4Address=ip_address, + ).get("NewDisallow") + except FRITZ_EXCEPTIONS as ex: + _LOGGER.debug( + "could not get WAN access rule for client device with IP '%s', error: %s", + ip_address, + ex, + ) + return None async def async_scan_devices(self, now: datetime | None = None) -> None: """Wrap up FritzboxTools class scan.""" From 445c47c7a090614ce7f549c8c71880457d2403ef Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 3 Feb 2022 21:46:05 +0100 Subject: [PATCH 0243/1098] Guard against empty Tuya data types (#65571) --- homeassistant/components/tuya/base.py | 32 ++++++++++++++++++--------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/tuya/base.py b/homeassistant/components/tuya/base.py index ac9a9c83be2891..15e57f223e9d2e 100644 --- a/homeassistant/components/tuya/base.py +++ b/homeassistant/components/tuya/base.py @@ -72,9 +72,11 @@ def remap_value_from( return remap_value(value, from_min, from_max, self.min, self.max, reverse) @classmethod - def from_json(cls, dpcode: DPCode, data: str) -> IntegerTypeData: + def from_json(cls, dpcode: DPCode, data: str) -> IntegerTypeData | None: """Load JSON string and return a IntegerTypeData object.""" - parsed = json.loads(data) + if not (parsed := json.loads(data)): + return None + return cls( dpcode, min=int(parsed["min"]), @@ -94,9 +96,11 @@ class EnumTypeData: range: list[str] @classmethod - def from_json(cls, dpcode: DPCode, data: str) -> EnumTypeData: + def from_json(cls, dpcode: DPCode, data: str) -> EnumTypeData | None: """Load JSON string and return a EnumTypeData object.""" - return cls(dpcode, **json.loads(data)) + if not (parsed := json.loads(data)): + return None + return cls(dpcode, **parsed) @dataclass @@ -222,17 +226,25 @@ def find_dpcode( dptype == DPType.ENUM and getattr(self.device, key)[dpcode].type == DPType.ENUM ): - return EnumTypeData.from_json( - dpcode, getattr(self.device, key)[dpcode].values - ) + if not ( + enum_type := EnumTypeData.from_json( + dpcode, getattr(self.device, key)[dpcode].values + ) + ): + continue + return enum_type if ( dptype == DPType.INTEGER and getattr(self.device, key)[dpcode].type == DPType.INTEGER ): - return IntegerTypeData.from_json( - dpcode, getattr(self.device, key)[dpcode].values - ) + if not ( + integer_type := IntegerTypeData.from_json( + dpcode, getattr(self.device, key)[dpcode].values + ) + ): + continue + return integer_type if dptype not in (DPType.ENUM, DPType.INTEGER): return dpcode From 8432fe7bd6ef470916798729ce98aef9a8077dce Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Thu, 3 Feb 2022 22:36:36 +0100 Subject: [PATCH 0244/1098] Extend diagnostics data in Fritz!Tools (#65573) --- homeassistant/components/fritz/common.py | 14 ++++++++++++++ homeassistant/components/fritz/diagnostics.py | 13 +++++++++++++ 2 files changed, 27 insertions(+) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index fa4096cb4ffa55..2cd6616f134c2c 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -574,6 +574,13 @@ async def async_get_wan_dsl_interface_config(self) -> dict[str, Any]: partial(self.get_wan_dsl_interface_config) ) + async def async_get_wan_link_properties(self) -> dict[str, Any]: + """Call WANCommonInterfaceConfig service.""" + + return await self.hass.async_add_executor_job( + partial(self.get_wan_link_properties) + ) + async def async_get_port_mapping(self, con_type: str, index: int) -> dict[str, Any]: """Call GetGenericPortMappingEntry action.""" @@ -676,6 +683,13 @@ def get_wan_dsl_interface_config(self) -> dict[str, Any]: return self._service_call_action("WANDSLInterfaceConfig", "1", "GetInfo") + def get_wan_link_properties(self) -> dict[str, Any]: + """Call WANCommonInterfaceConfig service.""" + + return self._service_call_action( + "WANCommonInterfaceConfig", "1", "GetCommonLinkProperties" + ) + def set_wlan_configuration(self, index: int, turn_on: bool) -> dict[str, Any]: """Call SetEnable action from WLANConfiguration service.""" diff --git a/homeassistant/components/fritz/diagnostics.py b/homeassistant/components/fritz/diagnostics.py index 4305ee4d7cb2e5..f35eca6b9140fe 100644 --- a/homeassistant/components/fritz/diagnostics.py +++ b/homeassistant/components/fritz/diagnostics.py @@ -29,6 +29,19 @@ async def async_get_config_entry_diagnostics( "mesh_role": avm_wrapper.mesh_role, "last_update success": avm_wrapper.last_update_success, "last_exception": avm_wrapper.last_exception, + "discovered_services": list(avm_wrapper.connection.services), + "client_devices": [ + { + "connected_to": device.connected_to, + "connection_type": device.connection_type, + "hostname": device.hostname, + "is_connected": device.is_connected, + "last_activity": device.last_activity, + "wan_access": device.wan_access, + } + for _, device in avm_wrapper.devices.items() + ], + "wan_link_properties": await avm_wrapper.async_get_wan_link_properties(), }, } From d3e36230cb1195c58406442ed6f664e901d5c99d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 3 Feb 2022 22:37:10 +0100 Subject: [PATCH 0245/1098] Update pvo to 0.2.1 (#65584) --- homeassistant/components/pvoutput/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/pvoutput/manifest.json b/homeassistant/components/pvoutput/manifest.json index 378ad399ffe194..042c6b9aa99c0b 100644 --- a/homeassistant/components/pvoutput/manifest.json +++ b/homeassistant/components/pvoutput/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/pvoutput", "config_flow": true, "codeowners": ["@fabaff", "@frenck"], - "requirements": ["pvo==0.2.0"], + "requirements": ["pvo==0.2.1"], "iot_class": "cloud_polling", "quality_scale": "platinum" } diff --git a/requirements_all.txt b/requirements_all.txt index 3f978e595d73ef..0957c0bf3b088e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1310,7 +1310,7 @@ pushbullet.py==0.11.0 pushover_complete==1.1.1 # homeassistant.components.pvoutput -pvo==0.2.0 +pvo==0.2.1 # homeassistant.components.rpi_gpio_pwm pwmled==1.6.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 734a452a85464b..f25e984e287a2d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -814,7 +814,7 @@ pure-python-adb[async]==0.3.0.dev0 pushbullet.py==0.11.0 # homeassistant.components.pvoutput -pvo==0.2.0 +pvo==0.2.1 # homeassistant.components.canary py-canary==0.5.1 From 157276f4e6de6c58b59f8c743d43ec14edc0dd36 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 3 Feb 2022 13:43:23 -0800 Subject: [PATCH 0246/1098] Add a Lovelace cast platform (#65401) --- .../components/cast/home_assistant_cast.py | 9 +- homeassistant/components/lovelace/cast.py | 204 ++++++++++++++++++ .../cast/test_home_assistant_cast.py | 21 +- tests/components/lovelace/test_cast.py | 182 ++++++++++++++++ 4 files changed, 410 insertions(+), 6 deletions(-) create mode 100644 homeassistant/components/lovelace/cast.py create mode 100644 tests/components/lovelace/test_cast.py diff --git a/homeassistant/components/cast/home_assistant_cast.py b/homeassistant/components/cast/home_assistant_cast.py index 0f312d6a37a7ca..2f9583f329c05c 100644 --- a/homeassistant/components/cast/home_assistant_cast.py +++ b/homeassistant/components/cast/home_assistant_cast.py @@ -6,14 +6,16 @@ from homeassistant import auth, config_entries, core from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv, dispatcher -from homeassistant.helpers.network import get_url +from homeassistant.helpers.network import NoURLAvailableError, get_url from .const import DOMAIN, SIGNAL_HASS_CAST_SHOW_VIEW SERVICE_SHOW_VIEW = "show_lovelace_view" ATTR_VIEW_PATH = "view_path" ATTR_URL_PATH = "dashboard_path" +NO_URL_AVAILABLE_ERROR = "Home Assistant Cast requires your instance to be reachable via HTTPS. Enable Home Assistant Cloud or set up an external URL with valid SSL certificates" async def async_setup_ha_cast( @@ -41,7 +43,10 @@ async def async_setup_ha_cast( async def handle_show_view(call: core.ServiceCall) -> None: """Handle a Show View service call.""" - hass_url = get_url(hass, require_ssl=True, prefer_external=True) + try: + hass_url = get_url(hass, require_ssl=True, prefer_external=True) + except NoURLAvailableError as err: + raise HomeAssistantError(NO_URL_AVAILABLE_ERROR) from err controller = HomeAssistantController( # If you are developing Home Assistant Cast, uncomment and set to your dev app id. diff --git a/homeassistant/components/lovelace/cast.py b/homeassistant/components/lovelace/cast.py new file mode 100644 index 00000000000000..02280ebd18254b --- /dev/null +++ b/homeassistant/components/lovelace/cast.py @@ -0,0 +1,204 @@ +"""Home Assistant Cast platform.""" + +from __future__ import annotations + +from pychromecast import Chromecast +from pychromecast.const import CAST_TYPE_CHROMECAST + +from homeassistant.components.cast.const import DOMAIN as CAST_DOMAIN +from homeassistant.components.cast.home_assistant_cast import ( + ATTR_URL_PATH, + ATTR_VIEW_PATH, + NO_URL_AVAILABLE_ERROR, + SERVICE_SHOW_VIEW, +) +from homeassistant.components.media_player import BrowseError, BrowseMedia +from homeassistant.components.media_player.const import MEDIA_CLASS_APP +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.network import NoURLAvailableError, get_url + +from .const import DOMAIN, ConfigNotFound +from .dashboard import LovelaceConfig + +DEFAULT_DASHBOARD = "_default_" + + +async def async_get_media_browser_root_object( + hass: HomeAssistant, cast_type: str +) -> list[BrowseMedia]: + """Create a root object for media browsing.""" + if cast_type != CAST_TYPE_CHROMECAST: + return [] + return [ + BrowseMedia( + title="Lovelace", + media_class=MEDIA_CLASS_APP, + media_content_id="", + media_content_type=DOMAIN, + thumbnail="https://brands.home-assistant.io/_/lovelace/logo.png", + can_play=False, + can_expand=True, + ) + ] + + +async def async_browse_media( + hass: HomeAssistant, + media_content_type: str, + media_content_id: str, + cast_type: str, +) -> BrowseMedia | None: + """Browse media.""" + if media_content_type != DOMAIN: + return None + + try: + get_url(hass, require_ssl=True, prefer_external=True) + except NoURLAvailableError as err: + raise BrowseError(NO_URL_AVAILABLE_ERROR) from err + + # List dashboards. + if not media_content_id: + children = [ + BrowseMedia( + title="Default", + media_class=MEDIA_CLASS_APP, + media_content_id=DEFAULT_DASHBOARD, + media_content_type=DOMAIN, + thumbnail="https://brands.home-assistant.io/_/lovelace/logo.png", + can_play=True, + can_expand=False, + ) + ] + for url_path in hass.data[DOMAIN]["dashboards"]: + if url_path is None: + continue + + info = await _get_dashboard_info(hass, url_path) + children.append(_item_from_info(info)) + + root = (await async_get_media_browser_root_object(hass, CAST_TYPE_CHROMECAST))[ + 0 + ] + root.children = children + return root + + try: + info = await _get_dashboard_info(hass, media_content_id) + except ValueError as err: + raise BrowseError(f"Dashboard {media_content_id} not found") from err + + children = [] + + for view in info["views"]: + children.append( + BrowseMedia( + title=view["title"], + media_class=MEDIA_CLASS_APP, + media_content_id=f'{info["url_path"]}/{view["path"]}', + media_content_type=DOMAIN, + thumbnail="https://brands.home-assistant.io/_/lovelace/logo.png", + can_play=True, + can_expand=False, + ) + ) + + root = _item_from_info(info) + root.children = children + return root + + +async def async_play_media( + hass: HomeAssistant, + cast_entity_id: str, + chromecast: Chromecast, + media_type: str, + media_id: str, +) -> bool: + """Play media.""" + if media_type != DOMAIN: + return False + + if "/" in media_id: + url_path, view_path = media_id.split("/", 1) + else: + url_path = media_id + try: + info = await _get_dashboard_info(hass, media_id) + except ValueError as err: + raise HomeAssistantError(f"Invalid dashboard {media_id} specified") from err + view_path = info["views"][0]["path"] if info["views"] else "0" + + data = { + ATTR_ENTITY_ID: cast_entity_id, + ATTR_VIEW_PATH: view_path, + } + if url_path != DEFAULT_DASHBOARD: + data[ATTR_URL_PATH] = url_path + + await hass.services.async_call( + CAST_DOMAIN, + SERVICE_SHOW_VIEW, + data, + blocking=True, + ) + return True + + +async def _get_dashboard_info(hass, url_path): + """Load a dashboard and return info on views.""" + if url_path == DEFAULT_DASHBOARD: + url_path = None + dashboard: LovelaceConfig | None = hass.data[DOMAIN]["dashboards"].get(url_path) + + if dashboard is None: + raise ValueError("Invalid dashboard specified") + + try: + config = await dashboard.async_load(False) + except ConfigNotFound: + config = None + + if dashboard.url_path is None: + url_path = DEFAULT_DASHBOARD + title = "Default" + else: + url_path = dashboard.url_path + title = config.get("title", url_path) if config else url_path + + views = [] + data = { + "title": title, + "url_path": url_path, + "views": views, + } + + if config is None: + return data + + for idx, view in enumerate(config["views"]): + path = view.get("path", f"{idx}") + views.append( + { + "title": view.get("title", path), + "path": path, + } + ) + + return data + + +@callback +def _item_from_info(info: dict) -> BrowseMedia: + """Convert dashboard info to browse item.""" + return BrowseMedia( + title=info["title"], + media_class=MEDIA_CLASS_APP, + media_content_id=info["url_path"], + media_content_type=DOMAIN, + thumbnail="https://brands.home-assistant.io/_/lovelace/logo.png", + can_play=True, + can_expand=len(info["views"]) > 1, + ) diff --git a/tests/components/cast/test_home_assistant_cast.py b/tests/components/cast/test_home_assistant_cast.py index 67b5454b6e1391..a799a6d1d36345 100644 --- a/tests/components/cast/test_home_assistant_cast.py +++ b/tests/components/cast/test_home_assistant_cast.py @@ -2,21 +2,34 @@ from unittest.mock import patch +import pytest + from homeassistant.components.cast import home_assistant_cast from homeassistant.config import async_process_ha_core_config +from homeassistant.exceptions import HomeAssistantError from tests.common import MockConfigEntry, async_mock_signal async def test_service_show_view(hass, mock_zeroconf): - """Test we don't set app id in prod.""" + """Test showing a view.""" + await home_assistant_cast.async_setup_ha_cast(hass, MockConfigEntry()) + calls = async_mock_signal(hass, home_assistant_cast.SIGNAL_HASS_CAST_SHOW_VIEW) + + # No valid URL + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + "cast", + "show_lovelace_view", + {"entity_id": "media_player.kitchen", "view_path": "mock_path"}, + blocking=True, + ) + + # Set valid URL await async_process_ha_core_config( hass, {"external_url": "https://example.com"}, ) - await home_assistant_cast.async_setup_ha_cast(hass, MockConfigEntry()) - calls = async_mock_signal(hass, home_assistant_cast.SIGNAL_HASS_CAST_SHOW_VIEW) - await hass.services.async_call( "cast", "show_lovelace_view", diff --git a/tests/components/lovelace/test_cast.py b/tests/components/lovelace/test_cast.py new file mode 100644 index 00000000000000..d5b8e43d2bb6fb --- /dev/null +++ b/tests/components/lovelace/test_cast.py @@ -0,0 +1,182 @@ +"""Test the Lovelace Cast platform.""" +from time import time +from unittest.mock import patch + +import pytest + +from homeassistant.components.lovelace import cast as lovelace_cast +from homeassistant.config import async_process_ha_core_config +from homeassistant.exceptions import HomeAssistantError +from homeassistant.setup import async_setup_component + +from tests.common import async_mock_service + + +@pytest.fixture +async def mock_https_url(hass): + """Mock valid URL.""" + await async_process_ha_core_config( + hass, + {"external_url": "https://example.com"}, + ) + + +@pytest.fixture +async def mock_yaml_dashboard(hass): + """Mock the content of a YAML dashboard.""" + # Set up a YAML dashboard with 2 views. + assert await async_setup_component( + hass, + "lovelace", + { + "lovelace": { + "dashboards": { + "yaml-with-views": { + "title": "YAML Title", + "mode": "yaml", + "filename": "bla.yaml", + } + } + } + }, + ) + + with patch( + "homeassistant.components.lovelace.dashboard.load_yaml", + return_value={ + "title": "YAML Title", + "views": [ + { + "title": "Hello", + }, + {"path": "second-view"}, + ], + }, + ), patch( + "homeassistant.components.lovelace.dashboard.os.path.getmtime", + return_value=time() + 10, + ): + yield + + +async def test_root_object(hass): + """Test getting a root object.""" + assert ( + await lovelace_cast.async_get_media_browser_root_object(hass, "some-type") == [] + ) + + root = await lovelace_cast.async_get_media_browser_root_object( + hass, lovelace_cast.CAST_TYPE_CHROMECAST + ) + assert len(root) == 1 + item = root[0] + assert item.title == "Lovelace" + assert item.media_class == lovelace_cast.MEDIA_CLASS_APP + assert item.media_content_id == "" + assert item.media_content_type == lovelace_cast.DOMAIN + assert item.thumbnail == "https://brands.home-assistant.io/_/lovelace/logo.png" + assert item.can_play is False + assert item.can_expand is True + + +async def test_browse_media_error(hass): + """Test browse media checks valid URL.""" + assert await async_setup_component(hass, "lovelace", {}) + + with pytest.raises(HomeAssistantError): + await lovelace_cast.async_browse_media( + hass, "lovelace", "", lovelace_cast.CAST_TYPE_CHROMECAST + ) + + assert ( + await lovelace_cast.async_browse_media( + hass, "not_lovelace", "", lovelace_cast.CAST_TYPE_CHROMECAST + ) + is None + ) + + +async def test_browse_media(hass, mock_yaml_dashboard, mock_https_url): + """Test browse media.""" + top_level_items = await lovelace_cast.async_browse_media( + hass, "lovelace", "", lovelace_cast.CAST_TYPE_CHROMECAST + ) + + assert len(top_level_items.children) == 2 + + child_1 = top_level_items.children[0] + assert child_1.title == "Default" + assert child_1.media_class == lovelace_cast.MEDIA_CLASS_APP + assert child_1.media_content_id == lovelace_cast.DEFAULT_DASHBOARD + assert child_1.media_content_type == lovelace_cast.DOMAIN + assert child_1.thumbnail == "https://brands.home-assistant.io/_/lovelace/logo.png" + assert child_1.can_play is True + assert child_1.can_expand is False + + child_2 = top_level_items.children[1] + assert child_2.title == "YAML Title" + assert child_2.media_class == lovelace_cast.MEDIA_CLASS_APP + assert child_2.media_content_id == "yaml-with-views" + assert child_2.media_content_type == lovelace_cast.DOMAIN + assert child_2.thumbnail == "https://brands.home-assistant.io/_/lovelace/logo.png" + assert child_2.can_play is True + assert child_2.can_expand is True + + child_2 = await lovelace_cast.async_browse_media( + hass, "lovelace", child_2.media_content_id, lovelace_cast.CAST_TYPE_CHROMECAST + ) + + assert len(child_2.children) == 2 + + grandchild_1 = child_2.children[0] + assert grandchild_1.title == "Hello" + assert grandchild_1.media_class == lovelace_cast.MEDIA_CLASS_APP + assert grandchild_1.media_content_id == "yaml-with-views/0" + assert grandchild_1.media_content_type == lovelace_cast.DOMAIN + assert ( + grandchild_1.thumbnail == "https://brands.home-assistant.io/_/lovelace/logo.png" + ) + assert grandchild_1.can_play is True + assert grandchild_1.can_expand is False + + grandchild_2 = child_2.children[1] + assert grandchild_2.title == "second-view" + assert grandchild_2.media_class == lovelace_cast.MEDIA_CLASS_APP + assert grandchild_2.media_content_id == "yaml-with-views/second-view" + assert grandchild_2.media_content_type == lovelace_cast.DOMAIN + assert ( + grandchild_2.thumbnail == "https://brands.home-assistant.io/_/lovelace/logo.png" + ) + assert grandchild_2.can_play is True + assert grandchild_2.can_expand is False + + with pytest.raises(HomeAssistantError): + await lovelace_cast.async_browse_media( + hass, + "lovelace", + "non-existing-dashboard", + lovelace_cast.CAST_TYPE_CHROMECAST, + ) + + +async def test_play_media(hass, mock_yaml_dashboard): + """Test playing media.""" + calls = async_mock_service(hass, "cast", "show_lovelace_view") + + await lovelace_cast.async_play_media( + hass, "media_player.my_cast", None, "lovelace", lovelace_cast.DEFAULT_DASHBOARD + ) + + assert len(calls) == 1 + assert calls[0].data["entity_id"] == "media_player.my_cast" + assert "dashboard_path" not in calls[0].data + assert calls[0].data["view_path"] == "0" + + await lovelace_cast.async_play_media( + hass, "media_player.my_cast", None, "lovelace", "yaml-with-views/second-view" + ) + + assert len(calls) == 2 + assert calls[1].data["entity_id"] == "media_player.my_cast" + assert calls[1].data["dashboard_path"] == "yaml-with-views" + assert calls[1].data["view_path"] == "second-view" From 718da64728260cbe0e59f424838d31d22470b278 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Thu, 3 Feb 2022 23:34:01 +0100 Subject: [PATCH 0247/1098] Improve code quality sensibo (#65503) --- homeassistant/components/sensibo/climate.py | 63 +++++++-------------- 1 file changed, 22 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index 16d7f8601c9b95..b0cee4fbac01a4 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -2,7 +2,6 @@ from __future__ import annotations import asyncio -from typing import Any from aiohttp.client_exceptions import ClientConnectionError import async_timeout @@ -71,6 +70,14 @@ HA_TO_SENSIBO = {value: key for key, value in SENSIBO_TO_HA.items()} +AC_STATE_TO_DATA = { + "targetTemperature": "target_temp", + "fanLevel": "fan_mode", + "on": "on", + "mode": "hvac_mode", + "swing": "swing_mode", +} + async def async_setup_platform( hass: HomeAssistant, @@ -253,68 +260,39 @@ async def async_set_temperature(self, **kwargs) -> None: else: return - result = await self._async_set_ac_state_property( - "targetTemperature", int(temperature) - ) - if result: - self.coordinator.data[self.unique_id]["target_temp"] = int(temperature) - self.async_write_ha_state() + await self._async_set_ac_state_property("targetTemperature", int(temperature)) async def async_set_fan_mode(self, fan_mode: str) -> None: """Set new target fan mode.""" - result = await self._async_set_ac_state_property("fanLevel", fan_mode) - if result: - self.coordinator.data[self.unique_id]["fan_mode"] = fan_mode - self.async_write_ha_state() + await self._async_set_ac_state_property("fanLevel", fan_mode) async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new target operation mode.""" if hvac_mode == HVAC_MODE_OFF: - result = await self._async_set_ac_state_property("on", False) - if result: - self.coordinator.data[self.unique_id]["on"] = False - self.async_write_ha_state() + await self._async_set_ac_state_property("on", False) return # Turn on if not currently on. if not self.coordinator.data[self.unique_id]["on"]: - result = await self._async_set_ac_state_property("on", True) - if result: - self.coordinator.data[self.unique_id]["on"] = True + await self._async_set_ac_state_property("on", True) - result = await self._async_set_ac_state_property( - "mode", HA_TO_SENSIBO[hvac_mode] - ) - if result: - self.coordinator.data[self.unique_id]["hvac_mode"] = HA_TO_SENSIBO[ - hvac_mode - ] - self.async_write_ha_state() + await self._async_set_ac_state_property("mode", HA_TO_SENSIBO[hvac_mode]) async def async_set_swing_mode(self, swing_mode: str) -> None: """Set new target swing operation.""" - result = await self._async_set_ac_state_property("swing", swing_mode) - if result: - self.coordinator.data[self.unique_id]["swing_mode"] = swing_mode - self.async_write_ha_state() + await self._async_set_ac_state_property("swing", swing_mode) async def async_turn_on(self) -> None: """Turn Sensibo unit on.""" - result = await self._async_set_ac_state_property("on", True) - if result: - self.coordinator.data[self.unique_id]["on"] = True - self.async_write_ha_state() + await self._async_set_ac_state_property("on", True) async def async_turn_off(self) -> None: """Turn Sensibo unit on.""" - result = await self._async_set_ac_state_property("on", False) - if result: - self.coordinator.data[self.unique_id]["on"] = False - self.async_write_ha_state() + await self._async_set_ac_state_property("on", False) async def _async_set_ac_state_property( - self, name: str, value: Any, assumed_state: bool = False - ) -> bool: + self, name: str, value: str | int | bool, assumed_state: bool = False + ) -> None: """Set AC state.""" result = {} try: @@ -336,7 +314,10 @@ async def _async_set_ac_state_property( ) from err LOGGER.debug("Result: %s", result) if result["status"] == "Success": - return True + self.coordinator.data[self.unique_id][AC_STATE_TO_DATA[name]] = value + self.async_write_ha_state() + return + failure = result["failureReason"] raise HomeAssistantError( f"Could not set state for device {self.name} due to reason {failure}" From 8c0c4f9bcac153054caf2fcc8025fc63805e2601 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 3 Feb 2022 17:03:18 -0600 Subject: [PATCH 0248/1098] Log traceback in debug for Sonos unsubscribe errors (#65596) --- homeassistant/components/sonos/speaker.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index b5ae20e1123742..e40fe901b0998c 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -400,7 +400,12 @@ async def async_unsubscribe(self) -> None: ) for result in results: if isinstance(result, Exception): - _LOGGER.debug("Unsubscribe failed for %s: %s", self.zone_name, result) + _LOGGER.debug( + "Unsubscribe failed for %s: %s", + self.zone_name, + result, + exc_info=result, + ) self._subscriptions = [] @callback From 24cd63973d5cdd114f74a3c2ae99fa90bd81a74e Mon Sep 17 00:00:00 2001 From: G Johansson Date: Fri, 4 Feb 2022 00:05:56 +0100 Subject: [PATCH 0249/1098] Add back resolvers config flow dnsip (#65570) --- homeassistant/components/dnsip/config_flow.py | 17 ++++++- homeassistant/components/dnsip/strings.json | 4 +- .../components/dnsip/translations/en.json | 4 +- tests/components/dnsip/test_config_flow.py | 44 +++++++++++++++++++ 4 files changed, 65 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/dnsip/config_flow.py b/homeassistant/components/dnsip/config_flow.py index bedcc5f821cddb..2db0034b697fd3 100644 --- a/homeassistant/components/dnsip/config_flow.py +++ b/homeassistant/components/dnsip/config_flow.py @@ -33,6 +33,13 @@ vol.Required(CONF_HOSTNAME, default=DEFAULT_HOSTNAME): cv.string, } ) +DATA_SCHEMA_ADV = vol.Schema( + { + vol.Required(CONF_HOSTNAME, default=DEFAULT_HOSTNAME): cv.string, + vol.Optional(CONF_RESOLVER, default=DEFAULT_RESOLVER): cv.string, + vol.Optional(CONF_RESOLVER_IPV6, default=DEFAULT_RESOLVER_IPV6): cv.string, + } +) async def async_validate_hostname( @@ -94,8 +101,8 @@ async def async_step_user( hostname = user_input[CONF_HOSTNAME] name = DEFAULT_NAME if hostname == DEFAULT_HOSTNAME else hostname - resolver = DEFAULT_RESOLVER - resolver_ipv6 = DEFAULT_RESOLVER_IPV6 + resolver = user_input.get(CONF_RESOLVER, DEFAULT_RESOLVER) + resolver_ipv6 = user_input.get(CONF_RESOLVER_IPV6, DEFAULT_RESOLVER_IPV6) validate = await async_validate_hostname(hostname, resolver, resolver_ipv6) @@ -119,6 +126,12 @@ async def async_step_user( }, ) + if self.show_advanced_options is True: + return self.async_show_form( + step_id="user", + data_schema=DATA_SCHEMA_ADV, + errors=errors, + ) return self.async_show_form( step_id="user", data_schema=DATA_SCHEMA, diff --git a/homeassistant/components/dnsip/strings.json b/homeassistant/components/dnsip/strings.json index 06672e6fb68f69..cd95c9db27f7d2 100644 --- a/homeassistant/components/dnsip/strings.json +++ b/homeassistant/components/dnsip/strings.json @@ -3,7 +3,9 @@ "step": { "user": { "data": { - "hostname": "The hostname for which to perform the DNS query" + "hostname": "The hostname for which to perform the DNS query", + "resolver": "Resolver for IPV4 lookup", + "resolver_ipv6": "Resolver for IPV6 lookup" } } }, diff --git a/homeassistant/components/dnsip/translations/en.json b/homeassistant/components/dnsip/translations/en.json index 7b2e2f9e6c7fd2..2c773375860bbe 100644 --- a/homeassistant/components/dnsip/translations/en.json +++ b/homeassistant/components/dnsip/translations/en.json @@ -6,7 +6,9 @@ "step": { "user": { "data": { - "hostname": "The hostname for which to perform the DNS query" + "hostname": "The hostname for which to perform the DNS query", + "resolver": "Resolver for IPV4 lookup", + "resolver_ipv6": "Resolver for IPV6 lookup" } } } diff --git a/tests/components/dnsip/test_config_flow.py b/tests/components/dnsip/test_config_flow.py index 59dcb81aa94a0a..f4684eb1cc4099 100644 --- a/tests/components/dnsip/test_config_flow.py +++ b/tests/components/dnsip/test_config_flow.py @@ -7,6 +7,7 @@ import pytest from homeassistant import config_entries +from homeassistant.components.dnsip.config_flow import DATA_SCHEMA, DATA_SCHEMA_ADV from homeassistant.components.dnsip.const import ( CONF_HOSTNAME, CONF_IPV4, @@ -47,6 +48,7 @@ async def test_form(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) assert result["type"] == "form" + assert result["data_schema"] == DATA_SCHEMA assert result["errors"] == {} with patch( @@ -79,6 +81,48 @@ async def test_form(hass: HomeAssistant) -> None: assert len(mock_setup_entry.mock_calls) == 1 +async def test_form_adv(hass: HomeAssistant) -> None: + """Test we get the form with advanced options on.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_USER, "show_advanced_options": True}, + ) + + assert result["data_schema"] == DATA_SCHEMA_ADV + + with patch( + "homeassistant.components.dnsip.config_flow.aiodns.DNSResolver", + return_value=RetrieveDNS(), + ), patch( + "homeassistant.components.dnsip.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_HOSTNAME: "home-assistant.io", + CONF_RESOLVER: "8.8.8.8", + CONF_RESOLVER_IPV6: "2620:0:ccc::2", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == "home-assistant.io" + assert result2["data"] == { + "hostname": "home-assistant.io", + "name": "home-assistant.io", + "ipv4": True, + "ipv6": True, + } + assert result2["options"] == { + "resolver": "8.8.8.8", + "resolver_ipv6": "2620:0:ccc::2", + } + assert len(mock_setup_entry.mock_calls) == 1 + + async def test_form_error(hass: HomeAssistant) -> None: """Test validate url fails.""" result = await hass.config_entries.flow.async_init( From 189f1f8c25bc68abab8ef103fc19134c2552b57c Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Thu, 3 Feb 2022 23:30:09 +0000 Subject: [PATCH 0250/1098] Add kmtronic device_info (#65456) Co-authored-by: Martin Hjelmare Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> --- homeassistant/components/kmtronic/switch.py | 26 +++++++++++---------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/kmtronic/switch.py b/homeassistant/components/kmtronic/switch.py index 26cce2c736d416..e941a2ffafac6a 100644 --- a/homeassistant/components/kmtronic/switch.py +++ b/homeassistant/components/kmtronic/switch.py @@ -1,11 +1,13 @@ """KMtronic Switch integration.""" +import urllib.parse + from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import CONF_REVERSE, DATA_COORDINATOR, DATA_HUB, DOMAIN +from .const import CONF_REVERSE, DATA_COORDINATOR, DATA_HUB, DOMAIN, MANUFACTURER async def async_setup_entry( @@ -19,7 +21,7 @@ async def async_setup_entry( async_add_entities( [ - KMtronicSwitch(coordinator, relay, reverse, entry.entry_id) + KMtronicSwitch(hub, coordinator, relay, reverse, entry.entry_id) for relay in hub.relays ] ) @@ -28,22 +30,22 @@ async def async_setup_entry( class KMtronicSwitch(CoordinatorEntity, SwitchEntity): """KMtronic Switch Entity.""" - def __init__(self, coordinator, relay, reverse, config_entry_id): + def __init__(self, hub, coordinator, relay, reverse, config_entry_id): """Pass coordinator to CoordinatorEntity.""" super().__init__(coordinator) self._relay = relay - self._config_entry_id = config_entry_id self._reverse = reverse - @property - def name(self) -> str: - """Return the name of the entity.""" - return f"Relay{self._relay.id}" + hostname = urllib.parse.urlsplit(hub.host).hostname + self._attr_device_info = { + "identifiers": {(DOMAIN, config_entry_id)}, + "name": f"Controller {hostname}", + "manufacturer": MANUFACTURER, + "configuration_url": hub.host, + } - @property - def unique_id(self) -> str: - """Return the unique ID of the entity.""" - return f"{self._config_entry_id}_relay{self._relay.id}" + self._attr_name = f"Relay{relay.id}" + self._attr_unique_id = f"{config_entry_id}_relay{relay.id}" @property def is_on(self): From 727743c4cd2745542ec64ad11780b0c66c42a22b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 3 Feb 2022 17:44:19 -0600 Subject: [PATCH 0251/1098] Fix lutron_caseta button events including area name in device name (#65601) --- homeassistant/components/lutron_caseta/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index d73e03b44d44d7..546bb055ca8a9d 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -227,7 +227,7 @@ def _async_button_event(button_id, event_type): action = ACTION_RELEASE type_ = device["type"] - name = device["name"] + area, name = device["name"].split("_", 1) button_number = device["button_number"] # The original implementation used LIP instead of LEAP # so we need to convert the button number to maintain compat @@ -252,7 +252,7 @@ def _async_button_event(button_id, event_type): ATTR_BUTTON_NUMBER: lip_button_number, ATTR_LEAP_BUTTON_NUMBER: button_number, ATTR_DEVICE_NAME: name, - ATTR_AREA_NAME: name.split("_")[0], + ATTR_AREA_NAME: area, ATTR_ACTION: action, }, ) From b2f0882e67d930eda496ffcbc1d90a14a1245faf Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 4 Feb 2022 00:13:58 +0000 Subject: [PATCH 0252/1098] [ci skip] Translation update --- .../components/airly/translations/el.json | 3 ++- .../ambient_station/translations/el.json | 9 +++++++ .../components/august/translations/nb.json | 11 ++++++++ .../aussie_broadband/translations/nb.json | 11 ++++++++ .../components/auth/translations/el.json | 9 +++++++ .../components/axis/translations/el.json | 1 + .../components/bond/translations/el.json | 3 +++ .../components/brunt/translations/nb.json | 11 ++++++++ .../components/bsblan/translations/el.json | 4 +++ .../cpuspeed/translations/zh-Hans.json | 6 +++++ .../components/daikin/translations/el.json | 10 +++++++ .../components/denonavr/translations/el.json | 16 +++++++++++ .../devolo_home_control/translations/el.json | 3 ++- .../diagnostics/translations/zh-Hans.json | 3 +++ .../dialogflow/translations/nb.json | 7 +++++ .../dnsip/translations/zh-Hans.json | 27 +++++++++++++++++++ .../components/dsmr/translations/el.json | 12 +++++++++ .../components/elkm1/translations/pt-BR.json | 2 +- .../components/elmax/translations/nb.json | 15 +++++++++++ .../emulated_roku/translations/el.json | 12 +++++++++ .../components/enocean/translations/el.json | 24 +++++++++++++++++ .../enphase_envoy/translations/nb.json | 11 ++++++++ .../components/ezviz/translations/nb.json | 21 +++++++++++++++ .../faa_delays/translations/pt-BR.json | 2 +- .../flick_electric/translations/el.json | 13 +++++++++ .../forked_daapd/translations/el.json | 1 + .../components/fritz/translations/nb.json | 26 ++++++++++++++++++ .../components/fronius/translations/nb.json | 7 +++++ .../components/geofency/translations/el.json | 6 +++++ .../components/geofency/translations/nb.json | 7 +++++ .../github/translations/zh-Hans.json | 16 +++++++++++ .../components/gpslogger/translations/nb.json | 7 +++++ .../growatt_server/translations/nb.json | 11 ++++++++ .../components/hive/translations/nb.json | 16 +++++++++++ .../components/homekit/translations/el.json | 3 ++- .../components/homekit/translations/nb.json | 16 +++++++++++ .../homekit_controller/translations/el.json | 8 +++++- .../homematicip_cloud/translations/el.json | 23 ++++++++++++++++ .../components/honeywell/translations/nb.json | 11 ++++++++ .../components/hue/translations/el.json | 16 +++++++++++ .../components/ifttt/translations/el.json | 9 +++++++ .../components/ifttt/translations/nb.json | 7 +++++ .../components/iotawatt/translations/nb.json | 11 ++++++++ .../components/iss/translations/nb.json | 8 ++++++ .../components/iss/translations/zh-Hans.json | 11 ++++++++ .../components/isy994/translations/el.json | 3 ++- .../components/jellyfin/translations/nb.json | 11 ++++++++ .../components/juicenet/translations/el.json | 9 +++++++ .../keenetic_ndms2/translations/el.json | 9 +++++++ .../keenetic_ndms2/translations/nb.json | 11 ++++++++ .../components/kmtronic/translations/nb.json | 11 ++++++++ .../components/konnected/translations/el.json | 17 ++++++++++-- .../components/lcn/translations/de.json | 2 +- .../components/lifx/translations/el.json | 9 +++++++ .../litterrobot/translations/nb.json | 11 ++++++++ .../components/locative/translations/nb.json | 7 +++++ .../components/mailgun/translations/el.json | 8 ++++++ .../components/mailgun/translations/nb.json | 7 +++++ .../media_player/translations/nb.json | 2 +- .../components/melcloud/translations/el.json | 6 +++++ .../melcloud/translations/pt-BR.json | 2 +- .../components/metoffice/translations/el.json | 10 +++++++ .../components/mill/translations/nb.json | 11 ++++++++ .../moon/translations/sensor.el.json | 8 ++++++ .../components/mqtt/translations/el.json | 6 +++++ .../components/nam/translations/nb.json | 16 +++++++++++ .../components/nest/translations/el.json | 8 +++++- .../components/netatmo/translations/el.json | 9 +++++++ .../components/netgear/translations/nb.json | 11 ++++++++ .../components/octoprint/translations/nb.json | 11 ++++++++ .../components/oncue/translations/nb.json | 12 +++++++++ .../components/onewire/translations/el.json | 18 +++++++++++++ .../components/onvif/translations/nb.json | 11 ++++++++ .../components/overkiz/translations/nb.json | 11 ++++++++ .../components/owntracks/translations/el.json | 9 +++++++ .../components/owntracks/translations/nb.json | 7 +++++ .../components/ozw/translations/el.json | 13 +++++++++ .../components/picnic/translations/nb.json | 11 ++++++++ .../components/plaato/translations/nb.json | 7 +++++ .../components/plex/translations/el.json | 3 ++- .../components/powerwall/translations/ca.json | 14 +++++++++- .../components/powerwall/translations/et.json | 14 +++++++++- .../components/powerwall/translations/hu.json | 14 +++++++++- .../powerwall/translations/pt-BR.json | 14 +++++++++- .../components/prosegur/translations/nb.json | 16 +++++++++++ .../components/ps4/translations/el.json | 6 ++++- .../components/ridwell/translations/nb.json | 11 ++++++++ .../components/ring/translations/el.json | 9 +++++++ .../components/samsungtv/translations/el.json | 3 +++ .../simplisafe/translations/pt-BR.json | 2 +- .../components/sma/translations/el.json | 3 ++- .../components/smarthab/translations/el.json | 10 +++++++ .../smartthings/translations/el.json | 7 ++++- .../components/smhi/translations/el.json | 3 ++- .../components/solax/translations/nb.json | 13 +++++++++ .../components/songpal/translations/el.json | 11 ++++++++ .../components/subaru/translations/nb.json | 11 ++++++++ .../surepetcare/translations/nb.json | 11 ++++++++ .../components/syncthru/translations/el.json | 17 ++++++++++++ .../synology_dsm/translations/nb.json | 16 +++++++++++ .../components/tailscale/translations/nb.json | 15 +++++++++++ .../tellduslive/translations/el.json | 12 +++++++++ .../components/tibber/translations/el.json | 6 +++++ .../components/toon/translations/el.json | 20 ++++++++++++++ .../components/toon/translations/pt-BR.json | 2 +- .../components/traccar/translations/nb.json | 7 +++++ .../translations/nb.json | 19 +++++++++++++ .../transmission/translations/el.json | 1 + .../components/tuya/translations/el.json | 16 ++++++++++- .../tuya/translations/select.nb.json | 9 +++++++ .../components/twilio/translations/nb.json | 7 +++++ .../components/unifi/translations/el.json | 3 +++ .../components/unifi/translations/pt-BR.json | 2 +- .../unifiprotect/translations/nb.json | 18 +++++++++++++ .../unifiprotect/translations/zh-Hans.json | 2 +- .../components/venstar/translations/nb.json | 11 ++++++++ .../components/wallbox/translations/nb.json | 11 ++++++++ .../components/watttime/translations/nb.json | 11 ++++++++ .../components/whirlpool/translations/nb.json | 11 ++++++++ .../whois/translations/zh-Hans.json | 19 +++++++++++++ .../xiaomi_aqara/translations/el.json | 1 + .../yale_smart_alarm/translations/nb.json | 16 +++++++++++ .../components/zha/translations/el.json | 3 ++- .../components/zone/translations/el.json | 13 ++++++--- .../components/zwave/translations/el.json | 3 +++ 125 files changed, 1195 insertions(+), 31 deletions(-) create mode 100644 homeassistant/components/ambient_station/translations/el.json create mode 100644 homeassistant/components/august/translations/nb.json create mode 100644 homeassistant/components/aussie_broadband/translations/nb.json create mode 100644 homeassistant/components/brunt/translations/nb.json create mode 100644 homeassistant/components/daikin/translations/el.json create mode 100644 homeassistant/components/diagnostics/translations/zh-Hans.json create mode 100644 homeassistant/components/dialogflow/translations/nb.json create mode 100644 homeassistant/components/dnsip/translations/zh-Hans.json create mode 100644 homeassistant/components/dsmr/translations/el.json create mode 100644 homeassistant/components/elmax/translations/nb.json create mode 100644 homeassistant/components/emulated_roku/translations/el.json create mode 100644 homeassistant/components/enocean/translations/el.json create mode 100644 homeassistant/components/enphase_envoy/translations/nb.json create mode 100644 homeassistant/components/ezviz/translations/nb.json create mode 100644 homeassistant/components/flick_electric/translations/el.json create mode 100644 homeassistant/components/fritz/translations/nb.json create mode 100644 homeassistant/components/fronius/translations/nb.json create mode 100644 homeassistant/components/geofency/translations/nb.json create mode 100644 homeassistant/components/github/translations/zh-Hans.json create mode 100644 homeassistant/components/gpslogger/translations/nb.json create mode 100644 homeassistant/components/growatt_server/translations/nb.json create mode 100644 homeassistant/components/hive/translations/nb.json create mode 100644 homeassistant/components/homekit/translations/nb.json create mode 100644 homeassistant/components/homematicip_cloud/translations/el.json create mode 100644 homeassistant/components/honeywell/translations/nb.json create mode 100644 homeassistant/components/ifttt/translations/nb.json create mode 100644 homeassistant/components/iotawatt/translations/nb.json create mode 100644 homeassistant/components/iss/translations/nb.json create mode 100644 homeassistant/components/iss/translations/zh-Hans.json create mode 100644 homeassistant/components/jellyfin/translations/nb.json create mode 100644 homeassistant/components/juicenet/translations/el.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/nb.json create mode 100644 homeassistant/components/kmtronic/translations/nb.json create mode 100644 homeassistant/components/lifx/translations/el.json create mode 100644 homeassistant/components/litterrobot/translations/nb.json create mode 100644 homeassistant/components/locative/translations/nb.json create mode 100644 homeassistant/components/mailgun/translations/nb.json create mode 100644 homeassistant/components/metoffice/translations/el.json create mode 100644 homeassistant/components/mill/translations/nb.json create mode 100644 homeassistant/components/moon/translations/sensor.el.json create mode 100644 homeassistant/components/nam/translations/nb.json create mode 100644 homeassistant/components/netgear/translations/nb.json create mode 100644 homeassistant/components/octoprint/translations/nb.json create mode 100644 homeassistant/components/oncue/translations/nb.json create mode 100644 homeassistant/components/onewire/translations/el.json create mode 100644 homeassistant/components/onvif/translations/nb.json create mode 100644 homeassistant/components/overkiz/translations/nb.json create mode 100644 homeassistant/components/owntracks/translations/nb.json create mode 100644 homeassistant/components/picnic/translations/nb.json create mode 100644 homeassistant/components/plaato/translations/nb.json create mode 100644 homeassistant/components/prosegur/translations/nb.json create mode 100644 homeassistant/components/ridwell/translations/nb.json create mode 100644 homeassistant/components/ring/translations/el.json create mode 100644 homeassistant/components/smarthab/translations/el.json create mode 100644 homeassistant/components/solax/translations/nb.json create mode 100644 homeassistant/components/subaru/translations/nb.json create mode 100644 homeassistant/components/surepetcare/translations/nb.json create mode 100644 homeassistant/components/syncthru/translations/el.json create mode 100644 homeassistant/components/synology_dsm/translations/nb.json create mode 100644 homeassistant/components/tailscale/translations/nb.json create mode 100644 homeassistant/components/tellduslive/translations/el.json create mode 100644 homeassistant/components/toon/translations/el.json create mode 100644 homeassistant/components/traccar/translations/nb.json create mode 100644 homeassistant/components/trafikverket_weatherstation/translations/nb.json create mode 100644 homeassistant/components/tuya/translations/select.nb.json create mode 100644 homeassistant/components/twilio/translations/nb.json create mode 100644 homeassistant/components/unifiprotect/translations/nb.json create mode 100644 homeassistant/components/venstar/translations/nb.json create mode 100644 homeassistant/components/wallbox/translations/nb.json create mode 100644 homeassistant/components/watttime/translations/nb.json create mode 100644 homeassistant/components/whirlpool/translations/nb.json create mode 100644 homeassistant/components/whois/translations/zh-Hans.json create mode 100644 homeassistant/components/yale_smart_alarm/translations/nb.json diff --git a/homeassistant/components/airly/translations/el.json b/homeassistant/components/airly/translations/el.json index 29133caadb8b92..79013ab88a42f2 100644 --- a/homeassistant/components/airly/translations/el.json +++ b/homeassistant/components/airly/translations/el.json @@ -1,7 +1,8 @@ { "config": { "error": { - "invalid_api_key": "\u0386\u03ba\u03c5\u03c1\u03bf API \u03ba\u03bb\u03b5\u03b9\u03b4\u03af" + "invalid_api_key": "\u0386\u03ba\u03c5\u03c1\u03bf API \u03ba\u03bb\u03b5\u03b9\u03b4\u03af", + "wrong_location": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03bd \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03af \u03bc\u03ad\u03c4\u03c1\u03b7\u03c3\u03b7\u03c2 Airly \u03c3\u03c4\u03b7\u03bd \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae \u03b1\u03c5\u03c4\u03ae." }, "step": { "user": { diff --git a/homeassistant/components/ambient_station/translations/el.json b/homeassistant/components/ambient_station/translations/el.json new file mode 100644 index 00000000000000..1339995b647a63 --- /dev/null +++ b/homeassistant/components/ambient_station/translations/el.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "title": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03b1\u03c2" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/august/translations/nb.json b/homeassistant/components/august/translations/nb.json new file mode 100644 index 00000000000000..0b5511ab84542e --- /dev/null +++ b/homeassistant/components/august/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user_validate": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aussie_broadband/translations/nb.json b/homeassistant/components/aussie_broadband/translations/nb.json new file mode 100644 index 00000000000000..847c45368fd80b --- /dev/null +++ b/homeassistant/components/aussie_broadband/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/auth/translations/el.json b/homeassistant/components/auth/translations/el.json index fb65cbae3f8004..3eda86b60cb6bf 100644 --- a/homeassistant/components/auth/translations/el.json +++ b/homeassistant/components/auth/translations/el.json @@ -1,5 +1,14 @@ { "mfa_setup": { + "notify": { + "step": { + "setup": { + "description": "\u0388\u03bd\u03b1\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03bc\u03b9\u03b1\u03c2 \u03c7\u03c1\u03ae\u03c3\u03b7\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03c3\u03c4\u03b1\u03bb\u03b5\u03af \u03bc\u03ad\u03c3\u03c9 **notify.{notify_service}**. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03ad \u03c4\u03bf\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9:", + "title": "\u0395\u03c0\u03b1\u03bb\u03ae\u03b8\u03b5\u03c5\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7\u03c2" + } + }, + "title": "\u0395\u03b9\u03b4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03bc\u03af\u03b1\u03c2 \u03c7\u03c1\u03ae\u03c3\u03b7\u03c2" + }, "totp": { "error": { "invalid_code": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2, \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac. \u0395\u03ac\u03bd \u03b5\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03c3\u03c5\u03bd\u03b5\u03c7\u03ce\u03c2, \u03b2\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03c4\u03bf \u03c1\u03bf\u03bb\u03cc\u03b9 \u03c4\u03bf\u03c5 \u03c3\u03c5\u03c3\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2 Home Assistant \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03ba\u03c1\u03b9\u03b2\u03ad\u03c2." diff --git a/homeassistant/components/axis/translations/el.json b/homeassistant/components/axis/translations/el.json index 4ad186fd8b46a9..c113c987cea949 100644 --- a/homeassistant/components/axis/translations/el.json +++ b/homeassistant/components/axis/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "link_local_address": "\u039f\u03b9 \u03c4\u03bf\u03c0\u03b9\u03ba\u03ad\u03c2 \u03b4\u03b9\u03b5\u03c5\u03b8\u03cd\u03bd\u03c3\u03b5\u03b9\u03c2 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03bc\u03bf\u03c5 \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03bf\u03bd\u03c4\u03b1\u03b9", "not_axis_device": "\u0397 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03c5\u03c6\u03b8\u03b5\u03af\u03c3\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Axis" }, "flow_title": "{name} ({host})", diff --git a/homeassistant/components/bond/translations/el.json b/homeassistant/components/bond/translations/el.json index e430a82e67ee78..14bbe0e5dd8b8d 100644 --- a/homeassistant/components/bond/translations/el.json +++ b/homeassistant/components/bond/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "old_firmware": "\u039c\u03b7 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03bf \u03c0\u03b1\u03bb\u03b9\u03cc \u03c5\u03bb\u03b9\u03ba\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03bc\u03b9\u03ba\u03cc \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Bond - \u03b1\u03bd\u03b1\u03b2\u03b1\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03c1\u03b9\u03bd \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03c4\u03b5" + }, "flow_title": "{name} ({host})", "step": { "confirm": { diff --git a/homeassistant/components/brunt/translations/nb.json b/homeassistant/components/brunt/translations/nb.json new file mode 100644 index 00000000000000..847c45368fd80b --- /dev/null +++ b/homeassistant/components/brunt/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bsblan/translations/el.json b/homeassistant/components/bsblan/translations/el.json index 02c3aa97f4636b..3c28dd6ca6a3ee 100644 --- a/homeassistant/components/bsblan/translations/el.json +++ b/homeassistant/components/bsblan/translations/el.json @@ -3,8 +3,12 @@ "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, + "flow_title": "{name}", "step": { "user": { + "data": { + "passkey": "\u03a3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae BSB-Lan \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Home Assistant.", "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae BSB-Lan" } diff --git a/homeassistant/components/cpuspeed/translations/zh-Hans.json b/homeassistant/components/cpuspeed/translations/zh-Hans.json index 327a580fc13c56..41130cbdd39fc5 100644 --- a/homeassistant/components/cpuspeed/translations/zh-Hans.json +++ b/homeassistant/components/cpuspeed/translations/zh-Hans.json @@ -1,7 +1,13 @@ { "config": { + "abort": { + "alread_configured": "\u5f53\u524d\u96c6\u6210\u5df2\u88ab\u914d\u7f6e\uff0c\u4ec5\u80fd\u53ea\u6709\u4e00\u4e2a\u914d\u7f6e", + "already_configured": "\u5f53\u524d\u96c6\u6210\u5df2\u88ab\u914d\u7f6e\uff0c\u4ec5\u80fd\u53ea\u6709\u4e00\u4e2a\u914d\u7f6e", + "not_compatible": "\u65e0\u6cd5\u83b7\u53d6 CPU \u4fe1\u606f\uff0c\u8be5\u96c6\u6210\u4e0e\u60a8\u7684\u7cfb\u7edf\u4e0d\u517c\u5bb9" + }, "step": { "user": { + "description": "\u8bf7\u95ee\u60a8\u662f\u5426\u8981\u5f00\u59cb\u914d\u7f6e\uff1f", "title": "CPU \u901f\u5ea6" } } diff --git a/homeassistant/components/daikin/translations/el.json b/homeassistant/components/daikin/translations/el.json new file mode 100644 index 00000000000000..606c529facc687 --- /dev/null +++ b/homeassistant/components/daikin/translations/el.json @@ -0,0 +1,10 @@ +{ + "config": { + "step": { + "user": { + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03bf\u03c5 Daikin AC. \n\n \u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03c4\u03b1 \u039a\u03bb\u03b5\u03b9\u03b4\u03af API \u03ba\u03b1\u03b9 \u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03bf\u03cd\u03bd\u03c4\u03b1\u03b9 \u03bc\u03cc\u03bd\u03bf \u03b1\u03c0\u03cc \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 BRP072Cxx \u03ba\u03b1\u03b9 SKYFi \u03b1\u03bd\u03c4\u03af\u03c3\u03c4\u03bf\u03b9\u03c7\u03b1.", + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Daikin AC" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/denonavr/translations/el.json b/homeassistant/components/denonavr/translations/el.json index b648ed0573a884..12f69b931c8247 100644 --- a/homeassistant/components/denonavr/translations/el.json +++ b/homeassistant/components/denonavr/translations/el.json @@ -1,6 +1,21 @@ { "config": { + "abort": { + "not_denonavr_manufacturer": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03ad\u03ba\u03c4\u03b7\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 Denon AVR, \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b5 \u03cc\u03c4\u03b9 \u03bf \u03ba\u03b1\u03c4\u03b1\u03c3\u03ba\u03b5\u03c5\u03b1\u03c3\u03c4\u03ae\u03c2 \u03b4\u03b5\u03bd \u03c4\u03b1\u03b9\u03c1\u03b9\u03ac\u03b6\u03b5\u03b9", + "not_denonavr_missing": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03ad\u03ba\u03c4\u03b7\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 Denon AVR, \u03bf\u03b9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03bc\u03bf\u03cd \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03bb\u03ae\u03c1\u03b5\u03b9\u03c2" + }, + "error": { + "discovery_error": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7\u03c2 \u03b4\u03ad\u03ba\u03c4\u03b7 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 Denon AVR" + }, + "flow_title": "{name}", "step": { + "select": { + "data": { + "select_host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03b4\u03ad\u03ba\u03c4\u03b7" + }, + "description": "\u0395\u03ba\u03c4\u03b5\u03bb\u03ad\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b1\u03bd \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03b5\u03c0\u03b9\u03c0\u03bb\u03ad\u03bf\u03bd \u03b4\u03ad\u03ba\u03c4\u03b5\u03c2.", + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf \u03b4\u03ad\u03ba\u03c4\u03b7 \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5" + }, "user": { "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b4\u03ad\u03ba\u03c4\u03b7 \u03c3\u03b1\u03c2, \u03b5\u03ac\u03bd \u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7.", "title": "\u0394\u03ad\u03ba\u03c4\u03b5\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 Denon AVR" @@ -16,6 +31,7 @@ "zone2": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b6\u03ce\u03bd\u03b7\u03c2 2", "zone3": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b6\u03ce\u03bd\u03b7\u03c2 3" }, + "description": "\u039a\u03b1\u03b8\u03bf\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ad\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2", "title": "\u0394\u03ad\u03ba\u03c4\u03b5\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 Denon AVR" } } diff --git a/homeassistant/components/devolo_home_control/translations/el.json b/homeassistant/components/devolo_home_control/translations/el.json index 97a0f70ec26e52..b7fc1d36b5259b 100644 --- a/homeassistant/components/devolo_home_control/translations/el.json +++ b/homeassistant/components/devolo_home_control/translations/el.json @@ -6,7 +6,8 @@ "step": { "user": { "data": { - "mydevolo_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c4\u03bf\u03c5 mydevolo" + "mydevolo_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c4\u03bf\u03c5 mydevolo", + "username": "Email / devolo ID" } } } diff --git a/homeassistant/components/diagnostics/translations/zh-Hans.json b/homeassistant/components/diagnostics/translations/zh-Hans.json new file mode 100644 index 00000000000000..6baa0721470102 --- /dev/null +++ b/homeassistant/components/diagnostics/translations/zh-Hans.json @@ -0,0 +1,3 @@ +{ + "title": "\u8bca\u65ad" +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/translations/nb.json b/homeassistant/components/dialogflow/translations/nb.json new file mode 100644 index 00000000000000..d5b8a58a422e02 --- /dev/null +++ b/homeassistant/components/dialogflow/translations/nb.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cloud_not_connected": "Ikke tilkoblet Home Assistant Cloud." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dnsip/translations/zh-Hans.json b/homeassistant/components/dnsip/translations/zh-Hans.json new file mode 100644 index 00000000000000..5ef5b36c80d795 --- /dev/null +++ b/homeassistant/components/dnsip/translations/zh-Hans.json @@ -0,0 +1,27 @@ +{ + "config": { + "error": { + "invalid_hostname": "\u65e0\u6548\u7684\u57df\u540d\u6216\u4e3b\u673a\u540d" + }, + "step": { + "user": { + "data": { + "hostname": "\u8bf7\u952e\u5165\u60a8\u60f3\u8981\u6267\u884c DNS \u67e5\u8be2\u7684\u57df\u540d\u6216\u4e3b\u673a\u540d" + } + } + } + }, + "options": { + "error": { + "invalid_resolver": "DNS \u89e3\u6790\u670d\u52a1\u5668 IP \u5730\u5740\u65e0\u6548" + }, + "step": { + "init": { + "data": { + "resolver": "IPv4 DNS \u89e3\u6790\u670d\u52a1\u5668", + "resolver_ipv6": "IPv6 DNS \u89e3\u6790\u670d\u52a1\u5668" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dsmr/translations/el.json b/homeassistant/components/dsmr/translations/el.json new file mode 100644 index 00000000000000..2c314f33c2d83e --- /dev/null +++ b/homeassistant/components/dsmr/translations/el.json @@ -0,0 +1,12 @@ +{ + "options": { + "step": { + "init": { + "data": { + "time_between_update": "\u0395\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf\u03c2 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c2 \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03b5\u03c9\u03bd \u03bf\u03bd\u03c4\u03bf\u03c4\u03ae\u03c4\u03c9\u03bd [s]" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 DSMR" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elkm1/translations/pt-BR.json b/homeassistant/components/elkm1/translations/pt-BR.json index ffc9c5ba4ec3ff..4178ce86cb678d 100644 --- a/homeassistant/components/elkm1/translations/pt-BR.json +++ b/homeassistant/components/elkm1/translations/pt-BR.json @@ -2,7 +2,7 @@ "config": { "abort": { "address_already_configured": "Um ElkM1 com este endere\u00e7o j\u00e1 est\u00e1 configurado", - "already_configured": "Um ElkM1 com este prefixo j\u00e1 est\u00e1 configurado" + "already_configured": "A conta j\u00e1 foi configurada" }, "error": { "cannot_connect": "Falha ao conectar", diff --git a/homeassistant/components/elmax/translations/nb.json b/homeassistant/components/elmax/translations/nb.json new file mode 100644 index 00000000000000..f126937f2fea10 --- /dev/null +++ b/homeassistant/components/elmax/translations/nb.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "step": { + "user": { + "data": { + "password": "Passord", + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/translations/el.json b/homeassistant/components/emulated_roku/translations/el.json new file mode 100644 index 00000000000000..8bd1aa046be7ea --- /dev/null +++ b/homeassistant/components/emulated_roku/translations/el.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host_ip": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae", + "listen_port": "\u0391\u03ba\u03c1\u03cc\u03b1\u03c3\u03b7 \u03b8\u03cd\u03c1\u03b1\u03c2" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/enocean/translations/el.json b/homeassistant/components/enocean/translations/el.json new file mode 100644 index 00000000000000..dfbcfc4711f7cd --- /dev/null +++ b/homeassistant/components/enocean/translations/el.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "invalid_dongle_path": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae dongle" + }, + "error": { + "invalid_dongle_path": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf dongle \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae" + }, + "step": { + "detect": { + "data": { + "path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae dongle USB" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf ENOcean dongle" + }, + "manual": { + "data": { + "path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae dongle USB" + }, + "title": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03b3\u03b9\u03b1 \u03c4\u03bf dongle ENOcean" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/enphase_envoy/translations/nb.json b/homeassistant/components/enphase_envoy/translations/nb.json new file mode 100644 index 00000000000000..847c45368fd80b --- /dev/null +++ b/homeassistant/components/enphase_envoy/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ezviz/translations/nb.json b/homeassistant/components/ezviz/translations/nb.json new file mode 100644 index 00000000000000..533218a036aecf --- /dev/null +++ b/homeassistant/components/ezviz/translations/nb.json @@ -0,0 +1,21 @@ +{ + "config": { + "step": { + "confirm": { + "data": { + "username": "Brukernavn" + } + }, + "user": { + "data": { + "username": "Brukernavn" + } + }, + "user_custom_url": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/pt-BR.json b/homeassistant/components/faa_delays/translations/pt-BR.json index 87e64d0db17b41..89246ded4a1d77 100644 --- a/homeassistant/components/faa_delays/translations/pt-BR.json +++ b/homeassistant/components/faa_delays/translations/pt-BR.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Este aeroporto j\u00e1 est\u00e1 configurado." + "already_configured": "A conta j\u00e1 foi configurada" }, "error": { "cannot_connect": "Falha ao conectar", diff --git a/homeassistant/components/flick_electric/translations/el.json b/homeassistant/components/flick_electric/translations/el.json new file mode 100644 index 00000000000000..136c083b7fb261 --- /dev/null +++ b/homeassistant/components/flick_electric/translations/el.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "client_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03bb\u03ac\u03c4\u03b7 (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", + "client_secret": "\u039c\u03c5\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03bb\u03ac\u03c4\u03b7 (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)" + }, + "title": "\u0394\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 Flick" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/el.json b/homeassistant/components/forked_daapd/translations/el.json index 43b3557af3ac96..569372f25793de 100644 --- a/homeassistant/components/forked_daapd/translations/el.json +++ b/homeassistant/components/forked_daapd/translations/el.json @@ -4,6 +4,7 @@ "not_forked_daapd": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 forked-daapd." }, "error": { + "forbidden": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03ba\u03b1\u03b9\u03ce\u03bc\u03b1\u03c4\u03b1 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 \u03c4\u03bf\u03c5 forked-daapd.", "websocket_not_enabled": "\u039f forked-daapd \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 websocket \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf\u03c2.", "wrong_host_or_port": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03b8\u03cd\u03c1\u03b1.", "wrong_password": "\u0395\u03c3\u03c6\u03b1\u03bb\u03bc\u03ad\u03bd\u03bf\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2.", diff --git a/homeassistant/components/fritz/translations/nb.json b/homeassistant/components/fritz/translations/nb.json new file mode 100644 index 00000000000000..5e712fccbd9839 --- /dev/null +++ b/homeassistant/components/fritz/translations/nb.json @@ -0,0 +1,26 @@ +{ + "config": { + "step": { + "confirm": { + "data": { + "username": "Brukernavn" + } + }, + "reauth_confirm": { + "data": { + "username": "Brukernavn" + } + }, + "start_config": { + "data": { + "username": "Brukernavn" + } + }, + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fronius/translations/nb.json b/homeassistant/components/fronius/translations/nb.json new file mode 100644 index 00000000000000..89900954d12aea --- /dev/null +++ b/homeassistant/components/fronius/translations/nb.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "invalid_host": "Ugyldig vertsnavn eller IP-adresse" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/translations/el.json b/homeassistant/components/geofency/translations/el.json index 5252249c79bb8d..1fc438ae03fe58 100644 --- a/homeassistant/components/geofency/translations/el.json +++ b/homeassistant/components/geofency/translations/el.json @@ -5,6 +5,12 @@ }, "create_entry": { "default": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c4\u03b5\u03af\u03bb\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03b1 \u03c3\u03c4\u03bf Home Assistant, \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 webhook \u03c3\u03c4\u03bf Geofency.\n\n\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b1\u03ba\u03cc\u03bb\u03bf\u03c5\u03b8\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2:\n\n- URL: `{webhook_url}`\n- \u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2: POST\n\n\u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]({docs_url}) \u03b3\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2." + }, + "step": { + "user": { + "description": "\u0395\u03af\u03c3\u03c4\u03b5 \u03c3\u03af\u03b3\u03bf\u03c5\u03c1\u03bf\u03b9 \u03cc\u03c4\u03b9 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Webhook Geofency;", + "title": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf Geofency Webhook" + } } } } \ No newline at end of file diff --git a/homeassistant/components/geofency/translations/nb.json b/homeassistant/components/geofency/translations/nb.json new file mode 100644 index 00000000000000..d5b8a58a422e02 --- /dev/null +++ b/homeassistant/components/geofency/translations/nb.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cloud_not_connected": "Ikke tilkoblet Home Assistant Cloud." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/github/translations/zh-Hans.json b/homeassistant/components/github/translations/zh-Hans.json new file mode 100644 index 00000000000000..74f09833a817c2 --- /dev/null +++ b/homeassistant/components/github/translations/zh-Hans.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "\u670d\u52a1\u5df2\u88ab\u914d\u7f6e", + "could_not_register": "\u65e0\u6cd5\u4f7f\u96c6\u6210\u4e0e Github \u6ce8\u518c" + }, + "step": { + "repositories": { + "data": { + "repositories": "\u9009\u62e9\u60a8\u60f3\u8981\u8ddf\u8e2a\u7684\u4ed3\u5e93(Repo)" + }, + "title": "\u914d\u7f6e\u4ed3\u5e93(Repo)" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/translations/nb.json b/homeassistant/components/gpslogger/translations/nb.json new file mode 100644 index 00000000000000..d5b8a58a422e02 --- /dev/null +++ b/homeassistant/components/gpslogger/translations/nb.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cloud_not_connected": "Ikke tilkoblet Home Assistant Cloud." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/growatt_server/translations/nb.json b/homeassistant/components/growatt_server/translations/nb.json new file mode 100644 index 00000000000000..847c45368fd80b --- /dev/null +++ b/homeassistant/components/growatt_server/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/nb.json b/homeassistant/components/hive/translations/nb.json new file mode 100644 index 00000000000000..a9f534742c5175 --- /dev/null +++ b/homeassistant/components/hive/translations/nb.json @@ -0,0 +1,16 @@ +{ + "config": { + "step": { + "reauth": { + "data": { + "username": "Brukernavn" + } + }, + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/el.json b/homeassistant/components/homekit/translations/el.json index add7d39e39a7e0..a412a0914c6499 100644 --- a/homeassistant/components/homekit/translations/el.json +++ b/homeassistant/components/homekit/translations/el.json @@ -35,7 +35,8 @@ }, "cameras": { "data": { - "camera_audio": "\u039a\u03ac\u03bc\u03b5\u03c1\u03b5\u03c2 \u03c0\u03bf\u03c5 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03bf\u03c5\u03bd \u03ae\u03c7\u03bf" + "camera_audio": "\u039a\u03ac\u03bc\u03b5\u03c1\u03b5\u03c2 \u03c0\u03bf\u03c5 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03bf\u03c5\u03bd \u03ae\u03c7\u03bf", + "camera_copy": "\u039a\u03ac\u03bc\u03b5\u03c1\u03b5\u03c2 \u03c0\u03bf\u03c5 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03bf\u03c5\u03bd \u03b5\u03b3\u03b3\u03b5\u03bd\u03b5\u03af\u03c2 \u03c1\u03bf\u03ad\u03c2 H.264" }, "description": "\u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03cc\u03bb\u03b5\u03c2 \u03c4\u03b9\u03c2 \u03ba\u03ac\u03bc\u03b5\u03c1\u03b5\u03c2 \u03c0\u03bf\u03c5 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03bf\u03c5\u03bd \u03b5\u03b3\u03b3\u03b5\u03bd\u03b5\u03af\u03c2 \u03c1\u03bf\u03ad\u03c2 H.264. \u0395\u03ac\u03bd \u03b7 \u03ba\u03ac\u03bc\u03b5\u03c1\u03b1 \u03b4\u03b5\u03bd \u03c0\u03b1\u03c1\u03ac\u03b3\u03b5\u03b9 \u03c1\u03bf\u03ae H.264, \u03c4\u03bf \u03c3\u03cd\u03c3\u03c4\u03b7\u03bc\u03b1 \u03b8\u03b1 \u03bc\u03b5\u03c4\u03b1\u03c3\u03c7\u03b7\u03bc\u03b1\u03c4\u03af\u03c3\u03b5\u03b9 \u03c4\u03bf \u03b2\u03af\u03bd\u03c4\u03b5\u03bf \u03c3\u03b5 H.264 \u03b3\u03b9\u03b1 \u03c4\u03bf HomeKit. \u0397 \u03bc\u03b5\u03c4\u03b1\u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03bc\u03b9\u03b1 \u03b1\u03c0\u03bf\u03b4\u03bf\u03c4\u03b9\u03ba\u03ae CPU \u03ba\u03b1\u03b9 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c0\u03af\u03b8\u03b1\u03bd\u03bf \u03bd\u03b1 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03b9 \u03c3\u03b5 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ad\u03c2 \u03bc\u03bf\u03bd\u03ae\u03c2 \u03c0\u03bb\u03b1\u03ba\u03ad\u03c4\u03b1\u03c2.", "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03ba\u03ac\u03bc\u03b5\u03c1\u03b1\u03c2" diff --git a/homeassistant/components/homekit/translations/nb.json b/homeassistant/components/homekit/translations/nb.json new file mode 100644 index 00000000000000..0015010c63c295 --- /dev/null +++ b/homeassistant/components/homekit/translations/nb.json @@ -0,0 +1,16 @@ +{ + "options": { + "step": { + "exclude": { + "data": { + "entities": "Entiteter" + } + }, + "include": { + "data": { + "entities": "Entiteter" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/el.json b/homeassistant/components/homekit_controller/translations/el.json index 5c687ceb5b66a5..0c40b77035daa6 100644 --- a/homeassistant/components/homekit_controller/translations/el.json +++ b/homeassistant/components/homekit_controller/translations/el.json @@ -2,9 +2,15 @@ "config": { "abort": { "accessory_not_found_error": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03b6\u03b5\u03cd\u03be\u03b7\u03c2 \u03ba\u03b1\u03b8\u03ce\u03c2 \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03c0\u03bb\u03ad\u03bf\u03bd \u03bd\u03b1 \u03b2\u03c1\u03b5\u03b8\u03b5\u03af.", - "invalid_properties": "\u0391\u03bd\u03b1\u03ba\u03bf\u03b9\u03bd\u03ce\u03b8\u03b7\u03ba\u03b1\u03bd \u03bc\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b5\u03c2 \u03b9\u03b4\u03b9\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03b1\u03c0\u03cc \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae." + "already_configured": "\u03a4\u03bf \u03b5\u03be\u03ac\u03c1\u03c4\u03b7\u03bc\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c7\u03b5\u03b9\u03c1\u03b9\u03c3\u03c4\u03ae\u03c1\u03b9\u03bf.", + "already_paired": "\u0391\u03c5\u03c4\u03cc \u03c4\u03bf \u03b1\u03be\u03b5\u03c3\u03bf\u03c5\u03ac\u03c1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 \u03ac\u03bb\u03bb\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae. \u0395\u03c0\u03b1\u03bd\u03b1\u03c6\u03ad\u03c1\u03b5\u03c4\u03b5 \u03c4\u03bf \u03b1\u03be\u03b5\u03c3\u03bf\u03c5\u03ac\u03c1 \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", + "ignored_model": "\u0397 \u03c5\u03c0\u03bf\u03c3\u03c4\u03ae\u03c1\u03b9\u03be\u03b7 \u03c4\u03bf\u03c5 HomeKit \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03bc\u03bf\u03bd\u03c4\u03ad\u03bb\u03bf \u03b5\u03af\u03bd\u03b1\u03b9 \u03bc\u03c0\u03bb\u03bf\u03ba\u03b1\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7, \u03ba\u03b1\u03b8\u03ce\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03bc\u03b9\u03b1 \u03c0\u03b9\u03bf \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03c9\u03bc\u03ad\u03bd\u03b7 \u03b5\u03b3\u03b3\u03b5\u03bd\u03ae\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7.", + "invalid_config_entry": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c9\u03c2 \u03ad\u03c4\u03bf\u03b9\u03bc\u03b7 \u03b3\u03b9\u03b1 \u03b6\u03b5\u03cd\u03be\u03b7, \u03b1\u03bb\u03bb\u03ac \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03bc\u03b9\u03b1 \u03b1\u03bd\u03c4\u03b9\u03ba\u03c1\u03bf\u03c5\u03cc\u03bc\u03b5\u03bd\u03b7 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c3\u03c4\u03bf Home Assistant, \u03b7 \u03bf\u03c0\u03bf\u03af\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03c0\u03c1\u03ce\u03c4\u03b1 \u03bd\u03b1 \u03b1\u03c6\u03b1\u03b9\u03c1\u03b5\u03b8\u03b5\u03af.", + "invalid_properties": "\u0391\u03bd\u03b1\u03ba\u03bf\u03b9\u03bd\u03ce\u03b8\u03b7\u03ba\u03b1\u03bd \u03bc\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b5\u03c2 \u03b9\u03b4\u03b9\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03b1\u03c0\u03cc \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae.", + "no_devices": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03bc\u03b7 \u03c3\u03c5\u03b6\u03b5\u03c5\u03b3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2" }, "error": { + "authentication_error": "\u039b\u03b1\u03bd\u03b8\u03b1\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 HomeKit. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", "insecure_setup_code": "\u039f \u03b6\u03b7\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03bf\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03bd\u03b1\u03c3\u03c6\u03b1\u03bb\u03ae\u03c2 \u03bb\u03cc\u03b3\u03c9 \u03c4\u03b7\u03c2 \u03b1\u03c3\u03ae\u03bc\u03b1\u03bd\u03c4\u03b7\u03c2 \u03c6\u03cd\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5. \u0391\u03c5\u03c4\u03cc \u03c4\u03bf \u03b1\u03be\u03b5\u03c3\u03bf\u03c5\u03ac\u03c1 \u03b4\u03b5\u03bd \u03c0\u03bb\u03b7\u03c1\u03bf\u03af \u03c4\u03b9\u03c2 \u03b2\u03b1\u03c3\u03b9\u03ba\u03ad\u03c2 \u03b1\u03c0\u03b1\u03b9\u03c4\u03ae\u03c3\u03b5\u03b9\u03c2 \u03b1\u03c3\u03c6\u03b1\u03bb\u03b5\u03af\u03b1\u03c2.", "max_peers_error": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b1\u03c1\u03bd\u03ae\u03b8\u03b7\u03ba\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03b9 \u03b1\u03bd\u03c4\u03b9\u03c3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7 \u03ba\u03b1\u03b8\u03ce\u03c2 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b5\u03bb\u03b5\u03cd\u03b8\u03b5\u03c1\u03bf \u03c7\u03ce\u03c1\u03bf \u03b1\u03c0\u03bf\u03b8\u03ae\u03ba\u03b5\u03c5\u03c3\u03b7\u03c2 \u03b1\u03bd\u03c4\u03b9\u03c3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7\u03c2.", "pairing_failed": "\u03a0\u03c1\u03bf\u03ad\u03ba\u03c5\u03c8\u03b5 \u03ad\u03bd\u03b1 \u03bc\u03b7 \u03b4\u03b9\u03b1\u03c7\u03b5\u03b9\u03c1\u03af\u03c3\u03b9\u03bc\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03c0\u03ac\u03b8\u03b5\u03b9\u03b1 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7\u03c2 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae. \u039c\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c0\u03c1\u03cc\u03ba\u03b5\u03b9\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c9\u03c1\u03b9\u03bd\u03ae \u03b1\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c3\u03b1\u03c2 \u03bd\u03b1 \u03bc\u03b7\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03b5\u03c0\u03af \u03c4\u03bf\u03c5 \u03c0\u03b1\u03c1\u03cc\u03bd\u03c4\u03bf\u03c2.", diff --git a/homeassistant/components/homematicip_cloud/translations/el.json b/homeassistant/components/homematicip_cloud/translations/el.json new file mode 100644 index 00000000000000..f7927ce0f96d11 --- /dev/null +++ b/homeassistant/components/homematicip_cloud/translations/el.json @@ -0,0 +1,23 @@ +{ + "config": { + "error": { + "invalid_sgtin_or_pin": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf SGTIN \u03ae \u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN, \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", + "press_the_button": "\u03a0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03bc\u03c0\u03bb\u03b5 \u03ba\u03bf\u03c5\u03bc\u03c0\u03af.", + "register_failed": "\u0397 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", + "timeout_button": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03c0\u03af\u03b5\u03c3\u03b7\u03c2 \u03bc\u03c0\u03bb\u03b5 \u03ba\u03bf\u03c5\u03bc\u03c0\u03b9\u03bf\u03cd, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac." + }, + "step": { + "init": { + "data": { + "hapid": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c3\u03b7\u03bc\u03b5\u03af\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 (SGTIN)", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1 (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03c9\u03c2 \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 \u03bf\u03bd\u03cc\u03bc\u03b1\u03c4\u03bf\u03c2 \u03b3\u03b9\u03b1 \u03cc\u03bb\u03b5\u03c2 \u03c4\u03b9\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2)" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c3\u03b7\u03bc\u03b5\u03af\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 HomematicIP" + }, + "link": { + "description": "\u03a0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03bc\u03c0\u03bb\u03b5 \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03c3\u03c4\u03bf \u03c3\u03b7\u03bc\u03b5\u03af\u03bf \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03ba\u03b1\u03b9 \u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03c5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c7\u03c9\u03c1\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf HomematicIP \u03c3\u03c4\u03bf Home Assistant.\n\n![\u0398\u03ad\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03ba\u03bf\u03c5\u03bc\u03c0\u03b9\u03bf\u03cd \u03c3\u03c4\u03b7 \u03b3\u03ad\u03c6\u03c5\u03c1\u03b1](/static/images/config_flows/config_homematicip_cloud.png)", + "title": "\u03a3\u03b7\u03bc\u03b5\u03af\u03bf \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/honeywell/translations/nb.json b/homeassistant/components/honeywell/translations/nb.json new file mode 100644 index 00000000000000..847c45368fd80b --- /dev/null +++ b/homeassistant/components/honeywell/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hue/translations/el.json b/homeassistant/components/hue/translations/el.json index 56cc2ce2752f1c..6084975c60e46e 100644 --- a/homeassistant/components/hue/translations/el.json +++ b/homeassistant/components/hue/translations/el.json @@ -1,7 +1,22 @@ { "config": { "abort": { + "all_configured": "\u038c\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03b3\u03ad\u03c6\u03c5\u03c1\u03b5\u03c2 Philips Hue \u03ad\u03c7\u03bf\u03c5\u03bd \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", + "discover_timeout": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03bc\u03bf\u03cd \u03b3\u03b5\u03c6\u03c5\u03c1\u03ce\u03bd Hue", + "no_bridges": "\u0394\u03b5\u03bd \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b1\u03bd \u03b3\u03ad\u03c6\u03c5\u03c1\u03b5\u03c2 Philips Hue", "not_hue_bridge": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b3\u03ad\u03c6\u03c5\u03c1\u03b1 Hue" + }, + "error": { + "register_failed": "\u0397 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac" + }, + "step": { + "init": { + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03b3\u03ad\u03c6\u03c5\u03c1\u03b1 Hue" + }, + "link": { + "description": "\u03a0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03c3\u03c4\u03b7 \u03b3\u03ad\u03c6\u03c5\u03c1\u03b1 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c7\u03c9\u03c1\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Philips Hue \u03bc\u03b5 \u03c4\u03bf Home Assistant. \n\n ![\u03a4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ba\u03bf\u03c5\u03bc\u03c0\u03b9\u03bf\u03cd \u03c3\u03c4\u03b7 \u03b3\u03ad\u03c6\u03c5\u03c1\u03b1](/static/images/config_philips_hue.jpg)", + "title": "\u039a\u03cc\u03bc\u03b2\u03bf\u03c2 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c9\u03bd" + } } }, "device_automation": { @@ -31,6 +46,7 @@ "step": { "init": { "data": { + "allow_hue_groups": "\u039d\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03bf\u03bd\u03c4\u03b1\u03b9 \u03bf\u03b9 \u03bf\u03bc\u03ac\u03b4\u03b5\u03c2 Hue", "allow_hue_scenes": "\u039d\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03bf\u03bd\u03c4\u03b1\u03b9 \u03c3\u03ba\u03b7\u03bd\u03ad\u03c2 Hue", "ignore_availability": "\u03a0\u03b1\u03c1\u03ac\u03b2\u03bb\u03b5\u03c8\u03b7 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2 \u03c3\u03c5\u03bd\u03b4\u03b5\u03c3\u03b9\u03bc\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b9\u03c2 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2" } diff --git a/homeassistant/components/ifttt/translations/el.json b/homeassistant/components/ifttt/translations/el.json index aecb2ee553fa42..77ccacd89d6fdd 100644 --- a/homeassistant/components/ifttt/translations/el.json +++ b/homeassistant/components/ifttt/translations/el.json @@ -2,6 +2,15 @@ "config": { "abort": { "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "create_entry": { + "default": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c4\u03b5\u03af\u03bb\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03b1 \u03c3\u03c4\u03bf\u03bd Home Assistant, \u03b8\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03ad\u03c1\u03b3\u03b5\u03b9\u03b1 \"\u0394\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b1\u03b9\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2 \u0399\u03c3\u03c4\u03bf\u03cd\" \u03b1\u03c0\u03cc \u03c4\u03b7 [\u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae IFTTT Webhook]({applet_url}). \n\n \u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2: \n\n - URL: `{webhook_url}`\n - \u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2: POST\n - \u03a4\u03cd\u03c0\u03bf\u03c2 \u03c0\u03b5\u03c1\u03b9\u03b5\u03c7\u03bf\u03bc\u03ad\u03bd\u03bf\u03c5: \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae/json \n\n \u0394\u03b5\u03af\u03c4\u03b5 [\u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]({docs_url}) \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03c4\u03bf\u03bd \u03c4\u03c1\u03cc\u03c0\u03bf \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b1\u03c5\u03c4\u03bf\u03bc\u03b1\u03c4\u03b9\u03c3\u03bc\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03c7\u03b5\u03af\u03c1\u03b9\u03c3\u03b7 \u03c4\u03c9\u03bd \u03b5\u03b9\u03c3\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03c9\u03bd \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd." + }, + "step": { + "user": { + "description": "\u0395\u03af\u03c3\u03c4\u03b5 \u03c3\u03af\u03b3\u03bf\u03c5\u03c1\u03bf\u03b9 \u03cc\u03c4\u03b9 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf IFTTT;", + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 IFTTT Webhook Applet" + } } } } \ No newline at end of file diff --git a/homeassistant/components/ifttt/translations/nb.json b/homeassistant/components/ifttt/translations/nb.json new file mode 100644 index 00000000000000..d5b8a58a422e02 --- /dev/null +++ b/homeassistant/components/ifttt/translations/nb.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cloud_not_connected": "Ikke tilkoblet Home Assistant Cloud." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iotawatt/translations/nb.json b/homeassistant/components/iotawatt/translations/nb.json new file mode 100644 index 00000000000000..b97053efa85815 --- /dev/null +++ b/homeassistant/components/iotawatt/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "auth": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iss/translations/nb.json b/homeassistant/components/iss/translations/nb.json new file mode 100644 index 00000000000000..686be80ba764cc --- /dev/null +++ b/homeassistant/components/iss/translations/nb.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "latitude_longitude_not_defined": "Lengde- og breddegrad er ikke definert i Home Assistant.", + "single_instance_allowed": "Allerede konfigurert. Kun \u00e9n enkelt konfigurasjon er mulig." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iss/translations/zh-Hans.json b/homeassistant/components/iss/translations/zh-Hans.json new file mode 100644 index 00000000000000..47c25d7ddff3c5 --- /dev/null +++ b/homeassistant/components/iss/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "show_on_map": "\u5728\u5730\u56fe\u4e0a\u663e\u793a\uff1f" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/isy994/translations/el.json b/homeassistant/components/isy994/translations/el.json index 40d3a848254927..d7732fb29f9599 100644 --- a/homeassistant/components/isy994/translations/el.json +++ b/homeassistant/components/isy994/translations/el.json @@ -23,7 +23,8 @@ "sensor_string": "\u03a3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03ba\u03cc\u03bc\u03b2\u03bf\u03c5", "variable_sensor_string": "\u039c\u03b5\u03c4\u03b1\u03b2\u03bb\u03b7\u03c4\u03ae \u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1" }, - "description": "\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 ISY: \n - \u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03ba\u03cc\u03bc\u03b2\u03bf\u03c5: \u039a\u03ac\u03b8\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ae \u03c6\u03ac\u03ba\u03b5\u03bb\u03bf\u03c2 \u03c0\u03bf\u03c5 \u03c0\u03b5\u03c1\u03b9\u03ad\u03c7\u03b5\u03b9 'Node Sensor String' \u03c3\u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03b8\u03b1 \u03b1\u03bd\u03c4\u03b9\u03bc\u03b5\u03c4\u03c9\u03c0\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c9\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03ae \u03b4\u03c5\u03b1\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2. \n - Ignore String (\u0391\u03b3\u03bd\u03bf\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac): \u039f\u03c0\u03bf\u03b9\u03b1\u03b4\u03ae\u03c0\u03bf\u03c4\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bc\u03b5 \u03c4\u03bf 'Ignore String' \u03c3\u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03b8\u03b1 \u03b1\u03b3\u03bd\u03bf\u03b5\u03af\u03c4\u03b1\u03b9. \n - \u039c\u03b5\u03c4\u03b1\u03b2\u03bb\u03b7\u03c4\u03ae \u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1: \u039a\u03ac\u03b8\u03b5 \u03bc\u03b5\u03c4\u03b1\u03b2\u03bb\u03b7\u03c4\u03ae \u03c0\u03bf\u03c5 \u03c0\u03b5\u03c1\u03b9\u03ad\u03c7\u03b5\u03b9 \u03c4\u03bf 'Variable Sensor String' \u03b8\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c4\u03b5\u03b8\u03b5\u03af \u03c9\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2. \n - \u0395\u03c0\u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac \u03c6\u03c9\u03c4\u03b5\u03b9\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03c6\u03c9\u03c4\u03cc\u03c2: \u0395\u03ac\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7, \u03b7 \u03c0\u03c1\u03bf\u03b7\u03b3\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7 \u03c6\u03c9\u03c4\u03b5\u03b9\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b8\u03b1 \u03b1\u03c0\u03bf\u03ba\u03b1\u03b8\u03af\u03c3\u03c4\u03b1\u03c4\u03b1\u03b9 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03bd\u03cc\u03c2 \u03c6\u03c9\u03c4\u03cc\u03c2 \u03b1\u03bd\u03c4\u03af \u03b3\u03b9\u03b1 \u03c4\u03bf \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03bc\u03ad\u03bd\u03bf \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2." + "description": "\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 ISY: \n - \u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03ba\u03cc\u03bc\u03b2\u03bf\u03c5: \u039a\u03ac\u03b8\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ae \u03c6\u03ac\u03ba\u03b5\u03bb\u03bf\u03c2 \u03c0\u03bf\u03c5 \u03c0\u03b5\u03c1\u03b9\u03ad\u03c7\u03b5\u03b9 'Node Sensor String' \u03c3\u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03b8\u03b1 \u03b1\u03bd\u03c4\u03b9\u03bc\u03b5\u03c4\u03c9\u03c0\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c9\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03ae \u03b4\u03c5\u03b1\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2. \n - Ignore String (\u0391\u03b3\u03bd\u03bf\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac): \u039f\u03c0\u03bf\u03b9\u03b1\u03b4\u03ae\u03c0\u03bf\u03c4\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bc\u03b5 \u03c4\u03bf 'Ignore String' \u03c3\u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03b8\u03b1 \u03b1\u03b3\u03bd\u03bf\u03b5\u03af\u03c4\u03b1\u03b9. \n - \u039c\u03b5\u03c4\u03b1\u03b2\u03bb\u03b7\u03c4\u03ae \u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1: \u039a\u03ac\u03b8\u03b5 \u03bc\u03b5\u03c4\u03b1\u03b2\u03bb\u03b7\u03c4\u03ae \u03c0\u03bf\u03c5 \u03c0\u03b5\u03c1\u03b9\u03ad\u03c7\u03b5\u03b9 \u03c4\u03bf 'Variable Sensor String' \u03b8\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c4\u03b5\u03b8\u03b5\u03af \u03c9\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2. \n - \u0395\u03c0\u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac \u03c6\u03c9\u03c4\u03b5\u03b9\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03c6\u03c9\u03c4\u03cc\u03c2: \u0395\u03ac\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7, \u03b7 \u03c0\u03c1\u03bf\u03b7\u03b3\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7 \u03c6\u03c9\u03c4\u03b5\u03b9\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b8\u03b1 \u03b1\u03c0\u03bf\u03ba\u03b1\u03b8\u03af\u03c3\u03c4\u03b1\u03c4\u03b1\u03b9 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03bd\u03cc\u03c2 \u03c6\u03c9\u03c4\u03cc\u03c2 \u03b1\u03bd\u03c4\u03af \u03b3\u03b9\u03b1 \u03c4\u03bf \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03bc\u03ad\u03bd\u03bf \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2.", + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 ISY994" } } }, diff --git a/homeassistant/components/jellyfin/translations/nb.json b/homeassistant/components/jellyfin/translations/nb.json new file mode 100644 index 00000000000000..847c45368fd80b --- /dev/null +++ b/homeassistant/components/jellyfin/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/juicenet/translations/el.json b/homeassistant/components/juicenet/translations/el.json new file mode 100644 index 00000000000000..32140df6fa7ebe --- /dev/null +++ b/homeassistant/components/juicenet/translations/el.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API \u03b1\u03c0\u03cc https://home.juice.net/Manage." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/keenetic_ndms2/translations/el.json b/homeassistant/components/keenetic_ndms2/translations/el.json index 36a89b17449435..251383c2115ae1 100644 --- a/homeassistant/components/keenetic_ndms2/translations/el.json +++ b/homeassistant/components/keenetic_ndms2/translations/el.json @@ -1,8 +1,17 @@ { + "config": { + "step": { + "user": { + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae Keenetic NDMS2" + } + } + }, "options": { "step": { "user": { "data": { + "include_arp": "\u03a7\u03c1\u03ae\u03c3\u03b7 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd ARP (\u03b1\u03b3\u03bd\u03bf\u03bf\u03cd\u03bd\u03c4\u03b1\u03b9 \u03b5\u03ac\u03bd \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03bf\u03cd\u03bd\u03c4\u03b1\u03b9 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 hotspot)", + "include_associated": "\u03a7\u03c1\u03ae\u03c3\u03b7 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd \u03c3\u03c5\u03c3\u03c7\u03b5\u03c4\u03af\u03c3\u03b5\u03c9\u03bd WiFi AP (\u03b1\u03b3\u03bd\u03bf\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03bd \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03bf\u03cd\u03bd\u03c4\u03b1\u03b9 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 hotspot)", "interfaces": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03b4\u03b9\u03b5\u03c0\u03b1\u03c6\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7", "scan_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7\u03c2", "try_hotspot": "\u03a7\u03c1\u03ae\u03c3\u03b7 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd 'ip hotspot' (\u03c0\u03b9\u03bf \u03b1\u03ba\u03c1\u03b9\u03b2\u03ad\u03c2)" diff --git a/homeassistant/components/keenetic_ndms2/translations/nb.json b/homeassistant/components/keenetic_ndms2/translations/nb.json new file mode 100644 index 00000000000000..847c45368fd80b --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/nb.json b/homeassistant/components/kmtronic/translations/nb.json new file mode 100644 index 00000000000000..847c45368fd80b --- /dev/null +++ b/homeassistant/components/kmtronic/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/konnected/translations/el.json b/homeassistant/components/konnected/translations/el.json index 5ac9183347125c..13afb78596ffe6 100644 --- a/homeassistant/components/konnected/translations/el.json +++ b/homeassistant/components/konnected/translations/el.json @@ -1,9 +1,19 @@ { "config": { + "abort": { + "not_konn_panel": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Konnected.io" + }, "step": { + "confirm": { + "description": "\u039c\u03bf\u03bd\u03c4\u03ad\u03bb\u03bf: {model}\n \u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc: {id}\n \u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2: {host}\n \u0398\u03cd\u03c1\u03b1: {port} \n\n \u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03c6\u03bf\u03c1\u03ac \u03c4\u03c9\u03bd IO \u03ba\u03b1\u03b9 \u03c0\u03af\u03bd\u03b1\u03ba\u03b1 \u03c3\u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c0\u03af\u03bd\u03b1\u03ba\u03b1 \u03c3\u03c5\u03bd\u03b1\u03b3\u03b5\u03c1\u03bc\u03bf\u03cd Konnected.", + "title": "\u0388\u03c4\u03bf\u03b9\u03bc\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Konnected" + }, "import_confirm": { "description": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03ad\u03bd\u03b1\u03c2 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf\u03c2 \u03c0\u03af\u03bd\u03b1\u03ba\u03b1\u03c2 \u03c3\u03c5\u03bd\u03b1\u03b3\u03b5\u03c1\u03bc\u03bf\u03cd \u03bc\u03b5 \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc {id} \u03c3\u03c4\u03bf configuration.yaml. \u0391\u03c5\u03c4\u03ae \u03b7 \u03c1\u03bf\u03ae \u03b8\u03b1 \u03c3\u03b1\u03c2 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c8\u03b5\u03b9 \u03bd\u03b1 \u03c4\u03bf \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c3\u03b5 \u03bc\u03b9\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd.", "title": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 Konnected" + }, + "user": { + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03b3\u03b9\u03b1 \u03c4\u03bf Konnected Panel \u03c3\u03b1\u03c2." } } }, @@ -57,7 +67,9 @@ "alarm1": "ALARM1", "alarm2_out2": "OUT2/ALARM2", "out1": "OUT1" - } + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03c9\u03bd \u03c5\u03c0\u03cc\u03bb\u03bf\u03b9\u03c0\u03c9\u03bd I/O \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9. \u0398\u03b1 \u03bc\u03c0\u03bf\u03c1\u03ad\u03c3\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03b5\u03c4\u03b5 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03b5\u03c1\u03b5\u03af\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03c3\u03c4\u03b1 \u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03b1 \u03b2\u03ae\u03bc\u03b1\u03c4\u03b1.", + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03ba\u03c4\u03b5\u03c4\u03b1\u03bc\u03ad\u03bd\u03c9\u03bd \u03b5\u03b9\u03c3\u03cc\u03b4\u03c9\u03bd/\u03b5\u03be\u03cc\u03b4\u03c9\u03bd" }, "options_misc": { "data": { @@ -78,7 +90,8 @@ "pause": "\u03a0\u03b1\u03cd\u03c3\u03b7 \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03c0\u03b1\u03bb\u03bc\u03ce\u03bd (ms) (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", "repeat": "\u03a6\u03bf\u03c1\u03ad\u03c2 \u03b5\u03c0\u03b1\u03bd\u03ac\u03bb\u03b7\u03c8\u03b7\u03c2 (-1=\u03ac\u03c0\u03b5\u03b9\u03c1\u03b5\u03c2) (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)" }, - "description": "{zone} : \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 {state}" + "description": "{zone} : \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 {state}", + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b4\u03b9\u03b1\u03ba\u03bf\u03c0\u03c4\u03cc\u03bc\u03b5\u03bd\u03b7\u03c2 \u03b5\u03be\u03cc\u03b4\u03bf\u03c5" } } } diff --git a/homeassistant/components/lcn/translations/de.json b/homeassistant/components/lcn/translations/de.json index e7716b1bebae5c..b4a731fc1f62b3 100644 --- a/homeassistant/components/lcn/translations/de.json +++ b/homeassistant/components/lcn/translations/de.json @@ -2,7 +2,7 @@ "device_automation": { "trigger_type": { "fingerprint": "Fingerabdruckcode empfangen", - "send_keys": "Sendeschl\u00fcssel empfangen", + "send_keys": "Sende Tasten empfangen", "transmitter": "Sendercode empfangen", "transponder": "Transpondercode empfangen" } diff --git a/homeassistant/components/lifx/translations/el.json b/homeassistant/components/lifx/translations/el.json new file mode 100644 index 00000000000000..f8853cd2d4716f --- /dev/null +++ b/homeassistant/components/lifx/translations/el.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf LIFX;" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/nb.json b/homeassistant/components/litterrobot/translations/nb.json new file mode 100644 index 00000000000000..847c45368fd80b --- /dev/null +++ b/homeassistant/components/litterrobot/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/translations/nb.json b/homeassistant/components/locative/translations/nb.json new file mode 100644 index 00000000000000..d5b8a58a422e02 --- /dev/null +++ b/homeassistant/components/locative/translations/nb.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cloud_not_connected": "Ikke tilkoblet Home Assistant Cloud." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/translations/el.json b/homeassistant/components/mailgun/translations/el.json index aecb2ee553fa42..19873f86a31c2e 100644 --- a/homeassistant/components/mailgun/translations/el.json +++ b/homeassistant/components/mailgun/translations/el.json @@ -2,6 +2,14 @@ "config": { "abort": { "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "create_entry": { + "default": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c4\u03b5\u03af\u03bb\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03b1 \u03c3\u03c4\u03bf\u03bd Home Assistant, \u03b8\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf [Webhooks with Mailgun]({mailgun_url}). \n\n \u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2: \n\n - URL: `{webhook_url}`\n - \u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2: POST\n - \u03a4\u03cd\u03c0\u03bf\u03c2 \u03c0\u03b5\u03c1\u03b9\u03b5\u03c7\u03bf\u03bc\u03ad\u03bd\u03bf\u03c5: \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae/json \n\n \u0394\u03b5\u03af\u03c4\u03b5 [\u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]({docs_url}) \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03c4\u03bf\u03bd \u03c4\u03c1\u03cc\u03c0\u03bf \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b1\u03c5\u03c4\u03bf\u03bc\u03b1\u03c4\u03b9\u03c3\u03bc\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03c7\u03b5\u03af\u03c1\u03b9\u03c3\u03b7 \u03c4\u03c9\u03bd \u03b5\u03b9\u03c3\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03c9\u03bd \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd." + }, + "step": { + "user": { + "description": "\u0395\u03af\u03c3\u03c4\u03b5 \u03c3\u03af\u03b3\u03bf\u03c5\u03c1\u03bf\u03b9 \u03cc\u03c4\u03b9 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Mailgun;" + } } } } \ No newline at end of file diff --git a/homeassistant/components/mailgun/translations/nb.json b/homeassistant/components/mailgun/translations/nb.json new file mode 100644 index 00000000000000..d5b8a58a422e02 --- /dev/null +++ b/homeassistant/components/mailgun/translations/nb.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cloud_not_connected": "Ikke tilkoblet Home Assistant Cloud." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/media_player/translations/nb.json b/homeassistant/components/media_player/translations/nb.json index d533b6e447123f..f0621dde7bebce 100644 --- a/homeassistant/components/media_player/translations/nb.json +++ b/homeassistant/components/media_player/translations/nb.json @@ -6,7 +6,7 @@ "on": "P\u00e5", "paused": "Pauset", "playing": "Spiller", - "standby": "Avventer" + "standby": "Hvilemodus" } }, "title": "Mediaspiller" diff --git a/homeassistant/components/melcloud/translations/el.json b/homeassistant/components/melcloud/translations/el.json index 835523c59d749d..15d48e9c63cf54 100644 --- a/homeassistant/components/melcloud/translations/el.json +++ b/homeassistant/components/melcloud/translations/el.json @@ -2,6 +2,12 @@ "config": { "abort": { "already_configured": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 MELCloud \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf email. \u03a4\u03bf \u03ba\u03bf\u03c5\u03c0\u03cc\u03bd\u03b9 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03b1\u03bd\u03b1\u03bd\u03b5\u03c9\u03b8\u03b5\u03af." + }, + "step": { + "user": { + "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ce\u03bd\u03c4\u03b1\u03c2 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf MELCloud.", + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf MELCloud" + } } } } \ No newline at end of file diff --git a/homeassistant/components/melcloud/translations/pt-BR.json b/homeassistant/components/melcloud/translations/pt-BR.json index 6cd33d9fbb13d6..2982f4997fe2ba 100644 --- a/homeassistant/components/melcloud/translations/pt-BR.json +++ b/homeassistant/components/melcloud/translations/pt-BR.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Integra\u00e7\u00e3o MELCloud j\u00e1 configurada para este email. O token de acesso foi atualizado." + "already_configured": "A conta j\u00e1 foi configurada" }, "error": { "cannot_connect": "Falha ao conectar", diff --git a/homeassistant/components/metoffice/translations/el.json b/homeassistant/components/metoffice/translations/el.json new file mode 100644 index 00000000000000..61ccae30de9b8c --- /dev/null +++ b/homeassistant/components/metoffice/translations/el.json @@ -0,0 +1,10 @@ +{ + "config": { + "step": { + "user": { + "description": "\u03a4\u03bf \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2 \u03ba\u03b1\u03b9 \u03c4\u03bf \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2 \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03bf\u03cd\u03bd \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03bb\u03b7\u03c3\u03b9\u03ad\u03c3\u03c4\u03b5\u03c1\u03bf\u03c5 \u03bc\u03b5\u03c4\u03b5\u03c9\u03c1\u03bf\u03bb\u03bf\u03b3\u03b9\u03ba\u03bf\u03cd \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd.", + "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03bc\u03b5 \u03c4\u03bf UK Met Office" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mill/translations/nb.json b/homeassistant/components/mill/translations/nb.json new file mode 100644 index 00000000000000..cb56d003e525e6 --- /dev/null +++ b/homeassistant/components/mill/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "cloud": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/translations/sensor.el.json b/homeassistant/components/moon/translations/sensor.el.json new file mode 100644 index 00000000000000..7fc549d5a9a224 --- /dev/null +++ b/homeassistant/components/moon/translations/sensor.el.json @@ -0,0 +1,8 @@ +{ + "state": { + "moon__phase": { + "full_moon": "\u03a0\u03b1\u03bd\u03c3\u03ad\u03bb\u03b7\u03bd\u03bf\u03c2", + "new_moon": "\u039d\u03ad\u03b1 \u03a3\u03b5\u03bb\u03ae\u03bd\u03b7" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mqtt/translations/el.json b/homeassistant/components/mqtt/translations/el.json index 668a292e39b90e..68e185b73394b1 100644 --- a/homeassistant/components/mqtt/translations/el.json +++ b/homeassistant/components/mqtt/translations/el.json @@ -6,6 +6,7 @@ "step": { "broker": { "data": { + "broker": "\u039c\u03b5\u03c3\u03af\u03c4\u03b7\u03c2", "discovery": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7\u03c2" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 MQTT broker." @@ -14,6 +15,7 @@ "data": { "discovery": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7\u03c2" }, + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03bf\u03c5\u03c2 \u03c4\u03bf\u03c5 Home Assistant \u03ce\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03b5\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03bc\u03b5\u03c3\u03af\u03c4\u03b7 MQTT \u03c0\u03bf\u03c5 \u03c0\u03b1\u03c1\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf {addon};", "title": "MQTT Broker \u03bc\u03ad\u03c3\u03c9 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Home Assistant" } } @@ -41,6 +43,10 @@ "options": { "step": { "broker": { + "data": { + "broker": "\u039c\u03b5\u03c3\u03af\u03c4\u03b7\u03c2" + }, + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03bc\u03b5\u03c3\u03af\u03c4\u03b7 MQTT.", "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 broker" }, "options": { diff --git a/homeassistant/components/nam/translations/nb.json b/homeassistant/components/nam/translations/nb.json new file mode 100644 index 00000000000000..5d04c17f9326fd --- /dev/null +++ b/homeassistant/components/nam/translations/nb.json @@ -0,0 +1,16 @@ +{ + "config": { + "step": { + "credentials": { + "data": { + "username": "Brukernavn" + } + }, + "reauth_confirm": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nest/translations/el.json b/homeassistant/components/nest/translations/el.json index 0cde52b9297f95..d7b4acd20c18fb 100644 --- a/homeassistant/components/nest/translations/el.json +++ b/homeassistant/components/nest/translations/el.json @@ -1,16 +1,22 @@ { "config": { "error": { - "invalid_pin": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN" + "internal_error": "\u0395\u03c3\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03cc \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03b5\u03c0\u03b9\u03ba\u03cd\u03c1\u03c9\u03c3\u03b7\u03c2 \u03ba\u03ce\u03b4\u03b9\u03ba\u03b1", + "invalid_pin": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN", + "timeout": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03c0\u03b9\u03ba\u03cd\u03c1\u03c9\u03c3\u03b7\u03c2 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5" }, "step": { "auth": { "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd Google" }, "init": { + "data": { + "flow_impl": "\u03a0\u03ac\u03c1\u03bf\u03c7\u03bf\u03c2" + }, "title": "\u03a0\u03ac\u03c1\u03bf\u03c7\u03bf\u03c2 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "link": { + "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03c3\u03c4\u03b7 Nest, [\u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2]({url}).\n\n\u039c\u03b5\u03c4\u03ac \u03c4\u03b7\u03bd \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7, \u03b1\u03bd\u03c4\u03b9\u03b3\u03c1\u03ac\u03c8\u03c4\u03b5-\u03b5\u03c0\u03b9\u03ba\u03bf\u03bb\u03bb\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc PIN \u03c0\u03bf\u03c5 \u03c0\u03b1\u03c1\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd Nest" }, "pubsub": { diff --git a/homeassistant/components/netatmo/translations/el.json b/homeassistant/components/netatmo/translations/el.json index 16e7f3b5d17931..c71df1f9b4e27b 100644 --- a/homeassistant/components/netatmo/translations/el.json +++ b/homeassistant/components/netatmo/translations/el.json @@ -23,6 +23,15 @@ "options": { "step": { "public_weather": { + "data": { + "area_name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c4\u03b7\u03c2 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae\u03c2", + "lat_ne": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2 \u0392\u03bf\u03c1\u03b5\u03b9\u03bf\u03b1\u03bd\u03b1\u03c4\u03bf\u03bb\u03b9\u03ba\u03ae \u03b3\u03c9\u03bd\u03af\u03b1", + "lat_sw": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2 \u039d\u03bf\u03c4\u03b9\u03bf\u03b4\u03c5\u03c4\u03b9\u03ba\u03ae \u03b3\u03c9\u03bd\u03af\u03b1", + "lon_ne": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2 \u0392\u03bf\u03c1\u03b5\u03b9\u03bf\u03b1\u03bd\u03b1\u03c4\u03bf\u03bb\u03b9\u03ba\u03ae \u03b3\u03c9\u03bd\u03af\u03b1", + "lon_sw": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2 \u039d\u03bf\u03c4\u03b9\u03bf\u03b4\u03c5\u03c4\u03b9\u03ba\u03ae \u03b3\u03c9\u03bd\u03af\u03b1", + "mode": "\u03a5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03bc\u03cc\u03c2", + "show_on_map": "\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03c3\u03c4\u03bf \u03c7\u03ac\u03c1\u03c4\u03b7" + }, "description": "\u0394\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03b4\u03b7\u03bc\u03cc\u03c3\u03b9\u03bf \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd \u03b3\u03b9\u03b1 \u03bc\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae.", "title": "\u0394\u03b7\u03bc\u03cc\u03c3\u03b9\u03bf\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd Netatmo" }, diff --git a/homeassistant/components/netgear/translations/nb.json b/homeassistant/components/netgear/translations/nb.json new file mode 100644 index 00000000000000..371cb62d9b4be6 --- /dev/null +++ b/homeassistant/components/netgear/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Brukernavn (Valgfritt)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/octoprint/translations/nb.json b/homeassistant/components/octoprint/translations/nb.json new file mode 100644 index 00000000000000..847c45368fd80b --- /dev/null +++ b/homeassistant/components/octoprint/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/oncue/translations/nb.json b/homeassistant/components/oncue/translations/nb.json new file mode 100644 index 00000000000000..ef1398553b590d --- /dev/null +++ b/homeassistant/components/oncue/translations/nb.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Passord", + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onewire/translations/el.json b/homeassistant/components/onewire/translations/el.json new file mode 100644 index 00000000000000..ae7c5ea3f307ee --- /dev/null +++ b/homeassistant/components/onewire/translations/el.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "invalid_path": "\u039f \u03ba\u03b1\u03c4\u03ac\u03bb\u03bf\u03b3\u03bf\u03c2 \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5." + }, + "step": { + "owserver": { + "title": "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03b5\u03c1\u03b5\u03b9\u03ce\u03bd owserver" + }, + "user": { + "data": { + "type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 1-Wire" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/nb.json b/homeassistant/components/onvif/translations/nb.json new file mode 100644 index 00000000000000..309e8428413099 --- /dev/null +++ b/homeassistant/components/onvif/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "configure": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/overkiz/translations/nb.json b/homeassistant/components/overkiz/translations/nb.json new file mode 100644 index 00000000000000..847c45368fd80b --- /dev/null +++ b/homeassistant/components/overkiz/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/owntracks/translations/el.json b/homeassistant/components/owntracks/translations/el.json index aecb2ee553fa42..bf45bc1b864304 100644 --- a/homeassistant/components/owntracks/translations/el.json +++ b/homeassistant/components/owntracks/translations/el.json @@ -2,6 +2,15 @@ "config": { "abort": { "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "create_entry": { + "default": "\n\n \u03a3\u03c4\u03bf Android, \u03b1\u03bd\u03bf\u03af\u03be\u03c4\u03b5 [\u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae OwnTracks] ( {android_url} ), \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b9\u03c2 \u03c0\u03c1\u03bf\u03c4\u03b9\u03bc\u03ae\u03c3\u03b5\u03b9\u03c2 - > \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7. \u0391\u03bb\u03bb\u03ac\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b1\u03ba\u03cc\u03bb\u03bf\u03c5\u03b8\u03b5\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2:\n - \u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1: \u0399\u03b4\u03b9\u03c9\u03c4\u03b9\u03ba\u03cc HTTP\n - \u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2: {webhook_url}\n - \u03a4\u03b1\u03c5\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7:\n - \u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7: `' '`\n - \u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2: `` '` \n\n \u03a3\u03c4\u03bf iOS, \u03b1\u03bd\u03bf\u03af\u03be\u03c4\u03b5 [\u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae OwnTracks] ( {ios_url} ), \u03c0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03b5\u03b9\u03ba\u03bf\u03bd\u03af\u03b4\u03b9\u03bf (i) \u03b5\u03c0\u03ac\u03bd\u03c9 \u03b1\u03c1\u03b9\u03c3\u03c4\u03b5\u03c1\u03ac - > \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2. \u0391\u03bb\u03bb\u03ac\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b1\u03ba\u03cc\u03bb\u03bf\u03c5\u03b8\u03b5\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2:\n - \u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1: HTTP\n - URL: {webhook_url}\n - \u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2\n - ID \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7: `' '` \n\n {secret}\n\n \u0394\u03b5\u03af\u03c4\u03b5 [\u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]({docs_url}) \u03b3\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2." + }, + "step": { + "user": { + "description": "\u0395\u03af\u03c3\u03c4\u03b5 \u03b2\u03ad\u03b2\u03b1\u03b9\u03bf\u03b9 \u03cc\u03c4\u03b9 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf OwnTracks;", + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 OwnTracks" + } } } } \ No newline at end of file diff --git a/homeassistant/components/owntracks/translations/nb.json b/homeassistant/components/owntracks/translations/nb.json new file mode 100644 index 00000000000000..d5b8a58a422e02 --- /dev/null +++ b/homeassistant/components/owntracks/translations/nb.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cloud_not_connected": "Ikke tilkoblet Home Assistant Cloud." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ozw/translations/el.json b/homeassistant/components/ozw/translations/el.json index f30b504897dcdf..76192bb3427553 100644 --- a/homeassistant/components/ozw/translations/el.json +++ b/homeassistant/components/ozw/translations/el.json @@ -1,9 +1,15 @@ { "config": { "abort": { + "addon_info_failed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03bb\u03ae\u03c8\u03b7\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03b9\u03ce\u03bd \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 OpenZWave.", + "addon_install_failed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 OpenZWave.", + "addon_set_config_failed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd OpenZWave.", "mqtt_required": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 MQTT \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, + "error": { + "addon_start_failed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 OpenZWave. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7." + }, "progress": { "install_addon": "\u03a0\u03b5\u03c1\u03b9\u03bc\u03ad\u03bd\u03b5\u03c4\u03b5 \u03bc\u03ad\u03c7\u03c1\u03b9 \u03bd\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03c9\u03b8\u03b5\u03af \u03b7 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 OpenZWave. \u0391\u03c5\u03c4\u03cc \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b4\u03b9\u03b1\u03c1\u03ba\u03ad\u03c3\u03b5\u03b9 \u03b1\u03c1\u03ba\u03b5\u03c4\u03ac \u03bb\u03b5\u03c0\u03c4\u03ac." }, @@ -11,6 +17,13 @@ "install_addon": { "title": "\u0397 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 OpenZWave \u03ad\u03c7\u03b5\u03b9 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03b9" }, + "on_supervisor": { + "data": { + "use_addon": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf OpenZWave Supervisor" + }, + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf OpenZWave Supervisor;", + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03ad\u03b8\u03bf\u03b4\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "start_addon": { "data": { "network_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5" diff --git a/homeassistant/components/picnic/translations/nb.json b/homeassistant/components/picnic/translations/nb.json new file mode 100644 index 00000000000000..847c45368fd80b --- /dev/null +++ b/homeassistant/components/picnic/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plaato/translations/nb.json b/homeassistant/components/plaato/translations/nb.json new file mode 100644 index 00000000000000..d5b8a58a422e02 --- /dev/null +++ b/homeassistant/components/plaato/translations/nb.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cloud_not_connected": "Ikke tilkoblet Home Assistant Cloud." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plex/translations/el.json b/homeassistant/components/plex/translations/el.json index 033f78316a9654..debd53f9f8a35d 100644 --- a/homeassistant/components/plex/translations/el.json +++ b/homeassistant/components/plex/translations/el.json @@ -3,7 +3,8 @@ "abort": { "all_configured": "\u038c\u03bb\u03bf\u03b9 \u03bf\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf\u03b9 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ad\u03c2 \u03ad\u03c7\u03bf\u03c5\u03bd \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", "already_configured": "\u0391\u03c5\u03c4\u03cc\u03c2 \u03bf \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 Plex \u03b5\u03af\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2", - "reauth_successful": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c4\u03b7\u03ba\u03b5 \u03be\u03b1\u03bd\u03ac \u03bc\u03b5 \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03af\u03b1" + "reauth_successful": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c4\u03b7\u03ba\u03b5 \u03be\u03b1\u03bd\u03ac \u03bc\u03b5 \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03af\u03b1", + "token_request_timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03bb\u03ae\u03c8\u03b7\u03c2 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03bf\u03cd" }, "error": { "faulty_credentials": "\u0397 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5, \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf Token", diff --git a/homeassistant/components/powerwall/translations/ca.json b/homeassistant/components/powerwall/translations/ca.json index c8020069676a67..bbba4d0bf5eed6 100644 --- a/homeassistant/components/powerwall/translations/ca.json +++ b/homeassistant/components/powerwall/translations/ca.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "El dispositiu ja est\u00e0 configurat", + "cannot_connect": "Ha fallat la connexi\u00f3", "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" }, "error": { @@ -10,8 +11,19 @@ "unknown": "Error inesperat", "wrong_version": "El teu Powerwall utilitza una versi\u00f3 de programari no compatible. L'hauries d'actualitzar o informar d'aquest problema perqu\u00e8 sigui solucionat." }, - "flow_title": "{ip_address}", + "flow_title": "{name} ({ip_address})", "step": { + "confirm_discovery": { + "description": "Vols configurar {name} ({ip_address})?", + "title": "Connexi\u00f3 amb el Powerwall" + }, + "reauth_confim": { + "data": { + "password": "Contrasenya" + }, + "description": "La contrasenya normalment s\u00f3n els darrers cinc car\u00e0cters del n\u00famero de s\u00e8rie de la pasarel\u00b7la (backup gateway) i es pot trobar a l'aplicaci\u00f3 de Tesla. Tamb\u00e9 s\u00f3n els darrers 5 car\u00e0cters de la contrasenya que es troba a l'interior de la tapa de la pasarel\u00b7la vers\u00f3 2 (backup gateway 2).", + "title": "Re-autenticaci\u00f3 del powerwall" + }, "user": { "data": { "ip_address": "Adre\u00e7a IP", diff --git a/homeassistant/components/powerwall/translations/et.json b/homeassistant/components/powerwall/translations/et.json index 98eb25ca17af68..511bc7825d8af5 100644 --- a/homeassistant/components/powerwall/translations/et.json +++ b/homeassistant/components/powerwall/translations/et.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "cannot_connect": "\u00dchendamine nurjus", "reauth_successful": "Taastuvastamine \u00f5nnestus" }, "error": { @@ -10,8 +11,19 @@ "unknown": "Ootamatu t\u00f5rge", "wrong_version": "Powerwall kasutab tarkvaraversiooni, mida ei toetata. Kaaluge tarkvara uuendamist v\u00f5i probleemist teavitamist, et see saaks lahendatud." }, - "flow_title": "{ip_address}", + "flow_title": "{name} ({ip_address})", "step": { + "confirm_discovery": { + "description": "Kas soovid seadistada {name} ({ip_address})?", + "title": "\u00dchendu Powerwalliga" + }, + "reauth_confim": { + "data": { + "password": "Salas\u00f5na" + }, + "description": "Salas\u00f5naks on tavaliselt Backup Gateway seerianumbri 5 viimast t\u00e4rki mille leiab Tesla rakendusest v\u00f5i 5 viimast t\u00e4rki Backup Gateway 2 ukse sisek\u00fcljel.", + "title": "Taasautendi Powerwall" + }, "user": { "data": { "ip_address": "IP aadress", diff --git a/homeassistant/components/powerwall/translations/hu.json b/homeassistant/components/powerwall/translations/hu.json index 8975694ca95125..6f53b1ef575f43 100644 --- a/homeassistant/components/powerwall/translations/hu.json +++ b/homeassistant/components/powerwall/translations/hu.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt." }, "error": { @@ -10,8 +11,19 @@ "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt", "wrong_version": "Az powerwall nem t\u00e1mogatott szoftververzi\u00f3t haszn\u00e1l. K\u00e9rj\u00fck, fontolja meg a probl\u00e9ma friss\u00edt\u00e9s\u00e9t vagy jelent\u00e9s\u00e9t, hogy megoldhat\u00f3 legyen." }, - "flow_title": "{ip_address}", + "flow_title": "{name} ({ip_address})", "step": { + "confirm_discovery": { + "description": "Szeretn\u00e9 be\u00e1ll\u00edtani: {name} ({ip_address})?", + "title": "Csatlakoz\u00e1s a powerwallhoz" + }, + "reauth_confim": { + "data": { + "password": "Jelsz\u00f3" + }, + "description": "A jelsz\u00f3 \u00e1ltal\u00e1ban a Biztons\u00e1gi ment\u00e9s k\u00f6zponti egys\u00e9g sorozatsz\u00e1m\u00e1nak utols\u00f3 5 karaktere, \u00e9s megtal\u00e1lhat\u00f3 a Tesla alkalmaz\u00e1sban, vagy a jelsz\u00f3 utols\u00f3 5 karaktere a Biztons\u00e1gi ment\u00e9s k\u00f6zponti egys\u00e9g 2 ajtaj\u00e1ban.", + "title": "A powerwall \u00fajrahiteles\u00edt\u00e9se" + }, "user": { "data": { "ip_address": "IP c\u00edm", diff --git a/homeassistant/components/powerwall/translations/pt-BR.json b/homeassistant/components/powerwall/translations/pt-BR.json index 1da49f708d9260..c3eea93473ad0a 100644 --- a/homeassistant/components/powerwall/translations/pt-BR.json +++ b/homeassistant/components/powerwall/translations/pt-BR.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falha em conectar", "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { @@ -10,8 +11,19 @@ "unknown": "Erro inesperado", "wrong_version": "Seu powerwall usa uma vers\u00e3o de software que n\u00e3o \u00e9 compat\u00edvel. Considere atualizar ou relatar este problema para que ele possa ser resolvido." }, - "flow_title": "{ip_address}", + "flow_title": "{name} ( {ip_address})", "step": { + "confirm_discovery": { + "description": "Deseja configurar {name} ( {ip_address} )?", + "title": "Conectar-se a owerwall" + }, + "reauth_confim": { + "data": { + "password": "Senha" + }, + "description": "A senha geralmente s\u00e3o os \u00faltimos 5 caracteres do n\u00famero de s\u00e9rie do Backup Gateway e pode ser encontrada no aplicativo Tesla ou os \u00faltimos 5 caracteres da senha encontrados dentro da porta do Backup Gateway 2.", + "title": "Reautentique o powerwall" + }, "user": { "data": { "ip_address": "Endere\u00e7o IP", diff --git a/homeassistant/components/prosegur/translations/nb.json b/homeassistant/components/prosegur/translations/nb.json new file mode 100644 index 00000000000000..c106bc179b3179 --- /dev/null +++ b/homeassistant/components/prosegur/translations/nb.json @@ -0,0 +1,16 @@ +{ + "config": { + "step": { + "reauth_confirm": { + "data": { + "username": "Brukernavn" + } + }, + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ps4/translations/el.json b/homeassistant/components/ps4/translations/el.json index 6d682ff545b4b6..e02eefac9db148 100644 --- a/homeassistant/components/ps4/translations/el.json +++ b/homeassistant/components/ps4/translations/el.json @@ -7,6 +7,7 @@ }, "error": { "credential_timeout": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03b4\u03b9\u03b1\u03c0\u03af\u03c3\u03c4\u03b5\u03c5\u03c3\u03b7\u03c2 \u03c4\u03b5\u03c1\u03bc\u03ac\u03c4\u03b9\u03c3\u03b5 \u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c4\u03b7\u03c2. \u03a0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 submit \u03b3\u03b9\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7.", + "login_failed": "\u0397 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7 \u03bc\u03b5 \u03c4\u03bf PlayStation 4 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5. \u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03bf \u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c9\u03c3\u03c4\u03cc\u03c2.", "no_ipaddress": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03bf\u03c5 PlayStation 4 \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03b5\u03c4\u03b5." }, "step": { @@ -18,12 +19,15 @@ "data": { "region": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae" }, + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c4\u03bf\u03c5 PlayStation 4. \u0393\u03b9\u03b1 \u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN, \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b9\u03c2 \"\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2\" \u03c3\u03c4\u03b7\u03bd \u03ba\u03bf\u03bd\u03c3\u03cc\u03bb\u03b1 PlayStation 4. \u03a3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c0\u03bb\u03bf\u03b7\u03b3\u03b7\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf 'Mobile App Connection Settings' (\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ce\u03bd \u03b3\u03b9\u03b1 \u03ba\u03b9\u03bd\u03b7\u03c4\u03ac) \u03ba\u03b1\u03b9 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 'Add Device' (\u03a0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2). \u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN \u03c0\u03bf\u03c5 \u03b5\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03c4\u03b1\u03b9. \u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7](https://www.home-assistant.io/components/ps4/) \u03b3\u03b9\u03b1 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2.", "title": "PlayStation 4" }, "mode": { "data": { - "ip_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP (\u0391\u03c6\u03ae\u03c3\u03c4\u03b5 \u03ba\u03b5\u03bd\u03cc \u03b5\u03ac\u03bd \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 \u03c4\u03b7\u03bd \u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u0391\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7)." + "ip_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP (\u0391\u03c6\u03ae\u03c3\u03c4\u03b5 \u03ba\u03b5\u03bd\u03cc \u03b5\u03ac\u03bd \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 \u03c4\u03b7\u03bd \u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u0391\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7).", + "mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2" }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2. \u03a4\u03bf \u03c0\u03b5\u03b4\u03af\u03bf \u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03af\u03bd\u03b5\u03b9 \u03ba\u03b5\u03bd\u03cc \u03b5\u03ac\u03bd \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03b5\u03c4\u03b5 Auto Discovery, \u03ba\u03b1\u03b8\u03ce\u03c2 \u03bf\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03b8\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03c4\u03bf\u03cd\u03bd \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b1.", "title": "PlayStation 4" } } diff --git a/homeassistant/components/ridwell/translations/nb.json b/homeassistant/components/ridwell/translations/nb.json new file mode 100644 index 00000000000000..847c45368fd80b --- /dev/null +++ b/homeassistant/components/ridwell/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ring/translations/el.json b/homeassistant/components/ring/translations/el.json new file mode 100644 index 00000000000000..ebf05b607dd87a --- /dev/null +++ b/homeassistant/components/ring/translations/el.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc Ring" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/samsungtv/translations/el.json b/homeassistant/components/samsungtv/translations/el.json index 40037f5e3eb22f..74db188f67af6a 100644 --- a/homeassistant/components/samsungtv/translations/el.json +++ b/homeassistant/components/samsungtv/translations/el.json @@ -14,6 +14,9 @@ }, "reauth_confirm": { "description": "\u039c\u03b5\u03c4\u03ac \u03c4\u03b7\u03bd \u03c5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae, \u03b1\u03c0\u03bf\u03b4\u03b5\u03c7\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b4\u03c5\u03cc\u03bc\u03b5\u03bd\u03bf \u03c0\u03b1\u03c1\u03ac\u03b8\u03c5\u03c1\u03bf \u03c3\u03c4\u03b7 {device} \u03c0\u03bf\u03c5 \u03b6\u03b7\u03c4\u03ac \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7 \u03b5\u03bd\u03c4\u03cc\u03c2 30 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03bf\u03bb\u03ad\u03c0\u03c4\u03c9\u03bd." + }, + "user": { + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c4\u03b7\u03c2 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 Samsung. \u0395\u03ac\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03b9 \u03c0\u03bf\u03c4\u03ad \u03c0\u03c1\u03b9\u03bd \u03c4\u03bf Home Assistant, \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b4\u03b5\u03af\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03b1\u03bd\u03b1\u03b4\u03c5\u03cc\u03bc\u03b5\u03bd\u03bf \u03c0\u03b1\u03c1\u03ac\u03b8\u03c5\u03c1\u03bf \u03c3\u03c4\u03b7\u03bd \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae \u03c3\u03b1\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03c3\u03b1\u03c2 \u03b6\u03b7\u03c4\u03ac \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7." } } } diff --git a/homeassistant/components/simplisafe/translations/pt-BR.json b/homeassistant/components/simplisafe/translations/pt-BR.json index f64e478a485d2b..d1473074ccaed8 100644 --- a/homeassistant/components/simplisafe/translations/pt-BR.json +++ b/homeassistant/components/simplisafe/translations/pt-BR.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Esta conta SimpliSafe j\u00e1 est\u00e1 em uso.", + "already_configured": "A conta j\u00e1 foi configurada", "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", "wrong_account": "As credenciais de usu\u00e1rio fornecidas n\u00e3o correspondem a esta conta SimpliSafe." }, diff --git a/homeassistant/components/sma/translations/el.json b/homeassistant/components/sma/translations/el.json index 26695d002b3111..14c3fe70ac4b4a 100644 --- a/homeassistant/components/sma/translations/el.json +++ b/homeassistant/components/sma/translations/el.json @@ -8,7 +8,8 @@ "data": { "group": "\u039f\u03bc\u03ac\u03b4\u03b1" }, - "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 SMA." + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 SMA.", + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 SMA Solar" } } } diff --git a/homeassistant/components/smarthab/translations/el.json b/homeassistant/components/smarthab/translations/el.json new file mode 100644 index 00000000000000..143386f4703a89 --- /dev/null +++ b/homeassistant/components/smarthab/translations/el.json @@ -0,0 +1,10 @@ +{ + "config": { + "step": { + "user": { + "description": "\u0393\u03b9\u03b1 \u03c4\u03b5\u03c7\u03bd\u03b9\u03ba\u03bf\u03cd\u03c2 \u03bb\u03cc\u03b3\u03bf\u03c5\u03c2, \u03b2\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03b5\u03cd\u03bf\u03bd\u03c4\u03b1 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03b5\u03b9\u03b4\u03b9\u03ba\u03ac \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Home Assistant. \u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae SmartHab.", + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 SmartHab" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smartthings/translations/el.json b/homeassistant/components/smartthings/translations/el.json index 4541308b526144..5900d42cba9fe2 100644 --- a/homeassistant/components/smartthings/translations/el.json +++ b/homeassistant/components/smartthings/translations/el.json @@ -1,6 +1,10 @@ { "config": { "error": { + "app_setup_error": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 SmartApp. \u03a0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", + "token_forbidden": "\u03a4\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c4\u03b1 \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b1 \u03c0\u03b5\u03b4\u03af\u03b1 OAuth.", + "token_invalid_format": "\u03a4\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03b5 \u03bc\u03bf\u03c1\u03c6\u03ae UID/GUID", + "token_unauthorized": "\u03a4\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03bb\u03ad\u03bf\u03bd \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03b7\u03bc\u03ad\u03bd\u03bf.", "webhook_error": "\u03a4\u03bf SmartThings \u03b4\u03b5\u03bd \u03bc\u03c0\u03cc\u03c1\u03b5\u03c3\u03b5 \u03bd\u03b1 \u03b5\u03c0\u03b9\u03ba\u03c5\u03c1\u03ce\u03c3\u03b5\u03b9 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c4\u03bf\u03c5 webhook. \u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c4\u03bf\u03c5 webhook \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c3\u03b2\u03ac\u03c3\u03b9\u03bc\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf \u03b4\u03b9\u03b1\u03b4\u03af\u03ba\u03c4\u03c5\u03bf \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac." }, "step": { @@ -13,7 +17,8 @@ "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1" }, "user": { - "description": "\u03a4\u03bf SmartThings \u03b8\u03b1 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af \u03ce\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c4\u03ad\u03bb\u03bd\u03b5\u03b9 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03b5\u03b9\u03c2 push \u03c3\u03c4\u03bf Home Assistant \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7:\n> {webhook_url}\n\n\u0395\u03ac\u03bd \u03b1\u03c5\u03c4\u03cc \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c9\u03c3\u03c4\u03cc, \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03ae \u03c3\u03b1\u03c2, \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf Home Assistant \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac." + "description": "\u03a4\u03bf SmartThings \u03b8\u03b1 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af \u03ce\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c4\u03ad\u03bb\u03bd\u03b5\u03b9 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03b5\u03b9\u03c2 push \u03c3\u03c4\u03bf Home Assistant \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7:\n> {webhook_url}\n\n\u0395\u03ac\u03bd \u03b1\u03c5\u03c4\u03cc \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c9\u03c3\u03c4\u03cc, \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03ae \u03c3\u03b1\u03c2, \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf Home Assistant \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", + "title": "\u0395\u03c0\u03b9\u03b2\u03b5\u03b2\u03b1\u03af\u03c9\u03c3\u03b7 URL \u03b5\u03c0\u03b1\u03bd\u03ac\u03ba\u03bb\u03b7\u03c3\u03b7\u03c2" } } } diff --git a/homeassistant/components/smhi/translations/el.json b/homeassistant/components/smhi/translations/el.json index d5323c2c07495b..65fe6161ed03a8 100644 --- a/homeassistant/components/smhi/translations/el.json +++ b/homeassistant/components/smhi/translations/el.json @@ -4,7 +4,8 @@ "already_configured": "\u039f \u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2" }, "error": { - "name_exists": "\u03a4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7" + "name_exists": "\u03a4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7", + "wrong_location": "\u03a4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u039c\u03cc\u03bd\u03bf \u03a3\u03bf\u03c5\u03b7\u03b4\u03af\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/solax/translations/nb.json b/homeassistant/components/solax/translations/nb.json new file mode 100644 index 00000000000000..66b7784c3fc7ce --- /dev/null +++ b/homeassistant/components/solax/translations/nb.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "ip_address": "IP-adresse", + "password": "Passord", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/songpal/translations/el.json b/homeassistant/components/songpal/translations/el.json index 59827459f94c65..7f36d019843581 100644 --- a/homeassistant/components/songpal/translations/el.json +++ b/homeassistant/components/songpal/translations/el.json @@ -2,6 +2,17 @@ "config": { "abort": { "not_songpal_device": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Songpal" + }, + "flow_title": "{name} ({host})", + "step": { + "init": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ({host});" + }, + "user": { + "data": { + "endpoint": "\u03a4\u03b5\u03bb\u03b9\u03ba\u03cc \u03c3\u03b7\u03bc\u03b5\u03af\u03bf" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/nb.json b/homeassistant/components/subaru/translations/nb.json new file mode 100644 index 00000000000000..847c45368fd80b --- /dev/null +++ b/homeassistant/components/subaru/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/surepetcare/translations/nb.json b/homeassistant/components/surepetcare/translations/nb.json new file mode 100644 index 00000000000000..847c45368fd80b --- /dev/null +++ b/homeassistant/components/surepetcare/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/syncthru/translations/el.json b/homeassistant/components/syncthru/translations/el.json new file mode 100644 index 00000000000000..d22c90b4e10024 --- /dev/null +++ b/homeassistant/components/syncthru/translations/el.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "invalid_url": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", + "syncthru_not_supported": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03b9 SyncThru", + "unknown_state": "\u039a\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae \u03ac\u03b3\u03bd\u03c9\u03c3\u03c4\u03b7, \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03c3\u03c5\u03bd\u03b4\u03b5\u03c3\u03b9\u03bc\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5" + }, + "flow_title": "{name}", + "step": { + "user": { + "data": { + "url": "URL \u03b4\u03b9\u03b5\u03c0\u03b1\u03c6\u03ae\u03c2 \u03b9\u03c3\u03c4\u03bf\u03cd" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/nb.json b/homeassistant/components/synology_dsm/translations/nb.json new file mode 100644 index 00000000000000..3a397e1f7d35e1 --- /dev/null +++ b/homeassistant/components/synology_dsm/translations/nb.json @@ -0,0 +1,16 @@ +{ + "config": { + "step": { + "reauth": { + "data": { + "username": "Brukernavn" + } + }, + "reauth_confirm": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tailscale/translations/nb.json b/homeassistant/components/tailscale/translations/nb.json new file mode 100644 index 00000000000000..7fa228d894a0d9 --- /dev/null +++ b/homeassistant/components/tailscale/translations/nb.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig autentisering" + }, + "step": { + "user": { + "data": { + "api_key": "API-n\u00f8kkel" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tellduslive/translations/el.json b/homeassistant/components/tellduslive/translations/el.json new file mode 100644 index 00000000000000..a7911f7f907676 --- /dev/null +++ b/homeassistant/components/tellduslive/translations/el.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "auth": { + "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 TelldusLive:\n 1. \u039a\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf\n 2. \u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf Telldus Live\n 3. \u0395\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7 **{\u03cc\u03bd\u03bf\u03bc\u03b1 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2}** (\u03ba\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf **\u039d\u03b1\u03b9**).\n 4. \u0395\u03c0\u03b9\u03c3\u03c4\u03c1\u03ad\u03c8\u03c4\u03b5 \u03b5\u03b4\u03ce \u03ba\u03b1\u03b9 \u03ba\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf **\u03a5\u03a0\u039f\u0392\u039f\u039b\u0397**.\n\n [\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd TelldusLive]({auth_url})" + }, + "user": { + "description": "\u039a\u03b5\u03bd\u03cc" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tibber/translations/el.json b/homeassistant/components/tibber/translations/el.json index bf736e096640fe..402d66a0d5576d 100644 --- a/homeassistant/components/tibber/translations/el.json +++ b/homeassistant/components/tibber/translations/el.json @@ -2,6 +2,12 @@ "config": { "error": { "timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03c4\u03bf Tibber" + }, + "step": { + "user": { + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b1\u03c0\u03cc \u03c4\u03bf https://developer.tibber.com/settings/accesstoken", + "title": "Tibber" + } } } } \ No newline at end of file diff --git a/homeassistant/components/toon/translations/el.json b/homeassistant/components/toon/translations/el.json new file mode 100644 index 00000000000000..d67f873296b484 --- /dev/null +++ b/homeassistant/components/toon/translations/el.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u0397 \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03c3\u03c5\u03bc\u03c6\u03c9\u03bd\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af.", + "no_agreements": "\u0391\u03c5\u03c4\u03cc\u03c2 \u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03bf\u03b8\u03cc\u03bd\u03b5\u03c2 Toon." + }, + "step": { + "agreement": { + "data": { + "agreement": "\u03a3\u03c5\u03bc\u03c6\u03c9\u03bd\u03af\u03b1" + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c3\u03c5\u03bc\u03c6\u03c9\u03bd\u03af\u03b1\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5.", + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03bc\u03c6\u03c9\u03bd\u03af\u03b1 \u03c3\u03b1\u03c2" + }, + "pick_implementation": { + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b5\u03bd\u03bf\u03b9\u03ba\u03b9\u03b1\u03c3\u03c4\u03ae \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/toon/translations/pt-BR.json b/homeassistant/components/toon/translations/pt-BR.json index 982203402059be..dc450b8f5ae6dc 100644 --- a/homeassistant/components/toon/translations/pt-BR.json +++ b/homeassistant/components/toon/translations/pt-BR.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "O contrato selecionado j\u00e1 est\u00e1 configurado.", + "already_configured": "A conta j\u00e1 foi configurada", "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", "no_agreements": "Esta conta n\u00e3o possui exibi\u00e7\u00f5es Toon.", diff --git a/homeassistant/components/traccar/translations/nb.json b/homeassistant/components/traccar/translations/nb.json new file mode 100644 index 00000000000000..d5b8a58a422e02 --- /dev/null +++ b/homeassistant/components/traccar/translations/nb.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cloud_not_connected": "Ikke tilkoblet Home Assistant Cloud." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_weatherstation/translations/nb.json b/homeassistant/components/trafikverket_weatherstation/translations/nb.json new file mode 100644 index 00000000000000..19fbf894f8d5d7 --- /dev/null +++ b/homeassistant/components/trafikverket_weatherstation/translations/nb.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Kontoen er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig autentisering" + }, + "step": { + "user": { + "data": { + "api_key": "API-n\u00f8kkel", + "name": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/transmission/translations/el.json b/homeassistant/components/transmission/translations/el.json index fdb3d8a981d556..9879388d0d83d2 100644 --- a/homeassistant/components/transmission/translations/el.json +++ b/homeassistant/components/transmission/translations/el.json @@ -13,6 +13,7 @@ "step": { "init": { "data": { + "limit": "\u038c\u03c1\u03b9\u03bf", "scan_interval": "\u03a3\u03c5\u03c7\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7\u03c2" }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c4\u03bf Transmission" diff --git a/homeassistant/components/tuya/translations/el.json b/homeassistant/components/tuya/translations/el.json index bfb955dca5f76f..f8d2ba4acb0645 100644 --- a/homeassistant/components/tuya/translations/el.json +++ b/homeassistant/components/tuya/translations/el.json @@ -25,11 +25,25 @@ } }, "options": { + "error": { + "dev_multi_type": "\u03a0\u03bf\u03bb\u03bb\u03ad\u03c2 \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ad\u03c7\u03bf\u03c5\u03bd \u03c4\u03bf\u03bd \u03af\u03b4\u03b9\u03bf \u03c4\u03cd\u03c0\u03bf", + "dev_not_config": "\u039f \u03c4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", + "dev_not_found": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5" + }, "step": { "device": { "data": { + "brightness_range_mode": "\u0395\u03cd\u03c1\u03bf\u03c2 \u03c6\u03c9\u03c4\u03b5\u03b9\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae", + "curr_temp_divider": "\u0394\u03b9\u03b1\u03b9\u03c1\u03ad\u03c4\u03b7\u03c2 \u03c4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1\u03c2 \u03c4\u03b9\u03bc\u03ae\u03c2 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 (0 = \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae\u03c2)", + "max_kelvin": "\u039c\u03ad\u03b3\u03b9\u03c3\u03c4\u03b7 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1 \u03c7\u03c1\u03ce\u03bc\u03b1\u03c4\u03bf\u03c2 \u03c0\u03bf\u03c5 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03bf kelvin", + "max_temp": "\u039c\u03ad\u03b3\u03b9\u03c3\u03c4\u03b7 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1-\u03c3\u03c4\u03cc\u03c7\u03bf\u03c2 (\u03c7\u03c1\u03ae\u03c3\u03b7 min \u03ba\u03b1\u03b9 max = 0 \u03b3\u03b9\u03b1 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae)", + "min_kelvin": "\u0395\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03b7 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1 \u03c7\u03c1\u03ce\u03bc\u03b1\u03c4\u03bf\u03c2 \u03c0\u03bf\u03c5 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c3\u03b5 kelvin", + "min_temp": "\u0395\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03b7 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1-\u03c3\u03c4\u03cc\u03c7\u03bf\u03c2 (\u03c7\u03c1\u03ae\u03c3\u03b7 min \u03ba\u03b1\u03b9 max = 0 \u03b3\u03b9\u03b1 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae)", "set_temp_divided": "\u03a7\u03c1\u03ae\u03c3\u03b7 \u03b4\u03b9\u03b1\u03b9\u03c1\u03b5\u03bc\u03ad\u03bd\u03b7\u03c2 \u03c4\u03b9\u03bc\u03ae\u03c2 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c4\u03bf\u03bb\u03ae \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7\u03c2 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2", - "temp_step_override": "\u0392\u03ae\u03bc\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03c3\u03c4\u03cc\u03c7\u03bf\u03c5" + "support_color": "\u0391\u03bd\u03b1\u03b3\u03ba\u03b1\u03c3\u03c4\u03b9\u03ba\u03ae \u03c5\u03c0\u03bf\u03c3\u03c4\u03ae\u03c1\u03b9\u03be\u03b7 \u03c7\u03c1\u03ce\u03bc\u03b1\u03c4\u03bf\u03c2", + "temp_divider": "\u0394\u03b9\u03b1\u03b9\u03c1\u03ad\u03c4\u03b7\u03c2 \u03c4\u03b9\u03bc\u03ce\u03bd \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 (0 = \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae\u03c2)", + "temp_step_override": "\u0392\u03ae\u03bc\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03c3\u03c4\u03cc\u03c7\u03bf\u03c5", + "tuya_max_coltemp": "\u039c\u03ad\u03b3\u03b9\u03c3\u03c4\u03b7 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1 \u03c7\u03c1\u03ce\u03bc\u03b1\u03c4\u03bf\u03c2 \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03c6\u03ad\u03c1\u03b5\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" }, "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 Tuya" }, diff --git a/homeassistant/components/tuya/translations/select.nb.json b/homeassistant/components/tuya/translations/select.nb.json new file mode 100644 index 00000000000000..f7653b352e49f2 --- /dev/null +++ b/homeassistant/components/tuya/translations/select.nb.json @@ -0,0 +1,9 @@ +{ + "state": { + "tuya__fan_angle": { + "30": "30\u00b0", + "60": "60\u00b0", + "90": "90\u00b0" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twilio/translations/nb.json b/homeassistant/components/twilio/translations/nb.json new file mode 100644 index 00000000000000..d5b8a58a422e02 --- /dev/null +++ b/homeassistant/components/twilio/translations/nb.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cloud_not_connected": "Ikke tilkoblet Home Assistant Cloud." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/translations/el.json b/homeassistant/components/unifi/translations/el.json index 4c3917b4caeef0..13413fd102c722 100644 --- a/homeassistant/components/unifi/translations/el.json +++ b/homeassistant/components/unifi/translations/el.json @@ -10,6 +10,9 @@ "flow_title": "{site} ({host})", "step": { "user": { + "data": { + "site": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2" + }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 UniFi" } } diff --git a/homeassistant/components/unifi/translations/pt-BR.json b/homeassistant/components/unifi/translations/pt-BR.json index 2419418479b39e..0e5ba9af217976 100644 --- a/homeassistant/components/unifi/translations/pt-BR.json +++ b/homeassistant/components/unifi/translations/pt-BR.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "O site de controle j\u00e1 est\u00e1 configurado", + "already_configured": "A conta j\u00e1 foi configurada", "configuration_updated": "Configura\u00e7\u00e3o atualizada.", "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, diff --git a/homeassistant/components/unifiprotect/translations/nb.json b/homeassistant/components/unifiprotect/translations/nb.json new file mode 100644 index 00000000000000..f605133f204f2c --- /dev/null +++ b/homeassistant/components/unifiprotect/translations/nb.json @@ -0,0 +1,18 @@ +{ + "config": { + "step": { + "reauth_confirm": { + "data": { + "password": "Passord", + "port": "Port", + "username": "Brukernavn" + } + }, + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifiprotect/translations/zh-Hans.json b/homeassistant/components/unifiprotect/translations/zh-Hans.json index abc844c32d9e23..4163b736ac8201 100644 --- a/homeassistant/components/unifiprotect/translations/zh-Hans.json +++ b/homeassistant/components/unifiprotect/translations/zh-Hans.json @@ -11,7 +11,7 @@ "all_updates": "\u5b9e\u65f6\u6307\u6807\uff08\u8b66\u544a\uff1a\u5c06\u663e\u8457\u589e\u52a0 CPU \u5360\u7528\uff09", "disable_rtsp": "\u7981\u7528 RTSP \u6d41" }, - "description": "\u4ec5\u5f53\u60a8\u542f\u7528\u4e86\u8bca\u65ad\u4f20\u611f\u5668\u5e76\u5e0c\u671b\u5176\u5b9e\u65f6\u66f4\u65b0\u65f6\uff0c\u624d\u5e94\u542f\u7528\u5b9e\u65f6\u6307\u6807\u9009\u9879\u3002\u5982\u679c\u672a\u542f\u7528\uff0c\u5b83\u4eec\u5c06\u6bcf 15 \u5206\u949f\u66f4\u65b0\u4e00\u6b21\u3002", + "description": "\u5f53\u60a8\u542f\u7528\u4e86\u8bca\u65ad\u4f20\u611f\u5668\u5e76\u5e0c\u671b\u5176\u5b9e\u65f6\u66f4\u65b0\u65f6\uff0c\u624d\u5e94\u542f\u7528\u5b9e\u65f6\u6307\u6807\u9009\u9879\u3002\n\u82e5\u672a\u542f\u7528\uff0c\u5219\u6bcf 15 \u5206\u949f\u66f4\u65b0\u4e00\u6b21\u6570\u636e\u3002", "title": "UniFi Protect \u9009\u9879" } } diff --git a/homeassistant/components/venstar/translations/nb.json b/homeassistant/components/venstar/translations/nb.json new file mode 100644 index 00000000000000..847c45368fd80b --- /dev/null +++ b/homeassistant/components/venstar/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/nb.json b/homeassistant/components/wallbox/translations/nb.json new file mode 100644 index 00000000000000..847c45368fd80b --- /dev/null +++ b/homeassistant/components/wallbox/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/watttime/translations/nb.json b/homeassistant/components/watttime/translations/nb.json new file mode 100644 index 00000000000000..847c45368fd80b --- /dev/null +++ b/homeassistant/components/watttime/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/whirlpool/translations/nb.json b/homeassistant/components/whirlpool/translations/nb.json new file mode 100644 index 00000000000000..847c45368fd80b --- /dev/null +++ b/homeassistant/components/whirlpool/translations/nb.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/whois/translations/zh-Hans.json b/homeassistant/components/whois/translations/zh-Hans.json new file mode 100644 index 00000000000000..821295d2c822f7 --- /dev/null +++ b/homeassistant/components/whois/translations/zh-Hans.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u670d\u52a1\u5df2\u88ab\u914d\u7f6e" + }, + "error": { + "unexpected_response": "\u6765\u81ea Whois \u7684\u672a\u77e5\u9519\u8bef", + "unknown_date_format": "Whois \u670d\u52a1\u5668\u8fd4\u56de\u672a\u77e5\u7684\u65e5\u671f\u683c\u5f0f", + "whois_command_failed": "\u6267\u884c Whois \u547d\u4ee4\u5931\u8d25\uff0c\u65e0\u6cd5\u68c0\u7d22\u5230 Whois \u4fe1\u606f" + }, + "step": { + "user": { + "data": { + "domain": "\u57df\u540d\u540d\u79f0" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_aqara/translations/el.json b/homeassistant/components/xiaomi_aqara/translations/el.json index d35fc5a2b082dc..3d7f30e39dfc7f 100644 --- a/homeassistant/components/xiaomi_aqara/translations/el.json +++ b/homeassistant/components/xiaomi_aqara/translations/el.json @@ -26,6 +26,7 @@ }, "user": { "data": { + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", "interface": "\u0397 \u03b4\u03b9\u03b5\u03c0\u03b1\u03c6\u03ae \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 \u03c0\u03c1\u03bf\u03c2 \u03c7\u03c1\u03ae\u03c3\u03b7", "mac": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 Mac (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)" }, diff --git a/homeassistant/components/yale_smart_alarm/translations/nb.json b/homeassistant/components/yale_smart_alarm/translations/nb.json new file mode 100644 index 00000000000000..c106bc179b3179 --- /dev/null +++ b/homeassistant/components/yale_smart_alarm/translations/nb.json @@ -0,0 +1,16 @@ +{ + "config": { + "step": { + "reauth_confirm": { + "data": { + "username": "Brukernavn" + } + }, + "user": { + "data": { + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zha/translations/el.json b/homeassistant/components/zha/translations/el.json index f4104fe78a505d..19e12f287ff797 100644 --- a/homeassistant/components/zha/translations/el.json +++ b/homeassistant/components/zha/translations/el.json @@ -28,7 +28,8 @@ "data": { "path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" }, - "description": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae\u03c2 \u03b8\u03cd\u03c1\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03ba\u03b5\u03c1\u03b1\u03af\u03b1 Zigbee" + "description": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae\u03c2 \u03b8\u03cd\u03c1\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03ba\u03b5\u03c1\u03b1\u03af\u03b1 Zigbee", + "title": "\u0396\u0397\u0391" } } }, diff --git a/homeassistant/components/zone/translations/el.json b/homeassistant/components/zone/translations/el.json index c71e66f6434af5..1d8e5a1c163275 100644 --- a/homeassistant/components/zone/translations/el.json +++ b/homeassistant/components/zone/translations/el.json @@ -6,9 +6,16 @@ "step": { "init": { "data": { - "icon": "\u0395\u03b9\u03ba\u03bf\u03bd\u03af\u03b4\u03b9\u03bf" - } + "icon": "\u0395\u03b9\u03ba\u03bf\u03bd\u03af\u03b4\u03b9\u03bf", + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", + "passive": "\u03a0\u03b1\u03b8\u03b7\u03c4\u03b9\u03ba\u03cc", + "radius": "\u0391\u03ba\u03c4\u03af\u03bd\u03b1" + }, + "title": "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd \u03b6\u03ce\u03bd\u03b7\u03c2" } - } + }, + "title": "\u0396\u03ce\u03bd\u03b7" } } \ No newline at end of file diff --git a/homeassistant/components/zwave/translations/el.json b/homeassistant/components/zwave/translations/el.json index 1663d4975a9f69..028a6302d0b827 100644 --- a/homeassistant/components/zwave/translations/el.json +++ b/homeassistant/components/zwave/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "option_error": "\u0397 \u03b5\u03c0\u03b9\u03ba\u03cd\u03c1\u03c9\u03c3\u03b7 Z-Wave \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5. \u0395\u03af\u03bd\u03b1\u03b9 \u03c3\u03c9\u03c3\u03c4\u03ae \u03b7 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf \u03c3\u03c4\u03b9\u03ba\u03ac\u03ba\u03b9 USB;" + }, "step": { "user": { "data": { From 5e577058bb04b51eca6721fcbc6d16ff7e83567a Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Fri, 4 Feb 2022 02:19:36 +0200 Subject: [PATCH 0253/1098] Fix Shelly Plus i4 KeyError (#65604) --- homeassistant/components/shelly/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index a01b5de133a4bb..7a41c914e8a0b3 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -264,7 +264,8 @@ def get_model_name(info: dict[str, Any]) -> str: def get_rpc_channel_name(device: RpcDevice, key: str) -> str: """Get name based on device and channel name.""" - key = key.replace("input", "switch") + if device.config.get("switch:0"): + key = key.replace("input", "switch") device_name = get_rpc_device_name(device) entity_name: str | None = device.config[key].get("name", device_name) From c477378835d13a0b39d813868e9c6b38cc973b87 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 4 Feb 2022 02:50:47 +0100 Subject: [PATCH 0254/1098] Raise when zwave_js device automation fails validation (#65610) --- .../components/zwave_js/device_condition.py | 11 ++++++++++- .../components/zwave_js/device_trigger.py | 11 ++++++++++- homeassistant/components/zwave_js/helpers.py | 3 ++- .../zwave_js/test_device_condition.py | 17 +++++++++++++++++ .../components/zwave_js/test_device_trigger.py | 16 ++++++++++++++++ 5 files changed, 55 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_js/device_condition.py b/homeassistant/components/zwave_js/device_condition.py index 9840d89dc9d3c1..fcd769dc8a4a2d 100644 --- a/homeassistant/components/zwave_js/device_condition.py +++ b/homeassistant/components/zwave_js/device_condition.py @@ -99,7 +99,16 @@ async def async_validate_condition_config( # We return early if the config entry for this device is not ready because we can't # validate the value without knowing the state of the device - if async_is_device_config_entry_not_loaded(hass, config[CONF_DEVICE_ID]): + try: + device_config_entry_not_loaded = async_is_device_config_entry_not_loaded( + hass, config[CONF_DEVICE_ID] + ) + except ValueError as err: + raise InvalidDeviceAutomationConfig( + f"Device {config[CONF_DEVICE_ID]} not found" + ) from err + + if device_config_entry_not_loaded: return config if config[CONF_TYPE] == VALUE_TYPE: diff --git a/homeassistant/components/zwave_js/device_trigger.py b/homeassistant/components/zwave_js/device_trigger.py index 481fc429cb0abc..888efbf2bfd197 100644 --- a/homeassistant/components/zwave_js/device_trigger.py +++ b/homeassistant/components/zwave_js/device_trigger.py @@ -217,7 +217,16 @@ async def async_validate_trigger_config( # We return early if the config entry for this device is not ready because we can't # validate the value without knowing the state of the device - if async_is_device_config_entry_not_loaded(hass, config[CONF_DEVICE_ID]): + try: + device_config_entry_not_loaded = async_is_device_config_entry_not_loaded( + hass, config[CONF_DEVICE_ID] + ) + except ValueError as err: + raise InvalidDeviceAutomationConfig( + f"Device {config[CONF_DEVICE_ID]} not found" + ) from err + + if device_config_entry_not_loaded: return config trigger_type = config[CONF_TYPE] diff --git a/homeassistant/components/zwave_js/helpers.py b/homeassistant/components/zwave_js/helpers.py index 3f57f4bbe6f980..de7ed5da502135 100644 --- a/homeassistant/components/zwave_js/helpers.py +++ b/homeassistant/components/zwave_js/helpers.py @@ -298,7 +298,8 @@ def async_is_device_config_entry_not_loaded( """Return whether device's config entries are not loaded.""" dev_reg = dr.async_get(hass) device = dev_reg.async_get(device_id) - assert device + if device is None: + raise ValueError(f"Device {device_id} not found") return any( (entry := hass.config_entries.async_get_entry(entry_id)) and entry.state != ConfigEntryState.LOADED diff --git a/tests/components/zwave_js/test_device_condition.py b/tests/components/zwave_js/test_device_condition.py index 3919edbd3401e1..71a6865287c55c 100644 --- a/tests/components/zwave_js/test_device_condition.py +++ b/tests/components/zwave_js/test_device_condition.py @@ -596,6 +596,23 @@ async def test_failure_scenarios(hass, client, hank_binary_switch, integration): == INVALID_CONFIG ) + # Test invalid device ID fails validation + with pytest.raises(InvalidDeviceAutomationConfig): + await device_condition.async_validate_condition_config( + hass, + { + "condition": "device", + "domain": DOMAIN, + "type": "value", + "device_id": "invalid_device_id", + "command_class": CommandClass.DOOR_LOCK.value, + "property": 9999, + "property_key": 9999, + "endpoint": 9999, + "value": 9999, + }, + ) + async def test_get_value_from_config_failure( hass, client, hank_binary_switch, integration diff --git a/tests/components/zwave_js/test_device_trigger.py b/tests/components/zwave_js/test_device_trigger.py index 19c86af22edcdc..bf3738a7fb3fd8 100644 --- a/tests/components/zwave_js/test_device_trigger.py +++ b/tests/components/zwave_js/test_device_trigger.py @@ -1370,3 +1370,19 @@ async def test_failure_scenarios(hass, client, hank_binary_switch, integration): await device_trigger.async_validate_trigger_config(hass, INVALID_CONFIG) == INVALID_CONFIG ) + + # Test invalid device ID fails validation + with pytest.raises(InvalidDeviceAutomationConfig): + await device_trigger.async_validate_trigger_config( + hass, + { + "platform": "device", + "domain": DOMAIN, + "device_id": "invalid_device_id", + "type": "zwave_js.value_updated.value", + "command_class": CommandClass.DOOR_LOCK.value, + "property": 9999, + "property_key": 9999, + "endpoint": 9999, + }, + ) From d6693cdff9d24b68eb13e8527d695e0ff881c277 Mon Sep 17 00:00:00 2001 From: Timo S Date: Fri, 4 Feb 2022 08:57:14 +0100 Subject: [PATCH 0255/1098] Add fritz set guest wifi password service (#62892) * Add a new service set_guest_wifi_password to the fritz integration. * Remove unnecessary params defaults * Remove default password length * Add service schema, cleanup code * Fix min password length in services.yaml * Move schema to `services.py`, add typing * Add default password length from upstream lib * Remove None typing Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com> Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com> --- homeassistant/components/fritz/common.py | 19 ++++++++++++++ homeassistant/components/fritz/const.py | 1 + homeassistant/components/fritz/services.py | 27 ++++++++++++++++---- homeassistant/components/fritz/services.yaml | 27 ++++++++++++++++++++ 4 files changed, 69 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index 2cd6616f134c2c..667d94695d3a44 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -18,6 +18,7 @@ ) from fritzconnection.lib.fritzhosts import FritzHosts from fritzconnection.lib.fritzstatus import FritzStatus +from fritzconnection.lib.fritzwlan import DEFAULT_PASSWORD_LENGTH, FritzGuestWLAN from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER_DOMAIN from homeassistant.components.device_tracker.const import ( @@ -47,6 +48,7 @@ SERVICE_CLEANUP, SERVICE_REBOOT, SERVICE_RECONNECT, + SERVICE_SET_GUEST_WIFI_PW, MeshRoles, ) @@ -150,6 +152,7 @@ def __init__( self._options: MappingProxyType[str, Any] | None = None self._unique_id: str | None = None self.connection: FritzConnection = None + self.fritz_guest_wifi: FritzGuestWLAN = None self.fritz_hosts: FritzHosts = None self.fritz_status: FritzStatus = None self.hass = hass @@ -193,6 +196,7 @@ def setup(self) -> None: ) self.fritz_hosts = FritzHosts(fc=self.connection) + self.fritz_guest_wifi = FritzGuestWLAN(fc=self.connection) self.fritz_status = FritzStatus(fc=self.connection) info = self.connection.call_action("DeviceInfo:1", "GetInfo") @@ -421,6 +425,14 @@ async def async_trigger_reconnect(self) -> None: """Trigger device reconnect.""" await self.hass.async_add_executor_job(self.connection.reconnect) + async def async_trigger_set_guest_password( + self, password: str | None, length: int + ) -> None: + """Trigger service to set a new guest wifi password.""" + await self.hass.async_add_executor_job( + self.fritz_guest_wifi.set_password, password, length + ) + async def async_trigger_cleanup( self, config_entry: ConfigEntry | None = None ) -> None: @@ -520,6 +532,13 @@ async def service_fritzbox( await self.async_trigger_cleanup(config_entry) return + if service_call.service == SERVICE_SET_GUEST_WIFI_PW: + await self.async_trigger_set_guest_password( + service_call.data.get("password"), + service_call.data.get("length", DEFAULT_PASSWORD_LENGTH), + ) + return + except (FritzServiceError, FritzActionError) as ex: raise HomeAssistantError("Service or parameter unknown") from ex except FritzConnectionException as ex: diff --git a/homeassistant/components/fritz/const.py b/homeassistant/components/fritz/const.py index 59200e07c782b5..0a4e9fd6cd84fb 100644 --- a/homeassistant/components/fritz/const.py +++ b/homeassistant/components/fritz/const.py @@ -49,6 +49,7 @@ class MeshRoles(StrEnum): SERVICE_REBOOT = "reboot" SERVICE_RECONNECT = "reconnect" SERVICE_CLEANUP = "cleanup" +SERVICE_SET_GUEST_WIFI_PW = "set_guest_wifi_password" SWITCH_TYPE_DEFLECTION = "CallDeflection" SWITCH_TYPE_PORTFORWARD = "PortForward" diff --git a/homeassistant/components/fritz/services.py b/homeassistant/components/fritz/services.py index e32f4c7ffd721f..c4e7de8df5e8d4 100644 --- a/homeassistant/components/fritz/services.py +++ b/homeassistant/components/fritz/services.py @@ -1,6 +1,10 @@ """Services for Fritz integration.""" +from __future__ import annotations + import logging +import voluptuous as vol + from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.exceptions import HomeAssistantError @@ -13,18 +17,31 @@ SERVICE_CLEANUP, SERVICE_REBOOT, SERVICE_RECONNECT, + SERVICE_SET_GUEST_WIFI_PW, ) _LOGGER = logging.getLogger(__name__) +SERVICE_SCHEMA_SET_GUEST_WIFI_PW = vol.Schema( + { + vol.Required("device_id"): str, + vol.Optional("password"): vol.Length(min=8, max=63), + vol.Optional("length"): vol.Range(min=8, max=63), + } +) -SERVICE_LIST = [SERVICE_CLEANUP, SERVICE_REBOOT, SERVICE_RECONNECT] +SERVICE_LIST: list[tuple[str, vol.Schema | None]] = [ + (SERVICE_CLEANUP, None), + (SERVICE_REBOOT, None), + (SERVICE_RECONNECT, None), + (SERVICE_SET_GUEST_WIFI_PW, SERVICE_SCHEMA_SET_GUEST_WIFI_PW), +] async def async_setup_services(hass: HomeAssistant) -> None: """Set up services for Fritz integration.""" - for service in SERVICE_LIST: + for service, _ in SERVICE_LIST: if hass.services.has_service(DOMAIN, service): return @@ -51,8 +68,8 @@ async def async_call_fritz_service(service_call: ServiceCall) -> None: service_call.service, ) - for service in SERVICE_LIST: - hass.services.async_register(DOMAIN, service, async_call_fritz_service) + for service, schema in SERVICE_LIST: + hass.services.async_register(DOMAIN, service, async_call_fritz_service, schema) async def _async_get_configured_avm_device( @@ -80,5 +97,5 @@ async def async_unload_services(hass: HomeAssistant) -> None: hass.data[FRITZ_SERVICES] = False - for service in SERVICE_LIST: + for service, _ in SERVICE_LIST: hass.services.async_remove(DOMAIN, service) diff --git a/homeassistant/components/fritz/services.yaml b/homeassistant/components/fritz/services.yaml index 2375aa71f575a1..3c7ed6438417a6 100644 --- a/homeassistant/components/fritz/services.yaml +++ b/homeassistant/components/fritz/services.yaml @@ -35,3 +35,30 @@ cleanup: integration: fritz entity: device_class: connectivity +set_guest_wifi_password: + name: Set guest wifi password + description: Set a new password for the guest wifi. The password must be between 8 and 63 characters long. If no additional parameter is set, the password will be auto-generated with a length of 12 characters. + fields: + device_id: + name: Fritz!Box Device + description: Select the Fritz!Box to check + required: true + selector: + device: + integration: fritz + entity: + device_class: connectivity + password: + name: Password + description: New password for the guest wifi + required: false + selector: + text: + length: + name: Password length + description: Length of the new password. The password will be auto-generated, if no password is set. + required: false + selector: + number: + min: 8 + max: 63 From 7ab4214553b7b5321183505d9cddb89d31f4be2d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 4 Feb 2022 02:12:29 -0600 Subject: [PATCH 0256/1098] Bump flux_led to 0.28.20 (#65621) --- homeassistant/components/flux_led/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/flux_led/manifest.json b/homeassistant/components/flux_led/manifest.json index 7eb75f54a5586a..583fd0a70c6249 100644 --- a/homeassistant/components/flux_led/manifest.json +++ b/homeassistant/components/flux_led/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "dependencies": ["network"], "documentation": "https://www.home-assistant.io/integrations/flux_led", - "requirements": ["flux_led==0.28.17"], + "requirements": ["flux_led==0.28.20"], "quality_scale": "platinum", "codeowners": ["@icemanch", "@bdraco"], "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index 0957c0bf3b088e..7721aa70812a35 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -681,7 +681,7 @@ fjaraskupan==1.0.2 flipr-api==1.4.1 # homeassistant.components.flux_led -flux_led==0.28.17 +flux_led==0.28.20 # homeassistant.components.homekit fnvhash==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f25e984e287a2d..fdb18ea080d428 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -427,7 +427,7 @@ fjaraskupan==1.0.2 flipr-api==1.4.1 # homeassistant.components.flux_led -flux_led==0.28.17 +flux_led==0.28.20 # homeassistant.components.homekit fnvhash==0.1.0 From 777eba3bab9a8dec970f409d44201cf5cc53c139 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 4 Feb 2022 00:18:10 -0800 Subject: [PATCH 0257/1098] Bump homematicip to 1.0.2 (#65620) --- homeassistant/components/homematicip_cloud/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homematicip_cloud/manifest.json b/homeassistant/components/homematicip_cloud/manifest.json index f5fc8bc61cc379..b13c8ca19b21ac 100644 --- a/homeassistant/components/homematicip_cloud/manifest.json +++ b/homeassistant/components/homematicip_cloud/manifest.json @@ -3,7 +3,7 @@ "name": "HomematicIP Cloud", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homematicip_cloud", - "requirements": ["homematicip==1.0.1"], + "requirements": ["homematicip==1.0.2"], "codeowners": [], "quality_scale": "platinum", "iot_class": "cloud_push", diff --git a/requirements_all.txt b/requirements_all.txt index 7721aa70812a35..71b83158f23012 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -851,7 +851,7 @@ homeassistant-pyozw==0.1.10 homeconnect==0.6.3 # homeassistant.components.homematicip_cloud -homematicip==1.0.1 +homematicip==1.0.2 # homeassistant.components.home_plus_control homepluscontrol==0.0.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fdb18ea080d428..c2522431b98b4b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -552,7 +552,7 @@ homeassistant-pyozw==0.1.10 homeconnect==0.6.3 # homeassistant.components.homematicip_cloud -homematicip==1.0.1 +homematicip==1.0.2 # homeassistant.components.home_plus_control homepluscontrol==0.0.5 From bc410288005729c48e4893a34078f68075992a17 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 4 Feb 2022 00:49:47 -0800 Subject: [PATCH 0258/1098] Some tweaks to the demo (#65623) Co-authored-by: Franck Nijhof --- homeassistant/components/demo/alarm_control_panel.py | 2 +- homeassistant/components/demo/media_player.py | 11 ++++++++--- tests/components/google_assistant/__init__.py | 4 ++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/demo/alarm_control_panel.py b/homeassistant/components/demo/alarm_control_panel.py index 110753ac15f3de..b73e5444b22e41 100644 --- a/homeassistant/components/demo/alarm_control_panel.py +++ b/homeassistant/components/demo/alarm_control_panel.py @@ -33,7 +33,7 @@ async def async_setup_platform( [ ManualAlarm( hass, - "Alarm", + "Security", "1234", None, True, diff --git a/homeassistant/components/demo/media_player.py b/homeassistant/components/demo/media_player.py index 317babfe74c7c9..661e218a1adc32 100644 --- a/homeassistant/components/demo/media_player.py +++ b/homeassistant/components/demo/media_player.py @@ -1,7 +1,10 @@ """Demo implementation of the media player.""" from __future__ import annotations -from homeassistant.components.media_player import MediaPlayerEntity +from homeassistant.components.media_player import ( + MediaPlayerDeviceClass, + MediaPlayerEntity, +) from homeassistant.components.media_player.const import ( MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, @@ -68,8 +71,8 @@ async def async_setup_entry( await async_setup_platform(hass, {}, async_add_entities) -SOUND_MODE_LIST = ["Dummy Music", "Dummy Movie"] -DEFAULT_SOUND_MODE = "Dummy Music" +SOUND_MODE_LIST = ["Music", "Movie"] +DEFAULT_SOUND_MODE = "Music" YOUTUBE_PLAYER_SUPPORT = ( SUPPORT_PAUSE @@ -449,6 +452,8 @@ class DemoTVShowPlayer(AbstractDemoPlayer): # We only implement the methods that we support + _attr_device_class = MediaPlayerDeviceClass.TV + def __init__(self): """Initialize the demo device.""" super().__init__("Lounge room") diff --git a/tests/components/google_assistant/__init__.py b/tests/components/google_assistant/__init__.py index 2edd750a6e0b87..423bb1b55d7f47 100644 --- a/tests/components/google_assistant/__init__.py +++ b/tests/components/google_assistant/__init__.py @@ -383,8 +383,8 @@ def should_2fa(self, state): "willReportState": False, }, { - "id": "alarm_control_panel.alarm", - "name": {"name": "Alarm"}, + "id": "alarm_control_panel.security", + "name": {"name": "Security"}, "traits": ["action.devices.traits.ArmDisarm"], "type": "action.devices.types.SECURITYSYSTEM", "willReportState": False, From ff6969a2558b069a3b5d0bd3d8c72d46e93ef747 Mon Sep 17 00:00:00 2001 From: Thomas Schamm Date: Fri, 4 Feb 2022 09:51:34 +0100 Subject: [PATCH 0259/1098] Bumped boschshcpy 0.2.28 to 0.2.29 (#65328) --- homeassistant/components/bosch_shc/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/bosch_shc/manifest.json b/homeassistant/components/bosch_shc/manifest.json index ecc4e13e54e850..f4fd65748f16ec 100644 --- a/homeassistant/components/bosch_shc/manifest.json +++ b/homeassistant/components/bosch_shc/manifest.json @@ -3,7 +3,7 @@ "name": "Bosch SHC", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/bosch_shc", - "requirements": ["boschshcpy==0.2.28"], + "requirements": ["boschshcpy==0.2.29"], "zeroconf": [{ "type": "_http._tcp.local.", "name": "bosch shc*" }], "iot_class": "local_push", "codeowners": ["@tschamm"], diff --git a/requirements_all.txt b/requirements_all.txt index 71b83158f23012..a625d305289f44 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -438,7 +438,7 @@ blockchain==1.4.4 bond-api==0.1.16 # homeassistant.components.bosch_shc -boschshcpy==0.2.28 +boschshcpy==0.2.29 # homeassistant.components.amazon_polly # homeassistant.components.route53 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c2522431b98b4b..168e9d10d51e17 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -291,7 +291,7 @@ blinkpy==0.18.0 bond-api==0.1.16 # homeassistant.components.bosch_shc -boschshcpy==0.2.28 +boschshcpy==0.2.29 # homeassistant.components.braviatv bravia-tv==1.0.11 From 98a7125933b54411d2fe360984dcac1904f4aa2a Mon Sep 17 00:00:00 2001 From: alexanv1 <44785744+alexanv1@users.noreply.github.com> Date: Fri, 4 Feb 2022 01:50:24 -0800 Subject: [PATCH 0260/1098] Fix Z-Wave lights (#65638) * Fix Z-Wave lights * Update tests --- homeassistant/components/zwave/light.py | 2 +- tests/components/zwave/test_light.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/zwave/light.py b/homeassistant/components/zwave/light.py index a029fa35a65018..ea2b34a874fafb 100644 --- a/homeassistant/components/zwave/light.py +++ b/homeassistant/components/zwave/light.py @@ -123,7 +123,7 @@ def __init__(self, values, refresh, delay): self._state = None self._color_mode = None self._supported_color_modes = set() - self._supported_features = None + self._supported_features = 0 self._delay = delay self._refresh_value = refresh self._zw098 = None diff --git a/tests/components/zwave/test_light.py b/tests/components/zwave/test_light.py index 35128ccc69aae5..74c541f4d5a0f3 100644 --- a/tests/components/zwave/test_light.py +++ b/tests/components/zwave/test_light.py @@ -39,7 +39,7 @@ def test_get_device_detects_dimmer(mock_openzwave): device = light.get_device(node=node, values=values, node_config={}) assert isinstance(device, light.ZwaveDimmer) assert device.color_mode == COLOR_MODE_BRIGHTNESS - assert device.supported_features is None + assert device.supported_features == 0 assert device.supported_color_modes == {COLOR_MODE_BRIGHTNESS} @@ -52,7 +52,7 @@ def test_get_device_detects_colorlight(mock_openzwave): device = light.get_device(node=node, values=values, node_config={}) assert isinstance(device, light.ZwaveColorLight) assert device.color_mode == COLOR_MODE_RGB - assert device.supported_features is None + assert device.supported_features == 0 assert device.supported_color_modes == {COLOR_MODE_RGB} @@ -68,7 +68,7 @@ def test_get_device_detects_zw098(mock_openzwave): device = light.get_device(node=node, values=values, node_config={}) assert isinstance(device, light.ZwaveColorLight) assert device.color_mode == COLOR_MODE_RGB - assert device.supported_features is None + assert device.supported_features == 0 assert device.supported_color_modes == {COLOR_MODE_COLOR_TEMP, COLOR_MODE_RGB} @@ -84,7 +84,7 @@ def test_get_device_detects_rgbw_light(mock_openzwave): device.value_added() assert isinstance(device, light.ZwaveColorLight) assert device.color_mode == COLOR_MODE_RGBW - assert device.supported_features is None + assert device.supported_features == 0 assert device.supported_color_modes == {COLOR_MODE_RGBW} From 320df10a26c3c4cd9ffb6011c24e343576c65478 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Fri, 4 Feb 2022 11:40:38 +0100 Subject: [PATCH 0261/1098] Use _attr_last_reset to set last_reset (#65648) --- homeassistant/components/iotawatt/sensor.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/iotawatt/sensor.py b/homeassistant/components/iotawatt/sensor.py index acc4577fa8a172..c3c173f778ec10 100644 --- a/homeassistant/components/iotawatt/sensor.py +++ b/homeassistant/components/iotawatt/sensor.py @@ -8,7 +8,6 @@ from iotawattpy.sensor import Sensor from homeassistant.components.sensor import ( - ATTR_LAST_RESET, SensorDeviceClass, SensorEntity, SensorEntityDescription, @@ -211,6 +210,12 @@ def _handle_coordinator_update(self) -> None: else: self.hass.async_create_task(self.async_remove()) return + + if (begin := self._sensor_data.getBegin()) and ( + last_reset := dt.parse_datetime(begin) + ): + self._attr_last_reset = last_reset + super()._handle_coordinator_update() @property @@ -220,8 +225,6 @@ def extra_state_attributes(self) -> dict[str, str]: attrs = {"type": data.getType()} if attrs["type"] == "Input": attrs["channel"] = data.getChannel() - if (begin := data.getBegin()) and (last_reset := dt.parse_datetime(begin)): - attrs[ATTR_LAST_RESET] = last_reset.isoformat() return attrs From b7007b364a133d2e0fad187559972ee4ebbb4c5c Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 4 Feb 2022 12:34:15 +0100 Subject: [PATCH 0262/1098] Add TRV (`wkf`) support to Tuya (#65649) --- homeassistant/components/tuya/binary_sensor.py | 10 ++++++++++ homeassistant/components/tuya/climate.py | 6 ++++++ homeassistant/components/tuya/const.py | 2 ++ homeassistant/components/tuya/sensor.py | 3 +++ homeassistant/components/tuya/switch.py | 16 ++++++++++++++++ 5 files changed, 37 insertions(+) diff --git a/homeassistant/components/tuya/binary_sensor.py b/homeassistant/components/tuya/binary_sensor.py index 7142949f0c60f0..56bc59e546f10a 100644 --- a/homeassistant/components/tuya/binary_sensor.py +++ b/homeassistant/components/tuya/binary_sensor.py @@ -194,6 +194,16 @@ class TuyaBinarySensorEntityDescription(BinarySensorEntityDescription): ), TAMPER_BINARY_SENSOR, ), + # Thermostatic Radiator Valve + # Not documented + "wkf": ( + TuyaBinarySensorEntityDescription( + key=DPCode.WINDOW_STATE, + name="Window", + device_class=BinarySensorDeviceClass.WINDOW, + on_value="opened", + ), + ), # Temperature and Humidity Sensor # https://developer.tuya.com/en/docs/iot/categorywsdcg?id=Kaiuz3hinij34 "wsdcg": (TAMPER_BINARY_SENSOR,), diff --git a/homeassistant/components/tuya/climate.py b/homeassistant/components/tuya/climate.py index a97a27a7453280..17e6e967a34710 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -79,6 +79,12 @@ class TuyaClimateEntityDescription( key="wk", switch_only_hvac_mode=HVAC_MODE_HEAT_COOL, ), + # Thermostatic Radiator Valve + # Not documented + "wkf": TuyaClimateEntityDescription( + key="wkf", + switch_only_hvac_mode=HVAC_MODE_HEAT, + ), } diff --git a/homeassistant/components/tuya/const.py b/homeassistant/components/tuya/const.py index 7765cd27322b09..e38671ae91271d 100644 --- a/homeassistant/components/tuya/const.py +++ b/homeassistant/components/tuya/const.py @@ -367,6 +367,8 @@ class DPCode(StrEnum): WATER_SET = "water_set" # Water level WATERSENSOR_STATE = "watersensor_state" WET = "wet" # Humidification + WINDOW_CHECK = "window_check" + WINDOW_STATE = "window_state" WINDSPEED = "windspeed" WIRELESS_BATTERYLOCK = "wireless_batterylock" WIRELESS_ELECTRICITY = "wireless_electricity" diff --git a/homeassistant/components/tuya/sensor.py b/homeassistant/components/tuya/sensor.py index 95ed463ee813cb..06c4a783066061 100644 --- a/homeassistant/components/tuya/sensor.py +++ b/homeassistant/components/tuya/sensor.py @@ -447,6 +447,9 @@ class TuyaSensorEntityDescription(SensorEntityDescription): ), *BATTERY_SENSORS, ), + # Thermostatic Radiator Valve + # Not documented + "wkf": BATTERY_SENSORS, # Temperature and Humidity Sensor # https://developer.tuya.com/en/docs/iot/categorywsdcg?id=Kaiuz3hinij34 "wsdcg": ( diff --git a/homeassistant/components/tuya/switch.py b/homeassistant/components/tuya/switch.py index 619e85cbdd4eae..4559bdf3364d69 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -443,6 +443,22 @@ entity_category=EntityCategory.CONFIG, ), ), + # Thermostatic Radiator Valve + # Not documented + "wkf": ( + SwitchEntityDescription( + key=DPCode.CHILD_LOCK, + name="Child Lock", + icon="mdi:account-lock", + entity_category=EntityCategory.CONFIG, + ), + SwitchEntityDescription( + key=DPCode.WINDOW_CHECK, + name="Open Window Detection", + icon="mdi:window-open", + entity_category=EntityCategory.CONFIG, + ), + ), # Ceiling Light # https://developer.tuya.com/en/docs/iot/ceiling-light?id=Kaiuz03xxfc4r "xdd": ( From 96c4e33b2427308307f2983c446617512075c39f Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 4 Feb 2022 12:58:07 +0100 Subject: [PATCH 0263/1098] Parametrize deCONZ binary sensors (#65012) * Improve test coverage prior to improving deCONZ binary sensor platform * Define all relevant binary sensors as DeconzBinarySensorDescription * Fix review comment * Allow providing extra update keys if sensor provides extra attributes * Minor touch up of naming * Remove duplicate assert --- .../components/deconz/binary_sensor.py | 242 +++---- tests/components/deconz/test_binary_sensor.py | 623 +++++++++++++----- 2 files changed, 594 insertions(+), 271 deletions(-) diff --git a/homeassistant/components/deconz/binary_sensor.py b/homeassistant/components/deconz/binary_sensor.py index cda743f0893a30..fd674dc1cba062 100644 --- a/homeassistant/components/deconz/binary_sensor.py +++ b/homeassistant/components/deconz/binary_sensor.py @@ -7,7 +7,6 @@ from pydeconz.sensor import ( Alarm, CarbonMonoxide, - DeconzBinarySensor as PydeconzBinarySensor, DeconzSensor as PydeconzSensor, Fire, GenericFlag, @@ -34,21 +33,21 @@ from .deconz_device import DeconzDevice from .gateway import DeconzGateway, get_gateway_from_config_entry -DECONZ_BINARY_SENSORS = ( - Alarm, - CarbonMonoxide, - Fire, - GenericFlag, - OpenClose, - Presence, - Vibration, - Water, -) - ATTR_ORIENTATION = "orientation" ATTR_TILTANGLE = "tiltangle" ATTR_VIBRATIONSTRENGTH = "vibrationstrength" +PROVIDES_EXTRA_ATTRIBUTES = ( + "alarm", + "carbon_monoxide", + "fire", + "flag", + "open", + "presence", + "vibration", + "water", +) + @dataclass class DeconzBinarySensorDescriptionMixin: @@ -56,7 +55,6 @@ class DeconzBinarySensorDescriptionMixin: suffix: str update_key: str - required_attr: str value_fn: Callable[[PydeconzSensor], bool | None] @@ -69,41 +67,90 @@ class DeconzBinarySensorDescription( ENTITY_DESCRIPTIONS = { - Alarm: BinarySensorEntityDescription( - key="alarm", - device_class=BinarySensorDeviceClass.SAFETY, - ), - CarbonMonoxide: BinarySensorEntityDescription( - key="carbonmonoxide", - device_class=BinarySensorDeviceClass.CO, - ), - Fire: BinarySensorEntityDescription( - key="fire", - device_class=BinarySensorDeviceClass.SMOKE, - ), - OpenClose: BinarySensorEntityDescription( - key="openclose", - device_class=BinarySensorDeviceClass.OPENING, - ), - Presence: BinarySensorEntityDescription( - key="presence", - device_class=BinarySensorDeviceClass.MOTION, - ), - Vibration: BinarySensorEntityDescription( - key="vibration", - device_class=BinarySensorDeviceClass.VIBRATION, - ), - Water: BinarySensorEntityDescription( - key="water", - device_class=BinarySensorDeviceClass.MOISTURE, - ), + Alarm: [ + DeconzBinarySensorDescription( + key="alarm", + value_fn=lambda device: device.alarm, + suffix="", + update_key="alarm", + device_class=BinarySensorDeviceClass.SAFETY, + ) + ], + CarbonMonoxide: [ + DeconzBinarySensorDescription( + key="carbon_monoxide", + value_fn=lambda device: device.carbon_monoxide, + suffix="", + update_key="carbonmonoxide", + device_class=BinarySensorDeviceClass.CO, + ) + ], + Fire: [ + DeconzBinarySensorDescription( + key="fire", + value_fn=lambda device: device.fire, + suffix="", + update_key="fire", + device_class=BinarySensorDeviceClass.SMOKE, + ), + DeconzBinarySensorDescription( + key="in_test_mode", + value_fn=lambda device: device.in_test_mode, + suffix="Test Mode", + update_key="test", + device_class=BinarySensorDeviceClass.SMOKE, + entity_category=EntityCategory.DIAGNOSTIC, + ), + ], + GenericFlag: [ + DeconzBinarySensorDescription( + key="flag", + value_fn=lambda device: device.flag, + suffix="", + update_key="flag", + ) + ], + OpenClose: [ + DeconzBinarySensorDescription( + key="open", + value_fn=lambda device: device.open, + suffix="", + update_key="open", + device_class=BinarySensorDeviceClass.OPENING, + ) + ], + Presence: [ + DeconzBinarySensorDescription( + key="presence", + value_fn=lambda device: device.presence, + suffix="", + update_key="presence", + device_class=BinarySensorDeviceClass.MOTION, + ) + ], + Vibration: [ + DeconzBinarySensorDescription( + key="vibration", + value_fn=lambda device: device.vibration, + suffix="", + update_key="vibration", + device_class=BinarySensorDeviceClass.VIBRATION, + ) + ], + Water: [ + DeconzBinarySensorDescription( + key="water", + value_fn=lambda device: device.water, + suffix="", + update_key="water", + device_class=BinarySensorDeviceClass.MOISTURE, + ) + ], } - BINARY_SENSOR_DESCRIPTIONS = [ DeconzBinarySensorDescription( - key="tamper", - required_attr="tampered", + key="tampered", value_fn=lambda device: device.tampered, suffix="Tampered", update_key="tampered", @@ -112,22 +159,12 @@ class DeconzBinarySensorDescription( ), DeconzBinarySensorDescription( key="low_battery", - required_attr="low_battery", value_fn=lambda device: device.low_battery, suffix="Low Battery", update_key="lowbattery", device_class=BinarySensorDeviceClass.BATTERY, entity_category=EntityCategory.DIAGNOSTIC, ), - DeconzBinarySensorDescription( - key="in_test_mode", - required_attr="in_test_mode", - value_fn=lambda device: device.in_test_mode, - suffix="Test Mode", - update_key="test", - device_class=BinarySensorDeviceClass.SMOKE, - entity_category=EntityCategory.DIAGNOSTIC, - ), ] @@ -146,32 +183,26 @@ def async_add_sensor( | ValuesView[PydeconzSensor] = gateway.api.sensors.values(), ) -> None: """Add binary sensor from deCONZ.""" - entities: list[DeconzBinarySensor | DeconzPropertyBinarySensor] = [] + entities: list[DeconzBinarySensor] = [] for sensor in sensors: if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"): continue - if ( - isinstance(sensor, DECONZ_BINARY_SENSORS) - and sensor.unique_id not in gateway.entities[DOMAIN] + known_entities = set(gateway.entities[DOMAIN]) + for description in ( + ENTITY_DESCRIPTIONS.get(type(sensor), []) + BINARY_SENSOR_DESCRIPTIONS ): - entities.append(DeconzBinarySensor(sensor, gateway)) - - known_sensor_entities = set(gateway.entities[DOMAIN]) - for sensor_description in BINARY_SENSOR_DESCRIPTIONS: if ( - not hasattr(sensor, sensor_description.required_attr) - or sensor_description.value_fn(sensor) is None + not hasattr(sensor, description.key) + or description.value_fn(sensor) is None ): continue - new_sensor = DeconzPropertyBinarySensor( - sensor, gateway, sensor_description - ) - if new_sensor.unique_id not in known_sensor_entities: + new_sensor = DeconzBinarySensor(sensor, gateway, description) + if new_sensor.unique_id not in known_entities: entities.append(new_sensor) if entities: @@ -194,30 +225,50 @@ class DeconzBinarySensor(DeconzDevice, BinarySensorEntity): """Representation of a deCONZ binary sensor.""" TYPE = DOMAIN - _device: PydeconzBinarySensor + _device: PydeconzSensor + entity_description: DeconzBinarySensorDescription - def __init__(self, device: PydeconzBinarySensor, gateway: DeconzGateway) -> None: + def __init__( + self, + device: PydeconzSensor, + gateway: DeconzGateway, + description: DeconzBinarySensorDescription, + ) -> None: """Initialize deCONZ binary sensor.""" + self.entity_description: DeconzBinarySensorDescription = description super().__init__(device, gateway) - if entity_description := ENTITY_DESCRIPTIONS.get(type(device)): - self.entity_description = entity_description + if description.suffix: + self._attr_name = f"{self._device.name} {description.suffix}" + + self._update_keys = {description.update_key, "reachable"} + if self.entity_description.key in PROVIDES_EXTRA_ATTRIBUTES: + self._update_keys.update({"on", "state"}) + + @property + def unique_id(self) -> str: + """Return a unique identifier for this device.""" + if self.entity_description.suffix: + return f"{self.serial}-{self.entity_description.suffix.lower()}" + return super().unique_id @callback def async_update_callback(self) -> None: """Update the sensor's state.""" - keys = {"on", "reachable", "state"} - if self._device.changed_keys.intersection(keys): + if self._device.changed_keys.intersection(self._update_keys): super().async_update_callback() @property - def is_on(self) -> bool: - """Return true if sensor is on.""" - return self._device.state # type: ignore[no-any-return] + def is_on(self) -> bool | None: + """Return the state of the sensor.""" + return self.entity_description.value_fn(self._device) @property def extra_state_attributes(self) -> dict[str, bool | float | int | list | None]: """Return the state attributes of the sensor.""" + if self.entity_description.key not in PROVIDES_EXTRA_ATTRIBUTES: + return + attr: dict[str, bool | float | int | list | None] = {} if self._device.on is not None: @@ -237,40 +288,3 @@ def extra_state_attributes(self) -> dict[str, bool | float | int | list | None]: attr[ATTR_VIBRATIONSTRENGTH] = self._device.vibration_strength return attr - - -class DeconzPropertyBinarySensor(DeconzDevice, BinarySensorEntity): - """Representation of a deCONZ Property sensor.""" - - TYPE = DOMAIN - _device: PydeconzSensor - entity_description: DeconzBinarySensorDescription - - def __init__( - self, - device: PydeconzSensor, - gateway: DeconzGateway, - description: DeconzBinarySensorDescription, - ) -> None: - """Initialize deCONZ binary sensor.""" - self.entity_description = description - super().__init__(device, gateway) - - self._attr_name = f"{self._device.name} {description.suffix}" - self._update_keys = {description.update_key, "reachable"} - - @property - def unique_id(self) -> str: - """Return a unique identifier for this device.""" - return f"{self.serial}-{self.entity_description.suffix.lower()}" - - @callback - def async_update_callback(self) -> None: - """Update the sensor's state.""" - if self._device.changed_keys.intersection(self._update_keys): - super().async_update_callback() - - @property - def is_on(self) -> bool | None: - """Return the state of the sensor.""" - return self.entity_description.value_fn(self._device) diff --git a/tests/components/deconz/test_binary_sensor.py b/tests/components/deconz/test_binary_sensor.py index 11f9483e277234..0bd308caeed787 100644 --- a/tests/components/deconz/test_binary_sensor.py +++ b/tests/components/deconz/test_binary_sensor.py @@ -2,6 +2,8 @@ from unittest.mock import patch +import pytest + from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.components.deconz.const import ( CONF_ALLOW_CLIP_SENSOR, @@ -10,14 +12,13 @@ DOMAIN as DECONZ_DOMAIN, ) from homeassistant.components.deconz.services import SERVICE_DEVICE_REFRESH -from homeassistant.components.sensor import SensorDeviceClass from homeassistant.const import ( ATTR_DEVICE_CLASS, STATE_OFF, STATE_ON, STATE_UNAVAILABLE, ) -from homeassistant.helpers import entity_registry as er +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_registry import async_entries_for_config_entry @@ -34,204 +35,512 @@ async def test_no_binary_sensors(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_binary_sensors(hass, aioclient_mock, mock_deconz_websocket): - """Test successful creation of binary sensor entities.""" - data = { - "sensors": { - "1": { - "name": "Presence sensor", - "type": "ZHAPresence", - "state": {"dark": False, "presence": False}, - "config": {"on": True, "reachable": True, "temperature": 10}, - "uniqueid": "00:00:00:00:00:00:00:00-00", +TEST_DATA = [ + ( # Alarm binary sensor + { + "config": { + "battery": 100, + "on": True, + "reachable": True, + "temperature": 2600, }, - "2": { - "name": "Temperature sensor", - "type": "ZHATemperature", - "state": {"temperature": False}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:01-00", + "ep": 1, + "etag": "18c0f3c2100904e31a7f938db2ba9ba9", + "manufacturername": "dresden elektronik", + "modelid": "lumi.sensor_motion.aq2", + "name": "Alarm 10", + "state": { + "alarm": False, + "lastupdated": "none", + "lowbattery": None, + "tampered": None, }, - "3": { - "name": "CLIP presence sensor", - "type": "CLIPPresence", - "state": {"presence": False}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:02-00", + "swversion": "20170627", + "type": "ZHAAlarm", + "uniqueid": "00:15:8d:00:02:b5:d1:80-01-0500", + }, + { + "entity_count": 3, + "device_count": 3, + "entity_id": "binary_sensor.alarm_10", + "unique_id": "00:15:8d:00:02:b5:d1:80-01-0500", + "state": STATE_OFF, + "entity_category": None, + "device_class": BinarySensorDeviceClass.SAFETY, + "attributes": { + "on": True, + "temperature": 26.0, + "device_class": "safety", + "friendly_name": "Alarm 10", }, - "4": { - "name": "Vibration sensor", - "type": "ZHAVibration", - "state": { - "orientation": [1, 2, 3], - "tiltangle": 36, - "vibration": True, - "vibrationstrength": 10, - }, - "config": {"on": True, "reachable": True, "temperature": 10}, - "uniqueid": "00:00:00:00:00:00:00:03-00", + "websocket_event": {"alarm": True}, + "next_state": STATE_ON, + }, + ), + ( # Carbon monoxide binary sensor + { + "config": { + "battery": 100, + "on": True, + "pending": [], + "reachable": True, }, - } - } - with patch.dict(DECONZ_WEB_REQUEST, data): - config_entry = await setup_deconz_integration(hass, aioclient_mock) - - assert len(hass.states.async_all()) == 5 - presence_sensor = hass.states.get("binary_sensor.presence_sensor") - assert presence_sensor.state == STATE_OFF - assert ( - presence_sensor.attributes[ATTR_DEVICE_CLASS] == BinarySensorDeviceClass.MOTION - ) - presence_temp = hass.states.get("sensor.presence_sensor_temperature") - assert presence_temp.state == "0.1" - assert presence_temp.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.TEMPERATURE - assert hass.states.get("binary_sensor.temperature_sensor") is None - assert hass.states.get("binary_sensor.clip_presence_sensor") is None - vibration_sensor = hass.states.get("binary_sensor.vibration_sensor") - assert vibration_sensor.state == STATE_ON - assert ( - vibration_sensor.attributes[ATTR_DEVICE_CLASS] - == BinarySensorDeviceClass.VIBRATION - ) - vibration_temp = hass.states.get("sensor.vibration_sensor_temperature") - assert vibration_temp.state == "0.1" - assert vibration_temp.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.TEMPERATURE + "ep": 1, + "etag": "b7599df551944df97b2aa87d160b9c45", + "manufacturername": "Heiman", + "modelid": "CO_V16", + "name": "Cave CO", + "state": { + "carbonmonoxide": False, + "lastupdated": "none", + "lowbattery": False, + "tampered": False, + }, + "swversion": "20150330", + "type": "ZHACarbonMonoxide", + "uniqueid": "00:15:8d:00:02:a5:21:24-01-0101", + }, + { + "entity_count": 4, + "device_count": 3, + "entity_id": "binary_sensor.cave_co", + "unique_id": "00:15:8d:00:02:a5:21:24-01-0101", + "state": STATE_OFF, + "entity_category": None, + "device_class": BinarySensorDeviceClass.CO, + "attributes": { + "on": True, + "device_class": "carbon_monoxide", + "friendly_name": "Cave CO", + }, + "websocket_event": {"carbonmonoxide": True}, + "next_state": STATE_ON, + }, + ), + ( # Fire binary sensor + { + "config": { + "on": True, + "reachable": True, + }, + "ep": 1, + "etag": "2b585d2c016bfd665ba27a8fdad28670", + "manufacturername": "LUMI", + "modelid": "lumi.sensor_smoke", + "name": "sensor_kitchen_smoke", + "state": { + "fire": False, + "lastupdated": "2018-02-20T11:25:02", + }, + "type": "ZHAFire", + "uniqueid": "00:15:8d:00:01:d9:3e:7c-01-0500", + }, + { + "entity_count": 2, + "device_count": 3, + "entity_id": "binary_sensor.sensor_kitchen_smoke", + "unique_id": "00:15:8d:00:01:d9:3e:7c-01-0500", + "state": STATE_OFF, + "entity_category": None, + "device_class": BinarySensorDeviceClass.SMOKE, + "attributes": { + "on": True, + "device_class": "smoke", + "friendly_name": "sensor_kitchen_smoke", + }, + "websocket_event": {"fire": True}, + "next_state": STATE_ON, + }, + ), + ( # Fire test mode binary sensor + { + "config": { + "on": True, + "reachable": True, + }, + "ep": 1, + "etag": "2b585d2c016bfd665ba27a8fdad28670", + "manufacturername": "LUMI", + "modelid": "lumi.sensor_smoke", + "name": "sensor_kitchen_smoke", + "state": { + "fire": False, + "test": False, + "lastupdated": "2018-02-20T11:25:02", + }, + "type": "ZHAFire", + "uniqueid": "00:15:8d:00:01:d9:3e:7c-01-0500", + }, + { + "entity_count": 2, + "device_count": 3, + "entity_id": "binary_sensor.sensor_kitchen_smoke_test_mode", + "unique_id": "00:15:8d:00:01:d9:3e:7c-test mode", + "state": STATE_OFF, + "entity_category": EntityCategory.DIAGNOSTIC, + "device_class": BinarySensorDeviceClass.SMOKE, + "attributes": { + "device_class": "smoke", + "friendly_name": "sensor_kitchen_smoke Test Mode", + }, + "websocket_event": {"test": True}, + "next_state": STATE_ON, + }, + ), + ( # Generic flag binary sensor + { + "config": { + "on": True, + "reachable": True, + }, + "modelid": "Switch", + "name": "Kitchen Switch", + "state": { + "flag": True, + "lastupdated": "2018-07-01T10:40:35", + }, + "swversion": "1.0.0", + "type": "CLIPGenericFlag", + "uniqueid": "kitchen-switch", + }, + { + "entity_count": 1, + "device_count": 2, + "entity_id": "binary_sensor.kitchen_switch", + "unique_id": "kitchen-switch", + "state": STATE_ON, + "entity_category": None, + "device_class": None, + "attributes": { + "on": True, + "friendly_name": "Kitchen Switch", + }, + "websocket_event": {"flag": False}, + "next_state": STATE_OFF, + }, + ), + ( # Open/Close binary sensor + { + "config": { + "battery": 95, + "on": True, + "reachable": True, + "temperature": 3300, + }, + "ep": 1, + "etag": "66cc641d0368110da6882b50090174ac", + "manufacturername": "LUMI", + "modelid": "lumi.sensor_magnet.aq2", + "name": "Back Door", + "state": { + "lastupdated": "2019-05-05T14:54:32", + "open": False, + }, + "swversion": "20161128", + "type": "ZHAOpenClose", + "uniqueid": "00:15:8d:00:02:2b:96:b4-01-0006", + }, + { + "entity_count": 3, + "device_count": 3, + "entity_id": "binary_sensor.back_door", + "unique_id": "00:15:8d:00:02:2b:96:b4-01-0006", + "state": STATE_OFF, + "entity_category": None, + "device_class": BinarySensorDeviceClass.OPENING, + "attributes": { + "on": True, + "temperature": 33.0, + "device_class": "opening", + "friendly_name": "Back Door", + }, + "websocket_event": {"open": True}, + "next_state": STATE_ON, + }, + ), + ( # Presence binary sensor + { + "config": { + "alert": "none", + "battery": 100, + "delay": 0, + "ledindication": False, + "on": True, + "pending": [], + "reachable": True, + "sensitivity": 1, + "sensitivitymax": 2, + "usertest": False, + }, + "ep": 2, + "etag": "5cfb81765e86aa53ace427cfd52c6d52", + "manufacturername": "Philips", + "modelid": "SML001", + "name": "Motion sensor 4", + "state": { + "dark": False, + "lastupdated": "2019-05-05T14:37:06", + "presence": False, + }, + "swversion": "6.1.0.18912", + "type": "ZHAPresence", + "uniqueid": "00:17:88:01:03:28:8c:9b-02-0406", + }, + { + "entity_count": 3, + "device_count": 3, + "entity_id": "binary_sensor.motion_sensor_4", + "unique_id": "00:17:88:01:03:28:8c:9b-02-0406", + "state": STATE_OFF, + "entity_category": None, + "device_class": BinarySensorDeviceClass.MOTION, + "attributes": { + "on": True, + "dark": False, + "device_class": "motion", + "friendly_name": "Motion sensor 4", + }, + "websocket_event": {"presence": True}, + "next_state": STATE_ON, + }, + ), + ( # Water leak binary sensor + { + "config": { + "battery": 100, + "on": True, + "reachable": True, + "temperature": 2500, + }, + "ep": 1, + "etag": "fae893708dfe9b358df59107d944fa1c", + "manufacturername": "LUMI", + "modelid": "lumi.sensor_wleak.aq1", + "name": "water2", + "state": { + "lastupdated": "2019-01-29T07:13:20", + "lowbattery": False, + "tampered": False, + "water": False, + }, + "swversion": "20170721", + "type": "ZHAWater", + "uniqueid": "00:15:8d:00:02:2f:07:db-01-0500", + }, + { + "entity_count": 5, + "device_count": 3, + "entity_id": "binary_sensor.water2", + "unique_id": "00:15:8d:00:02:2f:07:db-01-0500", + "state": STATE_OFF, + "entity_category": None, + "device_class": BinarySensorDeviceClass.MOISTURE, + "attributes": { + "on": True, + "temperature": 25.0, + "device_class": "moisture", + "friendly_name": "water2", + }, + "websocket_event": {"water": True}, + "next_state": STATE_ON, + }, + ), + ( # Vibration binary sensor + { + "config": { + "battery": 91, + "on": True, + "pending": [], + "reachable": True, + "sensitivity": 21, + "sensitivitymax": 21, + "temperature": 3200, + }, + "ep": 1, + "etag": "b7599df551944df97b2aa87d160b9c45", + "manufacturername": "LUMI", + "modelid": "lumi.vibration.aq1", + "name": "Vibration 1", + "state": { + "lastupdated": "2019-03-09T15:53:07", + "orientation": [10, 1059, 0], + "tiltangle": 83, + "vibration": True, + "vibrationstrength": 114, + }, + "swversion": "20180130", + "type": "ZHAVibration", + "uniqueid": "00:15:8d:00:02:a5:21:24-01-0101", + }, + { + "entity_count": 3, + "device_count": 3, + "entity_id": "binary_sensor.vibration_1", + "unique_id": "00:15:8d:00:02:a5:21:24-01-0101", + "state": STATE_ON, + "entity_category": None, + "device_class": BinarySensorDeviceClass.VIBRATION, + "attributes": { + "on": True, + "temperature": 32.0, + "orientation": [10, 1059, 0], + "tiltangle": 83, + "vibrationstrength": 114, + "device_class": "vibration", + "friendly_name": "Vibration 1", + }, + "websocket_event": {"vibration": False}, + "next_state": STATE_OFF, + }, + ), + ( # Tampering binary sensor + { + "name": "Presence sensor", + "type": "ZHAPresence", + "state": { + "dark": False, + "lowbattery": False, + "presence": False, + "tampered": False, + }, + "config": { + "on": True, + "reachable": True, + "temperature": 10, + }, + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, + { + "entity_count": 4, + "device_count": 3, + "entity_id": "binary_sensor.presence_sensor_tampered", + "unique_id": "00:00:00:00:00:00:00:00-tampered", + "state": STATE_OFF, + "entity_category": EntityCategory.DIAGNOSTIC, + "device_class": BinarySensorDeviceClass.TAMPER, + "attributes": { + "device_class": "tamper", + "friendly_name": "Presence sensor Tampered", + }, + "websocket_event": {"tampered": True}, + "next_state": STATE_ON, + }, + ), + ( # Low battery binary sensor + { + "name": "Presence sensor", + "type": "ZHAPresence", + "state": { + "dark": False, + "lowbattery": False, + "presence": False, + "tampered": False, + }, + "config": { + "on": True, + "reachable": True, + "temperature": 10, + }, + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, + { + "entity_count": 4, + "device_count": 3, + "entity_id": "binary_sensor.presence_sensor_low_battery", + "unique_id": "00:00:00:00:00:00:00:00-low battery", + "state": STATE_OFF, + "entity_category": EntityCategory.DIAGNOSTIC, + "device_class": BinarySensorDeviceClass.BATTERY, + "attributes": { + "device_class": "battery", + "friendly_name": "Presence sensor Low Battery", + }, + "websocket_event": {"lowbattery": True}, + "next_state": STATE_ON, + }, + ), +] - event_changed_sensor = { - "t": "event", - "e": "changed", - "r": "sensors", - "id": "1", - "state": {"presence": True}, - } - await mock_deconz_websocket(data=event_changed_sensor) - await hass.async_block_till_done() - assert hass.states.get("binary_sensor.presence_sensor").state == STATE_ON +@pytest.mark.parametrize("sensor_data, expected", TEST_DATA) +async def test_binary_sensors( + hass, aioclient_mock, mock_deconz_websocket, sensor_data, expected +): + """Test successful creation of binary sensor entities.""" + ent_reg = er.async_get(hass) + dev_reg = dr.async_get(hass) - await hass.config_entries.async_unload(config_entry.entry_id) + with patch.dict(DECONZ_WEB_REQUEST, {"sensors": {"1": sensor_data}}): + config_entry = await setup_deconz_integration( + hass, aioclient_mock, options={CONF_ALLOW_CLIP_SENSOR: True} + ) - assert hass.states.get("binary_sensor.presence_sensor").state == STATE_UNAVAILABLE + assert len(hass.states.async_all()) == expected["entity_count"] - await hass.config_entries.async_remove(config_entry.entry_id) - await hass.async_block_till_done() + # Verify state data - assert len(hass.states.async_all()) == 0 + sensor = hass.states.get(expected["entity_id"]) + assert sensor.state == expected["state"] + assert sensor.attributes.get(ATTR_DEVICE_CLASS) == expected["device_class"] + assert sensor.attributes == expected["attributes"] + # Verify entity registry data -async def test_tampering_sensor(hass, aioclient_mock, mock_deconz_websocket): - """Verify tampering sensor works.""" - data = { - "sensors": { - "1": { - "name": "Presence sensor", - "type": "ZHAPresence", - "state": { - "dark": False, - "lowbattery": False, - "presence": False, - "tampered": False, - }, - "config": {"on": True, "reachable": True, "temperature": 10}, - "uniqueid": "00:00:00:00:00:00:00:00-00", - }, - } - } - with patch.dict(DECONZ_WEB_REQUEST, data): - config_entry = await setup_deconz_integration(hass, aioclient_mock) + ent_reg_entry = ent_reg.async_get(expected["entity_id"]) + assert ent_reg_entry.entity_category is expected["entity_category"] + assert ent_reg_entry.unique_id == expected["unique_id"] - ent_reg = er.async_get(hass) + # Verify device registry data - assert len(hass.states.async_all()) == 4 - hass.states.get("binary_sensor.presence_sensor_low_battery").state == STATE_OFF - assert ( - ent_reg.async_get("binary_sensor.presence_sensor_low_battery").entity_category - is EntityCategory.DIAGNOSTIC - ) - presence_tamper = hass.states.get("binary_sensor.presence_sensor_tampered") - assert presence_tamper.state == STATE_OFF assert ( - presence_tamper.attributes[ATTR_DEVICE_CLASS] == BinarySensorDeviceClass.TAMPER - ) - assert ( - ent_reg.async_get("binary_sensor.presence_sensor_tampered").entity_category - is EntityCategory.DIAGNOSTIC + len(dr.async_entries_for_config_entry(dev_reg, config_entry.entry_id)) + == expected["device_count"] ) + # Change state + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "1", - "state": {"tampered": True}, + "state": expected["websocket_event"], } await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() + assert hass.states.get(expected["entity_id"]).state == expected["next_state"] - assert hass.states.get("binary_sensor.presence_sensor_tampered").state == STATE_ON + # Unload entry await hass.config_entries.async_unload(config_entry.entry_id) + assert hass.states.get(expected["entity_id"]).state == STATE_UNAVAILABLE - assert ( - hass.states.get("binary_sensor.presence_sensor_tampered").state - == STATE_UNAVAILABLE - ) + # Remove entry await hass.config_entries.async_remove(config_entry.entry_id) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 0 -async def test_fire_sensor(hass, aioclient_mock, mock_deconz_websocket): - """Verify smoke alarm sensor works.""" +async def test_not_allow_clip_sensor(hass, aioclient_mock): + """Test that CLIP sensors are not allowed.""" data = { "sensors": { "1": { - "name": "Fire alarm", - "type": "ZHAFire", - "state": {"fire": False, "test": False}, - "config": {"on": True, "reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:00-00", + "name": "CLIP presence sensor", + "type": "CLIPPresence", + "state": {"presence": False}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:02-00", }, } } - with patch.dict(DECONZ_WEB_REQUEST, data): - config_entry = await setup_deconz_integration(hass, aioclient_mock) - ent_reg = er.async_get(hass) - - assert len(hass.states.async_all()) == 2 - assert hass.states.get("binary_sensor.fire_alarm").state == STATE_OFF - assert ent_reg.async_get("binary_sensor.fire_alarm").entity_category is None - - assert hass.states.get("binary_sensor.fire_alarm_test_mode").state == STATE_OFF - assert ( - ent_reg.async_get("binary_sensor.fire_alarm_test_mode").entity_category - is EntityCategory.DIAGNOSTIC - ) - - event_changed_sensor = { - "t": "event", - "e": "changed", - "r": "sensors", - "id": "1", - "state": {"fire": True, "test": True}, - } - await mock_deconz_websocket(data=event_changed_sensor) - await hass.async_block_till_done() - - assert hass.states.get("binary_sensor.fire_alarm").state == STATE_ON - assert hass.states.get("binary_sensor.fire_alarm_test_mode").state == STATE_ON - - await hass.config_entries.async_unload(config_entry.entry_id) - assert hass.states.get("binary_sensor.fire_alarm").state == STATE_UNAVAILABLE - assert ( - hass.states.get("binary_sensor.fire_alarm_test_mode").state == STATE_UNAVAILABLE - ) + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration( + hass, aioclient_mock, options={CONF_ALLOW_CLIP_SENSOR: False} + ) - await hass.config_entries.async_remove(config_entry.entry_id) - await hass.async_block_till_done() assert len(hass.states.async_all()) == 0 From 2e594b5025dd42b0321c41339d4353ae70ebb5a2 Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Fri, 4 Feb 2022 13:51:04 +0100 Subject: [PATCH 0264/1098] Bump velbusaio to 2022.2.2 (#65657) --- homeassistant/components/velbus/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index e935cf004be7a5..71a5b89d534a3e 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -2,7 +2,7 @@ "domain": "velbus", "name": "Velbus", "documentation": "https://www.home-assistant.io/integrations/velbus", - "requirements": ["velbus-aio==2022.2.1"], + "requirements": ["velbus-aio==2022.2.2"], "config_flow": true, "codeowners": ["@Cereal2nd", "@brefra"], "dependencies": ["usb"], diff --git a/requirements_all.txt b/requirements_all.txt index a625d305289f44..4f0e477938be88 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2423,7 +2423,7 @@ vallox-websocket-api==2.9.0 vehicle==0.3.1 # homeassistant.components.velbus -velbus-aio==2022.2.1 +velbus-aio==2022.2.2 # homeassistant.components.venstar venstarcolortouch==0.15 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 168e9d10d51e17..be7d3602893c7d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1484,7 +1484,7 @@ vallox-websocket-api==2.9.0 vehicle==0.3.1 # homeassistant.components.velbus -velbus-aio==2022.2.1 +velbus-aio==2022.2.2 # homeassistant.components.venstar venstarcolortouch==0.15 From ac7662c82d8ec9365eb0d62a7aba3218713433a2 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 4 Feb 2022 14:49:45 +0100 Subject: [PATCH 0265/1098] Remove limit of amount of duplicated statistics (#65641) --- .../components/recorder/statistics.py | 11 -- tests/components/recorder/test_statistics.py | 142 ------------------ 2 files changed, 153 deletions(-) diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index 347722be0a5d5e..0bf10ca71c6882 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -119,8 +119,6 @@ StatisticsMeta.statistic_id, ] -MAX_DUPLICATES = 1000000 - STATISTICS_BAKERY = "recorder_statistics_bakery" STATISTICS_META_BAKERY = "recorder_statistics_meta_bakery" STATISTICS_SHORT_TERM_BAKERY = "recorder_statistics_short_term_bakery" @@ -351,8 +349,6 @@ def _delete_duplicates_from_table( .delete(synchronize_session=False) ) total_deleted_rows += deleted_rows - if total_deleted_rows >= MAX_DUPLICATES: - break return (total_deleted_rows, all_non_identical_duplicates) @@ -389,13 +385,6 @@ def delete_duplicates(instance: Recorder, session: scoped_session) -> None: backup_path, ) - if deleted_statistics_rows >= MAX_DUPLICATES: - _LOGGER.warning( - "Found more than %s duplicated statistic rows, please report at " - 'https://github.com/home-assistant/core/issues?q=is%%3Aissue+label%%3A"integration%%3A+recorder"+', - MAX_DUPLICATES - 1, - ) - deleted_short_term_statistics_rows, _ = _delete_duplicates_from_table( session, StatisticsShortTerm ) diff --git a/tests/components/recorder/test_statistics.py b/tests/components/recorder/test_statistics.py index 296409d984fbf1..25590c712d9ed5 100644 --- a/tests/components/recorder/test_statistics.py +++ b/tests/components/recorder/test_statistics.py @@ -852,7 +852,6 @@ def test_delete_duplicates(caplog, tmpdir): assert "Deleted 2 duplicated statistics rows" in caplog.text assert "Found non identical" not in caplog.text - assert "Found more than" not in caplog.text assert "Found duplicated" not in caplog.text @@ -989,7 +988,6 @@ def test_delete_duplicates_non_identical(caplog, tmpdir): assert "Deleted 2 duplicated statistics rows" in caplog.text assert "Deleted 1 non identical" in caplog.text - assert "Found more than" not in caplog.text assert "Found duplicated" not in caplog.text isotime = dt_util.utcnow().isoformat() @@ -1028,144 +1026,6 @@ def test_delete_duplicates_non_identical(caplog, tmpdir): ] -@patch.object(statistics, "MAX_DUPLICATES", 2) -def test_delete_duplicates_too_many(caplog, tmpdir): - """Test removal of duplicated statistics.""" - test_db_file = tmpdir.mkdir("sqlite").join("test_run_info.db") - dburl = f"{SQLITE_URL_PREFIX}//{test_db_file}" - - module = "tests.components.recorder.models_schema_23" - importlib.import_module(module) - old_models = sys.modules[module] - - period1 = dt_util.as_utc(dt_util.parse_datetime("2021-09-01 00:00:00")) - period2 = dt_util.as_utc(dt_util.parse_datetime("2021-09-30 23:00:00")) - period3 = dt_util.as_utc(dt_util.parse_datetime("2021-10-01 00:00:00")) - period4 = dt_util.as_utc(dt_util.parse_datetime("2021-10-31 23:00:00")) - - external_energy_statistics_1 = ( - { - "start": period1, - "last_reset": None, - "state": 0, - "sum": 2, - }, - { - "start": period2, - "last_reset": None, - "state": 1, - "sum": 3, - }, - { - "start": period3, - "last_reset": None, - "state": 2, - "sum": 4, - }, - { - "start": period4, - "last_reset": None, - "state": 3, - "sum": 5, - }, - { - "start": period4, - "last_reset": None, - "state": 3, - "sum": 5, - }, - ) - external_energy_metadata_1 = { - "has_mean": False, - "has_sum": True, - "name": "Total imported energy", - "source": "test", - "statistic_id": "test:total_energy_import_tariff_1", - "unit_of_measurement": "kWh", - } - external_energy_statistics_2 = ( - { - "start": period1, - "last_reset": None, - "state": 0, - "sum": 20, - }, - { - "start": period2, - "last_reset": None, - "state": 1, - "sum": 30, - }, - { - "start": period3, - "last_reset": None, - "state": 2, - "sum": 40, - }, - { - "start": period4, - "last_reset": None, - "state": 3, - "sum": 50, - }, - { - "start": period4, - "last_reset": None, - "state": 3, - "sum": 50, - }, - ) - external_energy_metadata_2 = { - "has_mean": False, - "has_sum": True, - "name": "Total imported energy", - "source": "test", - "statistic_id": "test:total_energy_import_tariff_2", - "unit_of_measurement": "kWh", - } - - # Create some duplicated statistics with schema version 23 - with patch.object(recorder, "models", old_models), patch.object( - recorder.migration, "SCHEMA_VERSION", old_models.SCHEMA_VERSION - ), patch( - "homeassistant.components.recorder.create_engine", new=_create_engine_test - ): - hass = get_test_home_assistant() - setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) - wait_recording_done(hass) - wait_recording_done(hass) - - with session_scope(hass=hass) as session: - session.add( - recorder.models.StatisticsMeta.from_meta(external_energy_metadata_1) - ) - session.add( - recorder.models.StatisticsMeta.from_meta(external_energy_metadata_2) - ) - with session_scope(hass=hass) as session: - for stat in external_energy_statistics_1: - session.add(recorder.models.Statistics.from_stats(1, stat)) - for stat in external_energy_statistics_2: - session.add(recorder.models.Statistics.from_stats(2, stat)) - - hass.stop() - - # Test that the duplicates are removed during migration from schema 23 - hass = get_test_home_assistant() - hass.config.config_dir = tmpdir - setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) - hass.start() - wait_recording_done(hass) - wait_recording_done(hass) - hass.stop() - - assert "Deleted 2 duplicated statistics rows" in caplog.text - assert "Found non identical" not in caplog.text - assert "Found more than 1 duplicated statistic rows" in caplog.text - assert "Found duplicated" not in caplog.text - - -@patch.object(statistics, "MAX_DUPLICATES", 2) def test_delete_duplicates_short_term(caplog, tmpdir): """Test removal of duplicated statistics.""" test_db_file = tmpdir.mkdir("sqlite").join("test_run_info.db") @@ -1228,7 +1088,6 @@ def test_delete_duplicates_short_term(caplog, tmpdir): assert "duplicated statistics rows" not in caplog.text assert "Found non identical" not in caplog.text - assert "Found more than" not in caplog.text assert "Deleted duplicated short term statistic" in caplog.text @@ -1240,7 +1099,6 @@ def test_delete_duplicates_no_duplicates(hass_recorder, caplog): delete_duplicates(hass.data[DATA_INSTANCE], session) assert "duplicated statistics rows" not in caplog.text assert "Found non identical" not in caplog.text - assert "Found more than" not in caplog.text assert "Found duplicated" not in caplog.text From a95988c9702fb8567d043b5f5f979396a83124ba Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Fri, 4 Feb 2022 16:30:57 +0100 Subject: [PATCH 0266/1098] Bump renault-api to 0.1.8 (#65670) Co-authored-by: epenet --- homeassistant/components/renault/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/renault/manifest.json b/homeassistant/components/renault/manifest.json index 33b719f88c9cea..131e02ceba0b14 100644 --- a/homeassistant/components/renault/manifest.json +++ b/homeassistant/components/renault/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/renault", "requirements": [ - "renault-api==0.1.7" + "renault-api==0.1.8" ], "codeowners": [ "@epenet" diff --git a/requirements_all.txt b/requirements_all.txt index 4f0e477938be88..1da5ead8fe0b99 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2087,7 +2087,7 @@ raspyrfm-client==1.2.8 regenmaschine==2022.01.0 # homeassistant.components.renault -renault-api==0.1.7 +renault-api==0.1.8 # homeassistant.components.python_script restrictedpython==5.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index be7d3602893c7d..9a296d256ce318 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1285,7 +1285,7 @@ rachiopy==1.0.3 regenmaschine==2022.01.0 # homeassistant.components.renault -renault-api==0.1.7 +renault-api==0.1.8 # homeassistant.components.python_script restrictedpython==5.2 From 8245ff7473a4242faad007a53f610d9dc98b46b0 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 4 Feb 2022 17:35:32 +0100 Subject: [PATCH 0267/1098] Log transmitted MQTT messages (#65550) --- homeassistant/components/mqtt/__init__.py | 2 +- .../components/mqtt/alarm_control_panel.py | 3 +- homeassistant/components/mqtt/button.py | 3 +- homeassistant/components/mqtt/climate.py | 3 +- homeassistant/components/mqtt/cover.py | 21 +-- homeassistant/components/mqtt/debug_info.py | 68 +++++++++- homeassistant/components/mqtt/fan.py | 15 +-- homeassistant/components/mqtt/humidifier.py | 12 +- .../components/mqtt/light/schema_basic.py | 6 +- .../components/mqtt/light/schema_json.py | 6 +- .../components/mqtt/light/schema_template.py | 6 +- homeassistant/components/mqtt/lock.py | 9 +- homeassistant/components/mqtt/mixins.py | 28 +++- homeassistant/components/mqtt/number.py | 3 +- homeassistant/components/mqtt/select.py | 3 +- homeassistant/components/mqtt/siren.py | 3 +- homeassistant/components/mqtt/switch.py | 6 +- .../components/mqtt/vacuum/schema_legacy.py | 27 ++-- .../components/mqtt/vacuum/schema_state.py | 24 ++-- .../mqtt/test_alarm_control_panel.py | 7 +- tests/components/mqtt/test_binary_sensor.py | 2 +- tests/components/mqtt/test_button.py | 14 ++ tests/components/mqtt/test_camera.py | 8 +- tests/components/mqtt/test_climate.py | 10 +- tests/components/mqtt/test_common.py | 124 +++++++++++++----- tests/components/mqtt/test_cover.py | 7 +- tests/components/mqtt/test_device_trigger.py | 4 +- tests/components/mqtt/test_fan.py | 2 +- tests/components/mqtt/test_humidifier.py | 2 +- tests/components/mqtt/test_init.py | 24 ++-- tests/components/mqtt/test_legacy_vacuum.py | 6 +- tests/components/mqtt/test_light.py | 2 +- tests/components/mqtt/test_light_json.py | 8 +- tests/components/mqtt/test_light_template.py | 6 +- tests/components/mqtt/test_lock.py | 7 +- tests/components/mqtt/test_number.py | 9 +- tests/components/mqtt/test_select.py | 9 +- tests/components/mqtt/test_sensor.py | 2 +- tests/components/mqtt/test_siren.py | 7 +- tests/components/mqtt/test_state_vacuum.py | 8 +- tests/components/mqtt/test_switch.py | 2 +- 41 files changed, 341 insertions(+), 177 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 9ff389325cee44..964f19e1d7c2bc 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -1184,7 +1184,7 @@ def _matcher_for_topic(subscription: str) -> Any: async def websocket_mqtt_info(hass, connection, msg): """Get MQTT debug info for device.""" device_id = msg["device_id"] - mqtt_info = await debug_info.info_for_device(hass, device_id) + mqtt_info = debug_info.info_for_device(hass, device_id) connection.send_result(msg["id"], mqtt_info) diff --git a/homeassistant/components/mqtt/alarm_control_panel.py b/homeassistant/components/mqtt/alarm_control_panel.py index eaea908e3589e5..5d7c67d621b327 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -328,8 +328,7 @@ async def _publish(self, code, action): """Publish via mqtt.""" variables = {"action": action, "code": code} payload = self._command_template(None, variables=variables) - await mqtt.async_publish( - self.hass, + await self.async_publish( self._config[CONF_COMMAND_TOPIC], payload, self._config[CONF_QOS], diff --git a/homeassistant/components/mqtt/button.py b/homeassistant/components/mqtt/button.py index 993251606ed255..c4b4b19b120e06 100644 --- a/homeassistant/components/mqtt/button.py +++ b/homeassistant/components/mqtt/button.py @@ -112,8 +112,7 @@ async def async_press(self, **kwargs): This method is a coroutine. """ payload = self._command_template(self._config[CONF_PAYLOAD_PRESS]) - await mqtt.async_publish( - self.hass, + await self.async_publish( self._config[CONF_COMMAND_TOPIC], payload, self._config[CONF_QOS], diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index 02d4f267fe86e6..91d7cdd772d949 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -708,8 +708,7 @@ def fan_modes(self): async def _publish(self, topic, payload): if self._topic[topic] is not None: - await mqtt.async_publish( - self.hass, + await self.async_publish( self._topic[topic], payload, self._config[CONF_QOS], diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index dfb48fb89e232f..4dfa9e20798670 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -534,8 +534,7 @@ async def async_open_cover(self, **kwargs): This method is a coroutine. """ - await mqtt.async_publish( - self.hass, + await self.async_publish( self._config.get(CONF_COMMAND_TOPIC), self._config[CONF_PAYLOAD_OPEN], self._config[CONF_QOS], @@ -556,8 +555,7 @@ async def async_close_cover(self, **kwargs): This method is a coroutine. """ - await mqtt.async_publish( - self.hass, + await self.async_publish( self._config.get(CONF_COMMAND_TOPIC), self._config[CONF_PAYLOAD_CLOSE], self._config[CONF_QOS], @@ -578,8 +576,7 @@ async def async_stop_cover(self, **kwargs): This method is a coroutine. """ - await mqtt.async_publish( - self.hass, + await self.async_publish( self._config.get(CONF_COMMAND_TOPIC), self._config[CONF_PAYLOAD_STOP], self._config[CONF_QOS], @@ -599,8 +596,7 @@ async def async_open_cover_tilt(self, **kwargs): "tilt_max": self._config.get(CONF_TILT_MAX), } tilt_payload = self._set_tilt_template(tilt_open_position, variables=variables) - await mqtt.async_publish( - self.hass, + await self.async_publish( self._config.get(CONF_TILT_COMMAND_TOPIC), tilt_payload, self._config[CONF_QOS], @@ -627,8 +623,7 @@ async def async_close_cover_tilt(self, **kwargs): tilt_payload = self._set_tilt_template( tilt_closed_position, variables=variables ) - await mqtt.async_publish( - self.hass, + await self.async_publish( self._config.get(CONF_TILT_COMMAND_TOPIC), tilt_payload, self._config[CONF_QOS], @@ -657,8 +652,7 @@ async def async_set_cover_tilt_position(self, **kwargs): } tilt = self._set_tilt_template(tilt, variables=variables) - await mqtt.async_publish( - self.hass, + await self.async_publish( self._config.get(CONF_TILT_COMMAND_TOPIC), tilt, self._config[CONF_QOS], @@ -685,8 +679,7 @@ async def async_set_cover_position(self, **kwargs): } position = self._set_position_template(position, variables=variables) - await mqtt.async_publish( - self.hass, + await self.async_publish( self._config.get(CONF_SET_POSITION_TOPIC), position, self._config[CONF_QOS], diff --git a/homeassistant/components/mqtt/debug_info.py b/homeassistant/components/mqtt/debug_info.py index 3e32d301b707fe..e1001ab7b04dfd 100644 --- a/homeassistant/components/mqtt/debug_info.py +++ b/homeassistant/components/mqtt/debug_info.py @@ -3,13 +3,18 @@ from collections import deque from collections.abc import Callable +import datetime as dt from functools import wraps from typing import Any +import attr + from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er +from homeassistant.util import dt as dt_util from .const import ATTR_DISCOVERY_PAYLOAD, ATTR_DISCOVERY_TOPIC -from .models import MessageCallbackType +from .models import MessageCallbackType, PublishPayloadType DATA_MQTT_DEBUG_INFO = "mqtt_debug_info" STORED_MESSAGES = 10 @@ -42,6 +47,42 @@ def wrapper(msg: Any) -> None: return _decorator +@attr.s(slots=True, frozen=True) +class TimestampedPublishMessage: + """MQTT Message.""" + + topic: str = attr.ib() + payload: PublishPayloadType = attr.ib() + qos: int = attr.ib() + retain: bool = attr.ib() + timestamp: dt.datetime = attr.ib(default=None) + + +def log_message( + hass: HomeAssistant, + entity_id: str, + topic: str, + payload: PublishPayloadType, + qos: int, + retain: bool, +) -> None: + """Log an outgoing MQTT message.""" + debug_info = hass.data.setdefault( + DATA_MQTT_DEBUG_INFO, {"entities": {}, "triggers": {}} + ) + entity_info = debug_info["entities"].setdefault( + entity_id, {"subscriptions": {}, "discovery_data": {}, "transmitted": {}} + ) + if topic not in entity_info["transmitted"]: + entity_info["transmitted"][topic] = { + "messages": deque([], STORED_MESSAGES), + } + msg = TimestampedPublishMessage( + topic, payload, qos, retain, timestamp=dt_util.utcnow() + ) + entity_info["transmitted"][topic]["messages"].append(msg) + + def add_subscription(hass, message_callback, subscription): """Prepare debug data for subscription.""" if entity_id := getattr(message_callback, "__entity_id", None): @@ -49,7 +90,7 @@ def add_subscription(hass, message_callback, subscription): DATA_MQTT_DEBUG_INFO, {"entities": {}, "triggers": {}} ) entity_info = debug_info["entities"].setdefault( - entity_id, {"subscriptions": {}, "discovery_data": {}} + entity_id, {"subscriptions": {}, "discovery_data": {}, "transmitted": {}} ) if subscription not in entity_info["subscriptions"]: entity_info["subscriptions"][subscription] = { @@ -80,7 +121,7 @@ def add_entity_discovery_data(hass, discovery_data, entity_id): DATA_MQTT_DEBUG_INFO, {"entities": {}, "triggers": {}} ) entity_info = debug_info["entities"].setdefault( - entity_id, {"subscriptions": {}, "discovery_data": {}} + entity_id, {"subscriptions": {}, "discovery_data": {}, "transmitted": {}} ) entity_info["discovery_data"] = discovery_data @@ -118,10 +159,10 @@ def remove_trigger_discovery_data(hass, discovery_hash): hass.data[DATA_MQTT_DEBUG_INFO]["triggers"][discovery_hash]["discovery_data"] = None -async def info_for_device(hass, device_id): +def info_for_device(hass, device_id): """Get debug info for a device.""" mqtt_info = {"entities": [], "triggers": []} - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entries = hass.helpers.entity_registry.async_entries_for_device( entity_registry, device_id, include_disabled_entities=True @@ -150,6 +191,22 @@ async def info_for_device(hass, device_id): } for topic, subscription in entity_info["subscriptions"].items() ] + transmitted = [ + { + "topic": topic, + "messages": [ + { + "payload": str(msg.payload), + "qos": msg.qos, + "retain": msg.retain, + "time": msg.timestamp, + "topic": msg.topic, + } + for msg in list(subscription["messages"]) + ], + } + for topic, subscription in entity_info["transmitted"].items() + ] discovery_data = { "topic": entity_info["discovery_data"].get(ATTR_DISCOVERY_TOPIC, ""), "payload": entity_info["discovery_data"].get(ATTR_DISCOVERY_PAYLOAD, ""), @@ -159,6 +216,7 @@ async def info_for_device(hass, device_id): "entity_id": entry.entity_id, "subscriptions": subscriptions, "discovery_data": discovery_data, + "transmitted": transmitted, } ) diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index f5b347d6b71a20..6fe36cd5fcd85c 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -544,8 +544,7 @@ async def async_turn_on( This method is a coroutine. """ mqtt_payload = self._command_templates[CONF_STATE](self._payload["STATE_ON"]) - await mqtt.async_publish( - self.hass, + await self.async_publish( self._topic[CONF_COMMAND_TOPIC], mqtt_payload, self._config[CONF_QOS], @@ -566,8 +565,7 @@ async def async_turn_off(self, **kwargs) -> None: This method is a coroutine. """ mqtt_payload = self._command_templates[CONF_STATE](self._payload["STATE_OFF"]) - await mqtt.async_publish( - self.hass, + await self.async_publish( self._topic[CONF_COMMAND_TOPIC], mqtt_payload, self._config[CONF_QOS], @@ -587,8 +585,7 @@ async def async_set_percentage(self, percentage: int) -> None: percentage_to_ranged_value(self._speed_range, percentage) ) mqtt_payload = self._command_templates[ATTR_PERCENTAGE](percentage_payload) - await mqtt.async_publish( - self.hass, + await self.async_publish( self._topic[CONF_PERCENTAGE_COMMAND_TOPIC], mqtt_payload, self._config[CONF_QOS], @@ -611,8 +608,7 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: mqtt_payload = self._command_templates[ATTR_PRESET_MODE](preset_mode) - await mqtt.async_publish( - self.hass, + await self.async_publish( self._topic[CONF_PRESET_MODE_COMMAND_TOPIC], mqtt_payload, self._config[CONF_QOS], @@ -638,8 +634,7 @@ async def async_oscillate(self, oscillating: bool) -> None: self._payload["OSCILLATE_OFF_PAYLOAD"] ) - await mqtt.async_publish( - self.hass, + await self.async_publish( self._topic[CONF_OSCILLATION_COMMAND_TOPIC], mqtt_payload, self._config[CONF_QOS], diff --git a/homeassistant/components/mqtt/humidifier.py b/homeassistant/components/mqtt/humidifier.py index e5f4cea6f88b2b..c9a5341a43f6d6 100644 --- a/homeassistant/components/mqtt/humidifier.py +++ b/homeassistant/components/mqtt/humidifier.py @@ -419,8 +419,7 @@ async def async_turn_on( This method is a coroutine. """ mqtt_payload = self._command_templates[CONF_STATE](self._payload["STATE_ON"]) - await mqtt.async_publish( - self.hass, + await self.async_publish( self._topic[CONF_COMMAND_TOPIC], mqtt_payload, self._config[CONF_QOS], @@ -437,8 +436,7 @@ async def async_turn_off(self, **kwargs) -> None: This method is a coroutine. """ mqtt_payload = self._command_templates[CONF_STATE](self._payload["STATE_OFF"]) - await mqtt.async_publish( - self.hass, + await self.async_publish( self._topic[CONF_COMMAND_TOPIC], mqtt_payload, self._config[CONF_QOS], @@ -455,8 +453,7 @@ async def async_set_humidity(self, humidity: int) -> None: This method is a coroutine. """ mqtt_payload = self._command_templates[ATTR_HUMIDITY](humidity) - await mqtt.async_publish( - self.hass, + await self.async_publish( self._topic[CONF_TARGET_HUMIDITY_COMMAND_TOPIC], mqtt_payload, self._config[CONF_QOS], @@ -479,8 +476,7 @@ async def async_set_mode(self, mode: str) -> None: mqtt_payload = self._command_templates[ATTR_MODE](mode) - await mqtt.async_publish( - self.hass, + await self.async_publish( self._topic[CONF_MODE_COMMAND_TOPIC], mqtt_payload, self._config[CONF_QOS], diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index d917b379eab779..1ff08a49ab1720 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -833,8 +833,7 @@ async def async_turn_on(self, **kwargs): # noqa: C901 async def publish(topic, payload): """Publish an MQTT message.""" - await mqtt.async_publish( - self.hass, + await self.async_publish( self._topic[topic], payload, self._config[CONF_QOS], @@ -1081,8 +1080,7 @@ async def async_turn_off(self, **kwargs): This method is a coroutine. """ - await mqtt.async_publish( - self.hass, + await self.async_publish( self._topic[CONF_COMMAND_TOPIC], self._payload["off"], self._config[CONF_QOS], diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index 32435948b1e19a..52b840fcfaffc3 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -638,8 +638,7 @@ async def async_turn_on(self, **kwargs): # noqa: C901 self._white_value = kwargs[ATTR_WHITE_VALUE] should_update = True - await mqtt.async_publish( - self.hass, + await self.async_publish( self._topic[CONF_COMMAND_TOPIC], json.dumps(message), self._config[CONF_QOS], @@ -664,8 +663,7 @@ async def async_turn_off(self, **kwargs): self._set_flash_and_transition(message, **kwargs) - await mqtt.async_publish( - self.hass, + await self.async_publish( self._topic[CONF_COMMAND_TOPIC], json.dumps(message), self._config[CONF_QOS], diff --git a/homeassistant/components/mqtt/light/schema_template.py b/homeassistant/components/mqtt/light/schema_template.py index b7f03fd0508457..5700a8ab868d7c 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -392,8 +392,7 @@ async def async_turn_on(self, **kwargs): if ATTR_TRANSITION in kwargs: values["transition"] = kwargs[ATTR_TRANSITION] - await mqtt.async_publish( - self.hass, + await self.async_publish( self._topics[CONF_COMMAND_TOPIC], self._templates[CONF_COMMAND_ON_TEMPLATE].async_render( parse_result=False, **values @@ -418,8 +417,7 @@ async def async_turn_off(self, **kwargs): if ATTR_TRANSITION in kwargs: values["transition"] = kwargs[ATTR_TRANSITION] - await mqtt.async_publish( - self.hass, + await self.async_publish( self._topics[CONF_COMMAND_TOPIC], self._templates[CONF_COMMAND_OFF_TEMPLATE].async_render( parse_result=False, **values diff --git a/homeassistant/components/mqtt/lock.py b/homeassistant/components/mqtt/lock.py index 89917f4cc5c6ea..788c6be1fefec2 100644 --- a/homeassistant/components/mqtt/lock.py +++ b/homeassistant/components/mqtt/lock.py @@ -179,8 +179,7 @@ async def async_lock(self, **kwargs): This method is a coroutine. """ - await mqtt.async_publish( - self.hass, + await self.async_publish( self._config[CONF_COMMAND_TOPIC], self._config[CONF_PAYLOAD_LOCK], self._config[CONF_QOS], @@ -197,8 +196,7 @@ async def async_unlock(self, **kwargs): This method is a coroutine. """ - await mqtt.async_publish( - self.hass, + await self.async_publish( self._config[CONF_COMMAND_TOPIC], self._config[CONF_PAYLOAD_UNLOCK], self._config[CONF_QOS], @@ -215,8 +213,7 @@ async def async_open(self, **kwargs): This method is a coroutine. """ - await mqtt.async_publish( - self.hass, + await self.async_publish( self._config[CONF_COMMAND_TOPIC], self._config[CONF_PAYLOAD_OPEN], self._config[CONF_QOS], diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 1f4bbe9d949e72..722bfd51c9f8eb 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -42,7 +42,7 @@ ) from homeassistant.helpers.typing import ConfigType -from . import DATA_MQTT, MqttValueTemplate, debug_info, publish, subscription +from . import DATA_MQTT, MqttValueTemplate, async_publish, debug_info, subscription from .const import ( ATTR_DISCOVERY_HASH, ATTR_DISCOVERY_PAYLOAD, @@ -51,13 +51,14 @@ CONF_ENCODING, CONF_QOS, CONF_TOPIC, + DEFAULT_ENCODING, DEFAULT_PAYLOAD_AVAILABLE, DEFAULT_PAYLOAD_NOT_AVAILABLE, DOMAIN, MQTT_CONNECTED, MQTT_DISCONNECTED, ) -from .debug_info import log_messages +from .debug_info import log_message, log_messages from .discovery import ( MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, @@ -65,7 +66,7 @@ clear_discovery_hash, set_discovery_hash, ) -from .models import ReceiveMessage +from .models import PublishPayloadType, ReceiveMessage from .subscription import ( async_prepare_subscribe_topics, async_subscribe_topics, @@ -552,7 +553,7 @@ async def async_removed_from_registry(self) -> None: # Clear the discovery topic so the entity is not rediscovered after a restart discovery_topic = self._discovery_data[ATTR_DISCOVERY_TOPIC] - publish(self.hass, discovery_topic, "", retain=True) + await async_publish(self.hass, discovery_topic, "", retain=True) @callback def add_to_platform_abort(self) -> None: @@ -709,6 +710,25 @@ async def async_will_remove_from_hass(self): await MqttAvailability.async_will_remove_from_hass(self) await MqttDiscoveryUpdate.async_will_remove_from_hass(self) + async def async_publish( + self, + topic: str, + payload: PublishPayloadType, + qos: int = 0, + retain: bool = False, + encoding: str = DEFAULT_ENCODING, + ): + """Publish message to an MQTT topic.""" + log_message(self.hass, self.entity_id, topic, payload, qos, retain) + await async_publish( + self.hass, + topic, + payload, + qos, + retain, + encoding, + ) + @staticmethod @abstractmethod def config_schema(): diff --git a/homeassistant/components/mqtt/number.py b/homeassistant/components/mqtt/number.py index 511b6e470acbe8..6f9c4c38eadbe8 100644 --- a/homeassistant/components/mqtt/number.py +++ b/homeassistant/components/mqtt/number.py @@ -257,8 +257,7 @@ async def async_set_value(self, value: float) -> None: self._current_number = current_number self.async_write_ha_state() - await mqtt.async_publish( - self.hass, + await self.async_publish( self._config[CONF_COMMAND_TOPIC], payload, self._config[CONF_QOS], diff --git a/homeassistant/components/mqtt/select.py b/homeassistant/components/mqtt/select.py index 24bed158eda9a1..58e6e7e4d6409f 100644 --- a/homeassistant/components/mqtt/select.py +++ b/homeassistant/components/mqtt/select.py @@ -183,8 +183,7 @@ async def async_select_option(self, option: str) -> None: self._attr_current_option = option self.async_write_ha_state() - await mqtt.async_publish( - self.hass, + await self.async_publish( self._config[CONF_COMMAND_TOPIC], payload, self._config[CONF_QOS], diff --git a/homeassistant/components/mqtt/siren.py b/homeassistant/components/mqtt/siren.py index e33a13545b310f..b3c5df157c91ce 100644 --- a/homeassistant/components/mqtt/siren.py +++ b/homeassistant/components/mqtt/siren.py @@ -328,8 +328,7 @@ async def _async_publish( else json.dumps(template_variables) ) if payload and payload not in PAYLOAD_NONE: - await mqtt.async_publish( - self.hass, + await self.async_publish( self._config[topic], payload, self._config[CONF_QOS], diff --git a/homeassistant/components/mqtt/switch.py b/homeassistant/components/mqtt/switch.py index cf5422a93eb0e2..906901b6080df0 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -197,8 +197,7 @@ async def async_turn_on(self, **kwargs): This method is a coroutine. """ - await mqtt.async_publish( - self.hass, + await self.async_publish( self._config[CONF_COMMAND_TOPIC], self._config[CONF_PAYLOAD_ON], self._config[CONF_QOS], @@ -215,8 +214,7 @@ async def async_turn_off(self, **kwargs): This method is a coroutine. """ - await mqtt.async_publish( - self.hass, + await self.async_publish( self._config[CONF_COMMAND_TOPIC], self._config[CONF_PAYLOAD_OFF], self._config[CONF_QOS], diff --git a/homeassistant/components/mqtt/vacuum/schema_legacy.py b/homeassistant/components/mqtt/vacuum/schema_legacy.py index 3a764ca9e454a6..087de1086b59c3 100644 --- a/homeassistant/components/mqtt/vacuum/schema_legacy.py +++ b/homeassistant/components/mqtt/vacuum/schema_legacy.py @@ -388,8 +388,7 @@ async def async_turn_on(self, **kwargs): if self.supported_features & SUPPORT_TURN_ON == 0: return - await mqtt.async_publish( - self.hass, + await self.async_publish( self._command_topic, self._payloads[CONF_PAYLOAD_TURN_ON], self._qos, @@ -404,8 +403,7 @@ async def async_turn_off(self, **kwargs): if self.supported_features & SUPPORT_TURN_OFF == 0: return None - await mqtt.async_publish( - self.hass, + await self.async_publish( self._command_topic, self._payloads[CONF_PAYLOAD_TURN_OFF], self._qos, @@ -420,8 +418,7 @@ async def async_stop(self, **kwargs): if self.supported_features & SUPPORT_STOP == 0: return None - await mqtt.async_publish( - self.hass, + await self.async_publish( self._command_topic, self._payloads[CONF_PAYLOAD_STOP], self._qos, @@ -436,8 +433,7 @@ async def async_clean_spot(self, **kwargs): if self.supported_features & SUPPORT_CLEAN_SPOT == 0: return None - await mqtt.async_publish( - self.hass, + await self.async_publish( self._command_topic, self._payloads[CONF_PAYLOAD_CLEAN_SPOT], self._qos, @@ -452,8 +448,7 @@ async def async_locate(self, **kwargs): if self.supported_features & SUPPORT_LOCATE == 0: return None - await mqtt.async_publish( - self.hass, + await self.async_publish( self._command_topic, self._payloads[CONF_PAYLOAD_LOCATE], self._qos, @@ -468,8 +463,7 @@ async def async_start_pause(self, **kwargs): if self.supported_features & SUPPORT_PAUSE == 0: return None - await mqtt.async_publish( - self.hass, + await self.async_publish( self._command_topic, self._payloads[CONF_PAYLOAD_START_PAUSE], self._qos, @@ -484,8 +478,7 @@ async def async_return_to_base(self, **kwargs): if self.supported_features & SUPPORT_RETURN_HOME == 0: return None - await mqtt.async_publish( - self.hass, + await self.async_publish( self._command_topic, self._payloads[CONF_PAYLOAD_RETURN_TO_BASE], self._qos, @@ -502,8 +495,7 @@ async def async_set_fan_speed(self, fan_speed, **kwargs): ) or fan_speed not in self._fan_speed_list: return None - await mqtt.async_publish( - self.hass, + await self.async_publish( self._set_fan_speed_topic, fan_speed, self._qos, @@ -523,8 +515,7 @@ async def async_send_command(self, command, params=None, **kwargs): message = json.dumps(message) else: message = command - await mqtt.async_publish( - self.hass, + await self.async_publish( self._send_command_topic, message, self._qos, diff --git a/homeassistant/components/mqtt/vacuum/schema_state.py b/homeassistant/components/mqtt/vacuum/schema_state.py index 494fc60fabd1bd..e5c138c96ff16f 100644 --- a/homeassistant/components/mqtt/vacuum/schema_state.py +++ b/homeassistant/components/mqtt/vacuum/schema_state.py @@ -260,8 +260,7 @@ async def async_start(self): """Start the vacuum.""" if self.supported_features & SUPPORT_START == 0: return None - await mqtt.async_publish( - self.hass, + await self.async_publish( self._command_topic, self._config[CONF_PAYLOAD_START], self._config[CONF_QOS], @@ -273,8 +272,7 @@ async def async_pause(self): """Pause the vacuum.""" if self.supported_features & SUPPORT_PAUSE == 0: return None - await mqtt.async_publish( - self.hass, + await self.async_publish( self._command_topic, self._config[CONF_PAYLOAD_PAUSE], self._config[CONF_QOS], @@ -286,8 +284,7 @@ async def async_stop(self, **kwargs): """Stop the vacuum.""" if self.supported_features & SUPPORT_STOP == 0: return None - await mqtt.async_publish( - self.hass, + await self.async_publish( self._command_topic, self._config[CONF_PAYLOAD_STOP], self._config[CONF_QOS], @@ -301,8 +298,7 @@ async def async_set_fan_speed(self, fan_speed, **kwargs): fan_speed not in self._fan_speed_list ): return None - await mqtt.async_publish( - self.hass, + await self.async_publish( self._set_fan_speed_topic, fan_speed, self._config[CONF_QOS], @@ -314,8 +310,7 @@ async def async_return_to_base(self, **kwargs): """Tell the vacuum to return to its dock.""" if self.supported_features & SUPPORT_RETURN_HOME == 0: return None - await mqtt.async_publish( - self.hass, + await self.async_publish( self._command_topic, self._config[CONF_PAYLOAD_RETURN_TO_BASE], self._config[CONF_QOS], @@ -327,8 +322,7 @@ async def async_clean_spot(self, **kwargs): """Perform a spot clean-up.""" if self.supported_features & SUPPORT_CLEAN_SPOT == 0: return None - await mqtt.async_publish( - self.hass, + await self.async_publish( self._command_topic, self._config[CONF_PAYLOAD_CLEAN_SPOT], self._config[CONF_QOS], @@ -340,8 +334,7 @@ async def async_locate(self, **kwargs): """Locate the vacuum (usually by playing a song).""" if self.supported_features & SUPPORT_LOCATE == 0: return None - await mqtt.async_publish( - self.hass, + await self.async_publish( self._command_topic, self._config[CONF_PAYLOAD_LOCATE], self._config[CONF_QOS], @@ -359,8 +352,7 @@ async def async_send_command(self, command, params=None, **kwargs): message = json.dumps(message) else: message = command - await mqtt.async_publish( - self.hass, + await self.async_publish( self._send_command_topic, message, self._config[CONF_QOS], diff --git a/tests/components/mqtt/test_alarm_control_panel.py b/tests/components/mqtt/test_alarm_control_panel.py index 16e46faaef8565..091048513c749d 100644 --- a/tests/components/mqtt/test_alarm_control_panel.py +++ b/tests/components/mqtt/test_alarm_control_panel.py @@ -771,7 +771,12 @@ async def test_entity_id_update_discovery_update(hass, mqtt_mock): async def test_entity_debug_info_message(hass, mqtt_mock): """Test MQTT debug info.""" await help_test_entity_debug_info_message( - hass, mqtt_mock, alarm_control_panel.DOMAIN, DEFAULT_CONFIG + hass, + mqtt_mock, + alarm_control_panel.DOMAIN, + DEFAULT_CONFIG, + alarm_control_panel.SERVICE_ALARM_DISARM, + command_payload="DISARM", ) diff --git a/tests/components/mqtt/test_binary_sensor.py b/tests/components/mqtt/test_binary_sensor.py index 39685db2afc282..aa726f4fff21cf 100644 --- a/tests/components/mqtt/test_binary_sensor.py +++ b/tests/components/mqtt/test_binary_sensor.py @@ -868,7 +868,7 @@ async def test_entity_id_update_discovery_update(hass, mqtt_mock): async def test_entity_debug_info_message(hass, mqtt_mock): """Test MQTT debug info.""" await help_test_entity_debug_info_message( - hass, mqtt_mock, binary_sensor.DOMAIN, DEFAULT_CONFIG + hass, mqtt_mock, binary_sensor.DOMAIN, DEFAULT_CONFIG, None ) diff --git a/tests/components/mqtt/test_button.py b/tests/components/mqtt/test_button.py index a533e0f0ec717e..e4997085ce23ee 100644 --- a/tests/components/mqtt/test_button.py +++ b/tests/components/mqtt/test_button.py @@ -18,6 +18,7 @@ help_test_discovery_update, help_test_discovery_update_attr, help_test_discovery_update_unchanged, + help_test_entity_debug_info_message, help_test_entity_device_info_remove, help_test_entity_device_info_update, help_test_entity_device_info_with_connection, @@ -302,6 +303,19 @@ async def test_entity_id_update_discovery_update(hass, mqtt_mock): ) +async def test_entity_debug_info_message(hass, mqtt_mock): + """Test MQTT debug info.""" + await help_test_entity_debug_info_message( + hass, + mqtt_mock, + button.DOMAIN, + DEFAULT_CONFIG, + button.SERVICE_PRESS, + command_payload="PRESS", + state_topic=None, + ) + + async def test_invalid_device_class(hass, mqtt_mock): """Test device_class option with invalid value.""" assert await async_setup_component( diff --git a/tests/components/mqtt/test_camera.py b/tests/components/mqtt/test_camera.py index 95e8c467a5298e..936e4ef4664ef0 100644 --- a/tests/components/mqtt/test_camera.py +++ b/tests/components/mqtt/test_camera.py @@ -237,7 +237,13 @@ async def test_entity_id_update_discovery_update(hass, mqtt_mock): async def test_entity_debug_info_message(hass, mqtt_mock): """Test MQTT debug info.""" await help_test_entity_debug_info_message( - hass, mqtt_mock, camera.DOMAIN, DEFAULT_CONFIG, "test_topic", b"ON" + hass, + mqtt_mock, + camera.DOMAIN, + DEFAULT_CONFIG, + None, + state_topic="test_topic", + state_payload=b"ON", ) diff --git a/tests/components/mqtt/test_climate.py b/tests/components/mqtt/test_climate.py index 3b2da69f94b835..16c765dc51afc1 100644 --- a/tests/components/mqtt/test_climate.py +++ b/tests/components/mqtt/test_climate.py @@ -1240,11 +1240,19 @@ async def test_entity_debug_info_message(hass, mqtt_mock): CLIMATE_DOMAIN: { "platform": "mqtt", "name": "test", + "mode_command_topic": "command-topic", "mode_state_topic": "test-topic", } } await help_test_entity_debug_info_message( - hass, mqtt_mock, CLIMATE_DOMAIN, config, "test-topic" + hass, + mqtt_mock, + CLIMATE_DOMAIN, + config, + climate.SERVICE_TURN_ON, + command_topic="command-topic", + command_payload="heat", + state_topic="test-topic", ) diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index 78c37b1105a4c7..758cdd801aeaae 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -43,6 +43,8 @@ "configuration_url": "http://example.com", } +_SENTINEL = object() + async def help_test_availability_when_connection_lost(hass, mqtt_mock, domain, config): """Test availability after MQTT disconnection.""" @@ -1110,7 +1112,7 @@ async def help_test_entity_debug_info(hass, mqtt_mock, domain, config): device = registry.async_get_device({("mqtt", "helloworld")}) assert device is not None - debug_info_data = await debug_info.info_for_device(hass, device.id) + debug_info_data = debug_info.info_for_device(hass, device.id) assert len(debug_info_data["entities"]) == 1 assert ( debug_info_data["entities"][0]["discovery_data"]["topic"] @@ -1121,6 +1123,7 @@ async def help_test_entity_debug_info(hass, mqtt_mock, domain, config): assert {"topic": "test-topic", "messages": []} in debug_info_data["entities"][0][ "subscriptions" ] + assert debug_info_data["entities"][0]["transmitted"] == [] assert len(debug_info_data["triggers"]) == 0 @@ -1143,7 +1146,7 @@ async def help_test_entity_debug_info_max_messages(hass, mqtt_mock, domain, conf device = registry.async_get_device({("mqtt", "helloworld")}) assert device is not None - debug_info_data = await debug_info.info_for_device(hass, device.id) + debug_info_data = debug_info.info_for_device(hass, device.id) assert len(debug_info_data["entities"][0]["subscriptions"]) == 1 assert {"topic": "test-topic", "messages": []} in debug_info_data["entities"][0][ "subscriptions" @@ -1155,7 +1158,7 @@ async def help_test_entity_debug_info_max_messages(hass, mqtt_mock, domain, conf for i in range(0, debug_info.STORED_MESSAGES + 1): async_fire_mqtt_message(hass, "test-topic", f"{i}") - debug_info_data = await debug_info.info_for_device(hass, device.id) + debug_info_data = debug_info.info_for_device(hass, device.id) assert len(debug_info_data["entities"][0]["subscriptions"]) == 1 assert ( len(debug_info_data["entities"][0]["subscriptions"][0]["messages"]) @@ -1177,9 +1180,18 @@ async def help_test_entity_debug_info_max_messages(hass, mqtt_mock, domain, conf async def help_test_entity_debug_info_message( - hass, mqtt_mock, domain, config, topic=None, payload=None + hass, + mqtt_mock, + domain, + config, + service, + command_topic=_SENTINEL, + command_payload=_SENTINEL, + state_topic=_SENTINEL, + state_payload=_SENTINEL, + service_parameters=None, ): - """Test debug_info message overflow. + """Test debug_info. This is a test helper for MQTT debug_info. """ @@ -1188,13 +1200,21 @@ async def help_test_entity_debug_info_message( config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID) config["unique_id"] = "veryunique" - if topic is None: + if command_topic is _SENTINEL: + # Add default topic to config + config["command_topic"] = "command-topic" + command_topic = "command-topic" + + if command_payload is _SENTINEL: + command_payload = "ON" + + if state_topic is _SENTINEL: # Add default topic to config config["state_topic"] = "state-topic" - topic = "state-topic" + state_topic = "state-topic" - if payload is None: - payload = "ON" + if state_payload is _SENTINEL: + state_payload = "ON" registry = dr.async_get(hass) @@ -1205,31 +1225,69 @@ async def help_test_entity_debug_info_message( device = registry.async_get_device({("mqtt", "helloworld")}) assert device is not None - debug_info_data = await debug_info.info_for_device(hass, device.id) - assert len(debug_info_data["entities"][0]["subscriptions"]) >= 1 - assert {"topic": topic, "messages": []} in debug_info_data["entities"][0][ - "subscriptions" - ] + debug_info_data = debug_info.info_for_device(hass, device.id) start_dt = datetime(2019, 1, 1, 0, 0, 0) - with patch("homeassistant.util.dt.utcnow") as dt_utcnow: - dt_utcnow.return_value = start_dt - async_fire_mqtt_message(hass, topic, payload) - debug_info_data = await debug_info.info_for_device(hass, device.id) - assert len(debug_info_data["entities"][0]["subscriptions"]) >= 1 - assert { - "topic": topic, - "messages": [ + if state_topic is not None: + assert len(debug_info_data["entities"][0]["subscriptions"]) >= 1 + assert {"topic": state_topic, "messages": []} in debug_info_data["entities"][0][ + "subscriptions" + ] + + with patch("homeassistant.util.dt.utcnow") as dt_utcnow: + dt_utcnow.return_value = start_dt + async_fire_mqtt_message(hass, state_topic, state_payload) + + debug_info_data = debug_info.info_for_device(hass, device.id) + assert len(debug_info_data["entities"][0]["subscriptions"]) >= 1 + assert { + "topic": state_topic, + "messages": [ + { + "payload": str(state_payload), + "qos": 0, + "retain": False, + "time": start_dt, + "topic": state_topic, + } + ], + } in debug_info_data["entities"][0]["subscriptions"] + + expected_transmissions = [] + if service: + # Trigger an outgoing MQTT message + with patch("homeassistant.util.dt.utcnow") as dt_utcnow: + dt_utcnow.return_value = start_dt + if service: + service_data = {ATTR_ENTITY_ID: f"{domain}.test"} + if service_parameters: + service_data.update(service_parameters) + + await hass.services.async_call( + domain, + service, + service_data, + blocking=True, + ) + + expected_transmissions = [ { - "payload": str(payload), - "qos": 0, - "retain": False, - "time": start_dt, - "topic": topic, + "topic": command_topic, + "messages": [ + { + "payload": str(command_payload), + "qos": 0, + "retain": False, + "time": start_dt, + "topic": command_topic, + } + ], } - ], - } in debug_info_data["entities"][0]["subscriptions"] + ] + + debug_info_data = debug_info.info_for_device(hass, device.id) + assert debug_info_data["entities"][0]["transmitted"] == expected_transmissions async def help_test_entity_debug_info_remove(hass, mqtt_mock, domain, config): @@ -1251,7 +1309,7 @@ async def help_test_entity_debug_info_remove(hass, mqtt_mock, domain, config): device = registry.async_get_device({("mqtt", "helloworld")}) assert device is not None - debug_info_data = await debug_info.info_for_device(hass, device.id) + debug_info_data = debug_info.info_for_device(hass, device.id) assert len(debug_info_data["entities"]) == 1 assert ( debug_info_data["entities"][0]["discovery_data"]["topic"] @@ -1269,7 +1327,7 @@ async def help_test_entity_debug_info_remove(hass, mqtt_mock, domain, config): async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", "") await hass.async_block_till_done() - debug_info_data = await debug_info.info_for_device(hass, device.id) + debug_info_data = debug_info.info_for_device(hass, device.id) assert len(debug_info_data["entities"]) == 0 assert len(debug_info_data["triggers"]) == 0 assert entity_id not in hass.data[debug_info.DATA_MQTT_DEBUG_INFO]["entities"] @@ -1295,7 +1353,7 @@ async def help_test_entity_debug_info_update_entity_id(hass, mqtt_mock, domain, device = dev_registry.async_get_device({("mqtt", "helloworld")}) assert device is not None - debug_info_data = await debug_info.info_for_device(hass, device.id) + debug_info_data = debug_info.info_for_device(hass, device.id) assert len(debug_info_data["entities"]) == 1 assert ( debug_info_data["entities"][0]["discovery_data"]["topic"] @@ -1313,7 +1371,7 @@ async def help_test_entity_debug_info_update_entity_id(hass, mqtt_mock, domain, await hass.async_block_till_done() await hass.async_block_till_done() - debug_info_data = await debug_info.info_for_device(hass, device.id) + debug_info_data = debug_info.info_for_device(hass, device.id) assert len(debug_info_data["entities"]) == 1 assert ( debug_info_data["entities"][0]["discovery_data"]["topic"] diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index 0d24f805cc12eb..59e03dadfe88bb 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -2521,7 +2521,12 @@ async def test_entity_id_update_discovery_update(hass, mqtt_mock): async def test_entity_debug_info_message(hass, mqtt_mock): """Test MQTT debug info.""" await help_test_entity_debug_info_message( - hass, mqtt_mock, cover.DOMAIN, DEFAULT_CONFIG + hass, + mqtt_mock, + cover.DOMAIN, + DEFAULT_CONFIG, + SERVICE_OPEN_COVER, + command_payload="OPEN", ) diff --git a/tests/components/mqtt/test_device_trigger.py b/tests/components/mqtt/test_device_trigger.py index a5359563d926fb..972b0678ed276b 100644 --- a/tests/components/mqtt/test_device_trigger.py +++ b/tests/components/mqtt/test_device_trigger.py @@ -1246,7 +1246,7 @@ async def test_trigger_debug_info(hass, mqtt_mock): ) assert device is not None - debug_info_data = await debug_info.info_for_device(hass, device.id) + debug_info_data = debug_info.info_for_device(hass, device.id) assert len(debug_info_data["entities"]) == 0 assert len(debug_info_data["triggers"]) == 2 topic_map = { @@ -1268,7 +1268,7 @@ async def test_trigger_debug_info(hass, mqtt_mock): async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", "") await hass.async_block_till_done() - debug_info_data = await debug_info.info_for_device(hass, device.id) + debug_info_data = debug_info.info_for_device(hass, device.id) assert len(debug_info_data["entities"]) == 0 assert len(debug_info_data["triggers"]) == 1 assert ( diff --git a/tests/components/mqtt/test_fan.py b/tests/components/mqtt/test_fan.py index ecc1f15c2040e1..9ce5e54262eb08 100644 --- a/tests/components/mqtt/test_fan.py +++ b/tests/components/mqtt/test_fan.py @@ -1724,7 +1724,7 @@ async def test_entity_id_update_discovery_update(hass, mqtt_mock): async def test_entity_debug_info_message(hass, mqtt_mock): """Test MQTT debug info.""" await help_test_entity_debug_info_message( - hass, mqtt_mock, fan.DOMAIN, DEFAULT_CONFIG + hass, mqtt_mock, fan.DOMAIN, DEFAULT_CONFIG, fan.SERVICE_TURN_ON ) diff --git a/tests/components/mqtt/test_humidifier.py b/tests/components/mqtt/test_humidifier.py index 48fe5b29a0c18f..f8685898ed5b85 100644 --- a/tests/components/mqtt/test_humidifier.py +++ b/tests/components/mqtt/test_humidifier.py @@ -1102,7 +1102,7 @@ async def test_entity_id_update_discovery_update(hass, mqtt_mock): async def test_entity_debug_info_message(hass, mqtt_mock): """Test MQTT debug info.""" await help_test_entity_debug_info_message( - hass, mqtt_mock, humidifier.DOMAIN, DEFAULT_CONFIG + hass, mqtt_mock, humidifier.DOMAIN, DEFAULT_CONFIG, humidifier.SERVICE_TURN_ON ) diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 9101b895218f9f..3cb49598e8c7d5 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -1533,6 +1533,7 @@ async def test_mqtt_ws_get_device_debug_info( "payload": config, "topic": "homeassistant/sensor/bla/config", }, + "transmitted": [], } ], "triggers": [], @@ -1595,6 +1596,7 @@ async def test_mqtt_ws_get_device_debug_info_binary( "payload": config, "topic": "homeassistant/camera/bla/config", }, + "transmitted": [], } ], "triggers": [], @@ -1662,7 +1664,7 @@ async def test_debug_info_multiple_devices(hass, mqtt_mock): device = registry.async_get_device({("mqtt", id)}) assert device is not None - debug_info_data = await debug_info.info_for_device(hass, device.id) + debug_info_data = debug_info.info_for_device(hass, device.id) if d["domain"] != "device_automation": assert len(debug_info_data["entities"]) == 1 assert len(debug_info_data["triggers"]) == 0 @@ -1739,7 +1741,7 @@ async def test_debug_info_multiple_entities_triggers(hass, mqtt_mock): device_id = config[0]["config"]["device"]["identifiers"][0] device = registry.async_get_device({("mqtt", device_id)}) assert device is not None - debug_info_data = await debug_info.info_for_device(hass, device.id) + debug_info_data = debug_info.info_for_device(hass, device.id) assert len(debug_info_data["entities"]) == 2 assert len(debug_info_data["triggers"]) == 2 @@ -1786,7 +1788,7 @@ async def test_debug_info_non_mqtt(hass, device_reg, entity_reg): assert await async_setup_component(hass, DOMAIN, {DOMAIN: {"platform": "test"}}) - debug_info_data = await debug_info.info_for_device(hass, device_entry.id) + debug_info_data = debug_info.info_for_device(hass, device_entry.id) assert len(debug_info_data["entities"]) == 0 assert len(debug_info_data["triggers"]) == 0 @@ -1810,7 +1812,7 @@ async def test_debug_info_wildcard(hass, mqtt_mock): device = registry.async_get_device({("mqtt", "helloworld")}) assert device is not None - debug_info_data = await debug_info.info_for_device(hass, device.id) + debug_info_data = debug_info.info_for_device(hass, device.id) assert len(debug_info_data["entities"][0]["subscriptions"]) >= 1 assert {"topic": "sensor/#", "messages": []} in debug_info_data["entities"][0][ "subscriptions" @@ -1821,7 +1823,7 @@ async def test_debug_info_wildcard(hass, mqtt_mock): dt_utcnow.return_value = start_dt async_fire_mqtt_message(hass, "sensor/abc", "123") - debug_info_data = await debug_info.info_for_device(hass, device.id) + debug_info_data = debug_info.info_for_device(hass, device.id) assert len(debug_info_data["entities"][0]["subscriptions"]) >= 1 assert { "topic": "sensor/#", @@ -1856,7 +1858,7 @@ async def test_debug_info_filter_same(hass, mqtt_mock): device = registry.async_get_device({("mqtt", "helloworld")}) assert device is not None - debug_info_data = await debug_info.info_for_device(hass, device.id) + debug_info_data = debug_info.info_for_device(hass, device.id) assert len(debug_info_data["entities"][0]["subscriptions"]) >= 1 assert {"topic": "sensor/#", "messages": []} in debug_info_data["entities"][0][ "subscriptions" @@ -1871,7 +1873,7 @@ async def test_debug_info_filter_same(hass, mqtt_mock): dt_utcnow.return_value = dt2 async_fire_mqtt_message(hass, "sensor/abc", "123") - debug_info_data = await debug_info.info_for_device(hass, device.id) + debug_info_data = debug_info.info_for_device(hass, device.id) assert len(debug_info_data["entities"][0]["subscriptions"]) == 1 assert len(debug_info_data["entities"][0]["subscriptions"][0]["messages"]) == 2 assert { @@ -1915,7 +1917,7 @@ async def test_debug_info_same_topic(hass, mqtt_mock): device = registry.async_get_device({("mqtt", "helloworld")}) assert device is not None - debug_info_data = await debug_info.info_for_device(hass, device.id) + debug_info_data = debug_info.info_for_device(hass, device.id) assert len(debug_info_data["entities"][0]["subscriptions"]) >= 1 assert {"topic": "sensor/status", "messages": []} in debug_info_data["entities"][0][ "subscriptions" @@ -1926,7 +1928,7 @@ async def test_debug_info_same_topic(hass, mqtt_mock): dt_utcnow.return_value = start_dt async_fire_mqtt_message(hass, "sensor/status", "123", qos=0, retain=False) - debug_info_data = await debug_info.info_for_device(hass, device.id) + debug_info_data = debug_info.info_for_device(hass, device.id) assert len(debug_info_data["entities"][0]["subscriptions"]) == 1 assert { "payload": "123", @@ -1966,7 +1968,7 @@ async def test_debug_info_qos_retain(hass, mqtt_mock): device = registry.async_get_device({("mqtt", "helloworld")}) assert device is not None - debug_info_data = await debug_info.info_for_device(hass, device.id) + debug_info_data = debug_info.info_for_device(hass, device.id) assert len(debug_info_data["entities"][0]["subscriptions"]) >= 1 assert {"topic": "sensor/#", "messages": []} in debug_info_data["entities"][0][ "subscriptions" @@ -1979,7 +1981,7 @@ async def test_debug_info_qos_retain(hass, mqtt_mock): async_fire_mqtt_message(hass, "sensor/abc", "123", qos=1, retain=True) async_fire_mqtt_message(hass, "sensor/abc", "123", qos=2, retain=False) - debug_info_data = await debug_info.info_for_device(hass, device.id) + debug_info_data = debug_info.info_for_device(hass, device.id) assert len(debug_info_data["entities"][0]["subscriptions"]) == 1 assert { "payload": "123", diff --git a/tests/components/mqtt/test_legacy_vacuum.py b/tests/components/mqtt/test_legacy_vacuum.py index 808212014c7585..d6e1524fc40977 100644 --- a/tests/components/mqtt/test_legacy_vacuum.py +++ b/tests/components/mqtt/test_legacy_vacuum.py @@ -747,14 +747,14 @@ async def test_entity_debug_info_message(hass, mqtt_mock): vacuum.DOMAIN: { "platform": "mqtt", "name": "test", - "battery_level_topic": "test-topic", + "battery_level_topic": "state-topic", "battery_level_template": "{{ value_json.battery_level }}", "command_topic": "command-topic", - "availability_topic": "avty-topic", + "payload_turn_on": "ON", } } await help_test_entity_debug_info_message( - hass, mqtt_mock, vacuum.DOMAIN, config, "test-topic" + hass, mqtt_mock, vacuum.DOMAIN, config, vacuum.SERVICE_TURN_ON ) diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index a1f929244a0a19..0eb77990d32535 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -3343,7 +3343,7 @@ async def test_entity_id_update_discovery_update(hass, mqtt_mock): async def test_entity_debug_info_message(hass, mqtt_mock): """Test MQTT debug info.""" await help_test_entity_debug_info_message( - hass, mqtt_mock, light.DOMAIN, DEFAULT_CONFIG + hass, mqtt_mock, light.DOMAIN, DEFAULT_CONFIG, light.SERVICE_TURN_ON ) diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index 2811e44618d350..8f2dce599ac6f4 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -1894,7 +1894,13 @@ async def test_entity_id_update_discovery_update(hass, mqtt_mock): async def test_entity_debug_info_message(hass, mqtt_mock): """Test MQTT debug info.""" await help_test_entity_debug_info_message( - hass, mqtt_mock, light.DOMAIN, DEFAULT_CONFIG, payload='{"state":"ON"}' + hass, + mqtt_mock, + light.DOMAIN, + DEFAULT_CONFIG, + light.SERVICE_TURN_ON, + command_payload='{"state": "ON"}', + state_payload='{"state":"ON"}', ) diff --git a/tests/components/mqtt/test_light_template.py b/tests/components/mqtt/test_light_template.py index 45977343b95098..c68ed8e7f35ffc 100644 --- a/tests/components/mqtt/test_light_template.py +++ b/tests/components/mqtt/test_light_template.py @@ -1082,12 +1082,14 @@ async def test_entity_debug_info_message(hass, mqtt_mock): "schema": "template", "name": "test", "command_topic": "test-topic", - "command_on_template": "on,{{ transition }}", + "command_on_template": "ON", "command_off_template": "off,{{ transition|d }}", "state_template": '{{ value.split(",")[0] }}', } } - await help_test_entity_debug_info_message(hass, mqtt_mock, light.DOMAIN, config) + await help_test_entity_debug_info_message( + hass, mqtt_mock, light.DOMAIN, config, light.SERVICE_TURN_ON + ) async def test_max_mireds(hass, mqtt_mock): diff --git a/tests/components/mqtt/test_lock.py b/tests/components/mqtt/test_lock.py index f29222f97d58e1..35849c4f9fc03e 100644 --- a/tests/components/mqtt/test_lock.py +++ b/tests/components/mqtt/test_lock.py @@ -590,7 +590,12 @@ async def test_entity_id_update_discovery_update(hass, mqtt_mock): async def test_entity_debug_info_message(hass, mqtt_mock): """Test MQTT debug info.""" await help_test_entity_debug_info_message( - hass, mqtt_mock, LOCK_DOMAIN, DEFAULT_CONFIG + hass, + mqtt_mock, + LOCK_DOMAIN, + DEFAULT_CONFIG, + SERVICE_LOCK, + command_payload="LOCK", ) diff --git a/tests/components/mqtt/test_number.py b/tests/components/mqtt/test_number.py index c233bf14ab517f..70bc1b40e75fd4 100644 --- a/tests/components/mqtt/test_number.py +++ b/tests/components/mqtt/test_number.py @@ -541,7 +541,14 @@ async def test_entity_id_update_discovery_update(hass, mqtt_mock): async def test_entity_debug_info_message(hass, mqtt_mock): """Test MQTT debug info.""" await help_test_entity_debug_info_message( - hass, mqtt_mock, number.DOMAIN, DEFAULT_CONFIG, payload="1" + hass, + mqtt_mock, + number.DOMAIN, + DEFAULT_CONFIG, + SERVICE_SET_VALUE, + service_parameters={ATTR_VALUE: 45}, + command_payload="45", + state_payload="1", ) diff --git a/tests/components/mqtt/test_select.py b/tests/components/mqtt/test_select.py index c09c0aebca83b1..f6f005bbfb5f78 100644 --- a/tests/components/mqtt/test_select.py +++ b/tests/components/mqtt/test_select.py @@ -475,7 +475,14 @@ async def test_entity_id_update_discovery_update(hass, mqtt_mock): async def test_entity_debug_info_message(hass, mqtt_mock): """Test MQTT debug info.""" await help_test_entity_debug_info_message( - hass, mqtt_mock, select.DOMAIN, DEFAULT_CONFIG, payload="milk" + hass, + mqtt_mock, + select.DOMAIN, + DEFAULT_CONFIG, + select.SERVICE_SELECT_OPTION, + service_parameters={ATTR_OPTION: "beer"}, + command_payload="beer", + state_payload="milk", ) diff --git a/tests/components/mqtt/test_sensor.py b/tests/components/mqtt/test_sensor.py index c758b670b3d37e..b556bcf7537342 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -906,7 +906,7 @@ async def test_entity_debug_info_max_messages(hass, mqtt_mock): async def test_entity_debug_info_message(hass, mqtt_mock): """Test MQTT debug info.""" await help_test_entity_debug_info_message( - hass, mqtt_mock, sensor.DOMAIN, DEFAULT_CONFIG + hass, mqtt_mock, sensor.DOMAIN, DEFAULT_CONFIG, None ) diff --git a/tests/components/mqtt/test_siren.py b/tests/components/mqtt/test_siren.py index e500bdb6ea7034..7f174582a46da7 100644 --- a/tests/components/mqtt/test_siren.py +++ b/tests/components/mqtt/test_siren.py @@ -802,7 +802,12 @@ async def test_entity_id_update_discovery_update(hass, mqtt_mock): async def test_entity_debug_info_message(hass, mqtt_mock): """Test MQTT debug info.""" await help_test_entity_debug_info_message( - hass, mqtt_mock, siren.DOMAIN, DEFAULT_CONFIG + hass, + mqtt_mock, + siren.DOMAIN, + DEFAULT_CONFIG, + siren.SERVICE_TURN_ON, + command_payload='{"state": "ON"}', ) diff --git a/tests/components/mqtt/test_state_vacuum.py b/tests/components/mqtt/test_state_vacuum.py index a1b90f52c37206..7b8928ccb32f04 100644 --- a/tests/components/mqtt/test_state_vacuum.py +++ b/tests/components/mqtt/test_state_vacuum.py @@ -510,7 +510,13 @@ async def test_entity_id_update_discovery_update(hass, mqtt_mock): async def test_entity_debug_info_message(hass, mqtt_mock): """Test MQTT debug info.""" await help_test_entity_debug_info_message( - hass, mqtt_mock, vacuum.DOMAIN, DEFAULT_CONFIG_2, payload="{}" + hass, + mqtt_mock, + vacuum.DOMAIN, + DEFAULT_CONFIG_2, + vacuum.SERVICE_START, + command_payload="start", + state_payload="{}", ) diff --git a/tests/components/mqtt/test_switch.py b/tests/components/mqtt/test_switch.py index 3eb998193a07ca..79ee56998e8071 100644 --- a/tests/components/mqtt/test_switch.py +++ b/tests/components/mqtt/test_switch.py @@ -499,7 +499,7 @@ async def test_entity_id_update_discovery_update(hass, mqtt_mock): async def test_entity_debug_info_message(hass, mqtt_mock): """Test MQTT debug info.""" await help_test_entity_debug_info_message( - hass, mqtt_mock, switch.DOMAIN, DEFAULT_CONFIG + hass, mqtt_mock, switch.DOMAIN, DEFAULT_CONFIG, switch.SERVICE_TURN_ON ) From a97e69196c7863dbf8bda45244f591ec536946ec Mon Sep 17 00:00:00 2001 From: Duco Sebel <74970928+DCSBL@users.noreply.github.com> Date: Fri, 4 Feb 2022 18:12:35 +0100 Subject: [PATCH 0268/1098] Add migration to migrate 'homewizard_energy' to 'homewizard' (#65594) --- .../components/homewizard/__init__.py | 48 +++++++++- .../components/homewizard/config_flow.py | 26 +++++- .../components/homewizard/test_config_flow.py | 33 +++++++ tests/components/homewizard/test_init.py | 90 +++++++++++++++++++ 4 files changed, 193 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/homewizard/__init__.py b/homeassistant/components/homewizard/__init__.py index bca041c6a27666..b50d87a940de16 100644 --- a/homeassistant/components/homewizard/__init__.py +++ b/homeassistant/components/homewizard/__init__.py @@ -3,10 +3,11 @@ from aiohwenergy import DisabledError -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_IP_ADDRESS from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.update_coordinator import UpdateFailed from .const import DOMAIN, PLATFORMS @@ -20,6 +21,51 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: _LOGGER.debug("__init__ async_setup_entry") + # Migrate `homewizard_energy` (custom_component) to `homewizard` + if entry.source == SOURCE_IMPORT and "old_config_entry_id" in entry.data: + # Remove the old config entry ID from the entry data so we don't try this again + # on the next setup + data = entry.data.copy() + old_config_entry_id = data.pop("old_config_entry_id") + + hass.config_entries.async_update_entry(entry, data=data) + _LOGGER.debug( + ( + "Setting up imported homewizard_energy entry %s for the first time as " + "homewizard entry %s" + ), + old_config_entry_id, + entry.entry_id, + ) + + ent_reg = er.async_get(hass) + for entity in er.async_entries_for_config_entry(ent_reg, old_config_entry_id): + _LOGGER.debug("Removing %s", entity.entity_id) + ent_reg.async_remove(entity.entity_id) + + _LOGGER.debug("Re-creating %s for the new config entry", entity.entity_id) + # We will precreate the entity so that any customizations can be preserved + new_entity = ent_reg.async_get_or_create( + entity.domain, + DOMAIN, + entity.unique_id, + suggested_object_id=entity.entity_id.split(".")[1], + disabled_by=entity.disabled_by, + config_entry=entry, + original_name=entity.original_name, + original_icon=entity.original_icon, + ) + _LOGGER.debug("Re-created %s", new_entity.entity_id) + + # If there are customizations on the old entity, apply them to the new one + if entity.name or entity.icon: + ent_reg.async_update_entity( + new_entity.entity_id, name=entity.name, icon=entity.icon + ) + + # Remove the old config entry and now the entry is fully migrated + hass.async_create_task(hass.config_entries.async_remove(old_config_entry_id)) + # Create coordinator coordinator = Coordinator(hass, entry.data[CONF_IP_ADDRESS]) try: diff --git a/homeassistant/components/homewizard/config_flow.py b/homeassistant/components/homewizard/config_flow.py index 17f87680c62043..45a912fefec92a 100644 --- a/homeassistant/components/homewizard/config_flow.py +++ b/homeassistant/components/homewizard/config_flow.py @@ -28,6 +28,21 @@ def __init__(self) -> None: """Initialize the HomeWizard config flow.""" self.config: dict[str, str | int] = {} + async def async_step_import(self, import_config: dict) -> FlowResult: + """Handle a flow initiated by older `homewizard_energy` component.""" + _LOGGER.debug("config_flow async_step_import") + + self.hass.components.persistent_notification.async_create( + ( + "The custom integration of HomeWizard Energy has been migrated to core. " + "You can safely remove the custom integration from the custom_integrations folder." + ), + "HomeWizard Energy", + f"homewizard_energy_to_{DOMAIN}", + ) + + return await self.async_step_user({CONF_IP_ADDRESS: import_config["host"]}) + async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: @@ -59,12 +74,17 @@ async def async_step_user( } ) + data: dict[str, str] = {CONF_IP_ADDRESS: user_input[CONF_IP_ADDRESS]} + + if self.source == config_entries.SOURCE_IMPORT: + old_config_entry_id = self.context["old_config_entry_id"] + assert self.hass.config_entries.async_get_entry(old_config_entry_id) + data["old_config_entry_id"] = old_config_entry_id + # Add entry return self.async_create_entry( title=f"{device_info[CONF_PRODUCT_NAME]} ({device_info[CONF_SERIAL]})", - data={ - CONF_IP_ADDRESS: user_input[CONF_IP_ADDRESS], - }, + data=data, ) async def async_step_zeroconf( diff --git a/tests/components/homewizard/test_config_flow.py b/tests/components/homewizard/test_config_flow.py index 7364a0e632e4d0..f416027da4a646 100644 --- a/tests/components/homewizard/test_config_flow.py +++ b/tests/components/homewizard/test_config_flow.py @@ -12,6 +12,8 @@ from .generator import get_mock_device +from tests.common import MockConfigEntry + _LOGGER = logging.getLogger(__name__) @@ -88,6 +90,37 @@ async def test_discovery_flow_works(hass, aioclient_mock): assert result["result"].unique_id == "HWE-P1_aabbccddeeff" +async def test_config_flow_imports_entry(aioclient_mock, hass): + """Test config flow accepts imported configuration.""" + + device = get_mock_device() + + mock_entry = MockConfigEntry(domain="homewizard_energy", data={"host": "1.2.3.4"}) + mock_entry.add_to_hass(hass) + + with patch("aiohwenergy.HomeWizardEnergy", return_value=device,), patch( + "homeassistant.components.homewizard.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_IMPORT, + "old_config_entry_id": mock_entry.entry_id, + }, + data=mock_entry.data, + ) + + assert result["type"] == "create_entry" + assert result["title"] == f"{device.device.product_name} (aabbccddeeff)" + assert result["data"][CONF_IP_ADDRESS] == "1.2.3.4" + + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert len(device.initialize.mock_calls) == 1 + assert len(device.close.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + async def test_discovery_disabled_api(hass, aioclient_mock): """Test discovery detecting disabled api.""" diff --git a/tests/components/homewizard/test_init.py b/tests/components/homewizard/test_init.py index f7aa4de7adecc2..87a02a446e90e8 100644 --- a/tests/components/homewizard/test_init.py +++ b/tests/components/homewizard/test_init.py @@ -4,9 +4,11 @@ from aiohwenergy import AiohwenergyException, DisabledError +from homeassistant import config_entries from homeassistant.components.homewizard.const import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.const import CONF_IP_ADDRESS +from homeassistant.helpers import entity_registry as er from .generator import get_mock_device @@ -68,6 +70,94 @@ def MockInitialize(): assert entry.state is ConfigEntryState.SETUP_RETRY +async def test_init_accepts_and_migrates_old_entry(aioclient_mock, hass): + """Test config flow accepts imported configuration.""" + + device = get_mock_device() + + # Add original entry + original_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_IP_ADDRESS: "1.2.3.4"}, + entry_id="old_id", + ) + original_entry.add_to_hass(hass) + + # Give it some entities to see of they migrate properly + ent_reg = er.async_get(hass) + old_entity_active_power = ent_reg.async_get_or_create( + "sensor", + "homewizard_energy", + "p1_active_power_unique_id", + config_entry=original_entry, + original_name="Active Power", + suggested_object_id="p1_active_power", + ) + old_entity_switch = ent_reg.async_get_or_create( + "switch", + "homewizard_energy", + "socket_switch_unique_id", + config_entry=original_entry, + original_name="Switch", + suggested_object_id="socket_switch", + ) + old_entity_disabled_sensor = ent_reg.async_get_or_create( + "sensor", + "homewizard_energy", + "socket_disabled_unique_id", + config_entry=original_entry, + original_name="Switch Disabled", + suggested_object_id="socket_disabled", + disabled_by=er.DISABLED_USER, + ) + # Update some user-customs + ent_reg.async_update_entity(old_entity_active_power.entity_id, name="new_name") + ent_reg.async_update_entity(old_entity_switch.entity_id, icon="new_icon") + + imported_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_IP_ADDRESS: "1.2.3.4", "old_config_entry_id": "old_id"}, + source=config_entries.SOURCE_IMPORT, + entry_id="new_id", + ) + imported_entry.add_to_hass(hass) + + # Add the entry_id to trigger migration + with patch( + "aiohwenergy.HomeWizardEnergy", + return_value=device, + ): + await hass.config_entries.async_setup(imported_entry.entry_id) + await hass.async_block_till_done() + + assert original_entry.state is ConfigEntryState.NOT_LOADED + assert imported_entry.state is ConfigEntryState.LOADED + + # Check if new entities are migrated + new_entity_active_power = ent_reg.async_get(old_entity_active_power.entity_id) + assert new_entity_active_power.platform == DOMAIN + assert new_entity_active_power.name == "new_name" + assert new_entity_active_power.icon is None + assert new_entity_active_power.original_name == "Active Power" + assert new_entity_active_power.unique_id == "p1_active_power_unique_id" + assert new_entity_active_power.disabled_by is None + + new_entity_switch = ent_reg.async_get(old_entity_switch.entity_id) + assert new_entity_switch.platform == DOMAIN + assert new_entity_switch.name is None + assert new_entity_switch.icon == "new_icon" + assert new_entity_switch.original_name == "Switch" + assert new_entity_switch.unique_id == "socket_switch_unique_id" + assert new_entity_switch.disabled_by is None + + new_entity_disabled_sensor = ent_reg.async_get(old_entity_disabled_sensor.entity_id) + assert new_entity_disabled_sensor.platform == DOMAIN + assert new_entity_disabled_sensor.name is None + assert new_entity_disabled_sensor.original_name == "Switch Disabled" + assert new_entity_disabled_sensor.unique_id == "socket_disabled_unique_id" + assert new_entity_disabled_sensor.disabled_by == er.DISABLED_USER + + async def test_load_detect_api_disabled(aioclient_mock, hass): """Test setup detects disabled API.""" From 5519888dc11eb978b02ce9e959013b975696c143 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Fri, 4 Feb 2022 18:36:56 +0100 Subject: [PATCH 0269/1098] Netgear add traffic sensors (#65645) * add sensors * use entity discription * use lambda functions * use seperate coordinators * styling * revieuw comments * set proper default * move api lock * fix styling * Update homeassistant/components/netgear/sensor.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/netgear/sensor.py Co-authored-by: Martin Hjelmare * use coordinator data * fix styling * fix typing * move typing * fix lock * Update homeassistant/components/netgear/sensor.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/netgear/sensor.py Co-authored-by: Martin Hjelmare Co-authored-by: Martin Hjelmare --- homeassistant/components/netgear/__init__.py | 33 ++- homeassistant/components/netgear/const.py | 1 + homeassistant/components/netgear/router.py | 69 +++++- homeassistant/components/netgear/sensor.py | 209 ++++++++++++++++++- 4 files changed, 294 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/netgear/__init__.py b/homeassistant/components/netgear/__init__.py index 919cf25ae82552..2842157f578ade 100644 --- a/homeassistant/components/netgear/__init__.py +++ b/homeassistant/components/netgear/__init__.py @@ -9,7 +9,13 @@ from homeassistant.helpers import device_registry as dr from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from .const import DOMAIN, KEY_COORDINATOR, KEY_ROUTER, PLATFORMS +from .const import ( + DOMAIN, + KEY_COORDINATOR, + KEY_COORDINATOR_TRAFFIC, + KEY_ROUTER, + PLATFORMS, +) from .errors import CannotLoginException from .router import NetgearRouter @@ -53,28 +59,41 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: name=router.device_name, model=router.model, sw_version=router.firmware_version, + hw_version=router.hardware_version, configuration_url=f"http://{entry.data[CONF_HOST]}/", ) - async def async_update_data() -> bool: + async def async_update_devices() -> bool: """Fetch data from the router.""" - data = await router.async_update_device_trackers() - return data + return await router.async_update_device_trackers() - # Create update coordinator + async def async_update_traffic_meter() -> dict: + """Fetch data from the router.""" + return await router.async_get_traffic_meter() + + # Create update coordinators coordinator = DataUpdateCoordinator( hass, _LOGGER, - name=router.device_name, - update_method=async_update_data, + name=f"{router.device_name} Devices", + update_method=async_update_devices, + update_interval=SCAN_INTERVAL, + ) + coordinator_traffic_meter = DataUpdateCoordinator( + hass, + _LOGGER, + name=f"{router.device_name} Traffic meter", + update_method=async_update_traffic_meter, update_interval=SCAN_INTERVAL, ) await coordinator.async_config_entry_first_refresh() + await coordinator_traffic_meter.async_config_entry_first_refresh() hass.data[DOMAIN][entry.entry_id] = { KEY_ROUTER: router, KEY_COORDINATOR: coordinator, + KEY_COORDINATOR_TRAFFIC: coordinator_traffic_meter, } hass.config_entries.async_setup_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/netgear/const.py b/homeassistant/components/netgear/const.py index b1d5dd229425dd..02b78e164ac6ca 100644 --- a/homeassistant/components/netgear/const.py +++ b/homeassistant/components/netgear/const.py @@ -9,6 +9,7 @@ KEY_ROUTER = "router" KEY_COORDINATOR = "coordinator" +KEY_COORDINATOR_TRAFFIC = "coordinator_traffic" PLATFORMS = [Platform.DEVICE_TRACKER, Platform.SENSOR] diff --git a/homeassistant/components/netgear/router.py b/homeassistant/components/netgear/router.py index d275aaf7fb25c3..b358939d122094 100644 --- a/homeassistant/components/netgear/router.py +++ b/homeassistant/components/netgear/router.py @@ -2,6 +2,7 @@ from __future__ import annotations from abc import abstractmethod +import asyncio from datetime import timedelta import logging @@ -69,9 +70,11 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: self._password = entry.data[CONF_PASSWORD] self._info = None - self.model = None - self.device_name = None - self.firmware_version = None + self.model = "" + self.device_name = "" + self.firmware_version = "" + self.hardware_version = "" + self.serial_number = "" self.method_version = 1 consider_home_int = entry.options.get( @@ -80,7 +83,7 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: self._consider_home = timedelta(seconds=consider_home_int) self._api: Netgear = None - self._attrs = {} + self._api_lock = asyncio.Lock() self.devices = {} @@ -101,6 +104,8 @@ def _setup(self) -> None: self.device_name = self._info.get("DeviceName", DEFAULT_NAME) self.model = self._info.get("ModelName") self.firmware_version = self._info.get("Firmwareversion") + self.hardware_version = self._info.get("Hardwareversion") + self.serial_number = self._info["SerialNumber"] for model in MODELS_V2: if self.model.startswith(model): @@ -149,12 +154,16 @@ async def async_setup(self) -> bool: async def async_get_attached_devices(self) -> list: """Get the devices connected to the router.""" if self.method_version == 1: + async with self._api_lock: + return await self.hass.async_add_executor_job( + self._api.get_attached_devices + ) + + async with self._api_lock: return await self.hass.async_add_executor_job( - self._api.get_attached_devices + self._api.get_attached_devices_2 ) - return await self.hass.async_add_executor_job(self._api.get_attached_devices_2) - async def async_update_device_trackers(self, now=None) -> None: """Update Netgear devices.""" new_device = False @@ -186,6 +195,11 @@ async def async_update_device_trackers(self, now=None) -> None: return new_device + async def async_get_traffic_meter(self) -> None: + """Get the traffic meter data of the router.""" + async with self._api_lock: + return await self.hass.async_add_executor_job(self._api.get_traffic_meter) + @property def port(self) -> int: """Port used by the API.""" @@ -261,3 +275,44 @@ def device_info(self) -> DeviceInfo: default_model=self._device["device_model"], via_device=(DOMAIN, self._router.unique_id), ) + + +class NetgearRouterEntity(CoordinatorEntity): + """Base class for a Netgear router entity.""" + + def __init__( + self, coordinator: DataUpdateCoordinator, router: NetgearRouter + ) -> None: + """Initialize a Netgear device.""" + super().__init__(coordinator) + self._router = router + self._name = router.device_name + self._unique_id = router.serial_number + + @abstractmethod + @callback + def async_update_device(self) -> None: + """Update the Netgear device.""" + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self.async_update_device() + super()._handle_coordinator_update() + + @property + def unique_id(self) -> str: + """Return a unique ID.""" + return self._unique_id + + @property + def name(self) -> str: + """Return the name.""" + return self._name + + @property + def device_info(self) -> DeviceInfo: + """Return the device information.""" + return DeviceInfo( + identifiers={(DOMAIN, self._router.unique_id)}, + ) diff --git a/homeassistant/components/netgear/sensor.py b/homeassistant/components/netgear/sensor.py index 0db0e4f19f4868..16c63a8cdcbb5f 100644 --- a/homeassistant/components/netgear/sensor.py +++ b/homeassistant/components/netgear/sensor.py @@ -1,30 +1,35 @@ """Support for Netgear routers.""" +from collections.abc import Callable +from dataclasses import dataclass + from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, SensorEntityDescription, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import PERCENTAGE +from homeassistant.const import DATA_MEGABYTES, PERCENTAGE from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from .const import DOMAIN, KEY_COORDINATOR, KEY_ROUTER -from .router import NetgearDeviceEntity, NetgearRouter +from .const import DOMAIN, KEY_COORDINATOR, KEY_COORDINATOR_TRAFFIC, KEY_ROUTER +from .router import NetgearDeviceEntity, NetgearRouter, NetgearRouterEntity SENSOR_TYPES = { "type": SensorEntityDescription( key="type", name="link type", entity_category=EntityCategory.DIAGNOSTIC, + icon="mdi:lan", ), "link_rate": SensorEntityDescription( key="link_rate", name="link rate", native_unit_of_measurement="Mbps", entity_category=EntityCategory.DIAGNOSTIC, + icon="mdi:speedometer", ), "signal": SensorEntityDescription( key="signal", @@ -37,23 +42,185 @@ key="ssid", name="ssid", entity_category=EntityCategory.DIAGNOSTIC, + icon="mdi:wifi-marker", ), "conn_ap_mac": SensorEntityDescription( key="conn_ap_mac", name="access point mac", entity_category=EntityCategory.DIAGNOSTIC, + icon="mdi:router-network", ), } +@dataclass +class NetgearSensorEntityDescription(SensorEntityDescription): + """Class describing Netgear sensor entities.""" + + value: Callable = lambda data: data + index: int = 0 + + +SENSOR_TRAFFIC_TYPES = [ + NetgearSensorEntityDescription( + key="NewTodayUpload", + name="Upload today", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=DATA_MEGABYTES, + icon="mdi:upload", + ), + NetgearSensorEntityDescription( + key="NewTodayDownload", + name="Download today", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=DATA_MEGABYTES, + icon="mdi:download", + ), + NetgearSensorEntityDescription( + key="NewYesterdayUpload", + name="Upload yesterday", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=DATA_MEGABYTES, + icon="mdi:upload", + ), + NetgearSensorEntityDescription( + key="NewYesterdayDownload", + name="Download yesterday", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=DATA_MEGABYTES, + icon="mdi:download", + ), + NetgearSensorEntityDescription( + key="NewWeekUpload", + name="Upload week", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=DATA_MEGABYTES, + icon="mdi:upload", + index=0, + value=lambda data: data[0] if data is not None else None, + ), + NetgearSensorEntityDescription( + key="NewWeekUpload", + name="Upload week average", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=DATA_MEGABYTES, + icon="mdi:upload", + index=1, + value=lambda data: data[1] if data is not None else None, + ), + NetgearSensorEntityDescription( + key="NewWeekDownload", + name="Download week", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=DATA_MEGABYTES, + icon="mdi:download", + index=0, + value=lambda data: data[0] if data is not None else None, + ), + NetgearSensorEntityDescription( + key="NewWeekDownload", + name="Download week average", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=DATA_MEGABYTES, + icon="mdi:download", + index=1, + value=lambda data: data[1] if data is not None else None, + ), + NetgearSensorEntityDescription( + key="NewMonthUpload", + name="Upload month", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=DATA_MEGABYTES, + icon="mdi:upload", + index=0, + value=lambda data: data[0] if data is not None else None, + ), + NetgearSensorEntityDescription( + key="NewMonthUpload", + name="Upload month average", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=DATA_MEGABYTES, + icon="mdi:upload", + index=1, + value=lambda data: data[1] if data is not None else None, + ), + NetgearSensorEntityDescription( + key="NewMonthDownload", + name="Download month", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=DATA_MEGABYTES, + icon="mdi:download", + index=0, + value=lambda data: data[0] if data is not None else None, + ), + NetgearSensorEntityDescription( + key="NewMonthDownload", + name="Download month average", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=DATA_MEGABYTES, + icon="mdi:download", + index=1, + value=lambda data: data[1] if data is not None else None, + ), + NetgearSensorEntityDescription( + key="NewLastMonthUpload", + name="Upload last month", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=DATA_MEGABYTES, + icon="mdi:upload", + index=0, + value=lambda data: data[0] if data is not None else None, + ), + NetgearSensorEntityDescription( + key="NewLastMonthUpload", + name="Upload last month average", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=DATA_MEGABYTES, + icon="mdi:upload", + index=1, + value=lambda data: data[1] if data is not None else None, + ), + NetgearSensorEntityDescription( + key="NewLastMonthDownload", + name="Download last month", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=DATA_MEGABYTES, + icon="mdi:download", + index=0, + value=lambda data: data[0] if data is not None else None, + ), + NetgearSensorEntityDescription( + key="NewLastMonthDownload", + name="Download last month average", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=DATA_MEGABYTES, + icon="mdi:download", + index=1, + value=lambda data: data[1] if data is not None else None, + ), +] + + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up device tracker for Netgear component.""" router = hass.data[DOMAIN][entry.entry_id][KEY_ROUTER] coordinator = hass.data[DOMAIN][entry.entry_id][KEY_COORDINATOR] - tracked = set() + coordinator_traffic = hass.data[DOMAIN][entry.entry_id][KEY_COORDINATOR_TRAFFIC] + + # Router entities + router_entities = [] + for description in SENSOR_TRAFFIC_TYPES: + router_entities.append( + NetgearRouterSensorEntity(coordinator_traffic, router, description) + ) + + async_add_entities(router_entities) + + # Entities per network device + tracked = set() sensors = ["type", "link_rate", "signal"] if router.method_version == 2: sensors.extend(["ssid", "conn_ap_mac"]) @@ -119,3 +286,37 @@ def async_update_device(self) -> None: self._active = self._device["active"] if self._device.get(self._attribute) is not None: self._state = self._device[self._attribute] + + +class NetgearRouterSensorEntity(NetgearRouterEntity, SensorEntity): + """Representation of a device connected to a Netgear router.""" + + _attr_entity_registry_enabled_default = False + entity_description: NetgearSensorEntityDescription + + def __init__( + self, + coordinator: DataUpdateCoordinator, + router: NetgearRouter, + entity_description: NetgearSensorEntityDescription, + ) -> None: + """Initialize a Netgear device.""" + super().__init__(coordinator, router) + self.entity_description = entity_description + self._name = f"{router.device_name} {entity_description.name}" + self._unique_id = f"{router.serial_number}-{entity_description.key}-{entity_description.index}" + + self._value = None + self.async_update_device() + + @property + def native_value(self): + """Return the state of the sensor.""" + return self._value + + @callback + def async_update_device(self) -> None: + """Update the Netgear device.""" + if self.coordinator.data is not None: + data = self.coordinator.data.get(self.entity_description.key) + self._value = self.entity_description.value(data) From 42024c1ed33352eda7ef3e3c55d5df3558e5dfcd Mon Sep 17 00:00:00 2001 From: jkuettner <12213711+jkuettner@users.noreply.github.com> Date: Fri, 4 Feb 2022 18:47:31 +0100 Subject: [PATCH 0270/1098] Fix "vevent" KeyError in caldav component again (#65685) * Fix "vevent" KeyError in caldav component again * code formatting --- homeassistant/components/caldav/calendar.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/caldav/calendar.py b/homeassistant/components/caldav/calendar.py index e9e1657065d02c..f44a59f18eb946 100644 --- a/homeassistant/components/caldav/calendar.py +++ b/homeassistant/components/caldav/calendar.py @@ -232,7 +232,11 @@ def update(self): new_events.append(new_event) elif _start_of_tomorrow <= start_dt: break - vevents = [event.instance.vevent for event in results + new_events] + vevents = [ + event.instance.vevent + for event in results + new_events + if hasattr(event.instance, "vevent") + ] # dtstart can be a date or datetime depending if the event lasts a # whole day. Convert everything to datetime to be able to sort it From 41ab12cb88741807a246bb296762d7c683a30b58 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 4 Feb 2022 18:55:11 +0100 Subject: [PATCH 0271/1098] Don't use shared session during recorder migration (#65672) --- .../components/recorder/migration.py | 297 ++++++++++-------- tests/components/recorder/test_migrate.py | 22 +- 2 files changed, 176 insertions(+), 143 deletions(-) diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index 32119b85597ac9..b49aee29ba18a2 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -68,20 +68,18 @@ def schema_is_current(current_version): def migrate_schema(instance, current_version): """Check if the schema needs to be upgraded.""" - with session_scope(session=instance.get_session()) as session: - _LOGGER.warning( - "Database is about to upgrade. Schema version: %s", current_version - ) - for version in range(current_version, SCHEMA_VERSION): - new_version = version + 1 - _LOGGER.info("Upgrading recorder db schema to version %s", new_version) - _apply_update(instance, session, new_version, current_version) + _LOGGER.warning("Database is about to upgrade. Schema version: %s", current_version) + for version in range(current_version, SCHEMA_VERSION): + new_version = version + 1 + _LOGGER.info("Upgrading recorder db schema to version %s", new_version) + _apply_update(instance, new_version, current_version) + with session_scope(session=instance.get_session()) as session: session.add(SchemaChanges(schema_version=new_version)) - _LOGGER.info("Upgrade to version %s done", new_version) + _LOGGER.info("Upgrade to version %s done", new_version) -def _create_index(connection, table_name, index_name): +def _create_index(instance, table_name, index_name): """Create an index for the specified table. The index name should match the name given for the index @@ -103,7 +101,9 @@ def _create_index(connection, table_name, index_name): index_name, ) try: - index.create(connection) + with session_scope(session=instance.get_session()) as session: + connection = session.connection() + index.create(connection) except (InternalError, ProgrammingError, OperationalError) as err: raise_if_exception_missing_str(err, ["already exists", "duplicate"]) _LOGGER.warning( @@ -113,7 +113,7 @@ def _create_index(connection, table_name, index_name): _LOGGER.debug("Finished creating %s", index_name) -def _drop_index(connection, table_name, index_name): +def _drop_index(instance, table_name, index_name): """Drop an index from a specified table. There is no universal way to do something like `DROP INDEX IF EXISTS` @@ -129,7 +129,9 @@ def _drop_index(connection, table_name, index_name): # Engines like DB2/Oracle try: - connection.execute(text(f"DROP INDEX {index_name}")) + with session_scope(session=instance.get_session()) as session: + connection = session.connection() + connection.execute(text(f"DROP INDEX {index_name}")) except SQLAlchemyError: pass else: @@ -138,13 +140,15 @@ def _drop_index(connection, table_name, index_name): # Engines like SQLite, SQL Server if not success: try: - connection.execute( - text( - "DROP INDEX {table}.{index}".format( - index=index_name, table=table_name + with session_scope(session=instance.get_session()) as session: + connection = session.connection() + connection.execute( + text( + "DROP INDEX {table}.{index}".format( + index=index_name, table=table_name + ) ) ) - ) except SQLAlchemyError: pass else: @@ -153,13 +157,15 @@ def _drop_index(connection, table_name, index_name): if not success: # Engines like MySQL, MS Access try: - connection.execute( - text( - "DROP INDEX {index} ON {table}".format( - index=index_name, table=table_name + with session_scope(session=instance.get_session()) as session: + connection = session.connection() + connection.execute( + text( + "DROP INDEX {index} ON {table}".format( + index=index_name, table=table_name + ) ) ) - ) except SQLAlchemyError: pass else: @@ -184,7 +190,7 @@ def _drop_index(connection, table_name, index_name): ) -def _add_columns(connection, table_name, columns_def): +def _add_columns(instance, table_name, columns_def): """Add columns to a table.""" _LOGGER.warning( "Adding columns %s to table %s. Note: this can take several " @@ -197,14 +203,16 @@ def _add_columns(connection, table_name, columns_def): columns_def = [f"ADD {col_def}" for col_def in columns_def] try: - connection.execute( - text( - "ALTER TABLE {table} {columns_def}".format( - table=table_name, columns_def=", ".join(columns_def) + with session_scope(session=instance.get_session()) as session: + connection = session.connection() + connection.execute( + text( + "ALTER TABLE {table} {columns_def}".format( + table=table_name, columns_def=", ".join(columns_def) + ) ) ) - ) - return + return except (InternalError, OperationalError): # Some engines support adding all columns at once, # this error is when they don't @@ -212,13 +220,15 @@ def _add_columns(connection, table_name, columns_def): for column_def in columns_def: try: - connection.execute( - text( - "ALTER TABLE {table} {column_def}".format( - table=table_name, column_def=column_def + with session_scope(session=instance.get_session()) as session: + connection = session.connection() + connection.execute( + text( + "ALTER TABLE {table} {column_def}".format( + table=table_name, column_def=column_def + ) ) ) - ) except (InternalError, OperationalError) as err: raise_if_exception_missing_str(err, ["already exists", "duplicate"]) _LOGGER.warning( @@ -228,7 +238,7 @@ def _add_columns(connection, table_name, columns_def): ) -def _modify_columns(connection, engine, table_name, columns_def): +def _modify_columns(instance, engine, table_name, columns_def): """Modify columns in a table.""" if engine.dialect.name == "sqlite": _LOGGER.debug( @@ -261,33 +271,37 @@ def _modify_columns(connection, engine, table_name, columns_def): columns_def = [f"MODIFY {col_def}" for col_def in columns_def] try: - connection.execute( - text( - "ALTER TABLE {table} {columns_def}".format( - table=table_name, columns_def=", ".join(columns_def) + with session_scope(session=instance.get_session()) as session: + connection = session.connection() + connection.execute( + text( + "ALTER TABLE {table} {columns_def}".format( + table=table_name, columns_def=", ".join(columns_def) + ) ) ) - ) - return + return except (InternalError, OperationalError): _LOGGER.info("Unable to use quick column modify. Modifying 1 by 1") for column_def in columns_def: try: - connection.execute( - text( - "ALTER TABLE {table} {column_def}".format( - table=table_name, column_def=column_def + with session_scope(session=instance.get_session()) as session: + connection = session.connection() + connection.execute( + text( + "ALTER TABLE {table} {column_def}".format( + table=table_name, column_def=column_def + ) ) ) - ) except (InternalError, OperationalError): _LOGGER.exception( "Could not modify column %s in table %s", column_def, table_name ) -def _update_states_table_with_foreign_key_options(connection, engine): +def _update_states_table_with_foreign_key_options(instance, engine): """Add the options to foreign key constraints.""" inspector = sqlalchemy.inspect(engine) alters = [] @@ -316,17 +330,19 @@ def _update_states_table_with_foreign_key_options(connection, engine): for alter in alters: try: - connection.execute(DropConstraint(alter["old_fk"])) - for fkc in states_key_constraints: - if fkc.column_keys == alter["columns"]: - connection.execute(AddConstraint(fkc)) + with session_scope(session=instance.get_session()) as session: + connection = session.connection() + connection.execute(DropConstraint(alter["old_fk"])) + for fkc in states_key_constraints: + if fkc.column_keys == alter["columns"]: + connection.execute(AddConstraint(fkc)) except (InternalError, OperationalError): _LOGGER.exception( "Could not update foreign options in %s table", TABLE_STATES ) -def _drop_foreign_key_constraints(connection, engine, table, columns): +def _drop_foreign_key_constraints(instance, engine, table, columns): """Drop foreign key constraints for a table on specific columns.""" inspector = sqlalchemy.inspect(engine) drops = [] @@ -345,7 +361,9 @@ def _drop_foreign_key_constraints(connection, engine, table, columns): for drop in drops: try: - connection.execute(DropConstraint(drop)) + with session_scope(session=instance.get_session()) as session: + connection = session.connection() + connection.execute(DropConstraint(drop)) except (InternalError, OperationalError): _LOGGER.exception( "Could not drop foreign constraints in %s table on %s", @@ -354,17 +372,16 @@ def _drop_foreign_key_constraints(connection, engine, table, columns): ) -def _apply_update(instance, session, new_version, old_version): # noqa: C901 +def _apply_update(instance, new_version, old_version): # noqa: C901 """Perform operations to bring schema up to date.""" engine = instance.engine - connection = session.connection() if new_version == 1: - _create_index(connection, "events", "ix_events_time_fired") + _create_index(instance, "events", "ix_events_time_fired") elif new_version == 2: # Create compound start/end index for recorder_runs - _create_index(connection, "recorder_runs", "ix_recorder_runs_start_end") + _create_index(instance, "recorder_runs", "ix_recorder_runs_start_end") # Create indexes for states - _create_index(connection, "states", "ix_states_last_updated") + _create_index(instance, "states", "ix_states_last_updated") elif new_version == 3: # There used to be a new index here, but it was removed in version 4. pass @@ -374,41 +391,41 @@ def _apply_update(instance, session, new_version, old_version): # noqa: C901 if old_version == 3: # Remove index that was added in version 3 - _drop_index(connection, "states", "ix_states_created_domain") + _drop_index(instance, "states", "ix_states_created_domain") if old_version == 2: # Remove index that was added in version 2 - _drop_index(connection, "states", "ix_states_entity_id_created") + _drop_index(instance, "states", "ix_states_entity_id_created") # Remove indexes that were added in version 0 - _drop_index(connection, "states", "states__state_changes") - _drop_index(connection, "states", "states__significant_changes") - _drop_index(connection, "states", "ix_states_entity_id_created") + _drop_index(instance, "states", "states__state_changes") + _drop_index(instance, "states", "states__significant_changes") + _drop_index(instance, "states", "ix_states_entity_id_created") - _create_index(connection, "states", "ix_states_entity_id_last_updated") + _create_index(instance, "states", "ix_states_entity_id_last_updated") elif new_version == 5: # Create supporting index for States.event_id foreign key - _create_index(connection, "states", "ix_states_event_id") + _create_index(instance, "states", "ix_states_event_id") elif new_version == 6: _add_columns( - session, + instance, "events", ["context_id CHARACTER(36)", "context_user_id CHARACTER(36)"], ) - _create_index(connection, "events", "ix_events_context_id") - _create_index(connection, "events", "ix_events_context_user_id") + _create_index(instance, "events", "ix_events_context_id") + _create_index(instance, "events", "ix_events_context_user_id") _add_columns( - connection, + instance, "states", ["context_id CHARACTER(36)", "context_user_id CHARACTER(36)"], ) - _create_index(connection, "states", "ix_states_context_id") - _create_index(connection, "states", "ix_states_context_user_id") + _create_index(instance, "states", "ix_states_context_id") + _create_index(instance, "states", "ix_states_context_user_id") elif new_version == 7: - _create_index(connection, "states", "ix_states_entity_id") + _create_index(instance, "states", "ix_states_entity_id") elif new_version == 8: - _add_columns(connection, "events", ["context_parent_id CHARACTER(36)"]) - _add_columns(connection, "states", ["old_state_id INTEGER"]) - _create_index(connection, "events", "ix_events_context_parent_id") + _add_columns(instance, "events", ["context_parent_id CHARACTER(36)"]) + _add_columns(instance, "states", ["old_state_id INTEGER"]) + _create_index(instance, "events", "ix_events_context_parent_id") elif new_version == 9: # We now get the context from events with a join # since its always there on state_changed events @@ -418,36 +435,36 @@ def _apply_update(instance, session, new_version, old_version): # noqa: C901 # and we would have to move to something like # sqlalchemy alembic to make that work # - _drop_index(connection, "states", "ix_states_context_id") - _drop_index(connection, "states", "ix_states_context_user_id") + _drop_index(instance, "states", "ix_states_context_id") + _drop_index(instance, "states", "ix_states_context_user_id") # This index won't be there if they were not running # nightly but we don't treat that as a critical issue - _drop_index(connection, "states", "ix_states_context_parent_id") + _drop_index(instance, "states", "ix_states_context_parent_id") # Redundant keys on composite index: # We already have ix_states_entity_id_last_updated - _drop_index(connection, "states", "ix_states_entity_id") - _create_index(connection, "events", "ix_events_event_type_time_fired") - _drop_index(connection, "events", "ix_events_event_type") + _drop_index(instance, "states", "ix_states_entity_id") + _create_index(instance, "events", "ix_events_event_type_time_fired") + _drop_index(instance, "events", "ix_events_event_type") elif new_version == 10: # Now done in step 11 pass elif new_version == 11: - _create_index(connection, "states", "ix_states_old_state_id") - _update_states_table_with_foreign_key_options(connection, engine) + _create_index(instance, "states", "ix_states_old_state_id") + _update_states_table_with_foreign_key_options(instance, engine) elif new_version == 12: if engine.dialect.name == "mysql": - _modify_columns(connection, engine, "events", ["event_data LONGTEXT"]) - _modify_columns(connection, engine, "states", ["attributes LONGTEXT"]) + _modify_columns(instance, engine, "events", ["event_data LONGTEXT"]) + _modify_columns(instance, engine, "states", ["attributes LONGTEXT"]) elif new_version == 13: if engine.dialect.name == "mysql": _modify_columns( - connection, + instance, engine, "events", ["time_fired DATETIME(6)", "created DATETIME(6)"], ) _modify_columns( - connection, + instance, engine, "states", [ @@ -457,14 +474,12 @@ def _apply_update(instance, session, new_version, old_version): # noqa: C901 ], ) elif new_version == 14: - _modify_columns(connection, engine, "events", ["event_type VARCHAR(64)"]) + _modify_columns(instance, engine, "events", ["event_type VARCHAR(64)"]) elif new_version == 15: # This dropped the statistics table, done again in version 18. pass elif new_version == 16: - _drop_foreign_key_constraints( - connection, engine, TABLE_STATES, ["old_state_id"] - ) + _drop_foreign_key_constraints(instance, engine, TABLE_STATES, ["old_state_id"]) elif new_version == 17: # This dropped the statistics table, done again in version 18. pass @@ -489,12 +504,13 @@ def _apply_update(instance, session, new_version, old_version): # noqa: C901 elif new_version == 19: # This adds the statistic runs table, insert a fake run to prevent duplicating # statistics. - session.add(StatisticsRuns(start=get_start_time())) + with session_scope(session=instance.get_session()) as session: + session.add(StatisticsRuns(start=get_start_time())) elif new_version == 20: # This changed the precision of statistics from float to double if engine.dialect.name in ["mysql", "postgresql"]: _modify_columns( - connection, + instance, engine, "statistics", [ @@ -516,14 +532,16 @@ def _apply_update(instance, session, new_version, old_version): # noqa: C901 table, ) with contextlib.suppress(SQLAlchemyError): - connection.execute( - # Using LOCK=EXCLUSIVE to prevent the database from corrupting - # https://github.com/home-assistant/core/issues/56104 - text( - f"ALTER TABLE {table} CONVERT TO " - "CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci LOCK=EXCLUSIVE" + with session_scope(session=instance.get_session()) as session: + connection = session.connection() + connection.execute( + # Using LOCK=EXCLUSIVE to prevent the database from corrupting + # https://github.com/home-assistant/core/issues/56104 + text( + f"ALTER TABLE {table} CONVERT TO " + "CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci LOCK=EXCLUSIVE" + ) ) - ) elif new_version == 22: # Recreate the all statistics tables for Oracle DB with Identity columns # @@ -549,57 +567,64 @@ def _apply_update(instance, session, new_version, old_version): # noqa: C901 # Block 5-minute statistics for one hour from the last run, or it will overlap # with existing hourly statistics. Don't block on a database with no existing # statistics. - if session.query(Statistics.id).count() and ( - last_run_string := session.query(func.max(StatisticsRuns.start)).scalar() - ): - last_run_start_time = process_timestamp(last_run_string) - if last_run_start_time: - fake_start_time = last_run_start_time + timedelta(minutes=5) - while fake_start_time < last_run_start_time + timedelta(hours=1): - session.add(StatisticsRuns(start=fake_start_time)) - fake_start_time += timedelta(minutes=5) + with session_scope(session=instance.get_session()) as session: + if session.query(Statistics.id).count() and ( + last_run_string := session.query( + func.max(StatisticsRuns.start) + ).scalar() + ): + last_run_start_time = process_timestamp(last_run_string) + if last_run_start_time: + fake_start_time = last_run_start_time + timedelta(minutes=5) + while fake_start_time < last_run_start_time + timedelta(hours=1): + session.add(StatisticsRuns(start=fake_start_time)) + fake_start_time += timedelta(minutes=5) # When querying the database, be careful to only explicitly query for columns # which were present in schema version 21. If querying the table, SQLAlchemy # will refer to future columns. - for sum_statistic in session.query(StatisticsMeta.id).filter_by(has_sum=true()): - last_statistic = ( - session.query( - Statistics.start, - Statistics.last_reset, - Statistics.state, - Statistics.sum, - ) - .filter_by(metadata_id=sum_statistic.id) - .order_by(Statistics.start.desc()) - .first() - ) - if last_statistic: - session.add( - StatisticsShortTerm( - metadata_id=sum_statistic.id, - start=last_statistic.start, - last_reset=last_statistic.last_reset, - state=last_statistic.state, - sum=last_statistic.sum, + with session_scope(session=instance.get_session()) as session: + for sum_statistic in session.query(StatisticsMeta.id).filter_by( + has_sum=true() + ): + last_statistic = ( + session.query( + Statistics.start, + Statistics.last_reset, + Statistics.state, + Statistics.sum, ) + .filter_by(metadata_id=sum_statistic.id) + .order_by(Statistics.start.desc()) + .first() ) + if last_statistic: + session.add( + StatisticsShortTerm( + metadata_id=sum_statistic.id, + start=last_statistic.start, + last_reset=last_statistic.last_reset, + state=last_statistic.state, + sum=last_statistic.sum, + ) + ) elif new_version == 23: # Add name column to StatisticsMeta - _add_columns(session, "statistics_meta", ["name VARCHAR(255)"]) + _add_columns(instance, "statistics_meta", ["name VARCHAR(255)"]) elif new_version == 24: # Delete duplicated statistics - delete_duplicates(instance, session) + with session_scope(session=instance.get_session()) as session: + delete_duplicates(instance, session) # Recreate statistics indices to block duplicated statistics - _drop_index(connection, "statistics", "ix_statistics_statistic_id_start") - _create_index(connection, "statistics", "ix_statistics_statistic_id_start") + _drop_index(instance, "statistics", "ix_statistics_statistic_id_start") + _create_index(instance, "statistics", "ix_statistics_statistic_id_start") _drop_index( - connection, + instance, "statistics_short_term", "ix_statistics_short_term_statistic_id_start", ) _create_index( - connection, + instance, "statistics_short_term", "ix_statistics_short_term_statistic_id_start", ) diff --git a/tests/components/recorder/test_migrate.py b/tests/components/recorder/test_migrate.py index 5c8a1c556c9ff9..5e837eb36ac395 100644 --- a/tests/components/recorder/test_migrate.py +++ b/tests/components/recorder/test_migrate.py @@ -5,7 +5,7 @@ import sqlite3 import sys import threading -from unittest.mock import ANY, Mock, PropertyMock, call, patch +from unittest.mock import Mock, PropertyMock, call, patch import pytest from sqlalchemy import create_engine, text @@ -57,7 +57,7 @@ async def test_schema_update_calls(hass): assert recorder.util.async_migration_in_progress(hass) is False update.assert_has_calls( [ - call(hass.data[DATA_INSTANCE], ANY, version + 1, 0) + call(hass.data[DATA_INSTANCE], version + 1, 0) for version in range(0, models.SCHEMA_VERSION) ] ) @@ -309,7 +309,7 @@ def _instrument_migration(*args): def test_invalid_update(): """Test that an invalid new version raises an exception.""" with pytest.raises(ValueError): - migration._apply_update(Mock(), Mock(), -1, 0) + migration._apply_update(Mock(), -1, 0) @pytest.mark.parametrize( @@ -324,9 +324,13 @@ def test_invalid_update(): def test_modify_column(engine_type, substr): """Test that modify column generates the expected query.""" connection = Mock() + session = Mock() + session.connection = Mock(return_value=connection) + instance = Mock() + instance.get_session = Mock(return_value=session) engine = Mock() engine.dialect.name = engine_type - migration._modify_columns(connection, engine, "events", ["event_type VARCHAR(64)"]) + migration._modify_columns(instance, engine, "events", ["event_type VARCHAR(64)"]) if substr: assert substr in connection.execute.call_args[0][0].text else: @@ -338,8 +342,10 @@ def test_forgiving_add_column(): engine = create_engine("sqlite://", poolclass=StaticPool) with Session(engine) as session: session.execute(text("CREATE TABLE hello (id int)")) - migration._add_columns(session, "hello", ["context_id CHARACTER(36)"]) - migration._add_columns(session, "hello", ["context_id CHARACTER(36)"]) + instance = Mock() + instance.get_session = Mock(return_value=session) + migration._add_columns(instance, "hello", ["context_id CHARACTER(36)"]) + migration._add_columns(instance, "hello", ["context_id CHARACTER(36)"]) def test_forgiving_add_index(): @@ -347,7 +353,9 @@ def test_forgiving_add_index(): engine = create_engine("sqlite://", poolclass=StaticPool) models.Base.metadata.create_all(engine) with Session(engine) as session: - migration._create_index(session, "states", "ix_states_context_id") + instance = Mock() + instance.get_session = Mock(return_value=session) + migration._create_index(instance, "states", "ix_states_context_id") @pytest.mark.parametrize( From a6caf3f579461ed896c4eedcb2a65a84ba057504 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 4 Feb 2022 09:57:14 -0800 Subject: [PATCH 0272/1098] Call out 3rd party containers more clearly (#65684) --- homeassistant/helpers/system_info.py | 5 ++++- tests/helpers/test_system_info.py | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/helpers/system_info.py b/homeassistant/helpers/system_info.py index e137d0f673ef67..a551c6e3b9e9ad 100644 --- a/homeassistant/helpers/system_info.py +++ b/homeassistant/helpers/system_info.py @@ -41,8 +41,11 @@ async def async_get_system_info(hass: HomeAssistant) -> dict[str, Any]: # Determine installation type on current data if info_object["docker"]: - if info_object["user"] == "root": + if info_object["user"] == "root" and os.path.isfile("/OFFICIAL_IMAGE"): info_object["installation_type"] = "Home Assistant Container" + else: + info_object["installation_type"] = "Unsupported Third Party Container" + elif is_virtual_env(): info_object["installation_type"] = "Home Assistant Core" diff --git a/tests/helpers/test_system_info.py b/tests/helpers/test_system_info.py index f4cb70f421ade0..e4aba5fbb243f1 100644 --- a/tests/helpers/test_system_info.py +++ b/tests/helpers/test_system_info.py @@ -18,15 +18,15 @@ async def test_container_installationtype(hass): """Test container installation type.""" with patch("platform.system", return_value="Linux"), patch( "os.path.isfile", return_value=True - ): + ), patch("homeassistant.helpers.system_info.getuser", return_value="root"): info = await hass.helpers.system_info.async_get_system_info() assert info["installation_type"] == "Home Assistant Container" with patch("platform.system", return_value="Linux"), patch( - "os.path.isfile", return_value=True + "os.path.isfile", side_effect=lambda file: file == "/.dockerenv" ), patch("homeassistant.helpers.system_info.getuser", return_value="user"): info = await hass.helpers.system_info.async_get_system_info() - assert info["installation_type"] == "Unknown" + assert info["installation_type"] == "Unsupported Third Party Container" async def test_getuser_keyerror(hass): From 8021b054480a90b7b25cc9ea8dfd65ea9f3923b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Fri, 4 Feb 2022 19:33:10 +0100 Subject: [PATCH 0273/1098] Allow selecting own repositories (#65695) --- .../components/github/config_flow.py | 50 ++++++++++++++----- homeassistant/components/github/manifest.json | 6 ++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/github/test_config_flow.py | 26 +++++++--- 5 files changed, 64 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/github/config_flow.py b/homeassistant/components/github/config_flow.py index f0e27283355ea1..9afbf80297c7a6 100644 --- a/homeassistant/components/github/config_flow.py +++ b/homeassistant/components/github/config_flow.py @@ -10,7 +10,6 @@ GitHubException, GitHubLoginDeviceModel, GitHubLoginOauthModel, - GitHubRepositoryModel, ) from aiogithubapi.const import OAUTH_USER_LOGIN import voluptuous as vol @@ -34,11 +33,12 @@ ) -async def starred_repositories(hass: HomeAssistant, access_token: str) -> list[str]: - """Return a list of repositories that the user has starred.""" +async def get_repositories(hass: HomeAssistant, access_token: str) -> list[str]: + """Return a list of repositories that the user owns or has starred.""" client = GitHubAPI(token=access_token, session=async_get_clientsession(hass)) + repositories = set() - async def _get_starred() -> list[GitHubRepositoryModel] | None: + async def _get_starred_repositories() -> None: response = await client.user.starred(**{"params": {"per_page": 100}}) if not response.is_last_page: results = await asyncio.gather( @@ -54,16 +54,44 @@ async def _get_starred() -> list[GitHubRepositoryModel] | None: for result in results: response.data.extend(result.data) - return response.data + repositories.update(response.data) + + async def _get_personal_repositories() -> None: + response = await client.user.repos(**{"params": {"per_page": 100}}) + if not response.is_last_page: + results = await asyncio.gather( + *( + client.user.repos( + **{"params": {"per_page": 100, "page": page_number}}, + ) + for page_number in range( + response.next_page_number, response.last_page_number + 1 + ) + ) + ) + for result in results: + response.data.extend(result.data) + + repositories.update(response.data) try: - result = await _get_starred() + await asyncio.gather( + *( + _get_starred_repositories(), + _get_personal_repositories(), + ) + ) + except GitHubException: return DEFAULT_REPOSITORIES - if not result or len(result) == 0: + if len(repositories) == 0: return DEFAULT_REPOSITORIES - return sorted((repo.full_name for repo in result), key=str.casefold) + + return sorted( + (repo.full_name for repo in repositories), + key=str.casefold, + ) class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -153,9 +181,7 @@ async def async_step_repositories( assert self._login is not None if not user_input: - repositories = await starred_repositories( - self.hass, self._login.access_token - ) + repositories = await get_repositories(self.hass, self._login.access_token) return self.async_show_form( step_id="repositories", data_schema=vol.Schema( @@ -205,7 +231,7 @@ async def async_step_init( configured_repositories: list[str] = self.config_entry.options[ CONF_REPOSITORIES ] - repositories = await starred_repositories( + repositories = await get_repositories( self.hass, self.config_entry.data[CONF_ACCESS_TOKEN] ) diff --git a/homeassistant/components/github/manifest.json b/homeassistant/components/github/manifest.json index 7a23156759d239..79e792aa7d87e0 100644 --- a/homeassistant/components/github/manifest.json +++ b/homeassistant/components/github/manifest.json @@ -3,7 +3,7 @@ "name": "GitHub", "documentation": "https://www.home-assistant.io/integrations/github", "requirements": [ - "aiogithubapi==22.1.0" + "aiogithubapi==22.2.0" ], "codeowners": [ "@timmo001", @@ -11,5 +11,7 @@ ], "iot_class": "cloud_polling", "config_flow": true, - "loggers": ["aiogithubapi"] + "loggers": [ + "aiogithubapi" + ] } \ No newline at end of file diff --git a/requirements_all.txt b/requirements_all.txt index 1da5ead8fe0b99..25b544b591f60c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -175,7 +175,7 @@ aioflo==2021.11.0 aioftp==0.12.0 # homeassistant.components.github -aiogithubapi==22.1.0 +aiogithubapi==22.2.0 # homeassistant.components.guardian aioguardian==2021.11.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9a296d256ce318..cbc1398208c258 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -125,7 +125,7 @@ aioesphomeapi==10.8.1 aioflo==2021.11.0 # homeassistant.components.github -aiogithubapi==22.1.0 +aiogithubapi==22.2.0 # homeassistant.components.guardian aioguardian==2021.11.0 diff --git a/tests/components/github/test_config_flow.py b/tests/components/github/test_config_flow.py index dad974726209ee..2bf0fac209ff40 100644 --- a/tests/components/github/test_config_flow.py +++ b/tests/components/github/test_config_flow.py @@ -4,7 +4,7 @@ from aiogithubapi import GitHubException from homeassistant import config_entries -from homeassistant.components.github.config_flow import starred_repositories +from homeassistant.components.github.config_flow import get_repositories from homeassistant.components.github.const import ( CONF_ACCESS_TOKEN, CONF_REPOSITORIES, @@ -161,11 +161,19 @@ async def test_starred_pagination_with_paginated_result(hass: HomeAssistant) -> last_page_number=2, data=[MagicMock(full_name="home-assistant/core")], ) - ) + ), + repos=AsyncMock( + return_value=MagicMock( + is_last_page=False, + next_page_number=2, + last_page_number=2, + data=[MagicMock(full_name="awesome/reposiotry")], + ) + ), ) ), ): - repos = await starred_repositories(hass, MOCK_ACCESS_TOKEN) + repos = await get_repositories(hass, MOCK_ACCESS_TOKEN) assert len(repos) == 2 assert repos[-1] == DEFAULT_REPOSITORIES[0] @@ -182,11 +190,17 @@ async def test_starred_pagination_with_no_starred(hass: HomeAssistant) -> None: is_last_page=True, data=[], ) - ) + ), + repos=AsyncMock( + return_value=MagicMock( + is_last_page=True, + data=[], + ) + ), ) ), ): - repos = await starred_repositories(hass, MOCK_ACCESS_TOKEN) + repos = await get_repositories(hass, MOCK_ACCESS_TOKEN) assert len(repos) == 2 assert repos == DEFAULT_REPOSITORIES @@ -200,7 +214,7 @@ async def test_starred_pagination_with_exception(hass: HomeAssistant) -> None: user=MagicMock(starred=AsyncMock(side_effect=GitHubException("Error"))) ), ): - repos = await starred_repositories(hass, MOCK_ACCESS_TOKEN) + repos = await get_repositories(hass, MOCK_ACCESS_TOKEN) assert len(repos) == 2 assert repos == DEFAULT_REPOSITORIES From 2a8797ae3fa206017279b40cee06175e70a5000d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 4 Feb 2022 10:43:06 -0800 Subject: [PATCH 0274/1098] Move scene and button restore to internal hook (#65696) --- homeassistant/components/button/__init__.py | 3 ++- homeassistant/components/scene/__init__.py | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/button/__init__.py b/homeassistant/components/button/__init__.py index 2e9a8c05163f8b..d0e27662d4174c 100644 --- a/homeassistant/components/button/__init__.py +++ b/homeassistant/components/button/__init__.py @@ -113,8 +113,9 @@ async def _async_press_action(self) -> None: self.async_write_ha_state() await self.async_press() - async def async_added_to_hass(self) -> None: + async def async_internal_added_to_hass(self) -> None: """Call when the button is added to hass.""" + await super().async_internal_added_to_hass() state = await self.async_get_last_state() if state is not None and state.state is not None: self.__last_pressed = dt_util.parse_datetime(state.state) diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py index 774aaad0ee46df..846c0fbc7c68d6 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -113,8 +113,9 @@ async def _async_activate(self, **kwargs: Any) -> None: self.async_write_ha_state() await self.async_activate(**kwargs) - async def async_added_to_hass(self) -> None: - """Call when the button is added to hass.""" + async def async_internal_added_to_hass(self) -> None: + """Call when the scene is added to hass.""" + await super().async_internal_added_to_hass() state = await self.async_get_last_state() if state is not None and state.state is not None: self.__last_activated = state.state From 020953e9437400c27d9dc9c6dc61b3a374203d8a Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 4 Feb 2022 19:55:28 +0100 Subject: [PATCH 0275/1098] Improve recorder migration for PostgreSQL when columns already exist (#65680) --- homeassistant/components/recorder/migration.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index b49aee29ba18a2..cb94018b1b119d 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -104,7 +104,7 @@ def _create_index(instance, table_name, index_name): with session_scope(session=instance.get_session()) as session: connection = session.connection() index.create(connection) - except (InternalError, ProgrammingError, OperationalError) as err: + except (InternalError, OperationalError, ProgrammingError) as err: raise_if_exception_missing_str(err, ["already exists", "duplicate"]) _LOGGER.warning( "Index %s already exists on %s, continuing", index_name, table_name @@ -213,7 +213,7 @@ def _add_columns(instance, table_name, columns_def): ) ) return - except (InternalError, OperationalError): + except (InternalError, OperationalError, ProgrammingError): # Some engines support adding all columns at once, # this error is when they don't _LOGGER.info("Unable to use quick column add. Adding 1 by 1") @@ -229,7 +229,7 @@ def _add_columns(instance, table_name, columns_def): ) ) ) - except (InternalError, OperationalError) as err: + except (InternalError, OperationalError, ProgrammingError) as err: raise_if_exception_missing_str(err, ["already exists", "duplicate"]) _LOGGER.warning( "Column %s already exists on %s, continuing", From 8574ee04ba42b6ed3a81a086789514abacac2f56 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 4 Feb 2022 10:55:45 -0800 Subject: [PATCH 0276/1098] Fix passing a string to device registry disabled_by (#65701) --- homeassistant/components/config/device_registry.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/config/device_registry.py b/homeassistant/components/config/device_registry.py index 50d56915dd4fa2..5e7c2ef193851e 100644 --- a/homeassistant/components/config/device_registry.py +++ b/homeassistant/components/config/device_registry.py @@ -62,6 +62,9 @@ async def websocket_update_device(hass, connection, msg): msg.pop("type") msg_id = msg.pop("id") + if "disabled_by" in msg: + msg["disabled_by"] = DeviceEntryDisabler(msg["disabled_by"]) + entry = registry.async_update_device(**msg) connection.send_message(websocket_api.result_message(msg_id, _entry_dict(entry))) From d279211f0c0cd3d886f92952dbb337cd4868f13e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 4 Feb 2022 11:11:21 -0800 Subject: [PATCH 0277/1098] Fix tuya diagnostics mutating cached state objects (#65708) --- homeassistant/components/tuya/diagnostics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/tuya/diagnostics.py b/homeassistant/components/tuya/diagnostics.py index f0e5ed2852faef..67bbad0aceb5fc 100644 --- a/homeassistant/components/tuya/diagnostics.py +++ b/homeassistant/components/tuya/diagnostics.py @@ -157,7 +157,7 @@ def _async_device_as_dict(hass: HomeAssistant, device: TuyaDevice) -> dict[str, state = hass.states.get(entity_entry.entity_id) state_dict = None if state: - state_dict = state.as_dict() + state_dict = dict(state.as_dict()) # Redact the `entity_picture` attribute as it contains a token. if "entity_picture" in state_dict["attributes"]: From fe05d6680c0793f9e25dbbd5443bf0024df72b13 Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Fri, 4 Feb 2022 11:13:08 -0800 Subject: [PATCH 0278/1098] Bump androidtv to 0.0.63 (fix MAC issues) (#65615) --- .../components/androidtv/config_flow.py | 8 ++ .../components/androidtv/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/androidtv/patchers.py | 12 ++ .../components/androidtv/test_media_player.py | 122 +++++++++++------- 6 files changed, 99 insertions(+), 49 deletions(-) diff --git a/homeassistant/components/androidtv/config_flow.py b/homeassistant/components/androidtv/config_flow.py index c346378fbc221b..0ec37fdeb6faa8 100644 --- a/homeassistant/components/androidtv/config_flow.py +++ b/homeassistant/components/androidtv/config_flow.py @@ -124,6 +124,14 @@ async def _async_check_connection(self, user_input): return RESULT_CONN_ERROR, None dev_prop = aftv.device_properties + _LOGGER.info( + "Android TV at %s: %s = %r, %s = %r", + user_input[CONF_HOST], + PROP_ETHMAC, + dev_prop.get(PROP_ETHMAC), + PROP_WIFIMAC, + dev_prop.get(PROP_WIFIMAC), + ) unique_id = format_mac( dev_prop.get(PROP_ETHMAC) or dev_prop.get(PROP_WIFIMAC, "") ) diff --git a/homeassistant/components/androidtv/manifest.json b/homeassistant/components/androidtv/manifest.json index 37e0ae485c6f53..cd8e86a42a26e0 100644 --- a/homeassistant/components/androidtv/manifest.json +++ b/homeassistant/components/androidtv/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/androidtv", "requirements": [ "adb-shell[async]==0.4.0", - "androidtv[async]==0.0.62", + "androidtv[async]==0.0.63", "pure-python-adb[async]==0.3.0.dev0" ], "codeowners": ["@JeffLIrion", "@ollo69"], diff --git a/requirements_all.txt b/requirements_all.txt index 25b544b591f60c..52f3b1eb58b0b4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -311,7 +311,7 @@ ambiclimate==0.2.1 amcrest==1.9.3 # homeassistant.components.androidtv -androidtv[async]==0.0.62 +androidtv[async]==0.0.63 # homeassistant.components.anel_pwrctrl anel_pwrctrl-homeassistant==0.0.1.dev2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cbc1398208c258..946187fec5f8a7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -237,7 +237,7 @@ amberelectric==1.0.3 ambiclimate==0.2.1 # homeassistant.components.androidtv -androidtv[async]==0.0.62 +androidtv[async]==0.0.63 # homeassistant.components.apns apns2==0.3.0 diff --git a/tests/components/androidtv/patchers.py b/tests/components/androidtv/patchers.py index c92ac11ba4bcac..4411945c71b144 100644 --- a/tests/components/androidtv/patchers.py +++ b/tests/components/androidtv/patchers.py @@ -185,3 +185,15 @@ def patch_androidtv_update( "androidtv.androidtv.androidtv_async.AndroidTVAsync.update", side_effect=ZeroDivisionError, ) + +PATCH_DEVICE_PROPERTIES = patch( + "androidtv.basetv.basetv_async.BaseTVAsync.get_device_properties", + return_value={ + "manufacturer": "a", + "model": "b", + "serialno": "c", + "sw_version": "d", + "wifimac": "ab:cd:ef:gh:ij:kl", + "ethmac": None, + }, +) diff --git a/tests/components/androidtv/test_media_player.py b/tests/components/androidtv/test_media_player.py index 5326e48f7b98fd..e97de0fc928dea 100644 --- a/tests/components/androidtv/test_media_player.py +++ b/tests/components/androidtv/test_media_player.py @@ -157,8 +157,10 @@ async def test_setup_with_properties(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(response)[patch_key]: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + state = hass.states.get(entity_id) assert state is not None @@ -188,8 +190,9 @@ async def test_reconnect(hass, caplog, config): ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ patch_key ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) @@ -256,8 +259,10 @@ async def test_adb_shell_returns_none(hass, config): ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ patch_key ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None @@ -284,8 +289,10 @@ async def test_setup_with_adbkey(hass): ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ patch_key ], patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER, PATCH_ISFILE, PATCH_ACCESS: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None @@ -317,8 +324,10 @@ async def test_sources(hass, config0): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None @@ -395,8 +404,10 @@ async def _test_exclude_sources(hass, config0, expected_sources): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None @@ -475,8 +486,10 @@ async def _test_select_source(hass, config0, source, expected_arg, method_patch) with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None @@ -701,8 +714,10 @@ async def test_setup_fail(hass, config): ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ patch_key ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: - assert await hass.config_entries.async_setup(config_entry.entry_id) is False - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) is False + await hass.async_block_till_done() + await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is None @@ -718,8 +733,9 @@ async def test_adb_command(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patch( "androidtv.basetv.basetv_async.BaseTVAsync.adb_shell", return_value=response @@ -747,8 +763,9 @@ async def test_adb_command_unicode_decode_error(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patch( "androidtv.basetv.basetv_async.BaseTVAsync.adb_shell", @@ -776,8 +793,9 @@ async def test_adb_command_key(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patch( "androidtv.basetv.basetv_async.BaseTVAsync.adb_shell", return_value=response @@ -805,8 +823,9 @@ async def test_adb_command_get_properties(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patch( "androidtv.androidtv.androidtv_async.AndroidTVAsync.get_properties_dict", @@ -834,8 +853,9 @@ async def test_learn_sendevent(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patch( "androidtv.basetv.basetv_async.BaseTVAsync.learn_sendevent", @@ -862,8 +882,9 @@ async def test_update_lock_not_acquired(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: await hass.helpers.entity_component.async_update_entity(entity_id) @@ -897,8 +918,9 @@ async def test_download(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() # Failed download because path is not whitelisted with patch("androidtv.basetv.basetv_async.BaseTVAsync.adb_pull") as patch_pull: @@ -943,8 +965,9 @@ async def test_upload(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() # Failed upload because path is not whitelisted with patch("androidtv.basetv.basetv_async.BaseTVAsync.adb_push") as patch_push: @@ -987,8 +1010,9 @@ async def test_androidtv_volume_set(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patch( "androidtv.basetv.basetv_async.BaseTVAsync.set_volume_level", return_value=0.5 @@ -1014,8 +1038,9 @@ async def test_get_image(hass, hass_ws_client): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patchers.patch_shell("11")[patch_key]: await hass.helpers.entity_component.async_update_entity(entity_id) @@ -1090,8 +1115,9 @@ async def test_services_androidtv(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[patch_key]: with patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patchers.patch_shell(SHELL_RESPONSE_STANDBY)[patch_key]: await _test_service( @@ -1136,8 +1162,9 @@ async def test_services_firetv(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[patch_key]: with patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patchers.patch_shell(SHELL_RESPONSE_STANDBY)[patch_key]: await _test_service(hass, entity_id, SERVICE_MEDIA_STOP, "back") @@ -1152,8 +1179,9 @@ async def test_volume_mute(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[patch_key]: with patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patchers.patch_shell(SHELL_RESPONSE_STANDBY)[patch_key]: service_data = {ATTR_ENTITY_ID: entity_id, ATTR_MEDIA_VOLUME_MUTED: True} @@ -1196,8 +1224,9 @@ async def test_connection_closed_on_ha_stop(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patch( "androidtv.androidtv.androidtv_async.AndroidTVAsync.adb_close" @@ -1220,8 +1249,9 @@ async def test_exception(hass): ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ patch_key ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + with patchers.PATCH_DEVICE_PROPERTIES: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) From 1f8e8926fea63f86e41bf5de515a14a5a5204beb Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 4 Feb 2022 20:31:12 +0100 Subject: [PATCH 0279/1098] Only remove duplicated statistics on error (#65653) --- .../components/recorder/migration.py | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index cb94018b1b119d..b8f15a811db7f2 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -6,6 +6,7 @@ import sqlalchemy from sqlalchemy import ForeignKeyConstraint, MetaData, Table, func, text from sqlalchemy.exc import ( + DatabaseError, InternalError, OperationalError, ProgrammingError, @@ -612,22 +613,31 @@ def _apply_update(instance, new_version, old_version): # noqa: C901 # Add name column to StatisticsMeta _add_columns(instance, "statistics_meta", ["name VARCHAR(255)"]) elif new_version == 24: - # Delete duplicated statistics - with session_scope(session=instance.get_session()) as session: - delete_duplicates(instance, session) # Recreate statistics indices to block duplicated statistics _drop_index(instance, "statistics", "ix_statistics_statistic_id_start") - _create_index(instance, "statistics", "ix_statistics_statistic_id_start") _drop_index( instance, "statistics_short_term", "ix_statistics_short_term_statistic_id_start", ) - _create_index( - instance, - "statistics_short_term", - "ix_statistics_short_term_statistic_id_start", - ) + try: + _create_index(instance, "statistics", "ix_statistics_statistic_id_start") + _create_index( + instance, + "statistics_short_term", + "ix_statistics_short_term_statistic_id_start", + ) + except DatabaseError: + # There may be duplicated statistics entries, delete duplicated statistics + # and try again + with session_scope(session=instance.get_session()) as session: + delete_duplicates(instance, session) + _create_index(instance, "statistics", "ix_statistics_statistic_id_start") + _create_index( + instance, + "statistics_short_term", + "ix_statistics_short_term_statistic_id_start", + ) else: raise ValueError(f"No schema migration defined for version {new_version}") From 26ff6d2aa02f3d6e69d169613e07f33a7007c1f0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 4 Feb 2022 13:36:30 -0600 Subject: [PATCH 0280/1098] Fix warm/cold reversal in rgbww_to_color_temperature (#65677) --- homeassistant/util/color.py | 26 ++++++- tests/components/light/test_init.py | 61 +++++++++++++++- tests/util/test_color.py | 105 ++++++++++++++++++++++++++-- 3 files changed, 180 insertions(+), 12 deletions(-) diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py index f308595adbdcd8..f055a5f32eb2ed 100644 --- a/homeassistant/util/color.py +++ b/homeassistant/util/color.py @@ -531,13 +531,33 @@ def color_temperature_to_rgb( def color_temperature_to_rgbww( temperature: int, brightness: int, min_mireds: int, max_mireds: int ) -> tuple[int, int, int, int, int]: - """Convert color temperature to rgbcw.""" + """Convert color temperature in mireds to rgbcw.""" mired_range = max_mireds - min_mireds - warm = ((max_mireds - temperature) / mired_range) * brightness - cold = brightness - warm + cold = ((max_mireds - temperature) / mired_range) * brightness + warm = brightness - cold return (0, 0, 0, round(cold), round(warm)) +def rgbww_to_color_temperature( + rgbww: tuple[int, int, int, int, int], min_mireds: int, max_mireds: int +) -> tuple[int, int]: + """Convert rgbcw to color temperature in mireds.""" + _, _, _, cold, warm = rgbww + return while_levels_to_color_temperature(cold, warm, min_mireds, max_mireds) + + +def while_levels_to_color_temperature( + cold: int, warm: int, min_mireds: int, max_mireds: int +) -> tuple[int, int]: + """Convert whites to color temperature in mireds.""" + brightness = warm / 255 + cold / 255 + if brightness == 0: + return (max_mireds, 0) + return round( + ((cold / 255 / brightness) * (min_mireds - max_mireds)) + max_mireds + ), min(255, round(brightness * 255)) + + def _clamp(color_component: float, minimum: float = 0, maximum: float = 255) -> float: """ Clamp the given color component value between the given min and max values. diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index a8a6ebc901e12e..640a4b4533bcb1 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -1899,7 +1899,8 @@ async def test_light_service_call_color_temp_conversion( _, data = entity0.last_call("turn_on") assert data == {"brightness": 255, "color_temp": 153} _, data = entity1.last_call("turn_on") - assert data == {"brightness": 255, "rgbww_color": (0, 0, 0, 0, 255)} + # Home Assistant uses RGBCW so a mireds of 153 should be maximum cold at 100% brightness so 255 + assert data == {"brightness": 255, "rgbww_color": (0, 0, 0, 255, 0)} await hass.services.async_call( "light", @@ -1917,7 +1918,63 @@ async def test_light_service_call_color_temp_conversion( _, data = entity0.last_call("turn_on") assert data == {"brightness": 128, "color_temp": 500} _, data = entity1.last_call("turn_on") - assert data == {"brightness": 128, "rgbww_color": (0, 0, 0, 128, 0)} + # Home Assistant uses RGBCW so a mireds of 500 should be maximum warm at 50% brightness so 128 + assert data == {"brightness": 128, "rgbww_color": (0, 0, 0, 0, 128)} + + await hass.services.async_call( + "light", + "turn_on", + { + "entity_id": [ + entity0.entity_id, + entity1.entity_id, + ], + "brightness_pct": 100, + "color_temp": 327, + }, + blocking=True, + ) + _, data = entity0.last_call("turn_on") + assert data == {"brightness": 255, "color_temp": 327} + _, data = entity1.last_call("turn_on") + # Home Assistant uses RGBCW so a mireds of 328 should be the midway point at 100% brightness so 127 (rounding), 128 + assert data == {"brightness": 255, "rgbww_color": (0, 0, 0, 127, 128)} + + await hass.services.async_call( + "light", + "turn_on", + { + "entity_id": [ + entity0.entity_id, + entity1.entity_id, + ], + "brightness_pct": 100, + "color_temp": 240, + }, + blocking=True, + ) + _, data = entity0.last_call("turn_on") + assert data == {"brightness": 255, "color_temp": 240} + _, data = entity1.last_call("turn_on") + assert data == {"brightness": 255, "rgbww_color": (0, 0, 0, 191, 64)} + + await hass.services.async_call( + "light", + "turn_on", + { + "entity_id": [ + entity0.entity_id, + entity1.entity_id, + ], + "brightness_pct": 100, + "color_temp": 410, + }, + blocking=True, + ) + _, data = entity0.last_call("turn_on") + assert data == {"brightness": 255, "color_temp": 410} + _, data = entity1.last_call("turn_on") + assert data == {"brightness": 255, "rgbww_color": (0, 0, 0, 66, 189)} async def test_light_service_call_white_mode(hass, enable_custom_integrations): diff --git a/tests/util/test_color.py b/tests/util/test_color.py index 3177878167673b..0b1b8f7d17f454 100644 --- a/tests/util/test_color.py +++ b/tests/util/test_color.py @@ -406,46 +406,137 @@ def test_color_rgb_to_rgbww(): def test_color_temperature_to_rgbww(): - """Test color temp to warm, cold conversion.""" + """Test color temp to warm, cold conversion. + + Temperature values must be in mireds + Home Assistant uses rgbcw for rgbww + """ assert color_util.color_temperature_to_rgbww(153, 255, 153, 500) == ( 0, 0, 0, - 0, 255, + 0, ) assert color_util.color_temperature_to_rgbww(153, 128, 153, 500) == ( 0, 0, 0, - 0, 128, + 0, ) assert color_util.color_temperature_to_rgbww(500, 255, 153, 500) == ( 0, 0, 0, - 255, 0, + 255, ) assert color_util.color_temperature_to_rgbww(500, 128, 153, 500) == ( 0, 0, 0, - 128, 0, + 128, ) assert color_util.color_temperature_to_rgbww(347, 255, 153, 500) == ( 0, 0, 0, - 143, 112, + 143, ) assert color_util.color_temperature_to_rgbww(347, 128, 153, 500) == ( 0, 0, 0, - 72, 56, + 72, + ) + + +def test_rgbww_to_color_temperature(): + """Test rgbww conversion to color temp. + + Temperature values must be in mireds + Home Assistant uses rgbcw for rgbww + """ + assert ( + color_util.rgbww_to_color_temperature( + ( + 0, + 0, + 0, + 255, + 0, + ), + 153, + 500, + ) + == (153, 255) + ) + assert color_util.rgbww_to_color_temperature((0, 0, 0, 128, 0), 153, 500) == ( + 153, + 128, + ) + assert color_util.rgbww_to_color_temperature((0, 0, 0, 0, 255), 153, 500) == ( + 500, + 255, + ) + assert color_util.rgbww_to_color_temperature((0, 0, 0, 0, 128), 153, 500) == ( + 500, + 128, + ) + assert color_util.rgbww_to_color_temperature((0, 0, 0, 112, 143), 153, 500) == ( + 348, + 255, + ) + assert color_util.rgbww_to_color_temperature((0, 0, 0, 56, 72), 153, 500) == ( + 348, + 128, + ) + assert color_util.rgbww_to_color_temperature((0, 0, 0, 0, 0), 153, 500) == ( + 500, + 0, + ) + + +def test_white_levels_to_color_temperature(): + """Test warm, cold conversion to color temp. + + Temperature values must be in mireds + Home Assistant uses rgbcw for rgbww + """ + assert ( + color_util.while_levels_to_color_temperature( + 255, + 0, + 153, + 500, + ) + == (153, 255) + ) + assert color_util.while_levels_to_color_temperature(128, 0, 153, 500) == ( + 153, + 128, + ) + assert color_util.while_levels_to_color_temperature(0, 255, 153, 500) == ( + 500, + 255, + ) + assert color_util.while_levels_to_color_temperature(0, 128, 153, 500) == ( + 500, + 128, + ) + assert color_util.while_levels_to_color_temperature(112, 143, 153, 500) == ( + 348, + 255, + ) + assert color_util.while_levels_to_color_temperature(56, 72, 153, 500) == ( + 348, + 128, + ) + assert color_util.while_levels_to_color_temperature(0, 0, 153, 500) == ( + 500, + 0, ) From bebf5009d6f1b3f0e3962ffc39a7f64c1ac39eb3 Mon Sep 17 00:00:00 2001 From: ollo69 <60491700+ollo69@users.noreply.github.com> Date: Fri, 4 Feb 2022 20:40:29 +0100 Subject: [PATCH 0281/1098] Add diagnostics support for Asuswrt (#65605) Co-authored-by: Paulus Schoutsen --- .coveragerc | 1 + .../components/asuswrt/diagnostics.py | 84 +++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 homeassistant/components/asuswrt/diagnostics.py diff --git a/.coveragerc b/.coveragerc index 03e99959fd4f80..f16666609bb9cc 100644 --- a/.coveragerc +++ b/.coveragerc @@ -80,6 +80,7 @@ omit = homeassistant/components/asterisk_cdr/mailbox.py homeassistant/components/asterisk_mbox/* homeassistant/components/asuswrt/__init__.py + homeassistant/components/asuswrt/diagnostics.py homeassistant/components/asuswrt/router.py homeassistant/components/aten_pe/* homeassistant/components/atome/* diff --git a/homeassistant/components/asuswrt/diagnostics.py b/homeassistant/components/asuswrt/diagnostics.py new file mode 100644 index 00000000000000..dc26bca551291a --- /dev/null +++ b/homeassistant/components/asuswrt/diagnostics.py @@ -0,0 +1,84 @@ +"""Diagnostics support for Asuswrt.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, entity_registry as er + +from .const import DATA_ASUSWRT, DOMAIN +from .router import AsusWrtRouter + +TO_REDACT = {CONF_PASSWORD, CONF_USERNAME} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, dict[str, Any]]: + """Return diagnostics for a config entry.""" + data = {"entry": async_redact_data(entry.as_dict(), TO_REDACT)} + + router: AsusWrtRouter = hass.data[DOMAIN][entry.entry_id][DATA_ASUSWRT] + + # Gather information how this AsusWrt device is represented in Home Assistant + device_registry = dr.async_get(hass) + entity_registry = er.async_get(hass) + hass_device = device_registry.async_get_device( + identifiers=router.device_info["identifiers"] + ) + if not hass_device: + return data + + data["device"] = { + "name": hass_device.name, + "name_by_user": hass_device.name_by_user, + "disabled": hass_device.disabled, + "disabled_by": hass_device.disabled_by, + "device_info": async_redact_data(dict(router.device_info), {"identifiers"}), + "entities": {}, + "tracked_devices": [], + } + + hass_entities = er.async_entries_for_device( + entity_registry, + device_id=hass_device.id, + include_disabled_entities=True, + ) + + for entity_entry in hass_entities: + state = hass.states.get(entity_entry.entity_id) + state_dict = None + if state: + state_dict = dict(state.as_dict()) + # The entity_id is already provided at root level. + state_dict.pop("entity_id", None) + # The context doesn't provide useful information in this case. + state_dict.pop("context", None) + + data["device"]["entities"][entity_entry.entity_id] = { + "name": entity_entry.name, + "original_name": entity_entry.original_name, + "disabled": entity_entry.disabled, + "disabled_by": entity_entry.disabled_by, + "entity_category": entity_entry.entity_category, + "device_class": entity_entry.device_class, + "original_device_class": entity_entry.original_device_class, + "icon": entity_entry.icon, + "original_icon": entity_entry.original_icon, + "unit_of_measurement": entity_entry.unit_of_measurement, + "state": state_dict, + } + + for device in router.devices.values(): + data["device"]["tracked_devices"].append( + { + "name": device.name or "Unknown device", + "ip_address": device.ip_address, + "last_activity": device.last_activity, + } + ) + + return data From 40857bda9098eb663676e0b643c335df0b2a34ed Mon Sep 17 00:00:00 2001 From: jjlawren Date: Fri, 4 Feb 2022 13:41:24 -0600 Subject: [PATCH 0282/1098] Use SSDP byebye to mark Sonos as offline (#65686) --- homeassistant/components/sonos/__init__.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py index 673e0ac4dfe460..5f3cc285517fb5 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -38,6 +38,7 @@ SONOS_CHECK_ACTIVITY, SONOS_REBOOTED, SONOS_SPEAKER_ACTIVITY, + SONOS_VANISHED, UPNP_ST, ) from .favorites import SonosFavorites @@ -274,14 +275,19 @@ async def _async_create_discovered_player(self, uid, discovered_ip, boot_seqnum) async def _async_ssdp_discovered_player( self, info: ssdp.SsdpServiceInfo, change: ssdp.SsdpChange ) -> None: - if change == ssdp.SsdpChange.BYEBYE: - return - uid = info.upnp[ssdp.ATTR_UPNP_UDN] if not uid.startswith("uuid:RINCON_"): return - uid = uid[5:] + + if change == ssdp.SsdpChange.BYEBYE: + _LOGGER.debug( + "ssdp:byebye received from %s", info.upnp.get("friendlyName", uid) + ) + reason = info.ssdp_headers.get("X-RINCON-REASON", "ssdp:byebye") + async_dispatcher_send(self.hass, f"{SONOS_VANISHED}-{uid}", reason) + return + discovered_ip = urlparse(info.ssdp_location).hostname boot_seqnum = info.ssdp_headers.get("X-RINCON-BOOTSEQ") self.async_discovered_player( From 2370dda1f011692d2528116c8aa133949ff12928 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 4 Feb 2022 20:47:01 +0100 Subject: [PATCH 0283/1098] Depend on diagnostics in the frontend (#65710) --- homeassistant/components/default_config/manifest.json | 1 - homeassistant/components/frontend/manifest.json | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/default_config/manifest.json b/homeassistant/components/default_config/manifest.json index 94f2aa2b9f601d..88f86034aeaaea 100644 --- a/homeassistant/components/default_config/manifest.json +++ b/homeassistant/components/default_config/manifest.json @@ -7,7 +7,6 @@ "cloud", "counter", "dhcp", - "diagnostics", "energy", "frontend", "history", diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 8a9b13648e962d..e29b27e90269ed 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -10,6 +10,7 @@ "auth", "config", "device_automation", + "diagnostics", "http", "lovelace", "onboarding", From 0d3bbfc9a73828425dc5202ffc08e9f477c511de Mon Sep 17 00:00:00 2001 From: Duco Sebel <74970928+DCSBL@users.noreply.github.com> Date: Fri, 4 Feb 2022 23:45:06 +0100 Subject: [PATCH 0284/1098] Fix `homewizard_energy` migration issues from #65594 (#65718) --- homeassistant/components/homewizard/config_flow.py | 11 ++++++----- tests/components/homewizard/test_init.py | 5 ++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/homewizard/config_flow.py b/homeassistant/components/homewizard/config_flow.py index 45a912fefec92a..c9f7d19a96a7fc 100644 --- a/homeassistant/components/homewizard/config_flow.py +++ b/homeassistant/components/homewizard/config_flow.py @@ -10,7 +10,7 @@ from voluptuous import Required, Schema from homeassistant import config_entries -from homeassistant.components import zeroconf +from homeassistant.components import persistent_notification, zeroconf from homeassistant.const import CONF_IP_ADDRESS from homeassistant.data_entry_flow import AbortFlow, FlowResult @@ -32,13 +32,14 @@ async def async_step_import(self, import_config: dict) -> FlowResult: """Handle a flow initiated by older `homewizard_energy` component.""" _LOGGER.debug("config_flow async_step_import") - self.hass.components.persistent_notification.async_create( - ( + persistent_notification.async_create( + self.hass, + title="HomeWizard Energy", + message=( "The custom integration of HomeWizard Energy has been migrated to core. " "You can safely remove the custom integration from the custom_integrations folder." ), - "HomeWizard Energy", - f"homewizard_energy_to_{DOMAIN}", + notification_id=f"homewizard_energy_to_{DOMAIN}", ) return await self.async_step_user({CONF_IP_ADDRESS: import_config["host"]}) diff --git a/tests/components/homewizard/test_init.py b/tests/components/homewizard/test_init.py index 87a02a446e90e8..02e0b5c0c23cc1 100644 --- a/tests/components/homewizard/test_init.py +++ b/tests/components/homewizard/test_init.py @@ -77,7 +77,7 @@ async def test_init_accepts_and_migrates_old_entry(aioclient_mock, hass): # Add original entry original_entry = MockConfigEntry( - domain=DOMAIN, + domain="homewizard_energy", data={CONF_IP_ADDRESS: "1.2.3.4"}, entry_id="old_id", ) @@ -122,6 +122,9 @@ async def test_init_accepts_and_migrates_old_entry(aioclient_mock, hass): ) imported_entry.add_to_hass(hass) + assert imported_entry.domain == DOMAIN + assert imported_entry.domain != original_entry.domain + # Add the entry_id to trigger migration with patch( "aiohwenergy.HomeWizardEnergy", From 5da923c34104c9bc8e894f136f07d53ce27d8580 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 4 Feb 2022 14:45:25 -0800 Subject: [PATCH 0285/1098] Protect state.as_dict from mutation (#65693) --- homeassistant/components/diagnostics/util.py | 14 ++++++-- homeassistant/components/esphome/__init__.py | 2 +- .../components/fan/reproduce_state.py | 7 ++-- .../input_select/reproduce_state.py | 7 ++-- homeassistant/components/knx/__init__.py | 2 +- .../components/light/reproduce_state.py | 7 ++-- homeassistant/components/renault/services.py | 4 +-- homeassistant/components/shelly/climate.py | 4 +-- homeassistant/core.py | 30 ++++++++-------- homeassistant/util/__init__.py | 5 ++- homeassistant/util/read_only_dict.py | 23 ++++++++++++ tests/common.py | 9 +++-- tests/test_core.py | 9 +++-- tests/util/test_read_only_dict.py | 36 +++++++++++++++++++ 14 files changed, 114 insertions(+), 45 deletions(-) create mode 100644 homeassistant/util/read_only_dict.py create mode 100644 tests/util/test_read_only_dict.py diff --git a/homeassistant/components/diagnostics/util.py b/homeassistant/components/diagnostics/util.py index 6154dd14bd21d8..84971ba89f1c24 100644 --- a/homeassistant/components/diagnostics/util.py +++ b/homeassistant/components/diagnostics/util.py @@ -2,7 +2,7 @@ from __future__ import annotations from collections.abc import Iterable, Mapping -from typing import Any, TypeVar, cast +from typing import Any, TypeVar, cast, overload from homeassistant.core import callback @@ -11,6 +11,16 @@ T = TypeVar("T") +@overload +def async_redact_data(data: Mapping, to_redact: Iterable[Any]) -> dict: # type: ignore + ... + + +@overload +def async_redact_data(data: T, to_redact: Iterable[Any]) -> T: + ... + + @callback def async_redact_data(data: T, to_redact: Iterable[Any]) -> T: """Redact sensitive data in a dict.""" @@ -25,7 +35,7 @@ def async_redact_data(data: T, to_redact: Iterable[Any]) -> T: for key, value in redacted.items(): if key in to_redact: redacted[key] = REDACTED - elif isinstance(value, dict): + elif isinstance(value, Mapping): redacted[key] = async_redact_data(value, to_redact) elif isinstance(value, list): redacted[key] = [async_redact_data(item, to_redact) for item in value] diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index ca6eca9ea9f14d..7d5736a2e689d2 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -457,7 +457,7 @@ async def _register_service( } async def execute_service(call: ServiceCall) -> None: - await entry_data.client.execute_service(service, call.data) # type: ignore[arg-type] + await entry_data.client.execute_service(service, call.data) hass.services.async_register( DOMAIN, service_name, execute_service, vol.Schema(schema) diff --git a/homeassistant/components/fan/reproduce_state.py b/homeassistant/components/fan/reproduce_state.py index c18e8352b24fcc..140fdfe9178857 100644 --- a/homeassistant/components/fan/reproduce_state.py +++ b/homeassistant/components/fan/reproduce_state.py @@ -2,9 +2,8 @@ from __future__ import annotations import asyncio -from collections.abc import Iterable +from collections.abc import Iterable, Mapping import logging -from types import MappingProxyType from typing import Any from homeassistant.const import ( @@ -112,8 +111,6 @@ async def async_reproduce_states( ) -def check_attr_equal( - attr1: MappingProxyType, attr2: MappingProxyType, attr_str: str -) -> bool: +def check_attr_equal(attr1: Mapping, attr2: Mapping, attr_str: str) -> bool: """Return true if the given attributes are equal.""" return attr1.get(attr_str) == attr2.get(attr_str) diff --git a/homeassistant/components/input_select/reproduce_state.py b/homeassistant/components/input_select/reproduce_state.py index 5a8bd4651c56d5..8ba16391d7e492 100644 --- a/homeassistant/components/input_select/reproduce_state.py +++ b/homeassistant/components/input_select/reproduce_state.py @@ -2,9 +2,8 @@ from __future__ import annotations import asyncio -from collections.abc import Iterable +from collections.abc import Iterable, Mapping import logging -from types import MappingProxyType from typing import Any from homeassistant.const import ATTR_ENTITY_ID, ATTR_OPTION @@ -80,8 +79,6 @@ async def async_reproduce_states( ) -def check_attr_equal( - attr1: MappingProxyType, attr2: MappingProxyType, attr_str: str -) -> bool: +def check_attr_equal(attr1: Mapping, attr2: Mapping, attr_str: str) -> bool: """Return true if the given attributes are equal.""" return attr1.get(attr_str) == attr2.get(attr_str) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index cdaf5c73e74ae3..02e54c9dd732e2 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -546,7 +546,7 @@ async def service_exposure_register_modify(self, call: ServiceCall) -> None: replaced_exposure.device.name, ) replaced_exposure.shutdown() - exposure = create_knx_exposure(self.hass, self.xknx, call.data) # type: ignore[arg-type] + exposure = create_knx_exposure(self.hass, self.xknx, call.data) self.service_exposures[group_address] = exposure _LOGGER.debug( "Service exposure_register registered exposure for '%s' - %s", diff --git a/homeassistant/components/light/reproduce_state.py b/homeassistant/components/light/reproduce_state.py index 9c382fcb7fac2b..d60e0a10f3a636 100644 --- a/homeassistant/components/light/reproduce_state.py +++ b/homeassistant/components/light/reproduce_state.py @@ -2,9 +2,8 @@ from __future__ import annotations import asyncio -from collections.abc import Iterable +from collections.abc import Iterable, Mapping import logging -from types import MappingProxyType from typing import Any, NamedTuple, cast from homeassistant.const import ( @@ -213,8 +212,6 @@ async def async_reproduce_states( ) -def check_attr_equal( - attr1: MappingProxyType, attr2: MappingProxyType, attr_str: str -) -> bool: +def check_attr_equal(attr1: Mapping, attr2: Mapping, attr_str: str) -> bool: """Return true if the given attributes are equal.""" return attr1.get(attr_str) == attr2.get(attr_str) diff --git a/homeassistant/components/renault/services.py b/homeassistant/components/renault/services.py index de69daefef6b51..91dc31d17f7e78 100644 --- a/homeassistant/components/renault/services.py +++ b/homeassistant/components/renault/services.py @@ -1,9 +1,9 @@ """Support for Renault services.""" from __future__ import annotations +from collections.abc import Mapping from datetime import datetime import logging -from types import MappingProxyType from typing import TYPE_CHECKING, Any import voluptuous as vol @@ -126,7 +126,7 @@ async def charge_start(service_call: ServiceCall) -> None: result = await proxy.vehicle.set_charge_start() LOGGER.debug("Charge start result: %s", result) - def get_vehicle_proxy(service_call_data: MappingProxyType) -> RenaultVehicleProxy: + def get_vehicle_proxy(service_call_data: Mapping) -> RenaultVehicleProxy: """Get vehicle from service_call data.""" device_registry = dr.async_get(hass) device_id = service_call_data[ATTR_VEHICLE] diff --git a/homeassistant/components/shelly/climate.py b/homeassistant/components/shelly/climate.py index 9a4eb342f71d53..2c81ecbe183ed9 100644 --- a/homeassistant/components/shelly/climate.py +++ b/homeassistant/components/shelly/climate.py @@ -2,8 +2,8 @@ from __future__ import annotations import asyncio +from collections.abc import Mapping import logging -from types import MappingProxyType from typing import Any, Final, cast from aioshelly.block_device import Block @@ -140,7 +140,7 @@ def __init__( self.control_result: dict[str, Any] | None = None self.device_block: Block | None = device_block self.last_state: State | None = None - self.last_state_attributes: MappingProxyType[str, Any] + self.last_state_attributes: Mapping[str, Any] self._preset_modes: list[str] = [] if self.block is not None and self.device_block is not None: diff --git a/homeassistant/core.py b/homeassistant/core.py index 30e98da763731c..0c44ea52f07572 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -24,7 +24,6 @@ import re import threading from time import monotonic -from types import MappingProxyType from typing import ( TYPE_CHECKING, Any, @@ -83,6 +82,7 @@ run_callback_threadsafe, shutdown_run_callback_threadsafe, ) +from .util.read_only_dict import ReadOnlyDict from .util.timeout import TimeoutManager from .util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM, UnitSystem @@ -1049,12 +1049,12 @@ def __init__( self.entity_id = entity_id.lower() self.state = state - self.attributes = MappingProxyType(attributes or {}) + self.attributes = ReadOnlyDict(attributes or {}) self.last_updated = last_updated or dt_util.utcnow() self.last_changed = last_changed or self.last_updated self.context = context or Context() self.domain, self.object_id = split_entity_id(self.entity_id) - self._as_dict: dict[str, Collection[Any]] | None = None + self._as_dict: ReadOnlyDict[str, Collection[Any]] | None = None @property def name(self) -> str: @@ -1063,7 +1063,7 @@ def name(self) -> str: "_", " " ) - def as_dict(self) -> dict[str, Collection[Any]]: + def as_dict(self) -> ReadOnlyDict[str, Collection[Any]]: """Return a dict representation of the State. Async friendly. @@ -1077,14 +1077,16 @@ def as_dict(self) -> dict[str, Collection[Any]]: last_updated_isoformat = last_changed_isoformat else: last_updated_isoformat = self.last_updated.isoformat() - self._as_dict = { - "entity_id": self.entity_id, - "state": self.state, - "attributes": dict(self.attributes), - "last_changed": last_changed_isoformat, - "last_updated": last_updated_isoformat, - "context": self.context.as_dict(), - } + self._as_dict = ReadOnlyDict( + { + "entity_id": self.entity_id, + "state": self.state, + "attributes": self.attributes, + "last_changed": last_changed_isoformat, + "last_updated": last_updated_isoformat, + "context": ReadOnlyDict(self.context.as_dict()), + } + ) return self._as_dict @classmethod @@ -1343,7 +1345,7 @@ def async_set( last_changed = None else: same_state = old_state.state == new_state and not force_update - same_attr = old_state.attributes == MappingProxyType(attributes) + same_attr = old_state.attributes == attributes last_changed = old_state.last_changed if same_state else None if same_state and same_attr: @@ -1404,7 +1406,7 @@ def __init__( """Initialize a service call.""" self.domain = domain.lower() self.service = service.lower() - self.data = MappingProxyType(data or {}) + self.data = ReadOnlyDict(data or {}) self.context = context or Context() def __repr__(self) -> str: diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py index 3c82639251a8bf..5c2882ec2e212f 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -2,14 +2,13 @@ from __future__ import annotations import asyncio -from collections.abc import Callable, Coroutine, Iterable, KeysView +from collections.abc import Callable, Coroutine, Iterable, KeysView, Mapping from datetime import datetime, timedelta from functools import wraps import random import re import string import threading -from types import MappingProxyType from typing import Any, TypeVar import slugify as unicode_slug @@ -53,7 +52,7 @@ def slugify(text: str | None, *, separator: str = "_") -> str: def repr_helper(inp: Any) -> str: """Help creating a more readable string representation of objects.""" - if isinstance(inp, (dict, MappingProxyType)): + if isinstance(inp, Mapping): return ", ".join( f"{repr_helper(key)}={repr_helper(item)}" for key, item in inp.items() ) diff --git a/homeassistant/util/read_only_dict.py b/homeassistant/util/read_only_dict.py new file mode 100644 index 00000000000000..f9cc949afdce35 --- /dev/null +++ b/homeassistant/util/read_only_dict.py @@ -0,0 +1,23 @@ +"""Read only dictionary.""" +from typing import Any, TypeVar + + +def _readonly(*args: Any, **kwargs: Any) -> Any: + """Raise an exception when a read only dict is modified.""" + raise RuntimeError("Cannot modify ReadOnlyDict") + + +Key = TypeVar("Key") +Value = TypeVar("Value") + + +class ReadOnlyDict(dict[Key, Value]): + """Read only version of dict that is compatible with dict types.""" + + __setitem__ = _readonly + __delitem__ = _readonly + pop = _readonly + popitem = _readonly + clear = _readonly + update = _readonly + setdefault = _readonly diff --git a/tests/common.py b/tests/common.py index 3ea4cde2cecb0e..3da0fcb98ddf43 100644 --- a/tests/common.py +++ b/tests/common.py @@ -931,9 +931,12 @@ def mock_restore_cache(hass, states): last_states = {} for state in states: restored_state = state.as_dict() - restored_state["attributes"] = json.loads( - json.dumps(restored_state["attributes"], cls=JSONEncoder) - ) + restored_state = { + **restored_state, + "attributes": json.loads( + json.dumps(restored_state["attributes"], cls=JSONEncoder) + ), + } last_states[state.entity_id] = restore_state.StoredState( State.from_dict(restored_state), now ) diff --git a/tests/test_core.py b/tests/test_core.py index c2d99967a4b4e4..afb5f507044160 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -39,6 +39,7 @@ ServiceNotFound, ) import homeassistant.util.dt as dt_util +from homeassistant.util.read_only_dict import ReadOnlyDict from homeassistant.util.unit_system import METRIC_SYSTEM from tests.common import async_capture_events, async_mock_service @@ -377,10 +378,14 @@ def test_state_as_dict(): "last_updated": last_time.isoformat(), "state": "on", } - assert state.as_dict() == expected + as_dict_1 = state.as_dict() + assert isinstance(as_dict_1, ReadOnlyDict) + assert isinstance(as_dict_1["attributes"], ReadOnlyDict) + assert isinstance(as_dict_1["context"], ReadOnlyDict) + assert as_dict_1 == expected # 2nd time to verify cache assert state.as_dict() == expected - assert state.as_dict() is state.as_dict() + assert state.as_dict() is as_dict_1 async def test_eventbus_add_remove_listener(hass): diff --git a/tests/util/test_read_only_dict.py b/tests/util/test_read_only_dict.py new file mode 100644 index 00000000000000..7528c843f5094b --- /dev/null +++ b/tests/util/test_read_only_dict.py @@ -0,0 +1,36 @@ +"""Test read only dictionary.""" +import json + +import pytest + +from homeassistant.util.read_only_dict import ReadOnlyDict + + +def test_read_only_dict(): + """Test read only dictionary.""" + data = ReadOnlyDict({"hello": "world"}) + + with pytest.raises(RuntimeError): + data["hello"] = "universe" + + with pytest.raises(RuntimeError): + data["other_key"] = "universe" + + with pytest.raises(RuntimeError): + data.pop("hello") + + with pytest.raises(RuntimeError): + data.popitem() + + with pytest.raises(RuntimeError): + data.clear() + + with pytest.raises(RuntimeError): + data.update({"yo": "yo"}) + + with pytest.raises(RuntimeError): + data.setdefault("yo", "yo") + + assert isinstance(data, dict) + assert dict(data) == {"hello": "world"} + assert json.dumps(data) == json.dumps({"hello": "world"}) From d8830aa4e0a5064096abe89d72312eb74b732f85 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sat, 5 Feb 2022 00:12:01 +0000 Subject: [PATCH 0286/1098] [ci skip] Translation update --- .../components/dnsip/translations/ca.json | 4 ++- .../components/dnsip/translations/de.json | 4 ++- .../components/dnsip/translations/el.json | 4 ++- .../components/dnsip/translations/et.json | 4 ++- .../components/dnsip/translations/hu.json | 4 ++- .../components/dnsip/translations/pt-BR.json | 4 ++- .../components/dnsip/translations/ru.json | 4 ++- .../dnsip/translations/zh-Hant.json | 4 ++- .../components/dsmr/translations/el.json | 23 +++++++++++++ .../forecast_solar/translations/el.json | 19 +++++++++++ .../nmap_tracker/translations/el.json | 3 ++ .../components/powerwall/translations/de.json | 14 +++++++- .../components/powerwall/translations/el.json | 6 ++++ .../components/powerwall/translations/ru.json | 14 +++++++- .../powerwall/translations/zh-Hant.json | 14 +++++++- .../components/zwave_js/translations/el.json | 34 +++++++++++++++++++ 16 files changed, 148 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/dnsip/translations/ca.json b/homeassistant/components/dnsip/translations/ca.json index 813dcdd694f171..f84a0e0a643071 100644 --- a/homeassistant/components/dnsip/translations/ca.json +++ b/homeassistant/components/dnsip/translations/ca.json @@ -6,7 +6,9 @@ "step": { "user": { "data": { - "hostname": "El nom d'amfitri\u00f3 al qual realitzar la consulta DNS" + "hostname": "El nom d'amfitri\u00f3 al qual realitzar la consulta DNS", + "resolver": "Resolutor de cerca IPV4", + "resolver_ipv6": "Resolutor de cerca IPV6" } } } diff --git a/homeassistant/components/dnsip/translations/de.json b/homeassistant/components/dnsip/translations/de.json index 76aef3a035d97f..9d82d5c76551cd 100644 --- a/homeassistant/components/dnsip/translations/de.json +++ b/homeassistant/components/dnsip/translations/de.json @@ -6,7 +6,9 @@ "step": { "user": { "data": { - "hostname": "Der Hostname, f\u00fcr den die DNS-Abfrage durchgef\u00fchrt werden soll" + "hostname": "Der Hostname, f\u00fcr den die DNS-Abfrage durchgef\u00fchrt werden soll", + "resolver": "Resolver f\u00fcr IPV4-Lookup", + "resolver_ipv6": "Resolver f\u00fcr IPV6-Lookup" } } } diff --git a/homeassistant/components/dnsip/translations/el.json b/homeassistant/components/dnsip/translations/el.json index 0e90341ef03be7..1faba4734f5f89 100644 --- a/homeassistant/components/dnsip/translations/el.json +++ b/homeassistant/components/dnsip/translations/el.json @@ -6,7 +6,9 @@ "step": { "user": { "data": { - "hostname": "\u03a4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03b3\u03b9\u03b1 \u03c4\u03bf \u03bf\u03c0\u03bf\u03af\u03bf \u03b8\u03b1 \u03b5\u03ba\u03c4\u03b5\u03bb\u03b5\u03c3\u03c4\u03b5\u03af \u03c4\u03bf \u03b5\u03c1\u03ce\u03c4\u03b7\u03bc\u03b1 DNS" + "hostname": "\u03a4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03b3\u03b9\u03b1 \u03c4\u03bf \u03bf\u03c0\u03bf\u03af\u03bf \u03b8\u03b1 \u03b5\u03ba\u03c4\u03b5\u03bb\u03b5\u03c3\u03c4\u03b5\u03af \u03c4\u03bf \u03b5\u03c1\u03ce\u03c4\u03b7\u03bc\u03b1 DNS", + "resolver": "\u0395\u03c0\u03b9\u03bb\u03cd\u03c4\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7 IPV4", + "resolver_ipv6": "\u0395\u03c0\u03b9\u03bb\u03cd\u03c4\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7 IPV6" } } } diff --git a/homeassistant/components/dnsip/translations/et.json b/homeassistant/components/dnsip/translations/et.json index 7518a12d2007eb..f49e83e9b2a34e 100644 --- a/homeassistant/components/dnsip/translations/et.json +++ b/homeassistant/components/dnsip/translations/et.json @@ -6,7 +6,9 @@ "step": { "user": { "data": { - "hostname": "Hostnimi mille kohta DNS p\u00e4ring tehakse" + "hostname": "Hostnimi mille kohta DNS p\u00e4ring tehakse", + "resolver": "IPV4 otsingu lahendaja", + "resolver_ipv6": "IPV6 otsingu lahendaja" } } } diff --git a/homeassistant/components/dnsip/translations/hu.json b/homeassistant/components/dnsip/translations/hu.json index e9dbb39a609ea6..bb60366aa86c61 100644 --- a/homeassistant/components/dnsip/translations/hu.json +++ b/homeassistant/components/dnsip/translations/hu.json @@ -6,7 +6,9 @@ "step": { "user": { "data": { - "hostname": "A gazdag\u00e9pn\u00e9v, amelyhez a DNS-lek\u00e9rdez\u00e9st v\u00e9gre kell hajtani" + "hostname": "A gazdag\u00e9pn\u00e9v, amelyhez a DNS-lek\u00e9rdez\u00e9st v\u00e9gre kell hajtani", + "resolver": "Felold\u00f3 az IPV4-keres\u00e9shez", + "resolver_ipv6": "Felold\u00f3 az IPV6-keres\u00e9shez" } } } diff --git a/homeassistant/components/dnsip/translations/pt-BR.json b/homeassistant/components/dnsip/translations/pt-BR.json index fca625597d4cca..3d294a43d3132c 100644 --- a/homeassistant/components/dnsip/translations/pt-BR.json +++ b/homeassistant/components/dnsip/translations/pt-BR.json @@ -6,7 +6,9 @@ "step": { "user": { "data": { - "hostname": "O hostname para o qual realizar a consulta DNS" + "hostname": "O hostname para o qual realizar a consulta DNS", + "resolver": "Resolvedor para consulta IPV4", + "resolver_ipv6": "Resolvedor para consulta IPV6" } } } diff --git a/homeassistant/components/dnsip/translations/ru.json b/homeassistant/components/dnsip/translations/ru.json index 3ced95d27ff824..a4421b566f60f7 100644 --- a/homeassistant/components/dnsip/translations/ru.json +++ b/homeassistant/components/dnsip/translations/ru.json @@ -6,7 +6,9 @@ "step": { "user": { "data": { - "hostname": "\u0414\u043e\u043c\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f, \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c\u0441\u044f DNS-\u0437\u0430\u043f\u0440\u043e\u0441" + "hostname": "\u0414\u043e\u043c\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f, \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c\u0441\u044f DNS-\u0437\u0430\u043f\u0440\u043e\u0441", + "resolver": "\u0420\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u0442\u0435\u043b\u044c \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 IPV4", + "resolver_ipv6": "\u0420\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u0442\u0435\u043b\u044c \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 IPV6" } } } diff --git a/homeassistant/components/dnsip/translations/zh-Hant.json b/homeassistant/components/dnsip/translations/zh-Hant.json index 975dacd5c2aef2..5c46b1b0282ac2 100644 --- a/homeassistant/components/dnsip/translations/zh-Hant.json +++ b/homeassistant/components/dnsip/translations/zh-Hant.json @@ -6,7 +6,9 @@ "step": { "user": { "data": { - "hostname": "\u57f7\u884c DNS \u67e5\u8a62\u7684\u4e3b\u6a5f\u540d\u7a31" + "hostname": "\u57f7\u884c DNS \u67e5\u8a62\u7684\u4e3b\u6a5f\u540d\u7a31", + "resolver": "IPV4 \u89e3\u6790\u5668", + "resolver_ipv6": "IPV6 \u89e3\u6790\u5668" } } } diff --git a/homeassistant/components/dsmr/translations/el.json b/homeassistant/components/dsmr/translations/el.json index 2c314f33c2d83e..31af38ae14baa6 100644 --- a/homeassistant/components/dsmr/translations/el.json +++ b/homeassistant/components/dsmr/translations/el.json @@ -1,4 +1,27 @@ { + "config": { + "step": { + "setup_network": { + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "setup_serial": { + "data": { + "dsmr_version": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7\u03c2 DSMR", + "port": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" + }, + "title": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" + }, + "setup_serial_manual_path": { + "title": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae" + }, + "user": { + "data": { + "type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c4\u03cd\u03c0\u03bf\u03c5 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + } + } + }, "options": { "step": { "init": { diff --git a/homeassistant/components/forecast_solar/translations/el.json b/homeassistant/components/forecast_solar/translations/el.json index ac82268cef49bb..5230b14e1928ee 100644 --- a/homeassistant/components/forecast_solar/translations/el.json +++ b/homeassistant/components/forecast_solar/translations/el.json @@ -1,7 +1,26 @@ { + "config": { + "step": { + "user": { + "data": { + "azimuth": "\u0391\u03b6\u03b9\u03bc\u03bf\u03cd\u03b8\u03b9\u03bf (360 \u03bc\u03bf\u03af\u03c1\u03b5\u03c2, 0 = \u0392\u03bf\u03c1\u03c1\u03ac\u03c2, 90 = \u0391\u03bd\u03b1\u03c4\u03bf\u03bb\u03ae, 180 = \u039d\u03cc\u03c4\u03bf\u03c2, 270 = \u0394\u03cd\u03c3\u03b7)", + "declination": "\u0391\u03c0\u03cc\u03ba\u03bb\u03b9\u03c3\u03b7 (0 = \u03bf\u03c1\u03b9\u03b6\u03cc\u03bd\u03c4\u03b9\u03b1, 90 = \u03ba\u03b1\u03c4\u03b1\u03ba\u03cc\u03c1\u03c5\u03c6\u03b7)", + "modules power": "\u03a3\u03c5\u03bd\u03bf\u03bb\u03b9\u03ba\u03ae \u03bc\u03ad\u03b3\u03b9\u03c3\u03c4\u03b7 \u03b9\u03c3\u03c7\u03cd\u03c2 Watt \u03c4\u03c9\u03bd \u03b7\u03bb\u03b9\u03b1\u03ba\u03ce\u03bd \u03c3\u03b1\u03c2 \u03bc\u03bf\u03bd\u03ac\u03b4\u03c9\u03bd" + }, + "description": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 \u03c4\u03c9\u03bd \u03b7\u03bb\u03b9\u03b1\u03ba\u03ce\u03bd \u03c3\u03b1\u03c2 \u03c3\u03c5\u03bb\u03bb\u03b5\u03ba\u03c4\u03ce\u03bd. \u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7 \u03b5\u03ac\u03bd \u03ad\u03bd\u03b1 \u03c0\u03b5\u03b4\u03af\u03bf \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c3\u03b1\u03c6\u03ad\u03c2." + } + } + }, "options": { "step": { "init": { + "data": { + "api_key": "Forecast.Solar API Key (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", + "azimuth": "\u0391\u03b6\u03b9\u03bc\u03bf\u03cd\u03b8\u03b9\u03bf (360 \u03bc\u03bf\u03af\u03c1\u03b5\u03c2, 0 = \u0392\u03bf\u03c1\u03c1\u03ac\u03c2, 90 = \u0391\u03bd\u03b1\u03c4\u03bf\u03bb\u03ae, 180 = \u039d\u03cc\u03c4\u03bf\u03c2, 270 = \u0394\u03cd\u03c3\u03b7)", + "damping": "\u03a3\u03c5\u03bd\u03c4\u03b5\u03bb\u03b5\u03c3\u03c4\u03ae\u03c2 \u03b1\u03c0\u03cc\u03c3\u03b2\u03b5\u03c3\u03b7\u03c2: \u03c1\u03c5\u03b8\u03bc\u03af\u03b6\u03b5\u03b9 \u03c4\u03b1 \u03b1\u03c0\u03bf\u03c4\u03b5\u03bb\u03ad\u03c3\u03bc\u03b1\u03c4\u03b1 \u03c0\u03c1\u03c9\u03af \u03ba\u03b1\u03b9 \u03b2\u03c1\u03ac\u03b4\u03c5", + "declination": "\u0391\u03c0\u03cc\u03ba\u03bb\u03b9\u03c3\u03b7 (0 = \u03bf\u03c1\u03b9\u03b6\u03cc\u03bd\u03c4\u03b9\u03b1, 90 = \u03ba\u03b1\u03c4\u03b1\u03ba\u03cc\u03c1\u03c5\u03c6\u03b7)", + "modules power": "\u03a3\u03c5\u03bd\u03bf\u03bb\u03b9\u03ba\u03ae \u03bc\u03ad\u03b3\u03b9\u03c3\u03c4\u03b7 \u03b9\u03c3\u03c7\u03cd\u03c2 Watt \u03c4\u03c9\u03bd \u03b7\u03bb\u03b9\u03b1\u03ba\u03ce\u03bd \u03c3\u03b1\u03c2 \u03bc\u03bf\u03bd\u03ac\u03b4\u03c9\u03bd" + }, "description": "\u0391\u03c5\u03c4\u03ad\u03c2 \u03bf\u03b9 \u03c4\u03b9\u03bc\u03ad\u03c2 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03bf\u03c5\u03bd \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b1\u03c0\u03bf\u03c4\u03b5\u03bb\u03ad\u03c3\u03bc\u03b1\u03c4\u03bf\u03c2 Solar.Forecast. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7 \u03b5\u03ac\u03bd \u03ba\u03ac\u03c0\u03bf\u03b9\u03bf \u03c0\u03b5\u03b4\u03af\u03bf \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03b1\u03c6\u03ad\u03c2." } } diff --git a/homeassistant/components/nmap_tracker/translations/el.json b/homeassistant/components/nmap_tracker/translations/el.json index 873867c981965d..971cfedd05d403 100644 --- a/homeassistant/components/nmap_tracker/translations/el.json +++ b/homeassistant/components/nmap_tracker/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "invalid_hosts": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03b9 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03af \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ad\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/powerwall/translations/de.json b/homeassistant/components/powerwall/translations/de.json index 8ac8a1a5b1d6ee..17f852542080bf 100644 --- a/homeassistant/components/powerwall/translations/de.json +++ b/homeassistant/components/powerwall/translations/de.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { @@ -10,8 +11,19 @@ "unknown": "Unerwarteter Fehler", "wrong_version": "Deine Powerwall verwendet eine Softwareversion, die nicht unterst\u00fctzt wird. Bitte ziehe ein Upgrade in Betracht oder melde dieses Problem, damit es behoben werden kann." }, - "flow_title": "{ip_address}", + "flow_title": "{name} ({ip_address})", "step": { + "confirm_discovery": { + "description": "M\u00f6chtest du {name} ({ip_address}) einrichten?", + "title": "Powerwall verbinden" + }, + "reauth_confim": { + "data": { + "password": "Passwort" + }, + "description": "Das Kennwort ist in der Regel die letzten 5 Zeichen der Seriennummer des Backup Gateway und kann in der Tesla-App gefunden werden oder es sind die letzten 5 Zeichen des Kennworts, das sich in der T\u00fcr f\u00fcr Backup Gateway 2 befindet.", + "title": "Powerwall erneut authentifizieren" + }, "user": { "data": { "ip_address": "IP-Adresse", diff --git a/homeassistant/components/powerwall/translations/el.json b/homeassistant/components/powerwall/translations/el.json index b3943953621eb6..d3263b9c849ce3 100644 --- a/homeassistant/components/powerwall/translations/el.json +++ b/homeassistant/components/powerwall/translations/el.json @@ -5,6 +5,12 @@ }, "flow_title": "{ip_address}", "step": { + "confirm_discovery": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ( {ip_address});" + }, + "reauth_confim": { + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 powerwall" + }, "user": { "description": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03ae\u03b8\u03c9\u03c2 \u03bf\u03b9 \u03c4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03bf\u03b9 5 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b5\u03c2 \u03c4\u03bf\u03c5 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03bf\u03cd \u03b1\u03c1\u03b9\u03b8\u03bc\u03bf\u03cd \u03b3\u03b9\u03b1 \u03c4\u03bf Backup Gateway \u03ba\u03b1\u03b9 \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b2\u03c1\u03b5\u03b8\u03b5\u03af \u03c3\u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae Tesla \u03ae \u03bf\u03b9 \u03c4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03bf\u03b9 5 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b5\u03c2 \u03c4\u03bf\u03c5 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c0\u03bf\u03c5 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03bf \u03b5\u03c3\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03cc \u03c4\u03b7\u03c2 \u03c0\u03cc\u03c1\u03c4\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf Backup Gateway 2.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf powerwall" diff --git a/homeassistant/components/powerwall/translations/ru.json b/homeassistant/components/powerwall/translations/ru.json index f8299a59445aab..e96897ad1df260 100644 --- a/homeassistant/components/powerwall/translations/ru.json +++ b/homeassistant/components/powerwall/translations/ru.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." }, "error": { @@ -10,8 +11,19 @@ "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430.", "wrong_version": "\u0412\u0430\u0448 powerwall \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0432\u0435\u0440\u0441\u0438\u044e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u043e\u0433\u043e \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0438\u0442\u0435 \u043e\u0431 \u044d\u0442\u043e\u0439 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0435, \u0447\u0442\u043e\u0431\u044b \u0435\u0435 \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0440\u0435\u0448\u0438\u0442\u044c." }, - "flow_title": "{ip_address}", + "flow_title": "{name} ({ip_address})", "step": { + "confirm_discovery": { + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name} ({ip_address})?", + "title": "Tesla Powerwall" + }, + "reauth_confim": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u041f\u0430\u0440\u043e\u043b\u044c \u043e\u0431\u044b\u0447\u043d\u043e \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 5 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432 \u0441\u0435\u0440\u0438\u0439\u043d\u043e\u0433\u043e \u043d\u043e\u043c\u0435\u0440\u0430 \u0434\u043b\u044f Backup Gateway, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 Telsa; \u0438\u043b\u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 5 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432 \u043f\u0430\u0440\u043e\u043b\u044f, \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u043e\u0433\u043e \u0432\u043d\u0443\u0442\u0440\u0438 Backup Gateway 2.", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f" + }, "user": { "data": { "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441", diff --git a/homeassistant/components/powerwall/translations/zh-Hant.json b/homeassistant/components/powerwall/translations/zh-Hant.json index 21a2a4f2159535..0f3b63d4b45c07 100644 --- a/homeassistant/components/powerwall/translations/zh-Hant.json +++ b/homeassistant/components/powerwall/translations/zh-Hant.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" }, "error": { @@ -10,8 +11,19 @@ "unknown": "\u672a\u9810\u671f\u932f\u8aa4", "wrong_version": "\u4e0d\u652f\u63f4\u60a8\u6240\u4f7f\u7528\u7684 Powerwall \u7248\u672c\u3002\u8acb\u8003\u616e\u9032\u884c\u5347\u7d1a\u6216\u56de\u5831\u6b64\u554f\u984c\u3001\u4ee5\u671f\u554f\u984c\u53ef\u4ee5\u7372\u5f97\u89e3\u6c7a\u3002" }, - "flow_title": "{ip_address}", + "flow_title": "{name} ({ip_address})", "step": { + "confirm_discovery": { + "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name} ({ip_address})\uff1f", + "title": "\u9023\u7dda\u81f3 Powerwall" + }, + "reauth_confim": { + "data": { + "password": "\u5bc6\u78bc" + }, + "description": "\u5bc6\u78bc\u901a\u5e38\u70ba\u81f3\u5c11\u5099\u4efd\u9598\u9053\u5668\u5e8f\u865f\u7684\u6700\u5f8c\u4e94\u78bc\uff0c\u4e26\u4e14\u80fd\u5920\u65bc Telsa App \u4e2d\n\u627e\u5230\u3002\u6216\u8005\u70ba\u5099\u4efd\u9598\u9053\u5668 2 \u9580\u5167\u5074\u627e\u5230\u7684\u5bc6\u78bc\u6700\u5f8c\u4e94\u78bc\u3002", + "title": "\u91cd\u65b0\u8a8d\u8b49 Powerwall" + }, "user": { "data": { "ip_address": "IP \u4f4d\u5740", diff --git a/homeassistant/components/zwave_js/translations/el.json b/homeassistant/components/zwave_js/translations/el.json index 6617d107471d97..00539cbcb70a3a 100644 --- a/homeassistant/components/zwave_js/translations/el.json +++ b/homeassistant/components/zwave_js/translations/el.json @@ -52,5 +52,39 @@ "zwave_js.value_updated.config_parameter": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ae \u03c4\u03b9\u03bc\u03ae\u03c2 \u03c3\u03c4\u03b7\u03bd \u03c0\u03b1\u03c1\u03ac\u03bc\u03b5\u03c4\u03c1\u03bf config {subtype}", "zwave_js.value_updated.value": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ae \u03c4\u03b9\u03bc\u03ae\u03c2 \u03c3\u03b5 \u03c4\u03b9\u03bc\u03ae Z-Wave JS" } + }, + "options": { + "abort": { + "addon_get_discovery_info_failed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03bb\u03ae\u03c8\u03b7\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03b9\u03ce\u03bd \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS.", + "addon_info_failed": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03bb\u03ae\u03c8\u03b7 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03c9\u03bd \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03b9\u03ce\u03bd Z-Wave JS.", + "addon_install_failed": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS.", + "addon_set_config_failed": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03bf \u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03c4\u03b7\u03c2 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd JS Z-Wave.", + "addon_start_failed": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS.", + "different_device": "\u0397 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae USB \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b7 \u03af\u03b4\u03b9\u03b1 \u03bc\u03b5 \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03b7\u03b3\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7\u03bd \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2. \u0394\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03bd\u03ad\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03bd\u03ad\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae." + }, + "error": { + "invalid_ws_url": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL websocket" + }, + "progress": { + "install_addon": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03c0\u03b5\u03c1\u03b9\u03bc\u03ad\u03bd\u03b5\u03c4\u03b5 \u03bc\u03ad\u03c7\u03c1\u03b9 \u03bd\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03c9\u03b8\u03b5\u03af \u03b7 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS. \u0391\u03c5\u03c4\u03cc \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b4\u03b9\u03b1\u03c1\u03ba\u03ad\u03c3\u03b5\u03b9 \u03b1\u03c1\u03ba\u03b5\u03c4\u03ac \u03bb\u03b5\u03c0\u03c4\u03ac.", + "start_addon": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03c0\u03b5\u03c1\u03b9\u03bc\u03ad\u03bd\u03b5\u03c4\u03b5 \u03bc\u03ad\u03c7\u03c1\u03b9 \u03bd\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03c9\u03b8\u03b5\u03af \u03b7 \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS. \u0391\u03c5\u03c4\u03cc \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b4\u03b9\u03b1\u03c1\u03ba\u03ad\u03c3\u03b5\u03b9 \u03bc\u03b5\u03c1\u03b9\u03ba\u03ac \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1." + }, + "step": { + "configure_addon": { + "data": { + "emulate_hardware": "\u0395\u03be\u03bf\u03bc\u03bf\u03af\u03c9\u03c3\u03b7 \u03c5\u03bb\u03b9\u03ba\u03bf\u03cd" + } + }, + "on_supervisor": { + "data": { + "use_addon": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf Z-Wave JS Supervisor" + }, + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf Z-Wave JS Supervisor;", + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03ad\u03b8\u03bf\u03b4\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "start_addon": { + "title": "\u03a4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf Z-Wave JS \u03be\u03b5\u03ba\u03b9\u03bd\u03ac." + } + } } } \ No newline at end of file From 432d9a8f19454628d815152516cac0ee24f81420 Mon Sep 17 00:00:00 2001 From: Stephan Traub Date: Sat, 5 Feb 2022 01:20:21 +0100 Subject: [PATCH 0287/1098] Introduce wiz integration for the WiZ Platform (#44779) Co-authored-by: Marvin Wichmann Co-authored-by: J. Nick Koston Co-authored-by: Jan Stienstra <65826735+j-stienstra@users.noreply.github.com> Co-authored-by: Franck Nijhof Co-authored-by: Paulus Schoutsen Co-authored-by: Franck Nijhof Co-authored-by: Paulus Schoutsen --- .coveragerc | 3 + CODEOWNERS | 2 + homeassistant/components/wiz/__init__.py | 52 +++ homeassistant/components/wiz/config_flow.py | 52 +++ homeassistant/components/wiz/const.py | 4 + homeassistant/components/wiz/light.py | 348 ++++++++++++++++++ homeassistant/components/wiz/manifest.json | 13 + homeassistant/components/wiz/strings.json | 26 ++ .../components/wiz/translations/en.json | 26 ++ homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/wiz/__init__.py | 61 +++ tests/components/wiz/test_config_flow.py | 128 +++++++ 14 files changed, 722 insertions(+) create mode 100644 homeassistant/components/wiz/__init__.py create mode 100644 homeassistant/components/wiz/config_flow.py create mode 100644 homeassistant/components/wiz/const.py create mode 100644 homeassistant/components/wiz/light.py create mode 100644 homeassistant/components/wiz/manifest.json create mode 100644 homeassistant/components/wiz/strings.json create mode 100644 homeassistant/components/wiz/translations/en.json create mode 100644 tests/components/wiz/__init__.py create mode 100644 tests/components/wiz/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index f16666609bb9cc..a3e2294e5ab66e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1315,6 +1315,9 @@ omit = homeassistant/components/waze_travel_time/sensor.py homeassistant/components/wiffi/* homeassistant/components/wirelesstag/* + homeassistant/components/wiz/__init__.py + homeassistant/components/wiz/const.py + homeassistant/components/wiz/light.py homeassistant/components/wolflink/__init__.py homeassistant/components/wolflink/sensor.py homeassistant/components/wolflink/const.py diff --git a/CODEOWNERS b/CODEOWNERS index cb2573eedb824c..b7391dbc823a84 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1065,6 +1065,8 @@ tests/components/wilight/* @leofig-rj homeassistant/components/wirelesstag/* @sergeymaysak homeassistant/components/withings/* @vangorra tests/components/withings/* @vangorra +homeassistant/components/wiz/* @sbidy +tests/components/wiz/* @sbidy homeassistant/components/wled/* @frenck tests/components/wled/* @frenck homeassistant/components/wolflink/* @adamkrol93 diff --git a/homeassistant/components/wiz/__init__.py b/homeassistant/components/wiz/__init__.py new file mode 100644 index 00000000000000..ec0979877cc1fc --- /dev/null +++ b/homeassistant/components/wiz/__init__.py @@ -0,0 +1,52 @@ +"""WiZ Platform integration.""" +from dataclasses import dataclass +import logging + +from pywizlight import wizlight +from pywizlight.exceptions import WizLightConnectionError, WizLightTimeOutError + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_HOST +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +PLATFORMS = ["light"] + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up the wiz integration from a config entry.""" + ip_address = entry.data.get(CONF_HOST) + _LOGGER.debug("Get bulb with IP: %s", ip_address) + try: + bulb = wizlight(ip_address) + scenes = await bulb.getSupportedScenes() + await bulb.getMac() + except ( + WizLightTimeOutError, + WizLightConnectionError, + ConnectionRefusedError, + ) as err: + raise ConfigEntryNotReady from err + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = WizData(bulb=bulb, scenes=scenes) + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + hass.data[DOMAIN].pop(entry.entry_id) + return unload_ok + + +@dataclass +class WizData: + """Data for the wiz integration.""" + + bulb: wizlight + scenes: list diff --git a/homeassistant/components/wiz/config_flow.py b/homeassistant/components/wiz/config_flow.py new file mode 100644 index 00000000000000..dbe17adac00506 --- /dev/null +++ b/homeassistant/components/wiz/config_flow.py @@ -0,0 +1,52 @@ +"""Config flow for WiZ Platform.""" +import logging + +from pywizlight import wizlight +from pywizlight.exceptions import WizLightConnectionError, WizLightTimeOutError +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_HOST, CONF_NAME + +from .const import DEFAULT_NAME, DOMAIN + +_LOGGER = logging.getLogger(__name__) + +STEP_USER_DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_HOST): str, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): str, + } +) + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for WiZ.""" + + VERSION = 1 + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + errors = {} + if user_input is not None: + bulb = wizlight(user_input[CONF_HOST]) + try: + mac = await bulb.getMac() + except WizLightTimeOutError: + errors["base"] = "bulb_time_out" + except ConnectionRefusedError: + errors["base"] = "cannot_connect" + except WizLightConnectionError: + errors["base"] = "no_wiz_light" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + else: + await self.async_set_unique_id(mac) + self._abort_if_unique_id_configured() + return self.async_create_entry( + title=user_input[CONF_NAME], data=user_input + ) + return self.async_show_form( + step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors + ) diff --git a/homeassistant/components/wiz/const.py b/homeassistant/components/wiz/const.py new file mode 100644 index 00000000000000..30b3efb11d42fb --- /dev/null +++ b/homeassistant/components/wiz/const.py @@ -0,0 +1,4 @@ +"""Constants for the WiZ Platform integration.""" + +DOMAIN = "wiz" +DEFAULT_NAME = "WiZ" diff --git a/homeassistant/components/wiz/light.py b/homeassistant/components/wiz/light.py new file mode 100644 index 00000000000000..6efacfb8b9580b --- /dev/null +++ b/homeassistant/components/wiz/light.py @@ -0,0 +1,348 @@ +"""WiZ integration.""" +from __future__ import annotations + +from datetime import timedelta +import logging + +from pywizlight import PilotBuilder, wizlight +from pywizlight.bulblibrary import BulbClass, BulbType +from pywizlight.exceptions import WizLightNotKnownBulb, WizLightTimeOutError +from pywizlight.rgbcw import convertHSfromRGBCW +from pywizlight.scenes import get_id_from_scene_name + +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + ATTR_COLOR_TEMP, + ATTR_EFFECT, + ATTR_HS_COLOR, + ATTR_RGB_COLOR, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, + SUPPORT_COLOR_TEMP, + SUPPORT_EFFECT, + LightEntity, +) +from homeassistant.const import CONF_NAME +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +import homeassistant.util.color as color_utils + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +SUPPORT_FEATURES_RGB = ( + SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_COLOR_TEMP | SUPPORT_EFFECT +) + + +# set poll interval to 15 sec because of changes from external to the bulb +SCAN_INTERVAL = timedelta(seconds=15) + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up the WiZ Platform from config_flow.""" + # Assign configuration variables. + wiz_data = hass.data[DOMAIN][entry.entry_id] + wizbulb = WizBulbEntity(wiz_data.bulb, entry.data.get(CONF_NAME), wiz_data.scenes) + # Add devices with defined name + async_add_entities([wizbulb], update_before_add=True) + return True + + +class WizBulbEntity(LightEntity): + """Representation of WiZ Light bulb.""" + + def __init__(self, light: wizlight, name, scenes): + """Initialize an WiZLight.""" + self._light = light + self._state = None + self._brightness = None + self._attr_name = name + self._rgb_color = None + self._temperature = None + self._hscolor = None + self._available = None + self._effect = None + self._scenes: list[str] = scenes + self._bulbtype: BulbType = light.bulbtype + self._mac = light.mac + self._attr_unique_id = light.mac + # new init states + self._attr_min_mireds = self.get_min_mireds() + self._attr_max_mireds = self.get_max_mireds() + self._attr_supported_features = self.get_supported_features() + + @property + def brightness(self): + """Return the brightness of the light.""" + return self._brightness + + @property + def rgb_color(self): + """Return the color property.""" + return self._rgb_color + + @property + def hs_color(self): + """Return the hs color value.""" + return self._hscolor + + @property + def is_on(self): + """Return true if light is on.""" + return self._state + + async def async_turn_on(self, **kwargs): + """Instruct the light to turn on.""" + brightness = None + + if ATTR_BRIGHTNESS in kwargs: + brightness = kwargs.get(ATTR_BRIGHTNESS) + + if ATTR_RGB_COLOR in kwargs: + pilot = PilotBuilder(rgb=kwargs.get(ATTR_RGB_COLOR), brightness=brightness) + + if ATTR_HS_COLOR in kwargs: + pilot = PilotBuilder( + hucolor=(kwargs[ATTR_HS_COLOR][0], kwargs[ATTR_HS_COLOR][1]), + brightness=brightness, + ) + else: + colortemp = None + if ATTR_COLOR_TEMP in kwargs: + kelvin = color_utils.color_temperature_mired_to_kelvin( + kwargs[ATTR_COLOR_TEMP] + ) + colortemp = kelvin + _LOGGER.debug( + "[wizlight %s] kelvin changed and send to bulb: %s", + self._light.ip, + colortemp, + ) + + sceneid = None + if ATTR_EFFECT in kwargs: + sceneid = get_id_from_scene_name(kwargs[ATTR_EFFECT]) + + if sceneid == 1000: # rhythm + pilot = PilotBuilder() + else: + pilot = PilotBuilder( + brightness=brightness, colortemp=colortemp, scene=sceneid + ) + _LOGGER.debug( + "[wizlight %s] Pilot will be send with brightness=%s, colortemp=%s, scene=%s", + self._light.ip, + brightness, + colortemp, + sceneid, + ) + + sceneid = None + if ATTR_EFFECT in kwargs: + sceneid = get_id_from_scene_name(kwargs[ATTR_EFFECT]) + + if sceneid == 1000: # rhythm + pilot = PilotBuilder() + else: + pilot = PilotBuilder( + brightness=brightness, colortemp=colortemp, scene=sceneid + ) + + await self._light.turn_on(pilot) + + async def async_turn_off(self, **kwargs): + """Instruct the light to turn off.""" + await self._light.turn_off() + + @property + def color_temp(self): + """Return the CT color value in mireds.""" + return self._temperature + + def get_min_mireds(self) -> int: + """Return the coldest color_temp that this light supports.""" + if self._bulbtype is None: + return color_utils.color_temperature_kelvin_to_mired(6500) + # DW bulbs have no kelvin + if self._bulbtype.bulb_type == BulbClass.DW: + return 0 + # If bulbtype is TW or RGB then return the kelvin value + try: + return color_utils.color_temperature_kelvin_to_mired( + self._bulbtype.kelvin_range.max + ) + except WizLightNotKnownBulb: + _LOGGER.debug("Kelvin is not present in the library. Fallback to 6500") + return color_utils.color_temperature_kelvin_to_mired(6500) + + def get_max_mireds(self) -> int: + """Return the warmest color_temp that this light supports.""" + if self._bulbtype is None: + return color_utils.color_temperature_kelvin_to_mired(2200) + # DW bulbs have no kelvin + if self._bulbtype.bulb_type == BulbClass.DW: + return 0 + # If bulbtype is TW or RGB then return the kelvin value + try: + return color_utils.color_temperature_kelvin_to_mired( + self._bulbtype.kelvin_range.min + ) + except WizLightNotKnownBulb: + _LOGGER.debug("Kelvin is not present in the library. Fallback to 2200") + return color_utils.color_temperature_kelvin_to_mired(2200) + + def get_supported_features(self) -> int: + """Flag supported features.""" + if not self._bulbtype: + # fallback + return SUPPORT_FEATURES_RGB + features = 0 + try: + # Map features for better reading + if self._bulbtype.features.brightness: + features = features | SUPPORT_BRIGHTNESS + if self._bulbtype.features.color: + features = features | SUPPORT_COLOR + if self._bulbtype.features.effect: + features = features | SUPPORT_EFFECT + if self._bulbtype.features.color_tmp: + features = features | SUPPORT_COLOR_TEMP + return features + except WizLightNotKnownBulb: + _LOGGER.warning( + "Bulb is not present in the library. Fallback to full feature" + ) + return SUPPORT_FEATURES_RGB + + @property + def effect(self): + """Return the current effect.""" + return self._effect + + @property + def effect_list(self): + """Return the list of supported effects. + + URL: https://docs.pro.wizconnected.com/#light-modes + """ + return self._scenes + + @property + def available(self): + """Return if light is available.""" + return self._available + + async def async_update(self): + """Fetch new state data for this light.""" + await self.update_state() + + if self._state is not None and self._state is not False: + self.update_brightness() + self.update_temperature() + self.update_color() + self.update_effect() + + @property + def device_info(self): + """Get device specific attributes.""" + return { + "connections": {(CONNECTION_NETWORK_MAC, self._mac)}, + "name": self._attr_name, + "manufacturer": "WiZ Light Platform", + "model": self._bulbtype.name, + } + + def update_state_available(self): + """Update the state if bulb is available.""" + self._state = self._light.status + self._available = True + + def update_state_unavailable(self): + """Update the state if bulb is unavailable.""" + self._state = False + self._available = False + + async def update_state(self): + """Update the state.""" + try: + await self._light.updateState() + except (ConnectionRefusedError, TimeoutError, WizLightTimeOutError) as ex: + _LOGGER.debug(ex) + self.update_state_unavailable() + else: + if self._light.state is None: + self.update_state_unavailable() + else: + self.update_state_available() + _LOGGER.debug( + "[wizlight %s] updated state: %s and available: %s", + self._light.ip, + self._state, + self._available, + ) + + def update_brightness(self): + """Update the brightness.""" + if self._light.state.get_brightness() is None: + return + brightness = self._light.state.get_brightness() + if 0 <= int(brightness) <= 255: + self._brightness = int(brightness) + else: + _LOGGER.error( + "Received invalid brightness : %s. Expected: 0-255", brightness + ) + self._brightness = None + + def update_temperature(self): + """Update the temperature.""" + colortemp = self._light.state.get_colortemp() + if colortemp is None or colortemp == 0: + self._temperature = None + return + + _LOGGER.debug( + "[wizlight %s] kelvin from the bulb: %s", self._light.ip, colortemp + ) + temperature = color_utils.color_temperature_kelvin_to_mired(colortemp) + self._temperature = temperature + + def update_color(self): + """Update the hs color.""" + colortemp = self._light.state.get_colortemp() + if colortemp is not None and colortemp != 0: + self._hscolor = None + return + if self._light.state.get_rgb() is None: + return + + rgb = self._light.state.get_rgb() + if rgb[0] is None: + # this is the case if the temperature was changed + # do nothing until the RGB color was changed + return + warmwhite = self._light.state.get_warm_white() + if warmwhite is None: + return + self._hscolor = convertHSfromRGBCW(rgb, warmwhite) + + def update_effect(self): + """Update the bulb scene.""" + self._effect = self._light.state.get_scene() + + async def get_bulb_type(self): + """Get the bulb type.""" + if self._bulbtype is not None: + return self._bulbtype + try: + self._bulbtype = await self._light.get_bulbtype() + _LOGGER.info( + "[wizlight %s] Initiate the WiZ bulb as %s", + self._light.ip, + self._bulbtype.name, + ) + except WizLightTimeOutError: + _LOGGER.debug( + "[wizlight %s] Bulbtype update failed - Timeout", self._light.ip + ) diff --git a/homeassistant/components/wiz/manifest.json b/homeassistant/components/wiz/manifest.json new file mode 100644 index 00000000000000..ca50cd8c7e2818 --- /dev/null +++ b/homeassistant/components/wiz/manifest.json @@ -0,0 +1,13 @@ +{ + "domain": "wiz", + "name": "WiZ", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/wiz", + "requirements": [ + "pywizlight==0.4.15" + ], + "iot_class": "local_polling", + "codeowners": [ + "@sbidy" + ] +} \ No newline at end of file diff --git a/homeassistant/components/wiz/strings.json b/homeassistant/components/wiz/strings.json new file mode 100644 index 00000000000000..59e6d18c179dbd --- /dev/null +++ b/homeassistant/components/wiz/strings.json @@ -0,0 +1,26 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "[%key:common::config_flow::data::host%]", + "name": "[%key:common::config_flow::data::name%]" + }, + "description": "Please enter a hostname or IP address and name to add a new bulb:" + }, + "confirm": { + "description": "[%key:common::config_flow::description::confirm_setup%]" + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]", + "bulb_time_out": "Can not connect to the bulb. Maybe the bulb is offline or a wrong IP/host was entered. Please turn on the light and try again!", + "no_wiz_light": "The bulb can not be connected via WiZ Platform integration." + }, + "abort": { + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/en.json b/homeassistant/components/wiz/translations/en.json new file mode 100644 index 00000000000000..7d95281e14aff0 --- /dev/null +++ b/homeassistant/components/wiz/translations/en.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured", + "no_devices_found": "No devices found on the network" + }, + "error": { + "bulb_time_out": "Can not connect to the bulb. Maybe the bulb is offline or a wrong IP/host was entered. Please turn on the light and try again!", + "cannot_connect": "Failed to connect", + "no_wiz_light": "The bulb can not be connected via WiZ Platform integration.", + "unknown": "Unexpected error" + }, + "step": { + "confirm": { + "description": "Do you want to add a new Bulb?" + }, + "user": { + "data": { + "host": "Hostname or IP", + "name": "Name" + }, + "description": "Please enter a hostname or IP address and name to add a new bulb:" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 365aa903b09bbe..c521bd85e3e33c 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -365,6 +365,7 @@ "wiffi", "wilight", "withings", + "wiz", "wled", "wolflink", "xbox", diff --git a/requirements_all.txt b/requirements_all.txt index 52f3b1eb58b0b4..30f4abb1a342c5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2053,6 +2053,9 @@ pywemo==0.7.0 # homeassistant.components.wilight pywilight==0.0.70 +# homeassistant.components.wiz +pywizlight==0.4.15 + # homeassistant.components.xeoma pyxeoma==1.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 946187fec5f8a7..ab5bab95f90cdd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1275,6 +1275,9 @@ pywemo==0.7.0 # homeassistant.components.wilight pywilight==0.0.70 +# homeassistant.components.wiz +pywizlight==0.4.15 + # homeassistant.components.zerproc pyzerproc==0.4.8 diff --git a/tests/components/wiz/__init__.py b/tests/components/wiz/__init__.py new file mode 100644 index 00000000000000..c2f67982b84ecd --- /dev/null +++ b/tests/components/wiz/__init__.py @@ -0,0 +1,61 @@ +"""Tests for the WiZ Platform integration.""" + +import json + +from homeassistant.components.wiz.const import DOMAIN +from homeassistant.const import CONF_IP_ADDRESS, CONF_NAME +from homeassistant.helpers.typing import HomeAssistantType + +from tests.common import MockConfigEntry + +FAKE_BULB_CONFIG = json.loads( + '{"method":"getSystemConfig","env":"pro","result":\ + {"mac":"ABCABCABCABC",\ + "homeId":653906,\ + "roomId":989983,\ + "moduleName":"ESP_0711_STR",\ + "fwVersion":"1.21.0",\ + "groupId":0,"drvConf":[20,2],\ + "ewf":[255,0,255,255,0,0,0],\ + "ewfHex":"ff00ffff000000",\ + "ping":0}}' +) + +REAL_BULB_CONFIG = json.loads( + '{"method":"getSystemConfig","env":"pro","result":\ + {"mac":"ABCABCABCABC",\ + "homeId":653906,\ + "roomId":989983,\ + "moduleName":"ESP01_SHRGB_03",\ + "fwVersion":"1.21.0",\ + "groupId":0,"drvConf":[20,2],\ + "ewf":[255,0,255,255,0,0,0],\ + "ewfHex":"ff00ffff000000",\ + "ping":0}}' +) + +TEST_SYSTEM_INFO = {"id": "ABCABCABCABC", "name": "Test Bulb"} + +TEST_CONNECTION = {CONF_IP_ADDRESS: "1.1.1.1", CONF_NAME: "Test Bulb"} + + +async def setup_integration( + hass: HomeAssistantType, +) -> MockConfigEntry: + """Mock ConfigEntry in Home Assistant.""" + + entry = MockConfigEntry( + domain=DOMAIN, + unique_id=TEST_SYSTEM_INFO["id"], + data={ + CONF_IP_ADDRESS: "127.0.0.1", + CONF_NAME: TEST_SYSTEM_INFO["name"], + }, + ) + + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + return entry diff --git a/tests/components/wiz/test_config_flow.py b/tests/components/wiz/test_config_flow.py new file mode 100644 index 00000000000000..e08ca87a389b70 --- /dev/null +++ b/tests/components/wiz/test_config_flow.py @@ -0,0 +1,128 @@ +"""Test the WiZ Platform config flow.""" +from unittest.mock import patch + +import pytest + +from homeassistant import config_entries +from homeassistant.components.wiz.config_flow import ( + WizLightConnectionError, + WizLightTimeOutError, +) +from homeassistant.components.wiz.const import DOMAIN +from homeassistant.const import CONF_HOST, CONF_NAME + +from tests.common import MockConfigEntry + +FAKE_BULB_CONFIG = '{"method":"getSystemConfig","env":"pro","result":\ + {"mac":"ABCABCABCABC",\ + "homeId":653906,\ + "roomId":989983,\ + "moduleName":"ESP_0711_STR",\ + "fwVersion":"1.21.0",\ + "groupId":0,"drvConf":[20,2],\ + "ewf":[255,0,255,255,0,0,0],\ + "ewfHex":"ff00ffff000000",\ + "ping":0}}' + +TEST_SYSTEM_INFO = {"id": "ABCABCABCABC", "name": "Test Bulb"} + + +TEST_CONNECTION = {CONF_HOST: "1.1.1.1", CONF_NAME: "Test Bulb"} + +TEST_NO_IP = {CONF_HOST: "this is no IP input", CONF_NAME: "Test Bulb"} + + +async def test_form(hass): + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + # Patch functions + with patch( + "homeassistant.components.wiz.wizlight.getBulbConfig", + return_value=FAKE_BULB_CONFIG, + ), patch( + "homeassistant.components.wiz.wizlight.getMac", + return_value="ABCABCABCABC", + ) as mock_setup, patch( + "homeassistant.components.wiz.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + TEST_CONNECTION, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "Test Bulb" + assert result2["data"] == TEST_CONNECTION + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +@pytest.mark.parametrize( + "side_effect, error_base", + [ + (WizLightTimeOutError, "bulb_time_out"), + (WizLightConnectionError, "no_wiz_light"), + (Exception, "unknown"), + (ConnectionRefusedError, "cannot_connect"), + ], +) +async def test_user_form_exceptions(hass, side_effect, error_base): + """Test all user exceptions in the flow.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.wiz.wizlight.getBulbConfig", + side_effect=side_effect, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + TEST_CONNECTION, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": error_base} + + +async def test_form_updates_unique_id(hass): + """Test a duplicate id aborts and updates existing entry.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id=TEST_SYSTEM_INFO["id"], + data={ + CONF_HOST: "dummy", + CONF_NAME: TEST_SYSTEM_INFO["name"], + "id": TEST_SYSTEM_INFO["id"], + }, + ) + + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + with patch( + "homeassistant.components.wiz.wizlight.getBulbConfig", + return_value=FAKE_BULB_CONFIG, + ), patch( + "homeassistant.components.wiz.wizlight.getMac", + return_value="ABCABCABCABC", + ), patch( + "homeassistant.components.wiz.async_setup_entry", + return_value=True, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + TEST_CONNECTION, + ) + await hass.async_block_till_done() + + assert result2["type"] == "abort" + assert result2["reason"] == "already_configured" From b9d7d0cae2ccd2d88e90e49cc09e154a27ed809b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 5 Feb 2022 02:34:46 +0100 Subject: [PATCH 0288/1098] Add diagnostics to issue form (#65715) --- .github/ISSUE_TEMPLATE/bug_report.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index ac4c8453327a3e..040f4a128eed09 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -75,6 +75,17 @@ body: attributes: value: | # Details + - type: textarea + attributes: + label: Diagnostics information + description: >- + Many integrations provide the ability to download diagnostic data + on the device page (and on the integration dashboard). + + **It would really help if you could download the diagnostics data for the device you are having issues with, + and drag-and-drop that file into the textbox below.** + + It generally allows pinpointing defects and thus resolving issues faster. - type: textarea attributes: label: Example YAML snippet From 92842b796bfb2e8289862b05c84f1770aeab4f5a Mon Sep 17 00:00:00 2001 From: dougiteixeira <31328123+dougiteixeira@users.noreply.github.com> Date: Fri, 4 Feb 2022 23:19:00 -0300 Subject: [PATCH 0289/1098] Complementing the Tuya Air Purifier (kj) category (#65283) Co-authored-by: Franck Nijhof --- homeassistant/components/tuya/const.py | 10 +++ homeassistant/components/tuya/select.py | 18 +++++ homeassistant/components/tuya/sensor.py | 65 +++++++++++++++++++ .../components/tuya/strings.select.json | 9 +++ .../components/tuya/strings.sensor.json | 6 ++ homeassistant/components/tuya/switch.py | 6 ++ 6 files changed, 114 insertions(+) diff --git a/homeassistant/components/tuya/const.py b/homeassistant/components/tuya/const.py index e38671ae91271d..5d2070b901552b 100644 --- a/homeassistant/components/tuya/const.py +++ b/homeassistant/components/tuya/const.py @@ -87,10 +87,12 @@ class TuyaDeviceClass(StrEnum): """Tuya specific device classes, used for translations.""" + AIR_QUALITY = "tuya__air_quality" CURTAIN_MODE = "tuya__curtain_mode" CURTAIN_MOTOR_MODE = "tuya__curtain_motor_mode" BASIC_ANTI_FLICKR = "tuya__basic_anti_flickr" BASIC_NIGHTVISION = "tuya__basic_nightvision" + COUNTDOWN = "tuya__countdown" DECIBEL_SENSITIVITY = "tuya__decibel_sensitivity" FAN_ANGLE = "tuya__fan_angle" FINGERBOT_MODE = "tuya__fingerbot_mode" @@ -132,6 +134,7 @@ class DPCode(StrEnum): https://developer.tuya.com/en/docs/iot/standarddescription?id=K9i5ql6waswzq """ + AIR_QUALITY = "air_quality" ALARM_SWITCH = "alarm_switch" # Alarm switch ALARM_TIME = "alarm_time" # Alarm time ALARM_VOLUME = "alarm_volume" # Alarm volume @@ -207,6 +210,7 @@ class DPCode(StrEnum): DOORCONTACT_STATE_2 = "doorcontact_state_2" DOORCONTACT_STATE_3 = "doorcontact_state_3" DUSTER_CLOTH = "duster_cloth" + ECO2 = "eco2" EDGE_BRUSH = "edge_brush" ELECTRICITY_LEFT = "electricity_left" FAN_BEEP = "fan_beep" # Sound @@ -222,6 +226,7 @@ class DPCode(StrEnum): FAULT = "fault" FEED_REPORT = "feed_report" FEED_STATE = "feed_state" + FILTER = "filter" FILTER_LIFE = "filter" FILTER_RESET = "filter_reset" # Filter (cartridge) reset FLOODLIGHT_LIGHTNESS = "floodlight_lightness" @@ -231,6 +236,7 @@ class DPCode(StrEnum): GAS_SENSOR_STATUS = "gas_sensor_status" GAS_SENSOR_VALUE = "gas_sensor_value" HUMIDIFIER = "humidifier" # Humidification + HUMIDITY = "humidity" # Humidity HUMIDITY_CURRENT = "humidity_current" # Current humidity HUMIDITY_SET = "humidity_set" # Humidity setting HUMIDITY_VALUE = "humidity_value" # Humidity @@ -269,6 +275,7 @@ class DPCode(StrEnum): PIR = "pir" # Motion sensor PM1 = "pm1" PM10 = "pm10" + PM25 = "pm25" PM25_STATE = "pm25_state" PM25_VALUE = "pm25_value" POWDER_SET = "powder_set" # Powder @@ -352,6 +359,9 @@ class DPCode(StrEnum): TOTAL_CLEAN_COUNT = "total_clean_count" TOTAL_CLEAN_TIME = "total_clean_time" TOTAL_FORWARD_ENERGY = "total_forward_energy" + TOTAL_TIME = "total_time" + TOTAL_PM = "total_pm" + TVOC = "tvoc" UV = "uv" # UV sterilization VA_BATTERY = "va_battery" VA_HUMIDITY = "va_humidity" diff --git a/homeassistant/components/tuya/select.py b/homeassistant/components/tuya/select.py index 5154afb9ab5b38..b0b7aeec2be4a7 100644 --- a/homeassistant/components/tuya/select.py +++ b/homeassistant/components/tuya/select.py @@ -274,6 +274,24 @@ entity_category=EntityCategory.CONFIG, ), ), + # Air Purifier + # https://developer.tuya.com/en/docs/iot/f?id=K9gf46h2s6dzm + "kj": ( + SelectEntityDescription( + key=DPCode.COUNTDOWN, + name="Countdown", + device_class=TuyaDeviceClass.COUNTDOWN, + entity_category=EntityCategory.CONFIG, + icon="mdi:timer-cog-outline", + ), + SelectEntityDescription( + key=DPCode.COUNTDOWN_SET, + name="Countdown", + device_class=TuyaDeviceClass.COUNTDOWN, + entity_category=EntityCategory.CONFIG, + icon="mdi:timer-cog-outline", + ), + ), } # Socket (duplicate of `kg`) diff --git a/homeassistant/components/tuya/sensor.py b/homeassistant/components/tuya/sensor.py index 06c4a783066061..e415d26ad4cac1 100644 --- a/homeassistant/components/tuya/sensor.py +++ b/homeassistant/components/tuya/sensor.py @@ -742,6 +742,67 @@ class TuyaSensorEntityDescription(SensorEntityDescription): icon="mdi:progress-clock", ), ), + # Air Purifier + # https://developer.tuya.com/en/docs/iot/s?id=K9gf48r41mn81 + "kj": ( + TuyaSensorEntityDescription( + key=DPCode.FILTER, + name="Filter Utilization", + entity_category=EntityCategory.DIAGNOSTIC, + icon="mdi:ticket-percent-outline", + ), + TuyaSensorEntityDescription( + key=DPCode.PM25, + name="Particulate Matter 2.5 µm", + device_class=SensorDeviceClass.PM25, + state_class=SensorStateClass.MEASUREMENT, + icon="mdi:molecule", + ), + TuyaSensorEntityDescription( + key=DPCode.TEMP, + name="Temperature", + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + ), + TuyaSensorEntityDescription( + key=DPCode.HUMIDITY, + name="Humidity", + device_class=SensorDeviceClass.HUMIDITY, + state_class=SensorStateClass.MEASUREMENT, + ), + TuyaSensorEntityDescription( + key=DPCode.TVOC, + name="Total Volatile Organic Compound", + device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS, + state_class=SensorStateClass.MEASUREMENT, + ), + TuyaSensorEntityDescription( + key=DPCode.ECO2, + name="Concentration of Carbon Dioxide", + device_class=SensorDeviceClass.CO2, + state_class=SensorStateClass.MEASUREMENT, + ), + TuyaSensorEntityDescription( + key=DPCode.TOTAL_TIME, + name="Total Operating Time", + icon="mdi:history", + state_class=SensorStateClass.TOTAL_INCREASING, + entity_category=EntityCategory.DIAGNOSTIC, + ), + TuyaSensorEntityDescription( + key=DPCode.TOTAL_PM, + name="Total Absorption of Particles", + icon="mdi:texture-box", + state_class=SensorStateClass.TOTAL_INCREASING, + entity_category=EntityCategory.DIAGNOSTIC, + ), + TuyaSensorEntityDescription( + key=DPCode.AIR_QUALITY, + name="Air quality", + icon="mdi:air-filter", + device_class=TuyaDeviceClass.AIR_QUALITY, + ), + ), } # Socket (duplicate of `kg`) @@ -845,6 +906,10 @@ def __init__( self._attr_device_class = None return + # If we still have a device class, we should not use an icon + if self.device_class: + self._attr_icon = None + # Found unit of measurement, use the standardized Unit # Use the target conversion unit (if set) self._attr_native_unit_of_measurement = ( diff --git a/homeassistant/components/tuya/strings.select.json b/homeassistant/components/tuya/strings.select.json index c1171f81fcbe7f..ada9c528ac8542 100644 --- a/homeassistant/components/tuya/strings.select.json +++ b/homeassistant/components/tuya/strings.select.json @@ -93,6 +93,15 @@ "tuya__curtain_motor_mode": { "forward": "Forward", "back": "Back" + }, + "tuya__countdown": { + "cancel": "Cancel", + "1h": "1 hour", + "2h": "2 hours", + "3h": "3 hours", + "4h": "4 hours", + "5h": "5 hours", + "6h": "6 hours" } } } diff --git a/homeassistant/components/tuya/strings.sensor.json b/homeassistant/components/tuya/strings.sensor.json index ff246817f61eaa..a11aadba3219a2 100644 --- a/homeassistant/components/tuya/strings.sensor.json +++ b/homeassistant/components/tuya/strings.sensor.json @@ -10,6 +10,12 @@ "reserve_3": "Reserve 3", "standby": "Standby", "warm": "Heat preservation" + }, + "tuya__air_quality": { + "great": "Great", + "mild": "Mild", + "good": "Good", + "severe": "Severe" } } } diff --git a/homeassistant/components/tuya/switch.py b/homeassistant/components/tuya/switch.py index 4559bdf3364d69..ae63830bd8ddcb 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -198,6 +198,12 @@ icon="mdi:water-percent", entity_category=EntityCategory.CONFIG, ), + SwitchEntityDescription( + key=DPCode.UV, + name="UV Sterilization", + icon="mdi:minus-circle-outline", + entity_category=EntityCategory.CONFIG, + ), ), # Air conditioner # https://developer.tuya.com/en/docs/iot/categorykt?id=Kaiuz0z71ov2n From 2f46382565e705d069c9b1ab912a2a259a896e5d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 5 Feb 2022 06:33:31 +0100 Subject: [PATCH 0290/1098] Remove async_timeout backcompat (#65732) --- homeassistant/async_timeout_backcompat.py | 42 ----------------------- homeassistant/core.py | 3 +- 2 files changed, 1 insertion(+), 44 deletions(-) delete mode 100644 homeassistant/async_timeout_backcompat.py diff --git a/homeassistant/async_timeout_backcompat.py b/homeassistant/async_timeout_backcompat.py deleted file mode 100644 index 212beddfae3262..00000000000000 --- a/homeassistant/async_timeout_backcompat.py +++ /dev/null @@ -1,42 +0,0 @@ -"""Provide backwards compat for async_timeout.""" -from __future__ import annotations - -import asyncio -from typing import Any - -import async_timeout - -from .helpers.frame import report - - -def timeout( - delay: float | None, loop: asyncio.AbstractEventLoop | None = None -) -> async_timeout.Timeout: - """Backwards compatible timeout context manager that warns with loop usage.""" - if loop is None: - loop = asyncio.get_running_loop() - else: - report( - "called async_timeout.timeout with loop keyword argument. The loop keyword argument is deprecated and calls will fail after Home Assistant 2022.3", - error_if_core=False, - ) - if delay is not None: - deadline: float | None = loop.time() + delay - else: - deadline = None - return async_timeout.Timeout(deadline, loop) - - -def current_task(loop: asyncio.AbstractEventLoop) -> asyncio.Task[Any] | None: - """Backwards compatible current_task.""" - report( - "called async_timeout.current_task. The current_task call is deprecated and calls will fail after Home Assistant 2022.3; use asyncio.current_task instead", - error_if_core=False, - ) - return asyncio.current_task() - - -def enable() -> None: - """Enable backwards compat transitions.""" - async_timeout.timeout = timeout - async_timeout.current_task = current_task # type: ignore[attr-defined] diff --git a/homeassistant/core.py b/homeassistant/core.py index 0c44ea52f07572..a29067cdb0d686 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -40,7 +40,7 @@ import voluptuous as vol import yarl -from . import async_timeout_backcompat, block_async_io, loader, util +from . import block_async_io, loader, util from .backports.enum import StrEnum from .const import ( ATTR_DOMAIN, @@ -97,7 +97,6 @@ STAGE_2_SHUTDOWN_TIMEOUT = 60 STAGE_3_SHUTDOWN_TIMEOUT = 30 -async_timeout_backcompat.enable() block_async_io.enable() T = TypeVar("T") From d753f4a2e79107db7b26b0402c285743d7ecf7e6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 4 Feb 2022 21:33:53 -0800 Subject: [PATCH 0291/1098] Fix UPNP access to SSDP info (#65728) --- homeassistant/components/upnp/device.py | 17 ++++++++++------- tests/components/upnp/test_init.py | 13 +++++++++---- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/upnp/device.py b/homeassistant/components/upnp/device.py index c2c92f06488a8f..3231d34a342adb 100644 --- a/homeassistant/components/upnp/device.py +++ b/homeassistant/components/upnp/device.py @@ -12,7 +12,7 @@ from async_upnp_client.profiles.igd import IgdDevice from homeassistant.components import ssdp -from homeassistant.components.ssdp import SsdpChange +from homeassistant.components.ssdp import SsdpChange, SsdpServiceInfo from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.update_coordinator import DataUpdateCoordinator @@ -71,19 +71,22 @@ async def async_create_device( return device async def async_ssdp_callback( - self, headers: Mapping[str, Any], change: SsdpChange + self, service_info: SsdpServiceInfo, change: SsdpChange ) -> None: """SSDP callback, update if needed.""" - _LOGGER.debug("SSDP Callback, change: %s, headers: %s", change, headers) - if ssdp.ATTR_SSDP_LOCATION not in headers: + _LOGGER.debug( + "SSDP Callback, change: %s, headers: %s", change, service_info.ssdp_headers + ) + if service_info.ssdp_location is None: return - location = headers[ssdp.ATTR_SSDP_LOCATION] device = self._igd_device.device - if location == device.device_url: + if service_info.ssdp_location == device.device_url: return - new_upnp_device = await async_create_upnp_device(self.hass, location) + new_upnp_device = await async_create_upnp_device( + self.hass, service_info.ssdp_location + ) device.reinit(new_upnp_device) @property diff --git a/tests/components/upnp/test_init.py b/tests/components/upnp/test_init.py index 39a63893e33661..bd2096b59a061c 100644 --- a/tests/components/upnp/test_init.py +++ b/tests/components/upnp/test_init.py @@ -45,8 +45,13 @@ async def test_reinitialize_device( # Reinit. new_location = "http://192.168.1.1:12345/desc.xml" - headers = { - ssdp.ATTR_SSDP_LOCATION: new_location, - } - await device.async_ssdp_callback(headers, ...) + await device.async_ssdp_callback( + ssdp.SsdpServiceInfo( + ssdp_usn="mock_usn", + ssdp_st="mock_st", + ssdp_location="http://192.168.1.1:12345/desc.xml", + upnp={}, + ), + ..., + ) assert device._igd_device.device.device_url == new_location From b9b53bef00d6bf57732c57200fc11c44fa0bfce9 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sat, 5 Feb 2022 00:39:01 -0700 Subject: [PATCH 0292/1098] Bump simplisafe-python to 2022.02.0 (#65748) --- homeassistant/components/simplisafe/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index 6465b542f36ab0..4a4ffe6eb0ad4d 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,7 +3,7 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==2022.01.0"], + "requirements": ["simplisafe-python==2022.02.0"], "codeowners": ["@bachya"], "iot_class": "cloud_polling", "dhcp": [ diff --git a/requirements_all.txt b/requirements_all.txt index 30f4abb1a342c5..268bdad0ce7717 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2193,7 +2193,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==2022.01.0 +simplisafe-python==2022.02.0 # homeassistant.components.sisyphus sisyphus-control==3.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ab5bab95f90cdd..a2f2194c0a2e73 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1343,7 +1343,7 @@ sharkiqpy==0.1.8 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==2022.01.0 +simplisafe-python==2022.02.0 # homeassistant.components.slack slackclient==2.5.0 From 3387e8368b04aa8c1440f005e51ce7cc20b394cc Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sat, 5 Feb 2022 00:41:12 -0700 Subject: [PATCH 0293/1098] Add redacted subscription data to SimpliSafe diagnostics (#65751) --- .../components/simplisafe/__init__.py | 1 + .../components/simplisafe/diagnostics.py | 15 +++ tests/components/simplisafe/conftest.py | 3 +- .../components/simplisafe/test_diagnostics.py | 93 ++++++++++++++++++- 4 files changed, 109 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index 9a0566531c3ecb..f5a8df0d652719 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -449,6 +449,7 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry, api: API) -> None: self._websocket_reconnect_task: asyncio.Task | None = None self.entry = entry self.initial_event_to_use: dict[int, dict[str, Any]] = {} + self.subscription_data: dict[int, Any] = api.subscription_data self.systems: dict[int, SystemType] = {} # This will get filled in by async_init: diff --git a/homeassistant/components/simplisafe/diagnostics.py b/homeassistant/components/simplisafe/diagnostics.py index bc0dddef47c7f8..c7c03467c94ba2 100644 --- a/homeassistant/components/simplisafe/diagnostics.py +++ b/homeassistant/components/simplisafe/diagnostics.py @@ -11,14 +11,28 @@ from . import SimpliSafe from .const import DOMAIN +CONF_CREDIT_CARD = "creditCard" +CONF_EXPIRES = "expires" +CONF_LOCATION = "location" +CONF_LOCATION_NAME = "locationName" +CONF_PAYMENT_PROFILE_ID = "paymentProfileId" CONF_SERIAL = "serial" +CONF_SID = "sid" CONF_SYSTEM_ID = "system_id" +CONF_UID = "uid" CONF_WIFI_SSID = "wifi_ssid" TO_REDACT = { CONF_ADDRESS, + CONF_CREDIT_CARD, + CONF_EXPIRES, + CONF_LOCATION, + CONF_LOCATION_NAME, + CONF_PAYMENT_PROFILE_ID, CONF_SERIAL, + CONF_SID, CONF_SYSTEM_ID, + CONF_UID, CONF_WIFI_SSID, } @@ -34,6 +48,7 @@ async def async_get_config_entry_diagnostics( "entry": { "options": dict(entry.options), }, + "subscription_data": simplisafe.subscription_data, "systems": [system.as_dict() for system in simplisafe.systems.values()], }, TO_REDACT, diff --git a/tests/components/simplisafe/conftest.py b/tests/components/simplisafe/conftest.py index d9e6d46c2eb1a0..d4517717434cd3 100644 --- a/tests/components/simplisafe/conftest.py +++ b/tests/components/simplisafe/conftest.py @@ -18,11 +18,12 @@ @pytest.fixture(name="api") -def api_fixture(system_v3, websocket): +def api_fixture(data_subscription, system_v3, websocket): """Define a fixture for a simplisafe-python API object.""" return Mock( async_get_systems=AsyncMock(return_value={SYSTEM_ID: system_v3}), refresh_token=REFRESH_TOKEN, + subscription_data=data_subscription, user_id=USER_ID, websocket=websocket, ) diff --git a/tests/components/simplisafe/test_diagnostics.py b/tests/components/simplisafe/test_diagnostics.py index d2c2866bf5b790..13d5c778e89e90 100644 --- a/tests/components/simplisafe/test_diagnostics.py +++ b/tests/components/simplisafe/test_diagnostics.py @@ -7,7 +7,96 @@ async def test_entry_diagnostics(hass, config_entry, hass_client, setup_simplisafe): """Test config entry diagnostics.""" assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { - "entry": {"options": {}}, + "entry": { + "options": {}, + }, + "subscription_data": { + "system_123": { + "uid": REDACTED, + "sid": REDACTED, + "sStatus": 20, + "activated": 1445034752, + "planSku": "SSEDSM2", + "planName": "Interactive Monitoring", + "price": 24.99, + "currency": "USD", + "country": "US", + "expires": REDACTED, + "canceled": 0, + "extraTime": 0, + "creditCard": REDACTED, + "time": 2628000, + "paymentProfileId": REDACTED, + "features": { + "monitoring": True, + "alerts": True, + "online": True, + "hazard": True, + "video": True, + "cameras": 10, + "dispatch": True, + "proInstall": False, + "discount": 0, + "vipCS": False, + "medical": True, + "careVisit": False, + "storageDays": 30, + }, + "status": { + "hasBaseStation": True, + "isActive": True, + "monitoring": "Active", + }, + "subscriptionFeatures": { + "monitoredSensorsTypes": [ + "Entry", + "Motion", + "GlassBreak", + "Smoke", + "CO", + "Freeze", + "Water", + ], + "monitoredPanicConditions": ["Fire", "Medical", "Duress"], + "dispatchTypes": ["Police", "Fire", "Medical", "Guard"], + "remoteControl": [ + "ArmDisarm", + "LockUnlock", + "ViewSettings", + "ConfigureSettings", + ], + "cameraFeatures": { + "liveView": True, + "maxRecordingCameras": 10, + "recordingStorageDays": 30, + "videoVerification": True, + }, + "support": { + "level": "Basic", + "annualVisit": False, + "professionalInstall": False, + }, + "cellCommunicationBackup": True, + "alertChannels": ["Push", "SMS", "Email"], + "alertTypes": ["Alarm", "Error", "Activity", "Camera"], + "alarmModes": ["Alarm", "SecretAlert", "Disabled"], + "supportedIntegrations": [ + "GoogleAssistant", + "AmazonAlexa", + "AugustLock", + ], + "timeline": {}, + }, + "dispatcher": "cops", + "dcid": 0, + "location": REDACTED, + "pinUnlocked": True, + "billDate": 1602887552, + "billInterval": 2628000, + "pinUnlockedBy": "pin", + "autoActivation": None, + } + }, "systems": [ { "address": REDACTED, @@ -183,7 +272,7 @@ async def test_entry_diagnostics(hass, config_entry, hass_client, setup_simplisa "shutter_open_when_off": False, "status": "online", "subscription_enabled": True, - }, + } ], "chime_volume": 2, "entry_delay_away": 30, From fbe4d4272912a2ac5e2783b90eb75c90a6d7e6f5 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sat, 5 Feb 2022 00:41:40 -0700 Subject: [PATCH 0294/1098] Remove unnecessary `TYPE_CHECKING` declarations in SimpliSafe (#65750) --- .../components/simplisafe/__init__.py | 24 +++++++------------ .../simplisafe/alarm_control_panel.py | 7 +++--- .../components/simplisafe/config_flow.py | 5 ++-- homeassistant/components/simplisafe/lock.py | 6 ++--- 4 files changed, 16 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index f5a8df0d652719..a4ff2a618bc645 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -4,7 +4,7 @@ import asyncio from collections.abc import Callable, Iterable from datetime import timedelta -from typing import TYPE_CHECKING, Any, cast +from typing import Any, cast from simplipy import API from simplipy.device import Device, DeviceTypes @@ -235,8 +235,7 @@ def _async_get_system_for_service_call( ) is None: raise vol.Invalid("Invalid device ID specified") - if TYPE_CHECKING: - assert alarm_control_panel_device_entry.via_device_id + assert alarm_control_panel_device_entry.via_device_id if ( base_station_device_entry := device_registry.async_get( @@ -494,8 +493,7 @@ def _async_process_new_notifications(self, system: SystemType) -> None: async def _async_start_websocket_loop(self) -> None: """Start a websocket reconnection loop.""" - if TYPE_CHECKING: - assert self._api.websocket + assert self._api.websocket should_reconnect = True @@ -527,8 +525,7 @@ async def _async_cancel_websocket_loop(self) -> None: LOGGER.debug("Websocket reconnection task successfully canceled") self._websocket_reconnect_task = None - if TYPE_CHECKING: - assert self._api.websocket + assert self._api.websocket await self._api.websocket.async_disconnect() @callback @@ -565,9 +562,8 @@ def _async_websocket_on_event(self, event: WebsocketEvent) -> None: async def async_init(self) -> None: """Initialize the SimpliSafe "manager" class.""" - if TYPE_CHECKING: - assert self._api.refresh_token - assert self._api.websocket + assert self._api.refresh_token + assert self._api.websocket self._api.websocket.add_event_callback(self._async_websocket_on_event) self._websocket_reconnect_task = asyncio.create_task( @@ -576,9 +572,7 @@ async def async_init(self) -> None: async def async_websocket_disconnect_listener(_: Event) -> None: """Define an event handler to disconnect from the websocket.""" - if TYPE_CHECKING: - assert self._api.websocket - + assert self._api.websocket await self._async_cancel_websocket_loop() self.entry.async_on_unload( @@ -625,10 +619,8 @@ async def async_handle_refresh_token(token: str) -> None: """Handle a new refresh token.""" async_save_refresh_token(token) - if TYPE_CHECKING: - assert self._api.websocket - # Open a new websocket connection with the fresh token: + assert self._api.websocket await self._async_cancel_websocket_loop() self._websocket_reconnect_task = self._hass.async_create_task( self._async_start_websocket_loop() diff --git a/homeassistant/components/simplisafe/alarm_control_panel.py b/homeassistant/components/simplisafe/alarm_control_panel.py index 43bcaee2059236..cf896c3a320f34 100644 --- a/homeassistant/components/simplisafe/alarm_control_panel.py +++ b/homeassistant/components/simplisafe/alarm_control_panel.py @@ -1,8 +1,6 @@ """Support for SimpliSafe alarm control panels.""" from __future__ import annotations -from typing import TYPE_CHECKING - from simplipy.errors import SimplipyError from simplipy.system import SystemStates from simplipy.system.v3 import SystemV3 @@ -240,8 +238,9 @@ def async_update_from_rest_api(self) -> None: def async_update_from_websocket_event(self, event: WebsocketEvent) -> None: """Update the entity when new data comes from the websocket.""" self._attr_changed_by = event.changed_by - if TYPE_CHECKING: - assert event.event_type + + assert event.event_type + if state := STATE_MAP_FROM_WEBSOCKET_EVENT.get(event.event_type): self._attr_state = state self.async_reset_error_count() diff --git a/homeassistant/components/simplisafe/config_flow.py b/homeassistant/components/simplisafe/config_flow.py index 44244e9c5739f3..ad6e01f0422ee7 100644 --- a/homeassistant/components/simplisafe/config_flow.py +++ b/homeassistant/components/simplisafe/config_flow.py @@ -1,7 +1,7 @@ """Config flow to configure the SimpliSafe component.""" from __future__ import annotations -from typing import TYPE_CHECKING, Any, NamedTuple +from typing import Any, NamedTuple from simplipy import API from simplipy.errors import InvalidCredentialsError, SimplipyError @@ -97,8 +97,7 @@ async def async_step_user( if user_input is None: return self._async_show_form() - if TYPE_CHECKING: - assert self._oauth_values + assert self._oauth_values errors = {} session = aiohttp_client.async_get_clientsession(self.hass) diff --git a/homeassistant/components/simplisafe/lock.py b/homeassistant/components/simplisafe/lock.py index 14816cdd579e4e..1e7be48979b036 100644 --- a/homeassistant/components/simplisafe/lock.py +++ b/homeassistant/components/simplisafe/lock.py @@ -1,7 +1,7 @@ """Support for SimpliSafe locks.""" from __future__ import annotations -from typing import TYPE_CHECKING, Any +from typing import Any from simplipy.device.lock import Lock, LockStates from simplipy.errors import SimplipyError @@ -100,8 +100,8 @@ def async_update_from_rest_api(self) -> None: @callback def async_update_from_websocket_event(self, event: WebsocketEvent) -> None: """Update the entity when new data comes from the websocket.""" - if TYPE_CHECKING: - assert event.event_type + assert event.event_type + if state := STATE_MAP_FROM_WEBSOCKET_EVENT.get(event.event_type) is not None: self._attr_is_locked = state self.async_reset_error_count() From e242796394548980fdc3cf885bc5fc8efcd5a1ae Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 5 Feb 2022 08:42:29 +0100 Subject: [PATCH 0295/1098] Remove deprecated format for date(time) sensors (#65734) --- homeassistant/components/sensor/__init__.py | 43 +--------------- tests/components/sensor/test_init.py | 57 +++------------------ 2 files changed, 9 insertions(+), 91 deletions(-) diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 2b879c30a16206..38461bce859e36 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -9,7 +9,6 @@ import logging from typing import Any, Final, cast, final -import ciso8601 import voluptuous as vol from homeassistant.backports.enum import StrEnum @@ -371,44 +370,6 @@ def state(self) -> Any: value = self.native_value device_class = self.device_class - # We have an old non-datetime value, warn about it and convert it during - # the deprecation period. - if ( - value is not None - and device_class in (DEVICE_CLASS_DATE, DEVICE_CLASS_TIMESTAMP) - and not isinstance(value, (date, datetime)) - ): - # Deprecation warning for date/timestamp device classes - if not self.__datetime_as_string_deprecation_logged: - report_issue = self._suggest_report_issue() - _LOGGER.warning( - "%s is providing a string for its state, while the device " - "class is '%s', this is not valid and will be unsupported " - "from Home Assistant 2022.2. Please %s", - self.entity_id, - device_class, - report_issue, - ) - self.__datetime_as_string_deprecation_logged = True - - # Anyways, lets validate the date at least.. - try: - value = ciso8601.parse_datetime(str(value)) - except (ValueError, IndexError) as error: - raise ValueError( - f"Invalid date/datetime: {self.entity_id} provide state '{value}', " - f"while it has device class '{device_class}'" - ) from error - - if value.tzinfo is not None and value.tzinfo != timezone.utc: - value = value.astimezone(timezone.utc) - - # Convert the date object to a standardized state string. - if device_class == DEVICE_CLASS_DATE: - return value.date().isoformat() - - return value.isoformat(timespec="seconds") - # Received a datetime if value is not None and device_class == DEVICE_CLASS_TIMESTAMP: try: @@ -427,7 +388,7 @@ def state(self) -> Any: return value.isoformat(timespec="seconds") except (AttributeError, TypeError) as err: raise ValueError( - f"Invalid datetime: {self.entity_id} has a timestamp device class" + f"Invalid datetime: {self.entity_id} has a timestamp device class " f"but does not provide a datetime state but {type(value)}" ) from err @@ -437,7 +398,7 @@ def state(self) -> Any: return value.isoformat() # type: ignore except (AttributeError, TypeError) as err: raise ValueError( - f"Invalid date: {self.entity_id} has a date device class" + f"Invalid date: {self.entity_id} has a date device class " f"but does not provide a date state but {type(value)}" ) from err diff --git a/tests/components/sensor/test_init.py b/tests/components/sensor/test_init.py index b49d8894932b3d..eed88d92d0417f 100644 --- a/tests/components/sensor/test_init.py +++ b/tests/components/sensor/test_init.py @@ -165,71 +165,28 @@ async def test_datetime_conversion(hass, caplog, enable_custom_integrations): @pytest.mark.parametrize( - "device_class,native_value,state_value", + "device_class,state_value,provides", [ - (SensorDeviceClass.DATE, "2021-11-09", "2021-11-09"), - ( - SensorDeviceClass.DATE, - "2021-01-09T12:00:00+00:00", - "2021-01-09", - ), - ( - SensorDeviceClass.DATE, - "2021-01-09T00:00:00+01:00", - "2021-01-08", - ), - ( - SensorDeviceClass.TIMESTAMP, - "2021-01-09T12:00:00+00:00", - "2021-01-09T12:00:00+00:00", - ), - ( - SensorDeviceClass.TIMESTAMP, - "2021-01-09 12:00:00+00:00", - "2021-01-09T12:00:00+00:00", - ), - ( - SensorDeviceClass.TIMESTAMP, - "2021-01-09T12:00:00+04:00", - "2021-01-09T08:00:00+00:00", - ), - ( - SensorDeviceClass.TIMESTAMP, - "2021-01-09 12:00:00+01:00", - "2021-01-09T11:00:00+00:00", - ), - ( - SensorDeviceClass.TIMESTAMP, - "2021-01-09 12:00:00", - "2021-01-09T12:00:00", - ), - ( - SensorDeviceClass.TIMESTAMP, - "2021-01-09T12:00:00", - "2021-01-09T12:00:00", - ), + (SensorDeviceClass.DATE, "2021-01-09", "date"), + (SensorDeviceClass.TIMESTAMP, "2021-01-09T12:00:00+00:00", "datetime"), ], ) async def test_deprecated_datetime_str( - hass, caplog, enable_custom_integrations, device_class, native_value, state_value + hass, caplog, enable_custom_integrations, device_class, state_value, provides ): """Test warning on deprecated str for a date(time) value.""" platform = getattr(hass.components, "test.sensor") platform.init(empty=True) platform.ENTITIES["0"] = platform.MockSensor( - name="Test", native_value=native_value, device_class=device_class + name="Test", native_value=state_value, device_class=device_class ) - entity0 = platform.ENTITIES["0"] assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() - state = hass.states.get(entity0.entity_id) - assert state.state == state_value assert ( - "is providing a string for its state, while the device class is " - f"'{device_class}', this is not valid and will be unsupported " - "from Home Assistant 2022.2." + f"Invalid {provides}: sensor.test has a {device_class} device class " + f"but does not provide a {provides} state but {type(state_value)}" ) in caplog.text From 313387fda53bec73c380dec992ca8e4f9407bc1a Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 5 Feb 2022 08:42:57 +0100 Subject: [PATCH 0296/1098] Remove deprecated GNTP integration (#65741) --- .coveragerc | 1 - homeassistant/components/gntp/__init__.py | 1 - homeassistant/components/gntp/manifest.json | 9 -- homeassistant/components/gntp/notify.py | 96 --------------------- requirements_all.txt | 3 - 5 files changed, 110 deletions(-) delete mode 100644 homeassistant/components/gntp/__init__.py delete mode 100644 homeassistant/components/gntp/manifest.json delete mode 100644 homeassistant/components/gntp/notify.py diff --git a/.coveragerc b/.coveragerc index a3e2294e5ab66e..003d148a702abe 100644 --- a/.coveragerc +++ b/.coveragerc @@ -401,7 +401,6 @@ omit = homeassistant/components/glances/__init__.py homeassistant/components/glances/const.py homeassistant/components/glances/sensor.py - homeassistant/components/gntp/notify.py homeassistant/components/goalfeed/* homeassistant/components/goodwe/__init__.py homeassistant/components/goodwe/const.py diff --git a/homeassistant/components/gntp/__init__.py b/homeassistant/components/gntp/__init__.py deleted file mode 100644 index c2814f86f06d87..00000000000000 --- a/homeassistant/components/gntp/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""The gntp component.""" diff --git a/homeassistant/components/gntp/manifest.json b/homeassistant/components/gntp/manifest.json deleted file mode 100644 index 3a5f4fb8daab97..00000000000000 --- a/homeassistant/components/gntp/manifest.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "domain": "gntp", - "name": "Growl (GnGNTP)", - "documentation": "https://www.home-assistant.io/integrations/gntp", - "requirements": ["gntp==1.0.3"], - "codeowners": [], - "iot_class": "local_push", - "loggers": ["gntp"] -} diff --git a/homeassistant/components/gntp/notify.py b/homeassistant/components/gntp/notify.py deleted file mode 100644 index b3291e256174c6..00000000000000 --- a/homeassistant/components/gntp/notify.py +++ /dev/null @@ -1,96 +0,0 @@ -"""GNTP (aka Growl) notification service.""" -import logging -import os - -import gntp.errors -import gntp.notifier -import voluptuous as vol - -from homeassistant.components.notify import ( - ATTR_TITLE, - ATTR_TITLE_DEFAULT, - PLATFORM_SCHEMA, - BaseNotificationService, -) -from homeassistant.const import CONF_PASSWORD, CONF_PORT -import homeassistant.helpers.config_validation as cv - -_LOGGER = logging.getLogger(__name__) - -CONF_APP_NAME = "app_name" -CONF_APP_ICON = "app_icon" -CONF_HOSTNAME = "hostname" - -DEFAULT_APP_NAME = "HomeAssistant" -DEFAULT_HOST = "localhost" -DEFAULT_PORT = 23053 - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_APP_NAME, default=DEFAULT_APP_NAME): cv.string, - vol.Optional(CONF_APP_ICON): vol.Url, - vol.Optional(CONF_HOSTNAME, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_PASSWORD): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - } -) - - -def get_service(hass, config, discovery_info=None): - """Get the GNTP notification service.""" - _LOGGER.warning( - "The GNTP (Growl) integration has been deprecated and is going to be " - "removed in Home Assistant Core 2021.6. The Growl project has retired" - ) - - logging.getLogger("gntp").setLevel(logging.ERROR) - - if config.get(CONF_APP_ICON) is None: - icon_file = os.path.join( - os.path.dirname(__file__), - "..", - "frontend", - "www_static", - "icons", - "favicon-192x192.png", - ) - with open(icon_file, "rb") as file: - app_icon = file.read() - else: - app_icon = config.get(CONF_APP_ICON) - - return GNTPNotificationService( - config.get(CONF_APP_NAME), - app_icon, - config.get(CONF_HOSTNAME), - config.get(CONF_PASSWORD), - config.get(CONF_PORT), - ) - - -class GNTPNotificationService(BaseNotificationService): - """Implement the notification service for GNTP.""" - - def __init__(self, app_name, app_icon, hostname, password, port): - """Initialize the service.""" - self.gntp = gntp.notifier.GrowlNotifier( - applicationName=app_name, - notifications=["Notification"], - applicationIcon=app_icon, - hostname=hostname, - password=password, - port=port, - ) - try: - self.gntp.register() - except gntp.errors.NetworkError: - _LOGGER.error("Unable to register with the GNTP host") - return - - def send_message(self, message="", **kwargs): - """Send a message to a user.""" - self.gntp.notify( - noteType="Notification", - title=kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT), - description=message, - ) diff --git a/requirements_all.txt b/requirements_all.txt index 268bdad0ce7717..6ed8342b0e6503 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -745,9 +745,6 @@ gitterpy==0.1.7 # homeassistant.components.glances glances_api==0.3.4 -# homeassistant.components.gntp -gntp==1.0.3 - # homeassistant.components.goalzero goalzero==0.2.1 From 6871271e7328b8cbf1a2c5c6604f292f43780dcd Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 5 Feb 2022 08:44:31 +0100 Subject: [PATCH 0297/1098] Small cleanup in Plugwise binary sensors (#65738) --- .../components/plugwise/binary_sensor.py | 81 ++++++++++--------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index bf72d9edc31e4f..e8bb0f3366a904 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -1,10 +1,13 @@ """Plugwise Binary Sensor component for Home Assistant.""" import logging +from plugwise.smile import Smile + from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import ( COORDINATOR, @@ -33,8 +36,10 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Smile binary_sensors from a config entry.""" - api = hass.data[DOMAIN][config_entry.entry_id]["api"] - coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] + api: Smile = hass.data[DOMAIN][config_entry.entry_id]["api"] + coordinator: DataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id][ + COORDINATOR + ] entities: list[BinarySensorEntity] = [] is_thermostat = api.single_master_thermostat() @@ -72,17 +77,21 @@ async def async_setup_entry( async_add_entities(entities, True) -class SmileBinarySensor(SmileGateway): +class SmileBinarySensor(SmileGateway, BinarySensorEntity): """Represent Smile Binary Sensors.""" - def __init__(self, api, coordinator, name, dev_id, binary_sensor): + def __init__( + self, + api: Smile, + coordinator: DataUpdateCoordinator, + name: str, + dev_id: str, + binary_sensor: str, + ) -> None: """Initialise the binary_sensor.""" super().__init__(api, coordinator, name, dev_id) - self._binary_sensor = binary_sensor - - self._icon = None - self._is_on = False + self._attr_is_on = False if dev_id == self._api.heater_id: self._entity_name = "Auxiliary" @@ -95,27 +104,17 @@ def __init__(self, api, coordinator, name, dev_id, binary_sensor): self._unique_id = f"{dev_id}-{binary_sensor}" - @property - def icon(self): - """Return the icon of this entity.""" - return self._icon - - @property - def is_on(self): - """Return true if the binary sensor is on.""" - return self._is_on - @callback - def _async_process_data(self): + def _async_process_data(self) -> None: """Update the entity.""" raise NotImplementedError -class PwBinarySensor(SmileBinarySensor, BinarySensorEntity): +class PwBinarySensor(SmileBinarySensor): """Representation of a Plugwise binary_sensor.""" @callback - def _async_process_data(self): + def _async_process_data(self) -> None: """Update the entity.""" if not (data := self._api.get_device_data(self._dev_id)): _LOGGER.error("Received no data for device %s", self._binary_sensor) @@ -126,50 +125,54 @@ def _async_process_data(self): self.async_write_ha_state() return - self._is_on = data[self._binary_sensor] + self._attr_is_on = data[self._binary_sensor] if self._binary_sensor == "dhw_state": - self._icon = FLOW_ON_ICON if self._is_on else FLOW_OFF_ICON + self._attr_icon = FLOW_ON_ICON if self._attr_is_on else FLOW_OFF_ICON if self._binary_sensor == "slave_boiler_state": - self._icon = FLAME_ICON if self._is_on else IDLE_ICON + self._attr_icon = FLAME_ICON if self._attr_is_on else IDLE_ICON self.async_write_ha_state() -class PwNotifySensor(SmileBinarySensor, BinarySensorEntity): +class PwNotifySensor(SmileBinarySensor): """Representation of a Plugwise Notification binary_sensor.""" - def __init__(self, api, coordinator, name, dev_id, binary_sensor): + def __init__( + self, + api: Smile, + coordinator: DataUpdateCoordinator, + name: str, + dev_id: str, + binary_sensor: str, + ) -> None: """Set up the Plugwise API.""" super().__init__(api, coordinator, name, dev_id, binary_sensor) - self._attributes = {} - - @property - def extra_state_attributes(self): - """Return the state attributes.""" - return self._attributes + self._attr_extra_state_attributes = {} @callback - def _async_process_data(self): + def _async_process_data(self) -> None: """Update the entity.""" notify = self._api.notifications for severity in SEVERITIES: - self._attributes[f"{severity}_msg"] = [] + self._attr_extra_state_attributes[f"{severity}_msg"] = [] - self._is_on = False - self._icon = NO_NOTIFICATION_ICON + self._attr_is_on = False + self._attr_icon = NO_NOTIFICATION_ICON if notify: - self._is_on = True - self._icon = NOTIFICATION_ICON + self._attr_is_on = True + self._attr_icon = NOTIFICATION_ICON for details in notify.values(): for msg_type, msg in details.items(): if msg_type not in SEVERITIES: msg_type = "other" - self._attributes[f"{msg_type.lower()}_msg"].append(msg) + self._attr_extra_state_attributes[f"{msg_type.lower()}_msg"].append( + msg + ) self.async_write_ha_state() From bf0816d4c665ac8cbb5d7197c30403d819bd6068 Mon Sep 17 00:00:00 2001 From: dougiteixeira <31328123+dougiteixeira@users.noreply.github.com> Date: Sat, 5 Feb 2022 07:19:29 -0300 Subject: [PATCH 0298/1098] Add current temperature sensor for Tuya Fan (fs) (#65744) --- homeassistant/components/tuya/select.py | 4 +++- homeassistant/components/tuya/sensor.py | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/tuya/select.py b/homeassistant/components/tuya/select.py index b0b7aeec2be4a7..a3e1d0439ae63d 100644 --- a/homeassistant/components/tuya/select.py +++ b/homeassistant/components/tuya/select.py @@ -247,12 +247,14 @@ SelectEntityDescription( key=DPCode.COUNTDOWN, name="Countdown", + device_class=TuyaDeviceClass.COUNTDOWN, entity_category=EntityCategory.CONFIG, icon="mdi:timer-cog-outline", ), SelectEntityDescription( key=DPCode.COUNTDOWN_SET, - name="Countdown Setting", + name="Countdown", + device_class=TuyaDeviceClass.COUNTDOWN, entity_category=EntityCategory.CONFIG, icon="mdi:timer-cog-outline", ), diff --git a/homeassistant/components/tuya/sensor.py b/homeassistant/components/tuya/sensor.py index e415d26ad4cac1..effb5a1443b91e 100644 --- a/homeassistant/components/tuya/sensor.py +++ b/homeassistant/components/tuya/sensor.py @@ -803,6 +803,16 @@ class TuyaSensorEntityDescription(SensorEntityDescription): device_class=TuyaDeviceClass.AIR_QUALITY, ), ), + # Fan + # https://developer.tuya.com/en/docs/iot/s?id=K9gf48quojr54 + "fs": ( + TuyaSensorEntityDescription( + key=DPCode.TEMP_CURRENT, + name="Temperature", + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + ), + ), } # Socket (duplicate of `kg`) From b1bf9b50d894c97fcbcd558d8c1bd6fb0334ed93 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Sat, 5 Feb 2022 10:46:52 +0000 Subject: [PATCH 0299/1098] Fix OVO Energy NoneType error occurring for some users (#65714) --- homeassistant/components/ovo_energy/sensor.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/ovo_energy/sensor.py b/homeassistant/components/ovo_energy/sensor.py index ba332a08a162f6..8f9a18d1f113a0 100644 --- a/homeassistant/components/ovo_energy/sensor.py +++ b/homeassistant/components/ovo_energy/sensor.py @@ -121,14 +121,22 @@ async def async_setup_entry( if coordinator.data: if coordinator.data.electricity: for description in SENSOR_TYPES_ELECTRICITY: - if description.key == KEY_LAST_ELECTRICITY_COST: + if ( + description.key == KEY_LAST_ELECTRICITY_COST + and coordinator.data.electricity[-1] is not None + and coordinator.data.electricity[-1].cost is not None + ): description.native_unit_of_measurement = ( coordinator.data.electricity[-1].cost.currency_unit ) entities.append(OVOEnergySensor(coordinator, description, client)) if coordinator.data.gas: for description in SENSOR_TYPES_GAS: - if description.key == KEY_LAST_GAS_COST: + if ( + description.key == KEY_LAST_GAS_COST + and coordinator.data.gas[-1] is not None + and coordinator.data.gas[-1].cost is not None + ): description.native_unit_of_measurement = coordinator.data.gas[ -1 ].cost.currency_unit From 5613a80d2871726d5f292e492a24db41e4dc6c39 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 5 Feb 2022 12:09:29 +0100 Subject: [PATCH 0300/1098] Add Heater (rs) support Tuya Climate (#65707) --- homeassistant/components/tuya/climate.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/homeassistant/components/tuya/climate.py b/homeassistant/components/tuya/climate.py index 17e6e967a34710..12241a00513ef2 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -73,6 +73,12 @@ class TuyaClimateEntityDescription( key="qn", switch_only_hvac_mode=HVAC_MODE_HEAT, ), + # Heater + # https://developer.tuya.com/en/docs/iot/categoryrs?id=Kaiuz0nfferyx + "rs": TuyaClimateEntityDescription( + key="rs", + switch_only_hvac_mode=HVAC_MODE_HEAT, + ), # Thermostat # https://developer.tuya.com/en/docs/iot/f?id=K9gf45ld5l0t9 "wk": TuyaClimateEntityDescription( From 07edbc42a48a4ccedab660ec20fa0e93fe79ad46 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 5 Feb 2022 12:53:27 +0100 Subject: [PATCH 0301/1098] Bugfix temp step list out of range sensibo (#65782) --- homeassistant/components/sensibo/coordinator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensibo/coordinator.py b/homeassistant/components/sensibo/coordinator.py index 4781250874f402..ebdc40f6f126cf 100644 --- a/homeassistant/components/sensibo/coordinator.py +++ b/homeassistant/components/sensibo/coordinator.py @@ -68,7 +68,7 @@ async def _async_update_data(self) -> dict[str, dict[str, Any]]: temperatures_list = ( current_capabilities["temperatures"] .get(temperature_unit_key, {}) - .get("values", [0]) + .get("values", [0, 1]) ) if temperatures_list: temperature_step = temperatures_list[1] - temperatures_list[0] From 54e1e905b1c41c47f6772b7a63685e5e7ed98f92 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 5 Feb 2022 13:00:56 +0100 Subject: [PATCH 0302/1098] Add capabilities to sensibo coordinator data (#65775) --- homeassistant/components/sensibo/coordinator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/sensibo/coordinator.py b/homeassistant/components/sensibo/coordinator.py index ebdc40f6f126cf..9509be88266ee1 100644 --- a/homeassistant/components/sensibo/coordinator.py +++ b/homeassistant/components/sensibo/coordinator.py @@ -107,5 +107,6 @@ async def _async_update_data(self) -> dict[str, dict[str, Any]]: "model": model, "calibration_temp": calibration_temp, "calibration_hum": calibration_hum, + "full_capabilities": capabilities, } return device_data From daedbbb1ee86a731158a7ce294078b892e5b8fe7 Mon Sep 17 00:00:00 2001 From: Patrik Lindgren <21142447+ggravlingen@users.noreply.github.com> Date: Sat, 5 Feb 2022 13:25:50 +0100 Subject: [PATCH 0303/1098] Bump pytradfri to 9.0.0 (#65784) * Bump pytradfri to 8.1.0 * Bump to 9.0.0 * Bump manifest --- homeassistant/components/tradfri/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tradfri/manifest.json b/homeassistant/components/tradfri/manifest.json index 1950b00a079828..ae4eb460c6cf6e 100644 --- a/homeassistant/components/tradfri/manifest.json +++ b/homeassistant/components/tradfri/manifest.json @@ -3,7 +3,7 @@ "name": "IKEA TR\u00c5DFRI", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tradfri", - "requirements": ["pytradfri[async]==8.0.1"], + "requirements": ["pytradfri[async]==9.0.0"], "homekit": { "models": ["TRADFRI"] }, diff --git a/requirements_all.txt b/requirements_all.txt index 6ed8342b0e6503..b01acde1717299 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2005,7 +2005,7 @@ pytouchline==0.7 pytraccar==0.10.0 # homeassistant.components.tradfri -pytradfri[async]==8.0.1 +pytradfri[async]==9.0.0 # homeassistant.components.trafikverket_train # homeassistant.components.trafikverket_weatherstation diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a2f2194c0a2e73..b8595653a364bc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1239,7 +1239,7 @@ pytile==2022.02.0 pytraccar==0.10.0 # homeassistant.components.tradfri -pytradfri[async]==8.0.1 +pytradfri[async]==9.0.0 # homeassistant.components.trafikverket_train # homeassistant.components.trafikverket_weatherstation From efd7b2a9787dca4228b9827938096e9e328c6e39 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 5 Feb 2022 13:28:32 +0100 Subject: [PATCH 0304/1098] Update apprise to 0.9.7 (#65780) --- homeassistant/components/apprise/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/apprise/manifest.json b/homeassistant/components/apprise/manifest.json index f060bf8d8c61c9..21e2ed7c94d611 100644 --- a/homeassistant/components/apprise/manifest.json +++ b/homeassistant/components/apprise/manifest.json @@ -2,7 +2,7 @@ "domain": "apprise", "name": "Apprise", "documentation": "https://www.home-assistant.io/integrations/apprise", - "requirements": ["apprise==0.9.6"], + "requirements": ["apprise==0.9.7"], "codeowners": ["@caronc"], "iot_class": "cloud_push", "loggers": ["apprise"] diff --git a/requirements_all.txt b/requirements_all.txt index b01acde1717299..8293181d523022 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -326,7 +326,7 @@ apcaccess==0.0.13 apns2==0.3.0 # homeassistant.components.apprise -apprise==0.9.6 +apprise==0.9.7 # homeassistant.components.aprs aprslib==0.7.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b8595653a364bc..8de02cc3a648b2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -243,7 +243,7 @@ androidtv[async]==0.0.63 apns2==0.3.0 # homeassistant.components.apprise -apprise==0.9.6 +apprise==0.9.7 # homeassistant.components.aprs aprslib==0.7.0 From e386f4846d32092f26de8f85bca78b00e5f416f4 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 5 Feb 2022 13:31:04 +0100 Subject: [PATCH 0305/1098] Update delijn to 1.0.0 (#65776) * Update delijn to 1.0.0 * -1 --- homeassistant/components/delijn/manifest.json | 2 +- homeassistant/components/delijn/sensor.py | 1 - requirements_all.txt | 2 +- script/pip_check | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/delijn/manifest.json b/homeassistant/components/delijn/manifest.json index 1209dff749546e..07fa93d976c48d 100644 --- a/homeassistant/components/delijn/manifest.json +++ b/homeassistant/components/delijn/manifest.json @@ -3,7 +3,7 @@ "name": "De Lijn", "documentation": "https://www.home-assistant.io/integrations/delijn", "codeowners": ["@bollewolle", "@Emilv2"], - "requirements": ["pydelijn==0.6.1"], + "requirements": ["pydelijn==1.0.0"], "iot_class": "cloud_polling", "loggers": ["pydelijn"] } diff --git a/homeassistant/components/delijn/sensor.py b/homeassistant/components/delijn/sensor.py index dea3fcafcded3d..e04385dcf3d0ac 100644 --- a/homeassistant/components/delijn/sensor.py +++ b/homeassistant/components/delijn/sensor.py @@ -67,7 +67,6 @@ async def async_setup_platform( sensors.append( DeLijnPublicTransportSensor( Passages( - hass.loop, nextpassage[CONF_STOP_ID], nextpassage[CONF_NUMBER_OF_DEPARTURES], api_key, diff --git a/requirements_all.txt b/requirements_all.txt index 8293181d523022..08f69d1a9dc1c3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1464,7 +1464,7 @@ pydanfossair==0.1.0 pydeconz==86 # homeassistant.components.delijn -pydelijn==0.6.1 +pydelijn==1.0.0 # homeassistant.components.dexcom pydexcom==0.2.2 diff --git a/script/pip_check b/script/pip_check index 14be2d1a7964e4..bd988b50e3a0ec 100755 --- a/script/pip_check +++ b/script/pip_check @@ -3,7 +3,7 @@ PIP_CACHE=$1 # Number of existing dependency conflicts # Update if a PR resolve one! -DEPENDENCY_CONFLICTS=12 +DEPENDENCY_CONFLICTS=11 PIP_CHECK=$(pip check --cache-dir=$PIP_CACHE) LINE_COUNT=$(echo "$PIP_CHECK" | wc -l) From 3373b7332951bfac79d1f0cbc2717c8a9b137bd1 Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 5 Feb 2022 14:10:07 +0100 Subject: [PATCH 0306/1098] Update pyfritzhome to 0.6.4 (#65777) --- homeassistant/components/fritzbox/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/fritzbox/manifest.json b/homeassistant/components/fritzbox/manifest.json index 26dc9f65bc2c45..1dac4ddd78a5ed 100644 --- a/homeassistant/components/fritzbox/manifest.json +++ b/homeassistant/components/fritzbox/manifest.json @@ -2,7 +2,7 @@ "domain": "fritzbox", "name": "AVM FRITZ!SmartHome", "documentation": "https://www.home-assistant.io/integrations/fritzbox", - "requirements": ["pyfritzhome==0.6.2"], + "requirements": ["pyfritzhome==0.6.4"], "ssdp": [ { "st": "urn:schemas-upnp-org:device:fritzbox:1" diff --git a/requirements_all.txt b/requirements_all.txt index 08f69d1a9dc1c3..f2f1e67bd17d29 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1536,7 +1536,7 @@ pyforked-daapd==0.1.11 pyfreedompro==1.1.0 # homeassistant.components.fritzbox -pyfritzhome==0.6.2 +pyfritzhome==0.6.4 # homeassistant.components.fronius pyfronius==0.7.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8de02cc3a648b2..786c4683b7181b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -953,7 +953,7 @@ pyforked-daapd==0.1.11 pyfreedompro==1.1.0 # homeassistant.components.fritzbox -pyfritzhome==0.6.2 +pyfritzhome==0.6.4 # homeassistant.components.fronius pyfronius==0.7.1 From 6cf0d9d37a46397b2cdf9ad8a6fec3a4e2d99a15 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 5 Feb 2022 14:14:31 +0100 Subject: [PATCH 0307/1098] Update sentry-dsk to 1.5.4 (#65792) --- homeassistant/components/sentry/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sentry/manifest.json b/homeassistant/components/sentry/manifest.json index 9c90067efef4c9..c74860c7f6409e 100644 --- a/homeassistant/components/sentry/manifest.json +++ b/homeassistant/components/sentry/manifest.json @@ -3,7 +3,7 @@ "name": "Sentry", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sentry", - "requirements": ["sentry-sdk==1.5.3"], + "requirements": ["sentry-sdk==1.5.4"], "codeowners": ["@dcramer", "@frenck"], "iot_class": "cloud_polling" } diff --git a/requirements_all.txt b/requirements_all.txt index f2f1e67bd17d29..bcada9af42b5c0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2172,7 +2172,7 @@ sense-hat==2.2.0 sense_energy==0.9.6 # homeassistant.components.sentry -sentry-sdk==1.5.3 +sentry-sdk==1.5.4 # homeassistant.components.sharkiq sharkiqpy==0.1.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 786c4683b7181b..3dad22474dbb32 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1334,7 +1334,7 @@ screenlogicpy==0.5.4 sense_energy==0.9.6 # homeassistant.components.sentry -sentry-sdk==1.5.3 +sentry-sdk==1.5.4 # homeassistant.components.sharkiq sharkiqpy==0.1.8 From 58409d0895a467f144e8f82ef11538564cfaf81d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 5 Feb 2022 14:19:24 +0100 Subject: [PATCH 0308/1098] Update Pillow to 9.0.1 (#65779) --- homeassistant/components/doods/manifest.json | 2 +- homeassistant/components/image/manifest.json | 2 +- homeassistant/components/proxy/manifest.json | 2 +- homeassistant/components/qrcode/manifest.json | 2 +- homeassistant/components/seven_segments/manifest.json | 2 +- homeassistant/components/sighthound/manifest.json | 2 +- homeassistant/components/tensorflow/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/doods/manifest.json b/homeassistant/components/doods/manifest.json index fe451db44b861b..a8be4e4fcdb2b9 100644 --- a/homeassistant/components/doods/manifest.json +++ b/homeassistant/components/doods/manifest.json @@ -2,7 +2,7 @@ "domain": "doods", "name": "DOODS - Dedicated Open Object Detection Service", "documentation": "https://www.home-assistant.io/integrations/doods", - "requirements": ["pydoods==1.0.2", "pillow==9.0.0"], + "requirements": ["pydoods==1.0.2", "pillow==9.0.1"], "codeowners": [], "iot_class": "local_polling", "loggers": ["pydoods"] diff --git a/homeassistant/components/image/manifest.json b/homeassistant/components/image/manifest.json index 5f624ea8e1cbab..2363b124e4323e 100644 --- a/homeassistant/components/image/manifest.json +++ b/homeassistant/components/image/manifest.json @@ -3,7 +3,7 @@ "name": "Image", "config_flow": false, "documentation": "https://www.home-assistant.io/integrations/image", - "requirements": ["pillow==9.0.0"], + "requirements": ["pillow==9.0.1"], "dependencies": ["http"], "codeowners": ["@home-assistant/core"], "quality_scale": "internal" diff --git a/homeassistant/components/proxy/manifest.json b/homeassistant/components/proxy/manifest.json index 4f19e6afae2ac8..d1be59ebc87002 100644 --- a/homeassistant/components/proxy/manifest.json +++ b/homeassistant/components/proxy/manifest.json @@ -2,6 +2,6 @@ "domain": "proxy", "name": "Camera Proxy", "documentation": "https://www.home-assistant.io/integrations/proxy", - "requirements": ["pillow==9.0.0"], + "requirements": ["pillow==9.0.1"], "codeowners": [] } diff --git a/homeassistant/components/qrcode/manifest.json b/homeassistant/components/qrcode/manifest.json index cb1f3a176a4242..259b3ec3b7bc20 100644 --- a/homeassistant/components/qrcode/manifest.json +++ b/homeassistant/components/qrcode/manifest.json @@ -2,7 +2,7 @@ "domain": "qrcode", "name": "QR Code", "documentation": "https://www.home-assistant.io/integrations/qrcode", - "requirements": ["pillow==9.0.0", "pyzbar==0.1.7"], + "requirements": ["pillow==9.0.1", "pyzbar==0.1.7"], "codeowners": [], "iot_class": "calculated", "loggers": ["pyzbar"] diff --git a/homeassistant/components/seven_segments/manifest.json b/homeassistant/components/seven_segments/manifest.json index a49a471038ce7f..db8e57673b1aed 100644 --- a/homeassistant/components/seven_segments/manifest.json +++ b/homeassistant/components/seven_segments/manifest.json @@ -2,7 +2,7 @@ "domain": "seven_segments", "name": "Seven Segments OCR", "documentation": "https://www.home-assistant.io/integrations/seven_segments", - "requirements": ["pillow==9.0.0"], + "requirements": ["pillow==9.0.1"], "codeowners": ["@fabaff"], "iot_class": "local_polling" } diff --git a/homeassistant/components/sighthound/manifest.json b/homeassistant/components/sighthound/manifest.json index 817bdaccd3c8f1..92baec1e42b26c 100644 --- a/homeassistant/components/sighthound/manifest.json +++ b/homeassistant/components/sighthound/manifest.json @@ -2,7 +2,7 @@ "domain": "sighthound", "name": "Sighthound", "documentation": "https://www.home-assistant.io/integrations/sighthound", - "requirements": ["pillow==9.0.0", "simplehound==0.3"], + "requirements": ["pillow==9.0.1", "simplehound==0.3"], "codeowners": ["@robmarkcole"], "iot_class": "cloud_polling", "loggers": ["simplehound"] diff --git a/homeassistant/components/tensorflow/manifest.json b/homeassistant/components/tensorflow/manifest.json index 771d6f6fd9dd56..6d8f50c81bfcbb 100644 --- a/homeassistant/components/tensorflow/manifest.json +++ b/homeassistant/components/tensorflow/manifest.json @@ -7,7 +7,7 @@ "tf-models-official==2.5.0", "pycocotools==2.0.1", "numpy==1.21.4", - "pillow==9.0.0" + "pillow==9.0.1" ], "codeowners": [], "iot_class": "local_polling", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 3f0b9516eadecc..5cded6a179d9e1 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -20,7 +20,7 @@ httpx==0.21.3 ifaddr==0.1.7 jinja2==3.0.3 paho-mqtt==1.6.1 -pillow==9.0.0 +pillow==9.0.1 pip>=8.0.3,<20.3 pyserial==3.5 python-slugify==4.0.1 diff --git a/requirements_all.txt b/requirements_all.txt index bcada9af42b5c0..a886a0f98b34ef 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1246,7 +1246,7 @@ pilight==0.1.1 # homeassistant.components.seven_segments # homeassistant.components.sighthound # homeassistant.components.tensorflow -pillow==9.0.0 +pillow==9.0.1 # homeassistant.components.dominos pizzapi==0.0.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3dad22474dbb32..74b3e53e8c8950 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -771,7 +771,7 @@ pilight==0.1.1 # homeassistant.components.seven_segments # homeassistant.components.sighthound # homeassistant.components.tensorflow -pillow==9.0.0 +pillow==9.0.1 # homeassistant.components.plex plexapi==4.9.1 From fa09cf663e759ccc94afc972e98a6bae57e8385e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 5 Feb 2022 14:19:37 +0100 Subject: [PATCH 0309/1098] Update black to 22.1.0 (#65788) --- .pre-commit-config.yaml | 2 +- homeassistant/components/apple_tv/__init__.py | 2 +- homeassistant/components/climacell/const.py | 8 +- homeassistant/components/conversation/util.py | 4 +- homeassistant/components/cpuspeed/sensor.py | 4 +- homeassistant/components/enocean/sensor.py | 2 +- homeassistant/components/enocean/switch.py | 2 +- homeassistant/components/fail2ban/sensor.py | 2 +- homeassistant/components/fritz/switch.py | 9 +- homeassistant/components/glances/const.py | 2 +- homeassistant/components/glances/sensor.py | 16 ++-- .../components/google_assistant/trait.py | 12 +-- .../components/homekit/accessories.py | 12 +-- homeassistant/components/http/__init__.py | 2 +- .../components/integration/sensor.py | 2 +- homeassistant/components/knx/schema.py | 2 +- .../components/netgear_lte/sensor.py | 2 +- homeassistant/components/nzbget/sensor.py | 2 +- .../components/pandora/media_player.py | 2 +- homeassistant/components/pyload/sensor.py | 2 +- homeassistant/components/sonarr/sensor.py | 6 +- .../components/speedtestdotnet/const.py | 4 +- homeassistant/components/startca/sensor.py | 2 +- .../components/synology_dsm/sensor.py | 4 +- .../components/system_bridge/sensor.py | 8 +- .../components/systemmonitor/sensor.py | 18 ++-- homeassistant/components/tuya/base.py | 4 +- homeassistant/components/waqi/sensor.py | 14 +-- homeassistant/components/zha/climate.py | 12 +-- homeassistant/core.py | 2 +- homeassistant/helpers/__init__.py | 2 +- homeassistant/helpers/template.py | 2 +- homeassistant/scripts/benchmark/__init__.py | 24 ++--- homeassistant/util/location.py | 12 +-- requirements_test_pre_commit.txt | 2 +- tests/components/blueprint/test_models.py | 21 ++--- tests/helpers/test_condition.py | 93 +++++++++---------- tests/test_config.py | 52 +++++------ tests/test_util/aiohttp.py | 2 +- tests/util/test_color.py | 40 ++++---- tests/util/test_json.py | 33 +++---- tests/util/yaml/test_input.py | 11 +-- 42 files changed, 204 insertions(+), 255 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 72716f15dfd761..f16a65fb4c0784 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ repos: - id: pyupgrade args: [--py39-plus] - repo: https://github.com/psf/black - rev: 21.12b0 + rev: 22.1.0 hooks: - id: black args: diff --git a/homeassistant/components/apple_tv/__init__.py b/homeassistant/components/apple_tv/__init__.py index bd511d84eb5364..d61c21972fbe1e 100644 --- a/homeassistant/components/apple_tv/__init__.py +++ b/homeassistant/components/apple_tv/__init__.py @@ -242,7 +242,7 @@ async def _connect_loop(self): backoff = min( max( BACKOFF_TIME_LOWER_LIMIT, - randrange(2 ** self._connection_attempts), + randrange(2**self._connection_attempts), ), BACKOFF_TIME_UPPER_LIMIT, ) diff --git a/homeassistant/components/climacell/const.py b/homeassistant/components/climacell/const.py index 69567bf65fc5b5..7ee804f42f188d 100644 --- a/homeassistant/components/climacell/const.py +++ b/homeassistant/components/climacell/const.py @@ -260,7 +260,7 @@ def __post_init__(self) -> None: name="Particulate Matter < 2.5 μm", unit_imperial=CONCENTRATION_MICROGRAMS_PER_CUBIC_FOOT, unit_metric=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - metric_conversion=3.2808399 ** 3, + metric_conversion=3.2808399**3, is_metric_check=True, ), ClimaCellSensorEntityDescription( @@ -268,7 +268,7 @@ def __post_init__(self) -> None: name="Particulate Matter < 10 μm", unit_imperial=CONCENTRATION_MICROGRAMS_PER_CUBIC_FOOT, unit_metric=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - metric_conversion=3.2808399 ** 3, + metric_conversion=3.2808399**3, is_metric_check=True, ), ClimaCellSensorEntityDescription( @@ -424,7 +424,7 @@ def __post_init__(self) -> None: name="Particulate Matter < 2.5 μm", unit_imperial=CONCENTRATION_MICROGRAMS_PER_CUBIC_FOOT, unit_metric=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - metric_conversion=3.2808399 ** 3, + metric_conversion=3.2808399**3, is_metric_check=False, ), ClimaCellSensorEntityDescription( @@ -432,7 +432,7 @@ def __post_init__(self) -> None: name="Particulate Matter < 10 μm", unit_imperial=CONCENTRATION_MICROGRAMS_PER_CUBIC_FOOT, unit_metric=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - metric_conversion=3.2808399 ** 3, + metric_conversion=3.2808399**3, is_metric_check=False, ), ClimaCellSensorEntityDescription( diff --git a/homeassistant/components/conversation/util.py b/homeassistant/components/conversation/util.py index b21b75be9b580b..2e931d835a8dda 100644 --- a/homeassistant/components/conversation/util.py +++ b/homeassistant/components/conversation/util.py @@ -24,11 +24,11 @@ def create_matcher(utterance): # Group part if group_match is not None: - pattern.append(fr"(?P<{group_match.groups()[0]}>[\w ]+?)\s*") + pattern.append(rf"(?P<{group_match.groups()[0]}>[\w ]+?)\s*") # Optional part elif optional_match is not None: - pattern.append(fr"(?:{optional_match.groups()[0]} *)?") + pattern.append(rf"(?:{optional_match.groups()[0]} *)?") pattern.append("$") return re.compile("".join(pattern), re.I) diff --git a/homeassistant/components/cpuspeed/sensor.py b/homeassistant/components/cpuspeed/sensor.py index 86c02ae9ee9da0..686a7c13e58ad1 100644 --- a/homeassistant/components/cpuspeed/sensor.py +++ b/homeassistant/components/cpuspeed/sensor.py @@ -75,7 +75,7 @@ def update(self) -> None: info = cpuinfo.get_cpu_info() if info and HZ_ACTUAL in info: - self._attr_native_value = round(float(info[HZ_ACTUAL][0]) / 10 ** 9, 2) + self._attr_native_value = round(float(info[HZ_ACTUAL][0]) / 10**9, 2) else: self._attr_native_value = None @@ -86,5 +86,5 @@ def update(self) -> None: } if HZ_ADVERTISED in info: self._attr_extra_state_attributes[ATTR_HZ] = round( - info[HZ_ADVERTISED][0] / 10 ** 9, 2 + info[HZ_ADVERTISED][0] / 10**9, 2 ) diff --git a/homeassistant/components/enocean/sensor.py b/homeassistant/components/enocean/sensor.py index 87bcd685a1f7c3..e48f117648ab87 100644 --- a/homeassistant/components/enocean/sensor.py +++ b/homeassistant/components/enocean/sensor.py @@ -168,7 +168,7 @@ def value_changed(self, packet): # this packet reports the current value raw_val = packet.parsed["MR"]["raw_value"] divisor = packet.parsed["DIV"]["raw_value"] - self._attr_native_value = raw_val / (10 ** divisor) + self._attr_native_value = raw_val / (10**divisor) self.schedule_update_ha_state() diff --git a/homeassistant/components/enocean/switch.py b/homeassistant/components/enocean/switch.py index b86b6844551816..fc788e88d72ada 100644 --- a/homeassistant/components/enocean/switch.py +++ b/homeassistant/components/enocean/switch.py @@ -91,7 +91,7 @@ def value_changed(self, packet): if packet.parsed["DT"]["raw_value"] == 1: raw_val = packet.parsed["MR"]["raw_value"] divisor = packet.parsed["DIV"]["raw_value"] - watts = raw_val / (10 ** divisor) + watts = raw_val / (10**divisor) if watts > 1: self._on_state = True self.schedule_update_ha_state() diff --git a/homeassistant/components/fail2ban/sensor.py b/homeassistant/components/fail2ban/sensor.py index 1063dfda08af06..f74dc7690ca163 100644 --- a/homeassistant/components/fail2ban/sensor.py +++ b/homeassistant/components/fail2ban/sensor.py @@ -65,7 +65,7 @@ def __init__(self, name, jail, log_parser): self.last_ban = None self.log_parser = log_parser self.log_parser.ip_regex[self.jail] = re.compile( - fr"\[{re.escape(self.jail)}\]\s*(Ban|Unban) (.*)" + rf"\[{re.escape(self.jail)}\]\s*(Ban|Unban) (.*)" ) _LOGGER.debug("Setting up jail %s", self.jail) diff --git a/homeassistant/components/fritz/switch.py b/homeassistant/components/fritz/switch.py index fec760fbe7aacc..59cb75901a2a57 100644 --- a/homeassistant/components/fritz/switch.py +++ b/homeassistant/components/fritz/switch.py @@ -158,12 +158,9 @@ def wifi_entities_list( if network_info: ssid = network_info["NewSSID"] _LOGGER.debug("SSID from device: <%s>", ssid) - if ( - slugify( - ssid, - ) - in [slugify(v) for v in networks.values()] - ): + if slugify( + ssid, + ) in [slugify(v) for v in networks.values()]: _LOGGER.debug("SSID duplicated, adding suffix") networks[i] = f'{ssid} {std_table[network_info["NewStandard"]]}' else: diff --git a/homeassistant/components/glances/const.py b/homeassistant/components/glances/const.py index a25ae1b46608a5..e5a8f1424c2b78 100644 --- a/homeassistant/components/glances/const.py +++ b/homeassistant/components/glances/const.py @@ -19,7 +19,7 @@ DATA_UPDATED = "glances_data_updated" SUPPORTED_VERSIONS = [2, 3] -if sys.maxsize > 2 ** 32: +if sys.maxsize > 2**32: CPU_ICON = "mdi:cpu-64-bit" else: CPU_ICON = "mdi:cpu-32-bit" diff --git a/homeassistant/components/glances/sensor.py b/homeassistant/components/glances/sensor.py index b28ccd86b84fe1..a907dd1695ad9d 100644 --- a/homeassistant/components/glances/sensor.py +++ b/homeassistant/components/glances/sensor.py @@ -129,14 +129,14 @@ async def async_update(self): # noqa: C901 break if self.entity_description.key == "disk_free": try: - self._state = round(disk["free"] / 1024 ** 3, 1) + self._state = round(disk["free"] / 1024**3, 1) except KeyError: self._state = round( - (disk["size"] - disk["used"]) / 1024 ** 3, + (disk["size"] - disk["used"]) / 1024**3, 1, ) elif self.entity_description.key == "disk_use": - self._state = round(disk["used"] / 1024 ** 3, 1) + self._state = round(disk["used"] / 1024**3, 1) elif self.entity_description.key == "disk_use_percent": self._state = disk["percent"] elif self.entity_description.key == "battery": @@ -170,15 +170,15 @@ async def async_update(self): # noqa: C901 elif self.entity_description.key == "memory_use_percent": self._state = value["mem"]["percent"] elif self.entity_description.key == "memory_use": - self._state = round(value["mem"]["used"] / 1024 ** 2, 1) + self._state = round(value["mem"]["used"] / 1024**2, 1) elif self.entity_description.key == "memory_free": - self._state = round(value["mem"]["free"] / 1024 ** 2, 1) + self._state = round(value["mem"]["free"] / 1024**2, 1) elif self.entity_description.key == "swap_use_percent": self._state = value["memswap"]["percent"] elif self.entity_description.key == "swap_use": - self._state = round(value["memswap"]["used"] / 1024 ** 3, 1) + self._state = round(value["memswap"]["used"] / 1024**3, 1) elif self.entity_description.key == "swap_free": - self._state = round(value["memswap"]["free"] / 1024 ** 3, 1) + self._state = round(value["memswap"]["free"] / 1024**3, 1) elif self.entity_description.key == "processor_load": # Windows systems don't provide load details try: @@ -219,7 +219,7 @@ async def async_update(self): # noqa: C901 for container in value["docker"]["containers"]: if container["Status"] == "running" or "Up" in container["Status"]: mem_use += container["memory"]["usage"] - self._state = round(mem_use / 1024 ** 2, 1) + self._state = round(mem_use / 1024**2, 1) except KeyError: self._state = STATE_UNAVAILABLE elif self.entity_description.type == "raid": diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index 5b3a2db0b805ff..20191c616685f0 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -774,14 +774,10 @@ async def _execute_cover(self, command, data, params, challenge): """Execute a StartStop command.""" if command == COMMAND_STARTSTOP: if params["start"] is False: - if ( - self.state.state - in ( - cover.STATE_CLOSING, - cover.STATE_OPENING, - ) - or self.state.attributes.get(ATTR_ASSUMED_STATE) - ): + if self.state.state in ( + cover.STATE_CLOSING, + cover.STATE_OPENING, + ) or self.state.attributes.get(ATTR_ASSUMED_STATE): await self.hass.services.async_call( self.state.domain, cover.SERVICE_STOP_COVER, diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index d8d5a16ac5076c..922c4c52568be9 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -119,14 +119,10 @@ def get_accessory(hass, driver, state, aid, config): # noqa: C901 elif state.domain == "cover": device_class = state.attributes.get(ATTR_DEVICE_CLASS) - if ( - device_class - in ( - cover.CoverDeviceClass.GARAGE, - cover.CoverDeviceClass.GATE, - ) - and features & (cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE) - ): + if device_class in ( + cover.CoverDeviceClass.GARAGE, + cover.CoverDeviceClass.GATE, + ) and features & (cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE): a_type = "GarageDoorOpener" elif ( device_class == cover.CoverDeviceClass.WINDOW diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 9e77563f7a2e6c..bb168fce09fde2 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -59,7 +59,7 @@ DEFAULT_CORS: Final[list[str]] = ["https://cast.home-assistant.io"] NO_LOGIN_ATTEMPT_THRESHOLD: Final = -1 -MAX_CLIENT_SIZE: Final = 1024 ** 2 * 16 +MAX_CLIENT_SIZE: Final = 1024**2 * 16 STORAGE_KEY: Final = DOMAIN STORAGE_VERSION: Final = 1 diff --git a/homeassistant/components/integration/sensor.py b/homeassistant/components/integration/sensor.py index c95c96c505bb66..7a6248254d82d9 100644 --- a/homeassistant/components/integration/sensor.py +++ b/homeassistant/components/integration/sensor.py @@ -49,7 +49,7 @@ INTEGRATION_METHOD = [TRAPEZOIDAL_METHOD, LEFT_METHOD, RIGHT_METHOD] # SI Metric prefixes -UNIT_PREFIXES = {None: 1, "k": 10 ** 3, "M": 10 ** 6, "G": 10 ** 9, "T": 10 ** 12} +UNIT_PREFIXES = {None: 1, "k": 10**3, "M": 10**6, "G": 10**9, "T": 10**12} # SI Time prefixes UNIT_TIME = { diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index af475e9c380ed7..c118e56f9e3285 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -130,7 +130,7 @@ def numeric_type_validator(value: Any) -> str | int: def _max_payload_value(payload_length: int) -> int: if payload_length == 0: return 0x3F - return int(256 ** payload_length) - 1 + return int(256**payload_length) - 1 def button_payload_sub_validator(entity_config: OrderedDict) -> OrderedDict: diff --git a/homeassistant/components/netgear_lte/sensor.py b/homeassistant/components/netgear_lte/sensor.py index de607f2a3c0c9c..c27c4f43920c55 100644 --- a/homeassistant/components/netgear_lte/sensor.py +++ b/homeassistant/components/netgear_lte/sensor.py @@ -76,7 +76,7 @@ class UsageSensor(LTESensor): @property def native_value(self): """Return the state of the sensor.""" - return round(self.modem_data.data.usage / 1024 ** 2, 1) + return round(self.modem_data.data.usage / 1024**2, 1) class GenericSensor(LTESensor): diff --git a/homeassistant/components/nzbget/sensor.py b/homeassistant/components/nzbget/sensor.py index 42cacfc8ab5f9e..9e5bd6e4ac957b 100644 --- a/homeassistant/components/nzbget/sensor.py +++ b/homeassistant/components/nzbget/sensor.py @@ -126,7 +126,7 @@ def native_value(self): if "DownloadRate" in sensor_type and value > 0: # Convert download rate from Bytes/s to MBytes/s - return round(value / 2 ** 20, 2) + return round(value / 2**20, 2) if "UpTimeSec" in sensor_type and value > 0: uptime = utcnow() - timedelta(seconds=value) diff --git a/homeassistant/components/pandora/media_player.py b/homeassistant/components/pandora/media_player.py index 45e8f55a790421..7866b99221e6a3 100644 --- a/homeassistant/components/pandora/media_player.py +++ b/homeassistant/components/pandora/media_player.py @@ -253,7 +253,7 @@ def _query_for_playing_status(self): try: match_idx = self._pianobar.expect( [ - br"(\d\d):(\d\d)/(\d\d):(\d\d)", + rb"(\d\d):(\d\d)/(\d\d):(\d\d)", "No song playing", "Select station", "Receiving new playlist", diff --git a/homeassistant/components/pyload/sensor.py b/homeassistant/components/pyload/sensor.py index 65c33a41ff3244..e4bbf7df3d2e0d 100644 --- a/homeassistant/components/pyload/sensor.py +++ b/homeassistant/components/pyload/sensor.py @@ -132,7 +132,7 @@ def update(self): if "speed" in self.type and value > 0: # Convert download rate from Bytes/s to MBytes/s - self._state = round(value / 2 ** 20, 2) + self._state = round(value / 2**20, 2) else: self._state = value diff --git a/homeassistant/components/sonarr/sensor.py b/homeassistant/components/sonarr/sensor.py index 8911927d732022..91e1eeb325727f 100644 --- a/homeassistant/components/sonarr/sensor.py +++ b/homeassistant/components/sonarr/sensor.py @@ -167,8 +167,8 @@ def extra_state_attributes(self) -> dict[str, str] | None: if key == "diskspace": for disk in self.sonarr.app.disks: - free = disk.free / 1024 ** 3 - total = disk.total / 1024 ** 3 + free = disk.free / 1024**3 + total = disk.total / 1024**3 usage = free / total * 100 attrs[ @@ -203,7 +203,7 @@ def native_value(self) -> StateType: if key == "diskspace": total_free = sum(disk.free for disk in self.sonarr.app.disks) - free = total_free / 1024 ** 3 + free = total_free / 1024**3 return f"{free:.2f}" if key == "commands" and self.data.get(key) is not None: diff --git a/homeassistant/components/speedtestdotnet/const.py b/homeassistant/components/speedtestdotnet/const.py index 735c656134b265..e2455ad63df09f 100644 --- a/homeassistant/components/speedtestdotnet/const.py +++ b/homeassistant/components/speedtestdotnet/const.py @@ -36,14 +36,14 @@ class SpeedtestSensorEntityDescription(SensorEntityDescription): name="Download", native_unit_of_measurement=DATA_RATE_MEGABITS_PER_SECOND, state_class=SensorStateClass.MEASUREMENT, - value=lambda value: round(value / 10 ** 6, 2), + value=lambda value: round(value / 10**6, 2), ), SpeedtestSensorEntityDescription( key="upload", name="Upload", native_unit_of_measurement=DATA_RATE_MEGABITS_PER_SECOND, state_class=SensorStateClass.MEASUREMENT, - value=lambda value: round(value / 10 ** 6, 2), + value=lambda value: round(value / 10**6, 2), ), ) diff --git a/homeassistant/components/startca/sensor.py b/homeassistant/components/startca/sensor.py index dc87116914af7c..042fc33060a37c 100644 --- a/homeassistant/components/startca/sensor.py +++ b/homeassistant/components/startca/sensor.py @@ -194,7 +194,7 @@ def bytes_to_gb(value): :param value: The value in bytes to convert to GB. :return: Converted GB value """ - return float(value) * 10 ** -9 + return float(value) * 10**-9 @Throttle(MIN_TIME_BETWEEN_UPDATES) async def async_update(self): diff --git a/homeassistant/components/synology_dsm/sensor.py b/homeassistant/components/synology_dsm/sensor.py index 305e6dda4e7c12..18014de8c7a1d0 100644 --- a/homeassistant/components/synology_dsm/sensor.py +++ b/homeassistant/components/synology_dsm/sensor.py @@ -105,7 +105,7 @@ def native_value(self) -> Any | None: # Data (RAM) if self.native_unit_of_measurement == DATA_MEGABYTES: - return round(attr / 1024.0 ** 2, 1) + return round(attr / 1024.0**2, 1) # Network if self.native_unit_of_measurement == DATA_RATE_KILOBYTES_PER_SECOND: @@ -147,7 +147,7 @@ def native_value(self) -> Any | None: # Data (disk space) if self.native_unit_of_measurement == DATA_TERABYTES: - return round(attr / 1024.0 ** 4, 2) + return round(attr / 1024.0**4, 2) return attr diff --git a/homeassistant/components/system_bridge/sensor.py b/homeassistant/components/system_bridge/sensor.py index 9a4f0cc0aa84a8..c4969e2c14cf4a 100644 --- a/homeassistant/components/system_bridge/sensor.py +++ b/homeassistant/components/system_bridge/sensor.py @@ -104,7 +104,7 @@ class SystemBridgeSensorEntityDescription(SensorEntityDescription): state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=DATA_GIGABYTES, icon="mdi:memory", - value=lambda bridge: round(bridge.memory.free / 1000 ** 3, 2), + value=lambda bridge: round(bridge.memory.free / 1000**3, 2), ), SystemBridgeSensorEntityDescription( key="memory_used_percentage", @@ -121,7 +121,7 @@ class SystemBridgeSensorEntityDescription(SensorEntityDescription): state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=DATA_GIGABYTES, icon="mdi:memory", - value=lambda bridge: round(bridge.memory.used / 1000 ** 3, 2), + value=lambda bridge: round(bridge.memory.used / 1000**3, 2), ), SystemBridgeSensorEntityDescription( key="os", @@ -324,7 +324,7 @@ async def async_setup_entry( native_unit_of_measurement=DATA_GIGABYTES, icon="mdi:memory", value=lambda bridge, i=index: round( - bridge.graphics.controllers[i].memoryFree / 10 ** 3, 2 + bridge.graphics.controllers[i].memoryFree / 10**3, 2 ), ), ), @@ -356,7 +356,7 @@ async def async_setup_entry( native_unit_of_measurement=DATA_GIGABYTES, icon="mdi:memory", value=lambda bridge, i=index: round( - bridge.graphics.controllers[i].memoryUsed / 10 ** 3, 2 + bridge.graphics.controllers[i].memoryUsed / 10**3, 2 ), ), ), diff --git a/homeassistant/components/systemmonitor/sensor.py b/homeassistant/components/systemmonitor/sensor.py index bceda1b453a683..7a57c06ef5813e 100644 --- a/homeassistant/components/systemmonitor/sensor.py +++ b/homeassistant/components/systemmonitor/sensor.py @@ -51,7 +51,7 @@ CONF_ARG = "arg" -if sys.maxsize > 2 ** 32: +if sys.maxsize > 2**32: CPU_ICON = "mdi:cpu-64-bit" else: CPU_ICON = "mdi:cpu-32-bit" @@ -473,22 +473,22 @@ def _update( # noqa: C901 if type_ == "disk_use_percent": state = _disk_usage(data.argument).percent elif type_ == "disk_use": - state = round(_disk_usage(data.argument).used / 1024 ** 3, 1) + state = round(_disk_usage(data.argument).used / 1024**3, 1) elif type_ == "disk_free": - state = round(_disk_usage(data.argument).free / 1024 ** 3, 1) + state = round(_disk_usage(data.argument).free / 1024**3, 1) elif type_ == "memory_use_percent": state = _virtual_memory().percent elif type_ == "memory_use": virtual_memory = _virtual_memory() - state = round((virtual_memory.total - virtual_memory.available) / 1024 ** 2, 1) + state = round((virtual_memory.total - virtual_memory.available) / 1024**2, 1) elif type_ == "memory_free": - state = round(_virtual_memory().available / 1024 ** 2, 1) + state = round(_virtual_memory().available / 1024**2, 1) elif type_ == "swap_use_percent": state = _swap_memory().percent elif type_ == "swap_use": - state = round(_swap_memory().used / 1024 ** 2, 1) + state = round(_swap_memory().used / 1024**2, 1) elif type_ == "swap_free": - state = round(_swap_memory().free / 1024 ** 2, 1) + state = round(_swap_memory().free / 1024**2, 1) elif type_ == "processor_use": state = round(psutil.cpu_percent(interval=None)) elif type_ == "processor_temperature": @@ -510,7 +510,7 @@ def _update( # noqa: C901 counters = _net_io_counters() if data.argument in counters: counter = counters[data.argument][IO_COUNTER[type_]] - state = round(counter / 1024 ** 2, 1) + state = round(counter / 1024**2, 1) else: state = None elif type_ in ("packets_out", "packets_in"): @@ -527,7 +527,7 @@ def _update( # noqa: C901 if data.value and data.value < counter: state = round( (counter - data.value) - / 1000 ** 2 + / 1000**2 / (now - (data.update_time or now)).total_seconds(), 3, ) diff --git a/homeassistant/components/tuya/base.py b/homeassistant/components/tuya/base.py index 15e57f223e9d2e..22764f810805a0 100644 --- a/homeassistant/components/tuya/base.py +++ b/homeassistant/components/tuya/base.py @@ -45,11 +45,11 @@ def step_scaled(self) -> float: def scale_value(self, value: float | int) -> float: """Scale a value.""" - return value * 1.0 / (10 ** self.scale) + return value * 1.0 / (10**self.scale) def scale_value_back(self, value: float | int) -> int: """Return raw value for scaled.""" - return int(value * (10 ** self.scale)) + return int(value * (10**self.scale)) def remap_value_to( self, diff --git a/homeassistant/components/waqi/sensor.py b/homeassistant/components/waqi/sensor.py index 8a3b9f046ce303..499233717b6044 100644 --- a/homeassistant/components/waqi/sensor.py +++ b/homeassistant/components/waqi/sensor.py @@ -91,15 +91,11 @@ async def async_setup_platform( _LOGGER.debug("The following stations were returned: %s", stations) for station in stations: waqi_sensor = WaqiSensor(client, station) - if ( - not station_filter - or { - waqi_sensor.uid, - waqi_sensor.url, - waqi_sensor.station_name, - } - & set(station_filter) - ): + if not station_filter or { + waqi_sensor.uid, + waqi_sensor.url, + waqi_sensor.station_name, + } & set(station_filter): dev.append(waqi_sensor) except ( aiohttp.client_exceptions.ClientConnectorError, diff --git a/homeassistant/components/zha/climate.py b/homeassistant/components/zha/climate.py index 65de5fd04cc796..b892fc9a67f21b 100644 --- a/homeassistant/components/zha/climate.py +++ b/homeassistant/components/zha/climate.py @@ -431,14 +431,10 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: self.debug("preset mode '%s' is not supported", preset_mode) return - if ( - self.preset_mode - not in ( - preset_mode, - PRESET_NONE, - ) - and not await self.async_preset_handler(self.preset_mode, enable=False) - ): + if self.preset_mode not in ( + preset_mode, + PRESET_NONE, + ) and not await self.async_preset_handler(self.preset_mode, enable=False): self.debug("Couldn't turn off '%s' preset", self.preset_mode) return diff --git a/homeassistant/core.py b/homeassistant/core.py index a29067cdb0d686..27906e0401f3a1 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -1919,7 +1919,7 @@ def schedule_tick(now: datetime.datetime) -> None: """Schedule a timer tick when the next second rolls around.""" nonlocal handle - slp_seconds = 1 - (now.microsecond / 10 ** 6) + slp_seconds = 1 - (now.microsecond / 10**6) target = monotonic() + slp_seconds handle = hass.loop.call_later(slp_seconds, fire_time_event, target) diff --git a/homeassistant/helpers/__init__.py b/homeassistant/helpers/__init__.py index f74aec0efe87e5..281ab3108b6330 100644 --- a/homeassistant/helpers/__init__.py +++ b/homeassistant/helpers/__init__.py @@ -42,5 +42,5 @@ def extract_domain_configs(config: ConfigType, domain: str) -> Sequence[str]: Async friendly. """ - pattern = re.compile(fr"^{domain}(| .+)$") + pattern = re.compile(rf"^{domain}(| .+)$") return [key for key in config.keys() if pattern.match(key)] diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 916d203782ab3e..d371017a475090 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -1302,7 +1302,7 @@ def forgiving_round(value, precision=0, method="common", default=_SENTINEL): """Filter to round a value.""" try: # support rounding methods like jinja - multiplier = float(10 ** precision) + multiplier = float(10**precision) if method == "ceil": value = math.ceil(float(value) * multiplier) / multiplier elif method == "floor": diff --git a/homeassistant/scripts/benchmark/__init__.py b/homeassistant/scripts/benchmark/__init__.py index 1005b48e1ca7b3..c57d082de61e1f 100644 --- a/homeassistant/scripts/benchmark/__init__.py +++ b/homeassistant/scripts/benchmark/__init__.py @@ -65,7 +65,7 @@ async def fire_events(hass): """Fire a million events.""" count = 0 event_name = "benchmark_event" - events_to_fire = 10 ** 6 + events_to_fire = 10**6 @core.callback def listener(_): @@ -92,7 +92,7 @@ async def fire_events_with_filter(hass): """Fire a million events with a filter that rejects them.""" count = 0 event_name = "benchmark_event" - events_to_fire = 10 ** 6 + events_to_fire = 10**6 @core.callback def event_filter(event): @@ -131,13 +131,13 @@ def listener(_): nonlocal count count += 1 - if count == 10 ** 6: + if count == 10**6: event.set() hass.helpers.event.async_track_time_change(listener, minute=0, second=0) event_data = {ATTR_NOW: datetime(2017, 10, 10, 15, 0, 0, tzinfo=dt_util.UTC)} - for _ in range(10 ** 6): + for _ in range(10**6): hass.bus.async_fire(EVENT_TIME_CHANGED, event_data) start = timer() @@ -160,7 +160,7 @@ def listener(*args): nonlocal count count += 1 - if count == 10 ** 6: + if count == 10**6: event.set() for idx in range(1000): @@ -173,7 +173,7 @@ def listener(*args): "new_state": core.State(entity_id, "on"), } - for _ in range(10 ** 6): + for _ in range(10**6): hass.bus.async_fire(EVENT_STATE_CHANGED, event_data) start = timer() @@ -188,7 +188,7 @@ async def state_changed_event_helper(hass): """Run a million events through state changed event helper with 1000 entities.""" count = 0 entity_id = "light.kitchen" - events_to_fire = 10 ** 6 + events_to_fire = 10**6 @core.callback def listener(*args): @@ -223,7 +223,7 @@ async def state_changed_event_filter_helper(hass): """Run a million events through state changed event helper with 1000 entities that all get filtered.""" count = 0 entity_id = "light.kitchen" - events_to_fire = 10 ** 6 + events_to_fire = 10**6 @core.callback def listener(*args): @@ -292,7 +292,7 @@ async def _logbook_filtering(hass, last_changed, last_updated): ) def yield_events(event): - for _ in range(10 ** 5): + for _ in range(10**5): # pylint: disable=protected-access if logbook._keep_event(hass, event, entities_filter): yield event @@ -363,7 +363,7 @@ async def filtering_entity_id(hass): start = timer() - for i in range(10 ** 5): + for i in range(10**5): entities_filter(entity_ids[i % size]) return timer() - start @@ -373,7 +373,7 @@ async def filtering_entity_id(hass): async def valid_entity_id(hass): """Run valid entity ID a million times.""" start = timer() - for _ in range(10 ** 6): + for _ in range(10**6): core.valid_entity_id("light.kitchen") return timer() - start @@ -383,7 +383,7 @@ async def json_serialize_states(hass): """Serialize million states with websocket default encoder.""" states = [ core.State("light.kitchen", "on", {"friendly_name": "Kitchen Lights"}) - for _ in range(10 ** 6) + for _ in range(10**6) ] start = timer() diff --git a/homeassistant/util/location.py b/homeassistant/util/location.py index b967a6a0b1e82c..4e76fa32de39fe 100644 --- a/homeassistant/util/location.py +++ b/homeassistant/util/location.py @@ -115,7 +115,7 @@ def vincenty( cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda sigma = math.atan2(sinSigma, cosSigma) sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma - cosSqAlpha = 1 - sinAlpha ** 2 + cosSqAlpha = 1 - sinAlpha**2 try: cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha except ZeroDivisionError: @@ -124,14 +124,14 @@ def vincenty( LambdaPrev = Lambda Lambda = L + (1 - C) * FLATTENING * sinAlpha * ( sigma - + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM ** 2)) + + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM**2)) ) if abs(Lambda - LambdaPrev) < CONVERGENCE_THRESHOLD: break # successful convergence else: return None # failure to converge - uSq = cosSqAlpha * (AXIS_A ** 2 - AXIS_B ** 2) / (AXIS_B ** 2) + uSq = cosSqAlpha * (AXIS_A**2 - AXIS_B**2) / (AXIS_B**2) A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq))) B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq))) deltaSigma = ( @@ -142,12 +142,12 @@ def vincenty( + B / 4 * ( - cosSigma * (-1 + 2 * cos2SigmaM ** 2) + cosSigma * (-1 + 2 * cos2SigmaM**2) - B / 6 * cos2SigmaM - * (-3 + 4 * sinSigma ** 2) - * (-3 + 4 * cos2SigmaM ** 2) + * (-3 + 4 * sinSigma**2) + * (-3 + 4 * cos2SigmaM**2) ) ) ) diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index 2e37b2175ba947..ca7828267b6eac 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -1,7 +1,7 @@ # Automatically generated from .pre-commit-config.yaml by gen_requirements_all.py, do not edit bandit==1.7.0 -black==21.12b0 +black==22.1.0 codespell==2.1.0 flake8-comprehensions==3.7.0 flake8-docstrings==1.6.0 diff --git a/tests/components/blueprint/test_models.py b/tests/components/blueprint/test_models.py index ba8914c3b1d9ff..497e8b36e99e70 100644 --- a/tests/components/blueprint/test_models.py +++ b/tests/components/blueprint/test_models.py @@ -118,18 +118,15 @@ def test_blueprint_validate(): is None ) - assert ( - models.Blueprint( - { - "blueprint": { - "name": "Hello", - "domain": "automation", - "homeassistant": {"min_version": "100000.0.0"}, - }, - } - ).validate() - == ["Requires at least Home Assistant 100000.0.0"] - ) + assert models.Blueprint( + { + "blueprint": { + "name": "Hello", + "domain": "automation", + "homeassistant": {"min_version": "100000.0.0"}, + }, + } + ).validate() == ["Requires at least Home Assistant 100000.0.0"] def test_blueprint_inputs(blueprint_2): diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index d958c633b621fd..e0cc9b715c10df 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -1810,54 +1810,51 @@ async def test_extract_entities(): async def test_extract_devices(): """Test extracting devices.""" - assert ( - condition.async_extract_devices( - { - "condition": "and", - "conditions": [ - {"condition": "device", "device_id": "abcd", "domain": "light"}, - {"condition": "device", "device_id": "qwer", "domain": "switch"}, - { - "condition": "state", - "entity_id": "sensor.not_a_device", - "state": "100", - }, - { - "condition": "not", - "conditions": [ - { - "condition": "device", - "device_id": "abcd_not", - "domain": "light", - }, - { - "condition": "device", - "device_id": "qwer_not", - "domain": "switch", - }, - ], - }, - { - "condition": "or", - "conditions": [ - { - "condition": "device", - "device_id": "abcd_or", - "domain": "light", - }, - { - "condition": "device", - "device_id": "qwer_or", - "domain": "switch", - }, - ], - }, - Template("{{ is_state('light.example', 'on') }}"), - ], - } - ) - == {"abcd", "qwer", "abcd_not", "qwer_not", "abcd_or", "qwer_or"} - ) + assert condition.async_extract_devices( + { + "condition": "and", + "conditions": [ + {"condition": "device", "device_id": "abcd", "domain": "light"}, + {"condition": "device", "device_id": "qwer", "domain": "switch"}, + { + "condition": "state", + "entity_id": "sensor.not_a_device", + "state": "100", + }, + { + "condition": "not", + "conditions": [ + { + "condition": "device", + "device_id": "abcd_not", + "domain": "light", + }, + { + "condition": "device", + "device_id": "qwer_not", + "domain": "switch", + }, + ], + }, + { + "condition": "or", + "conditions": [ + { + "condition": "device", + "device_id": "abcd_or", + "domain": "light", + }, + { + "condition": "device", + "device_id": "qwer_or", + "domain": "switch", + }, + ], + }, + Template("{{ is_state('light.example', 'on') }}"), + ], + } + ) == {"abcd", "qwer", "abcd_not", "qwer_not", "abcd_or", "qwer_or"} async def test_condition_template_error(hass): diff --git a/tests/test_config.py b/tests/test_config.py index 41e9bc50038035..93dd1b87b44443 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1050,23 +1050,20 @@ async def test_component_config_exceptions(hass, caplog): # component.PLATFORM_SCHEMA caplog.clear() - assert ( - await config_util.async_process_component_config( - hass, - {"test_domain": {"platform": "test_platform"}}, - integration=Mock( - domain="test_domain", - get_platform=Mock(return_value=None), - get_component=Mock( - return_value=Mock( - spec=["PLATFORM_SCHEMA_BASE"], - PLATFORM_SCHEMA_BASE=Mock(side_effect=ValueError("broken")), - ) - ), + assert await config_util.async_process_component_config( + hass, + {"test_domain": {"platform": "test_platform"}}, + integration=Mock( + domain="test_domain", + get_platform=Mock(return_value=None), + get_component=Mock( + return_value=Mock( + spec=["PLATFORM_SCHEMA_BASE"], + PLATFORM_SCHEMA_BASE=Mock(side_effect=ValueError("broken")), + ) ), - ) - == {"test_domain": []} - ) + ), + ) == {"test_domain": []} assert "ValueError: broken" in caplog.text assert ( "Unknown error validating test_platform platform config with test_domain component platform schema" @@ -1085,20 +1082,15 @@ async def test_component_config_exceptions(hass, caplog): ) ), ): - assert ( - await config_util.async_process_component_config( - hass, - {"test_domain": {"platform": "test_platform"}}, - integration=Mock( - domain="test_domain", - get_platform=Mock(return_value=None), - get_component=Mock( - return_value=Mock(spec=["PLATFORM_SCHEMA_BASE"]) - ), - ), - ) - == {"test_domain": []} - ) + assert await config_util.async_process_component_config( + hass, + {"test_domain": {"platform": "test_platform"}}, + integration=Mock( + domain="test_domain", + get_platform=Mock(return_value=None), + get_component=Mock(return_value=Mock(spec=["PLATFORM_SCHEMA_BASE"])), + ), + ) == {"test_domain": []} assert "ValueError: broken" in caplog.text assert ( "Unknown error validating config for test_platform platform for test_domain component with PLATFORM_SCHEMA" diff --git a/tests/test_util/aiohttp.py b/tests/test_util/aiohttp.py index 31a8a5c71e3254..6868ff1f71db3a 100644 --- a/tests/test_util/aiohttp.py +++ b/tests/test_util/aiohttp.py @@ -21,7 +21,7 @@ def mock_stream(data): """Mock a stream with data.""" protocol = mock.Mock(_reading_paused=False) - stream = StreamReader(protocol, limit=2 ** 16) + stream = StreamReader(protocol, limit=2**16) stream.feed_data(data) stream.feed_eof() return stream diff --git a/tests/util/test_color.py b/tests/util/test_color.py index 0b1b8f7d17f454..e9ab935f6ab304 100644 --- a/tests/util/test_color.py +++ b/tests/util/test_color.py @@ -461,20 +461,17 @@ def test_rgbww_to_color_temperature(): Temperature values must be in mireds Home Assistant uses rgbcw for rgbww """ - assert ( - color_util.rgbww_to_color_temperature( - ( - 0, - 0, - 0, - 255, - 0, - ), - 153, - 500, - ) - == (153, 255) - ) + assert color_util.rgbww_to_color_temperature( + ( + 0, + 0, + 0, + 255, + 0, + ), + 153, + 500, + ) == (153, 255) assert color_util.rgbww_to_color_temperature((0, 0, 0, 128, 0), 153, 500) == ( 153, 128, @@ -507,15 +504,12 @@ def test_white_levels_to_color_temperature(): Temperature values must be in mireds Home Assistant uses rgbcw for rgbww """ - assert ( - color_util.while_levels_to_color_temperature( - 255, - 0, - 153, - 500, - ) - == (153, 255) - ) + assert color_util.while_levels_to_color_temperature( + 255, + 0, + 153, + 500, + ) == (153, 255) assert color_util.while_levels_to_color_temperature(128, 0, 153, 500) == ( 153, 128, diff --git a/tests/util/test_json.py b/tests/util/test_json.py index d8851868719879..af7f43eae4d1d3 100644 --- a/tests/util/test_json.py +++ b/tests/util/test_json.py @@ -143,21 +143,15 @@ def default(self, o): bad_data = object() - assert ( - find_paths_unserializable_data( - [State("mock_domain.mock_entity", "on", {"bad": bad_data})], - dump=partial(dumps, cls=MockJSONEncoder), - ) - == {"$[0](State: mock_domain.mock_entity).attributes.bad": bad_data} - ) + assert find_paths_unserializable_data( + [State("mock_domain.mock_entity", "on", {"bad": bad_data})], + dump=partial(dumps, cls=MockJSONEncoder), + ) == {"$[0](State: mock_domain.mock_entity).attributes.bad": bad_data} - assert ( - find_paths_unserializable_data( - [Event("bad_event", {"bad_attribute": bad_data})], - dump=partial(dumps, cls=MockJSONEncoder), - ) - == {"$[0](Event: bad_event).data.bad_attribute": bad_data} - ) + assert find_paths_unserializable_data( + [Event("bad_event", {"bad_attribute": bad_data})], + dump=partial(dumps, cls=MockJSONEncoder), + ) == {"$[0](Event: bad_event).data.bad_attribute": bad_data} class BadData: def __init__(self): @@ -166,10 +160,7 @@ def __init__(self): def as_dict(self): return {"bla": self.bla} - assert ( - find_paths_unserializable_data( - BadData(), - dump=partial(dumps, cls=MockJSONEncoder), - ) - == {"$(BadData).bla": bad_data} - ) + assert find_paths_unserializable_data( + BadData(), + dump=partial(dumps, cls=MockJSONEncoder), + ) == {"$(BadData).bla": bad_data} diff --git a/tests/util/yaml/test_input.py b/tests/util/yaml/test_input.py index 1c13d1b36847a6..fe118c79dbd0cb 100644 --- a/tests/util/yaml/test_input.py +++ b/tests/util/yaml/test_input.py @@ -25,10 +25,7 @@ def test_substitute(): with pytest.raises(UndefinedSubstitution): substitute(Input("hello"), {}) - assert ( - substitute( - {"info": [1, Input("hello"), 2, Input("world")]}, - {"hello": 5, "world": 10}, - ) - == {"info": [1, 5, 2, 10]} - ) + assert substitute( + {"info": [1, Input("hello"), 2, Input("world")]}, + {"hello": 5, "world": 10}, + ) == {"info": [1, 5, 2, 10]} From 9f8c0685e35da0ee4ba693395be5d2f77917ea3f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 5 Feb 2022 14:20:07 +0100 Subject: [PATCH 0310/1098] Small cleanup in Plugwise sensors (#65765) --- homeassistant/components/plugwise/sensor.py | 117 ++++++++++---------- 1 file changed, 59 insertions(+), 58 deletions(-) diff --git a/homeassistant/components/plugwise/sensor.py b/homeassistant/components/plugwise/sensor.py index 9307cb3f98fa8c..2fdfd952d8ef0c 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -1,6 +1,10 @@ """Plugwise Sensor component for Home Assistant.""" +from __future__ import annotations + import logging +from plugwise.smile import Smile + from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, @@ -18,6 +22,7 @@ ) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import ( COOL_ICON, @@ -298,18 +303,17 @@ async def async_setup_entry( class SmileSensor(SmileGateway, SensorEntity): """Represent Smile Sensors.""" - def __init__(self, api, coordinator, name, dev_id, sensor): + def __init__( + self, + api: Smile, + coordinator: DataUpdateCoordinator, + name: str, + dev_id: str, + sensor: str, + ) -> None: """Initialise the sensor.""" super().__init__(api, coordinator, name, dev_id) - self._sensor = sensor - - self._dev_class = None - self._icon = None - self._state = None - self._state_class = None - self._unit_of_measurement = None - if dev_id == self._api.heater_id: self._entity_name = "Auxiliary" @@ -321,47 +325,29 @@ def __init__(self, api, coordinator, name, dev_id, sensor): self._unique_id = f"{dev_id}-{sensor}" - @property - def device_class(self): - """Device class of this entity.""" - return self._dev_class - - @property - def icon(self): - """Return the icon of this entity.""" - return self._icon - - @property - def native_value(self): - """Return the state of this entity.""" - return self._state - - @property - def native_unit_of_measurement(self): - """Return the unit of measurement of this entity, if any.""" - return self._unit_of_measurement - - @property - def state_class(self): - """Return the state_class of this entity.""" - return self._state_class - class PwThermostatSensor(SmileSensor): """Thermostat (or generic) sensor devices.""" - def __init__(self, api, coordinator, name, dev_id, sensor, sensor_type): + def __init__( + self, + api: Smile, + coordinator: DataUpdateCoordinator, + name: str, + dev_id: str, + sensor: str, + sensor_type: list[str], + ) -> None: """Set up the Plugwise API.""" super().__init__(api, coordinator, name, dev_id, sensor) - self._icon = None self._model = sensor_type[SENSOR_MAP_MODEL] - self._unit_of_measurement = sensor_type[SENSOR_MAP_UOM] - self._dev_class = sensor_type[SENSOR_MAP_DEVICE_CLASS] - self._state_class = sensor_type[SENSOR_MAP_STATE_CLASS] + self._attr_native_unit_of_measurement = sensor_type[SENSOR_MAP_UOM] + self._attr_device_class = sensor_type[SENSOR_MAP_DEVICE_CLASS] + self._attr_state_class = sensor_type[SENSOR_MAP_STATE_CLASS] @callback - def _async_process_data(self): + def _async_process_data(self) -> None: """Update the entity.""" if not (data := self._api.get_device_data(self._dev_id)): _LOGGER.error("Received no data for device %s", self._entity_name) @@ -369,8 +355,8 @@ def _async_process_data(self): return if data.get(self._sensor) is not None: - self._state = data[self._sensor] - self._icon = CUSTOM_ICONS.get(self._sensor, self._icon) + self._attr_native_value = data[self._sensor] + self._attr_icon = CUSTOM_ICONS.get(self._sensor, self.icon) self.async_write_ha_state() @@ -378,7 +364,14 @@ def _async_process_data(self): class PwAuxDeviceSensor(SmileSensor): """Auxiliary Device Sensors.""" - def __init__(self, api, coordinator, name, dev_id, sensor): + def __init__( + self, + api: Smile, + coordinator: DataUpdateCoordinator, + name: str, + dev_id: str, + sensor: str, + ) -> None: """Set up the Plugwise API.""" super().__init__(api, coordinator, name, dev_id, sensor) @@ -386,7 +379,7 @@ def __init__(self, api, coordinator, name, dev_id, sensor): self._heating_state = False @callback - def _async_process_data(self): + def _async_process_data(self) -> None: """Update the entity.""" if not (data := self._api.get_device_data(self._dev_id)): _LOGGER.error("Received no data for device %s", self._entity_name) @@ -398,14 +391,14 @@ def _async_process_data(self): if data.get("cooling_state") is not None: self._cooling_state = data["cooling_state"] - self._state = "idle" - self._icon = IDLE_ICON + self._attr_native_value = "idle" + self._attr_icon = IDLE_ICON if self._heating_state: - self._state = "heating" - self._icon = FLAME_ICON + self._attr_native_value = "heating" + self._attr_icon = FLAME_ICON if self._cooling_state: - self._state = "cooling" - self._icon = COOL_ICON + self._attr_native_value = "cooling" + self._attr_icon = COOL_ICON self.async_write_ha_state() @@ -413,24 +406,32 @@ def _async_process_data(self): class PwPowerSensor(SmileSensor): """Power sensor entities.""" - def __init__(self, api, coordinator, name, dev_id, sensor, sensor_type, model): + def __init__( + self, + api: Smile, + coordinator: DataUpdateCoordinator, + name: str, + dev_id: str, + sensor: str, + sensor_type: list[str], + model: str | None, + ) -> None: """Set up the Plugwise API.""" super().__init__(api, coordinator, name, dev_id, sensor) - self._icon = None self._model = model if model is None: self._model = sensor_type[SENSOR_MAP_MODEL] - self._unit_of_measurement = sensor_type[SENSOR_MAP_UOM] - self._dev_class = sensor_type[SENSOR_MAP_DEVICE_CLASS] - self._state_class = sensor_type[SENSOR_MAP_STATE_CLASS] + self._attr_native_unit_of_measurement = sensor_type[SENSOR_MAP_UOM] + self._attr_device_class = sensor_type[SENSOR_MAP_DEVICE_CLASS] + self._attr_state_class = sensor_type[SENSOR_MAP_STATE_CLASS] if dev_id == self._api.gateway_id: self._model = "P1 DSMR" @callback - def _async_process_data(self): + def _async_process_data(self) -> None: """Update the entity.""" if not (data := self._api.get_device_data(self._dev_id)): _LOGGER.error("Received no data for device %s", self._entity_name) @@ -438,7 +439,7 @@ def _async_process_data(self): return if data.get(self._sensor) is not None: - self._state = data[self._sensor] - self._icon = CUSTOM_ICONS.get(self._sensor, self._icon) + self._attr_native_value = data[self._sensor] + self._attr_icon = CUSTOM_ICONS.get(self._sensor, self.icon) self.async_write_ha_state() From 99fd16d675a5ef65416224c0e5832406c8ca58f1 Mon Sep 17 00:00:00 2001 From: Hans Oischinger Date: Sat, 5 Feb 2022 14:26:44 +0100 Subject: [PATCH 0311/1098] Clean up vicare code (#65774) * Clean up vicare code Remove constants that were only used once Remove _build_entity and use constructor directly * Fix copy/paste issue --- homeassistant/components/vicare/climate.py | 10 +- homeassistant/components/vicare/sensor.py | 100 ++++++------------ .../components/vicare/water_heater.py | 14 +-- 3 files changed, 33 insertions(+), 91 deletions(-) diff --git a/homeassistant/components/vicare/climate.py b/homeassistant/components/vicare/climate.py index 451ea70edab574..c0c0db85cf3a1c 100644 --- a/homeassistant/components/vicare/climate.py +++ b/homeassistant/components/vicare/climate.py @@ -95,12 +95,6 @@ } -def _build_entity(name, vicare_api, circuit, device_config, heating_type): - """Create a ViCare climate entity.""" - _LOGGER.debug("Found device %s", name) - return ViCareClimate(name, vicare_api, device_config, circuit, heating_type) - - def _get_circuits(vicare_api): """Return the list of circuits.""" try: @@ -126,11 +120,11 @@ async def async_setup_entry( if len(circuits) > 1: suffix = f" {circuit.id}" - entity = _build_entity( + entity = ViCareClimate( f"{name} Heating{suffix}", api, - hass.data[DOMAIN][config_entry.entry_id][VICARE_DEVICE_CONFIG], circuit, + hass.data[DOMAIN][config_entry.entry_id][VICARE_DEVICE_CONFIG], config_entry.data[CONF_HEATING_TYPE], ) entities.append(entity) diff --git a/homeassistant/components/vicare/sensor.py b/homeassistant/components/vicare/sensor.py index 42594ec202e68b..23096cfeacdef1 100644 --- a/homeassistant/components/vicare/sensor.py +++ b/homeassistant/components/vicare/sensor.py @@ -43,46 +43,6 @@ _LOGGER = logging.getLogger(__name__) -SENSOR_OUTSIDE_TEMPERATURE = "outside_temperature" -SENSOR_SUPPLY_TEMPERATURE = "supply_temperature" -SENSOR_RETURN_TEMPERATURE = "return_temperature" - -# gas sensors -SENSOR_BOILER_TEMPERATURE = "boiler_temperature" -SENSOR_BURNER_MODULATION = "burner_modulation" -SENSOR_BURNER_STARTS = "burner_starts" -SENSOR_BURNER_HOURS = "burner_hours" -SENSOR_BURNER_POWER = "burner_power" -SENSOR_DHW_GAS_CONSUMPTION_TODAY = "hotwater_gas_consumption_today" -SENSOR_DHW_GAS_CONSUMPTION_THIS_WEEK = "hotwater_gas_consumption_heating_this_week" -SENSOR_DHW_GAS_CONSUMPTION_THIS_MONTH = "hotwater_gas_consumption_heating_this_month" -SENSOR_DHW_GAS_CONSUMPTION_THIS_YEAR = "hotwater_gas_consumption_heating_this_year" -SENSOR_GAS_CONSUMPTION_TODAY = "gas_consumption_heating_today" -SENSOR_GAS_CONSUMPTION_THIS_WEEK = "gas_consumption_heating_this_week" -SENSOR_GAS_CONSUMPTION_THIS_MONTH = "gas_consumption_heating_this_month" -SENSOR_GAS_CONSUMPTION_THIS_YEAR = "gas_consumption_heating_this_year" - -# heatpump sensors -SENSOR_COMPRESSOR_STARTS = "compressor_starts" -SENSOR_COMPRESSOR_HOURS = "compressor_hours" -SENSOR_COMPRESSOR_HOURS_LOADCLASS1 = "compressor_hours_loadclass1" -SENSOR_COMPRESSOR_HOURS_LOADCLASS2 = "compressor_hours_loadclass2" -SENSOR_COMPRESSOR_HOURS_LOADCLASS3 = "compressor_hours_loadclass3" -SENSOR_COMPRESSOR_HOURS_LOADCLASS4 = "compressor_hours_loadclass4" -SENSOR_COMPRESSOR_HOURS_LOADCLASS5 = "compressor_hours_loadclass5" - -# fuelcell sensors -SENSOR_POWER_PRODUCTION_CURRENT = "power_production_current" -SENSOR_POWER_PRODUCTION_TODAY = "power_production_today" -SENSOR_POWER_PRODUCTION_THIS_WEEK = "power_production_this_week" -SENSOR_POWER_PRODUCTION_THIS_MONTH = "power_production_this_month" -SENSOR_POWER_PRODUCTION_THIS_YEAR = "power_production_this_year" - -# solar sensors -SENSOR_COLLECTOR_TEMPERATURE = "collector temperature" -SENSOR_SOLAR_STORAGE_TEMPERATURE = "solar storage temperature" -SENSOR_SOLAR_POWER_PRODUCTION = "solar power production" - @dataclass class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysMixin): @@ -93,84 +53,84 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ViCareSensorEntityDescription( - key=SENSOR_OUTSIDE_TEMPERATURE, + key="outside_temperature", name="Outside Temperature", native_unit_of_measurement=TEMP_CELSIUS, value_getter=lambda api: api.getOutsideTemperature(), device_class=SensorDeviceClass.TEMPERATURE, ), ViCareSensorEntityDescription( - key=SENSOR_RETURN_TEMPERATURE, + key="return_temperature", name="Return Temperature", native_unit_of_measurement=TEMP_CELSIUS, value_getter=lambda api: api.getReturnTemperature(), device_class=SensorDeviceClass.TEMPERATURE, ), ViCareSensorEntityDescription( - key=SENSOR_BOILER_TEMPERATURE, + key="boiler_temperature", name="Boiler Temperature", native_unit_of_measurement=TEMP_CELSIUS, value_getter=lambda api: api.getBoilerTemperature(), device_class=SensorDeviceClass.TEMPERATURE, ), ViCareSensorEntityDescription( - key=SENSOR_DHW_GAS_CONSUMPTION_TODAY, + key="hotwater_gas_consumption_today", name="Hot water gas consumption today", value_getter=lambda api: api.getGasConsumptionDomesticHotWaterToday(), unit_getter=lambda api: api.getGasConsumptionDomesticHotWaterUnit(), state_class=SensorStateClass.TOTAL_INCREASING, ), ViCareSensorEntityDescription( - key=SENSOR_DHW_GAS_CONSUMPTION_THIS_WEEK, + key="hotwater_gas_consumption_heating_this_week", name="Hot water gas consumption this week", value_getter=lambda api: api.getGasConsumptionDomesticHotWaterThisWeek(), unit_getter=lambda api: api.getGasConsumptionDomesticHotWaterUnit(), state_class=SensorStateClass.TOTAL_INCREASING, ), ViCareSensorEntityDescription( - key=SENSOR_DHW_GAS_CONSUMPTION_THIS_MONTH, + key="hotwater_gas_consumption_heating_this_month", name="Hot water gas consumption this month", value_getter=lambda api: api.getGasConsumptionDomesticHotWaterThisMonth(), unit_getter=lambda api: api.getGasConsumptionDomesticHotWaterUnit(), state_class=SensorStateClass.TOTAL_INCREASING, ), ViCareSensorEntityDescription( - key=SENSOR_DHW_GAS_CONSUMPTION_THIS_YEAR, + key="hotwater_gas_consumption_heating_this_year", name="Hot water gas consumption this year", value_getter=lambda api: api.getGasConsumptionDomesticHotWaterThisYear(), unit_getter=lambda api: api.getGasConsumptionDomesticHotWaterUnit(), state_class=SensorStateClass.TOTAL_INCREASING, ), ViCareSensorEntityDescription( - key=SENSOR_GAS_CONSUMPTION_TODAY, + key="gas_consumption_heating_today", name="Heating gas consumption today", value_getter=lambda api: api.getGasConsumptionHeatingToday(), unit_getter=lambda api: api.getGasConsumptionHeatingUnit(), state_class=SensorStateClass.TOTAL_INCREASING, ), ViCareSensorEntityDescription( - key=SENSOR_GAS_CONSUMPTION_THIS_WEEK, + key="gas_consumption_heating_this_week", name="Heating gas consumption this week", value_getter=lambda api: api.getGasConsumptionHeatingThisWeek(), unit_getter=lambda api: api.getGasConsumptionHeatingUnit(), state_class=SensorStateClass.TOTAL_INCREASING, ), ViCareSensorEntityDescription( - key=SENSOR_GAS_CONSUMPTION_THIS_MONTH, + key="gas_consumption_heating_this_month", name="Heating gas consumption this month", value_getter=lambda api: api.getGasConsumptionHeatingThisMonth(), unit_getter=lambda api: api.getGasConsumptionHeatingUnit(), state_class=SensorStateClass.TOTAL_INCREASING, ), ViCareSensorEntityDescription( - key=SENSOR_GAS_CONSUMPTION_THIS_YEAR, + key="gas_consumption_heating_this_year", name="Heating gas consumption this year", value_getter=lambda api: api.getGasConsumptionHeatingThisYear(), unit_getter=lambda api: api.getGasConsumptionHeatingUnit(), state_class=SensorStateClass.TOTAL_INCREASING, ), ViCareSensorEntityDescription( - key=SENSOR_POWER_PRODUCTION_CURRENT, + key="power_production_current", name="Power production current", native_unit_of_measurement=POWER_WATT, value_getter=lambda api: api.getPowerProductionCurrent(), @@ -178,7 +138,7 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM state_class=SensorStateClass.MEASUREMENT, ), ViCareSensorEntityDescription( - key=SENSOR_POWER_PRODUCTION_TODAY, + key="power_production_today", name="Power production today", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, value_getter=lambda api: api.getPowerProductionToday(), @@ -186,7 +146,7 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM state_class=SensorStateClass.TOTAL_INCREASING, ), ViCareSensorEntityDescription( - key=SENSOR_POWER_PRODUCTION_THIS_WEEK, + key="power_production_this_week", name="Power production this week", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, value_getter=lambda api: api.getPowerProductionThisWeek(), @@ -194,7 +154,7 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM state_class=SensorStateClass.TOTAL_INCREASING, ), ViCareSensorEntityDescription( - key=SENSOR_POWER_PRODUCTION_THIS_MONTH, + key="power_production_this_month", name="Power production this month", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, value_getter=lambda api: api.getPowerProductionThisMonth(), @@ -202,7 +162,7 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM state_class=SensorStateClass.TOTAL_INCREASING, ), ViCareSensorEntityDescription( - key=SENSOR_POWER_PRODUCTION_THIS_YEAR, + key="power_production_this_year", name="Power production this year", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, value_getter=lambda api: api.getPowerProductionThisYear(), @@ -210,21 +170,21 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM state_class=SensorStateClass.TOTAL_INCREASING, ), ViCareSensorEntityDescription( - key=SENSOR_SOLAR_STORAGE_TEMPERATURE, + key="solar storage temperature", name="Solar Storage Temperature", native_unit_of_measurement=TEMP_CELSIUS, value_getter=lambda api: api.getSolarStorageTemperature(), device_class=SensorDeviceClass.TEMPERATURE, ), ViCareSensorEntityDescription( - key=SENSOR_COLLECTOR_TEMPERATURE, + key="collector temperature", name="Solar Collector Temperature", native_unit_of_measurement=TEMP_CELSIUS, value_getter=lambda api: api.getSolarCollectorTemperature(), device_class=SensorDeviceClass.TEMPERATURE, ), ViCareSensorEntityDescription( - key=SENSOR_SOLAR_POWER_PRODUCTION, + key="solar power production", name="Solar Power Production", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, value_getter=lambda api: api.getSolarPowerProduction(), @@ -235,7 +195,7 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM CIRCUIT_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ViCareSensorEntityDescription( - key=SENSOR_SUPPLY_TEMPERATURE, + key="supply_temperature", name="Supply Temperature", native_unit_of_measurement=TEMP_CELSIUS, value_getter=lambda api: api.getSupplyTemperature(), @@ -244,20 +204,20 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM BURNER_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ViCareSensorEntityDescription( - key=SENSOR_BURNER_STARTS, + key="burner_starts", name="Burner Starts", icon="mdi:counter", value_getter=lambda api: api.getStarts(), ), ViCareSensorEntityDescription( - key=SENSOR_BURNER_HOURS, + key="burner_hours", name="Burner Hours", icon="mdi:counter", native_unit_of_measurement=TIME_HOURS, value_getter=lambda api: api.getHours(), ), ViCareSensorEntityDescription( - key=SENSOR_BURNER_MODULATION, + key="burner_modulation", name="Burner Modulation", icon="mdi:percent", native_unit_of_measurement=PERCENTAGE, @@ -267,48 +227,48 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM COMPRESSOR_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ViCareSensorEntityDescription( - key=SENSOR_COMPRESSOR_STARTS, + key="compressor_starts", name="Compressor Starts", icon="mdi:counter", value_getter=lambda api: api.getStarts(), ), ViCareSensorEntityDescription( - key=SENSOR_COMPRESSOR_HOURS, + key="compressor_hours", name="Compressor Hours", icon="mdi:counter", native_unit_of_measurement=TIME_HOURS, value_getter=lambda api: api.getHours(), ), ViCareSensorEntityDescription( - key=SENSOR_COMPRESSOR_HOURS_LOADCLASS1, + key="compressor_hours_loadclass1", name="Compressor Hours Load Class 1", icon="mdi:counter", native_unit_of_measurement=TIME_HOURS, value_getter=lambda api: api.getHoursLoadClass1(), ), ViCareSensorEntityDescription( - key=SENSOR_COMPRESSOR_HOURS_LOADCLASS2, + key="compressor_hours_loadclass2", name="Compressor Hours Load Class 2", icon="mdi:counter", native_unit_of_measurement=TIME_HOURS, value_getter=lambda api: api.getHoursLoadClass2(), ), ViCareSensorEntityDescription( - key=SENSOR_COMPRESSOR_HOURS_LOADCLASS3, + key="compressor_hours_loadclass3", name="Compressor Hours Load Class 3", icon="mdi:counter", native_unit_of_measurement=TIME_HOURS, value_getter=lambda api: api.getHoursLoadClass3(), ), ViCareSensorEntityDescription( - key=SENSOR_COMPRESSOR_HOURS_LOADCLASS4, + key="compressor_hours_loadclass4", name="Compressor Hours Load Class 4", icon="mdi:counter", native_unit_of_measurement=TIME_HOURS, value_getter=lambda api: api.getHoursLoadClass4(), ), ViCareSensorEntityDescription( - key=SENSOR_COMPRESSOR_HOURS_LOADCLASS5, + key="compressor_hours_loadclass5", name="Compressor Hours Load Class 5", icon="mdi:counter", native_unit_of_measurement=TIME_HOURS, diff --git a/homeassistant/components/vicare/water_heater.py b/homeassistant/components/vicare/water_heater.py index 0107ff8fe4cede..8a7169333bfe9a 100644 --- a/homeassistant/components/vicare/water_heater.py +++ b/homeassistant/components/vicare/water_heater.py @@ -56,18 +56,6 @@ } -def _build_entity(name, vicare_api, circuit, device_config, heating_type): - """Create a ViCare water_heater entity.""" - _LOGGER.debug("Found device %s", name) - return ViCareWater( - name, - vicare_api, - circuit, - device_config, - heating_type, - ) - - def _get_circuits(vicare_api): """Return the list of circuits.""" try: @@ -93,7 +81,7 @@ async def async_setup_entry( if len(circuits) > 1: suffix = f" {circuit.id}" - entity = _build_entity( + entity = ViCareWater( f"{name} Water{suffix}", api, circuit, From d92ed3ff2b6917817815632f9e48099d4059273d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 5 Feb 2022 14:38:14 +0100 Subject: [PATCH 0312/1098] Update coverage to 6.3.1 (#65790) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 6c516c09d8356c..c118093f2e1b6b 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -8,7 +8,7 @@ -c homeassistant/package_constraints.txt -r requirements_test_pre_commit.txt codecov==2.1.12 -coverage==6.2.0 +coverage==6.3.1 freezegun==1.1.0 jsonpickle==1.4.1 mock-open==1.4.0 From f3c5f9c9724d1481c9395b563790857980427a47 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 5 Feb 2022 15:17:12 +0100 Subject: [PATCH 0313/1098] Drop responses from test requirements (#65793) --- requirements_test.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index c118093f2e1b6b..2bc65934477604 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -27,7 +27,6 @@ pytest-timeout==2.1.0 pytest-xdist==2.4.0 pytest==6.2.5 requests_mock==1.9.2 -responses==0.12.0 respx==0.19.0 stdlib-list==0.7.0 tqdm==4.49.0 From 342f5182b9797138798d961206ae655f40218487 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 5 Feb 2022 09:23:19 -0600 Subject: [PATCH 0314/1098] WiZ cleanups part 1 (#65746) Co-authored-by: Franck Nijhof --- .strict-typing | 1 + homeassistant/components/wiz/__init__.py | 64 ++- homeassistant/components/wiz/config_flow.py | 33 +- homeassistant/components/wiz/const.py | 9 + homeassistant/components/wiz/light.py | 395 +++++++----------- homeassistant/components/wiz/manifest.json | 8 +- homeassistant/components/wiz/models.py | 15 + homeassistant/components/wiz/strings.json | 8 +- .../components/wiz/translations/en.json | 8 +- homeassistant/components/wiz/utils.py | 7 + mypy.ini | 11 + requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/wiz/test_config_flow.py | 84 ++-- 14 files changed, 311 insertions(+), 336 deletions(-) create mode 100644 homeassistant/components/wiz/models.py create mode 100644 homeassistant/components/wiz/utils.py diff --git a/.strict-typing b/.strict-typing index e84e01b180313b..f2c0c29ef2714c 100644 --- a/.strict-typing +++ b/.strict-typing @@ -203,6 +203,7 @@ homeassistant.components.webostv.* homeassistant.components.websocket_api.* homeassistant.components.wemo.* homeassistant.components.whois.* +homeassistant.components.wiz.* homeassistant.components.zodiac.* homeassistant.components.zeroconf.* homeassistant.components.zone.* diff --git a/homeassistant/components/wiz/__init__.py b/homeassistant/components/wiz/__init__.py index ec0979877cc1fc..6e7f841ebb2c50 100644 --- a/homeassistant/components/wiz/__init__.py +++ b/homeassistant/components/wiz/__init__.py @@ -1,38 +1,66 @@ """WiZ Platform integration.""" -from dataclasses import dataclass +from datetime import timedelta import logging from pywizlight import wizlight -from pywizlight.exceptions import WizLightConnectionError, WizLightTimeOutError from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST +from homeassistant.const import CONF_HOST, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.debounce import Debouncer +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import DOMAIN +from .const import DOMAIN, WIZ_EXCEPTIONS +from .models import WizData _LOGGER = logging.getLogger(__name__) -PLATFORMS = ["light"] +PLATFORMS = [Platform.LIGHT] + +REQUEST_REFRESH_DELAY = 0.35 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up the wiz integration from a config entry.""" - ip_address = entry.data.get(CONF_HOST) + ip_address = entry.data[CONF_HOST] _LOGGER.debug("Get bulb with IP: %s", ip_address) + bulb = wizlight(ip_address) try: - bulb = wizlight(ip_address) - scenes = await bulb.getSupportedScenes() await bulb.getMac() - except ( - WizLightTimeOutError, - WizLightConnectionError, - ConnectionRefusedError, - ) as err: + scenes = await bulb.getSupportedScenes() + # ValueError gets thrown if the bulb type + # cannot be determined on the first try. + # This is likely because way the library + # processes responses and can be cleaned up + # in the future. + except (ValueError, *WIZ_EXCEPTIONS) as err: raise ConfigEntryNotReady from err - hass.data.setdefault(DOMAIN, {})[entry.entry_id] = WizData(bulb=bulb, scenes=scenes) + async def _async_update() -> None: + """Update the WiZ device.""" + try: + await bulb.updateState() + except WIZ_EXCEPTIONS as ex: + raise UpdateFailed(f"Failed to update device at {ip_address}: {ex}") from ex + + coordinator = DataUpdateCoordinator( + hass=hass, + logger=_LOGGER, + name=entry.title, + update_interval=timedelta(seconds=15), + update_method=_async_update, + # We don't want an immediate refresh since the device + # takes a moment to reflect the state change + request_refresh_debouncer=Debouncer( + hass, _LOGGER, cooldown=REQUEST_REFRESH_DELAY, immediate=False + ), + ) + await coordinator.async_config_entry_first_refresh() + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = WizData( + coordinator=coordinator, bulb=bulb, scenes=scenes + ) hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True @@ -42,11 +70,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): hass.data[DOMAIN].pop(entry.entry_id) return unload_ok - - -@dataclass -class WizData: - """Data for the wiz integration.""" - - bulb: wizlight - scenes: list diff --git a/homeassistant/components/wiz/config_flow.py b/homeassistant/components/wiz/config_flow.py index dbe17adac00506..efa9cc1443edbf 100644 --- a/homeassistant/components/wiz/config_flow.py +++ b/homeassistant/components/wiz/config_flow.py @@ -1,37 +1,38 @@ """Config flow for WiZ Platform.""" +from __future__ import annotations + import logging +from typing import Any from pywizlight import wizlight from pywizlight.exceptions import WizLightConnectionError, WizLightTimeOutError import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_HOST, CONF_NAME +from homeassistant.const import CONF_HOST +from homeassistant.data_entry_flow import FlowResult from .const import DEFAULT_NAME, DOMAIN +from .utils import _short_mac _LOGGER = logging.getLogger(__name__) -STEP_USER_DATA_SCHEMA = vol.Schema( - { - vol.Required(CONF_HOST): str, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): str, - } -) - class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for WiZ.""" VERSION = 1 - async def async_step_user(self, user_input=None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle a flow initialized by the user.""" errors = {} if user_input is not None: bulb = wizlight(user_input[CONF_HOST]) try: mac = await bulb.getMac() + bulbtype = await bulb.get_bulbtype() except WizLightTimeOutError: errors["base"] = "bulb_time_out" except ConnectionRefusedError: @@ -43,10 +44,18 @@ async def async_step_user(self, user_input=None): errors["base"] = "unknown" else: await self.async_set_unique_id(mac) - self._abort_if_unique_id_configured() + self._abort_if_unique_id_configured( + updates={CONF_HOST: user_input[CONF_HOST]} + ) + bulb_type = bulbtype.bulb_type.value if bulbtype else "Unknown" + name = f"{DEFAULT_NAME} {bulb_type} {_short_mac(mac)}" return self.async_create_entry( - title=user_input[CONF_NAME], data=user_input + title=name, + data=user_input, ) + return self.async_show_form( - step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors + step_id="user", + data_schema=vol.Schema({vol.Required(CONF_HOST): str}), + errors=errors, ) diff --git a/homeassistant/components/wiz/const.py b/homeassistant/components/wiz/const.py index 30b3efb11d42fb..96a96e662f1d82 100644 --- a/homeassistant/components/wiz/const.py +++ b/homeassistant/components/wiz/const.py @@ -1,4 +1,13 @@ """Constants for the WiZ Platform integration.""" +from pywizlight.exceptions import WizLightConnectionError, WizLightTimeOutError DOMAIN = "wiz" DEFAULT_NAME = "WiZ" + +WIZ_EXCEPTIONS = ( + OSError, + WizLightTimeOutError, + TimeoutError, + WizLightConnectionError, + ConnectionRefusedError, +) diff --git a/homeassistant/components/wiz/light.py b/homeassistant/components/wiz/light.py index 6efacfb8b9580b..a8fcf1e1dae01b 100644 --- a/homeassistant/components/wiz/light.py +++ b/homeassistant/components/wiz/light.py @@ -1,12 +1,13 @@ """WiZ integration.""" from __future__ import annotations -from datetime import timedelta +import contextlib import logging +from typing import Any -from pywizlight import PilotBuilder, wizlight +from pywizlight import PilotBuilder from pywizlight.bulblibrary import BulbClass, BulbType -from pywizlight.exceptions import WizLightNotKnownBulb, WizLightTimeOutError +from pywizlight.exceptions import WizLightNotKnownBulb from pywizlight.rgbcw import convertHSfromRGBCW from pywizlight.scenes import get_id_from_scene_name @@ -16,83 +17,177 @@ ATTR_EFFECT, ATTR_HS_COLOR, ATTR_RGB_COLOR, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_COLOR_TEMP, + COLOR_MODE_BRIGHTNESS, + COLOR_MODE_COLOR_TEMP, + COLOR_MODE_HS, SUPPORT_EFFECT, LightEntity, ) -from homeassistant.const import CONF_NAME +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity import homeassistant.util.color as color_utils from .const import DOMAIN +from .models import WizData _LOGGER = logging.getLogger(__name__) -SUPPORT_FEATURES_RGB = ( - SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_COLOR_TEMP | SUPPORT_EFFECT -) - - -# set poll interval to 15 sec because of changes from external to the bulb -SCAN_INTERVAL = timedelta(seconds=15) - - -async def async_setup_entry(hass, entry, async_add_entities): +DEFAULT_COLOR_MODES = {COLOR_MODE_HS, COLOR_MODE_COLOR_TEMP} +DEFAULT_MIN_MIREDS = 153 +DEFAULT_MAX_MIREDS = 454 + + +def get_supported_color_modes(bulb_type: BulbType) -> set[str]: + """Flag supported features.""" + if not bulb_type: + # fallback + return DEFAULT_COLOR_MODES + color_modes = set() + try: + features = bulb_type.features + if features.color: + color_modes.add(COLOR_MODE_HS) + if features.color_tmp: + color_modes.add(COLOR_MODE_COLOR_TEMP) + if not color_modes and features.brightness: + color_modes.add(COLOR_MODE_BRIGHTNESS) + return color_modes + except WizLightNotKnownBulb: + _LOGGER.warning("Bulb is not present in the library. Fallback to full feature") + return DEFAULT_COLOR_MODES + + +def supports_effects(bulb_type: BulbType) -> bool: + """Check if a bulb supports effects.""" + with contextlib.suppress(WizLightNotKnownBulb): + return bool(bulb_type.features.effect) + return True # default is true + + +def get_min_max_mireds(bulb_type: BulbType) -> tuple[int, int]: + """Return the coldest and warmest color_temp that this light supports.""" + if bulb_type is None: + return DEFAULT_MIN_MIREDS, DEFAULT_MAX_MIREDS + # DW bulbs have no kelvin + if bulb_type.bulb_type == BulbClass.DW: + return 0, 0 + # If bulbtype is TW or RGB then return the kelvin value + try: + return color_utils.color_temperature_kelvin_to_mired( + bulb_type.kelvin_range.max + ), color_utils.color_temperature_kelvin_to_mired(bulb_type.kelvin_range.min) + except WizLightNotKnownBulb: + _LOGGER.debug("Kelvin is not present in the library. Fallback to 6500") + return DEFAULT_MIN_MIREDS, DEFAULT_MAX_MIREDS + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: """Set up the WiZ Platform from config_flow.""" - # Assign configuration variables. - wiz_data = hass.data[DOMAIN][entry.entry_id] - wizbulb = WizBulbEntity(wiz_data.bulb, entry.data.get(CONF_NAME), wiz_data.scenes) - # Add devices with defined name - async_add_entities([wizbulb], update_before_add=True) - return True + wiz_data: WizData = hass.data[DOMAIN][entry.entry_id] + async_add_entities([WizBulbEntity(wiz_data, entry.title)]) -class WizBulbEntity(LightEntity): +class WizBulbEntity(CoordinatorEntity, LightEntity): """Representation of WiZ Light bulb.""" - def __init__(self, light: wizlight, name, scenes): + def __init__(self, wiz_data: WizData, name: str) -> None: """Initialize an WiZLight.""" - self._light = light - self._state = None - self._brightness = None + super().__init__(wiz_data.coordinator) + self._light = wiz_data.bulb + bulb_type: BulbType = self._light.bulbtype + self._attr_unique_id = self._light.mac self._attr_name = name - self._rgb_color = None - self._temperature = None - self._hscolor = None - self._available = None - self._effect = None - self._scenes: list[str] = scenes - self._bulbtype: BulbType = light.bulbtype - self._mac = light.mac - self._attr_unique_id = light.mac - # new init states - self._attr_min_mireds = self.get_min_mireds() - self._attr_max_mireds = self.get_max_mireds() - self._attr_supported_features = self.get_supported_features() + self._attr_effect_list = wiz_data.scenes + self._attr_min_mireds, self._attr_max_mireds = get_min_max_mireds(bulb_type) + self._attr_supported_color_modes = get_supported_color_modes(bulb_type) + if supports_effects(bulb_type): + self._attr_supported_features = SUPPORT_EFFECT + self._attr_device_info = DeviceInfo( + connections={(CONNECTION_NETWORK_MAC, self._light.mac)}, + name=name, + manufacturer="WiZ", + model=bulb_type.name, + ) + + @property + def is_on(self) -> bool | None: + """Return true if light is on.""" + is_on: bool | None = self._light.status + return is_on @property - def brightness(self): + def brightness(self) -> int | None: """Return the brightness of the light.""" - return self._brightness + if (brightness := self._light.state.get_brightness()) is None: + return None + if 0 <= int(brightness) <= 255: + return int(brightness) + _LOGGER.error("Received invalid brightness : %s. Expected: 0-255", brightness) + return None @property - def rgb_color(self): - """Return the color property.""" - return self._rgb_color + def color_mode(self) -> str: + """Return the current color mode.""" + color_modes = self.supported_color_modes + assert color_modes is not None + if ( + COLOR_MODE_COLOR_TEMP in color_modes + and self._light.state.get_colortemp() is not None + ): + return COLOR_MODE_COLOR_TEMP + if ( + COLOR_MODE_HS in color_modes + and (rgb := self._light.state.get_rgb()) is not None + and rgb[0] is not None + ): + return COLOR_MODE_HS + return COLOR_MODE_BRIGHTNESS @property - def hs_color(self): + def hs_color(self) -> tuple[float, float] | None: """Return the hs color value.""" - return self._hscolor + colortemp = self._light.state.get_colortemp() + if colortemp is not None and colortemp != 0: + return None + if (rgb := self._light.state.get_rgb()) is None: + return None + if rgb[0] is None: + # this is the case if the temperature was changed + # do nothing until the RGB color was changed + return None + if (warmwhite := self._light.state.get_warm_white()) is None: + return None + hue_sat = convertHSfromRGBCW(rgb, warmwhite) + hue: float = hue_sat[0] + sat: float = hue_sat[1] + return hue, sat @property - def is_on(self): - """Return true if light is on.""" - return self._state + def color_temp(self) -> int | None: + """Return the CT color value in mireds.""" + colortemp = self._light.state.get_colortemp() + if colortemp is None or colortemp == 0: + return None + _LOGGER.debug( + "[wizlight %s] kelvin from the bulb: %s", self._light.ip, colortemp + ) + return color_utils.color_temperature_kelvin_to_mired(colortemp) - async def async_turn_on(self, **kwargs): + @property + def effect(self) -> str | None: + """Return the current effect.""" + effect: str | None = self._light.state.get_scene() + return effect + + async def async_turn_on(self, **kwargs: Any) -> None: """Instruct the light to turn on.""" brightness = None @@ -150,199 +245,9 @@ async def async_turn_on(self, **kwargs): ) await self._light.turn_on(pilot) + await self.coordinator.async_request_refresh() - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Instruct the light to turn off.""" await self._light.turn_off() - - @property - def color_temp(self): - """Return the CT color value in mireds.""" - return self._temperature - - def get_min_mireds(self) -> int: - """Return the coldest color_temp that this light supports.""" - if self._bulbtype is None: - return color_utils.color_temperature_kelvin_to_mired(6500) - # DW bulbs have no kelvin - if self._bulbtype.bulb_type == BulbClass.DW: - return 0 - # If bulbtype is TW or RGB then return the kelvin value - try: - return color_utils.color_temperature_kelvin_to_mired( - self._bulbtype.kelvin_range.max - ) - except WizLightNotKnownBulb: - _LOGGER.debug("Kelvin is not present in the library. Fallback to 6500") - return color_utils.color_temperature_kelvin_to_mired(6500) - - def get_max_mireds(self) -> int: - """Return the warmest color_temp that this light supports.""" - if self._bulbtype is None: - return color_utils.color_temperature_kelvin_to_mired(2200) - # DW bulbs have no kelvin - if self._bulbtype.bulb_type == BulbClass.DW: - return 0 - # If bulbtype is TW or RGB then return the kelvin value - try: - return color_utils.color_temperature_kelvin_to_mired( - self._bulbtype.kelvin_range.min - ) - except WizLightNotKnownBulb: - _LOGGER.debug("Kelvin is not present in the library. Fallback to 2200") - return color_utils.color_temperature_kelvin_to_mired(2200) - - def get_supported_features(self) -> int: - """Flag supported features.""" - if not self._bulbtype: - # fallback - return SUPPORT_FEATURES_RGB - features = 0 - try: - # Map features for better reading - if self._bulbtype.features.brightness: - features = features | SUPPORT_BRIGHTNESS - if self._bulbtype.features.color: - features = features | SUPPORT_COLOR - if self._bulbtype.features.effect: - features = features | SUPPORT_EFFECT - if self._bulbtype.features.color_tmp: - features = features | SUPPORT_COLOR_TEMP - return features - except WizLightNotKnownBulb: - _LOGGER.warning( - "Bulb is not present in the library. Fallback to full feature" - ) - return SUPPORT_FEATURES_RGB - - @property - def effect(self): - """Return the current effect.""" - return self._effect - - @property - def effect_list(self): - """Return the list of supported effects. - - URL: https://docs.pro.wizconnected.com/#light-modes - """ - return self._scenes - - @property - def available(self): - """Return if light is available.""" - return self._available - - async def async_update(self): - """Fetch new state data for this light.""" - await self.update_state() - - if self._state is not None and self._state is not False: - self.update_brightness() - self.update_temperature() - self.update_color() - self.update_effect() - - @property - def device_info(self): - """Get device specific attributes.""" - return { - "connections": {(CONNECTION_NETWORK_MAC, self._mac)}, - "name": self._attr_name, - "manufacturer": "WiZ Light Platform", - "model": self._bulbtype.name, - } - - def update_state_available(self): - """Update the state if bulb is available.""" - self._state = self._light.status - self._available = True - - def update_state_unavailable(self): - """Update the state if bulb is unavailable.""" - self._state = False - self._available = False - - async def update_state(self): - """Update the state.""" - try: - await self._light.updateState() - except (ConnectionRefusedError, TimeoutError, WizLightTimeOutError) as ex: - _LOGGER.debug(ex) - self.update_state_unavailable() - else: - if self._light.state is None: - self.update_state_unavailable() - else: - self.update_state_available() - _LOGGER.debug( - "[wizlight %s] updated state: %s and available: %s", - self._light.ip, - self._state, - self._available, - ) - - def update_brightness(self): - """Update the brightness.""" - if self._light.state.get_brightness() is None: - return - brightness = self._light.state.get_brightness() - if 0 <= int(brightness) <= 255: - self._brightness = int(brightness) - else: - _LOGGER.error( - "Received invalid brightness : %s. Expected: 0-255", brightness - ) - self._brightness = None - - def update_temperature(self): - """Update the temperature.""" - colortemp = self._light.state.get_colortemp() - if colortemp is None or colortemp == 0: - self._temperature = None - return - - _LOGGER.debug( - "[wizlight %s] kelvin from the bulb: %s", self._light.ip, colortemp - ) - temperature = color_utils.color_temperature_kelvin_to_mired(colortemp) - self._temperature = temperature - - def update_color(self): - """Update the hs color.""" - colortemp = self._light.state.get_colortemp() - if colortemp is not None and colortemp != 0: - self._hscolor = None - return - if self._light.state.get_rgb() is None: - return - - rgb = self._light.state.get_rgb() - if rgb[0] is None: - # this is the case if the temperature was changed - # do nothing until the RGB color was changed - return - warmwhite = self._light.state.get_warm_white() - if warmwhite is None: - return - self._hscolor = convertHSfromRGBCW(rgb, warmwhite) - - def update_effect(self): - """Update the bulb scene.""" - self._effect = self._light.state.get_scene() - - async def get_bulb_type(self): - """Get the bulb type.""" - if self._bulbtype is not None: - return self._bulbtype - try: - self._bulbtype = await self._light.get_bulbtype() - _LOGGER.info( - "[wizlight %s] Initiate the WiZ bulb as %s", - self._light.ip, - self._bulbtype.name, - ) - except WizLightTimeOutError: - _LOGGER.debug( - "[wizlight %s] Bulbtype update failed - Timeout", self._light.ip - ) + await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/wiz/manifest.json b/homeassistant/components/wiz/manifest.json index ca50cd8c7e2818..8b366e7f506ee7 100644 --- a/homeassistant/components/wiz/manifest.json +++ b/homeassistant/components/wiz/manifest.json @@ -3,11 +3,7 @@ "name": "WiZ", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/wiz", - "requirements": [ - "pywizlight==0.4.15" - ], + "requirements": ["pywizlight==0.4.16"], "iot_class": "local_polling", - "codeowners": [ - "@sbidy" - ] + "codeowners": ["@sbidy"] } \ No newline at end of file diff --git a/homeassistant/components/wiz/models.py b/homeassistant/components/wiz/models.py new file mode 100644 index 00000000000000..efbb2a664b152d --- /dev/null +++ b/homeassistant/components/wiz/models.py @@ -0,0 +1,15 @@ +"""WiZ integration models.""" +from dataclasses import dataclass + +from pywizlight import wizlight + +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + + +@dataclass +class WizData: + """Data for the wiz integration.""" + + coordinator: DataUpdateCoordinator + bulb: wizlight + scenes: list diff --git a/homeassistant/components/wiz/strings.json b/homeassistant/components/wiz/strings.json index 59e6d18c179dbd..3088a7f098d015 100644 --- a/homeassistant/components/wiz/strings.json +++ b/homeassistant/components/wiz/strings.json @@ -3,13 +3,9 @@ "step": { "user": { "data": { - "host": "[%key:common::config_flow::data::host%]", - "name": "[%key:common::config_flow::data::name%]" + "host": "[%key:common::config_flow::data::host%]" }, - "description": "Please enter a hostname or IP address and name to add a new bulb:" - }, - "confirm": { - "description": "[%key:common::config_flow::description::confirm_setup%]" + "description": "Enter the IP address of the device." } }, "error": { diff --git a/homeassistant/components/wiz/translations/en.json b/homeassistant/components/wiz/translations/en.json index 7d95281e14aff0..24f34bd0f5be37 100644 --- a/homeassistant/components/wiz/translations/en.json +++ b/homeassistant/components/wiz/translations/en.json @@ -11,15 +11,11 @@ "unknown": "Unexpected error" }, "step": { - "confirm": { - "description": "Do you want to add a new Bulb?" - }, "user": { "data": { - "host": "Hostname or IP", - "name": "Name" + "host": "Host" }, - "description": "Please enter a hostname or IP address and name to add a new bulb:" + "description": "Enter the IP address of the device." } } } diff --git a/homeassistant/components/wiz/utils.py b/homeassistant/components/wiz/utils.py new file mode 100644 index 00000000000000..edce7d47fea48a --- /dev/null +++ b/homeassistant/components/wiz/utils.py @@ -0,0 +1,7 @@ +"""WiZ utils.""" +from __future__ import annotations + + +def _short_mac(mac: str) -> str: + """Get the short mac address from the full mac.""" + return mac.replace(":", "").upper()[-6:] diff --git a/mypy.ini b/mypy.ini index 32ee9352326944..524ef2c542084d 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2050,6 +2050,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.wiz.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.zodiac.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/requirements_all.txt b/requirements_all.txt index a886a0f98b34ef..513a69e59b4e81 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2051,7 +2051,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.4.15 +pywizlight==0.4.16 # homeassistant.components.xeoma pyxeoma==1.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 74b3e53e8c8950..f098beb6261b50 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1276,7 +1276,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.4.15 +pywizlight==0.4.16 # homeassistant.components.zerproc pyzerproc==0.4.8 diff --git a/tests/components/wiz/test_config_flow.py b/tests/components/wiz/test_config_flow.py index e08ca87a389b70..20afd994046701 100644 --- a/tests/components/wiz/test_config_flow.py +++ b/tests/components/wiz/test_config_flow.py @@ -1,4 +1,5 @@ """Test the WiZ Platform config flow.""" +from contextlib import contextmanager from unittest.mock import patch import pytest @@ -9,27 +10,49 @@ WizLightTimeOutError, ) from homeassistant.components.wiz.const import DOMAIN -from homeassistant.const import CONF_HOST, CONF_NAME +from homeassistant.const import CONF_HOST from tests.common import MockConfigEntry -FAKE_BULB_CONFIG = '{"method":"getSystemConfig","env":"pro","result":\ - {"mac":"ABCABCABCABC",\ - "homeId":653906,\ - "roomId":989983,\ - "moduleName":"ESP_0711_STR",\ - "fwVersion":"1.21.0",\ - "groupId":0,"drvConf":[20,2],\ - "ewf":[255,0,255,255,0,0,0],\ - "ewfHex":"ff00ffff000000",\ - "ping":0}}' - -TEST_SYSTEM_INFO = {"id": "ABCABCABCABC", "name": "Test Bulb"} - - -TEST_CONNECTION = {CONF_HOST: "1.1.1.1", CONF_NAME: "Test Bulb"} - -TEST_NO_IP = {CONF_HOST: "this is no IP input", CONF_NAME: "Test Bulb"} +FAKE_MAC = "ABCABCABCABC" +FAKE_BULB_CONFIG = { + "method": "getSystemConfig", + "env": "pro", + "result": { + "mac": FAKE_MAC, + "homeId": 653906, + "roomId": 989983, + "moduleName": "ESP_0711_STR", + "fwVersion": "1.21.0", + "groupId": 0, + "drvConf": [20, 2], + "ewf": [255, 0, 255, 255, 0, 0, 0], + "ewfHex": "ff00ffff000000", + "ping": 0, + }, +} +FAKE_EXTENDED_WHITE_RANGE = [2200, 2700, 6500, 6500] +TEST_SYSTEM_INFO = {"id": FAKE_MAC, "name": "Test Bulb"} +TEST_CONNECTION = {CONF_HOST: "1.1.1.1"} +TEST_NO_IP = {CONF_HOST: "this is no IP input"} + + +def _patch_wizlight(): + @contextmanager + def _patcher(): + with patch( + "homeassistant.components.wiz.wizlight.getBulbConfig", + return_value=FAKE_BULB_CONFIG, + ), patch( + "homeassistant.components.wiz.wizlight.getExtendedWhiteRange", + return_value=FAKE_EXTENDED_WHITE_RANGE, + ), patch( + "homeassistant.components.wiz.wizlight.getMac", + return_value=FAKE_MAC, + ): + yield + + return _patcher() async def test_form(hass): @@ -40,13 +63,7 @@ async def test_form(hass): assert result["type"] == "form" assert result["errors"] == {} # Patch functions - with patch( - "homeassistant.components.wiz.wizlight.getBulbConfig", - return_value=FAKE_BULB_CONFIG, - ), patch( - "homeassistant.components.wiz.wizlight.getMac", - return_value="ABCABCABCABC", - ) as mock_setup, patch( + with _patch_wizlight(), patch( "homeassistant.components.wiz.async_setup_entry", return_value=True, ) as mock_setup_entry: @@ -57,9 +74,10 @@ async def test_form(hass): await hass.async_block_till_done() assert result2["type"] == "create_entry" - assert result2["title"] == "Test Bulb" - assert result2["data"] == TEST_CONNECTION - assert len(mock_setup.mock_calls) == 1 + assert result2["title"] == "WiZ Dimmable White ABCABC" + assert result2["data"] == { + CONF_HOST: "1.1.1.1", + } assert len(mock_setup_entry.mock_calls) == 1 @@ -98,8 +116,6 @@ async def test_form_updates_unique_id(hass): unique_id=TEST_SYSTEM_INFO["id"], data={ CONF_HOST: "dummy", - CONF_NAME: TEST_SYSTEM_INFO["name"], - "id": TEST_SYSTEM_INFO["id"], }, ) @@ -108,13 +124,7 @@ async def test_form_updates_unique_id(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "homeassistant.components.wiz.wizlight.getBulbConfig", - return_value=FAKE_BULB_CONFIG, - ), patch( - "homeassistant.components.wiz.wizlight.getMac", - return_value="ABCABCABCABC", - ), patch( + with _patch_wizlight(), patch( "homeassistant.components.wiz.async_setup_entry", return_value=True, ): From ae3b337d3eaa63d2a7d48ed9f36c492b189ab740 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Sat, 5 Feb 2022 17:44:05 +0200 Subject: [PATCH 0315/1098] Bump aioshelly to 1.0.9 (#65803) --- homeassistant/components/shelly/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json index 30e766b6ec48a3..085b3ad733d715 100644 --- a/homeassistant/components/shelly/manifest.json +++ b/homeassistant/components/shelly/manifest.json @@ -3,7 +3,7 @@ "name": "Shelly", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/shelly", - "requirements": ["aioshelly==1.0.8"], + "requirements": ["aioshelly==1.0.9"], "zeroconf": [ { "type": "_http._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index 513a69e59b4e81..bfc7f30cfb2b17 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -254,7 +254,7 @@ aioridwell==2021.12.2 aiosenseme==0.6.1 # homeassistant.components.shelly -aioshelly==1.0.8 +aioshelly==1.0.9 # homeassistant.components.steamist aiosteamist==0.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f098beb6261b50..c81f4943114859 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -189,7 +189,7 @@ aioridwell==2021.12.2 aiosenseme==0.6.1 # homeassistant.components.shelly -aioshelly==1.0.8 +aioshelly==1.0.9 # homeassistant.components.steamist aiosteamist==0.3.1 From 5c6d019d6bcb1b49a9fec2429227621c711346a2 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 5 Feb 2022 16:46:50 +0100 Subject: [PATCH 0316/1098] Remove options flow from Plugwise (#65808) --- .../components/plugwise/config_flow.py | 34 --------- homeassistant/components/plugwise/const.py | 9 +-- homeassistant/components/plugwise/gateway.py | 35 +--------- tests/components/plugwise/test_config_flow.py | 69 +------------------ 4 files changed, 9 insertions(+), 138 deletions(-) diff --git a/homeassistant/components/plugwise/config_flow.py b/homeassistant/components/plugwise/config_flow.py index a120daf0083bea..eb0c56115c8ac0 100644 --- a/homeassistant/components/plugwise/config_flow.py +++ b/homeassistant/components/plugwise/config_flow.py @@ -15,17 +15,14 @@ CONF_NAME, CONF_PASSWORD, CONF_PORT, - CONF_SCAN_INTERVAL, CONF_USERNAME, ) -from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( API, DEFAULT_PORT, - DEFAULT_SCAN_INTERVAL, DEFAULT_USERNAME, DOMAIN, FLOW_NET, @@ -186,37 +183,6 @@ async def async_step_user(self, user_input=None): errors=errors, ) - @staticmethod - @callback - def async_get_options_flow(config_entry): - """Get the options flow for this handler.""" - return PlugwiseOptionsFlowHandler(config_entry) - - -class PlugwiseOptionsFlowHandler(config_entries.OptionsFlow): - """Plugwise option flow.""" - - def __init__(self, config_entry): - """Initialize options flow.""" - self.config_entry = config_entry - - async def async_step_init(self, user_input=None): - """Manage the Plugwise options.""" - if user_input is not None: - return self.async_create_entry(title="", data=user_input) - - api = self.hass.data[DOMAIN][self.config_entry.entry_id][API] - interval = DEFAULT_SCAN_INTERVAL[api.smile_type] - - data = { - vol.Optional( - CONF_SCAN_INTERVAL, - default=self.config_entry.options.get(CONF_SCAN_INTERVAL, interval), - ): int - } - - return self.async_show_form(step_id="init", data_schema=vol.Schema(data)) - class CannotConnect(exceptions.HomeAssistantError): """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/plugwise/const.py b/homeassistant/components/plugwise/const.py index 9c6823e22e4750..772bccd92c1a7d 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -1,4 +1,6 @@ """Constants for Plugwise component.""" +from datetime import timedelta + from homeassistant.const import Platform API = "api" @@ -18,7 +20,6 @@ SMILE = "smile" STRETCH = "stretch" STRETCH_USERNAME = "stretch" -UNDO_UPDATE_LISTENER = "undo_update_listener" UNIT_LUMEN = "lm" PLATFORMS_GATEWAY = [ @@ -47,9 +48,9 @@ DEFAULT_NAME = "Smile" DEFAULT_PORT = 80 DEFAULT_SCAN_INTERVAL = { - "power": 10, - "stretch": 60, - "thermostat": 60, + "power": timedelta(seconds=10), + "stretch": timedelta(seconds=60), + "thermostat": timedelta(seconds=60), } DEFAULT_TIMEOUT = 60 DEFAULT_USERNAME = "smile" diff --git a/homeassistant/components/plugwise/gateway.py b/homeassistant/components/plugwise/gateway.py index 67f1895943dccf..24c937799ba051 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -2,7 +2,6 @@ from __future__ import annotations import asyncio -from datetime import timedelta import logging import async_timeout @@ -21,7 +20,6 @@ CONF_HOST, CONF_PASSWORD, CONF_PORT, - CONF_SCAN_INTERVAL, CONF_USERNAME, ) from homeassistant.core import HomeAssistant, callback @@ -36,7 +34,6 @@ ) from .const import ( - API, COORDINATOR, DEFAULT_PORT, DEFAULT_SCAN_INTERVAL, @@ -47,7 +44,6 @@ PLATFORMS_GATEWAY, PW_TYPE, SENSOR_PLATFORMS, - UNDO_UPDATE_LISTENER, ) _LOGGER = logging.getLogger(__name__) @@ -85,12 +81,6 @@ async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: _LOGGER.error("Timeout while connecting to Smile %s", api.smile_name) raise ConfigEntryNotReady from err - update_interval = timedelta( - seconds=entry.options.get( - CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL[api.smile_type] - ) - ) - async def async_update_data(): """Update data via API endpoint.""" try: @@ -105,7 +95,7 @@ async def async_update_data(): _LOGGER, name=f"Smile {api.smile_name}", update_method=async_update_data, - update_interval=update_interval, + update_interval=DEFAULT_SCAN_INTERVAL[api.smile_type], ) await coordinator.async_config_entry_first_refresh() @@ -115,13 +105,10 @@ async def async_update_data(): if entry.unique_id is None and api.smile_version[0] != "1.8.0": hass.config_entries.async_update_entry(entry, unique_id=api.smile_hostname) - undo_listener = entry.add_update_listener(_update_listener) - hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { "api": api, COORDINATOR: coordinator, PW_TYPE: GATEWAY, - UNDO_UPDATE_LISTENER: undo_listener, } device_registry = dr.async_get(hass) @@ -145,28 +132,12 @@ async def async_update_data(): return True -async def _update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Handle options update.""" - coordinator = hass.data[DOMAIN][entry.entry_id][COORDINATOR] - update_interval = entry.options.get(CONF_SCAN_INTERVAL) - if update_interval is None: - api = hass.data[DOMAIN][entry.entry_id][API] - update_interval = DEFAULT_SCAN_INTERVAL[api.smile_type] - - coordinator.update_interval = timedelta(seconds=update_interval) - - async def async_unload_entry_gw(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" - unload_ok = await hass.config_entries.async_unload_platforms( + if unload_ok := await hass.config_entries.async_unload_platforms( entry, PLATFORMS_GATEWAY - ) - - hass.data[DOMAIN][entry.entry_id][UNDO_UPDATE_LISTENER]() - - if unload_ok: + ): hass.data[DOMAIN].pop(entry.entry_id) - return unload_ok diff --git a/tests/components/plugwise/test_config_flow.py b/tests/components/plugwise/test_config_flow.py index 9f9be299f843b3..284be67a386f4b 100644 --- a/tests/components/plugwise/test_config_flow.py +++ b/tests/components/plugwise/test_config_flow.py @@ -1,5 +1,5 @@ """Test the Plugwise config flow.""" -from unittest.mock import AsyncMock, MagicMock, patch +from unittest.mock import AsyncMock, patch from plugwise.exceptions import ( ConnectionFailedError, @@ -12,7 +12,6 @@ from homeassistant.components.plugwise.const import ( API, DEFAULT_PORT, - DEFAULT_SCAN_INTERVAL, DOMAIN, FLOW_NET, FLOW_TYPE, @@ -24,7 +23,6 @@ CONF_NAME, CONF_PASSWORD, CONF_PORT, - CONF_SCAN_INTERVAL, CONF_SOURCE, CONF_USERNAME, ) @@ -375,68 +373,3 @@ async def test_form_other_problem(hass, mock_smile): assert result2["type"] == RESULT_TYPE_FORM assert result2["errors"] == {"base": "unknown"} - - -async def test_options_flow_power(hass, mock_smile) -> None: - """Test config flow options DSMR environments.""" - entry = MockConfigEntry( - domain=DOMAIN, - title=CONF_NAME, - data={CONF_HOST: TEST_HOST, CONF_PASSWORD: TEST_PASSWORD}, - options={CONF_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL}, - ) - - hass.data[DOMAIN] = {entry.entry_id: {"api": MagicMock(smile_type="power")}} - entry.add_to_hass(hass) - - with patch( - "homeassistant.components.plugwise.async_setup_entry", return_value=True - ): - assert await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - result = await hass.config_entries.options.async_init(entry.entry_id) - - assert result["type"] == RESULT_TYPE_FORM - assert result["step_id"] == "init" - - result = await hass.config_entries.options.async_configure( - result["flow_id"], user_input={CONF_SCAN_INTERVAL: 10} - ) - assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["data"] == { - CONF_SCAN_INTERVAL: 10, - } - - -async def test_options_flow_thermo(hass, mock_smile) -> None: - """Test config flow options for thermostatic environments.""" - entry = MockConfigEntry( - domain=DOMAIN, - title=CONF_NAME, - data={CONF_HOST: TEST_HOST, CONF_PASSWORD: TEST_PASSWORD}, - options={CONF_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL}, - ) - - hass.data[DOMAIN] = {entry.entry_id: {"api": MagicMock(smile_type="thermostat")}} - entry.add_to_hass(hass) - - with patch( - "homeassistant.components.plugwise.async_setup_entry", return_value=True - ): - assert await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - result = await hass.config_entries.options.async_init(entry.entry_id) - - assert result["type"] == RESULT_TYPE_FORM - assert result["step_id"] == "init" - - result = await hass.config_entries.options.async_configure( - result["flow_id"], user_input={CONF_SCAN_INTERVAL: 60} - ) - - assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["data"] == { - CONF_SCAN_INTERVAL: 60, - } From df0e98f4283be25307078858f772fa23fc88a080 Mon Sep 17 00:00:00 2001 From: David McClosky Date: Sat, 5 Feb 2022 11:01:39 -0500 Subject: [PATCH 0317/1098] Remove dmcc from codeowners in vlc_telnet (#65810) --- CODEOWNERS | 4 ++-- homeassistant/components/vlc_telnet/manifest.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index b7391dbc823a84..b89de45775108f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1032,8 +1032,8 @@ tests/components/vilfo/* @ManneW homeassistant/components/vivotek/* @HarlemSquirrel homeassistant/components/vizio/* @raman325 tests/components/vizio/* @raman325 -homeassistant/components/vlc_telnet/* @rodripf @dmcc @MartinHjelmare -tests/components/vlc_telnet/* @rodripf @dmcc @MartinHjelmare +homeassistant/components/vlc_telnet/* @rodripf @MartinHjelmare +tests/components/vlc_telnet/* @rodripf @MartinHjelmare homeassistant/components/volkszaehler/* @fabaff homeassistant/components/volumio/* @OnFreund tests/components/volumio/* @OnFreund diff --git a/homeassistant/components/vlc_telnet/manifest.json b/homeassistant/components/vlc_telnet/manifest.json index 494cae37b5712d..2d2b01cb04b5f2 100644 --- a/homeassistant/components/vlc_telnet/manifest.json +++ b/homeassistant/components/vlc_telnet/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/vlc_telnet", "requirements": ["aiovlc==0.1.0"], - "codeowners": ["@rodripf", "@dmcc", "@MartinHjelmare"], + "codeowners": ["@rodripf", "@MartinHjelmare"], "iot_class": "local_polling", "loggers": ["aiovlc"] } From 00e8d2bc3d94059c69f135a7445a9da4113907fc Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 5 Feb 2022 17:09:49 +0100 Subject: [PATCH 0318/1098] Small cleanup in Plugwise climate (#65800) --- homeassistant/components/plugwise/climate.py | 229 +++++++------------ 1 file changed, 83 insertions(+), 146 deletions(-) diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index 86f701ccb7de6f..600b61841919de 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -1,7 +1,9 @@ """Plugwise Climate component for Home Assistant.""" import logging +from typing import Any from plugwise.exceptions import PlugwiseException +from plugwise.smile import Smile from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( @@ -18,6 +20,7 @@ from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import ( COORDINATOR, @@ -32,8 +35,6 @@ HVAC_MODES_HEAT_ONLY = [HVAC_MODE_HEAT, HVAC_MODE_AUTO] HVAC_MODES_HEAT_COOL = [HVAC_MODE_HEAT_COOL, HVAC_MODE_AUTO] -SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE - _LOGGER = logging.getLogger(__name__) @@ -66,8 +67,6 @@ async def async_setup_entry( dev_id, device_properties["location"], device_properties["class"], - DEFAULT_MIN_TEMP, - DEFAULT_MAX_TEMP, ) entities.append(thermostat) @@ -78,199 +77,137 @@ async def async_setup_entry( class PwThermostat(SmileGateway, ClimateEntity): """Representation of an Plugwise thermostat.""" + _attr_hvac_mode = HVAC_MODE_HEAT + _attr_max_temp = DEFAULT_MAX_TEMP + _attr_min_temp = DEFAULT_MIN_TEMP + _attr_preset_mode = None + _attr_preset_modes = None + _attr_supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE + _attr_temperature_unit = TEMP_CELSIUS + _attr_hvac_modes = HVAC_MODES_HEAT_ONLY + _attr_hvac_mode = HVAC_MODE_HEAT + def __init__( - self, api, coordinator, name, dev_id, loc_id, model, min_temp, max_temp - ): + self, + api: Smile, + coordinator: DataUpdateCoordinator, + name: str, + dev_id: str, + loc_id: str, + model: str, + ) -> None: """Set up the Plugwise API.""" super().__init__(api, coordinator, name, dev_id) + self._attr_extra_state_attributes = {} self._api = api self._loc_id = loc_id self._model = model - self._min_temp = min_temp - self._max_temp = max_temp - self._selected_schema = None - self._last_active_schema = None - self._preset_mode = None self._presets = None - self._presets_list = None - self._heating_state = None - self._cooling_state = None - self._compressor_state = None - self._dhw_state = None - self._hvac_mode = None - self._schema_names = None - self._schema_status = None - self._temperature = None - self._setpoint = None - self._water_pressure = None - self._schedule_temp = None - self._hvac_mode = None self._single_thermostat = self._api.single_master_thermostat() self._unique_id = f"{dev_id}-climate" - @property - def hvac_action(self): - """Return the current action.""" - if self._single_thermostat: - if self._heating_state: - return CURRENT_HVAC_HEAT - if self._cooling_state: - return CURRENT_HVAC_COOL - return CURRENT_HVAC_IDLE - if self._setpoint > self._temperature: - return CURRENT_HVAC_HEAT - return CURRENT_HVAC_IDLE - - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_FLAGS - - @property - def extra_state_attributes(self): - """Return the device specific state attributes.""" - attributes = {} - if self._schema_names: - attributes["available_schemas"] = self._schema_names - if self._selected_schema: - attributes["selected_schema"] = self._selected_schema - return attributes - - @property - def preset_modes(self): - """Return the available preset modes list.""" - return self._presets_list - - @property - def hvac_modes(self): - """Return the available hvac modes list.""" - if self._compressor_state is not None: - return HVAC_MODES_HEAT_COOL - return HVAC_MODES_HEAT_ONLY - - @property - def hvac_mode(self): - """Return current active hvac state.""" - return self._hvac_mode - - @property - def target_temperature(self): - """Return the target_temperature.""" - return self._setpoint - - @property - def preset_mode(self): - """Return the active preset.""" - if self._presets: - return self._preset_mode - return None - - @property - def current_temperature(self): - """Return the current room temperature.""" - return self._temperature - - @property - def min_temp(self): - """Return the minimal temperature possible to set.""" - return self._min_temp - - @property - def max_temp(self): - """Return the maximum temperature possible to set.""" - return self._max_temp - - @property - def temperature_unit(self): - """Return the unit of measured temperature.""" - return TEMP_CELSIUS - - async def async_set_temperature(self, **kwargs): + async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" temperature = kwargs.get(ATTR_TEMPERATURE) if (temperature is not None) and ( - self._min_temp < temperature < self._max_temp + self._attr_min_temp < temperature < self._attr_max_temp ): try: await self._api.set_temperature(self._loc_id, temperature) - self._setpoint = temperature + self._attr_target_temperature = temperature self.async_write_ha_state() except PlugwiseException: _LOGGER.error("Error while communicating to device") else: _LOGGER.error("Invalid temperature requested") - async def async_set_hvac_mode(self, hvac_mode): + async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set the hvac mode.""" state = SCHEDULE_OFF + climate_data = self._api.get_device_data(self._dev_id) + if hvac_mode == HVAC_MODE_AUTO: state = SCHEDULE_ON try: - await self._api.set_temperature(self._loc_id, self._schedule_temp) - self._setpoint = self._schedule_temp + await self._api.set_temperature( + self._loc_id, climate_data.get("schedule_temperature") + ) + self._attr_target_temperature = climate_data.get("schedule_temperature") except PlugwiseException: _LOGGER.error("Error while communicating to device") + try: await self._api.set_schedule_state( - self._loc_id, self._last_active_schema, state + self._loc_id, climate_data.get("last_used"), state ) - self._hvac_mode = hvac_mode + self._attr_hvac_mode = hvac_mode self.async_write_ha_state() except PlugwiseException: _LOGGER.error("Error while communicating to device") - async def async_set_preset_mode(self, preset_mode): + async def async_set_preset_mode(self, preset_mode: str) -> None: """Set the preset mode.""" + if self._presets is None: + raise ValueError("No presets available") + try: await self._api.set_preset(self._loc_id, preset_mode) - self._preset_mode = preset_mode - self._setpoint = self._presets.get(self._preset_mode, "none")[0] + self._attr_preset_mode = preset_mode + self._attr_target_temperature = self._presets.get(preset_mode, "none")[0] self.async_write_ha_state() except PlugwiseException: _LOGGER.error("Error while communicating to device") @callback - def _async_process_data(self): + def _async_process_data(self) -> None: """Update the data for this climate device.""" climate_data = self._api.get_device_data(self._dev_id) heater_central_data = self._api.get_device_data(self._api.heater_id) - if "setpoint" in climate_data: - self._setpoint = climate_data["setpoint"] - if "temperature" in climate_data: - self._temperature = climate_data["temperature"] - if "schedule_temperature" in climate_data: - self._schedule_temp = climate_data["schedule_temperature"] - if "available_schedules" in climate_data: - self._schema_names = climate_data["available_schedules"] - if "selected_schedule" in climate_data: - self._selected_schema = climate_data["selected_schedule"] - self._schema_status = False - if self._selected_schema is not None: - self._schema_status = True - if "last_used" in climate_data: - self._last_active_schema = climate_data["last_used"] - if "presets" in climate_data: - self._presets = climate_data["presets"] - if self._presets: - self._presets_list = list(self._presets) - if "active_preset" in climate_data: - self._preset_mode = climate_data["active_preset"] - - if heater_central_data.get("heating_state") is not None: - self._heating_state = heater_central_data["heating_state"] - if heater_central_data.get("cooling_state") is not None: - self._cooling_state = heater_central_data["cooling_state"] - if heater_central_data.get("compressor_state") is not None: - self._compressor_state = heater_central_data["compressor_state"] + # Current & set temperatures + if setpoint := climate_data.get("setpoint"): + self._attr_target_temperature = setpoint + if temperature := climate_data.get("temperature"): + self._attr_current_temperature = temperature + + # Presets handling + self._attr_preset_mode = climate_data.get("active_preset") + if presets := climate_data.get("presets"): + self._presets = presets + self._attr_preset_modes = list(presets) + else: + self._presets = None + self._attr_preset_mode = None - self._hvac_mode = HVAC_MODE_HEAT - if self._compressor_state is not None: - self._hvac_mode = HVAC_MODE_HEAT_COOL + # Determine current hvac action + self._attr_hvac_action = CURRENT_HVAC_IDLE + if self._single_thermostat: + if heater_central_data.get("heating_state"): + self._attr_hvac_action = CURRENT_HVAC_HEAT + elif heater_central_data.get("cooling_state"): + self._attr_hvac_action = CURRENT_HVAC_COOL + elif ( + self.target_temperature is not None + and self.current_temperature is not None + and self.target_temperature > self.current_temperature + ): + self._attr_hvac_action = CURRENT_HVAC_HEAT - if self._schema_status: - self._hvac_mode = HVAC_MODE_AUTO + # Determine hvac modes and current hvac mode + self._attr_hvac_mode = HVAC_MODE_HEAT + self._attr_hvac_modes = HVAC_MODES_HEAT_ONLY + if heater_central_data.get("compressor_state") is not None: + self._attr_hvac_mode = HVAC_MODE_HEAT_COOL + self._attr_hvac_modes = HVAC_MODES_HEAT_COOL + if climate_data.get("selected_schedule") is not None: + self._attr_hvac_mode = HVAC_MODE_AUTO + + # Extra attributes + self._attr_extra_state_attributes = { + "available_schemas": climate_data.get("available_schedules"), + "selected_schema": climate_data.get("selected_schedule"), + } self.async_write_ha_state() From 854d56fc6d1cd32d956f19375674fb14d872886c Mon Sep 17 00:00:00 2001 From: Ferdinand <96470489+Smitplaza@users.noreply.github.com> Date: Sat, 5 Feb 2022 17:12:17 +0100 Subject: [PATCH 0319/1098] Fix the restart when the saj device is down (#65796) --- homeassistant/components/saj/sensor.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/saj/sensor.py b/homeassistant/components/saj/sensor.py index 670455d7354939..2fb3729d0a8a00 100644 --- a/homeassistant/components/saj/sensor.py +++ b/homeassistant/components/saj/sensor.py @@ -20,7 +20,6 @@ CONF_TYPE, CONF_USERNAME, ENERGY_KILO_WATT_HOUR, - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, MASS_KILOGRAMS, POWER_WATT, @@ -33,6 +32,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_call_later +from homeassistant.helpers.start import async_at_start from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType _LOGGER = logging.getLogger(__name__) @@ -131,17 +131,19 @@ async def async_saj(): return values + @callback def start_update_interval(event): """Start the update interval scheduling.""" nonlocal remove_interval_update remove_interval_update = async_track_time_interval_backoff(hass, async_saj) + @callback def stop_update_interval(event): """Properly cancel the scheduled update.""" remove_interval_update() # pylint: disable=not-callable - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, start_update_interval) hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, stop_update_interval) + async_at_start(hass, start_update_interval) @callback From ff59f1ee51c148381aefff7af3edff0d444d371a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 5 Feb 2022 10:36:04 -0600 Subject: [PATCH 0320/1098] Add INTEGRATION_DISCOVERY to DISCOVERY_SOURCES (#65811) --- homeassistant/config_entries.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index a5d0ca736fc40a..0af31b477303dd 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -117,6 +117,7 @@ def recoverable(self) -> bool: SOURCE_DHCP, SOURCE_DISCOVERY, SOURCE_IMPORT, + SOURCE_INTEGRATION_DISCOVERY, SOURCE_UNIGNORE, ) From 2bcd4f8f9315aeb0918958a2e01956062c8a2c44 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 5 Feb 2022 10:36:44 -0600 Subject: [PATCH 0321/1098] Add discovery support to WiZ Part 1 (#65752) --- .coveragerc | 1 + homeassistant/components/wiz/__init__.py | 27 +++- homeassistant/components/wiz/config_flow.py | 71 ++++++++- homeassistant/components/wiz/const.py | 7 + homeassistant/components/wiz/discovery.py | 61 ++++++++ homeassistant/components/wiz/light.py | 39 ++--- homeassistant/components/wiz/manifest.json | 7 +- homeassistant/components/wiz/strings.json | 4 + .../components/wiz/translations/en.json | 4 + homeassistant/components/wiz/utils.py | 13 ++ homeassistant/generated/dhcp.py | 8 + tests/components/wiz/test_config_flow.py | 145 +++++++++++++++++- 12 files changed, 346 insertions(+), 41 deletions(-) create mode 100644 homeassistant/components/wiz/discovery.py diff --git a/.coveragerc b/.coveragerc index 003d148a702abe..6b0de56b35d37f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1316,6 +1316,7 @@ omit = homeassistant/components/wirelesstag/* homeassistant/components/wiz/__init__.py homeassistant/components/wiz/const.py + homeassistant/components/wiz/discovery.py homeassistant/components/wiz/light.py homeassistant/components/wolflink/__init__.py homeassistant/components/wolflink/sensor.py diff --git a/homeassistant/components/wiz/__init__.py b/homeassistant/components/wiz/__init__.py index 6e7f841ebb2c50..c45925adec7d9f 100644 --- a/homeassistant/components/wiz/__init__.py +++ b/homeassistant/components/wiz/__init__.py @@ -1,17 +1,23 @@ """WiZ Platform integration.""" +import asyncio from datetime import timedelta import logging +from typing import Any from pywizlight import wizlight +from pywizlight.exceptions import WizLightNotKnownBulb from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.debounce import Debouncer +from homeassistant.helpers.event import async_track_time_interval +from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import DOMAIN, WIZ_EXCEPTIONS +from .const import DISCOVER_SCAN_TIMEOUT, DISCOVERY_INTERVAL, DOMAIN, WIZ_EXCEPTIONS +from .discovery import async_discover_devices, async_trigger_discovery from .models import WizData _LOGGER = logging.getLogger(__name__) @@ -21,6 +27,19 @@ REQUEST_REFRESH_DELAY = 0.35 +async def async_setup(hass: HomeAssistant, hass_config: ConfigType) -> bool: + """Set up the wiz integration.""" + + async def _async_discovery(*_: Any) -> None: + async_trigger_discovery( + hass, await async_discover_devices(hass, DISCOVER_SCAN_TIMEOUT) + ) + + asyncio.create_task(_async_discovery()) + async_track_time_interval(hass, _async_discovery, DISCOVERY_INTERVAL) + return True + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up the wiz integration from a config entry.""" ip_address = entry.data[CONF_HOST] @@ -34,6 +53,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # This is likely because way the library # processes responses and can be cleaned up # in the future. + except WizLightNotKnownBulb: + # This is only thrown on IndexError when the + # bulb responds with invalid data? It may + # not actually be possible anymore + _LOGGER.warning("The WiZ bulb type could not be determined for %s", ip_address) + return False except (ValueError, *WIZ_EXCEPTIONS) as err: raise ConfigEntryNotReady from err diff --git a/homeassistant/components/wiz/config_flow.py b/homeassistant/components/wiz/config_flow.py index efa9cc1443edbf..34b484f145ec83 100644 --- a/homeassistant/components/wiz/config_flow.py +++ b/homeassistant/components/wiz/config_flow.py @@ -5,15 +5,17 @@ from typing import Any from pywizlight import wizlight +from pywizlight.discovery import DiscoveredBulb from pywizlight.exceptions import WizLightConnectionError, WizLightTimeOutError import voluptuous as vol from homeassistant import config_entries +from homeassistant.components import dhcp from homeassistant.const import CONF_HOST from homeassistant.data_entry_flow import FlowResult -from .const import DEFAULT_NAME, DOMAIN -from .utils import _short_mac +from .const import DOMAIN, WIZ_EXCEPTIONS +from .utils import name_from_bulb_type_and_mac _LOGGER = logging.getLogger(__name__) @@ -23,6 +25,66 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 + def __init__(self) -> None: + """Initialize the config flow.""" + self._discovered_device: DiscoveredBulb | None = None + self._name: str | None = None + + async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + """Handle discovery via dhcp.""" + self._discovered_device = DiscoveredBulb( + discovery_info.ip, discovery_info.macaddress + ) + return await self._async_handle_discovery() + + async def async_step_integration_discovery( + self, discovery_info: dict[str, str] + ) -> FlowResult: + """Handle integration discovery.""" + self._discovered_device = DiscoveredBulb( + discovery_info["ip_address"], discovery_info["mac_address"] + ) + return await self._async_handle_discovery() + + async def _async_handle_discovery(self) -> FlowResult: + """Handle any discovery.""" + device = self._discovered_device + assert device is not None + _LOGGER.debug("Discovered device: %s", device) + ip_address = device.ip_address + mac = device.mac_address + await self.async_set_unique_id(mac) + self._abort_if_unique_id_configured(updates={CONF_HOST: ip_address}) + bulb = wizlight(ip_address) + try: + bulbtype = await bulb.get_bulbtype() + except WIZ_EXCEPTIONS: + return self.async_abort(reason="cannot_connect") + self._name = name_from_bulb_type_and_mac(bulbtype, mac) + return await self.async_step_discovery_confirm() + + async def async_step_discovery_confirm( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Confirm discovery.""" + assert self._discovered_device is not None + assert self._name is not None + ip_address = self._discovered_device.ip_address + if user_input is not None: + return self.async_create_entry( + title=self._name, + data={CONF_HOST: ip_address}, + ) + + self._set_confirm_only() + placeholders = {"name": self._name, "host": ip_address} + self.context["title_placeholders"] = placeholders + return self.async_show_form( + step_id="discovery_confirm", + description_placeholders=placeholders, + data_schema=vol.Schema({}), + ) + async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: @@ -43,12 +105,11 @@ async def async_step_user( _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" else: - await self.async_set_unique_id(mac) + await self.async_set_unique_id(mac, raise_on_progress=False) self._abort_if_unique_id_configured( updates={CONF_HOST: user_input[CONF_HOST]} ) - bulb_type = bulbtype.bulb_type.value if bulbtype else "Unknown" - name = f"{DEFAULT_NAME} {bulb_type} {_short_mac(mac)}" + name = name_from_bulb_type_and_mac(bulbtype, mac) return self.async_create_entry( title=name, data=user_input, diff --git a/homeassistant/components/wiz/const.py b/homeassistant/components/wiz/const.py index 96a96e662f1d82..a88c445614ae7e 100644 --- a/homeassistant/components/wiz/const.py +++ b/homeassistant/components/wiz/const.py @@ -1,9 +1,16 @@ """Constants for the WiZ Platform integration.""" +from datetime import timedelta + from pywizlight.exceptions import WizLightConnectionError, WizLightTimeOutError DOMAIN = "wiz" DEFAULT_NAME = "WiZ" +DISCOVER_SCAN_TIMEOUT = 10 +DISCOVERY_INTERVAL = timedelta(minutes=15) + +SOCKET_DEVICE_STR = "_SOCKET_" + WIZ_EXCEPTIONS = ( OSError, WizLightTimeOutError, diff --git a/homeassistant/components/wiz/discovery.py b/homeassistant/components/wiz/discovery.py new file mode 100644 index 00000000000000..c7ee612c6a9364 --- /dev/null +++ b/homeassistant/components/wiz/discovery.py @@ -0,0 +1,61 @@ +"""The wiz integration discovery.""" +from __future__ import annotations + +import asyncio +from dataclasses import asdict +import logging + +from pywizlight.discovery import DiscoveredBulb, find_wizlights + +from homeassistant import config_entries +from homeassistant.components import network +from homeassistant.core import HomeAssistant, callback + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_discover_devices( + hass: HomeAssistant, timeout: int, address: str | None = None +) -> list[DiscoveredBulb]: + """Discover wiz devices.""" + if address: + targets = [address] + else: + targets = [ + str(address) + for address in await network.async_get_ipv4_broadcast_addresses(hass) + ] + + combined_discoveries: dict[str, DiscoveredBulb] = {} + for idx, discovered in enumerate( + await asyncio.gather( + *[find_wizlights(timeout, address) for address in targets], + return_exceptions=True, + ) + ): + if isinstance(discovered, Exception): + _LOGGER.debug("Scanning %s failed with error: %s", targets[idx], discovered) + continue + for device in discovered: + assert isinstance(device, DiscoveredBulb) + combined_discoveries[device.ip_address] = device + + return list(combined_discoveries.values()) + + +@callback +def async_trigger_discovery( + hass: HomeAssistant, + discovered_devices: list[DiscoveredBulb], +) -> None: + """Trigger config flows for discovered devices.""" + for device in discovered_devices: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, + data=asdict(device), + ) + ) diff --git a/homeassistant/components/wiz/light.py b/homeassistant/components/wiz/light.py index a8fcf1e1dae01b..09e17976eb7fa8 100644 --- a/homeassistant/components/wiz/light.py +++ b/homeassistant/components/wiz/light.py @@ -1,13 +1,11 @@ """WiZ integration.""" from __future__ import annotations -import contextlib import logging from typing import Any from pywizlight import PilotBuilder from pywizlight.bulblibrary import BulbClass, BulbType -from pywizlight.exceptions import WizLightNotKnownBulb from pywizlight.rgbcw import convertHSfromRGBCW from pywizlight.scenes import get_id_from_scene_name @@ -43,29 +41,20 @@ def get_supported_color_modes(bulb_type: BulbType) -> set[str]: """Flag supported features.""" - if not bulb_type: - # fallback - return DEFAULT_COLOR_MODES color_modes = set() - try: - features = bulb_type.features - if features.color: - color_modes.add(COLOR_MODE_HS) - if features.color_tmp: - color_modes.add(COLOR_MODE_COLOR_TEMP) - if not color_modes and features.brightness: - color_modes.add(COLOR_MODE_BRIGHTNESS) - return color_modes - except WizLightNotKnownBulb: - _LOGGER.warning("Bulb is not present in the library. Fallback to full feature") - return DEFAULT_COLOR_MODES + features = bulb_type.features + if features.color: + color_modes.add(COLOR_MODE_HS) + if features.color_tmp: + color_modes.add(COLOR_MODE_COLOR_TEMP) + if not color_modes and features.brightness: + color_modes.add(COLOR_MODE_BRIGHTNESS) + return color_modes def supports_effects(bulb_type: BulbType) -> bool: """Check if a bulb supports effects.""" - with contextlib.suppress(WizLightNotKnownBulb): - return bool(bulb_type.features.effect) - return True # default is true + return bool(bulb_type.features.effect) def get_min_max_mireds(bulb_type: BulbType) -> tuple[int, int]: @@ -76,13 +65,9 @@ def get_min_max_mireds(bulb_type: BulbType) -> tuple[int, int]: if bulb_type.bulb_type == BulbClass.DW: return 0, 0 # If bulbtype is TW or RGB then return the kelvin value - try: - return color_utils.color_temperature_kelvin_to_mired( - bulb_type.kelvin_range.max - ), color_utils.color_temperature_kelvin_to_mired(bulb_type.kelvin_range.min) - except WizLightNotKnownBulb: - _LOGGER.debug("Kelvin is not present in the library. Fallback to 6500") - return DEFAULT_MIN_MIREDS, DEFAULT_MAX_MIREDS + return color_utils.color_temperature_kelvin_to_mired( + bulb_type.kelvin_range.max + ), color_utils.color_temperature_kelvin_to_mired(bulb_type.kelvin_range.min) async def async_setup_entry( diff --git a/homeassistant/components/wiz/manifest.json b/homeassistant/components/wiz/manifest.json index 8b366e7f506ee7..0842fd967b0206 100644 --- a/homeassistant/components/wiz/manifest.json +++ b/homeassistant/components/wiz/manifest.json @@ -2,8 +2,13 @@ "domain": "wiz", "name": "WiZ", "config_flow": true, + "dhcp": [ + {"macaddress":"A8BB50*"}, + {"hostname":"wiz_[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]"} + ], + "dependencies": ["network"], "documentation": "https://www.home-assistant.io/integrations/wiz", "requirements": ["pywizlight==0.4.16"], "iot_class": "local_polling", "codeowners": ["@sbidy"] -} \ No newline at end of file +} diff --git a/homeassistant/components/wiz/strings.json b/homeassistant/components/wiz/strings.json index 3088a7f098d015..99f491e7360888 100644 --- a/homeassistant/components/wiz/strings.json +++ b/homeassistant/components/wiz/strings.json @@ -1,11 +1,15 @@ { "config": { + "flow_title": "{name} ({host})", "step": { "user": { "data": { "host": "[%key:common::config_flow::data::host%]" }, "description": "Enter the IP address of the device." + }, + "discovery_confirm": { + "description": "Do you want to setup {name} ({host})?" } }, "error": { diff --git a/homeassistant/components/wiz/translations/en.json b/homeassistant/components/wiz/translations/en.json index 24f34bd0f5be37..b0d65e9f95780d 100644 --- a/homeassistant/components/wiz/translations/en.json +++ b/homeassistant/components/wiz/translations/en.json @@ -10,7 +10,11 @@ "no_wiz_light": "The bulb can not be connected via WiZ Platform integration.", "unknown": "Unexpected error" }, + "flow_title": "{name} ({host})", "step": { + "discovery_confirm": { + "description": "Do you want to setup {name} ({host})?" + }, "user": { "data": { "host": "Host" diff --git a/homeassistant/components/wiz/utils.py b/homeassistant/components/wiz/utils.py index edce7d47fea48a..ceaab797d5e6a0 100644 --- a/homeassistant/components/wiz/utils.py +++ b/homeassistant/components/wiz/utils.py @@ -1,7 +1,20 @@ """WiZ utils.""" from __future__ import annotations +from pywizlight import BulbType + +from .const import DEFAULT_NAME, SOCKET_DEVICE_STR + def _short_mac(mac: str) -> str: """Get the short mac address from the full mac.""" return mac.replace(":", "").upper()[-6:] + + +def name_from_bulb_type_and_mac(bulb_type: BulbType, mac: str) -> str: + """Generate a name from bulb_type and mac.""" + if SOCKET_DEVICE_STR in bulb_type.name: + description = "Socket" + else: + description = bulb_type.bulb_type.value + return f"{DEFAULT_NAME} {description} {_short_mac(mac)}" diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 701117cb562ff6..2e9672f99eb4b9 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -608,6 +608,14 @@ "domain": "vicare", "macaddress": "B87424*" }, + { + "domain": "wiz", + "macaddress": "A8BB50*" + }, + { + "domain": "wiz", + "hostname": "wiz_[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]" + }, { "domain": "yeelight", "hostname": "yeelink-*" diff --git a/tests/components/wiz/test_config_flow.py b/tests/components/wiz/test_config_flow.py index 20afd994046701..18c28f50c0ec6c 100644 --- a/tests/components/wiz/test_config_flow.py +++ b/tests/components/wiz/test_config_flow.py @@ -1,19 +1,23 @@ """Test the WiZ Platform config flow.""" from contextlib import contextmanager +from copy import deepcopy from unittest.mock import patch import pytest from homeassistant import config_entries +from homeassistant.components import dhcp from homeassistant.components.wiz.config_flow import ( WizLightConnectionError, WizLightTimeOutError, ) from homeassistant.components.wiz.const import DOMAIN from homeassistant.const import CONF_HOST +from homeassistant.data_entry_flow import RESULT_TYPE_ABORT, RESULT_TYPE_FORM from tests.common import MockConfigEntry +FAKE_IP = "1.1.1.1" FAKE_MAC = "ABCABCABCABC" FAKE_BULB_CONFIG = { "method": "getSystemConfig", @@ -31,21 +35,36 @@ "ping": 0, }, } +FAKE_SOCKET_CONFIG = deepcopy(FAKE_BULB_CONFIG) +FAKE_SOCKET_CONFIG["result"]["moduleName"] = "ESP10_SOCKET_06" FAKE_EXTENDED_WHITE_RANGE = [2200, 2700, 6500, 6500] TEST_SYSTEM_INFO = {"id": FAKE_MAC, "name": "Test Bulb"} TEST_CONNECTION = {CONF_HOST: "1.1.1.1"} TEST_NO_IP = {CONF_HOST: "this is no IP input"} -def _patch_wizlight(): +DHCP_DISCOVERY = dhcp.DhcpServiceInfo( + hostname="wiz_abcabc", + ip=FAKE_IP, + macaddress=FAKE_MAC, +) + + +INTEGRATION_DISCOVERY = { + "ip_address": FAKE_IP, + "mac_address": FAKE_MAC, +} + + +def _patch_wizlight(device=None, extended_white_range=None): @contextmanager def _patcher(): with patch( "homeassistant.components.wiz.wizlight.getBulbConfig", - return_value=FAKE_BULB_CONFIG, + return_value=device or FAKE_BULB_CONFIG, ), patch( "homeassistant.components.wiz.wizlight.getExtendedWhiteRange", - return_value=FAKE_EXTENDED_WHITE_RANGE, + return_value=extended_white_range or FAKE_EXTENDED_WHITE_RANGE, ), patch( "homeassistant.components.wiz.wizlight.getMac", return_value=FAKE_MAC, @@ -114,11 +133,8 @@ async def test_form_updates_unique_id(hass): entry = MockConfigEntry( domain=DOMAIN, unique_id=TEST_SYSTEM_INFO["id"], - data={ - CONF_HOST: "dummy", - }, + data={CONF_HOST: "dummy"}, ) - entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( @@ -136,3 +152,118 @@ async def test_form_updates_unique_id(hass): assert result2["type"] == "abort" assert result2["reason"] == "already_configured" + + +@pytest.mark.parametrize( + "source, data", + [ + (config_entries.SOURCE_DHCP, DHCP_DISCOVERY), + (config_entries.SOURCE_INTEGRATION_DISCOVERY, INTEGRATION_DISCOVERY), + ], +) +async def test_discovered_by_dhcp_connection_fails(hass, source, data): + """Test we abort on connection failure.""" + with patch( + "homeassistant.components.wiz.wizlight.getBulbConfig", + side_effect=WizLightTimeOutError, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": source}, data=data + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "cannot_connect" + + +@pytest.mark.parametrize( + "source, data, device, extended_white_range, name", + [ + ( + config_entries.SOURCE_DHCP, + DHCP_DISCOVERY, + FAKE_BULB_CONFIG, + FAKE_EXTENDED_WHITE_RANGE, + "WiZ Dimmable White ABCABC", + ), + ( + config_entries.SOURCE_INTEGRATION_DISCOVERY, + INTEGRATION_DISCOVERY, + FAKE_BULB_CONFIG, + FAKE_EXTENDED_WHITE_RANGE, + "WiZ Dimmable White ABCABC", + ), + ( + config_entries.SOURCE_DHCP, + DHCP_DISCOVERY, + FAKE_SOCKET_CONFIG, + None, + "WiZ Socket ABCABC", + ), + ( + config_entries.SOURCE_INTEGRATION_DISCOVERY, + INTEGRATION_DISCOVERY, + FAKE_SOCKET_CONFIG, + None, + "WiZ Socket ABCABC", + ), + ], +) +async def test_discovered_by_dhcp_or_integration_discovery( + hass, source, data, device, extended_white_range, name +): + """Test we can configure when discovered from dhcp or discovery.""" + with _patch_wizlight(device=device, extended_white_range=extended_white_range): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": source}, data=data + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "discovery_confirm" + + with patch( + "homeassistant.components.wiz.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {}, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == name + assert result2["data"] == { + CONF_HOST: "1.1.1.1", + } + assert len(mock_setup_entry.mock_calls) == 1 + + +@pytest.mark.parametrize( + "source, data", + [ + (config_entries.SOURCE_DHCP, DHCP_DISCOVERY), + (config_entries.SOURCE_INTEGRATION_DISCOVERY, INTEGRATION_DISCOVERY), + ], +) +async def test_discovered_by_dhcp_or_integration_discovery_updates_host( + hass, source, data +): + """Test dhcp or discovery updates existing host.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id=TEST_SYSTEM_INFO["id"], + data={CONF_HOST: "dummy"}, + ) + entry.add_to_hass(hass) + + with _patch_wizlight(): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": source}, data=data + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + assert entry.data[CONF_HOST] == FAKE_IP From adbc0e595547e37b617a4739453205ad898eee6b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 5 Feb 2022 10:59:22 -0600 Subject: [PATCH 0322/1098] Prevent multiple dhcp flows from being started for the same device/domain (#65753) --- homeassistant/components/dhcp/__init__.py | 6 +++++ tests/components/dhcp/test_init.py | 27 +++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/homeassistant/components/dhcp/__init__.py b/homeassistant/components/dhcp/__init__.py index 4310f8f2caf5be..dd247c4cab9e23 100644 --- a/homeassistant/components/dhcp/__init__.py +++ b/homeassistant/components/dhcp/__init__.py @@ -179,6 +179,7 @@ def async_process_client(self, ip_address, hostname, mac_address): lowercase_hostname, ) + matched_domains = set() for entry in self._integration_matchers: if MAC_ADDRESS in entry and not fnmatch.fnmatch( uppercase_mac, entry[MAC_ADDRESS] @@ -191,6 +192,11 @@ def async_process_client(self, ip_address, hostname, mac_address): continue _LOGGER.debug("Matched %s against %s", data, entry) + if entry["domain"] in matched_domains: + # Only match once per domain + continue + + matched_domains.add(entry["domain"]) discovery_flow.async_create_flow( self.hass, entry["domain"], diff --git a/tests/components/dhcp/test_init.py b/tests/components/dhcp/test_init.py index fb3387aeab664c..0956230d7871c5 100644 --- a/tests/components/dhcp/test_init.py +++ b/tests/components/dhcp/test_init.py @@ -255,6 +255,33 @@ async def test_dhcp_match_macaddress(hass): ) +async def test_dhcp_multiple_match_only_one_flow(hass): + """Test matching the domain multiple times only generates one flow.""" + integration_matchers = [ + {"domain": "mock-domain", "macaddress": "B8B7F1*"}, + {"domain": "mock-domain", "hostname": "connect"}, + ] + + packet = Ether(RAW_DHCP_REQUEST) + + async_handle_dhcp_packet = await _async_get_handle_dhcp_packet( + hass, integration_matchers + ) + with patch.object(hass.config_entries.flow, "async_init") as mock_init: + await async_handle_dhcp_packet(packet) + + assert len(mock_init.mock_calls) == 1 + assert mock_init.mock_calls[0][1][0] == "mock-domain" + assert mock_init.mock_calls[0][2]["context"] == { + "source": config_entries.SOURCE_DHCP + } + assert mock_init.mock_calls[0][2]["data"] == dhcp.DhcpServiceInfo( + ip="192.168.210.56", + hostname="connect", + macaddress="b8b7f16db533", + ) + + async def test_dhcp_match_macaddress_without_hostname(hass): """Test matching based on macaddress only.""" integration_matchers = [{"domain": "mock-domain", "macaddress": "606BBD*"}] From 676edb610f6cbb62287ea204c875db490a5125e9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 5 Feb 2022 10:59:32 -0600 Subject: [PATCH 0323/1098] Add coverage for color_rgbww_to_rgb, fix divzero case (#65721) --- homeassistant/util/color.py | 5 ++++- tests/util/test_color.py | 43 +++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py index f055a5f32eb2ed..21c877f9377f47 100644 --- a/homeassistant/util/color.py +++ b/homeassistant/util/color.py @@ -472,7 +472,10 @@ def color_rgbww_to_rgb( except ZeroDivisionError: ct_ratio = 0.5 color_temp_mired = min_mireds + ct_ratio * mired_range - color_temp_kelvin = color_temperature_mired_to_kelvin(color_temp_mired) + if color_temp_mired: + color_temp_kelvin = color_temperature_mired_to_kelvin(color_temp_mired) + else: + color_temp_kelvin = 0 w_r, w_g, w_b = color_temperature_to_rgb(color_temp_kelvin) white_level = max(cw, ww) / 255 diff --git a/tests/util/test_color.py b/tests/util/test_color.py index e9ab935f6ab304..b77540acc2be63 100644 --- a/tests/util/test_color.py +++ b/tests/util/test_color.py @@ -405,6 +405,49 @@ def test_color_rgb_to_rgbww(): assert color_util.color_rgb_to_rgbww(255, 255, 255, 1, 5) == (103, 69, 0, 255, 255) +def test_color_rgbww_to_rgb(): + """Test color_rgbww_to_rgb conversions.""" + assert color_util.color_rgbww_to_rgb(0, 54, 98, 255, 255, 154, 370) == ( + 255, + 255, + 255, + ) + assert color_util.color_rgbww_to_rgb(255, 255, 255, 0, 0, 154, 370) == ( + 255, + 255, + 255, + ) + assert color_util.color_rgbww_to_rgb(0, 118, 241, 255, 255, 154, 370) == ( + 163, + 204, + 255, + ) + assert color_util.color_rgbww_to_rgb(0, 27, 49, 128, 128, 154, 370) == ( + 128, + 128, + 128, + ) + assert color_util.color_rgbww_to_rgb(0, 14, 25, 64, 64, 154, 370) == (64, 64, 64) + assert color_util.color_rgbww_to_rgb(9, 64, 0, 38, 38, 154, 370) == (32, 64, 16) + assert color_util.color_rgbww_to_rgb(0, 0, 0, 0, 0, 154, 370) == (0, 0, 0) + assert color_util.color_rgbww_to_rgb(103, 69, 0, 255, 255, 153, 370) == ( + 255, + 193, + 112, + ) + assert color_util.color_rgbww_to_rgb(255, 255, 255, 0, 0, 0, 0) == (255, 255, 255) + assert color_util.color_rgbww_to_rgb(255, 255, 255, 255, 255, 0, 0) == ( + 255, + 161, + 128, + ) + assert color_util.color_rgbww_to_rgb(255, 255, 255, 255, 255, 0, 370) == ( + 255, + 245, + 237, + ) + + def test_color_temperature_to_rgbww(): """Test color temp to warm, cold conversion. From f171ec4676f3b57320e5fe420185ed9515166472 Mon Sep 17 00:00:00 2001 From: Hans Oischinger Date: Sat, 5 Feb 2022 18:17:09 +0100 Subject: [PATCH 0324/1098] Add missing vicare state class (#65795) --- homeassistant/components/vicare/sensor.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/homeassistant/components/vicare/sensor.py b/homeassistant/components/vicare/sensor.py index 23096cfeacdef1..a0d7208f1b2d49 100644 --- a/homeassistant/components/vicare/sensor.py +++ b/homeassistant/components/vicare/sensor.py @@ -58,6 +58,7 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM native_unit_of_measurement=TEMP_CELSIUS, value_getter=lambda api: api.getOutsideTemperature(), device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, ), ViCareSensorEntityDescription( key="return_temperature", @@ -65,6 +66,7 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM native_unit_of_measurement=TEMP_CELSIUS, value_getter=lambda api: api.getReturnTemperature(), device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, ), ViCareSensorEntityDescription( key="boiler_temperature", @@ -72,6 +74,7 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM native_unit_of_measurement=TEMP_CELSIUS, value_getter=lambda api: api.getBoilerTemperature(), device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, ), ViCareSensorEntityDescription( key="hotwater_gas_consumption_today", @@ -175,6 +178,7 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM native_unit_of_measurement=TEMP_CELSIUS, value_getter=lambda api: api.getSolarStorageTemperature(), device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, ), ViCareSensorEntityDescription( key="collector temperature", @@ -182,6 +186,7 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM native_unit_of_measurement=TEMP_CELSIUS, value_getter=lambda api: api.getSolarCollectorTemperature(), device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, ), ViCareSensorEntityDescription( key="solar power production", @@ -199,6 +204,8 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM name="Supply Temperature", native_unit_of_measurement=TEMP_CELSIUS, value_getter=lambda api: api.getSupplyTemperature(), + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, ), ) @@ -208,6 +215,7 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM name="Burner Starts", icon="mdi:counter", value_getter=lambda api: api.getStarts(), + state_class=SensorStateClass.TOTAL_INCREASING, ), ViCareSensorEntityDescription( key="burner_hours", @@ -215,6 +223,7 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM icon="mdi:counter", native_unit_of_measurement=TIME_HOURS, value_getter=lambda api: api.getHours(), + state_class=SensorStateClass.TOTAL_INCREASING, ), ViCareSensorEntityDescription( key="burner_modulation", @@ -222,6 +231,7 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM icon="mdi:percent", native_unit_of_measurement=PERCENTAGE, value_getter=lambda api: api.getModulation(), + state_class=SensorStateClass.MEASUREMENT, ), ) @@ -231,6 +241,7 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM name="Compressor Starts", icon="mdi:counter", value_getter=lambda api: api.getStarts(), + state_class=SensorStateClass.TOTAL_INCREASING, ), ViCareSensorEntityDescription( key="compressor_hours", @@ -238,6 +249,7 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM icon="mdi:counter", native_unit_of_measurement=TIME_HOURS, value_getter=lambda api: api.getHours(), + state_class=SensorStateClass.TOTAL_INCREASING, ), ViCareSensorEntityDescription( key="compressor_hours_loadclass1", @@ -245,6 +257,7 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM icon="mdi:counter", native_unit_of_measurement=TIME_HOURS, value_getter=lambda api: api.getHoursLoadClass1(), + state_class=SensorStateClass.TOTAL_INCREASING, ), ViCareSensorEntityDescription( key="compressor_hours_loadclass2", @@ -252,6 +265,7 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM icon="mdi:counter", native_unit_of_measurement=TIME_HOURS, value_getter=lambda api: api.getHoursLoadClass2(), + state_class=SensorStateClass.TOTAL_INCREASING, ), ViCareSensorEntityDescription( key="compressor_hours_loadclass3", @@ -259,6 +273,7 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM icon="mdi:counter", native_unit_of_measurement=TIME_HOURS, value_getter=lambda api: api.getHoursLoadClass3(), + state_class=SensorStateClass.TOTAL_INCREASING, ), ViCareSensorEntityDescription( key="compressor_hours_loadclass4", @@ -266,6 +281,7 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM icon="mdi:counter", native_unit_of_measurement=TIME_HOURS, value_getter=lambda api: api.getHoursLoadClass4(), + state_class=SensorStateClass.TOTAL_INCREASING, ), ViCareSensorEntityDescription( key="compressor_hours_loadclass5", @@ -273,6 +289,7 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM icon="mdi:counter", native_unit_of_measurement=TIME_HOURS, value_getter=lambda api: api.getHoursLoadClass5(), + state_class=SensorStateClass.TOTAL_INCREASING, ), ) From 5621e209632577f29c91579e460511840d742b63 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 5 Feb 2022 11:56:17 -0600 Subject: [PATCH 0325/1098] WiZ Cleanups part 3 (#65819) * WiZ Cleanups part 3 - Sockets are now using the switch platform * tweaks * remove rgb colorcheck * tweaks * tweaks * cover * cover --- .coveragerc | 2 + homeassistant/components/wiz/__init__.py | 2 +- homeassistant/components/wiz/entity.py | 42 ++++ homeassistant/components/wiz/light.py | 214 +++++------------- homeassistant/components/wiz/strings.json | 1 - homeassistant/components/wiz/switch.py | 35 +++ .../components/wiz/translations/en.json | 3 +- tests/components/wiz/test_config_flow.py | 1 + 8 files changed, 144 insertions(+), 156 deletions(-) create mode 100644 homeassistant/components/wiz/entity.py create mode 100644 homeassistant/components/wiz/switch.py diff --git a/.coveragerc b/.coveragerc index 6b0de56b35d37f..8babaab4e25a4a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1317,7 +1317,9 @@ omit = homeassistant/components/wiz/__init__.py homeassistant/components/wiz/const.py homeassistant/components/wiz/discovery.py + homeassistant/components/wiz/entity.py homeassistant/components/wiz/light.py + homeassistant/components/wiz/switch.py homeassistant/components/wolflink/__init__.py homeassistant/components/wolflink/sensor.py homeassistant/components/wolflink/const.py diff --git a/homeassistant/components/wiz/__init__.py b/homeassistant/components/wiz/__init__.py index c45925adec7d9f..ddf324278cfd77 100644 --- a/homeassistant/components/wiz/__init__.py +++ b/homeassistant/components/wiz/__init__.py @@ -22,7 +22,7 @@ _LOGGER = logging.getLogger(__name__) -PLATFORMS = [Platform.LIGHT] +PLATFORMS = [Platform.LIGHT, Platform.SWITCH] REQUEST_REFRESH_DELAY = 0.35 diff --git a/homeassistant/components/wiz/entity.py b/homeassistant/components/wiz/entity.py new file mode 100644 index 00000000000000..0c4829383d30bc --- /dev/null +++ b/homeassistant/components/wiz/entity.py @@ -0,0 +1,42 @@ +"""WiZ integration entities.""" +from __future__ import annotations + +from typing import Any + +from pywizlight.bulblibrary import BulbType + +from homeassistant.core import callback +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.helpers.entity import DeviceInfo, ToggleEntity +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .models import WizData + + +class WizToggleEntity(CoordinatorEntity, ToggleEntity): + """Representation of WiZ toggle entity.""" + + def __init__(self, wiz_data: WizData, name: str) -> None: + """Initialize an WiZ device.""" + super().__init__(wiz_data.coordinator) + self._device = wiz_data.bulb + bulb_type: BulbType = self._device.bulbtype + self._attr_unique_id = self._device.mac + self._attr_name = name + self._attr_device_info = DeviceInfo( + connections={(CONNECTION_NETWORK_MAC, self._device.mac)}, + name=name, + manufacturer="WiZ", + model=bulb_type.name, + ) + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self._attr_is_on = self._device.status + super()._handle_coordinator_update() + + async def async_turn_off(self, **kwargs: Any) -> None: + """Instruct the device to turn off.""" + await self._device.turn_off() + await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/wiz/light.py b/homeassistant/components/wiz/light.py index 09e17976eb7fa8..ee586c8939d91b 100644 --- a/homeassistant/components/wiz/light.py +++ b/homeassistant/components/wiz/light.py @@ -1,4 +1,4 @@ -"""WiZ integration.""" +"""WiZ integration light platform.""" from __future__ import annotations import logging @@ -14,7 +14,6 @@ ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_HS_COLOR, - ATTR_RGB_COLOR, COLOR_MODE_BRIGHTNESS, COLOR_MODE_COLOR_TEMP, COLOR_MODE_HS, @@ -22,14 +21,15 @@ LightEntity, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC -from homeassistant.helpers.entity import DeviceInfo +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity -import homeassistant.util.color as color_utils +from homeassistant.util.color import ( + color_temperature_kelvin_to_mired, + color_temperature_mired_to_kelvin, +) -from .const import DOMAIN +from .const import DOMAIN, SOCKET_DEVICE_STR +from .entity import WizToggleEntity from .models import WizData _LOGGER = logging.getLogger(__name__) @@ -52,22 +52,15 @@ def get_supported_color_modes(bulb_type: BulbType) -> set[str]: return color_modes -def supports_effects(bulb_type: BulbType) -> bool: - """Check if a bulb supports effects.""" - return bool(bulb_type.features.effect) - - def get_min_max_mireds(bulb_type: BulbType) -> tuple[int, int]: """Return the coldest and warmest color_temp that this light supports.""" - if bulb_type is None: - return DEFAULT_MIN_MIREDS, DEFAULT_MAX_MIREDS # DW bulbs have no kelvin if bulb_type.bulb_type == BulbClass.DW: return 0, 0 # If bulbtype is TW or RGB then return the kelvin value - return color_utils.color_temperature_kelvin_to_mired( + return color_temperature_kelvin_to_mired( bulb_type.kelvin_range.max - ), color_utils.color_temperature_kelvin_to_mired(bulb_type.kelvin_range.min) + ), color_temperature_kelvin_to_mired(bulb_type.kelvin_range.min) async def async_setup_entry( @@ -77,162 +70,79 @@ async def async_setup_entry( ) -> None: """Set up the WiZ Platform from config_flow.""" wiz_data: WizData = hass.data[DOMAIN][entry.entry_id] - async_add_entities([WizBulbEntity(wiz_data, entry.title)]) + if SOCKET_DEVICE_STR not in wiz_data.bulb.bulbtype.name: + async_add_entities([WizBulbEntity(wiz_data, entry.title)]) -class WizBulbEntity(CoordinatorEntity, LightEntity): +class WizBulbEntity(WizToggleEntity, LightEntity): """Representation of WiZ Light bulb.""" def __init__(self, wiz_data: WizData, name: str) -> None: """Initialize an WiZLight.""" - super().__init__(wiz_data.coordinator) - self._light = wiz_data.bulb - bulb_type: BulbType = self._light.bulbtype - self._attr_unique_id = self._light.mac - self._attr_name = name + super().__init__(wiz_data, name) + bulb_type: BulbType = self._device.bulbtype self._attr_effect_list = wiz_data.scenes self._attr_min_mireds, self._attr_max_mireds = get_min_max_mireds(bulb_type) self._attr_supported_color_modes = get_supported_color_modes(bulb_type) - if supports_effects(bulb_type): + if bulb_type.features.effect: self._attr_supported_features = SUPPORT_EFFECT - self._attr_device_info = DeviceInfo( - connections={(CONNECTION_NETWORK_MAC, self._light.mac)}, - name=name, - manufacturer="WiZ", - model=bulb_type.name, - ) - @property - def is_on(self) -> bool | None: - """Return true if light is on.""" - is_on: bool | None = self._light.status - return is_on - - @property - def brightness(self) -> int | None: - """Return the brightness of the light.""" - if (brightness := self._light.state.get_brightness()) is None: - return None - if 0 <= int(brightness) <= 255: - return int(brightness) - _LOGGER.error("Received invalid brightness : %s. Expected: 0-255", brightness) - return None - - @property - def color_mode(self) -> str: - """Return the current color mode.""" + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + state = self._device.state + if (brightness := state.get_brightness()) is not None: + self._attr_brightness = max(0, min(255, brightness)) color_modes = self.supported_color_modes assert color_modes is not None - if ( - COLOR_MODE_COLOR_TEMP in color_modes - and self._light.state.get_colortemp() is not None - ): - return COLOR_MODE_COLOR_TEMP - if ( + if COLOR_MODE_COLOR_TEMP in color_modes and state.get_colortemp() is not None: + self._attr_color_mode = COLOR_MODE_COLOR_TEMP + if color_temp := state.get_colortemp(): + self._attr_color_temp = color_temperature_kelvin_to_mired(color_temp) + elif ( COLOR_MODE_HS in color_modes - and (rgb := self._light.state.get_rgb()) is not None + and (rgb := state.get_rgb()) is not None and rgb[0] is not None ): - return COLOR_MODE_HS - return COLOR_MODE_BRIGHTNESS - - @property - def hs_color(self) -> tuple[float, float] | None: - """Return the hs color value.""" - colortemp = self._light.state.get_colortemp() - if colortemp is not None and colortemp != 0: - return None - if (rgb := self._light.state.get_rgb()) is None: - return None - if rgb[0] is None: - # this is the case if the temperature was changed - # do nothing until the RGB color was changed - return None - if (warmwhite := self._light.state.get_warm_white()) is None: - return None - hue_sat = convertHSfromRGBCW(rgb, warmwhite) - hue: float = hue_sat[0] - sat: float = hue_sat[1] - return hue, sat - - @property - def color_temp(self) -> int | None: - """Return the CT color value in mireds.""" - colortemp = self._light.state.get_colortemp() - if colortemp is None or colortemp == 0: - return None - _LOGGER.debug( - "[wizlight %s] kelvin from the bulb: %s", self._light.ip, colortemp - ) - return color_utils.color_temperature_kelvin_to_mired(colortemp) - - @property - def effect(self) -> str | None: - """Return the current effect.""" - effect: str | None = self._light.state.get_scene() - return effect - - async def async_turn_on(self, **kwargs: Any) -> None: - """Instruct the light to turn on.""" - brightness = None - - if ATTR_BRIGHTNESS in kwargs: - brightness = kwargs.get(ATTR_BRIGHTNESS) + if (warm_white := state.get_warm_white()) is not None: + self._attr_hs_color = convertHSfromRGBCW(rgb, warm_white) + self._attr_color_mode = COLOR_MODE_HS + else: + self._attr_color_mode = COLOR_MODE_BRIGHTNESS + self._attr_effect = state.get_scene() + super()._handle_coordinator_update() - if ATTR_RGB_COLOR in kwargs: - pilot = PilotBuilder(rgb=kwargs.get(ATTR_RGB_COLOR), brightness=brightness) + @callback + def _async_pilot_builder(self, **kwargs: Any) -> PilotBuilder: + """Create the PilotBuilder for turn on.""" + brightness = kwargs.get(ATTR_BRIGHTNESS) if ATTR_HS_COLOR in kwargs: - pilot = PilotBuilder( + return PilotBuilder( hucolor=(kwargs[ATTR_HS_COLOR][0], kwargs[ATTR_HS_COLOR][1]), brightness=brightness, ) - else: - colortemp = None - if ATTR_COLOR_TEMP in kwargs: - kelvin = color_utils.color_temperature_mired_to_kelvin( - kwargs[ATTR_COLOR_TEMP] - ) - colortemp = kelvin - _LOGGER.debug( - "[wizlight %s] kelvin changed and send to bulb: %s", - self._light.ip, - colortemp, - ) - - sceneid = None - if ATTR_EFFECT in kwargs: - sceneid = get_id_from_scene_name(kwargs[ATTR_EFFECT]) - - if sceneid == 1000: # rhythm - pilot = PilotBuilder() - else: - pilot = PilotBuilder( - brightness=brightness, colortemp=colortemp, scene=sceneid - ) - _LOGGER.debug( - "[wizlight %s] Pilot will be send with brightness=%s, colortemp=%s, scene=%s", - self._light.ip, - brightness, - colortemp, - sceneid, - ) - - sceneid = None - if ATTR_EFFECT in kwargs: - sceneid = get_id_from_scene_name(kwargs[ATTR_EFFECT]) - - if sceneid == 1000: # rhythm - pilot = PilotBuilder() - else: - pilot = PilotBuilder( - brightness=brightness, colortemp=colortemp, scene=sceneid - ) - - await self._light.turn_on(pilot) - await self.coordinator.async_request_refresh() - async def async_turn_off(self, **kwargs: Any) -> None: - """Instruct the light to turn off.""" - await self._light.turn_off() + color_temp = None + if ATTR_COLOR_TEMP in kwargs: + color_temp = color_temperature_mired_to_kelvin(kwargs[ATTR_COLOR_TEMP]) + + scene_id = None + if ATTR_EFFECT in kwargs: + scene_id = get_id_from_scene_name(kwargs[ATTR_EFFECT]) + if scene_id == 1000: # rhythm + return PilotBuilder() + + _LOGGER.debug( + "[wizlight %s] Pilot will be sent with brightness=%s, color_temp=%s, scene_id=%s", + self._device.ip, + brightness, + color_temp, + scene_id, + ) + return PilotBuilder(brightness=brightness, colortemp=color_temp, scene=scene_id) + + async def async_turn_on(self, **kwargs: Any) -> None: + """Instruct the light to turn on.""" + await self._device.turn_on(self._async_pilot_builder(**kwargs)) await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/wiz/strings.json b/homeassistant/components/wiz/strings.json index 99f491e7360888..2195bb09a03139 100644 --- a/homeassistant/components/wiz/strings.json +++ b/homeassistant/components/wiz/strings.json @@ -19,7 +19,6 @@ "no_wiz_light": "The bulb can not be connected via WiZ Platform integration." }, "abort": { - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } } diff --git a/homeassistant/components/wiz/switch.py b/homeassistant/components/wiz/switch.py new file mode 100644 index 00000000000000..eae7e3d47a8827 --- /dev/null +++ b/homeassistant/components/wiz/switch.py @@ -0,0 +1,35 @@ +"""WiZ integration switch platform.""" +from __future__ import annotations + +from typing import Any + +from pywizlight import PilotBuilder + +from homeassistant.components.switch import SwitchEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN, SOCKET_DEVICE_STR +from .entity import WizToggleEntity +from .models import WizData + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the WiZ switch platform.""" + wiz_data: WizData = hass.data[DOMAIN][entry.entry_id] + if SOCKET_DEVICE_STR in wiz_data.bulb.bulbtype.name: + async_add_entities([WizSocketEntity(wiz_data, entry.title)]) + + +class WizSocketEntity(WizToggleEntity, SwitchEntity): + """Representation of a WiZ socket.""" + + async def async_turn_on(self, **kwargs: Any) -> None: + """Instruct the socket to turn on.""" + await self._device.turn_on(PilotBuilder()) + await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/wiz/translations/en.json b/homeassistant/components/wiz/translations/en.json index b0d65e9f95780d..192d1137c2f08c 100644 --- a/homeassistant/components/wiz/translations/en.json +++ b/homeassistant/components/wiz/translations/en.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "Device is already configured", - "no_devices_found": "No devices found on the network" + "already_configured": "Device is already configured" }, "error": { "bulb_time_out": "Can not connect to the bulb. Maybe the bulb is offline or a wrong IP/host was entered. Please turn on the light and try again!", diff --git a/tests/components/wiz/test_config_flow.py b/tests/components/wiz/test_config_flow.py index 18c28f50c0ec6c..a043d4a654ed88 100644 --- a/tests/components/wiz/test_config_flow.py +++ b/tests/components/wiz/test_config_flow.py @@ -152,6 +152,7 @@ async def test_form_updates_unique_id(hass): assert result2["type"] == "abort" assert result2["reason"] == "already_configured" + assert entry.data[CONF_HOST] == FAKE_IP @pytest.mark.parametrize( From b0bb2d24534ae5e3e3898a024480d2994e181ae2 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 5 Feb 2022 19:07:02 +0100 Subject: [PATCH 0326/1098] Cleanup Plugwise config flow and tests (#65818) --- .../components/plugwise/config_flow.py | 86 +---- homeassistant/components/plugwise/const.py | 2 - tests/components/plugwise/conftest.py | 29 +- tests/components/plugwise/test_config_flow.py | 345 +++++++----------- 4 files changed, 163 insertions(+), 299 deletions(-) diff --git a/homeassistant/components/plugwise/config_flow.py b/homeassistant/components/plugwise/config_flow.py index eb0c56115c8ac0..e611199ccf65f8 100644 --- a/homeassistant/components/plugwise/config_flow.py +++ b/homeassistant/components/plugwise/config_flow.py @@ -2,13 +2,14 @@ from __future__ import annotations import logging +from typing import Any from plugwise.exceptions import InvalidAuthentication, PlugwiseException from plugwise.smile import Smile import voluptuous as vol -from homeassistant import config_entries, core, exceptions -from homeassistant.components import zeroconf +from homeassistant.components.zeroconf import ZeroconfServiceInfo +from homeassistant.config_entries import ConfigFlow from homeassistant.const import ( CONF_BASE, CONF_HOST, @@ -17,6 +18,7 @@ CONF_PORT, CONF_USERNAME, ) +from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -25,11 +27,8 @@ DEFAULT_PORT, DEFAULT_USERNAME, DOMAIN, - FLOW_NET, FLOW_SMILE, FLOW_STRETCH, - FLOW_TYPE, - FLOW_USB, PW_TYPE, SMILE, STRETCH, @@ -39,14 +38,6 @@ _LOGGER = logging.getLogger(__name__) -CONF_MANUAL_PATH = "Enter Manually" - -CONNECTION_SCHEMA = vol.Schema( - {vol.Required(FLOW_TYPE, default=FLOW_NET): vol.In([FLOW_NET, FLOW_USB])} -) - -# PLACEHOLDER USB connection validation - def _base_gw_schema(discovery_info): """Generate base schema for gateways.""" @@ -64,14 +55,13 @@ def _base_gw_schema(discovery_info): return vol.Schema(base_gw_schema) -async def validate_gw_input(hass: core.HomeAssistant, data): +async def validate_gw_input(hass: HomeAssistant, data: dict[str, Any]) -> Smile: """ Validate whether the user input allows us to connect to the gateway. Data has the keys from _base_gw_schema() with values provided by the user. """ websession = async_get_clientsession(hass, verify_ssl=False) - api = Smile( host=data[CONF_HOST], password=data[CONF_PASSWORD], @@ -80,35 +70,25 @@ async def validate_gw_input(hass: core.HomeAssistant, data): timeout=30, websession=websession, ) - - try: - await api.connect() - except InvalidAuthentication as err: - raise InvalidAuth from err - except PlugwiseException as err: - raise CannotConnect from err - + await api.connect() return api -class PlugwiseConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class PlugwiseConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Plugwise Smile.""" VERSION = 1 - def __init__(self): - """Initialize the Plugwise config flow.""" - self.discovery_info: zeroconf.ZeroconfServiceInfo | None = None - self._username: str = DEFAULT_USERNAME + discovery_info: ZeroconfServiceInfo | None = None + _username: str = DEFAULT_USERNAME async def async_step_zeroconf( - self, discovery_info: zeroconf.ZeroconfServiceInfo + self, discovery_info: ZeroconfServiceInfo ) -> FlowResult: """Prepare configuration for a discovered Plugwise Smile.""" self.discovery_info = discovery_info _properties = discovery_info.properties - # unique_id is needed here, to be able to determine whether the discovered device is known, or not. unique_id = discovery_info.hostname.split(".")[0] await self.async_set_unique_id(unique_id) self._abort_if_unique_id_configured({CONF_HOST: discovery_info.host}) @@ -125,18 +105,15 @@ async def async_step_zeroconf( CONF_PORT: discovery_info.port, CONF_USERNAME: self._username, } - return await self.async_step_user_gateway() - - # PLACEHOLDER USB step_user + return await self.async_step_user() - async def async_step_user_gateway(self, user_input=None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle the initial step when using network/gateway setups.""" - api = None errors = {} if user_input is not None: - user_input.pop(FLOW_TYPE, None) - if self.discovery_info: user_input[CONF_HOST] = self.discovery_info.host user_input[CONF_PORT] = self.discovery_info.port @@ -144,16 +121,14 @@ async def async_step_user_gateway(self, user_input=None): try: api = await validate_gw_input(self.hass, user_input) - - except CannotConnect: - errors[CONF_BASE] = "cannot_connect" - except InvalidAuth: + except InvalidAuthentication: errors[CONF_BASE] = "invalid_auth" + except PlugwiseException: + errors[CONF_BASE] = "cannot_connect" except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors[CONF_BASE] = "unknown" - - if not errors: + else: await self.async_set_unique_id( api.smile_hostname or api.gateway_id, raise_on_progress=False ) @@ -162,31 +137,8 @@ async def async_step_user_gateway(self, user_input=None): user_input[PW_TYPE] = API return self.async_create_entry(title=api.smile_name, data=user_input) - return self.async_show_form( - step_id="user_gateway", - data_schema=_base_gw_schema(self.discovery_info), - errors=errors, - ) - - async def async_step_user(self, user_input=None): - """Handle the initial step when using network/gateway setups.""" - errors = {} - if user_input is not None: - if user_input[FLOW_TYPE] == FLOW_NET: - return await self.async_step_user_gateway() - - # PLACEHOLDER for USB_FLOW - return self.async_show_form( step_id="user", - data_schema=CONNECTION_SCHEMA, + data_schema=_base_gw_schema(self.discovery_info), errors=errors, ) - - -class CannotConnect(exceptions.HomeAssistantError): - """Error to indicate we cannot connect.""" - - -class InvalidAuth(exceptions.HomeAssistantError): - """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/plugwise/const.py b/homeassistant/components/plugwise/const.py index 772bccd92c1a7d..cd31255a040908 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -8,11 +8,9 @@ COORDINATOR = "coordinator" DEVICE_STATE = "device_state" DOMAIN = "plugwise" -FLOW_NET = "Network: Smile/Stretch" FLOW_SMILE = "smile (Adam/Anna/P1)" FLOW_STRETCH = "stretch (Stretch)" FLOW_TYPE = "flow_type" -FLOW_USB = "USB: Stick - Coming soon" GATEWAY = "gateway" PW_TYPE = "plugwise_type" SCHEDULE_OFF = "false" diff --git a/tests/components/plugwise/conftest.py b/tests/components/plugwise/conftest.py index 06f9b56e6894e2..65415285de2a17 100644 --- a/tests/components/plugwise/conftest.py +++ b/tests/components/plugwise/conftest.py @@ -1,9 +1,11 @@ """Setup mocks for the Plugwise integration tests.""" +from __future__ import annotations +from collections.abc import Generator from functools import partial from http import HTTPStatus import re -from unittest.mock import AsyncMock, Mock, patch +from unittest.mock import AsyncMock, MagicMock, Mock, patch import jsonpickle from plugwise.exceptions import ( @@ -24,16 +26,27 @@ def _read_json(environment, call): return jsonpickle.decode(fixture) -@pytest.fixture(name="mock_smile") -def mock_smile(): - """Create a Mock Smile for testing exceptions.""" +@pytest.fixture +def mock_setup_entry() -> Generator[AsyncMock, None, None]: + """Mock setting up a config entry.""" + with patch( + "homeassistant.components.plugwise.async_setup_entry", return_value=True + ) as mock_setup: + yield mock_setup + + +@pytest.fixture() +def mock_smile_config_flow() -> Generator[None, MagicMock, None]: + """Return a mocked Smile client.""" with patch( "homeassistant.components.plugwise.config_flow.Smile", + autospec=True, ) as smile_mock: - smile_mock.InvalidAuthentication = InvalidAuthentication - smile_mock.ConnectionFailedError = ConnectionFailedError - smile_mock.return_value.connect.return_value = True - yield smile_mock.return_value + smile = smile_mock.return_value + smile.smile_hostname = "smile12345" + smile.smile_name = "Test Smile Name" + smile.connect.return_value = True + yield smile @pytest.fixture(name="mock_smile_unauth") diff --git a/tests/components/plugwise/test_config_flow.py b/tests/components/plugwise/test_config_flow.py index 284be67a386f4b..3e9ce985a5c8ef 100644 --- a/tests/components/plugwise/test_config_flow.py +++ b/tests/components/plugwise/test_config_flow.py @@ -1,5 +1,5 @@ """Test the Plugwise config flow.""" -from unittest.mock import AsyncMock, patch +from unittest.mock import AsyncMock, MagicMock, patch from plugwise.exceptions import ( ConnectionFailedError, @@ -8,15 +8,8 @@ ) import pytest -from homeassistant.components import zeroconf -from homeassistant.components.plugwise.const import ( - API, - DEFAULT_PORT, - DOMAIN, - FLOW_NET, - FLOW_TYPE, - PW_TYPE, -) +from homeassistant.components.plugwise.const import API, DEFAULT_PORT, DOMAIN, PW_TYPE +from homeassistant.components.zeroconf import ZeroconfServiceInfo from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF from homeassistant.const import ( CONF_HOST, @@ -26,7 +19,12 @@ CONF_SOURCE, CONF_USERNAME, ) -from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_FORM +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import ( + RESULT_TYPE_ABORT, + RESULT_TYPE_CREATE_ENTRY, + RESULT_TYPE_FORM, +) from tests.common import MockConfigEntry @@ -38,7 +36,7 @@ TEST_USERNAME = "smile" TEST_USERNAME2 = "stretch" -TEST_DISCOVERY = zeroconf.ZeroconfServiceInfo( +TEST_DISCOVERY = ZeroconfServiceInfo( host=TEST_HOST, hostname=f"{TEST_HOSTNAME}.local.", name="mock_name", @@ -50,7 +48,8 @@ }, type="mock_type", ) -TEST_DISCOVERY2 = zeroconf.ZeroconfServiceInfo( + +TEST_DISCOVERY2 = ZeroconfServiceInfo( host=TEST_HOST, hostname=f"{TEST_HOSTNAME2}.local.", name="mock_name", @@ -77,49 +76,32 @@ def mock_smile(): yield smile_mock.return_value -async def test_form_flow_gateway(hass): - """Test we get the form for Plugwise Gateway product type.""" - +async def test_form( + hass: HomeAssistant, + mock_setup_entry: AsyncMock, + mock_smile_config_flow: MagicMock, +) -> None: + """Test the full user configuration flow.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={CONF_SOURCE: SOURCE_USER} ) - assert result["type"] == RESULT_TYPE_FORM - assert result["errors"] == {} - assert result["step_id"] == "user" - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={FLOW_TYPE: FLOW_NET} - ) - assert result["type"] == RESULT_TYPE_FORM - assert result["errors"] == {} - assert result["step_id"] == "user_gateway" + assert result.get("type") == RESULT_TYPE_FORM + assert result.get("errors") == {} + assert result.get("step_id") == "user" + assert "flow_id" in result - -async def test_form(hass): - """Test we get the form.""" - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data={FLOW_TYPE: FLOW_NET} + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_HOST: TEST_HOST, + CONF_PASSWORD: TEST_PASSWORD, + }, ) - assert result["type"] == RESULT_TYPE_FORM - assert result["errors"] == {} - - with patch( - "homeassistant.components.plugwise.config_flow.Smile.connect", - return_value=True, - ), patch( - "homeassistant.components.plugwise.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_HOST: TEST_HOST, CONF_PASSWORD: TEST_PASSWORD}, - ) - await hass.async_block_till_done() - assert result2["type"] == RESULT_TYPE_CREATE_ENTRY - assert result2["data"] == { + assert result2.get("type") == RESULT_TYPE_CREATE_ENTRY + assert result2.get("title") == "Test Smile Name" + assert result2.get("data") == { CONF_HOST: TEST_HOST, CONF_PASSWORD: TEST_PASSWORD, CONF_PORT: DEFAULT_PORT, @@ -128,72 +110,79 @@ async def test_form(hass): } assert len(mock_setup_entry.mock_calls) == 1 + assert len(mock_smile_config_flow.connect.mock_calls) == 1 -async def test_zeroconf_form(hass): - """Test we get the form.""" - +@pytest.mark.parametrize( + "discovery,username", + [ + (TEST_DISCOVERY, TEST_USERNAME), + (TEST_DISCOVERY2, TEST_USERNAME2), + ], +) +async def test_zeroconf_flow( + hass: HomeAssistant, + mock_setup_entry: AsyncMock, + mock_smile_config_flow: MagicMock, + discovery: ZeroconfServiceInfo, + username: str, +) -> None: + """Test config flow for smile devices.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={CONF_SOURCE: SOURCE_ZEROCONF}, - data=TEST_DISCOVERY, + data=discovery, ) - assert result["type"] == RESULT_TYPE_FORM - assert result["errors"] == {} - - with patch( - "homeassistant.components.plugwise.config_flow.Smile.connect", - return_value=True, - ), patch( - "homeassistant.components.plugwise.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_PASSWORD: TEST_PASSWORD}, - ) + assert result.get("type") == RESULT_TYPE_FORM + assert result.get("errors") == {} + assert result.get("step_id") == "user" + assert "flow_id" in result + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_PASSWORD: TEST_PASSWORD}, + ) await hass.async_block_till_done() - assert result2["type"] == RESULT_TYPE_CREATE_ENTRY - assert result2["data"] == { + assert result2.get("type") == RESULT_TYPE_CREATE_ENTRY + assert result2.get("title") == "Test Smile Name" + assert result2.get("data") == { CONF_HOST: TEST_HOST, CONF_PASSWORD: TEST_PASSWORD, CONF_PORT: DEFAULT_PORT, - CONF_USERNAME: TEST_USERNAME, + CONF_USERNAME: username, PW_TYPE: API, } assert len(mock_setup_entry.mock_calls) == 1 + assert len(mock_smile_config_flow.connect.mock_calls) == 1 -async def test_zeroconf_stretch_form(hass): - """Test we get the form.""" - +async def test_zeroconf_flow_stretch( + hass: HomeAssistant, + mock_setup_entry: AsyncMock, + mock_smile_config_flow: MagicMock, +) -> None: + """Test config flow for stretch devices.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={CONF_SOURCE: SOURCE_ZEROCONF}, data=TEST_DISCOVERY2, ) - assert result["type"] == RESULT_TYPE_FORM - assert result["errors"] == {} - - with patch( - "homeassistant.components.plugwise.config_flow.Smile.connect", - return_value=True, - ), patch( - "homeassistant.components.plugwise.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_PASSWORD: TEST_PASSWORD}, - ) + assert result.get("type") == RESULT_TYPE_FORM + assert result.get("errors") == {} + assert result.get("step_id") == "user" + assert "flow_id" in result + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_PASSWORD: TEST_PASSWORD}, + ) await hass.async_block_till_done() - assert result2["type"] == RESULT_TYPE_CREATE_ENTRY - assert result2["data"] == { + assert result2.get("type") == RESULT_TYPE_CREATE_ENTRY + assert result2.get("title") == "Test Smile Name" + assert result2.get("data") == { CONF_HOST: TEST_HOST, CONF_PASSWORD: TEST_PASSWORD, CONF_PORT: DEFAULT_PORT, @@ -202,9 +191,10 @@ async def test_zeroconf_stretch_form(hass): } assert len(mock_setup_entry.mock_calls) == 1 + assert len(mock_smile_config_flow.connect.mock_calls) == 1 -async def test_zercoconf_discovery_update_configuration(hass): +async def test_zercoconf_discovery_update_configuration(hass: HomeAssistant) -> None: """Test if a discovered device is configured and updated with new host.""" entry = MockConfigEntry( domain=DOMAIN, @@ -222,154 +212,65 @@ async def test_zercoconf_discovery_update_configuration(hass): data=TEST_DISCOVERY, ) - assert result["type"] == "abort" - assert result["reason"] == "already_configured" + assert result.get("type") == RESULT_TYPE_ABORT + assert result.get("reason") == "already_configured" assert entry.data[CONF_HOST] == "1.1.1.1" -async def test_form_username(hass): - """Test we get the username data back.""" - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data={FLOW_TYPE: FLOW_NET} - ) - assert result["type"] == RESULT_TYPE_FORM - assert result["errors"] == {} - - with patch( - "homeassistant.components.plugwise.config_flow.Smile", - ) as smile_mock, patch( - "homeassistant.components.plugwise.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - smile_mock.return_value.connect.side_effect = AsyncMock(return_value=True) - smile_mock.return_value.gateway_id = "abcdefgh12345678" - smile_mock.return_value.smile_hostname = TEST_HOST - smile_mock.return_value.smile_name = "Adam" - - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={ - CONF_HOST: TEST_HOST, - CONF_PASSWORD: TEST_PASSWORD, - CONF_USERNAME: TEST_USERNAME2, - }, - ) - - await hass.async_block_till_done() - - assert result2["type"] == RESULT_TYPE_CREATE_ENTRY - assert result2["data"] == { - CONF_HOST: TEST_HOST, - CONF_PASSWORD: TEST_PASSWORD, - CONF_PORT: DEFAULT_PORT, - CONF_USERNAME: TEST_USERNAME2, - PW_TYPE: API, - } - - assert len(mock_setup_entry.mock_calls) == 1 - - result3 = await hass.config_entries.flow.async_init( - DOMAIN, - context={CONF_SOURCE: SOURCE_ZEROCONF}, - data=TEST_DISCOVERY, - ) - assert result3["type"] == RESULT_TYPE_FORM - - with patch( - "homeassistant.components.plugwise.config_flow.Smile", - ) as smile_mock, patch( - "homeassistant.components.plugwise.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - smile_mock.return_value.side_effect = AsyncMock(return_value=True) - smile_mock.return_value.connect.side_effect = AsyncMock(return_value=True) - smile_mock.return_value.gateway_id = "abcdefgh12345678" - smile_mock.return_value.smile_hostname = TEST_HOST - smile_mock.return_value.smile_name = "Adam" - - result4 = await hass.config_entries.flow.async_configure( - result3["flow_id"], - user_input={CONF_PASSWORD: TEST_PASSWORD}, - ) - - await hass.async_block_till_done() - - assert result4["type"] == "abort" - assert result4["reason"] == "already_configured" - - -async def test_form_invalid_auth(hass, mock_smile): +@pytest.mark.parametrize( + "side_effect,reason", + [ + (InvalidAuthentication, "invalid_auth"), + (ConnectionFailedError, "cannot_connect"), + (PlugwiseException, "cannot_connect"), + (RuntimeError, "unknown"), + ], +) +async def test_flow_errors( + hass: HomeAssistant, + mock_setup_entry: AsyncMock, + mock_smile_config_flow: MagicMock, + side_effect: Exception, + reason: str, +) -> None: """Test we handle invalid auth.""" result = await hass.config_entries.flow.async_init( - DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data={FLOW_TYPE: FLOW_NET} + DOMAIN, + context={CONF_SOURCE: SOURCE_USER}, ) + assert result.get("type") == RESULT_TYPE_FORM + assert result.get("errors") == {} + assert result.get("step_id") == "user" + assert "flow_id" in result - mock_smile.connect.side_effect = InvalidAuthentication - mock_smile.gateway_id = "0a636a4fc1704ab4a24e4f7e37fb187a" - + mock_smile_config_flow.connect.side_effect = side_effect result2 = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_HOST: TEST_HOST, CONF_PASSWORD: TEST_PASSWORD}, ) - assert result2["type"] == RESULT_TYPE_FORM - assert result2["errors"] == {"base": "invalid_auth"} - + assert result2.get("type") == RESULT_TYPE_FORM + assert result2.get("errors") == {"base": reason} + assert result2.get("step_id") == "user" -async def test_form_cannot_connect(hass, mock_smile): - """Test we handle cannot connect error.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data={FLOW_TYPE: FLOW_NET} - ) - - mock_smile.connect.side_effect = ConnectionFailedError - mock_smile.gateway_id = "0a636a4fc1704ab4a24e4f7e37fb187a" + assert len(mock_setup_entry.mock_calls) == 0 + assert len(mock_smile_config_flow.connect.mock_calls) == 1 - result2 = await hass.config_entries.flow.async_configure( + mock_smile_config_flow.connect.side_effect = None + result3 = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_HOST: TEST_HOST, CONF_PASSWORD: TEST_PASSWORD}, ) - assert result2["type"] == RESULT_TYPE_FORM - assert result2["errors"] == {"base": "cannot_connect"} - - -async def test_form_cannot_connect_port(hass, mock_smile): - """Test we handle cannot connect to port error.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data={FLOW_TYPE: FLOW_NET} - ) - - mock_smile.connect.side_effect = ConnectionFailedError - mock_smile.gateway_id = "0a636a4fc1704ab4a24e4f7e37fb187a" - - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={ - CONF_HOST: TEST_HOST, - CONF_PASSWORD: TEST_PASSWORD, - CONF_PORT: TEST_PORT, - }, - ) - - assert result2["type"] == RESULT_TYPE_FORM - assert result2["errors"] == {"base": "cannot_connect"} - - -async def test_form_other_problem(hass, mock_smile): - """Test we handle cannot connect error.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data={FLOW_TYPE: FLOW_NET} - ) - - mock_smile.connect.side_effect = TimeoutError - mock_smile.gateway_id = "0a636a4fc1704ab4a24e4f7e37fb187a" - - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_HOST: TEST_HOST, CONF_PASSWORD: TEST_PASSWORD}, - ) + assert result3.get("type") == RESULT_TYPE_CREATE_ENTRY + assert result3.get("title") == "Test Smile Name" + assert result3.get("data") == { + CONF_HOST: TEST_HOST, + CONF_PASSWORD: TEST_PASSWORD, + CONF_PORT: DEFAULT_PORT, + CONF_USERNAME: TEST_USERNAME, + PW_TYPE: API, + } - assert result2["type"] == RESULT_TYPE_FORM - assert result2["errors"] == {"base": "unknown"} + assert len(mock_setup_entry.mock_calls) == 1 + assert len(mock_smile_config_flow.connect.mock_calls) == 2 From 87049283c17ee0995afc1b3d6e4e5e25cceb9715 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 5 Feb 2022 19:09:37 +0100 Subject: [PATCH 0327/1098] Extract base entity class in Plugwise (#65821) --- .../components/plugwise/binary_sensor.py | 4 +- homeassistant/components/plugwise/climate.py | 4 +- homeassistant/components/plugwise/entity.py | 82 +++++++++++++++++++ homeassistant/components/plugwise/gateway.py | 80 +----------------- homeassistant/components/plugwise/sensor.py | 4 +- homeassistant/components/plugwise/switch.py | 4 +- 6 files changed, 93 insertions(+), 85 deletions(-) create mode 100644 homeassistant/components/plugwise/entity.py diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index e8bb0f3366a904..25b1578cd8a3cc 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -19,7 +19,7 @@ NO_NOTIFICATION_ICON, NOTIFICATION_ICON, ) -from .gateway import SmileGateway +from .entity import PlugwiseEntity BINARY_SENSOR_MAP = { "dhw_state": ["Domestic Hot Water State", None], @@ -77,7 +77,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class SmileBinarySensor(SmileGateway, BinarySensorEntity): +class SmileBinarySensor(PlugwiseEntity, BinarySensorEntity): """Represent Smile Binary Sensors.""" def __init__( diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index 600b61841919de..e3185c0701d4c3 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -30,7 +30,7 @@ SCHEDULE_OFF, SCHEDULE_ON, ) -from .gateway import SmileGateway +from .entity import PlugwiseEntity HVAC_MODES_HEAT_ONLY = [HVAC_MODE_HEAT, HVAC_MODE_AUTO] HVAC_MODES_HEAT_COOL = [HVAC_MODE_HEAT_COOL, HVAC_MODE_AUTO] @@ -74,7 +74,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class PwThermostat(SmileGateway, ClimateEntity): +class PwThermostat(PlugwiseEntity, ClimateEntity): """Representation of an Plugwise thermostat.""" _attr_hvac_mode = HVAC_MODE_HEAT diff --git a/homeassistant/components/plugwise/entity.py b/homeassistant/components/plugwise/entity.py new file mode 100644 index 00000000000000..ed9f2c61a78698 --- /dev/null +++ b/homeassistant/components/plugwise/entity.py @@ -0,0 +1,82 @@ +"""Generic Plugwise Entity Class.""" +from __future__ import annotations + +from plugwise.smile import Smile + +from homeassistant.const import ( + ATTR_CONFIGURATION_URL, + ATTR_MODEL, + ATTR_VIA_DEVICE, + CONF_HOST, +) +from homeassistant.core import callback +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, +) + +from .const import DOMAIN + + +class PlugwiseEntity(CoordinatorEntity): + """Represent a PlugWise Entity.""" + + def __init__( + self, api: Smile, coordinator: DataUpdateCoordinator, name: str, dev_id: str + ) -> None: + """Initialise the gateway.""" + super().__init__(coordinator) + + self._api = api + self._name = name + self._dev_id = dev_id + + self._unique_id: str | None = None + self._model: str | None = None + + self._entity_name = self._name + + @property + def unique_id(self) -> str | None: + """Return a unique ID.""" + return self._unique_id + + @property + def name(self) -> str | None: + """Return the name of the entity, if any.""" + return self._name + + @property + def device_info(self) -> DeviceInfo: + """Return the device information.""" + device_information = DeviceInfo( + identifiers={(DOMAIN, self._dev_id)}, + name=self._entity_name, + manufacturer="Plugwise", + ) + + if entry := self.coordinator.config_entry: + device_information[ + ATTR_CONFIGURATION_URL + ] = f"http://{entry.data[CONF_HOST]}" + + if self._model is not None: + device_information[ATTR_MODEL] = self._model.replace("_", " ").title() + + if self._dev_id != self._api.gateway_id: + device_information[ATTR_VIA_DEVICE] = (DOMAIN, str(self._api.gateway_id)) + + return device_information + + async def async_added_to_hass(self) -> None: + """Subscribe to updates.""" + self._async_process_data() + self.async_on_remove( + self.coordinator.async_add_listener(self._async_process_data) + ) + + @callback + def _async_process_data(self) -> None: + """Interpret and process API data.""" + raise NotImplementedError diff --git a/homeassistant/components/plugwise/gateway.py b/homeassistant/components/plugwise/gateway.py index 24c937799ba051..9d453596450c62 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -13,25 +13,12 @@ from plugwise.smile import Smile from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - ATTR_CONFIGURATION_URL, - ATTR_MODEL, - ATTR_VIA_DEVICE, - CONF_HOST, - CONF_PASSWORD, - CONF_PORT, - CONF_USERNAME, -) -from homeassistant.core import HomeAssistant, callback +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME +from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.entity import DeviceInfo -from homeassistant.helpers.update_coordinator import ( - CoordinatorEntity, - DataUpdateCoordinator, - UpdateFailed, -) +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import ( COORDINATOR, @@ -139,64 +126,3 @@ async def async_unload_entry_gw(hass: HomeAssistant, entry: ConfigEntry): ): hass.data[DOMAIN].pop(entry.entry_id) return unload_ok - - -class SmileGateway(CoordinatorEntity): - """Represent Smile Gateway.""" - - def __init__(self, api, coordinator, name, dev_id): - """Initialise the gateway.""" - super().__init__(coordinator) - - self._api = api - self._name = name - self._dev_id = dev_id - - self._unique_id = None - self._model = None - - self._entity_name = self._name - - @property - def unique_id(self): - """Return a unique ID.""" - return self._unique_id - - @property - def name(self): - """Return the name of the entity, if any.""" - return self._name - - @property - def device_info(self) -> DeviceInfo: - """Return the device information.""" - device_information = DeviceInfo( - identifiers={(DOMAIN, self._dev_id)}, - name=self._entity_name, - manufacturer="Plugwise", - ) - - if entry := self.coordinator.config_entry: - device_information[ - ATTR_CONFIGURATION_URL - ] = f"http://{entry.data[CONF_HOST]}" - - if self._model is not None: - device_information[ATTR_MODEL] = self._model.replace("_", " ").title() - - if self._dev_id != self._api.gateway_id: - device_information[ATTR_VIA_DEVICE] = (DOMAIN, self._api.gateway_id) - - return device_information - - async def async_added_to_hass(self): - """Subscribe to updates.""" - self._async_process_data() - self.async_on_remove( - self.coordinator.async_add_listener(self._async_process_data) - ) - - @callback - def _async_process_data(self): - """Interpret and process API data.""" - raise NotImplementedError diff --git a/homeassistant/components/plugwise/sensor.py b/homeassistant/components/plugwise/sensor.py index 2fdfd952d8ef0c..da09d4afa1367c 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -37,7 +37,7 @@ SENSOR_MAP_UOM, UNIT_LUMEN, ) -from .gateway import SmileGateway +from .entity import PlugwiseEntity _LOGGER = logging.getLogger(__name__) @@ -300,7 +300,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class SmileSensor(SmileGateway, SensorEntity): +class SmileSensor(PlugwiseEntity, SensorEntity): """Represent Smile Sensors.""" def __init__( diff --git a/homeassistant/components/plugwise/switch.py b/homeassistant/components/plugwise/switch.py index 1aa8bdc51d739e..baefcaeb7105ce 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -9,7 +9,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import COORDINATOR, DOMAIN, SWITCH_ICON -from .gateway import SmileGateway +from .entity import PlugwiseEntity _LOGGER = logging.getLogger(__name__) @@ -56,7 +56,7 @@ async def async_setup_entry_gateway(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class GwSwitch(SmileGateway, SwitchEntity): +class GwSwitch(PlugwiseEntity, SwitchEntity): """Representation of a Plugwise plug.""" def __init__(self, api, coordinator, name, dev_id, members, model): From 52d7ca6b1c196d3564f2d6d1372df870f2a3e023 Mon Sep 17 00:00:00 2001 From: dougiteixeira <31328123+dougiteixeira@users.noreply.github.com> Date: Sat, 5 Feb 2022 16:31:20 -0300 Subject: [PATCH 0328/1098] Complementing the Tuya Humidifier (jsq) category (#65276) Co-authored-by: Franck Nijhof --- homeassistant/components/tuya/const.py | 9 +++++ homeassistant/components/tuya/number.py | 14 +++++++ homeassistant/components/tuya/select.py | 39 +++++++++++++++++++ homeassistant/components/tuya/sensor.py | 28 +++++++++++++ .../components/tuya/strings.select.json | 26 +++++++++++++ homeassistant/components/tuya/switch.py | 22 +++++++++++ 6 files changed, 138 insertions(+) diff --git a/homeassistant/components/tuya/const.py b/homeassistant/components/tuya/const.py index 5d2070b901552b..d976bee279265d 100644 --- a/homeassistant/components/tuya/const.py +++ b/homeassistant/components/tuya/const.py @@ -96,6 +96,9 @@ class TuyaDeviceClass(StrEnum): DECIBEL_SENSITIVITY = "tuya__decibel_sensitivity" FAN_ANGLE = "tuya__fan_angle" FINGERBOT_MODE = "tuya__fingerbot_mode" + HUMIDIFIER_SPRAY_MODE = "tuya__humidifier_spray_mode" + HUMIDIFIER_LEVEL = "tuya__humidifier_level" + HUMIDIFIER_MOODLIGHTING = "tuya__humidifier_moodlighting" IPC_WORK_MODE = "tuya__ipc_work_mode" LED_TYPE = "tuya__led_type" LIGHT_MODE = "tuya__light_mode" @@ -245,6 +248,7 @@ class DPCode(StrEnum): LED_TYPE_2 = "led_type_2" LED_TYPE_3 = "led_type_3" LEVEL = "level" + LEVEL_CURRENT = "level_current" LIGHT = "light" # Light LIGHT_MODE = "light_mode" LOCK = "lock" # Lock / Child lock @@ -253,6 +257,7 @@ class DPCode(StrEnum): MANUAL_FEED = "manual_feed" MATERIAL = "material" # Material MODE = "mode" # Working mode / Mode + MOODLIGHTING = "moodlighting" # Mood light MOTION_RECORD = "motion_record" MOTION_SENSITIVITY = "motion_sensitivity" MOTION_SWITCH = "motion_switch" # Motion switch @@ -303,6 +308,7 @@ class DPCode(StrEnum): SHOCK_STATE = "shock_state" # Vibration status SIREN_SWITCH = "siren_switch" SITUATION_SET = "situation_set" + SLEEP = "sleep" # Sleep function SLOW_FEED = "slow_feed" SMOKE_SENSOR_STATE = "smoke_sensor_state" SMOKE_SENSOR_STATUS = "smoke_sensor_status" @@ -310,8 +316,10 @@ class DPCode(StrEnum): SOS = "sos" # Emergency State SOS_STATE = "sos_state" # Emergency mode SPEED = "speed" # Speed level + SPRAY_MODE = "spray_mode" # Spraying mode START = "start" # Start STATUS = "status" + STERILIZATION = "sterilization" # Sterilization SUCTION = "suction" SWING = "swing" # Swing mode SWITCH = "switch" # Switch @@ -333,6 +341,7 @@ class DPCode(StrEnum): SWITCH_LED_3 = "switch_led_3" SWITCH_NIGHT_LIGHT = "switch_night_light" SWITCH_SAVE_ENERGY = "switch_save_energy" + SWITCH_SOUND = "switch_sound" # Voice switch SWITCH_SPRAY = "switch_spray" # Spraying switch SWITCH_USB1 = "switch_usb1" # USB 1 SWITCH_USB2 = "switch_usb2" # USB 2 diff --git a/homeassistant/components/tuya/number.py b/homeassistant/components/tuya/number.py index d234b6778beb56..d9cde61a2760ee 100644 --- a/homeassistant/components/tuya/number.py +++ b/homeassistant/components/tuya/number.py @@ -250,6 +250,20 @@ icon="mdi:thermometer-lines", ), ), + # Humidifier + # https://developer.tuya.com/en/docs/iot/categoryjsq?id=Kaiuz1smr440b + "jsq": ( + NumberEntityDescription( + key=DPCode.TEMP_SET, + name="Temperature", + icon="mdi:thermometer-lines", + ), + NumberEntityDescription( + key=DPCode.TEMP_SET_F, + name="Temperature", + icon="mdi:thermometer-lines", + ), + ), } diff --git a/homeassistant/components/tuya/select.py b/homeassistant/components/tuya/select.py index a3e1d0439ae63d..d9103b916f461e 100644 --- a/homeassistant/components/tuya/select.py +++ b/homeassistant/components/tuya/select.py @@ -276,6 +276,45 @@ entity_category=EntityCategory.CONFIG, ), ), + # Humidifier + # https://developer.tuya.com/en/docs/iot/categoryjsq?id=Kaiuz1smr440b + "jsq": ( + SelectEntityDescription( + key=DPCode.SPRAY_MODE, + name="Spray Mode", + device_class=TuyaDeviceClass.HUMIDIFIER_SPRAY_MODE, + entity_category=EntityCategory.CONFIG, + icon="mdi:spray", + ), + SelectEntityDescription( + key=DPCode.LEVEL, + name="Spraying Level", + device_class=TuyaDeviceClass.HUMIDIFIER_LEVEL, + entity_category=EntityCategory.CONFIG, + icon="mdi:spray", + ), + SelectEntityDescription( + key=DPCode.MOODLIGHTING, + name="Moodlighting", + device_class=TuyaDeviceClass.HUMIDIFIER_MOODLIGHTING, + entity_category=EntityCategory.CONFIG, + icon="mdi:lightbulb-multiple", + ), + SelectEntityDescription( + key=DPCode.COUNTDOWN, + name="Countdown", + device_class=TuyaDeviceClass.COUNTDOWN, + entity_category=EntityCategory.CONFIG, + icon="mdi:timer-cog-outline", + ), + SelectEntityDescription( + key=DPCode.COUNTDOWN_SET, + name="Countdown", + device_class=TuyaDeviceClass.COUNTDOWN, + entity_category=EntityCategory.CONFIG, + icon="mdi:timer-cog-outline", + ), + ), # Air Purifier # https://developer.tuya.com/en/docs/iot/f?id=K9gf46h2s6dzm "kj": ( diff --git a/homeassistant/components/tuya/sensor.py b/homeassistant/components/tuya/sensor.py index effb5a1443b91e..7fcea1095d4e3b 100644 --- a/homeassistant/components/tuya/sensor.py +++ b/homeassistant/components/tuya/sensor.py @@ -742,6 +742,34 @@ class TuyaSensorEntityDescription(SensorEntityDescription): icon="mdi:progress-clock", ), ), + # Humidifier + # https://developer.tuya.com/en/docs/iot/s?id=K9gf48qwjz0i3 + "jsq": ( + TuyaSensorEntityDescription( + key=DPCode.HUMIDITY_CURRENT, + name="Humidity", + device_class=SensorDeviceClass.HUMIDITY, + state_class=SensorStateClass.MEASUREMENT, + ), + TuyaSensorEntityDescription( + key=DPCode.TEMP_CURRENT, + name="Temperature", + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + ), + TuyaSensorEntityDescription( + key=DPCode.TEMP_CURRENT_F, + name="Temperature", + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + ), + TuyaSensorEntityDescription( + key=DPCode.LEVEL_CURRENT, + name="Water Level", + entity_category=EntityCategory.DIAGNOSTIC, + icon="mdi:waves-arrow-up", + ), + ), # Air Purifier # https://developer.tuya.com/en/docs/iot/s?id=K9gf48r41mn81 "kj": ( diff --git a/homeassistant/components/tuya/strings.select.json b/homeassistant/components/tuya/strings.select.json index ada9c528ac8542..a765912d036106 100644 --- a/homeassistant/components/tuya/strings.select.json +++ b/homeassistant/components/tuya/strings.select.json @@ -102,6 +102,32 @@ "4h": "4 hours", "5h": "5 hours", "6h": "6 hours" + }, + "tuya__humidifier_spray_mode": { + "auto": "Auto", + "health": "Health", + "sleep": "Sleep", + "humidity": "Humidity", + "work": "Work" + }, + "tuya__humidifier_level": { + "level_1": "Level 1", + "level_2": "Level 2", + "level_3": "Level 3", + "level_4": "Level 4", + "level_5": "Level 5", + "level_6": "Level 6", + "level_7": "Level 7", + "level_8": "Level 8", + "level_9": "Level 9", + "level_10": "Level 10" + }, + "tuya__humidifier_moodlighting": { + "1": "Mood 1", + "2": "Mood 2", + "3": "Mood 3", + "4": "Mood 4", + "5": "Mood 5" } } } diff --git a/homeassistant/components/tuya/switch.py b/homeassistant/components/tuya/switch.py index ae63830bd8ddcb..fc3d8d86bafdd2 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -558,6 +558,28 @@ entity_category=EntityCategory.CONFIG, ), ), + # Humidifier + # https://developer.tuya.com/en/docs/iot/categoryjsq?id=Kaiuz1smr440b + "jsq": ( + SwitchEntityDescription( + key=DPCode.SWITCH_SOUND, + name="Voice", + icon="mdi:account-voice", + entity_category=EntityCategory.CONFIG, + ), + SwitchEntityDescription( + key=DPCode.SLEEP, + name="Sleep", + icon="mdi:power-sleep", + entity_category=EntityCategory.CONFIG, + ), + SwitchEntityDescription( + key=DPCode.STERILIZATION, + name="Sterilization", + icon="mdi:minus-circle-outline", + entity_category=EntityCategory.CONFIG, + ), + ), } # Socket (duplicate of `pc`) From b299f80feb04770fb8e8553ff15d10668eba4a3a Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 5 Feb 2022 21:23:25 +0100 Subject: [PATCH 0329/1098] Improve entry setup error logging for Plugwise (#65830) --- homeassistant/components/plugwise/gateway.py | 24 ++++++++------------ 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/plugwise/gateway.py b/homeassistant/components/plugwise/gateway.py index 9d453596450c62..611a9153cadec4 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -39,7 +39,6 @@ async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Plugwise Smiles from a config entry.""" websession = async_get_clientsession(hass, verify_ssl=False) - api = Smile( host=entry.data[CONF_HOST], username=entry.data.get(CONF_USERNAME, DEFAULT_USERNAME), @@ -51,22 +50,20 @@ async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: try: connected = await api.connect() - - if not connected: - _LOGGER.error("Unable to connect to Smile") - raise ConfigEntryNotReady - except InvalidAuthentication: _LOGGER.error("Invalid username or Smile ID") return False - except PlugwiseException as err: - _LOGGER.error("Error while communicating to device %s", api.smile_name) - raise ConfigEntryNotReady from err - + raise ConfigEntryNotReady( + f"Error while communicating to device {api.smile_name}" + ) from err except asyncio.TimeoutError as err: - _LOGGER.error("Timeout while connecting to Smile %s", api.smile_name) - raise ConfigEntryNotReady from err + raise ConfigEntryNotReady( + f"Timeout while connecting to Smile {api.smile_name}" + ) from err + + if not connected: + raise ConfigEntryNotReady("Unable to connect to Smile") async def async_update_data(): """Update data via API endpoint.""" @@ -84,7 +81,6 @@ async def async_update_data(): update_method=async_update_data, update_interval=DEFAULT_SCAN_INTERVAL[api.smile_type], ) - await coordinator.async_config_entry_first_refresh() api.get_all_devices() @@ -101,7 +97,7 @@ async def async_update_data(): device_registry = dr.async_get(hass) device_registry.async_get_or_create( config_entry_id=entry.entry_id, - identifiers={(DOMAIN, api.gateway_id)}, + identifiers={(DOMAIN, str(api.gateway_id))}, manufacturer="Plugwise", name=entry.title, model=f"Smile {api.smile_name}", From 9dc158f5e02682fbeb203f80d0ae2be72868d901 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 5 Feb 2022 15:23:31 -0600 Subject: [PATCH 0330/1098] Add support for picking discovered devices to WiZ (#65826) * Add support for picking discovered devices - Also fixes state not being written initially (it was not so obvious since the next coordinator update wrote it) * store it * store it * order * fixes * more cleanups * hints * naming * merge branches --- homeassistant/components/wiz/config_flow.py | 56 +++++++- homeassistant/components/wiz/entity.py | 7 +- homeassistant/components/wiz/light.py | 59 +++----- homeassistant/components/wiz/strings.json | 7 +- homeassistant/components/wiz/switch.py | 5 + .../components/wiz/translations/en.json | 7 +- tests/components/wiz/test_config_flow.py | 135 ++++++++++++++++-- 7 files changed, 223 insertions(+), 53 deletions(-) diff --git a/homeassistant/components/wiz/config_flow.py b/homeassistant/components/wiz/config_flow.py index 34b484f145ec83..9b753284dc5eae 100644 --- a/homeassistant/components/wiz/config_flow.py +++ b/homeassistant/components/wiz/config_flow.py @@ -14,11 +14,14 @@ from homeassistant.const import CONF_HOST from homeassistant.data_entry_flow import FlowResult -from .const import DOMAIN, WIZ_EXCEPTIONS -from .utils import name_from_bulb_type_and_mac +from .const import DEFAULT_NAME, DISCOVER_SCAN_TIMEOUT, DOMAIN, WIZ_EXCEPTIONS +from .discovery import async_discover_devices +from .utils import _short_mac, name_from_bulb_type_and_mac _LOGGER = logging.getLogger(__name__) +CONF_DEVICE = "device" + class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for WiZ.""" @@ -28,6 +31,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Initialize the config flow.""" self._discovered_device: DiscoveredBulb | None = None + self._discovered_devices: dict[str, DiscoveredBulb] = {} self._name: str | None = None async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: @@ -85,13 +89,57 @@ async def async_step_discovery_confirm( data_schema=vol.Schema({}), ) + async def async_step_pick_device( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the step to pick discovered device.""" + if user_input is not None: + device = self._discovered_devices[user_input[CONF_DEVICE]] + await self.async_set_unique_id(device.mac_address, raise_on_progress=False) + bulb = wizlight(device.ip_address) + try: + bulbtype = await bulb.get_bulbtype() + except WIZ_EXCEPTIONS: + return self.async_abort(reason="cannot_connect") + else: + return self.async_create_entry( + title=name_from_bulb_type_and_mac(bulbtype, device.mac_address), + data={CONF_HOST: device.ip_address}, + ) + + current_unique_ids = self._async_current_ids() + current_hosts = { + entry.data[CONF_HOST] + for entry in self._async_current_entries(include_ignore=False) + } + discovered_devices = await async_discover_devices( + self.hass, DISCOVER_SCAN_TIMEOUT + ) + self._discovered_devices = { + device.mac_address: device for device in discovered_devices + } + devices_name = { + mac: f"{DEFAULT_NAME} {_short_mac(mac)} ({device.ip_address})" + for mac, device in self._discovered_devices.items() + if mac not in current_unique_ids and device.ip_address not in current_hosts + } + # Check if there is at least one device + if not devices_name: + return self.async_abort(reason="no_devices_found") + return self.async_show_form( + step_id="pick_device", + data_schema=vol.Schema({vol.Required(CONF_DEVICE): vol.In(devices_name)}), + ) + async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle a flow initialized by the user.""" errors = {} if user_input is not None: - bulb = wizlight(user_input[CONF_HOST]) + if not (host := user_input[CONF_HOST]): + return await self.async_step_pick_device() + bulb = wizlight(host) try: mac = await bulb.getMac() bulbtype = await bulb.get_bulbtype() @@ -117,6 +165,6 @@ async def async_step_user( return self.async_show_form( step_id="user", - data_schema=vol.Schema({vol.Required(CONF_HOST): str}), + data_schema=vol.Schema({vol.Optional(CONF_HOST, default=""): str}), errors=errors, ) diff --git a/homeassistant/components/wiz/entity.py b/homeassistant/components/wiz/entity.py index 0c4829383d30bc..de0b0e3f947e4c 100644 --- a/homeassistant/components/wiz/entity.py +++ b/homeassistant/components/wiz/entity.py @@ -33,9 +33,14 @@ def __init__(self, wiz_data: WizData, name: str) -> None: @callback def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator.""" - self._attr_is_on = self._device.status + self._async_update_attrs() super()._handle_coordinator_update() + @callback + def _async_update_attrs(self) -> None: + """Handle updating _attr values.""" + self._attr_is_on = self._device.status + async def async_turn_off(self, **kwargs: Any) -> None: """Instruct the device to turn off.""" await self._device.turn_off() diff --git a/homeassistant/components/wiz/light.py b/homeassistant/components/wiz/light.py index ee586c8939d91b..bc5ac078ec78a0 100644 --- a/homeassistant/components/wiz/light.py +++ b/homeassistant/components/wiz/light.py @@ -5,7 +5,7 @@ from typing import Any from pywizlight import PilotBuilder -from pywizlight.bulblibrary import BulbClass, BulbType +from pywizlight.bulblibrary import BulbClass, BulbType, Features from pywizlight.rgbcw import convertHSfromRGBCW from pywizlight.scenes import get_id_from_scene_name @@ -34,34 +34,6 @@ _LOGGER = logging.getLogger(__name__) -DEFAULT_COLOR_MODES = {COLOR_MODE_HS, COLOR_MODE_COLOR_TEMP} -DEFAULT_MIN_MIREDS = 153 -DEFAULT_MAX_MIREDS = 454 - - -def get_supported_color_modes(bulb_type: BulbType) -> set[str]: - """Flag supported features.""" - color_modes = set() - features = bulb_type.features - if features.color: - color_modes.add(COLOR_MODE_HS) - if features.color_tmp: - color_modes.add(COLOR_MODE_COLOR_TEMP) - if not color_modes and features.brightness: - color_modes.add(COLOR_MODE_BRIGHTNESS) - return color_modes - - -def get_min_max_mireds(bulb_type: BulbType) -> tuple[int, int]: - """Return the coldest and warmest color_temp that this light supports.""" - # DW bulbs have no kelvin - if bulb_type.bulb_type == BulbClass.DW: - return 0, 0 - # If bulbtype is TW or RGB then return the kelvin value - return color_temperature_kelvin_to_mired( - bulb_type.kelvin_range.max - ), color_temperature_kelvin_to_mired(bulb_type.kelvin_range.min) - async def async_setup_entry( hass: HomeAssistant, @@ -81,20 +53,35 @@ def __init__(self, wiz_data: WizData, name: str) -> None: """Initialize an WiZLight.""" super().__init__(wiz_data, name) bulb_type: BulbType = self._device.bulbtype + features: Features = bulb_type.features + color_modes = set() + if features.color: + color_modes.add(COLOR_MODE_HS) + if features.color_tmp: + color_modes.add(COLOR_MODE_COLOR_TEMP) + if not color_modes and features.brightness: + color_modes.add(COLOR_MODE_BRIGHTNESS) + self._attr_supported_color_modes = color_modes self._attr_effect_list = wiz_data.scenes - self._attr_min_mireds, self._attr_max_mireds = get_min_max_mireds(bulb_type) - self._attr_supported_color_modes = get_supported_color_modes(bulb_type) + if bulb_type.bulb_type != BulbClass.DW: + self._attr_min_mireds = color_temperature_kelvin_to_mired( + bulb_type.kelvin_range.max + ) + self._attr_max_mireds = color_temperature_kelvin_to_mired( + bulb_type.kelvin_range.min + ) if bulb_type.features.effect: self._attr_supported_features = SUPPORT_EFFECT + self._async_update_attrs() @callback - def _handle_coordinator_update(self) -> None: - """Handle updated data from the coordinator.""" + def _async_update_attrs(self) -> None: + """Handle updating _attr values.""" state = self._device.state - if (brightness := state.get_brightness()) is not None: - self._attr_brightness = max(0, min(255, brightness)) color_modes = self.supported_color_modes assert color_modes is not None + if (brightness := state.get_brightness()) is not None: + self._attr_brightness = max(0, min(255, brightness)) if COLOR_MODE_COLOR_TEMP in color_modes and state.get_colortemp() is not None: self._attr_color_mode = COLOR_MODE_COLOR_TEMP if color_temp := state.get_colortemp(): @@ -110,7 +97,7 @@ def _handle_coordinator_update(self) -> None: else: self._attr_color_mode = COLOR_MODE_BRIGHTNESS self._attr_effect = state.get_scene() - super()._handle_coordinator_update() + super()._async_update_attrs() @callback def _async_pilot_builder(self, **kwargs: Any) -> PilotBuilder: diff --git a/homeassistant/components/wiz/strings.json b/homeassistant/components/wiz/strings.json index 2195bb09a03139..288fd76acc4264 100644 --- a/homeassistant/components/wiz/strings.json +++ b/homeassistant/components/wiz/strings.json @@ -6,10 +6,15 @@ "data": { "host": "[%key:common::config_flow::data::host%]" }, - "description": "Enter the IP address of the device." + "description": "If you leave the host empty, discovery will be used to find devices." }, "discovery_confirm": { "description": "Do you want to setup {name} ({host})?" + }, + "pick_device": { + "data": { + "device": "Device" + } } }, "error": { diff --git a/homeassistant/components/wiz/switch.py b/homeassistant/components/wiz/switch.py index eae7e3d47a8827..e6e34c73c3b324 100644 --- a/homeassistant/components/wiz/switch.py +++ b/homeassistant/components/wiz/switch.py @@ -29,6 +29,11 @@ async def async_setup_entry( class WizSocketEntity(WizToggleEntity, SwitchEntity): """Representation of a WiZ socket.""" + def __init__(self, wiz_data: WizData, name: str) -> None: + """Initialize a WiZ socket.""" + super().__init__(wiz_data, name) + self._async_update_attrs() + async def async_turn_on(self, **kwargs: Any) -> None: """Instruct the socket to turn on.""" await self._device.turn_on(PilotBuilder()) diff --git a/homeassistant/components/wiz/translations/en.json b/homeassistant/components/wiz/translations/en.json index 192d1137c2f08c..c8ee3d6df2e5c2 100644 --- a/homeassistant/components/wiz/translations/en.json +++ b/homeassistant/components/wiz/translations/en.json @@ -14,11 +14,16 @@ "discovery_confirm": { "description": "Do you want to setup {name} ({host})?" }, + "pick_device": { + "data": { + "device": "Device" + } + }, "user": { "data": { "host": "Host" }, - "description": "Enter the IP address of the device." + "description": "If you leave the host empty, discovery will be used to find devices." } } } diff --git a/tests/components/wiz/test_config_flow.py b/tests/components/wiz/test_config_flow.py index a043d4a654ed88..a52ca323830c71 100644 --- a/tests/components/wiz/test_config_flow.py +++ b/tests/components/wiz/test_config_flow.py @@ -4,13 +4,12 @@ from unittest.mock import patch import pytest +from pywizlight.discovery import DiscoveredBulb +from pywizlight.exceptions import WizLightConnectionError, WizLightTimeOutError from homeassistant import config_entries from homeassistant.components import dhcp -from homeassistant.components.wiz.config_flow import ( - WizLightConnectionError, - WizLightTimeOutError, -) +from homeassistant.components.wiz.config_flow import CONF_DEVICE from homeassistant.components.wiz.const import DOMAIN from homeassistant.const import CONF_HOST from homeassistant.data_entry_flow import RESULT_TYPE_ABORT, RESULT_TYPE_FORM @@ -74,6 +73,18 @@ def _patcher(): return _patcher() +def _patch_discovery(): + @contextmanager + def _patcher(): + with patch( + "homeassistant.components.wiz.discovery.find_wizlights", + return_value=[DiscoveredBulb(FAKE_IP, FAKE_MAC)], + ): + yield + + return _patcher() + + async def test_form(hass): """Test we get the form.""" result = await hass.config_entries.flow.async_init( @@ -85,7 +96,9 @@ async def test_form(hass): with _patch_wizlight(), patch( "homeassistant.components.wiz.async_setup_entry", return_value=True, - ) as mock_setup_entry: + ) as mock_setup_entry, patch( + "homeassistant.components.wiz.async_setup", return_value=True + ) as mock_setup: result2 = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_CONNECTION, @@ -97,6 +110,7 @@ async def test_form(hass): assert result2["data"] == { CONF_HOST: "1.1.1.1", } + assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 @@ -140,10 +154,7 @@ async def test_form_updates_unique_id(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with _patch_wizlight(), patch( - "homeassistant.components.wiz.async_setup_entry", - return_value=True, - ): + with _patch_wizlight(): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_CONNECTION, @@ -226,7 +237,9 @@ async def test_discovered_by_dhcp_or_integration_discovery( with patch( "homeassistant.components.wiz.async_setup_entry", return_value=True, - ) as mock_setup_entry: + ) as mock_setup_entry, patch( + "homeassistant.components.wiz.async_setup", return_value=True + ) as mock_setup: result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {}, @@ -238,6 +251,7 @@ async def test_discovered_by_dhcp_or_integration_discovery( assert result2["data"] == { CONF_HOST: "1.1.1.1", } + assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 @@ -268,3 +282,104 @@ async def test_discovered_by_dhcp_or_integration_discovery_updates_host( assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "already_configured" assert entry.data[CONF_HOST] == FAKE_IP + + +async def test_setup_via_discovery(hass): + """Test setting up via discovery.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + await hass.async_block_till_done() + assert result["type"] == "form" + assert result["step_id"] == "user" + assert not result["errors"] + + with _patch_discovery(): + result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + await hass.async_block_till_done() + + assert result2["type"] == "form" + assert result2["step_id"] == "pick_device" + assert not result2["errors"] + + # test we can try again + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["step_id"] == "user" + assert not result["errors"] + + with _patch_discovery(): + result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + await hass.async_block_till_done() + + assert result2["type"] == "form" + assert result2["step_id"] == "pick_device" + assert not result2["errors"] + + with _patch_wizlight(), patch( + "homeassistant.components.wiz.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.wiz.async_setup_entry", return_value=True + ) as mock_setup_entry: + result3 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_DEVICE: FAKE_MAC}, + ) + await hass.async_block_till_done() + + assert result3["type"] == "create_entry" + assert result3["title"] == "WiZ Dimmable White ABCABC" + assert result3["data"] == { + CONF_HOST: "1.1.1.1", + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + # ignore configured devices + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["step_id"] == "user" + assert not result["errors"] + + with _patch_discovery(): + result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + await hass.async_block_till_done() + + assert result2["type"] == "abort" + assert result2["reason"] == "no_devices_found" + + +async def test_setup_via_discovery_cannot_connect(hass): + """Test setting up via discovery and we fail to connect to the discovered device.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + await hass.async_block_till_done() + assert result["type"] == "form" + assert result["step_id"] == "user" + assert not result["errors"] + + with _patch_discovery(): + result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + await hass.async_block_till_done() + + assert result2["type"] == "form" + assert result2["step_id"] == "pick_device" + assert not result2["errors"] + + with patch( + "homeassistant.components.wiz.wizlight.getBulbConfig", + side_effect=WizLightTimeOutError, + ), _patch_discovery(): + result3 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_DEVICE: FAKE_MAC}, + ) + await hass.async_block_till_done() + + assert result3["type"] == "abort" + assert result3["reason"] == "cannot_connect" From acb7e24852baa99f38c7314ac342f216e3ef6c05 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Sat, 5 Feb 2022 21:56:36 +0000 Subject: [PATCH 0331/1098] Reduce System Bridge load on server (#65794) --- .../components/system_bridge/coordinator.py | 24 +++++++++++-------- .../components/system_bridge/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/system_bridge/coordinator.py b/homeassistant/components/system_bridge/coordinator.py index 7610e76b7bbd68..896309f2593a52 100644 --- a/homeassistant/components/system_bridge/coordinator.py +++ b/homeassistant/components/system_bridge/coordinator.py @@ -63,16 +63,20 @@ async def _listen_for_events(self) -> None: await self.bridge.async_send_event( "get-data", [ - "battery", - "cpu", - "display", - "filesystem", - "graphics", - "memory", - "network", - "os", - "processes", - "system", + {"service": "battery", "method": "findAll", "observe": True}, + {"service": "cpu", "method": "findAll", "observe": True}, + {"service": "display", "method": "findAll", "observe": True}, + {"service": "filesystem", "method": "findSizes", "observe": True}, + {"service": "graphics", "method": "findAll", "observe": True}, + {"service": "memory", "method": "findAll", "observe": True}, + {"service": "network", "method": "findAll", "observe": True}, + {"service": "os", "method": "findAll", "observe": False}, + { + "service": "processes", + "method": "findCurrentLoad", + "observe": True, + }, + {"service": "system", "method": "findAll", "observe": False}, ], ) await self.bridge.listen_for_events(callback=self.async_handle_event) diff --git a/homeassistant/components/system_bridge/manifest.json b/homeassistant/components/system_bridge/manifest.json index 31c19e4614f66c..8fba9dd30cfd9a 100644 --- a/homeassistant/components/system_bridge/manifest.json +++ b/homeassistant/components/system_bridge/manifest.json @@ -3,7 +3,7 @@ "name": "System Bridge", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/system_bridge", - "requirements": ["systembridge==2.2.3"], + "requirements": ["systembridge==2.3.1"], "codeowners": ["@timmo001"], "zeroconf": ["_system-bridge._udp.local."], "after_dependencies": ["zeroconf"], diff --git a/requirements_all.txt b/requirements_all.txt index bfc7f30cfb2b17..88b2b92409a17f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2311,7 +2311,7 @@ swisshydrodata==0.1.0 synology-srm==0.2.0 # homeassistant.components.system_bridge -systembridge==2.2.3 +systembridge==2.3.1 # homeassistant.components.tailscale tailscale==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c81f4943114859..d9397b88cf0e02 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1423,7 +1423,7 @@ sunwatcher==0.2.1 surepy==0.7.2 # homeassistant.components.system_bridge -systembridge==2.2.3 +systembridge==2.3.1 # homeassistant.components.tailscale tailscale==0.2.0 From a6e36a6eb944ae7279e41a3cab9ed47b179b2c5e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 5 Feb 2022 23:59:37 +0100 Subject: [PATCH 0332/1098] Simplify unique ID handling in Plugwise (#65839) --- homeassistant/components/plugwise/binary_sensor.py | 3 +-- homeassistant/components/plugwise/climate.py | 2 +- homeassistant/components/plugwise/entity.py | 11 ++--------- homeassistant/components/plugwise/sensor.py | 4 ++-- homeassistant/components/plugwise/switch.py | 3 +-- 5 files changed, 7 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index 25b1578cd8a3cc..d0bfe0a2fe79d9 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -92,6 +92,7 @@ def __init__( super().__init__(api, coordinator, name, dev_id) self._binary_sensor = binary_sensor self._attr_is_on = False + self._attr_unique_id = f"{dev_id}-{binary_sensor}" if dev_id == self._api.heater_id: self._entity_name = "Auxiliary" @@ -102,8 +103,6 @@ def __init__( if dev_id == self._api.gateway_id: self._entity_name = f"Smile {self._entity_name}" - self._unique_id = f"{dev_id}-{binary_sensor}" - @callback def _async_process_data(self) -> None: """Update the entity.""" diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index e3185c0701d4c3..cba9b6a095a4a1 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -99,6 +99,7 @@ def __init__( """Set up the Plugwise API.""" super().__init__(api, coordinator, name, dev_id) self._attr_extra_state_attributes = {} + self._attr_unique_id = f"{dev_id}-climate" self._api = api self._loc_id = loc_id @@ -106,7 +107,6 @@ def __init__( self._presets = None self._single_thermostat = self._api.single_master_thermostat() - self._unique_id = f"{dev_id}-climate" async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" diff --git a/homeassistant/components/plugwise/entity.py b/homeassistant/components/plugwise/entity.py index ed9f2c61a78698..8a2526d48117a1 100644 --- a/homeassistant/components/plugwise/entity.py +++ b/homeassistant/components/plugwise/entity.py @@ -22,6 +22,8 @@ class PlugwiseEntity(CoordinatorEntity): """Represent a PlugWise Entity.""" + _model: str | None = None + def __init__( self, api: Smile, coordinator: DataUpdateCoordinator, name: str, dev_id: str ) -> None: @@ -31,17 +33,8 @@ def __init__( self._api = api self._name = name self._dev_id = dev_id - - self._unique_id: str | None = None - self._model: str | None = None - self._entity_name = self._name - @property - def unique_id(self) -> str | None: - """Return a unique ID.""" - return self._unique_id - @property def name(self) -> str | None: """Return the name of the entity, if any.""" diff --git a/homeassistant/components/plugwise/sensor.py b/homeassistant/components/plugwise/sensor.py index da09d4afa1367c..642a7a5e1e6d7f 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -313,7 +313,9 @@ def __init__( ) -> None: """Initialise the sensor.""" super().__init__(api, coordinator, name, dev_id) + self._attr_unique_id = f"{dev_id}-{sensor}" self._sensor = sensor + if dev_id == self._api.heater_id: self._entity_name = "Auxiliary" @@ -323,8 +325,6 @@ def __init__( if dev_id == self._api.gateway_id: self._entity_name = f"Smile {self._entity_name}" - self._unique_id = f"{dev_id}-{sensor}" - class PwThermostatSensor(SmileSensor): """Thermostat (or generic) sensor devices.""" diff --git a/homeassistant/components/plugwise/switch.py b/homeassistant/components/plugwise/switch.py index baefcaeb7105ce..161728a9474c2a 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -62,6 +62,7 @@ class GwSwitch(PlugwiseEntity, SwitchEntity): def __init__(self, api, coordinator, name, dev_id, members, model): """Set up the Plugwise API.""" super().__init__(api, coordinator, name, dev_id) + self._attr_unique_id = f"{dev_id}-plug" self._members = members self._model = model @@ -69,8 +70,6 @@ def __init__(self, api, coordinator, name, dev_id, members, model): self._is_on = False self._icon = SWITCH_ICON - self._unique_id = f"{dev_id}-plug" - @property def is_on(self): """Return true if device is on.""" From 131dbd6c8e04a2996714849fa929caa2d183d87b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 6 Feb 2022 00:43:05 +0100 Subject: [PATCH 0333/1098] Move Plugwise logger into constants (#65842) --- homeassistant/components/plugwise/binary_sensor.py | 7 ++----- homeassistant/components/plugwise/climate.py | 14 ++++++-------- homeassistant/components/plugwise/config_flow.py | 6 ++---- homeassistant/components/plugwise/const.py | 6 +++++- homeassistant/components/plugwise/gateway.py | 8 +++----- homeassistant/components/plugwise/sensor.py | 11 ++++------- homeassistant/components/plugwise/switch.py | 12 ++++-------- 7 files changed, 26 insertions(+), 38 deletions(-) diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index d0bfe0a2fe79d9..ca65a66b4da703 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -1,6 +1,4 @@ """Plugwise Binary Sensor component for Home Assistant.""" -import logging - from plugwise.smile import Smile from homeassistant.components.binary_sensor import BinarySensorEntity @@ -16,6 +14,7 @@ FLOW_OFF_ICON, FLOW_ON_ICON, IDLE_ICON, + LOGGER, NO_NOTIFICATION_ICON, NOTIFICATION_ICON, ) @@ -27,8 +26,6 @@ } SEVERITIES = ["other", "info", "warning", "error"] -_LOGGER = logging.getLogger(__name__) - async def async_setup_entry( hass: HomeAssistant, @@ -116,7 +113,7 @@ class PwBinarySensor(SmileBinarySensor): def _async_process_data(self) -> None: """Update the entity.""" if not (data := self._api.get_device_data(self._dev_id)): - _LOGGER.error("Received no data for device %s", self._binary_sensor) + LOGGER.error("Received no data for device %s", self._binary_sensor) self.async_write_ha_state() return diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index cba9b6a095a4a1..1063254299eeef 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -1,5 +1,4 @@ """Plugwise Climate component for Home Assistant.""" -import logging from typing import Any from plugwise.exceptions import PlugwiseException @@ -27,6 +26,7 @@ DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, DOMAIN, + LOGGER, SCHEDULE_OFF, SCHEDULE_ON, ) @@ -35,8 +35,6 @@ HVAC_MODES_HEAT_ONLY = [HVAC_MODE_HEAT, HVAC_MODE_AUTO] HVAC_MODES_HEAT_COOL = [HVAC_MODE_HEAT_COOL, HVAC_MODE_AUTO] -_LOGGER = logging.getLogger(__name__) - async def async_setup_entry( hass: HomeAssistant, @@ -119,9 +117,9 @@ async def async_set_temperature(self, **kwargs: Any) -> None: self._attr_target_temperature = temperature self.async_write_ha_state() except PlugwiseException: - _LOGGER.error("Error while communicating to device") + LOGGER.error("Error while communicating to device") else: - _LOGGER.error("Invalid temperature requested") + LOGGER.error("Invalid temperature requested") async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set the hvac mode.""" @@ -136,7 +134,7 @@ async def async_set_hvac_mode(self, hvac_mode: str) -> None: ) self._attr_target_temperature = climate_data.get("schedule_temperature") except PlugwiseException: - _LOGGER.error("Error while communicating to device") + LOGGER.error("Error while communicating to device") try: await self._api.set_schedule_state( @@ -145,7 +143,7 @@ async def async_set_hvac_mode(self, hvac_mode: str) -> None: self._attr_hvac_mode = hvac_mode self.async_write_ha_state() except PlugwiseException: - _LOGGER.error("Error while communicating to device") + LOGGER.error("Error while communicating to device") async def async_set_preset_mode(self, preset_mode: str) -> None: """Set the preset mode.""" @@ -158,7 +156,7 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: self._attr_target_temperature = self._presets.get(preset_mode, "none")[0] self.async_write_ha_state() except PlugwiseException: - _LOGGER.error("Error while communicating to device") + LOGGER.error("Error while communicating to device") @callback def _async_process_data(self) -> None: diff --git a/homeassistant/components/plugwise/config_flow.py b/homeassistant/components/plugwise/config_flow.py index e611199ccf65f8..2ce8a686c8b3ec 100644 --- a/homeassistant/components/plugwise/config_flow.py +++ b/homeassistant/components/plugwise/config_flow.py @@ -1,7 +1,6 @@ """Config flow for Plugwise integration.""" from __future__ import annotations -import logging from typing import Any from plugwise.exceptions import InvalidAuthentication, PlugwiseException @@ -29,6 +28,7 @@ DOMAIN, FLOW_SMILE, FLOW_STRETCH, + LOGGER, PW_TYPE, SMILE, STRETCH, @@ -36,8 +36,6 @@ ZEROCONF_MAP, ) -_LOGGER = logging.getLogger(__name__) - def _base_gw_schema(discovery_info): """Generate base schema for gateways.""" @@ -126,7 +124,7 @@ async def async_step_user( except PlugwiseException: errors[CONF_BASE] = "cannot_connect" except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") + LOGGER.exception("Unexpected exception") errors[CONF_BASE] = "unknown" else: await self.async_set_unique_id( diff --git a/homeassistant/components/plugwise/const.py b/homeassistant/components/plugwise/const.py index cd31255a040908..a885a047f7a905 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -1,13 +1,17 @@ """Constants for Plugwise component.""" from datetime import timedelta +import logging from homeassistant.const import Platform +DOMAIN = "plugwise" + +LOGGER = logging.getLogger(__package__) + API = "api" ATTR_ILLUMINANCE = "illuminance" COORDINATOR = "coordinator" DEVICE_STATE = "device_state" -DOMAIN = "plugwise" FLOW_SMILE = "smile (Adam/Anna/P1)" FLOW_STRETCH = "stretch (Stretch)" FLOW_TYPE = "flow_type" diff --git a/homeassistant/components/plugwise/gateway.py b/homeassistant/components/plugwise/gateway.py index 611a9153cadec4..e851941cf27519 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -2,7 +2,6 @@ from __future__ import annotations import asyncio -import logging import async_timeout from plugwise.exceptions import ( @@ -28,13 +27,12 @@ DEFAULT_USERNAME, DOMAIN, GATEWAY, + LOGGER, PLATFORMS_GATEWAY, PW_TYPE, SENSOR_PLATFORMS, ) -_LOGGER = logging.getLogger(__name__) - async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Plugwise Smiles from a config entry.""" @@ -51,7 +49,7 @@ async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: try: connected = await api.connect() except InvalidAuthentication: - _LOGGER.error("Invalid username or Smile ID") + LOGGER.error("Invalid username or Smile ID") return False except PlugwiseException as err: raise ConfigEntryNotReady( @@ -76,7 +74,7 @@ async def async_update_data(): coordinator = DataUpdateCoordinator( hass, - _LOGGER, + LOGGER, name=f"Smile {api.smile_name}", update_method=async_update_data, update_interval=DEFAULT_SCAN_INTERVAL[api.smile_type], diff --git a/homeassistant/components/plugwise/sensor.py b/homeassistant/components/plugwise/sensor.py index 642a7a5e1e6d7f..9d3f2d780b1884 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -1,8 +1,6 @@ """Plugwise Sensor component for Home Assistant.""" from __future__ import annotations -import logging - from plugwise.smile import Smile from homeassistant.components.sensor import ( @@ -31,6 +29,7 @@ DOMAIN, FLAME_ICON, IDLE_ICON, + LOGGER, SENSOR_MAP_DEVICE_CLASS, SENSOR_MAP_MODEL, SENSOR_MAP_STATE_CLASS, @@ -39,8 +38,6 @@ ) from .entity import PlugwiseEntity -_LOGGER = logging.getLogger(__name__) - ATTR_TEMPERATURE = [ "Temperature", TEMP_CELSIUS, @@ -350,7 +347,7 @@ def __init__( def _async_process_data(self) -> None: """Update the entity.""" if not (data := self._api.get_device_data(self._dev_id)): - _LOGGER.error("Received no data for device %s", self._entity_name) + LOGGER.error("Received no data for device %s", self._entity_name) self.async_write_ha_state() return @@ -382,7 +379,7 @@ def __init__( def _async_process_data(self) -> None: """Update the entity.""" if not (data := self._api.get_device_data(self._dev_id)): - _LOGGER.error("Received no data for device %s", self._entity_name) + LOGGER.error("Received no data for device %s", self._entity_name) self.async_write_ha_state() return @@ -434,7 +431,7 @@ def __init__( def _async_process_data(self) -> None: """Update the entity.""" if not (data := self._api.get_device_data(self._dev_id)): - _LOGGER.error("Received no data for device %s", self._entity_name) + LOGGER.error("Received no data for device %s", self._entity_name) self.async_write_ha_state() return diff --git a/homeassistant/components/plugwise/switch.py b/homeassistant/components/plugwise/switch.py index 161728a9474c2a..c862983119af20 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -1,6 +1,4 @@ """Plugwise Switch component for HomeAssistant.""" -import logging - from plugwise.exceptions import PlugwiseException from homeassistant.components.switch import SwitchEntity @@ -8,11 +6,9 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import COORDINATOR, DOMAIN, SWITCH_ICON +from .const import COORDINATOR, DOMAIN, LOGGER, SWITCH_ICON from .entity import PlugwiseEntity -_LOGGER = logging.getLogger(__name__) - async def async_setup_entry( hass: HomeAssistant, @@ -90,7 +86,7 @@ async def async_turn_on(self, **kwargs): self._is_on = True self.async_write_ha_state() except PlugwiseException: - _LOGGER.error("Error while communicating to device") + LOGGER.error("Error while communicating to device") async def async_turn_off(self, **kwargs): """Turn the device off.""" @@ -102,13 +98,13 @@ async def async_turn_off(self, **kwargs): self._is_on = False self.async_write_ha_state() except PlugwiseException: - _LOGGER.error("Error while communicating to device") + LOGGER.error("Error while communicating to device") @callback def _async_process_data(self): """Update the data from the Plugs.""" if not (data := self._api.get_device_data(self._dev_id)): - _LOGGER.error("Received no data for device %s", self._name) + LOGGER.error("Received no data for device %s", self._name) self.async_write_ha_state() return From f4eb7e31a4e1cb61fbaadbc6ff0f5f9f96e103c4 Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Sun, 6 Feb 2022 09:49:26 +1000 Subject: [PATCH 0334/1098] Bump Advantage Air to 0.2.6 (#65849) --- homeassistant/components/advantage_air/manifest.json | 6 ++++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/advantage_air/manifest.json b/homeassistant/components/advantage_air/manifest.json index 73de35987ec071..a230208a04e9e8 100644 --- a/homeassistant/components/advantage_air/manifest.json +++ b/homeassistant/components/advantage_air/manifest.json @@ -7,9 +7,11 @@ "@Bre77" ], "requirements": [ - "advantage_air==0.2.5" + "advantage_air==0.2.6" ], "quality_scale": "platinum", "iot_class": "local_polling", - "loggers": ["advantage_air"] + "loggers": [ + "advantage_air" + ] } \ No newline at end of file diff --git a/requirements_all.txt b/requirements_all.txt index 88b2b92409a17f..39b2f6a8378578 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -114,7 +114,7 @@ adext==0.4.2 adguardhome==0.5.1 # homeassistant.components.advantage_air -advantage_air==0.2.5 +advantage_air==0.2.6 # homeassistant.components.frontier_silicon afsapi==0.0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d9397b88cf0e02..9f89b0faa50e97 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -70,7 +70,7 @@ adext==0.4.2 adguardhome==0.5.1 # homeassistant.components.advantage_air -advantage_air==0.2.5 +advantage_air==0.2.6 # homeassistant.components.agent_dvr agent-py==0.0.23 From 1269483923a0d9cee2a4982a8363e8683820b2ca Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sun, 6 Feb 2022 01:00:42 +0100 Subject: [PATCH 0335/1098] Remove port from description (#65851) --- homeassistant/components/netgear/strings.json | 2 +- homeassistant/components/netgear/translations/en.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/netgear/strings.json b/homeassistant/components/netgear/strings.json index ce575277dad8e3..7a81d414e2f325 100644 --- a/homeassistant/components/netgear/strings.json +++ b/homeassistant/components/netgear/strings.json @@ -2,7 +2,7 @@ "config": { "step": { "user": { - "description": "Default host: {host}\nDefault port: {port}\nDefault username: {username}", + "description": "Default host: {host}\nDefault username: {username}", "data": { "host": "[%key:common::config_flow::data::host%] (Optional)", "username": "[%key:common::config_flow::data::username%] (Optional)", diff --git a/homeassistant/components/netgear/translations/en.json b/homeassistant/components/netgear/translations/en.json index f9c2dbf2c91479..42b014d9ed3a2d 100644 --- a/homeassistant/components/netgear/translations/en.json +++ b/homeassistant/components/netgear/translations/en.json @@ -15,7 +15,7 @@ "ssl": "Uses an SSL certificate", "username": "Username (Optional)" }, - "description": "Default host: {host}\nDefault port: {port}\nDefault username: {username}", + "description": "Default host: {host}\nDefault username: {username}", "title": "Netgear" } } From 2da4d280b239ea081ca3c77bcb8c642c72d86537 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 6 Feb 2022 00:17:31 +0000 Subject: [PATCH 0336/1098] [ci skip] Translation update --- .../components/adax/translations/el.json | 20 ++++++- .../components/adguard/translations/el.json | 4 ++ .../components/airvisual/translations/el.json | 12 ++++- .../airvisual/translations/sensor.el.json | 4 ++ .../components/androidtv/translations/el.json | 20 +++++-- .../components/arcam_fmj/translations/el.json | 5 ++ .../components/asuswrt/translations/el.json | 1 + .../aurora_abb_powerone/translations/el.json | 21 ++++++++ .../components/auth/translations/el.json | 10 ++++ .../azure_event_hub/translations/el.json | 43 +++++++++++++++ .../binary_sensor/translations/el.json | 11 ++++ .../components/bosch_shc/translations/el.json | 3 +- .../components/climate/translations/el.json | 1 + .../components/coinbase/translations/el.json | 1 + .../components/daikin/translations/el.json | 3 ++ .../demo/translations/select.el.json | 9 ++++ .../device_tracker/translations/el.json | 3 +- .../devolo_home_control/translations/el.json | 6 +++ .../devolo_home_network/translations/el.json | 4 ++ .../components/dexcom/translations/el.json | 18 +++++++ .../dialogflow/translations/el.json | 9 ++++ .../components/dlna_dmr/translations/el.json | 3 ++ .../components/dnsip/translations/ja.json | 4 +- .../components/dnsip/translations/nl.json | 4 +- .../components/doorbird/translations/el.json | 5 ++ .../components/dsmr/translations/el.json | 9 ++++ .../components/dunehd/translations/el.json | 10 ++++ .../components/econet/translations/el.json | 9 ++++ .../components/efergy/translations/el.json | 9 ++++ .../components/elmax/translations/el.json | 26 +++++++++ .../emulated_roku/translations/el.json | 9 ++-- .../environment_canada/translations/el.json | 19 +++++++ .../components/ezviz/translations/el.json | 10 ++++ .../faa_delays/translations/el.json | 12 +++++ .../components/flipr/translations/el.json | 20 +++++++ .../flunearyou/translations/el.json | 3 +- .../components/flux_led/translations/el.json | 25 +++++++++ .../forked_daapd/translations/el.json | 11 ++-- .../components/gios/translations/el.json | 5 ++ .../components/glances/translations/el.json | 18 +++++++ .../components/gpslogger/translations/el.json | 9 ++++ .../components/guardian/translations/el.json | 6 +++ .../home_plus_control/translations/el.json | 3 ++ .../huawei_lte/translations/el.json | 4 ++ .../components/hue/translations/el.json | 6 ++- .../components/insteon/translations/el.json | 3 ++ .../components/ipp/translations/el.json | 3 +- .../components/izone/translations/el.json | 9 ++++ .../components/juicenet/translations/el.json | 3 +- .../keenetic_ndms2/translations/el.json | 1 + .../components/knx/translations/el.json | 2 + .../components/lcn/translations/el.json | 7 +++ .../components/litejet/translations/el.json | 13 +++++ .../components/locative/translations/el.json | 5 ++ .../components/lookin/translations/el.json | 10 ++++ .../components/luftdaten/translations/el.json | 16 ++++++ .../lutron_caseta/translations/el.json | 15 +++++- .../components/mailgun/translations/el.json | 3 +- .../motion_blinds/translations/el.json | 7 +++ .../components/motioneye/translations/el.json | 5 ++ .../components/neato/translations/el.json | 3 ++ .../components/nest/translations/el.json | 7 ++- .../components/netatmo/translations/el.json | 8 +++ .../components/netgear/translations/el.json | 18 ++++++- .../nfandroidtv/translations/el.json | 10 ++++ .../nmap_tracker/translations/el.json | 3 +- .../components/nut/translations/el.json | 3 +- .../components/octoprint/translations/el.json | 7 +++ .../components/onvif/translations/el.json | 6 +++ .../open_meteo/translations/el.json | 12 +++++ .../opentherm_gw/translations/el.json | 3 +- .../components/ozw/translations/el.json | 3 ++ .../philips_js/translations/el.json | 1 + .../components/plaato/translations/el.json | 18 ++++++- .../components/plugwise/translations/el.json | 3 ++ .../components/poolsense/translations/el.json | 9 ++++ .../components/powerwall/translations/ja.json | 12 +++++ .../components/powerwall/translations/nl.json | 12 +++++ .../pvpc_hourly_pricing/translations/el.json | 15 ++++++ .../components/rachio/translations/el.json | 9 ++++ .../components/rfxtrx/translations/el.json | 3 +- .../components/roku/translations/el.json | 4 ++ .../components/roomba/translations/el.json | 19 +++++++ .../components/select/translations/el.json | 14 +++++ .../components/sense/translations/el.json | 3 ++ .../components/sensor/translations/el.json | 11 ++++ .../components/shelly/translations/el.json | 3 ++ .../simplisafe/translations/el.json | 12 ++++- .../smartthings/translations/el.json | 3 ++ .../components/solarlog/translations/el.json | 12 +++++ .../somfy_mylink/translations/el.json | 31 ++++++++++- .../squeezebox/translations/el.json | 11 ++++ .../stookalert/translations/el.json | 11 ++++ .../components/subaru/translations/el.json | 3 +- .../components/syncthing/translations/el.json | 3 +- .../synology_dsm/translations/el.json | 3 +- .../components/tado/translations/el.json | 3 ++ .../components/tag/translations/el.json | 3 ++ .../components/tibber/translations/el.json | 3 ++ .../components/tile/translations/el.json | 7 +++ .../totalconnect/translations/el.json | 10 +++- .../components/tradfri/translations/el.json | 1 + .../transmission/translations/el.json | 1 + .../components/tuya/translations/el.json | 6 +++ .../tuya/translations/select.ca.json | 35 ++++++++++++ .../tuya/translations/select.de.json | 9 ++++ .../tuya/translations/select.el.json | 27 ++++++++++ .../tuya/translations/select.en.json | 35 ++++++++++++ .../tuya/translations/select.pt-BR.json | 35 ++++++++++++ .../tuya/translations/sensor.ca.json | 6 +++ .../tuya/translations/sensor.de.json | 6 +++ .../tuya/translations/sensor.el.json | 18 +++++++ .../tuya/translations/sensor.en.json | 6 +++ .../tuya/translations/sensor.pt-BR.json | 6 +++ .../components/twilio/translations/el.json | 8 +++ .../components/twinkly/translations/el.json | 3 ++ .../components/upnp/translations/el.json | 3 ++ .../uptimerobot/translations/el.json | 6 +++ .../uptimerobot/translations/sensor.el.json | 1 + .../components/venstar/translations/el.json | 9 ++++ .../components/vera/translations/el.json | 3 ++ .../components/vicare/translations/el.json | 4 +- .../components/vizio/translations/el.json | 13 +++-- .../vlc_telnet/translations/el.json | 13 +++++ .../components/watttime/translations/el.json | 13 +++++ .../waze_travel_time/translations/el.json | 12 ++++- .../components/wemo/translations/el.json | 5 ++ .../components/wiz/translations/ca.json | 35 ++++++++++++ .../components/wiz/translations/de.json | 30 +++++++++++ .../components/wiz/translations/el.json | 13 +++++ .../components/wiz/translations/en.json | 9 +++- .../components/wiz/translations/et.json | 26 +++++++++ .../components/wiz/translations/ja.json | 26 +++++++++ .../components/wiz/translations/nl.json | 26 +++++++++ .../components/wiz/translations/pt-BR.json | 35 ++++++++++++ .../components/wiz/translations/zh-Hant.json | 26 +++++++++ .../components/wled/translations/el.json | 1 + .../wolflink/translations/sensor.el.json | 49 +++++++++++++++++ .../xiaomi_miio/translations/el.json | 54 ++++++++++++++++++- .../yale_smart_alarm/translations/el.json | 5 ++ .../translations/select.el.json | 10 ++++ .../components/zha/translations/el.json | 1 + .../components/zwave_js/translations/el.json | 29 ++++++++-- 143 files changed, 1490 insertions(+), 52 deletions(-) create mode 100644 homeassistant/components/aurora_abb_powerone/translations/el.json create mode 100644 homeassistant/components/azure_event_hub/translations/el.json create mode 100644 homeassistant/components/demo/translations/select.el.json create mode 100644 homeassistant/components/dunehd/translations/el.json create mode 100644 homeassistant/components/econet/translations/el.json create mode 100644 homeassistant/components/efergy/translations/el.json create mode 100644 homeassistant/components/elmax/translations/el.json create mode 100644 homeassistant/components/environment_canada/translations/el.json create mode 100644 homeassistant/components/flipr/translations/el.json create mode 100644 homeassistant/components/flux_led/translations/el.json create mode 100644 homeassistant/components/home_plus_control/translations/el.json create mode 100644 homeassistant/components/izone/translations/el.json create mode 100644 homeassistant/components/lcn/translations/el.json create mode 100644 homeassistant/components/lookin/translations/el.json create mode 100644 homeassistant/components/luftdaten/translations/el.json create mode 100644 homeassistant/components/neato/translations/el.json create mode 100644 homeassistant/components/nfandroidtv/translations/el.json create mode 100644 homeassistant/components/octoprint/translations/el.json create mode 100644 homeassistant/components/open_meteo/translations/el.json create mode 100644 homeassistant/components/poolsense/translations/el.json create mode 100644 homeassistant/components/select/translations/el.json create mode 100644 homeassistant/components/solarlog/translations/el.json create mode 100644 homeassistant/components/squeezebox/translations/el.json create mode 100644 homeassistant/components/stookalert/translations/el.json create mode 100644 homeassistant/components/tag/translations/el.json create mode 100644 homeassistant/components/tuya/translations/sensor.el.json create mode 100644 homeassistant/components/venstar/translations/el.json create mode 100644 homeassistant/components/vlc_telnet/translations/el.json create mode 100644 homeassistant/components/wiz/translations/ca.json create mode 100644 homeassistant/components/wiz/translations/de.json create mode 100644 homeassistant/components/wiz/translations/el.json create mode 100644 homeassistant/components/wiz/translations/et.json create mode 100644 homeassistant/components/wiz/translations/ja.json create mode 100644 homeassistant/components/wiz/translations/nl.json create mode 100644 homeassistant/components/wiz/translations/pt-BR.json create mode 100644 homeassistant/components/wiz/translations/zh-Hant.json create mode 100644 homeassistant/components/yamaha_musiccast/translations/select.el.json diff --git a/homeassistant/components/adax/translations/el.json b/homeassistant/components/adax/translations/el.json index 14a2f350f88d4b..024d61ad8d49bd 100644 --- a/homeassistant/components/adax/translations/el.json +++ b/homeassistant/components/adax/translations/el.json @@ -1,10 +1,28 @@ { "config": { + "abort": { + "heater_not_available": "\u039f \u03b8\u03b5\u03c1\u03bc\u03b1\u03bd\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03bf\u03c2. \u03a0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b1\u03c6\u03ad\u03c1\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b8\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7 \u03c0\u03b1\u03c4\u03ce\u03bd\u03c4\u03b1\u03c2 + \u03ba\u03b1\u03b9 OK \u03b3\u03b9\u03b1 \u03bc\u03b5\u03c1\u03b9\u03ba\u03ac \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1.", + "heater_not_found": "\u039f \u03b8\u03b5\u03c1\u03bc\u03b1\u03bd\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5. \u03a0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03bc\u03b5\u03c4\u03b1\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b8\u03b5\u03c1\u03bc\u03b1\u03bd\u03c4\u03ae\u03c1\u03b1 \u03c0\u03b9\u03bf \u03ba\u03bf\u03bd\u03c4\u03ac \u03c3\u03c4\u03bf\u03bd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae Home Assistant." + }, "step": { - "user": { + "cloud": { "data": { "account_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd" } + }, + "local": { + "data": { + "wifi_pswd": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 Wi-Fi", + "wifi_ssid": "Wi-Fi SSID" + }, + "description": "\u0395\u03c0\u03b1\u03bd\u03b1\u03c6\u03ad\u03c1\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b8\u03b5\u03c1\u03bc\u03b1\u03bd\u03c4\u03ae\u03c1\u03b1 \u03c0\u03b1\u03c4\u03ce\u03bd\u03c4\u03b1\u03c2 + \u03ba\u03b1\u03b9 OK \u03bc\u03ad\u03c7\u03c1\u03b9 \u03bd\u03b1 \u03b5\u03bc\u03c6\u03b1\u03bd\u03b9\u03c3\u03c4\u03b5\u03af \u03c3\u03c4\u03b7\u03bd \u03bf\u03b8\u03cc\u03bd\u03b7 \u03b7 \u03ad\u03bd\u03b4\u03b5\u03b9\u03be\u03b7 \"Reset\" (\u0395\u03c0\u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac). \u03a3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 \u03ba\u03b1\u03b9 \u03ba\u03c1\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c0\u03b1\u03c4\u03b7\u03bc\u03ad\u03bd\u03bf \u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af OK \u03c3\u03c4\u03b7 \u03b8\u03b5\u03c1\u03bc\u03ac\u03c3\u03c4\u03c1\u03b1 \u03bc\u03ad\u03c7\u03c1\u03b9 \u03bd\u03b1 \u03b1\u03c1\u03c7\u03af\u03c3\u03b5\u03b9 \u03bd\u03b1 \u03b1\u03bd\u03b1\u03b2\u03bf\u03c3\u03b2\u03ae\u03bd\u03b5\u03b9 \u03c4\u03bf \u03bc\u03c0\u03bb\u03b5 led \u03c0\u03c1\u03b9\u03bd \u03c0\u03b1\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03a5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae. \u0397 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b8\u03b5\u03c1\u03bc\u03ac\u03c3\u03c4\u03c1\u03b1\u03c2 \u03b5\u03bd\u03b4\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03c1\u03ba\u03ad\u03c3\u03b5\u03b9 \u03bc\u03b5\u03c1\u03b9\u03ba\u03ac \u03bb\u03b5\u03c0\u03c4\u03ac." + }, + "user": { + "data": { + "account_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd", + "connection_type": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c4\u03cd\u03c0\u03bf\u03c5 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03cd\u03c0\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2. \u03a4\u03bf\u03c0\u03b9\u03ba\u03ae \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03b8\u03b5\u03c1\u03bc\u03ac\u03c3\u03c4\u03c1\u03b5\u03c2 \u03bc\u03b5 bluetooth" } } } diff --git a/homeassistant/components/adguard/translations/el.json b/homeassistant/components/adguard/translations/el.json index 1ab13f21d962ca..619dcfa915356a 100644 --- a/homeassistant/components/adguard/translations/el.json +++ b/homeassistant/components/adguard/translations/el.json @@ -12,6 +12,10 @@ "title": "AdGuard Home \u03bc\u03ad\u03c3\u03c9 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Home Assistant" }, "user": { + "data": { + "host": "\u0394\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" + }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf AdGuard Home \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c8\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf." } } diff --git a/homeassistant/components/airvisual/translations/el.json b/homeassistant/components/airvisual/translations/el.json index 58431a4d313a20..6fae2369fd3e87 100644 --- a/homeassistant/components/airvisual/translations/el.json +++ b/homeassistant/components/airvisual/translations/el.json @@ -4,15 +4,23 @@ "already_configured": "\u0397 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af \u03ae \u03c4\u03bf Node/Pro ID \u03b5\u03af\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03ba\u03b1\u03c4\u03b1\u03c7\u03c9\u03c1\u03b7\u03bc\u03ad\u03bd\u03bf." }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_api_key": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API", + "location_not_found": "\u0397 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5" }, "step": { + "geography_by_coords": { + "description": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf AirVisual cloud API \u03b3\u03b9\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03bf\u03cd \u03c0\u03bb\u03ac\u03c4\u03bf\u03c5\u03c2/\u03bc\u03ae\u03ba\u03bf\u03c5\u03c2.", + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03af\u03b1\u03c2" + }, "geography_by_name": { "data": { + "city": "\u03a0\u03cc\u03bb\u03b7", "country": "\u03a7\u03ce\u03c1\u03b1", "state": "\u03ba\u03c1\u03ac\u03c4\u03bf\u03c2" }, - "description": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf AirVisual cloud API \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03b5\u03af\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03cc\u03bb\u03b7/\u03c0\u03bf\u03bb\u03b9\u03c4\u03b5\u03af\u03b1/\u03c7\u03ce\u03c1\u03b1." + "description": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf AirVisual cloud API \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03b5\u03af\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03cc\u03bb\u03b7/\u03c0\u03bf\u03bb\u03b9\u03c4\u03b5\u03af\u03b1/\u03c7\u03ce\u03c1\u03b1.", + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03af\u03b1\u03c2" }, "node_pro": { "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c9\u03c0\u03b9\u03ba\u03ae \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 AirVisual. \u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b1\u03bd\u03b1\u03ba\u03c4\u03b7\u03b8\u03b5\u03af \u03b1\u03c0\u03cc \u03c4\u03bf UI \u03c4\u03b7\u03c2 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1\u03c2.", diff --git a/homeassistant/components/airvisual/translations/sensor.el.json b/homeassistant/components/airvisual/translations/sensor.el.json index 4bb3fc0fc0962e..5367a2e0c869ff 100644 --- a/homeassistant/components/airvisual/translations/sensor.el.json +++ b/homeassistant/components/airvisual/translations/sensor.el.json @@ -10,6 +10,10 @@ }, "airvisual__pollutant_level": { "good": "\u039a\u03b1\u03bb\u03cc", + "hazardous": "\u0395\u03c0\u03b9\u03ba\u03af\u03bd\u03b4\u03c5\u03bd\u03bf", + "moderate": "\u039c\u03ad\u03c4\u03c1\u03b9\u03bf", + "unhealthy": "\u0391\u03bd\u03b8\u03c5\u03b3\u03b9\u03b5\u03b9\u03bd\u03cc", + "unhealthy_sensitive": "\u0391\u03bd\u03b8\u03c5\u03b3\u03b9\u03b5\u03b9\u03bd\u03cc \u03b3\u03b9\u03b1 \u03b5\u03c5\u03b1\u03af\u03c3\u03b8\u03b7\u03c4\u03b5\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b5\u03c2", "very_unhealthy": "\u03a0\u03bf\u03bb\u03cd \u03b1\u03bd\u03b8\u03c5\u03b3\u03b9\u03b5\u03b9\u03bd\u03cc" } } diff --git a/homeassistant/components/androidtv/translations/el.json b/homeassistant/components/androidtv/translations/el.json index 20c22238c1244c..c294fa7c86b9fe 100644 --- a/homeassistant/components/androidtv/translations/el.json +++ b/homeassistant/components/androidtv/translations/el.json @@ -28,17 +28,31 @@ "apps": { "data": { "app_delete": "\u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c8\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae", - "app_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2" - } + "app_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2", + "app_name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2" + }, + "description": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03bf\u03cd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2 {app_id}", + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ce\u03bd Android TV" }, "init": { + "data": { + "apps": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03bb\u03af\u03c3\u03c4\u03b1\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ce\u03bd", + "exclude_unnamed_apps": "\u0395\u03be\u03b1\u03af\u03c1\u03b5\u03c3\u03b7 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ce\u03bd \u03bc\u03b5 \u03ac\u03b3\u03bd\u03c9\u03c3\u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03bb\u03af\u03c3\u03c4\u03b1 \u03c0\u03b7\u03b3\u03ce\u03bd", + "get_sources": "\u0391\u03bd\u03ac\u03ba\u03c4\u03b7\u03c3\u03b7 \u03c4\u03c9\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ce\u03bd \u03c0\u03bf\u03c5 \u03b5\u03ba\u03c4\u03b5\u03bb\u03bf\u03cd\u03bd\u03c4\u03b1\u03b9 \u03c9\u03c2 \u03bb\u03af\u03c3\u03c4\u03b1 \u03c0\u03b7\u03b3\u03ce\u03bd", + "screencap": "\u03a7\u03c1\u03ae\u03c3\u03b7 \u03c3\u03cd\u03bb\u03bb\u03b7\u03c8\u03b7\u03c2 \u03bf\u03b8\u03cc\u03bd\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf \u03b5\u03be\u03ce\u03c6\u03c5\u03bb\u03bb\u03bf \u03c4\u03bf\u03c5 \u03ac\u03bb\u03bc\u03c0\u03bf\u03c5\u03bc", + "state_detection_rules": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03ba\u03b1\u03bd\u03cc\u03bd\u03c9\u03bd \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7\u03c2 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2", + "turn_off_command": "\u0395\u03bd\u03c4\u03bf\u03bb\u03ae \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2 \u03ba\u03b5\u03bb\u03cd\u03c6\u03bf\u03c5\u03c2 ADB (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae)", + "turn_on_command": "\u0395\u03bd\u03c4\u03bf\u03bb\u03ae \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03ba\u03b5\u03bb\u03cd\u03c6\u03bf\u03c5\u03c2 ADB (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae)" + }, "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 Android TV" }, "rules": { "data": { "rule_delete": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c8\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03ba\u03b1\u03bd\u03cc\u03bd\u03b1", - "rule_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2" + "rule_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2", + "rule_values": "\u039b\u03af\u03c3\u03c4\u03b1 \u03ba\u03b1\u03bd\u03cc\u03bd\u03c9\u03bd \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7\u03c2 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2 (\u03b2\u03bb. \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7)" }, + "description": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03ba\u03b1\u03bd\u03cc\u03bd\u03b1 \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2 {rule_id}", "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03ba\u03b1\u03bd\u03cc\u03bd\u03c9\u03bd \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7\u03c2 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2 Android TV" } } diff --git a/homeassistant/components/arcam_fmj/translations/el.json b/homeassistant/components/arcam_fmj/translations/el.json index 350f7f9865cbbb..789906c36e430a 100644 --- a/homeassistant/components/arcam_fmj/translations/el.json +++ b/homeassistant/components/arcam_fmj/translations/el.json @@ -9,5 +9,10 @@ "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2." } } + }, + "device_automation": { + "trigger_type": { + "turn_on": "\u0396\u03b7\u03c4\u03ae\u03b8\u03b7\u03ba\u03b5 \u03b1\u03c0\u03cc {entity_name} \u03bd\u03b1 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af" + } } } \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/el.json b/homeassistant/components/asuswrt/translations/el.json index e2b2ea2b4b0893..0b58b36014ac03 100644 --- a/homeassistant/components/asuswrt/translations/el.json +++ b/homeassistant/components/asuswrt/translations/el.json @@ -8,6 +8,7 @@ "step": { "user": { "data": { + "protocol": "\u03a0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf \u03b5\u03c0\u03b9\u03ba\u03bf\u03b9\u03bd\u03c9\u03bd\u03af\u03b1\u03c2 \u03c0\u03c1\u03bf\u03c2 \u03c7\u03c1\u03ae\u03c3\u03b7", "ssh_key": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd SSH (\u03b1\u03bd\u03c4\u03af \u03c4\u03bf\u03c5 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2)" }, "description": "\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7 \u03c0\u03b1\u03c1\u03ac\u03bc\u03b5\u03c4\u03c1\u03bf \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2", diff --git a/homeassistant/components/aurora_abb_powerone/translations/el.json b/homeassistant/components/aurora_abb_powerone/translations/el.json new file mode 100644 index 00000000000000..eec0a7cb3831d4 --- /dev/null +++ b/homeassistant/components/aurora_abb_powerone/translations/el.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "no_serial_ports": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03b8\u03cd\u03c1\u03b5\u03c2 com. \u03a7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae RS485 \u03b3\u03b9\u03b1 \u03b5\u03c0\u03b9\u03ba\u03bf\u03b9\u03bd\u03c9\u03bd\u03af\u03b1." + }, + "error": { + "cannot_connect": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7, \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1, \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7, \u03c4\u03b7\u03bd \u03b7\u03bb\u03b5\u03ba\u03c4\u03c1\u03b9\u03ba\u03ae \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03cc\u03c4\u03b9 \u03bf \u03bc\u03b5\u03c4\u03b1\u03c4\u03c1\u03bf\u03c0\u03ad\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf\u03c2 (\u03c3\u03c4\u03bf \u03c6\u03c9\u03c2 \u03c4\u03b7\u03c2 \u03b7\u03bc\u03ad\u03c1\u03b1\u03c2)", + "cannot_open_serial_port": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc \u03c4\u03bf \u03ac\u03bd\u03bf\u03b9\u03b3\u03bc\u03b1 \u03c4\u03b7\u03c2 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae\u03c2 \u03b8\u03cd\u03c1\u03b1\u03c2, \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac", + "invalid_serial_port": "\u0397 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ae \u03b4\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc \u03bd\u03b1 \u03b1\u03bd\u03bf\u03af\u03be\u03b5\u03b9" + }, + "step": { + "user": { + "data": { + "address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03bc\u03b5\u03c4\u03b1\u03c4\u03c1\u03bf\u03c0\u03ad\u03b1", + "port": "\u0398\u03cd\u03c1\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b1\u03c1\u03bc\u03bf\u03b3\u03ad\u03b1 RS485 \u03ae USB-RS485" + }, + "description": "\u039f \u03bc\u03b5\u03c4\u03b1\u03c4\u03c1\u03bf\u03c0\u03ad\u03b1\u03c2 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03bc\u03ad\u03c3\u03c9 \u03b5\u03bd\u03cc\u03c2 \u03c0\u03c1\u03bf\u03c3\u03b1\u03c1\u03bc\u03bf\u03b3\u03ad\u03b1 RS485, \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1 \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03bc\u03b5\u03c4\u03b1\u03c4\u03c1\u03bf\u03c0\u03ad\u03b1, \u03cc\u03c0\u03c9\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af \u03c3\u03c4\u03bf\u03bd \u03c0\u03af\u03bd\u03b1\u03ba\u03b1 LCD." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/auth/translations/el.json b/homeassistant/components/auth/translations/el.json index 3eda86b60cb6bf..6b838983637e09 100644 --- a/homeassistant/components/auth/translations/el.json +++ b/homeassistant/components/auth/translations/el.json @@ -1,7 +1,17 @@ { "mfa_setup": { "notify": { + "abort": { + "no_available_service": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03bd \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b5\u03c2 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b5\u03c2 \u03b5\u03b9\u03b4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c9\u03bd." + }, + "error": { + "invalid_code": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2, \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac." + }, "step": { + "init": { + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b9\u03c2 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b5\u03c2 \u03b5\u03b9\u03b4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c9\u03bd:", + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03bc\u03af\u03b1\u03c2 \u03c7\u03c1\u03ae\u03c3\u03b7\u03c2 \u03c0\u03bf\u03c5 \u03c0\u03b1\u03c1\u03b1\u03b4\u03af\u03b4\u03b5\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf \u03b5\u03b9\u03b4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2" + }, "setup": { "description": "\u0388\u03bd\u03b1\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03bc\u03b9\u03b1\u03c2 \u03c7\u03c1\u03ae\u03c3\u03b7\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03c3\u03c4\u03b1\u03bb\u03b5\u03af \u03bc\u03ad\u03c3\u03c9 **notify.{notify_service}**. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03ad \u03c4\u03bf\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9:", "title": "\u0395\u03c0\u03b1\u03bb\u03ae\u03b8\u03b5\u03c5\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7\u03c2" diff --git a/homeassistant/components/azure_event_hub/translations/el.json b/homeassistant/components/azure_event_hub/translations/el.json new file mode 100644 index 00000000000000..c0011b9e240190 --- /dev/null +++ b/homeassistant/components/azure_event_hub/translations/el.json @@ -0,0 +1,43 @@ +{ + "config": { + "abort": { + "cannot_connect": "\u0397 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03b1\u03c0\u03cc \u03c4\u03bf configuration.yaml \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5, \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03c6\u03b1\u03b9\u03c1\u03ad\u03c3\u03c4\u03b5 \u03b1\u03c0\u03cc \u03c4\u03bf yaml \u03ba\u03b1\u03b9 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03bf\u03ae \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c9\u03bd.", + "unknown": "\u0397 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03b1\u03c0\u03cc \u03c4\u03bf configuration.yaml \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03bc\u03b5 \u03ac\u03b3\u03bd\u03c9\u03c3\u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1, \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03c6\u03b1\u03b9\u03c1\u03ad\u03c3\u03c4\u03b5 \u03c4\u03bf yaml \u03ba\u03b1\u03b9 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03bf\u03ae config." + }, + "step": { + "conn_string": { + "data": { + "event_hub_connection_string": "\u03a3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 Event Hub" + }, + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1: {event_hub_instance_name}", + "title": "\u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2 \u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "sas": { + "data": { + "event_hub_namespace": "\u03a7\u03ce\u03c1\u03bf\u03c2 \u03bf\u03bd\u03bf\u03bc\u03ac\u03c4\u03c9\u03bd Event Hub", + "event_hub_sas_key": "Event Hub SAS Key", + "event_hub_sas_policy": "\u03a0\u03bf\u03bb\u03b9\u03c4\u03b9\u03ba\u03ae \u03c4\u03bf\u03c5 Event Hub SAS" + }, + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 SAS (\u03c5\u03c0\u03bf\u03b3\u03c1\u03b1\u03c6\u03ae \u03ba\u03bf\u03b9\u03bd\u03ae\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2) \u03b3\u03b9\u03b1: {event_hub_instance_name}", + "title": "\u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03b7\u03c1\u03af\u03c9\u03bd SAS" + }, + "user": { + "data": { + "event_hub_instance_name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1\u03c2 Event Hub", + "use_connection_string": "\u03a7\u03c1\u03ae\u03c3\u03b7 \u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 Azure Event Hub" + } + } + }, + "options": { + "step": { + "options": { + "data": { + "send_interval": "\u03a7\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03b4\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03c4\u03b7\u03c2 \u03b1\u03c0\u03bf\u03c3\u03c4\u03bf\u03bb\u03ae\u03c2 \u03c0\u03b1\u03c1\u03c4\u03af\u03b4\u03c9\u03bd \u03c3\u03c4\u03bf\u03bd \u03ba\u03cc\u03bc\u03b2\u03bf." + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf Azure Event Hub." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/binary_sensor/translations/el.json b/homeassistant/components/binary_sensor/translations/el.json index 8ad46475a7148e..9c1644382e5941 100644 --- a/homeassistant/components/binary_sensor/translations/el.json +++ b/homeassistant/components/binary_sensor/translations/el.json @@ -7,6 +7,7 @@ "is_moist": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03c5\u03b3\u03c1\u03cc", "is_motion": "{entity_name} \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03ba\u03af\u03bd\u03b7\u03c3\u03b7", "is_moving": "{entity_name} \u03ba\u03b9\u03bd\u03b5\u03af\u03c4\u03b1\u03b9", + "is_no_co": "{entity_name} \u03b4\u03b5\u03bd \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03bc\u03bf\u03bd\u03bf\u03be\u03b5\u03af\u03b4\u03b9\u03bf \u03c4\u03bf\u03c5 \u03ac\u03bd\u03b8\u03c1\u03b1\u03ba\u03b1", "is_no_gas": "{entity_name} \u03b4\u03b5\u03bd \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03b1\u03ad\u03c1\u03b9\u03bf", "is_no_light": "{entity_name} \u03b4\u03b5\u03bd \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03c6\u03c9\u03c2", "is_no_motion": "{entity_name} \u03b4\u03b5\u03bd \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03ba\u03af\u03bd\u03b7\u03c3\u03b7", @@ -26,6 +27,7 @@ "is_not_open": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03ba\u03bb\u03b5\u03b9\u03c3\u03c4\u03cc", "is_not_powered": "{entity_name} \u03b4\u03b5\u03bd \u03c4\u03c1\u03bf\u03c6\u03bf\u03b4\u03bf\u03c4\u03b5\u03af\u03c4\u03b1\u03b9", "is_not_present": "{entity_name} \u03b4\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9", + "is_not_running": "{entity_name} \u03b4\u03b5\u03bd \u03b5\u03ba\u03c4\u03b5\u03bb\u03b5\u03af\u03c4\u03b1\u03b9", "is_not_unsafe": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c3\u03c6\u03b1\u03bb\u03ad\u03c2", "is_occupied": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03ba\u03b1\u03c4\u03b5\u03b9\u03bb\u03b7\u03bc\u03bc\u03ad\u03bd\u03bf", "is_off": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf", @@ -35,6 +37,7 @@ "is_powered": "{entity_name} \u03c4\u03c1\u03bf\u03c6\u03bf\u03b4\u03bf\u03c4\u03b5\u03af\u03c4\u03b1\u03b9", "is_present": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03b1\u03c1\u03cc\u03bd", "is_problem": "{entity_name} \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03b6\u03b5\u03b9 \u03c0\u03c1\u03cc\u03b2\u03bb\u03b7\u03bc\u03b1", + "is_running": "{entity_name} \u03b5\u03ba\u03c4\u03b5\u03bb\u03b5\u03af\u03c4\u03b1\u03b9", "is_smoke": "{entity_name} \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03ba\u03b1\u03c0\u03bd\u03cc", "is_sound": "{entity_name} \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03ae\u03c7\u03bf", "is_unsafe": "{entity_name} \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c3\u03c6\u03b1\u03bb\u03ad\u03c2", @@ -43,6 +46,7 @@ }, "trigger_type": { "bat_low": "{entity_name} \u03bc\u03c0\u03b1\u03c4\u03b1\u03c1\u03af\u03b1 \u03c7\u03b1\u03bc\u03b7\u03bb\u03ae", + "co": "{entity_name} \u03ac\u03c1\u03c7\u03b9\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03bc\u03bf\u03bd\u03bf\u03be\u03b5\u03af\u03b4\u03b9\u03bf \u03c4\u03bf\u03c5 \u03ac\u03bd\u03b8\u03c1\u03b1\u03ba\u03b1", "cold": "{entity_name} \u03ba\u03c1\u03cd\u03c9\u03c3\u03b5", "connected": "{entity_name} \u03c3\u03c5\u03bd\u03b4\u03ad\u03b8\u03b7\u03ba\u03b5", "hot": "{entity_name} \u03b6\u03b5\u03c3\u03c4\u03ac\u03b8\u03b7\u03ba\u03b5", @@ -51,6 +55,7 @@ "moist": "{entity_name} \u03ad\u03b3\u03b9\u03bd\u03b5 \u03c5\u03b3\u03c1\u03cc", "motion": "{entity_name} \u03ac\u03c1\u03c7\u03b9\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03ba\u03af\u03bd\u03b7\u03c3\u03b7", "moving": "{entity_name} \u03ac\u03c1\u03c7\u03b9\u03c3\u03b5 \u03bd\u03b1 \u03ba\u03b9\u03bd\u03b5\u03af\u03c4\u03b1\u03b9", + "no_co": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03bc\u03bf\u03bd\u03bf\u03be\u03b5\u03af\u03b4\u03b9\u03bf \u03c4\u03bf\u03c5 \u03ac\u03bd\u03b8\u03c1\u03b1\u03ba\u03b1", "no_gas": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03b1\u03ad\u03c1\u03b9\u03bf", "no_light": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03c6\u03c9\u03c2", "no_motion": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03ba\u03af\u03bd\u03b7\u03c3\u03b7", @@ -59,7 +64,9 @@ "no_sound": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03ae\u03c7\u03bf", "no_update": "{entity_name} \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5", "not_opened": "{entity_name} \u03ad\u03ba\u03bb\u03b5\u03b9\u03c3\u03b5", + "not_running": "{entity_name} \u03b4\u03b5\u03bd \u03b5\u03ba\u03c4\u03b5\u03bb\u03b5\u03af\u03c4\u03b1\u03b9 \u03c0\u03bb\u03ad\u03bf\u03bd", "not_tampered": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03b6\u03b5\u03b9 \u03c0\u03b1\u03c1\u03b1\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", + "running": "{entity_name} \u03ac\u03c1\u03c7\u03b9\u03c3\u03b5 \u03bd\u03b1 \u03b5\u03ba\u03c4\u03b5\u03bb\u03b5\u03af\u03c4\u03b1\u03b9", "tampered": "{entity_name} \u03ac\u03c1\u03c7\u03b9\u03c3\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03b6\u03b5\u03b9 \u03c0\u03b1\u03c1\u03b1\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", "turned_off": "{entity_name} \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", "turned_on": "{entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", @@ -161,6 +168,10 @@ "off": "\u0395\u03bd\u03c4\u03ac\u03be\u03b5\u03b9", "on": "\u03a0\u03c1\u03cc\u03b2\u03bb\u03b7\u03bc\u03b1" }, + "running": { + "off": "\u0394\u03b5\u03bd \u03b5\u03ba\u03c4\u03b5\u03bb\u03b5\u03af\u03c4\u03b1\u03b9", + "on": "\u0395\u03ba\u03c4\u03b5\u03bb\u03ad\u03b9\u03c4\u03b1\u03b9" + }, "safety": { "off": "\u0391\u03c3\u03c6\u03b1\u03bb\u03ae\u03c2", "on": "\u0391\u03bd\u03b1\u03c3\u03c6\u03b1\u03bb\u03ae\u03c2" diff --git a/homeassistant/components/bosch_shc/translations/el.json b/homeassistant/components/bosch_shc/translations/el.json index 08d797fd113dbc..46fecc87e2c0f5 100644 --- a/homeassistant/components/bosch_shc/translations/el.json +++ b/homeassistant/components/bosch_shc/translations/el.json @@ -1,7 +1,8 @@ { "config": { "error": { - "pairing_failed": "\u0397 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5. \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03c4\u03bf Bosch Smart Home Controller \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03c3\u03b5 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7\u03c2 (\u03c4\u03bf LED \u03b1\u03bd\u03b1\u03b2\u03bf\u03c3\u03b2\u03ae\u03bd\u03b5\u03b9) \u03ba\u03b1\u03b8\u03ce\u03c2 \u03ba\u03b1\u03b9 \u03cc\u03c4\u03b9 \u03bf \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c9\u03c3\u03c4\u03cc\u03c2." + "pairing_failed": "\u0397 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5. \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03c4\u03bf Bosch Smart Home Controller \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03c3\u03b5 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7\u03c2 (\u03c4\u03bf LED \u03b1\u03bd\u03b1\u03b2\u03bf\u03c3\u03b2\u03ae\u03bd\u03b5\u03b9) \u03ba\u03b1\u03b8\u03ce\u03c2 \u03ba\u03b1\u03b9 \u03cc\u03c4\u03b9 \u03bf \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c9\u03c3\u03c4\u03cc\u03c2.", + "session_error": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03c3\u03c5\u03bd\u03b5\u03b4\u03c1\u03af\u03b1\u03c2: \u039c\u03b7 \u039f\u039a \u03b1\u03c0\u03bf\u03c4\u03ad\u03bb\u03b5\u03c3\u03bc\u03b1." }, "flow_title": "Bosch SHC: {name}", "step": { diff --git a/homeassistant/components/climate/translations/el.json b/homeassistant/components/climate/translations/el.json index 5f5ef326485efe..a7aa5386417499 100644 --- a/homeassistant/components/climate/translations/el.json +++ b/homeassistant/components/climate/translations/el.json @@ -5,6 +5,7 @@ "set_preset_mode": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ae \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae\u03c2 \u03c3\u03c4\u03bf {entity_name}" }, "condition_type": { + "is_hvac_mode": "{entity_name} \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af \u03c3\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03b3\u03ba\u03b5\u03ba\u03c1\u03b9\u03bc\u03ad\u03bd\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 HVAC", "is_preset_mode": "{entity_name} \u03ad\u03c7\u03b5\u03b9 \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03c3\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03b3\u03ba\u03b5\u03ba\u03c1\u03b9\u03bc\u03ad\u03bd\u03b7 \u03c0\u03c1\u03bf\u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1" }, "trigger_type": { diff --git a/homeassistant/components/coinbase/translations/el.json b/homeassistant/components/coinbase/translations/el.json index cb7723d297b5c9..13a43362f4af88 100644 --- a/homeassistant/components/coinbase/translations/el.json +++ b/homeassistant/components/coinbase/translations/el.json @@ -1,6 +1,7 @@ { "config": { "error": { + "invalid_auth_key": "\u03a4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 API \u03b1\u03c0\u03bf\u03c1\u03c1\u03af\u03c6\u03b8\u03b7\u03ba\u03b1\u03bd \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Coinbase \u03bb\u03cc\u03b3\u03c9 \u03bc\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c5 \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd API.", "invalid_auth_secret": "\u03a4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 API \u03b1\u03c0\u03bf\u03c1\u03c1\u03af\u03c6\u03b8\u03b7\u03ba\u03b1\u03bd \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Coinbase \u03bb\u03cc\u03b3\u03c9 \u03bc\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c5 \u03bc\u03c5\u03c3\u03c4\u03b9\u03ba\u03bf\u03cd API." }, "step": { diff --git a/homeassistant/components/daikin/translations/el.json b/homeassistant/components/daikin/translations/el.json index 606c529facc687..3148940ef05d4a 100644 --- a/homeassistant/components/daikin/translations/el.json +++ b/homeassistant/components/daikin/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "api_password": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03b5\u03af\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2." + }, "step": { "user": { "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03bf\u03c5 Daikin AC. \n\n \u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03c4\u03b1 \u039a\u03bb\u03b5\u03b9\u03b4\u03af API \u03ba\u03b1\u03b9 \u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03bf\u03cd\u03bd\u03c4\u03b1\u03b9 \u03bc\u03cc\u03bd\u03bf \u03b1\u03c0\u03cc \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 BRP072Cxx \u03ba\u03b1\u03b9 SKYFi \u03b1\u03bd\u03c4\u03af\u03c3\u03c4\u03bf\u03b9\u03c7\u03b1.", diff --git a/homeassistant/components/demo/translations/select.el.json b/homeassistant/components/demo/translations/select.el.json new file mode 100644 index 00000000000000..e6c6e3e686ea8b --- /dev/null +++ b/homeassistant/components/demo/translations/select.el.json @@ -0,0 +1,9 @@ +{ + "state": { + "demo__speed": { + "light_speed": "\u03a4\u03b1\u03c7\u03cd\u03c4\u03b7\u03c4\u03b1 \u03c6\u03c9\u03c4\u03cc\u03c2", + "ludicrous_speed": "\u039b\u03c5\u03c3\u03c3\u03b1\u03bb\u03ad\u03b1 \u03c4\u03b1\u03c7\u03cd\u03c4\u03b7\u03c4\u03b1", + "ridiculous_speed": "\u0393\u03b5\u03bb\u03bf\u03af\u03b1 \u03c4\u03b1\u03c7\u03cd\u03c4\u03b7\u03c4\u03b1" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/device_tracker/translations/el.json b/homeassistant/components/device_tracker/translations/el.json index 598c747d87b28e..7d42d82534538d 100644 --- a/homeassistant/components/device_tracker/translations/el.json +++ b/homeassistant/components/device_tracker/translations/el.json @@ -1,7 +1,8 @@ { "device_automation": { "condition_type": { - "is_home": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c4\u03bf \u03c3\u03c0\u03af\u03c4\u03b9" + "is_home": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c4\u03bf \u03c3\u03c0\u03af\u03c4\u03b9", + "is_not_home": "{entity_name} \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c0\u03af\u03c4\u03b9" }, "trigger_type": { "enters": "{entity_name} \u03b5\u03b9\u03c3\u03ad\u03c1\u03c7\u03b5\u03c4\u03b1\u03b9 \u03c3\u03b5 \u03bc\u03b9\u03b1 \u03b6\u03ce\u03bd\u03b7", diff --git a/homeassistant/components/devolo_home_control/translations/el.json b/homeassistant/components/devolo_home_control/translations/el.json index b7fc1d36b5259b..907bb2b0d76bb9 100644 --- a/homeassistant/components/devolo_home_control/translations/el.json +++ b/homeassistant/components/devolo_home_control/translations/el.json @@ -9,6 +9,12 @@ "mydevolo_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c4\u03bf\u03c5 mydevolo", "username": "Email / devolo ID" } + }, + "zeroconf_confirm": { + "data": { + "mydevolo_url": "mydevolo \u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", + "username": "Email / \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc devolo" + } } } } diff --git a/homeassistant/components/devolo_home_network/translations/el.json b/homeassistant/components/devolo_home_network/translations/el.json index 78e103f8b69027..5d07abe3507d4f 100644 --- a/homeassistant/components/devolo_home_network/translations/el.json +++ b/homeassistant/components/devolo_home_network/translations/el.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "home_control": "\u0397 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03ae \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 Home \u03c4\u03b7\u03c2 devolo \u03b4\u03b5\u03bd \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03b5\u03af \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7." + }, + "flow_title": "{product} ({name})", "step": { "zeroconf_confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bf\u03b9\u03ba\u03b9\u03b1\u03ba\u03bf\u03cd \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 devolo \u03bc\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae `{host_name}` \u03c3\u03c4\u03bf Home Assistant;", diff --git a/homeassistant/components/dexcom/translations/el.json b/homeassistant/components/dexcom/translations/el.json index b30be708065e42..0012898808cb70 100644 --- a/homeassistant/components/dexcom/translations/el.json +++ b/homeassistant/components/dexcom/translations/el.json @@ -3,6 +3,24 @@ "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03c5\u03b8\u03b5\u03bd\u03c4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7" + }, + "step": { + "user": { + "data": { + "server": "\u0394\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2" + }, + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 Dexcom Share", + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 Dexcom" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "unit_of_measurement": "\u039c\u03bf\u03bd\u03ac\u03b4\u03b1 \u03bc\u03ad\u03c4\u03c1\u03b7\u03c3\u03b7\u03c2" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/dialogflow/translations/el.json b/homeassistant/components/dialogflow/translations/el.json index aecb2ee553fa42..381078f6d8dcde 100644 --- a/homeassistant/components/dialogflow/translations/el.json +++ b/homeassistant/components/dialogflow/translations/el.json @@ -2,6 +2,15 @@ "config": { "abort": { "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "create_entry": { + "default": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c4\u03b5\u03af\u03bb\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03b1 \u03c3\u03c4\u03bf\u03bd Home Assistant, \u03b8\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd [\u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 webhook \u03c4\u03bf\u03c5 Dialogflow]( {dialogflow_url} ). \n\n \u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2: \n\n - URL: ` {webhook_url} `\n - \u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2: POST\n - \u03a4\u03cd\u03c0\u03bf\u03c2 \u03c0\u03b5\u03c1\u03b9\u03b5\u03c7\u03bf\u03bc\u03ad\u03bd\u03bf\u03c5: \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae/json \n\n \u0394\u03b5\u03af\u03c4\u03b5 [\u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]( {docs_url} ) \u03b3\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2." + }, + "step": { + "user": { + "description": "\u0395\u03af\u03c3\u03c4\u03b5 \u03b2\u03ad\u03b2\u03b1\u03b9\u03bf\u03b9 \u03cc\u03c4\u03b9 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Dialogflow;", + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Dialogflow Webhook" + } } } } \ No newline at end of file diff --git a/homeassistant/components/dlna_dmr/translations/el.json b/homeassistant/components/dlna_dmr/translations/el.json index fcbf8246205418..8d71dd3070eb41 100644 --- a/homeassistant/components/dlna_dmr/translations/el.json +++ b/homeassistant/components/dlna_dmr/translations/el.json @@ -13,6 +13,9 @@ }, "flow_title": "{name}", "step": { + "import_turn_on": { + "description": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ba\u03b1\u03b9 \u03ba\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03b7\u03bd \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03a5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03bc\u03b5\u03c4\u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7" + }, "user": { "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b3\u03b9\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03ae \u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03b5\u03bd\u03ae \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 DLNA DMR" diff --git a/homeassistant/components/dnsip/translations/ja.json b/homeassistant/components/dnsip/translations/ja.json index 4b2e6a1fc6566e..6b23c26b2b20d8 100644 --- a/homeassistant/components/dnsip/translations/ja.json +++ b/homeassistant/components/dnsip/translations/ja.json @@ -6,7 +6,9 @@ "step": { "user": { "data": { - "hostname": "DNS\u30af\u30a8\u30ea\u3092\u5b9f\u884c\u3059\u308b\u30db\u30b9\u30c8\u540d" + "hostname": "DNS\u30af\u30a8\u30ea\u3092\u5b9f\u884c\u3059\u308b\u30db\u30b9\u30c8\u540d", + "resolver": "IPV4\u30eb\u30c3\u30af\u30a2\u30c3\u30d7\u7528\u306e\u30ea\u30be\u30eb\u30d0\u30fc", + "resolver_ipv6": "IPV6\u30eb\u30c3\u30af\u30a2\u30c3\u30d7\u7528\u306e\u30ea\u30be\u30eb\u30d0\u30fc" } } } diff --git a/homeassistant/components/dnsip/translations/nl.json b/homeassistant/components/dnsip/translations/nl.json index 6528bdb5a61045..99de016dace998 100644 --- a/homeassistant/components/dnsip/translations/nl.json +++ b/homeassistant/components/dnsip/translations/nl.json @@ -6,7 +6,9 @@ "step": { "user": { "data": { - "hostname": "De hostnaam waarvoor de DNS query moet worden uitgevoerd" + "hostname": "De hostnaam waarvoor de DNS query moet worden uitgevoerd", + "resolver": "Resolver voor IPV4 lookup", + "resolver_ipv6": "Resolver voor IPV6 lookup" } } } diff --git a/homeassistant/components/doorbird/translations/el.json b/homeassistant/components/doorbird/translations/el.json index 5eaa736cd5c0fe..2f023dc3050199 100644 --- a/homeassistant/components/doorbird/translations/el.json +++ b/homeassistant/components/doorbird/translations/el.json @@ -4,6 +4,11 @@ "link_local_address": "\u039f\u03b9 \u03c4\u03bf\u03c0\u03b9\u03ba\u03ad\u03c2 \u03b4\u03b9\u03b5\u03c5\u03b8\u03cd\u03bd\u03c3\u03b5\u03b9\u03c2 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03bc\u03bf\u03c5 \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03bf\u03bd\u03c4\u03b1\u03b9", "not_doorbird_device": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 DoorBird" }, + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", + "unknown": "\u039c\u03b7 \u03b1\u03bd\u03b1\u03bc\u03b5\u03bd\u03cc\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/dsmr/translations/el.json b/homeassistant/components/dsmr/translations/el.json index 31af38ae14baa6..c576116efb27e8 100644 --- a/homeassistant/components/dsmr/translations/el.json +++ b/homeassistant/components/dsmr/translations/el.json @@ -1,7 +1,16 @@ { "config": { + "abort": { + "cannot_communicate": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b5\u03c0\u03b9\u03ba\u03bf\u03b9\u03bd\u03c9\u03bd\u03af\u03b1" + }, + "error": { + "cannot_communicate": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b5\u03c0\u03b9\u03ba\u03bf\u03b9\u03bd\u03c9\u03bd\u03af\u03b1" + }, "step": { "setup_network": { + "data": { + "dsmr_version": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7\u03c2 DSMR" + }, "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "setup_serial": { diff --git a/homeassistant/components/dunehd/translations/el.json b/homeassistant/components/dunehd/translations/el.json new file mode 100644 index 00000000000000..3210d36f1c8473 --- /dev/null +++ b/homeassistant/components/dunehd/translations/el.json @@ -0,0 +1,10 @@ +{ + "config": { + "step": { + "user": { + "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Dune HD. \u0391\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03c0\u03c1\u03bf\u03b2\u03bb\u03ae\u03bc\u03b1\u03c4\u03b1 \u03bc\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c0\u03b7\u03b3\u03b1\u03af\u03bd\u03b5\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7: https://www.home-assistant.io/integrations/dunehd \n\n\u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7.", + "title": "Dune HD" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/el.json b/homeassistant/components/econet/translations/el.json new file mode 100644 index 00000000000000..8e1e8eae41d7ad --- /dev/null +++ b/homeassistant/components/econet/translations/el.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd Rheem EcoNet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/efergy/translations/el.json b/homeassistant/components/efergy/translations/el.json new file mode 100644 index 00000000000000..896c5b50791049 --- /dev/null +++ b/homeassistant/components/efergy/translations/el.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "title": "Efergy" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/el.json b/homeassistant/components/elmax/translations/el.json new file mode 100644 index 00000000000000..34c3a0340b6bbb --- /dev/null +++ b/homeassistant/components/elmax/translations/el.json @@ -0,0 +1,26 @@ +{ + "config": { + "error": { + "invalid_pin": "\u03a4\u03bf \u03c0\u03b1\u03c1\u03b5\u03c7\u03cc\u03bc\u03b5\u03bd\u03bf pin \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf", + "network_error": "\u03a0\u03b1\u03c1\u03bf\u03c5\u03c3\u03b9\u03ac\u03c3\u03c4\u03b7\u03ba\u03b5 \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5", + "no_panel_online": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03b7\u03bb\u03b5\u03ba\u03c4\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc\u03c2 \u03c0\u03af\u03bd\u03b1\u03ba\u03b1\u03c2 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 Elmax.", + "unknown_error": "\u03a0\u03b1\u03c1\u03bf\u03c5\u03c3\u03b9\u03ac\u03c3\u03c4\u03b7\u03ba\u03b5 \u03bc\u03b7 \u03b1\u03bd\u03b1\u03bc\u03b5\u03bd\u03cc\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "panels": { + "data": { + "panel_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03af\u03bd\u03b1\u03ba\u03b1", + "panel_name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03af\u03bd\u03b1\u03ba\u03b1", + "panel_pin": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN" + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c0\u03af\u03bd\u03b1\u03ba\u03b1 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03b8\u03ad\u03bb\u03b1\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03bb\u03ad\u03b3\u03be\u03b5\u03c4\u03b5 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7. \u03a3\u03b7\u03bc\u03b5\u03b9\u03ce\u03c3\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03bf \u03c0\u03af\u03bd\u03b1\u03ba\u03b1\u03c2 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af.", + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c0\u03af\u03bd\u03b1\u03ba\u03b1" + }, + "user": { + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf cloud \u03c4\u03b7\u03c2 Elmax \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ce\u03bd\u03c4\u03b1\u03c2 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2", + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd" + } + } + }, + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 Elmax Cloud" +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/translations/el.json b/homeassistant/components/emulated_roku/translations/el.json index 8bd1aa046be7ea..4ac528bc7ef757 100644 --- a/homeassistant/components/emulated_roku/translations/el.json +++ b/homeassistant/components/emulated_roku/translations/el.json @@ -4,9 +4,12 @@ "user": { "data": { "host_ip": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae", - "listen_port": "\u0391\u03ba\u03c1\u03cc\u03b1\u03c3\u03b7 \u03b8\u03cd\u03c1\u03b1\u03c2" - } + "listen_port": "\u0391\u03ba\u03c1\u03cc\u03b1\u03c3\u03b7 \u03b8\u03cd\u03c1\u03b1\u03c2", + "upnp_bind_multicast": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ae\u03c2 \u03b5\u03ba\u03c0\u03bf\u03bc\u03c0\u03ae\u03c2 (\u03a3\u03c9\u03c3\u03c4\u03cc/\u039b\u03ac\u03b8\u03bf\u03c2)" + }, + "title": "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae" } } - } + }, + "title": "Emulated Roku" } \ No newline at end of file diff --git a/homeassistant/components/environment_canada/translations/el.json b/homeassistant/components/environment_canada/translations/el.json new file mode 100644 index 00000000000000..ebb51349049d2c --- /dev/null +++ b/homeassistant/components/environment_canada/translations/el.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "bad_station_id": "\u03a4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ac\u03ba\u03c5\u03c1\u03bf, \u03bb\u03b5\u03af\u03c0\u03b5\u03b9 \u03ae \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b2\u03c1\u03b5\u03b8\u03b5\u03af \u03c3\u03c4\u03b7 \u03b2\u03ac\u03c3\u03b7 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03ce\u03bd \u03c3\u03c4\u03b1\u03b8\u03bc\u03ce\u03bd", + "error_response": "\u0391\u03c0\u03ac\u03bd\u03c4\u03b7\u03c3\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf Environment Canada \u03ba\u03b1\u03c4\u03ac \u03bb\u03ac\u03b8\u03bf\u03c2", + "too_many_attempts": "\u039f\u03b9 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03b9\u03c2 \u03bc\u03b5 \u03c4\u03bf Environment Canada \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b5\u03c2. \u0394\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c3\u03b5 60 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1" + }, + "step": { + "user": { + "data": { + "language": "\u0393\u03bb\u03ce\u03c3\u03c3\u03b1 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03b9\u03ce\u03bd \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd", + "station": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bc\u03b5\u03c4\u03b5\u03c9\u03c1\u03bf\u03bb\u03bf\u03b3\u03b9\u03ba\u03bf\u03cd \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd" + }, + "description": "\u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03b5\u03af\u03c4\u03b5 \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd \u03b5\u03af\u03c4\u03b5 \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2/\u03bc\u03ae\u03ba\u03bf\u03c2. \u03a4\u03bf \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2/\u03bc\u03ae\u03ba\u03bf\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b5\u03af\u03bd\u03b1\u03b9 \u03bf\u03b9 \u03c4\u03b9\u03bc\u03ad\u03c2 \u03c0\u03bf\u03c5 \u03ad\u03c7\u03bf\u03c5\u03bd \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af \u03c3\u03c4\u03b7\u03bd \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 Home Assistant. \u039f \u03c0\u03bb\u03b7\u03c3\u03b9\u03ad\u03c3\u03c4\u03b5\u03c1\u03bf\u03c2 \u03bc\u03b5\u03c4\u03b5\u03c9\u03c1\u03bf\u03bb\u03bf\u03b3\u03b9\u03ba\u03cc\u03c2 \u03c3\u03c4\u03b1\u03b8\u03bc\u03cc\u03c2 \u03c3\u03c4\u03b9\u03c2 \u03c3\u03c5\u03bd\u03c4\u03b5\u03c4\u03b1\u03b3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b5\u03ac\u03bd \u03ba\u03b1\u03b8\u03bf\u03c1\u03af\u03b6\u03bf\u03bd\u03c4\u03b1\u03b9 \u03c3\u03c5\u03bd\u03c4\u03b5\u03c4\u03b1\u03b3\u03bc\u03ad\u03bd\u03b5\u03c2. \u0395\u03ac\u03bd \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd, \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03b5\u03af \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae: PP/\u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2, \u03cc\u03c0\u03bf\u03c5 PP \u03b5\u03af\u03bd\u03b1\u03b9 \u03b7 \u03b5\u03c0\u03b1\u03c1\u03c7\u03af\u03b1 \u03bc\u03b5 \u03b4\u03cd\u03bf \u03b3\u03c1\u03ac\u03bc\u03bc\u03b1\u03c4\u03b1 \u03ba\u03b1\u03b9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd. \u0397 \u03bb\u03af\u03c3\u03c4\u03b1 \u03c4\u03c9\u03bd \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03ce\u03bd \u03c3\u03c4\u03b1\u03b8\u03bc\u03ce\u03bd \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03b5\u03b4\u03ce: https://dd.weather.gc.ca/citypage_weather/docs/site_list_towns_en.csv. \u039f\u03b9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03ba\u03b1\u03b9\u03c1\u03cc \u03bc\u03c0\u03bf\u03c1\u03bf\u03cd\u03bd \u03bd\u03b1 \u03b1\u03bd\u03b1\u03ba\u03c4\u03b7\u03b8\u03bf\u03cd\u03bd \u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b1 \u03b1\u03b3\u03b3\u03bb\u03b9\u03ba\u03ac \u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b1 \u03b3\u03b1\u03bb\u03bb\u03b9\u03ba\u03ac.", + "title": "Environment Canada: \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ba\u03b1\u03b9 \u03b3\u03bb\u03ce\u03c3\u03c3\u03b1 \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ezviz/translations/el.json b/homeassistant/components/ezviz/translations/el.json index 571ba01a0ac163..50724b72c5821e 100644 --- a/homeassistant/components/ezviz/translations/el.json +++ b/homeassistant/components/ezviz/translations/el.json @@ -17,5 +17,15 @@ "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03b5 \u03c0\u03c1\u03bf\u03c3\u03b1\u03c1\u03bc\u03bf\u03c3\u03bc\u03ad\u03bd\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c4\u03bf\u03c5 Ezviz" } } + }, + "options": { + "step": { + "init": { + "data": { + "ffmpeg_arguments": "\u03a4\u03b1 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03b7\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03ac \u03c0\u03bf\u03c5 \u03b4\u03b9\u03b1\u03b2\u03b9\u03b2\u03ac\u03c3\u03c4\u03b7\u03ba\u03b1\u03bd \u03c3\u03c4\u03bf ffmpeg \u03b3\u03b9\u03b1 \u03ba\u03ac\u03bc\u03b5\u03c1\u03b5\u03c2", + "timeout": "\u0391\u03af\u03c4\u03b7\u03bc\u03b1 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/el.json b/homeassistant/components/faa_delays/translations/el.json index c9eb2c5db3733b..fd575836741bc1 100644 --- a/homeassistant/components/faa_delays/translations/el.json +++ b/homeassistant/components/faa_delays/translations/el.json @@ -2,6 +2,18 @@ "config": { "abort": { "already_configured": "\u0391\u03c5\u03c4\u03cc \u03c4\u03bf \u03b1\u03b5\u03c1\u03bf\u03b4\u03c1\u03cc\u03bc\u03b9\u03bf \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af." + }, + "error": { + "invalid_airport": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b1\u03b5\u03c1\u03bf\u03b4\u03c1\u03bf\u03bc\u03af\u03bf\u03c5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2" + }, + "step": { + "user": { + "data": { + "id": "\u0391\u03b5\u03c1\u03bf\u03b4\u03c1\u03cc\u03bc\u03b9\u03bf" + }, + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03b1\u03b5\u03c1\u03bf\u03b4\u03c1\u03bf\u03bc\u03af\u03bf\u03c5 \u03c4\u03c9\u03bd \u0397\u03a0\u0391 \u03c3\u03b5 \u03bc\u03bf\u03c1\u03c6\u03ae IATA", + "title": "\u039a\u03b1\u03b8\u03c5\u03c3\u03c4\u03b5\u03c1\u03ae\u03c3\u03b5\u03b9\u03c2 FAA" + } } } } \ No newline at end of file diff --git a/homeassistant/components/flipr/translations/el.json b/homeassistant/components/flipr/translations/el.json new file mode 100644 index 00000000000000..18ef9d6890a8a8 --- /dev/null +++ b/homeassistant/components/flipr/translations/el.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "no_flipr_id_found": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc flipr \u03c0\u03bf\u03c5 \u03c3\u03c5\u03c3\u03c7\u03b5\u03c4\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf \u03c0\u03b1\u03c1\u03cc\u03bd. \u0398\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03c0\u03c1\u03ce\u03c4\u03b1 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03b5\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03b5\u03af \u03bc\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u03b3\u03b9\u03b1 \u03ba\u03b9\u03bd\u03b7\u03c4\u03ac \u03c4\u03bf\u03c5 Flipr." + }, + "step": { + "flipr_id": { + "data": { + "flipr_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc Flipr" + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc Flipr \u03c3\u03c4\u03b7 \u03bb\u03af\u03c3\u03c4\u03b1", + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf Flipr \u03c3\u03b1\u03c2" + }, + "user": { + "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ce\u03bd\u03c4\u03b1\u03c2 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Flipr.", + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf Flipr" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flunearyou/translations/el.json b/homeassistant/components/flunearyou/translations/el.json index a9437c09ba4e5f..1707156c71a787 100644 --- a/homeassistant/components/flunearyou/translations/el.json +++ b/homeassistant/components/flunearyou/translations/el.json @@ -2,7 +2,8 @@ "config": { "step": { "user": { - "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ce\u03bd \u03b2\u03ac\u03c3\u03b5\u03b9 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 CDC \u03b3\u03b9\u03b1 \u03ad\u03bd\u03b1 \u03b6\u03b5\u03cd\u03b3\u03bf\u03c2 \u03c3\u03c5\u03bd\u03c4\u03b5\u03c4\u03b1\u03b3\u03bc\u03ad\u03bd\u03c9\u03bd." + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ce\u03bd \u03b2\u03ac\u03c3\u03b5\u03b9 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 CDC \u03b3\u03b9\u03b1 \u03ad\u03bd\u03b1 \u03b6\u03b5\u03cd\u03b3\u03bf\u03c2 \u03c3\u03c5\u03bd\u03c4\u03b5\u03c4\u03b1\u03b3\u03bc\u03ad\u03bd\u03c9\u03bd.", + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Flu Near You" } } } diff --git a/homeassistant/components/flux_led/translations/el.json b/homeassistant/components/flux_led/translations/el.json new file mode 100644 index 00000000000000..8471b3b5f11cb8 --- /dev/null +++ b/homeassistant/components/flux_led/translations/el.json @@ -0,0 +1,25 @@ +{ + "config": { + "flow_title": "{model} {id} ({ipaddr})", + "step": { + "discovery_confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {model} {id} ({ipaddr});" + }, + "user": { + "description": "\u0391\u03bd \u03b1\u03c6\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ba\u03b5\u03bd\u03cc, \u03b7 \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7 \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "custom_effect_colors": "\u03a0\u03c1\u03bf\u03c3\u03b1\u03c1\u03bc\u03bf\u03c3\u03bc\u03ad\u03bd\u03bf \u03b5\u03c6\u03ad: \u039b\u03af\u03c3\u03c4\u03b1 \u03bc\u03b5 1 \u03ad\u03c9\u03c2 16 \u03c7\u03c1\u03ce\u03bc\u03b1\u03c4\u03b1 [R,G,B]. \u03a0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3\u03bc\u03b1: [255,0,255],[60,128,0]", + "custom_effect_speed_pct": "\u03a0\u03c1\u03bf\u03c3\u03b1\u03c1\u03bc\u03bf\u03c3\u03bc\u03ad\u03bd\u03bf \u03b5\u03c6\u03ad: \u03a4\u03b1\u03c7\u03cd\u03c4\u03b7\u03c4\u03b1 \u03c3\u03b5 \u03c0\u03bf\u03c3\u03bf\u03c3\u03c4\u03ac \u03b3\u03b9\u03b1 \u03c4\u03bf \u03b5\u03c6\u03ad \u03c0\u03bf\u03c5 \u03b1\u03bb\u03bb\u03ac\u03b6\u03b5\u03b9 \u03c7\u03c1\u03ce\u03bc\u03b1\u03c4\u03b1.", + "custom_effect_transition": "\u03a0\u03c1\u03bf\u03c3\u03b1\u03c1\u03bc\u03bf\u03c3\u03bc\u03ad\u03bd\u03bf \u03b5\u03c6\u03ad: \u03a4\u03cd\u03c0\u03bf\u03c2 \u03bc\u03b5\u03c4\u03ac\u03b2\u03b1\u03c3\u03b7\u03c2 \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03c4\u03c9\u03bd \u03c7\u03c1\u03c9\u03bc\u03ac\u03c4\u03c9\u03bd.", + "mode": "\u0397 \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c6\u03c9\u03c4\u03b5\u03b9\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2." + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/el.json b/homeassistant/components/forked_daapd/translations/el.json index 569372f25793de..dd01e64ac64c37 100644 --- a/homeassistant/components/forked_daapd/translations/el.json +++ b/homeassistant/components/forked_daapd/translations/el.json @@ -17,7 +17,8 @@ "name": "\u03a6\u03b9\u03bb\u03b9\u03ba\u03cc \u03cc\u03bd\u03bf\u03bc\u03b1", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 API (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03cc \u03b5\u03ac\u03bd \u03b4\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2)", "port": "\u0398\u03cd\u03c1\u03b1 API" - } + }, + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 forked-daapd" } } }, @@ -25,9 +26,13 @@ "step": { "init": { "data": { - "librespot_java_port": "\u0398\u03cd\u03c1\u03b1 \u03b3\u03b9\u03b1 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c3\u03c9\u03bb\u03ae\u03bd\u03b1 librespot-java (\u03b5\u03ac\u03bd \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9)" + "librespot_java_port": "\u0398\u03cd\u03c1\u03b1 \u03b3\u03b9\u03b1 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c3\u03c9\u03bb\u03ae\u03bd\u03b1 librespot-java (\u03b5\u03ac\u03bd \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9)", + "max_playlists": "\u039c\u03ad\u03b3\u03b9\u03c3\u03c4\u03bf\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03bb\u03b9\u03c3\u03c4\u03ce\u03bd \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03bf\u03cd\u03bd\u03c4\u03b1\u03b9 \u03c9\u03c2 \u03c0\u03b7\u03b3\u03ad\u03c2", + "tts_pause_time": "\u0394\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1 \u03b3\u03b9\u03b1 \u03c0\u03b1\u03cd\u03c3\u03b7 \u03c0\u03c1\u03b9\u03bd \u03ba\u03b1\u03b9 \u03bc\u03b5\u03c4\u03ac \u03c4\u03bf TTS", + "tts_volume": "\u0388\u03bd\u03c4\u03b1\u03c3\u03b7 TTS (\u03b4\u03b5\u03ba\u03b1\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03c3\u03c4\u03bf \u03b5\u03cd\u03c1\u03bf\u03c2 [0,1])" }, - "description": "\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03b4\u03b9\u03ac\u03c6\u03bf\u03c1\u03b5\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 forked-daapd." + "description": "\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03b4\u03b9\u03ac\u03c6\u03bf\u03c1\u03b5\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 forked-daapd.", + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd forked-daapd" } } } diff --git a/homeassistant/components/gios/translations/el.json b/homeassistant/components/gios/translations/el.json index b8d85b6960bbb7..0614fd18b68760 100644 --- a/homeassistant/components/gios/translations/el.json +++ b/homeassistant/components/gios/translations/el.json @@ -13,5 +13,10 @@ "title": "GIO\u015a (\u03a0\u03bf\u03bb\u03c9\u03bd\u03b9\u03ba\u03ae \u0393\u03b5\u03bd\u03b9\u03ba\u03ae \u0395\u03c0\u03b9\u03b8\u03b5\u03ce\u03c1\u03b7\u03c3\u03b7 \u03a0\u03c1\u03bf\u03c3\u03c4\u03b1\u03c3\u03af\u03b1\u03c2 \u03a0\u03b5\u03c1\u03b9\u03b2\u03ac\u03bb\u03bb\u03bf\u03bd\u03c4\u03bf\u03c2)" } } + }, + "system_health": { + "info": { + "can_reach_server": "\u03a0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7 \u03c3\u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae GIO\u015a" + } } } \ No newline at end of file diff --git a/homeassistant/components/glances/translations/el.json b/homeassistant/components/glances/translations/el.json index fd43f179032fdf..c6bb137e571438 100644 --- a/homeassistant/components/glances/translations/el.json +++ b/homeassistant/components/glances/translations/el.json @@ -2,6 +2,24 @@ "config": { "error": { "wrong_version": "\u0397 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 (\u03bc\u03cc\u03bd\u03bf 2 \u03ae 3)" + }, + "step": { + "user": { + "data": { + "version": "\u0388\u03ba\u03b4\u03bf\u03c3\u03b7 API Glances (2 \u03ae 3)" + }, + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 Glances" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u03a3\u03c5\u03c7\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7\u03c2" + }, + "description": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c4\u03bf Glances" + } } } } \ No newline at end of file diff --git a/homeassistant/components/gpslogger/translations/el.json b/homeassistant/components/gpslogger/translations/el.json index aecb2ee553fa42..57cf824fd44c02 100644 --- a/homeassistant/components/gpslogger/translations/el.json +++ b/homeassistant/components/gpslogger/translations/el.json @@ -2,6 +2,15 @@ "config": { "abort": { "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "create_entry": { + "default": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c4\u03b5\u03af\u03bb\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03b1 \u03c3\u03c4\u03bf Home Assistant, \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 webhook \u03c3\u03c4\u03bf GPSLogger.\n\n\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b1\u03ba\u03cc\u03bb\u03bf\u03c5\u03b8\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2:\n\n- URL: `{webhook_url}`\n- \u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2: POST\n\n\u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]({docs_url}) \u03b3\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2." + }, + "step": { + "user": { + "description": "\u0395\u03af\u03c3\u03c4\u03b5 \u03b2\u03ad\u03b2\u03b1\u03b9\u03bf\u03b9 \u03cc\u03c4\u03b9 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf GPSLogger Webhook;", + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 GPSLogger Webhook" + } } } } \ No newline at end of file diff --git a/homeassistant/components/guardian/translations/el.json b/homeassistant/components/guardian/translations/el.json index b6d105dc21a162..ed7ca36d8c2638 100644 --- a/homeassistant/components/guardian/translations/el.json +++ b/homeassistant/components/guardian/translations/el.json @@ -6,6 +6,12 @@ "step": { "discovery_confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Guardian;" + }, + "user": { + "description": "\u0394\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c4\u03bf\u03c0\u03b9\u03ba\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Elexa Guardian." + }, + "zeroconf_confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Guardian;" } } } diff --git a/homeassistant/components/home_plus_control/translations/el.json b/homeassistant/components/home_plus_control/translations/el.json new file mode 100644 index 00000000000000..788a8c5a11df96 --- /dev/null +++ b/homeassistant/components/home_plus_control/translations/el.json @@ -0,0 +1,3 @@ +{ + "title": "\u0388\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 Legrand Home+" +} \ No newline at end of file diff --git a/homeassistant/components/huawei_lte/translations/el.json b/homeassistant/components/huawei_lte/translations/el.json index 55ab5b1d17dcec..7fdd2f6c2c5e32 100644 --- a/homeassistant/components/huawei_lte/translations/el.json +++ b/homeassistant/components/huawei_lte/translations/el.json @@ -5,7 +5,11 @@ }, "error": { "connection_timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "incorrect_password": "\u039b\u03b1\u03bd\u03b8\u03b1\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "incorrect_username": "\u039b\u03b1\u03bd\u03b8\u03b1\u03c3\u03bc\u03ad\u03bd\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03c5\u03b8\u03b5\u03bd\u03c4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", + "invalid_url": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", + "login_attempts_exceeded": "\u03a5\u03c0\u03ad\u03c1\u03b2\u03b1\u03c3\u03b7 \u03c4\u03c9\u03bd \u03bc\u03ad\u03b3\u03b9\u03c3\u03c4\u03c9\u03bd \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03b5\u03b9\u03ce\u03bd \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2, \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1", "response_error": "\u0386\u03b3\u03bd\u03c9\u03c3\u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae", "unknown": "\u039c\u03b7 \u03b1\u03bd\u03b1\u03bc\u03b5\u03bd\u03cc\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, diff --git a/homeassistant/components/hue/translations/el.json b/homeassistant/components/hue/translations/el.json index 6084975c60e46e..e76e50d1cafd1a 100644 --- a/homeassistant/components/hue/translations/el.json +++ b/homeassistant/components/hue/translations/el.json @@ -37,9 +37,13 @@ "turn_on": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7" }, "trigger_type": { + "initial_press": "\u03a4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \"{subtype}\" \u03c0\u03b1\u03c4\u03ae\u03b8\u03b7\u03ba\u03b5 \u03b1\u03c1\u03c7\u03b9\u03ba\u03ac", + "long_release": "\u03a4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \"{subtype}\" \u03b1\u03c0\u03b5\u03bb\u03b5\u03c5\u03b8\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03bc\u03b5\u03c4\u03ac \u03b1\u03c0\u03cc \u03c0\u03b1\u03c1\u03b1\u03c4\u03b5\u03c4\u03b1\u03bc\u03ad\u03bd\u03bf \u03c0\u03ac\u03c4\u03b7\u03bc\u03b1", "remote_button_long_release": "\u03a4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03c4\u03bf\u03c5 \"{subtype}\" \u03b1\u03c0\u03b5\u03bb\u03b5\u03c5\u03b8\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03bc\u03b5\u03c4\u03ac \u03b1\u03c0\u03cc \u03c0\u03b1\u03c1\u03b1\u03c4\u03b5\u03c4\u03b1\u03bc\u03ad\u03bd\u03bf \u03c0\u03ac\u03c4\u03b7\u03bc\u03b1", "remote_button_short_press": "\u03a0\u03b1\u03c4\u03ae\u03b8\u03b7\u03ba\u03b5 \u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03c4\u03bf\u03c5 \"{subtype}\"", - "remote_button_short_release": "\u0391\u03c6\u03ad\u03b8\u03b7\u03ba\u03b5 \u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03c4\u03bf\u03c5 \"{subtype}\"" + "remote_button_short_release": "\u0391\u03c6\u03ad\u03b8\u03b7\u03ba\u03b5 \u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03c4\u03bf\u03c5 \"{subtype}\"", + "repeat": "\u03a4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \"{subtype}\" \u03ba\u03c1\u03b1\u03c4\u03ae\u03b8\u03b7\u03ba\u03b5 \u03c0\u03b1\u03c4\u03b7\u03bc\u03ad\u03bd\u03bf", + "short_release": "\u03a4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \" {subtype} \" \u03b1\u03c0\u03b5\u03bb\u03b5\u03c5\u03b8\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03bc\u03b5\u03c4\u03ac \u03b1\u03c0\u03cc \u03c3\u03cd\u03bd\u03c4\u03bf\u03bc\u03bf \u03c0\u03ac\u03c4\u03b7\u03bc\u03b1" } }, "options": { diff --git a/homeassistant/components/insteon/translations/el.json b/homeassistant/components/insteon/translations/el.json index 54900940d9b686..36320b286063d0 100644 --- a/homeassistant/components/insteon/translations/el.json +++ b/homeassistant/components/insteon/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "select_single": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae." + }, "step": { "hubv1": { "data": { diff --git a/homeassistant/components/ipp/translations/el.json b/homeassistant/components/ipp/translations/el.json index b758a17357f7f2..ea4684299247a7 100644 --- a/homeassistant/components/ipp/translations/el.json +++ b/homeassistant/components/ipp/translations/el.json @@ -2,7 +2,8 @@ "config": { "abort": { "connection_upgrade": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae \u03b5\u03c0\u03b5\u03b9\u03b4\u03ae \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03bd\u03b1\u03b2\u03ac\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2.", - "parse_error": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b1\u03bd\u03ac\u03bb\u03c5\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b1\u03c0\u03ac\u03bd\u03c4\u03b7\u03c3\u03b7\u03c2 \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae." + "parse_error": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b1\u03bd\u03ac\u03bb\u03c5\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b1\u03c0\u03ac\u03bd\u03c4\u03b7\u03c3\u03b7\u03c2 \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae.", + "unique_id_required": "\u039b\u03b5\u03af\u03c0\u03b5\u03b9 \u03b7 \u03bc\u03bf\u03bd\u03b1\u03b4\u03b9\u03ba\u03ae \u03b1\u03bd\u03b1\u03b3\u03bd\u03ce\u03c1\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03c0\u03bf\u03c5 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7." }, "error": { "connection_upgrade": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae. \u0394\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03bc\u03b5 \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03c4\u03b7\u03bd \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae SSL/TLS." diff --git a/homeassistant/components/izone/translations/el.json b/homeassistant/components/izone/translations/el.json new file mode 100644 index 00000000000000..eeceb373ce8729 --- /dev/null +++ b/homeassistant/components/izone/translations/el.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf iZone;" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/juicenet/translations/el.json b/homeassistant/components/juicenet/translations/el.json index 32140df6fa7ebe..472765dfba5b85 100644 --- a/homeassistant/components/juicenet/translations/el.json +++ b/homeassistant/components/juicenet/translations/el.json @@ -2,7 +2,8 @@ "config": { "step": { "user": { - "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API \u03b1\u03c0\u03cc https://home.juice.net/Manage." + "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API \u03b1\u03c0\u03cc https://home.juice.net/Manage.", + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf JuiceNet" } } } diff --git a/homeassistant/components/keenetic_ndms2/translations/el.json b/homeassistant/components/keenetic_ndms2/translations/el.json index 251383c2115ae1..d00e805ad1d527 100644 --- a/homeassistant/components/keenetic_ndms2/translations/el.json +++ b/homeassistant/components/keenetic_ndms2/translations/el.json @@ -10,6 +10,7 @@ "step": { "user": { "data": { + "consider_home": "\u0395\u03be\u03b5\u03c4\u03ac\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03b4\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03c3\u03c4\u03bf \u03c3\u03c0\u03af\u03c4\u03b9", "include_arp": "\u03a7\u03c1\u03ae\u03c3\u03b7 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd ARP (\u03b1\u03b3\u03bd\u03bf\u03bf\u03cd\u03bd\u03c4\u03b1\u03b9 \u03b5\u03ac\u03bd \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03bf\u03cd\u03bd\u03c4\u03b1\u03b9 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 hotspot)", "include_associated": "\u03a7\u03c1\u03ae\u03c3\u03b7 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd \u03c3\u03c5\u03c3\u03c7\u03b5\u03c4\u03af\u03c3\u03b5\u03c9\u03bd WiFi AP (\u03b1\u03b3\u03bd\u03bf\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03bd \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03bf\u03cd\u03bd\u03c4\u03b1\u03b9 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 hotspot)", "interfaces": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03b4\u03b9\u03b5\u03c0\u03b1\u03c6\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7", diff --git a/homeassistant/components/knx/translations/el.json b/homeassistant/components/knx/translations/el.json index 28e9033bc11e68..ea7fe194fefdcd 100644 --- a/homeassistant/components/knx/translations/el.json +++ b/homeassistant/components/knx/translations/el.json @@ -13,6 +13,7 @@ "routing": { "data": { "individual_address": "\u0391\u03c4\u03bf\u03bc\u03b9\u03ba\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03cc\u03b3\u03b7\u03c3\u03b7\u03c2", + "local_ip": "\u03a4\u03bf\u03c0\u03b9\u03ba\u03ae IP \u03c4\u03bf\u03c5 Home Assistant (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03b5\u03bd\u03ae \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7)", "multicast_group": "\u0397 \u03bf\u03bc\u03ac\u03b4\u03b1 \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ae\u03c2 \u03b5\u03ba\u03c0\u03bf\u03bc\u03c0\u03ae\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03cc\u03b3\u03b7\u03c3\u03b7", "multicast_port": "\u0397 \u03b8\u03cd\u03c1\u03b1 \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ae\u03c2 \u03b4\u03b9\u03b1\u03bd\u03bf\u03bc\u03ae\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03cc\u03b3\u03b7\u03c3\u03b7" }, @@ -38,6 +39,7 @@ "data": { "connection_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 KNX", "individual_address": "\u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03b1\u03c4\u03bf\u03bc\u03b9\u03ba\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7", + "local_ip": "\u03a4\u03bf\u03c0\u03b9\u03ba\u03ae IP \u03c4\u03bf\u03c5 Home Assistant (\u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 0.0.0.0.0 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7)", "multicast_group": "\u039f\u03bc\u03ac\u03b4\u03b1 \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ae\u03c2 \u03b4\u03b9\u03b1\u03bd\u03bf\u03bc\u03ae\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03cc\u03b3\u03b7\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7", "multicast_port": "\u0398\u03cd\u03c1\u03b1 \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ae\u03c2 \u03b4\u03b9\u03b1\u03bd\u03bf\u03bc\u03ae\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03cc\u03b3\u03b7\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7", "rate_limit": "\u039c\u03ad\u03b3\u03b9\u03c3\u03c4\u03b1 \u03b5\u03be\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03b1 \u03c4\u03b7\u03bb\u03b5\u03b3\u03c1\u03b1\u03c6\u03ae\u03bc\u03b1\u03c4\u03b1 \u03b1\u03bd\u03ac \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03bf", diff --git a/homeassistant/components/lcn/translations/el.json b/homeassistant/components/lcn/translations/el.json new file mode 100644 index 00000000000000..f5d8a4d53a0e6d --- /dev/null +++ b/homeassistant/components/lcn/translations/el.json @@ -0,0 +1,7 @@ +{ + "device_automation": { + "trigger_type": { + "fingerprint": "\u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b4\u03b1\u03ba\u03c4\u03c5\u03bb\u03b9\u03ba\u03bf\u03cd \u03b1\u03c0\u03bf\u03c4\u03c5\u03c0\u03ce\u03bc\u03b1\u03c4\u03bf\u03c2 \u03b5\u03bb\u03ae\u03c6\u03b8\u03b7" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/el.json b/homeassistant/components/litejet/translations/el.json index 9724aa87b12ba7..98eaae46a3fc7c 100644 --- a/homeassistant/components/litejet/translations/el.json +++ b/homeassistant/components/litejet/translations/el.json @@ -1,10 +1,23 @@ { "config": { + "error": { + "open_failed": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc \u03c4\u03bf \u03ac\u03bd\u03bf\u03b9\u03b3\u03bc\u03b1 \u03c4\u03b7\u03c2 \u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7\u03c2 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae\u03c2 \u03b8\u03cd\u03c1\u03b1\u03c2." + }, "step": { "user": { "description": "\u03a3\u03c5\u03bd\u03b4\u03ad\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03b8\u03cd\u03c1\u03b1 RS232-2 \u03c4\u03bf\u03c5 LiteJet \u03c3\u03c4\u03bf\u03bd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03c3\u03b1\u03c2 \u03ba\u03b1\u03b9 \u03b5\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c0\u03c1\u03bf\u03c2 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae\u03c2 \u03b8\u03cd\u03c1\u03b1\u03c2.\n\n\u03a4\u03bf LiteJet MCP \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af \u03b3\u03b9\u03b1 19,2 K baud, 8 bit \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd, 1 stop bit, \u03c7\u03c9\u03c1\u03af\u03c2 \u03b9\u03c3\u03bf\u03c4\u03b9\u03bc\u03af\u03b1 \u03ba\u03b1\u03b9 \u03bd\u03b1 \u03bc\u03b5\u03c4\u03b1\u03b4\u03af\u03b4\u03b5\u03b9 \u03ad\u03bd\u03b1 'CR' \u03bc\u03b5\u03c4\u03ac \u03b1\u03c0\u03cc \u03ba\u03ac\u03b8\u03b5 \u03b1\u03c0\u03ac\u03bd\u03c4\u03b7\u03c3\u03b7.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf LiteJet" } } + }, + "options": { + "step": { + "init": { + "data": { + "default_transition": "\u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03bc\u03b5\u03c4\u03ac\u03b2\u03b1\u03c3\u03b7 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)" + }, + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 LiteJet" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/locative/translations/el.json b/homeassistant/components/locative/translations/el.json index e041741e99298b..a1cdfb27918692 100644 --- a/homeassistant/components/locative/translations/el.json +++ b/homeassistant/components/locative/translations/el.json @@ -5,6 +5,11 @@ }, "create_entry": { "default": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c4\u03b5\u03af\u03bb\u03b5\u03c4\u03b5 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b5\u03c2 \u03c3\u03c4\u03bf Home Assistant, \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 webhook \u03c3\u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae Locative.\n\n\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b1\u03ba\u03cc\u03bb\u03bf\u03c5\u03b8\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2:\n\n- URL: `{webhook_url}`\n- \u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2: \n\n\u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]({docs_url}) \u03b3\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2." + }, + "step": { + "user": { + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Locative Webhook" + } } } } \ No newline at end of file diff --git a/homeassistant/components/lookin/translations/el.json b/homeassistant/components/lookin/translations/el.json new file mode 100644 index 00000000000000..8fc295ebf9a6b9 --- /dev/null +++ b/homeassistant/components/lookin/translations/el.json @@ -0,0 +1,10 @@ +{ + "config": { + "flow_title": "{name} ({host})", + "step": { + "discovery_confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ({host});" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/luftdaten/translations/el.json b/homeassistant/components/luftdaten/translations/el.json new file mode 100644 index 00000000000000..e5fc7179f865fc --- /dev/null +++ b/homeassistant/components/luftdaten/translations/el.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "invalid_sensor": "\u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03bc\u03b7 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03bf\u03c2 \u03ae \u03ac\u03ba\u03c5\u03c1\u03bf\u03c2" + }, + "step": { + "user": { + "data": { + "show_on_map": "\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03c3\u03c4\u03bf \u03c7\u03ac\u03c1\u03c4\u03b7", + "station_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1" + }, + "title": "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 Luftdaten" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/el.json b/homeassistant/components/lutron_caseta/translations/el.json index 4f0bc6628e3695..eea8c3495b4918 100644 --- a/homeassistant/components/lutron_caseta/translations/el.json +++ b/homeassistant/components/lutron_caseta/translations/el.json @@ -21,6 +21,8 @@ }, "device_automation": { "trigger_subtype": { + "button_1": "\u03a0\u03c1\u03ce\u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af", + "button_2": "\u0394\u03b5\u03cd\u03c4\u03b5\u03c1\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af", "button_3": "\u03a4\u03c1\u03af\u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af", "button_4": "\u03a4\u03ad\u03c4\u03b1\u03c1\u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af", "close_1": "\u039a\u03bb\u03b5\u03af\u03c3\u03b9\u03bc\u03bf 1", @@ -44,7 +46,18 @@ "open_2": "\u0386\u03bd\u03bf\u03b9\u03b3\u03bc\u03b1 2", "open_3": "\u0386\u03bd\u03bf\u03b9\u03b3\u03bc\u03b1 3", "open_4": "\u0386\u03bd\u03bf\u03b9\u03b3\u03bc\u03b1 4", - "open_all": "\u0386\u03bd\u03bf\u03b9\u03b3\u03bc\u03b1 \u03cc\u03bb\u03c9\u03bd" + "open_all": "\u0386\u03bd\u03bf\u03b9\u03b3\u03bc\u03b1 \u03cc\u03bb\u03c9\u03bd", + "raise_all": "\u03a3\u03b7\u03ba\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03cc\u03bb\u03b1", + "stop": "\u0394\u03b9\u03b1\u03ba\u03bf\u03c0\u03ae (\u03b1\u03b3\u03b1\u03c0\u03b7\u03bc\u03ad\u03bd\u03bf)", + "stop_1": "\u0394\u03b9\u03b1\u03ba\u03bf\u03c0\u03ae 1", + "stop_2": "\u0394\u03b9\u03b1\u03ba\u03bf\u03c0\u03ae 2", + "stop_3": "\u0394\u03b9\u03b1\u03ba\u03bf\u03c0\u03ae 3", + "stop_4": "\u0394\u03b9\u03b1\u03ba\u03bf\u03c0\u03ae 4", + "stop_all": "\u03a3\u03c4\u03b1\u03bc\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03cc\u03bb\u03b1" + }, + "trigger_type": { + "press": "\u03a0\u03b1\u03c4\u03ae\u03b8\u03b7\u03ba\u03b5 \u03c4\u03bf \"{subtype}\"", + "release": "\u0391\u03c0\u03b5\u03bb\u03b5\u03c5\u03b8\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03c4\u03bf \"{subtype}\"" } } } \ No newline at end of file diff --git a/homeassistant/components/mailgun/translations/el.json b/homeassistant/components/mailgun/translations/el.json index 19873f86a31c2e..385d9d8973de53 100644 --- a/homeassistant/components/mailgun/translations/el.json +++ b/homeassistant/components/mailgun/translations/el.json @@ -8,7 +8,8 @@ }, "step": { "user": { - "description": "\u0395\u03af\u03c3\u03c4\u03b5 \u03c3\u03af\u03b3\u03bf\u03c5\u03c1\u03bf\u03b9 \u03cc\u03c4\u03b9 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Mailgun;" + "description": "\u0395\u03af\u03c3\u03c4\u03b5 \u03c3\u03af\u03b3\u03bf\u03c5\u03c1\u03bf\u03b9 \u03cc\u03c4\u03b9 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Mailgun;", + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Webhook Mailgun" } } } diff --git a/homeassistant/components/motion_blinds/translations/el.json b/homeassistant/components/motion_blinds/translations/el.json index 8c6a8c07fdfcba..29fa43b4a167a8 100644 --- a/homeassistant/components/motion_blinds/translations/el.json +++ b/homeassistant/components/motion_blinds/translations/el.json @@ -13,5 +13,12 @@ "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03cd\u03bb\u03b7 \u03ba\u03af\u03bd\u03b7\u03c3\u03b7\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5" } } + }, + "options": { + "step": { + "init": { + "title": "Motion Blinds" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/motioneye/translations/el.json b/homeassistant/components/motioneye/translations/el.json index b0c39d2c59776b..845e6949c010ad 100644 --- a/homeassistant/components/motioneye/translations/el.json +++ b/homeassistant/components/motioneye/translations/el.json @@ -2,6 +2,11 @@ "config": { "error": { "invalid_url": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL" + }, + "step": { + "hassio_confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03bf\u03c5\u03c2 \u03c4\u03bf\u03c5 Home Assistant \u03ce\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03b5\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03c4\u03b7\u03bd \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 motionEye \u03c0\u03bf\u03c5 \u03c0\u03b1\u03c1\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf: {addon};" + } } }, "options": { diff --git a/homeassistant/components/neato/translations/el.json b/homeassistant/components/neato/translations/el.json new file mode 100644 index 00000000000000..a73e4283fb16db --- /dev/null +++ b/homeassistant/components/neato/translations/el.json @@ -0,0 +1,3 @@ +{ + "title": "Neato Botvac" +} \ No newline at end of file diff --git a/homeassistant/components/nest/translations/el.json b/homeassistant/components/nest/translations/el.json index d7b4acd20c18fb..b507bda970d03a 100644 --- a/homeassistant/components/nest/translations/el.json +++ b/homeassistant/components/nest/translations/el.json @@ -3,7 +3,9 @@ "error": { "internal_error": "\u0395\u03c3\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03cc \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03b5\u03c0\u03b9\u03ba\u03cd\u03c1\u03c9\u03c3\u03b7\u03c2 \u03ba\u03ce\u03b4\u03b9\u03ba\u03b1", "invalid_pin": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN", - "timeout": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03c0\u03b9\u03ba\u03cd\u03c1\u03c9\u03c3\u03b7\u03c2 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5" + "subscriber_error": "\u0386\u03b3\u03bd\u03c9\u03c3\u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03c3\u03c5\u03bd\u03b4\u03c1\u03bf\u03bc\u03b7\u03c4\u03ae, \u03b4\u03b5\u03af\u03c4\u03b5 \u03c4\u03b1 \u03b1\u03c1\u03c7\u03b5\u03af\u03b1 \u03ba\u03b1\u03c4\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2", + "timeout": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03c0\u03b9\u03ba\u03cd\u03c1\u03c9\u03c3\u03b7\u03c2 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5", + "wrong_project_id": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03ad\u03c1\u03b3\u03bf\u03c5 Cloud (\u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03ad\u03c1\u03b3\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2)" }, "step": { "auth": { @@ -20,6 +22,9 @@ "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd Nest" }, "pubsub": { + "data": { + "cloud_project_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03ad\u03c1\u03b3\u03bf\u03c5 Google Cloud" + }, "description": "\u0395\u03c0\u03b9\u03c3\u03ba\u03b5\u03c6\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf [Cloud Console]({url}) \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b2\u03c1\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03bf\u03c5 \u03ad\u03c1\u03b3\u03bf\u03c5 \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Google Cloud.", "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Google Cloud" }, diff --git a/homeassistant/components/netatmo/translations/el.json b/homeassistant/components/netatmo/translations/el.json index c71df1f9b4e27b..640cf82fba1239 100644 --- a/homeassistant/components/netatmo/translations/el.json +++ b/homeassistant/components/netatmo/translations/el.json @@ -1,4 +1,11 @@ { + "config": { + "step": { + "reauth_confirm": { + "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 Netatmo \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2" + } + } + }, "device_automation": { "trigger_subtype": { "away": "\u03b5\u03ba\u03c4\u03cc\u03c2", @@ -14,6 +21,7 @@ "outdoor": "\u03a4\u03bf {entity_name} \u03b5\u03bd\u03c4\u03cc\u03c0\u03b9\u03c3\u03b5 \u03ad\u03bd\u03b1 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd \u03b5\u03be\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03bf\u03cd \u03c7\u03ce\u03c1\u03bf\u03c5", "person": "{entity_name} \u03b5\u03bd\u03c4\u03cc\u03c0\u03b9\u03c3\u03b5 \u03ad\u03bd\u03b1 \u03ac\u03c4\u03bf\u03bc\u03bf", "person_away": "{entity_name} \u03b5\u03bd\u03c4\u03cc\u03c0\u03b9\u03c3\u03b5 \u03cc\u03c4\u03b9 \u03ad\u03bd\u03b1 \u03ac\u03c4\u03bf\u03bc\u03bf \u03ad\u03c7\u03b5\u03b9 \u03c6\u03cd\u03b3\u03b5\u03b9", + "set_point": "\u0397 \u03c3\u03c4\u03bf\u03c7\u03b5\u03c5\u03cc\u03bc\u03b5\u03bd\u03b7 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1 {entity_name} \u03bf\u03c1\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03bc\u03b7 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b1", "therm_mode": "{entity_name} \u03ac\u03bb\u03bb\u03b1\u03be\u03b5 \u03c3\u03b5 \"{subtype}\"", "turned_off": "{entity_name} \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", "turned_on": "{entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", diff --git a/homeassistant/components/netgear/translations/el.json b/homeassistant/components/netgear/translations/el.json index 2b5744077e18a4..59a86b9dcef8f5 100644 --- a/homeassistant/components/netgear/translations/el.json +++ b/homeassistant/components/netgear/translations/el.json @@ -7,8 +7,22 @@ "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2 (\u03a0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", - "port": "\u0398\u03cd\u03c1\u03b1 (\u03a0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)" - } + "port": "\u0398\u03cd\u03c1\u03b1 (\u03a0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 (\u03a0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)" + }, + "description": "\u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf\u03c2 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2: {host}\n\u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03b8\u03cd\u03c1\u03b1: {port}\n\u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7: {username}", + "title": "Netgear" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "consider_home": "\u0395\u03be\u03b5\u03c4\u03ac\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ce\u03c1\u03b1 \u03c3\u03c4\u03bf \u03c3\u03c0\u03af\u03c4\u03b9 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)" + }, + "description": "\u039a\u03b1\u03b8\u03bf\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ad\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2", + "title": "Netgear" } } } diff --git a/homeassistant/components/nfandroidtv/translations/el.json b/homeassistant/components/nfandroidtv/translations/el.json new file mode 100644 index 00000000000000..26e4eed8900ee8 --- /dev/null +++ b/homeassistant/components/nfandroidtv/translations/el.json @@ -0,0 +1,10 @@ +{ + "config": { + "step": { + "user": { + "description": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u0395\u03b9\u03b4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9\u03c2 \u03b3\u03b9\u03b1 Android TV. \n\n \u0393\u03b9\u03b1 Android TV: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\n \u0393\u03b9\u03b1 Fire TV: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK \n\n \u0398\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03b5\u03af\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03c1\u03ac\u03c4\u03b7\u03c3\u03b7 DHCP \u03c3\u03c4\u03bf \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2 (\u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b5\u03b3\u03c7\u03b5\u03b9\u03c1\u03af\u03b4\u03b9\u03bf \u03c7\u03c1\u03ae\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2) \u03b5\u03af\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c4\u03b1\u03c4\u03b9\u03ba\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae. \u0395\u03ac\u03bd \u03cc\u03c7\u03b9, \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b8\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03b5\u03af \u03c4\u03b5\u03bb\u03b9\u03ba\u03ac \u03bc\u03b7 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7.", + "title": "\u0395\u03b9\u03b4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9\u03c2 \u03b3\u03b9\u03b1 Android TV / Fire TV" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nmap_tracker/translations/el.json b/homeassistant/components/nmap_tracker/translations/el.json index 971cfedd05d403..370bbd900c2f71 100644 --- a/homeassistant/components/nmap_tracker/translations/el.json +++ b/homeassistant/components/nmap_tracker/translations/el.json @@ -19,7 +19,8 @@ "step": { "init": { "data": { - "consider_home": "\u0394\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1 \u03b1\u03bd\u03b1\u03bc\u03bf\u03bd\u03ae\u03c2 \u03bc\u03ad\u03c7\u03c1\u03b9 \u03bd\u03b1 \u03b5\u03c0\u03b9\u03c3\u03b7\u03bc\u03b1\u03bd\u03b8\u03b5\u03af \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03bc\u03bf\u03cd \u03c9\u03c2 \u03cc\u03c7\u03b9 \u03c3\u03c0\u03af\u03c4\u03b9, \u03b1\u03c6\u03bf\u03cd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b5\u03bc\u03c6\u03b1\u03bd\u03b9\u03c3\u03c4\u03b5\u03af." + "consider_home": "\u0394\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1 \u03b1\u03bd\u03b1\u03bc\u03bf\u03bd\u03ae\u03c2 \u03bc\u03ad\u03c7\u03c1\u03b9 \u03bd\u03b1 \u03b5\u03c0\u03b9\u03c3\u03b7\u03bc\u03b1\u03bd\u03b8\u03b5\u03af \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03bc\u03bf\u03cd \u03c9\u03c2 \u03cc\u03c7\u03b9 \u03c3\u03c0\u03af\u03c4\u03b9, \u03b1\u03c6\u03bf\u03cd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b5\u03bc\u03c6\u03b1\u03bd\u03b9\u03c3\u03c4\u03b5\u03af.", + "track_new_devices": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03bd\u03ad\u03c9\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd" } } } diff --git a/homeassistant/components/nut/translations/el.json b/homeassistant/components/nut/translations/el.json index 67273a20482984..2f13daba62c80b 100644 --- a/homeassistant/components/nut/translations/el.json +++ b/homeassistant/components/nut/translations/el.json @@ -23,7 +23,8 @@ "step": { "init": { "data": { - "resources": "\u03a0\u03cc\u03c1\u03bf\u03b9" + "resources": "\u03a0\u03cc\u03c1\u03bf\u03b9", + "scan_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7\u03c2 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)" }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c0\u03cc\u03c1\u03bf\u03c5\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03c9\u03bd." } diff --git a/homeassistant/components/octoprint/translations/el.json b/homeassistant/components/octoprint/translations/el.json new file mode 100644 index 00000000000000..e29ff8dee5f10f --- /dev/null +++ b/homeassistant/components/octoprint/translations/el.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "auth_failed": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b1\u03bd\u03ac\u03ba\u03c4\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd api \u03c4\u03b7\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/el.json b/homeassistant/components/onvif/translations/el.json index 945b9cfa6a85fd..676e0e9ca898e6 100644 --- a/homeassistant/components/onvif/translations/el.json +++ b/homeassistant/components/onvif/translations/el.json @@ -14,6 +14,9 @@ }, "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, + "configure": { + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 ONVIF" + }, "configure_profile": { "data": { "include": "\u0394\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ba\u03ac\u03bc\u03b5\u03c1\u03b1\u03c2" @@ -35,6 +38,9 @@ "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 ONVIF" }, "user": { + "data": { + "auto": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7" + }, "description": "\u039a\u03ac\u03bd\u03bf\u03bd\u03c4\u03b1\u03c2 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03b7\u03bd \u03c5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae, \u03b8\u03b1 \u03b1\u03bd\u03b1\u03b6\u03b7\u03c4\u03ae\u03c3\u03bf\u03c5\u03bc\u03b5 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03cc \u03c3\u03b1\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 ONVIF \u03c0\u03bf\u03c5 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03bf\u03c5\u03bd \u03c4\u03bf Profile S. \n\n \u039f\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03b9 \u03ba\u03b1\u03c4\u03b1\u03c3\u03ba\u03b5\u03c5\u03b1\u03c3\u03c4\u03ad\u03c2 \u03ad\u03c7\u03bf\u03c5\u03bd \u03b1\u03c1\u03c7\u03af\u03c3\u03b5\u03b9 \u03bd\u03b1 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03bf\u03cd\u03bd \u03c4\u03bf ONVIF \u03b1\u03c0\u03cc \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae. \u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03c4\u03bf ONVIF \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03ba\u03ac\u03bc\u03b5\u03c1\u03ac\u03c2 \u03c3\u03b1\u03c2.", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 ONVIF" } diff --git a/homeassistant/components/open_meteo/translations/el.json b/homeassistant/components/open_meteo/translations/el.json new file mode 100644 index 00000000000000..dd95b5e28d70f3 --- /dev/null +++ b/homeassistant/components/open_meteo/translations/el.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "zone": "\u0396\u03ce\u03bd\u03b7" + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c0\u03c1\u03cc\u03b3\u03bd\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/opentherm_gw/translations/el.json b/homeassistant/components/opentherm_gw/translations/el.json index 11f543797fe13f..049ed9ce79e138 100644 --- a/homeassistant/components/opentherm_gw/translations/el.json +++ b/homeassistant/components/opentherm_gw/translations/el.json @@ -19,7 +19,8 @@ "data": { "floor_temperature": "\u0398\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1 \u03b4\u03b1\u03c0\u03ad\u03b4\u03bf\u03c5", "read_precision": "\u0394\u03b9\u03ac\u03b2\u03b1\u03c3\u03b5 \u03c4\u03b7\u03bd \u03b1\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1", - "set_precision": "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03b1\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1\u03c2" + "set_precision": "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03b1\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1\u03c2", + "temporary_override_mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c9\u03c1\u03b9\u03bd\u03ae\u03c2 \u03c0\u03b1\u03c1\u03ac\u03ba\u03b1\u03bc\u03c8\u03b7\u03c2 \u03c3\u03b7\u03bc\u03b5\u03af\u03bf\u03c5 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7\u03c2" }, "description": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c0\u03cd\u03bb\u03b7 OpenTherm" } diff --git a/homeassistant/components/ozw/translations/el.json b/homeassistant/components/ozw/translations/el.json index 76192bb3427553..10e5ee24ffd8c6 100644 --- a/homeassistant/components/ozw/translations/el.json +++ b/homeassistant/components/ozw/translations/el.json @@ -14,6 +14,9 @@ "install_addon": "\u03a0\u03b5\u03c1\u03b9\u03bc\u03ad\u03bd\u03b5\u03c4\u03b5 \u03bc\u03ad\u03c7\u03c1\u03b9 \u03bd\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03c9\u03b8\u03b5\u03af \u03b7 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 OpenZWave. \u0391\u03c5\u03c4\u03cc \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b4\u03b9\u03b1\u03c1\u03ba\u03ad\u03c3\u03b5\u03b9 \u03b1\u03c1\u03ba\u03b5\u03c4\u03ac \u03bb\u03b5\u03c0\u03c4\u03ac." }, "step": { + "hassio_confirm": { + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 OpenZWave \u03bc\u03b5 \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf OpenZWave" + }, "install_addon": { "title": "\u0397 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 OpenZWave \u03ad\u03c7\u03b5\u03b9 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03b9" }, diff --git a/homeassistant/components/philips_js/translations/el.json b/homeassistant/components/philips_js/translations/el.json index d5432d5d60e6ea..11833024f9cf2a 100644 --- a/homeassistant/components/philips_js/translations/el.json +++ b/homeassistant/components/philips_js/translations/el.json @@ -1,6 +1,7 @@ { "config": { "error": { + "invalid_pin": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf PIN", "pairing_failure": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7: {error_id}" }, "step": { diff --git a/homeassistant/components/plaato/translations/el.json b/homeassistant/components/plaato/translations/el.json index 85fc79981ce651..bd62f226b2e394 100644 --- a/homeassistant/components/plaato/translations/el.json +++ b/homeassistant/components/plaato/translations/el.json @@ -28,7 +28,23 @@ "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03c9\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd Plaato" }, "webhook": { - "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c4\u03b5\u03af\u03bb\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03b1 \u03c3\u03c4\u03bf Home Assistant, \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 webhook \u03c3\u03c4\u03bf Plaato Airlock.\n\n\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b1\u03ba\u03cc\u03bb\u03bf\u03c5\u03b8\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2:\n\n- URL: `{webhook_url}`\n- \u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2: \n\n\u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]({docs_url}) \u03b3\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2." + "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c4\u03b5\u03af\u03bb\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03b1 \u03c3\u03c4\u03bf Home Assistant, \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 webhook \u03c3\u03c4\u03bf Plaato Airlock.\n\n\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b1\u03ba\u03cc\u03bb\u03bf\u03c5\u03b8\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2:\n\n- URL: `{webhook_url}`\n- \u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2: \n\n\u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]({docs_url}) \u03b3\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2.", + "title": "Webhook \u03b3\u03b9\u03b1 \u03c7\u03c1\u03ae\u03c3\u03b7" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7\u03c2 (\u03bb\u03b5\u03c0\u03c4\u03ac)" + }, + "description": "\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf \u03b4\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7\u03c2 (\u03bb\u03b5\u03c0\u03c4\u03ac)", + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf Plaato" + }, + "webhook": { + "description": "\u03a0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 Webhook: \n\n - URL: `{webhook_url}`\n - \u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2: POST\n\n", + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf Plaato Airlock" } } } diff --git a/homeassistant/components/plugwise/translations/el.json b/homeassistant/components/plugwise/translations/el.json index b81e93e09fd61f..e9e8ad9bf5ae59 100644 --- a/homeassistant/components/plugwise/translations/el.json +++ b/homeassistant/components/plugwise/translations/el.json @@ -2,6 +2,9 @@ "config": { "step": { "user": { + "data": { + "flow_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "description": "\u03a0\u03c1\u03bf\u03ca\u03cc\u03bd:", "title": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03b2\u03cd\u03c3\u03bc\u03b1\u03c4\u03bf\u03c2" } diff --git a/homeassistant/components/poolsense/translations/el.json b/homeassistant/components/poolsense/translations/el.json new file mode 100644 index 00000000000000..8b1e7f9d28205c --- /dev/null +++ b/homeassistant/components/poolsense/translations/el.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "title": "PoolSense" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/ja.json b/homeassistant/components/powerwall/translations/ja.json index c53bbe573ec50b..be7078143b05e7 100644 --- a/homeassistant/components/powerwall/translations/ja.json +++ b/homeassistant/components/powerwall/translations/ja.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", "reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f" }, "error": { @@ -12,6 +13,17 @@ }, "flow_title": "{ip_address}", "step": { + "confirm_discovery": { + "description": "{name} ({ip_address}) \u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u304b\uff1f", + "title": "Powerwall\u306b\u63a5\u7d9a" + }, + "reauth_confim": { + "data": { + "password": "\u30d1\u30b9\u30ef\u30fc\u30c9" + }, + "description": "\u30d1\u30b9\u30ef\u30fc\u30c9\u306f\u901a\u5e38\u3001Backup Gateway\u306e\u30b7\u30ea\u30a2\u30eb\u756a\u53f7\u306e\u6700\u5f8c\u306e5\u6587\u5b57\u3067\u3042\u308a\u3001Tesla\u30a2\u30d7\u30ea\u3067\u898b\u3064\u3051\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002\u307e\u305f\u306f\u3001Backup Gateway2\u306e\u30c9\u30a2\u306e\u5185\u5074\u306b\u3042\u308b\u30d1\u30b9\u30ef\u30fc\u30c9\u306e\u6700\u5f8c\u306e5\u6587\u5b57\u3067\u3059\u3002", + "title": "powerwall\u306e\u518d\u8a8d\u8a3c" + }, "user": { "data": { "ip_address": "IP\u30a2\u30c9\u30ec\u30b9", diff --git a/homeassistant/components/powerwall/translations/nl.json b/homeassistant/components/powerwall/translations/nl.json index 87b78e719d0e24..a59a3efc089af0 100644 --- a/homeassistant/components/powerwall/translations/nl.json +++ b/homeassistant/components/powerwall/translations/nl.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", + "cannot_connect": "Kan geen verbinding maken", "reauth_successful": "Herauthenticatie was succesvol" }, "error": { @@ -12,6 +13,17 @@ }, "flow_title": "({ip_adres})", "step": { + "confirm_discovery": { + "description": "Wilt u {name} ({ip_address}) instellen?", + "title": "Maak verbinding met de powerwall" + }, + "reauth_confim": { + "data": { + "password": "Wachtwoord" + }, + "description": "Het wachtwoord is meestal de laatste 5 tekens van het serienummer voor Backup Gateway en is te vinden in de Tesla-app of de laatste 5 tekens van het wachtwoord aan de binnenkant van de deur voor Backup Gateway 2.", + "title": "De powerwall opnieuw verifi\u00ebren" + }, "user": { "data": { "ip_address": "IP-adres", diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/el.json b/homeassistant/components/pvpc_hourly_pricing/translations/el.json index 1f842e20cf6e78..4eac27f1b1a712 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/el.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/el.json @@ -4,6 +4,21 @@ "user": { "data": { "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2", + "power": "\u03a3\u03c5\u03bc\u03b2\u03b1\u03c4\u03b9\u03ba\u03ae \u03b9\u03c3\u03c7\u03cd\u03c2 (kW)", + "power_p3": "\u03a3\u03c5\u03bc\u03b2\u03b1\u03c4\u03b9\u03ba\u03ae \u03b9\u03c3\u03c7\u03cd\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c0\u03b5\u03c1\u03af\u03bf\u03b4\u03bf \u03ba\u03bf\u03b9\u03bb\u03ac\u03b4\u03b1\u03c2 P3 (kW)", + "tariff": "\u0399\u03c3\u03c7\u03cd\u03bf\u03bd \u03c4\u03b9\u03bc\u03bf\u03bb\u03cc\u03b3\u03b9\u03bf \u03b1\u03bd\u03ac \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03ae \u03b6\u03ce\u03bd\u03b7" + }, + "description": "\u0391\u03c5\u03c4\u03cc\u03c2 \u03bf \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03c4\u03bf \u03b5\u03c0\u03af\u03c3\u03b7\u03bc\u03bf API \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03ac\u03b2\u03b5\u03b9 [\u03c9\u03c1\u03b9\u03b1\u03af\u03b1 \u03c4\u03b9\u03bc\u03bf\u03bb\u03cc\u03b3\u03b7\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b7\u03bb\u03b5\u03ba\u03c4\u03c1\u03b9\u03ba\u03ae\u03c2 \u03b5\u03bd\u03ad\u03c1\u03b3\u03b5\u03b9\u03b1\u03c2 (PVPC)](https://www.esios.ree.es/es/pvpc) \u03c3\u03c4\u03b7\u03bd \u0399\u03c3\u03c0\u03b1\u03bd\u03af\u03b1.\n\u0393\u03b9\u03b1 \u03c0\u03b9\u03bf \u03b1\u03ba\u03c1\u03b9\u03b2\u03b5\u03af\u03c2 \u03b5\u03be\u03b7\u03b3\u03ae\u03c3\u03b5\u03b9\u03c2 \u03b5\u03c0\u03b9\u03c3\u03ba\u03b5\u03c6\u03b8\u03b5\u03af\u03c4\u03b5 \u03c4\u03b1 [integration docs](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "power": "\u03a3\u03c5\u03bc\u03b2\u03b1\u03c4\u03b9\u03ba\u03ae \u03b9\u03c3\u03c7\u03cd\u03c2 (kW)", + "power_p3": "\u03a3\u03c5\u03bc\u03b2\u03b1\u03c4\u03b9\u03ba\u03ae \u03b9\u03c3\u03c7\u03cd\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c0\u03b5\u03c1\u03af\u03bf\u03b4\u03bf \u03ba\u03bf\u03b9\u03bb\u03ac\u03b4\u03b1\u03c2 P3 (kW)", "tariff": "\u0399\u03c3\u03c7\u03cd\u03bf\u03bd \u03c4\u03b9\u03bc\u03bf\u03bb\u03cc\u03b3\u03b9\u03bf \u03b1\u03bd\u03ac \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03ae \u03b6\u03ce\u03bd\u03b7" }, "description": "\u0391\u03c5\u03c4\u03cc\u03c2 \u03bf \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03c4\u03bf \u03b5\u03c0\u03af\u03c3\u03b7\u03bc\u03bf API \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03ac\u03b2\u03b5\u03b9 [\u03c9\u03c1\u03b9\u03b1\u03af\u03b1 \u03c4\u03b9\u03bc\u03bf\u03bb\u03cc\u03b3\u03b7\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b7\u03bb\u03b5\u03ba\u03c4\u03c1\u03b9\u03ba\u03ae\u03c2 \u03b5\u03bd\u03ad\u03c1\u03b3\u03b5\u03b9\u03b1\u03c2 (PVPC)](https://www.esios.ree.es/es/pvpc) \u03c3\u03c4\u03b7\u03bd \u0399\u03c3\u03c0\u03b1\u03bd\u03af\u03b1.\n\u0393\u03b9\u03b1 \u03c0\u03b9\u03bf \u03b1\u03ba\u03c1\u03b9\u03b2\u03b5\u03af\u03c2 \u03b5\u03be\u03b7\u03b3\u03ae\u03c3\u03b5\u03b9\u03c2 \u03b5\u03c0\u03b9\u03c3\u03ba\u03b5\u03c6\u03b8\u03b5\u03af\u03c4\u03b5 \u03c4\u03b1 [integration docs](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", diff --git a/homeassistant/components/rachio/translations/el.json b/homeassistant/components/rachio/translations/el.json index 4e3e9483945721..f8c2e32c5df73f 100644 --- a/homeassistant/components/rachio/translations/el.json +++ b/homeassistant/components/rachio/translations/el.json @@ -6,5 +6,14 @@ "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c3\u03b1\u03c2 Rachio" } } + }, + "options": { + "step": { + "init": { + "data": { + "manual_run_mins": "\u0394\u03b9\u03ac\u03c1\u03ba\u03b5\u03b9\u03b1 \u03c3\u03b5 \u03bb\u03b5\u03c0\u03c4\u03ac \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03bd\u03cc\u03c2 \u03b4\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b7 \u03b6\u03ce\u03bd\u03b7\u03c2" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/el.json b/homeassistant/components/rfxtrx/translations/el.json index e6f90beed31d46..cbc8f9fb3543c7 100644 --- a/homeassistant/components/rfxtrx/translations/el.json +++ b/homeassistant/components/rfxtrx/translations/el.json @@ -32,7 +32,8 @@ }, "device_automation": { "action_type": { - "send_command": "\u0391\u03c0\u03bf\u03c3\u03c4\u03bf\u03bb\u03ae \u03b5\u03bd\u03c4\u03bf\u03bb\u03ae\u03c2: {subtype}" + "send_command": "\u0391\u03c0\u03bf\u03c3\u03c4\u03bf\u03bb\u03ae \u03b5\u03bd\u03c4\u03bf\u03bb\u03ae\u03c2: {subtype}", + "send_status": "\u0391\u03c0\u03bf\u03c3\u03c4\u03bf\u03bb\u03ae \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7\u03c2 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2: {subtype}" }, "trigger_type": { "command": "\u039b\u03ae\u03c6\u03b8\u03b7\u03ba\u03b5 \u03b5\u03bd\u03c4\u03bf\u03bb\u03ae: {subtype}", diff --git a/homeassistant/components/roku/translations/el.json b/homeassistant/components/roku/translations/el.json index 7850ff1c34d53e..204ada67d7c360 100644 --- a/homeassistant/components/roku/translations/el.json +++ b/homeassistant/components/roku/translations/el.json @@ -5,6 +5,10 @@ "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name};", "title": "Roku" }, + "ssdp_confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name};", + "title": "Roku" + }, "user": { "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 Roku \u03c3\u03b1\u03c2." } diff --git a/homeassistant/components/roomba/translations/el.json b/homeassistant/components/roomba/translations/el.json index dad23eecdcf350..551ff120de76ae 100644 --- a/homeassistant/components/roomba/translations/el.json +++ b/homeassistant/components/roomba/translations/el.json @@ -6,6 +6,25 @@ }, "flow_title": "{name} ({host})", "step": { + "init": { + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 Roomba \u03ae Braava.", + "title": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" + }, + "link": { + "description": "\u03a0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 \u03ba\u03b1\u03b9 \u03ba\u03c1\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c0\u03b1\u03c4\u03b7\u03bc\u03ad\u03bd\u03bf \u03c4\u03bf \u03c0\u03bb\u03ae\u03ba\u03c4\u03c1\u03bf Home \u03c3\u03c4\u03bf {name} \u03bc\u03ad\u03c7\u03c1\u03b9 \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bd\u03b1 \u03c0\u03b1\u03c1\u03ac\u03b3\u03b5\u03b9 \u03ad\u03bd\u03b1\u03bd \u03ae\u03c7\u03bf (\u03c0\u03b5\u03c1\u03af\u03c0\u03bf\u03c5 \u03b4\u03cd\u03bf \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1) \u03ba\u03b1\u03b9, \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c5\u03c0\u03bf\u03b2\u03ac\u03bb\u03b5\u03c4\u03b5 \u03b5\u03bd\u03c4\u03cc\u03c2 30 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03bf\u03bb\u03ad\u03c0\u03c4\u03c9\u03bd.", + "title": "\u0391\u03bd\u03ac\u03ba\u03c4\u03b7\u03c3\u03b7 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd" + }, + "link_manual": { + "description": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b4\u03b5\u03bd \u03bc\u03c0\u03cc\u03c1\u03b5\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b1\u03ba\u03c4\u03b7\u03b8\u03b5\u03af \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae. \u0391\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03b2\u03ae\u03bc\u03b1\u03c4\u03b1 \u03c0\u03bf\u03c5 \u03c0\u03b5\u03c1\u03b9\u03b3\u03c1\u03ac\u03c6\u03bf\u03bd\u03c4\u03b1\u03b9 \u03c3\u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7: {auth_help_url}", + "title": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, + "manual": { + "data": { + "blid": "BLID" + }, + "description": "\u0394\u03b5\u03bd \u03ad\u03c7\u03bf\u03c5\u03bd \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03c5\u03c6\u03b8\u03b5\u03af Roomba \u03ae Braava \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03cc \u03c3\u03b1\u03c2.", + "title": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" + }, "user": { "data": { "blid": "BLID", diff --git a/homeassistant/components/select/translations/el.json b/homeassistant/components/select/translations/el.json new file mode 100644 index 00000000000000..8a43dab9681591 --- /dev/null +++ b/homeassistant/components/select/translations/el.json @@ -0,0 +1,14 @@ +{ + "device_automation": { + "action_type": { + "select_option": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ae \u03c4\u03b7\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae\u03c2 {entity_name}" + }, + "condition_type": { + "selected_option": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae {entity_name}" + }, + "trigger_type": { + "current_option_changed": "\u0397 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae {entity_name} \u03ac\u03bb\u03bb\u03b1\u03be\u03b5" + } + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae" +} \ No newline at end of file diff --git a/homeassistant/components/sense/translations/el.json b/homeassistant/components/sense/translations/el.json index 1fa97f42105026..5cec565d287ee0 100644 --- a/homeassistant/components/sense/translations/el.json +++ b/homeassistant/components/sense/translations/el.json @@ -2,6 +2,9 @@ "config": { "step": { "user": { + "data": { + "timeout": "\u03a7\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03cc\u03c1\u03b9\u03bf" + }, "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf Sense Energy Monitor" } } diff --git a/homeassistant/components/sensor/translations/el.json b/homeassistant/components/sensor/translations/el.json index 9838417fb498e1..bc8b40943374ca 100644 --- a/homeassistant/components/sensor/translations/el.json +++ b/homeassistant/components/sensor/translations/el.json @@ -1,16 +1,27 @@ { "device_automation": { "condition_type": { + "is_apparent_power": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03c6\u03b1\u03b9\u03bd\u03bf\u03bc\u03b5\u03bd\u03b9\u03ba\u03ae \u03b9\u03c3\u03c7\u03cd\u03c2 {entity_name}", + "is_battery_level": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03bc\u03c0\u03b1\u03c4\u03b1\u03c1\u03af\u03b1\u03c2 {entity_name}", "is_frequency": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03c3\u03c5\u03c7\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 {entity_name}", "is_gas": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b1\u03ad\u03c1\u03b9\u03bf {entity_name}", + "is_humidity": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03c5\u03b3\u03c1\u03b1\u03c3\u03af\u03b1 {entity_name}", + "is_illuminance": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03c6\u03c9\u03c4\u03b5\u03b9\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 {entity_name}", + "is_nitrogen_dioxide": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03b4\u03b9\u03bf\u03be\u03b5\u03b9\u03b4\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 \u03b1\u03b6\u03ce\u03c4\u03bf\u03c5 {entity_name}", "is_ozone": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03cc\u03b6\u03bf\u03bd\u03c4\u03bf\u03c2 {entity_name}", "is_pm1": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 PM1 {entity_name}", "is_pm10": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 PM10 {entity_name}", "is_pm25": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 {entity_name} PM2.5", + "is_power": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03b9\u03c3\u03c7\u03cd\u03c2 {entity_name}", + "is_pressure": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03c0\u03af\u03b5\u03c3\u03b7 {entity_name}", + "is_reactive_power": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03ac\u03b5\u03c1\u03b3\u03b7 \u03b9\u03c3\u03c7\u03cd\u03c2 {entity_name}", + "is_signal_strength": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03b9\u03c3\u03c7\u03cd\u03c2 \u03c3\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2 {entity_name}", "is_sulphur_dioxide": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03b4\u03b9\u03bf\u03be\u03b5\u03b9\u03b4\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 \u03b8\u03b5\u03af\u03bf\u03c5 {entity_name}", + "is_temperature": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1 {entity_name}", "is_volatile_organic_compounds": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03c0\u03c4\u03b7\u03c4\u03b9\u03ba\u03ce\u03bd \u03bf\u03c1\u03b3\u03b1\u03bd\u03b9\u03ba\u03ce\u03bd \u03b5\u03bd\u03ce\u03c3\u03b5\u03c9\u03bd {entity_name}" }, "trigger_type": { + "battery_level": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ae \u03b5\u03c0\u03b9\u03c0\u03ad\u03b4\u03bf\u03c5 \u03bc\u03c0\u03b1\u03c4\u03b1\u03c1\u03af\u03b1\u03c2 \u03b3\u03b9\u03b1 {entity_name}", "frequency": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ad\u03c2 \u03c3\u03c5\u03c7\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 {entity_name}", "gas": "{entity_name} \u03bc\u03b5\u03c4\u03b1\u03b2\u03bf\u03bb\u03ad\u03c2 \u03b1\u03b5\u03c1\u03af\u03bf\u03c5", "nitrogen_dioxide": "{entity_name} \u03bc\u03b5\u03c4\u03b1\u03b2\u03bf\u03bb\u03ad\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03b4\u03b9\u03bf\u03be\u03b5\u03b9\u03b4\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 \u03b1\u03b6\u03ce\u03c4\u03bf\u03c5", diff --git a/homeassistant/components/shelly/translations/el.json b/homeassistant/components/shelly/translations/el.json index 9a899257addf44..c87fb65f44634a 100644 --- a/homeassistant/components/shelly/translations/el.json +++ b/homeassistant/components/shelly/translations/el.json @@ -20,6 +20,9 @@ "button2": "\u0394\u03b5\u03cd\u03c4\u03b5\u03c1\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af", "button3": "\u03a4\u03c1\u03af\u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af", "button4": "\u03a4\u03ad\u03c4\u03b1\u03c1\u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af" + }, + "trigger_type": { + "triple": "\u03a4\u03c1\u03b9\u03c0\u03bb\u03cc \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf {subtype}" } } } \ No newline at end of file diff --git a/homeassistant/components/simplisafe/translations/el.json b/homeassistant/components/simplisafe/translations/el.json index 35cb5786fd2c1e..3ba3fdb4330dac 100644 --- a/homeassistant/components/simplisafe/translations/el.json +++ b/homeassistant/components/simplisafe/translations/el.json @@ -1,13 +1,21 @@ { "config": { "abort": { - "already_configured": "\u0391\u03c5\u03c4\u03cc\u03c2 \u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 SimpliSafe \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7." + "already_configured": "\u0391\u03c5\u03c4\u03cc\u03c2 \u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 SimpliSafe \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7.", + "wrong_account": "\u03a4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03c0\u03bf\u03c5 \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b4\u03b5\u03bd \u03c4\u03b1\u03b9\u03c1\u03b9\u03ac\u03b6\u03bf\u03c5\u03bd \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc SimpliSafe." }, "error": { "identifier_exists": "\u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ae\u03b4\u03b7 \u03ba\u03b1\u03c4\u03b1\u03c7\u03c9\u03c1\u03b7\u03bc\u03ad\u03bd\u03bf\u03c2", "still_awaiting_mfa": "\u0391\u03bd\u03b1\u03bc\u03ad\u03bd\u03b5\u03c4\u03b1\u03b9 \u03b1\u03ba\u03cc\u03bc\u03b7 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf email \u03c4\u03bf\u03c5 \u03a5\u03c0\u03bf\u03c5\u03c1\u03b3\u03b5\u03af\u03bf\u03c5 \u039f\u03b9\u03ba\u03bf\u03bd\u03bf\u03bc\u03b9\u03ba\u03ce\u03bd" }, "step": { + "input_auth_code": { + "data": { + "auth_code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2" + }, + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c4\u03b7\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2 SimpliSafe web:", + "title": "\u039f\u03bb\u03bf\u03ba\u03bb\u03ae\u03c1\u03c9\u03c3\u03b7 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2" + }, "mfa": { "description": "\u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03bf email \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03ad\u03bd\u03b1\u03bd \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd SimpliSafe. \u0391\u03c6\u03bf\u03cd \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf, \u03b5\u03c0\u03b9\u03c3\u03c4\u03c1\u03ad\u03c8\u03c4\u03b5 \u03b5\u03b4\u03ce \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03ce\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2.", "title": "\u03a0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ce\u03bd \u03c0\u03b1\u03c1\u03b1\u03b3\u03cc\u03bd\u03c4\u03c9\u03bd SimpliSafe" @@ -17,8 +25,10 @@ }, "user": { "data": { + "auth_code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2", "code": "\u039a\u03ce\u03b4\u03b9\u03ba\u03b1\u03c2 (\u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03c3\u03c4\u03bf UI \u03c4\u03bf\u03c5 Home Assistant)" }, + "description": "\u03a4\u03bf SimpliSafe \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03c4\u03bf Home Assistant \u03bc\u03ad\u03c3\u03c9 \u03c4\u03b7\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2 SimpliSafe web. \u039b\u03cc\u03b3\u03c9 \u03c4\u03b5\u03c7\u03bd\u03b9\u03ba\u03ce\u03bd \u03c0\u03b5\u03c1\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03ce\u03bd, \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03ad\u03bd\u03b1 \u03c7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03bf \u03b2\u03ae\u03bc\u03b1 \u03c3\u03c4\u03bf \u03c4\u03ad\u03bb\u03bf\u03c2 \u03b1\u03c5\u03c4\u03ae\u03c2 \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03b9\u03ba\u03b1\u03c3\u03af\u03b1\u03c2- \u03b2\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03ad\u03c7\u03b5\u03c4\u03b5 \u03b4\u03b9\u03b1\u03b2\u03ac\u03c3\u03b5\u03b9 \u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]({docs_url}) \u03c0\u03c1\u03b9\u03bd \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5.\n\n1. \u039a\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf [\u03b5\u03b4\u03ce]({url}) \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03bd\u03bf\u03af\u03be\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae SimpliSafe web \u03ba\u03b1\u03b9 \u03bd\u03b1 \u03b5\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2.\n\n2. \u038c\u03c4\u03b1\u03bd \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03c9\u03b8\u03b5\u03af \u03b7 \u03b4\u03b9\u03b1\u03b4\u03b9\u03ba\u03b1\u03c3\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2, \u03b5\u03c0\u03b9\u03c3\u03c4\u03c1\u03ad\u03c8\u03c4\u03b5 \u03b5\u03b4\u03ce \u03ba\u03b1\u03b9 \u03b5\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2.", "title": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03b1\u03c2" } } diff --git a/homeassistant/components/smartthings/translations/el.json b/homeassistant/components/smartthings/translations/el.json index 5900d42cba9fe2..f7da38143eafca 100644 --- a/homeassistant/components/smartthings/translations/el.json +++ b/homeassistant/components/smartthings/translations/el.json @@ -8,6 +8,9 @@ "webhook_error": "\u03a4\u03bf SmartThings \u03b4\u03b5\u03bd \u03bc\u03c0\u03cc\u03c1\u03b5\u03c3\u03b5 \u03bd\u03b1 \u03b5\u03c0\u03b9\u03ba\u03c5\u03c1\u03ce\u03c3\u03b5\u03b9 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c4\u03bf\u03c5 webhook. \u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c4\u03bf\u03c5 webhook \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c3\u03b2\u03ac\u03c3\u03b9\u03bc\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf \u03b4\u03b9\u03b1\u03b4\u03af\u03ba\u03c4\u03c5\u03bf \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac." }, "step": { + "authorize": { + "title": "\u0395\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7 Home Assistant" + }, "pat": { "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 SmartThings [Personal Access Token]({token_url}) \u03c0\u03bf\u03c5 \u03ad\u03c7\u03b5\u03b9 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03b7\u03b8\u03b5\u03af \u03c3\u03cd\u03bc\u03c6\u03c9\u03bd\u03b1 \u03bc\u03b5 \u03c4\u03b9\u03c2 [\u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2]({component_url}). \u0391\u03c5\u03c4\u03cc \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 Home Assistant \u03c3\u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 SmartThings.", "title": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03bf\u03c3\u03c9\u03c0\u03b9\u03ba\u03ae\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" diff --git a/homeassistant/components/solarlog/translations/el.json b/homeassistant/components/solarlog/translations/el.json new file mode 100644 index 00000000000000..9970910c04dd73 --- /dev/null +++ b/homeassistant/components/solarlog/translations/el.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "\u03a4\u03bf \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 \u03c0\u03bf\u03c5 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c4\u03bf\u03c5\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b5\u03c2 Solar-Log" + }, + "title": "\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 Solar-Log" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/el.json b/homeassistant/components/somfy_mylink/translations/el.json index 29a6b53d5506fe..cb96280313692d 100644 --- a/homeassistant/components/somfy_mylink/translations/el.json +++ b/homeassistant/components/somfy_mylink/translations/el.json @@ -1,13 +1,40 @@ { "config": { - "flow_title": "{mac} ({ip})" + "flow_title": "{mac} ({ip})", + "step": { + "user": { + "data": { + "system_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c3\u03c5\u03c3\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2" + }, + "description": "\u03a4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c3\u03c5\u03c3\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2 \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03bb\u03b7\u03c6\u03b8\u03b5\u03af \u03c3\u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae MyLink \u03c3\u03c4\u03b7\u03bd \u03b5\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u0395\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03ad\u03b3\u03bf\u03bd\u03c4\u03b1\u03c2 \u03bf\u03c0\u03bf\u03b9\u03b1\u03b4\u03ae\u03c0\u03bf\u03c4\u03b5 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03c0\u03bf\u03c5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 Cloud." + } + } }, "options": { "step": { + "entity_config": { + "data": { + "reverse": "\u03a4\u03bf \u03ba\u03ac\u03bb\u03c5\u03bc\u03bc\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03bd\u03c4\u03b5\u03c3\u03c4\u03c1\u03b1\u03bc\u03bc\u03ad\u03bd\u03bf" + }, + "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd \u03b3\u03b9\u03b1 `{entity_id}`", + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, + "init": { + "data": { + "default_reverse": "\u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03b1\u03bd\u03c4\u03b9\u03c3\u03c4\u03c1\u03bf\u03c6\u03ae\u03c2 \u03b3\u03b9\u03b1 \u03bc\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03bc\u03ad\u03bd\u03b1 \u03ba\u03b1\u03bb\u03cd\u03bc\u03bc\u03b1\u03c4\u03b1", + "entity_id": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03bc\u03b9\u03b1\u03c2 \u03c3\u03c5\u03b3\u03ba\u03b5\u03ba\u03c1\u03b9\u03bc\u03ad\u03bd\u03b7\u03c2 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2.", + "target_id": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd \u03b3\u03b9\u03b1 \u03ad\u03bd\u03b1 \u03ba\u03ac\u03bb\u03c5\u03bc\u03bc\u03b1." + }, + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd MyLink" + }, "target_config": { + "data": { + "reverse": "\u03a4\u03bf \u03ba\u03ac\u03bb\u03c5\u03bc\u03bc\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03bd\u03b5\u03c3\u03c4\u03c1\u03b1\u03bc\u03bc\u03ad\u03bd\u03bf" + }, "description": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c4\u03bf `{target_name}`", "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 MyLink Cover" } } - } + }, + "title": "Somfy MyLink" } \ No newline at end of file diff --git a/homeassistant/components/squeezebox/translations/el.json b/homeassistant/components/squeezebox/translations/el.json new file mode 100644 index 00000000000000..1a4a2627af3095 --- /dev/null +++ b/homeassistant/components/squeezebox/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "no_server_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 LMS." + }, + "error": { + "no_server_found": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae." + }, + "flow_title": "{host}" + } +} \ No newline at end of file diff --git a/homeassistant/components/stookalert/translations/el.json b/homeassistant/components/stookalert/translations/el.json new file mode 100644 index 00000000000000..20cabc1bbe74cc --- /dev/null +++ b/homeassistant/components/stookalert/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "province": "\u0395\u03c0\u03b1\u03c1\u03c7\u03af\u03b1" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/el.json b/homeassistant/components/subaru/translations/el.json index f0d2561e4eed36..78b2a8f80889a8 100644 --- a/homeassistant/components/subaru/translations/el.json +++ b/homeassistant/components/subaru/translations/el.json @@ -1,7 +1,8 @@ { "config": { "error": { - "bad_pin_format": "\u03a4\u03bf PIN \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 4 \u03c8\u03b7\u03c6\u03af\u03b1" + "bad_pin_format": "\u03a4\u03bf PIN \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 4 \u03c8\u03b7\u03c6\u03af\u03b1", + "incorrect_pin": "\u039b\u03b1\u03bd\u03b8\u03b1\u03c3\u03bc\u03ad\u03bd\u03bf PIN" }, "step": { "pin": { diff --git a/homeassistant/components/syncthing/translations/el.json b/homeassistant/components/syncthing/translations/el.json index d71bea91ddb3ad..4d7c3f964d40ae 100644 --- a/homeassistant/components/syncthing/translations/el.json +++ b/homeassistant/components/syncthing/translations/el.json @@ -3,7 +3,8 @@ "step": { "user": { "data": { - "title": "\u0395\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 Syncthing" + "title": "\u0395\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 Syncthing", + "token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc" } } } diff --git a/homeassistant/components/synology_dsm/translations/el.json b/homeassistant/components/synology_dsm/translations/el.json index 0f69f0e96c9fa9..539792754bc0dd 100644 --- a/homeassistant/components/synology_dsm/translations/el.json +++ b/homeassistant/components/synology_dsm/translations/el.json @@ -20,7 +20,8 @@ "title": "Synology DSM" }, "reauth": { - "description": "\u0391\u03b9\u03c4\u03af\u03b1: {details}" + "description": "\u0391\u03b9\u03c4\u03af\u03b1: {details}", + "title": "Synology DSM \u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, "reauth_confirm": { "title": "Synology DSM \u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" diff --git a/homeassistant/components/tado/translations/el.json b/homeassistant/components/tado/translations/el.json index 319a399445947e..03e8b3f1513635 100644 --- a/homeassistant/components/tado/translations/el.json +++ b/homeassistant/components/tado/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "no_homes": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03bd \u03c3\u03c0\u03af\u03c4\u03b9\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03b1 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc tado." + }, "step": { "user": { "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 Tado" diff --git a/homeassistant/components/tag/translations/el.json b/homeassistant/components/tag/translations/el.json new file mode 100644 index 00000000000000..192b190cf37459 --- /dev/null +++ b/homeassistant/components/tag/translations/el.json @@ -0,0 +1,3 @@ +{ + "title": "\u0395\u03c4\u03b9\u03ba\u03ad\u03c4\u03b1" +} \ No newline at end of file diff --git a/homeassistant/components/tibber/translations/el.json b/homeassistant/components/tibber/translations/el.json index 402d66a0d5576d..6bf5e8416b0eef 100644 --- a/homeassistant/components/tibber/translations/el.json +++ b/homeassistant/components/tibber/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { "timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03c4\u03bf Tibber" }, diff --git a/homeassistant/components/tile/translations/el.json b/homeassistant/components/tile/translations/el.json index 805c15c252a6ef..35f0bd1867ed8d 100644 --- a/homeassistant/components/tile/translations/el.json +++ b/homeassistant/components/tile/translations/el.json @@ -1,4 +1,11 @@ { + "config": { + "step": { + "user": { + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Tile" + } + } + }, "options": { "step": { "init": { diff --git a/homeassistant/components/totalconnect/translations/el.json b/homeassistant/components/totalconnect/translations/el.json index 49156d1cbc20e3..b61c256ef18547 100644 --- a/homeassistant/components/totalconnect/translations/el.json +++ b/homeassistant/components/totalconnect/translations/el.json @@ -4,9 +4,17 @@ "no_locations": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03bd \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b5\u03c2 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7, \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 TotalConnect" }, "error": { - "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03c5\u03b8\u03b5\u03bd\u03c4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7" + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03c5\u03b8\u03b5\u03bd\u03c4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", + "usercode": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03b4\u03b5\u03bd \u03b9\u03c3\u03c7\u03cd\u03b5\u03b9 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03c3\u03b5 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7\u03bd \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1" }, "step": { + "locations": { + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03c3\u03c4\u03b7\u03bd \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 {location_id}", + "title": "\u039a\u03c9\u03b4\u03b9\u03ba\u03bf\u03af \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2" + }, + "reauth_confirm": { + "description": "\u03a4\u03bf Total Connect \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2" + }, "user": { "title": "Total Connect" } diff --git a/homeassistant/components/tradfri/translations/el.json b/homeassistant/components/tradfri/translations/el.json index feee649656d64a..f4ccdf8b400afb 100644 --- a/homeassistant/components/tradfri/translations/el.json +++ b/homeassistant/components/tradfri/translations/el.json @@ -1,6 +1,7 @@ { "config": { "error": { + "cannot_authenticate": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2, \u03b5\u03af\u03bd\u03b1\u03b9 \u03b7 \u03c0\u03cd\u03bb\u03b7 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03b7 \u03bc\u03b5 \u03ad\u03bd\u03b1\u03bd \u03ac\u03bb\u03bb\u03bf \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae, \u03cc\u03c0\u03c9\u03c2 \u03c0.\u03c7. \u03c4\u03bf Homekit;", "invalid_key": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae \u03bc\u03b5 \u03c4\u03bf \u03c0\u03b1\u03c1\u03b5\u03c7\u03cc\u03bc\u03b5\u03bd\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af. \u0391\u03bd \u03b1\u03c5\u03c4\u03cc \u03c3\u03c5\u03bc\u03b2\u03b1\u03af\u03bd\u03b5\u03b9 \u03c3\u03c5\u03bd\u03b5\u03c7\u03ce\u03c2, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03cd\u03bb\u03b7.", "timeout": "\u03a7\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03cc\u03c1\u03b9\u03bf \u03b5\u03c0\u03b9\u03ba\u03cd\u03c1\u03c9\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd." }, diff --git a/homeassistant/components/transmission/translations/el.json b/homeassistant/components/transmission/translations/el.json index 9879388d0d83d2..e19942a36afa23 100644 --- a/homeassistant/components/transmission/translations/el.json +++ b/homeassistant/components/transmission/translations/el.json @@ -14,6 +14,7 @@ "init": { "data": { "limit": "\u038c\u03c1\u03b9\u03bf", + "order": "\u03a3\u03b5\u03b9\u03c1\u03ac", "scan_interval": "\u03a3\u03c5\u03c7\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7\u03c2" }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c4\u03bf Transmission" diff --git a/homeassistant/components/tuya/translations/el.json b/homeassistant/components/tuya/translations/el.json index f8d2ba4acb0645..b25fbd9f5df0c8 100644 --- a/homeassistant/components/tuya/translations/el.json +++ b/homeassistant/components/tuya/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "login_error": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 ({code}): {msg}" + }, "flow_title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Tuya", "step": { "login": { @@ -15,8 +18,11 @@ }, "user": { "data": { + "access_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 Tuya IoT", + "access_secret": "\u039c\u03c5\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 Tuya IoT", "country_code": "\u03a7\u03ce\u03c1\u03b1", "platform": "\u0397 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u03c3\u03c4\u03b7\u03bd \u03bf\u03c0\u03bf\u03af\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03b3\u03b3\u03b5\u03b3\u03c1\u03b1\u03bc\u03bc\u03ad\u03bd\u03bf\u03c2 \u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03c3\u03b1\u03c2", + "region": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae", "username": "\u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2 Tuya", diff --git a/homeassistant/components/tuya/translations/select.ca.json b/homeassistant/components/tuya/translations/select.ca.json index 17846d1760de8c..2727efd1a08f8a 100644 --- a/homeassistant/components/tuya/translations/select.ca.json +++ b/homeassistant/components/tuya/translations/select.ca.json @@ -10,6 +10,15 @@ "1": "OFF", "2": "ON" }, + "tuya__countdown": { + "1h": "1 hora", + "2h": "2 hores", + "3h": "3 hores", + "4h": "4 hores", + "5h": "5 hores", + "6h": "6 hores", + "cancel": "Cancel\u00b7la" + }, "tuya__curtain_mode": { "morning": "Mat\u00ed", "night": "Nit" @@ -31,6 +40,32 @@ "click": "Polsador", "switch": "Interruptor" }, + "tuya__humidifier_level": { + "level_1": "Nivell 1", + "level_10": "Nivell 10", + "level_2": "Nivell 2", + "level_3": "Nivell 3", + "level_4": "Nivell 4", + "level_5": "Nivell 5", + "level_6": "Nivell 6", + "level_7": "Nivell 7", + "level_8": "Nivell 8", + "level_9": "Nivell 9" + }, + "tuya__humidifier_moodlighting": { + "1": "Estat 1", + "2": "Estat 2", + "3": "Estat 3", + "4": "Estat 4", + "5": "Estat 5" + }, + "tuya__humidifier_spray_mode": { + "auto": "Autom\u00e0tic", + "health": "Salut", + "humidity": "Humitat", + "sleep": "Dormir", + "work": "Feina" + }, "tuya__ipc_work_mode": { "0": "Mode de baix consum", "1": "Mode de funcionament continu" diff --git a/homeassistant/components/tuya/translations/select.de.json b/homeassistant/components/tuya/translations/select.de.json index 0aadad144437db..f49061c90aa44b 100644 --- a/homeassistant/components/tuya/translations/select.de.json +++ b/homeassistant/components/tuya/translations/select.de.json @@ -10,6 +10,15 @@ "1": "Aus", "2": "An" }, + "tuya__countdown": { + "1h": "1 Stunde", + "2h": "2 Stunden", + "3h": "3 Stunden", + "4h": "4 Stunden", + "5h": "5 Stunden", + "6h": "6 Stunden", + "cancel": "Abbrechen" + }, "tuya__curtain_mode": { "morning": "Morgen", "night": "Nacht" diff --git a/homeassistant/components/tuya/translations/select.el.json b/homeassistant/components/tuya/translations/select.el.json index f8c23abd578ecb..effe871645a2bb 100644 --- a/homeassistant/components/tuya/translations/select.el.json +++ b/homeassistant/components/tuya/translations/select.el.json @@ -8,6 +8,15 @@ "tuya__basic_nightvision": { "0": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03bf" }, + "tuya__countdown": { + "1h": "1 \u03ce\u03c1\u03b1", + "2h": "2 \u03ce\u03c1\u03b5\u03c2", + "3h": "3 \u03ce\u03c1\u03b5\u03c2", + "4h": "4 \u03ce\u03c1\u03b5\u03c2", + "5h": "5 \u03ce\u03c1\u03b5\u03c2", + "6h": "6 \u03ce\u03c1\u03b5\u03c2", + "cancel": "\u0391\u03ba\u03cd\u03c1\u03c9\u03c3\u03b7" + }, "tuya__curtain_mode": { "morning": "\u03a0\u03c1\u03c9\u03af", "night": "\u039d\u03cd\u03c7\u03c4\u03b1" @@ -25,6 +34,24 @@ "click": "\u03a0\u03af\u03b5\u03c3\u03b5", "switch": "\u0394\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b7\u03c2" }, + "tuya__ipc_work_mode": { + "0": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c7\u03b1\u03bc\u03b7\u03bb\u03ae\u03c2 \u03b9\u03c3\u03c7\u03cd\u03bf\u03c2", + "1": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c3\u03c5\u03bd\u03b5\u03c7\u03bf\u03cd\u03c2 \u03b5\u03c1\u03b3\u03b1\u03c3\u03af\u03b1\u03c2" + }, + "tuya__led_type": { + "halogen": "\u0391\u03bb\u03bf\u03b3\u03cc\u03bd\u03bf\u03c5", + "incandescent": "\u03a0\u03c5\u03c1\u03b1\u03ba\u03c4\u03ce\u03c3\u03b5\u03c9\u03c2", + "led": "LED" + }, + "tuya__motion_sensitivity": { + "0": "\u03a7\u03b1\u03bc\u03b7\u03bb\u03ae \u03b5\u03c5\u03b1\u03b9\u03c3\u03b8\u03b7\u03c3\u03af\u03b1", + "1": "\u039c\u03b5\u03c3\u03b1\u03af\u03b1 \u03b5\u03c5\u03b1\u03b9\u03c3\u03b8\u03b7\u03c3\u03af\u03b1", + "2": "\u03a5\u03c8\u03b7\u03bb\u03ae \u03b5\u03c5\u03b1\u03b9\u03c3\u03b8\u03b7\u03c3\u03af\u03b1" + }, + "tuya__record_mode": { + "1": "\u039a\u03b1\u03c4\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03c9\u03bd \u03bc\u03cc\u03bd\u03bf", + "2": "\u03a3\u03c5\u03bd\u03b5\u03c7\u03ae\u03c2 \u03ba\u03b1\u03c4\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae" + }, "tuya__vacuum_cistern": { "closed": "\u039a\u03bb\u03b5\u03b9\u03c3\u03c4\u03cc", "high": "\u03a5\u03c8\u03b7\u03bb\u03cc", diff --git a/homeassistant/components/tuya/translations/select.en.json b/homeassistant/components/tuya/translations/select.en.json index 29c3400008991a..65d4bdeca80ac1 100644 --- a/homeassistant/components/tuya/translations/select.en.json +++ b/homeassistant/components/tuya/translations/select.en.json @@ -10,6 +10,15 @@ "1": "Off", "2": "On" }, + "tuya__countdown": { + "1h": "1 hour", + "2h": "2 hours", + "3h": "3 hours", + "4h": "4 hours", + "5h": "5 hours", + "6h": "6 hours", + "cancel": "Cancel" + }, "tuya__curtain_mode": { "morning": "Morning", "night": "Night" @@ -31,6 +40,32 @@ "click": "Push", "switch": "Switch" }, + "tuya__humidifier_level": { + "level_1": "Level 1", + "level_10": "Level 10", + "level_2": "Level 2", + "level_3": "Level 3", + "level_4": "Level 4", + "level_5": "Level 5", + "level_6": "Level 6", + "level_7": "Level 7", + "level_8": "Level 8", + "level_9": "Level 9" + }, + "tuya__humidifier_moodlighting": { + "1": "Mood 1", + "2": "Mood 2", + "3": "Mood 3", + "4": "Mood 4", + "5": "Mood 5" + }, + "tuya__humidifier_spray_mode": { + "auto": "Auto", + "health": "Health", + "humidity": "Humidity", + "sleep": "Sleep", + "work": "Work" + }, "tuya__ipc_work_mode": { "0": "Low power mode", "1": "Continuous working mode" diff --git a/homeassistant/components/tuya/translations/select.pt-BR.json b/homeassistant/components/tuya/translations/select.pt-BR.json index e32b8ebcd3c40f..aed86dfe4ce051 100644 --- a/homeassistant/components/tuya/translations/select.pt-BR.json +++ b/homeassistant/components/tuya/translations/select.pt-BR.json @@ -10,6 +10,15 @@ "1": "Desligado", "2": "Ligado" }, + "tuya__countdown": { + "1h": "1 hora", + "2h": "2 horas", + "3h": "3 horas", + "4h": "4 horas", + "5h": "5 horas", + "6h": "6 horas", + "cancel": "Cancelar" + }, "tuya__curtain_mode": { "morning": "Manh\u00e3", "night": "Noite" @@ -31,6 +40,32 @@ "click": "Pulsador", "switch": "Interruptor" }, + "tuya__humidifier_level": { + "level_1": "N\u00edvel 1", + "level_10": "N\u00edvel 10", + "level_2": "N\u00edvel 2", + "level_3": "N\u00edvel 3", + "level_4": "N\u00edvel 4", + "level_5": "N\u00edvel 5", + "level_6": "N\u00edvel 6", + "level_7": "N\u00edvel 7", + "level_8": "N\u00edvel 8", + "level_9": "N\u00edvel 9" + }, + "tuya__humidifier_moodlighting": { + "1": "Ambiente 1", + "2": "Ambiente 2", + "3": "Ambiente 3", + "4": "Ambiente 4", + "5": "Ambiente 5" + }, + "tuya__humidifier_spray_mode": { + "auto": "Autom\u00e1tico", + "health": "Sa\u00fade", + "humidity": "Umidade", + "sleep": "Sono", + "work": "Trabalho" + }, "tuya__ipc_work_mode": { "0": "Modo de baixo consumo", "1": "Modo de trabalho cont\u00ednuo" diff --git a/homeassistant/components/tuya/translations/sensor.ca.json b/homeassistant/components/tuya/translations/sensor.ca.json index 681ae04107ad66..d5ecb8c52ab315 100644 --- a/homeassistant/components/tuya/translations/sensor.ca.json +++ b/homeassistant/components/tuya/translations/sensor.ca.json @@ -1,5 +1,11 @@ { "state": { + "tuya__air_quality": { + "good": "Bo", + "great": "Genial", + "mild": "Mitj\u00e0", + "severe": "Sever" + }, "tuya__status": { "boiling_temp": "Temperatura d'ebullici\u00f3", "cooling": "Refredant", diff --git a/homeassistant/components/tuya/translations/sensor.de.json b/homeassistant/components/tuya/translations/sensor.de.json index ffe5ddd2c99bda..01daca76a1168c 100644 --- a/homeassistant/components/tuya/translations/sensor.de.json +++ b/homeassistant/components/tuya/translations/sensor.de.json @@ -1,5 +1,11 @@ { "state": { + "tuya__air_quality": { + "good": "Gut", + "great": "Gro\u00dfartig", + "mild": "Mild", + "severe": "Stark" + }, "tuya__status": { "boiling_temp": "Siedetemperatur", "cooling": "K\u00fchlung", diff --git a/homeassistant/components/tuya/translations/sensor.el.json b/homeassistant/components/tuya/translations/sensor.el.json new file mode 100644 index 00000000000000..0f034693986112 --- /dev/null +++ b/homeassistant/components/tuya/translations/sensor.el.json @@ -0,0 +1,18 @@ +{ + "state": { + "tuya__air_quality": { + "good": "\u039a\u03b1\u03bb\u03ae", + "great": "\u0395\u03be\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ae", + "mild": "\u0389\u03c0\u03b9\u03b1", + "severe": "\u03a3\u03bf\u03b2\u03b1\u03c1\u03ae" + }, + "tuya__status": { + "boiling_temp": "\u0398\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1 \u03b2\u03c1\u03b1\u03c3\u03bc\u03bf\u03cd", + "cooling": "\u03a8\u03cd\u03be\u03b7", + "heating": "\u0398\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7", + "heating_temp": "\u0398\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1 \u03b8\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7\u03c2", + "standby": "\u039a\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03b1\u03bd\u03b1\u03bc\u03bf\u03bd\u03ae\u03c2", + "warm": "\u0394\u03b9\u03b1\u03c4\u03ae\u03c1\u03b7\u03c3\u03b7 \u03b8\u03b5\u03c1\u03bc\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/sensor.en.json b/homeassistant/components/tuya/translations/sensor.en.json index 4057f75c1eaf06..fda8003d3e756f 100644 --- a/homeassistant/components/tuya/translations/sensor.en.json +++ b/homeassistant/components/tuya/translations/sensor.en.json @@ -1,5 +1,11 @@ { "state": { + "tuya__air_quality": { + "good": "Good", + "great": "Great", + "mild": "Mild", + "severe": "Severe" + }, "tuya__status": { "boiling_temp": "Boiling temperature", "cooling": "Cooling", diff --git a/homeassistant/components/tuya/translations/sensor.pt-BR.json b/homeassistant/components/tuya/translations/sensor.pt-BR.json index b8e8beeb0940b2..eaf2621ff142e8 100644 --- a/homeassistant/components/tuya/translations/sensor.pt-BR.json +++ b/homeassistant/components/tuya/translations/sensor.pt-BR.json @@ -1,5 +1,11 @@ { "state": { + "tuya__air_quality": { + "good": "Bom", + "great": "\u00d3timo", + "mild": "Moderada", + "severe": "Grave" + }, "tuya__status": { "boiling_temp": "Temperatura de ebuli\u00e7\u00e3o", "cooling": "Resfriamento", diff --git a/homeassistant/components/twilio/translations/el.json b/homeassistant/components/twilio/translations/el.json index aecb2ee553fa42..9f791196b75964 100644 --- a/homeassistant/components/twilio/translations/el.json +++ b/homeassistant/components/twilio/translations/el.json @@ -2,6 +2,14 @@ "config": { "abort": { "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "create_entry": { + "default": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c4\u03b5\u03af\u03bb\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03b1 \u03c3\u03c4\u03bf\u03bd Home Assistant, \u03b8\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf [Webhooks with Twilio]( {twilio_url} ). \n\n \u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2: \n\n - URL: ` {webhook_url} `\n - \u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2: POST\n - \u03a4\u03cd\u03c0\u03bf\u03c2 \u03c0\u03b5\u03c1\u03b9\u03b5\u03c7\u03bf\u03bc\u03ad\u03bd\u03bf\u03c5: \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae/x-www-form-urlencoded \n\n \u0394\u03b5\u03af\u03c4\u03b5 [\u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]( {docs_url} ) \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03c4\u03bf\u03bd \u03c4\u03c1\u03cc\u03c0\u03bf \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b1\u03c5\u03c4\u03bf\u03bc\u03b1\u03c4\u03b9\u03c3\u03bc\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03c7\u03b5\u03af\u03c1\u03b9\u03c3\u03b7 \u03c4\u03c9\u03bd \u03b5\u03b9\u03c3\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03c9\u03bd \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd." + }, + "step": { + "user": { + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Twilio Webhook" + } } } } \ No newline at end of file diff --git a/homeassistant/components/twinkly/translations/el.json b/homeassistant/components/twinkly/translations/el.json index a3847cc08f8f5d..a73ad869c2c1de 100644 --- a/homeassistant/components/twinkly/translations/el.json +++ b/homeassistant/components/twinkly/translations/el.json @@ -1,6 +1,9 @@ { "config": { "step": { + "discovery_confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} - {model} ({host});" + }, "user": { "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf Twinkly led string \u03c3\u03b1\u03c2", "title": "Twinkly" diff --git a/homeassistant/components/upnp/translations/el.json b/homeassistant/components/upnp/translations/el.json index 5472b660388a88..6c58f72eef7719 100644 --- a/homeassistant/components/upnp/translations/el.json +++ b/homeassistant/components/upnp/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "incomplete_discovery": "\u0395\u03bb\u03bb\u03b9\u03c0\u03ae\u03c2 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7" + }, "flow_title": "{name}", "step": { "ssdp_confirm": { diff --git a/homeassistant/components/uptimerobot/translations/el.json b/homeassistant/components/uptimerobot/translations/el.json index b9f2b180b4b94c..138602fb4721d4 100644 --- a/homeassistant/components/uptimerobot/translations/el.json +++ b/homeassistant/components/uptimerobot/translations/el.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "reauth_failed_existing": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7\u03c2 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2, \u03b1\u03c6\u03b1\u03b9\u03c1\u03ad\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03be\u03b1\u03bd\u03ac." + }, + "error": { + "reauth_failed_matching_account": "\u03a4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03c0\u03bf\u03c5 \u03b4\u03ce\u03c3\u03b1\u03c4\u03b5 \u03b4\u03b5\u03bd \u03c4\u03b1\u03b9\u03c1\u03b9\u03ac\u03b6\u03b5\u03b9 \u03bc\u03b5 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03c3\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7." + }, "step": { "reauth_confirm": { "description": "\u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03ad\u03c7\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03bc\u03cc\u03bd\u03bf \u03b3\u03b9\u03b1 \u03b1\u03bd\u03ac\u03b3\u03bd\u03c9\u03c3\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf {intergration}." diff --git a/homeassistant/components/uptimerobot/translations/sensor.el.json b/homeassistant/components/uptimerobot/translations/sensor.el.json index e9aa45f91f2b74..cb4f5010017146 100644 --- a/homeassistant/components/uptimerobot/translations/sensor.el.json +++ b/homeassistant/components/uptimerobot/translations/sensor.el.json @@ -4,6 +4,7 @@ "down": "\u039a\u03ac\u03c4\u03c9", "not_checked_yet": "\u0394\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b5\u03bb\u03b5\u03b3\u03c7\u03b8\u03b5\u03af \u03b1\u03ba\u03cc\u03bc\u03b1", "pause": "\u03a0\u03b1\u03cd\u03c3\u03b7", + "seems_down": "\u03a6\u03b1\u03af\u03bd\u03b5\u03c4\u03b1\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03ba\u03c4\u03cc\u03c2", "up": "\u03a0\u03ac\u03bd\u03c9" } } diff --git a/homeassistant/components/venstar/translations/el.json b/homeassistant/components/venstar/translations/el.json new file mode 100644 index 00000000000000..1b7397feadda01 --- /dev/null +++ b/homeassistant/components/venstar/translations/el.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03b8\u03b5\u03c1\u03bc\u03bf\u03c3\u03c4\u03ac\u03c4\u03b7 Venstar" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vera/translations/el.json b/homeassistant/components/vera/translations/el.json index 43dcd322834f80..fcd56ed621e27c 100644 --- a/homeassistant/components/vera/translations/el.json +++ b/homeassistant/components/vera/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "cannot_connect": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ae \u03bc\u03b5 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL {base_url}" + }, "step": { "user": { "description": "\u0394\u03ce\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c4\u03bf\u03c5 \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ae Vera \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9. \u0398\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03bc\u03bf\u03b9\u03ac\u03b6\u03b5\u03b9 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc: http://192.168.1.161:3480.", diff --git a/homeassistant/components/vicare/translations/el.json b/homeassistant/components/vicare/translations/el.json index 1109812260d53c..a813fc3ede2fc5 100644 --- a/homeassistant/components/vicare/translations/el.json +++ b/homeassistant/components/vicare/translations/el.json @@ -1,12 +1,14 @@ { "config": { + "flow_title": "{name} ({host})", "step": { "user": { "data": { "heating_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03b8\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7\u03c2", "scan_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7\u03c2 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)" }, - "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 ViCare. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://developer.viessmann.com" + "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 ViCare. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://developer.viessmann.com", + "title": "{name}" } } } diff --git a/homeassistant/components/vizio/translations/el.json b/homeassistant/components/vizio/translations/el.json index 3cfce894ca25c6..f4d134979cee46 100644 --- a/homeassistant/components/vizio/translations/el.json +++ b/homeassistant/components/vizio/translations/el.json @@ -5,14 +5,19 @@ }, "error": { "complete_pairing_failed": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03ae\u03c1\u03c9\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03b1\u03bd\u03c4\u03b9\u03c3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7\u03c2. \u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03bf PIN \u03c0\u03bf\u03c5 \u03b4\u03ce\u03c3\u03b1\u03c4\u03b5 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c9\u03c3\u03c4\u03cc\u03c2 \u03ba\u03b1\u03b9 \u03cc\u03c4\u03b9 \u03b7 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7 \u03b5\u03be\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03b5\u03af \u03bd\u03b1 \u03c4\u03c1\u03bf\u03c6\u03bf\u03b4\u03bf\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03c1\u03b5\u03cd\u03bc\u03b1 \u03ba\u03b1\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03b7 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf \u03c0\u03c1\u03b9\u03bd \u03c4\u03b7\u03bd \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae.", - "existing_config_entry_found": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af \u03bc\u03b9\u03b1 \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03c3\u03b1 VIZIO SmartCast Device \u03bc\u03b5 \u03c4\u03bf\u03bd \u03af\u03b4\u03b9\u03bf \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc. \u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c8\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03c3\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae\u03bd." + "existing_config_entry_found": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af \u03bc\u03b9\u03b1 \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03c3\u03b1 VIZIO SmartCast \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bc\u03b5 \u03c4\u03bf\u03bd \u03af\u03b4\u03b9\u03bf \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc. \u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c8\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03c3\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae\u03bd." }, "step": { "pair_tv": { - "description": "\u0397 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae \u03c3\u03b1\u03c2 \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03b9 \u03ad\u03bd\u03b1\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc. \u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c3\u03c4\u03b7 \u03c6\u03cc\u03c1\u03bc\u03b1 \u03ba\u03b1\u03b9, \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03bf \u03b2\u03ae\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03ce\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7." + "description": "\u0397 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae \u03c3\u03b1\u03c2 \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03b9 \u03ad\u03bd\u03b1\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc. \u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c3\u03c4\u03b7 \u03c6\u03cc\u03c1\u03bc\u03b1 \u03ba\u03b1\u03b9, \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03bf \u03b2\u03ae\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03ce\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7.", + "title": "\u039f\u03bb\u03bf\u03ba\u03bb\u03ae\u03c1\u03c9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03b9\u03ba\u03b1\u03c3\u03af\u03b1\u03c2 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7\u03c2" + }, + "pairing_complete": { + "description": "\u0397 VIZIO SmartCast \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03bb\u03ad\u03bf\u03bd \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03b7 \u03c3\u03c4\u03bf Home Assistant.", + "title": "\u039f\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7" }, "pairing_complete_import": { - "description": "\u03a4\u03bf VIZIO SmartCast Device \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03bb\u03ad\u03bf\u03bd \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03c3\u03c4\u03bf Home Assistant. \n\n \u03a4\u03bf \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \"**{access_token}**\".", + "description": "\u03a4\u03bf VIZIO SmartCast \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03bb\u03ad\u03bf\u03bd \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03c3\u03c4\u03bf Home Assistant. \n\n \u03a4\u03bf \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \"**{access_token}**\".", "title": "\u039f\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7" }, "user": { @@ -20,7 +25,7 @@ "device_class": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" }, "description": "\u0388\u03bd\u03b1 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03bc\u03cc\u03bd\u03bf \u03b3\u03b9\u03b1 \u03c4\u03b9\u03c2 \u03c4\u03b7\u03bb\u03b5\u03bf\u03c1\u03ac\u03c3\u03b5\u03b9\u03c2. \u0395\u03ac\u03bd \u03c1\u03c5\u03b8\u03bc\u03af\u03b6\u03b5\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03b1\u03ba\u03cc\u03bc\u03b7 \u03ad\u03bd\u03b1 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2, \u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03b5\u03c1\u03ac\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c0\u03cc \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b1\u03b4\u03b9\u03ba\u03b1\u03c3\u03af\u03b1 \u03b1\u03bd\u03c4\u03b9\u03c3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7\u03c2.", - "title": "VIZIO SmartCast Device" + "title": "VIZIO SmartCast \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" } } }, diff --git a/homeassistant/components/vlc_telnet/translations/el.json b/homeassistant/components/vlc_telnet/translations/el.json new file mode 100644 index 00000000000000..e2c4495a77eb51 --- /dev/null +++ b/homeassistant/components/vlc_telnet/translations/el.json @@ -0,0 +1,13 @@ +{ + "config": { + "flow_title": "{host}", + "step": { + "hassio_confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03bc\u03b5 \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf {addon};" + }, + "reauth_confirm": { + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c3\u03c9\u03c3\u03c4\u03cc \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae: {host}" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/watttime/translations/el.json b/homeassistant/components/watttime/translations/el.json index 5c7ba4adb9de92..0a551fc8874bf8 100644 --- a/homeassistant/components/watttime/translations/el.json +++ b/homeassistant/components/watttime/translations/el.json @@ -14,9 +14,22 @@ "location": { "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03b3\u03b9\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7:" }, + "reauth_confirm": { + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username}:" + }, "user": { "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2:" } } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7\u03c2 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2 \u03c3\u03c4\u03bf\u03bd \u03c7\u03ac\u03c1\u03c4\u03b7" + }, + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 WattTime" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/waze_travel_time/translations/el.json b/homeassistant/components/waze_travel_time/translations/el.json index 2dbb86c6dd4c6b..2336b3ded4f646 100644 --- a/homeassistant/components/waze_travel_time/translations/el.json +++ b/homeassistant/components/waze_travel_time/translations/el.json @@ -3,8 +3,11 @@ "step": { "user": { "data": { - "destination": "\u03a0\u03c1\u03bf\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2" - } + "destination": "\u03a0\u03c1\u03bf\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2", + "origin": "\u03a0\u03c1\u03bf\u03ad\u03bb\u03b5\u03c5\u03c3\u03b7", + "region": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae" + }, + "description": "\u0393\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03ad\u03bb\u03b5\u03c5\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03c0\u03c1\u03bf\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc, \u03b5\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03ae \u03c4\u03b9\u03c2 \u03c3\u03c5\u03bd\u03c4\u03b5\u03c4\u03b1\u03b3\u03bc\u03ad\u03bd\u03b5\u03c2 GPS \u03c4\u03b7\u03c2 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2 (\u03bf\u03b9 \u03c3\u03c5\u03bd\u03c4\u03b5\u03c4\u03b1\u03b3\u03bc\u03ad\u03bd\u03b5\u03c2 GPS \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03af\u03b6\u03bf\u03bd\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03ba\u03cc\u03bc\u03bc\u03b1). \u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03b5\u03c0\u03af\u03c3\u03b7\u03c2 \u03bd\u03b1 \u03b5\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03c0\u03bf\u03c5 \u03c0\u03b1\u03c1\u03ad\u03c7\u03b5\u03b9 \u03b1\u03c5\u03c4\u03ad\u03c2 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03c4\u03b7\u03bd \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03ae \u03c4\u03bf\u03c5, \u03ad\u03bd\u03b1 \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03b7\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03ac \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03bf\u03cd \u03c0\u03bb\u03ac\u03c4\u03bf\u03c5\u03c2 \u03ba\u03b1\u03b9 \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03bf\u03cd \u03bc\u03ae\u03ba\u03bf\u03c5\u03c2 \u03ae \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c6\u03b9\u03bb\u03b9\u03ba\u03cc \u03c0\u03c1\u03bf\u03c2 \u03c4\u03b7 \u03b6\u03ce\u03bd\u03b7." } } }, @@ -12,6 +15,11 @@ "step": { "init": { "data": { + "avoid_ferries": "\u0391\u03c0\u03bf\u03c6\u03cd\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c0\u03bb\u03bf\u03af\u03b1;", + "avoid_subscription_roads": "\u0391\u03c0\u03bf\u03c6\u03cd\u03b3\u03b5\u03c4\u03b5 \u03b4\u03c1\u03cc\u03bc\u03bf\u03c5\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b5\u03b9\u03ac\u03b6\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b2\u03b9\u03bd\u03b9\u03ad\u03c4\u03b1 / \u03c3\u03c5\u03bd\u03b4\u03c1\u03bf\u03bc\u03ae;", + "avoid_toll_roads": "\u0391\u03c0\u03bf\u03c6\u03cd\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03cc\u03b4\u03b9\u03b1;", + "incl_filter": "\u03a5\u03c0\u03bf\u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03c3\u03c4\u03b7\u03bd \u03a0\u03b5\u03c1\u03b9\u03b3\u03c1\u03b1\u03c6\u03ae \u03c4\u03b7\u03c2 \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae\u03c2", + "realtime": "\u03a7\u03c1\u03cc\u03bd\u03bf\u03c2 \u03c4\u03b1\u03be\u03b9\u03b4\u03b9\u03bf\u03cd \u03c3\u03b5 \u03c0\u03c1\u03b1\u03b3\u03bc\u03b1\u03c4\u03b9\u03ba\u03cc \u03c7\u03c1\u03cc\u03bd\u03bf;", "units": "\u039c\u03bf\u03bd\u03ac\u03b4\u03b5\u03c2", "vehicle_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03bf\u03c7\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2" }, diff --git a/homeassistant/components/wemo/translations/el.json b/homeassistant/components/wemo/translations/el.json index 6767d5d4d68031..e31b6d6443cfef 100644 --- a/homeassistant/components/wemo/translations/el.json +++ b/homeassistant/components/wemo/translations/el.json @@ -5,5 +5,10 @@ "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Wemo;" } } + }, + "device_automation": { + "trigger_type": { + "long_press": "\u03a4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af Wemo \u03c0\u03b1\u03c4\u03ae\u03b8\u03b7\u03ba\u03b5 \u03b3\u03b9\u03b1 2 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1" + } } } \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/ca.json b/homeassistant/components/wiz/translations/ca.json new file mode 100644 index 00000000000000..505788115c96ff --- /dev/null +++ b/homeassistant/components/wiz/translations/ca.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat", + "no_devices_found": "No s'han trobat dispositius a la xarxa" + }, + "error": { + "bulb_time_out": "No s'ha pogut connectar amb la bombeta. Pot ser que la bombeta estigui desconnectada o s'hagi introdu\u00eft una IP/amfitri\u00f3 incorrecta. Enc\u00e9n el llum i torna-ho a provar.", + "cannot_connect": "Ha fallat la connexi\u00f3", + "no_wiz_light": "La bombeta no es pot connectar mitjan\u00e7ant la integraci\u00f3 Plataforma WiZ.", + "unknown": "Error inesperat" + }, + "flow_title": "{name} ({host})", + "step": { + "confirm": { + "description": "Vols comen\u00e7ar la configuraci\u00f3?" + }, + "discovery_confirm": { + "description": "Vols configurar {name} ({host})?" + }, + "pick_device": { + "data": { + "device": "Dispositiu" + } + }, + "user": { + "data": { + "host": "Amfitri\u00f3", + "name": "Nom" + }, + "description": "Si deixes l'amfitri\u00f3 buit, s'utilitzar\u00e0 el descobriment per cercar dispositius." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/de.json b/homeassistant/components/wiz/translations/de.json new file mode 100644 index 00000000000000..829a2b0a04810f --- /dev/null +++ b/homeassistant/components/wiz/translations/de.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden" + }, + "error": { + "bulb_time_out": "Es kann keine Verbindung zur Gl\u00fchbirne hergestellt werden. Vielleicht ist die Gl\u00fchbirne offline oder es wurde eine falsche IP/Host eingegeben. Bitte schalte das Licht ein und versuche es erneut!", + "cannot_connect": "Verbindung fehlgeschlagen", + "no_wiz_light": "Die Gl\u00fchbirne kann nicht \u00fcber die Integration der WiZ-Plattform verbunden werden.", + "unknown": "Unerwarteter Fehler" + }, + "flow_title": "{name} ({host})", + "step": { + "confirm": { + "description": "M\u00f6chtest Du mit der Einrichtung beginnen?" + }, + "discovery_confirm": { + "description": "M\u00f6chtest du {name} ({host}) einrichten?" + }, + "user": { + "data": { + "host": "Host", + "name": "Name" + }, + "description": "Gib die IP-Adresse des Ger\u00e4ts ein." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/el.json b/homeassistant/components/wiz/translations/el.json new file mode 100644 index 00000000000000..81d140f3d4f0bb --- /dev/null +++ b/homeassistant/components/wiz/translations/el.json @@ -0,0 +1,13 @@ +{ + "config": { + "error": { + "bulb_time_out": "\u0394\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf\u03bd \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b1. \u038a\u03c3\u03c9\u03c2 \u03bf \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03ba\u03c4\u03cc\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03ae \u03ad\u03c7\u03b5\u03b9 \u03b5\u03b9\u03c3\u03b1\u03c7\u03b8\u03b5\u03af \u03bb\u03ac\u03b8\u03bf\u03c2 IP/host. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03bd\u03ac\u03c8\u03c4\u03b5 \u03c4\u03bf \u03c6\u03c9\u03c2 \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac!", + "no_wiz_light": "\u039f \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03bc\u03ad\u03c3\u03c9 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c0\u03bb\u03b1\u03c4\u03c6\u03cc\u03c1\u03bc\u03b1\u03c2 WiZ." + }, + "step": { + "user": { + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03ba\u03b1\u03b9 \u03ad\u03bd\u03b1 \u03cc\u03bd\u03bf\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03bd\u03ad\u03bf \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b1:" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/en.json b/homeassistant/components/wiz/translations/en.json index c8ee3d6df2e5c2..5747182a231e87 100644 --- a/homeassistant/components/wiz/translations/en.json +++ b/homeassistant/components/wiz/translations/en.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Device is already configured" + "already_configured": "Device is already configured", + "no_devices_found": "No devices found on the network" }, "error": { "bulb_time_out": "Can not connect to the bulb. Maybe the bulb is offline or a wrong IP/host was entered. Please turn on the light and try again!", @@ -11,6 +12,9 @@ }, "flow_title": "{name} ({host})", "step": { + "confirm": { + "description": "Do you want to start set up?" + }, "discovery_confirm": { "description": "Do you want to setup {name} ({host})?" }, @@ -21,7 +25,8 @@ }, "user": { "data": { - "host": "Host" + "host": "Host", + "name": "Name" }, "description": "If you leave the host empty, discovery will be used to find devices." } diff --git a/homeassistant/components/wiz/translations/et.json b/homeassistant/components/wiz/translations/et.json new file mode 100644 index 00000000000000..9de0612b2f71d2 --- /dev/null +++ b/homeassistant/components/wiz/translations/et.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "no_devices_found": "V\u00f5rgust seadmeid ei leitud" + }, + "error": { + "bulb_time_out": "Ei saa \u00fchendust pirniga. Pirn v\u00f5ib-olla v\u00f5rgu\u00fchenduseta v\u00f5i on sisestatud vale IP/host. L\u00fclita lamp sisse ja proovi uuesti!", + "cannot_connect": "\u00dchendamine nurjus", + "no_wiz_light": "Pirni ei saa \u00fchendada WiZ Platvormi sidumise kaudu.", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "confirm": { + "description": "Kas alustan seadistamist?" + }, + "user": { + "data": { + "host": "Host", + "name": "Nimi" + }, + "description": "Uue pirni lisamiseks sisesta hostnimi v\u00f5i IP-aadress ja nimi:" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/ja.json b/homeassistant/components/wiz/translations/ja.json new file mode 100644 index 00000000000000..e417002cb90955 --- /dev/null +++ b/homeassistant/components/wiz/translations/ja.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", + "no_devices_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u4e0a\u306b\u30c7\u30d0\u30a4\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093" + }, + "error": { + "bulb_time_out": "\u96fb\u7403\u306b\u63a5\u7d9a\u3067\u304d\u307e\u305b\u3093\u3002\u96fb\u7403\u304c\u30aa\u30d5\u30e9\u30a4\u30f3\u306b\u306a\u3063\u3066\u3044\u308b\u304b\u3001\u9593\u9055\u3063\u305fIP/\u30db\u30b9\u30c8\u304c\u5165\u529b\u3055\u308c\u3066\u3044\u308b\u53ef\u80fd\u6027\u304c\u3042\u308a\u307e\u3059\u3002\u96fb\u7403\u306e\u96fb\u6e90\u3092\u5165\u308c\u3066\u518d\u5ea6\u304a\u8a66\u3057\u304f\u3060\u3055\u3044\u3002", + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "no_wiz_light": "\u3053\u306e\u96fb\u7403\u306f\u3001WiZ Platform\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u4ecb\u3057\u3066\u63a5\u7d9a\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002", + "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" + }, + "step": { + "confirm": { + "description": "\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3092\u958b\u59cb\u3057\u307e\u3059\u304b\uff1f" + }, + "user": { + "data": { + "host": "\u30db\u30b9\u30c8", + "name": "\u540d\u524d" + }, + "description": "\u65b0\u3057\u3044\u96fb\u7403(bulb)\u3092\u8ffd\u52a0\u3059\u308b\u306b\u306f\u3001\u30db\u30b9\u30c8\u540d\u307e\u305f\u306fIP\u30a2\u30c9\u30ec\u30b9\u3068\u540d\u524d\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044:" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/nl.json b/homeassistant/components/wiz/translations/nl.json new file mode 100644 index 00000000000000..c0108fc32c1326 --- /dev/null +++ b/homeassistant/components/wiz/translations/nl.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd", + "no_devices_found": "Geen apparaten gevonden op het netwerk" + }, + "error": { + "bulb_time_out": "Kan geen verbinding maken met de lamp. Misschien is de lamp offline of is er een verkeerde IP/host ingevoerd. Doe het licht aan en probeer het opnieuw!", + "cannot_connect": "Kan geen verbinding maken", + "no_wiz_light": "De lamp kan niet worden aangesloten via WiZ Platform integratie.", + "unknown": "Onverwachte fout" + }, + "step": { + "confirm": { + "description": "Wilt u beginnen met instellen?" + }, + "user": { + "data": { + "host": "Host", + "name": "Naam" + }, + "description": "Voer een hostnaam of IP-adres en naam in om een nieuwe lamp toe te voegen:" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/pt-BR.json b/homeassistant/components/wiz/translations/pt-BR.json new file mode 100644 index 00000000000000..92d0c3f84d318f --- /dev/null +++ b/homeassistant/components/wiz/translations/pt-BR.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", + "no_devices_found": "Nenhum dispositivo encontrado na rede" + }, + "error": { + "bulb_time_out": "N\u00e3o \u00e9 poss\u00edvel conectar \u00e0 l\u00e2mpada. Talvez a l\u00e2mpada esteja offline ou um IP/host errado foi inserido. Por favor, acenda a luz e tente novamente!", + "cannot_connect": "Falha em conectar", + "no_wiz_light": "A l\u00e2mpada n\u00e3o pode ser conectada via integra\u00e7\u00e3o com a plataforma WiZ.", + "unknown": "Erro inesperado" + }, + "flow_title": "{name} ( {host} )", + "step": { + "confirm": { + "description": "Deseja iniciar a configura\u00e7\u00e3o?" + }, + "discovery_confirm": { + "description": "Deseja configurar {name} ( {host} )?" + }, + "pick_device": { + "data": { + "device": "Dispositivo" + } + }, + "user": { + "data": { + "host": "Host", + "name": "Nome" + }, + "description": "Se voc\u00ea deixar o host vazio, a descoberta ser\u00e1 usada para localizar dispositivos." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/zh-Hant.json b/homeassistant/components/wiz/translations/zh-Hant.json new file mode 100644 index 00000000000000..df4c08995f3a06 --- /dev/null +++ b/homeassistant/components/wiz/translations/zh-Hant.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e" + }, + "error": { + "bulb_time_out": "\u7121\u6cd5\u9023\u7dda\u81f3\u71c8\u6ce1\u3002\u53ef\u80fd\u539f\u56e0\u70ba\u71c8\u6ce1\u5df2\u96e2\u7dda\u6216\u6240\u8f38\u5165\u7684 IP/\u4e3b\u6a5f\u540d\u7a31\u932f\u8aa4\u3002\u8acb\u958b\u555f\u71c8\u6ce1\u4e26\u518d\u8a66\u4e00\u6b21\uff01", + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "no_wiz_light": "\u71c8\u6ce1\u7121\u6cd5\u900f\u904e WiZ \u5e73\u53f0\u6574\u5408\u9023\u63a5\u3002", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "confirm": { + "description": "\u662f\u5426\u8981\u958b\u59cb\u8a2d\u5b9a\uff1f" + }, + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "name": "\u540d\u7a31" + }, + "description": "\u8acb\u8f38\u5165\u4e3b\u6a5f\u540d\u7a31\u6216 IP \u4f4d\u5740\u4ee5\u65b0\u589e\u71c8\u6ce1\uff1a" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wled/translations/el.json b/homeassistant/components/wled/translations/el.json index 35de8dfdcdc5f0..9bfe5cc02a010f 100644 --- a/homeassistant/components/wled/translations/el.json +++ b/homeassistant/components/wled/translations/el.json @@ -7,6 +7,7 @@ "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, + "flow_title": "{name}", "step": { "user": { "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf WLED \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf Home Assistant." diff --git a/homeassistant/components/wolflink/translations/sensor.el.json b/homeassistant/components/wolflink/translations/sensor.el.json index 4064892a1fbe2b..d752c023c7247a 100644 --- a/homeassistant/components/wolflink/translations/sensor.el.json +++ b/homeassistant/components/wolflink/translations/sensor.el.json @@ -1,19 +1,68 @@ { "state": { "wolflink__state": { + "abgasklappe": "\u0391\u03c0\u03bf\u03c3\u03b2\u03b5\u03c3\u03c4\u03ae\u03c1\u03b1\u03c2 \u03ba\u03b1\u03c5\u03c3\u03b1\u03b5\u03c1\u03af\u03c9\u03bd", + "absenkbetrieb": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03bf\u03c0\u03b9\u03c3\u03b8\u03bf\u03b4\u03c1\u03cc\u03bc\u03b7\u03c3\u03b7\u03c2", + "absenkstop": "\u0394\u03b9\u03b1\u03ba\u03bf\u03c0\u03ae \u03bf\u03c0\u03b9\u03c3\u03b8\u03bf\u03b4\u03c1\u03cc\u03bc\u03b7\u03c3\u03b7\u03c2", + "aktiviert": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf", + "antilegionellenfunktion": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03c2 \u03bb\u03b5\u03b3\u03b9\u03bf\u03bd\u03ad\u03bb\u03bb\u03b1\u03c2", + "at_abschaltung": "\u0394\u03b9\u03b1\u03ba\u03bf\u03c0\u03ae \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 OT", + "at_frostschutz": "\u03a0\u03c1\u03bf\u03c3\u03c4\u03b1\u03c3\u03af\u03b1 \u03b1\u03c0\u03cc \u03c0\u03b1\u03b3\u03b5\u03c4\u03cc OT", + "aus": "\u0391\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf", + "auto": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03bf", + "auto_off_cool": "AutoOffCool", + "auto_on_cool": "AutoOnCool", + "automatik_aus": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03bf OFF", + "automatik_ein": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03bf ON", + "bereit_keine_ladung": "\u0388\u03c4\u03bf\u03b9\u03bc\u03bf, \u03b4\u03b5\u03bd \u03c6\u03bf\u03c1\u03c4\u03ce\u03bd\u03b5\u03b9", + "betrieb_ohne_brenner": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c7\u03c9\u03c1\u03af\u03c2 \u03ba\u03b1\u03c5\u03c3\u03c4\u03ae\u03c1\u03b1", + "cooling": "\u03a8\u03cd\u03be\u03b7", + "deaktiviert": "\u0391\u03b4\u03c1\u03b1\u03bd\u03ad\u03c2", + "dhw_prior": "DHWPrior", + "eco": "Eco", + "ein": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf", "externe_deaktivierung": "\u0395\u03be\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03ae \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", "fernschalter_ein": "\u0391\u03c0\u03bf\u03bc\u03b1\u03ba\u03c1\u03c5\u03c3\u03bc\u03ad\u03bd\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u0395\u03bd\u03b5\u03c1\u03b3\u03ae", "frost_heizkreis": "\u03a0\u03b1\u03b3\u03b5\u03c4\u03cc\u03c2 \u03c3\u03c4\u03bf \u03ba\u03cd\u03ba\u03bb\u03c9\u03bc\u03b1 \u03b8\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7\u03c2", + "frost_warmwasser": "\u03a0\u03b1\u03b3\u03b5\u03c4\u03cc\u03c2 DHW", "frostschutz": "\u03a0\u03c1\u03bf\u03c3\u03c4\u03b1\u03c3\u03af\u03b1 \u03b1\u03c0\u03cc \u03c0\u03b1\u03b3\u03b5\u03c4\u03cc", "gasdruck": "\u03a0\u03af\u03b5\u03c3\u03b7 \u03b1\u03b5\u03c1\u03af\u03bf\u03c5", + "glt_betrieb": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 BMS", "gradienten_uberwachung": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03ba\u03bb\u03af\u03c3\u03b7\u03c2", "heizbetrieb": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b8\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7\u03c2", "heizgerat_mit_speicher": "\u039b\u03ad\u03b2\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03ba\u03cd\u03bb\u03b9\u03bd\u03b4\u03c1\u03bf", "heizung": "\u0398\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7", + "nur_heizgerat": "\u039c\u03cc\u03bd\u03bf \u03bb\u03ad\u03b2\u03b7\u03c4\u03b1\u03c2", + "parallelbetrieb": "\u03a0\u03b1\u03c1\u03ac\u03bb\u03bb\u03b7\u03bb\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1", + "partymodus": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c0\u03ac\u03c1\u03c4\u03b9", + "perm_cooling": "PermCooling", + "permanent": "\u039c\u03cc\u03bd\u03b9\u03bc\u03bf", + "permanentbetrieb": "\u039c\u03cc\u03bd\u03b9\u03bc\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1", + "reduzierter_betrieb": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1", + "rt_abschaltung": "\u03a4\u03b5\u03c1\u03bc\u03b1\u03c4\u03b9\u03c3\u03bc\u03cc\u03c2 RT", + "rt_frostschutz": "RT \u03c0\u03c1\u03bf\u03c3\u03c4\u03b1\u03c3\u03af\u03b1 \u03b1\u03c0\u03cc \u03c0\u03b1\u03b3\u03b5\u03c4\u03cc", + "ruhekontakt": "\u0395\u03c0\u03b1\u03c6\u03ae \u03b1\u03bd\u03ac\u03c0\u03b1\u03c5\u03c3\u03b7\u03c2", + "schornsteinfeger": "\u0394\u03bf\u03ba\u03b9\u03bc\u03ae \u03b5\u03ba\u03c0\u03bf\u03bc\u03c0\u03ce\u03bd", + "smart_grid": "SmartGrid", + "smart_home": "SmartHome", + "softstart": "\u0389\u03c0\u03b9\u03b1 \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7", + "solarbetrieb": "\u0397\u03bb\u03b9\u03b1\u03ba\u03ae \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1", + "sparbetrieb": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03bf\u03b9\u03ba\u03bf\u03bd\u03bf\u03bc\u03af\u03b1\u03c2", + "sparen": "\u039f\u03b9\u03ba\u03bf\u03bd\u03bf\u03bc\u03af\u03b1", + "spreizung_hoch": "dT \u03c0\u03bf\u03bb\u03cd \u03c6\u03b1\u03c1\u03b4\u03cd", + "spreizung_kf": "\u0394\u03b9\u03ac\u03b4\u03bf\u03c3\u03b7 KF", + "stabilisierung": "\u03a3\u03c4\u03b1\u03b8\u03b5\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", + "standby": "\u039a\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03b1\u03bd\u03b1\u03bc\u03bf\u03bd\u03ae\u03c2", + "start": "\u0388\u03bd\u03b1\u03c1\u03be\u03b7", + "storung": "\u0392\u03bb\u03ac\u03b2\u03b7", + "telefonfernschalter": "\u03a4\u03b7\u03bb\u03b5\u03c6\u03c9\u03bd\u03b9\u03ba\u03cc\u03c2 \u03b1\u03c0\u03bf\u03bc\u03b1\u03ba\u03c1\u03c5\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2 \u03b4\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b7\u03c2", "test": "\u0394\u03bf\u03ba\u03b9\u03bc\u03ae", "tpw": "TPW", "urlaubsmodus": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b4\u03b9\u03b1\u03ba\u03bf\u03c0\u03ce\u03bd", + "vorspulen": "\u039e\u03ad\u03b2\u03b3\u03b1\u03bb\u03bc\u03b1 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5", "warmwasser": "DHW", + "warmwasser_schnellstart": "\u0393\u03c1\u03ae\u03b3\u03bf\u03c1\u03b7 \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7 DHW", + "warmwasserbetrieb": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 DHW", "warmwasservorrang": "\u03a0\u03c1\u03bf\u03c4\u03b5\u03c1\u03b1\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1 DHW", "zunden": "\u0391\u03bd\u03ac\u03c6\u03bb\u03b5\u03be\u03b7" } diff --git a/homeassistant/components/xiaomi_miio/translations/el.json b/homeassistant/components/xiaomi_miio/translations/el.json index 47716969d47b48..3f6dcb8b015af9 100644 --- a/homeassistant/components/xiaomi_miio/translations/el.json +++ b/homeassistant/components/xiaomi_miio/translations/el.json @@ -1,14 +1,36 @@ { "config": { "abort": { - "incomplete_info": "\u0395\u03bb\u03bb\u03b9\u03c0\u03b5\u03af\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2, \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c0\u03b1\u03c1\u03b1\u03c3\u03c7\u03b5\u03b8\u03b5\u03af \u03c5\u03c0\u03bf\u03b4\u03bf\u03c7\u03ad\u03b1\u03c2 \u03ae \u03ba\u03bf\u03c5\u03c0\u03cc\u03bd\u03b9." + "incomplete_info": "\u0395\u03bb\u03bb\u03b9\u03c0\u03b5\u03af\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2, \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c0\u03b1\u03c1\u03b1\u03c3\u03c7\u03b5\u03b8\u03b5\u03af \u03c5\u03c0\u03bf\u03b4\u03bf\u03c7\u03ad\u03b1\u03c2 \u03ae \u03ba\u03bf\u03c5\u03c0\u03cc\u03bd\u03b9.", + "not_xiaomi_miio": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 (\u03b1\u03ba\u03cc\u03bc\u03b1) \u03b1\u03c0\u03cc \u03c4\u03bf Xiaomi Miio." }, "error": { + "cloud_credentials_incomplete": "\u03a4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 Cloud \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bb\u03bb\u03b9\u03c0\u03ae, \u03c3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7, \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03c7\u03ce\u03c1\u03b1", + "cloud_login_error": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf Xiaomi Miio Cloud, \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1.", + "cloud_no_devices": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc Xiaomi Miio cloud.", "no_device_selected": "\u0394\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03b5\u03af \u03ba\u03b1\u03bc\u03af\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae, \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae.", - "unknown_device": "\u03a4\u03bf \u03bc\u03bf\u03bd\u03c4\u03ad\u03bb\u03bf \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b3\u03bd\u03c9\u03c3\u03c4\u03cc, \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03bc\u03b5 \u03c4\u03b7 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c1\u03bf\u03ae\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c9\u03bd." + "unknown_device": "\u03a4\u03bf \u03bc\u03bf\u03bd\u03c4\u03ad\u03bb\u03bf \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b3\u03bd\u03c9\u03c3\u03c4\u03cc, \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03bc\u03b5 \u03c4\u03b7 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c1\u03bf\u03ae\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c9\u03bd.", + "wrong_token": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03b1\u03b8\u03c1\u03bf\u03af\u03c3\u03bc\u03b1\u03c4\u03bf\u03c2 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5, \u03bb\u03ac\u03b8\u03bf\u03c2 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc" }, "flow_title": "{name}", "step": { + "cloud": { + "data": { + "cloud_country": "\u03a7\u03ce\u03c1\u03b1 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae cloud", + "cloud_password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 Cloud", + "cloud_username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 Cloud", + "manual": "\u039c\u03b7 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 (\u03b4\u03b5\u03bd \u03c3\u03c5\u03bd\u03b9\u03c3\u03c4\u03ac\u03c4\u03b1\u03b9)" + }, + "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf cloud Xiaomi Miio, \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://www.openhab.org/addons/bindings/miio/#country-servers \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae cloud \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5.", + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Xiaomi Miio \u03ae \u03c0\u03cd\u03bb\u03b7 Xiaomi" + }, + "connect": { + "data": { + "model": "\u039c\u03bf\u03bd\u03c4\u03ad\u03bb\u03bf \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b1 \u03c4\u03bf \u03bc\u03bf\u03bd\u03c4\u03ad\u03bb\u03bf \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b1 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03b1 \u03bc\u03bf\u03bd\u03c4\u03ad\u03bb\u03b1.", + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Xiaomi Miio \u03ae \u03c0\u03cd\u03bb\u03b7 Xiaomi" + }, "device": { "data": { "model": "\u039c\u03bf\u03bd\u03c4\u03ad\u03bb\u03bf \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 (\u03a0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", @@ -24,6 +46,20 @@ "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf\u03c5\u03c2 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b5\u03c2 32 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API , \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u03b3\u03b9\u03b1 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2. \u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03b1\u03c0\u03cc \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Xiaomi Aqara.", "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03cd\u03bb\u03b7 Xiaomi" }, + "manual": { + "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf 32 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03c9\u03bd \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API, \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \u03b3\u03b9\u03b1 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2. \u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03b1\u03c0\u03cc \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Xiaomi Aqara.", + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Xiaomi Miio \u03ae \u03c0\u03cd\u03bb\u03b7 Xiaomi" + }, + "reauth_confirm": { + "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 xiaomi Miio \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03b5\u03b9 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03ac \u03ae \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03b9 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 cloud \u03c0\u03bf\u03c5 \u03bb\u03b5\u03af\u03c0\u03bf\u03c5\u03bd." + }, + "select": { + "data": { + "select_device": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Miio" + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Xiaomi Miio \u03b3\u03b9\u03b1 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7.", + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Xiaomi Miio \u03ae \u03c0\u03cd\u03bb\u03b7 Xiaomi" + }, "user": { "data": { "gateway": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03cd\u03bb\u03b7 Xiaomi" @@ -32,5 +68,19 @@ "title": "Xiaomi Miio" } } + }, + "options": { + "error": { + "cloud_credentials_incomplete": "\u03a4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 cloud \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bb\u03bb\u03b9\u03c0\u03ae, \u03c3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7, \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03c7\u03ce\u03c1\u03b1" + }, + "step": { + "init": { + "data": { + "cloud_subdevices": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf cloud \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03b5\u03c2 \u03c5\u03c0\u03bf\u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2" + }, + "description": "\u039a\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ce\u03bd \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c9\u03bd", + "title": "Xiaomi Miio" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/yale_smart_alarm/translations/el.json b/homeassistant/components/yale_smart_alarm/translations/el.json index 6a8ad33c53bc51..3d46faee2ebb35 100644 --- a/homeassistant/components/yale_smart_alarm/translations/el.json +++ b/homeassistant/components/yale_smart_alarm/translations/el.json @@ -1,6 +1,11 @@ { "config": { "step": { + "reauth_confirm": { + "data": { + "area_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae\u03c2" + } + }, "user": { "data": { "area_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae\u03c2" diff --git a/homeassistant/components/yamaha_musiccast/translations/select.el.json b/homeassistant/components/yamaha_musiccast/translations/select.el.json new file mode 100644 index 00000000000000..68bca5c452faea --- /dev/null +++ b/homeassistant/components/yamaha_musiccast/translations/select.el.json @@ -0,0 +1,10 @@ +{ + "state": { + "yamaha_musiccast__dimmer": { + "auto": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03bf" + }, + "yamaha_musiccast__zone_equalizer_mode": { + "auto": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03bf" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zha/translations/el.json b/homeassistant/components/zha/translations/el.json index 19e12f287ff797..13dac9bdf42b06 100644 --- a/homeassistant/components/zha/translations/el.json +++ b/homeassistant/components/zha/translations/el.json @@ -4,6 +4,7 @@ "not_zha_device": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae zha", "usb_probe_failed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 usb" }, + "flow_title": "{name}", "step": { "confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ;" diff --git a/homeassistant/components/zwave_js/translations/el.json b/homeassistant/components/zwave_js/translations/el.json index 00539cbcb70a3a..2cf46d541659e5 100644 --- a/homeassistant/components/zwave_js/translations/el.json +++ b/homeassistant/components/zwave_js/translations/el.json @@ -1,19 +1,32 @@ { "config": { "abort": { + "addon_get_discovery_info_failed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03bb\u03ae\u03c8\u03b7\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03b9\u03ce\u03bd \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS.", + "addon_info_failed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03bb\u03ae\u03c8\u03b7\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03b9\u03ce\u03bd \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS.", + "addon_install_failed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS.", + "addon_set_config_failed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd Z-Wave JS.", "addon_start_failed": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS.", "discovery_requires_supervisor": "\u0397 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03c4\u03bf\u03bd \u03b5\u03c0\u03cc\u03c0\u03c4\u03b7.", "not_zwave_device": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Z-Wave." }, + "error": { + "addon_start_failed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7.", + "invalid_ws_url": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL websocket" + }, "flow_title": "{name}", "progress": { + "install_addon": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03c0\u03b5\u03c1\u03b9\u03bc\u03ad\u03bd\u03b5\u03c4\u03b5 \u03bc\u03ad\u03c7\u03c1\u03b9 \u03bd\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03c9\u03b8\u03b5\u03af \u03b7 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS. \u0391\u03c5\u03c4\u03cc \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b4\u03b9\u03b1\u03c1\u03ba\u03ad\u03c3\u03b5\u03b9 \u03b1\u03c1\u03ba\u03b5\u03c4\u03ac \u03bb\u03b5\u03c0\u03c4\u03ac.", "start_addon": "\u03a0\u03b5\u03c1\u03b9\u03bc\u03ad\u03bd\u03b5\u03c4\u03b5 \u03bc\u03ad\u03c7\u03c1\u03b9 \u03bd\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03c9\u03b8\u03b5\u03af \u03b7 \u03ad\u03bd\u03b1\u03c1\u03be\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS. \u0391\u03c5\u03c4\u03cc \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b4\u03b9\u03b1\u03c1\u03ba\u03ad\u03c3\u03b5\u03b9 \u03bc\u03b5\u03c1\u03b9\u03ba\u03ac \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1." }, "step": { "configure_addon": { "data": { - "network_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5" + "network_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5", + "s2_access_control_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 S2", + "s2_authenticated_key": "\u03a0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af S2", + "s2_unauthenticated_key": "\u039c\u03b7 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af S2" }, + "description": "\u03a4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf \u03b8\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03b9 \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03ac \u03b1\u03c3\u03c6\u03b1\u03bb\u03b5\u03af\u03b1\u03c2 \u03b5\u03ac\u03bd \u03c4\u03b1 \u03c0\u03b5\u03b4\u03af\u03b1 \u03b1\u03c5\u03c4\u03ac \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03af\u03bd\u03bf\u03c5\u03bd \u03ba\u03b5\u03bd\u03ac.", "title": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS" }, "on_supervisor": { @@ -40,6 +53,8 @@ "set_value": "\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b9\u03bc\u03ae \u03bc\u03b9\u03b1\u03c2 \u03c4\u03b9\u03bc\u03ae\u03c2 Z-Wave" }, "condition_type": { + "config_parameter": "\u03a4\u03b9\u03bc\u03ae \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03bf\u03c5 {subtype}", + "node_status": "\u039a\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03ba\u03cc\u03bc\u03b2\u03bf\u03c5", "value": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03c4\u03b9\u03bc\u03ae \u03bc\u03b9\u03b1\u03c2 \u03c4\u03b9\u03bc\u03ae\u03c2 Z-Wave" }, "trigger_type": { @@ -72,8 +87,13 @@ "step": { "configure_addon": { "data": { - "emulate_hardware": "\u0395\u03be\u03bf\u03bc\u03bf\u03af\u03c9\u03c3\u03b7 \u03c5\u03bb\u03b9\u03ba\u03bf\u03cd" - } + "emulate_hardware": "\u0395\u03be\u03bf\u03bc\u03bf\u03af\u03c9\u03c3\u03b7 \u03c5\u03bb\u03b9\u03ba\u03bf\u03cd", + "s0_legacy_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af S0 (\u03c0\u03b1\u03bb\u03b1\u03b9\u03bf\u03cd \u03c4\u03cd\u03c0\u03bf\u03c5)", + "s2_access_control_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 S2", + "s2_authenticated_key": "\u03a0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af S2", + "s2_unauthenticated_key": "\u039c\u03b7 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af S2" + }, + "description": "\u03a4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf \u03b8\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03b9 \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03ac \u03b1\u03c3\u03c6\u03b1\u03bb\u03b5\u03af\u03b1\u03c2 \u03b5\u03ac\u03bd \u03c4\u03b1 \u03c0\u03b5\u03b4\u03af\u03b1 \u03b1\u03c5\u03c4\u03ac \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03af\u03bd\u03bf\u03c5\u03bd \u03ba\u03b5\u03bd\u03ac." }, "on_supervisor": { "data": { @@ -86,5 +106,6 @@ "title": "\u03a4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf Z-Wave JS \u03be\u03b5\u03ba\u03b9\u03bd\u03ac." } } - } + }, + "title": "Z-Wave JS" } \ No newline at end of file From 15e5f516d2229ce011e78fc64a53e62bcc23ede8 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Sat, 5 Feb 2022 22:17:31 -0600 Subject: [PATCH 0337/1098] Update rokuecp to 0.13.1 (#65814) --- .strict-typing | 1 + homeassistant/components/roku/__init__.py | 5 ++-- homeassistant/components/roku/browse_media.py | 8 +++--- homeassistant/components/roku/config_flow.py | 13 ++++++--- homeassistant/components/roku/entity.py | 7 ++--- homeassistant/components/roku/manifest.json | 2 +- homeassistant/components/roku/media_player.py | 27 +++++++++++-------- homeassistant/components/roku/remote.py | 8 +++--- mypy.ini | 11 ++++++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 11 files changed, 56 insertions(+), 30 deletions(-) diff --git a/.strict-typing b/.strict-typing index f2c0c29ef2714c..efdccdb1a43c3a 100644 --- a/.strict-typing +++ b/.strict-typing @@ -152,6 +152,7 @@ homeassistant.components.remote.* homeassistant.components.renault.* homeassistant.components.ridwell.* homeassistant.components.rituals_perfume_genie.* +homeassistant.components.roku.* homeassistant.components.rpi_power.* homeassistant.components.rtsp_to_webrtc.* homeassistant.components.samsungtv.* diff --git a/homeassistant/components/roku/__init__.py b/homeassistant/components/roku/__init__.py index 4d97e8c5fac4ce..ac7a9e385654f1 100644 --- a/homeassistant/components/roku/__init__.py +++ b/homeassistant/components/roku/__init__.py @@ -1,6 +1,7 @@ """Support for Roku.""" from __future__ import annotations +from collections.abc import Callable import logging from rokuecp import RokuConnectionError, RokuError @@ -46,10 +47,10 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -def roku_exception_handler(func): +def roku_exception_handler(func: Callable) -> Callable: """Decorate Roku calls to handle Roku exceptions.""" - async def handler(self, *args, **kwargs): + async def handler(self, *args, **kwargs) -> None: # type: ignore try: await func(self, *args, **kwargs) except RokuConnectionError as error: diff --git a/homeassistant/components/roku/browse_media.py b/homeassistant/components/roku/browse_media.py index 49af47401236e5..7aed3849ce866a 100644 --- a/homeassistant/components/roku/browse_media.py +++ b/homeassistant/components/roku/browse_media.py @@ -69,12 +69,12 @@ def get_thumbnail_url_full( async def async_browse_media( - hass, + hass: HomeAssistant, coordinator: RokuDataUpdateCoordinator, get_browse_image_url: GetBrowseImageUrlType, media_content_id: str | None, media_content_type: str | None, -): +) -> BrowseMedia: """Browse media.""" if media_content_id is None: return await root_payload( @@ -113,7 +113,7 @@ async def root_payload( hass: HomeAssistant, coordinator: RokuDataUpdateCoordinator, get_browse_image_url: GetBrowseImageUrlType, -): +) -> BrowseMedia: """Return root payload for Roku.""" device = coordinator.data @@ -223,7 +223,7 @@ def item_payload( item: dict, coordinator: RokuDataUpdateCoordinator, get_browse_image_url: GetBrowseImageUrlType, -): +) -> BrowseMedia: """ Create response payload for a single media item. diff --git a/homeassistant/components/roku/config_flow.py b/homeassistant/components/roku/config_flow.py index dff0f7d53c657e..e3b8b97aa8ffc0 100644 --- a/homeassistant/components/roku/config_flow.py +++ b/homeassistant/components/roku/config_flow.py @@ -2,6 +2,7 @@ from __future__ import annotations import logging +from typing import Any from urllib.parse import urlparse from rokuecp import Roku, RokuError @@ -24,7 +25,7 @@ _LOGGER = logging.getLogger(__name__) -async def validate_input(hass: HomeAssistant, data: dict) -> dict: +async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -44,12 +45,14 @@ class RokuConfigFlow(ConfigFlow, domain=DOMAIN): VERSION = 1 - def __init__(self): + discovery_info: dict[str, Any] + + def __init__(self) -> None: """Set up the instance.""" self.discovery_info = {} @callback - def _show_form(self, errors: dict | None = None) -> FlowResult: + def _show_form(self, errors: dict[str, Any] | None = None) -> FlowResult: """Show the form to the user.""" return self.async_show_form( step_id="user", @@ -57,7 +60,9 @@ def _show_form(self, errors: dict | None = None) -> FlowResult: errors=errors or {}, ) - async def async_step_user(self, user_input: dict | None = None) -> FlowResult: + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle a flow initialized by the user.""" if not user_input: return self._show_form() diff --git a/homeassistant/components/roku/entity.py b/homeassistant/components/roku/entity.py index 261d0c95445cec..ef969b846aae9c 100644 --- a/homeassistant/components/roku/entity.py +++ b/homeassistant/components/roku/entity.py @@ -17,7 +17,7 @@ class RokuEntity(CoordinatorEntity): def __init__( self, *, - device_id: str, + device_id: str | None, coordinator: RokuDataUpdateCoordinator, description: EntityDescription | None = None, ) -> None: @@ -28,10 +28,11 @@ def __init__( if description is not None: self.entity_description = description self._attr_name = f"{coordinator.data.info.name} {description.name}" - self._attr_unique_id = f"{device_id}_{description.key}" + if device_id is not None: + self._attr_unique_id = f"{device_id}_{description.key}" @property - def device_info(self) -> DeviceInfo: + def device_info(self) -> DeviceInfo | None: """Return device information about this Roku device.""" if self._device_id is None: return None diff --git a/homeassistant/components/roku/manifest.json b/homeassistant/components/roku/manifest.json index 6d7989e93cbdc8..619672e8f1fcca 100644 --- a/homeassistant/components/roku/manifest.json +++ b/homeassistant/components/roku/manifest.json @@ -2,7 +2,7 @@ "domain": "roku", "name": "Roku", "documentation": "https://www.home-assistant.io/integrations/roku", - "requirements": ["rokuecp==0.12.0"], + "requirements": ["rokuecp==0.13.1"], "homekit": { "models": ["3810X", "4660X", "7820X", "C105X", "C135X"] }, diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index 67abae262d5307..d6ad5c9cd13e80 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -117,7 +117,9 @@ async def async_setup_entry( class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): """Representation of a Roku media player on the network.""" - def __init__(self, unique_id: str, coordinator: RokuDataUpdateCoordinator) -> None: + def __init__( + self, unique_id: str | None, coordinator: RokuDataUpdateCoordinator + ) -> None: """Initialize the Roku device.""" super().__init__( coordinator=coordinator, @@ -231,7 +233,7 @@ def media_title(self) -> str | None: @property def media_duration(self) -> int | None: """Duration of current playing media in seconds.""" - if self._media_playback_trackable(): + if self.coordinator.data.media is not None and self._media_playback_trackable(): return self.coordinator.data.media.duration return None @@ -239,7 +241,7 @@ def media_duration(self) -> int | None: @property def media_position(self) -> int | None: """Position of current playing media in seconds.""" - if self._media_playback_trackable(): + if self.coordinator.data.media is not None and self._media_playback_trackable(): return self.coordinator.data.media.position return None @@ -247,7 +249,7 @@ def media_position(self) -> int | None: @property def media_position_updated_at(self) -> dt.datetime | None: """When was the position of the current playing media valid.""" - if self._media_playback_trackable(): + if self.coordinator.data.media is not None and self._media_playback_trackable(): return self.coordinator.data.media.at return None @@ -263,10 +265,12 @@ def source(self) -> str | None: @property def source_list(self) -> list: """List of available input sources.""" - return ["Home"] + sorted(app.name for app in self.coordinator.data.apps) + return ["Home"] + sorted( + app.name for app in self.coordinator.data.apps if app.name is not None + ) @roku_exception_handler - async def search(self, keyword): + async def search(self, keyword: str) -> None: """Emulate opening the search screen and entering the search keyword.""" await self.coordinator.roku.search(keyword) @@ -343,7 +347,7 @@ async def async_media_next_track(self) -> None: await self.coordinator.async_request_refresh() @roku_exception_handler - async def async_mute_volume(self, mute) -> None: + async def async_mute_volume(self, mute: bool) -> None: """Mute the volume.""" await self.coordinator.roku.remote("volume_mute") await self.coordinator.async_request_refresh() @@ -359,7 +363,9 @@ async def async_volume_down(self) -> None: await self.coordinator.roku.remote("volume_down") @roku_exception_handler - async def async_play_media(self, media_type: str, media_id: str, **kwargs) -> None: + async def async_play_media( + self, media_type: str, media_id: str, **kwargs: Any + ) -> None: """Play media from a URL or file, launch an application, or tune to a channel.""" extra: dict[str, Any] = kwargs.get(ATTR_MEDIA_EXTRA) or {} @@ -433,7 +439,6 @@ async def async_select_source(self, source: str) -> None: None, ) - if appl is not None: + if appl is not None and appl.app_id is not None: await self.coordinator.roku.launch(appl.app_id) - - await self.coordinator.async_request_refresh() + await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/roku/remote.py b/homeassistant/components/roku/remote.py index 8f0d39ed1d90e1..96c04597d89a5d 100644 --- a/homeassistant/components/roku/remote.py +++ b/homeassistant/components/roku/remote.py @@ -1,6 +1,8 @@ """Support for the Roku remote.""" from __future__ import annotations +from typing import Any + from homeassistant.components.remote import ATTR_NUM_REPEATS, RemoteEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -42,19 +44,19 @@ def is_on(self) -> bool: return not self.coordinator.data.state.standby @roku_exception_handler - async def async_turn_on(self, **kwargs) -> None: + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" await self.coordinator.roku.remote("poweron") await self.coordinator.async_request_refresh() @roku_exception_handler - async def async_turn_off(self, **kwargs) -> None: + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" await self.coordinator.roku.remote("poweroff") await self.coordinator.async_request_refresh() @roku_exception_handler - async def async_send_command(self, command: list, **kwargs) -> None: + async def async_send_command(self, command: list, **kwargs: Any) -> None: """Send a command to one device.""" num_repeats = kwargs[ATTR_NUM_REPEATS] diff --git a/mypy.ini b/mypy.ini index 524ef2c542084d..96d58927959b2d 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1489,6 +1489,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.roku.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.rpi_power.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/requirements_all.txt b/requirements_all.txt index 39b2f6a8378578..04df44d6fa8968 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2111,7 +2111,7 @@ rjpl==0.3.6 rocketchat-API==0.6.1 # homeassistant.components.roku -rokuecp==0.12.0 +rokuecp==0.13.1 # homeassistant.components.roomba roombapy==1.6.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9f89b0faa50e97..e38232288875db 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1300,7 +1300,7 @@ rflink==0.0.62 ring_doorbell==0.7.2 # homeassistant.components.roku -rokuecp==0.12.0 +rokuecp==0.13.1 # homeassistant.components.roomba roombapy==1.6.5 From 66c9b95da854b11d52b94a77e7384ec16ccdd48f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 5 Feb 2022 22:49:37 -0600 Subject: [PATCH 0338/1098] Fix flash at turn on with newer 0x04 Magic Home models (#65836) --- homeassistant/components/flux_led/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/flux_led/manifest.json b/homeassistant/components/flux_led/manifest.json index 583fd0a70c6249..bd97d9e72d4376 100644 --- a/homeassistant/components/flux_led/manifest.json +++ b/homeassistant/components/flux_led/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "dependencies": ["network"], "documentation": "https://www.home-assistant.io/integrations/flux_led", - "requirements": ["flux_led==0.28.20"], + "requirements": ["flux_led==0.28.21"], "quality_scale": "platinum", "codeowners": ["@icemanch", "@bdraco"], "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index 04df44d6fa8968..3b2aa3bccf974d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -681,7 +681,7 @@ fjaraskupan==1.0.2 flipr-api==1.4.1 # homeassistant.components.flux_led -flux_led==0.28.20 +flux_led==0.28.21 # homeassistant.components.homekit fnvhash==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e38232288875db..60971abf7cc883 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -427,7 +427,7 @@ fjaraskupan==1.0.2 flipr-api==1.4.1 # homeassistant.components.flux_led -flux_led==0.28.20 +flux_led==0.28.21 # homeassistant.components.homekit fnvhash==0.1.0 From 72601ccf32bf9dada8225b6ffbc682b3cc110f86 Mon Sep 17 00:00:00 2001 From: Jeef Date: Sun, 6 Feb 2022 01:32:04 -0700 Subject: [PATCH 0339/1098] feat: bumped version (#65863) --- homeassistant/components/intellifire/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/intellifire/manifest.json b/homeassistant/components/intellifire/manifest.json index 5009e25cb60e04..a71a93b970e628 100644 --- a/homeassistant/components/intellifire/manifest.json +++ b/homeassistant/components/intellifire/manifest.json @@ -3,7 +3,7 @@ "name": "IntelliFire", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/intellifire", - "requirements": ["intellifire4py==0.5"], + "requirements": ["intellifire4py==0.6"], "dependencies": [], "codeowners": ["@jeeftor"], "iot_class": "local_polling", diff --git a/requirements_all.txt b/requirements_all.txt index 3b2aa3bccf974d..68cfbf749ae6bd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -911,7 +911,7 @@ influxdb-client==1.24.0 influxdb==5.3.1 # homeassistant.components.intellifire -intellifire4py==0.5 +intellifire4py==0.6 # homeassistant.components.iotawatt iotawattpy==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 60971abf7cc883..8e7a45105d4736 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -586,7 +586,7 @@ influxdb-client==1.24.0 influxdb==5.3.1 # homeassistant.components.intellifire -intellifire4py==0.5 +intellifire4py==0.6 # homeassistant.components.iotawatt iotawattpy==0.1.0 From 1c8a34c3499dd608b0733ad88d3709febc2b6f10 Mon Sep 17 00:00:00 2001 From: ollo69 <60491700+ollo69@users.noreply.github.com> Date: Sun, 6 Feb 2022 09:35:49 +0100 Subject: [PATCH 0340/1098] Clean-up AsusWRT setup entry (#65860) --- homeassistant/components/asuswrt/__init__.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/asuswrt/__init__.py b/homeassistant/components/asuswrt/__init__.py index ec0162af915f60..1a680a7fb65e8a 100644 --- a/homeassistant/components/asuswrt/__init__.py +++ b/homeassistant/components/asuswrt/__init__.py @@ -123,33 +123,26 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: router.async_on_close(entry.add_update_listener(update_listener)) - hass.config_entries.async_setup_platforms(entry, PLATFORMS) - async def async_close_connection(event): """Close AsusWrt connection on HA Stop.""" await router.close() - stop_listener = hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, async_close_connection + entry.async_on_unload( + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_close_connection) ) - hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { - DATA_ASUSWRT: router, - "stop_listener": stop_listener, - } + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {DATA_ASUSWRT: router} + + hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" - unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - - if unload_ok: - hass.data[DOMAIN][entry.entry_id]["stop_listener"]() + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): router = hass.data[DOMAIN][entry.entry_id][DATA_ASUSWRT] await router.close() - hass.data[DOMAIN].pop(entry.entry_id) return unload_ok From a0895f1bf23f72e7689328165e68f4e3450c91ab Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 6 Feb 2022 10:51:50 +0100 Subject: [PATCH 0341/1098] Small cleanup in Plugwise switch (#65845) --- homeassistant/components/plugwise/switch.py | 59 +++++++++++---------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/plugwise/switch.py b/homeassistant/components/plugwise/switch.py index c862983119af20..f9ae7f62e18532 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -1,10 +1,16 @@ """Plugwise Switch component for HomeAssistant.""" +from __future__ import annotations + +from typing import Any + +from plugwise import Smile from plugwise.exceptions import PlugwiseException from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import COORDINATOR, DOMAIN, LOGGER, SWITCH_ICON from .entity import PlugwiseEntity @@ -15,12 +21,6 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up the Smile switches from a config entry.""" - # PLACEHOLDER USB entry setup - return await async_setup_entry_gateway(hass, config_entry, async_add_entities) - - -async def async_setup_entry_gateway(hass, config_entry, async_add_entities): """Set up the Smile switches from a config entry.""" api = hass.data[DOMAIN][config_entry.entry_id]["api"] coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] @@ -55,7 +55,17 @@ async def async_setup_entry_gateway(hass, config_entry, async_add_entities): class GwSwitch(PlugwiseEntity, SwitchEntity): """Representation of a Plugwise plug.""" - def __init__(self, api, coordinator, name, dev_id, members, model): + _attr_icon = SWITCH_ICON + + def __init__( + self, + api: Smile, + coordinator: DataUpdateCoordinator, + name: str, + dev_id: str, + members: list[str] | None, + model: str | None, + ) -> None: """Set up the Plugwise API.""" super().__init__(api, coordinator, name, dev_id) self._attr_unique_id = f"{dev_id}-plug" @@ -63,45 +73,36 @@ def __init__(self, api, coordinator, name, dev_id, members, model): self._members = members self._model = model - self._is_on = False - self._icon = SWITCH_ICON - - @property - def is_on(self): - """Return true if device is on.""" - return self._is_on + self._attr_is_on = False - @property - def icon(self): - """Return the icon of this entity.""" - return self._icon - - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" try: state_on = await self._api.set_relay_state( self._dev_id, self._members, "on" ) - if state_on: - self._is_on = True - self.async_write_ha_state() except PlugwiseException: LOGGER.error("Error while communicating to device") + else: + if state_on: + self._attr_is_on = True + self.async_write_ha_state() - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" try: state_off = await self._api.set_relay_state( self._dev_id, self._members, "off" ) - if state_off: - self._is_on = False - self.async_write_ha_state() except PlugwiseException: LOGGER.error("Error while communicating to device") + else: + if state_off: + self._attr_is_on = False + self.async_write_ha_state() @callback - def _async_process_data(self): + def _async_process_data(self) -> None: """Update the data from the Plugs.""" if not (data := self._api.get_device_data(self._dev_id)): LOGGER.error("Received no data for device %s", self._name) @@ -109,6 +110,6 @@ def _async_process_data(self): return if "relay" in data: - self._is_on = data["relay"] + self._attr_is_on = data["relay"] self.async_write_ha_state() From 13699baa4dd1a691e17a2dd4ab3c327349c6bc75 Mon Sep 17 00:00:00 2001 From: ollo69 <60491700+ollo69@users.noreply.github.com> Date: Sun, 6 Feb 2022 16:03:04 +0100 Subject: [PATCH 0342/1098] Remove deprecated yaml config from AsusWRT (#65904) --- homeassistant/components/asuswrt/__init__.py | 115 +----------------- .../components/asuswrt/config_flow.py | 4 - tests/components/asuswrt/test_config_flow.py | 67 +--------- 3 files changed, 6 insertions(+), 180 deletions(-) diff --git a/homeassistant/components/asuswrt/__init__.py b/homeassistant/components/asuswrt/__init__.py index 1a680a7fb65e8a..f735e520bdc3ad 100644 --- a/homeassistant/components/asuswrt/__init__.py +++ b/homeassistant/components/asuswrt/__init__.py @@ -1,123 +1,18 @@ """Support for ASUSWRT devices.""" -import voluptuous as vol - -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import ( - CONF_HOST, - CONF_MODE, - CONF_PASSWORD, - CONF_PORT, - CONF_PROTOCOL, - CONF_SENSORS, - CONF_USERNAME, - EVENT_HOMEASSISTANT_STOP, - Platform, -) + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EVENT_HOMEASSISTANT_STOP, Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.typing import ConfigType - -from .const import ( - CONF_DNSMASQ, - CONF_INTERFACE, - CONF_REQUIRE_IP, - CONF_SSH_KEY, - DATA_ASUSWRT, - DEFAULT_DNSMASQ, - DEFAULT_INTERFACE, - DEFAULT_SSH_PORT, - DOMAIN, - MODE_AP, - MODE_ROUTER, - PROTOCOL_SSH, - PROTOCOL_TELNET, -) + +from .const import DATA_ASUSWRT, DOMAIN from .router import AsusWrtRouter PLATFORMS = [Platform.DEVICE_TRACKER, Platform.SENSOR] -CONF_PUB_KEY = "pub_key" -SECRET_GROUP = "Password or SSH Key" -SENSOR_TYPES = ["devices", "upload_speed", "download_speed", "download", "upload"] - -CONFIG_SCHEMA = vol.Schema( - vol.All( - cv.deprecated(DOMAIN), - { - DOMAIN: vol.Schema( - { - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_PROTOCOL, default=PROTOCOL_SSH): vol.In( - [PROTOCOL_SSH, PROTOCOL_TELNET] - ), - vol.Optional(CONF_MODE, default=MODE_ROUTER): vol.In( - [MODE_ROUTER, MODE_AP] - ), - vol.Optional(CONF_PORT, default=DEFAULT_SSH_PORT): cv.port, - vol.Optional(CONF_REQUIRE_IP, default=True): cv.boolean, - vol.Exclusive(CONF_PASSWORD, SECRET_GROUP): cv.string, - vol.Exclusive(CONF_SSH_KEY, SECRET_GROUP): cv.isfile, - vol.Exclusive(CONF_PUB_KEY, SECRET_GROUP): cv.isfile, - vol.Optional(CONF_SENSORS): vol.All( - cv.ensure_list, [vol.In(SENSOR_TYPES)] - ), - vol.Optional(CONF_INTERFACE, default=DEFAULT_INTERFACE): cv.string, - vol.Optional(CONF_DNSMASQ, default=DEFAULT_DNSMASQ): cv.string, - } - ) - }, - ), - extra=vol.ALLOW_EXTRA, -) - - -async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up the AsusWrt integration.""" - if (conf := config.get(DOMAIN)) is None: - return True - - # save the options from config yaml - options = {} - mode = conf.get(CONF_MODE, MODE_ROUTER) - for name, value in conf.items(): - if name in [CONF_DNSMASQ, CONF_INTERFACE, CONF_REQUIRE_IP]: - if name == CONF_REQUIRE_IP and mode != MODE_AP: - continue - options[name] = value - hass.data[DOMAIN] = {"yaml_options": options} - - # check if already configured - domains_list = hass.config_entries.async_domains() - if DOMAIN in domains_list: - return True - - # remove not required config keys - if pub_key := conf.pop(CONF_PUB_KEY, ""): - conf[CONF_SSH_KEY] = pub_key - - conf.pop(CONF_REQUIRE_IP, True) - conf.pop(CONF_SENSORS, {}) - conf.pop(CONF_INTERFACE, "") - conf.pop(CONF_DNSMASQ, "") - - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data=conf - ) - ) - - return True - async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up AsusWrt platform.""" - # import options from yaml if empty - yaml_options = hass.data.get(DOMAIN, {}).pop("yaml_options", {}) - if not entry.options and yaml_options: - hass.config_entries.async_update_entry(entry, options=yaml_options) - router = AsusWrtRouter(hass, entry) await router.setup() diff --git a/homeassistant/components/asuswrt/config_flow.py b/homeassistant/components/asuswrt/config_flow.py index 5a20880b4b020c..a06071a33d19bd 100644 --- a/homeassistant/components/asuswrt/config_flow.py +++ b/homeassistant/components/asuswrt/config_flow.py @@ -165,10 +165,6 @@ async def async_step_user(self, user_input=None): data=user_input, ) - async def async_step_import(self, user_input=None): - """Import a config entry.""" - return await self.async_step_user(user_input) - @staticmethod @callback def async_get_options_flow(config_entry): diff --git a/tests/components/asuswrt/test_config_flow.py b/tests/components/asuswrt/test_config_flow.py index a6e24b09462154..7fe2681bafd971 100644 --- a/tests/components/asuswrt/test_config_flow.py +++ b/tests/components/asuswrt/test_config_flow.py @@ -14,7 +14,7 @@ DOMAIN, ) from homeassistant.components.device_tracker.const import CONF_CONSIDER_HOME -from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from homeassistant.config_entries import SOURCE_USER from homeassistant.const import ( CONF_HOST, CONF_MODE, @@ -80,62 +80,6 @@ async def test_user(hass, connect): assert len(mock_setup_entry.mock_calls) == 1 -async def test_import(hass, connect): - """Test import step.""" - with patch( - "homeassistant.components.asuswrt.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.asuswrt.config_flow.socket.gethostbyname", - return_value=IP_ADDRESS, - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data=CONFIG_DATA, - ) - await hass.async_block_till_done() - - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == HOST - assert result["data"] == CONFIG_DATA - - assert len(mock_setup_entry.mock_calls) == 1 - - -async def test_import_ssh(hass, connect): - """Test import step with ssh file.""" - config_data = CONFIG_DATA.copy() - config_data.pop(CONF_PASSWORD) - config_data[CONF_SSH_KEY] = SSH_KEY - - with patch( - "homeassistant.components.asuswrt.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.asuswrt.config_flow.socket.gethostbyname", - return_value=IP_ADDRESS, - ), patch( - "homeassistant.components.asuswrt.config_flow.os.path.isfile", - return_value=True, - ), patch( - "homeassistant.components.asuswrt.config_flow.os.access", - return_value=True, - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data=config_data, - ) - await hass.async_block_till_done() - - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == HOST - assert result["data"] == config_data - - assert len(mock_setup_entry.mock_calls) == 1 - - async def test_error_no_password_ssh(hass): """Test we abort if component is already setup.""" config_data = CONFIG_DATA.copy() @@ -215,15 +159,6 @@ async def test_abort_if_already_setup(hass): assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "single_instance_allowed" - # Should fail, same HOST (import) - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data=CONFIG_DATA, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "single_instance_allowed" - async def test_on_connect_failed(hass): """Test when we have errors connecting the router.""" From 540f1c19d51414180daa6fc53c8d7f0edacdfe2e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 6 Feb 2022 10:13:51 -0600 Subject: [PATCH 0343/1098] Fix Task exception was never retrieved when WiZ devices are offline (#65844) --- homeassistant/components/wiz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/wiz/manifest.json b/homeassistant/components/wiz/manifest.json index 0842fd967b0206..773c0ff0e6e386 100644 --- a/homeassistant/components/wiz/manifest.json +++ b/homeassistant/components/wiz/manifest.json @@ -8,7 +8,7 @@ ], "dependencies": ["network"], "documentation": "https://www.home-assistant.io/integrations/wiz", - "requirements": ["pywizlight==0.4.16"], + "requirements": ["pywizlight==0.5"], "iot_class": "local_polling", "codeowners": ["@sbidy"] } diff --git a/requirements_all.txt b/requirements_all.txt index 68cfbf749ae6bd..f54627b7617e8d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2051,7 +2051,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.4.16 +pywizlight==0.5 # homeassistant.components.xeoma pyxeoma==1.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8e7a45105d4736..1c8f174668ff68 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1276,7 +1276,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.4.16 +pywizlight==0.5 # homeassistant.components.zerproc pyzerproc==0.4.8 From 62a314015cdc584e8db4ddfaa0266d0c8df05af1 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 6 Feb 2022 17:25:55 +0100 Subject: [PATCH 0344/1098] BinarySensorEntityDescriptions for Plugwise (#65887) --- .../components/plugwise/binary_sensor.py | 56 ++++++++++++------- .../components/plugwise/test_binary_sensor.py | 2 +- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index ca65a66b4da703..dfaea46c6158c8 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -1,9 +1,13 @@ """Plugwise Binary Sensor component for Home Assistant.""" from plugwise.smile import Smile -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + BinarySensorEntity, + BinarySensorEntityDescription, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import DataUpdateCoordinator @@ -20,11 +24,19 @@ ) from .entity import PlugwiseEntity -BINARY_SENSOR_MAP = { - "dhw_state": ["Domestic Hot Water State", None], - "slave_boiler_state": ["Secondary Heater Device State", None], -} SEVERITIES = ["other", "info", "warning", "error"] +BINARY_SENSORS: tuple[BinarySensorEntityDescription, ...] = ( + BinarySensorEntityDescription( + key="dhw_state", + name="DHW State", + entity_category=EntityCategory.DIAGNOSTIC, + ), + BinarySensorEntityDescription( + key="slave_boiler_state", + name="Secondary Boiler State", + entity_category=EntityCategory.DIAGNOSTIC, + ), +) async def async_setup_entry( @@ -46,8 +58,8 @@ async def async_setup_entry( if device_properties["class"] == "heater_central": data = api.get_device_data(dev_id) - for binary_sensor in BINARY_SENSOR_MAP: - if binary_sensor not in data: + for description in BINARY_SENSORS: + if description.key not in data: continue entities.append( @@ -56,7 +68,7 @@ async def async_setup_entry( coordinator, device_properties["name"], dev_id, - binary_sensor, + description, ) ) @@ -67,7 +79,10 @@ async def async_setup_entry( coordinator, device_properties["name"], dev_id, - "plugwise_notification", + BinarySensorEntityDescription( + key="plugwise_notification", + name="Plugwise Notification", + ), ) ) @@ -83,19 +98,18 @@ def __init__( coordinator: DataUpdateCoordinator, name: str, dev_id: str, - binary_sensor: str, + description: BinarySensorEntityDescription, ) -> None: """Initialise the binary_sensor.""" super().__init__(api, coordinator, name, dev_id) - self._binary_sensor = binary_sensor + self.entity_description = description self._attr_is_on = False - self._attr_unique_id = f"{dev_id}-{binary_sensor}" + self._attr_unique_id = f"{dev_id}-{description.key}" if dev_id == self._api.heater_id: self._entity_name = "Auxiliary" - sensorname = binary_sensor.replace("_", " ").title() - self._name = f"{self._entity_name} {sensorname}" + self._name = f"{self._entity_name} {description.name}" if dev_id == self._api.gateway_id: self._entity_name = f"Smile {self._entity_name}" @@ -113,19 +127,19 @@ class PwBinarySensor(SmileBinarySensor): def _async_process_data(self) -> None: """Update the entity.""" if not (data := self._api.get_device_data(self._dev_id)): - LOGGER.error("Received no data for device %s", self._binary_sensor) + LOGGER.error("Received no data for device %s", self._dev_id) self.async_write_ha_state() return - if self._binary_sensor not in data: + if self.entity_description.key not in data: self.async_write_ha_state() return - self._attr_is_on = data[self._binary_sensor] + self._attr_is_on = data[self.entity_description.key] - if self._binary_sensor == "dhw_state": + if self.entity_description.key == "dhw_state": self._attr_icon = FLOW_ON_ICON if self._attr_is_on else FLOW_OFF_ICON - if self._binary_sensor == "slave_boiler_state": + if self.entity_description.key == "slave_boiler_state": self._attr_icon = FLAME_ICON if self._attr_is_on else IDLE_ICON self.async_write_ha_state() @@ -140,10 +154,10 @@ def __init__( coordinator: DataUpdateCoordinator, name: str, dev_id: str, - binary_sensor: str, + description: BinarySensorEntityDescription, ) -> None: """Set up the Plugwise API.""" - super().__init__(api, coordinator, name, dev_id, binary_sensor) + super().__init__(api, coordinator, name, dev_id, description) self._attr_extra_state_attributes = {} diff --git a/tests/components/plugwise/test_binary_sensor.py b/tests/components/plugwise/test_binary_sensor.py index 5d802fb42a0123..69080b364f0f83 100644 --- a/tests/components/plugwise/test_binary_sensor.py +++ b/tests/components/plugwise/test_binary_sensor.py @@ -11,7 +11,7 @@ async def test_anna_climate_binary_sensor_entities(hass, mock_smile_anna): entry = await async_init_integration(hass, mock_smile_anna) assert entry.state is ConfigEntryState.LOADED - state = hass.states.get("binary_sensor.auxiliary_slave_boiler_state") + state = hass.states.get("binary_sensor.auxiliary_secondary_boiler_state") assert str(state.state) == STATE_OFF state = hass.states.get("binary_sensor.auxiliary_dhw_state") From e54c89dc0d1d6117627f92c37fe6eee039f98cb6 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Sun, 6 Feb 2022 17:48:56 +0100 Subject: [PATCH 0345/1098] Update xknx to 0.19.2 - fix TCP tunnelling (#65920) --- homeassistant/components/knx/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index 9e4557276ca1f7..924fa284e93087 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/knx", "requirements": [ - "xknx==0.19.1" + "xknx==0.19.2" ], "codeowners": [ "@Julius2342", diff --git a/requirements_all.txt b/requirements_all.txt index f54627b7617e8d..45d1f9cc0d241a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2496,7 +2496,7 @@ xbox-webapi==2.0.11 xboxapi==2.0.1 # homeassistant.components.knx -xknx==0.19.1 +xknx==0.19.2 # homeassistant.components.bluesound # homeassistant.components.fritz diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1c8f174668ff68..600d7ddf5cf36e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1533,7 +1533,7 @@ wolf_smartset==0.1.11 xbox-webapi==2.0.11 # homeassistant.components.knx -xknx==0.19.1 +xknx==0.19.2 # homeassistant.components.bluesound # homeassistant.components.fritz From 92856bab56eb38c6336fe74dc535937aa9eb150d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 6 Feb 2022 17:55:03 +0100 Subject: [PATCH 0346/1098] Add mbd Tuya light support (#65918) --- homeassistant/components/tuya/light.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/homeassistant/components/tuya/light.py b/homeassistant/components/tuya/light.py index 36a7bdf1ef1fb6..88bd545c0a9fd9 100644 --- a/homeassistant/components/tuya/light.py +++ b/homeassistant/components/tuya/light.py @@ -170,6 +170,17 @@ class TuyaLightEntityDescription(LightEntityDescription): entity_category=EntityCategory.CONFIG, ), ), + # Unknown light product + # Found as VECINO RGBW as provided by diagnostics + # Not documented + "mbd": ( + TuyaLightEntityDescription( + key=DPCode.SWITCH_LED, + color_mode=DPCode.WORK_MODE, + brightness=DPCode.BRIGHT_VALUE, + color_data=DPCode.COLOUR_DATA, + ), + ), # Heater # https://developer.tuya.com/en/docs/iot/categoryqn?id=Kaiuz18kih0sm "qn": ( From 34a636ef0bab7ae9d513da94e723120186b42545 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 6 Feb 2022 18:03:50 +0100 Subject: [PATCH 0347/1098] Extract Plugwise DataUpdateCoordinator into module (#65915) --- .../components/plugwise/binary_sensor.py | 12 +++---- homeassistant/components/plugwise/climate.py | 4 +-- .../components/plugwise/coordinator.py | 36 +++++++++++++++++++ homeassistant/components/plugwise/entity.py | 14 ++++---- homeassistant/components/plugwise/gateway.py | 28 ++------------- homeassistant/components/plugwise/sensor.py | 10 +++--- homeassistant/components/plugwise/switch.py | 4 +-- 7 files changed, 62 insertions(+), 46 deletions(-) create mode 100644 homeassistant/components/plugwise/coordinator.py diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index dfaea46c6158c8..e83904ab25d2ea 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -9,7 +9,6 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import ( COORDINATOR, @@ -22,6 +21,7 @@ NO_NOTIFICATION_ICON, NOTIFICATION_ICON, ) +from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity SEVERITIES = ["other", "info", "warning", "error"] @@ -46,9 +46,9 @@ async def async_setup_entry( ) -> None: """Set up the Smile binary_sensors from a config entry.""" api: Smile = hass.data[DOMAIN][config_entry.entry_id]["api"] - coordinator: DataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id][ - COORDINATOR - ] + coordinator: PlugwiseDataUpdateCoordinator = hass.data[DOMAIN][ + config_entry.entry_id + ][COORDINATOR] entities: list[BinarySensorEntity] = [] is_thermostat = api.single_master_thermostat() @@ -95,7 +95,7 @@ class SmileBinarySensor(PlugwiseEntity, BinarySensorEntity): def __init__( self, api: Smile, - coordinator: DataUpdateCoordinator, + coordinator: PlugwiseDataUpdateCoordinator, name: str, dev_id: str, description: BinarySensorEntityDescription, @@ -151,7 +151,7 @@ class PwNotifySensor(SmileBinarySensor): def __init__( self, api: Smile, - coordinator: DataUpdateCoordinator, + coordinator: PlugwiseDataUpdateCoordinator, name: str, dev_id: str, description: BinarySensorEntityDescription, diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index 1063254299eeef..ff1325ffbf93d9 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -19,7 +19,6 @@ from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import ( COORDINATOR, @@ -30,6 +29,7 @@ SCHEDULE_OFF, SCHEDULE_ON, ) +from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity HVAC_MODES_HEAT_ONLY = [HVAC_MODE_HEAT, HVAC_MODE_AUTO] @@ -88,7 +88,7 @@ class PwThermostat(PlugwiseEntity, ClimateEntity): def __init__( self, api: Smile, - coordinator: DataUpdateCoordinator, + coordinator: PlugwiseDataUpdateCoordinator, name: str, dev_id: str, loc_id: str, diff --git a/homeassistant/components/plugwise/coordinator.py b/homeassistant/components/plugwise/coordinator.py new file mode 100644 index 00000000000000..ed6dad01f85ceb --- /dev/null +++ b/homeassistant/components/plugwise/coordinator.py @@ -0,0 +1,36 @@ +"""DataUpdateCoordinator for Plugwise.""" +from datetime import timedelta + +import async_timeout +from plugwise import Smile +from plugwise.exceptions import XMLDataMissingError + +from homeassistant.core import HomeAssistant +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import DEFAULT_SCAN_INTERVAL, DEFAULT_TIMEOUT, DOMAIN, LOGGER + + +class PlugwiseDataUpdateCoordinator(DataUpdateCoordinator[bool]): + """Class to manage fetching Plugwise data from single endpoint.""" + + def __init__(self, hass: HomeAssistant, api: Smile) -> None: + """Initialize the coordinator.""" + super().__init__( + hass, + LOGGER, + name=api.smile_name or DOMAIN, + update_interval=DEFAULT_SCAN_INTERVAL.get( + str(api.smile_type), timedelta(seconds=60) + ), + ) + self.api = api + + async def _async_update_data(self) -> bool: + """Fetch data from Plugwise.""" + try: + async with async_timeout.timeout(DEFAULT_TIMEOUT): + await self.api.full_update_device() + except XMLDataMissingError as err: + raise UpdateFailed("Smile update failed") from err + return True diff --git a/homeassistant/components/plugwise/entity.py b/homeassistant/components/plugwise/entity.py index 8a2526d48117a1..f33ba98235bb8f 100644 --- a/homeassistant/components/plugwise/entity.py +++ b/homeassistant/components/plugwise/entity.py @@ -11,21 +11,23 @@ ) from homeassistant.core import callback from homeassistant.helpers.entity import DeviceInfo -from homeassistant.helpers.update_coordinator import ( - CoordinatorEntity, - DataUpdateCoordinator, -) +from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN +from .coordinator import PlugwiseDataUpdateCoordinator -class PlugwiseEntity(CoordinatorEntity): +class PlugwiseEntity(CoordinatorEntity[bool]): """Represent a PlugWise Entity.""" _model: str | None = None def __init__( - self, api: Smile, coordinator: DataUpdateCoordinator, name: str, dev_id: str + self, + api: Smile, + coordinator: PlugwiseDataUpdateCoordinator, + name: str, + dev_id: str, ) -> None: """Initialise the gateway.""" super().__init__(coordinator) diff --git a/homeassistant/components/plugwise/gateway.py b/homeassistant/components/plugwise/gateway.py index e851941cf27519..d1d35e3134bd56 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -3,12 +3,7 @@ import asyncio -import async_timeout -from plugwise.exceptions import ( - InvalidAuthentication, - PlugwiseException, - XMLDataMissingError, -) +from plugwise.exceptions import InvalidAuthentication, PlugwiseException from plugwise.smile import Smile from homeassistant.config_entries import ConfigEntry @@ -17,13 +12,10 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import ( COORDINATOR, DEFAULT_PORT, - DEFAULT_SCAN_INTERVAL, - DEFAULT_TIMEOUT, DEFAULT_USERNAME, DOMAIN, GATEWAY, @@ -32,6 +24,7 @@ PW_TYPE, SENSOR_PLATFORMS, ) +from .coordinator import PlugwiseDataUpdateCoordinator async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -63,22 +56,7 @@ async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: if not connected: raise ConfigEntryNotReady("Unable to connect to Smile") - async def async_update_data(): - """Update data via API endpoint.""" - try: - async with async_timeout.timeout(DEFAULT_TIMEOUT): - await api.full_update_device() - return True - except XMLDataMissingError as err: - raise UpdateFailed("Smile update failed") from err - - coordinator = DataUpdateCoordinator( - hass, - LOGGER, - name=f"Smile {api.smile_name}", - update_method=async_update_data, - update_interval=DEFAULT_SCAN_INTERVAL[api.smile_type], - ) + coordinator = PlugwiseDataUpdateCoordinator(hass, api) await coordinator.async_config_entry_first_refresh() api.get_all_devices() diff --git a/homeassistant/components/plugwise/sensor.py b/homeassistant/components/plugwise/sensor.py index 9d3f2d780b1884..3cba9f59651c76 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -20,7 +20,6 @@ ) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import ( COOL_ICON, @@ -36,6 +35,7 @@ SENSOR_MAP_UOM, UNIT_LUMEN, ) +from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity ATTR_TEMPERATURE = [ @@ -303,7 +303,7 @@ class SmileSensor(PlugwiseEntity, SensorEntity): def __init__( self, api: Smile, - coordinator: DataUpdateCoordinator, + coordinator: PlugwiseDataUpdateCoordinator, name: str, dev_id: str, sensor: str, @@ -329,7 +329,7 @@ class PwThermostatSensor(SmileSensor): def __init__( self, api: Smile, - coordinator: DataUpdateCoordinator, + coordinator: PlugwiseDataUpdateCoordinator, name: str, dev_id: str, sensor: str, @@ -364,7 +364,7 @@ class PwAuxDeviceSensor(SmileSensor): def __init__( self, api: Smile, - coordinator: DataUpdateCoordinator, + coordinator: PlugwiseDataUpdateCoordinator, name: str, dev_id: str, sensor: str, @@ -406,7 +406,7 @@ class PwPowerSensor(SmileSensor): def __init__( self, api: Smile, - coordinator: DataUpdateCoordinator, + coordinator: PlugwiseDataUpdateCoordinator, name: str, dev_id: str, sensor: str, diff --git a/homeassistant/components/plugwise/switch.py b/homeassistant/components/plugwise/switch.py index f9ae7f62e18532..e5436389fca675 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -10,9 +10,9 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import COORDINATOR, DOMAIN, LOGGER, SWITCH_ICON +from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity @@ -60,7 +60,7 @@ class GwSwitch(PlugwiseEntity, SwitchEntity): def __init__( self, api: Smile, - coordinator: DataUpdateCoordinator, + coordinator: PlugwiseDataUpdateCoordinator, name: str, dev_id: str, members: list[str] | None, From 9597ed3f8aa220a772507325d0077c60ab6510da Mon Sep 17 00:00:00 2001 From: "M. Frister" Date: Sun, 6 Feb 2022 18:17:41 +0100 Subject: [PATCH 0348/1098] Bump soco to 0.26.2 (#65919) --- homeassistant/components/sonos/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonos/manifest.json b/homeassistant/components/sonos/manifest.json index 5986cb18c756cf..87bfc7f89bc198 100644 --- a/homeassistant/components/sonos/manifest.json +++ b/homeassistant/components/sonos/manifest.json @@ -3,7 +3,7 @@ "name": "Sonos", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sonos", - "requirements": ["soco==0.26.0"], + "requirements": ["soco==0.26.2"], "dependencies": ["ssdp"], "after_dependencies": ["plex", "spotify", "zeroconf", "media_source"], "zeroconf": ["_sonos._tcp.local."], diff --git a/requirements_all.txt b/requirements_all.txt index 45d1f9cc0d241a..ef3626b011a81b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2228,7 +2228,7 @@ smhi-pkg==1.0.15 snapcast==2.1.3 # homeassistant.components.sonos -soco==0.26.0 +soco==0.26.2 # homeassistant.components.solaredge_local solaredge-local==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 600d7ddf5cf36e..73272640802737 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1361,7 +1361,7 @@ smarthab==0.21 smhi-pkg==1.0.15 # homeassistant.components.sonos -soco==0.26.0 +soco==0.26.2 # homeassistant.components.solaredge solaredge==0.0.2 From ac7e8c40a318523dc3ae3556c6b2b7ee8320744c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 6 Feb 2022 18:36:02 +0100 Subject: [PATCH 0349/1098] Remove homeassistant import [pylint plugin] (#65911) --- pylint/plugins/hass_enforce_type_hints.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pylint/plugins/hass_enforce_type_hints.py b/pylint/plugins/hass_enforce_type_hints.py index 0137e26a8a2c9c..1264fa2c1df060 100644 --- a/pylint/plugins/hass_enforce_type_hints.py +++ b/pylint/plugins/hass_enforce_type_hints.py @@ -10,7 +10,8 @@ from pylint.lint import PyLinter from homeassistant.const import Platform -from homeassistant.helpers.typing import UNDEFINED + +UNDEFINED = object() @dataclass From 6d4df93bc7e10f75931422837d940265d0a21609 Mon Sep 17 00:00:00 2001 From: Christopher Masto Date: Sun, 6 Feb 2022 12:36:38 -0500 Subject: [PATCH 0350/1098] Correct description of entity_globs (#65805) --- homeassistant/components/recorder/services.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/recorder/services.yaml b/homeassistant/components/recorder/services.yaml index b2ea33fe2dd785..43ff7548dd6d1a 100644 --- a/homeassistant/components/recorder/services.yaml +++ b/homeassistant/components/recorder/services.yaml @@ -44,7 +44,7 @@ purge_entities: entity_globs: name: Entity Globs to remove - description: List the regular expressions to select entities for removal from the recorder database. + description: List the glob patterns to select entities for removal from the recorder database. example: "domain*.object_id*" required: false default: [] From b02a030336e21bce2a9d8c75c2c23d3df140e5b9 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Sun, 6 Feb 2022 11:37:23 -0600 Subject: [PATCH 0351/1098] Fix Spotify, Tidal, Apple Music playback on Sonos groups (#65838) --- homeassistant/components/sonos/media_player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index d490120faf88b5..41453117c13ea0 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -558,7 +558,7 @@ def play_media(self, media_type: str, media_id: str, **kwargs: Any) -> None: plex_plugin.play_now(media) return - share_link = self.speaker.share_link + share_link = self.coordinator.share_link if share_link.is_share_link(media_id): if kwargs.get(ATTR_MEDIA_ENQUEUE): share_link.add_share_link_to_queue(media_id) From e7dfc8945241e250bf3cf73fb8d1c8baea845299 Mon Sep 17 00:00:00 2001 From: Sander Huisman Date: Sun, 6 Feb 2022 18:40:37 +0100 Subject: [PATCH 0352/1098] Add unique ID to InfluxDB sensor (#65518) --- homeassistant/components/influxdb/sensor.py | 3 +++ tests/components/influxdb/test_sensor.py | 24 +++++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/influxdb/sensor.py b/homeassistant/components/influxdb/sensor.py index 7404ceb4c7f1cb..f9535292e69c4a 100644 --- a/homeassistant/components/influxdb/sensor.py +++ b/homeassistant/components/influxdb/sensor.py @@ -14,6 +14,7 @@ from homeassistant.const import ( CONF_API_VERSION, CONF_NAME, + CONF_UNIQUE_ID, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_STOP, @@ -109,6 +110,7 @@ def validate_query_format_for_version(conf: dict) -> dict: _QUERY_SENSOR_SCHEMA = vol.Schema( { vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, } @@ -198,6 +200,7 @@ def __init__(self, hass, influx, query): self._value_template = None self._state = None self._hass = hass + self._attr_unique_id = query.get(CONF_UNIQUE_ID) if query[CONF_LANGUAGE] == LANGUAGE_FLUX: query_clause = query.get(CONF_QUERY) diff --git a/tests/components/influxdb/test_sensor.py b/tests/components/influxdb/test_sensor.py index 86a32877a792bc..6bc2014d24e2fc 100644 --- a/tests/components/influxdb/test_sensor.py +++ b/tests/components/influxdb/test_sensor.py @@ -44,13 +44,22 @@ "queries": [ { "name": "test", + "unique_id": "unique_test_id", "measurement": "measurement", "where": "where", "field": "field", } ], } -BASE_V2_QUERY = {"queries_flux": [{"name": "test", "query": "query"}]} +BASE_V2_QUERY = { + "queries_flux": [ + { + "name": "test", + "unique_id": "unique_test_id", + "query": "query", + } + ] +} @dataclass @@ -232,6 +241,7 @@ async def test_minimal_config(hass, mock_client, config_ext, queries, set_query_ "queries": [ { "name": "test", + "unique_id": "unique_test_id", "unit_of_measurement": "unit", "measurement": "measurement", "where": "where", @@ -260,6 +270,7 @@ async def test_minimal_config(hass, mock_client, config_ext, queries, set_query_ "queries_flux": [ { "name": "test", + "unique_id": "unique_test_id", "unit_of_measurement": "unit", "range_start": "start", "range_stop": "end", @@ -452,6 +463,7 @@ async def test_error_querying_influx( "queries": [ { "name": "test", + "unique_id": "unique_test_id", "measurement": "measurement", "where": "{{ illegal.template }}", "field": "field", @@ -465,7 +477,15 @@ async def test_error_querying_influx( ( API_VERSION_2, BASE_V2_CONFIG, - {"queries_flux": [{"name": "test", "query": "{{ illegal.template }}"}]}, + { + "queries_flux": [ + { + "name": "test", + "unique_id": "unique_test_id", + "query": "{{ illegal.template }}", + } + ] + }, _set_query_mock_v2, _make_v2_resultset, "query", From 900c793c3a78691814c295d724dfb27866179ed8 Mon Sep 17 00:00:00 2001 From: ollo69 <60491700+ollo69@users.noreply.github.com> Date: Sun, 6 Feb 2022 19:00:39 +0100 Subject: [PATCH 0353/1098] Add diagnostics support for Nut (#65893) --- .coveragerc | 1 + homeassistant/components/nut/diagnostics.py | 68 +++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 homeassistant/components/nut/diagnostics.py diff --git a/.coveragerc b/.coveragerc index 8babaab4e25a4a..be94c4a9a7f39a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -759,6 +759,7 @@ omit = homeassistant/components/nuki/const.py homeassistant/components/nuki/binary_sensor.py homeassistant/components/nuki/lock.py + homeassistant/components/nut/diagnostics.py homeassistant/components/nx584/alarm_control_panel.py homeassistant/components/nzbget/coordinator.py homeassistant/components/obihai/* diff --git a/homeassistant/components/nut/diagnostics.py b/homeassistant/components/nut/diagnostics.py new file mode 100644 index 00000000000000..e8c0a0711dc78b --- /dev/null +++ b/homeassistant/components/nut/diagnostics.py @@ -0,0 +1,68 @@ +"""Diagnostics support for Nut.""" +from __future__ import annotations + +from typing import Any + +import attr + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, entity_registry as er + +from . import PyNUTData +from .const import DOMAIN, PYNUT_DATA, PYNUT_UNIQUE_ID + +TO_REDACT = {CONF_PASSWORD, CONF_USERNAME} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, dict[str, Any]]: + """Return diagnostics for a config entry.""" + data = {"entry": async_redact_data(entry.as_dict(), TO_REDACT)} + hass_data = hass.data[DOMAIN][entry.entry_id] + + # Get information from Nut library + nut_data: PyNUTData = hass_data[PYNUT_DATA] + data["nut_data"] = {"ups_list": nut_data.ups_list, "status": nut_data.status} + + # Gather information how this Nut device is represented in Home Assistant + device_registry = dr.async_get(hass) + entity_registry = er.async_get(hass) + hass_device = device_registry.async_get_device( + identifiers={(DOMAIN, hass_data[PYNUT_UNIQUE_ID])} + ) + if not hass_device: + return data + + data["device"] = { + **attr.asdict(hass_device), + "entities": {}, + } + + hass_entities = er.async_entries_for_device( + entity_registry, + device_id=hass_device.id, + include_disabled_entities=True, + ) + + for entity_entry in hass_entities: + state = hass.states.get(entity_entry.entity_id) + state_dict = None + if state: + state_dict = dict(state.as_dict()) + # The entity_id is already provided at root level. + state_dict.pop("entity_id", None) + # The context doesn't provide useful information in this case. + state_dict.pop("context", None) + + data["device"]["entities"][entity_entry.entity_id] = { + **attr.asdict( + entity_entry, filter=lambda attr, value: attr.name != "entity_id" + ), + "state": state_dict, + } + + return data From c41ec6f941252300cb4d0e50cbb2896ff28b95da Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 6 Feb 2022 19:19:57 +0100 Subject: [PATCH 0354/1098] SensorEntityDescriptions for Plugwise (#65898) --- homeassistant/components/plugwise/const.py | 7 - homeassistant/components/plugwise/sensor.py | 521 ++++++++++---------- 2 files changed, 262 insertions(+), 266 deletions(-) diff --git a/homeassistant/components/plugwise/const.py b/homeassistant/components/plugwise/const.py index a885a047f7a905..4c221b2860a171 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -9,9 +9,7 @@ LOGGER = logging.getLogger(__package__) API = "api" -ATTR_ILLUMINANCE = "illuminance" COORDINATOR = "coordinator" -DEVICE_STATE = "device_state" FLOW_SMILE = "smile (Adam/Anna/P1)" FLOW_STRETCH = "stretch (Stretch)" FLOW_TYPE = "flow_type" @@ -38,11 +36,6 @@ "stretch": "Stretch", } -# Sensor mapping -SENSOR_MAP_DEVICE_CLASS = 2 -SENSOR_MAP_MODEL = 0 -SENSOR_MAP_STATE_CLASS = 3 -SENSOR_MAP_UOM = 1 # Default directives DEFAULT_MAX_TEMP = 30 diff --git a/homeassistant/components/plugwise/sensor.py b/homeassistant/components/plugwise/sensor.py index 3cba9f59651c76..d55c2df4df9386 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -6,6 +6,7 @@ from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, + SensorEntityDescription, SensorStateClass, ) from homeassistant.config_entries import ConfigEntry @@ -24,207 +25,259 @@ from .const import ( COOL_ICON, COORDINATOR, - DEVICE_STATE, DOMAIN, FLAME_ICON, IDLE_ICON, LOGGER, - SENSOR_MAP_DEVICE_CLASS, - SENSOR_MAP_MODEL, - SENSOR_MAP_STATE_CLASS, - SENSOR_MAP_UOM, UNIT_LUMEN, ) from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity -ATTR_TEMPERATURE = [ - "Temperature", - TEMP_CELSIUS, - SensorDeviceClass.TEMPERATURE, - SensorStateClass.MEASUREMENT, -] -ATTR_BATTERY_LEVEL = [ - "Charge", - PERCENTAGE, - SensorDeviceClass.BATTERY, - SensorStateClass.MEASUREMENT, -] -ATTR_ILLUMINANCE = [ - "Illuminance", - UNIT_LUMEN, - SensorDeviceClass.ILLUMINANCE, - SensorStateClass.MEASUREMENT, -] -ATTR_PRESSURE = [ - "Pressure", - PRESSURE_BAR, - SensorDeviceClass.PRESSURE, - SensorStateClass.MEASUREMENT, -] - -TEMP_SENSOR_MAP: dict[str, list] = { - "setpoint": ATTR_TEMPERATURE, - "temperature": ATTR_TEMPERATURE, - "intended_boiler_temperature": ATTR_TEMPERATURE, - "temperature_difference": ATTR_TEMPERATURE, - "outdoor_temperature": ATTR_TEMPERATURE, - "water_temperature": ATTR_TEMPERATURE, - "return_temperature": ATTR_TEMPERATURE, -} - -ENERGY_SENSOR_MAP: dict[str, list] = { - "electricity_consumed": [ - "Current Consumed Power", - POWER_WATT, - SensorDeviceClass.POWER, - SensorStateClass.MEASUREMENT, - ], - "electricity_produced": [ - "Current Produced Power", - POWER_WATT, - SensorDeviceClass.POWER, - SensorStateClass.MEASUREMENT, - ], - "electricity_consumed_interval": [ - "Consumed Power Interval", - ENERGY_WATT_HOUR, - SensorDeviceClass.ENERGY, - SensorStateClass.TOTAL, - ], - "electricity_consumed_peak_interval": [ - "Consumed Power Interval", - ENERGY_WATT_HOUR, - SensorDeviceClass.ENERGY, - SensorStateClass.TOTAL, - ], - "electricity_consumed_off_peak_interval": [ - "Consumed Power Interval (off peak)", - ENERGY_WATT_HOUR, - SensorDeviceClass.ENERGY, - SensorStateClass.TOTAL, - ], - "electricity_produced_interval": [ - "Produced Power Interval", - ENERGY_WATT_HOUR, - SensorDeviceClass.ENERGY, - SensorStateClass.TOTAL, - ], - "electricity_produced_peak_interval": [ - "Produced Power Interval", - ENERGY_WATT_HOUR, - SensorDeviceClass.ENERGY, - SensorStateClass.TOTAL, - ], - "electricity_produced_off_peak_interval": [ - "Produced Power Interval (off peak)", - ENERGY_WATT_HOUR, - SensorDeviceClass.ENERGY, - SensorStateClass.TOTAL, - ], - "electricity_consumed_off_peak_point": [ - "Current Consumed Power (off peak)", - POWER_WATT, - SensorDeviceClass.POWER, - SensorStateClass.MEASUREMENT, - ], - "electricity_consumed_peak_point": [ - "Current Consumed Power", - POWER_WATT, - SensorDeviceClass.POWER, - SensorStateClass.MEASUREMENT, - ], - "electricity_consumed_off_peak_cumulative": [ - "Cumulative Consumed Power (off peak)", - ENERGY_KILO_WATT_HOUR, - SensorDeviceClass.ENERGY, - SensorStateClass.TOTAL_INCREASING, - ], - "electricity_consumed_peak_cumulative": [ - "Cumulative Consumed Power", - ENERGY_KILO_WATT_HOUR, - SensorDeviceClass.ENERGY, - SensorStateClass.TOTAL_INCREASING, - ], - "electricity_produced_off_peak_point": [ - "Current Produced Power (off peak)", - POWER_WATT, - SensorDeviceClass.POWER, - SensorStateClass.MEASUREMENT, - ], - "electricity_produced_peak_point": [ - "Current Produced Power", - POWER_WATT, - SensorDeviceClass.POWER, - SensorStateClass.MEASUREMENT, - ], - "electricity_produced_off_peak_cumulative": [ - "Cumulative Produced Power (off peak)", - ENERGY_KILO_WATT_HOUR, - SensorDeviceClass.ENERGY, - SensorStateClass.TOTAL_INCREASING, - ], - "electricity_produced_peak_cumulative": [ - "Cumulative Produced Power", - ENERGY_KILO_WATT_HOUR, - SensorDeviceClass.ENERGY, - SensorStateClass.TOTAL_INCREASING, - ], - "gas_consumed_interval": [ - "Current Consumed Gas Interval", - VOLUME_CUBIC_METERS, - SensorDeviceClass.GAS, - SensorStateClass.TOTAL, - ], - "gas_consumed_cumulative": [ - "Consumed Gas", - VOLUME_CUBIC_METERS, - SensorDeviceClass.GAS, - SensorStateClass.TOTAL_INCREASING, - ], - "net_electricity_point": [ - "Current net Power", - POWER_WATT, - SensorDeviceClass.POWER, - SensorStateClass.MEASUREMENT, - ], - "net_electricity_cumulative": [ - "Cumulative net Power", - ENERGY_KILO_WATT_HOUR, - SensorDeviceClass.ENERGY, - SensorStateClass.TOTAL, - ], -} - -MISC_SENSOR_MAP: dict[str, list] = { - "battery": ATTR_BATTERY_LEVEL, - "illuminance": ATTR_ILLUMINANCE, - "modulation_level": [ - "Heater Modulation Level", - PERCENTAGE, - None, - SensorStateClass.MEASUREMENT, - ], - "valve_position": [ - "Valve Position", - PERCENTAGE, - None, - SensorStateClass.MEASUREMENT, - ], - "water_pressure": ATTR_PRESSURE, -} - -INDICATE_ACTIVE_LOCAL_DEVICE = [ - "cooling_state", - "flame_state", -] - -CUSTOM_ICONS = { - "gas_consumed_interval": "mdi:fire", - "gas_consumed_cumulative": "mdi:fire", - "modulation_level": "mdi:percent", - "valve_position": "mdi:valve", -} +SENSORS: tuple[SensorEntityDescription, ...] = ( + SensorEntityDescription( + key="setpoint", + name="Setpoint", + native_unit_of_measurement=TEMP_CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="temperature", + name="Temperature", + native_unit_of_measurement=TEMP_CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="intended_boiler_temperature", + name="Intended Boiler Temperature", + native_unit_of_measurement=TEMP_CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="temperature_difference", + name="Temperature Difference", + native_unit_of_measurement=TEMP_CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="outdoor_temperature", + name="Outdoor Temperature", + native_unit_of_measurement=TEMP_CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="water_temperature", + name="Water Temperature", + native_unit_of_measurement=TEMP_CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="return_temperature", + name="Return Temperature", + native_unit_of_measurement=TEMP_CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="return_temperature", + name="Return Temperature", + native_unit_of_measurement=TEMP_CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="electricity_consumed", + name="Electricity Consumed", + native_unit_of_measurement=POWER_WATT, + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="electricity_produced", + name="Electricity Produced", + native_unit_of_measurement=POWER_WATT, + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="electricity_consumed_interval", + name="Electricity Consumed Interval", + native_unit_of_measurement=ENERGY_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL, + ), + SensorEntityDescription( + key="electricity_consumed_peak_interval", + name="Electricity Consumed Peak Interval", + native_unit_of_measurement=ENERGY_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL, + ), + SensorEntityDescription( + key="electricity_consumed_off_peak_interval", + name="Electricity Consumed Off Peak Interval", + native_unit_of_measurement=ENERGY_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL, + ), + SensorEntityDescription( + key="electricity_produced_interval", + name="Electricity Produced Interval", + native_unit_of_measurement=ENERGY_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL, + ), + SensorEntityDescription( + key="electricity_produced_peak_interval", + name="Electricity Produced Peak Interval", + native_unit_of_measurement=ENERGY_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL, + ), + SensorEntityDescription( + key="electricity_produced_off_peak_interval", + name="Electricity Produced Off Peak Interval", + native_unit_of_measurement=ENERGY_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL, + ), + SensorEntityDescription( + key="electricity_consumed_off_peak_point", + name="Electricity Consumed Off Peak Point", + native_unit_of_measurement=POWER_WATT, + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="electricity_consumed_peak_point", + name="Electricity Consumed Peak Point", + native_unit_of_measurement=POWER_WATT, + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="electricity_consumed_off_peak_cumulative", + name="Electricity Consumed Off Peak Cumulative", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + SensorEntityDescription( + key="electricity_consumed_peak_cumulative", + name="Electricity Consumed Peak Cumulative", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + SensorEntityDescription( + key="electricity_produced_off_peak_point", + name="Electricity Produced Off Peak Point", + native_unit_of_measurement=POWER_WATT, + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="electricity_produced_peak_point", + name="Electricity Produced Peak Point", + native_unit_of_measurement=POWER_WATT, + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="electricity_produced_off_peak_cumulative", + name="Electricity Produced Off Peak Cumulative", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + SensorEntityDescription( + key="electricity_produced_peak_cumulative", + name="Electricity Produced Peak Cumulative", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + SensorEntityDescription( + key="gas_consumed_interval", + name="Gas Consumed Interval", + native_unit_of_measurement=VOLUME_CUBIC_METERS, + device_class=SensorDeviceClass.GAS, + state_class=SensorStateClass.TOTAL, + ), + SensorEntityDescription( + key="gas_consumed_cumulative", + name="Gas Consumed Cumulative", + native_unit_of_measurement=VOLUME_CUBIC_METERS, + device_class=SensorDeviceClass.GAS, + state_class=SensorStateClass.TOTAL, + ), + SensorEntityDescription( + key="net_electricity_point", + name="Net Electricity Point", + native_unit_of_measurement=POWER_WATT, + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="net_electricity_cumulative", + name="Net Electricity Cumulative", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL, + ), + SensorEntityDescription( + key="battery", + name="Battery", + native_unit_of_measurement=PERCENTAGE, + device_class=SensorDeviceClass.BATTERY, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="illuminance", + name="Illuminance", + native_unit_of_measurement=UNIT_LUMEN, + device_class=SensorDeviceClass.ILLUMINANCE, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="modulation_level", + name="Modulation Level", + icon="mdi:percent", + native_unit_of_measurement=PERCENTAGE, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="valve_position", + name="Valve Position", + icon="mdi:valve", + native_unit_of_measurement=PERCENTAGE, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="water_pressure", + name="Water Pressure", + native_unit_of_measurement=PRESSURE_BAR, + device_class=SensorDeviceClass.PRESSURE, + state_class=SensorStateClass.MEASUREMENT, + ), +) + +INDICATE_ACTIVE_LOCAL_DEVICE_SENSORS: tuple[SensorEntityDescription, ...] = ( + SensorEntityDescription( + key="cooling_state", + name="Cooling State", + ), + SensorEntityDescription( + key="flame_state", + name="Flame State", + ), +) async def async_setup_entry( @@ -241,12 +294,8 @@ async def async_setup_entry( single_thermostat = api.single_master_thermostat() for dev_id, device_properties in all_devices.items(): data = api.get_device_data(dev_id) - for sensor, sensor_type in { - **TEMP_SENSOR_MAP, - **ENERGY_SENSOR_MAP, - **MISC_SENSOR_MAP, - }.items(): - if data.get(sensor) is None: + for description in SENSORS: + if data.get(description.key) is None: continue if "power" in device_properties["types"]: @@ -261,9 +310,8 @@ async def async_setup_entry( coordinator, device_properties["name"], dev_id, - sensor, - sensor_type, model, + description, ) ) else: @@ -273,14 +321,13 @@ async def async_setup_entry( coordinator, device_properties["name"], dev_id, - sensor, - sensor_type, + description, ) ) if single_thermostat is False: - for state in INDICATE_ACTIVE_LOCAL_DEVICE: - if state not in data: + for description in INDICATE_ACTIVE_LOCAL_DEVICE_SENSORS: + if description.key not in data: continue entities.append( @@ -289,7 +336,7 @@ async def async_setup_entry( coordinator, device_properties["name"], dev_id, - DEVICE_STATE, + description, ) ) break @@ -306,18 +353,17 @@ def __init__( coordinator: PlugwiseDataUpdateCoordinator, name: str, dev_id: str, - sensor: str, + description: SensorEntityDescription, ) -> None: """Initialise the sensor.""" super().__init__(api, coordinator, name, dev_id) - self._attr_unique_id = f"{dev_id}-{sensor}" - self._sensor = sensor + self.entity_description = description + self._attr_unique_id = f"{dev_id}-{description.key}" if dev_id == self._api.heater_id: self._entity_name = "Auxiliary" - sensorname = sensor.replace("_", " ").title() - self._name = f"{self._entity_name} {sensorname}" + self._name = f"{self._entity_name} {description.name}" if dev_id == self._api.gateway_id: self._entity_name = f"Smile {self._entity_name}" @@ -326,23 +372,6 @@ def __init__( class PwThermostatSensor(SmileSensor): """Thermostat (or generic) sensor devices.""" - def __init__( - self, - api: Smile, - coordinator: PlugwiseDataUpdateCoordinator, - name: str, - dev_id: str, - sensor: str, - sensor_type: list[str], - ) -> None: - """Set up the Plugwise API.""" - super().__init__(api, coordinator, name, dev_id, sensor) - - self._model = sensor_type[SENSOR_MAP_MODEL] - self._attr_native_unit_of_measurement = sensor_type[SENSOR_MAP_UOM] - self._attr_device_class = sensor_type[SENSOR_MAP_DEVICE_CLASS] - self._attr_state_class = sensor_type[SENSOR_MAP_STATE_CLASS] - @callback def _async_process_data(self) -> None: """Update the entity.""" @@ -351,29 +380,15 @@ def _async_process_data(self) -> None: self.async_write_ha_state() return - if data.get(self._sensor) is not None: - self._attr_native_value = data[self._sensor] - self._attr_icon = CUSTOM_ICONS.get(self._sensor, self.icon) - + self._attr_native_value = data.get(self.entity_description.key) self.async_write_ha_state() class PwAuxDeviceSensor(SmileSensor): """Auxiliary Device Sensors.""" - def __init__( - self, - api: Smile, - coordinator: PlugwiseDataUpdateCoordinator, - name: str, - dev_id: str, - sensor: str, - ) -> None: - """Set up the Plugwise API.""" - super().__init__(api, coordinator, name, dev_id, sensor) - - self._cooling_state = False - self._heating_state = False + _cooling_state = False + _heating_state = False @callback def _async_process_data(self) -> None: @@ -409,21 +424,12 @@ def __init__( coordinator: PlugwiseDataUpdateCoordinator, name: str, dev_id: str, - sensor: str, - sensor_type: list[str], model: str | None, + description: SensorEntityDescription, ) -> None: """Set up the Plugwise API.""" - super().__init__(api, coordinator, name, dev_id, sensor) - + super().__init__(api, coordinator, name, dev_id, description) self._model = model - if model is None: - self._model = sensor_type[SENSOR_MAP_MODEL] - - self._attr_native_unit_of_measurement = sensor_type[SENSOR_MAP_UOM] - self._attr_device_class = sensor_type[SENSOR_MAP_DEVICE_CLASS] - self._attr_state_class = sensor_type[SENSOR_MAP_STATE_CLASS] - if dev_id == self._api.gateway_id: self._model = "P1 DSMR" @@ -435,8 +441,5 @@ def _async_process_data(self) -> None: self.async_write_ha_state() return - if data.get(self._sensor) is not None: - self._attr_native_value = data[self._sensor] - self._attr_icon = CUSTOM_ICONS.get(self._sensor, self.icon) - + self._attr_native_value = data.get(self.entity_description.key) self.async_write_ha_state() From ccdf182d31f1237668d029f306c0fb2a9d9b150d Mon Sep 17 00:00:00 2001 From: Niels AD Date: Sun, 6 Feb 2022 18:39:57 +0000 Subject: [PATCH 0355/1098] rfxtrx: Add command_on/command_off support for pt2262 switch entities (#65798) --- homeassistant/components/rfxtrx/switch.py | 65 ++++++++++++-- tests/components/rfxtrx/test_binary_sensor.py | 2 +- tests/components/rfxtrx/test_switch.py | 85 +++++++++++++++++++ 3 files changed, 144 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/rfxtrx/switch.py b/homeassistant/components/rfxtrx/switch.py index 988beaa8fb6010..8bc1fa428744fa 100644 --- a/homeassistant/components/rfxtrx/switch.py +++ b/homeassistant/components/rfxtrx/switch.py @@ -7,7 +7,7 @@ from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import STATE_ON +from homeassistant.const import CONF_COMMAND_OFF, CONF_COMMAND_ON, STATE_ON from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -17,8 +17,15 @@ DeviceTuple, RfxtrxCommandEntity, async_setup_platform_entry, + get_pt2262_cmd, +) +from .const import ( + COMMAND_OFF_LIST, + COMMAND_ON_LIST, + CONF_DATA_BITS, + CONF_SIGNAL_REPETITIONS, + DEVICE_PACKET_TYPE_LIGHTING4, ) -from .const import COMMAND_OFF_LIST, COMMAND_ON_LIST, CONF_SIGNAL_REPETITIONS DATA_SWITCH = f"{DOMAIN}_switch" @@ -53,6 +60,9 @@ def _constructor( event.device, device_id, entity_info.get(CONF_SIGNAL_REPETITIONS, DEFAULT_SIGNAL_REPETITIONS), + entity_info.get(CONF_DATA_BITS), + entity_info.get(CONF_COMMAND_ON), + entity_info.get(CONF_COMMAND_OFF), event=event if auto else None, ) ] @@ -65,6 +75,22 @@ def _constructor( class RfxtrxSwitch(RfxtrxCommandEntity, SwitchEntity): """Representation of a RFXtrx switch.""" + def __init__( + self, + device: rfxtrxmod.RFXtrxDevice, + device_id: DeviceTuple, + signal_repetitions: int = 1, + data_bits: int | None = None, + cmd_on: int | None = None, + cmd_off: int | None = None, + event: rfxtrxmod.RFXtrxEvent | None = None, + ) -> None: + """Initialize the RFXtrx switch.""" + super().__init__(device, device_id, signal_repetitions, event=event) + self._data_bits = data_bits + self._cmd_on = cmd_on + self._cmd_off = cmd_off + async def async_added_to_hass(self): """Restore device state.""" await super().async_added_to_hass() @@ -74,15 +100,34 @@ async def async_added_to_hass(self): if old_state is not None: self._state = old_state.state == STATE_ON - def _apply_event(self, event: rfxtrxmod.RFXtrxEvent) -> None: - """Apply command from rfxtrx.""" + def _apply_event_lighting4(self, event: rfxtrxmod.RFXtrxEvent): + """Apply event for a lighting 4 device.""" + if self._data_bits is not None: + cmdstr = get_pt2262_cmd(event.device.id_string, self._data_bits) + assert cmdstr + cmd = int(cmdstr, 16) + if cmd == self._cmd_on: + self._state = True + elif cmd == self._cmd_off: + self._state = False + else: + self._state = True + + def _apply_event_standard(self, event: rfxtrxmod.RFXtrxEvent) -> None: assert isinstance(event, rfxtrxmod.ControlEvent) - super()._apply_event(event) if event.values["Command"] in COMMAND_ON_LIST: self._state = True elif event.values["Command"] in COMMAND_OFF_LIST: self._state = False + def _apply_event(self, event: rfxtrxmod.RFXtrxEvent) -> None: + """Apply command from rfxtrx.""" + super()._apply_event(event) + if event.device.packettype == DEVICE_PACKET_TYPE_LIGHTING4: + self._apply_event_lighting4(event) + else: + self._apply_event_standard(event) + @callback def _handle_event( self, event: rfxtrxmod.RFXtrxEvent, device_id: DeviceTuple @@ -100,12 +145,18 @@ def is_on(self): async def async_turn_on(self, **kwargs): """Turn the device on.""" - await self._async_send(self._device.send_on) + if self._cmd_on is not None: + await self._async_send(self._device.send_command, self._cmd_on) + else: + await self._async_send(self._device.send_on) self._state = True self.async_write_ha_state() async def async_turn_off(self, **kwargs): """Turn the device off.""" - await self._async_send(self._device.send_off) + if self._cmd_off is not None: + await self._async_send(self._device.send_command, self._cmd_off) + else: + await self._async_send(self._device.send_off) self._state = False self.async_write_ha_state() diff --git a/tests/components/rfxtrx/test_binary_sensor.py b/tests/components/rfxtrx/test_binary_sensor.py index 96368d166d7739..175b455da6b784 100644 --- a/tests/components/rfxtrx/test_binary_sensor.py +++ b/tests/components/rfxtrx/test_binary_sensor.py @@ -38,7 +38,7 @@ async def test_one(hass, rfxtrx): async def test_one_pt2262(hass, rfxtrx): - """Test with 1 sensor.""" + """Test with 1 PT2262 sensor.""" entry_data = create_rfx_test_cfg( devices={ "0913000022670e013970": { diff --git a/tests/components/rfxtrx/test_switch.py b/tests/components/rfxtrx/test_switch.py index 6c22ee02920d0e..a4560934ee1de5 100644 --- a/tests/components/rfxtrx/test_switch.py +++ b/tests/components/rfxtrx/test_switch.py @@ -52,6 +52,50 @@ async def test_one_switch(hass, rfxtrx): ] +async def test_one_pt2262_switch(hass, rfxtrx): + """Test with 1 PT2262 switch.""" + entry_data = create_rfx_test_cfg( + devices={ + "0913000022670e013970": { + "signal_repetitions": 1, + "data_bits": 4, + "command_on": 0xE, + "command_off": 0x7, + } + } + ) + mock_entry = MockConfigEntry(domain="rfxtrx", unique_id=DOMAIN, data=entry_data) + + mock_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get("switch.pt2262_22670e") + assert state + assert state.state == STATE_UNKNOWN + assert state.attributes.get("friendly_name") == "PT2262 22670e" + + await hass.services.async_call( + "switch", "turn_on", {"entity_id": "switch.pt2262_22670e"}, blocking=True + ) + + state = hass.states.get("switch.pt2262_22670e") + assert state.state == "on" + + await hass.services.async_call( + "switch", "turn_off", {"entity_id": "switch.pt2262_22670e"}, blocking=True + ) + + state = hass.states.get("switch.pt2262_22670e") + assert state.state == "off" + + assert rfxtrx.transport.send.mock_calls == [ + call(bytearray(b"\x09\x13\x00\x00\x22\x67\x0e\x01\x39\x00")), + call(bytearray(b"\x09\x13\x00\x00\x22\x67\x0f\x01\x39\x00")), + ] + + @pytest.mark.parametrize("state", ["on", "off"]) async def test_state_restore(hass, rfxtrx, state): """State restoration.""" @@ -182,6 +226,47 @@ async def test_switch_events(hass, rfxtrx): assert hass.states.get("switch.ac_213c7f2_16").state == "off" +async def test_pt2262_switch_events(hass, rfxtrx): + """Test with 1 PT2262 switch.""" + entry_data = create_rfx_test_cfg( + devices={ + "0913000022670e013970": { + "signal_repetitions": 1, + "data_bits": 4, + "command_on": 0xE, + "command_off": 0x7, + } + } + ) + mock_entry = MockConfigEntry(domain="rfxtrx", unique_id=DOMAIN, data=entry_data) + + mock_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get("switch.pt2262_22670e") + assert state + assert state.state == STATE_UNKNOWN + assert state.attributes.get("friendly_name") == "PT2262 22670e" + + # "Command: 0xE" + await rfxtrx.signal("0913000022670e013970") + assert hass.states.get("switch.pt2262_22670e").state == "on" + + # "Command: 0x0" + await rfxtrx.signal("09130000226700013970") + assert hass.states.get("switch.pt2262_22670e").state == "on" + + # "Command: 0x7" + await rfxtrx.signal("09130000226707013d70") + assert hass.states.get("switch.pt2262_22670e").state == "off" + + # "Command: 0x1" + await rfxtrx.signal("09130000226701013d70") + assert hass.states.get("switch.pt2262_22670e").state == "off" + + async def test_discover_switch(hass, rfxtrx_automatic): """Test with discovery of switches.""" rfxtrx = rfxtrx_automatic From 63d3a47599becc313c11b0bdebbded8a5a3f35f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Sun, 6 Feb 2022 20:23:31 +0100 Subject: [PATCH 0356/1098] disabled_by can be None when updating devices (#65934) --- homeassistant/components/config/device_registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/config/device_registry.py b/homeassistant/components/config/device_registry.py index 5e7c2ef193851e..686fffec252431 100644 --- a/homeassistant/components/config/device_registry.py +++ b/homeassistant/components/config/device_registry.py @@ -62,7 +62,7 @@ async def websocket_update_device(hass, connection, msg): msg.pop("type") msg_id = msg.pop("id") - if "disabled_by" in msg: + if msg.get("disabled_by") is not None: msg["disabled_by"] = DeviceEntryDisabler(msg["disabled_by"]) entry = registry.async_update_device(**msg) From c6aa526469df9a7b94c5d32685b92b7562b83102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Elio=20Petten=C3=B2?= Date: Sun, 6 Feb 2022 21:33:58 +0000 Subject: [PATCH 0357/1098] Support songpal wireless-only soundbar identifiers (#65330) As shown in #64868, a number of newer models don't come wiht a macAddr attributes, so for those fall back to the wireless address. This could be hidden by the python-songpal library but for now this will make it possible to have multiple modern songpal devices on the same network. --- .../components/songpal/media_player.py | 9 +- tests/components/songpal/__init__.py | 16 ++- tests/components/songpal/test_media_player.py | 100 +++++++++++++++++- 3 files changed, 114 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/songpal/media_player.py b/homeassistant/components/songpal/media_player.py index 7492bd536e364e..e161f818c8c057 100644 --- a/homeassistant/components/songpal/media_player.py +++ b/homeassistant/components/songpal/media_player.py @@ -212,13 +212,18 @@ def name(self): @property def unique_id(self): """Return a unique ID.""" - return self._sysinfo.macAddr + return self._sysinfo.macAddr or self._sysinfo.wirelessMacAddr @property def device_info(self) -> DeviceInfo: """Return the device info.""" + connections = set() + if self._sysinfo.macAddr: + connections.add((dr.CONNECTION_NETWORK_MAC, self._sysinfo.macAddr)) + if self._sysinfo.wirelessMacAddr: + connections.add((dr.CONNECTION_NETWORK_MAC, self._sysinfo.wirelessMacAddr)) return DeviceInfo( - connections={(dr.CONNECTION_NETWORK_MAC, self._sysinfo.macAddr)}, + connections=connections, identifiers={(DOMAIN, self.unique_id)}, manufacturer="Sony Corporation", model=self._model, diff --git a/tests/components/songpal/__init__.py b/tests/components/songpal/__init__.py index f3004ef22e2b1a..a9ca62ecb09143 100644 --- a/tests/components/songpal/__init__.py +++ b/tests/components/songpal/__init__.py @@ -2,6 +2,7 @@ from unittest.mock import AsyncMock, MagicMock, patch from songpal import SongpalException +from songpal.containers import Sysinfo from homeassistant.components.songpal.const import CONF_ENDPOINT from homeassistant.const import CONF_NAME @@ -12,6 +13,7 @@ ENDPOINT = f"http://{HOST}:10000/sony" MODEL = "model" MAC = "mac" +WIRELESS_MAC = "wmac" SW_VERSION = "sw_ver" CONF_DATA = { @@ -20,7 +22,7 @@ } -def _create_mocked_device(throw_exception=False): +def _create_mocked_device(throw_exception=False, wired_mac=MAC, wireless_mac=None): mocked_device = MagicMock() type(mocked_device).get_supported_methods = AsyncMock( @@ -35,9 +37,15 @@ def _create_mocked_device(throw_exception=False): return_value=interface_info ) - sys_info = MagicMock() - sys_info.macAddr = MAC - sys_info.version = SW_VERSION + sys_info = Sysinfo( + bdAddr=None, + macAddr=wired_mac, + wirelessMacAddr=wireless_mac, + bssid=None, + ssid=None, + bleID=None, + version=SW_VERSION, + ) type(mocked_device).get_system_info = AsyncMock(return_value=sys_info) volume1 = MagicMock() diff --git a/tests/components/songpal/test_media_player.py b/tests/components/songpal/test_media_player.py index 0fd5644e794ac4..815995814f90b1 100644 --- a/tests/components/songpal/test_media_player.py +++ b/tests/components/songpal/test_media_player.py @@ -29,6 +29,7 @@ MAC, MODEL, SW_VERSION, + WIRELESS_MAC, _create_mocked_device, _patch_media_player_device, ) @@ -126,6 +127,78 @@ async def test_state(hass): assert entity.unique_id == MAC +async def test_state_wireless(hass): + """Test state of the entity with only Wireless MAC.""" + mocked_device = _create_mocked_device(wired_mac=None, wireless_mac=WIRELESS_MAC) + entry = MockConfigEntry(domain=songpal.DOMAIN, data=CONF_DATA) + entry.add_to_hass(hass) + + with _patch_media_player_device(mocked_device): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.name == FRIENDLY_NAME + assert state.state == STATE_ON + attributes = state.as_dict()["attributes"] + assert attributes["volume_level"] == 0.5 + assert attributes["is_volume_muted"] is False + assert attributes["source_list"] == ["title1", "title2"] + assert attributes["source"] == "title2" + assert attributes["supported_features"] == SUPPORT_SONGPAL + + device_registry = dr.async_get(hass) + device = device_registry.async_get_device( + identifiers={(songpal.DOMAIN, WIRELESS_MAC)} + ) + assert device.connections == {(dr.CONNECTION_NETWORK_MAC, WIRELESS_MAC)} + assert device.manufacturer == "Sony Corporation" + assert device.name == FRIENDLY_NAME + assert device.sw_version == SW_VERSION + assert device.model == MODEL + + entity_registry = er.async_get(hass) + entity = entity_registry.async_get(ENTITY_ID) + assert entity.unique_id == WIRELESS_MAC + + +async def test_state_both(hass): + """Test state of the entity with both Wired and Wireless MAC.""" + mocked_device = _create_mocked_device(wired_mac=MAC, wireless_mac=WIRELESS_MAC) + entry = MockConfigEntry(domain=songpal.DOMAIN, data=CONF_DATA) + entry.add_to_hass(hass) + + with _patch_media_player_device(mocked_device): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.name == FRIENDLY_NAME + assert state.state == STATE_ON + attributes = state.as_dict()["attributes"] + assert attributes["volume_level"] == 0.5 + assert attributes["is_volume_muted"] is False + assert attributes["source_list"] == ["title1", "title2"] + assert attributes["source"] == "title2" + assert attributes["supported_features"] == SUPPORT_SONGPAL + + device_registry = dr.async_get(hass) + device = device_registry.async_get_device(identifiers={(songpal.DOMAIN, MAC)}) + assert device.connections == { + (dr.CONNECTION_NETWORK_MAC, MAC), + (dr.CONNECTION_NETWORK_MAC, WIRELESS_MAC), + } + assert device.manufacturer == "Sony Corporation" + assert device.name == FRIENDLY_NAME + assert device.sw_version == SW_VERSION + assert device.model == MODEL + + entity_registry = er.async_get(hass) + entity = entity_registry.async_get(ENTITY_ID) + # We prefer the wired mac if present. + assert entity.unique_id == MAC + + async def test_services(hass): """Test services.""" mocked_device = _create_mocked_device() @@ -173,11 +246,7 @@ async def _call(service, **argv): mocked_device.set_sound_settings.assert_called_once_with("name", "value") mocked_device.set_sound_settings.reset_mock() - mocked_device2 = _create_mocked_device() - sys_info = MagicMock() - sys_info.macAddr = "mac2" - sys_info.version = SW_VERSION - type(mocked_device2).get_system_info = AsyncMock(return_value=sys_info) + mocked_device2 = _create_mocked_device(wired_mac="mac2") entry2 = MockConfigEntry( domain=songpal.DOMAIN, data={CONF_NAME: "d2", CONF_ENDPOINT: ENDPOINT} ) @@ -194,6 +263,27 @@ async def _call(service, **argv): ) mocked_device.set_sound_settings.assert_called_once_with("name", "value") mocked_device2.set_sound_settings.assert_called_once_with("name", "value") + mocked_device.set_sound_settings.reset_mock() + mocked_device2.set_sound_settings.reset_mock() + + mocked_device3 = _create_mocked_device(wired_mac=None, wireless_mac=WIRELESS_MAC) + entry3 = MockConfigEntry( + domain=songpal.DOMAIN, data={CONF_NAME: "d2", CONF_ENDPOINT: ENDPOINT} + ) + entry3.add_to_hass(hass) + with _patch_media_player_device(mocked_device3): + await hass.config_entries.async_setup(entry3.entry_id) + await hass.async_block_till_done() + + await hass.services.async_call( + songpal.DOMAIN, + SET_SOUND_SETTING, + {"entity_id": "all", "name": "name", "value": "value"}, + blocking=True, + ) + mocked_device.set_sound_settings.assert_called_once_with("name", "value") + mocked_device2.set_sound_settings.assert_called_once_with("name", "value") + mocked_device3.set_sound_settings.assert_called_once_with("name", "value") async def test_websocket_events(hass): From 88309a26b78f6b1df80b7dbe4baa89704f2a19f7 Mon Sep 17 00:00:00 2001 From: Alex Yao <33379584+alexyao2015@users.noreply.github.com> Date: Sun, 6 Feb 2022 16:34:27 -0500 Subject: [PATCH 0358/1098] Fix Yeelight Music Mode Rate Limits (#64891) --- CODEOWNERS | 4 ++-- homeassistant/components/yeelight/light.py | 11 ++++++---- .../components/yeelight/manifest.json | 4 ++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/yeelight/__init__.py | 2 +- tests/components/yeelight/test_light.py | 22 +++++++++---------- 7 files changed, 25 insertions(+), 22 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index b89de45775108f..6eec61b49e20f5 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1090,8 +1090,8 @@ homeassistant/components/yamaha_musiccast/* @vigonotion @micha91 tests/components/yamaha_musiccast/* @vigonotion @micha91 homeassistant/components/yandex_transport/* @rishatik92 @devbis tests/components/yandex_transport/* @rishatik92 @devbis -homeassistant/components/yeelight/* @zewelor @shenxn @starkillerOG -tests/components/yeelight/* @zewelor @shenxn @starkillerOG +homeassistant/components/yeelight/* @zewelor @shenxn @starkillerOG @alexyao2015 +tests/components/yeelight/* @zewelor @shenxn @starkillerOG @alexyao2015 homeassistant/components/yeelightsunflower/* @lindsaymarkward homeassistant/components/yi/* @bachya homeassistant/components/youless/* @gjong diff --git a/homeassistant/components/yeelight/light.py b/homeassistant/components/yeelight/light.py index 84b76d98658487..0c906b3e268cd7 100644 --- a/homeassistant/components/yeelight/light.py +++ b/homeassistant/components/yeelight/light.py @@ -7,7 +7,8 @@ import voluptuous as vol import yeelight -from yeelight import Bulb, Flow, RGBTransition, SleepTransition, flows +from yeelight import Flow, RGBTransition, SleepTransition, flows +from yeelight.aio import AsyncBulb from yeelight.enums import BulbType, LightType, PowerMode, SceneClass from yeelight.main import BulbException @@ -549,7 +550,7 @@ def effect(self): return self._effect if self.device.is_color_flow_enabled else None @property - def _bulb(self) -> Bulb: + def _bulb(self) -> AsyncBulb: return self.device.bulb @property @@ -608,8 +609,10 @@ async def async_set_music_mode(self, music_mode) -> None: async def _async_set_music_mode(self, music_mode) -> None: """Set the music mode on or off wrapped with _async_cmd.""" bulb = self._bulb - method = bulb.stop_music if not music_mode else bulb.start_music - await self.hass.async_add_executor_job(method) + if music_mode: + await bulb.async_start_music() + else: + await bulb.async_stop_music() @_async_cmd async def async_set_brightness(self, brightness, duration) -> None: diff --git a/homeassistant/components/yeelight/manifest.json b/homeassistant/components/yeelight/manifest.json index 7820e17a9782e2..351cb879da9e9b 100644 --- a/homeassistant/components/yeelight/manifest.json +++ b/homeassistant/components/yeelight/manifest.json @@ -2,8 +2,8 @@ "domain": "yeelight", "name": "Yeelight", "documentation": "https://www.home-assistant.io/integrations/yeelight", - "requirements": ["yeelight==0.7.8", "async-upnp-client==0.23.4"], - "codeowners": ["@zewelor", "@shenxn", "@starkillerOG"], + "requirements": ["yeelight==0.7.9", "async-upnp-client==0.23.4"], + "codeowners": ["@zewelor", "@shenxn", "@starkillerOG", "@alexyao2015"], "config_flow": true, "dependencies": ["network"], "quality_scale": "platinum", diff --git a/requirements_all.txt b/requirements_all.txt index ef3626b011a81b..d33b08c78ce60c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2516,7 +2516,7 @@ yalesmartalarmclient==0.3.7 yalexs==1.1.20 # homeassistant.components.yeelight -yeelight==0.7.8 +yeelight==0.7.9 # homeassistant.components.yeelightsunflower yeelightsunflower==0.0.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 73272640802737..e62662b7d790f6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1550,7 +1550,7 @@ yalesmartalarmclient==0.3.7 yalexs==1.1.20 # homeassistant.components.yeelight -yeelight==0.7.8 +yeelight==0.7.9 # homeassistant.components.youless youless-api==0.16 diff --git a/tests/components/yeelight/__init__.py b/tests/components/yeelight/__init__.py index b48cfc5402a89c..81972ca435261c 100644 --- a/tests/components/yeelight/__init__.py +++ b/tests/components/yeelight/__init__.py @@ -153,7 +153,7 @@ def _mocked_bulb(cannot_connect=False): bulb.async_set_power_mode = AsyncMock() bulb.async_set_scene = AsyncMock() bulb.async_set_default = AsyncMock() - bulb.start_music = MagicMock() + bulb.async_start_music = AsyncMock() return bulb diff --git a/tests/components/yeelight/test_light.py b/tests/components/yeelight/test_light.py index 059a47b53a1c4f..2e37daaf9dda3a 100644 --- a/tests/components/yeelight/test_light.py +++ b/tests/components/yeelight/test_light.py @@ -219,8 +219,8 @@ async def _async_test_service( power_mode=PowerMode.NORMAL, ) mocked_bulb.async_turn_on.reset_mock() - mocked_bulb.start_music.assert_called_once() - mocked_bulb.start_music.reset_mock() + mocked_bulb.async_start_music.assert_called_once() + mocked_bulb.async_start_music.reset_mock() mocked_bulb.async_set_brightness.assert_called_once_with( brightness / 255 * 100, duration=transition * 1000, light_type=LightType.Main ) @@ -261,8 +261,8 @@ async def _async_test_service( power_mode=PowerMode.NORMAL, ) mocked_bulb.async_turn_on.reset_mock() - mocked_bulb.start_music.assert_called_once() - mocked_bulb.start_music.reset_mock() + mocked_bulb.async_start_music.assert_called_once() + mocked_bulb.async_start_music.reset_mock() mocked_bulb.async_set_brightness.assert_called_once_with( brightness / 255 * 100, duration=transition * 1000, light_type=LightType.Main ) @@ -304,7 +304,7 @@ async def _async_test_service( power_mode=PowerMode.NORMAL, ) mocked_bulb.async_turn_on.reset_mock() - mocked_bulb.start_music.assert_called_once() + mocked_bulb.async_start_music.assert_called_once() mocked_bulb.async_set_brightness.assert_called_once_with( brightness / 255 * 100, duration=transition * 1000, light_type=LightType.Main ) @@ -322,7 +322,7 @@ async def _async_test_service( brightness = 100 color_temp = 200 transition = 1 - mocked_bulb.start_music.reset_mock() + mocked_bulb.async_start_music.reset_mock() mocked_bulb.async_set_brightness.reset_mock() mocked_bulb.async_set_color_temp.reset_mock() mocked_bulb.async_start_flow.reset_mock() @@ -348,7 +348,7 @@ async def _async_test_service( power_mode=PowerMode.NORMAL, ) mocked_bulb.async_turn_on.reset_mock() - mocked_bulb.start_music.assert_called_once() + mocked_bulb.async_start_music.assert_called_once() mocked_bulb.async_set_brightness.assert_called_once_with( brightness / 255 * 100, duration=transition * 1000, light_type=LightType.Main ) @@ -452,7 +452,7 @@ async def _async_test_service( ) # set_music_mode failure enable - mocked_bulb.start_music = MagicMock(side_effect=AssertionError) + mocked_bulb.async_start_music = MagicMock(side_effect=AssertionError) assert "Unable to turn on music mode, consider disabling it" not in caplog.text await hass.services.async_call( DOMAIN, @@ -460,14 +460,14 @@ async def _async_test_service( {ATTR_ENTITY_ID: ENTITY_LIGHT, ATTR_MODE_MUSIC: "true"}, blocking=True, ) - assert mocked_bulb.start_music.mock_calls == [call()] + assert mocked_bulb.async_start_music.mock_calls == [call()] assert "Unable to turn on music mode, consider disabling it" in caplog.text # set_music_mode disable await _async_test_service( SERVICE_SET_MUSIC_MODE, {ATTR_ENTITY_ID: ENTITY_LIGHT, ATTR_MODE_MUSIC: "false"}, - "stop_music", + "async_stop_music", failure_side_effect=None, ) @@ -475,7 +475,7 @@ async def _async_test_service( await _async_test_service( SERVICE_SET_MUSIC_MODE, {ATTR_ENTITY_ID: ENTITY_LIGHT, ATTR_MODE_MUSIC: "true"}, - "start_music", + "async_start_music", failure_side_effect=None, ) # test _cmd wrapper error handler From e7d725e810054ed3f772349842a8c0ff276938b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20=C3=98stergaard=20Nielsen?= Date: Sun, 6 Feb 2022 22:42:15 +0100 Subject: [PATCH 0359/1098] Ihc integration, move manual setup out of init.py (#65087) * Move manual setup out of init.py * Type hints on additional parameters * Complete missing typings --- homeassistant/components/ihc/__init__.py | 153 +++---------------- homeassistant/components/ihc/manual_setup.py | 142 +++++++++++++++++ 2 files changed, 160 insertions(+), 135 deletions(-) create mode 100644 homeassistant/components/ihc/manual_setup.py diff --git a/homeassistant/components/ihc/__init__.py b/homeassistant/components/ihc/__init__.py index 4c5c9d7249709c..9a3ae9f8d6c365 100644 --- a/homeassistant/components/ihc/__init__.py +++ b/homeassistant/components/ihc/__init__.py @@ -4,115 +4,20 @@ from ihcsdk.ihccontroller import IHCController import voluptuous as vol -from homeassistant.components.binary_sensor import DEVICE_CLASSES_SCHEMA -from homeassistant.const import ( - CONF_ID, - CONF_NAME, - CONF_PASSWORD, - CONF_TYPE, - CONF_UNIT_OF_MEASUREMENT, - CONF_URL, - CONF_USERNAME, - TEMP_CELSIUS, -) +from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME from homeassistant.core import HomeAssistant -from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType from .auto_setup import autosetup_ihc_products -from .const import ( - CONF_AUTOSETUP, - CONF_BINARY_SENSOR, - CONF_DIMMABLE, - CONF_INFO, - CONF_INVERTING, - CONF_LIGHT, - CONF_NOTE, - CONF_OFF_ID, - CONF_ON_ID, - CONF_POSITION, - CONF_SENSOR, - CONF_SWITCH, - DOMAIN, - IHC_CONTROLLER, - IHC_PLATFORMS, -) +from .const import CONF_AUTOSETUP, CONF_INFO, DOMAIN, IHC_CONTROLLER +from .manual_setup import IHC_SCHEMA, get_manual_configuration from .service_functions import setup_service_functions _LOGGER = logging.getLogger(__name__) IHC_INFO = "info" - -def validate_name(config): - """Validate the device name.""" - if CONF_NAME in config: - return config - ihcid = config[CONF_ID] - name = f"ihc_{ihcid}" - config[CONF_NAME] = name - return config - - -DEVICE_SCHEMA = vol.Schema( - { - vol.Required(CONF_ID): cv.positive_int, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_NOTE): cv.string, - vol.Optional(CONF_POSITION): cv.string, - } -) - - -SWITCH_SCHEMA = DEVICE_SCHEMA.extend( - { - vol.Optional(CONF_OFF_ID, default=0): cv.positive_int, - vol.Optional(CONF_ON_ID, default=0): cv.positive_int, - } -) - -BINARY_SENSOR_SCHEMA = DEVICE_SCHEMA.extend( - { - vol.Optional(CONF_INVERTING, default=False): cv.boolean, - vol.Optional(CONF_TYPE): DEVICE_CLASSES_SCHEMA, - } -) - -LIGHT_SCHEMA = DEVICE_SCHEMA.extend( - { - vol.Optional(CONF_DIMMABLE, default=False): cv.boolean, - vol.Optional(CONF_OFF_ID, default=0): cv.positive_int, - vol.Optional(CONF_ON_ID, default=0): cv.positive_int, - } -) - -SENSOR_SCHEMA = DEVICE_SCHEMA.extend( - {vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=TEMP_CELSIUS): cv.string} -) - -IHC_SCHEMA = vol.Schema( - { - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_URL): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_AUTOSETUP, default=True): cv.boolean, - vol.Optional(CONF_BINARY_SENSOR, default=[]): vol.All( - cv.ensure_list, [vol.All(BINARY_SENSOR_SCHEMA, validate_name)] - ), - vol.Optional(CONF_INFO, default=True): cv.boolean, - vol.Optional(CONF_LIGHT, default=[]): vol.All( - cv.ensure_list, [vol.All(LIGHT_SCHEMA, validate_name)] - ), - vol.Optional(CONF_SENSOR, default=[]): vol.All( - cv.ensure_list, [vol.All(SENSOR_SCHEMA, validate_name)] - ), - vol.Optional(CONF_SWITCH, default=[]): vol.All( - cv.ensure_list, [vol.All(SWITCH_SCHEMA, validate_name)] - ), - } -) - CONFIG_SCHEMA = vol.Schema( {DOMAIN: vol.Schema(vol.All(cv.ensure_list, [IHC_SCHEMA]))}, extra=vol.ALLOW_EXTRA ) @@ -128,57 +33,35 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: return True -def ihc_setup(hass, config, conf, controller_id): +def ihc_setup( + hass: HomeAssistant, + config: ConfigType, + controller_conf: ConfigType, + controller_id: int, +) -> bool: """Set up the IHC integration.""" - url = conf[CONF_URL] - username = conf[CONF_USERNAME] - password = conf[CONF_PASSWORD] + url = controller_conf[CONF_URL] + username = controller_conf[CONF_USERNAME] + password = controller_conf[CONF_PASSWORD] ihc_controller = IHCController(url, username, password) if not ihc_controller.authenticate(): _LOGGER.error("Unable to authenticate on IHC controller") return False - if conf[CONF_AUTOSETUP] and not autosetup_ihc_products( + if controller_conf[CONF_AUTOSETUP] and not autosetup_ihc_products( hass, config, ihc_controller, controller_id ): return False # Manual configuration - get_manual_configuration(hass, config, conf, ihc_controller, controller_id) + get_manual_configuration(hass, config, controller_conf, controller_id) # Store controller configuration ihc_key = f"ihc{controller_id}" - hass.data[ihc_key] = {IHC_CONTROLLER: ihc_controller, IHC_INFO: conf[CONF_INFO]} + hass.data[ihc_key] = { + IHC_CONTROLLER: ihc_controller, + IHC_INFO: controller_conf[CONF_INFO], + } # We only want to register the service functions once for the first controller if controller_id == 0: setup_service_functions(hass) return True - - -def get_manual_configuration(hass, config, conf, ihc_controller, controller_id): - """Get manual configuration for IHC devices.""" - for platform in IHC_PLATFORMS: - discovery_info = {} - if platform in conf: - platform_setup = conf.get(platform) - for sensor_cfg in platform_setup: - name = sensor_cfg[CONF_NAME] - device = { - "ihc_id": sensor_cfg[CONF_ID], - "ctrl_id": controller_id, - "product": { - "name": name, - "note": sensor_cfg.get(CONF_NOTE) or "", - "position": sensor_cfg.get(CONF_POSITION) or "", - }, - "product_cfg": { - "type": sensor_cfg.get(CONF_TYPE), - "inverting": sensor_cfg.get(CONF_INVERTING), - "off_id": sensor_cfg.get(CONF_OFF_ID), - "on_id": sensor_cfg.get(CONF_ON_ID), - "dimmable": sensor_cfg.get(CONF_DIMMABLE), - "unit_of_measurement": sensor_cfg.get(CONF_UNIT_OF_MEASUREMENT), - }, - } - discovery_info[name] = device - if discovery_info: - discovery.load_platform(hass, platform, DOMAIN, discovery_info, config) diff --git a/homeassistant/components/ihc/manual_setup.py b/homeassistant/components/ihc/manual_setup.py new file mode 100644 index 00000000000000..a68230c9900e34 --- /dev/null +++ b/homeassistant/components/ihc/manual_setup.py @@ -0,0 +1,142 @@ +"""Handle manual setup of ihc resources as entities in Home Assistant.""" +import logging + +import voluptuous as vol + +from homeassistant.components.binary_sensor import DEVICE_CLASSES_SCHEMA +from homeassistant.const import ( + CONF_ID, + CONF_NAME, + CONF_PASSWORD, + CONF_TYPE, + CONF_UNIT_OF_MEASUREMENT, + CONF_URL, + CONF_USERNAME, + TEMP_CELSIUS, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers import discovery +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.typing import ConfigType + +from .const import ( + CONF_AUTOSETUP, + CONF_BINARY_SENSOR, + CONF_DIMMABLE, + CONF_INFO, + CONF_INVERTING, + CONF_LIGHT, + CONF_NOTE, + CONF_OFF_ID, + CONF_ON_ID, + CONF_POSITION, + CONF_SENSOR, + CONF_SWITCH, + DOMAIN, + IHC_PLATFORMS, +) + +_LOGGER = logging.getLogger(__name__) + + +def validate_name(config): + """Validate the device name.""" + if CONF_NAME in config: + return config + ihcid = config[CONF_ID] + name = f"ihc_{ihcid}" + config[CONF_NAME] = name + return config + + +DEVICE_SCHEMA = vol.Schema( + { + vol.Required(CONF_ID): cv.positive_int, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NOTE): cv.string, + vol.Optional(CONF_POSITION): cv.string, + } +) + +SWITCH_SCHEMA = DEVICE_SCHEMA.extend( + { + vol.Optional(CONF_OFF_ID, default=0): cv.positive_int, + vol.Optional(CONF_ON_ID, default=0): cv.positive_int, + } +) + +BINARY_SENSOR_SCHEMA = DEVICE_SCHEMA.extend( + { + vol.Optional(CONF_INVERTING, default=False): cv.boolean, + vol.Optional(CONF_TYPE): DEVICE_CLASSES_SCHEMA, + } +) + +LIGHT_SCHEMA = DEVICE_SCHEMA.extend( + { + vol.Optional(CONF_DIMMABLE, default=False): cv.boolean, + vol.Optional(CONF_OFF_ID, default=0): cv.positive_int, + vol.Optional(CONF_ON_ID, default=0): cv.positive_int, + } +) + +SENSOR_SCHEMA = DEVICE_SCHEMA.extend( + {vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=TEMP_CELSIUS): cv.string} +) + +IHC_SCHEMA = vol.Schema( + { + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_URL): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_AUTOSETUP, default=True): cv.boolean, + vol.Optional(CONF_BINARY_SENSOR, default=[]): vol.All( + cv.ensure_list, [vol.All(BINARY_SENSOR_SCHEMA, validate_name)] + ), + vol.Optional(CONF_INFO, default=True): cv.boolean, + vol.Optional(CONF_LIGHT, default=[]): vol.All( + cv.ensure_list, [vol.All(LIGHT_SCHEMA, validate_name)] + ), + vol.Optional(CONF_SENSOR, default=[]): vol.All( + cv.ensure_list, [vol.All(SENSOR_SCHEMA, validate_name)] + ), + vol.Optional(CONF_SWITCH, default=[]): vol.All( + cv.ensure_list, [vol.All(SWITCH_SCHEMA, validate_name)] + ), + } +) + + +def get_manual_configuration( + hass: HomeAssistant, + config: ConfigType, + controller_conf: ConfigType, + controller_id: int, +) -> None: + """Get manual configuration for IHC devices.""" + for platform in IHC_PLATFORMS: + discovery_info = {} + if platform in controller_conf: + platform_setup = controller_conf.get(platform, {}) + for sensor_cfg in platform_setup: + name = sensor_cfg[CONF_NAME] + device = { + "ihc_id": sensor_cfg[CONF_ID], + "ctrl_id": controller_id, + "product": { + "name": name, + "note": sensor_cfg.get(CONF_NOTE) or "", + "position": sensor_cfg.get(CONF_POSITION) or "", + }, + "product_cfg": { + "type": sensor_cfg.get(CONF_TYPE), + "inverting": sensor_cfg.get(CONF_INVERTING), + "off_id": sensor_cfg.get(CONF_OFF_ID), + "on_id": sensor_cfg.get(CONF_ON_ID), + "dimmable": sensor_cfg.get(CONF_DIMMABLE), + "unit_of_measurement": sensor_cfg.get(CONF_UNIT_OF_MEASUREMENT), + }, + } + discovery_info[name] = device + if discovery_info: + discovery.load_platform(hass, platform, DOMAIN, discovery_info, config) From 62da194340c27c97b2605933fb6f8428329e226f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 6 Feb 2022 16:02:07 -0600 Subject: [PATCH 0360/1098] Add diagnostics support to HomeKit (#65942) * Add diagnostics support to HomeKit * remove debug --- .../components/homekit/diagnostics.py | 44 +++++++ tests/components/homekit/test_diagnostics.py | 119 ++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 homeassistant/components/homekit/diagnostics.py create mode 100644 tests/components/homekit/test_diagnostics.py diff --git a/homeassistant/components/homekit/diagnostics.py b/homeassistant/components/homekit/diagnostics.py new file mode 100644 index 00000000000000..2a54c1ef543ead --- /dev/null +++ b/homeassistant/components/homekit/diagnostics.py @@ -0,0 +1,44 @@ +"""Diagnostics support for HomeKit.""" +from __future__ import annotations + +from typing import Any + +from pyhap.accessory_driver import AccessoryDriver +from pyhap.state import State + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from . import HomeKit +from .const import DOMAIN, HOMEKIT + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + homekit: HomeKit = hass.data[DOMAIN][entry.entry_id][HOMEKIT] + driver: AccessoryDriver = homekit.driver + data: dict[str, Any] = { + "status": homekit.status, + "config-entry": { + "title": entry.title, + "version": entry.version, + "data": dict(entry.data), + "options": dict(entry.options), + }, + } + if not driver: + return data + data.update(driver.get_accessories()) + state: State = driver.state + data.update( + { + "client_properties": { + str(client): props for client, props in state.client_properties.items() + }, + "config_version": state.config_version, + "pairing_id": state.mac, + } + ) + return data diff --git a/tests/components/homekit/test_diagnostics.py b/tests/components/homekit/test_diagnostics.py new file mode 100644 index 00000000000000..e3a85b85972f97 --- /dev/null +++ b/tests/components/homekit/test_diagnostics.py @@ -0,0 +1,119 @@ +"""Test homekit diagnostics.""" +from unittest.mock import ANY, patch + +from homeassistant.components.homekit.const import DOMAIN +from homeassistant.const import CONF_NAME, CONF_PORT, EVENT_HOMEASSISTANT_STARTED + +from .util import async_init_integration + +from tests.common import MockConfigEntry +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_config_entry_not_running( + hass, hass_client, hk_driver, mock_async_zeroconf +): + """Test generating diagnostics for a config entry.""" + entry = await async_init_integration(hass) + diag = await get_diagnostics_for_config_entry(hass, hass_client, entry) + assert diag == { + "config-entry": { + "data": {"name": "mock_name", "port": 12345}, + "options": {}, + "title": "Mock Title", + "version": 1, + }, + "status": 0, + } + + +async def test_config_entry_running(hass, hass_client, hk_driver, mock_async_zeroconf): + """Test generating diagnostics for a config entry.""" + entry = MockConfigEntry( + domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345} + ) + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + diag = await get_diagnostics_for_config_entry(hass, hass_client, entry) + assert diag == { + "accessories": [ + { + "aid": 1, + "services": [ + { + "characteristics": [ + {"format": "bool", "iid": 2, "perms": ["pw"], "type": "14"}, + { + "format": "string", + "iid": 3, + "perms": ["pr"], + "type": "20", + "value": "Home Assistant", + }, + { + "format": "string", + "iid": 4, + "perms": ["pr"], + "type": "21", + "value": "Bridge", + }, + { + "format": "string", + "iid": 5, + "perms": ["pr"], + "type": "23", + "value": "mock_name", + }, + { + "format": "string", + "iid": 6, + "perms": ["pr"], + "type": "30", + "value": "homekit.bridge", + }, + { + "format": "string", + "iid": 7, + "perms": ["pr"], + "type": "52", + "value": ANY, + }, + ], + "iid": 1, + "type": "3E", + }, + { + "characteristics": [ + { + "format": "string", + "iid": 9, + "perms": ["pr", "ev"], + "type": "37", + "value": "01.01.00", + } + ], + "iid": 8, + "type": "A2", + }, + ], + } + ], + "client_properties": {}, + "config-entry": { + "data": {"name": "mock_name", "port": 12345}, + "options": {}, + "title": "Mock Title", + "version": 1, + }, + "config_version": 2, + "pairing_id": ANY, + "status": 1, + } + + with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( + "homeassistant.components.homekit.HomeKit.async_stop" + ), patch("homeassistant.components.homekit.async_port_is_available"): + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() From 275d4b9770497a07711bc11bf439ff3c460b7ea5 Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sun, 6 Feb 2022 23:02:31 +0100 Subject: [PATCH 0361/1098] Improve device shutdown and unload of Synology DSM integration (#65936) * ignore errors during unload/logout * automatic host update is an info, nut debug --- homeassistant/components/synology_dsm/common.py | 7 ++++++- homeassistant/components/synology_dsm/config_flow.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/synology_dsm/common.py b/homeassistant/components/synology_dsm/common.py index 273c9cc6a4213f..54a0735186fc96 100644 --- a/homeassistant/components/synology_dsm/common.py +++ b/homeassistant/components/synology_dsm/common.py @@ -16,6 +16,7 @@ from synology_dsm.api.surveillance_station import SynoSurveillanceStation from synology_dsm.exceptions import ( SynologyDSMAPIErrorException, + SynologyDSMException, SynologyDSMLoginFailedException, SynologyDSMRequestException, ) @@ -237,7 +238,11 @@ async def async_shutdown(self) -> None: async def async_unload(self) -> None: """Stop interacting with the NAS and prepare for removal from hass.""" - await self._syno_api_executer(self.dsm.logout) + try: + await self._syno_api_executer(self.dsm.logout) + except SynologyDSMException: + # ignore API errors during logout + pass async def async_update(self, now: timedelta | None = None) -> None: """Update function for updating API information.""" diff --git a/homeassistant/components/synology_dsm/config_flow.py b/homeassistant/components/synology_dsm/config_flow.py index 91ad49c5f84698..256ad5eef8ed75 100644 --- a/homeassistant/components/synology_dsm/config_flow.py +++ b/homeassistant/components/synology_dsm/config_flow.py @@ -267,7 +267,7 @@ async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowRes and existing_entry.data[CONF_HOST] != parsed_url.hostname and not fqdn_with_ssl_verification ): - _LOGGER.debug( + _LOGGER.info( "Update host from '%s' to '%s' for NAS '%s' via SSDP discovery", existing_entry.data[CONF_HOST], parsed_url.hostname, From b1dcf7e0d8a7babcc691b326daf7d4b2d55c446a Mon Sep 17 00:00:00 2001 From: Milan Meulemans Date: Sun, 6 Feb 2022 23:11:52 +0100 Subject: [PATCH 0362/1098] Add DataUpdateCoordinator to Nanoleaf (#65950) --- homeassistant/components/nanoleaf/__init__.py | 44 +++++++++++------ homeassistant/components/nanoleaf/button.py | 9 ++-- homeassistant/components/nanoleaf/entity.py | 11 +++-- homeassistant/components/nanoleaf/light.py | 47 +++---------------- 4 files changed, 50 insertions(+), 61 deletions(-) diff --git a/homeassistant/components/nanoleaf/__init__.py b/homeassistant/components/nanoleaf/__init__.py index 4560f340416595..677c0d4cc2a081 100644 --- a/homeassistant/components/nanoleaf/__init__.py +++ b/homeassistant/components/nanoleaf/__init__.py @@ -3,15 +3,17 @@ import asyncio from dataclasses import dataclass +from datetime import timedelta +import logging from aionanoleaf import EffectsEvent, InvalidToken, Nanoleaf, StateEvent, Unavailable from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_TOKEN, Platform from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady +from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import DOMAIN @@ -23,6 +25,7 @@ class NanoleafEntryData: """Class for sharing data within the Nanoleaf integration.""" device: Nanoleaf + coordinator: DataUpdateCoordinator event_listener: asyncio.Task @@ -31,26 +34,39 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: nanoleaf = Nanoleaf( async_get_clientsession(hass), entry.data[CONF_HOST], entry.data[CONF_TOKEN] ) - try: - await nanoleaf.get_info() - except Unavailable as err: - raise ConfigEntryNotReady from err - except InvalidToken as err: - raise ConfigEntryAuthFailed from err - - async def _callback_update_light_state(event: StateEvent | EffectsEvent) -> None: + + async def async_get_state() -> None: + """Get the state of the device.""" + try: + await nanoleaf.get_info() + except Unavailable as err: + raise UpdateFailed from err + except InvalidToken as err: + raise ConfigEntryAuthFailed from err + + coordinator = DataUpdateCoordinator( + hass, + logging.getLogger(__name__), + name=entry.title, + update_interval=timedelta(minutes=1), + update_method=async_get_state, + ) + + await coordinator.async_config_entry_first_refresh() + + async def update_light_state_callback(event: StateEvent | EffectsEvent) -> None: """Receive state and effect event.""" - async_dispatcher_send(hass, f"{DOMAIN}_update_light_{nanoleaf.serial_no}") + coordinator.async_set_updated_data(None) event_listener = asyncio.create_task( nanoleaf.listen_events( - state_callback=_callback_update_light_state, - effects_callback=_callback_update_light_state, + state_callback=update_light_state_callback, + effects_callback=update_light_state_callback, ) ) hass.data.setdefault(DOMAIN, {})[entry.entry_id] = NanoleafEntryData( - nanoleaf, event_listener + nanoleaf, coordinator, event_listener ) hass.config_entries.async_setup_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/nanoleaf/button.py b/homeassistant/components/nanoleaf/button.py index 2d9388196c17a0..5297e98c88ef31 100644 --- a/homeassistant/components/nanoleaf/button.py +++ b/homeassistant/components/nanoleaf/button.py @@ -7,6 +7,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from . import NanoleafEntryData from .const import DOMAIN @@ -18,15 +19,17 @@ async def async_setup_entry( ) -> None: """Set up the Nanoleaf button.""" entry_data: NanoleafEntryData = hass.data[DOMAIN][entry.entry_id] - async_add_entities([NanoleafIdentifyButton(entry_data.device)]) + async_add_entities( + [NanoleafIdentifyButton(entry_data.device, entry_data.coordinator)] + ) class NanoleafIdentifyButton(NanoleafEntity, ButtonEntity): """Representation of a Nanoleaf identify button.""" - def __init__(self, nanoleaf: Nanoleaf) -> None: + def __init__(self, nanoleaf: Nanoleaf, coordinator: DataUpdateCoordinator) -> None: """Initialize the Nanoleaf button.""" - super().__init__(nanoleaf) + super().__init__(nanoleaf, coordinator) self._attr_unique_id = f"{nanoleaf.serial_no}_identify" self._attr_name = f"Identify {nanoleaf.name}" self._attr_icon = "mdi:magnify" diff --git a/homeassistant/components/nanoleaf/entity.py b/homeassistant/components/nanoleaf/entity.py index c6efed9178787e..8df9e243d712b1 100644 --- a/homeassistant/components/nanoleaf/entity.py +++ b/homeassistant/components/nanoleaf/entity.py @@ -2,16 +2,21 @@ from aionanoleaf import Nanoleaf -from homeassistant.helpers.entity import DeviceInfo, Entity +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, +) from .const import DOMAIN -class NanoleafEntity(Entity): +class NanoleafEntity(CoordinatorEntity): """Representation of a Nanoleaf entity.""" - def __init__(self, nanoleaf: Nanoleaf) -> None: + def __init__(self, nanoleaf: Nanoleaf, coordinator: DataUpdateCoordinator) -> None: """Initialize an Nanoleaf entity.""" + super().__init__(coordinator) self._nanoleaf = nanoleaf self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, nanoleaf.serial_no)}, diff --git a/homeassistant/components/nanoleaf/light.py b/homeassistant/components/nanoleaf/light.py index b4eb89ac8c75cf..29c7cb786e6e58 100644 --- a/homeassistant/components/nanoleaf/light.py +++ b/homeassistant/components/nanoleaf/light.py @@ -1,12 +1,11 @@ """Support for Nanoleaf Lights.""" from __future__ import annotations -from datetime import timedelta import logging import math from typing import Any -from aionanoleaf import Nanoleaf, Unavailable +from aionanoleaf import Nanoleaf import voluptuous as vol from homeassistant.components.light import ( @@ -25,11 +24,11 @@ ) from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.util.color import ( color_temperature_kelvin_to_mired as kelvin_to_mired, color_temperature_mired_to_kelvin as mired_to_kelvin, @@ -52,8 +51,6 @@ _LOGGER = logging.getLogger(__name__) -SCAN_INTERVAL = timedelta(minutes=5) - async def async_setup_platform( hass: HomeAssistant, @@ -82,15 +79,15 @@ async def async_setup_entry( ) -> None: """Set up the Nanoleaf light.""" entry_data: NanoleafEntryData = hass.data[DOMAIN][entry.entry_id] - async_add_entities([NanoleafLight(entry_data.device)]) + async_add_entities([NanoleafLight(entry_data.device, entry_data.coordinator)]) class NanoleafLight(NanoleafEntity, LightEntity): """Representation of a Nanoleaf Light.""" - def __init__(self, nanoleaf: Nanoleaf) -> None: + def __init__(self, nanoleaf: Nanoleaf, coordinator: DataUpdateCoordinator) -> None: """Initialize the Nanoleaf light.""" - super().__init__(nanoleaf) + super().__init__(nanoleaf, coordinator) self._attr_unique_id = nanoleaf.serial_no self._attr_name = nanoleaf.name self._attr_min_mireds = math.ceil(1000000 / nanoleaf.color_temperature_max) @@ -186,35 +183,3 @@ async def async_turn_off(self, **kwargs: Any) -> None: """Instruct the light to turn off.""" transition: float | None = kwargs.get(ATTR_TRANSITION) await self._nanoleaf.turn_off(None if transition is None else int(transition)) - - async def async_update(self) -> None: - """Fetch new state data for this light.""" - try: - await self._nanoleaf.get_info() - except Unavailable: - if self.available: - _LOGGER.warning("Could not connect to %s", self.name) - self._attr_available = False - return - if not self.available: - _LOGGER.info("Fetching %s data recovered", self.name) - self._attr_available = True - - @callback - def async_handle_update(self) -> None: - """Handle state update.""" - self.async_write_ha_state() - if not self.available: - _LOGGER.info("Connection to %s recovered", self.name) - self._attr_available = True - - async def async_added_to_hass(self) -> None: - """Handle entity being added to Home Assistant.""" - await super().async_added_to_hass() - self.async_on_remove( - async_dispatcher_connect( - self.hass, - f"{DOMAIN}_update_light_{self._nanoleaf.serial_no}", - self.async_handle_update, - ) - ) From 41f602c3df0494558170eed6b0fed1d48ea20da9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 6 Feb 2022 16:12:30 -0600 Subject: [PATCH 0363/1098] Fix loss of ability to control white channel in HomeKit on RGB&W lights (#65864) * Fix loss of ability to control white channel in HomeKit on RGB&W lights - Fix white channel missing from RGB/W lights - Fix temp missing from RGB/CW lights - Fixes #65529 * cover the missing case * bright fix * force brightness notify on color mode change as well --- .../components/homekit/type_lights.py | 156 +++--- tests/components/homekit/test_type_lights.py | 460 ++++++++++++++++-- 2 files changed, 523 insertions(+), 93 deletions(-) diff --git a/homeassistant/components/homekit/type_lights.py b/homeassistant/components/homekit/type_lights.py index cdff3105ec3fdb..081f6f1bdd4ac3 100644 --- a/homeassistant/components/homekit/type_lights.py +++ b/homeassistant/components/homekit/type_lights.py @@ -1,4 +1,6 @@ """Class to hold all light accessories.""" +from __future__ import annotations + import logging import math @@ -12,12 +14,13 @@ ATTR_HS_COLOR, ATTR_MAX_MIREDS, ATTR_MIN_MIREDS, - ATTR_RGB_COLOR, ATTR_RGBW_COLOR, ATTR_RGBWW_COLOR, ATTR_SUPPORTED_COLOR_MODES, + ATTR_WHITE, COLOR_MODE_RGBW, COLOR_MODE_RGBWW, + COLOR_MODE_WHITE, DOMAIN, brightness_supported, color_supported, @@ -32,9 +35,9 @@ from homeassistant.core import callback from homeassistant.helpers.event import async_call_later from homeassistant.util.color import ( - color_hsv_to_RGB, color_temperature_mired_to_kelvin, color_temperature_to_hs, + color_temperature_to_rgbww, ) from .accessories import TYPES, HomeAccessory @@ -51,12 +54,13 @@ _LOGGER = logging.getLogger(__name__) -RGB_COLOR = "rgb_color" CHANGE_COALESCE_TIME_WINDOW = 0.01 +DEFAULT_MIN_MIREDS = 153 +DEFAULT_MAX_MIREDS = 500 -COLOR_MODES_WITH_WHITES = {COLOR_MODE_RGBW, COLOR_MODE_RGBWW} +COLOR_MODES_WITH_WHITES = {COLOR_MODE_RGBW, COLOR_MODE_RGBWW, COLOR_MODE_WHITE} @TYPES.register("Light") @@ -79,8 +83,12 @@ def __init__(self, *args): self.color_modes = color_modes = ( attributes.get(ATTR_SUPPORTED_COLOR_MODES) or [] ) + self._previous_color_mode = attributes.get(ATTR_COLOR_MODE) self.color_supported = color_supported(color_modes) self.color_temp_supported = color_temp_supported(color_modes) + self.rgbw_supported = COLOR_MODE_RGBW in color_modes + self.rgbww_supported = COLOR_MODE_RGBWW in color_modes + self.white_supported = COLOR_MODE_WHITE in color_modes self.brightness_supported = brightness_supported(color_modes) if self.brightness_supported: @@ -89,7 +97,9 @@ def __init__(self, *args): if self.color_supported: self.chars.extend([CHAR_HUE, CHAR_SATURATION]) - if self.color_temp_supported: + if self.color_temp_supported or COLOR_MODES_WITH_WHITES.intersection( + self.color_modes + ): self.chars.append(CHAR_COLOR_TEMPERATURE) serv_light = self.add_preload_service(SERV_LIGHTBULB, self.chars) @@ -101,13 +111,22 @@ def __init__(self, *args): # to set to the correct initial value. self.char_brightness = serv_light.configure_char(CHAR_BRIGHTNESS, value=100) - if self.color_temp_supported: - min_mireds = math.floor(attributes.get(ATTR_MIN_MIREDS, 153)) - max_mireds = math.ceil(attributes.get(ATTR_MAX_MIREDS, 500)) + if CHAR_COLOR_TEMPERATURE in self.chars: + self.min_mireds = math.floor( + attributes.get(ATTR_MIN_MIREDS, DEFAULT_MIN_MIREDS) + ) + self.max_mireds = math.ceil( + attributes.get(ATTR_MAX_MIREDS, DEFAULT_MAX_MIREDS) + ) + if not self.color_temp_supported and not self.rgbww_supported: + self.max_mireds = self.min_mireds self.char_color_temp = serv_light.configure_char( CHAR_COLOR_TEMPERATURE, - value=min_mireds, - properties={PROP_MIN_VALUE: min_mireds, PROP_MAX_VALUE: max_mireds}, + value=self.min_mireds, + properties={ + PROP_MIN_VALUE: self.min_mireds, + PROP_MAX_VALUE: self.max_mireds, + }, ) if self.color_supported: @@ -165,33 +184,32 @@ def _async_send_events(self, *_): ) return + # Handle white channels if CHAR_COLOR_TEMPERATURE in char_values: - params[ATTR_COLOR_TEMP] = char_values[CHAR_COLOR_TEMPERATURE] - events.append(f"color temperature at {params[ATTR_COLOR_TEMP]}") - - elif ( - CHAR_HUE in char_values - or CHAR_SATURATION in char_values - # If we are adjusting brightness we need to send the full RGBW/RGBWW values - # since HomeKit does not support RGBW/RGBWW - or brightness_pct - and COLOR_MODES_WITH_WHITES.intersection(self.color_modes) - ): + temp = char_values[CHAR_COLOR_TEMPERATURE] + events.append(f"color temperature at {temp}") + bright_val = round( + ((brightness_pct or self.char_brightness.value) * 255) / 100 + ) + if self.color_temp_supported: + params[ATTR_COLOR_TEMP] = temp + elif self.rgbww_supported: + params[ATTR_RGBWW_COLOR] = color_temperature_to_rgbww( + temp, bright_val, self.min_mireds, self.max_mireds + ) + elif self.rgbw_supported: + params[ATTR_RGBW_COLOR] = (*(0,) * 3, bright_val) + elif self.white_supported: + params[ATTR_WHITE] = bright_val + + elif CHAR_HUE in char_values or CHAR_SATURATION in char_values: hue_sat = ( char_values.get(CHAR_HUE, self.char_hue.value), char_values.get(CHAR_SATURATION, self.char_saturation.value), ) _LOGGER.debug("%s: Set hs_color to %s", self.entity_id, hue_sat) events.append(f"set color at {hue_sat}") - # HomeKit doesn't support RGBW/RGBWW so we need to remove any white values - if COLOR_MODE_RGBWW in self.color_modes: - val = brightness_pct or self.char_brightness.value - params[ATTR_RGBWW_COLOR] = (*color_hsv_to_RGB(*hue_sat, val), 0, 0) - elif COLOR_MODE_RGBW in self.color_modes: - val = brightness_pct or self.char_brightness.value - params[ATTR_RGBW_COLOR] = (*color_hsv_to_RGB(*hue_sat, val), 0) - else: - params[ATTR_HS_COLOR] = hue_sat + params[ATTR_HS_COLOR] = hue_sat if ( brightness_pct @@ -200,6 +218,9 @@ def _async_send_events(self, *_): ): params[ATTR_BRIGHTNESS_PCT] = brightness_pct + _LOGGER.debug( + "Calling light service with params: %s -> %s", char_values, params + ) self.async_call_service(DOMAIN, service, params, ", ".join(events)) @callback @@ -210,52 +231,59 @@ def async_update_state(self, new_state): attributes = new_state.attributes color_mode = attributes.get(ATTR_COLOR_MODE) self.char_on.set_value(int(state == STATE_ON)) + color_mode_changed = self._previous_color_mode != color_mode + self._previous_color_mode = color_mode # Handle Brightness - if self.brightness_supported: - if ( - color_mode - and COLOR_MODES_WITH_WHITES.intersection({color_mode}) - and (rgb_color := attributes.get(ATTR_RGB_COLOR)) - ): - # HomeKit doesn't support RGBW/RGBWW so we need to - # give it the color brightness only - brightness = max(rgb_color) - else: - brightness = attributes.get(ATTR_BRIGHTNESS) - if isinstance(brightness, (int, float)): - brightness = round(brightness / 255 * 100, 0) - # The homeassistant component might report its brightness as 0 but is - # not off. But 0 is a special value in homekit. When you turn on a - # homekit accessory it will try to restore the last brightness state - # which will be the last value saved by char_brightness.set_value. - # But if it is set to 0, HomeKit will update the brightness to 100 as - # it thinks 0 is off. - # - # Therefore, if the the brightness is 0 and the device is still on, - # the brightness is mapped to 1 otherwise the update is ignored in - # order to avoid this incorrect behavior. - if brightness == 0 and state == STATE_ON: - brightness = 1 - self.char_brightness.set_value(brightness) + if ( + self.brightness_supported + and (brightness := attributes.get(ATTR_BRIGHTNESS)) is not None + and isinstance(brightness, (int, float)) + ): + brightness = round(brightness / 255 * 100, 0) + # The homeassistant component might report its brightness as 0 but is + # not off. But 0 is a special value in homekit. When you turn on a + # homekit accessory it will try to restore the last brightness state + # which will be the last value saved by char_brightness.set_value. + # But if it is set to 0, HomeKit will update the brightness to 100 as + # it thinks 0 is off. + # + # Therefore, if the the brightness is 0 and the device is still on, + # the brightness is mapped to 1 otherwise the update is ignored in + # order to avoid this incorrect behavior. + if brightness == 0 and state == STATE_ON: + brightness = 1 + self.char_brightness.set_value(brightness) + if color_mode_changed: + self.char_brightness.notify() # Handle Color - color must always be set before color temperature # or the iOS UI will not display it correctly. if self.color_supported: - if ATTR_COLOR_TEMP in attributes: + if color_temp := attributes.get(ATTR_COLOR_TEMP): hue, saturation = color_temperature_to_hs( - color_temperature_mired_to_kelvin( - new_state.attributes[ATTR_COLOR_TEMP] - ) + color_temperature_mired_to_kelvin(color_temp) ) + elif color_mode == COLOR_MODE_WHITE: + hue, saturation = 0, 0 else: hue, saturation = attributes.get(ATTR_HS_COLOR, (None, None)) if isinstance(hue, (int, float)) and isinstance(saturation, (int, float)): self.char_hue.set_value(round(hue, 0)) self.char_saturation.set_value(round(saturation, 0)) - - # Handle color temperature - if self.color_temp_supported: - color_temp = attributes.get(ATTR_COLOR_TEMP) + if color_mode_changed: + # If the color temp changed, be sure to force the color to update + self.char_hue.notify() + self.char_saturation.notify() + + # Handle white channels + if CHAR_COLOR_TEMPERATURE in self.chars: + color_temp = None + if self.color_temp_supported: + color_temp = attributes.get(ATTR_COLOR_TEMP) + elif color_mode == COLOR_MODE_WHITE: + color_temp = self.min_mireds if isinstance(color_temp, (int, float)): self.char_color_temp.set_value(round(color_temp, 0)) + if color_mode_changed: + self.char_color_temp.notify() diff --git a/tests/components/homekit/test_type_lights.py b/tests/components/homekit/test_type_lights.py index 8e7b60b0a47f16..6835629be3746b 100644 --- a/tests/components/homekit/test_type_lights.py +++ b/tests/components/homekit/test_type_lights.py @@ -5,7 +5,11 @@ from pyhap.const import HAP_REPR_AID, HAP_REPR_CHARS, HAP_REPR_IID, HAP_REPR_VALUE import pytest -from homeassistant.components.homekit.const import ATTR_VALUE +from homeassistant.components.homekit.const import ( + ATTR_VALUE, + PROP_MAX_VALUE, + PROP_MIN_VALUE, +) from homeassistant.components.homekit.type_lights import ( CHANGE_COALESCE_TIME_WINDOW, Light, @@ -22,9 +26,12 @@ ATTR_RGBW_COLOR, ATTR_RGBWW_COLOR, ATTR_SUPPORTED_COLOR_MODES, + ATTR_WHITE, COLOR_MODE_COLOR_TEMP, + COLOR_MODE_RGB, COLOR_MODE_RGBW, COLOR_MODE_RGBWW, + COLOR_MODE_WHITE, DOMAIN, ) from homeassistant.const import ( @@ -573,7 +580,7 @@ async def test_light_restore(hass, hk_driver, events): @pytest.mark.parametrize( - "supported_color_modes, state_props, turn_on_props, turn_on_props_with_brightness", + "supported_color_modes, state_props, turn_on_props_with_brightness", [ [ [COLOR_MODE_COLOR_TEMP, COLOR_MODE_RGBW], @@ -584,8 +591,7 @@ async def test_light_restore(hass, hk_driver, events): ATTR_BRIGHTNESS: 255, ATTR_COLOR_MODE: COLOR_MODE_RGBW, }, - {ATTR_RGBW_COLOR: (31, 127, 71, 0)}, - {ATTR_RGBW_COLOR: (15, 63, 35, 0)}, + {ATTR_HS_COLOR: (145, 75), ATTR_BRIGHTNESS_PCT: 25}, ], [ [COLOR_MODE_COLOR_TEMP, COLOR_MODE_RGBWW], @@ -596,21 +602,19 @@ async def test_light_restore(hass, hk_driver, events): ATTR_BRIGHTNESS: 255, ATTR_COLOR_MODE: COLOR_MODE_RGBWW, }, - {ATTR_RGBWW_COLOR: (31, 127, 71, 0, 0)}, - {ATTR_RGBWW_COLOR: (15, 63, 35, 0, 0)}, + {ATTR_HS_COLOR: (145, 75), ATTR_BRIGHTNESS_PCT: 25}, ], ], ) -async def test_light_rgb_with_white( +async def test_light_rgb_with_color_temp( hass, hk_driver, events, supported_color_modes, state_props, - turn_on_props, turn_on_props_with_brightness, ): - """Test lights with RGBW/RGBWW.""" + """Test lights with RGBW/RGBWW with color temp support.""" entity_id = "light.demo" hass.states.async_set( @@ -629,7 +633,7 @@ async def test_light_rgb_with_white( await hass.async_block_till_done() assert acc.char_hue.value == 23 assert acc.char_saturation.value == 100 - assert acc.char_brightness.value == 50 + assert acc.char_brightness.value == 100 # Set from HomeKit call_turn_on = async_mock_service(hass, DOMAIN, "turn_on") @@ -658,11 +662,10 @@ async def test_light_rgb_with_white( await _wait_for_light_coalesce(hass) assert call_turn_on assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id - for k, v in turn_on_props.items(): - assert call_turn_on[-1].data[k] == v + assert call_turn_on[-1].data[ATTR_HS_COLOR] == (145, 75) assert len(events) == 1 assert events[-1].data[ATTR_VALUE] == "set color at (145, 75)" - assert acc.char_brightness.value == 50 + assert acc.char_brightness.value == 100 hk_driver.set_characteristics( { @@ -697,7 +700,204 @@ async def test_light_rgb_with_white( @pytest.mark.parametrize( - "supported_color_modes, state_props, turn_on_props, turn_on_props_with_brightness", + "supported_color_modes, state_props, turn_on_props_with_brightness", + [ + [ + [COLOR_MODE_RGBW], + { + ATTR_RGBW_COLOR: (128, 50, 0, 255), + ATTR_RGB_COLOR: (128, 50, 0), + ATTR_HS_COLOR: (23.438, 100.0), + ATTR_BRIGHTNESS: 255, + ATTR_COLOR_MODE: COLOR_MODE_RGBW, + }, + {ATTR_RGBW_COLOR: (0, 0, 0, 191)}, + ], + [ + [COLOR_MODE_RGBWW], + { + ATTR_RGBWW_COLOR: (128, 50, 0, 255, 255), + ATTR_RGB_COLOR: (128, 50, 0), + ATTR_HS_COLOR: (23.438, 100.0), + ATTR_BRIGHTNESS: 255, + ATTR_COLOR_MODE: COLOR_MODE_RGBWW, + }, + {ATTR_RGBWW_COLOR: (0, 0, 0, 165, 26)}, + ], + ], +) +async def test_light_rgbwx_with_color_temp_and_brightness( + hass, + hk_driver, + events, + supported_color_modes, + state_props, + turn_on_props_with_brightness, +): + """Test lights with RGBW/RGBWW with color temp support and setting brightness.""" + entity_id = "light.demo" + + hass.states.async_set( + entity_id, + STATE_ON, + {ATTR_SUPPORTED_COLOR_MODES: supported_color_modes, **state_props}, + ) + await hass.async_block_till_done() + acc = Light(hass, hk_driver, "Light", entity_id, 1, None) + hk_driver.add_accessory(acc) + + assert acc.char_hue.value == 23 + assert acc.char_saturation.value == 100 + + await acc.run() + await hass.async_block_till_done() + assert acc.char_hue.value == 23 + assert acc.char_saturation.value == 100 + assert acc.char_brightness.value == 100 + + # Set from HomeKit + call_turn_on = async_mock_service(hass, DOMAIN, "turn_on") + + char_color_temp_iid = acc.char_color_temp.to_HAP()[HAP_REPR_IID] + char_brightness_iid = acc.char_brightness.to_HAP()[HAP_REPR_IID] + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_color_temp_iid, + HAP_REPR_VALUE: 200, + }, + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_brightness_iid, + HAP_REPR_VALUE: 75, + }, + ] + }, + "mock_addr", + ) + await _wait_for_light_coalesce(hass) + assert call_turn_on + assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id + for k, v in turn_on_props_with_brightness.items(): + assert call_turn_on[-1].data[k] == v + assert len(events) == 1 + assert events[-1].data[ATTR_VALUE] == "brightness at 75%, color temperature at 200" + assert acc.char_brightness.value == 75 + + +async def test_light_rgb_or_w_lights( + hass, + hk_driver, + events, +): + """Test lights with RGB or W lights.""" + entity_id = "light.demo" + + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_RGB, COLOR_MODE_WHITE], + ATTR_RGBW_COLOR: (128, 50, 0, 255), + ATTR_RGB_COLOR: (128, 50, 0), + ATTR_HS_COLOR: (23.438, 100.0), + ATTR_BRIGHTNESS: 255, + ATTR_COLOR_MODE: COLOR_MODE_RGB, + }, + ) + await hass.async_block_till_done() + acc = Light(hass, hk_driver, "Light", entity_id, 1, None) + hk_driver.add_accessory(acc) + + assert acc.char_hue.value == 23 + assert acc.char_saturation.value == 100 + + await acc.run() + await hass.async_block_till_done() + assert acc.char_hue.value == 23 + assert acc.char_saturation.value == 100 + assert acc.char_brightness.value == 100 + assert acc.char_color_temp.value == 153 + + # Set from HomeKit + call_turn_on = async_mock_service(hass, DOMAIN, "turn_on") + + char_hue_iid = acc.char_hue.to_HAP()[HAP_REPR_IID] + char_saturation_iid = acc.char_saturation.to_HAP()[HAP_REPR_IID] + char_brightness_iid = acc.char_brightness.to_HAP()[HAP_REPR_IID] + char_color_temp_iid = acc.char_color_temp.to_HAP()[HAP_REPR_IID] + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_hue_iid, + HAP_REPR_VALUE: 145, + }, + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_saturation_iid, + HAP_REPR_VALUE: 75, + }, + ] + }, + "mock_addr", + ) + await _wait_for_light_coalesce(hass) + assert call_turn_on + assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id + assert call_turn_on[-1].data[ATTR_HS_COLOR] == (145, 75) + assert len(events) == 1 + assert events[-1].data[ATTR_VALUE] == "set color at (145, 75)" + assert acc.char_brightness.value == 100 + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_color_temp_iid, + HAP_REPR_VALUE: acc.min_mireds, + }, + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_brightness_iid, + HAP_REPR_VALUE: 25, + }, + ] + }, + "mock_addr", + ) + await _wait_for_light_coalesce(hass) + assert call_turn_on + assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id + assert call_turn_on[-1].data[ATTR_WHITE] == round(25 * 255 / 100) + assert len(events) == 2 + assert events[-1].data[ATTR_VALUE] == "brightness at 25%, color temperature at 153" + assert acc.char_brightness.value == 25 + + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_RGB, COLOR_MODE_WHITE], + ATTR_BRIGHTNESS: 255, + ATTR_COLOR_MODE: COLOR_MODE_WHITE, + }, + ) + await hass.async_block_till_done() + assert acc.char_hue.value == 0 + assert acc.char_saturation.value == 0 + assert acc.char_brightness.value == 100 + assert acc.char_color_temp.value == 153 + + +@pytest.mark.parametrize( + "supported_color_modes, state_props", [ [ [COLOR_MODE_COLOR_TEMP, COLOR_MODE_RGBW], @@ -708,8 +908,6 @@ async def test_light_rgb_with_white( ATTR_BRIGHTNESS: 255, ATTR_COLOR_MODE: COLOR_MODE_RGBW, }, - {ATTR_RGBW_COLOR: (31, 127, 71, 0)}, - {ATTR_COLOR_TEMP: 2700}, ], [ [COLOR_MODE_COLOR_TEMP, COLOR_MODE_RGBWW], @@ -720,8 +918,6 @@ async def test_light_rgb_with_white( ATTR_BRIGHTNESS: 255, ATTR_COLOR_MODE: COLOR_MODE_RGBWW, }, - {ATTR_RGBWW_COLOR: (31, 127, 71, 0, 0)}, - {ATTR_COLOR_TEMP: 2700}, ], ], ) @@ -731,8 +927,6 @@ async def test_light_rgb_with_white_switch_to_temp( events, supported_color_modes, state_props, - turn_on_props, - turn_on_props_with_brightness, ): """Test lights with RGBW/RGBWW that preserves brightness when switching to color temp.""" entity_id = "light.demo" @@ -753,7 +947,7 @@ async def test_light_rgb_with_white_switch_to_temp( await hass.async_block_till_done() assert acc.char_hue.value == 23 assert acc.char_saturation.value == 100 - assert acc.char_brightness.value == 50 + assert acc.char_brightness.value == 100 # Set from HomeKit call_turn_on = async_mock_service(hass, DOMAIN, "turn_on") @@ -782,11 +976,96 @@ async def test_light_rgb_with_white_switch_to_temp( await _wait_for_light_coalesce(hass) assert call_turn_on assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id - for k, v in turn_on_props.items(): - assert call_turn_on[-1].data[k] == v + assert call_turn_on[-1].data[ATTR_HS_COLOR] == (145, 75) + assert len(events) == 1 + assert events[-1].data[ATTR_VALUE] == "set color at (145, 75)" + assert acc.char_brightness.value == 100 + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_color_temp_iid, + HAP_REPR_VALUE: 500, + }, + ] + }, + "mock_addr", + ) + await _wait_for_light_coalesce(hass) + assert call_turn_on + assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id + assert call_turn_on[-1].data[ATTR_COLOR_TEMP] == 500 + assert len(events) == 2 + assert events[-1].data[ATTR_VALUE] == "color temperature at 500" + assert acc.char_brightness.value == 100 + + +async def test_light_rgbww_with_color_temp_conversion( + hass, + hk_driver, + events, +): + """Test lights with RGBWW convert color temp as expected.""" + entity_id = "light.demo" + + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_RGBWW], + ATTR_RGBWW_COLOR: (128, 50, 0, 255, 255), + ATTR_RGB_COLOR: (128, 50, 0), + ATTR_HS_COLOR: (23.438, 100.0), + ATTR_BRIGHTNESS: 255, + ATTR_COLOR_MODE: COLOR_MODE_RGBWW, + }, + ) + await hass.async_block_till_done() + acc = Light(hass, hk_driver, "Light", entity_id, 1, None) + hk_driver.add_accessory(acc) + + assert acc.char_hue.value == 23 + assert acc.char_saturation.value == 100 + + await acc.run() + await hass.async_block_till_done() + assert acc.char_hue.value == 23 + assert acc.char_saturation.value == 100 + assert acc.char_brightness.value == 100 + + # Set from HomeKit + call_turn_on = async_mock_service(hass, DOMAIN, "turn_on") + + char_hue_iid = acc.char_hue.to_HAP()[HAP_REPR_IID] + char_saturation_iid = acc.char_saturation.to_HAP()[HAP_REPR_IID] + char_color_temp_iid = acc.char_color_temp.to_HAP()[HAP_REPR_IID] + char_brightness_iid = acc.char_brightness.to_HAP()[HAP_REPR_IID] + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_hue_iid, + HAP_REPR_VALUE: 145, + }, + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_saturation_iid, + HAP_REPR_VALUE: 75, + }, + ] + }, + "mock_addr", + ) + await _wait_for_light_coalesce(hass) + assert call_turn_on + assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id + assert call_turn_on[-1].data[ATTR_HS_COLOR] == (145, 75) assert len(events) == 1 assert events[-1].data[ATTR_VALUE] == "set color at (145, 75)" - assert acc.char_brightness.value == 50 + assert acc.char_brightness.value == 100 hk_driver.set_characteristics( { @@ -794,7 +1073,7 @@ async def test_light_rgb_with_white_switch_to_temp( { HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_color_temp_iid, - HAP_REPR_VALUE: 2700, + HAP_REPR_VALUE: 200, }, ] }, @@ -803,11 +1082,134 @@ async def test_light_rgb_with_white_switch_to_temp( await _wait_for_light_coalesce(hass) assert call_turn_on assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id - for k, v in turn_on_props_with_brightness.items(): - assert call_turn_on[-1].data[k] == v + assert call_turn_on[-1].data[ATTR_RGBWW_COLOR] == (0, 0, 0, 220, 35) assert len(events) == 2 - assert events[-1].data[ATTR_VALUE] == "color temperature at 2700" - assert acc.char_brightness.value == 50 + assert events[-1].data[ATTR_VALUE] == "color temperature at 200" + assert acc.char_brightness.value == 100 + + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_RGBWW], + ATTR_RGBWW_COLOR: (0, 0, 0, 128, 255), + ATTR_RGB_COLOR: (255, 163, 79), + ATTR_HS_COLOR: (28.636, 69.02), + ATTR_BRIGHTNESS: 180, + ATTR_COLOR_MODE: COLOR_MODE_RGBWW, + }, + ) + await hass.async_block_till_done() + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_brightness_iid, + HAP_REPR_VALUE: 100, + }, + ] + }, + "mock_addr", + ) + await _wait_for_light_coalesce(hass) + assert call_turn_on + assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id + assert call_turn_on[-1].data[ATTR_BRIGHTNESS_PCT] == 100 + assert len(events) == 3 + assert events[-1].data[ATTR_VALUE] == "brightness at 100%" + assert acc.char_brightness.value == 100 + + +async def test_light_rgbw_with_color_temp_conversion( + hass, + hk_driver, + events, +): + """Test lights with RGBW convert color temp as expected.""" + entity_id = "light.demo" + + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_RGBW], + ATTR_RGBWW_COLOR: (128, 50, 0, 255, 255), + ATTR_RGB_COLOR: (128, 50, 0), + ATTR_HS_COLOR: (23.438, 100.0), + ATTR_BRIGHTNESS: 255, + ATTR_COLOR_MODE: COLOR_MODE_RGBW, + }, + ) + await hass.async_block_till_done() + acc = Light(hass, hk_driver, "Light", entity_id, 1, None) + hk_driver.add_accessory(acc) + + assert acc.char_hue.value == 23 + assert acc.char_saturation.value == 100 + + await acc.run() + await hass.async_block_till_done() + assert acc.char_hue.value == 23 + assert acc.char_saturation.value == 100 + assert acc.char_brightness.value == 100 + + # Set from HomeKit + call_turn_on = async_mock_service(hass, DOMAIN, "turn_on") + + char_hue_iid = acc.char_hue.to_HAP()[HAP_REPR_IID] + char_saturation_iid = acc.char_saturation.to_HAP()[HAP_REPR_IID] + char_color_temp_iid = acc.char_color_temp.to_HAP()[HAP_REPR_IID] + assert ( + acc.char_color_temp.properties[PROP_MIN_VALUE] + == acc.char_color_temp.properties[PROP_MAX_VALUE] + ) + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_hue_iid, + HAP_REPR_VALUE: 145, + }, + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_saturation_iid, + HAP_REPR_VALUE: 75, + }, + ] + }, + "mock_addr", + ) + await _wait_for_light_coalesce(hass) + assert call_turn_on + assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id + assert call_turn_on[-1].data[ATTR_HS_COLOR] == (145, 75) + assert len(events) == 1 + assert events[-1].data[ATTR_VALUE] == "set color at (145, 75)" + assert acc.char_brightness.value == 100 + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_color_temp_iid, + HAP_REPR_VALUE: 153, + }, + ] + }, + "mock_addr", + ) + await _wait_for_light_coalesce(hass) + assert call_turn_on + assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id + assert call_turn_on[-1].data[ATTR_RGBW_COLOR] == (0, 0, 0, 255) + assert len(events) == 2 + assert events[-1].data[ATTR_VALUE] == "color temperature at 153" + assert acc.char_brightness.value == 100 async def test_light_set_brightness_and_color(hass, hk_driver, events): From 6d37979e10b9af824d1b00511b109292afb7a707 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Sun, 6 Feb 2022 23:13:05 +0100 Subject: [PATCH 0364/1098] Fix wind speed unit (#65723) --- homeassistant/components/accuweather/weather.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/accuweather/weather.py b/homeassistant/components/accuweather/weather.py index 00726f6db3815a..4ab9342de62817 100644 --- a/homeassistant/components/accuweather/weather.py +++ b/homeassistant/components/accuweather/weather.py @@ -17,7 +17,12 @@ WeatherEntity, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_NAME, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.const import ( + CONF_NAME, + SPEED_MILES_PER_HOUR, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, +) from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo @@ -62,9 +67,13 @@ def __init__( """Initialize.""" super().__init__(coordinator) self._unit_system = API_METRIC if coordinator.is_metric else API_IMPERIAL - self._attr_wind_speed_unit = self.coordinator.data["Wind"]["Speed"][ - self._unit_system - ]["Unit"] + wind_speed_unit = self.coordinator.data["Wind"]["Speed"][self._unit_system][ + "Unit" + ] + if wind_speed_unit == "mi/h": + self._attr_wind_speed_unit = SPEED_MILES_PER_HOUR + else: + self._attr_wind_speed_unit = wind_speed_unit self._attr_name = name self._attr_unique_id = coordinator.location_key self._attr_temperature_unit = ( From ddd198de372ee87a2b164413b48c406b00410743 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 6 Feb 2022 14:14:25 -0800 Subject: [PATCH 0365/1098] Fix legacy nest diagnostics to return empty rather than fail (#65824) Fix legacy nest diangostics to return gracefully, rather than a TypError by checking explicitiy for SDM in the config entry. Update diagnostics to use the common nest test fixture, and extend with support for the legacy nest config. Use the sdm test fixture in the existing legacy tests so they all share the same config files. --- homeassistant/components/nest/diagnostics.py | 5 +- tests/components/nest/common.py | 18 ++++ .../nest/test_config_flow_legacy.py | 6 +- tests/components/nest/test_diagnostics.py | 85 ++++++++++--------- tests/components/nest/test_init_legacy.py | 31 ++----- 5 files changed, 78 insertions(+), 67 deletions(-) diff --git a/homeassistant/components/nest/diagnostics.py b/homeassistant/components/nest/diagnostics.py index 0b6cfff6bae00f..859aa83458141e 100644 --- a/homeassistant/components/nest/diagnostics.py +++ b/homeassistant/components/nest/diagnostics.py @@ -12,7 +12,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from .const import DATA_SUBSCRIBER, DOMAIN +from .const import DATA_SDM, DATA_SUBSCRIBER, DOMAIN REDACT_DEVICE_TRAITS = {InfoTrait.NAME} @@ -21,6 +21,9 @@ async def async_get_config_entry_diagnostics( hass: HomeAssistant, config_entry: ConfigEntry ) -> dict: """Return diagnostics for a config entry.""" + if DATA_SDM not in config_entry.data: + return {} + if DATA_SUBSCRIBER not in hass.data[DOMAIN]: return {"error": "No subscriber configured"} diff --git a/tests/components/nest/common.py b/tests/components/nest/common.py index ca761e8987f150..e80ca84d58f003 100644 --- a/tests/components/nest/common.py +++ b/tests/components/nest/common.py @@ -102,6 +102,24 @@ class NestTestConfig: }, ) +TEST_CONFIG_LEGACY = NestTestConfig( + config={ + "nest": { + "client_id": "some-client-id", + "client_secret": "some-client-secret", + }, + }, + config_entry_data={ + "auth_implementation": "local", + "tokens": { + "expires_at": time.time() + 86400, + "access_token": { + "token": "some-token", + }, + }, + }, +) + class FakeSubscriber(GoogleNestSubscriber): """Fake subscriber that supplies a FakeDeviceManager.""" diff --git a/tests/components/nest/test_config_flow_legacy.py b/tests/components/nest/test_config_flow_legacy.py index d21920b9e6f107..843c9b582ae18a 100644 --- a/tests/components/nest/test_config_flow_legacy.py +++ b/tests/components/nest/test_config_flow_legacy.py @@ -6,9 +6,11 @@ from homeassistant.components.nest import DOMAIN, config_flow from homeassistant.setup import async_setup_component +from .common import TEST_CONFIG_LEGACY + from tests.common import MockConfigEntry -CONFIG = {DOMAIN: {"client_id": "bla", "client_secret": "bla"}} +CONFIG = TEST_CONFIG_LEGACY.config async def test_abort_if_no_implementation_registered(hass): @@ -59,7 +61,7 @@ async def test_full_flow_implementation(hass): assert ( result["description_placeholders"] .get("url") - .startswith("https://home.nest.com/login/oauth2?client_id=bla") + .startswith("https://home.nest.com/login/oauth2?client_id=some-client-id") ) def mock_login(auth): diff --git a/tests/components/nest/test_diagnostics.py b/tests/components/nest/test_diagnostics.py index b603019da815c8..cf6c9c5b20fcc1 100644 --- a/tests/components/nest/test_diagnostics.py +++ b/tests/components/nest/test_diagnostics.py @@ -2,54 +2,45 @@ from unittest.mock import patch -from google_nest_sdm.device import Device from google_nest_sdm.exceptions import SubscriberException +import pytest -from homeassistant.components.nest import DOMAIN from homeassistant.config_entries import ConfigEntryState -from homeassistant.setup import async_setup_component -from .common import CONFIG, async_setup_sdm_platform, create_config_entry +from .common import TEST_CONFIG_LEGACY from tests.components.diagnostics import get_diagnostics_for_config_entry -THERMOSTAT_TYPE = "sdm.devices.types.THERMOSTAT" - -async def test_entry_diagnostics(hass, hass_client): +async def test_entry_diagnostics( + hass, hass_client, create_device, setup_platform, config_entry +): """Test config entry diagnostics.""" - devices = { - "some-device-id": Device.MakeDevice( - { - "name": "enterprises/project-id/devices/device-id", - "type": "sdm.devices.types.THERMOSTAT", - "assignee": "enterprises/project-id/structures/structure-id/rooms/room-id", - "traits": { - "sdm.devices.traits.Info": { - "customName": "My Sensor", - }, - "sdm.devices.traits.Temperature": { - "ambientTemperatureCelsius": 25.1, - }, - "sdm.devices.traits.Humidity": { - "ambientHumidityPercent": 35.0, - }, + create_device.create( + raw_data={ + "name": "enterprises/project-id/devices/device-id", + "type": "sdm.devices.types.THERMOSTAT", + "assignee": "enterprises/project-id/structures/structure-id/rooms/room-id", + "traits": { + "sdm.devices.traits.Info": { + "customName": "My Sensor", + }, + "sdm.devices.traits.Temperature": { + "ambientTemperatureCelsius": 25.1, + }, + "sdm.devices.traits.Humidity": { + "ambientHumidityPercent": 35.0, }, - "parentRelations": [ - { - "parent": "enterprises/project-id/structures/structure-id/rooms/room-id", - "displayName": "Lobby", - } - ], }, - auth=None, - ) - } - assert await async_setup_sdm_platform(hass, platform=None, devices=devices) - - entries = hass.config_entries.async_entries(DOMAIN) - assert len(entries) == 1 - config_entry = entries[0] + "parentRelations": [ + { + "parent": "enterprises/project-id/structures/structure-id/rooms/room-id", + "displayName": "Lobby", + } + ], + } + ) + await setup_platform() assert config_entry.state is ConfigEntryState.LOADED # Test that only non identifiable device information is returned @@ -76,20 +67,32 @@ async def test_entry_diagnostics(hass, hass_client): } -async def test_setup_susbcriber_failure(hass, hass_client): +async def test_setup_susbcriber_failure( + hass, hass_client, config_entry, setup_base_platform +): """Test configuration error.""" - config_entry = create_config_entry() - config_entry.add_to_hass(hass) with patch( "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation" ), patch( "homeassistant.components.nest.api.GoogleNestSubscriber.start_async", side_effect=SubscriberException(), ): - assert await async_setup_component(hass, DOMAIN, CONFIG) + await setup_base_platform() assert config_entry.state is ConfigEntryState.SETUP_RETRY assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { "error": "No subscriber configured" } + + +@pytest.mark.parametrize("nest_test_config", [TEST_CONFIG_LEGACY]) +async def test_legacy_config_entry_diagnostics( + hass, hass_client, config_entry, setup_base_platform +): + """Test config entry diagnostics for legacy integration doesn't fail.""" + + with patch("homeassistant.components.nest.legacy.Nest"): + await setup_base_platform() + + assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == {} diff --git a/tests/components/nest/test_init_legacy.py b/tests/components/nest/test_init_legacy.py index 3a78877a235179..cbf1bfe2d488a6 100644 --- a/tests/components/nest/test_init_legacy.py +++ b/tests/components/nest/test_init_legacy.py @@ -1,30 +1,18 @@ """Test basic initialization for the Legacy Nest API using mocks for the Nest python library.""" -import time from unittest.mock import MagicMock, PropertyMock, patch -from homeassistant.setup import async_setup_component +import pytest -from tests.common import MockConfigEntry +from .common import TEST_CONFIG_LEGACY DOMAIN = "nest" -CONFIG = { - "nest": { - "client_id": "some-client-id", - "client_secret": "some-client-secret", - }, -} -CONFIG_ENTRY_DATA = { - "auth_implementation": "local", - "tokens": { - "expires_at": time.time() + 86400, - "access_token": { - "token": "some-token", - }, - }, -} +@pytest.fixture +def nest_test_config(): + """Fixture to specify the overall test fixture configuration.""" + return TEST_CONFIG_LEGACY def make_thermostat(): @@ -45,7 +33,7 @@ def make_thermostat(): return device -async def test_thermostat(hass): +async def test_thermostat(hass, setup_base_platform): """Test simple initialization for thermostat entities.""" thermostat = make_thermostat() @@ -58,8 +46,6 @@ async def test_thermostat(hass): nest = MagicMock() type(nest).structures = PropertyMock(return_value=[structure]) - config_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG_ENTRY_DATA) - config_entry.add_to_hass(hass) with patch("homeassistant.components.nest.legacy.Nest", return_value=nest), patch( "homeassistant.components.nest.legacy.sensor._VALID_SENSOR_TYPES", ["humidity", "temperature"], @@ -67,8 +53,7 @@ async def test_thermostat(hass): "homeassistant.components.nest.legacy.binary_sensor._VALID_BINARY_SENSOR_TYPES", {"fan": None}, ): - assert await async_setup_component(hass, DOMAIN, CONFIG) - await hass.async_block_till_done() + await setup_base_platform() climate = hass.states.get("climate.my_thermostat") assert climate is not None From 50525e25b678aa79dd8642f5e87ef948b25b8c44 Mon Sep 17 00:00:00 2001 From: Sean Vig Date: Sun, 6 Feb 2022 17:14:44 -0500 Subject: [PATCH 0366/1098] Fix Amcrest service calls (#65717) Fixes #65522 Fixes #65647 --- homeassistant/components/amcrest/camera.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/amcrest/camera.py b/homeassistant/components/amcrest/camera.py index 6e729a8f1b57af..3846f4945a986e 100644 --- a/homeassistant/components/amcrest/camera.py +++ b/homeassistant/components/amcrest/camera.py @@ -515,8 +515,8 @@ async def _async_change_setting( max_tries = 3 for tries in range(max_tries, 0, -1): try: - await getattr(self, f"_set_{func}")(value) - new_value = await getattr(self, f"_get_{func}")() + await getattr(self, f"_async_set_{func}")(value) + new_value = await getattr(self, f"_async_get_{func}")() if new_value != value: raise AmcrestCommandFailed except (AmcrestError, AmcrestCommandFailed) as error: From 341d039252fe4e4f30d8b34235a16000b7936840 Mon Sep 17 00:00:00 2001 From: ollo69 <60491700+ollo69@users.noreply.github.com> Date: Sun, 6 Feb 2022 23:15:50 +0100 Subject: [PATCH 0367/1098] Improve androidtv mac address handling and test coverage (#65749) * Better mac addr handling and improve test coverage * Apply suggested changes * Apply more suggested changes --- .../components/androidtv/__init__.py | 15 ++ .../components/androidtv/config_flow.py | 7 +- .../components/androidtv/media_player.py | 7 +- tests/components/androidtv/patchers.py | 26 ++-- .../components/androidtv/test_config_flow.py | 24 +++- .../components/androidtv/test_media_player.py | 133 ++++++------------ 6 files changed, 94 insertions(+), 118 deletions(-) diff --git a/homeassistant/components/androidtv/__init__.py b/homeassistant/components/androidtv/__init__.py index 81d4a3f06458f0..9b9683856028d4 100644 --- a/homeassistant/components/androidtv/__init__.py +++ b/homeassistant/components/androidtv/__init__.py @@ -18,6 +18,7 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import entity_registry as er +from homeassistant.helpers.device_registry import format_mac from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.storage import STORAGE_DIR from homeassistant.helpers.typing import ConfigType @@ -33,16 +34,30 @@ DEVICE_ANDROIDTV, DEVICE_FIRETV, DOMAIN, + PROP_ETHMAC, PROP_SERIALNO, + PROP_WIFIMAC, SIGNAL_CONFIG_ENTITY, ) PLATFORMS = [Platform.MEDIA_PLAYER] RELOAD_OPTIONS = [CONF_STATE_DETECTION_RULES] +_INVALID_MACS = {"ff:ff:ff:ff:ff:ff"} + _LOGGER = logging.getLogger(__name__) +def get_androidtv_mac(dev_props): + """Return formatted mac from device properties.""" + for prop_mac in (PROP_ETHMAC, PROP_WIFIMAC): + if if_mac := dev_props.get(prop_mac): + mac = format_mac(if_mac) + if mac not in _INVALID_MACS: + return mac + return None + + def _setup_androidtv(hass, config): """Generate an ADB key (if needed) and load it.""" adbkey = config.get(CONF_ADBKEY, hass.config.path(STORAGE_DIR, "androidtv_adbkey")) diff --git a/homeassistant/components/androidtv/config_flow.py b/homeassistant/components/androidtv/config_flow.py index 0ec37fdeb6faa8..8f0efc3479922f 100644 --- a/homeassistant/components/androidtv/config_flow.py +++ b/homeassistant/components/androidtv/config_flow.py @@ -11,9 +11,8 @@ from homeassistant.const import CONF_DEVICE_CLASS, CONF_HOST, CONF_NAME, CONF_PORT from homeassistant.core import callback from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.device_registry import format_mac -from . import async_connect_androidtv +from . import async_connect_androidtv, get_androidtv_mac from .const import ( CONF_ADB_SERVER_IP, CONF_ADB_SERVER_PORT, @@ -132,9 +131,7 @@ async def _async_check_connection(self, user_input): PROP_WIFIMAC, dev_prop.get(PROP_WIFIMAC), ) - unique_id = format_mac( - dev_prop.get(PROP_ETHMAC) or dev_prop.get(PROP_WIFIMAC, "") - ) + unique_id = get_androidtv_mac(dev_prop) await aftv.adb_close() return None, unique_id diff --git a/homeassistant/components/androidtv/media_player.py b/homeassistant/components/androidtv/media_player.py index 03b1e6799618f4..1ab592143c62e1 100644 --- a/homeassistant/components/androidtv/media_player.py +++ b/homeassistant/components/androidtv/media_player.py @@ -51,12 +51,13 @@ ) from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv, entity_platform -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, format_mac +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from . import get_androidtv_mac from .const import ( ANDROID_DEV, ANDROID_DEV_OPT, @@ -80,8 +81,6 @@ DEVICE_ANDROIDTV, DEVICE_CLASSES, DOMAIN, - PROP_ETHMAC, - PROP_WIFIMAC, SIGNAL_CONFIG_ENTITY, ) @@ -343,7 +342,7 @@ def __init__( self._attr_device_info[ATTR_MANUFACTURER] = manufacturer if sw_version := info.get(ATTR_SW_VERSION): self._attr_device_info[ATTR_SW_VERSION] = sw_version - if mac := format_mac(info.get(PROP_ETHMAC) or info.get(PROP_WIFIMAC, "")): + if mac := get_androidtv_mac(info): self._attr_device_info[ATTR_CONNECTIONS] = {(CONNECTION_NETWORK_MAC, mac)} self._app_id_to_name = {} diff --git a/tests/components/androidtv/patchers.py b/tests/components/androidtv/patchers.py index 4411945c71b144..7cc14bbd7b59ba 100644 --- a/tests/components/androidtv/patchers.py +++ b/tests/components/androidtv/patchers.py @@ -1,13 +1,17 @@ """Define patches used for androidtv tests.""" - from unittest.mock import mock_open, patch +from androidtv.constants import CMD_DEVICE_PROPERTIES, CMD_MAC_ETH0, CMD_MAC_WLAN0 + KEY_PYTHON = "python" KEY_SERVER = "server" ADB_DEVICE_TCP_ASYNC_FAKE = "AdbDeviceTcpAsyncFake" DEVICE_ASYNC_FAKE = "DeviceAsyncFake" +PROPS_DEV_INFO = "fake\nfake\n0123456\nfake" +PROPS_DEV_MAC = "ether ab:cd:ef:gh:ij:kl brd" + class AdbDeviceTcpAsyncFake: """A fake of the `adb_shell.adb_device_async.AdbDeviceTcpAsync` class.""" @@ -100,12 +104,18 @@ async def connect_fail_python(self, *args, **kwargs): } -def patch_shell(response=None, error=False): +def patch_shell(response=None, error=False, mac_eth=False): """Mock the `AdbDeviceTcpAsyncFake.shell` and `DeviceAsyncFake.shell` methods.""" async def shell_success(self, cmd, *args, **kwargs): """Mock the `AdbDeviceTcpAsyncFake.shell` and `DeviceAsyncFake.shell` methods when they are successful.""" self.shell_cmd = cmd + if cmd == CMD_DEVICE_PROPERTIES: + return PROPS_DEV_INFO + if cmd == CMD_MAC_WLAN0: + return PROPS_DEV_MAC + if cmd == CMD_MAC_ETH0: + return PROPS_DEV_MAC if mac_eth else None return response async def shell_fail_python(self, cmd, *args, **kwargs): @@ -185,15 +195,3 @@ def patch_androidtv_update( "androidtv.androidtv.androidtv_async.AndroidTVAsync.update", side_effect=ZeroDivisionError, ) - -PATCH_DEVICE_PROPERTIES = patch( - "androidtv.basetv.basetv_async.BaseTVAsync.get_device_properties", - return_value={ - "manufacturer": "a", - "model": "b", - "serialno": "c", - "sw_version": "d", - "wifimac": "ab:cd:ef:gh:ij:kl", - "ethmac": None, - }, -) diff --git a/tests/components/androidtv/test_config_flow.py b/tests/components/androidtv/test_config_flow.py index 757be8f6d8d994..991d3757749fa3 100644 --- a/tests/components/androidtv/test_config_flow.py +++ b/tests/components/androidtv/test_config_flow.py @@ -31,6 +31,7 @@ DEFAULT_PORT, DOMAIN, PROP_ETHMAC, + PROP_WIFIMAC, ) from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER @@ -42,6 +43,7 @@ ADBKEY = "adbkey" ETH_MAC = "a1:b1:c1:d1:e1:f1" +WIFI_MAC = "a2:b2:c2:d2:e2:f2" HOST = "127.0.0.1" VALID_DETECT_RULE = [{"paused": {"media_session_state": 3}}] @@ -84,18 +86,28 @@ class MockConfigDevice: """Mock class to emulate Android TV device.""" - def __init__(self, eth_mac=ETH_MAC): + def __init__(self, eth_mac=ETH_MAC, wifi_mac=None): """Initialize a fake device to test config flow.""" self.available = True - self.device_properties = {PROP_ETHMAC: eth_mac} + self.device_properties = {PROP_ETHMAC: eth_mac, PROP_WIFIMAC: wifi_mac} async def adb_close(self): """Fake method to close connection.""" self.available = False -@pytest.mark.parametrize("config", [CONFIG_PYTHON_ADB, CONFIG_ADB_SERVER]) -async def test_user(hass, config): +@pytest.mark.parametrize( + ["config", "eth_mac", "wifi_mac"], + [ + (CONFIG_PYTHON_ADB, ETH_MAC, None), + (CONFIG_ADB_SERVER, ETH_MAC, None), + (CONFIG_PYTHON_ADB, None, WIFI_MAC), + (CONFIG_ADB_SERVER, None, WIFI_MAC), + (CONFIG_PYTHON_ADB, ETH_MAC, WIFI_MAC), + (CONFIG_ADB_SERVER, ETH_MAC, WIFI_MAC), + ], +) +async def test_user(hass, config, eth_mac, wifi_mac): """Test user config.""" flow_result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER, "show_advanced_options": True} @@ -106,7 +118,7 @@ async def test_user(hass, config): # test with all provided with patch( CONNECT_METHOD, - return_value=(MockConfigDevice(), None), + return_value=(MockConfigDevice(eth_mac, wifi_mac), None), ), PATCH_SETUP_ENTRY as mock_setup_entry, PATCH_GET_HOST_IP: result = await hass.config_entries.flow.async_configure( flow_result["flow_id"], user_input=config @@ -273,7 +285,7 @@ async def test_invalid_serial(hass): """Test for invalid serialno.""" with patch( CONNECT_METHOD, - return_value=(MockConfigDevice(eth_mac=""), None), + return_value=(MockConfigDevice(eth_mac=None), None), ), PATCH_GET_HOST_IP: result = await hass.config_entries.flow.async_init( DOMAIN, diff --git a/tests/components/androidtv/test_media_player.py b/tests/components/androidtv/test_media_player.py index e97de0fc928dea..a0bab1736ff00f 100644 --- a/tests/components/androidtv/test_media_player.py +++ b/tests/components/androidtv/test_media_player.py @@ -142,29 +142,6 @@ def _setup(config): return patch_key, entity_id, config_entry -async def test_setup_with_properties(hass): - """Test that setup succeeds with device properties. - - the response must be a string with the following info separated with line break: - "manufacturer, model, serialno, version, mac_wlan0_output, mac_eth0_output" - - """ - - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) - config_entry.add_to_hass(hass) - response = "fake\nfake\n0123456\nfake\nether a1:b1:c1:d1:e1:f1 brd\nnone" - - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(response)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - state = hass.states.get(entity_id) - assert state is not None - - @pytest.mark.parametrize( "config", [ @@ -190,9 +167,8 @@ async def test_reconnect(hass, caplog, config): ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ patch_key ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) @@ -259,9 +235,8 @@ async def test_adb_shell_returns_none(hass, config): ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ patch_key ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) @@ -289,9 +264,8 @@ async def test_setup_with_adbkey(hass): ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ patch_key ], patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER, PATCH_ISFILE, PATCH_ACCESS: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) @@ -324,9 +298,8 @@ async def test_sources(hass, config0): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) @@ -404,9 +377,8 @@ async def _test_exclude_sources(hass, config0, expected_sources): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) @@ -486,9 +458,8 @@ async def _test_select_source(hass, config0, source, expected_arg, method_patch) with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) @@ -714,9 +685,8 @@ async def test_setup_fail(hass, config): ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ patch_key ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) is False - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) is False + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) @@ -733,9 +703,8 @@ async def test_adb_command(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patch( "androidtv.basetv.basetv_async.BaseTVAsync.adb_shell", return_value=response @@ -763,9 +732,8 @@ async def test_adb_command_unicode_decode_error(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patch( "androidtv.basetv.basetv_async.BaseTVAsync.adb_shell", @@ -793,9 +761,8 @@ async def test_adb_command_key(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patch( "androidtv.basetv.basetv_async.BaseTVAsync.adb_shell", return_value=response @@ -823,9 +790,8 @@ async def test_adb_command_get_properties(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patch( "androidtv.androidtv.androidtv_async.AndroidTVAsync.get_properties_dict", @@ -853,9 +819,8 @@ async def test_learn_sendevent(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patch( "androidtv.basetv.basetv_async.BaseTVAsync.learn_sendevent", @@ -882,9 +847,8 @@ async def test_update_lock_not_acquired(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: await hass.helpers.entity_component.async_update_entity(entity_id) @@ -918,9 +882,8 @@ async def test_download(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() # Failed download because path is not whitelisted with patch("androidtv.basetv.basetv_async.BaseTVAsync.adb_pull") as patch_pull: @@ -965,9 +928,8 @@ async def test_upload(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() # Failed upload because path is not whitelisted with patch("androidtv.basetv.basetv_async.BaseTVAsync.adb_push") as patch_push: @@ -1010,9 +972,8 @@ async def test_androidtv_volume_set(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patch( "androidtv.basetv.basetv_async.BaseTVAsync.set_volume_level", return_value=0.5 @@ -1038,9 +999,8 @@ async def test_get_image(hass, hass_ws_client): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patchers.patch_shell("11")[patch_key]: await hass.helpers.entity_component.async_update_entity(entity_id) @@ -1115,9 +1075,8 @@ async def test_services_androidtv(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[patch_key]: with patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patchers.patch_shell(SHELL_RESPONSE_STANDBY)[patch_key]: await _test_service( @@ -1162,9 +1121,8 @@ async def test_services_firetv(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[patch_key]: with patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patchers.patch_shell(SHELL_RESPONSE_STANDBY)[patch_key]: await _test_service(hass, entity_id, SERVICE_MEDIA_STOP, "back") @@ -1179,9 +1137,8 @@ async def test_volume_mute(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[patch_key]: with patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patchers.patch_shell(SHELL_RESPONSE_STANDBY)[patch_key]: service_data = {ATTR_ENTITY_ID: entity_id, ATTR_MEDIA_VOLUME_MUTED: True} @@ -1224,9 +1181,8 @@ async def test_connection_closed_on_ha_stop(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patch( "androidtv.androidtv.androidtv_async.AndroidTVAsync.adb_close" @@ -1249,9 +1205,8 @@ async def test_exception(hass): ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ patch_key ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) From dc65c621edf0e0a7914fd69c0439fce22367235f Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sun, 6 Feb 2022 23:17:10 +0100 Subject: [PATCH 0368/1098] check wan access type (#65389) --- homeassistant/components/fritz/common.py | 14 ++++++++------ homeassistant/components/fritz/sensor.py | 12 ++++++++---- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index 667d94695d3a44..8786a09b215ccf 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -586,11 +586,11 @@ def _service_call_action( ) return {} - async def async_get_wan_dsl_interface_config(self) -> dict[str, Any]: - """Call WANDSLInterfaceConfig service.""" + async def async_get_wan_link_properties(self) -> dict[str, Any]: + """Call WANCommonInterfaceConfig service.""" return await self.hass.async_add_executor_job( - partial(self.get_wan_dsl_interface_config) + partial(self.get_wan_link_properties) ) async def async_get_wan_link_properties(self) -> dict[str, Any]: @@ -697,10 +697,12 @@ def get_wlan_configuration(self, index: int) -> dict[str, Any]: return self._service_call_action("WLANConfiguration", str(index), "GetInfo") - def get_wan_dsl_interface_config(self) -> dict[str, Any]: - """Call WANDSLInterfaceConfig service.""" + def get_wan_link_properties(self) -> dict[str, Any]: + """Call WANCommonInterfaceConfig service.""" - return self._service_call_action("WANDSLInterfaceConfig", "1", "GetInfo") + return self._service_call_action( + "WANCommonInterfaceConfig", "1", "GetCommonLinkProperties" + ) def get_wan_link_properties(self) -> dict[str, Any]: """Call WANCommonInterfaceConfig service.""" diff --git a/homeassistant/components/fritz/sensor.py b/homeassistant/components/fritz/sensor.py index 6155cdc591441f..5e4b18eebcaf1d 100644 --- a/homeassistant/components/fritz/sensor.py +++ b/homeassistant/components/fritz/sensor.py @@ -277,10 +277,14 @@ async def async_setup_entry( _LOGGER.debug("Setting up FRITZ!Box sensors") avm_wrapper: AvmWrapper = hass.data[DOMAIN][entry.entry_id] - dsl: bool = False - dslinterface = await avm_wrapper.async_get_wan_dsl_interface_config() - if dslinterface: - dsl = dslinterface["NewEnable"] + link_properties = await avm_wrapper.async_get_wan_link_properties() + dsl: bool = link_properties.get("NewWANAccessType") == "DSL" + + _LOGGER.debug( + "WANAccessType of FritzBox %s is '%s'", + avm_wrapper.host, + link_properties.get("NewWANAccessType"), + ) entities = [ FritzBoxSensor(avm_wrapper, entry.title, description) From c28821aeca6ff25c30c0d84102bdcd933e2a022c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 6 Feb 2022 23:34:23 +0100 Subject: [PATCH 0369/1098] Remove unused temp_unit attr [sensibo] (#65953) --- homeassistant/components/sensibo/climate.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index b0cee4fbac01a4..e8017e7d849748 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -106,7 +106,7 @@ async def async_setup_entry( coordinator: SensiboDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] entities = [ - SensiboClimate(coordinator, device_id, hass.config.units.temperature_unit) + SensiboClimate(coordinator, device_id) for device_id, device_data in coordinator.data.items() # Remove none climate devices if device_data["hvac_modes"] and device_data["temp"] @@ -130,10 +130,7 @@ class SensiboClimate(CoordinatorEntity, ClimateEntity): coordinator: SensiboDataUpdateCoordinator def __init__( - self, - coordinator: SensiboDataUpdateCoordinator, - device_id: str, - temp_unit: str, + self, coordinator: SensiboDataUpdateCoordinator, device_id: str ) -> None: """Initiate SensiboClimate.""" super().__init__(coordinator) From f4ebb03bab4a6d47edf9b62d29508b1e5473e21c Mon Sep 17 00:00:00 2001 From: Teemu R Date: Sun, 6 Feb 2022 23:37:54 +0100 Subject: [PATCH 0370/1098] Add tplink hardware version to device info (#65951) * Add tplink hardware version to device info * Update mocks Co-authored-by: J. Nick Koston --- homeassistant/components/tplink/entity.py | 1 + tests/components/tplink/__init__.py | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/tplink/entity.py b/homeassistant/components/tplink/entity.py index f04a7459543e95..4380b1397b6a44 100644 --- a/homeassistant/components/tplink/entity.py +++ b/homeassistant/components/tplink/entity.py @@ -54,6 +54,7 @@ def device_info(self) -> DeviceInfo: model=self.device.model, name=self.device.alias, sw_version=self.device.hw_info["sw_ver"], + hw_version=self.device.hw_info["hw_ver"], ) @property diff --git a/tests/components/tplink/__init__.py b/tests/components/tplink/__init__.py index 50249f54f03bcb..02b7404201a44a 100644 --- a/tests/components/tplink/__init__.py +++ b/tests/components/tplink/__init__.py @@ -38,7 +38,7 @@ def _mocked_bulb() -> SmartBulb: bulb.device_id = MAC_ADDRESS bulb.valid_temperature_range.min = 4000 bulb.valid_temperature_range.max = 9000 - bulb.hw_info = {"sw_ver": "1.0.0"} + bulb.hw_info = {"sw_ver": "1.0.0", "hw_ver": "1.0.0"} bulb.turn_off = AsyncMock() bulb.turn_on = AsyncMock() bulb.set_brightness = AsyncMock() @@ -65,7 +65,7 @@ def _mocked_dimmer() -> SmartDimmer: dimmer.device_id = MAC_ADDRESS dimmer.valid_temperature_range.min = 4000 dimmer.valid_temperature_range.max = 9000 - dimmer.hw_info = {"sw_ver": "1.0.0"} + dimmer.hw_info = {"sw_ver": "1.0.0", "hw_ver": "1.0.0"} dimmer.turn_off = AsyncMock() dimmer.turn_on = AsyncMock() dimmer.set_brightness = AsyncMock() @@ -88,7 +88,7 @@ def _mocked_plug() -> SmartPlug: plug.is_strip = False plug.is_plug = True plug.device_id = MAC_ADDRESS - plug.hw_info = {"sw_ver": "1.0.0"} + plug.hw_info = {"sw_ver": "1.0.0", "hw_ver": "1.0.0"} plug.turn_off = AsyncMock() plug.turn_on = AsyncMock() plug.set_led = AsyncMock() @@ -109,7 +109,7 @@ def _mocked_strip() -> SmartStrip: strip.is_strip = True strip.is_plug = True strip.device_id = MAC_ADDRESS - strip.hw_info = {"sw_ver": "1.0.0"} + strip.hw_info = {"sw_ver": "1.0.0", "hw_ver": "1.0.0"} strip.turn_off = AsyncMock() strip.turn_on = AsyncMock() strip.set_led = AsyncMock() From f820806e3cb599e3cf895cba5449f655fb2323f0 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 6 Feb 2022 14:38:26 -0800 Subject: [PATCH 0371/1098] Remove duplicate methods from Frtiz (#65956) --- homeassistant/components/fritz/common.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index 8786a09b215ccf..79acc54b9b2116 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -593,13 +593,6 @@ async def async_get_wan_link_properties(self) -> dict[str, Any]: partial(self.get_wan_link_properties) ) - async def async_get_wan_link_properties(self) -> dict[str, Any]: - """Call WANCommonInterfaceConfig service.""" - - return await self.hass.async_add_executor_job( - partial(self.get_wan_link_properties) - ) - async def async_get_port_mapping(self, con_type: str, index: int) -> dict[str, Any]: """Call GetGenericPortMappingEntry action.""" @@ -704,13 +697,6 @@ def get_wan_link_properties(self) -> dict[str, Any]: "WANCommonInterfaceConfig", "1", "GetCommonLinkProperties" ) - def get_wan_link_properties(self) -> dict[str, Any]: - """Call WANCommonInterfaceConfig service.""" - - return self._service_call_action( - "WANCommonInterfaceConfig", "1", "GetCommonLinkProperties" - ) - def set_wlan_configuration(self, index: int, turn_on: bool) -> dict[str, Any]: """Call SetEnable action from WLANConfiguration service.""" From 4cd00a1a6fc660043e7b850e8cf18eee73047e0a Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sun, 6 Feb 2022 23:41:02 +0100 Subject: [PATCH 0372/1098] remove EntityCategory from home_mode switch (#65949) --- homeassistant/components/synology_dsm/const.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/synology_dsm/const.py b/homeassistant/components/synology_dsm/const.py index 55e83dc52bf5fc..37100f1a75973a 100644 --- a/homeassistant/components/synology_dsm/const.py +++ b/homeassistant/components/synology_dsm/const.py @@ -382,6 +382,5 @@ class SynologyDSMSwitchEntityDescription( key="home_mode", name="Home Mode", icon="mdi:home-account", - entity_category=EntityCategory.CONFIG, ), ) From fd7e2e76e7d17b2811eac0519b4f672e510d4f6e Mon Sep 17 00:00:00 2001 From: Teemu R Date: Sun, 6 Feb 2022 23:50:44 +0100 Subject: [PATCH 0373/1098] Add tplink diagnostics (#65822) --- .../components/tplink/diagnostics.py | 46 +++++++ tests/components/tplink/__init__.py | 28 ++++- tests/components/tplink/consts.py | 116 ------------------ .../tplink-diagnostics-data-bulb-kl130.json | 108 ++++++++++++++++ .../tplink-diagnostics-data-plug-hs110.json | 74 +++++++++++ tests/components/tplink/test_diagnostics.py | 60 +++++++++ 6 files changed, 315 insertions(+), 117 deletions(-) create mode 100644 homeassistant/components/tplink/diagnostics.py delete mode 100644 tests/components/tplink/consts.py create mode 100644 tests/components/tplink/fixtures/tplink-diagnostics-data-bulb-kl130.json create mode 100644 tests/components/tplink/fixtures/tplink-diagnostics-data-plug-hs110.json create mode 100644 tests/components/tplink/test_diagnostics.py diff --git a/homeassistant/components/tplink/diagnostics.py b/homeassistant/components/tplink/diagnostics.py new file mode 100644 index 00000000000000..5771bee5bd3fc5 --- /dev/null +++ b/homeassistant/components/tplink/diagnostics.py @@ -0,0 +1,46 @@ +"""Diagnostics support for TPLink.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import DOMAIN +from .coordinator import TPLinkDataUpdateCoordinator + +TO_REDACT = { + # Entry fields + "unique_id", # based on mac address + # Device identifiers + "alias", + "mac", + "mic_mac", + "host", + "hwId", + "oemId", + "deviceId", + # Device location + "latitude", + "latitude_i", + "longitude", + "longitude_i", + # Cloud connectivity info + "username", +} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + coordinator: TPLinkDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + device = coordinator.device + + data = {} + data[ + "device_last_response" + ] = device._last_update # pylint: disable=protected-access + + return async_redact_data(data, TO_REDACT) diff --git a/tests/components/tplink/__init__.py b/tests/components/tplink/__init__.py index 02b7404201a44a..beeaa21bf27540 100644 --- a/tests/components/tplink/__init__.py +++ b/tests/components/tplink/__init__.py @@ -2,10 +2,16 @@ from unittest.mock import AsyncMock, MagicMock, patch -from kasa import SmartBulb, SmartDimmer, SmartPlug, SmartStrip +from kasa import SmartBulb, SmartDevice, SmartDimmer, SmartPlug, SmartStrip from kasa.exceptions import SmartDeviceException from kasa.protocol import TPLinkSmartHomeProtocol +from homeassistant.components.tplink import CONF_HOST +from homeassistant.components.tplink.const import DOMAIN +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + MODULE = "homeassistant.components.tplink" MODULE_CONFIG_FLOW = "homeassistant.components.tplink.config_flow" IP_ADDRESS = "127.0.0.1" @@ -146,3 +152,23 @@ async def _discover_single(*_): return patch( "homeassistant.components.tplink.Discover.discover_single", new=_discover_single ) + + +async def initialize_config_entry_for_device( + hass: HomeAssistant, dev: SmartDevice +) -> MockConfigEntry: + """Create a mocked configuration entry for the given device. + + Note, the rest of the tests should probably be converted over to use this + instead of repeating the initialization routine for each test separately + """ + config_entry = MockConfigEntry( + title="TP-Link", domain=DOMAIN, unique_id=dev.mac, data={CONF_HOST: dev.host} + ) + config_entry.add_to_hass(hass) + + with _patch_discovery(device=dev), _patch_single_discovery(device=dev): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + return config_entry diff --git a/tests/components/tplink/consts.py b/tests/components/tplink/consts.py deleted file mode 100644 index e579be61df23f6..00000000000000 --- a/tests/components/tplink/consts.py +++ /dev/null @@ -1,116 +0,0 @@ -"""Constants for the TP-Link component tests.""" - -SMARTPLUG_HS110_DATA = { - "sysinfo": { - "sw_ver": "1.0.4 Build 191111 Rel.143500", - "hw_ver": "4.0", - "model": "HS110(EU)", - "deviceId": "4C56447B395BB7A2FAC68C9DFEE2E84163222581", - "oemId": "40F54B43071E9436B6395611E9D91CEA", - "hwId": "A6C77E4FDD238B53D824AC8DA361F043", - "rssi": -24, - "longitude_i": 130793, - "latitude_i": 480582, - "alias": "SmartPlug", - "status": "new", - "mic_type": "IOT.SMARTPLUGSWITCH", - "feature": "TIM:ENE", - "mac": "69:F2:3C:8E:E3:47", - "updating": 0, - "led_off": 0, - "relay_state": 0, - "on_time": 0, - "active_mode": "none", - "icon_hash": "", - "dev_name": "Smart Wi-Fi Plug With Energy Monitoring", - "next_action": {"type": -1}, - "err_code": 0, - }, - "realtime": { - "voltage_mv": 233957, - "current_ma": 21, - "power_mw": 0, - "total_wh": 1793, - "err_code": 0, - }, -} -SMARTPLUG_HS100_DATA = { - "sysinfo": { - "sw_ver": "1.0.4 Build 191111 Rel.143500", - "hw_ver": "4.0", - "model": "HS100(EU)", - "deviceId": "4C56447B395BB7A2FAC68C9DFEE2E84163222581", - "oemId": "40F54B43071E9436B6395611E9D91CEA", - "hwId": "A6C77E4FDD238B53D824AC8DA361F043", - "rssi": -24, - "longitude_i": 130793, - "latitude_i": 480582, - "alias": "SmartPlug", - "status": "new", - "mic_type": "IOT.SMARTPLUGSWITCH", - "feature": "TIM:", - "mac": "A9:F4:3D:A4:E3:47", - "updating": 0, - "led_off": 0, - "relay_state": 0, - "on_time": 0, - "active_mode": "none", - "icon_hash": "", - "dev_name": "Smart Wi-Fi Plug", - "next_action": {"type": -1}, - "err_code": 0, - } -} -SMARTSTRIP_KP303_DATA = { - "sysinfo": { - "sw_ver": "1.0.4 Build 210428 Rel.135415", - "hw_ver": "1.0", - "model": "KP303(AU)", - "deviceId": "03102547AB1A57A4E4AA5B4EFE34C3005726B97D", - "oemId": "1F950FC9BFF278D9D35E046C129D9411", - "hwId": "9E86D4F840D2787D3D7A6523A731BA2C", - "rssi": -74, - "longitude_i": 1158985, - "latitude_i": -319172, - "alias": "TP-LINK_Power Strip_00B1", - "status": "new", - "mic_type": "IOT.SMARTPLUGSWITCH", - "feature": "TIM", - "mac": "D4:DD:D6:95:B0:F9", - "updating": 0, - "led_off": 0, - "children": [ - { - "id": "8006B399B7FE68D4E6991CCCEA239C081DFA913000", - "state": 0, - "alias": "R-Plug 1", - "on_time": 0, - "next_action": {"type": -1}, - }, - { - "id": "8006B399B7FE68D4E6991CCCEA239C081DFA913001", - "state": 1, - "alias": "R-Plug 2", - "on_time": 93835, - "next_action": {"type": -1}, - }, - { - "id": "8006B399B7FE68D4E6991CCCEA239C081DFA913002", - "state": 1, - "alias": "R-Plug 3", - "on_time": 93834, - "next_action": {"type": -1}, - }, - ], - "child_num": 3, - "err_code": 0, - }, - "realtime": { - "voltage_mv": 233957, - "current_ma": 21, - "power_mw": 0, - "total_wh": 1793, - "err_code": 0, - }, - "context": "1", -} diff --git a/tests/components/tplink/fixtures/tplink-diagnostics-data-bulb-kl130.json b/tests/components/tplink/fixtures/tplink-diagnostics-data-bulb-kl130.json new file mode 100644 index 00000000000000..4e3d4f01f20827 --- /dev/null +++ b/tests/components/tplink/fixtures/tplink-diagnostics-data-bulb-kl130.json @@ -0,0 +1,108 @@ +{ + "device_last_response": { + "system": { + "get_sysinfo": { + "sw_ver": "1.8.8 Build 190613 Rel.123436", + "hw_ver": "1.0", + "model": "KL130(EU)", + "description": "Smart Wi-Fi LED Bulb with Color Changing", + "alias": "bedroom light", + "mic_type": "IOT.SMARTBULB", + "dev_state": "normal", + "mic_mac": "aa:bb:cc:dd:ee:ff", + "deviceId": "1234", + "oemId": "1234", + "hwId": "1234", + "is_factory": false, + "disco_ver": "1.0", + "ctrl_protocols": { + "name": "Linkie", + "version": "1.0" + }, + "light_state": { + "on_off": 1, + "mode": "normal", + "hue": 0, + "saturation": 0, + "color_temp": 2500, + "brightness": 58 + }, + "is_dimmable": 1, + "is_color": 1, + "is_variable_color_temp": 1, + "preferred_state": [ + { + "index": 0, + "hue": 0, + "saturation": 0, + "color_temp": 2500, + "brightness": 10 + }, + { + "index": 1, + "hue": 299, + "saturation": 95, + "color_temp": 0, + "brightness": 100 + }, + { + "index": 2, + "hue": 120, + "saturation": 75, + "color_temp": 0, + "brightness": 100 + }, + { + "index": 3, + "hue": 240, + "saturation": 75, + "color_temp": 0, + "brightness": 100 + } + ], + "rssi": -66, + "active_mode": "none", + "heapsize": 334532, + "err_code": 0 + } + }, + "smartlife.iot.common.emeter": { + "get_realtime": { + "power_mw": 6600, + "err_code": 0 + }, + "get_monthstat": { + "month_list": [ + { + "year": 2022, + "month": 1, + "energy_wh": 321 + }, + { + "year": 2022, + "month": 2, + "energy_wh": 321 + } + ], + "err_code": 0 + }, + "get_daystat": { + "day_list": [ + { + "year": 2022, + "month": 2, + "day": 1, + "energy_wh": 123 + }, + { + "year": 2022, + "month": 2, + "day": 2, + "energy_wh": 123 + } + ], + "err_code": 0 + } + } + } +} diff --git a/tests/components/tplink/fixtures/tplink-diagnostics-data-plug-hs110.json b/tests/components/tplink/fixtures/tplink-diagnostics-data-plug-hs110.json new file mode 100644 index 00000000000000..13dd14bbdda4b8 --- /dev/null +++ b/tests/components/tplink/fixtures/tplink-diagnostics-data-plug-hs110.json @@ -0,0 +1,74 @@ +{ + "device_last_response": { + "system": { + "get_sysinfo": { + "sw_ver": "1.0.4 Build 191111 Rel.143500", + "hw_ver": "4.0", + "model": "HS110(EU)", + "deviceId": "1234", + "oemId": "1234", + "hwId": "1234", + "rssi": -57, + "longitude_i": "0.0", + "latitude_i": "0.0", + "alias": "some plug", + "status": "new", + "mic_type": "IOT.SMARTPLUGSWITCH", + "feature": "TIM:ENE", + "mac": "aa:bb:cc:dd:ee:ff", + "updating": 0, + "led_off": 1, + "relay_state": 1, + "on_time": 254454, + "active_mode": "none", + "icon_hash": "", + "dev_name": "Smart Wi-Fi Plug With Energy Monitoring", + "next_action": { + "type": -1 + }, + "err_code": 0 + } + }, + "emeter": { + "get_realtime": { + "voltage_mv": 230118, + "current_ma": 303, + "power_mw": 28825, + "total_wh": 18313, + "err_code": 0 + }, + "get_monthstat": { + "month_list": [ + { + "year": 2022, + "month": 2, + "energy_wh": 321 + }, + { + "year": 2022, + "month": 1, + "energy_wh": 321 + } + ], + "err_code": 0 + }, + "get_daystat": { + "day_list": [ + { + "year": 2022, + "month": 2, + "day": 1, + "energy_wh": 123 + }, + { + "year": 2022, + "month": 2, + "day": 2, + "energy_wh": 123 + } + ], + "err_code": 0 + } + } + } +} diff --git a/tests/components/tplink/test_diagnostics.py b/tests/components/tplink/test_diagnostics.py new file mode 100644 index 00000000000000..d09ea72b70a776 --- /dev/null +++ b/tests/components/tplink/test_diagnostics.py @@ -0,0 +1,60 @@ +"""Tests for the diagnostics data provided by the TP-Link integration.""" +import json + +from aiohttp import ClientSession +from kasa import SmartDevice +import pytest + +from homeassistant.core import HomeAssistant + +from . import _mocked_bulb, _mocked_plug, initialize_config_entry_for_device + +from tests.common import load_fixture +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +@pytest.mark.parametrize( + "mocked_dev,fixture_file,sysinfo_vars", + [ + ( + _mocked_bulb(), + "tplink-diagnostics-data-bulb-kl130.json", + ["mic_mac", "deviceId", "oemId", "hwId", "alias"], + ), + ( + _mocked_plug(), + "tplink-diagnostics-data-plug-hs110.json", + ["mac", "deviceId", "oemId", "hwId", "alias", "longitude_i", "latitude_i"], + ), + ], +) +async def test_diagnostics( + hass: HomeAssistant, + hass_client: ClientSession, + mocked_dev: SmartDevice, + fixture_file: str, + sysinfo_vars: list[str], +): + """Test diagnostics for config entry.""" + diagnostics_data = json.loads(load_fixture(fixture_file, "tplink")) + + mocked_dev._last_update = diagnostics_data["device_last_response"] + + config_entry = await initialize_config_entry_for_device(hass, mocked_dev) + result = await get_diagnostics_for_config_entry(hass, hass_client, config_entry) + + assert isinstance(result, dict) + assert "device_last_response" in result + + # There must be some redactions in place, so the raw data must not match + assert result["device_last_response"] != diagnostics_data["device_last_response"] + + last_response = result["device_last_response"] + + # We should always have sysinfo available + assert "system" in last_response + assert "get_sysinfo" in last_response["system"] + + sysinfo = last_response["system"]["get_sysinfo"] + for var in sysinfo_vars: + assert sysinfo[var] == "**REDACTED**" From 633aad3a60d70b3cbc6b25ffc3add4933e7bb780 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 7 Feb 2022 00:25:22 +0100 Subject: [PATCH 0374/1098] Cycle pip wheel cache on dev version bump [CI] (#65791) --- .github/workflows/ci.yaml | 15 ++++++++------- script/version_bump.py | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5e87fb71e2be21..205468b3e4f775 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -10,8 +10,9 @@ on: pull_request: ~ env: - CACHE_VERSION: 5 + CACHE_VERSION: 7 PIP_CACHE_VERSION: 1 + HA_SHORT_VERSION: 2022.3 DEFAULT_PYTHON: 3.9 PRE_COMMIT_CACHE: ~/.cache/pre-commit PIP_CACHE: /tmp/pip-cache @@ -155,8 +156,8 @@ jobs: - name: Generate partial pip restore key id: generate-pip-key run: >- - echo "::set-output name=key::base-pip-${{ env.PIP_CACHE_VERSION }}-$( - date -u '+%Y-%m-%dT%H:%M:%s')" + echo "::set-output name=key::base-pip-${{ env.PIP_CACHE_VERSION }}-${{ + env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" - name: Restore base Python virtual environment id: cache-venv uses: actions/cache@v2.1.7 @@ -183,7 +184,7 @@ jobs: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ steps.generate-pip-key.outputs.key }} restore-keys: | - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-pip-${{ env.PIP_CACHE_VERSION }}- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-pip-${{ env.PIP_CACHE_VERSION }}-${{ env.HA_SHORT_VERSION }}- - name: Create Python virtual environment if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -543,8 +544,8 @@ jobs: - name: Generate partial pip restore key id: generate-pip-key run: >- - echo "::set-output name=key::pip-${{ env.PIP_CACHE_VERSION }}-$( - date -u '+%Y-%m-%dT%H:%M:%s')" + echo "::set-output name=key::pip-${{ env.PIP_CACHE_VERSION }}-${{ + env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv uses: actions/cache@v2.1.7 @@ -571,7 +572,7 @@ jobs: ${{ runner.os }}-${{ matrix.python-version }}-${{ steps.generate-pip-key.outputs.key }} restore-keys: | - ${{ runner.os }}-${{ matrix.python-version }}-pip-${{ env.PIP_CACHE_VERSION }}- + ${{ runner.os }}-${{ matrix.python-version }}-pip-${{ env.PIP_CACHE_VERSION }}-${{ env.HA_SHORT_VERSION }}- - name: Create full Python ${{ matrix.python-version }} virtual environment if: steps.cache-venv.outputs.cache-hit != 'true' run: | diff --git a/script/version_bump.py b/script/version_bump.py index 6044cdb277c408..7cc27c2e1e7458 100755 --- a/script/version_bump.py +++ b/script/version_bump.py @@ -131,6 +131,23 @@ def write_version_metadata(version: Version) -> None: fp.write(content) +def write_ci_workflow(version: Version) -> None: + """Update ci workflow with new version.""" + with open(".github/workflows/ci.yaml") as fp: + content = fp.read() + + short_version = ".".join(str(version).split(".", maxsplit=2)[:2]) + content = re.sub( + r"(\n\W+HA_SHORT_VERSION: )\d{4}\.\d{1,2}\n", + f"\\g<1>{short_version}\n", + content, + count=1, + ) + + with open(".github/workflows/ci.yaml", "w") as fp: + fp.write(content) + + def main(): """Execute script.""" parser = argparse.ArgumentParser(description="Bump version of Home Assistant") @@ -154,6 +171,7 @@ def main(): write_version(bumped) write_version_metadata(bumped) + write_ci_workflow(bumped) if not arguments.commit: return From 2772437a2b0455c3b5dde92f6af58fb45b7206c5 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 7 Feb 2022 00:14:20 +0000 Subject: [PATCH 0375/1098] [ci skip] Translation update --- .../components/dnsip/translations/it.json | 4 ++- .../components/netgear/translations/ca.json | 2 +- .../components/netgear/translations/de.json | 2 +- .../components/netgear/translations/et.json | 2 +- .../netgear/translations/pt-BR.json | 2 +- .../netgear/translations/zh-Hant.json | 2 +- .../components/powerwall/translations/it.json | 18 ++++++++-- .../tuya/translations/select.de.json | 26 ++++++++++++++ .../tuya/translations/select.et.json | 35 +++++++++++++++++++ .../tuya/translations/select.it.json | 35 +++++++++++++++++++ .../tuya/translations/select.ja.json | 35 +++++++++++++++++++ .../tuya/translations/select.pl.json | 3 ++ .../tuya/translations/select.zh-Hant.json | 35 +++++++++++++++++++ .../tuya/translations/sensor.et.json | 6 ++++ .../tuya/translations/sensor.zh-Hant.json | 6 ++++ .../components/wiz/translations/de.json | 7 +++- .../components/wiz/translations/et.json | 11 +++++- .../components/wiz/translations/it.json | 15 ++++++++ .../components/wiz/translations/ja.json | 9 +++++ .../components/wiz/translations/pl.json | 11 ++++++ .../components/wiz/translations/zh-Hant.json | 11 +++++- 21 files changed, 265 insertions(+), 12 deletions(-) create mode 100644 homeassistant/components/wiz/translations/it.json create mode 100644 homeassistant/components/wiz/translations/pl.json diff --git a/homeassistant/components/dnsip/translations/it.json b/homeassistant/components/dnsip/translations/it.json index 30ca953b2438db..2ed18baa178645 100644 --- a/homeassistant/components/dnsip/translations/it.json +++ b/homeassistant/components/dnsip/translations/it.json @@ -6,7 +6,9 @@ "step": { "user": { "data": { - "hostname": "Il nome host per il quale eseguire la query DNS" + "hostname": "Il nome host per il quale eseguire la query DNS", + "resolver": "Risolutore per la ricerca IPV4", + "resolver_ipv6": "Risolutore per la ricerca IPV6" } } } diff --git a/homeassistant/components/netgear/translations/ca.json b/homeassistant/components/netgear/translations/ca.json index 48de8c99684cca..6d5edbd8e1ffe4 100644 --- a/homeassistant/components/netgear/translations/ca.json +++ b/homeassistant/components/netgear/translations/ca.json @@ -15,7 +15,7 @@ "ssl": "Utilitza un certificat SSL", "username": "Nom d'usuari (opcional)" }, - "description": "Amfitri\u00f3 predeterminat: {host}\nPort predeterminat: {port}\nNom d'usuari predeterminat: {username}", + "description": "Amfitri\u00f3 predeterminat: {host}\nNom d'usuari predeterminat: {username}", "title": "Netgear" } } diff --git a/homeassistant/components/netgear/translations/de.json b/homeassistant/components/netgear/translations/de.json index d1ee1310cadfe1..b742fb36ec5c1d 100644 --- a/homeassistant/components/netgear/translations/de.json +++ b/homeassistant/components/netgear/translations/de.json @@ -15,7 +15,7 @@ "ssl": "Verwendet ein SSL-Zertifikat", "username": "Benutzername (Optional)" }, - "description": "Standardhost: {host}\nStandardport: {port}\nStandardbenutzername: {username}", + "description": "Standardhost: {host}\nStandardbenutzername: {username}", "title": "Netgear" } } diff --git a/homeassistant/components/netgear/translations/et.json b/homeassistant/components/netgear/translations/et.json index ad100c4b83ebc8..dcbaf4bcb2e8be 100644 --- a/homeassistant/components/netgear/translations/et.json +++ b/homeassistant/components/netgear/translations/et.json @@ -15,7 +15,7 @@ "ssl": "Kasutusel on SSL sert", "username": "Kasutajanimi (valikuline)" }, - "description": "Vaikimisi host: {host}\nVaikeport: {port}\nVaikimisi kasutajanimi: {username}", + "description": "Vaikimisi host: {host}\nVaikimisi kasutajanimi: {username}", "title": "Netgear" } } diff --git a/homeassistant/components/netgear/translations/pt-BR.json b/homeassistant/components/netgear/translations/pt-BR.json index 4789dcc042bd9b..66ffd692374a17 100644 --- a/homeassistant/components/netgear/translations/pt-BR.json +++ b/homeassistant/components/netgear/translations/pt-BR.json @@ -15,7 +15,7 @@ "ssl": "Usar um certificado SSL", "username": "Usu\u00e1rio (Opcional)" }, - "description": "Host padr\u00e3o: {host}\nPorta padr\u00e3o: {port}\nUsu\u00e1rio padr\u00e3o: {username}", + "description": "Host padr\u00e3o: {host}\n Nome de usu\u00e1rio padr\u00e3o: {username}", "title": "Netgear" } } diff --git a/homeassistant/components/netgear/translations/zh-Hant.json b/homeassistant/components/netgear/translations/zh-Hant.json index a4978fbb6bc2cc..39b481f85b9e1d 100644 --- a/homeassistant/components/netgear/translations/zh-Hant.json +++ b/homeassistant/components/netgear/translations/zh-Hant.json @@ -15,7 +15,7 @@ "ssl": "\u4f7f\u7528 SSL \u8a8d\u8b49", "username": "\u4f7f\u7528\u8005\u540d\u7a31\uff08\u9078\u9805\uff09" }, - "description": "\u9810\u8a2d\u4e3b\u6a5f\u7aef\uff1a{host}\n\u9810\u8a2d\u901a\u8a0a\u57e0\uff1a{port}\n\u9810\u8a2d\u4f7f\u7528\u8005\u540d\u7a31\uff1a{username}", + "description": "\u9810\u8a2d\u4e3b\u6a5f\u7aef\uff1a{host}\n\u9810\u8a2d\u4f7f\u7528\u8005\u540d\u7a31\uff1a{username}", "title": "Netgear" } } diff --git a/homeassistant/components/powerwall/translations/it.json b/homeassistant/components/powerwall/translations/it.json index 15a76c31f48462..f323efbabff70c 100644 --- a/homeassistant/components/powerwall/translations/it.json +++ b/homeassistant/components/powerwall/translations/it.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "cannot_connect": "Impossibile connettersi", "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" }, "error": { @@ -10,15 +11,26 @@ "unknown": "Errore imprevisto", "wrong_version": "Il tuo powerwall utilizza una versione del software non supportata. Considera l'aggiornamento o la segnalazione di questo problema in modo che possa essere risolto." }, - "flow_title": "{ip_address}", + "flow_title": "{name} ({ip_address})", "step": { + "confirm_discovery": { + "description": "Vuoi configurare {name} ({ip_address})?", + "title": "Connessione al powerwall" + }, + "reauth_confim": { + "data": { + "password": "Password" + }, + "description": "La password di solito \u00e8 costituita dagli ultimi 5 caratteri del numero di serie per il Backup Gateway e pu\u00f2 essere trovata nell'applicazione Tesla o dagli ultimi 5 caratteri della password trovata all'interno della porta per il Backup Gateway 2.", + "title": "Nuova autenticazione powerwall" + }, "user": { "data": { "ip_address": "Indirizzo IP", "password": "Password" }, - "description": "La password di solito \u00e8 costituita dagli ultimi 5 caratteri del numero di serie per il Backup Gateway e pu\u00f2 essere trovata nell'app Tesla; oppure dagli ultimi 5 caratteri della password trovata all'interno della porta per il Backup Gateway 2.", - "title": "Connessione al Powerwall" + "description": "La password di solito \u00e8 costituita dagli ultimi 5 caratteri del numero di serie per il Backup Gateway e pu\u00f2 essere trovata nell'applicazione Tesla o dagli ultimi 5 caratteri della password trovata all'interno della porta per il Backup Gateway 2.", + "title": "Connessione al powerwall" } } } diff --git a/homeassistant/components/tuya/translations/select.de.json b/homeassistant/components/tuya/translations/select.de.json index f49061c90aa44b..d63c92365cbc3b 100644 --- a/homeassistant/components/tuya/translations/select.de.json +++ b/homeassistant/components/tuya/translations/select.de.json @@ -40,6 +40,32 @@ "click": "Dr\u00fccken", "switch": "Schalter" }, + "tuya__humidifier_level": { + "level_1": "Stufe 1", + "level_10": "Stufe 10", + "level_2": "Stufe 2", + "level_3": "Stufe 3", + "level_4": "Stufe 4", + "level_5": "Stufe 5", + "level_6": "Stufe 6", + "level_7": "Stufe 7", + "level_8": "Stufe 8", + "level_9": "Stufe 9" + }, + "tuya__humidifier_moodlighting": { + "1": "Stimmung 1", + "2": "Stimmung 2", + "3": "Stimmung 3", + "4": "Stimmung 4", + "5": "Stimmung 5" + }, + "tuya__humidifier_spray_mode": { + "auto": "Automatisch", + "health": "Gesundheit", + "humidity": "Luftfeuchtigkeit", + "sleep": "Schlafen", + "work": "Arbeit" + }, "tuya__ipc_work_mode": { "0": "Energiesparmodus", "1": "Kontinuierlicher Arbeitsmodus" diff --git a/homeassistant/components/tuya/translations/select.et.json b/homeassistant/components/tuya/translations/select.et.json index 8d52bf30cca4f8..eba76886439a28 100644 --- a/homeassistant/components/tuya/translations/select.et.json +++ b/homeassistant/components/tuya/translations/select.et.json @@ -10,6 +10,15 @@ "1": "V\u00e4ljas", "2": "Sees" }, + "tuya__countdown": { + "1h": "1 tund", + "2h": "2 tundi", + "3h": "3 tundi", + "4h": "4 tundi", + "5h": "5 tundi", + "6h": "6 tundi", + "cancel": "Loobu" + }, "tuya__curtain_mode": { "morning": "Hommik", "night": "\u00d6\u00f6" @@ -31,6 +40,32 @@ "click": "Vajutus", "switch": "L\u00fcliti" }, + "tuya__humidifier_level": { + "level_1": "Tase 1", + "level_10": "Tase 10", + "level_2": "Tase 2", + "level_3": "Tase 3", + "level_4": "Tase 4", + "level_5": "Tase 5", + "level_6": "Tase 6", + "level_7": "Tase 7", + "level_8": "Tase 8", + "level_9": "Tase 9" + }, + "tuya__humidifier_moodlighting": { + "1": "Meeleolu 1", + "2": "Meeleolu 2", + "3": "Meeleolu 3", + "4": "Meeleolu 4", + "5": "Meeleolu 5" + }, + "tuya__humidifier_spray_mode": { + "auto": "Automaatne", + "health": "Tervis", + "humidity": "Niiskus", + "sleep": "Uneaeg", + "work": "T\u00f6\u00f6aeg" + }, "tuya__ipc_work_mode": { "0": "Madala energiatarbega re\u017eiim", "1": "Pidev t\u00f6\u00f6re\u017eiim" diff --git a/homeassistant/components/tuya/translations/select.it.json b/homeassistant/components/tuya/translations/select.it.json index 629e98f698609b..dde91a52d9a7cf 100644 --- a/homeassistant/components/tuya/translations/select.it.json +++ b/homeassistant/components/tuya/translations/select.it.json @@ -10,6 +10,15 @@ "1": "Spento", "2": "Acceso" }, + "tuya__countdown": { + "1h": "1 ora", + "2h": "2 ore", + "3h": "3 ore", + "4h": "4 ore", + "5h": "5 ore", + "6h": "6 ore", + "cancel": "Annulla" + }, "tuya__curtain_mode": { "morning": "Mattina", "night": "Notte" @@ -31,6 +40,32 @@ "click": "Spingere", "switch": "Interruttore" }, + "tuya__humidifier_level": { + "level_1": "Livello 1", + "level_10": "Livello 10", + "level_2": "Livello 2", + "level_3": "Livello 3", + "level_4": "Livello 4", + "level_5": "Livello 5", + "level_6": "Livello 6", + "level_7": "Livello 7", + "level_8": "Livello 8", + "level_9": "Livello 9" + }, + "tuya__humidifier_moodlighting": { + "1": "Umore 1", + "2": "Umore 2", + "3": "Umore 3", + "4": "Umore 4", + "5": "Umore 5" + }, + "tuya__humidifier_spray_mode": { + "auto": "Automatico", + "health": "Salute", + "humidity": "Umidit\u00e0", + "sleep": "Sonno", + "work": "Lavoro" + }, "tuya__ipc_work_mode": { "0": "Modalit\u00e0 a basso consumo", "1": "Modalit\u00e0 di lavoro continua" diff --git a/homeassistant/components/tuya/translations/select.ja.json b/homeassistant/components/tuya/translations/select.ja.json index 89ea2c39090814..5712544feb312b 100644 --- a/homeassistant/components/tuya/translations/select.ja.json +++ b/homeassistant/components/tuya/translations/select.ja.json @@ -10,6 +10,15 @@ "1": "\u30aa\u30d5", "2": "\u30aa\u30f3" }, + "tuya__countdown": { + "1h": "1\u6642\u9593", + "2h": "2\u6642\u9593", + "3h": "3\u6642\u9593", + "4h": "4\u6642\u9593", + "5h": "5\u6642\u9593", + "6h": "6\u6642\u9593", + "cancel": "\u30ad\u30e3\u30f3\u30bb\u30eb" + }, "tuya__curtain_mode": { "morning": "\u671d", "night": "\u591c" @@ -31,6 +40,32 @@ "click": "\u62bc\u3059", "switch": "\u30b9\u30a4\u30c3\u30c1" }, + "tuya__humidifier_level": { + "level_1": "\u30ec\u30d9\u30eb 1", + "level_10": "\u30ec\u30d9\u30eb 10", + "level_2": "\u30ec\u30d9\u30eb 2", + "level_3": "\u30ec\u30d9\u30eb 3", + "level_4": "\u30ec\u30d9\u30eb 4", + "level_5": "\u30ec\u30d9\u30eb 5", + "level_6": "\u30ec\u30d9\u30eb 6", + "level_7": "\u30ec\u30d9\u30eb 7", + "level_8": "\u30ec\u30d9\u30eb 8", + "level_9": "\u30ec\u30d9\u30eb 9" + }, + "tuya__humidifier_moodlighting": { + "1": "\u30e0\u30fc\u30c9 1", + "2": "\u30e0\u30fc\u30c9 2", + "3": "\u30e0\u30fc\u30c9 3", + "4": "\u30e0\u30fc\u30c9 4", + "5": "\u30e0\u30fc\u30c9 5" + }, + "tuya__humidifier_spray_mode": { + "auto": "\u30aa\u30fc\u30c8", + "health": "\u30d8\u30eb\u30b9", + "humidity": "\u6e7f\u5ea6", + "sleep": "\u30b9\u30ea\u30fc\u30d7", + "work": "\u30ef\u30fc\u30af" + }, "tuya__ipc_work_mode": { "0": "\u4f4e\u96fb\u529b\u30e2\u30fc\u30c9", "1": "\u9023\u7d9a\u4f5c\u696d\u30e2\u30fc\u30c9" diff --git a/homeassistant/components/tuya/translations/select.pl.json b/homeassistant/components/tuya/translations/select.pl.json index 832b86e2a7ad8e..be98268cf540df 100644 --- a/homeassistant/components/tuya/translations/select.pl.json +++ b/homeassistant/components/tuya/translations/select.pl.json @@ -18,6 +18,9 @@ "click": "Naci\u015bni\u0119cie", "switch": "Prze\u0142\u0105cznik" }, + "tuya__humidifier_spray_mode": { + "humidity": "Wilgotno\u015b\u0107" + }, "tuya__ipc_work_mode": { "0": "Tryb niskiego poboru mocy", "1": "Tryb pracy ci\u0105g\u0142ej" diff --git a/homeassistant/components/tuya/translations/select.zh-Hant.json b/homeassistant/components/tuya/translations/select.zh-Hant.json index 71ce79519ed80c..ae835e6db38191 100644 --- a/homeassistant/components/tuya/translations/select.zh-Hant.json +++ b/homeassistant/components/tuya/translations/select.zh-Hant.json @@ -10,6 +10,15 @@ "1": "\u95dc\u9589", "2": "\u958b\u555f" }, + "tuya__countdown": { + "1h": "1 \u5c0f\u6642", + "2h": "2 \u5c0f\u6642", + "3h": "3 \u5c0f\u6642", + "4h": "4 \u5c0f\u6642", + "5h": "5 \u5c0f\u6642", + "6h": "6 \u5c0f\u6642", + "cancel": "\u53d6\u6d88" + }, "tuya__curtain_mode": { "morning": "\u65e9\u6668", "night": "\u591c\u9593" @@ -31,6 +40,32 @@ "click": "\u63a8", "switch": "\u958b\u95dc" }, + "tuya__humidifier_level": { + "level_1": "\u7b49\u7d1a 1", + "level_10": "\u7b49\u7d1a 10", + "level_2": "\u7b49\u7d1a 2", + "level_3": "\u7b49\u7d1a 3", + "level_4": "\u7b49\u7d1a 4", + "level_5": "\u7b49\u7d1a 5", + "level_6": "\u7b49\u7d1a 6", + "level_7": "\u7b49\u7d1a 7", + "level_8": "\u7b49\u7d1a 8", + "level_9": "\u7b49\u7d1a 9" + }, + "tuya__humidifier_moodlighting": { + "1": "\u5fc3\u60c5\u60c5\u5883 1", + "2": "\u5fc3\u60c5\u60c5\u5883 2", + "3": "\u5fc3\u60c5\u60c5\u5883 3", + "4": "\u5fc3\u60c5\u60c5\u5883 4", + "5": "\u5fc3\u60c5\u60c5\u5883 5" + }, + "tuya__humidifier_spray_mode": { + "auto": "\u81ea\u52d5", + "health": "\u5065\u5eb7", + "humidity": "\u6fd5\u5ea6", + "sleep": "\u7761\u7720", + "work": "\u5de5\u4f5c" + }, "tuya__ipc_work_mode": { "0": "\u4f4e\u529f\u8017\u6a21\u5f0f", "1": "\u6301\u7e8c\u5de5\u4f5c\u6a21\u5f0f" diff --git a/homeassistant/components/tuya/translations/sensor.et.json b/homeassistant/components/tuya/translations/sensor.et.json index 7e59f93c77ecaa..a5588a7f0d515d 100644 --- a/homeassistant/components/tuya/translations/sensor.et.json +++ b/homeassistant/components/tuya/translations/sensor.et.json @@ -1,5 +1,11 @@ { "state": { + "tuya__air_quality": { + "good": "Hea", + "great": "Suurep\u00e4rane", + "mild": "Talutav", + "severe": "Ohtlik" + }, "tuya__status": { "boiling_temp": "Keemistemperatuur", "cooling": "Jahutamine", diff --git a/homeassistant/components/tuya/translations/sensor.zh-Hant.json b/homeassistant/components/tuya/translations/sensor.zh-Hant.json index 1fd1c2b4d9807a..d71fbe849e00af 100644 --- a/homeassistant/components/tuya/translations/sensor.zh-Hant.json +++ b/homeassistant/components/tuya/translations/sensor.zh-Hant.json @@ -1,5 +1,11 @@ { "state": { + "tuya__air_quality": { + "good": "\u826f\u597d", + "great": "\u6975\u4f73", + "mild": "\u8f15\u5fae", + "severe": "\u56b4\u91cd" + }, "tuya__status": { "boiling_temp": "\u6cb8\u9a30\u6eab\u5ea6", "cooling": "\u51b7\u6c23", diff --git a/homeassistant/components/wiz/translations/de.json b/homeassistant/components/wiz/translations/de.json index 829a2b0a04810f..d1abe4aa1be9c6 100644 --- a/homeassistant/components/wiz/translations/de.json +++ b/homeassistant/components/wiz/translations/de.json @@ -18,12 +18,17 @@ "discovery_confirm": { "description": "M\u00f6chtest du {name} ({host}) einrichten?" }, + "pick_device": { + "data": { + "device": "Ger\u00e4t" + } + }, "user": { "data": { "host": "Host", "name": "Name" }, - "description": "Gib die IP-Adresse des Ger\u00e4ts ein." + "description": "Wenn du den Host leer l\u00e4sst, wird die Erkennung verwendet, um Ger\u00e4te zu finden." } } } diff --git a/homeassistant/components/wiz/translations/et.json b/homeassistant/components/wiz/translations/et.json index 9de0612b2f71d2..c31366f17d367f 100644 --- a/homeassistant/components/wiz/translations/et.json +++ b/homeassistant/components/wiz/translations/et.json @@ -10,16 +10,25 @@ "no_wiz_light": "Pirni ei saa \u00fchendada WiZ Platvormi sidumise kaudu.", "unknown": "Ootamatu t\u00f5rge" }, + "flow_title": "{name} ({host})", "step": { "confirm": { "description": "Kas alustan seadistamist?" }, + "discovery_confirm": { + "description": "Kas soovid seadistada {name}({host})?" + }, + "pick_device": { + "data": { + "device": "Seade" + } + }, "user": { "data": { "host": "Host", "name": "Nimi" }, - "description": "Uue pirni lisamiseks sisesta hostnimi v\u00f5i IP-aadress ja nimi:" + "description": "Kui j\u00e4tad hosti t\u00fchjaks kasutatakse seadmete leidmiseks avastamist." } } } diff --git a/homeassistant/components/wiz/translations/it.json b/homeassistant/components/wiz/translations/it.json new file mode 100644 index 00000000000000..4ede47e2a3258c --- /dev/null +++ b/homeassistant/components/wiz/translations/it.json @@ -0,0 +1,15 @@ +{ + "config": { + "flow_title": "{name} ({host})", + "step": { + "discovery_confirm": { + "description": "Vuoi configurare {name} ({host})?" + }, + "pick_device": { + "data": { + "device": "Dispositivo" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/ja.json b/homeassistant/components/wiz/translations/ja.json index e417002cb90955..2614230087b2e9 100644 --- a/homeassistant/components/wiz/translations/ja.json +++ b/homeassistant/components/wiz/translations/ja.json @@ -10,10 +10,19 @@ "no_wiz_light": "\u3053\u306e\u96fb\u7403\u306f\u3001WiZ Platform\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u4ecb\u3057\u3066\u63a5\u7d9a\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002", "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, + "flow_title": "{name} ({host})", "step": { "confirm": { "description": "\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3092\u958b\u59cb\u3057\u307e\u3059\u304b\uff1f" }, + "discovery_confirm": { + "description": "{name} ({host})\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u304b\uff1f" + }, + "pick_device": { + "data": { + "device": "\u30c7\u30d0\u30a4\u30b9" + } + }, "user": { "data": { "host": "\u30db\u30b9\u30c8", diff --git a/homeassistant/components/wiz/translations/pl.json b/homeassistant/components/wiz/translations/pl.json new file mode 100644 index 00000000000000..6ec2488368be97 --- /dev/null +++ b/homeassistant/components/wiz/translations/pl.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "pick_device": { + "data": { + "device": "Urz\u0105dzenie" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/zh-Hant.json b/homeassistant/components/wiz/translations/zh-Hant.json index df4c08995f3a06..fed43b2d96fcdc 100644 --- a/homeassistant/components/wiz/translations/zh-Hant.json +++ b/homeassistant/components/wiz/translations/zh-Hant.json @@ -10,16 +10,25 @@ "no_wiz_light": "\u71c8\u6ce1\u7121\u6cd5\u900f\u904e WiZ \u5e73\u53f0\u6574\u5408\u9023\u63a5\u3002", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, + "flow_title": "{name} ({host})", "step": { "confirm": { "description": "\u662f\u5426\u8981\u958b\u59cb\u8a2d\u5b9a\uff1f" }, + "discovery_confirm": { + "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name} ({host})\uff1f" + }, + "pick_device": { + "data": { + "device": "\u88dd\u7f6e" + } + }, "user": { "data": { "host": "\u4e3b\u6a5f\u7aef", "name": "\u540d\u7a31" }, - "description": "\u8acb\u8f38\u5165\u4e3b\u6a5f\u540d\u7a31\u6216 IP \u4f4d\u5740\u4ee5\u65b0\u589e\u71c8\u6ce1\uff1a" + "description": "\u5047\u5982\u4e3b\u6a5f\u7aef\u4f4d\u5740\u6b04\u4f4d\u70ba\u7a7a\u767d\uff0c\u5c07\u6703\u63a2\u7d22\u6240\u6709\u53ef\u7528\u88dd\u7f6e\u3002" } } } From 0321f208ff1e85a21aef527442486e0f1f080e7b Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Sun, 6 Feb 2022 19:13:01 -0600 Subject: [PATCH 0376/1098] Address late review from #65814 for roku (#65967) --- homeassistant/components/roku/__init__.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/roku/__init__.py b/homeassistant/components/roku/__init__.py index ac7a9e385654f1..e76921b945b67d 100644 --- a/homeassistant/components/roku/__init__.py +++ b/homeassistant/components/roku/__init__.py @@ -1,10 +1,13 @@ """Support for Roku.""" from __future__ import annotations -from collections.abc import Callable +from collections.abc import Awaitable, Callable, Coroutine +from functools import wraps import logging +from typing import Any, TypeVar from rokuecp import RokuConnectionError, RokuError +from typing_extensions import Concatenate, ParamSpec from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, Platform @@ -13,6 +16,7 @@ from .const import DOMAIN from .coordinator import RokuDataUpdateCoordinator +from .entity import RokuEntity CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False) @@ -24,6 +28,9 @@ ] _LOGGER = logging.getLogger(__name__) +_T = TypeVar("_T", bound="RokuEntity") +_P = ParamSpec("_P") + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Roku from a config entry.""" @@ -47,10 +54,13 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -def roku_exception_handler(func: Callable) -> Callable: +def roku_exception_handler( + func: Callable[Concatenate[_T, _P], Awaitable[None]] # type: ignore[misc] +) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, None]]: # type: ignore[misc] """Decorate Roku calls to handle Roku exceptions.""" - async def handler(self, *args, **kwargs) -> None: # type: ignore + @wraps(func) + async def wrapper(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> None: try: await func(self, *args, **kwargs) except RokuConnectionError as error: @@ -60,4 +70,4 @@ async def handler(self, *args, **kwargs) -> None: # type: ignore if self.available: _LOGGER.error("Invalid response from API: %s", error) - return handler + return wrapper From 9e0926f9438eb729f93b707539ba353d6331f40e Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Mon, 7 Feb 2022 08:19:32 +0100 Subject: [PATCH 0377/1098] Remove LIFX devices with no entities (#65964) --- homeassistant/components/lifx/light.py | 33 ++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/lifx/light.py b/homeassistant/components/lifx/light.py index f921dabdf4a2db..a7c52424d979c8 100644 --- a/homeassistant/components/lifx/light.py +++ b/homeassistant/components/lifx/light.py @@ -51,6 +51,7 @@ import homeassistant.helpers.device_registry as dr from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback +import homeassistant.helpers.entity_registry as er from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType import homeassistant.util.color as color_util @@ -199,9 +200,12 @@ async def async_setup_entry( interfaces = [{}] platform = entity_platform.async_get_current_platform() - lifx_manager = LIFXManager(hass, platform, async_add_entities) + lifx_manager = LIFXManager(hass, platform, config_entry, async_add_entities) hass.data[DATA_LIFX_MANAGER] = lifx_manager + # This is to clean up old litter. Can be removed in Home Assistant 2022.5. + await lifx_manager.remove_empty_devices() + for interface in interfaces: lifx_manager.start_discovery(interface) @@ -254,17 +258,21 @@ def merge_hsbk(base, change): class LIFXManager: """Representation of all known LIFX entities.""" - def __init__(self, hass, platform, async_add_entities): + def __init__(self, hass, platform, config_entry, async_add_entities): """Initialize the light.""" self.entities = {} self.hass = hass self.platform = platform + self.config_entry = config_entry self.async_add_entities = async_add_entities self.effects_conductor = aiolifx_effects().Conductor(hass.loop) self.discoveries = [] self.cleanup_unsub = self.hass.bus.async_listen( EVENT_HOMEASSISTANT_STOP, self.cleanup ) + self.entity_registry_updated_unsub = self.hass.bus.async_listen( + er.EVENT_ENTITY_REGISTRY_UPDATED, self.entity_registry_updated + ) self.register_set_state() self.register_effects() @@ -289,6 +297,7 @@ def start_discovery(self, interface): def cleanup(self, event=None): """Release resources.""" self.cleanup_unsub() + self.entity_registry_updated_unsub() for discovery in self.discoveries: discovery.cleanup() @@ -424,6 +433,26 @@ def unregister(self, bulb): entity.registered = False entity.async_write_ha_state() + async def entity_registry_updated(self, event): + """Handle entity registry updated.""" + if event.data["action"] == "remove": + await self.remove_empty_devices() + + async def remove_empty_devices(self): + """Remove devices with no entities.""" + entity_reg = await er.async_get_registry(self.hass) + device_reg = dr.async_get(self.hass) + device_list = dr.async_entries_for_config_entry( + device_reg, self.config_entry.entry_id + ) + for device_entry in device_list: + if not er.async_entries_for_device( + entity_reg, + device_entry.id, + include_disabled_entities=True, + ): + device_reg.async_remove_device(device_entry.id) + class AwaitAioLIFX: """Wait for an aiolifx callback and return the message.""" From f5bdbb7727f227751532bec65c990b99b6efa3dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Feb 2022 08:50:27 +0100 Subject: [PATCH 0378/1098] Bump actions/setup-python from 2.3.1 to 2.3.2 (#65974) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/builder.yml | 6 +++--- .github/workflows/ci.yaml | 12 ++++++------ .github/workflows/translations.yaml | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 191e8fa97e9b9f..37e220521677a2 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -29,7 +29,7 @@ jobs: fetch-depth: 0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -69,7 +69,7 @@ jobs: uses: actions/checkout@v2.4.0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -103,7 +103,7 @@ jobs: - name: Set up Python ${{ env.DEFAULT_PYTHON }} if: needs.init.outputs.channel == 'dev' - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ env.DEFAULT_PYTHON }} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 205468b3e4f775..f4563b18bbff67 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -143,7 +143,7 @@ jobs: uses: actions/checkout@v2.4.0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Generate partial Python venv restore key @@ -223,7 +223,7 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@v2.4.0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -273,7 +273,7 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@v2.4.0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -324,7 +324,7 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@v2.4.0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -366,7 +366,7 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@v2.4.0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -500,7 +500,7 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@v2.4.0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} diff --git a/.github/workflows/translations.yaml b/.github/workflows/translations.yaml index e9badfc0479fe6..1e637b02b1a4ab 100644 --- a/.github/workflows/translations.yaml +++ b/.github/workflows/translations.yaml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v2.4.0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -43,7 +43,7 @@ jobs: uses: actions/checkout@v2.4.0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ env.DEFAULT_PYTHON }} From 211db79f116fa2b5630b0758acf482f8a20c51ef Mon Sep 17 00:00:00 2001 From: jjlawren Date: Mon, 7 Feb 2022 01:56:24 -0600 Subject: [PATCH 0379/1098] Bump plexapi to 4.9.2 (#65972) --- homeassistant/components/plex/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/plex/manifest.json b/homeassistant/components/plex/manifest.json index a3331b5c99152a..238c25ad917c97 100644 --- a/homeassistant/components/plex/manifest.json +++ b/homeassistant/components/plex/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/plex", "requirements": [ - "plexapi==4.9.1", + "plexapi==4.9.2", "plexauth==0.0.6", "plexwebsocket==0.0.13" ], diff --git a/requirements_all.txt b/requirements_all.txt index d33b08c78ce60c..67dad92c5d0cec 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1252,7 +1252,7 @@ pillow==9.0.1 pizzapi==0.0.3 # homeassistant.components.plex -plexapi==4.9.1 +plexapi==4.9.2 # homeassistant.components.plex plexauth==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e62662b7d790f6..029138806b5ced 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -774,7 +774,7 @@ pilight==0.1.1 pillow==9.0.1 # homeassistant.components.plex -plexapi==4.9.1 +plexapi==4.9.2 # homeassistant.components.plex plexauth==0.0.6 From 18ac72f613aeeee397b92b7854312a86298ab9e2 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Mon, 7 Feb 2022 02:03:19 -0600 Subject: [PATCH 0380/1098] Reduce coordinator cooldown for roku (#65973) --- homeassistant/components/roku/coordinator.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/homeassistant/components/roku/coordinator.py b/homeassistant/components/roku/coordinator.py index 5b0d76349963d5..f084302841eed5 100644 --- a/homeassistant/components/roku/coordinator.py +++ b/homeassistant/components/roku/coordinator.py @@ -9,11 +9,14 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.util.dt import utcnow from .const import DOMAIN +REQUEST_REFRESH_DELAY = 0.35 + SCAN_INTERVAL = timedelta(seconds=10) _LOGGER = logging.getLogger(__name__) @@ -41,6 +44,11 @@ def __init__( _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL, + # We don't want an immediate refresh since the device + # takes a moment to reflect the state change + request_refresh_debouncer=Debouncer( + hass, _LOGGER, cooldown=REQUEST_REFRESH_DELAY, immediate=False + ), ) async def _async_update_data(self) -> Device: From f05caf451ec148726e65cebaf42bf26e0acf5b10 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Mon, 7 Feb 2022 02:53:23 -0600 Subject: [PATCH 0381/1098] Small cleanup of sonarr sensor platform (#65962) --- homeassistant/components/sonarr/sensor.py | 30 +++++++++++++++++------ tests/components/sonarr/test_sensor.py | 6 +++++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/sonarr/sensor.py b/homeassistant/components/sonarr/sensor.py index 91e1eeb325727f..01046ded7c2ad6 100644 --- a/homeassistant/components/sonarr/sensor.py +++ b/homeassistant/components/sonarr/sensor.py @@ -1,11 +1,14 @@ """Support for Sonarr sensors.""" from __future__ import annotations +from collections.abc import Awaitable, Callable, Coroutine from datetime import timedelta +from functools import wraps import logging -from typing import Any +from typing import Any, TypeVar from sonarr import Sonarr, SonarrConnectionError, SonarrError +from typing_extensions import Concatenate, ParamSpec from homeassistant.components.sensor import SensorEntity, SensorEntityDescription from homeassistant.config_entries import ConfigEntry @@ -64,6 +67,9 @@ ), ) +_T = TypeVar("_T", bound="SonarrSensor") +_P = ParamSpec("_P") + async def async_setup_entry( hass: HomeAssistant, @@ -82,14 +88,17 @@ async def async_setup_entry( async_add_entities(entities, True) -def sonarr_exception_handler(func): +def sonarr_exception_handler( + func: Callable[Concatenate[_T, _P], Awaitable[None]] # type: ignore[misc] +) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, None]]: # type: ignore[misc] """Decorate Sonarr calls to handle Sonarr exceptions. A decorator that wraps the passed in function, catches Sonarr errors, and handles the availability of the entity. """ - async def handler(self, *args, **kwargs): + @wraps(func) + async def wrapper(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> None: try: await func(self, *args, **kwargs) self.last_update_success = True @@ -102,12 +111,17 @@ async def handler(self, *args, **kwargs): _LOGGER.error("Invalid response from API: %s", error) self.last_update_success = False - return handler + return wrapper class SonarrSensor(SonarrEntity, SensorEntity): """Implementation of the Sonarr sensor.""" + data: dict[str, Any] + last_update_success: bool + upcoming_days: int + wanted_max_items: int + def __init__( self, sonarr: Sonarr, @@ -119,10 +133,10 @@ def __init__( self.entity_description = description self._attr_unique_id = f"{entry_id}_{description.key}" - self.data: dict[str, Any] = {} - self.last_update_success: bool = False - self.upcoming_days: int = options[CONF_UPCOMING_DAYS] - self.wanted_max_items: int = options[CONF_WANTED_MAX_ITEMS] + self.data = {} + self.last_update_success = True + self.upcoming_days = options[CONF_UPCOMING_DAYS] + self.wanted_max_items = options[CONF_WANTED_MAX_ITEMS] super().__init__( sonarr=sonarr, diff --git a/tests/components/sonarr/test_sensor.py b/tests/components/sonarr/test_sensor.py index 8acf7d5b2c8293..cc15376efb1e26 100644 --- a/tests/components/sonarr/test_sensor.py +++ b/tests/components/sonarr/test_sensor.py @@ -68,30 +68,36 @@ async def test_sensors( assert state assert state.attributes.get(ATTR_ICON) == "mdi:harddisk" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == DATA_GIGABYTES + assert state.attributes.get("C:\\") == "263.10/465.42GB (56.53%)" assert state.state == "263.10" state = hass.states.get("sensor.sonarr_queue") assert state assert state.attributes.get(ATTR_ICON) == "mdi:download" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "Episodes" + assert state.attributes.get("The Andy Griffith Show S01E01") == "100.00%" assert state.state == "1" state = hass.states.get("sensor.sonarr_shows") assert state assert state.attributes.get(ATTR_ICON) == "mdi:television" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "Series" + assert state.attributes.get("The Andy Griffith Show") == "0/0 Episodes" assert state.state == "1" state = hass.states.get("sensor.sonarr_upcoming") assert state assert state.attributes.get(ATTR_ICON) == "mdi:television" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "Episodes" + assert state.attributes.get("Bob's Burgers") == "S04E11" assert state.state == "1" state = hass.states.get("sensor.sonarr_wanted") assert state assert state.attributes.get(ATTR_ICON) == "mdi:television" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "Episodes" + assert state.attributes.get("Bob's Burgers S04E11") == "2014-01-26" + assert state.attributes.get("The Andy Griffith Show S01E01") == "1960-10-03" assert state.state == "2" From bb762d5b0f13781385c432c7e93c7593332a0823 Mon Sep 17 00:00:00 2001 From: rhpijnacker Date: Mon, 7 Feb 2022 10:48:33 +0100 Subject: [PATCH 0382/1098] 100% code coverage for config_flow of dsmr component (#65238) --- homeassistant/components/dsmr/config_flow.py | 30 ------- tests/components/dsmr/test_config_flow.py | 82 ++++++++++++++++++++ 2 files changed, 82 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/dsmr/config_flow.py b/homeassistant/components/dsmr/config_flow.py index e4c7ac4e658caf..8bbf6197a10088 100644 --- a/homeassistant/components/dsmr/config_flow.py +++ b/homeassistant/components/dsmr/config_flow.py @@ -147,36 +147,6 @@ def async_get_options_flow(config_entry: ConfigEntry) -> DSMROptionFlowHandler: """Get the options flow for this handler.""" return DSMROptionFlowHandler(config_entry) - def _abort_if_host_port_configured( - self, - port: str, - host: str | None = None, - updates: dict[Any, Any] | None = None, - reload_on_update: bool = True, - ) -> FlowResult | None: - """Test if host and port are already configured.""" - for entry in self._async_current_entries(): - if entry.data.get(CONF_HOST) == host and entry.data[CONF_PORT] == port: - if updates is not None: - changed = self.hass.config_entries.async_update_entry( - entry, data={**entry.data, **updates} - ) - if ( - changed - and reload_on_update - and entry.state - in ( - config_entries.ConfigEntryState.LOADED, - config_entries.ConfigEntryState.SETUP_RETRY, - ) - ): - self.hass.async_create_task( - self.hass.config_entries.async_reload(entry.entry_id) - ) - return self.async_abort(reason="already_configured") - - return None - async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: diff --git a/tests/components/dsmr/test_config_flow.py b/tests/components/dsmr/test_config_flow.py index 692870d70374f9..669fcfac386b59 100644 --- a/tests/components/dsmr/test_config_flow.py +++ b/tests/components/dsmr/test_config_flow.py @@ -1,4 +1,5 @@ """Test the DSMR config flow.""" +import asyncio from itertools import chain, repeat import os from unittest.mock import DEFAULT, AsyncMock, MagicMock, patch, sentinel @@ -138,6 +139,45 @@ async def test_setup_5L(com_mock, hass, dsmr_connection_send_validate_fixture): assert result["data"] == entry_data +@patch("serial.tools.list_ports.comports", return_value=[com_port()]) +async def test_setup_5S(com_mock, hass, dsmr_connection_send_validate_fixture): + """Test we can setup serial.""" + port = com_port() + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] is None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"type": "Serial"}, + ) + + assert result["type"] == "form" + assert result["step_id"] == "setup_serial" + assert result["errors"] == {} + + with patch("homeassistant.components.dsmr.async_setup_entry", return_value=True): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"port": port.device, "dsmr_version": "5S"} + ) + + entry_data = { + "port": port.device, + "dsmr_version": "5S", + "serial_id": None, + "serial_id_gas": None, + } + + assert result["type"] == "create_entry" + assert result["title"] == port.device + assert result["data"] == entry_data + + @patch("serial.tools.list_ports.comports", return_value=[com_port()]) async def test_setup_Q3D(com_mock, hass, dsmr_connection_send_validate_fixture): """Test we can setup serial.""" @@ -265,6 +305,48 @@ async def test_setup_serial_fail(com_mock, hass, dsmr_connection_send_validate_f assert result["errors"] == {"base": "cannot_connect"} +@patch("serial.tools.list_ports.comports", return_value=[com_port()]) +async def test_setup_serial_timeout( + com_mock, hass, dsmr_connection_send_validate_fixture +): + """Test failed serial connection.""" + (connection_factory, transport, protocol) = dsmr_connection_send_validate_fixture + + port = com_port() + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + first_timeout_wait_closed = AsyncMock( + return_value=True, + side_effect=chain([asyncio.TimeoutError], repeat(DEFAULT)), + ) + protocol.wait_closed = first_timeout_wait_closed + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] is None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"type": "Serial"}, + ) + + assert result["type"] == "form" + assert result["step_id"] == "setup_serial" + assert result["errors"] == {} + + with patch("homeassistant.components.dsmr.async_setup_entry", return_value=True): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"port": port.device, "dsmr_version": "2.2"} + ) + + assert result["type"] == "form" + assert result["step_id"] == "setup_serial" + assert result["errors"] == {"base": "cannot_communicate"} + + @patch("serial.tools.list_ports.comports", return_value=[com_port()]) async def test_setup_serial_wrong_telegram( com_mock, hass, dsmr_connection_send_validate_fixture From 791b700ac2c783e408f40814b15bdc472f1e5454 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 7 Feb 2022 10:57:42 +0100 Subject: [PATCH 0383/1098] bump motionblinds to 0.5.11 (#65988) --- homeassistant/components/motion_blinds/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/motion_blinds/manifest.json b/homeassistant/components/motion_blinds/manifest.json index 21200789fbcf05..136fde657e5295 100644 --- a/homeassistant/components/motion_blinds/manifest.json +++ b/homeassistant/components/motion_blinds/manifest.json @@ -3,7 +3,7 @@ "name": "Motion Blinds", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/motion_blinds", - "requirements": ["motionblinds==0.5.10"], + "requirements": ["motionblinds==0.5.11"], "dependencies": ["network"], "codeowners": ["@starkillerOG"], "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index 67dad92c5d0cec..e03e8eafc36bf6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1046,7 +1046,7 @@ minio==5.0.10 mitemp_bt==0.0.5 # homeassistant.components.motion_blinds -motionblinds==0.5.10 +motionblinds==0.5.11 # homeassistant.components.motioneye motioneye-client==0.3.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 029138806b5ced..fd769f4cf15d6f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -655,7 +655,7 @@ millheater==0.9.0 minio==5.0.10 # homeassistant.components.motion_blinds -motionblinds==0.5.10 +motionblinds==0.5.11 # homeassistant.components.motioneye motioneye-client==0.3.12 From 03ade194ab2a2fa7ab84d9db0f5e6f15b0017d9a Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 7 Feb 2022 10:58:30 +0100 Subject: [PATCH 0384/1098] Trigger full CI run on pylint amends (#65430) Co-authored-by: epenet --- .core_files.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.core_files.yaml b/.core_files.yaml index 1a220eef7a242f..374a8957bcc7f0 100644 --- a/.core_files.yaml +++ b/.core_files.yaml @@ -101,6 +101,7 @@ components: &components # Testing related files that affect the whole test/linting suite tests: &tests - codecov.yaml + - pylint/* - requirements_test_pre_commit.txt - requirements_test.txt - tests/auth/** @@ -111,6 +112,7 @@ tests: &tests - tests/helpers/* - tests/ignore_uncaught_exceptions.py - tests/mock/* + - tests/pylint/* - tests/scripts/* - tests/test_util/* - tests/testing_config/** From 1ae809293fc0ccdc11d5dc6f1722718434fb4561 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 7 Feb 2022 11:14:48 +0100 Subject: [PATCH 0385/1098] Add support for qjdcz to Tuya (#65985) --- homeassistant/components/tuya/light.py | 11 +++++++++++ homeassistant/components/tuya/switch.py | 9 +++++++++ 2 files changed, 20 insertions(+) diff --git a/homeassistant/components/tuya/light.py b/homeassistant/components/tuya/light.py index 88bd545c0a9fd9..0fb75dbe6a8b83 100644 --- a/homeassistant/components/tuya/light.py +++ b/homeassistant/components/tuya/light.py @@ -181,6 +181,17 @@ class TuyaLightEntityDescription(LightEntityDescription): color_data=DPCode.COLOUR_DATA, ), ), + # Unknown product with light capabilities + # Fond in some diffusers, plugs and PIR flood lights + # Not documented + "qjdcz": ( + TuyaLightEntityDescription( + key=DPCode.SWITCH_LED, + color_mode=DPCode.WORK_MODE, + brightness=DPCode.BRIGHT_VALUE, + color_data=DPCode.COLOUR_DATA, + ), + ), # Heater # https://developer.tuya.com/en/docs/iot/categoryqn?id=Kaiuz18kih0sm "qn": ( diff --git a/homeassistant/components/tuya/switch.py b/homeassistant/components/tuya/switch.py index fc3d8d86bafdd2..6be35e0102b1de 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -290,6 +290,15 @@ device_class=SwitchDeviceClass.OUTLET, ), ), + # Unknown product with switch capabilities + # Fond in some diffusers, plugs and PIR flood lights + # Not documented + "qjdcz": ( + SwitchEntityDescription( + key=DPCode.SWITCH_1, + name="Switch", + ), + ), # Heater # https://developer.tuya.com/en/docs/iot/categoryqn?id=Kaiuz18kih0sm "qn": ( From 38f721300265adfab5a94cef4529855ce480fdbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 7 Feb 2022 11:15:04 +0100 Subject: [PATCH 0386/1098] Add more tests to device registry updates (#65989) --- .../components/config/test_device_registry.py | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/tests/components/config/test_device_registry.py b/tests/components/config/test_device_registry.py index 11b2f663f19b94..f43f9a4d8ce9ae 100644 --- a/tests/components/config/test_device_registry.py +++ b/tests/components/config/test_device_registry.py @@ -80,7 +80,19 @@ async def test_list_devices(hass, client, registry): ] -async def test_update_device(hass, client, registry): +@pytest.mark.parametrize( + "payload_key,payload_value", + [ + ["area_id", "12345A"], + ["area_id", None], + ["disabled_by", helpers_dr.DeviceEntryDisabler.USER], + ["disabled_by", "user"], + ["disabled_by", None], + ["name_by_user", "Test Friendly Name"], + ["name_by_user", None], + ], +) +async def test_update_device(hass, client, registry, payload_key, payload_value): """Test update entry.""" device = registry.async_get_or_create( config_entry_id="1234", @@ -90,24 +102,27 @@ async def test_update_device(hass, client, registry): model="model", ) - assert not device.area_id - assert not device.name_by_user + assert not getattr(device, payload_key) await client.send_json( { "id": 1, - "device_id": device.id, - "area_id": "12345A", - "name_by_user": "Test Friendly Name", - "disabled_by": helpers_dr.DeviceEntryDisabler.USER, "type": "config/device_registry/update", + "device_id": device.id, + payload_key: payload_value, } ) msg = await client.receive_json() - - assert msg["result"]["id"] == device.id - assert msg["result"]["area_id"] == "12345A" - assert msg["result"]["name_by_user"] == "Test Friendly Name" - assert msg["result"]["disabled_by"] == helpers_dr.DeviceEntryDisabler.USER + await hass.async_block_till_done() assert len(registry.devices) == 1 + + device = registry.async_get_device( + identifiers={("bridgeid", "0123")}, + connections={("ethernet", "12:34:56:78:90:AB:CD:EF")}, + ) + + assert msg["result"][payload_key] == payload_value + assert getattr(device, payload_key) == payload_value + + assert isinstance(device.disabled_by, (helpers_dr.DeviceEntryDisabler, type(None))) From bd31cfbd4002f9fdd90755e6d9c01e0c7eec85d6 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 7 Feb 2022 11:15:28 +0100 Subject: [PATCH 0387/1098] Add secondary dimmer to dj in Tuya (#65990) --- homeassistant/components/tuya/light.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homeassistant/components/tuya/light.py b/homeassistant/components/tuya/light.py index 0fb75dbe6a8b83..4c35c850c33ffa 100644 --- a/homeassistant/components/tuya/light.py +++ b/homeassistant/components/tuya/light.py @@ -108,6 +108,13 @@ class TuyaLightEntityDescription(LightEntityDescription): color_temp=(DPCode.TEMP_VALUE_V2, DPCode.TEMP_VALUE), color_data=(DPCode.COLOUR_DATA_V2, DPCode.COLOUR_DATA), ), + # Not documented + # Based on multiple reports: manufacturer customized Dimmer 2 switches + TuyaLightEntityDescription( + key=DPCode.SWITCH_LED_1, + name="Light", + brightness=DPCode.BRIGHT_VALUE_1, + ), ), # Ceiling Fan Light # https://developer.tuya.com/en/docs/iot/fsd?id=Kaof8eiei4c2v From d81139377c51190c5872c5d9429fd567e533ce88 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 7 Feb 2022 12:00:02 +0100 Subject: [PATCH 0388/1098] Add Netgear allow/block switch (#65705) * add allow/block switch * keep api private * typing * change default to None * retain None state * change default to None --- .coveragerc | 1 + homeassistant/components/netgear/const.py | 4 +- homeassistant/components/netgear/router.py | 8 ++ homeassistant/components/netgear/switch.py | 106 +++++++++++++++++++++ 4 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/netgear/switch.py diff --git a/.coveragerc b/.coveragerc index be94c4a9a7f39a..8bc20400dba151 100644 --- a/.coveragerc +++ b/.coveragerc @@ -733,6 +733,7 @@ omit = homeassistant/components/netgear/device_tracker.py homeassistant/components/netgear/router.py homeassistant/components/netgear/sensor.py + homeassistant/components/netgear/switch.py homeassistant/components/netgear_lte/* homeassistant/components/netio/switch.py homeassistant/components/neurio_energy/sensor.py diff --git a/homeassistant/components/netgear/const.py b/homeassistant/components/netgear/const.py index 02b78e164ac6ca..d366ae3996140b 100644 --- a/homeassistant/components/netgear/const.py +++ b/homeassistant/components/netgear/const.py @@ -5,14 +5,14 @@ DOMAIN = "netgear" +PLATFORMS = [Platform.DEVICE_TRACKER, Platform.SENSOR, Platform.SWITCH] + CONF_CONSIDER_HOME = "consider_home" KEY_ROUTER = "router" KEY_COORDINATOR = "coordinator" KEY_COORDINATOR_TRAFFIC = "coordinator_traffic" -PLATFORMS = [Platform.DEVICE_TRACKER, Platform.SENSOR] - DEFAULT_CONSIDER_HOME = timedelta(seconds=180) DEFAULT_NAME = "Netgear router" diff --git a/homeassistant/components/netgear/router.py b/homeassistant/components/netgear/router.py index b358939d122094..722bcb27ae0ae4 100644 --- a/homeassistant/components/netgear/router.py +++ b/homeassistant/components/netgear/router.py @@ -147,6 +147,7 @@ async def async_setup(self) -> bool: "ip": None, "ssid": None, "conn_ap_mac": None, + "allow_or_block": None, } return True @@ -200,6 +201,13 @@ async def async_get_traffic_meter(self) -> None: async with self._api_lock: return await self.hass.async_add_executor_job(self._api.get_traffic_meter) + async def async_allow_block_device(self, mac: str, allow_block: str) -> None: + """Allow or block a device connected to the router.""" + async with self._api_lock: + await self.hass.async_add_executor_job( + self._api.allow_block_device, mac, allow_block + ) + @property def port(self) -> int: """Port used by the API.""" diff --git a/homeassistant/components/netgear/switch.py b/homeassistant/components/netgear/switch.py new file mode 100644 index 00000000000000..cf8cd835f89668 --- /dev/null +++ b/homeassistant/components/netgear/switch.py @@ -0,0 +1,106 @@ +"""Support for Netgear switches.""" +import logging + +from pynetgear import ALLOW, BLOCK + +from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + +from .const import DOMAIN, KEY_COORDINATOR, KEY_ROUTER +from .router import NetgearDeviceEntity, NetgearRouter + +_LOGGER = logging.getLogger(__name__) + + +SWITCH_TYPES = [ + SwitchEntityDescription( + key="allow_or_block", + name="Allowed on network", + icon="mdi:block-helper", + entity_category=EntityCategory.CONFIG, + ) +] + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up switches for Netgear component.""" + router = hass.data[DOMAIN][entry.entry_id][KEY_ROUTER] + coordinator = hass.data[DOMAIN][entry.entry_id][KEY_COORDINATOR] + tracked = set() + + @callback + def new_device_callback() -> None: + """Add new devices if needed.""" + new_entities = [] + if not coordinator.data: + return + + for mac, device in router.devices.items(): + if mac in tracked: + continue + + new_entities.extend( + [ + NetgearAllowBlock(coordinator, router, device, entity_description) + for entity_description in SWITCH_TYPES + ] + ) + tracked.add(mac) + + if new_entities: + async_add_entities(new_entities) + + entry.async_on_unload(coordinator.async_add_listener(new_device_callback)) + + coordinator.data = True + new_device_callback() + + +class NetgearAllowBlock(NetgearDeviceEntity, SwitchEntity): + """Allow or Block a device from the network.""" + + _attr_entity_registry_enabled_default = False + + def __init__( + self, + coordinator: DataUpdateCoordinator, + router: NetgearRouter, + device: dict, + entity_description: SwitchEntityDescription, + ) -> None: + """Initialize a Netgear device.""" + super().__init__(coordinator, router, device) + self.entity_description = entity_description + self._name = f"{self.get_device_name()} {self.entity_description.name}" + self._unique_id = f"{self._mac}-{self.entity_description.key}" + self._state = None + self.async_update_device() + + @property + def is_on(self): + """Return true if switch is on.""" + return self._state + + async def async_turn_on(self, **kwargs): + """Turn the switch on.""" + await self._router.async_allow_block_device(self._mac, ALLOW) + + async def async_turn_off(self, **kwargs): + """Turn the switch off.""" + await self._router.async_allow_block_device(self._mac, BLOCK) + + @callback + def async_update_device(self) -> None: + """Update the Netgear device.""" + self._device = self._router.devices[self._mac] + self._active = self._device["active"] + if self._device[self.entity_description.key] is None: + self._state = None + else: + self._state = self._device[self.entity_description.key] == "Allow" From 486c068111ca9f7ea81e99faa5f1adb35b338af9 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 7 Feb 2022 14:06:40 +0100 Subject: [PATCH 0389/1098] Allow None on Renault binary sensors (#65997) * Enable None on renault binary sensors * Adjust tests Co-authored-by: epenet --- homeassistant/components/renault/binary_sensor.py | 7 +++---- tests/components/renault/test_binary_sensor.py | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/renault/binary_sensor.py b/homeassistant/components/renault/binary_sensor.py index c2ebdb5cb0f50c..ec8ca77bded49f 100644 --- a/homeassistant/components/renault/binary_sensor.py +++ b/homeassistant/components/renault/binary_sensor.py @@ -64,10 +64,9 @@ class RenaultBinarySensor( @property def is_on(self) -> bool | None: """Return true if the binary sensor is on.""" - return ( - self._get_data_attr(self.entity_description.on_key) - == self.entity_description.on_value - ) + if (data := self._get_data_attr(self.entity_description.on_key)) is None: + return None + return data == self.entity_description.on_value BINARY_SENSOR_TYPES: tuple[RenaultBinarySensorEntityDescription, ...] = ( diff --git a/tests/components/renault/test_binary_sensor.py b/tests/components/renault/test_binary_sensor.py index 0a2460edca14a3..feef06746bd20c 100644 --- a/tests/components/renault/test_binary_sensor.py +++ b/tests/components/renault/test_binary_sensor.py @@ -4,7 +4,7 @@ import pytest from homeassistant.config_entries import ConfigEntry -from homeassistant.const import STATE_OFF, Platform +from homeassistant.const import STATE_UNKNOWN, Platform from homeassistant.core import HomeAssistant from . import ( @@ -63,7 +63,7 @@ async def test_binary_sensor_empty( expected_entities = mock_vehicle[Platform.BINARY_SENSOR] assert len(entity_registry.entities) == len(expected_entities) - check_entities_no_data(hass, entity_registry, expected_entities, STATE_OFF) + check_entities_no_data(hass, entity_registry, expected_entities, STATE_UNKNOWN) @pytest.mark.usefixtures("fixtures_with_invalid_upstream_exception") From 78d2fbb40222bf520a7e237f4d2daf70814d3be9 Mon Sep 17 00:00:00 2001 From: Milan Meulemans Date: Mon, 7 Feb 2022 14:49:34 +0100 Subject: [PATCH 0390/1098] Upgrade aionanoleaf to 0.2.0 (#66008) --- homeassistant/components/nanoleaf/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/nanoleaf/manifest.json b/homeassistant/components/nanoleaf/manifest.json index e5ba3a0594142d..604e4daaf35971 100644 --- a/homeassistant/components/nanoleaf/manifest.json +++ b/homeassistant/components/nanoleaf/manifest.json @@ -3,7 +3,7 @@ "name": "Nanoleaf", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/nanoleaf", - "requirements": ["aionanoleaf==0.1.1"], + "requirements": ["aionanoleaf==0.2.0"], "zeroconf": ["_nanoleafms._tcp.local.", "_nanoleafapi._tcp.local."], "homekit" : { "models": [ diff --git a/requirements_all.txt b/requirements_all.txt index e03e8eafc36bf6..8dc8a0f5bfd19e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -224,7 +224,7 @@ aiomodernforms==0.1.8 aiomusiccast==0.14.3 # homeassistant.components.nanoleaf -aionanoleaf==0.1.1 +aionanoleaf==0.2.0 # homeassistant.components.keyboard_remote aionotify==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fd769f4cf15d6f..8bb1d6564e5910 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -162,7 +162,7 @@ aiomodernforms==0.1.8 aiomusiccast==0.14.3 # homeassistant.components.nanoleaf -aionanoleaf==0.1.1 +aionanoleaf==0.2.0 # homeassistant.components.notion aionotion==3.0.2 From da3024e16207e0ef0e00abde0922abcfacb724df Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 7 Feb 2022 15:12:04 +0100 Subject: [PATCH 0391/1098] Upgrade to newer Python pip>=21.0 (#59769) --- .github/workflows/builder.yml | 2 +- .github/workflows/ci.yaml | 10 +++++----- .vscode/tasks.json | 4 ++-- Dockerfile | 6 +++--- Dockerfile.dev | 4 ++-- homeassistant/package_constraints.txt | 2 +- requirements.txt | 2 +- script/bootstrap | 2 +- script/setup | 2 +- setup.cfg | 2 +- tox.ini | 3 ++- 11 files changed, 20 insertions(+), 19 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 37e220521677a2..1ede9c62e16543 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -112,7 +112,7 @@ jobs: shell: bash run: | python3 -m pip install packaging - python3 -m pip install . + python3 -m pip install --use-deprecated=legacy-resolver . python3 script/version_bump.py nightly version="$(python setup.py -V)" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f4563b18bbff67..321cb7f622b2d8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -191,8 +191,8 @@ jobs: python -m venv venv . venv/bin/activate python --version - pip install --cache-dir=$PIP_CACHE -U "pip<20.3" setuptools wheel - pip install --cache-dir=$PIP_CACHE -r requirements.txt -r requirements_test.txt + pip install --cache-dir=$PIP_CACHE -U "pip>=21.0,<22.1" setuptools wheel + pip install --cache-dir=$PIP_CACHE -r requirements.txt -r requirements_test.txt --use-deprecated=legacy-resolver - name: Generate partial pre-commit restore key id: generate-pre-commit-key run: >- @@ -583,9 +583,9 @@ jobs: python -m venv venv . venv/bin/activate python --version - pip install --cache-dir=$PIP_CACHE -U "pip<20.3" setuptools wheel - pip install --cache-dir=$PIP_CACHE -r requirements_all.txt - pip install --cache-dir=$PIP_CACHE -r requirements_test.txt + pip install --cache-dir=$PIP_CACHE -U "pip>=21.0,<22.1" setuptools wheel + pip install --cache-dir=$PIP_CACHE -r requirements_all.txt --use-deprecated=legacy-resolver + pip install --cache-dir=$PIP_CACHE -r requirements_test.txt --use-deprecated=legacy-resolver pip install -e . pylint: diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 3fecfd8ba4801d..d71571d2594db5 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -88,7 +88,7 @@ { "label": "Install all Requirements", "type": "shell", - "command": "pip3 install -r requirements_all.txt", + "command": "pip3 install --use-deprecated=legacy-resolver -r requirements_all.txt", "group": { "kind": "build", "isDefault": true @@ -102,7 +102,7 @@ { "label": "Install all Test Requirements", "type": "shell", - "command": "pip3 install -r requirements_test_all.txt", + "command": "pip3 install --use-deprecated=legacy-resolver -r requirements_test_all.txt", "group": { "kind": "build", "isDefault": true diff --git a/Dockerfile b/Dockerfile index a4d5ce3045dc13..1d6ce675e74da5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,17 +12,17 @@ COPY requirements.txt homeassistant/ COPY homeassistant/package_constraints.txt homeassistant/homeassistant/ RUN \ pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \ - -r homeassistant/requirements.txt + -r homeassistant/requirements.txt --use-deprecated=legacy-resolver COPY requirements_all.txt homeassistant/ RUN \ pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \ - -r homeassistant/requirements_all.txt + -r homeassistant/requirements_all.txt --use-deprecated=legacy-resolver ## Setup Home Assistant Core COPY . homeassistant/ RUN \ pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \ - -e ./homeassistant \ + -e ./homeassistant --use-deprecated=legacy-resolver \ && python3 -m compileall homeassistant/homeassistant # Fix Bug with Alpine 3.14 and sqlite 3.35 diff --git a/Dockerfile.dev b/Dockerfile.dev index b908bf01a32ce9..39ce36074ad4b1 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -33,9 +33,9 @@ WORKDIR /workspaces # Install Python dependencies from requirements COPY requirements.txt ./ COPY homeassistant/package_constraints.txt homeassistant/package_constraints.txt -RUN pip3 install -r requirements.txt +RUN pip3 install -r requirements.txt --use-deprecated=legacy-resolver COPY requirements_test.txt requirements_test_pre_commit.txt ./ -RUN pip3 install -r requirements_test.txt +RUN pip3 install -r requirements_test.txt --use-deprecated=legacy-resolver RUN rm -rf requirements.txt requirements_test.txt requirements_test_pre_commit.txt homeassistant/ # Set the default shell to bash instead of sh diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 5cded6a179d9e1..18ed69348b02b5 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -21,7 +21,7 @@ ifaddr==0.1.7 jinja2==3.0.3 paho-mqtt==1.6.1 pillow==9.0.1 -pip>=8.0.3,<20.3 +pip>=21.0,<22.1 pyserial==3.5 python-slugify==4.0.1 pyudev==0.22.0 diff --git a/requirements.txt b/requirements.txt index c8ee1d91368ac7..b907cc50cbb33d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ ifaddr==0.1.7 jinja2==3.0.3 PyJWT==2.1.0 cryptography==35.0.0 -pip>=8.0.3,<20.3 +pip>=21.0,<22.1 python-slugify==4.0.1 pyyaml==6.0 requests==2.27.1 diff --git a/script/bootstrap b/script/bootstrap index b641ec7e8c0384..5040a322b62056 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -8,4 +8,4 @@ cd "$(dirname "$0")/.." echo "Installing development dependencies..." python3 -m pip install wheel --constraint homeassistant/package_constraints.txt -python3 -m pip install tox tox-pip-version colorlog pre-commit $(grep mypy requirements_test.txt) $(grep stdlib-list requirements_test.txt) $(grep tqdm requirements_test.txt) $(grep pipdeptree requirements_test.txt) $(grep awesomeversion requirements.txt) --constraint homeassistant/package_constraints.txt +python3 -m pip install tox tox-pip-version colorlog pre-commit $(grep mypy requirements_test.txt) $(grep stdlib-list requirements_test.txt) $(grep tqdm requirements_test.txt) $(grep pipdeptree requirements_test.txt) $(grep awesomeversion requirements.txt) --constraint homeassistant/package_constraints.txt --use-deprecated=legacy-resolver diff --git a/script/setup b/script/setup index f827c3a373f5a3..210779eec4580a 100755 --- a/script/setup +++ b/script/setup @@ -24,7 +24,7 @@ fi script/bootstrap pre-commit install -python3 -m pip install -e . --constraint homeassistant/package_constraints.txt +python3 -m pip install -e . --constraint homeassistant/package_constraints.txt --use-deprecated=legacy-resolver hass --script ensure_config -c config diff --git a/setup.cfg b/setup.cfg index 061e0bbc0cbe64..75d7905ff0eebb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -48,7 +48,7 @@ install_requires = PyJWT==2.1.0 # PyJWT has loose dependency. We want the latest one. cryptography==35.0.0 - pip>=8.0.3,<20.3 + pip>=21.0,<22.1 python-slugify==4.0.1 pyyaml==6.0 requests==2.27.1 diff --git a/tox.ini b/tox.ini index af2f9961956158..b47a6c94ac87f9 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,8 @@ ignore_basepython_conflict = True [testenv] basepython = {env:PYTHON3_PATH:python3} # pip version duplicated in homeassistant/package_constraints.txt -pip_version = pip>=8.0.3,<20.3 +pip_version = pip>=21.0,<22.1 +install_command = python -m pip install --use-deprecated legacy-resolver {opts} {packages} commands = {envpython} -X dev -m pytest --timeout=9 --durations=10 -n auto --dist=loadfile -qq -o console_output_style=count -p no:sugar {posargs} {toxinidir}/script/check_dirty From e226cfaeb2cd28d8d415500689a55e758db3d298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 7 Feb 2022 16:04:18 +0100 Subject: [PATCH 0392/1098] Use strings directly instead of Enums in version config (#66007) --- .../components/version/config_flow.py | 23 +++++++++---------- homeassistant/components/version/const.py | 22 +++++++++--------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/version/config_flow.py b/homeassistant/components/version/config_flow.py index 30f03663de114b..f37fa1c3da2a41 100644 --- a/homeassistant/components/version/config_flow.py +++ b/homeassistant/components/version/config_flow.py @@ -3,7 +3,6 @@ from typing import Any -from pyhaversion.consts import HaVersionChannel, HaVersionSource import voluptuous as vol from homeassistant import config_entries @@ -75,8 +74,8 @@ async def async_step_user( self._entry_data.update(user_input) if not self.show_advanced_options or user_input[CONF_SOURCE] in ( - HaVersionSource.LOCAL, - HaVersionSource.HAIO, + "local", + "haio", ): return self.async_create_entry( title=self._config_entry_name, @@ -92,8 +91,8 @@ async def async_step_version_source( """Handle the version_source step.""" if user_input is None: if self._entry_data[CONF_SOURCE] in ( - HaVersionSource.SUPERVISOR, - HaVersionSource.CONTAINER, + "supervisor", + "container", ): data_schema = vol.Schema( { @@ -102,7 +101,7 @@ async def async_step_version_source( ): vol.In(VALID_CHANNELS), } ) - if self._entry_data[CONF_SOURCE] == HaVersionSource.SUPERVISOR: + if self._entry_data[CONF_SOURCE] == "supervisor": data_schema = data_schema.extend( { vol.Required(CONF_IMAGE, default=DEFAULT_IMAGE): vol.In( @@ -151,7 +150,7 @@ async def async_step_import(self, import_config: dict[str, Any]) -> FlowResult: @property def _config_entry_name(self) -> str: """Return the name of the config entry.""" - if self._entry_data[CONF_SOURCE] == HaVersionSource.LOCAL: + if self._entry_data[CONF_SOURCE] == "local": return DEFAULT_NAME_CURRENT name = self._entry_data[CONF_VERSION_SOURCE] @@ -166,21 +165,21 @@ def _convert_imported_configuration(config: dict[str, Any]) -> Any: """Convert a key from the imported configuration.""" data = DEFAULT_CONFIGURATION.copy() if config.get(CONF_BETA): - data[CONF_CHANNEL] = HaVersionChannel.BETA + data[CONF_CHANNEL] = "beta" if (source := config.get(CONF_SOURCE)) and source != DEFAULT_SOURCE: if source == SOURCE_HASSIO: - data[CONF_SOURCE] = HaVersionSource.SUPERVISOR + data[CONF_SOURCE] = "supervisor" data[CONF_VERSION_SOURCE] = VERSION_SOURCE_VERSIONS elif source == SOURCE_DOKCER: - data[CONF_SOURCE] = HaVersionSource.CONTAINER + data[CONF_SOURCE] = "container" data[CONF_VERSION_SOURCE] = VERSION_SOURCE_DOCKER_HUB else: data[CONF_SOURCE] = source data[CONF_VERSION_SOURCE] = VERSION_SOURCE_MAP_INVERTED[source] if (image := config.get(CONF_IMAGE)) and image != DEFAULT_IMAGE: - if data[CONF_SOURCE] == HaVersionSource.CONTAINER: + if data[CONF_SOURCE] == "container": data[CONF_IMAGE] = f"{config[CONF_IMAGE]}{POSTFIX_CONTAINER_NAME}" else: data[CONF_IMAGE] = config[CONF_IMAGE] @@ -188,7 +187,7 @@ def _convert_imported_configuration(config: dict[str, Any]) -> Any: if (name := config.get(CONF_NAME)) and name != DEFAULT_NAME: data[CONF_NAME] = config[CONF_NAME] else: - if data[CONF_SOURCE] == HaVersionSource.LOCAL: + if data[CONF_SOURCE] == "local": data[CONF_NAME] = DEFAULT_NAME_CURRENT else: data[CONF_NAME] = DEFAULT_NAME_LATEST diff --git a/homeassistant/components/version/const.py b/homeassistant/components/version/const.py index 8575b17a703050..8f1005961e83fc 100644 --- a/homeassistant/components/version/const.py +++ b/homeassistant/components/version/const.py @@ -41,12 +41,12 @@ DEFAULT_BETA: Final = False DEFAULT_BOARD: Final = "OVA" -DEFAULT_CHANNEL: Final[HaVersionChannel] = HaVersionChannel.STABLE +DEFAULT_CHANNEL: Final = "stable" DEFAULT_IMAGE: Final = "default" DEFAULT_NAME_CURRENT: Final = "Current Version" DEFAULT_NAME_LATEST: Final = "Latest Version" DEFAULT_NAME: Final = "" -DEFAULT_SOURCE: Final[HaVersionSource] = HaVersionSource.LOCAL +DEFAULT_SOURCE: Final = "local" DEFAULT_CONFIGURATION: Final[dict[str, Any]] = { CONF_NAME: DEFAULT_NAME, CONF_CHANNEL: DEFAULT_CHANNEL, @@ -81,22 +81,22 @@ VALID_BOARDS: Final[list[str]] = list(BOARD_MAP) -VERSION_SOURCE_MAP: Final[dict[str, HaVersionSource]] = { - VERSION_SOURCE_LOCAL: HaVersionSource.LOCAL, - VERSION_SOURCE_VERSIONS: HaVersionSource.SUPERVISOR, - VERSION_SOURCE_HAIO: HaVersionSource.HAIO, - VERSION_SOURCE_DOCKER_HUB: HaVersionSource.CONTAINER, - VERSION_SOURCE_PYPI: HaVersionSource.PYPI, +VERSION_SOURCE_MAP: Final[dict[str, str]] = { + VERSION_SOURCE_LOCAL: "local", + VERSION_SOURCE_VERSIONS: "supervisor", + VERSION_SOURCE_HAIO: "haio", + VERSION_SOURCE_DOCKER_HUB: "container", + VERSION_SOURCE_PYPI: "pypi", } -VERSION_SOURCE_MAP_INVERTED: Final[dict[HaVersionSource, str]] = { +VERSION_SOURCE_MAP_INVERTED: Final[dict[str, str]] = { value: key for key, value in VERSION_SOURCE_MAP.items() } VALID_SOURCES: Final[list[str]] = HA_VERSION_SOURCES + [ - SOURCE_HASSIO, # Kept to not break existing configurations - SOURCE_DOKCER, # Kept to not break existing configurations + "hassio", # Kept to not break existing configurations + "docker", # Kept to not break existing configurations ] VALID_IMAGES: Final = [ From b1015296d99d64d07ff4d94e28d0d6ba23b7a4e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 7 Feb 2022 16:11:04 +0100 Subject: [PATCH 0393/1098] Add diagnostics to Version integration (#65999) Co-authored-by: Martin Hjelmare --- .../components/version/diagnostics.py | 56 +++++++++++++++++++ tests/components/version/test_diagnostics.py | 36 ++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 homeassistant/components/version/diagnostics.py create mode 100644 tests/components/version/test_diagnostics.py diff --git a/homeassistant/components/version/diagnostics.py b/homeassistant/components/version/diagnostics.py new file mode 100644 index 00000000000000..2ba31bc88701da --- /dev/null +++ b/homeassistant/components/version/diagnostics.py @@ -0,0 +1,56 @@ +"""Provides diagnostics for Version.""" +from __future__ import annotations + +from typing import Any + +from attr import asdict + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, entity_registry as er + +from .const import DOMAIN + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, + config_entry: ConfigEntry, +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + coordinator = hass.data[DOMAIN][config_entry.entry_id] + device_registry = dr.async_get(hass) + entity_registry = er.async_get(hass) + + devices = [] + + registry_devices = dr.async_entries_for_config_entry( + device_registry, config_entry.entry_id + ) + + for device in registry_devices: + entities = [] + + registry_entities = er.async_entries_for_device( + entity_registry, + device_id=device.id, + include_disabled_entities=True, + ) + + for entity in registry_entities: + state_dict = None + if state := hass.states.get(entity.entity_id): + state_dict = dict(state.as_dict()) + state_dict.pop("context", None) + + entities.append({"entry": asdict(entity), "state": state_dict}) + + devices.append({"device": asdict(device), "entities": entities}) + + return { + "entry": config_entry.as_dict(), + "coordinator_data": { + "version": coordinator.version, + "version_data": coordinator.version_data, + }, + "devices": devices, + } diff --git a/tests/components/version/test_diagnostics.py b/tests/components/version/test_diagnostics.py new file mode 100644 index 00000000000000..1c9c8df4c62f3c --- /dev/null +++ b/tests/components/version/test_diagnostics.py @@ -0,0 +1,36 @@ +"""Test version diagnostics.""" + + +from aioaseko import ClientSession + +from homeassistant.core import HomeAssistant + +from .common import MOCK_VERSION, setup_version_integration + +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_diagnostics( + hass: HomeAssistant, + hass_client: ClientSession, +) -> None: + """Test diagnostic information.""" + config_entry = await setup_version_integration(hass) + + diagnostics = await get_diagnostics_for_config_entry( + hass, hass_client, config_entry + ) + assert diagnostics["entry"]["data"] == { + "name": "", + "channel": "stable", + "image": "default", + "board": "OVA", + "version_source": "Local installation", + "source": "local", + } + + assert diagnostics["coordinator_data"] == { + "version": MOCK_VERSION, + "version_data": None, + } + assert len(diagnostics["devices"]) == 1 From 3c5a667d9784bb5f2fab426b133b5582706c6e68 Mon Sep 17 00:00:00 2001 From: Poltorak Serguei Date: Mon, 7 Feb 2022 18:27:11 +0300 Subject: [PATCH 0394/1098] Add Z-Wave.Me integration (#65473) * Add support of Z-Wave.Me Z-Way and RaZberry server (#61182) Co-authored-by: Paulus Schoutsen Co-authored-by: Martin Hjelmare Co-authored-by: LawfulChaos * Add switch platform to Z-Wave.Me integration (#64957) Co-authored-by: Martin Hjelmare Co-authored-by: Dmitry Vlasov * Add button platform to Z-Wave.Me integration (#65109) Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> Co-authored-by: Dmitry Vlasov Co-authored-by: Martin Hjelmare * Fix button controller access (#65117) * Add lock platform to Z-Wave.Me integration #65109 (#65114) Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> Co-authored-by: Dmitry Vlasov Co-authored-by: Martin Hjelmare * Add sensor platform to Z-Wave.Me integration (#65132) * Sensor Entity * Sensor fixes * Apply suggestions from code review Co-authored-by: Martin Hjelmare * Inline descriotion according to review proposal * State Classes for sensor * Generic sensor * Generic sensor Co-authored-by: Dmitry Vlasov Co-authored-by: Martin Hjelmare * Add binary sensor platform to Z-Wave.Me integration (#65306) * Binary Sensor Entity * Update docstring Co-authored-by: Dmitry Vlasov Co-authored-by: Martin Hjelmare * Add Light Entity platform to Z-Wave.Me integration (#65331) * Light Entity * mypy fix * Fixes, ZWaveMePlatforms enum * Apply suggestions from code review Co-authored-by: Martin Hjelmare * Fixes * Fixes * Fixes Co-authored-by: Dmitry Vlasov Co-authored-by: Martin Hjelmare * Add Thermostat platform to Z-Wave.Me integration #65331 (#65371) * Climate entity * Climate entity * Apply suggestions from code review Co-authored-by: Martin Hjelmare * Climate entity fix * Clean up * cleanup * Import order fix * Correct naming Co-authored-by: Dmitry Vlasov Co-authored-by: Martin Hjelmare * Correct zwave_me .coveragerc (#65491) Co-authored-by: Martin Hjelmare Co-authored-by: Paulus Schoutsen Co-authored-by: Martin Hjelmare Co-authored-by: LawfulChaos Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> --- .coveragerc | 10 + CODEOWNERS | 2 + homeassistant/components/zwave_me/__init__.py | 123 ++++++++++++ .../components/zwave_me/binary_sensor.py | 75 +++++++ homeassistant/components/zwave_me/button.py | 40 ++++ homeassistant/components/zwave_me/climate.py | 101 ++++++++++ .../components/zwave_me/config_flow.py | 84 ++++++++ homeassistant/components/zwave_me/const.py | 32 +++ homeassistant/components/zwave_me/helpers.py | 14 ++ homeassistant/components/zwave_me/light.py | 85 ++++++++ homeassistant/components/zwave_me/lock.py | 60 ++++++ .../components/zwave_me/manifest.json | 17 ++ homeassistant/components/zwave_me/number.py | 45 +++++ homeassistant/components/zwave_me/sensor.py | 120 ++++++++++++ .../components/zwave_me/strings.json | 20 ++ homeassistant/components/zwave_me/switch.py | 67 +++++++ .../components/zwave_me/translations/en.json | 20 ++ homeassistant/generated/config_flows.py | 3 +- homeassistant/generated/zeroconf.py | 4 + requirements_all.txt | 4 + requirements_test_all.txt | 4 + tests/components/zwave_me/__init__.py | 1 + tests/components/zwave_me/test_config_flow.py | 184 ++++++++++++++++++ 23 files changed, 1114 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/zwave_me/__init__.py create mode 100644 homeassistant/components/zwave_me/binary_sensor.py create mode 100644 homeassistant/components/zwave_me/button.py create mode 100644 homeassistant/components/zwave_me/climate.py create mode 100644 homeassistant/components/zwave_me/config_flow.py create mode 100644 homeassistant/components/zwave_me/const.py create mode 100644 homeassistant/components/zwave_me/helpers.py create mode 100644 homeassistant/components/zwave_me/light.py create mode 100644 homeassistant/components/zwave_me/lock.py create mode 100644 homeassistant/components/zwave_me/manifest.json create mode 100644 homeassistant/components/zwave_me/number.py create mode 100644 homeassistant/components/zwave_me/sensor.py create mode 100644 homeassistant/components/zwave_me/strings.json create mode 100644 homeassistant/components/zwave_me/switch.py create mode 100644 homeassistant/components/zwave_me/translations/en.json create mode 100644 tests/components/zwave_me/__init__.py create mode 100644 tests/components/zwave_me/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index 8bc20400dba151..db20f089e4bde4 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1410,6 +1410,16 @@ omit = homeassistant/components/zwave/util.py homeassistant/components/zwave_js/discovery.py homeassistant/components/zwave_js/sensor.py + homeassistant/components/zwave_me/__init__.py + homeassistant/components/zwave_me/binary_sensor.py + homeassistant/components/zwave_me/button.py + homeassistant/components/zwave_me/climate.py + homeassistant/components/zwave_me/helpers.py + homeassistant/components/zwave_me/light.py + homeassistant/components/zwave_me/lock.py + homeassistant/components/zwave_me/number.py + homeassistant/components/zwave_me/sensor.py + homeassistant/components/zwave_me/switch.py [report] # Regexes for lines to exclude from consideration diff --git a/CODEOWNERS b/CODEOWNERS index 6eec61b49e20f5..7e56031c2bfd57 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1111,6 +1111,8 @@ homeassistant/components/zwave/* @home-assistant/z-wave tests/components/zwave/* @home-assistant/z-wave homeassistant/components/zwave_js/* @home-assistant/z-wave tests/components/zwave_js/* @home-assistant/z-wave +homeassistant/components/zwave_me/* @lawfulchaos @Z-Wave-Me +tests/components/zwave_me/* @lawfulchaos @Z-Wave-Me # Individual files homeassistant/components/demo/weather @fabaff diff --git a/homeassistant/components/zwave_me/__init__.py b/homeassistant/components/zwave_me/__init__.py new file mode 100644 index 00000000000000..42b510f9417f5d --- /dev/null +++ b/homeassistant/components/zwave_me/__init__.py @@ -0,0 +1,123 @@ +"""The Z-Wave-Me WS integration.""" +import asyncio +import logging + +from zwave_me_ws import ZWaveMe, ZWaveMeData + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_TOKEN, CONF_URL +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send +from homeassistant.helpers.entity import Entity + +from .const import DOMAIN, PLATFORMS, ZWaveMePlatform + +_LOGGER = logging.getLogger(__name__) +ZWAVE_ME_PLATFORMS = [platform.value for platform in ZWaveMePlatform] + + +async def async_setup_entry(hass, entry): + """Set up Z-Wave-Me from a config entry.""" + hass.data.setdefault(DOMAIN, {}) + controller = hass.data[DOMAIN][entry.entry_id] = ZWaveMeController(hass, entry) + if await controller.async_establish_connection(): + hass.async_create_task(async_setup_platforms(hass, entry, controller)) + return True + raise ConfigEntryNotReady() + + +async def async_unload_entry(hass, entry): + """Unload a config entry.""" + + unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + if unload_ok: + controller = hass.data[DOMAIN].pop(entry.entry_id) + await controller.zwave_api.close_ws() + return unload_ok + + +class ZWaveMeController: + """Main ZWave-Me API class.""" + + def __init__(self, hass: HomeAssistant, config: ConfigEntry) -> None: + """Create the API instance.""" + self.device_ids: set = set() + self._hass = hass + self.config = config + self.zwave_api = ZWaveMe( + on_device_create=self.on_device_create, + on_device_update=self.on_device_update, + on_new_device=self.add_device, + token=self.config.data[CONF_TOKEN], + url=self.config.data[CONF_URL], + platforms=ZWAVE_ME_PLATFORMS, + ) + self.platforms_inited = False + + async def async_establish_connection(self): + """Get connection status.""" + is_connected = await self.zwave_api.get_connection() + return is_connected + + def add_device(self, device: ZWaveMeData) -> None: + """Send signal to create device.""" + if device.deviceType in ZWAVE_ME_PLATFORMS and self.platforms_inited: + if device.id in self.device_ids: + dispatcher_send(self._hass, f"ZWAVE_ME_INFO_{device.id}", device) + else: + dispatcher_send( + self._hass, f"ZWAVE_ME_NEW_{device.deviceType.upper()}", device + ) + self.device_ids.add(device.id) + + def on_device_create(self, devices: list) -> None: + """Create multiple devices.""" + for device in devices: + self.add_device(device) + + def on_device_update(self, new_info: ZWaveMeData) -> None: + """Send signal to update device.""" + dispatcher_send(self._hass, f"ZWAVE_ME_INFO_{new_info.id}", new_info) + + +async def async_setup_platforms( + hass: HomeAssistant, entry: ConfigEntry, controller: ZWaveMeController +) -> None: + """Set up platforms.""" + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_setup(entry, platform) + for platform in PLATFORMS + ] + ) + controller.platforms_inited = True + + await hass.async_add_executor_job(controller.zwave_api.get_devices) + + +class ZWaveMeEntity(Entity): + """Representation of a ZWaveMe device.""" + + def __init__(self, controller, device): + """Initialize the device.""" + self.controller = controller + self.device = device + self._attr_name = device.title + self._attr_unique_id = f"{self.controller.config.unique_id}-{self.device.id}" + self._attr_should_poll = False + + async def async_added_to_hass(self) -> None: + """Connect to an updater.""" + self.async_on_remove( + async_dispatcher_connect( + self.hass, f"ZWAVE_ME_INFO_{self.device.id}", self.get_new_data + ) + ) + + @callback + def get_new_data(self, new_data): + """Update info in the HAss.""" + self.device = new_data + self._attr_available = not new_data.isFailed + self.async_write_ha_state() diff --git a/homeassistant/components/zwave_me/binary_sensor.py b/homeassistant/components/zwave_me/binary_sensor.py new file mode 100644 index 00000000000000..40d850b8483d27 --- /dev/null +++ b/homeassistant/components/zwave_me/binary_sensor.py @@ -0,0 +1,75 @@ +"""Representation of a sensorBinary.""" +from __future__ import annotations + +from zwave_me_ws import ZWaveMeData + +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_MOTION, + BinarySensorEntity, + BinarySensorEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import ZWaveMeController, ZWaveMeEntity +from .const import DOMAIN, ZWaveMePlatform + +BINARY_SENSORS_MAP: dict[str, BinarySensorEntityDescription] = { + "generic": BinarySensorEntityDescription( + key="generic", + ), + "motion": BinarySensorEntityDescription( + key="motion", + device_class=DEVICE_CLASS_MOTION, + ), +} +DEVICE_NAME = ZWaveMePlatform.BINARY_SENSOR + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the binary sensor platform.""" + + @callback + def add_new_device(new_device: ZWaveMeData) -> None: + controller: ZWaveMeController = hass.data[DOMAIN][config_entry.entry_id] + description = BINARY_SENSORS_MAP.get( + new_device.probeType, BINARY_SENSORS_MAP["generic"] + ) + sensor = ZWaveMeBinarySensor(controller, new_device, description) + + async_add_entities( + [ + sensor, + ] + ) + + config_entry.async_on_unload( + async_dispatcher_connect( + hass, f"ZWAVE_ME_NEW_{DEVICE_NAME.upper()}", add_new_device + ) + ) + + +class ZWaveMeBinarySensor(ZWaveMeEntity, BinarySensorEntity): + """Representation of a ZWaveMe binary sensor.""" + + def __init__( + self, + controller: ZWaveMeController, + device: ZWaveMeData, + description: BinarySensorEntityDescription, + ) -> None: + """Initialize the device.""" + super().__init__(controller=controller, device=device) + self.entity_description = description + + @property + def is_on(self) -> bool: + """Return the state of the sensor.""" + return self.device.level == "on" diff --git a/homeassistant/components/zwave_me/button.py b/homeassistant/components/zwave_me/button.py new file mode 100644 index 00000000000000..40105d100d4115 --- /dev/null +++ b/homeassistant/components/zwave_me/button.py @@ -0,0 +1,40 @@ +"""Representation of a toggleButton.""" +from typing import Any + +from homeassistant.components.button import ButtonEntity +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from . import ZWaveMeEntity +from .const import DOMAIN, ZWaveMePlatform + +DEVICE_NAME = ZWaveMePlatform.BUTTON + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the number platform.""" + + @callback + def add_new_device(new_device): + controller = hass.data[DOMAIN][config_entry.entry_id] + button = ZWaveMeButton(controller, new_device) + + async_add_entities( + [ + button, + ] + ) + + config_entry.async_on_unload( + async_dispatcher_connect( + hass, f"ZWAVE_ME_NEW_{DEVICE_NAME.upper()}", add_new_device + ) + ) + + +class ZWaveMeButton(ZWaveMeEntity, ButtonEntity): + """Representation of a ZWaveMe button.""" + + def press(self, **kwargs: Any) -> None: + """Turn the entity on.""" + self.controller.zwave_api.send_command(self.device.id, "on") diff --git a/homeassistant/components/zwave_me/climate.py b/homeassistant/components/zwave_me/climate.py new file mode 100644 index 00000000000000..140c397ecdec11 --- /dev/null +++ b/homeassistant/components/zwave_me/climate.py @@ -0,0 +1,101 @@ +"""Representation of a thermostat.""" +from __future__ import annotations + +from zwave_me_ws import ZWaveMeData + +from homeassistant.components.climate import ClimateEntity +from homeassistant.components.climate.const import ( + HVAC_MODE_HEAT, + SUPPORT_TARGET_TEMPERATURE, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ATTR_TEMPERATURE +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import ZWaveMeEntity +from .const import DOMAIN, ZWaveMePlatform + +TEMPERATURE_DEFAULT_STEP = 0.5 + +DEVICE_NAME = ZWaveMePlatform.CLIMATE + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the climate platform.""" + + @callback + def add_new_device(new_device: ZWaveMeData) -> None: + """Add a new device.""" + controller = hass.data[DOMAIN][config_entry.entry_id] + climate = ZWaveMeClimate(controller, new_device) + + async_add_entities( + [ + climate, + ] + ) + + config_entry.async_on_unload( + async_dispatcher_connect( + hass, f"ZWAVE_ME_NEW_{DEVICE_NAME.upper()}", add_new_device + ) + ) + + +class ZWaveMeClimate(ZWaveMeEntity, ClimateEntity): + """Representation of a ZWaveMe sensor.""" + + def set_temperature(self, **kwargs) -> None: + """Set new target temperature.""" + if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None: + return + + self.controller.zwave_api.send_command( + self.device.id, f"exact?level={temperature}" + ) + + @property + def temperature_unit(self) -> str: + """Return the temperature_unit.""" + return self.device.scaleTitle + + @property + def target_temperature(self) -> float: + """Return the state of the sensor.""" + return self.device.level + + @property + def max_temp(self) -> float: + """Return min temperature for the device.""" + return self.device.max + + @property + def min_temp(self) -> float: + """Return max temperature for the device.""" + return self.device.min + + @property + def hvac_modes(self) -> list[str]: + """Return the list of available operation modes.""" + return [HVAC_MODE_HEAT] + + @property + def hvac_mode(self) -> str: + """Return the current mode.""" + return HVAC_MODE_HEAT + + @property + def supported_features(self) -> int: + """Return the supported features.""" + return SUPPORT_TARGET_TEMPERATURE + + @property + def target_temperature_step(self) -> float: + """Return the supported step of target temperature.""" + return TEMPERATURE_DEFAULT_STEP diff --git a/homeassistant/components/zwave_me/config_flow.py b/homeassistant/components/zwave_me/config_flow.py new file mode 100644 index 00000000000000..a4b257bdab5e03 --- /dev/null +++ b/homeassistant/components/zwave_me/config_flow.py @@ -0,0 +1,84 @@ +"""Config flow to configure ZWaveMe integration.""" + +import logging + +from url_normalize import url_normalize +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_TOKEN, CONF_URL + +from . import helpers +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +class ZWaveMeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """ZWaveMe integration config flow.""" + + def __init__(self): + """Initialize flow.""" + self.url = None + self.token = None + self.uuid = None + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user or started with zeroconf.""" + errors = {} + if self.url is None: + schema = vol.Schema( + { + vol.Required(CONF_URL): str, + vol.Required(CONF_TOKEN): str, + } + ) + else: + schema = vol.Schema( + { + vol.Required(CONF_TOKEN): str, + } + ) + + if user_input is not None: + if self.url is None: + self.url = user_input[CONF_URL] + + self.token = user_input[CONF_TOKEN] + if not self.url.startswith(("ws://", "wss://")): + self.url = f"ws://{self.url}" + self.url = url_normalize(self.url, default_scheme="ws") + if self.uuid is None: + self.uuid = await helpers.get_uuid(self.url, self.token) + if self.uuid is not None: + await self.async_set_unique_id(self.uuid, raise_on_progress=False) + self._abort_if_unique_id_configured() + else: + errors["base"] = "no_valid_uuid_set" + + if not errors: + return self.async_create_entry( + title=self.url, + data={CONF_URL: self.url, CONF_TOKEN: self.token}, + ) + + return self.async_show_form( + step_id="user", + data_schema=schema, + errors=errors, + ) + + async def async_step_zeroconf(self, discovery_info): + """ + Handle a discovered Z-Wave accessory - get url to pass into user step. + + This flow is triggered by the discovery component. + """ + self.url = discovery_info.host + self.uuid = await helpers.get_uuid(self.url) + if self.uuid is None: + return self.async_abort(reason="no_valid_uuid_set") + + await self.async_set_unique_id(self.uuid) + self._abort_if_unique_id_configured() + return await self.async_step_user() diff --git a/homeassistant/components/zwave_me/const.py b/homeassistant/components/zwave_me/const.py new file mode 100644 index 00000000000000..ccbf6989f070c1 --- /dev/null +++ b/homeassistant/components/zwave_me/const.py @@ -0,0 +1,32 @@ +"""Constants for ZWaveMe.""" +from homeassistant.backports.enum import StrEnum +from homeassistant.const import Platform + +# Base component constants +DOMAIN = "zwave_me" + + +class ZWaveMePlatform(StrEnum): + """Included ZWaveMe platforms.""" + + BINARY_SENSOR = "sensorBinary" + BUTTON = "toggleButton" + CLIMATE = "thermostat" + LOCK = "doorlock" + NUMBER = "switchMultilevel" + SWITCH = "switchBinary" + SENSOR = "sensorMultilevel" + RGBW_LIGHT = "switchRGBW" + RGB_LIGHT = "switchRGB" + + +PLATFORMS = [ + Platform.BINARY_SENSOR, + Platform.BUTTON, + Platform.CLIMATE, + Platform.LIGHT, + Platform.LOCK, + Platform.NUMBER, + Platform.SENSOR, + Platform.SWITCH, +] diff --git a/homeassistant/components/zwave_me/helpers.py b/homeassistant/components/zwave_me/helpers.py new file mode 100644 index 00000000000000..0d53512d1cb973 --- /dev/null +++ b/homeassistant/components/zwave_me/helpers.py @@ -0,0 +1,14 @@ +"""Helpers for zwave_me config flow.""" +from __future__ import annotations + +from zwave_me_ws import ZWaveMe + + +async def get_uuid(url: str, token: str | None = None) -> str | None: + """Get an uuid from Z-Wave-Me.""" + conn = ZWaveMe(url=url, token=token) + uuid = None + if await conn.get_connection(): + uuid = await conn.get_uuid() + await conn.close_ws() + return uuid diff --git a/homeassistant/components/zwave_me/light.py b/homeassistant/components/zwave_me/light.py new file mode 100644 index 00000000000000..df2a14d3bbde82 --- /dev/null +++ b/homeassistant/components/zwave_me/light.py @@ -0,0 +1,85 @@ +"""Representation of an RGB light.""" +from __future__ import annotations + +from typing import Any + +from zwave_me_ws import ZWaveMeData + +from homeassistant.components.light import ATTR_RGB_COLOR, COLOR_MODE_RGB, LightEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import ZWaveMeEntity +from .const import DOMAIN, ZWaveMePlatform + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the rgb platform.""" + + @callback + def add_new_device(new_device: ZWaveMeData) -> None: + """Add a new device.""" + controller = hass.data[DOMAIN][config_entry.entry_id] + rgb = ZWaveMeRGB(controller, new_device) + + async_add_entities( + [ + rgb, + ] + ) + + async_dispatcher_connect( + hass, f"ZWAVE_ME_NEW_{ZWaveMePlatform.RGB_LIGHT.upper()}", add_new_device + ) + async_dispatcher_connect( + hass, f"ZWAVE_ME_NEW_{ZWaveMePlatform.RGBW_LIGHT.upper()}", add_new_device + ) + + +class ZWaveMeRGB(ZWaveMeEntity, LightEntity): + """Representation of a ZWaveMe light.""" + + def turn_off(self, **kwargs: Any) -> None: + """Turn the device on.""" + self.controller.zwave_api.send_command(self.device.id, "off") + + def turn_on(self, **kwargs: Any): + """Turn the device on.""" + color = kwargs.get(ATTR_RGB_COLOR) + + if color is None: + color = (122, 122, 122) + cmd = "exact?red={}&green={}&blue={}".format(*color) + self.controller.zwave_api.send_command(self.device.id, cmd) + + @property + def is_on(self) -> bool: + """Return true if the light is on.""" + return self.device.level == "on" + + @property + def brightness(self) -> int: + """Return the brightness of a device.""" + return max(self.device.color.values()) + + @property + def rgb_color(self) -> tuple[int, int, int]: + """Return the rgb color value [int, int, int].""" + rgb = self.device.color + return rgb["r"], rgb["g"], rgb["b"] + + @property + def supported_color_modes(self) -> set: + """Return all color modes.""" + return {COLOR_MODE_RGB} + + @property + def color_mode(self) -> str: + """Return current color mode.""" + return COLOR_MODE_RGB diff --git a/homeassistant/components/zwave_me/lock.py b/homeassistant/components/zwave_me/lock.py new file mode 100644 index 00000000000000..17e64ff1602ea4 --- /dev/null +++ b/homeassistant/components/zwave_me/lock.py @@ -0,0 +1,60 @@ +"""Representation of a doorlock.""" +from __future__ import annotations + +from typing import Any + +from zwave_me_ws import ZWaveMeData + +from homeassistant.components.lock import LockEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import ZWaveMeEntity +from .const import DOMAIN, ZWaveMePlatform + +DEVICE_NAME = ZWaveMePlatform.LOCK + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the lock platform.""" + + @callback + def add_new_device(new_device: ZWaveMeData) -> None: + """Add a new device.""" + controller = hass.data[DOMAIN][config_entry.entry_id] + lock = ZWaveMeLock(controller, new_device) + + async_add_entities( + [ + lock, + ] + ) + + config_entry.async_on_unload( + async_dispatcher_connect( + hass, f"ZWAVE_ME_NEW_{DEVICE_NAME.upper()}", add_new_device + ) + ) + + +class ZWaveMeLock(ZWaveMeEntity, LockEntity): + """Representation of a ZWaveMe lock.""" + + @property + def is_locked(self) -> bool: + """Return the state of the lock.""" + return self.device.level == "close" + + def unlock(self, **kwargs: Any) -> None: + """Send command to unlock the lock.""" + self.controller.zwave_api.send_command(self.device.id, "open") + + def lock(self, **kwargs: Any) -> None: + """Send command to lock the lock.""" + self.controller.zwave_api.send_command(self.device.id, "close") diff --git a/homeassistant/components/zwave_me/manifest.json b/homeassistant/components/zwave_me/manifest.json new file mode 100644 index 00000000000000..1a7177ca470111 --- /dev/null +++ b/homeassistant/components/zwave_me/manifest.json @@ -0,0 +1,17 @@ +{ + "domain": "zwave_me", + "name": "Z-Wave.Me", + "documentation": "https://www.home-assistant.io/integrations/zwave_me", + "iot_class": "local_push", + "requirements": [ + "zwave_me_ws==0.1.23", + "url-normalize==1.4.1" + ], + "after_dependencies": ["zeroconf"], + "zeroconf": [{"type":"_hap._tcp.local.", "name": "*z.wave-me*"}], + "config_flow": true, + "codeowners": [ + "@lawfulchaos", + "@Z-Wave-Me" + ] +} diff --git a/homeassistant/components/zwave_me/number.py b/homeassistant/components/zwave_me/number.py new file mode 100644 index 00000000000000..b955ade21db17b --- /dev/null +++ b/homeassistant/components/zwave_me/number.py @@ -0,0 +1,45 @@ +"""Representation of a switchMultilevel.""" +from homeassistant.components.number import NumberEntity +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from . import ZWaveMeEntity +from .const import DOMAIN, ZWaveMePlatform + +DEVICE_NAME = ZWaveMePlatform.NUMBER + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the number platform.""" + + @callback + def add_new_device(new_device): + controller = hass.data[DOMAIN][config_entry.entry_id] + switch = ZWaveMeNumber(controller, new_device) + + async_add_entities( + [ + switch, + ] + ) + + config_entry.async_on_unload( + async_dispatcher_connect( + hass, f"ZWAVE_ME_NEW_{DEVICE_NAME.upper()}", add_new_device + ) + ) + + +class ZWaveMeNumber(ZWaveMeEntity, NumberEntity): + """Representation of a ZWaveMe Multilevel Switch.""" + + @property + def value(self): + """Return the unit of measurement.""" + return self.device.level + + def set_value(self, value: float) -> None: + """Update the current value.""" + self.controller.zwave_api.send_command( + self.device.id, f"exact?level={str(round(value))}" + ) diff --git a/homeassistant/components/zwave_me/sensor.py b/homeassistant/components/zwave_me/sensor.py new file mode 100644 index 00000000000000..84c4f406495971 --- /dev/null +++ b/homeassistant/components/zwave_me/sensor.py @@ -0,0 +1,120 @@ +"""Representation of a sensorMultilevel.""" +from __future__ import annotations + +from zwave_me_ws import ZWaveMeData + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + ELECTRIC_POTENTIAL_VOLT, + ENERGY_KILO_WATT_HOUR, + LIGHT_LUX, + POWER_WATT, + SIGNAL_STRENGTH_DECIBELS, + TEMP_CELSIUS, +) +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import ZWaveMeController, ZWaveMeEntity +from .const import DOMAIN, ZWaveMePlatform + +SENSORS_MAP: dict[str, SensorEntityDescription] = { + "meterElectric_watt": SensorEntityDescription( + key="meterElectric_watt", + device_class=SensorDeviceClass.POWER, + native_unit_of_measurement=POWER_WATT, + state_class=SensorStateClass.MEASUREMENT, + ), + "meterElectric_kilowatt_hour": SensorEntityDescription( + key="meterElectric_kilowatt_hour", + device_class=SensorDeviceClass.ENERGY, + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + "meterElectric_voltage": SensorEntityDescription( + key="meterElectric_voltage", + device_class=SensorDeviceClass.VOLTAGE, + native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT, + state_class=SensorStateClass.MEASUREMENT, + ), + "light": SensorEntityDescription( + key="light", + device_class=SensorDeviceClass.ILLUMINANCE, + native_unit_of_measurement=LIGHT_LUX, + state_class=SensorStateClass.MEASUREMENT, + ), + "noise": SensorEntityDescription( + key="noise", + device_class=SensorDeviceClass.SIGNAL_STRENGTH, + native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS, + state_class=SensorStateClass.MEASUREMENT, + ), + "currentTemperature": SensorEntityDescription( + key="currentTemperature", + device_class=SensorDeviceClass.TEMPERATURE, + native_unit_of_measurement=TEMP_CELSIUS, + state_class=SensorStateClass.MEASUREMENT, + ), + "temperature": SensorEntityDescription( + key="temperature", + device_class=SensorDeviceClass.TEMPERATURE, + native_unit_of_measurement=TEMP_CELSIUS, + state_class=SensorStateClass.MEASUREMENT, + ), + "generic": SensorEntityDescription( + key="generic", + ), +} +DEVICE_NAME = ZWaveMePlatform.SENSOR + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the sensor platform.""" + + @callback + def add_new_device(new_device: ZWaveMeData) -> None: + controller: ZWaveMeController = hass.data[DOMAIN][config_entry.entry_id] + description = SENSORS_MAP.get(new_device.probeType, SENSORS_MAP["generic"]) + sensor = ZWaveMeSensor(controller, new_device, description) + + async_add_entities( + [ + sensor, + ] + ) + + config_entry.async_on_unload( + async_dispatcher_connect( + hass, f"ZWAVE_ME_NEW_{DEVICE_NAME.upper()}", add_new_device + ) + ) + + +class ZWaveMeSensor(ZWaveMeEntity, SensorEntity): + """Representation of a ZWaveMe sensor.""" + + def __init__( + self, + controller: ZWaveMeController, + device: ZWaveMeData, + description: SensorEntityDescription, + ) -> None: + """Initialize the device.""" + super().__init__(controller=controller, device=device) + self.entity_description = description + + @property + def native_value(self) -> str: + """Return the state of the sensor.""" + return self.device.level diff --git a/homeassistant/components/zwave_me/strings.json b/homeassistant/components/zwave_me/strings.json new file mode 100644 index 00000000000000..4986de744c0f87 --- /dev/null +++ b/homeassistant/components/zwave_me/strings.json @@ -0,0 +1,20 @@ +{ + "config": { + "step": { + "user": { + "description": "Input IP address of Z-Way server and Z-Way access token. IP address can be prefixed with wss:// if HTTPS should be used instead of HTTP. To get the token go to the Z-Way user interface > Menu > Settings > User > API token. It is suggested to create a new user for Home Assistant and grant access to devices you need to control from Home Assistant. It is also possible to use remote access via find.z-wave.me to connect a remote Z-Way. Input wss://find.z-wave.me in IP field and copy the token with Global scope (log-in to Z-Way via find.z-wave.me for this).", + "data": { + "url": "[%key:common::config_flow::data::url%]", + "token": "Token" + } + } + }, + "error": { + "no_valid_uuid_set": "No valid UUID set" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "no_valid_uuid_set": "No valid UUID set" + } + } +} diff --git a/homeassistant/components/zwave_me/switch.py b/homeassistant/components/zwave_me/switch.py new file mode 100644 index 00000000000000..c759809df15012 --- /dev/null +++ b/homeassistant/components/zwave_me/switch.py @@ -0,0 +1,67 @@ +"""Representation of a switchBinary.""" +import logging +from typing import Any + +from homeassistant.components.switch import ( + SwitchDeviceClass, + SwitchEntity, + SwitchEntityDescription, +) +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from . import ZWaveMeEntity +from .const import DOMAIN, ZWaveMePlatform + +_LOGGER = logging.getLogger(__name__) +DEVICE_NAME = ZWaveMePlatform.SWITCH + +SWITCH_MAP: dict[str, SwitchEntityDescription] = { + "generic": SwitchEntityDescription( + key="generic", + device_class=SwitchDeviceClass.SWITCH, + ) +} + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the switch platform.""" + + @callback + def add_new_device(new_device): + controller = hass.data[DOMAIN][config_entry.entry_id] + switch = ZWaveMeSwitch(controller, new_device, SWITCH_MAP["generic"]) + + async_add_entities( + [ + switch, + ] + ) + + config_entry.async_on_unload( + async_dispatcher_connect( + hass, f"ZWAVE_ME_NEW_{DEVICE_NAME.upper()}", add_new_device + ) + ) + + +class ZWaveMeSwitch(ZWaveMeEntity, SwitchEntity): + """Representation of a ZWaveMe binary switch.""" + + def __init__(self, controller, device, description): + """Initialize the device.""" + super().__init__(controller, device) + self.entity_description = description + + @property + def is_on(self) -> bool: + """Return the state of the switch.""" + return self.device.level == "on" + + def turn_on(self, **kwargs: Any) -> None: + """Turn the entity on.""" + self.controller.zwave_api.send_command(self.device.id, "on") + + def turn_off(self, **kwargs: Any) -> None: + """Turn the entity off.""" + self.controller.zwave_api.send_command(self.device.id, "off") diff --git a/homeassistant/components/zwave_me/translations/en.json b/homeassistant/components/zwave_me/translations/en.json new file mode 100644 index 00000000000000..81d09d5c350a83 --- /dev/null +++ b/homeassistant/components/zwave_me/translations/en.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured", + "no_valid_uuid_set": "No valid UUID set" + }, + "error": { + "no_valid_uuid_set": "No valid UUID set" + }, + "step": { + "user": { + "data": { + "token": "Token", + "url": "URL" + }, + "description": "Input IP address of Z-Way server and Z-Way access token. IP address can be prefixed with wss:// if HTTPS should be used instead of HTTP. To get the token go to the Z-Way user interface > Menu > Settings > User > API token. It is suggested to create a new user for Home Assistant and grant access to devices you need to control from Home Assistant. It is also possible to use remote access via find.z-wave.me to connect a remote Z-Way. Input wss://find.z-wave.me in IP field and copy the token with Global scope (log-in to Z-Way via find.z-wave.me for this)." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index c521bd85e3e33c..061dcd3a0adb7c 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -378,5 +378,6 @@ "zerproc", "zha", "zwave", - "zwave_js" + "zwave_js", + "zwave_me" ] diff --git a/homeassistant/generated/zeroconf.py b/homeassistant/generated/zeroconf.py index da48577a1460c5..06d1940f23d756 100644 --- a/homeassistant/generated/zeroconf.py +++ b/homeassistant/generated/zeroconf.py @@ -144,6 +144,10 @@ "_hap._tcp.local.": [ { "domain": "homekit_controller" + }, + { + "domain": "zwave_me", + "name": "*z.wave-me*" } ], "_homekit._tcp.local.": [ diff --git a/requirements_all.txt b/requirements_all.txt index 8dc8a0f5bfd19e..3a5da629839210 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2408,6 +2408,7 @@ upcloud-api==2.0.0 # homeassistant.components.huawei_lte # homeassistant.components.syncthru +# homeassistant.components.zwave_me url-normalize==1.4.1 # homeassistant.components.uscis @@ -2562,3 +2563,6 @@ zm-py==0.5.2 # homeassistant.components.zwave_js zwave-js-server-python==0.34.0 + +# homeassistant.components.zwave_me +zwave_me_ws==0.1.23 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8bb1d6564e5910..c86988eb62abd3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1475,6 +1475,7 @@ upcloud-api==2.0.0 # homeassistant.components.huawei_lte # homeassistant.components.syncthru +# homeassistant.components.zwave_me url-normalize==1.4.1 # homeassistant.components.uvc @@ -1578,3 +1579,6 @@ zigpy==0.43.0 # homeassistant.components.zwave_js zwave-js-server-python==0.34.0 + +# homeassistant.components.zwave_me +zwave_me_ws==0.1.23 diff --git a/tests/components/zwave_me/__init__.py b/tests/components/zwave_me/__init__.py new file mode 100644 index 00000000000000..da2457db55e02b --- /dev/null +++ b/tests/components/zwave_me/__init__.py @@ -0,0 +1 @@ +"""Tests for the zwave_me integration.""" diff --git a/tests/components/zwave_me/test_config_flow.py b/tests/components/zwave_me/test_config_flow.py new file mode 100644 index 00000000000000..9c7c9f51e2496b --- /dev/null +++ b/tests/components/zwave_me/test_config_flow.py @@ -0,0 +1,184 @@ +"""Test the zwave_me config flow.""" +from unittest.mock import patch + +from homeassistant import config_entries +from homeassistant.components import zeroconf +from homeassistant.components.zwave_me.const import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import ( + RESULT_TYPE_ABORT, + RESULT_TYPE_CREATE_ENTRY, + RESULT_TYPE_FORM, + FlowResult, +) + +from tests.common import MockConfigEntry + +MOCK_ZEROCONF_DATA = zeroconf.ZeroconfServiceInfo( + host="ws://192.168.1.14", + hostname="mock_hostname", + name="mock_name", + port=1234, + properties={ + "deviceid": "aa:bb:cc:dd:ee:ff", + "manufacturer": "fake_manufacturer", + "model": "fake_model", + "serialNumber": "fake_serial", + }, + type="mock_type", +) + + +async def test_form(hass: HomeAssistant) -> None: + """Test we get the form.""" + with patch( + "homeassistant.components.zwave_me.async_setup_entry", + return_value=True, + ) as mock_setup_entry, patch( + "homeassistant.components.zwave_me.helpers.get_uuid", + return_value="test_uuid", + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {} + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "url": "192.168.1.14", + "token": "test-token", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == "ws://192.168.1.14" + assert result2["data"] == { + "url": "ws://192.168.1.14", + "token": "test-token", + } + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_zeroconf(hass: HomeAssistant): + """Test starting a flow from zeroconf.""" + with patch( + "homeassistant.components.zwave_me.async_setup_entry", + return_value=True, + ) as mock_setup_entry, patch( + "homeassistant.components.zwave_me.helpers.get_uuid", + return_value="test_uuid", + ): + result: FlowResult = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data=MOCK_ZEROCONF_DATA, + ) + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "user" + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "token": "test-token", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == "ws://192.168.1.14" + assert result2["data"] == { + "url": "ws://192.168.1.14", + "token": "test-token", + } + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_error_handling_zeroconf(hass: HomeAssistant): + """Test getting proper errors from no uuid.""" + with patch("homeassistant.components.zwave_me.helpers.get_uuid", return_value=None): + result: FlowResult = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data=MOCK_ZEROCONF_DATA, + ) + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "no_valid_uuid_set" + + +async def test_handle_error_user(hass: HomeAssistant): + """Test getting proper errors from no uuid.""" + with patch("homeassistant.components.zwave_me.helpers.get_uuid", return_value=None): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {} + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "url": "192.168.1.15", + "token": "test-token", + }, + ) + assert result2["errors"] == {"base": "no_valid_uuid_set"} + + +async def test_duplicate_user(hass: HomeAssistant): + """Test getting proper errors from duplicate uuid.""" + entry: MockConfigEntry = MockConfigEntry( + domain=DOMAIN, + title="ZWave_me", + data={ + "url": "ws://192.168.1.15", + "token": "test-token", + }, + unique_id="test_uuid", + ) + entry.add_to_hass(hass) + with patch( + "homeassistant.components.zwave_me.helpers.get_uuid", + return_value="test_uuid", + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {} + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "url": "192.168.1.15", + "token": "test-token", + }, + ) + assert result2["type"] == RESULT_TYPE_ABORT + assert result2["reason"] == "already_configured" + + +async def test_duplicate_zeroconf(hass: HomeAssistant): + """Test getting proper errors from duplicate uuid.""" + entry: MockConfigEntry = MockConfigEntry( + domain=DOMAIN, + title="ZWave_me", + data={ + "url": "ws://192.168.1.14", + "token": "test-token", + }, + unique_id="test_uuid", + ) + entry.add_to_hass(hass) + + with patch( + "homeassistant.components.zwave_me.helpers.get_uuid", + return_value="test_uuid", + ): + + result: FlowResult = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data=MOCK_ZEROCONF_DATA, + ) + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" From 9c82dcdee7d6497c2163197748b389c5222ad1d8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 7 Feb 2022 09:46:00 -0600 Subject: [PATCH 0395/1098] Add push updates support to WiZ (#65987) --- homeassistant/components/wiz/__init__.py | 5 ++++- homeassistant/components/wiz/manifest.json | 4 ++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/wiz/__init__.py b/homeassistant/components/wiz/__init__.py index ddf324278cfd77..ce5d92c5f621b9 100644 --- a/homeassistant/components/wiz/__init__.py +++ b/homeassistant/components/wiz/__init__.py @@ -81,6 +81,8 @@ async def _async_update() -> None: hass, _LOGGER, cooldown=REQUEST_REFRESH_DELAY, immediate=False ), ) + + await bulb.start_push(lambda _: coordinator.async_set_updated_data(None)) await coordinator.async_config_entry_first_refresh() hass.data.setdefault(DOMAIN, {})[entry.entry_id] = WizData( @@ -93,5 +95,6 @@ async def _async_update() -> None: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): - hass.data[DOMAIN].pop(entry.entry_id) + data: WizData = hass.data[DOMAIN].pop(entry.entry_id) + await data.bulb.async_close() return unload_ok diff --git a/homeassistant/components/wiz/manifest.json b/homeassistant/components/wiz/manifest.json index 773c0ff0e6e386..90a6347dbc56aa 100644 --- a/homeassistant/components/wiz/manifest.json +++ b/homeassistant/components/wiz/manifest.json @@ -8,7 +8,7 @@ ], "dependencies": ["network"], "documentation": "https://www.home-assistant.io/integrations/wiz", - "requirements": ["pywizlight==0.5"], - "iot_class": "local_polling", + "requirements": ["pywizlight==0.5.1"], + "iot_class": "local_push", "codeowners": ["@sbidy"] } diff --git a/requirements_all.txt b/requirements_all.txt index 3a5da629839210..5e2c0ac5bdaa84 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2051,7 +2051,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.5 +pywizlight==0.5.1 # homeassistant.components.xeoma pyxeoma==1.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c86988eb62abd3..bf0123fea980e5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1276,7 +1276,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.5 +pywizlight==0.5.1 # homeassistant.components.zerproc pyzerproc==0.4.8 From ebbe1ff1a29b02b356dcd94c39c2d9382ef473e4 Mon Sep 17 00:00:00 2001 From: Pedro Lamas Date: Mon, 7 Feb 2022 15:49:18 +0000 Subject: [PATCH 0396/1098] Cache webostv supported_features state (#65930) * Cache webostv supported_features state * Fixes typings * Restore supported_features attribute on restart * Reverts change on supported_features initial state Co-authored-by: Shay Levy * Fixes tests Co-authored-by: Shay Levy --- .../components/webostv/media_player.py | 20 ++++++- tests/components/webostv/test_media_player.py | 54 +++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index 5aac52f6f7bd8f..8fa18ce3142ef9 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -34,6 +34,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, ENTITY_MATCH_ALL, ENTITY_MATCH_NONE, STATE_OFF, @@ -44,6 +45,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.restore_state import RestoreEntity from . import WebOsClientWrapper from .const import ( @@ -121,7 +123,7 @@ async def cmd_wrapper(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> None: return cmd_wrapper -class LgWebOSMediaPlayerEntity(MediaPlayerEntity): +class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity): """Representation of a LG webOS Smart TV.""" def __init__( @@ -144,8 +146,12 @@ def __init__( self._current_source = None self._source_list: dict = {} + self._supported_features: int | None = None + async def async_added_to_hass(self) -> None: """Connect and subscribe to dispatcher signals and state updates.""" + await super().async_added_to_hass() + self.async_on_remove( async_dispatcher_connect(self.hass, DOMAIN, self.async_signal_handler) ) @@ -154,6 +160,12 @@ async def async_added_to_hass(self) -> None: self.async_handle_state_update ) + if self._supported_features is not None: + return + + if (state := await self.async_get_last_state()) is not None: + self._supported_features = state.attributes.get(ATTR_SUPPORTED_FEATURES) + async def async_will_remove_from_hass(self) -> None: """Call disconnect on removal.""" self._client.unregister_state_update_callback(self.async_handle_state_update) @@ -313,6 +325,9 @@ def media_image_url(self) -> str | None: @property def supported_features(self) -> int: """Flag media player features that are supported.""" + if self.state == STATE_OFF and self._supported_features is not None: + return self._supported_features + supported = SUPPORT_WEBOSTV if self._client.sound_output in ("external_arc", "external_speaker"): @@ -323,6 +338,9 @@ def supported_features(self) -> int: if self._wrapper.turn_on: supported |= SUPPORT_TURN_ON + if self.state != STATE_OFF: + self._supported_features = supported + return supported @property diff --git a/tests/components/webostv/test_media_player.py b/tests/components/webostv/test_media_player.py index c249b491d9a8f7..450d3d90377a8a 100644 --- a/tests/components/webostv/test_media_player.py +++ b/tests/components/webostv/test_media_player.py @@ -556,3 +556,57 @@ async def test_supported_features(hass, client, monkeypatch): attrs = hass.states.get(ENTITY_ID).attributes assert attrs[ATTR_SUPPORTED_FEATURES] == supported + + +async def test_cached_supported_features(hass, client, monkeypatch): + """Test test supported features.""" + monkeypatch.setattr(client, "is_on", False) + monkeypatch.setattr(client, "sound_output", None) + await setup_webostv(hass) + await client.mock_state_update() + + # TV off, support volume mute, step, set + supported = SUPPORT_WEBOSTV | SUPPORT_WEBOSTV_VOLUME | SUPPORT_VOLUME_SET + attrs = hass.states.get(ENTITY_ID).attributes + + assert attrs[ATTR_SUPPORTED_FEATURES] == supported + + # TV on, support volume mute, step + monkeypatch.setattr(client, "is_on", True) + monkeypatch.setattr(client, "sound_output", "external_speaker") + await client.mock_state_update() + + supported = SUPPORT_WEBOSTV | SUPPORT_WEBOSTV_VOLUME + attrs = hass.states.get(ENTITY_ID).attributes + + assert attrs[ATTR_SUPPORTED_FEATURES] == supported + + # TV off, support volume mute, step + monkeypatch.setattr(client, "is_on", False) + monkeypatch.setattr(client, "sound_output", None) + await client.mock_state_update() + + supported = SUPPORT_WEBOSTV | SUPPORT_WEBOSTV_VOLUME + attrs = hass.states.get(ENTITY_ID).attributes + + assert attrs[ATTR_SUPPORTED_FEATURES] == supported + + # TV on, support volume mute, step, set + monkeypatch.setattr(client, "is_on", True) + monkeypatch.setattr(client, "sound_output", "speaker") + await client.mock_state_update() + + supported = SUPPORT_WEBOSTV | SUPPORT_WEBOSTV_VOLUME | SUPPORT_VOLUME_SET + attrs = hass.states.get(ENTITY_ID).attributes + + assert attrs[ATTR_SUPPORTED_FEATURES] == supported + + # TV off, support volume mute, step, step, set + monkeypatch.setattr(client, "is_on", False) + monkeypatch.setattr(client, "sound_output", None) + await client.mock_state_update() + + supported = SUPPORT_WEBOSTV | SUPPORT_WEBOSTV_VOLUME | SUPPORT_VOLUME_SET + attrs = hass.states.get(ENTITY_ID).attributes + + assert attrs[ATTR_SUPPORTED_FEATURES] == supported From d82899ed2fa3bd6f02c20557061cc18de06d8e41 Mon Sep 17 00:00:00 2001 From: Vincent Le Bourlot Date: Mon, 7 Feb 2022 16:53:05 +0100 Subject: [PATCH 0397/1098] Add title placeholders to overkiz discovery (#65506) * add gateway_id to the config flow context name. * obfuscate gateway_id. * replace const with homeassistant.const. * Remove obfuscation of gateway_id. * fix style. * Add translatable title according to comments * Update homeassistant/components/overkiz/strings.json Co-authored-by: J. Nick Koston --- homeassistant/components/overkiz/config_flow.py | 10 +++++----- homeassistant/components/overkiz/strings.json | 1 + homeassistant/components/overkiz/translations/en.json | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/overkiz/config_flow.py b/homeassistant/components/overkiz/config_flow.py index 1a0a94198cc3fe..2f8dcc189211bb 100644 --- a/homeassistant/components/overkiz/config_flow.py +++ b/homeassistant/components/overkiz/config_flow.py @@ -128,11 +128,7 @@ async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowRes gateway_id = hostname[8:22] LOGGER.debug("DHCP discovery detected gateway %s", obfuscate_id(gateway_id)) - - await self.async_set_unique_id(gateway_id) - self._abort_if_unique_id_configured() - - return await self.async_step_user() + return await self._process_discovery(gateway_id) async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo @@ -144,9 +140,13 @@ async def async_step_zeroconf( gateway_id = properties["gateway_pin"] LOGGER.debug("ZeroConf discovery detected gateway %s", obfuscate_id(gateway_id)) + return await self._process_discovery(gateway_id) + async def _process_discovery(self, gateway_id: str) -> FlowResult: + """Handle discovery of a gateway.""" await self.async_set_unique_id(gateway_id) self._abort_if_unique_id_configured() + self.context["title_placeholders"] = {"gateway_id": gateway_id} return await self.async_step_user() diff --git a/homeassistant/components/overkiz/strings.json b/homeassistant/components/overkiz/strings.json index 2bef16ec2dde9b..87487d53c66927 100644 --- a/homeassistant/components/overkiz/strings.json +++ b/homeassistant/components/overkiz/strings.json @@ -1,5 +1,6 @@ { "config": { + "flow_title": "Gateway: {gateway_id}", "step": { "user": { "description": "The Overkiz platform is used by various vendors like Somfy (Connexoon / TaHoma), Hitachi (Hi Kumo), Rexel (Energeasy Connect) and Atlantic (Cozytouch). Enter your application credentials and select your hub.", diff --git a/homeassistant/components/overkiz/translations/en.json b/homeassistant/components/overkiz/translations/en.json index 4238090458359d..c9551aa555cbc5 100644 --- a/homeassistant/components/overkiz/translations/en.json +++ b/homeassistant/components/overkiz/translations/en.json @@ -12,6 +12,7 @@ "too_many_requests": "Too many requests, try again later", "unknown": "Unexpected error" }, + "flow_title": "Gateway: {gateway_id}", "step": { "user": { "data": { From ace74279f1ec1ce5ec637f38a1e3f57eacae1179 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 7 Feb 2022 10:44:52 -0600 Subject: [PATCH 0398/1098] Move WiZ socket ident to upstream lib (#65958) --- homeassistant/components/wiz/const.py | 2 -- homeassistant/components/wiz/light.py | 4 ++-- homeassistant/components/wiz/switch.py | 5 +++-- homeassistant/components/wiz/utils.py | 8 ++------ 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/wiz/const.py b/homeassistant/components/wiz/const.py index a88c445614ae7e..e1a9848263583a 100644 --- a/homeassistant/components/wiz/const.py +++ b/homeassistant/components/wiz/const.py @@ -9,8 +9,6 @@ DISCOVER_SCAN_TIMEOUT = 10 DISCOVERY_INTERVAL = timedelta(minutes=15) -SOCKET_DEVICE_STR = "_SOCKET_" - WIZ_EXCEPTIONS = ( OSError, WizLightTimeOutError, diff --git a/homeassistant/components/wiz/light.py b/homeassistant/components/wiz/light.py index bc5ac078ec78a0..2c4485c5b72a94 100644 --- a/homeassistant/components/wiz/light.py +++ b/homeassistant/components/wiz/light.py @@ -28,7 +28,7 @@ color_temperature_mired_to_kelvin, ) -from .const import DOMAIN, SOCKET_DEVICE_STR +from .const import DOMAIN from .entity import WizToggleEntity from .models import WizData @@ -42,7 +42,7 @@ async def async_setup_entry( ) -> None: """Set up the WiZ Platform from config_flow.""" wiz_data: WizData = hass.data[DOMAIN][entry.entry_id] - if SOCKET_DEVICE_STR not in wiz_data.bulb.bulbtype.name: + if wiz_data.bulb.bulbtype.bulb_type != BulbClass.SOCKET: async_add_entities([WizBulbEntity(wiz_data, entry.title)]) diff --git a/homeassistant/components/wiz/switch.py b/homeassistant/components/wiz/switch.py index e6e34c73c3b324..ffe75910b40af2 100644 --- a/homeassistant/components/wiz/switch.py +++ b/homeassistant/components/wiz/switch.py @@ -4,13 +4,14 @@ from typing import Any from pywizlight import PilotBuilder +from pywizlight.bulblibrary import BulbClass from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN, SOCKET_DEVICE_STR +from .const import DOMAIN from .entity import WizToggleEntity from .models import WizData @@ -22,7 +23,7 @@ async def async_setup_entry( ) -> None: """Set up the WiZ switch platform.""" wiz_data: WizData = hass.data[DOMAIN][entry.entry_id] - if SOCKET_DEVICE_STR in wiz_data.bulb.bulbtype.name: + if wiz_data.bulb.bulbtype.bulb_type == BulbClass.SOCKET: async_add_entities([WizSocketEntity(wiz_data, entry.title)]) diff --git a/homeassistant/components/wiz/utils.py b/homeassistant/components/wiz/utils.py index ceaab797d5e6a0..42be575813037e 100644 --- a/homeassistant/components/wiz/utils.py +++ b/homeassistant/components/wiz/utils.py @@ -3,7 +3,7 @@ from pywizlight import BulbType -from .const import DEFAULT_NAME, SOCKET_DEVICE_STR +from .const import DEFAULT_NAME def _short_mac(mac: str) -> str: @@ -13,8 +13,4 @@ def _short_mac(mac: str) -> str: def name_from_bulb_type_and_mac(bulb_type: BulbType, mac: str) -> str: """Generate a name from bulb_type and mac.""" - if SOCKET_DEVICE_STR in bulb_type.name: - description = "Socket" - else: - description = bulb_type.bulb_type.value - return f"{DEFAULT_NAME} {description} {_short_mac(mac)}" + return f"{DEFAULT_NAME} {bulb_type.bulb_type.value} {_short_mac(mac)}" From 910b1f1ec8108a6576ba871b16235a12e92daa73 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 7 Feb 2022 18:11:52 +0100 Subject: [PATCH 0399/1098] Speed up deletion of duplicated statistics (#66014) --- .../components/recorder/statistics.py | 15 +- tests/components/recorder/test_statistics.py | 173 ++++++++++++++++++ 2 files changed, 181 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index 0bf10ca71c6882..6c305242f5fd0d 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -290,7 +290,7 @@ def _find_duplicates( ) .filter(subquery.c.is_duplicate == 1) .order_by(table.metadata_id, table.start, table.id.desc()) - .limit(MAX_ROWS_TO_PURGE) + .limit(1000 * MAX_ROWS_TO_PURGE) ) duplicates = execute(query) original_as_dict = {} @@ -343,12 +343,13 @@ def _delete_duplicates_from_table( if not duplicate_ids: break all_non_identical_duplicates.extend(non_identical_duplicates) - deleted_rows = ( - session.query(table) - .filter(table.id.in_(duplicate_ids)) - .delete(synchronize_session=False) - ) - total_deleted_rows += deleted_rows + for i in range(0, len(duplicate_ids), MAX_ROWS_TO_PURGE): + deleted_rows = ( + session.query(table) + .filter(table.id.in_(duplicate_ids[i : i + MAX_ROWS_TO_PURGE])) + .delete(synchronize_session=False) + ) + total_deleted_rows += deleted_rows return (total_deleted_rows, all_non_identical_duplicates) diff --git a/tests/components/recorder/test_statistics.py b/tests/components/recorder/test_statistics.py index 25590c712d9ed5..c96465a671f6a9 100644 --- a/tests/components/recorder/test_statistics.py +++ b/tests/components/recorder/test_statistics.py @@ -855,6 +855,179 @@ def test_delete_duplicates(caplog, tmpdir): assert "Found duplicated" not in caplog.text +def test_delete_duplicates_many(caplog, tmpdir): + """Test removal of duplicated statistics.""" + test_db_file = tmpdir.mkdir("sqlite").join("test_run_info.db") + dburl = f"{SQLITE_URL_PREFIX}//{test_db_file}" + + module = "tests.components.recorder.models_schema_23" + importlib.import_module(module) + old_models = sys.modules[module] + + period1 = dt_util.as_utc(dt_util.parse_datetime("2021-09-01 00:00:00")) + period2 = dt_util.as_utc(dt_util.parse_datetime("2021-09-30 23:00:00")) + period3 = dt_util.as_utc(dt_util.parse_datetime("2021-10-01 00:00:00")) + period4 = dt_util.as_utc(dt_util.parse_datetime("2021-10-31 23:00:00")) + + external_energy_statistics_1 = ( + { + "start": period1, + "last_reset": None, + "state": 0, + "sum": 2, + }, + { + "start": period2, + "last_reset": None, + "state": 1, + "sum": 3, + }, + { + "start": period3, + "last_reset": None, + "state": 2, + "sum": 4, + }, + { + "start": period4, + "last_reset": None, + "state": 3, + "sum": 5, + }, + { + "start": period4, + "last_reset": None, + "state": 3, + "sum": 5, + }, + ) + external_energy_metadata_1 = { + "has_mean": False, + "has_sum": True, + "name": "Total imported energy", + "source": "test", + "statistic_id": "test:total_energy_import_tariff_1", + "unit_of_measurement": "kWh", + } + external_energy_statistics_2 = ( + { + "start": period1, + "last_reset": None, + "state": 0, + "sum": 20, + }, + { + "start": period2, + "last_reset": None, + "state": 1, + "sum": 30, + }, + { + "start": period3, + "last_reset": None, + "state": 2, + "sum": 40, + }, + { + "start": period4, + "last_reset": None, + "state": 3, + "sum": 50, + }, + { + "start": period4, + "last_reset": None, + "state": 3, + "sum": 50, + }, + ) + external_energy_metadata_2 = { + "has_mean": False, + "has_sum": True, + "name": "Total imported energy", + "source": "test", + "statistic_id": "test:total_energy_import_tariff_2", + "unit_of_measurement": "kWh", + } + external_co2_statistics = ( + { + "start": period1, + "last_reset": None, + "mean": 10, + }, + { + "start": period2, + "last_reset": None, + "mean": 30, + }, + { + "start": period3, + "last_reset": None, + "mean": 60, + }, + { + "start": period4, + "last_reset": None, + "mean": 90, + }, + ) + external_co2_metadata = { + "has_mean": True, + "has_sum": False, + "name": "Fossil percentage", + "source": "test", + "statistic_id": "test:fossil_percentage", + "unit_of_measurement": "%", + } + + # Create some duplicated statistics with schema version 23 + with patch.object(recorder, "models", old_models), patch.object( + recorder.migration, "SCHEMA_VERSION", old_models.SCHEMA_VERSION + ), patch( + "homeassistant.components.recorder.create_engine", new=_create_engine_test + ): + hass = get_test_home_assistant() + setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) + wait_recording_done(hass) + wait_recording_done(hass) + + with session_scope(hass=hass) as session: + session.add( + recorder.models.StatisticsMeta.from_meta(external_energy_metadata_1) + ) + session.add( + recorder.models.StatisticsMeta.from_meta(external_energy_metadata_2) + ) + session.add(recorder.models.StatisticsMeta.from_meta(external_co2_metadata)) + with session_scope(hass=hass) as session: + for stat in external_energy_statistics_1: + session.add(recorder.models.Statistics.from_stats(1, stat)) + for _ in range(3000): + session.add( + recorder.models.Statistics.from_stats( + 1, external_energy_statistics_1[-1] + ) + ) + for stat in external_energy_statistics_2: + session.add(recorder.models.Statistics.from_stats(2, stat)) + for stat in external_co2_statistics: + session.add(recorder.models.Statistics.from_stats(3, stat)) + + hass.stop() + + # Test that the duplicates are removed during migration from schema 23 + hass = get_test_home_assistant() + setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) + hass.start() + wait_recording_done(hass) + wait_recording_done(hass) + hass.stop() + + assert "Deleted 3002 duplicated statistics rows" in caplog.text + assert "Found non identical" not in caplog.text + assert "Found duplicated" not in caplog.text + + @pytest.mark.freeze_time("2021-08-01 00:00:00+00:00") def test_delete_duplicates_non_identical(caplog, tmpdir): """Test removal of duplicated statistics.""" From 480ce84b8ab696fd9adb47726f02503ce593053f Mon Sep 17 00:00:00 2001 From: G Johansson Date: Mon, 7 Feb 2022 18:59:06 +0100 Subject: [PATCH 0400/1098] Improve code quality filesize (#65240) --- homeassistant/components/filesize/sensor.py | 80 ++++++++++----------- tests/components/filesize/test_sensor.py | 55 ++++++++++++-- 2 files changed, 87 insertions(+), 48 deletions(-) diff --git a/homeassistant/components/filesize/sensor.py b/homeassistant/components/filesize/sensor.py index e409b346c6d7b6..56542a0aadd576 100644 --- a/homeassistant/components/filesize/sensor.py +++ b/homeassistant/components/filesize/sensor.py @@ -4,10 +4,14 @@ import datetime import logging import os +import pathlib import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity +from homeassistant.components.sensor import ( + PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA, + SensorEntity, +) from homeassistant.const import DATA_MEGABYTES from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv @@ -23,7 +27,7 @@ CONF_FILE_PATHS = "file_paths" ICON = "mdi:file" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( +PLATFORM_SCHEMA = PARENT_PLATFORM_SCHEMA.extend( {vol.Required(CONF_FILE_PATHS): vol.All(cv.ensure_list, [cv.isfile])} ) @@ -39,11 +43,23 @@ def setup_platform( setup_reload_service(hass, DOMAIN, PLATFORMS) sensors = [] + paths = set() for path in config[CONF_FILE_PATHS]: + try: + fullpath = str(pathlib.Path(path).absolute()) + except OSError as error: + _LOGGER.error("Can not access file %s, error %s", path, error) + continue + + if fullpath in paths: + continue + paths.add(fullpath) + if not hass.config.is_allowed_path(path): _LOGGER.error("Filepath %s is not valid or allowed", path) continue - sensors.append(Filesize(path)) + + sensors.append(Filesize(fullpath)) if sensors: add_entities(sensors, True) @@ -52,48 +68,28 @@ def setup_platform( class Filesize(SensorEntity): """Encapsulates file size information.""" - def __init__(self, path): + _attr_native_unit_of_measurement = DATA_MEGABYTES + _attr_icon = ICON + + def __init__(self, path: str) -> None: """Initialize the data object.""" self._path = path # Need to check its a valid path - self._size = None - self._last_updated = None - self._name = path.split("/")[-1] - self._unit_of_measurement = DATA_MEGABYTES + self._attr_name = path.split("/")[-1] - def update(self): + def update(self) -> None: """Update the sensor.""" - statinfo = os.stat(self._path) - self._size = statinfo.st_size - last_updated = datetime.datetime.fromtimestamp(statinfo.st_mtime) - self._last_updated = last_updated.isoformat() - - @property - def name(self): - """Return the name of the sensor.""" - return self._name - - @property - def native_value(self): - """Return the size of the file in MB.""" - decimals = 2 - state_mb = round(self._size / 1e6, decimals) - return state_mb - - @property - def icon(self): - """Icon to use in the frontend, if any.""" - return ICON - - @property - def extra_state_attributes(self): - """Return other details about the sensor state.""" - return { + try: + statinfo = os.stat(self._path) + except OSError as error: + _LOGGER.error("Can not retrieve file statistics %s", error) + self._attr_native_value = None + return + + size = statinfo.st_size + last_updated = datetime.datetime.fromtimestamp(statinfo.st_mtime).isoformat() + self._attr_native_value = round(size / 1e6, 2) if size else None + self._attr_extra_state_attributes = { "path": self._path, - "last_updated": self._last_updated, - "bytes": self._size, + "last_updated": last_updated, + "bytes": size, } - - @property - def native_unit_of_measurement(self): - """Return the unit of measurement of this entity, if any.""" - return self._unit_of_measurement diff --git a/tests/components/filesize/test_sensor.py b/tests/components/filesize/test_sensor.py index 72d0d112f1752e..fa85ce414377fd 100644 --- a/tests/components/filesize/test_sensor.py +++ b/tests/components/filesize/test_sensor.py @@ -7,7 +7,9 @@ from homeassistant import config as hass_config from homeassistant.components.filesize import DOMAIN from homeassistant.components.filesize.sensor import CONF_FILE_PATHS -from homeassistant.const import SERVICE_RELOAD +from homeassistant.const import SERVICE_RELOAD, STATE_UNKNOWN +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_component import async_update_entity from homeassistant.setup import async_setup_component from tests.common import get_fixture_path @@ -16,21 +18,21 @@ TEST_FILE = os.path.join(TEST_DIR, "mock_file_test_filesize.txt") -def create_file(path): +def create_file(path) -> None: """Create a test file.""" with open(path, "w") as test_file: test_file.write("test") @pytest.fixture(autouse=True) -def remove_file(): +def remove_file() -> None: """Remove test file.""" yield if os.path.isfile(TEST_FILE): os.remove(TEST_FILE) -async def test_invalid_path(hass): +async def test_invalid_path(hass: HomeAssistant) -> None: """Test that an invalid path is caught.""" config = {"sensor": {"platform": "filesize", CONF_FILE_PATHS: ["invalid_path"]}} assert await async_setup_component(hass, "sensor", config) @@ -38,7 +40,21 @@ async def test_invalid_path(hass): assert len(hass.states.async_entity_ids("sensor")) == 0 -async def test_valid_path(hass): +async def test_cannot_access_file(hass: HomeAssistant) -> None: + """Test that an invalid path is caught.""" + config = {"sensor": {"platform": "filesize", CONF_FILE_PATHS: [TEST_FILE]}} + + with patch( + "homeassistant.components.filesize.sensor.pathlib", + side_effect=OSError("Can not access"), + ): + assert await async_setup_component(hass, "sensor", config) + await hass.async_block_till_done() + + assert len(hass.states.async_entity_ids("sensor")) == 0 + + +async def test_valid_path(hass: HomeAssistant) -> None: """Test for a valid path.""" create_file(TEST_FILE) config = {"sensor": {"platform": "filesize", CONF_FILE_PATHS: [TEST_FILE]}} @@ -51,7 +67,34 @@ async def test_valid_path(hass): assert state.attributes.get("bytes") == 4 -async def test_reload(hass, tmpdir): +async def test_state_unknown(hass: HomeAssistant, tmpdir: str) -> None: + """Verify we handle state unavailable.""" + create_file(TEST_FILE) + testfile = f"{tmpdir}/file" + await hass.async_add_executor_job(create_file, testfile) + with patch.object(hass.config, "is_allowed_path", return_value=True): + await async_setup_component( + hass, + "sensor", + { + "sensor": { + "platform": "filesize", + "file_paths": [testfile], + } + }, + ) + await hass.async_block_till_done() + + assert hass.states.get("sensor.file") + + await hass.async_add_executor_job(os.remove, testfile) + await async_update_entity(hass, "sensor.file") + + state = hass.states.get("sensor.file") + assert state.state == STATE_UNKNOWN + + +async def test_reload(hass: HomeAssistant, tmpdir: str) -> None: """Verify we can reload filesize sensors.""" testfile = f"{tmpdir}/file" await hass.async_add_executor_job(create_file, testfile) From 721d711762cdfd4fb608b6f6d6a46dc171a0ae05 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 7 Feb 2022 12:23:08 -0600 Subject: [PATCH 0401/1098] Add firmware and hardware version to WiZ (#66017) --- homeassistant/components/wiz/__init__.py | 2 +- homeassistant/components/wiz/entity.py | 7 ++++++- homeassistant/components/wiz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/wiz/__init__.py b/homeassistant/components/wiz/__init__.py index ce5d92c5f621b9..15b46a14ae0ac5 100644 --- a/homeassistant/components/wiz/__init__.py +++ b/homeassistant/components/wiz/__init__.py @@ -46,8 +46,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: _LOGGER.debug("Get bulb with IP: %s", ip_address) bulb = wizlight(ip_address) try: - await bulb.getMac() scenes = await bulb.getSupportedScenes() + await bulb.getMac() # ValueError gets thrown if the bulb type # cannot be determined on the first try. # This is likely because way the library diff --git a/homeassistant/components/wiz/entity.py b/homeassistant/components/wiz/entity.py index de0b0e3f947e4c..1ddaced401f7fc 100644 --- a/homeassistant/components/wiz/entity.py +++ b/homeassistant/components/wiz/entity.py @@ -23,11 +23,16 @@ def __init__(self, wiz_data: WizData, name: str) -> None: bulb_type: BulbType = self._device.bulbtype self._attr_unique_id = self._device.mac self._attr_name = name + hw_data = bulb_type.name.split("_") + board = hw_data.pop(0) + model = hw_data.pop(0) self._attr_device_info = DeviceInfo( connections={(CONNECTION_NETWORK_MAC, self._device.mac)}, name=name, manufacturer="WiZ", - model=bulb_type.name, + model=model, + hw_version=f"{board} {hw_data[0]}" if hw_data else board, + sw_version=bulb_type.fw_version, ) @callback diff --git a/homeassistant/components/wiz/manifest.json b/homeassistant/components/wiz/manifest.json index 90a6347dbc56aa..82e1393dec3a7e 100644 --- a/homeassistant/components/wiz/manifest.json +++ b/homeassistant/components/wiz/manifest.json @@ -8,7 +8,7 @@ ], "dependencies": ["network"], "documentation": "https://www.home-assistant.io/integrations/wiz", - "requirements": ["pywizlight==0.5.1"], + "requirements": ["pywizlight==0.5.2"], "iot_class": "local_push", "codeowners": ["@sbidy"] } diff --git a/requirements_all.txt b/requirements_all.txt index 5e2c0ac5bdaa84..c0cb46ef0ec072 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2051,7 +2051,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.5.1 +pywizlight==0.5.2 # homeassistant.components.xeoma pyxeoma==1.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bf0123fea980e5..04e53bd6f572d1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1276,7 +1276,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.5.1 +pywizlight==0.5.2 # homeassistant.components.zerproc pyzerproc==0.4.8 From 4732e370058a5907220aedcbfe5ab61e53c93f60 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 7 Feb 2022 20:08:54 +0100 Subject: [PATCH 0402/1098] Remove passing loop into sleep in SamsungTV (#66030) --- homeassistant/components/samsungtv/media_player.py | 2 +- tests/components/samsungtv/test_media_player.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/samsungtv/media_player.py b/homeassistant/components/samsungtv/media_player.py index 7fcc2268d9b167..e856d746b3d507 100644 --- a/homeassistant/components/samsungtv/media_player.py +++ b/homeassistant/components/samsungtv/media_player.py @@ -245,7 +245,7 @@ async def async_play_media( for digit in media_id: await self.hass.async_add_executor_job(self.send_key, f"KEY_{digit}") - await asyncio.sleep(KEY_PRESS_TIMEOUT, self.hass.loop) + await asyncio.sleep(KEY_PRESS_TIMEOUT) await self.hass.async_add_executor_job(self.send_key, "KEY_ENTER") def _wake_on_lan(self) -> None: diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py index f64634acd3bca9..dca7e4366f3e11 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -713,9 +713,9 @@ async def test_play_media(hass, remote): asyncio_sleep = asyncio.sleep sleeps = [] - async def sleep(duration, loop): + async def sleep(duration): sleeps.append(duration) - await asyncio_sleep(0, loop=loop) + await asyncio_sleep(0) await setup_samsungtv(hass, MOCK_CONFIG) with patch("asyncio.sleep", new=sleep): From 7cc6770f8356068a5d5b0149178fd9ac06c9bc10 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 7 Feb 2022 20:24:30 +0100 Subject: [PATCH 0403/1098] Revert "Make idle chromecasts appear as idle instead of off" (#66005) --- homeassistant/components/cast/media_player.py | 3 +- tests/components/cast/test_media_player.py | 30 +++++++++---------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 975fa3f58362b0..d418373e599ade 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -50,6 +50,7 @@ CAST_APP_ID_HOMEASSISTANT_LOVELACE, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, + STATE_OFF, STATE_PAUSED, STATE_PLAYING, ) @@ -636,7 +637,7 @@ def state(self): return STATE_PLAYING return STATE_IDLE if self._chromecast is not None and self._chromecast.is_idle: - return STATE_IDLE + return STATE_OFF return None @property diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index bd22a558314e74..51fe4a086a610a 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -595,7 +595,7 @@ async def test_entity_availability(hass: HomeAssistant): conn_status_cb(connection_status) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert state.state == "idle" + assert state.state == "off" connection_status = MagicMock() connection_status.status = "DISCONNECTED" @@ -624,7 +624,7 @@ async def test_entity_cast_status(hass: HomeAssistant): state = hass.states.get(entity_id) assert state is not None assert state.name == "Speaker" - assert state.state == "idle" + assert state.state == "off" assert entity_id == reg.async_get_entity_id("media_player", "cast", str(info.uuid)) # No media status, pause, play, stop not supported @@ -642,8 +642,8 @@ async def test_entity_cast_status(hass: HomeAssistant): cast_status_cb(cast_status) await hass.async_block_till_done() state = hass.states.get(entity_id) - # Volume not hidden even if no app is active - assert state.attributes.get("volume_level") == 0.5 + # Volume hidden if no app is active + assert state.attributes.get("volume_level") is None assert not state.attributes.get("is_volume_muted") chromecast.app_id = "1234" @@ -747,7 +747,7 @@ async def test_supported_features( state = hass.states.get(entity_id) assert state is not None assert state.name == "Speaker" - assert state.state == "idle" + assert state.state == "off" assert state.attributes.get("supported_features") == supported_features_no_media media_status = MagicMock(images=None) @@ -882,7 +882,7 @@ async def test_entity_play_media(hass: HomeAssistant, quick_play_mock): state = hass.states.get(entity_id) assert state is not None assert state.name == "Speaker" - assert state.state == "idle" + assert state.state == "off" assert entity_id == reg.async_get_entity_id("media_player", "cast", str(info.uuid)) # Play_media @@ -928,7 +928,7 @@ async def test_entity_play_media_cast(hass: HomeAssistant, quick_play_mock): state = hass.states.get(entity_id) assert state is not None assert state.name == "Speaker" - assert state.state == "idle" + assert state.state == "off" assert entity_id == reg.async_get_entity_id("media_player", "cast", str(info.uuid)) # Play_media - cast with app ID @@ -970,7 +970,7 @@ async def test_entity_play_media_cast_invalid(hass, caplog, quick_play_mock): state = hass.states.get(entity_id) assert state is not None assert state.name == "Speaker" - assert state.state == "idle" + assert state.state == "off" assert entity_id == reg.async_get_entity_id("media_player", "cast", str(info.uuid)) # play_media - media_type cast with invalid JSON @@ -1042,7 +1042,7 @@ async def test_entity_media_content_type(hass: HomeAssistant): state = hass.states.get(entity_id) assert state is not None assert state.name == "Speaker" - assert state.state == "idle" + assert state.state == "off" assert entity_id == reg.async_get_entity_id("media_player", "cast", str(info.uuid)) media_status = MagicMock(images=None) @@ -1213,7 +1213,7 @@ async def test_entity_media_states(hass: HomeAssistant, app_id, state_no_media): state = hass.states.get(entity_id) assert state is not None assert state.name == "Speaker" - assert state.state == "idle" + assert state.state == "off" assert entity_id == reg.async_get_entity_id("media_player", "cast", str(info.uuid)) # App id updated, but no media status @@ -1258,7 +1258,7 @@ async def test_entity_media_states(hass: HomeAssistant, app_id, state_no_media): cast_status_cb(cast_status) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert state.state == "idle" + assert state.state == "off" # No cast status chromecast.is_idle = False @@ -1286,7 +1286,7 @@ async def test_entity_media_states_lovelace_app(hass: HomeAssistant): state = hass.states.get(entity_id) assert state is not None assert state.name == "Speaker" - assert state.state == "idle" + assert state.state == "off" assert entity_id == reg.async_get_entity_id("media_player", "cast", str(info.uuid)) chromecast.app_id = CAST_APP_ID_HOMEASSISTANT_LOVELACE @@ -1326,7 +1326,7 @@ async def test_entity_media_states_lovelace_app(hass: HomeAssistant): media_status_cb(media_status) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert state.state == "idle" + assert state.state == "off" chromecast.is_idle = False media_status_cb(media_status) @@ -1355,7 +1355,7 @@ async def test_group_media_states(hass, mz_mock): state = hass.states.get(entity_id) assert state is not None assert state.name == "Speaker" - assert state.state == "idle" + assert state.state == "off" assert entity_id == reg.async_get_entity_id("media_player", "cast", str(info.uuid)) group_media_status = MagicMock(images=None) @@ -1406,7 +1406,7 @@ async def test_group_media_control(hass, mz_mock, quick_play_mock): state = hass.states.get(entity_id) assert state is not None assert state.name == "Speaker" - assert state.state == "idle" + assert state.state == "off" assert entity_id == reg.async_get_entity_id("media_player", "cast", str(info.uuid)) group_media_status = MagicMock(images=None) From 95a890c6e121022bf1d8aa36fdec782662f7c89c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 7 Feb 2022 15:44:02 -0800 Subject: [PATCH 0404/1098] Get_url to prefer external URL if SSL configured (#66039) --- homeassistant/core.py | 6 +++--- homeassistant/helpers/network.py | 10 ++++++++-- tests/helpers/test_network.py | 19 +++++++++++++++++++ 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 27906e0401f3a1..b8d159893fe214 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -89,7 +89,7 @@ # Typing imports that create a circular dependency if TYPE_CHECKING: from .auth import AuthManager - from .components.http import HomeAssistantHTTP + from .components.http import ApiConfig, HomeAssistantHTTP from .config_entries import ConfigEntries @@ -1701,8 +1701,8 @@ def __init__(self, hass: HomeAssistant) -> None: # List of loaded components self.components: set[str] = set() - # API (HTTP) server configuration, see components.http.ApiConfig - self.api: Any | None = None + # API (HTTP) server configuration + self.api: ApiConfig | None = None # Directory that holds the configuration self.config_dir: str | None = None diff --git a/homeassistant/helpers/network.py b/homeassistant/helpers/network.py index 0b2804f821df14..0c52012e43cba5 100644 --- a/homeassistant/helpers/network.py +++ b/homeassistant/helpers/network.py @@ -41,14 +41,20 @@ def get_url( allow_internal: bool = True, allow_external: bool = True, allow_cloud: bool = True, - allow_ip: bool = True, - prefer_external: bool = False, + allow_ip: bool | None = None, + prefer_external: bool | None = None, prefer_cloud: bool = False, ) -> str: """Get a URL to this instance.""" if require_current_request and http.current_request.get() is None: raise NoURLAvailableError + if prefer_external is None: + prefer_external = hass.config.api is not None and hass.config.api.use_ssl + + if allow_ip is None: + allow_ip = hass.config.api is None or not hass.config.api.use_ssl + order = [TYPE_URL_INTERNAL, TYPE_URL_EXTERNAL] if prefer_external: order.reverse() diff --git a/tests/helpers/test_network.py b/tests/helpers/test_network.py index 05c72f10db568f..7e9086f446770c 100644 --- a/tests/helpers/test_network.py +++ b/tests/helpers/test_network.py @@ -480,6 +480,12 @@ async def test_get_url(hass: HomeAssistant): get_url(hass, prefer_external=True, allow_external=False) == "http://example.local" ) + # Prefer external defaults to True if use_ssl=True + hass.config.api = Mock(use_ssl=True) + assert get_url(hass) == "https://example.com" + hass.config.api = Mock(use_ssl=False) + assert get_url(hass) == "http://example.local" + hass.config.api = None with pytest.raises(NoURLAvailableError): get_url(hass, allow_external=False, require_ssl=True) @@ -519,6 +525,19 @@ async def test_get_url(hass: HomeAssistant): ), pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_current_request=True) + # Test allow_ip defaults when SSL specified + await async_process_ha_core_config( + hass, + {"external_url": "https://1.1.1.1"}, + ) + assert hass.config.external_url == "https://1.1.1.1" + assert get_url(hass, allow_internal=False) == "https://1.1.1.1" + hass.config.api = Mock(use_ssl=False) + assert get_url(hass, allow_internal=False) == "https://1.1.1.1" + hass.config.api = Mock(use_ssl=True) + with pytest.raises(NoURLAvailableError): + assert get_url(hass, allow_internal=False) + async def test_get_request_host(hass: HomeAssistant): """Test getting the host of the current web request from the request context.""" From 175812d9e1e978c7cf763f8e023bc5f340022707 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 7 Feb 2022 17:45:40 -0600 Subject: [PATCH 0405/1098] Fix missing exception catch in august to prevent failed setup (#66045) --- homeassistant/components/august/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index 5d51017bfd6b6a..9b340096fde499 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -45,7 +45,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: raise ConfigEntryAuthFailed from err except asyncio.TimeoutError as err: raise ConfigEntryNotReady("Timed out connecting to august api") from err - except (ClientResponseError, CannotConnect) as err: + except (AugustApiAIOHTTPError, ClientResponseError, CannotConnect) as err: raise ConfigEntryNotReady from err From 95cc677ba6e24d51a0493c9a625bb14d973878fc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 7 Feb 2022 17:45:50 -0600 Subject: [PATCH 0406/1098] Fix decoding discovery with old Magic Home firmwares (#66038) --- homeassistant/components/flux_led/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/flux_led/manifest.json b/homeassistant/components/flux_led/manifest.json index bd97d9e72d4376..a41fead628c615 100644 --- a/homeassistant/components/flux_led/manifest.json +++ b/homeassistant/components/flux_led/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "dependencies": ["network"], "documentation": "https://www.home-assistant.io/integrations/flux_led", - "requirements": ["flux_led==0.28.21"], + "requirements": ["flux_led==0.28.22"], "quality_scale": "platinum", "codeowners": ["@icemanch", "@bdraco"], "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index c0cb46ef0ec072..eacf7a910066eb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -681,7 +681,7 @@ fjaraskupan==1.0.2 flipr-api==1.4.1 # homeassistant.components.flux_led -flux_led==0.28.21 +flux_led==0.28.22 # homeassistant.components.homekit fnvhash==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 04e53bd6f572d1..7e78e94010ca75 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -427,7 +427,7 @@ fjaraskupan==1.0.2 flipr-api==1.4.1 # homeassistant.components.flux_led -flux_led==0.28.21 +flux_led==0.28.22 # homeassistant.components.homekit fnvhash==0.1.0 From 39ed628cca80867496aebc0bc1841038dd7cb6a9 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 8 Feb 2022 00:46:40 +0100 Subject: [PATCH 0407/1098] Suppress unwanted error messages during recorder migration (#66004) --- .../components/recorder/migration.py | 128 +++++++++--------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index b8f15a811db7f2..48dca4d42ed691 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -101,15 +101,15 @@ def _create_index(instance, table_name, index_name): "be patient!", index_name, ) - try: - with session_scope(session=instance.get_session()) as session: + with session_scope(session=instance.get_session()) as session: + try: connection = session.connection() index.create(connection) - except (InternalError, OperationalError, ProgrammingError) as err: - raise_if_exception_missing_str(err, ["already exists", "duplicate"]) - _LOGGER.warning( - "Index %s already exists on %s, continuing", index_name, table_name - ) + except (InternalError, OperationalError, ProgrammingError) as err: + raise_if_exception_missing_str(err, ["already exists", "duplicate"]) + _LOGGER.warning( + "Index %s already exists on %s, continuing", index_name, table_name + ) _LOGGER.debug("Finished creating %s", index_name) @@ -129,19 +129,19 @@ def _drop_index(instance, table_name, index_name): success = False # Engines like DB2/Oracle - try: - with session_scope(session=instance.get_session()) as session: + with session_scope(session=instance.get_session()) as session: + try: connection = session.connection() connection.execute(text(f"DROP INDEX {index_name}")) - except SQLAlchemyError: - pass - else: - success = True + except SQLAlchemyError: + pass + else: + success = True # Engines like SQLite, SQL Server if not success: - try: - with session_scope(session=instance.get_session()) as session: + with session_scope(session=instance.get_session()) as session: + try: connection = session.connection() connection.execute( text( @@ -150,15 +150,15 @@ def _drop_index(instance, table_name, index_name): ) ) ) - except SQLAlchemyError: - pass - else: - success = True + except SQLAlchemyError: + pass + else: + success = True if not success: # Engines like MySQL, MS Access - try: - with session_scope(session=instance.get_session()) as session: + with session_scope(session=instance.get_session()) as session: + try: connection = session.connection() connection.execute( text( @@ -167,10 +167,10 @@ def _drop_index(instance, table_name, index_name): ) ) ) - except SQLAlchemyError: - pass - else: - success = True + except SQLAlchemyError: + pass + else: + success = True if success: _LOGGER.debug( @@ -203,8 +203,8 @@ def _add_columns(instance, table_name, columns_def): columns_def = [f"ADD {col_def}" for col_def in columns_def] - try: - with session_scope(session=instance.get_session()) as session: + with session_scope(session=instance.get_session()) as session: + try: connection = session.connection() connection.execute( text( @@ -214,14 +214,14 @@ def _add_columns(instance, table_name, columns_def): ) ) return - except (InternalError, OperationalError, ProgrammingError): - # Some engines support adding all columns at once, - # this error is when they don't - _LOGGER.info("Unable to use quick column add. Adding 1 by 1") + except (InternalError, OperationalError, ProgrammingError): + # Some engines support adding all columns at once, + # this error is when they don't + _LOGGER.info("Unable to use quick column add. Adding 1 by 1") for column_def in columns_def: - try: - with session_scope(session=instance.get_session()) as session: + with session_scope(session=instance.get_session()) as session: + try: connection = session.connection() connection.execute( text( @@ -230,13 +230,13 @@ def _add_columns(instance, table_name, columns_def): ) ) ) - except (InternalError, OperationalError, ProgrammingError) as err: - raise_if_exception_missing_str(err, ["already exists", "duplicate"]) - _LOGGER.warning( - "Column %s already exists on %s, continuing", - column_def.split(" ")[1], - table_name, - ) + except (InternalError, OperationalError, ProgrammingError) as err: + raise_if_exception_missing_str(err, ["already exists", "duplicate"]) + _LOGGER.warning( + "Column %s already exists on %s, continuing", + column_def.split(" ")[1], + table_name, + ) def _modify_columns(instance, engine, table_name, columns_def): @@ -271,8 +271,8 @@ def _modify_columns(instance, engine, table_name, columns_def): else: columns_def = [f"MODIFY {col_def}" for col_def in columns_def] - try: - with session_scope(session=instance.get_session()) as session: + with session_scope(session=instance.get_session()) as session: + try: connection = session.connection() connection.execute( text( @@ -282,12 +282,12 @@ def _modify_columns(instance, engine, table_name, columns_def): ) ) return - except (InternalError, OperationalError): - _LOGGER.info("Unable to use quick column modify. Modifying 1 by 1") + except (InternalError, OperationalError): + _LOGGER.info("Unable to use quick column modify. Modifying 1 by 1") for column_def in columns_def: - try: - with session_scope(session=instance.get_session()) as session: + with session_scope(session=instance.get_session()) as session: + try: connection = session.connection() connection.execute( text( @@ -296,10 +296,10 @@ def _modify_columns(instance, engine, table_name, columns_def): ) ) ) - except (InternalError, OperationalError): - _LOGGER.exception( - "Could not modify column %s in table %s", column_def, table_name - ) + except (InternalError, OperationalError): + _LOGGER.exception( + "Could not modify column %s in table %s", column_def, table_name + ) def _update_states_table_with_foreign_key_options(instance, engine): @@ -330,17 +330,17 @@ def _update_states_table_with_foreign_key_options(instance, engine): ) for alter in alters: - try: - with session_scope(session=instance.get_session()) as session: + with session_scope(session=instance.get_session()) as session: + try: connection = session.connection() connection.execute(DropConstraint(alter["old_fk"])) for fkc in states_key_constraints: if fkc.column_keys == alter["columns"]: connection.execute(AddConstraint(fkc)) - except (InternalError, OperationalError): - _LOGGER.exception( - "Could not update foreign options in %s table", TABLE_STATES - ) + except (InternalError, OperationalError): + _LOGGER.exception( + "Could not update foreign options in %s table", TABLE_STATES + ) def _drop_foreign_key_constraints(instance, engine, table, columns): @@ -361,16 +361,16 @@ def _drop_foreign_key_constraints(instance, engine, table, columns): ) for drop in drops: - try: - with session_scope(session=instance.get_session()) as session: + with session_scope(session=instance.get_session()) as session: + try: connection = session.connection() connection.execute(DropConstraint(drop)) - except (InternalError, OperationalError): - _LOGGER.exception( - "Could not drop foreign constraints in %s table on %s", - TABLE_STATES, - columns, - ) + except (InternalError, OperationalError): + _LOGGER.exception( + "Could not drop foreign constraints in %s table on %s", + TABLE_STATES, + columns, + ) def _apply_update(instance, new_version, old_version): # noqa: C901 From 33623c3fe86e52a5cf89b5d495901a213eeb4a07 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 8 Feb 2022 00:47:23 +0100 Subject: [PATCH 0408/1098] Fix race in MQTT sensor and binary_sensor expire_after (#66040) --- homeassistant/components/mqtt/binary_sensor.py | 5 ++++- homeassistant/components/mqtt/sensor.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index b84ddaad4041d3..150ce3a7eb6541 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -134,6 +134,10 @@ async def async_added_to_hass(self) -> None: self._expired = False self._state = last_state.state + if self._expiration_trigger: + # We might have set up a trigger already after subscribing from + # super().async_added_to_hass() + self._expiration_trigger() self._expiration_trigger = async_track_point_in_utc_time( self.hass, self._value_is_expired, expiration_at ) @@ -190,7 +194,6 @@ def state_message_received(msg): # Reset old trigger if self._expiration_trigger: self._expiration_trigger() - self._expiration_trigger = None # Set new trigger expiration_at = dt_util.utcnow() + timedelta(seconds=expire_after) diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index a8cad4b09f896f..137627047bc6f3 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -180,6 +180,10 @@ async def async_added_to_hass(self) -> None: self._expired = False self._state = last_state.state + if self._expiration_trigger: + # We might have set up a trigger already after subscribing from + # super().async_added_to_hass() + self._expiration_trigger() self._expiration_trigger = async_track_point_in_utc_time( self.hass, self._value_is_expired, expiration_at ) @@ -227,7 +231,6 @@ def _update_state(msg): # Reset old trigger if self._expiration_trigger: self._expiration_trigger() - self._expiration_trigger = None # Set new trigger expiration_at = dt_util.utcnow() + timedelta(seconds=expire_after) From 36cfa7786d1078a8bdd9e6b7809dad6eea03e0eb Mon Sep 17 00:00:00 2001 From: jjlawren Date: Mon, 7 Feb 2022 18:00:57 -0600 Subject: [PATCH 0409/1098] Clean up Sonos unsubscribe/resubscribe exception handling and logging (#66025) --- homeassistant/components/sonos/speaker.py | 44 +++++++++++++---------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index e40fe901b0998c..ca1e5a0a91ccf2 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -399,13 +399,20 @@ async def async_unsubscribe(self) -> None: return_exceptions=True, ) for result in results: - if isinstance(result, Exception): - _LOGGER.debug( - "Unsubscribe failed for %s: %s", - self.zone_name, - result, - exc_info=result, - ) + if isinstance(result, asyncio.exceptions.TimeoutError): + message = "Request timed out" + exc_info = None + elif isinstance(result, Exception): + message = result + exc_info = result if not str(result) else None + else: + continue + _LOGGER.debug( + "Unsubscribe failed for %s: %s", + self.zone_name, + message, + exc_info=exc_info, + ) self._subscriptions = [] @callback @@ -422,19 +429,18 @@ async def async_resubscribe(self, exception: Exception) -> None: if not self.available: return - if getattr(exception, "status", None) == 412: - _LOGGER.warning( - "Subscriptions for %s failed, speaker may have lost power", - self.zone_name, - ) + if isinstance(exception, asyncio.exceptions.TimeoutError): + message = "Request timed out" + exc_info = None else: - exc_info = exception if _LOGGER.isEnabledFor(logging.DEBUG) else None - _LOGGER.error( - "Subscription renewals for %s failed: %s", - self.zone_name, - exception, - exc_info=exc_info, - ) + message = exception + exc_info = exception if not str(exception) else None + _LOGGER.warning( + "Subscription renewals for %s failed, marking unavailable: %s", + self.zone_name, + message, + exc_info=exc_info, + ) await self.async_offline() @callback From cf70ad10e86593b8339d30a25715036bd73e2390 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 8 Feb 2022 00:15:56 +0000 Subject: [PATCH 0410/1098] [ci skip] Translation update --- .../components/asuswrt/translations/el.json | 3 +- .../fritzbox_callmonitor/translations/el.json | 8 +++++ .../components/iss/translations/id.json | 7 +++++ .../components/mazda/translations/el.json | 3 ++ .../components/netgear/translations/id.json | 2 +- .../components/netgear/translations/ru.json | 2 +- .../components/overkiz/translations/ca.json | 1 + .../components/overkiz/translations/el.json | 1 + .../components/overkiz/translations/et.json | 1 + .../overkiz/translations/pt-BR.json | 1 + .../overkiz/translations/select.id.json | 8 ++++- .../overkiz/translations/sensor.id.json | 25 ++++++++++++++++ .../components/powerwall/translations/id.json | 14 ++++++++- .../powerwall/translations/pt-BR.json | 8 ++--- .../tuya/translations/select.el.json | 26 ++++++++++++++++ .../tuya/translations/select.ru.json | 24 +++++++++++++++ .../tuya/translations/sensor.ru.json | 5 ++++ .../components/wiz/translations/el.json | 9 ++++++ .../components/wiz/translations/pt-BR.json | 10 +++---- .../components/wiz/translations/ru.json | 30 +++++++++++++++++++ .../components/zwave_me/translations/ca.json | 20 +++++++++++++ .../components/zwave_me/translations/el.json | 18 +++++++++++ .../components/zwave_me/translations/et.json | 20 +++++++++++++ .../zwave_me/translations/pt-BR.json | 20 +++++++++++++ 24 files changed, 252 insertions(+), 14 deletions(-) create mode 100644 homeassistant/components/iss/translations/id.json create mode 100644 homeassistant/components/wiz/translations/ru.json create mode 100644 homeassistant/components/zwave_me/translations/ca.json create mode 100644 homeassistant/components/zwave_me/translations/el.json create mode 100644 homeassistant/components/zwave_me/translations/et.json create mode 100644 homeassistant/components/zwave_me/translations/pt-BR.json diff --git a/homeassistant/components/asuswrt/translations/el.json b/homeassistant/components/asuswrt/translations/el.json index 0b58b36014ac03..2ac0e09634c173 100644 --- a/homeassistant/components/asuswrt/translations/el.json +++ b/homeassistant/components/asuswrt/translations/el.json @@ -23,7 +23,8 @@ "consider_home": "\u0394\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1 \u03b1\u03bd\u03b1\u03bc\u03bf\u03bd\u03ae\u03c2 \u03c0\u03c1\u03b9\u03bd \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03b1\u03c0\u03bf\u03bc\u03ac\u03ba\u03c1\u03c5\u03bd\u03c3\u03b7 \u03bc\u03b9\u03b1\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", "dnsmasq": "\u0397 \u03b8\u03ad\u03c3\u03b7 \u03c3\u03c4\u03bf\u03bd \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c4\u03c9\u03bd \u03b1\u03c1\u03c7\u03b5\u03af\u03c9\u03bd dnsmasq.leases", "interface": "\u0397 \u03b4\u03b9\u03b1\u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03bf\u03c0\u03bf\u03af\u03b1 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03c3\u03c4\u03b1\u03c4\u03b9\u03c3\u03c4\u03b9\u03ba\u03ac \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 (\u03c0.\u03c7. eth0, eth1 \u03ba.\u03bb\u03c0.)", - "require_ip": "\u039f\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ad\u03c7\u03bf\u03c5\u03bd IP (\u03b3\u03b9\u03b1 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c3\u03b7\u03bc\u03b5\u03af\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2)" + "require_ip": "\u039f\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ad\u03c7\u03bf\u03c5\u03bd IP (\u03b3\u03b9\u03b1 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c3\u03b7\u03bc\u03b5\u03af\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2)", + "track_unknown": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03ac\u03b3\u03bd\u03c9\u03c3\u03c4\u03c9\u03bd / \u03b1\u03bd\u03ce\u03bd\u03c5\u03bc\u03c9\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd" }, "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 AsusWRT" } diff --git a/homeassistant/components/fritzbox_callmonitor/translations/el.json b/homeassistant/components/fritzbox_callmonitor/translations/el.json index c78531f645faa0..d6841e0f81b6c8 100644 --- a/homeassistant/components/fritzbox_callmonitor/translations/el.json +++ b/homeassistant/components/fritzbox_callmonitor/translations/el.json @@ -15,6 +15,14 @@ "options": { "error": { "malformed_prefixes": "\u03a4\u03b1 \u03c0\u03c1\u03bf\u03b8\u03ad\u03bc\u03b1\u03c4\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03bb\u03b1\u03bd\u03b8\u03b1\u03c3\u03bc\u03ad\u03bd\u03b1, \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae \u03c4\u03bf\u03c5\u03c2." + }, + "step": { + "init": { + "data": { + "prefixes": "\u03a0\u03c1\u03bf\u03b8\u03ad\u03bc\u03b1\u03c4\u03b1 (\u03bb\u03af\u03c3\u03c4\u03b1 \u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7 \u03bc\u03b5 \u03ba\u03cc\u03bc\u03bc\u03b1)" + }, + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c0\u03c1\u03bf\u03b8\u03b5\u03bc\u03ac\u03c4\u03c9\u03bd" + } } } } \ No newline at end of file diff --git a/homeassistant/components/iss/translations/id.json b/homeassistant/components/iss/translations/id.json new file mode 100644 index 00000000000000..3a870e47986bf6 --- /dev/null +++ b/homeassistant/components/iss/translations/id.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/el.json b/homeassistant/components/mazda/translations/el.json index 8fd08509174f43..1bafcdf8e44415 100644 --- a/homeassistant/components/mazda/translations/el.json +++ b/homeassistant/components/mazda/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "account_locked": "\u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ba\u03bb\u03b5\u03b9\u03b4\u03c9\u03bc\u03ad\u03bd\u03bf\u03c2. \u03a0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1." + }, "step": { "user": { "data": { diff --git a/homeassistant/components/netgear/translations/id.json b/homeassistant/components/netgear/translations/id.json index 6292d3c18dc7e4..7f3eb3a07969b0 100644 --- a/homeassistant/components/netgear/translations/id.json +++ b/homeassistant/components/netgear/translations/id.json @@ -15,7 +15,7 @@ "ssl": "Menggunakan sertifikat SSL", "username": "Nama Pengguna (Opsional)" }, - "description": "Host default: {host}\nPort default: {port}\nNama pengguna default: {username}", + "description": "Host default: {host}\nNama pengguna default: {username}", "title": "Netgear" } } diff --git a/homeassistant/components/netgear/translations/ru.json b/homeassistant/components/netgear/translations/ru.json index 035492a01fe906..e6e5f9a008a64b 100644 --- a/homeassistant/components/netgear/translations/ru.json +++ b/homeassistant/components/netgear/translations/ru.json @@ -15,7 +15,7 @@ "ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL", "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)" }, - "description": "\u0425\u043e\u0441\u0442 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e: {host}\n\u041f\u043e\u0440\u0442 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e: {port}\n\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e: {username}", + "description": "\u0425\u043e\u0441\u0442 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e: {host}\n\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e: {username}", "title": "Netgear" } } diff --git a/homeassistant/components/overkiz/translations/ca.json b/homeassistant/components/overkiz/translations/ca.json index 2c604126b3ca48..1e1bccd1fb7143 100644 --- a/homeassistant/components/overkiz/translations/ca.json +++ b/homeassistant/components/overkiz/translations/ca.json @@ -12,6 +12,7 @@ "too_many_requests": "Massa sol\u00b7licituds, torna-ho a provar m\u00e9s tard", "unknown": "Error inesperat" }, + "flow_title": "Passarel\u00b7la: {gateway_id}", "step": { "user": { "data": { diff --git a/homeassistant/components/overkiz/translations/el.json b/homeassistant/components/overkiz/translations/el.json index b1a57f0ead26f2..232e757b643f43 100644 --- a/homeassistant/components/overkiz/translations/el.json +++ b/homeassistant/components/overkiz/translations/el.json @@ -7,6 +7,7 @@ "server_in_maintenance": "\u039f \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03ba\u03c4\u03cc\u03c2 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c3\u03c5\u03bd\u03c4\u03ae\u03c1\u03b7\u03c3\u03b7", "too_many_requests": "\u03a0\u03ac\u03c1\u03b1 \u03c0\u03bf\u03bb\u03bb\u03ac \u03b1\u03b9\u03c4\u03ae\u03bc\u03b1\u03c4\u03b1, \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1." }, + "flow_title": "\u03a0\u03cd\u03bb\u03b7: {gateway_id}", "step": { "user": { "data": { diff --git a/homeassistant/components/overkiz/translations/et.json b/homeassistant/components/overkiz/translations/et.json index c1c00bc1df61cd..c19d9c39ca9851 100644 --- a/homeassistant/components/overkiz/translations/et.json +++ b/homeassistant/components/overkiz/translations/et.json @@ -12,6 +12,7 @@ "too_many_requests": "Liiga palju p\u00e4ringuid, proovi hiljem uuesti", "unknown": "Ootamatu t\u00f5rge" }, + "flow_title": "L\u00fc\u00fcs: {gateway_id}", "step": { "user": { "data": { diff --git a/homeassistant/components/overkiz/translations/pt-BR.json b/homeassistant/components/overkiz/translations/pt-BR.json index 545ddf77c8d0a8..2f2c335ebd534d 100644 --- a/homeassistant/components/overkiz/translations/pt-BR.json +++ b/homeassistant/components/overkiz/translations/pt-BR.json @@ -12,6 +12,7 @@ "too_many_requests": "Muitas solicita\u00e7\u00f5es, tente novamente mais tarde", "unknown": "Erro inesperado" }, + "flow_title": "Gateway: {gateway_id}", "step": { "user": { "data": { diff --git a/homeassistant/components/overkiz/translations/select.id.json b/homeassistant/components/overkiz/translations/select.id.json index a0d7fb1cbfe76c..4a8760b91c53ed 100644 --- a/homeassistant/components/overkiz/translations/select.id.json +++ b/homeassistant/components/overkiz/translations/select.id.json @@ -1,7 +1,13 @@ { "state": { "overkiz__memorized_simple_volume": { - "highest": "Tertinggi" + "highest": "Tertinggi", + "standard": "Standar" + }, + "overkiz__open_closed_pedestrian": { + "closed": "Tertutup", + "open": "Buka", + "pedestrian": "Pejalan Kaki" } } } \ No newline at end of file diff --git a/homeassistant/components/overkiz/translations/sensor.id.json b/homeassistant/components/overkiz/translations/sensor.id.json index dea99c02991bde..f51b432b64212d 100644 --- a/homeassistant/components/overkiz/translations/sensor.id.json +++ b/homeassistant/components/overkiz/translations/sensor.id.json @@ -1,5 +1,30 @@ { "state": { + "overkiz__battery": { + "full": "Penuh", + "low": "Rendah", + "normal": "Normal", + "verylow": "Sangat rendah" + }, + "overkiz__discrete_rssi_level": { + "good": "Bagus", + "low": "Rendah", + "normal": "Normal", + "verylow": "Sangat rendah" + }, + "overkiz__priority_lock_originator": { + "external_gateway": "Gateway eksternal", + "local_user": "Pengguna lokal", + "lsc": "LSC", + "myself": "Saya sendiri", + "rain": "Hujan", + "saac": "SAAC", + "security": "Keamanan" + }, + "overkiz__sensor_defect": { + "maintenance_required": "Diperlukan perawatan", + "no_defect": "Tidak ada cacat" + }, "overkiz__sensor_room": { "clean": "Bersih", "dirty": "Kotor" diff --git a/homeassistant/components/powerwall/translations/id.json b/homeassistant/components/powerwall/translations/id.json index 95f8d6009018a8..eeba049a9f2cce 100644 --- a/homeassistant/components/powerwall/translations/id.json +++ b/homeassistant/components/powerwall/translations/id.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", "reauth_successful": "Autentikasi ulang berhasil" }, "error": { @@ -10,8 +11,19 @@ "unknown": "Kesalahan yang tidak diharapkan", "wrong_version": "Powerwall Anda menggunakan versi perangkat lunak yang tidak didukung. Pertimbangkan untuk memutakhirkan atau melaporkan masalah ini agar dapat diatasi." }, - "flow_title": "{ip_address}", + "flow_title": "{name} ({ip_address})", "step": { + "confirm_discovery": { + "description": "Ingin menyiapkan {name} ({ip_address})?", + "title": "Hubungkan ke powerwall" + }, + "reauth_confim": { + "data": { + "password": "Kata Sandi" + }, + "description": "Kata sandi umumnya adalah 5 karakter terakhir dari nomor seri untuk Backup Gateway dan dapat ditemukan di aplikasi Tesla atau 5 karakter terakhir kata sandi yang ditemukan di dalam pintu untuk Backup Gateway 2.", + "title": "Autentikasi ulang powerwall" + }, "user": { "data": { "ip_address": "Alamat IP", diff --git a/homeassistant/components/powerwall/translations/pt-BR.json b/homeassistant/components/powerwall/translations/pt-BR.json index c3eea93473ad0a..ea40df76ae7288 100644 --- a/homeassistant/components/powerwall/translations/pt-BR.json +++ b/homeassistant/components/powerwall/translations/pt-BR.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", - "cannot_connect": "Falha em conectar", + "cannot_connect": "Falha ao conectar", "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { @@ -14,14 +14,14 @@ "flow_title": "{name} ( {ip_address})", "step": { "confirm_discovery": { - "description": "Deseja configurar {name} ( {ip_address} )?", - "title": "Conectar-se a owerwall" + "description": "Deseja configurar {name} ({ip_address})?", + "title": "Conecte-se ao powerwall" }, "reauth_confim": { "data": { "password": "Senha" }, - "description": "A senha geralmente s\u00e3o os \u00faltimos 5 caracteres do n\u00famero de s\u00e9rie do Backup Gateway e pode ser encontrada no aplicativo Tesla ou os \u00faltimos 5 caracteres da senha encontrados dentro da porta do Backup Gateway 2.", + "description": "A senha \u00e9 geralmente os \u00faltimos 5 caracteres do n\u00famero de s\u00e9rie do Backup Gateway e pode ser encontrada no aplicativo Tesla ou os \u00faltimos 5 caracteres da senha encontrada dentro da porta do Backup Gateway 2.", "title": "Reautentique o powerwall" }, "user": { diff --git a/homeassistant/components/tuya/translations/select.el.json b/homeassistant/components/tuya/translations/select.el.json index effe871645a2bb..27e26acabde6a9 100644 --- a/homeassistant/components/tuya/translations/select.el.json +++ b/homeassistant/components/tuya/translations/select.el.json @@ -34,6 +34,32 @@ "click": "\u03a0\u03af\u03b5\u03c3\u03b5", "switch": "\u0394\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b7\u03c2" }, + "tuya__humidifier_level": { + "level_1": "\u0395\u03c0\u03af\u03c0\u03b5\u03b4\u03bf 1", + "level_10": "\u0395\u03c0\u03af\u03c0\u03b5\u03b4\u03bf 10", + "level_2": "\u0395\u03c0\u03af\u03c0\u03b5\u03b4\u03bf 2", + "level_3": "\u0395\u03c0\u03af\u03c0\u03b5\u03b4\u03bf 3", + "level_4": "\u0395\u03c0\u03af\u03c0\u03b5\u03b4\u03bf 4", + "level_5": "\u0395\u03c0\u03af\u03c0\u03b5\u03b4\u03bf 5", + "level_6": "\u0395\u03c0\u03af\u03c0\u03b5\u03b4\u03bf 6", + "level_7": "\u0395\u03c0\u03af\u03c0\u03b5\u03b4\u03bf 7", + "level_8": "\u0395\u03c0\u03af\u03c0\u03b5\u03b4\u03bf 8", + "level_9": "\u0395\u03c0\u03af\u03c0\u03b5\u03b4\u03bf 9" + }, + "tuya__humidifier_moodlighting": { + "1": "\u0394\u03b9\u03ac\u03b8\u03b5\u03c3\u03b7 1", + "2": "\u0394\u03b9\u03ac\u03b8\u03b5\u03c3\u03b7 2", + "3": "\u0394\u03b9\u03ac\u03b8\u03b5\u03c3\u03b7 3", + "4": "\u0394\u03b9\u03ac\u03b8\u03b5\u03c3\u03b7 4", + "5": "\u0394\u03b9\u03ac\u03b8\u03b5\u03c3\u03b7 5" + }, + "tuya__humidifier_spray_mode": { + "auto": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03bf", + "health": "\u03a5\u03b3\u03b5\u03af\u03b1", + "humidity": "\u03a5\u03b3\u03c1\u03b1\u03c3\u03af\u03b1", + "sleep": "\u038e\u03c0\u03bd\u03bf\u03c2", + "work": "\u0395\u03c1\u03b3\u03b1\u03c3\u03af\u03b1" + }, "tuya__ipc_work_mode": { "0": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c7\u03b1\u03bc\u03b7\u03bb\u03ae\u03c2 \u03b9\u03c3\u03c7\u03cd\u03bf\u03c2", "1": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c3\u03c5\u03bd\u03b5\u03c7\u03bf\u03cd\u03c2 \u03b5\u03c1\u03b3\u03b1\u03c3\u03af\u03b1\u03c2" diff --git a/homeassistant/components/tuya/translations/select.ru.json b/homeassistant/components/tuya/translations/select.ru.json index c6da7570d5e448..4755e9f5d09a9a 100644 --- a/homeassistant/components/tuya/translations/select.ru.json +++ b/homeassistant/components/tuya/translations/select.ru.json @@ -10,6 +10,15 @@ "1": "\u0412\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u043e", "2": "\u0412\u043a\u043b\u044e\u0447\u0435\u043d\u043e" }, + "tuya__countdown": { + "1h": "1 \u0447\u0430\u0441", + "2h": "2 \u0447\u0430\u0441\u0430", + "3h": "3 \u0447\u0430\u0441\u0430", + "4h": "4 \u0447\u0430\u0441\u0430", + "5h": "5 \u0447\u0430\u0441\u043e\u0432", + "6h": "6 \u0447\u0430\u0441\u043e\u0432", + "cancel": "\u041e\u0442\u043c\u0435\u043d\u0430" + }, "tuya__curtain_mode": { "morning": "\u0423\u0442\u0440\u043e", "night": "\u041d\u043e\u0447\u044c" @@ -31,6 +40,21 @@ "click": "\u041a\u043d\u043e\u043f\u043a\u0430", "switch": "\u0412\u044b\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u044c" }, + "tuya__humidifier_level": { + "level_1": "\u0423\u0440\u043e\u0432\u0435\u043d\u044c 1", + "level_10": "\u0423\u0440\u043e\u0432\u0435\u043d\u044c 10", + "level_2": "\u0423\u0440\u043e\u0432\u0435\u043d\u044c 2", + "level_3": "\u0423\u0440\u043e\u0432\u0435\u043d\u044c 3", + "level_4": "\u0423\u0440\u043e\u0432\u0435\u043d\u044c 4", + "level_5": "\u0423\u0440\u043e\u0432\u0435\u043d\u044c 5", + "level_6": "\u0423\u0440\u043e\u0432\u0435\u043d\u044c 6", + "level_7": "\u0423\u0440\u043e\u0432\u0435\u043d\u044c 7", + "level_8": "\u0423\u0440\u043e\u0432\u0435\u043d\u044c 8", + "level_9": "\u0423\u0440\u043e\u0432\u0435\u043d\u044c 9" + }, + "tuya__humidifier_spray_mode": { + "auto": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438" + }, "tuya__ipc_work_mode": { "0": "\u0420\u0435\u0436\u0438\u043c \u043d\u0438\u0437\u043a\u043e\u0433\u043e \u044d\u043d\u0435\u0440\u0433\u043e\u043f\u043e\u0442\u0440\u0435\u0431\u043b\u0435\u043d\u0438\u044f", "1": "\u041d\u0435\u043f\u0440\u0435\u0440\u044b\u0432\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c \u0440\u0430\u0431\u043e\u0442\u044b" diff --git a/homeassistant/components/tuya/translations/sensor.ru.json b/homeassistant/components/tuya/translations/sensor.ru.json index 617237c87683d9..9ec7eac14c0a28 100644 --- a/homeassistant/components/tuya/translations/sensor.ru.json +++ b/homeassistant/components/tuya/translations/sensor.ru.json @@ -1,5 +1,10 @@ { "state": { + "tuya__air_quality": { + "good": "\u0425\u043e\u0440\u043e\u0448\u0435\u0435", + "great": "\u041e\u0442\u043b\u0438\u0447\u043d\u043e\u0435", + "mild": "\u0423\u043c\u0435\u0440\u0435\u043d\u043d\u043e\u0435" + }, "tuya__status": { "boiling_temp": "\u0422\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 \u043a\u0438\u043f\u0435\u043d\u0438\u044f", "cooling": "\u041e\u0445\u043b\u0430\u0436\u0434\u0435\u043d\u0438\u0435", diff --git a/homeassistant/components/wiz/translations/el.json b/homeassistant/components/wiz/translations/el.json index 81d140f3d4f0bb..f481be62e73db0 100644 --- a/homeassistant/components/wiz/translations/el.json +++ b/homeassistant/components/wiz/translations/el.json @@ -4,7 +4,16 @@ "bulb_time_out": "\u0394\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf\u03bd \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b1. \u038a\u03c3\u03c9\u03c2 \u03bf \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03ba\u03c4\u03cc\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03ae \u03ad\u03c7\u03b5\u03b9 \u03b5\u03b9\u03c3\u03b1\u03c7\u03b8\u03b5\u03af \u03bb\u03ac\u03b8\u03bf\u03c2 IP/host. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03bd\u03ac\u03c8\u03c4\u03b5 \u03c4\u03bf \u03c6\u03c9\u03c2 \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac!", "no_wiz_light": "\u039f \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03bc\u03ad\u03c3\u03c9 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c0\u03bb\u03b1\u03c4\u03c6\u03cc\u03c1\u03bc\u03b1\u03c2 WiZ." }, + "flow_title": "{name} ({host})", "step": { + "discovery_confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ({host});" + }, + "pick_device": { + "data": { + "device": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" + } + }, "user": { "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03ba\u03b1\u03b9 \u03ad\u03bd\u03b1 \u03cc\u03bd\u03bf\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03bd\u03ad\u03bf \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b1:" } diff --git a/homeassistant/components/wiz/translations/pt-BR.json b/homeassistant/components/wiz/translations/pt-BR.json index 92d0c3f84d318f..a1163f5cf02040 100644 --- a/homeassistant/components/wiz/translations/pt-BR.json +++ b/homeassistant/components/wiz/translations/pt-BR.json @@ -1,22 +1,22 @@ { "config": { "abort": { - "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "no_devices_found": "Nenhum dispositivo encontrado na rede" }, "error": { "bulb_time_out": "N\u00e3o \u00e9 poss\u00edvel conectar \u00e0 l\u00e2mpada. Talvez a l\u00e2mpada esteja offline ou um IP/host errado foi inserido. Por favor, acenda a luz e tente novamente!", - "cannot_connect": "Falha em conectar", + "cannot_connect": "Falha ao conectar", "no_wiz_light": "A l\u00e2mpada n\u00e3o pode ser conectada via integra\u00e7\u00e3o com a plataforma WiZ.", "unknown": "Erro inesperado" }, - "flow_title": "{name} ( {host} )", + "flow_title": "{name} ({host})", "step": { "confirm": { "description": "Deseja iniciar a configura\u00e7\u00e3o?" }, "discovery_confirm": { - "description": "Deseja configurar {name} ( {host} )?" + "description": "Deseja configurar {name} ({host})?" }, "pick_device": { "data": { @@ -25,7 +25,7 @@ }, "user": { "data": { - "host": "Host", + "host": "Nome do host", "name": "Nome" }, "description": "Se voc\u00ea deixar o host vazio, a descoberta ser\u00e1 usada para localizar dispositivos." diff --git a/homeassistant/components/wiz/translations/ru.json b/homeassistant/components/wiz/translations/ru.json new file mode 100644 index 00000000000000..47c1d650fa50cd --- /dev/null +++ b/homeassistant/components/wiz/translations/ru.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.", + "no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0441\u0435\u0442\u0438." + }, + "error": { + "bulb_time_out": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u043b\u0430\u043c\u043f\u043e\u0447\u043a\u0435. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435, \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0430 \u043b\u0438 \u043b\u0430\u043c\u043f\u043e\u0447\u043a\u0430 \u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u043b\u0438 \u0443\u043a\u0430\u0437\u0430\u043d IP-\u0430\u0434\u0440\u0435\u0441 \u0438\u043b\u0438 \u0445\u043e\u0441\u0442.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "no_wiz_light": "\u042d\u0442\u0443 \u043b\u0430\u043c\u043f\u043e\u0447\u043a\u0443 \u043d\u0435\u043b\u044c\u0437\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0447\u0435\u0440\u0435\u0437 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e WiZ.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "flow_title": "{name} ({host})", + "step": { + "confirm": { + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443?" + }, + "discovery_confirm": { + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name} ({host})?" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" + }, + "description": "\u0415\u0441\u043b\u0438 \u043d\u0435 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0430\u0434\u0440\u0435\u0441 \u0445\u043e\u0441\u0442\u0430, \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0431\u0443\u0434\u0443\u0442 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave_me/translations/ca.json b/homeassistant/components/zwave_me/translations/ca.json new file mode 100644 index 00000000000000..ba91812d06fb8e --- /dev/null +++ b/homeassistant/components/zwave_me/translations/ca.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat", + "no_valid_uuid_set": "No hi ha cap UUID v\u00e0lid definit" + }, + "error": { + "no_valid_uuid_set": "No hi ha cap UUID v\u00e0lid definit" + }, + "step": { + "user": { + "data": { + "token": "Token", + "url": "URL" + }, + "description": "Introdueix l'adre\u00e7a IP del servidor Z-Way i el 'token' d'acc\u00e9s Z-Way. Si s'utilitza HTTPS en lloc d'HTTP, l'adre\u00e7a IP es pot prefixar amb wss://. Per obtenir el 'token', ves a la interf\u00edcie d'usuari de Z-Way > Men\u00fa > Configuraci\u00f3 > Usuari > Token API. Es recomana crear un nou usuaride Home Assistant i concedir-li acc\u00e9s als dispositius que necessitis controlar des de Home Assistant. Tamb\u00e9 \u00e9s possible utilitzar l'acc\u00e9s remot a trav\u00e9s de find.z-wave.me per connectar amb un Z-Way remot. Introdueix wss://find.z-wave.me al camp d'IP i copia el 'token' d'\u00e0mbit global (inicia sessi\u00f3 a Z-Way mitjan\u00e7ant find.z-wave.me)." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave_me/translations/el.json b/homeassistant/components/zwave_me/translations/el.json new file mode 100644 index 00000000000000..b5512c539fef87 --- /dev/null +++ b/homeassistant/components/zwave_me/translations/el.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "no_valid_uuid_set": "\u0394\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf UUID" + }, + "error": { + "no_valid_uuid_set": "\u0394\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf UUID" + }, + "step": { + "user": { + "data": { + "token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc" + }, + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae Z-Way \u03ba\u03b1\u03b9 \u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 Z-Way. \u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03c4\u03bf \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 wss:// \u03b5\u03ac\u03bd \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af HTTPS \u03b1\u03bd\u03c4\u03af \u03b3\u03b9\u03b1 HTTP. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc, \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03c0\u03b1\u03c6\u03ae \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 Z-Way > \u039c\u03b5\u03bd\u03bf\u03cd > \u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 > \u03a7\u03c1\u03ae\u03c3\u03c4\u03b7\u03c2 > \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API. \u03a0\u03c1\u03bf\u03c4\u03b5\u03af\u03bd\u03b5\u03c4\u03b1\u03b9 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03bd\u03ad\u03bf \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03b3\u03b9\u03b1 \u03c4\u03bf Home Assistant \u03ba\u03b1\u03b9 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03c7\u03c9\u03c1\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7 \u03c3\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c0\u03bf\u03c5 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03b5\u03c4\u03b5 \u03b1\u03c0\u03cc \u03c4\u03bf Home Assistant. \u0395\u03af\u03bd\u03b1\u03b9 \u03b5\u03c0\u03af\u03c3\u03b7\u03c2 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03b1\u03c0\u03bf\u03bc\u03b1\u03ba\u03c1\u03c5\u03c3\u03bc\u03ad\u03bd\u03b7\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03bc\u03ad\u03c3\u03c9 \u03c4\u03bf\u03c5 find.z-wave.me \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03b5\u03bd\u03cc\u03c2 \u03b1\u03c0\u03bf\u03bc\u03b1\u03ba\u03c1\u03c5\u03c3\u03bc\u03ad\u03bd\u03bf\u03c5 Z-Way. \u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf wss://find.z-wave.me \u03c3\u03c4\u03bf \u03c0\u03b5\u03b4\u03af\u03bf IP \u03ba\u03b1\u03b9 \u03b1\u03bd\u03c4\u03b9\u03b3\u03c1\u03ac\u03c8\u03c4\u03b5 \u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03bc\u03b5 Global scope (\u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf Z-Way \u03bc\u03ad\u03c3\u03c9 \u03c4\u03bf\u03c5 find.z-wave.me \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc)." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave_me/translations/et.json b/homeassistant/components/zwave_me/translations/et.json new file mode 100644 index 00000000000000..c07b4b2b5f815e --- /dev/null +++ b/homeassistant/components/zwave_me/translations/et.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "no_valid_uuid_set": "Kehtivat UUID-d pole m\u00e4\u00e4ratud" + }, + "error": { + "no_valid_uuid_set": "Kehtivat UUID-d pole m\u00e4\u00e4ratud" + }, + "step": { + "user": { + "data": { + "token": "Token", + "url": "URL" + }, + "description": "Sisesta Z-Way serveri IP-aadress ja Z-Way juurdep\u00e4\u00e4suluba. Kui HTTP asemel tuleks kasutada HTTPS-i, v\u00f5ib IP-aadressile lisada eesliite wss://. Tokeni hankimiseks mine Z-Way kasutajaliidesesse > Men\u00fc\u00fc > Seaded > Kasutaja > API tunnus. Soovitatav on luua Home Assistantile uus kasutaja ja anda juurdep\u00e4\u00e4s seadmetele mida pead Home Assistandi abil juhtima. Kaug-Z-Way \u00fchendamiseks on v\u00f5imalik kasutada ka kaugjuurdep\u00e4\u00e4su l\u00e4bi find.z-wave.me. Sisesta IP v\u00e4ljale wss://find.z-wave.me ja kopeeri globaalse ulatusega luba (selleks logi Z-Waysse sisse saidi find.z-wave.me kaudu)." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave_me/translations/pt-BR.json b/homeassistant/components/zwave_me/translations/pt-BR.json new file mode 100644 index 00000000000000..4a7be43ddb7097 --- /dev/null +++ b/homeassistant/components/zwave_me/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "no_valid_uuid_set": "Nenhum conjunto de UUID v\u00e1lido" + }, + "error": { + "no_valid_uuid_set": "Nenhum conjunto de UUID v\u00e1lido" + }, + "step": { + "user": { + "data": { + "token": "Token", + "url": "URL" + }, + "description": "Insira o endere\u00e7o IP do servidor Z-Way e o token de acesso Z-Way. O endere\u00e7o IP pode ser prefixado com wss:// e o HTTPS deve ser usado em vez de HTTP. Para obter o token, v\u00e1 para a interface do usu\u00e1rio Z-Way > Menu > Configura\u00e7\u00f5es > Usu\u00e1rio > Token de API. Sugere-se criar um novo usu\u00e1rio para o Home Assistant e conceder acesso aos dispositivos que voc\u00ea quer controlar no Home Assistant. Tamb\u00e9m \u00e9 poss\u00edvel usar o acesso remoto via find.z-wave.me para conectar um Z-Way remoto. Insira wss://find.z-wave.me no campo IP e copie o token com escopo Global (fa\u00e7a login no Z-Way via find.z-wave.me para isso)." + } + } + } +} \ No newline at end of file From f943f304926eb8a225cd7aad6d26cb73a8484e06 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 7 Feb 2022 18:25:26 -0600 Subject: [PATCH 0411/1098] Add discovery support to elkm1 (#65205) --- .coveragerc | 9 +- homeassistant/components/elkm1/__init__.py | 122 +-- homeassistant/components/elkm1/config_flow.py | 287 +++++-- homeassistant/components/elkm1/const.py | 9 +- homeassistant/components/elkm1/discovery.py | 94 +++ homeassistant/components/elkm1/manifest.json | 4 +- homeassistant/components/elkm1/strings.json | 22 +- .../components/elkm1/translations/en.json | 24 +- homeassistant/generated/dhcp.py | 4 + requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/elkm1/__init__.py | 60 ++ tests/components/elkm1/test_config_flow.py | 769 ++++++++++++++++-- 13 files changed, 1223 insertions(+), 185 deletions(-) create mode 100644 homeassistant/components/elkm1/discovery.py diff --git a/.coveragerc b/.coveragerc index db20f089e4bde4..05f1ed64c333ec 100644 --- a/.coveragerc +++ b/.coveragerc @@ -257,7 +257,14 @@ omit = homeassistant/components/egardia/* homeassistant/components/eight_sleep/* homeassistant/components/eliqonline/sensor.py - homeassistant/components/elkm1/* + homeassistant/components/elkm1/__init__.py + homeassistant/components/elkm1/alarm_control_panel.py + homeassistant/components/elkm1/climate.py + homeassistant/components/elkm1/discovery.py + homeassistant/components/elkm1/light.py + homeassistant/components/elkm1/scene.py + homeassistant/components/elkm1/sensor.py + homeassistant/components/elkm1/switch.py homeassistant/components/elmax/__init__.py homeassistant/components/elmax/common.py homeassistant/components/elmax/const.py diff --git a/homeassistant/components/elkm1/__init__.py b/homeassistant/components/elkm1/__init__.py index 6f5864235526b1..8ab9c0ac73f269 100644 --- a/homeassistant/components/elkm1/__init__.py +++ b/homeassistant/components/elkm1/__init__.py @@ -6,6 +6,7 @@ import re from types import MappingProxyType from typing import Any +from urllib.parse import urlparse import async_timeout import elkm1_lib as elkm1 @@ -28,15 +29,15 @@ from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity import DeviceInfo, Entity +from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType import homeassistant.util.dt as dt_util +from homeassistant.util.network import is_ip_address from .const import ( ATTR_KEY, ATTR_KEY_NAME, ATTR_KEYPAD_ID, - BARE_TEMP_CELSIUS, - BARE_TEMP_FAHRENHEIT, CONF_AREA, CONF_AUTO_CONFIGURE, CONF_COUNTER, @@ -48,9 +49,18 @@ CONF_TASK, CONF_THERMOSTAT, CONF_ZONE, + DISCOVER_SCAN_TIMEOUT, + DISCOVERY_INTERVAL, DOMAIN, ELK_ELEMENTS, EVENT_ELKM1_KEYPAD_KEY_PRESSED, + LOGIN_TIMEOUT, +) +from .discovery import ( + async_discover_device, + async_discover_devices, + async_trigger_discovery, + async_update_entry_from_discovery, ) SYNC_TIMEOUT = 120 @@ -127,28 +137,28 @@ def _has_all_unique_prefixes(value): } ) -DEVICE_SCHEMA = vol.Schema( - { - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_PREFIX, default=""): vol.All(cv.string, vol.Lower), - vol.Optional(CONF_USERNAME, default=""): cv.string, - vol.Optional(CONF_PASSWORD, default=""): cv.string, - vol.Optional(CONF_AUTO_CONFIGURE, default=False): cv.boolean, - # cv.temperature_unit will mutate 'C' -> '°C' and 'F' -> '°F' - vol.Optional( - CONF_TEMPERATURE_UNIT, default=BARE_TEMP_FAHRENHEIT - ): cv.temperature_unit, - vol.Optional(CONF_AREA, default={}): DEVICE_SCHEMA_SUBDOMAIN, - vol.Optional(CONF_COUNTER, default={}): DEVICE_SCHEMA_SUBDOMAIN, - vol.Optional(CONF_KEYPAD, default={}): DEVICE_SCHEMA_SUBDOMAIN, - vol.Optional(CONF_OUTPUT, default={}): DEVICE_SCHEMA_SUBDOMAIN, - vol.Optional(CONF_PLC, default={}): DEVICE_SCHEMA_SUBDOMAIN, - vol.Optional(CONF_SETTING, default={}): DEVICE_SCHEMA_SUBDOMAIN, - vol.Optional(CONF_TASK, default={}): DEVICE_SCHEMA_SUBDOMAIN, - vol.Optional(CONF_THERMOSTAT, default={}): DEVICE_SCHEMA_SUBDOMAIN, - vol.Optional(CONF_ZONE, default={}): DEVICE_SCHEMA_SUBDOMAIN, - }, - _host_validator, +DEVICE_SCHEMA = vol.All( + cv.deprecated(CONF_TEMPERATURE_UNIT), + vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PREFIX, default=""): vol.All(cv.string, vol.Lower), + vol.Optional(CONF_USERNAME, default=""): cv.string, + vol.Optional(CONF_PASSWORD, default=""): cv.string, + vol.Optional(CONF_AUTO_CONFIGURE, default=False): cv.boolean, + vol.Optional(CONF_TEMPERATURE_UNIT, default="F"): cv.temperature_unit, + vol.Optional(CONF_AREA, default={}): DEVICE_SCHEMA_SUBDOMAIN, + vol.Optional(CONF_COUNTER, default={}): DEVICE_SCHEMA_SUBDOMAIN, + vol.Optional(CONF_KEYPAD, default={}): DEVICE_SCHEMA_SUBDOMAIN, + vol.Optional(CONF_OUTPUT, default={}): DEVICE_SCHEMA_SUBDOMAIN, + vol.Optional(CONF_PLC, default={}): DEVICE_SCHEMA_SUBDOMAIN, + vol.Optional(CONF_SETTING, default={}): DEVICE_SCHEMA_SUBDOMAIN, + vol.Optional(CONF_TASK, default={}): DEVICE_SCHEMA_SUBDOMAIN, + vol.Optional(CONF_THERMOSTAT, default={}): DEVICE_SCHEMA_SUBDOMAIN, + vol.Optional(CONF_ZONE, default={}): DEVICE_SCHEMA_SUBDOMAIN, + }, + _host_validator, + ), ) CONFIG_SCHEMA = vol.Schema( @@ -162,6 +172,14 @@ async def async_setup(hass: HomeAssistant, hass_config: ConfigType) -> bool: hass.data.setdefault(DOMAIN, {}) _create_elk_services(hass) + async def _async_discovery(*_: Any) -> None: + async_trigger_discovery( + hass, await async_discover_devices(hass, DISCOVER_SCAN_TIMEOUT) + ) + + asyncio.create_task(_async_discovery()) + async_track_time_interval(hass, _async_discovery, DISCOVERY_INTERVAL) + if DOMAIN not in hass_config: return True @@ -204,13 +222,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Elk-M1 Control from a config entry.""" conf: MappingProxyType[str, Any] = entry.data + host = urlparse(entry.data[CONF_HOST]).hostname + _LOGGER.debug("Setting up elkm1 %s", conf["host"]) - temperature_unit = TEMP_FAHRENHEIT - if conf[CONF_TEMPERATURE_UNIT] in (BARE_TEMP_CELSIUS, TEMP_CELSIUS): - temperature_unit = TEMP_CELSIUS + if not entry.unique_id or ":" not in entry.unique_id and is_ip_address(host): + if device := await async_discover_device(hass, host): + async_update_entry_from_discovery(hass, entry, device) - config: dict[str, Any] = {"temperature_unit": temperature_unit} + config: dict[str, Any] = {} if not conf[CONF_AUTO_CONFIGURE]: # With elkm1-lib==0.7.16 and later auto configure is available @@ -253,11 +273,16 @@ def _element_changed(element, changeset): keypad.add_callback(_element_changed) try: - if not await async_wait_for_elk_to_sync(elk, SYNC_TIMEOUT, conf[CONF_HOST]): + if not await async_wait_for_elk_to_sync( + elk, LOGIN_TIMEOUT, SYNC_TIMEOUT, conf[CONF_HOST] + ): return False except asyncio.TimeoutError as exc: - raise ConfigEntryNotReady from exc + raise ConfigEntryNotReady(f"Timed out connecting to {conf[CONF_HOST]}") from exc + elk_temp_unit = elk.panel.temperature_units # pylint: disable=no-member + temperature_unit = TEMP_CELSIUS if elk_temp_unit == "C" else TEMP_FAHRENHEIT + config["temperature_unit"] = temperature_unit hass.data[DOMAIN][entry.entry_id] = { "elk": elk, "prefix": conf[CONF_PREFIX], @@ -298,38 +323,42 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -async def async_wait_for_elk_to_sync(elk, timeout, conf_host): +async def async_wait_for_elk_to_sync( + elk: elkm1.Elk, login_timeout: int, sync_timeout: int, conf_host: str +) -> bool: """Wait until the elk has finished sync. Can fail login or timeout.""" + sync_event = asyncio.Event() + login_event = asyncio.Event() + def login_status(succeeded): nonlocal success success = succeeded if succeeded: _LOGGER.debug("ElkM1 login succeeded") + login_event.set() else: elk.disconnect() _LOGGER.error("ElkM1 login failed; invalid username or password") - event.set() + login_event.set() + sync_event.set() def sync_complete(): - event.set() + sync_event.set() success = True - event = asyncio.Event() elk.add_handler("login", login_status) elk.add_handler("sync_complete", sync_complete) - try: - async with async_timeout.timeout(timeout): - await event.wait() - except asyncio.TimeoutError: - _LOGGER.error( - "Timed out after %d seconds while trying to sync with ElkM1 at %s", - timeout, - conf_host, - ) - elk.disconnect() - raise + events = ((login_event, login_timeout), (sync_event, sync_timeout)) + + for event, timeout in events: + try: + async with async_timeout.timeout(timeout): + await event.wait() + except asyncio.TimeoutError: + elk.disconnect() + raise return success @@ -392,6 +421,7 @@ def __init__(self, element, elk, elk_data): self._elk = elk self._element = element self._prefix = elk_data["prefix"] + self._name_prefix = f"{self._prefix} " if self._prefix else "" self._temperature_unit = elk_data["config"]["temperature_unit"] # unique_id starts with elkm1_ iff there is no prefix # it starts with elkm1m_{prefix} iff there is a prefix @@ -410,7 +440,7 @@ def __init__(self, element, elk, elk_data): @property def name(self): """Name of the element.""" - return f"{self._prefix}{self._element.name}" + return f"{self._name_prefix}{self._element.name}" @property def unique_id(self): diff --git a/homeassistant/components/elkm1/config_flow.py b/homeassistant/components/elkm1/config_flow.py index 905aa35ad1900c..19a3cf88473a18 100644 --- a/homeassistant/components/elkm1/config_flow.py +++ b/homeassistant/components/elkm1/config_flow.py @@ -1,27 +1,42 @@ """Config flow for Elk-M1 Control integration.""" +from __future__ import annotations + import asyncio import logging +from typing import Any from urllib.parse import urlparse import elkm1_lib as elkm1 +from elkm1_lib.discovery import ElkSystem import voluptuous as vol from homeassistant import config_entries, exceptions +from homeassistant.components import dhcp from homeassistant.const import ( CONF_ADDRESS, CONF_HOST, CONF_PASSWORD, CONF_PREFIX, CONF_PROTOCOL, - CONF_TEMPERATURE_UNIT, CONF_USERNAME, - TEMP_CELSIUS, - TEMP_FAHRENHEIT, ) +from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.typing import DiscoveryInfoType from homeassistant.util import slugify from . import async_wait_for_elk_to_sync -from .const import CONF_AUTO_CONFIGURE, DOMAIN +from .const import CONF_AUTO_CONFIGURE, DISCOVER_SCAN_TIMEOUT, DOMAIN, LOGIN_TIMEOUT +from .discovery import ( + _short_mac, + async_discover_device, + async_discover_devices, + async_update_entry_from_discovery, +) + +CONF_DEVICE = "device" + +SECURE_PORT = 2601 _LOGGER = logging.getLogger(__name__) @@ -32,25 +47,20 @@ "serial": "serial://", } -DATA_SCHEMA = vol.Schema( - { - vol.Required(CONF_PROTOCOL, default="secure"): vol.In( - ["secure", "TLS 1.2", "non-secure", "serial"] - ), - vol.Required(CONF_ADDRESS): str, - vol.Optional(CONF_USERNAME, default=""): str, - vol.Optional(CONF_PASSWORD, default=""): str, - vol.Optional(CONF_PREFIX, default=""): str, - vol.Optional(CONF_TEMPERATURE_UNIT, default=TEMP_FAHRENHEIT): vol.In( - [TEMP_FAHRENHEIT, TEMP_CELSIUS] - ), - } -) - VALIDATE_TIMEOUT = 35 +BASE_SCHEMA = { + vol.Optional(CONF_USERNAME, default=""): str, + vol.Optional(CONF_PASSWORD, default=""): str, +} + +SECURE_PROTOCOLS = ["secure", "TLS 1.2"] +ALL_PROTOCOLS = [*SECURE_PROTOCOLS, "non-secure", "serial"] +DEFAULT_SECURE_PROTOCOL = "secure" +DEFAULT_NON_SECURE_PROTOCOL = "non-secure" -async def validate_input(data): + +async def validate_input(data: dict[str, str], mac: str | None) -> dict[str, str]: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -70,11 +80,16 @@ async def validate_input(data): ) elk.connect() - if not await async_wait_for_elk_to_sync(elk, VALIDATE_TIMEOUT, url): + if not await async_wait_for_elk_to_sync(elk, LOGIN_TIMEOUT, VALIDATE_TIMEOUT, url): raise InvalidAuth - device_name = data[CONF_PREFIX] if data[CONF_PREFIX] else "ElkM1" - # Return info that you want to store in the config entry. + short_mac = _short_mac(mac) if mac else None + if prefix and prefix != short_mac: + device_name = prefix + elif mac: + device_name = f"ElkM1 {short_mac}" + else: + device_name = "ElkM1" return {"title": device_name, CONF_HOST: url, CONF_PREFIX: slugify(prefix)} @@ -87,6 +102,13 @@ def _make_url_from_data(data): return f"{protocol}{address}" +def _placeholders_from_device(device: ElkSystem) -> dict[str, str]: + return { + "mac_address": _short_mac(device.mac_address), + "host": f"{device.ip_address}:{device.port}", + } + + class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for Elk-M1 Control.""" @@ -94,53 +116,200 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize the elkm1 config flow.""" - self.importing = False + self._discovered_device: ElkSystem | None = None + self._discovered_devices: dict[str, ElkSystem] = {} - async def async_step_user(self, user_input=None): + async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + """Handle discovery via dhcp.""" + self._discovered_device = ElkSystem( + discovery_info.macaddress, discovery_info.ip, 0 + ) + return await self._async_handle_discovery() + + async def async_step_discovery( + self, discovery_info: DiscoveryInfoType + ) -> FlowResult: + """Handle discovery.""" + self._discovered_device = ElkSystem( + discovery_info["mac_address"], + discovery_info["ip_address"], + discovery_info["port"], + ) + return await self._async_handle_discovery() + + async def _async_handle_discovery(self) -> FlowResult: + """Handle any discovery.""" + device = self._discovered_device + assert device is not None + mac = dr.format_mac(device.mac_address) + host = device.ip_address + await self.async_set_unique_id(mac) + for entry in self._async_current_entries(include_ignore=False): + if ( + entry.unique_id == mac + or urlparse(entry.data[CONF_HOST]).hostname == host + ): + if async_update_entry_from_discovery(self.hass, entry, device): + self.hass.async_create_task( + self.hass.config_entries.async_reload(entry.entry_id) + ) + return self.async_abort(reason="already_configured") + self.context[CONF_HOST] = host + for progress in self._async_in_progress(): + if progress.get("context", {}).get(CONF_HOST) == host: + return self.async_abort(reason="already_in_progress") + if not device.port: + if discovered_device := await async_discover_device(self.hass, host): + self._discovered_device = discovered_device + else: + return self.async_abort(reason="cannot_connect") + return await self.async_step_discovery_confirm() + + async def async_step_discovery_confirm( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Confirm discovery.""" + assert self._discovered_device is not None + self.context["title_placeholders"] = _placeholders_from_device( + self._discovered_device + ) + return await self.async_step_discovered_connection() + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle the initial step.""" + if user_input is not None: + if mac := user_input[CONF_DEVICE]: + await self.async_set_unique_id(mac, raise_on_progress=False) + self._discovered_device = self._discovered_devices[mac] + return await self.async_step_discovered_connection() + return await self.async_step_manual_connection() + + current_unique_ids = self._async_current_ids() + current_hosts = { + urlparse(entry.data[CONF_HOST]).hostname + for entry in self._async_current_entries(include_ignore=False) + } + discovered_devices = await async_discover_devices( + self.hass, DISCOVER_SCAN_TIMEOUT + ) + self._discovered_devices = { + dr.format_mac(device.mac_address): device for device in discovered_devices + } + devices_name: dict[str | None, str] = { + mac: f"{_short_mac(device.mac_address)} ({device.ip_address})" + for mac, device in self._discovered_devices.items() + if mac not in current_unique_ids and device.ip_address not in current_hosts + } + if not devices_name: + return await self.async_step_manual_connection() + devices_name[None] = "Manual Entry" + return self.async_show_form( + step_id="user", + data_schema=vol.Schema({vol.Required(CONF_DEVICE): vol.In(devices_name)}), + ) + + async def _async_create_or_error( + self, user_input: dict[str, Any], importing: bool + ) -> tuple[dict[str, str] | None, FlowResult | None]: + """Try to connect and create the entry or error.""" + if self._url_already_configured(_make_url_from_data(user_input)): + return None, self.async_abort(reason="address_already_configured") + + try: + info = await validate_input(user_input, self.unique_id) + except asyncio.TimeoutError: + return {CONF_HOST: "cannot_connect"}, None + except InvalidAuth: + return {CONF_PASSWORD: "invalid_auth"}, None + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + return {"base": "unknown"}, None + + if importing: + return None, self.async_create_entry(title=info["title"], data=user_input) + + return None, self.async_create_entry( + title=info["title"], + data={ + CONF_HOST: info[CONF_HOST], + CONF_USERNAME: user_input[CONF_USERNAME], + CONF_PASSWORD: user_input[CONF_PASSWORD], + CONF_AUTO_CONFIGURE: True, + CONF_PREFIX: info[CONF_PREFIX], + }, + ) + + async def async_step_discovered_connection(self, user_input=None): + """Handle connecting the device when we have a discovery.""" errors = {} + device = self._discovered_device + assert device is not None if user_input is not None: - if self._url_already_configured(_make_url_from_data(user_input)): - return self.async_abort(reason="address_already_configured") - - try: - info = await validate_input(user_input) - - except asyncio.TimeoutError: - errors["base"] = "cannot_connect" - except InvalidAuth: - errors["base"] = "invalid_auth" - except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" - - if "base" not in errors: - await self.async_set_unique_id(user_input[CONF_PREFIX]) - self._abort_if_unique_id_configured() + user_input[CONF_ADDRESS] = f"{device.ip_address}:{device.port}" + if self._async_current_entries(): + user_input[CONF_PREFIX] = _short_mac(device.mac_address) + else: + user_input[CONF_PREFIX] = "" + if device.port != SECURE_PORT: + user_input[CONF_PROTOCOL] = DEFAULT_NON_SECURE_PROTOCOL + errors, result = await self._async_create_or_error(user_input, False) + if not errors: + return result + + base_schmea = BASE_SCHEMA.copy() + if device.port == SECURE_PORT: + base_schmea[ + vol.Required(CONF_PROTOCOL, default=DEFAULT_SECURE_PROTOCOL) + ] = vol.In(SECURE_PROTOCOLS) + + return self.async_show_form( + step_id="discovered_connection", + data_schema=vol.Schema(base_schmea), + errors=errors, + description_placeholders=_placeholders_from_device(device), + ) - if self.importing: - return self.async_create_entry(title=info["title"], data=user_input) - - return self.async_create_entry( - title=info["title"], - data={ - CONF_HOST: info[CONF_HOST], - CONF_USERNAME: user_input[CONF_USERNAME], - CONF_PASSWORD: user_input[CONF_PASSWORD], - CONF_AUTO_CONFIGURE: True, - CONF_TEMPERATURE_UNIT: user_input[CONF_TEMPERATURE_UNIT], - CONF_PREFIX: info[CONF_PREFIX], - }, - ) + async def async_step_manual_connection(self, user_input=None): + """Handle connecting the device when we need manual entry.""" + errors = {} + if user_input is not None: + # We might be able to discover the device via directed UDP + # in case its on another subnet + if device := await async_discover_device( + self.hass, user_input[CONF_ADDRESS] + ): + await self.async_set_unique_id(dr.format_mac(device.mac_address)) + self._abort_if_unique_id_configured() + user_input[CONF_ADDRESS] = f"{device.ip_address}:{device.port}" + errors, result = await self._async_create_or_error(user_input, False) + if not errors: + return result return self.async_show_form( - step_id="user", data_schema=DATA_SCHEMA, errors=errors + step_id="manual_connection", + data_schema=vol.Schema( + { + **BASE_SCHEMA, + vol.Required(CONF_ADDRESS): str, + vol.Optional(CONF_PREFIX, default=""): str, + vol.Required( + CONF_PROTOCOL, default=DEFAULT_SECURE_PROTOCOL + ): vol.In(ALL_PROTOCOLS), + } + ), + errors=errors, ) async def async_step_import(self, user_input): """Handle import.""" - self.importing = True - return await self.async_step_user(user_input) + if device := await async_discover_device( + self.hass, urlparse(user_input[CONF_HOST]).hostname + ): + await self.async_set_unique_id(dr.format_mac(device.mac_address)) + self._abort_if_unique_id_configured() + return (await self._async_create_or_error(user_input, True))[1] def _url_already_configured(self, url): """See if we already have a elkm1 matching user input configured.""" diff --git a/homeassistant/components/elkm1/const.py b/homeassistant/components/elkm1/const.py index 4d2dac4b1de1cf..80d594fce0a41f 100644 --- a/homeassistant/components/elkm1/const.py +++ b/homeassistant/components/elkm1/const.py @@ -1,5 +1,7 @@ """Support the ElkM1 Gold and ElkM1 EZ8 alarm/integration panels.""" +from datetime import timedelta + from elkm1_lib.const import Max import voluptuous as vol @@ -7,6 +9,8 @@ DOMAIN = "elkm1" +LOGIN_TIMEOUT = 15 + CONF_AUTO_CONFIGURE = "auto_configure" CONF_AREA = "area" CONF_COUNTER = "counter" @@ -18,9 +22,8 @@ CONF_TASK = "task" CONF_THERMOSTAT = "thermostat" - -BARE_TEMP_FAHRENHEIT = "F" -BARE_TEMP_CELSIUS = "C" +DISCOVER_SCAN_TIMEOUT = 10 +DISCOVERY_INTERVAL = timedelta(minutes=15) ELK_ELEMENTS = { CONF_AREA: Max.AREAS.value, diff --git a/homeassistant/components/elkm1/discovery.py b/homeassistant/components/elkm1/discovery.py new file mode 100644 index 00000000000000..10d9f7b6e405ee --- /dev/null +++ b/homeassistant/components/elkm1/discovery.py @@ -0,0 +1,94 @@ +"""The elkm1 integration discovery.""" +from __future__ import annotations + +import asyncio +from dataclasses import asdict +import logging + +from elkm1_lib.discovery import AIOELKDiscovery, ElkSystem + +from homeassistant import config_entries +from homeassistant.components import network +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import device_registry as dr + +from .const import DISCOVER_SCAN_TIMEOUT, DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +def _short_mac(mac_address: str) -> str: + return mac_address.replace(":", "")[-6:] + + +@callback +def async_update_entry_from_discovery( + hass: HomeAssistant, + entry: config_entries.ConfigEntry, + device: ElkSystem, +) -> bool: + """Update a config entry from a discovery.""" + if not entry.unique_id or ":" not in entry.unique_id: + return hass.config_entries.async_update_entry( + entry, unique_id=dr.format_mac(device.mac_address) + ) + return False + + +async def async_discover_devices( + hass: HomeAssistant, timeout: int, address: str | None = None +) -> list[ElkSystem]: + """Discover elkm1 devices.""" + if address: + targets = [address] + else: + targets = [ + str(address) + for address in await network.async_get_ipv4_broadcast_addresses(hass) + ] + + scanner = AIOELKDiscovery() + combined_discoveries: dict[str, ElkSystem] = {} + for idx, discovered in enumerate( + await asyncio.gather( + *[ + scanner.async_scan(timeout=timeout, address=address) + for address in targets + ], + return_exceptions=True, + ) + ): + if isinstance(discovered, Exception): + _LOGGER.debug("Scanning %s failed with error: %s", targets[idx], discovered) + continue + for device in discovered: + assert isinstance(device, ElkSystem) + combined_discoveries[device.ip_address] = device + + return list(combined_discoveries.values()) + + +async def async_discover_device(hass: HomeAssistant, host: str) -> ElkSystem | None: + """Direct discovery at a single ip instead of broadcast.""" + # If we are missing the unique_id we should be able to fetch it + # from the device by doing a directed discovery at the host only + for device in await async_discover_devices(hass, DISCOVER_SCAN_TIMEOUT, host): + if device.ip_address == host: + return device + return None + + +@callback +def async_trigger_discovery( + hass: HomeAssistant, + discovered_devices: list[ElkSystem], +) -> None: + """Trigger config flows for discovered devices.""" + for device in discovered_devices: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DISCOVERY}, + data=asdict(device), + ) + ) diff --git a/homeassistant/components/elkm1/manifest.json b/homeassistant/components/elkm1/manifest.json index 2d84604d53a181..a72a02219074ad 100644 --- a/homeassistant/components/elkm1/manifest.json +++ b/homeassistant/components/elkm1/manifest.json @@ -2,8 +2,10 @@ "domain": "elkm1", "name": "Elk-M1 Control", "documentation": "https://www.home-assistant.io/integrations/elkm1", - "requirements": ["elkm1-lib==1.0.0"], + "requirements": ["elkm1-lib==1.2.0"], + "dhcp": [{"macaddress":"00409D*"}], "codeowners": ["@gwww", "@bdraco"], + "dependencies": ["network"], "config_flow": true, "iot_class": "local_push", "loggers": ["elkm1_lib"] diff --git a/homeassistant/components/elkm1/strings.json b/homeassistant/components/elkm1/strings.json index bf0da956d445c1..35672d5df80ee2 100644 --- a/homeassistant/components/elkm1/strings.json +++ b/homeassistant/components/elkm1/strings.json @@ -1,8 +1,16 @@ { "config": { + "flow_title": "{mac_address} ({host})", "step": { "user": { "title": "Connect to Elk-M1 Control", + "description": "Choose a discovered system or 'Manual Entry' if no devices have been discovered.", + "data": { + "device": "Device" + } + }, + "manual_connection": { + "title": "[%key:component::elkm1::config::step::user::title%]", "description": "The address string must be in the form 'address[:port]' for 'secure' and 'non-secure'. Example: '192.168.1.1'. The port is optional and defaults to 2101 for 'non-secure' and 2601 for 'secure'. For the serial protocol, the address must be in the form 'tty[:baud]'. Example: '/dev/ttyS1'. The baud is optional and defaults to 115200.", "data": { "protocol": "Protocol", @@ -12,6 +20,16 @@ "prefix": "A unique prefix (leave blank if you only have one ElkM1).", "temperature_unit": "The temperature unit ElkM1 uses." } + }, + "discovered_connection": { + "title": "[%key:component::elkm1::config::step::user::title%]", + "description": "Connect to the discovered system: {mac_address} ({host})", + "data": { + "protocol": "[%key:component::elkm1::config::step::manual_connection::data::protocol%]", + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]", + "temperature_unit": "[%key:component::elkm1::config::step::manual_connection::data::temperature_unit%]" + } } }, "error": { @@ -20,8 +38,10 @@ "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "already_configured": "An ElkM1 with this prefix is already configured", "address_already_configured": "An ElkM1 with this address is already configured" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/elkm1/translations/en.json b/homeassistant/components/elkm1/translations/en.json index 04fd3c189b562f..238f1c3d30ea24 100644 --- a/homeassistant/components/elkm1/translations/en.json +++ b/homeassistant/components/elkm1/translations/en.json @@ -2,25 +2,43 @@ "config": { "abort": { "address_already_configured": "An ElkM1 with this address is already configured", - "already_configured": "An ElkM1 with this prefix is already configured" + "already_configured": "An ElkM1 with this prefix is already configured", + "already_in_progress": "Configuration flow is already in progress", + "cannot_connect": "Failed to connect" }, "error": { "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication", "unknown": "Unexpected error" }, + "flow_title": "{mac_address} ({host})", "step": { - "user": { + "discovered_connection": { + "data": { + "password": "Password", + "protocol": "Protocol", + "username": "Username" + }, + "description": "Connect to the discovered system: {mac_address} ({host})", + "title": "Connect to Elk-M1 Control" + }, + "manual_connection": { "data": { "address": "The IP address or domain or serial port if connecting via serial.", "password": "Password", "prefix": "A unique prefix (leave blank if you only have one ElkM1).", "protocol": "Protocol", - "temperature_unit": "The temperature unit ElkM1 uses.", "username": "Username" }, "description": "The address string must be in the form 'address[:port]' for 'secure' and 'non-secure'. Example: '192.168.1.1'. The port is optional and defaults to 2101 for 'non-secure' and 2601 for 'secure'. For the serial protocol, the address must be in the form 'tty[:baud]'. Example: '/dev/ttyS1'. The baud is optional and defaults to 115200.", "title": "Connect to Elk-M1 Control" + }, + "user": { + "data": { + "device": "Device" + }, + "description": "Choose a discovered system or 'Manual Entry' if no devices have been discovered.", + "title": "Connect to Elk-M1 Control" } } } diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 2e9672f99eb4b9..bf42a11f95168e 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -67,6 +67,10 @@ "domain": "broadlink", "macaddress": "B4430D*" }, + { + "domain": "elkm1", + "macaddress": "00409D*" + }, { "domain": "emonitor", "hostname": "emonitor*", diff --git a/requirements_all.txt b/requirements_all.txt index eacf7a910066eb..f6b385f5bd19e0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -605,7 +605,7 @@ elgato==3.0.0 eliqonline==1.2.2 # homeassistant.components.elkm1 -elkm1-lib==1.0.0 +elkm1-lib==1.2.0 # homeassistant.components.elmax elmax_api==0.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7e78e94010ca75..aaba6417465a63 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -388,7 +388,7 @@ dynalite_devices==0.1.46 elgato==3.0.0 # homeassistant.components.elkm1 -elkm1-lib==1.0.0 +elkm1-lib==1.2.0 # homeassistant.components.elmax elmax_api==0.0.2 diff --git a/tests/components/elkm1/__init__.py b/tests/components/elkm1/__init__.py index 8ae7f6d7b49eb1..128d0a0d777645 100644 --- a/tests/components/elkm1/__init__.py +++ b/tests/components/elkm1/__init__.py @@ -1 +1,61 @@ """Tests for the Elk-M1 Control integration.""" + +from contextlib import contextmanager +from unittest.mock import MagicMock, patch + +from elkm1_lib.discovery import ElkSystem + +MOCK_IP_ADDRESS = "127.0.0.1" +MOCK_MAC = "aa:bb:cc:dd:ee:ff" +ELK_DISCOVERY = ElkSystem(MOCK_MAC, MOCK_IP_ADDRESS, 2601) +ELK_NON_SECURE_DISCOVERY = ElkSystem(MOCK_MAC, MOCK_IP_ADDRESS, 2101) + + +def mock_elk(invalid_auth=None, sync_complete=None, exception=None): + """Mock m1lib Elk.""" + + def handler_callbacks(type_, callback): + nonlocal invalid_auth, sync_complete + if exception: + raise exception + if type_ == "login": + callback(not invalid_auth) + elif type_ == "sync_complete" and sync_complete: + callback() + + mocked_elk = MagicMock() + mocked_elk.add_handler.side_effect = handler_callbacks + return mocked_elk + + +def _patch_discovery(device=None, no_device=False): + async def _discovery(*args, **kwargs): + return [] if no_device else [device or ELK_DISCOVERY] + + @contextmanager + def _patcher(): + with patch( + "homeassistant.components.elkm1.discovery.AIOELKDiscovery.async_scan", + new=_discovery, + ): + yield + + return _patcher() + + +def _patch_elk(elk=None): + def _elk(*args, **kwargs): + return elk if elk else mock_elk() + + @contextmanager + def _patcher(): + with patch( + "homeassistant.components.elkm1.config_flow.elkm1.Elk", + new=_elk, + ), patch( + "homeassistant.components.elkm1.config_flow.elkm1.Elk", + new=_elk, + ): + yield + + return _patcher() diff --git a/tests/components/elkm1/test_config_flow.py b/tests/components/elkm1/test_config_flow.py index ab5ebba79ebe0c..76db04944b5837 100644 --- a/tests/components/elkm1/test_config_flow.py +++ b/tests/components/elkm1/test_config_flow.py @@ -1,43 +1,48 @@ """Test the Elk-M1 Control config flow.""" +from dataclasses import asdict +from unittest.mock import patch -from unittest.mock import MagicMock, patch +import pytest from homeassistant import config_entries +from homeassistant.components import dhcp from homeassistant.components.elkm1.const import DOMAIN +from homeassistant.const import CONF_HOST, CONF_PASSWORD +from homeassistant.data_entry_flow import RESULT_TYPE_ABORT, RESULT_TYPE_FORM +from . import ( + ELK_DISCOVERY, + ELK_NON_SECURE_DISCOVERY, + MOCK_IP_ADDRESS, + MOCK_MAC, + _patch_discovery, + _patch_elk, + mock_elk, +) -def mock_elk(invalid_auth=None, sync_complete=None): - """Mock m1lib Elk.""" +from tests.common import MockConfigEntry - def handler_callbacks(type_, callback): - nonlocal invalid_auth, sync_complete +DHCP_DISCOVERY = dhcp.DhcpServiceInfo(MOCK_IP_ADDRESS, "", MOCK_MAC) +ELK_DISCOVERY_INFO = asdict(ELK_DISCOVERY) +MODULE = "homeassistant.components.elkm1" - if type_ == "login": - if invalid_auth is not None: - callback(not invalid_auth) - elif type_ == "sync_complete" and sync_complete: - callback() - mocked_elk = MagicMock() - mocked_elk.add_handler.side_effect = handler_callbacks - return mocked_elk - - -async def test_form_user_with_secure_elk(hass): +async def test_form_user_with_secure_elk_no_discovery(hass): """Test we can setup a secure elk.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) + with _patch_discovery(no_device=True): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + await hass.async_block_till_done() + assert result["type"] == "form" assert result["errors"] == {} + assert result["step_id"] == "manual_connection" mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) - with patch( - "homeassistant.components.elkm1.config_flow.elkm1.Elk", - return_value=mocked_elk, - ), patch( + with _patch_discovery(no_device=True), _patch_elk(elk=mocked_elk), patch( "homeassistant.components.elkm1.async_setup", return_value=True ) as mock_setup, patch( "homeassistant.components.elkm1.async_setup_entry", @@ -50,7 +55,6 @@ async def test_form_user_with_secure_elk(hass): "address": "1.2.3.4", "username": "test-username", "password": "test-password", - "temperature_unit": "°F", "prefix": "", }, ) @@ -63,28 +67,227 @@ async def test_form_user_with_secure_elk(hass): "host": "elks://1.2.3.4", "password": "test-password", "prefix": "", - "temperature_unit": "°F", "username": "test-username", } assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 -async def test_form_user_with_tls_elk(hass): +async def test_form_user_with_secure_elk_no_discovery_ip_already_configured(hass): + """Test we abort when we try to configure the same ip.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: f"elks://{MOCK_IP_ADDRESS}"}, + unique_id="cc:cc:cc:cc:cc:cc", + ) + config_entry.add_to_hass(hass) + + with _patch_discovery(no_device=True): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + await hass.async_block_till_done() + + assert result["type"] == "form" + assert result["errors"] == {} + assert result["step_id"] == "manual_connection" + + mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) + + with _patch_discovery(no_device=True), _patch_elk(elk=mocked_elk): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "protocol": "secure", + "address": "127.0.0.1", + "username": "test-username", + "password": "test-password", + "prefix": "", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_ABORT + assert result2["reason"] == "address_already_configured" + + +async def test_form_user_with_secure_elk_with_discovery(hass): """Test we can setup a secure elk.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) + with _patch_discovery(): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + await hass.async_block_till_done() + + assert result["type"] == "form" + assert result["errors"] is None + assert result["step_id"] == "user" + + mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) + + with _patch_elk(elk=mocked_elk): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"device": MOCK_MAC}, + ) + await hass.async_block_till_done() + + with _patch_discovery(), _patch_elk(elk=mocked_elk), patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + { + "username": "test-username", + "password": "test-password", + }, + ) + await hass.async_block_till_done() + + assert result3["type"] == "create_entry" + assert result3["title"] == "ElkM1 ddeeff" + assert result3["data"] == { + "auto_configure": True, + "host": "elks://127.0.0.1:2601", + "password": "test-password", + "prefix": "", + "username": "test-username", + } + assert result3["result"].unique_id == "aa:bb:cc:dd:ee:ff" + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_user_with_secure_elk_with_discovery_pick_manual(hass): + """Test we can setup a secure elk with discovery but user picks manual and directed discovery fails.""" + + with _patch_discovery(): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + await hass.async_block_till_done() + + assert result["type"] == "form" + assert result["errors"] is None + assert result["step_id"] == "user" + + mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) + + with _patch_elk(elk=mocked_elk): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"device": None}, + ) + await hass.async_block_till_done() + + with _patch_discovery(), _patch_elk(elk=mocked_elk), patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + { + "protocol": "secure", + "address": "1.2.3.4", + "username": "test-username", + "password": "test-password", + "prefix": "", + }, + ) + await hass.async_block_till_done() + + assert result3["type"] == "create_entry" + assert result3["title"] == "ElkM1" + assert result3["data"] == { + "auto_configure": True, + "host": "elks://1.2.3.4", + "password": "test-password", + "prefix": "", + "username": "test-username", + } + assert result3["result"].unique_id is None + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_user_with_secure_elk_with_discovery_pick_manual_direct_discovery( + hass, +): + """Test we can setup a secure elk with discovery but user picks manual and directed discovery succeeds.""" + + with _patch_discovery(): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + await hass.async_block_till_done() + + assert result["type"] == "form" + assert result["errors"] is None + assert result["step_id"] == "user" + + mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) + + with _patch_elk(elk=mocked_elk): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"device": None}, + ) + await hass.async_block_till_done() + + with _patch_discovery(), _patch_elk(elk=mocked_elk), patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + { + "protocol": "secure", + "address": "127.0.0.1", + "username": "test-username", + "password": "test-password", + "prefix": "", + }, + ) + await hass.async_block_till_done() + + assert result3["type"] == "create_entry" + assert result3["title"] == "ElkM1 ddeeff" + assert result3["data"] == { + "auto_configure": True, + "host": "elks://127.0.0.1:2601", + "password": "test-password", + "prefix": "", + "username": "test-username", + } + assert result3["result"].unique_id == MOCK_MAC + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_user_with_tls_elk_no_discovery(hass): + """Test we can setup a secure elk.""" + + with _patch_discovery(no_device=True): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + await hass.async_block_till_done() + assert result["type"] == "form" assert result["errors"] == {} + assert result["step_id"] == "manual_connection" mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) - with patch( - "homeassistant.components.elkm1.config_flow.elkm1.Elk", - return_value=mocked_elk, - ), patch( + with _patch_discovery(no_device=True), _patch_elk(elk=mocked_elk), patch( "homeassistant.components.elkm1.async_setup", return_value=True ) as mock_setup, patch( "homeassistant.components.elkm1.async_setup_entry", @@ -97,7 +300,6 @@ async def test_form_user_with_tls_elk(hass): "address": "1.2.3.4", "username": "test-username", "password": "test-password", - "temperature_unit": "°F", "prefix": "", }, ) @@ -110,28 +312,28 @@ async def test_form_user_with_tls_elk(hass): "host": "elksv1_2://1.2.3.4", "password": "test-password", "prefix": "", - "temperature_unit": "°F", "username": "test-username", } assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 -async def test_form_user_with_non_secure_elk(hass): +async def test_form_user_with_non_secure_elk_no_discovery(hass): """Test we can setup a non-secure elk.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) + with _patch_discovery(no_device=True): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + await hass.async_block_till_done() + assert result["type"] == "form" assert result["errors"] == {} + assert result["step_id"] == "manual_connection" mocked_elk = mock_elk(invalid_auth=None, sync_complete=True) - with patch( - "homeassistant.components.elkm1.config_flow.elkm1.Elk", - return_value=mocked_elk, - ), patch( + with _patch_discovery(no_device=True), _patch_elk(elk=mocked_elk), patch( "homeassistant.components.elkm1.async_setup", return_value=True ) as mock_setup, patch( "homeassistant.components.elkm1.async_setup_entry", @@ -142,7 +344,6 @@ async def test_form_user_with_non_secure_elk(hass): { "protocol": "non-secure", "address": "1.2.3.4", - "temperature_unit": "°F", "prefix": "guest_house", }, ) @@ -156,27 +357,27 @@ async def test_form_user_with_non_secure_elk(hass): "prefix": "guest_house", "username": "", "password": "", - "temperature_unit": "°F", } assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 -async def test_form_user_with_serial_elk(hass): +async def test_form_user_with_serial_elk_no_discovery(hass): """Test we can setup a serial elk.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) + with _patch_discovery(no_device=True): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + await hass.async_block_till_done() + assert result["type"] == "form" assert result["errors"] == {} + assert result["step_id"] == "manual_connection" mocked_elk = mock_elk(invalid_auth=None, sync_complete=True) - with patch( - "homeassistant.components.elkm1.config_flow.elkm1.Elk", - return_value=mocked_elk, - ), patch( + with _patch_discovery(no_device=True), _patch_elk(elk=mocked_elk), patch( "homeassistant.components.elkm1.async_setup", return_value=True ) as mock_setup, patch( "homeassistant.components.elkm1.async_setup_entry", @@ -187,7 +388,6 @@ async def test_form_user_with_serial_elk(hass): { "protocol": "serial", "address": "/dev/ttyS0:115200", - "temperature_unit": "°C", "prefix": "", }, ) @@ -201,7 +401,6 @@ async def test_form_user_with_serial_elk(hass): "prefix": "", "username": "", "password": "", - "temperature_unit": "°C", } assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 @@ -209,18 +408,50 @@ async def test_form_user_with_serial_elk(hass): async def test_form_cannot_connect(hass): """Test we handle cannot connect error.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) + with _patch_discovery(no_device=True): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) mocked_elk = mock_elk(invalid_auth=None, sync_complete=None) - with patch( - "homeassistant.components.elkm1.config_flow.elkm1.Elk", - return_value=mocked_elk, + with _patch_discovery(no_device=True), _patch_elk(elk=mocked_elk), patch( + "homeassistant.components.elkm1.config_flow.VALIDATE_TIMEOUT", + 0, ), patch( + "homeassistant.components.elkm1.config_flow.LOGIN_TIMEOUT", + 0, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "protocol": "secure", + "address": "1.2.3.4", + "username": "test-username", + "password": "test-password", + "prefix": "", + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {CONF_HOST: "cannot_connect"} + + +async def test_unknown_exception(hass): + """Test we handle an unknown exception during connecting.""" + with _patch_discovery(no_device=True): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + mocked_elk = mock_elk(invalid_auth=None, sync_complete=None, exception=OSError) + + with _patch_discovery(no_device=True), _patch_elk(elk=mocked_elk), patch( "homeassistant.components.elkm1.config_flow.VALIDATE_TIMEOUT", 0, + ), patch( + "homeassistant.components.elkm1.config_flow.LOGIN_TIMEOUT", + 0, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -229,13 +460,12 @@ async def test_form_cannot_connect(hass): "address": "1.2.3.4", "username": "test-username", "password": "test-password", - "temperature_unit": "°F", "prefix": "", }, ) assert result2["type"] == "form" - assert result2["errors"] == {"base": "cannot_connect"} + assert result2["errors"] == {"base": "unknown"} async def test_form_invalid_auth(hass): @@ -257,23 +487,46 @@ async def test_form_invalid_auth(hass): "address": "1.2.3.4", "username": "test-username", "password": "test-password", - "temperature_unit": "°F", "prefix": "", }, ) assert result2["type"] == "form" - assert result2["errors"] == {"base": "invalid_auth"} + assert result2["errors"] == {CONF_PASSWORD: "invalid_auth"} -async def test_form_import(hass): - """Test we get the form with import source.""" +async def test_form_invalid_auth_no_password(hass): + """Test we handle invalid auth error when no password is provided.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + mocked_elk = mock_elk(invalid_auth=True, sync_complete=True) - mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) with patch( "homeassistant.components.elkm1.config_flow.elkm1.Elk", return_value=mocked_elk, - ), patch( + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "protocol": "secure", + "address": "1.2.3.4", + "username": "test-username", + "password": "", + "prefix": "", + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {CONF_PASSWORD: "invalid_auth"} + + +async def test_form_import(hass): + """Test we get the form with import source.""" + + mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) + with _patch_discovery(no_device=True), _patch_elk(elk=mocked_elk), patch( "homeassistant.components.elkm1.async_setup", return_value=True ) as mock_setup, patch( "homeassistant.components.elkm1.async_setup_entry", @@ -332,3 +585,381 @@ async def test_form_import(hass): } assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_import_device_discovered(hass): + """Test we can import with discovery.""" + + mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) + with _patch_discovery(), _patch_elk(elk=mocked_elk), patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + "host": "elks://127.0.0.1", + "username": "friend", + "password": "love", + "temperature_unit": "C", + "auto_configure": False, + "keypad": { + "enabled": True, + "exclude": [], + "include": [[1, 1], [2, 2], [3, 3]], + }, + "output": {"enabled": False, "exclude": [], "include": []}, + "counter": {"enabled": False, "exclude": [], "include": []}, + "plc": {"enabled": False, "exclude": [], "include": []}, + "prefix": "ohana", + "setting": {"enabled": False, "exclude": [], "include": []}, + "area": {"enabled": False, "exclude": [], "include": []}, + "task": {"enabled": False, "exclude": [], "include": []}, + "thermostat": {"enabled": False, "exclude": [], "include": []}, + "zone": { + "enabled": True, + "exclude": [[15, 15], [28, 208]], + "include": [], + }, + }, + ) + await hass.async_block_till_done() + + assert result["type"] == "create_entry" + assert result["title"] == "ohana" + assert result["result"].unique_id == MOCK_MAC + assert result["data"] == { + "auto_configure": False, + "host": "elks://127.0.0.1", + "keypad": {"enabled": True, "exclude": [], "include": [[1, 1], [2, 2], [3, 3]]}, + "output": {"enabled": False, "exclude": [], "include": []}, + "password": "love", + "plc": {"enabled": False, "exclude": [], "include": []}, + "prefix": "ohana", + "setting": {"enabled": False, "exclude": [], "include": []}, + "area": {"enabled": False, "exclude": [], "include": []}, + "counter": {"enabled": False, "exclude": [], "include": []}, + "task": {"enabled": False, "exclude": [], "include": []}, + "temperature_unit": "C", + "thermostat": {"enabled": False, "exclude": [], "include": []}, + "username": "friend", + "zone": {"enabled": True, "exclude": [[15, 15], [28, 208]], "include": []}, + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +@pytest.mark.parametrize( + "source, data", + [ + (config_entries.SOURCE_DHCP, DHCP_DISCOVERY), + (config_entries.SOURCE_DISCOVERY, ELK_DISCOVERY_INFO), + ], +) +async def test_discovered_by_dhcp_or_discovery_mac_address_mismatch_host_already_configured( + hass, source, data +): + """Test we abort if the host is already configured but the mac does not match.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: f"elks://{MOCK_IP_ADDRESS}"}, + unique_id="cc:cc:cc:cc:cc:cc", + ) + config_entry.add_to_hass(hass) + + with _patch_discovery(), _patch_elk(): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": source}, data=data + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + + assert config_entry.unique_id == "cc:cc:cc:cc:cc:cc" + + +@pytest.mark.parametrize( + "source, data", + [ + (config_entries.SOURCE_DHCP, DHCP_DISCOVERY), + (config_entries.SOURCE_DISCOVERY, ELK_DISCOVERY_INFO), + ], +) +async def test_discovered_by_dhcp_or_discovery_adds_missing_unique_id( + hass, source, data +): + """Test we add a missing unique id to the config entry.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: f"elks://{MOCK_IP_ADDRESS}"}, + ) + config_entry.add_to_hass(hass) + + with _patch_discovery(), _patch_elk(): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": source}, data=data + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + + assert config_entry.unique_id == MOCK_MAC + + +async def test_discovered_by_discovery_and_dhcp(hass): + """Test we get the form with discovery and abort for dhcp source when we get both.""" + + with _patch_discovery(), _patch_elk(): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DISCOVERY}, + data=ELK_DISCOVERY_INFO, + ) + await hass.async_block_till_done() + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {} + + with _patch_discovery(), _patch_elk(): + result2 = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DHCP}, + data=DHCP_DISCOVERY, + ) + await hass.async_block_till_done() + assert result2["type"] == RESULT_TYPE_ABORT + assert result2["reason"] == "already_in_progress" + + with _patch_discovery(), _patch_elk(): + result3 = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DHCP}, + data=dhcp.DhcpServiceInfo( + hostname="any", + ip=MOCK_IP_ADDRESS, + macaddress="00:00:00:00:00:00", + ), + ) + await hass.async_block_till_done() + assert result3["type"] == RESULT_TYPE_ABORT + assert result3["reason"] == "already_in_progress" + + +async def test_discovered_by_discovery(hass): + """Test we can setup when discovered from discovery.""" + + with _patch_discovery(), _patch_elk(): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DISCOVERY}, + data=ELK_DISCOVERY_INFO, + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "discovered_connection" + assert result["errors"] == {} + + mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) + + with _patch_discovery(), _patch_elk(elk=mocked_elk), patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "username": "test-username", + "password": "test-password", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "ElkM1 ddeeff" + assert result2["data"] == { + "auto_configure": True, + "host": "elks://127.0.0.1:2601", + "password": "test-password", + "prefix": "", + "username": "test-username", + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_discovered_by_discovery_url_already_configured(hass): + """Test we abort when we discover a device that is already setup.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: f"elks://{MOCK_IP_ADDRESS}"}, + unique_id="cc:cc:cc:cc:cc:cc", + ) + config_entry.add_to_hass(hass) + + with _patch_discovery(), _patch_elk(): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DISCOVERY}, + data=ELK_DISCOVERY_INFO, + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + + +async def test_discovered_by_dhcp_udp_responds(hass): + """Test we can setup when discovered from dhcp but with udp response.""" + + with _patch_discovery(), _patch_elk(): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=DHCP_DISCOVERY + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "discovered_connection" + assert result["errors"] == {} + + mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) + + with _patch_discovery(), _patch_elk(elk=mocked_elk), patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "username": "test-username", + "password": "test-password", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "ElkM1 ddeeff" + assert result2["data"] == { + "auto_configure": True, + "host": "elks://127.0.0.1:2601", + "password": "test-password", + "prefix": "", + "username": "test-username", + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_discovered_by_dhcp_udp_responds_with_nonsecure_port(hass): + """Test we can setup when discovered from dhcp but with udp response using the non-secure port.""" + + with _patch_discovery(device=ELK_NON_SECURE_DISCOVERY), _patch_elk(): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=DHCP_DISCOVERY + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "discovered_connection" + assert result["errors"] == {} + + mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) + + with _patch_discovery(device=ELK_NON_SECURE_DISCOVERY), _patch_elk( + elk=mocked_elk + ), patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "username": "test-username", + "password": "test-password", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "ElkM1 ddeeff" + assert result2["data"] == { + "auto_configure": True, + "host": "elk://127.0.0.1:2101", + "password": "test-password", + "prefix": "", + "username": "test-username", + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_discovered_by_dhcp_udp_responds_existing_config_entry(hass): + """Test we can setup when discovered from dhcp but with udp response with an existing config entry.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: "elks://6.6.6.6"}, + unique_id="cc:cc:cc:cc:cc:cc", + ) + config_entry.add_to_hass(hass) + + with _patch_discovery(), _patch_elk(): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=DHCP_DISCOVERY + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "discovered_connection" + assert result["errors"] == {} + + mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) + + with _patch_discovery(), _patch_elk(elk=mocked_elk), patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "username": "test-username", + "password": "test-password", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "ElkM1 ddeeff" + assert result2["data"] == { + "auto_configure": True, + "host": "elks://127.0.0.1:2601", + "password": "test-password", + "prefix": "ddeeff", + "username": "test-username", + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 2 + + +async def test_discovered_by_dhcp_no_udp_response(hass): + """Test we can setup when discovered from dhcp but no udp response.""" + + with _patch_discovery(no_device=True), _patch_elk(): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=DHCP_DISCOVERY + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "cannot_connect" From afd0005a318440cab99841c5d8b29afe0a6e2630 Mon Sep 17 00:00:00 2001 From: Patrik Lindgren <21142447+ggravlingen@users.noreply.github.com> Date: Tue, 8 Feb 2022 02:21:22 +0100 Subject: [PATCH 0412/1098] Add sensor for filter time left on Tradfri fan platform (#65877) * Add support for filter time left * Fix test for fan platform * Remove debug code * Add unique id migration tool * Convert to hours * Fix tests * Apply suggestions from code review Co-authored-by: Martin Hjelmare * Add comment, check migration * Refactor migration helper * Refactor migration helper * Move definition of new unique id * Return after warning * Add test for unique id migration Co-authored-by: Martin Hjelmare --- homeassistant/components/tradfri/const.py | 4 + homeassistant/components/tradfri/sensor.py | 114 ++++++++++++++++++--- tests/components/tradfri/test_fan.py | 4 +- tests/components/tradfri/test_sensor.py | 84 ++++++++++++++- 4 files changed, 185 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/tradfri/const.py b/homeassistant/components/tradfri/const.py index c87d2097929416..3d68ebbaee0c28 100644 --- a/homeassistant/components/tradfri/const.py +++ b/homeassistant/components/tradfri/const.py @@ -1,4 +1,6 @@ """Consts used by Tradfri.""" +from typing import Final + from homeassistant.components.light import SUPPORT_BRIGHTNESS, SUPPORT_TRANSITION from homeassistant.const import ( # noqa: F401 pylint: disable=unused-import CONF_HOST, @@ -43,3 +45,5 @@ COORDINATOR = "coordinator" COORDINATOR_LIST = "coordinator_list" GROUPS_LIST = "groups_list" + +ATTR_FILTER_LIFE_REMAINING: Final = "filter_life_remaining" diff --git a/homeassistant/components/tradfri/sensor.py b/homeassistant/components/tradfri/sensor.py index 693ebeead0082b..1654b780124601 100644 --- a/homeassistant/components/tradfri/sensor.py +++ b/homeassistant/components/tradfri/sensor.py @@ -3,6 +3,7 @@ from collections.abc import Callable from dataclasses import dataclass +import logging from typing import Any, cast from pytradfri.command import Command @@ -12,16 +13,32 @@ SensorDeviceClass, SensorEntity, SensorEntityDescription, + SensorStateClass, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, PERCENTAGE -from homeassistant.core import HomeAssistant +from homeassistant.const import ( + CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + PERCENTAGE, + TIME_HOURS, + Platform, +) +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import entity_registry from homeassistant.helpers.entity_platform import AddEntitiesCallback from .base_class import TradfriBaseEntity -from .const import CONF_GATEWAY_ID, COORDINATOR, COORDINATOR_LIST, DOMAIN, KEY_API +from .const import ( + ATTR_FILTER_LIFE_REMAINING, + CONF_GATEWAY_ID, + COORDINATOR, + COORDINATOR_LIST, + DOMAIN, + KEY_API, +) from .coordinator import TradfriDeviceDataUpdateCoordinator +_LOGGER = logging.getLogger(__name__) + @dataclass class TradfriSensorEntityDescriptionMixin: @@ -48,21 +65,71 @@ def _get_air_quality(device: Device) -> int | None: return cast(int, device.air_purifier_control.air_purifiers[0].air_quality) -SENSOR_DESCRIPTION_AQI = TradfriSensorEntityDescription( - device_class=SensorDeviceClass.AQI, - native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - key=SensorDeviceClass.AQI, - value=_get_air_quality, +def _get_filter_time_left(device: Device) -> int: + """Fetch the filter's remaining life (in hours).""" + return round( + device.air_purifier_control.air_purifiers[0].filter_lifetime_remaining / 60 + ) + + +SENSOR_DESCRIPTIONS_BATTERY: tuple[TradfriSensorEntityDescription, ...] = ( + TradfriSensorEntityDescription( + key="battery_level", + device_class=SensorDeviceClass.BATTERY, + native_unit_of_measurement=PERCENTAGE, + value=lambda device: cast(int, device.device_info.battery_level), + ), ) -SENSOR_DESCRIPTION_BATTERY = TradfriSensorEntityDescription( - device_class=SensorDeviceClass.BATTERY, - native_unit_of_measurement=PERCENTAGE, - key=SensorDeviceClass.BATTERY, - value=lambda device: cast(int, device.device_info.battery_level), + +SENSOR_DESCRIPTIONS_FAN: tuple[TradfriSensorEntityDescription, ...] = ( + TradfriSensorEntityDescription( + key="aqi", + name="air quality", + device_class=SensorDeviceClass.AQI, + native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + value=_get_air_quality, + ), + TradfriSensorEntityDescription( + key=ATTR_FILTER_LIFE_REMAINING, + name="filter time left", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=TIME_HOURS, + icon="mdi:clock-outline", + value=_get_filter_time_left, + ), ) +@callback +def _migrate_old_unique_ids(hass: HomeAssistant, old_unique_id: str, key: str) -> None: + """Migrate unique IDs to the new format.""" + ent_reg = entity_registry.async_get(hass) + + entity_id = ent_reg.async_get_entity_id(Platform.SENSOR, DOMAIN, old_unique_id) + + if entity_id is None: + return + + new_unique_id = f"{old_unique_id}-{key}" + + try: + ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id) + except ValueError: + _LOGGER.warning( + "Skip migration of id [%s] to [%s] because it already exists", + old_unique_id, + new_unique_id, + ) + return + + _LOGGER.debug( + "Migrating unique_id from [%s] to [%s]", + old_unique_id, + new_unique_id, + ) + + async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, @@ -76,18 +143,26 @@ async def async_setup_entry( entities: list[TradfriSensor] = [] for device_coordinator in coordinator_data[COORDINATOR_LIST]: - description = None if ( not device_coordinator.device.has_light_control and not device_coordinator.device.has_socket_control and not device_coordinator.device.has_signal_repeater_control and not device_coordinator.device.has_air_purifier_control ): - description = SENSOR_DESCRIPTION_BATTERY + descriptions = SENSOR_DESCRIPTIONS_BATTERY elif device_coordinator.device.has_air_purifier_control: - description = SENSOR_DESCRIPTION_AQI + descriptions = SENSOR_DESCRIPTIONS_FAN + else: + continue + + for description in descriptions: + # Added in Home assistant 2022.3 + _migrate_old_unique_ids( + hass=hass, + old_unique_id=f"{gateway_id}-{device_coordinator.device.id}", + key=description.key, + ) - if description: entities.append( TradfriSensor( device_coordinator, @@ -121,6 +196,11 @@ def __init__( self.entity_description = description + self._attr_unique_id = f"{self._attr_unique_id}-{description.key}" + + if description.name: + self._attr_name = f"{self._attr_name}: {description.name}" + self._refresh() # Set initial state def _refresh(self) -> None: diff --git a/tests/components/tradfri/test_fan.py b/tests/components/tradfri/test_fan.py index 4db4ed4e58505d..63e6a6558c9644 100644 --- a/tests/components/tradfri/test_fan.py +++ b/tests/components/tradfri/test_fan.py @@ -129,8 +129,8 @@ async def test_set_percentage( responses = mock_gateway.mock_responses mock_gateway_response = responses[0] - # A KeyError is raised if we don't add the 5908 response code - mock_gateway_response["15025"][0].update({"5908": 10, "5907": 12}) + # A KeyError is raised if we don't this to the response code + mock_gateway_response["15025"][0].update({"5908": 10, "5907": 12, "5910": 20}) # Use the callback function to update the fan state. dev = Device(mock_gateway_response) diff --git a/tests/components/tradfri/test_sensor.py b/tests/components/tradfri/test_sensor.py index 04f6534412558b..a36ff93a60774c 100644 --- a/tests/components/tradfri/test_sensor.py +++ b/tests/components/tradfri/test_sensor.py @@ -1,10 +1,17 @@ """Tradfri sensor platform tests.""" +from __future__ import annotations from unittest.mock import MagicMock, Mock +from homeassistant.components import tradfri +from homeassistant.helpers import entity_registry as er + +from . import GATEWAY_ID from .common import setup_integration from .test_fan import mock_fan +from tests.common import MockConfigEntry + def mock_sensor(test_state: list, device_number=0): """Mock a tradfri sensor.""" @@ -69,17 +76,42 @@ async def test_cover_battery_sensor(hass, mock_gateway, mock_api_factory): async def test_air_quality_sensor(hass, mock_gateway, mock_api_factory): """Test that a battery sensor is correctly added.""" mock_gateway.mock_devices.append( - mock_fan(test_state={"fan_speed": 10, "air_quality": 42}) + mock_fan( + test_state={ + "fan_speed": 10, + "air_quality": 42, + "filter_lifetime_remaining": 120, + } + ) ) await setup_integration(hass) - sensor_1 = hass.states.get("sensor.tradfri_fan_0") + sensor_1 = hass.states.get("sensor.tradfri_fan_0_air_quality") assert sensor_1 is not None assert sensor_1.state == "42" assert sensor_1.attributes["unit_of_measurement"] == "µg/m³" assert sensor_1.attributes["device_class"] == "aqi" +async def test_filter_time_left_sensor(hass, mock_gateway, mock_api_factory): + """Test that a battery sensor is correctly added.""" + mock_gateway.mock_devices.append( + mock_fan( + test_state={ + "fan_speed": 10, + "air_quality": 42, + "filter_lifetime_remaining": 120, + } + ) + ) + await setup_integration(hass) + + sensor_1 = hass.states.get("sensor.tradfri_fan_0_filter_time_left") + assert sensor_1 is not None + assert sensor_1.state == "2" + assert sensor_1.attributes["unit_of_measurement"] == "h" + + async def test_sensor_observed(hass, mock_gateway, mock_api_factory): """Test that sensors are correctly observed.""" sensor = mock_sensor(test_state=[{"attribute": "battery_level", "value": 60}]) @@ -106,3 +138,51 @@ async def test_sensor_available(hass, mock_gateway, mock_api_factory): assert hass.states.get("sensor.tradfri_sensor_1").state == "60" assert hass.states.get("sensor.tradfri_sensor_2").state == "unavailable" + + +async def test_unique_id_migration(hass, mock_gateway, mock_api_factory): + """Test unique ID is migrated from old format to new.""" + ent_reg = er.async_get(hass) + old_unique_id = f"{GATEWAY_ID}-mock-sensor-id-0" + entry = MockConfigEntry( + domain=tradfri.DOMAIN, + data={ + "host": "mock-host", + "identity": "mock-identity", + "key": "mock-key", + "import_groups": False, + "gateway_id": GATEWAY_ID, + }, + ) + entry.add_to_hass(hass) + + # Version 1 + sensor_name = "sensor.tradfri_sensor_0" + entity_name = sensor_name.split(".")[1] + + entity_entry = ent_reg.async_get_or_create( + "sensor", + tradfri.DOMAIN, + old_unique_id, + suggested_object_id=entity_name, + config_entry=entry, + original_name=entity_name, + ) + + assert entity_entry.entity_id == sensor_name + assert entity_entry.unique_id == old_unique_id + + # Add a sensor to the gateway so that it populates coordinator list + sensor = mock_sensor( + test_state=[{"attribute": "battery_level", "value": 60}], + ) + mock_gateway.mock_devices.append(sensor) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + # Check that new RegistryEntry is using new unique ID format + entity_entry = ent_reg.async_get(sensor_name) + new_unique_id = f"{GATEWAY_ID}-mock-sensor-id-0-battery_level" + assert entity_entry.unique_id == new_unique_id + assert ent_reg.async_get_entity_id("sensor", tradfri.DOMAIN, old_unique_id) is None From f9c81dd00be8ef0546658a7695bc6961e8f0b0c3 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Tue, 8 Feb 2022 02:50:38 +0100 Subject: [PATCH 0413/1098] Lock Netgear api during setup (#66033) --- homeassistant/components/netgear/router.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/netgear/router.py b/homeassistant/components/netgear/router.py index 722bcb27ae0ae4..3778c36d81a527 100644 --- a/homeassistant/components/netgear/router.py +++ b/homeassistant/components/netgear/router.py @@ -123,8 +123,9 @@ def _setup(self) -> None: async def async_setup(self) -> bool: """Set up a Netgear router.""" - if not await self.hass.async_add_executor_job(self._setup): - return False + async with self._api_lock: + if not await self.hass.async_add_executor_job(self._setup): + return False # set already known devices to away instead of unavailable device_registry = dr.async_get(self.hass) From 22e379cd54439128ab956e9662791cd42d67a8eb Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 8 Feb 2022 02:56:32 +0100 Subject: [PATCH 0414/1098] Add support for mc devices to Tuya (#66044) --- homeassistant/components/tuya/binary_sensor.py | 8 ++++++++ homeassistant/components/tuya/sensor.py | 3 +++ 2 files changed, 11 insertions(+) diff --git a/homeassistant/components/tuya/binary_sensor.py b/homeassistant/components/tuya/binary_sensor.py index 56bc59e546f10a..2c61151bfafb88 100644 --- a/homeassistant/components/tuya/binary_sensor.py +++ b/homeassistant/components/tuya/binary_sensor.py @@ -110,6 +110,14 @@ class TuyaBinarySensorEntityDescription(BinarySensorEntityDescription): ), TAMPER_BINARY_SENSOR, ), + # Door and Window Controller + # https://developer.tuya.com/en/docs/iot/s?id=K9gf48r5zjsy9 + "mc": ( + TuyaBinarySensorEntityDescription( + key=DPCode.DOORCONTACT_STATE, + device_class=BinarySensorDeviceClass.DOOR, + ), + ), # Door Window Sensor # https://developer.tuya.com/en/docs/iot/s?id=K9gf48hm02l8m "mcs": ( diff --git a/homeassistant/components/tuya/sensor.py b/homeassistant/components/tuya/sensor.py index 7fcea1095d4e3b..3ee88d2d57be6c 100644 --- a/homeassistant/components/tuya/sensor.py +++ b/homeassistant/components/tuya/sensor.py @@ -295,6 +295,9 @@ class TuyaSensorEntityDescription(SensorEntityDescription): ), *BATTERY_SENSORS, ), + # Door and Window Controller + # https://developer.tuya.com/en/docs/iot/s?id=K9gf48r5zjsy9 + "mc": BATTERY_SENSORS, # Door Window Sensor # https://developer.tuya.com/en/docs/iot/s?id=K9gf48hm02l8m "mcs": BATTERY_SENSORS, From 379945860b0276882d9b00b460a43e8138bb1980 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 8 Feb 2022 02:59:38 +0100 Subject: [PATCH 0415/1098] Add configuration_url to Octoprint discovery (#66046) --- .../components/octoprint/config_flow.py | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/octoprint/config_flow.py b/homeassistant/components/octoprint/config_flow.py index c2f0f96b6d5850..1bd54e2214eeaa 100644 --- a/homeassistant/components/octoprint/config_flow.py +++ b/homeassistant/components/octoprint/config_flow.py @@ -145,9 +145,15 @@ async def async_step_zeroconf( await self.async_set_unique_id(uuid) self._abort_if_unique_id_configured() - self.context["title_placeholders"] = { - CONF_HOST: discovery_info.host, - } + self.context.update( + { + "title_placeholders": {CONF_HOST: discovery_info.host}, + "configuration_url": ( + f"http://{discovery_info.host}:{discovery_info.port}" + f"{discovery_info.properties[CONF_PATH]}" + ), + } + ) self.discovery_schema = _schema_with_defaults( host=discovery_info.host, @@ -166,9 +172,12 @@ async def async_step_ssdp( self._abort_if_unique_id_configured() url = URL(discovery_info.upnp["presentationURL"]) - self.context["title_placeholders"] = { - CONF_HOST: url.host, - } + self.context.update( + { + "title_placeholders": {CONF_HOST: url.host}, + "configuration_url": discovery_info.upnp["presentationURL"], + } + ) self.discovery_schema = _schema_with_defaults( host=url.host, From 895aee3fb2ca7c0f8d6aeb694019334d47c035e9 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 8 Feb 2022 03:00:41 +0100 Subject: [PATCH 0416/1098] Add configuration_url to Plugwise discovery (#66047) --- .../components/plugwise/config_flow.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/plugwise/config_flow.py b/homeassistant/components/plugwise/config_flow.py index 2ce8a686c8b3ec..e69d92e3cd0cab 100644 --- a/homeassistant/components/plugwise/config_flow.py +++ b/homeassistant/components/plugwise/config_flow.py @@ -97,12 +97,17 @@ async def async_step_zeroconf( _version = _properties.get("version", "n/a") _name = f"{ZEROCONF_MAP.get(_product, _product)} v{_version}" - self.context["title_placeholders"] = { - CONF_HOST: discovery_info.host, - CONF_NAME: _name, - CONF_PORT: discovery_info.port, - CONF_USERNAME: self._username, - } + self.context.update( + { + "title_placeholders": { + CONF_HOST: discovery_info.host, + CONF_NAME: _name, + CONF_PORT: discovery_info.port, + CONF_USERNAME: self._username, + }, + "configuration_url": f"http://{discovery_info.host}:{discovery_info.port}", + } + ) return await self.async_step_user() async def async_step_user( From 4076ca96410fba1ec8b640cd7e0dc5762473fbb3 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 8 Feb 2022 00:53:48 -0500 Subject: [PATCH 0417/1098] Fix schema for zwave_js WS API (#66052) --- homeassistant/components/zwave_js/api.py | 3 +++ tests/components/zwave_js/test_api.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index ee0d4eb43a344b..6208091dd8d4eb 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -133,6 +133,7 @@ MAX_INCLUSION_REQUEST_INTERVAL = "max_inclusion_request_interval" UUID = "uuid" SUPPORTED_PROTOCOLS = "supported_protocols" +ADDITIONAL_PROPERTIES = "additional_properties" FEATURE = "feature" UNPROVISION = "unprovision" @@ -170,6 +171,7 @@ def convert_qr_provisioning_information(info: dict) -> QRProvisioningInformation max_inclusion_request_interval=info.get(MAX_INCLUSION_REQUEST_INTERVAL), uuid=info.get(UUID), supported_protocols=protocols if protocols else None, + additional_properties=info.get(ADDITIONAL_PROPERTIES, {}), ) return info @@ -212,6 +214,7 @@ def convert_qr_provisioning_information(info: dict) -> QRProvisioningInformation cv.ensure_list, [vol.Coerce(Protocols)], ), + vol.Optional(ADDITIONAL_PROPERTIES): dict, } ), convert_qr_provisioning_information, diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index 40f60b9018a48c..f93ba4fbb9395d 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -29,6 +29,7 @@ from homeassistant.components.websocket_api.const import ERR_NOT_FOUND from homeassistant.components.zwave_js.api import ( + ADDITIONAL_PROPERTIES, APPLICATION_VERSION, CLIENT_SIDE_AUTH, COMMAND_CLASS_ID, @@ -837,6 +838,7 @@ async def test_provision_smart_start_node(hass, integration, client, hass_ws_cli PRODUCT_TYPE: 1, PRODUCT_ID: 1, APPLICATION_VERSION: "test", + ADDITIONAL_PROPERTIES: {"name": "test"}, }, } ) @@ -861,6 +863,7 @@ async def test_provision_smart_start_node(hass, integration, client, hass_ws_cli max_inclusion_request_interval=None, uuid=None, supported_protocols=None, + additional_properties={"name": "test"}, ).to_dict(), } From a03d8179d01fc989a421885cb3d045509cb3ebd4 Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Tue, 8 Feb 2022 09:48:42 +0100 Subject: [PATCH 0418/1098] Bump velbusaio to 2022.2.3 (#66055) --- homeassistant/components/velbus/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index 71a5b89d534a3e..b38ab3ef5d3b8c 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -2,7 +2,7 @@ "domain": "velbus", "name": "Velbus", "documentation": "https://www.home-assistant.io/integrations/velbus", - "requirements": ["velbus-aio==2022.2.2"], + "requirements": ["velbus-aio==2022.2.3"], "config_flow": true, "codeowners": ["@Cereal2nd", "@brefra"], "dependencies": ["usb"], diff --git a/requirements_all.txt b/requirements_all.txt index f6b385f5bd19e0..0e49d5e7a5a275 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2424,7 +2424,7 @@ vallox-websocket-api==2.9.0 vehicle==0.3.1 # homeassistant.components.velbus -velbus-aio==2022.2.2 +velbus-aio==2022.2.3 # homeassistant.components.venstar venstarcolortouch==0.15 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index aaba6417465a63..8a057ba0fb36f9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1488,7 +1488,7 @@ vallox-websocket-api==2.9.0 vehicle==0.3.1 # homeassistant.components.velbus -velbus-aio==2022.2.2 +velbus-aio==2022.2.3 # homeassistant.components.venstar venstarcolortouch==0.15 From 8b38fa58aa45d1809f6900729b4046d6c02c2230 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Tue, 8 Feb 2022 10:03:27 +0100 Subject: [PATCH 0419/1098] Bump pytest to 7.0.0 (#65981) --- requirements_test.txt | 2 +- tests/components/sun/test_trigger.py | 3 +++ tests/helpers/test_condition.py | 3 +++ tests/helpers/test_event.py | 3 +++ tests/test_config.py | 9 +++++++++ tests/util/test_dt.py | 3 +++ tests/util/test_json.py | 8 ++++---- 7 files changed, 26 insertions(+), 5 deletions(-) diff --git a/requirements_test.txt b/requirements_test.txt index 2bc65934477604..f09102270cf9c1 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -25,7 +25,7 @@ pytest-test-groups==1.0.3 pytest-sugar==0.9.4 pytest-timeout==2.1.0 pytest-xdist==2.4.0 -pytest==6.2.5 +pytest==7.0.0 requests_mock==1.9.2 respx==0.19.0 stdlib-list==0.7.0 diff --git a/tests/components/sun/test_trigger.py b/tests/components/sun/test_trigger.py index f100fb53dc8a80..55c31da6c89a68 100644 --- a/tests/components/sun/test_trigger.py +++ b/tests/components/sun/test_trigger.py @@ -39,8 +39,11 @@ def setup_comp(hass): ) +@pytest.fixture(autouse=True) def teardown(): """Restore.""" + yield + dt_util.set_default_time_zone(ORIG_TIME_ZONE) diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index e0cc9b715c10df..b365c114b03360 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -46,8 +46,11 @@ def setup_comp(hass): ) +@pytest.fixture(autouse=True) def teardown(): """Restore.""" + yield + dt_util.set_default_time_zone(ORIG_TIME_ZONE) diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index 4f62d50da34fef..bd17aec92e655d 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -46,8 +46,11 @@ DEFAULT_TIME_ZONE = dt_util.DEFAULT_TIME_ZONE +@pytest.fixture(autouse=True) def teardown(): """Stop everything that was started.""" + yield + dt_util.set_default_time_zone(DEFAULT_TIME_ZONE) diff --git a/tests/test_config.py b/tests/test_config.py index 93dd1b87b44443..4e761bc3f4706f 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -53,8 +53,11 @@ def create_file(path): pass +@pytest.fixture(autouse=True) def teardown(): """Clean up.""" + yield + dt_util.DEFAULT_TIME_ZONE = ORIG_TIMEZONE if os.path.isfile(YAML_PATH): @@ -78,6 +81,11 @@ def teardown(): async def test_create_default_config(hass): """Test creation of default config.""" + assert not os.path.isfile(YAML_PATH) + assert not os.path.isfile(SECRET_PATH) + assert not os.path.isfile(VERSION_PATH) + assert not os.path.isfile(AUTOMATIONS_PATH) + await config_util.async_create_default_config(hass) assert os.path.isfile(YAML_PATH) @@ -91,6 +99,7 @@ async def test_ensure_config_exists_creates_config(hass): If not creates a new config file. """ + assert not os.path.isfile(YAML_PATH) with patch("builtins.print") as mock_print: await config_util.async_ensure_config_exists(hass) diff --git a/tests/util/test_dt.py b/tests/util/test_dt.py index 63513c9036002e..d2c453f070da0d 100644 --- a/tests/util/test_dt.py +++ b/tests/util/test_dt.py @@ -9,8 +9,11 @@ TEST_TIME_ZONE = "America/Los_Angeles" +@pytest.fixture(autouse=True) def teardown(): """Stop everything that was started.""" + yield + dt_util.set_default_time_zone(DEFAULT_TIME_ZONE) diff --git a/tests/util/test_json.py b/tests/util/test_json.py index af7f43eae4d1d3..461d94d0c6712a 100644 --- a/tests/util/test_json.py +++ b/tests/util/test_json.py @@ -26,14 +26,14 @@ TMP_DIR = None -def setup(): - """Set up for tests.""" +@pytest.fixture(autouse=True) +def setup_and_teardown(): + """Clean up after tests.""" global TMP_DIR TMP_DIR = mkdtemp() + yield -def teardown(): - """Clean up after tests.""" for fname in os.listdir(TMP_DIR): os.remove(os.path.join(TMP_DIR, fname)) os.rmdir(TMP_DIR) From 0ea82bdbfb0d58b1af273e39da65cbb9e4af1015 Mon Sep 17 00:00:00 2001 From: Sander Jochems Date: Tue, 8 Feb 2022 10:27:11 +0100 Subject: [PATCH 0420/1098] Fivem integration (#65089) * Initial fivem integration setup * Use licenseKey for unique ID * Create FiveMServer class * Create FiveMStatusBinarySensor * Fix platform loading * Create sensor platform * Remove config flow tests * Update manifest.json * Use attr_ instead or properties in sensors.py * Use entry_id as unique_id * Move device info to _attr instead of property * Register callback in FiveMEntity * Create config flow tests * Add loggin to fivem * Use FiveM in config_flow * Use update_coordinator instead of dispatcher * Bump fivem-api to 0.1.2 * Remove leftovers * More tests for config flow * Add component files to .coveragerc * Fix simple comments * Add gamename check to config flow * Use entity descriptions for sensors * Move extra attributes to init * Use [] instead of get() for server info * Fix error in gamename test --- .coveragerc | 3 + CODEOWNERS | 2 + homeassistant/components/fivem/__init__.py | 185 ++++++++++++++++++ .../components/fivem/binary_sensor.py | 52 +++++ homeassistant/components/fivem/config_flow.py | 76 +++++++ homeassistant/components/fivem/const.py | 24 +++ homeassistant/components/fivem/manifest.json | 13 ++ homeassistant/components/fivem/sensor.py | 78 ++++++++ homeassistant/components/fivem/strings.json | 20 ++ .../components/fivem/translations/en.json | 20 ++ homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/fivem/__init__.py | 1 + tests/components/fivem/test_config_flow.py | 146 ++++++++++++++ 15 files changed, 627 insertions(+) create mode 100644 homeassistant/components/fivem/__init__.py create mode 100644 homeassistant/components/fivem/binary_sensor.py create mode 100644 homeassistant/components/fivem/config_flow.py create mode 100644 homeassistant/components/fivem/const.py create mode 100644 homeassistant/components/fivem/manifest.json create mode 100644 homeassistant/components/fivem/sensor.py create mode 100644 homeassistant/components/fivem/strings.json create mode 100644 homeassistant/components/fivem/translations/en.json create mode 100644 tests/components/fivem/__init__.py create mode 100644 tests/components/fivem/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index 05f1ed64c333ec..61dccdb2d8b242 100644 --- a/.coveragerc +++ b/.coveragerc @@ -347,6 +347,9 @@ omit = homeassistant/components/firmata/sensor.py homeassistant/components/firmata/switch.py homeassistant/components/fitbit/* + homeassistant/components/fivem/__init__.py + homeassistant/components/fivem/binary_sensor.py + homeassistant/components/fivem/sensor.py homeassistant/components/fixer/sensor.py homeassistant/components/fjaraskupan/__init__.py homeassistant/components/fjaraskupan/binary_sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 7e56031c2bfd57..b78d12939a8fa4 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -287,6 +287,8 @@ homeassistant/components/fireservicerota/* @cyberjunky tests/components/fireservicerota/* @cyberjunky homeassistant/components/firmata/* @DaAwesomeP tests/components/firmata/* @DaAwesomeP +homeassistant/components/fivem/* @Sander0542 +tests/components/fivem/* @Sander0542 homeassistant/components/fixer/* @fabaff homeassistant/components/fjaraskupan/* @elupus tests/components/fjaraskupan/* @elupus diff --git a/homeassistant/components/fivem/__init__.py b/homeassistant/components/fivem/__init__.py new file mode 100644 index 00000000000000..4079b74a598a7c --- /dev/null +++ b/homeassistant/components/fivem/__init__.py @@ -0,0 +1,185 @@ +"""The FiveM integration.""" +from __future__ import annotations + +from collections.abc import Mapping +from dataclasses import dataclass +from datetime import timedelta +import logging +from typing import Any + +from fivem import FiveM, FiveMServerOfflineError + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, Platform +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.entity import DeviceInfo, EntityDescription +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, + UpdateFailed, +) + +from .const import ( + ATTR_PLAYERS_LIST, + ATTR_RESOURCES_LIST, + DOMAIN, + MANUFACTURER, + NAME_PLAYERS_MAX, + NAME_PLAYERS_ONLINE, + NAME_RESOURCES, + NAME_STATUS, + SCAN_INTERVAL, +) + +PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR] + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up FiveM from a config entry.""" + _LOGGER.debug( + "Create FiveM server instance for '%s:%s'", + entry.data[CONF_HOST], + entry.data[CONF_PORT], + ) + + try: + coordinator = FiveMDataUpdateCoordinator(hass, entry.data, entry.entry_id) + await coordinator.initialize() + except FiveMServerOfflineError as err: + raise ConfigEntryNotReady from err + + await coordinator.async_config_entry_first_refresh() + entry.async_on_unload(entry.add_update_listener(update_listener)) + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator + + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok + + +async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Update listener.""" + await hass.config_entries.async_reload(entry.entry_id) + + +class FiveMDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): + """Class to manage fetching FiveM data.""" + + def __init__(self, hass: HomeAssistant, config_data, unique_id: str) -> None: + """Initialize server instance.""" + self._hass = hass + + self.unique_id = unique_id + self.server = None + self.version = None + self.gamename: str | None = None + + self.server_name = config_data[CONF_NAME] + self.host = config_data[CONF_HOST] + self.port = config_data[CONF_PORT] + self.online = False + + self._fivem = FiveM(self.host, self.port) + + update_interval = timedelta(seconds=SCAN_INTERVAL) + _LOGGER.debug("Data will be updated every %s", update_interval) + + super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=update_interval) + + async def initialize(self) -> None: + """Initialize the FiveM server.""" + info = await self._fivem.get_info_raw() + self.server = info["server"] + self.version = info["version"] + self.gamename = info["vars"]["gamename"] + + async def _async_update_data(self) -> dict[str, Any]: + """Get server data from 3rd party library and update properties.""" + was_online = self.online + + try: + server = await self._fivem.get_server() + self.online = True + except FiveMServerOfflineError: + self.online = False + + if was_online and not self.online: + _LOGGER.warning("Connection to '%s:%s' lost", self.host, self.port) + elif not was_online and self.online: + _LOGGER.info("Connection to '%s:%s' (re-)established", self.host, self.port) + + if self.online: + players_list: list[str] = [] + for player in server.players: + players_list.append(player.name) + players_list.sort() + + resources_list = server.resources + resources_list.sort() + + return { + NAME_PLAYERS_ONLINE: len(players_list), + NAME_PLAYERS_MAX: server.max_players, + NAME_RESOURCES: len(resources_list), + NAME_STATUS: self.online, + ATTR_PLAYERS_LIST: players_list, + ATTR_RESOURCES_LIST: resources_list, + } + + raise UpdateFailed + + +@dataclass +class FiveMEntityDescription(EntityDescription): + """Describes FiveM entity.""" + + extra_attrs: list[str] | None = None + + +class FiveMEntity(CoordinatorEntity): + """Representation of a FiveM base entity.""" + + coordinator: FiveMDataUpdateCoordinator + entity_description: FiveMEntityDescription + + def __init__( + self, + coordinator: FiveMDataUpdateCoordinator, + description: FiveMEntityDescription, + ) -> None: + """Initialize base entity.""" + super().__init__(coordinator) + self.entity_description = description + + self._attr_name = f"{self.coordinator.server_name} {description.name}" + self._attr_unique_id = f"{self.coordinator.unique_id}-{description.key}".lower() + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, self.coordinator.unique_id)}, + manufacturer=MANUFACTURER, + model=self.coordinator.server, + name=self.coordinator.server_name, + sw_version=self.coordinator.version, + ) + + @property + def extra_state_attributes(self) -> Mapping[str, Any] | None: + """Return the extra attributes of the sensor.""" + if self.entity_description.extra_attrs is None: + return None + + return { + attr: self.coordinator.data[attr] + for attr in self.entity_description.extra_attrs + } diff --git a/homeassistant/components/fivem/binary_sensor.py b/homeassistant/components/fivem/binary_sensor.py new file mode 100644 index 00000000000000..2e9ea8347997f5 --- /dev/null +++ b/homeassistant/components/fivem/binary_sensor.py @@ -0,0 +1,52 @@ +"""The FiveM binary sensor platform.""" +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, + BinarySensorEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import FiveMEntity, FiveMEntityDescription +from .const import DOMAIN, ICON_STATUS, NAME_STATUS + + +class FiveMBinarySensorEntityDescription( + BinarySensorEntityDescription, FiveMEntityDescription +): + """Describes FiveM binary sensor entity.""" + + +BINARY_SENSORS: tuple[FiveMBinarySensorEntityDescription, ...] = ( + FiveMBinarySensorEntityDescription( + key=NAME_STATUS, + name=NAME_STATUS, + icon=ICON_STATUS, + device_class=BinarySensorDeviceClass.CONNECTIVITY, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the FiveM binary sensor platform.""" + coordinator = hass.data[DOMAIN][entry.entry_id] + + async_add_entities( + [FiveMSensorEntity(coordinator, description) for description in BINARY_SENSORS] + ) + + +class FiveMSensorEntity(FiveMEntity, BinarySensorEntity): + """Representation of a FiveM sensor base entity.""" + + entity_description: FiveMBinarySensorEntityDescription + + @property + def is_on(self) -> bool: + """Return the state of the sensor.""" + return self.coordinator.data[self.entity_description.key] diff --git a/homeassistant/components/fivem/config_flow.py b/homeassistant/components/fivem/config_flow.py new file mode 100644 index 00000000000000..792d5f1b0253cb --- /dev/null +++ b/homeassistant/components/fivem/config_flow.py @@ -0,0 +1,76 @@ +"""Config flow for FiveM integration.""" +from __future__ import annotations + +import logging +from typing import Any + +from fivem import FiveM, FiveMServerOfflineError +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResult +import homeassistant.helpers.config_validation as cv + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +DEFAULT_PORT = 30120 + +STEP_USER_DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } +) + + +async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None: + """Validate the user input allows us to connect.""" + + fivem = FiveM(data[CONF_HOST], data[CONF_PORT]) + info = await fivem.get_info_raw() + + gamename = info.get("vars")["gamename"] + if gamename is None or gamename != "gta5": + raise InvalidGamenameError + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for FiveM.""" + + VERSION = 1 + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the initial step.""" + if user_input is None: + return self.async_show_form( + step_id="user", data_schema=STEP_USER_DATA_SCHEMA + ) + + errors = {} + + try: + await validate_input(self.hass, user_input) + except FiveMServerOfflineError: + errors["base"] = "cannot_connect" + except InvalidGamenameError: + errors["base"] = "invalid_gamename" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + else: + return self.async_create_entry(title=user_input[CONF_NAME], data=user_input) + + return self.async_show_form( + step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors + ) + + +class InvalidGamenameError(Exception): + """Handle errors in the gamename from the api.""" diff --git a/homeassistant/components/fivem/const.py b/homeassistant/components/fivem/const.py new file mode 100644 index 00000000000000..5907aa7300eb27 --- /dev/null +++ b/homeassistant/components/fivem/const.py @@ -0,0 +1,24 @@ +"""Constants for the FiveM integration.""" + +ATTR_PLAYERS_LIST = "players_list" +ATTR_RESOURCES_LIST = "resources_list" + +DOMAIN = "fivem" + +ICON_PLAYERS_MAX = "mdi:account-multiple" +ICON_PLAYERS_ONLINE = "mdi:account-multiple" +ICON_RESOURCES = "mdi:playlist-check" +ICON_STATUS = "mdi:lan" + +MANUFACTURER = "Cfx.re" + +NAME_PLAYERS_MAX = "Players Max" +NAME_PLAYERS_ONLINE = "Players Online" +NAME_RESOURCES = "Resources" +NAME_STATUS = "Status" + +SCAN_INTERVAL = 60 + +UNIT_PLAYERS_MAX = "players" +UNIT_PLAYERS_ONLINE = "players" +UNIT_RESOURCES = "resources" diff --git a/homeassistant/components/fivem/manifest.json b/homeassistant/components/fivem/manifest.json new file mode 100644 index 00000000000000..4a18df0fc951e6 --- /dev/null +++ b/homeassistant/components/fivem/manifest.json @@ -0,0 +1,13 @@ +{ + "domain": "fivem", + "name": "FiveM", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/fivem", + "requirements": [ + "fivem-api==0.1.2" + ], + "codeowners": [ + "@Sander0542" + ], + "iot_class": "local_polling" +} \ No newline at end of file diff --git a/homeassistant/components/fivem/sensor.py b/homeassistant/components/fivem/sensor.py new file mode 100644 index 00000000000000..31e23565a6f6a9 --- /dev/null +++ b/homeassistant/components/fivem/sensor.py @@ -0,0 +1,78 @@ +"""The FiveM sensor platform.""" +from dataclasses import dataclass +from typing import Any + +from homeassistant.components.sensor import SensorEntity, SensorEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import FiveMEntity, FiveMEntityDescription +from .const import ( + ATTR_PLAYERS_LIST, + ATTR_RESOURCES_LIST, + DOMAIN, + ICON_PLAYERS_MAX, + ICON_PLAYERS_ONLINE, + ICON_RESOURCES, + NAME_PLAYERS_MAX, + NAME_PLAYERS_ONLINE, + NAME_RESOURCES, + UNIT_PLAYERS_MAX, + UNIT_PLAYERS_ONLINE, + UNIT_RESOURCES, +) + + +@dataclass +class FiveMSensorEntityDescription(SensorEntityDescription, FiveMEntityDescription): + """Describes FiveM sensor entity.""" + + +SENSORS: tuple[FiveMSensorEntityDescription, ...] = ( + FiveMSensorEntityDescription( + key=NAME_PLAYERS_MAX, + name=NAME_PLAYERS_MAX, + icon=ICON_PLAYERS_MAX, + native_unit_of_measurement=UNIT_PLAYERS_MAX, + ), + FiveMSensorEntityDescription( + key=NAME_PLAYERS_ONLINE, + name=NAME_PLAYERS_ONLINE, + icon=ICON_PLAYERS_ONLINE, + native_unit_of_measurement=UNIT_PLAYERS_ONLINE, + extra_attrs=[ATTR_PLAYERS_LIST], + ), + FiveMSensorEntityDescription( + key=NAME_RESOURCES, + name=NAME_RESOURCES, + icon=ICON_RESOURCES, + native_unit_of_measurement=UNIT_RESOURCES, + extra_attrs=[ATTR_RESOURCES_LIST], + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the FiveM sensor platform.""" + coordinator = hass.data[DOMAIN][entry.entry_id] + + # Add sensor entities. + async_add_entities( + [FiveMSensorEntity(coordinator, description) for description in SENSORS] + ) + + +class FiveMSensorEntity(FiveMEntity, SensorEntity): + """Representation of a FiveM sensor base entity.""" + + entity_description: FiveMSensorEntityDescription + + @property + def native_value(self) -> Any: + """Return the state of the sensor.""" + return self.coordinator.data[self.entity_description.key] diff --git a/homeassistant/components/fivem/strings.json b/homeassistant/components/fivem/strings.json new file mode 100644 index 00000000000000..2949dfb58ad815 --- /dev/null +++ b/homeassistant/components/fivem/strings.json @@ -0,0 +1,20 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "[%key:common::config_flow::data::name%]", + "host": "[%key:common::config_flow::data::host%]", + "port": "[%key:common::config_flow::data::port%]" + } + } + }, + "error": { + "cannot_connect": "Failed to connect. Please check the host and port and try again. Also ensure that you are running the latest FiveM server.", + "invalid_gamename": "The api of the game you are trying to connect to is not a FiveM game." + }, + "abort": { + "already_configured": "FiveM server is already configured" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/en.json b/homeassistant/components/fivem/translations/en.json new file mode 100644 index 00000000000000..a81ce1e1ce517c --- /dev/null +++ b/homeassistant/components/fivem/translations/en.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "FiveM server is already configured" + }, + "error": { + "cannot_connect": "Failed to connect. Please check the host and port and try again. Also ensure that you are running the latest FiveM server.", + "invalid_gamename": "The api of the game you are trying to connect to is not a FiveM game." + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Name", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 061dcd3a0adb7c..2aab7c814819d6 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -95,6 +95,7 @@ "ezviz", "faa_delays", "fireservicerota", + "fivem", "fjaraskupan", "flick_electric", "flipr", diff --git a/requirements_all.txt b/requirements_all.txt index 0e49d5e7a5a275..31994aa6489176 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -671,6 +671,9 @@ fints==1.0.1 # homeassistant.components.fitbit fitbit==0.3.1 +# homeassistant.components.fivem +fivem-api==0.1.2 + # homeassistant.components.fixer fixerio==1.0.0a0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8a057ba0fb36f9..df3f191abdaaa4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -420,6 +420,9 @@ faadelays==0.0.7 # homeassistant.components.feedreader feedparser==6.0.2 +# homeassistant.components.fivem +fivem-api==0.1.2 + # homeassistant.components.fjaraskupan fjaraskupan==1.0.2 diff --git a/tests/components/fivem/__init__.py b/tests/components/fivem/__init__.py new file mode 100644 index 00000000000000..019f9b829135b9 --- /dev/null +++ b/tests/components/fivem/__init__.py @@ -0,0 +1 @@ +"""Tests for the FiveM integration.""" diff --git a/tests/components/fivem/test_config_flow.py b/tests/components/fivem/test_config_flow.py new file mode 100644 index 00000000000000..7af6e0fca4857e --- /dev/null +++ b/tests/components/fivem/test_config_flow.py @@ -0,0 +1,146 @@ +"""Test the FiveM config flow.""" +from unittest.mock import patch + +from fivem import FiveMServerOfflineError + +from homeassistant import config_entries +from homeassistant.components.fivem.config_flow import DEFAULT_PORT +from homeassistant.components.fivem.const import DOMAIN +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_FORM + +USER_INPUT = { + CONF_NAME: "Dummy Server", + CONF_HOST: "fivem.dummyserver.com", + CONF_PORT: DEFAULT_PORT, +} + + +def __mock_fivem_info_success(): + return { + "resources": [ + "fivem", + "monitor", + ], + "server": "FXServer-dummy v0.0.0.DUMMY linux", + "vars": { + "gamename": "gta5", + }, + "version": 123456789, + } + + +def __mock_fivem_info_invalid(): + return { + "plugins": [ + "sample", + ], + "data": { + "gamename": "gta5", + }, + } + + +def __mock_fivem_info_invalid_gamename(): + info = __mock_fivem_info_success() + info["vars"]["gamename"] = "redm" + + return info + + +async def test_show_config_form(hass: HomeAssistant) -> None: + """Test if initial configuration form is shown.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "user" + + +async def test_form(hass: HomeAssistant) -> None: + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] is None + + with patch( + "fivem.fivem.FiveM.get_info_raw", + return_value=__mock_fivem_info_success(), + ), patch( + "homeassistant.components.fivem.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + USER_INPUT, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == USER_INPUT[CONF_NAME] + assert result2["data"] == USER_INPUT + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_cannot_connect(hass: HomeAssistant) -> None: + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "fivem.fivem.FiveM.get_info_raw", + side_effect=FiveMServerOfflineError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + USER_INPUT, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {"base": "cannot_connect"} + + +async def test_form_invalid(hass: HomeAssistant) -> None: + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "fivem.fivem.FiveM.get_info_raw", + return_value=__mock_fivem_info_invalid(), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + USER_INPUT, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {"base": "unknown"} + + +async def test_form_invalid_gamename(hass: HomeAssistant) -> None: + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "fivem.fivem.FiveM.get_info_raw", + return_value=__mock_fivem_info_invalid_gamename(), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + USER_INPUT, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {"base": "invalid_gamename"} From d12a3927678296bd87852b1c660ca511813c2247 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 8 Feb 2022 11:13:05 +0100 Subject: [PATCH 0421/1098] Update plugwise 0.16.2 (#65933) --- .../components/plugwise/binary_sensor.py | 65 +--- homeassistant/components/plugwise/climate.py | 69 ++-- .../components/plugwise/coordinator.py | 28 +- homeassistant/components/plugwise/entity.py | 18 +- homeassistant/components/plugwise/gateway.py | 11 +- .../components/plugwise/manifest.json | 2 +- homeassistant/components/plugwise/sensor.py | 110 ++---- homeassistant/components/plugwise/switch.py | 64 ++-- requirements_all.txt | 2 +- requirements_test.txt | 1 - requirements_test_all.txt | 2 +- script/pip_check | 2 +- tests/components/plugwise/conftest.py | 55 ++- .../all_data.json | 358 ++++++++++++++++++ .../get_all_devices.json | 1 - .../02cf28bfec924855854c544690a609ef.json | 1 - .../21f2b542c49845e6bb416884c55778d6.json | 1 - .../4a810418d5394b3f82727340b91ba740.json | 1 - .../675416a629f343c495449970e2ca37b5.json | 1 - .../680423ff840043738f42cc7f1ff97a36.json | 1 - .../6a3bf693d05e48e0b460c815a4fdd09d.json | 1 - .../78d1126fc4c743db81b61c20e88342a7.json | 1 - .../90986d591dcd426cae3ec3e8111ff730.json | 1 - .../a28f588dc4a049a483fd03a30361ad3a.json | 1 - .../a2c3583e0a6349358998b760cea82d2a.json | 1 - .../b310b72a0e354bfab43089919b9a88bf.json | 1 - .../b59bcebaf94b499ea7d46e4a66fb62d8.json | 1 - .../cd0ddb54ef694e11ac18ed1cbce5dbbd.json | 1 - .../d3da73bde12a47d5a6b8f9dad971f2ec.json | 1 - .../df4a4a8169904cdb9c03d61a21f42140.json | 1 - .../e7693eb9582644e5b865dba8d4447cf1.json | 1 - .../f1fee6043d3642a9b0a65297455f008e.json | 1 - .../fe799307f1624099878210aa0b9f1475.json | 1 - .../notifications.json | 6 +- .../fixtures/anna_heatpump/all_data.json | 79 ++++ .../anna_heatpump/get_all_devices.json | 1 - .../015ae9ea3f964e668e490fa39da3870b.json | 1 - .../1cbf783bb11e4a7c8a6843dee3a86927.json | 1 - .../3cb70739631c4d17a86b8b12e8a5161b.json | 1 - .../fixtures/p1v3_full_option/all_data.json | 39 ++ .../p1v3_full_option/get_all_devices.json | 1 - .../e950c7d5e1ee407a858e2a8b5016c8b3.json | 1 - .../fixtures/stretch_v31/all_data.json | 178 +++++++++ .../fixtures/stretch_v31/get_all_devices.json | 1 - .../059e4d03c7a34d278add5c7a4a781d19.json | 1 - .../5871317346d045bc9f6b987ef25ee638.json | 1 - .../5ca521ac179d468e91d772eeeb8a2117.json | 1 - .../71e1944f2a944b26ad73323e399efef0.json | 1 - .../99f89d097be34fca88d8598c6dbc18ea.json | 1 - .../aac7b735042c4832ac9ff33aae4f453b.json | 1 - .../cfe95cf3de1948c0b8955125bf754614.json | 1 - .../d03738edfcc947f7b8f4573571d90d2d.json | 1 - .../d950b314e9d8499f968e6db8d82ef78c.json | 1 - .../e1c884e7dede431dadee09506ec4f859.json | 1 - .../e309b52ea5684cf1a22f30cf0cd15051.json | 1 - tests/components/plugwise/test_climate.py | 18 +- tests/components/plugwise/test_init.py | 2 +- tests/components/plugwise/test_sensor.py | 8 +- tests/components/plugwise/test_switch.py | 2 +- 59 files changed, 838 insertions(+), 318 deletions(-) create mode 100644 tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json delete mode 100644 tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_all_devices.json delete mode 100644 tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/02cf28bfec924855854c544690a609ef.json delete mode 100644 tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/21f2b542c49845e6bb416884c55778d6.json delete mode 100644 tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/4a810418d5394b3f82727340b91ba740.json delete mode 100644 tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/675416a629f343c495449970e2ca37b5.json delete mode 100644 tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/680423ff840043738f42cc7f1ff97a36.json delete mode 100644 tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/6a3bf693d05e48e0b460c815a4fdd09d.json delete mode 100644 tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/78d1126fc4c743db81b61c20e88342a7.json delete mode 100644 tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/90986d591dcd426cae3ec3e8111ff730.json delete mode 100644 tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/a28f588dc4a049a483fd03a30361ad3a.json delete mode 100644 tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/a2c3583e0a6349358998b760cea82d2a.json delete mode 100644 tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/b310b72a0e354bfab43089919b9a88bf.json delete mode 100644 tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/b59bcebaf94b499ea7d46e4a66fb62d8.json delete mode 100644 tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/cd0ddb54ef694e11ac18ed1cbce5dbbd.json delete mode 100644 tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/d3da73bde12a47d5a6b8f9dad971f2ec.json delete mode 100644 tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/df4a4a8169904cdb9c03d61a21f42140.json delete mode 100644 tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/e7693eb9582644e5b865dba8d4447cf1.json delete mode 100644 tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/f1fee6043d3642a9b0a65297455f008e.json delete mode 100644 tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/fe799307f1624099878210aa0b9f1475.json create mode 100644 tests/components/plugwise/fixtures/anna_heatpump/all_data.json delete mode 100644 tests/components/plugwise/fixtures/anna_heatpump/get_all_devices.json delete mode 100644 tests/components/plugwise/fixtures/anna_heatpump/get_device_data/015ae9ea3f964e668e490fa39da3870b.json delete mode 100644 tests/components/plugwise/fixtures/anna_heatpump/get_device_data/1cbf783bb11e4a7c8a6843dee3a86927.json delete mode 100644 tests/components/plugwise/fixtures/anna_heatpump/get_device_data/3cb70739631c4d17a86b8b12e8a5161b.json create mode 100644 tests/components/plugwise/fixtures/p1v3_full_option/all_data.json delete mode 100644 tests/components/plugwise/fixtures/p1v3_full_option/get_all_devices.json delete mode 100644 tests/components/plugwise/fixtures/p1v3_full_option/get_device_data/e950c7d5e1ee407a858e2a8b5016c8b3.json create mode 100644 tests/components/plugwise/fixtures/stretch_v31/all_data.json delete mode 100644 tests/components/plugwise/fixtures/stretch_v31/get_all_devices.json delete mode 100644 tests/components/plugwise/fixtures/stretch_v31/get_device_data/059e4d03c7a34d278add5c7a4a781d19.json delete mode 100644 tests/components/plugwise/fixtures/stretch_v31/get_device_data/5871317346d045bc9f6b987ef25ee638.json delete mode 100644 tests/components/plugwise/fixtures/stretch_v31/get_device_data/5ca521ac179d468e91d772eeeb8a2117.json delete mode 100644 tests/components/plugwise/fixtures/stretch_v31/get_device_data/71e1944f2a944b26ad73323e399efef0.json delete mode 100644 tests/components/plugwise/fixtures/stretch_v31/get_device_data/99f89d097be34fca88d8598c6dbc18ea.json delete mode 100644 tests/components/plugwise/fixtures/stretch_v31/get_device_data/aac7b735042c4832ac9ff33aae4f453b.json delete mode 100644 tests/components/plugwise/fixtures/stretch_v31/get_device_data/cfe95cf3de1948c0b8955125bf754614.json delete mode 100644 tests/components/plugwise/fixtures/stretch_v31/get_device_data/d03738edfcc947f7b8f4573571d90d2d.json delete mode 100644 tests/components/plugwise/fixtures/stretch_v31/get_device_data/d950b314e9d8499f968e6db8d82ef78c.json delete mode 100644 tests/components/plugwise/fixtures/stretch_v31/get_device_data/e1c884e7dede431dadee09506ec4f859.json delete mode 100644 tests/components/plugwise/fixtures/stretch_v31/get_device_data/e309b52ea5684cf1a22f30cf0cd15051.json diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index e83904ab25d2ea..44d5e79ce166ad 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -50,35 +50,33 @@ async def async_setup_entry( config_entry.entry_id ][COORDINATOR] - entities: list[BinarySensorEntity] = [] - is_thermostat = api.single_master_thermostat() - - all_devices = api.get_all_devices() - for dev_id, device_properties in all_devices.items(): - + entities: list[PlugwiseBinarySensorEntity] = [] + for device_id, device_properties in coordinator.data.devices.items(): if device_properties["class"] == "heater_central": - data = api.get_device_data(dev_id) for description in BINARY_SENSORS: - if description.key not in data: + if ( + "binary_sensors" not in device_properties + or description.key not in device_properties["binary_sensors"] + ): continue entities.append( - PwBinarySensor( + PlugwiseBinarySensorEntity( api, coordinator, device_properties["name"], - dev_id, + device_id, description, ) ) - if device_properties["class"] == "gateway" and is_thermostat is not None: + if device_properties["class"] == "gateway": entities.append( - PwNotifySensor( + PlugwiseNotifyBinarySensorEntity( api, coordinator, device_properties["name"], - dev_id, + device_id, BinarySensorEntityDescription( key="plugwise_notification", name="Plugwise Notification", @@ -89,7 +87,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class SmileBinarySensor(PlugwiseEntity, BinarySensorEntity): +class PlugwiseBinarySensorEntity(PlugwiseEntity, BinarySensorEntity): """Represent Smile Binary Sensors.""" def __init__( @@ -106,36 +104,23 @@ def __init__( self._attr_is_on = False self._attr_unique_id = f"{dev_id}-{description.key}" - if dev_id == self._api.heater_id: + if dev_id == coordinator.data.gateway["heater_id"]: self._entity_name = "Auxiliary" self._name = f"{self._entity_name} {description.name}" - if dev_id == self._api.gateway_id: + if dev_id == coordinator.data.gateway["gateway_id"]: self._entity_name = f"Smile {self._entity_name}" @callback def _async_process_data(self) -> None: """Update the entity.""" - raise NotImplementedError - - -class PwBinarySensor(SmileBinarySensor): - """Representation of a Plugwise binary_sensor.""" - - @callback - def _async_process_data(self) -> None: - """Update the entity.""" - if not (data := self._api.get_device_data(self._dev_id)): + if not (data := self.coordinator.data.devices.get(self._dev_id)): LOGGER.error("Received no data for device %s", self._dev_id) self.async_write_ha_state() return - if self.entity_description.key not in data: - self.async_write_ha_state() - return - - self._attr_is_on = data[self.entity_description.key] + self._attr_is_on = data["binary_sensors"].get(self.entity_description.key) if self.entity_description.key == "dhw_state": self._attr_icon = FLOW_ON_ICON if self._attr_is_on else FLOW_OFF_ICON @@ -145,27 +130,15 @@ def _async_process_data(self) -> None: self.async_write_ha_state() -class PwNotifySensor(SmileBinarySensor): +class PlugwiseNotifyBinarySensorEntity(PlugwiseBinarySensorEntity): """Representation of a Plugwise Notification binary_sensor.""" - def __init__( - self, - api: Smile, - coordinator: PlugwiseDataUpdateCoordinator, - name: str, - dev_id: str, - description: BinarySensorEntityDescription, - ) -> None: - """Set up the Plugwise API.""" - super().__init__(api, coordinator, name, dev_id, description) - - self._attr_extra_state_attributes = {} - @callback def _async_process_data(self) -> None: """Update the entity.""" - notify = self._api.notifications + notify = self.coordinator.data.gateway["notifications"] + self._attr_extra_state_attributes = {} for severity in SEVERITIES: self._attr_extra_state_attributes[f"{severity}_msg"] = [] diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index ff1325ffbf93d9..49c73049a9abd7 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -10,8 +10,9 @@ CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, HVAC_MODE_AUTO, + HVAC_MODE_COOL, HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, + HVAC_MODE_OFF, SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, ) @@ -32,8 +33,8 @@ from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity -HVAC_MODES_HEAT_ONLY = [HVAC_MODE_HEAT, HVAC_MODE_AUTO] -HVAC_MODES_HEAT_COOL = [HVAC_MODE_HEAT_COOL, HVAC_MODE_AUTO] +HVAC_MODES_HEAT_ONLY = [HVAC_MODE_HEAT, HVAC_MODE_AUTO, HVAC_MODE_OFF] +HVAC_MODES_HEAT_COOL = [HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_AUTO, HVAC_MODE_OFF] async def async_setup_entry( @@ -45,24 +46,21 @@ async def async_setup_entry( api = hass.data[DOMAIN][config_entry.entry_id]["api"] coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] - entities = [] + entities: list[PlugwiseClimateEntity] = [] thermostat_classes = [ "thermostat", "zone_thermostat", "thermostatic_radiator_valve", ] - all_devices = api.get_all_devices() - - for dev_id, device_properties in all_devices.items(): - + for device_id, device_properties in coordinator.data.devices.items(): if device_properties["class"] not in thermostat_classes: continue - thermostat = PwThermostat( + thermostat = PlugwiseClimateEntity( api, coordinator, device_properties["name"], - dev_id, + device_id, device_properties["location"], device_properties["class"], ) @@ -72,7 +70,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class PwThermostat(PlugwiseEntity, ClimateEntity): +class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): """Representation of an Plugwise thermostat.""" _attr_hvac_mode = HVAC_MODE_HEAT @@ -90,21 +88,19 @@ def __init__( api: Smile, coordinator: PlugwiseDataUpdateCoordinator, name: str, - dev_id: str, + device_id: str, loc_id: str, model: str, ) -> None: """Set up the Plugwise API.""" - super().__init__(api, coordinator, name, dev_id) + super().__init__(api, coordinator, name, device_id) self._attr_extra_state_attributes = {} - self._attr_unique_id = f"{dev_id}-climate" + self._attr_unique_id = f"{device_id}-climate" self._api = api self._loc_id = loc_id - self._model = model self._presets = None - self._single_thermostat = self._api.single_master_thermostat() async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" @@ -124,7 +120,7 @@ async def async_set_temperature(self, **kwargs: Any) -> None: async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set the hvac mode.""" state = SCHEDULE_OFF - climate_data = self._api.get_device_data(self._dev_id) + climate_data = self.coordinator.data.devices[self._dev_id] if hvac_mode == HVAC_MODE_AUTO: state = SCHEDULE_ON @@ -161,18 +157,20 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: @callback def _async_process_data(self) -> None: """Update the data for this climate device.""" - climate_data = self._api.get_device_data(self._dev_id) - heater_central_data = self._api.get_device_data(self._api.heater_id) + data = self.coordinator.data.devices[self._dev_id] + heater_central_data = self.coordinator.data.devices[ + self.coordinator.data.gateway["heater_id"] + ] # Current & set temperatures - if setpoint := climate_data.get("setpoint"): + if setpoint := data["sensors"].get("setpoint"): self._attr_target_temperature = setpoint - if temperature := climate_data.get("temperature"): + if temperature := data["sensors"].get("temperature"): self._attr_current_temperature = temperature # Presets handling - self._attr_preset_mode = climate_data.get("active_preset") - if presets := climate_data.get("presets"): + self._attr_preset_mode = data.get("active_preset") + if presets := data.get("presets"): self._presets = presets self._attr_preset_modes = list(presets) else: @@ -181,31 +179,22 @@ def _async_process_data(self) -> None: # Determine current hvac action self._attr_hvac_action = CURRENT_HVAC_IDLE - if self._single_thermostat: - if heater_central_data.get("heating_state"): - self._attr_hvac_action = CURRENT_HVAC_HEAT - elif heater_central_data.get("cooling_state"): - self._attr_hvac_action = CURRENT_HVAC_COOL - elif ( - self.target_temperature is not None - and self.current_temperature is not None - and self.target_temperature > self.current_temperature - ): + if heater_central_data.get("heating_state"): self._attr_hvac_action = CURRENT_HVAC_HEAT + elif heater_central_data.get("cooling_state"): + self._attr_hvac_action = CURRENT_HVAC_COOL # Determine hvac modes and current hvac mode - self._attr_hvac_mode = HVAC_MODE_HEAT self._attr_hvac_modes = HVAC_MODES_HEAT_ONLY - if heater_central_data.get("compressor_state") is not None: - self._attr_hvac_mode = HVAC_MODE_HEAT_COOL + if self.coordinator.data.gateway.get("cooling_present"): self._attr_hvac_modes = HVAC_MODES_HEAT_COOL - if climate_data.get("selected_schedule") is not None: - self._attr_hvac_mode = HVAC_MODE_AUTO + if data.get("mode") in self._attr_hvac_modes: + self._attr_hvac_mode = data["mode"] # Extra attributes self._attr_extra_state_attributes = { - "available_schemas": climate_data.get("available_schedules"), - "selected_schema": climate_data.get("selected_schedule"), + "available_schemas": data.get("available_schedules"), + "selected_schema": data.get("selected_schedule"), } self.async_write_ha_state() diff --git a/homeassistant/components/plugwise/coordinator.py b/homeassistant/components/plugwise/coordinator.py index ed6dad01f85ceb..b0b2c4b12941e1 100644 --- a/homeassistant/components/plugwise/coordinator.py +++ b/homeassistant/components/plugwise/coordinator.py @@ -1,17 +1,24 @@ """DataUpdateCoordinator for Plugwise.""" from datetime import timedelta +from typing import Any, NamedTuple -import async_timeout from plugwise import Smile -from plugwise.exceptions import XMLDataMissingError +from plugwise.exceptions import PlugwiseException, XMLDataMissingError from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import DEFAULT_SCAN_INTERVAL, DEFAULT_TIMEOUT, DOMAIN, LOGGER +from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER -class PlugwiseDataUpdateCoordinator(DataUpdateCoordinator[bool]): +class PlugwiseData(NamedTuple): + """Plugwise data stored in the DataUpdateCoordinator.""" + + gateway: dict[str, Any] + devices: dict[str, dict[str, Any]] + + +class PlugwiseDataUpdateCoordinator(DataUpdateCoordinator[PlugwiseData]): """Class to manage fetching Plugwise data from single endpoint.""" def __init__(self, hass: HomeAssistant, api: Smile) -> None: @@ -26,11 +33,14 @@ def __init__(self, hass: HomeAssistant, api: Smile) -> None: ) self.api = api - async def _async_update_data(self) -> bool: + async def _async_update_data(self) -> PlugwiseData: """Fetch data from Plugwise.""" try: - async with async_timeout.timeout(DEFAULT_TIMEOUT): - await self.api.full_update_device() + data = await self.api.async_update() except XMLDataMissingError as err: - raise UpdateFailed("Smile update failed") from err - return True + raise UpdateFailed( + f"No XML data received for: {self.api.smile_name}" + ) from err + except PlugwiseException as err: + raise UpdateFailed(f"Updated failed for: {self.api.smile_name}") from err + return PlugwiseData(*data) diff --git a/homeassistant/components/plugwise/entity.py b/homeassistant/components/plugwise/entity.py index f33ba98235bb8f..c57f9c9281fb01 100644 --- a/homeassistant/components/plugwise/entity.py +++ b/homeassistant/components/plugwise/entity.py @@ -14,14 +14,12 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN -from .coordinator import PlugwiseDataUpdateCoordinator +from .coordinator import PlugwiseData, PlugwiseDataUpdateCoordinator -class PlugwiseEntity(CoordinatorEntity[bool]): +class PlugwiseEntity(CoordinatorEntity[PlugwiseData]): """Represent a PlugWise Entity.""" - _model: str | None = None - def __init__( self, api: Smile, @@ -45,6 +43,7 @@ def name(self) -> str | None: @property def device_info(self) -> DeviceInfo: """Return the device information.""" + data = self.coordinator.data.devices[self._dev_id] device_information = DeviceInfo( identifiers={(DOMAIN, self._dev_id)}, name=self._entity_name, @@ -56,11 +55,14 @@ def device_info(self) -> DeviceInfo: ATTR_CONFIGURATION_URL ] = f"http://{entry.data[CONF_HOST]}" - if self._model is not None: - device_information[ATTR_MODEL] = self._model.replace("_", " ").title() + if model := data.get("model"): + device_information[ATTR_MODEL] = model - if self._dev_id != self._api.gateway_id: - device_information[ATTR_VIA_DEVICE] = (DOMAIN, str(self._api.gateway_id)) + if self._dev_id != self.coordinator.data.gateway["gateway_id"]: + device_information[ATTR_VIA_DEVICE] = ( + DOMAIN, + str(self.coordinator.data.gateway["gateway_id"]), + ) return device_information diff --git a/homeassistant/components/plugwise/gateway.py b/homeassistant/components/plugwise/gateway.py index d1d35e3134bd56..eaccab58454461 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -55,15 +55,14 @@ async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: if not connected: raise ConfigEntryNotReady("Unable to connect to Smile") - - coordinator = PlugwiseDataUpdateCoordinator(hass, api) - await coordinator.async_config_entry_first_refresh() - api.get_all_devices() if entry.unique_id is None and api.smile_version[0] != "1.8.0": hass.config_entries.async_update_entry(entry, unique_id=api.smile_hostname) + coordinator = PlugwiseDataUpdateCoordinator(hass, api) + await coordinator.async_config_entry_first_refresh() + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { "api": api, COORDINATOR: coordinator, @@ -80,10 +79,8 @@ async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: sw_version=api.smile_version[0], ) - single_master_thermostat = api.single_master_thermostat() - platforms = PLATFORMS_GATEWAY - if single_master_thermostat is None: + if coordinator.data.gateway["single_master_thermostat"] is None: platforms = SENSOR_PLATFORMS hass.config_entries.async_setup_platforms(entry, platforms) diff --git a/homeassistant/components/plugwise/manifest.json b/homeassistant/components/plugwise/manifest.json index 5006b1e659d135..7dc3bdd9b1ddb8 100644 --- a/homeassistant/components/plugwise/manifest.json +++ b/homeassistant/components/plugwise/manifest.json @@ -2,7 +2,7 @@ "domain": "plugwise", "name": "Plugwise", "documentation": "https://www.home-assistant.io/integrations/plugwise", - "requirements": ["plugwise==0.8.5"], + "requirements": ["plugwise==0.16.2"], "codeowners": ["@CoMPaTech", "@bouwew", "@brefra"], "zeroconf": ["_plugwise._tcp.local."], "config_flow": true, diff --git a/homeassistant/components/plugwise/sensor.py b/homeassistant/components/plugwise/sensor.py index d55c2df4df9386..d2fd47d4b7d0fd 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -289,53 +289,37 @@ async def async_setup_entry( api = hass.data[DOMAIN][config_entry.entry_id]["api"] coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] - entities: list[SmileSensor] = [] - all_devices = api.get_all_devices() - single_thermostat = api.single_master_thermostat() - for dev_id, device_properties in all_devices.items(): - data = api.get_device_data(dev_id) + entities: list[PlugwiseSensorEnity] = [] + for device_id, device_properties in coordinator.data.devices.items(): for description in SENSORS: - if data.get(description.key) is None: + if ( + "sensors" not in device_properties + or device_properties["sensors"].get(description.key) is None + ): continue - if "power" in device_properties["types"]: - model = None - - if "plug" in device_properties["types"]: - model = "Metered Switch" - - entities.append( - PwPowerSensor( - api, - coordinator, - device_properties["name"], - dev_id, - model, - description, - ) - ) - else: - entities.append( - PwThermostatSensor( - api, - coordinator, - device_properties["name"], - dev_id, - description, - ) + entities.append( + PlugwiseSensorEnity( + api, + coordinator, + device_properties["name"], + device_id, + description, ) + ) - if single_thermostat is False: + if coordinator.data.gateway["single_master_thermostat"] is False: + # These sensors should actually be binary sensors. for description in INDICATE_ACTIVE_LOCAL_DEVICE_SENSORS: - if description.key not in data: + if description.key not in device_properties: continue entities.append( - PwAuxDeviceSensor( + PlugwiseAuxSensorEntity( api, coordinator, device_properties["name"], - dev_id, + device_id, description, ) ) @@ -344,47 +328,43 @@ async def async_setup_entry( async_add_entities(entities, True) -class SmileSensor(PlugwiseEntity, SensorEntity): - """Represent Smile Sensors.""" +class PlugwiseSensorEnity(PlugwiseEntity, SensorEntity): + """Represent Plugwise Sensors.""" def __init__( self, api: Smile, coordinator: PlugwiseDataUpdateCoordinator, name: str, - dev_id: str, + device_id: str, description: SensorEntityDescription, ) -> None: """Initialise the sensor.""" - super().__init__(api, coordinator, name, dev_id) + super().__init__(api, coordinator, name, device_id) self.entity_description = description - self._attr_unique_id = f"{dev_id}-{description.key}" + self._attr_unique_id = f"{device_id}-{description.key}" - if dev_id == self._api.heater_id: + if device_id == coordinator.data.gateway["heater_id"]: self._entity_name = "Auxiliary" self._name = f"{self._entity_name} {description.name}" - if dev_id == self._api.gateway_id: + if device_id == coordinator.data.gateway["gateway_id"]: self._entity_name = f"Smile {self._entity_name}" - -class PwThermostatSensor(SmileSensor): - """Thermostat (or generic) sensor devices.""" - @callback def _async_process_data(self) -> None: """Update the entity.""" - if not (data := self._api.get_device_data(self._dev_id)): + if not (data := self.coordinator.data.devices.get(self._dev_id)): LOGGER.error("Received no data for device %s", self._entity_name) self.async_write_ha_state() return - self._attr_native_value = data.get(self.entity_description.key) + self._attr_native_value = data["sensors"].get(self.entity_description.key) self.async_write_ha_state() -class PwAuxDeviceSensor(SmileSensor): +class PlugwiseAuxSensorEntity(PlugwiseSensorEnity): """Auxiliary Device Sensors.""" _cooling_state = False @@ -393,7 +373,7 @@ class PwAuxDeviceSensor(SmileSensor): @callback def _async_process_data(self) -> None: """Update the entity.""" - if not (data := self._api.get_device_data(self._dev_id)): + if not (data := self.coordinator.data.devices.get(self._dev_id)): LOGGER.error("Received no data for device %s", self._entity_name) self.async_write_ha_state() return @@ -413,33 +393,3 @@ def _async_process_data(self) -> None: self._attr_icon = COOL_ICON self.async_write_ha_state() - - -class PwPowerSensor(SmileSensor): - """Power sensor entities.""" - - def __init__( - self, - api: Smile, - coordinator: PlugwiseDataUpdateCoordinator, - name: str, - dev_id: str, - model: str | None, - description: SensorEntityDescription, - ) -> None: - """Set up the Plugwise API.""" - super().__init__(api, coordinator, name, dev_id, description) - self._model = model - if dev_id == self._api.gateway_id: - self._model = "P1 DSMR" - - @callback - def _async_process_data(self) -> None: - """Update the entity.""" - if not (data := self._api.get_device_data(self._dev_id)): - LOGGER.error("Received no data for device %s", self._entity_name) - self.async_write_ha_state() - return - - self._attr_native_value = data.get(self.entity_description.key) - self.async_write_ha_state() diff --git a/homeassistant/components/plugwise/switch.py b/homeassistant/components/plugwise/switch.py index e5436389fca675..4e5a00e3e6ada5 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -25,34 +25,27 @@ async def async_setup_entry( api = hass.data[DOMAIN][config_entry.entry_id]["api"] coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] - entities = [] - switch_classes = ["plug", "switch_group"] - - all_devices = api.get_all_devices() - for dev_id, device_properties in all_devices.items(): - members = None - model = None - - if any( - switch_class in device_properties["types"] - for switch_class in switch_classes + entities: list[PlugwiseSwitchEntity] = [] + for device_id, device_properties in coordinator.data.devices.items(): + if ( + "switches" not in device_properties + or "relay" not in device_properties["switches"] ): - if "plug" in device_properties["types"]: - model = "Metered Switch" - if "switch_group" in device_properties["types"]: - members = device_properties["members"] - model = "Switch Group" - - entities.append( - GwSwitch( - api, coordinator, device_properties["name"], dev_id, members, model - ) + continue + + entities.append( + PlugwiseSwitchEntity( + api, + coordinator, + device_properties["name"], + device_id, ) + ) async_add_entities(entities, True) -class GwSwitch(PlugwiseEntity, SwitchEntity): +class PlugwiseSwitchEntity(PlugwiseEntity, SwitchEntity): """Representation of a Plugwise plug.""" _attr_icon = SWITCH_ICON @@ -62,24 +55,19 @@ def __init__( api: Smile, coordinator: PlugwiseDataUpdateCoordinator, name: str, - dev_id: str, - members: list[str] | None, - model: str | None, + device_id: str, ) -> None: """Set up the Plugwise API.""" - super().__init__(api, coordinator, name, dev_id) - self._attr_unique_id = f"{dev_id}-plug" - - self._members = members - self._model = model - + super().__init__(api, coordinator, name, device_id) + self._attr_unique_id = f"{device_id}-plug" + self._members = coordinator.data.devices[device_id].get("members") self._attr_is_on = False async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" try: - state_on = await self._api.set_relay_state( - self._dev_id, self._members, "on" + state_on = await self._api.set_switch_state( + self._dev_id, self._members, "relay", "on" ) except PlugwiseException: LOGGER.error("Error while communicating to device") @@ -91,8 +79,8 @@ async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" try: - state_off = await self._api.set_relay_state( - self._dev_id, self._members, "off" + state_off = await self._api.set_switch_state( + self._dev_id, self._members, "relay", "off" ) except PlugwiseException: LOGGER.error("Error while communicating to device") @@ -104,12 +92,10 @@ async def async_turn_off(self, **kwargs: Any) -> None: @callback def _async_process_data(self) -> None: """Update the data from the Plugs.""" - if not (data := self._api.get_device_data(self._dev_id)): + if not (data := self.coordinator.data.devices.get(self._dev_id)): LOGGER.error("Received no data for device %s", self._name) self.async_write_ha_state() return - if "relay" in data: - self._attr_is_on = data["relay"] - + self._attr_is_on = data["switches"].get("relay") self.async_write_ha_state() diff --git a/requirements_all.txt b/requirements_all.txt index 31994aa6489176..a1e36db3cc50d1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1264,7 +1264,7 @@ plexauth==0.0.6 plexwebsocket==0.0.13 # homeassistant.components.plugwise -plugwise==0.8.5 +plugwise==0.16.2 # homeassistant.components.plum_lightpad plumlightpad==0.0.11 diff --git a/requirements_test.txt b/requirements_test.txt index f09102270cf9c1..bd83e1505a2843 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -10,7 +10,6 @@ codecov==2.1.12 coverage==6.3.1 freezegun==1.1.0 -jsonpickle==1.4.1 mock-open==1.4.0 mypy==0.931 pre-commit==2.17.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index df3f191abdaaa4..ef1086118bf765 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -786,7 +786,7 @@ plexauth==0.0.6 plexwebsocket==0.0.13 # homeassistant.components.plugwise -plugwise==0.8.5 +plugwise==0.16.2 # homeassistant.components.plum_lightpad plumlightpad==0.0.11 diff --git a/script/pip_check b/script/pip_check index bd988b50e3a0ec..c30a7382f27335 100755 --- a/script/pip_check +++ b/script/pip_check @@ -3,7 +3,7 @@ PIP_CACHE=$1 # Number of existing dependency conflicts # Update if a PR resolve one! -DEPENDENCY_CONFLICTS=11 +DEPENDENCY_CONFLICTS=10 PIP_CHECK=$(pip check --cache-dir=$PIP_CACHE) LINE_COUNT=$(echo "$PIP_CHECK" | wc -l) diff --git a/tests/components/plugwise/conftest.py b/tests/components/plugwise/conftest.py index 65415285de2a17..fa49967437d30f 100644 --- a/tests/components/plugwise/conftest.py +++ b/tests/components/plugwise/conftest.py @@ -4,10 +4,10 @@ from collections.abc import Generator from functools import partial from http import HTTPStatus +import json import re from unittest.mock import AsyncMock, MagicMock, Mock, patch -import jsonpickle from plugwise.exceptions import ( ConnectionFailedError, InvalidAuthentication, @@ -23,7 +23,7 @@ def _read_json(environment, call): """Undecode the json data.""" fixture = load_fixture(f"plugwise/{environment}/{call}.json") - return jsonpickle.decode(fixture) + return json.loads(fixture) @pytest.fixture @@ -93,13 +93,11 @@ def mock_smile_adam(): smile_mock.return_value.smile_version = "3.0.15" smile_mock.return_value.smile_type = "thermostat" smile_mock.return_value.smile_hostname = "smile98765" + smile_mock.return_value.smile_name = "Adam" smile_mock.return_value.notifications = _read_json(chosen_env, "notifications") smile_mock.return_value.connect.side_effect = AsyncMock(return_value=True) - smile_mock.return_value.full_update_device.side_effect = AsyncMock( - return_value=True - ) smile_mock.return_value.single_master_thermostat.side_effect = Mock( return_value=False ) @@ -110,15 +108,11 @@ def mock_smile_adam(): smile_mock.return_value.set_temperature.side_effect = AsyncMock( return_value=True ) - smile_mock.return_value.set_relay_state.side_effect = AsyncMock( - return_value=True - ) - - smile_mock.return_value.get_all_devices.return_value = _read_json( - chosen_env, "get_all_devices" + smile_mock.return_value.async_update.side_effect = AsyncMock( + return_value=_read_json(chosen_env, "all_data") ) - smile_mock.return_value.get_device_data.side_effect = partial( - _get_device_data, chosen_env + smile_mock.return_value.set_switch_state.side_effect = AsyncMock( + return_value=True ) yield smile_mock.return_value @@ -138,13 +132,11 @@ def mock_smile_anna(): smile_mock.return_value.smile_version = "4.0.15" smile_mock.return_value.smile_type = "thermostat" smile_mock.return_value.smile_hostname = "smile98765" + smile_mock.return_value.smile_name = "Anna" smile_mock.return_value.notifications = _read_json(chosen_env, "notifications") smile_mock.return_value.connect.side_effect = AsyncMock(return_value=True) - smile_mock.return_value.full_update_device.side_effect = AsyncMock( - return_value=True - ) smile_mock.return_value.single_master_thermostat.side_effect = Mock( return_value=True ) @@ -155,12 +147,12 @@ def mock_smile_anna(): smile_mock.return_value.set_temperature.side_effect = AsyncMock( return_value=True ) - smile_mock.return_value.set_relay_state.side_effect = AsyncMock( + smile_mock.return_value.set_switch_state.side_effect = AsyncMock( return_value=True ) - smile_mock.return_value.get_all_devices.return_value = _read_json( - chosen_env, "get_all_devices" + smile_mock.return_value.async_update.side_effect = AsyncMock( + return_value=_read_json(chosen_env, "all_data") ) smile_mock.return_value.get_device_data.side_effect = partial( _get_device_data, chosen_env @@ -183,25 +175,24 @@ def mock_smile_p1(): smile_mock.return_value.smile_version = "3.3.9" smile_mock.return_value.smile_type = "power" smile_mock.return_value.smile_hostname = "smile98765" + smile_mock.return_value.smile_name = "Smile P1" smile_mock.return_value.notifications = _read_json(chosen_env, "notifications") smile_mock.return_value.connect.side_effect = AsyncMock(return_value=True) - smile_mock.return_value.full_update_device.side_effect = AsyncMock( - return_value=True - ) smile_mock.return_value.single_master_thermostat.side_effect = Mock( return_value=None ) - smile_mock.return_value.get_all_devices.return_value = _read_json( - chosen_env, "get_all_devices" - ) smile_mock.return_value.get_device_data.side_effect = partial( _get_device_data, chosen_env ) + smile_mock.return_value.async_update.side_effect = AsyncMock( + return_value=_read_json(chosen_env, "all_data") + ) + yield smile_mock.return_value @@ -219,20 +210,18 @@ def mock_stretch(): smile_mock.return_value.smile_version = "3.1.11" smile_mock.return_value.smile_type = "stretch" smile_mock.return_value.smile_hostname = "stretch98765" + smile_mock.return_value.smile_name = "Stretch" smile_mock.return_value.connect.side_effect = AsyncMock(return_value=True) - smile_mock.return_value.full_update_device.side_effect = AsyncMock( - return_value=True - ) - smile_mock.return_value.set_relay_state.side_effect = AsyncMock( + smile_mock.return_value.set_switch_state.side_effect = AsyncMock( return_value=True ) - - smile_mock.return_value.get_all_devices.return_value = _read_json( - chosen_env, "get_all_devices" - ) smile_mock.return_value.get_device_data.side_effect = partial( _get_device_data, chosen_env ) + smile_mock.return_value.async_update.side_effect = AsyncMock( + return_value=_read_json(chosen_env, "all_data") + ) + yield smile_mock.return_value diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json new file mode 100644 index 00000000000000..7fc843e4aaedb0 --- /dev/null +++ b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json @@ -0,0 +1,358 @@ +[ + { + "active_device": true, + "cooling_present": null, + "gateway_id": "fe799307f1624099878210aa0b9f1475", + "heater_id": "90986d591dcd426cae3ec3e8111ff730", + "single_master_thermostat": false, + "smile_name": "Adam", + "notifications": { + "af82e4ccf9c548528166d38e560662a4": { + "warning": "Node Plug (with MAC address 000D6F000D13CB01, in room 'n.a.') has been unreachable since 23:03 2020-01-18. Please check the connection and restart the device." + } + } + }, + { + "df4a4a8169904cdb9c03d61a21f42140": { + "class": "zone_thermostat", + "fw": "2016-10-27T02:00:00+02:00", + "location": "12493538af164a409c6a1c79e38afe1c", + "model": "Lisa", + "name": "Zone Lisa Bios", + "vendor": "Plugwise", + "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "active_preset": "away", + "presets": { + "home": [20.0, 22.0], + "asleep": [17.0, 24.0], + "away": [15.0, 25.0], + "vacation": [15.0, 28.0], + "no_frost": [10.0, 30.0] + }, + "available_schedules": [ + "CV Roan", + "Bios Schema met Film Avond", + "GF7 Woonkamer", + "Badkamer Schema", + "CV Jessie" + ], + "selected_schedule": "None", + "schedule_temperature": 15.0, + "last_used": "Badkamer Schema", + "mode": "off", + "sensors": { "temperature": 16.5, "setpoint": 13.0, "battery": 67 } + }, + "b310b72a0e354bfab43089919b9a88bf": { + "class": "thermo_sensor", + "fw": "2019-03-27T01:00:00+01:00", + "location": "c50f167537524366a5af7aa3942feb1e", + "model": "Tom/Floor", + "name": "Floor kraan", + "vendor": "Plugwise", + "sensors": { + "temperature": 26.0, + "setpoint": 21.5, + "temperature_difference": 3.5, + "valve_position": 100 + } + }, + "a2c3583e0a6349358998b760cea82d2a": { + "class": "thermo_sensor", + "fw": "2019-03-27T01:00:00+01:00", + "location": "12493538af164a409c6a1c79e38afe1c", + "model": "Tom/Floor", + "name": "Bios Cv Thermostatic Radiator ", + "vendor": "Plugwise", + "sensors": { + "temperature": 17.2, + "setpoint": 13.0, + "battery": 62, + "temperature_difference": -0.2, + "valve_position": 0.0 + } + }, + "b59bcebaf94b499ea7d46e4a66fb62d8": { + "class": "zone_thermostat", + "fw": "2016-08-02T02:00:00+02:00", + "location": "c50f167537524366a5af7aa3942feb1e", + "model": "Lisa", + "name": "Zone Lisa WK", + "vendor": "Plugwise", + "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "active_preset": "home", + "presets": { + "home": [20.0, 22.0], + "asleep": [17.0, 24.0], + "away": [15.0, 25.0], + "vacation": [15.0, 28.0], + "no_frost": [10.0, 30.0] + }, + "available_schedules": [ + "CV Roan", + "Bios Schema met Film Avond", + "GF7 Woonkamer", + "Badkamer Schema", + "CV Jessie" + ], + "selected_schedule": "GF7 Woonkamer", + "schedule_temperature": 20.0, + "last_used": "GF7 Woonkamer", + "mode": "auto", + "sensors": { "temperature": 20.9, "setpoint": 21.5, "battery": 34 } + }, + "fe799307f1624099878210aa0b9f1475": { + "class": "gateway", + "fw": "3.0.15", + "location": "1f9dcf83fd4e4b66b72ff787957bfe5d", + "model": "Adam", + "name": "Adam", + "vendor": "Plugwise B.V.", + "binary_sensors": { "plugwise_notification": true }, + "sensors": { "outdoor_temperature": 7.81 } + }, + "d3da73bde12a47d5a6b8f9dad971f2ec": { + "class": "thermo_sensor", + "fw": "2019-03-27T01:00:00+01:00", + "location": "82fa13f017d240daa0d0ea1775420f24", + "model": "Tom/Floor", + "name": "Thermostatic Radiator Jessie", + "vendor": "Plugwise", + "sensors": { + "temperature": 17.1, + "setpoint": 15.0, + "battery": 62, + "temperature_difference": 0.1, + "valve_position": 0.0 + } + }, + "21f2b542c49845e6bb416884c55778d6": { + "class": "game_console", + "fw": "2019-06-21T02:00:00+02:00", + "location": "cd143c07248f491493cea0533bc3d669", + "model": "Plug", + "name": "Playstation Smart Plug", + "vendor": "Plugwise", + "sensors": { + "electricity_consumed": 82.6, + "electricity_consumed_interval": 8.6, + "electricity_produced": 0.0, + "electricity_produced_interval": 0.0 + }, + "switches": { "relay": true, "lock": false } + }, + "78d1126fc4c743db81b61c20e88342a7": { + "class": "central_heating_pump", + "fw": "2019-06-21T02:00:00+02:00", + "location": "c50f167537524366a5af7aa3942feb1e", + "model": "Plug", + "name": "CV Pomp", + "vendor": "Plugwise", + "sensors": { + "electricity_consumed": 35.6, + "electricity_consumed_interval": 7.37, + "electricity_produced": 0.0, + "electricity_produced_interval": 0.0 + }, + "switches": { "relay": true } + }, + "90986d591dcd426cae3ec3e8111ff730": { + "class": "heater_central", + "fw": null, + "location": "1f9dcf83fd4e4b66b72ff787957bfe5d", + "model": "Unknown", + "name": "OnOff", + "vendor": null, + "cooling_active": false, + "heating_state": true, + "sensors": { + "water_temperature": 70.0, + "intended_boiler_temperature": 70.0, + "modulation_level": 1, + "device_state": "heating" + } + }, + "cd0ddb54ef694e11ac18ed1cbce5dbbd": { + "class": "vcr", + "fw": "2019-06-21T02:00:00+02:00", + "location": "cd143c07248f491493cea0533bc3d669", + "model": "Plug", + "name": "NAS", + "vendor": "Plugwise", + "sensors": { + "electricity_consumed": 16.5, + "electricity_consumed_interval": 0.5, + "electricity_produced": 0.0, + "electricity_produced_interval": 0.0 + }, + "switches": { "relay": true, "lock": true } + }, + "4a810418d5394b3f82727340b91ba740": { + "class": "router", + "fw": "2019-06-21T02:00:00+02:00", + "location": "cd143c07248f491493cea0533bc3d669", + "model": "Plug", + "name": "USG Smart Plug", + "vendor": "Plugwise", + "sensors": { + "electricity_consumed": 8.5, + "electricity_consumed_interval": 0.0, + "electricity_produced": 0.0, + "electricity_produced_interval": 0.0 + }, + "switches": { "relay": true, "lock": true } + }, + "02cf28bfec924855854c544690a609ef": { + "class": "vcr", + "fw": "2019-06-21T02:00:00+02:00", + "location": "cd143c07248f491493cea0533bc3d669", + "model": "Plug", + "name": "NVR", + "vendor": "Plugwise", + "sensors": { + "electricity_consumed": 34.0, + "electricity_consumed_interval": 9.15, + "electricity_produced": 0.0, + "electricity_produced_interval": 0.0 + }, + "switches": { "relay": true, "lock": true } + }, + "a28f588dc4a049a483fd03a30361ad3a": { + "class": "settop", + "fw": "2019-06-21T02:00:00+02:00", + "location": "cd143c07248f491493cea0533bc3d669", + "model": "Plug", + "name": "Fibaro HC2", + "vendor": "Plugwise", + "sensors": { + "electricity_consumed": 12.5, + "electricity_consumed_interval": 3.8, + "electricity_produced": 0.0, + "electricity_produced_interval": 0.0 + }, + "switches": { "relay": true, "lock": true } + }, + "6a3bf693d05e48e0b460c815a4fdd09d": { + "class": "zone_thermostat", + "fw": "2016-10-27T02:00:00+02:00", + "location": "82fa13f017d240daa0d0ea1775420f24", + "model": "Lisa", + "name": "Zone Thermostat Jessie", + "vendor": "Plugwise", + "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "active_preset": "asleep", + "presets": { + "home": [20.0, 22.0], + "asleep": [17.0, 24.0], + "away": [15.0, 25.0], + "vacation": [15.0, 28.0], + "no_frost": [10.0, 30.0] + }, + "available_schedules": [ + "CV Roan", + "Bios Schema met Film Avond", + "GF7 Woonkamer", + "Badkamer Schema", + "CV Jessie" + ], + "selected_schedule": "CV Jessie", + "schedule_temperature": 15.0, + "last_used": "CV Jessie", + "mode": "auto", + "sensors": { "temperature": 17.2, "setpoint": 15.0, "battery": 37 } + }, + "680423ff840043738f42cc7f1ff97a36": { + "class": "thermo_sensor", + "fw": "2019-03-27T01:00:00+01:00", + "location": "08963fec7c53423ca5680aa4cb502c63", + "model": "Tom/Floor", + "name": "Thermostatic Radiator Badkamer", + "vendor": "Plugwise", + "sensors": { + "temperature": 19.1, + "setpoint": 14.0, + "battery": 51, + "temperature_difference": -0.4, + "valve_position": 0.0 + } + }, + "f1fee6043d3642a9b0a65297455f008e": { + "class": "zone_thermostat", + "fw": "2016-10-27T02:00:00+02:00", + "location": "08963fec7c53423ca5680aa4cb502c63", + "model": "Lisa", + "name": "Zone Thermostat Badkamer", + "vendor": "Plugwise", + "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "active_preset": "away", + "presets": { + "home": [20.0, 22.0], + "asleep": [17.0, 24.0], + "away": [15.0, 25.0], + "vacation": [15.0, 28.0], + "no_frost": [10.0, 30.0] + }, + "available_schedules": [ + "CV Roan", + "Bios Schema met Film Avond", + "GF7 Woonkamer", + "Badkamer Schema", + "CV Jessie" + ], + "selected_schedule": "Badkamer Schema", + "schedule_temperature": 15.0, + "last_used": "Badkamer Schema", + "mode": "auto", + "sensors": { "temperature": 18.9, "setpoint": 14.0, "battery": 92 } + }, + "675416a629f343c495449970e2ca37b5": { + "class": "router", + "fw": "2019-06-21T02:00:00+02:00", + "location": "cd143c07248f491493cea0533bc3d669", + "model": "Plug", + "name": "Ziggo Modem", + "vendor": "Plugwise", + "sensors": { + "electricity_consumed": 12.2, + "electricity_consumed_interval": 2.97, + "electricity_produced": 0.0, + "electricity_produced_interval": 0.0 + }, + "switches": { "relay": true, "lock": true } + }, + "e7693eb9582644e5b865dba8d4447cf1": { + "class": "thermostatic_radiator_valve", + "fw": "2019-03-27T01:00:00+01:00", + "location": "446ac08dd04d4eff8ac57489757b7314", + "model": "Tom/Floor", + "name": "CV Kraan Garage", + "vendor": "Plugwise", + "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "active_preset": "no_frost", + "presets": { + "home": [20.0, 22.0], + "asleep": [17.0, 24.0], + "away": [15.0, 25.0], + "vacation": [15.0, 28.0], + "no_frost": [10.0, 30.0] + }, + "available_schedules": [ + "CV Roan", + "Bios Schema met Film Avond", + "GF7 Woonkamer", + "Badkamer Schema", + "CV Jessie" + ], + "selected_schedule": "None", + "schedule_temperature": 15.0, + "last_used": "Badkamer Schema", + "mode": "heat", + "sensors": { + "temperature": 15.6, + "setpoint": 5.5, + "battery": 68, + "temperature_difference": 0.0, + "valve_position": 0.0 + } + } + } +] diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_all_devices.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_all_devices.json deleted file mode 100644 index 5a3492a3c6b9ff..00000000000000 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_all_devices.json +++ /dev/null @@ -1 +0,0 @@ -{"df4a4a8169904cdb9c03d61a21f42140": {"name": "Zone Lisa Bios", "model": "Zone Thermostat", "types": {"py/set": ["thermostat"]}, "class": "zone_thermostat", "location": "12493538af164a409c6a1c79e38afe1c"}, "b310b72a0e354bfab43089919b9a88bf": {"name": "Floor kraan", "model": "Thermostatic Radiator Valve", "types": {"py/set": ["thermostat"]}, "class": "thermo_sensor", "location": "c50f167537524366a5af7aa3942feb1e"}, "a2c3583e0a6349358998b760cea82d2a": {"name": "Bios Cv Thermostatic Radiator ", "model": "Thermostatic Radiator Valve", "types": {"py/set": ["thermostat"]}, "class": "thermo_sensor", "location": "12493538af164a409c6a1c79e38afe1c"}, "b59bcebaf94b499ea7d46e4a66fb62d8": {"name": "Zone Lisa WK", "model": "Zone Thermostat", "types": {"py/set": ["thermostat"]}, "class": "zone_thermostat", "location": "c50f167537524366a5af7aa3942feb1e"}, "fe799307f1624099878210aa0b9f1475": {"name": "Adam", "model": "Smile Adam", "types": {"py/set": ["temperature", "thermostat", "home"]}, "class": "gateway", "location": "1f9dcf83fd4e4b66b72ff787957bfe5d"}, "d3da73bde12a47d5a6b8f9dad971f2ec": {"name": "Thermostatic Radiator Jessie", "model": "Thermostatic Radiator Valve", "types": {"py/set": ["thermostat"]}, "class": "thermo_sensor", "location": "82fa13f017d240daa0d0ea1775420f24"}, "21f2b542c49845e6bb416884c55778d6": {"name": "Playstation Smart Plug", "model": "Plug", "types": {"py/set": ["plug", "power"]}, "class": "game_console", "location": "cd143c07248f491493cea0533bc3d669"}, "78d1126fc4c743db81b61c20e88342a7": {"name": "CV Pomp", "model": "Plug", "types": {"py/set": ["plug", "power"]}, "class": "central_heating_pump", "location": "c50f167537524366a5af7aa3942feb1e"}, "90986d591dcd426cae3ec3e8111ff730": {"name": "Adam", "model": "Heater Central", "types": {"py/set": ["temperature", "thermostat", "home"]}, "class": "heater_central", "location": "1f9dcf83fd4e4b66b72ff787957bfe5d"}, "cd0ddb54ef694e11ac18ed1cbce5dbbd": {"name": "NAS", "model": "Plug", "types": {"py/set": ["plug", "power"]}, "class": "vcr", "location": "cd143c07248f491493cea0533bc3d669"}, "4a810418d5394b3f82727340b91ba740": {"name": "USG Smart Plug", "model": "Plug", "types": {"py/set": ["plug", "power"]}, "class": "router", "location": "cd143c07248f491493cea0533bc3d669"}, "02cf28bfec924855854c544690a609ef": {"name": "NVR", "model": "Plug", "types": {"py/set": ["plug", "power"]}, "class": "vcr", "location": "cd143c07248f491493cea0533bc3d669"}, "a28f588dc4a049a483fd03a30361ad3a": {"name": "Fibaro HC2", "model": "Plug", "types": {"py/set": ["plug", "power"]}, "class": "settop", "location": "cd143c07248f491493cea0533bc3d669"}, "6a3bf693d05e48e0b460c815a4fdd09d": {"name": "Zone Thermostat Jessie", "model": "Zone Thermostat", "types": {"py/set": ["thermostat"]}, "class": "zone_thermostat", "location": "82fa13f017d240daa0d0ea1775420f24"}, "680423ff840043738f42cc7f1ff97a36": {"name": "Thermostatic Radiator Badkamer", "model": "Thermostatic Radiator Valve", "types": {"py/set": ["thermostat"]}, "class": "thermo_sensor", "location": "08963fec7c53423ca5680aa4cb502c63"}, "f1fee6043d3642a9b0a65297455f008e": {"name": "Zone Thermostat Badkamer", "model": "Zone Thermostat", "types": {"py/set": ["thermostat"]}, "class": "zone_thermostat", "location": "08963fec7c53423ca5680aa4cb502c63"}, "675416a629f343c495449970e2ca37b5": {"name": "Ziggo Modem", "model": "Plug", "types": {"py/set": ["plug", "power"]}, "class": "router", "location": "cd143c07248f491493cea0533bc3d669"}, "e7693eb9582644e5b865dba8d4447cf1": {"name": "CV Kraan Garage", "model": "Thermostatic Radiator Valve", "types": {"py/set": ["thermostat"]}, "class": "thermostatic_radiator_valve", "location": "446ac08dd04d4eff8ac57489757b7314"}} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/02cf28bfec924855854c544690a609ef.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/02cf28bfec924855854c544690a609ef.json deleted file mode 100644 index 238da9d846a36e..00000000000000 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/02cf28bfec924855854c544690a609ef.json +++ /dev/null @@ -1 +0,0 @@ -{"electricity_consumed": 34.0, "electricity_consumed_interval": 9.15, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/21f2b542c49845e6bb416884c55778d6.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/21f2b542c49845e6bb416884c55778d6.json deleted file mode 100644 index 4fcb40c4cf8d77..00000000000000 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/21f2b542c49845e6bb416884c55778d6.json +++ /dev/null @@ -1 +0,0 @@ -{"electricity_consumed": 82.6, "electricity_consumed_interval": 8.6, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/4a810418d5394b3f82727340b91ba740.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/4a810418d5394b3f82727340b91ba740.json deleted file mode 100644 index feb6290c9c4c9d..00000000000000 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/4a810418d5394b3f82727340b91ba740.json +++ /dev/null @@ -1 +0,0 @@ -{"electricity_consumed": 8.5, "electricity_consumed_interval": 0.0, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/675416a629f343c495449970e2ca37b5.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/675416a629f343c495449970e2ca37b5.json deleted file mode 100644 index 74d15fac3740b5..00000000000000 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/675416a629f343c495449970e2ca37b5.json +++ /dev/null @@ -1 +0,0 @@ -{"electricity_consumed": 12.2, "electricity_consumed_interval": 2.97, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/680423ff840043738f42cc7f1ff97a36.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/680423ff840043738f42cc7f1ff97a36.json deleted file mode 100644 index 3ea0a92387b6e0..00000000000000 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/680423ff840043738f42cc7f1ff97a36.json +++ /dev/null @@ -1 +0,0 @@ -{"temperature": 19.1, "setpoint": 14.0, "battery": 51, "temperature_difference": -0.4, "valve_position": 0.0} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/6a3bf693d05e48e0b460c815a4fdd09d.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/6a3bf693d05e48e0b460c815a4fdd09d.json deleted file mode 100644 index 2d8ace6fa3fa8e..00000000000000 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/6a3bf693d05e48e0b460c815a4fdd09d.json +++ /dev/null @@ -1 +0,0 @@ -{"temperature": 17.2, "setpoint": 15.0, "battery": 37, "active_preset": "asleep", "presets": {"home": [20.0, 22.0], "no_frost": [10.0, 30.0], "away": [12.0, 25.0], "vacation": [11.0, 28.0], "asleep": [16.0, 24.0]}, "schedule_temperature": 15.0, "available_schedules": ["CV Jessie"], "selected_schedule": "CV Jessie", "last_used": "CV Jessie"} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/78d1126fc4c743db81b61c20e88342a7.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/78d1126fc4c743db81b61c20e88342a7.json deleted file mode 100644 index 7a9c3e9be01ada..00000000000000 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/78d1126fc4c743db81b61c20e88342a7.json +++ /dev/null @@ -1 +0,0 @@ -{"electricity_consumed": 35.6, "electricity_consumed_interval": 7.37, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/90986d591dcd426cae3ec3e8111ff730.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/90986d591dcd426cae3ec3e8111ff730.json deleted file mode 100644 index d2f2f82bdf6d0a..00000000000000 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/90986d591dcd426cae3ec3e8111ff730.json +++ /dev/null @@ -1 +0,0 @@ -{"water_temperature": 70.0, "intended_boiler_temperature": 70.0, "modulation_level": 1, "heating_state": true} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/a28f588dc4a049a483fd03a30361ad3a.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/a28f588dc4a049a483fd03a30361ad3a.json deleted file mode 100644 index 0aeca4cc18e597..00000000000000 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/a28f588dc4a049a483fd03a30361ad3a.json +++ /dev/null @@ -1 +0,0 @@ -{"electricity_consumed": 12.5, "electricity_consumed_interval": 3.8, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/a2c3583e0a6349358998b760cea82d2a.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/a2c3583e0a6349358998b760cea82d2a.json deleted file mode 100644 index 3f01f47fc5c811..00000000000000 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/a2c3583e0a6349358998b760cea82d2a.json +++ /dev/null @@ -1 +0,0 @@ -{"temperature": 17.2, "setpoint": 13.0, "battery": 62, "temperature_difference": -0.2, "valve_position": 0.0} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/b310b72a0e354bfab43089919b9a88bf.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/b310b72a0e354bfab43089919b9a88bf.json deleted file mode 100644 index 3a1c902932ad15..00000000000000 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/b310b72a0e354bfab43089919b9a88bf.json +++ /dev/null @@ -1 +0,0 @@ -{"temperature": 26.0, "setpoint": 21.5, "temperature_difference": 3.5, "valve_position": 100} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/b59bcebaf94b499ea7d46e4a66fb62d8.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/b59bcebaf94b499ea7d46e4a66fb62d8.json deleted file mode 100644 index 2b314f589b60e8..00000000000000 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/b59bcebaf94b499ea7d46e4a66fb62d8.json +++ /dev/null @@ -1 +0,0 @@ -{"temperature": 20.9, "setpoint": 21.5, "battery": 34, "active_preset": "home", "presets": {"vacation": [15.0, 28.0], "asleep": [18.0, 24.0], "no_frost": [12.0, 30.0], "away": [17.0, 25.0], "home": [21.5, 22.0]}, "schedule_temperature": 21.5, "available_schedules": ["GF7 Woonkamer"], "selected_schedule": "GF7 Woonkamer", "last_used": "GF7 Woonkamer"} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/cd0ddb54ef694e11ac18ed1cbce5dbbd.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/cd0ddb54ef694e11ac18ed1cbce5dbbd.json deleted file mode 100644 index fbefc5bba25709..00000000000000 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/cd0ddb54ef694e11ac18ed1cbce5dbbd.json +++ /dev/null @@ -1 +0,0 @@ -{"electricity_consumed": 16.5, "electricity_consumed_interval": 0.5, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/d3da73bde12a47d5a6b8f9dad971f2ec.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/d3da73bde12a47d5a6b8f9dad971f2ec.json deleted file mode 100644 index 3e0615939535b3..00000000000000 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/d3da73bde12a47d5a6b8f9dad971f2ec.json +++ /dev/null @@ -1 +0,0 @@ -{"temperature": 17.1, "setpoint": 15.0, "battery": 62, "temperature_difference": 0.1, "valve_position": 0.0} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/df4a4a8169904cdb9c03d61a21f42140.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/df4a4a8169904cdb9c03d61a21f42140.json deleted file mode 100644 index 88420a8a6bd4c9..00000000000000 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/df4a4a8169904cdb9c03d61a21f42140.json +++ /dev/null @@ -1 +0,0 @@ -{"temperature": 16.5, "setpoint": 13.0, "battery": 67, "active_preset": "away", "presets": {"home": [20.0, 22.0], "away": [12.0, 25.0], "vacation": [12.0, 28.0], "no_frost": [8.0, 30.0], "asleep": [15.0, 24.0]}, "schedule_temperature": null, "available_schedules": [], "selected_schedule": null, "last_used": null} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/e7693eb9582644e5b865dba8d4447cf1.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/e7693eb9582644e5b865dba8d4447cf1.json deleted file mode 100644 index 7e4532987b033a..00000000000000 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/e7693eb9582644e5b865dba8d4447cf1.json +++ /dev/null @@ -1 +0,0 @@ -{"temperature": 15.6, "setpoint": 5.5, "battery": 68, "temperature_difference": 0.0, "valve_position": 0.0, "active_preset": "no_frost", "presets": {"home": [20.0, 22.0], "asleep": [17.0, 24.0], "away": [15.0, 25.0], "vacation": [15.0, 28.0], "no_frost": [10.0, 30.0]}, "schedule_temperature": null, "available_schedules": [], "selected_schedule": null, "last_used": null} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/f1fee6043d3642a9b0a65297455f008e.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/f1fee6043d3642a9b0a65297455f008e.json deleted file mode 100644 index 0d6e19967dc740..00000000000000 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/f1fee6043d3642a9b0a65297455f008e.json +++ /dev/null @@ -1 +0,0 @@ -{"temperature": 18.9, "setpoint": 14.0, "battery": 92, "active_preset": "away", "presets": {"asleep": [17.0, 24.0], "no_frost": [10.0, 30.0], "away": [14.0, 25.0], "home": [21.0, 22.0], "vacation": [12.0, 28.0]}, "schedule_temperature": 14.0, "available_schedules": ["Badkamer Schema"], "selected_schedule": "Badkamer Schema", "last_used": "Badkamer Schema"} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/fe799307f1624099878210aa0b9f1475.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/fe799307f1624099878210aa0b9f1475.json deleted file mode 100644 index ef325af7bc2cbb..00000000000000 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/get_device_data/fe799307f1624099878210aa0b9f1475.json +++ /dev/null @@ -1 +0,0 @@ -{"outdoor_temperature": 7.81} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/notifications.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/notifications.json index c229f64da04642..8749be4c345414 100644 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/notifications.json +++ b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/notifications.json @@ -1 +1,5 @@ -{"af82e4ccf9c548528166d38e560662a4": {"warning": "Node Plug (with MAC address 000D6F000D13CB01, in room 'n.a.') has been unreachable since 23:03 2020-01-18. Please check the connection and restart the device."}} \ No newline at end of file +{ + "af82e4ccf9c548528166d38e560662a4": { + "warning": "Node Plug (with MAC address 000D6F000D13CB01, in room 'n.a.') has been unreachable since 23:03 2020-01-18. Please check the connection and restart the device." + } +} diff --git a/tests/components/plugwise/fixtures/anna_heatpump/all_data.json b/tests/components/plugwise/fixtures/anna_heatpump/all_data.json new file mode 100644 index 00000000000000..017d1ce41e8244 --- /dev/null +++ b/tests/components/plugwise/fixtures/anna_heatpump/all_data.json @@ -0,0 +1,79 @@ +[ + { + "active_device": true, + "cooling_present": true, + "gateway_id": "015ae9ea3f964e668e490fa39da3870b", + "heater_id": "1cbf783bb11e4a7c8a6843dee3a86927", + "single_master_thermostat": true, + "smile_name": "Anna", + "notifications": {} + }, + { + "1cbf783bb11e4a7c8a6843dee3a86927": { + "class": "heater_central", + "fw": null, + "location": "a57efe5f145f498c9be62a9b63626fbf", + "model": "Generic heater", + "name": "OpenTherm", + "vendor": "Techneco", + "heating_state": true, + "compressor_state": true, + "cooling_state": false, + "cooling_active": false, + "binary_sensors": { + "dhw_state": false, + "slave_boiler_state": false, + "flame_state": false + }, + "sensors": { + "outdoor_temperature": 3.0, + "water_temperature": 29.1, + "intended_boiler_temperature": 0.0, + "modulation_level": 52, + "return_temperature": 25.1, + "water_pressure": 1.57, + "device_state": "heating" + }, + "switches": { "dhw_cm_switch": false } + }, + "015ae9ea3f964e668e490fa39da3870b": { + "class": "gateway", + "fw": "4.0.15", + "location": "a57efe5f145f498c9be62a9b63626fbf", + "model": "Anna", + "name": "Anna", + "vendor": "Plugwise B.V.", + "binary_sensors": { "plugwise_notification": false }, + "sensors": { "outdoor_temperature": 20.2 } + }, + "3cb70739631c4d17a86b8b12e8a5161b": { + "class": "thermostat", + "fw": "2018-02-08T11:15:53+01:00", + "location": "c784ee9fdab44e1395b8dee7d7a497d5", + "model": "Anna", + "name": "Anna", + "vendor": "Plugwise", + "preset_modes": ["no_frost", "home", "away", "asleep", "vacation"], + "active_preset": "home", + "presets": { + "no_frost": [10.0, 30.0], + "home": [21.0, 22.0], + "away": [20.0, 25.0], + "asleep": [20.5, 24.0], + "vacation": [17.0, 28.0] + }, + "available_schedules": ["None"], + "selected_schedule": "None", + "schedule_temperature": null, + "last_used": null, + "mode": "heat", + "sensors": { + "temperature": 19.3, + "setpoint": 21.0, + "illuminance": 86.0, + "cooling_activation_outdoor_temperature": 21.0, + "cooling_deactivation_threshold": 4 + } + } + } +] diff --git a/tests/components/plugwise/fixtures/anna_heatpump/get_all_devices.json b/tests/components/plugwise/fixtures/anna_heatpump/get_all_devices.json deleted file mode 100644 index ea46cd680542ba..00000000000000 --- a/tests/components/plugwise/fixtures/anna_heatpump/get_all_devices.json +++ /dev/null @@ -1 +0,0 @@ -{"1cbf783bb11e4a7c8a6843dee3a86927": {"name": "Anna", "model": "Heater Central", "types": {"py/set": ["temperature", "thermostat", "home"]}, "class": "heater_central", "location": "a57efe5f145f498c9be62a9b63626fbf"}, "015ae9ea3f964e668e490fa39da3870b": {"name": "Anna", "model": "Smile Anna", "types": {"py/set": ["temperature", "thermostat", "home"]}, "class": "gateway", "location": "a57efe5f145f498c9be62a9b63626fbf"}, "3cb70739631c4d17a86b8b12e8a5161b": {"name": "Anna", "model": "Thermostat", "types": {"py/set": ["thermostat"]}, "class": "thermostat", "location": "c784ee9fdab44e1395b8dee7d7a497d5"}} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/anna_heatpump/get_device_data/015ae9ea3f964e668e490fa39da3870b.json b/tests/components/plugwise/fixtures/anna_heatpump/get_device_data/015ae9ea3f964e668e490fa39da3870b.json deleted file mode 100644 index 750aa8b455c07e..00000000000000 --- a/tests/components/plugwise/fixtures/anna_heatpump/get_device_data/015ae9ea3f964e668e490fa39da3870b.json +++ /dev/null @@ -1 +0,0 @@ -{"outdoor_temperature": 20.2} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/anna_heatpump/get_device_data/1cbf783bb11e4a7c8a6843dee3a86927.json b/tests/components/plugwise/fixtures/anna_heatpump/get_device_data/1cbf783bb11e4a7c8a6843dee3a86927.json deleted file mode 100644 index 604b93889699c3..00000000000000 --- a/tests/components/plugwise/fixtures/anna_heatpump/get_device_data/1cbf783bb11e4a7c8a6843dee3a86927.json +++ /dev/null @@ -1 +0,0 @@ -{"water_temperature": 29.1, "dhw_state": false, "intended_boiler_temperature": 0.0, "heating_state": false, "modulation_level": 52, "return_temperature": 25.1, "compressor_state": true, "cooling_state": false, "slave_boiler_state": false, "flame_state": false, "water_pressure": 1.57, "outdoor_temperature": 18.0} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/anna_heatpump/get_device_data/3cb70739631c4d17a86b8b12e8a5161b.json b/tests/components/plugwise/fixtures/anna_heatpump/get_device_data/3cb70739631c4d17a86b8b12e8a5161b.json deleted file mode 100644 index 048cc0f77dcfb6..00000000000000 --- a/tests/components/plugwise/fixtures/anna_heatpump/get_device_data/3cb70739631c4d17a86b8b12e8a5161b.json +++ /dev/null @@ -1 +0,0 @@ -{"temperature": 23.3, "setpoint": 21.0, "heating_state": false, "active_preset": "home", "presets": {"no_frost": [10.0, 30.0], "home": [21.0, 22.0], "away": [20.0, 25.0], "asleep": [20.5, 24.0], "vacation": [17.0, 28.0]}, "schedule_temperature": null, "available_schedules": ["standaard"], "selected_schedule": "standaard", "last_used": "standaard", "illuminance": 86.0} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/p1v3_full_option/all_data.json b/tests/components/plugwise/fixtures/p1v3_full_option/all_data.json new file mode 100644 index 00000000000000..43c47c2b5e0ba5 --- /dev/null +++ b/tests/components/plugwise/fixtures/p1v3_full_option/all_data.json @@ -0,0 +1,39 @@ +[ + { + "active_device": false, + "cooling_present": null, + "gateway_id": "e950c7d5e1ee407a858e2a8b5016c8b3", + "heater_id": null, + "single_master_thermostat": null, + "smile_name": "P1", + "notifications": {} + }, + { + "e950c7d5e1ee407a858e2a8b5016c8b3": { + "class": "gateway", + "fw": "3.3.9", + "location": "cd3e822288064775a7c4afcdd70bdda2", + "model": "P1", + "name": "P1", + "vendor": "Plugwise B.V.", + "sensors": { + "net_electricity_point": -2816, + "electricity_consumed_peak_point": 0, + "electricity_consumed_off_peak_point": 0, + "net_electricity_cumulative": 442.972, + "electricity_consumed_peak_cumulative": 442.932, + "electricity_consumed_off_peak_cumulative": 551.09, + "electricity_consumed_peak_interval": 0, + "electricity_consumed_off_peak_interval": 0, + "electricity_produced_peak_point": 2816, + "electricity_produced_off_peak_point": 0, + "electricity_produced_peak_cumulative": 396.559, + "electricity_produced_off_peak_cumulative": 154.491, + "electricity_produced_peak_interval": 0, + "electricity_produced_off_peak_interval": 0, + "gas_consumed_cumulative": 584.85, + "gas_consumed_interval": 0.0 + } + } + } +] diff --git a/tests/components/plugwise/fixtures/p1v3_full_option/get_all_devices.json b/tests/components/plugwise/fixtures/p1v3_full_option/get_all_devices.json deleted file mode 100644 index a78f45ead8a35f..00000000000000 --- a/tests/components/plugwise/fixtures/p1v3_full_option/get_all_devices.json +++ /dev/null @@ -1 +0,0 @@ -{"e950c7d5e1ee407a858e2a8b5016c8b3": {"name": "P1", "model": "Smile P1", "types": {"py/set": ["home", "power"]}, "class": "gateway", "location": "cd3e822288064775a7c4afcdd70bdda2"}} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/p1v3_full_option/get_device_data/e950c7d5e1ee407a858e2a8b5016c8b3.json b/tests/components/plugwise/fixtures/p1v3_full_option/get_device_data/e950c7d5e1ee407a858e2a8b5016c8b3.json deleted file mode 100644 index eed9382a7e9bab..00000000000000 --- a/tests/components/plugwise/fixtures/p1v3_full_option/get_device_data/e950c7d5e1ee407a858e2a8b5016c8b3.json +++ /dev/null @@ -1 +0,0 @@ -{"net_electricity_point": -2761, "electricity_consumed_peak_point": 0, "electricity_consumed_off_peak_point": 0, "net_electricity_cumulative": 442.972, "electricity_consumed_peak_cumulative": 442.932, "electricity_consumed_off_peak_cumulative": 551.09, "net_electricity_interval": 0, "electricity_consumed_peak_interval": 0, "electricity_consumed_off_peak_interval": 0, "electricity_produced_peak_point": 2761, "electricity_produced_off_peak_point": 0, "electricity_produced_peak_cumulative": 396.559, "electricity_produced_off_peak_cumulative": 154.491, "electricity_produced_peak_interval": 0, "electricity_produced_off_peak_interval": 0, "gas_consumed_cumulative": 584.85, "gas_consumed_interval": 0.0} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/stretch_v31/all_data.json b/tests/components/plugwise/fixtures/stretch_v31/all_data.json new file mode 100644 index 00000000000000..3834eb516def9b --- /dev/null +++ b/tests/components/plugwise/fixtures/stretch_v31/all_data.json @@ -0,0 +1,178 @@ +[ + { + "active_device": false, + "cooling_present": null, + "gateway_id": "0000aaaa0000aaaa0000aaaa0000aa00", + "heater_id": null, + "single_master_thermostat": null, + "smile_name": "Stretch", + "notifications": {} + }, + { + "0000aaaa0000aaaa0000aaaa0000aa00": { + "class": "gateway", + "fw": "3.1.11", + "location": "0000aaaa0000aaaa0000aaaa0000aa00", + "vendor": "Plugwise B.V.", + "model": "Stretch", + "name": "Stretch" + }, + "5ca521ac179d468e91d772eeeb8a2117": { + "class": "zz_misc", + "fw": null, + "location": "0000aaaa0000aaaa0000aaaa0000aa00", + "model": null, + "name": "Oven (793F84)", + "vendor": null, + "sensors": { + "electricity_consumed": 0.0, + "electricity_consumed_interval": 0.0, + "electricity_produced": 0.0, + "electricity_produced_interval": 0.0 + }, + "switches": { "relay": true, "lock": false } + }, + "5871317346d045bc9f6b987ef25ee638": { + "class": "water_heater_vessel", + "fw": "2011-06-27T10:52:18+02:00", + "location": "0000aaaa0000aaaa0000aaaa0000aa00", + "model": "Circle type F", + "name": "Boiler (1EB31)", + "vendor": "Plugwise", + "sensors": { + "electricity_consumed": 1.19, + "electricity_consumed_interval": 0.0, + "electricity_produced": 0.0 + }, + "switches": { "relay": true, "lock": false } + }, + "e1c884e7dede431dadee09506ec4f859": { + "class": "refrigerator", + "fw": "2011-06-27T10:47:37+02:00", + "location": "0000aaaa0000aaaa0000aaaa0000aa00", + "model": "Circle+ type F", + "name": "Koelkast (92C4A)", + "vendor": "Plugwise", + "sensors": { + "electricity_consumed": 50.5, + "electricity_consumed_interval": 0.08, + "electricity_produced": 0.0 + }, + "switches": { "relay": true, "lock": false } + }, + "aac7b735042c4832ac9ff33aae4f453b": { + "class": "dishwasher", + "fw": "2011-06-27T10:52:18+02:00", + "location": "0000aaaa0000aaaa0000aaaa0000aa00", + "model": "Circle type F", + "name": "Vaatwasser (2a1ab)", + "vendor": "Plugwise", + "sensors": { + "electricity_consumed": 0.0, + "electricity_consumed_interval": 0.71, + "electricity_produced": 0.0 + }, + "switches": { "relay": true, "lock": false } + }, + "cfe95cf3de1948c0b8955125bf754614": { + "class": "dryer", + "fw": "2011-06-27T10:52:18+02:00", + "location": "0000aaaa0000aaaa0000aaaa0000aa00", + "model": "Circle type F", + "name": "Droger (52559)", + "vendor": "Plugwise", + "sensors": { + "electricity_consumed": 0.0, + "electricity_consumed_interval": 0.0, + "electricity_produced": 0.0 + }, + "switches": { "relay": true, "lock": false } + }, + "99f89d097be34fca88d8598c6dbc18ea": { + "class": "router", + "fw": null, + "location": "0000aaaa0000aaaa0000aaaa0000aa00", + "model": null, + "name": "Meterkast (787BFB)", + "vendor": null, + "sensors": { + "electricity_consumed": 27.6, + "electricity_consumed_interval": 28.2, + "electricity_produced": 0.0, + "electricity_produced_interval": 0.0 + }, + "switches": { "relay": true, "lock": true } + }, + "059e4d03c7a34d278add5c7a4a781d19": { + "class": "washingmachine", + "fw": "2011-06-27T10:52:18+02:00", + "location": "0000aaaa0000aaaa0000aaaa0000aa00", + "model": "Circle type F", + "name": "Wasmachine (52AC1)", + "vendor": "Plugwise", + "sensors": { + "electricity_consumed": 0.0, + "electricity_consumed_interval": 0.0, + "electricity_produced": 0.0 + }, + "switches": { "relay": true, "lock": false } + }, + "e309b52ea5684cf1a22f30cf0cd15051": { + "class": "computer_desktop", + "fw": null, + "location": "0000aaaa0000aaaa0000aaaa0000aa00", + "model": null, + "name": "Computer (788618)", + "vendor": null, + "sensors": { + "electricity_consumed": 156, + "electricity_consumed_interval": 163, + "electricity_produced": 0.0, + "electricity_produced_interval": 0.0 + }, + "switches": { "relay": true, "lock": true } + }, + "71e1944f2a944b26ad73323e399efef0": { + "class": "switching", + "fw": null, + "location": null, + "model": "Switchgroup", + "name": "Test", + "members": ["5ca521ac179d468e91d772eeeb8a2117"], + "types": ["switch_group"], + "vendor": null, + "switches": { "relay": true } + }, + "d950b314e9d8499f968e6db8d82ef78c": { + "class": "report", + "fw": null, + "location": null, + "model": "Switchgroup", + "name": "Stroomvreters", + "members": [ + "059e4d03c7a34d278add5c7a4a781d19", + "5871317346d045bc9f6b987ef25ee638", + "aac7b735042c4832ac9ff33aae4f453b", + "cfe95cf3de1948c0b8955125bf754614", + "e1c884e7dede431dadee09506ec4f859" + ], + "types": ["switch_group"], + "vendor": null, + "switches": { "relay": true } + }, + "d03738edfcc947f7b8f4573571d90d2d": { + "class": "switching", + "fw": null, + "location": null, + "model": "Switchgroup", + "name": "Schakel", + "members": [ + "059e4d03c7a34d278add5c7a4a781d19", + "cfe95cf3de1948c0b8955125bf754614" + ], + "types": ["switch_group"], + "vendor": null, + "switches": { "relay": true } + } + } +] diff --git a/tests/components/plugwise/fixtures/stretch_v31/get_all_devices.json b/tests/components/plugwise/fixtures/stretch_v31/get_all_devices.json deleted file mode 100644 index dab74fb74a231c..00000000000000 --- a/tests/components/plugwise/fixtures/stretch_v31/get_all_devices.json +++ /dev/null @@ -1 +0,0 @@ -{"5ca521ac179d468e91d772eeeb8a2117": {"name": "Oven (793F84)", "model": "Circle", "types": {"py/set": ["plug", "power"]}, "class": "zz_misc", "location": 0}, "5871317346d045bc9f6b987ef25ee638": {"name": "Boiler (1EB31)", "model": "Circle", "types": {"py/set": ["plug", "power"]}, "class": "water_heater_vessel", "location": 0}, "e1c884e7dede431dadee09506ec4f859": {"name": "Koelkast (92C4A)", "model": "Circle+", "types": {"py/set": ["plug", "power"]}, "class": "refrigerator", "location": 0}, "aac7b735042c4832ac9ff33aae4f453b": {"name": "Vaatwasser (2a1ab)", "model": "Circle", "types": {"py/set": ["plug", "power"]}, "class": "dishwasher", "location": 0}, "cfe95cf3de1948c0b8955125bf754614": {"name": "Droger (52559)", "model": "Circle", "types": {"py/set": ["plug", "power"]}, "class": "dryer", "location": 0}, "99f89d097be34fca88d8598c6dbc18ea": {"name": "Meterkast (787BFB)", "model": "Circle", "types": {"py/set": ["plug", "power"]}, "class": "router", "location": 0}, "059e4d03c7a34d278add5c7a4a781d19": {"name": "Wasmachine (52AC1)", "model": "Circle", "types": {"py/set": ["plug", "power"]}, "class": "washingmachine", "location": 0}, "e309b52ea5684cf1a22f30cf0cd15051": {"name": "Computer (788618)", "model": "Circle", "types": {"py/set": ["plug", "power"]}, "class": "computer_desktop", "location": 0}, "71e1944f2a944b26ad73323e399efef0": {"name": "Test", "model": "group_switch", "types": {"py/set": ["switch_group"]}, "class": "switching", "members": ["5ca521ac179d468e91d772eeeb8a2117"], "location": null}, "d950b314e9d8499f968e6db8d82ef78c": {"name": "Stroomvreters", "model": "group_switch", "types": {"py/set": ["switch_group"]}, "class": "report", "members": ["059e4d03c7a34d278add5c7a4a781d19", "5871317346d045bc9f6b987ef25ee638", "aac7b735042c4832ac9ff33aae4f453b", "cfe95cf3de1948c0b8955125bf754614", "e1c884e7dede431dadee09506ec4f859"], "location": null}, "d03738edfcc947f7b8f4573571d90d2d": {"name": "Schakel", "model": "group_switch", "types": {"py/set": ["switch_group"]}, "class": "switching", "members": ["059e4d03c7a34d278add5c7a4a781d19", "cfe95cf3de1948c0b8955125bf754614"], "location": null}} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/stretch_v31/get_device_data/059e4d03c7a34d278add5c7a4a781d19.json b/tests/components/plugwise/fixtures/stretch_v31/get_device_data/059e4d03c7a34d278add5c7a4a781d19.json deleted file mode 100644 index b08f6d6093adfe..00000000000000 --- a/tests/components/plugwise/fixtures/stretch_v31/get_device_data/059e4d03c7a34d278add5c7a4a781d19.json +++ /dev/null @@ -1 +0,0 @@ -{"electricity_consumed": 0.0, "electricity_consumed_interval": 0.0, "electricity_produced": 0.0, "relay": true} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/stretch_v31/get_device_data/5871317346d045bc9f6b987ef25ee638.json b/tests/components/plugwise/fixtures/stretch_v31/get_device_data/5871317346d045bc9f6b987ef25ee638.json deleted file mode 100644 index 4a3e493b246dd5..00000000000000 --- a/tests/components/plugwise/fixtures/stretch_v31/get_device_data/5871317346d045bc9f6b987ef25ee638.json +++ /dev/null @@ -1 +0,0 @@ -{"electricity_consumed": 1.19, "electricity_consumed_interval": 0.0, "electricity_produced": 0.0, "relay": true} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/stretch_v31/get_device_data/5ca521ac179d468e91d772eeeb8a2117.json b/tests/components/plugwise/fixtures/stretch_v31/get_device_data/5ca521ac179d468e91d772eeeb8a2117.json deleted file mode 100644 index 7325dff8271857..00000000000000 --- a/tests/components/plugwise/fixtures/stretch_v31/get_device_data/5ca521ac179d468e91d772eeeb8a2117.json +++ /dev/null @@ -1 +0,0 @@ -{"electricity_consumed": 0.0, "electricity_consumed_interval": 0.0, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/stretch_v31/get_device_data/71e1944f2a944b26ad73323e399efef0.json b/tests/components/plugwise/fixtures/stretch_v31/get_device_data/71e1944f2a944b26ad73323e399efef0.json deleted file mode 100644 index bbb8ac98c1c4fe..00000000000000 --- a/tests/components/plugwise/fixtures/stretch_v31/get_device_data/71e1944f2a944b26ad73323e399efef0.json +++ /dev/null @@ -1 +0,0 @@ -{"relay": true} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/stretch_v31/get_device_data/99f89d097be34fca88d8598c6dbc18ea.json b/tests/components/plugwise/fixtures/stretch_v31/get_device_data/99f89d097be34fca88d8598c6dbc18ea.json deleted file mode 100644 index b0cab0e3f304ef..00000000000000 --- a/tests/components/plugwise/fixtures/stretch_v31/get_device_data/99f89d097be34fca88d8598c6dbc18ea.json +++ /dev/null @@ -1 +0,0 @@ -{"electricity_consumed": 27.6, "electricity_consumed_interval": 28.2, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/stretch_v31/get_device_data/aac7b735042c4832ac9ff33aae4f453b.json b/tests/components/plugwise/fixtures/stretch_v31/get_device_data/aac7b735042c4832ac9ff33aae4f453b.json deleted file mode 100644 index e58bc4c6d6f240..00000000000000 --- a/tests/components/plugwise/fixtures/stretch_v31/get_device_data/aac7b735042c4832ac9ff33aae4f453b.json +++ /dev/null @@ -1 +0,0 @@ -{"electricity_consumed": 0.0, "electricity_consumed_interval": 0.71, "electricity_produced": 0.0, "relay": true} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/stretch_v31/get_device_data/cfe95cf3de1948c0b8955125bf754614.json b/tests/components/plugwise/fixtures/stretch_v31/get_device_data/cfe95cf3de1948c0b8955125bf754614.json deleted file mode 100644 index b08f6d6093adfe..00000000000000 --- a/tests/components/plugwise/fixtures/stretch_v31/get_device_data/cfe95cf3de1948c0b8955125bf754614.json +++ /dev/null @@ -1 +0,0 @@ -{"electricity_consumed": 0.0, "electricity_consumed_interval": 0.0, "electricity_produced": 0.0, "relay": true} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/stretch_v31/get_device_data/d03738edfcc947f7b8f4573571d90d2d.json b/tests/components/plugwise/fixtures/stretch_v31/get_device_data/d03738edfcc947f7b8f4573571d90d2d.json deleted file mode 100644 index bbb8ac98c1c4fe..00000000000000 --- a/tests/components/plugwise/fixtures/stretch_v31/get_device_data/d03738edfcc947f7b8f4573571d90d2d.json +++ /dev/null @@ -1 +0,0 @@ -{"relay": true} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/stretch_v31/get_device_data/d950b314e9d8499f968e6db8d82ef78c.json b/tests/components/plugwise/fixtures/stretch_v31/get_device_data/d950b314e9d8499f968e6db8d82ef78c.json deleted file mode 100644 index bbb8ac98c1c4fe..00000000000000 --- a/tests/components/plugwise/fixtures/stretch_v31/get_device_data/d950b314e9d8499f968e6db8d82ef78c.json +++ /dev/null @@ -1 +0,0 @@ -{"relay": true} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/stretch_v31/get_device_data/e1c884e7dede431dadee09506ec4f859.json b/tests/components/plugwise/fixtures/stretch_v31/get_device_data/e1c884e7dede431dadee09506ec4f859.json deleted file mode 100644 index 11ebae52f49a1f..00000000000000 --- a/tests/components/plugwise/fixtures/stretch_v31/get_device_data/e1c884e7dede431dadee09506ec4f859.json +++ /dev/null @@ -1 +0,0 @@ -{"electricity_consumed": 50.5, "electricity_consumed_interval": 0.08, "electricity_produced": 0.0, "relay": true} \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/stretch_v31/get_device_data/e309b52ea5684cf1a22f30cf0cd15051.json b/tests/components/plugwise/fixtures/stretch_v31/get_device_data/e309b52ea5684cf1a22f30cf0cd15051.json deleted file mode 100644 index 456fb6744d2abf..00000000000000 --- a/tests/components/plugwise/fixtures/stretch_v31/get_device_data/e309b52ea5684cf1a22f30cf0cd15051.json +++ /dev/null @@ -1 +0,0 @@ -{"electricity_consumed": 156, "electricity_consumed_interval": 163, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true} \ No newline at end of file diff --git a/tests/components/plugwise/test_climate.py b/tests/components/plugwise/test_climate.py index 2fed3d18fd2189..a7af9f9c0c99da 100644 --- a/tests/components/plugwise/test_climate.py +++ b/tests/components/plugwise/test_climate.py @@ -2,7 +2,11 @@ from plugwise.exceptions import PlugwiseException -from homeassistant.components.climate.const import HVAC_MODE_AUTO, HVAC_MODE_HEAT +from homeassistant.components.climate.const import ( + HVAC_MODE_AUTO, + HVAC_MODE_HEAT, + HVAC_MODE_OFF, +) from homeassistant.config_entries import ConfigEntryState from tests.components.plugwise.common import async_init_integration @@ -16,7 +20,7 @@ async def test_adam_climate_entity_attributes(hass, mock_smile_adam): state = hass.states.get("climate.zone_lisa_wk") attrs = state.attributes - assert attrs["hvac_modes"] == [HVAC_MODE_HEAT, HVAC_MODE_AUTO] + assert attrs["hvac_modes"] == [HVAC_MODE_HEAT, HVAC_MODE_AUTO, HVAC_MODE_OFF] assert "preset_modes" in attrs assert "no_frost" in attrs["preset_modes"] @@ -32,7 +36,7 @@ async def test_adam_climate_entity_attributes(hass, mock_smile_adam): state = hass.states.get("climate.zone_thermostat_jessie") attrs = state.attributes - assert attrs["hvac_modes"] == [HVAC_MODE_HEAT, HVAC_MODE_AUTO] + assert attrs["hvac_modes"] == [HVAC_MODE_HEAT, HVAC_MODE_AUTO, HVAC_MODE_OFF] assert "preset_modes" in attrs assert "no_frost" in attrs["preset_modes"] @@ -144,17 +148,17 @@ async def test_anna_climate_entity_attributes(hass, mock_smile_anna): attrs = state.attributes assert "hvac_modes" in attrs - assert "heat_cool" in attrs["hvac_modes"] + assert "heat" in attrs["hvac_modes"] assert "preset_modes" in attrs assert "no_frost" in attrs["preset_modes"] assert "home" in attrs["preset_modes"] - assert attrs["current_temperature"] == 23.3 + assert attrs["current_temperature"] == 19.3 assert attrs["temperature"] == 21.0 - assert state.state == HVAC_MODE_AUTO - assert attrs["hvac_action"] == "idle" + assert state.state == HVAC_MODE_HEAT + assert attrs["hvac_action"] == "heating" assert attrs["preset_mode"] == "home" assert attrs["supported_features"] == 17 diff --git a/tests/components/plugwise/test_init.py b/tests/components/plugwise/test_init.py index c4f7e1c6b3d0a1..d0d810804e2208 100644 --- a/tests/components/plugwise/test_init.py +++ b/tests/components/plugwise/test_init.py @@ -39,7 +39,7 @@ async def test_smile_timeout(hass, mock_smile_notconnect): async def test_smile_adam_xmlerror(hass, mock_smile_adam): """Detect malformed XML by Smile in Adam environment.""" - mock_smile_adam.full_update_device.side_effect = XMLDataMissingError + mock_smile_adam.async_update.side_effect = XMLDataMissingError entry = await async_init_integration(hass, mock_smile_adam) assert entry.state is ConfigEntryState.SETUP_RETRY diff --git a/tests/components/plugwise/test_sensor.py b/tests/components/plugwise/test_sensor.py index 3b5bff781e5f2f..3498a7a8cf1c2a 100644 --- a/tests/components/plugwise/test_sensor.py +++ b/tests/components/plugwise/test_sensor.py @@ -37,7 +37,7 @@ async def test_anna_as_smt_climate_sensor_entities(hass, mock_smile_anna): assert entry.state is ConfigEntryState.LOADED state = hass.states.get("sensor.auxiliary_outdoor_temperature") - assert float(state.state) == 18.0 + assert float(state.state) == 3.0 state = hass.states.get("sensor.auxiliary_water_temperature") assert float(state.state) == 29.1 @@ -53,7 +53,7 @@ async def test_anna_climate_sensor_entities(hass, mock_smile_anna): assert entry.state is ConfigEntryState.LOADED state = hass.states.get("sensor.auxiliary_outdoor_temperature") - assert float(state.state) == 18.0 + assert float(state.state) == 3.0 async def test_p1_dsmr_sensor_entities(hass, mock_smile_p1): @@ -62,13 +62,13 @@ async def test_p1_dsmr_sensor_entities(hass, mock_smile_p1): assert entry.state is ConfigEntryState.LOADED state = hass.states.get("sensor.p1_net_electricity_point") - assert float(state.state) == -2761.0 + assert float(state.state) == -2816.0 state = hass.states.get("sensor.p1_electricity_consumed_off_peak_cumulative") assert float(state.state) == 551.09 state = hass.states.get("sensor.p1_electricity_produced_peak_point") - assert float(state.state) == 2761.0 + assert float(state.state) == 2816.0 state = hass.states.get("sensor.p1_electricity_consumed_peak_cumulative") assert float(state.state) == 442.932 diff --git a/tests/components/plugwise/test_switch.py b/tests/components/plugwise/test_switch.py index 6355362fbd989a..1e1fb4a067951e 100644 --- a/tests/components/plugwise/test_switch.py +++ b/tests/components/plugwise/test_switch.py @@ -21,7 +21,7 @@ async def test_adam_climate_switch_entities(hass, mock_smile_adam): async def test_adam_climate_switch_negative_testing(hass, mock_smile_adam): """Test exceptions of climate related switch entities.""" - mock_smile_adam.set_relay_state.side_effect = PlugwiseException + mock_smile_adam.set_switch_state.side_effect = PlugwiseException entry = await async_init_integration(hass, mock_smile_adam) assert entry.state is ConfigEntryState.LOADED From 1fc717ed1cfee06d3ce70cc123a84c9d97319fb5 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 8 Feb 2022 12:43:45 +0100 Subject: [PATCH 0422/1098] Add diagnostics support to Plugwise (#65982) --- .../components/plugwise/diagnostics.py | 23 ++ tests/components/plugwise/test_diagnostics.py | 376 ++++++++++++++++++ 2 files changed, 399 insertions(+) create mode 100644 homeassistant/components/plugwise/diagnostics.py create mode 100644 tests/components/plugwise/test_diagnostics.py diff --git a/homeassistant/components/plugwise/diagnostics.py b/homeassistant/components/plugwise/diagnostics.py new file mode 100644 index 00000000000000..2b79d22f6a3d4d --- /dev/null +++ b/homeassistant/components/plugwise/diagnostics.py @@ -0,0 +1,23 @@ +"""Diagnostics support for Plugwise.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import COORDINATOR, DOMAIN +from .coordinator import PlugwiseDataUpdateCoordinator + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + coordinator: PlugwiseDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ + COORDINATOR + ] + return { + "gateway": coordinator.data.gateway, + "devices": coordinator.data.devices, + } diff --git a/tests/components/plugwise/test_diagnostics.py b/tests/components/plugwise/test_diagnostics.py new file mode 100644 index 00000000000000..fdc4ee879201b8 --- /dev/null +++ b/tests/components/plugwise/test_diagnostics.py @@ -0,0 +1,376 @@ +"""Tests for the diagnostics data provided by the Plugwise integration.""" +from unittest.mock import MagicMock + +from aiohttp import ClientSession + +from homeassistant.core import HomeAssistant + +from tests.components.diagnostics import get_diagnostics_for_config_entry +from tests.components.plugwise.common import async_init_integration + + +async def test_diagnostics( + hass: HomeAssistant, + hass_client: ClientSession, + mock_smile_adam: MagicMock, +): + """Test diagnostics.""" + entry = await async_init_integration(hass, mock_smile_adam) + assert await get_diagnostics_for_config_entry(hass, hass_client, entry) == { + "gateway": { + "active_device": True, + "cooling_present": None, + "gateway_id": "fe799307f1624099878210aa0b9f1475", + "heater_id": "90986d591dcd426cae3ec3e8111ff730", + "single_master_thermostat": False, + "smile_name": "Adam", + "notifications": { + "af82e4ccf9c548528166d38e560662a4": { + "warning": "Node Plug (with MAC address 000D6F000D13CB01, in room 'n.a.') has been unreachable since 23:03 2020-01-18. Please check the connection and restart the device." + } + }, + }, + "devices": { + "df4a4a8169904cdb9c03d61a21f42140": { + "class": "zone_thermostat", + "fw": "2016-10-27T02:00:00+02:00", + "location": "12493538af164a409c6a1c79e38afe1c", + "model": "Lisa", + "name": "Zone Lisa Bios", + "vendor": "Plugwise", + "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "active_preset": "away", + "presets": { + "home": [20.0, 22.0], + "asleep": [17.0, 24.0], + "away": [15.0, 25.0], + "vacation": [15.0, 28.0], + "no_frost": [10.0, 30.0], + }, + "available_schedules": [ + "CV Roan", + "Bios Schema met Film Avond", + "GF7 Woonkamer", + "Badkamer Schema", + "CV Jessie", + ], + "selected_schedule": "None", + "schedule_temperature": 15.0, + "last_used": "Badkamer Schema", + "mode": "off", + "sensors": {"temperature": 16.5, "setpoint": 13.0, "battery": 67}, + }, + "b310b72a0e354bfab43089919b9a88bf": { + "class": "thermo_sensor", + "fw": "2019-03-27T01:00:00+01:00", + "location": "c50f167537524366a5af7aa3942feb1e", + "model": "Tom/Floor", + "name": "Floor kraan", + "vendor": "Plugwise", + "sensors": { + "temperature": 26.0, + "setpoint": 21.5, + "temperature_difference": 3.5, + "valve_position": 100, + }, + }, + "a2c3583e0a6349358998b760cea82d2a": { + "class": "thermo_sensor", + "fw": "2019-03-27T01:00:00+01:00", + "location": "12493538af164a409c6a1c79e38afe1c", + "model": "Tom/Floor", + "name": "Bios Cv Thermostatic Radiator ", + "vendor": "Plugwise", + "sensors": { + "temperature": 17.2, + "setpoint": 13.0, + "battery": 62, + "temperature_difference": -0.2, + "valve_position": 0.0, + }, + }, + "b59bcebaf94b499ea7d46e4a66fb62d8": { + "class": "zone_thermostat", + "fw": "2016-08-02T02:00:00+02:00", + "location": "c50f167537524366a5af7aa3942feb1e", + "model": "Lisa", + "name": "Zone Lisa WK", + "vendor": "Plugwise", + "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "active_preset": "home", + "presets": { + "home": [20.0, 22.0], + "asleep": [17.0, 24.0], + "away": [15.0, 25.0], + "vacation": [15.0, 28.0], + "no_frost": [10.0, 30.0], + }, + "available_schedules": [ + "CV Roan", + "Bios Schema met Film Avond", + "GF7 Woonkamer", + "Badkamer Schema", + "CV Jessie", + ], + "selected_schedule": "GF7 Woonkamer", + "schedule_temperature": 20.0, + "last_used": "GF7 Woonkamer", + "mode": "auto", + "sensors": {"temperature": 20.9, "setpoint": 21.5, "battery": 34}, + }, + "fe799307f1624099878210aa0b9f1475": { + "class": "gateway", + "fw": "3.0.15", + "location": "1f9dcf83fd4e4b66b72ff787957bfe5d", + "model": "Adam", + "name": "Adam", + "vendor": "Plugwise B.V.", + "binary_sensors": {"plugwise_notification": True}, + "sensors": {"outdoor_temperature": 7.81}, + }, + "d3da73bde12a47d5a6b8f9dad971f2ec": { + "class": "thermo_sensor", + "fw": "2019-03-27T01:00:00+01:00", + "location": "82fa13f017d240daa0d0ea1775420f24", + "model": "Tom/Floor", + "name": "Thermostatic Radiator Jessie", + "vendor": "Plugwise", + "sensors": { + "temperature": 17.1, + "setpoint": 15.0, + "battery": 62, + "temperature_difference": 0.1, + "valve_position": 0.0, + }, + }, + "21f2b542c49845e6bb416884c55778d6": { + "class": "game_console", + "fw": "2019-06-21T02:00:00+02:00", + "location": "cd143c07248f491493cea0533bc3d669", + "model": "Plug", + "name": "Playstation Smart Plug", + "vendor": "Plugwise", + "sensors": { + "electricity_consumed": 82.6, + "electricity_consumed_interval": 8.6, + "electricity_produced": 0.0, + "electricity_produced_interval": 0.0, + }, + "switches": {"relay": True, "lock": False}, + }, + "78d1126fc4c743db81b61c20e88342a7": { + "class": "central_heating_pump", + "fw": "2019-06-21T02:00:00+02:00", + "location": "c50f167537524366a5af7aa3942feb1e", + "model": "Plug", + "name": "CV Pomp", + "vendor": "Plugwise", + "sensors": { + "electricity_consumed": 35.6, + "electricity_consumed_interval": 7.37, + "electricity_produced": 0.0, + "electricity_produced_interval": 0.0, + }, + "switches": {"relay": True}, + }, + "90986d591dcd426cae3ec3e8111ff730": { + "class": "heater_central", + "fw": None, + "location": "1f9dcf83fd4e4b66b72ff787957bfe5d", + "model": "Unknown", + "name": "OnOff", + "vendor": None, + "cooling_active": False, + "heating_state": True, + "sensors": { + "water_temperature": 70.0, + "intended_boiler_temperature": 70.0, + "modulation_level": 1, + "device_state": "heating", + }, + }, + "cd0ddb54ef694e11ac18ed1cbce5dbbd": { + "class": "vcr", + "fw": "2019-06-21T02:00:00+02:00", + "location": "cd143c07248f491493cea0533bc3d669", + "model": "Plug", + "name": "NAS", + "vendor": "Plugwise", + "sensors": { + "electricity_consumed": 16.5, + "electricity_consumed_interval": 0.5, + "electricity_produced": 0.0, + "electricity_produced_interval": 0.0, + }, + "switches": {"relay": True, "lock": True}, + }, + "4a810418d5394b3f82727340b91ba740": { + "class": "router", + "fw": "2019-06-21T02:00:00+02:00", + "location": "cd143c07248f491493cea0533bc3d669", + "model": "Plug", + "name": "USG Smart Plug", + "vendor": "Plugwise", + "sensors": { + "electricity_consumed": 8.5, + "electricity_consumed_interval": 0.0, + "electricity_produced": 0.0, + "electricity_produced_interval": 0.0, + }, + "switches": {"relay": True, "lock": True}, + }, + "02cf28bfec924855854c544690a609ef": { + "class": "vcr", + "fw": "2019-06-21T02:00:00+02:00", + "location": "cd143c07248f491493cea0533bc3d669", + "model": "Plug", + "name": "NVR", + "vendor": "Plugwise", + "sensors": { + "electricity_consumed": 34.0, + "electricity_consumed_interval": 9.15, + "electricity_produced": 0.0, + "electricity_produced_interval": 0.0, + }, + "switches": {"relay": True, "lock": True}, + }, + "a28f588dc4a049a483fd03a30361ad3a": { + "class": "settop", + "fw": "2019-06-21T02:00:00+02:00", + "location": "cd143c07248f491493cea0533bc3d669", + "model": "Plug", + "name": "Fibaro HC2", + "vendor": "Plugwise", + "sensors": { + "electricity_consumed": 12.5, + "electricity_consumed_interval": 3.8, + "electricity_produced": 0.0, + "electricity_produced_interval": 0.0, + }, + "switches": {"relay": True, "lock": True}, + }, + "6a3bf693d05e48e0b460c815a4fdd09d": { + "class": "zone_thermostat", + "fw": "2016-10-27T02:00:00+02:00", + "location": "82fa13f017d240daa0d0ea1775420f24", + "model": "Lisa", + "name": "Zone Thermostat Jessie", + "vendor": "Plugwise", + "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "active_preset": "asleep", + "presets": { + "home": [20.0, 22.0], + "asleep": [17.0, 24.0], + "away": [15.0, 25.0], + "vacation": [15.0, 28.0], + "no_frost": [10.0, 30.0], + }, + "available_schedules": [ + "CV Roan", + "Bios Schema met Film Avond", + "GF7 Woonkamer", + "Badkamer Schema", + "CV Jessie", + ], + "selected_schedule": "CV Jessie", + "schedule_temperature": 15.0, + "last_used": "CV Jessie", + "mode": "auto", + "sensors": {"temperature": 17.2, "setpoint": 15.0, "battery": 37}, + }, + "680423ff840043738f42cc7f1ff97a36": { + "class": "thermo_sensor", + "fw": "2019-03-27T01:00:00+01:00", + "location": "08963fec7c53423ca5680aa4cb502c63", + "model": "Tom/Floor", + "name": "Thermostatic Radiator Badkamer", + "vendor": "Plugwise", + "sensors": { + "temperature": 19.1, + "setpoint": 14.0, + "battery": 51, + "temperature_difference": -0.4, + "valve_position": 0.0, + }, + }, + "f1fee6043d3642a9b0a65297455f008e": { + "class": "zone_thermostat", + "fw": "2016-10-27T02:00:00+02:00", + "location": "08963fec7c53423ca5680aa4cb502c63", + "model": "Lisa", + "name": "Zone Thermostat Badkamer", + "vendor": "Plugwise", + "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "active_preset": "away", + "presets": { + "home": [20.0, 22.0], + "asleep": [17.0, 24.0], + "away": [15.0, 25.0], + "vacation": [15.0, 28.0], + "no_frost": [10.0, 30.0], + }, + "available_schedules": [ + "CV Roan", + "Bios Schema met Film Avond", + "GF7 Woonkamer", + "Badkamer Schema", + "CV Jessie", + ], + "selected_schedule": "Badkamer Schema", + "schedule_temperature": 15.0, + "last_used": "Badkamer Schema", + "mode": "auto", + "sensors": {"temperature": 18.9, "setpoint": 14.0, "battery": 92}, + }, + "675416a629f343c495449970e2ca37b5": { + "class": "router", + "fw": "2019-06-21T02:00:00+02:00", + "location": "cd143c07248f491493cea0533bc3d669", + "model": "Plug", + "name": "Ziggo Modem", + "vendor": "Plugwise", + "sensors": { + "electricity_consumed": 12.2, + "electricity_consumed_interval": 2.97, + "electricity_produced": 0.0, + "electricity_produced_interval": 0.0, + }, + "switches": {"relay": True, "lock": True}, + }, + "e7693eb9582644e5b865dba8d4447cf1": { + "class": "thermostatic_radiator_valve", + "fw": "2019-03-27T01:00:00+01:00", + "location": "446ac08dd04d4eff8ac57489757b7314", + "model": "Tom/Floor", + "name": "CV Kraan Garage", + "vendor": "Plugwise", + "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "active_preset": "no_frost", + "presets": { + "home": [20.0, 22.0], + "asleep": [17.0, 24.0], + "away": [15.0, 25.0], + "vacation": [15.0, 28.0], + "no_frost": [10.0, 30.0], + }, + "available_schedules": [ + "CV Roan", + "Bios Schema met Film Avond", + "GF7 Woonkamer", + "Badkamer Schema", + "CV Jessie", + ], + "selected_schedule": "None", + "schedule_temperature": 15.0, + "last_used": "Badkamer Schema", + "mode": "heat", + "sensors": { + "temperature": 15.6, + "setpoint": 5.5, + "battery": 68, + "temperature_difference": 0.0, + "valve_position": 0.0, + }, + }, + }, + } From f74706a2651ec3d059aa1033cb55c787cc471bd4 Mon Sep 17 00:00:00 2001 From: Eduard van Valkenburg Date: Tue, 8 Feb 2022 12:56:24 +0100 Subject: [PATCH 0423/1098] Bump azure-eventhub to 5.7.0 (#66061) --- homeassistant/components/azure_event_hub/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/azure_event_hub/manifest.json b/homeassistant/components/azure_event_hub/manifest.json index 59c589931f42f7..c70b0855b6d80f 100644 --- a/homeassistant/components/azure_event_hub/manifest.json +++ b/homeassistant/components/azure_event_hub/manifest.json @@ -2,7 +2,7 @@ "domain": "azure_event_hub", "name": "Azure Event Hub", "documentation": "https://www.home-assistant.io/integrations/azure_event_hub", - "requirements": ["azure-eventhub==5.5.0"], + "requirements": ["azure-eventhub==5.7.0"], "codeowners": ["@eavanvalkenburg"], "iot_class": "cloud_push", "config_flow": true, diff --git a/requirements_all.txt b/requirements_all.txt index a1e36db3cc50d1..a3efffdaa7638a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -377,7 +377,7 @@ av==8.1.0 axis==44 # homeassistant.components.azure_event_hub -azure-eventhub==5.5.0 +azure-eventhub==5.7.0 # homeassistant.components.azure_service_bus azure-servicebus==0.50.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ef1086118bf765..cf0136a857010d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -270,7 +270,7 @@ av==8.1.0 axis==44 # homeassistant.components.azure_event_hub -azure-eventhub==5.5.0 +azure-eventhub==5.7.0 # homeassistant.components.homekit base36==0.1.1 From dcab9a19d610d81b6cbbe21fee4d5a5e586cef6a Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Tue, 8 Feb 2022 04:05:35 -0800 Subject: [PATCH 0424/1098] Remove Overkiz switch platform todo and add 2 devices (#66069) --- homeassistant/components/overkiz/const.py | 2 ++ homeassistant/components/overkiz/switch.py | 24 +++++++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/overkiz/const.py b/homeassistant/components/overkiz/const.py index ba5470724cd3a0..a88706a428f94a 100644 --- a/homeassistant/components/overkiz/const.py +++ b/homeassistant/components/overkiz/const.py @@ -61,6 +61,8 @@ UIWidget.RTD_INDOOR_SIREN: Platform.SWITCH, # widgetName, uiClass is Siren (not supported) UIWidget.RTD_OUTDOOR_SIREN: Platform.SWITCH, # widgetName, uiClass is Siren (not supported) UIWidget.RTS_GENERIC: Platform.COVER, # widgetName, uiClass is Generic (not supported) + UIWidget.STATELESS_ALARM_CONTROLLER: Platform.SWITCH, # widgetName, uiClass is Alarm (not supported) + UIWidget.STATELESS_EXTERIOR_HEATING: Platform.SWITCH, # widgetName, uiClass is ExteriorHeatingSystem (not supported) } # Map Overkiz camelCase to Home Assistant snake_case for translation diff --git a/homeassistant/components/overkiz/switch.py b/homeassistant/components/overkiz/switch.py index 969a9508943040..8b3222cf44234a 100644 --- a/homeassistant/components/overkiz/switch.py +++ b/homeassistant/components/overkiz/switch.py @@ -30,13 +30,14 @@ class OverkizSwitchDescriptionMixin: turn_on: Callable[[Callable[..., Awaitable[None]]], Awaitable[None]] turn_off: Callable[[Callable[..., Awaitable[None]]], Awaitable[None]] - is_on: Callable[[Callable[[str], OverkizStateType]], bool] @dataclass class OverkizSwitchDescription(SwitchEntityDescription, OverkizSwitchDescriptionMixin): """Class to describe an Overkiz switch.""" + is_on: Callable[[Callable[[str], OverkizStateType]], bool] | None = None + SWITCH_DESCRIPTIONS: list[OverkizSwitchDescription] = [ OverkizSwitchDescription( @@ -75,14 +76,24 @@ class OverkizSwitchDescription(SwitchEntityDescription, OverkizSwitchDescription turn_on=lambda execute_command: execute_command(OverkizCommand.ON), turn_off=lambda execute_command: execute_command(OverkizCommand.OFF), icon="mdi:bell", - is_on=lambda select_state: False, # Remove when is_on in SwitchEntity doesn't require a bool value anymore ), OverkizSwitchDescription( key=UIWidget.RTD_OUTDOOR_SIREN, turn_on=lambda execute_command: execute_command(OverkizCommand.ON), turn_off=lambda execute_command: execute_command(OverkizCommand.OFF), icon="mdi:bell", - is_on=lambda select_state: False, # Remove when is_on in SwitchEntity doesn't require a bool value anymore + ), + OverkizSwitchDescription( + key=UIWidget.STATELESS_ALARM_CONTROLLER, + turn_on=lambda execute_command: execute_command(OverkizCommand.ALARM_ON), + turn_off=lambda execute_command: execute_command(OverkizCommand.ALARM_OFF), + icon="mdi:shield-lock", + ), + OverkizSwitchDescription( + key=UIWidget.STATELESS_EXTERIOR_HEATING, + turn_on=lambda execute_command: execute_command(OverkizCommand.ON), + turn_off=lambda execute_command: execute_command(OverkizCommand.OFF), + icon="mdi:radiator", ), ] @@ -121,9 +132,12 @@ class OverkizSwitch(OverkizDescriptiveEntity, SwitchEntity): entity_description: OverkizSwitchDescription @property - def is_on(self) -> bool: + def is_on(self) -> bool | None: """Return True if entity is on.""" - return self.entity_description.is_on(self.executor.select_state) + if self.entity_description.is_on: + return self.entity_description.is_on(self.executor.select_state) + + return None async def async_turn_on(self, **kwargs: Any) -> None: """Turn the entity on.""" From 2df5060d803da092cbd013b81e148b381db318b6 Mon Sep 17 00:00:00 2001 From: Tiernan Date: Tue, 8 Feb 2022 23:26:36 +1000 Subject: [PATCH 0425/1098] Fix TOD incorrectly determining the state between sunrise and sunset (#65884) * Fix TOD component incorrectly determining the state between sunrise and sunset (#30199) * TOD fix * Comment added * Review * Review * Review * Update time after day fix workaround for compatibility with current version. Only apply fix when using times and not when using sun events. Add unit test for behaviour. Co-authored-by: Nikolay Vasilchuk --- homeassistant/components/tod/binary_sensor.py | 15 +++++++++++++++ tests/components/tod/test_binary_sensor.py | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/homeassistant/components/tod/binary_sensor.py b/homeassistant/components/tod/binary_sensor.py index 5b4a6e12459c66..6f14d5735fb01c 100644 --- a/homeassistant/components/tod/binary_sensor.py +++ b/homeassistant/components/tod/binary_sensor.py @@ -161,6 +161,21 @@ def _calculate_boudary_time(self): self._time_before = before_event_date + # We are calculating the _time_after value assuming that it will happen today + # But that is not always true, e.g. after 23:00, before 12:00 and now is 10:00 + # If _time_before and _time_after are ahead of nowutc: + # _time_before is set to 12:00 next day + # _time_after is set to 23:00 today + # nowutc is set to 10:00 today + if ( + not is_sun_event(self._after) + and self._time_after > nowutc + and self._time_before > nowutc + timedelta(days=1) + ): + # remove one day from _time_before and _time_after + self._time_after -= timedelta(days=1) + self._time_before -= timedelta(days=1) + # Add offset to utc boundaries according to the configuration self._time_after += self._after_offset self._time_before += self._before_offset diff --git a/tests/components/tod/test_binary_sensor.py b/tests/components/tod/test_binary_sensor.py index ef8088d6aabdcc..06f29436d6e059 100644 --- a/tests/components/tod/test_binary_sensor.py +++ b/tests/components/tod/test_binary_sensor.py @@ -163,6 +163,25 @@ async def test_midnight_turnover_before_midnight_outside_period(hass): assert state.state == STATE_OFF +async def test_after_happens_tomorrow(hass): + """Test when both before and after are in the future, and after is later than before.""" + test_time = datetime(2019, 1, 10, 10, 00, 0, tzinfo=dt_util.UTC) + config = { + "binary_sensor": [ + {"platform": "tod", "name": "Night", "after": "23:00", "before": "12:00"} + ] + } + with patch( + "homeassistant.components.tod.binary_sensor.dt_util.utcnow", + return_value=test_time, + ): + await async_setup_component(hass, "binary_sensor", config) + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.night") + assert state.state == STATE_ON + + async def test_midnight_turnover_after_midnight_outside_period(hass): """Test midnight turnover setting before midnight inside period .""" test_time = datetime(2019, 1, 10, 20, 0, 0, tzinfo=dt_util.UTC) From 37525ae8c38abae3b0cd52db018ad0b46e465a4e Mon Sep 17 00:00:00 2001 From: AJ Schmidt Date: Tue, 8 Feb 2022 09:14:33 -0500 Subject: [PATCH 0426/1098] Remove AlarmDecoder Codeowner (#66078) --- CODEOWNERS | 2 -- homeassistant/components/alarmdecoder/manifest.json | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index b78d12939a8fa4..973837d3ee5e7f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -43,8 +43,6 @@ homeassistant/components/airtouch4/* @LonePurpleWolf tests/components/airtouch4/* @LonePurpleWolf homeassistant/components/airvisual/* @bachya tests/components/airvisual/* @bachya -homeassistant/components/alarmdecoder/* @ajschmidt8 -tests/components/alarmdecoder/* @ajschmidt8 homeassistant/components/alexa/* @home-assistant/cloud @ochlocracy tests/components/alexa/* @home-assistant/cloud @ochlocracy homeassistant/components/almond/* @gcampax @balloob diff --git a/homeassistant/components/alarmdecoder/manifest.json b/homeassistant/components/alarmdecoder/manifest.json index 0acb39801b541c..c1f0401e2b0305 100644 --- a/homeassistant/components/alarmdecoder/manifest.json +++ b/homeassistant/components/alarmdecoder/manifest.json @@ -3,7 +3,7 @@ "name": "AlarmDecoder", "documentation": "https://www.home-assistant.io/integrations/alarmdecoder", "requirements": ["adext==0.4.2"], - "codeowners": ["@ajschmidt8"], + "codeowners": [], "config_flow": true, "iot_class": "local_push", "loggers": ["adext", "alarmdecoder"] From 46b7b1ffb39c9da00fade46931e5eedd3f2d6d6e Mon Sep 17 00:00:00 2001 From: Pablo Ovelleiro Corral Date: Tue, 8 Feb 2022 15:20:50 +0100 Subject: [PATCH 0427/1098] Increase timeout for InfluxDB v2 connections (#63885) * Update influxdb timeout * Update homeassistant/components/influxdb/const.py Co-authored-by: Mike Degatano Co-authored-by: Mike Degatano --- homeassistant/components/influxdb/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/influxdb/const.py b/homeassistant/components/influxdb/const.py index 77f76745ba494a..f3b0b66df54797 100644 --- a/homeassistant/components/influxdb/const.py +++ b/homeassistant/components/influxdb/const.py @@ -72,7 +72,7 @@ EVENT_NEW_STATE = "new_state" DOMAIN = "influxdb" API_VERSION_2 = "2" -TIMEOUT = 5 +TIMEOUT = 10 # seconds RETRY_DELAY = 20 QUEUE_BACKLOG_SECONDS = 30 RETRY_INTERVAL = 60 # seconds From 473834acd2bbf5cc4b33e4dbb7ae4ad4a60d8f10 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 8 Feb 2022 15:23:11 +0100 Subject: [PATCH 0428/1098] Add myself as codeowner to Plugwise (#66080) --- CODEOWNERS | 4 ++-- homeassistant/components/plugwise/manifest.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 973837d3ee5e7f..c8cc32b2d35795 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -712,8 +712,8 @@ homeassistant/components/plaato/* @JohNan tests/components/plaato/* @JohNan homeassistant/components/plex/* @jjlawren tests/components/plex/* @jjlawren -homeassistant/components/plugwise/* @CoMPaTech @bouwew @brefra -tests/components/plugwise/* @CoMPaTech @bouwew @brefra +homeassistant/components/plugwise/* @CoMPaTech @bouwew @brefra @frenck +tests/components/plugwise/* @CoMPaTech @bouwew @brefra @frenck homeassistant/components/plum_lightpad/* @ColinHarrington @prystupa tests/components/plum_lightpad/* @ColinHarrington @prystupa homeassistant/components/point/* @fredrike diff --git a/homeassistant/components/plugwise/manifest.json b/homeassistant/components/plugwise/manifest.json index 7dc3bdd9b1ddb8..977838546145be 100644 --- a/homeassistant/components/plugwise/manifest.json +++ b/homeassistant/components/plugwise/manifest.json @@ -3,7 +3,7 @@ "name": "Plugwise", "documentation": "https://www.home-assistant.io/integrations/plugwise", "requirements": ["plugwise==0.16.2"], - "codeowners": ["@CoMPaTech", "@bouwew", "@brefra"], + "codeowners": ["@CoMPaTech", "@bouwew", "@brefra", "@frenck"], "zeroconf": ["_plugwise._tcp.local."], "config_flow": true, "iot_class": "local_polling", From 59c7af0f80d7abb05616e4632b707c8b14964715 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 8 Feb 2022 16:10:17 +0100 Subject: [PATCH 0429/1098] Update base image to 2022.02.0 (#66082) --- build.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.yaml b/build.yaml index 1d0e18c79ea885..96f73fda0f3c44 100644 --- a/build.yaml +++ b/build.yaml @@ -1,11 +1,11 @@ image: homeassistant/{arch}-homeassistant shadow_repository: ghcr.io/home-assistant build_from: - aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2021.09.0 - armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2021.09.0 - armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2021.09.0 - amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2021.09.0 - i386: ghcr.io/home-assistant/i386-homeassistant-base:2021.09.0 + aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2022.02.0 + armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2022.02.0 + armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2022.02.0 + amd64: ghcr.io/home-assistant/amd64-homeassistant-base:22022.02.0 + i386: ghcr.io/home-assistant/i386-homeassistant-base:2022.02.0 codenotary: signer: notary@home-assistant.io base_image: notary@home-assistant.io From b6ad79e2b893683e6788336e850563f8325fcfc1 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Tue, 8 Feb 2022 07:11:05 -0800 Subject: [PATCH 0430/1098] Update PyOverkiz to 1.3.4 (#66076) --- homeassistant/components/overkiz/const.py | 21 +++++++++---------- .../components/overkiz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/overkiz/const.py b/homeassistant/components/overkiz/const.py index a88706a428f94a..e6c55af0749d3a 100644 --- a/homeassistant/components/overkiz/const.py +++ b/homeassistant/components/overkiz/const.py @@ -5,8 +5,7 @@ import logging from typing import Final -from pyoverkiz.enums import UIClass -from pyoverkiz.enums.ui import UIWidget +from pyoverkiz.enums import OverkizCommandParam, UIClass, UIWidget from homeassistant.const import Platform @@ -67,13 +66,13 @@ # Map Overkiz camelCase to Home Assistant snake_case for translation OVERKIZ_STATE_TO_TRANSLATION: dict[str, str] = { - "externalGateway": "external_gateway", - "localUser": "local_user", - "lowBattery": "low_battery", - "LSC": "lsc", - "maintenanceRequired": "maintenance_required", - "noDefect": "no_defect", - "SAAC": "saac", - "SFC": "sfc", - "UPS": "ups", + OverkizCommandParam.EXTERNAL_GATEWAY: "external_gateway", + OverkizCommandParam.LOCAL_USER: "local_user", + OverkizCommandParam.LOW_BATTERY: "low_battery", + OverkizCommandParam.LSC: "lsc", + OverkizCommandParam.MAINTENANCE_REQUIRED: "maintenance_required", + OverkizCommandParam.NO_DEFECT: "no_defect", + OverkizCommandParam.SAAC: "saac", + OverkizCommandParam.SFC: "sfc", + OverkizCommandParam.UPS: "ups", } diff --git a/homeassistant/components/overkiz/manifest.json b/homeassistant/components/overkiz/manifest.json index 47c5c45f2ad7ed..1c08d47622d347 100644 --- a/homeassistant/components/overkiz/manifest.json +++ b/homeassistant/components/overkiz/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/overkiz", "requirements": [ - "pyoverkiz==1.3.2" + "pyoverkiz==1.3.4" ], "zeroconf": [ { diff --git a/requirements_all.txt b/requirements_all.txt index a3efffdaa7638a..a09b44aaf34485 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1746,7 +1746,7 @@ pyotgw==1.1b1 pyotp==2.6.0 # homeassistant.components.overkiz -pyoverkiz==1.3.2 +pyoverkiz==1.3.4 # homeassistant.components.openweathermap pyowm==3.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cf0136a857010d..f6a835c8558722 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1112,7 +1112,7 @@ pyotgw==1.1b1 pyotp==2.6.0 # homeassistant.components.overkiz -pyoverkiz==1.3.2 +pyoverkiz==1.3.4 # homeassistant.components.openweathermap pyowm==3.2.0 From a500205545db6354f082edd7bcf9b80c9c657713 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 8 Feb 2022 16:27:44 +0100 Subject: [PATCH 0431/1098] Fix typo in base image tag (#66087) --- build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.yaml b/build.yaml index 96f73fda0f3c44..3ced7b8d742b14 100644 --- a/build.yaml +++ b/build.yaml @@ -4,7 +4,7 @@ build_from: aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2022.02.0 armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2022.02.0 armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2022.02.0 - amd64: ghcr.io/home-assistant/amd64-homeassistant-base:22022.02.0 + amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2022.02.0 i386: ghcr.io/home-assistant/i386-homeassistant-base:2022.02.0 codenotary: signer: notary@home-assistant.io From 6f46d9830846052b0cf09217ef3b8d51e342c834 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Tue, 8 Feb 2022 08:40:06 -0800 Subject: [PATCH 0432/1098] Bump python-nest to 4.2.0 for python 3.10 fixes (#66090) --- homeassistant/components/nest/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/nest/manifest.json b/homeassistant/components/nest/manifest.json index 12ee32d532f2e0..19468fcf686734 100644 --- a/homeassistant/components/nest/manifest.json +++ b/homeassistant/components/nest/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "dependencies": ["ffmpeg", "http", "media_source"], "documentation": "https://www.home-assistant.io/integrations/nest", - "requirements": ["python-nest==4.1.0", "google-nest-sdm==1.6.0"], + "requirements": ["python-nest==4.2.0", "google-nest-sdm==1.6.0"], "codeowners": ["@allenporter"], "quality_scale": "platinum", "dhcp": [ diff --git a/requirements_all.txt b/requirements_all.txt index a09b44aaf34485..f045c11ad3ab0a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1954,7 +1954,7 @@ python-mpd2==3.0.4 python-mystrom==1.1.2 # homeassistant.components.nest -python-nest==4.1.0 +python-nest==4.2.0 # homeassistant.components.ozw python-openzwave-mqtt[mqtt-client]==1.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f6a835c8558722..13a39da4c4e7e1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1212,7 +1212,7 @@ python-kasa==0.4.1 python-miio==0.5.9.2 # homeassistant.components.nest -python-nest==4.1.0 +python-nest==4.2.0 # homeassistant.components.ozw python-openzwave-mqtt[mqtt-client]==1.4.0 From 199c8fef40df73d46a9a9c75005c820bf580c22b Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Tue, 8 Feb 2022 08:49:38 -0800 Subject: [PATCH 0433/1098] Fix MyFox Camera Shutter entity in Overkiz integration (#66088) --- homeassistant/components/overkiz/const.py | 2 +- homeassistant/components/overkiz/switch.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/overkiz/const.py b/homeassistant/components/overkiz/const.py index e6c55af0749d3a..370e56feeebb7f 100644 --- a/homeassistant/components/overkiz/const.py +++ b/homeassistant/components/overkiz/const.py @@ -56,7 +56,7 @@ UIClass.VENETIAN_BLIND: Platform.COVER, UIClass.WINDOW: Platform.COVER, UIWidget.DOMESTIC_HOT_WATER_TANK: Platform.SWITCH, # widgetName, uiClass is WaterHeatingSystem (not supported) - UIWidget.MY_FOX_SECURITY_CAMERA: Platform.COVER, # widgetName, uiClass is Camera (not supported) + UIWidget.MY_FOX_SECURITY_CAMERA: Platform.SWITCH, # widgetName, uiClass is Camera (not supported) UIWidget.RTD_INDOOR_SIREN: Platform.SWITCH, # widgetName, uiClass is Siren (not supported) UIWidget.RTD_OUTDOOR_SIREN: Platform.SWITCH, # widgetName, uiClass is Siren (not supported) UIWidget.RTS_GENERIC: Platform.COVER, # widgetName, uiClass is Generic (not supported) diff --git a/homeassistant/components/overkiz/switch.py b/homeassistant/components/overkiz/switch.py index 8b3222cf44234a..8fd38816bcd290 100644 --- a/homeassistant/components/overkiz/switch.py +++ b/homeassistant/components/overkiz/switch.py @@ -17,6 +17,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import HomeAssistantOverkizData @@ -95,6 +96,18 @@ class OverkizSwitchDescription(SwitchEntityDescription, OverkizSwitchDescription turn_off=lambda execute_command: execute_command(OverkizCommand.OFF), icon="mdi:radiator", ), + OverkizSwitchDescription( + key=UIWidget.MY_FOX_SECURITY_CAMERA, + name="Camera Shutter", + turn_on=lambda execute_command: execute_command(OverkizCommand.OPEN), + turn_off=lambda execute_command: execute_command(OverkizCommand.CLOSE), + icon="mdi:camera-lock", + is_on=lambda select_state: ( + select_state(OverkizState.MYFOX_SHUTTER_STATUS) + == OverkizCommandParam.OPENED + ), + entity_category=EntityCategory.CONFIG, + ), ] SUPPORTED_DEVICES = { From 4efebcb86c92a1ebaae74a1bd209d723b23c7d2b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 8 Feb 2022 19:08:01 +0100 Subject: [PATCH 0434/1098] Use upstream device information for Plugwise (#66074) --- .../components/plugwise/binary_sensor.py | 36 +++------- homeassistant/components/plugwise/climate.py | 15 ++--- homeassistant/components/plugwise/entity.py | 67 +++++++------------ homeassistant/components/plugwise/sensor.py | 29 +++----- homeassistant/components/plugwise/switch.py | 15 ++--- .../components/plugwise/test_binary_sensor.py | 20 +++--- tests/components/plugwise/test_sensor.py | 8 +-- 7 files changed, 71 insertions(+), 119 deletions(-) diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index 44d5e79ce166ad..1f5cfacb37e2d3 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -1,6 +1,4 @@ """Plugwise Binary Sensor component for Home Assistant.""" -from plugwise.smile import Smile - from homeassistant.components.binary_sensor import ( BinarySensorEntity, BinarySensorEntityDescription, @@ -45,37 +43,32 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Smile binary_sensors from a config entry.""" - api: Smile = hass.data[DOMAIN][config_entry.entry_id]["api"] coordinator: PlugwiseDataUpdateCoordinator = hass.data[DOMAIN][ config_entry.entry_id ][COORDINATOR] entities: list[PlugwiseBinarySensorEntity] = [] - for device_id, device_properties in coordinator.data.devices.items(): - if device_properties["class"] == "heater_central": + for device_id, device in coordinator.data.devices.items(): + if device["class"] == "heater_central": for description in BINARY_SENSORS: if ( - "binary_sensors" not in device_properties - or description.key not in device_properties["binary_sensors"] + "binary_sensors" not in device + or description.key not in device["binary_sensors"] ): continue entities.append( PlugwiseBinarySensorEntity( - api, coordinator, - device_properties["name"], device_id, description, ) ) - if device_properties["class"] == "gateway": + if device["class"] == "gateway": entities.append( PlugwiseNotifyBinarySensorEntity( - api, coordinator, - device_properties["name"], device_id, BinarySensorEntityDescription( key="plugwise_notification", @@ -92,25 +85,18 @@ class PlugwiseBinarySensorEntity(PlugwiseEntity, BinarySensorEntity): def __init__( self, - api: Smile, coordinator: PlugwiseDataUpdateCoordinator, - name: str, - dev_id: str, + device_id: str, description: BinarySensorEntityDescription, ) -> None: """Initialise the binary_sensor.""" - super().__init__(api, coordinator, name, dev_id) + super().__init__(coordinator, device_id) self.entity_description = description self._attr_is_on = False - self._attr_unique_id = f"{dev_id}-{description.key}" - - if dev_id == coordinator.data.gateway["heater_id"]: - self._entity_name = "Auxiliary" - - self._name = f"{self._entity_name} {description.name}" - - if dev_id == coordinator.data.gateway["gateway_id"]: - self._entity_name = f"Smile {self._entity_name}" + self._attr_unique_id = f"{device_id}-{description.key}" + self._attr_name = ( + f"{coordinator.data.devices[device_id].get('name', '')} {description.name}" + ).lstrip() @callback def _async_process_data(self) -> None: diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index 49c73049a9abd7..1cfb4ab7c6712f 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -52,17 +52,14 @@ async def async_setup_entry( "zone_thermostat", "thermostatic_radiator_valve", ] - for device_id, device_properties in coordinator.data.devices.items(): - if device_properties["class"] not in thermostat_classes: + for device_id, device in coordinator.data.devices.items(): + if device["class"] not in thermostat_classes: continue thermostat = PlugwiseClimateEntity( api, coordinator, - device_properties["name"], device_id, - device_properties["location"], - device_properties["class"], ) entities.append(thermostat) @@ -87,18 +84,16 @@ def __init__( self, api: Smile, coordinator: PlugwiseDataUpdateCoordinator, - name: str, device_id: str, - loc_id: str, - model: str, ) -> None: """Set up the Plugwise API.""" - super().__init__(api, coordinator, name, device_id) + super().__init__(coordinator, device_id) self._attr_extra_state_attributes = {} self._attr_unique_id = f"{device_id}-climate" + self._attr_name = coordinator.data.devices[device_id].get("name") self._api = api - self._loc_id = loc_id + self._loc_id = coordinator.data.devices[device_id]["location"] self._presets = None diff --git a/homeassistant/components/plugwise/entity.py b/homeassistant/components/plugwise/entity.py index c57f9c9281fb01..2f73d38e5e30d7 100644 --- a/homeassistant/components/plugwise/entity.py +++ b/homeassistant/components/plugwise/entity.py @@ -1,14 +1,7 @@ """Generic Plugwise Entity Class.""" from __future__ import annotations -from plugwise.smile import Smile - -from homeassistant.const import ( - ATTR_CONFIGURATION_URL, - ATTR_MODEL, - ATTR_VIA_DEVICE, - CONF_HOST, -) +from homeassistant.const import ATTR_NAME, ATTR_VIA_DEVICE, CONF_HOST from homeassistant.core import callback from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -22,50 +15,38 @@ class PlugwiseEntity(CoordinatorEntity[PlugwiseData]): def __init__( self, - api: Smile, coordinator: PlugwiseDataUpdateCoordinator, - name: str, - dev_id: str, + device_id: str, ) -> None: """Initialise the gateway.""" super().__init__(coordinator) + self._dev_id = device_id - self._api = api - self._name = name - self._dev_id = dev_id - self._entity_name = self._name - - @property - def name(self) -> str | None: - """Return the name of the entity, if any.""" - return self._name - - @property - def device_info(self) -> DeviceInfo: - """Return the device information.""" - data = self.coordinator.data.devices[self._dev_id] - device_information = DeviceInfo( - identifiers={(DOMAIN, self._dev_id)}, - name=self._entity_name, - manufacturer="Plugwise", - ) - + configuration_url: str | None = None if entry := self.coordinator.config_entry: - device_information[ - ATTR_CONFIGURATION_URL - ] = f"http://{entry.data[CONF_HOST]}" - - if model := data.get("model"): - device_information[ATTR_MODEL] = model + configuration_url = f"http://{entry.data[CONF_HOST]}" + + data = coordinator.data.devices[device_id] + self._attr_device_info = DeviceInfo( + configuration_url=configuration_url, + identifiers={(DOMAIN, device_id)}, + manufacturer=data.get("vendor"), + model=data.get("model"), + name=f"Smile {coordinator.data.gateway['smile_name']}", + sw_version=data.get("fw"), + ) - if self._dev_id != self.coordinator.data.gateway["gateway_id"]: - device_information[ATTR_VIA_DEVICE] = ( - DOMAIN, - str(self.coordinator.data.gateway["gateway_id"]), + if device_id != coordinator.data.gateway["gateway_id"]: + self._attr_device_info.update( + { + ATTR_NAME: data.get("name"), + ATTR_VIA_DEVICE: ( + DOMAIN, + str(self.coordinator.data.gateway["gateway_id"]), + ), + } ) - return device_information - async def async_added_to_hass(self) -> None: """Subscribe to updates.""" self._async_process_data() diff --git a/homeassistant/components/plugwise/sensor.py b/homeassistant/components/plugwise/sensor.py index d2fd47d4b7d0fd..47b4f898c250e0 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -290,11 +290,11 @@ async def async_setup_entry( coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] entities: list[PlugwiseSensorEnity] = [] - for device_id, device_properties in coordinator.data.devices.items(): + for device_id, device in coordinator.data.devices.items(): for description in SENSORS: if ( - "sensors" not in device_properties - or device_properties["sensors"].get(description.key) is None + "sensors" not in device + or device["sensors"].get(description.key) is None ): continue @@ -302,7 +302,6 @@ async def async_setup_entry( PlugwiseSensorEnity( api, coordinator, - device_properties["name"], device_id, description, ) @@ -311,14 +310,13 @@ async def async_setup_entry( if coordinator.data.gateway["single_master_thermostat"] is False: # These sensors should actually be binary sensors. for description in INDICATE_ACTIVE_LOCAL_DEVICE_SENSORS: - if description.key not in device_properties: + if description.key not in device: continue entities.append( PlugwiseAuxSensorEntity( api, coordinator, - device_properties["name"], device_id, description, ) @@ -335,28 +333,23 @@ def __init__( self, api: Smile, coordinator: PlugwiseDataUpdateCoordinator, - name: str, device_id: str, description: SensorEntityDescription, ) -> None: """Initialise the sensor.""" - super().__init__(api, coordinator, name, device_id) + super().__init__(coordinator, device_id) self.entity_description = description + self._api = api self._attr_unique_id = f"{device_id}-{description.key}" - - if device_id == coordinator.data.gateway["heater_id"]: - self._entity_name = "Auxiliary" - - self._name = f"{self._entity_name} {description.name}" - - if device_id == coordinator.data.gateway["gateway_id"]: - self._entity_name = f"Smile {self._entity_name}" + self._attr_name = ( + f"{coordinator.data.devices[device_id].get('name', '')} {description.name}" + ).lstrip() @callback def _async_process_data(self) -> None: """Update the entity.""" if not (data := self.coordinator.data.devices.get(self._dev_id)): - LOGGER.error("Received no data for device %s", self._entity_name) + LOGGER.error("Received no data for device %s", self._dev_id) self.async_write_ha_state() return @@ -374,7 +367,7 @@ class PlugwiseAuxSensorEntity(PlugwiseSensorEnity): def _async_process_data(self) -> None: """Update the entity.""" if not (data := self.coordinator.data.devices.get(self._dev_id)): - LOGGER.error("Received no data for device %s", self._entity_name) + LOGGER.error("Received no data for device %s", self._dev_id) self.async_write_ha_state() return diff --git a/homeassistant/components/plugwise/switch.py b/homeassistant/components/plugwise/switch.py index 4e5a00e3e6ada5..6079862fe42457 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -26,18 +26,14 @@ async def async_setup_entry( coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] entities: list[PlugwiseSwitchEntity] = [] - for device_id, device_properties in coordinator.data.devices.items(): - if ( - "switches" not in device_properties - or "relay" not in device_properties["switches"] - ): + for device_id, device in coordinator.data.devices.items(): + if "switches" not in device or "relay" not in device["switches"]: continue entities.append( PlugwiseSwitchEntity( api, coordinator, - device_properties["name"], device_id, ) ) @@ -54,14 +50,15 @@ def __init__( self, api: Smile, coordinator: PlugwiseDataUpdateCoordinator, - name: str, device_id: str, ) -> None: """Set up the Plugwise API.""" - super().__init__(api, coordinator, name, device_id) + super().__init__(coordinator, device_id) + self._api = api self._attr_unique_id = f"{device_id}-plug" self._members = coordinator.data.devices[device_id].get("members") self._attr_is_on = False + self._attr_name = coordinator.data.devices[device_id].get("name") async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" @@ -93,7 +90,7 @@ async def async_turn_off(self, **kwargs: Any) -> None: def _async_process_data(self) -> None: """Update the data from the Plugs.""" if not (data := self.coordinator.data.devices.get(self._dev_id)): - LOGGER.error("Received no data for device %s", self._name) + LOGGER.error("Received no data for device %s", self._dev_id) self.async_write_ha_state() return diff --git a/tests/components/plugwise/test_binary_sensor.py b/tests/components/plugwise/test_binary_sensor.py index 69080b364f0f83..b2356036eb99f8 100644 --- a/tests/components/plugwise/test_binary_sensor.py +++ b/tests/components/plugwise/test_binary_sensor.py @@ -11,11 +11,11 @@ async def test_anna_climate_binary_sensor_entities(hass, mock_smile_anna): entry = await async_init_integration(hass, mock_smile_anna) assert entry.state is ConfigEntryState.LOADED - state = hass.states.get("binary_sensor.auxiliary_secondary_boiler_state") - assert str(state.state) == STATE_OFF + state = hass.states.get("binary_sensor.opentherm_secondary_boiler_state") + assert state.state == STATE_OFF - state = hass.states.get("binary_sensor.auxiliary_dhw_state") - assert str(state.state) == STATE_OFF + state = hass.states.get("binary_sensor.opentherm_dhw_state") + assert state.state == STATE_OFF async def test_anna_climate_binary_sensor_change(hass, mock_smile_anna): @@ -23,18 +23,18 @@ async def test_anna_climate_binary_sensor_change(hass, mock_smile_anna): entry = await async_init_integration(hass, mock_smile_anna) assert entry.state is ConfigEntryState.LOADED - hass.states.async_set("binary_sensor.auxiliary_dhw_state", STATE_ON, {}) + hass.states.async_set("binary_sensor.opentherm_dhw_state", STATE_ON, {}) await hass.async_block_till_done() - state = hass.states.get("binary_sensor.auxiliary_dhw_state") - assert str(state.state) == STATE_ON + state = hass.states.get("binary_sensor.opentherm_dhw_state") + assert state.state == STATE_ON await hass.helpers.entity_component.async_update_entity( - "binary_sensor.auxiliary_dhw_state" + "binary_sensor.opentherm_dhw_state" ) - state = hass.states.get("binary_sensor.auxiliary_dhw_state") - assert str(state.state) == STATE_OFF + state = hass.states.get("binary_sensor.opentherm_dhw_state") + assert state.state == STATE_OFF async def test_adam_climate_binary_sensor_change(hass, mock_smile_adam): diff --git a/tests/components/plugwise/test_sensor.py b/tests/components/plugwise/test_sensor.py index 3498a7a8cf1c2a..47cbea1a8bc9cc 100644 --- a/tests/components/plugwise/test_sensor.py +++ b/tests/components/plugwise/test_sensor.py @@ -17,7 +17,7 @@ async def test_adam_climate_sensor_entities(hass, mock_smile_adam): state = hass.states.get("sensor.cv_pomp_electricity_consumed") assert float(state.state) == 35.6 - state = hass.states.get("sensor.auxiliary_water_temperature") + state = hass.states.get("sensor.onoff_water_temperature") assert float(state.state) == 70.0 state = hass.states.get("sensor.cv_pomp_electricity_consumed_interval") @@ -36,10 +36,10 @@ async def test_anna_as_smt_climate_sensor_entities(hass, mock_smile_anna): entry = await async_init_integration(hass, mock_smile_anna) assert entry.state is ConfigEntryState.LOADED - state = hass.states.get("sensor.auxiliary_outdoor_temperature") + state = hass.states.get("sensor.opentherm_outdoor_temperature") assert float(state.state) == 3.0 - state = hass.states.get("sensor.auxiliary_water_temperature") + state = hass.states.get("sensor.opentherm_water_temperature") assert float(state.state) == 29.1 state = hass.states.get("sensor.anna_illuminance") @@ -52,7 +52,7 @@ async def test_anna_climate_sensor_entities(hass, mock_smile_anna): entry = await async_init_integration(hass, mock_smile_anna) assert entry.state is ConfigEntryState.LOADED - state = hass.states.get("sensor.auxiliary_outdoor_temperature") + state = hass.states.get("sensor.opentherm_outdoor_temperature") assert float(state.state) == 3.0 From a7fd477c641eda40bda767a8f395ce42e4abf9a6 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Tue, 8 Feb 2022 12:17:05 -0600 Subject: [PATCH 0435/1098] Refactor Sonos polling (#65722) * Refactor Sonos polling Explicitly rename fallback polling Catch soco exceptions centrally where possible Create SonosPollingEntity subclass Remove unnecessary soco_error fixture argument Remove unnecessary polling in update_volume() Adjust log levels and wording Set explicit timeout on library * Adjust logging to use raised exceptions * Simplify availabiliity checks when using built-in poller * Fix typing for return values --- homeassistant/components/sonos/__init__.py | 1 + homeassistant/components/sonos/alarms.py | 9 ++--- .../components/sonos/binary_sensor.py | 4 +- homeassistant/components/sonos/const.py | 2 +- homeassistant/components/sonos/entity.py | 40 ++++++++++++++----- homeassistant/components/sonos/exception.py | 4 +- homeassistant/components/sonos/favorites.py | 2 + homeassistant/components/sonos/helpers.py | 28 +++++++------ .../components/sonos/household_coordinator.py | 7 ++-- .../components/sonos/media_player.py | 2 +- homeassistant/components/sonos/number.py | 14 +++---- homeassistant/components/sonos/sensor.py | 12 +++--- homeassistant/components/sonos/speaker.py | 17 +++----- homeassistant/components/sonos/switch.py | 32 ++++++--------- 14 files changed, 89 insertions(+), 85 deletions(-) diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py index 5f3cc285517fb5..7741ec36708fcf 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -119,6 +119,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Sonos from a config entry.""" soco_config.EVENTS_MODULE = events_asyncio + soco_config.REQUEST_TIMEOUT = 9.5 if DATA_SONOS not in hass.data: hass.data[DATA_SONOS] = SonosData() diff --git a/homeassistant/components/sonos/alarms.py b/homeassistant/components/sonos/alarms.py index dfe9e8328a45ed..e7cf05a1ff0258 100644 --- a/homeassistant/components/sonos/alarms.py +++ b/homeassistant/components/sonos/alarms.py @@ -8,11 +8,11 @@ from soco import SoCo from soco.alarms import Alarm, Alarms from soco.events_base import Event as SonosEvent -from soco.exceptions import SoCoException from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import DATA_SONOS, SONOS_ALARMS_UPDATED, SONOS_CREATE_ALARM +from .helpers import soco_error from .household_coordinator import SonosHouseholdCoordinator if TYPE_CHECKING: @@ -71,13 +71,10 @@ async def async_process_event( speaker.event_stats.process(event) await self.async_update_entities(speaker.soco, event_id) + @soco_error() def update_cache(self, soco: SoCo, update_id: int | None = None) -> bool: """Update cache of known alarms and return if cache has changed.""" - try: - self.alarms.update(soco) - except (OSError, SoCoException) as err: - _LOGGER.error("Could not update alarms using %s: %s", soco, err) - return False + self.alarms.update(soco) if update_id and self.alarms.last_id < update_id: # Skip updates if latest query result is outdated or lagging diff --git a/homeassistant/components/sonos/binary_sensor.py b/homeassistant/components/sonos/binary_sensor.py index 008e0b2cf57519..534e5f5dd02c20 100644 --- a/homeassistant/components/sonos/binary_sensor.py +++ b/homeassistant/components/sonos/binary_sensor.py @@ -64,7 +64,7 @@ def __init__(self, speaker: SonosSpeaker) -> None: self._attr_unique_id = f"{self.soco.uid}-power" self._attr_name = f"{self.speaker.zone_name} Power" - async def _async_poll(self) -> None: + async def _async_fallback_poll(self) -> None: """Poll the device for the current state.""" await self.speaker.async_poll_battery() @@ -98,7 +98,7 @@ def __init__(self, speaker: SonosSpeaker) -> None: self._attr_unique_id = f"{self.soco.uid}-microphone" self._attr_name = f"{self.speaker.zone_name} Microphone" - async def _async_poll(self) -> None: + async def _async_fallback_poll(self) -> None: """Stub for abstract class implementation. Not a pollable attribute.""" @property diff --git a/homeassistant/components/sonos/const.py b/homeassistant/components/sonos/const.py index abb0696360b7f2..2d5dbc5195a9c4 100644 --- a/homeassistant/components/sonos/const.py +++ b/homeassistant/components/sonos/const.py @@ -153,7 +153,7 @@ SONOS_CREATE_SWITCHES = "sonos_create_switches" SONOS_CREATE_LEVELS = "sonos_create_levels" SONOS_CREATE_MEDIA_PLAYER = "sonos_create_media_player" -SONOS_POLL_UPDATE = "sonos_poll_update" +SONOS_FALLBACK_POLL = "sonos_fallback_poll" SONOS_ALARMS_UPDATED = "sonos_alarms_updated" SONOS_FAVORITES_UPDATED = "sonos_favorites_updated" SONOS_SPEAKER_ACTIVITY = "sonos_speaker_activity" diff --git a/homeassistant/components/sonos/entity.py b/homeassistant/components/sonos/entity.py index 53768431a1d1db..8565fe08e9c9e7 100644 --- a/homeassistant/components/sonos/entity.py +++ b/homeassistant/components/sonos/entity.py @@ -7,7 +7,6 @@ import soco.config as soco_config from soco.core import SoCo -from soco.exceptions import SoCoException from homeassistant.components import persistent_notification import homeassistant.helpers.device_registry as dr @@ -17,10 +16,11 @@ from .const import ( DATA_SONOS, DOMAIN, + SONOS_FALLBACK_POLL, SONOS_FAVORITES_UPDATED, - SONOS_POLL_UPDATE, SONOS_STATE_UPDATED, ) +from .exception import SonosUpdateError from .speaker import SonosSpeaker SUB_FAIL_URL = "https://www.home-assistant.io/integrations/sonos/#network-requirements" @@ -43,8 +43,8 @@ async def async_added_to_hass(self) -> None: self.async_on_remove( async_dispatcher_connect( self.hass, - f"{SONOS_POLL_UPDATE}-{self.soco.uid}", - self.async_poll, + f"{SONOS_FALLBACK_POLL}-{self.soco.uid}", + self.async_fallback_poll, ) ) self.async_on_remove( @@ -66,7 +66,7 @@ async def async_will_remove_from_hass(self) -> None: """Clean up when entity is removed.""" del self.hass.data[DATA_SONOS].entity_id_mappings[self.entity_id] - async def async_poll(self, now: datetime.datetime) -> None: + async def async_fallback_poll(self, now: datetime.datetime) -> None: """Poll the entity if subscriptions fail.""" if not self.speaker.subscriptions_failed: if soco_config.EVENT_ADVERTISE_IP: @@ -86,13 +86,16 @@ async def async_poll(self, now: datetime.datetime) -> None: self.speaker.subscriptions_failed = True await self.speaker.async_unsubscribe() try: - await self._async_poll() - except (OSError, SoCoException) as ex: - _LOGGER.debug("Error connecting to %s: %s", self.entity_id, ex) + await self._async_fallback_poll() + except SonosUpdateError as err: + _LOGGER.debug("Could not fallback poll: %s", err) @abstractmethod - async def _async_poll(self) -> None: - """Poll the specific functionality. Should be implemented by platforms if needed.""" + async def _async_fallback_poll(self) -> None: + """Poll the specific functionality if subscriptions fail. + + Should be implemented by platforms if needed. + """ @property def soco(self) -> SoCo: @@ -120,3 +123,20 @@ def device_info(self) -> DeviceInfo: def available(self) -> bool: """Return whether this device is available.""" return self.speaker.available + + +class SonosPollingEntity(SonosEntity): + """Representation of a Sonos entity which may not support updating by subscriptions.""" + + @abstractmethod + def poll_state(self) -> None: + """Poll the device for the current state.""" + + def update(self) -> None: + """Update the state using the built-in entity poller.""" + if not self.available: + return + try: + self.poll_state() + except SonosUpdateError as err: + _LOGGER.debug("Could not poll: %s", err) diff --git a/homeassistant/components/sonos/exception.py b/homeassistant/components/sonos/exception.py index d7f1a2e6a9630b..bce1e3233c128f 100644 --- a/homeassistant/components/sonos/exception.py +++ b/homeassistant/components/sonos/exception.py @@ -7,5 +7,5 @@ class UnknownMediaType(BrowseError): """Unknown media type.""" -class SpeakerUnavailable(HomeAssistantError): - """Speaker is unavailable.""" +class SonosUpdateError(HomeAssistantError): + """Update failed.""" diff --git a/homeassistant/components/sonos/favorites.py b/homeassistant/components/sonos/favorites.py index 64c5b7438099a4..fd651b7740cccb 100644 --- a/homeassistant/components/sonos/favorites.py +++ b/homeassistant/components/sonos/favorites.py @@ -14,6 +14,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import SONOS_FAVORITES_UPDATED +from .helpers import soco_error from .household_coordinator import SonosHouseholdCoordinator if TYPE_CHECKING: @@ -90,6 +91,7 @@ async def async_process_event( self.last_processed_event_id = event_id await self.async_update_entities(speaker.soco, container_id) + @soco_error() def update_cache(self, soco: SoCo, update_id: int | None = None) -> bool: """Update cache of known favorites and return if cache has changed.""" new_favorites = soco.music_library.get_sonos_favorites() diff --git a/homeassistant/components/sonos/helpers.py b/homeassistant/components/sonos/helpers.py index 625b54b941e190..8e7f6fcf294a5c 100644 --- a/homeassistant/components/sonos/helpers.py +++ b/homeassistant/components/sonos/helpers.py @@ -5,17 +5,18 @@ import logging from typing import TYPE_CHECKING, TypeVar +from soco import SoCo from soco.exceptions import SoCoException, SoCoUPnPException from typing_extensions import Concatenate, ParamSpec -from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.dispatcher import dispatcher_send from .const import SONOS_SPEAKER_ACTIVITY -from .exception import SpeakerUnavailable +from .exception import SonosUpdateError if TYPE_CHECKING: from .entity import SonosEntity + from .household_coordinator import SonosHouseholdCoordinator from .speaker import SonosSpeaker UID_PREFIX = "RINCON_" @@ -23,13 +24,13 @@ _LOGGER = logging.getLogger(__name__) -_T = TypeVar("_T", "SonosSpeaker", "SonosEntity") +_T = TypeVar("_T", "SonosSpeaker", "SonosEntity", "SonosHouseholdCoordinator") _R = TypeVar("_R") _P = ParamSpec("_P") def soco_error( - errorcodes: list[str] | None = None, raise_on_err: bool = True + errorcodes: list[str] | None = None, ) -> Callable[ # type: ignore[misc] [Callable[Concatenate[_T, _P], _R]], Callable[Concatenate[_T, _P], _R | None] ]: @@ -42,10 +43,9 @@ def decorator( def wrapper(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> _R | None: """Wrap for all soco UPnP exception.""" + args_soco = next((arg for arg in args if isinstance(arg, SoCo)), None) try: result = funct(self, *args, **kwargs) - except SpeakerUnavailable: - return None except (OSError, SoCoException, SoCoUPnPException) as err: error_code = getattr(err, "error_code", None) function = funct.__qualname__ @@ -55,20 +55,22 @@ def wrapper(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> _R | None: ) return None + # In order of preference: + # * SonosSpeaker instance + # * SoCo instance passed as an arg + # * SoCo instance (as self) + speaker_or_soco = getattr(self, "speaker", args_soco or self) + zone_name = speaker_or_soco.zone_name # Prefer the entity_id if available, zone name as a fallback # Needed as SonosSpeaker instances are not entities - zone_name = getattr(self, "speaker", self).zone_name target = getattr(self, "entity_id", zone_name) message = f"Error calling {function} on {target}: {err}" - if raise_on_err: - raise HomeAssistantError(message) from err - - _LOGGER.warning(message) - return None + raise SonosUpdateError(message) from err + dispatch_soco = args_soco or self.soco dispatcher_send( self.hass, - f"{SONOS_SPEAKER_ACTIVITY}-{self.soco.uid}", + f"{SONOS_SPEAKER_ACTIVITY}-{dispatch_soco.uid}", funct.__qualname__, ) return result diff --git a/homeassistant/components/sonos/household_coordinator.py b/homeassistant/components/sonos/household_coordinator.py index f233b33827926f..0d76feae461e9b 100644 --- a/homeassistant/components/sonos/household_coordinator.py +++ b/homeassistant/components/sonos/household_coordinator.py @@ -6,12 +6,12 @@ import logging from soco import SoCo -from soco.exceptions import SoCoException from homeassistant.core import HomeAssistant from homeassistant.helpers.debounce import Debouncer from .const import DATA_SONOS +from .exception import SonosUpdateError _LOGGER = logging.getLogger(__name__) @@ -56,11 +56,10 @@ async def _async_poll(self) -> None: _LOGGER.debug("Polling %s using %s", self.class_type, speaker.soco) try: await self.async_update_entities(speaker.soco) - except (OSError, SoCoException) as err: + except SonosUpdateError as err: _LOGGER.error( - "Could not refresh %s using %s: %s", + "Could not refresh %s: %s", self.class_type, - speaker.soco, err, ) else: diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 41453117c13ea0..5f5220cd164ee9 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -283,7 +283,7 @@ def state(self) -> str: return STATE_PLAYING return STATE_IDLE - async def _async_poll(self) -> None: + async def _async_fallback_poll(self) -> None: """Retrieve latest state by polling.""" await self.hass.data[DATA_SONOS].favorites[ self.speaker.household_id diff --git a/homeassistant/components/sonos/number.py b/homeassistant/components/sonos/number.py index 2e6acd55a6678e..c9b8ec4758339a 100644 --- a/homeassistant/components/sonos/number.py +++ b/homeassistant/components/sonos/number.py @@ -12,7 +12,6 @@ from .const import SONOS_CREATE_LEVELS from .entity import SonosEntity -from .exception import SpeakerUnavailable from .helpers import soco_error from .speaker import SonosSpeaker @@ -75,16 +74,13 @@ def __init__( self.level_type = level_type self._attr_min_value, self._attr_max_value = valid_range - async def _async_poll(self) -> None: + async def _async_fallback_poll(self) -> None: """Poll the value if subscriptions are not working.""" - await self.hass.async_add_executor_job(self.update) - - @soco_error(raise_on_err=False) - def update(self) -> None: - """Fetch number state if necessary.""" - if not self.available: - raise SpeakerUnavailable + await self.hass.async_add_executor_job(self.poll_state) + @soco_error() + def poll_state(self) -> None: + """Poll the device for the current state.""" state = getattr(self.soco, self.level_type) setattr(self.speaker, self.level_type, state) diff --git a/homeassistant/components/sonos/sensor.py b/homeassistant/components/sonos/sensor.py index 4e86edc2ca3b48..5cccc40ac5f984 100644 --- a/homeassistant/components/sonos/sensor.py +++ b/homeassistant/components/sonos/sensor.py @@ -12,7 +12,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import SONOS_CREATE_AUDIO_FORMAT_SENSOR, SONOS_CREATE_BATTERY -from .entity import SonosEntity +from .entity import SonosEntity, SonosPollingEntity +from .helpers import soco_error from .speaker import SonosSpeaker _LOGGER = logging.getLogger(__name__) @@ -64,7 +65,7 @@ def __init__(self, speaker: SonosSpeaker) -> None: self._attr_unique_id = f"{self.soco.uid}-battery" self._attr_name = f"{self.speaker.zone_name} Battery" - async def _async_poll(self) -> None: + async def _async_fallback_poll(self) -> None: """Poll the device for the current state.""" await self.speaker.async_poll_battery() @@ -79,7 +80,7 @@ def available(self) -> bool: return self.speaker.available and self.speaker.power_source -class SonosAudioInputFormatSensorEntity(SonosEntity, SensorEntity): +class SonosAudioInputFormatSensorEntity(SonosPollingEntity, SensorEntity): """Representation of a Sonos audio import format sensor entity.""" _attr_entity_category = EntityCategory.DIAGNOSTIC @@ -93,9 +94,10 @@ def __init__(self, speaker: SonosSpeaker, audio_format: str) -> None: self._attr_name = f"{self.speaker.zone_name} Audio Input Format" self._attr_native_value = audio_format - def update(self) -> None: + @soco_error() + def poll_state(self) -> None: """Poll the device for the current state.""" self._attr_native_value = self.soco.soundbar_audio_input_format - async def _async_poll(self) -> None: + async def _async_fallback_poll(self) -> None: """Provide a stub for required ABC method.""" diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index ca1e5a0a91ccf2..b9844303956b28 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -16,7 +16,7 @@ from soco.core import MUSIC_SRC_LINE_IN, MUSIC_SRC_RADIO, MUSIC_SRC_TV, SoCo from soco.data_structures import DidlAudioBroadcast, DidlPlaylistContainer from soco.events_base import Event as SonosEvent, SubscriptionBase -from soco.exceptions import SoCoException, SoCoSlaveException, SoCoUPnPException +from soco.exceptions import SoCoException, SoCoUPnPException from soco.music_library import MusicLibrary from soco.plugins.plex import PlexPlugin from soco.plugins.sharelink import ShareLinkPlugin @@ -50,7 +50,7 @@ SONOS_CREATE_MEDIA_PLAYER, SONOS_CREATE_MIC_SENSOR, SONOS_CREATE_SWITCHES, - SONOS_POLL_UPDATE, + SONOS_FALLBACK_POLL, SONOS_REBOOTED, SONOS_SPEAKER_ACTIVITY, SONOS_SPEAKER_ADDED, @@ -354,7 +354,7 @@ async def async_subscribe(self) -> bool: partial( async_dispatcher_send, self.hass, - f"{SONOS_POLL_UPDATE}-{self.soco.uid}", + f"{SONOS_FALLBACK_POLL}-{self.soco.uid}", ), SCAN_INTERVAL, ) @@ -568,7 +568,7 @@ async def async_check_activity(self, now: datetime.datetime) -> None: if not self.available: return - _LOGGER.debug( + _LOGGER.warning( "No recent activity and cannot reach %s, marking unavailable", self.zone_name, ) @@ -1044,18 +1044,11 @@ def _test_groups(groups: list[list[SonosSpeaker]]) -> bool: # # Media and playback state handlers # - @soco_error(raise_on_err=False) + @soco_error() def update_volume(self) -> None: """Update information about current volume settings.""" self.volume = self.soco.volume self.muted = self.soco.mute - self.night_mode = self.soco.night_mode - self.dialog_level = self.soco.dialog_level - - try: - self.cross_fade = self.soco.cross_fade - except SoCoSlaveException: - pass @soco_error() def update_media(self, event: SonosEvent | None = None) -> None: diff --git a/homeassistant/components/sonos/switch.py b/homeassistant/components/sonos/switch.py index 4e3303db45da10..db10d9189e5f26 100644 --- a/homeassistant/components/sonos/switch.py +++ b/homeassistant/components/sonos/switch.py @@ -4,7 +4,7 @@ import datetime import logging -from soco.exceptions import SoCoException, SoCoSlaveException, SoCoUPnPException +from soco.exceptions import SoCoSlaveException, SoCoUPnPException from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchEntity from homeassistant.config_entries import ConfigEntry @@ -22,8 +22,7 @@ SONOS_CREATE_ALARM, SONOS_CREATE_SWITCHES, ) -from .entity import SonosEntity -from .exception import SpeakerUnavailable +from .entity import SonosEntity, SonosPollingEntity from .helpers import soco_error from .speaker import SonosSpeaker @@ -143,7 +142,7 @@ async def _async_create_switches(speaker: SonosSpeaker) -> None: ) -class SonosSwitchEntity(SonosEntity, SwitchEntity): +class SonosSwitchEntity(SonosPollingEntity, SwitchEntity): """Representation of a Sonos feature switch.""" def __init__(self, feature_type: str, speaker: SonosSpeaker) -> None: @@ -163,17 +162,14 @@ def __init__(self, feature_type: str, speaker: SonosSpeaker) -> None: self._attr_entity_registry_enabled_default = False self._attr_should_poll = True - async def _async_poll(self) -> None: + async def _async_fallback_poll(self) -> None: """Handle polling for subscription-based switches when subscription fails.""" if not self.should_poll: - await self.hass.async_add_executor_job(self.update) - - @soco_error(raise_on_err=False) - def update(self) -> None: - """Fetch switch state if necessary.""" - if not self.available: - raise SpeakerUnavailable + await self.hass.async_add_executor_job(self.poll_state) + @soco_error() + def poll_state(self) -> None: + """Poll the current state of the switch.""" state = getattr(self.soco, self.feature_type) setattr(self.speaker, self.feature_type, state) @@ -244,7 +240,7 @@ def name(self) -> str: str(self.alarm.start_time)[0:5], ) - async def _async_poll(self) -> None: + async def _async_fallback_poll(self) -> None: """Call the central alarm polling method.""" await self.hass.data[DATA_SONOS].alarms[self.household_id].async_poll() @@ -267,7 +263,6 @@ async def async_update_state(self) -> None: if not self.async_check_if_available(): return - _LOGGER.debug("Updating alarm: %s", self.entity_id) if self.speaker.soco.uid != self.alarm.zone.uid: self.speaker = self.hass.data[DATA_SONOS].discovered.get( self.alarm.zone.uid @@ -350,14 +345,11 @@ async def async_turn_off(self, **kwargs) -> None: """Turn alarm switch off.""" await self.async_handle_switch_on_off(turn_on=False) + @soco_error() async def async_handle_switch_on_off(self, turn_on: bool) -> None: """Handle turn on/off of alarm switch.""" - try: - _LOGGER.debug("Toggling the state of %s", self.entity_id) - self.alarm.enabled = turn_on - await self.hass.async_add_executor_job(self.alarm.save) - except (OSError, SoCoException, SoCoUPnPException) as exc: - _LOGGER.error("Could not update %s: %s", self.entity_id, exc) + self.alarm.enabled = turn_on + await self.hass.async_add_executor_job(self.alarm.save) @callback From 1bc82e2f857b92d3c98de84aedd320a89f3e07ce Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 8 Feb 2022 19:18:04 +0100 Subject: [PATCH 0436/1098] Use legacy pip resolver in machine builds (#66094) --- machine/raspberrypi | 3 ++- machine/raspberrypi2 | 3 ++- machine/raspberrypi3 | 3 ++- machine/raspberrypi3-64 | 3 ++- machine/raspberrypi4 | 3 ++- machine/raspberrypi4-64 | 3 ++- machine/tinker | 1 + 7 files changed, 13 insertions(+), 6 deletions(-) diff --git a/machine/raspberrypi b/machine/raspberrypi index 3f000b14db7af7..960e343792d24a 100644 --- a/machine/raspberrypi +++ b/machine/raspberrypi @@ -7,7 +7,8 @@ RUN apk --no-cache add \ usbutils \ && sed -i "s|# RPi.GPIO|RPi.GPIO|g" /usr/src/homeassistant/requirements_all.txt \ && pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \ - RPi.GPIO -c /usr/src/homeassistant/requirements_all.txt + RPi.GPIO -c /usr/src/homeassistant/requirements_all.txt \ + --use-deprecated=legacy-resolver ## # Set symlinks for raspberry pi camera binaries. diff --git a/machine/raspberrypi2 b/machine/raspberrypi2 index 484b209b6faa66..225c45423a1efe 100644 --- a/machine/raspberrypi2 +++ b/machine/raspberrypi2 @@ -7,7 +7,8 @@ RUN apk --no-cache add \ usbutils \ && sed -i "s|# RPi.GPIO|RPi.GPIO|g" /usr/src/homeassistant/requirements_all.txt \ && pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \ - RPi.GPIO -c /usr/src/homeassistant/requirements_all.txt + RPi.GPIO -c /usr/src/homeassistant/requirements_all.txt \ + --use-deprecated=legacy-resolver ## # Set symlinks for raspberry pi binaries. diff --git a/machine/raspberrypi3 b/machine/raspberrypi3 index 1aec7ebf39f841..6315cc3e885ce8 100644 --- a/machine/raspberrypi3 +++ b/machine/raspberrypi3 @@ -7,7 +7,8 @@ RUN apk --no-cache add \ usbutils \ && sed -i "s|# RPi.GPIO|RPi.GPIO|g" /usr/src/homeassistant/requirements_all.txt \ && pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \ - RPi.GPIO bluepy pybluez -c /usr/src/homeassistant/requirements_all.txt + RPi.GPIO bluepy pybluez -c /usr/src/homeassistant/requirements_all.txt \ + --use-deprecated=legacy-resolver ## # Set symlinks for raspberry pi binaries. diff --git a/machine/raspberrypi3-64 b/machine/raspberrypi3-64 index 165dc2e5397b64..51f41d68320664 100644 --- a/machine/raspberrypi3-64 +++ b/machine/raspberrypi3-64 @@ -7,7 +7,8 @@ RUN apk --no-cache add \ usbutils \ && sed -i "s|# RPi.GPIO|RPi.GPIO|g" /usr/src/homeassistant/requirements_all.txt \ && pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \ - RPi.GPIO bluepy pybluez -c /usr/src/homeassistant/requirements_all.txt + RPi.GPIO bluepy pybluez -c /usr/src/homeassistant/requirements_all.txt \ + --use-deprecated=legacy-resolver ## # Set symlinks for raspberry pi binaries. diff --git a/machine/raspberrypi4 b/machine/raspberrypi4 index 1aec7ebf39f841..6315cc3e885ce8 100644 --- a/machine/raspberrypi4 +++ b/machine/raspberrypi4 @@ -7,7 +7,8 @@ RUN apk --no-cache add \ usbutils \ && sed -i "s|# RPi.GPIO|RPi.GPIO|g" /usr/src/homeassistant/requirements_all.txt \ && pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \ - RPi.GPIO bluepy pybluez -c /usr/src/homeassistant/requirements_all.txt + RPi.GPIO bluepy pybluez -c /usr/src/homeassistant/requirements_all.txt \ + --use-deprecated=legacy-resolver ## # Set symlinks for raspberry pi binaries. diff --git a/machine/raspberrypi4-64 b/machine/raspberrypi4-64 index 165dc2e5397b64..51f41d68320664 100644 --- a/machine/raspberrypi4-64 +++ b/machine/raspberrypi4-64 @@ -7,7 +7,8 @@ RUN apk --no-cache add \ usbutils \ && sed -i "s|# RPi.GPIO|RPi.GPIO|g" /usr/src/homeassistant/requirements_all.txt \ && pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \ - RPi.GPIO bluepy pybluez -c /usr/src/homeassistant/requirements_all.txt + RPi.GPIO bluepy pybluez -c /usr/src/homeassistant/requirements_all.txt \ + --use-deprecated=legacy-resolver ## # Set symlinks for raspberry pi binaries. diff --git a/machine/tinker b/machine/tinker index 04a0aa6dc2caa2..9660ca71b9cbfb 100644 --- a/machine/tinker +++ b/machine/tinker @@ -4,6 +4,7 @@ FROM homeassistant/armv7-homeassistant:$BUILD_VERSION RUN apk --no-cache add usbutils \ && pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \ -c /usr/src/homeassistant/homeassistant/package_constraints.txt \ + --use-deprecated=legacy-resolver \ bluepy \ pybluez \ pygatt[GATTTOOL] From c93d389544e075fbb567acfe26067a96730963e2 Mon Sep 17 00:00:00 2001 From: Ben Edmunds Date: Tue, 8 Feb 2022 18:27:16 +0000 Subject: [PATCH 0437/1098] Bump async-upnp-client to 0.23.5 (#65922) --- homeassistant/components/dlna_dmr/manifest.json | 2 +- homeassistant/components/ssdp/manifest.json | 2 +- homeassistant/components/upnp/manifest.json | 2 +- homeassistant/components/yeelight/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/dlna_dmr/manifest.json b/homeassistant/components/dlna_dmr/manifest.json index d7109aa65ee327..885b7e3c65aa41 100644 --- a/homeassistant/components/dlna_dmr/manifest.json +++ b/homeassistant/components/dlna_dmr/manifest.json @@ -3,7 +3,7 @@ "name": "DLNA Digital Media Renderer", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/dlna_dmr", - "requirements": ["async-upnp-client==0.23.4"], + "requirements": ["async-upnp-client==0.23.5"], "dependencies": ["ssdp"], "ssdp": [ { diff --git a/homeassistant/components/ssdp/manifest.json b/homeassistant/components/ssdp/manifest.json index e1a5d20a987387..93512d08238df3 100644 --- a/homeassistant/components/ssdp/manifest.json +++ b/homeassistant/components/ssdp/manifest.json @@ -2,7 +2,7 @@ "domain": "ssdp", "name": "Simple Service Discovery Protocol (SSDP)", "documentation": "https://www.home-assistant.io/integrations/ssdp", - "requirements": ["async-upnp-client==0.23.4"], + "requirements": ["async-upnp-client==0.23.5"], "dependencies": ["network"], "after_dependencies": ["zeroconf"], "codeowners": [], diff --git a/homeassistant/components/upnp/manifest.json b/homeassistant/components/upnp/manifest.json index a52c2948557c4e..5b630973f67a4c 100644 --- a/homeassistant/components/upnp/manifest.json +++ b/homeassistant/components/upnp/manifest.json @@ -3,7 +3,7 @@ "name": "UPnP/IGD", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/upnp", - "requirements": ["async-upnp-client==0.23.4"], + "requirements": ["async-upnp-client==0.23.5"], "dependencies": ["network", "ssdp"], "codeowners": ["@StevenLooman","@ehendrix23"], "ssdp": [ diff --git a/homeassistant/components/yeelight/manifest.json b/homeassistant/components/yeelight/manifest.json index 351cb879da9e9b..7aa468819686b1 100644 --- a/homeassistant/components/yeelight/manifest.json +++ b/homeassistant/components/yeelight/manifest.json @@ -2,7 +2,7 @@ "domain": "yeelight", "name": "Yeelight", "documentation": "https://www.home-assistant.io/integrations/yeelight", - "requirements": ["yeelight==0.7.9", "async-upnp-client==0.23.4"], + "requirements": ["yeelight==0.7.9", "async-upnp-client==0.23.5"], "codeowners": ["@zewelor", "@shenxn", "@starkillerOG", "@alexyao2015"], "config_flow": true, "dependencies": ["network"], diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 18ed69348b02b5..fc0292523c25cb 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -4,7 +4,7 @@ aiodiscover==1.4.7 aiohttp==3.8.1 aiohttp_cors==0.7.0 astral==2.2 -async-upnp-client==0.23.4 +async-upnp-client==0.23.5 async_timeout==4.0.2 atomicwrites==1.4.0 attrs==21.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index f045c11ad3ab0a..d725f46bb50256 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -350,7 +350,7 @@ asterisk_mbox==0.5.0 # homeassistant.components.ssdp # homeassistant.components.upnp # homeassistant.components.yeelight -async-upnp-client==0.23.4 +async-upnp-client==0.23.5 # homeassistant.components.supla asyncpysupla==0.0.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 13a39da4c4e7e1..7d59fc3c2b8680 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -255,7 +255,7 @@ arcam-fmj==0.12.0 # homeassistant.components.ssdp # homeassistant.components.upnp # homeassistant.components.yeelight -async-upnp-client==0.23.4 +async-upnp-client==0.23.5 # homeassistant.components.aurora auroranoaa==0.0.2 From 41a4d40b71af8626f8a6aef4c0d9f30da4fb0f86 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 8 Feb 2022 19:54:10 +0100 Subject: [PATCH 0438/1098] Reuse existing coordinator entity update in Plugwise platforms (#66079) Co-authored-by: Martin Hjelmare --- .../components/plugwise/binary_sensor.py | 14 +++++++------- homeassistant/components/plugwise/climate.py | 6 +++--- homeassistant/components/plugwise/entity.py | 12 ++---------- homeassistant/components/plugwise/sensor.py | 16 ++++++++-------- homeassistant/components/plugwise/switch.py | 8 ++++---- 5 files changed, 24 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index 1f5cfacb37e2d3..35d351b4094bfa 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -99,11 +99,11 @@ def __init__( ).lstrip() @callback - def _async_process_data(self) -> None: - """Update the entity.""" + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" if not (data := self.coordinator.data.devices.get(self._dev_id)): LOGGER.error("Received no data for device %s", self._dev_id) - self.async_write_ha_state() + super()._handle_coordinator_update() return self._attr_is_on = data["binary_sensors"].get(self.entity_description.key) @@ -113,15 +113,15 @@ def _async_process_data(self) -> None: if self.entity_description.key == "slave_boiler_state": self._attr_icon = FLAME_ICON if self._attr_is_on else IDLE_ICON - self.async_write_ha_state() + super()._handle_coordinator_update() class PlugwiseNotifyBinarySensorEntity(PlugwiseBinarySensorEntity): """Representation of a Plugwise Notification binary_sensor.""" @callback - def _async_process_data(self) -> None: - """Update the entity.""" + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" notify = self.coordinator.data.gateway["notifications"] self._attr_extra_state_attributes = {} @@ -144,4 +144,4 @@ def _async_process_data(self) -> None: msg ) - self.async_write_ha_state() + super()._handle_coordinator_update() diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index 1cfb4ab7c6712f..89d7249ef38457 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -150,8 +150,8 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: LOGGER.error("Error while communicating to device") @callback - def _async_process_data(self) -> None: - """Update the data for this climate device.""" + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" data = self.coordinator.data.devices[self._dev_id] heater_central_data = self.coordinator.data.devices[ self.coordinator.data.gateway["heater_id"] @@ -192,4 +192,4 @@ def _async_process_data(self) -> None: "selected_schema": data.get("selected_schedule"), } - self.async_write_ha_state() + super()._handle_coordinator_update() diff --git a/homeassistant/components/plugwise/entity.py b/homeassistant/components/plugwise/entity.py index 2f73d38e5e30d7..7fdad9f4a91003 100644 --- a/homeassistant/components/plugwise/entity.py +++ b/homeassistant/components/plugwise/entity.py @@ -2,7 +2,6 @@ from __future__ import annotations from homeassistant.const import ATTR_NAME, ATTR_VIA_DEVICE, CONF_HOST -from homeassistant.core import callback from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -49,12 +48,5 @@ def __init__( async def async_added_to_hass(self) -> None: """Subscribe to updates.""" - self._async_process_data() - self.async_on_remove( - self.coordinator.async_add_listener(self._async_process_data) - ) - - @callback - def _async_process_data(self) -> None: - """Interpret and process API data.""" - raise NotImplementedError + self._handle_coordinator_update() + await super().async_added_to_hass() diff --git a/homeassistant/components/plugwise/sensor.py b/homeassistant/components/plugwise/sensor.py index 47b4f898c250e0..c99a5304eadcdd 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -346,15 +346,15 @@ def __init__( ).lstrip() @callback - def _async_process_data(self) -> None: - """Update the entity.""" + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" if not (data := self.coordinator.data.devices.get(self._dev_id)): LOGGER.error("Received no data for device %s", self._dev_id) - self.async_write_ha_state() + super()._handle_coordinator_update() return self._attr_native_value = data["sensors"].get(self.entity_description.key) - self.async_write_ha_state() + super()._handle_coordinator_update() class PlugwiseAuxSensorEntity(PlugwiseSensorEnity): @@ -364,11 +364,11 @@ class PlugwiseAuxSensorEntity(PlugwiseSensorEnity): _heating_state = False @callback - def _async_process_data(self) -> None: - """Update the entity.""" + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" if not (data := self.coordinator.data.devices.get(self._dev_id)): LOGGER.error("Received no data for device %s", self._dev_id) - self.async_write_ha_state() + super()._handle_coordinator_update() return if data.get("heating_state") is not None: @@ -385,4 +385,4 @@ def _async_process_data(self) -> None: self._attr_native_value = "cooling" self._attr_icon = COOL_ICON - self.async_write_ha_state() + super()._handle_coordinator_update() diff --git a/homeassistant/components/plugwise/switch.py b/homeassistant/components/plugwise/switch.py index 6079862fe42457..98288ca44b5314 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -87,12 +87,12 @@ async def async_turn_off(self, **kwargs: Any) -> None: self.async_write_ha_state() @callback - def _async_process_data(self) -> None: - """Update the data from the Plugs.""" + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" if not (data := self.coordinator.data.devices.get(self._dev_id)): LOGGER.error("Received no data for device %s", self._dev_id) - self.async_write_ha_state() + super()._handle_coordinator_update() return self._attr_is_on = data["switches"].get("relay") - self.async_write_ha_state() + super()._handle_coordinator_update() From d62e9c2b922aec4d59ba176f28fe5712a0e7a6ec Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 8 Feb 2022 12:57:21 -0600 Subject: [PATCH 0439/1098] Loosen wiz discovery matching (#66095) --- homeassistant/components/wiz/manifest.json | 2 +- homeassistant/generated/dhcp.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/wiz/manifest.json b/homeassistant/components/wiz/manifest.json index 82e1393dec3a7e..f1e44e5a774161 100644 --- a/homeassistant/components/wiz/manifest.json +++ b/homeassistant/components/wiz/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "dhcp": [ {"macaddress":"A8BB50*"}, - {"hostname":"wiz_[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]"} + {"hostname":"wiz_*"} ], "dependencies": ["network"], "documentation": "https://www.home-assistant.io/integrations/wiz", diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index bf42a11f95168e..a2193307b1876f 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -618,7 +618,7 @@ }, { "domain": "wiz", - "hostname": "wiz_[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]" + "hostname": "wiz_*" }, { "domain": "yeelight", From dad1dbeb6edf370c7dd4f3d2a0c60648a94dfe79 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 8 Feb 2022 20:17:49 +0100 Subject: [PATCH 0440/1098] Cleanup hass.data in Plugwise (#66096) --- .../components/plugwise/binary_sensor.py | 5 +-- homeassistant/components/plugwise/climate.py | 41 +++++-------------- homeassistant/components/plugwise/const.py | 1 - .../components/plugwise/diagnostics.py | 6 +-- homeassistant/components/plugwise/entity.py | 2 + homeassistant/components/plugwise/gateway.py | 9 +--- homeassistant/components/plugwise/sensor.py | 21 ++-------- homeassistant/components/plugwise/switch.py | 32 ++++----------- 8 files changed, 30 insertions(+), 87 deletions(-) diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index 35d351b4094bfa..27ad691e55e709 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -9,7 +9,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( - COORDINATOR, DOMAIN, FLAME_ICON, FLOW_OFF_ICON, @@ -45,7 +44,7 @@ async def async_setup_entry( """Set up the Smile binary_sensors from a config entry.""" coordinator: PlugwiseDataUpdateCoordinator = hass.data[DOMAIN][ config_entry.entry_id - ][COORDINATOR] + ] entities: list[PlugwiseBinarySensorEntity] = [] for device_id, device in coordinator.data.devices.items(): @@ -77,7 +76,7 @@ async def async_setup_entry( ) ) - async_add_entities(entities, True) + async_add_entities(entities) class PlugwiseBinarySensorEntity(PlugwiseEntity, BinarySensorEntity): diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index 89d7249ef38457..5ecab66bbde58e 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -2,7 +2,6 @@ from typing import Any from plugwise.exceptions import PlugwiseException -from plugwise.smile import Smile from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( @@ -22,7 +21,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( - COORDINATOR, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, DOMAIN, @@ -35,6 +33,7 @@ HVAC_MODES_HEAT_ONLY = [HVAC_MODE_HEAT, HVAC_MODE_AUTO, HVAC_MODE_OFF] HVAC_MODES_HEAT_COOL = [HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_AUTO, HVAC_MODE_OFF] +THERMOSTAT_CLASSES = ["thermostat", "zone_thermostat", "thermostatic_radiator_valve"] async def async_setup_entry( @@ -43,28 +42,12 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Smile Thermostats from a config entry.""" - api = hass.data[DOMAIN][config_entry.entry_id]["api"] - coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] - - entities: list[PlugwiseClimateEntity] = [] - thermostat_classes = [ - "thermostat", - "zone_thermostat", - "thermostatic_radiator_valve", - ] - for device_id, device in coordinator.data.devices.items(): - if device["class"] not in thermostat_classes: - continue - - thermostat = PlugwiseClimateEntity( - api, - coordinator, - device_id, - ) - - entities.append(thermostat) - - async_add_entities(entities, True) + coordinator = hass.data[DOMAIN][config_entry.entry_id] + async_add_entities( + PlugwiseClimateEntity(coordinator, device_id) + for device_id, device in coordinator.data.devices.items() + if device["class"] in THERMOSTAT_CLASSES + ) class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): @@ -82,7 +65,6 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): def __init__( self, - api: Smile, coordinator: PlugwiseDataUpdateCoordinator, device_id: str, ) -> None: @@ -92,7 +74,6 @@ def __init__( self._attr_unique_id = f"{device_id}-climate" self._attr_name = coordinator.data.devices[device_id].get("name") - self._api = api self._loc_id = coordinator.data.devices[device_id]["location"] self._presets = None @@ -104,7 +85,7 @@ async def async_set_temperature(self, **kwargs: Any) -> None: self._attr_min_temp < temperature < self._attr_max_temp ): try: - await self._api.set_temperature(self._loc_id, temperature) + await self.coordinator.api.set_temperature(self._loc_id, temperature) self._attr_target_temperature = temperature self.async_write_ha_state() except PlugwiseException: @@ -120,7 +101,7 @@ async def async_set_hvac_mode(self, hvac_mode: str) -> None: if hvac_mode == HVAC_MODE_AUTO: state = SCHEDULE_ON try: - await self._api.set_temperature( + await self.coordinator.api.set_temperature( self._loc_id, climate_data.get("schedule_temperature") ) self._attr_target_temperature = climate_data.get("schedule_temperature") @@ -128,7 +109,7 @@ async def async_set_hvac_mode(self, hvac_mode: str) -> None: LOGGER.error("Error while communicating to device") try: - await self._api.set_schedule_state( + await self.coordinator.api.set_schedule_state( self._loc_id, climate_data.get("last_used"), state ) self._attr_hvac_mode = hvac_mode @@ -142,7 +123,7 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: raise ValueError("No presets available") try: - await self._api.set_preset(self._loc_id, preset_mode) + await self.coordinator.api.set_preset(self._loc_id, preset_mode) self._attr_preset_mode = preset_mode self._attr_target_temperature = self._presets.get(preset_mode, "none")[0] self.async_write_ha_state() diff --git a/homeassistant/components/plugwise/const.py b/homeassistant/components/plugwise/const.py index 4c221b2860a171..d8d0a040d5f7c9 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -9,7 +9,6 @@ LOGGER = logging.getLogger(__package__) API = "api" -COORDINATOR = "coordinator" FLOW_SMILE = "smile (Adam/Anna/P1)" FLOW_STRETCH = "stretch (Stretch)" FLOW_TYPE = "flow_type" diff --git a/homeassistant/components/plugwise/diagnostics.py b/homeassistant/components/plugwise/diagnostics.py index 2b79d22f6a3d4d..ef54efbe96d983 100644 --- a/homeassistant/components/plugwise/diagnostics.py +++ b/homeassistant/components/plugwise/diagnostics.py @@ -6,7 +6,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from .const import COORDINATOR, DOMAIN +from .const import DOMAIN from .coordinator import PlugwiseDataUpdateCoordinator @@ -14,9 +14,7 @@ async def async_get_config_entry_diagnostics( hass: HomeAssistant, entry: ConfigEntry ) -> dict[str, Any]: """Return diagnostics for a config entry.""" - coordinator: PlugwiseDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ - COORDINATOR - ] + coordinator: PlugwiseDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] return { "gateway": coordinator.data.gateway, "devices": coordinator.data.devices, diff --git a/homeassistant/components/plugwise/entity.py b/homeassistant/components/plugwise/entity.py index 7fdad9f4a91003..5fa285418159b0 100644 --- a/homeassistant/components/plugwise/entity.py +++ b/homeassistant/components/plugwise/entity.py @@ -12,6 +12,8 @@ class PlugwiseEntity(CoordinatorEntity[PlugwiseData]): """Represent a PlugWise Entity.""" + coordinator: PlugwiseDataUpdateCoordinator + def __init__( self, coordinator: PlugwiseDataUpdateCoordinator, diff --git a/homeassistant/components/plugwise/gateway.py b/homeassistant/components/plugwise/gateway.py index eaccab58454461..6e518aad490d01 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -14,14 +14,11 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( - COORDINATOR, DEFAULT_PORT, DEFAULT_USERNAME, DOMAIN, - GATEWAY, LOGGER, PLATFORMS_GATEWAY, - PW_TYPE, SENSOR_PLATFORMS, ) from .coordinator import PlugwiseDataUpdateCoordinator @@ -63,11 +60,7 @@ async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: coordinator = PlugwiseDataUpdateCoordinator(hass, api) await coordinator.async_config_entry_first_refresh() - hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { - "api": api, - COORDINATOR: coordinator, - PW_TYPE: GATEWAY, - } + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator device_registry = dr.async_get(hass) device_registry.async_get_or_create( diff --git a/homeassistant/components/plugwise/sensor.py b/homeassistant/components/plugwise/sensor.py index c99a5304eadcdd..fa516c88c6cf97 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -1,8 +1,6 @@ """Plugwise Sensor component for Home Assistant.""" from __future__ import annotations -from plugwise.smile import Smile - from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, @@ -22,15 +20,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import ( - COOL_ICON, - COORDINATOR, - DOMAIN, - FLAME_ICON, - IDLE_ICON, - LOGGER, - UNIT_LUMEN, -) +from .const import COOL_ICON, DOMAIN, FLAME_ICON, IDLE_ICON, LOGGER, UNIT_LUMEN from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity @@ -286,8 +276,7 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Smile sensors from a config entry.""" - api = hass.data[DOMAIN][config_entry.entry_id]["api"] - coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] + coordinator = hass.data[DOMAIN][config_entry.entry_id] entities: list[PlugwiseSensorEnity] = [] for device_id, device in coordinator.data.devices.items(): @@ -300,7 +289,6 @@ async def async_setup_entry( entities.append( PlugwiseSensorEnity( - api, coordinator, device_id, description, @@ -315,7 +303,6 @@ async def async_setup_entry( entities.append( PlugwiseAuxSensorEntity( - api, coordinator, device_id, description, @@ -323,7 +310,7 @@ async def async_setup_entry( ) break - async_add_entities(entities, True) + async_add_entities(entities) class PlugwiseSensorEnity(PlugwiseEntity, SensorEntity): @@ -331,7 +318,6 @@ class PlugwiseSensorEnity(PlugwiseEntity, SensorEntity): def __init__( self, - api: Smile, coordinator: PlugwiseDataUpdateCoordinator, device_id: str, description: SensorEntityDescription, @@ -339,7 +325,6 @@ def __init__( """Initialise the sensor.""" super().__init__(coordinator, device_id) self.entity_description = description - self._api = api self._attr_unique_id = f"{device_id}-{description.key}" self._attr_name = ( f"{coordinator.data.devices[device_id].get('name', '')} {description.name}" diff --git a/homeassistant/components/plugwise/switch.py b/homeassistant/components/plugwise/switch.py index 98288ca44b5314..98ba1cd8183648 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -3,7 +3,6 @@ from typing import Any -from plugwise import Smile from plugwise.exceptions import PlugwiseException from homeassistant.components.switch import SwitchEntity @@ -11,7 +10,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import COORDINATOR, DOMAIN, LOGGER, SWITCH_ICON +from .const import DOMAIN, LOGGER, SWITCH_ICON from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity @@ -22,23 +21,12 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Smile switches from a config entry.""" - api = hass.data[DOMAIN][config_entry.entry_id]["api"] - coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] - - entities: list[PlugwiseSwitchEntity] = [] - for device_id, device in coordinator.data.devices.items(): - if "switches" not in device or "relay" not in device["switches"]: - continue - - entities.append( - PlugwiseSwitchEntity( - api, - coordinator, - device_id, - ) - ) - - async_add_entities(entities, True) + coordinator = hass.data[DOMAIN][config_entry.entry_id] + async_add_entities( + PlugwiseSwitchEntity(coordinator, device_id) + for device_id, device in coordinator.data.devices.items() + if "switches" in device and "relay" in device["switches"] + ) class PlugwiseSwitchEntity(PlugwiseEntity, SwitchEntity): @@ -48,13 +36,11 @@ class PlugwiseSwitchEntity(PlugwiseEntity, SwitchEntity): def __init__( self, - api: Smile, coordinator: PlugwiseDataUpdateCoordinator, device_id: str, ) -> None: """Set up the Plugwise API.""" super().__init__(coordinator, device_id) - self._api = api self._attr_unique_id = f"{device_id}-plug" self._members = coordinator.data.devices[device_id].get("members") self._attr_is_on = False @@ -63,7 +49,7 @@ def __init__( async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" try: - state_on = await self._api.set_switch_state( + state_on = await self.coordinator.api.set_switch_state( self._dev_id, self._members, "relay", "on" ) except PlugwiseException: @@ -76,7 +62,7 @@ async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" try: - state_off = await self._api.set_switch_state( + state_off = await self.coordinator.api.set_switch_state( self._dev_id, self._members, "relay", "off" ) except PlugwiseException: From d574e54fd8212fa85134a49f1cb8971065fd4d65 Mon Sep 17 00:00:00 2001 From: Sander Jochems Date: Tue, 8 Feb 2022 20:42:55 +0100 Subject: [PATCH 0441/1098] Fivem code quality improvements (#66086) * specify config type * move coordinator outside try block * rename gamename to game_name * remove log in __init__ * Remove logging and minify update * Add types to parameters * Remove name from device * Remove update listener * Remove status icon * Dont allow duplicate entries * Use default translation string * Remove online and port from coordinator --- homeassistant/components/fivem/__init__.py | 74 +++++++------------ .../components/fivem/binary_sensor.py | 3 +- homeassistant/components/fivem/config_flow.py | 25 +++---- homeassistant/components/fivem/const.py | 1 - homeassistant/components/fivem/strings.json | 5 +- .../components/fivem/translations/en.json | 5 +- tests/components/fivem/test_config_flow.py | 23 +++--- 7 files changed, 57 insertions(+), 79 deletions(-) diff --git a/homeassistant/components/fivem/__init__.py b/homeassistant/components/fivem/__init__.py index 4079b74a598a7c..2004aacd165b2d 100644 --- a/homeassistant/components/fivem/__init__.py +++ b/homeassistant/components/fivem/__init__.py @@ -10,7 +10,7 @@ from fivem import FiveM, FiveMServerOfflineError from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, Platform +from homeassistant.const import CONF_HOST, CONF_PORT, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.entity import DeviceInfo, EntityDescription @@ -45,14 +45,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entry.data[CONF_PORT], ) + coordinator = FiveMDataUpdateCoordinator(hass, entry.data, entry.entry_id) + try: - coordinator = FiveMDataUpdateCoordinator(hass, entry.data, entry.entry_id) await coordinator.initialize() except FiveMServerOfflineError as err: raise ConfigEntryNotReady from err await coordinator.async_config_entry_first_refresh() - entry.async_on_unload(entry.add_update_listener(update_listener)) hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator @@ -69,32 +69,23 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Update listener.""" - await hass.config_entries.async_reload(entry.entry_id) - - class FiveMDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): """Class to manage fetching FiveM data.""" - def __init__(self, hass: HomeAssistant, config_data, unique_id: str) -> None: + def __init__( + self, hass: HomeAssistant, config_data: Mapping[str, Any], unique_id: str + ) -> None: """Initialize server instance.""" - self._hass = hass - self.unique_id = unique_id self.server = None self.version = None - self.gamename: str | None = None + self.game_name: str | None = None - self.server_name = config_data[CONF_NAME] self.host = config_data[CONF_HOST] - self.port = config_data[CONF_PORT] - self.online = False - self._fivem = FiveM(self.host, self.port) + self._fivem = FiveM(self.host, config_data[CONF_PORT]) update_interval = timedelta(seconds=SCAN_INTERVAL) - _LOGGER.debug("Data will be updated every %s", update_interval) super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=update_interval) @@ -103,42 +94,31 @@ async def initialize(self) -> None: info = await self._fivem.get_info_raw() self.server = info["server"] self.version = info["version"] - self.gamename = info["vars"]["gamename"] + self.game_name = info["vars"]["gamename"] async def _async_update_data(self) -> dict[str, Any]: """Get server data from 3rd party library and update properties.""" - was_online = self.online - try: server = await self._fivem.get_server() - self.online = True - except FiveMServerOfflineError: - self.online = False - - if was_online and not self.online: - _LOGGER.warning("Connection to '%s:%s' lost", self.host, self.port) - elif not was_online and self.online: - _LOGGER.info("Connection to '%s:%s' (re-)established", self.host, self.port) - - if self.online: - players_list: list[str] = [] - for player in server.players: - players_list.append(player.name) - players_list.sort() + except FiveMServerOfflineError as err: + raise UpdateFailed from err - resources_list = server.resources - resources_list.sort() + players_list: list[str] = [] + for player in server.players: + players_list.append(player.name) + players_list.sort() - return { - NAME_PLAYERS_ONLINE: len(players_list), - NAME_PLAYERS_MAX: server.max_players, - NAME_RESOURCES: len(resources_list), - NAME_STATUS: self.online, - ATTR_PLAYERS_LIST: players_list, - ATTR_RESOURCES_LIST: resources_list, - } + resources_list = server.resources + resources_list.sort() - raise UpdateFailed + return { + NAME_PLAYERS_ONLINE: len(players_list), + NAME_PLAYERS_MAX: server.max_players, + NAME_RESOURCES: len(resources_list), + NAME_STATUS: self.last_update_success, + ATTR_PLAYERS_LIST: players_list, + ATTR_RESOURCES_LIST: resources_list, + } @dataclass @@ -163,13 +143,13 @@ def __init__( super().__init__(coordinator) self.entity_description = description - self._attr_name = f"{self.coordinator.server_name} {description.name}" + self._attr_name = f"{self.coordinator.host} {description.name}" self._attr_unique_id = f"{self.coordinator.unique_id}-{description.key}".lower() self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, self.coordinator.unique_id)}, manufacturer=MANUFACTURER, model=self.coordinator.server, - name=self.coordinator.server_name, + name=self.coordinator.host, sw_version=self.coordinator.version, ) diff --git a/homeassistant/components/fivem/binary_sensor.py b/homeassistant/components/fivem/binary_sensor.py index 2e9ea8347997f5..20ea057da6e717 100644 --- a/homeassistant/components/fivem/binary_sensor.py +++ b/homeassistant/components/fivem/binary_sensor.py @@ -9,7 +9,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import FiveMEntity, FiveMEntityDescription -from .const import DOMAIN, ICON_STATUS, NAME_STATUS +from .const import DOMAIN, NAME_STATUS class FiveMBinarySensorEntityDescription( @@ -22,7 +22,6 @@ class FiveMBinarySensorEntityDescription( FiveMBinarySensorEntityDescription( key=NAME_STATUS, name=NAME_STATUS, - icon=ICON_STATUS, device_class=BinarySensorDeviceClass.CONNECTIVITY, ), ) diff --git a/homeassistant/components/fivem/config_flow.py b/homeassistant/components/fivem/config_flow.py index 792d5f1b0253cb..e564faa81b7165 100644 --- a/homeassistant/components/fivem/config_flow.py +++ b/homeassistant/components/fivem/config_flow.py @@ -8,8 +8,7 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT -from homeassistant.core import HomeAssistant +from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv @@ -21,22 +20,21 @@ STEP_USER_DATA_SCHEMA = vol.Schema( { - vol.Required(CONF_NAME): cv.string, vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, } ) -async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None: +async def validate_input(data: dict[str, Any]) -> None: """Validate the user input allows us to connect.""" fivem = FiveM(data[CONF_HOST], data[CONF_PORT]) info = await fivem.get_info_raw() - gamename = info.get("vars")["gamename"] - if gamename is None or gamename != "gta5": - raise InvalidGamenameError + game_name = info.get("vars")["gamename"] + if game_name is None or game_name != "gta5": + raise InvalidGameNameError class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -56,21 +54,22 @@ async def async_step_user( errors = {} try: - await validate_input(self.hass, user_input) + await validate_input(user_input) except FiveMServerOfflineError: errors["base"] = "cannot_connect" - except InvalidGamenameError: - errors["base"] = "invalid_gamename" + except InvalidGameNameError: + errors["base"] = "invalid_game_name" except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" else: - return self.async_create_entry(title=user_input[CONF_NAME], data=user_input) + self._async_abort_entries_match(user_input) + return self.async_create_entry(title=user_input[CONF_HOST], data=user_input) return self.async_show_form( step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors ) -class InvalidGamenameError(Exception): - """Handle errors in the gamename from the api.""" +class InvalidGameNameError(Exception): + """Handle errors in the game name from the api.""" diff --git a/homeassistant/components/fivem/const.py b/homeassistant/components/fivem/const.py index 5907aa7300eb27..1676dc9f2b3717 100644 --- a/homeassistant/components/fivem/const.py +++ b/homeassistant/components/fivem/const.py @@ -8,7 +8,6 @@ ICON_PLAYERS_MAX = "mdi:account-multiple" ICON_PLAYERS_ONLINE = "mdi:account-multiple" ICON_RESOURCES = "mdi:playlist-check" -ICON_STATUS = "mdi:lan" MANUFACTURER = "Cfx.re" diff --git a/homeassistant/components/fivem/strings.json b/homeassistant/components/fivem/strings.json index 2949dfb58ad815..03afcc11f27b52 100644 --- a/homeassistant/components/fivem/strings.json +++ b/homeassistant/components/fivem/strings.json @@ -11,10 +11,11 @@ }, "error": { "cannot_connect": "Failed to connect. Please check the host and port and try again. Also ensure that you are running the latest FiveM server.", - "invalid_gamename": "The api of the game you are trying to connect to is not a FiveM game." + "invalid_game_name": "The api of the game you are trying to connect to is not a FiveM game.", + "unknown_error": "[%key:common::config_flow::error::unknown%]" }, "abort": { - "already_configured": "FiveM server is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" } } } \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/en.json b/homeassistant/components/fivem/translations/en.json index a81ce1e1ce517c..8c4f7a54156e18 100644 --- a/homeassistant/components/fivem/translations/en.json +++ b/homeassistant/components/fivem/translations/en.json @@ -1,11 +1,12 @@ { "config": { "abort": { - "already_configured": "FiveM server is already configured" + "already_configured": "Service is already configured" }, "error": { "cannot_connect": "Failed to connect. Please check the host and port and try again. Also ensure that you are running the latest FiveM server.", - "invalid_gamename": "The api of the game you are trying to connect to is not a FiveM game." + "invalid_game_name": "The api of the game you are trying to connect to is not a FiveM game.", + "unknown_error": "Unexpected error" }, "step": { "user": { diff --git a/tests/components/fivem/test_config_flow.py b/tests/components/fivem/test_config_flow.py index 7af6e0fca4857e..a1a1e8f2a379d0 100644 --- a/tests/components/fivem/test_config_flow.py +++ b/tests/components/fivem/test_config_flow.py @@ -6,18 +6,17 @@ from homeassistant import config_entries from homeassistant.components.fivem.config_flow import DEFAULT_PORT from homeassistant.components.fivem.const import DOMAIN -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT +from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_FORM USER_INPUT = { - CONF_NAME: "Dummy Server", CONF_HOST: "fivem.dummyserver.com", CONF_PORT: DEFAULT_PORT, } -def __mock_fivem_info_success(): +def _mock_fivem_info_success(): return { "resources": [ "fivem", @@ -31,7 +30,7 @@ def __mock_fivem_info_success(): } -def __mock_fivem_info_invalid(): +def _mock_fivem_info_invalid(): return { "plugins": [ "sample", @@ -42,8 +41,8 @@ def __mock_fivem_info_invalid(): } -def __mock_fivem_info_invalid_gamename(): - info = __mock_fivem_info_success() +def _mock_fivem_info_invalid_game_name(): + info = _mock_fivem_info_success() info["vars"]["gamename"] = "redm" return info @@ -69,7 +68,7 @@ async def test_form(hass: HomeAssistant) -> None: with patch( "fivem.fivem.FiveM.get_info_raw", - return_value=__mock_fivem_info_success(), + return_value=_mock_fivem_info_success(), ), patch( "homeassistant.components.fivem.async_setup_entry", return_value=True, @@ -81,7 +80,7 @@ async def test_form(hass: HomeAssistant) -> None: await hass.async_block_till_done() assert result2["type"] == RESULT_TYPE_CREATE_ENTRY - assert result2["title"] == USER_INPUT[CONF_NAME] + assert result2["title"] == USER_INPUT[CONF_HOST] assert result2["data"] == USER_INPUT assert len(mock_setup_entry.mock_calls) == 1 @@ -114,7 +113,7 @@ async def test_form_invalid(hass: HomeAssistant) -> None: with patch( "fivem.fivem.FiveM.get_info_raw", - return_value=__mock_fivem_info_invalid(), + return_value=_mock_fivem_info_invalid(), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -126,7 +125,7 @@ async def test_form_invalid(hass: HomeAssistant) -> None: assert result2["errors"] == {"base": "unknown"} -async def test_form_invalid_gamename(hass: HomeAssistant) -> None: +async def test_form_invalid_game_name(hass: HomeAssistant) -> None: """Test we get the form.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -134,7 +133,7 @@ async def test_form_invalid_gamename(hass: HomeAssistant) -> None: with patch( "fivem.fivem.FiveM.get_info_raw", - return_value=__mock_fivem_info_invalid_gamename(), + return_value=_mock_fivem_info_invalid_game_name(), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -143,4 +142,4 @@ async def test_form_invalid_gamename(hass: HomeAssistant) -> None: await hass.async_block_till_done() assert result2["type"] == RESULT_TYPE_FORM - assert result2["errors"] == {"base": "invalid_gamename"} + assert result2["errors"] == {"base": "invalid_game_name"} From 07b3d23835b0d64fbf57fcbe39e7408bb0c8be25 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 8 Feb 2022 14:34:52 -0600 Subject: [PATCH 0442/1098] Improve wiz performance (#66105) --- homeassistant/components/wiz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/wiz/manifest.json b/homeassistant/components/wiz/manifest.json index f1e44e5a774161..47a4f343d4fac4 100644 --- a/homeassistant/components/wiz/manifest.json +++ b/homeassistant/components/wiz/manifest.json @@ -8,7 +8,7 @@ ], "dependencies": ["network"], "documentation": "https://www.home-assistant.io/integrations/wiz", - "requirements": ["pywizlight==0.5.2"], + "requirements": ["pywizlight==0.5.3"], "iot_class": "local_push", "codeowners": ["@sbidy"] } diff --git a/requirements_all.txt b/requirements_all.txt index d725f46bb50256..4b0f67c5daf722 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2054,7 +2054,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.5.2 +pywizlight==0.5.3 # homeassistant.components.xeoma pyxeoma==1.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7d59fc3c2b8680..bc4ee48bb024db 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1279,7 +1279,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.5.2 +pywizlight==0.5.3 # homeassistant.components.zerproc pyzerproc==0.4.8 From 55d83140935ca9c8f26dc3635413daf8bf430a6d Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 8 Feb 2022 21:49:25 +0100 Subject: [PATCH 0443/1098] Fix cleanup of MQTT debug info (#66104) --- homeassistant/components/mqtt/__init__.py | 2 ++ homeassistant/components/mqtt/debug_info.py | 28 +++++++++------------ homeassistant/components/mqtt/mixins.py | 2 +- tests/components/mqtt/test_init.py | 2 +- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 964f19e1d7c2bc..eba2670aa952ae 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -596,6 +596,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ) ) + debug_info.initialize(hass) + return True diff --git a/homeassistant/components/mqtt/debug_info.py b/homeassistant/components/mqtt/debug_info.py index e1001ab7b04dfd..ca9e56f8efb4f7 100644 --- a/homeassistant/components/mqtt/debug_info.py +++ b/homeassistant/components/mqtt/debug_info.py @@ -20,6 +20,11 @@ STORED_MESSAGES = 10 +def initialize(hass: HomeAssistant): + """Initialize MQTT debug info.""" + hass.data[DATA_MQTT_DEBUG_INFO] = {"entities": {}, "triggers": {}} + + def log_messages( hass: HomeAssistant, entity_id: str ) -> Callable[[MessageCallbackType], MessageCallbackType]: @@ -67,9 +72,7 @@ def log_message( retain: bool, ) -> None: """Log an outgoing MQTT message.""" - debug_info = hass.data.setdefault( - DATA_MQTT_DEBUG_INFO, {"entities": {}, "triggers": {}} - ) + debug_info = hass.data[DATA_MQTT_DEBUG_INFO] entity_info = debug_info["entities"].setdefault( entity_id, {"subscriptions": {}, "discovery_data": {}, "transmitted": {}} ) @@ -86,9 +89,7 @@ def log_message( def add_subscription(hass, message_callback, subscription): """Prepare debug data for subscription.""" if entity_id := getattr(message_callback, "__entity_id", None): - debug_info = hass.data.setdefault( - DATA_MQTT_DEBUG_INFO, {"entities": {}, "triggers": {}} - ) + debug_info = hass.data[DATA_MQTT_DEBUG_INFO] entity_info = debug_info["entities"].setdefault( entity_id, {"subscriptions": {}, "discovery_data": {}, "transmitted": {}} ) @@ -117,9 +118,7 @@ def remove_subscription(hass, message_callback, subscription): def add_entity_discovery_data(hass, discovery_data, entity_id): """Add discovery data.""" - debug_info = hass.data.setdefault( - DATA_MQTT_DEBUG_INFO, {"entities": {}, "triggers": {}} - ) + debug_info = hass.data[DATA_MQTT_DEBUG_INFO] entity_info = debug_info["entities"].setdefault( entity_id, {"subscriptions": {}, "discovery_data": {}, "transmitted": {}} ) @@ -134,14 +133,13 @@ def update_entity_discovery_data(hass, discovery_payload, entity_id): def remove_entity_data(hass, entity_id): """Remove discovery data.""" - hass.data[DATA_MQTT_DEBUG_INFO]["entities"].pop(entity_id) + if entity_id in hass.data[DATA_MQTT_DEBUG_INFO]["entities"]: + hass.data[DATA_MQTT_DEBUG_INFO]["entities"].pop(entity_id) def add_trigger_discovery_data(hass, discovery_hash, discovery_data, device_id): """Add discovery data.""" - debug_info = hass.data.setdefault( - DATA_MQTT_DEBUG_INFO, {"entities": {}, "triggers": {}} - ) + debug_info = hass.data[DATA_MQTT_DEBUG_INFO] debug_info["triggers"][discovery_hash] = { "device_id": device_id, "discovery_data": discovery_data, @@ -167,9 +165,7 @@ def info_for_device(hass, device_id): entries = hass.helpers.entity_registry.async_entries_for_device( entity_registry, device_id, include_disabled_entities=True ) - mqtt_debug_info = hass.data.setdefault( - DATA_MQTT_DEBUG_INFO, {"entities": {}, "triggers": {}} - ) + mqtt_debug_info = hass.data[DATA_MQTT_DEBUG_INFO] for entry in entries: if entry.entity_id not in mqtt_debug_info["entities"]: continue diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 722bfd51c9f8eb..177c7d9b57faaa 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -573,7 +573,6 @@ async def async_will_remove_from_hass(self) -> None: def _cleanup_discovery_on_remove(self) -> None: """Stop listening to signal and cleanup discovery data.""" if self._discovery_data and not self._removed_from_hass: - debug_info.remove_entity_data(self.hass, self.entity_id) clear_discovery_hash(self.hass, self._discovery_data[ATTR_DISCOVERY_HASH]) self._removed_from_hass = True @@ -709,6 +708,7 @@ async def async_will_remove_from_hass(self): await MqttAttributes.async_will_remove_from_hass(self) await MqttAvailability.async_will_remove_from_hass(self) await MqttDiscoveryUpdate.async_will_remove_from_hass(self) + debug_info.remove_entity_data(self.hass, self.entity_id) async def async_publish( self, diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 3cb49598e8c7d5..3d249fa2144697 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -1766,7 +1766,7 @@ async def test_debug_info_multiple_entities_triggers(hass, mqtt_mock): } in discovery_data -async def test_debug_info_non_mqtt(hass, device_reg, entity_reg): +async def test_debug_info_non_mqtt(hass, device_reg, entity_reg, mqtt_mock): """Test we get empty debug_info for a device with non MQTT entities.""" DOMAIN = "sensor" platform = getattr(hass.components, f"test.{DOMAIN}") From 86ab500afdbec09a90df89c2b7aee1f1386089e2 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Tue, 8 Feb 2022 22:02:45 +0100 Subject: [PATCH 0444/1098] Add Renault hvac sensors (#65993) * Add hvac sensors to renault * Adjust fixtures * Adjust tests * Use icon_fn * Use lambda Co-authored-by: epenet --- .../components/renault/binary_sensor.py | 18 ++++++++ homeassistant/components/renault/sensor.py | 18 ++++++++ tests/components/renault/const.py | 45 +++++++++++++++++-- .../{hvac_status.json => hvac_status.1.json} | 0 .../renault/fixtures/hvac_status.2.json | 11 +++++ 5 files changed, 89 insertions(+), 3 deletions(-) rename tests/components/renault/fixtures/{hvac_status.json => hvac_status.1.json} (100%) create mode 100644 tests/components/renault/fixtures/hvac_status.2.json diff --git a/homeassistant/components/renault/binary_sensor.py b/homeassistant/components/renault/binary_sensor.py index ec8ca77bded49f..3d96624e628082 100644 --- a/homeassistant/components/renault/binary_sensor.py +++ b/homeassistant/components/renault/binary_sensor.py @@ -1,6 +1,7 @@ """Support for Renault binary sensors.""" from __future__ import annotations +from collections.abc import Callable from dataclasses import dataclass from renault_api.kamereon.enums import ChargeState, PlugState @@ -37,6 +38,8 @@ class RenaultBinarySensorEntityDescription( ): """Class describing Renault binary sensor entities.""" + icon_fn: Callable[[RenaultBinarySensor], str] | None = None + async def async_setup_entry( hass: HomeAssistant, @@ -68,6 +71,13 @@ def is_on(self) -> bool | None: return None return data == self.entity_description.on_value + @property + def icon(self) -> str | None: + """Icon handling.""" + if self.entity_description.icon_fn: + return self.entity_description.icon_fn(self) + return None + BINARY_SENSOR_TYPES: tuple[RenaultBinarySensorEntityDescription, ...] = ( RenaultBinarySensorEntityDescription( @@ -86,4 +96,12 @@ def is_on(self) -> bool | None: on_key="chargingStatus", on_value=ChargeState.CHARGE_IN_PROGRESS.value, ), + RenaultBinarySensorEntityDescription( + key="hvac_status", + coordinator="hvac_status", + icon_fn=lambda e: "mdi:fan" if e.is_on else "mdi:fan-off", + name="HVAC", + on_key="hvacStatus", + on_value="on", + ), ) diff --git a/homeassistant/components/renault/sensor.py b/homeassistant/components/renault/sensor.py index d98b9864875455..8e63d09b5c762c 100644 --- a/homeassistant/components/renault/sensor.py +++ b/homeassistant/components/renault/sensor.py @@ -305,6 +305,24 @@ def _get_utc_value(entity: RenaultSensor[T]) -> datetime: native_unit_of_measurement=TEMP_CELSIUS, state_class=SensorStateClass.MEASUREMENT, ), + RenaultSensorEntityDescription( + key="hvac_soc_threshold", + coordinator="hvac_status", + data_key="socThreshold", + entity_class=RenaultSensor[KamereonVehicleHvacStatusData], + name="HVAC SOC Threshold", + native_unit_of_measurement=PERCENTAGE, + ), + RenaultSensorEntityDescription( + key="hvac_last_activity", + coordinator="hvac_status", + device_class=SensorDeviceClass.TIMESTAMP, + data_key="lastUpdateTime", + entity_class=RenaultSensor[KamereonVehicleHvacStatusData], + entity_registry_enabled_default=False, + name="HVAC Last Activity", + value_lambda=_get_utc_value, + ), RenaultSensorEntityDescription( key="location_last_activity", coordinator="location", diff --git a/tests/components/renault/const.py b/tests/components/renault/const.py index 91704a59b5178f..90a1665b75b978 100644 --- a/tests/components/renault/const.py +++ b/tests/components/renault/const.py @@ -54,6 +54,7 @@ DYNAMIC_ATTRIBUTES = (ATTR_ICON,) ICON_FOR_EMPTY_VALUES = { + "binary_sensor.reg_number_hvac": "mdi:fan-off", "select.reg_number_charge_mode": "mdi:calendar-remove", "sensor.reg_number_charge_state": "mdi:flash-off", "sensor.reg_number_plug_state": "mdi:power-plug-off", @@ -89,7 +90,7 @@ "battery_status": "battery_status_charging.json", "charge_mode": "charge_mode_always.json", "cockpit": "cockpit_ev.json", - "hvac_status": "hvac_status.json", + "hvac_status": "hvac_status.1.json", }, Platform.BINARY_SENSOR: [ { @@ -104,6 +105,12 @@ ATTR_STATE: STATE_ON, ATTR_UNIQUE_ID: "vf1aaaaa555777999_charging", }, + { + ATTR_ENTITY_ID: "binary_sensor.reg_number_hvac", + ATTR_ICON: "mdi:fan-off", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "vf1aaaaa555777999_hvac_status", + }, ], Platform.BUTTON: [ { @@ -209,6 +216,19 @@ ATTR_UNIQUE_ID: "vf1aaaaa555777999_outside_temperature", ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, }, + { + ATTR_ENTITY_ID: "sensor.reg_number_hvac_soc_threshold", + ATTR_STATE: STATE_UNKNOWN, + ATTR_UNIQUE_ID: "vf1aaaaa555777999_hvac_soc_threshold", + ATTR_UNIT_OF_MEASUREMENT: PERCENTAGE, + }, + { + ATTR_DEFAULT_DISABLED: True, + ATTR_DEVICE_CLASS: SensorDeviceClass.TIMESTAMP, + ATTR_ENTITY_ID: "sensor.reg_number_hvac_last_activity", + ATTR_STATE: STATE_UNKNOWN, + ATTR_UNIQUE_ID: "vf1aaaaa555777999_hvac_last_activity", + }, { ATTR_DEVICE_CLASS: DEVICE_CLASS_PLUG_STATE, ATTR_ENTITY_ID: "sensor.reg_number_plug_state", @@ -237,7 +257,7 @@ "battery_status": "battery_status_not_charging.json", "charge_mode": "charge_mode_schedule.json", "cockpit": "cockpit_ev.json", - "hvac_status": "hvac_status.json", + "hvac_status": "hvac_status.2.json", "location": "location.json", }, Platform.BINARY_SENSOR: [ @@ -253,6 +273,12 @@ ATTR_STATE: STATE_OFF, ATTR_UNIQUE_ID: "vf1aaaaa555777999_charging", }, + { + ATTR_ENTITY_ID: "binary_sensor.reg_number_hvac", + ATTR_ICON: "mdi:fan-off", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "vf1aaaaa555777999_hvac_status", + }, ], Platform.BUTTON: [ { @@ -360,11 +386,24 @@ { ATTR_DEVICE_CLASS: SensorDeviceClass.TEMPERATURE, ATTR_ENTITY_ID: "sensor.reg_number_outside_temperature", - ATTR_STATE: "8.0", + ATTR_STATE: STATE_UNKNOWN, ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT, ATTR_UNIQUE_ID: "vf1aaaaa555777999_outside_temperature", ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, }, + { + ATTR_ENTITY_ID: "sensor.reg_number_hvac_soc_threshold", + ATTR_STATE: "30.0", + ATTR_UNIQUE_ID: "vf1aaaaa555777999_hvac_soc_threshold", + ATTR_UNIT_OF_MEASUREMENT: PERCENTAGE, + }, + { + ATTR_DEFAULT_DISABLED: True, + ATTR_DEVICE_CLASS: SensorDeviceClass.TIMESTAMP, + ATTR_ENTITY_ID: "sensor.reg_number_hvac_last_activity", + ATTR_STATE: "2020-12-03T00:00:00+00:00", + ATTR_UNIQUE_ID: "vf1aaaaa555777999_hvac_last_activity", + }, { ATTR_DEVICE_CLASS: DEVICE_CLASS_PLUG_STATE, ATTR_ENTITY_ID: "sensor.reg_number_plug_state", diff --git a/tests/components/renault/fixtures/hvac_status.json b/tests/components/renault/fixtures/hvac_status.1.json similarity index 100% rename from tests/components/renault/fixtures/hvac_status.json rename to tests/components/renault/fixtures/hvac_status.1.json diff --git a/tests/components/renault/fixtures/hvac_status.2.json b/tests/components/renault/fixtures/hvac_status.2.json new file mode 100644 index 00000000000000..a2ca08a71e9ec5 --- /dev/null +++ b/tests/components/renault/fixtures/hvac_status.2.json @@ -0,0 +1,11 @@ +{ + "data": { + "type": "Car", + "id": "VF1AAAAA555777999", + "attributes": { + "socThreshold": 30.0, + "hvacStatus": "off", + "lastUpdateTime": "2020-12-03T00:00:00Z" + } + } +} From f8a84f01017e206a828c24207aa88773a6680084 Mon Sep 17 00:00:00 2001 From: Patrik Lindgren <21142447+ggravlingen@users.noreply.github.com> Date: Tue, 8 Feb 2022 22:05:33 +0100 Subject: [PATCH 0445/1098] Add diagnostics for Tradfri platform (#66092) Co-authored-by: Martin Hjelmare --- .../components/tradfri/diagnostics.py | 36 +++++++++++++++ tests/components/tradfri/test_diagnostics.py | 45 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 homeassistant/components/tradfri/diagnostics.py create mode 100644 tests/components/tradfri/test_diagnostics.py diff --git a/homeassistant/components/tradfri/diagnostics.py b/homeassistant/components/tradfri/diagnostics.py new file mode 100644 index 00000000000000..81f20c4a46a249 --- /dev/null +++ b/homeassistant/components/tradfri/diagnostics.py @@ -0,0 +1,36 @@ +"""Diagnostics support for IKEA Tradfri.""" +from __future__ import annotations + +from typing import cast + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr + +from .const import CONF_GATEWAY_ID, COORDINATOR, COORDINATOR_LIST, DOMAIN, GROUPS_LIST + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict: + """Return diagnostics the Tradfri platform.""" + entry_data = hass.data[DOMAIN][entry.entry_id] + coordinator_data = entry_data[COORDINATOR] + + device_registry = dr.async_get(hass) + device = cast( + dr.DeviceEntry, + device_registry.async_get_device( + identifiers={(DOMAIN, entry.data[CONF_GATEWAY_ID])} + ), + ) + + device_data: list = [] + for coordinator in coordinator_data[COORDINATOR_LIST]: + device_data.append(coordinator.device.device_info.model_number) + + return { + "gateway_version": device.sw_version, + "device_data": sorted(device_data), + "no_of_groups": len(coordinator_data[GROUPS_LIST]), + } diff --git a/tests/components/tradfri/test_diagnostics.py b/tests/components/tradfri/test_diagnostics.py new file mode 100644 index 00000000000000..d76e80a8b9c301 --- /dev/null +++ b/tests/components/tradfri/test_diagnostics.py @@ -0,0 +1,45 @@ +"""Tests for Tradfri diagnostics.""" +from unittest.mock import MagicMock, Mock + +from aiohttp import ClientSession + +from homeassistant.core import HomeAssistant + +from .common import setup_integration +from .test_fan import mock_fan +from .test_light import mock_group + +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_diagnostics( + hass: HomeAssistant, + hass_client: ClientSession, + mock_gateway: Mock, + mock_api_factory: MagicMock, +) -> None: + """Test diagnostics for config entry.""" + mock_gateway.mock_devices.append( + # Add a fan + mock_fan( + test_state={ + "fan_speed": 10, + "air_quality": 42, + "filter_lifetime_remaining": 120, + } + ) + ) + + mock_gateway.mock_groups.append( + # Add a group + mock_group(test_state={"state": True, "dimmer": 100}) + ) + + init_integration = await setup_integration(hass) + + result = await get_diagnostics_for_config_entry(hass, hass_client, init_integration) + + assert isinstance(result, dict) + assert result["gateway_version"] == "1.2.1234" + assert len(result["device_data"]) == 1 + assert result["no_of_groups"] == 1 From 009b31941a45c3d880b69dcf91d14edeb61a78a7 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 8 Feb 2022 23:00:26 +0100 Subject: [PATCH 0446/1098] Support restoring SensorEntity native_value (#66068) --- homeassistant/components/sensor/__init__.py | 61 ++++++++ homeassistant/helpers/restore_state.py | 99 ++++++++++--- tests/common.py | 31 +++- tests/components/sensor/test_init.py | 132 ++++++++++++++++++ tests/helpers/test_restore_state.py | 23 +-- .../custom_components/test/sensor.py | 15 ++ 6 files changed, 330 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 38461bce859e36..c6c4d18d21d897 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -52,7 +52,9 @@ ) from homeassistant.helpers.entity import Entity, EntityDescription from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.restore_state import ExtraStoredData, RestoreEntity from homeassistant.helpers.typing import ConfigType, StateType +from homeassistant.util import dt as dt_util from .const import CONF_STATE_CLASS # noqa: F401 @@ -447,3 +449,62 @@ def __repr__(self) -> str: return f"" return super().__repr__() + + +@dataclass +class SensorExtraStoredData(ExtraStoredData): + """Object to hold extra stored data.""" + + native_value: StateType | date | datetime + native_unit_of_measurement: str | None + + def as_dict(self) -> dict[str, Any]: + """Return a dict representation of the sensor data.""" + native_value: StateType | date | datetime | dict[str, str] = self.native_value + if isinstance(native_value, (date, datetime)): + native_value = { + "__type": str(type(native_value)), + "isoformat": native_value.isoformat(), + } + return { + "native_value": native_value, + "native_unit_of_measurement": self.native_unit_of_measurement, + } + + @classmethod + def from_dict(cls, restored: dict[str, Any]) -> SensorExtraStoredData | None: + """Initialize a stored sensor state from a dict.""" + try: + native_value = restored["native_value"] + native_unit_of_measurement = restored["native_unit_of_measurement"] + except KeyError: + return None + try: + type_ = native_value["__type"] + if type_ == "": + native_value = dt_util.parse_datetime(native_value["isoformat"]) + elif type_ == "": + native_value = dt_util.parse_date(native_value["isoformat"]) + except TypeError: + # native_value is not a dict + pass + except KeyError: + # native_value is a dict, but does not have all values + return None + + return cls(native_value, native_unit_of_measurement) + + +class RestoreSensor(SensorEntity, RestoreEntity): + """Mixin class for restoring previous sensor state.""" + + @property + def extra_restore_state_data(self) -> SensorExtraStoredData: + """Return sensor specific state data to be restored.""" + return SensorExtraStoredData(self.native_value, self.native_unit_of_measurement) + + async def async_get_last_sensor_data(self) -> SensorExtraStoredData | None: + """Restore native_value and native_unit_of_measurement.""" + if (restored_last_extra_data := await self.async_get_last_extra_data()) is None: + return None + return SensorExtraStoredData.from_dict(restored_last_extra_data.as_dict()) diff --git a/homeassistant/helpers/restore_state.py b/homeassistant/helpers/restore_state.py index 4857210f125ed8..79d46f8ec2e6c4 100644 --- a/homeassistant/helpers/restore_state.py +++ b/homeassistant/helpers/restore_state.py @@ -1,6 +1,7 @@ """Support for restoring entity states on startup.""" from __future__ import annotations +from abc import abstractmethod import asyncio from datetime import datetime, timedelta import logging @@ -34,27 +35,65 @@ _StoredStateT = TypeVar("_StoredStateT", bound="StoredState") +class ExtraStoredData: + """Object to hold extra stored data.""" + + @abstractmethod + def as_dict(self) -> dict[str, Any]: + """Return a dict representation of the extra data. + + Must be serializable by Home Assistant's JSONEncoder. + """ + + +class RestoredExtraData(ExtraStoredData): + """Object to hold extra stored data loaded from storage.""" + + def __init__(self, json_dict: dict[str, Any]) -> None: + """Object to hold extra stored data.""" + self.json_dict = json_dict + + def as_dict(self) -> dict[str, Any]: + """Return a dict representation of the extra data.""" + return self.json_dict + + class StoredState: """Object to represent a stored state.""" - def __init__(self, state: State, last_seen: datetime) -> None: + def __init__( + self, + state: State, + extra_data: ExtraStoredData | None, + last_seen: datetime, + ) -> None: """Initialize a new stored state.""" - self.state = state + self.extra_data = extra_data self.last_seen = last_seen + self.state = state def as_dict(self) -> dict[str, Any]: """Return a dict representation of the stored state.""" - return {"state": self.state.as_dict(), "last_seen": self.last_seen} + result = { + "state": self.state.as_dict(), + "extra_data": self.extra_data.as_dict() if self.extra_data else None, + "last_seen": self.last_seen, + } + return result @classmethod def from_dict(cls: type[_StoredStateT], json_dict: dict) -> _StoredStateT: """Initialize a stored state from a dict.""" + extra_data_dict = json_dict.get("extra_data") + extra_data = RestoredExtraData(extra_data_dict) if extra_data_dict else None last_seen = json_dict["last_seen"] if isinstance(last_seen, str): last_seen = dt_util.parse_datetime(last_seen) - return cls(cast(State, State.from_dict(json_dict["state"])), last_seen) + return cls( + cast(State, State.from_dict(json_dict["state"])), extra_data, last_seen + ) class RestoreStateData: @@ -104,7 +143,7 @@ def __init__(self, hass: HomeAssistant) -> None: hass, STORAGE_VERSION, STORAGE_KEY, encoder=JSONEncoder ) self.last_states: dict[str, StoredState] = {} - self.entity_ids: set[str] = set() + self.entities: dict[str, RestoreEntity] = {} @callback def async_get_stored_states(self) -> list[StoredState]: @@ -125,9 +164,11 @@ def async_get_stored_states(self) -> list[StoredState]: # Start with the currently registered states stored_states = [ - StoredState(state, now) + StoredState( + state, self.entities[state.entity_id].extra_restore_state_data, now + ) for state in all_states - if state.entity_id in self.entity_ids and + if state.entity_id in self.entities and # Ignore all states that are entity registry placeholders not state.attributes.get(ATTR_RESTORED) ] @@ -188,12 +229,14 @@ async def _async_dump_states_at_stop(*_: Any) -> None: ) @callback - def async_restore_entity_added(self, entity_id: str) -> None: + def async_restore_entity_added(self, entity: RestoreEntity) -> None: """Store this entity's state when hass is shutdown.""" - self.entity_ids.add(entity_id) + self.entities[entity.entity_id] = entity @callback - def async_restore_entity_removed(self, entity_id: str) -> None: + def async_restore_entity_removed( + self, entity_id: str, extra_data: ExtraStoredData | None + ) -> None: """Unregister this entity from saving state.""" # When an entity is being removed from hass, store its last state. This # allows us to support state restoration if the entity is removed, then @@ -204,9 +247,11 @@ def async_restore_entity_removed(self, entity_id: str) -> None: if state is not None: state = State.from_dict(_encode_complex(state.as_dict())) if state is not None: - self.last_states[entity_id] = StoredState(state, dt_util.utcnow()) + self.last_states[entity_id] = StoredState( + state, extra_data, dt_util.utcnow() + ) - self.entity_ids.remove(entity_id) + self.entities.pop(entity_id) def _encode(value: Any) -> Any: @@ -244,7 +289,7 @@ async def async_internal_added_to_hass(self) -> None: super().async_internal_added_to_hass(), RestoreStateData.async_get_instance(self.hass), ) - data.async_restore_entity_added(self.entity_id) + data.async_restore_entity_added(self) async def async_internal_will_remove_from_hass(self) -> None: """Run when entity will be removed from hass.""" @@ -252,10 +297,10 @@ async def async_internal_will_remove_from_hass(self) -> None: super().async_internal_will_remove_from_hass(), RestoreStateData.async_get_instance(self.hass), ) - data.async_restore_entity_removed(self.entity_id) + data.async_restore_entity_removed(self.entity_id, self.extra_restore_state_data) - async def async_get_last_state(self) -> State | None: - """Get the entity state from the previous run.""" + async def _async_get_restored_data(self) -> StoredState | None: + """Get data stored for an entity, if any.""" if self.hass is None or self.entity_id is None: # Return None if this entity isn't added to hass yet _LOGGER.warning("Cannot get last state. Entity not added to hass") # type: ignore[unreachable] @@ -265,4 +310,24 @@ async def async_get_last_state(self) -> State | None: ) if self.entity_id not in data.last_states: return None - return data.last_states[self.entity_id].state + return data.last_states[self.entity_id] + + async def async_get_last_state(self) -> State | None: + """Get the entity state from the previous run.""" + if (stored_state := await self._async_get_restored_data()) is None: + return None + return stored_state.state + + async def async_get_last_extra_data(self) -> ExtraStoredData | None: + """Get the entity specific state data from the previous run.""" + if (stored_state := await self._async_get_restored_data()) is None: + return None + return stored_state.extra_data + + @property + def extra_restore_state_data(self) -> ExtraStoredData | None: + """Return entity specific state data to be restored. + + Implemented by platform classes. + """ + return None diff --git a/tests/common.py b/tests/common.py index 3da0fcb98ddf43..c8dfb3ed841dfe 100644 --- a/tests/common.py +++ b/tests/common.py @@ -44,7 +44,7 @@ STATE_OFF, STATE_ON, ) -from homeassistant.core import BLOCK_LOG_TIMEOUT, HomeAssistant, State +from homeassistant.core import BLOCK_LOG_TIMEOUT, HomeAssistant from homeassistant.helpers import ( area_registry, device_registry, @@ -937,8 +937,33 @@ def mock_restore_cache(hass, states): json.dumps(restored_state["attributes"], cls=JSONEncoder) ), } - last_states[state.entity_id] = restore_state.StoredState( - State.from_dict(restored_state), now + last_states[state.entity_id] = restore_state.StoredState.from_dict( + {"state": restored_state, "last_seen": now} + ) + data.last_states = last_states + _LOGGER.debug("Restore cache: %s", data.last_states) + assert len(data.last_states) == len(states), f"Duplicate entity_id? {states}" + + hass.data[key] = data + + +def mock_restore_cache_with_extra_data(hass, states): + """Mock the DATA_RESTORE_CACHE.""" + key = restore_state.DATA_RESTORE_STATE_TASK + data = restore_state.RestoreStateData(hass) + now = date_util.utcnow() + + last_states = {} + for state, extra_data in states: + restored_state = state.as_dict() + restored_state = { + **restored_state, + "attributes": json.loads( + json.dumps(restored_state["attributes"], cls=JSONEncoder) + ), + } + last_states[state.entity_id] = restore_state.StoredState.from_dict( + {"state": restored_state, "extra_data": extra_data, "last_seen": now} ) data.last_states = last_states _LOGGER.debug("Restore cache: %s", data.last_states) diff --git a/tests/components/sensor/test_init.py b/tests/components/sensor/test_init.py index eed88d92d0417f..df33cb1a081583 100644 --- a/tests/components/sensor/test_init.py +++ b/tests/components/sensor/test_init.py @@ -11,10 +11,14 @@ TEMP_CELSIUS, TEMP_FAHRENHEIT, ) +from homeassistant.core import State +from homeassistant.helpers.restore_state import STORAGE_KEY as RESTORE_STATE_KEY from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM +from tests.common import mock_restore_cache_with_extra_data + @pytest.mark.parametrize( "unit_system,native_unit,state_unit,native_value,state_value", @@ -210,3 +214,131 @@ async def test_reject_timezoneless_datetime_str( "Invalid datetime: sensor.test provides state '2017-12-19 18:29:42', " "which is missing timezone information" ) in caplog.text + + +RESTORE_DATA = { + "str": {"native_unit_of_measurement": "°F", "native_value": "abc123"}, + "int": {"native_unit_of_measurement": "°F", "native_value": 123}, + "float": {"native_unit_of_measurement": "°F", "native_value": 123.0}, + "date": { + "native_unit_of_measurement": "°F", + "native_value": { + "__type": "", + "isoformat": date(2020, 2, 8).isoformat(), + }, + }, + "datetime": { + "native_unit_of_measurement": "°F", + "native_value": { + "__type": "", + "isoformat": datetime(2020, 2, 8, 15, tzinfo=timezone.utc).isoformat(), + }, + }, +} + + +# None | str | int | float | date | datetime: +@pytest.mark.parametrize( + "native_value, native_value_type, expected_extra_data, device_class", + [ + ("abc123", str, RESTORE_DATA["str"], None), + (123, int, RESTORE_DATA["int"], SensorDeviceClass.TEMPERATURE), + (123.0, float, RESTORE_DATA["float"], SensorDeviceClass.TEMPERATURE), + (date(2020, 2, 8), dict, RESTORE_DATA["date"], SensorDeviceClass.DATE), + ( + datetime(2020, 2, 8, 15, tzinfo=timezone.utc), + dict, + RESTORE_DATA["datetime"], + SensorDeviceClass.TIMESTAMP, + ), + ], +) +async def test_restore_sensor_save_state( + hass, + enable_custom_integrations, + hass_storage, + native_value, + native_value_type, + expected_extra_data, + device_class, +): + """Test RestoreSensor.""" + platform = getattr(hass.components, "test.sensor") + platform.init(empty=True) + platform.ENTITIES["0"] = platform.MockRestoreSensor( + name="Test", + native_value=native_value, + native_unit_of_measurement=TEMP_FAHRENHEIT, + device_class=device_class, + ) + + entity0 = platform.ENTITIES["0"] + assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) + await hass.async_block_till_done() + + # Trigger saving state + await hass.async_stop() + + assert len(hass_storage[RESTORE_STATE_KEY]["data"]) == 1 + state = hass_storage[RESTORE_STATE_KEY]["data"][0]["state"] + assert state["entity_id"] == entity0.entity_id + extra_data = hass_storage[RESTORE_STATE_KEY]["data"][0]["extra_data"] + assert extra_data == expected_extra_data + assert type(extra_data["native_value"]) == native_value_type + + +@pytest.mark.parametrize( + "native_value, native_value_type, extra_data, device_class, uom", + [ + ("abc123", str, RESTORE_DATA["str"], None, "°F"), + (123, int, RESTORE_DATA["int"], SensorDeviceClass.TEMPERATURE, "°F"), + (123.0, float, RESTORE_DATA["float"], SensorDeviceClass.TEMPERATURE, "°F"), + (date(2020, 2, 8), date, RESTORE_DATA["date"], SensorDeviceClass.DATE, "°F"), + ( + datetime(2020, 2, 8, 15, tzinfo=timezone.utc), + datetime, + RESTORE_DATA["datetime"], + SensorDeviceClass.TIMESTAMP, + "°F", + ), + (None, type(None), None, None, None), + (None, type(None), {}, None, None), + (None, type(None), {"beer": 123}, None, None), + ( + None, + type(None), + {"native_unit_of_measurement": "°F", "native_value": {}}, + None, + None, + ), + ], +) +async def test_restore_sensor_restore_state( + hass, + enable_custom_integrations, + hass_storage, + native_value, + native_value_type, + extra_data, + device_class, + uom, +): + """Test RestoreSensor.""" + mock_restore_cache_with_extra_data(hass, ((State("sensor.test", ""), extra_data),)) + + platform = getattr(hass.components, "test.sensor") + platform.init(empty=True) + platform.ENTITIES["0"] = platform.MockRestoreSensor( + name="Test", + device_class=device_class, + ) + + entity0 = platform.ENTITIES["0"] + assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) + await hass.async_block_till_done() + + assert hass.states.get(entity0.entity_id) + + assert entity0.native_value == native_value + assert type(entity0.native_value) == native_value_type + assert entity0.native_unit_of_measurement == uom diff --git a/tests/helpers/test_restore_state.py b/tests/helpers/test_restore_state.py index 79719b75326147..efe951342fa252 100644 --- a/tests/helpers/test_restore_state.py +++ b/tests/helpers/test_restore_state.py @@ -22,9 +22,9 @@ async def test_caching_data(hass): """Test that we cache data.""" now = dt_util.utcnow() stored_states = [ - StoredState(State("input_boolean.b0", "on"), now), - StoredState(State("input_boolean.b1", "on"), now), - StoredState(State("input_boolean.b2", "on"), now), + StoredState(State("input_boolean.b0", "on"), None, now), + StoredState(State("input_boolean.b1", "on"), None, now), + StoredState(State("input_boolean.b2", "on"), None, now), ] data = await RestoreStateData.async_get_instance(hass) @@ -160,9 +160,9 @@ async def test_hass_starting(hass): now = dt_util.utcnow() stored_states = [ - StoredState(State("input_boolean.b0", "on"), now), - StoredState(State("input_boolean.b1", "on"), now), - StoredState(State("input_boolean.b2", "on"), now), + StoredState(State("input_boolean.b0", "on"), None, now), + StoredState(State("input_boolean.b1", "on"), None, now), + StoredState(State("input_boolean.b2", "on"), None, now), ] data = await RestoreStateData.async_get_instance(hass) @@ -225,15 +225,16 @@ async def test_dump_data(hass): data = await RestoreStateData.async_get_instance(hass) now = dt_util.utcnow() data.last_states = { - "input_boolean.b0": StoredState(State("input_boolean.b0", "off"), now), - "input_boolean.b1": StoredState(State("input_boolean.b1", "off"), now), - "input_boolean.b2": StoredState(State("input_boolean.b2", "off"), now), - "input_boolean.b3": StoredState(State("input_boolean.b3", "off"), now), + "input_boolean.b0": StoredState(State("input_boolean.b0", "off"), None, now), + "input_boolean.b1": StoredState(State("input_boolean.b1", "off"), None, now), + "input_boolean.b2": StoredState(State("input_boolean.b2", "off"), None, now), + "input_boolean.b3": StoredState(State("input_boolean.b3", "off"), None, now), "input_boolean.b4": StoredState( State("input_boolean.b4", "off"), + None, datetime(1985, 10, 26, 1, 22, tzinfo=dt_util.UTC), ), - "input_boolean.b5": StoredState(State("input_boolean.b5", "off"), now), + "input_boolean.b5": StoredState(State("input_boolean.b5", "off"), None, now), } with patch( diff --git a/tests/testing_config/custom_components/test/sensor.py b/tests/testing_config/custom_components/test/sensor.py index 4ad2580ad8bc32..56587c80c3480b 100644 --- a/tests/testing_config/custom_components/test/sensor.py +++ b/tests/testing_config/custom_components/test/sensor.py @@ -5,6 +5,7 @@ """ from homeassistant.components.sensor import ( DEVICE_CLASSES, + RestoreSensor, SensorDeviceClass, SensorEntity, ) @@ -109,3 +110,17 @@ def native_value(self): def state_class(self): """Return the state class of this sensor.""" return self._handle("state_class") + + +class MockRestoreSensor(MockSensor, RestoreSensor): + """Mock RestoreSensor class.""" + + async def async_added_to_hass(self) -> None: + """Restore native_value and native_unit_of_measurement.""" + await super().async_added_to_hass() + if (last_sensor_data := await self.async_get_last_sensor_data()) is None: + return + self._values["native_value"] = last_sensor_data.native_value + self._values[ + "native_unit_of_measurement" + ] = last_sensor_data.native_unit_of_measurement From 911e488d484f452848e1781a6a2ba839ceaf9e87 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 8 Feb 2022 23:00:53 +0100 Subject: [PATCH 0447/1098] Fix ENTITY_CATEGORIES_SCHEMA (#66108) Co-authored-by: Paulus Schoutsen --- homeassistant/components/knx/schema.py | 26 +++++++++---------- .../components/mobile_app/webhook.py | 4 +-- homeassistant/components/mqtt/mixins.py | 4 +-- homeassistant/helpers/entity.py | 10 +++++-- tests/helpers/test_entity.py | 25 ++++++++++++++++++ 5 files changed, 50 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index c118e56f9e3285..f5f8875bb5fc80 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -35,7 +35,7 @@ Platform, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import ENTITY_CATEGORIES_SCHEMA +from homeassistant.helpers.entity import validate_entity_category from .const import ( CONF_INVERT, @@ -320,7 +320,7 @@ class BinarySensorSchema(KNXPlatformSchema): ), vol.Optional(CONF_DEVICE_CLASS): BINARY_SENSOR_DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_RESET_AFTER): cv.positive_float, - vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, + vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, } ), ) @@ -356,7 +356,7 @@ class ButtonSchema(KNXPlatformSchema): vol.Exclusive( CONF_TYPE, "length_or_type", msg=length_or_type_msg ): object, - vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, + vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, } ), vol.Any( @@ -500,7 +500,7 @@ class ClimateSchema(KNXPlatformSchema): ): vol.In(HVAC_MODES), vol.Optional(CONF_MIN_TEMP): vol.Coerce(float), vol.Optional(CONF_MAX_TEMP): vol.Coerce(float), - vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, + vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, } ), ) @@ -555,7 +555,7 @@ class CoverSchema(KNXPlatformSchema): vol.Optional(CONF_INVERT_POSITION, default=False): cv.boolean, vol.Optional(CONF_INVERT_ANGLE, default=False): cv.boolean, vol.Optional(CONF_DEVICE_CLASS): COVER_DEVICE_CLASSES_SCHEMA, - vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, + vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, } ), ) @@ -618,7 +618,7 @@ class FanSchema(KNXPlatformSchema): vol.Optional(CONF_OSCILLATION_ADDRESS): ga_list_validator, vol.Optional(CONF_OSCILLATION_STATE_ADDRESS): ga_list_validator, vol.Optional(CONF_MAX_STEP): cv.byte, - vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, + vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, } ) @@ -722,7 +722,7 @@ class LightSchema(KNXPlatformSchema): vol.Optional(CONF_MAX_KELVIN, default=DEFAULT_MAX_KELVIN): vol.All( vol.Coerce(int), vol.Range(min=1) ), - vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, + vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, } ), vol.Any( @@ -802,7 +802,7 @@ class NumberSchema(KNXPlatformSchema): vol.Optional(CONF_MAX): vol.Coerce(float), vol.Optional(CONF_MIN): vol.Coerce(float), vol.Optional(CONF_STEP): cv.positive_float, - vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, + vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, } ), number_limit_sub_validator, @@ -824,7 +824,7 @@ class SceneSchema(KNXPlatformSchema): vol.Required(CONF_SCENE_NUMBER): vol.All( vol.Coerce(int), vol.Range(min=1, max=64) ), - vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, + vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, } ) @@ -855,7 +855,7 @@ class SelectSchema(KNXPlatformSchema): ], vol.Required(KNX_ADDRESS): ga_list_validator, vol.Optional(CONF_STATE_ADDRESS): ga_list_validator, - vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, + vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, } ), select_options_sub_validator, @@ -880,7 +880,7 @@ class SensorSchema(KNXPlatformSchema): vol.Optional(CONF_STATE_CLASS): STATE_CLASSES_SCHEMA, vol.Required(CONF_TYPE): sensor_type_validator, vol.Required(CONF_STATE_ADDRESS): ga_list_validator, - vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, + vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, } ) @@ -901,7 +901,7 @@ class SwitchSchema(KNXPlatformSchema): vol.Optional(CONF_RESPOND_TO_READ, default=False): cv.boolean, vol.Required(KNX_ADDRESS): ga_list_validator, vol.Optional(CONF_STATE_ADDRESS): ga_list_validator, - vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, + vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, } ) @@ -948,7 +948,7 @@ class WeatherSchema(KNXPlatformSchema): vol.Optional(CONF_KNX_DAY_NIGHT_ADDRESS): ga_list_validator, vol.Optional(CONF_KNX_AIR_PRESSURE_ADDRESS): ga_list_validator, vol.Optional(CONF_KNX_HUMIDITY_ADDRESS): ga_list_validator, - vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, + vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, } ), ) diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index d093fd234061b3..d659d7625c17ca 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -44,7 +44,7 @@ template, ) from homeassistant.helpers.dispatcher import async_dispatcher_send -from homeassistant.helpers.entity import ENTITY_CATEGORIES_SCHEMA +from homeassistant.helpers.entity import validate_entity_category from homeassistant.util.decorator import Registry from .const import ( @@ -423,7 +423,7 @@ def _validate_state_class_sensor(value: dict): vol.Optional(ATTR_SENSOR_STATE, default=None): vol.Any( None, bool, str, int, float ), - vol.Optional(ATTR_SENSOR_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, + vol.Optional(ATTR_SENSOR_ENTITY_CATEGORY): validate_entity_category, vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon, vol.Optional(ATTR_SENSOR_STATE_CLASS): vol.In(SENSOSR_STATE_CLASSES), }, diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 177c7d9b57faaa..fe989acb98e97c 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -34,11 +34,11 @@ async_dispatcher_send, ) from homeassistant.helpers.entity import ( - ENTITY_CATEGORIES_SCHEMA, DeviceInfo, Entity, EntityCategory, async_generate_entity_id, + validate_entity_category, ) from homeassistant.helpers.typing import ConfigType @@ -200,7 +200,7 @@ def validate_device_has_at_least_one_identifier(value: ConfigType) -> ConfigType { vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_ENABLED_BY_DEFAULT, default=True): cv.boolean, - vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, + vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, vol.Optional(CONF_ICON): cv.icon, vol.Optional(CONF_JSON_ATTRS_TOPIC): valid_subscribe_topic, vol.Optional(CONF_JSON_ATTRS_TEMPLATE): cv.template, diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index a716e465450c59..b670c734b47caf 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -11,7 +11,7 @@ import math import sys from timeit import default_timer as timer -from typing import Any, Final, Literal, TypedDict, final +from typing import Any, Literal, TypedDict, final import voluptuous as vol @@ -58,7 +58,13 @@ FLOAT_PRECISION = abs(int(math.floor(math.log10(abs(sys.float_info.epsilon))))) - 1 -ENTITY_CATEGORIES_SCHEMA: Final = vol.In(ENTITY_CATEGORIES) +def validate_entity_category(value: Any | None) -> EntityCategory: + """Validate entity category configuration.""" + value = vol.In(ENTITY_CATEGORIES)(value) + return EntityCategory(value) + + +ENTITY_CATEGORIES_SCHEMA = validate_entity_category @callback diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py index 1d28c50b9a1b3e..6b7de074a24dba 100644 --- a/tests/helpers/test_entity.py +++ b/tests/helpers/test_entity.py @@ -6,6 +6,7 @@ from unittest.mock import MagicMock, PropertyMock, patch import pytest +import voluptuous as vol from homeassistant.const import ( ATTR_ATTRIBUTION, @@ -829,3 +830,27 @@ async def test_entity_category_property(hass): ) mock_entity2.entity_id = "hello.world" assert mock_entity2.entity_category == "config" + + +@pytest.mark.parametrize( + "value,expected", + ( + ("config", entity.EntityCategory.CONFIG), + ("diagnostic", entity.EntityCategory.DIAGNOSTIC), + ("system", entity.EntityCategory.SYSTEM), + ), +) +def test_entity_category_schema(value, expected): + """Test entity category schema.""" + schema = vol.Schema(entity.ENTITY_CATEGORIES_SCHEMA) + result = schema(value) + assert result == expected + assert isinstance(result, entity.EntityCategory) + + +@pytest.mark.parametrize("value", (None, "non_existing")) +def test_entity_category_schema_error(value): + """Test entity category schema.""" + schema = vol.Schema(entity.ENTITY_CATEGORIES_SCHEMA) + with pytest.raises(vol.Invalid): + schema(value) From b012b7916728ce5aa42775157c8f4f02cb18264a Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 8 Feb 2022 23:03:37 +0100 Subject: [PATCH 0448/1098] Adapt deCONZ number platform to align with updated design of binary sensor and sensor platforms (#65248) * Adapt number to align with binary sensor and sensor platforms * Make number tests easier to expand --- homeassistant/components/deconz/number.py | 44 ++++---- tests/components/deconz/test_number.py | 120 ++++++++++++++++------ 2 files changed, 113 insertions(+), 51 deletions(-) diff --git a/homeassistant/components/deconz/number.py b/homeassistant/components/deconz/number.py index fff70b9f7b5e63..bf138aaef639fe 100644 --- a/homeassistant/components/deconz/number.py +++ b/homeassistant/components/deconz/number.py @@ -2,10 +2,10 @@ from __future__ import annotations -from collections.abc import ValuesView +from collections.abc import Callable, ValuesView from dataclasses import dataclass -from pydeconz.sensor import PRESENCE_DELAY, Presence +from pydeconz.sensor import PRESENCE_DELAY, DeconzSensor as PydeconzSensor, Presence from homeassistant.components.number import ( DOMAIN, @@ -23,33 +23,30 @@ @dataclass -class DeconzNumberEntityDescriptionBase: +class DeconzNumberDescriptionMixin: """Required values when describing deCONZ number entities.""" - device_property: str suffix: str update_key: str + value_fn: Callable[[PydeconzSensor], bool | None] @dataclass -class DeconzNumberEntityDescription( - NumberEntityDescription, DeconzNumberEntityDescriptionBase -): +class DeconzNumberDescription(NumberEntityDescription, DeconzNumberDescriptionMixin): """Class describing deCONZ number entities.""" - entity_category = EntityCategory.CONFIG - ENTITY_DESCRIPTIONS = { Presence: [ - DeconzNumberEntityDescription( + DeconzNumberDescription( key="delay", - device_property="delay", + value_fn=lambda device: device.delay, suffix="Delay", update_key=PRESENCE_DELAY, max_value=65535, min_value=0, step=1, + entity_category=EntityCategory.CONFIG, ) ] } @@ -76,15 +73,18 @@ def async_add_sensor( if sensor.type.startswith("CLIP"): continue - known_number_entities = set(gateway.entities[DOMAIN]) + known_entities = set(gateway.entities[DOMAIN]) for description in ENTITY_DESCRIPTIONS.get(type(sensor), []): - if getattr(sensor, description.device_property) is None: + if ( + not hasattr(sensor, description.key) + or description.value_fn(sensor) is None + ): continue - new_number_entity = DeconzNumber(sensor, gateway, description) - if new_number_entity.unique_id not in known_number_entities: - entities.append(new_number_entity) + new_entity = DeconzNumber(sensor, gateway, description) + if new_entity.unique_id not in known_entities: + entities.append(new_entity) if entities: async_add_entities(entities) @@ -112,29 +112,29 @@ def __init__( self, device: Presence, gateway: DeconzGateway, - description: DeconzNumberEntityDescription, + description: DeconzNumberDescription, ) -> None: """Initialize deCONZ number entity.""" - self.entity_description: DeconzNumberEntityDescription = description + self.entity_description: DeconzNumberDescription = description super().__init__(device, gateway) self._attr_name = f"{device.name} {description.suffix}" + self._update_keys = {self.entity_description.update_key, "reachable"} @callback def async_update_callback(self) -> None: """Update the number value.""" - keys = {self.entity_description.update_key, "reachable"} - if self._device.changed_keys.intersection(keys): + if self._device.changed_keys.intersection(self._update_keys): super().async_update_callback() @property def value(self) -> float: """Return the value of the sensor property.""" - return getattr(self._device, self.entity_description.device_property) # type: ignore[no-any-return] + return self.entity_description.value_fn(self._device) # type: ignore[no-any-return] async def async_set_value(self, value: float) -> None: """Set sensor config.""" - data = {self.entity_description.device_property: int(value)} + data = {self.entity_description.key: int(value)} await self._device.set_config(**data) @property diff --git a/tests/components/deconz/test_number.py b/tests/components/deconz/test_number.py index 0cf0650e3d12bb..e0c469a1ba2f97 100644 --- a/tests/components/deconz/test_number.py +++ b/tests/components/deconz/test_number.py @@ -10,6 +10,8 @@ SERVICE_SET_VALUE, ) from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE +from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers.entity import EntityCategory from .test_gateway import ( DECONZ_WEB_REQUEST, @@ -24,29 +26,79 @@ async def test_no_number_entities(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_binary_sensors(hass, aioclient_mock, mock_deconz_websocket): - """Test successful creation of binary sensor entities.""" - data = { - "sensors": { - "0": { - "name": "Presence sensor", - "type": "ZHAPresence", - "state": {"dark": False, "presence": False}, - "config": { - "delay": 0, - "on": True, - "reachable": True, - "temperature": 10, - }, - "uniqueid": "00:00:00:00:00:00:00:00-00", +TEST_DATA = [ + ( # Presence sensor - delay configuration + { + "name": "Presence sensor", + "type": "ZHAPresence", + "state": {"dark": False, "presence": False}, + "config": { + "delay": 0, + "on": True, + "reachable": True, + "temperature": 10, }, - } - } - with patch.dict(DECONZ_WEB_REQUEST, data): + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, + { + "entity_count": 3, + "device_count": 3, + "entity_id": "number.presence_sensor_delay", + "unique_id": "00:00:00:00:00:00:00:00-delay", + "state": "0", + "entity_category": EntityCategory.CONFIG, + "attributes": { + "min": 0, + "max": 65535, + "step": 1, + "mode": "auto", + "friendly_name": "Presence sensor Delay", + }, + "websocket_event": {"config": {"delay": 10}}, + "next_state": "10", + "supported_service_value": 111, + "supported_service_response": {"delay": 111}, + "unsupported_service_value": 0.1, + "unsupported_service_response": {"delay": 0}, + "out_of_range_service_value": 66666, + }, + ) +] + + +@pytest.mark.parametrize("sensor_data, expected", TEST_DATA) +async def test_number_entities( + hass, aioclient_mock, mock_deconz_websocket, sensor_data, expected +): + """Test successful creation of number entities.""" + ent_reg = er.async_get(hass) + dev_reg = dr.async_get(hass) + + with patch.dict(DECONZ_WEB_REQUEST, {"sensors": {"0": sensor_data}}): config_entry = await setup_deconz_integration(hass, aioclient_mock) - assert len(hass.states.async_all()) == 3 - assert hass.states.get("number.presence_sensor_delay").state == "0" + assert len(hass.states.async_all()) == expected["entity_count"] + + # Verify state data + + entity = hass.states.get(expected["entity_id"]) + assert entity.state == expected["state"] + assert entity.attributes == expected["attributes"] + + # Verify entity registry data + + ent_reg_entry = ent_reg.async_get(expected["entity_id"]) + assert ent_reg_entry.entity_category is expected["entity_category"] + assert ent_reg_entry.unique_id == expected["unique_id"] + + # Verify device registry data + + assert ( + len(dr.async_entries_for_config_entry(dev_reg, config_entry.entry_id)) + == expected["device_count"] + ) + + # Change state event_changed_sensor = { "t": "event", @@ -57,8 +109,7 @@ async def test_binary_sensors(hass, aioclient_mock, mock_deconz_websocket): } await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() - - assert hass.states.get("number.presence_sensor_delay").state == "10" + assert hass.states.get(expected["entity_id"]).state == expected["next_state"] # Verify service calls @@ -69,20 +120,26 @@ async def test_binary_sensors(hass, aioclient_mock, mock_deconz_websocket): await hass.services.async_call( NUMBER_DOMAIN, SERVICE_SET_VALUE, - {ATTR_ENTITY_ID: "number.presence_sensor_delay", ATTR_VALUE: 111}, + { + ATTR_ENTITY_ID: expected["entity_id"], + ATTR_VALUE: expected["supported_service_value"], + }, blocking=True, ) - assert aioclient_mock.mock_calls[1][2] == {"delay": 111} + assert aioclient_mock.mock_calls[1][2] == expected["supported_service_response"] # Service set float value await hass.services.async_call( NUMBER_DOMAIN, SERVICE_SET_VALUE, - {ATTR_ENTITY_ID: "number.presence_sensor_delay", ATTR_VALUE: 0.1}, + { + ATTR_ENTITY_ID: expected["entity_id"], + ATTR_VALUE: expected["unsupported_service_value"], + }, blocking=True, ) - assert aioclient_mock.mock_calls[2][2] == {"delay": 0} + assert aioclient_mock.mock_calls[2][2] == expected["unsupported_service_response"] # Service set value beyond the supported range @@ -90,15 +147,20 @@ async def test_binary_sensors(hass, aioclient_mock, mock_deconz_websocket): await hass.services.async_call( NUMBER_DOMAIN, SERVICE_SET_VALUE, - {ATTR_ENTITY_ID: "number.presence_sensor_delay", ATTR_VALUE: 66666}, + { + ATTR_ENTITY_ID: expected["entity_id"], + ATTR_VALUE: expected["out_of_range_service_value"], + }, blocking=True, ) + # Unload entry + await hass.config_entries.async_unload(config_entry.entry_id) + assert hass.states.get(expected["entity_id"]).state == STATE_UNAVAILABLE - assert hass.states.get("number.presence_sensor_delay").state == STATE_UNAVAILABLE + # Remove entry await hass.config_entries.async_remove(config_entry.entry_id) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 0 From b9f21d4e0720723ff762deb7c462e5fda895ec0e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 8 Feb 2022 23:07:13 +0100 Subject: [PATCH 0449/1098] Improve typing of Spotify (#66109) Co-authored-by: Martin Hjelmare --- homeassistant/components/spotify/__init__.py | 24 +- .../components/spotify/config_flow.py | 23 +- .../components/spotify/media_player.py | 353 ++++++++++-------- homeassistant/components/spotify/strings.json | 2 +- mypy.ini | 6 - script/hassfest/mypy_config.py | 2 - tests/components/spotify/test_config_flow.py | 26 +- 7 files changed, 250 insertions(+), 186 deletions(-) diff --git a/homeassistant/components/spotify/__init__.py b/homeassistant/components/spotify/__init__.py index 5c36a0c71c3b37..a2a1fee50f2118 100644 --- a/homeassistant/components/spotify/__init__.py +++ b/homeassistant/components/spotify/__init__.py @@ -4,7 +4,7 @@ from spotipy import Spotify, SpotifyException import voluptuous as vol -from homeassistant.components.media_player import BrowseError +from homeassistant.components.media_player import BrowseError, BrowseMedia from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_CREDENTIALS, @@ -47,19 +47,23 @@ PLATFORMS = [Platform.MEDIA_PLAYER] -def is_spotify_media_type(media_content_type): +def is_spotify_media_type(media_content_type: str) -> bool: """Return whether the media_content_type is a valid Spotify media_id.""" return media_content_type.startswith(MEDIA_PLAYER_PREFIX) -def resolve_spotify_media_type(media_content_type): +def resolve_spotify_media_type(media_content_type: str) -> str: """Return actual spotify media_content_type.""" return media_content_type[len(MEDIA_PLAYER_PREFIX) :] async def async_browse_media( - hass, media_content_type, media_content_id, *, can_play_artist=True -): + hass: HomeAssistant, + media_content_type: str, + media_content_id: str, + *, + can_play_artist: bool = True, +) -> BrowseMedia: """Browse Spotify media.""" if not (info := next(iter(hass.data[DOMAIN].values()), None)): raise BrowseError("No Spotify accounts available") @@ -128,12 +132,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload Spotify config entry.""" - # Unload entities for this entry/device. - unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - - # Cleanup - del hass.data[DOMAIN][entry.entry_id] - if not hass.data[DOMAIN]: - del hass.data[DOMAIN] - + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + del hass.data[DOMAIN][entry.entry_id] return unload_ok diff --git a/homeassistant/components/spotify/config_flow.py b/homeassistant/components/spotify/config_flow.py index 33e5e67a244140..bd01fa64acb827 100644 --- a/homeassistant/components/spotify/config_flow.py +++ b/homeassistant/components/spotify/config_flow.py @@ -8,6 +8,7 @@ import voluptuous as vol from homeassistant.components import persistent_notification +from homeassistant.config_entries import ConfigEntry from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_entry_oauth2_flow @@ -22,10 +23,7 @@ class SpotifyFlowHandler( DOMAIN = DOMAIN VERSION = 1 - def __init__(self) -> None: - """Instantiate config flow.""" - super().__init__() - self.entry: dict[str, Any] | None = None + reauth_entry: ConfigEntry | None = None @property def logger(self) -> logging.Logger: @@ -48,7 +46,7 @@ async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult: name = data["id"] = current_user["id"] - if self.entry and self.entry["id"] != current_user["id"]: + if self.reauth_entry and self.reauth_entry.data["id"] != current_user["id"]: return self.async_abort(reason="reauth_account_mismatch") if current_user.get("display_name"): @@ -61,8 +59,9 @@ async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult: async def async_step_reauth(self, entry: dict[str, Any]) -> FlowResult: """Perform reauth upon migration of old entries.""" - if entry: - self.entry = entry + self.reauth_entry = self.hass.config_entries.async_get_entry( + self.context["entry_id"] + ) persistent_notification.async_create( self.hass, @@ -77,16 +76,18 @@ async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Confirm reauth dialog.""" - if user_input is None: + if self.reauth_entry is None: + return self.async_abort(reason="reauth_account_mismatch") + + if user_input is None and self.reauth_entry: return self.async_show_form( step_id="reauth_confirm", - description_placeholders={"account": self.entry["id"]}, + description_placeholders={"account": self.reauth_entry.data["id"]}, data_schema=vol.Schema({}), errors={}, ) persistent_notification.async_dismiss(self.hass, "spotify_reauth") - return await self.async_step_pick_implementation( - user_input={"implementation": self.entry["auth_implementation"]} + user_input={"implementation": self.reauth_entry.data["auth_implementation"]} ) diff --git a/homeassistant/components/spotify/media_player.py b/homeassistant/components/spotify/media_player.py index e279b150883053..bdb0ea8b959345 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -6,6 +6,7 @@ from datetime import timedelta from functools import partial import logging +from typing import Any import requests from spotipy import Spotify, SpotifyException @@ -128,57 +129,57 @@ class BrowsableMedia(StrEnum): LIBRARY_MAP = { - BrowsableMedia.CURRENT_USER_PLAYLISTS: "Playlists", - BrowsableMedia.CURRENT_USER_FOLLOWED_ARTISTS: "Artists", - BrowsableMedia.CURRENT_USER_SAVED_ALBUMS: "Albums", - BrowsableMedia.CURRENT_USER_SAVED_TRACKS: "Tracks", - BrowsableMedia.CURRENT_USER_SAVED_SHOWS: "Podcasts", - BrowsableMedia.CURRENT_USER_RECENTLY_PLAYED: "Recently played", - BrowsableMedia.CURRENT_USER_TOP_ARTISTS: "Top Artists", - BrowsableMedia.CURRENT_USER_TOP_TRACKS: "Top Tracks", - BrowsableMedia.CATEGORIES: "Categories", - BrowsableMedia.FEATURED_PLAYLISTS: "Featured Playlists", - BrowsableMedia.NEW_RELEASES: "New Releases", + BrowsableMedia.CURRENT_USER_PLAYLISTS.value: "Playlists", + BrowsableMedia.CURRENT_USER_FOLLOWED_ARTISTS.value: "Artists", + BrowsableMedia.CURRENT_USER_SAVED_ALBUMS.value: "Albums", + BrowsableMedia.CURRENT_USER_SAVED_TRACKS.value: "Tracks", + BrowsableMedia.CURRENT_USER_SAVED_SHOWS.value: "Podcasts", + BrowsableMedia.CURRENT_USER_RECENTLY_PLAYED.value: "Recently played", + BrowsableMedia.CURRENT_USER_TOP_ARTISTS.value: "Top Artists", + BrowsableMedia.CURRENT_USER_TOP_TRACKS.value: "Top Tracks", + BrowsableMedia.CATEGORIES.value: "Categories", + BrowsableMedia.FEATURED_PLAYLISTS.value: "Featured Playlists", + BrowsableMedia.NEW_RELEASES.value: "New Releases", } -CONTENT_TYPE_MEDIA_CLASS = { - BrowsableMedia.CURRENT_USER_PLAYLISTS: { +CONTENT_TYPE_MEDIA_CLASS: dict[str, Any] = { + BrowsableMedia.CURRENT_USER_PLAYLISTS.value: { "parent": MEDIA_CLASS_DIRECTORY, "children": MEDIA_CLASS_PLAYLIST, }, - BrowsableMedia.CURRENT_USER_FOLLOWED_ARTISTS: { + BrowsableMedia.CURRENT_USER_FOLLOWED_ARTISTS.value: { "parent": MEDIA_CLASS_DIRECTORY, "children": MEDIA_CLASS_ARTIST, }, - BrowsableMedia.CURRENT_USER_SAVED_ALBUMS: { + BrowsableMedia.CURRENT_USER_SAVED_ALBUMS.value: { "parent": MEDIA_CLASS_DIRECTORY, "children": MEDIA_CLASS_ALBUM, }, - BrowsableMedia.CURRENT_USER_SAVED_TRACKS: { + BrowsableMedia.CURRENT_USER_SAVED_TRACKS.value: { "parent": MEDIA_CLASS_DIRECTORY, "children": MEDIA_CLASS_TRACK, }, - BrowsableMedia.CURRENT_USER_SAVED_SHOWS: { + BrowsableMedia.CURRENT_USER_SAVED_SHOWS.value: { "parent": MEDIA_CLASS_DIRECTORY, "children": MEDIA_CLASS_PODCAST, }, - BrowsableMedia.CURRENT_USER_RECENTLY_PLAYED: { + BrowsableMedia.CURRENT_USER_RECENTLY_PLAYED.value: { "parent": MEDIA_CLASS_DIRECTORY, "children": MEDIA_CLASS_TRACK, }, - BrowsableMedia.CURRENT_USER_TOP_ARTISTS: { + BrowsableMedia.CURRENT_USER_TOP_ARTISTS.value: { "parent": MEDIA_CLASS_DIRECTORY, "children": MEDIA_CLASS_ARTIST, }, - BrowsableMedia.CURRENT_USER_TOP_TRACKS: { + BrowsableMedia.CURRENT_USER_TOP_TRACKS.value: { "parent": MEDIA_CLASS_DIRECTORY, "children": MEDIA_CLASS_TRACK, }, - BrowsableMedia.FEATURED_PLAYLISTS: { + BrowsableMedia.FEATURED_PLAYLISTS.value: { "parent": MEDIA_CLASS_DIRECTORY, "children": MEDIA_CLASS_PLAYLIST, }, - BrowsableMedia.CATEGORIES: { + BrowsableMedia.CATEGORIES.value: { "parent": MEDIA_CLASS_DIRECTORY, "children": MEDIA_CLASS_GENRE, }, @@ -186,7 +187,7 @@ class BrowsableMedia(StrEnum): "parent": MEDIA_CLASS_DIRECTORY, "children": MEDIA_CLASS_PLAYLIST, }, - BrowsableMedia.NEW_RELEASES: { + BrowsableMedia.NEW_RELEASES.value: { "parent": MEDIA_CLASS_DIRECTORY, "children": MEDIA_CLASS_ALBUM, }, @@ -276,7 +277,7 @@ def __init__( self._attr_unique_id = user_id @property - def _me(self) -> dict: + def _me(self) -> dict[str, Any]: """Return spotify user info.""" return self._spotify_data[DATA_SPOTIFY_ME] @@ -319,23 +320,30 @@ def state(self) -> str | None: @property def volume_level(self) -> float | None: """Return the device volume.""" + if not self._currently_playing: + return None return self._currently_playing.get("device", {}).get("volume_percent", 0) / 100 @property def media_content_id(self) -> str | None: """Return the media URL.""" + if not self._currently_playing: + return None item = self._currently_playing.get("item") or {} return item.get("uri") @property def media_duration(self) -> int | None: """Duration of current playing media in seconds.""" - if self._currently_playing.get("item") is None: + if ( + self._currently_playing is None + or self._currently_playing.get("item") is None + ): return None return self._currently_playing["item"]["duration_ms"] / 1000 @property - def media_position(self) -> str | None: + def media_position(self) -> int | None: """Position of current playing media in seconds.""" if not self._currently_playing: return None @@ -352,7 +360,8 @@ def media_position_updated_at(self) -> dt.datetime | None: def media_image_url(self) -> str | None: """Return the media image URL.""" if ( - self._currently_playing.get("item") is None + not self._currently_playing + or self._currently_playing.get("item") is None or not self._currently_playing["item"]["album"]["images"] ): return None @@ -361,13 +370,15 @@ def media_image_url(self) -> str | None: @property def media_title(self) -> str | None: """Return the media title.""" + if not self._currently_playing: + return None item = self._currently_playing.get("item") or {} return item.get("name") @property def media_artist(self) -> str | None: """Return the media artist.""" - if self._currently_playing.get("item") is None: + if not self._currently_playing or self._currently_playing.get("item") is None: return None return ", ".join( artist["name"] for artist in self._currently_playing["item"]["artists"] @@ -376,13 +387,15 @@ def media_artist(self) -> str | None: @property def media_album_name(self) -> str | None: """Return the media album.""" - if self._currently_playing.get("item") is None: + if not self._currently_playing or self._currently_playing.get("item") is None: return None return self._currently_playing["item"]["album"]["name"] @property def media_track(self) -> int | None: """Track number of current playing media, music track only.""" + if not self._currently_playing: + return None item = self._currently_playing.get("item") or {} return item.get("track_number") @@ -396,6 +409,8 @@ def media_playlist(self): @property def source(self) -> str | None: """Return the current playback device.""" + if not self._currently_playing: + return None return self._currently_playing.get("device", {}).get("name") @property @@ -406,14 +421,20 @@ def source_list(self) -> list[str] | None: return [device["name"] for device in self._devices] @property - def shuffle(self) -> bool: + def shuffle(self) -> bool | None: """Shuffling state.""" - return bool(self._currently_playing.get("shuffle_state")) + if not self._currently_playing: + return None + return self._currently_playing.get("shuffle_state") @property def repeat(self) -> str | None: """Return current repeat mode.""" - repeat_state = self._currently_playing.get("repeat_state") + if ( + not self._currently_playing + or (repeat_state := self._currently_playing.get("repeat_state")) is None + ): + return None return REPEAT_MODE_MAPPING_TO_HA.get(repeat_state) @property @@ -473,7 +494,11 @@ def play_media(self, media_type: str, media_id: str, **kwargs) -> None: _LOGGER.error("Media type %s is not supported", media_type) return - if not self._currently_playing.get("device") and self._devices: + if ( + self._currently_playing + and not self._currently_playing.get("device") + and self._devices + ): kwargs["device_id"] = self._devices[0].get("id") self._spotify.start_playback(**kwargs) @@ -481,6 +506,9 @@ def play_media(self, media_type: str, media_id: str, **kwargs) -> None: @spotify_exception_handler def select_source(self, source: str) -> None: """Select playback device.""" + if not self._devices: + return + for device in self._devices: if device["name"] == source: self._spotify.transfer_playback( @@ -525,7 +553,9 @@ def update(self) -> None: devices = self._spotify.devices() or {} self._devices = devices.get("devices", []) - async def async_browse_media(self, media_content_type=None, media_content_id=None): + async def async_browse_media( + self, media_content_type: str | None = None, media_content_id: str | None = None + ) -> BrowseMedia: """Implement the websocket media browsing helper.""" if not self._scope_ok: @@ -545,15 +575,15 @@ async def async_browse_media(self, media_content_type=None, media_content_id=Non async def async_browse_media_internal( - hass, - spotify, - session, - current_user, - media_content_type, - media_content_id, + hass: HomeAssistant, + spotify: Spotify, + session: OAuth2Session, + current_user: dict[str, Any], + media_content_type: str | None, + media_content_id: str | None, *, - can_play_artist=True, -): + can_play_artist: bool = True, +) -> BrowseMedia: """Browse spotify media.""" if media_content_type in (None, f"{MEDIA_PLAYER_PREFIX}library"): return await hass.async_add_executor_job( @@ -563,7 +593,8 @@ async def async_browse_media_internal( await session.async_ensure_token_valid() # Strip prefix - media_content_type = media_content_type[len(MEDIA_PLAYER_PREFIX) :] + if media_content_type: + media_content_type = media_content_type[len(MEDIA_PLAYER_PREFIX) :] payload = { "media_content_type": media_content_type, @@ -583,76 +614,91 @@ async def async_browse_media_internal( return response -def build_item_response(spotify, user, payload, *, can_play_artist): # noqa: C901 +def build_item_response( # noqa: C901 + spotify: Spotify, + user: dict[str, Any], + payload: dict[str, str | None], + *, + can_play_artist: bool, +) -> BrowseMedia | None: """Create response payload for the provided media query.""" media_content_type = payload["media_content_type"] media_content_id = payload["media_content_id"] + + if media_content_type is None or media_content_id is None: + return None + title = None image = None + media: dict[str, Any] | None = None + items = [] + if media_content_type == BrowsableMedia.CURRENT_USER_PLAYLISTS: - media = spotify.current_user_playlists(limit=BROWSE_LIMIT) - items = media.get("items", []) + if media := spotify.current_user_playlists(limit=BROWSE_LIMIT): + items = media.get("items", []) elif media_content_type == BrowsableMedia.CURRENT_USER_FOLLOWED_ARTISTS: - media = spotify.current_user_followed_artists(limit=BROWSE_LIMIT) - items = media.get("artists", {}).get("items", []) + if media := spotify.current_user_followed_artists(limit=BROWSE_LIMIT): + items = media.get("artists", {}).get("items", []) elif media_content_type == BrowsableMedia.CURRENT_USER_SAVED_ALBUMS: - media = spotify.current_user_saved_albums(limit=BROWSE_LIMIT) - items = [item["album"] for item in media.get("items", [])] + if media := spotify.current_user_saved_albums(limit=BROWSE_LIMIT): + items = [item["album"] for item in media.get("items", [])] elif media_content_type == BrowsableMedia.CURRENT_USER_SAVED_TRACKS: - media = spotify.current_user_saved_tracks(limit=BROWSE_LIMIT) - items = [item["track"] for item in media.get("items", [])] + if media := spotify.current_user_saved_tracks(limit=BROWSE_LIMIT): + items = [item["track"] for item in media.get("items", [])] elif media_content_type == BrowsableMedia.CURRENT_USER_SAVED_SHOWS: - media = spotify.current_user_saved_shows(limit=BROWSE_LIMIT) - items = [item["show"] for item in media.get("items", [])] + if media := spotify.current_user_saved_shows(limit=BROWSE_LIMIT): + items = [item["show"] for item in media.get("items", [])] elif media_content_type == BrowsableMedia.CURRENT_USER_RECENTLY_PLAYED: - media = spotify.current_user_recently_played(limit=BROWSE_LIMIT) - items = [item["track"] for item in media.get("items", [])] + if media := spotify.current_user_recently_played(limit=BROWSE_LIMIT): + items = [item["track"] for item in media.get("items", [])] elif media_content_type == BrowsableMedia.CURRENT_USER_TOP_ARTISTS: - media = spotify.current_user_top_artists(limit=BROWSE_LIMIT) - items = media.get("items", []) + if media := spotify.current_user_top_artists(limit=BROWSE_LIMIT): + items = media.get("items", []) elif media_content_type == BrowsableMedia.CURRENT_USER_TOP_TRACKS: - media = spotify.current_user_top_tracks(limit=BROWSE_LIMIT) - items = media.get("items", []) + if media := spotify.current_user_top_tracks(limit=BROWSE_LIMIT): + items = media.get("items", []) elif media_content_type == BrowsableMedia.FEATURED_PLAYLISTS: - media = spotify.featured_playlists(country=user["country"], limit=BROWSE_LIMIT) - items = media.get("playlists", {}).get("items", []) + if media := spotify.featured_playlists( + country=user["country"], limit=BROWSE_LIMIT + ): + items = media.get("playlists", {}).get("items", []) elif media_content_type == BrowsableMedia.CATEGORIES: - media = spotify.categories(country=user["country"], limit=BROWSE_LIMIT) - items = media.get("categories", {}).get("items", []) + if media := spotify.categories(country=user["country"], limit=BROWSE_LIMIT): + items = media.get("categories", {}).get("items", []) elif media_content_type == "category_playlists": - media = spotify.category_playlists( - category_id=media_content_id, - country=user["country"], - limit=BROWSE_LIMIT, - ) - category = spotify.category(media_content_id, country=user["country"]) - title = category.get("name") - image = fetch_image_url(category, key="icons") - items = media.get("playlists", {}).get("items", []) + if ( + media := spotify.category_playlists( + category_id=media_content_id, + country=user["country"], + limit=BROWSE_LIMIT, + ) + ) and (category := spotify.category(media_content_id, country=user["country"])): + title = category.get("name") + image = fetch_image_url(category, key="icons") + items = media.get("playlists", {}).get("items", []) elif media_content_type == BrowsableMedia.NEW_RELEASES: - media = spotify.new_releases(country=user["country"], limit=BROWSE_LIMIT) - items = media.get("albums", {}).get("items", []) + if media := spotify.new_releases(country=user["country"], limit=BROWSE_LIMIT): + items = media.get("albums", {}).get("items", []) elif media_content_type == MEDIA_TYPE_PLAYLIST: - media = spotify.playlist(media_content_id) - items = [item["track"] for item in media.get("tracks", {}).get("items", [])] + if media := spotify.playlist(media_content_id): + items = [item["track"] for item in media.get("tracks", {}).get("items", [])] elif media_content_type == MEDIA_TYPE_ALBUM: - media = spotify.album(media_content_id) - items = media.get("tracks", {}).get("items", []) + if media := spotify.album(media_content_id): + items = media.get("tracks", {}).get("items", []) elif media_content_type == MEDIA_TYPE_ARTIST: - media = spotify.artist_albums(media_content_id, limit=BROWSE_LIMIT) - artist = spotify.artist(media_content_id) - title = artist.get("name") - image = fetch_image_url(artist) - items = media.get("items", []) + if (media := spotify.artist_albums(media_content_id, limit=BROWSE_LIMIT)) and ( + artist := spotify.artist(media_content_id) + ): + title = artist.get("name") + image = fetch_image_url(artist) + items = media.get("items", []) elif media_content_type == MEDIA_TYPE_SHOW: - media = spotify.show_episodes(media_content_id, limit=BROWSE_LIMIT) - show = spotify.show(media_content_id) - title = show.get("name") - image = fetch_image_url(show) - items = media.get("items", []) - else: - media = None - items = [] + if (media := spotify.show_episodes(media_content_id, limit=BROWSE_LIMIT)) and ( + show := spotify.show(media_content_id) + ): + title = show.get("name") + image = fetch_image_url(show) + items = media.get("items", []) if media is None: return None @@ -665,15 +711,16 @@ def build_item_response(spotify, user, payload, *, can_play_artist): # noqa: C9 if media_content_type == BrowsableMedia.CATEGORIES: media_item = BrowseMedia( - title=LIBRARY_MAP.get(media_content_id), - media_class=media_class["parent"], + can_expand=True, + can_play=False, children_media_class=media_class["children"], + media_class=media_class["parent"], media_content_id=media_content_id, - media_content_type=MEDIA_PLAYER_PREFIX + media_content_type, - can_play=False, - can_expand=True, - children=[], + media_content_type=f"{MEDIA_PLAYER_PREFIX}{media_content_type}", + title=LIBRARY_MAP.get(media_content_id, "Unknown"), ) + + media_item.children = [] for item in items: try: item_id = item["id"] @@ -682,52 +729,54 @@ def build_item_response(spotify, user, payload, *, can_play_artist): # noqa: C9 continue media_item.children.append( BrowseMedia( - title=item.get("name"), - media_class=MEDIA_CLASS_PLAYLIST, + can_expand=True, + can_play=False, children_media_class=MEDIA_CLASS_TRACK, + media_class=MEDIA_CLASS_PLAYLIST, media_content_id=item_id, - media_content_type=MEDIA_PLAYER_PREFIX + "category_playlists", + media_content_type=f"{MEDIA_PLAYER_PREFIX}category_playlists", thumbnail=fetch_image_url(item, key="icons"), - can_play=False, - can_expand=True, + title=item.get("name"), ) ) return media_item if title is None: + title = LIBRARY_MAP.get(media_content_id, "Unknown") if "name" in media: - title = media.get("name") - else: - title = LIBRARY_MAP.get(payload["media_content_id"]) + title = media["name"] - params = { - "title": title, - "media_class": media_class["parent"], - "children_media_class": media_class["children"], - "media_content_id": media_content_id, - "media_content_type": MEDIA_PLAYER_PREFIX + media_content_type, - "can_play": media_content_type in PLAYABLE_MEDIA_TYPES - and (media_content_type != MEDIA_TYPE_ARTIST or can_play_artist), - "children": [], - "can_expand": True, - } + can_play = media_content_type in PLAYABLE_MEDIA_TYPES and ( + media_content_type != MEDIA_TYPE_ARTIST or can_play_artist + ) + + browse_media = BrowseMedia( + can_expand=True, + can_play=can_play, + children_media_class=media_class["children"], + media_class=media_class["parent"], + media_content_id=media_content_id, + media_content_type=f"{MEDIA_PLAYER_PREFIX}{media_content_type}", + thumbnail=image, + title=title, + ) + + browse_media.children = [] for item in items: try: - params["children"].append( + browse_media.children.append( item_payload(item, can_play_artist=can_play_artist) ) except (MissingMediaInformation, UnknownMediaType): continue if "images" in media: - params["thumbnail"] = fetch_image_url(media) - elif image: - params["thumbnail"] = image + browse_media.thumbnail = fetch_image_url(media) - return BrowseMedia(**params) + return browse_media -def item_payload(item, *, can_play_artist): +def item_payload(item: dict[str, Any], *, can_play_artist: bool) -> BrowseMedia: """ Create response payload for a single media item. @@ -751,54 +800,56 @@ def item_payload(item, *, can_play_artist): MEDIA_TYPE_EPISODE, ] - payload = { - "title": item.get("name"), - "media_class": media_class["parent"], - "children_media_class": media_class["children"], - "media_content_id": media_id, - "media_content_type": MEDIA_PLAYER_PREFIX + media_type, - "can_play": media_type in PLAYABLE_MEDIA_TYPES - and (media_type != MEDIA_TYPE_ARTIST or can_play_artist), - "can_expand": can_expand, - } + can_play = media_type in PLAYABLE_MEDIA_TYPES and ( + media_type != MEDIA_TYPE_ARTIST or can_play_artist + ) + + browse_media = BrowseMedia( + can_expand=can_expand, + can_play=can_play, + children_media_class=media_class["children"], + media_class=media_class["parent"], + media_content_id=media_id, + media_content_type=f"{MEDIA_PLAYER_PREFIX}{media_type}", + title=item.get("name", "Unknown"), + ) if "images" in item: - payload["thumbnail"] = fetch_image_url(item) + browse_media.thumbnail = fetch_image_url(item) elif MEDIA_TYPE_ALBUM in item: - payload["thumbnail"] = fetch_image_url(item[MEDIA_TYPE_ALBUM]) + browse_media.thumbnail = fetch_image_url(item[MEDIA_TYPE_ALBUM]) - return BrowseMedia(**payload) + return browse_media -def library_payload(*, can_play_artist): +def library_payload(*, can_play_artist: bool) -> BrowseMedia: """ Create response payload to describe contents of a specific library. Used by async_browse_media. """ - library_info = { - "title": "Media Library", - "media_class": MEDIA_CLASS_DIRECTORY, - "media_content_id": "library", - "media_content_type": MEDIA_PLAYER_PREFIX + "library", - "can_play": False, - "can_expand": True, - "children": [], - } + browse_media = BrowseMedia( + can_expand=True, + can_play=False, + children_media_class=MEDIA_CLASS_DIRECTORY, + media_class=MEDIA_CLASS_DIRECTORY, + media_content_id="library", + media_content_type=f"{MEDIA_PLAYER_PREFIX}library", + title="Media Library", + ) + browse_media.children = [] for item in [{"name": n, "type": t} for t, n in LIBRARY_MAP.items()]: - library_info["children"].append( + browse_media.children.append( item_payload( {"name": item["name"], "type": item["type"], "uri": item["type"]}, can_play_artist=can_play_artist, ) ) - response = BrowseMedia(**library_info) - response.children_media_class = MEDIA_CLASS_DIRECTORY - return response + return browse_media -def fetch_image_url(item, key="images"): +def fetch_image_url(item: dict[str, Any], key="images") -> str | None: """Fetch image url.""" try: return item.get(key, [])[0].get("url") diff --git a/homeassistant/components/spotify/strings.json b/homeassistant/components/spotify/strings.json index f775e5df85d4ce..caec5b8a288f58 100644 --- a/homeassistant/components/spotify/strings.json +++ b/homeassistant/components/spotify/strings.json @@ -11,8 +11,8 @@ }, "abort": { "authorize_url_timeout": "Timeout generating authorize URL.", - "no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]", "missing_configuration": "The Spotify integration is not configured. Please follow the documentation.", + "no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]", "reauth_account_mismatch": "The Spotify account authenticated with, does not match the account needed re-authentication." }, "create_entry": { "default": "Successfully authenticated with Spotify." } diff --git a/mypy.ini b/mypy.ini index 96d58927959b2d..31788f3643dcb3 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2646,12 +2646,6 @@ ignore_errors = true [mypy-homeassistant.components.sonos.statistics] ignore_errors = true -[mypy-homeassistant.components.spotify.config_flow] -ignore_errors = true - -[mypy-homeassistant.components.spotify.media_player] -ignore_errors = true - [mypy-homeassistant.components.system_health] ignore_errors = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index 6b14803b4995b5..5e3d9bd4b11270 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -188,8 +188,6 @@ "homeassistant.components.sonos.sensor", "homeassistant.components.sonos.speaker", "homeassistant.components.sonos.statistics", - "homeassistant.components.spotify.config_flow", - "homeassistant.components.spotify.media_player", "homeassistant.components.system_health", "homeassistant.components.telegram_bot.polling", "homeassistant.components.template", diff --git a/tests/components/spotify/test_config_flow.py b/tests/components/spotify/test_config_flow.py index fb0279f9112344..a1a77da1d100b1 100644 --- a/tests/components/spotify/test_config_flow.py +++ b/tests/components/spotify/test_config_flow.py @@ -194,7 +194,13 @@ async def test_reauthentication( old_entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_REAUTH}, data=old_entry.data + DOMAIN, + context={ + "source": SOURCE_REAUTH, + "unique_id": old_entry.unique_id, + "entry_id": old_entry.entry_id, + }, + data=old_entry.data, ) flows = hass.config_entries.flow.async_progress() @@ -261,7 +267,13 @@ async def test_reauth_account_mismatch( old_entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_REAUTH}, data=old_entry.data + DOMAIN, + context={ + "source": SOURCE_REAUTH, + "unique_id": old_entry.unique_id, + "entry_id": old_entry.entry_id, + }, + data=old_entry.data, ) flows = hass.config_entries.flow.async_progress() @@ -294,3 +306,13 @@ async def test_reauth_account_mismatch( assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "reauth_account_mismatch" + + +async def test_abort_if_no_reauth_entry(hass): + """Check flow aborts when no entry is known when entring reauth confirmation.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "reauth_confirm"} + ) + + assert result.get("type") == data_entry_flow.RESULT_TYPE_ABORT + assert result.get("reason") == "reauth_account_mismatch" From b216f6f4480d22f5c4dea9150afb21399772ceb3 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 8 Feb 2022 23:12:22 +0100 Subject: [PATCH 0450/1098] Fix Plugwise notification sensor (#66116) --- homeassistant/components/plugwise/binary_sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index 27ad691e55e709..fa5a1d09920ca0 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -143,4 +143,4 @@ def _handle_coordinator_update(self) -> None: msg ) - super()._handle_coordinator_update() + self.async_write_ha_state() From 716a1e2a6489c65bce9a494c5d2b23c44e29b905 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 8 Feb 2022 14:32:02 -0800 Subject: [PATCH 0451/1098] Add camera media source (#65977) --- homeassistant/components/camera/__init__.py | 67 +++--------- .../components/camera/media_source.py | 103 ++++++++++++++++++ homeassistant/components/cast/media_player.py | 43 +++++--- homeassistant/helpers/network.py | 42 +++++++ tests/components/camera/common.py | 1 + tests/components/camera/conftest.py | 53 +++++++++ tests/components/camera/test_init.py | 46 +------- tests/components/camera/test_media_source.py | 72 ++++++++++++ tests/components/cast/test_media_player.py | 71 +++++++++++- tests/helpers/test_network.py | 45 ++++++++ 10 files changed, 433 insertions(+), 110 deletions(-) create mode 100644 homeassistant/components/camera/media_source.py create mode 100644 tests/components/camera/conftest.py create mode 100644 tests/components/camera/test_media_source.py diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 110cf11cde9fd2..a2eb70e1c4264d 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -4,7 +4,7 @@ import asyncio import base64 import collections -from collections.abc import Awaitable, Callable, Iterable, Mapping +from collections.abc import Awaitable, Callable, Iterable from contextlib import suppress from dataclasses import dataclass from datetime import datetime, timedelta @@ -26,7 +26,6 @@ from homeassistant.components.media_player.const import ( ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, - ATTR_MEDIA_EXTRA, DOMAIN as DOMAIN_MP, SERVICE_PLAY_MEDIA, ) @@ -49,7 +48,7 @@ PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, ) -from homeassistant.helpers.entity import Entity, EntityDescription, entity_sources +from homeassistant.helpers.entity import Entity, EntityDescription from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.network import get_url from homeassistant.helpers.typing import ConfigType @@ -970,56 +969,22 @@ async def async_handle_play_stream_service( camera: Camera, service_call: ServiceCall ) -> None: """Handle play stream services calls.""" + hass = camera.hass fmt = service_call.data[ATTR_FORMAT] url = await _async_stream_endpoint_url(camera.hass, camera, fmt) - - hass = camera.hass - data: Mapping[str, str] = { - ATTR_MEDIA_CONTENT_ID: f"{get_url(hass)}{url}", - ATTR_MEDIA_CONTENT_TYPE: FORMAT_CONTENT_TYPE[fmt], - } - - # It is required to send a different payload for cast media players - entity_ids = service_call.data[ATTR_MEDIA_PLAYER] - sources = entity_sources(hass) - cast_entity_ids = [ - entity - for entity in entity_ids - # All entities should be in sources. This extra guard is to - # avoid people writing to the state machine and breaking it. - if entity in sources and sources[entity]["domain"] == "cast" - ] - other_entity_ids = list(set(entity_ids) - set(cast_entity_ids)) - - if cast_entity_ids: - await hass.services.async_call( - DOMAIN_MP, - SERVICE_PLAY_MEDIA, - { - ATTR_ENTITY_ID: cast_entity_ids, - **data, - ATTR_MEDIA_EXTRA: { - "stream_type": "LIVE", - "media_info": { - "hlsVideoSegmentFormat": "fmp4", - }, - }, - }, - blocking=True, - context=service_call.context, - ) - - if other_entity_ids: - await hass.services.async_call( - DOMAIN_MP, - SERVICE_PLAY_MEDIA, - { - ATTR_ENTITY_ID: other_entity_ids, - **data, - }, - blocking=True, - context=service_call.context, - ) + url = f"{get_url(hass)}{url}" + + await hass.services.async_call( + DOMAIN_MP, + SERVICE_PLAY_MEDIA, + { + ATTR_ENTITY_ID: service_call.data[ATTR_MEDIA_PLAYER], + ATTR_MEDIA_CONTENT_ID: url, + ATTR_MEDIA_CONTENT_TYPE: FORMAT_CONTENT_TYPE[fmt], + }, + blocking=True, + context=service_call.context, + ) async def _async_stream_endpoint_url( diff --git a/homeassistant/components/camera/media_source.py b/homeassistant/components/camera/media_source.py new file mode 100644 index 00000000000000..841a936532035d --- /dev/null +++ b/homeassistant/components/camera/media_source.py @@ -0,0 +1,103 @@ +"""Expose cameras as media sources.""" +from __future__ import annotations + +from typing import Optional, cast + +from homeassistant.components.media_player.const import ( + MEDIA_CLASS_APP, + MEDIA_CLASS_VIDEO, +) +from homeassistant.components.media_player.errors import BrowseError +from homeassistant.components.media_source.error import Unresolvable +from homeassistant.components.media_source.models import ( + BrowseMediaSource, + MediaSource, + MediaSourceItem, + PlayMedia, +) +from homeassistant.components.stream.const import FORMAT_CONTENT_TYPE, HLS_PROVIDER +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.entity_component import EntityComponent + +from . import Camera, _async_stream_endpoint_url +from .const import DOMAIN, STREAM_TYPE_HLS + + +async def async_get_media_source(hass: HomeAssistant) -> CameraMediaSource: + """Set up camera media source.""" + return CameraMediaSource(hass) + + +class CameraMediaSource(MediaSource): + """Provide camera feeds as media sources.""" + + name: str = "Camera" + + def __init__(self, hass: HomeAssistant) -> None: + """Initialize CameraMediaSource.""" + super().__init__(DOMAIN) + self.hass = hass + + async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia: + """Resolve media to a url.""" + component: EntityComponent = self.hass.data[DOMAIN] + camera = cast(Optional[Camera], component.get_entity(item.identifier)) + + if not camera: + raise Unresolvable(f"Could not resolve media item: {item.identifier}") + + if camera.frontend_stream_type != STREAM_TYPE_HLS: + raise Unresolvable("Camera does not support HLS streaming.") + + try: + url = await _async_stream_endpoint_url(self.hass, camera, HLS_PROVIDER) + except HomeAssistantError as err: + raise Unresolvable(str(err)) from err + + return PlayMedia(url, FORMAT_CONTENT_TYPE[HLS_PROVIDER]) + + async def async_browse_media( + self, + item: MediaSourceItem, + ) -> BrowseMediaSource: + """Return media.""" + if item.identifier: + raise BrowseError("Unknown item") + + if "stream" not in self.hass.config.components: + raise BrowseError("Stream integration is not loaded") + + # Root. List cameras. + component: EntityComponent = self.hass.data[DOMAIN] + children = [] + for camera in component.entities: + camera = cast(Camera, camera) + + if camera.frontend_stream_type != STREAM_TYPE_HLS: + continue + + children.append( + BrowseMediaSource( + domain=DOMAIN, + identifier=camera.entity_id, + media_class=MEDIA_CLASS_VIDEO, + media_content_type=FORMAT_CONTENT_TYPE[HLS_PROVIDER], + title=camera.name, + thumbnail=f"/api/camera_proxy/{camera.entity_id}", + can_play=True, + can_expand=False, + ) + ) + + return BrowseMediaSource( + domain=DOMAIN, + identifier=None, + media_class=MEDIA_CLASS_APP, + media_content_type="", + title="Camera", + can_play=False, + can_expand=True, + children_media_class=MEDIA_CLASS_VIDEO, + children=children, + ) diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index d418373e599ade..1354c5c00fb195 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -18,6 +18,7 @@ CONNECTION_STATUS_DISCONNECTED, ) import voluptuous as vol +import yarl from homeassistant.components import media_source, zeroconf from homeassistant.components.http.auth import async_sign_path @@ -59,7 +60,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.network import NoURLAvailableError, get_url +from homeassistant.helpers.network import NoURLAvailableError, get_url, is_hass_url import homeassistant.util.dt as dt_util from homeassistant.util.logging import async_create_catching_coro @@ -535,19 +536,6 @@ async def async_play_media(self, media_type, media_id, **kwargs): media_type = sourced_media.mime_type media_id = sourced_media.url - # If media ID is a relative URL, we serve it from HA. - # Create a signed path. - if media_id[0] == "/": - media_id = async_sign_path( - self.hass, - quote(media_id), - timedelta(seconds=media_source.DEFAULT_EXPIRY_TIME), - ) - - # prepend external URL - hass_url = get_url(self.hass, prefer_external=True) - media_id = f"{hass_url}{media_id}" - extra = kwargs.get(ATTR_MEDIA_EXTRA, {}) metadata = extra.get("metadata") @@ -593,6 +581,33 @@ async def async_play_media(self, media_type, media_id, **kwargs): if result: return + # If media ID is a relative URL, we serve it from HA. + # Create a signed path. + if media_id[0] == "/" or is_hass_url(self.hass, media_id): + parsed = yarl.URL(media_id) + # Configure play command for when playing a HLS stream + if parsed.path.startswith("/api/hls/"): + extra = { + **extra, + "stream_type": "LIVE", + "media_info": { + "hlsVideoSegmentFormat": "fmp4", + }, + } + + if parsed.query: + _LOGGER.debug("Not signing path for content with query param") + else: + media_id = async_sign_path( + self.hass, + quote(media_id), + timedelta(seconds=media_source.DEFAULT_EXPIRY_TIME), + ) + + if media_id[0] == "/": + # prepend URL + media_id = f"{get_url(self.hass)}{media_id}" + # Default to play with the default media receiver app_data = {"media_id": media_id, "media_type": media_type, **extra} await self.hass.async_add_executor_job( diff --git a/homeassistant/helpers/network.py b/homeassistant/helpers/network.py index 0c52012e43cba5..9d7780ab9005df 100644 --- a/homeassistant/helpers/network.py +++ b/homeassistant/helpers/network.py @@ -31,6 +31,48 @@ def is_internal_request(hass: HomeAssistant) -> bool: return False +def is_hass_url(hass: HomeAssistant, url: str) -> bool: + """Return if the URL points at this Home Assistant instance.""" + parsed = yarl.URL(normalize_url(url)) + + def host_ip() -> str | None: + if hass.config.api is None or is_loopback(ip_address(hass.config.api.local_ip)): + return None + + return str( + yarl.URL.build( + scheme="http", host=hass.config.api.local_ip, port=hass.config.api.port + ) + ) + + def cloud_url() -> str | None: + try: + return _get_cloud_url(hass) + except NoURLAvailableError: + return None + + for potential_base_factory in ( + lambda: hass.config.internal_url, + lambda: hass.config.external_url, + cloud_url, + host_ip, + ): + potential_base = potential_base_factory() + + if potential_base is None: + continue + + potential_parsed = yarl.URL(normalize_url(potential_base)) + + if ( + parsed.scheme == potential_parsed.scheme + and parsed.authority == potential_parsed.authority + ): + return True + + return False + + @bind_hass def get_url( hass: HomeAssistant, diff --git a/tests/components/camera/common.py b/tests/components/camera/common.py index bd3841cc4e85c6..ee2a3cb2974198 100644 --- a/tests/components/camera/common.py +++ b/tests/components/camera/common.py @@ -8,6 +8,7 @@ from homeassistant.components.camera.const import DATA_CAMERA_PREFS, PREF_PRELOAD_STREAM EMPTY_8_6_JPEG = b"empty_8_6" +WEBRTC_ANSWER = "a=sendonly" def mock_camera_prefs(hass, entity_id, prefs=None): diff --git a/tests/components/camera/conftest.py b/tests/components/camera/conftest.py new file mode 100644 index 00000000000000..b09f7696ef2742 --- /dev/null +++ b/tests/components/camera/conftest.py @@ -0,0 +1,53 @@ +"""Test helpers for camera.""" +from unittest.mock import PropertyMock, patch + +import pytest + +from homeassistant.components import camera +from homeassistant.components.camera.const import STREAM_TYPE_HLS, STREAM_TYPE_WEB_RTC +from homeassistant.setup import async_setup_component + +from .common import WEBRTC_ANSWER + + +@pytest.fixture(name="mock_camera") +async def mock_camera_fixture(hass): + """Initialize a demo camera platform.""" + assert await async_setup_component( + hass, "camera", {camera.DOMAIN: {"platform": "demo"}} + ) + await hass.async_block_till_done() + + with patch( + "homeassistant.components.demo.camera.Path.read_bytes", + return_value=b"Test", + ): + yield + + +@pytest.fixture(name="mock_camera_hls") +async def mock_camera_hls_fixture(mock_camera): + """Initialize a demo camera platform with HLS.""" + with patch( + "homeassistant.components.camera.Camera.frontend_stream_type", + new_callable=PropertyMock(return_value=STREAM_TYPE_HLS), + ): + yield + + +@pytest.fixture(name="mock_camera_web_rtc") +async def mock_camera_web_rtc_fixture(hass): + """Initialize a demo camera platform with WebRTC.""" + assert await async_setup_component( + hass, "camera", {camera.DOMAIN: {"platform": "demo"}} + ) + await hass.async_block_till_done() + + with patch( + "homeassistant.components.camera.Camera.frontend_stream_type", + new_callable=PropertyMock(return_value=STREAM_TYPE_WEB_RTC), + ), patch( + "homeassistant.components.camera.Camera.async_handle_web_rtc_offer", + return_value=WEBRTC_ANSWER, + ): + yield diff --git a/tests/components/camera/test_init.py b/tests/components/camera/test_init.py index 403cacec1f1047..0e53e163404983 100644 --- a/tests/components/camera/test_init.py +++ b/tests/components/camera/test_init.py @@ -8,11 +8,7 @@ import pytest from homeassistant.components import camera -from homeassistant.components.camera.const import ( - DOMAIN, - PREF_PRELOAD_STREAM, - STREAM_TYPE_WEB_RTC, -) +from homeassistant.components.camera.const import DOMAIN, PREF_PRELOAD_STREAM from homeassistant.components.camera.prefs import CameraEntityPreferences from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.config import async_process_ha_core_config @@ -24,47 +20,11 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component -from .common import EMPTY_8_6_JPEG, mock_turbo_jpeg - -from tests.components.camera import common +from .common import EMPTY_8_6_JPEG, WEBRTC_ANSWER, mock_camera_prefs, mock_turbo_jpeg STREAM_SOURCE = "rtsp://127.0.0.1/stream" HLS_STREAM_SOURCE = "http://127.0.0.1/example.m3u" WEBRTC_OFFER = "v=0\r\n" -WEBRTC_ANSWER = "a=sendonly" - - -@pytest.fixture(name="mock_camera") -async def mock_camera_fixture(hass): - """Initialize a demo camera platform.""" - assert await async_setup_component( - hass, "camera", {camera.DOMAIN: {"platform": "demo"}} - ) - await hass.async_block_till_done() - - with patch( - "homeassistant.components.demo.camera.Path.read_bytes", - return_value=b"Test", - ): - yield - - -@pytest.fixture(name="mock_camera_web_rtc") -async def mock_camera_web_rtc_fixture(hass): - """Initialize a demo camera platform.""" - assert await async_setup_component( - hass, "camera", {camera.DOMAIN: {"platform": "demo"}} - ) - await hass.async_block_till_done() - - with patch( - "homeassistant.components.camera.Camera.frontend_stream_type", - new_callable=PropertyMock(return_value=STREAM_TYPE_WEB_RTC), - ), patch( - "homeassistant.components.camera.Camera.async_handle_web_rtc_offer", - return_value=WEBRTC_ANSWER, - ): - yield @pytest.fixture(name="mock_stream") @@ -78,7 +38,7 @@ def mock_stream_fixture(hass): @pytest.fixture(name="setup_camera_prefs") def setup_camera_prefs_fixture(hass): """Initialize HTTP API.""" - return common.mock_camera_prefs(hass, "camera.demo_camera") + return mock_camera_prefs(hass, "camera.demo_camera") @pytest.fixture(name="image_mock_url") diff --git a/tests/components/camera/test_media_source.py b/tests/components/camera/test_media_source.py new file mode 100644 index 00000000000000..d5d65296e65e75 --- /dev/null +++ b/tests/components/camera/test_media_source.py @@ -0,0 +1,72 @@ +"""Test camera media source.""" +from unittest.mock import PropertyMock, patch + +import pytest + +from homeassistant.components import media_source +from homeassistant.components.camera.const import STREAM_TYPE_WEB_RTC +from homeassistant.components.stream.const import FORMAT_CONTENT_TYPE +from homeassistant.setup import async_setup_component + + +@pytest.fixture(autouse=True) +async def setup_media_source(hass): + """Set up media source.""" + assert await async_setup_component(hass, "media_source", {}) + + +@pytest.fixture(autouse=True) +async def mock_stream(hass): + """Mock stream.""" + hass.config.components.add("stream") + + +async def test_browsing(hass, mock_camera_hls): + """Test browsing camera media source.""" + item = await media_source.async_browse_media(hass, "media-source://camera") + assert item is not None + assert item.title == "Camera" + assert len(item.children) == 2 + + +async def test_browsing_filter_non_hls(hass, mock_camera_web_rtc): + """Test browsing camera media source hides non-HLS cameras.""" + item = await media_source.async_browse_media(hass, "media-source://camera") + assert item is not None + assert item.title == "Camera" + assert len(item.children) == 0 + + +async def test_resolving(hass, mock_camera_hls): + """Test resolving.""" + with patch( + "homeassistant.components.camera.media_source._async_stream_endpoint_url", + return_value="http://example.com/stream", + ): + item = await media_source.async_resolve_media( + hass, "media-source://camera/camera.demo_camera" + ) + assert item is not None + assert item.url == "http://example.com/stream" + assert item.mime_type == FORMAT_CONTENT_TYPE["hls"] + + +async def test_resolving_errors(hass, mock_camera_hls): + """Test resolving.""" + with pytest.raises(media_source.Unresolvable): + await media_source.async_resolve_media( + hass, "media-source://camera/camera.non_existing" + ) + + with pytest.raises(media_source.Unresolvable), patch( + "homeassistant.components.camera.Camera.frontend_stream_type", + new_callable=PropertyMock(return_value=STREAM_TYPE_WEB_RTC), + ): + await media_source.async_resolve_media( + hass, "media-source://camera/camera.demo_camera" + ) + + with pytest.raises(media_source.Unresolvable): + await media_source.async_resolve_media( + hass, "media-source://camera/camera.demo_camera" + ) diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 51fe4a086a610a..36ee2ce818a2cb 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -10,6 +10,7 @@ import pychromecast from pychromecast.const import CAST_TYPE_CHROMECAST, CAST_TYPE_GROUP import pytest +import yarl from homeassistant.components import media_player, tts from homeassistant.components.cast import media_player as cast @@ -37,7 +38,7 @@ EVENT_HOMEASSISTANT_STOP, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er +from homeassistant.helpers import entity_registry as er, network from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.setup import async_setup_component @@ -1001,7 +1002,7 @@ async def test_entity_play_media_sign_URL(hass: HomeAssistant, quick_play_mock): await async_process_ha_core_config( hass, - {"external_url": "http://example.com:8123"}, + {"internal_url": "http://example.com:8123"}, ) info = get_fake_chromecast_info() @@ -1824,3 +1825,69 @@ async def test_cast_platform_browse_media(hass: HomeAssistant, hass_ws_client): "children": [], } assert response["result"] == expected_response + + +async def test_cast_platform_play_media_local_media( + hass: HomeAssistant, quick_play_mock, caplog +): + """Test we process data when playing local media.""" + entity_id = "media_player.speaker" + info = get_fake_chromecast_info() + + chromecast, _ = await async_setup_media_player_cast(hass, info) + _, conn_status_cb, _ = get_status_callbacks(chromecast) + + # Bring Chromecast online + connection_status = MagicMock() + connection_status.status = "CONNECTED" + conn_status_cb(connection_status) + await hass.async_block_till_done() + + # This will play using the cast platform + await hass.services.async_call( + media_player.DOMAIN, + media_player.SERVICE_PLAY_MEDIA, + { + ATTR_ENTITY_ID: entity_id, + media_player.ATTR_MEDIA_CONTENT_TYPE: "application/vnd.apple.mpegurl", + media_player.ATTR_MEDIA_CONTENT_ID: "/api/hls/bla/master_playlist.m3u8", + }, + blocking=True, + ) + await hass.async_block_till_done() + + # Assert we added extra play information + quick_play_mock.assert_called() + app_data = quick_play_mock.call_args[0][2] + + assert not app_data["media_id"].startswith("/") + assert "authSig" in yarl.URL(app_data["media_id"]).query + assert app_data["media_type"] == "application/vnd.apple.mpegurl" + assert app_data["stream_type"] == "LIVE" + assert app_data["media_info"] == { + "hlsVideoSegmentFormat": "fmp4", + } + + quick_play_mock.reset_mock() + + # Test not appending if we have a signature + await hass.services.async_call( + media_player.DOMAIN, + media_player.SERVICE_PLAY_MEDIA, + { + ATTR_ENTITY_ID: entity_id, + media_player.ATTR_MEDIA_CONTENT_TYPE: "application/vnd.apple.mpegurl", + media_player.ATTR_MEDIA_CONTENT_ID: f"{network.get_url(hass)}/api/hls/bla/master_playlist.m3u8?token=bla", + }, + blocking=True, + ) + await hass.async_block_till_done() + + # Assert we added extra play information + quick_play_mock.assert_called() + app_data = quick_play_mock.call_args[0][2] + # No authSig appended + assert ( + app_data["media_id"] + == f"{network.get_url(hass)}/api/hls/bla/master_playlist.m3u8?token=bla" + ) diff --git a/tests/helpers/test_network.py b/tests/helpers/test_network.py index 7e9086f446770c..15a9b8d1ff8a75 100644 --- a/tests/helpers/test_network.py +++ b/tests/helpers/test_network.py @@ -13,6 +13,7 @@ _get_internal_url, _get_request_host, get_url, + is_hass_url, is_internal_request, ) @@ -645,3 +646,47 @@ async def test_is_internal_request(hass: HomeAssistant): "homeassistant.helpers.network._get_request_host", return_value="192.168.0.1" ): assert is_internal_request(hass) + + +async def test_is_hass_url(hass): + """Test is_hass_url.""" + assert hass.config.api is None + assert hass.config.internal_url is None + assert hass.config.external_url is None + + assert is_hass_url(hass, "http://example.com") is False + + hass.config.api = Mock(use_ssl=False, port=8123, local_ip="192.168.123.123") + assert is_hass_url(hass, "http://192.168.123.123:8123") is True + assert is_hass_url(hass, "https://192.168.123.123:8123") is False + assert is_hass_url(hass, "http://192.168.123.123") is False + + await async_process_ha_core_config( + hass, + {"internal_url": "http://example.local:8123"}, + ) + assert is_hass_url(hass, "http://example.local:8123") is True + assert is_hass_url(hass, "https://example.local:8123") is False + assert is_hass_url(hass, "http://example.local") is False + + await async_process_ha_core_config( + hass, + {"external_url": "https://example.com:443"}, + ) + assert is_hass_url(hass, "https://example.com:443") is True + assert is_hass_url(hass, "https://example.com") is True + assert is_hass_url(hass, "http://example.com:443") is False + assert is_hass_url(hass, "http://example.com") is False + + with patch.object( + hass.components.cloud, + "async_remote_ui_url", + return_value="https://example.nabu.casa", + ): + assert is_hass_url(hass, "https://example.nabu.casa") is False + + hass.config.components.add("cloud") + assert is_hass_url(hass, "https://example.nabu.casa:443") is True + assert is_hass_url(hass, "https://example.nabu.casa") is True + assert is_hass_url(hass, "http://example.nabu.casa:443") is False + assert is_hass_url(hass, "http://example.nabu.casa") is False From 5ebc02cef6ccb4651b5ee580cd39ed61ca305a90 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Tue, 8 Feb 2022 22:47:36 +0000 Subject: [PATCH 0452/1098] Fix generic camera typo in attr_frame_interval (#65390) --- homeassistant/components/generic/camera.py | 2 +- tests/components/generic/test_camera.py | 27 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/generic/camera.py b/homeassistant/components/generic/camera.py index b6084d148a3e73..b4aaad3861839a 100644 --- a/homeassistant/components/generic/camera.py +++ b/homeassistant/components/generic/camera.py @@ -96,7 +96,7 @@ def __init__(self, hass, device_info): if self._stream_source is not None: self._stream_source.hass = hass self._limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE] - self._attr_frames_interval = 1 / device_info[CONF_FRAMERATE] + self._attr_frame_interval = 1 / device_info[CONF_FRAMERATE] self._supported_features = SUPPORT_STREAM if self._stream_source else 0 self.content_type = device_info[CONF_CONTENT_TYPE] self.verify_ssl = device_info[CONF_VERIFY_SSL] diff --git a/tests/components/generic/test_camera.py b/tests/components/generic/test_camera.py index 60e68a1e7b1e6d..042cd2ee650edd 100644 --- a/tests/components/generic/test_camera.py +++ b/tests/components/generic/test_camera.py @@ -9,6 +9,7 @@ import respx from homeassistant import config as hass_config +from homeassistant.components.camera import async_get_mjpeg_stream from homeassistant.components.generic import DOMAIN from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.const import SERVICE_RELOAD @@ -515,3 +516,29 @@ async def test_no_still_image_url(hass, hass_client): mock_stream.async_get_image.assert_called_once() assert resp.status == HTTPStatus.OK assert await resp.read() == b"stream_keyframe_image" + + +async def test_frame_interval_property(hass): + """Test that the frame interval is calculated and returned correctly.""" + + await async_setup_component( + hass, + "camera", + { + "camera": { + "name": "config_test", + "platform": "generic", + "stream_source": "rtsp://example.com:554/rtsp/", + "framerate": 5, + }, + }, + ) + await hass.async_block_till_done() + + request = Mock() + with patch( + "homeassistant.components.camera.async_get_still_stream" + ) as mock_get_stream: + await async_get_mjpeg_stream(hass, request, "camera.config_test") + + assert mock_get_stream.call_args_list[0][0][3] == pytest.approx(0.2) From f2fe091979cb42562b144127063e0393d39ddffb Mon Sep 17 00:00:00 2001 From: Duco Sebel <74970928+DCSBL@users.noreply.github.com> Date: Tue, 8 Feb 2022 23:51:28 +0100 Subject: [PATCH 0453/1098] Allow HomeWizard devices with disabled api to show up in discovery (#65295) --- .../components/homewizard/config_flow.py | 48 +++++++++++-------- homeassistant/components/homewizard/const.py | 8 ++-- .../components/homewizard/test_config_flow.py | 30 ++++++++++-- 3 files changed, 59 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/homewizard/config_flow.py b/homeassistant/components/homewizard/config_flow.py index c9f7d19a96a7fc..6ab485f534f8c6 100644 --- a/homeassistant/components/homewizard/config_flow.py +++ b/homeassistant/components/homewizard/config_flow.py @@ -14,7 +14,14 @@ from homeassistant.const import CONF_IP_ADDRESS from homeassistant.data_entry_flow import AbortFlow, FlowResult -from .const import CONF_PRODUCT_NAME, CONF_PRODUCT_TYPE, CONF_SERIAL, DOMAIN +from .const import ( + CONF_API_ENABLED, + CONF_PATH, + CONF_PRODUCT_NAME, + CONF_PRODUCT_TYPE, + CONF_SERIAL, + DOMAIN, +) _LOGGER = logging.getLogger(__name__) @@ -28,7 +35,7 @@ def __init__(self) -> None: """Initialize the HomeWizard config flow.""" self.config: dict[str, str | int] = {} - async def async_step_import(self, import_config: dict) -> FlowResult: + async def async_step_import(self, import_config: dict[str, Any]) -> FlowResult: """Handle a flow initiated by older `homewizard_energy` component.""" _LOGGER.debug("config_flow async_step_import") @@ -97,40 +104,33 @@ async def async_step_zeroconf( # Validate doscovery entry if ( - "api_enabled" not in discovery_info.properties - or "path" not in discovery_info.properties - or "product_name" not in discovery_info.properties - or "product_type" not in discovery_info.properties - or "serial" not in discovery_info.properties + CONF_API_ENABLED not in discovery_info.properties + or CONF_PATH not in discovery_info.properties + or CONF_PRODUCT_NAME not in discovery_info.properties + or CONF_PRODUCT_TYPE not in discovery_info.properties + or CONF_SERIAL not in discovery_info.properties ): return self.async_abort(reason="invalid_discovery_parameters") - if (discovery_info.properties["path"]) != "/api/v1": + if (discovery_info.properties[CONF_PATH]) != "/api/v1": return self.async_abort(reason="unsupported_api_version") - if (discovery_info.properties["api_enabled"]) != "1": - return self.async_abort(reason="api_not_enabled") - # Sets unique ID and aborts if it is already exists await self._async_set_and_check_unique_id( { CONF_IP_ADDRESS: discovery_info.host, - CONF_PRODUCT_TYPE: discovery_info.properties["product_type"], - CONF_SERIAL: discovery_info.properties["serial"], + CONF_PRODUCT_TYPE: discovery_info.properties[CONF_PRODUCT_TYPE], + CONF_SERIAL: discovery_info.properties[CONF_SERIAL], } ) - # Check connection and fetch - device_info: dict[str, Any] = await self._async_try_connect_and_fetch( - discovery_info.host - ) - # Pass parameters self.config = { + CONF_API_ENABLED: discovery_info.properties[CONF_API_ENABLED], CONF_IP_ADDRESS: discovery_info.host, - CONF_PRODUCT_TYPE: device_info[CONF_PRODUCT_TYPE], - CONF_PRODUCT_NAME: device_info[CONF_PRODUCT_NAME], - CONF_SERIAL: device_info[CONF_SERIAL], + CONF_PRODUCT_TYPE: discovery_info.properties[CONF_PRODUCT_TYPE], + CONF_PRODUCT_NAME: discovery_info.properties[CONF_PRODUCT_NAME], + CONF_SERIAL: discovery_info.properties[CONF_SERIAL], } return await self.async_step_discovery_confirm() @@ -139,6 +139,12 @@ async def async_step_discovery_confirm( ) -> FlowResult: """Confirm discovery.""" if user_input is not None: + if (self.config[CONF_API_ENABLED]) != "1": + raise AbortFlow(reason="api_not_enabled") + + # Check connection + await self._async_try_connect_and_fetch(str(self.config[CONF_IP_ADDRESS])) + return self.async_create_entry( title=f"{self.config[CONF_PRODUCT_NAME]} ({self.config[CONF_SERIAL]})", data={ diff --git a/homeassistant/components/homewizard/const.py b/homeassistant/components/homewizard/const.py index 9a6c465532fe51..75c522a211e67a 100644 --- a/homeassistant/components/homewizard/const.py +++ b/homeassistant/components/homewizard/const.py @@ -14,11 +14,13 @@ PLATFORMS = [Platform.SENSOR, Platform.SWITCH] # Platform config. -CONF_SERIAL = "serial" +CONF_API_ENABLED = "api_enabled" +CONF_DATA = "data" +CONF_DEVICE = "device" +CONF_PATH = "path" CONF_PRODUCT_NAME = "product_name" CONF_PRODUCT_TYPE = "product_type" -CONF_DEVICE = "device" -CONF_DATA = "data" +CONF_SERIAL = "serial" UPDATE_INTERVAL = timedelta(seconds=5) diff --git a/tests/components/homewizard/test_config_flow.py b/tests/components/homewizard/test_config_flow.py index f416027da4a646..061b7b634d4dcb 100644 --- a/tests/components/homewizard/test_config_flow.py +++ b/tests/components/homewizard/test_config_flow.py @@ -8,7 +8,11 @@ from homeassistant.components import zeroconf from homeassistant.components.homewizard.const import DOMAIN from homeassistant.const import CONF_IP_ADDRESS -from homeassistant.data_entry_flow import RESULT_TYPE_ABORT, RESULT_TYPE_CREATE_ENTRY +from homeassistant.data_entry_flow import ( + RESULT_TYPE_ABORT, + RESULT_TYPE_CREATE_ENTRY, + RESULT_TYPE_FORM, +) from .generator import get_mock_device @@ -77,9 +81,19 @@ async def test_discovery_flow_works(hass, aioclient_mock): with patch( "homeassistant.components.homewizard.async_setup_entry", return_value=True, - ): + ), patch("aiohwenergy.HomeWizardEnergy", return_value=get_mock_device()): + result = await hass.config_entries.flow.async_configure( + flow["flow_id"], user_input=None + ) + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "discovery_confirm" + + with patch( + "homeassistant.components.homewizard.async_setup_entry", + return_value=True, + ), patch("aiohwenergy.HomeWizardEnergy", return_value=get_mock_device()): result = await hass.config_entries.flow.async_configure( - flow["flow_id"], user_input={} + flow["flow_id"], user_input={"ip_address": "192.168.43.183"} ) assert result["type"] == RESULT_TYPE_CREATE_ENTRY @@ -145,6 +159,16 @@ async def test_discovery_disabled_api(hass, aioclient_mock): data=service_info, ) + assert result["type"] == RESULT_TYPE_FORM + + with patch( + "homeassistant.components.homewizard.async_setup_entry", + return_value=True, + ), patch("aiohwenergy.HomeWizardEnergy", return_value=get_mock_device()): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"ip_address": "192.168.43.183"} + ) + assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "api_not_enabled" From fb96c31a277961158328c4de14afb4ba78cb5381 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 9 Feb 2022 00:23:56 +0000 Subject: [PATCH 0454/1098] [ci skip] Translation update --- .../aussie_broadband/translations/pl.json | 26 ++++++- .../binary_sensor/translations/ja.json | 2 +- .../bmw_connected_drive/translations/id.json | 2 +- .../components/bsblan/translations/pl.json | 3 +- .../climacell/translations/sensor.id.json | 1 + .../components/cloud/translations/ja.json | 2 +- .../components/coinbase/translations/pl.json | 2 + .../dialogflow/translations/pl.json | 1 + .../components/dnsip/translations/id.json | 4 +- .../components/dnsip/translations/pl.json | 4 +- .../components/elkm1/translations/ca.json | 32 +++++++- .../components/elkm1/translations/de.json | 30 +++++++- .../components/elkm1/translations/en.json | 10 ++- .../components/elkm1/translations/et.json | 30 +++++++- .../components/elkm1/translations/id.json | 30 +++++++- .../components/elkm1/translations/ja.json | 26 ++++++- .../components/elkm1/translations/pl.json | 30 +++++++- .../components/elkm1/translations/pt-BR.json | 30 +++++++- .../components/elkm1/translations/ru.json | 30 +++++++- .../elkm1/translations/zh-Hant.json | 30 +++++++- .../components/fan/translations/pl.json | 1 + .../components/fivem/translations/bg.json | 13 ++++ .../components/fivem/translations/ca.json | 20 +++++ .../components/fivem/translations/de.json | 22 ++++++ .../components/fivem/translations/en.json | 1 + .../components/fivem/translations/et.json | 20 +++++ .../components/fivem/translations/id.json | 22 ++++++ .../components/fivem/translations/ja.json | 19 +++++ .../components/fivem/translations/pl.json | 20 +++++ .../components/fivem/translations/pt-BR.json | 22 ++++++ .../fivem/translations/zh-Hant.json | 20 +++++ .../components/geofency/translations/pl.json | 1 + .../components/github/translations/id.json | 3 + .../components/github/translations/pl.json | 5 +- .../components/gpslogger/translations/pl.json | 1 + .../components/homekit/translations/id.json | 2 +- .../components/homekit/translations/pl.json | 12 ++- .../translations/select.pl.json | 9 +++ .../homewizard/translations/pl.json | 3 +- .../humidifier/translations/pl.json | 1 + .../components/ifttt/translations/pl.json | 1 + .../intellifire/translations/pl.json | 18 +++++ .../components/iss/translations/id.json | 9 +++ .../components/iss/translations/pl.json | 16 ++++ .../components/isy994/translations/ja.json | 2 +- .../components/knx/translations/pl.json | 6 +- .../launch_library/translations/id.json | 5 ++ .../components/light/translations/pl.json | 1 + .../components/locative/translations/pl.json | 1 + .../components/luftdaten/translations/pl.json | 2 +- .../components/mailgun/translations/pl.json | 1 + .../media_player/translations/pl.json | 1 + .../components/mutesync/translations/id.json | 2 +- .../components/netgear/translations/pl.json | 2 +- .../components/overkiz/translations/de.json | 1 + .../components/overkiz/translations/id.json | 1 + .../components/overkiz/translations/ja.json | 1 + .../components/overkiz/translations/pl.json | 5 +- .../components/overkiz/translations/ru.json | 1 + .../overkiz/translations/select.pl.json | 4 +- .../overkiz/translations/sensor.id.json | 10 ++- .../overkiz/translations/sensor.pl.json | 42 +++++++---- .../overkiz/translations/zh-Hant.json | 1 + .../components/owntracks/translations/pl.json | 1 + .../components/plaato/translations/pl.json | 1 + .../components/powerwall/translations/bg.json | 5 ++ .../components/powerwall/translations/pl.json | 14 +++- .../components/remote/translations/pl.json | 1 + .../rtsp_to_webrtc/translations/id.json | 20 +++++ .../rtsp_to_webrtc/translations/pl.json | 2 + .../components/senseme/translations/id.json | 13 +++- .../components/senseme/translations/pl.json | 3 +- .../components/sensor/translations/pl.json | 2 +- .../components/sia/translations/id.json | 2 +- .../components/smhi/translations/pl.json | 3 + .../components/solax/translations/pl.json | 17 +++++ .../components/steamist/translations/id.json | 9 ++- .../components/switch/translations/pl.json | 1 + .../synology_dsm/translations/id.json | 1 + .../synology_dsm/translations/pl.json | 1 + .../components/traccar/translations/pl.json | 1 + .../tuya/translations/select.bg.json | 11 +++ .../tuya/translations/select.id.json | 75 +++++++++++++++++++ .../tuya/translations/select.pl.json | 62 +++++++++++++-- .../tuya/translations/sensor.id.json | 6 ++ .../tuya/translations/sensor.ja.json | 6 ++ .../tuya/translations/sensor.pl.json | 6 ++ .../components/twilio/translations/pl.json | 1 + .../components/twinkly/translations/id.json | 2 +- .../unifiprotect/translations/id.json | 9 ++- .../unifiprotect/translations/pl.json | 12 ++- .../uptimerobot/translations/sensor.id.json | 11 +++ .../uptimerobot/translations/sensor.pl.json | 11 +++ .../components/webostv/translations/pl.json | 17 +++-- .../components/whois/translations/pl.json | 3 +- .../components/wiz/translations/bg.json | 13 ++++ .../components/wiz/translations/id.json | 35 +++++++++ .../components/wiz/translations/pl.json | 24 ++++++ .../components/wiz/translations/ru.json | 5 ++ .../wolflink/translations/sensor.id.json | 5 ++ .../yale_smart_alarm/translations/pl.json | 2 +- .../components/zwave_me/translations/bg.json | 11 +++ .../components/zwave_me/translations/ca.json | 2 +- .../components/zwave_me/translations/de.json | 20 +++++ .../components/zwave_me/translations/id.json | 20 +++++ .../components/zwave_me/translations/ja.json | 19 +++++ .../components/zwave_me/translations/pl.json | 20 +++++ .../components/zwave_me/translations/ru.json | 20 +++++ .../zwave_me/translations/zh-Hant.json | 20 +++++ 109 files changed, 1132 insertions(+), 88 deletions(-) create mode 100644 homeassistant/components/fivem/translations/bg.json create mode 100644 homeassistant/components/fivem/translations/ca.json create mode 100644 homeassistant/components/fivem/translations/de.json create mode 100644 homeassistant/components/fivem/translations/et.json create mode 100644 homeassistant/components/fivem/translations/id.json create mode 100644 homeassistant/components/fivem/translations/ja.json create mode 100644 homeassistant/components/fivem/translations/pl.json create mode 100644 homeassistant/components/fivem/translations/pt-BR.json create mode 100644 homeassistant/components/fivem/translations/zh-Hant.json create mode 100644 homeassistant/components/homekit_controller/translations/select.pl.json create mode 100644 homeassistant/components/intellifire/translations/pl.json create mode 100644 homeassistant/components/iss/translations/pl.json create mode 100644 homeassistant/components/solax/translations/pl.json create mode 100644 homeassistant/components/uptimerobot/translations/sensor.id.json create mode 100644 homeassistant/components/uptimerobot/translations/sensor.pl.json create mode 100644 homeassistant/components/wiz/translations/bg.json create mode 100644 homeassistant/components/wiz/translations/id.json create mode 100644 homeassistant/components/zwave_me/translations/bg.json create mode 100644 homeassistant/components/zwave_me/translations/de.json create mode 100644 homeassistant/components/zwave_me/translations/id.json create mode 100644 homeassistant/components/zwave_me/translations/ja.json create mode 100644 homeassistant/components/zwave_me/translations/pl.json create mode 100644 homeassistant/components/zwave_me/translations/ru.json create mode 100644 homeassistant/components/zwave_me/translations/zh-Hant.json diff --git a/homeassistant/components/aussie_broadband/translations/pl.json b/homeassistant/components/aussie_broadband/translations/pl.json index d17b0f33c2a05d..7fa1e0d7c46e96 100644 --- a/homeassistant/components/aussie_broadband/translations/pl.json +++ b/homeassistant/components/aussie_broadband/translations/pl.json @@ -1,21 +1,43 @@ { "config": { "abort": { - "no_services_found": "Nie znaleziono \u017cadnych us\u0142ug dla tego konta" + "already_configured": "Konto jest ju\u017c skonfigurowane", + "no_services_found": "Nie znaleziono \u017cadnych us\u0142ug dla tego konta", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "reauth": { - "description": "Zaktualizuj has\u0142o dla {username}" + "data": { + "password": "Has\u0142o" + }, + "description": "Zaktualizuj has\u0142o dla {username}", + "title": "Ponownie uwierzytelnij integracj\u0119" }, "service": { "data": { "services": "Us\u0142ugi" }, "title": "Wybierz us\u0142ugi" + }, + "user": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + } } } }, "options": { + "abort": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/binary_sensor/translations/ja.json b/homeassistant/components/binary_sensor/translations/ja.json index 3d961086dfc672..79c2222a6da0da 100644 --- a/homeassistant/components/binary_sensor/translations/ja.json +++ b/homeassistant/components/binary_sensor/translations/ja.json @@ -144,7 +144,7 @@ }, "connectivity": { "off": "\u5207\u65ad", - "on": "\u63a5\u7d9a\u6e08" + "on": "\u63a5\u7d9a\u6e08\u307f" }, "door": { "off": "\u9589\u9396", diff --git a/homeassistant/components/bmw_connected_drive/translations/id.json b/homeassistant/components/bmw_connected_drive/translations/id.json index e49e9202dbe2e5..3701a49ccfbef5 100644 --- a/homeassistant/components/bmw_connected_drive/translations/id.json +++ b/homeassistant/components/bmw_connected_drive/translations/id.json @@ -22,7 +22,7 @@ "account_options": { "data": { "read_only": "Hanya baca (hanya sensor dan notifikasi, tidak ada eksekusi layanan, tidak ada fitur penguncian)", - "use_location": "Gunakan lokasi Asisten Rumah untuk polling lokasi mobil (diperlukan untuk kendaraan non i3/i8 yang diproduksi sebelum Juli 2014)" + "use_location": "Gunakan lokasi Home Assistant untuk polling lokasi mobil (diperlukan untuk kendaraan non i3/i8 yang diproduksi sebelum Juli 2014)" } } } diff --git a/homeassistant/components/bsblan/translations/pl.json b/homeassistant/components/bsblan/translations/pl.json index 3667c2432bfca0..c442cda74684b4 100644 --- a/homeassistant/components/bsblan/translations/pl.json +++ b/homeassistant/components/bsblan/translations/pl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" diff --git a/homeassistant/components/climacell/translations/sensor.id.json b/homeassistant/components/climacell/translations/sensor.id.json index 1ee479c51dafb0..37ac0f7d87628f 100644 --- a/homeassistant/components/climacell/translations/sensor.id.json +++ b/homeassistant/components/climacell/translations/sensor.id.json @@ -18,6 +18,7 @@ }, "climacell__precipitation_type": { "freezing_rain": "Hujan Beku", + "ice_pellets": "Hujan Es", "none": "Tidak Ada", "rain": "Hujan", "snow": "Salju" diff --git a/homeassistant/components/cloud/translations/ja.json b/homeassistant/components/cloud/translations/ja.json index 298163c3d41c46..bf0ae52b106b37 100644 --- a/homeassistant/components/cloud/translations/ja.json +++ b/homeassistant/components/cloud/translations/ja.json @@ -6,7 +6,7 @@ "can_reach_cloud": "Home Assistant Cloud\u3078\u306e\u30a2\u30af\u30bb\u30b9", "can_reach_cloud_auth": "\u8a8d\u8a3c\u30b5\u30fc\u30d0\u30fc\u3078\u306e\u30a2\u30af\u30bb\u30b9", "google_enabled": "Google\u6709\u52b9", - "logged_in": "\u30ed\u30b0\u30a4\u30f3\u6e08", + "logged_in": "\u30ed\u30b0\u30a4\u30f3\u6e08\u307f", "relayer_connected": "\u63a5\u7d9a\u3055\u308c\u305f\u518d\u30ec\u30a4\u30e4\u30fc", "remote_connected": "\u30ea\u30e2\u30fc\u30c8\u63a5\u7d9a", "remote_enabled": "\u30ea\u30e2\u30fc\u30c8\u6709\u52b9", diff --git a/homeassistant/components/coinbase/translations/pl.json b/homeassistant/components/coinbase/translations/pl.json index 1f01a9d69c9f54..e93e71d9e269dc 100644 --- a/homeassistant/components/coinbase/translations/pl.json +++ b/homeassistant/components/coinbase/translations/pl.json @@ -25,7 +25,9 @@ }, "options": { "error": { + "currency_unavailable": "Jeden lub wi\u0119cej \u017c\u0105danych sald walutowych nie jest dostarczanych przez interfejs API Coinbase.", "currency_unavaliable": "Jeden lub wi\u0119cej \u017c\u0105danych sald walutowych nie jest dostarczanych przez interfejs API Coinbase.", + "exchange_rate_unavailable": "Jeden lub wi\u0119cej z \u017c\u0105danych kurs\u00f3w wymiany nie jest dostarczany przez Coinbase.", "exchange_rate_unavaliable": "Jeden lub wi\u0119cej z \u017c\u0105danych kurs\u00f3w wymiany nie jest dostarczany przez Coinbase.", "unknown": "Nieoczekiwany b\u0142\u0105d" }, diff --git a/homeassistant/components/dialogflow/translations/pl.json b/homeassistant/components/dialogflow/translations/pl.json index c90ed20af7494d..2ca14c224d9bf2 100644 --- a/homeassistant/components/dialogflow/translations/pl.json +++ b/homeassistant/components/dialogflow/translations/pl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Brak po\u0142\u0105czenia z chmur\u0105 Home Assistant.", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja.", "webhook_not_internet_accessible": "Tw\u00f3j Home Assistant musi by\u0107 dost\u0119pny z Internetu, aby odbiera\u0107 komunikaty webhook" }, diff --git a/homeassistant/components/dnsip/translations/id.json b/homeassistant/components/dnsip/translations/id.json index 8e3dc496a295d1..23313013af4539 100644 --- a/homeassistant/components/dnsip/translations/id.json +++ b/homeassistant/components/dnsip/translations/id.json @@ -6,7 +6,9 @@ "step": { "user": { "data": { - "hostname": "Nama host untuk melakukan kueri DNS" + "hostname": "Nama host untuk melakukan kueri DNS", + "resolver": "Resolver untuk pencarian IPV4", + "resolver_ipv6": "Resolver untuk pencarian IPV6" } } } diff --git a/homeassistant/components/dnsip/translations/pl.json b/homeassistant/components/dnsip/translations/pl.json index 5a7d7a4bff010a..f67e5bbfbee52c 100644 --- a/homeassistant/components/dnsip/translations/pl.json +++ b/homeassistant/components/dnsip/translations/pl.json @@ -6,7 +6,9 @@ "step": { "user": { "data": { - "hostname": "Nazwa hosta, dla kt\u00f3rego ma zosta\u0107 wykonane zapytanie DNS" + "hostname": "Nazwa hosta, dla kt\u00f3rego ma zosta\u0107 wykonane zapytanie DNS", + "resolver": "Program do rozpoznawania nazw dla wyszukiwania IPV4", + "resolver_ipv6": "Program do rozpoznawania nazw dla wyszukiwania IPV6" } } } diff --git a/homeassistant/components/elkm1/translations/ca.json b/homeassistant/components/elkm1/translations/ca.json index ce766c314ed9aa..2317e475a374aa 100644 --- a/homeassistant/components/elkm1/translations/ca.json +++ b/homeassistant/components/elkm1/translations/ca.json @@ -2,24 +2,50 @@ "config": { "abort": { "address_already_configured": "Ja hi ha un Elk-M1 configurat amb aquesta adre\u00e7a", - "already_configured": "Ja hi ha un Elk-M1 configurat amb aquest prefix" + "already_configured": "Ja hi ha un Elk-M1 configurat amb aquest prefix", + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", + "cannot_connect": "Ha fallat la connexi\u00f3" }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "unknown": "Error inesperat" }, + "flow_title": "{mac_address} ({host})", "step": { + "discovered_connection": { + "data": { + "password": "Contrasenya", + "protocol": "Protocol", + "temperature_unit": "Unitat de temperatura que utilitza ElkM1.", + "username": "Nom d'usuari" + }, + "description": "Connecta't al sistema descobert: {mac_address} ({host})", + "title": "Connexi\u00f3 amb el controlador Elk-M1" + }, + "manual_connection": { + "data": { + "address": "Adre\u00e7a IP, domini o port s\u00e8rie (en cas d'una connexi\u00f3 s\u00e8rie).", + "password": "Contrasenya", + "prefix": "Prefix \u00fanic (deixa-ho en blanc si nom\u00e9s tens un \u00fanic controlador ElkM1).", + "protocol": "Protocol", + "temperature_unit": "Unitat de temperatura que utilitza ElkM1.", + "username": "Nom d'usuari" + }, + "description": "La cadena de car\u00e0cters (string) de l'adre\u00e7a ha de tenir el format: 'adre\u00e7a[:port]' tant per al mode 'segur' com el 'no segur'. Exemple: '192.168.1.1'. El port \u00e9s opcional, per defecte \u00e9s el 2101 pel mode 'no segur' i el 2601 pel 'segur'. Per al protocol s\u00e8rie, l'adre\u00e7a ha de tenir el format 'tty[:baud]'. Exemple: '/dev/ttyS1'. La velocitat en bauds \u00e9s opcional (115200 per defecte).", + "title": "Connexi\u00f3 amb el controlador Elk-M1" + }, "user": { "data": { - "address": "Adre\u00e7a IP, domini o port s\u00e8rie (si es est\u00e0 connectat amb una connexi\u00f3 s\u00e8rie).", + "address": "Adre\u00e7a IP, domini o port s\u00e8rie (en cas d'una connexi\u00f3 s\u00e8rie).", + "device": "Dispositiu", "password": "Contrasenya", "prefix": "Prefix \u00fanic (deixa-ho en blanc si nom\u00e9s tens un \u00fanic controlador Elk-M1).", "protocol": "Protocol", "temperature_unit": "Unitats de temperatura que utilitza l'Elk-M1.", "username": "Nom d'usuari" }, - "description": "La cadena de car\u00e0cters (string) de l'adre\u00e7a ha de tenir el format: 'adre\u00e7a[:port]' tant per al mode 'segur' com el 'no segur'. Exemple: '192.168.1.1'. El port \u00e9s opcional, per defecte \u00e9s el 2101 pel mode 'no segur' i el 2601 pel 'segur'. Per al protocol s\u00e8rie, l'adre\u00e7a ha de tenir el format 'tty[:baud]'. Exemple: '/dev/ttyS1'. La velocitat en bauds \u00e9s opcional (115200 per defecte).", + "description": "Selecciona un sistema descobert o 'entrada manual' si no s'han descobert dispositius.", "title": "Connexi\u00f3 amb el controlador Elk-M1" } } diff --git a/homeassistant/components/elkm1/translations/de.json b/homeassistant/components/elkm1/translations/de.json index 137f781fd05a83..cf318a626c9ff3 100644 --- a/homeassistant/components/elkm1/translations/de.json +++ b/homeassistant/components/elkm1/translations/de.json @@ -2,15 +2,28 @@ "config": { "abort": { "address_already_configured": "Ein ElkM1 mit dieser Adresse ist bereits konfiguriert", - "already_configured": "Ein ElkM1 mit diesem Pr\u00e4fix ist bereits konfiguriert" + "already_configured": "Ein ElkM1 mit diesem Pr\u00e4fix ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "cannot_connect": "Verbinden fehlgeschlagen" }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, + "flow_title": "{mac_address} ({host})", "step": { - "user": { + "discovered_connection": { + "data": { + "password": "Passwort", + "protocol": "Protokoll", + "temperature_unit": "Die von ElkM1 verwendete Temperatureinheit.", + "username": "Benutzername" + }, + "description": "Verbinde dich mit dem ermittelten System: {mac_address} ( {host} )", + "title": "Stelle eine Verbindung zur Elk-M1-Steuerung her" + }, + "manual_connection": { "data": { "address": "Die IP-Adresse, die Domain oder der serielle Port bei einer seriellen Verbindung.", "password": "Passwort", @@ -21,6 +34,19 @@ }, "description": "Die Adresszeichenfolge muss in der Form 'adresse[:port]' f\u00fcr 'sicher' und 'nicht sicher' vorliegen. Beispiel: '192.168.1.1'. Der Port ist optional und standardm\u00e4\u00dfig 2101 f\u00fcr \"nicht sicher\" und 2601 f\u00fcr \"sicher\". F\u00fcr das serielle Protokoll muss die Adresse die Form 'tty[:baud]' haben. Beispiel: '/dev/ttyS1'. Der Baudrate ist optional und standardm\u00e4\u00dfig 115200.", "title": "Stelle eine Verbindung zur Elk-M1-Steuerung her" + }, + "user": { + "data": { + "address": "Die IP-Adresse, die Domain oder der serielle Port bei einer seriellen Verbindung.", + "device": "Ger\u00e4t", + "password": "Passwort", + "prefix": "Ein eindeutiges Pr\u00e4fix (leer lassen, wenn du nur einen ElkM1 hast).", + "protocol": "Protokoll", + "temperature_unit": "Die von ElkM1 verwendete Temperatureinheit.", + "username": "Benutzername" + }, + "description": "W\u00e4hle ein erkanntes System oder \"Manuelle Eingabe\", wenn keine Ger\u00e4te erkannt wurden.", + "title": "Stelle eine Verbindung zur Elk-M1-Steuerung her" } } } diff --git a/homeassistant/components/elkm1/translations/en.json b/homeassistant/components/elkm1/translations/en.json index 238f1c3d30ea24..3b1993f3fedf06 100644 --- a/homeassistant/components/elkm1/translations/en.json +++ b/homeassistant/components/elkm1/translations/en.json @@ -17,6 +17,7 @@ "data": { "password": "Password", "protocol": "Protocol", + "temperature_unit": "The temperature unit ElkM1 uses.", "username": "Username" }, "description": "Connect to the discovered system: {mac_address} ({host})", @@ -28,6 +29,7 @@ "password": "Password", "prefix": "A unique prefix (leave blank if you only have one ElkM1).", "protocol": "Protocol", + "temperature_unit": "The temperature unit ElkM1 uses.", "username": "Username" }, "description": "The address string must be in the form 'address[:port]' for 'secure' and 'non-secure'. Example: '192.168.1.1'. The port is optional and defaults to 2101 for 'non-secure' and 2601 for 'secure'. For the serial protocol, the address must be in the form 'tty[:baud]'. Example: '/dev/ttyS1'. The baud is optional and defaults to 115200.", @@ -35,7 +37,13 @@ }, "user": { "data": { - "device": "Device" + "address": "The IP address or domain or serial port if connecting via serial.", + "device": "Device", + "password": "Password", + "prefix": "A unique prefix (leave blank if you only have one ElkM1).", + "protocol": "Protocol", + "temperature_unit": "The temperature unit ElkM1 uses.", + "username": "Username" }, "description": "Choose a discovered system or 'Manual Entry' if no devices have been discovered.", "title": "Connect to Elk-M1 Control" diff --git a/homeassistant/components/elkm1/translations/et.json b/homeassistant/components/elkm1/translations/et.json index 7ced75e0a2be93..d874763045f8eb 100644 --- a/homeassistant/components/elkm1/translations/et.json +++ b/homeassistant/components/elkm1/translations/et.json @@ -2,24 +2,50 @@ "config": { "abort": { "address_already_configured": "Selle aadressiga ElkM1 on juba seadistatud", - "already_configured": "Selle eesliitega ElkM1 on juba seadistatud" + "already_configured": "Selle eesliitega ElkM1 on juba seadistatud", + "already_in_progress": "Seadistamine juba k\u00e4ib", + "cannot_connect": "\u00dchendumine nurjus" }, "error": { "cannot_connect": "\u00dchendamine nurjus", "invalid_auth": "Tuvastamine nurjus", "unknown": "Tundmatu viga" }, + "flow_title": "{mac_address} ({host})", "step": { + "discovered_connection": { + "data": { + "password": "Salas\u00f5na", + "protocol": "Protokoll", + "temperature_unit": "Temperatuuri\u00fchik mida ElkM1 kasutab.", + "username": "Kasutajanimi" + }, + "description": "\u00dchendu avastatud s\u00fcsteemiga: {mac_address} ( {host} )", + "title": "\u00dchendu Elk-M1 Controliga" + }, + "manual_connection": { + "data": { + "address": "IP-aadress v\u00f5i domeen v\u00f5i jadaport kui \u00fchendus toimub jadapordi kaudu.", + "password": "Salas\u00f5na", + "prefix": "Unikaalne eesliide (j\u00e4ta t\u00fchjaks kui sul on ainult \u00fcks ElkM1).", + "protocol": "Protokoll", + "temperature_unit": "Temperatuuri\u00fchik mida ElkM1 kasutab.", + "username": "Kasutajanimi" + }, + "description": "Turvalise ja mitteturvalise aadressi puhul peab aadressi string olema kujul 'address[:port]'. N\u00e4ide: '192.168.1.1'. Port on valikuline ja vaikimisi on see 2101 \"mitteturvalise\" ja 2601 \"turvalise\" puhul. Seeriaprotokolli puhul peab aadress olema kujul 'tty[:baud]'. N\u00e4ide: '/dev/ttyS1'. Baud on valikuline ja vaikimisi 115200.", + "title": "\u00dchendu Elk-M1 Controliga" + }, "user": { "data": { "address": "IP-aadress v\u00f5i domeen v\u00f5i jadaport, kui \u00fchendatakse jadaliidese kaudu.", + "device": "Seade", "password": "Salas\u00f5na", "prefix": "Unikaalne eesliide (j\u00e4ta t\u00fchjaks kui on ainult \u00fcks ElkM1).", "protocol": "Protokoll", "temperature_unit": "ElkM1'i temperatuuri\u00fchik.", "username": "Kasutajanimi" }, - "description": "Aadressistring peab olema kujul \"aadress[:port]\" \"secure\" ja \"non-secure\" puhul. N\u00e4ide: \"192.168.1.1\". Port on valikuline ja vaikimisi 2101 \"secure\" ja 2601 \"non-secure puhul\". Jadaprotokolli puhul peab aadress olema kujul \"tty[:baud]\". N\u00e4ide: \"/dev/ttyS1\". Baud on valikuline ja vaikimisi 115200.", + "description": "Vali avastatud s\u00fcsteem v\u00f5i \"K\u00e4sitsi sisestamine\" kui \u00fchtegi seadet ei ole avastatud.", "title": "\u00dchendu Elk-M1 Control" } } diff --git a/homeassistant/components/elkm1/translations/id.json b/homeassistant/components/elkm1/translations/id.json index e7ddd3cf9ee4e7..782906fac0ab13 100644 --- a/homeassistant/components/elkm1/translations/id.json +++ b/homeassistant/components/elkm1/translations/id.json @@ -2,15 +2,28 @@ "config": { "abort": { "address_already_configured": "ElkM1 dengan alamat ini sudah dikonfigurasi", - "already_configured": "ElkM1 dengan prefiks ini sudah dikonfigurasi" + "already_configured": "ElkM1 dengan prefiks ini sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "cannot_connect": "Gagal terhubung" }, "error": { "cannot_connect": "Gagal terhubung", "invalid_auth": "Autentikasi tidak valid", "unknown": "Kesalahan yang tidak diharapkan" }, + "flow_title": "{mac_address} ({host})", "step": { - "user": { + "discovered_connection": { + "data": { + "password": "Kata Sandi", + "protocol": "Protokol", + "temperature_unit": "Unit suhu yang digunakan ElkM1.", + "username": "Nama Pengguna" + }, + "description": "Hubungkan ke sistem yang ditemukan: {mac_address} ({host})", + "title": "Hubungkan ke Kontrol Elk-M1" + }, + "manual_connection": { "data": { "address": "Alamat IP atau domain atau port serial jika terhubung melalui serial.", "password": "Kata Sandi", @@ -21,6 +34,19 @@ }, "description": "String alamat harus dalam format 'alamat[:port]' untuk 'aman' dan 'tidak aman'. Misalnya, '192.168.1.1'. Port bersifat opsional dan nilai baku adalah 2101 untuk 'tidak aman' dan 2601 untuk 'aman'. Untuk protokol serial, alamat harus dalam format 'tty[:baud]'. Misalnya, '/dev/ttyS1'. Baud bersifat opsional dan nilai bakunya adalah 115200.", "title": "Hubungkan ke Kontrol Elk-M1" + }, + "user": { + "data": { + "address": "Alamat IP atau domain atau port serial jika terhubung melalui serial.", + "device": "Perangkat", + "password": "Kata Sandi", + "prefix": "Prefiks unik (kosongkan jika hanya ada satu ElkM1).", + "protocol": "Protokol", + "temperature_unit": "Unit suhu yang digunakan ElkM1.", + "username": "Nama Pengguna" + }, + "description": "Pilih sistem yang ditemukan atau 'Entri Manual' jika tidak ada perangkat yang ditemukan.", + "title": "Hubungkan ke Kontrol Elk-M1" } } } diff --git a/homeassistant/components/elkm1/translations/ja.json b/homeassistant/components/elkm1/translations/ja.json index a2dea3c10cfba3..c5f8f422b27967 100644 --- a/homeassistant/components/elkm1/translations/ja.json +++ b/homeassistant/components/elkm1/translations/ja.json @@ -2,17 +2,41 @@ "config": { "abort": { "address_already_configured": "\u3053\u306e\u30a2\u30c9\u30ec\u30b9\u306eElkM1\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", - "already_configured": "\u3053\u306e\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u3092\u6301\u3064ElkM1\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" + "already_configured": "\u3053\u306e\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u3092\u6301\u3064ElkM1\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", + "already_in_progress": "\u69cb\u6210\u30d5\u30ed\u30fc\u306f\u3059\u3067\u306b\u9032\u884c\u4e2d\u3067\u3059", + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f" }, "error": { "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, + "flow_title": "{mac_address} ({host})", "step": { + "discovered_connection": { + "data": { + "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", + "protocol": "\u30d7\u30ed\u30c8\u30b3\u30eb", + "temperature_unit": "ElkM1\u304c\u4f7f\u7528\u3059\u308b\u6e29\u5ea6\u5358\u4f4d\u3002", + "username": "\u30e6\u30fc\u30b6\u30fc\u540d" + }, + "description": "\u691c\u51fa\u3055\u308c\u305f\u30b7\u30b9\u30c6\u30e0\u306b\u63a5\u7d9a\u3057\u307e\u3059: {mac_address} ({host})", + "title": "Elk-M1 Control\u306b\u63a5\u7d9a" + }, + "manual_connection": { + "data": { + "address": "IP\u30a2\u30c9\u30ec\u30b9\u307e\u305f\u306f\u30c9\u30e1\u30a4\u30f3\u3001\u3082\u3057\u304f\u306f\u30b7\u30ea\u30a2\u30eb\u3067\u63a5\u7d9a\u3059\u308b\u5834\u5408\u306b\u306f\u30b7\u30ea\u30a2\u30eb\u30dd\u30fc\u30c8\u3002", + "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", + "protocol": "\u30d7\u30ed\u30c8\u30b3\u30eb", + "temperature_unit": "ElkM1\u304c\u4f7f\u7528\u3059\u308b\u6e29\u5ea6\u5358\u4f4d\u3002", + "username": "\u30e6\u30fc\u30b6\u30fc\u540d" + }, + "title": "Elk-M1 Control\u306b\u63a5\u7d9a" + }, "user": { "data": { "address": "IP\u30a2\u30c9\u30ec\u30b9\u307e\u305f\u306f\u30c9\u30e1\u30a4\u30f3\u3001\u30b7\u30ea\u30a2\u30eb\u3067\u63a5\u7d9a\u3059\u308b\u5834\u5408\u306f\u30b7\u30ea\u30a2\u30eb\u30dd\u30fc\u30c8\u3002", + "device": "\u30c7\u30d0\u30a4\u30b9", "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", "prefix": "\u30e6\u30cb\u30fc\u30af(\u4e00\u610f)\u306a\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9(ElkM1\u304c1\u3064\u3057\u304b\u306a\u3044\u5834\u5408\u306f\u7a7a\u767d\u306e\u307e\u307e\u306b\u3057\u307e\u3059)", "protocol": "\u30d7\u30ed\u30c8\u30b3\u30eb", diff --git a/homeassistant/components/elkm1/translations/pl.json b/homeassistant/components/elkm1/translations/pl.json index b9c0322af20909..62900716f62e76 100644 --- a/homeassistant/components/elkm1/translations/pl.json +++ b/homeassistant/components/elkm1/translations/pl.json @@ -2,24 +2,50 @@ "config": { "abort": { "address_already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane z tym adresem", - "already_configured": "ElkM1 z tym prefiksem jest ju\u017c skonfigurowany" + "already_configured": "ElkM1 z tym prefiksem jest ju\u017c skonfigurowany", + "already_in_progress": "Konfiguracja jest ju\u017c w toku", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "invalid_auth": "Niepoprawne uwierzytelnienie", "unknown": "Nieoczekiwany b\u0142\u0105d" }, + "flow_title": "{mac_address} ({host})", "step": { + "discovered_connection": { + "data": { + "password": "Has\u0142o", + "protocol": "Protok\u00f3\u0142", + "temperature_unit": "Jednostka temperatury u\u017cywana przez ElkM1.", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Po\u0142\u0105cz si\u0119 z wykrytym systemem: {mac_address} ({host})", + "title": "Pod\u0142\u0105czenie do sterownika Elk-M1" + }, + "manual_connection": { + "data": { + "address": "Adres IP, domena lub port szeregowy w przypadku po\u0142\u0105czenia szeregowego.", + "password": "Has\u0142o", + "prefix": "Unikalny prefiks (pozostaw puste, je\u015bli masz tylko jedno urz\u0105dzenie ElkM1)", + "protocol": "Protok\u00f3\u0142", + "temperature_unit": "Jednostka temperatury u\u017cywana przez ElkM1.", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Adres musi by\u0107 w postaci 'adres[:port]' dla tryb\u00f3w 'zabezpieczony' i 'niezabezpieczony'. Przyk\u0142ad: '192.168.1.1'. Port jest opcjonalny i domy\u015blnie ustawiony na 2101 dla po\u0142\u0105cze\u0144 'niezabezpieczonych' i 2601 dla 'zabezpieczonych'. W przypadku protoko\u0142u szeregowego adres musi by\u0107 w formie 'tty[:baudrate]'. Przyk\u0142ad: '/dev/ttyS1'. Warto\u015b\u0107 transmisji jest opcjonalna i domy\u015blnie wynosi 115200.", + "title": "Pod\u0142\u0105czenie do sterownika Elk-M1" + }, "user": { "data": { "address": "Adres IP, domena lub port szeregowy w przypadku po\u0142\u0105czenia szeregowego.", + "device": "Urz\u0105dzenie", "password": "Has\u0142o", "prefix": "Unikatowy prefiks (pozostaw pusty, je\u015bli masz tylko jeden ElkM1).", "protocol": "Protok\u00f3\u0142", "temperature_unit": "Jednostka temperatury u\u017cywanej przez ElkM1.", "username": "Nazwa u\u017cytkownika" }, - "description": "Adres musi by\u0107 w postaci 'adres[:port]' dla tryb\u00f3w 'zabezpieczony' i 'niezabezpieczony'. Przyk\u0142ad: '192.168.1.1'. Port jest opcjonalny i domy\u015blnie ustawiony na 2101 dla po\u0142\u0105cze\u0144 'niezabezpieczonych' i 2601 dla 'zabezpieczonych'. W przypadku protoko\u0142u szeregowego adres musi by\u0107 w formie 'tty[:baudrate]'. Przyk\u0142ad: '/dev/ttyS1'. Warto\u015b\u0107 transmisji jest opcjonalna i domy\u015blnie wynosi 115200.", + "description": "Wybierz wykryty system lub \u201eWpis r\u0119czny\u201d, je\u015bli nie wykryto \u017cadnych urz\u0105dze\u0144.", "title": "Pod\u0142\u0105czenie do sterownika Elk-M1" } } diff --git a/homeassistant/components/elkm1/translations/pt-BR.json b/homeassistant/components/elkm1/translations/pt-BR.json index 4178ce86cb678d..bbf0437ba06f2d 100644 --- a/homeassistant/components/elkm1/translations/pt-BR.json +++ b/homeassistant/components/elkm1/translations/pt-BR.json @@ -2,24 +2,50 @@ "config": { "abort": { "address_already_configured": "Um ElkM1 com este endere\u00e7o j\u00e1 est\u00e1 configurado", - "already_configured": "A conta j\u00e1 foi configurada" + "already_configured": "A conta j\u00e1 foi configurada", + "already_in_progress": "A configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "cannot_connect": "Falhou ao conectar" }, "error": { "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, + "flow_title": "{mac_address} ( {host} )", "step": { + "discovered_connection": { + "data": { + "password": "Senha", + "protocol": "Protocolo", + "temperature_unit": "A unidade de temperatura que ElkM1 usa.", + "username": "Nome de usu\u00e1rio" + }, + "description": "Conecte-se ao sistema descoberto: {mac_address} ( {host} )", + "title": "Conecte ao controle Elk-M1" + }, + "manual_connection": { + "data": { + "address": "O endere\u00e7o IP ou dom\u00ednio ou porta serial se estiver conectando via serial.", + "password": "Senha", + "prefix": "Um prefixo exclusivo (deixe em branco se voc\u00ea tiver apenas um ElkM1).", + "protocol": "Protocolo", + "temperature_unit": "A unidade de temperatura que ElkM1 usa.", + "username": "Nome de usu\u00e1rio" + }, + "description": "A string de endere\u00e7o deve estar no formato 'address[:port]' para 'seguro' e 'n\u00e3o seguro'. Exemplo: '192.168.1.1'. A porta \u00e9 opcional e o padr\u00e3o \u00e9 2101 para 'n\u00e3o seguro' e 2601 para 'seguro'. Para o protocolo serial, o endere\u00e7o deve estar no formato 'tty[:baud]'. Exemplo: '/dev/ttyS1'. O baud \u00e9 opcional e o padr\u00e3o \u00e9 115200.", + "title": "Conecte ao controle Elk-M1" + }, "user": { "data": { "address": "O endere\u00e7o IP ou dom\u00ednio ou porta serial se estiver conectando via serial.", + "device": "Dispositivo", "password": "Senha", "prefix": "Um prefixo exclusivo (deixe em branco se voc\u00ea tiver apenas um ElkM1).", "protocol": "Protocolo", "temperature_unit": "A unidade de temperatura que ElkM1 usa.", "username": "Usu\u00e1rio" }, - "description": "A string de endere\u00e7o deve estar no formato 'address[:port]' para 'seguro' e 'n\u00e3o seguro'. Exemplo: '192.168.1.1'. A porta \u00e9 opcional e o padr\u00e3o \u00e9 2101 para 'n\u00e3o seguro' e 2601 para 'seguro'. Para o protocolo serial, o endere\u00e7o deve estar no formato 'tty[:baud]'. Exemplo: '/dev/ttyS1'. O baud \u00e9 opcional e o padr\u00e3o \u00e9 115200.", + "description": "Escolha um sistema descoberto ou 'Entrada Manual' se nenhum dispositivo foi descoberto.", "title": "Conecte ao controle Elk-M1" } } diff --git a/homeassistant/components/elkm1/translations/ru.json b/homeassistant/components/elkm1/translations/ru.json index 954722ecf5267e..1b4bf7250d7f35 100644 --- a/homeassistant/components/elkm1/translations/ru.json +++ b/homeassistant/components/elkm1/translations/ru.json @@ -2,15 +2,28 @@ "config": { "abort": { "address_already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0441 \u044d\u0442\u0438\u043c \u0430\u0434\u0440\u0435\u0441\u043e\u043c \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0441 \u044d\u0442\u0438\u043c \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u043c \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0441 \u044d\u0442\u0438\u043c \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u043c \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "already_in_progress": "\u041f\u0440\u043e\u0446\u0435\u0441\u0441 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." }, "error": { "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, + "flow_title": "{mac_address} ({host})", "step": { - "user": { + "discovered_connection": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "protocol": "\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b", + "temperature_unit": "\u0415\u0434\u0438\u043d\u0438\u0446\u0430 \u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u044b", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + }, + "description": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043d\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u0435: {mac_address} ({host})", + "title": "Elk-M1 Control" + }, + "manual_connection": { "data": { "address": "IP-\u0430\u0434\u0440\u0435\u0441, \u0434\u043e\u043c\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f \u0438\u043b\u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0440\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", @@ -21,6 +34,19 @@ }, "description": "\u0421\u0442\u0440\u043e\u043a\u0430 \u0430\u0434\u0440\u0435\u0441\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u0431\u044b\u0442\u044c \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 'addres[:port]' \u0434\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u043e\u0432 'secure' \u0438 'non-secure' (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: '192.168.1.1'). \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 'port' \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e, \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u043e\u043d \u0440\u0430\u0432\u0435\u043d 2101 \u0434\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0430 'non-secure' \u0438 2601 \u0434\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0430 'secure'. \u0414\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0430 'serial' \u0430\u0434\u0440\u0435\u0441 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 'tty[:baud]' (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: '/dev/ttyS1'). \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 'baud' \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e, \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u043e\u043d \u0440\u0430\u0432\u0435\u043d 115200.", "title": "Elk-M1 Control" + }, + "user": { + "data": { + "address": "IP-\u0430\u0434\u0440\u0435\u0441, \u0434\u043e\u043c\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f \u0438\u043b\u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0440\u0442", + "device": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "prefix": "\u0423\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u0440\u0435\u0444\u0438\u043a\u0441 (\u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c, \u0435\u0441\u043b\u0438 \u0443 \u0412\u0430\u0441 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d ElkM1)", + "protocol": "\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b", + "temperature_unit": "\u0415\u0434\u0438\u043d\u0438\u0446\u0430 \u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u044b", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + }, + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043d\u0443\u044e \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0438\u043b\u0438 'Manual Entry', \u0435\u0441\u043b\u0438 \u043d\u0438\u043a\u0430\u043a\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u0431\u044b\u043b\u0438 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u044b.", + "title": "Elk-M1 Control" } } } diff --git a/homeassistant/components/elkm1/translations/zh-Hant.json b/homeassistant/components/elkm1/translations/zh-Hant.json index 0a2f1f60faac0b..7b25413c6fc535 100644 --- a/homeassistant/components/elkm1/translations/zh-Hant.json +++ b/homeassistant/components/elkm1/translations/zh-Hant.json @@ -2,24 +2,50 @@ "config": { "abort": { "address_already_configured": "\u4f7f\u7528\u6b64\u4f4d\u5740\u7684\u4e00\u7d44 ElkM1 \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "already_configured": "\u4f7f\u7528\u6b64 Prefix \u7684\u4e00\u7d44 ElkM1 \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + "already_configured": "\u4f7f\u7528\u6b64 Prefix \u7684\u4e00\u7d44 ElkM1 \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", + "cannot_connect": "\u9023\u7dda\u5931\u6557" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, + "flow_title": "{mac_address} ({host})", "step": { + "discovered_connection": { + "data": { + "password": "\u5bc6\u78bc", + "protocol": "\u901a\u8a0a\u5354\u5b9a", + "temperature_unit": "ElkM1 \u6240\u4f7f\u7528\u6eab\u5ea6\u55ae\u4f4d\u3002", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "description": "\u9023\u7dda\u81f3\u6240\u63a2\u7d22\u7684\u7cfb\u7d71\uff1a{mac_address} ({host})", + "title": "\u9023\u7dda\u81f3 Elk-M1 Control" + }, + "manual_connection": { + "data": { + "address": "IP \u6216\u7db2\u57df\u540d\u7a31\u3001\u5e8f\u5217\u57e0\uff08\u5047\u5982\u900f\u904e\u5e8f\u5217\u9023\u7dda\uff09\u3002", + "password": "\u5bc6\u78bc", + "prefix": "\u7368\u4e00\u7684 Prefix\uff08\u5047\u5982\u50c5\u6709\u4e00\u7d44 ElkM1 \u5247\u4fdd\u7559\u7a7a\u767d\uff09\u3002", + "protocol": "\u901a\u8a0a\u5354\u5b9a", + "temperature_unit": "ElkM1 \u6240\u4f7f\u7528\u6eab\u5ea6\u55ae\u4f4d\u3002", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "description": "\u52a0\u5bc6\u8207\u975e\u52a0\u5bc6\u4e4b\u4f4d\u5740\u5b57\u4e32\u683c\u5f0f\u5fc5\u9808\u70ba 'address[:port]'\u3002\u4f8b\u5982\uff1a'192.168.1.1'\u3002\u901a\u8a0a\u57e0\u70ba\u9078\u9805\u8f38\u5165\uff0c\u975e\u52a0\u5bc6\u9810\u8a2d\u503c\u70ba 2101\u3001\u52a0\u5bc6\u5247\u70ba 2601\u3002\u5e8f\u5217\u901a\u8a0a\u5354\u5b9a\u3001\u4f4d\u5740\u683c\u5f0f\u5fc5\u9808\u70ba 'tty[:baud]'\u3002\u4f8b\u5982\uff1a'/dev/ttyS1'\u3002\u50b3\u8f38\u7387\u70ba\u9078\u9805\u8f38\u5165\uff0c\u9810\u8a2d\u503c\u70ba 115200\u3002", + "title": "\u9023\u7dda\u81f3 Elk-M1 Control" + }, "user": { "data": { "address": "IP \u6216\u7db2\u57df\u540d\u7a31\u3001\u5e8f\u5217\u57e0\uff08\u5047\u5982\u900f\u904e\u5e8f\u5217\u9023\u7dda\uff09\u3002", + "device": "\u88dd\u7f6e", "password": "\u5bc6\u78bc", "prefix": "\u7368\u4e00\u7684 Prefix\uff08\u5047\u5982\u50c5\u6709\u4e00\u7d44 ElkM1 \u5247\u4fdd\u7559\u7a7a\u767d\uff09\u3002", "protocol": "\u901a\u8a0a\u5354\u5b9a", "temperature_unit": "ElkM1 \u4f7f\u7528\u6eab\u5ea6\u55ae\u4f4d\u3002", "username": "\u4f7f\u7528\u8005\u540d\u7a31" }, - "description": "\u52a0\u5bc6\u8207\u975e\u52a0\u5bc6\u4e4b\u4f4d\u5740\u5b57\u4e32\u683c\u5f0f\u5fc5\u9808\u70ba 'address[:port]'\u3002\u4f8b\u5982\uff1a'192.168.1.1'\u3002\u901a\u8a0a\u57e0\u70ba\u9078\u9805\u8f38\u5165\uff0c\u975e\u52a0\u5bc6\u9810\u8a2d\u503c\u70ba 2101\u3001\u52a0\u5bc6\u5247\u70ba 2601\u3002\u5e8f\u5217\u901a\u8a0a\u5354\u5b9a\u3001\u4f4d\u5740\u683c\u5f0f\u5fc5\u9808\u70ba 'tty[:baud]'\u3002\u4f8b\u5982\uff1a'/dev/ttyS1'\u3002\u50b3\u8f38\u7387\u70ba\u9078\u9805\u8f38\u5165\uff0c\u9810\u8a2d\u503c\u70ba 115200\u3002", + "description": "\u9078\u64c7\u6240\u63a2\u7d22\u5230\u7684\u7cfb\u7d71\uff0c\u6216\u5047\u5982\u6c92\u627e\u5230\u7684\u8a71\u9032\u884c\u624b\u52d5\u8f38\u5165\u3002", "title": "\u9023\u7dda\u81f3 Elk-M1 Control" } } diff --git a/homeassistant/components/fan/translations/pl.json b/homeassistant/components/fan/translations/pl.json index 8cbf872fb9c072..9e4f6b4534114c 100644 --- a/homeassistant/components/fan/translations/pl.json +++ b/homeassistant/components/fan/translations/pl.json @@ -9,6 +9,7 @@ "is_on": "wentylator {entity_name} jest w\u0142\u0105czony" }, "trigger_type": { + "changed_states": "{entity_name} zostanie w\u0142\u0105czony lub wy\u0142\u0105czony", "toggled": "{entity_name} zostanie w\u0142\u0105czony lub wy\u0142\u0105czony", "turned_off": "nast\u0105pi wy\u0142\u0105czenie {entity_name}", "turned_on": "nast\u0105pi w\u0142\u0105czenie {entity_name}" diff --git a/homeassistant/components/fivem/translations/bg.json b/homeassistant/components/fivem/translations/bg.json new file mode 100644 index 00000000000000..d269046fe318c7 --- /dev/null +++ b/homeassistant/components/fivem/translations/bg.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u0418\u043c\u0435", + "port": "\u041f\u043e\u0440\u0442" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/ca.json b/homeassistant/components/fivem/translations/ca.json new file mode 100644 index 00000000000000..dfff0f0ce9399d --- /dev/null +++ b/homeassistant/components/fivem/translations/ca.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Aquest servidor FiveM ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3. Comprova l'amfitri\u00f3 i el port i torna-ho a provar. Assegurat que est\u00e0s utilitzant la versi\u00f3 del servidor FiveM m\u00e9s recent.", + "invalid_gamename": "L'API del joc al qual est\u00e0s intentant connectar-te no \u00e9s d'un joc FiveM." + }, + "step": { + "user": { + "data": { + "host": "Amfitri\u00f3", + "name": "Nom", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/de.json b/homeassistant/components/fivem/translations/de.json new file mode 100644 index 00000000000000..5c6852a126ee1a --- /dev/null +++ b/homeassistant/components/fivem/translations/de.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Der Dienst ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen. Bitte \u00fcberpr\u00fcfe den Host und den Port und versuche es erneut. Vergewissere dich auch, dass du den neuesten FiveM-Server verwendest.", + "invalid_game_name": "Die API des Spiels, mit dem du dich verbinden willst, ist kein FiveM-Spiel.", + "invalid_gamename": "Die API des Spiels, mit dem du dich verbinden willst, ist kein FiveM-Spiel.", + "unknown_error": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Name", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/en.json b/homeassistant/components/fivem/translations/en.json index 8c4f7a54156e18..e07c0666e24615 100644 --- a/homeassistant/components/fivem/translations/en.json +++ b/homeassistant/components/fivem/translations/en.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Failed to connect. Please check the host and port and try again. Also ensure that you are running the latest FiveM server.", "invalid_game_name": "The api of the game you are trying to connect to is not a FiveM game.", + "invalid_gamename": "The api of the game you are trying to connect to is not a FiveM game.", "unknown_error": "Unexpected error" }, "step": { diff --git a/homeassistant/components/fivem/translations/et.json b/homeassistant/components/fivem/translations/et.json new file mode 100644 index 00000000000000..b0c2bce602d2dc --- /dev/null +++ b/homeassistant/components/fivem/translations/et.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "FiveM server on juba seadistatud" + }, + "error": { + "cannot_connect": "\u00dchendamine eba\u00f5nnestus. Kontrolli hosti ja porti ning proovi uuesti. Veendu, et kasutad uusimat FiveM-i serverit.", + "invalid_gamename": "M\u00e4ngu API, millega proovid \u00fchendust luua, ei ole FiveM-m\u00e4ng." + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nimi", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/id.json b/homeassistant/components/fivem/translations/id.json new file mode 100644 index 00000000000000..3cf44f86f5d8a6 --- /dev/null +++ b/homeassistant/components/fivem/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung ke server. Periksa host dan port lalu coba lagi. Pastikan juga Anda menjalankan server FiveM terbaru.", + "invalid_game_name": "API dari permainan yang Anda coba hubungkan bukanlah game FiveM.", + "invalid_gamename": "API dari permainan yang Anda coba hubungkan bukanlah game FiveM.", + "unknown_error": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/ja.json b/homeassistant/components/fivem/translations/ja.json new file mode 100644 index 00000000000000..36bc21ec26d414 --- /dev/null +++ b/homeassistant/components/fivem/translations/ja.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "FiveM\u30b5\u30fc\u30d0\u30fc\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" + }, + "error": { + "invalid_gamename": "\u63a5\u7d9a\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u308b\u30b2\u30fc\u30e0\u306eAPI\u306f\u3001FiveM\u306e\u30b2\u30fc\u30e0\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002" + }, + "step": { + "user": { + "data": { + "host": "\u30db\u30b9\u30c8", + "name": "\u540d\u524d", + "port": "\u30dd\u30fc\u30c8" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/pl.json b/homeassistant/components/fivem/translations/pl.json new file mode 100644 index 00000000000000..592db241d21285 --- /dev/null +++ b/homeassistant/components/fivem/translations/pl.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Serwer FiveM jest ju\u017c skonfigurowany" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia. Sprawd\u017a adres hosta oraz port i spr\u00f3buj ponownie. Upewnij si\u0119, \u017ce posiadasz najnowsz\u0105 wersj\u0119 serwera FiveM.", + "invalid_gamename": "API gry, do kt\u00f3rej pr\u00f3bujesz si\u0119 po\u0142\u0105czy\u0107, nie jest gr\u0105 FiveM." + }, + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "name": "Nazwa", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/pt-BR.json b/homeassistant/components/fivem/translations/pt-BR.json new file mode 100644 index 00000000000000..2b179dda182ab3 --- /dev/null +++ b/homeassistant/components/fivem/translations/pt-BR.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao se conectar. Verifique o host e a porta e tente novamente. Verifique tamb\u00e9m se voc\u00ea est\u00e1 executando o servidor FiveM mais recente.", + "invalid_game_name": "A API do jogo ao qual voc\u00ea est\u00e1 tentando se conectar n\u00e3o \u00e9 um jogo FiveM.", + "invalid_gamename": "A API do jogo ao qual voc\u00ea est\u00e1 tentando se conectar n\u00e3o \u00e9 um jogo FiveM.", + "unknown_error": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Noma", + "port": "Porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/zh-Hant.json b/homeassistant/components/fivem/translations/zh-Hant.json new file mode 100644 index 00000000000000..fdc2d8b49efe8b --- /dev/null +++ b/homeassistant/components/fivem/translations/zh-Hant.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "FiveM \u4f3a\u670d\u5668\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u4f3a\u670d\u5668\u9023\u7dda\u5931\u6557\u3002\u8acb\u6aa2\u67e5\u4e3b\u6a5f\u7aef\u8207\u901a\u8a0a\u57e0\u5f8c\u518d\u8a66\u4e00\u6b21\u3002\u53e6\u8acb\u78ba\u8a8d\u57f7\u884c\u6700\u65b0\u7248 FiveM \u4f3a\u670d\u5668\u3002", + "invalid_gamename": "\u5617\u8a66\u9023\u7dda\u7684\u904a\u6232 API \u4e26\u975e FiveM \u904a\u6232\u3002" + }, + "step": { + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "name": "\u540d\u7a31", + "port": "\u901a\u8a0a\u57e0" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/translations/pl.json b/homeassistant/components/geofency/translations/pl.json index c504e31051a991..109c0b58c7096d 100644 --- a/homeassistant/components/geofency/translations/pl.json +++ b/homeassistant/components/geofency/translations/pl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Brak po\u0142\u0105czenia z chmur\u0105 Home Assistant.", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja.", "webhook_not_internet_accessible": "Tw\u00f3j Home Assistant musi by\u0107 dost\u0119pny z Internetu, aby odbiera\u0107 komunikaty webhook" }, diff --git a/homeassistant/components/github/translations/id.json b/homeassistant/components/github/translations/id.json index 33d580d03f8fcc..93c2616be99460 100644 --- a/homeassistant/components/github/translations/id.json +++ b/homeassistant/components/github/translations/id.json @@ -4,6 +4,9 @@ "already_configured": "Layanan sudah dikonfigurasi", "could_not_register": "Tidak dapat mendaftarkan integrasi dengan GitHub" }, + "progress": { + "wait_for_device": "1. Buka {url} \n2.Tempelkan kunci berikut untuk mengotorisasi integrasi: \n```\n{code}\n```\n" + }, "step": { "repositories": { "data": { diff --git a/homeassistant/components/github/translations/pl.json b/homeassistant/components/github/translations/pl.json index 79f1444ff44611..857958a8490910 100644 --- a/homeassistant/components/github/translations/pl.json +++ b/homeassistant/components/github/translations/pl.json @@ -1,17 +1,18 @@ { "config": { "abort": { + "already_configured": "Us\u0142uga jest ju\u017c skonfigurowana", "could_not_register": "Nie mo\u017cna zarejestrowa\u0107 integracji z GitHub" }, "progress": { - "wait_for_device": "1. Otw\u00f3rz {url}\n 2. Wklej nast\u0119puj\u0105cy klucz, aby autoryzowa\u0107 integracj\u0119:\n ```\n {code}\n ```\n" + "wait_for_device": "1. Otw\u00f3rz {url}\n2. Wklej nast\u0119puj\u0105cy klucz, aby autoryzowa\u0107 integracj\u0119:\n ```\n {code}\n ```\n" }, "step": { "repositories": { "data": { "repositories": "Wybierz repozytoria do \u015bledzenia." }, - "title": "Konfiguruj repozytoria" + "title": "Konfiguracja repozytori\u00f3w" } } } diff --git a/homeassistant/components/gpslogger/translations/pl.json b/homeassistant/components/gpslogger/translations/pl.json index f868f770c5d2c8..beb5447f89c21f 100644 --- a/homeassistant/components/gpslogger/translations/pl.json +++ b/homeassistant/components/gpslogger/translations/pl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Brak po\u0142\u0105czenia z chmur\u0105 Home Assistant.", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja.", "webhook_not_internet_accessible": "Tw\u00f3j Home Assistant musi by\u0107 dost\u0119pny z Internetu, aby odbiera\u0107 komunikaty webhook" }, diff --git a/homeassistant/components/homekit/translations/id.json b/homeassistant/components/homekit/translations/id.json index 851cac800e60e1..5faedff893b6c1 100644 --- a/homeassistant/components/homekit/translations/id.json +++ b/homeassistant/components/homekit/translations/id.json @@ -12,7 +12,7 @@ "data": { "include_domains": "Domain yang disertakan" }, - "description": "Pilih domain yang akan disertakan. Semua entitas yang didukung di domain akan disertakan. Instans HomeKit terpisah dalam mode aksesori akan dibuat untuk setiap pemutar media TV, remote berbasis aktivitas, kunci, dan kamera.", + "description": "Pilih domain yang akan disertakan. Semua entitas yang didukung di domain akan disertakan kecuali entitas yang dikategorikan. Instans HomeKit terpisah dalam mode aksesori akan dibuat untuk setiap pemutar media TV, remote berbasis aktivitas, kunci, dan kamera.", "title": "Pilih domain yang akan disertakan" } } diff --git a/homeassistant/components/homekit/translations/pl.json b/homeassistant/components/homekit/translations/pl.json index 52dc19b164db6f..745f07bdadbc26 100644 --- a/homeassistant/components/homekit/translations/pl.json +++ b/homeassistant/components/homekit/translations/pl.json @@ -12,7 +12,7 @@ "data": { "include_domains": "Domeny do uwzgl\u0119dnienia" }, - "description": "Wybierz domeny do uwzgl\u0119dnienia. Wszystkie wspierane encje w danej domenie b\u0119d\u0105 uwzgl\u0119dnione. W trybie akcesorium, oddzielna instancja HomeKit zostanie utworzona dla ka\u017cdego tv media playera, pilota na bazie aktywno\u015bci, zamka oraz kamery.", + "description": "Wybierz domeny do uwzgl\u0119dnienia. Wszystkie wspierane encje w danej domenie b\u0119d\u0105 uwzgl\u0119dnione z wyj\u0105tkiem skategoryzowanych encji. W trybie akcesorium, oddzielna instancja HomeKit zostanie utworzona dla ka\u017cdego tv media playera, pilota na bazie aktywno\u015bci, zamka oraz kamery.", "title": "Wybierz uwzgl\u0119dniane domeny" } } @@ -42,6 +42,9 @@ "title": "Konfiguracja kamery" }, "exclude": { + "data": { + "entities": "Encje" + }, "description": "Wszystkie encje \"{domains}\" zostan\u0105 uwzgl\u0119dnione z wyj\u0105tkiem encji wykluczonych oraz encji skategoryzowanych.", "title": "Wybierz encje do wykluczenia" }, @@ -62,12 +65,13 @@ }, "init": { "data": { + "domains": "Domeny do uwzgl\u0119dnienia", "include_domains": "Domeny do uwzgl\u0119dnienia", - "include_exclude_mode": "Tryb w\u0142\u0105czania", - "mode": "Tryb" + "include_exclude_mode": "Tryb dodawania", + "mode": "Tryb HomeKit" }, "description": "HomeKit mo\u017ce by\u0107 skonfigurowany, by pokaza\u0142 mostek lub pojedyncze akcesorium. W trybie \"Akcesorium\", tylko pojedyncza encja mo\u017ce by\u0107 u\u017cyta. Tryb ten jest wymagany w przypadku odtwarzaczy multimedialnych z klas\u0105 urz\u0105dzenia TV, by dzia\u0142a\u0142 prawid\u0142owo. Encje w \"uwzgl\u0119dnionych domenach\" b\u0119d\u0105 widoczne w HomeKit. B\u0119dziesz m\u00f3g\u0142 wybra\u0107, kt\u00f3re encje maj\u0105 zosta\u0107 uwzgl\u0119dnione lub wykluczone z tej listy na nast\u0119pnym ekranie.", - "title": "Domeny do uwzgl\u0119dnienia." + "title": "Wybierz tryb i domeny." }, "yaml": { "description": "Ten wpis jest kontrolowany przez YAML", diff --git a/homeassistant/components/homekit_controller/translations/select.pl.json b/homeassistant/components/homekit_controller/translations/select.pl.json new file mode 100644 index 00000000000000..0a59529b6ba7ef --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/select.pl.json @@ -0,0 +1,9 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "away": "Poza domem", + "home": "W domu", + "sleep": "U\u015bpiony" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homewizard/translations/pl.json b/homeassistant/components/homewizard/translations/pl.json index 881521a2c2995f..4388fa7d5179d4 100644 --- a/homeassistant/components/homewizard/translations/pl.json +++ b/homeassistant/components/homewizard/translations/pl.json @@ -2,8 +2,9 @@ "config": { "abort": { "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", - "api_not_enabled": "Interfejs API nie jest w\u0142\u0105czony. W\u0142\u0105cz API w aplikacji HomeWizard Energy w ustawieniach.", + "api_not_enabled": "Interfejs API nie jest w\u0142\u0105czony. W\u0142\u0105cz API w ustawieniach aplikacji HomeWizard Energy.", "device_not_supported": "To urz\u0105dzenie nie jest obs\u0142ugiwane", + "invalid_discovery_parameters": "Wykryto nieobs\u0142ugiwan\u0105 wersj\u0119 API", "unknown_error": "Nieoczekiwany b\u0142\u0105d" }, "step": { diff --git a/homeassistant/components/humidifier/translations/pl.json b/homeassistant/components/humidifier/translations/pl.json index 34e67bcf73e72f..5c82a1e944bd98 100644 --- a/homeassistant/components/humidifier/translations/pl.json +++ b/homeassistant/components/humidifier/translations/pl.json @@ -13,6 +13,7 @@ "is_on": "nawil\u017cacz {entity_name} jest w\u0142\u0105czony" }, "trigger_type": { + "changed_states": "{entity_name} zostanie w\u0142\u0105czony lub wy\u0142\u0105czony", "target_humidity_changed": "zmieni si\u0119 wilgotno\u015b\u0107 docelowa {entity_name}", "toggled": "{entity_name} zostanie w\u0142\u0105czony lub wy\u0142\u0105czony", "turned_off": "nast\u0105pi wy\u0142\u0105czenie {entity_name}", diff --git a/homeassistant/components/ifttt/translations/pl.json b/homeassistant/components/ifttt/translations/pl.json index d8d7bff1585498..21144ed7829a65 100644 --- a/homeassistant/components/ifttt/translations/pl.json +++ b/homeassistant/components/ifttt/translations/pl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Brak po\u0142\u0105czenia z chmur\u0105 Home Assistant.", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja.", "webhook_not_internet_accessible": "Tw\u00f3j Home Assistant musi by\u0107 dost\u0119pny z Internetu, aby odbiera\u0107 komunikaty webhook" }, diff --git a/homeassistant/components/intellifire/translations/pl.json b/homeassistant/components/intellifire/translations/pl.json new file mode 100644 index 00000000000000..d455990b1f0326 --- /dev/null +++ b/homeassistant/components/intellifire/translations/pl.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iss/translations/id.json b/homeassistant/components/iss/translations/id.json index 3a870e47986bf6..c533197ca840c4 100644 --- a/homeassistant/components/iss/translations/id.json +++ b/homeassistant/components/iss/translations/id.json @@ -1,7 +1,16 @@ { "config": { "abort": { + "latitude_longitude_not_defined": "Lintang dan bujur tidak didefinisikan dalam Home Assistant.", "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "user": { + "data": { + "show_on_map": "Tampilkan di peta?" + }, + "description": "Ingin mengonfigurasi Stasiun Luar Angkasa Internasional?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/iss/translations/pl.json b/homeassistant/components/iss/translations/pl.json new file mode 100644 index 00000000000000..b7ab8eebbafa6d --- /dev/null +++ b/homeassistant/components/iss/translations/pl.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "latitude_longitude_not_defined": "Szeroko\u015b\u0107 i d\u0142ugo\u015b\u0107 geograficzna nie s\u0105 zdefiniowane w Home Assistant.", + "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." + }, + "step": { + "user": { + "data": { + "show_on_map": "Pokaza\u0107 na mapie?" + }, + "description": "Czy chcesz skonfigurowa\u0107 Mi\u0119dzynarodow\u0105 Stacj\u0119 Kosmiczn\u0105?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/isy994/translations/ja.json b/homeassistant/components/isy994/translations/ja.json index f1021cbd6e957d..271cba9e0c489f 100644 --- a/homeassistant/components/isy994/translations/ja.json +++ b/homeassistant/components/isy994/translations/ja.json @@ -39,7 +39,7 @@ }, "system_health": { "info": { - "device_connected": "ISY\u63a5\u7d9a\u6e08", + "device_connected": "ISY\u63a5\u7d9a\u6e08\u307f", "host_reachable": "\u30db\u30b9\u30c8\u306b\u30a2\u30af\u30bb\u30b9\u53ef\u80fd", "last_heartbeat": "\u6700\u5f8c\u306e\u30cf\u30fc\u30c8\u30d3\u30fc\u30c8\u30bf\u30a4\u30e0", "websocket_status": "\u30a4\u30d9\u30f3\u30c8\u30bd\u30b1\u30c3\u30c8 \u30b9\u30c6\u30fc\u30bf\u30b9" diff --git a/homeassistant/components/knx/translations/pl.json b/homeassistant/components/knx/translations/pl.json index 17770f9e1ddab5..8a30af3fd5904b 100644 --- a/homeassistant/components/knx/translations/pl.json +++ b/homeassistant/components/knx/translations/pl.json @@ -14,7 +14,8 @@ "individual_address": "Indywidualny adres dla po\u0142\u0105czenia", "local_ip": "Lokalny adres IP Home Assistant (pozostaw puste w celu automatycznego wykrywania)", "port": "Port", - "route_back": "Tryb Route Back / NAT" + "route_back": "Tryb Route Back / NAT", + "tunneling_type": "Typ tunelowania KNX" }, "description": "Prosz\u0119 wprowadzi\u0107 informacje o po\u0142\u0105czeniu urz\u0105dzenia tuneluj\u0105cego." }, @@ -59,7 +60,8 @@ "host": "Nazwa hosta lub adres IP", "local_ip": "Lokalny adres IP (pozostaw pusty, je\u015bli nie masz pewno\u015bci)", "port": "Port", - "route_back": "Tryb Route Back / NAT" + "route_back": "Tryb Route Back / NAT", + "tunneling_type": "Typ tunelowania KNX" } } } diff --git a/homeassistant/components/launch_library/translations/id.json b/homeassistant/components/launch_library/translations/id.json index 3a870e47986bf6..a6b60231a85df7 100644 --- a/homeassistant/components/launch_library/translations/id.json +++ b/homeassistant/components/launch_library/translations/id.json @@ -2,6 +2,11 @@ "config": { "abort": { "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "user": { + "description": "Ingin mengonfigurasi Launch Library?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/light/translations/pl.json b/homeassistant/components/light/translations/pl.json index c1a3a0a8084cd7..375cf8a8ce3f2b 100644 --- a/homeassistant/components/light/translations/pl.json +++ b/homeassistant/components/light/translations/pl.json @@ -13,6 +13,7 @@ "is_on": "\u015bwiat\u0142o {entity_name} jest w\u0142\u0105czone" }, "trigger_type": { + "changed_states": "{entity_name} zostanie w\u0142\u0105czony lub wy\u0142\u0105czony", "toggled": "{entity_name} zostanie w\u0142\u0105czony lub wy\u0142\u0105czony", "turned_off": "nast\u0105pi wy\u0142\u0105czenie {entity_name}", "turned_on": "nast\u0105pi w\u0142\u0105czenie {entity_name}" diff --git a/homeassistant/components/locative/translations/pl.json b/homeassistant/components/locative/translations/pl.json index f91afa32f74a62..2eeb40aee6b660 100644 --- a/homeassistant/components/locative/translations/pl.json +++ b/homeassistant/components/locative/translations/pl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Brak po\u0142\u0105czenia z chmur\u0105 Home Assistant.", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja.", "webhook_not_internet_accessible": "Tw\u00f3j Home Assistant musi by\u0107 dost\u0119pny z Internetu, aby odbiera\u0107 komunikaty webhook" }, diff --git a/homeassistant/components/luftdaten/translations/pl.json b/homeassistant/components/luftdaten/translations/pl.json index 60fc07142286dc..7045a0a3dbeb1f 100644 --- a/homeassistant/components/luftdaten/translations/pl.json +++ b/homeassistant/components/luftdaten/translations/pl.json @@ -9,7 +9,7 @@ "user": { "data": { "show_on_map": "Poka\u017c na mapie", - "station_id": "ID sensora Luftdaten" + "station_id": "ID sensora" }, "title": "Konfiguracja Luftdaten" } diff --git a/homeassistant/components/mailgun/translations/pl.json b/homeassistant/components/mailgun/translations/pl.json index 931cd7a8167024..dcd95ab75caacb 100644 --- a/homeassistant/components/mailgun/translations/pl.json +++ b/homeassistant/components/mailgun/translations/pl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Brak po\u0142\u0105czenia z chmur\u0105 Home Assistant.", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja.", "webhook_not_internet_accessible": "Tw\u00f3j Home Assistant musi by\u0107 dost\u0119pny z Internetu, aby odbiera\u0107 komunikaty webhook" }, diff --git a/homeassistant/components/media_player/translations/pl.json b/homeassistant/components/media_player/translations/pl.json index 2a70661d788831..08c664a909d00e 100644 --- a/homeassistant/components/media_player/translations/pl.json +++ b/homeassistant/components/media_player/translations/pl.json @@ -8,6 +8,7 @@ "is_playing": "{entity_name} odtwarza media" }, "trigger_type": { + "changed_states": "{entity_name} zmieni\u0142o stan", "idle": "odtwarzacz {entity_name} stanie si\u0119 bezczynny", "paused": "odtwarzacz {entity_name} zostanie wstrzymany", "playing": "odtwarzacz {entity_name} rozpocznie odtwarzanie", diff --git a/homeassistant/components/mutesync/translations/id.json b/homeassistant/components/mutesync/translations/id.json index 31da1a72f64c24..e67dddbb0b4c90 100644 --- a/homeassistant/components/mutesync/translations/id.json +++ b/homeassistant/components/mutesync/translations/id.json @@ -2,7 +2,7 @@ "config": { "error": { "cannot_connect": "Gagal terhubung", - "invalid_auth": "Aktifkan autentikasi di m\u00fctesync: Preferensi > Autentikasi", + "invalid_auth": "Aktifkan autentikasi di m\u00fctesync Preferensi > Autentikasi", "unknown": "Kesalahan yang tidak diharapkan" }, "step": { diff --git a/homeassistant/components/netgear/translations/pl.json b/homeassistant/components/netgear/translations/pl.json index f5feb67cfe116e..0f3a6ad3cde6d5 100644 --- a/homeassistant/components/netgear/translations/pl.json +++ b/homeassistant/components/netgear/translations/pl.json @@ -15,7 +15,7 @@ "ssl": "Certyfikat SSL", "username": "Nazwa u\u017cytkownika (opcjonalnie)" }, - "description": "Domy\u015blne IP lub nazwa hosta: {host}\nDomy\u015blny port: {port}\nDomy\u015blna nazwa u\u017cytkownika: {username}", + "description": "Domy\u015blne IP lub nazwa hosta: {host}\nDomy\u015blna nazwa u\u017cytkownika: {username}", "title": "Netgear" } } diff --git a/homeassistant/components/overkiz/translations/de.json b/homeassistant/components/overkiz/translations/de.json index 38e69556faaa08..84d09f9116bcc9 100644 --- a/homeassistant/components/overkiz/translations/de.json +++ b/homeassistant/components/overkiz/translations/de.json @@ -12,6 +12,7 @@ "too_many_requests": "Zu viele Anfragen, versuche es sp\u00e4ter erneut.", "unknown": "Unerwarteter Fehler" }, + "flow_title": "Gateway: {gateway_id}", "step": { "user": { "data": { diff --git a/homeassistant/components/overkiz/translations/id.json b/homeassistant/components/overkiz/translations/id.json index 3bbdb67e6ad7ee..becbae65f9eb4c 100644 --- a/homeassistant/components/overkiz/translations/id.json +++ b/homeassistant/components/overkiz/translations/id.json @@ -12,6 +12,7 @@ "too_many_requests": "Terlalu banyak permintaan, coba lagi nanti.", "unknown": "Kesalahan yang tidak diharapkan" }, + "flow_title": "Gateway: {gateway_id}", "step": { "user": { "data": { diff --git a/homeassistant/components/overkiz/translations/ja.json b/homeassistant/components/overkiz/translations/ja.json index b2bf92f329f623..bbf2b15d13e0fd 100644 --- a/homeassistant/components/overkiz/translations/ja.json +++ b/homeassistant/components/overkiz/translations/ja.json @@ -11,6 +11,7 @@ "too_many_requests": "\u30ea\u30af\u30a8\u30b9\u30c8\u304c\u591a\u3059\u304e\u307e\u3059\u3002\u3057\u3070\u3089\u304f\u3057\u3066\u304b\u3089\u3082\u3046\u4e00\u5ea6\u304a\u8a66\u3057\u304f\u3060\u3055\u3044\u3002", "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, + "flow_title": "\u30b2\u30fc\u30c8\u30a6\u30a7\u30a4: {gateway_id}", "step": { "user": { "data": { diff --git a/homeassistant/components/overkiz/translations/pl.json b/homeassistant/components/overkiz/translations/pl.json index 7705581178b5b4..6881d7edb5b4e0 100644 --- a/homeassistant/components/overkiz/translations/pl.json +++ b/homeassistant/components/overkiz/translations/pl.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "already_configured": "Konto jest ju\u017c skonfigurowane" + "already_configured": "Konto jest ju\u017c skonfigurowane", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119", + "reauth_wrong_account": "Mo\u017cesz ponownie uwierzytelni\u0107 ten wpis tylko przy u\u017cyciu tego samego konta Overkiz i huba." }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", @@ -10,6 +12,7 @@ "too_many_requests": "Zbyt wiele \u017c\u0105da\u0144, spr\u00f3buj ponownie p\u00f3\u017aniej.", "unknown": "Nieoczekiwany b\u0142\u0105d" }, + "flow_title": "Bramka: {gateway_id}", "step": { "user": { "data": { diff --git a/homeassistant/components/overkiz/translations/ru.json b/homeassistant/components/overkiz/translations/ru.json index d351645cebd809..611c478490273c 100644 --- a/homeassistant/components/overkiz/translations/ru.json +++ b/homeassistant/components/overkiz/translations/ru.json @@ -12,6 +12,7 @@ "too_many_requests": "\u0421\u043b\u0438\u0448\u043a\u043e\u043c \u043c\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432, \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0437\u0436\u0435.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, + "flow_title": "\u0428\u043b\u044e\u0437: {gateway_id}", "step": { "user": { "data": { diff --git a/homeassistant/components/overkiz/translations/select.pl.json b/homeassistant/components/overkiz/translations/select.pl.json index 00769a3799ec33..0989aec66fee9d 100644 --- a/homeassistant/components/overkiz/translations/select.pl.json +++ b/homeassistant/components/overkiz/translations/select.pl.json @@ -5,8 +5,8 @@ "standard": "Normalnie" }, "overkiz__open_closed_pedestrian": { - "closed": "Zamkni\u0119te", - "open": "Otw\u00f3rz", + "closed": "Zamkni\u0119ta", + "open": "Otwarte", "pedestrian": "Pieszy" } } diff --git a/homeassistant/components/overkiz/translations/sensor.id.json b/homeassistant/components/overkiz/translations/sensor.id.json index f51b432b64212d..efe4f588a71ea2 100644 --- a/homeassistant/components/overkiz/translations/sensor.id.json +++ b/homeassistant/components/overkiz/translations/sensor.id.json @@ -19,9 +19,17 @@ "myself": "Saya sendiri", "rain": "Hujan", "saac": "SAAC", - "security": "Keamanan" + "security": "Keamanan", + "sfc": "SFC", + "temperature": "Suhu", + "timer": "Timer", + "ups": "UPS", + "user": "Pengguna", + "wind": "Angin" }, "overkiz__sensor_defect": { + "dead": "Mati", + "low_battery": "Baterai lemah", "maintenance_required": "Diperlukan perawatan", "no_defect": "Tidak ada cacat" }, diff --git a/homeassistant/components/overkiz/translations/sensor.pl.json b/homeassistant/components/overkiz/translations/sensor.pl.json index 6b942c21a9a32e..0633c0d8424842 100644 --- a/homeassistant/components/overkiz/translations/sensor.pl.json +++ b/homeassistant/components/overkiz/translations/sensor.pl.json @@ -1,29 +1,41 @@ { "state": { + "overkiz__battery": { + "full": "pe\u0142na", + "low": "niski", + "normal": "normalny", + "verylow": "bardzo niski" + }, "overkiz__discrete_rssi_level": { - "verylow": "Bardzo niskie" + "good": "dobry", + "low": "s\u0142aby", + "normal": "normalny", + "verylow": "bardzo s\u0142aby" }, "overkiz__priority_lock_originator": { - "external_gateway": "Bramka zewn\u0119trzna", - "local_user": "U\u017cytkownik lokalny", + "external_gateway": "bramka zewn\u0119trzna", + "local_user": "u\u017cytkownik lokalny", + "lsc": "LSC", "myself": "Ja", - "rain": "Deszcz", - "security": "Bezpiecze\u0144stwo", - "temperature": "Temperatura", - "timer": "Minutnik", + "rain": "deszcz", + "saac": "SAAC", + "security": "bezpiecze\u0144stwo", + "sfc": "SFC", + "temperature": "temperatura", + "timer": "minutnik", "ups": "UPS", - "user": "U\u017cytkownik", - "wind": "Wiatr" + "user": "u\u017cytkownik", + "wind": "wiatr" }, "overkiz__sensor_defect": { - "dead": "Martwe", - "low_battery": "Niski poziom baterii", - "maintenance_required": "Wymagany przegl\u0105d", - "no_defect": "Brak uszkodze\u0144" + "dead": "martwe", + "low_battery": "niski poziom baterii", + "maintenance_required": "wymagany przegl\u0105d", + "no_defect": "brak uszkodze\u0144" }, "overkiz__sensor_room": { - "clean": "Czyste", - "dirty": "Brudne" + "clean": "czysto", + "dirty": "brudno" } } } \ No newline at end of file diff --git a/homeassistant/components/overkiz/translations/zh-Hant.json b/homeassistant/components/overkiz/translations/zh-Hant.json index 371f1ce022ef09..04c2fc2b07a096 100644 --- a/homeassistant/components/overkiz/translations/zh-Hant.json +++ b/homeassistant/components/overkiz/translations/zh-Hant.json @@ -12,6 +12,7 @@ "too_many_requests": "\u8acb\u6c42\u6b21\u6578\u904e\u591a\uff0c\u8acb\u7a0d\u5f8c\u91cd\u8a66\u3002", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, + "flow_title": "\u9598\u9053\u5668\uff1a{gateway_id}", "step": { "user": { "data": { diff --git a/homeassistant/components/owntracks/translations/pl.json b/homeassistant/components/owntracks/translations/pl.json index 98c8779fe1f9a3..a54a366849d70a 100644 --- a/homeassistant/components/owntracks/translations/pl.json +++ b/homeassistant/components/owntracks/translations/pl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Brak po\u0142\u0105czenia z chmur\u0105 Home Assistant.", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." }, "create_entry": { diff --git a/homeassistant/components/plaato/translations/pl.json b/homeassistant/components/plaato/translations/pl.json index ddfb779ea2e7d9..af94e340408db1 100644 --- a/homeassistant/components/plaato/translations/pl.json +++ b/homeassistant/components/plaato/translations/pl.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Konto jest ju\u017c skonfigurowane", + "cloud_not_connected": "Brak po\u0142\u0105czenia z chmur\u0105 Home Assistant.", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja.", "webhook_not_internet_accessible": "Tw\u00f3j Home Assistant musi by\u0107 dost\u0119pny z Internetu, aby odbiera\u0107 komunikaty webhook" }, diff --git a/homeassistant/components/powerwall/translations/bg.json b/homeassistant/components/powerwall/translations/bg.json index cef3726d759676..d5c10289c0928e 100644 --- a/homeassistant/components/powerwall/translations/bg.json +++ b/homeassistant/components/powerwall/translations/bg.json @@ -1,6 +1,11 @@ { "config": { "step": { + "reauth_confim": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430" + } + }, "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u0430" diff --git a/homeassistant/components/powerwall/translations/pl.json b/homeassistant/components/powerwall/translations/pl.json index 8d4f82fa14eab6..9c56edf13b9e58 100644 --- a/homeassistant/components/powerwall/translations/pl.json +++ b/homeassistant/components/powerwall/translations/pl.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" }, "error": { @@ -10,8 +11,19 @@ "unknown": "Nieoczekiwany b\u0142\u0105d", "wrong_version": "Powerwall u\u017cywa wersji oprogramowania, kt\u00f3ra nie jest obs\u0142ugiwana. Rozwa\u017c uaktualnienie lub zg\u0142oszenie tego problemu, aby mo\u017cna go by\u0142o rozwi\u0105za\u0107." }, - "flow_title": "{ip_address}", + "flow_title": "{name} ({ip_address})", "step": { + "confirm_discovery": { + "description": "Czy chcesz skonfigurowa\u0107 {name} ({ip_address})?", + "title": "Po\u0142\u0105czenie z Powerwall" + }, + "reauth_confim": { + "data": { + "password": "Has\u0142o" + }, + "description": "Has\u0142o to zazwyczaj 5 ostatnich znak\u00f3w numeru seryjnego Backup Gateway i mo\u017cna je znale\u017a\u0107 w aplikacji Tesla; lub ostatnie 5 znak\u00f3w has\u0142a na wewn\u0119trznej stronie drzwiczek Backup Gateway 2.", + "title": "Ponownie uwierzytelnij powerwall" + }, "user": { "data": { "ip_address": "Adres IP", diff --git a/homeassistant/components/remote/translations/pl.json b/homeassistant/components/remote/translations/pl.json index 2724fdd05f2472..2aaaf6dbd0124a 100644 --- a/homeassistant/components/remote/translations/pl.json +++ b/homeassistant/components/remote/translations/pl.json @@ -10,6 +10,7 @@ "is_on": "pilot {entity_name} jest w\u0142\u0105czony" }, "trigger_type": { + "changed_states": "{entity_name} zostanie w\u0142\u0105czony lub wy\u0142\u0105czony", "toggled": "{entity_name} zostanie w\u0142\u0105czony lub wy\u0142\u0105czony", "turned_off": "nast\u0105pi wy\u0142\u0105czenie {entity_name}", "turned_on": "nast\u0105pi w\u0142\u0105czenie {entity_name}" diff --git a/homeassistant/components/rtsp_to_webrtc/translations/id.json b/homeassistant/components/rtsp_to_webrtc/translations/id.json index 3a870e47986bf6..105e4072300a76 100644 --- a/homeassistant/components/rtsp_to_webrtc/translations/id.json +++ b/homeassistant/components/rtsp_to_webrtc/translations/id.json @@ -1,7 +1,27 @@ { "config": { "abort": { + "server_failure": "Server RTSPtoWebRTC mengembalikan kesalahan. Periksa log untuk informasi lebih lanjut.", + "server_unreachable": "Tidak dapat berkomunikasi dengan server RTSPtoWebRTC. Periksa log untuk informasi lebih lanjut.", "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "invalid_url": "Harus menjadi URL server RTSPtoWebRTC yang valid misalnya https://example.com", + "server_failure": "Server RTSPtoWebRTC mengembalikan kesalahan. Periksa log untuk informasi lebih lanjut.", + "server_unreachable": "Tidak dapat berkomunikasi dengan server RTSPtoWebRTC. Periksa log untuk informasi lebih lanjut." + }, + "step": { + "hassio_confirm": { + "description": "Ingin mengonfigurasi Home Assistant untuk terhubung ke server RTSPtoWebRTC yang disediakan oleh add-on: {addon}?", + "title": "RTSPtoWebRTC melalui add-on Home Assistant" + }, + "user": { + "data": { + "server_url": "URL server RTSPtoWebRTC misalnya https://example.com" + }, + "description": "Integrasi RTSPtoWebRTC membutuhkan server untuk menerjemahkan aliran RTSP ke WebRTC. Masukkan URL ke server RTSPtoWebRTC.", + "title": "Konfigurasikan RTSPtoWebrTC" + } } } } \ No newline at end of file diff --git a/homeassistant/components/rtsp_to_webrtc/translations/pl.json b/homeassistant/components/rtsp_to_webrtc/translations/pl.json index 34307edfce06f0..25f3ffe785f04f 100644 --- a/homeassistant/components/rtsp_to_webrtc/translations/pl.json +++ b/homeassistant/components/rtsp_to_webrtc/translations/pl.json @@ -1,6 +1,8 @@ { "config": { "abort": { + "server_failure": "Serwer RTSPtoWebRTC zwr\u00f3ci\u0142 b\u0142\u0105d. Sprawd\u017a logi, aby uzyska\u0107 wi\u0119cej informacji.", + "server_unreachable": "Nie mo\u017cna nawi\u0105za\u0107 komunikacji z serwerem RTSPtoWebRTC. Sprawd\u017a logi, aby uzyska\u0107 wi\u0119cej informacji.", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." }, "error": { diff --git a/homeassistant/components/senseme/translations/id.json b/homeassistant/components/senseme/translations/id.json index 5d3b29018dd80f..9aef5e20dbd5a0 100644 --- a/homeassistant/components/senseme/translations/id.json +++ b/homeassistant/components/senseme/translations/id.json @@ -8,11 +8,22 @@ "cannot_connect": "Gagal terhubung", "invalid_host": "Nama host atau alamat IP tidak valid" }, + "flow_title": "{name} - {model} ({host})", "step": { + "discovery_confirm": { + "description": "Ingin menyiapkan {name} - {model} ({host})?" + }, "manual": { "data": { "host": "Host" - } + }, + "description": "Masukkan Alamat IP." + }, + "user": { + "data": { + "device": "Perangkat" + }, + "description": "Pilih perangkat, atau pilih 'Alamat IP' untuk memasukkan Alamat IP secara manual." } } } diff --git a/homeassistant/components/senseme/translations/pl.json b/homeassistant/components/senseme/translations/pl.json index f348b97d111230..a5fb61a685b771 100644 --- a/homeassistant/components/senseme/translations/pl.json +++ b/homeassistant/components/senseme/translations/pl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", diff --git a/homeassistant/components/sensor/translations/pl.json b/homeassistant/components/sensor/translations/pl.json index 39f0e35a93b932..05c0a69708c75d 100644 --- a/homeassistant/components/sensor/translations/pl.json +++ b/homeassistant/components/sensor/translations/pl.json @@ -8,7 +8,7 @@ "is_current": "obecne nat\u0119\u017cenie pr\u0105du {entity_name}", "is_energy": "obecna energia {entity_name}", "is_frequency": "Obecna cz\u0119stotliwo\u015b\u0107 {entity_name}", - "is_gas": "obecny poziom gazu {entity_name}", + "is_gas": "Obecny poziom gazu {entity_name}", "is_humidity": "obecna wilgotno\u015b\u0107 {entity_name}", "is_illuminance": "obecne nat\u0119\u017cenie o\u015bwietlenia {entity_name}", "is_nitrogen_dioxide": "obecny poziom st\u0119\u017cenia dwutlenku azotu {entity_name}", diff --git a/homeassistant/components/sia/translations/id.json b/homeassistant/components/sia/translations/id.json index e7ab7918fb3e4a..4c80d089cbe823 100644 --- a/homeassistant/components/sia/translations/id.json +++ b/homeassistant/components/sia/translations/id.json @@ -4,7 +4,7 @@ "invalid_account_format": "Format akun ini tidak dalam nilai heksadesimal, gunakan hanya karakter 0-9 dan A-F.", "invalid_account_length": "Panjang format akun tidak tepat, harus antara 3 dan 16 karakter.", "invalid_key_format": "Format kunci ini tidak dalam nilai heksadesimal, gunakan hanya karakter 0-9 dan A-F.", - "invalid_key_length": "Panjang format kunci tidak tepat, harus antara 16, 25, atau 32 karakter heksadesimal.", + "invalid_key_length": "Panjang format kunci tidak tepat, harus antara 16, 24, atau 32 karakter heksadesimal.", "invalid_ping": "Interval ping harus antara 1 dan 1440 menit.", "invalid_zones": "Setidaknya harus ada 1 zona.", "unknown": "Kesalahan yang tidak diharapkan" diff --git a/homeassistant/components/smhi/translations/pl.json b/homeassistant/components/smhi/translations/pl.json index f1b30177d5a2c1..18e9156a936bec 100644 --- a/homeassistant/components/smhi/translations/pl.json +++ b/homeassistant/components/smhi/translations/pl.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Konto jest ju\u017c skonfigurowane" + }, "error": { "name_exists": "Nazwa ju\u017c istnieje", "wrong_location": "Lokalizacja w Szwecji" diff --git a/homeassistant/components/solax/translations/pl.json b/homeassistant/components/solax/translations/pl.json new file mode 100644 index 00000000000000..e92874775ada20 --- /dev/null +++ b/homeassistant/components/solax/translations/pl.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "ip_address": "Adres IP", + "password": "Has\u0142o", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steamist/translations/id.json b/homeassistant/components/steamist/translations/id.json index 3ade230c467d95..46fc072d9fe33e 100644 --- a/homeassistant/components/steamist/translations/id.json +++ b/homeassistant/components/steamist/translations/id.json @@ -4,7 +4,8 @@ "already_configured": "Perangkat sudah dikonfigurasi", "already_in_progress": "Alur konfigurasi sedang berlangsung", "cannot_connect": "Gagal terhubung", - "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan" + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "not_steamist_device": "Bukan perangkat steamist" }, "error": { "cannot_connect": "Gagal terhubung", @@ -12,6 +13,9 @@ }, "flow_title": "{name} ({ipaddress})", "step": { + "discovery_confirm": { + "description": "Ingin menyiapkan {name} ({ipaddress})?" + }, "pick_device": { "data": { "device": "Perangkat" @@ -20,7 +24,8 @@ "user": { "data": { "host": "Host" - } + }, + "description": "Jika host dibiarkan kosong, proses penemuan akan digunakan untuk menemukan perangkat." } } } diff --git a/homeassistant/components/switch/translations/pl.json b/homeassistant/components/switch/translations/pl.json index 4fb5542f510d6f..9132a6c6b9eb02 100644 --- a/homeassistant/components/switch/translations/pl.json +++ b/homeassistant/components/switch/translations/pl.json @@ -10,6 +10,7 @@ "is_on": "prze\u0142\u0105cznik {entity_name} jest w\u0142\u0105czony" }, "trigger_type": { + "changed_states": "{entity_name} zostanie w\u0142\u0105czony lub wy\u0142\u0105czony", "toggled": "{entity_name} zostanie w\u0142\u0105czony lub wy\u0142\u0105czony", "turned_off": "nast\u0105pi wy\u0142\u0105czenie {entity_name}", "turned_on": "nast\u0105pi w\u0142\u0105czenie {entity_name}" diff --git a/homeassistant/components/synology_dsm/translations/id.json b/homeassistant/components/synology_dsm/translations/id.json index 8169a6be4bf6e9..7f0242fedd03b0 100644 --- a/homeassistant/components/synology_dsm/translations/id.json +++ b/homeassistant/components/synology_dsm/translations/id.json @@ -64,6 +64,7 @@ "init": { "data": { "scan_interval": "Interval pemindaian dalam menit", + "snap_profile_type": "Tingkat kualitas snapshot kamera (0:tinggi, 1:sedang, 2:rendah)", "timeout": "Tenggang waktu (detik)" } } diff --git a/homeassistant/components/synology_dsm/translations/pl.json b/homeassistant/components/synology_dsm/translations/pl.json index 06a21cfdf18f15..9e285f65f843bf 100644 --- a/homeassistant/components/synology_dsm/translations/pl.json +++ b/homeassistant/components/synology_dsm/translations/pl.json @@ -64,6 +64,7 @@ "init": { "data": { "scan_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji [min]", + "snap_profile_type": "Poziom jako\u015bci zdj\u0119\u0107 z kamery (0:wysoka 1:\u015brednia 2:niska)", "timeout": "Limit czasu (sekundy)" } } diff --git a/homeassistant/components/traccar/translations/pl.json b/homeassistant/components/traccar/translations/pl.json index 619f6e571928bb..0c0cb4249d1d22 100644 --- a/homeassistant/components/traccar/translations/pl.json +++ b/homeassistant/components/traccar/translations/pl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Brak po\u0142\u0105czenia z chmur\u0105 Home Assistant.", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja.", "webhook_not_internet_accessible": "Tw\u00f3j Home Assistant musi by\u0107 dost\u0119pny z Internetu, aby odbiera\u0107 komunikaty webhook" }, diff --git a/homeassistant/components/tuya/translations/select.bg.json b/homeassistant/components/tuya/translations/select.bg.json index ce690131579b2e..28b629678947a1 100644 --- a/homeassistant/components/tuya/translations/select.bg.json +++ b/homeassistant/components/tuya/translations/select.bg.json @@ -9,6 +9,14 @@ "1": "\u0418\u0437\u043a\u043b\u044e\u0447\u0435\u043d\u043e", "2": "\u0412\u043a\u043b\u044e\u0447\u0435\u043d\u043e" }, + "tuya__countdown": { + "1h": "1 \u0447\u0430\u0441", + "2h": "2 \u0447\u0430\u0441\u0430", + "3h": "3 \u0447\u0430\u0441\u0430", + "4h": "4 \u0447\u0430\u0441\u0430", + "5h": "5 \u0447\u0430\u0441\u0430", + "6h": "6 \u0447\u0430\u0441\u0430" + }, "tuya__curtain_mode": { "morning": "\u0421\u0443\u0442\u0440\u0438\u043d", "night": "\u041d\u043e\u0449" @@ -26,6 +34,9 @@ "60": "60\u00b0", "90": "90\u00b0" }, + "tuya__humidifier_spray_mode": { + "auto": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e" + }, "tuya__led_type": { "halogen": "\u0425\u0430\u043b\u043e\u0433\u0435\u043d\u043d\u0438", "incandescent": "\u0421 \u043d\u0430\u0436\u0435\u0436\u0430\u0435\u043c\u0430 \u0436\u0438\u0447\u043a\u0430", diff --git a/homeassistant/components/tuya/translations/select.id.json b/homeassistant/components/tuya/translations/select.id.json index 4ea665e4a79c13..9b404daf612895 100644 --- a/homeassistant/components/tuya/translations/select.id.json +++ b/homeassistant/components/tuya/translations/select.id.json @@ -10,14 +10,62 @@ "1": "Mati", "2": "Nyala" }, + "tuya__countdown": { + "1h": "1 jam", + "2h": "2 jam", + "3h": "3 jam", + "4h": "4 jam", + "5h": "5 jam", + "6h": "6 jam", + "cancel": "Batalkan" + }, + "tuya__curtain_mode": { + "morning": "Pagi", + "night": "Malam" + }, + "tuya__curtain_motor_mode": { + "back": "Mundur", + "forward": "Maju" + }, "tuya__decibel_sensitivity": { "0": "Sensitivitas rendah", "1": "Sensitivitas tinggi" }, + "tuya__fan_angle": { + "30": "30\u00b0", + "60": "60\u00b0", + "90": "90\u00b0" + }, "tuya__fingerbot_mode": { "click": "Dorong", "switch": "Sakelar" }, + "tuya__humidifier_level": { + "level_1": "Tingkat 1", + "level_10": "Tingkat 10", + "level_2": "Tingkat 2", + "level_3": "Tingkat 3", + "level_4": "Tingkat 4", + "level_5": "Tingkat 5", + "level_6": "Tingkat 6", + "level_7": "Tingkat 7", + "level_8": "Tingkat 8", + "level_9": "Tingkat 9" + }, + "tuya__humidifier_moodlighting": { + "1": "Suasana 1", + "2": "Suasana 2", + "3": "Suasana 3", + "4": "Suasana 4", + "5": "Suasana 5" + }, + "tuya__humidifier_spray_mode": { + "auto": "Otomatis", + "health": "Kesehatan", + "humidity": "Kelembaban", + "sleep": "Tidur", + "work": "Bekerja" + }, "tuya__ipc_work_mode": { "0": "Mode daya rendah", "1": "Mode kerja terus menerus" @@ -48,6 +96,33 @@ "on": "Nyala", "power_off": "Mati", "power_on": "Nyala" + }, + "tuya__vacuum_cistern": { + "closed": "Tutup", + "high": "Tinggi", + "low": "Rendah", + "middle": "Tengah" + }, + "tuya__vacuum_collection": { + "large": "Besar", + "middle": "Tengah", + "small": "Kecil" + }, + "tuya__vacuum_mode": { + "chargego": "Kembali ke dock", + "left_spiral": "Spiral Kiri", + "mop": "Pel", + "part": "Bagian", + "pick_zone": "Pilih Zona", + "point": "Titik", + "random": "Acak", + "right_spiral": "Spiral Kanan", + "single": "Tunggal", + "smart": "Cerdas", + "spiral": "Spiral", + "standby": "Siaga", + "wall_follow": "Ikuti Dinding", + "zone": "Zona" } } } \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/select.pl.json b/homeassistant/components/tuya/translations/select.pl.json index be98268cf540df..d75bbd8a8be759 100644 --- a/homeassistant/components/tuya/translations/select.pl.json +++ b/homeassistant/components/tuya/translations/select.pl.json @@ -10,16 +10,61 @@ "1": "wy\u0142.", "2": "w\u0142." }, + "tuya__countdown": { + "1h": "1 godzina", + "2h": "2 godziny", + "3h": "3 godziny", + "4h": "4 godziny", + "5h": "5 godzin", + "6h": "6 godzin", + "cancel": "Anuluj" + }, + "tuya__curtain_mode": { + "morning": "Ranek", + "night": "Noc" + }, + "tuya__curtain_motor_mode": { + "back": "Do ty\u0142u", + "forward": "Do przodu" + }, "tuya__decibel_sensitivity": { "0": "Niska czu\u0142o\u015b\u0107", "1": "Wysoka czu\u0142o\u015b\u0107" }, + "tuya__fan_angle": { + "30": "30\u00b0", + "60": "60\u00b0", + "90": "90\u00b0" + }, "tuya__fingerbot_mode": { "click": "Naci\u015bni\u0119cie", "switch": "Prze\u0142\u0105cznik" }, + "tuya__humidifier_level": { + "level_1": "Poziom 1", + "level_10": "Poziom 10", + "level_2": "Poziom 2", + "level_3": "Poziom 3", + "level_4": "Poziom 4", + "level_5": "Poziom 5", + "level_6": "Poziom 6", + "level_7": "Poziom 7", + "level_8": "Poziom 8", + "level_9": "Poziom 9" + }, + "tuya__humidifier_moodlighting": { + "1": "Nastr\u00f3j 1", + "2": "Nastr\u00f3j 2", + "3": "Nastr\u00f3j 3", + "4": "Nastr\u00f3j 4", + "5": "Nastr\u00f3j 5" + }, "tuya__humidifier_spray_mode": { - "humidity": "Wilgotno\u015b\u0107" + "auto": "Auto", + "health": "Zdrowotny", + "humidity": "Wilgotno\u015b\u0107", + "sleep": "U\u015bpiony", + "work": "Praca" }, "tuya__ipc_work_mode": { "0": "Tryb niskiego poboru mocy", @@ -54,14 +99,14 @@ }, "tuya__vacuum_cistern": { "closed": "zamkni\u0119ta", - "high": "wysoki", - "low": "niski", - "middle": "w po\u0142owie" + "high": "Du\u017ce", + "low": "Ma\u0142e", + "middle": "\u015arednie" }, "tuya__vacuum_collection": { - "large": "du\u017cy", - "middle": "\u015bredni", - "small": "ma\u0142y" + "large": "Du\u017ce", + "middle": "\u015arednie", + "small": "Ma\u0142e" }, "tuya__vacuum_mode": { "bow": "\u0141uk", @@ -69,10 +114,11 @@ "left_bow": "\u0141uk w lewo", "left_spiral": "Spirala w lewo", "mop": "Mop", - "part": "Cz\u0119\u015b\u0107", + "part": "Cz\u0119\u015bciowe", "partial_bow": "Cz\u0119\u015bciowy \u0142uk", "pick_zone": "Wybierz stref\u0119", "point": "Punkt", + "pose": "Pozycja", "random": "Losowo", "right_bow": "\u0141uk w prawo", "right_spiral": "Spirala w prawo", diff --git a/homeassistant/components/tuya/translations/sensor.id.json b/homeassistant/components/tuya/translations/sensor.id.json index 0697075be00d95..c841d5faf3bd2a 100644 --- a/homeassistant/components/tuya/translations/sensor.id.json +++ b/homeassistant/components/tuya/translations/sensor.id.json @@ -1,5 +1,11 @@ { "state": { + "tuya__air_quality": { + "good": "Bagus", + "great": "Hebat", + "mild": "Ringan", + "severe": "Parah" + }, "tuya__status": { "boiling_temp": "Suhu mendidih", "cooling": "Mendinginkan", diff --git a/homeassistant/components/tuya/translations/sensor.ja.json b/homeassistant/components/tuya/translations/sensor.ja.json index aecb556cf173a8..317a276315216c 100644 --- a/homeassistant/components/tuya/translations/sensor.ja.json +++ b/homeassistant/components/tuya/translations/sensor.ja.json @@ -1,5 +1,11 @@ { "state": { + "tuya__air_quality": { + "good": "\u30b0\u30c3\u30c9", + "great": "\u30b0\u30ec\u30fc\u30c8", + "mild": "\u30de\u30a4\u30eb\u30c9", + "severe": "\u30b7\u30d3\u30a2" + }, "tuya__status": { "boiling_temp": "\u6cb8\u70b9", "cooling": "\u51b7\u5374", diff --git a/homeassistant/components/tuya/translations/sensor.pl.json b/homeassistant/components/tuya/translations/sensor.pl.json index 090849227f88d4..0529ebebba0dfd 100644 --- a/homeassistant/components/tuya/translations/sensor.pl.json +++ b/homeassistant/components/tuya/translations/sensor.pl.json @@ -1,5 +1,11 @@ { "state": { + "tuya__air_quality": { + "good": "Dobra", + "great": "\u015awietna", + "mild": "Umiarkowana", + "severe": "Z\u0142a" + }, "tuya__status": { "boiling_temp": "temperatura wrzenia", "cooling": "ch\u0142odzenie", diff --git a/homeassistant/components/twilio/translations/pl.json b/homeassistant/components/twilio/translations/pl.json index e6be0a02aedebe..e0ece3f30421f9 100644 --- a/homeassistant/components/twilio/translations/pl.json +++ b/homeassistant/components/twilio/translations/pl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Brak po\u0142\u0105czenia z chmur\u0105 Home Assistant.", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja.", "webhook_not_internet_accessible": "Tw\u00f3j Home Assistant musi by\u0107 dost\u0119pny z Internetu, aby odbiera\u0107 komunikaty webhook" }, diff --git a/homeassistant/components/twinkly/translations/id.json b/homeassistant/components/twinkly/translations/id.json index 7cebe87febbc2c..330bcf6ce11c2c 100644 --- a/homeassistant/components/twinkly/translations/id.json +++ b/homeassistant/components/twinkly/translations/id.json @@ -12,7 +12,7 @@ }, "user": { "data": { - "host": "Host (atau alamat IP) perangkat twinkly Anda" + "host": "Host" }, "description": "Siapkan string led Twinkly Anda", "title": "Twinkly" diff --git a/homeassistant/components/unifiprotect/translations/id.json b/homeassistant/components/unifiprotect/translations/id.json index 3612367090e6a5..b2f8e2541fe586 100644 --- a/homeassistant/components/unifiprotect/translations/id.json +++ b/homeassistant/components/unifiprotect/translations/id.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Perangkat sudah dikonfigurasi" + "already_configured": "Perangkat sudah dikonfigurasi", + "discovery_started": "Proses penemuan dimulai" }, "error": { "cannot_connect": "Gagal terhubung", @@ -16,10 +17,13 @@ "password": "Kata Sandi", "username": "Nama Pengguna", "verify_ssl": "Verifikasi sertifikat SSL" - } + }, + "description": "Ingin menyiapkan {name} ({ip_address})? Anda akan memerlukan pengguna lokal yang dibuat di Konsol OS UniFi Anda untuk masuk. Pengguna Ubiquiti Cloud tidak akan berfungsi. Untuk informasi lebih lanjut: {local_user_documentation_url}", + "title": "UniFi Protect Ditemukan" }, "reauth_confirm": { "data": { + "host": "IP/Host dari Server UniFi Protect", "password": "Kata Sandi", "port": "Port", "username": "Nama Pengguna" @@ -34,6 +38,7 @@ "username": "Nama Pengguna", "verify_ssl": "Verifikasi sertifikat SSL" }, + "description": "Anda akan memerlukan pengguna lokal yang dibuat di Konsol OS UniFi Anda untuk masuk. Pengguna Ubiquiti Cloud tidak akan berfungsi. Untuk informasi lebih lanjut: {local_user_documentation_url}", "title": "Penyiapan UniFi Protect" } } diff --git a/homeassistant/components/unifiprotect/translations/pl.json b/homeassistant/components/unifiprotect/translations/pl.json index 0ea84e9d658f3d..724609199899cc 100644 --- a/homeassistant/components/unifiprotect/translations/pl.json +++ b/homeassistant/components/unifiprotect/translations/pl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "discovery_started": "Wykrywanie rozpocz\u0119te" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", @@ -9,9 +10,16 @@ "protect_version": "Minimalna wymagana wersja to v1.20.0. Zaktualizuj UniFi Protect, a nast\u0119pnie spr\u00f3buj ponownie.", "unknown": "Nieoczekiwany b\u0142\u0105d" }, + "flow_title": "{name} ({ip_address})", "step": { "discovery_confirm": { - "title": "Odkryto UniFi Protect" + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika", + "verify_ssl": "Weryfikacja certyfikatu SSL" + }, + "description": "Czy chcesz skonfigurowa\u0107 {name} ({ip_address})? Aby si\u0119 zalogowa\u0107, b\u0119dziesz potrzebowa\u0107 lokalnego u\u017cytkownika utworzonego w konsoli UniFi OS. U\u017cytkownicy Ubiquiti Cloud nie b\u0119d\u0105 dzia\u0142a\u0107. Wi\u0119cej informacji: {local_user_documentation_url}", + "title": "Wykryto UniFi Protect" }, "reauth_confirm": { "data": { diff --git a/homeassistant/components/uptimerobot/translations/sensor.id.json b/homeassistant/components/uptimerobot/translations/sensor.id.json new file mode 100644 index 00000000000000..9d66fde17f3e5f --- /dev/null +++ b/homeassistant/components/uptimerobot/translations/sensor.id.json @@ -0,0 +1,11 @@ +{ + "state": { + "uptimerobot__monitor_status": { + "down": "Mati", + "not_checked_yet": "Belum diperiksa", + "pause": "Jeda", + "seems_down": "Sepertinya mati", + "up": "Nyala" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/uptimerobot/translations/sensor.pl.json b/homeassistant/components/uptimerobot/translations/sensor.pl.json new file mode 100644 index 00000000000000..d1002376d8272b --- /dev/null +++ b/homeassistant/components/uptimerobot/translations/sensor.pl.json @@ -0,0 +1,11 @@ +{ + "state": { + "uptimerobot__monitor_status": { + "down": "offline", + "not_checked_yet": "jeszcze nie sprawdzone", + "pause": "wstrzymano", + "seems_down": "wydaje si\u0119 offline", + "up": "online" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/webostv/translations/pl.json b/homeassistant/components/webostv/translations/pl.json index 6f974191dc744e..9792994653678a 100644 --- a/homeassistant/components/webostv/translations/pl.json +++ b/homeassistant/components/webostv/translations/pl.json @@ -1,25 +1,32 @@ { "config": { "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "already_in_progress": "Konfiguracja jest ju\u017c w toku", "error_pairing": "Po\u0142\u0105czono z telewizorem LG webOS, ale nie sparowano" }, "error": { - "cannot_connect": "Nie uda\u0142o si\u0119 po\u0142\u0105czy\u0107, w\u0142\u0105cz telewizor lub sprawd\u017a adres IP" + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, w\u0142\u0105cz telewizor lub sprawd\u017a adres IP" }, - "flow_title": "Smart telewizor LG webOS", + "flow_title": "LG webOS Smart TV", "step": { "pairing": { + "description": "Kliknij \"Zatwierd\u017a\" i zaakceptuj \u017c\u0105danie parowania na swoim telewizorze. \n\n![Obraz](/static/images/config_webos.png)", "title": "Parowanie webOS TV" }, "user": { - "description": "W\u0142\u0105cz telewizor, wype\u0142nij wymgane pola i kliknij prze\u015blij", - "title": "Po\u0142\u0105cz z webOS TV" + "data": { + "host": "Nazwa hosta lub adres IP", + "name": "Nazwa" + }, + "description": "W\u0142\u0105cz telewizor, wype\u0142nij wymagane pola i kliknij \"Zatwierd\u017a\"", + "title": "Po\u0142\u0105czenie z webOS TV" } } }, "device_automation": { "trigger_type": { - "webostv.turn_on": "Urz\u0105dzenie jest poproszone o w\u0142\u0105czenie si\u0119" + "webostv.turn_on": "urz\u0105dzenie zostanie poproszone o w\u0142\u0105czenie" } }, "options": { diff --git a/homeassistant/components/whois/translations/pl.json b/homeassistant/components/whois/translations/pl.json index 621be0d70cc8e7..f0624f627bbdc4 100644 --- a/homeassistant/components/whois/translations/pl.json +++ b/homeassistant/components/whois/translations/pl.json @@ -4,9 +4,10 @@ "already_configured": "Us\u0142uga jest ju\u017c skonfigurowana" }, "error": { + "unexpected_response": "Nieoczekiwana odpowied\u017a z serwera whois", "unknown_date_format": "Nieznany format daty w odpowiedzi serwera whois", "unknown_tld": "Podana domena TLD jest nieznana lub niedost\u0119pna dla tej integracji", - "whois_command_failed": "Komenda Whois nie powiod\u0142a si\u0119: nie mo\u017cna pobra\u0107 informacji whois" + "whois_command_failed": "Komenda Whois nie powiod\u0142a si\u0119: nie mo\u017cna pobra\u0107 informacji z whois" }, "step": { "user": { diff --git a/homeassistant/components/wiz/translations/bg.json b/homeassistant/components/wiz/translations/bg.json new file mode 100644 index 00000000000000..54a7c553a532d0 --- /dev/null +++ b/homeassistant/components/wiz/translations/bg.json @@ -0,0 +1,13 @@ +{ + "config": { + "flow_title": "{name} ({host})", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u0418\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/id.json b/homeassistant/components/wiz/translations/id.json new file mode 100644 index 00000000000000..ffd1a983989da9 --- /dev/null +++ b/homeassistant/components/wiz/translations/id.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan" + }, + "error": { + "bulb_time_out": "Tidak dapat terhubung ke bohlam. Mungkin bohlam offline atau IP/host yang salah dimasukkan. Nyalakan lampu dan coba lagi!", + "cannot_connect": "Gagal terhubung", + "no_wiz_light": "Bohlam tidak dapat dihubungkan melalui integrasi Platform WiZ.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "{name} ({host})", + "step": { + "confirm": { + "description": "Ingin memulai penyiapan?" + }, + "discovery_confirm": { + "description": "Ingin menyiapkan {name} ({host})?" + }, + "pick_device": { + "data": { + "device": "Perangkat" + } + }, + "user": { + "data": { + "host": "Host", + "name": "Nama" + }, + "description": "Jika host dibiarkan kosong, proses penemuan akan digunakan untuk menemukan perangkat." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/pl.json b/homeassistant/components/wiz/translations/pl.json index 6ec2488368be97..7a3523ede16240 100644 --- a/homeassistant/components/wiz/translations/pl.json +++ b/homeassistant/components/wiz/translations/pl.json @@ -1,10 +1,34 @@ { "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "no_devices_found": "Nie znaleziono urz\u0105dze\u0144 w sieci" + }, + "error": { + "bulb_time_out": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z \u017car\u00f3wk\u0105. Mo\u017ce \u017car\u00f3wka jest w trybie offline lub wprowadzono z\u0142y adres IP/host. W\u0142\u0105cz \u015bwiat\u0142o i spr\u00f3buj ponownie!", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "no_wiz_light": "\u017bar\u00f3wka nie mo\u017ce by\u0107 pod\u0142\u0105czona poprzez integracj\u0119 z platform\u0105 WiZ.", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "flow_title": "{name} ({host})", "step": { + "confirm": { + "description": "Czy chcesz rozpocz\u0105\u0107 konfiguracj\u0119?" + }, + "discovery_confirm": { + "description": "Czy chcesz skonfigurowa\u0107 {name} ({host})?" + }, "pick_device": { "data": { "device": "Urz\u0105dzenie" } + }, + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "name": "Nazwa" + }, + "description": "Je\u015bli nie podasz IP lub nazwy hosta, zostanie u\u017cyte wykrywanie do odnalezienia urz\u0105dze\u0144." } } } diff --git a/homeassistant/components/wiz/translations/ru.json b/homeassistant/components/wiz/translations/ru.json index 47c1d650fa50cd..16f679e9c7d4e9 100644 --- a/homeassistant/components/wiz/translations/ru.json +++ b/homeassistant/components/wiz/translations/ru.json @@ -18,6 +18,11 @@ "discovery_confirm": { "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name} ({host})?" }, + "pick_device": { + "data": { + "device": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" + } + }, "user": { "data": { "host": "\u0425\u043e\u0441\u0442", diff --git a/homeassistant/components/wolflink/translations/sensor.id.json b/homeassistant/components/wolflink/translations/sensor.id.json index 9e7932b2dd74f4..3ceb09bf90a9c6 100644 --- a/homeassistant/components/wolflink/translations/sensor.id.json +++ b/homeassistant/components/wolflink/translations/sensor.id.json @@ -57,6 +57,11 @@ "test": "Pengujian", "urlaubsmodus": "Mode liburan", "ventilprufung": "Uji katup", + "warmwasser": "Air Panas Domestik", + "warmwasser_schnellstart": "Mulai cepat Air Panas Domestik", + "warmwasserbetrieb": "Mode Air Panas Domestik", + "warmwassernachlauf": "Air Panas Domestik sisa", + "warmwasservorrang": "Prioritas Air Panas Domestik", "zunden": "Pengapian" } } diff --git a/homeassistant/components/yale_smart_alarm/translations/pl.json b/homeassistant/components/yale_smart_alarm/translations/pl.json index 31897a0d3c9866..de49d07361aa01 100644 --- a/homeassistant/components/yale_smart_alarm/translations/pl.json +++ b/homeassistant/components/yale_smart_alarm/translations/pl.json @@ -5,7 +5,7 @@ "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" }, "error": { - "cannot_connect": "Nie uda\u0142o si\u0119 po\u0142\u0105czy\u0107", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "invalid_auth": "Niepoprawne uwierzytelnienie" }, "step": { diff --git a/homeassistant/components/zwave_me/translations/bg.json b/homeassistant/components/zwave_me/translations/bg.json new file mode 100644 index 00000000000000..02c83a6e9167b4 --- /dev/null +++ b/homeassistant/components/zwave_me/translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave_me/translations/ca.json b/homeassistant/components/zwave_me/translations/ca.json index ba91812d06fb8e..a382cef549479e 100644 --- a/homeassistant/components/zwave_me/translations/ca.json +++ b/homeassistant/components/zwave_me/translations/ca.json @@ -13,7 +13,7 @@ "token": "Token", "url": "URL" }, - "description": "Introdueix l'adre\u00e7a IP del servidor Z-Way i el 'token' d'acc\u00e9s Z-Way. Si s'utilitza HTTPS en lloc d'HTTP, l'adre\u00e7a IP es pot prefixar amb wss://. Per obtenir el 'token', ves a la interf\u00edcie d'usuari de Z-Way > Men\u00fa > Configuraci\u00f3 > Usuari > Token API. Es recomana crear un nou usuaride Home Assistant i concedir-li acc\u00e9s als dispositius que necessitis controlar des de Home Assistant. Tamb\u00e9 \u00e9s possible utilitzar l'acc\u00e9s remot a trav\u00e9s de find.z-wave.me per connectar amb un Z-Way remot. Introdueix wss://find.z-wave.me al camp d'IP i copia el 'token' d'\u00e0mbit global (inicia sessi\u00f3 a Z-Way mitjan\u00e7ant find.z-wave.me)." + "description": "Introdueix l'adre\u00e7a IP del servidor Z-Way i el 'token' d'acc\u00e9s Z-Way. Si s'utilitza HTTPS en lloc d'HTTP, l'adre\u00e7a IP es pot prefixar amb wss://. Per obtenir el 'token', v\u00e9s a la interf\u00edcie d'usuari de Z-Way > Men\u00fa > Configuraci\u00f3 > Usuari > Token API. Es recomana crear un nou usuari de Home Assistant i concedir-li acc\u00e9s als dispositius que necessitis controlar des de Home Assistant. Tamb\u00e9 \u00e9s possible utilitzar l'acc\u00e9s remot a trav\u00e9s de find.z-wave.me per connectar amb un Z-Way remot. Introdueix wss://find.z-wave.me al camp d'IP i copia el 'token' d'\u00e0mbit global (inicia sessi\u00f3 a Z-Way mitjan\u00e7ant find.z-wave.me)." } } } diff --git a/homeassistant/components/zwave_me/translations/de.json b/homeassistant/components/zwave_me/translations/de.json new file mode 100644 index 00000000000000..5afd788a3fbae4 --- /dev/null +++ b/homeassistant/components/zwave_me/translations/de.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits eingerichtet", + "no_valid_uuid_set": "Keine g\u00fcltige UUID gesetzt" + }, + "error": { + "no_valid_uuid_set": "Keine g\u00fcltige UUID gesetzt" + }, + "step": { + "user": { + "data": { + "token": "Token", + "url": "URL" + }, + "description": "Gib die IP-Adresse des Z-Way-Servers und das Z-Way-Zugangs-Token ein. Der IP-Adresse kann wss:// vorangestellt werden, wenn HTTPS anstelle von HTTP verwendet werden soll. Um das Token zu erhalten, gehe auf Z-Way-Benutzeroberfl\u00e4che > Men\u00fc > Einstellungen > Benutzer > API-Token. Es wird empfohlen, einen neuen Benutzer f\u00fcr Home Assistant zu erstellen und den Ger\u00e4ten, die du \u00fcber den Home Assistant steuern m\u00f6chtest, Zugriff zu gew\u00e4hren. Es ist auch m\u00f6glich, den Fernzugriff \u00fcber find.z-wave.me zu nutzen, um ein entferntes Z-Way zu verbinden. Gib wss://find.z-wave.me in das IP-Feld ein und kopiere das Token mit globalem Geltungsbereich (logge dich dazu \u00fcber find.z-wave.me bei Z-Way ein)." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave_me/translations/id.json b/homeassistant/components/zwave_me/translations/id.json new file mode 100644 index 00000000000000..da9ddeb6d67487 --- /dev/null +++ b/homeassistant/components/zwave_me/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "no_valid_uuid_set": "Tidak ada UUID yang valid ditetapkan" + }, + "error": { + "no_valid_uuid_set": "Tidak ada UUID yang valid ditetapkan" + }, + "step": { + "user": { + "data": { + "token": "Token", + "url": "URL" + }, + "description": "Masukkan alamat IP server Z-Way dan token akses Z-Way. Alamat IP dapat diawali dengan wss:// jika HTTPS harus digunakan sebagai pengganti HTTP. Untuk mendapatkan token, buka antarmuka pengguna Z-Way > Menu > Pengaturan > Token API > Pengguna. Disarankan untuk membuat pengguna baru untuk Home Assistant dan memberikan akses ke perangkat yang perlu Anda kontrol dari Home Assistant. Dimungkinkan juga untuk menggunakan akses jarak jauh melalui find.z-wave.me untuk menghubungkan Z-Way jarak jauh. Masukkan wss://find.z-wave.me di bidang IP dan salin token dengan cakupan Global (masuk ke Z-Way melalui find.z-wave.me untuk ini)." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave_me/translations/ja.json b/homeassistant/components/zwave_me/translations/ja.json new file mode 100644 index 00000000000000..f46f1f6c452deb --- /dev/null +++ b/homeassistant/components/zwave_me/translations/ja.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", + "no_valid_uuid_set": "\u6709\u52b9\u306aUUID\u30bb\u30c3\u30c8\u304c\u3042\u308a\u307e\u305b\u3093" + }, + "error": { + "no_valid_uuid_set": "\u6709\u52b9\u306aUUID\u30bb\u30c3\u30c8\u304c\u3042\u308a\u307e\u305b\u3093" + }, + "step": { + "user": { + "data": { + "token": "\u30c8\u30fc\u30af\u30f3", + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave_me/translations/pl.json b/homeassistant/components/zwave_me/translations/pl.json new file mode 100644 index 00000000000000..d75251367cd525 --- /dev/null +++ b/homeassistant/components/zwave_me/translations/pl.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "no_valid_uuid_set": "Nie ustawiono prawid\u0142owego UUID" + }, + "error": { + "no_valid_uuid_set": "Nie ustawiono prawid\u0142owego UUID" + }, + "step": { + "user": { + "data": { + "token": "Token", + "url": "URL" + }, + "description": "Wprowad\u017a adres IP serwera Z-Way i token dost\u0119pu Z-Way. Adres IP mo\u017ce by\u0107 poprzedzony wss://, je\u015bli zamiast HTTP powinien by\u0107 u\u017cywany HTTPS. Aby uzyska\u0107 token, przejd\u017a do interfejsu u\u017cytkownika Z-Way > Menu > Ustawienia > U\u017cytkownik > Token API. Sugeruje si\u0119 utworzenie nowego u\u017cytkownika Home Assistant i przyznanie dost\u0119pu do urz\u0105dze\u0144, kt\u00f3rymi chcesz sterowa\u0107 z Home Assistant. Mo\u017cliwe jest r\u00f3wnie\u017c u\u017cycie zdalnego dost\u0119pu za po\u015brednictwem find.z-wave.me, aby po\u0142\u0105czy\u0107 si\u0119 ze zdalnym Z-Way. Wpisz wss://find.z-wave.me w polu IP i skopiuj token z zakresem globalnym (w tym celu zaloguj si\u0119 do Z-Way przez find.z-wave.me)." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave_me/translations/ru.json b/homeassistant/components/zwave_me/translations/ru.json new file mode 100644 index 00000000000000..d24b2f7327a9d8 --- /dev/null +++ b/homeassistant/components/zwave_me/translations/ru.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.", + "no_valid_uuid_set": "\u041d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0439 UUID." + }, + "error": { + "no_valid_uuid_set": "\u041d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0439 UUID." + }, + "step": { + "user": { + "data": { + "token": "\u0422\u043e\u043a\u0435\u043d", + "url": "URL-\u0430\u0434\u0440\u0435\u0441" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 IP-\u0430\u0434\u0440\u0435\u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 Z-Way \u0438 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430 Z-Way. \u0415\u0441\u043b\u0438 \u0432\u043c\u0435\u0441\u0442\u043e HTTP \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c HTTPS, IP-\u0430\u0434\u0440\u0435\u0441 \u043d\u0443\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0441 \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u043c 'wss://'. \u0427\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0442\u043e\u043a\u0435\u043d, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 Z-Way > \u041c\u0435\u043d\u044e > \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 > \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c > \u0422\u043e\u043a\u0435\u043d API. \u0417\u0430\u0442\u0435\u043c \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043d\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0434\u043b\u044f Home Assistant \u0438 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u0412\u044b \u0445\u043e\u0442\u0435\u043b\u0438 \u0431\u044b \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0438\u0437 Home Assistant. \u0422\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0439 \u0434\u043e\u0441\u0442\u0443\u043f \u0447\u0435\u0440\u0435\u0437 find.z-wave.me \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e\u0433\u043e Z-Way. \u0412\u0432\u0435\u0434\u0438\u0442\u0435 wss://find.z-wave.me \u0432 \u043f\u043e\u043b\u0435 IP-\u0430\u0434\u0440\u0435\u0441\u0430 \u0438 \u0441\u043a\u043e\u043f\u0438\u0440\u0443\u0439\u0442\u0435 \u0442\u043e\u043a\u0435\u043d \u0441 \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u043e\u0439 \u043e\u0431\u043b\u0430\u0441\u0442\u044c\u044e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f (\u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432\u043e\u0439\u0434\u0438\u0442\u0435 \u0432 Z-Way \u0447\u0435\u0440\u0435\u0437 find.z-wave.me)." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave_me/translations/zh-Hant.json b/homeassistant/components/zwave_me/translations/zh-Hant.json new file mode 100644 index 00000000000000..ee6eb1e9ae7698 --- /dev/null +++ b/homeassistant/components/zwave_me/translations/zh-Hant.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "no_valid_uuid_set": "\u672a\u8a2d\u5b9a\u6709\u6548 UUID" + }, + "error": { + "no_valid_uuid_set": "\u672a\u8a2d\u5b9a\u6709\u6548 UUID" + }, + "step": { + "user": { + "data": { + "token": "\u6b0a\u6756", + "url": "\u7db2\u5740" + }, + "description": "\u8f38\u5165 Z-Way \u4f3a\u670d\u5668 IP \u4f4d\u5740\u8207 Z-Way \u5b58\u53d6\u6b0a\u6756\u3002\u5047\u5982\u4f7f\u7528 HTTPS \u800c\u975e HTTP\u3001IP \u4f4d\u5740\u524d\u7db4\u53ef\u80fd\u70ba wss://\u3002\u6b32\u53d6\u5f97\u6b0a\u6756\u3001\u8acb\u81f3 Z-Way \u4f7f\u7528\u8005\u4ecb\u9762 > \u9078\u55ae > \u8a2d\u5b9a > \u4f7f\u7528\u8005 > API \u6b0a\u6756\u7372\u5f97\u3002\u5efa\u8b70\u91dd\u5c0d Home Assistant \u65b0\u5275\u4f7f\u7528\u8005\u4e26\u7531 Home Assistant \u63a7\u5236\u53ef\u4ee5\u5b58\u53d6\u7684\u88dd\u7f6e\u3002\u53e6\u5916\u4e5f\u53ef\u4ee5\u900f\u904e find.z-wave.me \u9023\u7dda\u81f3\u9060\u7aef Z-Way \u4e26\u7372\u5f97\u9060\u7aef\u5b58\u53d6\u529f\u80fd\u3002\u65bc IP \u6b04\u4f4d\u8f38\u5165 wss://find.z-wave.me \u4e26\u7531 Global scope\uff08\u900f\u904e find.z-wave.me \u767b\u5165 Z-Way\uff09\u8907\u88fd\u6b0a\u6756\u3002" + } + } + } +} \ No newline at end of file From f2843283bfbb4e05175e46b6329e55da9e45ffc9 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 8 Feb 2022 16:55:52 -0800 Subject: [PATCH 0455/1098] Roku to sign all HASS urls (#66122) --- homeassistant/components/roku/media_player.py | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index d6ad5c9cd13e80..4011a420ab19fa 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -7,6 +7,7 @@ from urllib.parse import quote import voluptuous as vol +import yarl from homeassistant.components import media_source from homeassistant.components.http.auth import async_sign_path @@ -46,7 +47,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_platform from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.network import get_url +from homeassistant.helpers.network import get_url, is_hass_url from . import roku_exception_handler from .browse_media import async_browse_media @@ -376,16 +377,21 @@ async def async_play_media( media_id = sourced_media.url # Sign and prefix with URL if playing a relative URL - if media_id[0] == "/": - media_id = async_sign_path( - self.hass, - quote(media_id), - dt.timedelta(seconds=media_source.DEFAULT_EXPIRY_TIME), - ) + if media_id[0] == "/" or is_hass_url(self.hass, media_id): + parsed = yarl.URL(media_id) + + if parsed.query: + _LOGGER.debug("Not signing path for content with query param") + else: + media_id = async_sign_path( + self.hass, + quote(media_id), + dt.timedelta(seconds=media_source.DEFAULT_EXPIRY_TIME), + ) # prepend external URL - hass_url = get_url(self.hass) - media_id = f"{hass_url}{media_id}" + if media_id[0] == "/": + media_id = f"{get_url(self.hass)}{media_id}" if media_type not in PLAY_MEDIA_SUPPORTED_TYPES: _LOGGER.error( From e6e49dcb0712291d3b184d9ac56e928c899d4116 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 8 Feb 2022 17:13:14 -0800 Subject: [PATCH 0456/1098] VLC Telnet to sign all HASS URLs (#66123) --- .../components/vlc_telnet/media_player.py | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/vlc_telnet/media_player.py b/homeassistant/components/vlc_telnet/media_player.py index 11930aada99c24..2bca965c3ebefd 100644 --- a/homeassistant/components/vlc_telnet/media_player.py +++ b/homeassistant/components/vlc_telnet/media_player.py @@ -10,6 +10,7 @@ from aiovlc.client import Client from aiovlc.exceptions import AuthError, CommandError, ConnectError from typing_extensions import Concatenate, ParamSpec +import yarl from homeassistant.components import media_source from homeassistant.components.http.auth import async_sign_path @@ -32,10 +33,11 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME, STATE_IDLE, STATE_PAUSED, STATE_PLAYING from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.network import get_url +from homeassistant.helpers.network import get_url, is_hass_url import homeassistant.util.dt as dt_util from .const import DATA_AVAILABLE, DATA_VLC, DEFAULT_NAME, DOMAIN, LOGGER @@ -305,28 +307,30 @@ async def async_play_media( # Handle media_source if media_source.is_media_source_id(media_id): sourced_media = await media_source.async_resolve_media(self.hass, media_id) - media_type = MEDIA_TYPE_MUSIC + media_type = sourced_media.mime_type media_id = sourced_media.url - # Sign and prefix with URL if playing a relative URL - if media_id[0] == "/": - media_id = async_sign_path( - self.hass, - quote(media_id), - timedelta(seconds=media_source.DEFAULT_EXPIRY_TIME), + if media_type != MEDIA_TYPE_MUSIC and not media_type.startswith("audio/"): + raise HomeAssistantError( + f"Invalid media type {media_type}. Only {MEDIA_TYPE_MUSIC} is supported" ) + # Sign and prefix with URL if playing a relative URL + if media_id[0] == "/" or is_hass_url(self.hass, media_id): + parsed = yarl.URL(media_id) + + if parsed.query: + LOGGER.debug("Not signing path for content with query param") + else: + media_id = async_sign_path( + self.hass, + quote(media_id), + timedelta(seconds=media_source.DEFAULT_EXPIRY_TIME), + ) + # prepend external URL - hass_url = get_url(self.hass) - media_id = f"{hass_url}{media_id}" - - if media_type != MEDIA_TYPE_MUSIC: - LOGGER.error( - "Invalid media type %s. Only %s is supported", - media_type, - MEDIA_TYPE_MUSIC, - ) - return + if media_id[0] == "/": + media_id = f"{get_url(self.hass)}{media_id}" await self._vlc.add(media_id) self._state = STATE_PLAYING From 9446b39ae02976a7f4706786f1fe1c19eace82aa Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Wed, 9 Feb 2022 00:12:49 -0800 Subject: [PATCH 0457/1098] Bump google-nest-sdm to 1.7.0 (#66145) --- homeassistant/components/nest/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/nest/manifest.json b/homeassistant/components/nest/manifest.json index 19468fcf686734..d66c56d208f92f 100644 --- a/homeassistant/components/nest/manifest.json +++ b/homeassistant/components/nest/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "dependencies": ["ffmpeg", "http", "media_source"], "documentation": "https://www.home-assistant.io/integrations/nest", - "requirements": ["python-nest==4.2.0", "google-nest-sdm==1.6.0"], + "requirements": ["python-nest==4.2.0", "google-nest-sdm==1.7.0"], "codeowners": ["@allenporter"], "quality_scale": "platinum", "dhcp": [ diff --git a/requirements_all.txt b/requirements_all.txt index 4b0f67c5daf722..960ee057f29510 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -764,7 +764,7 @@ google-cloud-pubsub==2.9.0 google-cloud-texttospeech==0.4.0 # homeassistant.components.nest -google-nest-sdm==1.6.0 +google-nest-sdm==1.7.0 # homeassistant.components.google_travel_time googlemaps==2.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bc4ee48bb024db..04ef6c9ea9f0ba 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -495,7 +495,7 @@ google-api-python-client==1.6.4 google-cloud-pubsub==2.9.0 # homeassistant.components.nest -google-nest-sdm==1.6.0 +google-nest-sdm==1.7.0 # homeassistant.components.google_travel_time googlemaps==2.5.1 From 83b7fac9a422bf845376eac89c3cb65683ce620b Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 9 Feb 2022 01:20:57 -0700 Subject: [PATCH 0458/1098] Bump simplisafe-python to 2022.02.1 (#66140) --- homeassistant/components/simplisafe/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index 4a4ffe6eb0ad4d..deb9577d576746 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,7 +3,7 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==2022.02.0"], + "requirements": ["simplisafe-python==2022.02.1"], "codeowners": ["@bachya"], "iot_class": "cloud_polling", "dhcp": [ diff --git a/requirements_all.txt b/requirements_all.txt index 960ee057f29510..cefeb4aa52868d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2193,7 +2193,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==2022.02.0 +simplisafe-python==2022.02.1 # homeassistant.components.sisyphus sisyphus-control==3.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 04ef6c9ea9f0ba..98fca3a0c88b23 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1346,7 +1346,7 @@ sharkiqpy==0.1.8 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==2022.02.0 +simplisafe-python==2022.02.1 # homeassistant.components.slack slackclient==2.5.0 From da38d9ab8028e0627f57574addc952ffb7f2468c Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 9 Feb 2022 09:43:03 +0100 Subject: [PATCH 0459/1098] Fix MQTT debug info (#66146) --- homeassistant/components/mqtt/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index eba2670aa952ae..b797491b7a9638 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -578,6 +578,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: websocket_api.async_register_command(hass, websocket_subscribe) websocket_api.async_register_command(hass, websocket_remove_device) websocket_api.async_register_command(hass, websocket_mqtt_info) + debug_info.initialize(hass) if conf is None: # If we have a config entry, setup is done by that config entry. @@ -596,8 +597,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ) ) - debug_info.initialize(hass) - return True From bfc3c29c6a8e6012247d1f60d5fce267df1efe6b Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Wed, 9 Feb 2022 09:44:04 +0100 Subject: [PATCH 0460/1098] Change detection of router devices for Fritz (#65965) --- homeassistant/components/fritz/common.py | 13 +++++++++++-- homeassistant/components/fritz/diagnostics.py | 1 + homeassistant/components/fritz/switch.py | 12 ++++-------- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index 79acc54b9b2116..4886bbac621dbf 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -158,7 +158,8 @@ def __init__( self.hass = hass self.host = host self.mesh_role = MeshRoles.NONE - self.device_is_router: bool = True + self.device_conn_type: str | None = None + self.device_is_router: bool = False self.password = password self.port = port self.username = username @@ -217,7 +218,15 @@ def setup(self) -> None: self._current_firmware = info.get("NewSoftwareVersion") self._update_available, self._latest_firmware = self._update_device_info() - self.device_is_router = "WANIPConn1" in self.connection.services + if "Layer3Forwarding1" in self.connection.services: + if connection_type := self.connection.call_action( + "Layer3Forwarding1", "GetDefaultConnectionService" + ).get("NewDefaultConnectionService"): + # Return NewDefaultConnectionService sample: "1.WANPPPConnection.1" + self.device_conn_type = connection_type[2:][:-2] + self.device_is_router = self.connection.call_action( + self.device_conn_type, "GetInfo" + ).get("NewEnable") @callback async def _async_update_data(self) -> None: diff --git a/homeassistant/components/fritz/diagnostics.py b/homeassistant/components/fritz/diagnostics.py index f35eca6b9140fe..fa4ff6a7db8449 100644 --- a/homeassistant/components/fritz/diagnostics.py +++ b/homeassistant/components/fritz/diagnostics.py @@ -25,6 +25,7 @@ async def async_get_config_entry_diagnostics( "current_firmware": avm_wrapper.current_firmware, "latest_firmware": avm_wrapper.latest_firmware, "update_available": avm_wrapper.update_available, + "connection_type": avm_wrapper.device_conn_type, "is_router": avm_wrapper.device_is_router, "mesh_role": avm_wrapper.mesh_role, "last_update success": avm_wrapper.last_update_success, diff --git a/homeassistant/components/fritz/switch.py b/homeassistant/components/fritz/switch.py index 59cb75901a2a57..d168878c5da66f 100644 --- a/homeassistant/components/fritz/switch.py +++ b/homeassistant/components/fritz/switch.py @@ -81,16 +81,12 @@ def port_entities_list( _LOGGER.debug("Setting up %s switches", SWITCH_TYPE_PORTFORWARD) entities_list: list[FritzBoxPortSwitch] = [] - connection_type = avm_wrapper.get_default_connection() - if not connection_type: + if not avm_wrapper.device_conn_type: _LOGGER.debug("The FRITZ!Box has no %s options", SWITCH_TYPE_PORTFORWARD) return [] - # Return NewDefaultConnectionService sample: "1.WANPPPConnection.1" - con_type: str = connection_type["NewDefaultConnectionService"][2:][:-2] - # Query port forwardings and setup a switch for each forward for the current device - resp = avm_wrapper.get_num_port_mapping(con_type) + resp = avm_wrapper.get_num_port_mapping(avm_wrapper.device_conn_type) if not resp: _LOGGER.debug("The FRITZ!Box has no %s options", SWITCH_TYPE_DEFLECTION) return [] @@ -107,7 +103,7 @@ def port_entities_list( for i in range(port_forwards_count): - portmap = avm_wrapper.get_port_mapping(con_type, i) + portmap = avm_wrapper.get_port_mapping(avm_wrapper.device_conn_type, i) if not portmap: _LOGGER.debug("The FRITZ!Box has no %s options", SWITCH_TYPE_DEFLECTION) continue @@ -133,7 +129,7 @@ def port_entities_list( portmap, port_name, i, - con_type, + avm_wrapper.device_conn_type, ) ) From d9e9820e219d5bba84a446f893f2478b06a7948b Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Wed, 9 Feb 2022 09:50:15 +0100 Subject: [PATCH 0461/1098] Move the buttonlights to diagnostic entities (#65423) --- homeassistant/components/velbus/light.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/velbus/light.py b/homeassistant/components/velbus/light.py index f1a9065171688c..e0d1b797bb3f8d 100644 --- a/homeassistant/components/velbus/light.py +++ b/homeassistant/components/velbus/light.py @@ -22,7 +22,7 @@ ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity import Entity, EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import VelbusEntity @@ -96,6 +96,7 @@ class VelbusButtonLight(VelbusEntity, LightEntity): _channel: VelbusButton _attr_entity_registry_enabled_default = False + _attr_entity_category = EntityCategory.DIAGNOSTIC _attr_supported_features = SUPPORT_FLASH def __init__(self, channel: VelbusChannel) -> None: From 95aec44292bc6d3026463dd16ab4e060918ddb0d Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Wed, 9 Feb 2022 10:02:21 +0100 Subject: [PATCH 0462/1098] Fix system is loaded flag during reboot/shutdown of Synology DSM (#66125) --- homeassistant/components/synology_dsm/common.py | 9 ++++++++- homeassistant/components/synology_dsm/service.py | 2 -- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/synology_dsm/common.py b/homeassistant/components/synology_dsm/common.py index 54a0735186fc96..e27c74752512a0 100644 --- a/homeassistant/components/synology_dsm/common.py +++ b/homeassistant/components/synology_dsm/common.py @@ -33,7 +33,7 @@ ) from homeassistant.core import HomeAssistant, callback -from .const import CONF_DEVICE_TOKEN +from .const import CONF_DEVICE_TOKEN, DOMAIN, SYSTEM_LOADED LOGGER = logging.getLogger(__name__) @@ -218,6 +218,11 @@ def _fetch_device_configuration(self) -> None: ) self.surveillance_station = self.dsm.surveillance_station + def _set_system_loaded(self, state: bool = False) -> None: + """Set system loaded flag.""" + dsm_device = self._hass.data[DOMAIN].get(self.information.serial) + dsm_device[SYSTEM_LOADED] = state + async def _syno_api_executer(self, api_call: Callable) -> None: """Synology api call wrapper.""" try: @@ -231,10 +236,12 @@ async def _syno_api_executer(self, api_call: Callable) -> None: async def async_reboot(self) -> None: """Reboot NAS.""" await self._syno_api_executer(self.system.reboot) + self._set_system_loaded() async def async_shutdown(self) -> None: """Shutdown NAS.""" await self._syno_api_executer(self.system.shutdown) + self._set_system_loaded() async def async_unload(self) -> None: """Stop interacting with the NAS and prepare for removal from hass.""" diff --git a/homeassistant/components/synology_dsm/service.py b/homeassistant/components/synology_dsm/service.py index f26a43b2ca032c..a7a336e0c1b07b 100644 --- a/homeassistant/components/synology_dsm/service.py +++ b/homeassistant/components/synology_dsm/service.py @@ -15,7 +15,6 @@ SERVICE_SHUTDOWN, SERVICES, SYNO_API, - SYSTEM_LOADED, ) LOGGER = logging.getLogger(__name__) @@ -57,7 +56,6 @@ async def service_handler(call: ServiceCall) -> None: ) dsm_api: SynoApi = dsm_device[SYNO_API] try: - dsm_device[SYSTEM_LOADED] = False await getattr(dsm_api, f"async_{call.service}")() except SynologyDSMException as ex: LOGGER.error( From 924bcdf269a53c374c832a71e79cffacdb670fe6 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 9 Feb 2022 10:15:20 +0100 Subject: [PATCH 0463/1098] Move Plugewise binary sensor icon state into entity description (#66148) --- .../components/plugwise/binary_sensor.py | 51 +++++++++++-------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index fa5a1d09920ca0..c022f209c0a30d 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -1,4 +1,8 @@ """Plugwise Binary Sensor component for Home Assistant.""" +from __future__ import annotations + +from dataclasses import dataclass + from homeassistant.components.binary_sensor import ( BinarySensorEntity, BinarySensorEntityDescription, @@ -8,29 +12,33 @@ from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import ( - DOMAIN, - FLAME_ICON, - FLOW_OFF_ICON, - FLOW_ON_ICON, - IDLE_ICON, - LOGGER, - NO_NOTIFICATION_ICON, - NOTIFICATION_ICON, -) +from .const import DOMAIN, LOGGER, NO_NOTIFICATION_ICON, NOTIFICATION_ICON from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity SEVERITIES = ["other", "info", "warning", "error"] -BINARY_SENSORS: tuple[BinarySensorEntityDescription, ...] = ( - BinarySensorEntityDescription( + + +@dataclass +class PlugwiseBinarySensorEntityDescription(BinarySensorEntityDescription): + """Describes a Plugwise binary sensor entity.""" + + icon_off: str | None = None + + +BINARY_SENSORS: tuple[PlugwiseBinarySensorEntityDescription, ...] = ( + PlugwiseBinarySensorEntityDescription( key="dhw_state", name="DHW State", + icon="mdi:water-pump", + icon_off="mdi:water-pump-off", entity_category=EntityCategory.DIAGNOSTIC, ), - BinarySensorEntityDescription( + PlugwiseBinarySensorEntityDescription( key="slave_boiler_state", name="Secondary Boiler State", + icon="mdi:fire", + icon_off="mdi:circle-off-outline", entity_category=EntityCategory.DIAGNOSTIC, ), ) @@ -69,7 +77,7 @@ async def async_setup_entry( PlugwiseNotifyBinarySensorEntity( coordinator, device_id, - BinarySensorEntityDescription( + PlugwiseBinarySensorEntityDescription( key="plugwise_notification", name="Plugwise Notification", ), @@ -82,16 +90,17 @@ async def async_setup_entry( class PlugwiseBinarySensorEntity(PlugwiseEntity, BinarySensorEntity): """Represent Smile Binary Sensors.""" + entity_description: PlugwiseBinarySensorEntityDescription + def __init__( self, coordinator: PlugwiseDataUpdateCoordinator, device_id: str, - description: BinarySensorEntityDescription, + description: PlugwiseBinarySensorEntityDescription, ) -> None: """Initialise the binary_sensor.""" super().__init__(coordinator, device_id) self.entity_description = description - self._attr_is_on = False self._attr_unique_id = f"{device_id}-{description.key}" self._attr_name = ( f"{coordinator.data.devices[device_id].get('name', '')} {description.name}" @@ -105,12 +114,10 @@ def _handle_coordinator_update(self) -> None: super()._handle_coordinator_update() return - self._attr_is_on = data["binary_sensors"].get(self.entity_description.key) - - if self.entity_description.key == "dhw_state": - self._attr_icon = FLOW_ON_ICON if self._attr_is_on else FLOW_OFF_ICON - if self.entity_description.key == "slave_boiler_state": - self._attr_icon = FLAME_ICON if self._attr_is_on else IDLE_ICON + state = data["binary_sensors"].get(self.entity_description.key) + self._attr_is_on = state + if icon_off := self.entity_description.icon_off: + self._attr_icon = self.entity_description.icon if state else icon_off super()._handle_coordinator_update() From d34c289691c6eca84ccce4b2f54da952371d1e00 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 9 Feb 2022 10:23:39 +0100 Subject: [PATCH 0464/1098] Clean up unneeded preset variable in Plugwise climate (#66151) --- homeassistant/components/plugwise/climate.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index 5ecab66bbde58e..b3e7472369c3fb 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -76,8 +76,6 @@ def __init__( self._loc_id = coordinator.data.devices[device_id]["location"] - self._presets = None - async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" temperature = kwargs.get(ATTR_TEMPERATURE) @@ -119,13 +117,13 @@ async def async_set_hvac_mode(self, hvac_mode: str) -> None: async def async_set_preset_mode(self, preset_mode: str) -> None: """Set the preset mode.""" - if self._presets is None: + if not (presets := self.coordinator.data.devices[self._dev_id].get("presets")): raise ValueError("No presets available") try: await self.coordinator.api.set_preset(self._loc_id, preset_mode) self._attr_preset_mode = preset_mode - self._attr_target_temperature = self._presets.get(preset_mode, "none")[0] + self._attr_target_temperature = presets.get(preset_mode, "none")[0] self.async_write_ha_state() except PlugwiseException: LOGGER.error("Error while communicating to device") @@ -147,10 +145,8 @@ def _handle_coordinator_update(self) -> None: # Presets handling self._attr_preset_mode = data.get("active_preset") if presets := data.get("presets"): - self._presets = presets self._attr_preset_modes = list(presets) else: - self._presets = None self._attr_preset_mode = None # Determine current hvac action From b90434e8d69139d177280cf7425e14f060e73704 Mon Sep 17 00:00:00 2001 From: Richard Benson Date: Wed, 9 Feb 2022 04:25:07 -0500 Subject: [PATCH 0465/1098] Bump amcrest to 1.9.4 (#66124) Co-authored-by: Franck Nijhof --- homeassistant/components/amcrest/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/amcrest/manifest.json b/homeassistant/components/amcrest/manifest.json index 6f590d410fd757..5a7bec89e31b66 100644 --- a/homeassistant/components/amcrest/manifest.json +++ b/homeassistant/components/amcrest/manifest.json @@ -2,7 +2,7 @@ "domain": "amcrest", "name": "Amcrest", "documentation": "https://www.home-assistant.io/integrations/amcrest", - "requirements": ["amcrest==1.9.3"], + "requirements": ["amcrest==1.9.4"], "dependencies": ["ffmpeg"], "codeowners": ["@flacjacket"], "iot_class": "local_polling", diff --git a/requirements_all.txt b/requirements_all.txt index cefeb4aa52868d..81942492a4c66c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -308,7 +308,7 @@ amberelectric==1.0.3 ambiclimate==0.2.1 # homeassistant.components.amcrest -amcrest==1.9.3 +amcrest==1.9.4 # homeassistant.components.androidtv androidtv[async]==0.0.63 From 2f08372026ba38d0fc8913fbc0152434696b8af8 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 9 Feb 2022 10:31:58 +0100 Subject: [PATCH 0466/1098] Correct Velbus button light entity category (#66156) --- homeassistant/components/velbus/light.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/velbus/light.py b/homeassistant/components/velbus/light.py index e0d1b797bb3f8d..2926b30c22f098 100644 --- a/homeassistant/components/velbus/light.py +++ b/homeassistant/components/velbus/light.py @@ -96,7 +96,7 @@ class VelbusButtonLight(VelbusEntity, LightEntity): _channel: VelbusButton _attr_entity_registry_enabled_default = False - _attr_entity_category = EntityCategory.DIAGNOSTIC + _attr_entity_category = EntityCategory.CONFIG _attr_supported_features = SUPPORT_FLASH def __init__(self, channel: VelbusChannel) -> None: From bc9ccf0e47e2788e0fabf00937efdb9bbfaa421c Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 9 Feb 2022 10:33:06 +0100 Subject: [PATCH 0467/1098] Add device availability to Plugwise (#66152) --- homeassistant/components/plugwise/entity.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/plugwise/entity.py b/homeassistant/components/plugwise/entity.py index 5fa285418159b0..349ee6d05e9526 100644 --- a/homeassistant/components/plugwise/entity.py +++ b/homeassistant/components/plugwise/entity.py @@ -48,6 +48,11 @@ def __init__( } ) + @property + def available(self) -> bool: + """Return if entity is available.""" + return super().available and self._dev_id in self.coordinator.data.devices + async def async_added_to_hass(self) -> None: """Subscribe to updates.""" self._handle_coordinator_update() From a0119f7ed0cfa32b2ff3b5f273aad8209cae055a Mon Sep 17 00:00:00 2001 From: Kevin Stillhammer Date: Wed, 9 Feb 2022 10:43:20 +0100 Subject: [PATCH 0468/1098] Resolve zones and return state in find_coordinates (#66081) --- homeassistant/helpers/location.py | 60 +++++++++++++++++-------------- tests/helpers/test_location.py | 26 +++++++++++--- 2 files changed, 55 insertions(+), 31 deletions(-) diff --git a/homeassistant/helpers/location.py b/homeassistant/helpers/location.py index a3f2dfb4c6fbdc..06fb07608185f8 100644 --- a/homeassistant/helpers/location.py +++ b/homeassistant/helpers/location.py @@ -4,8 +4,6 @@ from collections.abc import Iterable import logging -import voluptuous as vol - from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE from homeassistant.core import HomeAssistant, State from homeassistant.util import location as loc_util @@ -48,29 +46,42 @@ def closest(latitude: float, longitude: float, states: Iterable[State]) -> State def find_coordinates( - hass: HomeAssistant, entity_id: str, recursion_history: list | None = None + hass: HomeAssistant, name: str, recursion_history: list | None = None ) -> str | None: - """Find the gps coordinates of the entity in the form of '90.000,180.000'.""" - if (entity_state := hass.states.get(entity_id)) is None: - _LOGGER.error("Unable to find entity %s", entity_id) - return None + """Try to resolve the a location from a supplied name or entity_id. + + Will recursively resolve an entity if pointed to by the state of the supplied entity. + Returns coordinates in the form of '90.000,180.000', an address or the state of the last resolved entity. + """ + # Check if a friendly name of a zone was supplied + if (zone_coords := resolve_zone(hass, name)) is not None: + return zone_coords + + # Check if an entity_id was supplied. + if (entity_state := hass.states.get(name)) is None: + _LOGGER.debug("Unable to find entity %s", name) + return name - # Check if the entity has location attributes + # Check if the entity_state has location attributes if has_location(entity_state): return _get_location_from_attributes(entity_state) - # Check if device is in a zone + # Check if entity_state is a zone zone_entity = hass.states.get(f"zone.{entity_state.state}") if has_location(zone_entity): # type: ignore _LOGGER.debug( - "%s is in %s, getting zone location", entity_id, zone_entity.entity_id # type: ignore + "%s is in %s, getting zone location", name, zone_entity.entity_id # type: ignore ) return _get_location_from_attributes(zone_entity) # type: ignore - # Resolve nested entity + # Check if entity_state is a friendly name of a zone + if (zone_coords := resolve_zone(hass, entity_state.state)) is not None: + return zone_coords + + # Check if entity_state is an entity_id if recursion_history is None: recursion_history = [] - recursion_history.append(entity_id) + recursion_history.append(name) if entity_state.state in recursion_history: _LOGGER.error( "Circular reference detected while trying to find coordinates of an entity. The state of %s has already been checked", @@ -83,21 +94,18 @@ def find_coordinates( _LOGGER.debug("Resolving nested entity_id: %s", entity_state.state) return find_coordinates(hass, entity_state.state, recursion_history) - # Check if state is valid coordinate set - try: - # Import here, not at top-level to avoid circular import - from . import config_validation as cv # pylint: disable=import-outside-toplevel + # Might be an address, coordinates or anything else. This has to be checked by the caller. + return entity_state.state - cv.gps(entity_state.state.split(",")) - except vol.Invalid: - _LOGGER.error( - "Entity %s does not contain a location and does not point at an entity that does: %s", - entity_id, - entity_state.state, - ) - return None - else: - return entity_state.state + +def resolve_zone(hass: HomeAssistant, zone_name: str) -> str | None: + """Get a lat/long from a zones friendly_name or None if no zone is found by that friendly_name.""" + states = hass.states.async_all("zone") + for state in states: + if state.name == zone_name: + return _get_location_from_attributes(state) + + return None def _get_location_from_attributes(entity_state: State) -> str: diff --git a/tests/helpers/test_location.py b/tests/helpers/test_location.py index 219d015bdf7e85..5ae1891e45acf9 100644 --- a/tests/helpers/test_location.py +++ b/tests/helpers/test_location.py @@ -1,5 +1,5 @@ """Tests Home Assistant location helpers.""" -from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE +from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_LATITUDE, ATTR_LONGITUDE from homeassistant.core import State from homeassistant.helpers import location @@ -73,6 +73,21 @@ async def test_coordinates_function_device_tracker_in_zone(hass): ) +async def test_coordinates_function_zone_friendly_name(hass): + """Test coordinates function.""" + hass.states.async_set( + "zone.home", + "zoning", + {"latitude": 32.87336, "longitude": -117.22943, ATTR_FRIENDLY_NAME: "my_home"}, + ) + hass.states.async_set( + "test.object", + "my_home", + ) + assert location.find_coordinates(hass, "test.object") == "32.87336,-117.22943" + assert location.find_coordinates(hass, "my_home") == "32.87336,-117.22943" + + async def test_coordinates_function_device_tracker_from_input_select(hass): """Test coordinates function.""" hass.states.async_set( @@ -96,15 +111,16 @@ def test_coordinates_function_returns_none_on_recursion(hass): assert location.find_coordinates(hass, "test.first") is None -async def test_coordinates_function_returns_none_if_invalid_coord(hass): +async def test_coordinates_function_returns_state_if_no_coords(hass): """Test test_coordinates function.""" hass.states.async_set( "test.object", "abc", ) - assert location.find_coordinates(hass, "test.object") is None + assert location.find_coordinates(hass, "test.object") == "abc" -def test_coordinates_function_returns_none_if_invalid_input(hass): +def test_coordinates_function_returns_input_if_no_coords(hass): """Test test_coordinates function.""" - assert location.find_coordinates(hass, "test.abc") is None + assert location.find_coordinates(hass, "test.abc") == "test.abc" + assert location.find_coordinates(hass, "abc") == "abc" From 3f7b7187ab410fd8dbf04fcfc834fda731dc8087 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 9 Feb 2022 14:27:46 +0100 Subject: [PATCH 0469/1098] Fix controlling nested groups (#66176) --- homeassistant/components/group/cover.py | 2 + homeassistant/components/group/fan.py | 2 + homeassistant/components/group/light.py | 3 ++ tests/components/group/test_cover.py | 50 ++++++++++++++++++ tests/components/group/test_fan.py | 56 +++++++++++++++++++++ tests/components/group/test_light.py | 23 +++++++-- tests/components/group/test_media_player.py | 28 ++++++++--- 7 files changed, 155 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/group/cover.py b/homeassistant/components/group/cover.py index a98f75fceb8779..a4c550b8119bbc 100644 --- a/homeassistant/components/group/cover.py +++ b/homeassistant/components/group/cover.py @@ -57,6 +57,8 @@ DEFAULT_NAME = "Cover Group" +# No limit on parallel updates to enable a group calling another group +PARALLEL_UPDATES = 0 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { diff --git a/homeassistant/components/group/fan.py b/homeassistant/components/group/fan.py index cef30dc3c693f9..7920e0f5d204f9 100644 --- a/homeassistant/components/group/fan.py +++ b/homeassistant/components/group/fan.py @@ -52,6 +52,8 @@ DEFAULT_NAME = "Fan Group" +# No limit on parallel updates to enable a group calling another group +PARALLEL_UPDATES = 0 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py index 201156db6009d7..ea74136b204ede 100644 --- a/homeassistant/components/group/light.py +++ b/homeassistant/components/group/light.py @@ -58,6 +58,9 @@ DEFAULT_NAME = "Light Group" +# No limit on parallel updates to enable a group calling another group +PARALLEL_UPDATES = 0 + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, diff --git a/tests/components/group/test_cover.py b/tests/components/group/test_cover.py index cf1fba992e700e..d090141a9d2c20 100644 --- a/tests/components/group/test_cover.py +++ b/tests/components/group/test_cover.py @@ -1,6 +1,7 @@ """The tests for the group cover platform.""" from datetime import timedelta +import async_timeout import pytest from homeassistant.components.cover import ( @@ -735,3 +736,52 @@ async def test_is_opening_closing(hass, setup_comp): assert hass.states.get(DEMO_COVER_TILT).state == STATE_OPENING assert hass.states.get(DEMO_COVER_POS).state == STATE_OPEN assert hass.states.get(COVER_GROUP).state == STATE_OPENING + + +async def test_nested_group(hass): + """Test nested cover group.""" + await async_setup_component( + hass, + DOMAIN, + { + DOMAIN: [ + {"platform": "demo"}, + { + "platform": "group", + "entities": ["cover.bedroom_group"], + "name": "Nested Group", + }, + { + "platform": "group", + CONF_ENTITIES: [DEMO_COVER_POS, DEMO_COVER_TILT], + "name": "Bedroom Group", + }, + ] + }, + ) + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.get("cover.bedroom_group") + assert state is not None + assert state.state == STATE_OPEN + assert state.attributes.get(ATTR_ENTITY_ID) == [DEMO_COVER_POS, DEMO_COVER_TILT] + + state = hass.states.get("cover.nested_group") + assert state is not None + assert state.state == STATE_OPEN + assert state.attributes.get(ATTR_ENTITY_ID) == ["cover.bedroom_group"] + + # Test controlling the nested group + async with async_timeout.timeout(0.5): + await hass.services.async_call( + DOMAIN, + SERVICE_CLOSE_COVER, + {ATTR_ENTITY_ID: "cover.nested_group"}, + blocking=True, + ) + assert hass.states.get(DEMO_COVER_POS).state == STATE_CLOSING + assert hass.states.get(DEMO_COVER_TILT).state == STATE_CLOSING + assert hass.states.get("cover.bedroom_group").state == STATE_CLOSING + assert hass.states.get("cover.nested_group").state == STATE_CLOSING diff --git a/tests/components/group/test_fan.py b/tests/components/group/test_fan.py index abb1dcf245a8a3..19b4fe4670ad18 100644 --- a/tests/components/group/test_fan.py +++ b/tests/components/group/test_fan.py @@ -1,6 +1,7 @@ """The tests for the group fan platform.""" from unittest.mock import patch +import async_timeout import pytest from homeassistant import config as hass_config @@ -497,3 +498,58 @@ async def test_service_calls(hass, setup_comp): assert percentage_full_fan_state.attributes[ATTR_DIRECTION] == DIRECTION_REVERSE fan_group_state = hass.states.get(FAN_GROUP) assert fan_group_state.attributes[ATTR_DIRECTION] == DIRECTION_REVERSE + + +async def test_nested_group(hass): + """Test nested fan group.""" + await async_setup_component( + hass, + DOMAIN, + { + DOMAIN: [ + {"platform": "demo"}, + { + "platform": "group", + "entities": ["fan.bedroom_group"], + "name": "Nested Group", + }, + { + "platform": "group", + CONF_ENTITIES: [ + LIVING_ROOM_FAN_ENTITY_ID, + PERCENTAGE_FULL_FAN_ENTITY_ID, + ], + "name": "Bedroom Group", + }, + ] + }, + ) + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.get("fan.bedroom_group") + assert state is not None + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ENTITY_ID) == [ + LIVING_ROOM_FAN_ENTITY_ID, + PERCENTAGE_FULL_FAN_ENTITY_ID, + ] + + state = hass.states.get("fan.nested_group") + assert state is not None + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ENTITY_ID) == ["fan.bedroom_group"] + + # Test controlling the nested group + async with async_timeout.timeout(0.5): + await hass.services.async_call( + DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "fan.nested_group"}, + blocking=True, + ) + assert hass.states.get(LIVING_ROOM_FAN_ENTITY_ID).state == STATE_ON + assert hass.states.get(PERCENTAGE_FULL_FAN_ENTITY_ID).state == STATE_ON + assert hass.states.get("fan.bedroom_group").state == STATE_ON + assert hass.states.get("fan.nested_group").state == STATE_ON diff --git a/tests/components/group/test_light.py b/tests/components/group/test_light.py index 843f15c71134c0..d356b20b40f1e6 100644 --- a/tests/components/group/test_light.py +++ b/tests/components/group/test_light.py @@ -2,6 +2,7 @@ import unittest.mock from unittest.mock import MagicMock, patch +import async_timeout import pytest from homeassistant import config as hass_config @@ -1470,12 +1471,12 @@ async def test_reload_with_base_integration_platform_not_setup(hass): async def test_nested_group(hass): """Test nested light group.""" - hass.states.async_set("light.kitchen", "on") await async_setup_component( hass, LIGHT_DOMAIN, { LIGHT_DOMAIN: [ + {"platform": "demo"}, { "platform": DOMAIN, "entities": ["light.bedroom_group"], @@ -1483,7 +1484,7 @@ async def test_nested_group(hass): }, { "platform": DOMAIN, - "entities": ["light.kitchen", "light.bedroom"], + "entities": ["light.bed_light", "light.kitchen_lights"], "name": "Bedroom Group", }, ] @@ -1496,9 +1497,25 @@ async def test_nested_group(hass): state = hass.states.get("light.bedroom_group") assert state is not None assert state.state == STATE_ON - assert state.attributes.get(ATTR_ENTITY_ID) == ["light.kitchen", "light.bedroom"] + assert state.attributes.get(ATTR_ENTITY_ID) == [ + "light.bed_light", + "light.kitchen_lights", + ] state = hass.states.get("light.nested_group") assert state is not None assert state.state == STATE_ON assert state.attributes.get(ATTR_ENTITY_ID) == ["light.bedroom_group"] + + # Test controlling the nested group + async with async_timeout.timeout(0.5): + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TOGGLE, + {ATTR_ENTITY_ID: "light.nested_group"}, + blocking=True, + ) + assert hass.states.get("light.bed_light").state == STATE_OFF + assert hass.states.get("light.kitchen_lights").state == STATE_OFF + assert hass.states.get("light.bedroom_group").state == STATE_OFF + assert hass.states.get("light.nested_group").state == STATE_OFF diff --git a/tests/components/group/test_media_player.py b/tests/components/group/test_media_player.py index 27962297952a67..f741e2d1a848fe 100644 --- a/tests/components/group/test_media_player.py +++ b/tests/components/group/test_media_player.py @@ -1,6 +1,7 @@ """The tests for the Media group platform.""" from unittest.mock import patch +import async_timeout import pytest from homeassistant.components.group import DOMAIN @@ -486,12 +487,12 @@ async def test_service_calls(hass, mock_media_seek): async def test_nested_group(hass): """Test nested media group.""" - hass.states.async_set("media_player.player_1", "on") await async_setup_component( hass, MEDIA_DOMAIN, { MEDIA_DOMAIN: [ + {"platform": "demo"}, { "platform": DOMAIN, "entities": ["media_player.group_1"], @@ -499,7 +500,7 @@ async def test_nested_group(hass): }, { "platform": DOMAIN, - "entities": ["media_player.player_1", "media_player.player_2"], + "entities": ["media_player.bedroom", "media_player.kitchen"], "name": "Group 1", }, ] @@ -511,13 +512,28 @@ async def test_nested_group(hass): state = hass.states.get("media_player.group_1") assert state is not None - assert state.state == STATE_ON + assert state.state == STATE_PLAYING assert state.attributes.get(ATTR_ENTITY_ID) == [ - "media_player.player_1", - "media_player.player_2", + "media_player.bedroom", + "media_player.kitchen", ] state = hass.states.get("media_player.nested_group") assert state is not None - assert state.state == STATE_ON + assert state.state == STATE_PLAYING assert state.attributes.get(ATTR_ENTITY_ID) == ["media_player.group_1"] + + # Test controlling the nested group + async with async_timeout.timeout(0.5): + await hass.services.async_call( + MEDIA_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "media_player.group_1"}, + blocking=True, + ) + + await hass.async_block_till_done() + assert hass.states.get("media_player.bedroom").state == STATE_OFF + assert hass.states.get("media_player.kitchen").state == STATE_OFF + assert hass.states.get("media_player.group_1").state == STATE_OFF + assert hass.states.get("media_player.nested_group").state == STATE_OFF From 567f07c96eedfce739ecfff79314c6a2d582cabc Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Wed, 9 Feb 2022 16:52:32 +0100 Subject: [PATCH 0470/1098] Fix hdmi-cec initialization (#66172) --- homeassistant/components/hdmi_cec/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/hdmi_cec/__init__.py b/homeassistant/components/hdmi_cec/__init__.py index 3d4851d8852235..056eacb6a5bd56 100644 --- a/homeassistant/components/hdmi_cec/__init__.py +++ b/homeassistant/components/hdmi_cec/__init__.py @@ -191,6 +191,8 @@ def parse_mapping(mapping, parents=None): def setup(hass: HomeAssistant, base_config: ConfigType) -> bool: # noqa: C901 """Set up the CEC capability.""" + hass.data[DOMAIN] = {} + # Parse configuration into a dict of device name to physical address # represented as a list of four elements. device_aliases = {} From 1f4ee3c265753e76b628b8a3358edc86c344fc4f Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 9 Feb 2022 17:54:27 +0100 Subject: [PATCH 0471/1098] Bump aioesphomeapi from 10.8.1 to 10.8.2 (#66189) --- homeassistant/components/esphome/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json index 81c85a93056827..72a36076bf49e0 100644 --- a/homeassistant/components/esphome/manifest.json +++ b/homeassistant/components/esphome/manifest.json @@ -3,7 +3,7 @@ "name": "ESPHome", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/esphome", - "requirements": ["aioesphomeapi==10.8.1"], + "requirements": ["aioesphomeapi==10.8.2"], "zeroconf": ["_esphomelib._tcp.local."], "codeowners": ["@OttoWinter", "@jesserockz"], "after_dependencies": ["zeroconf", "tag"], diff --git a/requirements_all.txt b/requirements_all.txt index 81942492a4c66c..5fa50cde8423f9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -166,7 +166,7 @@ aioeagle==1.1.0 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==10.8.1 +aioesphomeapi==10.8.2 # homeassistant.components.flo aioflo==2021.11.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 98fca3a0c88b23..4a74f1de59c3f7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -119,7 +119,7 @@ aioeagle==1.1.0 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==10.8.1 +aioesphomeapi==10.8.2 # homeassistant.components.flo aioflo==2021.11.0 From aa95150360891102dccc99885c44b4a9e7014f71 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 9 Feb 2022 17:56:07 +0100 Subject: [PATCH 0472/1098] Add entity descriptions to Plugwise switch platform (#66174) --- homeassistant/components/plugwise/const.py | 1 - homeassistant/components/plugwise/gateway.py | 20 ++++++++- homeassistant/components/plugwise/switch.py | 44 ++++++++++++------- tests/components/plugwise/test_switch.py | 46 ++++++++++++++++++++ 4 files changed, 94 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/plugwise/const.py b/homeassistant/components/plugwise/const.py index d8d0a040d5f7c9..62eabd8b0de93d 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -62,6 +62,5 @@ FLOW_OFF_ICON = "mdi:water-pump-off" FLOW_ON_ICON = "mdi:water-pump" IDLE_ICON = "mdi:circle-off-outline" -SWITCH_ICON = "mdi:electric-switch" NO_NOTIFICATION_ICON = "mdi:mailbox-outline" NOTIFICATION_ICON = "mdi:mailbox-up-outline" diff --git a/homeassistant/components/plugwise/gateway.py b/homeassistant/components/plugwise/gateway.py index 6e518aad490d01..05ef937c6fdc3b 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -2,16 +2,19 @@ from __future__ import annotations import asyncio +from typing import Any from plugwise.exceptions import InvalidAuthentication, PlugwiseException from plugwise.smile import Smile +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.entity_registry import RegistryEntry, async_migrate_entries from .const import ( DEFAULT_PORT, @@ -26,6 +29,8 @@ async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Plugwise Smiles from a config entry.""" + await async_migrate_entries(hass, entry.entry_id, async_migrate_entity_entry) + websession = async_get_clientsession(hass, verify_ssl=False) api = Smile( host=entry.data[CONF_HOST], @@ -88,3 +93,16 @@ async def async_unload_entry_gw(hass: HomeAssistant, entry: ConfigEntry): ): hass.data[DOMAIN].pop(entry.entry_id) return unload_ok + + +@callback +def async_migrate_entity_entry(entry: RegistryEntry) -> dict[str, Any] | None: + """Migrate Plugwise entity entries. + + - Migrates unique ID from old relay switches to the new unique ID + """ + if entry.domain == SWITCH_DOMAIN and entry.unique_id.endswith("-plug"): + return {"new_unique_id": entry.unique_id.replace("-plug", "-relay")} + + # No migration needed + return None diff --git a/homeassistant/components/plugwise/switch.py b/homeassistant/components/plugwise/switch.py index 98ba1cd8183648..c95474a2b5a131 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -5,15 +5,23 @@ from plugwise.exceptions import PlugwiseException -from homeassistant.components.switch import SwitchEntity +from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN, LOGGER, SWITCH_ICON +from .const import DOMAIN, LOGGER from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity +SWITCHES: tuple[SwitchEntityDescription, ...] = ( + SwitchEntityDescription( + key="relay", + name="Relay", + icon="mdi:electric-switch", + ), +) + async def async_setup_entry( hass: HomeAssistant, @@ -22,35 +30,38 @@ async def async_setup_entry( ) -> None: """Set up the Smile switches from a config entry.""" coordinator = hass.data[DOMAIN][config_entry.entry_id] - async_add_entities( - PlugwiseSwitchEntity(coordinator, device_id) - for device_id, device in coordinator.data.devices.items() - if "switches" in device and "relay" in device["switches"] - ) + entities: list[PlugwiseSwitchEntity] = [] + for device_id, device in coordinator.data.devices.items(): + for description in SWITCHES: + if "switches" not in device or description.key not in device["switches"]: + continue + entities.append(PlugwiseSwitchEntity(coordinator, device_id, description)) + async_add_entities(entities) class PlugwiseSwitchEntity(PlugwiseEntity, SwitchEntity): """Representation of a Plugwise plug.""" - _attr_icon = SWITCH_ICON - def __init__( self, coordinator: PlugwiseDataUpdateCoordinator, device_id: str, + description: SwitchEntityDescription, ) -> None: """Set up the Plugwise API.""" super().__init__(coordinator, device_id) - self._attr_unique_id = f"{device_id}-plug" - self._members = coordinator.data.devices[device_id].get("members") - self._attr_is_on = False + self.entity_description = description + self._attr_unique_id = f"{device_id}-{description.key}" self._attr_name = coordinator.data.devices[device_id].get("name") async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" try: state_on = await self.coordinator.api.set_switch_state( - self._dev_id, self._members, "relay", "on" + self._dev_id, + self.coordinator.data.devices[self._dev_id].get("members"), + self.entity_description.key, + "on", ) except PlugwiseException: LOGGER.error("Error while communicating to device") @@ -63,7 +74,10 @@ async def async_turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" try: state_off = await self.coordinator.api.set_switch_state( - self._dev_id, self._members, "relay", "off" + self._dev_id, + self.coordinator.data.devices[self._dev_id].get("members"), + self.entity_description.key, + "off", ) except PlugwiseException: LOGGER.error("Error while communicating to device") @@ -80,5 +94,5 @@ def _handle_coordinator_update(self) -> None: super()._handle_coordinator_update() return - self._attr_is_on = data["switches"].get("relay") + self._attr_is_on = data["switches"].get(self.entity_description.key) super()._handle_coordinator_update() diff --git a/tests/components/plugwise/test_switch.py b/tests/components/plugwise/test_switch.py index 1e1fb4a067951e..4d09489944d703 100644 --- a/tests/components/plugwise/test_switch.py +++ b/tests/components/plugwise/test_switch.py @@ -2,8 +2,12 @@ from plugwise.exceptions import PlugwiseException +from homeassistant.components.plugwise.const import DOMAIN +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.config_entries import ConfigEntryState +from homeassistant.helpers import entity_registry as er +from tests.common import MockConfigEntry from tests.components.plugwise.common import async_init_integration @@ -121,3 +125,45 @@ async def test_stretch_switch_changes(hass, mock_stretch): ) state = hass.states.get("switch.droger_52559") assert str(state.state) == "on" + + +async def test_unique_id_migration_plug_relay(hass, mock_smile_adam): + """Test unique ID migration of -plugs to -relay.""" + entry = MockConfigEntry( + domain=DOMAIN, data={"host": "1.1.1.1", "password": "test-password"} + ) + entry.add_to_hass(hass) + + registry = er.async_get(hass) + # Entry to migrate + registry.async_get_or_create( + SWITCH_DOMAIN, + DOMAIN, + "21f2b542c49845e6bb416884c55778d6-plug", + config_entry=entry, + suggested_object_id="playstation_smart_plug", + disabled_by=None, + ) + # Entry not needing migration + registry.async_get_or_create( + SWITCH_DOMAIN, + DOMAIN, + "675416a629f343c495449970e2ca37b5-relay", + config_entry=entry, + suggested_object_id="router", + disabled_by=None, + ) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert hass.states.get("switch.playstation_smart_plug") is not None + assert hass.states.get("switch.router") is not None + + entity_entry = registry.async_get("switch.playstation_smart_plug") + assert entity_entry + assert entity_entry.unique_id == "21f2b542c49845e6bb416884c55778d6-relay" + + entity_entry = registry.async_get("switch.router") + assert entity_entry + assert entity_entry.unique_id == "675416a629f343c495449970e2ca37b5-relay" From d4995624ee08518a09c2320f35de1db27498f3c9 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 9 Feb 2022 18:08:28 +0100 Subject: [PATCH 0473/1098] Refactor Plugwise notification binary sensor (#66159) --- .../components/plugwise/binary_sensor.py | 82 +++++++------------ homeassistant/components/plugwise/const.py | 2 - 2 files changed, 29 insertions(+), 55 deletions(-) diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index c022f209c0a30d..f6118dde3701ed 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -12,7 +12,7 @@ from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN, LOGGER, NO_NOTIFICATION_ICON, NOTIFICATION_ICON +from .const import DOMAIN, LOGGER from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity @@ -41,6 +41,13 @@ class PlugwiseBinarySensorEntityDescription(BinarySensorEntityDescription): icon_off="mdi:circle-off-outline", entity_category=EntityCategory.DIAGNOSTIC, ), + PlugwiseBinarySensorEntityDescription( + key="plugwise_notification", + name="Plugwise Notification", + icon="mdi:mailbox-up-outline", + icon_off="mdi:mailbox-outline", + entity_category=EntityCategory.DIAGNOSTIC, + ), ) @@ -56,34 +63,20 @@ async def async_setup_entry( entities: list[PlugwiseBinarySensorEntity] = [] for device_id, device in coordinator.data.devices.items(): - if device["class"] == "heater_central": - for description in BINARY_SENSORS: - if ( - "binary_sensors" not in device - or description.key not in device["binary_sensors"] - ): - continue - - entities.append( - PlugwiseBinarySensorEntity( - coordinator, - device_id, - description, - ) - ) + for description in BINARY_SENSORS: + if ( + "binary_sensors" not in device + or description.key not in device["binary_sensors"] + ): + continue - if device["class"] == "gateway": entities.append( - PlugwiseNotifyBinarySensorEntity( + PlugwiseBinarySensorEntity( coordinator, device_id, - PlugwiseBinarySensorEntityDescription( - key="plugwise_notification", - name="Plugwise Notification", - ), + description, ) ) - async_add_entities(entities) @@ -119,35 +112,18 @@ def _handle_coordinator_update(self) -> None: if icon_off := self.entity_description.icon_off: self._attr_icon = self.entity_description.icon if state else icon_off - super()._handle_coordinator_update() - + # Add entity attribute for Plugwise notifications + if self.entity_description.key == "plugwise_notification": + self._attr_extra_state_attributes = { + f"{severity}_msg": [] for severity in SEVERITIES + } -class PlugwiseNotifyBinarySensorEntity(PlugwiseBinarySensorEntity): - """Representation of a Plugwise Notification binary_sensor.""" + if notify := self.coordinator.data.gateway["notifications"]: + for details in notify.values(): + for msg_type, msg in details.items(): + msg_type = msg_type.lower() + if msg_type not in SEVERITIES: + msg_type = "other" + self._attr_extra_state_attributes[f"{msg_type}_msg"].append(msg) - @callback - def _handle_coordinator_update(self) -> None: - """Handle updated data from the coordinator.""" - notify = self.coordinator.data.gateway["notifications"] - - self._attr_extra_state_attributes = {} - for severity in SEVERITIES: - self._attr_extra_state_attributes[f"{severity}_msg"] = [] - - self._attr_is_on = False - self._attr_icon = NO_NOTIFICATION_ICON - - if notify: - self._attr_is_on = True - self._attr_icon = NOTIFICATION_ICON - - for details in notify.values(): - for msg_type, msg in details.items(): - if msg_type not in SEVERITIES: - msg_type = "other" - - self._attr_extra_state_attributes[f"{msg_type.lower()}_msg"].append( - msg - ) - - self.async_write_ha_state() + super()._handle_coordinator_update() diff --git a/homeassistant/components/plugwise/const.py b/homeassistant/components/plugwise/const.py index 62eabd8b0de93d..506f20c27680f5 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -62,5 +62,3 @@ FLOW_OFF_ICON = "mdi:water-pump-off" FLOW_ON_ICON = "mdi:water-pump" IDLE_ICON = "mdi:circle-off-outline" -NO_NOTIFICATION_ICON = "mdi:mailbox-outline" -NOTIFICATION_ICON = "mdi:mailbox-up-outline" From 83a10cca539e9c03ac4f8658473831496c58c3e2 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 9 Feb 2022 19:09:55 +0100 Subject: [PATCH 0474/1098] Enable basic type checking for config (#66197) --- homeassistant/components/config/auth.py | 4 +- .../components/config/config_entries.py | 6 +-- homeassistant/components/config/core.py | 4 +- .../components/config/entity_registry.py | 47 +++++++++---------- mypy.ini | 12 ----- script/hassfest/mypy_config.py | 4 -- 6 files changed, 28 insertions(+), 49 deletions(-) diff --git a/homeassistant/components/config/auth.py b/homeassistant/components/config/auth.py index d2f630a8b6de1c..15fc6634f5b296 100644 --- a/homeassistant/components/config/auth.py +++ b/homeassistant/components/config/auth.py @@ -60,7 +60,6 @@ async def websocket_delete(hass, connection, msg): @websocket_api.require_admin -@websocket_api.async_response @websocket_api.websocket_command( { vol.Required("type"): "config/auth/create", @@ -69,6 +68,7 @@ async def websocket_delete(hass, connection, msg): vol.Optional("local_only"): bool, } ) +@websocket_api.async_response async def websocket_create(hass, connection, msg): """Create a user.""" user = await hass.auth.async_create_user( @@ -81,7 +81,6 @@ async def websocket_create(hass, connection, msg): @websocket_api.require_admin -@websocket_api.async_response @websocket_api.websocket_command( { vol.Required("type"): "config/auth/update", @@ -92,6 +91,7 @@ async def websocket_create(hass, connection, msg): vol.Optional("local_only"): bool, } ) +@websocket_api.async_response async def websocket_update(hass, connection, msg): """Update a user.""" if not (user := await hass.auth.async_get_user(msg.pop("user_id"))): diff --git a/homeassistant/components/config/config_entries.py b/homeassistant/components/config/config_entries.py index 887c0517d05c36..2cf7005cb66121 100644 --- a/homeassistant/components/config/config_entries.py +++ b/homeassistant/components/config/config_entries.py @@ -262,7 +262,6 @@ def get_entry( @websocket_api.require_admin -@websocket_api.async_response @websocket_api.websocket_command( { "type": "config_entries/update", @@ -272,6 +271,7 @@ def get_entry( vol.Optional("pref_disable_polling"): bool, } ) +@websocket_api.async_response async def config_entry_update(hass, connection, msg): """Update config entry.""" changes = dict(msg) @@ -305,7 +305,6 @@ async def config_entry_update(hass, connection, msg): @websocket_api.require_admin -@websocket_api.async_response @websocket_api.websocket_command( { "type": "config_entries/disable", @@ -315,6 +314,7 @@ async def config_entry_update(hass, connection, msg): "disabled_by": vol.Any(config_entries.ConfigEntryDisabler.USER.value, None), } ) +@websocket_api.async_response async def config_entry_disable(hass, connection, msg): """Disable config entry.""" disabled_by = msg["disabled_by"] @@ -339,10 +339,10 @@ async def config_entry_disable(hass, connection, msg): @websocket_api.require_admin -@websocket_api.async_response @websocket_api.websocket_command( {"type": "config_entries/ignore_flow", "flow_id": str, "title": str} ) +@websocket_api.async_response async def ignore_config_flow(hass, connection, msg): """Ignore a config flow.""" flow = next( diff --git a/homeassistant/components/config/core.py b/homeassistant/components/config/core.py index e9e54a688c4fbf..3f665e475f070d 100644 --- a/homeassistant/components/config/core.py +++ b/homeassistant/components/config/core.py @@ -35,7 +35,6 @@ async def post(self, request): @websocket_api.require_admin -@websocket_api.async_response @websocket_api.websocket_command( { "type": "config/core/update", @@ -50,6 +49,7 @@ async def post(self, request): vol.Optional("currency"): cv.currency, } ) +@websocket_api.async_response async def websocket_update_config(hass, connection, msg): """Handle update core config command.""" data = dict(msg) @@ -64,8 +64,8 @@ async def websocket_update_config(hass, connection, msg): @websocket_api.require_admin -@websocket_api.async_response @websocket_api.websocket_command({"type": "config/core/detect"}) +@websocket_api.async_response async def websocket_detect_config(hass, connection, msg): """Detect core config.""" session = async_get_clientsession(hass) diff --git a/homeassistant/components/config/entity_registry.py b/homeassistant/components/config/entity_registry.py index 26a2d930d187dc..f5ffc574b86155 100644 --- a/homeassistant/components/config/entity_registry.py +++ b/homeassistant/components/config/entity_registry.py @@ -4,15 +4,12 @@ from homeassistant import config_entries from homeassistant.components import websocket_api from homeassistant.components.websocket_api.const import ERR_NOT_FOUND -from homeassistant.components.websocket_api.decorators import ( - async_response, - require_admin, -) +from homeassistant.components.websocket_api.decorators import require_admin from homeassistant.core import callback -from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.entity_registry import ( - RegistryEntryDisabler, - async_get_registry, +from homeassistant.helpers import ( + config_validation as cv, + device_registry as dr, + entity_registry as er, ) @@ -25,14 +22,11 @@ async def async_setup(hass): return True -@async_response @websocket_api.websocket_command({vol.Required("type"): "config/entity_registry/list"}) -async def websocket_list_entities(hass, connection, msg): - """Handle list registry entries command. - - Async friendly. - """ - registry = await async_get_registry(hass) +@callback +def websocket_list_entities(hass, connection, msg): + """Handle list registry entries command.""" + registry = er.async_get(hass) connection.send_message( websocket_api.result_message( msg["id"], [_entry_dict(entry) for entry in registry.entities.values()] @@ -40,19 +34,19 @@ async def websocket_list_entities(hass, connection, msg): ) -@async_response @websocket_api.websocket_command( { vol.Required("type"): "config/entity_registry/get", vol.Required("entity_id"): cv.entity_id, } ) -async def websocket_get_entity(hass, connection, msg): +@callback +def websocket_get_entity(hass, connection, msg): """Handle get entity registry entry command. Async friendly. """ - registry = await async_get_registry(hass) + registry = er.async_get(hass) if (entry := registry.entities.get(msg["entity_id"])) is None: connection.send_message( @@ -66,7 +60,6 @@ async def websocket_get_entity(hass, connection, msg): @require_admin -@async_response @websocket_api.websocket_command( { vol.Required("type"): "config/entity_registry/update", @@ -81,17 +74,19 @@ async def websocket_get_entity(hass, connection, msg): vol.Optional("disabled_by"): vol.Any( None, vol.All( - vol.Coerce(RegistryEntryDisabler), RegistryEntryDisabler.USER.value + vol.Coerce(er.RegistryEntryDisabler), + er.RegistryEntryDisabler.USER.value, ), ), } ) -async def websocket_update_entity(hass, connection, msg): +@callback +def websocket_update_entity(hass, connection, msg): """Handle update entity websocket command. Async friendly. """ - registry = await async_get_registry(hass) + registry = er.async_get(hass) if msg["entity_id"] not in registry.entities: connection.send_message( @@ -120,7 +115,7 @@ async def websocket_update_entity(hass, connection, msg): if "disabled_by" in msg and msg["disabled_by"] is None: entity = registry.entities[msg["entity_id"]] if entity.device_id: - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entity.device_id) if device.disabled: connection.send_message( @@ -149,19 +144,19 @@ async def websocket_update_entity(hass, connection, msg): @require_admin -@async_response @websocket_api.websocket_command( { vol.Required("type"): "config/entity_registry/remove", vol.Required("entity_id"): cv.entity_id, } ) -async def websocket_remove_entity(hass, connection, msg): +@callback +def websocket_remove_entity(hass, connection, msg): """Handle remove entity websocket command. Async friendly. """ - registry = await async_get_registry(hass) + registry = er.async_get(hass) if msg["entity_id"] not in registry.entities: connection.send_message( diff --git a/mypy.ini b/mypy.ini index 31788f3643dcb3..e2b406301da581 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2145,18 +2145,6 @@ ignore_errors = true [mypy-homeassistant.components.cloud.http_api] ignore_errors = true -[mypy-homeassistant.components.config.auth] -ignore_errors = true - -[mypy-homeassistant.components.config.config_entries] -ignore_errors = true - -[mypy-homeassistant.components.config.core] -ignore_errors = true - -[mypy-homeassistant.components.config.entity_registry] -ignore_errors = true - [mypy-homeassistant.components.conversation] ignore_errors = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index 5e3d9bd4b11270..19bd3ee77f3608 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -21,10 +21,6 @@ "homeassistant.components.blueprint.websocket_api", "homeassistant.components.cloud.client", "homeassistant.components.cloud.http_api", - "homeassistant.components.config.auth", - "homeassistant.components.config.config_entries", - "homeassistant.components.config.core", - "homeassistant.components.config.entity_registry", "homeassistant.components.conversation", "homeassistant.components.conversation.default_agent", "homeassistant.components.deconz", From a6013dc0de5c04e8425cf9ce3c7409f096de8fab Mon Sep 17 00:00:00 2001 From: Stephan Traub Date: Wed, 9 Feb 2022 19:53:32 +0100 Subject: [PATCH 0475/1098] Update WiZ with IP address validation (#66117) --- homeassistant/components/wiz/config_flow.py | 48 ++++++++++--------- homeassistant/components/wiz/strings.json | 9 ++-- .../components/wiz/translations/en.json | 14 ++---- tests/components/wiz/test_config_flow.py | 38 +++++++++++++++ 4 files changed, 74 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/wiz/config_flow.py b/homeassistant/components/wiz/config_flow.py index 9b753284dc5eae..3fe3b9071b06d6 100644 --- a/homeassistant/components/wiz/config_flow.py +++ b/homeassistant/components/wiz/config_flow.py @@ -13,6 +13,7 @@ from homeassistant.components import dhcp from homeassistant.const import CONF_HOST from homeassistant.data_entry_flow import FlowResult +from homeassistant.util.network import is_ip_address from .const import DEFAULT_NAME, DISCOVER_SCAN_TIMEOUT, DOMAIN, WIZ_EXCEPTIONS from .discovery import async_discover_devices @@ -139,29 +140,32 @@ async def async_step_user( if user_input is not None: if not (host := user_input[CONF_HOST]): return await self.async_step_pick_device() - bulb = wizlight(host) - try: - mac = await bulb.getMac() - bulbtype = await bulb.get_bulbtype() - except WizLightTimeOutError: - errors["base"] = "bulb_time_out" - except ConnectionRefusedError: - errors["base"] = "cannot_connect" - except WizLightConnectionError: - errors["base"] = "no_wiz_light" - except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" + if not is_ip_address(user_input[CONF_HOST]): + errors["base"] = "no_ip" else: - await self.async_set_unique_id(mac, raise_on_progress=False) - self._abort_if_unique_id_configured( - updates={CONF_HOST: user_input[CONF_HOST]} - ) - name = name_from_bulb_type_and_mac(bulbtype, mac) - return self.async_create_entry( - title=name, - data=user_input, - ) + bulb = wizlight(host) + try: + bulbtype = await bulb.get_bulbtype() + mac = await bulb.getMac() + except WizLightTimeOutError: + errors["base"] = "bulb_time_out" + except ConnectionRefusedError: + errors["base"] = "cannot_connect" + except WizLightConnectionError: + errors["base"] = "no_wiz_light" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + else: + await self.async_set_unique_id(mac, raise_on_progress=False) + self._abort_if_unique_id_configured( + updates={CONF_HOST: user_input[CONF_HOST]} + ) + name = name_from_bulb_type_and_mac(bulbtype, mac) + return self.async_create_entry( + title=name, + data=user_input, + ) return self.async_show_form( step_id="user", diff --git a/homeassistant/components/wiz/strings.json b/homeassistant/components/wiz/strings.json index 288fd76acc4264..548e79e915787d 100644 --- a/homeassistant/components/wiz/strings.json +++ b/homeassistant/components/wiz/strings.json @@ -4,9 +4,9 @@ "step": { "user": { "data": { - "host": "[%key:common::config_flow::data::host%]" + "host": "[%key:common::config_flow::data::ip%]" }, - "description": "If you leave the host empty, discovery will be used to find devices." + "description": "If you leave the IP Address empty, discovery will be used to find devices." }, "discovery_confirm": { "description": "Do you want to setup {name} ({host})?" @@ -20,8 +20,9 @@ "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "unknown": "[%key:common::config_flow::error::unknown%]", - "bulb_time_out": "Can not connect to the bulb. Maybe the bulb is offline or a wrong IP/host was entered. Please turn on the light and try again!", - "no_wiz_light": "The bulb can not be connected via WiZ Platform integration." + "bulb_time_out": "Can not connect to the bulb. Maybe the bulb is offline or a wrong IP was entered. Please turn on the light and try again!", + "no_wiz_light": "The bulb can not be connected via WiZ Platform integration.", + "no_ip": "Not a valid IP address." }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" diff --git a/homeassistant/components/wiz/translations/en.json b/homeassistant/components/wiz/translations/en.json index 5747182a231e87..87a89822641c0d 100644 --- a/homeassistant/components/wiz/translations/en.json +++ b/homeassistant/components/wiz/translations/en.json @@ -1,20 +1,17 @@ { "config": { "abort": { - "already_configured": "Device is already configured", - "no_devices_found": "No devices found on the network" + "already_configured": "Device is already configured" }, "error": { - "bulb_time_out": "Can not connect to the bulb. Maybe the bulb is offline or a wrong IP/host was entered. Please turn on the light and try again!", + "bulb_time_out": "Can not connect to the bulb. Maybe the bulb is offline or a wrong IP was entered. Please turn on the light and try again!", "cannot_connect": "Failed to connect", + "no_ip": "Not a valid IP address.", "no_wiz_light": "The bulb can not be connected via WiZ Platform integration.", "unknown": "Unexpected error" }, "flow_title": "{name} ({host})", "step": { - "confirm": { - "description": "Do you want to start set up?" - }, "discovery_confirm": { "description": "Do you want to setup {name} ({host})?" }, @@ -25,10 +22,9 @@ }, "user": { "data": { - "host": "Host", - "name": "Name" + "host": "IP Address" }, - "description": "If you leave the host empty, discovery will be used to find devices." + "description": "If you leave the IP Address empty, discovery will be used to find devices." } } } diff --git a/tests/components/wiz/test_config_flow.py b/tests/components/wiz/test_config_flow.py index a52ca323830c71..28645e53e14cb6 100644 --- a/tests/components/wiz/test_config_flow.py +++ b/tests/components/wiz/test_config_flow.py @@ -114,6 +114,44 @@ async def test_form(hass): assert len(mock_setup_entry.mock_calls) == 1 +async def test_user_flow_enters_dns_name(hass): + """Test we reject dns names and want ips.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: "ip.only"}, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {"base": "no_ip"} + + with _patch_wizlight(), patch( + "homeassistant.components.wiz.async_setup_entry", + return_value=True, + ) as mock_setup_entry, patch( + "homeassistant.components.wiz.async_setup", return_value=True + ) as mock_setup: + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + TEST_CONNECTION, + ) + await hass.async_block_till_done() + + assert result3["type"] == "create_entry" + assert result3["title"] == "WiZ Dimmable White ABCABC" + assert result3["data"] == { + CONF_HOST: "1.1.1.1", + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + @pytest.mark.parametrize( "side_effect, error_base", [ From bd657e5dd749c44b3bb92ab9e4d88218bc30c0a4 Mon Sep 17 00:00:00 2001 From: Maximilian <43999966+DeerMaximum@users.noreply.github.com> Date: Wed, 9 Feb 2022 21:08:46 +0100 Subject: [PATCH 0476/1098] Add missing nina warnings (#66211) --- homeassistant/components/nina/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/nina/const.py b/homeassistant/components/nina/const.py index 12df703a480e84..18af5021544fa9 100644 --- a/homeassistant/components/nina/const.py +++ b/homeassistant/components/nina/const.py @@ -26,7 +26,7 @@ CONST_LIST_I_TO_L: list[str] = ["I", "J", "K", "L"] CONST_LIST_M_TO_Q: list[str] = ["M", "N", "O", "Ö", "P", "Q"] CONST_LIST_R_TO_U: list[str] = ["R", "S", "T", "U", "Ü"] -CONST_LIST_V_TO_Z: list[str] = ["V", "W", "X", "Y"] +CONST_LIST_V_TO_Z: list[str] = ["V", "W", "X", "Y", "Z"] CONST_REGION_A_TO_D: Final = "_a_to_d" CONST_REGION_E_TO_H: Final = "_e_to_h" From 3bce870c6dec3becc6ea84d5c2abe02563d56e27 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 9 Feb 2022 12:50:33 -0800 Subject: [PATCH 0477/1098] Add helper for media players to handle HA hosted media (#66120) * Sonos to sign all HASS urls * Don't sign if queries in url * Extract media player hass URL handling to helper --- homeassistant/components/cast/media_player.py | 25 +--- .../components/media_player/__init__.py | 76 +----------- .../components/media_player/browse_media.py | 112 ++++++++++++++++++ .../components/media_player/const.py | 2 + .../components/media_source/__init__.py | 5 +- homeassistant/components/roku/media_player.py | 23 +--- .../components/sonos/media_player.py | 20 +--- .../components/vlc_telnet/media_player.py | 30 ++--- .../media_player/test_browse_media.py | 60 ++++++++++ 9 files changed, 201 insertions(+), 152 deletions(-) create mode 100644 homeassistant/components/media_player/browse_media.py create mode 100644 tests/components/media_player/test_browse_media.py diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 1354c5c00fb195..29fa38fbdc65fc 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -3,10 +3,9 @@ import asyncio from contextlib import suppress -from datetime import datetime, timedelta +from datetime import datetime import json import logging -from urllib.parse import quote import pychromecast from pychromecast.controllers.homeassistant import HomeAssistantController @@ -21,11 +20,11 @@ import yarl from homeassistant.components import media_source, zeroconf -from homeassistant.components.http.auth import async_sign_path from homeassistant.components.media_player import ( BrowseError, BrowseMedia, MediaPlayerEntity, + async_process_play_media_url, ) from homeassistant.components.media_player.const import ( ATTR_MEDIA_EXTRA, @@ -582,10 +581,11 @@ async def async_play_media(self, media_type, media_id, **kwargs): return # If media ID is a relative URL, we serve it from HA. - # Create a signed path. - if media_id[0] == "/" or is_hass_url(self.hass, media_id): + media_id = async_process_play_media_url(self.hass, media_id) + + # Configure play command for when playing a HLS stream + if is_hass_url(self.hass, media_id): parsed = yarl.URL(media_id) - # Configure play command for when playing a HLS stream if parsed.path.startswith("/api/hls/"): extra = { **extra, @@ -595,19 +595,6 @@ async def async_play_media(self, media_type, media_id, **kwargs): }, } - if parsed.query: - _LOGGER.debug("Not signing path for content with query param") - else: - media_id = async_sign_path( - self.hass, - quote(media_id), - timedelta(seconds=media_source.DEFAULT_EXPIRY_TIME), - ) - - if media_id[0] == "/": - # prepend URL - media_id = f"{get_url(self.hass)}{media_id}" - # Default to play with the default media receiver app_data = {"media_id": media_id, "media_type": media_type, **extra} await self.hass.async_add_executor_job( diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 587c75dd035d70..2de42c05dde983 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -59,7 +59,6 @@ from homeassistant.helpers.config_validation import ( # noqa: F401 PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, - datetime, ) from homeassistant.helpers.entity import Entity, EntityDescription from homeassistant.helpers.entity_component import EntityComponent @@ -67,7 +66,8 @@ from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass -from .const import ( +from .browse_media import BrowseMedia, async_process_play_media_url # noqa: F401 +from .const import ( # noqa: F401 ATTR_APP_ID, ATTR_APP_NAME, ATTR_GROUP_MEMBERS, @@ -97,6 +97,7 @@ ATTR_MEDIA_VOLUME_MUTED, ATTR_SOUND_MODE, ATTR_SOUND_MODE_LIST, + CONTENT_AUTH_EXPIRY_TIME, DOMAIN, MEDIA_CLASS_DIRECTORY, REPEAT_MODES, @@ -1204,74 +1205,3 @@ async def websocket_browse_media(hass, connection, msg): _LOGGER.warning("Browse Media should use new BrowseMedia class") connection.send_result(msg["id"], payload) - - -class BrowseMedia: - """Represent a browsable media file.""" - - def __init__( - self, - *, - media_class: str, - media_content_id: str, - media_content_type: str, - title: str, - can_play: bool, - can_expand: bool, - children: list[BrowseMedia] | None = None, - children_media_class: str | None = None, - thumbnail: str | None = None, - ) -> None: - """Initialize browse media item.""" - self.media_class = media_class - self.media_content_id = media_content_id - self.media_content_type = media_content_type - self.title = title - self.can_play = can_play - self.can_expand = can_expand - self.children = children - self.children_media_class = children_media_class - self.thumbnail = thumbnail - - def as_dict(self, *, parent: bool = True) -> dict: - """Convert Media class to browse media dictionary.""" - if self.children_media_class is None: - self.calculate_children_class() - - response = { - "title": self.title, - "media_class": self.media_class, - "media_content_type": self.media_content_type, - "media_content_id": self.media_content_id, - "can_play": self.can_play, - "can_expand": self.can_expand, - "children_media_class": self.children_media_class, - "thumbnail": self.thumbnail, - } - - if not parent: - return response - - if self.children: - response["children"] = [ - child.as_dict(parent=False) for child in self.children - ] - else: - response["children"] = [] - - return response - - def calculate_children_class(self) -> None: - """Count the children media classes and calculate the correct class.""" - if self.children is None or len(self.children) == 0: - return - - self.children_media_class = MEDIA_CLASS_DIRECTORY - - proposed_class = self.children[0].media_class - if all(child.media_class == proposed_class for child in self.children): - self.children_media_class = proposed_class - - def __repr__(self): - """Return representation of browse media.""" - return f"" diff --git a/homeassistant/components/media_player/browse_media.py b/homeassistant/components/media_player/browse_media.py new file mode 100644 index 00000000000000..829c96671a9f44 --- /dev/null +++ b/homeassistant/components/media_player/browse_media.py @@ -0,0 +1,112 @@ +"""Browse media features for media player.""" +from __future__ import annotations + +from datetime import timedelta +import logging +from urllib.parse import quote + +import yarl + +from homeassistant.components.http.auth import async_sign_path +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.network import get_url, is_hass_url + +from .const import CONTENT_AUTH_EXPIRY_TIME, MEDIA_CLASS_DIRECTORY + + +@callback +def async_process_play_media_url(hass: HomeAssistant, media_content_id: str) -> str: + """Update a media URL with authentication if it points at Home Assistant.""" + if media_content_id[0] != "/" and not is_hass_url(hass, media_content_id): + return media_content_id + + parsed = yarl.URL(media_content_id) + + if parsed.query: + logging.getLogger(__name__).debug( + "Not signing path for content with query param" + ) + else: + signed_path = async_sign_path( + hass, + quote(parsed.path), + timedelta(seconds=CONTENT_AUTH_EXPIRY_TIME), + ) + media_content_id = str(parsed.join(yarl.URL(signed_path))) + + # prepend external URL + if media_content_id[0] == "/": + media_content_id = f"{get_url(hass)}{media_content_id}" + + return media_content_id + + +class BrowseMedia: + """Represent a browsable media file.""" + + def __init__( + self, + *, + media_class: str, + media_content_id: str, + media_content_type: str, + title: str, + can_play: bool, + can_expand: bool, + children: list[BrowseMedia] | None = None, + children_media_class: str | None = None, + thumbnail: str | None = None, + ) -> None: + """Initialize browse media item.""" + self.media_class = media_class + self.media_content_id = media_content_id + self.media_content_type = media_content_type + self.title = title + self.can_play = can_play + self.can_expand = can_expand + self.children = children + self.children_media_class = children_media_class + self.thumbnail = thumbnail + + def as_dict(self, *, parent: bool = True) -> dict: + """Convert Media class to browse media dictionary.""" + if self.children_media_class is None: + self.calculate_children_class() + + response = { + "title": self.title, + "media_class": self.media_class, + "media_content_type": self.media_content_type, + "media_content_id": self.media_content_id, + "can_play": self.can_play, + "can_expand": self.can_expand, + "children_media_class": self.children_media_class, + "thumbnail": self.thumbnail, + } + + if not parent: + return response + + if self.children: + response["children"] = [ + child.as_dict(parent=False) for child in self.children + ] + else: + response["children"] = [] + + return response + + def calculate_children_class(self) -> None: + """Count the children media classes and calculate the correct class.""" + if self.children is None or len(self.children) == 0: + return + + self.children_media_class = MEDIA_CLASS_DIRECTORY + + proposed_class = self.children[0].media_class + if all(child.media_class == proposed_class for child in self.children): + self.children_media_class = proposed_class + + def __repr__(self) -> str: + """Return representation of browse media.""" + return f"" diff --git a/homeassistant/components/media_player/const.py b/homeassistant/components/media_player/const.py index 67f4331aa60c43..e7b16f6ac88149 100644 --- a/homeassistant/components/media_player/const.py +++ b/homeassistant/components/media_player/const.py @@ -1,4 +1,6 @@ """Provides the constants needed for component.""" +# How long our auth signature on the content should be valid for +CONTENT_AUTH_EXPIRY_TIME = 3600 * 24 ATTR_APP_ID = "app_id" ATTR_APP_NAME = "app_name" diff --git a/homeassistant/components/media_source/__init__.py b/homeassistant/components/media_source/__init__.py index 2374eca2e6a80e..81c629529df887 100644 --- a/homeassistant/components/media_source/__init__.py +++ b/homeassistant/components/media_source/__init__.py @@ -12,6 +12,7 @@ from homeassistant.components.http.auth import async_sign_path from homeassistant.components.media_player import ( ATTR_MEDIA_CONTENT_ID, + CONTENT_AUTH_EXPIRY_TIME, BrowseError, BrowseMedia, ) @@ -28,8 +29,6 @@ from .error import MediaSourceError, Unresolvable from .models import BrowseMediaSource, MediaSourceItem, PlayMedia -DEFAULT_EXPIRY_TIME = 3600 * 24 - __all__ = [ "DOMAIN", "is_media_source_id", @@ -147,7 +146,7 @@ async def websocket_browse_media( { vol.Required("type"): "media_source/resolve_media", vol.Required(ATTR_MEDIA_CONTENT_ID): str, - vol.Optional("expires", default=DEFAULT_EXPIRY_TIME): int, + vol.Optional("expires", default=CONTENT_AUTH_EXPIRY_TIME): int, } ) @websocket_api.async_response diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index 4011a420ab19fa..48b85f5912cab5 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -4,17 +4,15 @@ import datetime as dt import logging from typing import Any -from urllib.parse import quote import voluptuous as vol -import yarl from homeassistant.components import media_source -from homeassistant.components.http.auth import async_sign_path from homeassistant.components.media_player import ( BrowseMedia, MediaPlayerDeviceClass, MediaPlayerEntity, + async_process_play_media_url, ) from homeassistant.components.media_player.const import ( ATTR_MEDIA_EXTRA, @@ -47,7 +45,6 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_platform from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.network import get_url, is_hass_url from . import roku_exception_handler from .browse_media import async_browse_media @@ -376,22 +373,8 @@ async def async_play_media( media_type = MEDIA_TYPE_URL media_id = sourced_media.url - # Sign and prefix with URL if playing a relative URL - if media_id[0] == "/" or is_hass_url(self.hass, media_id): - parsed = yarl.URL(media_id) - - if parsed.query: - _LOGGER.debug("Not signing path for content with query param") - else: - media_id = async_sign_path( - self.hass, - quote(media_id), - dt.timedelta(seconds=media_source.DEFAULT_EXPIRY_TIME), - ) - - # prepend external URL - if media_id[0] == "/": - media_id = f"{get_url(self.hass)}{media_id}" + # If media ID is a relative URL, we serve it from HA. + media_id = async_process_play_media_url(self.hass, media_id) if media_type not in PLAY_MEDIA_SUPPORTED_TYPES: _LOGGER.error( diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 5f5220cd164ee9..e7ee76070a1ecb 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -6,7 +6,6 @@ import json import logging from typing import Any -from urllib.parse import quote from soco import alarms from soco.core import ( @@ -19,8 +18,10 @@ import voluptuous as vol from homeassistant.components import media_source, spotify -from homeassistant.components.http.auth import async_sign_path -from homeassistant.components.media_player import MediaPlayerEntity +from homeassistant.components.media_player import ( + MediaPlayerEntity, + async_process_play_media_url, +) from homeassistant.components.media_player.const import ( ATTR_MEDIA_ENQUEUE, MEDIA_TYPE_ALBUM, @@ -56,7 +57,6 @@ from homeassistant.helpers import config_validation as cv, entity_platform, service from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.network import get_url from . import media_browser from .const import ( @@ -568,17 +568,7 @@ def play_media(self, media_type: str, media_id: str, **kwargs: Any) -> None: soco.play_from_queue(0) elif media_type in (MEDIA_TYPE_MUSIC, MEDIA_TYPE_TRACK): # If media ID is a relative URL, we serve it from HA. - # Create a signed path. - if media_id[0] == "/": - media_id = async_sign_path( - self.hass, - quote(media_id), - datetime.timedelta(seconds=media_source.DEFAULT_EXPIRY_TIME), - ) - - # prepend external URL - hass_url = get_url(self.hass, prefer_external=True) - media_id = f"{hass_url}{media_id}" + media_id = async_process_play_media_url(self.hass, media_id) if kwargs.get(ATTR_MEDIA_ENQUEUE): soco.add_uri_to_queue(media_id) diff --git a/homeassistant/components/vlc_telnet/media_player.py b/homeassistant/components/vlc_telnet/media_player.py index 2bca965c3ebefd..140c2b2c253a91 100644 --- a/homeassistant/components/vlc_telnet/media_player.py +++ b/homeassistant/components/vlc_telnet/media_player.py @@ -2,19 +2,20 @@ from __future__ import annotations from collections.abc import Awaitable, Callable, Coroutine -from datetime import datetime, timedelta +from datetime import datetime from functools import wraps from typing import Any, TypeVar -from urllib.parse import quote from aiovlc.client import Client from aiovlc.exceptions import AuthError, CommandError, ConnectError from typing_extensions import Concatenate, ParamSpec -import yarl from homeassistant.components import media_source -from homeassistant.components.http.auth import async_sign_path -from homeassistant.components.media_player import BrowseMedia, MediaPlayerEntity +from homeassistant.components.media_player import ( + BrowseMedia, + MediaPlayerEntity, + async_process_play_media_url, +) from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_BROWSE_MEDIA, @@ -37,7 +38,6 @@ from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.network import get_url, is_hass_url import homeassistant.util.dt as dt_util from .const import DATA_AVAILABLE, DATA_VLC, DEFAULT_NAME, DOMAIN, LOGGER @@ -315,22 +315,8 @@ async def async_play_media( f"Invalid media type {media_type}. Only {MEDIA_TYPE_MUSIC} is supported" ) - # Sign and prefix with URL if playing a relative URL - if media_id[0] == "/" or is_hass_url(self.hass, media_id): - parsed = yarl.URL(media_id) - - if parsed.query: - LOGGER.debug("Not signing path for content with query param") - else: - media_id = async_sign_path( - self.hass, - quote(media_id), - timedelta(seconds=media_source.DEFAULT_EXPIRY_TIME), - ) - - # prepend external URL - if media_id[0] == "/": - media_id = f"{get_url(self.hass)}{media_id}" + # If media ID is a relative URL, we serve it from HA. + media_id = async_process_play_media_url(self.hass, media_id) await self._vlc.add(media_id) self._state = STATE_PLAYING diff --git a/tests/components/media_player/test_browse_media.py b/tests/components/media_player/test_browse_media.py new file mode 100644 index 00000000000000..ba7a93fc3a3cee --- /dev/null +++ b/tests/components/media_player/test_browse_media.py @@ -0,0 +1,60 @@ +"""Test media browser helpers for media player.""" +from unittest.mock import Mock, patch + +import pytest + +from homeassistant.components.media_player.browse_media import ( + async_process_play_media_url, +) +from homeassistant.config import async_process_ha_core_config + + +@pytest.fixture +def mock_sign_path(): + """Mock sign path.""" + with patch( + "homeassistant.components.media_player.browse_media.async_sign_path", + side_effect=lambda _, url, _2: url + "?authSig=bla", + ): + yield + + +async def test_process_play_media_url(hass, mock_sign_path): + """Test it prefixes and signs urls.""" + await async_process_ha_core_config( + hass, + {"internal_url": "http://example.local:8123"}, + ) + hass.config.api = Mock(use_ssl=False, port=8123, local_ip="192.168.123.123") + + # Not changing a url that is not a hass url + assert ( + async_process_play_media_url(hass, "https://not-hass.com/path") + == "https://not-hass.com/path" + ) + + # Testing signing hass URLs + assert ( + async_process_play_media_url(hass, "/path") + == "http://example.local:8123/path?authSig=bla" + ) + assert ( + async_process_play_media_url(hass, "http://example.local:8123/path") + == "http://example.local:8123/path?authSig=bla" + ) + assert ( + async_process_play_media_url(hass, "http://192.168.123.123:8123/path") + == "http://192.168.123.123:8123/path?authSig=bla" + ) + + # Test skip signing URLs that have a query param + assert ( + async_process_play_media_url(hass, "/path?hello=world") + == "http://example.local:8123/path?hello=world" + ) + assert ( + async_process_play_media_url( + hass, "http://192.168.123.123:8123/path?hello=world" + ) + == "http://192.168.123.123:8123/path?hello=world" + ) From 8a09303c98e0abb847b6d12c61a647ec4ef739e9 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 9 Feb 2022 22:03:15 +0100 Subject: [PATCH 0478/1098] Extract Spotify media browsing into a module (#66175) --- .coveragerc | 2 + homeassistant/components/spotify/__init__.py | 40 +- .../components/spotify/browse_media.py | 439 ++++++++++++++++++ homeassistant/components/spotify/const.py | 17 + .../components/spotify/media_player.py | 411 +--------------- homeassistant/components/spotify/util.py | 24 + 6 files changed, 493 insertions(+), 440 deletions(-) create mode 100644 homeassistant/components/spotify/browse_media.py create mode 100644 homeassistant/components/spotify/util.py diff --git a/.coveragerc b/.coveragerc index 61dccdb2d8b242..b64c08cc1af69f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1078,8 +1078,10 @@ omit = homeassistant/components/spider/* homeassistant/components/splunk/* homeassistant/components/spotify/__init__.py + homeassistant/components/spotify/browse_media.py homeassistant/components/spotify/media_player.py homeassistant/components/spotify/system_health.py + homeassistant/components/spotify/util.py homeassistant/components/squeezebox/__init__.py homeassistant/components/squeezebox/browse_media.py homeassistant/components/squeezebox/media_player.py diff --git a/homeassistant/components/spotify/__init__.py b/homeassistant/components/spotify/__init__.py index a2a1fee50f2118..5599965a2a63cf 100644 --- a/homeassistant/components/spotify/__init__.py +++ b/homeassistant/components/spotify/__init__.py @@ -4,7 +4,6 @@ from spotipy import Spotify, SpotifyException import voluptuous as vol -from homeassistant.components.media_player import BrowseError, BrowseMedia from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_CREDENTIALS, @@ -22,15 +21,15 @@ from homeassistant.helpers.typing import ConfigType from . import config_flow +from .browse_media import async_browse_media from .const import ( DATA_SPOTIFY_CLIENT, DATA_SPOTIFY_ME, DATA_SPOTIFY_SESSION, DOMAIN, - MEDIA_PLAYER_PREFIX, SPOTIFY_SCOPES, ) -from .media_player import async_browse_media_internal +from .util import is_spotify_media_type, resolve_spotify_media_type CONFIG_SCHEMA = vol.Schema( { @@ -47,35 +46,12 @@ PLATFORMS = [Platform.MEDIA_PLAYER] -def is_spotify_media_type(media_content_type: str) -> bool: - """Return whether the media_content_type is a valid Spotify media_id.""" - return media_content_type.startswith(MEDIA_PLAYER_PREFIX) - - -def resolve_spotify_media_type(media_content_type: str) -> str: - """Return actual spotify media_content_type.""" - return media_content_type[len(MEDIA_PLAYER_PREFIX) :] - - -async def async_browse_media( - hass: HomeAssistant, - media_content_type: str, - media_content_id: str, - *, - can_play_artist: bool = True, -) -> BrowseMedia: - """Browse Spotify media.""" - if not (info := next(iter(hass.data[DOMAIN].values()), None)): - raise BrowseError("No Spotify accounts available") - return await async_browse_media_internal( - hass, - info[DATA_SPOTIFY_CLIENT], - info[DATA_SPOTIFY_SESSION], - info[DATA_SPOTIFY_ME], - media_content_type, - media_content_id, - can_play_artist=can_play_artist, - ) +__all__ = [ + "async_browse_media", + "DOMAIN", + "is_spotify_media_type", + "resolve_spotify_media_type", +] async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: diff --git a/homeassistant/components/spotify/browse_media.py b/homeassistant/components/spotify/browse_media.py new file mode 100644 index 00000000000000..0ffdfd6ace6e37 --- /dev/null +++ b/homeassistant/components/spotify/browse_media.py @@ -0,0 +1,439 @@ +"""Support for Spotify media browsing.""" +from __future__ import annotations + +from functools import partial +import logging +from typing import Any + +from spotipy import Spotify + +from homeassistant.backports.enum import StrEnum +from homeassistant.components.media_player import BrowseError, BrowseMedia +from homeassistant.components.media_player.const import ( + MEDIA_CLASS_ALBUM, + MEDIA_CLASS_ARTIST, + MEDIA_CLASS_DIRECTORY, + MEDIA_CLASS_EPISODE, + MEDIA_CLASS_GENRE, + MEDIA_CLASS_PLAYLIST, + MEDIA_CLASS_PODCAST, + MEDIA_CLASS_TRACK, + MEDIA_TYPE_ALBUM, + MEDIA_TYPE_ARTIST, + MEDIA_TYPE_EPISODE, + MEDIA_TYPE_PLAYLIST, + MEDIA_TYPE_TRACK, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session + +from .const import ( + DATA_SPOTIFY_CLIENT, + DATA_SPOTIFY_ME, + DATA_SPOTIFY_SESSION, + DOMAIN, + MEDIA_PLAYER_PREFIX, + MEDIA_TYPE_SHOW, + PLAYABLE_MEDIA_TYPES, +) +from .util import fetch_image_url + +BROWSE_LIMIT = 48 + + +_LOGGER = logging.getLogger(__name__) + + +class BrowsableMedia(StrEnum): + """Enum of browsable media.""" + + CURRENT_USER_PLAYLISTS = "current_user_playlists" + CURRENT_USER_FOLLOWED_ARTISTS = "current_user_followed_artists" + CURRENT_USER_SAVED_ALBUMS = "current_user_saved_albums" + CURRENT_USER_SAVED_TRACKS = "current_user_saved_tracks" + CURRENT_USER_SAVED_SHOWS = "current_user_saved_shows" + CURRENT_USER_RECENTLY_PLAYED = "current_user_recently_played" + CURRENT_USER_TOP_ARTISTS = "current_user_top_artists" + CURRENT_USER_TOP_TRACKS = "current_user_top_tracks" + CATEGORIES = "categories" + FEATURED_PLAYLISTS = "featured_playlists" + NEW_RELEASES = "new_releases" + + +LIBRARY_MAP = { + BrowsableMedia.CURRENT_USER_PLAYLISTS.value: "Playlists", + BrowsableMedia.CURRENT_USER_FOLLOWED_ARTISTS.value: "Artists", + BrowsableMedia.CURRENT_USER_SAVED_ALBUMS.value: "Albums", + BrowsableMedia.CURRENT_USER_SAVED_TRACKS.value: "Tracks", + BrowsableMedia.CURRENT_USER_SAVED_SHOWS.value: "Podcasts", + BrowsableMedia.CURRENT_USER_RECENTLY_PLAYED.value: "Recently played", + BrowsableMedia.CURRENT_USER_TOP_ARTISTS.value: "Top Artists", + BrowsableMedia.CURRENT_USER_TOP_TRACKS.value: "Top Tracks", + BrowsableMedia.CATEGORIES.value: "Categories", + BrowsableMedia.FEATURED_PLAYLISTS.value: "Featured Playlists", + BrowsableMedia.NEW_RELEASES.value: "New Releases", +} + +CONTENT_TYPE_MEDIA_CLASS: dict[str, Any] = { + BrowsableMedia.CURRENT_USER_PLAYLISTS.value: { + "parent": MEDIA_CLASS_DIRECTORY, + "children": MEDIA_CLASS_PLAYLIST, + }, + BrowsableMedia.CURRENT_USER_FOLLOWED_ARTISTS.value: { + "parent": MEDIA_CLASS_DIRECTORY, + "children": MEDIA_CLASS_ARTIST, + }, + BrowsableMedia.CURRENT_USER_SAVED_ALBUMS.value: { + "parent": MEDIA_CLASS_DIRECTORY, + "children": MEDIA_CLASS_ALBUM, + }, + BrowsableMedia.CURRENT_USER_SAVED_TRACKS.value: { + "parent": MEDIA_CLASS_DIRECTORY, + "children": MEDIA_CLASS_TRACK, + }, + BrowsableMedia.CURRENT_USER_SAVED_SHOWS.value: { + "parent": MEDIA_CLASS_DIRECTORY, + "children": MEDIA_CLASS_PODCAST, + }, + BrowsableMedia.CURRENT_USER_RECENTLY_PLAYED.value: { + "parent": MEDIA_CLASS_DIRECTORY, + "children": MEDIA_CLASS_TRACK, + }, + BrowsableMedia.CURRENT_USER_TOP_ARTISTS.value: { + "parent": MEDIA_CLASS_DIRECTORY, + "children": MEDIA_CLASS_ARTIST, + }, + BrowsableMedia.CURRENT_USER_TOP_TRACKS.value: { + "parent": MEDIA_CLASS_DIRECTORY, + "children": MEDIA_CLASS_TRACK, + }, + BrowsableMedia.FEATURED_PLAYLISTS.value: { + "parent": MEDIA_CLASS_DIRECTORY, + "children": MEDIA_CLASS_PLAYLIST, + }, + BrowsableMedia.CATEGORIES.value: { + "parent": MEDIA_CLASS_DIRECTORY, + "children": MEDIA_CLASS_GENRE, + }, + "category_playlists": { + "parent": MEDIA_CLASS_DIRECTORY, + "children": MEDIA_CLASS_PLAYLIST, + }, + BrowsableMedia.NEW_RELEASES.value: { + "parent": MEDIA_CLASS_DIRECTORY, + "children": MEDIA_CLASS_ALBUM, + }, + MEDIA_TYPE_PLAYLIST: { + "parent": MEDIA_CLASS_PLAYLIST, + "children": MEDIA_CLASS_TRACK, + }, + MEDIA_TYPE_ALBUM: {"parent": MEDIA_CLASS_ALBUM, "children": MEDIA_CLASS_TRACK}, + MEDIA_TYPE_ARTIST: {"parent": MEDIA_CLASS_ARTIST, "children": MEDIA_CLASS_ALBUM}, + MEDIA_TYPE_EPISODE: {"parent": MEDIA_CLASS_EPISODE, "children": None}, + MEDIA_TYPE_SHOW: {"parent": MEDIA_CLASS_PODCAST, "children": MEDIA_CLASS_EPISODE}, + MEDIA_TYPE_TRACK: {"parent": MEDIA_CLASS_TRACK, "children": None}, +} + + +class MissingMediaInformation(BrowseError): + """Missing media required information.""" + + +class UnknownMediaType(BrowseError): + """Unknown media type.""" + + +async def async_browse_media( + hass: HomeAssistant, + media_content_type: str, + media_content_id: str, + *, + can_play_artist: bool = True, +) -> BrowseMedia: + """Browse Spotify media.""" + if not (info := next(iter(hass.data[DOMAIN].values()), None)): + raise BrowseError("No Spotify accounts available") + return await async_browse_media_internal( + hass, + info[DATA_SPOTIFY_CLIENT], + info[DATA_SPOTIFY_SESSION], + info[DATA_SPOTIFY_ME], + media_content_type, + media_content_id, + can_play_artist=can_play_artist, + ) + + +async def async_browse_media_internal( + hass: HomeAssistant, + spotify: Spotify, + session: OAuth2Session, + current_user: dict[str, Any], + media_content_type: str | None, + media_content_id: str | None, + *, + can_play_artist: bool = True, +) -> BrowseMedia: + """Browse spotify media.""" + if media_content_type in (None, f"{MEDIA_PLAYER_PREFIX}library"): + return await hass.async_add_executor_job( + partial(library_payload, can_play_artist=can_play_artist) + ) + + await session.async_ensure_token_valid() + + # Strip prefix + if media_content_type: + media_content_type = media_content_type[len(MEDIA_PLAYER_PREFIX) :] + + payload = { + "media_content_type": media_content_type, + "media_content_id": media_content_id, + } + response = await hass.async_add_executor_job( + partial( + build_item_response, + spotify, + current_user, + payload, + can_play_artist=can_play_artist, + ) + ) + if response is None: + raise BrowseError(f"Media not found: {media_content_type} / {media_content_id}") + return response + + +def build_item_response( # noqa: C901 + spotify: Spotify, + user: dict[str, Any], + payload: dict[str, str | None], + *, + can_play_artist: bool, +) -> BrowseMedia | None: + """Create response payload for the provided media query.""" + media_content_type = payload["media_content_type"] + media_content_id = payload["media_content_id"] + + if media_content_type is None or media_content_id is None: + return None + + title = None + image = None + media: dict[str, Any] | None = None + items = [] + + if media_content_type == BrowsableMedia.CURRENT_USER_PLAYLISTS: + if media := spotify.current_user_playlists(limit=BROWSE_LIMIT): + items = media.get("items", []) + elif media_content_type == BrowsableMedia.CURRENT_USER_FOLLOWED_ARTISTS: + if media := spotify.current_user_followed_artists(limit=BROWSE_LIMIT): + items = media.get("artists", {}).get("items", []) + elif media_content_type == BrowsableMedia.CURRENT_USER_SAVED_ALBUMS: + if media := spotify.current_user_saved_albums(limit=BROWSE_LIMIT): + items = [item["album"] for item in media.get("items", [])] + elif media_content_type == BrowsableMedia.CURRENT_USER_SAVED_TRACKS: + if media := spotify.current_user_saved_tracks(limit=BROWSE_LIMIT): + items = [item["track"] for item in media.get("items", [])] + elif media_content_type == BrowsableMedia.CURRENT_USER_SAVED_SHOWS: + if media := spotify.current_user_saved_shows(limit=BROWSE_LIMIT): + items = [item["show"] for item in media.get("items", [])] + elif media_content_type == BrowsableMedia.CURRENT_USER_RECENTLY_PLAYED: + if media := spotify.current_user_recently_played(limit=BROWSE_LIMIT): + items = [item["track"] for item in media.get("items", [])] + elif media_content_type == BrowsableMedia.CURRENT_USER_TOP_ARTISTS: + if media := spotify.current_user_top_artists(limit=BROWSE_LIMIT): + items = media.get("items", []) + elif media_content_type == BrowsableMedia.CURRENT_USER_TOP_TRACKS: + if media := spotify.current_user_top_tracks(limit=BROWSE_LIMIT): + items = media.get("items", []) + elif media_content_type == BrowsableMedia.FEATURED_PLAYLISTS: + if media := spotify.featured_playlists( + country=user["country"], limit=BROWSE_LIMIT + ): + items = media.get("playlists", {}).get("items", []) + elif media_content_type == BrowsableMedia.CATEGORIES: + if media := spotify.categories(country=user["country"], limit=BROWSE_LIMIT): + items = media.get("categories", {}).get("items", []) + elif media_content_type == "category_playlists": + if ( + media := spotify.category_playlists( + category_id=media_content_id, + country=user["country"], + limit=BROWSE_LIMIT, + ) + ) and (category := spotify.category(media_content_id, country=user["country"])): + title = category.get("name") + image = fetch_image_url(category, key="icons") + items = media.get("playlists", {}).get("items", []) + elif media_content_type == BrowsableMedia.NEW_RELEASES: + if media := spotify.new_releases(country=user["country"], limit=BROWSE_LIMIT): + items = media.get("albums", {}).get("items", []) + elif media_content_type == MEDIA_TYPE_PLAYLIST: + if media := spotify.playlist(media_content_id): + items = [item["track"] for item in media.get("tracks", {}).get("items", [])] + elif media_content_type == MEDIA_TYPE_ALBUM: + if media := spotify.album(media_content_id): + items = media.get("tracks", {}).get("items", []) + elif media_content_type == MEDIA_TYPE_ARTIST: + if (media := spotify.artist_albums(media_content_id, limit=BROWSE_LIMIT)) and ( + artist := spotify.artist(media_content_id) + ): + title = artist.get("name") + image = fetch_image_url(artist) + items = media.get("items", []) + elif media_content_type == MEDIA_TYPE_SHOW: + if (media := spotify.show_episodes(media_content_id, limit=BROWSE_LIMIT)) and ( + show := spotify.show(media_content_id) + ): + title = show.get("name") + image = fetch_image_url(show) + items = media.get("items", []) + + if media is None: + return None + + try: + media_class = CONTENT_TYPE_MEDIA_CLASS[media_content_type] + except KeyError: + _LOGGER.debug("Unknown media type received: %s", media_content_type) + return None + + if media_content_type == BrowsableMedia.CATEGORIES: + media_item = BrowseMedia( + can_expand=True, + can_play=False, + children_media_class=media_class["children"], + media_class=media_class["parent"], + media_content_id=media_content_id, + media_content_type=f"{MEDIA_PLAYER_PREFIX}{media_content_type}", + title=LIBRARY_MAP.get(media_content_id, "Unknown"), + ) + + media_item.children = [] + for item in items: + try: + item_id = item["id"] + except KeyError: + _LOGGER.debug("Missing ID for media item: %s", item) + continue + media_item.children.append( + BrowseMedia( + can_expand=True, + can_play=False, + children_media_class=MEDIA_CLASS_TRACK, + media_class=MEDIA_CLASS_PLAYLIST, + media_content_id=item_id, + media_content_type=f"{MEDIA_PLAYER_PREFIX}category_playlists", + thumbnail=fetch_image_url(item, key="icons"), + title=item.get("name"), + ) + ) + return media_item + + if title is None: + title = LIBRARY_MAP.get(media_content_id, "Unknown") + if "name" in media: + title = media["name"] + + can_play = media_content_type in PLAYABLE_MEDIA_TYPES and ( + media_content_type != MEDIA_TYPE_ARTIST or can_play_artist + ) + + browse_media = BrowseMedia( + can_expand=True, + can_play=can_play, + children_media_class=media_class["children"], + media_class=media_class["parent"], + media_content_id=media_content_id, + media_content_type=f"{MEDIA_PLAYER_PREFIX}{media_content_type}", + thumbnail=image, + title=title, + ) + + browse_media.children = [] + for item in items: + try: + browse_media.children.append( + item_payload(item, can_play_artist=can_play_artist) + ) + except (MissingMediaInformation, UnknownMediaType): + continue + + if "images" in media: + browse_media.thumbnail = fetch_image_url(media) + + return browse_media + + +def item_payload(item: dict[str, Any], *, can_play_artist: bool) -> BrowseMedia: + """ + Create response payload for a single media item. + + Used by async_browse_media. + """ + try: + media_type = item["type"] + media_id = item["uri"] + except KeyError as err: + _LOGGER.debug("Missing type or URI for media item: %s", item) + raise MissingMediaInformation from err + + try: + media_class = CONTENT_TYPE_MEDIA_CLASS[media_type] + except KeyError as err: + _LOGGER.debug("Unknown media type received: %s", media_type) + raise UnknownMediaType from err + + can_expand = media_type not in [ + MEDIA_TYPE_TRACK, + MEDIA_TYPE_EPISODE, + ] + + can_play = media_type in PLAYABLE_MEDIA_TYPES and ( + media_type != MEDIA_TYPE_ARTIST or can_play_artist + ) + + browse_media = BrowseMedia( + can_expand=can_expand, + can_play=can_play, + children_media_class=media_class["children"], + media_class=media_class["parent"], + media_content_id=media_id, + media_content_type=f"{MEDIA_PLAYER_PREFIX}{media_type}", + title=item.get("name", "Unknown"), + ) + + if "images" in item: + browse_media.thumbnail = fetch_image_url(item) + elif MEDIA_TYPE_ALBUM in item: + browse_media.thumbnail = fetch_image_url(item[MEDIA_TYPE_ALBUM]) + + return browse_media + + +def library_payload(*, can_play_artist: bool) -> BrowseMedia: + """ + Create response payload to describe contents of a specific library. + + Used by async_browse_media. + """ + browse_media = BrowseMedia( + can_expand=True, + can_play=False, + children_media_class=MEDIA_CLASS_DIRECTORY, + media_class=MEDIA_CLASS_DIRECTORY, + media_content_id="library", + media_content_type=f"{MEDIA_PLAYER_PREFIX}library", + title="Media Library", + ) + + browse_media.children = [] + for item in [{"name": n, "type": t} for t, n in LIBRARY_MAP.items()]: + browse_media.children.append( + item_payload( + {"name": item["name"], "type": item["type"], "uri": item["type"]}, + can_play_artist=can_play_artist, + ) + ) + return browse_media diff --git a/homeassistant/components/spotify/const.py b/homeassistant/components/spotify/const.py index 7978ac8712f62a..6e54ed21ec1f2e 100644 --- a/homeassistant/components/spotify/const.py +++ b/homeassistant/components/spotify/const.py @@ -1,4 +1,11 @@ """Define constants for the Spotify integration.""" +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_ALBUM, + MEDIA_TYPE_ARTIST, + MEDIA_TYPE_EPISODE, + MEDIA_TYPE_PLAYLIST, + MEDIA_TYPE_TRACK, +) DOMAIN = "spotify" @@ -24,3 +31,13 @@ ] MEDIA_PLAYER_PREFIX = "spotify://" +MEDIA_TYPE_SHOW = "show" + +PLAYABLE_MEDIA_TYPES = [ + MEDIA_TYPE_PLAYLIST, + MEDIA_TYPE_ALBUM, + MEDIA_TYPE_ARTIST, + MEDIA_TYPE_EPISODE, + MEDIA_TYPE_SHOW, + MEDIA_TYPE_TRACK, +] diff --git a/homeassistant/components/spotify/media_player.py b/homeassistant/components/spotify/media_player.py index bdb0ea8b959345..58f581fdf8285f 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -4,7 +4,6 @@ from asyncio import run_coroutine_threadsafe import datetime as dt from datetime import timedelta -from functools import partial import logging from typing import Any @@ -12,19 +11,8 @@ from spotipy import Spotify, SpotifyException from yarl import URL -from homeassistant.backports.enum import StrEnum from homeassistant.components.media_player import BrowseMedia, MediaPlayerEntity from homeassistant.components.media_player.const import ( - MEDIA_CLASS_ALBUM, - MEDIA_CLASS_ARTIST, - MEDIA_CLASS_DIRECTORY, - MEDIA_CLASS_EPISODE, - MEDIA_CLASS_GENRE, - MEDIA_CLASS_PLAYLIST, - MEDIA_CLASS_PODCAST, - MEDIA_CLASS_TRACK, - MEDIA_TYPE_ALBUM, - MEDIA_TYPE_ARTIST, MEDIA_TYPE_EPISODE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, @@ -44,7 +32,6 @@ SUPPORT_SHUFFLE_SET, SUPPORT_VOLUME_SET, ) -from homeassistant.components.media_player.errors import BrowseError from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_ID, @@ -61,14 +48,17 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util.dt import utc_from_timestamp +from .browse_media import async_browse_media_internal from .const import ( DATA_SPOTIFY_CLIENT, DATA_SPOTIFY_ME, DATA_SPOTIFY_SESSION, DOMAIN, MEDIA_PLAYER_PREFIX, + PLAYABLE_MEDIA_TYPES, SPOTIFY_SCOPES, ) +from .util import fetch_image_url _LOGGER = logging.getLogger(__name__) @@ -98,118 +88,6 @@ value: key for key, value in REPEAT_MODE_MAPPING_TO_HA.items() } -BROWSE_LIMIT = 48 - -MEDIA_TYPE_SHOW = "show" - -PLAYABLE_MEDIA_TYPES = [ - MEDIA_TYPE_PLAYLIST, - MEDIA_TYPE_ALBUM, - MEDIA_TYPE_ARTIST, - MEDIA_TYPE_EPISODE, - MEDIA_TYPE_SHOW, - MEDIA_TYPE_TRACK, -] - - -class BrowsableMedia(StrEnum): - """Enum of browsable media.""" - - CURRENT_USER_PLAYLISTS = "current_user_playlists" - CURRENT_USER_FOLLOWED_ARTISTS = "current_user_followed_artists" - CURRENT_USER_SAVED_ALBUMS = "current_user_saved_albums" - CURRENT_USER_SAVED_TRACKS = "current_user_saved_tracks" - CURRENT_USER_SAVED_SHOWS = "current_user_saved_shows" - CURRENT_USER_RECENTLY_PLAYED = "current_user_recently_played" - CURRENT_USER_TOP_ARTISTS = "current_user_top_artists" - CURRENT_USER_TOP_TRACKS = "current_user_top_tracks" - CATEGORIES = "categories" - FEATURED_PLAYLISTS = "featured_playlists" - NEW_RELEASES = "new_releases" - - -LIBRARY_MAP = { - BrowsableMedia.CURRENT_USER_PLAYLISTS.value: "Playlists", - BrowsableMedia.CURRENT_USER_FOLLOWED_ARTISTS.value: "Artists", - BrowsableMedia.CURRENT_USER_SAVED_ALBUMS.value: "Albums", - BrowsableMedia.CURRENT_USER_SAVED_TRACKS.value: "Tracks", - BrowsableMedia.CURRENT_USER_SAVED_SHOWS.value: "Podcasts", - BrowsableMedia.CURRENT_USER_RECENTLY_PLAYED.value: "Recently played", - BrowsableMedia.CURRENT_USER_TOP_ARTISTS.value: "Top Artists", - BrowsableMedia.CURRENT_USER_TOP_TRACKS.value: "Top Tracks", - BrowsableMedia.CATEGORIES.value: "Categories", - BrowsableMedia.FEATURED_PLAYLISTS.value: "Featured Playlists", - BrowsableMedia.NEW_RELEASES.value: "New Releases", -} - -CONTENT_TYPE_MEDIA_CLASS: dict[str, Any] = { - BrowsableMedia.CURRENT_USER_PLAYLISTS.value: { - "parent": MEDIA_CLASS_DIRECTORY, - "children": MEDIA_CLASS_PLAYLIST, - }, - BrowsableMedia.CURRENT_USER_FOLLOWED_ARTISTS.value: { - "parent": MEDIA_CLASS_DIRECTORY, - "children": MEDIA_CLASS_ARTIST, - }, - BrowsableMedia.CURRENT_USER_SAVED_ALBUMS.value: { - "parent": MEDIA_CLASS_DIRECTORY, - "children": MEDIA_CLASS_ALBUM, - }, - BrowsableMedia.CURRENT_USER_SAVED_TRACKS.value: { - "parent": MEDIA_CLASS_DIRECTORY, - "children": MEDIA_CLASS_TRACK, - }, - BrowsableMedia.CURRENT_USER_SAVED_SHOWS.value: { - "parent": MEDIA_CLASS_DIRECTORY, - "children": MEDIA_CLASS_PODCAST, - }, - BrowsableMedia.CURRENT_USER_RECENTLY_PLAYED.value: { - "parent": MEDIA_CLASS_DIRECTORY, - "children": MEDIA_CLASS_TRACK, - }, - BrowsableMedia.CURRENT_USER_TOP_ARTISTS.value: { - "parent": MEDIA_CLASS_DIRECTORY, - "children": MEDIA_CLASS_ARTIST, - }, - BrowsableMedia.CURRENT_USER_TOP_TRACKS.value: { - "parent": MEDIA_CLASS_DIRECTORY, - "children": MEDIA_CLASS_TRACK, - }, - BrowsableMedia.FEATURED_PLAYLISTS.value: { - "parent": MEDIA_CLASS_DIRECTORY, - "children": MEDIA_CLASS_PLAYLIST, - }, - BrowsableMedia.CATEGORIES.value: { - "parent": MEDIA_CLASS_DIRECTORY, - "children": MEDIA_CLASS_GENRE, - }, - "category_playlists": { - "parent": MEDIA_CLASS_DIRECTORY, - "children": MEDIA_CLASS_PLAYLIST, - }, - BrowsableMedia.NEW_RELEASES.value: { - "parent": MEDIA_CLASS_DIRECTORY, - "children": MEDIA_CLASS_ALBUM, - }, - MEDIA_TYPE_PLAYLIST: { - "parent": MEDIA_CLASS_PLAYLIST, - "children": MEDIA_CLASS_TRACK, - }, - MEDIA_TYPE_ALBUM: {"parent": MEDIA_CLASS_ALBUM, "children": MEDIA_CLASS_TRACK}, - MEDIA_TYPE_ARTIST: {"parent": MEDIA_CLASS_ARTIST, "children": MEDIA_CLASS_ALBUM}, - MEDIA_TYPE_EPISODE: {"parent": MEDIA_CLASS_EPISODE, "children": None}, - MEDIA_TYPE_SHOW: {"parent": MEDIA_CLASS_PODCAST, "children": MEDIA_CLASS_EPISODE}, - MEDIA_TYPE_TRACK: {"parent": MEDIA_CLASS_TRACK, "children": None}, -} - - -class MissingMediaInformation(BrowseError): - """Missing media required information.""" - - -class UnknownMediaType(BrowseError): - """Unknown media type.""" - async def async_setup_entry( hass: HomeAssistant, @@ -572,286 +450,3 @@ async def async_browse_media( media_content_type, media_content_id, ) - - -async def async_browse_media_internal( - hass: HomeAssistant, - spotify: Spotify, - session: OAuth2Session, - current_user: dict[str, Any], - media_content_type: str | None, - media_content_id: str | None, - *, - can_play_artist: bool = True, -) -> BrowseMedia: - """Browse spotify media.""" - if media_content_type in (None, f"{MEDIA_PLAYER_PREFIX}library"): - return await hass.async_add_executor_job( - partial(library_payload, can_play_artist=can_play_artist) - ) - - await session.async_ensure_token_valid() - - # Strip prefix - if media_content_type: - media_content_type = media_content_type[len(MEDIA_PLAYER_PREFIX) :] - - payload = { - "media_content_type": media_content_type, - "media_content_id": media_content_id, - } - response = await hass.async_add_executor_job( - partial( - build_item_response, - spotify, - current_user, - payload, - can_play_artist=can_play_artist, - ) - ) - if response is None: - raise BrowseError(f"Media not found: {media_content_type} / {media_content_id}") - return response - - -def build_item_response( # noqa: C901 - spotify: Spotify, - user: dict[str, Any], - payload: dict[str, str | None], - *, - can_play_artist: bool, -) -> BrowseMedia | None: - """Create response payload for the provided media query.""" - media_content_type = payload["media_content_type"] - media_content_id = payload["media_content_id"] - - if media_content_type is None or media_content_id is None: - return None - - title = None - image = None - media: dict[str, Any] | None = None - items = [] - - if media_content_type == BrowsableMedia.CURRENT_USER_PLAYLISTS: - if media := spotify.current_user_playlists(limit=BROWSE_LIMIT): - items = media.get("items", []) - elif media_content_type == BrowsableMedia.CURRENT_USER_FOLLOWED_ARTISTS: - if media := spotify.current_user_followed_artists(limit=BROWSE_LIMIT): - items = media.get("artists", {}).get("items", []) - elif media_content_type == BrowsableMedia.CURRENT_USER_SAVED_ALBUMS: - if media := spotify.current_user_saved_albums(limit=BROWSE_LIMIT): - items = [item["album"] for item in media.get("items", [])] - elif media_content_type == BrowsableMedia.CURRENT_USER_SAVED_TRACKS: - if media := spotify.current_user_saved_tracks(limit=BROWSE_LIMIT): - items = [item["track"] for item in media.get("items", [])] - elif media_content_type == BrowsableMedia.CURRENT_USER_SAVED_SHOWS: - if media := spotify.current_user_saved_shows(limit=BROWSE_LIMIT): - items = [item["show"] for item in media.get("items", [])] - elif media_content_type == BrowsableMedia.CURRENT_USER_RECENTLY_PLAYED: - if media := spotify.current_user_recently_played(limit=BROWSE_LIMIT): - items = [item["track"] for item in media.get("items", [])] - elif media_content_type == BrowsableMedia.CURRENT_USER_TOP_ARTISTS: - if media := spotify.current_user_top_artists(limit=BROWSE_LIMIT): - items = media.get("items", []) - elif media_content_type == BrowsableMedia.CURRENT_USER_TOP_TRACKS: - if media := spotify.current_user_top_tracks(limit=BROWSE_LIMIT): - items = media.get("items", []) - elif media_content_type == BrowsableMedia.FEATURED_PLAYLISTS: - if media := spotify.featured_playlists( - country=user["country"], limit=BROWSE_LIMIT - ): - items = media.get("playlists", {}).get("items", []) - elif media_content_type == BrowsableMedia.CATEGORIES: - if media := spotify.categories(country=user["country"], limit=BROWSE_LIMIT): - items = media.get("categories", {}).get("items", []) - elif media_content_type == "category_playlists": - if ( - media := spotify.category_playlists( - category_id=media_content_id, - country=user["country"], - limit=BROWSE_LIMIT, - ) - ) and (category := spotify.category(media_content_id, country=user["country"])): - title = category.get("name") - image = fetch_image_url(category, key="icons") - items = media.get("playlists", {}).get("items", []) - elif media_content_type == BrowsableMedia.NEW_RELEASES: - if media := spotify.new_releases(country=user["country"], limit=BROWSE_LIMIT): - items = media.get("albums", {}).get("items", []) - elif media_content_type == MEDIA_TYPE_PLAYLIST: - if media := spotify.playlist(media_content_id): - items = [item["track"] for item in media.get("tracks", {}).get("items", [])] - elif media_content_type == MEDIA_TYPE_ALBUM: - if media := spotify.album(media_content_id): - items = media.get("tracks", {}).get("items", []) - elif media_content_type == MEDIA_TYPE_ARTIST: - if (media := spotify.artist_albums(media_content_id, limit=BROWSE_LIMIT)) and ( - artist := spotify.artist(media_content_id) - ): - title = artist.get("name") - image = fetch_image_url(artist) - items = media.get("items", []) - elif media_content_type == MEDIA_TYPE_SHOW: - if (media := spotify.show_episodes(media_content_id, limit=BROWSE_LIMIT)) and ( - show := spotify.show(media_content_id) - ): - title = show.get("name") - image = fetch_image_url(show) - items = media.get("items", []) - - if media is None: - return None - - try: - media_class = CONTENT_TYPE_MEDIA_CLASS[media_content_type] - except KeyError: - _LOGGER.debug("Unknown media type received: %s", media_content_type) - return None - - if media_content_type == BrowsableMedia.CATEGORIES: - media_item = BrowseMedia( - can_expand=True, - can_play=False, - children_media_class=media_class["children"], - media_class=media_class["parent"], - media_content_id=media_content_id, - media_content_type=f"{MEDIA_PLAYER_PREFIX}{media_content_type}", - title=LIBRARY_MAP.get(media_content_id, "Unknown"), - ) - - media_item.children = [] - for item in items: - try: - item_id = item["id"] - except KeyError: - _LOGGER.debug("Missing ID for media item: %s", item) - continue - media_item.children.append( - BrowseMedia( - can_expand=True, - can_play=False, - children_media_class=MEDIA_CLASS_TRACK, - media_class=MEDIA_CLASS_PLAYLIST, - media_content_id=item_id, - media_content_type=f"{MEDIA_PLAYER_PREFIX}category_playlists", - thumbnail=fetch_image_url(item, key="icons"), - title=item.get("name"), - ) - ) - return media_item - - if title is None: - title = LIBRARY_MAP.get(media_content_id, "Unknown") - if "name" in media: - title = media["name"] - - can_play = media_content_type in PLAYABLE_MEDIA_TYPES and ( - media_content_type != MEDIA_TYPE_ARTIST or can_play_artist - ) - - browse_media = BrowseMedia( - can_expand=True, - can_play=can_play, - children_media_class=media_class["children"], - media_class=media_class["parent"], - media_content_id=media_content_id, - media_content_type=f"{MEDIA_PLAYER_PREFIX}{media_content_type}", - thumbnail=image, - title=title, - ) - - browse_media.children = [] - for item in items: - try: - browse_media.children.append( - item_payload(item, can_play_artist=can_play_artist) - ) - except (MissingMediaInformation, UnknownMediaType): - continue - - if "images" in media: - browse_media.thumbnail = fetch_image_url(media) - - return browse_media - - -def item_payload(item: dict[str, Any], *, can_play_artist: bool) -> BrowseMedia: - """ - Create response payload for a single media item. - - Used by async_browse_media. - """ - try: - media_type = item["type"] - media_id = item["uri"] - except KeyError as err: - _LOGGER.debug("Missing type or URI for media item: %s", item) - raise MissingMediaInformation from err - - try: - media_class = CONTENT_TYPE_MEDIA_CLASS[media_type] - except KeyError as err: - _LOGGER.debug("Unknown media type received: %s", media_type) - raise UnknownMediaType from err - - can_expand = media_type not in [ - MEDIA_TYPE_TRACK, - MEDIA_TYPE_EPISODE, - ] - - can_play = media_type in PLAYABLE_MEDIA_TYPES and ( - media_type != MEDIA_TYPE_ARTIST or can_play_artist - ) - - browse_media = BrowseMedia( - can_expand=can_expand, - can_play=can_play, - children_media_class=media_class["children"], - media_class=media_class["parent"], - media_content_id=media_id, - media_content_type=f"{MEDIA_PLAYER_PREFIX}{media_type}", - title=item.get("name", "Unknown"), - ) - - if "images" in item: - browse_media.thumbnail = fetch_image_url(item) - elif MEDIA_TYPE_ALBUM in item: - browse_media.thumbnail = fetch_image_url(item[MEDIA_TYPE_ALBUM]) - - return browse_media - - -def library_payload(*, can_play_artist: bool) -> BrowseMedia: - """ - Create response payload to describe contents of a specific library. - - Used by async_browse_media. - """ - browse_media = BrowseMedia( - can_expand=True, - can_play=False, - children_media_class=MEDIA_CLASS_DIRECTORY, - media_class=MEDIA_CLASS_DIRECTORY, - media_content_id="library", - media_content_type=f"{MEDIA_PLAYER_PREFIX}library", - title="Media Library", - ) - - browse_media.children = [] - for item in [{"name": n, "type": t} for t, n in LIBRARY_MAP.items()]: - browse_media.children.append( - item_payload( - {"name": item["name"], "type": item["type"], "uri": item["type"]}, - can_play_artist=can_play_artist, - ) - ) - return browse_media - - -def fetch_image_url(item: dict[str, Any], key="images") -> str | None: - """Fetch image url.""" - try: - return item.get(key, [])[0].get("url") - except IndexError: - return None diff --git a/homeassistant/components/spotify/util.py b/homeassistant/components/spotify/util.py new file mode 100644 index 00000000000000..cdb8e933523598 --- /dev/null +++ b/homeassistant/components/spotify/util.py @@ -0,0 +1,24 @@ +"""Utils for Spotify.""" +from __future__ import annotations + +from typing import Any + +from .const import MEDIA_PLAYER_PREFIX + + +def is_spotify_media_type(media_content_type: str) -> bool: + """Return whether the media_content_type is a valid Spotify media_id.""" + return media_content_type.startswith(MEDIA_PLAYER_PREFIX) + + +def resolve_spotify_media_type(media_content_type: str) -> str: + """Return actual spotify media_content_type.""" + return media_content_type[len(MEDIA_PLAYER_PREFIX) :] + + +def fetch_image_url(item: dict[str, Any], key="images") -> str | None: + """Fetch image url.""" + try: + return item.get(key, [])[0].get("url") + except IndexError: + return None From f4aaa981a11a06b25f8fb3314602ea6f024ad602 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Wed, 9 Feb 2022 22:19:29 +0100 Subject: [PATCH 0479/1098] Adjust coverage to include all config flows (#66193) * Adjust tradfri * Adjust huawei_lte * Adjust iqvia * Adjust wiffi * Adjust solarlog * Adjust lifx * Adjust doorbird * Adjust rachio * Adjust starline * Adjust konnected * Adjust ambient_station * Adjust tado * Adjust point * Adjust daikin * Adjust hangouts * Adjust ifttt * Adjust ios * Adjust life360 * Adjust sms * Adjust spider * Adjust upnp * Adjust hassfest --- .coveragerc | 106 +++++++++++++++++++++++++++++------- script/hassfest/coverage.py | 33 ----------- 2 files changed, 85 insertions(+), 54 deletions(-) diff --git a/.coveragerc b/.coveragerc index b64c08cc1af69f..b4b84dd4025204 100644 --- a/.coveragerc +++ b/.coveragerc @@ -52,7 +52,9 @@ omit = homeassistant/components/amazon_polly/* homeassistant/components/amberelectric/__init__.py homeassistant/components/ambiclimate/climate.py - homeassistant/components/ambient_station/* + homeassistant/components/ambient_station/__init__.py + homeassistant/components/ambient_station/binary_sensor.py + homeassistant/components/ambient_station/sensor.py homeassistant/components/amcrest/* homeassistant/components/ampio/* homeassistant/components/android_ip_webcam/* @@ -190,7 +192,10 @@ omit = homeassistant/components/crownstone/light.py homeassistant/components/cups/sensor.py homeassistant/components/currencylayer/sensor.py - homeassistant/components/daikin/* + homeassistant/components/daikin/__init__.py + homeassistant/components/daikin/climate.py + homeassistant/components/daikin/sensor.py + homeassistant/components/daikin/switch.py homeassistant/components/danfoss_air/* homeassistant/components/darksky/weather.py homeassistant/components/ddwrt/device_tracker.py @@ -223,7 +228,12 @@ omit = homeassistant/components/dnsip/sensor.py homeassistant/components/dominos/* homeassistant/components/doods/* - homeassistant/components/doorbird/* + homeassistant/components/doorbird/__init__.py + homeassistant/components/doorbird/button.py + homeassistant/components/doorbird/camera.py + homeassistant/components/doorbird/entity.py + homeassistant/components/doorbird/logbook.py + homeassistant/components/doorbird/util.py homeassistant/components/dovado/* homeassistant/components/downloader/* homeassistant/components/dsmr_reader/* @@ -439,7 +449,11 @@ omit = homeassistant/components/habitica/__init__.py homeassistant/components/habitica/const.py homeassistant/components/habitica/sensor.py - homeassistant/components/hangouts/* + homeassistant/components/hangouts/__init__.py + homeassistant/components/hangouts/hangouts_bot.py + homeassistant/components/hangouts/hangups_utils.py + homeassistant/components/hangouts/intents.py + homeassistant/components/hangouts/notify.py homeassistant/components/harman_kardon_avr/media_player.py homeassistant/components/harmony/const.py homeassistant/components/harmony/data.py @@ -480,7 +494,12 @@ omit = homeassistant/components/horizon/media_player.py homeassistant/components/hp_ilo/sensor.py homeassistant/components/htu21d/sensor.py - homeassistant/components/huawei_lte/* + homeassistant/components/huawei_lte/__init__.py + homeassistant/components/huawei_lte/binary_sensor.py + homeassistant/components/huawei_lte/device_tracker.py + homeassistant/components/huawei_lte/notify.py + homeassistant/components/huawei_lte/sensor.py + homeassistant/components/huawei_lte/switch.py homeassistant/components/hue/light.py homeassistant/components/hunterdouglas_powerview/__init__.py homeassistant/components/hunterdouglas_powerview/scene.py @@ -506,7 +525,9 @@ omit = homeassistant/components/izone/discovery.py homeassistant/components/izone/__init__.py homeassistant/components/idteck_prox/* - homeassistant/components/ifttt/* + homeassistant/components/ifttt/__init__.py + homeassistant/components/ifttt/alarm_control_panel.py + homeassistant/components/ifttt/const.py homeassistant/components/iglo/light.py homeassistant/components/ihc/* homeassistant/components/imap/sensor.py @@ -528,9 +549,12 @@ omit = homeassistant/components/intellifire/sensor.py homeassistant/components/incomfort/* homeassistant/components/intesishome/* - homeassistant/components/ios/* + homeassistant/components/ios/__init__.py + homeassistant/components/ios/notify.py + homeassistant/components/ios/sensor.py homeassistant/components/iperf3/* - homeassistant/components/iqvia/* + homeassistant/components/iqvia/__init__.py + homeassistant/components/iqvia/sensor.py homeassistant/components/irish_rail_transport/sensor.py homeassistant/components/iss/__init__.py homeassistant/components/iss/binary_sensor.py @@ -579,7 +603,10 @@ omit = homeassistant/components/kodi/const.py homeassistant/components/kodi/media_player.py homeassistant/components/kodi/notify.py - homeassistant/components/konnected/* + homeassistant/components/konnected/__init__.py + homeassistant/components/konnected/handlers.py + homeassistant/components/konnected/panel.py + homeassistant/components/konnected/switch.py homeassistant/components/kostal_plenticore/__init__.py homeassistant/components/kostal_plenticore/const.py homeassistant/components/kostal_plenticore/helper.py @@ -604,8 +631,13 @@ omit = homeassistant/components/lcn/services.py homeassistant/components/lg_netcast/media_player.py homeassistant/components/lg_soundbar/media_player.py - homeassistant/components/life360/* - homeassistant/components/lifx/* + homeassistant/components/life360/__init__.py + homeassistant/components/life360/const.py + homeassistant/components/life360/device_tracker.py + homeassistant/components/life360/helpers.py + homeassistant/components/lifx/__init__.py + homeassistant/components/lifx/const.py + homeassistant/components/lifx/light.py homeassistant/components/lifx_cloud/scene.py homeassistant/components/lightwave/* homeassistant/components/limitlessled/light.py @@ -881,7 +913,10 @@ omit = homeassistant/components/plex/media_player.py homeassistant/components/plum_lightpad/light.py homeassistant/components/pocketcasts/sensor.py - homeassistant/components/point/* + homeassistant/components/point/__init__.py + homeassistant/components/point/alarm_control_panel.py + homeassistant/components/point/binary_sensor.py + homeassistant/components/point/sensor.py homeassistant/components/poolsense/__init__.py homeassistant/components/poolsense/sensor.py homeassistant/components/poolsense/binary_sensor.py @@ -904,7 +939,12 @@ omit = homeassistant/components/qrcode/image_processing.py homeassistant/components/quantum_gateway/device_tracker.py homeassistant/components/qvr_pro/* - homeassistant/components/rachio/* + homeassistant/components/rachio/__init__.py + homeassistant/components/rachio/binary_sensor.py + homeassistant/components/rachio/device.py + homeassistant/components/rachio/entity.py + homeassistant/components/rachio/switch.py + homeassistant/components/rachio/webhooks.py homeassistant/components/radarr/sensor.py homeassistant/components/radiotherm/climate.py homeassistant/components/rainbird/* @@ -1038,7 +1078,11 @@ omit = homeassistant/components/smarthab/__init__.py homeassistant/components/smarthab/cover.py homeassistant/components/smarthab/light.py - homeassistant/components/sms/* + homeassistant/components/sms/__init__.py + homeassistant/components/sms/const.py + homeassistant/components/sms/gateway.py + homeassistant/components/sms/notify.py + homeassistant/components/sms/sensor.py homeassistant/components/smtp/notify.py homeassistant/components/snapcast/* homeassistant/components/snmp/* @@ -1047,7 +1091,8 @@ omit = homeassistant/components/solaredge/coordinator.py homeassistant/components/solaredge/sensor.py homeassistant/components/solaredge_local/sensor.py - homeassistant/components/solarlog/* + homeassistant/components/solarlog/__init__.py + homeassistant/components/solarlog/sensor.py homeassistant/components/solax/__init__.py homeassistant/components/solax/sensor.py homeassistant/components/soma/__init__.py @@ -1075,7 +1120,10 @@ omit = homeassistant/components/sonos/switch.py homeassistant/components/sony_projector/switch.py homeassistant/components/spc/* - homeassistant/components/spider/* + homeassistant/components/spider/__init__.py + homeassistant/components/spider/climate.py + homeassistant/components/spider/sensor.py + homeassistant/components/spider/switch.py homeassistant/components/splunk/* homeassistant/components/spotify/__init__.py homeassistant/components/spotify/browse_media.py @@ -1085,7 +1133,14 @@ omit = homeassistant/components/squeezebox/__init__.py homeassistant/components/squeezebox/browse_media.py homeassistant/components/squeezebox/media_player.py - homeassistant/components/starline/* + homeassistant/components/starline/__init__.py + homeassistant/components/starline/account.py + homeassistant/components/starline/binary_sensor.py + homeassistant/components/starline/device_tracker.py + homeassistant/components/starline/entity.py + homeassistant/components/starline/lock.py + homeassistant/components/starline/sensor.py + homeassistant/components/starline/switch.py homeassistant/components/starlingbank/sensor.py homeassistant/components/steam_online/sensor.py homeassistant/components/stiebel_eltron/* @@ -1135,7 +1190,12 @@ omit = homeassistant/components/system_bridge/coordinator.py homeassistant/components/system_bridge/sensor.py homeassistant/components/systemmonitor/sensor.py - homeassistant/components/tado/* + homeassistant/components/tado/__init__.py + homeassistant/components/tado/binary_sensor.py + homeassistant/components/tado/climate.py + homeassistant/components/tado/device_tracker.py + homeassistant/components/tado/sensor.py + homeassistant/components/tado/water_heater.py homeassistant/components/tank_utility/sensor.py homeassistant/components/tankerkoenig/* homeassistant/components/tapsaff/binary_sensor.py @@ -1208,7 +1268,6 @@ omit = homeassistant/components/tractive/switch.py homeassistant/components/tradfri/__init__.py homeassistant/components/tradfri/base_class.py - homeassistant/components/tradfri/config_flow.py homeassistant/components/tradfri/coordinator.py homeassistant/components/tradfri/cover.py homeassistant/components/tradfri/fan.py @@ -1257,7 +1316,9 @@ omit = homeassistant/components/upcloud/__init__.py homeassistant/components/upcloud/binary_sensor.py homeassistant/components/upcloud/switch.py - homeassistant/components/upnp/* + homeassistant/components/upnp/__init__.py + homeassistant/components/upnp/device.py + homeassistant/components/upnp/sensor.py homeassistant/components/upc_connect/* homeassistant/components/uscis/sensor.py homeassistant/components/vallox/__init__.py @@ -1326,7 +1387,10 @@ omit = homeassistant/components/waze_travel_time/__init__.py homeassistant/components/waze_travel_time/helpers.py homeassistant/components/waze_travel_time/sensor.py - homeassistant/components/wiffi/* + homeassistant/components/wiffi/__init__.py + homeassistant/components/wiffi/binary_sensor.py + homeassistant/components/wiffi/sensor.py + homeassistant/components/wiffi/wiffi_strings.py homeassistant/components/wirelesstag/* homeassistant/components/wiz/__init__.py homeassistant/components/wiz/const.py diff --git a/script/hassfest/coverage.py b/script/hassfest/coverage.py index ec0b437186e738..7c259adbfa317d 100644 --- a/script/hassfest/coverage.py +++ b/script/hassfest/coverage.py @@ -20,46 +20,13 @@ # They were violating when we introduced this check # Need to be fixed in a future PR. ALLOWED_IGNORE_VIOLATIONS = { - ("ambient_station", "config_flow.py"), - ("cast", "config_flow.py"), - ("daikin", "config_flow.py"), - ("doorbird", "config_flow.py"), ("doorbird", "logbook.py"), - ("elkm1", "config_flow.py"), ("elkm1", "scene.py"), ("fibaro", "scene.py"), - ("hangouts", "config_flow.py"), - ("harmony", "config_flow.py"), - ("huawei_lte", "config_flow.py"), - ("ifttt", "config_flow.py"), - ("ios", "config_flow.py"), - ("iqvia", "config_flow.py"), - ("konnected", "config_flow.py"), ("lcn", "scene.py"), - ("life360", "config_flow.py"), - ("lifx", "config_flow.py"), ("lutron", "scene.py"), - ("mobile_app", "config_flow.py"), - ("nest", "config_flow.py"), - ("plaato", "config_flow.py"), - ("point", "config_flow.py"), - ("rachio", "config_flow.py"), - ("sense", "config_flow.py"), - ("sms", "config_flow.py"), - ("solarlog", "config_flow.py"), - ("sonos", "config_flow.py"), - ("speedtestdotnet", "config_flow.py"), - ("spider", "config_flow.py"), - ("starline", "config_flow.py"), - ("tado", "config_flow.py"), - ("totalconnect", "config_flow.py"), - ("tradfri", "config_flow.py"), - ("tuya", "config_flow.py"), ("tuya", "scene.py"), - ("upnp", "config_flow.py"), ("velux", "scene.py"), - ("wemo", "config_flow.py"), - ("wiffi", "config_flow.py"), } From cc5bb556c82c69f02edbc08b38962227cbd32086 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Wed, 9 Feb 2022 22:20:24 +0100 Subject: [PATCH 0480/1098] Move Freebox reboot service to a button entity (#65501) * Add restart button to freebox * Add warning * restart => reboot * Add button tests Co-authored-by: epenet --- homeassistant/components/freebox/__init__.py | 10 +++ homeassistant/components/freebox/button.py | 76 ++++++++++++++++++++ homeassistant/components/freebox/const.py | 2 +- tests/components/freebox/test_button.py | 43 +++++++++++ 4 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/freebox/button.py create mode 100644 tests/components/freebox/test_button.py diff --git a/homeassistant/components/freebox/__init__.py b/homeassistant/components/freebox/__init__.py index b5deb8517bbb93..7d7bc7695cd061 100644 --- a/homeassistant/components/freebox/__init__.py +++ b/homeassistant/components/freebox/__init__.py @@ -1,5 +1,6 @@ """Support for Freebox devices (Freebox v6 and Freebox mini 4K).""" from datetime import timedelta +import logging from freebox_api.exceptions import HttpRequestError import voluptuous as vol @@ -29,6 +30,8 @@ SCAN_INTERVAL = timedelta(seconds=30) +_LOGGER = logging.getLogger(__name__) + async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Freebox integration.""" @@ -67,6 +70,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # Services async def async_reboot(call: ServiceCall) -> None: """Handle reboot service call.""" + # The Freebox reboot service has been replaced by a + # dedicated button entity and marked as deprecated + _LOGGER.warning( + "The 'freebox.reboot' service is deprecated and " + "replaced by a dedicated reboot button entity; please " + "use that entity to reboot the freebox instead" + ) await router.reboot() hass.services.async_register(DOMAIN, SERVICE_REBOOT, async_reboot) diff --git a/homeassistant/components/freebox/button.py b/homeassistant/components/freebox/button.py new file mode 100644 index 00000000000000..b3313bba9ddce1 --- /dev/null +++ b/homeassistant/components/freebox/button.py @@ -0,0 +1,76 @@ +"""Support for Freebox devices (Freebox v6 and Freebox mini 4K).""" +from __future__ import annotations + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass + +from homeassistant.components.button import ( + ButtonDeviceClass, + ButtonEntity, + ButtonEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .router import FreeboxRouter + + +@dataclass +class FreeboxButtonRequiredKeysMixin: + """Mixin for required keys.""" + + async_press: Callable[[FreeboxRouter], Awaitable] + + +@dataclass +class FreeboxButtonEntityDescription( + ButtonEntityDescription, FreeboxButtonRequiredKeysMixin +): + """Class describing Freebox button entities.""" + + +BUTTON_DESCRIPTIONS: tuple[FreeboxButtonEntityDescription, ...] = ( + FreeboxButtonEntityDescription( + key="reboot", + name="Reboot Freebox", + device_class=ButtonDeviceClass.RESTART, + async_press=lambda router: router.reboot(), + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up the buttons.""" + router: FreeboxRouter = hass.data[DOMAIN][entry.unique_id] + entities = [ + FreeboxButton(router, description) for description in BUTTON_DESCRIPTIONS + ] + async_add_entities(entities, True) + + +class FreeboxButton(ButtonEntity): + """Representation of a Freebox button.""" + + entity_description: FreeboxButtonEntityDescription + + def __init__( + self, router: FreeboxRouter, description: FreeboxButtonEntityDescription + ) -> None: + """Initialize a Freebox button.""" + self.entity_description = description + self._router = router + self._attr_unique_id = f"{router.mac} {description.name}" + + @property + def device_info(self) -> DeviceInfo: + """Return the device information.""" + return self._router.device_info + + async def async_press(self) -> None: + """Press the button.""" + await self.entity_description.async_press(self._router) diff --git a/homeassistant/components/freebox/const.py b/homeassistant/components/freebox/const.py index 77f36cf44deb29..65e5576f9d7ad8 100644 --- a/homeassistant/components/freebox/const.py +++ b/homeassistant/components/freebox/const.py @@ -17,7 +17,7 @@ } API_VERSION = "v6" -PLATFORMS = [Platform.DEVICE_TRACKER, Platform.SENSOR, Platform.SWITCH] +PLATFORMS = [Platform.BUTTON, Platform.DEVICE_TRACKER, Platform.SENSOR, Platform.SWITCH] DEFAULT_DEVICE_NAME = "Unknown device" diff --git a/tests/components/freebox/test_button.py b/tests/components/freebox/test_button.py new file mode 100644 index 00000000000000..0a6625e163af93 --- /dev/null +++ b/tests/components/freebox/test_button.py @@ -0,0 +1,43 @@ +"""Tests for the Freebox config flow.""" +from unittest.mock import Mock, patch + +from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN +from homeassistant.components.button.const import SERVICE_PRESS +from homeassistant.components.freebox.const import DOMAIN +from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_PORT +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from .const import MOCK_HOST, MOCK_PORT + +from tests.common import MockConfigEntry + + +async def test_reboot_button(hass: HomeAssistant, router: Mock): + """Test reboot button.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, + unique_id=MOCK_HOST, + ) + entry.add_to_hass(hass) + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + assert hass.config_entries.async_entries() == [entry] + + assert router.call_count == 1 + assert router().open.call_count == 1 + + with patch( + "homeassistant.components.freebox.router.FreeboxRouter.reboot" + ) as mock_service: + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + service_data={ + ATTR_ENTITY_ID: "button.reboot_freebox", + }, + blocking=True, + ) + await hass.async_block_till_done() + mock_service.assert_called_once() From 604fe3f19b153172a2caed024fec4aee5cc96f1f Mon Sep 17 00:00:00 2001 From: Kevin Stillhammer Date: Wed, 9 Feb 2022 22:29:39 +0100 Subject: [PATCH 0481/1098] add @eifinger as google_travel_time_codeowner (#66215) --- CODEOWNERS | 2 ++ homeassistant/components/google_travel_time/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index c8cc32b2d35795..64b450a14c7c50 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -353,6 +353,8 @@ tests/components/goodwe/* @mletenay @starkillerOG homeassistant/components/google_assistant/* @home-assistant/cloud tests/components/google_assistant/* @home-assistant/cloud homeassistant/components/google_cloud/* @lufton +homeassistant/components/google_travel_time/* @eifinger +tests/components/google_travel_time/* @eifinger homeassistant/components/gpsd/* @fabaff homeassistant/components/gree/* @cmroche tests/components/gree/* @cmroche diff --git a/homeassistant/components/google_travel_time/manifest.json b/homeassistant/components/google_travel_time/manifest.json index 5b353141215490..4b2693374f67c9 100644 --- a/homeassistant/components/google_travel_time/manifest.json +++ b/homeassistant/components/google_travel_time/manifest.json @@ -3,7 +3,7 @@ "name": "Google Maps Travel Time", "documentation": "https://www.home-assistant.io/integrations/google_travel_time", "requirements": ["googlemaps==2.5.1"], - "codeowners": [], + "codeowners": ["@eifinger"], "config_flow": true, "iot_class": "cloud_polling", "loggers": ["googlemaps"] From 7a1bbb06bed426582ae260399682569cc702c0b3 Mon Sep 17 00:00:00 2001 From: Kevin Stillhammer Date: Wed, 9 Feb 2022 22:30:29 +0100 Subject: [PATCH 0482/1098] add @eifinger as waze_travel_time codeowner (#66214) --- CODEOWNERS | 2 ++ homeassistant/components/waze_travel_time/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 64b450a14c7c50..1becdf7a5024c5 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1048,6 +1048,8 @@ homeassistant/components/waqi/* @andrey-git homeassistant/components/watson_tts/* @rutkai homeassistant/components/watttime/* @bachya tests/components/watttime/* @bachya +homeassistant/components/waze_travel_time/* @eifinger +tests/components/waze_travel_time/* @eifinger homeassistant/components/weather/* @fabaff tests/components/weather/* @fabaff homeassistant/components/webostv/* @bendavid @thecode diff --git a/homeassistant/components/waze_travel_time/manifest.json b/homeassistant/components/waze_travel_time/manifest.json index 1a08c5c4703549..24e52d3186e15a 100644 --- a/homeassistant/components/waze_travel_time/manifest.json +++ b/homeassistant/components/waze_travel_time/manifest.json @@ -3,7 +3,7 @@ "name": "Waze Travel Time", "documentation": "https://www.home-assistant.io/integrations/waze_travel_time", "requirements": ["WazeRouteCalculator==0.14"], - "codeowners": [], + "codeowners": ["@eifinger"], "config_flow": true, "iot_class": "cloud_polling", "loggers": ["WazeRouteCalculator"] From 47af25661051b2ea04e9c7d069c7fd3c0b10f96a Mon Sep 17 00:00:00 2001 From: jjlawren Date: Wed, 9 Feb 2022 16:55:00 -0600 Subject: [PATCH 0483/1098] Schedule activity checks when using manual hosts (#65970) --- homeassistant/components/sonos/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py index 7741ec36708fcf..71068479fe46be 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -343,7 +343,6 @@ async def setup_platforms_and_discovery(self): ) ) await self.hass.async_add_executor_job(self._manual_hosts) - return self.entry.async_on_unload( await ssdp.async_register_callback( From d7fcda01b82e1783ebe636cfb2b031e26bdcd96d Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Wed, 9 Feb 2022 15:36:56 -0800 Subject: [PATCH 0484/1098] Add siren platform to Overkiz (#65300) --- .coveragerc | 1 + homeassistant/components/overkiz/const.py | 5 +- homeassistant/components/overkiz/siren.py | 71 +++++++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/overkiz/siren.py diff --git a/.coveragerc b/.coveragerc index b4b84dd4025204..7e3bd6c1e50633 100644 --- a/.coveragerc +++ b/.coveragerc @@ -876,6 +876,7 @@ omit = homeassistant/components/overkiz/scene.py homeassistant/components/overkiz/select.py homeassistant/components/overkiz/sensor.py + homeassistant/components/overkiz/siren.py homeassistant/components/overkiz/switch.py homeassistant/components/ovo_energy/__init__.py homeassistant/components/ovo_energy/const.py diff --git a/homeassistant/components/overkiz/const.py b/homeassistant/components/overkiz/const.py index 370e56feeebb7f..60a9163df5f485 100644 --- a/homeassistant/components/overkiz/const.py +++ b/homeassistant/components/overkiz/const.py @@ -27,6 +27,7 @@ Platform.NUMBER, Platform.SCENE, Platform.SENSOR, + Platform.SIREN, Platform.SWITCH, ] @@ -36,7 +37,7 @@ ] # Used to map the Somfy widget and ui_class to the Home Assistant platform -OVERKIZ_DEVICE_TO_PLATFORM: dict[UIClass | UIWidget, Platform] = { +OVERKIZ_DEVICE_TO_PLATFORM: dict[UIClass | UIWidget, Platform | None] = { UIClass.ADJUSTABLE_SLATS_ROLLER_SHUTTER: Platform.COVER, UIClass.AWNING: Platform.COVER, UIClass.CURTAIN: Platform.COVER, @@ -51,6 +52,7 @@ UIClass.ROLLER_SHUTTER: Platform.COVER, UIClass.SCREEN: Platform.COVER, UIClass.SHUTTER: Platform.COVER, + UIClass.SIREN: Platform.SIREN, UIClass.SWIMMING_POOL: Platform.SWITCH, UIClass.SWINGING_SHUTTER: Platform.COVER, UIClass.VENETIAN_BLIND: Platform.COVER, @@ -60,6 +62,7 @@ UIWidget.RTD_INDOOR_SIREN: Platform.SWITCH, # widgetName, uiClass is Siren (not supported) UIWidget.RTD_OUTDOOR_SIREN: Platform.SWITCH, # widgetName, uiClass is Siren (not supported) UIWidget.RTS_GENERIC: Platform.COVER, # widgetName, uiClass is Generic (not supported) + UIWidget.SIREN_STATUS: None, # widgetName, uiClass is Siren (siren) UIWidget.STATELESS_ALARM_CONTROLLER: Platform.SWITCH, # widgetName, uiClass is Alarm (not supported) UIWidget.STATELESS_EXTERIOR_HEATING: Platform.SWITCH, # widgetName, uiClass is ExteriorHeatingSystem (not supported) } diff --git a/homeassistant/components/overkiz/siren.py b/homeassistant/components/overkiz/siren.py new file mode 100644 index 00000000000000..8f9d2d6167ffdd --- /dev/null +++ b/homeassistant/components/overkiz/siren.py @@ -0,0 +1,71 @@ +"""Support for Overkiz sirens.""" +from typing import Any + +from pyoverkiz.enums import OverkizState +from pyoverkiz.enums.command import OverkizCommand, OverkizCommandParam + +from homeassistant.components.siren import SirenEntity +from homeassistant.components.siren.const import ( + ATTR_DURATION, + SUPPORT_DURATION, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import HomeAssistantOverkizData +from .const import DOMAIN +from .entity import OverkizEntity + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the Overkiz sirens from a config entry.""" + data: HomeAssistantOverkizData = hass.data[DOMAIN][entry.entry_id] + + async_add_entities( + OverkizSiren(device.device_url, data.coordinator) + for device in data.platforms[Platform.SIREN] + ) + + +class OverkizSiren(OverkizEntity, SirenEntity): + """Representation an Overkiz Siren.""" + + _attr_supported_features = SUPPORT_TURN_OFF | SUPPORT_TURN_ON | SUPPORT_DURATION + + @property + def is_on(self) -> bool: + """Get whether the siren is in on state.""" + return ( + self.executor.select_state(OverkizState.CORE_ON_OFF) + == OverkizCommandParam.ON + ) + + async def async_turn_on(self, **kwargs: Any) -> None: + """Send the on command.""" + if kwargs.get(ATTR_DURATION): + duration = kwargs[ATTR_DURATION] + else: + duration = 2 * 60 # 2 minutes + + duration_in_ms = duration * 1000 + + await self.executor.async_execute_command( + # https://www.tahomalink.com/enduser-mobile-web/steer-html5-client/vendor/somfy/io/siren/const.js + OverkizCommand.RING_WITH_SINGLE_SIMPLE_SEQUENCE, + duration_in_ms, # duration + 75, # 90 seconds bip, 30 seconds silence + 2, # repeat 3 times + OverkizCommandParam.MEMORIZED_VOLUME, + ) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Send the off command.""" + await self.executor.async_execute_command(OverkizCommand.OFF) From 7d70b0a16b17f543c7284e6682370ac3acc6a815 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 10 Feb 2022 00:15:56 +0000 Subject: [PATCH 0485/1098] [ci skip] Translation update --- .../components/atag/translations/it.json | 2 +- .../deconz/translations/zh-Hans.json | 5 +++ .../dnsip/translations/zh-Hans.json | 4 ++- .../components/doorbird/translations/it.json | 2 +- .../components/elkm1/translations/bg.json | 29 +++++++++++++++++ .../components/elkm1/translations/it.json | 32 +++++++++++++++++-- .../components/fivem/translations/bg.json | 6 ++++ .../components/fivem/translations/ca.json | 6 ++-- .../components/fivem/translations/et.json | 6 ++-- .../components/fivem/translations/it.json | 22 +++++++++++++ .../components/fivem/translations/ru.json | 19 +++++++++++ .../components/fivem/translations/uk.json | 7 ++++ .../fivem/translations/zh-Hant.json | 6 ++-- .../components/homekit/translations/bg.json | 16 ++++++++++ .../homekit/translations/zh-Hans.json | 18 ++++++++++- .../translations/zh-Hans.json | 4 +-- .../translations/it.json | 4 +-- .../components/iaqualink/translations/it.json | 2 +- .../components/juicenet/translations/it.json | 2 +- .../components/melcloud/translations/it.json | 2 +- .../components/myq/translations/it.json | 2 +- .../components/nest/translations/uk.json | 2 +- .../components/netgear/translations/it.json | 2 +- .../netgear/translations/zh-Hans.json | 31 ++++++++++++++++++ .../components/nut/translations/it.json | 2 +- .../components/overkiz/translations/it.json | 1 + .../overkiz/translations/sensor.bg.json | 3 ++ .../components/person/translations/uk.json | 2 +- .../components/powerwall/translations/bg.json | 6 ++++ .../components/powerwall/translations/it.json | 4 +-- .../components/powerwall/translations/uk.json | 8 ++++- .../rtsp_to_webrtc/translations/it.json | 2 +- .../components/sms/translations/it.json | 2 +- .../components/solaredge/translations/it.json | 2 +- .../tuya/translations/select.bg.json | 12 +++++++ .../tuya/translations/sensor.it.json | 6 ++++ .../unifiprotect/translations/zh-Hans.json | 16 +++++++++- .../components/upb/translations/it.json | 2 +- .../components/venstar/translations/it.json | 2 +- .../components/wiz/translations/bg.json | 15 +++++++++ .../components/wiz/translations/ca.json | 7 ++-- .../components/wiz/translations/en.json | 9 ++++-- .../components/wiz/translations/et.json | 7 ++-- .../components/wiz/translations/id.json | 1 + .../components/wiz/translations/it.json | 21 ++++++++++++ .../components/wiz/translations/pt-BR.json | 7 ++-- .../xiaomi_miio/translations/it.json | 2 +- .../components/zwave_me/translations/bg.json | 3 ++ .../components/zwave_me/translations/it.json | 20 ++++++++++++ 49 files changed, 347 insertions(+), 46 deletions(-) create mode 100644 homeassistant/components/elkm1/translations/bg.json create mode 100644 homeassistant/components/fivem/translations/it.json create mode 100644 homeassistant/components/fivem/translations/ru.json create mode 100644 homeassistant/components/fivem/translations/uk.json create mode 100644 homeassistant/components/netgear/translations/zh-Hans.json create mode 100644 homeassistant/components/zwave_me/translations/it.json diff --git a/homeassistant/components/atag/translations/it.json b/homeassistant/components/atag/translations/it.json index 1bc473a60018c6..46fbbc3d97250b 100644 --- a/homeassistant/components/atag/translations/it.json +++ b/homeassistant/components/atag/translations/it.json @@ -13,7 +13,7 @@ "host": "Host", "port": "Porta" }, - "title": "Connettersi al dispositivo" + "title": "Connettiti al dispositivo" } } } diff --git a/homeassistant/components/deconz/translations/zh-Hans.json b/homeassistant/components/deconz/translations/zh-Hans.json index dfe8209fa1c93b..11e196e75f20e8 100644 --- a/homeassistant/components/deconz/translations/zh-Hans.json +++ b/homeassistant/components/deconz/translations/zh-Hans.json @@ -11,6 +11,11 @@ "link": { "description": "\u89e3\u9501\u60a8\u7684 deCONZ \u7f51\u5173\u4ee5\u6ce8\u518c\u5230 Home Assistant\u3002 \n\n 1. \u524d\u5f80 deCONZ \u7cfb\u7edf\u8bbe\u7f6e\n 2. \u70b9\u51fb\u201c\u89e3\u9501\u7f51\u5173\u201d\u6309\u94ae", "title": "\u8fde\u63a5 deCONZ" + }, + "manual_input": { + "data": { + "port": "\u7aef\u53e3" + } } } }, diff --git a/homeassistant/components/dnsip/translations/zh-Hans.json b/homeassistant/components/dnsip/translations/zh-Hans.json index 5ef5b36c80d795..780b8f817538a5 100644 --- a/homeassistant/components/dnsip/translations/zh-Hans.json +++ b/homeassistant/components/dnsip/translations/zh-Hans.json @@ -6,7 +6,9 @@ "step": { "user": { "data": { - "hostname": "\u8bf7\u952e\u5165\u60a8\u60f3\u8981\u6267\u884c DNS \u67e5\u8be2\u7684\u57df\u540d\u6216\u4e3b\u673a\u540d" + "hostname": "\u8bf7\u952e\u5165\u60a8\u60f3\u8981\u6267\u884c DNS \u67e5\u8be2\u7684\u57df\u540d\u6216\u4e3b\u673a\u540d", + "resolver": "IPv4 DNS \u89e3\u6790\u670d\u52a1\u5668", + "resolver_ipv6": "IPv6 DNS \u89e3\u6790\u670d\u52a1\u5668" } } } diff --git a/homeassistant/components/doorbird/translations/it.json b/homeassistant/components/doorbird/translations/it.json index 4a39ef36f3c3d8..83f1ab9c5ebfa4 100644 --- a/homeassistant/components/doorbird/translations/it.json +++ b/homeassistant/components/doorbird/translations/it.json @@ -19,7 +19,7 @@ "password": "Password", "username": "Nome utente" }, - "title": "Connetti a DoorBird" + "title": "Connettiti a DoorBird" } } }, diff --git a/homeassistant/components/elkm1/translations/bg.json b/homeassistant/components/elkm1/translations/bg.json new file mode 100644 index 00000000000000..8fd587a0640384 --- /dev/null +++ b/homeassistant/components/elkm1/translations/bg.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" + }, + "flow_title": "{mac_address} ({host})", + "step": { + "discovered_connection": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "protocol": "\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + } + }, + "manual_connection": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "protocol": "\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + } + }, + "user": { + "data": { + "device": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elkm1/translations/it.json b/homeassistant/components/elkm1/translations/it.json index b22ba8c8528783..fa05ab5d436c61 100644 --- a/homeassistant/components/elkm1/translations/it.json +++ b/homeassistant/components/elkm1/translations/it.json @@ -2,25 +2,51 @@ "config": { "abort": { "address_already_configured": "Un ElkM1 con questo indirizzo \u00e8 gi\u00e0 configurato", - "already_configured": "Un ElkM1 con questo prefisso \u00e8 gi\u00e0 configurato" + "already_configured": "Un ElkM1 con questo prefisso \u00e8 gi\u00e0 configurato", + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", + "cannot_connect": "Impossibile connettersi" }, "error": { "cannot_connect": "Impossibile connettersi", "invalid_auth": "Autenticazione non valida", "unknown": "Errore imprevisto" }, + "flow_title": "{mac_address} ({host})", "step": { + "discovered_connection": { + "data": { + "password": "Password", + "protocol": "Protocollo", + "temperature_unit": "L'unit\u00e0 di temperatura utilizzata da ElkM1.", + "username": "Nome utente" + }, + "description": "Connetti al sistema rilevato: {mac_address} ({host})", + "title": "Connettiti al controllo Elk-M1" + }, + "manual_connection": { + "data": { + "address": "L'indirizzo IP o il dominio o la porta seriale in caso di connessione tramite seriale.", + "password": "Password", + "prefix": "Un prefisso univoco (lascia vuoto se hai solo un ElkM1).", + "protocol": "Protocollo", + "temperature_unit": "L'unit\u00e0 di temperatura utilizzata da ElkM1.", + "username": "Nome utente" + }, + "description": "La stringa dell'indirizzo deve essere nel formato 'indirizzo[:porta]' per 'sicuro' e 'non-sicuro'. Esempio: '192.168.1.1'. La porta \u00e8 facoltativa e per impostazione predefinita \u00e8 2101 per 'non-sicuro' e 2601 per 'sicuro'. Per il protocollo seriale, l'indirizzo deve essere nel formato 'tty[:baud]'. Esempio: '/dev/ttyS1'. Il baud \u00e8 opzionale e il valore predefinito \u00e8 115200.", + "title": "Connettiti al controllo Elk-M1" + }, "user": { "data": { "address": "L'indirizzo IP o il dominio o la porta seriale se ci si connette tramite seriale.", + "device": "Dispositivo", "password": "Password", "prefix": "Un prefisso univoco (lascia vuoto se disponi di un solo ElkM1).", "protocol": "Protocollo", "temperature_unit": "L'unit\u00e0 di temperatura utilizzata da ElkM1.", "username": "Nome utente" }, - "description": "La stringa di indirizzi deve essere nella forma 'indirizzo[:porta]' per 'sicuro' e 'non sicuro'. Esempio: '192.168.1.1.1'. La porta \u00e8 facoltativa e il valore predefinito \u00e8 2101 per 'non sicuro' e 2601 per 'sicuro'. Per il protocollo seriale, l'indirizzo deve essere nella forma 'tty[:baud]'. Esempio: '/dev/ttyS1'. Il baud \u00e8 opzionale e il valore predefinito \u00e8 115200.", - "title": "Collegamento al controllo Elk-M1" + "description": "Scegli un sistema rilevato o \"Inserimento manuale\" se non sono stati rilevati dispositivi.", + "title": "Connettiti al controllo Elk-M1" } } } diff --git a/homeassistant/components/fivem/translations/bg.json b/homeassistant/components/fivem/translations/bg.json index d269046fe318c7..98f8a2a7d266fa 100644 --- a/homeassistant/components/fivem/translations/bg.json +++ b/homeassistant/components/fivem/translations/bg.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "already_configured": "\u0423\u0441\u043b\u0443\u0433\u0430\u0442\u0430 \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u0430" + }, + "error": { + "unknown_error": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/fivem/translations/ca.json b/homeassistant/components/fivem/translations/ca.json index dfff0f0ce9399d..8e2a192a18c027 100644 --- a/homeassistant/components/fivem/translations/ca.json +++ b/homeassistant/components/fivem/translations/ca.json @@ -1,11 +1,13 @@ { "config": { "abort": { - "already_configured": "Aquest servidor FiveM ja est\u00e0 configurat" + "already_configured": "El servei ja est\u00e0 configurat" }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3. Comprova l'amfitri\u00f3 i el port i torna-ho a provar. Assegurat que est\u00e0s utilitzant la versi\u00f3 del servidor FiveM m\u00e9s recent.", - "invalid_gamename": "L'API del joc al qual est\u00e0s intentant connectar-te no \u00e9s d'un joc FiveM." + "invalid_game_name": "L'API del joc al qual est\u00e0s intentant connectar-te no \u00e9s d'un joc FiveM.", + "invalid_gamename": "L'API del joc al qual est\u00e0s intentant connectar-te no \u00e9s d'un joc FiveM.", + "unknown_error": "Error inesperat" }, "step": { "user": { diff --git a/homeassistant/components/fivem/translations/et.json b/homeassistant/components/fivem/translations/et.json index b0c2bce602d2dc..0ca491bd2322bc 100644 --- a/homeassistant/components/fivem/translations/et.json +++ b/homeassistant/components/fivem/translations/et.json @@ -1,11 +1,13 @@ { "config": { "abort": { - "already_configured": "FiveM server on juba seadistatud" + "already_configured": "Teenus on juba seadistatud" }, "error": { "cannot_connect": "\u00dchendamine eba\u00f5nnestus. Kontrolli hosti ja porti ning proovi uuesti. Veendu, et kasutad uusimat FiveM-i serverit.", - "invalid_gamename": "M\u00e4ngu API, millega proovid \u00fchendust luua, ei ole FiveM-m\u00e4ng." + "invalid_game_name": "M\u00e4ngu API, millega proovid \u00fchendust luua, ei ole FiveM-m\u00e4ng.", + "invalid_gamename": "M\u00e4ngu API, millega proovid \u00fchendust luua, ei ole FiveM-m\u00e4ng.", + "unknown_error": "Ootamatu t\u00f5rge" }, "step": { "user": { diff --git a/homeassistant/components/fivem/translations/it.json b/homeassistant/components/fivem/translations/it.json new file mode 100644 index 00000000000000..0128d1fbecaf59 --- /dev/null +++ b/homeassistant/components/fivem/translations/it.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Connessione non riuscita. Controlla l'host e la porta e riprova. Assicurati inoltre di eseguire il server FiveM pi\u00f9 recente.", + "invalid_game_name": "L'API del gioco a cui stai tentando di connetterti non \u00e8 un gioco FiveM.", + "invalid_gamename": "L'API del gioco a cui stai tentando di connetterti non \u00e8 un gioco FiveM.", + "unknown_error": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nome", + "port": "Porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/ru.json b/homeassistant/components/fivem/translations/ru.json new file mode 100644 index 00000000000000..b7c45c6bad1629 --- /dev/null +++ b/homeassistant/components/fivem/translations/ru.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u0430 \u0441\u043b\u0443\u0436\u0431\u0430 \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant." + }, + "error": { + "unknown_error": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", + "port": "\u041f\u043e\u0440\u0442" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/uk.json b/homeassistant/components/fivem/translations/uk.json new file mode 100644 index 00000000000000..b932679af93b1e --- /dev/null +++ b/homeassistant/components/fivem/translations/uk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "unknown_error": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/zh-Hant.json b/homeassistant/components/fivem/translations/zh-Hant.json index fdc2d8b49efe8b..3b1527f7a35dbb 100644 --- a/homeassistant/components/fivem/translations/zh-Hant.json +++ b/homeassistant/components/fivem/translations/zh-Hant.json @@ -1,11 +1,13 @@ { "config": { "abort": { - "already_configured": "FiveM \u4f3a\u670d\u5668\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + "already_configured": "\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { "cannot_connect": "\u4f3a\u670d\u5668\u9023\u7dda\u5931\u6557\u3002\u8acb\u6aa2\u67e5\u4e3b\u6a5f\u7aef\u8207\u901a\u8a0a\u57e0\u5f8c\u518d\u8a66\u4e00\u6b21\u3002\u53e6\u8acb\u78ba\u8a8d\u57f7\u884c\u6700\u65b0\u7248 FiveM \u4f3a\u670d\u5668\u3002", - "invalid_gamename": "\u5617\u8a66\u9023\u7dda\u7684\u904a\u6232 API \u4e26\u975e FiveM \u904a\u6232\u3002" + "invalid_game_name": "\u5617\u8a66\u9023\u7dda\u7684\u904a\u6232 API \u4e26\u975e FiveM \u904a\u6232\u3002", + "invalid_gamename": "\u5617\u8a66\u9023\u7dda\u7684\u904a\u6232 API \u4e26\u975e FiveM \u904a\u6232\u3002", + "unknown_error": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { "user": { diff --git a/homeassistant/components/homekit/translations/bg.json b/homeassistant/components/homekit/translations/bg.json index 4e5677f124a3f7..9eb419288a87a6 100644 --- a/homeassistant/components/homekit/translations/bg.json +++ b/homeassistant/components/homekit/translations/bg.json @@ -1,8 +1,24 @@ { "options": { "step": { + "accessory": { + "data": { + "entities": "\u041e\u0431\u0435\u043a\u0442" + } + }, + "exclude": { + "data": { + "entities": "\u041e\u0431\u0435\u043a\u0442\u0438" + } + }, + "include": { + "data": { + "entities": "\u041e\u0431\u0435\u043a\u0442\u0438" + } + }, "include_exclude": { "data": { + "entities": "\u041e\u0431\u0435\u043a\u0442\u0438", "mode": "\u0420\u0435\u0436\u0438\u043c" } }, diff --git a/homeassistant/components/homekit/translations/zh-Hans.json b/homeassistant/components/homekit/translations/zh-Hans.json index 5dd3cb32bf4986..cf009baed45cbb 100644 --- a/homeassistant/components/homekit/translations/zh-Hans.json +++ b/homeassistant/components/homekit/translations/zh-Hans.json @@ -41,17 +41,33 @@ "description": "\u67e5\u627e\u6240\u6709\u652f\u6301\u539f\u751f H.264 \u63a8\u6d41\u7684\u6444\u50cf\u673a\u3002\u5982\u679c\u6444\u50cf\u673a\u8f93\u51fa\u7684\u4e0d\u662f H.264 \u6d41\uff0c\u7cfb\u7edf\u4f1a\u5c06\u89c6\u9891\u8f6c\u7801\u4e3a H.264 \u4ee5\u4f9b HomeKit \u4f7f\u7528\u3002\u8f6c\u7801\u9700\u8981\u9ad8\u6027\u80fd\u7684 CPU\uff0c\u56e0\u6b64\u5728\u5f00\u53d1\u677f\u8ba1\u7b97\u673a\u4e0a\u5f88\u96be\u5b8c\u6210\u3002", "title": "\u6444\u50cf\u673a\u914d\u7f6e" }, + "exclude": { + "data": { + "entities": "\u5b9e\u4f53" + }, + "description": "\u9664\u6392\u9664\u6307\u5b9a\u7684\u5b9e\u4f53\u548c\u5206\u7c7b\uff0c\u6240\u6709\u5728 \u201c{domains}\u201d \u4e0b\u7684\u5b9e\u4f53\u548c\u5206\u7c7b\u5c06\u88ab\u7eb3\u5165", + "title": "\u9009\u62e9\u8981\u6392\u9664\u7684\u5b9e\u4f53" + }, + "include": { + "data": { + "entities": "\u5b9e\u4f53" + }, + "description": "\u9664\u975e\u5df2\u9009\u62e9\u4e86\u6307\u5b9a\u7684\u5b9e\u4f53\uff0c\u5426\u5219\u6240\u6709\u5728 \u201c{domains}\u201d \u4e0b\u7684\u5b9e\u4f53\u5c06\u88ab\u7eb3\u5165", + "title": "\u9009\u62e9\u8981\u5305\u542b\u7684\u5b9e\u4f53" + }, "include_exclude": { "data": { "entities": "\u5b9e\u4f53", "mode": "\u6a21\u5f0f" }, - "description": "\u9009\u62e9\u8981\u5f00\u653e\u7684\u5b9e\u4f53\u3002\u5728\u9644\u4ef6\u6a21\u5f0f\u4e2d\uff0c\u53ea\u80fd\u5f00\u653e\u4e00\u4e2a\u5b9e\u4f53\u3002\u5728\u6865\u63a5\u5305\u542b\u6a21\u5f0f\u4e2d\uff0c\u5982\u679c\u4e0d\u9009\u62e9\u5305\u542b\u7684\u5b9e\u4f53\uff0c\u57df\u4e2d\u6240\u6709\u5b9e\u4f53\u90fd\u4f1a\u5f00\u653e\u3002\u5728\u6865\u63a5\u6392\u9664\u6a21\u5f0f\u4e2d\uff0c\u5982\u679c\u4e0d\u9009\u62e9\u6392\u9664\u7684\u5b9e\u4f53\uff0c\u57df\u4e2d\u6240\u6709\u5b9e\u4f53\u4e5f\u90fd\u4f1a\u5f00\u653e\u3002", + "description": "\u9009\u62e9\u8981\u5f00\u653e\u7684\u5b9e\u4f53\u3002\n\u5728\u9644\u4ef6\u6a21\u5f0f\u4e2d\uff0c\u53ea\u80fd\u5f00\u653e\u4e00\u4e2a\u5b9e\u4f53\u3002\u5728\u6865\u63a5\u5305\u542b\u6a21\u5f0f\u4e2d\uff0c\u5982\u679c\u4e0d\u9009\u62e9\u5305\u542b\u7684\u5b9e\u4f53\uff0c\u57df\u4e2d\u6240\u6709\u5b9e\u4f53\u90fd\u4f1a\u5f00\u653e\u3002\u5728\u6865\u63a5\u6392\u9664\u6a21\u5f0f\u4e2d\uff0c\u5982\u679c\u4e0d\u9009\u62e9\u6392\u9664\u7684\u5b9e\u4f53\uff0c\u57df\u4e2d\u6240\u6709\u5b9e\u4f53\u4e5f\u90fd\u4f1a\u5f00\u653e\u3002\n\u4e3a\u83b7\u5f97\u6700\u4f73\u4f53\u9a8c\uff0c\u5c06\u4f1a\u4e3a\u6bcf\u4e2a\u7535\u89c6\u5a92\u4f53\u64ad\u653e\u5668\u3001\u57fa\u4e8e\u6d3b\u52a8\u7684\u9065\u63a7\u5668\u3001\u9501\u548c\u6444\u50cf\u5934\u521b\u5efa\u4e00\u4e2a\u5355\u72ec\u7684 HomeKit \u914d\u4ef6\u3002", "title": "\u9009\u62e9\u8981\u5305\u542b\u7684\u5b9e\u4f53" }, "init": { "data": { + "domains": "\u8981\u5305\u542b\u7684\u57df\u540d", "include_domains": "\u8981\u5305\u542b\u7684\u57df", + "include_exclude_mode": "\u5305\u542b\u6a21\u5f0f", "mode": "HomeKit \u6a21\u5f0f" }, "description": "HomeKit \u53ef\u4ee5\u88ab\u914d\u7f6e\u4e3a\u5bf9\u5916\u5c55\u793a\u4e00\u4e2a\u6865\u63a5\u5668\u6216\u5355\u4e2a\u914d\u4ef6\u3002\u5728\u914d\u4ef6\u6a21\u5f0f\u4e2d\uff0c\u53ea\u80fd\u4f7f\u7528\u4e00\u4e2a\u5b9e\u4f53\u3002\u8bbe\u5907\u7c7b\u578b\u4e3a\u201c\u7535\u89c6\u201d\u7684\u5a92\u4f53\u64ad\u653e\u5668\u5fc5\u987b\u4f7f\u7528\u914d\u4ef6\u6a21\u5f0f\u624d\u80fd\u6b63\u5e38\u5de5\u4f5c\u3002\u201c\u8981\u5305\u542b\u7684\u57df\u201d\u4e2d\u7684\u5b9e\u4f53\u5c06\u5411 HomeKit \u5f00\u653e\u3002\u5728\u4e0b\u4e00\u9875\u53ef\u4ee5\u9009\u62e9\u8981\u5305\u542b\u6216\u6392\u9664\u5176\u4e2d\u7684\u54ea\u4e9b\u5b9e\u4f53\u3002", diff --git a/homeassistant/components/homekit_controller/translations/zh-Hans.json b/homeassistant/components/homekit_controller/translations/zh-Hans.json index 17f42da58053fa..7bf46c79e924a4 100644 --- a/homeassistant/components/homekit_controller/translations/zh-Hans.json +++ b/homeassistant/components/homekit_controller/translations/zh-Hans.json @@ -5,7 +5,7 @@ "already_configured": "\u914d\u4ef6\u5df2\u901a\u8fc7\u6b64\u63a7\u5236\u5668\u914d\u7f6e\u5b8c\u6210\u3002", "already_in_progress": "\u6b64\u8bbe\u5907\u7684\u914d\u7f6e\u6d41\u7a0b\u5df2\u5728\u8fdb\u884c\u4e2d\u3002", "already_paired": "\u6b64\u914d\u4ef6\u5df2\u4e0e\u53e6\u4e00\u53f0\u8bbe\u5907\u914d\u5bf9\u3002\u8bf7\u91cd\u7f6e\u914d\u4ef6\uff0c\u7136\u540e\u91cd\u8bd5\u3002", - "ignored_model": "HomeKit \u5bf9\u6b64\u8bbe\u5907\u7684\u652f\u6301\u5df2\u88ab\u963b\u6b62\uff0c\u56e0\u4e3a\u6709\u529f\u80fd\u66f4\u5b8c\u6574\u7684\u539f\u751f\u96c6\u6210\u53ef\u4ee5\u4f7f\u7528\u3002", + "ignored_model": "HomeKit \u5bf9\u6b64\u8bbe\u5907\u7684\u652f\u6301\u5df2\u88ab\u963b\u6b62\uff0c\u56e0\u4e3a\u6709\u529f\u80fd\u66f4\u5b8c\u6574\u7684\u539f\u751f\u96c6\u6210\u53ef\u4ee5\u66ff\u4ee3\u4f7f\u7528\u3002", "invalid_config_entry": "\u6b64\u8bbe\u5907\u5df2\u51c6\u5907\u597d\u914d\u5bf9\uff0c\u4f46\u662f Home Assistant \u4e2d\u5b58\u5728\u4e0e\u4e4b\u51b2\u7a81\u7684\u914d\u7f6e\uff0c\u5fc5\u987b\u5148\u5c06\u5176\u5220\u9664\u3002", "invalid_properties": "\u8bbe\u5907\u901a\u544a\u7684\u5c5e\u6027\u65e0\u6548\u3002", "no_devices": "\u6ca1\u6709\u627e\u5230\u672a\u914d\u5bf9\u7684\u8bbe\u5907" @@ -33,7 +33,7 @@ "allow_insecure_setup_codes": "\u5141\u8bb8\u4f7f\u7528\u4e0d\u5b89\u5168\u7684\u8bbe\u7f6e\u4ee3\u7801\u914d\u5bf9\u3002", "pairing_code": "\u914d\u5bf9\u4ee3\u7801" }, - "description": "HomeKit \u63a7\u5236\u5668\u4f7f\u7528\u5b89\u5168\u7684\u52a0\u5bc6\u8fde\u63a5\u901a\u8fc7\u5c40\u57df\u7f51\u4e0e\u914d\u4ef6\u76f4\u63a5\u901a\u4fe1\uff0c\u65e0\u9700\u4f7f\u7528 iCloud \u6216\u5176\u4ed6\u8f6c\u63a5\u5668\u3002\u8bf7\u8f93\u5165\u201c{name}\u201d\u7684 HomeKit \u914d\u5bf9\u4ee3\u7801\u4ee5\u4f7f\u7528\u6b64\u914d\u4ef6\u3002\u6b64\u4ee3\u7801\u4e3a 8 \u4f4d\u6570\u5b57\uff0c\u901a\u5e38\u4f4d\u4e8e\u8bbe\u5907\u672c\u4f53\u6216\u5305\u88c5\u76d2\u4e0a\u3002", + "description": "HomeKit \u63a7\u5236\u5668\u4f7f\u7528\u5b89\u5168\u52a0\u5bc6\u8fde\u63a5\uff0c\u901a\u8fc7\u5c40\u57df\u7f51\u4e0e\u914d\u4ef6\u76f4\u63a5\u901a\u4fe1\uff0c\u65e0\u9700\u4f7f\u7528 iCloud \u6216\u5176\u4ed6\u8f6c\u63a5\u5668\u3002\n\u8bf7\u8f93\u5165\u201c{name}\u201d\u7684 HomeKit \u914d\u5bf9\u4ee3\u7801\u4ee5\u4f7f\u7528\u6b64\u914d\u4ef6\u3002\u6b64\u4ee3\u7801\u4e3a 8 \u4f4d\u6570\u5b57\uff0c\u4ee3\u7801\u901a\u5e38\u53ef\u5728\u8bbe\u5907\u672c\u4f53\u6216\u5305\u88c5\u76d2\u4e0a\u627e\u5230\u3002", "title": "\u4e0e HomeKit \u914d\u4ef6\u914d\u5bf9" }, "protocol_error": { diff --git a/homeassistant/components/hunterdouglas_powerview/translations/it.json b/homeassistant/components/hunterdouglas_powerview/translations/it.json index df027146b12b96..6a5606b05f405f 100644 --- a/homeassistant/components/hunterdouglas_powerview/translations/it.json +++ b/homeassistant/components/hunterdouglas_powerview/translations/it.json @@ -11,13 +11,13 @@ "step": { "link": { "description": "Vuoi impostare {name} ({host})?", - "title": "Connettersi all'Hub PowerView" + "title": "Connettiti all'Hub PowerView" }, "user": { "data": { "host": "Indirizzo IP" }, - "title": "Collegamento al PowerView Hub" + "title": "Connettiti al PowerView Hub" } } } diff --git a/homeassistant/components/iaqualink/translations/it.json b/homeassistant/components/iaqualink/translations/it.json index 5947717e69b4c6..2337bbd42163e3 100644 --- a/homeassistant/components/iaqualink/translations/it.json +++ b/homeassistant/components/iaqualink/translations/it.json @@ -14,7 +14,7 @@ "username": "Nome utente" }, "description": "Inserisci il nome utente e la password del tuo account iAqualink.", - "title": "Collegati a iAqualink" + "title": "Connettiti a iAqualink" } } } diff --git a/homeassistant/components/juicenet/translations/it.json b/homeassistant/components/juicenet/translations/it.json index 90e3ccd3c17f5b..489f29db7a6e0f 100644 --- a/homeassistant/components/juicenet/translations/it.json +++ b/homeassistant/components/juicenet/translations/it.json @@ -14,7 +14,7 @@ "api_token": "Token API" }, "description": "Avrete bisogno del Token API da https://home.juice.net/Manage.", - "title": "Connettersi a JuiceNet" + "title": "Connettiti a JuiceNet" } } } diff --git a/homeassistant/components/melcloud/translations/it.json b/homeassistant/components/melcloud/translations/it.json index c76f2fd9cf2603..df40631e6d99dd 100644 --- a/homeassistant/components/melcloud/translations/it.json +++ b/homeassistant/components/melcloud/translations/it.json @@ -15,7 +15,7 @@ "username": "Email" }, "description": "Connettiti utilizzando il tuo account MELCloud.", - "title": "Connettersi a MELCloud" + "title": "Connettiti a MELCloud" } } } diff --git a/homeassistant/components/myq/translations/it.json b/homeassistant/components/myq/translations/it.json index 78747578d0eeb6..1d8befd56c9614 100644 --- a/homeassistant/components/myq/translations/it.json +++ b/homeassistant/components/myq/translations/it.json @@ -22,7 +22,7 @@ "password": "Password", "username": "Nome utente" }, - "title": "Connettersi al gateway MyQ" + "title": "Connettiti al gateway MyQ" } } } diff --git a/homeassistant/components/nest/translations/uk.json b/homeassistant/components/nest/translations/uk.json index 0d105c59c05fd3..9ab8349670eab9 100644 --- a/homeassistant/components/nest/translations/uk.json +++ b/homeassistant/components/nest/translations/uk.json @@ -44,7 +44,7 @@ "device_automation": { "trigger_type": { "camera_motion": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u0440\u0443\u0445", - "camera_person": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u043f\u0440\u0438\u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c \u043b\u044e\u0434\u0438\u043d\u0438", + "camera_person": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u043b\u044e\u0434\u0438\u043d\u0443", "camera_sound": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u0437\u0432\u0443\u043a", "doorbell_chime": "\u041d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u043a\u043d\u043e\u043f\u043a\u0430 \u0434\u0432\u0435\u0440\u043d\u043e\u0433\u043e \u0434\u0437\u0432\u0456\u043d\u043a\u0430" } diff --git a/homeassistant/components/netgear/translations/it.json b/homeassistant/components/netgear/translations/it.json index 8235fcc0ce9762..15237e3a16a63a 100644 --- a/homeassistant/components/netgear/translations/it.json +++ b/homeassistant/components/netgear/translations/it.json @@ -15,7 +15,7 @@ "ssl": "Utilizza un certificato SSL", "username": "Nome utente (Facoltativo)" }, - "description": "Host predefinito: {host}\nPorta predefinita: {port}\nNome utente predefinito: {username}", + "description": "Host predefinito: {host}\nNome utente predefinito: {username}", "title": "Netgear" } } diff --git a/homeassistant/components/netgear/translations/zh-Hans.json b/homeassistant/components/netgear/translations/zh-Hans.json new file mode 100644 index 00000000000000..c7296d1b565919 --- /dev/null +++ b/homeassistant/components/netgear/translations/zh-Hans.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "\u8bbe\u5907\u5df2\u88ab\u914d\u7f6e" + }, + "error": { + "config": "\u8fde\u63a5\u9519\u8bef\uff1a\u8bf7\u68c0\u67e5\u60a8\u7684\u914d\u7f6e\u662f\u5426\u6b63\u786e" + }, + "step": { + "user": { + "data": { + "host": "\u4e3b\u673a\u5730\u5740 (\u53ef\u9009)", + "password": "\u5bc6\u7801", + "port": "\u7aef\u53e3 (\u53ef\u9009)", + "ssl": "\u4f7f\u7528 SSL \u51ed\u8bc1\u767b\u5f55", + "username": "\u7528\u6237\u540d (\u53ef\u9009)" + }, + "description": "\u9ed8\u8ba4\u914d\u7f6e\uff1a\n\u9ed8\u8ba4\u4e3b\u673a\u5730\u5740: {host}\n\u9ed8\u8ba4\u7528\u6237\u540d: {username}", + "title": "\u7f51\u4ef6\u8def\u7531\u5668" + } + } + }, + "options": { + "step": { + "init": { + "description": "\u6307\u5b9a\u53ef\u9009\u8bbe\u7f6e", + "title": "\u7f51\u4ef6\u8def\u7531\u5668" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nut/translations/it.json b/homeassistant/components/nut/translations/it.json index bb4d9f9907e5fa..6b29911b28a3e9 100644 --- a/homeassistant/components/nut/translations/it.json +++ b/homeassistant/components/nut/translations/it.json @@ -28,7 +28,7 @@ "port": "Porta", "username": "Nome utente" }, - "title": "Connessione al server NUT" + "title": "Connettiti al server NUT" } } }, diff --git a/homeassistant/components/overkiz/translations/it.json b/homeassistant/components/overkiz/translations/it.json index a228f0c05062bd..00898513f1fd66 100644 --- a/homeassistant/components/overkiz/translations/it.json +++ b/homeassistant/components/overkiz/translations/it.json @@ -12,6 +12,7 @@ "too_many_requests": "Troppe richieste, riprova pi\u00f9 tardi.", "unknown": "Errore imprevisto" }, + "flow_title": "Gateway: {gateway_id}", "step": { "user": { "data": { diff --git a/homeassistant/components/overkiz/translations/sensor.bg.json b/homeassistant/components/overkiz/translations/sensor.bg.json index cb5016cc23005e..0c74eb8b640099 100644 --- a/homeassistant/components/overkiz/translations/sensor.bg.json +++ b/homeassistant/components/overkiz/translations/sensor.bg.json @@ -8,6 +8,9 @@ "temperature": "\u0422\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430", "ups": "UPS", "user": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b" + }, + "overkiz__sensor_defect": { + "low_battery": "\u0418\u0437\u0442\u043e\u0449\u0435\u043d\u0430 \u0431\u0430\u0442\u0435\u0440\u0438\u044f" } } } \ No newline at end of file diff --git a/homeassistant/components/person/translations/uk.json b/homeassistant/components/person/translations/uk.json index 5e6b186e38ccf7..ca42365d84b408 100644 --- a/homeassistant/components/person/translations/uk.json +++ b/homeassistant/components/person/translations/uk.json @@ -5,5 +5,5 @@ "not_home": "\u041d\u0435 \u0432\u0434\u043e\u043c\u0430" } }, - "title": "\u041b\u044e\u0434\u0438\u043d\u0430" + "title": "\u041e\u0441\u043e\u0431\u0430" } \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/bg.json b/homeassistant/components/powerwall/translations/bg.json index d5c10289c0928e..12186a54eec192 100644 --- a/homeassistant/components/powerwall/translations/bg.json +++ b/homeassistant/components/powerwall/translations/bg.json @@ -1,6 +1,12 @@ { "config": { + "abort": { + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" + }, "step": { + "confirm_discovery": { + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 {name} ({ip_address})?" + }, "reauth_confim": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u0430" diff --git a/homeassistant/components/powerwall/translations/it.json b/homeassistant/components/powerwall/translations/it.json index f323efbabff70c..889356fe8a17ef 100644 --- a/homeassistant/components/powerwall/translations/it.json +++ b/homeassistant/components/powerwall/translations/it.json @@ -15,7 +15,7 @@ "step": { "confirm_discovery": { "description": "Vuoi configurare {name} ({ip_address})?", - "title": "Connessione al powerwall" + "title": "Connettiti al powerwall" }, "reauth_confim": { "data": { @@ -30,7 +30,7 @@ "password": "Password" }, "description": "La password di solito \u00e8 costituita dagli ultimi 5 caratteri del numero di serie per il Backup Gateway e pu\u00f2 essere trovata nell'applicazione Tesla o dagli ultimi 5 caratteri della password trovata all'interno della porta per il Backup Gateway 2.", - "title": "Connessione al powerwall" + "title": "Connettiti al powerwall" } } } diff --git a/homeassistant/components/powerwall/translations/uk.json b/homeassistant/components/powerwall/translations/uk.json index 9b397138c5272c..cee740b4d50e10 100644 --- a/homeassistant/components/powerwall/translations/uk.json +++ b/homeassistant/components/powerwall/translations/uk.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u0426\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + "already_configured": "\u0426\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" }, "error": { "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", @@ -9,6 +10,11 @@ "wrong_version": "\u0412\u0430\u0448 Powerwall \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454 \u0432\u0435\u0440\u0441\u0456\u044e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043d\u043e\u0433\u043e \u0437\u0430\u0431\u0435\u0437\u043f\u0435\u0447\u0435\u043d\u043d\u044f, \u044f\u043a\u0430 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0440\u043e\u0437\u0433\u043b\u044f\u043d\u044c\u0442\u0435 \u043c\u043e\u0436\u043b\u0438\u0432\u0456\u0441\u0442\u044c \u043f\u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f \u0430\u0431\u043e \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u0442\u0435 \u043f\u0440\u043e \u0446\u044e \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443, \u0449\u043e\u0431 \u0457\u0457 \u043c\u043e\u0436\u043d\u0430 \u0431\u0443\u043b\u043e \u0432\u0438\u0440\u0456\u0448\u0438\u0442\u0438." }, "step": { + "reauth_confim": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + } + }, "user": { "data": { "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441\u0430" diff --git a/homeassistant/components/rtsp_to_webrtc/translations/it.json b/homeassistant/components/rtsp_to_webrtc/translations/it.json index 1247b8be42b1e9..c91e0bc34e827d 100644 --- a/homeassistant/components/rtsp_to_webrtc/translations/it.json +++ b/homeassistant/components/rtsp_to_webrtc/translations/it.json @@ -12,7 +12,7 @@ }, "step": { "hassio_confirm": { - "description": "Si desidera configurare Home Assistant per la connessione al server RTSPtoWebRTC fornito dal componente aggiuntivo: {addon}?", + "description": "Vuoi configurare Home Assistant per la connessione al server RTSPtoWebRTC fornito dal componente aggiuntivo: {addon}?", "title": "RTSPtoWebRTC tramite il componente aggiuntivo di Home Assistant" }, "user": { diff --git a/homeassistant/components/sms/translations/it.json b/homeassistant/components/sms/translations/it.json index 77098f93470ab3..9d2ac87d83318d 100644 --- a/homeassistant/components/sms/translations/it.json +++ b/homeassistant/components/sms/translations/it.json @@ -13,7 +13,7 @@ "data": { "device": "Dispositivo" }, - "title": "Connettersi al modem" + "title": "Connettiti al modem" } } } diff --git a/homeassistant/components/solaredge/translations/it.json b/homeassistant/components/solaredge/translations/it.json index 6d41904836ab44..f89c85eeaee5ad 100644 --- a/homeassistant/components/solaredge/translations/it.json +++ b/homeassistant/components/solaredge/translations/it.json @@ -5,7 +5,7 @@ }, "error": { "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", - "could_not_connect": "Impossibile connettersi all'API Solaredge", + "could_not_connect": "Impossibile connettersi all'API solaredge", "invalid_api_key": "Chiave API non valida", "site_not_active": "Il sito non \u00e8 attivo" }, diff --git a/homeassistant/components/tuya/translations/select.bg.json b/homeassistant/components/tuya/translations/select.bg.json index 28b629678947a1..12be88bbd8c72b 100644 --- a/homeassistant/components/tuya/translations/select.bg.json +++ b/homeassistant/components/tuya/translations/select.bg.json @@ -34,6 +34,18 @@ "60": "60\u00b0", "90": "90\u00b0" }, + "tuya__humidifier_level": { + "level_1": "\u041d\u0438\u0432\u043e 1", + "level_10": "\u041d\u0438\u0432\u043e 10", + "level_2": "\u041d\u0438\u0432\u043e 2", + "level_3": "\u041d\u0438\u0432\u043e 3", + "level_4": "\u041d\u0438\u0432\u043e 4", + "level_5": "\u041d\u0438\u0432\u043e 5", + "level_6": "\u041d\u0438\u0432\u043e 6", + "level_7": "\u041d\u0438\u0432\u043e 7", + "level_8": "\u041d\u0438\u0432\u043e 8", + "level_9": "\u041d\u0438\u0432\u043e 9" + }, "tuya__humidifier_spray_mode": { "auto": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e" }, diff --git a/homeassistant/components/tuya/translations/sensor.it.json b/homeassistant/components/tuya/translations/sensor.it.json index a7b7bb272dda3a..f8cc5ffcb57569 100644 --- a/homeassistant/components/tuya/translations/sensor.it.json +++ b/homeassistant/components/tuya/translations/sensor.it.json @@ -1,5 +1,11 @@ { "state": { + "tuya__air_quality": { + "good": "Buona", + "great": "Grande", + "mild": "Lieve", + "severe": "Forte" + }, "tuya__status": { "boiling_temp": "Temperatura di ebollizione", "cooling": "Raffreddamento", diff --git a/homeassistant/components/unifiprotect/translations/zh-Hans.json b/homeassistant/components/unifiprotect/translations/zh-Hans.json index 4163b736ac8201..72e385606c90f6 100644 --- a/homeassistant/components/unifiprotect/translations/zh-Hans.json +++ b/homeassistant/components/unifiprotect/translations/zh-Hans.json @@ -1,7 +1,21 @@ { "config": { "error": { - "protect_version": "\u6240\u9700\u7684\u6700\u4f4e\u7248\u672c\u4e3a v1.20.0\u3002\u8bf7\u5347\u7ea7 UniFi Protect\uff0c\u7136\u540e\u91cd\u8bd5\u3002" + "protect_version": "\u8981\u6c42\u8f6f\u4ef6\u6700\u4f4e\u7248\u672c\u4e3a v1.20.0\u3002\u8bf7\u5347\u7ea7 UniFi Protect \u540e\u91cd\u8bd5\u3002" + }, + "step": { + "discovery_confirm": { + "title": "UniFi Protect \u53d1\u73b0\u670d\u52a1" + }, + "reauth_confirm": { + "data": { + "host": "UniFi Protect \u670d\u52a1\u5668\u4e3b\u673a\u5730\u5740" + }, + "title": "UniFi Protect \u91cd\u9a8c\u8bc1" + }, + "user": { + "title": "UniFi Protect \u914d\u7f6e" + } } }, "options": { diff --git a/homeassistant/components/upb/translations/it.json b/homeassistant/components/upb/translations/it.json index 1de3cdddabdae2..e84a9319f755af 100644 --- a/homeassistant/components/upb/translations/it.json +++ b/homeassistant/components/upb/translations/it.json @@ -16,7 +16,7 @@ "protocol": "Protocollo" }, "description": "Collega un Modulo Interfaccia Powerline del Bus Universale Powerline (UPB PIM). La stringa dell'indirizzo deve essere nel formato 'indirizzo[:porta]' per 'tcp'. La porta \u00e8 facoltativa e il valore predefinito \u00e8 2101. Esempio: '192.168.1.42'. Per il protocollo seriale, l'indirizzo deve essere nella forma 'tty[:baud]'. Baud \u00e8 opzionale e il valore predefinito \u00e8 4800. Esempio: '/dev/ttyS1'.", - "title": "Collegamento a UPB PIM" + "title": "Connettiti a UPB PIM" } } } diff --git a/homeassistant/components/venstar/translations/it.json b/homeassistant/components/venstar/translations/it.json index 66b7fac78bdbb6..c5ddc019984f93 100644 --- a/homeassistant/components/venstar/translations/it.json +++ b/homeassistant/components/venstar/translations/it.json @@ -16,7 +16,7 @@ "ssl": "Utilizza un certificato SSL", "username": "Nome utente" }, - "title": "Collegati al termostato Venstar" + "title": "Connettiti al termostato Venstar" } } } diff --git a/homeassistant/components/wiz/translations/bg.json b/homeassistant/components/wiz/translations/bg.json index 54a7c553a532d0..f9a0ae7bd8c4b7 100644 --- a/homeassistant/components/wiz/translations/bg.json +++ b/homeassistant/components/wiz/translations/bg.json @@ -1,7 +1,22 @@ { "config": { + "abort": { + "already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e" + }, + "error": { + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, "flow_title": "{name} ({host})", "step": { + "discovery_confirm": { + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 {name} ({host})?" + }, + "pick_device": { + "data": { + "device": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" + } + }, "user": { "data": { "host": "\u0425\u043e\u0441\u0442", diff --git a/homeassistant/components/wiz/translations/ca.json b/homeassistant/components/wiz/translations/ca.json index 505788115c96ff..2c244b1bfc07f3 100644 --- a/homeassistant/components/wiz/translations/ca.json +++ b/homeassistant/components/wiz/translations/ca.json @@ -5,8 +5,9 @@ "no_devices_found": "No s'han trobat dispositius a la xarxa" }, "error": { - "bulb_time_out": "No s'ha pogut connectar amb la bombeta. Pot ser que la bombeta estigui desconnectada o s'hagi introdu\u00eft una IP/amfitri\u00f3 incorrecta. Enc\u00e9n el llum i torna-ho a provar.", + "bulb_time_out": "No s'ha pogut connectar amb la bombeta. Pot ser que la bombeta estigui desconnectada o s'hagi introdu\u00eft una IP incorrecta. Enc\u00e9n el llum i torna-ho a provar.", "cannot_connect": "Ha fallat la connexi\u00f3", + "no_ip": "Adre\u00e7a IP no v\u00e0lida.", "no_wiz_light": "La bombeta no es pot connectar mitjan\u00e7ant la integraci\u00f3 Plataforma WiZ.", "unknown": "Error inesperat" }, @@ -25,10 +26,10 @@ }, "user": { "data": { - "host": "Amfitri\u00f3", + "host": "Adre\u00e7a IP", "name": "Nom" }, - "description": "Si deixes l'amfitri\u00f3 buit, s'utilitzar\u00e0 el descobriment per cercar dispositius." + "description": "Si deixes l'adre\u00e7a IP buida, s'utilitzar\u00e0 el descobriment per cercar dispositius." } } } diff --git a/homeassistant/components/wiz/translations/en.json b/homeassistant/components/wiz/translations/en.json index 87a89822641c0d..ab4b7929739e02 100644 --- a/homeassistant/components/wiz/translations/en.json +++ b/homeassistant/components/wiz/translations/en.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Device is already configured" + "already_configured": "Device is already configured", + "no_devices_found": "No devices found on the network" }, "error": { "bulb_time_out": "Can not connect to the bulb. Maybe the bulb is offline or a wrong IP was entered. Please turn on the light and try again!", @@ -12,6 +13,9 @@ }, "flow_title": "{name} ({host})", "step": { + "confirm": { + "description": "Do you want to start set up?" + }, "discovery_confirm": { "description": "Do you want to setup {name} ({host})?" }, @@ -22,7 +26,8 @@ }, "user": { "data": { - "host": "IP Address" + "host": "IP Address", + "name": "Name" }, "description": "If you leave the IP Address empty, discovery will be used to find devices." } diff --git a/homeassistant/components/wiz/translations/et.json b/homeassistant/components/wiz/translations/et.json index c31366f17d367f..f398c1a49bdd65 100644 --- a/homeassistant/components/wiz/translations/et.json +++ b/homeassistant/components/wiz/translations/et.json @@ -5,8 +5,9 @@ "no_devices_found": "V\u00f5rgust seadmeid ei leitud" }, "error": { - "bulb_time_out": "Ei saa \u00fchendust pirniga. Pirn v\u00f5ib-olla v\u00f5rgu\u00fchenduseta v\u00f5i on sisestatud vale IP/host. L\u00fclita lamp sisse ja proovi uuesti!", + "bulb_time_out": "Ei saa \u00fchendust pirniga. Pirn v\u00f5ib-olla v\u00f5rgu\u00fchenduseta v\u00f5i on sisestatud vale IP aadress. L\u00fclita lamp sisse ja proovi uuesti!", "cannot_connect": "\u00dchendamine nurjus", + "no_ip": "Sobimatu IP-aadress", "no_wiz_light": "Pirni ei saa \u00fchendada WiZ Platvormi sidumise kaudu.", "unknown": "Ootamatu t\u00f5rge" }, @@ -25,10 +26,10 @@ }, "user": { "data": { - "host": "Host", + "host": "IP aadress", "name": "Nimi" }, - "description": "Kui j\u00e4tad hosti t\u00fchjaks kasutatakse seadmete leidmiseks avastamist." + "description": "Kui j\u00e4tad IP aadressi t\u00fchjaks kasutatakse seadmete leidmiseks avastamist." } } } diff --git a/homeassistant/components/wiz/translations/id.json b/homeassistant/components/wiz/translations/id.json index ffd1a983989da9..e99e8e7e14cb1b 100644 --- a/homeassistant/components/wiz/translations/id.json +++ b/homeassistant/components/wiz/translations/id.json @@ -7,6 +7,7 @@ "error": { "bulb_time_out": "Tidak dapat terhubung ke bohlam. Mungkin bohlam offline atau IP/host yang salah dimasukkan. Nyalakan lampu dan coba lagi!", "cannot_connect": "Gagal terhubung", + "no_ip": "Bukan alamat IP yang valid.", "no_wiz_light": "Bohlam tidak dapat dihubungkan melalui integrasi Platform WiZ.", "unknown": "Kesalahan yang tidak diharapkan" }, diff --git a/homeassistant/components/wiz/translations/it.json b/homeassistant/components/wiz/translations/it.json index 4ede47e2a3258c..3b0781bbc731eb 100644 --- a/homeassistant/components/wiz/translations/it.json +++ b/homeassistant/components/wiz/translations/it.json @@ -1,7 +1,21 @@ { "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "no_devices_found": "Nessun dispositivo trovato sulla rete" + }, + "error": { + "bulb_time_out": "Impossibile connettersi alla lampadina. Forse la lampadina non \u00e8 in linea o \u00e8 stato inserito un IP errato. AccendI la luce e riprova!", + "cannot_connect": "Impossibile connettersi", + "no_ip": "Non \u00e8 un indirizzo IP valido.", + "no_wiz_light": "La lampadina non pu\u00f2 essere collegata tramite l'integrazione della piattaforma WiZ.", + "unknown": "Errore imprevisto" + }, "flow_title": "{name} ({host})", "step": { + "confirm": { + "description": "Vuoi iniziare la configurazione?" + }, "discovery_confirm": { "description": "Vuoi configurare {name} ({host})?" }, @@ -9,6 +23,13 @@ "data": { "device": "Dispositivo" } + }, + "user": { + "data": { + "host": "Indirizzo IP", + "name": "Nome" + }, + "description": "Se lasci vuoto l'indirizzo IP, il rilevamento sar\u00e0 utilizzato per trovare i dispositivi." } } } diff --git a/homeassistant/components/wiz/translations/pt-BR.json b/homeassistant/components/wiz/translations/pt-BR.json index a1163f5cf02040..7d5485e354ee8c 100644 --- a/homeassistant/components/wiz/translations/pt-BR.json +++ b/homeassistant/components/wiz/translations/pt-BR.json @@ -5,8 +5,9 @@ "no_devices_found": "Nenhum dispositivo encontrado na rede" }, "error": { - "bulb_time_out": "N\u00e3o \u00e9 poss\u00edvel conectar \u00e0 l\u00e2mpada. Talvez a l\u00e2mpada esteja offline ou um IP/host errado foi inserido. Por favor, acenda a luz e tente novamente!", + "bulb_time_out": "N\u00e3o foi poss\u00edvel se conectar \u00e0 l\u00e2mpada. Talvez a l\u00e2mpada esteja offline ou um IP errado foi inserido. Por favor, acenda a luz e tente novamente!", "cannot_connect": "Falha ao conectar", + "no_ip": "N\u00e3o \u00e9 um endere\u00e7o IP v\u00e1lido.", "no_wiz_light": "A l\u00e2mpada n\u00e3o pode ser conectada via integra\u00e7\u00e3o com a plataforma WiZ.", "unknown": "Erro inesperado" }, @@ -25,10 +26,10 @@ }, "user": { "data": { - "host": "Nome do host", + "host": "Endere\u00e7o IP", "name": "Nome" }, - "description": "Se voc\u00ea deixar o host vazio, a descoberta ser\u00e1 usada para localizar dispositivos." + "description": "Se voc\u00ea deixar o endere\u00e7o IP vazio, a descoberta ser\u00e1 usada para localizar dispositivos." } } } diff --git a/homeassistant/components/xiaomi_miio/translations/it.json b/homeassistant/components/xiaomi_miio/translations/it.json index 35bdecea0822d1..b643f7df4dec05 100644 --- a/homeassistant/components/xiaomi_miio/translations/it.json +++ b/homeassistant/components/xiaomi_miio/translations/it.json @@ -52,7 +52,7 @@ "token": "Token API" }, "description": "\u00c8 necessario il Token API di 32 caratteri, vedi https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token per le istruzioni. Nota che questo Token API \u00e8 diverso dalla chiave usata dall'integrazione di Xiaomi Aqara.", - "title": "Connessione a un Xiaomi Gateway " + "title": "Connettiti a un Xiaomi Gateway " }, "manual": { "data": { diff --git a/homeassistant/components/zwave_me/translations/bg.json b/homeassistant/components/zwave_me/translations/bg.json index 02c83a6e9167b4..34c3f5b94996bf 100644 --- a/homeassistant/components/zwave_me/translations/bg.json +++ b/homeassistant/components/zwave_me/translations/bg.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/zwave_me/translations/it.json b/homeassistant/components/zwave_me/translations/it.json new file mode 100644 index 00000000000000..d60ac60d899ff9 --- /dev/null +++ b/homeassistant/components/zwave_me/translations/it.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "no_valid_uuid_set": "Nessun UUID valido impostato" + }, + "error": { + "no_valid_uuid_set": "Nessun UUID valido impostato" + }, + "step": { + "user": { + "data": { + "token": "Token", + "url": "URL" + }, + "description": "Digita l'indirizzo IP del server Z-Way e il token di accesso Z-Way. L'indirizzo IP pu\u00f2 essere preceduto da wss:// se si deve utilizzare HTTPS invece di HTTP. Per ottenere il token, vai all'interfaccia utente di Z-Way > Menu > Impostazioni > Utente > Token API. Si consiglia di creare un nuovo utente per Home Assistant e concedere l'accesso ai dispositivi che devi controllare da Home Assistant. \u00c8 anche possibile utilizzare l'accesso remoto tramite find.z-wave.me per connettere uno Z-Way remoto. Inserisci wss://find.z-wave.me nel campo IP e copia il token con ambito globale (accedi a Z-Way tramite find.z-wave.me per questo)." + } + } + } +} \ No newline at end of file From 9fd842825488d59aa4ccf6b805f1b828d72052aa Mon Sep 17 00:00:00 2001 From: jjlawren Date: Wed, 9 Feb 2022 21:56:10 -0600 Subject: [PATCH 0486/1098] Sonos lock subscription actions (#66204) * Move exception logging to helper * Wrap subscription in lock * Rewrite subscribe method to use new logging helper * Mark entitites as unavailable sooner to avoid unnecessary polls * Rename unclear method and update docstring * Move lock to unsubscribe --- homeassistant/components/sonos/speaker.py | 120 +++++++++++----------- 1 file changed, 62 insertions(+), 58 deletions(-) diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index b9844303956b28..ed530704550042 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -175,7 +175,7 @@ def __init__( # Subscriptions and events self.subscriptions_failed: bool = False self._subscriptions: list[SubscriptionBase] = [] - self._resubscription_lock: asyncio.Lock | None = None + self._subscription_lock: asyncio.Lock | None = None self._event_dispatchers: dict[str, Callable] = {} self._last_activity: float = NEVER_TIME self._last_event_cache: dict[str, Any] = {} @@ -343,8 +343,41 @@ def subscription_address(self) -> str | None: # # Subscription handling and event dispatchers # - async def async_subscribe(self) -> bool: - """Initiate event subscriptions.""" + def log_subscription_result( + self, result: Any, event: str, level: str = logging.DEBUG + ) -> None: + """Log a message if a subscription action (create/renew/stop) results in an exception.""" + if not isinstance(result, Exception): + return + + if isinstance(result, asyncio.exceptions.TimeoutError): + message = "Request timed out" + exc_info = None + else: + message = result + exc_info = result if not str(result) else None + + _LOGGER.log( + level, + "%s failed for %s: %s", + event, + self.zone_name, + message, + exc_info=exc_info, + ) + + async def async_subscribe(self) -> None: + """Initiate event subscriptions under an async lock.""" + if not self._subscription_lock: + self._subscription_lock = asyncio.Lock() + + async with self._subscription_lock: + if self._subscriptions: + return + await self._async_subscribe() + + async def _async_subscribe(self) -> None: + """Create event subscriptions.""" _LOGGER.debug("Creating subscriptions for %s", self.zone_name) # Create a polling task in case subscriptions fail or callback events do not arrive @@ -359,24 +392,15 @@ async def async_subscribe(self) -> bool: SCAN_INTERVAL, ) - try: - await self.hass.async_add_executor_job(self.set_basic_info) - - if self._subscriptions: - raise RuntimeError( - f"Attempted to attach subscriptions to player: {self.soco} " - f"when existing subscriptions exist: {self._subscriptions}" - ) - - subscriptions = [ - self._subscribe(getattr(self.soco, service), self.async_dispatch_event) - for service in SUBSCRIPTION_SERVICES - ] - await asyncio.gather(*subscriptions) - except SoCoException as ex: - _LOGGER.warning("Could not connect %s: %s", self.zone_name, ex) - return False - return True + subscriptions = [ + self._subscribe(getattr(self.soco, service), self.async_dispatch_event) + for service in SUBSCRIPTION_SERVICES + ] + results = await asyncio.gather(*subscriptions, return_exceptions=True) + for result in results: + self.log_subscription_result( + result, "Creating subscription", logging.WARNING + ) async def _subscribe( self, target: SubscriptionBase, sub_callback: Callable @@ -399,49 +423,24 @@ async def async_unsubscribe(self) -> None: return_exceptions=True, ) for result in results: - if isinstance(result, asyncio.exceptions.TimeoutError): - message = "Request timed out" - exc_info = None - elif isinstance(result, Exception): - message = result - exc_info = result if not str(result) else None - else: - continue - _LOGGER.debug( - "Unsubscribe failed for %s: %s", - self.zone_name, - message, - exc_info=exc_info, - ) + self.log_subscription_result(result, "Unsubscribe") self._subscriptions = [] @callback def async_renew_failed(self, exception: Exception) -> None: """Handle a failed subscription renewal.""" - self.hass.async_create_task(self.async_resubscribe(exception)) + self.hass.async_create_task(self._async_renew_failed(exception)) - async def async_resubscribe(self, exception: Exception) -> None: - """Attempt to resubscribe when a renewal failure is detected.""" - if not self._resubscription_lock: - self._resubscription_lock = asyncio.Lock() + async def _async_renew_failed(self, exception: Exception) -> None: + """Mark the speaker as offline after a subscription renewal failure. - async with self._resubscription_lock: - if not self.available: - return + This is to reset the state to allow a future clean subscription attempt. + """ + if not self.available: + return - if isinstance(exception, asyncio.exceptions.TimeoutError): - message = "Request timed out" - exc_info = None - else: - message = exception - exc_info = exception if not str(exception) else None - _LOGGER.warning( - "Subscription renewals for %s failed, marking unavailable: %s", - self.zone_name, - message, - exc_info=exc_info, - ) - await self.async_offline() + self.log_subscription_result(exception, "Subscription renewal", logging.WARNING) + await self.async_offline() @callback def async_dispatch_event(self, event: SonosEvent) -> None: @@ -576,17 +575,22 @@ async def async_check_activity(self, now: datetime.datetime) -> None: async def async_offline(self) -> None: """Handle removal of speaker when unavailable.""" + if not self.available: + return + self.available = False + self.async_write_entity_states() + self._share_link_plugin = None if self._poll_timer: self._poll_timer() self._poll_timer = None - await self.async_unsubscribe() + async with self._subscription_lock: + await self.async_unsubscribe() self.hass.data[DATA_SONOS].discovery_known.discard(self.soco.uid) - self.async_write_entity_states() async def async_vanished(self, reason: str) -> None: """Handle removal of speaker when marked as vanished.""" From 543b49728a8db88797e3024c7892d2f800953b59 Mon Sep 17 00:00:00 2001 From: Patrik Lindgren <21142447+ggravlingen@users.noreply.github.com> Date: Thu, 10 Feb 2022 08:19:59 +0100 Subject: [PATCH 0487/1098] Fix tradfri device name (#66219) --- homeassistant/components/tradfri/base_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/tradfri/base_class.py b/homeassistant/components/tradfri/base_class.py index af923538fb2743..8c4f483b9a83c6 100644 --- a/homeassistant/components/tradfri/base_class.py +++ b/homeassistant/components/tradfri/base_class.py @@ -84,7 +84,7 @@ def device_info(self) -> DeviceInfo: identifiers={(DOMAIN, self._device.id)}, manufacturer=info.manufacturer, model=info.model_number, - name=self._attr_name, + name=self._device.name, sw_version=info.firmware_version, via_device=(DOMAIN, self._gateway_id), ) From 243d003acc11d638feb3867410c3cbb1987520bc Mon Sep 17 00:00:00 2001 From: j-a-n Date: Thu, 10 Feb 2022 08:28:52 +0100 Subject: [PATCH 0488/1098] Add Moehlenhoff Alpha2 underfloor heating system integration (#42771) * Add Moehlenhoff Alpha2 underfloor heating system integration * isort changes * flake8 changes * Do not exclude config_flow.py * pylint changes * Add config_flow test * correct requirements_test_all.txt * more tests * Update test description * Test connection and catch TimeoutError in async_setup_entry * Add version to manifest file * Remove version from manifest file * Replace tests.async_mock.patch by unittest.mock.patch * Update moehlenhoff-alpha2 to version 1.0.1 * Update requirements for moehlenhoff-alpha2 1.0.1 * Update moehlenhoff-alpha2 to 1.0.2 * Use async_setup_platforms * Use async_unload_platforms * Separate connection and devices for each entry_id * Use async_track_time_interval to schedule updates * Check if input is valid before checking uniqueness * Move Exception handling to validate_input * Catch aiohttp.client_exceptions.ClientConnectorError * Remove translation files * Mock TimeoutError * Fix data update * Replace current callback implementation with ha dispatcher * Return False in should_poll * Remove unused argument * Remove CONNECTION_CLASS * Use _async_current_entries * Call async_schedule_update_ha_state after data update * Remove unneeded async_setup Co-authored-by: Milan Meulemans * Remove unneeded async_setup_platform Co-authored-by: Milan Meulemans * Set Schema attribute host required Co-authored-by: Milan Meulemans * Remove unused Exception class Co-authored-by: Milan Meulemans * Update manifest.json Co-authored-by: Milan Meulemans * pylint constructor return type None * Replace properties by class variables * use pass instead of return * Remove unused sync update method * remove property hvac_action * remove pass * rework exception handling * Update homeassistant/components/moehlenhoff_alpha2/config_flow.py Co-authored-by: Milan Meulemans * Correct indentation * catch Exception in validate_input * Replace HomeAssistantType with HomeAssistant * Update to moehlenhoff-alpha2 1.0.3 * Allow to switch between heating and cooling mode * Update moehlenhoff-alpha2 to version 1.0.4 * Update heatarea data after setting target temperature * Support hvac_action * Fix heatarea update with multiple bases * Update data after setting preset mode * Use custom preset modes like defined by device * Fix config flow test * Fix test_duplicate_error * Rename property to extra_state_attributes Rename property device_state_attributes to extra_state_attributes and return lowercase keys in dict. * Refactor using DataUpdateCoordinator * Remove _attr_should_poll * Raise HomeAssistantError on communication error Catch HTTPError instead of broad except and reraise as HomeAssistantError * Change DataUpdateCoordinator name to alpha2_base * Refresh coordinator before setting data * Raise ValueError on invalid heat area mode * Rename heatarea to heat_area * Set type annotation in class attribute * Move coordinator to top * Move exception handling to the coordinator * Use heat_area_id directly * Sore get_cooling() result into local var * Add explanation of status attributes and remove BLOCK_HC * Fix pylint warnings * from __future__ import annotations * Use Platform Enum * Move data handling to coordinator * Remove property extra_state_attributes * Add missing annotations * Update moehlenhoff-alpha2 to version 1.1.2 * Rework tests based on the scaffold template * Set also heat/cool/day/night temp with target temp * Remove unneeded code from tests Co-authored-by: Milan Meulemans --- .coveragerc | 3 + CODEOWNERS | 2 + .../components/moehlenhoff_alpha2/__init__.py | 159 ++++++++++++++++++ .../components/moehlenhoff_alpha2/climate.py | 131 +++++++++++++++ .../moehlenhoff_alpha2/config_flow.py | 55 ++++++ .../components/moehlenhoff_alpha2/const.py | 6 + .../moehlenhoff_alpha2/manifest.json | 11 ++ .../moehlenhoff_alpha2/strings.json | 19 +++ homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + .../components/moehlenhoff_alpha2/__init__.py | 1 + .../moehlenhoff_alpha2/test_config_flow.py | 106 ++++++++++++ 13 files changed, 500 insertions(+) create mode 100644 homeassistant/components/moehlenhoff_alpha2/__init__.py create mode 100644 homeassistant/components/moehlenhoff_alpha2/climate.py create mode 100644 homeassistant/components/moehlenhoff_alpha2/config_flow.py create mode 100644 homeassistant/components/moehlenhoff_alpha2/const.py create mode 100644 homeassistant/components/moehlenhoff_alpha2/manifest.json create mode 100644 homeassistant/components/moehlenhoff_alpha2/strings.json create mode 100644 tests/components/moehlenhoff_alpha2/__init__.py create mode 100644 tests/components/moehlenhoff_alpha2/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index 7e3bd6c1e50633..d3c5094d4b137f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -721,6 +721,9 @@ omit = homeassistant/components/mochad/* homeassistant/components/modbus/climate.py homeassistant/components/modem_callerid/sensor.py + homeassistant/components/moehlenhoff_alpha2/__init__.py + homeassistant/components/moehlenhoff_alpha2/climate.py + homeassistant/components/moehlenhoff_alpha2/const.py homeassistant/components/motion_blinds/__init__.py homeassistant/components/motion_blinds/const.py homeassistant/components/motion_blinds/cover.py diff --git a/CODEOWNERS b/CODEOWNERS index 1becdf7a5024c5..20fe7d086e01a4 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -574,6 +574,8 @@ homeassistant/components/modem_callerid/* @tkdrob tests/components/modem_callerid/* @tkdrob homeassistant/components/modern_forms/* @wonderslug tests/components/modern_forms/* @wonderslug +homeassistant/components/moehlenhoff_alpha2/* @j-a-n +tests/components/moehlenhoff_alpha2/* @j-a-n homeassistant/components/monoprice/* @etsinko @OnFreund tests/components/monoprice/* @etsinko @OnFreund homeassistant/components/moon/* @fabaff diff --git a/homeassistant/components/moehlenhoff_alpha2/__init__.py b/homeassistant/components/moehlenhoff_alpha2/__init__.py new file mode 100644 index 00000000000000..62e18917dc6030 --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/__init__.py @@ -0,0 +1,159 @@ +"""Support for the Moehlenhoff Alpha2.""" +from __future__ import annotations + +from datetime import timedelta +import logging + +import aiohttp +from moehlenhoff_alpha2 import Alpha2Base + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +PLATFORMS = [Platform.CLIMATE] + +UPDATE_INTERVAL = timedelta(seconds=60) + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up a config entry.""" + base = Alpha2Base(entry.data["host"]) + coordinator = Alpha2BaseCoordinator(hass, base) + + await coordinator.async_config_entry_first_refresh() + + hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN][entry.entry_id] = coordinator + + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + + if unload_ok and entry.entry_id in hass.data[DOMAIN]: + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok + + +async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Handle options update.""" + await hass.config_entries.async_reload(entry.entry_id) + + +class Alpha2BaseCoordinator(DataUpdateCoordinator[dict[str, dict]]): + """Keep the base instance in one place and centralize the update.""" + + def __init__(self, hass: HomeAssistant, base: Alpha2Base) -> None: + """Initialize Alpha2Base data updater.""" + self.base = base + super().__init__( + hass=hass, + logger=_LOGGER, + name="alpha2_base", + update_interval=UPDATE_INTERVAL, + ) + + async def _async_update_data(self) -> dict[str, dict]: + """Fetch the latest data from the source.""" + await self.base.update_data() + return {ha["ID"]: ha for ha in self.base.heat_areas if ha.get("ID")} + + def get_cooling(self) -> bool: + """Return if cooling mode is enabled.""" + return self.base.cooling + + async def async_set_cooling(self, enabled: bool) -> None: + """Enable or disable cooling mode.""" + await self.base.set_cooling(enabled) + for update_callback in self._listeners: + update_callback() + + async def async_set_target_temperature( + self, heat_area_id: str, target_temperature: float + ) -> None: + """Set the target temperature of the given heat area.""" + _LOGGER.debug( + "Setting target temperature of heat area %s to %0.1f", + heat_area_id, + target_temperature, + ) + + update_data = {"T_TARGET": target_temperature} + is_cooling = self.get_cooling() + heat_area_mode = self.data[heat_area_id]["HEATAREA_MODE"] + if heat_area_mode == 1: + if is_cooling: + update_data["T_COOL_DAY"] = target_temperature + else: + update_data["T_HEAT_DAY"] = target_temperature + elif heat_area_mode == 2: + if is_cooling: + update_data["T_COOL_NIGHT"] = target_temperature + else: + update_data["T_HEAT_NIGHT"] = target_temperature + + try: + await self.base.update_heat_area(heat_area_id, update_data) + except aiohttp.ClientError as http_err: + raise HomeAssistantError( + "Failed to set target temperature, communication error with alpha2 base" + ) from http_err + self.data[heat_area_id].update(update_data) + for update_callback in self._listeners: + update_callback() + + async def async_set_heat_area_mode( + self, heat_area_id: str, heat_area_mode: int + ) -> None: + """Set the mode of the given heat area.""" + # HEATAREA_MODE: 0=Auto, 1=Tag, 2=Nacht + if heat_area_mode not in (0, 1, 2): + ValueError(f"Invalid heat area mode: {heat_area_mode}") + _LOGGER.debug( + "Setting mode of heat area %s to %d", + heat_area_id, + heat_area_mode, + ) + try: + await self.base.update_heat_area( + heat_area_id, {"HEATAREA_MODE": heat_area_mode} + ) + except aiohttp.ClientError as http_err: + raise HomeAssistantError( + "Failed to set heat area mode, communication error with alpha2 base" + ) from http_err + + self.data[heat_area_id]["HEATAREA_MODE"] = heat_area_mode + is_cooling = self.get_cooling() + if heat_area_mode == 1: + if is_cooling: + self.data[heat_area_id]["T_TARGET"] = self.data[heat_area_id][ + "T_COOL_DAY" + ] + else: + self.data[heat_area_id]["T_TARGET"] = self.data[heat_area_id][ + "T_HEAT_DAY" + ] + elif heat_area_mode == 2: + if is_cooling: + self.data[heat_area_id]["T_TARGET"] = self.data[heat_area_id][ + "T_COOL_NIGHT" + ] + else: + self.data[heat_area_id]["T_TARGET"] = self.data[heat_area_id][ + "T_HEAT_NIGHT" + ] + for update_callback in self._listeners: + update_callback() diff --git a/homeassistant/components/moehlenhoff_alpha2/climate.py b/homeassistant/components/moehlenhoff_alpha2/climate.py new file mode 100644 index 00000000000000..da536c4bd06534 --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/climate.py @@ -0,0 +1,131 @@ +"""Support for Alpha2 room control unit via Alpha2 base.""" +import logging + +from homeassistant.components.climate import ClimateEntity +from homeassistant.components.climate.const import ( + CURRENT_HVAC_COOL, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + HVAC_MODE_COOL, + HVAC_MODE_HEAT, + SUPPORT_PRESET_MODE, + SUPPORT_TARGET_TEMPERATURE, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from . import Alpha2BaseCoordinator +from .const import DOMAIN, PRESET_AUTO, PRESET_DAY, PRESET_NIGHT + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Add Alpha2Climate entities from a config_entry.""" + + coordinator: Alpha2BaseCoordinator = hass.data[DOMAIN][config_entry.entry_id] + + async_add_entities( + Alpha2Climate(coordinator, heat_area_id) for heat_area_id in coordinator.data + ) + + +# https://developers.home-assistant.io/docs/core/entity/climate/ +class Alpha2Climate(CoordinatorEntity, ClimateEntity): + """Alpha2 ClimateEntity.""" + + coordinator: Alpha2BaseCoordinator + target_temperature_step = 0.2 + + _attr_supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE + _attr_hvac_modes = [HVAC_MODE_HEAT, HVAC_MODE_COOL] + _attr_temperature_unit = TEMP_CELSIUS + _attr_preset_modes = [PRESET_AUTO, PRESET_DAY, PRESET_NIGHT] + + def __init__(self, coordinator: Alpha2BaseCoordinator, heat_area_id: str) -> None: + """Initialize Alpha2 ClimateEntity.""" + super().__init__(coordinator) + self.heat_area_id = heat_area_id + + @property + def name(self) -> str: + """Return the name of the climate device.""" + return self.coordinator.data[self.heat_area_id]["HEATAREA_NAME"] + + @property + def min_temp(self) -> float: + """Return the minimum temperature.""" + return float(self.coordinator.data[self.heat_area_id].get("T_TARGET_MIN", 0.0)) + + @property + def max_temp(self) -> float: + """Return the maximum temperature.""" + return float(self.coordinator.data[self.heat_area_id].get("T_TARGET_MAX", 30.0)) + + @property + def current_temperature(self) -> float: + """Return the current temperature.""" + return float(self.coordinator.data[self.heat_area_id].get("T_ACTUAL", 0.0)) + + @property + def hvac_mode(self) -> str: + """Return current hvac mode.""" + if self.coordinator.get_cooling(): + return HVAC_MODE_COOL + return HVAC_MODE_HEAT + + async def async_set_hvac_mode(self, hvac_mode: str) -> None: + """Set new target hvac mode.""" + await self.coordinator.async_set_cooling(hvac_mode == HVAC_MODE_COOL) + + @property + def hvac_action(self) -> str: + """Return the current running hvac operation.""" + if not self.coordinator.data[self.heat_area_id]["_HEATCTRL_STATE"]: + return CURRENT_HVAC_IDLE + if self.coordinator.get_cooling(): + return CURRENT_HVAC_COOL + return CURRENT_HVAC_HEAT + + @property + def target_temperature(self) -> float: + """Return the temperature we try to reach.""" + return float(self.coordinator.data[self.heat_area_id].get("T_TARGET", 0.0)) + + async def async_set_temperature(self, **kwargs) -> None: + """Set new target temperatures.""" + target_temperature = kwargs.get(ATTR_TEMPERATURE) + if target_temperature is None: + return + + await self.coordinator.async_set_target_temperature( + self.heat_area_id, target_temperature + ) + + @property + def preset_mode(self) -> str: + """Return the current preset mode.""" + if self.coordinator.data[self.heat_area_id]["HEATAREA_MODE"] == 1: + return PRESET_DAY + if self.coordinator.data[self.heat_area_id]["HEATAREA_MODE"] == 2: + return PRESET_NIGHT + return PRESET_AUTO + + async def async_set_preset_mode(self, preset_mode: str) -> None: + """Set new operation mode.""" + heat_area_mode = 0 + if preset_mode == PRESET_DAY: + heat_area_mode = 1 + elif preset_mode == PRESET_NIGHT: + heat_area_mode = 2 + + await self.coordinator.async_set_heat_area_mode( + self.heat_area_id, heat_area_mode + ) diff --git a/homeassistant/components/moehlenhoff_alpha2/config_flow.py b/homeassistant/components/moehlenhoff_alpha2/config_flow.py new file mode 100644 index 00000000000000..cafdca040b369e --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/config_flow.py @@ -0,0 +1,55 @@ +"""Alpha2 config flow.""" +import asyncio +import logging + +import aiohttp +from moehlenhoff_alpha2 import Alpha2Base +import voluptuous as vol + +from homeassistant import config_entries + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +DATA_SCHEMA = vol.Schema({vol.Required("host"): str}) + + +async def validate_input(data): + """Validate the user input allows us to connect. + + Data has the keys from DATA_SCHEMA with values provided by the user. + """ + + base = Alpha2Base(data["host"]) + try: + await base.update_data() + except (aiohttp.client_exceptions.ClientConnectorError, asyncio.TimeoutError): + return {"error": "cannot_connect"} + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + return {"error": "unknown"} + + # Return info that you want to store in the config entry. + return {"title": base.name} + + +class Alpha2BaseConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Möhlenhoff Alpha2 config flow.""" + + VERSION = 1 + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + errors = {} + if user_input is not None: + self._async_abort_entries_match({"host": user_input["host"]}) + result = await validate_input(user_input) + if result.get("error"): + errors["base"] = result["error"] + else: + return self.async_create_entry(title=result["title"], data=user_input) + + return self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA, errors=errors + ) diff --git a/homeassistant/components/moehlenhoff_alpha2/const.py b/homeassistant/components/moehlenhoff_alpha2/const.py new file mode 100644 index 00000000000000..268936982bd892 --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/const.py @@ -0,0 +1,6 @@ +"""Constants for the Alpha2 integration.""" + +DOMAIN = "moehlenhoff_alpha2" +PRESET_AUTO = "auto" +PRESET_DAY = "day" +PRESET_NIGHT = "night" diff --git a/homeassistant/components/moehlenhoff_alpha2/manifest.json b/homeassistant/components/moehlenhoff_alpha2/manifest.json new file mode 100644 index 00000000000000..b755b28f826ce5 --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/manifest.json @@ -0,0 +1,11 @@ +{ + "domain": "moehlenhoff_alpha2", + "name": "Möhlenhoff Alpha 2", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/moehlenhoff_alpha2", + "requirements": ["moehlenhoff-alpha2==1.1.2"], + "iot_class": "local_push", + "codeowners": [ + "@j-a-n" + ] +} diff --git a/homeassistant/components/moehlenhoff_alpha2/strings.json b/homeassistant/components/moehlenhoff_alpha2/strings.json new file mode 100644 index 00000000000000..3347b2f318c14f --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/strings.json @@ -0,0 +1,19 @@ +{ + "title": "Möhlenhoff Alpha2", + "config": { + "step": { + "user": { + "data": { + "host": "[%key:common::config_flow::data::host%]" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 2aab7c814819d6..be18826bedba45 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -199,6 +199,7 @@ "mobile_app", "modem_callerid", "modern_forms", + "moehlenhoff_alpha2", "monoprice", "motion_blinds", "motioneye", diff --git a/requirements_all.txt b/requirements_all.txt index 5fa50cde8423f9..01011b048b734d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1048,6 +1048,9 @@ minio==5.0.10 # homeassistant.components.mitemp_bt mitemp_bt==0.0.5 +# homeassistant.components.moehlenhoff_alpha2 +moehlenhoff-alpha2==1.1.2 + # homeassistant.components.motion_blinds motionblinds==0.5.11 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4a74f1de59c3f7..8f57bc2b05e554 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -657,6 +657,9 @@ millheater==0.9.0 # homeassistant.components.minio minio==5.0.10 +# homeassistant.components.moehlenhoff_alpha2 +moehlenhoff-alpha2==1.1.2 + # homeassistant.components.motion_blinds motionblinds==0.5.11 diff --git a/tests/components/moehlenhoff_alpha2/__init__.py b/tests/components/moehlenhoff_alpha2/__init__.py new file mode 100644 index 00000000000000..76bd1fd00aad06 --- /dev/null +++ b/tests/components/moehlenhoff_alpha2/__init__.py @@ -0,0 +1 @@ +"""Tests for the moehlenhoff_alpha2 integration.""" diff --git a/tests/components/moehlenhoff_alpha2/test_config_flow.py b/tests/components/moehlenhoff_alpha2/test_config_flow.py new file mode 100644 index 00000000000000..ccfa98718e5727 --- /dev/null +++ b/tests/components/moehlenhoff_alpha2/test_config_flow.py @@ -0,0 +1,106 @@ +"""Test the moehlenhoff_alpha2 config flow.""" +import asyncio +from unittest.mock import patch + +from homeassistant import config_entries +from homeassistant.components.moehlenhoff_alpha2.const import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import ( + RESULT_TYPE_ABORT, + RESULT_TYPE_CREATE_ENTRY, + RESULT_TYPE_FORM, +) + +from tests.common import MockConfigEntry + +MOCK_BASE_ID = "fake-base-id" +MOCK_BASE_NAME = "fake-base-name" +MOCK_BASE_HOST = "fake-base-host" + + +async def mock_update_data(self): + """Mock moehlenhoff_alpha2.Alpha2Base.update_data.""" + self.static_data = { + "Devices": { + "Device": {"ID": MOCK_BASE_ID, "NAME": MOCK_BASE_NAME, "HEATAREA": []} + } + } + + +async def test_form(hass: HomeAssistant) -> None: + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_USER}, + ) + assert result["type"] == RESULT_TYPE_FORM + assert not result["errors"] + + with patch("moehlenhoff_alpha2.Alpha2Base.update_data", mock_update_data), patch( + "homeassistant.components.moehlenhoff_alpha2.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + flow_id=result["flow_id"], + user_input={"host": MOCK_BASE_HOST}, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == MOCK_BASE_NAME + assert result2["data"] == {"host": MOCK_BASE_HOST} + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_duplicate_error(hass: HomeAssistant) -> None: + """Test that errors are shown when duplicates are added.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={"host": MOCK_BASE_HOST}, + source=config_entries.SOURCE_USER, + ) + config_entry.add_to_hass(hass) + + assert config_entry.data["host"] == MOCK_BASE_HOST + + with patch("moehlenhoff_alpha2.Alpha2Base.update_data", mock_update_data): + + result = await hass.config_entries.flow.async_init( + DOMAIN, + data={"host": MOCK_BASE_HOST}, + context={"source": config_entries.SOURCE_USER}, + ) + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + + +async def test_form_cannot_connect_error(hass: HomeAssistant) -> None: + """Test connection error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + with patch( + "moehlenhoff_alpha2.Alpha2Base.update_data", side_effect=asyncio.TimeoutError + ): + result2 = await hass.config_entries.flow.async_configure( + flow_id=result["flow_id"], + user_input={"host": MOCK_BASE_HOST}, + ) + + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {"base": "cannot_connect"} + + +async def test_form_unexpected_error(hass: HomeAssistant) -> None: + """Test unexpected error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + with patch("moehlenhoff_alpha2.Alpha2Base.update_data", side_effect=Exception): + result2 = await hass.config_entries.flow.async_configure( + flow_id=result["flow_id"], + user_input={"host": MOCK_BASE_HOST}, + ) + + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {"base": "unknown"} From 86cf5ec5c2f088823d52f3075ebd0770bd9ba666 Mon Sep 17 00:00:00 2001 From: Milan Meulemans Date: Thu, 10 Feb 2022 08:37:35 +0100 Subject: [PATCH 0489/1098] Bump aioaseko to 0.0.2 to fix issue (#66240) --- homeassistant/components/aseko_pool_live/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/aseko_pool_live/manifest.json b/homeassistant/components/aseko_pool_live/manifest.json index 90c2c81e552b58..3b5b994282dff9 100644 --- a/homeassistant/components/aseko_pool_live/manifest.json +++ b/homeassistant/components/aseko_pool_live/manifest.json @@ -3,7 +3,7 @@ "name": "Aseko Pool Live", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/aseko_pool_live", - "requirements": ["aioaseko==0.0.1"], + "requirements": ["aioaseko==0.0.2"], "codeowners": [ "@milanmeu" ], diff --git a/requirements_all.txt b/requirements_all.txt index 01011b048b734d..399d5cdb0e787a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -138,7 +138,7 @@ aio_georss_gdacs==0.5 aioambient==2021.11.0 # homeassistant.components.aseko_pool_live -aioaseko==0.0.1 +aioaseko==0.0.2 # homeassistant.components.asuswrt aioasuswrt==1.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8f57bc2b05e554..8bb1ac39ed3103 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -91,7 +91,7 @@ aio_georss_gdacs==0.5 aioambient==2021.11.0 # homeassistant.components.aseko_pool_live -aioaseko==0.0.1 +aioaseko==0.0.2 # homeassistant.components.asuswrt aioasuswrt==1.4.0 From 23e39d62d4d06f6d52661551979b7032d274f0bf Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Thu, 10 Feb 2022 08:43:48 +0100 Subject: [PATCH 0490/1098] bump py-synologydsm-api to 1.0.6 (#66226) --- homeassistant/components/synology_dsm/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/synology_dsm/manifest.json b/homeassistant/components/synology_dsm/manifest.json index 1efefb62109f28..39eb11903888d4 100644 --- a/homeassistant/components/synology_dsm/manifest.json +++ b/homeassistant/components/synology_dsm/manifest.json @@ -2,7 +2,7 @@ "domain": "synology_dsm", "name": "Synology DSM", "documentation": "https://www.home-assistant.io/integrations/synology_dsm", - "requirements": ["py-synologydsm-api==1.0.5"], + "requirements": ["py-synologydsm-api==1.0.6"], "codeowners": ["@hacf-fr", "@Quentame", "@mib1185"], "config_flow": true, "ssdp": [ diff --git a/requirements_all.txt b/requirements_all.txt index 399d5cdb0e787a..7245f7970fd9d0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1334,7 +1334,7 @@ py-nightscout==1.2.2 py-schluter==0.1.7 # homeassistant.components.synology_dsm -py-synologydsm-api==1.0.5 +py-synologydsm-api==1.0.6 # homeassistant.components.zabbix py-zabbix==1.1.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8bb1ac39ed3103..3b024a1ac32d4b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -835,7 +835,7 @@ py-melissa-climate==2.1.4 py-nightscout==1.2.2 # homeassistant.components.synology_dsm -py-synologydsm-api==1.0.5 +py-synologydsm-api==1.0.6 # homeassistant.components.seventeentrack py17track==2021.12.2 From 72acda81a753712c1cba2376c384b9ed0cf26ec3 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 10 Feb 2022 02:46:32 -0500 Subject: [PATCH 0491/1098] Simplify get_unique_id helper function for zwave_js (#66221) --- homeassistant/components/zwave_js/__init__.py | 4 +--- homeassistant/components/zwave_js/entity.py | 2 +- homeassistant/components/zwave_js/helpers.py | 6 +++--- homeassistant/components/zwave_js/migrate.py | 10 ++-------- 4 files changed, 7 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 10088f62414352..7fb785d429e595 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -446,9 +446,7 @@ def async_on_value_updated_fire_event( # We assert because we know the device exists assert device - unique_id = get_unique_id( - client.driver.controller.home_id, disc_info.primary_value.value_id - ) + unique_id = get_unique_id(client, disc_info.primary_value.value_id) entity_id = ent_reg.async_get_entity_id(disc_info.platform, DOMAIN, unique_id) raw_value = value_ = value.value diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 87e9f3adbbd097..a61fc3765c7ccc 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -48,7 +48,7 @@ def __init__( # Entity class attributes self._attr_name = self.generate_name() self._attr_unique_id = get_unique_id( - self.client.driver.controller.home_id, self.info.primary_value.value_id + self.client, self.info.primary_value.value_id ) self._attr_entity_registry_enabled_default = ( self.info.entity_registry_enabled_default diff --git a/homeassistant/components/zwave_js/helpers.py b/homeassistant/components/zwave_js/helpers.py index de7ed5da502135..3deb75cf761e0b 100644 --- a/homeassistant/components/zwave_js/helpers.py +++ b/homeassistant/components/zwave_js/helpers.py @@ -55,9 +55,9 @@ def update_data_collection_preference( @callback -def get_unique_id(home_id: str, value_id: str) -> str: - """Get unique ID from home ID and value ID.""" - return f"{home_id}.{value_id}" +def get_unique_id(client: ZwaveClient, value_id: str) -> str: + """Get unique ID from client and value ID.""" + return f"{client.driver.controller.home_id}.{value_id}" @callback diff --git a/homeassistant/components/zwave_js/migrate.py b/homeassistant/components/zwave_js/migrate.py index d9b46ac725c0df..73a094fd95a823 100644 --- a/homeassistant/components/zwave_js/migrate.py +++ b/homeassistant/components/zwave_js/migrate.py @@ -470,10 +470,7 @@ def async_migrate_discovered_value( ) -> None: """Migrate unique ID for entity/entities tied to discovered value.""" - new_unique_id = get_unique_id( - client.driver.controller.home_id, - disc_info.primary_value.value_id, - ) + new_unique_id = get_unique_id(client, disc_info.primary_value.value_id) # On reinterviews, there is no point in going through this logic again for already # discovered values @@ -485,10 +482,7 @@ def async_migrate_discovered_value( # 2021.2.*, 2021.3.0b0, and 2021.3.0 formats old_unique_ids = [ - get_unique_id( - client.driver.controller.home_id, - value_id, - ) + get_unique_id(client, value_id) for value_id in get_old_value_ids(disc_info.primary_value) ] From 3733aa9494a695cba63e26f4afa3610091d8f9a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hjelseth=20H=C3=B8yer?= Date: Thu, 10 Feb 2022 08:47:34 +0100 Subject: [PATCH 0492/1098] Add more sensors for users with Tibber Pulse (#66201) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Tibber, Add stats and sensors for homes with real time meter Signed-off-by: Daniel Hjelseth Høyer * Tibber stats Signed-off-by: Daniel Hjelseth Høyer * Monthly peak hour --- homeassistant/components/tibber/manifest.json | 2 +- homeassistant/components/tibber/sensor.py | 14 +++++++------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/tibber/manifest.json b/homeassistant/components/tibber/manifest.json index 90a19526c7c7db..9b84b9a54c2863 100644 --- a/homeassistant/components/tibber/manifest.json +++ b/homeassistant/components/tibber/manifest.json @@ -3,7 +3,7 @@ "domain": "tibber", "name": "Tibber", "documentation": "https://www.home-assistant.io/integrations/tibber", - "requirements": ["pyTibber==0.21.7"], + "requirements": ["pyTibber==0.22.1"], "codeowners": ["@danielhiversen"], "quality_scale": "silver", "config_flow": true, diff --git a/homeassistant/components/tibber/sensor.py b/homeassistant/components/tibber/sensor.py index eeb072e3a625d4..ebb986d6a7ed1e 100644 --- a/homeassistant/components/tibber/sensor.py +++ b/homeassistant/components/tibber/sensor.py @@ -210,7 +210,7 @@ ), SensorEntityDescription( key="peak_hour", - name="Month peak hour consumption", + name="Monthly peak hour consumption", device_class=SensorDeviceClass.ENERGY, native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, ), @@ -253,17 +253,17 @@ async def async_setup_entry( if home.has_active_subscription: entities.append(TibberSensorElPrice(home)) + if coordinator is None: + coordinator = TibberDataCoordinator(hass, tibber_connection) + for entity_description in SENSORS: + entities.append(TibberDataSensor(home, coordinator, entity_description)) + if home.has_real_time_consumption: await home.rt_subscribe( TibberRtDataCoordinator( async_add_entities, home, hass ).async_set_updated_data ) - if home.has_active_subscription and not home.has_real_time_consumption: - if coordinator is None: - coordinator = TibberDataCoordinator(hass, tibber_connection) - for entity_description in SENSORS: - entities.append(TibberDataSensor(home, coordinator, entity_description)) # migrate old_id = home.info["viewer"]["home"]["meteringPointData"]["consumptionEan"] @@ -547,7 +547,7 @@ def __init__(self, hass, tibber_connection): hass, _LOGGER, name=f"Tibber {tibber_connection.name}", - update_interval=timedelta(hours=1), + update_interval=timedelta(minutes=20), ) self._tibber_connection = tibber_connection diff --git a/requirements_all.txt b/requirements_all.txt index 7245f7970fd9d0..5376c7a5f11c28 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1362,7 +1362,7 @@ pyRFXtrx==0.27.1 # pySwitchmate==0.4.6 # homeassistant.components.tibber -pyTibber==0.21.7 +pyTibber==0.22.1 # homeassistant.components.dlink pyW215==0.7.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3b024a1ac32d4b..ae3fe7eb5c4181 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -854,7 +854,7 @@ pyMetno==0.9.0 pyRFXtrx==0.27.1 # homeassistant.components.tibber -pyTibber==0.21.7 +pyTibber==0.22.1 # homeassistant.components.nextbus py_nextbusnext==0.1.5 From e90143e5c9daac0e7da83a4511b0294bc1096f61 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 10 Feb 2022 00:03:29 -0800 Subject: [PATCH 0493/1098] Sort media sources (#66237) * Sort media sources * Flatten media sources and sort Cast children * Only expand if possible --- homeassistant/components/cast/media_player.py | 6 ++-- .../components/media_source/models.py | 29 ++++++++++--------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 29fa38fbdc65fc..565961e1b8c539 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -473,13 +473,13 @@ async def _async_root_payload(self, content_filter): result = await media_source.async_browse_media( self.hass, None, content_filter=content_filter ) - children.append(result) + children.extend(result.children) except BrowseError: if not children: raise # If there's only one media source, resolve it - if len(children) == 1: + if len(children) == 1 and children[0].can_expand: return await self.async_browse_media( children[0].media_content_type, children[0].media_content_id, @@ -492,7 +492,7 @@ async def _async_root_payload(self, content_filter): media_content_type="", can_play=False, can_expand=True, - children=children, + children=sorted(children, key=lambda c: c.title), ) async def async_browse_media(self, media_content_type=None, media_content_id=None): diff --git a/homeassistant/components/media_source/models.py b/homeassistant/components/media_source/models.py index 8ebf87b98d5950..ceb57ef1fb4f27 100644 --- a/homeassistant/components/media_source/models.py +++ b/homeassistant/components/media_source/models.py @@ -64,19 +64,22 @@ async def async_browse(self) -> BrowseMediaSource: can_expand=True, children_media_class=MEDIA_CLASS_APP, ) - base.children = [ - BrowseMediaSource( - domain=source.domain, - identifier=None, - media_class=MEDIA_CLASS_APP, - media_content_type=MEDIA_TYPE_APP, - thumbnail=f"https://brands.home-assistant.io/_/{source.domain}/logo.png", - title=source.name, - can_play=False, - can_expand=True, - ) - for source in self.hass.data[DOMAIN].values() - ] + base.children = sorted( + ( + BrowseMediaSource( + domain=source.domain, + identifier=None, + media_class=MEDIA_CLASS_APP, + media_content_type=MEDIA_TYPE_APP, + thumbnail=f"https://brands.home-assistant.io/_/{source.domain}/logo.png", + title=source.name, + can_play=False, + can_expand=True, + ) + for source in self.hass.data[DOMAIN].values() + ), + key=lambda item: item.title, + ) return base return await self.async_media_source().async_browse_media(self) From f17d66228c84d2d66c05997ff00e30091e3b5584 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 10 Feb 2022 09:04:36 +0100 Subject: [PATCH 0494/1098] Enable basic type checking in demo init (#66218) * Enable basic type checking in demo init * Remove from mypy ignore list --- homeassistant/components/demo/__init__.py | 16 ++++++++-------- mypy.ini | 3 --- script/hassfest/mypy_config.py | 1 - 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/demo/__init__.py b/homeassistant/components/demo/__init__.py index 3ad84825090319..abee8310e171ef 100644 --- a/homeassistant/components/demo/__init__.py +++ b/homeassistant/components/demo/__init__.py @@ -3,7 +3,7 @@ import datetime from random import random -from homeassistant import bootstrap, config_entries +from homeassistant import config_entries, setup from homeassistant.components import persistent_notification from homeassistant.components.recorder.statistics import ( async_add_external_statistics, @@ -83,11 +83,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: if not hass.config.longitude: hass.config.longitude = 117.22743 - tasks = [bootstrap.async_setup_component(hass, "sun", config)] + tasks = [setup.async_setup_component(hass, "sun", config)] # Set up input select tasks.append( - bootstrap.async_setup_component( + setup.async_setup_component( hass, "input_select", { @@ -108,7 +108,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # Set up input boolean tasks.append( - bootstrap.async_setup_component( + setup.async_setup_component( hass, "input_boolean", { @@ -125,7 +125,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # Set up input button tasks.append( - bootstrap.async_setup_component( + setup.async_setup_component( hass, "input_button", { @@ -141,7 +141,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # Set up input number tasks.append( - bootstrap.async_setup_component( + setup.async_setup_component( hass, "input_number", { @@ -280,7 +280,7 @@ async def finish_setup(hass, config): lights = sorted(hass.states.async_entity_ids("light")) # Set up scripts - await bootstrap.async_setup_component( + await setup.async_setup_component( hass, "script", { @@ -309,7 +309,7 @@ async def finish_setup(hass, config): ) # Set up scenes - await bootstrap.async_setup_component( + await setup.async_setup_component( hass, "scene", { diff --git a/mypy.ini b/mypy.ini index e2b406301da581..c5afffd5638e44 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2196,9 +2196,6 @@ ignore_errors = true [mypy-homeassistant.components.deconz.switch] ignore_errors = true -[mypy-homeassistant.components.demo] -ignore_errors = true - [mypy-homeassistant.components.demo.fan] ignore_errors = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index 19bd3ee77f3608..376f33de65bdeb 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -38,7 +38,6 @@ "homeassistant.components.deconz.services", "homeassistant.components.deconz.siren", "homeassistant.components.deconz.switch", - "homeassistant.components.demo", "homeassistant.components.demo.fan", "homeassistant.components.demo.light", "homeassistant.components.demo.number", From 678e56b8b765c24fc83020a054304e9d9a6890af Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Thu, 10 Feb 2022 09:18:35 +0100 Subject: [PATCH 0495/1098] Mqtt move to .const (#65631) --- homeassistant/components/mqtt/binary_sensor.py | 3 +-- homeassistant/components/mqtt/climate.py | 4 +--- homeassistant/components/mqtt/const.py | 1 + homeassistant/components/mqtt/fan.py | 5 ++--- homeassistant/components/mqtt/humidifier.py | 5 ++--- homeassistant/components/mqtt/light/schema_basic.py | 5 ++--- homeassistant/components/mqtt/light/schema_json.py | 2 -- homeassistant/components/mqtt/light/schema_template.py | 3 +-- homeassistant/components/mqtt/siren.py | 2 +- homeassistant/components/mqtt/switch.py | 3 +-- 10 files changed, 12 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index 150ce3a7eb6541..17daaaf08eea3c 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -35,7 +35,7 @@ from . import PLATFORMS, MqttValueTemplate, subscription from .. import mqtt -from .const import CONF_ENCODING, CONF_QOS, CONF_STATE_TOPIC, DOMAIN +from .const import CONF_ENCODING, CONF_QOS, CONF_STATE_TOPIC, DOMAIN, PAYLOAD_NONE from .debug_info import log_messages from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, @@ -52,7 +52,6 @@ DEFAULT_PAYLOAD_ON = "ON" DEFAULT_FORCE_UPDATE = False CONF_EXPIRE_AFTER = "expire_after" -PAYLOAD_NONE = "None" PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( { diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index 91d7cdd772d949..2b4e03f866380d 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -64,7 +64,7 @@ subscription, ) from .. import mqtt -from .const import CONF_ENCODING, CONF_QOS, CONF_RETAIN, DOMAIN +from .const import CONF_ENCODING, CONF_QOS, CONF_RETAIN, DOMAIN, PAYLOAD_NONE from .debug_info import log_messages from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper @@ -125,8 +125,6 @@ CONF_TEMP_MIN = "min_temp" CONF_TEMP_STEP = "temp_step" -PAYLOAD_NONE = "None" - MQTT_CLIMATE_ATTRIBUTES_BLOCKED = frozenset( { climate.ATTR_AUX_HEAT, diff --git a/homeassistant/components/mqtt/const.py b/homeassistant/components/mqtt/const.py index 0feb21b0010047..4639682054528f 100644 --- a/homeassistant/components/mqtt/const.py +++ b/homeassistant/components/mqtt/const.py @@ -18,6 +18,7 @@ CONF_QOS = ATTR_QOS CONF_RETAIN = ATTR_RETAIN CONF_STATE_TOPIC = "state_topic" +CONF_STATE_VALUE_TEMPLATE = "state_value_template" CONF_TOPIC = "topic" CONF_WILL_MESSAGE = "will_message" diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index 6fe36cd5fcd85c..114c8f5729ff59 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -49,12 +49,13 @@ CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, + CONF_STATE_VALUE_TEMPLATE, DOMAIN, + PAYLOAD_NONE, ) from .debug_info import log_messages from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper -CONF_STATE_VALUE_TEMPLATE = "state_value_template" CONF_PERCENTAGE_STATE_TOPIC = "percentage_state_topic" CONF_PERCENTAGE_COMMAND_TOPIC = "percentage_command_topic" CONF_PERCENTAGE_VALUE_TEMPLATE = "percentage_value_template" @@ -94,8 +95,6 @@ OSCILLATE_ON_PAYLOAD = "oscillate_on" OSCILLATE_OFF_PAYLOAD = "oscillate_off" -PAYLOAD_NONE = "None" - MQTT_FAN_ATTRIBUTES_BLOCKED = frozenset( { fan.ATTR_DIRECTION, diff --git a/homeassistant/components/mqtt/humidifier.py b/homeassistant/components/mqtt/humidifier.py index c9a5341a43f6d6..0ab99cf091458d 100644 --- a/homeassistant/components/mqtt/humidifier.py +++ b/homeassistant/components/mqtt/humidifier.py @@ -39,7 +39,9 @@ CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, + CONF_STATE_VALUE_TEMPLATE, DOMAIN, + PAYLOAD_NONE, ) from .debug_info import log_messages from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper @@ -52,7 +54,6 @@ CONF_MODE_STATE_TEMPLATE = "mode_state_template" CONF_PAYLOAD_RESET_MODE = "payload_reset_mode" CONF_PAYLOAD_RESET_HUMIDITY = "payload_reset_humidity" -CONF_STATE_VALUE_TEMPLATE = "state_value_template" CONF_TARGET_HUMIDITY_COMMAND_TEMPLATE = "target_humidity_command_template" CONF_TARGET_HUMIDITY_COMMAND_TOPIC = "target_humidity_command_topic" CONF_TARGET_HUMIDITY_MIN = "min_humidity" @@ -66,8 +67,6 @@ DEFAULT_PAYLOAD_OFF = "OFF" DEFAULT_PAYLOAD_RESET = "None" -PAYLOAD_NONE = "None" - MQTT_HUMIDIFIER_ATTRIBUTES_BLOCKED = frozenset( { humidifier.ATTR_HUMIDITY, diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index 1ff08a49ab1720..d5665a1beff77e 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -59,6 +59,8 @@ CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, + CONF_STATE_VALUE_TEMPLATE, + PAYLOAD_NONE, ) from ..debug_info import log_messages from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity @@ -97,7 +99,6 @@ CONF_RGBWW_COMMAND_TOPIC = "rgbww_command_topic" CONF_RGBWW_STATE_TOPIC = "rgbww_state_topic" CONF_RGBWW_VALUE_TEMPLATE = "rgbww_value_template" -CONF_STATE_VALUE_TEMPLATE = "state_value_template" CONF_XY_COMMAND_TOPIC = "xy_command_topic" CONF_XY_STATE_TOPIC = "xy_state_topic" CONF_XY_VALUE_TEMPLATE = "xy_value_template" @@ -109,8 +110,6 @@ CONF_WHITE_VALUE_TEMPLATE = "white_value_template" CONF_ON_COMMAND_TYPE = "on_command_type" -PAYLOAD_NONE = "None" - MQTT_LIGHT_ATTRIBUTES_BLOCKED = frozenset( { ATTR_COLOR_MODE, diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index 52b840fcfaffc3..83d40ed5aaec20 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -100,8 +100,6 @@ CONF_MAX_MIREDS = "max_mireds" CONF_MIN_MIREDS = "min_mireds" -PAYLOAD_NONE = "None" - def valid_color_configuration(config): """Test color_mode is not combined with deprecated config.""" diff --git a/homeassistant/components/mqtt/light/schema_template.py b/homeassistant/components/mqtt/light/schema_template.py index 5700a8ab868d7c..4f25bde928dfd8 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -41,6 +41,7 @@ CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, + PAYLOAD_NONE, ) from ..debug_info import log_messages from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity @@ -67,8 +68,6 @@ CONF_RED_TEMPLATE = "red_template" CONF_WHITE_VALUE_TEMPLATE = "white_value_template" -PAYLOAD_NONE = "None" - PLATFORM_SCHEMA_TEMPLATE = ( mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( { diff --git a/homeassistant/components/mqtt/siren.py b/homeassistant/components/mqtt/siren.py index b3c5df157c91ce..e43edb12873dec 100644 --- a/homeassistant/components/mqtt/siren.py +++ b/homeassistant/components/mqtt/siren.py @@ -48,6 +48,7 @@ CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, + CONF_STATE_VALUE_TEMPLATE, DOMAIN, PAYLOAD_EMPTY_JSON, PAYLOAD_NONE, @@ -66,7 +67,6 @@ CONF_COMMAND_OFF_TEMPLATE = "command_off_template" CONF_STATE_ON = "state_on" CONF_STATE_OFF = "state_off" -CONF_STATE_VALUE_TEMPLATE = "state_value_template" CONF_SUPPORT_DURATION = "support_duration" CONF_SUPPORT_VOLUME_SET = "support_volume_set" diff --git a/homeassistant/components/mqtt/switch.py b/homeassistant/components/mqtt/switch.py index 906901b6080df0..f0ec18cc379d21 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -33,6 +33,7 @@ CONF_RETAIN, CONF_STATE_TOPIC, DOMAIN, + PAYLOAD_NONE, ) from .debug_info import log_messages from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper @@ -51,8 +52,6 @@ CONF_STATE_ON = "state_on" CONF_STATE_OFF = "state_off" -PAYLOAD_NONE = "None" - PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, From b3814aa4e62764db4e1a843b66599acc800d930b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 10 Feb 2022 09:53:26 +0100 Subject: [PATCH 0496/1098] Refactor Plugwise command handling (#66202) --- homeassistant/components/plugwise/climate.py | 62 ++++-------- homeassistant/components/plugwise/switch.py | 43 +++----- homeassistant/components/plugwise/util.py | 36 +++++++ tests/components/plugwise/test_climate.py | 100 ++++++++++--------- tests/components/plugwise/test_switch.py | 83 +++++++++------ 5 files changed, 177 insertions(+), 147 deletions(-) create mode 100644 homeassistant/components/plugwise/util.py diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index b3e7472369c3fb..4cea3d9499e981 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -1,8 +1,6 @@ """Plugwise Climate component for Home Assistant.""" from typing import Any -from plugwise.exceptions import PlugwiseException - from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_COOL, @@ -20,16 +18,10 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import ( - DEFAULT_MAX_TEMP, - DEFAULT_MIN_TEMP, - DOMAIN, - LOGGER, - SCHEDULE_OFF, - SCHEDULE_ON, -) +from .const import DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, DOMAIN, SCHEDULE_OFF, SCHEDULE_ON from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity +from .util import plugwise_command HVAC_MODES_HEAT_ONLY = [HVAC_MODE_HEAT, HVAC_MODE_AUTO, HVAC_MODE_OFF] HVAC_MODES_HEAT_COOL = [HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_AUTO, HVAC_MODE_OFF] @@ -76,21 +68,16 @@ def __init__( self._loc_id = coordinator.data.devices[device_id]["location"] + @plugwise_command async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" - temperature = kwargs.get(ATTR_TEMPERATURE) - if (temperature is not None) and ( - self._attr_min_temp < temperature < self._attr_max_temp + if ((temperature := kwargs.get(ATTR_TEMPERATURE)) is None) or ( + self._attr_max_temp < temperature < self._attr_min_temp ): - try: - await self.coordinator.api.set_temperature(self._loc_id, temperature) - self._attr_target_temperature = temperature - self.async_write_ha_state() - except PlugwiseException: - LOGGER.error("Error while communicating to device") - else: - LOGGER.error("Invalid temperature requested") + raise ValueError("Invalid temperature requested") + await self.coordinator.api.set_temperature(self._loc_id, temperature) + @plugwise_command async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set the hvac mode.""" state = SCHEDULE_OFF @@ -98,35 +85,22 @@ async def async_set_hvac_mode(self, hvac_mode: str) -> None: if hvac_mode == HVAC_MODE_AUTO: state = SCHEDULE_ON - try: - await self.coordinator.api.set_temperature( - self._loc_id, climate_data.get("schedule_temperature") - ) - self._attr_target_temperature = climate_data.get("schedule_temperature") - except PlugwiseException: - LOGGER.error("Error while communicating to device") - - try: - await self.coordinator.api.set_schedule_state( - self._loc_id, climate_data.get("last_used"), state + await self.coordinator.api.set_temperature( + self._loc_id, climate_data.get("schedule_temperature") ) - self._attr_hvac_mode = hvac_mode - self.async_write_ha_state() - except PlugwiseException: - LOGGER.error("Error while communicating to device") + self._attr_target_temperature = climate_data.get("schedule_temperature") + + await self.coordinator.api.set_schedule_state( + self._loc_id, climate_data.get("last_used"), state + ) + @plugwise_command async def async_set_preset_mode(self, preset_mode: str) -> None: """Set the preset mode.""" - if not (presets := self.coordinator.data.devices[self._dev_id].get("presets")): + if not self.coordinator.data.devices[self._dev_id].get("presets"): raise ValueError("No presets available") - try: - await self.coordinator.api.set_preset(self._loc_id, preset_mode) - self._attr_preset_mode = preset_mode - self._attr_target_temperature = presets.get(preset_mode, "none")[0] - self.async_write_ha_state() - except PlugwiseException: - LOGGER.error("Error while communicating to device") + await self.coordinator.api.set_preset(self._loc_id, preset_mode) @callback def _handle_coordinator_update(self) -> None: diff --git a/homeassistant/components/plugwise/switch.py b/homeassistant/components/plugwise/switch.py index c95474a2b5a131..5365d5834a8373 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -3,8 +3,6 @@ from typing import Any -from plugwise.exceptions import PlugwiseException - from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -13,6 +11,7 @@ from .const import DOMAIN, LOGGER from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity +from .util import plugwise_command SWITCHES: tuple[SwitchEntityDescription, ...] = ( SwitchEntityDescription( @@ -54,37 +53,25 @@ def __init__( self._attr_unique_id = f"{device_id}-{description.key}" self._attr_name = coordinator.data.devices[device_id].get("name") + @plugwise_command async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" - try: - state_on = await self.coordinator.api.set_switch_state( - self._dev_id, - self.coordinator.data.devices[self._dev_id].get("members"), - self.entity_description.key, - "on", - ) - except PlugwiseException: - LOGGER.error("Error while communicating to device") - else: - if state_on: - self._attr_is_on = True - self.async_write_ha_state() + await self.coordinator.api.set_switch_state( + self._dev_id, + self.coordinator.data.devices[self._dev_id].get("members"), + self.entity_description.key, + "on", + ) + @plugwise_command async def async_turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" - try: - state_off = await self.coordinator.api.set_switch_state( - self._dev_id, - self.coordinator.data.devices[self._dev_id].get("members"), - self.entity_description.key, - "off", - ) - except PlugwiseException: - LOGGER.error("Error while communicating to device") - else: - if state_off: - self._attr_is_on = False - self.async_write_ha_state() + await self.coordinator.api.set_switch_state( + self._dev_id, + self.coordinator.data.devices[self._dev_id].get("members"), + self.entity_description.key, + "off", + ) @callback def _handle_coordinator_update(self) -> None: diff --git a/homeassistant/components/plugwise/util.py b/homeassistant/components/plugwise/util.py new file mode 100644 index 00000000000000..58c7715815ea4e --- /dev/null +++ b/homeassistant/components/plugwise/util.py @@ -0,0 +1,36 @@ +"""Utilities for Plugwise.""" +from collections.abc import Awaitable, Callable, Coroutine +from typing import Any, TypeVar + +from plugwise.exceptions import PlugwiseException +from typing_extensions import Concatenate, ParamSpec + +from homeassistant.exceptions import HomeAssistantError + +from .entity import PlugwiseEntity + +_P = ParamSpec("_P") +_R = TypeVar("_R") +_T = TypeVar("_T", bound=PlugwiseEntity) + + +def plugwise_command( + func: Callable[Concatenate[_T, _P], Awaitable[_R]] # type: ignore[misc] +) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, _R]]: # type: ignore[misc] + """Decorate Plugwise calls that send commands/make changes to the device. + + A decorator that wraps the passed in function, catches Plugwise errors, + and requests an coordinator update to update status of the devices asap. + """ + + async def handler(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> _R: + try: + return await func(self, *args, **kwargs) + except PlugwiseException as error: + raise HomeAssistantError( + f"Error communicating with API: {error}" + ) from error + finally: + await self.coordinator.async_request_refresh() + + return handler diff --git a/tests/components/plugwise/test_climate.py b/tests/components/plugwise/test_climate.py index a7af9f9c0c99da..5eb60ebeb6fb6b 100644 --- a/tests/components/plugwise/test_climate.py +++ b/tests/components/plugwise/test_climate.py @@ -1,6 +1,7 @@ """Tests for the Plugwise Climate integration.""" from plugwise.exceptions import PlugwiseException +import pytest from homeassistant.components.climate.const import ( HVAC_MODE_AUTO, @@ -8,6 +9,7 @@ HVAC_MODE_OFF, ) from homeassistant.config_entries import ConfigEntryState +from homeassistant.exceptions import HomeAssistantError from tests.components.plugwise.common import async_init_integration @@ -56,32 +58,38 @@ async def test_adam_climate_adjust_negative_testing(hass, mock_smile_adam): entry = await async_init_integration(hass, mock_smile_adam) assert entry.state is ConfigEntryState.LOADED - await hass.services.async_call( - "climate", - "set_temperature", - {"entity_id": "climate.zone_lisa_wk", "temperature": 25}, - blocking=True, - ) + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + "climate", + "set_temperature", + {"entity_id": "climate.zone_lisa_wk", "temperature": 25}, + blocking=True, + ) state = hass.states.get("climate.zone_lisa_wk") attrs = state.attributes assert attrs["temperature"] == 21.5 - await hass.services.async_call( - "climate", - "set_preset_mode", - {"entity_id": "climate.zone_thermostat_jessie", "preset_mode": "home"}, - blocking=True, - ) + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + "climate", + "set_preset_mode", + {"entity_id": "climate.zone_thermostat_jessie", "preset_mode": "home"}, + blocking=True, + ) state = hass.states.get("climate.zone_thermostat_jessie") attrs = state.attributes assert attrs["preset_mode"] == "asleep" - await hass.services.async_call( - "climate", - "set_hvac_mode", - {"entity_id": "climate.zone_thermostat_jessie", "hvac_mode": HVAC_MODE_AUTO}, - blocking=True, - ) + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + "climate", + "set_hvac_mode", + { + "entity_id": "climate.zone_thermostat_jessie", + "hvac_mode": HVAC_MODE_AUTO, + }, + blocking=True, + ) state = hass.states.get("climate.zone_thermostat_jessie") attrs = state.attributes @@ -97,10 +105,11 @@ async def test_adam_climate_entity_climate_changes(hass, mock_smile_adam): {"entity_id": "climate.zone_lisa_wk", "temperature": 25}, blocking=True, ) - state = hass.states.get("climate.zone_lisa_wk") - attrs = state.attributes - assert attrs["temperature"] == 25.0 + assert mock_smile_adam.set_temperature.call_count == 1 + mock_smile_adam.set_temperature.assert_called_with( + "c50f167537524366a5af7aa3942feb1e", 25.0 + ) await hass.services.async_call( "climate", @@ -108,12 +117,11 @@ async def test_adam_climate_entity_climate_changes(hass, mock_smile_adam): {"entity_id": "climate.zone_lisa_wk", "preset_mode": "away"}, blocking=True, ) - state = hass.states.get("climate.zone_lisa_wk") - attrs = state.attributes - - assert attrs["preset_mode"] == "away" - assert attrs["supported_features"] == 17 + assert mock_smile_adam.set_preset.call_count == 1 + mock_smile_adam.set_preset.assert_called_with( + "c50f167537524366a5af7aa3942feb1e", "away" + ) await hass.services.async_call( "climate", @@ -122,10 +130,10 @@ async def test_adam_climate_entity_climate_changes(hass, mock_smile_adam): blocking=True, ) - state = hass.states.get("climate.zone_thermostat_jessie") - attrs = state.attributes - - assert attrs["temperature"] == 25.0 + assert mock_smile_adam.set_temperature.call_count == 2 + mock_smile_adam.set_temperature.assert_called_with( + "82fa13f017d240daa0d0ea1775420f24", 25.0 + ) await hass.services.async_call( "climate", @@ -133,10 +141,11 @@ async def test_adam_climate_entity_climate_changes(hass, mock_smile_adam): {"entity_id": "climate.zone_thermostat_jessie", "preset_mode": "home"}, blocking=True, ) - state = hass.states.get("climate.zone_thermostat_jessie") - attrs = state.attributes - assert attrs["preset_mode"] == "home" + assert mock_smile_adam.set_preset.call_count == 2 + mock_smile_adam.set_preset.assert_called_with( + "82fa13f017d240daa0d0ea1775420f24", "home" + ) async def test_anna_climate_entity_attributes(hass, mock_smile_anna): @@ -176,10 +185,10 @@ async def test_anna_climate_entity_climate_changes(hass, mock_smile_anna): blocking=True, ) - state = hass.states.get("climate.anna") - attrs = state.attributes - - assert attrs["temperature"] == 25.0 + assert mock_smile_anna.set_temperature.call_count == 1 + mock_smile_anna.set_temperature.assert_called_with( + "c784ee9fdab44e1395b8dee7d7a497d5", 25.0 + ) await hass.services.async_call( "climate", @@ -188,10 +197,10 @@ async def test_anna_climate_entity_climate_changes(hass, mock_smile_anna): blocking=True, ) - state = hass.states.get("climate.anna") - attrs = state.attributes - - assert attrs["preset_mode"] == "away" + assert mock_smile_anna.set_preset.call_count == 1 + mock_smile_anna.set_preset.assert_called_with( + "c784ee9fdab44e1395b8dee7d7a497d5", "away" + ) await hass.services.async_call( "climate", @@ -200,7 +209,8 @@ async def test_anna_climate_entity_climate_changes(hass, mock_smile_anna): blocking=True, ) - state = hass.states.get("climate.anna") - attrs = state.attributes - - assert state.state == "heat_cool" + assert mock_smile_anna.set_temperature.call_count == 1 + assert mock_smile_anna.set_schedule_state.call_count == 1 + mock_smile_anna.set_schedule_state.assert_called_with( + "c784ee9fdab44e1395b8dee7d7a497d5", None, "false" + ) diff --git a/tests/components/plugwise/test_switch.py b/tests/components/plugwise/test_switch.py index 4d09489944d703..2816b2dece68bb 100644 --- a/tests/components/plugwise/test_switch.py +++ b/tests/components/plugwise/test_switch.py @@ -1,10 +1,11 @@ """Tests for the Plugwise switch integration.""" - from plugwise.exceptions import PlugwiseException +import pytest from homeassistant.components.plugwise.const import DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.config_entries import ConfigEntryState +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er from tests.common import MockConfigEntry @@ -29,23 +30,31 @@ async def test_adam_climate_switch_negative_testing(hass, mock_smile_adam): entry = await async_init_integration(hass, mock_smile_adam) assert entry.state is ConfigEntryState.LOADED - await hass.services.async_call( - "switch", - "turn_off", - {"entity_id": "switch.cv_pomp"}, - blocking=True, + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + "switch", + "turn_off", + {"entity_id": "switch.cv_pomp"}, + blocking=True, + ) + + assert mock_smile_adam.set_switch_state.call_count == 1 + mock_smile_adam.set_switch_state.assert_called_with( + "78d1126fc4c743db81b61c20e88342a7", None, "relay", "off" ) - state = hass.states.get("switch.cv_pomp") - assert str(state.state) == "on" - await hass.services.async_call( - "switch", - "turn_on", - {"entity_id": "switch.fibaro_hc2"}, - blocking=True, + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + "switch", + "turn_on", + {"entity_id": "switch.fibaro_hc2"}, + blocking=True, + ) + + assert mock_smile_adam.set_switch_state.call_count == 2 + mock_smile_adam.set_switch_state.assert_called_with( + "a28f588dc4a049a483fd03a30361ad3a", None, "relay", "on" ) - state = hass.states.get("switch.fibaro_hc2") - assert str(state.state) == "on" async def test_adam_climate_switch_changes(hass, mock_smile_adam): @@ -59,8 +68,11 @@ async def test_adam_climate_switch_changes(hass, mock_smile_adam): {"entity_id": "switch.cv_pomp"}, blocking=True, ) - state = hass.states.get("switch.cv_pomp") - assert str(state.state) == "off" + + assert mock_smile_adam.set_switch_state.call_count == 1 + mock_smile_adam.set_switch_state.assert_called_with( + "78d1126fc4c743db81b61c20e88342a7", None, "relay", "off" + ) await hass.services.async_call( "switch", @@ -68,17 +80,23 @@ async def test_adam_climate_switch_changes(hass, mock_smile_adam): {"entity_id": "switch.fibaro_hc2"}, blocking=True, ) - state = hass.states.get("switch.fibaro_hc2") - assert str(state.state) == "off" + + assert mock_smile_adam.set_switch_state.call_count == 2 + mock_smile_adam.set_switch_state.assert_called_with( + "a28f588dc4a049a483fd03a30361ad3a", None, "relay", "off" + ) await hass.services.async_call( "switch", - "toggle", + "turn_on", {"entity_id": "switch.fibaro_hc2"}, blocking=True, ) - state = hass.states.get("switch.fibaro_hc2") - assert str(state.state) == "on" + + assert mock_smile_adam.set_switch_state.call_count == 3 + mock_smile_adam.set_switch_state.assert_called_with( + "a28f588dc4a049a483fd03a30361ad3a", None, "relay", "on" + ) async def test_stretch_switch_entities(hass, mock_stretch): @@ -104,9 +122,10 @@ async def test_stretch_switch_changes(hass, mock_stretch): {"entity_id": "switch.koelkast_92c4a"}, blocking=True, ) - - state = hass.states.get("switch.koelkast_92c4a") - assert str(state.state) == "off" + assert mock_stretch.set_switch_state.call_count == 1 + mock_stretch.set_switch_state.assert_called_with( + "e1c884e7dede431dadee09506ec4f859", None, "relay", "off" + ) await hass.services.async_call( "switch", @@ -114,17 +133,21 @@ async def test_stretch_switch_changes(hass, mock_stretch): {"entity_id": "switch.droger_52559"}, blocking=True, ) - state = hass.states.get("switch.droger_52559") - assert str(state.state) == "off" + assert mock_stretch.set_switch_state.call_count == 2 + mock_stretch.set_switch_state.assert_called_with( + "cfe95cf3de1948c0b8955125bf754614", None, "relay", "off" + ) await hass.services.async_call( "switch", - "toggle", + "turn_on", {"entity_id": "switch.droger_52559"}, blocking=True, ) - state = hass.states.get("switch.droger_52559") - assert str(state.state) == "on" + assert mock_stretch.set_switch_state.call_count == 3 + mock_stretch.set_switch_state.assert_called_with( + "cfe95cf3de1948c0b8955125bf754614", None, "relay", "on" + ) async def test_unique_id_migration_plug_relay(hass, mock_smile_adam): From ea325ef0279a92a9d4b8bddec70caec061f8d248 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 10 Feb 2022 10:05:58 +0100 Subject: [PATCH 0497/1098] Enable basic type checking in demo platforms (#66212) * Adjust type hints in demo platforms * Adjust mypy config * Adjust name --- homeassistant/components/demo/fan.py | 14 +++++++------- homeassistant/components/demo/light.py | 6 +++--- homeassistant/components/demo/number.py | 7 ++----- homeassistant/components/demo/remote.py | 2 +- homeassistant/components/demo/siren.py | 2 +- homeassistant/components/demo/switch.py | 8 ++------ mypy.ini | 18 ------------------ script/hassfest/mypy_config.py | 6 ------ 8 files changed, 16 insertions(+), 47 deletions(-) diff --git a/homeassistant/components/demo/fan.py b/homeassistant/components/demo/fan.py index 89256c954686d7..e70f5efc6264c9 100644 --- a/homeassistant/components/demo/fan.py +++ b/homeassistant/components/demo/fan.py @@ -114,11 +114,11 @@ def __init__( self.hass = hass self._unique_id = unique_id self._supported_features = supported_features - self._percentage = None + self._percentage: int | None = None self._preset_modes = preset_modes - self._preset_mode = None - self._oscillating = None - self._direction = None + self._preset_mode: str | None = None + self._oscillating: bool | None = None + self._direction: str | None = None self._name = name if supported_features & SUPPORT_OSCILLATE: self._oscillating = False @@ -141,12 +141,12 @@ def should_poll(self): return False @property - def current_direction(self) -> str: + def current_direction(self) -> str | None: """Fan direction.""" return self._direction @property - def oscillating(self) -> bool: + def oscillating(self) -> bool | None: """Oscillating.""" return self._oscillating @@ -257,7 +257,7 @@ def preset_modes(self) -> list[str] | None: async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" - if preset_mode not in self.preset_modes: + if self.preset_modes is None or preset_mode not in self.preset_modes: raise ValueError( "{preset_mode} is not a valid preset_mode: {self.preset_modes}" ) diff --git a/homeassistant/components/demo/light.py b/homeassistant/components/demo/light.py index 13e15eb274ee0c..9bb3a30686d559 100644 --- a/homeassistant/components/demo/light.py +++ b/homeassistant/components/demo/light.py @@ -195,17 +195,17 @@ def color_mode(self) -> str | None: return self._color_mode @property - def hs_color(self) -> tuple: + def hs_color(self) -> tuple[float, float]: """Return the hs color value.""" return self._hs_color @property - def rgbw_color(self) -> tuple: + def rgbw_color(self) -> tuple[int, int, int, int]: """Return the rgbw color value.""" return self._rgbw_color @property - def rgbww_color(self) -> tuple: + def rgbww_color(self) -> tuple[int, int, int, int, int]: """Return the rgbww color value.""" return self._rgbww_color diff --git a/homeassistant/components/demo/number.py b/homeassistant/components/demo/number.py index dd22c5f9827138..8660604af9ea79 100644 --- a/homeassistant/components/demo/number.py +++ b/homeassistant/components/demo/number.py @@ -105,13 +105,10 @@ def __init__( if step is not None: self._attr_step = step - @property - def device_info(self) -> DeviceInfo: - """Return device info.""" - return DeviceInfo( + self._attr_device_info = DeviceInfo( identifiers={ # Serial numbers are unique identifiers within a specific domain - (DOMAIN, self.unique_id) + (DOMAIN, unique_id) }, name=self.name, ) diff --git a/homeassistant/components/demo/remote.py b/homeassistant/components/demo/remote.py index 2e06b009c9cfde..f2c1ce11b0a697 100644 --- a/homeassistant/components/demo/remote.py +++ b/homeassistant/components/demo/remote.py @@ -46,7 +46,7 @@ def __init__(self, name: str | None, state: bool, icon: str | None) -> None: self._attr_name = name or DEVICE_DEFAULT_NAME self._attr_is_on = state self._attr_icon = icon - self._last_command_sent = None + self._last_command_sent: str | None = None @property def extra_state_attributes(self) -> dict[str, Any] | None: diff --git a/homeassistant/components/demo/siren.py b/homeassistant/components/demo/siren.py index 32d3de4b497ce1..9cf18a4c9025d3 100644 --- a/homeassistant/components/demo/siren.py +++ b/homeassistant/components/demo/siren.py @@ -54,7 +54,7 @@ class DemoSiren(SirenEntity): def __init__( self, name: str, - available_tones: str | None = None, + available_tones: list[str | int] | None = None, support_volume_set: bool = False, support_duration: bool = False, is_on: bool = True, diff --git a/homeassistant/components/demo/switch.py b/homeassistant/components/demo/switch.py index c61e39d085b98d..217119e93721c3 100644 --- a/homeassistant/components/demo/switch.py +++ b/homeassistant/components/demo/switch.py @@ -64,12 +64,8 @@ def __init__( self._attr_is_on = state self._attr_name = name or DEVICE_DEFAULT_NAME self._attr_unique_id = unique_id - - @property - def device_info(self) -> DeviceInfo: - """Return device info.""" - return DeviceInfo( - identifiers={(DOMAIN, self.unique_id)}, + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, unique_id)}, name=self.name, ) diff --git a/mypy.ini b/mypy.ini index c5afffd5638e44..2ad49bfdc4cdeb 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2196,24 +2196,6 @@ ignore_errors = true [mypy-homeassistant.components.deconz.switch] ignore_errors = true -[mypy-homeassistant.components.demo.fan] -ignore_errors = true - -[mypy-homeassistant.components.demo.light] -ignore_errors = true - -[mypy-homeassistant.components.demo.number] -ignore_errors = true - -[mypy-homeassistant.components.demo.remote] -ignore_errors = true - -[mypy-homeassistant.components.demo.siren] -ignore_errors = true - -[mypy-homeassistant.components.demo.switch] -ignore_errors = true - [mypy-homeassistant.components.denonavr.config_flow] ignore_errors = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index 376f33de65bdeb..f776f1b635bd51 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -38,12 +38,6 @@ "homeassistant.components.deconz.services", "homeassistant.components.deconz.siren", "homeassistant.components.deconz.switch", - "homeassistant.components.demo.fan", - "homeassistant.components.demo.light", - "homeassistant.components.demo.number", - "homeassistant.components.demo.remote", - "homeassistant.components.demo.siren", - "homeassistant.components.demo.switch", "homeassistant.components.denonavr.config_flow", "homeassistant.components.denonavr.media_player", "homeassistant.components.denonavr.receiver", From 8760cb035abf0bca6db80841c3d9126bf0af19a9 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 10 Feb 2022 10:17:37 +0100 Subject: [PATCH 0498/1098] More cleanup in Plugwise switch (#66254) --- homeassistant/components/plugwise/entity.py | 7 ++++++ homeassistant/components/plugwise/switch.py | 26 ++++++++------------- tests/components/plugwise/test_switch.py | 24 +++++++++---------- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/plugwise/entity.py b/homeassistant/components/plugwise/entity.py index 349ee6d05e9526..d57681ae504983 100644 --- a/homeassistant/components/plugwise/entity.py +++ b/homeassistant/components/plugwise/entity.py @@ -1,6 +1,8 @@ """Generic Plugwise Entity Class.""" from __future__ import annotations +from typing import Any + from homeassistant.const import ATTR_NAME, ATTR_VIA_DEVICE, CONF_HOST from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -53,6 +55,11 @@ def available(self) -> bool: """Return if entity is available.""" return super().available and self._dev_id in self.coordinator.data.devices + @property + def device(self) -> dict[str, Any]: + """Return data for this device.""" + return self.coordinator.data.devices[self._dev_id] + async def async_added_to_hass(self) -> None: """Subscribe to updates.""" self._handle_coordinator_update() diff --git a/homeassistant/components/plugwise/switch.py b/homeassistant/components/plugwise/switch.py index 5365d5834a8373..e7fb0b6f3dbc89 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -5,10 +5,10 @@ from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN, LOGGER +from .const import DOMAIN from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity from .util import plugwise_command @@ -51,14 +51,19 @@ def __init__( super().__init__(coordinator, device_id) self.entity_description = description self._attr_unique_id = f"{device_id}-{description.key}" - self._attr_name = coordinator.data.devices[device_id].get("name") + self._attr_name = (f"{self.device.get('name', '')} {description.name}").lstrip() + + @property + def is_on(self) -> bool | None: + """Return True if entity is on.""" + return self.device["switches"].get(self.entity_description.key) @plugwise_command async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" await self.coordinator.api.set_switch_state( self._dev_id, - self.coordinator.data.devices[self._dev_id].get("members"), + self.device.get("members"), self.entity_description.key, "on", ) @@ -68,18 +73,7 @@ async def async_turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" await self.coordinator.api.set_switch_state( self._dev_id, - self.coordinator.data.devices[self._dev_id].get("members"), + self.device.get("members"), self.entity_description.key, "off", ) - - @callback - def _handle_coordinator_update(self) -> None: - """Handle updated data from the coordinator.""" - if not (data := self.coordinator.data.devices.get(self._dev_id)): - LOGGER.error("Received no data for device %s", self._dev_id) - super()._handle_coordinator_update() - return - - self._attr_is_on = data["switches"].get(self.entity_description.key) - super()._handle_coordinator_update() diff --git a/tests/components/plugwise/test_switch.py b/tests/components/plugwise/test_switch.py index 2816b2dece68bb..4785a222f69d52 100644 --- a/tests/components/plugwise/test_switch.py +++ b/tests/components/plugwise/test_switch.py @@ -17,10 +17,10 @@ async def test_adam_climate_switch_entities(hass, mock_smile_adam): entry = await async_init_integration(hass, mock_smile_adam) assert entry.state is ConfigEntryState.LOADED - state = hass.states.get("switch.cv_pomp") + state = hass.states.get("switch.cv_pomp_relay") assert str(state.state) == "on" - state = hass.states.get("switch.fibaro_hc2") + state = hass.states.get("switch.fibaro_hc2_relay") assert str(state.state) == "on" @@ -34,7 +34,7 @@ async def test_adam_climate_switch_negative_testing(hass, mock_smile_adam): await hass.services.async_call( "switch", "turn_off", - {"entity_id": "switch.cv_pomp"}, + {"entity_id": "switch.cv_pomp_relay"}, blocking=True, ) @@ -47,7 +47,7 @@ async def test_adam_climate_switch_negative_testing(hass, mock_smile_adam): await hass.services.async_call( "switch", "turn_on", - {"entity_id": "switch.fibaro_hc2"}, + {"entity_id": "switch.fibaro_hc2_relay"}, blocking=True, ) @@ -65,7 +65,7 @@ async def test_adam_climate_switch_changes(hass, mock_smile_adam): await hass.services.async_call( "switch", "turn_off", - {"entity_id": "switch.cv_pomp"}, + {"entity_id": "switch.cv_pomp_relay"}, blocking=True, ) @@ -77,7 +77,7 @@ async def test_adam_climate_switch_changes(hass, mock_smile_adam): await hass.services.async_call( "switch", "toggle", - {"entity_id": "switch.fibaro_hc2"}, + {"entity_id": "switch.fibaro_hc2_relay"}, blocking=True, ) @@ -89,7 +89,7 @@ async def test_adam_climate_switch_changes(hass, mock_smile_adam): await hass.services.async_call( "switch", "turn_on", - {"entity_id": "switch.fibaro_hc2"}, + {"entity_id": "switch.fibaro_hc2_relay"}, blocking=True, ) @@ -104,10 +104,10 @@ async def test_stretch_switch_entities(hass, mock_stretch): entry = await async_init_integration(hass, mock_stretch) assert entry.state is ConfigEntryState.LOADED - state = hass.states.get("switch.koelkast_92c4a") + state = hass.states.get("switch.koelkast_92c4a_relay") assert str(state.state) == "on" - state = hass.states.get("switch.droger_52559") + state = hass.states.get("switch.droger_52559_relay") assert str(state.state) == "on" @@ -119,7 +119,7 @@ async def test_stretch_switch_changes(hass, mock_stretch): await hass.services.async_call( "switch", "turn_off", - {"entity_id": "switch.koelkast_92c4a"}, + {"entity_id": "switch.koelkast_92c4a_relay"}, blocking=True, ) assert mock_stretch.set_switch_state.call_count == 1 @@ -130,7 +130,7 @@ async def test_stretch_switch_changes(hass, mock_stretch): await hass.services.async_call( "switch", "toggle", - {"entity_id": "switch.droger_52559"}, + {"entity_id": "switch.droger_52559_relay"}, blocking=True, ) assert mock_stretch.set_switch_state.call_count == 2 @@ -141,7 +141,7 @@ async def test_stretch_switch_changes(hass, mock_stretch): await hass.services.async_call( "switch", "turn_on", - {"entity_id": "switch.droger_52559"}, + {"entity_id": "switch.droger_52559_relay"}, blocking=True, ) assert mock_stretch.set_switch_state.call_count == 3 From 47d6f75c1762846410c2d48173d971181af6423c Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 10 Feb 2022 10:59:54 +0100 Subject: [PATCH 0499/1098] Enable basic type checking in template (#66222) * Fix binary_sensor * Adjust button * Adjust fan * Adjust select * Adjust template_entity * Adjust trigger_entity * Adjust weather * Adjust init * Adjust number * Adjust None check --- homeassistant/components/template/__init__.py | 4 ++-- .../components/template/binary_sensor.py | 8 +++---- homeassistant/components/template/button.py | 3 ++- homeassistant/components/template/fan.py | 20 +++++----------- homeassistant/components/template/number.py | 1 + homeassistant/components/template/select.py | 3 ++- .../components/template/template_entity.py | 16 ++++++++----- .../components/template/trigger_entity.py | 6 +++-- mypy.ini | 24 ------------------- script/hassfest/mypy_config.py | 8 ------- 10 files changed, 31 insertions(+), 62 deletions(-) diff --git a/homeassistant/components/template/__init__.py b/homeassistant/components/template/__init__.py index 318b457c24ba23..a1231708b91000 100644 --- a/homeassistant/components/template/__init__.py +++ b/homeassistant/components/template/__init__.py @@ -61,7 +61,7 @@ async def _reload_config(call: Event | ServiceCall) -> None: return True -async def _process_config(hass, hass_config): +async def _process_config(hass: HomeAssistant, hass_config: ConfigType) -> None: """Process config.""" coordinators: list[TriggerUpdateCoordinator] | None = hass.data.pop(DOMAIN, None) @@ -126,7 +126,7 @@ def async_remove(self): if self._unsub_trigger: self._unsub_trigger() - async def async_setup(self: HomeAssistant, hass_config: ConfigType) -> bool: + async def async_setup(self, hass_config: ConfigType) -> None: """Set up the trigger and create entities.""" if self.hass.state == CoreState.running: await self._attach_triggers() diff --git a/homeassistant/components/template/binary_sensor.py b/homeassistant/components/template/binary_sensor.py index d25784688860d7..4c080c736d04fe 100644 --- a/homeassistant/components/template/binary_sensor.py +++ b/homeassistant/components/template/binary_sensor.py @@ -31,7 +31,7 @@ CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.exceptions import TemplateError from homeassistant.helpers import template import homeassistant.helpers.config_validation as cv @@ -300,12 +300,12 @@ def __init__( self._to_render_simple.append(key) self._parse_result.add(key) - self._delay_cancel = None + self._delay_cancel: CALLBACK_TYPE | None = None self._auto_off_cancel = None - self._state = None + self._state: bool | None = None @property - def is_on(self) -> bool: + def is_on(self) -> bool | None: """Return state of the sensor.""" return self._state diff --git a/homeassistant/components/template/button.py b/homeassistant/components/template/button.py index 86f481d7032c87..d6f41649734baf 100644 --- a/homeassistant/components/template/button.py +++ b/homeassistant/components/template/button.py @@ -63,7 +63,7 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the template button.""" - if "coordinator" in discovery_info: + if not discovery_info or "coordinator" in discovery_info: raise PlatformNotReady( "The template button platform doesn't support trigger entities" ) @@ -86,6 +86,7 @@ def __init__( ) -> None: """Initialize the button.""" super().__init__(hass, config=config, unique_id=unique_id) + assert self._attr_name is not None self._command_press = Script(hass, config[CONF_PRESS], self._attr_name, DOMAIN) self._attr_device_class = config.get(CONF_DEVICE_CLASS) self._attr_state = None diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index 9d0a32f3eddd32..1ddd37ba7bc08c 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -2,6 +2,7 @@ from __future__ import annotations import logging +from typing import Any import voluptuous as vol @@ -63,7 +64,6 @@ CONF_SET_PRESET_MODE_ACTION = "set_preset_mode" _VALID_STATES = [STATE_ON, STATE_OFF] -_VALID_OSC = [True, False] _VALID_DIRECTIONS = [DIRECTION_FORWARD, DIRECTION_REVERSE] FAN_SCHEMA = vol.All( @@ -273,8 +273,7 @@ async def async_turn_on( elif speed is not None: await self.async_set_speed(speed) - # pylint: disable=arguments-differ - async def async_turn_off(self) -> None: + async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the fan.""" await self._off_script.async_run(context=self._context) self._state = STATE_OFF @@ -316,17 +315,10 @@ async def async_oscillate(self, oscillating: bool) -> None: if self._set_oscillating_script is None: return - if oscillating in _VALID_OSC: - self._oscillating = oscillating - await self._set_oscillating_script.async_run( - {ATTR_OSCILLATING: oscillating}, context=self._context - ) - else: - _LOGGER.error( - "Received invalid oscillating value: %s. Expected: %s", - oscillating, - ", ".join(_VALID_OSC), - ) + self._oscillating = oscillating + await self._set_oscillating_script.async_run( + {ATTR_OSCILLATING: oscillating}, context=self._context + ) async def async_set_direction(self, direction: str) -> None: """Set the direction of the fan.""" diff --git a/homeassistant/components/template/number.py b/homeassistant/components/template/number.py index 036485cd363c8c..89adb45a6d0a06 100644 --- a/homeassistant/components/template/number.py +++ b/homeassistant/components/template/number.py @@ -108,6 +108,7 @@ def __init__( ) -> None: """Initialize the number.""" super().__init__(hass, config=config, unique_id=unique_id) + assert self._attr_name is not None self._value_template = config[CONF_STATE] self._command_set_value = Script( hass, config[CONF_SET_VALUE], self._attr_name, DOMAIN diff --git a/homeassistant/components/template/select.py b/homeassistant/components/template/select.py index 2829583531826c..fa2668c1e8151b 100644 --- a/homeassistant/components/template/select.py +++ b/homeassistant/components/template/select.py @@ -102,13 +102,14 @@ def __init__( ) -> None: """Initialize the select.""" super().__init__(hass, config=config, unique_id=unique_id) + assert self._attr_name is not None self._value_template = config[CONF_STATE] self._command_select_option = Script( hass, config[CONF_SELECT_OPTION], self._attr_name, DOMAIN ) self._options_template = config[ATTR_OPTIONS] self._attr_assumed_state = self._optimistic = config[CONF_OPTIMISTIC] - self._attr_options = None + self._attr_options = [] self._attr_current_option = None async def async_added_to_hass(self) -> None: diff --git a/homeassistant/components/template/template_entity.py b/homeassistant/components/template/template_entity.py index ee4b62447573fe..5e592e5d71730a 100644 --- a/homeassistant/components/template/template_entity.py +++ b/homeassistant/components/template/template_entity.py @@ -16,13 +16,13 @@ CONF_ICON, CONF_ICON_TEMPLATE, CONF_NAME, + EVENT_HOMEASSISTANT_START, ) -from homeassistant.core import EVENT_HOMEASSISTANT_START, CoreState, callback +from homeassistant.core import CoreState, Event, callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import ( - Event, TrackTemplate, TrackTemplateResult, async_track_template_result, @@ -94,7 +94,7 @@ def rewrite_common_legacy_to_modern_conf( entity_cfg: dict[str, Any], extra_legacy_fields: dict[str, str] = None -) -> list[dict]: +) -> dict[str, Any]: """Rewrite legacy config.""" entity_cfg = {**entity_cfg} if extra_legacy_fields is None: @@ -176,10 +176,12 @@ def handle_result( if self.none_on_template_error: self._default_update(result) else: + assert self.on_update self.on_update(result) return if not self.validator: + assert self.on_update self.on_update(result) return @@ -197,9 +199,11 @@ def handle_result( self._entity.entity_id, ex.msg, ) + assert self.on_update self.on_update(None) return + assert self.on_update self.on_update(validated) return @@ -321,11 +325,11 @@ def add_template_attribute( """ assert self.hass is not None, "hass cannot be None" template.hass = self.hass - attribute = _TemplateAttribute( + template_attribute = _TemplateAttribute( self, attribute, template, validator, on_update, none_on_template_error ) self._template_attrs.setdefault(template, []) - self._template_attrs[template].append(attribute) + self._template_attrs[template].append(template_attribute) @callback def _handle_results( @@ -362,7 +366,7 @@ def _handle_results( self.async_write_ha_state() async def _async_template_startup(self, *_) -> None: - template_var_tups = [] + template_var_tups: list[TrackTemplate] = [] has_availability_template = False for template, attributes in self._template_attrs.items(): template_var_tup = TrackTemplate(template, None) diff --git a/homeassistant/components/template/trigger_entity.py b/homeassistant/components/template/trigger_entity.py index d4bc96e43bf4ce..2768609e65da5d 100644 --- a/homeassistant/components/template/trigger_entity.py +++ b/homeassistant/components/template/trigger_entity.py @@ -12,6 +12,7 @@ CONF_UNIT_OF_MEASUREMENT, ) from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import TemplateError from homeassistant.helpers import template, update_coordinator from . import TriggerUpdateCoordinator @@ -36,6 +37,7 @@ def __init__( entity_unique_id = config.get(CONF_UNIQUE_ID) + self._unique_id: str | None if entity_unique_id and coordinator.unique_id: self._unique_id = f"{coordinator.unique_id}-{entity_unique_id}" else: @@ -45,7 +47,7 @@ def __init__( self._static_rendered = {} self._to_render_simple = [] - self._to_render_complex = [] + self._to_render_complex: list[str] = [] for itm in ( CONF_NAME, @@ -148,7 +150,7 @@ def _process_data(self) -> None: ) self._rendered = rendered - except template.TemplateError as err: + except TemplateError as err: logging.getLogger(f"{__package__}.{self.entity_id.split('.')[0]}").error( "Error rendering %s template for %s: %s", key, self.entity_id, err ) diff --git a/mypy.ini b/mypy.ini index 2ad49bfdc4cdeb..d4db04e8e8213a 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2619,36 +2619,12 @@ ignore_errors = true [mypy-homeassistant.components.telegram_bot.polling] ignore_errors = true -[mypy-homeassistant.components.template] -ignore_errors = true - -[mypy-homeassistant.components.template.binary_sensor] -ignore_errors = true - -[mypy-homeassistant.components.template.button] -ignore_errors = true - -[mypy-homeassistant.components.template.fan] -ignore_errors = true - [mypy-homeassistant.components.template.number] ignore_errors = true -[mypy-homeassistant.components.template.select] -ignore_errors = true - [mypy-homeassistant.components.template.sensor] ignore_errors = true -[mypy-homeassistant.components.template.template_entity] -ignore_errors = true - -[mypy-homeassistant.components.template.trigger_entity] -ignore_errors = true - -[mypy-homeassistant.components.template.weather] -ignore_errors = true - [mypy-homeassistant.components.toon] ignore_errors = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index f776f1b635bd51..99104702f26c13 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -179,16 +179,8 @@ "homeassistant.components.sonos.statistics", "homeassistant.components.system_health", "homeassistant.components.telegram_bot.polling", - "homeassistant.components.template", - "homeassistant.components.template.binary_sensor", - "homeassistant.components.template.button", - "homeassistant.components.template.fan", "homeassistant.components.template.number", - "homeassistant.components.template.select", "homeassistant.components.template.sensor", - "homeassistant.components.template.template_entity", - "homeassistant.components.template.trigger_entity", - "homeassistant.components.template.weather", "homeassistant.components.toon", "homeassistant.components.toon.config_flow", "homeassistant.components.toon.models", From f5fff95e8b65c7151d8d3250d1bce9d2af94b051 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 10 Feb 2022 11:12:38 +0100 Subject: [PATCH 0500/1098] Tweak constant config_entries.DISCOVERY_SOURCES (#66249) --- homeassistant/config_entries.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 0af31b477303dd..f5cef1e7484e87 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -36,6 +36,7 @@ _LOGGER = logging.getLogger(__name__) +SOURCE_DHCP = "dhcp" SOURCE_DISCOVERY = "discovery" SOURCE_HASSIO = "hassio" SOURCE_HOMEKIT = "homekit" @@ -46,7 +47,6 @@ SOURCE_USB = "usb" SOURCE_USER = "user" SOURCE_ZEROCONF = "zeroconf" -SOURCE_DHCP = "dhcp" # If a user wants to hide a discovery from the UI they can "Ignore" it. The config_entries/ignore_flow # websocket command creates a config entry with this source and while it exists normal discoveries @@ -108,17 +108,16 @@ def recoverable(self) -> bool: DEFAULT_DISCOVERY_UNIQUE_ID = "default_discovery_unique_id" DISCOVERY_NOTIFICATION_ID = "config_entry_discovery" DISCOVERY_SOURCES = ( - SOURCE_SSDP, - SOURCE_USB, - SOURCE_DHCP, - SOURCE_HOMEKIT, - SOURCE_ZEROCONF, - SOURCE_HOMEKIT, SOURCE_DHCP, SOURCE_DISCOVERY, + SOURCE_HOMEKIT, SOURCE_IMPORT, SOURCE_INTEGRATION_DISCOVERY, + SOURCE_MQTT, + SOURCE_SSDP, SOURCE_UNIGNORE, + SOURCE_USB, + SOURCE_ZEROCONF, ) RECONFIGURE_NOTIFICATION_ID = "config_entry_reconfigure" From 4cad29d7d4820fcaaec64cf361f040f80413005b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 10 Feb 2022 11:17:35 +0100 Subject: [PATCH 0501/1098] More cleanup in Plugwise binary sensor (#66255) --- .../components/plugwise/binary_sensor.py | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index f6118dde3701ed..c9053e8468e368 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -1,18 +1,20 @@ """Plugwise Binary Sensor component for Home Assistant.""" from __future__ import annotations +from collections.abc import Mapping from dataclasses import dataclass +from typing import Any from homeassistant.components.binary_sensor import ( BinarySensorEntity, BinarySensorEntityDescription, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN, LOGGER +from .const import DOMAIN from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity @@ -95,35 +97,33 @@ def __init__( super().__init__(coordinator, device_id) self.entity_description = description self._attr_unique_id = f"{device_id}-{description.key}" - self._attr_name = ( - f"{coordinator.data.devices[device_id].get('name', '')} {description.name}" - ).lstrip() - - @callback - def _handle_coordinator_update(self) -> None: - """Handle updated data from the coordinator.""" - if not (data := self.coordinator.data.devices.get(self._dev_id)): - LOGGER.error("Received no data for device %s", self._dev_id) - super()._handle_coordinator_update() - return - - state = data["binary_sensors"].get(self.entity_description.key) - self._attr_is_on = state - if icon_off := self.entity_description.icon_off: - self._attr_icon = self.entity_description.icon if state else icon_off - - # Add entity attribute for Plugwise notifications - if self.entity_description.key == "plugwise_notification": - self._attr_extra_state_attributes = { - f"{severity}_msg": [] for severity in SEVERITIES - } - - if notify := self.coordinator.data.gateway["notifications"]: - for details in notify.values(): - for msg_type, msg in details.items(): - msg_type = msg_type.lower() - if msg_type not in SEVERITIES: - msg_type = "other" - self._attr_extra_state_attributes[f"{msg_type}_msg"].append(msg) - - super()._handle_coordinator_update() + self._attr_name = (f"{self.device.get('name', '')} {description.name}").lstrip() + + @property + def is_on(self) -> bool | None: + """Return true if the binary sensor is on.""" + return self.device["binary_sensors"].get(self.entity_description.key) + + @property + def icon(self) -> str | None: + """Return the icon to use in the frontend, if any.""" + if (icon_off := self.entity_description.icon_off) and self.is_on is False: + return icon_off + return self.entity_description.icon + + @property + def extra_state_attributes(self) -> Mapping[str, Any] | None: + """Return entity specific state attributes.""" + if self.entity_description.key != "plugwise_notification": + return None + + attrs: dict[str, list[str]] = {f"{severity}_msg": [] for severity in SEVERITIES} + if notify := self.coordinator.data.gateway["notifications"]: + for details in notify.values(): + for msg_type, msg in details.items(): + msg_type = msg_type.lower() + if msg_type not in SEVERITIES: + msg_type = "other" + attrs[f"{msg_type}_msg"].append(msg) + + return attrs From 3896b4a31d993587aeb432a16f0b8627217ccb44 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 10 Feb 2022 15:49:19 +0100 Subject: [PATCH 0502/1098] Add additional switches to Plugwise (#66261) --- homeassistant/components/plugwise/switch.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/plugwise/switch.py b/homeassistant/components/plugwise/switch.py index e7fb0b6f3dbc89..45a10297ed5571 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -3,9 +3,14 @@ from typing import Any -from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription +from homeassistant.components.switch import ( + SwitchDeviceClass, + SwitchEntity, + SwitchEntityDescription, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN @@ -14,10 +19,22 @@ from .util import plugwise_command SWITCHES: tuple[SwitchEntityDescription, ...] = ( + SwitchEntityDescription( + key="dhw_cm_switch", + name="DHW Comfort Mode", + icon="mdi:water-plus", + entity_category=EntityCategory.CONFIG, + ), + SwitchEntityDescription( + key="lock", + name="Lock", + icon="mdi:lock", + entity_category=EntityCategory.CONFIG, + ), SwitchEntityDescription( key="relay", name="Relay", - icon="mdi:electric-switch", + device_class=SwitchDeviceClass.SWITCH, ), ) From 51e14cebe34106177ff2eb406811ae2a625e8b0f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 10 Feb 2022 15:55:27 +0100 Subject: [PATCH 0503/1098] Remove Plugwise Auxiliary sensors (#66259) --- homeassistant/components/plugwise/const.py | 16 ------- homeassistant/components/plugwise/sensor.py | 52 +-------------------- 2 files changed, 2 insertions(+), 66 deletions(-) diff --git a/homeassistant/components/plugwise/const.py b/homeassistant/components/plugwise/const.py index 506f20c27680f5..1beeff501b05b3 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -39,26 +39,10 @@ # Default directives DEFAULT_MAX_TEMP = 30 DEFAULT_MIN_TEMP = 4 -DEFAULT_NAME = "Smile" DEFAULT_PORT = 80 DEFAULT_SCAN_INTERVAL = { "power": timedelta(seconds=10), "stretch": timedelta(seconds=60), "thermostat": timedelta(seconds=60), } -DEFAULT_TIMEOUT = 60 DEFAULT_USERNAME = "smile" - -# Configuration directives -CONF_GAS = "gas" -CONF_MAX_TEMP = "max_temp" -CONF_MIN_TEMP = "min_temp" -CONF_POWER = "power" -CONF_THERMOSTAT = "thermostat" - -# Icons -COOL_ICON = "mdi:snowflake" -FLAME_ICON = "mdi:fire" -FLOW_OFF_ICON = "mdi:water-pump-off" -FLOW_ON_ICON = "mdi:water-pump" -IDLE_ICON = "mdi:circle-off-outline" diff --git a/homeassistant/components/plugwise/sensor.py b/homeassistant/components/plugwise/sensor.py index fa516c88c6cf97..92867a3dfe0133 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -20,7 +20,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import COOL_ICON, DOMAIN, FLAME_ICON, IDLE_ICON, LOGGER, UNIT_LUMEN +from .const import DOMAIN, LOGGER, UNIT_LUMEN from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity @@ -295,21 +295,6 @@ async def async_setup_entry( ) ) - if coordinator.data.gateway["single_master_thermostat"] is False: - # These sensors should actually be binary sensors. - for description in INDICATE_ACTIVE_LOCAL_DEVICE_SENSORS: - if description.key not in device: - continue - - entities.append( - PlugwiseAuxSensorEntity( - coordinator, - device_id, - description, - ) - ) - break - async_add_entities(entities) @@ -326,9 +311,7 @@ def __init__( super().__init__(coordinator, device_id) self.entity_description = description self._attr_unique_id = f"{device_id}-{description.key}" - self._attr_name = ( - f"{coordinator.data.devices[device_id].get('name', '')} {description.name}" - ).lstrip() + self._attr_name = (f"{self.device.get('name', '')} {description.name}").lstrip() @callback def _handle_coordinator_update(self) -> None: @@ -340,34 +323,3 @@ def _handle_coordinator_update(self) -> None: self._attr_native_value = data["sensors"].get(self.entity_description.key) super()._handle_coordinator_update() - - -class PlugwiseAuxSensorEntity(PlugwiseSensorEnity): - """Auxiliary Device Sensors.""" - - _cooling_state = False - _heating_state = False - - @callback - def _handle_coordinator_update(self) -> None: - """Handle updated data from the coordinator.""" - if not (data := self.coordinator.data.devices.get(self._dev_id)): - LOGGER.error("Received no data for device %s", self._dev_id) - super()._handle_coordinator_update() - return - - if data.get("heating_state") is not None: - self._heating_state = data["heating_state"] - if data.get("cooling_state") is not None: - self._cooling_state = data["cooling_state"] - - self._attr_native_value = "idle" - self._attr_icon = IDLE_ICON - if self._heating_state: - self._attr_native_value = "heating" - self._attr_icon = FLAME_ICON - if self._cooling_state: - self._attr_native_value = "cooling" - self._attr_icon = COOL_ICON - - super()._handle_coordinator_update() From 0fb2c78b6dcb50438be909f5290c9b1222b440ab Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 10 Feb 2022 09:08:33 -0600 Subject: [PATCH 0504/1098] Add RGBW/RGBWW support to WiZ (#66196) --- homeassistant/components/wiz/light.py | 90 +++++++------ homeassistant/components/wiz/manifest.json | 2 +- homeassistant/components/wiz/utils.py | 10 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/wiz/__init__.py | 142 ++++++++++++++++++++- tests/components/wiz/test_config_flow.py | 119 +++++++++-------- tests/components/wiz/test_light.py | 30 +++++ 8 files changed, 279 insertions(+), 118 deletions(-) create mode 100644 tests/components/wiz/test_light.py diff --git a/homeassistant/components/wiz/light.py b/homeassistant/components/wiz/light.py index 2c4485c5b72a94..d0473c4f5c3324 100644 --- a/homeassistant/components/wiz/light.py +++ b/homeassistant/components/wiz/light.py @@ -1,22 +1,22 @@ """WiZ integration light platform.""" from __future__ import annotations -import logging from typing import Any from pywizlight import PilotBuilder from pywizlight.bulblibrary import BulbClass, BulbType, Features -from pywizlight.rgbcw import convertHSfromRGBCW from pywizlight.scenes import get_id_from_scene_name from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, - ATTR_HS_COLOR, + ATTR_RGBW_COLOR, + ATTR_RGBWW_COLOR, COLOR_MODE_BRIGHTNESS, COLOR_MODE_COLOR_TEMP, - COLOR_MODE_HS, + COLOR_MODE_RGBW, + COLOR_MODE_RGBWW, SUPPORT_EFFECT, LightEntity, ) @@ -32,7 +32,30 @@ from .entity import WizToggleEntity from .models import WizData -_LOGGER = logging.getLogger(__name__) + +def _async_pilot_builder(**kwargs: Any) -> PilotBuilder: + """Create the PilotBuilder for turn on.""" + brightness = kwargs.get(ATTR_BRIGHTNESS) + + if ATTR_RGBWW_COLOR in kwargs: + return PilotBuilder(brightness=brightness, rgbww=kwargs[ATTR_RGBWW_COLOR]) + + if ATTR_RGBW_COLOR in kwargs: + return PilotBuilder(brightness=brightness, rgbw=kwargs[ATTR_RGBW_COLOR]) + + if ATTR_COLOR_TEMP in kwargs: + return PilotBuilder( + brightness=brightness, + colortemp=color_temperature_mired_to_kelvin(kwargs[ATTR_COLOR_TEMP]), + ) + + if ATTR_EFFECT in kwargs: + scene_id = get_id_from_scene_name(kwargs[ATTR_EFFECT]) + if scene_id == 1000: # rhythm + return PilotBuilder() + return PilotBuilder(brightness=brightness, scene=scene_id) + + return PilotBuilder(brightness=brightness) async def async_setup_entry( @@ -56,7 +79,10 @@ def __init__(self, wiz_data: WizData, name: str) -> None: features: Features = bulb_type.features color_modes = set() if features.color: - color_modes.add(COLOR_MODE_HS) + if bulb_type.white_channels == 2: + color_modes.add(COLOR_MODE_RGBWW) + else: + color_modes.add(COLOR_MODE_RGBW) if features.color_tmp: color_modes.add(COLOR_MODE_COLOR_TEMP) if not color_modes and features.brightness: @@ -82,54 +108,26 @@ def _async_update_attrs(self) -> None: assert color_modes is not None if (brightness := state.get_brightness()) is not None: self._attr_brightness = max(0, min(255, brightness)) - if COLOR_MODE_COLOR_TEMP in color_modes and state.get_colortemp() is not None: + if ( + COLOR_MODE_COLOR_TEMP in color_modes + and (color_temp := state.get_colortemp()) is not None + ): self._attr_color_mode = COLOR_MODE_COLOR_TEMP - if color_temp := state.get_colortemp(): - self._attr_color_temp = color_temperature_kelvin_to_mired(color_temp) + self._attr_color_temp = color_temperature_kelvin_to_mired(color_temp) elif ( - COLOR_MODE_HS in color_modes - and (rgb := state.get_rgb()) is not None - and rgb[0] is not None + COLOR_MODE_RGBWW in color_modes and (rgbww := state.get_rgbww()) is not None ): - if (warm_white := state.get_warm_white()) is not None: - self._attr_hs_color = convertHSfromRGBCW(rgb, warm_white) - self._attr_color_mode = COLOR_MODE_HS + self._attr_rgbww_color = rgbww + self._attr_color_mode = COLOR_MODE_RGBWW + elif COLOR_MODE_RGBW in color_modes and (rgbw := state.get_rgbw()) is not None: + self._attr_rgbw_color = rgbw + self._attr_color_mode = COLOR_MODE_RGBW else: self._attr_color_mode = COLOR_MODE_BRIGHTNESS self._attr_effect = state.get_scene() super()._async_update_attrs() - @callback - def _async_pilot_builder(self, **kwargs: Any) -> PilotBuilder: - """Create the PilotBuilder for turn on.""" - brightness = kwargs.get(ATTR_BRIGHTNESS) - - if ATTR_HS_COLOR in kwargs: - return PilotBuilder( - hucolor=(kwargs[ATTR_HS_COLOR][0], kwargs[ATTR_HS_COLOR][1]), - brightness=brightness, - ) - - color_temp = None - if ATTR_COLOR_TEMP in kwargs: - color_temp = color_temperature_mired_to_kelvin(kwargs[ATTR_COLOR_TEMP]) - - scene_id = None - if ATTR_EFFECT in kwargs: - scene_id = get_id_from_scene_name(kwargs[ATTR_EFFECT]) - if scene_id == 1000: # rhythm - return PilotBuilder() - - _LOGGER.debug( - "[wizlight %s] Pilot will be sent with brightness=%s, color_temp=%s, scene_id=%s", - self._device.ip, - brightness, - color_temp, - scene_id, - ) - return PilotBuilder(brightness=brightness, colortemp=color_temp, scene=scene_id) - async def async_turn_on(self, **kwargs: Any) -> None: """Instruct the light to turn on.""" - await self._device.turn_on(self._async_pilot_builder(**kwargs)) + await self._device.turn_on(_async_pilot_builder(**kwargs)) await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/wiz/manifest.json b/homeassistant/components/wiz/manifest.json index 47a4f343d4fac4..07b306f1121dc2 100644 --- a/homeassistant/components/wiz/manifest.json +++ b/homeassistant/components/wiz/manifest.json @@ -8,7 +8,7 @@ ], "dependencies": ["network"], "documentation": "https://www.home-assistant.io/integrations/wiz", - "requirements": ["pywizlight==0.5.3"], + "requirements": ["pywizlight==0.5.5"], "iot_class": "local_push", "codeowners": ["@sbidy"] } diff --git a/homeassistant/components/wiz/utils.py b/homeassistant/components/wiz/utils.py index 42be575813037e..278989b5b2b99e 100644 --- a/homeassistant/components/wiz/utils.py +++ b/homeassistant/components/wiz/utils.py @@ -2,6 +2,7 @@ from __future__ import annotations from pywizlight import BulbType +from pywizlight.bulblibrary import BulbClass from .const import DEFAULT_NAME @@ -13,4 +14,11 @@ def _short_mac(mac: str) -> str: def name_from_bulb_type_and_mac(bulb_type: BulbType, mac: str) -> str: """Generate a name from bulb_type and mac.""" - return f"{DEFAULT_NAME} {bulb_type.bulb_type.value} {_short_mac(mac)}" + if bulb_type.bulb_type == BulbClass.RGB: + if bulb_type.white_channels == 2: + description = "RGBWW Tunable" + else: + description = "RGBW Tunable" + else: + description = bulb_type.bulb_type.value + return f"{DEFAULT_NAME} {description} {_short_mac(mac)}" diff --git a/requirements_all.txt b/requirements_all.txt index 5376c7a5f11c28..09e983c66f951b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2057,7 +2057,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.5.3 +pywizlight==0.5.5 # homeassistant.components.xeoma pyxeoma==1.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ae3fe7eb5c4181..78826760d0beea 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1282,7 +1282,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.5.3 +pywizlight==0.5.5 # homeassistant.components.zerproc pyzerproc==0.4.8 diff --git a/tests/components/wiz/__init__.py b/tests/components/wiz/__init__.py index c2f67982b84ecd..57650ede272d88 100644 --- a/tests/components/wiz/__init__.py +++ b/tests/components/wiz/__init__.py @@ -1,13 +1,63 @@ """Tests for the WiZ Platform integration.""" +from contextlib import contextmanager +from copy import deepcopy import json +from typing import Callable +from unittest.mock import AsyncMock, MagicMock, patch + +from pywizlight import SCENES, BulbType, PilotParser, wizlight +from pywizlight.bulblibrary import FEATURE_MAP, BulbClass, KelvinRange +from pywizlight.discovery import DiscoveredBulb from homeassistant.components.wiz.const import DOMAIN -from homeassistant.const import CONF_IP_ADDRESS, CONF_NAME +from homeassistant.const import CONF_HOST, CONF_NAME from homeassistant.helpers.typing import HomeAssistantType from tests.common import MockConfigEntry +FAKE_STATE = PilotParser( + { + "mac": "a8bb50818e7c", + "rssi": -55, + "src": "hb", + "mqttCd": 0, + "ts": 1644425347, + "state": True, + "sceneId": 0, + "r": 0, + "g": 0, + "b": 255, + "c": 0, + "w": 0, + "dimming": 100, + } +) +FAKE_IP = "1.1.1.1" +FAKE_MAC = "ABCABCABCABC" +FAKE_BULB_CONFIG = { + "method": "getSystemConfig", + "env": "pro", + "result": { + "mac": FAKE_MAC, + "homeId": 653906, + "roomId": 989983, + "moduleName": "ESP_0711_STR", + "fwVersion": "1.21.0", + "groupId": 0, + "drvConf": [20, 2], + "ewf": [255, 0, 255, 255, 0, 0, 0], + "ewfHex": "ff00ffff000000", + "ping": 0, + }, +} +FAKE_SOCKET_CONFIG = deepcopy(FAKE_BULB_CONFIG) +FAKE_SOCKET_CONFIG["result"]["moduleName"] = "ESP10_SOCKET_06" +FAKE_EXTENDED_WHITE_RANGE = [2200, 2700, 6500, 6500] +TEST_SYSTEM_INFO = {"id": FAKE_MAC, "name": "Test Bulb"} +TEST_CONNECTION = {CONF_HOST: "1.1.1.1"} +TEST_NO_IP = {CONF_HOST: "this is no IP input"} + FAKE_BULB_CONFIG = json.loads( '{"method":"getSystemConfig","env":"pro","result":\ {"mac":"ABCABCABCABC",\ @@ -33,10 +83,42 @@ "ewfHex":"ff00ffff000000",\ "ping":0}}' ) - -TEST_SYSTEM_INFO = {"id": "ABCABCABCABC", "name": "Test Bulb"} - -TEST_CONNECTION = {CONF_IP_ADDRESS: "1.1.1.1", CONF_NAME: "Test Bulb"} +FAKE_RGBWW_BULB = BulbType( + bulb_type=BulbClass.RGB, + name="ESP01_SHRGB_03", + features=FEATURE_MAP[BulbClass.RGB], + kelvin_range=KelvinRange(2700, 6500), + fw_version="1.0.0", + white_channels=2, + white_to_color_ratio=80, +) +FAKE_RGBW_BULB = BulbType( + bulb_type=BulbClass.RGB, + name="ESP01_SHRGB_03", + features=FEATURE_MAP[BulbClass.RGB], + kelvin_range=KelvinRange(2700, 6500), + fw_version="1.0.0", + white_channels=1, + white_to_color_ratio=80, +) +FAKE_DIMMABLE_BULB = BulbType( + bulb_type=BulbClass.DW, + name="ESP01_DW_03", + features=FEATURE_MAP[BulbClass.DW], + kelvin_range=KelvinRange(2700, 6500), + fw_version="1.0.0", + white_channels=1, + white_to_color_ratio=80, +) +FAKE_SOCKET = BulbType( + bulb_type=BulbClass.SOCKET, + name="ESP01_SOCKET_03", + features=FEATURE_MAP[BulbClass.SOCKET], + kelvin_range=KelvinRange(2700, 6500), + fw_version="1.0.0", + white_channels=2, + white_to_color_ratio=80, +) async def setup_integration( @@ -48,7 +130,7 @@ async def setup_integration( domain=DOMAIN, unique_id=TEST_SYSTEM_INFO["id"], data={ - CONF_IP_ADDRESS: "127.0.0.1", + CONF_HOST: "127.0.0.1", CONF_NAME: TEST_SYSTEM_INFO["name"], }, ) @@ -59,3 +141,51 @@ async def setup_integration( await hass.async_block_till_done() return entry + + +def _mocked_wizlight(device, extended_white_range, bulb_type) -> wizlight: + bulb = MagicMock(auto_spec=wizlight) + + async def _save_setup_callback(callback: Callable) -> None: + bulb.data_receive_callback = callback + + bulb.getBulbConfig = AsyncMock(return_value=device or FAKE_BULB_CONFIG) + bulb.getExtendedWhiteRange = AsyncMock( + return_value=extended_white_range or FAKE_EXTENDED_WHITE_RANGE + ) + bulb.getMac = AsyncMock(return_value=FAKE_MAC) + bulb.updateState = AsyncMock(return_value=FAKE_STATE) + bulb.getSupportedScenes = AsyncMock(return_value=list(SCENES)) + bulb.start_push = AsyncMock(side_effect=_save_setup_callback) + bulb.async_close = AsyncMock() + bulb.state = FAKE_STATE + bulb.mac = FAKE_MAC + bulb.bulbtype = bulb_type or FAKE_DIMMABLE_BULB + bulb.get_bulbtype = AsyncMock(return_value=bulb_type or FAKE_DIMMABLE_BULB) + + return bulb + + +def _patch_wizlight(device=None, extended_white_range=None, bulb_type=None): + @contextmanager + def _patcher(): + bulb = _mocked_wizlight(device, extended_white_range, bulb_type) + with patch("homeassistant.components.wiz.wizlight", return_value=bulb,), patch( + "homeassistant.components.wiz.config_flow.wizlight", + return_value=bulb, + ): + yield + + return _patcher() + + +def _patch_discovery(): + @contextmanager + def _patcher(): + with patch( + "homeassistant.components.wiz.discovery.find_wizlights", + return_value=[DiscoveredBulb(FAKE_IP, FAKE_MAC)], + ): + yield + + return _patcher() diff --git a/tests/components/wiz/test_config_flow.py b/tests/components/wiz/test_config_flow.py index 28645e53e14cb6..465bc3e3d2772d 100644 --- a/tests/components/wiz/test_config_flow.py +++ b/tests/components/wiz/test_config_flow.py @@ -1,10 +1,7 @@ """Test the WiZ Platform config flow.""" -from contextlib import contextmanager -from copy import deepcopy from unittest.mock import patch import pytest -from pywizlight.discovery import DiscoveredBulb from pywizlight.exceptions import WizLightConnectionError, WizLightTimeOutError from homeassistant import config_entries @@ -14,33 +11,23 @@ from homeassistant.const import CONF_HOST from homeassistant.data_entry_flow import RESULT_TYPE_ABORT, RESULT_TYPE_FORM -from tests.common import MockConfigEntry - -FAKE_IP = "1.1.1.1" -FAKE_MAC = "ABCABCABCABC" -FAKE_BULB_CONFIG = { - "method": "getSystemConfig", - "env": "pro", - "result": { - "mac": FAKE_MAC, - "homeId": 653906, - "roomId": 989983, - "moduleName": "ESP_0711_STR", - "fwVersion": "1.21.0", - "groupId": 0, - "drvConf": [20, 2], - "ewf": [255, 0, 255, 255, 0, 0, 0], - "ewfHex": "ff00ffff000000", - "ping": 0, - }, -} -FAKE_SOCKET_CONFIG = deepcopy(FAKE_BULB_CONFIG) -FAKE_SOCKET_CONFIG["result"]["moduleName"] = "ESP10_SOCKET_06" -FAKE_EXTENDED_WHITE_RANGE = [2200, 2700, 6500, 6500] -TEST_SYSTEM_INFO = {"id": FAKE_MAC, "name": "Test Bulb"} -TEST_CONNECTION = {CONF_HOST: "1.1.1.1"} -TEST_NO_IP = {CONF_HOST: "this is no IP input"} +from . import ( + FAKE_BULB_CONFIG, + FAKE_DIMMABLE_BULB, + FAKE_EXTENDED_WHITE_RANGE, + FAKE_IP, + FAKE_MAC, + FAKE_RGBW_BULB, + FAKE_RGBWW_BULB, + FAKE_SOCKET, + FAKE_SOCKET_CONFIG, + TEST_CONNECTION, + TEST_SYSTEM_INFO, + _patch_discovery, + _patch_wizlight, +) +from tests.common import MockConfigEntry DHCP_DISCOVERY = dhcp.DhcpServiceInfo( hostname="wiz_abcabc", @@ -55,36 +42,6 @@ } -def _patch_wizlight(device=None, extended_white_range=None): - @contextmanager - def _patcher(): - with patch( - "homeassistant.components.wiz.wizlight.getBulbConfig", - return_value=device or FAKE_BULB_CONFIG, - ), patch( - "homeassistant.components.wiz.wizlight.getExtendedWhiteRange", - return_value=extended_white_range or FAKE_EXTENDED_WHITE_RANGE, - ), patch( - "homeassistant.components.wiz.wizlight.getMac", - return_value=FAKE_MAC, - ): - yield - - return _patcher() - - -def _patch_discovery(): - @contextmanager - def _patcher(): - with patch( - "homeassistant.components.wiz.discovery.find_wizlights", - return_value=[DiscoveredBulb(FAKE_IP, FAKE_MAC)], - ): - yield - - return _patcher() - - async def test_form(hass): """Test we get the form.""" result = await hass.config_entries.flow.async_init( @@ -227,12 +184,13 @@ async def test_discovered_by_dhcp_connection_fails(hass, source, data): @pytest.mark.parametrize( - "source, data, device, extended_white_range, name", + "source, data, device, bulb_type, extended_white_range, name", [ ( config_entries.SOURCE_DHCP, DHCP_DISCOVERY, FAKE_BULB_CONFIG, + FAKE_DIMMABLE_BULB, FAKE_EXTENDED_WHITE_RANGE, "WiZ Dimmable White ABCABC", ), @@ -240,13 +198,47 @@ async def test_discovered_by_dhcp_connection_fails(hass, source, data): config_entries.SOURCE_INTEGRATION_DISCOVERY, INTEGRATION_DISCOVERY, FAKE_BULB_CONFIG, + FAKE_DIMMABLE_BULB, FAKE_EXTENDED_WHITE_RANGE, "WiZ Dimmable White ABCABC", ), + ( + config_entries.SOURCE_DHCP, + DHCP_DISCOVERY, + FAKE_BULB_CONFIG, + FAKE_RGBW_BULB, + FAKE_EXTENDED_WHITE_RANGE, + "WiZ RGBW Tunable ABCABC", + ), + ( + config_entries.SOURCE_INTEGRATION_DISCOVERY, + INTEGRATION_DISCOVERY, + FAKE_BULB_CONFIG, + FAKE_RGBW_BULB, + FAKE_EXTENDED_WHITE_RANGE, + "WiZ RGBW Tunable ABCABC", + ), + ( + config_entries.SOURCE_DHCP, + DHCP_DISCOVERY, + FAKE_BULB_CONFIG, + FAKE_RGBWW_BULB, + FAKE_EXTENDED_WHITE_RANGE, + "WiZ RGBWW Tunable ABCABC", + ), + ( + config_entries.SOURCE_INTEGRATION_DISCOVERY, + INTEGRATION_DISCOVERY, + FAKE_BULB_CONFIG, + FAKE_RGBWW_BULB, + FAKE_EXTENDED_WHITE_RANGE, + "WiZ RGBWW Tunable ABCABC", + ), ( config_entries.SOURCE_DHCP, DHCP_DISCOVERY, FAKE_SOCKET_CONFIG, + FAKE_SOCKET, None, "WiZ Socket ABCABC", ), @@ -254,16 +246,19 @@ async def test_discovered_by_dhcp_connection_fails(hass, source, data): config_entries.SOURCE_INTEGRATION_DISCOVERY, INTEGRATION_DISCOVERY, FAKE_SOCKET_CONFIG, + FAKE_SOCKET, None, "WiZ Socket ABCABC", ), ], ) async def test_discovered_by_dhcp_or_integration_discovery( - hass, source, data, device, extended_white_range, name + hass, source, data, device, bulb_type, extended_white_range, name ): """Test we can configure when discovered from dhcp or discovery.""" - with _patch_wizlight(device=device, extended_white_range=extended_white_range): + with _patch_wizlight( + device=device, extended_white_range=extended_white_range, bulb_type=bulb_type + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": source}, data=data ) diff --git a/tests/components/wiz/test_light.py b/tests/components/wiz/test_light.py new file mode 100644 index 00000000000000..16d7c6a0a5d251 --- /dev/null +++ b/tests/components/wiz/test_light.py @@ -0,0 +1,30 @@ +"""Tests for light platform.""" + +from homeassistant.components import wiz +from homeassistant.const import CONF_HOST, STATE_ON +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er +from homeassistant.setup import async_setup_component + +from . import FAKE_IP, FAKE_MAC, _patch_discovery, _patch_wizlight + +from tests.common import MockConfigEntry + + +async def test_light_unique_id(hass: HomeAssistant) -> None: + """Test a light unique id.""" + entry = MockConfigEntry( + domain=wiz.DOMAIN, + unique_id=FAKE_MAC, + data={CONF_HOST: FAKE_IP}, + ) + entry.add_to_hass(hass) + with _patch_discovery(), _patch_wizlight(): + await async_setup_component(hass, wiz.DOMAIN, {wiz.DOMAIN: {}}) + await hass.async_block_till_done() + + entity_id = "light.mock_title" + entity_registry = er.async_get(hass) + assert entity_registry.async_get(entity_id).unique_id == FAKE_MAC + state = hass.states.get(entity_id) + assert state.state == STATE_ON From dd48f1e6fc2e170e65c00dfd20bcd28fedb2f4bd Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 10 Feb 2022 08:03:14 -0800 Subject: [PATCH 0505/1098] Allow uploading media to media folder (#66143) --- .../components/media_source/local_source.py | 101 ++++++++++++++- .../media_source/test_local_source.py | 122 ++++++++++++++++++ 2 files changed, 221 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_source/local_source.py b/homeassistant/components/media_source/local_source.py index 7213d6ac7a0306..f43bb3d97c709a 100644 --- a/homeassistant/components/media_source/local_source.py +++ b/homeassistant/components/media_source/local_source.py @@ -1,21 +1,29 @@ """Local Media Source Implementation.""" from __future__ import annotations +import logging import mimetypes from pathlib import Path from aiohttp import web +from aiohttp.web_request import FileField +from aioshutil import shutil +import voluptuous as vol from homeassistant.components.http import HomeAssistantView from homeassistant.components.media_player.const import MEDIA_CLASS_DIRECTORY from homeassistant.components.media_player.errors import BrowseError from homeassistant.core import HomeAssistant, callback -from homeassistant.util import raise_if_invalid_path +from homeassistant.exceptions import Unauthorized +from homeassistant.util import raise_if_invalid_filename, raise_if_invalid_path from .const import DOMAIN, MEDIA_CLASS_MAP, MEDIA_MIME_TYPES from .error import Unresolvable from .models import BrowseMediaSource, MediaSource, MediaSourceItem, PlayMedia +MAX_UPLOAD_SIZE = 1024 * 1024 * 10 +LOGGER = logging.getLogger(__name__) + @callback def async_setup(hass: HomeAssistant) -> None: @@ -23,6 +31,7 @@ def async_setup(hass: HomeAssistant) -> None: source = LocalSource(hass) hass.data[DOMAIN][DOMAIN] = source hass.http.register_view(LocalMediaView(hass, source)) + hass.http.register_view(UploadMediaView(hass, source)) class LocalSource(MediaSource): @@ -43,11 +52,14 @@ def async_full_path(self, source_dir_id: str, location: str) -> Path: @callback def async_parse_identifier(self, item: MediaSourceItem) -> tuple[str, str]: """Parse identifier.""" + if item.domain != DOMAIN: + raise Unresolvable("Unknown domain.") + if not item.identifier: # Empty source_dir_id and location return "", "" - source_dir_id, location = item.identifier.split("/", 1) + source_dir_id, _, location = item.identifier.partition("/") if source_dir_id not in self.hass.config.media_dirs: raise Unresolvable("Unknown source directory.") @@ -217,3 +229,88 @@ async def get( raise web.HTTPNotFound() return web.FileResponse(media_path) + + +class UploadMediaView(HomeAssistantView): + """View to upload images.""" + + url = "/api/media_source/local_source/upload" + name = "api:media_source:local_source:upload" + + def __init__(self, hass: HomeAssistant, source: LocalSource) -> None: + """Initialize the media view.""" + self.hass = hass + self.source = source + self.schema = vol.Schema( + { + "media_content_id": str, + "file": FileField, + } + ) + + async def post(self, request: web.Request) -> web.Response: + """Handle upload.""" + if not request["hass_user"].is_admin: + raise Unauthorized() + + # Increase max payload + request._client_max_size = MAX_UPLOAD_SIZE # pylint: disable=protected-access + + try: + data = self.schema(dict(await request.post())) + except vol.Invalid as err: + LOGGER.error("Received invalid upload data: %s", err) + raise web.HTTPBadRequest() from err + + try: + item = MediaSourceItem.from_uri(self.hass, data["media_content_id"]) + except ValueError as err: + LOGGER.error("Received invalid upload data: %s", err) + raise web.HTTPBadRequest() from err + + try: + source_dir_id, location = self.source.async_parse_identifier(item) + except Unresolvable as err: + LOGGER.error("Invalid local source ID") + raise web.HTTPBadRequest() from err + + uploaded_file: FileField = data["file"] + + if not uploaded_file.content_type.startswith(("image/", "video/")): + LOGGER.error("Content type not allowed") + raise vol.Invalid("Only images and video are allowed") + + try: + raise_if_invalid_filename(uploaded_file.filename) + except ValueError as err: + LOGGER.error("Invalid filename") + raise web.HTTPBadRequest() from err + + try: + await self.hass.async_add_executor_job( + self._move_file, + self.source.async_full_path(source_dir_id, location), + uploaded_file, + ) + except ValueError as err: + LOGGER.error("Moving upload failed: %s", err) + raise web.HTTPBadRequest() from err + + return self.json( + {"media_content_id": f"{data['media_content_id']}/{uploaded_file.filename}"} + ) + + def _move_file( # pylint: disable=no-self-use + self, target_dir: Path, uploaded_file: FileField + ) -> None: + """Move file to target.""" + if not target_dir.is_dir(): + raise ValueError("Target is not an existing directory") + + target_path = target_dir / uploaded_file.filename + + target_path.relative_to(target_dir) + raise_if_invalid_path(str(target_path)) + + with target_path.open("wb") as target_fp: + shutil.copyfileobj(uploaded_file.file, target_fp) diff --git a/tests/components/media_source/test_local_source.py b/tests/components/media_source/test_local_source.py index 8a9005d7a86160..f9ee560620c5c5 100644 --- a/tests/components/media_source/test_local_source.py +++ b/tests/components/media_source/test_local_source.py @@ -1,5 +1,9 @@ """Test Local Media Source.""" from http import HTTPStatus +import io +from pathlib import Path +from tempfile import TemporaryDirectory +from unittest.mock import patch import pytest @@ -9,6 +13,20 @@ from homeassistant.setup import async_setup_component +@pytest.fixture +async def temp_dir(hass): + """Return a temp dir.""" + with TemporaryDirectory() as tmpdirname: + target_dir = Path(tmpdirname) / "another_subdir" + target_dir.mkdir() + await async_process_ha_core_config( + hass, {"media_dirs": {"test_dir": str(target_dir)}} + ) + assert await async_setup_component(hass, const.DOMAIN, {}) + + yield str(target_dir) + + async def test_async_browse_media(hass): """Test browse media.""" local_media = hass.config.path("media") @@ -102,3 +120,107 @@ async def test_media_view(hass, hass_client): resp = await client.get("/media/recordings/test.mp3") assert resp.status == HTTPStatus.OK + + +async def test_upload_view(hass, hass_client, temp_dir, hass_admin_user): + """Allow uploading media.""" + + img = (Path(__file__).parent.parent / "image/logo.png").read_bytes() + + def get_file(name): + pic = io.BytesIO(img) + pic.name = name + return pic + + client = await hass_client() + + # Test normal upload + res = await client.post( + "/api/media_source/local_source/upload", + data={ + "media_content_id": "media-source://media_source/test_dir/.", + "file": get_file("logo.png"), + }, + ) + + assert res.status == 200 + assert (Path(temp_dir) / "logo.png").is_file() + + # Test with bad media source ID + for bad_id in ( + # Subdir doesn't exist + "media-source://media_source/test_dir/some-other-dir", + # Main dir doesn't exist + "media-source://media_source/test_dir2", + # Location is invalid + "media-source://media_source/test_dir/..", + # Domain != media_source + "media-source://nest/test_dir/.", + # Completely something else + "http://bla", + ): + res = await client.post( + "/api/media_source/local_source/upload", + data={ + "media_content_id": bad_id, + "file": get_file("bad-source-id.png"), + }, + ) + + assert res.status == 400 + assert not (Path(temp_dir) / "bad-source-id.png").is_file() + + # Test invalid POST data + res = await client.post( + "/api/media_source/local_source/upload", + data={ + "media_content_id": "media-source://media_source/test_dir/.", + "file": get_file("invalid-data.png"), + "incorrect": "format", + }, + ) + + assert res.status == 400 + assert not (Path(temp_dir) / "invalid-data.png").is_file() + + # Test invalid content type + text_file = io.BytesIO(b"Hello world") + text_file.name = "hello.txt" + res = await client.post( + "/api/media_source/local_source/upload", + data={ + "media_content_id": "media-source://media_source/test_dir/.", + "file": text_file, + }, + ) + + assert res.status == 400 + assert not (Path(temp_dir) / "hello.txt").is_file() + + # Test invalid filename + with patch( + "aiohttp.formdata.guess_filename", return_value="../invalid-filename.png" + ): + res = await client.post( + "/api/media_source/local_source/upload", + data={ + "media_content_id": "media-source://media_source/test_dir/.", + "file": get_file("../invalid-filename.png"), + }, + ) + + assert res.status == 400 + assert not (Path(temp_dir) / "../invalid-filename.png").is_file() + + # Remove admin access + hass_admin_user.groups = [] + res = await client.post( + "/api/media_source/local_source/upload", + data={ + "media_content_id": "media-source://media_source/test_dir/.", + "file": get_file("no-admin-test.png"), + }, + ) + + assert res.status == 401 + assert not (Path(temp_dir) / "no-admin-test.png").is_file() From deb81859110a26ab1b884bacc5ddf035bca663c6 Mon Sep 17 00:00:00 2001 From: Jarod Wilson Date: Thu, 10 Feb 2022 11:10:58 -0500 Subject: [PATCH 0506/1098] Add unique_id for decora_wifi lights (#66142) I have some decora_wifi switches that I want to be able to move around to different rooms within Home Assistant, and for that to be practical, they need unique IDs, so here we are. Signed-off-by: Jarod Wilson --- homeassistant/components/decora_wifi/light.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/decora_wifi/light.py b/homeassistant/components/decora_wifi/light.py index 4f1f9f059d898c..84caf0ad29acb6 100644 --- a/homeassistant/components/decora_wifi/light.py +++ b/homeassistant/components/decora_wifi/light.py @@ -96,6 +96,7 @@ class DecoraWifiLight(LightEntity): def __init__(self, switch): """Initialize the switch.""" self._switch = switch + self._attr_unique_id = switch.serial @property def supported_features(self): From f5829173db5b56d7f52465440e340fcea1668b6c Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 10 Feb 2022 17:13:22 +0100 Subject: [PATCH 0507/1098] More cleanup in Plugwise climate (#66257) --- homeassistant/components/plugwise/climate.py | 120 ++++++++++--------- 1 file changed, 61 insertions(+), 59 deletions(-) diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index 4cea3d9499e981..dba041f1028e09 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -1,4 +1,7 @@ """Plugwise Climate component for Home Assistant.""" +from __future__ import annotations + +from collections.abc import Mapping from typing import Any from homeassistant.components.climate import ClimateEntity @@ -15,7 +18,7 @@ ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, DOMAIN, SCHEDULE_OFF, SCHEDULE_ON @@ -45,15 +48,12 @@ async def async_setup_entry( class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): """Representation of an Plugwise thermostat.""" - _attr_hvac_mode = HVAC_MODE_HEAT _attr_max_temp = DEFAULT_MAX_TEMP _attr_min_temp = DEFAULT_MIN_TEMP - _attr_preset_mode = None _attr_preset_modes = None _attr_supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE _attr_temperature_unit = TEMP_CELSIUS _attr_hvac_modes = HVAC_MODES_HEAT_ONLY - _attr_hvac_mode = HVAC_MODE_HEAT def __init__( self, @@ -64,9 +64,59 @@ def __init__( super().__init__(coordinator, device_id) self._attr_extra_state_attributes = {} self._attr_unique_id = f"{device_id}-climate" - self._attr_name = coordinator.data.devices[device_id].get("name") + self._attr_name = self.device.get("name") + + # Determine preset modes + if presets := self.device.get("presets"): + self._attr_preset_modes = list(presets) + + # Determine hvac modes and current hvac mode + if self.coordinator.data.gateway.get("cooling_present"): + self._attr_hvac_modes = HVAC_MODES_HEAT_COOL + + @property + def current_temperature(self) -> float | None: + """Return the current temperature.""" + return self.device["sensors"].get("temperature") + + @property + def target_temperature(self) -> float | None: + """Return the temperature we try to reach.""" + return self.device["sensors"].get("setpoint") + + @property + def hvac_mode(self) -> str: + """Return HVAC operation ie. heat, cool mode.""" + if (mode := self.device.get("mode")) is None or mode not in self.hvac_modes: + return HVAC_MODE_OFF + return mode + + @property + def hvac_action(self) -> str: + """Return the current running hvac operation if supported.""" + heater_central_data = self.coordinator.data.devices[ + self.coordinator.data.gateway["heater_id"] + ] - self._loc_id = coordinator.data.devices[device_id]["location"] + if heater_central_data.get("heating_state"): + return CURRENT_HVAC_HEAT + if heater_central_data.get("cooling_state"): + return CURRENT_HVAC_COOL + + return CURRENT_HVAC_IDLE + + @property + def preset_mode(self) -> str | None: + """Return the current preset mode.""" + return self.device.get("active_preset") + + @property + def extra_state_attributes(self) -> Mapping[str, Any] | None: + """Return entity specific state attributes.""" + return { + "available_schemas": self.device.get("available_schedules"), + "selected_schema": self.device.get("selected_schedule"), + } @plugwise_command async def async_set_temperature(self, **kwargs: Any) -> None: @@ -75,72 +125,24 @@ async def async_set_temperature(self, **kwargs: Any) -> None: self._attr_max_temp < temperature < self._attr_min_temp ): raise ValueError("Invalid temperature requested") - await self.coordinator.api.set_temperature(self._loc_id, temperature) + await self.coordinator.api.set_temperature(self.device["location"], temperature) @plugwise_command async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set the hvac mode.""" state = SCHEDULE_OFF - climate_data = self.coordinator.data.devices[self._dev_id] - if hvac_mode == HVAC_MODE_AUTO: state = SCHEDULE_ON await self.coordinator.api.set_temperature( - self._loc_id, climate_data.get("schedule_temperature") + self.device["location"], self.device.get("schedule_temperature") ) - self._attr_target_temperature = climate_data.get("schedule_temperature") + self._attr_target_temperature = self.device.get("schedule_temperature") await self.coordinator.api.set_schedule_state( - self._loc_id, climate_data.get("last_used"), state + self.device["location"], self.device.get("last_used"), state ) @plugwise_command async def async_set_preset_mode(self, preset_mode: str) -> None: """Set the preset mode.""" - if not self.coordinator.data.devices[self._dev_id].get("presets"): - raise ValueError("No presets available") - - await self.coordinator.api.set_preset(self._loc_id, preset_mode) - - @callback - def _handle_coordinator_update(self) -> None: - """Handle updated data from the coordinator.""" - data = self.coordinator.data.devices[self._dev_id] - heater_central_data = self.coordinator.data.devices[ - self.coordinator.data.gateway["heater_id"] - ] - - # Current & set temperatures - if setpoint := data["sensors"].get("setpoint"): - self._attr_target_temperature = setpoint - if temperature := data["sensors"].get("temperature"): - self._attr_current_temperature = temperature - - # Presets handling - self._attr_preset_mode = data.get("active_preset") - if presets := data.get("presets"): - self._attr_preset_modes = list(presets) - else: - self._attr_preset_mode = None - - # Determine current hvac action - self._attr_hvac_action = CURRENT_HVAC_IDLE - if heater_central_data.get("heating_state"): - self._attr_hvac_action = CURRENT_HVAC_HEAT - elif heater_central_data.get("cooling_state"): - self._attr_hvac_action = CURRENT_HVAC_COOL - - # Determine hvac modes and current hvac mode - self._attr_hvac_modes = HVAC_MODES_HEAT_ONLY - if self.coordinator.data.gateway.get("cooling_present"): - self._attr_hvac_modes = HVAC_MODES_HEAT_COOL - if data.get("mode") in self._attr_hvac_modes: - self._attr_hvac_mode = data["mode"] - - # Extra attributes - self._attr_extra_state_attributes = { - "available_schemas": data.get("available_schedules"), - "selected_schema": data.get("selected_schedule"), - } - - super()._handle_coordinator_update() + await self.coordinator.api.set_preset(self.device["location"], preset_mode) From 08ab00d3abb1a13e246d822a7922b5d22a8ba077 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 10 Feb 2022 18:09:24 +0100 Subject: [PATCH 0508/1098] More cleanup in Plugwise sensor (#66274) --- homeassistant/components/plugwise/sensor.py | 36 ++++----------------- 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/plugwise/sensor.py b/homeassistant/components/plugwise/sensor.py index 92867a3dfe0133..1d6dfbd3e90481 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -17,10 +17,10 @@ TEMP_CELSIUS, VOLUME_CUBIC_METERS, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN, LOGGER, UNIT_LUMEN +from .const import DOMAIN, UNIT_LUMEN from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity @@ -74,13 +74,6 @@ device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, ), - SensorEntityDescription( - key="return_temperature", - name="Return Temperature", - native_unit_of_measurement=TEMP_CELSIUS, - device_class=SensorDeviceClass.TEMPERATURE, - state_class=SensorStateClass.MEASUREMENT, - ), SensorEntityDescription( key="electricity_consumed", name="Electricity Consumed", @@ -258,17 +251,6 @@ ), ) -INDICATE_ACTIVE_LOCAL_DEVICE_SENSORS: tuple[SensorEntityDescription, ...] = ( - SensorEntityDescription( - key="cooling_state", - name="Cooling State", - ), - SensorEntityDescription( - key="flame_state", - name="Flame State", - ), -) - async def async_setup_entry( hass: HomeAssistant, @@ -313,13 +295,7 @@ def __init__( self._attr_unique_id = f"{device_id}-{description.key}" self._attr_name = (f"{self.device.get('name', '')} {description.name}").lstrip() - @callback - def _handle_coordinator_update(self) -> None: - """Handle updated data from the coordinator.""" - if not (data := self.coordinator.data.devices.get(self._dev_id)): - LOGGER.error("Received no data for device %s", self._dev_id) - super()._handle_coordinator_update() - return - - self._attr_native_value = data["sensors"].get(self.entity_description.key) - super()._handle_coordinator_update() + @property + def native_value(self) -> int | float | None: + """Return the value reported by the sensor.""" + return self.device["sensors"].get(self.entity_description.key) From 18056325bbd52bc6896c158948038e578d497ec7 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 10 Feb 2022 18:09:38 +0100 Subject: [PATCH 0509/1098] Add Flame State binary sensor to Plugwise (#66275) --- homeassistant/components/plugwise/binary_sensor.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index c9053e8468e368..ec1f2e5a2b4949 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -36,6 +36,13 @@ class PlugwiseBinarySensorEntityDescription(BinarySensorEntityDescription): icon_off="mdi:water-pump-off", entity_category=EntityCategory.DIAGNOSTIC, ), + PlugwiseBinarySensorEntityDescription( + key="flame_state", + name="Flame State", + icon="mdi:fire", + icon_off="mdi:fire-off", + entity_category=EntityCategory.DIAGNOSTIC, + ), PlugwiseBinarySensorEntityDescription( key="slave_boiler_state", name="Secondary Boiler State", From b8253b1b477ec0414fa2ca47876b993a2a138d94 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 10 Feb 2022 18:10:21 +0100 Subject: [PATCH 0510/1098] Plugwise HVAC/Preset mode fixes (#66273) --- homeassistant/components/plugwise/climate.py | 28 +++++++++++--------- homeassistant/components/plugwise/const.py | 2 -- tests/components/plugwise/test_climate.py | 19 ++++++++++--- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index dba041f1028e09..f8e1b4f981c6d6 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -21,13 +21,11 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, DOMAIN, SCHEDULE_OFF, SCHEDULE_ON +from .const import DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, DOMAIN from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity from .util import plugwise_command -HVAC_MODES_HEAT_ONLY = [HVAC_MODE_HEAT, HVAC_MODE_AUTO, HVAC_MODE_OFF] -HVAC_MODES_HEAT_COOL = [HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_AUTO, HVAC_MODE_OFF] THERMOSTAT_CLASSES = ["thermostat", "zone_thermostat", "thermostatic_radiator_valve"] @@ -50,10 +48,7 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): _attr_max_temp = DEFAULT_MAX_TEMP _attr_min_temp = DEFAULT_MIN_TEMP - _attr_preset_modes = None - _attr_supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE _attr_temperature_unit = TEMP_CELSIUS - _attr_hvac_modes = HVAC_MODES_HEAT_ONLY def __init__( self, @@ -67,12 +62,17 @@ def __init__( self._attr_name = self.device.get("name") # Determine preset modes + self._attr_supported_features = SUPPORT_TARGET_TEMPERATURE if presets := self.device.get("presets"): + self._attr_supported_features |= SUPPORT_PRESET_MODE self._attr_preset_modes = list(presets) # Determine hvac modes and current hvac mode + self._attr_hvac_modes = [HVAC_MODE_HEAT, HVAC_MODE_OFF] if self.coordinator.data.gateway.get("cooling_present"): - self._attr_hvac_modes = HVAC_MODES_HEAT_COOL + self._attr_hvac_modes.append(HVAC_MODE_COOL) + if self.device.get("available_schedules") != ["None"]: + self._attr_hvac_modes.append(HVAC_MODE_AUTO) @property def current_temperature(self) -> float | None: @@ -130,16 +130,20 @@ async def async_set_temperature(self, **kwargs: Any) -> None: @plugwise_command async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set the hvac mode.""" - state = SCHEDULE_OFF if hvac_mode == HVAC_MODE_AUTO: - state = SCHEDULE_ON + if ( + schedule_temperature := self.device.get("schedule_temperature") + ) is None: + raise ValueError("Cannot set HVAC mode to Auto: No schedule available") + await self.coordinator.api.set_temperature( - self.device["location"], self.device.get("schedule_temperature") + self.device["location"], schedule_temperature ) - self._attr_target_temperature = self.device.get("schedule_temperature") await self.coordinator.api.set_schedule_state( - self.device["location"], self.device.get("last_used"), state + self.device["location"], + self.device.get("last_used"), + "true" if hvac_mode == HVAC_MODE_AUTO else "false", ) @plugwise_command diff --git a/homeassistant/components/plugwise/const.py b/homeassistant/components/plugwise/const.py index 1beeff501b05b3..ab4bde47d9114e 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -14,8 +14,6 @@ FLOW_TYPE = "flow_type" GATEWAY = "gateway" PW_TYPE = "plugwise_type" -SCHEDULE_OFF = "false" -SCHEDULE_ON = "true" SMILE = "smile" STRETCH = "stretch" STRETCH_USERNAME = "stretch" diff --git a/tests/components/plugwise/test_climate.py b/tests/components/plugwise/test_climate.py index 5eb60ebeb6fb6b..5c9df093487b3f 100644 --- a/tests/components/plugwise/test_climate.py +++ b/tests/components/plugwise/test_climate.py @@ -5,6 +5,7 @@ from homeassistant.components.climate.const import ( HVAC_MODE_AUTO, + HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF, ) @@ -22,7 +23,7 @@ async def test_adam_climate_entity_attributes(hass, mock_smile_adam): state = hass.states.get("climate.zone_lisa_wk") attrs = state.attributes - assert attrs["hvac_modes"] == [HVAC_MODE_HEAT, HVAC_MODE_AUTO, HVAC_MODE_OFF] + assert attrs["hvac_modes"] == [HVAC_MODE_HEAT, HVAC_MODE_OFF, HVAC_MODE_AUTO] assert "preset_modes" in attrs assert "no_frost" in attrs["preset_modes"] @@ -38,7 +39,7 @@ async def test_adam_climate_entity_attributes(hass, mock_smile_adam): state = hass.states.get("climate.zone_thermostat_jessie") attrs = state.attributes - assert attrs["hvac_modes"] == [HVAC_MODE_HEAT, HVAC_MODE_AUTO, HVAC_MODE_OFF] + assert attrs["hvac_modes"] == [HVAC_MODE_HEAT, HVAC_MODE_OFF, HVAC_MODE_AUTO] assert "preset_modes" in attrs assert "no_frost" in attrs["preset_modes"] @@ -157,7 +158,7 @@ async def test_anna_climate_entity_attributes(hass, mock_smile_anna): attrs = state.attributes assert "hvac_modes" in attrs - assert "heat" in attrs["hvac_modes"] + assert attrs["hvac_modes"] == [HVAC_MODE_HEAT, HVAC_MODE_OFF, HVAC_MODE_COOL] assert "preset_modes" in attrs assert "no_frost" in attrs["preset_modes"] @@ -214,3 +215,15 @@ async def test_anna_climate_entity_climate_changes(hass, mock_smile_anna): mock_smile_anna.set_schedule_state.assert_called_with( "c784ee9fdab44e1395b8dee7d7a497d5", None, "false" ) + + # Auto mode is not available, no schedules + with pytest.raises(ValueError): + await hass.services.async_call( + "climate", + "set_hvac_mode", + {"entity_id": "climate.anna", "hvac_mode": "auto"}, + blocking=True, + ) + + assert mock_smile_anna.set_temperature.call_count == 1 + assert mock_smile_anna.set_schedule_state.call_count == 1 From e5eba88ecc5ee14e2db5885d10f1a43fbfe3f7fd Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 10 Feb 2022 10:13:26 -0700 Subject: [PATCH 0511/1098] Clean up unnecessary branch in SimpliSafe (#66268) --- homeassistant/components/simplisafe/__init__.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index a4ff2a618bc645..77732f4e2cb892 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -495,8 +495,6 @@ async def _async_start_websocket_loop(self) -> None: """Start a websocket reconnection loop.""" assert self._api.websocket - should_reconnect = True - try: await self._api.websocket.async_connect() await self._api.websocket.async_listen() @@ -508,12 +506,11 @@ async def _async_start_websocket_loop(self) -> None: except Exception as err: # pylint: disable=broad-except LOGGER.error("Unknown exception while connecting to websocket: %s", err) - if should_reconnect: - LOGGER.info("Disconnected from websocket; reconnecting") - await self._async_cancel_websocket_loop() - self._websocket_reconnect_task = self._hass.async_create_task( - self._async_start_websocket_loop() - ) + LOGGER.info("Reconnecting to websocket") + await self._async_cancel_websocket_loop() + self._websocket_reconnect_task = self._hass.async_create_task( + self._async_start_websocket_loop() + ) async def _async_cancel_websocket_loop(self) -> None: """Stop any existing websocket reconnection loop.""" From bd920aa43de584f6a4db934902d64b39aabbd6d6 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 10 Feb 2022 20:41:30 +0100 Subject: [PATCH 0512/1098] Cleanup existing Plugwise tests and test fixtures (#66282) * Cleanup existing Plugwise tests and test fixtures * More cleanup --- tests/components/plugwise/common.py | 26 -- tests/components/plugwise/conftest.py | 289 ++++++++---------- .../components/plugwise/test_binary_sensor.py | 36 ++- tests/components/plugwise/test_climate.py | 127 ++++---- tests/components/plugwise/test_diagnostics.py | 10 +- tests/components/plugwise/test_init.py | 99 +++--- tests/components/plugwise/test_sensor.py | 60 ++-- tests/components/plugwise/test_switch.py | 66 ++-- 8 files changed, 323 insertions(+), 390 deletions(-) delete mode 100644 tests/components/plugwise/common.py diff --git a/tests/components/plugwise/common.py b/tests/components/plugwise/common.py deleted file mode 100644 index 379929ce2f150b..00000000000000 --- a/tests/components/plugwise/common.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Common initialisation for the Plugwise integration.""" - -from homeassistant.components.plugwise.const import DOMAIN -from homeassistant.core import HomeAssistant - -from tests.common import MockConfigEntry -from tests.test_util.aiohttp import AiohttpClientMocker - - -async def async_init_integration( - hass: HomeAssistant, - aioclient_mock: AiohttpClientMocker, - skip_setup: bool = False, -): - """Initialize the Smile integration.""" - - entry = MockConfigEntry( - domain=DOMAIN, data={"host": "1.1.1.1", "password": "test-password"} - ) - entry.add_to_hass(hass) - - if not skip_setup: - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - return entry diff --git a/tests/components/plugwise/conftest.py b/tests/components/plugwise/conftest.py index fa49967437d30f..012734315f6873 100644 --- a/tests/components/plugwise/conftest.py +++ b/tests/components/plugwise/conftest.py @@ -2,30 +2,49 @@ from __future__ import annotations from collections.abc import Generator -from functools import partial -from http import HTTPStatus import json -import re -from unittest.mock import AsyncMock, MagicMock, Mock, patch - -from plugwise.exceptions import ( - ConnectionFailedError, - InvalidAuthentication, - PlugwiseException, - XMLDataMissingError, -) +from typing import Any +from unittest.mock import AsyncMock, MagicMock, patch + import pytest -from tests.common import load_fixture -from tests.test_util.aiohttp import AiohttpClientMocker +from homeassistant.components.plugwise.const import API, DOMAIN, PW_TYPE +from homeassistant.const import ( + CONF_HOST, + CONF_MAC, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, +) +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry, load_fixture -def _read_json(environment, call): +def _read_json(environment: str, call: str) -> dict[str, Any]: """Undecode the json data.""" fixture = load_fixture(f"plugwise/{environment}/{call}.json") return json.loads(fixture) +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Return the default mocked config entry.""" + return MockConfigEntry( + title="My Plugwise", + domain=DOMAIN, + data={ + CONF_HOST: "127.0.0.1", + CONF_MAC: "AA:BB:CC:DD:EE:FF", + CONF_PASSWORD: "test-password", + CONF_PORT: 80, + CONF_USERNAME: "smile", + PW_TYPE: API, + }, + unique_id="smile98765", + ) + + @pytest.fixture def mock_setup_entry() -> Generator[AsyncMock, None, None]: """Mock setting up a config entry.""" @@ -49,179 +68,109 @@ def mock_smile_config_flow() -> Generator[None, MagicMock, None]: yield smile -@pytest.fixture(name="mock_smile_unauth") -def mock_smile_unauth(aioclient_mock: AiohttpClientMocker) -> None: - """Mock the Plugwise Smile unauthorized for Home Assistant.""" - aioclient_mock.get(re.compile(".*"), status=HTTPStatus.UNAUTHORIZED) - aioclient_mock.put(re.compile(".*"), status=HTTPStatus.UNAUTHORIZED) - +@pytest.fixture +def mock_smile_adam() -> Generator[None, MagicMock, None]: + """Create a Mock Adam environment for testing exceptions.""" + chosen_env = "adam_multiple_devices_per_zone" -@pytest.fixture(name="mock_smile_error") -def mock_smile_error(aioclient_mock: AiohttpClientMocker) -> None: - """Mock the Plugwise Smile server failure for Home Assistant.""" - aioclient_mock.get(re.compile(".*"), status=HTTPStatus.INTERNAL_SERVER_ERROR) - aioclient_mock.put(re.compile(".*"), status=HTTPStatus.INTERNAL_SERVER_ERROR) + with patch( + "homeassistant.components.plugwise.gateway.Smile", autospec=True + ) as smile_mock: + smile = smile_mock.return_value + smile.gateway_id = "fe799307f1624099878210aa0b9f1475" + smile.heater_id = "90986d591dcd426cae3ec3e8111ff730" + smile.smile_version = "3.0.15" + smile.smile_type = "thermostat" + smile.smile_hostname = "smile98765" + smile.smile_name = "Adam" -@pytest.fixture(name="mock_smile_notconnect") -def mock_smile_notconnect(): - """Mock the Plugwise Smile general connection failure for Home Assistant.""" - with patch("homeassistant.components.plugwise.gateway.Smile") as smile_mock: - smile_mock.InvalidAuthentication = InvalidAuthentication - smile_mock.ConnectionFailedError = ConnectionFailedError - smile_mock.PlugwiseException = PlugwiseException - smile_mock.return_value.connect.side_effect = AsyncMock(return_value=False) - yield smile_mock.return_value + smile.connect.return_value = True + smile.notifications = _read_json(chosen_env, "notifications") + smile.async_update.return_value = _read_json(chosen_env, "all_data") -def _get_device_data(chosen_env, device_id): - """Mock return data for specific devices.""" - return _read_json(chosen_env, "get_device_data/" + device_id) + yield smile -@pytest.fixture(name="mock_smile_adam") -def mock_smile_adam(): - """Create a Mock Adam environment for testing exceptions.""" - chosen_env = "adam_multiple_devices_per_zone" - with patch("homeassistant.components.plugwise.gateway.Smile") as smile_mock: - smile_mock.InvalidAuthentication = InvalidAuthentication - smile_mock.ConnectionFailedError = ConnectionFailedError - smile_mock.XMLDataMissingError = XMLDataMissingError - - smile_mock.return_value.gateway_id = "fe799307f1624099878210aa0b9f1475" - smile_mock.return_value.heater_id = "90986d591dcd426cae3ec3e8111ff730" - smile_mock.return_value.smile_version = "3.0.15" - smile_mock.return_value.smile_type = "thermostat" - smile_mock.return_value.smile_hostname = "smile98765" - smile_mock.return_value.smile_name = "Adam" - - smile_mock.return_value.notifications = _read_json(chosen_env, "notifications") - - smile_mock.return_value.connect.side_effect = AsyncMock(return_value=True) - smile_mock.return_value.single_master_thermostat.side_effect = Mock( - return_value=False - ) - smile_mock.return_value.set_schedule_state.side_effect = AsyncMock( - return_value=True - ) - smile_mock.return_value.set_preset.side_effect = AsyncMock(return_value=True) - smile_mock.return_value.set_temperature.side_effect = AsyncMock( - return_value=True - ) - smile_mock.return_value.async_update.side_effect = AsyncMock( - return_value=_read_json(chosen_env, "all_data") - ) - smile_mock.return_value.set_switch_state.side_effect = AsyncMock( - return_value=True - ) - - yield smile_mock.return_value - - -@pytest.fixture(name="mock_smile_anna") -def mock_smile_anna(): +@pytest.fixture +def mock_smile_anna() -> Generator[None, MagicMock, None]: """Create a Mock Anna environment for testing exceptions.""" chosen_env = "anna_heatpump" - with patch("homeassistant.components.plugwise.gateway.Smile") as smile_mock: - smile_mock.InvalidAuthentication = InvalidAuthentication - smile_mock.ConnectionFailedError = ConnectionFailedError - smile_mock.XMLDataMissingError = XMLDataMissingError - - smile_mock.return_value.gateway_id = "015ae9ea3f964e668e490fa39da3870b" - smile_mock.return_value.heater_id = "1cbf783bb11e4a7c8a6843dee3a86927" - smile_mock.return_value.smile_version = "4.0.15" - smile_mock.return_value.smile_type = "thermostat" - smile_mock.return_value.smile_hostname = "smile98765" - smile_mock.return_value.smile_name = "Anna" - - smile_mock.return_value.notifications = _read_json(chosen_env, "notifications") - - smile_mock.return_value.connect.side_effect = AsyncMock(return_value=True) - smile_mock.return_value.single_master_thermostat.side_effect = Mock( - return_value=True - ) - smile_mock.return_value.set_schedule_state.side_effect = AsyncMock( - return_value=True - ) - smile_mock.return_value.set_preset.side_effect = AsyncMock(return_value=True) - smile_mock.return_value.set_temperature.side_effect = AsyncMock( - return_value=True - ) - smile_mock.return_value.set_switch_state.side_effect = AsyncMock( - return_value=True - ) - - smile_mock.return_value.async_update.side_effect = AsyncMock( - return_value=_read_json(chosen_env, "all_data") - ) - smile_mock.return_value.get_device_data.side_effect = partial( - _get_device_data, chosen_env - ) - - yield smile_mock.return_value - - -@pytest.fixture(name="mock_smile_p1") -def mock_smile_p1(): - """Create a Mock P1 DSMR environment for testing exceptions.""" - chosen_env = "p1v3_full_option" - with patch("homeassistant.components.plugwise.gateway.Smile") as smile_mock: - smile_mock.InvalidAuthentication = InvalidAuthentication - smile_mock.ConnectionFailedError = ConnectionFailedError - smile_mock.XMLDataMissingError = XMLDataMissingError + with patch( + "homeassistant.components.plugwise.gateway.Smile", autospec=True + ) as smile_mock: + smile = smile_mock.return_value - smile_mock.return_value.gateway_id = "e950c7d5e1ee407a858e2a8b5016c8b3" - smile_mock.return_value.heater_id = None - smile_mock.return_value.smile_version = "3.3.9" - smile_mock.return_value.smile_type = "power" - smile_mock.return_value.smile_hostname = "smile98765" - smile_mock.return_value.smile_name = "Smile P1" + smile.gateway_id = "015ae9ea3f964e668e490fa39da3870b" + smile.heater_id = "1cbf783bb11e4a7c8a6843dee3a86927" + smile.smile_version = "4.0.15" + smile.smile_type = "thermostat" + smile.smile_hostname = "smile98765" + smile.smile_name = "Anna" - smile_mock.return_value.notifications = _read_json(chosen_env, "notifications") + smile.connect.return_value = True - smile_mock.return_value.connect.side_effect = AsyncMock(return_value=True) + smile.notifications = _read_json(chosen_env, "notifications") + smile.async_update.return_value = _read_json(chosen_env, "all_data") - smile_mock.return_value.single_master_thermostat.side_effect = Mock( - return_value=None - ) + yield smile - smile_mock.return_value.get_device_data.side_effect = partial( - _get_device_data, chosen_env - ) - smile_mock.return_value.async_update.side_effect = AsyncMock( - return_value=_read_json(chosen_env, "all_data") - ) +@pytest.fixture +def mock_smile_p1() -> Generator[None, MagicMock, None]: + """Create a Mock P1 DSMR environment for testing exceptions.""" + chosen_env = "p1v3_full_option" + with patch( + "homeassistant.components.plugwise.gateway.Smile", autospec=True + ) as smile_mock: + smile = smile_mock.return_value - yield smile_mock.return_value + smile.gateway_id = "e950c7d5e1ee407a858e2a8b5016c8b3" + smile.heater_id = None + smile.smile_version = "3.3.9" + smile.smile_type = "power" + smile.smile_hostname = "smile98765" + smile.smile_name = "Smile P1" + smile.connect.return_value = True -@pytest.fixture(name="mock_stretch") -def mock_stretch(): + smile.notifications = _read_json(chosen_env, "notifications") + smile.async_update.return_value = _read_json(chosen_env, "all_data") + + yield smile + + +@pytest.fixture +def mock_stretch() -> Generator[None, MagicMock, None]: """Create a Mock Stretch environment for testing exceptions.""" chosen_env = "stretch_v31" - with patch("homeassistant.components.plugwise.gateway.Smile") as smile_mock: - smile_mock.InvalidAuthentication = InvalidAuthentication - smile_mock.ConnectionFailedError = ConnectionFailedError - smile_mock.XMLDataMissingError = XMLDataMissingError - - smile_mock.return_value.gateway_id = "259882df3c05415b99c2d962534ce820" - smile_mock.return_value.heater_id = None - smile_mock.return_value.smile_version = "3.1.11" - smile_mock.return_value.smile_type = "stretch" - smile_mock.return_value.smile_hostname = "stretch98765" - smile_mock.return_value.smile_name = "Stretch" - - smile_mock.return_value.connect.side_effect = AsyncMock(return_value=True) - smile_mock.return_value.set_switch_state.side_effect = AsyncMock( - return_value=True - ) - smile_mock.return_value.get_device_data.side_effect = partial( - _get_device_data, chosen_env - ) - - smile_mock.return_value.async_update.side_effect = AsyncMock( - return_value=_read_json(chosen_env, "all_data") - ) - - yield smile_mock.return_value + with patch( + "homeassistant.components.plugwise.gateway.Smile", autospec=True + ) as smile_mock: + smile = smile_mock.return_value + + smile.gateway_id = "259882df3c05415b99c2d962534ce820" + smile.heater_id = None + smile.smile_version = "3.1.11" + smile.smile_type = "stretch" + smile.smile_hostname = "stretch98765" + smile.smile_name = "Stretch" + + smile.connect.return_value = True + smile.async_update.return_value = _read_json(chosen_env, "all_data") + + yield smile + + +@pytest.fixture +async def init_integration( + hass: HomeAssistant, mock_config_entry: MockConfigEntry +) -> MockConfigEntry: + """Set up the Plugwise integration for testing.""" + mock_config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + return mock_config_entry diff --git a/tests/components/plugwise/test_binary_sensor.py b/tests/components/plugwise/test_binary_sensor.py index b2356036eb99f8..4bcecf83157623 100644 --- a/tests/components/plugwise/test_binary_sensor.py +++ b/tests/components/plugwise/test_binary_sensor.py @@ -1,32 +1,36 @@ """Tests for the Plugwise binary_sensor integration.""" -from homeassistant.config_entries import ConfigEntryState +from unittest.mock import MagicMock + from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.core import HomeAssistant -from tests.components.plugwise.common import async_init_integration +from tests.common import MockConfigEntry -async def test_anna_climate_binary_sensor_entities(hass, mock_smile_anna): +async def test_anna_climate_binary_sensor_entities( + hass: HomeAssistant, mock_smile_anna: MagicMock, init_integration: MockConfigEntry +) -> None: """Test creation of climate related binary_sensor entities.""" - entry = await async_init_integration(hass, mock_smile_anna) - assert entry.state is ConfigEntryState.LOADED state = hass.states.get("binary_sensor.opentherm_secondary_boiler_state") + assert state assert state.state == STATE_OFF state = hass.states.get("binary_sensor.opentherm_dhw_state") + assert state assert state.state == STATE_OFF -async def test_anna_climate_binary_sensor_change(hass, mock_smile_anna): +async def test_anna_climate_binary_sensor_change( + hass: HomeAssistant, mock_smile_anna: MagicMock, init_integration: MockConfigEntry +) -> None: """Test change of climate related binary_sensor entities.""" - entry = await async_init_integration(hass, mock_smile_anna) - assert entry.state is ConfigEntryState.LOADED - hass.states.async_set("binary_sensor.opentherm_dhw_state", STATE_ON, {}) await hass.async_block_till_done() state = hass.states.get("binary_sensor.opentherm_dhw_state") + assert state assert state.state == STATE_ON await hass.helpers.entity_component.async_update_entity( @@ -34,16 +38,18 @@ async def test_anna_climate_binary_sensor_change(hass, mock_smile_anna): ) state = hass.states.get("binary_sensor.opentherm_dhw_state") + assert state assert state.state == STATE_OFF -async def test_adam_climate_binary_sensor_change(hass, mock_smile_adam): +async def test_adam_climate_binary_sensor_change( + hass: HomeAssistant, mock_smile_adam: MagicMock, init_integration: MockConfigEntry +) -> None: """Test change of climate related binary_sensor entities.""" - entry = await async_init_integration(hass, mock_smile_adam) - assert entry.state is ConfigEntryState.LOADED - state = hass.states.get("binary_sensor.adam_plugwise_notification") - assert str(state.state) == STATE_ON - assert "unreachable" in state.attributes.get("warning_msg")[0] + assert state + assert state.state == STATE_ON + assert "warning_msg" in state.attributes + assert "unreachable" in state.attributes["warning_msg"][0] assert not state.attributes.get("error_msg") assert not state.attributes.get("other_msg") diff --git a/tests/components/plugwise/test_climate.py b/tests/components/plugwise/test_climate.py index 5c9df093487b3f..6f9a258bb381f2 100644 --- a/tests/components/plugwise/test_climate.py +++ b/tests/components/plugwise/test_climate.py @@ -1,5 +1,7 @@ """Tests for the Plugwise Climate integration.""" +from unittest.mock import MagicMock + from plugwise.exceptions import PlugwiseException import pytest @@ -9,55 +11,59 @@ HVAC_MODE_HEAT, HVAC_MODE_OFF, ) -from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError -from tests.components.plugwise.common import async_init_integration +from tests.common import MockConfigEntry -async def test_adam_climate_entity_attributes(hass, mock_smile_adam): +async def test_adam_climate_entity_attributes( + hass: HomeAssistant, mock_smile_adam: MagicMock, init_integration: MockConfigEntry +) -> None: """Test creation of adam climate device environment.""" - entry = await async_init_integration(hass, mock_smile_adam) - assert entry.state is ConfigEntryState.LOADED - state = hass.states.get("climate.zone_lisa_wk") - attrs = state.attributes - - assert attrs["hvac_modes"] == [HVAC_MODE_HEAT, HVAC_MODE_OFF, HVAC_MODE_AUTO] - assert "preset_modes" in attrs - assert "no_frost" in attrs["preset_modes"] - assert "home" in attrs["preset_modes"] + assert state + assert state.attributes["hvac_modes"] == [ + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ] - assert attrs["current_temperature"] == 20.9 - assert attrs["temperature"] == 21.5 + assert "preset_modes" in state.attributes + assert "no_frost" in state.attributes["preset_modes"] + assert "home" in state.attributes["preset_modes"] - assert attrs["preset_mode"] == "home" - - assert attrs["supported_features"] == 17 + assert state.attributes["current_temperature"] == 20.9 + assert state.attributes["preset_mode"] == "home" + assert state.attributes["supported_features"] == 17 + assert state.attributes["temperature"] == 21.5 state = hass.states.get("climate.zone_thermostat_jessie") - attrs = state.attributes - - assert attrs["hvac_modes"] == [HVAC_MODE_HEAT, HVAC_MODE_OFF, HVAC_MODE_AUTO] + assert state - assert "preset_modes" in attrs - assert "no_frost" in attrs["preset_modes"] - assert "home" in attrs["preset_modes"] + assert state.attributes["hvac_modes"] == [ + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ] - assert attrs["current_temperature"] == 17.2 - assert attrs["temperature"] == 15.0 + assert "preset_modes" in state.attributes + assert "no_frost" in state.attributes["preset_modes"] + assert "home" in state.attributes["preset_modes"] - assert attrs["preset_mode"] == "asleep" + assert state.attributes["current_temperature"] == 17.2 + assert state.attributes["preset_mode"] == "asleep" + assert state.attributes["temperature"] == 15.0 -async def test_adam_climate_adjust_negative_testing(hass, mock_smile_adam): +async def test_adam_climate_adjust_negative_testing( + hass: HomeAssistant, mock_smile_adam: MagicMock, init_integration: MockConfigEntry +) -> None: """Test exceptions of climate entities.""" mock_smile_adam.set_preset.side_effect = PlugwiseException mock_smile_adam.set_schedule_state.side_effect = PlugwiseException mock_smile_adam.set_temperature.side_effect = PlugwiseException - entry = await async_init_integration(hass, mock_smile_adam) - assert entry.state is ConfigEntryState.LOADED with pytest.raises(HomeAssistantError): await hass.services.async_call( @@ -66,9 +72,6 @@ async def test_adam_climate_adjust_negative_testing(hass, mock_smile_adam): {"entity_id": "climate.zone_lisa_wk", "temperature": 25}, blocking=True, ) - state = hass.states.get("climate.zone_lisa_wk") - attrs = state.attributes - assert attrs["temperature"] == 21.5 with pytest.raises(HomeAssistantError): await hass.services.async_call( @@ -77,9 +80,6 @@ async def test_adam_climate_adjust_negative_testing(hass, mock_smile_adam): {"entity_id": "climate.zone_thermostat_jessie", "preset_mode": "home"}, blocking=True, ) - state = hass.states.get("climate.zone_thermostat_jessie") - attrs = state.attributes - assert attrs["preset_mode"] == "asleep" with pytest.raises(HomeAssistantError): await hass.services.async_call( @@ -91,15 +91,12 @@ async def test_adam_climate_adjust_negative_testing(hass, mock_smile_adam): }, blocking=True, ) - state = hass.states.get("climate.zone_thermostat_jessie") - attrs = state.attributes -async def test_adam_climate_entity_climate_changes(hass, mock_smile_adam): +async def test_adam_climate_entity_climate_changes( + hass: HomeAssistant, mock_smile_adam: MagicMock, init_integration: MockConfigEntry +) -> None: """Test handling of user requests in adam climate device environment.""" - entry = await async_init_integration(hass, mock_smile_adam) - assert entry.state is ConfigEntryState.LOADED - await hass.services.async_call( "climate", "set_temperature", @@ -149,36 +146,32 @@ async def test_adam_climate_entity_climate_changes(hass, mock_smile_adam): ) -async def test_anna_climate_entity_attributes(hass, mock_smile_anna): +async def test_anna_climate_entity_attributes( + hass: HomeAssistant, mock_smile_anna: MagicMock, init_integration: MockConfigEntry +) -> None: """Test creation of anna climate device environment.""" - entry = await async_init_integration(hass, mock_smile_anna) - assert entry.state is ConfigEntryState.LOADED - state = hass.states.get("climate.anna") - attrs = state.attributes - - assert "hvac_modes" in attrs - assert attrs["hvac_modes"] == [HVAC_MODE_HEAT, HVAC_MODE_OFF, HVAC_MODE_COOL] - - assert "preset_modes" in attrs - assert "no_frost" in attrs["preset_modes"] - assert "home" in attrs["preset_modes"] - - assert attrs["current_temperature"] == 19.3 - assert attrs["temperature"] == 21.0 - + assert state assert state.state == HVAC_MODE_HEAT - assert attrs["hvac_action"] == "heating" - assert attrs["preset_mode"] == "home" - - assert attrs["supported_features"] == 17 - - -async def test_anna_climate_entity_climate_changes(hass, mock_smile_anna): + assert state.attributes["hvac_modes"] == [ + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + HVAC_MODE_COOL, + ] + assert "no_frost" in state.attributes["preset_modes"] + assert "home" in state.attributes["preset_modes"] + + assert state.attributes["current_temperature"] == 19.3 + assert state.attributes["hvac_action"] == "heating" + assert state.attributes["preset_mode"] == "home" + assert state.attributes["supported_features"] == 17 + assert state.attributes["temperature"] == 21.0 + + +async def test_anna_climate_entity_climate_changes( + hass: HomeAssistant, mock_smile_anna: MagicMock, init_integration: MockConfigEntry +) -> None: """Test handling of user requests in anna climate device environment.""" - entry = await async_init_integration(hass, mock_smile_anna) - assert entry.state is ConfigEntryState.LOADED - await hass.services.async_call( "climate", "set_temperature", diff --git a/tests/components/plugwise/test_diagnostics.py b/tests/components/plugwise/test_diagnostics.py index fdc4ee879201b8..673e77f1630462 100644 --- a/tests/components/plugwise/test_diagnostics.py +++ b/tests/components/plugwise/test_diagnostics.py @@ -5,18 +5,20 @@ from homeassistant.core import HomeAssistant +from tests.common import MockConfigEntry from tests.components.diagnostics import get_diagnostics_for_config_entry -from tests.components.plugwise.common import async_init_integration async def test_diagnostics( hass: HomeAssistant, hass_client: ClientSession, mock_smile_adam: MagicMock, -): + init_integration: MockConfigEntry, +) -> None: """Test diagnostics.""" - entry = await async_init_integration(hass, mock_smile_adam) - assert await get_diagnostics_for_config_entry(hass, hass_client, entry) == { + assert await get_diagnostics_for_config_entry( + hass, hass_client, init_integration + ) == { "gateway": { "active_device": True, "cooling_present": None, diff --git a/tests/components/plugwise/test_init.py b/tests/components/plugwise/test_init.py index d0d810804e2208..d63b81bd0d33cf 100644 --- a/tests/components/plugwise/test_init.py +++ b/tests/components/plugwise/test_init.py @@ -1,65 +1,62 @@ """Tests for the Plugwise Climate integration.""" - import asyncio +from unittest.mock import MagicMock -from plugwise.exceptions import XMLDataMissingError +from plugwise.exceptions import ( + ConnectionFailedError, + PlugwiseException, + XMLDataMissingError, +) +import pytest from homeassistant.components.plugwise.const import DOMAIN from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant -from tests.common import AsyncMock, MockConfigEntry -from tests.components.plugwise.common import async_init_integration - - -async def test_smile_unauthorized(hass, mock_smile_unauth): - """Test failing unauthorization by Smile.""" - entry = await async_init_integration(hass, mock_smile_unauth) - assert entry.state is ConfigEntryState.SETUP_ERROR - - -async def test_smile_error(hass, mock_smile_error): - """Test server error handling by Smile.""" - entry = await async_init_integration(hass, mock_smile_error) - assert entry.state is ConfigEntryState.SETUP_RETRY - - -async def test_smile_notconnect(hass, mock_smile_notconnect): - """Connection failure error handling by Smile.""" - mock_smile_notconnect.connect.return_value = False - entry = await async_init_integration(hass, mock_smile_notconnect) - assert entry.state is ConfigEntryState.SETUP_RETRY +from tests.common import MockConfigEntry -async def test_smile_timeout(hass, mock_smile_notconnect): - """Timeout error handling by Smile.""" - mock_smile_notconnect.connect.side_effect = asyncio.TimeoutError - entry = await async_init_integration(hass, mock_smile_notconnect) - assert entry.state is ConfigEntryState.SETUP_RETRY - - -async def test_smile_adam_xmlerror(hass, mock_smile_adam): - """Detect malformed XML by Smile in Adam environment.""" - mock_smile_adam.async_update.side_effect = XMLDataMissingError - entry = await async_init_integration(hass, mock_smile_adam) - assert entry.state is ConfigEntryState.SETUP_RETRY - - -async def test_unload_entry(hass, mock_smile_adam): - """Test being able to unload an entry.""" - entry = await async_init_integration(hass, mock_smile_adam) - - mock_smile_adam.async_reset = AsyncMock(return_value=True) - await hass.config_entries.async_unload(entry.entry_id) +async def test_load_unload_config_entry( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_smile_anna: MagicMock, +) -> None: + """Test the Plugwise configuration entry loading/unloading.""" + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() - assert entry.state is ConfigEntryState.NOT_LOADED - assert not hass.data[DOMAIN] + assert mock_config_entry.state is ConfigEntryState.LOADED + assert len(mock_smile_anna.connect.mock_calls) == 1 -async def test_async_setup_entry_fail(hass): - """Test async_setup_entry.""" - entry = MockConfigEntry(domain=DOMAIN, data={}) + await hass.config_entries.async_unload(mock_config_entry.entry_id) + await hass.async_block_till_done() - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) + assert not hass.data.get(DOMAIN) + assert mock_config_entry.state is ConfigEntryState.NOT_LOADED + + +@pytest.mark.parametrize( + "side_effect", + [ + (ConnectionFailedError), + (PlugwiseException), + (XMLDataMissingError), + (asyncio.TimeoutError), + ], +) +async def test_config_entry_not_ready( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_smile_anna: MagicMock, + side_effect: Exception, +) -> None: + """Test the Plugwise configuration entry not ready.""" + mock_smile_anna.connect.side_effect = side_effect + + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() - assert entry.state is ConfigEntryState.SETUP_ERROR + + assert len(mock_smile_anna.connect.mock_calls) == 1 + assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY diff --git a/tests/components/plugwise/test_sensor.py b/tests/components/plugwise/test_sensor.py index 47cbea1a8bc9cc..6f5309d3810cdb 100644 --- a/tests/components/plugwise/test_sensor.py +++ b/tests/components/plugwise/test_sensor.py @@ -1,26 +1,30 @@ """Tests for the Plugwise Sensor integration.""" -from homeassistant.config_entries import ConfigEntryState +from unittest.mock import MagicMock -from tests.common import Mock -from tests.components.plugwise.common import async_init_integration +from homeassistant.core import HomeAssistant +from tests.common import MockConfigEntry -async def test_adam_climate_sensor_entities(hass, mock_smile_adam): - """Test creation of climate related sensor entities.""" - entry = await async_init_integration(hass, mock_smile_adam) - assert entry.state is ConfigEntryState.LOADED +async def test_adam_climate_sensor_entities( + hass: HomeAssistant, mock_smile_adam: MagicMock, init_integration: MockConfigEntry +) -> None: + """Test creation of climate related sensor entities.""" state = hass.states.get("sensor.adam_outdoor_temperature") + assert state assert float(state.state) == 7.81 state = hass.states.get("sensor.cv_pomp_electricity_consumed") + assert state assert float(state.state) == 35.6 state = hass.states.get("sensor.onoff_water_temperature") + assert state assert float(state.state) == 70.0 state = hass.states.get("sensor.cv_pomp_electricity_consumed_interval") + assert state assert float(state.state) == 7.37 await hass.helpers.entity_component.async_update_entity( @@ -28,62 +32,70 @@ async def test_adam_climate_sensor_entities(hass, mock_smile_adam): ) state = hass.states.get("sensor.zone_lisa_wk_battery") + assert state assert int(state.state) == 34 -async def test_anna_as_smt_climate_sensor_entities(hass, mock_smile_anna): +async def test_anna_as_smt_climate_sensor_entities( + hass: HomeAssistant, mock_smile_anna: MagicMock, init_integration: MockConfigEntry +) -> None: """Test creation of climate related sensor entities.""" - entry = await async_init_integration(hass, mock_smile_anna) - assert entry.state is ConfigEntryState.LOADED - state = hass.states.get("sensor.opentherm_outdoor_temperature") + assert state assert float(state.state) == 3.0 state = hass.states.get("sensor.opentherm_water_temperature") + assert state assert float(state.state) == 29.1 state = hass.states.get("sensor.anna_illuminance") + assert state assert float(state.state) == 86.0 -async def test_anna_climate_sensor_entities(hass, mock_smile_anna): +async def test_anna_climate_sensor_entities( + hass: HomeAssistant, mock_smile_anna: MagicMock, init_integration: MockConfigEntry +) -> None: """Test creation of climate related sensor entities as single master thermostat.""" - mock_smile_anna.single_master_thermostat.side_effect = Mock(return_value=False) - entry = await async_init_integration(hass, mock_smile_anna) - assert entry.state is ConfigEntryState.LOADED - + mock_smile_anna.single_master_thermostat.return_value = False state = hass.states.get("sensor.opentherm_outdoor_temperature") + assert state assert float(state.state) == 3.0 -async def test_p1_dsmr_sensor_entities(hass, mock_smile_p1): +async def test_p1_dsmr_sensor_entities( + hass: HomeAssistant, mock_smile_p1: MagicMock, init_integration: MockConfigEntry +) -> None: """Test creation of power related sensor entities.""" - entry = await async_init_integration(hass, mock_smile_p1) - assert entry.state is ConfigEntryState.LOADED - state = hass.states.get("sensor.p1_net_electricity_point") + assert state assert float(state.state) == -2816.0 state = hass.states.get("sensor.p1_electricity_consumed_off_peak_cumulative") + assert state assert float(state.state) == 551.09 state = hass.states.get("sensor.p1_electricity_produced_peak_point") + assert state assert float(state.state) == 2816.0 state = hass.states.get("sensor.p1_electricity_consumed_peak_cumulative") + assert state assert float(state.state) == 442.932 state = hass.states.get("sensor.p1_gas_consumed_cumulative") + assert state assert float(state.state) == 584.85 -async def test_stretch_sensor_entities(hass, mock_stretch): +async def test_stretch_sensor_entities( + hass: HomeAssistant, mock_stretch: MagicMock, init_integration: MockConfigEntry +) -> None: """Test creation of power related sensor entities.""" - entry = await async_init_integration(hass, mock_stretch) - assert entry.state is ConfigEntryState.LOADED - state = hass.states.get("sensor.koelkast_92c4a_electricity_consumed") + assert state assert float(state.state) == 50.5 state = hass.states.get("sensor.droger_52559_electricity_consumed_interval") + assert state assert float(state.state) == 0.0 diff --git a/tests/components/plugwise/test_switch.py b/tests/components/plugwise/test_switch.py index 4785a222f69d52..74e19e987d7c5f 100644 --- a/tests/components/plugwise/test_switch.py +++ b/tests/components/plugwise/test_switch.py @@ -1,34 +1,36 @@ """Tests for the Plugwise switch integration.""" +from unittest.mock import MagicMock + from plugwise.exceptions import PlugwiseException import pytest from homeassistant.components.plugwise.const import DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN -from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er from tests.common import MockConfigEntry -from tests.components.plugwise.common import async_init_integration -async def test_adam_climate_switch_entities(hass, mock_smile_adam): +async def test_adam_climate_switch_entities( + hass: HomeAssistant, mock_smile_adam: MagicMock, init_integration: MockConfigEntry +) -> None: """Test creation of climate related switch entities.""" - entry = await async_init_integration(hass, mock_smile_adam) - assert entry.state is ConfigEntryState.LOADED - state = hass.states.get("switch.cv_pomp_relay") - assert str(state.state) == "on" + assert state + assert state.state == "on" state = hass.states.get("switch.fibaro_hc2_relay") - assert str(state.state) == "on" + assert state + assert state.state == "on" -async def test_adam_climate_switch_negative_testing(hass, mock_smile_adam): +async def test_adam_climate_switch_negative_testing( + hass: HomeAssistant, mock_smile_adam: MagicMock, init_integration: MockConfigEntry +): """Test exceptions of climate related switch entities.""" mock_smile_adam.set_switch_state.side_effect = PlugwiseException - entry = await async_init_integration(hass, mock_smile_adam) - assert entry.state is ConfigEntryState.LOADED with pytest.raises(HomeAssistantError): await hass.services.async_call( @@ -57,11 +59,10 @@ async def test_adam_climate_switch_negative_testing(hass, mock_smile_adam): ) -async def test_adam_climate_switch_changes(hass, mock_smile_adam): +async def test_adam_climate_switch_changes( + hass: HomeAssistant, mock_smile_adam: MagicMock, init_integration: MockConfigEntry +) -> None: """Test changing of climate related switch entities.""" - entry = await async_init_integration(hass, mock_smile_adam) - assert entry.state is ConfigEntryState.LOADED - await hass.services.async_call( "switch", "turn_off", @@ -99,23 +100,23 @@ async def test_adam_climate_switch_changes(hass, mock_smile_adam): ) -async def test_stretch_switch_entities(hass, mock_stretch): +async def test_stretch_switch_entities( + hass: HomeAssistant, mock_stretch: MagicMock, init_integration: MockConfigEntry +) -> None: """Test creation of climate related switch entities.""" - entry = await async_init_integration(hass, mock_stretch) - assert entry.state is ConfigEntryState.LOADED - state = hass.states.get("switch.koelkast_92c4a_relay") - assert str(state.state) == "on" + assert state + assert state.state == "on" state = hass.states.get("switch.droger_52559_relay") - assert str(state.state) == "on" + assert state + assert state.state == "on" -async def test_stretch_switch_changes(hass, mock_stretch): +async def test_stretch_switch_changes( + hass: HomeAssistant, mock_stretch: MagicMock, init_integration: MockConfigEntry +) -> None: """Test changing of power related switch entities.""" - entry = await async_init_integration(hass, mock_stretch) - assert entry.state is ConfigEntryState.LOADED - await hass.services.async_call( "switch", "turn_off", @@ -150,12 +151,11 @@ async def test_stretch_switch_changes(hass, mock_stretch): ) -async def test_unique_id_migration_plug_relay(hass, mock_smile_adam): +async def test_unique_id_migration_plug_relay( + hass: HomeAssistant, mock_smile_adam: MagicMock, mock_config_entry: MockConfigEntry +) -> None: """Test unique ID migration of -plugs to -relay.""" - entry = MockConfigEntry( - domain=DOMAIN, data={"host": "1.1.1.1", "password": "test-password"} - ) - entry.add_to_hass(hass) + mock_config_entry.add_to_hass(hass) registry = er.async_get(hass) # Entry to migrate @@ -163,7 +163,7 @@ async def test_unique_id_migration_plug_relay(hass, mock_smile_adam): SWITCH_DOMAIN, DOMAIN, "21f2b542c49845e6bb416884c55778d6-plug", - config_entry=entry, + config_entry=mock_config_entry, suggested_object_id="playstation_smart_plug", disabled_by=None, ) @@ -172,12 +172,12 @@ async def test_unique_id_migration_plug_relay(hass, mock_smile_adam): SWITCH_DOMAIN, DOMAIN, "675416a629f343c495449970e2ca37b5-relay", - config_entry=entry, + config_entry=mock_config_entry, suggested_object_id="router", disabled_by=None, ) - await hass.config_entries.async_setup(entry.entry_id) + await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() assert hass.states.get("switch.playstation_smart_plug") is not None From fbd6e9f4981c31f47a710194b039d82edb8e3e8d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 10 Feb 2022 20:53:14 +0100 Subject: [PATCH 0513/1098] Extend Plugwise climate support (#66278) --- homeassistant/components/plugwise/climate.py | 30 +++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index f8e1b4f981c6d6..a3354007868b0b 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -26,7 +26,12 @@ from .entity import PlugwiseEntity from .util import plugwise_command -THERMOSTAT_CLASSES = ["thermostat", "zone_thermostat", "thermostatic_radiator_valve"] +THERMOSTAT_CLASSES = [ + "thermostat", + "thermostatic_radiator_valve", + "zone_thermometer", + "zone_thermostat", +] async def async_setup_entry( @@ -94,15 +99,20 @@ def hvac_mode(self) -> str: @property def hvac_action(self) -> str: """Return the current running hvac operation if supported.""" - heater_central_data = self.coordinator.data.devices[ - self.coordinator.data.gateway["heater_id"] - ] - - if heater_central_data.get("heating_state"): - return CURRENT_HVAC_HEAT - if heater_central_data.get("cooling_state"): - return CURRENT_HVAC_COOL - + # When control_state is present, prefer this data + if "control_state" in self.device: + if self.device.get("control_state") == "cooling": + return CURRENT_HVAC_COOL + if self.device.get("control_state") == "heating": + return CURRENT_HVAC_HEAT + else: + heater_central_data = self.coordinator.data.devices[ + self.coordinator.data.gateway["heater_id"] + ] + if heater_central_data.get("heating_state"): + return CURRENT_HVAC_HEAT + if heater_central_data.get("cooling_state"): + return CURRENT_HVAC_COOL return CURRENT_HVAC_IDLE @property From dc7ab40acd77f6b56313e334fcba1adc3cbc37cd Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 10 Feb 2022 20:54:09 +0100 Subject: [PATCH 0514/1098] Add humidity sensor to Plugwise (#66280) --- homeassistant/components/plugwise/sensor.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homeassistant/components/plugwise/sensor.py b/homeassistant/components/plugwise/sensor.py index 1d6dfbd3e90481..4ee75e21a45a06 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -249,6 +249,13 @@ device_class=SensorDeviceClass.PRESSURE, state_class=SensorStateClass.MEASUREMENT, ), + SensorEntityDescription( + key="humidity", + name="Relative Humidity", + native_unit_of_measurement=PERCENTAGE, + device_class=SensorDeviceClass.HUMIDITY, + state_class=SensorStateClass.MEASUREMENT, + ), ) From fe38e6ba875cb267da2ed066109b946a124166cc Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 10 Feb 2022 21:09:57 +0100 Subject: [PATCH 0515/1098] Drop MQTT import flow (#66160) * Drop MQTT import flow * Reload manually configured MQTT entities when config entry is setup * Address review comments * Actually remove the import flow --- homeassistant/components/mqtt/__init__.py | 37 +++++------- .../components/mqtt/alarm_control_panel.py | 16 +++-- .../components/mqtt/binary_sensor.py | 11 ++-- homeassistant/components/mqtt/button.py | 16 +++-- homeassistant/components/mqtt/camera.py | 17 ++++-- homeassistant/components/mqtt/climate.py | 16 +++-- homeassistant/components/mqtt/config_flow.py | 11 ---- homeassistant/components/mqtt/const.py | 1 + homeassistant/components/mqtt/cover.py | 16 +++-- homeassistant/components/mqtt/fan.py | 16 +++-- homeassistant/components/mqtt/humidifier.py | 16 +++-- .../components/mqtt/light/__init__.py | 9 ++- homeassistant/components/mqtt/lock.py | 16 +++-- homeassistant/components/mqtt/mixins.py | 50 +++++++++++++++- homeassistant/components/mqtt/number.py | 16 +++-- homeassistant/components/mqtt/scene.py | 10 ++-- homeassistant/components/mqtt/select.py | 16 +++-- homeassistant/components/mqtt/sensor.py | 11 ++-- homeassistant/components/mqtt/siren.py | 16 +++-- homeassistant/components/mqtt/switch.py | 16 +++-- .../components/mqtt/vacuum/__init__.py | 13 ++--- .../mqtt/test_alarm_control_panel.py | 8 +++ tests/components/mqtt/test_binary_sensor.py | 8 +++ tests/components/mqtt/test_button.py | 8 +++ tests/components/mqtt/test_camera.py | 8 +++ tests/components/mqtt/test_climate.py | 8 +++ tests/components/mqtt/test_common.py | 58 ++++++++++++++++++- tests/components/mqtt/test_config_flow.py | 23 +++++++- tests/components/mqtt/test_cover.py | 8 +++ tests/components/mqtt/test_fan.py | 8 +++ tests/components/mqtt/test_humidifier.py | 8 +++ tests/components/mqtt/test_init.py | 44 +++++++++++--- tests/components/mqtt/test_legacy_vacuum.py | 8 +++ tests/components/mqtt/test_light.py | 8 +++ tests/components/mqtt/test_light_json.py | 8 +++ tests/components/mqtt/test_light_template.py | 8 +++ tests/components/mqtt/test_lock.py | 8 +++ tests/components/mqtt/test_number.py | 8 +++ tests/components/mqtt/test_scene.py | 8 +++ tests/components/mqtt/test_select.py | 8 +++ tests/components/mqtt/test_sensor.py | 8 +++ tests/components/mqtt/test_siren.py | 8 +++ tests/components/mqtt/test_state_vacuum.py | 8 +++ tests/components/mqtt/test_switch.py | 8 +++ tests/conftest.py | 18 ++++-- 45 files changed, 494 insertions(+), 155 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index b797491b7a9638..6cb2eb41d9c8aa 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -21,7 +21,6 @@ import jinja2 import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import websocket_api from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -37,6 +36,7 @@ CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP, + SERVICE_RELOAD, Platform, ) from homeassistant.core import ( @@ -76,6 +76,7 @@ CONF_TOPIC, CONF_WILL_MESSAGE, DATA_MQTT_CONFIG, + DATA_MQTT_RELOAD_NEEDED, DEFAULT_BIRTH, DEFAULT_DISCOVERY, DEFAULT_ENCODING, @@ -580,22 +581,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: websocket_api.async_register_command(hass, websocket_mqtt_info) debug_info.initialize(hass) - if conf is None: - # If we have a config entry, setup is done by that config entry. - # If there is no config entry, this should fail. - return bool(hass.config_entries.async_entries(DOMAIN)) - - conf = dict(conf) - - hass.data[DATA_MQTT_CONFIG] = conf - - # Only import if we haven't before. - 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={} - ) - ) + if conf: + conf = dict(conf) + hass.data[DATA_MQTT_CONFIG] = conf return True @@ -609,12 +597,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Load a config entry.""" conf = hass.data.get(DATA_MQTT_CONFIG) - # Config entry was created because user had configuration.yaml entry - # They removed that, so remove entry. - if conf is None and entry.source == config_entries.SOURCE_IMPORT: - hass.async_create_task(hass.config_entries.async_remove(entry.entry_id)) - return False - # If user didn't have configuration.yaml config, generate defaults if conf is None: conf = CONFIG_SCHEMA({DOMAIN: dict(entry.data)})[DOMAIN] @@ -735,6 +717,15 @@ async def finish_dump(_): if conf.get(CONF_DISCOVERY): await _async_setup_discovery(hass, conf, entry) + if DATA_MQTT_RELOAD_NEEDED in hass.data: + hass.data.pop(DATA_MQTT_RELOAD_NEEDED) + await hass.services.async_call( + DOMAIN, + SERVICE_RELOAD, + {}, + blocking=False, + ) + return True diff --git a/homeassistant/components/mqtt/alarm_control_panel.py b/homeassistant/components/mqtt/alarm_control_panel.py index 5d7c67d621b327..cca4e58658c4c5 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -35,10 +35,9 @@ from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import PLATFORMS, MqttCommandTemplate, MqttValueTemplate, subscription +from . import MqttCommandTemplate, MqttValueTemplate, subscription from .. import mqtt from .const import ( CONF_COMMAND_TEMPLATE, @@ -47,10 +46,14 @@ CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, - DOMAIN, ) from .debug_info import log_messages -from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper +from .mixins import ( + MQTT_ENTITY_COMMON_SCHEMA, + MqttEntity, + async_setup_entry_helper, + async_setup_platform_helper, +) _LOGGER = logging.getLogger(__name__) @@ -124,8 +127,9 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up MQTT alarm control panel through configuration.yaml.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - await _async_setup_entity(hass, async_add_entities, config) + await async_setup_platform_helper( + hass, alarm.DOMAIN, config, async_add_entities, _async_setup_entity + ) async def async_setup_entry( diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index 17daaaf08eea3c..166b7cda34c5e6 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -28,20 +28,20 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback import homeassistant.helpers.event as evt from homeassistant.helpers.event import async_track_point_in_utc_time -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util import dt as dt_util -from . import PLATFORMS, MqttValueTemplate, subscription +from . import MqttValueTemplate, subscription from .. import mqtt -from .const import CONF_ENCODING, CONF_QOS, CONF_STATE_TOPIC, DOMAIN, PAYLOAD_NONE +from .const import CONF_ENCODING, CONF_QOS, CONF_STATE_TOPIC, PAYLOAD_NONE from .debug_info import log_messages from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, MqttAvailability, MqttEntity, async_setup_entry_helper, + async_setup_platform_helper, ) _LOGGER = logging.getLogger(__name__) @@ -75,8 +75,9 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up MQTT binary sensor through configuration.yaml.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - await _async_setup_entity(hass, async_add_entities, config) + await async_setup_platform_helper( + hass, binary_sensor.DOMAIN, config, async_add_entities, _async_setup_entity + ) async def async_setup_entry( diff --git a/homeassistant/components/mqtt/button.py b/homeassistant/components/mqtt/button.py index c4b4b19b120e06..22ee7b6d5aec64 100644 --- a/homeassistant/components/mqtt/button.py +++ b/homeassistant/components/mqtt/button.py @@ -12,10 +12,9 @@ from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import PLATFORMS, MqttCommandTemplate +from . import MqttCommandTemplate from .. import mqtt from .const import ( CONF_COMMAND_TEMPLATE, @@ -23,9 +22,13 @@ CONF_ENCODING, CONF_QOS, CONF_RETAIN, - DOMAIN, ) -from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper +from .mixins import ( + MQTT_ENTITY_COMMON_SCHEMA, + MqttEntity, + async_setup_entry_helper, + async_setup_platform_helper, +) CONF_PAYLOAD_PRESS = "payload_press" DEFAULT_NAME = "MQTT Button" @@ -52,8 +55,9 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up MQTT button through configuration.yaml.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - await _async_setup_entity(hass, async_add_entities, config) + await async_setup_platform_helper( + hass, button.DOMAIN, config, async_add_entities, _async_setup_entity + ) async def async_setup_entry( diff --git a/homeassistant/components/mqtt/camera.py b/homeassistant/components/mqtt/camera.py index 1c060f7f32a332..0e387023a396de 100644 --- a/homeassistant/components/mqtt/camera.py +++ b/homeassistant/components/mqtt/camera.py @@ -12,14 +12,18 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import PLATFORMS, subscription +from . import subscription from .. import mqtt -from .const import CONF_QOS, CONF_TOPIC, DOMAIN +from .const import CONF_QOS, CONF_TOPIC from .debug_info import log_messages -from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper +from .mixins import ( + MQTT_ENTITY_COMMON_SCHEMA, + MqttEntity, + async_setup_entry_helper, + async_setup_platform_helper, +) DEFAULT_NAME = "MQTT Camera" @@ -49,8 +53,9 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up MQTT camera through configuration.yaml.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - await _async_setup_entity(hass, async_add_entities, config) + await async_setup_platform_helper( + hass, camera.DOMAIN, config, async_add_entities, _async_setup_entity + ) async def async_setup_entry( diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index 2b4e03f866380d..043a291f159e41 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -53,20 +53,23 @@ from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from . import ( MQTT_BASE_PLATFORM_SCHEMA, - PLATFORMS, MqttCommandTemplate, MqttValueTemplate, subscription, ) from .. import mqtt -from .const import CONF_ENCODING, CONF_QOS, CONF_RETAIN, DOMAIN, PAYLOAD_NONE +from .const import CONF_ENCODING, CONF_QOS, CONF_RETAIN, PAYLOAD_NONE from .debug_info import log_messages -from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper +from .mixins import ( + MQTT_ENTITY_COMMON_SCHEMA, + MqttEntity, + async_setup_entry_helper, + async_setup_platform_helper, +) _LOGGER = logging.getLogger(__name__) @@ -303,8 +306,9 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up MQTT climate device through configuration.yaml.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - await _async_setup_entity(hass, async_add_entities, config) + await async_setup_platform_helper( + hass, climate.DOMAIN, config, async_add_entities, _async_setup_entity + ) async def async_setup_entry( diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index 84322ddc1eefc2..a26e62b62272b4 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -84,17 +84,6 @@ async def async_step_broker(self, user_input=None): step_id="broker", data_schema=vol.Schema(fields), errors=errors ) - async def async_step_import(self, user_input): - """Import a config entry. - - Special type of import, we're not actually going to store any data. - Instead, we're going to rely on the values that are in config file. - """ - if self._async_current_entries(): - return self.async_abort(reason="single_instance_allowed") - - return self.async_create_entry(title="configuration.yaml", data={}) - async def async_step_hassio(self, discovery_info: HassioServiceInfo) -> FlowResult: """Receive a Hass.io discovery.""" await self._async_handle_discovery_without_unique_id() diff --git a/homeassistant/components/mqtt/const.py b/homeassistant/components/mqtt/const.py index 4639682054528f..f04348ee0020b8 100644 --- a/homeassistant/components/mqtt/const.py +++ b/homeassistant/components/mqtt/const.py @@ -23,6 +23,7 @@ CONF_WILL_MESSAGE = "will_message" DATA_MQTT_CONFIG = "mqtt_config" +DATA_MQTT_RELOAD_NEEDED = "mqtt_reload_needed" DEFAULT_PREFIX = "homeassistant" DEFAULT_BIRTH_WILL_TOPIC = DEFAULT_PREFIX + "/status" diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index 4dfa9e20798670..282e57fea9eb9f 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -37,10 +37,9 @@ from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import PLATFORMS, MqttCommandTemplate, MqttValueTemplate, subscription +from . import MqttCommandTemplate, MqttValueTemplate, subscription from .. import mqtt from .const import ( CONF_COMMAND_TOPIC, @@ -48,10 +47,14 @@ CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, - DOMAIN, ) from .debug_info import log_messages -from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper +from .mixins import ( + MQTT_ENTITY_COMMON_SCHEMA, + MqttEntity, + async_setup_entry_helper, + async_setup_platform_helper, +) _LOGGER = logging.getLogger(__name__) @@ -217,8 +220,9 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up MQTT cover through configuration.yaml.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - await _async_setup_entity(hass, async_add_entities, config) + await async_setup_platform_helper( + hass, cover.DOMAIN, config, async_add_entities, _async_setup_entity + ) async def async_setup_entry( diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index 114c8f5729ff59..bedc3467c3b17d 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -32,7 +32,6 @@ from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util.percentage import ( int_states_in_range, @@ -40,7 +39,7 @@ ranged_value_to_percentage, ) -from . import PLATFORMS, MqttCommandTemplate, MqttValueTemplate, subscription +from . import MqttCommandTemplate, MqttValueTemplate, subscription from .. import mqtt from .const import ( CONF_COMMAND_TEMPLATE, @@ -50,11 +49,15 @@ CONF_RETAIN, CONF_STATE_TOPIC, CONF_STATE_VALUE_TEMPLATE, - DOMAIN, PAYLOAD_NONE, ) from .debug_info import log_messages -from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper +from .mixins import ( + MQTT_ENTITY_COMMON_SCHEMA, + MqttEntity, + async_setup_entry_helper, + async_setup_platform_helper, +) CONF_PERCENTAGE_STATE_TOPIC = "percentage_state_topic" CONF_PERCENTAGE_COMMAND_TOPIC = "percentage_command_topic" @@ -213,8 +216,9 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up MQTT fan through configuration.yaml.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - await _async_setup_entity(hass, async_add_entities, config) + await async_setup_platform_helper( + hass, fan.DOMAIN, config, async_add_entities, _async_setup_entity + ) async def async_setup_entry( diff --git a/homeassistant/components/mqtt/humidifier.py b/homeassistant/components/mqtt/humidifier.py index 0ab99cf091458d..99674051521cba 100644 --- a/homeassistant/components/mqtt/humidifier.py +++ b/homeassistant/components/mqtt/humidifier.py @@ -27,10 +27,9 @@ from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import PLATFORMS, MqttCommandTemplate, MqttValueTemplate, subscription +from . import MqttCommandTemplate, MqttValueTemplate, subscription from .. import mqtt from .const import ( CONF_COMMAND_TEMPLATE, @@ -40,11 +39,15 @@ CONF_RETAIN, CONF_STATE_TOPIC, CONF_STATE_VALUE_TEMPLATE, - DOMAIN, PAYLOAD_NONE, ) from .debug_info import log_messages -from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper +from .mixins import ( + MQTT_ENTITY_COMMON_SCHEMA, + MqttEntity, + async_setup_entry_helper, + async_setup_platform_helper, +) CONF_AVAILABLE_MODES_LIST = "modes" CONF_DEVICE_CLASS = "device_class" @@ -157,8 +160,9 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up MQTT humidifier through configuration.yaml.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - await _async_setup_entity(hass, async_add_entities, config) + await async_setup_platform_helper( + hass, humidifier.DOMAIN, config, async_add_entities, _async_setup_entity + ) async def async_setup_entry( diff --git a/homeassistant/components/mqtt/light/__init__.py b/homeassistant/components/mqtt/light/__init__.py index 09dfb8417ccc56..d78cd5e7baa036 100644 --- a/homeassistant/components/mqtt/light/__init__.py +++ b/homeassistant/components/mqtt/light/__init__.py @@ -8,11 +8,9 @@ from homeassistant.components import light from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from .. import DOMAIN, PLATFORMS -from ..mixins import async_setup_entry_helper +from ..mixins import async_setup_entry_helper, async_setup_platform_helper from .schema import CONF_SCHEMA, MQTT_LIGHT_SCHEMA_SCHEMA from .schema_basic import ( DISCOVERY_SCHEMA_BASIC, @@ -69,8 +67,9 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up MQTT light through configuration.yaml.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - await _async_setup_entity(hass, async_add_entities, config) + await async_setup_platform_helper( + hass, light.DOMAIN, config, async_add_entities, _async_setup_entity + ) async def async_setup_entry(hass, config_entry, async_add_entities): diff --git a/homeassistant/components/mqtt/lock.py b/homeassistant/components/mqtt/lock.py index 788c6be1fefec2..1dd73175b3b881 100644 --- a/homeassistant/components/mqtt/lock.py +++ b/homeassistant/components/mqtt/lock.py @@ -12,10 +12,9 @@ from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import PLATFORMS, MqttValueTemplate, subscription +from . import MqttValueTemplate, subscription from .. import mqtt from .const import ( CONF_COMMAND_TOPIC, @@ -23,10 +22,14 @@ CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, - DOMAIN, ) from .debug_info import log_messages -from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper +from .mixins import ( + MQTT_ENTITY_COMMON_SCHEMA, + MqttEntity, + async_setup_entry_helper, + async_setup_platform_helper, +) CONF_PAYLOAD_LOCK = "payload_lock" CONF_PAYLOAD_UNLOCK = "payload_unlock" @@ -73,8 +76,9 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up MQTT lock panel through configuration.yaml.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - await _async_setup_entity(hass, async_add_entities, config) + await async_setup_platform_helper( + hass, lock.DOMAIN, config, async_add_entities, _async_setup_entity + ) async def async_setup_entry( diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index fe989acb98e97c..421ad3203b3617 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -5,9 +5,11 @@ from collections.abc import Callable import json import logging +from typing import Any, Protocol import voluptuous as vol +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_CONFIGURATION_URL, ATTR_MANUFACTURER, @@ -23,7 +25,7 @@ CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, ) -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import ( config_validation as cv, device_registry as dr, @@ -40,9 +42,18 @@ async_generate_entity_id, validate_entity_category, ) +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.typing import ConfigType -from . import DATA_MQTT, MqttValueTemplate, async_publish, debug_info, subscription +from . import ( + DATA_MQTT, + PLATFORMS, + MqttValueTemplate, + async_publish, + debug_info, + subscription, +) from .const import ( ATTR_DISCOVERY_HASH, ATTR_DISCOVERY_PAYLOAD, @@ -51,6 +62,7 @@ CONF_ENCODING, CONF_QOS, CONF_TOPIC, + DATA_MQTT_RELOAD_NEEDED, DEFAULT_ENCODING, DEFAULT_PAYLOAD_AVAILABLE, DEFAULT_PAYLOAD_NOT_AVAILABLE, @@ -210,6 +222,20 @@ def validate_device_has_at_least_one_identifier(value: ConfigType) -> ConfigType ) +class SetupEntity(Protocol): + """Protocol type for async_setup_entities.""" + + async def __call__( + self, + hass: HomeAssistant, + async_add_entities: AddEntitiesCallback, + config: ConfigType, + config_entry: ConfigEntry | None = None, + discovery_data: dict[str, Any] | None = None, + ) -> None: + """Define setup_entities type.""" + + async def async_setup_entry_helper(hass, domain, async_setup, schema): """Set up entity, automation or tag creation dynamically through MQTT discovery.""" @@ -232,6 +258,26 @@ async def async_discover(discovery_payload): ) +async def async_setup_platform_helper( + hass: HomeAssistant, + platform_domain: str, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + async_setup_entities: SetupEntity, +) -> None: + """Return true if platform setup should be aborted.""" + await async_setup_reload_service(hass, DOMAIN, PLATFORMS) + if not bool(hass.config_entries.async_entries(DOMAIN)): + hass.data[DATA_MQTT_RELOAD_NEEDED] = None + _LOGGER.warning( + "MQTT integration is not setup, skipping setup of manually configured " + "MQTT %s", + platform_domain, + ) + return + await async_setup_entities(hass, async_add_entities, config) + + def init_entity_id_from_config(hass, entity, config, entity_id_format): """Set entity_id from object_id if defined in config.""" if CONF_OBJECT_ID in config: diff --git a/homeassistant/components/mqtt/number.py b/homeassistant/components/mqtt/number.py index 6f9c4c38eadbe8..e13ae4ded84f2c 100644 --- a/homeassistant/components/mqtt/number.py +++ b/homeassistant/components/mqtt/number.py @@ -23,11 +23,10 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import PLATFORMS, MqttCommandTemplate, MqttValueTemplate, subscription +from . import MqttCommandTemplate, MqttValueTemplate, subscription from .. import mqtt from .const import ( CONF_COMMAND_TEMPLATE, @@ -36,10 +35,14 @@ CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, - DOMAIN, ) from .debug_info import log_messages -from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper +from .mixins import ( + MQTT_ENTITY_COMMON_SCHEMA, + MqttEntity, + async_setup_entry_helper, + async_setup_platform_helper, +) _LOGGER = logging.getLogger(__name__) @@ -103,8 +106,9 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up MQTT number through configuration.yaml.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - await _async_setup_entity(hass, async_add_entities, config) + await async_setup_platform_helper( + hass, number.DOMAIN, config, async_add_entities, _async_setup_entity + ) async def async_setup_entry( diff --git a/homeassistant/components/mqtt/scene.py b/homeassistant/components/mqtt/scene.py index b12eb2d336ac46..c44ea1dca533ec 100644 --- a/homeassistant/components/mqtt/scene.py +++ b/homeassistant/components/mqtt/scene.py @@ -12,18 +12,17 @@ from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import PLATFORMS from .. import mqtt -from .const import CONF_COMMAND_TOPIC, CONF_ENCODING, CONF_QOS, CONF_RETAIN, DOMAIN +from .const import CONF_COMMAND_TOPIC, CONF_ENCODING, CONF_QOS, CONF_RETAIN from .mixins import ( CONF_OBJECT_ID, MQTT_AVAILABILITY_SCHEMA, MqttAvailability, MqttDiscoveryUpdate, async_setup_entry_helper, + async_setup_platform_helper, init_entity_id_from_config, ) @@ -52,8 +51,9 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up MQTT scene through configuration.yaml.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - await _async_setup_entity(hass, async_add_entities, config) + await async_setup_platform_helper( + hass, scene.DOMAIN, config, async_add_entities, _async_setup_entity + ) async def async_setup_entry( diff --git a/homeassistant/components/mqtt/select.py b/homeassistant/components/mqtt/select.py index 58e6e7e4d6409f..f873a32a5de79b 100644 --- a/homeassistant/components/mqtt/select.py +++ b/homeassistant/components/mqtt/select.py @@ -13,11 +13,10 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import PLATFORMS, MqttCommandTemplate, MqttValueTemplate, subscription +from . import MqttCommandTemplate, MqttValueTemplate, subscription from .. import mqtt from .const import ( CONF_COMMAND_TEMPLATE, @@ -26,10 +25,14 @@ CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, - DOMAIN, ) from .debug_info import log_messages -from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper +from .mixins import ( + MQTT_ENTITY_COMMON_SCHEMA, + MqttEntity, + async_setup_entry_helper, + async_setup_platform_helper, +) _LOGGER = logging.getLogger(__name__) @@ -67,8 +70,9 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up MQTT select through configuration.yaml.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - await _async_setup_entity(hass, async_add_entities, config) + await async_setup_platform_helper( + hass, select.DOMAIN, config, async_add_entities, _async_setup_entity + ) async def async_setup_entry( diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index 137627047bc6f3..8dd5390175567a 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -30,20 +30,20 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_track_point_in_utc_time -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util import dt as dt_util -from . import PLATFORMS, MqttValueTemplate, subscription +from . import MqttValueTemplate, subscription from .. import mqtt -from .const import CONF_ENCODING, CONF_QOS, CONF_STATE_TOPIC, DOMAIN +from .const import CONF_ENCODING, CONF_QOS, CONF_STATE_TOPIC from .debug_info import log_messages from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, MqttAvailability, MqttEntity, async_setup_entry_helper, + async_setup_platform_helper, ) _LOGGER = logging.getLogger(__name__) @@ -120,8 +120,9 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up MQTT sensors through configuration.yaml.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - await _async_setup_entity(hass, async_add_entities, config) + await async_setup_platform_helper( + hass, sensor.DOMAIN, config, async_add_entities, _async_setup_entity + ) async def async_setup_entry( diff --git a/homeassistant/components/mqtt/siren.py b/homeassistant/components/mqtt/siren.py index e43edb12873dec..8619bc799a497f 100644 --- a/homeassistant/components/mqtt/siren.py +++ b/homeassistant/components/mqtt/siren.py @@ -36,10 +36,9 @@ from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import PLATFORMS, MqttCommandTemplate, MqttValueTemplate, subscription +from . import MqttCommandTemplate, MqttValueTemplate, subscription from .. import mqtt from .const import ( CONF_COMMAND_TEMPLATE, @@ -49,12 +48,16 @@ CONF_RETAIN, CONF_STATE_TOPIC, CONF_STATE_VALUE_TEMPLATE, - DOMAIN, PAYLOAD_EMPTY_JSON, PAYLOAD_NONE, ) from .debug_info import log_messages -from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper +from .mixins import ( + MQTT_ENTITY_COMMON_SCHEMA, + MqttEntity, + async_setup_entry_helper, + async_setup_platform_helper, +) DEFAULT_NAME = "MQTT Siren" DEFAULT_PAYLOAD_ON = "ON" @@ -118,8 +121,9 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up MQTT siren through configuration.yaml.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - await _async_setup_entity(hass, async_add_entities, config) + await async_setup_platform_helper( + hass, siren.DOMAIN, config, async_add_entities, _async_setup_entity + ) async def async_setup_entry( diff --git a/homeassistant/components/mqtt/switch.py b/homeassistant/components/mqtt/switch.py index f0ec18cc379d21..1a471ea2ad03e9 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -20,11 +20,10 @@ from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import PLATFORMS, MqttValueTemplate, subscription +from . import MqttValueTemplate, subscription from .. import mqtt from .const import ( CONF_COMMAND_TOPIC, @@ -32,11 +31,15 @@ CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, - DOMAIN, PAYLOAD_NONE, ) from .debug_info import log_messages -from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper +from .mixins import ( + MQTT_ENTITY_COMMON_SCHEMA, + MqttEntity, + async_setup_entry_helper, + async_setup_platform_helper, +) MQTT_SWITCH_ATTRIBUTES_BLOCKED = frozenset( { @@ -75,8 +78,9 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up MQTT switch through configuration.yaml.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - await _async_setup_entity(hass, async_add_entities, config) + await async_setup_platform_helper( + hass, switch.DOMAIN, config, async_add_entities, _async_setup_entity + ) async def async_setup_entry( diff --git a/homeassistant/components/mqtt/vacuum/__init__.py b/homeassistant/components/mqtt/vacuum/__init__.py index 6ff03a437e541d..f64a67820d43e7 100644 --- a/homeassistant/components/mqtt/vacuum/__init__.py +++ b/homeassistant/components/mqtt/vacuum/__init__.py @@ -3,11 +3,9 @@ import voluptuous as vol -from homeassistant.components.vacuum import DOMAIN -from homeassistant.helpers.reload import async_setup_reload_service +from homeassistant.components import vacuum -from .. import DOMAIN as MQTT_DOMAIN, PLATFORMS -from ..mixins import async_setup_entry_helper +from ..mixins import async_setup_entry_helper, async_setup_platform_helper from .schema import CONF_SCHEMA, LEGACY, MQTT_VACUUM_SCHEMA, STATE from .schema_legacy import ( DISCOVERY_SCHEMA_LEGACY, @@ -44,8 +42,9 @@ def validate_mqtt_vacuum(value): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up MQTT vacuum through configuration.yaml.""" - await async_setup_reload_service(hass, MQTT_DOMAIN, PLATFORMS) - await _async_setup_entity(hass, async_add_entities, config) + await async_setup_platform_helper( + hass, vacuum.DOMAIN, config, async_add_entities, _async_setup_entity + ) async def async_setup_entry(hass, config_entry, async_add_entities): @@ -53,7 +52,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) - await async_setup_entry_helper(hass, DOMAIN, setup, DISCOVERY_SCHEMA) + await async_setup_entry_helper(hass, vacuum.DOMAIN, setup, DISCOVERY_SCHEMA) async def _async_setup_entity( diff --git a/tests/components/mqtt/test_alarm_control_panel.py b/tests/components/mqtt/test_alarm_control_panel.py index 091048513c749d..a278cde768b255 100644 --- a/tests/components/mqtt/test_alarm_control_panel.py +++ b/tests/components/mqtt/test_alarm_control_panel.py @@ -53,6 +53,7 @@ help_test_entity_id_update_subscriptions, help_test_publishing_with_custom_encoding, help_test_reloadable, + help_test_reloadable_late, help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, @@ -840,3 +841,10 @@ async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): domain = alarm_control_panel.DOMAIN config = DEFAULT_CONFIG[domain] await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) + + +async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): + """Test reloading the MQTT platform with late entry setup.""" + domain = alarm_control_panel.DOMAIN + config = DEFAULT_CONFIG[domain] + await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) diff --git a/tests/components/mqtt/test_binary_sensor.py b/tests/components/mqtt/test_binary_sensor.py index aa726f4fff21cf..5fa71d73632ed1 100644 --- a/tests/components/mqtt/test_binary_sensor.py +++ b/tests/components/mqtt/test_binary_sensor.py @@ -38,6 +38,7 @@ help_test_entity_id_update_subscriptions, help_test_reload_with_config, help_test_reloadable, + help_test_reloadable_late, help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_unique_id, @@ -879,6 +880,13 @@ async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) +async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): + """Test reloading the MQTT platform with late entry setup.""" + domain = binary_sensor.DOMAIN + config = DEFAULT_CONFIG[domain] + await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) + + async def test_cleanup_triggers_and_restoring_state( hass, mqtt_mock, caplog, tmp_path, freezer ): diff --git a/tests/components/mqtt/test_button.py b/tests/components/mqtt/test_button.py index e4997085ce23ee..83ef7a42705cc1 100644 --- a/tests/components/mqtt/test_button.py +++ b/tests/components/mqtt/test_button.py @@ -26,6 +26,7 @@ help_test_entity_id_update_discovery_update, help_test_publishing_with_custom_encoding, help_test_reloadable, + help_test_reloadable_late, help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, @@ -405,3 +406,10 @@ async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): domain = button.DOMAIN config = DEFAULT_CONFIG[domain] await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) + + +async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): + """Test reloading the MQTT platform with late entry setup.""" + domain = button.DOMAIN + config = DEFAULT_CONFIG[domain] + await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) diff --git a/tests/components/mqtt/test_camera.py b/tests/components/mqtt/test_camera.py index 936e4ef4664ef0..07fd7dc2c14345 100644 --- a/tests/components/mqtt/test_camera.py +++ b/tests/components/mqtt/test_camera.py @@ -27,6 +27,7 @@ help_test_entity_id_update_discovery_update, help_test_entity_id_update_subscriptions, help_test_reloadable, + help_test_reloadable_late, help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, @@ -252,3 +253,10 @@ async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): domain = camera.DOMAIN config = DEFAULT_CONFIG[domain] await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) + + +async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): + """Test reloading the MQTT platform with late entry setup.""" + domain = camera.DOMAIN + config = DEFAULT_CONFIG[domain] + await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) diff --git a/tests/components/mqtt/test_climate.py b/tests/components/mqtt/test_climate.py index 16c765dc51afc1..624823e0ebb321 100644 --- a/tests/components/mqtt/test_climate.py +++ b/tests/components/mqtt/test_climate.py @@ -58,6 +58,7 @@ help_test_entity_id_update_subscriptions, help_test_publishing_with_custom_encoding, help_test_reloadable, + help_test_reloadable_late, help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, @@ -1422,3 +1423,10 @@ async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): domain = CLIMATE_DOMAIN config = DEFAULT_CONFIG[domain] await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) + + +async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): + """Test reloading the MQTT platform with late entry setup.""" + domain = CLIMATE_DOMAIN + config = DEFAULT_CONFIG[domain] + await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index 758cdd801aeaae..8cf7353d196fc9 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -21,7 +21,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.setup import async_setup_component -from tests.common import async_fire_mqtt_message, mock_registry +from tests.common import MockConfigEntry, async_fire_mqtt_message, mock_registry DEFAULT_CONFIG_DEVICE_INFO_ID = { "identifiers": ["helloworld"], @@ -1619,7 +1619,61 @@ async def help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config assert hass.states.get(f"{domain}.test_old_2") assert len(hass.states.async_all(domain)) == 2 - # Create temporary fixture for configuration.yaml based on the supplied config and test a reload with this new config + # Create temporary fixture for configuration.yaml based on the supplied config and + # test a reload with this new config + new_config_1 = copy.deepcopy(config) + new_config_1["name"] = "test_new_1" + new_config_2 = copy.deepcopy(config) + new_config_2["name"] = "test_new_2" + new_config_3 = copy.deepcopy(config) + new_config_3["name"] = "test_new_3" + + await help_test_reload_with_config( + hass, caplog, tmp_path, domain, [new_config_1, new_config_2, new_config_3] + ) + + assert len(hass.states.async_all(domain)) == 3 + + assert hass.states.get(f"{domain}.test_new_1") + assert hass.states.get(f"{domain}.test_new_2") + assert hass.states.get(f"{domain}.test_new_3") + + +async def help_test_reloadable_late(hass, caplog, tmp_path, domain, config): + """Test reloading an MQTT platform when config entry is setup late.""" + # Create and test an old config of 2 entities based on the config supplied + old_config_1 = copy.deepcopy(config) + old_config_1["name"] = "test_old_1" + old_config_2 = copy.deepcopy(config) + old_config_2["name"] = "test_old_2" + + old_yaml_config_file = tmp_path / "configuration.yaml" + old_yaml_config = yaml.dump({domain: [old_config_1, old_config_2]}) + old_yaml_config_file.write_text(old_yaml_config) + assert old_yaml_config_file.read_text() == old_yaml_config + + assert await async_setup_component( + hass, domain, {domain: [old_config_1, old_config_2]} + ) + await hass.async_block_till_done() + + # No MQTT config entry, there should be a warning and no entities + assert ( + "MQTT integration is not setup, skipping setup of manually " + f"configured MQTT {domain}" + ) in caplog.text + assert len(hass.states.async_all(domain)) == 0 + + # User sets up a config entry, should succeed and entities will setup + entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"}) + entry.add_to_hass(hass) + with patch.object(hass_config, "YAML_CONFIG_FILE", old_yaml_config_file): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + assert len(hass.states.async_all(domain)) == 2 + + # Create temporary fixture for configuration.yaml based on the supplied config and + # test a reload with this new config new_config_1 = copy.deepcopy(config) new_config_1["name"] = "test_new_1" new_config_2 = copy.deepcopy(config) diff --git a/tests/components/mqtt/test_config_flow.py b/tests/components/mqtt/test_config_flow.py index befdc139eeba7e..83dafd6b43697a 100644 --- a/tests/components/mqtt/test_config_flow.py +++ b/tests/components/mqtt/test_config_flow.py @@ -82,17 +82,34 @@ async def test_user_connection_fails(hass, mock_try_connection, mock_finish_setu async def test_manual_config_set( hass, mock_try_connection, mock_finish_setup, mqtt_client_mock ): - """Test we ignore entry if manual config available.""" + """Test manual config does not create an entry, and entry can be setup late.""" + # MQTT config present in yaml config assert await async_setup_component(hass, "mqtt", {"mqtt": {"broker": "bla"}}) await hass.async_block_till_done() - assert len(mock_finish_setup.mock_calls) == 1 + assert len(mock_finish_setup.mock_calls) == 0 mock_try_connection.return_value = True + # Start config flow result = await hass.config_entries.flow.async_init( "mqtt", context={"source": config_entries.SOURCE_USER} ) - assert result["type"] == "abort" + assert result["type"] == "form" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"broker": "127.0.0.1"} + ) + + assert result["type"] == "create_entry" + assert result["result"].data == { + "broker": "127.0.0.1", + "port": 1883, + "discovery": True, + } + # Check we tried the connection, with precedence for config entry settings + mock_try_connection.assert_called_once_with("127.0.0.1", 1883, None, None) + # Check config entry got setup + assert len(mock_finish_setup.mock_calls) == 1 async def test_user_single_instance(hass): diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index 59e03dadfe88bb..aad6fa5d9ca32b 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -64,6 +64,7 @@ help_test_entity_id_update_subscriptions, help_test_publishing_with_custom_encoding, help_test_reloadable, + help_test_reloadable_late, help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, @@ -3173,6 +3174,13 @@ async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) +async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): + """Test reloading the MQTT platform with late entry setup.""" + domain = cover.DOMAIN + config = DEFAULT_CONFIG[domain] + await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) + + @pytest.mark.parametrize( "topic,value,attribute,attribute_value", [ diff --git a/tests/components/mqtt/test_fan.py b/tests/components/mqtt/test_fan.py index 9ce5e54262eb08..5418727ec0ed36 100644 --- a/tests/components/mqtt/test_fan.py +++ b/tests/components/mqtt/test_fan.py @@ -51,6 +51,7 @@ help_test_entity_id_update_subscriptions, help_test_publishing_with_custom_encoding, help_test_reloadable, + help_test_reloadable_late, help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, @@ -1803,3 +1804,10 @@ async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): domain = fan.DOMAIN config = DEFAULT_CONFIG[domain] await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) + + +async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): + """Test reloading the MQTT platform with late entry setup.""" + domain = fan.DOMAIN + config = DEFAULT_CONFIG[domain] + await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) diff --git a/tests/components/mqtt/test_humidifier.py b/tests/components/mqtt/test_humidifier.py index f8685898ed5b85..4aa5ff2350b90d 100644 --- a/tests/components/mqtt/test_humidifier.py +++ b/tests/components/mqtt/test_humidifier.py @@ -52,6 +52,7 @@ help_test_entity_id_update_subscriptions, help_test_publishing_with_custom_encoding, help_test_reloadable, + help_test_reloadable_late, help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, @@ -1174,3 +1175,10 @@ async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): domain = humidifier.DOMAIN config = DEFAULT_CONFIG[domain] await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) + + +async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): + """Test reloading the MQTT platform with late entry setup.""" + domain = humidifier.DOMAIN + config = DEFAULT_CONFIG[domain] + await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 3d249fa2144697..92884dcef93fe3 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -1134,6 +1134,8 @@ def mock_tls_set(certificate, certfile=None, keyfile=None, tls_version=None): mqtt.CONF_BIRTH_MESSAGE: { mqtt.ATTR_TOPIC: "birth", mqtt.ATTR_PAYLOAD: "birth", + mqtt.ATTR_QOS: 0, + mqtt.ATTR_RETAIN: False, }, } ], @@ -1162,6 +1164,8 @@ async def wait_birth(topic, payload, qos): mqtt.CONF_BIRTH_MESSAGE: { mqtt.ATTR_TOPIC: "homeassistant/status", mqtt.ATTR_PAYLOAD: "online", + mqtt.ATTR_QOS: 0, + mqtt.ATTR_RETAIN: False, }, } ], @@ -1205,6 +1209,8 @@ async def test_no_birth_message(hass, mqtt_client_mock, mqtt_mock): mqtt.CONF_BIRTH_MESSAGE: { mqtt.ATTR_TOPIC: "homeassistant/status", mqtt.ATTR_PAYLOAD: "online", + mqtt.ATTR_QOS: 0, + mqtt.ATTR_RETAIN: False, }, } ], @@ -1214,17 +1220,16 @@ async def test_delayed_birth_message(hass, mqtt_client_mock, mqtt_config): hass.state = CoreState.starting birth = asyncio.Event() - result = await async_setup_component(hass, mqtt.DOMAIN, {mqtt.DOMAIN: mqtt_config}) - assert result await hass.async_block_till_done() - # Workaround: asynctest==0.13 fails on @functools.lru_cache - spec = dir(hass.data["mqtt"]) - spec.remove("_matching_subscriptions") + entry = MockConfigEntry(domain=mqtt.DOMAIN, data=mqtt_config) + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() mqtt_component_mock = MagicMock( return_value=hass.data["mqtt"], - spec_set=spec, + spec_set=hass.data["mqtt"], wraps=hass.data["mqtt"], ) mqtt_component_mock._mqttc = mqtt_client_mock @@ -1261,6 +1266,8 @@ async def wait_birth(topic, payload, qos): mqtt.CONF_WILL_MESSAGE: { mqtt.ATTR_TOPIC: "death", mqtt.ATTR_PAYLOAD: "death", + mqtt.ATTR_QOS: 0, + mqtt.ATTR_RETAIN: False, }, } ], @@ -1317,9 +1324,28 @@ async def test_mqtt_subscribes_topics_on_connect(hass, mqtt_client_mock, mqtt_mo assert calls == expected -async def test_setup_fails_without_config(hass): - """Test if the MQTT component fails to load with no config.""" - assert not await async_setup_component(hass, mqtt.DOMAIN, {}) +async def test_setup_entry_with_config_override(hass, device_reg, mqtt_client_mock): + """Test if the MQTT component loads with no config and config entry can be setup.""" + data = ( + '{ "device":{"identifiers":["0AFFD2"]},' + ' "state_topic": "foobar/sensor",' + ' "unique_id": "unique" }' + ) + + # mqtt present in yaml config + assert await async_setup_component(hass, mqtt.DOMAIN, {}) + + # User sets up a config entry + entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"}) + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + + # Discover a device to verify the entry was setup correctly + async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data) + await hass.async_block_till_done() + + device_entry = device_reg.async_get_device({("mqtt", "0AFFD2")}) + assert device_entry is not None @pytest.mark.no_fail_on_log_exception diff --git a/tests/components/mqtt/test_legacy_vacuum.py b/tests/components/mqtt/test_legacy_vacuum.py index d6e1524fc40977..1667053f65b7a3 100644 --- a/tests/components/mqtt/test_legacy_vacuum.py +++ b/tests/components/mqtt/test_legacy_vacuum.py @@ -51,6 +51,7 @@ help_test_entity_id_update_subscriptions, help_test_publishing_with_custom_encoding, help_test_reloadable, + help_test_reloadable_late, help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, @@ -840,6 +841,13 @@ async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) +async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): + """Test reloading the MQTT platform with late entry setup.""" + domain = vacuum.DOMAIN + config = DEFAULT_CONFIG + await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) + + @pytest.mark.parametrize( "topic,value,attribute,attribute_value", [ diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index 0eb77990d32535..782ee40f0c8573 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -202,6 +202,7 @@ help_test_entity_id_update_subscriptions, help_test_publishing_with_custom_encoding, help_test_reloadable, + help_test_reloadable_late, help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, @@ -3496,6 +3497,13 @@ async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) +async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): + """Test reloading the MQTT platform with late entry setup.""" + domain = light.DOMAIN + config = DEFAULT_CONFIG[domain] + await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) + + @pytest.mark.parametrize( "topic,value,attribute,attribute_value,init_payload", [ diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index 8f2dce599ac6f4..93bb9b0f573577 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -127,6 +127,7 @@ help_test_entity_id_update_subscriptions, help_test_publishing_with_custom_encoding, help_test_reloadable, + help_test_reloadable_late, help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, @@ -1991,6 +1992,13 @@ async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) +async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): + """Test reloading the MQTT platform with late entry setup.""" + domain = light.DOMAIN + config = DEFAULT_CONFIG[domain] + await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) + + @pytest.mark.parametrize( "topic,value,attribute,attribute_value,init_payload", [ diff --git a/tests/components/mqtt/test_light_template.py b/tests/components/mqtt/test_light_template.py index c68ed8e7f35ffc..4461cf14ef49a4 100644 --- a/tests/components/mqtt/test_light_template.py +++ b/tests/components/mqtt/test_light_template.py @@ -65,6 +65,7 @@ help_test_entity_id_update_subscriptions, help_test_publishing_with_custom_encoding, help_test_reloadable, + help_test_reloadable_late, help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, @@ -1181,6 +1182,13 @@ async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) +async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): + """Test reloading the MQTT platform with late entry setup.""" + domain = light.DOMAIN + config = DEFAULT_CONFIG[domain] + await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) + + @pytest.mark.parametrize( "topic,value,attribute,attribute_value,init_payload", [ diff --git a/tests/components/mqtt/test_lock.py b/tests/components/mqtt/test_lock.py index 35849c4f9fc03e..86e21a261a3327 100644 --- a/tests/components/mqtt/test_lock.py +++ b/tests/components/mqtt/test_lock.py @@ -40,6 +40,7 @@ help_test_entity_id_update_subscriptions, help_test_publishing_with_custom_encoding, help_test_reloadable, + help_test_reloadable_late, help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, @@ -646,6 +647,13 @@ async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) +async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): + """Test reloading the MQTT platform with late entry setup.""" + domain = LOCK_DOMAIN + config = DEFAULT_CONFIG[domain] + await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) + + @pytest.mark.parametrize( "topic,value,attribute,attribute_value", [ diff --git a/tests/components/mqtt/test_number.py b/tests/components/mqtt/test_number.py index 70bc1b40e75fd4..73c1da357fa480 100644 --- a/tests/components/mqtt/test_number.py +++ b/tests/components/mqtt/test_number.py @@ -46,6 +46,7 @@ help_test_entity_id_update_subscriptions, help_test_publishing_with_custom_encoding, help_test_reloadable, + help_test_reloadable_late, help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, @@ -699,6 +700,13 @@ async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) +async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): + """Test reloading the MQTT platform with late entry setup.""" + domain = number.DOMAIN + config = DEFAULT_CONFIG[domain] + await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) + + @pytest.mark.parametrize( "topic,value,attribute,attribute_value", [ diff --git a/tests/components/mqtt/test_scene.py b/tests/components/mqtt/test_scene.py index 97f13ba90c09a0..1ccacc1c5eec90 100644 --- a/tests/components/mqtt/test_scene.py +++ b/tests/components/mqtt/test_scene.py @@ -19,6 +19,7 @@ help_test_discovery_update, help_test_discovery_update_unchanged, help_test_reloadable, + help_test_reloadable_late, help_test_unique_id, ) @@ -183,3 +184,10 @@ async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): domain = scene.DOMAIN config = DEFAULT_CONFIG[domain] await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) + + +async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): + """Test reloading the MQTT platform with late entry setup.""" + domain = scene.DOMAIN + config = DEFAULT_CONFIG[domain] + await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) diff --git a/tests/components/mqtt/test_select.py b/tests/components/mqtt/test_select.py index f6f005bbfb5f78..069c0dfe4c9fc2 100644 --- a/tests/components/mqtt/test_select.py +++ b/tests/components/mqtt/test_select.py @@ -37,6 +37,7 @@ help_test_entity_id_update_subscriptions, help_test_publishing_with_custom_encoding, help_test_reloadable, + help_test_reloadable_late, help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, @@ -578,6 +579,13 @@ async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) +async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): + """Test reloading the MQTT platform with late entry setup.""" + domain = select.DOMAIN + config = DEFAULT_CONFIG[domain] + await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) + + @pytest.mark.parametrize( "topic,value,attribute,attribute_value", [ diff --git a/tests/components/mqtt/test_sensor.py b/tests/components/mqtt/test_sensor.py index b556bcf7537342..8a1be6b11e2860 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -45,6 +45,7 @@ help_test_entity_id_update_subscriptions, help_test_reload_with_config, help_test_reloadable, + help_test_reloadable_late, help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, @@ -972,6 +973,13 @@ async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) +async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): + """Test reloading the MQTT platform with late entry setup.""" + domain = sensor.DOMAIN + config = DEFAULT_CONFIG[domain] + await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) + + async def test_cleanup_triggers_and_restoring_state( hass, mqtt_mock, caplog, tmp_path, freezer ): diff --git a/tests/components/mqtt/test_siren.py b/tests/components/mqtt/test_siren.py index 7f174582a46da7..f39154badc95bc 100644 --- a/tests/components/mqtt/test_siren.py +++ b/tests/components/mqtt/test_siren.py @@ -38,6 +38,7 @@ help_test_entity_id_update_subscriptions, help_test_publishing_with_custom_encoding, help_test_reloadable, + help_test_reloadable_late, help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, @@ -866,6 +867,13 @@ async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) +async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): + """Test reloading the MQTT platform with late entry setup.""" + domain = siren.DOMAIN + config = DEFAULT_CONFIG[domain] + await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) + + @pytest.mark.parametrize( "topic,value,attribute,attribute_value", [ diff --git a/tests/components/mqtt/test_state_vacuum.py b/tests/components/mqtt/test_state_vacuum.py index 7b8928ccb32f04..8691aa73323e67 100644 --- a/tests/components/mqtt/test_state_vacuum.py +++ b/tests/components/mqtt/test_state_vacuum.py @@ -54,6 +54,7 @@ help_test_entity_id_update_subscriptions, help_test_publishing_with_custom_encoding, help_test_reloadable, + help_test_reloadable_late, help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, @@ -607,6 +608,13 @@ async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) +async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): + """Test reloading the MQTT platform with late entry setup.""" + domain = vacuum.DOMAIN + config = DEFAULT_CONFIG + await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) + + @pytest.mark.parametrize( "topic,value,attribute,attribute_value", [ diff --git a/tests/components/mqtt/test_switch.py b/tests/components/mqtt/test_switch.py index 79ee56998e8071..a458ac03baa14d 100644 --- a/tests/components/mqtt/test_switch.py +++ b/tests/components/mqtt/test_switch.py @@ -36,6 +36,7 @@ help_test_entity_id_update_subscriptions, help_test_publishing_with_custom_encoding, help_test_reloadable, + help_test_reloadable_late, help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, @@ -557,6 +558,13 @@ async def test_reloadable(hass, mqtt_mock, caplog, tmp_path): await help_test_reloadable(hass, mqtt_mock, caplog, tmp_path, domain, config) +async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): + """Test reloading the MQTT platform with late entry setup.""" + domain = switch.DOMAIN + config = DEFAULT_CONFIG[domain] + await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) + + @pytest.mark.parametrize( "topic,value,attribute,attribute_value", [ diff --git a/tests/conftest.py b/tests/conftest.py index 9f0958e6aced30..564480a0e91386 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -38,6 +38,7 @@ from tests.common import ( # noqa: E402, isort:skip CLIENT_ID, INSTANCES, + MockConfigEntry, MockUser, async_fire_mqtt_message, async_test_home_assistant, @@ -590,17 +591,22 @@ async def mqtt_mock(hass, mqtt_client_mock, mqtt_config): if mqtt_config is None: mqtt_config = {mqtt.CONF_BROKER: "mock-broker", mqtt.CONF_BIRTH_MESSAGE: {}} - result = await async_setup_component(hass, mqtt.DOMAIN, {mqtt.DOMAIN: mqtt_config}) - assert result await hass.async_block_till_done() - # Workaround: asynctest==0.13 fails on @functools.lru_cache - spec = dir(hass.data["mqtt"]) - spec.remove("_matching_subscriptions") + entry = MockConfigEntry( + data=mqtt_config, + domain=mqtt.DOMAIN, + title="Tasmota", + ) + + entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() mqtt_component_mock = MagicMock( return_value=hass.data["mqtt"], - spec_set=spec, + spec_set=hass.data["mqtt"], wraps=hass.data["mqtt"], ) mqtt_component_mock._mqttc = mqtt_client_mock From e86c82e6750a036a9b9b8bb7ede9f40647f87174 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 10 Feb 2022 14:11:47 -0600 Subject: [PATCH 0516/1098] Tweak Sonos activity monitoring (#66207) * Tweak Sonos activity monitoring * Support new 'sleeping' vanish reason * Check availability before last-ditch check * Use short-timeout call for last-ditch check * Adjust reboot logging message and severity * Simplify activity check failure --- homeassistant/components/sonos/speaker.py | 39 +++++++++++++---------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index ed530704550042..16ca58448e20f4 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -79,6 +79,7 @@ "renderingControl", "zoneGroupTopology", ] +SUPPORTED_VANISH_REASONS = ("sleeping", "upgrade") UNAVAILABLE_VALUES = {"", "NOT_IMPLEMENTED", None} UNUSED_DEVICE_KEYS = ["SPID", "TargetRoomName"] @@ -553,25 +554,29 @@ def speaker_activity(self, source): async def async_check_activity(self, now: datetime.datetime) -> None: """Validate availability of the speaker based on recent activity.""" + if not self.available: + return if time.monotonic() - self._last_activity < AVAILABILITY_TIMEOUT: return try: - _ = await self.hass.async_add_executor_job(getattr, self.soco, "volume") - except (OSError, SoCoException): - pass + # Make a short-timeout call as a final check + # before marking this speaker as unavailable + await self.hass.async_add_executor_job( + partial( + self.soco.renderingControl.GetVolume, + [("InstanceID", 0), ("Channel", "Master")], + timeout=1, + ) + ) + except OSError: + _LOGGER.warning( + "No recent activity and cannot reach %s, marking unavailable", + self.zone_name, + ) + await self.async_offline() else: self.speaker_activity("timeout poll") - return - - if not self.available: - return - - _LOGGER.warning( - "No recent activity and cannot reach %s, marking unavailable", - self.zone_name, - ) - await self.async_offline() async def async_offline(self) -> None: """Handle removal of speaker when unavailable.""" @@ -603,8 +608,8 @@ async def async_vanished(self, reason: str) -> None: async def async_rebooted(self, soco: SoCo) -> None: """Handle a detected speaker reboot.""" - _LOGGER.warning( - "%s rebooted or lost network connectivity, reconnecting with %s", + _LOGGER.debug( + "%s rebooted, reconnecting with %s", self.zone_name, soco, ) @@ -717,7 +722,9 @@ def async_update_groups(self, event: SonosEvent) -> None: if xml := event.variables.get("zone_group_state"): zgs = ET.fromstring(xml) for vanished_device in zgs.find("VanishedDevices") or []: - if (reason := vanished_device.get("Reason")) != "sleeping": + if ( + reason := vanished_device.get("Reason") + ) not in SUPPORTED_VANISH_REASONS: _LOGGER.debug( "Ignoring %s marked %s as vanished with reason: %s", self.zone_name, From 854d7d49367d560406d6099a5ba56a0be6c0b9c7 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 10 Feb 2022 12:23:41 -0800 Subject: [PATCH 0517/1098] Fix shutil import for local source (#66286) --- homeassistant/components/media_source/local_source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/media_source/local_source.py b/homeassistant/components/media_source/local_source.py index f43bb3d97c709a..66e6bdd8379e60 100644 --- a/homeassistant/components/media_source/local_source.py +++ b/homeassistant/components/media_source/local_source.py @@ -4,10 +4,10 @@ import logging import mimetypes from pathlib import Path +import shutil from aiohttp import web from aiohttp.web_request import FileField -from aioshutil import shutil import voluptuous as vol from homeassistant.components.http import HomeAssistantView From 9fa2bdd3fdeda81b96f2ab8c7732aad11fa4b180 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 10 Feb 2022 14:25:07 -0600 Subject: [PATCH 0518/1098] Handle more Sonos favorites in media browser (#66205) --- homeassistant/components/sonos/const.py | 3 +++ .../components/sonos/media_browser.py | 20 ++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonos/const.py b/homeassistant/components/sonos/const.py index 2d5dbc5195a9c4..2f6ea3c20cbb3d 100644 --- a/homeassistant/components/sonos/const.py +++ b/homeassistant/components/sonos/const.py @@ -44,6 +44,7 @@ SONOS_TRACKS = "tracks" SONOS_COMPOSER = "composers" SONOS_RADIO = "radio" +SONOS_OTHER_ITEM = "other items" SONOS_STATE_PLAYING = "PLAYING" SONOS_STATE_TRANSITIONING = "TRANSITIONING" @@ -76,6 +77,7 @@ "object.container.person.musicArtist": MEDIA_CLASS_ARTIST, "object.container.playlistContainer.sameArtist": MEDIA_CLASS_ARTIST, "object.container.playlistContainer": MEDIA_CLASS_PLAYLIST, + "object.item": MEDIA_CLASS_TRACK, "object.item.audioItem.musicTrack": MEDIA_CLASS_TRACK, "object.item.audioItem.audioBroadcast": MEDIA_CLASS_GENRE, } @@ -121,6 +123,7 @@ "object.container.person.musicArtist": SONOS_ALBUM_ARTIST, "object.container.playlistContainer.sameArtist": SONOS_ARTIST, "object.container.playlistContainer": SONOS_PLAYLISTS, + "object.item": SONOS_OTHER_ITEM, "object.item.audioItem.musicTrack": SONOS_TRACKS, "object.item.audioItem.audioBroadcast": SONOS_RADIO, } diff --git a/homeassistant/components/sonos/media_browser.py b/homeassistant/components/sonos/media_browser.py index 2e3bf9d1fcb12e..2d9714699284c8 100644 --- a/homeassistant/components/sonos/media_browser.py +++ b/homeassistant/components/sonos/media_browser.py @@ -162,8 +162,17 @@ def build_item_response(media_library, payload, get_thumbnail_url=None): payload["idstring"].split("/")[2:] ) + try: + search_type = MEDIA_TYPES_TO_SONOS[payload["search_type"]] + except KeyError: + _LOGGER.debug( + "Unknown media type received when building item response: %s", + payload["search_type"], + ) + return + media = media_library.browse_by_idstring( - MEDIA_TYPES_TO_SONOS[payload["search_type"]], + search_type, payload["idstring"], full_album_art_uri=True, max_items=0, @@ -371,11 +380,16 @@ def favorites_payload(favorites): group_types = {fav.reference.item_class for fav in favorites} for group_type in sorted(group_types): - media_content_type = SONOS_TYPES_MAPPING[group_type] + try: + media_content_type = SONOS_TYPES_MAPPING[group_type] + media_class = SONOS_TO_MEDIA_CLASSES[group_type] + except KeyError: + _LOGGER.debug("Unknown media type or class received %s", group_type) + continue children.append( BrowseMedia( title=media_content_type.title(), - media_class=SONOS_TO_MEDIA_CLASSES[group_type], + media_class=media_class, media_content_id=group_type, media_content_type="favorites_folder", can_play=False, From c222be095866ef2dd874afec5ff1a56ee5c04523 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Fri, 11 Feb 2022 04:25:31 +0800 Subject: [PATCH 0519/1098] Catch ConnectionResetError when writing MJPEG in camera (#66245) --- homeassistant/components/camera/__init__.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index a2eb70e1c4264d..b955f1a0249bf3 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -221,7 +221,12 @@ async def async_get_mjpeg_stream( """Fetch an mjpeg stream from a camera entity.""" camera = _get_camera_from_entity_id(hass, entity_id) - return await camera.handle_async_mjpeg_stream(request) + try: + stream = await camera.handle_async_mjpeg_stream(request) + except ConnectionResetError: + stream = None + _LOGGER.debug("Error while writing MJPEG stream to transport") + return stream async def async_get_still_stream( @@ -783,7 +788,11 @@ class CameraMjpegStream(CameraView): async def handle(self, request: web.Request, camera: Camera) -> web.StreamResponse: """Serve camera stream, possibly with interval.""" if (interval_str := request.query.get("interval")) is None: - stream = await camera.handle_async_mjpeg_stream(request) + try: + stream = await camera.handle_async_mjpeg_stream(request) + except ConnectionResetError: + stream = None + _LOGGER.debug("Error while writing MJPEG stream to transport") if stream is None: raise web.HTTPBadGateway() return stream From c91a20537ae49747dd117b28cb421a190e780150 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 10 Feb 2022 14:30:18 -0600 Subject: [PATCH 0520/1098] Add discovery on network up to WiZ (#66144) --- homeassistant/components/wiz/__init__.py | 1 + homeassistant/components/wiz/config_flow.py | 22 ++++++--- tests/components/wiz/test_config_flow.py | 50 ++++++++++++++++++++- 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/wiz/__init__.py b/homeassistant/components/wiz/__init__.py index 15b46a14ae0ac5..a29990b6d44f5a 100644 --- a/homeassistant/components/wiz/__init__.py +++ b/homeassistant/components/wiz/__init__.py @@ -83,6 +83,7 @@ async def _async_update() -> None: ) await bulb.start_push(lambda _: coordinator.async_set_updated_data(None)) + bulb.set_discovery_callback(lambda bulb: async_trigger_discovery(hass, [bulb])) await coordinator.async_config_entry_first_refresh() hass.data.setdefault(DOMAIN, {})[entry.entry_id] = WizData( diff --git a/homeassistant/components/wiz/config_flow.py b/homeassistant/components/wiz/config_flow.py index 3fe3b9071b06d6..aa564a14f333b8 100644 --- a/homeassistant/components/wiz/config_flow.py +++ b/homeassistant/components/wiz/config_flow.py @@ -12,7 +12,7 @@ from homeassistant import config_entries from homeassistant.components import dhcp from homeassistant.const import CONF_HOST -from homeassistant.data_entry_flow import FlowResult +from homeassistant.data_entry_flow import AbortFlow, FlowResult from homeassistant.util.network import is_ip_address from .const import DEFAULT_NAME, DISCOVER_SCAN_TIMEOUT, DOMAIN, WIZ_EXCEPTIONS @@ -60,13 +60,19 @@ async def _async_handle_discovery(self) -> FlowResult: mac = device.mac_address await self.async_set_unique_id(mac) self._abort_if_unique_id_configured(updates={CONF_HOST: ip_address}) - bulb = wizlight(ip_address) + await self._async_connect_discovered_or_abort() + return await self.async_step_discovery_confirm() + + async def _async_connect_discovered_or_abort(self) -> None: + """Connect to the device and verify its responding.""" + device = self._discovered_device + assert device is not None + bulb = wizlight(device.ip_address) try: bulbtype = await bulb.get_bulbtype() - except WIZ_EXCEPTIONS: - return self.async_abort(reason="cannot_connect") - self._name = name_from_bulb_type_and_mac(bulbtype, mac) - return await self.async_step_discovery_confirm() + except WIZ_EXCEPTIONS as ex: + raise AbortFlow("cannot_connect") from ex + self._name = name_from_bulb_type_and_mac(bulbtype, device.mac_address) async def async_step_discovery_confirm( self, user_input: dict[str, Any] | None = None @@ -76,6 +82,10 @@ async def async_step_discovery_confirm( assert self._name is not None ip_address = self._discovered_device.ip_address if user_input is not None: + # Make sure the device is still there and + # update the name if the firmware has auto + # updated since discovery + await self._async_connect_discovered_or_abort() return self.async_create_entry( title=self._name, data={CONF_HOST: ip_address}, diff --git a/tests/components/wiz/test_config_flow.py b/tests/components/wiz/test_config_flow.py index 465bc3e3d2772d..dc4bd4de32933e 100644 --- a/tests/components/wiz/test_config_flow.py +++ b/tests/components/wiz/test_config_flow.py @@ -267,7 +267,9 @@ async def test_discovered_by_dhcp_or_integration_discovery( assert result["type"] == RESULT_TYPE_FORM assert result["step_id"] == "discovery_confirm" - with patch( + with _patch_wizlight( + device=device, extended_white_range=extended_white_range, bulb_type=bulb_type + ), patch( "homeassistant.components.wiz.async_setup_entry", return_value=True, ) as mock_setup_entry, patch( @@ -416,3 +418,49 @@ async def test_setup_via_discovery_cannot_connect(hass): assert result3["type"] == "abort" assert result3["reason"] == "cannot_connect" + + +async def test_discovery_with_firmware_update(hass): + """Test we check the device again between first discovery and config entry creation.""" + with _patch_wizlight( + device=FAKE_BULB_CONFIG, + extended_white_range=FAKE_EXTENDED_WHITE_RANGE, + bulb_type=FAKE_RGBW_BULB, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, + data=INTEGRATION_DISCOVERY, + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "discovery_confirm" + + # In between discovery and when the user clicks to set it up the firmware + # updates and we now can see its really RGBWW not RGBW since the older + # firmwares did not tell us how many white channels exist + + with patch( + "homeassistant.components.wiz.async_setup_entry", + return_value=True, + ) as mock_setup_entry, patch( + "homeassistant.components.wiz.async_setup", return_value=True + ) as mock_setup, _patch_wizlight( + device=FAKE_BULB_CONFIG, + extended_white_range=FAKE_EXTENDED_WHITE_RANGE, + bulb_type=FAKE_RGBWW_BULB, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {}, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "WiZ RGBWW Tunable ABCABC" + assert result2["data"] == { + CONF_HOST: "1.1.1.1", + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 From c6f3c5da79df3eae49605b78ea90ad9eae4dc6ab Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 10 Feb 2022 21:38:33 +0100 Subject: [PATCH 0521/1098] Type Spotify hass data (#66285) --- homeassistant/components/spotify/__init__.py | 33 +++-- .../components/spotify/browse_media.py | 16 +-- homeassistant/components/spotify/const.py | 4 - .../components/spotify/media_player.py | 123 ++++++------------ 4 files changed, 64 insertions(+), 112 deletions(-) diff --git a/homeassistant/components/spotify/__init__.py b/homeassistant/components/spotify/__init__.py index 5599965a2a63cf..24266e2d8bb08a 100644 --- a/homeassistant/components/spotify/__init__.py +++ b/homeassistant/components/spotify/__init__.py @@ -1,5 +1,8 @@ """The spotify integration.""" +from dataclasses import dataclass +from typing import Any + import aiohttp from spotipy import Spotify, SpotifyException import voluptuous as vol @@ -22,13 +25,7 @@ from . import config_flow from .browse_media import async_browse_media -from .const import ( - DATA_SPOTIFY_CLIENT, - DATA_SPOTIFY_ME, - DATA_SPOTIFY_SESSION, - DOMAIN, - SPOTIFY_SCOPES, -) +from .const import DOMAIN, SPOTIFY_SCOPES from .util import is_spotify_media_type, resolve_spotify_media_type CONFIG_SCHEMA = vol.Schema( @@ -54,6 +51,15 @@ ] +@dataclass +class HomeAssistantSpotifyData: + """Spotify data stored in the Home Assistant data object.""" + + client: Spotify + current_user: dict[str, Any] + session: OAuth2Session + + async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Spotify integration.""" if DOMAIN not in config: @@ -92,12 +98,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: except SpotifyException as err: raise ConfigEntryNotReady from err + if not current_user: + raise ConfigEntryNotReady + hass.data.setdefault(DOMAIN, {}) - hass.data[DOMAIN][entry.entry_id] = { - DATA_SPOTIFY_CLIENT: spotify, - DATA_SPOTIFY_ME: current_user, - DATA_SPOTIFY_SESSION: session, - } + hass.data[DOMAIN][entry.entry_id] = HomeAssistantSpotifyData( + client=spotify, + current_user=current_user, + session=session, + ) if not set(session.token["scope"].split(" ")).issuperset(SPOTIFY_SCOPES): raise ConfigEntryAuthFailed diff --git a/homeassistant/components/spotify/browse_media.py b/homeassistant/components/spotify/browse_media.py index 0ffdfd6ace6e37..ae7c24e80d2a76 100644 --- a/homeassistant/components/spotify/browse_media.py +++ b/homeassistant/components/spotify/browse_media.py @@ -27,15 +27,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session -from .const import ( - DATA_SPOTIFY_CLIENT, - DATA_SPOTIFY_ME, - DATA_SPOTIFY_SESSION, - DOMAIN, - MEDIA_PLAYER_PREFIX, - MEDIA_TYPE_SHOW, - PLAYABLE_MEDIA_TYPES, -) +from .const import DOMAIN, MEDIA_PLAYER_PREFIX, MEDIA_TYPE_SHOW, PLAYABLE_MEDIA_TYPES from .util import fetch_image_url BROWSE_LIMIT = 48 @@ -155,9 +147,9 @@ async def async_browse_media( raise BrowseError("No Spotify accounts available") return await async_browse_media_internal( hass, - info[DATA_SPOTIFY_CLIENT], - info[DATA_SPOTIFY_SESSION], - info[DATA_SPOTIFY_ME], + info.client, + info.session, + info.current_user, media_content_type, media_content_id, can_play_artist=can_play_artist, diff --git a/homeassistant/components/spotify/const.py b/homeassistant/components/spotify/const.py index 6e54ed21ec1f2e..4c86234045be11 100644 --- a/homeassistant/components/spotify/const.py +++ b/homeassistant/components/spotify/const.py @@ -9,10 +9,6 @@ DOMAIN = "spotify" -DATA_SPOTIFY_CLIENT = "spotify_client" -DATA_SPOTIFY_ME = "spotify_me" -DATA_SPOTIFY_SESSION = "spotify_session" - SPOTIFY_SCOPES = [ # Needed to be able to control playback "user-modify-playback-state", diff --git a/homeassistant/components/spotify/media_player.py b/homeassistant/components/spotify/media_player.py index 58f581fdf8285f..cdcc0132f937b7 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -5,7 +5,6 @@ import datetime as dt from datetime import timedelta import logging -from typing import Any import requests from spotipy import Spotify, SpotifyException @@ -33,31 +32,17 @@ SUPPORT_VOLUME_SET, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - CONF_ID, - CONF_NAME, - STATE_IDLE, - STATE_PAUSED, - STATE_PLAYING, -) +from homeassistant.const import CONF_ID, STATE_IDLE, STATE_PAUSED, STATE_PLAYING from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util.dt import utc_from_timestamp +from . import HomeAssistantSpotifyData from .browse_media import async_browse_media_internal -from .const import ( - DATA_SPOTIFY_CLIENT, - DATA_SPOTIFY_ME, - DATA_SPOTIFY_SESSION, - DOMAIN, - MEDIA_PLAYER_PREFIX, - PLAYABLE_MEDIA_TYPES, - SPOTIFY_SCOPES, -) +from .const import DOMAIN, MEDIA_PLAYER_PREFIX, PLAYABLE_MEDIA_TYPES, SPOTIFY_SCOPES from .util import fetch_image_url _LOGGER = logging.getLogger(__name__) @@ -98,7 +83,7 @@ async def async_setup_entry( spotify = SpotifyMediaPlayer( hass.data[DOMAIN][entry.entry_id], entry.data[CONF_ID], - entry.data[CONF_NAME], + entry.title, ) async_add_entities([spotify], True) @@ -135,57 +120,36 @@ class SpotifyMediaPlayer(MediaPlayerEntity): def __init__( self, - spotify_data, + data: HomeAssistantSpotifyData, user_id: str, name: str, ) -> None: """Initialize.""" self._id = user_id - self._spotify_data = spotify_data - self._name = f"Spotify {name}" - self._scope_ok = set(self._session.token["scope"].split(" ")).issuperset( - SPOTIFY_SCOPES - ) - - self._currently_playing: dict | None = {} - self._devices: list[dict] | None = [] - self._playlist: dict | None = None + self.data = data - self._attr_name = self._name + self._attr_name = f"Spotify {name}" self._attr_unique_id = user_id - @property - def _me(self) -> dict[str, Any]: - """Return spotify user info.""" - return self._spotify_data[DATA_SPOTIFY_ME] - - @property - def _session(self) -> OAuth2Session: - """Return spotify session.""" - return self._spotify_data[DATA_SPOTIFY_SESSION] + if self.data.current_user["product"] == "premium": + self._attr_supported_features = SUPPORT_SPOTIFY - @property - def _spotify(self) -> Spotify: - """Return spotify API.""" - return self._spotify_data[DATA_SPOTIFY_CLIENT] - - @property - def device_info(self) -> DeviceInfo: - """Return device information about this entity.""" - model = "Spotify Free" - if self._me is not None: - product = self._me["product"] - model = f"Spotify {product}" - - return DeviceInfo( - identifiers={(DOMAIN, self._id)}, + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, user_id)}, manufacturer="Spotify AB", - model=model, - name=self._name, + model=f"Spotify {data.current_user['product']}", + name=f"Spotify {name}", entry_type=DeviceEntryType.SERVICE, configuration_url="https://open.spotify.com", ) + self._scope_ok = set(data.session.token["scope"].split(" ")).issuperset( + SPOTIFY_SCOPES + ) + self._currently_playing: dict | None = {} + self._devices: list[dict] | None = [] + self._playlist: dict | None = None + @property def state(self) -> str | None: """Return the playback state.""" @@ -315,42 +279,35 @@ def repeat(self) -> str | None: return None return REPEAT_MODE_MAPPING_TO_HA.get(repeat_state) - @property - def supported_features(self) -> int: - """Return the media player features that are supported.""" - if self._me["product"] != "premium": - return 0 - return SUPPORT_SPOTIFY - @spotify_exception_handler def set_volume_level(self, volume: int) -> None: """Set the volume level.""" - self._spotify.volume(int(volume * 100)) + self.data.client.volume(int(volume * 100)) @spotify_exception_handler def media_play(self) -> None: """Start or resume playback.""" - self._spotify.start_playback() + self.data.client.start_playback() @spotify_exception_handler def media_pause(self) -> None: """Pause playback.""" - self._spotify.pause_playback() + self.data.client.pause_playback() @spotify_exception_handler def media_previous_track(self) -> None: """Skip to previous track.""" - self._spotify.previous_track() + self.data.client.previous_track() @spotify_exception_handler def media_next_track(self) -> None: """Skip to next track.""" - self._spotify.next_track() + self.data.client.next_track() @spotify_exception_handler def media_seek(self, position): """Send seek command.""" - self._spotify.seek_track(int(position * 1000)) + self.data.client.seek_track(int(position * 1000)) @spotify_exception_handler def play_media(self, media_type: str, media_id: str, **kwargs) -> None: @@ -379,7 +336,7 @@ def play_media(self, media_type: str, media_id: str, **kwargs) -> None: ): kwargs["device_id"] = self._devices[0].get("id") - self._spotify.start_playback(**kwargs) + self.data.client.start_playback(**kwargs) @spotify_exception_handler def select_source(self, source: str) -> None: @@ -389,7 +346,7 @@ def select_source(self, source: str) -> None: for device in self._devices: if device["name"] == source: - self._spotify.transfer_playback( + self.data.client.transfer_playback( device["id"], self.state == STATE_PLAYING ) return @@ -397,14 +354,14 @@ def select_source(self, source: str) -> None: @spotify_exception_handler def set_shuffle(self, shuffle: bool) -> None: """Enable/Disable shuffle mode.""" - self._spotify.shuffle(shuffle) + self.data.client.shuffle(shuffle) @spotify_exception_handler def set_repeat(self, repeat: str) -> None: """Set repeat mode.""" if repeat not in REPEAT_MODE_MAPPING_TO_SPOTIFY: raise ValueError(f"Unsupported repeat mode: {repeat}") - self._spotify.repeat(REPEAT_MODE_MAPPING_TO_SPOTIFY[repeat]) + self.data.client.repeat(REPEAT_MODE_MAPPING_TO_SPOTIFY[repeat]) @spotify_exception_handler def update(self) -> None: @@ -412,23 +369,21 @@ def update(self) -> None: if not self.enabled: return - if not self._session.valid_token or self._spotify is None: + if not self.data.session.valid_token or self.data.client is None: run_coroutine_threadsafe( - self._session.async_ensure_token_valid(), self.hass.loop + self.data.session.async_ensure_token_valid(), self.hass.loop ).result() - self._spotify_data[DATA_SPOTIFY_CLIENT] = Spotify( - auth=self._session.token["access_token"] - ) + self.data.client = Spotify(auth=self.data.session.token["access_token"]) - current = self._spotify.current_playback() + current = self.data.client.current_playback() self._currently_playing = current or {} self._playlist = None context = self._currently_playing.get("context") if context is not None and context["type"] == MEDIA_TYPE_PLAYLIST: - self._playlist = self._spotify.playlist(current["context"]["uri"]) + self._playlist = self.data.client.playlist(current["context"]["uri"]) - devices = self._spotify.devices() or {} + devices = self.data.client.devices() or {} self._devices = devices.get("devices", []) async def async_browse_media( @@ -444,9 +399,9 @@ async def async_browse_media( return await async_browse_media_internal( self.hass, - self._spotify, - self._session, - self._me, + self.data.client, + self.data.session, + self.data.current_user, media_content_type, media_content_id, ) From 4d944e35fd0fb954eb91aad020ca4bcd0c843168 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 10 Feb 2022 14:48:13 -0600 Subject: [PATCH 0522/1098] Skip polling Sonos audio input sensor when idle (#66271) --- homeassistant/components/sonos/sensor.py | 10 ++- tests/components/sonos/conftest.py | 78 +++++++++++++++++++++++- tests/components/sonos/test_sensor.py | 40 +++++++++++- 3 files changed, 124 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sonos/sensor.py b/homeassistant/components/sonos/sensor.py index 5cccc40ac5f984..f011cb2d7540af 100644 --- a/homeassistant/components/sonos/sensor.py +++ b/homeassistant/components/sonos/sensor.py @@ -11,7 +11,7 @@ from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import SONOS_CREATE_AUDIO_FORMAT_SENSOR, SONOS_CREATE_BATTERY +from .const import SONOS_CREATE_AUDIO_FORMAT_SENSOR, SONOS_CREATE_BATTERY, SOURCE_TV from .entity import SonosEntity, SonosPollingEntity from .helpers import soco_error from .speaker import SonosSpeaker @@ -94,8 +94,14 @@ def __init__(self, speaker: SonosSpeaker, audio_format: str) -> None: self._attr_name = f"{self.speaker.zone_name} Audio Input Format" self._attr_native_value = audio_format - @soco_error() def poll_state(self) -> None: + """Poll the state if TV source is active and state has settled.""" + if self.speaker.media.source_name != SOURCE_TV and self.state == "No input": + return + self._poll_state() + + @soco_error() + def _poll_state(self) -> None: """Poll the device for the current state.""" self._attr_native_value = self.soco.soundbar_audio_input_format diff --git a/tests/components/sonos/conftest.py b/tests/components/sonos/conftest.py index 14ad17bec8b65f..d7791c6ce72a79 100644 --- a/tests/components/sonos/conftest.py +++ b/tests/components/sonos/conftest.py @@ -3,6 +3,7 @@ from unittest.mock import AsyncMock, MagicMock, Mock, patch import pytest +from soco import SoCo from homeassistant.components import ssdp, zeroconf from homeassistant.components.media_player import DOMAIN as MP_DOMAIN @@ -82,7 +83,9 @@ def config_entry_fixture(): @pytest.fixture(name="soco") -def soco_fixture(music_library, speaker_info, battery_info, alarm_clock): +def soco_fixture( + music_library, speaker_info, current_track_info_empty, battery_info, alarm_clock +): """Create a mock soco SoCo fixture.""" with patch("homeassistant.components.sonos.SoCo", autospec=True) as mock, patch( "socket.gethostbyname", return_value="192.168.42.2" @@ -92,6 +95,8 @@ def soco_fixture(music_library, speaker_info, battery_info, alarm_clock): mock_soco.uid = "RINCON_test" mock_soco.play_mode = "NORMAL" mock_soco.music_library = music_library + mock_soco.get_current_track_info.return_value = current_track_info_empty + mock_soco.music_source_from_uri = SoCo.music_source_from_uri mock_soco.get_speaker_info.return_value = speaker_info mock_soco.avTransport = SonosMockService("AVTransport") mock_soco.renderingControl = SonosMockService("RenderingControl") @@ -216,6 +221,22 @@ def speaker_info_fixture(): } +@pytest.fixture(name="current_track_info_empty") +def current_track_info_empty_fixture(): + """Create current_track_info_empty fixture.""" + return { + "title": "", + "artist": "", + "album": "", + "album_art": "", + "position": "NOT_IMPLEMENTED", + "playlist_position": "1", + "duration": "NOT_IMPLEMENTED", + "uri": "", + "metadata": "NOT_IMPLEMENTED", + } + + @pytest.fixture(name="battery_info") def battery_info_fixture(): """Create battery_info fixture.""" @@ -254,6 +275,61 @@ def alarm_event_fixture(soco): return SonosMockEvent(soco, soco.alarmClock, variables) +@pytest.fixture(name="no_media_event") +def no_media_event_fixture(soco): + """Create no_media_event_fixture.""" + variables = { + "current_crossfade_mode": "0", + "current_play_mode": "NORMAL", + "current_section": "0", + "current_track_uri": "", + "enqueued_transport_uri": "", + "enqueued_transport_uri_meta_data": "", + "transport_state": "STOPPED", + } + return SonosMockEvent(soco, soco.avTransport, variables) + + +@pytest.fixture(name="tv_event") +def tv_event_fixture(soco): + """Create alarm_event fixture.""" + variables = { + "transport_state": "PLAYING", + "current_play_mode": "NORMAL", + "current_crossfade_mode": "0", + "number_of_tracks": "1", + "current_track": "1", + "current_section": "0", + "current_track_uri": f"x-sonos-htastream:{soco.uid}:spdif", + "current_track_duration": "", + "current_track_meta_data": { + "title": " ", + "parent_id": "-1", + "item_id": "-1", + "restricted": True, + "resources": [], + "desc": None, + }, + "next_track_uri": "", + "next_track_meta_data": "", + "enqueued_transport_uri": "", + "enqueued_transport_uri_meta_data": "", + "playback_storage_medium": "NETWORK", + "av_transport_uri": f"x-sonos-htastream:{soco.uid}:spdif", + "av_transport_uri_meta_data": { + "title": soco.uid, + "parent_id": "0", + "item_id": "spdif-input", + "restricted": False, + "resources": [], + "desc": None, + }, + "current_transport_actions": "Set, Play", + "current_valid_play_modes": "", + } + return SonosMockEvent(soco, soco.avTransport, variables) + + @pytest.fixture(autouse=True) def mock_get_source_ip(mock_get_source_ip): """Mock network util's async_get_source_ip in all sonos tests.""" diff --git a/tests/components/sonos/test_sensor.py b/tests/components/sonos/test_sensor.py index 8fb757891496a6..bda4e08cd25b9d 100644 --- a/tests/components/sonos/test_sensor.py +++ b/tests/components/sonos/test_sensor.py @@ -1,9 +1,15 @@ """Tests for the Sonos battery sensor platform.""" +from unittest.mock import PropertyMock + from soco.exceptions import NotSupportedException +from homeassistant.components.sensor import SCAN_INTERVAL from homeassistant.components.sonos.binary_sensor import ATTR_BATTERY_POWER_SOURCE from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.helpers import entity_registry as ent_reg +from homeassistant.util import dt as dt_util + +from tests.common import async_fire_time_changed async def test_entity_registry_unsupported(hass, async_setup_sonos, soco): @@ -113,14 +119,46 @@ async def test_device_payload_without_battery_and_ignored_keys( assert ignored_payload not in caplog.text -async def test_audio_input_sensor(hass, async_autosetup_sonos, soco): +async def test_audio_input_sensor( + hass, async_autosetup_sonos, soco, tv_event, no_media_event +): """Test audio input sensor.""" entity_registry = ent_reg.async_get(hass) + subscription = soco.avTransport.subscribe.return_value + sub_callback = subscription.callback + sub_callback(tv_event) + await hass.async_block_till_done() + audio_input_sensor = entity_registry.entities["sensor.zone_a_audio_input_format"] audio_input_state = hass.states.get(audio_input_sensor.entity_id) assert audio_input_state.state == "Dolby 5.1" + # Set mocked input format to new value and ensure poll success + no_input_mock = PropertyMock(return_value="No input") + type(soco).soundbar_audio_input_format = no_input_mock + + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + + no_input_mock.assert_called_once() + audio_input_state = hass.states.get(audio_input_sensor.entity_id) + assert audio_input_state.state == "No input" + + # Ensure state is not polled when source is not TV and state is already "No input" + sub_callback(no_media_event) + await hass.async_block_till_done() + + unpolled_mock = PropertyMock(return_value="Will not be polled") + type(soco).soundbar_audio_input_format = unpolled_mock + + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + + unpolled_mock.assert_not_called() + audio_input_state = hass.states.get(audio_input_sensor.entity_id) + assert audio_input_state.state == "No input" + async def test_microphone_binary_sensor( hass, async_autosetup_sonos, soco, device_properties_event From ad09e875a6e572cb08ee209caff1b45e0ab866ac Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Thu, 10 Feb 2022 21:59:42 +0100 Subject: [PATCH 0523/1098] Correct philips_js usage of the overloaded coordinator (#66287) --- homeassistant/components/philips_js/media_player.py | 7 +++---- homeassistant/components/philips_js/remote.py | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/philips_js/media_player.py b/homeassistant/components/philips_js/media_player.py index 1a3c6b52a0d976..be24be632fcb44 100644 --- a/homeassistant/components/philips_js/media_player.py +++ b/homeassistant/components/philips_js/media_player.py @@ -82,7 +82,7 @@ async def async_setup_entry( class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): """Representation of a Philips TV exposing the JointSpace API.""" - _coordinator: PhilipsTVDataUpdateCoordinator + coordinator: PhilipsTVDataUpdateCoordinator _attr_device_class = MediaPlayerDeviceClass.TV def __init__( @@ -91,7 +91,6 @@ def __init__( ) -> None: """Initialize the Philips TV.""" self._tv = coordinator.api - self._coordinator = coordinator self._sources = {} self._channels = {} self._supports = SUPPORT_PHILIPS_JS @@ -125,7 +124,7 @@ async def _async_update_soon(self): def supported_features(self): """Flag media player features that are supported.""" supports = self._supports - if self._coordinator.turn_on or ( + if self.coordinator.turn_on or ( self._tv.on and self._tv.powerstate is not None ): supports |= SUPPORT_TURN_ON @@ -170,7 +169,7 @@ async def async_turn_on(self): await self._tv.setPowerState("On") self._state = STATE_ON else: - await self._coordinator.turn_on.async_run(self.hass, self._context) + await self.coordinator.turn_on.async_run(self.hass, self._context) await self._async_update_soon() async def async_turn_off(self): diff --git a/homeassistant/components/philips_js/remote.py b/homeassistant/components/philips_js/remote.py index 6bf60f7f5b0320..09fe16215b6ca4 100644 --- a/homeassistant/components/philips_js/remote.py +++ b/homeassistant/components/philips_js/remote.py @@ -30,7 +30,7 @@ async def async_setup_entry( class PhilipsTVRemote(CoordinatorEntity, RemoteEntity): """Device that sends commands.""" - _coordinator: PhilipsTVDataUpdateCoordinator + coordinator: PhilipsTVDataUpdateCoordinator def __init__( self, @@ -63,7 +63,7 @@ async def async_turn_on(self, **kwargs): if self._tv.on and self._tv.powerstate: await self._tv.setPowerState("On") else: - await self._coordinator.turn_on.async_run(self.hass, self._context) + await self.coordinator.turn_on.async_run(self.hass, self._context) self.async_write_ha_state() async def async_turn_off(self, **kwargs): From c1760683a0e414fd5e1541b14395ea222121d3d0 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Thu, 10 Feb 2022 22:11:40 +0100 Subject: [PATCH 0524/1098] Add diagnostics for philips_js (#66233) * Add diagnostics for philips_js * Update homeassistant/components/philips_js/diagnostics.py Co-authored-by: Paulus Schoutsen * Update homeassistant/components/philips_js/diagnostics.py Co-authored-by: Robert Svensson * Also redact username/password They are really not that secret, but seem logical. * Redact unique id Co-authored-by: Paulus Schoutsen Co-authored-by: Robert Svensson --- .coveragerc | 1 + .../components/philips_js/diagnostics.py | 59 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 homeassistant/components/philips_js/diagnostics.py diff --git a/.coveragerc b/.coveragerc index d3c5094d4b137f..171415f1c793f5 100644 --- a/.coveragerc +++ b/.coveragerc @@ -893,6 +893,7 @@ omit = homeassistant/components/pcal9535a/* homeassistant/components/pencom/switch.py homeassistant/components/philips_js/__init__.py + homeassistant/components/philips_js/diagnostics.py homeassistant/components/philips_js/light.py homeassistant/components/philips_js/media_player.py homeassistant/components/philips_js/remote.py diff --git a/homeassistant/components/philips_js/diagnostics.py b/homeassistant/components/philips_js/diagnostics.py new file mode 100644 index 00000000000000..889b8e47e3f2e5 --- /dev/null +++ b/homeassistant/components/philips_js/diagnostics.py @@ -0,0 +1,59 @@ +"""Diagnostics support for Philips JS.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from . import PhilipsTVDataUpdateCoordinator +from .const import DOMAIN + +TO_REDACT = { + "serialnumber_encrypted", + "serialnumber", + "deviceid_encrypted", + "deviceid", + "username", + "password", + "title", + "unique_id", +} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + coordinator: PhilipsTVDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + api = coordinator.api + + return { + "entry": async_redact_data(entry.as_dict(), TO_REDACT), + "data": { + "system": async_redact_data(api.system, TO_REDACT), + "powerstate": api.powerstate, + "context": api.context, + "application": api.application, + "applications": api.applications, + "source_id": api.source_id, + "sources": api.sources, + "ambilight_styles": api.ambilight_styles, + "ambilight_topology": api.ambilight_topology, + "ambilight_current_configuration": api.ambilight_current_configuration, + "ambilight_mode_raw": api.ambilight_mode_raw, + "ambilight_modes": api.ambilight_modes, + "ambilight_power_raw": api.ambilight_power_raw, + "ambilight_power": api.ambilight_power, + "ambilight_cached": api.ambilight_cached, + "ambilight_measured": api.ambilight_measured, + "ambilight_processed": api.ambilight_processed, + "screenstate": api.screenstate, + "on": api.on, + "channel": api.channel, + "channels": api.channels, + "channel_lists": api.channel_lists, + "favorite_lists": api.favorite_lists, + }, + } From cb2e486f6e4696beec1499a51213bcba89ad33c0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 10 Feb 2022 18:12:36 -0600 Subject: [PATCH 0525/1098] Guard against 0 value for color temp in WiZ when turning off (#66295) --- homeassistant/components/wiz/light.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/wiz/light.py b/homeassistant/components/wiz/light.py index d0473c4f5c3324..ef60deea956b9e 100644 --- a/homeassistant/components/wiz/light.py +++ b/homeassistant/components/wiz/light.py @@ -108,9 +108,8 @@ def _async_update_attrs(self) -> None: assert color_modes is not None if (brightness := state.get_brightness()) is not None: self._attr_brightness = max(0, min(255, brightness)) - if ( - COLOR_MODE_COLOR_TEMP in color_modes - and (color_temp := state.get_colortemp()) is not None + if COLOR_MODE_COLOR_TEMP in color_modes and ( + color_temp := state.get_colortemp() ): self._attr_color_mode = COLOR_MODE_COLOR_TEMP self._attr_color_temp = color_temperature_kelvin_to_mired(color_temp) From bed5002d6144cdd5c79ccfa9ccbbb5b9271ecacc Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 11 Feb 2022 00:14:55 +0000 Subject: [PATCH 0526/1098] [ci skip] Translation update --- .../climacell/translations/sensor.fr.json | 5 +- .../dialogflow/translations/fr.json | 1 + .../components/dnsip/translations/no.json | 4 +- .../components/elkm1/translations/el.json | 14 ++++++ .../components/elkm1/translations/fr.json | 20 ++++++++ .../components/elkm1/translations/no.json | 30 +++++++++++- .../components/elkm1/translations/pt-BR.json | 14 +++--- .../components/fan/translations/fr.json | 1 + .../components/fivem/translations/el.json | 9 ++++ .../components/fivem/translations/fr.json | 21 ++++++++ .../components/fivem/translations/ja.json | 3 +- .../components/fivem/translations/no.json | 22 +++++++++ .../components/fivem/translations/pt-BR.json | 4 +- .../components/fivem/translations/ru.json | 3 ++ .../components/geofency/translations/fr.json | 1 + .../components/gpslogger/translations/fr.json | 1 + .../translations/select.fr.json | 9 ++++ .../homewizard/translations/fr.json | 1 + .../components/ifttt/translations/fr.json | 1 + .../components/knx/translations/fr.json | 6 ++- .../components/locative/translations/fr.json | 1 + .../components/mailgun/translations/fr.json | 1 + .../moehlenhoff_alpha2/translations/ca.json | 19 +++++++ .../moehlenhoff_alpha2/translations/de.json | 19 +++++++ .../moehlenhoff_alpha2/translations/el.json | 3 ++ .../moehlenhoff_alpha2/translations/en.json | 19 +++++++ .../moehlenhoff_alpha2/translations/et.json | 19 +++++++ .../moehlenhoff_alpha2/translations/fr.json | 18 +++++++ .../moehlenhoff_alpha2/translations/it.json | 19 +++++++ .../moehlenhoff_alpha2/translations/ja.json | 19 +++++++ .../moehlenhoff_alpha2/translations/no.json | 19 +++++++ .../translations/pt-BR.json | 19 +++++++ .../moehlenhoff_alpha2/translations/ru.json | 19 +++++++ .../components/mqtt/translations/es.json | 34 ++++++------- .../components/mysensors/translations/es.json | 4 +- .../components/netgear/translations/no.json | 2 +- .../components/overkiz/translations/no.json | 1 + .../components/owntracks/translations/fr.json | 1 + .../components/plaato/translations/fr.json | 1 + .../components/powerwall/translations/no.json | 14 +++++- .../rtsp_to_webrtc/translations/fr.json | 3 +- .../components/smhi/translations/fr.json | 3 ++ .../components/solax/translations/fr.json | 17 +++++++ .../components/traccar/translations/fr.json | 1 + .../tuya/translations/select.fr.json | 49 ++++++++++++++++++- .../tuya/translations/select.no.json | 35 +++++++++++++ .../tuya/translations/sensor.fr.json | 6 +++ .../tuya/translations/sensor.no.json | 6 +++ .../components/twilio/translations/fr.json | 1 + .../uptimerobot/translations/sensor.fr.json | 11 +++++ .../components/webostv/translations/fr.json | 4 +- .../components/wiz/translations/de.json | 7 +-- .../components/wiz/translations/el.json | 1 + .../components/wiz/translations/fr.json | 13 +++++ .../components/wiz/translations/ja.json | 1 + .../components/wiz/translations/no.json | 36 ++++++++++++++ .../components/wiz/translations/ru.json | 7 +-- .../yale_smart_alarm/translations/fr.json | 4 ++ .../components/zwave_me/translations/no.json | 20 ++++++++ 59 files changed, 600 insertions(+), 46 deletions(-) create mode 100644 homeassistant/components/fivem/translations/el.json create mode 100644 homeassistant/components/fivem/translations/fr.json create mode 100644 homeassistant/components/fivem/translations/no.json create mode 100644 homeassistant/components/homekit_controller/translations/select.fr.json create mode 100644 homeassistant/components/moehlenhoff_alpha2/translations/ca.json create mode 100644 homeassistant/components/moehlenhoff_alpha2/translations/de.json create mode 100644 homeassistant/components/moehlenhoff_alpha2/translations/el.json create mode 100644 homeassistant/components/moehlenhoff_alpha2/translations/en.json create mode 100644 homeassistant/components/moehlenhoff_alpha2/translations/et.json create mode 100644 homeassistant/components/moehlenhoff_alpha2/translations/fr.json create mode 100644 homeassistant/components/moehlenhoff_alpha2/translations/it.json create mode 100644 homeassistant/components/moehlenhoff_alpha2/translations/ja.json create mode 100644 homeassistant/components/moehlenhoff_alpha2/translations/no.json create mode 100644 homeassistant/components/moehlenhoff_alpha2/translations/pt-BR.json create mode 100644 homeassistant/components/moehlenhoff_alpha2/translations/ru.json create mode 100644 homeassistant/components/solax/translations/fr.json create mode 100644 homeassistant/components/uptimerobot/translations/sensor.fr.json create mode 100644 homeassistant/components/wiz/translations/fr.json create mode 100644 homeassistant/components/wiz/translations/no.json create mode 100644 homeassistant/components/zwave_me/translations/no.json diff --git a/homeassistant/components/climacell/translations/sensor.fr.json b/homeassistant/components/climacell/translations/sensor.fr.json index 8f5fbb244ff785..e5118e6f9eeecb 100644 --- a/homeassistant/components/climacell/translations/sensor.fr.json +++ b/homeassistant/components/climacell/translations/sensor.fr.json @@ -3,12 +3,15 @@ "climacell__health_concern": { "good": "Bon", "hazardous": "Hasardeux", - "moderate": "Mod\u00e9r\u00e9" + "moderate": "Mod\u00e9r\u00e9", + "unhealthy": "Mauvais pour la sant\u00e9", + "very_unhealthy": "Tr\u00e8s mauvais pour la sant\u00e9" }, "climacell__pollen_index": { "high": "Haut", "low": "Faible", "medium": "Moyen", + "none": "Aucun", "very_high": "Tr\u00e8s \u00e9lev\u00e9", "very_low": "Tr\u00e8s faible" }, diff --git a/homeassistant/components/dialogflow/translations/fr.json b/homeassistant/components/dialogflow/translations/fr.json index 1eb34e210003c5..302c0df7f05671 100644 --- a/homeassistant/components/dialogflow/translations/fr.json +++ b/homeassistant/components/dialogflow/translations/fr.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Pas connect\u00e9 \u00e0 Home Assistant Cloud", "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible.", "webhook_not_internet_accessible": "Votre installation de Home Assistant doit \u00eatre accessible depuis internet pour recevoir des messages webhook." }, diff --git a/homeassistant/components/dnsip/translations/no.json b/homeassistant/components/dnsip/translations/no.json index e99d67902e0d93..ef665c89805c39 100644 --- a/homeassistant/components/dnsip/translations/no.json +++ b/homeassistant/components/dnsip/translations/no.json @@ -6,7 +6,9 @@ "step": { "user": { "data": { - "hostname": "Vertsnavnet som DNS-sp\u00f8rringen skal utf\u00f8res for" + "hostname": "Vertsnavnet som DNS-sp\u00f8rringen skal utf\u00f8res for", + "resolver": "L\u00f8ser for IPV4-oppslag", + "resolver_ipv6": "L\u00f8ser for IPV6-oppslag" } } } diff --git a/homeassistant/components/elkm1/translations/el.json b/homeassistant/components/elkm1/translations/el.json index d86e777877e6b3..8e2990982d62b6 100644 --- a/homeassistant/components/elkm1/translations/el.json +++ b/homeassistant/components/elkm1/translations/el.json @@ -4,10 +4,24 @@ "address_already_configured": "\u0388\u03bd\u03b1 ElkM1 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "already_configured": "\u0388\u03bd\u03b1 ElkM1 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" }, + "flow_title": "{mac_address} ({host})", "step": { + "discovered_connection": { + "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03c3\u03cd\u03c3\u03c4\u03b7\u03bc\u03b1 \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5: {mac_address} ({host})" + }, + "manual_connection": { + "data": { + "address": "\u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03ae \u03bf \u03c4\u03bf\u03bc\u03ad\u03b1\u03c2 \u03ae \u03b7 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1 \u03b5\u03ac\u03bd \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03b3\u03af\u03bd\u03b5\u03c4\u03b1\u03b9 \u03bc\u03ad\u03c3\u03c9 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2.", + "prefix": "\u0388\u03bd\u03b1 \u03bc\u03bf\u03bd\u03b1\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b1\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03bc\u03cc\u03bd\u03bf \u03ad\u03bd\u03b1 ElkM1).", + "protocol": "\u03a0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf", + "temperature_unit": "\u0397 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03c4\u03bf ElkM1." + }, + "description": "\u0397 \u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae \u00abaddress[:port]\u00bb \u03b3\u03b9\u03b1 \u00absecure\u00bb \u03ba\u03b1\u03b9 \u00abnon-secure\u00bb. \u03a0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3\u03bc\u03b1: '192.168.1.1'. \u0397 \u03b8\u03cd\u03c1\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ae \u03ba\u03b1\u03b9 \u03ad\u03c7\u03b5\u03b9 \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03c9\u03c2 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c3\u03b5 2101 \u03b3\u03b9\u03b1 \"non-secure\" \u03ba\u03b1\u03b9 2601 \u03b3\u03b9\u03b1 \"secure\". \u0393\u03b9\u03b1 \u03c4\u03bf \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc \u03c0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf, \u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae 'tty[:baud]'. \u03a0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3\u03bc\u03b1: '/dev/ttyS1'. \u03a4\u03bf baud \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03ba\u03b1\u03b9 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03b5\u03af\u03bd\u03b1\u03b9 115200." + }, "user": { "data": { "address": "\u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03ae \u03bf \u03c4\u03bf\u03bc\u03ad\u03b1\u03c2 \u03ae \u03b7 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1 \u03b5\u03ac\u03bd \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03b3\u03af\u03bd\u03b5\u03c4\u03b1\u03b9 \u03bc\u03ad\u03c3\u03c9 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2.", + "device": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae", "prefix": "\u0388\u03bd\u03b1 \u03bc\u03bf\u03bd\u03b1\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b1\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03bc\u03cc\u03bd\u03bf \u03ad\u03bd\u03b1 ElkM1).", "protocol": "\u03a0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf", "temperature_unit": "\u0397 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03c4\u03bf ElkM1." diff --git a/homeassistant/components/elkm1/translations/fr.json b/homeassistant/components/elkm1/translations/fr.json index 665ac4b4d92d51..05560930fc3325 100644 --- a/homeassistant/components/elkm1/translations/fr.json +++ b/homeassistant/components/elkm1/translations/fr.json @@ -9,10 +9,30 @@ "invalid_auth": "Authentification invalide", "unknown": "Erreur inattendue" }, + "flow_title": "{mac_address} ({host})", "step": { + "discovered_connection": { + "data": { + "password": "Mot de passe", + "protocol": "Protocole", + "username": "Nom d'utilisateur" + }, + "description": "Connectez-vous au syst\u00e8me d\u00e9couvert : {mac_address} ({host})" + }, + "manual_connection": { + "data": { + "address": "L'adresse IP ou le domaine ou le port s\u00e9rie en cas de connexion via le port s\u00e9rie.", + "password": "Mot de passe", + "prefix": "Un pr\u00e9fixe unique (laissez vide si vous n'avez qu'un seul ElkM1).", + "protocol": "Protocole", + "temperature_unit": "L'unit\u00e9 de temp\u00e9rature utilis\u00e9e par ElkM1.", + "username": "Nom d'utilisateur" + } + }, "user": { "data": { "address": "L'adresse IP ou le domaine ou le port s\u00e9rie si vous vous connectez via s\u00e9rie.", + "device": "Appareil", "password": "Mot de passe", "prefix": "Un pr\u00e9fixe unique (laissez vide si vous n'avez qu'un seul ElkM1).", "protocol": "Protocole", diff --git a/homeassistant/components/elkm1/translations/no.json b/homeassistant/components/elkm1/translations/no.json index 39452b58094716..ced77ff0d046e0 100644 --- a/homeassistant/components/elkm1/translations/no.json +++ b/homeassistant/components/elkm1/translations/no.json @@ -2,24 +2,50 @@ "config": { "abort": { "address_already_configured": "En ElkM1 med denne adressen er allerede konfigurert", - "already_configured": "En ElkM1 med dette prefikset er allerede konfigurert" + "already_configured": "En ElkM1 med dette prefikset er allerede konfigurert", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", + "cannot_connect": "Tilkobling mislyktes" }, "error": { "cannot_connect": "Tilkobling mislyktes", "invalid_auth": "Ugyldig godkjenning", "unknown": "Uventet feil" }, + "flow_title": "{mac_address} ( {host} )", "step": { + "discovered_connection": { + "data": { + "password": "Passord", + "protocol": "Protokoll", + "temperature_unit": "Temperaturenheten ElkM1 bruker.", + "username": "Brukernavn" + }, + "description": "Koble til det oppdagede systemet: {mac_address} ( {host} )", + "title": "Koble til Elk-M1-kontroll" + }, + "manual_connection": { + "data": { + "address": "IP-adressen eller domene- eller serieporten hvis du kobler til via seriell.", + "password": "Passord", + "prefix": "Et unikt prefiks (la det st\u00e5 tomt hvis du bare har \u00e9n ElkM1).", + "protocol": "Protokoll", + "temperature_unit": "Temperaturenheten ElkM1 bruker.", + "username": "Brukernavn" + }, + "description": "Adressestrengen m\u00e5 ha formen 'adresse[:port]' for 'sikker' og 'ikke-sikker'. Eksempel: '192.168.1.1'. Porten er valgfri og er standard til 2101 for \"ikke-sikker\" og 2601 for \"sikker\". For serieprotokollen m\u00e5 adressen v\u00e6re i formen 'tty[:baud]'. Eksempel: '/dev/ttyS1'. Bauden er valgfri og er standard til 115200.", + "title": "Koble til Elk-M1-kontroll" + }, "user": { "data": { "address": "IP-adressen eller domenet eller seriell port hvis du kobler til via seriell.", + "device": "Enhet", "password": "Passord", "prefix": "Et unikt prefiks (la v\u00e6re tomt hvis du bare har en ElkM1).", "protocol": "Protokoll", "temperature_unit": "Temperaturenheten ElkM1 bruker.", "username": "Brukernavn" }, - "description": "Adressestrengen m\u00e5 v\u00e6re i formen 'adresse [: port]' for 'sikker' og 'ikke-sikker'. Eksempel: '192.168.1.1'. Porten er valgfri og er standard til 2101 for 'ikke-sikker' og 2601 for 'sikker'. For den serielle protokollen m\u00e5 adressen v\u00e6re i formen 'tty [: baud]'. Eksempel: '/ dev / ttyS1'. Baud er valgfri og er standard til 115200.", + "description": "Velg et oppdaget system eller \"Manuell oppf\u00f8ring\" hvis ingen enheter har blitt oppdaget.", "title": "Koble til Elk-M1-kontroll" } } diff --git a/homeassistant/components/elkm1/translations/pt-BR.json b/homeassistant/components/elkm1/translations/pt-BR.json index bbf0437ba06f2d..7cb9a5f101a741 100644 --- a/homeassistant/components/elkm1/translations/pt-BR.json +++ b/homeassistant/components/elkm1/translations/pt-BR.json @@ -3,34 +3,34 @@ "abort": { "address_already_configured": "Um ElkM1 com este endere\u00e7o j\u00e1 est\u00e1 configurado", "already_configured": "A conta j\u00e1 foi configurada", - "already_in_progress": "A configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", - "cannot_connect": "Falhou ao conectar" + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "cannot_connect": "Falha ao conectar" }, "error": { "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, - "flow_title": "{mac_address} ( {host} )", + "flow_title": "{mac_address} ({host})", "step": { "discovered_connection": { "data": { "password": "Senha", "protocol": "Protocolo", "temperature_unit": "A unidade de temperatura que ElkM1 usa.", - "username": "Nome de usu\u00e1rio" + "username": "Usu\u00e1rio" }, - "description": "Conecte-se ao sistema descoberto: {mac_address} ( {host} )", + "description": "Conecte-se ao sistema descoberto: {mac_address} ({host})", "title": "Conecte ao controle Elk-M1" }, "manual_connection": { "data": { - "address": "O endere\u00e7o IP ou dom\u00ednio ou porta serial se estiver conectando via serial.", + "address": "O endere\u00e7o IP, dom\u00ednio ou porta serial se estiver conectando via serial.", "password": "Senha", "prefix": "Um prefixo exclusivo (deixe em branco se voc\u00ea tiver apenas um ElkM1).", "protocol": "Protocolo", "temperature_unit": "A unidade de temperatura que ElkM1 usa.", - "username": "Nome de usu\u00e1rio" + "username": "Usu\u00e1rio" }, "description": "A string de endere\u00e7o deve estar no formato 'address[:port]' para 'seguro' e 'n\u00e3o seguro'. Exemplo: '192.168.1.1'. A porta \u00e9 opcional e o padr\u00e3o \u00e9 2101 para 'n\u00e3o seguro' e 2601 para 'seguro'. Para o protocolo serial, o endere\u00e7o deve estar no formato 'tty[:baud]'. Exemplo: '/dev/ttyS1'. O baud \u00e9 opcional e o padr\u00e3o \u00e9 115200.", "title": "Conecte ao controle Elk-M1" diff --git a/homeassistant/components/fan/translations/fr.json b/homeassistant/components/fan/translations/fr.json index b41de6b5657f0f..e1c9567dc0f976 100644 --- a/homeassistant/components/fan/translations/fr.json +++ b/homeassistant/components/fan/translations/fr.json @@ -9,6 +9,7 @@ "is_on": "{entity_name} est activ\u00e9" }, "trigger_type": { + "changed_states": "{entity_name} allum\u00e9 ou \u00e9teint", "toggled": "{entity_name} activ\u00e9 ou d\u00e9sactiv\u00e9", "turned_off": "{entity_name} est \u00e9teint", "turned_on": "{entity_name} allum\u00e9" diff --git a/homeassistant/components/fivem/translations/el.json b/homeassistant/components/fivem/translations/el.json new file mode 100644 index 00000000000000..760a3a91365753 --- /dev/null +++ b/homeassistant/components/fivem/translations/el.json @@ -0,0 +1,9 @@ +{ + "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03b8\u03cd\u03c1\u03b1 \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac. \u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03b5\u03c0\u03af\u03c3\u03b7\u03c2 \u03cc\u03c4\u03b9 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c0\u03b9\u03bf \u03c0\u03c1\u03cc\u03c3\u03c6\u03b1\u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae FiveM.", + "invalid_game_name": "\u03a4\u03bf api \u03c4\u03bf\u03c5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd \u03c3\u03c4\u03bf \u03bf\u03c0\u03bf\u03af\u03bf \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9 FiveM.", + "invalid_gamename": "\u03a4\u03bf api \u03c4\u03bf\u03c5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd \u03c3\u03c4\u03bf \u03bf\u03c0\u03bf\u03af\u03bf \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9 FiveM." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/fr.json b/homeassistant/components/fivem/translations/fr.json new file mode 100644 index 00000000000000..dd801a69f33e22 --- /dev/null +++ b/homeassistant/components/fivem/translations/fr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Le service est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion. Veuillez v\u00e9rifier l'h\u00f4te et le port et r\u00e9essayer. Assurez-vous \u00e9galement que vous utilisez le dernier serveur FiveM.", + "invalid_gamename": "L\u2019API du jeu auquel vous essayez de vous connecter n\u2019est pas un jeu FiveM.", + "unknown_error": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "host": "H\u00f4te", + "name": "Nom", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/ja.json b/homeassistant/components/fivem/translations/ja.json index 36bc21ec26d414..7b2e79583085b3 100644 --- a/homeassistant/components/fivem/translations/ja.json +++ b/homeassistant/components/fivem/translations/ja.json @@ -4,7 +4,8 @@ "already_configured": "FiveM\u30b5\u30fc\u30d0\u30fc\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" }, "error": { - "invalid_gamename": "\u63a5\u7d9a\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u308b\u30b2\u30fc\u30e0\u306eAPI\u306f\u3001FiveM\u306e\u30b2\u30fc\u30e0\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002" + "invalid_gamename": "\u63a5\u7d9a\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u308b\u30b2\u30fc\u30e0\u306eAPI\u306f\u3001FiveM\u306e\u30b2\u30fc\u30e0\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002", + "unknown_error": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, "step": { "user": { diff --git a/homeassistant/components/fivem/translations/no.json b/homeassistant/components/fivem/translations/no.json new file mode 100644 index 00000000000000..ac292c10b643b5 --- /dev/null +++ b/homeassistant/components/fivem/translations/no.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Tjenesten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes. Kontroller verten og porten og pr\u00f8v igjen. S\u00f8rg ogs\u00e5 for at du kj\u00f8rer den nyeste FiveM-serveren.", + "invalid_game_name": "API-et til spillet du pr\u00f8ver \u00e5 koble til er ikke et FiveM-spill.", + "invalid_gamename": "API-et til spillet du pr\u00f8ver \u00e5 koble til er ikke et FiveM-spill.", + "unknown_error": "Uventet feil" + }, + "step": { + "user": { + "data": { + "host": "Vert", + "name": "Navn", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/pt-BR.json b/homeassistant/components/fivem/translations/pt-BR.json index 2b179dda182ab3..b576192718fbf3 100644 --- a/homeassistant/components/fivem/translations/pt-BR.json +++ b/homeassistant/components/fivem/translations/pt-BR.json @@ -12,8 +12,8 @@ "step": { "user": { "data": { - "host": "Host", - "name": "Noma", + "host": "Nome do host", + "name": "Nome", "port": "Porta" } } diff --git a/homeassistant/components/fivem/translations/ru.json b/homeassistant/components/fivem/translations/ru.json index b7c45c6bad1629..c6da81663cae2d 100644 --- a/homeassistant/components/fivem/translations/ru.json +++ b/homeassistant/components/fivem/translations/ru.json @@ -4,6 +4,9 @@ "already_configured": "\u042d\u0442\u0430 \u0441\u043b\u0443\u0436\u0431\u0430 \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant." }, "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0430\u0434\u0440\u0435\u0441 \u0445\u043e\u0441\u0442\u0430 \u0438 \u043f\u043e\u0440\u0442 \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443. \u0422\u0430\u043a\u0436\u0435 \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0412\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u044e\u044e \u0432\u0435\u0440\u0441\u0438\u044e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 FiveM.", + "invalid_game_name": "API \u0438\u0433\u0440\u044b, \u043a \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0412\u044b \u043f\u044b\u0442\u0430\u0435\u0442\u0435\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f, \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0438\u0433\u0440\u043e\u0439 FiveM.", + "invalid_gamename": "API \u0438\u0433\u0440\u044b, \u043a \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0412\u044b \u043f\u044b\u0442\u0430\u0435\u0442\u0435\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f, \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0438\u0433\u0440\u043e\u0439 FiveM.", "unknown_error": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "step": { diff --git a/homeassistant/components/geofency/translations/fr.json b/homeassistant/components/geofency/translations/fr.json index 270a627579128a..db163efeac7123 100644 --- a/homeassistant/components/geofency/translations/fr.json +++ b/homeassistant/components/geofency/translations/fr.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Pas connect\u00e9 \u00e0 Home Assistant Cloud", "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible.", "webhook_not_internet_accessible": "Votre installation de Home Assistant doit \u00eatre accessible depuis internet pour recevoir des messages webhook." }, diff --git a/homeassistant/components/gpslogger/translations/fr.json b/homeassistant/components/gpslogger/translations/fr.json index 05f5985a8dfba7..a69191d05c6486 100644 --- a/homeassistant/components/gpslogger/translations/fr.json +++ b/homeassistant/components/gpslogger/translations/fr.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Pas connect\u00e9 \u00e0 Home Assistant Cloud", "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible.", "webhook_not_internet_accessible": "Votre installation de Home Assistant doit \u00eatre accessible depuis internet pour recevoir des messages webhook." }, diff --git a/homeassistant/components/homekit_controller/translations/select.fr.json b/homeassistant/components/homekit_controller/translations/select.fr.json new file mode 100644 index 00000000000000..9da1f007992573 --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/select.fr.json @@ -0,0 +1,9 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "away": "Loin", + "home": "Domicile", + "sleep": "Sommeil" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homewizard/translations/fr.json b/homeassistant/components/homewizard/translations/fr.json index 66d3edbc978147..6ddc51565fb84f 100644 --- a/homeassistant/components/homewizard/translations/fr.json +++ b/homeassistant/components/homewizard/translations/fr.json @@ -4,6 +4,7 @@ "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", "api_not_enabled": "L'API n'est pas activ\u00e9e. Activer l'API dans l'application HomeWizard Energy dans les param\u00e8tres", "device_not_supported": "Cet appareil n'est pas compatible", + "invalid_discovery_parameters": "Version d'API non prise en charge d\u00e9tect\u00e9e", "unknown_error": "Erreur inattendue" }, "step": { diff --git a/homeassistant/components/ifttt/translations/fr.json b/homeassistant/components/ifttt/translations/fr.json index fe72a0df1725e8..4628b7bea8bc10 100644 --- a/homeassistant/components/ifttt/translations/fr.json +++ b/homeassistant/components/ifttt/translations/fr.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Pas connect\u00e9 \u00e0 Home Assistant Cloud", "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible.", "webhook_not_internet_accessible": "Votre installation de Home Assistant doit \u00eatre accessible depuis internet pour recevoir des messages webhook." }, diff --git a/homeassistant/components/knx/translations/fr.json b/homeassistant/components/knx/translations/fr.json index 763d028803a229..08e53ad8d49528 100644 --- a/homeassistant/components/knx/translations/fr.json +++ b/homeassistant/components/knx/translations/fr.json @@ -14,7 +14,8 @@ "individual_address": "Adresse individuelle pour la connexion", "local_ip": "IP locale (laisser vide en cas de doute)", "port": "Port", - "route_back": "Retour/Mode NAT" + "route_back": "Retour/Mode NAT", + "tunneling_type": "Type de tunnel KNX" }, "description": "Veuillez saisir les informations de connexion de votre p\u00e9riph\u00e9rique de tunneling." }, @@ -59,7 +60,8 @@ "host": "H\u00f4te", "local_ip": "IP locale (laisser vide en cas de doute)", "port": "Port", - "route_back": "Retour/Mode NAT" + "route_back": "Retour/Mode NAT", + "tunneling_type": "Type de tunnel KNX" } } } diff --git a/homeassistant/components/locative/translations/fr.json b/homeassistant/components/locative/translations/fr.json index 9c9414caf9ba52..45a6f6fe5943a0 100644 --- a/homeassistant/components/locative/translations/fr.json +++ b/homeassistant/components/locative/translations/fr.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Pas connect\u00e9 \u00e0 Home Assistant Cloud", "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible.", "webhook_not_internet_accessible": "Votre installation de Home Assistant doit \u00eatre accessible depuis internet pour recevoir des messages webhook." }, diff --git a/homeassistant/components/mailgun/translations/fr.json b/homeassistant/components/mailgun/translations/fr.json index b822522af10ea1..a8d4b08ed83d9c 100644 --- a/homeassistant/components/mailgun/translations/fr.json +++ b/homeassistant/components/mailgun/translations/fr.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Pas connect\u00e9 \u00e0 Home Assistant Cloud", "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible.", "webhook_not_internet_accessible": "Votre installation de Home Assistant doit \u00eatre accessible depuis internet pour recevoir des messages webhook." }, diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/ca.json b/homeassistant/components/moehlenhoff_alpha2/translations/ca.json new file mode 100644 index 00000000000000..120b7ef6d4aa82 --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/translations/ca.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "host": "Amfitri\u00f3" + } + } + } + }, + "title": "M\u00f6hlenhoff Alpha2" +} \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/de.json b/homeassistant/components/moehlenhoff_alpha2/translations/de.json new file mode 100644 index 00000000000000..b35bb0c25cc166 --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/translations/de.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "host": "Host" + } + } + } + }, + "title": "M\u00f6hlenhoff Alpha2" +} \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/el.json b/homeassistant/components/moehlenhoff_alpha2/translations/el.json new file mode 100644 index 00000000000000..d15111e97b0d19 --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/translations/el.json @@ -0,0 +1,3 @@ +{ + "title": "M\u00f6hlenhoff Alpha2" +} \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/en.json b/homeassistant/components/moehlenhoff_alpha2/translations/en.json new file mode 100644 index 00000000000000..d2bae9be52cffd --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/translations/en.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "host": "Host" + } + } + } + }, + "title": "M\u00f6hlenhoff Alpha2" +} \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/et.json b/homeassistant/components/moehlenhoff_alpha2/translations/et.json new file mode 100644 index 00000000000000..8f3df1c4b71dc5 --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/translations/et.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "host": "Host" + } + } + } + }, + "title": "M\u00f6hlenhoff Alpha2" +} \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/fr.json b/homeassistant/components/moehlenhoff_alpha2/translations/fr.json new file mode 100644 index 00000000000000..7c97f50a85f62a --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/translations/fr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "Impossible de se connecter", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "host": "H\u00f4te" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/it.json b/homeassistant/components/moehlenhoff_alpha2/translations/it.json new file mode 100644 index 00000000000000..72abc5f1e5f955 --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/translations/it.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "host": "Host" + } + } + } + }, + "title": "M\u00f6hlenhoff Alpha2" +} \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/ja.json b/homeassistant/components/moehlenhoff_alpha2/translations/ja.json new file mode 100644 index 00000000000000..7de2c8da4d885f --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/translations/ja.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" + }, + "error": { + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" + }, + "step": { + "user": { + "data": { + "host": "\u30db\u30b9\u30c8" + } + } + } + }, + "title": "M\u00f6hlenhoff Alpha2" +} \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/no.json b/homeassistant/components/moehlenhoff_alpha2/translations/no.json new file mode 100644 index 00000000000000..32fae944a717da --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/translations/no.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "host": "Vert" + } + } + } + }, + "title": "M\u00f6hlenhoff Alpha2" +} \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/pt-BR.json b/homeassistant/components/moehlenhoff_alpha2/translations/pt-BR.json new file mode 100644 index 00000000000000..b5f02d5b3d5a8b --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falhou ao conectar", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Host" + } + } + } + }, + "title": "M\u00f6hlenhoff Alpha2" +} \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/ru.json b/homeassistant/components/moehlenhoff_alpha2/translations/ru.json new file mode 100644 index 00000000000000..e843c048d89170 --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/translations/ru.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + } + } + } + }, + "title": "M\u00f6hlenhoff Alpha2" +} \ No newline at end of file diff --git a/homeassistant/components/mqtt/translations/es.json b/homeassistant/components/mqtt/translations/es.json index 50cac3172ab5b6..89a5ce04d978f6 100644 --- a/homeassistant/components/mqtt/translations/es.json +++ b/homeassistant/components/mqtt/translations/es.json @@ -2,28 +2,28 @@ "config": { "abort": { "already_configured": "El servicio ya est\u00e1 configurado", - "single_instance_allowed": "S\u00f3lo se permite una \u00fanica configuraci\u00f3n de MQTT." + "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo se permite una \u00fanica configuraci\u00f3n." }, "error": { - "cannot_connect": "No se puede conectar con el agente" + "cannot_connect": "No se puede conectar" }, "step": { "broker": { "data": { - "broker": "Agente", + "broker": "Br\u00f3ker", "discovery": "Habilitar descubrimiento", "password": "Contrase\u00f1a", "port": "Puerto", "username": "Usuario" }, - "description": "Por favor, introduce la informaci\u00f3n de tu agente MQTT" + "description": "Por favor, introduzca la informaci\u00f3n de conexi\u00f3n de su br\u00f3ker MQTT." }, "hassio_confirm": { "data": { "discovery": "Habilitar descubrimiento" }, - "description": "\u00bfQuieres configurar Home Assistant para conectar con el broker de MQTT proporcionado por el complemento Supervisor {addon}?", - "title": "MQTT Broker a trav\u00e9s del complemento Supervisor" + "description": "\u00bfDesea configurar Home Assistant para conectar con el br\u00f3ker de MQTT proporcionado por el complemento {addon}?", + "title": "Br\u00f3ker MQTT a trav\u00e9s de complemento de Home Assistant" } } }, @@ -52,19 +52,19 @@ "options": { "error": { "bad_birth": "Tema de nacimiento inv\u00e1lido.", - "bad_will": "Tema deseado inv\u00e1lido.", + "bad_will": "Tema de voluntad inv\u00e1lido.", "cannot_connect": "No se pudo conectar" }, "step": { "broker": { "data": { - "broker": "Agente", + "broker": "Br\u00f3ker", "password": "Contrase\u00f1a", "port": "Puerto", "username": "Usuario" }, - "description": "Por favor, introduce la informaci\u00f3n de tu agente MQTT.", - "title": "Opciones para el Broker" + "description": "Por favor, introduzca la informaci\u00f3n de conexi\u00f3n de su br\u00f3ker MQTT.", + "title": "Opciones del br\u00f3ker" }, "options": { "data": { @@ -74,14 +74,14 @@ "birth_retain": "Retenci\u00f3n del mensaje de nacimiento", "birth_topic": "Tema del mensaje de nacimiento", "discovery": "Habilitar descubrimiento", - "will_enable": "Habilitar mensaje de nacimiento", - "will_payload": "Enviar\u00e1 la carga", - "will_qos": "El mensaje usar\u00e1 el QoS", - "will_retain": "Retendr\u00e1 el mensaje", - "will_topic": "Enviar\u00e1 un mensaje al tema" + "will_enable": "Habilitar mensaje de voluntad", + "will_payload": "Carga del mensaje de voluntad", + "will_qos": "QoS del mensaje de voluntad", + "will_retain": "Retenci\u00f3n del mensaje de voluntad", + "will_topic": "Tema del mensaje de voluntad" }, - "description": "Por favor, selecciona las opciones para MQTT.", - "title": "Opciones para MQTT" + "description": "Descubrimiento - Si el descubrimiento est\u00e1 habilitado (recomendado), Home Assistant descubrir\u00e1 autom\u00e1ticamente los dispositivos y entidades que publiquen su configuraci\u00f3n en el br\u00f3ker MQTT. Si el descubrimiento est\u00e1 deshabilitado, toda la configuraci\u00f3n debe hacerse manualmente.\nMensaje de nacimiento - El mensaje de nacimiento se enviar\u00e1 cada vez que Home Assistant se (re)conecte al br\u00f3ker MQTT.\nMensaje de voluntad - El mensaje de voluntad se enviar\u00e1 cada vez que Home Assistant pierda su conexi\u00f3n con el br\u00f3ker, tanto en el caso de una desconexi\u00f3n limpia (por ejemplo, el cierre de Home Assistant) como en el caso de una desconexi\u00f3n no limpia (por ejemplo, el cierre de Home Assistant o la p\u00e9rdida de su conexi\u00f3n de red).", + "title": "Opciones de MQTT" } } } diff --git a/homeassistant/components/mysensors/translations/es.json b/homeassistant/components/mysensors/translations/es.json index 4bb5f5cfd15288..411501db49d0e9 100644 --- a/homeassistant/components/mysensors/translations/es.json +++ b/homeassistant/components/mysensors/translations/es.json @@ -43,12 +43,12 @@ "gw_mqtt": { "data": { "persistence_file": "archivo de persistencia (d\u00e9jelo vac\u00edo para que se genere autom\u00e1ticamente)", - "retain": "retener mqtt", + "retain": "retenci\u00f3n mqtt", "topic_in_prefix": "prefijo para los temas de entrada (topic_in_prefix)", "topic_out_prefix": "prefijo para los temas de salida (topic_out_prefix)", "version": "Versi\u00f3n de MySensors" }, - "description": "Configuraci\u00f3n del gateway MQTT" + "description": "Configuraci\u00f3n de la puerta de enlace MQTT" }, "gw_serial": { "data": { diff --git a/homeassistant/components/netgear/translations/no.json b/homeassistant/components/netgear/translations/no.json index 52020ae382496d..73735f3e91a90a 100644 --- a/homeassistant/components/netgear/translations/no.json +++ b/homeassistant/components/netgear/translations/no.json @@ -15,7 +15,7 @@ "ssl": "Bruker et SSL-sertifikat", "username": "Brukernavn (Valgfritt)" }, - "description": "Standard vert: {host}\nStandardport: {port}\nStandard brukernavn: {username}", + "description": "Standard vert: {host}\n Standard brukernavn: {username}", "title": "Netgear" } } diff --git a/homeassistant/components/overkiz/translations/no.json b/homeassistant/components/overkiz/translations/no.json index 201ecbf3779407..ed691aa388f37e 100644 --- a/homeassistant/components/overkiz/translations/no.json +++ b/homeassistant/components/overkiz/translations/no.json @@ -12,6 +12,7 @@ "too_many_requests": "For mange foresp\u00f8rsler. Pr\u00f8v igjen senere", "unknown": "Uventet feil" }, + "flow_title": "Gateway: {gateway_id}", "step": { "user": { "data": { diff --git a/homeassistant/components/owntracks/translations/fr.json b/homeassistant/components/owntracks/translations/fr.json index 35530bd2c86b39..cecdab86436144 100644 --- a/homeassistant/components/owntracks/translations/fr.json +++ b/homeassistant/components/owntracks/translations/fr.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Pas connect\u00e9 \u00e0 Home Assistant Cloud", "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, "create_entry": { diff --git a/homeassistant/components/plaato/translations/fr.json b/homeassistant/components/plaato/translations/fr.json index 3bac269eaf92d9..370796c18f3974 100644 --- a/homeassistant/components/plaato/translations/fr.json +++ b/homeassistant/components/plaato/translations/fr.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", + "cloud_not_connected": "Pas connect\u00e9 \u00e0 Home Assistant Cloud", "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible.", "webhook_not_internet_accessible": "Votre installation de Home Assistant doit \u00eatre accessible depuis internet pour recevoir des messages webhook." }, diff --git a/homeassistant/components/powerwall/translations/no.json b/homeassistant/components/powerwall/translations/no.json index 6f45fb144f53b1..01cf58c6ed4ca9 100644 --- a/homeassistant/components/powerwall/translations/no.json +++ b/homeassistant/components/powerwall/translations/no.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Enheten er allerede konfigurert", + "cannot_connect": "Tilkobling mislyktes", "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" }, "error": { @@ -10,8 +11,19 @@ "unknown": "Uventet feil", "wrong_version": "Powerwall bruker en programvareversjon som ikke st\u00f8ttes. Vennligst vurder \u00e5 oppgradere eller rapportere dette problemet, s\u00e5 det kan l\u00f8ses." }, - "flow_title": "{ip_address}", + "flow_title": "{name} ( {ip_address} )", "step": { + "confirm_discovery": { + "description": "Vil du konfigurere {name} ( {ip_address} )?", + "title": "Koble til powerwall" + }, + "reauth_confim": { + "data": { + "password": "Passord" + }, + "description": "Passordet er vanligvis de siste 5 tegnene i serienummeret for Backup Gateway, og kan bli funnet i Tesla-appen eller de siste 5 tegnene i passordet som er funnet inne i d\u00f8ren til Backup Gateway 2.", + "title": "Autentiser powerwallen p\u00e5 nytt" + }, "user": { "data": { "ip_address": "IP adresse", diff --git a/homeassistant/components/rtsp_to_webrtc/translations/fr.json b/homeassistant/components/rtsp_to_webrtc/translations/fr.json index be139286113642..1235f36d26aa26 100644 --- a/homeassistant/components/rtsp_to_webrtc/translations/fr.json +++ b/homeassistant/components/rtsp_to_webrtc/translations/fr.json @@ -12,7 +12,8 @@ }, "step": { "hassio_confirm": { - "description": "Voulez-vous configurer Home Assistant pour qu'il se connecte au serveur RTSPtoWebRTC fourni par l'add-on\u00a0: {addon}\u00a0?" + "description": "Voulez-vous configurer Home Assistant pour qu'il se connecte au serveur RTSPtoWebRTC fourni par l'add-on\u00a0: {addon}\u00a0?", + "title": "RTSPtoWebRTC via le module compl\u00e9mentaire Home Assistant" }, "user": { "data": { diff --git a/homeassistant/components/smhi/translations/fr.json b/homeassistant/components/smhi/translations/fr.json index ec5350903487ec..6bf27915f97c8b 100644 --- a/homeassistant/components/smhi/translations/fr.json +++ b/homeassistant/components/smhi/translations/fr.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9" + }, "error": { "name_exists": "Ce nom est d\u00e9j\u00e0 utilis\u00e9", "wrong_location": "En Su\u00e8de uniquement" diff --git a/homeassistant/components/solax/translations/fr.json b/homeassistant/components/solax/translations/fr.json new file mode 100644 index 00000000000000..a0d7a14dcdb50b --- /dev/null +++ b/homeassistant/components/solax/translations/fr.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "Impossible de se connecter", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "ip_address": "Adresse IP", + "password": "Mot de passe", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/traccar/translations/fr.json b/homeassistant/components/traccar/translations/fr.json index 3c32100078dc40..b3e9684424bb8e 100644 --- a/homeassistant/components/traccar/translations/fr.json +++ b/homeassistant/components/traccar/translations/fr.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Pas connect\u00e9 \u00e0 Home Assistant Cloud", "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible.", "webhook_not_internet_accessible": "Votre installation de Home Assistant doit \u00eatre accessible depuis internet pour recevoir des messages webhook." }, diff --git a/homeassistant/components/tuya/translations/select.fr.json b/homeassistant/components/tuya/translations/select.fr.json index a01a11bf5df83a..caebd296512a25 100644 --- a/homeassistant/components/tuya/translations/select.fr.json +++ b/homeassistant/components/tuya/translations/select.fr.json @@ -10,14 +10,54 @@ "1": "Inactif", "2": "Actif" }, + "tuya__countdown": { + "1h": "1 heure", + "2h": "2 heures", + "3h": "3 heures", + "4h": "4 heures", + "5h": "5 heures", + "6h": "6 heures", + "cancel": "Annuler" + }, + "tuya__curtain_mode": { + "morning": "Matin" + }, "tuya__decibel_sensitivity": { "0": "Faible sensibilit\u00e9", "1": "Haute sensibilit\u00e9" }, + "tuya__fan_angle": { + "30": "30\u00b0", + "60": "60\u00b0", + "90": "90\u00b0" + }, "tuya__fingerbot_mode": { "click": "Appuyer", "switch": "Interrupteur" }, + "tuya__humidifier_level": { + "level_1": "Niveau 1", + "level_10": "Niveau 10", + "level_2": "Niveau 2", + "level_3": "Niveau 3", + "level_4": "Niveau 4", + "level_5": "Niveau 5", + "level_6": "Niveau 6", + "level_7": "Niveau 7", + "level_8": "Niveau 8", + "level_9": "Niveau 9" + }, + "tuya__humidifier_moodlighting": { + "1": "Humeur 1", + "2": "Humeur 2", + "3": "Humeur 3", + "4": "Humeur 4", + "5": "Humeur 5" + }, + "tuya__humidifier_spray_mode": { + "auto": "Auto", + "health": "Sant\u00e9" + }, "tuya__ipc_work_mode": { "0": "Mode faible consommation", "1": "Mode de travail continu" @@ -51,7 +91,9 @@ }, "tuya__vacuum_cistern": { "closed": "Ferm\u00e9", - "high": "Haut" + "high": "Haut", + "low": "Faible", + "middle": "Milieu" }, "tuya__vacuum_collection": { "large": "Grand", @@ -59,11 +101,16 @@ "small": "Petit" }, "tuya__vacuum_mode": { + "bow": "Arc", "chargego": "Retour \u00e0 la base", + "left_bow": "Arc gauche", "left_spiral": "Spirale gauche", "mop": "Serpilli\u00e8re", + "part": "Partie", "partial_bow": "Arc partiel", "pick_zone": "S\u00e9lectionner une zone", + "point": "Point", + "pose": "Pose", "random": "Al\u00e9atoire", "right_bow": "Arc \u00e0 droite", "right_spiral": "Spirale droite", diff --git a/homeassistant/components/tuya/translations/select.no.json b/homeassistant/components/tuya/translations/select.no.json index 71b49b9ad2f5d1..57b5dc30d48fe8 100644 --- a/homeassistant/components/tuya/translations/select.no.json +++ b/homeassistant/components/tuya/translations/select.no.json @@ -10,6 +10,15 @@ "1": "Av", "2": "P\u00e5" }, + "tuya__countdown": { + "1h": "1 time", + "2h": "2 timer", + "3h": "3 timer", + "4h": "4 timer", + "5h": "5 timer", + "6h": "6 timer", + "cancel": "Avbryt" + }, "tuya__curtain_mode": { "morning": "Morgen", "night": "Natt" @@ -31,6 +40,32 @@ "click": "Trykk", "switch": "Bryter" }, + "tuya__humidifier_level": { + "level_1": "Niv\u00e5 1", + "level_10": "Niv\u00e5 10", + "level_2": "Niv\u00e5 2", + "level_3": "Niv\u00e5 3", + "level_4": "Niv\u00e5 4", + "level_5": "Niv\u00e5 5", + "level_6": "Niv\u00e5 6", + "level_7": "Niv\u00e5 7", + "level_8": "Niv\u00e5 8", + "level_9": "Niv\u00e5 9" + }, + "tuya__humidifier_moodlighting": { + "1": "Stemning 1", + "2": "Stemning 2", + "3": "Stemning 3", + "4": "Stemning 4", + "5": "Stemning 5" + }, + "tuya__humidifier_spray_mode": { + "auto": "Auto", + "health": "Helse", + "humidity": "Fuktighet", + "sleep": "Sove", + "work": "Arbeid" + }, "tuya__ipc_work_mode": { "0": "Lav effekt modus", "1": "Kontinuerlig arbeidsmodus" diff --git a/homeassistant/components/tuya/translations/sensor.fr.json b/homeassistant/components/tuya/translations/sensor.fr.json index 795e4c5d43ec94..7041d3f27c6658 100644 --- a/homeassistant/components/tuya/translations/sensor.fr.json +++ b/homeassistant/components/tuya/translations/sensor.fr.json @@ -1,5 +1,11 @@ { "state": { + "tuya__air_quality": { + "good": "Bon", + "great": "Super", + "mild": "B\u00e9nin", + "severe": "S\u00e9v\u00e8re" + }, "tuya__status": { "boiling_temp": "Temp\u00e9rature de chauffage", "cooling": "Refroidissement", diff --git a/homeassistant/components/tuya/translations/sensor.no.json b/homeassistant/components/tuya/translations/sensor.no.json index 2992fcb2f4be7b..daf17a266f22c2 100644 --- a/homeassistant/components/tuya/translations/sensor.no.json +++ b/homeassistant/components/tuya/translations/sensor.no.json @@ -1,5 +1,11 @@ { "state": { + "tuya__air_quality": { + "good": "Bra", + "great": "Utmerket", + "mild": "Mild", + "severe": "Alvorlig" + }, "tuya__status": { "boiling_temp": "Kokende temperatur", "cooling": "Kj\u00f8ling", diff --git a/homeassistant/components/twilio/translations/fr.json b/homeassistant/components/twilio/translations/fr.json index cfd4d9813c8488..f602994d8b26a5 100644 --- a/homeassistant/components/twilio/translations/fr.json +++ b/homeassistant/components/twilio/translations/fr.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Pas connect\u00e9 \u00e0 Home Assistant Cloud", "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible.", "webhook_not_internet_accessible": "Votre installation de Home Assistant doit \u00eatre accessible depuis internet pour recevoir des messages webhook." }, diff --git a/homeassistant/components/uptimerobot/translations/sensor.fr.json b/homeassistant/components/uptimerobot/translations/sensor.fr.json new file mode 100644 index 00000000000000..d5d7602e084b11 --- /dev/null +++ b/homeassistant/components/uptimerobot/translations/sensor.fr.json @@ -0,0 +1,11 @@ +{ + "state": { + "uptimerobot__monitor_status": { + "down": "En panne", + "not_checked_yet": "Pas encore v\u00e9rifi\u00e9", + "pause": "Pause", + "seems_down": "Semble en panne", + "up": "Allum\u00e9" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/webostv/translations/fr.json b/homeassistant/components/webostv/translations/fr.json index 82b4196eb751cf..d6628f9747cbf9 100644 --- a/homeassistant/components/webostv/translations/fr.json +++ b/homeassistant/components/webostv/translations/fr.json @@ -36,7 +36,9 @@ "init": { "data": { "sources": "Liste des sources" - } + }, + "description": "S\u00e9lectionnez les sources activ\u00e9es", + "title": "Options pour webOS Smart TV" } } } diff --git a/homeassistant/components/wiz/translations/de.json b/homeassistant/components/wiz/translations/de.json index d1abe4aa1be9c6..ad8951c92c5156 100644 --- a/homeassistant/components/wiz/translations/de.json +++ b/homeassistant/components/wiz/translations/de.json @@ -5,8 +5,9 @@ "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden" }, "error": { - "bulb_time_out": "Es kann keine Verbindung zur Gl\u00fchbirne hergestellt werden. Vielleicht ist die Gl\u00fchbirne offline oder es wurde eine falsche IP/Host eingegeben. Bitte schalte das Licht ein und versuche es erneut!", + "bulb_time_out": "Es kann keine Verbindung zur Gl\u00fchbirne hergestellt werden. Vielleicht ist die Gl\u00fchbirne offline oder es wurde eine falsche IP eingegeben. Bitte schalte das Licht ein und versuche es erneut!", "cannot_connect": "Verbindung fehlgeschlagen", + "no_ip": "Keine g\u00fcltige IP-Adresse.", "no_wiz_light": "Die Gl\u00fchbirne kann nicht \u00fcber die Integration der WiZ-Plattform verbunden werden.", "unknown": "Unerwarteter Fehler" }, @@ -25,10 +26,10 @@ }, "user": { "data": { - "host": "Host", + "host": "IP-Adresse", "name": "Name" }, - "description": "Wenn du den Host leer l\u00e4sst, wird die Erkennung verwendet, um Ger\u00e4te zu finden." + "description": "Wenn du die IP-Adresse leer l\u00e4sst, wird die Erkennung verwendet, um Ger\u00e4te zu finden." } } } diff --git a/homeassistant/components/wiz/translations/el.json b/homeassistant/components/wiz/translations/el.json index f481be62e73db0..278dda5120894f 100644 --- a/homeassistant/components/wiz/translations/el.json +++ b/homeassistant/components/wiz/translations/el.json @@ -2,6 +2,7 @@ "config": { "error": { "bulb_time_out": "\u0394\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf\u03bd \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b1. \u038a\u03c3\u03c9\u03c2 \u03bf \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03ba\u03c4\u03cc\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03ae \u03ad\u03c7\u03b5\u03b9 \u03b5\u03b9\u03c3\u03b1\u03c7\u03b8\u03b5\u03af \u03bb\u03ac\u03b8\u03bf\u03c2 IP/host. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03bd\u03ac\u03c8\u03c4\u03b5 \u03c4\u03bf \u03c6\u03c9\u03c2 \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac!", + "no_ip": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP.", "no_wiz_light": "\u039f \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03bc\u03ad\u03c3\u03c9 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c0\u03bb\u03b1\u03c4\u03c6\u03cc\u03c1\u03bc\u03b1\u03c2 WiZ." }, "flow_title": "{name} ({host})", diff --git a/homeassistant/components/wiz/translations/fr.json b/homeassistant/components/wiz/translations/fr.json new file mode 100644 index 00000000000000..5d7bc4006009c1 --- /dev/null +++ b/homeassistant/components/wiz/translations/fr.json @@ -0,0 +1,13 @@ +{ + "config": { + "error": { + "no_ip": "Adresse IP non valide" + }, + "flow_title": "{name} ({host})", + "step": { + "discovery_confirm": { + "description": "Voulez-vous configurer {name} ({host}) ?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/ja.json b/homeassistant/components/wiz/translations/ja.json index 2614230087b2e9..21a6adca8545c9 100644 --- a/homeassistant/components/wiz/translations/ja.json +++ b/homeassistant/components/wiz/translations/ja.json @@ -7,6 +7,7 @@ "error": { "bulb_time_out": "\u96fb\u7403\u306b\u63a5\u7d9a\u3067\u304d\u307e\u305b\u3093\u3002\u96fb\u7403\u304c\u30aa\u30d5\u30e9\u30a4\u30f3\u306b\u306a\u3063\u3066\u3044\u308b\u304b\u3001\u9593\u9055\u3063\u305fIP/\u30db\u30b9\u30c8\u304c\u5165\u529b\u3055\u308c\u3066\u3044\u308b\u53ef\u80fd\u6027\u304c\u3042\u308a\u307e\u3059\u3002\u96fb\u7403\u306e\u96fb\u6e90\u3092\u5165\u308c\u3066\u518d\u5ea6\u304a\u8a66\u3057\u304f\u3060\u3055\u3044\u3002", "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "no_ip": "\u6709\u52b9\u306aIP\u30a2\u30c9\u30ec\u30b9\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002", "no_wiz_light": "\u3053\u306e\u96fb\u7403\u306f\u3001WiZ Platform\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u4ecb\u3057\u3066\u63a5\u7d9a\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002", "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, diff --git a/homeassistant/components/wiz/translations/no.json b/homeassistant/components/wiz/translations/no.json new file mode 100644 index 00000000000000..bf6563a3f48366 --- /dev/null +++ b/homeassistant/components/wiz/translations/no.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert", + "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket" + }, + "error": { + "bulb_time_out": "Kan ikke koble til p\u00e6ren. Kanskje p\u00e6ren er frakoblet eller feil IP ble lagt inn. Sl\u00e5 p\u00e5 lyset, og pr\u00f8v p\u00e5 nytt!", + "cannot_connect": "Tilkobling mislyktes", + "no_ip": "Ikke en gyldig IP-adresse.", + "no_wiz_light": "P\u00e6ren kan ikke kobles til via WiZ Platform-integrasjon.", + "unknown": "Uventet feil" + }, + "flow_title": "{name} ({host})", + "step": { + "confirm": { + "description": "Vil du starte oppsettet?" + }, + "discovery_confirm": { + "description": "Vil du konfigurere {name} ({host})?" + }, + "pick_device": { + "data": { + "device": "Enhet" + } + }, + "user": { + "data": { + "host": "IP adresse", + "name": "Navn" + }, + "description": "Hvis du lar IP-adressen st\u00e5 tom, vil oppdagelse bli brukt til \u00e5 finne enheter." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/ru.json b/homeassistant/components/wiz/translations/ru.json index 16f679e9c7d4e9..bef8eae0d3274a 100644 --- a/homeassistant/components/wiz/translations/ru.json +++ b/homeassistant/components/wiz/translations/ru.json @@ -5,8 +5,9 @@ "no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0441\u0435\u0442\u0438." }, "error": { - "bulb_time_out": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u043b\u0430\u043c\u043f\u043e\u0447\u043a\u0435. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435, \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0430 \u043b\u0438 \u043b\u0430\u043c\u043f\u043e\u0447\u043a\u0430 \u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u043b\u0438 \u0443\u043a\u0430\u0437\u0430\u043d IP-\u0430\u0434\u0440\u0435\u0441 \u0438\u043b\u0438 \u0445\u043e\u0441\u0442.", + "bulb_time_out": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u043b\u0430\u043c\u043f\u043e\u0447\u043a\u0435. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435, \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0430 \u043b\u0438 \u043b\u0430\u043c\u043f\u043e\u0447\u043a\u0430 \u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u043b\u0438 \u0443\u043a\u0430\u0437\u0430\u043d IP-\u0430\u0434\u0440\u0435\u0441.", "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "no_ip": "\u041d\u0435\u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0439 IP-\u0430\u0434\u0440\u0435\u0441.", "no_wiz_light": "\u042d\u0442\u0443 \u043b\u0430\u043c\u043f\u043e\u0447\u043a\u0443 \u043d\u0435\u043b\u044c\u0437\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0447\u0435\u0440\u0435\u0437 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e WiZ.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, @@ -25,10 +26,10 @@ }, "user": { "data": { - "host": "\u0425\u043e\u0441\u0442", + "host": "IP-\u0430\u0434\u0440\u0435\u0441", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" }, - "description": "\u0415\u0441\u043b\u0438 \u043d\u0435 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0430\u0434\u0440\u0435\u0441 \u0445\u043e\u0441\u0442\u0430, \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0431\u0443\u0434\u0443\u0442 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438." + "description": "\u0415\u0441\u043b\u0438 \u043d\u0435 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c IP-\u0430\u0434\u0440\u0435\u0441, \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0431\u0443\u0434\u0443\u0442 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438." } } } diff --git a/homeassistant/components/yale_smart_alarm/translations/fr.json b/homeassistant/components/yale_smart_alarm/translations/fr.json index 50ad7f4b9ca4ac..b78e4a327a122e 100644 --- a/homeassistant/components/yale_smart_alarm/translations/fr.json +++ b/homeassistant/components/yale_smart_alarm/translations/fr.json @@ -28,9 +28,13 @@ } }, "options": { + "error": { + "code_format_mismatch": "Le code ne correspond pas au nombre de chiffres requis" + }, "step": { "init": { "data": { + "code": "Code par d\u00e9faut pour les serrures, utilis\u00e9 si aucun n'est donn\u00e9", "lock_code_digits": "Nombre de chiffres dans le code PIN pour les serrures" } } diff --git a/homeassistant/components/zwave_me/translations/no.json b/homeassistant/components/zwave_me/translations/no.json new file mode 100644 index 00000000000000..8a9a6928e9345e --- /dev/null +++ b/homeassistant/components/zwave_me/translations/no.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert", + "no_valid_uuid_set": "Ingen gyldig UUID satt" + }, + "error": { + "no_valid_uuid_set": "Ingen gyldig UUID satt" + }, + "step": { + "user": { + "data": { + "token": "Token", + "url": "URL" + }, + "description": "Skriv inn IP-adressen til Z-Way-serveren og Z-Way-tilgangstoken. IP-adressen kan settes foran med wss:// hvis HTTPS skal brukes i stedet for HTTP. For \u00e5 f\u00e5 tokenet, g\u00e5 til Z-Way-brukergrensesnittet > Meny > Innstillinger > Bruker > API-token. Det foresl\u00e5s \u00e5 opprette en ny bruker for Home Assistant og gi tilgang til enheter du m\u00e5 kontrollere fra Home Assistant. Det er ogs\u00e5 mulig \u00e5 bruke ekstern tilgang via find.z-wave.me for \u00e5 koble til en ekstern Z-Way. Skriv inn wss://find.z-wave.me i IP-feltet og kopier token med Global scope (logg inn p\u00e5 Z-Way via find.z-wave.me for dette)." + } + } + } +} \ No newline at end of file From 122ada718a20d184bfabf86b486454517eddfe79 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Fri, 11 Feb 2022 01:13:00 -0800 Subject: [PATCH 0527/1098] Bump google-nest-sdm to 1.7.1 (minor patch) (#66304) --- homeassistant/components/nest/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/nest/manifest.json b/homeassistant/components/nest/manifest.json index d66c56d208f92f..6e7ac1257fa116 100644 --- a/homeassistant/components/nest/manifest.json +++ b/homeassistant/components/nest/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "dependencies": ["ffmpeg", "http", "media_source"], "documentation": "https://www.home-assistant.io/integrations/nest", - "requirements": ["python-nest==4.2.0", "google-nest-sdm==1.7.0"], + "requirements": ["python-nest==4.2.0", "google-nest-sdm==1.7.1"], "codeowners": ["@allenporter"], "quality_scale": "platinum", "dhcp": [ diff --git a/requirements_all.txt b/requirements_all.txt index 09e983c66f951b..9ce6f6294d91fb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -764,7 +764,7 @@ google-cloud-pubsub==2.9.0 google-cloud-texttospeech==0.4.0 # homeassistant.components.nest -google-nest-sdm==1.7.0 +google-nest-sdm==1.7.1 # homeassistant.components.google_travel_time googlemaps==2.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 78826760d0beea..c5ff306c079488 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -495,7 +495,7 @@ google-api-python-client==1.6.4 google-cloud-pubsub==2.9.0 # homeassistant.components.nest -google-nest-sdm==1.7.0 +google-nest-sdm==1.7.1 # homeassistant.components.google_travel_time googlemaps==2.5.1 From 43671da7cfcd7e6454c8b4fbc3b435b60d2e98af Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 11 Feb 2022 03:15:50 -0600 Subject: [PATCH 0528/1098] Fix august token refresh when data contains characters outside of latin1 (#66303) * WIP * bump version * bump --- homeassistant/components/august/__init__.py | 1 + homeassistant/components/august/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index 9b340096fde499..8a7c0f9359241f 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -75,6 +75,7 @@ async def async_setup_august( hass.config_entries.async_update_entry(config_entry, data=config_data) await august_gateway.async_authenticate() + await august_gateway.async_refresh_access_token_if_needed() hass.data.setdefault(DOMAIN, {}) data = hass.data[DOMAIN][config_entry.entry_id] = { diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index 71a8b6c83aa10f..1eb923b91a8afc 100644 --- a/homeassistant/components/august/manifest.json +++ b/homeassistant/components/august/manifest.json @@ -2,7 +2,7 @@ "domain": "august", "name": "August", "documentation": "https://www.home-assistant.io/integrations/august", - "requirements": ["yalexs==1.1.20"], + "requirements": ["yalexs==1.1.22"], "codeowners": ["@bdraco"], "dhcp": [ { diff --git a/requirements_all.txt b/requirements_all.txt index 9ce6f6294d91fb..c7906515c38157 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2520,7 +2520,7 @@ xs1-api-client==3.0.0 yalesmartalarmclient==0.3.7 # homeassistant.components.august -yalexs==1.1.20 +yalexs==1.1.22 # homeassistant.components.yeelight yeelight==0.7.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c5ff306c079488..25ca7e4c50b97d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1554,7 +1554,7 @@ xmltodict==0.12.0 yalesmartalarmclient==0.3.7 # homeassistant.components.august -yalexs==1.1.20 +yalexs==1.1.22 # homeassistant.components.yeelight yeelight==0.7.9 From a644baf3cd6655e160eac65cd77ebe6f5c3bef51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20=C3=98stergaard=20Nielsen?= Date: Fri, 11 Feb 2022 10:24:31 +0100 Subject: [PATCH 0529/1098] Prepare for Ihc config flow (#64852) * Extracting group and extra info from ihc products * Make suggested area not optional * Revert back to assignment expression := * Make auto setup show device info for all platforms * Change code comment to # * Add return typing * Remove device_info key without value * get_manual_configuration typings for everything * Adding IHCController typings * Remove "ihc" from unique id * Remove device_info * Separator in unique id * Return typing on ihc_setup Co-authored-by: Martin Hjelmare * Update homeassistant/components/ihc/ihcdevice.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/ihc/ihcdevice.py Co-authored-by: Martin Hjelmare * Raise ValueError instead of logging an error * Update homeassistant/components/ihc/service_functions.py Co-authored-by: Martin Hjelmare * Catch up with dev Co-authored-by: Martin Hjelmare --- homeassistant/components/ihc/__init__.py | 30 +++++++------ homeassistant/components/ihc/auto_setup.py | 6 +++ homeassistant/components/ihc/binary_sensor.py | 22 +++++----- homeassistant/components/ihc/const.py | 1 + homeassistant/components/ihc/ihcdevice.py | 42 +++++++++++++++++-- homeassistant/components/ihc/light.py | 28 ++++++++----- homeassistant/components/ihc/manual_setup.py | 2 +- homeassistant/components/ihc/sensor.py | 22 ++++++---- .../components/ihc/service_functions.py | 18 ++++---- homeassistant/components/ihc/switch.py | 19 ++++----- 10 files changed, 122 insertions(+), 68 deletions(-) diff --git a/homeassistant/components/ihc/__init__.py b/homeassistant/components/ihc/__init__.py index 9a3ae9f8d6c365..34b65c5b791ba6 100644 --- a/homeassistant/components/ihc/__init__.py +++ b/homeassistant/components/ihc/__init__.py @@ -10,13 +10,18 @@ from homeassistant.helpers.typing import ConfigType from .auto_setup import autosetup_ihc_products -from .const import CONF_AUTOSETUP, CONF_INFO, DOMAIN, IHC_CONTROLLER +from .const import ( + CONF_AUTOSETUP, + CONF_INFO, + DOMAIN, + IHC_CONTROLLER, + IHC_CONTROLLER_INDEX, +) from .manual_setup import IHC_SCHEMA, get_manual_configuration from .service_functions import setup_service_functions _LOGGER = logging.getLogger(__name__) -IHC_INFO = "info" CONFIG_SCHEMA = vol.Schema( {DOMAIN: vol.Schema(vol.All(cv.ensure_list, [IHC_SCHEMA]))}, extra=vol.ALLOW_EXTRA @@ -29,7 +34,6 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: for index, controller_conf in enumerate(conf): if not ihc_setup(hass, config, controller_conf, index): return False - return True @@ -37,7 +41,7 @@ def ihc_setup( hass: HomeAssistant, config: ConfigType, controller_conf: ConfigType, - controller_id: int, + controller_index: int, ) -> bool: """Set up the IHC integration.""" url = controller_conf[CONF_URL] @@ -48,20 +52,20 @@ def ihc_setup( if not ihc_controller.authenticate(): _LOGGER.error("Unable to authenticate on IHC controller") return False - + controller_id: str = ihc_controller.client.get_system_info()["serial_number"] + # Store controller configuration + hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN][controller_id] = { + IHC_CONTROLLER: ihc_controller, + CONF_INFO: controller_conf[CONF_INFO], + IHC_CONTROLLER_INDEX: controller_index, + } if controller_conf[CONF_AUTOSETUP] and not autosetup_ihc_products( hass, config, ihc_controller, controller_id ): return False - # Manual configuration get_manual_configuration(hass, config, controller_conf, controller_id) - # Store controller configuration - ihc_key = f"ihc{controller_id}" - hass.data[ihc_key] = { - IHC_CONTROLLER: ihc_controller, - IHC_INFO: controller_conf[CONF_INFO], - } # We only want to register the service functions once for the first controller - if controller_id == 0: + if controller_index == 0: setup_service_functions(hass) return True diff --git a/homeassistant/components/ihc/auto_setup.py b/homeassistant/components/ihc/auto_setup.py index f782cd590c0265..ae271108848bb8 100644 --- a/homeassistant/components/ihc/auto_setup.py +++ b/homeassistant/components/ihc/auto_setup.py @@ -120,19 +120,25 @@ def get_discovery_info(platform_setup, groups, controller_id): for product_cfg in platform_setup: products = group.findall(product_cfg[CONF_XPATH]) for product in products: + product_id = int(product.attrib["id"].strip("_"), 0) nodes = product.findall(product_cfg[CONF_NODE]) for node in nodes: if "setting" in node.attrib and node.attrib["setting"] == "yes": continue ihc_id = int(node.attrib["id"].strip("_"), 0) name = f"{groupname}_{ihc_id}" + # make the model number look a bit nicer - strip leading _ + model = product.get("product_identifier", "").lstrip("_") device = { "ihc_id": ihc_id, "ctrl_id": controller_id, "product": { + "id": product_id, "name": product.get("name") or "", "note": product.get("note") or "", "position": product.get("position") or "", + "model": model, + "group": groupname, }, "product_cfg": product_cfg, } diff --git a/homeassistant/components/ihc/binary_sensor.py b/homeassistant/components/ihc/binary_sensor.py index 7a981fc1b633cb..48035d27a4d14e 100644 --- a/homeassistant/components/ihc/binary_sensor.py +++ b/homeassistant/components/ihc/binary_sensor.py @@ -1,14 +1,15 @@ """Support for IHC binary sensors.""" from __future__ import annotations +from ihcsdk.ihccontroller import IHCController + from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.const import CONF_TYPE from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import IHC_CONTROLLER, IHC_INFO -from .const import CONF_INVERTING +from .const import CONF_INVERTING, DOMAIN, IHC_CONTROLLER from .ihcdevice import IHCDevice @@ -27,16 +28,13 @@ def setup_platform( product_cfg = device["product_cfg"] product = device["product"] # Find controller that corresponds with device id - ctrl_id = device["ctrl_id"] - ihc_key = f"ihc{ctrl_id}" - info = hass.data[ihc_key][IHC_INFO] - ihc_controller = hass.data[ihc_key][IHC_CONTROLLER] - + controller_id = device["ctrl_id"] + ihc_controller: IHCController = hass.data[DOMAIN][controller_id][IHC_CONTROLLER] sensor = IHCBinarySensor( ihc_controller, + controller_id, name, ihc_id, - info, product_cfg.get(CONF_TYPE), product_cfg[CONF_INVERTING], product, @@ -54,16 +52,16 @@ class IHCBinarySensor(IHCDevice, BinarySensorEntity): def __init__( self, - ihc_controller, - name, + ihc_controller: IHCController, + controller_id: str, + name: str, ihc_id: int, - info: bool, sensor_type: str, inverting: bool, product=None, ) -> None: """Initialize the IHC binary sensor.""" - super().__init__(ihc_controller, name, ihc_id, info, product) + super().__init__(ihc_controller, controller_id, name, ihc_id, product) self._state = None self._sensor_type = sensor_type self.inverting = inverting diff --git a/homeassistant/components/ihc/const.py b/homeassistant/components/ihc/const.py index 2f3651c7bb7969..c86e77870c8eee 100644 --- a/homeassistant/components/ihc/const.py +++ b/homeassistant/components/ihc/const.py @@ -25,6 +25,7 @@ DOMAIN = "ihc" IHC_CONTROLLER = "controller" +IHC_CONTROLLER_INDEX = "controller_index" IHC_PLATFORMS = ( Platform.BINARY_SENSOR, Platform.LIGHT, diff --git a/homeassistant/components/ihc/ihcdevice.py b/homeassistant/components/ihc/ihcdevice.py index e351d2f38eafff..31887c51397577 100644 --- a/homeassistant/components/ihc/ihcdevice.py +++ b/homeassistant/components/ihc/ihcdevice.py @@ -1,6 +1,14 @@ """Implementation of a base class for all IHC devices.""" +import logging + +from ihcsdk.ihccontroller import IHCController + from homeassistant.helpers.entity import Entity +from .const import CONF_INFO, DOMAIN + +_LOGGER = logging.getLogger(__name__) + class IHCDevice(Entity): """Base class for all IHC devices. @@ -11,17 +19,33 @@ class IHCDevice(Entity): """ def __init__( - self, ihc_controller, name, ihc_id: int, info: bool, product=None + self, + ihc_controller: IHCController, + controller_id: str, + name: str, + ihc_id: int, + product=None, ) -> None: """Initialize IHC attributes.""" self.ihc_controller = ihc_controller self._name = name self.ihc_id = ihc_id - self.info = info + self.controller_id = controller_id + self.device_id = None + self.suggested_area = None if product: self.ihc_name = product["name"] self.ihc_note = product["note"] self.ihc_position = product["position"] + self.suggested_area = product["group"] if "group" in product else None + if "id" in product: + product_id = product["id"] + self.device_id = f"{controller_id}_{product_id }" + # this will name the device the same way as the IHC visual application: Product name + position + self.device_name = product["name"] + if self.ihc_position: + self.device_name += f" ({self.ihc_position})" + self.device_model = product["model"] else: self.ihc_name = "" self.ihc_note = "" @@ -29,6 +53,7 @@ def __init__( async def async_added_to_hass(self): """Add callback for IHC changes.""" + _LOGGER.debug("Adding IHC entity notify event: %s", self.ihc_id) self.ihc_controller.add_notify_event(self.ihc_id, self.on_ihc_change, True) @property @@ -41,17 +66,26 @@ def name(self): """Return the device name.""" return self._name + @property + def unique_id(self): + """Return a unique ID.""" + return f"{self.controller_id}-{self.ihc_id}" + @property def extra_state_attributes(self): """Return the state attributes.""" - if not self.info: + if not self.hass.data[DOMAIN][self.controller_id][CONF_INFO]: return {} - return { + attributes = { "ihc_id": self.ihc_id, "ihc_name": self.ihc_name, "ihc_note": self.ihc_note, "ihc_position": self.ihc_position, } + if len(self.hass.data[DOMAIN]) > 1: + # We only want to show the controller id if we have more than one + attributes["ihc_controller"] = self.controller_id + return attributes def on_ihc_change(self, ihc_id, value): """Handle IHC resource change. diff --git a/homeassistant/components/ihc/light.py b/homeassistant/components/ihc/light.py index b6269865072c64..b86f9fb3c8aa57 100644 --- a/homeassistant/components/ihc/light.py +++ b/homeassistant/components/ihc/light.py @@ -1,6 +1,8 @@ """Support for IHC lights.""" from __future__ import annotations +from ihcsdk.ihccontroller import IHCController + from homeassistant.components.light import ( ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, @@ -10,8 +12,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import IHC_CONTROLLER, IHC_INFO -from .const import CONF_DIMMABLE, CONF_OFF_ID, CONF_ON_ID +from .const import CONF_DIMMABLE, CONF_OFF_ID, CONF_ON_ID, DOMAIN, IHC_CONTROLLER from .ihcdevice import IHCDevice from .util import async_pulse, async_set_bool, async_set_int @@ -31,15 +32,20 @@ def setup_platform( product_cfg = device["product_cfg"] product = device["product"] # Find controller that corresponds with device id - ctrl_id = device["ctrl_id"] - ihc_key = f"ihc{ctrl_id}" - info = hass.data[ihc_key][IHC_INFO] - ihc_controller = hass.data[ihc_key][IHC_CONTROLLER] + controller_id = device["ctrl_id"] + ihc_controller: IHCController = hass.data[DOMAIN][controller_id][IHC_CONTROLLER] ihc_off_id = product_cfg.get(CONF_OFF_ID) ihc_on_id = product_cfg.get(CONF_ON_ID) dimmable = product_cfg[CONF_DIMMABLE] light = IhcLight( - ihc_controller, name, ihc_id, ihc_off_id, ihc_on_id, info, dimmable, product + ihc_controller, + controller_id, + name, + ihc_id, + ihc_off_id, + ihc_on_id, + dimmable, + product, ) devices.append(light) add_entities(devices) @@ -55,17 +61,17 @@ class IhcLight(IHCDevice, LightEntity): def __init__( self, - ihc_controller, - name, + ihc_controller: IHCController, + controller_id: str, + name: str, ihc_id: int, ihc_off_id: int, ihc_on_id: int, - info: bool, dimmable=False, product=None, ) -> None: """Initialize the light.""" - super().__init__(ihc_controller, name, ihc_id, info, product) + super().__init__(ihc_controller, controller_id, name, ihc_id, product) self._ihc_off_id = ihc_off_id self._ihc_on_id = ihc_on_id self._brightness = 0 diff --git a/homeassistant/components/ihc/manual_setup.py b/homeassistant/components/ihc/manual_setup.py index a68230c9900e34..297997281c6e39 100644 --- a/homeassistant/components/ihc/manual_setup.py +++ b/homeassistant/components/ihc/manual_setup.py @@ -111,7 +111,7 @@ def get_manual_configuration( hass: HomeAssistant, config: ConfigType, controller_conf: ConfigType, - controller_id: int, + controller_id: str, ) -> None: """Get manual configuration for IHC devices.""" for platform in IHC_PLATFORMS: diff --git a/homeassistant/components/ihc/sensor.py b/homeassistant/components/ihc/sensor.py index d9dcab431d04d6..d3c38687caa767 100644 --- a/homeassistant/components/ihc/sensor.py +++ b/homeassistant/components/ihc/sensor.py @@ -1,6 +1,8 @@ """Support for IHC sensors.""" from __future__ import annotations +from ihcsdk.ihccontroller import IHCController + from homeassistant.components.sensor import SensorDeviceClass, SensorEntity from homeassistant.const import CONF_UNIT_OF_MEASUREMENT from homeassistant.core import HomeAssistant @@ -8,7 +10,7 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util.unit_system import TEMPERATURE_UNITS -from . import IHC_CONTROLLER, IHC_INFO +from .const import DOMAIN, IHC_CONTROLLER from .ihcdevice import IHCDevice @@ -27,12 +29,10 @@ def setup_platform( product_cfg = device["product_cfg"] product = device["product"] # Find controller that corresponds with device id - ctrl_id = device["ctrl_id"] - ihc_key = f"ihc{ctrl_id}" - info = hass.data[ihc_key][IHC_INFO] - ihc_controller = hass.data[ihc_key][IHC_CONTROLLER] + controller_id = device["ctrl_id"] + ihc_controller: IHCController = hass.data[DOMAIN][controller_id][IHC_CONTROLLER] unit = product_cfg[CONF_UNIT_OF_MEASUREMENT] - sensor = IHCSensor(ihc_controller, name, ihc_id, info, unit, product) + sensor = IHCSensor(ihc_controller, controller_id, name, ihc_id, unit, product) devices.append(sensor) add_entities(devices) @@ -41,10 +41,16 @@ class IHCSensor(IHCDevice, SensorEntity): """Implementation of the IHC sensor.""" def __init__( - self, ihc_controller, name, ihc_id: int, info: bool, unit, product=None + self, + ihc_controller: IHCController, + controller_id: str, + name: str, + ihc_id: int, + unit: str, + product=None, ) -> None: """Initialize the IHC sensor.""" - super().__init__(ihc_controller, name, ihc_id, info, product) + super().__init__(ihc_controller, controller_id, name, ihc_id, product) self._state = None self._unit_of_measurement = unit diff --git a/homeassistant/components/ihc/service_functions.py b/homeassistant/components/ihc/service_functions.py index 6136c8ccd46744..3d7008ee38b1b8 100644 --- a/homeassistant/components/ihc/service_functions.py +++ b/homeassistant/components/ihc/service_functions.py @@ -1,6 +1,4 @@ """Support for IHC devices.""" -import logging - import voluptuous as vol from homeassistant.core import HomeAssistant @@ -12,6 +10,7 @@ ATTR_VALUE, DOMAIN, IHC_CONTROLLER, + IHC_CONTROLLER_INDEX, SERVICE_PULSE, SERVICE_SET_RUNTIME_VALUE_BOOL, SERVICE_SET_RUNTIME_VALUE_FLOAT, @@ -19,9 +18,6 @@ ) from .util import async_pulse, async_set_bool, async_set_float, async_set_int -_LOGGER = logging.getLogger(__name__) - - SET_RUNTIME_VALUE_BOOL_SCHEMA = vol.Schema( { vol.Required(ATTR_IHC_ID): cv.positive_int, @@ -54,13 +50,17 @@ ) -def setup_service_functions(hass: HomeAssistant): +def setup_service_functions(hass: HomeAssistant) -> None: """Set up the IHC service functions.""" def _get_controller(call): - controller_id = call.data[ATTR_CONTROLLER_ID] - ihc_key = f"ihc{controller_id}" - return hass.data[ihc_key][IHC_CONTROLLER] + controller_index = call.data[ATTR_CONTROLLER_ID] + for controller_id in hass.data[DOMAIN]: + controller_conf = hass.data[DOMAIN][controller_id] + if controller_conf[IHC_CONTROLLER_INDEX] == controller_index: + return controller_conf[IHC_CONTROLLER] + # if not found the controller_index is ouf of range + raise ValueError("The controller index is out of range") async def async_set_runtime_value_bool(call): """Set a IHC runtime bool value service function.""" diff --git a/homeassistant/components/ihc/switch.py b/homeassistant/components/ihc/switch.py index fb1e2f1642d3bc..e33d3b6bb5eae1 100644 --- a/homeassistant/components/ihc/switch.py +++ b/homeassistant/components/ihc/switch.py @@ -1,13 +1,14 @@ """Support for IHC switches.""" from __future__ import annotations +from ihcsdk.ihccontroller import IHCController + from homeassistant.components.switch import SwitchEntity from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import IHC_CONTROLLER, IHC_INFO -from .const import CONF_OFF_ID, CONF_ON_ID +from .const import CONF_OFF_ID, CONF_ON_ID, DOMAIN, IHC_CONTROLLER from .ihcdevice import IHCDevice from .util import async_pulse, async_set_bool @@ -27,15 +28,13 @@ def setup_platform( product_cfg = device["product_cfg"] product = device["product"] # Find controller that corresponds with device id - ctrl_id = device["ctrl_id"] - ihc_key = f"ihc{ctrl_id}" - info = hass.data[ihc_key][IHC_INFO] - ihc_controller = hass.data[ihc_key][IHC_CONTROLLER] + controller_id = device["ctrl_id"] + ihc_controller: IHCController = hass.data[DOMAIN][controller_id][IHC_CONTROLLER] ihc_off_id = product_cfg.get(CONF_OFF_ID) ihc_on_id = product_cfg.get(CONF_ON_ID) switch = IHCSwitch( - ihc_controller, name, ihc_id, ihc_off_id, ihc_on_id, info, product + ihc_controller, controller_id, name, ihc_id, ihc_off_id, ihc_on_id, product ) devices.append(switch) add_entities(devices) @@ -46,16 +45,16 @@ class IHCSwitch(IHCDevice, SwitchEntity): def __init__( self, - ihc_controller, + ihc_controller: IHCController, + controller_id: str, name: str, ihc_id: int, ihc_off_id: int, ihc_on_id: int, - info: bool, product=None, ) -> None: """Initialize the IHC switch.""" - super().__init__(ihc_controller, name, ihc_id, product) + super().__init__(ihc_controller, controller_id, name, ihc_id, product) self._ihc_off_id = ihc_off_id self._ihc_on_id = ihc_on_id self._state = False From 335a9181185959f9bce20bff73b76543cf00be31 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 11 Feb 2022 10:31:51 +0100 Subject: [PATCH 0530/1098] Create MQTT discovery flow when manual config is present (#66248) * Create MQTT discovery flow when manual config is present * Change to integration_discovery flow * Add test * Add default handler for integration_discovery --- homeassistant/components/mqtt/__init__.py | 9 +++++++ homeassistant/config_entries.py | 30 ++++++++++++++--------- tests/components/mqtt/test_config_flow.py | 20 +++++++++++++++ 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 6cb2eb41d9c8aa..7c3bc63779b092 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -21,6 +21,7 @@ import jinja2 import voluptuous as vol +from homeassistant import config_entries from homeassistant.components import websocket_api from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -585,6 +586,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: conf = dict(conf) hass.data[DATA_MQTT_CONFIG] = conf + if not bool(hass.config_entries.async_entries(DOMAIN)): + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, + data={}, + ) + ) return True diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index f5cef1e7484e87..a0017c36684461 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1393,12 +1393,24 @@ def async_abort( reason=reason, description_placeholders=description_placeholders ) + async def async_step_dhcp( + self, discovery_info: DhcpServiceInfo + ) -> data_entry_flow.FlowResult: + """Handle a flow initialized by DHCP discovery.""" + return await self.async_step_discovery(dataclasses.asdict(discovery_info)) + async def async_step_hassio( self, discovery_info: HassioServiceInfo ) -> data_entry_flow.FlowResult: """Handle a flow initialized by HASS IO discovery.""" return await self.async_step_discovery(discovery_info.config) + async def async_step_integration_discovery( + self, discovery_info: DiscoveryInfoType + ) -> data_entry_flow.FlowResult: + """Handle a flow initialized by integration specific discovery.""" + return await self.async_step_discovery(discovery_info) + async def async_step_homekit( self, discovery_info: ZeroconfServiceInfo ) -> data_entry_flow.FlowResult: @@ -1417,24 +1429,18 @@ async def async_step_ssdp( """Handle a flow initialized by SSDP discovery.""" return await self.async_step_discovery(dataclasses.asdict(discovery_info)) - async def async_step_zeroconf( - self, discovery_info: ZeroconfServiceInfo - ) -> data_entry_flow.FlowResult: - """Handle a flow initialized by Zeroconf discovery.""" - return await self.async_step_discovery(dataclasses.asdict(discovery_info)) - - async def async_step_dhcp( - self, discovery_info: DhcpServiceInfo - ) -> data_entry_flow.FlowResult: - """Handle a flow initialized by DHCP discovery.""" - return await self.async_step_discovery(dataclasses.asdict(discovery_info)) - async def async_step_usb( self, discovery_info: UsbServiceInfo ) -> data_entry_flow.FlowResult: """Handle a flow initialized by USB discovery.""" return await self.async_step_discovery(dataclasses.asdict(discovery_info)) + async def async_step_zeroconf( + self, discovery_info: ZeroconfServiceInfo + ) -> data_entry_flow.FlowResult: + """Handle a flow initialized by Zeroconf discovery.""" + return await self.async_step_discovery(dataclasses.asdict(discovery_info)) + @callback def async_create_entry( # pylint: disable=arguments-differ self, diff --git a/tests/components/mqtt/test_config_flow.py b/tests/components/mqtt/test_config_flow.py index 83dafd6b43697a..f16a0e5e83a4a9 100644 --- a/tests/components/mqtt/test_config_flow.py +++ b/tests/components/mqtt/test_config_flow.py @@ -79,6 +79,26 @@ async def test_user_connection_fails(hass, mock_try_connection, mock_finish_setu assert len(mock_finish_setup.mock_calls) == 0 +async def test_manual_config_starts_discovery_flow( + hass, mock_try_connection, mock_finish_setup, mqtt_client_mock +): + """Test manual config initiates a discovery flow.""" + # No flows in progress + assert hass.config_entries.flow.async_progress() == [] + + # MQTT config present in yaml config + assert await async_setup_component(hass, "mqtt", {"mqtt": {}}) + await hass.async_block_till_done() + assert len(mock_finish_setup.mock_calls) == 0 + + # There should now be a discovery flow + flows = hass.config_entries.flow.async_progress() + assert len(flows) == 1 + assert flows[0]["context"]["source"] == "integration_discovery" + assert flows[0]["handler"] == "mqtt" + assert flows[0]["step_id"] == "broker" + + async def test_manual_config_set( hass, mock_try_connection, mock_finish_setup, mqtt_client_mock ): From 49a41ebe14b047dd4f73d023a9ab8c44312a6555 Mon Sep 17 00:00:00 2001 From: ufodone <35497351+ufodone@users.noreply.github.com> Date: Fri, 11 Feb 2022 02:38:50 -0800 Subject: [PATCH 0531/1098] Disable zone bypass switch feature (#66243) * Add configuration option to disable the creation of zone bypass switches * Removed temporary workaround and bumped pyenvisalink version to pick up the correct fix. * Remove zone bypass configuration option and disable zone bypass switches per code review instructions. --- CODEOWNERS | 1 + .../components/envisalink/__init__.py | 18 ++++-------------- .../components/envisalink/manifest.json | 4 ++-- requirements_all.txt | 2 +- 4 files changed, 8 insertions(+), 17 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 20fe7d086e01a4..b7b688a0e73c06 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -262,6 +262,7 @@ tests/components/enphase_envoy/* @gtdiehl homeassistant/components/entur_public_transport/* @hfurubotten homeassistant/components/environment_canada/* @gwww @michaeldavie tests/components/environment_canada/* @gwww @michaeldavie +homeassistant/components/envisalink/* @ufodone homeassistant/components/ephember/* @ttroy50 homeassistant/components/epson/* @pszafer tests/components/epson/* @pszafer diff --git a/homeassistant/components/envisalink/__init__.py b/homeassistant/components/envisalink/__init__.py index 183627fdfa6097..aa276af492c276 100644 --- a/homeassistant/components/envisalink/__init__.py +++ b/homeassistant/components/envisalink/__init__.py @@ -137,6 +137,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: keep_alive, hass.loop, connection_timeout, + False, ) hass.data[DATA_EVL] = controller @@ -181,12 +182,6 @@ def async_partition_updated_callback(data): _LOGGER.debug("The envisalink sent a partition update event") async_dispatcher_send(hass, SIGNAL_PARTITION_UPDATE, data) - @callback - def async_zone_bypass_update(data): - """Handle zone bypass status updates.""" - _LOGGER.debug("Envisalink sent a zone bypass update event. Updating zones") - async_dispatcher_send(hass, SIGNAL_ZONE_BYPASS_UPDATE, data) - @callback def stop_envisalink(event): """Shutdown envisalink connection and thread on exit.""" @@ -206,7 +201,6 @@ async def handle_custom_function(call: ServiceCall) -> None: controller.callback_login_failure = async_login_fail_callback controller.callback_login_timeout = async_connection_fail_callback controller.callback_login_success = async_connection_success_callback - controller.callback_zone_bypass_update = async_zone_bypass_update _LOGGER.info("Start envisalink") controller.start() @@ -240,13 +234,9 @@ async def handle_custom_function(call: ServiceCall) -> None: hass, Platform.BINARY_SENSOR, "envisalink", {CONF_ZONES: zones}, config ) ) - # Only DSC panels support getting zone bypass status - if panel_type == PANEL_TYPE_DSC: - hass.async_create_task( - async_load_platform( - hass, "switch", "envisalink", {CONF_ZONES: zones}, config - ) - ) + + # Zone bypass switches are not currently created due to an issue with some panels. + # These switches will be re-added in the future after some further refactoring of the integration. hass.services.async_register( DOMAIN, SERVICE_CUSTOM_FUNCTION, handle_custom_function, schema=SERVICE_SCHEMA diff --git a/homeassistant/components/envisalink/manifest.json b/homeassistant/components/envisalink/manifest.json index a7e6a29bfe8c5a..2154cd687726c0 100644 --- a/homeassistant/components/envisalink/manifest.json +++ b/homeassistant/components/envisalink/manifest.json @@ -2,8 +2,8 @@ "domain": "envisalink", "name": "Envisalink", "documentation": "https://www.home-assistant.io/integrations/envisalink", - "requirements": ["pyenvisalink==4.3"], - "codeowners": [], + "requirements": ["pyenvisalink==4.4"], + "codeowners": ["@ufodone"], "iot_class": "local_push", "loggers": ["pyenvisalink"] } diff --git a/requirements_all.txt b/requirements_all.txt index c7906515c38157..ce3a8f71828b30 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1503,7 +1503,7 @@ pyeight==0.2.0 pyemby==1.8 # homeassistant.components.envisalink -pyenvisalink==4.3 +pyenvisalink==4.4 # homeassistant.components.ephember pyephember==0.3.1 From 6c3b36a978311f8fbb2de21e60fd0fb3370ae011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stef=C3=A1n=20J=C3=B6kull=20Sigur=C3=B0arson?= Date: Fri, 11 Feb 2022 11:53:09 +0000 Subject: [PATCH 0532/1098] Add Icelandic to list of supported Azure languages (#66310) It was missing, as it is a supported language as per the list here: https://docs.microsoft.com/en-us/azure/cognitive-services/speech-service/language-support#text-to-speech --- homeassistant/components/microsoft/tts.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/microsoft/tts.py b/homeassistant/components/microsoft/tts.py index 611cf57e56ba2a..59902335d4765f 100644 --- a/homeassistant/components/microsoft/tts.py +++ b/homeassistant/components/microsoft/tts.py @@ -58,6 +58,7 @@ "hr-hr", "hu-hu", "id-id", + "is-is", "it-it", "ja-jp", "ko-kr", From 11a13aa0b8908ee6421c24a23eacf1635f6ba1b3 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 11 Feb 2022 13:36:27 +0100 Subject: [PATCH 0533/1098] Add heating and cooling binary sensors to Plugwise (#66317) --- .../components/plugwise/binary_sensor.py | 18 +++++++++++++++++- .../components/plugwise/test_binary_sensor.py | 8 ++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index ec1f2e5a2b4949..129f4faef923dd 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -43,6 +43,20 @@ class PlugwiseBinarySensorEntityDescription(BinarySensorEntityDescription): icon_off="mdi:fire-off", entity_category=EntityCategory.DIAGNOSTIC, ), + PlugwiseBinarySensorEntityDescription( + key="heating_state", + name="Heating", + icon="mdi:radiator", + icon_off="mdi:radiator-off", + entity_category=EntityCategory.DIAGNOSTIC, + ), + PlugwiseBinarySensorEntityDescription( + key="cooling_state", + name="Cooling", + icon="mdi:snowflake", + icon_off="mdi:snowflake-off", + entity_category=EntityCategory.DIAGNOSTIC, + ), PlugwiseBinarySensorEntityDescription( key="slave_boiler_state", name="Secondary Boiler State", @@ -73,7 +87,7 @@ async def async_setup_entry( entities: list[PlugwiseBinarySensorEntity] = [] for device_id, device in coordinator.data.devices.items(): for description in BINARY_SENSORS: - if ( + if description.key not in device and ( "binary_sensors" not in device or description.key not in device["binary_sensors"] ): @@ -109,6 +123,8 @@ def __init__( @property def is_on(self) -> bool | None: """Return true if the binary sensor is on.""" + if self.entity_description.key in self.device: + return self.device[self.entity_description.key] return self.device["binary_sensors"].get(self.entity_description.key) @property diff --git a/tests/components/plugwise/test_binary_sensor.py b/tests/components/plugwise/test_binary_sensor.py index 4bcecf83157623..aacb9e469bb477 100644 --- a/tests/components/plugwise/test_binary_sensor.py +++ b/tests/components/plugwise/test_binary_sensor.py @@ -21,6 +21,14 @@ async def test_anna_climate_binary_sensor_entities( assert state assert state.state == STATE_OFF + state = hass.states.get("binary_sensor.opentherm_heating") + assert state + assert state.state == STATE_ON + + state = hass.states.get("binary_sensor.opentherm_cooling") + assert state + assert state.state == STATE_OFF + async def test_anna_climate_binary_sensor_change( hass: HomeAssistant, mock_smile_anna: MagicMock, init_integration: MockConfigEntry From 1c087f5c701e7cfc622e2eafb768978343a90a4d Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Fri, 11 Feb 2022 14:48:32 +0100 Subject: [PATCH 0534/1098] Bump velbusaio to 2022.2.4 (#66321) --- homeassistant/components/velbus/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index b38ab3ef5d3b8c..c9a72aa2d8e789 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -2,7 +2,7 @@ "domain": "velbus", "name": "Velbus", "documentation": "https://www.home-assistant.io/integrations/velbus", - "requirements": ["velbus-aio==2022.2.3"], + "requirements": ["velbus-aio==2022.2.4"], "config_flow": true, "codeowners": ["@Cereal2nd", "@brefra"], "dependencies": ["usb"], diff --git a/requirements_all.txt b/requirements_all.txt index ce3a8f71828b30..916818db1c09d8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2430,7 +2430,7 @@ vallox-websocket-api==2.9.0 vehicle==0.3.1 # homeassistant.components.velbus -velbus-aio==2022.2.3 +velbus-aio==2022.2.4 # homeassistant.components.venstar venstarcolortouch==0.15 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 25ca7e4c50b97d..7ad4cfc748a1b6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1494,7 +1494,7 @@ vallox-websocket-api==2.9.0 vehicle==0.3.1 # homeassistant.components.velbus -velbus-aio==2022.2.3 +velbus-aio==2022.2.4 # homeassistant.components.venstar venstarcolortouch==0.15 From 7a40ae13a4012b6518d80bcc8d7650db988354ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Fri, 11 Feb 2022 14:57:45 +0100 Subject: [PATCH 0535/1098] Add guard for invalid EntityCategory value (#66316) --- homeassistant/helpers/entity.py | 5 ++++- tests/helpers/test_entity_registry.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index b670c734b47caf..e9038d1f658593 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -223,7 +223,10 @@ def convert_to_entity_category( "EntityCategory instead" % (type(value).__name__, value), error_if_core=False, ) - return EntityCategory(value) + try: + return EntityCategory(value) + except ValueError: + return None return value diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index 5f49889b6c6af7..714ac037e2a525 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -1138,3 +1138,15 @@ async def test_deprecated_entity_category_str(hass, registry, caplog): assert entry.entity_category is EntityCategory.DIAGNOSTIC assert " should be updated to use EntityCategory" in caplog.text + + +async def test_invalid_entity_category_str(hass, registry, caplog): + """Test use of invalid entity category.""" + entry = er.RegistryEntry( + "light", + "hue", + "5678", + entity_category="invalid", + ) + + assert entry.entity_category is None From 89b20b91339e80e80d878acb1e466e9c3e711832 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Fri, 11 Feb 2022 16:48:36 +0200 Subject: [PATCH 0536/1098] Fix webostv restored supported features turn on (#66318) * Fix webostv restored supported features turn on * Remove ternary operator expression --- .../components/webostv/media_player.py | 5 +- tests/components/webostv/test_media_player.py | 52 +++++++++++++++++-- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index 8fa18ce3142ef9..320ce092427f17 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -326,7 +326,10 @@ def media_image_url(self) -> str | None: def supported_features(self) -> int: """Flag media player features that are supported.""" if self.state == STATE_OFF and self._supported_features is not None: - return self._supported_features + if self._wrapper.turn_on: + return self._supported_features | SUPPORT_TURN_ON + + return self._supported_features & ~SUPPORT_TURN_ON supported = SUPPORT_WEBOSTV diff --git a/tests/components/webostv/test_media_player.py b/tests/components/webostv/test_media_player.py index 450d3d90377a8a..c9cc4a78aeef87 100644 --- a/tests/components/webostv/test_media_player.py +++ b/tests/components/webostv/test_media_player.py @@ -59,6 +59,7 @@ STATE_OFF, STATE_ON, ) +from homeassistant.core import State from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry from homeassistant.setup import async_setup_component @@ -67,7 +68,7 @@ from . import setup_webostv from .const import CHANNEL_2, ENTITY_ID, TV_NAME -from tests.common import async_fire_time_changed +from tests.common import async_fire_time_changed, mock_restore_cache @pytest.mark.parametrize( @@ -562,14 +563,27 @@ async def test_cached_supported_features(hass, client, monkeypatch): """Test test supported features.""" monkeypatch.setattr(client, "is_on", False) monkeypatch.setattr(client, "sound_output", None) + supported = SUPPORT_WEBOSTV | SUPPORT_WEBOSTV_VOLUME | SUPPORT_TURN_ON + mock_restore_cache( + hass, + [ + State( + ENTITY_ID, + STATE_OFF, + attributes={ + ATTR_SUPPORTED_FEATURES: supported, + }, + ) + ], + ) await setup_webostv(hass) await client.mock_state_update() - # TV off, support volume mute, step, set - supported = SUPPORT_WEBOSTV | SUPPORT_WEBOSTV_VOLUME | SUPPORT_VOLUME_SET + # TV off, restored state supports mute, step + # validate SUPPORT_TURN_ON is not cached attrs = hass.states.get(ENTITY_ID).attributes - assert attrs[ATTR_SUPPORTED_FEATURES] == supported + assert attrs[ATTR_SUPPORTED_FEATURES] == supported & ~SUPPORT_TURN_ON # TV on, support volume mute, step monkeypatch.setattr(client, "is_on", True) @@ -601,7 +615,7 @@ async def test_cached_supported_features(hass, client, monkeypatch): assert attrs[ATTR_SUPPORTED_FEATURES] == supported - # TV off, support volume mute, step, step, set + # TV off, support volume mute, step, set monkeypatch.setattr(client, "is_on", False) monkeypatch.setattr(client, "sound_output", None) await client.mock_state_update() @@ -610,3 +624,31 @@ async def test_cached_supported_features(hass, client, monkeypatch): attrs = hass.states.get(ENTITY_ID).attributes assert attrs[ATTR_SUPPORTED_FEATURES] == supported + + # Test support turn on is updated on cached state + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": "webostv.turn_on", + "entity_id": ENTITY_ID, + }, + "action": { + "service": "test.automation", + "data_template": { + "some": ENTITY_ID, + "id": "{{ trigger.id }}", + }, + }, + }, + ], + }, + ) + await client.mock_state_update() + + attrs = hass.states.get(ENTITY_ID).attributes + + assert attrs[ATTR_SUPPORTED_FEATURES] == supported | SUPPORT_TURN_ON From 212c3b7c109b0ee26c17a5b80d54473a512b3bc7 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Fri, 11 Feb 2022 15:49:54 +0100 Subject: [PATCH 0537/1098] bump motionblinds to 0.5.12 (#66323) --- homeassistant/components/motion_blinds/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/motion_blinds/manifest.json b/homeassistant/components/motion_blinds/manifest.json index 136fde657e5295..47efe9bf18e8b1 100644 --- a/homeassistant/components/motion_blinds/manifest.json +++ b/homeassistant/components/motion_blinds/manifest.json @@ -3,7 +3,7 @@ "name": "Motion Blinds", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/motion_blinds", - "requirements": ["motionblinds==0.5.11"], + "requirements": ["motionblinds==0.5.12"], "dependencies": ["network"], "codeowners": ["@starkillerOG"], "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index 916818db1c09d8..b07ac4156adba7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1052,7 +1052,7 @@ mitemp_bt==0.0.5 moehlenhoff-alpha2==1.1.2 # homeassistant.components.motion_blinds -motionblinds==0.5.11 +motionblinds==0.5.12 # homeassistant.components.motioneye motioneye-client==0.3.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7ad4cfc748a1b6..a61afbd574db16 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -661,7 +661,7 @@ minio==5.0.10 moehlenhoff-alpha2==1.1.2 # homeassistant.components.motion_blinds -motionblinds==0.5.11 +motionblinds==0.5.12 # homeassistant.components.motioneye motioneye-client==0.3.12 From 768de8d515ca002eb45e3982a1785a626efdafe8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 11 Feb 2022 09:33:39 -0600 Subject: [PATCH 0538/1098] Fix WiZ bulb being offline if kelvin limits cannot be obtained (#66305) --- homeassistant/components/wiz/__init__.py | 15 ++------------- homeassistant/components/wiz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 5 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/wiz/__init__.py b/homeassistant/components/wiz/__init__.py index a29990b6d44f5a..abdbbf1191a3bc 100644 --- a/homeassistant/components/wiz/__init__.py +++ b/homeassistant/components/wiz/__init__.py @@ -48,19 +48,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: try: scenes = await bulb.getSupportedScenes() await bulb.getMac() - # ValueError gets thrown if the bulb type - # cannot be determined on the first try. - # This is likely because way the library - # processes responses and can be cleaned up - # in the future. - except WizLightNotKnownBulb: - # This is only thrown on IndexError when the - # bulb responds with invalid data? It may - # not actually be possible anymore - _LOGGER.warning("The WiZ bulb type could not be determined for %s", ip_address) - return False - except (ValueError, *WIZ_EXCEPTIONS) as err: - raise ConfigEntryNotReady from err + except (WizLightNotKnownBulb, *WIZ_EXCEPTIONS) as err: + raise ConfigEntryNotReady(f"{ip_address}: {err}") from err async def _async_update() -> None: """Update the WiZ device.""" diff --git a/homeassistant/components/wiz/manifest.json b/homeassistant/components/wiz/manifest.json index 07b306f1121dc2..1296cf50d1b36b 100644 --- a/homeassistant/components/wiz/manifest.json +++ b/homeassistant/components/wiz/manifest.json @@ -8,7 +8,7 @@ ], "dependencies": ["network"], "documentation": "https://www.home-assistant.io/integrations/wiz", - "requirements": ["pywizlight==0.5.5"], + "requirements": ["pywizlight==0.5.6"], "iot_class": "local_push", "codeowners": ["@sbidy"] } diff --git a/requirements_all.txt b/requirements_all.txt index b07ac4156adba7..292f3910904a19 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2057,7 +2057,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.5.5 +pywizlight==0.5.6 # homeassistant.components.xeoma pyxeoma==1.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a61afbd574db16..6869cde00ad427 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1282,7 +1282,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.5.5 +pywizlight==0.5.6 # homeassistant.components.zerproc pyzerproc==0.4.8 From 57624347e6a01c0caa3ea9a45bef1f7cacbba8d4 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 11 Feb 2022 17:53:25 +0100 Subject: [PATCH 0539/1098] Don't requests known Spotify playlist (#66313) --- homeassistant/components/spotify/media_player.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/spotify/media_player.py b/homeassistant/components/spotify/media_player.py index cdcc0132f937b7..fb431ab4824517 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -378,10 +378,13 @@ def update(self) -> None: current = self.data.client.current_playback() self._currently_playing = current or {} - self._playlist = None context = self._currently_playing.get("context") - if context is not None and context["type"] == MEDIA_TYPE_PLAYLIST: - self._playlist = self.data.client.playlist(current["context"]["uri"]) + if context is not None and ( + self._playlist is None or self._playlist["uri"] != context["uri"] + ): + self._playlist = None + if context["type"] == MEDIA_TYPE_PLAYLIST: + self._playlist = self.data.client.playlist(current["context"]["uri"]) devices = self.data.client.devices() or {} self._devices = devices.get("devices", []) From acf20337349b6c83379d61e7d5cc7f0f76382c5e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 11 Feb 2022 18:04:49 +0100 Subject: [PATCH 0540/1098] Use DataUpdateCoordinator for Spotify devices (#66314) --- homeassistant/components/spotify/__init__.py | 32 +++++++++++++++++- homeassistant/components/spotify/const.py | 5 +++ .../components/spotify/media_player.py | 33 +++++++++++-------- 3 files changed, 55 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/spotify/__init__.py b/homeassistant/components/spotify/__init__.py index 24266e2d8bb08a..fb66622d8938ed 100644 --- a/homeassistant/components/spotify/__init__.py +++ b/homeassistant/components/spotify/__init__.py @@ -1,9 +1,12 @@ """The spotify integration.""" +from __future__ import annotations from dataclasses import dataclass +from datetime import timedelta from typing import Any import aiohttp +import requests from spotipy import Spotify, SpotifyException import voluptuous as vol @@ -22,10 +25,11 @@ async_get_config_entry_implementation, ) from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from . import config_flow from .browse_media import async_browse_media -from .const import DOMAIN, SPOTIFY_SCOPES +from .const import DOMAIN, LOGGER, SPOTIFY_SCOPES from .util import is_spotify_media_type, resolve_spotify_media_type CONFIG_SCHEMA = vol.Schema( @@ -57,6 +61,7 @@ class HomeAssistantSpotifyData: client: Spotify current_user: dict[str, Any] + devices: DataUpdateCoordinator session: OAuth2Session @@ -101,10 +106,35 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if not current_user: raise ConfigEntryNotReady + async def _update_devices() -> list[dict[str, Any]]: + try: + devices: dict[str, Any] | None = await hass.async_add_executor_job( + spotify.devices + ) + except (requests.RequestException, SpotifyException) as err: + raise UpdateFailed from err + + if devices is None: + return [] + + return devices.get("devices", []) + + device_coordinator: DataUpdateCoordinator[ + list[dict[str, Any]] + ] = DataUpdateCoordinator( + hass, + LOGGER, + name=f"{entry.title} Devices", + update_interval=timedelta(minutes=5), + update_method=_update_devices, + ) + await device_coordinator.async_config_entry_first_refresh() + hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = HomeAssistantSpotifyData( client=spotify, current_user=current_user, + devices=device_coordinator, session=session, ) diff --git a/homeassistant/components/spotify/const.py b/homeassistant/components/spotify/const.py index 4c86234045be11..ad73262921b1b1 100644 --- a/homeassistant/components/spotify/const.py +++ b/homeassistant/components/spotify/const.py @@ -1,4 +1,7 @@ """Define constants for the Spotify integration.""" + +import logging + from homeassistant.components.media_player.const import ( MEDIA_TYPE_ALBUM, MEDIA_TYPE_ARTIST, @@ -9,6 +12,8 @@ DOMAIN = "spotify" +LOGGER = logging.getLogger(__package__) + SPOTIFY_SCOPES = [ # Needed to be able to control playback "user-modify-playback-state", diff --git a/homeassistant/components/spotify/media_player.py b/homeassistant/components/spotify/media_player.py index fb431ab4824517..f6b229e99fd1e5 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -33,7 +33,7 @@ ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ID, STATE_IDLE, STATE_PAUSED, STATE_PLAYING -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo @@ -147,7 +147,6 @@ def __init__( SPOTIFY_SCOPES ) self._currently_playing: dict | None = {} - self._devices: list[dict] | None = [] self._playlist: dict | None = None @property @@ -258,9 +257,7 @@ def source(self) -> str | None: @property def source_list(self) -> list[str] | None: """Return a list of source devices.""" - if not self._devices: - return None - return [device["name"] for device in self._devices] + return [device["name"] for device in self.data.devices.data] @property def shuffle(self) -> bool | None: @@ -332,19 +329,16 @@ def play_media(self, media_type: str, media_id: str, **kwargs) -> None: if ( self._currently_playing and not self._currently_playing.get("device") - and self._devices + and self.data.devices.data ): - kwargs["device_id"] = self._devices[0].get("id") + kwargs["device_id"] = self.data.devices.data[0].get("id") self.data.client.start_playback(**kwargs) @spotify_exception_handler def select_source(self, source: str) -> None: """Select playback device.""" - if not self._devices: - return - - for device in self._devices: + for device in self.data.devices.data: if device["name"] == source: self.data.client.transfer_playback( device["id"], self.state == STATE_PLAYING @@ -386,9 +380,6 @@ def update(self) -> None: if context["type"] == MEDIA_TYPE_PLAYLIST: self._playlist = self.data.client.playlist(current["context"]["uri"]) - devices = self.data.client.devices() or {} - self._devices = devices.get("devices", []) - async def async_browse_media( self, media_content_type: str | None = None, media_content_id: str | None = None ) -> BrowseMedia: @@ -408,3 +399,17 @@ async def async_browse_media( media_content_type, media_content_id, ) + + @callback + def _handle_devices_update(self) -> None: + """Handle updated data from the coordinator.""" + if not self.enabled: + return + self.async_write_ha_state() + + async def async_added_to_hass(self) -> None: + """When entity is added to hass.""" + await super().async_added_to_hass() + self.async_on_remove( + self.data.devices.async_add_listener(self._handle_devices_update) + ) From 323af9f59c4fe93c016566679ce27dec738490a3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 11 Feb 2022 11:07:32 -0600 Subject: [PATCH 0541/1098] Reduce number of parallel api calls to august (#66328) --- homeassistant/components/august/__init__.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index 8a7c0f9359241f..031f513843f2c9 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -107,11 +107,10 @@ def __init__(self, hass, august_gateway): async def async_setup(self): """Async setup of august device data and activities.""" token = self._august_gateway.access_token - user_data, locks, doorbells = await asyncio.gather( - self._api.async_get_user(token), - self._api.async_get_operable_locks(token), - self._api.async_get_doorbells(token), - ) + # This used to be a gather but it was less reliable with august's recent api changes. + user_data = await self._api.async_get_user(token) + locks = await self._api.async_get_operable_locks(token) + doorbells = await self._api.async_get_doorbells(token) if not doorbells: doorbells = [] if not locks: From df994a1e844f2f04662cde54c8a4ca76d411de4f Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Fri, 11 Feb 2022 18:08:19 +0100 Subject: [PATCH 0542/1098] Fix raspihats initialization (#66330) Co-authored-by: epenet --- homeassistant/components/raspihats/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/raspihats/__init__.py b/homeassistant/components/raspihats/__init__.py index 3d28086db43c71..8f4a8b0aca4d5a 100644 --- a/homeassistant/components/raspihats/__init__.py +++ b/homeassistant/components/raspihats/__init__.py @@ -40,7 +40,7 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: "https://github.com/home-assistant/architecture/blob/master/adr/0019-GPIO.md" ) - hass.data[DOMAIN][I2C_HATS_MANAGER] = I2CHatsManager() + hass.data[DOMAIN] = {I2C_HATS_MANAGER: I2CHatsManager()} def start_i2c_hats_keep_alive(event): """Start I2C-HATs keep alive.""" From 8ff987d90c621308060adc292a2eb944b12a859d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 11 Feb 2022 19:11:06 +0100 Subject: [PATCH 0543/1098] Fix PVOutput when no data is available (#66338) --- .../components/pvoutput/config_flow.py | 2 +- .../components/pvoutput/coordinator.py | 6 ++-- .../components/pvoutput/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/pvoutput/test_config_flow.py | 28 +++++++++---------- tests/components/pvoutput/test_init.py | 10 +++++-- 7 files changed, 30 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/pvoutput/config_flow.py b/homeassistant/components/pvoutput/config_flow.py index a1933ff9315f49..53eabe225f6964 100644 --- a/homeassistant/components/pvoutput/config_flow.py +++ b/homeassistant/components/pvoutput/config_flow.py @@ -23,7 +23,7 @@ async def validate_input(hass: HomeAssistant, *, api_key: str, system_id: int) - api_key=api_key, system_id=system_id, ) - await pvoutput.status() + await pvoutput.system() class PVOutputFlowHandler(ConfigFlow, domain=DOMAIN): diff --git a/homeassistant/components/pvoutput/coordinator.py b/homeassistant/components/pvoutput/coordinator.py index cadef8c8a0db83..7b307f20274abd 100644 --- a/homeassistant/components/pvoutput/coordinator.py +++ b/homeassistant/components/pvoutput/coordinator.py @@ -1,14 +1,14 @@ """DataUpdateCoordinator for the PVOutput integration.""" from __future__ import annotations -from pvo import PVOutput, PVOutputAuthenticationError, Status +from pvo import PVOutput, PVOutputAuthenticationError, PVOutputNoDataError, Status from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_API_KEY from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import CONF_SYSTEM_ID, DOMAIN, LOGGER, SCAN_INTERVAL @@ -33,5 +33,7 @@ async def _async_update_data(self) -> Status: """Fetch system status from PVOutput.""" try: return await self.pvoutput.status() + except PVOutputNoDataError as err: + raise UpdateFailed("PVOutput has no data available") from err except PVOutputAuthenticationError as err: raise ConfigEntryAuthFailed from err diff --git a/homeassistant/components/pvoutput/manifest.json b/homeassistant/components/pvoutput/manifest.json index 042c6b9aa99c0b..021fffe0e0160f 100644 --- a/homeassistant/components/pvoutput/manifest.json +++ b/homeassistant/components/pvoutput/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/pvoutput", "config_flow": true, "codeowners": ["@fabaff", "@frenck"], - "requirements": ["pvo==0.2.1"], + "requirements": ["pvo==0.2.2"], "iot_class": "cloud_polling", "quality_scale": "platinum" } diff --git a/requirements_all.txt b/requirements_all.txt index 292f3910904a19..7d05f5eca7be7f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1313,7 +1313,7 @@ pushbullet.py==0.11.0 pushover_complete==1.1.1 # homeassistant.components.pvoutput -pvo==0.2.1 +pvo==0.2.2 # homeassistant.components.rpi_gpio_pwm pwmled==1.6.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6869cde00ad427..983b50d9e7f441 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -820,7 +820,7 @@ pure-python-adb[async]==0.3.0.dev0 pushbullet.py==0.11.0 # homeassistant.components.pvoutput -pvo==0.2.1 +pvo==0.2.2 # homeassistant.components.canary py-canary==0.5.1 diff --git a/tests/components/pvoutput/test_config_flow.py b/tests/components/pvoutput/test_config_flow.py index 0c060a75a9d412..8cd776beea340e 100644 --- a/tests/components/pvoutput/test_config_flow.py +++ b/tests/components/pvoutput/test_config_flow.py @@ -47,7 +47,7 @@ async def test_full_user_flow( } assert len(mock_setup_entry.mock_calls) == 1 - assert len(mock_pvoutput_config_flow.status.mock_calls) == 1 + assert len(mock_pvoutput_config_flow.system.mock_calls) == 1 async def test_full_flow_with_authentication_error( @@ -68,7 +68,7 @@ async def test_full_flow_with_authentication_error( assert result.get("step_id") == SOURCE_USER assert "flow_id" in result - mock_pvoutput_config_flow.status.side_effect = PVOutputAuthenticationError + mock_pvoutput_config_flow.system.side_effect = PVOutputAuthenticationError result2 = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ @@ -83,9 +83,9 @@ async def test_full_flow_with_authentication_error( assert "flow_id" in result2 assert len(mock_setup_entry.mock_calls) == 0 - assert len(mock_pvoutput_config_flow.status.mock_calls) == 1 + assert len(mock_pvoutput_config_flow.system.mock_calls) == 1 - mock_pvoutput_config_flow.status.side_effect = None + mock_pvoutput_config_flow.system.side_effect = None result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], user_input={ @@ -102,14 +102,14 @@ async def test_full_flow_with_authentication_error( } assert len(mock_setup_entry.mock_calls) == 1 - assert len(mock_pvoutput_config_flow.status.mock_calls) == 2 + assert len(mock_pvoutput_config_flow.system.mock_calls) == 2 async def test_connection_error( hass: HomeAssistant, mock_pvoutput_config_flow: MagicMock ) -> None: """Test API connection error.""" - mock_pvoutput_config_flow.status.side_effect = PVOutputConnectionError + mock_pvoutput_config_flow.system.side_effect = PVOutputConnectionError result = await hass.config_entries.flow.async_init( DOMAIN, @@ -123,7 +123,7 @@ async def test_connection_error( assert result.get("type") == RESULT_TYPE_FORM assert result.get("errors") == {"base": "cannot_connect"} - assert len(mock_pvoutput_config_flow.status.mock_calls) == 1 + assert len(mock_pvoutput_config_flow.system.mock_calls) == 1 async def test_already_configured( @@ -175,7 +175,7 @@ async def test_import_flow( } assert len(mock_setup_entry.mock_calls) == 1 - assert len(mock_pvoutput_config_flow.status.mock_calls) == 1 + assert len(mock_pvoutput_config_flow.system.mock_calls) == 1 async def test_reauth_flow( @@ -214,7 +214,7 @@ async def test_reauth_flow( } assert len(mock_setup_entry.mock_calls) == 1 - assert len(mock_pvoutput_config_flow.status.mock_calls) == 1 + assert len(mock_pvoutput_config_flow.system.mock_calls) == 1 async def test_reauth_with_authentication_error( @@ -243,7 +243,7 @@ async def test_reauth_with_authentication_error( assert result.get("step_id") == "reauth_confirm" assert "flow_id" in result - mock_pvoutput_config_flow.status.side_effect = PVOutputAuthenticationError + mock_pvoutput_config_flow.system.side_effect = PVOutputAuthenticationError result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_API_KEY: "invalid_key"}, @@ -256,9 +256,9 @@ async def test_reauth_with_authentication_error( assert "flow_id" in result2 assert len(mock_setup_entry.mock_calls) == 0 - assert len(mock_pvoutput_config_flow.status.mock_calls) == 1 + assert len(mock_pvoutput_config_flow.system.mock_calls) == 1 - mock_pvoutput_config_flow.status.side_effect = None + mock_pvoutput_config_flow.system.side_effect = None result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], user_input={CONF_API_KEY: "valid_key"}, @@ -273,7 +273,7 @@ async def test_reauth_with_authentication_error( } assert len(mock_setup_entry.mock_calls) == 1 - assert len(mock_pvoutput_config_flow.status.mock_calls) == 2 + assert len(mock_pvoutput_config_flow.system.mock_calls) == 2 async def test_reauth_api_error( @@ -297,7 +297,7 @@ async def test_reauth_api_error( assert result.get("step_id") == "reauth_confirm" assert "flow_id" in result - mock_pvoutput_config_flow.status.side_effect = PVOutputConnectionError + mock_pvoutput_config_flow.system.side_effect = PVOutputConnectionError result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_API_KEY: "some_new_key"}, diff --git a/tests/components/pvoutput/test_init.py b/tests/components/pvoutput/test_init.py index faaff3d4214f67..b583e0807e0233 100644 --- a/tests/components/pvoutput/test_init.py +++ b/tests/components/pvoutput/test_init.py @@ -1,7 +1,11 @@ """Tests for the PVOutput integration.""" from unittest.mock import MagicMock -from pvo import PVOutputAuthenticationError, PVOutputConnectionError +from pvo import ( + PVOutputAuthenticationError, + PVOutputConnectionError, + PVOutputNoDataError, +) import pytest from homeassistant.components.pvoutput.const import CONF_SYSTEM_ID, DOMAIN @@ -35,13 +39,15 @@ async def test_load_unload_config_entry( assert mock_config_entry.state is ConfigEntryState.NOT_LOADED +@pytest.mark.parametrize("side_effect", [PVOutputConnectionError, PVOutputNoDataError]) async def test_config_entry_not_ready( hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_pvoutput: MagicMock, + side_effect: Exception, ) -> None: """Test the PVOutput configuration entry not ready.""" - mock_pvoutput.status.side_effect = PVOutputConnectionError + mock_pvoutput.status.side_effect = side_effect mock_config_entry.add_to_hass(hass) await hass.config_entries.async_setup(mock_config_entry.entry_id) From 2f220b27d47ae9c432027b908336b75876edc6a5 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 11 Feb 2022 19:38:55 +0100 Subject: [PATCH 0544/1098] Fix CPUSpeed with missing info (#66339) --- homeassistant/components/cpuspeed/sensor.py | 4 ++-- tests/components/cpuspeed/test_sensor.py | 22 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/cpuspeed/sensor.py b/homeassistant/components/cpuspeed/sensor.py index 686a7c13e58ad1..8d21b365ad674d 100644 --- a/homeassistant/components/cpuspeed/sensor.py +++ b/homeassistant/components/cpuspeed/sensor.py @@ -81,8 +81,8 @@ def update(self) -> None: if info: self._attr_extra_state_attributes = { - ATTR_ARCH: info["arch_string_raw"], - ATTR_BRAND: info["brand_raw"], + ATTR_ARCH: info.get("arch_string_raw"), + ATTR_BRAND: info.get("brand_raw"), } if HZ_ADVERTISED in info: self._attr_extra_state_attributes[ATTR_HZ] = round( diff --git a/tests/components/cpuspeed/test_sensor.py b/tests/components/cpuspeed/test_sensor.py index 134d19b31ea2ea..ebf9f0111bd392 100644 --- a/tests/components/cpuspeed/test_sensor.py +++ b/tests/components/cpuspeed/test_sensor.py @@ -61,3 +61,25 @@ async def test_sensor( assert state.attributes.get(ATTR_ARCH) == "aargh" assert state.attributes.get(ATTR_BRAND) == "Intel Ryzen 7" assert state.attributes.get(ATTR_HZ) == 3.6 + + +async def test_sensor_partial_info( + hass: HomeAssistant, + mock_cpuinfo: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test the CPU Speed sensor missing info.""" + mock_config_entry.add_to_hass(hass) + + # Pop some info from the mocked CPUSpeed + mock_cpuinfo.return_value.pop("brand_raw") + mock_cpuinfo.return_value.pop("arch_string_raw") + + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get("sensor.cpu_speed") + assert state + assert state.state == "3.2" + assert state.attributes.get(ATTR_ARCH) is None + assert state.attributes.get(ATTR_BRAND) is None From 0daf20c0cce2a60e98952b219f32879d23afaf00 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Fri, 11 Feb 2022 19:26:35 +0000 Subject: [PATCH 0545/1098] Prepare for new aiohomekit lifecycle API (#66340) --- .strict-typing | 1 + .../components/homekit_controller/__init__.py | 14 +++---- .../homekit_controller/config_flow.py | 6 +-- .../homekit_controller/manifest.json | 2 +- .../components/homekit_controller/utils.py | 42 +++++++++++++++++++ mypy.ini | 11 +++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/homekit_controller/common.py | 4 +- .../components/homekit_controller/conftest.py | 5 ++- .../homekit_controller/test_init.py | 18 ++++---- 11 files changed, 79 insertions(+), 28 deletions(-) create mode 100644 homeassistant/components/homekit_controller/utils.py diff --git a/.strict-typing b/.strict-typing index efdccdb1a43c3a..a206f562c17709 100644 --- a/.strict-typing +++ b/.strict-typing @@ -94,6 +94,7 @@ homeassistant.components.homekit_controller.const homeassistant.components.homekit_controller.lock homeassistant.components.homekit_controller.select homeassistant.components.homekit_controller.storage +homeassistant.components.homekit_controller.utils homeassistant.components.homewizard.* homeassistant.components.http.* homeassistant.components.huawei_lte.* diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index eeca98167d0cf4..e4f8f715bc8a2b 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -14,7 +14,6 @@ ) from aiohomekit.model.services import Service, ServicesTypes -from homeassistant.components import zeroconf from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import Event, HomeAssistant @@ -24,8 +23,9 @@ from .config_flow import normalize_hkid from .connection import HKDevice, valid_serial_number -from .const import CONTROLLER, ENTITY_MAP, KNOWN_DEVICES, TRIGGERS +from .const import ENTITY_MAP, KNOWN_DEVICES, TRIGGERS from .storage import EntityMapStorage +from .utils import async_get_controller _LOGGER = logging.getLogger(__name__) @@ -208,10 +208,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: map_storage = hass.data[ENTITY_MAP] = EntityMapStorage(hass) await map_storage.async_initialize() - async_zeroconf_instance = await zeroconf.async_get_async_instance(hass) - hass.data[CONTROLLER] = aiohomekit.Controller( - async_zeroconf_instance=async_zeroconf_instance - ) + await async_get_controller(hass) + hass.data[KNOWN_DEVICES] = {} hass.data[TRIGGERS] = {} @@ -246,10 +244,10 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: # Remove cached type data from .storage/homekit_controller-entity-map hass.data[ENTITY_MAP].async_delete_map(hkid) + controller = await async_get_controller(hass) + # Remove the pairing on the device, making the device discoverable again. # Don't reuse any objects in hass.data as they are already unloaded - async_zeroconf_instance = await zeroconf.async_get_async_instance(hass) - controller = aiohomekit.Controller(async_zeroconf_instance=async_zeroconf_instance) controller.load_pairing(hkid, dict(entry.data)) try: await controller.remove_pairing(hkid) diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index 54d265a5f4ad5b..9053c79b939695 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -20,6 +20,7 @@ ) from .const import DOMAIN, KNOWN_DEVICES +from .utils import async_get_controller HOMEKIT_DIR = ".homekit" HOMEKIT_BRIDGE_DOMAIN = "homekit" @@ -104,10 +105,7 @@ def __init__(self): async def _async_setup_controller(self): """Create the controller.""" - async_zeroconf_instance = await zeroconf.async_get_async_instance(self.hass) - self.controller = aiohomekit.Controller( - async_zeroconf_instance=async_zeroconf_instance - ) + self.controller = await async_get_controller(self.hass) async def async_step_user(self, user_input=None): """Handle a flow start.""" diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index 8cc6ce2b575f78..ce7ae876e036d3 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit Controller", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homekit_controller", - "requirements": ["aiohomekit==0.7.5"], + "requirements": ["aiohomekit==0.7.7"], "zeroconf": ["_hap._tcp.local."], "after_dependencies": ["zeroconf"], "codeowners": ["@Jc2k", "@bdraco"], diff --git a/homeassistant/components/homekit_controller/utils.py b/homeassistant/components/homekit_controller/utils.py new file mode 100644 index 00000000000000..6831c3cee4add4 --- /dev/null +++ b/homeassistant/components/homekit_controller/utils.py @@ -0,0 +1,42 @@ +"""Helper functions for the homekit_controller component.""" +from typing import cast + +from aiohomekit import Controller + +from homeassistant.components import zeroconf +from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.core import Event, HomeAssistant + +from .const import CONTROLLER + + +async def async_get_controller(hass: HomeAssistant) -> Controller: + """Get or create an aiohomekit Controller instance.""" + if existing := hass.data.get(CONTROLLER): + return cast(Controller, existing) + + async_zeroconf_instance = await zeroconf.async_get_async_instance(hass) + + # In theory another call to async_get_controller could have run while we were + # trying to get the zeroconf instance. So we check again to make sure we + # don't leak a Controller instance here. + if existing := hass.data.get(CONTROLLER): + return cast(Controller, existing) + + controller = Controller(async_zeroconf_instance=async_zeroconf_instance) + + hass.data[CONTROLLER] = controller + + async def _async_stop_homekit_controller(event: Event) -> None: + # Pop first so that in theory another controller /could/ start + # While this one was shutting down + hass.data.pop(CONTROLLER, None) + await controller.async_stop() + + # Right now _async_stop_homekit_controller is only called on HA exiting + # So we don't have to worry about leaking a callback here. + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_stop_homekit_controller) + + await controller.async_start() + + return controller diff --git a/mypy.ini b/mypy.ini index d4db04e8e8213a..bbab7d20f80c99 100644 --- a/mypy.ini +++ b/mypy.ini @@ -851,6 +851,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.homekit_controller.utils] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.homewizard.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/requirements_all.txt b/requirements_all.txt index 7d05f5eca7be7f..6d373252f81c2b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -184,7 +184,7 @@ aioguardian==2021.11.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==0.7.5 +aiohomekit==0.7.7 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 983b50d9e7f441..c6ff0311cca197 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -134,7 +134,7 @@ aioguardian==2021.11.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==0.7.5 +aiohomekit==0.7.7 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/tests/components/homekit_controller/common.py b/tests/components/homekit_controller/common.py index 4fabe504d4b22c..7fae69ee01ba17 100644 --- a/tests/components/homekit_controller/common.py +++ b/tests/components/homekit_controller/common.py @@ -174,7 +174,9 @@ async def setup_platform(hass): """Load the platform but with a fake Controller API.""" config = {"discovery": {}} - with mock.patch("aiohomekit.Controller") as controller: + with mock.patch( + "homeassistant.components.homekit_controller.utils.Controller" + ) as controller: fake_controller = controller.return_value = FakeController() await async_setup_component(hass, DOMAIN, config) diff --git a/tests/components/homekit_controller/conftest.py b/tests/components/homekit_controller/conftest.py index 46b8a5de3e70bc..81688f88a4b50d 100644 --- a/tests/components/homekit_controller/conftest.py +++ b/tests/components/homekit_controller/conftest.py @@ -27,7 +27,10 @@ def utcnow(request): def controller(hass): """Replace aiohomekit.Controller with an instance of aiohomekit.testing.FakeController.""" instance = FakeController() - with unittest.mock.patch("aiohomekit.Controller", return_value=instance): + with unittest.mock.patch( + "homeassistant.components.homekit_controller.utils.Controller", + return_value=instance, + ): yield instance diff --git a/tests/components/homekit_controller/test_init.py b/tests/components/homekit_controller/test_init.py index d1b133468d5a95..03694e7186ab16 100644 --- a/tests/components/homekit_controller/test_init.py +++ b/tests/components/homekit_controller/test_init.py @@ -4,7 +4,6 @@ from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes -from aiohomekit.testing import FakeController from homeassistant.components.homekit_controller.const import ENTITY_MAP from homeassistant.const import EVENT_HOMEASSISTANT_STOP @@ -35,19 +34,16 @@ async def test_unload_on_stop(hass, utcnow): async def test_async_remove_entry(hass: HomeAssistant): """Test unpairing a component.""" helper = await setup_test_component(hass, create_motion_sensor_service) + controller = helper.pairing.controller hkid = "00:00:00:00:00:00" - with patch("aiohomekit.Controller") as controller_cls: - # Setup a fake controller with 1 pairing - controller = controller_cls.return_value = FakeController() - await controller.add_paired_device([helper.accessory], hkid) - assert len(controller.pairings) == 1 + assert len(controller.pairings) == 1 - assert hkid in hass.data[ENTITY_MAP].storage_data + assert hkid in hass.data[ENTITY_MAP].storage_data - # Remove it via config entry and number of pairings should go down - await helper.config_entry.async_remove(hass) - assert len(controller.pairings) == 0 + # Remove it via config entry and number of pairings should go down + await helper.config_entry.async_remove(hass) + assert len(controller.pairings) == 0 - assert hkid not in hass.data[ENTITY_MAP].storage_data + assert hkid not in hass.data[ENTITY_MAP].storage_data From 5743661f6ec0257e90942bdc2d0b2690acfdc5db Mon Sep 17 00:00:00 2001 From: Stuart Clark Date: Fri, 11 Feb 2022 20:39:55 +0000 Subject: [PATCH 0546/1098] Upgrade OVO library to v1.2.0 (#66210) --- homeassistant/components/ovo_energy/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ovo_energy/manifest.json b/homeassistant/components/ovo_energy/manifest.json index 19e737f51ca966..6d994e472c8560 100644 --- a/homeassistant/components/ovo_energy/manifest.json +++ b/homeassistant/components/ovo_energy/manifest.json @@ -3,7 +3,7 @@ "name": "OVO Energy", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/ovo_energy", - "requirements": ["ovoenergy==1.1.12"], + "requirements": ["ovoenergy==1.2.0"], "codeowners": ["@timmo001"], "iot_class": "cloud_polling", "loggers": ["ovoenergy"] diff --git a/requirements_all.txt b/requirements_all.txt index 6d373252f81c2b..b2a15ae0064c1b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1194,7 +1194,7 @@ oru==0.1.11 orvibo==1.1.1 # homeassistant.components.ovo_energy -ovoenergy==1.1.12 +ovoenergy==1.2.0 # homeassistant.components.p1_monitor p1monitor==1.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c6ff0311cca197..03a713478e6422 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -740,7 +740,7 @@ open-meteo==0.2.1 openerz-api==0.1.0 # homeassistant.components.ovo_energy -ovoenergy==1.1.12 +ovoenergy==1.2.0 # homeassistant.components.p1_monitor p1monitor==1.0.1 From 24418417fd3c45b9976d9c527310e93223f8f748 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Fri, 11 Feb 2022 13:17:19 -0800 Subject: [PATCH 0547/1098] Fix nest streams that get stuck broken (#66334) --- homeassistant/components/stream/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index b6bfd2122ba40b..79506c0bda256b 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -338,7 +338,6 @@ def _run_worker(self) -> None: ) except StreamWorkerError as err: self._logger.error("Error from stream worker: %s", str(err)) - self._available = False stream_state.discontinuity() if not self.keepalive or self._thread_quit.is_set(): From 43d57e7ae801252255cbbe0cab32429db9f97a2c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 11 Feb 2022 15:19:57 -0600 Subject: [PATCH 0548/1098] Add unique id to lutron caseta config entry when missing (#66346) --- homeassistant/components/lutron_caseta/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index 546bb055ca8a9d..0408f547f25460 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -138,6 +138,11 @@ async def async_setup_entry( devices = bridge.get_devices() bridge_device = devices[BRIDGE_DEVICE_ID] + if not config_entry.unique_id: + hass.config_entries.async_update_entry( + config_entry, unique_id=hex(bridge_device["serial"])[2:].zfill(8) + ) + buttons = bridge.buttons _async_register_bridge_device(hass, entry_id, bridge_device) button_devices = _async_register_button_devices( From 018975bbb9b3fb54aa0d8d696c374624c3c228d3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 11 Feb 2022 16:26:11 -0600 Subject: [PATCH 0549/1098] Add additional OUI for G3 wifi cameras to unifiprotect (#66349) --- homeassistant/components/unifiprotect/manifest.json | 5 ++++- homeassistant/generated/dhcp.py | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/unifiprotect/manifest.json b/homeassistant/components/unifiprotect/manifest.json index a4b7064e564c97..5fcead53da2a9d 100644 --- a/homeassistant/components/unifiprotect/manifest.json +++ b/homeassistant/components/unifiprotect/manifest.json @@ -44,7 +44,10 @@ }, { "macaddress": "265A4C*" - } + }, + { + "macaddress": "74ACB9*" + } ], "ssdp": [ { diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index a2193307b1876f..5b8e1545406097 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -604,6 +604,10 @@ "domain": "unifiprotect", "macaddress": "265A4C*" }, + { + "domain": "unifiprotect", + "macaddress": "74ACB9*" + }, { "domain": "verisure", "macaddress": "0023C1*" From f8fec3d9900f48fd1b10c36e661733e747ac356e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 11 Feb 2022 16:26:39 -0600 Subject: [PATCH 0550/1098] Add additional oui to blink (#66348) --- homeassistant/components/blink/manifest.json | 6 +++++- homeassistant/generated/dhcp.py | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/blink/manifest.json b/homeassistant/components/blink/manifest.json index 8282795a8e9873..58e2d3a1b52279 100644 --- a/homeassistant/components/blink/manifest.json +++ b/homeassistant/components/blink/manifest.json @@ -12,7 +12,11 @@ { "hostname": "blink*", "macaddress": "00037F*" - } + }, + { + "hostname": "blink*", + "macaddress": "20A171*" + } ], "config_flow": true, "iot_class": "cloud_polling", diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 5b8e1545406097..a3d29f3107d219 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -51,6 +51,11 @@ "hostname": "blink*", "macaddress": "00037F*" }, + { + "domain": "blink", + "hostname": "blink*", + "macaddress": "20A171*" + }, { "domain": "broadlink", "macaddress": "34EA34*" From cc0fb5d9dbaa04138e346853c1ffbf084661f4f8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 11 Feb 2022 16:27:00 -0600 Subject: [PATCH 0551/1098] Add dhcp discovery to Sensibo for non-HomeKit devices (#66350) --- homeassistant/components/sensibo/manifest.json | 3 +++ homeassistant/generated/dhcp.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/homeassistant/components/sensibo/manifest.json b/homeassistant/components/sensibo/manifest.json index bb9d9ad7569eda..71fd3e1c241da2 100644 --- a/homeassistant/components/sensibo/manifest.json +++ b/homeassistant/components/sensibo/manifest.json @@ -9,5 +9,8 @@ "homekit": { "models": ["Sensibo"] }, + "dhcp": [ + {"hostname":"sensibo*"} + ], "loggers": ["pysensibo"] } diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index a3d29f3107d219..2fbefe9bbca2ec 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -312,6 +312,10 @@ "domain": "senseme", "macaddress": "20F85E*" }, + { + "domain": "sensibo", + "hostname": "sensibo*" + }, { "domain": "simplisafe", "hostname": "simplisafe*", From 2ffb46dc93b38a86de64a342c25e526fcb95e2c1 Mon Sep 17 00:00:00 2001 From: Igor Pakhomov Date: Sat, 12 Feb 2022 00:28:22 +0200 Subject: [PATCH 0552/1098] Initial xiaomi_miio support for dmaker.airfresh.a1/t2017 (#66331) * Initial support for dmaker.airfresh.a1/t2017 * fix typo --- .../components/xiaomi_miio/__init__.py | 10 +- homeassistant/components/xiaomi_miio/const.py | 8 ++ homeassistant/components/xiaomi_miio/fan.py | 96 +++++++++++++++++++ .../components/xiaomi_miio/number.py | 6 ++ .../components/xiaomi_miio/sensor.py | 13 ++- .../components/xiaomi_miio/switch.py | 6 ++ 6 files changed, 137 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index 63fc31722535ba..2849b249762c16 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -8,6 +8,8 @@ import async_timeout from miio import ( AirFresh, + AirFreshA1, + AirFreshT2017, AirHumidifier, AirHumidifierMiot, AirHumidifierMjjsq, @@ -48,6 +50,8 @@ DOMAIN, KEY_COORDINATOR, KEY_DEVICE, + MODEL_AIRFRESH_A1, + MODEL_AIRFRESH_T2017, MODEL_AIRPURIFIER_3C, MODEL_FAN_1C, MODEL_FAN_P5, @@ -310,7 +314,7 @@ async def async_create_miio_device_and_coordinator( device = AirHumidifier(host, token, model=model) migrate = True # Airpurifiers and Airfresh - elif model in MODEL_AIRPURIFIER_3C: + elif model == MODEL_AIRPURIFIER_3C: device = AirPurifierMB4(host, token) elif model in MODELS_PURIFIER_MIOT: device = AirPurifierMiot(host, token) @@ -318,6 +322,10 @@ async def async_create_miio_device_and_coordinator( device = AirPurifier(host, token) elif model.startswith("zhimi.airfresh."): device = AirFresh(host, token) + elif model == MODEL_AIRFRESH_A1: + device = AirFreshA1(host, token) + elif model == MODEL_AIRFRESH_T2017: + device = AirFreshT2017(host, token) elif ( model in MODELS_VACUUM or model.startswith(ROBOROCK_GENERIC) diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index b361e8ba1b3069..cc607b3f419015 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -75,7 +75,9 @@ class SetupException(Exception): MODEL_AIRHUMIDIFIER_JSQ1 = "deerma.humidifier.jsq1" MODEL_AIRHUMIDIFIER_MJJSQ = "deerma.humidifier.mjjsq" +MODEL_AIRFRESH_A1 = "dmaker.airfresh.a1" MODEL_AIRFRESH_VA2 = "zhimi.airfresh.va2" +MODEL_AIRFRESH_T2017 = "dmaker.airfresh.t2017" MODEL_FAN_1C = "dmaker.fan.1c" MODEL_FAN_P10 = "dmaker.fan.p10" @@ -129,7 +131,9 @@ class SetupException(Exception): MODEL_AIRPURIFIER_SA2, MODEL_AIRPURIFIER_2S, MODEL_AIRPURIFIER_2H, + MODEL_AIRFRESH_A1, MODEL_AIRFRESH_VA2, + MODEL_AIRFRESH_T2017, ] MODELS_HUMIDIFIER_MIIO = [ MODEL_AIRHUMIDIFIER_V1, @@ -383,6 +387,8 @@ class SetupException(Exception): | FEATURE_SET_CLEAN ) +FEATURE_FLAGS_AIRFRESH_A1 = FEATURE_SET_BUZZER | FEATURE_SET_CHILD_LOCK + FEATURE_FLAGS_AIRFRESH = ( FEATURE_SET_BUZZER | FEATURE_SET_CHILD_LOCK @@ -392,6 +398,8 @@ class SetupException(Exception): | FEATURE_SET_EXTRA_FEATURES ) +FEATURE_FLAGS_AIRFRESH_T2017 = FEATURE_SET_BUZZER | FEATURE_SET_CHILD_LOCK + FEATURE_FLAGS_FAN_P5 = ( FEATURE_SET_BUZZER | FEATURE_SET_CHILD_LOCK diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index a12a8a6063bb50..1337aa05895d7a 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -5,6 +5,7 @@ import math from miio.airfresh import OperationMode as AirfreshOperationMode +from miio.airfresh_t2017 import OperationMode as AirfreshOperationModeT2017 from miio.airpurifier import OperationMode as AirpurifierOperationMode from miio.airpurifier_miot import OperationMode as AirpurifierMiotOperationMode from miio.fan import ( @@ -39,6 +40,8 @@ CONF_FLOW_TYPE, DOMAIN, FEATURE_FLAGS_AIRFRESH, + FEATURE_FLAGS_AIRFRESH_A1, + FEATURE_FLAGS_AIRFRESH_T2017, FEATURE_FLAGS_AIRPURIFIER_2S, FEATURE_FLAGS_AIRPURIFIER_3C, FEATURE_FLAGS_AIRPURIFIER_MIIO, @@ -56,6 +59,8 @@ FEATURE_SET_EXTRA_FEATURES, KEY_COORDINATOR, KEY_DEVICE, + MODEL_AIRFRESH_A1, + MODEL_AIRFRESH_T2017, MODEL_AIRPURIFIER_2H, MODEL_AIRPURIFIER_2S, MODEL_AIRPURIFIER_3C, @@ -97,6 +102,9 @@ ATTR_USE_TIME = "use_time" ATTR_BUTTON_PRESSED = "button_pressed" +# Air Fresh A1 +ATTR_FAVORITE_SPEED = "favorite_speed" + # Map attributes to properties of the state object AVAILABLE_ATTRIBUTES_AIRPURIFIER_COMMON = { ATTR_EXTRA_FEATURES: "extra_features", @@ -153,6 +161,7 @@ "Strong", ] PRESET_MODES_AIRFRESH = ["Auto", "Interval"] +PRESET_MODES_AIRFRESH_A1 = ["Auto", "Sleep", "Favorite"] AIRPURIFIER_SERVICE_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.entity_ids}) @@ -213,6 +222,10 @@ async def async_setup_entry( entity = XiaomiAirPurifier(name, device, config_entry, unique_id, coordinator) elif model.startswith("zhimi.airfresh."): entity = XiaomiAirFresh(name, device, config_entry, unique_id, coordinator) + elif model == MODEL_AIRFRESH_A1: + entity = XiaomiAirFreshA1(name, device, config_entry, unique_id, coordinator) + elif model == MODEL_AIRFRESH_T2017: + entity = XiaomiAirFreshT2017(name, device, config_entry, unique_id, coordinator) elif model == MODEL_FAN_P5: entity = XiaomiFanP5(name, device, config_entry, unique_id, coordinator) elif model in MODELS_FAN_MIIO: @@ -709,6 +722,89 @@ async def async_reset_filter(self): ) +class XiaomiAirFreshA1(XiaomiGenericAirPurifier): + """Representation of a Xiaomi Air Fresh A1.""" + + def __init__(self, name, device, entry, unique_id, coordinator): + """Initialize the miio device.""" + super().__init__(name, device, entry, unique_id, coordinator) + self._favorite_speed = None + self._device_features = FEATURE_FLAGS_AIRFRESH_A1 + self._preset_modes = PRESET_MODES_AIRFRESH_A1 + self._supported_features = SUPPORT_SET_SPEED | SUPPORT_PRESET_MODE + + self._state = self.coordinator.data.is_on + self._mode = self.coordinator.data.mode.value + self._speed_range = (60, 150) + + @property + def operation_mode_class(self): + """Hold operation mode class.""" + return AirfreshOperationModeT2017 + + @property + def percentage(self): + """Return the current percentage based speed.""" + if self._favorite_speed is None: + return None + if self._state: + return ranged_value_to_percentage(self._speed_range, self._favorite_speed) + + return None + + async def async_set_percentage(self, percentage: int) -> None: + """Set the percentage of the fan. This method is a coroutine.""" + if percentage == 0: + await self.async_turn_off() + return + + await self.async_set_preset_mode("Favorite") + + favorite_speed = math.ceil( + percentage_to_ranged_value(self._speed_range, percentage) + ) + if not favorite_speed: + return + if await self._try_command( + "Setting fan level of the miio device failed.", + self._device.set_favorite_speed, + favorite_speed, + ): + self._favorite_speed = favorite_speed + self.async_write_ha_state() + + async def async_set_preset_mode(self, preset_mode: str) -> None: + """Set the preset mode of the fan. This method is a coroutine.""" + if preset_mode not in self.preset_modes: + _LOGGER.warning("'%s'is not a valid preset mode", preset_mode) + return + if await self._try_command( + "Setting operation mode of the miio device failed.", + self._device.set_mode, + self.operation_mode_class[preset_mode], + ): + self._mode = self.operation_mode_class[preset_mode].value + self.async_write_ha_state() + + @callback + def _handle_coordinator_update(self): + """Fetch state from the device.""" + self._state = self.coordinator.data.is_on + self._mode = self.coordinator.data.mode.value + self._favorite_speed = getattr(self.coordinator.data, ATTR_FAVORITE_SPEED, None) + self.async_write_ha_state() + + +class XiaomiAirFreshT2017(XiaomiAirFreshA1): + """Representation of a Xiaomi Air Fresh T2017.""" + + def __init__(self, name, device, entry, unique_id, coordinator): + """Initialize the miio device.""" + super().__init__(name, device, entry, unique_id, coordinator) + self._device_features = FEATURE_FLAGS_AIRFRESH_T2017 + self._speed_range = (60, 300) + + class XiaomiGenericFan(XiaomiGenericDevice): """Representation of a generic Xiaomi Fan.""" diff --git a/homeassistant/components/xiaomi_miio/number.py b/homeassistant/components/xiaomi_miio/number.py index 8939200d1073c1..4ffc773d1c11e6 100644 --- a/homeassistant/components/xiaomi_miio/number.py +++ b/homeassistant/components/xiaomi_miio/number.py @@ -18,6 +18,8 @@ CONF_MODEL, DOMAIN, FEATURE_FLAGS_AIRFRESH, + FEATURE_FLAGS_AIRFRESH_A1, + FEATURE_FLAGS_AIRFRESH_T2017, FEATURE_FLAGS_AIRHUMIDIFIER_CA4, FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB, FEATURE_FLAGS_AIRPURIFIER_2S, @@ -45,6 +47,8 @@ FEATURE_SET_VOLUME, KEY_COORDINATOR, KEY_DEVICE, + MODEL_AIRFRESH_A1, + MODEL_AIRFRESH_T2017, MODEL_AIRFRESH_VA2, MODEL_AIRHUMIDIFIER_CA1, MODEL_AIRHUMIDIFIER_CA4, @@ -199,7 +203,9 @@ class OscillationAngleValues: } MODEL_TO_FEATURES_MAP = { + MODEL_AIRFRESH_A1: FEATURE_FLAGS_AIRFRESH_A1, MODEL_AIRFRESH_VA2: FEATURE_FLAGS_AIRFRESH, + MODEL_AIRFRESH_T2017: FEATURE_FLAGS_AIRFRESH_T2017, MODEL_AIRHUMIDIFIER_CA1: FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB, MODEL_AIRHUMIDIFIER_CA4: FEATURE_FLAGS_AIRHUMIDIFIER_CA4, MODEL_AIRHUMIDIFIER_CB1: FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB, diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index d6d1c8500ed41e..6095bfe5647f1f 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -52,6 +52,8 @@ DOMAIN, KEY_COORDINATOR, KEY_DEVICE, + MODEL_AIRFRESH_A1, + MODEL_AIRFRESH_T2017, MODEL_AIRFRESH_VA2, MODEL_AIRHUMIDIFIER_CA1, MODEL_AIRHUMIDIFIER_CB1, @@ -375,6 +377,14 @@ class XiaomiMiioSensorDescription(SensorEntityDescription): ATTR_TEMPERATURE, ATTR_USE_TIME, ) +AIRFRESH_SENSORS_A1 = ( + ATTR_CARBON_DIOXIDE, + ATTR_TEMPERATURE, +) +AIRFRESH_SENSORS_T2017 = ( + ATTR_CARBON_DIOXIDE, + ATTR_TEMPERATURE, +) FAN_V2_V3_SENSORS = ( ATTR_BATTERY, ATTR_HUMIDITY, @@ -384,7 +394,9 @@ class XiaomiMiioSensorDescription(SensorEntityDescription): FAN_ZA5_SENSORS = (ATTR_HUMIDITY, ATTR_TEMPERATURE) MODEL_TO_SENSORS_MAP = { + MODEL_AIRFRESH_A1: AIRFRESH_SENSORS_A1, MODEL_AIRFRESH_VA2: AIRFRESH_SENSORS, + MODEL_AIRFRESH_T2017: AIRFRESH_SENSORS_T2017, MODEL_AIRHUMIDIFIER_CA1: HUMIDIFIER_CA1_CB1_SENSORS, MODEL_AIRHUMIDIFIER_CB1: HUMIDIFIER_CA1_CB1_SENSORS, MODEL_AIRPURIFIER_3C: PURIFIER_3C_SENSORS, @@ -808,7 +820,6 @@ class XiaomiGatewayIlluminanceSensor(SensorEntity): def __init__(self, gateway_device, gateway_name, gateway_device_id, description): """Initialize the entity.""" - self._attr_name = f"{gateway_name} {description.name}" self._attr_unique_id = f"{gateway_device_id}-{description.key}" self._attr_device_info = {"identifiers": {(DOMAIN, gateway_device_id)}} diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index bd6482e891b7c4..ce05a891c0ac71 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -35,6 +35,8 @@ CONF_MODEL, DOMAIN, FEATURE_FLAGS_AIRFRESH, + FEATURE_FLAGS_AIRFRESH_A1, + FEATURE_FLAGS_AIRFRESH_T2017, FEATURE_FLAGS_AIRHUMIDIFIER, FEATURE_FLAGS_AIRHUMIDIFIER_CA4, FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB, @@ -63,6 +65,8 @@ FEATURE_SET_LED, KEY_COORDINATOR, KEY_DEVICE, + MODEL_AIRFRESH_A1, + MODEL_AIRFRESH_T2017, MODEL_AIRFRESH_VA2, MODEL_AIRHUMIDIFIER_CA1, MODEL_AIRHUMIDIFIER_CA4, @@ -167,7 +171,9 @@ } MODEL_TO_FEATURES_MAP = { + MODEL_AIRFRESH_A1: FEATURE_FLAGS_AIRFRESH_A1, MODEL_AIRFRESH_VA2: FEATURE_FLAGS_AIRFRESH, + MODEL_AIRFRESH_T2017: FEATURE_FLAGS_AIRFRESH_T2017, MODEL_AIRHUMIDIFIER_CA1: FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB, MODEL_AIRHUMIDIFIER_CA4: FEATURE_FLAGS_AIRHUMIDIFIER_CA4, MODEL_AIRHUMIDIFIER_CB1: FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB, From 9134e5c8444b9c61377905bee564f67a566a6067 Mon Sep 17 00:00:00 2001 From: Joshua Roys Date: Fri, 11 Feb 2022 17:46:17 -0500 Subject: [PATCH 0553/1098] Get discovered zeroconf IPv6 addresses (#65462) --- .../components/homekit_controller/config_flow.py | 1 + homeassistant/components/zeroconf/__init__.py | 2 ++ tests/components/apple_tv/test_config_flow.py | 13 +++++++++++++ tests/components/axis/test_config_flow.py | 5 +++++ tests/components/axis/test_device.py | 1 + tests/components/bond/test_config_flow.py | 8 ++++++++ tests/components/bosch_shc/test_config_flow.py | 2 ++ tests/components/brother/test_config_flow.py | 5 +++++ tests/components/daikin/test_config_flow.py | 1 + tests/components/devolo_home_control/const.py | 3 +++ tests/components/devolo_home_network/const.py | 2 ++ tests/components/doorbird/test_config_flow.py | 4 ++++ tests/components/elgato/test_config_flow.py | 4 ++++ tests/components/enphase_envoy/test_config_flow.py | 3 +++ tests/components/esphome/test_config_flow.py | 6 ++++++ tests/components/forked_daapd/test_config_flow.py | 6 ++++++ tests/components/freebox/test_config_flow.py | 1 + tests/components/gogogate2/test_config_flow.py | 5 +++++ tests/components/guardian/test_config_flow.py | 2 ++ tests/components/homekit_controller/common.py | 1 + .../homekit_controller/test_config_flow.py | 1 + tests/components/homewizard/test_config_flow.py | 4 ++++ tests/components/hue/test_config_flow.py | 4 ++++ .../hunterdouglas_powerview/test_config_flow.py | 2 ++ tests/components/ipp/__init__.py | 2 ++ tests/components/kodi/util.py | 2 ++ tests/components/lookin/__init__.py | 1 + tests/components/lutron_caseta/test_config_flow.py | 4 ++++ tests/components/modern_forms/test_config_flow.py | 4 ++++ tests/components/nam/test_config_flow.py | 1 + tests/components/nanoleaf/test_config_flow.py | 2 ++ tests/components/netatmo/test_config_flow.py | 1 + tests/components/nut/test_config_flow.py | 1 + tests/components/octoprint/test_config_flow.py | 2 ++ tests/components/overkiz/test_config_flow.py | 1 + tests/components/plugwise/test_config_flow.py | 2 ++ tests/components/rachio/test_config_flow.py | 2 ++ tests/components/rainmachine/test_config_flow.py | 5 +++++ tests/components/roku/__init__.py | 1 + tests/components/samsungtv/test_config_flow.py | 1 + tests/components/shelly/test_config_flow.py | 1 + tests/components/smappee/test_config_flow.py | 8 ++++++++ tests/components/sonos/conftest.py | 1 + tests/components/sonos/test_config_flow.py | 1 + tests/components/spotify/test_config_flow.py | 1 + tests/components/system_bridge/test_config_flow.py | 2 ++ tests/components/tado/test_config_flow.py | 2 ++ tests/components/tradfri/test_config_flow.py | 5 +++++ tests/components/vizio/const.py | 1 + tests/components/volumio/test_config_flow.py | 1 + tests/components/wled/test_config_flow.py | 5 +++++ tests/components/xiaomi_aqara/test_config_flow.py | 3 +++ tests/components/xiaomi_miio/test_config_flow.py | 5 +++++ tests/components/yeelight/__init__.py | 1 + tests/components/yeelight/test_config_flow.py | 3 +++ tests/components/zeroconf/test_init.py | 1 + tests/components/zha/test_config_flow.py | 4 ++++ 57 files changed, 163 insertions(+) diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index 9053c79b939695..3784e2830d7d99 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -162,6 +162,7 @@ async def async_step_unignore(self, user_input): return await self.async_step_zeroconf( zeroconf.ZeroconfServiceInfo( host=record["address"], + addresses=[record["address"]], port=record["port"], hostname=record["name"], type="_hap._tcp.local.", diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index 1dc70cde610d26..78ad9b25cd7051 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -101,6 +101,7 @@ class ZeroconfServiceInfo(BaseServiceInfo): """Prepared info from mDNS entries.""" host: str + addresses: list[str] port: int | None hostname: str type: str @@ -547,6 +548,7 @@ def info_from_service(service: AsyncServiceInfo) -> ZeroconfServiceInfo | None: return ZeroconfServiceInfo( host=str(host), + addresses=service.parsed_addresses(), port=service.port, hostname=service.server, type=service.type, diff --git a/tests/components/apple_tv/test_config_flow.py b/tests/components/apple_tv/test_config_flow.py index b4811e57739892..ca617026d94a50 100644 --- a/tests/components/apple_tv/test_config_flow.py +++ b/tests/components/apple_tv/test_config_flow.py @@ -22,6 +22,7 @@ DMAP_SERVICE = zeroconf.ZeroconfServiceInfo( host="127.0.0.1", + addresses=["127.0.0.1"], hostname="mock_hostname", port=None, type="_touch-able._tcp.local.", @@ -32,6 +33,7 @@ RAOP_SERVICE = zeroconf.ZeroconfServiceInfo( host="127.0.0.1", + addresses=["127.0.0.1"], hostname="mock_hostname", port=None, type="_raop._tcp.local.", @@ -531,6 +533,7 @@ async def test_zeroconf_unsupported_service_aborts(hass): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="127.0.0.1", + addresses=["127.0.0.1"], hostname="mock_hostname", name="mock_name", port=None, @@ -549,6 +552,7 @@ async def test_zeroconf_add_mrp_device(hass, mrp_device, pairing): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="127.0.0.2", + addresses=["127.0.0.2"], hostname="mock_hostname", port=None, name="Kitchen", @@ -563,6 +567,7 @@ async def test_zeroconf_add_mrp_device(hass, mrp_device, pairing): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="127.0.0.1", + addresses=["127.0.0.1"], hostname="mock_hostname", port=None, name="Kitchen", @@ -750,6 +755,7 @@ async def test_zeroconf_abort_if_other_in_progress(hass, mock_scan): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="127.0.0.1", + addresses=["127.0.0.1"], hostname="mock_hostname", port=None, type="_airplay._tcp.local.", @@ -772,6 +778,7 @@ async def test_zeroconf_abort_if_other_in_progress(hass, mock_scan): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="127.0.0.1", + addresses=["127.0.0.1"], hostname="mock_hostname", port=None, type="_mediaremotetv._tcp.local.", @@ -797,6 +804,7 @@ async def test_zeroconf_missing_device_during_protocol_resolve( context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="127.0.0.1", + addresses=["127.0.0.1"], hostname="mock_hostname", port=None, type="_airplay._tcp.local.", @@ -818,6 +826,7 @@ async def test_zeroconf_missing_device_during_protocol_resolve( context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="127.0.0.1", + addresses=["127.0.0.1"], hostname="mock_hostname", port=None, type="_mediaremotetv._tcp.local.", @@ -853,6 +862,7 @@ async def test_zeroconf_additional_protocol_resolve_failure( context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="127.0.0.1", + addresses=["127.0.0.1"], hostname="mock_hostname", port=None, type="_airplay._tcp.local.", @@ -874,6 +884,7 @@ async def test_zeroconf_additional_protocol_resolve_failure( context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="127.0.0.1", + addresses=["127.0.0.1"], hostname="mock_hostname", port=None, type="_mediaremotetv._tcp.local.", @@ -911,6 +922,7 @@ async def test_zeroconf_pair_additionally_found_protocols( context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="127.0.0.1", + addresses=["127.0.0.1"], hostname="mock_hostname", port=None, type="_airplay._tcp.local.", @@ -953,6 +965,7 @@ async def test_zeroconf_pair_additionally_found_protocols( context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="127.0.0.1", + addresses=["127.0.0.1"], hostname="mock_hostname", port=None, type="_mediaremotetv._tcp.local.", diff --git a/tests/components/axis/test_config_flow.py b/tests/components/axis/test_config_flow.py index bc03cb99f07b26..112fa57fa6461c 100644 --- a/tests/components/axis/test_config_flow.py +++ b/tests/components/axis/test_config_flow.py @@ -298,6 +298,7 @@ async def test_reauth_flow_update_configuration(hass): SOURCE_ZEROCONF, zeroconf.ZeroconfServiceInfo( host=DEFAULT_HOST, + addresses=[DEFAULT_HOST], port=80, hostname=f"axis-{MAC.lower()}.local.", type="_axis-video._tcp.local.", @@ -376,6 +377,7 @@ async def test_discovery_flow(hass, source: str, discovery_info: dict): SOURCE_ZEROCONF, zeroconf.ZeroconfServiceInfo( host=DEFAULT_HOST, + addresses=[DEFAULT_HOST], hostname="mock_hostname", name=f"AXIS M1065-LW - {MAC}._axis-video._tcp.local.", port=80, @@ -430,6 +432,7 @@ async def test_discovered_device_already_configured( SOURCE_ZEROCONF, zeroconf.ZeroconfServiceInfo( host="2.3.4.5", + addresses=["2.3.4.5"], hostname="mock_hostname", name=f"AXIS M1065-LW - {MAC}._axis-video._tcp.local.", port=8080, @@ -504,6 +507,7 @@ async def test_discovery_flow_updated_configuration( SOURCE_ZEROCONF, zeroconf.ZeroconfServiceInfo( host="", + addresses=[""], hostname="mock_hostname", name="", port=0, @@ -552,6 +556,7 @@ async def test_discovery_flow_ignore_non_axis_device( SOURCE_ZEROCONF, zeroconf.ZeroconfServiceInfo( host="169.254.3.4", + addresses=["169.254.3.4"], hostname="mock_hostname", name=f"AXIS M1065-LW - {MAC}._axis-video._tcp.local.", port=80, diff --git a/tests/components/axis/test_device.py b/tests/components/axis/test_device.py index d43845e01adbaa..cca62babbb59d6 100644 --- a/tests/components/axis/test_device.py +++ b/tests/components/axis/test_device.py @@ -385,6 +385,7 @@ async def test_update_address(hass): AXIS_DOMAIN, data=zeroconf.ZeroconfServiceInfo( host="2.3.4.5", + addresses=["2.3.4.5"], hostname="mock_hostname", name="name", port=80, diff --git a/tests/components/bond/test_config_flow.py b/tests/components/bond/test_config_flow.py index b36637897d8b78..5d3b357b9f74ca 100644 --- a/tests/components/bond/test_config_flow.py +++ b/tests/components/bond/test_config_flow.py @@ -201,6 +201,7 @@ async def test_zeroconf_form(hass: core.HomeAssistant): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="test-host", + addresses=["test-host"], hostname="mock_hostname", name="test-bond-id.some-other-tail-info", port=None, @@ -238,6 +239,7 @@ async def test_zeroconf_form_token_unavailable(hass: core.HomeAssistant): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="test-host", + addresses=["test-host"], hostname="mock_hostname", name="test-bond-id.some-other-tail-info", port=None, @@ -278,6 +280,7 @@ async def test_zeroconf_form_with_token_available(hass: core.HomeAssistant): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="test-host", + addresses=["test-host"], hostname="mock_hostname", name="test-bond-id.some-other-tail-info", port=None, @@ -318,6 +321,7 @@ async def test_zeroconf_form_with_token_available_name_unavailable( context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="test-host", + addresses=["test-host"], hostname="mock_hostname", name="test-bond-id.some-other-tail-info", port=None, @@ -361,6 +365,7 @@ async def test_zeroconf_already_configured(hass: core.HomeAssistant): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="updated-host", + addresses=["updated-host"], hostname="mock_hostname", name="already-registered-bond-id.some-other-tail-info", port=None, @@ -405,6 +410,7 @@ async def test_zeroconf_already_configured_refresh_token(hass: core.HomeAssistan context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="updated-host", + addresses=["updated-host"], hostname="mock_hostname", name="already-registered-bond-id.some-other-tail-info", port=None, @@ -442,6 +448,7 @@ async def test_zeroconf_already_configured_no_reload_same_host( context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="stored-host", + addresses=["stored-host"], hostname="mock_hostname", name="already-registered-bond-id.some-other-tail-info", port=None, @@ -463,6 +470,7 @@ async def test_zeroconf_form_unexpected_error(hass: core.HomeAssistant): source=config_entries.SOURCE_ZEROCONF, initial_input=zeroconf.ZeroconfServiceInfo( host="test-host", + addresses=["test-host"], hostname="mock_hostname", name="test-bond-id.some-other-tail-info", port=None, diff --git a/tests/components/bosch_shc/test_config_flow.py b/tests/components/bosch_shc/test_config_flow.py index 065035bedbe777..e7c5e6d7d4d971 100644 --- a/tests/components/bosch_shc/test_config_flow.py +++ b/tests/components/bosch_shc/test_config_flow.py @@ -22,6 +22,7 @@ } DISCOVERY_INFO = zeroconf.ZeroconfServiceInfo( host="1.1.1.1", + addresses=["1.1.1.1"], hostname="shc012345.local.", name="Bosch SHC [test-mac]._http._tcp.local.", port=0, @@ -533,6 +534,7 @@ async def test_zeroconf_not_bosch_shc(hass, mock_zeroconf): DOMAIN, data=zeroconf.ZeroconfServiceInfo( host="1.1.1.1", + addresses=["1.1.1.1"], hostname="mock_hostname", name="notboschshc", port=None, diff --git a/tests/components/brother/test_config_flow.py b/tests/components/brother/test_config_flow.py index 3bc7738bb8df26..6dbaebdfa7bcb5 100644 --- a/tests/components/brother/test_config_flow.py +++ b/tests/components/brother/test_config_flow.py @@ -146,6 +146,7 @@ async def test_zeroconf_snmp_error(hass): context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="mock_host", + addresses=["mock_host"], hostname="example.local.", name="Brother Printer", port=None, @@ -166,6 +167,7 @@ async def test_zeroconf_unsupported_model(hass): context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="mock_host", + addresses=["mock_host"], hostname="example.local.", name="Brother Printer", port=None, @@ -194,6 +196,7 @@ async def test_zeroconf_device_exists_abort(hass): context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="mock_host", + addresses=["mock_host"], hostname="example.local.", name="Brother Printer", port=None, @@ -216,6 +219,7 @@ async def test_zeroconf_no_probe_existing_device(hass): context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="mock_host", + addresses=["mock_host"], hostname="localhost", name="Brother Printer", port=None, @@ -242,6 +246,7 @@ async def test_zeroconf_confirm_create_entry(hass): context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="mock_host", + addresses=["mock_host"], hostname="example.local.", name="Brother Printer", port=None, diff --git a/tests/components/daikin/test_config_flow.py b/tests/components/daikin/test_config_flow.py index 5421f6e1d52be4..3a6a56fe097876 100644 --- a/tests/components/daikin/test_config_flow.py +++ b/tests/components/daikin/test_config_flow.py @@ -124,6 +124,7 @@ async def test_api_password_abort(hass): SOURCE_ZEROCONF, zeroconf.ZeroconfServiceInfo( host=HOST, + addresses=[HOST], hostname="mock_hostname", name="mock_name", port=None, diff --git a/tests/components/devolo_home_control/const.py b/tests/components/devolo_home_control/const.py index 96686e204eb4eb..96090195d20b77 100644 --- a/tests/components/devolo_home_control/const.py +++ b/tests/components/devolo_home_control/const.py @@ -4,6 +4,7 @@ DISCOVERY_INFO = zeroconf.ZeroconfServiceInfo( host="192.168.0.1", + addresses=["192.168.0.1"], port=14791, hostname="test.local.", type="_dvl-deviceapi._tcp.local.", @@ -21,6 +22,7 @@ DISCOVERY_INFO_WRONG_DEVOLO_DEVICE = zeroconf.ZeroconfServiceInfo( host="mock_host", + addresses=["mock_host"], hostname="mock_hostname", name="mock_name", port=None, @@ -30,6 +32,7 @@ DISCOVERY_INFO_WRONG_DEVICE = zeroconf.ZeroconfServiceInfo( host="mock_host", + addresses=["mock_host"], hostname="mock_hostname", name="mock_name", port=None, diff --git a/tests/components/devolo_home_network/const.py b/tests/components/devolo_home_network/const.py index 681c2673dff359..0e48833a78b1af 100644 --- a/tests/components/devolo_home_network/const.py +++ b/tests/components/devolo_home_network/const.py @@ -18,6 +18,7 @@ DISCOVERY_INFO = zeroconf.ZeroconfServiceInfo( host=IP, + addresses=[IP], port=14791, hostname="test.local.", type="_dvl-deviceapi._tcp.local.", @@ -38,6 +39,7 @@ DISCOVERY_INFO_WRONG_DEVICE = zeroconf.ZeroconfServiceInfo( host="mock_host", + addresses=["mock_host"], hostname="mock_hostname", name="mock_name", port=None, diff --git a/tests/components/doorbird/test_config_flow.py b/tests/components/doorbird/test_config_flow.py index ccb05b327ac560..10cc4a4fb778b5 100644 --- a/tests/components/doorbird/test_config_flow.py +++ b/tests/components/doorbird/test_config_flow.py @@ -84,6 +84,7 @@ async def test_form_zeroconf_wrong_oui(hass): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.8", + addresses=["192.168.1.8"], hostname="mock_hostname", name="Doorstation - abc123._axis-video._tcp.local.", port=None, @@ -103,6 +104,7 @@ async def test_form_zeroconf_link_local_ignored(hass): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="169.254.103.61", + addresses=["169.254.103.61"], hostname="mock_hostname", name="Doorstation - abc123._axis-video._tcp.local.", port=None, @@ -129,6 +131,7 @@ async def test_form_zeroconf_correct_oui(hass): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.5", + addresses=["192.168.1.5"], hostname="mock_hostname", name="Doorstation - abc123._axis-video._tcp.local.", port=None, @@ -191,6 +194,7 @@ async def test_form_zeroconf_correct_oui_wrong_device(hass, doorbell_state_side_ context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.5", + addresses=["192.168.1.5"], hostname="mock_hostname", name="Doorstation - abc123._axis-video._tcp.local.", port=None, diff --git a/tests/components/elgato/test_config_flow.py b/tests/components/elgato/test_config_flow.py index dffd59cedcc459..fdc0ad834d13d8 100644 --- a/tests/components/elgato/test_config_flow.py +++ b/tests/components/elgato/test_config_flow.py @@ -61,6 +61,7 @@ async def test_full_zeroconf_flow_implementation( context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="127.0.0.1", + addresses=["127.0.0.1"], hostname="example.local.", name="mock_name", port=9123, @@ -126,6 +127,7 @@ async def test_zeroconf_connection_error( context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="127.0.0.1", + addresses=["127.0.0.1"], hostname="mock_hostname", name="mock_name", port=9123, @@ -167,6 +169,7 @@ async def test_zeroconf_device_exists_abort( context={CONF_SOURCE: SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="127.0.0.1", + addresses=["127.0.0.1"], hostname="mock_hostname", name="mock_name", port=9123, @@ -187,6 +190,7 @@ async def test_zeroconf_device_exists_abort( context={CONF_SOURCE: SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="127.0.0.2", + addresses=["127.0.0.2"], hostname="mock_hostname", name="mock_name", port=9123, diff --git a/tests/components/enphase_envoy/test_config_flow.py b/tests/components/enphase_envoy/test_config_flow.py index d8b23ac9864d0f..76179c02e22fe9 100644 --- a/tests/components/enphase_envoy/test_config_flow.py +++ b/tests/components/enphase_envoy/test_config_flow.py @@ -212,6 +212,7 @@ async def test_zeroconf(hass: HomeAssistant) -> None: context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="1.1.1.1", + addresses=["1.1.1.1"], hostname="mock_hostname", name="mock_name", port=None, @@ -312,6 +313,7 @@ async def test_zeroconf_serial_already_exists(hass: HomeAssistant) -> None: context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="1.1.1.1", + addresses=["1.1.1.1"], hostname="mock_hostname", name="mock_name", port=None, @@ -351,6 +353,7 @@ async def test_zeroconf_host_already_exists(hass: HomeAssistant) -> None: context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="1.1.1.1", + addresses=["1.1.1.1"], hostname="mock_hostname", name="mock_name", port=None, diff --git a/tests/components/esphome/test_config_flow.py b/tests/components/esphome/test_config_flow.py index 25103c4fe2a3a4..f7da5d66bd5e86 100644 --- a/tests/components/esphome/test_config_flow.py +++ b/tests/components/esphome/test_config_flow.py @@ -218,6 +218,7 @@ async def test_discovery_initiation(hass, mock_client, mock_zeroconf): service_info = zeroconf.ZeroconfServiceInfo( host="192.168.43.183", + addresses=["192.168.43.183"], hostname="test8266.local.", name="mock_name", port=6053, @@ -252,6 +253,7 @@ async def test_discovery_already_configured_hostname(hass, mock_client): service_info = zeroconf.ZeroconfServiceInfo( host="192.168.43.183", + addresses=["192.168.43.183"], hostname="test8266.local.", name="mock_name", port=6053, @@ -279,6 +281,7 @@ async def test_discovery_already_configured_ip(hass, mock_client): service_info = zeroconf.ZeroconfServiceInfo( host="192.168.43.183", + addresses=["192.168.43.183"], hostname="test8266.local.", name="mock_name", port=6053, @@ -310,6 +313,7 @@ async def test_discovery_already_configured_name(hass, mock_client): service_info = zeroconf.ZeroconfServiceInfo( host="192.168.43.184", + addresses=["192.168.43.184"], hostname="test8266.local.", name="mock_name", port=6053, @@ -331,6 +335,7 @@ async def test_discovery_duplicate_data(hass, mock_client): """Test discovery aborts if same mDNS packet arrives.""" service_info = zeroconf.ZeroconfServiceInfo( host="192.168.43.183", + addresses=["192.168.43.183"], hostname="test8266.local.", name="mock_name", port=6053, @@ -364,6 +369,7 @@ async def test_discovery_updates_unique_id(hass, mock_client): service_info = zeroconf.ZeroconfServiceInfo( host="192.168.43.183", + addresses=["192.168.43.183"], hostname="test8266.local.", name="mock_name", port=6053, diff --git a/tests/components/forked_daapd/test_config_flow.py b/tests/components/forked_daapd/test_config_flow.py index abd2f1ab3a2048..fd4d82b177c76a 100644 --- a/tests/components/forked_daapd/test_config_flow.py +++ b/tests/components/forked_daapd/test_config_flow.py @@ -101,6 +101,7 @@ async def test_zeroconf_updates_title(hass, config_entry): assert len(hass.config_entries.async_entries(DOMAIN)) == 2 discovery_info = zeroconf.ZeroconfServiceInfo( host="192.168.1.1", + addresses=["192.168.1.1"], hostname="mock_hostname", name="mock_name", port=23, @@ -135,6 +136,7 @@ async def test_config_flow_zeroconf_invalid(hass): # test with no discovery properties discovery_info = zeroconf.ZeroconfServiceInfo( host="127.0.0.1", + addresses=["127.0.0.1"], hostname="mock_hostname", name="mock_name", port=23, @@ -149,6 +151,7 @@ async def test_config_flow_zeroconf_invalid(hass): # test with forked-daapd version < 27 discovery_info = zeroconf.ZeroconfServiceInfo( host="127.0.0.1", + addresses=["127.0.0.1"], hostname="mock_hostname", name="mock_name", port=23, @@ -163,6 +166,7 @@ async def test_config_flow_zeroconf_invalid(hass): # test with verbose mtd-version from Firefly discovery_info = zeroconf.ZeroconfServiceInfo( host="127.0.0.1", + addresses=["127.0.0.1"], hostname="mock_hostname", name="mock_name", port=23, @@ -177,6 +181,7 @@ async def test_config_flow_zeroconf_invalid(hass): # test with svn mtd-version from Firefly discovery_info = zeroconf.ZeroconfServiceInfo( host="127.0.0.1", + addresses=["127.0.0.1"], hostname="mock_hostname", name="mock_name", port=23, @@ -194,6 +199,7 @@ async def test_config_flow_zeroconf_valid(hass): """Test that a valid zeroconf entry works.""" discovery_info = zeroconf.ZeroconfServiceInfo( host="192.168.1.1", + addresses=["192.168.1.1"], hostname="mock_hostname", name="mock_name", port=23, diff --git a/tests/components/freebox/test_config_flow.py b/tests/components/freebox/test_config_flow.py index 3bffd213b7276a..cee1c28cebda88 100644 --- a/tests/components/freebox/test_config_flow.py +++ b/tests/components/freebox/test_config_flow.py @@ -20,6 +20,7 @@ MOCK_ZEROCONF_DATA = zeroconf.ZeroconfServiceInfo( host="192.168.0.254", + addresses=["192.168.0.254"], port=80, hostname="Freebox-Server.local.", type="_fbx-api._tcp.local.", diff --git a/tests/components/gogogate2/test_config_flow.py b/tests/components/gogogate2/test_config_flow.py index a20687c5f3d1e7..713295c0efdcc3 100644 --- a/tests/components/gogogate2/test_config_flow.py +++ b/tests/components/gogogate2/test_config_flow.py @@ -109,6 +109,7 @@ async def test_form_homekit_unique_id_already_setup(hass): context={"source": config_entries.SOURCE_HOMEKIT}, data=zeroconf.ZeroconfServiceInfo( host="1.2.3.4", + addresses=["1.2.3.4"], hostname="mock_hostname", name="mock_name", port=None, @@ -136,6 +137,7 @@ async def test_form_homekit_unique_id_already_setup(hass): context={"source": config_entries.SOURCE_HOMEKIT}, data=zeroconf.ZeroconfServiceInfo( host="1.2.3.4", + addresses=["1.2.3.4"], hostname="mock_hostname", name="mock_name", port=None, @@ -160,6 +162,7 @@ async def test_form_homekit_ip_address_already_setup(hass): context={"source": config_entries.SOURCE_HOMEKIT}, data=zeroconf.ZeroconfServiceInfo( host="1.2.3.4", + addresses=["1.2.3.4"], hostname="mock_hostname", name="mock_name", port=None, @@ -178,6 +181,7 @@ async def test_form_homekit_ip_address(hass): context={"source": config_entries.SOURCE_HOMEKIT}, data=zeroconf.ZeroconfServiceInfo( host="1.2.3.4", + addresses=["1.2.3.4"], hostname="mock_hostname", name="mock_name", port=None, @@ -260,6 +264,7 @@ async def test_discovered_by_homekit_and_dhcp(hass): context={"source": config_entries.SOURCE_HOMEKIT}, data=zeroconf.ZeroconfServiceInfo( host="1.2.3.4", + addresses=["1.2.3.4"], hostname="mock_hostname", name="mock_name", port=None, diff --git a/tests/components/guardian/test_config_flow.py b/tests/components/guardian/test_config_flow.py index fc3157289e9706..a56aa6355e8df4 100644 --- a/tests/components/guardian/test_config_flow.py +++ b/tests/components/guardian/test_config_flow.py @@ -74,6 +74,7 @@ async def test_step_zeroconf(hass, setup_guardian): """Test the zeroconf step.""" zeroconf_data = zeroconf.ZeroconfServiceInfo( host="192.168.1.100", + addresses=["192.168.1.100"], port=7777, hostname="GVC1-ABCD.local.", type="_api._udp.local.", @@ -103,6 +104,7 @@ async def test_step_zeroconf_already_in_progress(hass): """Test the zeroconf step aborting because it's already in progress.""" zeroconf_data = zeroconf.ZeroconfServiceInfo( host="192.168.1.100", + addresses=["192.168.1.100"], port=7777, hostname="GVC1-ABCD.local.", type="_api._udp.local.", diff --git a/tests/components/homekit_controller/common.py b/tests/components/homekit_controller/common.py index 7fae69ee01ba17..8f59fae8639f28 100644 --- a/tests/components/homekit_controller/common.py +++ b/tests/components/homekit_controller/common.py @@ -222,6 +222,7 @@ async def device_config_changed(hass, accessories): discovery_info = zeroconf.ZeroconfServiceInfo( host="127.0.0.1", + addresses=["127.0.0.1"], hostname="mock_hostname", name="TestDevice", port=8080, diff --git a/tests/components/homekit_controller/test_config_flow.py b/tests/components/homekit_controller/test_config_flow.py index c9354297a43467..8a1f77d0e4cebe 100644 --- a/tests/components/homekit_controller/test_config_flow.py +++ b/tests/components/homekit_controller/test_config_flow.py @@ -141,6 +141,7 @@ def get_device_discovery_info( record = device.info result = zeroconf.ZeroconfServiceInfo( host=record["address"], + addresses=[record["address"]], hostname=record["name"], name=record["name"], port=record["port"], diff --git a/tests/components/homewizard/test_config_flow.py b/tests/components/homewizard/test_config_flow.py index 061b7b634d4dcb..d0dc7d045098a8 100644 --- a/tests/components/homewizard/test_config_flow.py +++ b/tests/components/homewizard/test_config_flow.py @@ -58,6 +58,7 @@ async def test_discovery_flow_works(hass, aioclient_mock): service_info = zeroconf.ZeroconfServiceInfo( host="192.168.43.183", + addresses=["192.168.43.183"], port=80, hostname="p1meter-ddeeff.local.", type="", @@ -140,6 +141,7 @@ async def test_discovery_disabled_api(hass, aioclient_mock): service_info = zeroconf.ZeroconfServiceInfo( host="192.168.43.183", + addresses=["192.168.43.183"], port=80, hostname="p1meter-ddeeff.local.", type="", @@ -178,6 +180,7 @@ async def test_discovery_missing_data_in_service_info(hass, aioclient_mock): service_info = zeroconf.ZeroconfServiceInfo( host="192.168.43.183", + addresses=["192.168.43.183"], port=80, hostname="p1meter-ddeeff.local.", type="", @@ -206,6 +209,7 @@ async def test_discovery_invalid_api(hass, aioclient_mock): service_info = zeroconf.ZeroconfServiceInfo( host="192.168.43.183", + addresses=["192.168.43.183"], port=80, hostname="p1meter-ddeeff.local.", type="", diff --git a/tests/components/hue/test_config_flow.py b/tests/components/hue/test_config_flow.py index 0aa032ddb0da7a..3c7a07ef5d6e49 100644 --- a/tests/components/hue/test_config_flow.py +++ b/tests/components/hue/test_config_flow.py @@ -566,6 +566,7 @@ async def test_bridge_homekit(hass, aioclient_mock): context={"source": config_entries.SOURCE_HOMEKIT}, data=zeroconf.ZeroconfServiceInfo( host="0.0.0.0", + addresses=["0.0.0.0"], hostname="mock_hostname", name="mock_name", port=None, @@ -613,6 +614,7 @@ async def test_bridge_homekit_already_configured(hass, aioclient_mock): context={"source": config_entries.SOURCE_HOMEKIT}, data=zeroconf.ZeroconfServiceInfo( host="0.0.0.0", + addresses=["0.0.0.0"], hostname="mock_hostname", name="mock_name", port=None, @@ -739,6 +741,7 @@ async def test_bridge_zeroconf(hass, aioclient_mock): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.217", + addresses=["192.168.1.217"], port=443, hostname="Philips-hue.local", type="_hue._tcp.local.", @@ -772,6 +775,7 @@ async def test_bridge_zeroconf_already_exists(hass, aioclient_mock): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.217", + addresses=["192.168.1.217"], port=443, hostname="Philips-hue.local", type="_hue._tcp.local.", diff --git a/tests/components/hunterdouglas_powerview/test_config_flow.py b/tests/components/hunterdouglas_powerview/test_config_flow.py index 7f5d4a569edb48..114a747590ef06 100644 --- a/tests/components/hunterdouglas_powerview/test_config_flow.py +++ b/tests/components/hunterdouglas_powerview/test_config_flow.py @@ -13,6 +13,7 @@ HOMEKIT_DISCOVERY_INFO = zeroconf.ZeroconfServiceInfo( host="1.2.3.4", + addresses=["1.2.3.4"], hostname="mock_hostname", name="Hunter Douglas Powerview Hub._hap._tcp.local.", port=None, @@ -22,6 +23,7 @@ ZEROCONF_DISCOVERY_INFO = zeroconf.ZeroconfServiceInfo( host="1.2.3.4", + addresses=["1.2.3.4"], hostname="mock_hostname", name="Hunter Douglas Powerview Hub._powerview._tcp.local.", port=None, diff --git a/tests/components/ipp/__init__.py b/tests/components/ipp/__init__.py index 0e88fb21baf223..26e2c9b338e078 100644 --- a/tests/components/ipp/__init__.py +++ b/tests/components/ipp/__init__.py @@ -38,6 +38,7 @@ type=IPP_ZEROCONF_SERVICE_TYPE, name=f"{ZEROCONF_NAME}.{IPP_ZEROCONF_SERVICE_TYPE}", host=ZEROCONF_HOST, + addresses=[ZEROCONF_HOST], hostname=ZEROCONF_HOSTNAME, port=ZEROCONF_PORT, properties={"rp": ZEROCONF_RP}, @@ -47,6 +48,7 @@ type=IPPS_ZEROCONF_SERVICE_TYPE, name=f"{ZEROCONF_NAME}.{IPPS_ZEROCONF_SERVICE_TYPE}", host=ZEROCONF_HOST, + addresses=[ZEROCONF_HOST], hostname=ZEROCONF_HOSTNAME, port=ZEROCONF_PORT, properties={"rp": ZEROCONF_RP}, diff --git a/tests/components/kodi/util.py b/tests/components/kodi/util.py index c3aaca16d5afb7..5b8b07583c5397 100644 --- a/tests/components/kodi/util.py +++ b/tests/components/kodi/util.py @@ -17,6 +17,7 @@ UUID = "11111111-1111-1111-1111-111111111111" TEST_DISCOVERY = zeroconf.ZeroconfServiceInfo( host="1.1.1.1", + addresses=["1.1.1.1"], port=8080, hostname="hostname.local.", type="_xbmc-jsonrpc-h._tcp.local.", @@ -27,6 +28,7 @@ TEST_DISCOVERY_WO_UUID = zeroconf.ZeroconfServiceInfo( host="1.1.1.1", + addresses=["1.1.1.1"], port=8080, hostname="hostname.local.", type="_xbmc-jsonrpc-h._tcp.local.", diff --git a/tests/components/lookin/__init__.py b/tests/components/lookin/__init__.py index 911e984a57e852..11426f20e57898 100644 --- a/tests/components/lookin/__init__.py +++ b/tests/components/lookin/__init__.py @@ -19,6 +19,7 @@ ZC_TYPE = "_lookin._tcp." ZEROCONF_DATA = ZeroconfServiceInfo( host=IP_ADDRESS, + addresses=[IP_ADDRESS], hostname=f"{ZC_NAME.lower()}.local.", port=80, type=ZC_TYPE, diff --git a/tests/components/lutron_caseta/test_config_flow.py b/tests/components/lutron_caseta/test_config_flow.py index 2b947c369828ac..47956e270028bd 100644 --- a/tests/components/lutron_caseta/test_config_flow.py +++ b/tests/components/lutron_caseta/test_config_flow.py @@ -427,6 +427,7 @@ async def test_zeroconf_host_already_configured(hass, tmpdir): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="1.1.1.1", + addresses=["1.1.1.1"], hostname="LuTrOn-abc.local.", name="mock_name", port=None, @@ -454,6 +455,7 @@ async def test_zeroconf_lutron_id_already_configured(hass): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="1.1.1.1", + addresses=["1.1.1.1"], hostname="LuTrOn-abc.local.", name="mock_name", port=None, @@ -476,6 +478,7 @@ async def test_zeroconf_not_lutron_device(hass): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="1.1.1.1", + addresses=["1.1.1.1"], hostname="notlutron-abc.local.", name="mock_name", port=None, @@ -504,6 +507,7 @@ async def test_zeroconf(hass, source, tmpdir): context={"source": source}, data=zeroconf.ZeroconfServiceInfo( host="1.1.1.1", + addresses=["1.1.1.1"], hostname="LuTrOn-abc.local.", name="mock_name", port=None, diff --git a/tests/components/modern_forms/test_config_flow.py b/tests/components/modern_forms/test_config_flow.py index b1b2eb618afb8d..931d2918fe2ab1 100644 --- a/tests/components/modern_forms/test_config_flow.py +++ b/tests/components/modern_forms/test_config_flow.py @@ -71,6 +71,7 @@ async def test_full_zeroconf_flow_implementation( context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.123", + addresses=["192.168.1.123"], hostname="example.local.", name="mock_name", port=None, @@ -140,6 +141,7 @@ async def test_zeroconf_connection_error( context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.123", + addresses=["192.168.1.123"], hostname="example.local.", name="mock_name", port=None, @@ -171,6 +173,7 @@ async def test_zeroconf_confirm_connection_error( }, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.123", + addresses=["192.168.1.123"], hostname="example.com.", name="mock_name", port=None, @@ -240,6 +243,7 @@ async def test_zeroconf_with_mac_device_exists_abort( context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.123", + addresses=["192.168.1.123"], hostname="example.local.", name="mock_name", port=None, diff --git a/tests/components/nam/test_config_flow.py b/tests/components/nam/test_config_flow.py index 015c645a3e776a..9479e29cdeafdf 100644 --- a/tests/components/nam/test_config_flow.py +++ b/tests/components/nam/test_config_flow.py @@ -14,6 +14,7 @@ DISCOVERY_INFO = zeroconf.ZeroconfServiceInfo( host="10.10.2.3", + addresses=["10.10.2.3"], hostname="mock_hostname", name="mock_name", port=None, diff --git a/tests/components/nanoleaf/test_config_flow.py b/tests/components/nanoleaf/test_config_flow.py index 4e4a48e9bfe5d2..305f88a2e9089e 100644 --- a/tests/components/nanoleaf/test_config_flow.py +++ b/tests/components/nanoleaf/test_config_flow.py @@ -238,6 +238,7 @@ async def test_discovery_link_unavailable( context={"source": source}, data=zeroconf.ZeroconfServiceInfo( host=TEST_HOST, + addresses=[TEST_HOST], hostname="mock_hostname", name=f"{TEST_NAME}.{type_in_discovery_info}", port=None, @@ -422,6 +423,7 @@ async def test_import_discovery_integration( context={"source": source}, data=zeroconf.ZeroconfServiceInfo( host=TEST_HOST, + addresses=[TEST_HOST], hostname="mock_hostname", name=f"{TEST_NAME}.{type_in_discovery}", port=None, diff --git a/tests/components/netatmo/test_config_flow.py b/tests/components/netatmo/test_config_flow.py index b97f4c8b4ec8b7..30fb5fd3d47dbc 100644 --- a/tests/components/netatmo/test_config_flow.py +++ b/tests/components/netatmo/test_config_flow.py @@ -42,6 +42,7 @@ async def test_abort_if_existing_entry(hass): context={"source": config_entries.SOURCE_HOMEKIT}, data=zeroconf.ZeroconfServiceInfo( host="0.0.0.0", + addresses=["0.0.0.0"], hostname="mock_hostname", name="mock_name", port=None, diff --git a/tests/components/nut/test_config_flow.py b/tests/components/nut/test_config_flow.py index 733d5807c5f636..2261fec3a86147 100644 --- a/tests/components/nut/test_config_flow.py +++ b/tests/components/nut/test_config_flow.py @@ -37,6 +37,7 @@ async def test_form_zeroconf(hass): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.5", + addresses=["192.168.1.5"], hostname="mock_hostname", name="mock_name", port=1234, diff --git a/tests/components/octoprint/test_config_flow.py b/tests/components/octoprint/test_config_flow.py index 422b47668aa46f..7769e0820161c7 100644 --- a/tests/components/octoprint/test_config_flow.py +++ b/tests/components/octoprint/test_config_flow.py @@ -175,6 +175,7 @@ async def test_show_zerconf_form(hass: HomeAssistant) -> None: context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.123", + addresses=["192.168.1.123"], hostname="example.local.", name="mock_name", port=80, @@ -496,6 +497,7 @@ async def test_duplicate_zerconf_ignored(hass: HomeAssistant) -> None: context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.123", + addresses=["192.168.1.123"], hostname="example.local.", name="mock_name", port=80, diff --git a/tests/components/overkiz/test_config_flow.py b/tests/components/overkiz/test_config_flow.py index e80add482b0d3f..db86a4abc5c644 100644 --- a/tests/components/overkiz/test_config_flow.py +++ b/tests/components/overkiz/test_config_flow.py @@ -33,6 +33,7 @@ FAKE_ZERO_CONF_INFO = ZeroconfServiceInfo( host="192.168.0.51", + addresses=["192.168.0.51"], port=443, hostname=f"gateway-{TEST_GATEWAY_ID}.local.", type="_kizbox._tcp.local.", diff --git a/tests/components/plugwise/test_config_flow.py b/tests/components/plugwise/test_config_flow.py index 3e9ce985a5c8ef..9fdd0323518232 100644 --- a/tests/components/plugwise/test_config_flow.py +++ b/tests/components/plugwise/test_config_flow.py @@ -38,6 +38,7 @@ TEST_DISCOVERY = ZeroconfServiceInfo( host=TEST_HOST, + addresses=[TEST_HOST], hostname=f"{TEST_HOSTNAME}.local.", name="mock_name", port=DEFAULT_PORT, @@ -51,6 +52,7 @@ TEST_DISCOVERY2 = ZeroconfServiceInfo( host=TEST_HOST, + addresses=[TEST_HOST], hostname=f"{TEST_HOSTNAME2}.local.", name="mock_name", port=DEFAULT_PORT, diff --git a/tests/components/rachio/test_config_flow.py b/tests/components/rachio/test_config_flow.py index cf9e811ed5a3c0..00445f23c01ed7 100644 --- a/tests/components/rachio/test_config_flow.py +++ b/tests/components/rachio/test_config_flow.py @@ -114,6 +114,7 @@ async def test_form_homekit(hass): context={"source": config_entries.SOURCE_HOMEKIT}, data=zeroconf.ZeroconfServiceInfo( host="mock_host", + addresses=["mock_host"], hostname="mock_hostname", name="mock_name", port=None, @@ -138,6 +139,7 @@ async def test_form_homekit(hass): context={"source": config_entries.SOURCE_HOMEKIT}, data=zeroconf.ZeroconfServiceInfo( host="mock_host", + addresses=["mock_host"], hostname="mock_hostname", name="mock_name", port=None, diff --git a/tests/components/rainmachine/test_config_flow.py b/tests/components/rainmachine/test_config_flow.py index 4514bbfb9d856d..5a0e1fc08cc673 100644 --- a/tests/components/rainmachine/test_config_flow.py +++ b/tests/components/rainmachine/test_config_flow.py @@ -149,6 +149,7 @@ async def test_step_homekit_zeroconf_ip_already_exists( context={"source": source}, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.100", + addresses=["192.168.1.100"], hostname="mock_hostname", name="mock_name", port=None, @@ -174,6 +175,7 @@ async def test_step_homekit_zeroconf_ip_change(hass, client, config_entry, sourc context={"source": source}, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.2", + addresses=["192.168.1.2"], hostname="mock_hostname", name="mock_name", port=None, @@ -202,6 +204,7 @@ async def test_step_homekit_zeroconf_new_controller_when_some_exist( context={"source": source}, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.100", + addresses=["192.168.1.100"], hostname="mock_hostname", name="mock_name", port=None, @@ -249,6 +252,7 @@ async def test_discovery_by_homekit_and_zeroconf_same_time(hass, client): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.100", + addresses=["192.168.1.100"], hostname="mock_hostname", name="mock_name", port=None, @@ -268,6 +272,7 @@ async def test_discovery_by_homekit_and_zeroconf_same_time(hass, client): context={"source": config_entries.SOURCE_HOMEKIT}, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.100", + addresses=["192.168.1.100"], hostname="mock_hostname", name="mock_name", port=None, diff --git a/tests/components/roku/__init__.py b/tests/components/roku/__init__.py index c0e044a358947c..2ae0b308f9acc4 100644 --- a/tests/components/roku/__init__.py +++ b/tests/components/roku/__init__.py @@ -24,6 +24,7 @@ MOCK_HOMEKIT_DISCOVERY_INFO = zeroconf.ZeroconfServiceInfo( host=HOMEKIT_HOST, + addresses=[HOMEKIT_HOST], hostname="mock_hostname", name="onn._hap._tcp.local.", port=None, diff --git a/tests/components/samsungtv/test_config_flow.py b/tests/components/samsungtv/test_config_flow.py index c2a258b2afaef4..199c7d87eed4d6 100644 --- a/tests/components/samsungtv/test_config_flow.py +++ b/tests/components/samsungtv/test_config_flow.py @@ -101,6 +101,7 @@ EXISTING_IP = "192.168.40.221" MOCK_ZEROCONF_DATA = zeroconf.ZeroconfServiceInfo( host="fake_host", + addresses=["fake_host"], hostname="mock_hostname", name="mock_name", port=1234, diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index 12690a35faa63d..02e86ef03f84f6 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -19,6 +19,7 @@ } DISCOVERY_INFO = zeroconf.ZeroconfServiceInfo( host="1.1.1.1", + addresses=["1.1.1.1"], hostname="mock_hostname", name="shelly1pm-12345", port=None, diff --git a/tests/components/smappee/test_config_flow.py b/tests/components/smappee/test_config_flow.py index f6f988a3caab4b..16d330a21a8b44 100644 --- a/tests/components/smappee/test_config_flow.py +++ b/tests/components/smappee/test_config_flow.py @@ -57,6 +57,7 @@ async def test_show_zeroconf_connection_error_form(hass): context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="1.2.3.4", + addresses=["1.2.3.4"], port=22, hostname="Smappee1006000212.local.", type="_ssh._tcp.local.", @@ -86,6 +87,7 @@ async def test_show_zeroconf_connection_error_form_next_generation(hass): context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="1.2.3.4", + addresses=["1.2.3.4"], port=22, hostname="Smappee5001000212.local.", type="_ssh._tcp.local.", @@ -168,6 +170,7 @@ async def test_zeroconf_wrong_mdns(hass): context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="1.2.3.4", + addresses=["1.2.3.4"], port=22, hostname="example.local.", type="_ssh._tcp.local.", @@ -278,6 +281,7 @@ async def test_zeroconf_device_exists_abort(hass): context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="1.2.3.4", + addresses=["1.2.3.4"], port=22, hostname="Smappee1006000212.local.", type="_ssh._tcp.local.", @@ -327,6 +331,7 @@ async def test_zeroconf_abort_if_cloud_device_exists(hass): context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="1.2.3.4", + addresses=["1.2.3.4"], port=22, hostname="Smappee1006000212.local.", type="_ssh._tcp.local.", @@ -346,6 +351,7 @@ async def test_zeroconf_confirm_abort_if_cloud_device_exists(hass): context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="1.2.3.4", + addresses=["1.2.3.4"], port=22, hostname="Smappee1006000212.local.", type="_ssh._tcp.local.", @@ -465,6 +471,7 @@ async def test_full_zeroconf_flow(hass): context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="1.2.3.4", + addresses=["1.2.3.4"], port=22, hostname="Smappee1006000212.local.", type="_ssh._tcp.local.", @@ -540,6 +547,7 @@ async def test_full_zeroconf_flow_next_generation(hass): context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="1.2.3.4", + addresses=["1.2.3.4"], port=22, hostname="Smappee5001000212.local.", type="_ssh._tcp.local.", diff --git a/tests/components/sonos/conftest.py b/tests/components/sonos/conftest.py index d7791c6ce72a79..bc49c12ed81564 100644 --- a/tests/components/sonos/conftest.py +++ b/tests/components/sonos/conftest.py @@ -50,6 +50,7 @@ def zeroconf_payload(): """Return a default zeroconf payload.""" return zeroconf.ZeroconfServiceInfo( host="192.168.4.2", + addresses=["192.168.4.2"], hostname="Sonos-aaa", name="Sonos-aaa@Living Room._sonos._tcp.local.", port=None, diff --git a/tests/components/sonos/test_config_flow.py b/tests/components/sonos/test_config_flow.py index aa2ce6cc0be73a..f0e6c81a41131a 100644 --- a/tests/components/sonos/test_config_flow.py +++ b/tests/components/sonos/test_config_flow.py @@ -158,6 +158,7 @@ async def test_zeroconf_sonos_v1(hass: core.HomeAssistant): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.107", + addresses=["192.168.1.107"], port=1443, hostname="sonos5CAAFDE47AC8.local.", type="_sonos._tcp.local.", diff --git a/tests/components/spotify/test_config_flow.py b/tests/components/spotify/test_config_flow.py index a1a77da1d100b1..e2f1878c04d0aa 100644 --- a/tests/components/spotify/test_config_flow.py +++ b/tests/components/spotify/test_config_flow.py @@ -15,6 +15,7 @@ BLANK_ZEROCONF_INFO = zeroconf.ZeroconfServiceInfo( host="1.2.3.4", + addresses=["1.2.3.4"], hostname="mock_hostname", name="mock_name", port=None, diff --git a/tests/components/system_bridge/test_config_flow.py b/tests/components/system_bridge/test_config_flow.py index ecc36641e6c1c5..94d116bbd36cbe 100644 --- a/tests/components/system_bridge/test_config_flow.py +++ b/tests/components/system_bridge/test_config_flow.py @@ -30,6 +30,7 @@ FIXTURE_ZEROCONF = zeroconf.ZeroconfServiceInfo( host="1.1.1.1", + addresses=["1.1.1.1"], port=9170, hostname="test-bridge.local.", type="_system-bridge._udp.local.", @@ -47,6 +48,7 @@ FIXTURE_ZEROCONF_BAD = zeroconf.ZeroconfServiceInfo( host="1.1.1.1", + addresses=["1.1.1.1"], port=9170, hostname="test-bridge.local.", type="_system-bridge._udp.local.", diff --git a/tests/components/tado/test_config_flow.py b/tests/components/tado/test_config_flow.py index 4c234fee2484d2..8e30ff37de608e 100644 --- a/tests/components/tado/test_config_flow.py +++ b/tests/components/tado/test_config_flow.py @@ -129,6 +129,7 @@ async def test_form_homekit(hass): context={"source": config_entries.SOURCE_HOMEKIT}, data=zeroconf.ZeroconfServiceInfo( host="mock_host", + addresses=["mock_host"], hostname="mock_hostname", name="mock_name", port=None, @@ -155,6 +156,7 @@ async def test_form_homekit(hass): context={"source": config_entries.SOURCE_HOMEKIT}, data=zeroconf.ZeroconfServiceInfo( host="mock_host", + addresses=["mock_host"], hostname="mock_hostname", name="mock_name", port=None, diff --git a/tests/components/tradfri/test_config_flow.py b/tests/components/tradfri/test_config_flow.py index 12726bc553aa65..90fce929f581fe 100644 --- a/tests/components/tradfri/test_config_flow.py +++ b/tests/components/tradfri/test_config_flow.py @@ -106,6 +106,7 @@ async def test_discovery_connection(hass, mock_auth, mock_entry_setup): context={"source": config_entries.SOURCE_HOMEKIT}, data=zeroconf.ZeroconfServiceInfo( host="123.123.123.123", + addresses=["123.123.123.123"], hostname="mock_hostname", name="mock_name", port=None, @@ -261,6 +262,7 @@ async def test_discovery_duplicate_aborted(hass): context={"source": config_entries.SOURCE_HOMEKIT}, data=zeroconf.ZeroconfServiceInfo( host="new-host", + addresses=["new-host"], hostname="mock_hostname", name="mock_name", port=None, @@ -296,6 +298,7 @@ async def test_duplicate_discovery(hass, mock_auth, mock_entry_setup): context={"source": config_entries.SOURCE_HOMEKIT}, data=zeroconf.ZeroconfServiceInfo( host="123.123.123.123", + addresses=["123.123.123.123"], hostname="mock_hostname", name="mock_name", port=None, @@ -311,6 +314,7 @@ async def test_duplicate_discovery(hass, mock_auth, mock_entry_setup): context={"source": config_entries.SOURCE_HOMEKIT}, data=zeroconf.ZeroconfServiceInfo( host="123.123.123.123", + addresses=["123.123.123.123"], hostname="mock_hostname", name="mock_name", port=None, @@ -335,6 +339,7 @@ async def test_discovery_updates_unique_id(hass): context={"source": config_entries.SOURCE_HOMEKIT}, data=zeroconf.ZeroconfServiceInfo( host="some-host", + addresses=["some-host"], hostname="mock_hostname", name="mock_name", port=None, diff --git a/tests/components/vizio/const.py b/tests/components/vizio/const.py index b4a3fc047664c5..e39864a61576ef 100644 --- a/tests/components/vizio/const.py +++ b/tests/components/vizio/const.py @@ -199,6 +199,7 @@ def __init__(self, auth_token: str) -> None: MOCK_ZEROCONF_SERVICE_INFO = zeroconf.ZeroconfServiceInfo( host=ZEROCONF_HOST, + addresses=[ZEROCONF_HOST], hostname="mock_hostname", name=ZEROCONF_NAME, port=ZEROCONF_PORT, diff --git a/tests/components/volumio/test_config_flow.py b/tests/components/volumio/test_config_flow.py index 3eb254784d1c9e..8a47155815c889 100644 --- a/tests/components/volumio/test_config_flow.py +++ b/tests/components/volumio/test_config_flow.py @@ -19,6 +19,7 @@ TEST_DISCOVERY = zeroconf.ZeroconfServiceInfo( host="1.1.1.1", + addresses=["1.1.1.1"], hostname="mock_hostname", name="mock_name", port=3000, diff --git a/tests/components/wled/test_config_flow.py b/tests/components/wled/test_config_flow.py index 27023708400947..c23f35534b8315 100644 --- a/tests/components/wled/test_config_flow.py +++ b/tests/components/wled/test_config_flow.py @@ -51,6 +51,7 @@ async def test_full_zeroconf_flow_implementation( context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.123", + addresses=["192.168.1.123"], hostname="example.local.", name="mock_name", port=None, @@ -110,6 +111,7 @@ async def test_zeroconf_connection_error( context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.123", + addresses=["192.168.1.123"], hostname="example.local.", name="mock_name", port=None, @@ -168,6 +170,7 @@ async def test_zeroconf_without_mac_device_exists_abort( context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.123", + addresses=["192.168.1.123"], hostname="example.local.", name="mock_name", port=None, @@ -192,6 +195,7 @@ async def test_zeroconf_with_mac_device_exists_abort( context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.123", + addresses=["192.168.1.123"], hostname="example.local.", name="mock_name", port=None, @@ -216,6 +220,7 @@ async def test_zeroconf_with_cct_channel_abort( context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host="192.168.1.123", + addresses=["192.168.1.123"], hostname="example.local.", name="mock_name", port=None, diff --git a/tests/components/xiaomi_aqara/test_config_flow.py b/tests/components/xiaomi_aqara/test_config_flow.py index 554e0460443df1..c27f273f26b5f5 100644 --- a/tests/components/xiaomi_aqara/test_config_flow.py +++ b/tests/components/xiaomi_aqara/test_config_flow.py @@ -403,6 +403,7 @@ async def test_zeroconf_success(hass): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host=TEST_HOST, + addresses=[TEST_HOST], hostname="mock_hostname", name=TEST_ZEROCONF_NAME, port=None, @@ -449,6 +450,7 @@ async def test_zeroconf_missing_data(hass): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host=TEST_HOST, + addresses=[TEST_HOST], hostname="mock_hostname", name=TEST_ZEROCONF_NAME, port=None, @@ -468,6 +470,7 @@ async def test_zeroconf_unknown_device(hass): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host=TEST_HOST, + addresses=[TEST_HOST], hostname="mock_hostname", name="not-a-xiaomi-aqara-gateway", port=None, diff --git a/tests/components/xiaomi_miio/test_config_flow.py b/tests/components/xiaomi_miio/test_config_flow.py index 3be52e8323742a..09f0b4c0fb63e0 100644 --- a/tests/components/xiaomi_miio/test_config_flow.py +++ b/tests/components/xiaomi_miio/test_config_flow.py @@ -394,6 +394,7 @@ async def test_zeroconf_gateway_success(hass): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host=TEST_HOST, + addresses=[TEST_HOST], hostname="mock_hostname", name=TEST_ZEROCONF_NAME, port=None, @@ -436,6 +437,7 @@ async def test_zeroconf_unknown_device(hass): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host=TEST_HOST, + addresses=[TEST_HOST], hostname="mock_hostname", name="not-a-xiaomi-miio-device", port=None, @@ -455,6 +457,7 @@ async def test_zeroconf_no_data(hass): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host=None, + addresses=[], hostname="mock_hostname", name=None, port=None, @@ -474,6 +477,7 @@ async def test_zeroconf_missing_data(hass): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host=TEST_HOST, + addresses=[TEST_HOST], hostname="mock_hostname", name=TEST_ZEROCONF_NAME, port=None, @@ -771,6 +775,7 @@ async def zeroconf_device_success(hass, zeroconf_name_to_test, model_to_test): context={"source": config_entries.SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( host=TEST_HOST, + addresses=[TEST_HOST], hostname="mock_hostname", name=zeroconf_name_to_test, port=None, diff --git a/tests/components/yeelight/__init__.py b/tests/components/yeelight/__init__.py index 81972ca435261c..d0112c5a5444ff 100644 --- a/tests/components/yeelight/__init__.py +++ b/tests/components/yeelight/__init__.py @@ -42,6 +42,7 @@ ZEROCONF_DATA = zeroconf.ZeroconfServiceInfo( host=IP_ADDRESS, + addresses=[IP_ADDRESS], port=54321, hostname=f"yeelink-light-strip1_miio{ID_DECIMAL}.local.", type="_miio._udp.local.", diff --git a/tests/components/yeelight/test_config_flow.py b/tests/components/yeelight/test_config_flow.py index aa5e7f98a45510..dd6c8fe1cbd192 100644 --- a/tests/components/yeelight/test_config_flow.py +++ b/tests/components/yeelight/test_config_flow.py @@ -467,6 +467,7 @@ async def test_discovered_by_homekit_and_dhcp(hass): context={"source": config_entries.SOURCE_HOMEKIT}, data=zeroconf.ZeroconfServiceInfo( host=IP_ADDRESS, + addresses=[IP_ADDRESS], hostname="mock_hostname", name="mock_name", port=None, @@ -536,6 +537,7 @@ async def test_discovered_by_homekit_and_dhcp(hass): config_entries.SOURCE_HOMEKIT, zeroconf.ZeroconfServiceInfo( host=IP_ADDRESS, + addresses=[IP_ADDRESS], hostname="mock_hostname", name="mock_name", port=None, @@ -603,6 +605,7 @@ async def test_discovered_by_dhcp_or_homekit(hass, source, data): config_entries.SOURCE_HOMEKIT, zeroconf.ZeroconfServiceInfo( host=IP_ADDRESS, + addresses=[IP_ADDRESS], hostname="mock_hostname", name="mock_name", port=None, diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index 24b6ec97ec64cb..dc007d5c2c5f6c 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -1095,6 +1095,7 @@ async def test_service_info_compatibility(hass, caplog): """ discovery_info = zeroconf.ZeroconfServiceInfo( host="mock_host", + addresses=["mock_host"], port=None, hostname="mock_hostname", type="mock_type", diff --git a/tests/components/zha/test_config_flow.py b/tests/components/zha/test_config_flow.py index fe03f759304347..0c51ecffe9bf4c 100644 --- a/tests/components/zha/test_config_flow.py +++ b/tests/components/zha/test_config_flow.py @@ -51,6 +51,7 @@ async def test_discovery(detect_mock, hass): """Test zeroconf flow -- radio detected.""" service_info = zeroconf.ZeroconfServiceInfo( host="192.168.1.200", + addresses=["192.168.1.200"], hostname="_tube_zb_gw._tcp.local.", name="mock_name", port=6053, @@ -95,6 +96,7 @@ async def test_discovery_via_zeroconf_ip_change(detect_mock, hass): service_info = zeroconf.ZeroconfServiceInfo( host="192.168.1.22", + addresses=["192.168.1.22"], hostname="tube_zb_gw_cc2652p2_poe.local.", name="mock_name", port=6053, @@ -127,6 +129,7 @@ async def test_discovery_via_zeroconf_ip_change_ignored(detect_mock, hass): service_info = zeroconf.ZeroconfServiceInfo( host="192.168.1.22", + addresses=["192.168.1.22"], hostname="tube_zb_gw_cc2652p2_poe.local.", name="mock_name", port=6053, @@ -389,6 +392,7 @@ async def test_discovery_already_setup(detect_mock, hass): """Test zeroconf flow -- radio detected.""" service_info = zeroconf.ZeroconfServiceInfo( host="192.168.1.200", + addresses=["192.168.1.200"], hostname="_tube_zb_gw._tcp.local.", name="mock_name", port=6053, From 07975330162f2291b88f899376c00d6f16f6a822 Mon Sep 17 00:00:00 2001 From: PanicRide <99451581+PanicRide@users.noreply.github.com> Date: Fri, 11 Feb 2022 14:58:26 -0800 Subject: [PATCH 0554/1098] New amcrest binary sensor to monitor doorbell button (#66302) * New binary sensor to monitor doorbell button * New binary sensor to monitor doorbell button --- homeassistant/components/amcrest/binary_sensor.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/homeassistant/components/amcrest/binary_sensor.py b/homeassistant/components/amcrest/binary_sensor.py index e583aad904bfc1..697dc0cf4c4c88 100644 --- a/homeassistant/components/amcrest/binary_sensor.py +++ b/homeassistant/components/amcrest/binary_sensor.py @@ -65,6 +65,10 @@ class AmcrestSensorEntityDescription(BinarySensorEntityDescription): _ONLINE_KEY = "online" +_DOORBELL_KEY = "doorbell" +_DOORBELL_NAME = "Doorbell Button" +_DOORBELL_EVENT_CODE = "CallNoAnswered" + BINARY_SENSORS: tuple[AmcrestSensorEntityDescription, ...] = ( AmcrestSensorEntityDescription( key=_AUDIO_DETECTED_KEY, @@ -111,6 +115,12 @@ class AmcrestSensorEntityDescription(BinarySensorEntityDescription): device_class=BinarySensorDeviceClass.CONNECTIVITY, should_poll=True, ), + AmcrestSensorEntityDescription( + key=_DOORBELL_KEY, + name=_DOORBELL_NAME, + device_class=BinarySensorDeviceClass.OCCUPANCY, + event_code=_DOORBELL_EVENT_CODE, + ), ) BINARY_SENSOR_KEYS = [description.key for description in BINARY_SENSORS] _EXCLUSIVE_OPTIONS = [ From a6742eff34b952929a2516c7d7a7b31dfbae26d0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 11 Feb 2022 17:13:35 -0600 Subject: [PATCH 0555/1098] Add button to wake august locks from deep sleep (#66343) --- homeassistant/components/august/button.py | 39 +++++++++++++++++++++++ homeassistant/components/august/camera.py | 2 -- homeassistant/components/august/const.py | 1 + homeassistant/components/august/entity.py | 5 +++ homeassistant/components/august/lock.py | 7 ---- tests/components/august/mocks.py | 13 ++++++-- tests/components/august/test_button.py | 27 ++++++++++++++++ 7 files changed, 83 insertions(+), 11 deletions(-) create mode 100644 homeassistant/components/august/button.py create mode 100644 tests/components/august/test_button.py diff --git a/homeassistant/components/august/button.py b/homeassistant/components/august/button.py new file mode 100644 index 00000000000000..d7f2a5ba4aed0b --- /dev/null +++ b/homeassistant/components/august/button.py @@ -0,0 +1,39 @@ +"""Support for August buttons.""" +from yalexs.lock import Lock + +from homeassistant.components.button import ButtonEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import AugustData +from .const import DATA_AUGUST, DOMAIN +from .entity import AugustEntityMixin + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up August lock wake buttons.""" + data: AugustData = hass.data[DOMAIN][config_entry.entry_id][DATA_AUGUST] + async_add_entities([AugustWakeLockButton(data, lock) for lock in data.locks]) + + +class AugustWakeLockButton(AugustEntityMixin, ButtonEntity): + """Representation of an August lock wake button.""" + + def __init__(self, data: AugustData, device: Lock) -> None: + """Initialize the lock wake button.""" + super().__init__(data, device) + self._attr_name = f"{device.device_name} Wake" + self._attr_unique_id = f"{self._device_id}_wake" + + async def async_press(self, **kwargs): + """Wake the device.""" + await self._data.async_status_async(self._device_id, self._hyper_bridge) + + @callback + def _update_from_data(self): + """Nothing to update as buttons are stateless.""" diff --git a/homeassistant/components/august/camera.py b/homeassistant/components/august/camera.py index ce1ede865384f0..268894275552da 100644 --- a/homeassistant/components/august/camera.py +++ b/homeassistant/components/august/camera.py @@ -37,8 +37,6 @@ class AugustCamera(AugustEntityMixin, Camera): def __init__(self, data, device, session, timeout): """Initialize a August security camera.""" super().__init__(data, device) - self._data = data - self._device = device self._timeout = timeout self._session = session self._image_url = None diff --git a/homeassistant/components/august/const.py b/homeassistant/components/august/const.py index ae3fcdf90e3cb6..ac6f463467f476 100644 --- a/homeassistant/components/august/const.py +++ b/homeassistant/components/august/const.py @@ -46,6 +46,7 @@ LOGIN_METHODS = ["phone", "email"] PLATFORMS = [ + Platform.BUTTON, Platform.CAMERA, Platform.BINARY_SENSOR, Platform.LOCK, diff --git a/homeassistant/components/august/entity.py b/homeassistant/components/august/entity.py index a0fe44838c2276..209747da0bee76 100644 --- a/homeassistant/components/august/entity.py +++ b/homeassistant/components/august/entity.py @@ -36,6 +36,11 @@ def _device_id(self): def _detail(self): return self._data.get_device_detail(self._device.device_id) + @property + def _hyper_bridge(self): + """Check if the lock has a paired hyper bridge.""" + return bool(self._detail.bridge and self._detail.bridge.hyper_bridge) + @callback def _update_from_data_and_write_state(self): self._update_from_data() diff --git a/homeassistant/components/august/lock.py b/homeassistant/components/august/lock.py index 41abc1c6aa287d..e30f8301a8f6d9 100644 --- a/homeassistant/components/august/lock.py +++ b/homeassistant/components/august/lock.py @@ -39,18 +39,11 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity): def __init__(self, data, device): """Initialize the lock.""" super().__init__(data, device) - self._data = data - self._device = device self._lock_status = None self._attr_name = device.device_name self._attr_unique_id = f"{self._device_id:s}_lock" self._update_from_data() - @property - def _hyper_bridge(self): - """Check if the lock has a paired hyper bridge.""" - return bool(self._detail.bridge and self._detail.bridge.hyper_bridge) - async def async_lock(self, **kwargs): """Lock the device.""" if self._data.activity_stream.pubnub.connected: diff --git a/tests/components/august/mocks.py b/tests/components/august/mocks.py index 2d572b886f3e31..e419488beccbfb 100644 --- a/tests/components/august/mocks.py +++ b/tests/components/august/mocks.py @@ -75,7 +75,16 @@ async def _mock_setup_august( return entry -async def _create_august_with_devices( # noqa: C901 +async def _create_august_with_devices( + hass, devices, api_call_side_effects=None, activities=None, pubnub=None +): + entry, api_instance = await _create_august_api_with_devices( + hass, devices, api_call_side_effects, activities, pubnub + ) + return entry + + +async def _create_august_api_with_devices( # noqa: C901 hass, devices, api_call_side_effects=None, activities=None, pubnub=None ): if api_call_side_effects is None: @@ -171,7 +180,7 @@ def unlock_return_activities_side_effect(access_token, device_id): # are any locks assert api_instance.async_status_async.mock_calls - return entry + return entry, api_instance async def _mock_setup_august_with_api_side_effects(hass, api_call_side_effects, pubnub): diff --git a/tests/components/august/test_button.py b/tests/components/august/test_button.py new file mode 100644 index 00000000000000..2d3e6caf884a45 --- /dev/null +++ b/tests/components/august/test_button.py @@ -0,0 +1,27 @@ +"""The button tests for the august platform.""" + +from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN +from homeassistant.components.button.const import SERVICE_PRESS +from homeassistant.const import ATTR_ENTITY_ID + +from tests.components.august.mocks import ( + _create_august_api_with_devices, + _mock_lock_from_fixture, +) + + +async def test_wake_lock(hass): + """Test creation of a lock and wake it.""" + lock_one = await _mock_lock_from_fixture( + hass, "get_lock.online_with_doorsense.json" + ) + _, api_instance = await _create_august_api_with_devices(hass, [lock_one]) + entity_id = "button.online_with_doorsense_name_wake" + binary_sensor_online_with_doorsense_name = hass.states.get(entity_id) + assert binary_sensor_online_with_doorsense_name is not None + api_instance.async_status_async.reset_mock() + assert await hass.services.async_call( + BUTTON_DOMAIN, SERVICE_PRESS, {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + await hass.async_block_till_done() + api_instance.async_status_async.assert_called_once() From 13af2728c2b1a78fe4458b1b07d1d59115719302 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 11 Feb 2022 17:13:57 -0600 Subject: [PATCH 0556/1098] Fix zwave_me zeroconf mocking (#66356) --- tests/components/zwave_me/test_config_flow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/components/zwave_me/test_config_flow.py b/tests/components/zwave_me/test_config_flow.py index 9c7c9f51e2496b..36a73c4fc064a3 100644 --- a/tests/components/zwave_me/test_config_flow.py +++ b/tests/components/zwave_me/test_config_flow.py @@ -18,6 +18,7 @@ host="ws://192.168.1.14", hostname="mock_hostname", name="mock_name", + addresses=["192.168.1.14"], port=1234, properties={ "deviceid": "aa:bb:cc:dd:ee:ff", From d479949ca23a4d60569ae53a51edd753f8b81bad Mon Sep 17 00:00:00 2001 From: Jeef Date: Fri, 11 Feb 2022 17:01:38 -0700 Subject: [PATCH 0557/1098] Add a base class for Intellifire entities (#65077) Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> --- .coveragerc | 1 + .../components/intellifire/binary_sensor.py | 23 +----- .../components/intellifire/entity.py | 28 +++++++ .../components/intellifire/sensor.py | 81 ++++++++----------- 4 files changed, 64 insertions(+), 69 deletions(-) create mode 100644 homeassistant/components/intellifire/entity.py diff --git a/.coveragerc b/.coveragerc index 171415f1c793f5..0ada739763f546 100644 --- a/.coveragerc +++ b/.coveragerc @@ -547,6 +547,7 @@ omit = homeassistant/components/intellifire/coordinator.py homeassistant/components/intellifire/binary_sensor.py homeassistant/components/intellifire/sensor.py + homeassistant/components/intellifire/entity.py homeassistant/components/incomfort/* homeassistant/components/intesishome/* homeassistant/components/ios/__init__.py diff --git a/homeassistant/components/intellifire/binary_sensor.py b/homeassistant/components/intellifire/binary_sensor.py index 62082bb9ab40fe..747dcaa58bef3d 100644 --- a/homeassistant/components/intellifire/binary_sensor.py +++ b/homeassistant/components/intellifire/binary_sensor.py @@ -13,10 +13,10 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity from . import IntellifireDataUpdateCoordinator from .const import DOMAIN +from .entity import IntellifireEntity @dataclass @@ -75,28 +75,11 @@ async def async_setup_entry( ) -class IntellifireBinarySensor(CoordinatorEntity, BinarySensorEntity): - """A semi-generic wrapper around Binary Sensor entities for IntelliFire.""" +class IntellifireBinarySensor(IntellifireEntity, BinarySensorEntity): + """Extends IntellifireEntity with Binary Sensor specific logic.""" - # Define types - coordinator: IntellifireDataUpdateCoordinator entity_description: IntellifireBinarySensorEntityDescription - def __init__( - self, - coordinator: IntellifireDataUpdateCoordinator, - description: IntellifireBinarySensorEntityDescription, - ) -> None: - """Class initializer.""" - super().__init__(coordinator=coordinator) - self.entity_description = description - - # Set the Display name the User will see - self._attr_name = f"Fireplace {description.name}" - self._attr_unique_id = f"{description.key}_{coordinator.api.data.serial}" - # Configure the Device Info - self._attr_device_info = self.coordinator.device_info - @property def is_on(self) -> bool: """Use this to get the correct value.""" diff --git a/homeassistant/components/intellifire/entity.py b/homeassistant/components/intellifire/entity.py new file mode 100644 index 00000000000000..eeb5e7b51bd9a6 --- /dev/null +++ b/homeassistant/components/intellifire/entity.py @@ -0,0 +1,28 @@ +"""Platform for shared base classes for sensors.""" +from __future__ import annotations + +from homeassistant.helpers.entity import EntityDescription +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from . import IntellifireDataUpdateCoordinator + + +class IntellifireEntity(CoordinatorEntity): + """Define a generic class for Intellifire entities.""" + + coordinator: IntellifireDataUpdateCoordinator + _attr_attribution = "Data provided by unpublished Intellifire API" + + def __init__( + self, + coordinator: IntellifireDataUpdateCoordinator, + description: EntityDescription, + ) -> None: + """Class initializer.""" + super().__init__(coordinator=coordinator) + self.entity_description = description + # Set the Display name the User will see + self._attr_name = f"Fireplace {description.name}" + self._attr_unique_id = f"{description.key}_{coordinator.api.data.serial}" + # Configure the Device Info + self._attr_device_info = self.coordinator.device_info diff --git a/homeassistant/components/intellifire/sensor.py b/homeassistant/components/intellifire/sensor.py index 991e4e69e8c1e2..7c52581498b78f 100644 --- a/homeassistant/components/intellifire/sensor.py +++ b/homeassistant/components/intellifire/sensor.py @@ -17,47 +17,11 @@ from homeassistant.const import TEMP_CELSIUS from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.util.dt import utcnow from . import IntellifireDataUpdateCoordinator from .const import DOMAIN - - -class IntellifireSensor(CoordinatorEntity, SensorEntity): - """Define a generic class for Sensors.""" - - # Define types - coordinator: IntellifireDataUpdateCoordinator - entity_description: IntellifireSensorEntityDescription - _attr_attribution = "Data provided by unpublished Intellifire API" - - def __init__( - self, - coordinator: IntellifireDataUpdateCoordinator, - description: IntellifireSensorEntityDescription, - ) -> None: - """Init the sensor.""" - super().__init__(coordinator=coordinator) - self.entity_description = description - - # Set the Display name the User will see - self._attr_name = f"Fireplace {description.name}" - self._attr_unique_id = f"{description.key}_{coordinator.api.data.serial}" - # Configure the Device Info - self._attr_device_info = self.coordinator.device_info - - @property - def native_value(self) -> int | str | datetime | None: - """Return the state.""" - return self.entity_description.value_fn(self.coordinator.api.data) - - -def _time_remaining_to_timestamp(data: IntellifirePollData) -> datetime | None: - """Define a sensor that takes into account timezone.""" - if not (seconds_offset := data.timeremaining_s): - return None - return utcnow() + timedelta(seconds=seconds_offset) +from .entity import IntellifireEntity @dataclass @@ -69,21 +33,17 @@ class IntellifireSensorRequiredKeysMixin: @dataclass class IntellifireSensorEntityDescription( - SensorEntityDescription, IntellifireSensorRequiredKeysMixin + SensorEntityDescription, + IntellifireSensorRequiredKeysMixin, ): - """Describes a sensor sensor entity.""" + """Describes a sensor entity.""" -async def async_setup_entry( - hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback -) -> None: - """Define setup entry call.""" - - coordinator: IntellifireDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - async_add_entities( - IntellifireSensor(coordinator=coordinator, description=description) - for description in INTELLIFIRE_SENSORS - ) +def _time_remaining_to_timestamp(data: IntellifirePollData) -> datetime | None: + """Define a sensor that takes into account timezone.""" + if not (seconds_offset := data.timeremaining_s): + return None + return utcnow() + timedelta(seconds=seconds_offset) INTELLIFIRE_SENSORS: tuple[IntellifireSensorEntityDescription, ...] = ( @@ -126,3 +86,26 @@ async def async_setup_entry( value_fn=_time_remaining_to_timestamp, ), ) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Define setup entry call.""" + + coordinator: IntellifireDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + IntellifireSensor(coordinator=coordinator, description=description) + for description in INTELLIFIRE_SENSORS + ) + + +class IntellifireSensor(IntellifireEntity, SensorEntity): + """Extends IntellifireEntity with Sensor specific logic.""" + + entity_description: IntellifireSensorEntityDescription + + @property + def native_value(self) -> int | str | datetime | None: + """Return the state.""" + return self.entity_description.value_fn(self.coordinator.api.data) From 9610fa597905593c928b3cb13785835f2bf3c9a8 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 12 Feb 2022 01:04:04 +0100 Subject: [PATCH 0558/1098] Code cleanup yale_smart_alarm (#65081) --- .../yale_smart_alarm/config_flow.py | 24 ++++++++++--------- .../yale_smart_alarm/test_config_flow.py | 4 ---- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/yale_smart_alarm/config_flow.py b/homeassistant/components/yale_smart_alarm/config_flow.py index 1567f22be4471c..62daa639f50265 100644 --- a/homeassistant/components/yale_smart_alarm/config_flow.py +++ b/homeassistant/components/yale_smart_alarm/config_flow.py @@ -27,7 +27,6 @@ { vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Required(CONF_AREA_ID, default=DEFAULT_AREA_ID): cv.string, } ) @@ -45,7 +44,7 @@ class YaleConfigFlow(ConfigFlow, domain=DOMAIN): VERSION = 1 - entry: ConfigEntry + entry: ConfigEntry | None @staticmethod @callback @@ -53,20 +52,21 @@ def async_get_options_flow(config_entry: ConfigEntry) -> YaleOptionsFlowHandler: """Get the options flow for this handler.""" return YaleOptionsFlowHandler(config_entry) - async def async_step_import(self, config: dict): + async def async_step_import(self, config: dict[str, Any]) -> FlowResult: """Import a configuration from config.yaml.""" - self.context.update( - {"title_placeholders": {CONF_NAME: f"YAML import {DOMAIN}"}} - ) return await self.async_step_user(user_input=config) - async def async_step_reauth(self, user_input=None): + async def async_step_reauth( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle initiation of re-authentication with Yale.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) return await self.async_step_reauth_confirm() - async def async_step_reauth_confirm(self, user_input=None): + async def async_step_reauth_confirm( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Dialog that informs the user that reauth is required.""" errors = {} @@ -87,7 +87,7 @@ async def async_step_reauth_confirm(self, user_input=None): if not errors: existing_entry = await self.async_set_unique_id(username) - if existing_entry: + if existing_entry and self.entry: self.hass.config_entries.async_update_entry( existing_entry, data={ @@ -105,14 +105,16 @@ async def async_step_reauth_confirm(self, user_input=None): errors=errors, ) - async def async_step_user(self, user_input=None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle the initial step.""" errors = {} if user_input is not None: username = user_input[CONF_USERNAME] password = user_input[CONF_PASSWORD] - name = user_input.get(CONF_NAME, DEFAULT_NAME) + name = DEFAULT_NAME area = user_input.get(CONF_AREA_ID, DEFAULT_AREA_ID) try: diff --git a/tests/components/yale_smart_alarm/test_config_flow.py b/tests/components/yale_smart_alarm/test_config_flow.py index a7c454851bf6c6..fee5e5ab97ab96 100644 --- a/tests/components/yale_smart_alarm/test_config_flow.py +++ b/tests/components/yale_smart_alarm/test_config_flow.py @@ -38,7 +38,6 @@ async def test_form(hass: HomeAssistant) -> None: { "username": "test-username", "password": "test-password", - "name": "Yale Smart Alarm", "area_id": "1", }, ) @@ -81,7 +80,6 @@ async def test_form_invalid_auth( { "username": "test-username", "password": "test-password", - "name": "Yale Smart Alarm", "area_id": "1", }, ) @@ -101,7 +99,6 @@ async def test_form_invalid_auth( { "username": "test-username", "password": "test-password", - "name": "Yale Smart Alarm", "area_id": "1", }, ) @@ -124,7 +121,6 @@ async def test_form_invalid_auth( { "username": "test-username", "password": "test-password", - "name": "Yale Smart Alarm", "area_id": "1", }, { From 202ee0cd3d7cbf9bd9cfc10a7a04d026c7bb937a Mon Sep 17 00:00:00 2001 From: Sander Jochems Date: Sat, 12 Feb 2022 01:04:50 +0100 Subject: [PATCH 0559/1098] Add device info to Solax (#65244) --- homeassistant/components/solax/const.py | 2 ++ homeassistant/components/solax/sensor.py | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/solax/const.py b/homeassistant/components/solax/const.py index bf8abe19af162b..65894013adc41d 100644 --- a/homeassistant/components/solax/const.py +++ b/homeassistant/components/solax/const.py @@ -2,3 +2,5 @@ DOMAIN = "solax" + +MANUFACTURER = "SolaX Power" diff --git a/homeassistant/components/solax/sensor.py b/homeassistant/components/solax/sensor.py index 83c1ace569dcff..6f1b5ef6cf3e48 100644 --- a/homeassistant/components/solax/sensor.py +++ b/homeassistant/components/solax/sensor.py @@ -19,11 +19,12 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from .const import DOMAIN +from .const import DOMAIN, MANUFACTURER _LOGGER = logging.getLogger(__name__) @@ -46,6 +47,7 @@ async def async_setup_entry( api = hass.data[DOMAIN][entry.entry_id] resp = await api.get_data() serial = resp.serial_number + version = resp.version endpoint = RealTimeDataEndpoint(hass, api) hass.async_add_job(endpoint.async_refresh) async_track_time_interval(hass, endpoint.async_refresh, SCAN_INTERVAL) @@ -72,7 +74,9 @@ async def async_setup_entry( device_class = SensorDeviceClass.BATTERY state_class = SensorStateClass.MEASUREMENT uid = f"{serial}-{idx}" - devices.append(Inverter(uid, serial, sensor, unit, state_class, device_class)) + devices.append( + Inverter(uid, serial, version, sensor, unit, state_class, device_class) + ) endpoint.sensors = devices async_add_entities(devices) @@ -140,6 +144,7 @@ def __init__( self, uid, serial, + version, key, unit, state_class=None, @@ -151,6 +156,12 @@ def __init__( self._attr_native_unit_of_measurement = unit self._attr_state_class = state_class self._attr_device_class = device_class + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, serial)}, + manufacturer=MANUFACTURER, + name=f"Solax {serial}", + sw_version=version, + ) self.key = key self.value = None From 366609ea4461ba3ae125be33c7184c4d009ef4e1 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sat, 12 Feb 2022 00:16:37 +0000 Subject: [PATCH 0560/1098] [ci skip] Translation update --- .../components/fivem/translations/pl.json | 6 ++++-- .../components/konnected/translations/it.json | 4 ++-- .../moehlenhoff_alpha2/translations/bg.json | 18 ++++++++++++++++++ .../moehlenhoff_alpha2/translations/pl.json | 19 +++++++++++++++++++ .../translations/pt-BR.json | 6 +++--- .../components/overkiz/translations/bg.json | 1 + .../components/plant/translations/pt-BR.json | 2 +- .../powerwall/translations/pt-BR.json | 2 +- .../components/ps4/translations/it.json | 2 +- .../components/wiz/translations/pl.json | 7 ++++--- .../components/zwave_js/translations/it.json | 2 +- 11 files changed, 55 insertions(+), 14 deletions(-) create mode 100644 homeassistant/components/moehlenhoff_alpha2/translations/bg.json create mode 100644 homeassistant/components/moehlenhoff_alpha2/translations/pl.json diff --git a/homeassistant/components/fivem/translations/pl.json b/homeassistant/components/fivem/translations/pl.json index 592db241d21285..420ebca7463e5f 100644 --- a/homeassistant/components/fivem/translations/pl.json +++ b/homeassistant/components/fivem/translations/pl.json @@ -1,11 +1,13 @@ { "config": { "abort": { - "already_configured": "Serwer FiveM jest ju\u017c skonfigurowany" + "already_configured": "Us\u0142uga jest ju\u017c skonfigurowana" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia. Sprawd\u017a adres hosta oraz port i spr\u00f3buj ponownie. Upewnij si\u0119, \u017ce posiadasz najnowsz\u0105 wersj\u0119 serwera FiveM.", - "invalid_gamename": "API gry, do kt\u00f3rej pr\u00f3bujesz si\u0119 po\u0142\u0105czy\u0107, nie jest gr\u0105 FiveM." + "invalid_game_name": "API gry, do kt\u00f3rej pr\u00f3bujesz si\u0119 po\u0142\u0105czy\u0107, nie jest gr\u0105 FiveM.", + "invalid_gamename": "API gry, do kt\u00f3rej pr\u00f3bujesz si\u0119 po\u0142\u0105czy\u0107, nie jest gr\u0105 FiveM.", + "unknown_error": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/konnected/translations/it.json b/homeassistant/components/konnected/translations/it.json index 78190aff5b3617..81127f0dff2c64 100644 --- a/homeassistant/components/konnected/translations/it.json +++ b/homeassistant/components/konnected/translations/it.json @@ -81,8 +81,8 @@ "alarm2_out2": "OUT2 / ALARM2", "out1": "OUT1" }, - "description": "Selezionare di seguito la configurazione degli I/O rimanenti. Potrete configurare opzioni dettagliate nei prossimi passi.", - "title": "Configurazione I/O Esteso" + "description": "Selezionare di seguito la configurazione degli I/O rimanenti. Potrai configurare opzioni dettagliate nei prossimi passi.", + "title": "Configurazione I/O esteso" }, "options_misc": { "data": { diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/bg.json b/homeassistant/components/moehlenhoff_alpha2/translations/bg.json new file mode 100644 index 00000000000000..cbf1e2ae7c9b34 --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/translations/bg.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e" + }, + "error": { + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/pl.json b/homeassistant/components/moehlenhoff_alpha2/translations/pl.json new file mode 100644 index 00000000000000..6fa4bed7b54c15 --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/translations/pl.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP" + } + } + } + }, + "title": "M\u00f6hlenhoff Alpha2" +} \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/pt-BR.json b/homeassistant/components/moehlenhoff_alpha2/translations/pt-BR.json index b5f02d5b3d5a8b..d322f291553979 100644 --- a/homeassistant/components/moehlenhoff_alpha2/translations/pt-BR.json +++ b/homeassistant/components/moehlenhoff_alpha2/translations/pt-BR.json @@ -1,16 +1,16 @@ { "config": { "abort": { - "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "error": { - "cannot_connect": "Falhou ao conectar", + "cannot_connect": "Falha ao conectar", "unknown": "Erro inesperado" }, "step": { "user": { "data": { - "host": "Host" + "host": "Nome do host" } } } diff --git a/homeassistant/components/overkiz/translations/bg.json b/homeassistant/components/overkiz/translations/bg.json index bca9ff034712b0..afee50a6b0067f 100644 --- a/homeassistant/components/overkiz/translations/bg.json +++ b/homeassistant/components/overkiz/translations/bg.json @@ -12,6 +12,7 @@ "too_many_requests": "\u0422\u0432\u044a\u0440\u0434\u0435 \u043c\u043d\u043e\u0433\u043e \u0437\u0430\u044f\u0432\u043a\u0438, \u043e\u043f\u0438\u0442\u0430\u0439\u0442\u0435 \u043e\u0442\u043d\u043e\u0432\u043e \u043f\u043e-\u043a\u044a\u0441\u043d\u043e.", "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" }, + "flow_title": "\u0428\u043b\u044e\u0437: {gateway_id}", "step": { "user": { "data": { diff --git a/homeassistant/components/plant/translations/pt-BR.json b/homeassistant/components/plant/translations/pt-BR.json index 4a720d6cb77913..25b0fe796dac4b 100644 --- a/homeassistant/components/plant/translations/pt-BR.json +++ b/homeassistant/components/plant/translations/pt-BR.json @@ -1,7 +1,7 @@ { "state": { "_": { - "ok": "Ok", + "ok": "OK", "problem": "Problema" } }, diff --git a/homeassistant/components/powerwall/translations/pt-BR.json b/homeassistant/components/powerwall/translations/pt-BR.json index ea40df76ae7288..82d64eff575279 100644 --- a/homeassistant/components/powerwall/translations/pt-BR.json +++ b/homeassistant/components/powerwall/translations/pt-BR.json @@ -11,7 +11,7 @@ "unknown": "Erro inesperado", "wrong_version": "Seu powerwall usa uma vers\u00e3o de software que n\u00e3o \u00e9 compat\u00edvel. Considere atualizar ou relatar este problema para que ele possa ser resolvido." }, - "flow_title": "{name} ( {ip_address})", + "flow_title": "{name} ({ip_address})", "step": { "confirm_discovery": { "description": "Deseja configurar {name} ({ip_address})?", diff --git a/homeassistant/components/ps4/translations/it.json b/homeassistant/components/ps4/translations/it.json index 4d2390d89c585e..0a7db1888f6d5d 100644 --- a/homeassistant/components/ps4/translations/it.json +++ b/homeassistant/components/ps4/translations/it.json @@ -33,7 +33,7 @@ "ip_address": "Indirizzo IP (Lascia vuoto se stai usando il rilevamento automatico).", "mode": "Modalit\u00e0 di configurazione" }, - "description": "Seleziona la modalit\u00e0 per la configurazione. Il campo per l'indiriizzo IP pu\u00f2 essere lasciato vuoto se si seleziona il rilevamento automatico, poich\u00e9 i dispositivi saranno automaticamente individuati.", + "description": "Seleziona la modalit\u00e0 per la configurazione. Il campo per l'indirizzo IP pu\u00f2 essere lasciato vuoto se si seleziona il rilevamento automatico, poich\u00e9 i dispositivi saranno automaticamente individuati.", "title": "PlayStation 4" } } diff --git a/homeassistant/components/wiz/translations/pl.json b/homeassistant/components/wiz/translations/pl.json index 7a3523ede16240..60de0710e40af4 100644 --- a/homeassistant/components/wiz/translations/pl.json +++ b/homeassistant/components/wiz/translations/pl.json @@ -5,8 +5,9 @@ "no_devices_found": "Nie znaleziono urz\u0105dze\u0144 w sieci" }, "error": { - "bulb_time_out": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z \u017car\u00f3wk\u0105. Mo\u017ce \u017car\u00f3wka jest w trybie offline lub wprowadzono z\u0142y adres IP/host. W\u0142\u0105cz \u015bwiat\u0142o i spr\u00f3buj ponownie!", + "bulb_time_out": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z \u017car\u00f3wk\u0105. Mo\u017ce \u017car\u00f3wka jest w trybie offline lub wprowadzono z\u0142y adres IP. W\u0142\u0105cz \u015bwiat\u0142o i spr\u00f3buj ponownie!", "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "no_ip": "Nieprawid\u0142owy adres IP", "no_wiz_light": "\u017bar\u00f3wka nie mo\u017ce by\u0107 pod\u0142\u0105czona poprzez integracj\u0119 z platform\u0105 WiZ.", "unknown": "Nieoczekiwany b\u0142\u0105d" }, @@ -25,10 +26,10 @@ }, "user": { "data": { - "host": "Nazwa hosta lub adres IP", + "host": "[%key::common::config_flow::data::ip%]", "name": "Nazwa" }, - "description": "Je\u015bli nie podasz IP lub nazwy hosta, zostanie u\u017cyte wykrywanie do odnalezienia urz\u0105dze\u0144." + "description": "Je\u015bli nie podasz adresu IP, zostanie u\u017cyte wykrywanie do odnalezienia urz\u0105dze\u0144." } } } diff --git a/homeassistant/components/zwave_js/translations/it.json b/homeassistant/components/zwave_js/translations/it.json index bab70cd4b948eb..d455b7befe68bc 100644 --- a/homeassistant/components/zwave_js/translations/it.json +++ b/homeassistant/components/zwave_js/translations/it.json @@ -97,7 +97,7 @@ "addon_start_failed": "Impossibile avviare il componente aggiuntivo Z-Wave JS.", "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "cannot_connect": "Impossibile connettersi", - "different_device": "Il dispositivo USB connesso non \u00e8 lo stesso configurato in precedenza per questa voce di configurazione. Si prega, invece, di creare una nuova voce di configurazione per il nuovo dispositivo." + "different_device": "Il dispositivo USB connesso non \u00e8 lo stesso configurato in precedenza per questa voce di configurazione. Crea invece una nuova voce di configurazione per il nuovo dispositivo." }, "error": { "cannot_connect": "Impossibile connettersi", From 578456bbb5f9f6b30b41d7327f9d68db08e8cae8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 11 Feb 2022 20:42:41 -0600 Subject: [PATCH 0561/1098] Fix uncaught exception during WiZ discovery during firmware update (#66358) --- homeassistant/components/wiz/__init__.py | 11 ++++++++--- homeassistant/components/wiz/config_flow.py | 6 +++--- homeassistant/components/wiz/const.py | 7 ++++++- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/wiz/__init__.py b/homeassistant/components/wiz/__init__.py index abdbbf1191a3bc..9a4444c523ef26 100644 --- a/homeassistant/components/wiz/__init__.py +++ b/homeassistant/components/wiz/__init__.py @@ -5,7 +5,6 @@ from typing import Any from pywizlight import wizlight -from pywizlight.exceptions import WizLightNotKnownBulb from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, Platform @@ -16,7 +15,13 @@ from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import DISCOVER_SCAN_TIMEOUT, DISCOVERY_INTERVAL, DOMAIN, WIZ_EXCEPTIONS +from .const import ( + DISCOVER_SCAN_TIMEOUT, + DISCOVERY_INTERVAL, + DOMAIN, + WIZ_CONNECT_EXCEPTIONS, + WIZ_EXCEPTIONS, +) from .discovery import async_discover_devices, async_trigger_discovery from .models import WizData @@ -48,7 +53,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: try: scenes = await bulb.getSupportedScenes() await bulb.getMac() - except (WizLightNotKnownBulb, *WIZ_EXCEPTIONS) as err: + except WIZ_CONNECT_EXCEPTIONS as err: raise ConfigEntryNotReady(f"{ip_address}: {err}") from err async def _async_update() -> None: diff --git a/homeassistant/components/wiz/config_flow.py b/homeassistant/components/wiz/config_flow.py index aa564a14f333b8..924e88793e47ff 100644 --- a/homeassistant/components/wiz/config_flow.py +++ b/homeassistant/components/wiz/config_flow.py @@ -15,7 +15,7 @@ from homeassistant.data_entry_flow import AbortFlow, FlowResult from homeassistant.util.network import is_ip_address -from .const import DEFAULT_NAME, DISCOVER_SCAN_TIMEOUT, DOMAIN, WIZ_EXCEPTIONS +from .const import DEFAULT_NAME, DISCOVER_SCAN_TIMEOUT, DOMAIN, WIZ_CONNECT_EXCEPTIONS from .discovery import async_discover_devices from .utils import _short_mac, name_from_bulb_type_and_mac @@ -70,7 +70,7 @@ async def _async_connect_discovered_or_abort(self) -> None: bulb = wizlight(device.ip_address) try: bulbtype = await bulb.get_bulbtype() - except WIZ_EXCEPTIONS as ex: + except WIZ_CONNECT_EXCEPTIONS as ex: raise AbortFlow("cannot_connect") from ex self._name = name_from_bulb_type_and_mac(bulbtype, device.mac_address) @@ -110,7 +110,7 @@ async def async_step_pick_device( bulb = wizlight(device.ip_address) try: bulbtype = await bulb.get_bulbtype() - except WIZ_EXCEPTIONS: + except WIZ_CONNECT_EXCEPTIONS: return self.async_abort(reason="cannot_connect") else: return self.async_create_entry( diff --git a/homeassistant/components/wiz/const.py b/homeassistant/components/wiz/const.py index e1a9848263583a..d1b3a0f62512e1 100644 --- a/homeassistant/components/wiz/const.py +++ b/homeassistant/components/wiz/const.py @@ -1,7 +1,11 @@ """Constants for the WiZ Platform integration.""" from datetime import timedelta -from pywizlight.exceptions import WizLightConnectionError, WizLightTimeOutError +from pywizlight.exceptions import ( + WizLightConnectionError, + WizLightNotKnownBulb, + WizLightTimeOutError, +) DOMAIN = "wiz" DEFAULT_NAME = "WiZ" @@ -16,3 +20,4 @@ WizLightConnectionError, ConnectionRefusedError, ) +WIZ_CONNECT_EXCEPTIONS = (WizLightNotKnownBulb, *WIZ_EXCEPTIONS) From f344ea7bbba974c7592a1dcde8cfa7ccc71f3f0d Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Fri, 11 Feb 2022 20:52:31 -0600 Subject: [PATCH 0562/1098] Add select platform to roku (#66133) --- homeassistant/components/roku/__init__.py | 1 + homeassistant/components/roku/browse_media.py | 7 +- homeassistant/components/roku/helpers.py | 10 + homeassistant/components/roku/manifest.json | 2 +- homeassistant/components/roku/media_player.py | 6 +- homeassistant/components/roku/select.py | 174 +++++++++++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/roku/conftest.py | 28 +- .../roku/fixtures/rokutv-7820x.json | 12 + tests/components/roku/test_binary_sensor.py | 4 +- tests/components/roku/test_config_flow.py | 4 +- tests/components/roku/test_media_player.py | 26 +- tests/components/roku/test_select.py | 241 ++++++++++++++++++ tests/components/roku/test_sensor.py | 2 +- 15 files changed, 484 insertions(+), 37 deletions(-) create mode 100644 homeassistant/components/roku/helpers.py create mode 100644 homeassistant/components/roku/select.py create mode 100644 tests/components/roku/test_select.py diff --git a/homeassistant/components/roku/__init__.py b/homeassistant/components/roku/__init__.py index e76921b945b67d..e6e31f087132ed 100644 --- a/homeassistant/components/roku/__init__.py +++ b/homeassistant/components/roku/__init__.py @@ -24,6 +24,7 @@ Platform.BINARY_SENSOR, Platform.MEDIA_PLAYER, Platform.REMOTE, + Platform.SELECT, Platform.SENSOR, ] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/roku/browse_media.py b/homeassistant/components/roku/browse_media.py index 7aed3849ce866a..d8cd540e613b52 100644 --- a/homeassistant/components/roku/browse_media.py +++ b/homeassistant/components/roku/browse_media.py @@ -21,6 +21,7 @@ from homeassistant.helpers.network import is_internal_request from .coordinator import RokuDataUpdateCoordinator +from .helpers import format_channel_name CONTENT_TYPE_MEDIA_CLASS = { MEDIA_TYPE_APP: MEDIA_CLASS_APP, @@ -191,11 +192,11 @@ def build_item_response( title = "TV Channels" media = [ { - "channel_number": item.number, - "title": item.name, + "channel_number": channel.number, + "title": format_channel_name(channel.number, channel.name), "type": MEDIA_TYPE_CHANNEL, } - for item in coordinator.data.channels + for channel in coordinator.data.channels ] children_media_class = MEDIA_CLASS_CHANNEL diff --git a/homeassistant/components/roku/helpers.py b/homeassistant/components/roku/helpers.py new file mode 100644 index 00000000000000..7f507a9fe52c8f --- /dev/null +++ b/homeassistant/components/roku/helpers.py @@ -0,0 +1,10 @@ +"""Helpers for Roku.""" +from __future__ import annotations + + +def format_channel_name(channel_number: str, channel_name: str | None = None) -> str: + """Format a Roku Channel name.""" + if channel_name is not None and channel_name != "": + return f"{channel_name} ({channel_number})" + + return channel_number diff --git a/homeassistant/components/roku/manifest.json b/homeassistant/components/roku/manifest.json index 619672e8f1fcca..d68f2b4b24212c 100644 --- a/homeassistant/components/roku/manifest.json +++ b/homeassistant/components/roku/manifest.json @@ -2,7 +2,7 @@ "domain": "roku", "name": "Roku", "documentation": "https://www.home-assistant.io/integrations/roku", - "requirements": ["rokuecp==0.13.1"], + "requirements": ["rokuecp==0.13.2"], "homekit": { "models": ["3810X", "4660X", "7820X", "C105X", "C135X"] }, diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index 48b85f5912cab5..ff9e034e5d434e 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -58,6 +58,7 @@ ) from .coordinator import RokuDataUpdateCoordinator from .entity import RokuEntity +from .helpers import format_channel_name _LOGGER = logging.getLogger(__name__) @@ -212,10 +213,9 @@ def media_channel(self) -> str | None: if self.app_id != "tvinput.dtv" or self.coordinator.data.channel is None: return None - if self.coordinator.data.channel.name is not None: - return f"{self.coordinator.data.channel.name} ({self.coordinator.data.channel.number})" + channel = self.coordinator.data.channel - return self.coordinator.data.channel.number + return format_channel_name(channel.number, channel.name) @property def media_title(self) -> str | None: diff --git a/homeassistant/components/roku/select.py b/homeassistant/components/roku/select.py new file mode 100644 index 00000000000000..9120a4fe9cef22 --- /dev/null +++ b/homeassistant/components/roku/select.py @@ -0,0 +1,174 @@ +"""Support for Roku selects.""" +from __future__ import annotations + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass + +from rokuecp import Roku +from rokuecp.models import Device as RokuDevice + +from homeassistant.components.select import SelectEntity, SelectEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import roku_exception_handler +from .const import DOMAIN +from .coordinator import RokuDataUpdateCoordinator +from .entity import RokuEntity +from .helpers import format_channel_name + + +@dataclass +class RokuSelectEntityDescriptionMixin: + """Mixin for required keys.""" + + options_fn: Callable[[RokuDevice], list[str]] + value_fn: Callable[[RokuDevice], str | None] + set_fn: Callable[[RokuDevice, Roku, str], Awaitable[None]] + + +def _get_application_name(device: RokuDevice) -> str | None: + if device.app is None or device.app.name is None: + return None + + if device.app.name == "Roku": + return "Home" + + return device.app.name + + +def _get_applications(device: RokuDevice) -> list[str]: + return ["Home"] + sorted(app.name for app in device.apps if app.name is not None) + + +def _get_channel_name(device: RokuDevice) -> str | None: + if device.channel is None: + return None + + return format_channel_name(device.channel.number, device.channel.name) + + +def _get_channels(device: RokuDevice) -> list[str]: + return sorted( + format_channel_name(channel.number, channel.name) for channel in device.channels + ) + + +async def _launch_application(device: RokuDevice, roku: Roku, value: str) -> None: + if value == "Home": + await roku.remote("home") + + appl = next( + (app for app in device.apps if value == app.name), + None, + ) + + if appl is not None and appl.app_id is not None: + await roku.launch(appl.app_id) + + +async def _tune_channel(device: RokuDevice, roku: Roku, value: str) -> None: + _channel = next( + ( + channel + for channel in device.channels + if ( + channel.name is not None + and value == format_channel_name(channel.number, channel.name) + ) + or value == channel.number + ), + None, + ) + + if _channel is not None: + await roku.tune(_channel.number) + + +@dataclass +class RokuSelectEntityDescription( + SelectEntityDescription, RokuSelectEntityDescriptionMixin +): + """Describes Roku select entity.""" + + +ENTITIES: tuple[RokuSelectEntityDescription, ...] = ( + RokuSelectEntityDescription( + key="application", + name="Application", + icon="mdi:application", + set_fn=_launch_application, + value_fn=_get_application_name, + options_fn=_get_applications, + entity_registry_enabled_default=False, + ), +) + +CHANNEL_ENTITY = RokuSelectEntityDescription( + key="channel", + name="Channel", + icon="mdi:television", + set_fn=_tune_channel, + value_fn=_get_channel_name, + options_fn=_get_channels, +) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Roku select based on a config entry.""" + coordinator: RokuDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + device: RokuDevice = coordinator.data + unique_id = device.info.serial_number + + entities: list[RokuSelectEntity] = [] + + for description in ENTITIES: + entities.append( + RokuSelectEntity( + device_id=unique_id, + coordinator=coordinator, + description=description, + ) + ) + + if len(device.channels) > 0: + entities.append( + RokuSelectEntity( + device_id=unique_id, + coordinator=coordinator, + description=CHANNEL_ENTITY, + ) + ) + + async_add_entities(entities) + + +class RokuSelectEntity(RokuEntity, SelectEntity): + """Defines a Roku select entity.""" + + entity_description: RokuSelectEntityDescription + + @property + def current_option(self) -> str | None: + """Return the current value.""" + return self.entity_description.value_fn(self.coordinator.data) + + @property + def options(self) -> list[str]: + """Return a set of selectable options.""" + return self.entity_description.options_fn(self.coordinator.data) + + @roku_exception_handler + async def async_select_option(self, option: str) -> None: + """Set the option.""" + await self.entity_description.set_fn( + self.coordinator.data, + self.coordinator.roku, + option, + ) + await self.coordinator.async_request_refresh() diff --git a/requirements_all.txt b/requirements_all.txt index b2a15ae0064c1b..394dd8099bc3ab 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2117,7 +2117,7 @@ rjpl==0.3.6 rocketchat-API==0.6.1 # homeassistant.components.roku -rokuecp==0.13.1 +rokuecp==0.13.2 # homeassistant.components.roomba roombapy==1.6.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 03a713478e6422..4f81c60171c68e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1306,7 +1306,7 @@ rflink==0.0.62 ring_doorbell==0.7.2 # homeassistant.components.roku -rokuecp==0.13.1 +rokuecp==0.13.2 # homeassistant.components.roomba roombapy==1.6.5 diff --git a/tests/components/roku/conftest.py b/tests/components/roku/conftest.py index 16261e07a89108..677a10c697cb2a 100644 --- a/tests/components/roku/conftest.py +++ b/tests/components/roku/conftest.py @@ -38,38 +38,44 @@ def mock_setup_entry() -> Generator[None, None, None]: @pytest.fixture -def mock_roku_config_flow( +async def mock_device( request: pytest.FixtureRequest, -) -> Generator[None, MagicMock, None]: - """Return a mocked Roku client.""" +) -> RokuDevice: + """Return the mocked roku device.""" fixture: str = "roku/roku3.json" if hasattr(request, "param") and request.param: fixture = request.param - device = RokuDevice(json.loads(load_fixture(fixture))) + return RokuDevice(json.loads(load_fixture(fixture))) + + +@pytest.fixture +def mock_roku_config_flow( + mock_device: RokuDevice, +) -> Generator[None, MagicMock, None]: + """Return a mocked Roku client.""" + with patch( "homeassistant.components.roku.config_flow.Roku", autospec=True ) as roku_mock: client = roku_mock.return_value client.app_icon_url.side_effect = app_icon_url - client.update.return_value = device + client.update.return_value = mock_device yield client @pytest.fixture -def mock_roku(request: pytest.FixtureRequest) -> Generator[None, MagicMock, None]: +def mock_roku( + request: pytest.FixtureRequest, mock_device: RokuDevice +) -> Generator[None, MagicMock, None]: """Return a mocked Roku client.""" - fixture: str = "roku/roku3.json" - if hasattr(request, "param") and request.param: - fixture = request.param - device = RokuDevice(json.loads(load_fixture(fixture))) with patch( "homeassistant.components.roku.coordinator.Roku", autospec=True ) as roku_mock: client = roku_mock.return_value client.app_icon_url.side_effect = app_icon_url - client.update.return_value = device + client.update.return_value = mock_device yield client diff --git a/tests/components/roku/fixtures/rokutv-7820x.json b/tests/components/roku/fixtures/rokutv-7820x.json index 42181b087458ba..17c29ace2de1d5 100644 --- a/tests/components/roku/fixtures/rokutv-7820x.json +++ b/tests/components/roku/fixtures/rokutv-7820x.json @@ -167,6 +167,18 @@ "name": "QVC", "type": "air-digital", "user-hidden": "false" + }, + { + "number": "14.3", + "name": "getTV", + "type": "air-digital", + "user-hidden": "false" + }, + { + "number": "99.1", + "name": "", + "type": "air-digital", + "user-hidden": "false" } ], "media": { diff --git a/tests/components/roku/test_binary_sensor.py b/tests/components/roku/test_binary_sensor.py index d551a548c4c10b..24f92b0b11b610 100644 --- a/tests/components/roku/test_binary_sensor.py +++ b/tests/components/roku/test_binary_sensor.py @@ -2,6 +2,7 @@ from unittest.mock import MagicMock import pytest +from rokuecp import Device as RokuDevice from homeassistant.components.binary_sensor import STATE_OFF, STATE_ON from homeassistant.components.roku.const import DOMAIN @@ -82,10 +83,11 @@ async def test_roku_binary_sensors( assert device_entry.suggested_area is None -@pytest.mark.parametrize("mock_roku", ["roku/rokutv-7820x.json"], indirect=True) +@pytest.mark.parametrize("mock_device", ["roku/rokutv-7820x.json"], indirect=True) async def test_rokutv_binary_sensors( hass: HomeAssistant, init_integration: MockConfigEntry, + mock_device: RokuDevice, mock_roku: MagicMock, ) -> None: """Test the Roku binary sensors.""" diff --git a/tests/components/roku/test_config_flow.py b/tests/components/roku/test_config_flow.py index 99d0d1bb2c0150..f5a3d270f70878 100644 --- a/tests/components/roku/test_config_flow.py +++ b/tests/components/roku/test_config_flow.py @@ -158,9 +158,7 @@ async def test_homekit_unknown_error( assert result["reason"] == "unknown" -@pytest.mark.parametrize( - "mock_roku_config_flow", ["roku/rokutv-7820x.json"], indirect=True -) +@pytest.mark.parametrize("mock_device", ["roku/rokutv-7820x.json"], indirect=True) async def test_homekit_discovery( hass: HomeAssistant, mock_roku_config_flow: MagicMock, diff --git a/tests/components/roku/test_media_player.py b/tests/components/roku/test_media_player.py index a039b313702acf..2686a281dba235 100644 --- a/tests/components/roku/test_media_player.py +++ b/tests/components/roku/test_media_player.py @@ -115,7 +115,7 @@ async def test_setup(hass: HomeAssistant, init_integration: MockConfigEntry) -> assert device_entry.suggested_area is None -@pytest.mark.parametrize("mock_roku", ["roku/roku3-idle.json"], indirect=True) +@pytest.mark.parametrize("mock_device", ["roku/roku3-idle.json"], indirect=True) async def test_idle_setup( hass: HomeAssistant, init_integration: MockConfigEntry, @@ -127,7 +127,7 @@ async def test_idle_setup( assert state.state == STATE_STANDBY -@pytest.mark.parametrize("mock_roku", ["roku/rokutv-7820x.json"], indirect=True) +@pytest.mark.parametrize("mock_device", ["roku/rokutv-7820x.json"], indirect=True) async def test_tv_setup( hass: HomeAssistant, init_integration: MockConfigEntry, @@ -215,7 +215,7 @@ async def test_supported_features( ) -@pytest.mark.parametrize("mock_roku", ["roku/rokutv-7820x.json"], indirect=True) +@pytest.mark.parametrize("mock_device", ["roku/rokutv-7820x.json"], indirect=True) async def test_tv_supported_features( hass: HomeAssistant, init_integration: MockConfigEntry, @@ -254,7 +254,7 @@ async def test_attributes( assert state.attributes.get(ATTR_INPUT_SOURCE) == "Roku" -@pytest.mark.parametrize("mock_roku", ["roku/roku3-app.json"], indirect=True) +@pytest.mark.parametrize("mock_device", ["roku/roku3-app.json"], indirect=True) async def test_attributes_app( hass: HomeAssistant, init_integration: MockConfigEntry, @@ -271,7 +271,9 @@ async def test_attributes_app( assert state.attributes.get(ATTR_INPUT_SOURCE) == "Netflix" -@pytest.mark.parametrize("mock_roku", ["roku/roku3-media-playing.json"], indirect=True) +@pytest.mark.parametrize( + "mock_device", ["roku/roku3-media-playing.json"], indirect=True +) async def test_attributes_app_media_playing( hass: HomeAssistant, init_integration: MockConfigEntry, @@ -290,7 +292,7 @@ async def test_attributes_app_media_playing( assert state.attributes.get(ATTR_INPUT_SOURCE) == "Pluto TV - It's Free TV" -@pytest.mark.parametrize("mock_roku", ["roku/roku3-media-paused.json"], indirect=True) +@pytest.mark.parametrize("mock_device", ["roku/roku3-media-paused.json"], indirect=True) async def test_attributes_app_media_paused( hass: HomeAssistant, init_integration: MockConfigEntry, @@ -309,7 +311,7 @@ async def test_attributes_app_media_paused( assert state.attributes.get(ATTR_INPUT_SOURCE) == "Pluto TV - It's Free TV" -@pytest.mark.parametrize("mock_roku", ["roku/roku3-screensaver.json"], indirect=True) +@pytest.mark.parametrize("mock_device", ["roku/roku3-screensaver.json"], indirect=True) async def test_attributes_screensaver( hass: HomeAssistant, init_integration: MockConfigEntry, @@ -326,7 +328,7 @@ async def test_attributes_screensaver( assert state.attributes.get(ATTR_INPUT_SOURCE) == "Roku" -@pytest.mark.parametrize("mock_roku", ["roku/rokutv-7820x.json"], indirect=True) +@pytest.mark.parametrize("mock_device", ["roku/rokutv-7820x.json"], indirect=True) async def test_tv_attributes( hass: HomeAssistant, init_integration: MockConfigEntry ) -> None: @@ -557,7 +559,7 @@ async def test_services_play_media_local_source( assert "/media/local/Epic%20Sax%20Guy%2010%20Hours.mp4?authSig=" in call_args[0] -@pytest.mark.parametrize("mock_roku", ["roku/rokutv-7820x.json"], indirect=True) +@pytest.mark.parametrize("mock_device", ["roku/rokutv-7820x.json"], indirect=True) async def test_tv_services( hass: HomeAssistant, init_integration: MockConfigEntry, @@ -836,7 +838,7 @@ async def test_media_browse_local_source( ) -@pytest.mark.parametrize("mock_roku", ["roku/rokutv-7820x.json"], indirect=True) +@pytest.mark.parametrize("mock_device", ["roku/rokutv-7820x.json"], indirect=True) async def test_tv_media_browse( hass, init_integration, @@ -933,10 +935,10 @@ async def test_tv_media_browse( assert msg["result"]["children_media_class"] == MEDIA_CLASS_CHANNEL assert msg["result"]["can_expand"] assert not msg["result"]["can_play"] - assert len(msg["result"]["children"]) == 2 + assert len(msg["result"]["children"]) == 4 assert msg["result"]["children_media_class"] == MEDIA_CLASS_CHANNEL - assert msg["result"]["children"][0]["title"] == "WhatsOn" + assert msg["result"]["children"][0]["title"] == "WhatsOn (1.1)" assert msg["result"]["children"][0]["media_content_type"] == MEDIA_TYPE_CHANNEL assert msg["result"]["children"][0]["media_content_id"] == "1.1" assert msg["result"]["children"][0]["can_play"] diff --git a/tests/components/roku/test_select.py b/tests/components/roku/test_select.py new file mode 100644 index 00000000000000..e82a13c8511258 --- /dev/null +++ b/tests/components/roku/test_select.py @@ -0,0 +1,241 @@ +"""Tests for the Roku select platform.""" +from unittest.mock import MagicMock + +import pytest +from rokuecp import Application, Device as RokuDevice, RokuError + +from homeassistant.components.roku.const import DOMAIN +from homeassistant.components.roku.coordinator import SCAN_INTERVAL +from homeassistant.components.select import DOMAIN as SELECT_DOMAIN +from homeassistant.components.select.const import ATTR_OPTION, ATTR_OPTIONS +from homeassistant.const import ATTR_ENTITY_ID, ATTR_ICON, SERVICE_SELECT_OPTION +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er +import homeassistant.util.dt as dt_util + +from tests.common import MockConfigEntry, async_fire_time_changed + + +async def test_application_state( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_device: RokuDevice, + mock_roku: MagicMock, +) -> None: + """Test the creation and values of the Roku selects.""" + entity_registry = er.async_get(hass) + + entity_registry.async_get_or_create( + SELECT_DOMAIN, + DOMAIN, + "1GU48T017973_application", + suggested_object_id="my_roku_3_application", + disabled_by=None, + ) + + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get("select.my_roku_3_application") + assert state + assert state.attributes.get(ATTR_ICON) == "mdi:application" + assert state.attributes.get(ATTR_OPTIONS) == [ + "Home", + "Amazon Video on Demand", + "Free FrameChannel Service", + "MLB.TV" + "\u00AE", + "Mediafly", + "Netflix", + "Pandora", + "Pluto TV - It's Free TV", + "Roku Channel Store", + ] + assert state.state == "Home" + + entry = entity_registry.async_get("select.my_roku_3_application") + assert entry + assert entry.unique_id == "1GU48T017973_application" + + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: "select.my_roku_3_application", + ATTR_OPTION: "Netflix", + }, + blocking=True, + ) + + assert mock_roku.launch.call_count == 1 + mock_roku.launch.assert_called_with("12") + mock_device.app = mock_device.apps[1] + + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + + state = hass.states.get("select.my_roku_3_application") + assert state + + assert state.state == "Netflix" + + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: "select.my_roku_3_application", + ATTR_OPTION: "Home", + }, + blocking=True, + ) + + assert mock_roku.remote.call_count == 1 + mock_roku.remote.assert_called_with("home") + mock_device.app = Application( + app_id=None, name="Roku", version=None, screensaver=None + ) + async_fire_time_changed(hass, dt_util.utcnow() + (SCAN_INTERVAL * 2)) + await hass.async_block_till_done() + + state = hass.states.get("select.my_roku_3_application") + assert state + assert state.state == "Home" + + +async def test_application_select_error( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_roku: MagicMock, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test error handling of the Roku selects.""" + entity_registry = er.async_get(hass) + + entity_registry.async_get_or_create( + SELECT_DOMAIN, + DOMAIN, + "1GU48T017973_application", + suggested_object_id="my_roku_3_application", + disabled_by=None, + ) + + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + mock_roku.launch.side_effect = RokuError + + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: "select.my_roku_3_application", + ATTR_OPTION: "Netflix", + }, + blocking=True, + ) + + state = hass.states.get("select.my_roku_3_application") + assert state + assert state.state == "Home" + assert "Invalid response from API" in caplog.text + assert mock_roku.launch.call_count == 1 + mock_roku.launch.assert_called_with("12") + + +@pytest.mark.parametrize("mock_device", ["roku/rokutv-7820x.json"], indirect=True) +async def test_channel_state( + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_device: RokuDevice, + mock_roku: MagicMock, +) -> None: + """Test the creation and values of the Roku selects.""" + entity_registry = er.async_get(hass) + + state = hass.states.get("select.58_onn_roku_tv_channel") + assert state + assert state.attributes.get(ATTR_ICON) == "mdi:television" + assert state.attributes.get(ATTR_OPTIONS) == [ + "99.1", + "QVC (1.3)", + "WhatsOn (1.1)", + "getTV (14.3)", + ] + assert state.state == "getTV (14.3)" + + entry = entity_registry.async_get("select.58_onn_roku_tv_channel") + assert entry + assert entry.unique_id == "YN00H5555555_channel" + + # channel name + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: "select.58_onn_roku_tv_channel", + ATTR_OPTION: "WhatsOn (1.1)", + }, + blocking=True, + ) + + assert mock_roku.tune.call_count == 1 + mock_roku.tune.assert_called_with("1.1") + mock_device.channel = mock_device.channels[0] + + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + + state = hass.states.get("select.58_onn_roku_tv_channel") + assert state + assert state.state == "WhatsOn (1.1)" + + # channel number + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: "select.58_onn_roku_tv_channel", + ATTR_OPTION: "99.1", + }, + blocking=True, + ) + + assert mock_roku.tune.call_count == 2 + mock_roku.tune.assert_called_with("99.1") + mock_device.channel = mock_device.channels[3] + + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + + state = hass.states.get("select.58_onn_roku_tv_channel") + assert state + assert state.state == "99.1" + + +@pytest.mark.parametrize("mock_device", ["roku/rokutv-7820x.json"], indirect=True) +async def test_channel_select_error( + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_roku: MagicMock, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test error handling of the Roku selects.""" + mock_roku.tune.side_effect = RokuError + + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: "select.58_onn_roku_tv_channel", + ATTR_OPTION: "99.1", + }, + blocking=True, + ) + + state = hass.states.get("select.58_onn_roku_tv_channel") + assert state + assert state.state == "getTV (14.3)" + assert "Invalid response from API" in caplog.text + assert mock_roku.tune.call_count == 1 + mock_roku.tune.assert_called_with("99.1") diff --git a/tests/components/roku/test_sensor.py b/tests/components/roku/test_sensor.py index 6ca27635d30225..983455255faf55 100644 --- a/tests/components/roku/test_sensor.py +++ b/tests/components/roku/test_sensor.py @@ -65,7 +65,7 @@ async def test_roku_sensors( assert device_entry.suggested_area is None -@pytest.mark.parametrize("mock_roku", ["roku/rokutv-7820x.json"], indirect=True) +@pytest.mark.parametrize("mock_device", ["roku/rokutv-7820x.json"], indirect=True) async def test_rokutv_sensors( hass: HomeAssistant, init_integration: MockConfigEntry, From b2f5ab200811c19284dea81ba298a4566fd87eda Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Fri, 11 Feb 2022 21:22:53 -0800 Subject: [PATCH 0563/1098] Publish Nest Motion/Person events with optional user defined zone information (#66187) Publish Nest events with zone information if present. User defined zones are configured in the Google Home app, and are published with Motion/Person event. --- homeassistant/components/nest/__init__.py | 2 ++ homeassistant/components/nest/events.py | 3 +- tests/components/nest/test_events.py | 36 ++++++++++++++++++++++- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py index 85513378ed7cdf..1083b80ac478b6 100644 --- a/homeassistant/components/nest/__init__.py +++ b/homeassistant/components/nest/__init__.py @@ -158,6 +158,8 @@ async def async_handle_event(self, event_message: EventMessage) -> None: "timestamp": event_message.timestamp, "nest_event_id": image_event.event_token, } + if image_event.zones: + message["zones"] = image_event.zones self._hass.bus.async_fire(NEST_EVENT, message) diff --git a/homeassistant/components/nest/events.py b/homeassistant/components/nest/events.py index 10983768e17d03..752ab0e5069ddf 100644 --- a/homeassistant/components/nest/events.py +++ b/homeassistant/components/nest/events.py @@ -25,7 +25,8 @@ # "device_id": "my-device-id", # "type": "camera_motion", # "timestamp": "2021-10-24T19:42:43.304000+00:00", -# "nest_event_id": "KcO1HIR9sPKQ2bqby_vTcCcEov..." +# "nest_event_id": "KcO1HIR9sPKQ2bqby_vTcCcEov...", +# "zones": ["Zone 1"], # }, # ... # } diff --git a/tests/components/nest/test_events.py b/tests/components/nest/test_events.py index ee286242a8ce21..0ab387a7dea331 100644 --- a/tests/components/nest/test_events.py +++ b/tests/components/nest/test_events.py @@ -28,7 +28,7 @@ EVENT_SESSION_ID = "CjY5Y3VKaTZwR3o4Y19YbTVfMF..." EVENT_ID = "FWWVQVUdGNUlTU2V4MGV2aTNXV..." -EVENT_KEYS = {"device_id", "type", "timestamp"} +EVENT_KEYS = {"device_id", "type", "timestamp", "zones"} def event_view(d: Mapping[str, Any]) -> Mapping[str, Any]: @@ -514,3 +514,37 @@ async def test_structure_update_event(hass): assert registry.async_get("camera.front") # Currently need a manual reload to detect the new entity assert not registry.async_get("camera.back") + + +async def test_event_zones(hass): + """Test events published with zone information.""" + events = async_capture_events(hass, NEST_EVENT) + subscriber = await async_setup_devices( + hass, + "sdm.devices.types.DOORBELL", + create_device_traits(["sdm.devices.traits.CameraMotion"]), + ) + registry = er.async_get(hass) + entry = registry.async_get("camera.front") + assert entry is not None + + event_map = { + "sdm.devices.events.CameraMotion.Motion": { + "eventSessionId": EVENT_SESSION_ID, + "eventId": EVENT_ID, + "zones": ["Zone 1"], + }, + } + + timestamp = utcnow() + await subscriber.async_receive_event(create_events(event_map, timestamp=timestamp)) + await hass.async_block_till_done() + + event_time = timestamp.replace(microsecond=0) + assert len(events) == 1 + assert event_view(events[0].data) == { + "device_id": entry.device_id, + "type": "camera_motion", + "timestamp": event_time, + "zones": ["Zone 1"], + } From 1b270113207d8b75e5b077fae32d55ef85d6a031 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 12 Feb 2022 12:48:48 +0100 Subject: [PATCH 0564/1098] Fix supported features sensibo (#65895) * Fix features not available * Partial revert * Apply active features * Fix from review --- homeassistant/components/sensibo/climate.py | 16 +++++++++++++++- homeassistant/components/sensibo/coordinator.py | 15 +++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index e8017e7d849748..0963a4f927e977 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -157,7 +157,7 @@ def __init__( def get_features(self) -> int: """Get supported features.""" features = 0 - for key in self.coordinator.data[self.unique_id]["features"]: + for key in self.coordinator.data[self.unique_id]["full_features"]: if key in FIELD_TO_FLAG: features |= FIELD_TO_FLAG[key] return features @@ -240,6 +240,14 @@ def available(self) -> bool: async def async_set_temperature(self, **kwargs) -> None: """Set new target temperature.""" + if ( + "targetTemperature" + not in self.coordinator.data[self.unique_id]["active_features"] + ): + raise HomeAssistantError( + "Current mode doesn't support setting Target Temperature" + ) + if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None: return @@ -261,6 +269,9 @@ async def async_set_temperature(self, **kwargs) -> None: async def async_set_fan_mode(self, fan_mode: str) -> None: """Set new target fan mode.""" + if "fanLevel" not in self.coordinator.data[self.unique_id]["active_features"]: + raise HomeAssistantError("Current mode doesn't support setting Fanlevel") + await self._async_set_ac_state_property("fanLevel", fan_mode) async def async_set_hvac_mode(self, hvac_mode: str) -> None: @@ -277,6 +288,9 @@ async def async_set_hvac_mode(self, hvac_mode: str) -> None: async def async_set_swing_mode(self, swing_mode: str) -> None: """Set new target swing operation.""" + if "swing" not in self.coordinator.data[self.unique_id]["active_features"]: + raise HomeAssistantError("Current mode doesn't support setting Swing") + await self._async_set_ac_state_property("swing", swing_mode) async def async_turn_on(self) -> None: diff --git a/homeassistant/components/sensibo/coordinator.py b/homeassistant/components/sensibo/coordinator.py index 9509be88266ee1..41c44e741e9881 100644 --- a/homeassistant/components/sensibo/coordinator.py +++ b/homeassistant/components/sensibo/coordinator.py @@ -72,7 +72,17 @@ async def _async_update_data(self) -> dict[str, dict[str, Any]]: ) if temperatures_list: temperature_step = temperatures_list[1] - temperatures_list[0] - features = list(ac_states) + + active_features = list(ac_states) + full_features = set() + for mode in capabilities["modes"]: + if "temperatures" in capabilities["modes"][mode]: + full_features.add("targetTemperature") + if "swing" in capabilities["modes"][mode]: + full_features.add("swing") + if "fanLevels" in capabilities["modes"][mode]: + full_features.add("fanLevel") + state = hvac_mode if hvac_mode else "off" fw_ver = dev["firmwareVersion"] @@ -100,7 +110,8 @@ async def _async_update_data(self) -> dict[str, dict[str, Any]]: "temp_unit": temperature_unit_key, "temp_list": temperatures_list, "temp_step": temperature_step, - "features": features, + "active_features": active_features, + "full_features": full_features, "state": state, "fw_ver": fw_ver, "fw_type": fw_type, From 5e5659d75868b88763aad9e207533c56431a2d1f Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Sat, 12 Feb 2022 23:08:41 +1000 Subject: [PATCH 0565/1098] Add Diagnostics (#65755) --- .coveragerc | 1 + .../aussie_broadband/diagnostics.py | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 homeassistant/components/aussie_broadband/diagnostics.py diff --git a/.coveragerc b/.coveragerc index 0ada739763f546..a4819a124c942a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -90,6 +90,7 @@ omit = homeassistant/components/aurora/binary_sensor.py homeassistant/components/aurora/const.py homeassistant/components/aurora/sensor.py + homeassistant/components/aussie_broadband/diagnostics.py homeassistant/components/avea/light.py homeassistant/components/avion/light.py homeassistant/components/azure_devops/__init__.py diff --git a/homeassistant/components/aussie_broadband/diagnostics.py b/homeassistant/components/aussie_broadband/diagnostics.py new file mode 100644 index 00000000000000..f4e95a99f56789 --- /dev/null +++ b/homeassistant/components/aussie_broadband/diagnostics.py @@ -0,0 +1,28 @@ +"""Provides diagnostics for Aussie Broadband.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import DOMAIN + +TO_REDACT = ["address", "ipAddresses", "description", "discounts", "coordinator"] + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + services = [] + for service in hass.data[DOMAIN][config_entry.entry_id]["services"]: + services.append( + { + "service": async_redact_data(service, TO_REDACT), + "usage": async_redact_data(service["coordinator"].data, ["historical"]), + } + ) + + return {"services": services} From 95e4ea8fcda0b26811515a91a32f1e90378b0089 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Sat, 12 Feb 2022 13:16:05 +0000 Subject: [PATCH 0566/1098] Simplify the homekit_controller unignore journey (#66353) --- .../homekit_controller/config_flow.py | 36 +++++-------------- .../homekit_controller/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../homekit_controller/test_config_flow.py | 2 +- 5 files changed, 13 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index 3784e2830d7d99..82ab670f8db09e 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -154,34 +154,16 @@ async def async_step_unignore(self, user_input): if self.controller is None: await self._async_setup_controller() - devices = await self.controller.discover_ip(max_seconds=5) - for device in devices: - if normalize_hkid(device.device_id) != unique_id: - continue - record = device.info - return await self.async_step_zeroconf( - zeroconf.ZeroconfServiceInfo( - host=record["address"], - addresses=[record["address"]], - port=record["port"], - hostname=record["name"], - type="_hap._tcp.local.", - name=record["name"], - properties={ - "md": record["md"], - "pv": record["pv"], - zeroconf.ATTR_PROPERTIES_ID: unique_id, - "c#": record["c#"], - "s#": record["s#"], - "ff": record["ff"], - "ci": record["ci"], - "sf": record["sf"], - "sh": "", - }, - ) - ) + try: + device = await self.controller.find_ip_by_device_id(unique_id) + except aiohomekit.AccessoryNotFoundError: + return self.async_abort(reason="accessory_not_found_error") + + self.name = device.info["name"].replace("._hap._tcp.local.", "") + self.model = device.info["md"] + self.hkid = normalize_hkid(device.info["id"]) - return self.async_abort(reason="no_devices") + return self._async_step_pair_show_form() async def _hkid_is_homekit(self, hkid): """Determine if the device is a homekit bridge or accessory.""" diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index ce7ae876e036d3..d156ae142568f7 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit Controller", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homekit_controller", - "requirements": ["aiohomekit==0.7.7"], + "requirements": ["aiohomekit==0.7.8"], "zeroconf": ["_hap._tcp.local."], "after_dependencies": ["zeroconf"], "codeowners": ["@Jc2k", "@bdraco"], diff --git a/requirements_all.txt b/requirements_all.txt index 394dd8099bc3ab..b0c4267de7b279 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -184,7 +184,7 @@ aioguardian==2021.11.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==0.7.7 +aiohomekit==0.7.8 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4f81c60171c68e..3fac8b4b8597bd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -134,7 +134,7 @@ aioguardian==2021.11.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==0.7.7 +aiohomekit==0.7.8 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/tests/components/homekit_controller/test_config_flow.py b/tests/components/homekit_controller/test_config_flow.py index 8a1f77d0e4cebe..101e4ebd024ed3 100644 --- a/tests/components/homekit_controller/test_config_flow.py +++ b/tests/components/homekit_controller/test_config_flow.py @@ -844,7 +844,7 @@ async def test_unignore_ignores_missing_devices(hass, controller): ) assert result["type"] == "abort" - assert result["reason"] == "no_devices" + assert result["reason"] == "accessory_not_found_error" async def test_discovery_dismiss_existing_flow_on_paired(hass, controller): From 65ce2108d3c63718e3480522d19b98e87e743ed8 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Sat, 12 Feb 2022 14:12:27 +0000 Subject: [PATCH 0567/1098] Stop homekit_controller using backend specific API's (#66375) --- homeassistant/components/homekit_controller/connection.py | 4 ++-- homeassistant/components/homekit_controller/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index e4ad06f322afaa..9642d5d3bc59d9 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -378,7 +378,7 @@ async def async_process_entity_map(self) -> None: if self.watchable_characteristics: await self.pairing.subscribe(self.watchable_characteristics) - if not self.pairing.connection.is_connected: + if not self.pairing.is_connected: return await self.async_update() @@ -506,7 +506,7 @@ async def async_load_platforms(self) -> None: async def async_update(self, now=None): """Poll state of all entities attached to this bridge/accessory.""" if not self.pollable_characteristics: - self.async_set_available_state(self.pairing.connection.is_connected) + self.async_set_available_state(self.pairing.is_connected) _LOGGER.debug( "HomeKit connection not polling any characteristics: %s", self.unique_id ) diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index d156ae142568f7..4e216474ec0249 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit Controller", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homekit_controller", - "requirements": ["aiohomekit==0.7.8"], + "requirements": ["aiohomekit==0.7.11"], "zeroconf": ["_hap._tcp.local."], "after_dependencies": ["zeroconf"], "codeowners": ["@Jc2k", "@bdraco"], diff --git a/requirements_all.txt b/requirements_all.txt index b0c4267de7b279..7740a0363a18b6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -184,7 +184,7 @@ aioguardian==2021.11.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==0.7.8 +aiohomekit==0.7.11 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3fac8b4b8597bd..f9fd89fa2058ca 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -134,7 +134,7 @@ aioguardian==2021.11.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==0.7.8 +aiohomekit==0.7.11 # homeassistant.components.emulated_hue # homeassistant.components.http From 8da150bd71e9e134d908a9e575abb251d1a4ad26 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 12 Feb 2022 15:13:01 +0100 Subject: [PATCH 0568/1098] Improve code quality sql (#65321) --- homeassistant/components/sql/sensor.py | 112 +++++++++++------------ tests/components/sql/test_sensor.py | 121 ++++++++++++++++++++++++- 2 files changed, 168 insertions(+), 65 deletions(-) diff --git a/homeassistant/components/sql/sensor.py b/homeassistant/components/sql/sensor.py index b9e3b9ce81d987..1c8e87051beabb 100644 --- a/homeassistant/components/sql/sensor.py +++ b/homeassistant/components/sql/sensor.py @@ -1,8 +1,7 @@ """Sensor from an SQL Query.""" from __future__ import annotations -import datetime -import decimal +from datetime import date import logging import re @@ -11,11 +10,15 @@ import voluptuous as vol from homeassistant.components.recorder import CONF_DB_URL, DEFAULT_DB_FILE, DEFAULT_URL -from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity +from homeassistant.components.sensor import ( + PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA, + SensorEntity, +) from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.template import Template from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType _LOGGER = logging.getLogger(__name__) @@ -27,12 +30,12 @@ DB_URL_RE = re.compile("//.*:.*@") -def redact_credentials(data): +def redact_credentials(data: str) -> str: """Redact credentials from string data.""" return DB_URL_RE.sub("//****:****@", data) -def validate_sql_select(value): +def validate_sql_select(value: str) -> str: """Validate that value is a SQL SELECT query.""" if not value.lstrip().lower().startswith("select"): raise vol.Invalid("Only SELECT queries allowed") @@ -49,7 +52,7 @@ def validate_sql_select(value): } ) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( +PLATFORM_SCHEMA = PARENT_PLATFORM_SCHEMA.extend( {vol.Required(CONF_QUERIES): [_QUERY_SCHEME], vol.Optional(CONF_DB_URL): cv.string} ) @@ -64,7 +67,7 @@ def setup_platform( if not (db_url := config.get(CONF_DB_URL)): db_url = DEFAULT_URL.format(hass_config_path=hass.config.path(DEFAULT_DB_FILE)) - sess = None + sess: scoped_session | None = None try: engine = sqlalchemy.create_engine(db_url) sessmaker = scoped_session(sessionmaker(bind=engine)) @@ -87,11 +90,11 @@ def setup_platform( queries = [] for query in config[CONF_QUERIES]: - name = query.get(CONF_NAME) - query_str = query.get(CONF_QUERY) - unit = query.get(CONF_UNIT_OF_MEASUREMENT) - value_template = query.get(CONF_VALUE_TEMPLATE) - column_name = query.get(CONF_COLUMN_NAME) + name: str = query[CONF_NAME] + query_str: str = query[CONF_QUERY] + unit: str | None = query.get(CONF_UNIT_OF_MEASUREMENT) + value_template: Template | None = query.get(CONF_VALUE_TEMPLATE) + column_name: str = query[CONF_COLUMN_NAME] if value_template is not None: value_template.hass = hass @@ -115,60 +118,32 @@ def setup_platform( class SQLSensor(SensorEntity): """Representation of an SQL sensor.""" - def __init__(self, name, sessmaker, query, column, unit, value_template): + def __init__( + self, + name: str, + sessmaker: scoped_session, + query: str, + column: str, + unit: str | None, + value_template: Template | None, + ) -> None: """Initialize the SQL sensor.""" - self._name = name + self._attr_name = name self._query = query - self._unit_of_measurement = unit + self._attr_native_unit_of_measurement = unit self._template = value_template self._column_name = column self.sessionmaker = sessmaker - self._state = None - self._attributes = None - - @property - def name(self): - """Return the name of the query.""" - return self._name - - @property - def native_value(self): - """Return the query's current state.""" - return self._state - - @property - def native_unit_of_measurement(self): - """Return the unit of measurement.""" - return self._unit_of_measurement - - @property - def extra_state_attributes(self): - """Return the state attributes.""" - return self._attributes - - def update(self): + self._attr_extra_state_attributes = {} + + def update(self) -> None: """Retrieve sensor data from the query.""" data = None + self._attr_extra_state_attributes = {} + sess: scoped_session = self.sessionmaker() try: - sess = self.sessionmaker() result = sess.execute(self._query) - self._attributes = {} - - if not result.returns_rows or result.rowcount == 0: - _LOGGER.warning("%s returned no results", self._query) - self._state = None - return - - for res in result.mappings(): - _LOGGER.debug("result = %s", res.items()) - data = res[self._column_name] - for key, value in res.items(): - if isinstance(value, decimal.Decimal): - value = float(value) - if isinstance(value, datetime.date): - value = str(value) - self._attributes[key] = value except sqlalchemy.exc.SQLAlchemyError as err: _LOGGER.error( "Error executing query %s: %s", @@ -176,12 +151,27 @@ def update(self): redact_credentials(str(err)), ) return - finally: - sess.close() + + _LOGGER.debug("Result %s, ResultMapping %s", result, result.mappings()) + + for res in result.mappings(): + _LOGGER.debug("result = %s", res.items()) + data = res[self._column_name] + for key, value in res.items(): + if isinstance(value, float): + value = float(value) + if isinstance(value, date): + value = value.isoformat() + self._attr_extra_state_attributes[key] = value if data is not None and self._template is not None: - self._state = self._template.async_render_with_possible_json_value( - data, None + self._attr_native_value = ( + self._template.async_render_with_possible_json_value(data, None) ) else: - self._state = data + self._attr_native_value = data + + if not data: + _LOGGER.warning("%s returned no results", self._query) + + sess.close() diff --git a/tests/components/sql/test_sensor.py b/tests/components/sql/test_sensor.py index 629ec464e58b09..0e543f98a21849 100644 --- a/tests/components/sql/test_sensor.py +++ b/tests/components/sql/test_sensor.py @@ -1,13 +1,27 @@ """The test for the sql sensor platform.""" +import os + import pytest import voluptuous as vol from homeassistant.components.sql.sensor import validate_sql_select from homeassistant.const import STATE_UNKNOWN +from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component +from tests.common import get_test_config_dir + + +@pytest.fixture(autouse=True) +def remove_file(): + """Remove db.""" + yield + file = os.path.join(get_test_config_dir(), "home-assistant_v2.db") + if os.path.isfile(file): + os.remove(file) -async def test_query(hass): + +async def test_query(hass: HomeAssistant) -> None: """Test the SQL sensor.""" config = { "sensor": { @@ -31,7 +45,53 @@ async def test_query(hass): assert state.attributes["value"] == 5 -async def test_query_limit(hass): +async def test_query_no_db(hass: HomeAssistant) -> None: + """Test the SQL sensor.""" + config = { + "sensor": { + "platform": "sql", + "queries": [ + { + "name": "count_tables", + "query": "SELECT 5 as value", + "column": "value", + } + ], + } + } + + assert await async_setup_component(hass, "sensor", config) + await hass.async_block_till_done() + + state = hass.states.get("sensor.count_tables") + assert state.state == "5" + + +async def test_query_value_template(hass: HomeAssistant) -> None: + """Test the SQL sensor.""" + config = { + "sensor": { + "platform": "sql", + "db_url": "sqlite://", + "queries": [ + { + "name": "count_tables", + "query": "SELECT 5.01 as value", + "column": "value", + "value_template": "{{ value | int }}", + } + ], + } + } + + assert await async_setup_component(hass, "sensor", config) + await hass.async_block_till_done() + + state = hass.states.get("sensor.count_tables") + assert state.state == "5" + + +async def test_query_limit(hass: HomeAssistant) -> None: """Test the SQL sensor with a query containing 'LIMIT' in lowercase.""" config = { "sensor": { @@ -55,7 +115,30 @@ async def test_query_limit(hass): assert state.attributes["value"] == 5 -async def test_invalid_query(hass): +async def test_query_no_value(hass: HomeAssistant) -> None: + """Test the SQL sensor with a query that returns no value.""" + config = { + "sensor": { + "platform": "sql", + "db_url": "sqlite://", + "queries": [ + { + "name": "count_tables", + "query": "SELECT 5 as value where 1=2", + "column": "value", + } + ], + } + } + + assert await async_setup_component(hass, "sensor", config) + await hass.async_block_till_done() + + state = hass.states.get("sensor.count_tables") + assert state.state == STATE_UNKNOWN + + +async def test_invalid_query(hass: HomeAssistant) -> None: """Test the SQL sensor for invalid queries.""" with pytest.raises(vol.Invalid): validate_sql_select("DROP TABLE *") @@ -81,6 +164,30 @@ async def test_invalid_query(hass): assert state.state == STATE_UNKNOWN +async def test_value_float_and_date(hass: HomeAssistant) -> None: + """Test the SQL sensor with a query has float as value.""" + config = { + "sensor": { + "platform": "sql", + "db_url": "sqlite://", + "queries": [ + { + "name": "float_value", + "query": "SELECT 5 as value, cast(5.01 as decimal(10,2)) as value2", + "column": "value", + }, + ], + } + } + + assert await async_setup_component(hass, "sensor", config) + await hass.async_block_till_done() + + state = hass.states.get("sensor.float_value") + assert state.state == "5" + assert isinstance(state.attributes["value2"], float) + + @pytest.mark.parametrize( "url,expected_patterns,not_expected_patterns", [ @@ -96,7 +203,13 @@ async def test_invalid_query(hass): ), ], ) -async def test_invalid_url(hass, caplog, url, expected_patterns, not_expected_patterns): +async def test_invalid_url( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + url: str, + expected_patterns: str, + not_expected_patterns: str, +): """Test credentials in url is not logged.""" config = { "sensor": { From db6969739f761e5478c859173ccfbab323aacb5a Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 12 Feb 2022 15:15:28 +0100 Subject: [PATCH 0569/1098] Improve code quality telnet (#65239) --- homeassistant/components/telnet/switch.py | 88 ++++++++++------------- 1 file changed, 36 insertions(+), 52 deletions(-) diff --git a/homeassistant/components/telnet/switch.py b/homeassistant/components/telnet/switch.py index f59bfad92f406e..9ad295d9ac5055 100644 --- a/homeassistant/components/telnet/switch.py +++ b/homeassistant/components/telnet/switch.py @@ -4,6 +4,7 @@ from datetime import timedelta import logging import telnetlib +from typing import Any import voluptuous as vol @@ -26,6 +27,7 @@ from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.template import Template from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType _LOGGER = logging.getLogger(__name__) @@ -60,27 +62,26 @@ def setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Find and return switches controlled by telnet commands.""" - devices = config.get(CONF_SWITCHES, {}) + devices: dict[str, Any] = config[CONF_SWITCHES] switches = [] for object_id, device_config in devices.items(): - value_template = device_config.get(CONF_VALUE_TEMPLATE) + value_template: Template | None = device_config.get(CONF_VALUE_TEMPLATE) if value_template is not None: value_template.hass = hass switches.append( TelnetSwitch( - hass, object_id, - device_config.get(CONF_RESOURCE), - device_config.get(CONF_PORT), + device_config[CONF_RESOURCE], + device_config[CONF_PORT], device_config.get(CONF_NAME, object_id), - device_config.get(CONF_COMMAND_ON), - device_config.get(CONF_COMMAND_OFF), + device_config[CONF_COMMAND_ON], + device_config[CONF_COMMAND_OFF], device_config.get(CONF_COMMAND_STATE), value_template, - device_config.get(CONF_TIMEOUT), + device_config[CONF_TIMEOUT], ) ) @@ -96,82 +97,65 @@ class TelnetSwitch(SwitchEntity): def __init__( self, - hass, - object_id, - resource, - port, - friendly_name, - command_on, - command_off, - command_state, - value_template, - timeout, - ): + object_id: str, + resource: str, + port: int, + friendly_name: str, + command_on: str, + command_off: str, + command_state: str | None, + value_template: Template | None, + timeout: float, + ) -> None: """Initialize the switch.""" - self._hass = hass self.entity_id = ENTITY_ID_FORMAT.format(object_id) self._resource = resource self._port = port - self._name = friendly_name - self._state = False + self._attr_name = friendly_name + self._attr_is_on = False self._command_on = command_on self._command_off = command_off self._command_state = command_state self._value_template = value_template self._timeout = timeout + self._attr_should_poll = bool(command_state) + self._attr_assumed_state = bool(command_state is None) - def _telnet_command(self, command): + def _telnet_command(self, command: str) -> str | None: try: telnet = telnetlib.Telnet(self._resource, self._port) telnet.write(command.encode("ASCII") + b"\r") response = telnet.read_until(b"\r", timeout=self._timeout) - _LOGGER.debug("telnet response: %s", response.decode("ASCII").strip()) - return response.decode("ASCII").strip() except OSError as error: _LOGGER.error( 'Command "%s" failed with exception: %s', command, repr(error) ) return None + _LOGGER.debug("telnet response: %s", response.decode("ASCII").strip()) + return response.decode("ASCII").strip() - @property - def name(self): - """Return the name of the switch.""" - return self._name - - @property - def should_poll(self): - """Only poll if we have state command.""" - return self._command_state is not None - - @property - def is_on(self): - """Return true if device is on.""" - return self._state - - @property - def assumed_state(self): - """Return true if no state command is defined, false otherwise.""" - return self._command_state is None - - def update(self): + def update(self) -> None: """Update device state.""" + if not self._command_state: + return response = self._telnet_command(self._command_state) - if response: + if response and self._value_template: rendered = self._value_template.render_with_possible_json_value(response) - self._state = rendered == "True" else: _LOGGER.warning("Empty response for command: %s", self._command_state) + return None + self._attr_is_on = rendered == "True" - def turn_on(self, **kwargs): + def turn_on(self, **kwargs) -> None: """Turn the device on.""" self._telnet_command(self._command_on) if self.assumed_state: - self._state = True + self._attr_is_on = True self.schedule_update_ha_state() - def turn_off(self, **kwargs): + def turn_off(self, **kwargs) -> None: """Turn the device off.""" self._telnet_command(self._command_off) if self.assumed_state: - self._state = False + self._attr_is_on = False self.schedule_update_ha_state() From 3771c154fa0ea8e0b49d41ece55a7a18c444ee6a Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 12 Feb 2022 15:19:37 +0100 Subject: [PATCH 0570/1098] Improve code quality command_line (#65333) --- .../components/command_line/__init__.py | 7 +- .../components/command_line/binary_sensor.py | 63 +++++-------- .../components/command_line/cover.py | 66 +++++++------- .../components/command_line/notify.py | 18 ++-- .../components/command_line/sensor.py | 88 +++++++------------ .../components/command_line/switch.py | 82 ++++++++--------- .../command_line/test_binary_sensor.py | 4 +- tests/components/command_line/test_cover.py | 10 ++- tests/components/command_line/test_notify.py | 12 ++- tests/components/command_line/test_sensor.py | 28 ++++-- tests/components/command_line/test_switch.py | 17 ++-- 11 files changed, 190 insertions(+), 205 deletions(-) diff --git a/homeassistant/components/command_line/__init__.py b/homeassistant/components/command_line/__init__.py index 4f98818d9b316b..1bcaaaa60a6572 100644 --- a/homeassistant/components/command_line/__init__.py +++ b/homeassistant/components/command_line/__init__.py @@ -1,4 +1,5 @@ """The command_line component.""" +from __future__ import annotations import logging import subprocess @@ -6,7 +7,9 @@ _LOGGER = logging.getLogger(__name__) -def call_shell_with_timeout(command, timeout, *, log_return_code=True): +def call_shell_with_timeout( + command: str, timeout: int, *, log_return_code: bool = True +) -> int: """Run a shell command with a timeout. If log_return_code is set to False, it will not print an error if a non-zero @@ -30,7 +33,7 @@ def call_shell_with_timeout(command, timeout, *, log_return_code=True): return -1 -def check_output_or_log(command, timeout): +def check_output_or_log(command: str, timeout: int) -> str | None: """Run a shell command with a timeout and return the output.""" try: return_value = subprocess.check_output( diff --git a/homeassistant/components/command_line/binary_sensor.py b/homeassistant/components/command_line/binary_sensor.py index e7cfe5288d80de..b2c8b29478ab60 100644 --- a/homeassistant/components/command_line/binary_sensor.py +++ b/homeassistant/components/command_line/binary_sensor.py @@ -23,6 +23,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.reload import setup_reload_service +from homeassistant.helpers.template import Template from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT, DOMAIN, PLATFORMS @@ -59,14 +60,14 @@ def setup_platform( setup_reload_service(hass, DOMAIN, PLATFORMS) - name = config.get(CONF_NAME) - command = config.get(CONF_COMMAND) - payload_off = config.get(CONF_PAYLOAD_OFF) - payload_on = config.get(CONF_PAYLOAD_ON) - device_class = config.get(CONF_DEVICE_CLASS) - value_template = config.get(CONF_VALUE_TEMPLATE) - command_timeout = config.get(CONF_COMMAND_TIMEOUT) - unique_id = config.get(CONF_UNIQUE_ID) + name: str = config[CONF_NAME] + command: str = config[CONF_COMMAND] + payload_off: str = config[CONF_PAYLOAD_OFF] + payload_on: str = config[CONF_PAYLOAD_ON] + device_class: str | None = config.get(CONF_DEVICE_CLASS) + value_template: Template | None = config.get(CONF_VALUE_TEMPLATE) + command_timeout: int = config[CONF_COMMAND_TIMEOUT] + unique_id: str | None = config.get(CONF_UNIQUE_ID) if value_template is not None: value_template.hass = hass data = CommandSensorData(hass, command, command_timeout) @@ -74,7 +75,6 @@ def setup_platform( add_entities( [ CommandBinarySensor( - hass, data, name, device_class, @@ -93,42 +93,25 @@ class CommandBinarySensor(BinarySensorEntity): def __init__( self, - hass, - data, - name, - device_class, - payload_on, - payload_off, - value_template, - unique_id, - ): + data: CommandSensorData, + name: str, + device_class: str | None, + payload_on: str, + payload_off: str, + value_template: Template | None, + unique_id: str | None, + ) -> None: """Initialize the Command line binary sensor.""" - self._hass = hass self.data = data - self._name = name - self._device_class = device_class - self._state = False + self._attr_name = name + self._attr_device_class = device_class + self._attr_is_on = None self._payload_on = payload_on self._payload_off = payload_off self._value_template = value_template self._attr_unique_id = unique_id - @property - def name(self): - """Return the name of the sensor.""" - return self._name - - @property - def is_on(self): - """Return true if the binary sensor is on.""" - return self._state - - @property - def device_class(self): - """Return the class of the binary sensor.""" - return self._device_class - - def update(self): + def update(self) -> None: """Get the latest data and updates the state.""" self.data.update() value = self.data.value @@ -136,6 +119,6 @@ def update(self): if self._value_template is not None: value = self._value_template.render_with_possible_json_value(value, False) if value == self._payload_on: - self._state = True + self._attr_is_on = True elif value == self._payload_off: - self._state = False + self._attr_is_on = False diff --git a/homeassistant/components/command_line/cover.py b/homeassistant/components/command_line/cover.py index ff00138eba4406..321b18437d97e6 100644 --- a/homeassistant/components/command_line/cover.py +++ b/homeassistant/components/command_line/cover.py @@ -2,6 +2,7 @@ from __future__ import annotations import logging +from typing import TYPE_CHECKING, Any import voluptuous as vol @@ -20,6 +21,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.reload import setup_reload_service +from homeassistant.helpers.template import Template from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from . import call_shell_with_timeout, check_output_or_log @@ -55,17 +57,16 @@ def setup_platform( setup_reload_service(hass, DOMAIN, PLATFORMS) - devices = config.get(CONF_COVERS, {}) + devices: dict[str, Any] = config.get(CONF_COVERS, {}) covers = [] for device_name, device_config in devices.items(): - value_template = device_config.get(CONF_VALUE_TEMPLATE) + value_template: Template | None = device_config.get(CONF_VALUE_TEMPLATE) if value_template is not None: value_template.hass = hass covers.append( CommandCover( - hass, device_config.get(CONF_FRIENDLY_NAME, device_name), device_config[CONF_COMMAND_OPEN], device_config[CONF_COMMAND_CLOSE], @@ -89,20 +90,18 @@ class CommandCover(CoverEntity): def __init__( self, - hass, - name, - command_open, - command_close, - command_stop, - command_state, - value_template, - timeout, - unique_id, - ): + name: str, + command_open: str, + command_close: str, + command_stop: str, + command_state: str | None, + value_template: Template | None, + timeout: int, + unique_id: str | None, + ) -> None: """Initialize the cover.""" - self._hass = hass - self._name = name - self._state = None + self._attr_name = name + self._state: int | None = None self._command_open = command_open self._command_close = command_close self._command_stop = command_stop @@ -110,8 +109,9 @@ def __init__( self._value_template = value_template self._timeout = timeout self._attr_unique_id = unique_id + self._attr_should_poll = bool(command_state) - def _move_cover(self, command): + def _move_cover(self, command: str) -> bool: """Execute the actual commands.""" _LOGGER.info("Running command: %s", command) @@ -123,35 +123,29 @@ def _move_cover(self, command): return success @property - def should_poll(self): - """Only poll if we have state command.""" - return self._command_state is not None - - @property - def name(self): - """Return the name of the cover.""" - return self._name - - @property - def is_closed(self): + def is_closed(self) -> bool | None: """Return if the cover is closed.""" if self.current_cover_position is not None: return self.current_cover_position == 0 + return None @property - def current_cover_position(self): + def current_cover_position(self) -> int | None: """Return current position of cover. None is unknown, 0 is closed, 100 is fully open. """ return self._state - def _query_state(self): + def _query_state(self) -> str | None: """Query for the state.""" - _LOGGER.info("Running state value command: %s", self._command_state) - return check_output_or_log(self._command_state, self._timeout) + if self._command_state: + _LOGGER.info("Running state value command: %s", self._command_state) + return check_output_or_log(self._command_state, self._timeout) + if TYPE_CHECKING: + return None - def update(self): + def update(self) -> None: """Update device state.""" if self._command_state: payload = str(self._query_state()) @@ -159,14 +153,14 @@ def update(self): payload = self._value_template.render_with_possible_json_value(payload) self._state = int(payload) - def open_cover(self, **kwargs): + def open_cover(self, **kwargs) -> None: """Open the cover.""" self._move_cover(self._command_open) - def close_cover(self, **kwargs): + def close_cover(self, **kwargs) -> None: """Close the cover.""" self._move_cover(self._command_close) - def stop_cover(self, **kwargs): + def stop_cover(self, **kwargs) -> None: """Stop the cover.""" self._move_cover(self._command_stop) diff --git a/homeassistant/components/command_line/notify.py b/homeassistant/components/command_line/notify.py index 1086c6300c2e77..6f364947775ac8 100644 --- a/homeassistant/components/command_line/notify.py +++ b/homeassistant/components/command_line/notify.py @@ -1,4 +1,6 @@ """Support for command line notification services.""" +from __future__ import annotations + import logging import subprocess @@ -6,7 +8,9 @@ from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService from homeassistant.const import CONF_COMMAND, CONF_NAME +from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util.process import kill_subprocess from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT @@ -22,10 +26,14 @@ ) -def get_service(hass, config, discovery_info=None): +def get_service( + hass: HomeAssistant, + config: ConfigType, + discovery_info: DiscoveryInfoType | None = None, +) -> CommandLineNotificationService: """Get the Command Line notification service.""" - command = config[CONF_COMMAND] - timeout = config[CONF_COMMAND_TIMEOUT] + command: str = config[CONF_COMMAND] + timeout: int = config[CONF_COMMAND_TIMEOUT] return CommandLineNotificationService(command, timeout) @@ -33,12 +41,12 @@ def get_service(hass, config, discovery_info=None): class CommandLineNotificationService(BaseNotificationService): """Implement the notification service for the Command Line service.""" - def __init__(self, command, timeout): + def __init__(self, command: str, timeout: int) -> None: """Initialize the service.""" self.command = command self._timeout = timeout - def send_message(self, message="", **kwargs): + def send_message(self, message="", **kwargs) -> None: """Send a message to a command line.""" with subprocess.Popen( self.command, diff --git a/homeassistant/components/command_line/sensor.py b/homeassistant/components/command_line/sensor.py index 387dacfc8de505..5dbbbf88e58143 100644 --- a/homeassistant/components/command_line/sensor.py +++ b/homeassistant/components/command_line/sensor.py @@ -19,10 +19,10 @@ ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import TemplateError -from homeassistant.helpers import template import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.reload import setup_reload_service +from homeassistant.helpers.template import Template from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from . import check_output_or_log @@ -59,23 +59,19 @@ def setup_platform( setup_reload_service(hass, DOMAIN, PLATFORMS) - name = config.get(CONF_NAME) - command = config.get(CONF_COMMAND) - unit = config.get(CONF_UNIT_OF_MEASUREMENT) - value_template = config.get(CONF_VALUE_TEMPLATE) - command_timeout = config.get(CONF_COMMAND_TIMEOUT) - unique_id = config.get(CONF_UNIQUE_ID) + name: str = config[CONF_NAME] + command: str = config[CONF_COMMAND] + unit: str | None = config.get(CONF_UNIT_OF_MEASUREMENT) + value_template: Template | None = config.get(CONF_VALUE_TEMPLATE) + command_timeout: int = config[CONF_COMMAND_TIMEOUT] + unique_id: str | None = config.get(CONF_UNIQUE_ID) if value_template is not None: value_template.hass = hass - json_attributes = config.get(CONF_JSON_ATTRIBUTES) + json_attributes: list[str] | None = config.get(CONF_JSON_ATTRIBUTES) data = CommandSensorData(hass, command, command_timeout) add_entities( - [ - CommandSensor( - hass, data, name, unit, value_template, json_attributes, unique_id - ) - ], + [CommandSensor(data, name, unit, value_template, json_attributes, unique_id)], True, ) @@ -85,57 +81,35 @@ class CommandSensor(SensorEntity): def __init__( self, - hass, - data, - name, - unit_of_measurement, - value_template, - json_attributes, - unique_id, - ): + data: CommandSensorData, + name: str, + unit_of_measurement: str | None, + value_template: Template | None, + json_attributes: list[str] | None, + unique_id: str | None, + ) -> None: """Initialize the sensor.""" - self._hass = hass self.data = data - self._attributes = None + self._attr_extra_state_attributes = {} self._json_attributes = json_attributes - self._name = name - self._state = None - self._unit_of_measurement = unit_of_measurement + self._attr_name = name + self._attr_native_value = None + self._attr_native_unit_of_measurement = unit_of_measurement self._value_template = value_template self._attr_unique_id = unique_id - @property - def name(self): - """Return the name of the sensor.""" - return self._name - - @property - def native_unit_of_measurement(self): - """Return the unit the value is expressed in.""" - return self._unit_of_measurement - - @property - def native_value(self): - """Return the state of the device.""" - return self._state - - @property - def extra_state_attributes(self): - """Return the state attributes.""" - return self._attributes - - def update(self): + def update(self) -> None: """Get the latest data and updates the state.""" self.data.update() value = self.data.value if self._json_attributes: - self._attributes = {} + self._attr_extra_state_attributes = {} if value: try: json_dict = json.loads(value) if isinstance(json_dict, Mapping): - self._attributes = { + self._attr_extra_state_attributes = { k: json_dict[k] for k in self._json_attributes if k in json_dict @@ -150,24 +124,26 @@ def update(self): if value is None: value = STATE_UNKNOWN elif self._value_template is not None: - self._state = self._value_template.render_with_possible_json_value( - value, STATE_UNKNOWN + self._attr_native_value = ( + self._value_template.render_with_possible_json_value( + value, STATE_UNKNOWN + ) ) else: - self._state = value + self._attr_native_value = value class CommandSensorData: """The class for handling the data retrieval.""" - def __init__(self, hass, command, command_timeout): + def __init__(self, hass: HomeAssistant, command: str, command_timeout: int) -> None: """Initialize the data object.""" - self.value = None + self.value: str | None = None self.hass = hass self.command = command self.timeout = command_timeout - def update(self): + def update(self) -> None: """Get the latest data with a shell command.""" command = self.command @@ -177,7 +153,7 @@ def update(self): args_compiled = None else: prog, args = command.split(" ", 1) - args_compiled = template.Template(args, self.hass) + args_compiled = Template(args, self.hass) if args_compiled: try: diff --git a/homeassistant/components/command_line/switch.py b/homeassistant/components/command_line/switch.py index e65db8afcc8f48..ff0d9b65f9dde8 100644 --- a/homeassistant/components/command_line/switch.py +++ b/homeassistant/components/command_line/switch.py @@ -2,6 +2,7 @@ from __future__ import annotations import logging +from typing import TYPE_CHECKING, Any import voluptuous as vol @@ -24,6 +25,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.reload import setup_reload_service +from homeassistant.helpers.template import Template from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from . import call_shell_with_timeout, check_output_or_log @@ -59,22 +61,21 @@ def setup_platform( setup_reload_service(hass, DOMAIN, PLATFORMS) - devices = config.get(CONF_SWITCHES, {}) + devices: dict[str, Any] = config.get(CONF_SWITCHES, {}) switches = [] for object_id, device_config in devices.items(): - value_template = device_config.get(CONF_VALUE_TEMPLATE) + value_template: Template | None = device_config.get(CONF_VALUE_TEMPLATE) if value_template is not None: value_template.hass = hass - icon_template = device_config.get(CONF_ICON_TEMPLATE) + icon_template: Template | None = device_config.get(CONF_ICON_TEMPLATE) if icon_template is not None: icon_template.hass = hass switches.append( CommandSwitch( - hass, object_id, device_config.get(CONF_FRIENDLY_NAME, object_id), device_config[CONF_COMMAND_ON], @@ -99,22 +100,20 @@ class CommandSwitch(SwitchEntity): def __init__( self, - hass, - object_id, - friendly_name, - command_on, - command_off, - command_state, - icon_template, - value_template, - timeout, - unique_id, - ): + object_id: str, + friendly_name: str, + command_on: str, + command_off: str, + command_state: str | None, + icon_template: Template | None, + value_template: Template | None, + timeout: int, + unique_id: str | None, + ) -> None: """Initialize the switch.""" - self._hass = hass self.entity_id = ENTITY_ID_FORMAT.format(object_id) - self._name = friendly_name - self._state = False + self._attr_name = friendly_name + self._attr_is_on = False self._command_on = command_on self._command_off = command_off self._command_state = command_state @@ -122,8 +121,9 @@ def __init__( self._value_template = value_template self._timeout = timeout self._attr_unique_id = unique_id + self._attr_should_poll = bool(command_state) - def _switch(self, command): + def _switch(self, command: str) -> bool: """Execute the actual commands.""" _LOGGER.info("Running command: %s", command) @@ -134,12 +134,12 @@ def _switch(self, command): return success - def _query_state_value(self, command): + def _query_state_value(self, command: str) -> str | None: """Execute state command for return value.""" _LOGGER.info("Running state value command: %s", command) return check_output_or_log(command, self._timeout) - def _query_state_code(self, command): + def _query_state_code(self, command: str) -> bool: """Execute state command for return code.""" _LOGGER.info("Running state code command: %s", command) return ( @@ -147,32 +147,20 @@ def _query_state_code(self, command): ) @property - def should_poll(self): - """Only poll if we have state command.""" - return self._command_state is not None - - @property - def name(self): - """Return the name of the switch.""" - return self._name - - @property - def is_on(self): - """Return true if device is on.""" - return self._state - - @property - def assumed_state(self): + def assumed_state(self) -> bool: """Return true if we do optimistic updates.""" return self._command_state is None - def _query_state(self): + def _query_state(self) -> str | int | None: """Query for state.""" - if self._value_template: - return self._query_state_value(self._command_state) - return self._query_state_code(self._command_state) + if self._command_state: + if self._value_template: + return self._query_state_value(self._command_state) + return self._query_state_code(self._command_state) + if TYPE_CHECKING: + return None - def update(self): + def update(self) -> None: """Update device state.""" if self._command_state: payload = str(self._query_state()) @@ -182,16 +170,16 @@ def update(self): ) if self._value_template: payload = self._value_template.render_with_possible_json_value(payload) - self._state = payload.lower() == "true" + self._attr_is_on = payload.lower() == "true" - def turn_on(self, **kwargs): + def turn_on(self, **kwargs) -> None: """Turn the device on.""" if self._switch(self._command_on) and not self._command_state: - self._state = True + self._attr_is_on = True self.schedule_update_ha_state() - def turn_off(self, **kwargs): + def turn_off(self, **kwargs) -> None: """Turn the device off.""" if self._switch(self._command_off) and not self._command_state: - self._state = False + self._attr_is_on = False self.schedule_update_ha_state() diff --git a/tests/components/command_line/test_binary_sensor.py b/tests/components/command_line/test_binary_sensor.py index 532e14573f4059..b523b02aa64bda 100644 --- a/tests/components/command_line/test_binary_sensor.py +++ b/tests/components/command_line/test_binary_sensor.py @@ -51,6 +51,7 @@ async def test_template(hass: HomeAssistant) -> None: ) entity_state = hass.states.get("binary_sensor.test") + assert entity_state assert entity_state.state == STATE_ON @@ -65,10 +66,11 @@ async def test_sensor_off(hass: HomeAssistant) -> None: }, ) entity_state = hass.states.get("binary_sensor.test") + assert entity_state assert entity_state.state == STATE_OFF -async def test_unique_id(hass): +async def test_unique_id(hass: HomeAssistant) -> None: """Test unique_id option and if it only creates one binary sensor per id.""" assert await setup.async_setup_component( hass, diff --git a/tests/components/command_line/test_cover.py b/tests/components/command_line/test_cover.py index 9d4f5b60c8bbb1..98c917f51ba76b 100644 --- a/tests/components/command_line/test_cover.py +++ b/tests/components/command_line/test_cover.py @@ -6,6 +6,8 @@ from typing import Any from unittest.mock import patch +from pytest import LogCaptureFixture + from homeassistant import config as hass_config, setup from homeassistant.components.cover import DOMAIN, SCAN_INTERVAL from homeassistant.const import ( @@ -36,7 +38,7 @@ async def setup_test_entity(hass: HomeAssistant, config_dict: dict[str, Any]) -> await hass.async_block_till_done() -async def test_no_covers(caplog: Any, hass: HomeAssistant) -> None: +async def test_no_covers(caplog: LogCaptureFixture, hass: HomeAssistant) -> None: """Test that the cover does not polls when there's no state command.""" with patch( @@ -150,7 +152,9 @@ async def test_reload(hass: HomeAssistant) -> None: assert hass.states.get("cover.from_yaml") -async def test_move_cover_failure(caplog: Any, hass: HomeAssistant) -> None: +async def test_move_cover_failure( + caplog: LogCaptureFixture, hass: HomeAssistant +) -> None: """Test with state value.""" await setup_test_entity( @@ -163,7 +167,7 @@ async def test_move_cover_failure(caplog: Any, hass: HomeAssistant) -> None: assert "Command failed" in caplog.text -async def test_unique_id(hass): +async def test_unique_id(hass: HomeAssistant) -> None: """Test unique_id option and if it only creates one cover per id.""" await setup_test_entity( hass, diff --git a/tests/components/command_line/test_notify.py b/tests/components/command_line/test_notify.py index 561ac07df20ce7..26b53a827e76d7 100644 --- a/tests/components/command_line/test_notify.py +++ b/tests/components/command_line/test_notify.py @@ -7,6 +7,8 @@ from typing import Any from unittest.mock import patch +from pytest import LogCaptureFixture + from homeassistant import setup from homeassistant.components.notify import DOMAIN from homeassistant.core import HomeAssistant @@ -60,7 +62,9 @@ async def test_command_line_output(hass: HomeAssistant) -> None: assert message == handle.read() -async def test_error_for_none_zero_exit_code(caplog: Any, hass: HomeAssistant) -> None: +async def test_error_for_none_zero_exit_code( + caplog: LogCaptureFixture, hass: HomeAssistant +) -> None: """Test if an error is logged for non zero exit codes.""" await setup_test_service( hass, @@ -75,7 +79,7 @@ async def test_error_for_none_zero_exit_code(caplog: Any, hass: HomeAssistant) - assert "Command failed" in caplog.text -async def test_timeout(caplog: Any, hass: HomeAssistant) -> None: +async def test_timeout(caplog: LogCaptureFixture, hass: HomeAssistant) -> None: """Test blocking is not forever.""" await setup_test_service( hass, @@ -90,7 +94,9 @@ async def test_timeout(caplog: Any, hass: HomeAssistant) -> None: assert "Timeout" in caplog.text -async def test_subprocess_exceptions(caplog: Any, hass: HomeAssistant) -> None: +async def test_subprocess_exceptions( + caplog: LogCaptureFixture, hass: HomeAssistant +) -> None: """Test that notify subprocess exceptions are handled correctly.""" with patch( diff --git a/tests/components/command_line/test_sensor.py b/tests/components/command_line/test_sensor.py index c9a4860b987d17..62ec1dbe97bd0d 100644 --- a/tests/components/command_line/test_sensor.py +++ b/tests/components/command_line/test_sensor.py @@ -4,6 +4,8 @@ from typing import Any from unittest.mock import patch +from pytest import LogCaptureFixture + from homeassistant import setup from homeassistant.components.sensor import DOMAIN from homeassistant.core import HomeAssistant @@ -98,7 +100,9 @@ async def test_template_render_with_quote(hass: HomeAssistant) -> None: ) -async def test_bad_template_render(caplog: Any, hass: HomeAssistant) -> None: +async def test_bad_template_render( + caplog: LogCaptureFixture, hass: HomeAssistant +) -> None: """Test rendering a broken template.""" await setup_test_entities( @@ -141,7 +145,9 @@ async def test_update_with_json_attrs(hass: HomeAssistant) -> None: assert entity_state.attributes["key_three"] == "value_three" -async def test_update_with_json_attrs_no_data(caplog, hass: HomeAssistant) -> None: # type: ignore[no-untyped-def] +async def test_update_with_json_attrs_no_data( + caplog: LogCaptureFixture, hass: HomeAssistant +) -> None: """Test attributes when no JSON result fetched.""" await setup_test_entities( @@ -157,7 +163,9 @@ async def test_update_with_json_attrs_no_data(caplog, hass: HomeAssistant) -> No assert "Empty reply found when expecting JSON data" in caplog.text -async def test_update_with_json_attrs_not_dict(caplog, hass: HomeAssistant) -> None: # type: ignore[no-untyped-def] +async def test_update_with_json_attrs_not_dict( + caplog: LogCaptureFixture, hass: HomeAssistant +) -> None: """Test attributes when the return value not a dict.""" await setup_test_entities( @@ -173,7 +181,9 @@ async def test_update_with_json_attrs_not_dict(caplog, hass: HomeAssistant) -> N assert "JSON result was not a dictionary" in caplog.text -async def test_update_with_json_attrs_bad_json(caplog, hass: HomeAssistant) -> None: # type: ignore[no-untyped-def] +async def test_update_with_json_attrs_bad_json( + caplog: LogCaptureFixture, hass: HomeAssistant +) -> None: """Test attributes when the return value is invalid JSON.""" await setup_test_entities( @@ -189,7 +199,9 @@ async def test_update_with_json_attrs_bad_json(caplog, hass: HomeAssistant) -> N assert "Unable to parse output as JSON" in caplog.text -async def test_update_with_missing_json_attrs(caplog, hass: HomeAssistant) -> None: # type: ignore[no-untyped-def] +async def test_update_with_missing_json_attrs( + caplog: LogCaptureFixture, hass: HomeAssistant +) -> None: """Test attributes when an expected key is missing.""" await setup_test_entities( @@ -208,7 +220,9 @@ async def test_update_with_missing_json_attrs(caplog, hass: HomeAssistant) -> No assert "missing_key" not in entity_state.attributes -async def test_update_with_unnecessary_json_attrs(caplog, hass: HomeAssistant) -> None: # type: ignore[no-untyped-def] +async def test_update_with_unnecessary_json_attrs( + caplog: LogCaptureFixture, hass: HomeAssistant +) -> None: """Test attributes when an expected key is missing.""" await setup_test_entities( @@ -226,7 +240,7 @@ async def test_update_with_unnecessary_json_attrs(caplog, hass: HomeAssistant) - assert "key_three" not in entity_state.attributes -async def test_unique_id(hass): +async def test_unique_id(hass: HomeAssistant) -> None: """Test unique_id option and if it only creates one sensor per id.""" assert await setup.async_setup_component( hass, diff --git a/tests/components/command_line/test_switch.py b/tests/components/command_line/test_switch.py index f918c7500ad665..307974ab3fedf3 100644 --- a/tests/components/command_line/test_switch.py +++ b/tests/components/command_line/test_switch.py @@ -8,6 +8,8 @@ from typing import Any from unittest.mock import patch +from pytest import LogCaptureFixture + from homeassistant import setup from homeassistant.components.switch import DOMAIN, SCAN_INTERVAL from homeassistant.const import ( @@ -269,10 +271,13 @@ async def test_name_is_set_correctly(hass: HomeAssistant) -> None: ) entity_state = hass.states.get("switch.test") + assert entity_state assert entity_state.name == "Test friendly name!" -async def test_switch_command_state_fail(caplog: Any, hass: HomeAssistant) -> None: +async def test_switch_command_state_fail( + caplog: LogCaptureFixture, hass: HomeAssistant +) -> None: """Test that switch failures are handled correctly.""" await setup_test_entity( hass, @@ -289,6 +294,7 @@ async def test_switch_command_state_fail(caplog: Any, hass: HomeAssistant) -> No await hass.async_block_till_done() entity_state = hass.states.get("switch.test") + assert entity_state assert entity_state.state == "on" await hass.services.async_call( @@ -300,13 +306,14 @@ async def test_switch_command_state_fail(caplog: Any, hass: HomeAssistant) -> No await hass.async_block_till_done() entity_state = hass.states.get("switch.test") + assert entity_state assert entity_state.state == "on" assert "Command failed" in caplog.text async def test_switch_command_state_code_exceptions( - caplog: Any, hass: HomeAssistant + caplog: LogCaptureFixture, hass: HomeAssistant ) -> None: """Test that switch state code exceptions are handled correctly.""" @@ -339,7 +346,7 @@ async def test_switch_command_state_code_exceptions( async def test_switch_command_state_value_exceptions( - caplog: Any, hass: HomeAssistant + caplog: LogCaptureFixture, hass: HomeAssistant ) -> None: """Test that switch state value exceptions are handled correctly.""" @@ -372,14 +379,14 @@ async def test_switch_command_state_value_exceptions( assert "Error trying to exec command" in caplog.text -async def test_no_switches(caplog: Any, hass: HomeAssistant) -> None: +async def test_no_switches(caplog: LogCaptureFixture, hass: HomeAssistant) -> None: """Test with no switches.""" await setup_test_entity(hass, {}) assert "No switches" in caplog.text -async def test_unique_id(hass): +async def test_unique_id(hass: HomeAssistant) -> None: """Test unique_id option and if it only creates one switch per id.""" await setup_test_entity( hass, From 89b0d602d6afaaec8099458a279d5ae89852d3c2 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 12 Feb 2022 15:20:55 +0100 Subject: [PATCH 0571/1098] Bump yalesmartalarmclient to v0.3.8 (#66381) --- homeassistant/components/yale_smart_alarm/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/yale_smart_alarm/manifest.json b/homeassistant/components/yale_smart_alarm/manifest.json index b2c9cd82f5aabf..0b1a5a94da09e5 100644 --- a/homeassistant/components/yale_smart_alarm/manifest.json +++ b/homeassistant/components/yale_smart_alarm/manifest.json @@ -2,7 +2,7 @@ "domain": "yale_smart_alarm", "name": "Yale Smart Living", "documentation": "https://www.home-assistant.io/integrations/yale_smart_alarm", - "requirements": ["yalesmartalarmclient==0.3.7"], + "requirements": ["yalesmartalarmclient==0.3.8"], "codeowners": ["@gjohansson-ST"], "config_flow": true, "iot_class": "cloud_polling", diff --git a/requirements_all.txt b/requirements_all.txt index 7740a0363a18b6..7b5397cb44de44 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2517,7 +2517,7 @@ xmltodict==0.12.0 xs1-api-client==3.0.0 # homeassistant.components.yale_smart_alarm -yalesmartalarmclient==0.3.7 +yalesmartalarmclient==0.3.8 # homeassistant.components.august yalexs==1.1.22 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f9fd89fa2058ca..325b6f8f554cde 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1551,7 +1551,7 @@ xknx==0.19.2 xmltodict==0.12.0 # homeassistant.components.yale_smart_alarm -yalesmartalarmclient==0.3.7 +yalesmartalarmclient==0.3.8 # homeassistant.components.august yalexs==1.1.22 From 62d49dcf98574d456b0616877246e7063dba81a7 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Sat, 12 Feb 2022 14:22:21 +0000 Subject: [PATCH 0572/1098] Fix missing refactors of EntityCategory.XXX (#66379) * Fix missing refactors of EntityCategory.XXX * Fix entity_category refactor for homewizard --- homeassistant/components/fritz/button.py | 11 +++++------ homeassistant/components/fritzbox/binary_sensor.py | 6 +++--- homeassistant/components/goodwe/number.py | 8 ++++---- homeassistant/components/goodwe/select.py | 5 ++--- homeassistant/components/homewizard/sensor.py | 11 +++++------ homeassistant/components/onvif/button.py | 4 ++-- homeassistant/components/vicare/button.py | 4 ++-- homeassistant/components/zha/button.py | 5 +++-- homeassistant/components/zha/select.py | 5 +++-- homeassistant/components/zha/sensor.py | 3 +-- 10 files changed, 30 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/fritz/button.py b/homeassistant/components/fritz/button.py index d17d1f4d9ef077..e72af839e4cf9e 100644 --- a/homeassistant/components/fritz/button.py +++ b/homeassistant/components/fritz/button.py @@ -12,10 +12,9 @@ ButtonEntityDescription, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ENTITY_CATEGORY_CONFIG from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC -from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity import DeviceInfo, EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from .common import AvmWrapper @@ -41,28 +40,28 @@ class FritzButtonDescription(ButtonEntityDescription, FritzButtonDescriptionMixi key="firmware_update", name="Firmware Update", device_class=ButtonDeviceClass.UPDATE, - entity_category=ENTITY_CATEGORY_CONFIG, + entity_category=EntityCategory.CONFIG, press_action=lambda avm_wrapper: avm_wrapper.async_trigger_firmware_update(), ), FritzButtonDescription( key="reboot", name="Reboot", device_class=ButtonDeviceClass.RESTART, - entity_category=ENTITY_CATEGORY_CONFIG, + entity_category=EntityCategory.CONFIG, press_action=lambda avm_wrapper: avm_wrapper.async_trigger_reboot(), ), FritzButtonDescription( key="reconnect", name="Reconnect", device_class=ButtonDeviceClass.RESTART, - entity_category=ENTITY_CATEGORY_CONFIG, + entity_category=EntityCategory.CONFIG, press_action=lambda avm_wrapper: avm_wrapper.async_trigger_reconnect(), ), FritzButtonDescription( key="cleanup", name="Cleanup", icon="mdi:broom", - entity_category=ENTITY_CATEGORY_CONFIG, + entity_category=EntityCategory.CONFIG, press_action=lambda avm_wrapper: avm_wrapper.async_trigger_cleanup(), ), ] diff --git a/homeassistant/components/fritzbox/binary_sensor.py b/homeassistant/components/fritzbox/binary_sensor.py index b58f3311cb560e..b80d853e562b20 100644 --- a/homeassistant/components/fritzbox/binary_sensor.py +++ b/homeassistant/components/fritzbox/binary_sensor.py @@ -13,8 +13,8 @@ BinarySensorEntityDescription, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ENTITY_CATEGORY_CONFIG from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import FritzBoxEntity @@ -49,7 +49,7 @@ class FritzBinarySensorEntityDescription( key="lock", name="Button Lock on Device", device_class=BinarySensorDeviceClass.LOCK, - entity_category=ENTITY_CATEGORY_CONFIG, + entity_category=EntityCategory.CONFIG, suitable=lambda device: device.lock is not None, is_on=lambda device: not device.lock, ), @@ -57,7 +57,7 @@ class FritzBinarySensorEntityDescription( key="device_lock", name="Button Lock via UI", device_class=BinarySensorDeviceClass.LOCK, - entity_category=ENTITY_CATEGORY_CONFIG, + entity_category=EntityCategory.CONFIG, suitable=lambda device: device.device_lock is not None, is_on=lambda device: not device.device_lock, ), diff --git a/homeassistant/components/goodwe/number.py b/homeassistant/components/goodwe/number.py index 06a31a4e10ab44..80c7885f26c704 100644 --- a/homeassistant/components/goodwe/number.py +++ b/homeassistant/components/goodwe/number.py @@ -9,9 +9,9 @@ from homeassistant.components.number import NumberEntity, NumberEntityDescription from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ENTITY_CATEGORY_CONFIG, PERCENTAGE, POWER_WATT +from homeassistant.const import PERCENTAGE, POWER_WATT from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity import DeviceInfo, EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN, KEY_DEVICE_INFO, KEY_INVERTER @@ -39,7 +39,7 @@ class GoodweNumberEntityDescription( key="grid_export_limit", name="Grid export limit", icon="mdi:transmission-tower", - entity_category=ENTITY_CATEGORY_CONFIG, + entity_category=EntityCategory.CONFIG, unit_of_measurement=POWER_WATT, getter=lambda inv: inv.get_grid_export_limit(), setter=lambda inv, val: inv.set_grid_export_limit(val), @@ -51,7 +51,7 @@ class GoodweNumberEntityDescription( key="battery_discharge_depth", name="Depth of discharge (on-grid)", icon="mdi:battery-arrow-down", - entity_category=ENTITY_CATEGORY_CONFIG, + entity_category=EntityCategory.CONFIG, unit_of_measurement=PERCENTAGE, getter=lambda inv: inv.get_ongrid_battery_dod(), setter=lambda inv, val: inv.set_ongrid_battery_dod(val), diff --git a/homeassistant/components/goodwe/select.py b/homeassistant/components/goodwe/select.py index 985c799110d933..c8fa44b7e26051 100644 --- a/homeassistant/components/goodwe/select.py +++ b/homeassistant/components/goodwe/select.py @@ -5,9 +5,8 @@ from homeassistant.components.select import SelectEntity, SelectEntityDescription from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ENTITY_CATEGORY_CONFIG from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity import DeviceInfo, EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN, KEY_DEVICE_INFO, KEY_INVERTER @@ -26,7 +25,7 @@ key="operation_mode", name="Inverter operation mode", icon="mdi:solar-power", - entity_category=ENTITY_CATEGORY_CONFIG, + entity_category=EntityCategory.CONFIG, ) diff --git a/homeassistant/components/homewizard/sensor.py b/homeassistant/components/homewizard/sensor.py index c2a11386cf4982..e9a09f9db86098 100644 --- a/homeassistant/components/homewizard/sensor.py +++ b/homeassistant/components/homewizard/sensor.py @@ -16,13 +16,12 @@ DEVICE_CLASS_GAS, DEVICE_CLASS_POWER, ENERGY_KILO_WATT_HOUR, - ENTITY_CATEGORY_DIAGNOSTIC, PERCENTAGE, POWER_WATT, VOLUME_CUBIC_METERS, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity import DeviceInfo, EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -37,19 +36,19 @@ key="smr_version", name="DSMR Version", icon="mdi:counter", - entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + entity_category=EntityCategory.DIAGNOSTIC, ), SensorEntityDescription( key="meter_model", name="Smart Meter Model", icon="mdi:gauge", - entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + entity_category=EntityCategory.DIAGNOSTIC, ), SensorEntityDescription( key="wifi_ssid", name="Wifi SSID", icon="mdi:wifi", - entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + entity_category=EntityCategory.DIAGNOSTIC, ), SensorEntityDescription( key="wifi_strength", @@ -57,7 +56,7 @@ icon="mdi:wifi", native_unit_of_measurement=PERCENTAGE, state_class=STATE_CLASS_MEASUREMENT, - entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), SensorEntityDescription( diff --git a/homeassistant/components/onvif/button.py b/homeassistant/components/onvif/button.py index 034573299e647e..23ea5124e617df 100644 --- a/homeassistant/components/onvif/button.py +++ b/homeassistant/components/onvif/button.py @@ -2,8 +2,8 @@ from homeassistant.components.button import ButtonDeviceClass, ButtonEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ENTITY_CATEGORY_CONFIG from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from .base import ONVIFBaseEntity @@ -25,7 +25,7 @@ class RebootButton(ONVIFBaseEntity, ButtonEntity): """Defines a ONVIF reboot button.""" _attr_device_class = ButtonDeviceClass.RESTART - _attr_entity_category = ENTITY_CATEGORY_CONFIG + _attr_entity_category = EntityCategory.CONFIG def __init__(self, device: ONVIFDevice) -> None: """Initialize the button entity.""" diff --git a/homeassistant/components/vicare/button.py b/homeassistant/components/vicare/button.py index 35133b55bd1c2a..e924a735e0f7f8 100644 --- a/homeassistant/components/vicare/button.py +++ b/homeassistant/components/vicare/button.py @@ -14,8 +14,8 @@ from homeassistant.components.button import ButtonEntity, ButtonEntityDescription from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ENTITY_CATEGORY_CONFIG from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ViCareRequiredKeysMixin @@ -36,7 +36,7 @@ class ViCareButtonEntityDescription(ButtonEntityDescription, ViCareRequiredKeysM key=BUTTON_DHW_ACTIVATE_ONETIME_CHARGE, name="Activate one-time charge", icon="mdi:shower-head", - entity_category=ENTITY_CATEGORY_CONFIG, + entity_category=EntityCategory.CONFIG, value_getter=lambda api: api.activateOneTimeCharge(), ), ) diff --git a/homeassistant/components/zha/button.py b/homeassistant/components/zha/button.py index 90148ba42f39f5..abfa94f5906ef8 100644 --- a/homeassistant/components/zha/button.py +++ b/homeassistant/components/zha/button.py @@ -8,9 +8,10 @@ from homeassistant.components.button import ButtonDeviceClass, ButtonEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ENTITY_CATEGORY_DIAGNOSTIC, Platform +from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from .core import discovery @@ -96,7 +97,7 @@ def create_entity( return cls(unique_id, zha_device, channels, **kwargs) _attr_device_class: ButtonDeviceClass = ButtonDeviceClass.UPDATE - _attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC + _attr_entity_category = EntityCategory.DIAGNOSTIC _command_name = "identify" def get_args(self) -> list[Any]: diff --git a/homeassistant/components/zha/select.py b/homeassistant/components/zha/select.py index ff76023d96d82a..7cb214566d1fff 100644 --- a/homeassistant/components/zha/select.py +++ b/homeassistant/components/zha/select.py @@ -8,9 +8,10 @@ from homeassistant.components.select import SelectEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ENTITY_CATEGORY_CONFIG, STATE_UNKNOWN, Platform +from homeassistant.const import STATE_UNKNOWN, Platform from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from .core import discovery @@ -46,7 +47,7 @@ async def async_setup_entry( class ZHAEnumSelectEntity(ZhaEntity, SelectEntity): """Representation of a ZHA select entity.""" - _attr_entity_category = ENTITY_CATEGORY_CONFIG + _attr_entity_category = EntityCategory.CONFIG _enum: Enum = None def __init__( diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index eb79634695fe06..1e7d4f28a38c66 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -25,7 +25,6 @@ ELECTRIC_CURRENT_AMPERE, ELECTRIC_POTENTIAL_VOLT, ENERGY_KILO_WATT_HOUR, - ENTITY_CATEGORY_DIAGNOSTIC, LIGHT_LUX, PERCENTAGE, POWER_VOLT_AMPERE, @@ -699,7 +698,7 @@ class RSSISensor(Sensor, id_suffix="rssi"): _state_class: SensorStateClass = SensorStateClass.MEASUREMENT _device_class: SensorDeviceClass = SensorDeviceClass.SIGNAL_STRENGTH - _attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC + _attr_entity_category = EntityCategory.DIAGNOSTIC _attr_entity_registry_enabled_default = False @classmethod From 6d20e68e6dbb51c1023d4ee85bf706fc180b4acd Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 12 Feb 2022 15:28:54 +0100 Subject: [PATCH 0573/1098] Code quality scrape (#65441) --- .coveragerc | 1 - CODEOWNERS | 1 + homeassistant/components/scrape/sensor.py | 91 +++++----- requirements_test_all.txt | 3 + tests/components/scrape/__init__.py | 83 +++++++++ tests/components/scrape/test_sensor.py | 206 ++++++++++++++++++++++ 6 files changed, 335 insertions(+), 50 deletions(-) create mode 100644 tests/components/scrape/__init__.py create mode 100644 tests/components/scrape/test_sensor.py diff --git a/.coveragerc b/.coveragerc index a4819a124c942a..5fa34f27656837 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1009,7 +1009,6 @@ omit = homeassistant/components/samsungtv/diagnostics.py homeassistant/components/satel_integra/* homeassistant/components/schluter/* - homeassistant/components/scrape/sensor.py homeassistant/components/screenlogic/__init__.py homeassistant/components/screenlogic/binary_sensor.py homeassistant/components/screenlogic/climate.py diff --git a/CODEOWNERS b/CODEOWNERS index b7b688a0e73c06..ceefad2bad42a5 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -808,6 +808,7 @@ homeassistant/components/scene/* @home-assistant/core tests/components/scene/* @home-assistant/core homeassistant/components/schluter/* @prairieapps homeassistant/components/scrape/* @fabaff +tests/components/scrape/* @fabaff homeassistant/components/screenlogic/* @dieselrabbit @bdraco tests/components/screenlogic/* @dieselrabbit @bdraco homeassistant/components/script/* @home-assistant/core diff --git a/homeassistant/components/scrape/sensor.py b/homeassistant/components/scrape/sensor.py index c0523e4cbe2537..8f2a672ef06ab5 100644 --- a/homeassistant/components/scrape/sensor.py +++ b/homeassistant/components/scrape/sensor.py @@ -2,6 +2,7 @@ from __future__ import annotations import logging +from typing import Any from bs4 import BeautifulSoup import httpx @@ -11,7 +12,7 @@ from homeassistant.components.sensor import ( CONF_STATE_CLASS, DEVICE_CLASSES_SCHEMA, - PLATFORM_SCHEMA, + PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA, STATE_CLASSES_SCHEMA, SensorEntity, ) @@ -33,6 +34,7 @@ from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.template import Template from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType _LOGGER = logging.getLogger(__name__) @@ -44,7 +46,7 @@ DEFAULT_NAME = "Web scrape" DEFAULT_VERIFY_SSL = True -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( +PLATFORM_SCHEMA = PARENT_PLATFORM_SCHEMA.extend( { vol.Required(CONF_RESOURCE): cv.string, vol.Required(CONF_SELECT): cv.string, @@ -73,32 +75,32 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the Web scrape sensor.""" - name = config.get(CONF_NAME) - resource = config.get(CONF_RESOURCE) - method = "GET" - payload = None - headers = config.get(CONF_HEADERS) - verify_ssl = config.get(CONF_VERIFY_SSL) - select = config.get(CONF_SELECT) - attr = config.get(CONF_ATTR) - index = config.get(CONF_INDEX) - unit = config.get(CONF_UNIT_OF_MEASUREMENT) - device_class = config.get(CONF_DEVICE_CLASS) - state_class = config.get(CONF_STATE_CLASS) - username = config.get(CONF_USERNAME) - password = config.get(CONF_PASSWORD) - - if (value_template := config.get(CONF_VALUE_TEMPLATE)) is not None: + name: str = config[CONF_NAME] + resource: str = config[CONF_RESOURCE] + method: str = "GET" + payload: str | None = None + headers: str | None = config.get(CONF_HEADERS) + verify_ssl: bool = config[CONF_VERIFY_SSL] + select: str | None = config.get(CONF_SELECT) + attr: str | None = config.get(CONF_ATTR) + index: int = config[CONF_INDEX] + unit: str | None = config.get(CONF_UNIT_OF_MEASUREMENT) + device_class: str | None = config.get(CONF_DEVICE_CLASS) + state_class: str | None = config.get(CONF_STATE_CLASS) + username: str | None = config.get(CONF_USERNAME) + password: str | None = config.get(CONF_PASSWORD) + value_template: Template | None = config.get(CONF_VALUE_TEMPLATE) + + if value_template is not None: value_template.hass = hass - auth: httpx.DigestAuth | tuple[str, str] | None + auth: httpx.DigestAuth | tuple[str, str] | None = None if username and password: if config.get(CONF_AUTHENTICATION) == HTTP_DIGEST_AUTHENTICATION: auth = httpx.DigestAuth(username, password) else: auth = (username, password) - else: - auth = None + rest = RestData(hass, method, resource, auth, headers, None, payload, verify_ssl) await rest.async_update() @@ -128,19 +130,19 @@ class ScrapeSensor(SensorEntity): def __init__( self, - rest, - name, - select, - attr, - index, - value_template, - unit, - device_class, - state_class, - ): + rest: RestData, + name: str, + select: str | None, + attr: str | None, + index: int, + value_template: Template | None, + unit: str | None, + device_class: str | None, + state_class: str | None, + ) -> None: """Initialize a web scrape sensor.""" self.rest = rest - self._state = None + self._attr_native_value = None self._select = select self._attr = attr self._index = index @@ -150,12 +152,7 @@ def __init__( self._attr_device_class = device_class self._attr_state_class = state_class - @property - def native_value(self): - """Return the state of the device.""" - return self._state - - def _extract_value(self): + def _extract_value(self) -> Any: """Parse the html extraction in the executor.""" raw_data = BeautifulSoup(self.rest.data, "html.parser") _LOGGER.debug(raw_data) @@ -180,30 +177,26 @@ def _extract_value(self): _LOGGER.debug(value) return value - async def async_update(self): + async def async_update(self) -> None: """Get the latest data from the source and updates the state.""" await self.rest.async_update() await self._async_update_from_rest_data() - async def async_added_to_hass(self): + async def async_added_to_hass(self) -> None: """Ensure the data from the initial update is reflected in the state.""" await self._async_update_from_rest_data() - async def _async_update_from_rest_data(self): + async def _async_update_from_rest_data(self) -> None: """Update state from the rest data.""" if self.rest.data is None: _LOGGER.error("Unable to retrieve data for %s", self.name) return - try: - value = await self.hass.async_add_executor_job(self._extract_value) - except IndexError: - _LOGGER.error("Unable to extract data from HTML for %s", self.name) - return + value = await self.hass.async_add_executor_job(self._extract_value) if self._value_template is not None: - self._state = self._value_template.async_render_with_possible_json_value( - value, None + self._attr_native_value = ( + self._value_template.async_render_with_possible_json_value(value, None) ) else: - self._state = value + self._attr_native_value = value diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 325b6f8f554cde..10bb8f5af4d4c2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -275,6 +275,9 @@ azure-eventhub==5.7.0 # homeassistant.components.homekit base36==0.1.1 +# homeassistant.components.scrape +beautifulsoup4==4.10.0 + # homeassistant.components.zha bellows==0.29.0 diff --git a/tests/components/scrape/__init__.py b/tests/components/scrape/__init__.py new file mode 100644 index 00000000000000..0ba9266a79d964 --- /dev/null +++ b/tests/components/scrape/__init__.py @@ -0,0 +1,83 @@ +"""Tests for scrape component.""" +from __future__ import annotations + +from typing import Any + + +def return_config( + select, + name, + *, + attribute=None, + index=None, + template=None, + uom=None, + device_class=None, + state_class=None, + authentication=None, + username=None, + password=None, + headers=None, +) -> dict[str, dict[str, Any]]: + """Return config.""" + config = { + "platform": "scrape", + "resource": "https://www.home-assistant.io", + "select": select, + "name": name, + } + if attribute: + config["attribute"] = attribute + if index: + config["index"] = index + if template: + config["value_template"] = template + if uom: + config["unit_of_measurement"] = uom + if device_class: + config["device_class"] = device_class + if state_class: + config["state_class"] = state_class + if authentication: + config["authentication"] = authentication + config["username"] = username + config["password"] = password + if headers: + config["headers"] = headers + return config + + +class MockRestData: + """Mock RestData.""" + + def __init__( + self, + payload, + ): + """Init RestDataMock.""" + self.data: str | None = None + self.payload = payload + self.count = 0 + + async def async_update(self, data: bool | None = True) -> None: + """Update.""" + self.count += 1 + if self.payload == "test_scrape_sensor": + self.data = ( + "
" + "

Current Version: 2021.12.10

Released: January 17, 2022" + "
" + "" + ) + if self.payload == "test_scrape_uom_and_classes": + self.data = ( + "
" + "

Current Temperature: 22.1

" + "
" + ) + if self.payload == "test_scrape_sensor_authentication": + self.data = "
secret text
" + if self.payload == "test_scrape_sensor_no_data": + self.data = None + if self.count == 3: + self.data = None diff --git a/tests/components/scrape/test_sensor.py b/tests/components/scrape/test_sensor.py new file mode 100644 index 00000000000000..aaf156208efd34 --- /dev/null +++ b/tests/components/scrape/test_sensor.py @@ -0,0 +1,206 @@ +"""The tests for the Scrape sensor platform.""" +from __future__ import annotations + +from unittest.mock import patch + +from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass +from homeassistant.components.sensor.const import CONF_STATE_CLASS +from homeassistant.const import ( + CONF_DEVICE_CLASS, + CONF_UNIT_OF_MEASUREMENT, + STATE_UNKNOWN, + TEMP_CELSIUS, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_component import async_update_entity +from homeassistant.setup import async_setup_component + +from . import MockRestData, return_config + +DOMAIN = "scrape" + + +async def test_scrape_sensor(hass: HomeAssistant) -> None: + """Test Scrape sensor minimal.""" + config = {"sensor": return_config(select=".current-version h1", name="HA version")} + + mocker = MockRestData("test_scrape_sensor") + with patch( + "homeassistant.components.scrape.sensor.RestData", + return_value=mocker, + ): + assert await async_setup_component(hass, "sensor", config) + await hass.async_block_till_done() + + state = hass.states.get("sensor.ha_version") + assert state.state == "Current Version: 2021.12.10" + + +async def test_scrape_sensor_value_template(hass: HomeAssistant) -> None: + """Test Scrape sensor with value template.""" + config = { + "sensor": return_config( + select=".current-version h1", + name="HA version", + template="{{ value.split(':')[1] }}", + ) + } + + mocker = MockRestData("test_scrape_sensor") + with patch( + "homeassistant.components.scrape.sensor.RestData", + return_value=mocker, + ): + assert await async_setup_component(hass, "sensor", config) + await hass.async_block_till_done() + + state = hass.states.get("sensor.ha_version") + assert state.state == "2021.12.10" + + +async def test_scrape_uom_and_classes(hass: HomeAssistant) -> None: + """Test Scrape sensor for unit of measurement, device class and state class.""" + config = { + "sensor": return_config( + select=".current-temp h3", + name="Current Temp", + template="{{ value.split(':')[1] }}", + uom="°C", + device_class="temperature", + state_class="measurement", + ) + } + + mocker = MockRestData("test_scrape_uom_and_classes") + with patch( + "homeassistant.components.scrape.sensor.RestData", + return_value=mocker, + ): + assert await async_setup_component(hass, "sensor", config) + await hass.async_block_till_done() + + state = hass.states.get("sensor.current_temp") + assert state.state == "22.1" + assert state.attributes[CONF_UNIT_OF_MEASUREMENT] == TEMP_CELSIUS + assert state.attributes[CONF_DEVICE_CLASS] == SensorDeviceClass.TEMPERATURE + assert state.attributes[CONF_STATE_CLASS] == SensorStateClass.MEASUREMENT + + +async def test_scrape_sensor_authentication(hass: HomeAssistant) -> None: + """Test Scrape sensor with authentication.""" + config = { + "sensor": [ + return_config( + select=".return", + name="Auth page", + username="user@secret.com", + password="12345678", + authentication="digest", + ), + return_config( + select=".return", + name="Auth page2", + username="user@secret.com", + password="12345678", + ), + ] + } + + mocker = MockRestData("test_scrape_sensor_authentication") + with patch( + "homeassistant.components.scrape.sensor.RestData", + return_value=mocker, + ): + assert await async_setup_component(hass, "sensor", config) + await hass.async_block_till_done() + + state = hass.states.get("sensor.auth_page") + assert state.state == "secret text" + state2 = hass.states.get("sensor.auth_page2") + assert state2.state == "secret text" + + +async def test_scrape_sensor_no_data(hass: HomeAssistant) -> None: + """Test Scrape sensor fails on no data.""" + config = {"sensor": return_config(select=".current-version h1", name="HA version")} + + mocker = MockRestData("test_scrape_sensor_no_data") + with patch( + "homeassistant.components.scrape.sensor.RestData", + return_value=mocker, + ): + assert await async_setup_component(hass, "sensor", config) + await hass.async_block_till_done() + + state = hass.states.get("sensor.ha_version") + assert state is None + + +async def test_scrape_sensor_no_data_refresh(hass: HomeAssistant) -> None: + """Test Scrape sensor no data on refresh.""" + config = {"sensor": return_config(select=".current-version h1", name="HA version")} + + mocker = MockRestData("test_scrape_sensor") + with patch( + "homeassistant.components.scrape.sensor.RestData", + return_value=mocker, + ): + assert await async_setup_component(hass, "sensor", config) + await hass.async_block_till_done() + + state = hass.states.get("sensor.ha_version") + assert state + assert state.state == "Current Version: 2021.12.10" + + mocker.data = None + await async_update_entity(hass, "sensor.ha_version") + + assert mocker.data is None + assert state is not None + assert state.state == "Current Version: 2021.12.10" + + +async def test_scrape_sensor_attribute_and_tag(hass: HomeAssistant) -> None: + """Test Scrape sensor with attribute and tag.""" + config = { + "sensor": [ + return_config(select="div", name="HA class", index=1, attribute="class"), + return_config(select="template", name="HA template"), + ] + } + + mocker = MockRestData("test_scrape_sensor") + with patch( + "homeassistant.components.scrape.sensor.RestData", + return_value=mocker, + ): + assert await async_setup_component(hass, "sensor", config) + await hass.async_block_till_done() + + state = hass.states.get("sensor.ha_class") + assert state.state == "['links']" + state2 = hass.states.get("sensor.ha_template") + assert state2.state == "Trying to get" + + +async def test_scrape_sensor_errors(hass: HomeAssistant) -> None: + """Test Scrape sensor handle errors.""" + config = { + "sensor": [ + return_config(select="div", name="HA class", index=5, attribute="class"), + return_config(select="div", name="HA class2", attribute="classes"), + ] + } + + mocker = MockRestData("test_scrape_sensor") + with patch( + "homeassistant.components.scrape.sensor.RestData", + return_value=mocker, + ): + assert await async_setup_component(hass, "sensor", config) + await hass.async_block_till_done() + + state = hass.states.get("sensor.ha_class") + assert state.state == STATE_UNKNOWN + state2 = hass.states.get("sensor.ha_class2") + assert state2.state == STATE_UNKNOWN From d61d1bf49460fd8e64b79c90247a513d9353405c Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 12 Feb 2022 15:42:30 +0100 Subject: [PATCH 0574/1098] Implement diagnostics for yale_smart_alarm (#65085) --- .coveragerc | 1 + .../yale_smart_alarm/diagnostics.py | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 homeassistant/components/yale_smart_alarm/diagnostics.py diff --git a/.coveragerc b/.coveragerc index 5fa34f27656837..de48567eb6c176 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1453,6 +1453,7 @@ omit = homeassistant/components/yale_smart_alarm/binary_sensor.py homeassistant/components/yale_smart_alarm/const.py homeassistant/components/yale_smart_alarm/coordinator.py + homeassistant/components/yale_smart_alarm/diagnostics.py homeassistant/components/yale_smart_alarm/entity.py homeassistant/components/yale_smart_alarm/lock.py homeassistant/components/yamaha_musiccast/__init__.py diff --git a/homeassistant/components/yale_smart_alarm/diagnostics.py b/homeassistant/components/yale_smart_alarm/diagnostics.py new file mode 100644 index 00000000000000..896a3240a221dc --- /dev/null +++ b/homeassistant/components/yale_smart_alarm/diagnostics.py @@ -0,0 +1,30 @@ +"""Diagnostics support for Yale Smart Alarm.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import COORDINATOR, DOMAIN +from .coordinator import YaleDataUpdateCoordinator + +TO_REDACT = { + "address", + "name", + "mac", + "device_id", + "sensor_map", + "lock_map", +} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + coordinator: YaleDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ + COORDINATOR + ] + return async_redact_data(coordinator.data, TO_REDACT) From e5ef192f1a6fd1182914873c60f2553f3fa434cc Mon Sep 17 00:00:00 2001 From: Joshua Roys Date: Sat, 12 Feb 2022 10:56:38 -0500 Subject: [PATCH 0575/1098] Specify specific Nanoleaf models in the manifest (#66326) --- homeassistant/components/nanoleaf/manifest.json | 2 +- homeassistant/generated/zeroconf.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/nanoleaf/manifest.json b/homeassistant/components/nanoleaf/manifest.json index 604e4daaf35971..8d7d76dc3dbbd4 100644 --- a/homeassistant/components/nanoleaf/manifest.json +++ b/homeassistant/components/nanoleaf/manifest.json @@ -7,7 +7,7 @@ "zeroconf": ["_nanoleafms._tcp.local.", "_nanoleafapi._tcp.local."], "homekit" : { "models": [ - "NL*" + "NL29", "NL42", "NL47", "NL48", "NL52", "NL59" ] }, "ssdp": [ diff --git a/homeassistant/generated/zeroconf.py b/homeassistant/generated/zeroconf.py index 06d1940f23d756..93a24520497b09 100644 --- a/homeassistant/generated/zeroconf.py +++ b/homeassistant/generated/zeroconf.py @@ -391,7 +391,12 @@ "Iota": "abode", "LIFX": "lifx", "MYQ": "myq", - "NL*": "nanoleaf", + "NL29": "nanoleaf", + "NL42": "nanoleaf", + "NL47": "nanoleaf", + "NL48": "nanoleaf", + "NL52": "nanoleaf", + "NL59": "nanoleaf", "Netatmo Relay": "netatmo", "PowerView": "hunterdouglas_powerview", "Presence": "netatmo", From 491e2e59788bae3f255efcb3239368c3145d0176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Sat, 12 Feb 2022 17:05:30 +0100 Subject: [PATCH 0576/1098] Update zigpy-zigate to 0.8.0 (#66289) --- homeassistant/components/zha/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index 98c5e277f1708e..009b8e5c775837 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -11,7 +11,7 @@ "zigpy-deconz==0.14.0", "zigpy==0.43.0", "zigpy-xbee==0.14.0", - "zigpy-zigate==0.7.3", + "zigpy-zigate==0.8.0", "zigpy-znp==0.7.0" ], "usb": [ diff --git a/requirements_all.txt b/requirements_all.txt index 7b5397cb44de44..9186eeed73854d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2556,7 +2556,7 @@ zigpy-deconz==0.14.0 zigpy-xbee==0.14.0 # homeassistant.components.zha -zigpy-zigate==0.7.3 +zigpy-zigate==0.8.0 # homeassistant.components.zha zigpy-znp==0.7.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 10bb8f5af4d4c2..b9ece9bf5cf07a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1578,7 +1578,7 @@ zigpy-deconz==0.14.0 zigpy-xbee==0.14.0 # homeassistant.components.zha -zigpy-zigate==0.7.3 +zigpy-zigate==0.8.0 # homeassistant.components.zha zigpy-znp==0.7.0 From 840d33f271d279f610624de8174414f7a90d3dcf Mon Sep 17 00:00:00 2001 From: nklebedev <35492224+nklebedev@users.noreply.github.com> Date: Sat, 12 Feb 2022 19:09:51 +0300 Subject: [PATCH 0577/1098] Fix typo in ebusd WaterPressure const (#66355) --- homeassistant/components/ebusd/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/ebusd/const.py b/homeassistant/components/ebusd/const.py index ac5ca313f2f5a5..ce631297db6e6a 100644 --- a/homeassistant/components/ebusd/const.py +++ b/homeassistant/components/ebusd/const.py @@ -191,7 +191,7 @@ 4, SensorDeviceClass.TEMPERATURE, ], - "WaterPreasure": ["WaterPressure", PRESSURE_BAR, "mdi:pipe", 4, None], + "WaterPressure": ["WaterPressure", PRESSURE_BAR, "mdi:pipe", 4, None], "AverageIgnitionTime": [ "averageIgnitiontime", TIME_SECONDS, From ba83648d273cae1ad2d6f0ecf13d26099e035808 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 12 Feb 2022 17:13:32 +0100 Subject: [PATCH 0578/1098] Fix Spotify session token refresh (#66386) --- homeassistant/components/spotify/__init__.py | 6 ++++++ homeassistant/components/spotify/browse_media.py | 6 +++++- homeassistant/components/spotify/media_player.py | 4 ++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/spotify/__init__.py b/homeassistant/components/spotify/__init__.py index fb66622d8938ed..59ebf1ead55f18 100644 --- a/homeassistant/components/spotify/__init__.py +++ b/homeassistant/components/spotify/__init__.py @@ -107,6 +107,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: raise ConfigEntryNotReady async def _update_devices() -> list[dict[str, Any]]: + if not session.valid_token: + await session.async_ensure_token_valid() + await hass.async_add_executor_job( + spotify.set_auth, session.token["access_token"] + ) + try: devices: dict[str, Any] | None = await hass.async_add_executor_job( spotify.devices diff --git a/homeassistant/components/spotify/browse_media.py b/homeassistant/components/spotify/browse_media.py index ae7c24e80d2a76..efaa07b317286a 100644 --- a/homeassistant/components/spotify/browse_media.py +++ b/homeassistant/components/spotify/browse_media.py @@ -172,7 +172,11 @@ async def async_browse_media_internal( partial(library_payload, can_play_artist=can_play_artist) ) - await session.async_ensure_token_valid() + if not session.valid_token: + await session.async_ensure_token_valid() + await hass.async_add_executor_job( + spotify.set_auth, session.token["access_token"] + ) # Strip prefix if media_content_type: diff --git a/homeassistant/components/spotify/media_player.py b/homeassistant/components/spotify/media_player.py index f6b229e99fd1e5..2b62fdd78c49c4 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -7,7 +7,7 @@ import logging import requests -from spotipy import Spotify, SpotifyException +from spotipy import SpotifyException from yarl import URL from homeassistant.components.media_player import BrowseMedia, MediaPlayerEntity @@ -367,7 +367,7 @@ def update(self) -> None: run_coroutine_threadsafe( self.data.session.async_ensure_token_valid(), self.hass.loop ).result() - self.data.client = Spotify(auth=self.data.session.token["access_token"]) + self.data.client.set_auth(auth=self.data.session.token["access_token"]) current = self.data.client.current_playback() self._currently_playing = current or {} From 17a732197bb4f5b5a9b139bc7bdde707ffd37a02 Mon Sep 17 00:00:00 2001 From: corneyl Date: Sat, 12 Feb 2022 17:15:36 +0100 Subject: [PATCH 0579/1098] Add Picnic re-auth flow (#62938) * Add re-auth handler for Picnic * Extracted authentication part so right form/errors can be shown during re-auth flow * Add tests for Picnic's re-authentication flow * Simplify re-auth flow by using the same step as step_user * Use user step also for re-auth flow instead of having an authenticate step * Add check for when re-auth is done with different account * Remove unnecessary else in Picnic config flow * Fix the step id in the translation strings file --- .../components/picnic/config_flow.py | 37 ++-- homeassistant/components/picnic/strings.json | 6 +- .../components/picnic/translations/en.json | 7 +- tests/components/picnic/test_config_flow.py | 194 ++++++++++++++++-- 4 files changed, 208 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/picnic/config_flow.py b/homeassistant/components/picnic/config_flow.py index 09a1d52428333a..c2d48ca94152a6 100644 --- a/homeassistant/components/picnic/config_flow.py +++ b/homeassistant/components/picnic/config_flow.py @@ -9,6 +9,7 @@ import voluptuous as vol from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import SOURCE_REAUTH from homeassistant.const import CONF_ACCESS_TOKEN, CONF_PASSWORD, CONF_USERNAME from .const import CONF_COUNTRY_CODE, COUNTRY_CODES, DOMAIN @@ -71,8 +72,12 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 + async def async_step_reauth(self, _): + """Perform the re-auth step upon an API authentication error.""" + return await self.async_step_user() + async def async_step_user(self, user_input=None): - """Handle the initial step.""" + """Handle the authentication step, this is the generic step for both `step_user` and `step_reauth`.""" if user_input is None: return self.async_show_form( step_id="user", data_schema=STEP_USER_DATA_SCHEMA @@ -90,17 +95,25 @@ async def async_step_user(self, user_input=None): _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" else: - # Set the unique id and abort if it already exists - await self.async_set_unique_id(info["unique_id"]) - self._abort_if_unique_id_configured() - - return self.async_create_entry( - title=info["title"], - data={ - CONF_ACCESS_TOKEN: auth_token, - CONF_COUNTRY_CODE: user_input[CONF_COUNTRY_CODE], - }, - ) + data = { + CONF_ACCESS_TOKEN: auth_token, + CONF_COUNTRY_CODE: user_input[CONF_COUNTRY_CODE], + } + existing_entry = await self.async_set_unique_id(info["unique_id"]) + + # Abort if we're adding a new config and the unique id is already in use, else create the entry + if self.source != SOURCE_REAUTH: + self._abort_if_unique_id_configured() + return self.async_create_entry(title=info["title"], data=data) + + # In case of re-auth, only continue if an exiting account exists with the same unique id + if existing_entry: + self.hass.config_entries.async_update_entry(existing_entry, data=data) + await self.hass.config_entries.async_reload(existing_entry.entry_id) + return self.async_abort(reason="reauth_successful") + + # Set the error because the account is different + errors["base"] = "different_account" return self.async_show_form( step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors diff --git a/homeassistant/components/picnic/strings.json b/homeassistant/components/picnic/strings.json index 7fbd5e9bef6796..9eb51b2fd2a4ae 100644 --- a/homeassistant/components/picnic/strings.json +++ b/homeassistant/components/picnic/strings.json @@ -12,10 +12,12 @@ "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", - "unknown": "[%key:common::config_flow::error::unknown%]" + "unknown": "[%key:common::config_flow::error::unknown%]", + "different_account": "Account should be the same as used for setting up the integration" }, "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } } } diff --git a/homeassistant/components/picnic/translations/en.json b/homeassistant/components/picnic/translations/en.json index c7097df12a9613..13b62c78757a91 100644 --- a/homeassistant/components/picnic/translations/en.json +++ b/homeassistant/components/picnic/translations/en.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Device is already configured" + "already_configured": "Device is already configured", + "reauth_successful": "Re-authentication was successful" }, "error": { "cannot_connect": "Failed to connect", + "different_account": "Account should be the same as used for setting up the integration", "invalid_auth": "Invalid authentication", "unknown": "Unexpected error" }, @@ -17,6 +19,5 @@ } } } - }, - "title": "Picnic" + } } \ No newline at end of file diff --git a/tests/components/picnic/test_config_flow.py b/tests/components/picnic/test_config_flow.py index b6bcc17a03d22c..3ea54cee593719 100644 --- a/tests/components/picnic/test_config_flow.py +++ b/tests/components/picnic/test_config_flow.py @@ -1,23 +1,20 @@ """Test the Picnic config flow.""" from unittest.mock import patch +import pytest from python_picnic_api.session import PicnicAuthError import requests -from homeassistant import config_entries +from homeassistant import config_entries, data_entry_flow from homeassistant.components.picnic.const import CONF_COUNTRY_CODE, DOMAIN from homeassistant.const import CONF_ACCESS_TOKEN +from tests.common import MockConfigEntry -async def test_form(hass): - """Test we get the form.""" - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - assert result["type"] == "form" - assert result["errors"] is None +@pytest.fixture +def picnic_api(): + """Create PicnicAPI mock with set response data.""" auth_token = "af3wh738j3fa28l9fa23lhiufahu7l" auth_data = { "user_id": "f29-2a6-o32n", @@ -29,13 +26,27 @@ async def test_form(hass): } with patch( "homeassistant.components.picnic.config_flow.PicnicAPI", - ) as mock_picnic, patch( + ) as picnic_mock: + picnic_mock().session.auth_token = auth_token + picnic_mock().get_user.return_value = auth_data + + yield picnic_mock + + +async def test_form(hass, picnic_api): + """Test we get the form and a config entry is created.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["errors"] is None + + with patch( "homeassistant.components.picnic.async_setup_entry", return_value=True, ) as mock_setup_entry: - mock_picnic().session.auth_token = auth_token - mock_picnic().get_user.return_value = auth_data - result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -49,14 +60,14 @@ async def test_form(hass): assert result2["type"] == "create_entry" assert result2["title"] == "Teststreet 123b" assert result2["data"] == { - CONF_ACCESS_TOKEN: auth_token, + CONF_ACCESS_TOKEN: picnic_api().session.auth_token, CONF_COUNTRY_CODE: "NL", } assert len(mock_setup_entry.mock_calls) == 1 async def test_form_invalid_auth(hass): - """Test we handle invalid auth.""" + """Test we handle invalid authentication.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) @@ -74,12 +85,12 @@ async def test_form_invalid_auth(hass): }, ) - assert result2["type"] == "form" + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM assert result2["errors"] == {"base": "invalid_auth"} async def test_form_cannot_connect(hass): - """Test we handle cannot connect error.""" + """Test we handle connection errors.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) @@ -97,7 +108,7 @@ async def test_form_cannot_connect(hass): }, ) - assert result2["type"] == "form" + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM assert result2["errors"] == {"base": "cannot_connect"} @@ -120,5 +131,150 @@ async def test_form_exception(hass): }, ) - assert result2["type"] == "form" + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM assert result2["errors"] == {"base": "unknown"} + + +async def test_form_already_configured(hass, picnic_api): + """Test that an entry with unique id can only be added once.""" + # Create a mocked config entry and make sure to use the same user_id as set for the picnic_api mock response. + MockConfigEntry( + domain=DOMAIN, + unique_id=picnic_api().get_user()["user_id"], + data={CONF_ACCESS_TOKEN: "a3p98fsen.a39p3fap", CONF_COUNTRY_CODE: "NL"}, + ).add_to_hass(hass) + + result_init = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + result_configure = await hass.config_entries.flow.async_configure( + result_init["flow_id"], + { + "username": "test-username", + "password": "test-password", + "country_code": "NL", + }, + ) + await hass.async_block_till_done() + + assert result_configure["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result_configure["reason"] == "already_configured" + + +async def test_step_reauth(hass, picnic_api): + """Test the re-auth flow.""" + # Create a mocked config entry + conf = {CONF_ACCESS_TOKEN: "a3p98fsen.a39p3fap", CONF_COUNTRY_CODE: "NL"} + + MockConfigEntry( + domain=DOMAIN, + unique_id=picnic_api().get_user()["user_id"], + data=conf, + ).add_to_hass(hass) + + # Init a re-auth flow + result_init = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data=conf + ) + assert result_init["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result_init["step_id"] == "user" + + with patch( + "homeassistant.components.picnic.async_setup_entry", + return_value=True, + ): + result_configure = await hass.config_entries.flow.async_configure( + result_init["flow_id"], + { + "username": "test-username", + "password": "test-password", + "country_code": "NL", + }, + ) + await hass.async_block_till_done() + + # Check that the returned flow has type abort because of successful re-authentication + assert result_configure["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result_configure["reason"] == "reauth_successful" + + assert len(hass.config_entries.async_entries()) == 1 + + +async def test_step_reauth_failed(hass): + """Test the re-auth flow when authentication fails.""" + # Create a mocked config entry + user_id = "f29-2a6-o32n" + conf = {CONF_ACCESS_TOKEN: "a3p98fsen.a39p3fap", CONF_COUNTRY_CODE: "NL"} + + MockConfigEntry( + domain=DOMAIN, + unique_id=user_id, + data=conf, + ).add_to_hass(hass) + + # Init a re-auth flow + result_init = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data=conf + ) + assert result_init["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result_init["step_id"] == "user" + + with patch( + "homeassistant.components.picnic.config_flow.PicnicHub.authenticate", + side_effect=PicnicAuthError, + ): + result_configure = await hass.config_entries.flow.async_configure( + result_init["flow_id"], + { + "username": "test-username", + "password": "test-password", + "country_code": "NL", + }, + ) + await hass.async_block_till_done() + + # Check that the returned flow has type form with error set + assert result_configure["type"] == "form" + assert result_configure["errors"] == {"base": "invalid_auth"} + + assert len(hass.config_entries.async_entries()) == 1 + + +async def test_step_reauth_different_account(hass, picnic_api): + """Test the re-auth flow when authentication is done with a different account.""" + # Create a mocked config entry, unique_id should be different that the user id in the api response + conf = {CONF_ACCESS_TOKEN: "a3p98fsen.a39p3fap", CONF_COUNTRY_CODE: "NL"} + + MockConfigEntry( + domain=DOMAIN, + unique_id="3fpawh-ues-af3ho", + data=conf, + ).add_to_hass(hass) + + # Init a re-auth flow + result_init = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data=conf + ) + assert result_init["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result_init["step_id"] == "user" + + with patch( + "homeassistant.components.picnic.async_setup_entry", + return_value=True, + ): + result_configure = await hass.config_entries.flow.async_configure( + result_init["flow_id"], + { + "username": "test-username", + "password": "test-password", + "country_code": "NL", + }, + ) + await hass.async_block_till_done() + + # Check that the returned flow has type form with error set + assert result_configure["type"] == "form" + assert result_configure["errors"] == {"base": "different_account"} + + assert len(hass.config_entries.async_entries()) == 1 From 78064948169914aa2fc8290bba04e0bc76bbf98c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 12 Feb 2022 17:44:47 +0100 Subject: [PATCH 0580/1098] Fix typing [roku] (#66397) --- homeassistant/components/roku/remote.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/roku/remote.py b/homeassistant/components/roku/remote.py index 96c04597d89a5d..9a0cd6f51e3df0 100644 --- a/homeassistant/components/roku/remote.py +++ b/homeassistant/components/roku/remote.py @@ -1,6 +1,7 @@ """Support for the Roku remote.""" from __future__ import annotations +from collections.abc import Iterable from typing import Any from homeassistant.components.remote import ATTR_NUM_REPEATS, RemoteEntity @@ -56,7 +57,7 @@ async def async_turn_off(self, **kwargs: Any) -> None: await self.coordinator.async_request_refresh() @roku_exception_handler - async def async_send_command(self, command: list, **kwargs: Any) -> None: + async def async_send_command(self, command: Iterable[str], **kwargs: Any) -> None: """Send a command to one device.""" num_repeats = kwargs[ATTR_NUM_REPEATS] From a8304392b591e9004b1176c20f8bce3873c83407 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 12 Feb 2022 18:49:37 +0100 Subject: [PATCH 0581/1098] Code quality file (#65258) --- homeassistant/components/file/notify.py | 30 ++++++++++----- homeassistant/components/file/sensor.py | 51 ++++++++++--------------- tests/components/file/test_notify.py | 7 ++-- tests/components/file/test_sensor.py | 29 ++++++++++++-- 4 files changed, 70 insertions(+), 47 deletions(-) diff --git a/homeassistant/components/file/notify.py b/homeassistant/components/file/notify.py index adfe15b7a3ce35..4e9a2b39d1bea2 100644 --- a/homeassistant/components/file/notify.py +++ b/homeassistant/components/file/notify.py @@ -1,5 +1,8 @@ """Support for file notification.""" +from __future__ import annotations + import os +from typing import TextIO import voluptuous as vol @@ -10,7 +13,9 @@ BaseNotificationService, ) from homeassistant.const import CONF_FILENAME +from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.typing import ConfigType import homeassistant.util.dt as dt_util CONF_TIMESTAMP = "timestamp" @@ -23,26 +28,33 @@ ) -def get_service(hass, config, discovery_info=None): +def get_service( + hass: HomeAssistant, config: ConfigType, discovery_info=None +) -> FileNotificationService: """Get the file notification service.""" - filename = config[CONF_FILENAME] - timestamp = config[CONF_TIMESTAMP] + filename: str = config[CONF_FILENAME] + timestamp: bool = config[CONF_TIMESTAMP] - return FileNotificationService(hass, filename, timestamp) + return FileNotificationService(filename, timestamp) class FileNotificationService(BaseNotificationService): """Implement the notification service for the File service.""" - def __init__(self, hass, filename, add_timestamp): + def __init__(self, filename: str, add_timestamp: bool) -> None: """Initialize the service.""" - self.filepath = os.path.join(hass.config.config_dir, filename) + self.filename = filename self.add_timestamp = add_timestamp - def send_message(self, message="", **kwargs): + def send_message(self, message="", **kwargs) -> None: """Send a message to a file.""" - with open(self.filepath, "a", encoding="utf8") as file: - if os.stat(self.filepath).st_size == 0: + file: TextIO + if not self.hass.config.config_dir: + return + + filepath: str = os.path.join(self.hass.config.config_dir, self.filename) + with open(filepath, "a", encoding="utf8") as file: + if os.stat(filepath).st_size == 0: title = f"{kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT)} notifications (Log started: {dt_util.utcnow().isoformat()})\n{'-' * 80}\n" file.write(title) diff --git a/homeassistant/components/file/sensor.py b/homeassistant/components/file/sensor.py index b4822c7bcd5d5d..e69a7701eb9932 100644 --- a/homeassistant/components/file/sensor.py +++ b/homeassistant/components/file/sensor.py @@ -16,6 +16,7 @@ from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.template import Template from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType _LOGGER = logging.getLogger(__name__) @@ -41,11 +42,12 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the file sensor.""" - file_path = config[CONF_FILE_PATH] - name = config[CONF_NAME] - unit = config.get(CONF_UNIT_OF_MEASUREMENT) + file_path: str = config[CONF_FILE_PATH] + name: str = config[CONF_NAME] + unit: str | None = config.get(CONF_UNIT_OF_MEASUREMENT) + value_template: Template | None = config.get(CONF_VALUE_TEMPLATE) - if (value_template := config.get(CONF_VALUE_TEMPLATE)) is not None: + if value_template is not None: value_template.hass = hass if hass.config.is_allowed_path(file_path): @@ -57,33 +59,20 @@ async def async_setup_platform( class FileSensor(SensorEntity): """Implementation of a file sensor.""" - def __init__(self, name, file_path, unit_of_measurement, value_template): + _attr_icon = ICON + + def __init__( + self, + name: str, + file_path: str, + unit_of_measurement: str | None, + value_template: Template | None, + ) -> None: """Initialize the file sensor.""" - self._name = name + self._attr_name = name self._file_path = file_path - self._unit_of_measurement = unit_of_measurement + self._attr_native_unit_of_measurement = unit_of_measurement self._val_tpl = value_template - self._state = None - - @property - def name(self): - """Return the name of the sensor.""" - return self._name - - @property - def native_unit_of_measurement(self): - """Return the unit the value is expressed in.""" - return self._unit_of_measurement - - @property - def icon(self): - """Return the icon to use in the frontend, if any.""" - return ICON - - @property - def native_value(self): - """Return the state of the sensor.""" - return self._state def update(self): """Get the latest entry from a file and updates the state.""" @@ -100,8 +89,8 @@ def update(self): return if self._val_tpl is not None: - self._state = self._val_tpl.async_render_with_possible_json_value( - data, None + self._attr_native_value = ( + self._val_tpl.async_render_with_possible_json_value(data, None) ) else: - self._state = data + self._attr_native_value = data diff --git a/tests/components/file/test_notify.py b/tests/components/file/test_notify.py index 9650c6945a61db..5c91460237eb19 100644 --- a/tests/components/file/test_notify.py +++ b/tests/components/file/test_notify.py @@ -4,15 +4,16 @@ import pytest -import homeassistant.components.notify as notify +from homeassistant.components import notify from homeassistant.components.notify import ATTR_TITLE_DEFAULT +from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from tests.common import assert_setup_component -async def test_bad_config(hass): +async def test_bad_config(hass: HomeAssistant): """Test set up the platform with bad/missing config.""" config = {notify.DOMAIN: {"name": "test", "platform": "file"}} with assert_setup_component(0) as handle_config: @@ -27,7 +28,7 @@ async def test_bad_config(hass): True, ], ) -async def test_notify_file(hass, timestamp): +async def test_notify_file(hass: HomeAssistant, timestamp: bool): """Test the notify file output.""" filename = "mock_file" message = "one, two, testing, testing" diff --git a/tests/components/file/test_sensor.py b/tests/components/file/test_sensor.py index 99e08362ab75a0..97fe6250d024ef 100644 --- a/tests/components/file/test_sensor.py +++ b/tests/components/file/test_sensor.py @@ -4,6 +4,7 @@ import pytest from homeassistant.const import STATE_UNKNOWN +from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component from tests.common import mock_registry @@ -17,7 +18,7 @@ def entity_reg(hass): @patch("os.path.isfile", Mock(return_value=True)) @patch("os.access", Mock(return_value=True)) -async def test_file_value(hass, entity_reg): +async def test_file_value(hass: HomeAssistant) -> None: """Test the File sensor.""" config = { "sensor": {"platform": "file", "name": "file1", "file_path": "mock.file1"} @@ -36,7 +37,7 @@ async def test_file_value(hass, entity_reg): @patch("os.path.isfile", Mock(return_value=True)) @patch("os.access", Mock(return_value=True)) -async def test_file_value_template(hass, entity_reg): +async def test_file_value_template(hass: HomeAssistant) -> None: """Test the File sensor with JSON entries.""" config = { "sensor": { @@ -47,7 +48,9 @@ async def test_file_value_template(hass, entity_reg): } } - data = '{"temperature": 29, "humidity": 31}\n' '{"temperature": 26, "humidity": 36}' + data = ( + '{"temperature": 29, "humidity": 31}\n' + '{"temperature": 26, "humidity": 36}' + ) m_open = mock_open(read_data=data) with patch( @@ -62,7 +65,7 @@ async def test_file_value_template(hass, entity_reg): @patch("os.path.isfile", Mock(return_value=True)) @patch("os.access", Mock(return_value=True)) -async def test_file_empty(hass, entity_reg): +async def test_file_empty(hass: HomeAssistant) -> None: """Test the File sensor with an empty file.""" config = {"sensor": {"platform": "file", "name": "file3", "file_path": "mock.file"}} @@ -75,3 +78,21 @@ async def test_file_empty(hass, entity_reg): state = hass.states.get("sensor.file3") assert state.state == STATE_UNKNOWN + + +@patch("os.path.isfile", Mock(return_value=True)) +@patch("os.access", Mock(return_value=True)) +async def test_file_path_invalid(hass: HomeAssistant) -> None: + """Test the File sensor with invalid path.""" + config = { + "sensor": {"platform": "file", "name": "file4", "file_path": "mock.file4"} + } + + m_open = mock_open(read_data="43\n45\n21") + with patch( + "homeassistant.components.file.sensor.open", m_open, create=True + ), patch.object(hass.config, "is_allowed_path", return_value=False): + assert await async_setup_component(hass, "sensor", config) + await hass.async_block_till_done() + + assert len(hass.states.async_entity_ids("sensor")) == 0 From a7e5f38a3eadb04fae9243edf9c7289b1ec4187b Mon Sep 17 00:00:00 2001 From: kpine Date: Sat, 12 Feb 2022 13:08:39 -0800 Subject: [PATCH 0582/1098] Add is_controller_node flag to WS node status (#66404) --- homeassistant/components/zwave_js/api.py | 1 + tests/components/zwave_js/fixtures/multisensor_6_state.json | 3 ++- tests/components/zwave_js/test_api.py | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index 6208091dd8d4eb..f347a5187ea977 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -462,6 +462,7 @@ async def websocket_node_status( "ready": node.ready, "zwave_plus_version": node.zwave_plus_version, "highest_security_class": node.highest_security_class, + "is_controller_node": node.is_controller_node, } connection.send_result( msg[ID], diff --git a/tests/components/zwave_js/fixtures/multisensor_6_state.json b/tests/components/zwave_js/fixtures/multisensor_6_state.json index 88cdf893d4a556..2646fecfd3711b 100644 --- a/tests/components/zwave_js/fixtures/multisensor_6_state.json +++ b/tests/components/zwave_js/fixtures/multisensor_6_state.json @@ -1825,5 +1825,6 @@ } } ], - "highestSecurityClass": 7 + "highestSecurityClass": 7, + "isControllerNode": false } diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index f93ba4fbb9395d..3f29c3a2e67240 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -168,6 +168,7 @@ async def test_node_status(hass, multisensor_6, integration, hass_ws_client): assert result["status"] == 1 assert result["zwave_plus_version"] == 1 assert result["highest_security_class"] == SecurityClass.S0_LEGACY + assert not result["is_controller_node"] # Test getting non-existent node fails await ws_client.send_json( From 1053314a309ac2956b94666800790517e41d2589 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 12 Feb 2022 22:51:53 +0100 Subject: [PATCH 0583/1098] Fix error decorator [sonos] (#66399) --- homeassistant/components/sonos/helpers.py | 2 +- homeassistant/components/sonos/switch.py | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/sonos/helpers.py b/homeassistant/components/sonos/helpers.py index 8e7f6fcf294a5c..fbc1d2642eabb1 100644 --- a/homeassistant/components/sonos/helpers.py +++ b/homeassistant/components/sonos/helpers.py @@ -24,7 +24,7 @@ _LOGGER = logging.getLogger(__name__) -_T = TypeVar("_T", "SonosSpeaker", "SonosEntity", "SonosHouseholdCoordinator") +_T = TypeVar("_T", bound="SonosSpeaker | SonosEntity | SonosHouseholdCoordinator") _R = TypeVar("_R") _P = ParamSpec("_P") diff --git a/homeassistant/components/sonos/switch.py b/homeassistant/components/sonos/switch.py index db10d9189e5f26..4ee3253c88959e 100644 --- a/homeassistant/components/sonos/switch.py +++ b/homeassistant/components/sonos/switch.py @@ -3,6 +3,7 @@ import datetime import logging +from typing import Any from soco.exceptions import SoCoSlaveException, SoCoUPnPException @@ -337,19 +338,19 @@ def extra_state_attributes(self): ATTR_INCLUDE_LINKED_ZONES: self.alarm.include_linked_zones, } - async def async_turn_on(self, **kwargs) -> None: + def turn_on(self, **kwargs: Any) -> None: """Turn alarm switch on.""" - await self.async_handle_switch_on_off(turn_on=True) + self._handle_switch_on_off(turn_on=True) - async def async_turn_off(self, **kwargs) -> None: + def turn_off(self, **kwargs: Any) -> None: """Turn alarm switch off.""" - await self.async_handle_switch_on_off(turn_on=False) + self._handle_switch_on_off(turn_on=False) @soco_error() - async def async_handle_switch_on_off(self, turn_on: bool) -> None: + def _handle_switch_on_off(self, turn_on: bool) -> None: """Handle turn on/off of alarm switch.""" self.alarm.enabled = turn_on - await self.hass.async_add_executor_job(self.alarm.save) + self.alarm.save() @callback From e069074f9ea21db2d5412232a3822e81d6b23042 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Sat, 12 Feb 2022 22:58:35 +0100 Subject: [PATCH 0584/1098] Fix mesh role for Fritz old devices (#66369) --- homeassistant/components/fritz/common.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index 4886bbac621dbf..d5410bf232cc43 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -331,11 +331,19 @@ def scan_devices(self, now: datetime | None = None) -> None: _LOGGER.debug("Checking host info for FRITZ!Box device %s", self.host) self._update_available, self._latest_firmware = self._update_device_info() - try: - topology = self.fritz_hosts.get_mesh_topology() - except FritzActionError: - self.mesh_role = MeshRoles.SLAVE - return + if ( + "Hosts1" not in self.connection.services + or "X_AVM-DE_GetMeshListPath" + not in self.connection.services["Hosts1"].actions + ): + self.mesh_role = MeshRoles.NONE + else: + try: + topology = self.fritz_hosts.get_mesh_topology() + except FritzActionError: + self.mesh_role = MeshRoles.SLAVE + # Avoid duplicating device trackers + return _LOGGER.debug("Checking devices for FRITZ!Box device %s", self.host) _default_consider_home = DEFAULT_CONSIDER_HOME.total_seconds() From 2e54daa61f139c7533def7f9dafac935eca2f913 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sat, 12 Feb 2022 14:03:26 -0800 Subject: [PATCH 0585/1098] Redact stream url credentials in debug logging (#66407) --- homeassistant/components/stream/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index 79506c0bda256b..be166d13455618 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -312,7 +312,9 @@ def start(self) -> None: def update_source(self, new_source: str) -> None: """Restart the stream with a new stream source.""" - self._logger.debug("Updating stream source %s", new_source) + self._logger.debug( + "Updating stream source %s", redact_credentials(str(new_source)) + ) self.source = new_source self._fast_restart_once = True self._thread_quit.set() @@ -359,7 +361,7 @@ def _run_worker(self) -> None: self._logger.debug( "Restarting stream worker in %d seconds: %s", wait_timeout, - self.source, + redact_credentials(str(self.source)), ) self._worker_finished() From ac11a9b7ff3c8b62e648d351a2b63125f71fad1d Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 12 Feb 2022 23:08:23 +0100 Subject: [PATCH 0586/1098] Revert Amcrest change (#66412) --- homeassistant/components/amcrest/binary_sensor.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/homeassistant/components/amcrest/binary_sensor.py b/homeassistant/components/amcrest/binary_sensor.py index 697dc0cf4c4c88..e583aad904bfc1 100644 --- a/homeassistant/components/amcrest/binary_sensor.py +++ b/homeassistant/components/amcrest/binary_sensor.py @@ -65,10 +65,6 @@ class AmcrestSensorEntityDescription(BinarySensorEntityDescription): _ONLINE_KEY = "online" -_DOORBELL_KEY = "doorbell" -_DOORBELL_NAME = "Doorbell Button" -_DOORBELL_EVENT_CODE = "CallNoAnswered" - BINARY_SENSORS: tuple[AmcrestSensorEntityDescription, ...] = ( AmcrestSensorEntityDescription( key=_AUDIO_DETECTED_KEY, @@ -115,12 +111,6 @@ class AmcrestSensorEntityDescription(BinarySensorEntityDescription): device_class=BinarySensorDeviceClass.CONNECTIVITY, should_poll=True, ), - AmcrestSensorEntityDescription( - key=_DOORBELL_KEY, - name=_DOORBELL_NAME, - device_class=BinarySensorDeviceClass.OCCUPANCY, - event_code=_DOORBELL_EVENT_CODE, - ), ) BINARY_SENSOR_KEYS = [description.key for description in BINARY_SENSORS] _EXCLUSIVE_OPTIONS = [ From 203bda203dbf2c66a8a58ccf7be10f9dd6d8c3ff Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 13 Feb 2022 00:16:41 +0000 Subject: [PATCH 0587/1098] [ci skip] Translation update --- .../climacell/translations/sensor.nl.json | 2 +- .../components/demo/translations/ja.json | 2 +- .../components/elkm1/translations/he.json | 25 ++++++++++- .../components/elkm1/translations/hu.json | 30 ++++++++++++- .../components/elkm1/translations/nl.json | 30 ++++++++++++- .../components/fivem/translations/he.json | 19 ++++++++ .../components/fivem/translations/hu.json | 22 ++++++++++ .../components/fivem/translations/ja.json | 1 + .../components/fivem/translations/nl.json | 22 ++++++++++ .../components/homekit/translations/he.json | 10 +++++ .../translations/select.he.json | 9 ++++ .../homewizard/translations/he.json | 1 + .../input_boolean/translations/ja.json | 2 +- .../input_datetime/translations/ja.json | 2 +- .../moehlenhoff_alpha2/translations/he.json | 18 ++++++++ .../moehlenhoff_alpha2/translations/hu.json | 19 ++++++++ .../moehlenhoff_alpha2/translations/id.json | 19 ++++++++ .../moehlenhoff_alpha2/translations/nl.json | 19 ++++++++ .../translations/zh-Hant.json | 19 ++++++++ .../components/netgear/translations/hu.json | 2 +- .../components/netgear/translations/nl.json | 2 +- .../components/overkiz/translations/hu.json | 1 + .../components/overkiz/translations/ja.json | 3 +- .../components/overkiz/translations/nl.json | 1 + .../components/picnic/translations/ca.json | 4 +- .../components/picnic/translations/de.json | 4 +- .../components/picnic/translations/en.json | 3 +- .../components/picnic/translations/et.json | 4 +- .../components/picnic/translations/id.json | 4 +- .../components/picnic/translations/ja.json | 4 +- .../components/picnic/translations/pt-BR.json | 4 +- .../components/picnic/translations/ru.json | 4 +- .../components/powerwall/translations/he.json | 7 +++ .../components/powerwall/translations/nl.json | 2 +- .../rtsp_to_webrtc/translations/ja.json | 2 + .../components/steamist/translations/he.json | 5 +++ .../tuya/translations/select.he.json | 43 +++++++++++++++++++ .../tuya/translations/select.hu.json | 35 +++++++++++++++ .../tuya/translations/select.nl.json | 35 +++++++++++++++ .../tuya/translations/sensor.he.json | 8 ++++ .../tuya/translations/sensor.hu.json | 6 +++ .../tuya/translations/sensor.nl.json | 6 +++ .../components/vizio/translations/ja.json | 2 +- .../components/wiz/translations/he.json | 32 ++++++++++++++ .../components/wiz/translations/hu.json | 36 ++++++++++++++++ .../components/wiz/translations/id.json | 6 +-- .../components/wiz/translations/nl.json | 14 +++++- .../components/wiz/translations/zh-Hant.json | 7 +-- .../components/wled/translations/ja.json | 3 +- .../components/zwave_me/translations/he.json | 15 +++++++ .../components/zwave_me/translations/hu.json | 20 +++++++++ .../components/zwave_me/translations/nl.json | 20 +++++++++ 52 files changed, 584 insertions(+), 31 deletions(-) create mode 100644 homeassistant/components/fivem/translations/he.json create mode 100644 homeassistant/components/fivem/translations/hu.json create mode 100644 homeassistant/components/fivem/translations/nl.json create mode 100644 homeassistant/components/homekit_controller/translations/select.he.json create mode 100644 homeassistant/components/moehlenhoff_alpha2/translations/he.json create mode 100644 homeassistant/components/moehlenhoff_alpha2/translations/hu.json create mode 100644 homeassistant/components/moehlenhoff_alpha2/translations/id.json create mode 100644 homeassistant/components/moehlenhoff_alpha2/translations/nl.json create mode 100644 homeassistant/components/moehlenhoff_alpha2/translations/zh-Hant.json create mode 100644 homeassistant/components/tuya/translations/sensor.he.json create mode 100644 homeassistant/components/wiz/translations/he.json create mode 100644 homeassistant/components/wiz/translations/hu.json create mode 100644 homeassistant/components/zwave_me/translations/he.json create mode 100644 homeassistant/components/zwave_me/translations/hu.json create mode 100644 homeassistant/components/zwave_me/translations/nl.json diff --git a/homeassistant/components/climacell/translations/sensor.nl.json b/homeassistant/components/climacell/translations/sensor.nl.json index 37713ec0634251..c457988681bbb6 100644 --- a/homeassistant/components/climacell/translations/sensor.nl.json +++ b/homeassistant/components/climacell/translations/sensor.nl.json @@ -6,7 +6,7 @@ "moderate": "Gematigd", "unhealthy": "Ongezond", "unhealthy_for_sensitive_groups": "Ongezond voor gevoelige groepen", - "very_unhealthy": "Heel ongezond" + "very_unhealthy": "Zeer ongezond" }, "climacell__pollen_index": { "high": "Hoog", diff --git a/homeassistant/components/demo/translations/ja.json b/homeassistant/components/demo/translations/ja.json index d987ee472e2565..e543a83bfe5ca9 100644 --- a/homeassistant/components/demo/translations/ja.json +++ b/homeassistant/components/demo/translations/ja.json @@ -3,7 +3,7 @@ "step": { "options_1": { "data": { - "bool": "\u30aa\u30d7\u30b7\u30e7\u30f3\u306e\u771f\u507d\u5024(booleans)", + "bool": "\u30aa\u30d7\u30b7\u30e7\u30f3\u306e\u771f\u507d\u5024(Booleans)", "constant": "\u5b9a\u6570", "int": "\u6570\u5024\u5165\u529b" } diff --git a/homeassistant/components/elkm1/translations/he.json b/homeassistant/components/elkm1/translations/he.json index e85bab17ac036c..eb49e33c019432 100644 --- a/homeassistant/components/elkm1/translations/he.json +++ b/homeassistant/components/elkm1/translations/he.json @@ -1,17 +1,40 @@ { "config": { + "abort": { + "already_in_progress": "\u05d6\u05e8\u05d9\u05de\u05ea \u05d4\u05ea\u05e6\u05d5\u05e8\u05d4 \u05db\u05d1\u05e8 \u05de\u05ea\u05d1\u05e6\u05e2\u05ea", + "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4" + }, "error": { "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", "invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9", "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" }, + "flow_title": "{mac_address} ({host})", "step": { + "discovered_connection": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "protocol": "\u05e4\u05e8\u05d5\u05d8\u05d5\u05e7\u05d5\u05dc", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + }, + "title": "\u05d4\u05ea\u05d7\u05d1\u05e8 \u05d0\u05dc \u05d1\u05e7\u05e8\u05ea Elk-M1" + }, + "manual_connection": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "protocol": "\u05e4\u05e8\u05d5\u05d8\u05d5\u05e7\u05d5\u05dc", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + }, + "title": "\u05d4\u05ea\u05d7\u05d1\u05e8 \u05d0\u05dc \u05d1\u05e7\u05e8\u05ea Elk-M1" + }, "user": { "data": { + "device": "\u05d4\u05ea\u05e7\u05df", "password": "\u05e1\u05d9\u05e1\u05de\u05d4", "protocol": "\u05e4\u05e8\u05d5\u05d8\u05d5\u05e7\u05d5\u05dc", "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" - } + }, + "title": "\u05d4\u05ea\u05d7\u05d1\u05e8 \u05d0\u05dc \u05d1\u05e7\u05e8\u05ea Elk-M1" } } } diff --git a/homeassistant/components/elkm1/translations/hu.json b/homeassistant/components/elkm1/translations/hu.json index ff6445f0b728b5..d076ad684c2461 100644 --- a/homeassistant/components/elkm1/translations/hu.json +++ b/homeassistant/components/elkm1/translations/hu.json @@ -2,24 +2,50 @@ "config": { "abort": { "address_already_configured": "Az ElkM1 ezzel a c\u00edmmel m\u00e1r konfigur\u00e1lva van", - "already_configured": "Az ezzel az el\u0151taggal rendelkez\u0151 ElkM1 m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az ezzel az el\u0151taggal rendelkez\u0151 ElkM1 m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, + "flow_title": "{mac_address} ({host})", "step": { + "discovered_connection": { + "data": { + "password": "Jelsz\u00f3", + "protocol": "Protokoll", + "temperature_unit": "Az ElkM1 \u00e1ltal haszn\u00e1lt h\u0151m\u00e9rs\u00e9kleti egys\u00e9g.", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "description": "Csatlakoz\u00e1s a felfedezett rendszerhez: {mac_address} ({host})", + "title": "Csatlakoz\u00e1s az Elk-M1 vez\u00e9rl\u0151h\u00f6z" + }, + "manual_connection": { + "data": { + "address": "Az IP-c\u00edm vagy tartom\u00e1ny vagy soros port, ha soros kapcsolaton kereszt\u00fcl csatlakozik.", + "password": "Jelsz\u00f3", + "prefix": "Egyedi el\u0151tag (hagyja \u00fcresen, ha csak egy ElkM1 van).", + "protocol": "Protokoll", + "temperature_unit": "Az ElkM1 \u00e1ltal haszn\u00e1lt h\u0151m\u00e9rs\u00e9kleti egys\u00e9g.", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "description": "A c\u00edmsornak a \"biztons\u00e1gos\" \u00e9s a \"nem biztons\u00e1gos\" eset\u00e9ben a \"address[:port]\" form\u00e1j\u00fanak kell lennie. P\u00e9lda: '192.168.1.1'. A port megad\u00e1sa opcion\u00e1lis, \u00e9s alap\u00e9rtelmez\u00e9s szerint 2101 a \"nem biztons\u00e1gos\" \u00e9s 2601 a \"biztons\u00e1gos\" eset\u00e9ben. A soros protokoll eset\u00e9ben a c\u00edmnek a 'tty[:baud]' form\u00e1j\u00fanak kell lennie. P\u00e9lda: '/dev/ttyS1'. A baud nem k\u00f6telez\u0151, \u00e9s alap\u00e9rtelmez\u00e9s szerint 115200.", + "title": "Csatlakoz\u00e1s az Elk-M1 vez\u00e9rl\u0151h\u00f6z" + }, "user": { "data": { "address": "Az IP-c\u00edm vagy tartom\u00e1ny vagy soros port, ha soros kapcsolaton kereszt\u00fcl csatlakozik.", + "device": "Eszk\u00f6z", "password": "Jelsz\u00f3", "prefix": "Egyedi el\u0151tag (hagyja \u00fcresen, ha csak egy ElkM1 van).", "protocol": "Protokoll", "temperature_unit": "Az ElkM1 h\u0151m\u00e9rs\u00e9kleti egys\u00e9g haszn\u00e1lja.", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" }, - "description": "A c\u00edmsornak a \u201ebiztons\u00e1gos\u201d \u00e9s a \u201enem biztons\u00e1gos\u201d \u201ec\u00edm [: port]\u201d form\u00e1tum\u00fanak kell lennie. P\u00e9lda: '192.168.1.1'. A port opcion\u00e1lis, \u00e9s alap\u00e9rtelmez\u00e9s szerint 2101 \u201enem biztons\u00e1gos\u201d \u00e9s 2601 \u201ebiztons\u00e1gos\u201d. A soros protokollhoz a c\u00edmnek 'tty [: baud]' form\u00e1tum\u00fanak kell lennie. P\u00e9lda: '/dev/ttyS1'. A baud opcion\u00e1lis, \u00e9s alap\u00e9rtelmez\u00e9s szerint 115200.", + "description": "V\u00e1lasszon egy felfedezett rendszert vagy a \u201eK\u00e9zi bevitelt\u201d, ha nem \u00e9szlelt eszk\u00f6zt.", "title": "Csatlakoz\u00e1s az Elk-M1 vez\u00e9rl\u0151h\u00f6z" } } diff --git a/homeassistant/components/elkm1/translations/nl.json b/homeassistant/components/elkm1/translations/nl.json index de51e67b2062c9..c3efd45dbc0693 100644 --- a/homeassistant/components/elkm1/translations/nl.json +++ b/homeassistant/components/elkm1/translations/nl.json @@ -2,15 +2,28 @@ "config": { "abort": { "address_already_configured": "Een ElkM1 met dit adres is al geconfigureerd", - "already_configured": "Een ElkM1 met dit voorvoegsel is al geconfigureerd" + "already_configured": "Een ElkM1 met dit voorvoegsel is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", + "cannot_connect": "Kan geen verbinding maken" }, "error": { "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, + "flow_title": "{mac_address} ({host})", "step": { - "user": { + "discovered_connection": { + "data": { + "password": "Wachtwoord", + "protocol": "Protocol", + "temperature_unit": "De temperatuureenheid die ElkM1 gebruikt.", + "username": "Gebruikersnaam" + }, + "description": "Maak verbinding met het ontdekte systeem: {mac_address} ({host})", + "title": "Maak verbinding met Elk-M1 Control" + }, + "manual_connection": { "data": { "address": "Het IP-adres of domein of seri\u00eble poort bij verbinding via serieel.", "password": "Wachtwoord", @@ -21,6 +34,19 @@ }, "description": "De adresreeks moet de vorm 'adres [: poort]' hebben voor 'veilig' en 'niet-beveiligd'. Voorbeeld: '192.168.1.1'. De poort is optioneel en is standaard 2101 voor 'niet beveiligd' en 2601 voor 'beveiligd'. Voor het seri\u00eble protocol moet het adres de vorm 'tty [: baud]' hebben. Voorbeeld: '/ dev / ttyS1'. De baud is optioneel en is standaard ingesteld op 115200.", "title": "Maak verbinding met Elk-M1 Control" + }, + "user": { + "data": { + "address": "Het IP-adres of domein of seri\u00eble poort bij verbinding via serieel.", + "device": "Apparaat", + "password": "Wachtwoord", + "prefix": "Een uniek voorvoegsel (laat dit leeg als u maar \u00e9\u00e9n ElkM1 heeft).", + "protocol": "Protocol", + "temperature_unit": "De temperatuureenheid die ElkM1 gebruikt.", + "username": "Gebruikersnaam" + }, + "description": "Kies een ontdekt systeem of 'Handmatige invoer' als er geen apparaten zijn ontdekt.", + "title": "Maak verbinding met Elk-M1 Control" } } } diff --git a/homeassistant/components/fivem/translations/he.json b/homeassistant/components/fivem/translations/he.json new file mode 100644 index 00000000000000..3784dae202df97 --- /dev/null +++ b/homeassistant/components/fivem/translations/he.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u05e9\u05d9\u05e8\u05d5\u05ea \u05d6\u05d4 \u05db\u05d1\u05e8 \u05de\u05d5\u05d2\u05d3\u05e8" + }, + "error": { + "unknown_error": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" + }, + "step": { + "user": { + "data": { + "host": "\u05de\u05d0\u05e8\u05d7", + "name": "\u05e9\u05dd", + "port": "\u05e4\u05ea\u05d7\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/hu.json b/homeassistant/components/fivem/translations/hu.json new file mode 100644 index 00000000000000..b307c6e79fcf3d --- /dev/null +++ b/homeassistant/components/fivem/translations/hu.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Nem siker\u00fclt csatlakozni. K\u00e9rj\u00fck, ellen\u0151rizze a c\u00edmet \u00e9s a portot, \u00e9s pr\u00f3b\u00e1lja meg \u00fajra. Gy\u0151z\u0151dj\u00f6n meg arr\u00f3l is, hogy a leg\u00fajabb FiveM szervert futtatja.", + "invalid_game_name": "A j\u00e1t\u00e9k API-ja, amelyhez csatlakozni pr\u00f3b\u00e1l, nem FiveM.", + "invalid_gamename": "A j\u00e1t\u00e9k API-ja, amelyhez csatlakozni pr\u00f3b\u00e1l, nem FiveM.", + "unknown_error": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "C\u00edm", + "name": "N\u00e9v", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/ja.json b/homeassistant/components/fivem/translations/ja.json index 7b2e79583085b3..939d07c27aae6e 100644 --- a/homeassistant/components/fivem/translations/ja.json +++ b/homeassistant/components/fivem/translations/ja.json @@ -4,6 +4,7 @@ "already_configured": "FiveM\u30b5\u30fc\u30d0\u30fc\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" }, "error": { + "invalid_game_name": "\u63a5\u7d9a\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u308b\u30b2\u30fc\u30e0\u306eAPI\u306f\u3001FiveM\u306e\u30b2\u30fc\u30e0\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002", "invalid_gamename": "\u63a5\u7d9a\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u308b\u30b2\u30fc\u30e0\u306eAPI\u306f\u3001FiveM\u306e\u30b2\u30fc\u30e0\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002", "unknown_error": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, diff --git a/homeassistant/components/fivem/translations/nl.json b/homeassistant/components/fivem/translations/nl.json new file mode 100644 index 00000000000000..599bcbc771e78f --- /dev/null +++ b/homeassistant/components/fivem/translations/nl.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Service is al geconfigureerd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken. Controleer de host en poort en probeer het opnieuw. Zorg er ook voor dat u de nieuwste FiveM-server gebruikt.", + "invalid_game_name": "De api van het spel waarmee je probeert te verbinden is geen FiveM spel.", + "invalid_gamename": "De api van het spel waarmee je probeert te verbinden is geen FiveM spel.", + "unknown_error": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Naam", + "port": "Poort" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/he.json b/homeassistant/components/homekit/translations/he.json index cadbd1aa4b899b..22f3515b49707f 100644 --- a/homeassistant/components/homekit/translations/he.json +++ b/homeassistant/components/homekit/translations/he.json @@ -18,6 +18,16 @@ "cameras": { "title": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05de\u05e6\u05dc\u05de\u05d4" }, + "exclude": { + "data": { + "entities": "\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea" + } + }, + "include": { + "data": { + "entities": "\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea" + } + }, "include_exclude": { "data": { "mode": "\u05de\u05e6\u05d1" diff --git a/homeassistant/components/homekit_controller/translations/select.he.json b/homeassistant/components/homekit_controller/translations/select.he.json new file mode 100644 index 00000000000000..4985fb4770bb70 --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/select.he.json @@ -0,0 +1,9 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "away": "\u05d1\u05d7\u05d5\u05e5", + "home": "\u05d1\u05d1\u05d9\u05ea", + "sleep": "\u05e9\u05d9\u05e0\u05d4" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homewizard/translations/he.json b/homeassistant/components/homewizard/translations/he.json index 1ca3be234e50ea..9355e0d6b807ee 100644 --- a/homeassistant/components/homewizard/translations/he.json +++ b/homeassistant/components/homewizard/translations/he.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4", + "device_not_supported": "\u05d4\u05ea\u05e7\u05df \u05d6\u05d4 \u05d0\u05d9\u05e0\u05d5 \u05e0\u05ea\u05de\u05da", "unknown_error": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" }, "step": { diff --git a/homeassistant/components/input_boolean/translations/ja.json b/homeassistant/components/input_boolean/translations/ja.json index 5af782e4e46639..10f2d6d70fadaa 100644 --- a/homeassistant/components/input_boolean/translations/ja.json +++ b/homeassistant/components/input_boolean/translations/ja.json @@ -5,5 +5,5 @@ "on": "\u30aa\u30f3" } }, - "title": "\u771f\u507d\u5024\u5165\u529b(booleans)" + "title": "\u771f\u507d\u5024\u5165\u529b(Booleans)" } \ No newline at end of file diff --git a/homeassistant/components/input_datetime/translations/ja.json b/homeassistant/components/input_datetime/translations/ja.json index aef276095683c9..432f4405c03d11 100644 --- a/homeassistant/components/input_datetime/translations/ja.json +++ b/homeassistant/components/input_datetime/translations/ja.json @@ -1,3 +1,3 @@ { - "title": "\u65e5\u6642\u3092\u5165\u529b" + "title": "\u65e5\u6642\u5165\u529b" } \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/he.json b/homeassistant/components/moehlenhoff_alpha2/translations/he.json new file mode 100644 index 00000000000000..1699e0f8e1983b --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/translations/he.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4" + }, + "error": { + "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", + "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" + }, + "step": { + "user": { + "data": { + "host": "\u05de\u05d0\u05e8\u05d7" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/hu.json b/homeassistant/components/moehlenhoff_alpha2/translations/hu.json new file mode 100644 index 00000000000000..dfe114ef06869a --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/translations/hu.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "C\u00edm" + } + } + } + }, + "title": "M\u00f6hlenhoff Alpha2" +} \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/id.json b/homeassistant/components/moehlenhoff_alpha2/translations/id.json new file mode 100644 index 00000000000000..1a0e5f47ccfa2c --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/translations/id.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "host": "Host" + } + } + } + }, + "title": "M\u00f6hlenhoff Alpha2" +} \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/nl.json b/homeassistant/components/moehlenhoff_alpha2/translations/nl.json new file mode 100644 index 00000000000000..0ac34b3153fb43 --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/translations/nl.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "host": "Host" + } + } + } + }, + "title": "M\u00f6hlenhoff Alpha2" +} \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/zh-Hant.json b/homeassistant/components/moehlenhoff_alpha2/translations/zh-Hant.json new file mode 100644 index 00000000000000..2105be9fc89308 --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/translations/zh-Hant.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef" + } + } + } + }, + "title": "M\u00f6hlenhoff Alpha2" +} \ No newline at end of file diff --git a/homeassistant/components/netgear/translations/hu.json b/homeassistant/components/netgear/translations/hu.json index 64452c9ef58d28..cf8d31ec3891de 100644 --- a/homeassistant/components/netgear/translations/hu.json +++ b/homeassistant/components/netgear/translations/hu.json @@ -15,7 +15,7 @@ "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", "username": "Felhaszn\u00e1l\u00f3n\u00e9v (nem k\u00f6telez\u0151)" }, - "description": "Alap\u00e9rtelmezett c\u00edm: {host}\nAlap\u00e9rtelmezett port: {port}\nAlap\u00e9rtelmezett felhaszn\u00e1l\u00f3n\u00e9v: {username}", + "description": "Alap\u00e9rtelmezett c\u00edm: {host}\nAlap\u00e9rtelmezett felhaszn\u00e1l\u00f3n\u00e9v: {username}", "title": "Netgear" } } diff --git a/homeassistant/components/netgear/translations/nl.json b/homeassistant/components/netgear/translations/nl.json index 22ac348af4e738..7333a0cd0eedd0 100644 --- a/homeassistant/components/netgear/translations/nl.json +++ b/homeassistant/components/netgear/translations/nl.json @@ -15,7 +15,7 @@ "ssl": "Gebruikt een SSL certificaat", "username": "Gebruikersnaam (optioneel)" }, - "description": "Standaard host: {host}\nStandaard poort: {port}\nStandaard gebruikersnaam: {username}", + "description": "Standaardhost: {host}\n Standaard gebruikersnaam: {username}", "title": "Netgear" } } diff --git a/homeassistant/components/overkiz/translations/hu.json b/homeassistant/components/overkiz/translations/hu.json index e824d89da07353..ef7cacaaf96ef7 100644 --- a/homeassistant/components/overkiz/translations/hu.json +++ b/homeassistant/components/overkiz/translations/hu.json @@ -12,6 +12,7 @@ "too_many_requests": "T\u00fal sok a k\u00e9r\u00e9s, pr\u00f3b\u00e1lja meg k\u00e9s\u0151bb \u00fajra.", "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, + "flow_title": "\u00c1tj\u00e1r\u00f3: {gateway_id}", "step": { "user": { "data": { diff --git a/homeassistant/components/overkiz/translations/ja.json b/homeassistant/components/overkiz/translations/ja.json index bbf2b15d13e0fd..e978a8f36f2a76 100644 --- a/homeassistant/components/overkiz/translations/ja.json +++ b/homeassistant/components/overkiz/translations/ja.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "\u30a2\u30ab\u30a6\u30f3\u30c8\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", - "reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f" + "reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f", + "reauth_wrong_account": "\u3053\u306e\u30a8\u30f3\u30c8\u30ea\u306f\u3001\u540c\u3058Overkiz account\u3068hub\u3067\u306e\u307f\u518d\u8a8d\u8a3c\u3067\u304d\u307e\u3059" }, "error": { "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", diff --git a/homeassistant/components/overkiz/translations/nl.json b/homeassistant/components/overkiz/translations/nl.json index e76f534350baec..d33dd5bb44cad7 100644 --- a/homeassistant/components/overkiz/translations/nl.json +++ b/homeassistant/components/overkiz/translations/nl.json @@ -12,6 +12,7 @@ "too_many_requests": "Te veel verzoeken, probeer het later opnieuw.", "unknown": "Onverwachte fout" }, + "flow_title": "Gateway: {gateway_id}", "step": { "user": { "data": { diff --git a/homeassistant/components/picnic/translations/ca.json b/homeassistant/components/picnic/translations/ca.json index c81d180aef0060..292ae6a8539e35 100644 --- a/homeassistant/components/picnic/translations/ca.json +++ b/homeassistant/components/picnic/translations/ca.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "El dispositiu ja est\u00e0 configurat" + "already_configured": "El dispositiu ja est\u00e0 configurat", + "reauth_successful": "La re-autenticaci\u00f3 ha estat satisfact\u00f2ria" }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3", + "different_account": "El compte ha de ser el mateix que s'utilitza per configurar la integraci\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "unknown": "Error inesperat" }, diff --git a/homeassistant/components/picnic/translations/de.json b/homeassistant/components/picnic/translations/de.json index 1a11e00664cef5..65b10f61df3aa6 100644 --- a/homeassistant/components/picnic/translations/de.json +++ b/homeassistant/components/picnic/translations/de.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Ger\u00e4t ist bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", + "different_account": "Das Konto sollte dasselbe sein, das f\u00fcr die Einrichtung der Integration verwendet wurde.", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/picnic/translations/en.json b/homeassistant/components/picnic/translations/en.json index 13b62c78757a91..06b3018f88e78c 100644 --- a/homeassistant/components/picnic/translations/en.json +++ b/homeassistant/components/picnic/translations/en.json @@ -19,5 +19,6 @@ } } } - } + }, + "title": "Picnic" } \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/et.json b/homeassistant/components/picnic/translations/et.json index 11fc0f1fe881ed..41f5018079c9d7 100644 --- a/homeassistant/components/picnic/translations/et.json +++ b/homeassistant/components/picnic/translations/et.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "reauth_successful": "Taastuvastamine \u00f5nnestus" }, "error": { "cannot_connect": "T\u00f5rge \u00fchendamisel", + "different_account": "Konto peab olema sama mida kasutati sidumise seadistamisel", "invalid_auth": "Tuvastamine nurjus", "unknown": "Tundmatu t\u00f5rge" }, diff --git a/homeassistant/components/picnic/translations/id.json b/homeassistant/components/picnic/translations/id.json index 819125c690907c..db97b991f6f6d1 100644 --- a/homeassistant/components/picnic/translations/id.json +++ b/homeassistant/components/picnic/translations/id.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Perangkat sudah dikonfigurasi" + "already_configured": "Perangkat sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil" }, "error": { "cannot_connect": "Gagal terhubung", + "different_account": "Akun harus sama dengan yang digunakan untuk menyiapkan integrasi", "invalid_auth": "Autentikasi tidak valid", "unknown": "Kesalahan yang tidak diharapkan" }, diff --git a/homeassistant/components/picnic/translations/ja.json b/homeassistant/components/picnic/translations/ja.json index 5379949aa96714..194cffd7e6a3b8 100644 --- a/homeassistant/components/picnic/translations/ja.json +++ b/homeassistant/components/picnic/translations/ja.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" + "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", + "reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f" }, "error": { "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "different_account": "\u30a2\u30ab\u30a6\u30f3\u30c8\u306f\u3001\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u8a2d\u5b9a\u3067\u4f7f\u7528\u3057\u305f\u3082\u306e\u3068\u540c\u3058\u3067\u3042\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059", "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, diff --git a/homeassistant/components/picnic/translations/pt-BR.json b/homeassistant/components/picnic/translations/pt-BR.json index c11bc3fa96531f..b864d13923d465 100644 --- a/homeassistant/components/picnic/translations/pt-BR.json +++ b/homeassistant/components/picnic/translations/pt-BR.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { "cannot_connect": "Falha ao conectar", + "different_account": "A conta deve ser a mesma usada para configurar a integra\u00e7\u00e3o", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, diff --git a/homeassistant/components/picnic/translations/ru.json b/homeassistant/components/picnic/translations/ru.json index e754faf8a0e963..9d7a7fbdb23c06 100644 --- a/homeassistant/components/picnic/translations/ru.json +++ b/homeassistant/components/picnic/translations/ru.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant." + "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.", + "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." }, "error": { "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "different_account": "\u0423\u0447\u0435\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0434\u043e\u043b\u0436\u043d\u0430 \u0431\u044b\u0442\u044c \u0442\u0430\u043a\u043e\u0439 \u0436\u0435, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0430\u0441\u044c \u0434\u043b\u044f \u043f\u0435\u0440\u0432\u043e\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0439 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438.", "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, diff --git a/homeassistant/components/powerwall/translations/he.json b/homeassistant/components/powerwall/translations/he.json index f090e85c0cfa33..c4a69c402c48a6 100644 --- a/homeassistant/components/powerwall/translations/he.json +++ b/homeassistant/components/powerwall/translations/he.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4", + "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", "reauth_successful": "\u05d4\u05d0\u05d9\u05de\u05d5\u05ea \u05de\u05d7\u05d3\u05e9 \u05d4\u05e6\u05dc\u05d9\u05d7" }, "error": { @@ -11,6 +12,12 @@ }, "flow_title": "{ip_address}", "step": { + "reauth_confim": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + }, + "description": "\u05d4\u05e1\u05d9\u05e1\u05de\u05d4 \u05d4\u05d9\u05d0 \u05d1\u05d3\u05e8\u05da \u05db\u05dc\u05dc 5 \u05d4\u05ea\u05d5\u05d5\u05d9\u05dd \u05d4\u05d0\u05d7\u05e8\u05d5\u05e0\u05d9\u05dd \u05e9\u05dc \u05d4\u05de\u05e1\u05e4\u05e8 \u05d4\u05e1\u05d9\u05d3\u05d5\u05e8\u05d9 \u05e2\u05d1\u05d5\u05e8 Backup Gateway \u05d5\u05e0\u05d9\u05ea\u05df \u05dc\u05de\u05e6\u05d5\u05d0 \u05d0\u05d5\u05ea\u05d4 \u05d1\u05d9\u05d9\u05e9\u05d5\u05dd \u05d8\u05e1\u05dc\u05d4 \u05d0\u05d5 \u05d1-5 \u05d4\u05ea\u05d5\u05d5\u05d9\u05dd \u05d4\u05d0\u05d7\u05e8\u05d5\u05e0\u05d9\u05dd \u05e9\u05dc \u05d4\u05e1\u05d9\u05e1\u05de\u05d4 \u05e9\u05e0\u05de\u05e6\u05d0\u05d4 \u05d1\u05ea\u05d5\u05da \u05d4\u05d3\u05dc\u05ea \u05e2\u05d1\u05d5\u05e8 Backup Gateway 2." + }, "user": { "data": { "ip_address": "\u05db\u05ea\u05d5\u05d1\u05ea IP", diff --git a/homeassistant/components/powerwall/translations/nl.json b/homeassistant/components/powerwall/translations/nl.json index a59a3efc089af0..007d4e9e86b9fe 100644 --- a/homeassistant/components/powerwall/translations/nl.json +++ b/homeassistant/components/powerwall/translations/nl.json @@ -11,7 +11,7 @@ "unknown": "Onverwachte fout", "wrong_version": "Uw powerwall gebruikt een softwareversie die niet wordt ondersteund. Overweeg om dit probleem te upgraden of te melden, zodat het kan worden opgelost." }, - "flow_title": "({ip_adres})", + "flow_title": "{name} ({ip_address})", "step": { "confirm_discovery": { "description": "Wilt u {name} ({ip_address}) instellen?", diff --git a/homeassistant/components/rtsp_to_webrtc/translations/ja.json b/homeassistant/components/rtsp_to_webrtc/translations/ja.json index 256ae5aa6a85ee..6359d66f50a3b6 100644 --- a/homeassistant/components/rtsp_to_webrtc/translations/ja.json +++ b/homeassistant/components/rtsp_to_webrtc/translations/ja.json @@ -1,6 +1,8 @@ { "config": { "abort": { + "server_failure": "RTSPtoWebRTC\u30b5\u30fc\u30d0\u30fc\u304c\u30a8\u30e9\u30fc\u3092\u8fd4\u3057\u307e\u3057\u305f\u3002\u8a73\u7d30\u306b\u3064\u3044\u3066\u306f\u3001\u30ed\u30b0\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002", + "server_unreachable": "RTSPtoWebRTC\u30b5\u30fc\u30d0\u30fc\u3068\u306e\u901a\u4fe1\u304c\u3067\u304d\u307e\u305b\u3093\u3002\u8a73\u7d30\u306b\u3064\u3044\u3066\u306f\u3001\u30ed\u30b0\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002", "single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002" }, "error": { diff --git a/homeassistant/components/steamist/translations/he.json b/homeassistant/components/steamist/translations/he.json index ec05565b9a6e97..7b8528476e1edf 100644 --- a/homeassistant/components/steamist/translations/he.json +++ b/homeassistant/components/steamist/translations/he.json @@ -12,6 +12,11 @@ }, "flow_title": "{name} ({ipaddress})", "step": { + "pick_device": { + "data": { + "device": "\u05d4\u05ea\u05e7\u05df" + } + }, "user": { "data": { "host": "\u05de\u05d0\u05e8\u05d7" diff --git a/homeassistant/components/tuya/translations/select.he.json b/homeassistant/components/tuya/translations/select.he.json index 2e792646cecaf9..eacaecacb07ae0 100644 --- a/homeassistant/components/tuya/translations/select.he.json +++ b/homeassistant/components/tuya/translations/select.he.json @@ -8,6 +8,23 @@ "1": "\u05db\u05d1\u05d5\u05d9", "2": "\u05de\u05d5\u05e4\u05e2\u05dc" }, + "tuya__countdown": { + "1h": "\u05e9\u05e2\u05d4", + "2h": "\u05e9\u05e2\u05ea\u05d9\u05d9\u05dd", + "3h": "3 \u05e9\u05e2\u05d5\u05ea", + "4h": "4 \u05e9\u05e2\u05d5\u05ea", + "5h": "5 \u05e9\u05e2\u05d5\u05ea", + "6h": "6 \u05e9\u05e2\u05d5\u05ea", + "cancel": "\u05d1\u05d9\u05d8\u05d5\u05dc" + }, + "tuya__curtain_mode": { + "morning": "\u05d1\u05d5\u05e7\u05e8", + "night": "\u05dc\u05d9\u05dc\u05d4" + }, + "tuya__curtain_motor_mode": { + "back": "\u05d7\u05d6\u05d5\u05e8", + "forward": "\u05e7\u05d3\u05d9\u05de\u05d4" + }, "tuya__fan_angle": { "30": "30\u00b0", "60": "60\u00b0", @@ -17,6 +34,32 @@ "click": "\u05d3\u05d7\u05d9\u05e4\u05d4", "switch": "\u05de\u05ea\u05d2" }, + "tuya__humidifier_level": { + "level_1": "\u05e8\u05de\u05d4 1", + "level_10": "\u05e8\u05de\u05d4 10", + "level_2": "\u05e8\u05de\u05d4 2", + "level_3": "\u05e8\u05de\u05d4 3", + "level_4": "\u05e8\u05de\u05d4 4", + "level_5": "\u05e8\u05de\u05d4 5", + "level_6": "\u05e8\u05de\u05d4 6", + "level_7": "\u05e8\u05de\u05d4 7", + "level_8": "\u05e8\u05de\u05d4 8", + "level_9": "\u05e8\u05de\u05d4 9" + }, + "tuya__humidifier_moodlighting": { + "1": "\u05de\u05e6\u05d1 \u05e8\u05d5\u05d7 1", + "2": "\u05de\u05e6\u05d1 \u05e8\u05d5\u05d7 2", + "3": "\u05de\u05e6\u05d1 \u05e8\u05d5\u05d7 3", + "4": "\u05de\u05e6\u05d1 \u05e8\u05d5\u05d7 4", + "5": "\u05de\u05e6\u05d1 \u05e8\u05d5\u05d7 5" + }, + "tuya__humidifier_spray_mode": { + "auto": "\u05d0\u05d5\u05d8\u05d5\u05de\u05d8\u05d9", + "health": "\u05d1\u05e8\u05d9\u05d0\u05d5\u05ea", + "humidity": "\u05dc\u05d7\u05d5\u05ea", + "sleep": "\u05e9\u05d9\u05e0\u05d4", + "work": "\u05e2\u05d1\u05d5\u05d3\u05d4" + }, "tuya__led_type": { "led": "\u05dc\u05d3" }, diff --git a/homeassistant/components/tuya/translations/select.hu.json b/homeassistant/components/tuya/translations/select.hu.json index 613ce8cedd28c4..23a1d29adebde0 100644 --- a/homeassistant/components/tuya/translations/select.hu.json +++ b/homeassistant/components/tuya/translations/select.hu.json @@ -10,6 +10,15 @@ "1": "Ki", "2": "Be" }, + "tuya__countdown": { + "1h": "1 \u00f3ra", + "2h": "2 \u00f3ra", + "3h": "3 \u00f3ra", + "4h": "4 \u00f3ra", + "5h": "5 \u00f3ra", + "6h": "6 \u00f3ra", + "cancel": "M\u00e9gse" + }, "tuya__curtain_mode": { "morning": "Reggel", "night": "\u00c9jszaka" @@ -31,6 +40,32 @@ "click": "Lenyom\u00e1s", "switch": "Kapcsol\u00e1s" }, + "tuya__humidifier_level": { + "level_1": "1. szint", + "level_10": "10. szint", + "level_2": "2. szint", + "level_3": "3. szint", + "level_4": "4. szint", + "level_5": "5. szint", + "level_6": "6. szint", + "level_7": "7. szint", + "level_8": "8. szint", + "level_9": "9. szint" + }, + "tuya__humidifier_moodlighting": { + "1": "1. hangulat", + "2": "2. hangulat", + "3": "3. hangulat", + "4": "4. hangulat", + "5": "5. hangulat" + }, + "tuya__humidifier_spray_mode": { + "auto": "Automatikus", + "health": "Eg\u00e9szs\u00e9g", + "humidity": "P\u00e1ratartalom", + "sleep": "Alv\u00e1s", + "work": "Munka" + }, "tuya__ipc_work_mode": { "0": "Alacsony fogyaszt\u00e1s\u00fa m\u00f3d", "1": "Folyamatos \u00fczemm\u00f3d" diff --git a/homeassistant/components/tuya/translations/select.nl.json b/homeassistant/components/tuya/translations/select.nl.json index 979a8d50ee1479..e645694ae55de0 100644 --- a/homeassistant/components/tuya/translations/select.nl.json +++ b/homeassistant/components/tuya/translations/select.nl.json @@ -10,6 +10,15 @@ "1": "Uit", "2": "Aan" }, + "tuya__countdown": { + "1h": "1 uur", + "2h": "2 uur", + "3h": "3 uur", + "4h": "4 uur", + "5h": "5 uur", + "6h": "6 uur", + "cancel": "Annuleren" + }, "tuya__curtain_mode": { "morning": "Ochtend", "night": "Nacht" @@ -31,6 +40,32 @@ "click": "Duw", "switch": "Schakelaar" }, + "tuya__humidifier_level": { + "level_1": "Niveau 1", + "level_10": "Niveau 10", + "level_2": "Niveau 2", + "level_3": "Niveau 3", + "level_4": "Niveau 4", + "level_5": "Niveau 5", + "level_6": "Niveau 6", + "level_7": "Niveau 7", + "level_8": "Niveau 8", + "level_9": "Niveau 9" + }, + "tuya__humidifier_moodlighting": { + "1": "Stemming 1", + "2": "Stemming 2", + "3": "Stemming 3", + "4": "Stemming 4", + "5": "Stemming 5" + }, + "tuya__humidifier_spray_mode": { + "auto": "Auto", + "health": "Gezondheid", + "humidity": "Vochtigheid", + "sleep": "Slapen", + "work": "Werk" + }, "tuya__ipc_work_mode": { "0": "Energiezuinige modus", "1": "Continue werkmodus:" diff --git a/homeassistant/components/tuya/translations/sensor.he.json b/homeassistant/components/tuya/translations/sensor.he.json new file mode 100644 index 00000000000000..73e5af2fc79370 --- /dev/null +++ b/homeassistant/components/tuya/translations/sensor.he.json @@ -0,0 +1,8 @@ +{ + "state": { + "tuya__air_quality": { + "good": "\u05d8\u05d5\u05d1", + "great": "\u05e0\u05d4\u05d3\u05e8" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/sensor.hu.json b/homeassistant/components/tuya/translations/sensor.hu.json index 91cd93d627e6c4..743c7ffc97e70f 100644 --- a/homeassistant/components/tuya/translations/sensor.hu.json +++ b/homeassistant/components/tuya/translations/sensor.hu.json @@ -1,5 +1,11 @@ { "state": { + "tuya__air_quality": { + "good": "J\u00f3", + "great": "Nagyszer\u0171", + "mild": "Enyhe", + "severe": "S\u00falyos" + }, "tuya__status": { "boiling_temp": "Forral\u00e1si h\u0151m\u00e9rs\u00e9klet", "cooling": "H\u0171t\u00e9s", diff --git a/homeassistant/components/tuya/translations/sensor.nl.json b/homeassistant/components/tuya/translations/sensor.nl.json index 68092c434a3f82..256989b83dcdf9 100644 --- a/homeassistant/components/tuya/translations/sensor.nl.json +++ b/homeassistant/components/tuya/translations/sensor.nl.json @@ -1,5 +1,11 @@ { "state": { + "tuya__air_quality": { + "good": "Goed", + "great": "Geweldig", + "mild": "Mild", + "severe": "Ernstig" + }, "tuya__status": { "boiling_temp": "Kooktemperatuur", "cooling": "Koeling", diff --git a/homeassistant/components/vizio/translations/ja.json b/homeassistant/components/vizio/translations/ja.json index b40d9ef8680c68..ed2e934cbec941 100644 --- a/homeassistant/components/vizio/translations/ja.json +++ b/homeassistant/components/vizio/translations/ja.json @@ -3,7 +3,7 @@ "abort": { "already_configured_device": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", - "updated_entry": "\u3053\u306e\u30a8\u30f3\u30c8\u30ea\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u304c\u3001\u8a2d\u5b9a\u3067\u5b9a\u7fa9\u3055\u308c\u305f\u540d\u524d\u3001\u30a2\u30d7\u30ea\u3001\u30aa\u30d7\u30b7\u30e7\u30f3\u304c\u4ee5\u524d\u306b\u30a4\u30f3\u30dd\u30fc\u30c8\u3055\u308c\u305f\u8a2d\u5b9a\u3068\u4e00\u81f4\u3057\u306a\u304b\u3063\u305f\u305f\u3081\u3001\u8a2d\u5b9a\u30a8\u30f3\u30c8\u30ea\u306f\u305d\u308c\u306b\u5fdc\u3058\u3066\u66f4\u65b0\u3055\u308c\u3066\u3044\u307e\u3059\u3002" + "updated_entry": "\u3053\u306e\u30a8\u30f3\u30c8\u30ea\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u304c\u3001\u8a2d\u5b9a\u3067\u5b9a\u7fa9\u3055\u308c\u305f\u540d\u524d\u3001\u30a2\u30d7\u30ea\u3001\u30aa\u30d7\u30b7\u30e7\u30f3\u304c\u4ee5\u524d\u306b\u30a4\u30f3\u30dd\u30fc\u30c8\u3055\u308c\u305f\u8a2d\u5b9a\u3068\u4e00\u81f4\u3057\u306a\u304b\u3063\u305f\u305f\u3081\u3001\u8a2d\u5b9a\u30a8\u30f3\u30c8\u30ea\u306f\u3059\u3067\u306b\u305d\u308c\u306b\u5fdc\u3058\u3066\u66f4\u65b0\u3055\u308c\u3066\u3044\u307e\u3059\u3002" }, "error": { "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", diff --git a/homeassistant/components/wiz/translations/he.json b/homeassistant/components/wiz/translations/he.json new file mode 100644 index 00000000000000..dd091bd10eb0bd --- /dev/null +++ b/homeassistant/components/wiz/translations/he.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4", + "no_devices_found": "\u05dc\u05d0 \u05e0\u05de\u05e6\u05d0\u05d5 \u05de\u05db\u05e9\u05d9\u05e8\u05d9\u05dd \u05d1\u05e8\u05e9\u05ea" + }, + "error": { + "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", + "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" + }, + "flow_title": "{name} ({host})", + "step": { + "confirm": { + "description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05ea\u05d7\u05d9\u05dc \u05d1\u05d4\u05d2\u05d3\u05e8\u05d4?" + }, + "discovery_confirm": { + "description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05d2\u05d3\u05d9\u05e8 \u05d0\u05ea {name} ({host})?" + }, + "pick_device": { + "data": { + "device": "\u05d4\u05ea\u05e7\u05df" + } + }, + "user": { + "data": { + "host": "\u05db\u05ea\u05d5\u05d1\u05ea IP", + "name": "\u05e9\u05dd" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/hu.json b/homeassistant/components/wiz/translations/hu.json new file mode 100644 index 00000000000000..d26658e666ac84 --- /dev/null +++ b/homeassistant/components/wiz/translations/hu.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton" + }, + "error": { + "bulb_time_out": "Nem lehet csatlakoztatni az izz\u00f3hoz. Lehet, hogy az izz\u00f3 offline \u00e1llapotban van, vagy rossz IP-t adott meg. K\u00e9rj\u00fck, kapcsolja fel a l\u00e1mp\u00e1t, \u00e9s pr\u00f3b\u00e1lja \u00fajra!", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "no_ip": "\u00c9rv\u00e9nytelen IP-c\u00edm.", + "no_wiz_light": "Az izz\u00f3 nem csatlakoztathat\u00f3 a WiZ Platform integr\u00e1ci\u00f3n kereszt\u00fcl.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "flow_title": "{name} ({host})", + "step": { + "confirm": { + "description": "El szeretn\u00e9 kezdeni a be\u00e1ll\u00edt\u00e1st?" + }, + "discovery_confirm": { + "description": "Szeretn\u00e9 be\u00e1ll\u00edtani: {name} ({host})?" + }, + "pick_device": { + "data": { + "device": "Eszk\u00f6z" + } + }, + "user": { + "data": { + "host": "IP c\u00edm", + "name": "N\u00e9v" + }, + "description": "Ha az IP-c\u00edmet \u00fcresen hagyja, akkor az eszk\u00f6z\u00f6k keres\u00e9se a felder\u00edt\u00e9ssel t\u00f6rt\u00e9nik." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/id.json b/homeassistant/components/wiz/translations/id.json index e99e8e7e14cb1b..d4b1fc92ed8d2d 100644 --- a/homeassistant/components/wiz/translations/id.json +++ b/homeassistant/components/wiz/translations/id.json @@ -5,7 +5,7 @@ "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan" }, "error": { - "bulb_time_out": "Tidak dapat terhubung ke bohlam. Mungkin bohlam offline atau IP/host yang salah dimasukkan. Nyalakan lampu dan coba lagi!", + "bulb_time_out": "Tidak dapat terhubung ke bohlam. Mungkin bohlam offline atau IP yang salah dimasukkan. Nyalakan lampu dan coba lagi!", "cannot_connect": "Gagal terhubung", "no_ip": "Bukan alamat IP yang valid.", "no_wiz_light": "Bohlam tidak dapat dihubungkan melalui integrasi Platform WiZ.", @@ -26,10 +26,10 @@ }, "user": { "data": { - "host": "Host", + "host": "Alamat IP", "name": "Nama" }, - "description": "Jika host dibiarkan kosong, proses penemuan akan digunakan untuk menemukan perangkat." + "description": "Jika Alamat IP dibiarkan kosong, proses penemuan akan digunakan untuk menemukan perangkat." } } } diff --git a/homeassistant/components/wiz/translations/nl.json b/homeassistant/components/wiz/translations/nl.json index c0108fc32c1326..97ffaecd0f2499 100644 --- a/homeassistant/components/wiz/translations/nl.json +++ b/homeassistant/components/wiz/translations/nl.json @@ -7,19 +7,29 @@ "error": { "bulb_time_out": "Kan geen verbinding maken met de lamp. Misschien is de lamp offline of is er een verkeerde IP/host ingevoerd. Doe het licht aan en probeer het opnieuw!", "cannot_connect": "Kan geen verbinding maken", + "no_ip": "Geen geldig IP-adres.", "no_wiz_light": "De lamp kan niet worden aangesloten via WiZ Platform integratie.", "unknown": "Onverwachte fout" }, + "flow_title": "{name} ({host})", "step": { "confirm": { "description": "Wilt u beginnen met instellen?" }, + "discovery_confirm": { + "description": "Wilt u {name} ({host}) instellen?" + }, + "pick_device": { + "data": { + "device": "Apparaat" + } + }, "user": { "data": { - "host": "Host", + "host": "IP-adres", "name": "Naam" }, - "description": "Voer een hostnaam of IP-adres en naam in om een nieuwe lamp toe te voegen:" + "description": "Als u het IP-adres leeg laat, zal discovery worden gebruikt om apparaten te vinden." } } } diff --git a/homeassistant/components/wiz/translations/zh-Hant.json b/homeassistant/components/wiz/translations/zh-Hant.json index fed43b2d96fcdc..ce08826710677d 100644 --- a/homeassistant/components/wiz/translations/zh-Hant.json +++ b/homeassistant/components/wiz/translations/zh-Hant.json @@ -5,8 +5,9 @@ "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e" }, "error": { - "bulb_time_out": "\u7121\u6cd5\u9023\u7dda\u81f3\u71c8\u6ce1\u3002\u53ef\u80fd\u539f\u56e0\u70ba\u71c8\u6ce1\u5df2\u96e2\u7dda\u6216\u6240\u8f38\u5165\u7684 IP/\u4e3b\u6a5f\u540d\u7a31\u932f\u8aa4\u3002\u8acb\u958b\u555f\u71c8\u6ce1\u4e26\u518d\u8a66\u4e00\u6b21\uff01", + "bulb_time_out": "\u7121\u6cd5\u9023\u7dda\u81f3\u71c8\u6ce1\u3002\u53ef\u80fd\u539f\u56e0\u70ba\u71c8\u6ce1\u5df2\u96e2\u7dda\u6216\u6240\u8f38\u5165\u7684 IP \u932f\u8aa4\u3002\u8acb\u958b\u555f\u71c8\u6ce1\u4e26\u518d\u8a66\u4e00\u6b21\uff01", "cannot_connect": "\u9023\u7dda\u5931\u6557", + "no_ip": "\u975e\u6709\u6548 IP \u4f4d\u5740\u3002", "no_wiz_light": "\u71c8\u6ce1\u7121\u6cd5\u900f\u904e WiZ \u5e73\u53f0\u6574\u5408\u9023\u63a5\u3002", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, @@ -25,10 +26,10 @@ }, "user": { "data": { - "host": "\u4e3b\u6a5f\u7aef", + "host": "IP \u4f4d\u5740", "name": "\u540d\u7a31" }, - "description": "\u5047\u5982\u4e3b\u6a5f\u7aef\u4f4d\u5740\u6b04\u4f4d\u70ba\u7a7a\u767d\uff0c\u5c07\u6703\u63a2\u7d22\u6240\u6709\u53ef\u7528\u88dd\u7f6e\u3002" + "description": "\u5047\u5982 IP \u4f4d\u5740\u6b04\u4f4d\u70ba\u7a7a\u767d\uff0c\u5c07\u6703\u63a2\u7d22\u6240\u6709\u53ef\u7528\u88dd\u7f6e\u3002" } } } diff --git a/homeassistant/components/wled/translations/ja.json b/homeassistant/components/wled/translations/ja.json index 0efbe56e3e0e20..5f30617d7eb43a 100644 --- a/homeassistant/components/wled/translations/ja.json +++ b/homeassistant/components/wled/translations/ja.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", - "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f" + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "cct_unsupported": "\u3053\u306eWLED\u30c7\u30d0\u30a4\u30b9\u306fCCT\u30c1\u30e3\u30f3\u30cd\u30eb\u3092\u4f7f\u7528\u3057\u3066\u3044\u307e\u3059\u304c\u3001\u3053\u306e\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3067\u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093" }, "error": { "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f" diff --git a/homeassistant/components/zwave_me/translations/he.json b/homeassistant/components/zwave_me/translations/he.json new file mode 100644 index 00000000000000..5689db627e2a36 --- /dev/null +++ b/homeassistant/components/zwave_me/translations/he.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4" + }, + "step": { + "user": { + "data": { + "token": "\u05d0\u05e1\u05d9\u05de\u05d5\u05df", + "url": "\u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave_me/translations/hu.json b/homeassistant/components/zwave_me/translations/hu.json new file mode 100644 index 00000000000000..679604d6cfa7d0 --- /dev/null +++ b/homeassistant/components/zwave_me/translations/hu.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "no_valid_uuid_set": "Nincs \u00e9rv\u00e9nyes UUID be\u00e1ll\u00edtva" + }, + "error": { + "no_valid_uuid_set": "Nincs \u00e9rv\u00e9nyes UUID be\u00e1ll\u00edtva" + }, + "step": { + "user": { + "data": { + "token": "Token", + "url": "URL" + }, + "description": "Adja meg a Z-Way szerver IP-c\u00edm\u00e9t \u00e9s a Z-Way hozz\u00e1f\u00e9r\u00e9si tokent. Az IP-c\u00edm el\u00e9 wss:// el\u0151tagot lehet tenni, ha HTTP helyett HTTPS-t kell haszn\u00e1lni. A token megszerz\u00e9s\u00e9hez l\u00e9pjen a Z-Way felhaszn\u00e1l\u00f3i fel\u00fcletre > Men\u00fc > Be\u00e1ll\u00edt\u00e1sok > Felhaszn\u00e1l\u00f3 > API token men\u00fcpontba. Javasoljuk, hogy hozzon l\u00e9tre egy \u00faj felhaszn\u00e1l\u00f3t a Home Assistanthoz, \u00e9s adjon hozz\u00e1f\u00e9r\u00e9st azoknak az eszk\u00f6z\u00f6knek, amelyeket a Home Assistantb\u00f3l kell vez\u00e9relnie. A t\u00e1voli Z-Way csatlakoztat\u00e1s\u00e1hoz a find.z-wave.me oldalon kereszt\u00fcl t\u00e1voli hozz\u00e1f\u00e9r\u00e9st is haszn\u00e1lhat. \u00cdrja be az IP mez\u0151be a wss://find.z-wave.me c\u00edmet, \u00e9s m\u00e1solja be a tokent glob\u00e1lis hat\u00f3k\u00f6rrel (ehhez jelentkezzen be a Z-Way-be a find.z-wave.me oldalon kereszt\u00fcl)." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave_me/translations/nl.json b/homeassistant/components/zwave_me/translations/nl.json new file mode 100644 index 00000000000000..a2356ed1035e01 --- /dev/null +++ b/homeassistant/components/zwave_me/translations/nl.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd", + "no_valid_uuid_set": "Geen geldige UUID ingesteld" + }, + "error": { + "no_valid_uuid_set": "Geen geldige UUID ingesteld" + }, + "step": { + "user": { + "data": { + "token": "Token", + "url": "URL" + }, + "description": "Voer het IP-adres van de Z-Way server en het Z-Way toegangstoken in. Het IP adres kan voorafgegaan worden door wss:// indien HTTPS gebruikt moet worden in plaats van HTTP. Om het token te verkrijgen gaat u naar de Z-Way gebruikersinterface > Menu > Instellingen > Gebruiker > API token. Het is aanbevolen om een nieuwe gebruiker voor Home Assistant aan te maken en toegang te verlenen aan apparaten die u wilt bedienen vanuit Home Assistant. Het is ook mogelijk om toegang op afstand te gebruiken via find.z-wave.me om een Z-Way op afstand te verbinden. Voer wss://find.z-wave.me in het IP veld in en kopieer het token met Global scope (log hiervoor in op Z-Way via find.z-wave.me)." + } + } + } +} \ No newline at end of file From 0a128d006f0c0de4bdfe2acda66beb8fa5731463 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sat, 12 Feb 2022 20:59:11 -0800 Subject: [PATCH 0588/1098] Improve stream robustness by always retrying worker (#66417) Improve stream robustness by always retrying in the worker on failure, rather than only when keepalive is enabled. This will make cloud cameras like nest more robust, since they have a tendency to be flaky. This is also needed to improve client side retry behavior because when the client attempts to retry, the stream token is already revoked because the worker stopped. The worker will still idle timeout if no streams are present, so it won't go on forever if no frontend is viewing the stream. --- homeassistant/components/stream/__init__.py | 7 ++++++- tests/components/stream/conftest.py | 12 +++++++++++- tests/components/stream/test_hls.py | 13 ++++++++----- tests/components/stream/test_worker.py | 4 ++-- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index be166d13455618..365ea946f51743 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -342,7 +342,7 @@ def _run_worker(self) -> None: self._logger.error("Error from stream worker: %s", str(err)) stream_state.discontinuity() - if not self.keepalive or self._thread_quit.is_set(): + if not _should_retry() or self._thread_quit.is_set(): if self._fast_restart_once: # The stream source is updated, restart without any delay. self._fast_restart_once = False @@ -446,3 +446,8 @@ async def async_get_image( return await self._keyframe_converter.async_get_image( width=width, height=height ) + + +def _should_retry() -> bool: + """Return true if worker failures should be retried, for disabling during tests.""" + return True diff --git a/tests/components/stream/conftest.py b/tests/components/stream/conftest.py index c754903965ab1a..407b144267b28d 100644 --- a/tests/components/stream/conftest.py +++ b/tests/components/stream/conftest.py @@ -16,7 +16,8 @@ from http import HTTPStatus import logging import threading -from unittest.mock import patch +from typing import Generator +from unittest.mock import Mock, patch from aiohttp import web import async_timeout @@ -219,6 +220,15 @@ def hls_sync(): yield sync +@pytest.fixture(autouse=True) +def should_retry() -> Generator[Mock, None, None]: + """Fixture to disable stream worker retries in tests by default.""" + with patch( + "homeassistant.components.stream._should_retry", return_value=False + ) as mock_should_retry: + yield mock_should_retry + + @pytest.fixture(scope="package") def h264_video(): """Generate a video, shared across tests.""" diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py index 0492fec14f0fbd..a2bb328826d46a 100644 --- a/tests/components/stream/test_hls.py +++ b/tests/components/stream/test_hls.py @@ -1,6 +1,7 @@ """The tests for hls streams.""" from datetime import timedelta from http import HTTPStatus +import logging from unittest.mock import patch from urllib.parse import urlparse @@ -252,8 +253,8 @@ async def test_stream_timeout_after_stop( await hass.async_block_till_done() -async def test_stream_keepalive(hass, setup_component): - """Test hls stream retries the stream when keepalive=True.""" +async def test_stream_retries(hass, setup_component, should_retry): + """Test hls stream is retried on failure.""" # Setup demo HLS track source = "test_stream_keepalive_source" stream = create_stream(hass, source, {}) @@ -271,9 +272,11 @@ def update_callback() -> None: cur_time = 0 def time_side_effect(): + logging.info("time side effect") nonlocal cur_time if cur_time >= 80: - stream.keepalive = False # Thread should exit and be joinable. + logging.info("changing return value") + should_retry.return_value = False # Thread should exit and be joinable. cur_time += 40 return cur_time @@ -284,8 +287,8 @@ def time_side_effect(): ): av_open.side_effect = av.error.InvalidDataError(-2, "error") mock_time.time.side_effect = time_side_effect - # Request stream - stream.keepalive = True + # Request stream. Enable retries which are disabled by default in tests. + should_retry.return_value = True stream.start() stream._thread.join() stream._thread = None diff --git a/tests/components/stream/test_worker.py b/tests/components/stream/test_worker.py index b54c8dc3472440..e5477c66f524d6 100644 --- a/tests/components/stream/test_worker.py +++ b/tests/components/stream/test_worker.py @@ -669,8 +669,8 @@ async def test_update_stream_source(hass): stream = Stream(hass, STREAM_SOURCE, {}) stream.add_provider(HLS_PROVIDER) - # Note that keepalive is not set here. The stream is "restarted" even though - # it is not stopping due to failure. + # Note that retries are disabled by default in tests, however the stream is "restarted" when + # the stream source is updated. py_av = MockPyAv() py_av.container.packets = PacketSequence(TEST_SEQUENCE_LENGTH) From b8a8485e91bc64f6effeec83e1567497efa70bb3 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Sun, 13 Feb 2022 05:17:55 +0000 Subject: [PATCH 0589/1098] Replace use of deprecated APIs in aiohomekit (#66409) --- .../homekit_controller/config_flow.py | 17 ++++++++--------- .../homekit_controller/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../homekit_controller/test_config_flow.py | 18 ++++++++---------- 5 files changed, 19 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index 82ab670f8db09e..48551129b67a25 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -125,10 +125,9 @@ async def async_step_user(self, user_input=None): if self.controller is None: await self._async_setup_controller() - all_hosts = await self.controller.discover_ip() - self.devices = {} - for host in all_hosts: + + async for host in self.controller.async_discover(): status_flags = int(host.info["sf"]) paired = not status_flags & 0x01 if paired: @@ -155,7 +154,7 @@ async def async_step_unignore(self, user_input): await self._async_setup_controller() try: - device = await self.controller.find_ip_by_device_id(unique_id) + device = await self.controller.async_find(unique_id) except aiohomekit.AccessoryNotFoundError: return self.async_abort(reason="accessory_not_found_error") @@ -339,12 +338,12 @@ async def async_step_pair(self, pair_info=None): # If it doesn't have a screen then the pin is static. # If it has a display it will display a pin on that display. In - # this case the code is random. So we have to call the start_pairing + # this case the code is random. So we have to call the async_start_pairing # API before the user can enter a pin. But equally we don't want to - # call start_pairing when the device is discovered, only when they + # call async_start_pairing when the device is discovered, only when they # click on 'Configure' in the UI. - # start_pairing will make the device show its pin and return a + # async_start_pairing will make the device show its pin and return a # callable. We call the callable with the pin that the user has typed # in. @@ -399,8 +398,8 @@ async def async_step_pair(self, pair_info=None): # we always check to see if self.finish_paring has been # set. try: - discovery = await self.controller.find_ip_by_device_id(self.hkid) - self.finish_pairing = await discovery.start_pairing(self.hkid) + discovery = await self.controller.async_find(self.hkid) + self.finish_pairing = await discovery.async_start_pairing(self.hkid) except aiohomekit.BusyError: # Already performing a pair setup operation with a different diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index 4e216474ec0249..43b0a614f44442 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit Controller", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homekit_controller", - "requirements": ["aiohomekit==0.7.11"], + "requirements": ["aiohomekit==0.7.13"], "zeroconf": ["_hap._tcp.local."], "after_dependencies": ["zeroconf"], "codeowners": ["@Jc2k", "@bdraco"], diff --git a/requirements_all.txt b/requirements_all.txt index 9186eeed73854d..7ef4a52b8cb618 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -184,7 +184,7 @@ aioguardian==2021.11.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==0.7.11 +aiohomekit==0.7.13 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b9ece9bf5cf07a..a1210060d6bc0e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -134,7 +134,7 @@ aioguardian==2021.11.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==0.7.11 +aiohomekit==0.7.13 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/tests/components/homekit_controller/test_config_flow.py b/tests/components/homekit_controller/test_config_flow.py index 101e4ebd024ed3..54408e5882dd4e 100644 --- a/tests/components/homekit_controller/test_config_flow.py +++ b/tests/components/homekit_controller/test_config_flow.py @@ -86,13 +86,11 @@ def _setup_flow_handler(hass, pairing=None): discovery = mock.Mock() discovery.device_id = "00:00:00:00:00:00" - discovery.start_pairing = unittest.mock.AsyncMock(return_value=finish_pairing) + discovery.async_start_pairing = unittest.mock.AsyncMock(return_value=finish_pairing) flow.controller = mock.Mock() flow.controller.pairings = {} - flow.controller.find_ip_by_device_id = unittest.mock.AsyncMock( - return_value=discovery - ) + flow.controller.async_find = unittest.mock.AsyncMock(return_value=discovery) return flow @@ -520,7 +518,7 @@ async def test_pair_abort_errors_on_start(hass, controller, exception, expected) # User initiates pairing - device refuses to enter pairing mode test_exc = exception("error") - with patch.object(device, "start_pairing", side_effect=test_exc): + with patch.object(device, "async_start_pairing", side_effect=test_exc): result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["type"] == "abort" assert result["reason"] == expected @@ -542,7 +540,7 @@ async def test_pair_try_later_errors_on_start(hass, controller, exception, expec # User initiates pairing - device refuses to enter pairing mode but may be successful after entering pairing mode or rebooting test_exc = exception("error") - with patch.object(device, "start_pairing", side_effect=test_exc): + with patch.object(device, "async_start_pairing", side_effect=test_exc): result2 = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result2["step_id"] == expected assert result2["type"] == "form" @@ -585,7 +583,7 @@ async def test_pair_form_errors_on_start(hass, controller, exception, expected): # User initiates pairing - device refuses to enter pairing mode test_exc = exception("error") - with patch.object(device, "start_pairing", side_effect=test_exc): + with patch.object(device, "async_start_pairing", side_effect=test_exc): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={"pairing_code": "111-22-333"} ) @@ -634,7 +632,7 @@ async def test_pair_abort_errors_on_finish(hass, controller, exception, expected # User initiates pairing - this triggers the device to show a pairing code # and then HA to show a pairing form finish_pairing = unittest.mock.AsyncMock(side_effect=exception("error")) - with patch.object(device, "start_pairing", return_value=finish_pairing): + with patch.object(device, "async_start_pairing", return_value=finish_pairing): result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["type"] == "form" @@ -674,7 +672,7 @@ async def test_pair_form_errors_on_finish(hass, controller, exception, expected) # User initiates pairing - this triggers the device to show a pairing code # and then HA to show a pairing form finish_pairing = unittest.mock.AsyncMock(side_effect=exception("error")) - with patch.object(device, "start_pairing", return_value=finish_pairing): + with patch.object(device, "async_start_pairing", return_value=finish_pairing): result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["type"] == "form" @@ -789,7 +787,7 @@ async def test_user_no_unpaired_devices(hass, controller): device = setup_mock_accessory(controller) # Pair the mock device so that it shows as paired in discovery - finish_pairing = await device.start_pairing(device.device_id) + finish_pairing = await device.async_start_pairing(device.device_id) await finish_pairing(device.pairing_code) # Device discovery is requested From b4c487376f0e37607d2cef4f3daea528697f1fac Mon Sep 17 00:00:00 2001 From: Austin Mroczek Date: Sun, 13 Feb 2022 00:05:20 -0800 Subject: [PATCH 0590/1098] bump total_connect_client to 2022.2 (#66408) --- homeassistant/components/totalconnect/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/totalconnect/manifest.json b/homeassistant/components/totalconnect/manifest.json index 3960d21842395a..530d45750f5c05 100644 --- a/homeassistant/components/totalconnect/manifest.json +++ b/homeassistant/components/totalconnect/manifest.json @@ -2,7 +2,7 @@ "domain": "totalconnect", "name": "Total Connect", "documentation": "https://www.home-assistant.io/integrations/totalconnect", - "requirements": ["total_connect_client==2022.1"], + "requirements": ["total_connect_client==2022.2"], "dependencies": [], "codeowners": ["@austinmroczek"], "config_flow": true, diff --git a/requirements_all.txt b/requirements_all.txt index 7ef4a52b8cb618..3f200675ad2999 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2377,7 +2377,7 @@ tololib==0.1.0b3 toonapi==0.2.1 # homeassistant.components.totalconnect -total_connect_client==2022.1 +total_connect_client==2022.2 # homeassistant.components.tplink_lte tp-connected==0.0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a1210060d6bc0e..330f03b7b9cb01 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1453,7 +1453,7 @@ tololib==0.1.0b3 toonapi==0.2.1 # homeassistant.components.totalconnect -total_connect_client==2022.1 +total_connect_client==2022.2 # homeassistant.components.transmission transmissionrpc==0.11 From ac3c5db989a2867ff1d6cb612711a16026497029 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Sun, 13 Feb 2022 14:37:18 +0000 Subject: [PATCH 0591/1098] Handle NoneType error in OVO integration (#66439) --- homeassistant/components/ovo_energy/sensor.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/ovo_energy/sensor.py b/homeassistant/components/ovo_energy/sensor.py index 8f9a18d1f113a0..532bb25cbc8128 100644 --- a/homeassistant/components/ovo_energy/sensor.py +++ b/homeassistant/components/ovo_energy/sensor.py @@ -54,7 +54,9 @@ class OVOEnergySensorEntityDescription(SensorEntityDescription): name="OVO Last Electricity Cost", device_class=SensorDeviceClass.MONETARY, state_class=SensorStateClass.TOTAL_INCREASING, - value=lambda usage: usage.electricity[-1].cost.amount, + value=lambda usage: usage.electricity[-1].cost.amount + if usage.electricity[-1].cost is not None + else None, ), OVOEnergySensorEntityDescription( key="last_electricity_start_time", @@ -88,7 +90,9 @@ class OVOEnergySensorEntityDescription(SensorEntityDescription): device_class=SensorDeviceClass.MONETARY, state_class=SensorStateClass.TOTAL_INCREASING, icon="mdi:cash-multiple", - value=lambda usage: usage.gas[-1].cost.amount, + value=lambda usage: usage.gas[-1].cost.amount + if usage.gas[-1].cost is not None + else None, ), OVOEnergySensorEntityDescription( key="last_gas_start_time", From b016259206e0ffd20c76d7c8e49fb7d6dab8bac3 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 13 Feb 2022 07:09:37 -0800 Subject: [PATCH 0592/1098] Reset the stream backoff timeout when the url updates (#66426) Reset the stream backoff timeout when the url updates, meant to improve the retry behavior for nest cameras. The problem is the nest url updates faster than the stream reset time so the wait timeout never resets if there is a temporarily problem with the new url. In particular this *may* help with the flaky cloud nest urls, but seems more correct otherwise. --- homeassistant/components/stream/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index 365ea946f51743..66929fff79c32f 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -344,7 +344,9 @@ def _run_worker(self) -> None: stream_state.discontinuity() if not _should_retry() or self._thread_quit.is_set(): if self._fast_restart_once: - # The stream source is updated, restart without any delay. + # The stream source is updated, restart without any delay and reset the retry + # backoff for the new url. + wait_timeout = 0 self._fast_restart_once = False self._thread_quit.clear() continue From d40a830b892c3e0ead5a4531102bfcb96203bd47 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 13 Feb 2022 17:23:30 +0100 Subject: [PATCH 0593/1098] Remove entities when config entry is removed from device (#66385) * Remove entities when config entry is removed from device * Update tests/helpers/test_entity_registry.py Co-authored-by: Martin Hjelmare * Don't remove entities not connected to a config entry * Update homeassistant/helpers/entity_registry.py Co-authored-by: Martin Hjelmare Co-authored-by: Franck Nijhof Co-authored-by: Martin Hjelmare --- homeassistant/helpers/entity_registry.py | 27 +++++- tests/helpers/test_entity_registry.py | 103 +++++++++++++++++++++++ 2 files changed, 126 insertions(+), 4 deletions(-) diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 36ac5cc3dde575..4d4fce6e68529d 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -446,13 +446,31 @@ def async_device_modified(self, event: Event) -> None: return if event.data["action"] != "update": + # Ignore "create" action return device_registry = dr.async_get(self.hass) device = device_registry.async_get(event.data["device_id"]) - # The device may be deleted already if the event handling is late - if not device or not device.disabled: + # The device may be deleted already if the event handling is late, do nothing + # in that case. Entities will be removed when we get the "remove" event. + if not device: + return + + # Remove entities which belong to config entries no longer associated with the + # device + entities = async_entries_for_device( + self, event.data["device_id"], include_disabled_entities=True + ) + for entity in entities: + if ( + entity.config_entry_id is not None + and entity.config_entry_id not in device.config_entries + ): + self.async_remove(entity.entity_id) + + # Re-enable disabled entities if the device is no longer disabled + if not device.disabled: entities = async_entries_for_device( self, event.data["device_id"], include_disabled_entities=True ) @@ -462,11 +480,12 @@ def async_device_modified(self, event: Event) -> None: self.async_update_entity(entity.entity_id, disabled_by=None) return + # Ignore device disabled by config entry, this is handled by + # async_config_entry_disabled if device.disabled_by is dr.DeviceEntryDisabler.CONFIG_ENTRY: - # Handled by async_config_entry_disabled return - # Fetch entities which are not already disabled + # Fetch entities which are not already disabled and disable them entities = async_entries_for_device(self, event.data["device_id"]) for entity in entities: self.async_update_entity( diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index 714ac037e2a525..78c99640bd048c 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -810,6 +810,109 @@ async def test_remove_device_removes_entities(hass, registry): assert not registry.async_is_registered(entry.entity_id) +async def test_remove_config_entry_from_device_removes_entities(hass, registry): + """Test that we remove entities tied to a device when config entry is removed.""" + device_registry = mock_device_registry(hass) + config_entry_1 = MockConfigEntry(domain="hue") + config_entry_2 = MockConfigEntry(domain="device_tracker") + + # Create device with two config entries + device_registry.async_get_or_create( + config_entry_id=config_entry_1.entry_id, + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + device_entry = device_registry.async_get_or_create( + config_entry_id=config_entry_2.entry_id, + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + assert device_entry.config_entries == { + config_entry_1.entry_id, + config_entry_2.entry_id, + } + + # Create one entity for each config entry + entry_1 = registry.async_get_or_create( + "light", + "hue", + "5678", + config_entry=config_entry_1, + device_id=device_entry.id, + ) + + entry_2 = registry.async_get_or_create( + "sensor", + "device_tracker", + "6789", + config_entry=config_entry_2, + device_id=device_entry.id, + ) + + assert registry.async_is_registered(entry_1.entity_id) + assert registry.async_is_registered(entry_2.entity_id) + + # Remove the first config entry from the device, the entity associated with it + # should be removed + device_registry.async_update_device( + device_entry.id, remove_config_entry_id=config_entry_1.entry_id + ) + await hass.async_block_till_done() + + assert device_registry.async_get(device_entry.id) + assert not registry.async_is_registered(entry_1.entity_id) + assert registry.async_is_registered(entry_2.entity_id) + + # Remove the second config entry from the device, the entity associated with it + # (and the device itself) should be removed + device_registry.async_update_device( + device_entry.id, remove_config_entry_id=config_entry_2.entry_id + ) + await hass.async_block_till_done() + + assert not device_registry.async_get(device_entry.id) + assert not registry.async_is_registered(entry_1.entity_id) + assert not registry.async_is_registered(entry_2.entity_id) + + +async def test_remove_config_entry_from_device_removes_entities_2(hass, registry): + """Test that we don't remove entities with no config entry when device is modified.""" + device_registry = mock_device_registry(hass) + config_entry_1 = MockConfigEntry(domain="hue") + config_entry_2 = MockConfigEntry(domain="device_tracker") + + # Create device with two config entries + device_registry.async_get_or_create( + config_entry_id=config_entry_1.entry_id, + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + device_entry = device_registry.async_get_or_create( + config_entry_id=config_entry_2.entry_id, + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + assert device_entry.config_entries == { + config_entry_1.entry_id, + config_entry_2.entry_id, + } + + # Create one entity for each config entry + entry_1 = registry.async_get_or_create( + "light", + "hue", + "5678", + device_id=device_entry.id, + ) + + assert registry.async_is_registered(entry_1.entity_id) + + # Remove the first config entry from the device + device_registry.async_update_device( + device_entry.id, remove_config_entry_id=config_entry_1.entry_id + ) + await hass.async_block_till_done() + + assert device_registry.async_get(device_entry.id) + assert registry.async_is_registered(entry_1.entity_id) + + async def test_update_device_race(hass, registry): """Test race when a device is created, updated and removed.""" device_registry = mock_device_registry(hass) From 40c6832cc17048866b8c8dca0c09b1b15b29bf64 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Sun, 13 Feb 2022 18:45:30 +0000 Subject: [PATCH 0594/1098] Update homekit_controller to use the new typed discovery data (#66462) --- .../homekit_controller/config_flow.py | 23 ++++++------- .../homekit_controller/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../homekit_controller/test_config_flow.py | 33 +++++++++---------- 5 files changed, 29 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index 48551129b67a25..d41eb0ed2201d6 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -36,8 +36,6 @@ PAIRING_FILE = "pairing.json" -MDNS_SUFFIX = "._hap._tcp.local." - PIN_FORMAT = re.compile(r"^(\d{3})-{0,1}(\d{2})-{0,1}(\d{3})$") _LOGGER = logging.getLogger(__name__) @@ -113,9 +111,10 @@ async def async_step_user(self, user_input=None): if user_input is not None: key = user_input["device"] - self.hkid = self.devices[key].device_id - self.model = self.devices[key].info["md"] - self.name = key[: -len(MDNS_SUFFIX)] if key.endswith(MDNS_SUFFIX) else key + self.hkid = self.devices[key].description.id + self.model = self.devices[key].description.model + self.name = self.devices[key].description.name + await self.async_set_unique_id( normalize_hkid(self.hkid), raise_on_progress=False ) @@ -127,12 +126,10 @@ async def async_step_user(self, user_input=None): self.devices = {} - async for host in self.controller.async_discover(): - status_flags = int(host.info["sf"]) - paired = not status_flags & 0x01 - if paired: + async for discovery in self.controller.async_discover(): + if discovery.paired: continue - self.devices[host.info["name"]] = host + self.devices[discovery.description.name] = discovery if not self.devices: return self.async_abort(reason="no_devices") @@ -158,9 +155,9 @@ async def async_step_unignore(self, user_input): except aiohomekit.AccessoryNotFoundError: return self.async_abort(reason="accessory_not_found_error") - self.name = device.info["name"].replace("._hap._tcp.local.", "") - self.model = device.info["md"] - self.hkid = normalize_hkid(device.info["id"]) + self.name = device.description.name + self.model = device.description.model + self.hkid = device.description.id return self._async_step_pair_show_form() diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index 43b0a614f44442..44de321db4ae32 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit Controller", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homekit_controller", - "requirements": ["aiohomekit==0.7.13"], + "requirements": ["aiohomekit==0.7.14"], "zeroconf": ["_hap._tcp.local."], "after_dependencies": ["zeroconf"], "codeowners": ["@Jc2k", "@bdraco"], diff --git a/requirements_all.txt b/requirements_all.txt index 3f200675ad2999..14d97bec67a8a5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -184,7 +184,7 @@ aioguardian==2021.11.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==0.7.13 +aiohomekit==0.7.14 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 330f03b7b9cb01..d8c0eeaab4977b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -134,7 +134,7 @@ aioguardian==2021.11.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==0.7.13 +aiohomekit==0.7.14 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/tests/components/homekit_controller/test_config_flow.py b/tests/components/homekit_controller/test_config_flow.py index 54408e5882dd4e..a65d63b1af2c69 100644 --- a/tests/components/homekit_controller/test_config_flow.py +++ b/tests/components/homekit_controller/test_config_flow.py @@ -85,7 +85,7 @@ def _setup_flow_handler(hass, pairing=None): finish_pairing = unittest.mock.AsyncMock(return_value=pairing) discovery = mock.Mock() - discovery.device_id = "00:00:00:00:00:00" + discovery.description.id = "00:00:00:00:00:00" discovery.async_start_pairing = unittest.mock.AsyncMock(return_value=finish_pairing) flow.controller = mock.Mock() @@ -136,22 +136,21 @@ def get_device_discovery_info( device, upper_case_props=False, missing_csharp=False ) -> zeroconf.ZeroconfServiceInfo: """Turn a aiohomekit format zeroconf entry into a homeassistant one.""" - record = device.info result = zeroconf.ZeroconfServiceInfo( - host=record["address"], - addresses=[record["address"]], - hostname=record["name"], - name=record["name"], - port=record["port"], + host="127.0.0.1", + hostname=device.description.name, + name=device.description.name, + addresses=["127.0.0.1"], + port=8080, properties={ - "md": record["md"], - "pv": record["pv"], - zeroconf.ATTR_PROPERTIES_ID: device.device_id, - "c#": record["c#"], - "s#": record["s#"], - "ff": record["ff"], - "ci": record["ci"], - "sf": 0x01, # record["sf"], + "md": device.description.model, + "pv": "1.0", + zeroconf.ATTR_PROPERTIES_ID: device.description.id, + "c#": device.description.config_num, + "s#": device.description.state_num, + "ff": "0", + "ci": "0", + "sf": "1", "sh": "", }, type="_hap._tcp.local.", @@ -787,7 +786,7 @@ async def test_user_no_unpaired_devices(hass, controller): device = setup_mock_accessory(controller) # Pair the mock device so that it shows as paired in discovery - finish_pairing = await device.async_start_pairing(device.device_id) + finish_pairing = await device.async_start_pairing(device.description.id) await finish_pairing(device.pairing_code) # Device discovery is requested @@ -807,7 +806,7 @@ async def test_unignore_works(hass, controller): result = await hass.config_entries.flow.async_init( "homekit_controller", context={"source": config_entries.SOURCE_UNIGNORE}, - data={"unique_id": device.device_id}, + data={"unique_id": device.description.id}, ) assert result["type"] == "form" assert result["step_id"] == "pair" From 2bdf55465a88882374b1a6e0cff28fe986b634a9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 13 Feb 2022 14:29:34 -0600 Subject: [PATCH 0595/1098] Bump pywizlight to 0.5.8 (#66448) --- homeassistant/components/wiz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/wiz/manifest.json b/homeassistant/components/wiz/manifest.json index 1296cf50d1b36b..3aa137f24607e2 100644 --- a/homeassistant/components/wiz/manifest.json +++ b/homeassistant/components/wiz/manifest.json @@ -8,7 +8,7 @@ ], "dependencies": ["network"], "documentation": "https://www.home-assistant.io/integrations/wiz", - "requirements": ["pywizlight==0.5.6"], + "requirements": ["pywizlight==0.5.8"], "iot_class": "local_push", "codeowners": ["@sbidy"] } diff --git a/requirements_all.txt b/requirements_all.txt index 14d97bec67a8a5..65520ab40b9485 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2057,7 +2057,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.5.6 +pywizlight==0.5.8 # homeassistant.components.xeoma pyxeoma==1.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d8c0eeaab4977b..771dcc8cf8786a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1285,7 +1285,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.5.6 +pywizlight==0.5.8 # homeassistant.components.zerproc pyzerproc==0.4.8 From ffcac67d9950f569573a76c6431243c6eb5f1671 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 13 Feb 2022 15:23:11 -0600 Subject: [PATCH 0596/1098] Add is_ipv4_address and is_ipv6_address utils (#66472) --- homeassistant/util/network.py | 20 ++++++++++++++++++++ tests/util/test_network.py | 16 ++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/homeassistant/util/network.py b/homeassistant/util/network.py index e714b6b6b31527..396ab56b8c25d0 100644 --- a/homeassistant/util/network.py +++ b/homeassistant/util/network.py @@ -59,6 +59,26 @@ def is_ip_address(address: str) -> bool: return True +def is_ipv4_address(address: str) -> bool: + """Check if a given string is an IPv4 address.""" + try: + IPv4Address(address) + except ValueError: + return False + + return True + + +def is_ipv6_address(address: str) -> bool: + """Check if a given string is an IPv6 address.""" + try: + IPv6Address(address) + except ValueError: + return False + + return True + + def normalize_url(address: str) -> str: """Normalize a given URL.""" url = yarl.URL(address.rstrip("/")) diff --git a/tests/util/test_network.py b/tests/util/test_network.py index 089ef5e0ab8e87..b5c6b1a3e24178 100644 --- a/tests/util/test_network.py +++ b/tests/util/test_network.py @@ -56,6 +56,22 @@ def test_is_ip_address(): assert not network_util.is_ip_address("example.com") +def test_is_ipv4_address(): + """Test if strings are IPv4 addresses.""" + assert network_util.is_ipv4_address("192.168.0.1") is True + assert network_util.is_ipv4_address("8.8.8.8") is True + assert network_util.is_ipv4_address("192.168.0.999") is False + assert network_util.is_ipv4_address("192.168.0.0/24") is False + assert network_util.is_ipv4_address("example.com") is False + + +def test_is_ipv6_address(): + """Test if strings are IPv6 addresses.""" + assert network_util.is_ipv6_address("::1") is True + assert network_util.is_ipv6_address("8.8.8.8") is False + assert network_util.is_ipv6_address("8.8.8.8") is False + + def test_normalize_url(): """Test the normalizing of URLs.""" assert network_util.normalize_url("http://example.com") == "http://example.com" From bc2cc42955921c6033086f36ec23710c2529ce63 Mon Sep 17 00:00:00 2001 From: Joshua Roys Date: Sun, 13 Feb 2022 16:24:23 -0500 Subject: [PATCH 0597/1098] Don't abort zeroconf discovery for IPv6-only devices (#66455) --- homeassistant/components/zeroconf/__init__.py | 17 ++++++++++++----- tests/components/zeroconf/test_init.py | 9 +++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index 78ad9b25cd7051..ffe4140a434aa1 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -541,9 +541,9 @@ def info_from_service(service: AsyncServiceInfo) -> ZeroconfServiceInfo | None: if isinstance(value, bytes): properties[key] = value.decode("utf-8") - if not (addresses := service.addresses): + if not (addresses := service.addresses or service.parsed_addresses()): return None - if (host := _first_non_link_local_or_v6_address(addresses)) is None: + if (host := _first_non_link_local_address(addresses)) is None: return None return ZeroconfServiceInfo( @@ -557,11 +557,18 @@ def info_from_service(service: AsyncServiceInfo) -> ZeroconfServiceInfo | None: ) -def _first_non_link_local_or_v6_address(addresses: list[bytes]) -> str | None: - """Return the first ipv6 or non-link local ipv4 address.""" +def _first_non_link_local_address( + addresses: list[bytes] | list[str], +) -> str | None: + """Return the first ipv6 or non-link local ipv4 address, preferring IPv4.""" + for address in addresses: + ip_addr = ip_address(address) + if not ip_addr.is_link_local and ip_addr.version == 4: + return str(ip_addr) + # If we didn't find a good IPv4 address, check for IPv6 addresses. for address in addresses: ip_addr = ip_address(address) - if not ip_addr.is_link_local or ip_addr.version == 6: + if not ip_addr.is_link_local and ip_addr.version == 6: return str(ip_addr) return None diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index dc007d5c2c5f6c..b7e99991fdd230 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -780,6 +780,15 @@ async def test_info_from_service_prefers_ipv4(hass): assert info.host == "192.168.66.12" +async def test_info_from_service_can_return_ipv6(hass): + """Test that IPv6-only devices can be discovered.""" + service_type = "_test._tcp.local." + service_info = get_service_info_mock(service_type, f"test.{service_type}") + service_info.addresses = ["fd11:1111:1111:0:1234:1234:1234:1234"] + info = zeroconf.info_from_service(service_info) + assert info.host == "fd11:1111:1111:0:1234:1234:1234:1234" + + async def test_get_instance(hass, mock_async_zeroconf): """Test we get an instance.""" assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) From 36120c77f8db0216698c8d33418d6e3f8ffb766b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hjelseth=20H=C3=B8yer?= Date: Sun, 13 Feb 2022 22:41:46 +0100 Subject: [PATCH 0598/1098] Reduce update_interval for Opengarage (#66478) --- homeassistant/components/opengarage/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/opengarage/__init__.py b/homeassistant/components/opengarage/__init__.py index 76ffcc42bd1693..eb1b50db5b6987 100644 --- a/homeassistant/components/opengarage/__init__.py +++ b/homeassistant/components/opengarage/__init__.py @@ -66,7 +66,7 @@ def __init__( hass, _LOGGER, name=DOMAIN, - update_interval=timedelta(seconds=30), + update_interval=timedelta(seconds=5), ) async def _async_update_data(self) -> None: From fe077b6990452280b5c4eb438ab64f5f40827bb5 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Sun, 13 Feb 2022 23:47:37 +0200 Subject: [PATCH 0599/1098] Use shorthand attributes in webostv (#66418) --- .../components/webostv/media_player.py | 212 +++++++----------- tests/components/webostv/test_media_player.py | 34 +++ 2 files changed, 115 insertions(+), 131 deletions(-) diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index 320ce092427f17..f263d79e2dbbb8 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -126,6 +126,8 @@ async def cmd_wrapper(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> None: class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity): """Representation of a LG webOS Smart TV.""" + _attr_device_class = MediaPlayerDeviceClass.TV + def __init__( self, wrapper: WebOsClientWrapper, @@ -136,8 +138,8 @@ def __init__( """Initialize the webos device.""" self._wrapper = wrapper self._client: WebOsClient = wrapper.client - self._name = name - self._unique_id = unique_id + self._attr_name = name + self._attr_unique_id = unique_id self._sources = sources # Assume that the TV is not paused @@ -146,7 +148,8 @@ def __init__( self._current_source = None self._source_list: dict = {} - self._supported_features: int | None = None + self._supported_features: int = 0 + self._update_states() async def async_added_to_hass(self) -> None: """Connect and subscribe to dispatcher signals and state updates.""" @@ -160,11 +163,13 @@ async def async_added_to_hass(self) -> None: self.async_handle_state_update ) - if self._supported_features is not None: - return - - if (state := await self.async_get_last_state()) is not None: - self._supported_features = state.attributes.get(ATTR_SUPPORTED_FEATURES) + if ( + self.state == STATE_OFF + and (state := await self.async_get_last_state()) is not None + ): + self._supported_features = ( + state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) & ~SUPPORT_TURN_ON + ) async def async_will_remove_from_hass(self) -> None: """Call disconnect on removal.""" @@ -185,10 +190,74 @@ async def async_signal_handler(self, data: dict[str, Any]) -> None: async def async_handle_state_update(self, _client: WebOsClient) -> None: """Update state from WebOsClient.""" - self.update_sources() + self._update_states() self.async_write_ha_state() - def update_sources(self) -> None: + def _update_states(self) -> None: + """Update entity state attributes.""" + self._update_sources() + + self._attr_state = STATE_ON if self._client.is_on else STATE_OFF + self._attr_is_volume_muted = cast(bool, self._client.muted) + + self._attr_volume_level = None + if self._client.volume is not None: + self._attr_volume_level = cast(float, self._client.volume / 100.0) + + self._attr_source = self._current_source + self._attr_source_list = sorted(self._source_list) + + self._attr_media_content_type = None + if self._client.current_app_id == LIVE_TV_APP_ID: + self._attr_media_content_type = MEDIA_TYPE_CHANNEL + + self._attr_media_title = None + if (self._client.current_app_id == LIVE_TV_APP_ID) and ( + self._client.current_channel is not None + ): + self._attr_media_title = cast( + str, self._client.current_channel.get("channelName") + ) + + self._attr_media_image_url = None + if self._client.current_app_id in self._client.apps: + icon: str = self._client.apps[self._client.current_app_id]["largeIcon"] + if not icon.startswith("http"): + icon = self._client.apps[self._client.current_app_id]["icon"] + self._attr_media_image_url = icon + + if self.state != STATE_OFF or not self._supported_features: + supported = SUPPORT_WEBOSTV + if self._client.sound_output in ("external_arc", "external_speaker"): + supported = supported | SUPPORT_WEBOSTV_VOLUME + elif self._client.sound_output != "lineout": + supported = supported | SUPPORT_WEBOSTV_VOLUME | SUPPORT_VOLUME_SET + + self._supported_features = supported + + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, cast(str, self.unique_id))}, + manufacturer="LG", + name=self.name, + ) + + if self._client.system_info is not None or self.state != STATE_OFF: + maj_v = self._client.software_info.get("major_ver") + min_v = self._client.software_info.get("minor_ver") + if maj_v and min_v: + self._attr_device_info["sw_version"] = f"{maj_v}.{min_v}" + + model = self._client.system_info.get("modelName") + if model: + self._attr_device_info["model"] = model + + self._attr_extra_state_attributes = {} + if self._client.sound_output is not None or self.state != STATE_OFF: + self._attr_extra_state_attributes = { + ATTR_SOUND_OUTPUT: self._client.sound_output + } + + def _update_sources(self) -> None: """Update list of sources from current source, apps, inputs and configured list.""" source_list = self._source_list self._source_list = {} @@ -249,132 +318,13 @@ async def async_update(self) -> None: with suppress(*WEBOSTV_EXCEPTIONS, WebOsTvPairError): await self._client.connect() - @property - def unique_id(self) -> str: - """Return the unique id of the device.""" - return self._unique_id - - @property - def name(self) -> str: - """Return the name of the device.""" - return self._name - - @property - def device_class(self) -> MediaPlayerDeviceClass: - """Return the device class of the device.""" - return MediaPlayerDeviceClass.TV - - @property - def state(self) -> str: - """Return the state of the device.""" - if self._client.is_on: - return STATE_ON - - return STATE_OFF - - @property - def is_volume_muted(self) -> bool: - """Boolean if volume is currently muted.""" - return cast(bool, self._client.muted) - - @property - def volume_level(self) -> float | None: - """Volume level of the media player (0..1).""" - if self._client.volume is not None: - return cast(float, self._client.volume / 100.0) - - return None - - @property - def source(self) -> str | None: - """Return the current input source.""" - return self._current_source - - @property - def source_list(self) -> list[str]: - """List of available input sources.""" - return sorted(self._source_list) - - @property - def media_content_type(self) -> str | None: - """Content type of current playing media.""" - if self._client.current_app_id == LIVE_TV_APP_ID: - return MEDIA_TYPE_CHANNEL - - return None - - @property - def media_title(self) -> str | None: - """Title of current playing media.""" - if (self._client.current_app_id == LIVE_TV_APP_ID) and ( - self._client.current_channel is not None - ): - return cast(str, self._client.current_channel.get("channelName")) - return None - - @property - def media_image_url(self) -> str | None: - """Image url of current playing media.""" - if self._client.current_app_id in self._client.apps: - icon: str = self._client.apps[self._client.current_app_id]["largeIcon"] - if not icon.startswith("http"): - icon = self._client.apps[self._client.current_app_id]["icon"] - return icon - return None - @property def supported_features(self) -> int: """Flag media player features that are supported.""" - if self.state == STATE_OFF and self._supported_features is not None: - if self._wrapper.turn_on: - return self._supported_features | SUPPORT_TURN_ON - - return self._supported_features & ~SUPPORT_TURN_ON - - supported = SUPPORT_WEBOSTV - - if self._client.sound_output in ("external_arc", "external_speaker"): - supported = supported | SUPPORT_WEBOSTV_VOLUME - elif self._client.sound_output != "lineout": - supported = supported | SUPPORT_WEBOSTV_VOLUME | SUPPORT_VOLUME_SET - if self._wrapper.turn_on: - supported |= SUPPORT_TURN_ON - - if self.state != STATE_OFF: - self._supported_features = supported + return self._supported_features | SUPPORT_TURN_ON - return supported - - @property - def device_info(self) -> DeviceInfo: - """Return device information.""" - device_info = DeviceInfo( - identifiers={(DOMAIN, self._unique_id)}, - manufacturer="LG", - name=self._name, - ) - - if self._client.system_info is None and self.state == STATE_OFF: - return device_info - - maj_v = self._client.software_info.get("major_ver") - min_v = self._client.software_info.get("minor_ver") - if maj_v and min_v: - device_info["sw_version"] = f"{maj_v}.{min_v}" - - model = self._client.system_info.get("modelName") - if model: - device_info["model"] = model - - return device_info - - @property - def extra_state_attributes(self) -> dict[str, str] | None: - """Return device specific state attributes.""" - if self._client.sound_output is None and self.state == STATE_OFF: - return None - return {ATTR_SOUND_OUTPUT: self._client.sound_output} + return self._supported_features @cmd async def async_turn_off(self) -> None: diff --git a/tests/components/webostv/test_media_player.py b/tests/components/webostv/test_media_player.py index c9cc4a78aeef87..f0ebdd70e97c7c 100644 --- a/tests/components/webostv/test_media_player.py +++ b/tests/components/webostv/test_media_player.py @@ -652,3 +652,37 @@ async def test_cached_supported_features(hass, client, monkeypatch): attrs = hass.states.get(ENTITY_ID).attributes assert attrs[ATTR_SUPPORTED_FEATURES] == supported | SUPPORT_TURN_ON + + +async def test_supported_features_no_cache(hass, client, monkeypatch): + """Test supported features if device is off and no cache.""" + monkeypatch.setattr(client, "is_on", False) + monkeypatch.setattr(client, "sound_output", None) + await setup_webostv(hass) + + supported = SUPPORT_WEBOSTV | SUPPORT_WEBOSTV_VOLUME | SUPPORT_VOLUME_SET + attrs = hass.states.get(ENTITY_ID).attributes + + assert attrs[ATTR_SUPPORTED_FEATURES] == supported + + +async def test_supported_features_ignore_cache(hass, client): + """Test ignore cached supported features if device is on at startup.""" + mock_restore_cache( + hass, + [ + State( + ENTITY_ID, + STATE_OFF, + attributes={ + ATTR_SUPPORTED_FEATURES: SUPPORT_WEBOSTV | SUPPORT_WEBOSTV_VOLUME, + }, + ) + ], + ) + await setup_webostv(hass) + + supported = SUPPORT_WEBOSTV | SUPPORT_WEBOSTV_VOLUME | SUPPORT_VOLUME_SET + attrs = hass.states.get(ENTITY_ID).attributes + + assert attrs[ATTR_SUPPORTED_FEATURES] == supported From ad0cb4831e581f03660e5669ec55de4caa3ea7bb Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 14 Feb 2022 00:15:30 +0000 Subject: [PATCH 0600/1098] [ci skip] Translation update --- .../components/adguard/translations/el.json | 3 +- .../components/agent_dvr/translations/el.json | 3 ++ .../amberelectric/translations/el.json | 11 +++++++ .../components/androidtv/translations/el.json | 3 +- .../components/arcam_fmj/translations/el.json | 3 ++ .../components/asuswrt/translations/el.json | 5 ++- .../components/atag/translations/el.json | 3 ++ .../components/aurora/translations/el.json | 9 +++++ .../aussie_broadband/translations/el.json | 5 +++ .../aussie_broadband/translations/fr.json | 7 ++-- .../components/axis/translations/el.json | 3 ++ .../components/balboa/translations/el.json | 3 ++ .../binary_sensor/translations/el.json | 33 ++++++++++++++++++- .../bmw_connected_drive/translations/el.json | 3 +- .../components/bond/translations/el.json | 5 +++ .../components/bosch_shc/translations/el.json | 3 ++ .../components/braviatv/translations/el.json | 3 ++ .../components/brunt/translations/el.json | 3 ++ .../components/bsblan/translations/el.json | 4 ++- .../components/bsblan/translations/fr.json | 3 +- .../components/button/translations/el.json | 10 ++++++ .../components/climacell/translations/fr.json | 2 +- .../climacell/translations/sensor.fr.json | 1 + .../components/coinbase/translations/fr.json | 2 ++ .../components/dlna_dmr/translations/el.json | 8 +++++ .../components/dnsip/translations/fr.json | 4 ++- .../components/doorbird/translations/el.json | 1 + .../components/dsmr/translations/el.json | 3 +- .../components/dunehd/translations/el.json | 3 ++ .../components/elkm1/translations/el.json | 6 +++- .../components/elkm1/translations/fr.json | 14 +++++--- .../components/elmax/translations/el.json | 3 ++ .../components/emonitor/translations/el.json | 5 +++ .../enphase_envoy/translations/el.json | 4 +++ .../components/epson/translations/el.json | 7 ++++ .../evil_genius_labs/translations/el.json | 11 +++++++ .../fireservicerota/translations/el.json | 3 +- .../components/fivem/translations/el.json | 8 +++++ .../components/fivem/translations/fr.json | 1 + .../components/flo/translations/el.json | 11 +++++++ .../components/flux_led/translations/el.json | 3 ++ .../forked_daapd/translations/el.json | 1 + .../components/foscam/translations/el.json | 4 ++- .../components/fritz/translations/el.json | 6 ++++ .../components/fritzbox/translations/el.json | 6 ++++ .../fritzbox_callmonitor/translations/el.json | 6 ++++ .../components/fronius/translations/el.json | 3 ++ .../components/github/translations/fr.json | 8 +++++ .../components/glances/translations/el.json | 1 + .../components/goodwe/translations/fr.json | 6 ++-- .../google_travel_time/translations/el.json | 1 + .../components/hlk_sw16/translations/el.json | 11 +++++++ .../components/homekit/translations/fr.json | 8 +++-- .../huawei_lte/translations/el.json | 3 ++ .../components/hue/translations/el.json | 8 +++++ .../huisbaasje/translations/el.json | 11 +++++++ .../humidifier/translations/fr.json | 1 + .../hvv_departures/translations/el.json | 3 ++ .../components/ialarm/translations/el.json | 11 +++++++ .../components/iaqualink/translations/el.json | 3 ++ .../components/insteon/translations/el.json | 6 +++- .../intellifire/translations/el.json | 11 +++++++ .../intellifire/translations/fr.json | 1 + .../components/ipp/translations/el.json | 3 +- .../components/iss/translations/fr.json | 16 +++++++++ .../components/jellyfin/translations/el.json | 11 +++++++ .../keenetic_ndms2/translations/el.json | 9 +++++ .../components/kmtronic/translations/el.json | 10 ++++++ .../components/knx/translations/el.json | 2 ++ .../components/kodi/translations/el.json | 3 ++ .../kostal_plenticore/translations/el.json | 9 +++++ .../components/kraken/translations/el.json | 3 +- .../launch_library/translations/fr.json | 3 ++ .../components/life360/translations/el.json | 3 ++ .../components/light/translations/el.json | 1 + .../components/light/translations/fr.json | 1 + .../litterrobot/translations/el.json | 11 +++++++ .../components/lookin/translations/el.json | 5 +++ .../lutron_caseta/translations/el.json | 3 ++ .../media_player/translations/fr.json | 1 + .../met_eireann/translations/el.json | 3 ++ .../components/mikrotik/translations/el.json | 1 + .../components/mill/translations/el.json | 5 +++ .../modem_callerid/translations/el.json | 3 ++ .../modern_forms/translations/el.json | 3 ++ .../moehlenhoff_alpha2/translations/bg.json | 3 +- .../moehlenhoff_alpha2/translations/el.json | 9 +++++ .../moehlenhoff_alpha2/translations/fr.json | 3 +- .../motion_blinds/translations/el.json | 1 + .../components/motioneye/translations/el.json | 6 ++++ .../components/mqtt/translations/el.json | 3 +- .../components/mutesync/translations/el.json | 7 ++++ .../components/nam/translations/el.json | 12 +++++++ .../components/netgear/translations/fr.json | 2 +- .../components/notion/translations/el.json | 3 ++ .../components/nuki/translations/el.json | 5 +++ .../components/nut/translations/el.json | 3 ++ .../components/nzbget/translations/el.json | 4 +++ .../components/octoprint/translations/el.json | 15 +++++++++ .../components/oncue/translations/el.json | 11 +++++++ .../components/onvif/translations/el.json | 1 + .../components/overkiz/translations/el.json | 4 ++- .../components/overkiz/translations/fr.json | 5 ++- .../overkiz/translations/sensor.fr.json | 4 ++- .../ovo_energy/translations/el.json | 3 ++ .../p1_monitor/translations/el.json | 3 ++ .../panasonic_viera/translations/el.json | 3 ++ .../philips_js/translations/el.json | 3 +- .../components/pi_hole/translations/el.json | 2 ++ .../components/picnic/translations/bg.json | 7 ++++ .../components/picnic/translations/el.json | 6 ++++ .../components/picnic/translations/fr.json | 4 ++- .../components/picnic/translations/hu.json | 4 ++- .../components/picnic/translations/it.json | 4 ++- .../picnic/translations/zh-Hant.json | 4 ++- .../components/plex/translations/el.json | 1 + .../components/plugwise/translations/el.json | 6 ++++ .../components/powerwall/translations/fr.json | 14 +++++++- .../components/prosegur/translations/el.json | 6 ++-- .../rainmachine/translations/el.json | 3 ++ .../components/remote/translations/fr.json | 1 + .../components/ridwell/translations/el.json | 15 +++++++++ .../components/ring/translations/el.json | 3 ++ .../components/risco/translations/el.json | 3 +- .../components/roomba/translations/el.json | 6 ++-- .../ruckus_unleashed/translations/el.json | 11 +++++++ .../components/senseme/translations/el.json | 3 ++ .../components/senseme/translations/fr.json | 4 ++- .../components/sensibo/translations/el.json | 11 +++++++ .../components/sensor/translations/el.json | 11 +++++-- .../components/sharkiq/translations/el.json | 8 ++++- .../components/shelly/translations/el.json | 5 +++ .../components/sia/translations/el.json | 6 ++++ .../components/sma/translations/el.json | 3 +- .../smart_meter_texas/translations/el.json | 11 +++++++ .../components/sonarr/translations/el.json | 3 +- .../components/spider/translations/el.json | 3 ++ .../squeezebox/translations/el.json | 14 +++++++- .../srp_energy/translations/el.json | 3 +- .../components/starline/translations/el.json | 3 ++ .../components/steamist/translations/el.json | 6 ++++ .../components/steamist/translations/fr.json | 1 + .../surepetcare/translations/el.json | 11 +++++++ .../components/switch/translations/fr.json | 1 + .../components/switchbot/translations/el.json | 3 +- .../components/syncthru/translations/el.json | 6 ++++ .../synology_dsm/translations/el.json | 6 ++++ .../synology_dsm/translations/fr.json | 1 + .../system_bridge/translations/el.json | 3 ++ .../tellduslive/translations/el.json | 3 ++ .../tesla_wall_connector/translations/el.json | 3 ++ .../components/tolo/translations/el.json | 3 ++ .../components/tradfri/translations/el.json | 1 + .../translations/el.json | 1 + .../transmission/translations/el.json | 3 ++ .../components/tuya/translations/el.json | 1 + .../tuya/translations/select.el.json | 8 +++++ .../tuya/translations/select.fr.json | 12 +++++-- .../components/twinkly/translations/fr.json | 2 +- .../components/unifi/translations/el.json | 4 ++- .../unifiprotect/translations/el.json | 10 +++++- .../unifiprotect/translations/fr.json | 7 ++-- .../components/upcloud/translations/el.json | 9 +++++ .../components/vallox/translations/el.json | 4 +++ .../components/venstar/translations/el.json | 4 +++ .../components/version/translations/ja.json | 2 +- .../components/vicare/translations/el.json | 1 + .../vlc_telnet/translations/el.json | 6 ++++ .../components/wallbox/translations/el.json | 5 +++ .../components/watttime/translations/el.json | 3 ++ .../waze_travel_time/translations/el.json | 1 + .../components/webostv/translations/el.json | 1 + .../components/webostv/translations/fr.json | 2 ++ .../components/whirlpool/translations/el.json | 11 +++++++ .../components/wiz/translations/el.json | 3 ++ .../components/wiz/translations/fr.json | 25 +++++++++++++- .../components/wolflink/translations/el.json | 11 +++++++ .../yale_smart_alarm/translations/el.json | 6 ++-- .../yale_smart_alarm/translations/fr.json | 2 +- .../yamaha_musiccast/translations/el.json | 3 ++ .../components/youless/translations/el.json | 11 +++++++ .../components/zwave_me/translations/fr.json | 20 +++++++++++ 182 files changed, 914 insertions(+), 66 deletions(-) create mode 100644 homeassistant/components/amberelectric/translations/el.json create mode 100644 homeassistant/components/button/translations/el.json create mode 100644 homeassistant/components/evil_genius_labs/translations/el.json create mode 100644 homeassistant/components/flo/translations/el.json create mode 100644 homeassistant/components/hlk_sw16/translations/el.json create mode 100644 homeassistant/components/huisbaasje/translations/el.json create mode 100644 homeassistant/components/ialarm/translations/el.json create mode 100644 homeassistant/components/intellifire/translations/el.json create mode 100644 homeassistant/components/iss/translations/fr.json create mode 100644 homeassistant/components/jellyfin/translations/el.json create mode 100644 homeassistant/components/litterrobot/translations/el.json create mode 100644 homeassistant/components/oncue/translations/el.json create mode 100644 homeassistant/components/picnic/translations/bg.json create mode 100644 homeassistant/components/ridwell/translations/el.json create mode 100644 homeassistant/components/ruckus_unleashed/translations/el.json create mode 100644 homeassistant/components/sensibo/translations/el.json create mode 100644 homeassistant/components/smart_meter_texas/translations/el.json create mode 100644 homeassistant/components/surepetcare/translations/el.json create mode 100644 homeassistant/components/whirlpool/translations/el.json create mode 100644 homeassistant/components/wolflink/translations/el.json create mode 100644 homeassistant/components/youless/translations/el.json create mode 100644 homeassistant/components/zwave_me/translations/fr.json diff --git a/homeassistant/components/adguard/translations/el.json b/homeassistant/components/adguard/translations/el.json index 619dcfa915356a..fff34120dcf8d9 100644 --- a/homeassistant/components/adguard/translations/el.json +++ b/homeassistant/components/adguard/translations/el.json @@ -14,7 +14,8 @@ "user": { "data": { "host": "\u0394\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2", - "port": "\u0398\u03cd\u03c1\u03b1" + "port": "\u0398\u03cd\u03c1\u03b1", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf AdGuard Home \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c8\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf." } diff --git a/homeassistant/components/agent_dvr/translations/el.json b/homeassistant/components/agent_dvr/translations/el.json index 7613c5617597c7..84197d3ef01aa8 100644 --- a/homeassistant/components/agent_dvr/translations/el.json +++ b/homeassistant/components/agent_dvr/translations/el.json @@ -2,6 +2,9 @@ "config": { "step": { "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Agent DVR" } } diff --git a/homeassistant/components/amberelectric/translations/el.json b/homeassistant/components/amberelectric/translations/el.json new file mode 100644 index 00000000000000..6ca86ce05427a6 --- /dev/null +++ b/homeassistant/components/amberelectric/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "site": { + "data": { + "site_name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/androidtv/translations/el.json b/homeassistant/components/androidtv/translations/el.json index c294fa7c86b9fe..26cc502643a7b2 100644 --- a/homeassistant/components/androidtv/translations/el.json +++ b/homeassistant/components/androidtv/translations/el.json @@ -13,7 +13,8 @@ "adb_server_ip": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae ADB (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03b5\u03bd\u03ae \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bc\u03b7\u03bd \u03c4\u03b7 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5)", "adb_server_port": "\u0398\u03cd\u03c1\u03b1 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae ADB", "adbkey": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd ADB (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1)", - "device_class": "\u039f \u03c4\u03cd\u03c0\u03bf\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" + "device_class": "\u039f \u03c4\u03cd\u03c0\u03bf\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" }, "description": "\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b5\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03bf\u03c5\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c3\u03b1\u03c2 Android TV", "title": "Android TV" diff --git a/homeassistant/components/arcam_fmj/translations/el.json b/homeassistant/components/arcam_fmj/translations/el.json index 789906c36e430a..214605b1aa9c09 100644 --- a/homeassistant/components/arcam_fmj/translations/el.json +++ b/homeassistant/components/arcam_fmj/translations/el.json @@ -6,6 +6,9 @@ "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Arcam FMJ \u03c3\u03c4\u03bf `{host}` \u03c3\u03c4\u03bf Home Assistant;" }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2." } } diff --git a/homeassistant/components/asuswrt/translations/el.json b/homeassistant/components/asuswrt/translations/el.json index 2ac0e09634c173..a1f7f6508218e2 100644 --- a/homeassistant/components/asuswrt/translations/el.json +++ b/homeassistant/components/asuswrt/translations/el.json @@ -8,8 +8,11 @@ "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "protocol": "\u03a0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf \u03b5\u03c0\u03b9\u03ba\u03bf\u03b9\u03bd\u03c9\u03bd\u03af\u03b1\u03c2 \u03c0\u03c1\u03bf\u03c2 \u03c7\u03c1\u03ae\u03c3\u03b7", - "ssh_key": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd SSH (\u03b1\u03bd\u03c4\u03af \u03c4\u03bf\u03c5 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2)" + "ssh_key": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd SSH (\u03b1\u03bd\u03c4\u03af \u03c4\u03bf\u03c5 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2)", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7 \u03c0\u03b1\u03c1\u03ac\u03bc\u03b5\u03c4\u03c1\u03bf \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2", "title": "AsusWRT" diff --git a/homeassistant/components/atag/translations/el.json b/homeassistant/components/atag/translations/el.json index 60fd251d0a55ff..93e4bfa4262229 100644 --- a/homeassistant/components/atag/translations/el.json +++ b/homeassistant/components/atag/translations/el.json @@ -5,6 +5,9 @@ }, "step": { "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" } } diff --git a/homeassistant/components/aurora/translations/el.json b/homeassistant/components/aurora/translations/el.json index e45564b1116c60..491ef12d92006b 100644 --- a/homeassistant/components/aurora/translations/el.json +++ b/homeassistant/components/aurora/translations/el.json @@ -1,3 +1,12 @@ { + "config": { + "step": { + "user": { + "data": { + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + } + } + } + }, "title": "\u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 NOAA Aurora" } \ No newline at end of file diff --git a/homeassistant/components/aussie_broadband/translations/el.json b/homeassistant/components/aussie_broadband/translations/el.json index 88a9eb36f2f5f3..c90ecfda92549a 100644 --- a/homeassistant/components/aussie_broadband/translations/el.json +++ b/homeassistant/components/aussie_broadband/translations/el.json @@ -12,6 +12,11 @@ "services": "\u03a5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b5\u03c2" }, "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03b9\u03ce\u03bd" + }, + "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } } } }, diff --git a/homeassistant/components/aussie_broadband/translations/fr.json b/homeassistant/components/aussie_broadband/translations/fr.json index 06b0e44a70a8b6..518f05e8ac30cc 100644 --- a/homeassistant/components/aussie_broadband/translations/fr.json +++ b/homeassistant/components/aussie_broadband/translations/fr.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9" + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", + "no_services_found": "Aucun service n'a \u00e9t\u00e9 trouv\u00e9 pour ce compte", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" }, "error": { "cannot_connect": "Impossible de se connecter", @@ -13,7 +15,8 @@ "data": { "password": "Mot de passe" }, - "description": "Mettre \u00e0 jour le mot de passe pour {username}" + "description": "Mettre \u00e0 jour le mot de passe pour {username}", + "title": "R\u00e9-authentifier l'int\u00e9gration" }, "service": { "data": { diff --git a/homeassistant/components/axis/translations/el.json b/homeassistant/components/axis/translations/el.json index c113c987cea949..610a5a4f586d6e 100644 --- a/homeassistant/components/axis/translations/el.json +++ b/homeassistant/components/axis/translations/el.json @@ -7,6 +7,9 @@ "flow_title": "{name} ({host})", "step": { "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 Axis" } } diff --git a/homeassistant/components/balboa/translations/el.json b/homeassistant/components/balboa/translations/el.json index 63210112cbb2ae..ba3d5b988037ec 100644 --- a/homeassistant/components/balboa/translations/el.json +++ b/homeassistant/components/balboa/translations/el.json @@ -2,6 +2,9 @@ "config": { "step": { "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Balboa Wi-Fi" } } diff --git a/homeassistant/components/binary_sensor/translations/el.json b/homeassistant/components/binary_sensor/translations/el.json index 9c1644382e5941..0fdcc207646fd1 100644 --- a/homeassistant/components/binary_sensor/translations/el.json +++ b/homeassistant/components/binary_sensor/translations/el.json @@ -1,7 +1,12 @@ { "device_automation": { "condition_type": { + "is_bat_low": "\u0397 \u03bc\u03c0\u03b1\u03c4\u03b1\u03c1\u03af\u03b1 {entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03c7\u03b1\u03bc\u03b7\u03bb\u03ae", "is_co": "\u039f {entity_name} \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03bc\u03bf\u03bd\u03bf\u03be\u03b5\u03af\u03b4\u03b9\u03bf \u03c4\u03bf\u03c5 \u03ac\u03bd\u03b8\u03c1\u03b1\u03ba\u03b1", + "is_cold": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03ba\u03c1\u03cd\u03bf", + "is_connected": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf", + "is_gas": "{entity_name} \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03b1\u03ad\u03c1\u03b9\u03bf", + "is_hot": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03b6\u03b5\u03c3\u03c4\u03cc", "is_light": "{entity_name} \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03c6\u03c9\u03c2", "is_locked": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03ba\u03bb\u03b5\u03b9\u03b4\u03c9\u03bc\u03ad\u03bd\u03bf", "is_moist": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03c5\u03b3\u03c1\u03cc", @@ -25,9 +30,11 @@ "is_not_moving": "{entity_name} \u03b4\u03b5\u03bd \u03ba\u03b9\u03bd\u03b5\u03af\u03c4\u03b1\u03b9", "is_not_occupied": "{entity_name} \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ba\u03b1\u03c4\u03b5\u03b9\u03bb\u03b7\u03bc\u03bc\u03ad\u03bd\u03bf", "is_not_open": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03ba\u03bb\u03b5\u03b9\u03c3\u03c4\u03cc", + "is_not_plugged_in": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c0\u03bf\u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf", "is_not_powered": "{entity_name} \u03b4\u03b5\u03bd \u03c4\u03c1\u03bf\u03c6\u03bf\u03b4\u03bf\u03c4\u03b5\u03af\u03c4\u03b1\u03b9", "is_not_present": "{entity_name} \u03b4\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9", "is_not_running": "{entity_name} \u03b4\u03b5\u03bd \u03b5\u03ba\u03c4\u03b5\u03bb\u03b5\u03af\u03c4\u03b1\u03b9", + "is_not_tampered": "{entity_name} \u03b4\u03b5\u03bd \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03b6\u03b5\u03b9 \u03c0\u03b1\u03c1\u03b1\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", "is_not_unsafe": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c3\u03c6\u03b1\u03bb\u03ad\u03c2", "is_occupied": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03ba\u03b1\u03c4\u03b5\u03b9\u03bb\u03b7\u03bc\u03bc\u03ad\u03bd\u03bf", "is_off": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf", @@ -49,7 +56,10 @@ "co": "{entity_name} \u03ac\u03c1\u03c7\u03b9\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03bc\u03bf\u03bd\u03bf\u03be\u03b5\u03af\u03b4\u03b9\u03bf \u03c4\u03bf\u03c5 \u03ac\u03bd\u03b8\u03c1\u03b1\u03ba\u03b1", "cold": "{entity_name} \u03ba\u03c1\u03cd\u03c9\u03c3\u03b5", "connected": "{entity_name} \u03c3\u03c5\u03bd\u03b4\u03ad\u03b8\u03b7\u03ba\u03b5", + "gas": "{entity_name} \u03ac\u03c1\u03c7\u03b9\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03b1\u03ad\u03c1\u03b9\u03bf", "hot": "{entity_name} \u03b6\u03b5\u03c3\u03c4\u03ac\u03b8\u03b7\u03ba\u03b5", + "is_not_tampered": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03b6\u03b5\u03b9 \u03c0\u03b1\u03c1\u03b1\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", + "is_tampered": "{entity_name} \u03ac\u03c1\u03c7\u03b9\u03c3\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03b6\u03b5\u03b9 \u03c0\u03b1\u03c1\u03b1\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", "light": "{entity_name} \u03ac\u03c1\u03c7\u03b9\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03c6\u03c9\u03c2", "locked": "{entity_name} \u03ba\u03bb\u03b5\u03b9\u03b4\u03ce\u03b8\u03b7\u03ba\u03b5", "moist": "{entity_name} \u03ad\u03b3\u03b9\u03bd\u03b5 \u03c5\u03b3\u03c1\u03cc", @@ -63,14 +73,35 @@ "no_smoke": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03ba\u03b1\u03c0\u03bd\u03cc", "no_sound": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03ae\u03c7\u03bf", "no_update": "{entity_name} \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5", + "no_vibration": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03b4\u03cc\u03bd\u03b7\u03c3\u03b7", + "not_connected": "{entity_name} \u03b1\u03c0\u03bf\u03c3\u03c5\u03bd\u03b4\u03ad\u03b8\u03b7\u03ba\u03b5", + "not_hot": "{entity_name} \u03ad\u03b3\u03b9\u03bd\u03b5 \u03bc\u03b7 \u03ba\u03b1\u03c5\u03c4\u03cc", + "not_locked": "{entity_name} \u03be\u03b5\u03ba\u03bb\u03b5\u03b9\u03b4\u03ce\u03b8\u03b7\u03ba\u03b5", + "not_moist": "{entity_name} \u03ad\u03b3\u03b9\u03bd\u03b5 \u03be\u03b7\u03c1\u03cc", + "not_moving": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03ba\u03b9\u03bd\u03b5\u03af\u03c4\u03b1\u03b9", + "not_occupied": "{entity_name} \u03ad\u03b3\u03b9\u03bd\u03b5 \u03bc\u03b7 \u03ba\u03b1\u03c4\u03b5\u03b9\u03bb\u03b7\u03bc\u03bc\u03ad\u03bd\u03bf", "not_opened": "{entity_name} \u03ad\u03ba\u03bb\u03b5\u03b9\u03c3\u03b5", + "not_plugged_in": "{entity_name} \u03b1\u03c0\u03bf\u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf", + "not_powered": "{entity_name} \u03b4\u03b5\u03bd \u03c4\u03c1\u03bf\u03c6\u03bf\u03b4\u03bf\u03c4\u03b5\u03af\u03c4\u03b1\u03b9", + "not_present": "{entity_name} \u03b4\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9", "not_running": "{entity_name} \u03b4\u03b5\u03bd \u03b5\u03ba\u03c4\u03b5\u03bb\u03b5\u03af\u03c4\u03b1\u03b9 \u03c0\u03bb\u03ad\u03bf\u03bd", "not_tampered": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03b6\u03b5\u03b9 \u03c0\u03b1\u03c1\u03b1\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", + "not_unsafe": "{entity_name} \u03ad\u03b3\u03b9\u03bd\u03b5 \u03b1\u03c3\u03c6\u03b1\u03bb\u03ad\u03c2", + "occupied": "{entity_name} \u03ad\u03b3\u03b9\u03bd\u03b5 \u03ba\u03b1\u03c4\u03b5\u03b9\u03bb\u03b7\u03bc\u03bc\u03ad\u03bd\u03bf", + "opened": "{entity_name} \u03b1\u03bd\u03bf\u03b9\u03c7\u03c4\u03cc", + "plugged_in": "{entity_name} \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf", + "powered": "{entity_name} \u03c4\u03c1\u03bf\u03c6\u03bf\u03b4\u03bf\u03c4\u03b7\u03bc\u03ad\u03bd\u03bf", + "present": "{entity_name} \u03c0\u03b1\u03c1\u03cc\u03bd", + "problem": "{entity_name} \u03ac\u03c1\u03c7\u03b9\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03c0\u03c1\u03cc\u03b2\u03bb\u03b7\u03bc\u03b1", "running": "{entity_name} \u03ac\u03c1\u03c7\u03b9\u03c3\u03b5 \u03bd\u03b1 \u03b5\u03ba\u03c4\u03b5\u03bb\u03b5\u03af\u03c4\u03b1\u03b9", + "smoke": "{entity_name} \u03ac\u03c1\u03c7\u03b9\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03ba\u03b1\u03c0\u03bd\u03cc", + "sound": "{entity_name} \u03ac\u03c1\u03c7\u03b9\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03ae\u03c7\u03bf", "tampered": "{entity_name} \u03ac\u03c1\u03c7\u03b9\u03c3\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03b6\u03b5\u03b9 \u03c0\u03b1\u03c1\u03b1\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", "turned_off": "{entity_name} \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", "turned_on": "{entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", - "update": "{entity_name} \u03ad\u03bb\u03b1\u03b2\u03b5 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7" + "unsafe": "{entity_name} \u03ad\u03b3\u03b9\u03bd\u03b5 \u03bc\u03b7 \u03b1\u03c3\u03c6\u03b1\u03bb\u03ad\u03c2", + "update": "{entity_name} \u03ad\u03bb\u03b1\u03b2\u03b5 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7", + "vibration": "{entity_name} \u03ac\u03c1\u03c7\u03b9\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03b4\u03cc\u03bd\u03b7\u03c3\u03b7" } }, "device_class": { diff --git a/homeassistant/components/bmw_connected_drive/translations/el.json b/homeassistant/components/bmw_connected_drive/translations/el.json index ff1f06ff6f8d2e..6e3d2669c08377 100644 --- a/homeassistant/components/bmw_connected_drive/translations/el.json +++ b/homeassistant/components/bmw_connected_drive/translations/el.json @@ -3,7 +3,8 @@ "step": { "user": { "data": { - "region": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae ConnectedDrive" + "region": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae ConnectedDrive", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } } diff --git a/homeassistant/components/bond/translations/el.json b/homeassistant/components/bond/translations/el.json index 14bbe0e5dd8b8d..4a6d26cd73d7d6 100644 --- a/homeassistant/components/bond/translations/el.json +++ b/homeassistant/components/bond/translations/el.json @@ -7,6 +7,11 @@ "step": { "confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name};" + }, + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + } } } } diff --git a/homeassistant/components/bosch_shc/translations/el.json b/homeassistant/components/bosch_shc/translations/el.json index 46fecc87e2c0f5..e36f0621cdcc90 100644 --- a/homeassistant/components/bosch_shc/translations/el.json +++ b/homeassistant/components/bosch_shc/translations/el.json @@ -18,6 +18,9 @@ "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 bosch_shc \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2" }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf Bosch Smart Home Controller \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03b5\u03b9 \u03c4\u03b7\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03bc\u03b5 \u03c4\u03bf Home Assistant.", "title": "\u03a0\u03b1\u03c1\u03ac\u03bc\u03b5\u03c4\u03c1\u03bf\u03b9 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 SHC" } diff --git a/homeassistant/components/braviatv/translations/el.json b/homeassistant/components/braviatv/translations/el.json index e73f76235f049c..99acd65a994b93 100644 --- a/homeassistant/components/braviatv/translations/el.json +++ b/homeassistant/components/braviatv/translations/el.json @@ -12,6 +12,9 @@ "title": "\u0395\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7 Sony Bravia TV" }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7\u03c2 Sony Bravia. \u0395\u03ac\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03c0\u03c1\u03bf\u03b2\u03bb\u03ae\u03bc\u03b1\u03c4\u03b1 \u03bc\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7: https://www.home-assistant.io/integrations/braviatv \n\n\u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03b7 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae \u03c3\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7.", "title": "\u03a4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7 Sony Bravia" } diff --git a/homeassistant/components/brunt/translations/el.json b/homeassistant/components/brunt/translations/el.json index 95becd2dc0aa9a..69e1d1f1327404 100644 --- a/homeassistant/components/brunt/translations/el.json +++ b/homeassistant/components/brunt/translations/el.json @@ -5,6 +5,9 @@ "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd: {username}" }, "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "title": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 Brunt" } } diff --git a/homeassistant/components/bsblan/translations/el.json b/homeassistant/components/bsblan/translations/el.json index 3c28dd6ca6a3ee..995c16ed994ce9 100644 --- a/homeassistant/components/bsblan/translations/el.json +++ b/homeassistant/components/bsblan/translations/el.json @@ -7,7 +7,9 @@ "step": { "user": { "data": { - "passkey": "\u03a3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "passkey": "\u03a3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae BSB-Lan \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Home Assistant.", "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae BSB-Lan" diff --git a/homeassistant/components/bsblan/translations/fr.json b/homeassistant/components/bsblan/translations/fr.json index dda5e5c293c142..685cbe686bb76c 100644 --- a/homeassistant/components/bsblan/translations/fr.json +++ b/homeassistant/components/bsblan/translations/fr.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "cannot_connect": "\u00c9chec de connexion" }, "error": { "cannot_connect": "\u00c9chec de connexion" diff --git a/homeassistant/components/button/translations/el.json b/homeassistant/components/button/translations/el.json new file mode 100644 index 00000000000000..9cf028f4c831db --- /dev/null +++ b/homeassistant/components/button/translations/el.json @@ -0,0 +1,10 @@ +{ + "device_automation": { + "action_type": { + "press": "\u03a0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af {entity_name}" + }, + "trigger_type": { + "pressed": "{entity_name} \u03ad\u03c7\u03b5\u03b9 \u03c0\u03b1\u03c4\u03b7\u03b8\u03b5\u03af" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/fr.json b/homeassistant/components/climacell/translations/fr.json index 89e3f43be56db7..38182d67c4acdb 100644 --- a/homeassistant/components/climacell/translations/fr.json +++ b/homeassistant/components/climacell/translations/fr.json @@ -26,7 +26,7 @@ "timestep": "Min. Entre les pr\u00e9visions NowCast" }, "description": "Si vous choisissez d'activer l'entit\u00e9 de pr\u00e9vision \u00abnowcast\u00bb, vous pouvez configurer le nombre de minutes entre chaque pr\u00e9vision. Le nombre de pr\u00e9visions fournies d\u00e9pend du nombre de minutes choisies entre les pr\u00e9visions.", - "title": "Mettre \u00e0 jour les options de ClimaCell" + "title": "Mettre \u00e0 jour les options ClimaCell" } } }, diff --git a/homeassistant/components/climacell/translations/sensor.fr.json b/homeassistant/components/climacell/translations/sensor.fr.json index e5118e6f9eeecb..95625c2b8da680 100644 --- a/homeassistant/components/climacell/translations/sensor.fr.json +++ b/homeassistant/components/climacell/translations/sensor.fr.json @@ -5,6 +5,7 @@ "hazardous": "Hasardeux", "moderate": "Mod\u00e9r\u00e9", "unhealthy": "Mauvais pour la sant\u00e9", + "unhealthy_for_sensitive_groups": "Mauvaise qualit\u00e9 pour les groupes sensibles", "very_unhealthy": "Tr\u00e8s mauvais pour la sant\u00e9" }, "climacell__pollen_index": { diff --git a/homeassistant/components/coinbase/translations/fr.json b/homeassistant/components/coinbase/translations/fr.json index 3feddeb7cd0de7..74ee4c61f97970 100644 --- a/homeassistant/components/coinbase/translations/fr.json +++ b/homeassistant/components/coinbase/translations/fr.json @@ -25,7 +25,9 @@ }, "options": { "error": { + "currency_unavailable": "Un ou plusieurs des soldes de devises demand\u00e9s ne sont pas fournis par votre API Coinbase.", "currency_unavaliable": "Un ou plusieurs des soldes de devises demand\u00e9s ne sont pas fournis par votre API Coinbase.", + "exchange_rate_unavailable": "Un ou plusieurs des taux de change demand\u00e9s ne sont pas fournis par Coinbase.", "exchange_rate_unavaliable": "Un ou plusieurs des taux de change demand\u00e9s ne sont pas fournis par Coinbase.", "unknown": "Erreur inattendue" }, diff --git a/homeassistant/components/dlna_dmr/translations/el.json b/homeassistant/components/dlna_dmr/translations/el.json index 8d71dd3070eb41..72b454e4e0bd52 100644 --- a/homeassistant/components/dlna_dmr/translations/el.json +++ b/homeassistant/components/dlna_dmr/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "alternative_integration": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03ba\u03b1\u03bb\u03cd\u03c4\u03b5\u03c1\u03b1 \u03b1\u03c0\u03cc \u03ac\u03bb\u03bb\u03b7 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7", "could_not_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03bc\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae DLNA", "discovery_error": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7\u03c2 \u03bc\u03b9\u03b1\u03c2 \u03b1\u03bd\u03c4\u03af\u03c3\u03c4\u03bf\u03b9\u03c7\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 DLNA", "incomplete_config": "\u0391\u03c0\u03cc \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03bb\u03b5\u03af\u03c0\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7 \u03bc\u03b5\u03c4\u03b1\u03b2\u03bb\u03b7\u03c4\u03ae", @@ -16,7 +17,14 @@ "import_turn_on": { "description": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ba\u03b1\u03b9 \u03ba\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03b7\u03bd \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03a5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03bc\u03b5\u03c4\u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7" }, + "manual": { + "description": "URL \u03c3\u03b5 \u03ad\u03bd\u03b1 \u03b1\u03c1\u03c7\u03b5\u03af\u03bf XML \u03c0\u03b5\u03c1\u03b9\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", + "title": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 DLNA DMR" + }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b3\u03b9\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03ae \u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03b5\u03bd\u03ae \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 DLNA DMR" } diff --git a/homeassistant/components/dnsip/translations/fr.json b/homeassistant/components/dnsip/translations/fr.json index fb3e4a5f6ab4e2..ae6da0296c25cf 100644 --- a/homeassistant/components/dnsip/translations/fr.json +++ b/homeassistant/components/dnsip/translations/fr.json @@ -6,7 +6,9 @@ "step": { "user": { "data": { - "hostname": "Le nom d'h\u00f4te pour lequel la requ\u00eate DNS doit \u00eatre effectu\u00e9e." + "hostname": "Le nom d'h\u00f4te pour lequel la requ\u00eate DNS doit \u00eatre effectu\u00e9e.", + "resolver": "R\u00e9solveur pour la recherche IPV4", + "resolver_ipv6": "R\u00e9solveur pour la recherche IPV6" } } } diff --git a/homeassistant/components/doorbird/translations/el.json b/homeassistant/components/doorbird/translations/el.json index 2f023dc3050199..822a75ad7c77f5 100644 --- a/homeassistant/components/doorbird/translations/el.json +++ b/homeassistant/components/doorbird/translations/el.json @@ -12,6 +12,7 @@ "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf DoorBird" diff --git a/homeassistant/components/dsmr/translations/el.json b/homeassistant/components/dsmr/translations/el.json index c576116efb27e8..bb8a94df0cbc72 100644 --- a/homeassistant/components/dsmr/translations/el.json +++ b/homeassistant/components/dsmr/translations/el.json @@ -9,7 +9,8 @@ "step": { "setup_network": { "data": { - "dsmr_version": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7\u03c2 DSMR" + "dsmr_version": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7\u03c2 DSMR", + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" }, "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, diff --git a/homeassistant/components/dunehd/translations/el.json b/homeassistant/components/dunehd/translations/el.json index 3210d36f1c8473..92b697b7f0064d 100644 --- a/homeassistant/components/dunehd/translations/el.json +++ b/homeassistant/components/dunehd/translations/el.json @@ -2,6 +2,9 @@ "config": { "step": { "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Dune HD. \u0391\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03c0\u03c1\u03bf\u03b2\u03bb\u03ae\u03bc\u03b1\u03c4\u03b1 \u03bc\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c0\u03b7\u03b3\u03b1\u03af\u03bd\u03b5\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7: https://www.home-assistant.io/integrations/dunehd \n\n\u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7.", "title": "Dune HD" } diff --git a/homeassistant/components/elkm1/translations/el.json b/homeassistant/components/elkm1/translations/el.json index 8e2990982d62b6..46d31db307a7a2 100644 --- a/homeassistant/components/elkm1/translations/el.json +++ b/homeassistant/components/elkm1/translations/el.json @@ -7,6 +7,9 @@ "flow_title": "{mac_address} ({host})", "step": { "discovered_connection": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03c3\u03cd\u03c3\u03c4\u03b7\u03bc\u03b1 \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5: {mac_address} ({host})" }, "manual_connection": { @@ -14,7 +17,8 @@ "address": "\u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03ae \u03bf \u03c4\u03bf\u03bc\u03ad\u03b1\u03c2 \u03ae \u03b7 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1 \u03b5\u03ac\u03bd \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03b3\u03af\u03bd\u03b5\u03c4\u03b1\u03b9 \u03bc\u03ad\u03c3\u03c9 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2.", "prefix": "\u0388\u03bd\u03b1 \u03bc\u03bf\u03bd\u03b1\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b1\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03bc\u03cc\u03bd\u03bf \u03ad\u03bd\u03b1 ElkM1).", "protocol": "\u03a0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf", - "temperature_unit": "\u0397 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03c4\u03bf ElkM1." + "temperature_unit": "\u0397 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03c4\u03bf ElkM1.", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0397 \u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae \u00abaddress[:port]\u00bb \u03b3\u03b9\u03b1 \u00absecure\u00bb \u03ba\u03b1\u03b9 \u00abnon-secure\u00bb. \u03a0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3\u03bc\u03b1: '192.168.1.1'. \u0397 \u03b8\u03cd\u03c1\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ae \u03ba\u03b1\u03b9 \u03ad\u03c7\u03b5\u03b9 \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03c9\u03c2 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c3\u03b5 2101 \u03b3\u03b9\u03b1 \"non-secure\" \u03ba\u03b1\u03b9 2601 \u03b3\u03b9\u03b1 \"secure\". \u0393\u03b9\u03b1 \u03c4\u03bf \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc \u03c0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf, \u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae 'tty[:baud]'. \u03a0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3\u03bc\u03b1: '/dev/ttyS1'. \u03a4\u03bf baud \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03ba\u03b1\u03b9 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03b5\u03af\u03bd\u03b1\u03b9 115200." }, diff --git a/homeassistant/components/elkm1/translations/fr.json b/homeassistant/components/elkm1/translations/fr.json index 05560930fc3325..87193a9adf7d83 100644 --- a/homeassistant/components/elkm1/translations/fr.json +++ b/homeassistant/components/elkm1/translations/fr.json @@ -2,7 +2,9 @@ "config": { "abort": { "address_already_configured": "Un ElkM1 avec cette adresse est d\u00e9j\u00e0 configur\u00e9", - "already_configured": "Un ElkM1 avec ce pr\u00e9fixe est d\u00e9j\u00e0 configur\u00e9" + "already_configured": "Un ElkM1 avec ce pr\u00e9fixe est d\u00e9j\u00e0 configur\u00e9", + "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", + "cannot_connect": "\u00c9chec de connexion" }, "error": { "cannot_connect": "\u00c9chec de connexion", @@ -15,9 +17,11 @@ "data": { "password": "Mot de passe", "protocol": "Protocole", + "temperature_unit": "L'unit\u00e9 de temp\u00e9rature utilis\u00e9e par ElkM1.", "username": "Nom d'utilisateur" }, - "description": "Connectez-vous au syst\u00e8me d\u00e9couvert : {mac_address} ({host})" + "description": "Connectez-vous au syst\u00e8me d\u00e9couvert : {mac_address} ({host})", + "title": "Se connecter a Elk-M1 Control" }, "manual_connection": { "data": { @@ -27,7 +31,9 @@ "protocol": "Protocole", "temperature_unit": "L'unit\u00e9 de temp\u00e9rature utilis\u00e9e par ElkM1.", "username": "Nom d'utilisateur" - } + }, + "description": "La cha\u00eene d'adresse doit \u00eatre au format 'adresse[:port]' pour 's\u00e9curis\u00e9' et 'non s\u00e9curis\u00e9'. Exemple : '192.168.1.1'. Le port est facultatif et sa valeur par d\u00e9faut est 2101 pour \"non s\u00e9curis\u00e9\" et 2601 pour \"s\u00e9curis\u00e9\". Pour le protocole s\u00e9rie, l'adresse doit \u00eatre sous la forme 'tty[:baud]'. Exemple : '/dev/ttyS1'. Le baud est facultatif et sa valeur par d\u00e9faut est 115200.", + "title": "Se connecter a Elk-M1 Control" }, "user": { "data": { @@ -39,7 +45,7 @@ "temperature_unit": "L'unit\u00e9 de temp\u00e9rature utilis\u00e9e par ElkM1.", "username": "Nom d'utilisateur" }, - "description": "La cha\u00eene d'adresse doit \u00eatre au format \u00abadresse [: port]\u00bb pour \u00abs\u00e9curis\u00e9\u00bb et \u00abnon s\u00e9curis\u00e9\u00bb. Exemple: '192.168.1.1'. Le port est facultatif et vaut par d\u00e9faut 2101 pour \u00abnon s\u00e9curis\u00e9\u00bb et 2601 pour \u00abs\u00e9curis\u00e9\u00bb. Pour le protocole s\u00e9rie, l'adresse doit \u00eatre au format \u00abtty [: baud]\u00bb. Exemple: '/ dev / ttyS1'. Le baud est facultatif et par d\u00e9faut \u00e0 115200.", + "description": "Choisissez un syst\u00e8me d\u00e9couvert ou 'Entr\u00e9e manuelle' si aucun appareil n'a \u00e9t\u00e9 d\u00e9couvert.", "title": "Se connecter a Elk-M1 Control" } } diff --git a/homeassistant/components/elmax/translations/el.json b/homeassistant/components/elmax/translations/el.json index 34c3a0340b6bbb..3cdd4500930eb2 100644 --- a/homeassistant/components/elmax/translations/el.json +++ b/homeassistant/components/elmax/translations/el.json @@ -17,6 +17,9 @@ "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c0\u03af\u03bd\u03b1\u03ba\u03b1" }, "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf cloud \u03c4\u03b7\u03c2 Elmax \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ce\u03bd\u03c4\u03b1\u03c2 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd" } diff --git a/homeassistant/components/emonitor/translations/el.json b/homeassistant/components/emonitor/translations/el.json index 8da0b8dbd4e7a2..436a00cbfda444 100644 --- a/homeassistant/components/emonitor/translations/el.json +++ b/homeassistant/components/emonitor/translations/el.json @@ -5,6 +5,11 @@ "confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ({host});", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 SiteSage Emonitor" + }, + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + } } } } diff --git a/homeassistant/components/enphase_envoy/translations/el.json b/homeassistant/components/enphase_envoy/translations/el.json index f9852dcb28646e..c467239ea82f39 100644 --- a/homeassistant/components/enphase_envoy/translations/el.json +++ b/homeassistant/components/enphase_envoy/translations/el.json @@ -3,6 +3,10 @@ "flow_title": "{serial} ({host})", "step": { "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "description": "\u0393\u03b9\u03b1 \u03bd\u03b5\u03cc\u03c4\u03b5\u03c1\u03b1 \u03bc\u03bf\u03bd\u03c4\u03ad\u03bb\u03b1, \u03c0\u03bb\u03b7\u03ba\u03c4\u03c1\u03bf\u03bb\u03bf\u03b3\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 `envoy` \u03c7\u03c9\u03c1\u03af\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2. \u0393\u03b9\u03b1 \u03c0\u03b1\u03bb\u03b1\u03b9\u03cc\u03c4\u03b5\u03c1\u03b1 \u03bc\u03bf\u03bd\u03c4\u03ad\u03bb\u03b1, \u03c0\u03bb\u03b7\u03ba\u03c4\u03c1\u03bf\u03bb\u03bf\u03b3\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 `installer` \u03c7\u03c9\u03c1\u03af\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2. \u0393\u03b9\u03b1 \u03cc\u03bb\u03b1 \u03c4\u03b1 \u03ac\u03bb\u03bb\u03b1 \u03bc\u03bf\u03bd\u03c4\u03ad\u03bb\u03b1, \u03c0\u03bb\u03b7\u03ba\u03c4\u03c1\u03bf\u03bb\u03bf\u03b3\u03ae\u03c3\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2." } } diff --git a/homeassistant/components/epson/translations/el.json b/homeassistant/components/epson/translations/el.json index 12bff6f7b6c380..85731fbc8cfd53 100644 --- a/homeassistant/components/epson/translations/el.json +++ b/homeassistant/components/epson/translations/el.json @@ -2,6 +2,13 @@ "config": { "error": { "powered_off": "\u0395\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf\u03c2 \u03bf \u03c0\u03c1\u03bf\u03b2\u03bf\u03bb\u03ad\u03b1\u03c2; \u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b2\u03b9\u03bd\u03c4\u03b5\u03bf\u03c0\u03c1\u03bf\u03b2\u03bf\u03bb\u03ad\u03b1 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b1\u03c1\u03c7\u03b9\u03ba\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7." + }, + "step": { + "user": { + "data": { + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/evil_genius_labs/translations/el.json b/homeassistant/components/evil_genius_labs/translations/el.json new file mode 100644 index 00000000000000..b1115d5bf2c1e6 --- /dev/null +++ b/homeassistant/components/evil_genius_labs/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fireservicerota/translations/el.json b/homeassistant/components/fireservicerota/translations/el.json index 64eaa4c7733f50..3e8ec8ece2aab0 100644 --- a/homeassistant/components/fireservicerota/translations/el.json +++ b/homeassistant/components/fireservicerota/translations/el.json @@ -6,7 +6,8 @@ }, "user": { "data": { - "url": "\u0399\u03c3\u03c4\u03bf\u03c3\u03b5\u03bb\u03af\u03b4\u03b1" + "url": "\u0399\u03c3\u03c4\u03bf\u03c3\u03b5\u03bb\u03af\u03b4\u03b1", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } } diff --git a/homeassistant/components/fivem/translations/el.json b/homeassistant/components/fivem/translations/el.json index 760a3a91365753..9fad6c40d948d4 100644 --- a/homeassistant/components/fivem/translations/el.json +++ b/homeassistant/components/fivem/translations/el.json @@ -4,6 +4,14 @@ "cannot_connect": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03b8\u03cd\u03c1\u03b1 \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac. \u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03b5\u03c0\u03af\u03c3\u03b7\u03c2 \u03cc\u03c4\u03b9 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c0\u03b9\u03bf \u03c0\u03c1\u03cc\u03c3\u03c6\u03b1\u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae FiveM.", "invalid_game_name": "\u03a4\u03bf api \u03c4\u03bf\u03c5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd \u03c3\u03c4\u03bf \u03bf\u03c0\u03bf\u03af\u03bf \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9 FiveM.", "invalid_gamename": "\u03a4\u03bf api \u03c4\u03bf\u03c5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd \u03c3\u03c4\u03bf \u03bf\u03c0\u03bf\u03af\u03bf \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9 FiveM." + }, + "step": { + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/fr.json b/homeassistant/components/fivem/translations/fr.json index dd801a69f33e22..4cd16be65a36d9 100644 --- a/homeassistant/components/fivem/translations/fr.json +++ b/homeassistant/components/fivem/translations/fr.json @@ -5,6 +5,7 @@ }, "error": { "cannot_connect": "\u00c9chec de connexion. Veuillez v\u00e9rifier l'h\u00f4te et le port et r\u00e9essayer. Assurez-vous \u00e9galement que vous utilisez le dernier serveur FiveM.", + "invalid_game_name": "L'API du jeu auquel vous essayez de vous connecter n'est pas un jeu FiveM.", "invalid_gamename": "L\u2019API du jeu auquel vous essayez de vous connecter n\u2019est pas un jeu FiveM.", "unknown_error": "Erreur inattendue" }, diff --git a/homeassistant/components/flo/translations/el.json b/homeassistant/components/flo/translations/el.json new file mode 100644 index 00000000000000..18c2f0869bd1c2 --- /dev/null +++ b/homeassistant/components/flo/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flux_led/translations/el.json b/homeassistant/components/flux_led/translations/el.json index 8471b3b5f11cb8..4c5dd46faf7661 100644 --- a/homeassistant/components/flux_led/translations/el.json +++ b/homeassistant/components/flux_led/translations/el.json @@ -6,6 +6,9 @@ "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {model} {id} ({ipaddr});" }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u0391\u03bd \u03b1\u03c6\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ba\u03b5\u03bd\u03cc, \u03b7 \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7 \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd." } } diff --git a/homeassistant/components/forked_daapd/translations/el.json b/homeassistant/components/forked_daapd/translations/el.json index dd01e64ac64c37..9a87960381a538 100644 --- a/homeassistant/components/forked_daapd/translations/el.json +++ b/homeassistant/components/forked_daapd/translations/el.json @@ -14,6 +14,7 @@ "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "name": "\u03a6\u03b9\u03bb\u03b9\u03ba\u03cc \u03cc\u03bd\u03bf\u03bc\u03b1", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 API (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03cc \u03b5\u03ac\u03bd \u03b4\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2)", "port": "\u0398\u03cd\u03c1\u03b1 API" diff --git a/homeassistant/components/foscam/translations/el.json b/homeassistant/components/foscam/translations/el.json index 7022315d9c4900..43fdcaa476480c 100644 --- a/homeassistant/components/foscam/translations/el.json +++ b/homeassistant/components/foscam/translations/el.json @@ -6,8 +6,10 @@ "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "rtsp_port": "\u0398\u03cd\u03c1\u03b1 RTSP", - "stream": "\u03a1\u03bf\u03ae" + "stream": "\u03a1\u03bf\u03ae", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } } diff --git a/homeassistant/components/fritz/translations/el.json b/homeassistant/components/fritz/translations/el.json index 8ff8b1324c4672..86343321e1858a 100644 --- a/homeassistant/components/fritz/translations/el.json +++ b/homeassistant/components/fritz/translations/el.json @@ -11,10 +11,16 @@ "title": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 FRITZ!Box Tools - \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1" }, "start_config": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf FRITZ!Box Tools \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03b5\u03c4\u03b5 \u03c4\u03bf FRITZ!Box \u03c3\u03b1\u03c2.\n \u0395\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03bf: \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7, \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2.", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 FRITZ!Box Tools - \u03c5\u03c0\u03bf\u03c7\u03c1\u03b5\u03c9\u03c4\u03b9\u03ba\u03cc" }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf FRITZ!Box Tools \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03b5\u03c4\u03b5 \u03c4\u03bf FRITZ!Box \u03c3\u03b1\u03c2.\n \u0395\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03bf: \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7, \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2.", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 FRITZ!Box Tools" } diff --git a/homeassistant/components/fritzbox/translations/el.json b/homeassistant/components/fritzbox/translations/el.json index f3bd4c2d4c0006..21db3ef18e0c0e 100644 --- a/homeassistant/components/fritzbox/translations/el.json +++ b/homeassistant/components/fritzbox/translations/el.json @@ -9,9 +9,15 @@ "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name};" }, "reauth_confirm": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "description": "\u0395\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {name}." }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf AVM FRITZ!Box." } } diff --git a/homeassistant/components/fritzbox_callmonitor/translations/el.json b/homeassistant/components/fritzbox_callmonitor/translations/el.json index d6841e0f81b6c8..62d94fbb68b97c 100644 --- a/homeassistant/components/fritzbox_callmonitor/translations/el.json +++ b/homeassistant/components/fritzbox_callmonitor/translations/el.json @@ -9,6 +9,12 @@ "data": { "phonebook": "\u03a4\u03b7\u03bb\u03b5\u03c6\u03c9\u03bd\u03b9\u03ba\u03cc\u03c2 \u03ba\u03b1\u03c4\u03ac\u03bb\u03bf\u03b3\u03bf\u03c2" } + }, + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } } } }, diff --git a/homeassistant/components/fronius/translations/el.json b/homeassistant/components/fronius/translations/el.json index 1f22f20b3a2ead..611a3ebce8e55c 100644 --- a/homeassistant/components/fronius/translations/el.json +++ b/homeassistant/components/fronius/translations/el.json @@ -6,6 +6,9 @@ "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {device} \u03c3\u03c4\u03bf Home Assistant;" }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u0394\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03ae \u03c4\u03bf \u03c4\u03bf\u03c0\u03b9\u03ba\u03cc \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 Fronius.", "title": "Fronius SolarNet" } diff --git a/homeassistant/components/github/translations/fr.json b/homeassistant/components/github/translations/fr.json index 8a9f2a08ba4acf..d159c86efaab94 100644 --- a/homeassistant/components/github/translations/fr.json +++ b/homeassistant/components/github/translations/fr.json @@ -6,6 +6,14 @@ }, "progress": { "wait_for_device": "1. Ouvrez {url}\n2. Collez la cl\u00e9 suivante pour autoriser l'int\u00e9gration\u00a0:\n ```\n {code}\n ```\n" + }, + "step": { + "repositories": { + "data": { + "repositories": "S\u00e9lectionnez les r\u00e9f\u00e9rentiels \u00e0 suivre." + }, + "title": "Configurer les r\u00e9f\u00e9rentiels" + } } } } \ No newline at end of file diff --git a/homeassistant/components/glances/translations/el.json b/homeassistant/components/glances/translations/el.json index c6bb137e571438..4b2d98674c5062 100644 --- a/homeassistant/components/glances/translations/el.json +++ b/homeassistant/components/glances/translations/el.json @@ -6,6 +6,7 @@ "step": { "user": { "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", "version": "\u0388\u03ba\u03b4\u03bf\u03c3\u03b7 API Glances (2 \u03ae 3)" }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 Glances" diff --git a/homeassistant/components/goodwe/translations/fr.json b/homeassistant/components/goodwe/translations/fr.json index 8e5504b3ee4963..544d6cfda68965 100644 --- a/homeassistant/components/goodwe/translations/fr.json +++ b/homeassistant/components/goodwe/translations/fr.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours" }, "error": { "connection_error": "Impossible de se connecter" @@ -11,7 +12,8 @@ "data": { "host": "Adresse IP" }, - "description": "Connecter \u00e0 l'onduleur" + "description": "Connecter \u00e0 l'onduleur", + "title": "Onduleur GoodWe" } } } diff --git a/homeassistant/components/google_travel_time/translations/el.json b/homeassistant/components/google_travel_time/translations/el.json index 5d5d0dcf9a5793..6d14401f970329 100644 --- a/homeassistant/components/google_travel_time/translations/el.json +++ b/homeassistant/components/google_travel_time/translations/el.json @@ -4,6 +4,7 @@ "user": { "data": { "destination": "\u03a0\u03c1\u03bf\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "origin": "\u03a0\u03c1\u03bf\u03ad\u03bb\u03b5\u03c5\u03c3\u03b7" }, "description": "\u038c\u03c4\u03b1\u03bd \u03ba\u03b1\u03b8\u03bf\u03c1\u03af\u03b6\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03ad\u03bb\u03b5\u03c5\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03c0\u03c1\u03bf\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc, \u03bc\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03ce\u03c3\u03b5\u03c4\u03b5 \u03bc\u03af\u03b1 \u03ae \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b5\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c9\u03c1\u03af\u03b6\u03bf\u03bd\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03c4\u03bf \u03c3\u03cd\u03bc\u03b2\u03bf\u03bb\u03bf pipe (\"|\"), \u03bc\u03b5 \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2, \u03c3\u03c5\u03bd\u03c4\u03b5\u03c4\u03b1\u03b3\u03bc\u03ad\u03bd\u03c9\u03bd \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03bf\u03cd \u03c0\u03bb\u03ac\u03c4\u03bf\u03c5\u03c2/\u03bc\u03ae\u03ba\u03bf\u03c5\u03c2 \u03ae \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03bf\u03cd \u03c4\u03cc\u03c0\u03bf\u03c5 Google. \u038c\u03c4\u03b1\u03bd \u03c0\u03c1\u03bf\u03c3\u03b4\u03b9\u03bf\u03c1\u03af\u03b6\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ce\u03bd\u03c4\u03b1\u03c2 \u03ad\u03bd\u03b1 \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03cc\u03c0\u03bf\u03c5 Google, \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03c9\u03c2 \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 \u03c4\u03bf `place_id:`." diff --git a/homeassistant/components/hlk_sw16/translations/el.json b/homeassistant/components/hlk_sw16/translations/el.json new file mode 100644 index 00000000000000..18c2f0869bd1c2 --- /dev/null +++ b/homeassistant/components/hlk_sw16/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/fr.json b/homeassistant/components/homekit/translations/fr.json index 6c4a3e36836ff4..7de4c531d6aa43 100644 --- a/homeassistant/components/homekit/translations/fr.json +++ b/homeassistant/components/homekit/translations/fr.json @@ -22,7 +22,8 @@ "accessory": { "data": { "entities": "Entit\u00e9" - } + }, + "title": "S\u00e9lectionnez l'entit\u00e9 pour l'accessoire" }, "advanced": { "data": { @@ -44,6 +45,7 @@ "data": { "entities": "Entit\u00e9s" }, + "description": "Toutes les entit\u00e9s \u00ab {domains}\u00a0\u00bb seront incluses, \u00e0 l'exception des entit\u00e9s exclues et des entit\u00e9s cat\u00e9goris\u00e9es.", "title": "S\u00e9lectionnez les entit\u00e9s \u00e0 exclure" }, "include": { @@ -66,10 +68,10 @@ "domains": "Domaines \u00e0 inclure", "include_domains": "Domaines \u00e0 inclure", "include_exclude_mode": "Mode d'inclusion", - "mode": "Mode" + "mode": "Mode HomeKit" }, "description": "Les entit\u00e9s des \u00abdomaines \u00e0 inclure\u00bb seront pont\u00e9es vers HomeKit. Vous pourrez s\u00e9lectionner les entit\u00e9s \u00e0 exclure de cette liste sur l'\u00e9cran suivant.", - "title": "S\u00e9lectionnez les domaines \u00e0 relier." + "title": "S\u00e9lectionnez le mode et les domaines." }, "yaml": { "description": "Cette entr\u00e9e est contr\u00f4l\u00e9e via YAML", diff --git a/homeassistant/components/huawei_lte/translations/el.json b/homeassistant/components/huawei_lte/translations/el.json index 7fdd2f6c2c5e32..34e61e0a1ae30c 100644 --- a/homeassistant/components/huawei_lte/translations/el.json +++ b/homeassistant/components/huawei_lte/translations/el.json @@ -16,6 +16,9 @@ "flow_title": "{name}", "step": { "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2.", "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Huawei LTE" } diff --git a/homeassistant/components/hue/translations/el.json b/homeassistant/components/hue/translations/el.json index e76e50d1cafd1a..7da30f4a1be8e1 100644 --- a/homeassistant/components/hue/translations/el.json +++ b/homeassistant/components/hue/translations/el.json @@ -11,11 +11,19 @@ }, "step": { "init": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03b3\u03ad\u03c6\u03c5\u03c1\u03b1 Hue" }, "link": { "description": "\u03a0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03c3\u03c4\u03b7 \u03b3\u03ad\u03c6\u03c5\u03c1\u03b1 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c7\u03c9\u03c1\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Philips Hue \u03bc\u03b5 \u03c4\u03bf Home Assistant. \n\n ![\u03a4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ba\u03bf\u03c5\u03bc\u03c0\u03b9\u03bf\u03cd \u03c3\u03c4\u03b7 \u03b3\u03ad\u03c6\u03c5\u03c1\u03b1](/static/images/config_philips_hue.jpg)", "title": "\u039a\u03cc\u03bc\u03b2\u03bf\u03c2 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c9\u03bd" + }, + "manual": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + } } } }, diff --git a/homeassistant/components/huisbaasje/translations/el.json b/homeassistant/components/huisbaasje/translations/el.json new file mode 100644 index 00000000000000..18c2f0869bd1c2 --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/humidifier/translations/fr.json b/homeassistant/components/humidifier/translations/fr.json index 7f409ca75d65bb..3b1b60ebae3399 100644 --- a/homeassistant/components/humidifier/translations/fr.json +++ b/homeassistant/components/humidifier/translations/fr.json @@ -13,6 +13,7 @@ "is_on": "{entity_name} est activ\u00e9" }, "trigger_type": { + "changed_states": "{entity_name} activ\u00e9 ou d\u00e9sactiv\u00e9", "target_humidity_changed": "{nom_de_l'entit\u00e9} changement de l'humidit\u00e9 cible", "toggled": "{entity_name} activ\u00e9 ou d\u00e9sactiv\u00e9", "turned_off": "{entity_name} s'est \u00e9teint", diff --git a/homeassistant/components/hvv_departures/translations/el.json b/homeassistant/components/hvv_departures/translations/el.json index f974693fd2a8cd..00d9a26488325b 100644 --- a/homeassistant/components/hvv_departures/translations/el.json +++ b/homeassistant/components/hvv_departures/translations/el.json @@ -17,6 +17,9 @@ "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b1\u03b8\u03bc\u03cc/\u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7" }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf HVV API" } } diff --git a/homeassistant/components/ialarm/translations/el.json b/homeassistant/components/ialarm/translations/el.json new file mode 100644 index 00000000000000..b1115d5bf2c1e6 --- /dev/null +++ b/homeassistant/components/ialarm/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iaqualink/translations/el.json b/homeassistant/components/iaqualink/translations/el.json index d6deb0e84c655d..765a9f5d3b68cc 100644 --- a/homeassistant/components/iaqualink/translations/el.json +++ b/homeassistant/components/iaqualink/translations/el.json @@ -2,6 +2,9 @@ "config": { "step": { "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf iAqualink.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf iAqualink" } diff --git a/homeassistant/components/insteon/translations/el.json b/homeassistant/components/insteon/translations/el.json index 36320b286063d0..30d1c5e01e7041 100644 --- a/homeassistant/components/insteon/translations/el.json +++ b/homeassistant/components/insteon/translations/el.json @@ -15,7 +15,8 @@ "data": { "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "port": "\u0398\u03cd\u03c1\u03b1" + "port": "\u0398\u03cd\u03c1\u03b1", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd \u03c4\u03bf\u03c5 Insteon Hub Version 2.", "title": "Insteon Hub Version 2" @@ -59,6 +60,9 @@ "title": "Insteon" }, "change_hub_config": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "description": "\u0391\u03bb\u03bb\u03ac\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 Insteon Hub. \u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Home Assistant \u03bc\u03b5\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03b1\u03b3\u03bc\u03b1\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b1\u03c5\u03c4\u03ae\u03c2 \u03c4\u03b7\u03c2 \u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2. \u0391\u03c5\u03c4\u03cc \u03b4\u03b5\u03bd \u03b1\u03bb\u03bb\u03ac\u03b6\u03b5\u03b9 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03af\u03b4\u03b9\u03bf\u03c5 \u03c4\u03bf\u03c5 Hub. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03bb\u03bb\u03ac\u03be\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c3\u03c4\u03bf Hub \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae Hub.", "title": "Insteon" }, diff --git a/homeassistant/components/intellifire/translations/el.json b/homeassistant/components/intellifire/translations/el.json new file mode 100644 index 00000000000000..b1115d5bf2c1e6 --- /dev/null +++ b/homeassistant/components/intellifire/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/intellifire/translations/fr.json b/homeassistant/components/intellifire/translations/fr.json index e019c6ac5ef12d..88a0aeb68c8853 100644 --- a/homeassistant/components/intellifire/translations/fr.json +++ b/homeassistant/components/intellifire/translations/fr.json @@ -4,6 +4,7 @@ "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" }, "error": { + "cannot_connect": "\u00c9chec de connexion", "unknown": "L'appareil est d\u00e9j\u00e0 configur\u00e9" }, "step": { diff --git a/homeassistant/components/ipp/translations/el.json b/homeassistant/components/ipp/translations/el.json index ea4684299247a7..3fd7a86e99dceb 100644 --- a/homeassistant/components/ipp/translations/el.json +++ b/homeassistant/components/ipp/translations/el.json @@ -12,7 +12,8 @@ "step": { "user": { "data": { - "base_path": "\u03a3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ae \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae" + "base_path": "\u03a3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ae \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae", + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae \u03c3\u03b1\u03c2 \u03bc\u03ad\u03c3\u03c9 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03c9\u03c4\u03bf\u03ba\u03cc\u03bb\u03bb\u03bf\u03c5 \u03b5\u03ba\u03c4\u03cd\u03c0\u03c9\u03c3\u03b7\u03c2 Internet (IPP) \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Home Assistant.", "title": "\u03a3\u03c5\u03bd\u03b4\u03ad\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae \u03c3\u03b1\u03c2" diff --git a/homeassistant/components/iss/translations/fr.json b/homeassistant/components/iss/translations/fr.json new file mode 100644 index 00000000000000..4dee8082dbfd84 --- /dev/null +++ b/homeassistant/components/iss/translations/fr.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "latitude_longitude_not_defined": "La latitude et la longitude ne sont pas d\u00e9finies dans Home Assistant.", + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." + }, + "step": { + "user": { + "data": { + "show_on_map": "Afficher sur la carte?" + }, + "description": "Voulez-vous configurer la Station spatiale internationale?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/jellyfin/translations/el.json b/homeassistant/components/jellyfin/translations/el.json new file mode 100644 index 00000000000000..18c2f0869bd1c2 --- /dev/null +++ b/homeassistant/components/jellyfin/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/keenetic_ndms2/translations/el.json b/homeassistant/components/keenetic_ndms2/translations/el.json index d00e805ad1d527..99d75f26cc0112 100644 --- a/homeassistant/components/keenetic_ndms2/translations/el.json +++ b/homeassistant/components/keenetic_ndms2/translations/el.json @@ -1,7 +1,16 @@ { "config": { + "abort": { + "no_udn": "\u039f\u03b9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7\u03c2 SSDP \u03b4\u03b5\u03bd \u03ad\u03c7\u03bf\u03c5\u03bd UDN", + "not_keenetic_ndms2": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae\u03c2 Keenetic" + }, + "flow_title": "{name} ({host})", "step": { "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae Keenetic NDMS2" } } diff --git a/homeassistant/components/kmtronic/translations/el.json b/homeassistant/components/kmtronic/translations/el.json index 53d33e27e8fa79..c6929ecbbf2330 100644 --- a/homeassistant/components/kmtronic/translations/el.json +++ b/homeassistant/components/kmtronic/translations/el.json @@ -1,4 +1,14 @@ { + "config": { + "step": { + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } + } + } + }, "options": { "step": { "init": { diff --git a/homeassistant/components/knx/translations/el.json b/homeassistant/components/knx/translations/el.json index ea7fe194fefdcd..f08f75f2da9bfc 100644 --- a/homeassistant/components/knx/translations/el.json +++ b/homeassistant/components/knx/translations/el.json @@ -3,6 +3,7 @@ "step": { "manual_tunnel": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "individual_address": "\u0391\u03c4\u03bf\u03bc\u03b9\u03ba\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7", "local_ip": "\u03a4\u03bf\u03c0\u03b9\u03ba\u03ae IP \u03c4\u03bf\u03c5 Home Assistant (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03b5\u03bd\u03ae \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7)", "route_back": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 Route Back / NAT", @@ -48,6 +49,7 @@ }, "tunnel": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "local_ip": "\u03a4\u03bf\u03c0\u03b9\u03ba\u03ae IP (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03b5\u03bd\u03ae \u03b1\u03bd \u03b4\u03b5\u03bd \u03b5\u03af\u03c3\u03c4\u03b5 \u03c3\u03af\u03b3\u03bf\u03c5\u03c1\u03bf\u03b9)", "route_back": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 Route Back / NAT", "tunneling_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03ae\u03c1\u03b1\u03b3\u03b3\u03b1\u03c2 KNX" diff --git a/homeassistant/components/kodi/translations/el.json b/homeassistant/components/kodi/translations/el.json index 805490698bb9ce..d037c2e07fd1a8 100644 --- a/homeassistant/components/kodi/translations/el.json +++ b/homeassistant/components/kodi/translations/el.json @@ -3,6 +3,9 @@ "flow_title": "Kodi: {\u03cc\u03bd\u03bf\u03bc\u03b1}", "step": { "credentials": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 Kodi. \u0391\u03c5\u03c4\u03ac \u03bc\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03b2\u03c1\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03a3\u03cd\u03c3\u03c4\u03b7\u03bc\u03b1 / \u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 / \u0394\u03af\u03ba\u03c4\u03c5\u03bf / \u03a5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b5\u03c2." }, "discovery_confirm": { diff --git a/homeassistant/components/kostal_plenticore/translations/el.json b/homeassistant/components/kostal_plenticore/translations/el.json index 48edc219315ea9..aa6c808eec74b9 100644 --- a/homeassistant/components/kostal_plenticore/translations/el.json +++ b/homeassistant/components/kostal_plenticore/translations/el.json @@ -1,3 +1,12 @@ { + "config": { + "step": { + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + } + } + } + }, "title": "\u0397\u03bb\u03b9\u03b1\u03ba\u03cc\u03c2 \u03bc\u03b5\u03c4\u03b1\u03c4\u03c1\u03bf\u03c0\u03ad\u03b1\u03c2 Kostal Plenticore" } \ No newline at end of file diff --git a/homeassistant/components/kraken/translations/el.json b/homeassistant/components/kraken/translations/el.json index 6cb6a0d4ea0c44..c369993fa0dd08 100644 --- a/homeassistant/components/kraken/translations/el.json +++ b/homeassistant/components/kraken/translations/el.json @@ -3,7 +3,8 @@ "step": { "init": { "data": { - "scan_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7\u03c2" + "scan_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7\u03c2", + "tracked_asset_pairs": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03bf\u03cd\u03bc\u03b5\u03bd\u03b1 \u03b6\u03b5\u03cd\u03b3\u03b7 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c5\u03c3\u03b9\u03b1\u03ba\u03ce\u03bd \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03c9\u03bd" } } } diff --git a/homeassistant/components/launch_library/translations/fr.json b/homeassistant/components/launch_library/translations/fr.json index 57d3902c1f02e6..ba346a19acd9aa 100644 --- a/homeassistant/components/launch_library/translations/fr.json +++ b/homeassistant/components/launch_library/translations/fr.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." + }, "step": { "user": { "description": "Voulez-vous configurer la biblioth\u00e8que de lancement\u00a0?" diff --git a/homeassistant/components/life360/translations/el.json b/homeassistant/components/life360/translations/el.json index 14fc1ef094f2df..26ad68c7d5b35d 100644 --- a/homeassistant/components/life360/translations/el.json +++ b/homeassistant/components/life360/translations/el.json @@ -8,6 +8,9 @@ }, "step": { "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03bf\u03c1\u03af\u03c3\u03b5\u03c4\u03b5 \u03c0\u03c1\u03bf\u03b7\u03b3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2, \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7 Life360]({docs_url}).\n\u039c\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c4\u03bf \u03ba\u03ac\u03bd\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c0\u03c1\u03b9\u03bd \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd\u03c2.", "title": "\u03a0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd Life360" } diff --git a/homeassistant/components/light/translations/el.json b/homeassistant/components/light/translations/el.json index d3ec06dcbfbdbe..4b5c69c61018bf 100644 --- a/homeassistant/components/light/translations/el.json +++ b/homeassistant/components/light/translations/el.json @@ -3,6 +3,7 @@ "action_type": { "brightness_decrease": "\u039c\u03b5\u03af\u03c9\u03c3\u03b7 \u03c6\u03c9\u03c4\u03b5\u03b9\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 {entity_name}", "brightness_increase": "\u0391\u03cd\u03be\u03b7\u03c3\u03b7 \u03c6\u03c9\u03c4\u03b5\u03b9\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 {entity_name}", + "flash": "\u03a6\u03bb\u03b1\u03c2 {entity_name}", "toggle": "\u0395\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae {entity_name}", "turn_off": "\u0391\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 {entity_name}", "turn_on": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 {entity_name}" diff --git a/homeassistant/components/light/translations/fr.json b/homeassistant/components/light/translations/fr.json index 1976c7b8fd4f16..7863f5ad5ebb18 100644 --- a/homeassistant/components/light/translations/fr.json +++ b/homeassistant/components/light/translations/fr.json @@ -13,6 +13,7 @@ "is_on": "{entity_name} est allum\u00e9" }, "trigger_type": { + "changed_states": "{entity_name} activ\u00e9 ou d\u00e9sactiv\u00e9", "toggled": "{entity_name} activ\u00e9 ou d\u00e9sactiv\u00e9", "turned_off": "{entity_name} est d\u00e9sactiv\u00e9", "turned_on": "{entity_name} activ\u00e9" diff --git a/homeassistant/components/litterrobot/translations/el.json b/homeassistant/components/litterrobot/translations/el.json new file mode 100644 index 00000000000000..18c2f0869bd1c2 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lookin/translations/el.json b/homeassistant/components/lookin/translations/el.json index 8fc295ebf9a6b9..0188d8032e6a8b 100644 --- a/homeassistant/components/lookin/translations/el.json +++ b/homeassistant/components/lookin/translations/el.json @@ -2,6 +2,11 @@ "config": { "flow_title": "{name} ({host})", "step": { + "device_name": { + "data": { + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + } + }, "discovery_confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ({host});" } diff --git a/homeassistant/components/lutron_caseta/translations/el.json b/homeassistant/components/lutron_caseta/translations/el.json index eea8c3495b4918..803b5e1231ab99 100644 --- a/homeassistant/components/lutron_caseta/translations/el.json +++ b/homeassistant/components/lutron_caseta/translations/el.json @@ -14,6 +14,9 @@ "title": "\u03a3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7 \u03bc\u03b5 \u03c4\u03b7 \u03b3\u03ad\u03c6\u03c5\u03c1\u03b1" }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2.", "title": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03b3\u03ad\u03c6\u03c5\u03c1\u03b1" } diff --git a/homeassistant/components/media_player/translations/fr.json b/homeassistant/components/media_player/translations/fr.json index 9ecdd19037f8ca..bcda6d770a3f8c 100644 --- a/homeassistant/components/media_player/translations/fr.json +++ b/homeassistant/components/media_player/translations/fr.json @@ -8,6 +8,7 @@ "is_playing": "{entity_name} joue" }, "trigger_type": { + "changed_states": "{entity_name} a chang\u00e9 d'\u00e9tat", "idle": "{entity_name} devient inactif", "paused": "{entity_name} est mis en pause", "playing": "{entity_name} commence \u00e0 jouer", diff --git a/homeassistant/components/met_eireann/translations/el.json b/homeassistant/components/met_eireann/translations/el.json index 65335fe7c2be62..7e37a1c1f9d30c 100644 --- a/homeassistant/components/met_eireann/translations/el.json +++ b/homeassistant/components/met_eireann/translations/el.json @@ -2,6 +2,9 @@ "config": { "step": { "user": { + "data": { + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd \u03b1\u03c0\u03cc \u03c4\u03bf Met \u00c9ireann Public Weather Forecast API" } } diff --git a/homeassistant/components/mikrotik/translations/el.json b/homeassistant/components/mikrotik/translations/el.json index 83aba4ae98a9ea..0304a1423f6746 100644 --- a/homeassistant/components/mikrotik/translations/el.json +++ b/homeassistant/components/mikrotik/translations/el.json @@ -6,6 +6,7 @@ "step": { "user": { "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", "verify_ssl": "\u03a7\u03c1\u03ae\u03c3\u03b7 ssl" }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae Mikrotik" diff --git a/homeassistant/components/mill/translations/el.json b/homeassistant/components/mill/translations/el.json index ed475f6c0dd1a6..29ffda9a5e9b5d 100644 --- a/homeassistant/components/mill/translations/el.json +++ b/homeassistant/components/mill/translations/el.json @@ -1,6 +1,11 @@ { "config": { "step": { + "cloud": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } + }, "local": { "description": "\u03a4\u03bf\u03c0\u03b9\u03ba\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2." }, diff --git a/homeassistant/components/modem_callerid/translations/el.json b/homeassistant/components/modem_callerid/translations/el.json index 179004c8e437d1..447231bcb0c540 100644 --- a/homeassistant/components/modem_callerid/translations/el.json +++ b/homeassistant/components/modem_callerid/translations/el.json @@ -9,6 +9,9 @@ "title": "\u039c\u03cc\u03bd\u03c4\u03b5\u03bc \u03c4\u03b7\u03bb\u03b5\u03c6\u03ce\u03bd\u03bf\u03c5" }, "user": { + "data": { + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + }, "description": "\u03a0\u03c1\u03cc\u03ba\u03b5\u03b9\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03ba\u03bb\u03ae\u03c3\u03b5\u03b9\u03c2 \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae\u03c2 \u03c4\u03b7\u03bb\u03b5\u03c6\u03c9\u03bd\u03af\u03b1\u03c2 \u03bc\u03b5 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c6\u03c9\u03bd\u03b7\u03c4\u03b9\u03ba\u03bf\u03cd \u03bc\u03cc\u03bd\u03c4\u03b5\u03bc CX93001. \u0391\u03c5\u03c4\u03cc \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b1\u03bd\u03b1\u03ba\u03c4\u03ae\u03c3\u03b5\u03b9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ba\u03b1\u03bb\u03bf\u03cd\u03bd\u03c4\u03bf\u03c2 \u03bc\u03b5 \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b1\u03c0\u03cc\u03c1\u03c1\u03b9\u03c8\u03b7\u03c2 \u03bc\u03b9\u03b1\u03c2 \u03b5\u03b9\u03c3\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03b7\u03c2 \u03ba\u03bb\u03ae\u03c3\u03b7\u03c2.", "title": "\u039c\u03cc\u03bd\u03c4\u03b5\u03bc \u03c4\u03b7\u03bb\u03b5\u03c6\u03ce\u03bd\u03bf\u03c5" } diff --git a/homeassistant/components/modern_forms/translations/el.json b/homeassistant/components/modern_forms/translations/el.json index fe686797d776fa..44941e91084b53 100644 --- a/homeassistant/components/modern_forms/translations/el.json +++ b/homeassistant/components/modern_forms/translations/el.json @@ -3,6 +3,9 @@ "flow_title": "{name}", "step": { "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b1\u03bd\u03b5\u03bc\u03b9\u03c3\u03c4\u03ae\u03c1\u03b1 Modern Forms \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Home Assistant." }, "zeroconf_confirm": { diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/bg.json b/homeassistant/components/moehlenhoff_alpha2/translations/bg.json index cbf1e2ae7c9b34..48e3fdefc608f3 100644 --- a/homeassistant/components/moehlenhoff_alpha2/translations/bg.json +++ b/homeassistant/components/moehlenhoff_alpha2/translations/bg.json @@ -14,5 +14,6 @@ } } } - } + }, + "title": "M\u00f6hlenhoff Alpha2" } \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/el.json b/homeassistant/components/moehlenhoff_alpha2/translations/el.json index d15111e97b0d19..a044d731d173be 100644 --- a/homeassistant/components/moehlenhoff_alpha2/translations/el.json +++ b/homeassistant/components/moehlenhoff_alpha2/translations/el.json @@ -1,3 +1,12 @@ { + "config": { + "step": { + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + } + } + } + }, "title": "M\u00f6hlenhoff Alpha2" } \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/fr.json b/homeassistant/components/moehlenhoff_alpha2/translations/fr.json index 7c97f50a85f62a..205436aa03d11c 100644 --- a/homeassistant/components/moehlenhoff_alpha2/translations/fr.json +++ b/homeassistant/components/moehlenhoff_alpha2/translations/fr.json @@ -14,5 +14,6 @@ } } } - } + }, + "title": "M\u00f6hlenhoff Alpha2" } \ No newline at end of file diff --git a/homeassistant/components/motion_blinds/translations/el.json b/homeassistant/components/motion_blinds/translations/el.json index 29fa43b4a167a8..50851e08956823 100644 --- a/homeassistant/components/motion_blinds/translations/el.json +++ b/homeassistant/components/motion_blinds/translations/el.json @@ -17,6 +17,7 @@ "options": { "step": { "init": { + "description": "\u039a\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ce\u03bd \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c9\u03bd", "title": "Motion Blinds" } } diff --git a/homeassistant/components/motioneye/translations/el.json b/homeassistant/components/motioneye/translations/el.json index 845e6949c010ad..110dc2a5c0513b 100644 --- a/homeassistant/components/motioneye/translations/el.json +++ b/homeassistant/components/motioneye/translations/el.json @@ -6,6 +6,12 @@ "step": { "hassio_confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03bf\u03c5\u03c2 \u03c4\u03bf\u03c5 Home Assistant \u03ce\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03b5\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03c4\u03b7\u03bd \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 motionEye \u03c0\u03bf\u03c5 \u03c0\u03b1\u03c1\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf: {addon};" + }, + "user": { + "data": { + "admin_username": "\u0394\u03b9\u03b1\u03c7\u03b5\u03b9\u03c1\u03b9\u03c3\u03c4\u03ae\u03c2 \u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", + "surveillance_username": "\u0395\u03c0\u03b9\u03c4\u03ae\u03c1\u03b7\u03c3\u03b7 \u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } } } }, diff --git a/homeassistant/components/mqtt/translations/el.json b/homeassistant/components/mqtt/translations/el.json index 68e185b73394b1..4cf45560e8eb13 100644 --- a/homeassistant/components/mqtt/translations/el.json +++ b/homeassistant/components/mqtt/translations/el.json @@ -7,7 +7,8 @@ "broker": { "data": { "broker": "\u039c\u03b5\u03c3\u03af\u03c4\u03b7\u03c2", - "discovery": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7\u03c2" + "discovery": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 MQTT broker." }, diff --git a/homeassistant/components/mutesync/translations/el.json b/homeassistant/components/mutesync/translations/el.json index 0edaee152acd9e..1f731e6efbdc1e 100644 --- a/homeassistant/components/mutesync/translations/el.json +++ b/homeassistant/components/mutesync/translations/el.json @@ -2,6 +2,13 @@ "config": { "error": { "invalid_auth": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03c3\u03c4\u03bf m\u00fctesync \u03a0\u03c1\u03bf\u03c4\u03b9\u03bc\u03ae\u03c3\u03b5\u03b9\u03c2 > \u0388\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, + "step": { + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/nam/translations/el.json b/homeassistant/components/nam/translations/el.json index 06b4294bae9b3a..cc054a26d97dd8 100644 --- a/homeassistant/components/nam/translations/el.json +++ b/homeassistant/components/nam/translations/el.json @@ -9,10 +9,22 @@ "confirm_discovery": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Nettigo Air Monitor \u03c3\u03c4\u03bf {host};" }, + "credentials": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2." + }, "reauth_confirm": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03c3\u03c9\u03c3\u03c4\u03cc \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae: {host}" }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 Nettigo Air Monitor." } } diff --git a/homeassistant/components/netgear/translations/fr.json b/homeassistant/components/netgear/translations/fr.json index f4b0af9789675b..3230b8df45f36f 100644 --- a/homeassistant/components/netgear/translations/fr.json +++ b/homeassistant/components/netgear/translations/fr.json @@ -15,7 +15,7 @@ "ssl": "Utilise un certificat SSL", "username": "Nom d'utilisateur (Optional)" }, - "description": "H\u00f4te par d\u00e9faut\u00a0: {host}\n Port par d\u00e9faut\u00a0: {port}\n Nom d'utilisateur par d\u00e9faut\u00a0: {username}", + "description": "H\u00f4te par d\u00e9faut\u00a0: {host}\nNom d'utilisateur par d\u00e9faut\u00a0: {username}", "title": "Netgear" } } diff --git a/homeassistant/components/notion/translations/el.json b/homeassistant/components/notion/translations/el.json index 288856ef8a1555..24e5bde93c5f44 100644 --- a/homeassistant/components/notion/translations/el.json +++ b/homeassistant/components/notion/translations/el.json @@ -8,6 +8,9 @@ "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username}." }, "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "title": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03b1\u03c2" } } diff --git a/homeassistant/components/nuki/translations/el.json b/homeassistant/components/nuki/translations/el.json index 8e12bb837f4d54..cbe07cce0f74db 100644 --- a/homeassistant/components/nuki/translations/el.json +++ b/homeassistant/components/nuki/translations/el.json @@ -3,6 +3,11 @@ "step": { "reauth_confirm": { "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Nuki \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03bc\u03b5 \u03c4\u03b7 \u03b3\u03ad\u03c6\u03c5\u03c1\u03ac \u03c3\u03b1\u03c2." + }, + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + } } } } diff --git a/homeassistant/components/nut/translations/el.json b/homeassistant/components/nut/translations/el.json index 2f13daba62c80b..80d36689553f1a 100644 --- a/homeassistant/components/nut/translations/el.json +++ b/homeassistant/components/nut/translations/el.json @@ -15,6 +15,9 @@ "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf UPS \u03b3\u03b9\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7" }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae NUT" } } diff --git a/homeassistant/components/nzbget/translations/el.json b/homeassistant/components/nzbget/translations/el.json index 394e8ca41efcff..f8d2b4b460ea54 100644 --- a/homeassistant/components/nzbget/translations/el.json +++ b/homeassistant/components/nzbget/translations/el.json @@ -9,6 +9,10 @@ "flow_title": "NZBGet: {\u03cc\u03bd\u03bf\u03bc\u03b1}", "step": { "user": { + "data": { + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf NZBGet" } } diff --git a/homeassistant/components/octoprint/translations/el.json b/homeassistant/components/octoprint/translations/el.json index e29ff8dee5f10f..cf8474a3762c2e 100644 --- a/homeassistant/components/octoprint/translations/el.json +++ b/homeassistant/components/octoprint/translations/el.json @@ -2,6 +2,21 @@ "config": { "abort": { "auth_failed": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b1\u03bd\u03ac\u03ba\u03c4\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd api \u03c4\u03b7\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2" + }, + "flow_title": "\u0395\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae\u03c2 OctoPrint: {host}", + "progress": { + "get_api_key": "\u0391\u03bd\u03bf\u03af\u03be\u03c4\u03b5 \u03c4\u03bf OctoPrint UI \u03ba\u03b1\u03b9 \u03ba\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf 'Allow' \u03c3\u03c4\u03bf \u03b1\u03af\u03c4\u03b7\u03bc\u03b1 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf 'Home Assistant'." + }, + "step": { + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2", + "port": "\u0391\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03b8\u03cd\u03c1\u03b1\u03c2", + "ssl": "\u03a7\u03c1\u03ae\u03c3\u03b7 SSL", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/oncue/translations/el.json b/homeassistant/components/oncue/translations/el.json new file mode 100644 index 00000000000000..18c2f0869bd1c2 --- /dev/null +++ b/homeassistant/components/oncue/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/el.json b/homeassistant/components/onvif/translations/el.json index 676e0e9ca898e6..dedd4eb3def0f6 100644 --- a/homeassistant/components/onvif/translations/el.json +++ b/homeassistant/components/onvif/translations/el.json @@ -33,6 +33,7 @@ "manual_input": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "port": "\u0398\u03cd\u03c1\u03b1" }, "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 ONVIF" diff --git a/homeassistant/components/overkiz/translations/el.json b/homeassistant/components/overkiz/translations/el.json index 232e757b643f43..b81293d60a6526 100644 --- a/homeassistant/components/overkiz/translations/el.json +++ b/homeassistant/components/overkiz/translations/el.json @@ -11,7 +11,9 @@ "step": { "user": { "data": { - "hub": "\u039a\u03cc\u03bc\u03b2\u03bf\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "hub": "\u039a\u03cc\u03bc\u03b2\u03bf\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0397 \u03c0\u03bb\u03b1\u03c4\u03c6\u03cc\u03c1\u03bc\u03b1 Overkiz \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03b4\u03b9\u03ac\u03c6\u03bf\u03c1\u03bf\u03c5\u03c2 \u03c0\u03c1\u03bf\u03bc\u03b7\u03b8\u03b5\u03c5\u03c4\u03ad\u03c2 \u03cc\u03c0\u03c9\u03c2 \u03b7 Somfy (Connexoon / TaHoma), \u03b7 Hitachi (Hi Kumo), \u03b7 Rexel (Energeasy Connect) \u03ba\u03b1\u03b9 \u03b7 Atlantic (Cozytouch). \u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03c4\u03b7\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03ba\u03b1\u03b9 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03cc\u03bc\u03b2\u03bf \u03c3\u03b1\u03c2." } diff --git a/homeassistant/components/overkiz/translations/fr.json b/homeassistant/components/overkiz/translations/fr.json index 83243718739ec6..790aafc07966cd 100644 --- a/homeassistant/components/overkiz/translations/fr.json +++ b/homeassistant/components/overkiz/translations/fr.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9" + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi", + "reauth_wrong_account": "Vous ne pouvez r\u00e9authentifier cette entr\u00e9e qu'avec le m\u00eame compte et hub Overkiz" }, "error": { "cannot_connect": "\u00c9chec de connexion", @@ -10,6 +12,7 @@ "too_many_requests": "Trop de demandes, r\u00e9essayez plus tard.", "unknown": "Erreur inattendue" }, + "flow_title": "Passerelle : {gateway_id}", "step": { "user": { "data": { diff --git a/homeassistant/components/overkiz/translations/sensor.fr.json b/homeassistant/components/overkiz/translations/sensor.fr.json index 07b79c83c36889..af9fd658ab9d4f 100644 --- a/homeassistant/components/overkiz/translations/sensor.fr.json +++ b/homeassistant/components/overkiz/translations/sensor.fr.json @@ -9,9 +9,11 @@ "overkiz__discrete_rssi_level": { "good": "Bon", "low": "Bas", - "normal": "Normal" + "normal": "Normal", + "verylow": "Tr\u00e8s lent" }, "overkiz__priority_lock_originator": { + "external_gateway": "Passerelle externe", "local_user": "Utilisateur local", "lsc": "LSC", "myself": "Moi-m\u00eame", diff --git a/homeassistant/components/ovo_energy/translations/el.json b/homeassistant/components/ovo_energy/translations/el.json index 8e60f589fc5975..d1981d80c7e676 100644 --- a/homeassistant/components/ovo_energy/translations/el.json +++ b/homeassistant/components/ovo_energy/translations/el.json @@ -3,6 +3,9 @@ "flow_title": "{username}", "step": { "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1 OVO Energy \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03c0\u03bf\u03ba\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03b5\u03bd\u03ad\u03c1\u03b3\u03b5\u03b9\u03b1\u03c2.", "title": "\u03a0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd OVO Energy" } diff --git a/homeassistant/components/p1_monitor/translations/el.json b/homeassistant/components/p1_monitor/translations/el.json index 00e89f9735d821..b512655a7d7e00 100644 --- a/homeassistant/components/p1_monitor/translations/el.json +++ b/homeassistant/components/p1_monitor/translations/el.json @@ -6,6 +6,9 @@ }, "step": { "user": { + "data": { + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf {intergration} \u03b3\u03b9\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf Home Assistant." } } diff --git a/homeassistant/components/panasonic_viera/translations/el.json b/homeassistant/components/panasonic_viera/translations/el.json index de3abd8dca713c..d3e7ea45706098 100644 --- a/homeassistant/components/panasonic_viera/translations/el.json +++ b/homeassistant/components/panasonic_viera/translations/el.json @@ -12,6 +12,9 @@ "title": "\u03a3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7" }, "user": { + "data": { + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + }, "description": "\u03a0\u03bb\u03b7\u03ba\u03c4\u03c1\u03bf\u03bb\u03bf\u03b3\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c4\u03b7\u03c2 Panasonic Viera TV \u03c3\u03b1\u03c2 \u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2" } diff --git a/homeassistant/components/philips_js/translations/el.json b/homeassistant/components/philips_js/translations/el.json index 11833024f9cf2a..9992a30caea431 100644 --- a/homeassistant/components/philips_js/translations/el.json +++ b/homeassistant/components/philips_js/translations/el.json @@ -14,7 +14,8 @@ }, "user": { "data": { - "api_version": "\u0388\u03ba\u03b4\u03bf\u03c3\u03b7 API" + "api_version": "\u0388\u03ba\u03b4\u03bf\u03c3\u03b7 API", + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" } } } diff --git a/homeassistant/components/pi_hole/translations/el.json b/homeassistant/components/pi_hole/translations/el.json index 5c1ab6ee04d7de..105be4965c4455 100644 --- a/homeassistant/components/pi_hole/translations/el.json +++ b/homeassistant/components/pi_hole/translations/el.json @@ -3,6 +3,8 @@ "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "statistics_only": "\u039c\u03cc\u03bd\u03bf \u03c3\u03c4\u03b1\u03c4\u03b9\u03c3\u03c4\u03b9\u03ba\u03ac \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1" } } diff --git a/homeassistant/components/picnic/translations/bg.json b/homeassistant/components/picnic/translations/bg.json new file mode 100644 index 00000000000000..c0ccf23f5b5c37 --- /dev/null +++ b/homeassistant/components/picnic/translations/bg.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0442\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435 \u0431\u0435\u0448\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/el.json b/homeassistant/components/picnic/translations/el.json index f931aa6f3c00f3..206fbdab2a2675 100644 --- a/homeassistant/components/picnic/translations/el.json +++ b/homeassistant/components/picnic/translations/el.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "reauth_successful": "\u0397 \u03b5\u03c0\u03b1\u03bd\u03b1\u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "different_account": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03af\u03b4\u03b9\u03bf\u03c2 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/picnic/translations/fr.json b/homeassistant/components/picnic/translations/fr.json index 03b5566566f09c..794a33ffe754e9 100644 --- a/homeassistant/components/picnic/translations/fr.json +++ b/homeassistant/components/picnic/translations/fr.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" }, "error": { "cannot_connect": "\u00c9chec de connexion", + "different_account": "Le compte doit \u00eatre le m\u00eame que celui utilis\u00e9 pour configurer l'int\u00e9gration", "invalid_auth": "Authentification invalide", "unknown": "Erreur inattendue" }, diff --git a/homeassistant/components/picnic/translations/hu.json b/homeassistant/components/picnic/translations/hu.json index c70dcca0260500..3841b7ddbafb0e 100644 --- a/homeassistant/components/picnic/translations/hu.json +++ b/homeassistant/components/picnic/translations/hu.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt." }, "error": { "cannot_connect": "Nem siker\u00fclt csatlakozni", + "different_account": "A fi\u00f3knak meg kell egyeznie az integr\u00e1ci\u00f3 be\u00e1ll\u00edt\u00e1s\u00e1hoz haszn\u00e1lt fi\u00f3kkal", "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "unknown": "V\u00e1ratlan hiba" }, diff --git a/homeassistant/components/picnic/translations/it.json b/homeassistant/components/picnic/translations/it.json index e77faae817d106..209b6d6fdb9689 100644 --- a/homeassistant/components/picnic/translations/it.json +++ b/homeassistant/components/picnic/translations/it.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" }, "error": { "cannot_connect": "Impossibile connettersi", + "different_account": "L'account deve essere lo stesso utilizzato per impostare l'integrazione", "invalid_auth": "Autenticazione non valida", "unknown": "Errore imprevisto" }, diff --git a/homeassistant/components/picnic/translations/zh-Hant.json b/homeassistant/components/picnic/translations/zh-Hant.json index 2f72809d4fe91c..a82f4ace04de72 100644 --- a/homeassistant/components/picnic/translations/zh-Hant.json +++ b/homeassistant/components/picnic/translations/zh-Hant.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", + "different_account": "\u5e33\u865f\u5fc5\u9808\u76f8\u540c\u4ee5\u8a2d\u5b9a\u6574\u5408", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, diff --git a/homeassistant/components/plex/translations/el.json b/homeassistant/components/plex/translations/el.json index debd53f9f8a35d..9039fcf27bdd35 100644 --- a/homeassistant/components/plex/translations/el.json +++ b/homeassistant/components/plex/translations/el.json @@ -17,6 +17,7 @@ "step": { "manual_setup": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)" }, "title": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Plex" diff --git a/homeassistant/components/plugwise/translations/el.json b/homeassistant/components/plugwise/translations/el.json index e9e8ad9bf5ae59..45247108dc690d 100644 --- a/homeassistant/components/plugwise/translations/el.json +++ b/homeassistant/components/plugwise/translations/el.json @@ -1,5 +1,6 @@ { "config": { + "flow_title": "{name}", "step": { "user": { "data": { @@ -7,6 +8,11 @@ }, "description": "\u03a0\u03c1\u03bf\u03ca\u03cc\u03bd:", "title": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03b2\u03cd\u03c3\u03bc\u03b1\u03c4\u03bf\u03c2" + }, + "user_gateway": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 Smile" + } } } }, diff --git a/homeassistant/components/powerwall/translations/fr.json b/homeassistant/components/powerwall/translations/fr.json index a6a6edab938246..1f5d5ac22cd158 100644 --- a/homeassistant/components/powerwall/translations/fr.json +++ b/homeassistant/components/powerwall/translations/fr.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "cannot_connect": "\u00c9chec de connexion", "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" }, "error": { @@ -10,8 +11,19 @@ "unknown": "Erreur inattendue", "wrong_version": "Votre Powerwall utilise une version logicielle qui n'est pas prise en charge. Veuillez envisager de mettre \u00e0 niveau ou de signaler ce probl\u00e8me afin qu'il puisse \u00eatre r\u00e9solu." }, - "flow_title": "{ip_address}", + "flow_title": "{nom} ({ip_address})", "step": { + "confirm_discovery": { + "description": "Voulez-vous configurer {name} ({ip_address})?", + "title": "Connectez-vous au Powerwall" + }, + "reauth_confim": { + "data": { + "password": "Mot de passe" + }, + "description": "Le mot de passe est g\u00e9n\u00e9ralement les 5 derniers caract\u00e8res du num\u00e9ro de s\u00e9rie de Backup Gateway et peut \u00eatre trouv\u00e9 dans l\u2019application Tesla ou les 5 derniers caract\u00e8res du mot de passe trouv\u00e9 \u00e0 l\u2019int\u00e9rieur de la porte pour la passerelle de Backup Gateway 2.", + "title": "R\u00e9authentifier le powerwall" + }, "user": { "data": { "ip_address": "Adresse IP", diff --git a/homeassistant/components/prosegur/translations/el.json b/homeassistant/components/prosegur/translations/el.json index 221f35ebba6d01..a24f4954f97f73 100644 --- a/homeassistant/components/prosegur/translations/el.json +++ b/homeassistant/components/prosegur/translations/el.json @@ -3,12 +3,14 @@ "step": { "reauth_confirm": { "data": { - "description": "\u0395\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc Prosegur." + "description": "\u0395\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc Prosegur.", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } }, "user": { "data": { - "country": "\u03a7\u03ce\u03c1\u03b1" + "country": "\u03a7\u03ce\u03c1\u03b1", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } } diff --git a/homeassistant/components/rainmachine/translations/el.json b/homeassistant/components/rainmachine/translations/el.json index 8d986480ff0884..4fb06d067dc2a0 100644 --- a/homeassistant/components/rainmachine/translations/el.json +++ b/homeassistant/components/rainmachine/translations/el.json @@ -3,6 +3,9 @@ "flow_title": "{ip}", "step": { "user": { + "data": { + "ip_address": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + }, "title": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03b1\u03c2" } } diff --git a/homeassistant/components/remote/translations/fr.json b/homeassistant/components/remote/translations/fr.json index 2012c853dddbc1..c2052edaab8700 100644 --- a/homeassistant/components/remote/translations/fr.json +++ b/homeassistant/components/remote/translations/fr.json @@ -10,6 +10,7 @@ "is_on": "{entity_name} est activ\u00e9" }, "trigger_type": { + "changed_states": "{entity_name} activ\u00e9 ou d\u00e9sactiv\u00e9", "toggled": "{entity_name} activ\u00e9 ou d\u00e9sactiv\u00e9", "turned_off": "{entity_name} s'est \u00e9teint", "turned_on": "{entity_name} s'est allum\u00e9" diff --git a/homeassistant/components/ridwell/translations/el.json b/homeassistant/components/ridwell/translations/el.json new file mode 100644 index 00000000000000..50eb1dfd177330 --- /dev/null +++ b/homeassistant/components/ridwell/translations/el.json @@ -0,0 +1,15 @@ +{ + "config": { + "step": { + "reauth_confirm": { + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username}:" + }, + "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2:" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ring/translations/el.json b/homeassistant/components/ring/translations/el.json index ebf05b607dd87a..4ac4f3e8445a9b 100644 --- a/homeassistant/components/ring/translations/el.json +++ b/homeassistant/components/ring/translations/el.json @@ -2,6 +2,9 @@ "config": { "step": { "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc Ring" } } diff --git a/homeassistant/components/risco/translations/el.json b/homeassistant/components/risco/translations/el.json index f1faffc85e1d27..dd977e968601a4 100644 --- a/homeassistant/components/risco/translations/el.json +++ b/homeassistant/components/risco/translations/el.json @@ -8,7 +8,8 @@ "user": { "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "pin": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN" + "pin": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } } diff --git a/homeassistant/components/roomba/translations/el.json b/homeassistant/components/roomba/translations/el.json index 551ff120de76ae..cedcd1691cf81b 100644 --- a/homeassistant/components/roomba/translations/el.json +++ b/homeassistant/components/roomba/translations/el.json @@ -20,7 +20,8 @@ }, "manual": { "data": { - "blid": "BLID" + "blid": "BLID", + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" }, "description": "\u0394\u03b5\u03bd \u03ad\u03c7\u03bf\u03c5\u03bd \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03c5\u03c6\u03b8\u03b5\u03af Roomba \u03ae Braava \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03cc \u03c3\u03b1\u03c2.", "title": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" @@ -29,7 +30,8 @@ "data": { "blid": "BLID", "continuous": "\u03a3\u03c5\u03bd\u03b5\u03c7\u03ae\u03c2", - "delay": "\u039a\u03b1\u03b8\u03c5\u03c3\u03c4\u03ad\u03c1\u03b7\u03c3\u03b7" + "delay": "\u039a\u03b1\u03b8\u03c5\u03c3\u03c4\u03ad\u03c1\u03b7\u03c3\u03b7", + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 Roomba \u03ae Braava.", "title": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" diff --git a/homeassistant/components/ruckus_unleashed/translations/el.json b/homeassistant/components/ruckus_unleashed/translations/el.json new file mode 100644 index 00000000000000..18c2f0869bd1c2 --- /dev/null +++ b/homeassistant/components/ruckus_unleashed/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senseme/translations/el.json b/homeassistant/components/senseme/translations/el.json index 152051868bc5ad..026160f21e1133 100644 --- a/homeassistant/components/senseme/translations/el.json +++ b/homeassistant/components/senseme/translations/el.json @@ -6,6 +6,9 @@ "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} - {model} ({host});" }, "manual": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP." }, "user": { diff --git a/homeassistant/components/senseme/translations/fr.json b/homeassistant/components/senseme/translations/fr.json index d8f772f9e513ac..efa8292519187a 100644 --- a/homeassistant/components/senseme/translations/fr.json +++ b/homeassistant/components/senseme/translations/fr.json @@ -1,12 +1,14 @@ { "config": { "abort": { - "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "cannot_connect": "\u00c9chec de connexion" }, "error": { "cannot_connect": "Impossible de se connecter", "invalid_host": "Adresse IP ou nom d'h\u00f4te invalide" }, + "flow_title": "{name} - {model} ({host})", "step": { "discovery_confirm": { "description": "Voulez-vous configurer {name} - {model} ( {host} )\u00a0?" diff --git a/homeassistant/components/sensibo/translations/el.json b/homeassistant/components/sensibo/translations/el.json new file mode 100644 index 00000000000000..1dbdef83eea4e3 --- /dev/null +++ b/homeassistant/components/sensibo/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sensor/translations/el.json b/homeassistant/components/sensor/translations/el.json index bc8b40943374ca..23d8a479b73eef 100644 --- a/homeassistant/components/sensor/translations/el.json +++ b/homeassistant/components/sensor/translations/el.json @@ -3,22 +3,27 @@ "condition_type": { "is_apparent_power": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03c6\u03b1\u03b9\u03bd\u03bf\u03bc\u03b5\u03bd\u03b9\u03ba\u03ae \u03b9\u03c3\u03c7\u03cd\u03c2 {entity_name}", "is_battery_level": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03bc\u03c0\u03b1\u03c4\u03b1\u03c1\u03af\u03b1\u03c2 {entity_name}", + "is_energy": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03b5\u03bd\u03ad\u03c1\u03b3\u03b5\u03b9\u03b1 {entity_name}", "is_frequency": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03c3\u03c5\u03c7\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 {entity_name}", "is_gas": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b1\u03ad\u03c1\u03b9\u03bf {entity_name}", "is_humidity": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03c5\u03b3\u03c1\u03b1\u03c3\u03af\u03b1 {entity_name}", "is_illuminance": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03c6\u03c9\u03c4\u03b5\u03b9\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 {entity_name}", "is_nitrogen_dioxide": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03b4\u03b9\u03bf\u03be\u03b5\u03b9\u03b4\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 \u03b1\u03b6\u03ce\u03c4\u03bf\u03c5 {entity_name}", + "is_nitrogen_monoxide": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03bc\u03bf\u03bd\u03bf\u03be\u03b5\u03b9\u03b4\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 \u03b1\u03b6\u03ce\u03c4\u03bf\u03c5 {entity_name}", + "is_nitrous_oxide": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03bf\u03be\u03b5\u03b9\u03b4\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 \u03b1\u03b6\u03ce\u03c4\u03bf\u03c5 {entity_name}", "is_ozone": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03cc\u03b6\u03bf\u03bd\u03c4\u03bf\u03c2 {entity_name}", "is_pm1": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 PM1 {entity_name}", "is_pm10": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 PM10 {entity_name}", "is_pm25": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 {entity_name} PM2.5", "is_power": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03b9\u03c3\u03c7\u03cd\u03c2 {entity_name}", + "is_power_factor": "\u03a4\u03c1\u03ad\u03c7\u03c9\u03bd \u03c3\u03c5\u03bd\u03c4\u03b5\u03bb\u03b5\u03c3\u03c4\u03ae\u03c2 \u03b9\u03c3\u03c7\u03cd\u03bf\u03c2 {entity_name}", "is_pressure": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03c0\u03af\u03b5\u03c3\u03b7 {entity_name}", "is_reactive_power": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03ac\u03b5\u03c1\u03b3\u03b7 \u03b9\u03c3\u03c7\u03cd\u03c2 {entity_name}", "is_signal_strength": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03b9\u03c3\u03c7\u03cd\u03c2 \u03c3\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2 {entity_name}", "is_sulphur_dioxide": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03b4\u03b9\u03bf\u03be\u03b5\u03b9\u03b4\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 \u03b8\u03b5\u03af\u03bf\u03c5 {entity_name}", "is_temperature": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1 {entity_name}", - "is_volatile_organic_compounds": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03c0\u03c4\u03b7\u03c4\u03b9\u03ba\u03ce\u03bd \u03bf\u03c1\u03b3\u03b1\u03bd\u03b9\u03ba\u03ce\u03bd \u03b5\u03bd\u03ce\u03c3\u03b5\u03c9\u03bd {entity_name}" + "is_volatile_organic_compounds": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03c0\u03c4\u03b7\u03c4\u03b9\u03ba\u03ce\u03bd \u03bf\u03c1\u03b3\u03b1\u03bd\u03b9\u03ba\u03ce\u03bd \u03b5\u03bd\u03ce\u03c3\u03b5\u03c9\u03bd {entity_name}", + "is_voltage": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03c4\u03ac\u03c3\u03b7 {entity_name}" }, "trigger_type": { "battery_level": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ae \u03b5\u03c0\u03b9\u03c0\u03ad\u03b4\u03bf\u03c5 \u03bc\u03c0\u03b1\u03c4\u03b1\u03c1\u03af\u03b1\u03c2 \u03b3\u03b9\u03b1 {entity_name}", @@ -31,8 +36,10 @@ "pm1": "{entity_name} \u03bc\u03b5\u03c4\u03b1\u03b2\u03bf\u03bb\u03ad\u03c2 \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 PM1", "pm10": "{entity_name} \u03bc\u03b5\u03c4\u03b1\u03b2\u03bf\u03bb\u03ad\u03c2 \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 PM10", "pm25": "{entity_name} \u03bc\u03b5\u03c4\u03b1\u03b2\u03bf\u03bb\u03ad\u03c2 \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 PM2.5", + "power_factor": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ae \u03c3\u03c5\u03bd\u03c4\u03b5\u03bb\u03b5\u03c3\u03c4\u03ae \u03b9\u03c3\u03c7\u03cd\u03bf\u03c2 {entity_name}", "sulphur_dioxide": "{entity_name} \u03bc\u03b5\u03c4\u03b1\u03b2\u03bf\u03bb\u03ad\u03c2 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03b4\u03b9\u03bf\u03be\u03b5\u03b9\u03b4\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 \u03b8\u03b5\u03af\u03bf\u03c5", - "volatile_organic_compounds": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ad\u03c2 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7 \u03c4\u03c9\u03bd \u03c0\u03c4\u03b7\u03c4\u03b9\u03ba\u03ce\u03bd \u03bf\u03c1\u03b3\u03b1\u03bd\u03b9\u03ba\u03ce\u03bd \u03b5\u03bd\u03ce\u03c3\u03b5\u03c9\u03bd {entity_name}" + "volatile_organic_compounds": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ad\u03c2 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7 \u03c4\u03c9\u03bd \u03c0\u03c4\u03b7\u03c4\u03b9\u03ba\u03ce\u03bd \u03bf\u03c1\u03b3\u03b1\u03bd\u03b9\u03ba\u03ce\u03bd \u03b5\u03bd\u03ce\u03c3\u03b5\u03c9\u03bd {entity_name}", + "voltage": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ae \u03c4\u03ac\u03c3\u03b7\u03c2 {entity_name}" } }, "state": { diff --git a/homeassistant/components/sharkiq/translations/el.json b/homeassistant/components/sharkiq/translations/el.json index 4c6777955253d3..e146efbb749630 100644 --- a/homeassistant/components/sharkiq/translations/el.json +++ b/homeassistant/components/sharkiq/translations/el.json @@ -6,7 +6,13 @@ "step": { "reauth": { "data": { - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } + }, + "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } } diff --git a/homeassistant/components/shelly/translations/el.json b/homeassistant/components/shelly/translations/el.json index c87fb65f44634a..e8f1b53c6ea60f 100644 --- a/homeassistant/components/shelly/translations/el.json +++ b/homeassistant/components/shelly/translations/el.json @@ -8,6 +8,11 @@ "confirm_discovery": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {model} \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 {host};\n\n\u03a0\u03c1\u03b9\u03bd \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7, \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bc\u03b5 \u03bc\u03c0\u03b1\u03c4\u03b1\u03c1\u03af\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03be\u03c5\u03c0\u03bd\u03ae\u03c3\u03b5\u03b9 \u03c0\u03b1\u03c4\u03ce\u03bd\u03c4\u03b1\u03c2 \u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae." }, + "credentials": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } + }, "user": { "description": "\u03a0\u03c1\u03b9\u03bd \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7, \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bc\u03b5 \u03bc\u03c0\u03b1\u03c4\u03b1\u03c1\u03af\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03be\u03c5\u03c0\u03bd\u03ae\u03c3\u03b5\u03b9 \u03c0\u03b1\u03c4\u03ce\u03bd\u03c4\u03b1\u03c2 \u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae." } diff --git a/homeassistant/components/sia/translations/el.json b/homeassistant/components/sia/translations/el.json index a7663fff6d6813..06c6565c719ff5 100644 --- a/homeassistant/components/sia/translations/el.json +++ b/homeassistant/components/sia/translations/el.json @@ -1,5 +1,11 @@ { "config": { + "error": { + "invalid_key_format": "\u03a4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b5\u03ba\u03b1\u03b5\u03be\u03b1\u03b4\u03b9\u03ba\u03ae \u03c4\u03b9\u03bc\u03ae, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03bc\u03cc\u03bd\u03bf 0-9 \u03ba\u03b1\u03b9 A-F.", + "invalid_key_length": "\u03a4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c4\u03bf \u03c3\u03c9\u03c3\u03c4\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2, \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 16, 24 \u03ae 32 \u03b4\u03b5\u03ba\u03b1\u03b5\u03be\u03b1\u03b4\u03b9\u03ba\u03bf\u03af \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b5\u03c2.", + "invalid_ping": "\u03a4\u03bf \u03b4\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 ping \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03bc\u03b5\u03c4\u03b1\u03be\u03cd 1 \u03ba\u03b1\u03b9 1440 \u03bb\u03b5\u03c0\u03c4\u03ce\u03bd.", + "invalid_zones": "\u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03c4\u03bf\u03c5\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf\u03bd 1 \u03b6\u03ce\u03bd\u03b7." + }, "step": { "additional_account": { "title": "\u03a0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03ac\u03bb\u03bb\u03bf\u03c5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03c3\u03c4\u03b7\u03bd \u03c4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03b8\u03cd\u03c1\u03b1." diff --git a/homeassistant/components/sma/translations/el.json b/homeassistant/components/sma/translations/el.json index 14c3fe70ac4b4a..742ad113bb7297 100644 --- a/homeassistant/components/sma/translations/el.json +++ b/homeassistant/components/sma/translations/el.json @@ -6,7 +6,8 @@ "step": { "user": { "data": { - "group": "\u039f\u03bc\u03ac\u03b4\u03b1" + "group": "\u039f\u03bc\u03ac\u03b4\u03b1", + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 SMA.", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 SMA Solar" diff --git a/homeassistant/components/smart_meter_texas/translations/el.json b/homeassistant/components/smart_meter_texas/translations/el.json new file mode 100644 index 00000000000000..18c2f0869bd1c2 --- /dev/null +++ b/homeassistant/components/smart_meter_texas/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/el.json b/homeassistant/components/sonarr/translations/el.json index cdaa3e16590a8e..1124fde09c4799 100644 --- a/homeassistant/components/sonarr/translations/el.json +++ b/homeassistant/components/sonarr/translations/el.json @@ -11,7 +11,8 @@ }, "user": { "data": { - "base_path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf API" + "base_path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf API", + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" } } } diff --git a/homeassistant/components/spider/translations/el.json b/homeassistant/components/spider/translations/el.json index a2e96925a5bf0f..2a1f19d55fc84f 100644 --- a/homeassistant/components/spider/translations/el.json +++ b/homeassistant/components/spider/translations/el.json @@ -2,6 +2,9 @@ "config": { "step": { "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc mijn.ithodaalderop.nl" } } diff --git a/homeassistant/components/squeezebox/translations/el.json b/homeassistant/components/squeezebox/translations/el.json index 1a4a2627af3095..7e626db563b8e1 100644 --- a/homeassistant/components/squeezebox/translations/el.json +++ b/homeassistant/components/squeezebox/translations/el.json @@ -6,6 +6,18 @@ "error": { "no_server_found": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae." }, - "flow_title": "{host}" + "flow_title": "{host}", + "step": { + "edit": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + } + }, + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/el.json b/homeassistant/components/srp_energy/translations/el.json index e392cd4e4ffe7e..7f7e163cfbbf70 100644 --- a/homeassistant/components/srp_energy/translations/el.json +++ b/homeassistant/components/srp_energy/translations/el.json @@ -7,7 +7,8 @@ "user": { "data": { "id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd", - "is_tou": "\u0395\u03af\u03bd\u03b1\u03b9 \u03bf \u03c7\u03c1\u03cc\u03bd\u03bf\u03c2 \u03c7\u03c1\u03ae\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03c3\u03c7\u03b5\u03b4\u03af\u03bf\u03c5" + "is_tou": "\u0395\u03af\u03bd\u03b1\u03b9 \u03bf \u03c7\u03c1\u03cc\u03bd\u03bf\u03c2 \u03c7\u03c1\u03ae\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03c3\u03c7\u03b5\u03b4\u03af\u03bf\u03c5", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } } diff --git a/homeassistant/components/starline/translations/el.json b/homeassistant/components/starline/translations/el.json index 2716af6477b902..deb451383f6788 100644 --- a/homeassistant/components/starline/translations/el.json +++ b/homeassistant/components/starline/translations/el.json @@ -29,6 +29,9 @@ "title": "\u0388\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b4\u03cd\u03bf \u03c0\u03b1\u03c1\u03b1\u03b3\u03cc\u03bd\u03c4\u03c9\u03bd" }, "auth_user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "description": "Email \u03ba\u03b1\u03b9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd StarLine", "title": "\u0394\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } diff --git a/homeassistant/components/steamist/translations/el.json b/homeassistant/components/steamist/translations/el.json index 2f022cfb50e71c..1df0b2de9c3d2f 100644 --- a/homeassistant/components/steamist/translations/el.json +++ b/homeassistant/components/steamist/translations/el.json @@ -9,6 +9,12 @@ "data": { "device": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" } + }, + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, + "description": "\u0391\u03bd \u03b1\u03c6\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ba\u03b5\u03bd\u03cc, \u03b7 \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7 \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd." } } } diff --git a/homeassistant/components/steamist/translations/fr.json b/homeassistant/components/steamist/translations/fr.json index 95c4d883f02b2c..f3c122a2f2ca17 100644 --- a/homeassistant/components/steamist/translations/fr.json +++ b/homeassistant/components/steamist/translations/fr.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", "cannot_connect": "Impossible de se connecter", "no_devices_found": "Pas d'appareils trouv\u00e9 sur le r\u00e9seau", "not_steamist_device": "Pas un appareil \u00e0 vapeur" diff --git a/homeassistant/components/surepetcare/translations/el.json b/homeassistant/components/surepetcare/translations/el.json new file mode 100644 index 00000000000000..18c2f0869bd1c2 --- /dev/null +++ b/homeassistant/components/surepetcare/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/switch/translations/fr.json b/homeassistant/components/switch/translations/fr.json index 8758610a4a228f..e2abc37090953c 100644 --- a/homeassistant/components/switch/translations/fr.json +++ b/homeassistant/components/switch/translations/fr.json @@ -10,6 +10,7 @@ "is_on": "{entity_name} est allum\u00e9" }, "trigger_type": { + "changed_states": "{entity_name} activ\u00e9 ou d\u00e9sactiv\u00e9", "toggled": "{entity_name} activ\u00e9 ou d\u00e9sactiv\u00e9", "turned_off": "{entity_name} \u00e9teint", "turned_on": "{entity_name} allum\u00e9" diff --git a/homeassistant/components/switchbot/translations/el.json b/homeassistant/components/switchbot/translations/el.json index c74dcf759efa07..15d2736c2691f8 100644 --- a/homeassistant/components/switchbot/translations/el.json +++ b/homeassistant/components/switchbot/translations/el.json @@ -8,7 +8,8 @@ "step": { "user": { "data": { - "mac": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 MAC \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" + "mac": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 MAC \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 Switchbot" } diff --git a/homeassistant/components/syncthru/translations/el.json b/homeassistant/components/syncthru/translations/el.json index d22c90b4e10024..f1c469f8e5f953 100644 --- a/homeassistant/components/syncthru/translations/el.json +++ b/homeassistant/components/syncthru/translations/el.json @@ -7,8 +7,14 @@ }, "flow_title": "{name}", "step": { + "confirm": { + "data": { + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + } + }, "user": { "data": { + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "url": "URL \u03b4\u03b9\u03b5\u03c0\u03b1\u03c6\u03ae\u03c2 \u03b9\u03c3\u03c4\u03bf\u03cd" } } diff --git a/homeassistant/components/synology_dsm/translations/el.json b/homeassistant/components/synology_dsm/translations/el.json index 539792754bc0dd..a675c021cd32ec 100644 --- a/homeassistant/components/synology_dsm/translations/el.json +++ b/homeassistant/components/synology_dsm/translations/el.json @@ -24,9 +24,15 @@ "title": "Synology DSM \u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, "reauth_confirm": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "title": "Synology DSM \u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "title": "Synology DSM" } } diff --git a/homeassistant/components/synology_dsm/translations/fr.json b/homeassistant/components/synology_dsm/translations/fr.json index 503db0246034b0..3476676682853e 100644 --- a/homeassistant/components/synology_dsm/translations/fr.json +++ b/homeassistant/components/synology_dsm/translations/fr.json @@ -64,6 +64,7 @@ "init": { "data": { "scan_interval": "Minutes entre les scans", + "snap_profile_type": "Niveau de qualit\u00e9 des instantan\u00e9s de la cam\u00e9ra (0 : \u00e9lev\u00e9 1 : moyen 2 : faible)", "timeout": "D\u00e9lai d'expiration (secondes)" } } diff --git a/homeassistant/components/system_bridge/translations/el.json b/homeassistant/components/system_bridge/translations/el.json index 327da66531489c..274b4b7d11a020 100644 --- a/homeassistant/components/system_bridge/translations/el.json +++ b/homeassistant/components/system_bridge/translations/el.json @@ -6,6 +6,9 @@ "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03c0\u03bf\u03c5 \u03ad\u03c7\u03b5\u03c4\u03b5 \u03bf\u03c1\u03af\u03c3\u03b5\u03b9 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03ae \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {name}." }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c4\u03b7\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2." } } diff --git a/homeassistant/components/tellduslive/translations/el.json b/homeassistant/components/tellduslive/translations/el.json index a7911f7f907676..068d8a80913306 100644 --- a/homeassistant/components/tellduslive/translations/el.json +++ b/homeassistant/components/tellduslive/translations/el.json @@ -5,6 +5,9 @@ "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 TelldusLive:\n 1. \u039a\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf\n 2. \u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf Telldus Live\n 3. \u0395\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7 **{\u03cc\u03bd\u03bf\u03bc\u03b1 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2}** (\u03ba\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf **\u039d\u03b1\u03b9**).\n 4. \u0395\u03c0\u03b9\u03c3\u03c4\u03c1\u03ad\u03c8\u03c4\u03b5 \u03b5\u03b4\u03ce \u03ba\u03b1\u03b9 \u03ba\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf **\u03a5\u03a0\u039f\u0392\u039f\u039b\u0397**.\n\n [\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd TelldusLive]({auth_url})" }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u039a\u03b5\u03bd\u03cc" } } diff --git a/homeassistant/components/tesla_wall_connector/translations/el.json b/homeassistant/components/tesla_wall_connector/translations/el.json index 286fbaee105e9e..65a346ccfbfcbd 100644 --- a/homeassistant/components/tesla_wall_connector/translations/el.json +++ b/homeassistant/components/tesla_wall_connector/translations/el.json @@ -3,6 +3,9 @@ "flow_title": "{serial_number} ({host})", "step": { "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Tesla Wall Connector" } } diff --git a/homeassistant/components/tolo/translations/el.json b/homeassistant/components/tolo/translations/el.json index 26e05764d2e456..df42b4400489ad 100644 --- a/homeassistant/components/tolo/translations/el.json +++ b/homeassistant/components/tolo/translations/el.json @@ -3,6 +3,9 @@ "flow_title": "{name}", "step": { "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 TOLO Sauna." } } diff --git a/homeassistant/components/tradfri/translations/el.json b/homeassistant/components/tradfri/translations/el.json index f4ccdf8b400afb..5499c9ae10da67 100644 --- a/homeassistant/components/tradfri/translations/el.json +++ b/homeassistant/components/tradfri/translations/el.json @@ -8,6 +8,7 @@ "step": { "auth": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "security_code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b1\u03c3\u03c6\u03b1\u03bb\u03b5\u03af\u03b1\u03c2" }, "description": "\u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03b2\u03c1\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03b1\u03c3\u03c6\u03b1\u03bb\u03b5\u03af\u03b1\u03c2 \u03c3\u03c4\u03bf \u03c0\u03af\u03c3\u03c9 \u03bc\u03ad\u03c1\u03bf\u03c2 \u03c4\u03b7\u03c2 \u03c0\u03cd\u03bb\u03b7\u03c2 \u03c3\u03b1\u03c2.", diff --git a/homeassistant/components/trafikverket_weatherstation/translations/el.json b/homeassistant/components/trafikverket_weatherstation/translations/el.json index 32688c432c4609..899af053888aa1 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/el.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/el.json @@ -8,6 +8,7 @@ "user": { "data": { "conditions": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03bf\u03cd\u03bc\u03b5\u03bd\u03b5\u03c2 \u03c3\u03c5\u03bd\u03b8\u03ae\u03ba\u03b5\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", "station": "\u03a3\u03c4\u03b1\u03b8\u03bc\u03cc\u03c2/\u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7" } } diff --git a/homeassistant/components/transmission/translations/el.json b/homeassistant/components/transmission/translations/el.json index e19942a36afa23..fc1a4535178308 100644 --- a/homeassistant/components/transmission/translations/el.json +++ b/homeassistant/components/transmission/translations/el.json @@ -5,6 +5,9 @@ }, "step": { "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03c1\u03bf\u03b3\u03c1\u03ac\u03bc\u03bc\u03b1\u03c4\u03bf\u03c2-\u03c0\u03b5\u03bb\u03ac\u03c4\u03b7 \u03bc\u03b5\u03c4\u03ac\u03b4\u03bf\u03c3\u03b7\u03c2" } } diff --git a/homeassistant/components/tuya/translations/el.json b/homeassistant/components/tuya/translations/el.json index b25fbd9f5df0c8..e69a3c00eedd01 100644 --- a/homeassistant/components/tuya/translations/el.json +++ b/homeassistant/components/tuya/translations/el.json @@ -51,6 +51,7 @@ "temp_step_override": "\u0392\u03ae\u03bc\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03c3\u03c4\u03cc\u03c7\u03bf\u03c5", "tuya_max_coltemp": "\u039c\u03ad\u03b3\u03b9\u03c3\u03c4\u03b7 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1 \u03c7\u03c1\u03ce\u03bc\u03b1\u03c4\u03bf\u03c2 \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03c6\u03ad\u03c1\u03b5\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" }, + "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u03c4\u03c9\u03bd \u03b5\u03bc\u03c6\u03b1\u03bd\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03c9\u03bd \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03b9\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae {device_type} `{device_name}`", "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 Tuya" }, "init": { diff --git a/homeassistant/components/tuya/translations/select.el.json b/homeassistant/components/tuya/translations/select.el.json index 27e26acabde6a9..654095e2cfda34 100644 --- a/homeassistant/components/tuya/translations/select.el.json +++ b/homeassistant/components/tuya/translations/select.el.json @@ -69,6 +69,10 @@ "incandescent": "\u03a0\u03c5\u03c1\u03b1\u03ba\u03c4\u03ce\u03c3\u03b5\u03c9\u03c2", "led": "LED" }, + "tuya__light_mode": { + "pos": "\u03a5\u03c0\u03bf\u03b4\u03b5\u03af\u03be\u03c4\u03b5 \u03c4\u03b7 \u03b8\u03ad\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b7", + "relay": "\u0388\u03bd\u03b4\u03b5\u03b9\u03be\u03b7 \u03c4\u03b7\u03c2 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2/\u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b7" + }, "tuya__motion_sensitivity": { "0": "\u03a7\u03b1\u03bc\u03b7\u03bb\u03ae \u03b5\u03c5\u03b1\u03b9\u03c3\u03b8\u03b7\u03c3\u03af\u03b1", "1": "\u039c\u03b5\u03c3\u03b1\u03af\u03b1 \u03b5\u03c5\u03b1\u03b9\u03c3\u03b8\u03b7\u03c3\u03af\u03b1", @@ -78,6 +82,10 @@ "1": "\u039a\u03b1\u03c4\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03c9\u03bd \u03bc\u03cc\u03bd\u03bf", "2": "\u03a3\u03c5\u03bd\u03b5\u03c7\u03ae\u03c2 \u03ba\u03b1\u03c4\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae" }, + "tuya__relay_status": { + "last": "\u0398\u03c5\u03bc\u03b7\u03b8\u03b5\u03af\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03b1 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7", + "memory": "\u0398\u03c5\u03bc\u03b7\u03b8\u03b5\u03af\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03b1 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7" + }, "tuya__vacuum_cistern": { "closed": "\u039a\u03bb\u03b5\u03b9\u03c3\u03c4\u03cc", "high": "\u03a5\u03c8\u03b7\u03bb\u03cc", diff --git a/homeassistant/components/tuya/translations/select.fr.json b/homeassistant/components/tuya/translations/select.fr.json index caebd296512a25..ab67be514bcdda 100644 --- a/homeassistant/components/tuya/translations/select.fr.json +++ b/homeassistant/components/tuya/translations/select.fr.json @@ -20,7 +20,12 @@ "cancel": "Annuler" }, "tuya__curtain_mode": { - "morning": "Matin" + "morning": "Matin", + "night": "Nuit" + }, + "tuya__curtain_motor_mode": { + "back": "Retour", + "forward": "Avance rapide" }, "tuya__decibel_sensitivity": { "0": "Faible sensibilit\u00e9", @@ -56,7 +61,10 @@ }, "tuya__humidifier_spray_mode": { "auto": "Auto", - "health": "Sant\u00e9" + "health": "Sant\u00e9", + "humidity": "Humidit\u00e9", + "sleep": "Sommeil", + "work": "Travail" }, "tuya__ipc_work_mode": { "0": "Mode faible consommation", diff --git a/homeassistant/components/twinkly/translations/fr.json b/homeassistant/components/twinkly/translations/fr.json index 92171723b5536a..c5b01400457faa 100644 --- a/homeassistant/components/twinkly/translations/fr.json +++ b/homeassistant/components/twinkly/translations/fr.json @@ -12,7 +12,7 @@ }, "user": { "data": { - "host": "Nom r\u00e9seau (ou adresse IP) de votre Twinkly" + "host": "H\u00f4te" }, "description": "Configurer votre Twinkly", "title": "Twinkly" diff --git a/homeassistant/components/unifi/translations/el.json b/homeassistant/components/unifi/translations/el.json index 13413fd102c722..a8efc9d1e953b4 100644 --- a/homeassistant/components/unifi/translations/el.json +++ b/homeassistant/components/unifi/translations/el.json @@ -11,7 +11,9 @@ "step": { "user": { "data": { - "site": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "site": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 UniFi" } diff --git a/homeassistant/components/unifiprotect/translations/el.json b/homeassistant/components/unifiprotect/translations/el.json index aad2455a8828e1..a6782ec138ac14 100644 --- a/homeassistant/components/unifiprotect/translations/el.json +++ b/homeassistant/components/unifiprotect/translations/el.json @@ -9,16 +9,24 @@ "flow_title": "{name} ( {ip_address} )", "step": { "discovery_confirm": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ( {ip_address});", "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03c4\u03bf UniFi Protect" }, "reauth_confirm": { "data": { - "host": "IP/Host \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae UniFi Protect" + "host": "IP/Host \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae UniFi Protect", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "UniFi Protect Reauth" }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03c4\u03bf\u03c0\u03b9\u03ba\u03cc \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03c0\u03bf\u03c5 \u03ad\u03c7\u03b5\u03b9 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03b7\u03b8\u03b5\u03af \u03c3\u03c4\u03b7\u03bd \u039a\u03bf\u03bd\u03c3\u03cc\u03bb\u03b1 UniFi OS \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5. \u039f\u03b9 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b5\u03c2 \u03c4\u03bf\u03c5 Ubiquiti Cloud \u03b4\u03b5\u03bd \u03b8\u03b1 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03bf\u03c5\u03bd. \u0393\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2: {local_user_documentation_url}", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 UniFi Protect" } diff --git a/homeassistant/components/unifiprotect/translations/fr.json b/homeassistant/components/unifiprotect/translations/fr.json index 1ba8c5aa5c6c9e..efd0cb529b179e 100644 --- a/homeassistant/components/unifiprotect/translations/fr.json +++ b/homeassistant/components/unifiprotect/translations/fr.json @@ -15,9 +15,11 @@ "discovery_confirm": { "data": { "password": "Mot de passe", - "username": "Nom d'utilisateur" + "username": "Nom d'utilisateur", + "verify_ssl": "V\u00e9rifier le certificat SSL" }, - "description": "Voulez-vous configurer {name} ( {ip_address} )\u00a0?" + "description": "Voulez-vous configurer {name} ({ip_address})? Vous aurez besoin d'un utilisateur local cr\u00e9\u00e9 dans votre console UniFi OS pour vous connecter. Les utilisateurs Ubiquiti Cloud ne fonctionneront pas. Pour plus d'informations\u00a0: {local_user_documentation_url}", + "title": "UniFi Protect d\u00e9couvert" }, "reauth_confirm": { "data": { @@ -36,6 +38,7 @@ "username": "Nom d'utilisateur", "verify_ssl": "V\u00e9rifier le certificat SSL" }, + "description": "Vous aurez besoin d'un utilisateur local cr\u00e9\u00e9 dans votre console UniFi OS pour vous connecter. Les utilisateurs Ubiquiti Cloud ne fonctionneront pas. Pour plus d'informations\u00a0: {local_user_documentation_url}", "title": "Configuration d'UniFi Protect" } } diff --git a/homeassistant/components/upcloud/translations/el.json b/homeassistant/components/upcloud/translations/el.json index e906610d5659fd..ed87fbbdf78186 100644 --- a/homeassistant/components/upcloud/translations/el.json +++ b/homeassistant/components/upcloud/translations/el.json @@ -1,4 +1,13 @@ { + "config": { + "step": { + "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } + } + } + }, "options": { "step": { "init": { diff --git a/homeassistant/components/vallox/translations/el.json b/homeassistant/components/vallox/translations/el.json index a4a16fd34eed0f..ec9ff8394b611f 100644 --- a/homeassistant/components/vallox/translations/el.json +++ b/homeassistant/components/vallox/translations/el.json @@ -2,6 +2,10 @@ "config": { "step": { "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Vallox. \u0395\u03ac\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03c0\u03c1\u03bf\u03b2\u03bb\u03ae\u03bc\u03b1\u03c4\u03b1 \u03bc\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 {integration_docs_url}.", "title": "Vallox" } diff --git a/homeassistant/components/venstar/translations/el.json b/homeassistant/components/venstar/translations/el.json index 1b7397feadda01..5105f19e2bc0fb 100644 --- a/homeassistant/components/venstar/translations/el.json +++ b/homeassistant/components/venstar/translations/el.json @@ -2,6 +2,10 @@ "config": { "step": { "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03b8\u03b5\u03c1\u03bc\u03bf\u03c3\u03c4\u03ac\u03c4\u03b7 Venstar" } } diff --git a/homeassistant/components/version/translations/ja.json b/homeassistant/components/version/translations/ja.json index a2eba2ffbc0e77..5ae560ddae8967 100644 --- a/homeassistant/components/version/translations/ja.json +++ b/homeassistant/components/version/translations/ja.json @@ -16,7 +16,7 @@ "beta": "\u30d9\u30fc\u30bf\u7248\u3092\u542b\u3081\u308b", "board": "\u3069\u306e\u30dc\u30fc\u30c9\u3092\u8ffd\u8de1\u3059\u308b\u304b", "channel": "\u3069\u306e\u30c1\u30e3\u30f3\u30cd\u30eb\u3092\u8ffd\u8de1\u3059\u308b\u304b", - "image": "\u3069\u306e\u753b\u50cf\u3092\u8ffd\u8de1\u3059\u308b\u304b" + "image": "\u3069\u306e\u30a4\u30e1\u30fc\u30b8\u3092\u8ffd\u3044\u304b\u3051\u308b\u304b" }, "description": "{version_source} \u30d0\u30fc\u30b8\u30e7\u30f3\u30c8\u30e9\u30c3\u30ad\u30f3\u30b0\u306e\u8a2d\u5b9a", "title": "\u8a2d\u5b9a" diff --git a/homeassistant/components/vicare/translations/el.json b/homeassistant/components/vicare/translations/el.json index a813fc3ede2fc5..7d43245e6176d2 100644 --- a/homeassistant/components/vicare/translations/el.json +++ b/homeassistant/components/vicare/translations/el.json @@ -5,6 +5,7 @@ "user": { "data": { "heating_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03b8\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "scan_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7\u03c2 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)" }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 ViCare. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://developer.viessmann.com", diff --git a/homeassistant/components/vlc_telnet/translations/el.json b/homeassistant/components/vlc_telnet/translations/el.json index e2c4495a77eb51..a5bd6ebdebecdb 100644 --- a/homeassistant/components/vlc_telnet/translations/el.json +++ b/homeassistant/components/vlc_telnet/translations/el.json @@ -7,6 +7,12 @@ }, "reauth_confirm": { "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c3\u03c9\u03c3\u03c4\u03cc \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae: {host}" + }, + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + } } } } diff --git a/homeassistant/components/wallbox/translations/el.json b/homeassistant/components/wallbox/translations/el.json index da02cbb297fb40..376a62b9ff094e 100644 --- a/homeassistant/components/wallbox/translations/el.json +++ b/homeassistant/components/wallbox/translations/el.json @@ -4,6 +4,11 @@ "reauth_invalid": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5. \u039f \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03b4\u03b5\u03bd \u03c4\u03b1\u03b9\u03c1\u03b9\u03ac\u03b6\u03b5\u03b9 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03b1\u03c1\u03c7\u03b9\u03ba\u03cc" }, "step": { + "reauth_confirm": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } + }, "user": { "data": { "station": "\u03a3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd" diff --git a/homeassistant/components/watttime/translations/el.json b/homeassistant/components/watttime/translations/el.json index 0a551fc8874bf8..e1fd6af43bc4cc 100644 --- a/homeassistant/components/watttime/translations/el.json +++ b/homeassistant/components/watttime/translations/el.json @@ -18,6 +18,9 @@ "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username}:" }, "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2:" } } diff --git a/homeassistant/components/waze_travel_time/translations/el.json b/homeassistant/components/waze_travel_time/translations/el.json index 2336b3ded4f646..c1d7d1676a99e2 100644 --- a/homeassistant/components/waze_travel_time/translations/el.json +++ b/homeassistant/components/waze_travel_time/translations/el.json @@ -4,6 +4,7 @@ "user": { "data": { "destination": "\u03a0\u03c1\u03bf\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "origin": "\u03a0\u03c1\u03bf\u03ad\u03bb\u03b5\u03c5\u03c3\u03b7", "region": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae" }, diff --git a/homeassistant/components/webostv/translations/el.json b/homeassistant/components/webostv/translations/el.json index 115f2d4cdf83a1..d03b04c6261a9a 100644 --- a/homeassistant/components/webostv/translations/el.json +++ b/homeassistant/components/webostv/translations/el.json @@ -14,6 +14,7 @@ }, "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1" }, "description": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7, \u03c3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9 \u03c0\u03b5\u03b4\u03af\u03b1 \u03ba\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03b7\u03bd \u03c5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae", diff --git a/homeassistant/components/webostv/translations/fr.json b/homeassistant/components/webostv/translations/fr.json index d6628f9747cbf9..bccb1c3aa3ccc4 100644 --- a/homeassistant/components/webostv/translations/fr.json +++ b/homeassistant/components/webostv/translations/fr.json @@ -1,6 +1,8 @@ { "config": { "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", "error_pairing": "Connect\u00e9 au t\u00e9l\u00e9viseur LG webOS mais non jumel\u00e9" }, "error": { diff --git a/homeassistant/components/whirlpool/translations/el.json b/homeassistant/components/whirlpool/translations/el.json new file mode 100644 index 00000000000000..18c2f0869bd1c2 --- /dev/null +++ b/homeassistant/components/whirlpool/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/el.json b/homeassistant/components/wiz/translations/el.json index 278dda5120894f..3741ef6a8f36dd 100644 --- a/homeassistant/components/wiz/translations/el.json +++ b/homeassistant/components/wiz/translations/el.json @@ -16,6 +16,9 @@ } }, "user": { + "data": { + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03ba\u03b1\u03b9 \u03ad\u03bd\u03b1 \u03cc\u03bd\u03bf\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03bd\u03ad\u03bf \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b1:" } } diff --git a/homeassistant/components/wiz/translations/fr.json b/homeassistant/components/wiz/translations/fr.json index 5d7bc4006009c1..e6123a1d7159b9 100644 --- a/homeassistant/components/wiz/translations/fr.json +++ b/homeassistant/components/wiz/translations/fr.json @@ -1,12 +1,35 @@ { "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "no_devices_found": "Aucun appareil trouv\u00e9 sur le r\u00e9seau" + }, "error": { - "no_ip": "Adresse IP non valide" + "bulb_time_out": "Impossible de se connecter \u00e0 l'ampoule. Peut-\u00eatre que l'ampoule est hors ligne ou qu'une mauvaise adresse IP a \u00e9t\u00e9 saisie. Veuillez allumer la lumi\u00e8re et r\u00e9essayer\u00a0!", + "cannot_connect": "\u00c9chec de connexion", + "no_ip": "Adresse IP non valide", + "no_wiz_light": "L'ampoule ne peut pas \u00eatre connect\u00e9e via l'int\u00e9gration de la plate-forme WiZ.", + "unknown": "Erreur inattendue" }, "flow_title": "{name} ({host})", "step": { + "confirm": { + "description": "Voulez-vous commencer la configuration ?" + }, "discovery_confirm": { "description": "Voulez-vous configurer {name} ({host}) ?" + }, + "pick_device": { + "data": { + "device": "Appareil" + } + }, + "user": { + "data": { + "host": "Adresse IP", + "name": "Nom" + }, + "description": "Si vous laissez l'adresse IP vide, la d\u00e9couverte sera utilis\u00e9e pour trouver des appareils." } } } diff --git a/homeassistant/components/wolflink/translations/el.json b/homeassistant/components/wolflink/translations/el.json new file mode 100644 index 00000000000000..18c2f0869bd1c2 --- /dev/null +++ b/homeassistant/components/wolflink/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yale_smart_alarm/translations/el.json b/homeassistant/components/yale_smart_alarm/translations/el.json index 3d46faee2ebb35..2af575a32a1f7b 100644 --- a/homeassistant/components/yale_smart_alarm/translations/el.json +++ b/homeassistant/components/yale_smart_alarm/translations/el.json @@ -3,12 +3,14 @@ "step": { "reauth_confirm": { "data": { - "area_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae\u03c2" + "area_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" } }, "user": { "data": { - "area_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae\u03c2" + "area_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" } } } diff --git a/homeassistant/components/yale_smart_alarm/translations/fr.json b/homeassistant/components/yale_smart_alarm/translations/fr.json index b78e4a327a122e..76065006684c84 100644 --- a/homeassistant/components/yale_smart_alarm/translations/fr.json +++ b/homeassistant/components/yale_smart_alarm/translations/fr.json @@ -11,7 +11,7 @@ "step": { "reauth_confirm": { "data": { - "area_id": "ID de la zone", + "area_id": "ID de zone", "name": "Nom", "password": "Mot de passe", "username": "Nom d'utilisateur" diff --git a/homeassistant/components/yamaha_musiccast/translations/el.json b/homeassistant/components/yamaha_musiccast/translations/el.json index a5118ffd8492e1..d11e8bdc70993b 100644 --- a/homeassistant/components/yamaha_musiccast/translations/el.json +++ b/homeassistant/components/yamaha_musiccast/translations/el.json @@ -9,6 +9,9 @@ "flow_title": "MusicCast: {name}", "step": { "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf MusicCast \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Home Assistant." } } diff --git a/homeassistant/components/youless/translations/el.json b/homeassistant/components/youless/translations/el.json new file mode 100644 index 00000000000000..1dbdef83eea4e3 --- /dev/null +++ b/homeassistant/components/youless/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave_me/translations/fr.json b/homeassistant/components/zwave_me/translations/fr.json new file mode 100644 index 00000000000000..1705796cf77b96 --- /dev/null +++ b/homeassistant/components/zwave_me/translations/fr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "no_valid_uuid_set": "Aucun ensemble UUID valide" + }, + "error": { + "no_valid_uuid_set": "Aucun ensemble UUID valide" + }, + "step": { + "user": { + "data": { + "token": "Jeton", + "url": "URL" + }, + "description": "Entrez l'adresse IP du serveur Z-Way et le jeton d'acc\u00e8s Z-Way. L'adresse IP peut \u00eatre pr\u00e9c\u00e9d\u00e9e de wss:// si HTTPS doit \u00eatre utilis\u00e9 \u00e0 la place de HTTP. Pour obtenir le jeton, acc\u00e9dez \u00e0 l'interface utilisateur Z-Way > Menu > Param\u00e8tres > Utilisateur > Jeton API. Il est sugg\u00e9r\u00e9 de cr\u00e9er un nouvel utilisateur pour Home Assistant et d'accorder l'acc\u00e8s aux appareils que vous devez contr\u00f4ler \u00e0 partir de Home Assistant. Il est \u00e9galement possible d'utiliser l'acc\u00e8s \u00e0 distance via find.z-wave.me pour connecter un Z-Way distant. Entrez wss://find.z-wave.me dans le champ IP et copiez le jeton avec la port\u00e9e globale (connectez-vous \u00e0 Z-Way via find.z-wave.me pour cela)." + } + } + } +} \ No newline at end of file From 051bf173dc2ec2dfa392b46d543d3cd72a66b9cd Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 14 Feb 2022 02:49:19 -0500 Subject: [PATCH 0601/1098] revert change in vizio logic to fix bug (#66424) --- .../components/vizio/media_player.py | 22 ++++++------------- tests/components/vizio/test_media_player.py | 3 +-- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/vizio/media_player.py b/homeassistant/components/vizio/media_player.py index e9cd89c635a670..664a8ae7da86e0 100644 --- a/homeassistant/components/vizio/media_player.py +++ b/homeassistant/components/vizio/media_player.py @@ -145,7 +145,7 @@ def __init__( self._volume_step = config_entry.options[CONF_VOLUME_STEP] self._current_input = None self._current_app_config = None - self._app_name = None + self._attr_app_name = None self._available_inputs = [] self._available_apps = [] self._all_apps = apps_coordinator.data if apps_coordinator else None @@ -209,7 +209,7 @@ async def async_update(self) -> None: self._attr_volume_level = None self._attr_is_volume_muted = None self._current_input = None - self._app_name = None + self._attr_app_name = None self._current_app_config = None self._attr_sound_mode = None return @@ -265,13 +265,13 @@ async def async_update(self) -> None: log_api_exception=False ) - self._app_name = find_app_name( + self._attr_app_name = find_app_name( self._current_app_config, [APP_HOME, *self._all_apps, *self._additional_app_configs], ) - if self._app_name == NO_APP_RUNNING: - self._app_name = None + if self._attr_app_name == NO_APP_RUNNING: + self._attr_app_name = None def _get_additional_app_names(self) -> list[dict[str, Any]]: """Return list of additional apps that were included in configuration.yaml.""" @@ -337,8 +337,8 @@ def apps_list_update(): @property def source(self) -> str | None: """Return current input of the device.""" - if self._app_name is not None and self._current_input in INPUT_APPS: - return self._app_name + if self._attr_app_name is not None and self._current_input in INPUT_APPS: + return self._attr_app_name return self._current_input @@ -364,14 +364,6 @@ def source_list(self) -> list[str]: return self._available_inputs - @property - def app_name(self) -> str | None: - """Return the name of the current app.""" - if self.source == self._app_name: - return self._app_name - - return None - @property def app_id(self) -> str | None: """Return the ID of the current app if it is unknown by pyvizio.""" diff --git a/tests/components/vizio/test_media_player.py b/tests/components/vizio/test_media_player.py index d3ef4019c57258..80f722809511ea 100644 --- a/tests/components/vizio/test_media_player.py +++ b/tests/components/vizio/test_media_player.py @@ -764,6 +764,5 @@ async def test_vizio_update_with_apps_on_input( ) await _add_config_entry_to_hass(hass, config_entry) attr = _get_attr_and_assert_base_attr(hass, DEVICE_CLASS_TV, STATE_ON) - # App name and app ID should not be in the attributes - assert "app_name" not in attr + # app ID should not be in the attributes assert "app_id" not in attr From 71540a924b6ee82844b55b2efb37de484490d7e1 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Mon, 14 Feb 2022 08:51:22 +0100 Subject: [PATCH 0602/1098] Update requirements_test.txt (#66481) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index bd83e1505a2843..bf98c8a449ec7a 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -24,7 +24,7 @@ pytest-test-groups==1.0.3 pytest-sugar==0.9.4 pytest-timeout==2.1.0 pytest-xdist==2.4.0 -pytest==7.0.0 +pytest==7.0.1 requests_mock==1.9.2 respx==0.19.0 stdlib-list==0.7.0 From 35b343de9e8695fdbf10510f6ad82140990a033e Mon Sep 17 00:00:00 2001 From: Ryan Fleming Date: Mon, 14 Feb 2022 05:05:06 -0500 Subject: [PATCH 0603/1098] Octoprint buttons (#66368) --- .../components/octoprint/__init__.py | 2 +- homeassistant/components/octoprint/button.py | 133 ++++++++++++ tests/components/octoprint/test_button.py | 195 ++++++++++++++++++ 3 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/octoprint/button.py create mode 100644 tests/components/octoprint/test_button.py diff --git a/homeassistant/components/octoprint/__init__.py b/homeassistant/components/octoprint/__init__.py index 6c1eb62831c875..f92cf0c8d30b7f 100644 --- a/homeassistant/components/octoprint/__init__.py +++ b/homeassistant/components/octoprint/__init__.py @@ -52,7 +52,7 @@ def ensure_valid_path(value): return value -PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] +PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.SENSOR] DEFAULT_NAME = "OctoPrint" CONF_NUMBER_OF_TOOLS = "number_of_tools" CONF_BED = "bed" diff --git a/homeassistant/components/octoprint/button.py b/homeassistant/components/octoprint/button.py new file mode 100644 index 00000000000000..97676592f4753d --- /dev/null +++ b/homeassistant/components/octoprint/button.py @@ -0,0 +1,133 @@ +"""Support for Octoprint buttons.""" +from pyoctoprintapi import OctoprintClient, OctoprintPrinterInfo + +from homeassistant.components.button import ButtonEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from . import OctoprintDataUpdateCoordinator +from .const import DOMAIN + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Octoprint control buttons.""" + coordinator: OctoprintDataUpdateCoordinator = hass.data[DOMAIN][ + config_entry.entry_id + ]["coordinator"] + client: OctoprintClient = hass.data[DOMAIN][config_entry.entry_id]["client"] + device_id = config_entry.unique_id + assert device_id is not None + + async_add_entities( + [ + OctoprintResumeJobButton(coordinator, device_id, client), + OctoprintPauseJobButton(coordinator, device_id, client), + OctoprintStopJobButton(coordinator, device_id, client), + ] + ) + + +class OctoprintButton(CoordinatorEntity, ButtonEntity): + """Represent an OctoPrint binary sensor.""" + + coordinator: OctoprintDataUpdateCoordinator + client: OctoprintClient + + def __init__( + self, + coordinator: OctoprintDataUpdateCoordinator, + button_type: str, + device_id: str, + client: OctoprintClient, + ) -> None: + """Initialize a new OctoPrint button.""" + super().__init__(coordinator) + self.client = client + self._device_id = device_id + self._attr_name = f"OctoPrint {button_type}" + self._attr_unique_id = f"{button_type}-{device_id}" + + @property + def device_info(self): + """Device info.""" + return self.coordinator.device_info + + @property + def available(self) -> bool: + """Return if entity is available.""" + return self.coordinator.last_update_success and self.coordinator.data["printer"] + + +class OctoprintPauseJobButton(OctoprintButton): + """Pause the active job.""" + + def __init__( + self, + coordinator: OctoprintDataUpdateCoordinator, + device_id: str, + client: OctoprintClient, + ) -> None: + """Initialize a new OctoPrint button.""" + super().__init__(coordinator, "Pause Job", device_id, client) + + async def async_press(self) -> None: + """Handle the button press.""" + printer: OctoprintPrinterInfo = self.coordinator.data["printer"] + + if printer.state.flags.printing: + await self.client.pause_job() + elif not printer.state.flags.paused and not printer.state.flags.pausing: + raise InvalidPrinterState("Printer is not printing") + + +class OctoprintResumeJobButton(OctoprintButton): + """Resume the active job.""" + + def __init__( + self, + coordinator: OctoprintDataUpdateCoordinator, + device_id: str, + client: OctoprintClient, + ) -> None: + """Initialize a new OctoPrint button.""" + super().__init__(coordinator, "Resume Job", device_id, client) + + async def async_press(self) -> None: + """Handle the button press.""" + printer: OctoprintPrinterInfo = self.coordinator.data["printer"] + + if printer.state.flags.paused: + await self.client.resume_job() + elif not printer.state.flags.printing and not printer.state.flags.resuming: + raise InvalidPrinterState("Printer is not currently paused") + + +class OctoprintStopJobButton(OctoprintButton): + """Resume the active job.""" + + def __init__( + self, + coordinator: OctoprintDataUpdateCoordinator, + device_id: str, + client: OctoprintClient, + ) -> None: + """Initialize a new OctoPrint button.""" + super().__init__(coordinator, "Stop Job", device_id, client) + + async def async_press(self) -> None: + """Handle the button press.""" + printer: OctoprintPrinterInfo = self.coordinator.data["printer"] + + if printer.state.flags.printing or printer.state.flags.paused: + await self.client.cancel_job() + + +class InvalidPrinterState(HomeAssistantError): + """Service attempted in invalid state.""" diff --git a/tests/components/octoprint/test_button.py b/tests/components/octoprint/test_button.py new file mode 100644 index 00000000000000..603739159afb06 --- /dev/null +++ b/tests/components/octoprint/test_button.py @@ -0,0 +1,195 @@ +"""Test the OctoPrint buttons.""" +from unittest.mock import patch + +from pyoctoprintapi import OctoprintPrinterInfo +import pytest + +from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN +from homeassistant.components.button.const import SERVICE_PRESS +from homeassistant.components.octoprint import OctoprintDataUpdateCoordinator +from homeassistant.components.octoprint.button import InvalidPrinterState +from homeassistant.components.octoprint.const import DOMAIN +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.core import HomeAssistant + +from . import init_integration + + +async def test_pause_job(hass: HomeAssistant): + """Test the pause job button.""" + await init_integration(hass, BUTTON_DOMAIN) + + corrdinator: OctoprintDataUpdateCoordinator = hass.data[DOMAIN]["uuid"][ + "coordinator" + ] + + # Test pausing the printer when it is printing + with patch("pyoctoprintapi.OctoprintClient.pause_job") as pause_command: + corrdinator.data["printer"] = OctoprintPrinterInfo( + {"state": {"flags": {"printing": True}}, "temperature": []} + ) + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + { + ATTR_ENTITY_ID: "button.octoprint_pause_job", + }, + blocking=True, + ) + + assert len(pause_command.mock_calls) == 1 + + # Test pausing the printer when it is paused + with patch("pyoctoprintapi.OctoprintClient.pause_job") as pause_command: + corrdinator.data["printer"] = OctoprintPrinterInfo( + {"state": {"flags": {"printing": False, "paused": True}}, "temperature": []} + ) + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + { + ATTR_ENTITY_ID: "button.octoprint_pause_job", + }, + blocking=True, + ) + + assert len(pause_command.mock_calls) == 0 + + # Test pausing the printer when it is stopped + with patch( + "pyoctoprintapi.OctoprintClient.pause_job" + ) as pause_command, pytest.raises(InvalidPrinterState): + corrdinator.data["printer"] = OctoprintPrinterInfo( + { + "state": {"flags": {"printing": False, "paused": False}}, + "temperature": [], + } + ) + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + { + ATTR_ENTITY_ID: "button.octoprint_pause_job", + }, + blocking=True, + ) + + +async def test_resume_job(hass: HomeAssistant): + """Test the resume job button.""" + await init_integration(hass, BUTTON_DOMAIN) + + corrdinator: OctoprintDataUpdateCoordinator = hass.data[DOMAIN]["uuid"][ + "coordinator" + ] + + # Test resuming the printer when it is paused + with patch("pyoctoprintapi.OctoprintClient.resume_job") as resume_command: + corrdinator.data["printer"] = OctoprintPrinterInfo( + {"state": {"flags": {"printing": False, "paused": True}}, "temperature": []} + ) + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + { + ATTR_ENTITY_ID: "button.octoprint_resume_job", + }, + blocking=True, + ) + + assert len(resume_command.mock_calls) == 1 + + # Test resuming the printer when it is printing + with patch("pyoctoprintapi.OctoprintClient.resume_job") as resume_command: + corrdinator.data["printer"] = OctoprintPrinterInfo( + {"state": {"flags": {"printing": True, "paused": False}}, "temperature": []} + ) + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + { + ATTR_ENTITY_ID: "button.octoprint_resume_job", + }, + blocking=True, + ) + + assert len(resume_command.mock_calls) == 0 + + # Test resuming the printer when it is stopped + with patch( + "pyoctoprintapi.OctoprintClient.resume_job" + ) as resume_command, pytest.raises(InvalidPrinterState): + corrdinator.data["printer"] = OctoprintPrinterInfo( + { + "state": {"flags": {"printing": False, "paused": False}}, + "temperature": [], + } + ) + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + { + ATTR_ENTITY_ID: "button.octoprint_resume_job", + }, + blocking=True, + ) + + +async def test_stop_job(hass: HomeAssistant): + """Test the stop job button.""" + await init_integration(hass, BUTTON_DOMAIN) + + corrdinator: OctoprintDataUpdateCoordinator = hass.data[DOMAIN]["uuid"][ + "coordinator" + ] + + # Test stopping the printer when it is paused + with patch("pyoctoprintapi.OctoprintClient.cancel_job") as stop_command: + corrdinator.data["printer"] = OctoprintPrinterInfo( + {"state": {"flags": {"printing": False, "paused": True}}, "temperature": []} + ) + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + { + ATTR_ENTITY_ID: "button.octoprint_stop_job", + }, + blocking=True, + ) + + assert len(stop_command.mock_calls) == 1 + + # Test stopping the printer when it is printing + with patch("pyoctoprintapi.OctoprintClient.cancel_job") as stop_command: + corrdinator.data["printer"] = OctoprintPrinterInfo( + {"state": {"flags": {"printing": True, "paused": False}}, "temperature": []} + ) + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + { + ATTR_ENTITY_ID: "button.octoprint_stop_job", + }, + blocking=True, + ) + + assert len(stop_command.mock_calls) == 1 + + # Test stopping the printer when it is stopped + with patch("pyoctoprintapi.OctoprintClient.cancel_job") as stop_command: + corrdinator.data["printer"] = OctoprintPrinterInfo( + { + "state": {"flags": {"printing": False, "paused": False}}, + "temperature": [], + } + ) + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + { + ATTR_ENTITY_ID: "button.octoprint_stop_job", + }, + blocking=True, + ) + + assert len(stop_command.mock_calls) == 0 From 0a7b1dec7d026b2143a13dcc09131096f0ac0eda Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 14 Feb 2022 11:13:10 +0100 Subject: [PATCH 0604/1098] Adjust type hint in core add_job (#66503) Co-authored-by: epenet --- homeassistant/core.py | 4 +++- homeassistant/helpers/discovery.py | 10 ++-------- homeassistant/helpers/entity.py | 2 +- homeassistant/helpers/entity_component.py | 2 +- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index b8d159893fe214..5685b479d1c3f1 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -349,7 +349,9 @@ async def async_start(self) -> None: self.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) _async_create_timer(self) - def add_job(self, target: Callable[..., Any], *args: Any) -> None: + def add_job( + self, target: Callable[..., Any] | Coroutine[Any, Any, Any], *args: Any + ) -> None: """Add a job to be executed by the event loop or by an executor. If the job is either a coroutine or decorated with @callback, it will be diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py index ed90b5b893b584..20819ac75047cb 100644 --- a/homeassistant/helpers/discovery.py +++ b/homeassistant/helpers/discovery.py @@ -66,11 +66,7 @@ def discover( hass_config: ConfigType, ) -> None: """Fire discovery event. Can ensure a component is loaded.""" - hass.add_job( - async_discover( # type: ignore - hass, service, discovered, component, hass_config - ) - ) + hass.add_job(async_discover(hass, service, discovered, component, hass_config)) @bind_hass @@ -131,9 +127,7 @@ def load_platform( ) -> None: """Load a component and platform dynamically.""" hass.add_job( - async_load_platform( # type: ignore - hass, component, platform, discovered, hass_config - ) + async_load_platform(hass, component, platform, discovered, hass_config) ) diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index e9038d1f658593..bf2e13c1e24805 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -674,7 +674,7 @@ def schedule_update_ha_state(self, force_refresh: bool = False) -> None: If state is changed more than once before the ha state change task has been executed, the intermediate state transitions will be missed. """ - self.hass.add_job(self.async_update_ha_state(force_refresh)) # type: ignore + self.hass.add_job(self.async_update_ha_state(force_refresh)) @callback def async_schedule_update_ha_state(self, force_refresh: bool = False) -> None: diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index da6732d05e76fe..a1dba0d69622f6 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -105,7 +105,7 @@ def setup(self, config: ConfigType) -> None: This doesn't block the executor to protect from deadlocks. """ - self.hass.add_job(self.async_setup(config)) # type: ignore + self.hass.add_job(self.async_setup(config)) async def async_setup(self, config: ConfigType) -> None: """Set up a full entity component. From 5c5bb4835e9bcffb422565487f18daa10d5935cc Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Mon, 14 Feb 2022 12:14:57 +0200 Subject: [PATCH 0605/1098] Enable assumed state in webostv media player (#66486) --- homeassistant/components/webostv/media_player.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index f263d79e2dbbb8..95e0b059538794 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -138,6 +138,7 @@ def __init__( """Initialize the webos device.""" self._wrapper = wrapper self._client: WebOsClient = wrapper.client + self._attr_assumed_state = True self._attr_name = name self._attr_unique_id = unique_id self._sources = sources From 211b5b02df66bff92ae06efbf1b728a1bd2fadbe Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 14 Feb 2022 12:43:36 +0100 Subject: [PATCH 0606/1098] Fix access to hass.data in hdmi-cec (#66504) Co-authored-by: epenet --- homeassistant/components/hdmi_cec/media_player.py | 8 ++++---- homeassistant/components/hdmi_cec/switch.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/hdmi_cec/media_player.py b/homeassistant/components/hdmi_cec/media_player.py index c31daa85316e8e..9ee705c1c5eb7a 100644 --- a/homeassistant/components/hdmi_cec/media_player.py +++ b/homeassistant/components/hdmi_cec/media_player.py @@ -26,7 +26,7 @@ from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( - DOMAIN, + DOMAIN as MP_DOMAIN, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, @@ -48,11 +48,11 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import ATTR_NEW, CecEntity +from . import ATTR_NEW, DOMAIN, CecEntity _LOGGER = logging.getLogger(__name__) -ENTITY_ID_FORMAT = DOMAIN + ".{}" +ENTITY_ID_FORMAT = MP_DOMAIN + ".{}" def setup_platform( @@ -77,7 +77,7 @@ class CecPlayerEntity(CecEntity, MediaPlayerEntity): def __init__(self, device, logical) -> None: """Initialize the HDMI device.""" CecEntity.__init__(self, device, logical) - self.entity_id = f"{DOMAIN}.hdmi_{hex(self._logical_address)[2:]}" + self.entity_id = f"{MP_DOMAIN}.hdmi_{hex(self._logical_address)[2:]}" def send_keypress(self, key): """Send keypress to CEC adapter.""" diff --git a/homeassistant/components/hdmi_cec/switch.py b/homeassistant/components/hdmi_cec/switch.py index 8e6deae13946bd..a5d64b2a7fa478 100644 --- a/homeassistant/components/hdmi_cec/switch.py +++ b/homeassistant/components/hdmi_cec/switch.py @@ -3,17 +3,17 @@ import logging -from homeassistant.components.switch import DOMAIN, SwitchEntity +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN, SwitchEntity from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import ATTR_NEW, CecEntity +from . import ATTR_NEW, DOMAIN, CecEntity _LOGGER = logging.getLogger(__name__) -ENTITY_ID_FORMAT = DOMAIN + ".{}" +ENTITY_ID_FORMAT = SWITCH_DOMAIN + ".{}" def setup_platform( @@ -38,7 +38,7 @@ class CecSwitchEntity(CecEntity, SwitchEntity): def __init__(self, device, logical) -> None: """Initialize the HDMI device.""" CecEntity.__init__(self, device, logical) - self.entity_id = f"{DOMAIN}.hdmi_{hex(self._logical_address)[2:]}" + self.entity_id = f"{SWITCH_DOMAIN}.hdmi_{hex(self._logical_address)[2:]}" def turn_on(self, **kwargs) -> None: """Turn device on.""" From c9d99ad76db967a4d7e29c041ec48e545e3cc8e6 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 14 Feb 2022 12:51:52 +0100 Subject: [PATCH 0607/1098] Add missing dataclass decorator [fivem] (#66505) --- homeassistant/components/fivem/binary_sensor.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/fivem/binary_sensor.py b/homeassistant/components/fivem/binary_sensor.py index 20ea057da6e717..f3f253fe530e41 100644 --- a/homeassistant/components/fivem/binary_sensor.py +++ b/homeassistant/components/fivem/binary_sensor.py @@ -1,4 +1,6 @@ """The FiveM binary sensor platform.""" +from dataclasses import dataclass + from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, @@ -12,6 +14,7 @@ from .const import DOMAIN, NAME_STATUS +@dataclass class FiveMBinarySensorEntityDescription( BinarySensorEntityDescription, FiveMEntityDescription ): From 5a02bae63e72641e271d34c2540d6a72cb86c8b7 Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Mon, 14 Feb 2022 22:16:05 +1000 Subject: [PATCH 0608/1098] Bump Advantage Air 0.3.0 (#66488) --- homeassistant/components/advantage_air/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/advantage_air/manifest.json b/homeassistant/components/advantage_air/manifest.json index a230208a04e9e8..cbc5df6449622a 100644 --- a/homeassistant/components/advantage_air/manifest.json +++ b/homeassistant/components/advantage_air/manifest.json @@ -7,7 +7,7 @@ "@Bre77" ], "requirements": [ - "advantage_air==0.2.6" + "advantage_air==0.3.0" ], "quality_scale": "platinum", "iot_class": "local_polling", diff --git a/requirements_all.txt b/requirements_all.txt index 65520ab40b9485..89a326e672dbdf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -114,7 +114,7 @@ adext==0.4.2 adguardhome==0.5.1 # homeassistant.components.advantage_air -advantage_air==0.2.6 +advantage_air==0.3.0 # homeassistant.components.frontier_silicon afsapi==0.0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 771dcc8cf8786a..c12edbf285047a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -70,7 +70,7 @@ adext==0.4.2 adguardhome==0.5.1 # homeassistant.components.advantage_air -advantage_air==0.2.6 +advantage_air==0.3.0 # homeassistant.components.agent_dvr agent-py==0.0.23 From 370832f527cff236a86a3efe5a6e2a89169c36f7 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 14 Feb 2022 13:40:31 +0100 Subject: [PATCH 0609/1098] Fix http typing (#66506) --- .core_files.yaml | 1 + homeassistant/components/http/__init__.py | 11 ++++++----- homeassistant/components/http/ban.py | 8 ++++++-- homeassistant/components/http/forwarded.py | 6 ++++-- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/.core_files.yaml b/.core_files.yaml index 374a8957bcc7f0..b07dc04cd15bac 100644 --- a/.core_files.yaml +++ b/.core_files.yaml @@ -64,6 +64,7 @@ components: &components - homeassistant/components/group/* - homeassistant/components/hassio/* - homeassistant/components/homeassistant/** + - homeassistant/components/http/** - homeassistant/components/image/* - homeassistant/components/input_boolean/* - homeassistant/components/input_button/* diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index bb168fce09fde2..764138ca5f3a6e 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -1,11 +1,11 @@ """Support to serve the Home Assistant API as WSGI application.""" from __future__ import annotations -from ipaddress import ip_network +from ipaddress import IPv4Network, IPv6Network, ip_network import logging import os import ssl -from typing import Any, Final, Optional, TypedDict, cast +from typing import Any, Final, Optional, TypedDict, Union, cast from aiohttp import web from aiohttp.typedefs import StrOrURL @@ -109,7 +109,7 @@ class ConfData(TypedDict, total=False): ssl_key: str cors_allowed_origins: list[str] use_x_forwarded_for: bool - trusted_proxies: list[str] + trusted_proxies: list[IPv4Network | IPv6Network] login_attempts_threshold: int ip_ban_enabled: bool ssl_profile: str @@ -216,7 +216,7 @@ def __init__( ssl_key: str | None, server_host: list[str] | None, server_port: int, - trusted_proxies: list[str], + trusted_proxies: list[IPv4Network | IPv6Network], ssl_profile: str, ) -> None: """Initialize the HTTP Home Assistant server.""" @@ -399,7 +399,8 @@ async def start_http_server_and_save_config( if CONF_TRUSTED_PROXIES in conf: conf[CONF_TRUSTED_PROXIES] = [ - str(ip.network_address) for ip in conf[CONF_TRUSTED_PROXIES] + str(cast(Union[IPv4Network, IPv6Network], ip).network_address) + for ip in conf[CONF_TRUSTED_PROXIES] ] store.async_delay_save(lambda: conf, SAVE_DELAY) diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py index b50555b9841239..292c46e55f9cb0 100644 --- a/homeassistant/components/http/ban.py +++ b/homeassistant/components/http/ban.py @@ -6,7 +6,7 @@ from contextlib import suppress from datetime import datetime from http import HTTPStatus -from ipaddress import ip_address +from ipaddress import IPv4Address, IPv6Address, ip_address import logging from socket import gethostbyaddr, herror from typing import Any, Final @@ -189,7 +189,11 @@ async def process_success_login(request: Request) -> None: class IpBan: """Represents banned IP address.""" - def __init__(self, ip_ban: str, banned_at: datetime | None = None) -> None: + def __init__( + self, + ip_ban: str | IPv4Address | IPv6Address, + banned_at: datetime | None = None, + ) -> None: """Initialize IP Ban object.""" self.ip_address = ip_address(ip_ban) self.banned_at = banned_at or dt_util.utcnow() diff --git a/homeassistant/components/http/forwarded.py b/homeassistant/components/http/forwarded.py index ff50e9bd9658f5..c0aaa31fab0142 100644 --- a/homeassistant/components/http/forwarded.py +++ b/homeassistant/components/http/forwarded.py @@ -2,7 +2,7 @@ from __future__ import annotations from collections.abc import Awaitable, Callable -from ipaddress import ip_address +from ipaddress import IPv4Network, IPv6Network, ip_address import logging from types import ModuleType from typing import Literal @@ -17,7 +17,9 @@ @callback def async_setup_forwarded( - app: Application, use_x_forwarded_for: bool | None, trusted_proxies: list[str] + app: Application, + use_x_forwarded_for: bool | None, + trusted_proxies: list[IPv4Network | IPv6Network], ) -> None: """Create forwarded middleware for the app. From dbd26c7faf26e4e318362e54e4e3398d5f12b384 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 14 Feb 2022 13:59:11 +0100 Subject: [PATCH 0610/1098] Support browsing multiple Spotify accounts (#66256) * Support browsing multiple Spotify accounts * Fix rebase mistakes * Address review comments * Return root spotify node with config entries as children * Add util to get spotify URI for media browser URL * Only support browsing spotify with config entry specified --- homeassistant/components/spotify/__init__.py | 7 ++- .../components/spotify/browse_media.py | 57 +++++++++++++++++-- homeassistant/components/spotify/util.py | 10 ++++ 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/spotify/__init__.py b/homeassistant/components/spotify/__init__.py index 59ebf1ead55f18..c057ea240c040e 100644 --- a/homeassistant/components/spotify/__init__.py +++ b/homeassistant/components/spotify/__init__.py @@ -30,7 +30,11 @@ from . import config_flow from .browse_media import async_browse_media from .const import DOMAIN, LOGGER, SPOTIFY_SCOPES -from .util import is_spotify_media_type, resolve_spotify_media_type +from .util import ( + is_spotify_media_type, + resolve_spotify_media_type, + spotify_uri_from_media_browser_url, +) CONFIG_SCHEMA = vol.Schema( { @@ -50,6 +54,7 @@ __all__ = [ "async_browse_media", "DOMAIN", + "spotify_uri_from_media_browser_url", "is_spotify_media_type", "resolve_spotify_media_type", ] diff --git a/homeassistant/components/spotify/browse_media.py b/homeassistant/components/spotify/browse_media.py index efaa07b317286a..db2379a57fc379 100644 --- a/homeassistant/components/spotify/browse_media.py +++ b/homeassistant/components/spotify/browse_media.py @@ -6,11 +6,13 @@ from typing import Any from spotipy import Spotify +import yarl from homeassistant.backports.enum import StrEnum from homeassistant.components.media_player import BrowseError, BrowseMedia from homeassistant.components.media_player.const import ( MEDIA_CLASS_ALBUM, + MEDIA_CLASS_APP, MEDIA_CLASS_ARTIST, MEDIA_CLASS_DIRECTORY, MEDIA_CLASS_EPISODE, @@ -137,15 +139,53 @@ class UnknownMediaType(BrowseError): async def async_browse_media( hass: HomeAssistant, - media_content_type: str, - media_content_id: str, + media_content_type: str | None, + media_content_id: str | None, *, can_play_artist: bool = True, ) -> BrowseMedia: """Browse Spotify media.""" - if not (info := next(iter(hass.data[DOMAIN].values()), None)): - raise BrowseError("No Spotify accounts available") - return await async_browse_media_internal( + parsed_url = None + info = None + + # Check if caller is requesting the root nodes + if media_content_type is None and media_content_id is None: + children = [] + for config_entry_id, info in hass.data[DOMAIN].items(): + config_entry = hass.config_entries.async_get_entry(config_entry_id) + assert config_entry is not None + children.append( + BrowseMedia( + title=config_entry.title, + media_class=MEDIA_CLASS_APP, + media_content_id=f"{MEDIA_PLAYER_PREFIX}{config_entry_id}", + media_content_type=f"{MEDIA_PLAYER_PREFIX}library", + thumbnail="https://brands.home-assistant.io/_/spotify/logo.png", + can_play=False, + can_expand=True, + ) + ) + return BrowseMedia( + title="Spotify", + media_class=MEDIA_CLASS_APP, + media_content_id=MEDIA_PLAYER_PREFIX, + media_content_type="spotify", + thumbnail="https://brands.home-assistant.io/_/spotify/logo.png", + can_play=False, + can_expand=True, + children=children, + ) + + if media_content_id is None or not media_content_id.startswith(MEDIA_PLAYER_PREFIX): + raise BrowseError("Invalid Spotify URL specified") + + # Check for config entry specifier, and extract Spotify URI + parsed_url = yarl.URL(media_content_id) + if (info := hass.data[DOMAIN].get(parsed_url.host)) is None: + raise BrowseError("Invalid Spotify account specified") + media_content_id = parsed_url.name + + result = await async_browse_media_internal( hass, info.client, info.session, @@ -155,6 +195,13 @@ async def async_browse_media( can_play_artist=can_play_artist, ) + # Build new URLs with config entry specifyers + result.media_content_id = str(parsed_url.with_name(result.media_content_id)) + if result.children: + for child in result.children: + child.media_content_id = str(parsed_url.with_name(child.media_content_id)) + return result + async def async_browse_media_internal( hass: HomeAssistant, diff --git a/homeassistant/components/spotify/util.py b/homeassistant/components/spotify/util.py index cdb8e933523598..7f7f682fb9e8ee 100644 --- a/homeassistant/components/spotify/util.py +++ b/homeassistant/components/spotify/util.py @@ -3,6 +3,8 @@ from typing import Any +import yarl + from .const import MEDIA_PLAYER_PREFIX @@ -22,3 +24,11 @@ def fetch_image_url(item: dict[str, Any], key="images") -> str | None: return item.get(key, [])[0].get("url") except IndexError: return None + + +def spotify_uri_from_media_browser_url(media_content_id: str) -> str: + """Extract spotify URI from media browser URL.""" + if media_content_id and media_content_id.startswith(MEDIA_PLAYER_PREFIX): + parsed_url = yarl.URL(media_content_id) + media_content_id = parsed_url.name + return media_content_id From b2ee7cebc91dbd5ba5548283d9960a33dc585037 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 14 Feb 2022 14:24:58 +0100 Subject: [PATCH 0611/1098] Improve setup_time typing (#66509) --- homeassistant/bootstrap.py | 14 +++++++------- homeassistant/components/websocket_api/commands.py | 7 +++++-- homeassistant/setup.py | 5 +++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 49282c70cb0873..a58280158d8692 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -3,7 +3,7 @@ import asyncio import contextlib -from datetime import datetime +from datetime import datetime, timedelta import logging import logging.handlers import os @@ -450,7 +450,7 @@ async def _async_set_up_integrations( ) -> None: """Set up all the integrations.""" hass.data[DATA_SETUP_STARTED] = {} - setup_time = hass.data[DATA_SETUP_TIME] = {} + setup_time: dict[str, timedelta] = hass.data.setdefault(DATA_SETUP_TIME, {}) watch_task = asyncio.create_task(_async_watch_pending_setups(hass)) @@ -459,9 +459,9 @@ async def _async_set_up_integrations( # Resolve all dependencies so we know all integrations # that will have to be loaded and start rightaway integration_cache: dict[str, loader.Integration] = {} - to_resolve = domains_to_setup + to_resolve: set[str] = domains_to_setup while to_resolve: - old_to_resolve = to_resolve + old_to_resolve: set[str] = to_resolve to_resolve = set() integrations_to_process = [ @@ -508,11 +508,11 @@ async def _async_set_up_integrations( await async_setup_multi_components(hass, debuggers, config) # calculate what components to setup in what stage - stage_1_domains = set() + stage_1_domains: set[str] = set() # Find all dependencies of any dependency of any stage 1 integration that # we plan on loading and promote them to stage 1 - deps_promotion = STAGE_1_INTEGRATIONS + deps_promotion: set[str] = STAGE_1_INTEGRATIONS while deps_promotion: old_deps_promotion = deps_promotion deps_promotion = set() @@ -577,7 +577,7 @@ async def _async_set_up_integrations( { integration: timedelta.total_seconds() for integration, timedelta in sorted( - setup_time.items(), key=lambda item: item[1].total_seconds() # type: ignore + setup_time.items(), key=lambda item: item[1].total_seconds() ) }, ) diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index 4020601dc3f0f2..4b64e028f9710c 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -3,8 +3,9 @@ import asyncio from collections.abc import Callable +import datetime as dt import json -from typing import Any +from typing import Any, cast import voluptuous as vol @@ -305,7 +306,9 @@ async def handle_integration_setup_info( msg["id"], [ {"domain": integration, "seconds": timedelta.total_seconds()} - for integration, timedelta in hass.data[DATA_SETUP_TIME].items() + for integration, timedelta in cast( + dict[str, dt.timedelta], hass.data[DATA_SETUP_TIME] + ).items() ], ) diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 5c56cb55b19900..7b2f963102e528 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -4,6 +4,7 @@ import asyncio from collections.abc import Awaitable, Callable, Generator, Iterable import contextlib +from datetime import timedelta import logging.handlers from timeit import default_timer as timer from types import ModuleType @@ -436,7 +437,7 @@ def async_start_setup( """Keep track of when setup starts and finishes.""" setup_started = hass.data.setdefault(DATA_SETUP_STARTED, {}) started = dt_util.utcnow() - unique_components = {} + unique_components: dict[str, str] = {} for domain in components: unique = ensure_unique_string(domain, setup_started) unique_components[unique] = domain @@ -444,7 +445,7 @@ def async_start_setup( yield - setup_time = hass.data.setdefault(DATA_SETUP_TIME, {}) + setup_time: dict[str, timedelta] = hass.data.setdefault(DATA_SETUP_TIME, {}) time_taken = dt_util.utcnow() - started for unique, domain in unique_components.items(): del setup_started[unique] From 00d7fdd274a16fa2861722c7629079acdcba9835 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Feb 2022 07:25:15 -0600 Subject: [PATCH 0612/1098] Add WiZ occupancy sensor support (#66231) --- homeassistant/components/wiz/__init__.py | 19 ++++- homeassistant/components/wiz/binary_sensor.py | 81 ++++++++++++++++++ homeassistant/components/wiz/const.py | 2 + homeassistant/components/wiz/entity.py | 18 +++- tests/components/wiz/__init__.py | 45 +++++++++- tests/components/wiz/test_binary_sensor.py | 83 +++++++++++++++++++ tests/components/wiz/test_config_flow.py | 22 ++--- 7 files changed, 242 insertions(+), 28 deletions(-) create mode 100644 homeassistant/components/wiz/binary_sensor.py create mode 100644 tests/components/wiz/test_binary_sensor.py diff --git a/homeassistant/components/wiz/__init__.py b/homeassistant/components/wiz/__init__.py index 9a4444c523ef26..40dc4cf70d1a21 100644 --- a/homeassistant/components/wiz/__init__.py +++ b/homeassistant/components/wiz/__init__.py @@ -4,13 +4,15 @@ import logging from typing import Any -from pywizlight import wizlight +from pywizlight import PilotParser, wizlight +from pywizlight.bulb import PIR_SOURCE from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, Platform -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.debounce import Debouncer +from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed @@ -19,6 +21,7 @@ DISCOVER_SCAN_TIMEOUT, DISCOVERY_INTERVAL, DOMAIN, + SIGNAL_WIZ_PIR, WIZ_CONNECT_EXCEPTIONS, WIZ_EXCEPTIONS, ) @@ -27,7 +30,7 @@ _LOGGER = logging.getLogger(__name__) -PLATFORMS = [Platform.LIGHT, Platform.SWITCH] +PLATFORMS = [Platform.BINARY_SENSOR, Platform.LIGHT, Platform.SWITCH] REQUEST_REFRESH_DELAY = 0.35 @@ -76,7 +79,15 @@ async def _async_update() -> None: ), ) - await bulb.start_push(lambda _: coordinator.async_set_updated_data(None)) + @callback + def _async_push_update(state: PilotParser) -> None: + """Receive a push update.""" + _LOGGER.debug("%s: Got push update: %s", bulb.mac, state.pilotResult) + coordinator.async_set_updated_data(None) + if state.get_source() == PIR_SOURCE: + async_dispatcher_send(hass, SIGNAL_WIZ_PIR.format(bulb.mac)) + + await bulb.start_push(_async_push_update) bulb.set_discovery_callback(lambda bulb: async_trigger_discovery(hass, [bulb])) await coordinator.async_config_entry_first_refresh() diff --git a/homeassistant/components/wiz/binary_sensor.py b/homeassistant/components/wiz/binary_sensor.py new file mode 100644 index 00000000000000..1ecb31252155b2 --- /dev/null +++ b/homeassistant/components/wiz/binary_sensor.py @@ -0,0 +1,81 @@ +"""WiZ integration binary sensor platform.""" +from __future__ import annotations + +from collections.abc import Callable + +from pywizlight.bulb import PIR_SOURCE + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import entity_registry as er +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN, SIGNAL_WIZ_PIR +from .entity import WizEntity +from .models import WizData + +OCCUPANCY_UNIQUE_ID = "{}_occupancy" + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the WiZ binary sensor platform.""" + wiz_data: WizData = hass.data[DOMAIN][entry.entry_id] + mac = wiz_data.bulb.mac + + if er.async_get(hass).async_get_entity_id( + Platform.BINARY_SENSOR, DOMAIN, OCCUPANCY_UNIQUE_ID.format(mac) + ): + async_add_entities([WizOccupancyEntity(wiz_data, entry.title)]) + return + + cancel_dispatcher: Callable[[], None] | None = None + + @callback + def _async_add_occupancy_sensor() -> None: + nonlocal cancel_dispatcher + assert cancel_dispatcher is not None + cancel_dispatcher() + cancel_dispatcher = None + async_add_entities([WizOccupancyEntity(wiz_data, entry.title)]) + + cancel_dispatcher = async_dispatcher_connect( + hass, SIGNAL_WIZ_PIR.format(mac), _async_add_occupancy_sensor + ) + + @callback + def _async_cancel_dispatcher() -> None: + nonlocal cancel_dispatcher + if cancel_dispatcher is not None: + cancel_dispatcher() + cancel_dispatcher = None + + entry.async_on_unload(_async_cancel_dispatcher) + + +class WizOccupancyEntity(WizEntity, BinarySensorEntity): + """Representation of WiZ Occupancy sensor.""" + + _attr_device_class = BinarySensorDeviceClass.OCCUPANCY + + def __init__(self, wiz_data: WizData, name: str) -> None: + """Initialize an WiZ device.""" + super().__init__(wiz_data, name) + self._attr_unique_id = OCCUPANCY_UNIQUE_ID.format(self._device.mac) + self._attr_name = f"{name} Occupancy" + self._async_update_attrs() + + @callback + def _async_update_attrs(self) -> None: + """Handle updating _attr values.""" + if self._device.state.get_source() == PIR_SOURCE: + self._attr_is_on = self._device.status diff --git a/homeassistant/components/wiz/const.py b/homeassistant/components/wiz/const.py index d1b3a0f62512e1..1aeb2ada580827 100644 --- a/homeassistant/components/wiz/const.py +++ b/homeassistant/components/wiz/const.py @@ -21,3 +21,5 @@ ConnectionRefusedError, ) WIZ_CONNECT_EXCEPTIONS = (WizLightNotKnownBulb, *WIZ_EXCEPTIONS) + +SIGNAL_WIZ_PIR = "wiz_pir_{}" diff --git a/homeassistant/components/wiz/entity.py b/homeassistant/components/wiz/entity.py index 1ddaced401f7fc..82f19a61002e5b 100644 --- a/homeassistant/components/wiz/entity.py +++ b/homeassistant/components/wiz/entity.py @@ -1,23 +1,24 @@ """WiZ integration entities.""" from __future__ import annotations +from abc import abstractmethod from typing import Any from pywizlight.bulblibrary import BulbType from homeassistant.core import callback from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC -from homeassistant.helpers.entity import DeviceInfo, ToggleEntity +from homeassistant.helpers.entity import DeviceInfo, Entity, ToggleEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity from .models import WizData -class WizToggleEntity(CoordinatorEntity, ToggleEntity): - """Representation of WiZ toggle entity.""" +class WizEntity(CoordinatorEntity, Entity): + """Representation of WiZ entity.""" def __init__(self, wiz_data: WizData, name: str) -> None: - """Initialize an WiZ device.""" + """Initialize a WiZ entity.""" super().__init__(wiz_data.coordinator) self._device = wiz_data.bulb bulb_type: BulbType = self._device.bulbtype @@ -41,6 +42,15 @@ def _handle_coordinator_update(self) -> None: self._async_update_attrs() super()._handle_coordinator_update() + @callback + @abstractmethod + def _async_update_attrs(self) -> None: + """Handle updating _attr values.""" + + +class WizToggleEntity(WizEntity, ToggleEntity): + """Representation of WiZ toggle entity.""" + @callback def _async_update_attrs(self) -> None: """Handle updating _attr values.""" diff --git a/tests/components/wiz/__init__.py b/tests/components/wiz/__init__.py index 57650ede272d88..931dd5ec18cea0 100644 --- a/tests/components/wiz/__init__.py +++ b/tests/components/wiz/__init__.py @@ -13,6 +13,7 @@ from homeassistant.components.wiz.const import DOMAIN from homeassistant.const import CONF_HOST, CONF_NAME from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry @@ -110,6 +111,15 @@ white_channels=1, white_to_color_ratio=80, ) +FAKE_TURNABLE_BULB = BulbType( + bulb_type=BulbClass.TW, + name="ESP01_TW_03", + features=FEATURE_MAP[BulbClass.TW], + kelvin_range=KelvinRange(2700, 6500), + fw_version="1.0.0", + white_channels=1, + white_to_color_ratio=80, +) FAKE_SOCKET = BulbType( bulb_type=BulbClass.SOCKET, name="ESP01_SOCKET_03", @@ -144,16 +154,18 @@ async def setup_integration( def _mocked_wizlight(device, extended_white_range, bulb_type) -> wizlight: - bulb = MagicMock(auto_spec=wizlight) + bulb = MagicMock(auto_spec=wizlight, name="Mocked wizlight") async def _save_setup_callback(callback: Callable) -> None: - bulb.data_receive_callback = callback + bulb.push_callback = callback bulb.getBulbConfig = AsyncMock(return_value=device or FAKE_BULB_CONFIG) bulb.getExtendedWhiteRange = AsyncMock( return_value=extended_white_range or FAKE_EXTENDED_WHITE_RANGE ) bulb.getMac = AsyncMock(return_value=FAKE_MAC) + bulb.turn_on = AsyncMock() + bulb.turn_off = AsyncMock() bulb.updateState = AsyncMock(return_value=FAKE_STATE) bulb.getSupportedScenes = AsyncMock(return_value=list(SCENES)) bulb.start_push = AsyncMock(side_effect=_save_setup_callback) @@ -169,8 +181,8 @@ async def _save_setup_callback(callback: Callable) -> None: def _patch_wizlight(device=None, extended_white_range=None, bulb_type=None): @contextmanager def _patcher(): - bulb = _mocked_wizlight(device, extended_white_range, bulb_type) - with patch("homeassistant.components.wiz.wizlight", return_value=bulb,), patch( + bulb = device or _mocked_wizlight(device, extended_white_range, bulb_type) + with patch("homeassistant.components.wiz.wizlight", return_value=bulb), patch( "homeassistant.components.wiz.config_flow.wizlight", return_value=bulb, ): @@ -189,3 +201,28 @@ def _patcher(): yield return _patcher() + + +async def async_setup_integration( + hass, device=None, extended_white_range=None, bulb_type=None +): + """Set up the integration with a mock device.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id=FAKE_MAC, + data={CONF_HOST: FAKE_IP}, + ) + entry.add_to_hass(hass) + bulb = _mocked_wizlight(device, extended_white_range, bulb_type) + with _patch_discovery(), _patch_wizlight(device=bulb): + await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await hass.async_block_till_done() + return bulb, entry + + +async def async_push_update(hass, device, params): + """Push an update to the device.""" + device.state = PilotParser(params) + device.status = params["state"] + device.push_callback(device.state) + await hass.async_block_till_done() diff --git a/tests/components/wiz/test_binary_sensor.py b/tests/components/wiz/test_binary_sensor.py new file mode 100644 index 00000000000000..adfef066e16ee8 --- /dev/null +++ b/tests/components/wiz/test_binary_sensor.py @@ -0,0 +1,83 @@ +"""Tests for WiZ binary_sensor platform.""" + +from homeassistant import config_entries +from homeassistant.components import wiz +from homeassistant.components.wiz.binary_sensor import OCCUPANCY_UNIQUE_ID +from homeassistant.const import CONF_HOST, STATE_OFF, STATE_ON, STATE_UNKNOWN, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er +from homeassistant.setup import async_setup_component + +from . import ( + FAKE_IP, + FAKE_MAC, + _mocked_wizlight, + _patch_discovery, + _patch_wizlight, + async_push_update, + async_setup_integration, +) + +from tests.common import MockConfigEntry + + +async def test_binary_sensor_created_from_push_updates(hass: HomeAssistant) -> None: + """Test a binary sensor created from push updates.""" + bulb, _ = await async_setup_integration(hass) + + await async_push_update(hass, bulb, {"mac": FAKE_MAC, "src": "pir", "state": True}) + + entity_id = "binary_sensor.mock_title_occupancy" + entity_registry = er.async_get(hass) + assert entity_registry.async_get(entity_id).unique_id == f"{FAKE_MAC}_occupancy" + state = hass.states.get(entity_id) + assert state.state == STATE_ON + + await async_push_update(hass, bulb, {"mac": FAKE_MAC, "src": "pir", "state": False}) + + state = hass.states.get(entity_id) + assert state.state == STATE_OFF + + +async def test_binary_sensor_restored_from_registry(hass: HomeAssistant) -> None: + """Test a binary sensor restored from registry with state unknown.""" + entry = MockConfigEntry( + domain=wiz.DOMAIN, + unique_id=FAKE_MAC, + data={CONF_HOST: FAKE_IP}, + ) + entry.add_to_hass(hass) + bulb = _mocked_wizlight(None, None, None) + + entity_registry = er.async_get(hass) + reg_ent = entity_registry.async_get_or_create( + Platform.BINARY_SENSOR, wiz.DOMAIN, OCCUPANCY_UNIQUE_ID.format(bulb.mac) + ) + entity_id = reg_ent.entity_id + + with _patch_discovery(), _patch_wizlight(device=bulb): + await async_setup_component(hass, wiz.DOMAIN, {wiz.DOMAIN: {}}) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state.state == STATE_UNKNOWN + + await async_push_update(hass, bulb, {"mac": FAKE_MAC, "src": "pir", "state": True}) + + assert entity_registry.async_get(entity_id).unique_id == f"{FAKE_MAC}_occupancy" + state = hass.states.get(entity_id) + assert state.state == STATE_ON + + await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + assert entry.state == config_entries.ConfigEntryState.NOT_LOADED + + +async def test_binary_sensor_never_created_no_error_on_unload( + hass: HomeAssistant, +) -> None: + """Test a binary sensor does not error on unload.""" + _, entry = await async_setup_integration(hass) + await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + assert entry.state == config_entries.ConfigEntryState.NOT_LOADED diff --git a/tests/components/wiz/test_config_flow.py b/tests/components/wiz/test_config_flow.py index dc4bd4de32933e..f87f1b7543799b 100644 --- a/tests/components/wiz/test_config_flow.py +++ b/tests/components/wiz/test_config_flow.py @@ -12,7 +12,6 @@ from homeassistant.data_entry_flow import RESULT_TYPE_ABORT, RESULT_TYPE_FORM from . import ( - FAKE_BULB_CONFIG, FAKE_DIMMABLE_BULB, FAKE_EXTENDED_WHITE_RANGE, FAKE_IP, @@ -20,7 +19,6 @@ FAKE_RGBW_BULB, FAKE_RGBWW_BULB, FAKE_SOCKET, - FAKE_SOCKET_CONFIG, TEST_CONNECTION, TEST_SYSTEM_INFO, _patch_discovery, @@ -184,12 +182,11 @@ async def test_discovered_by_dhcp_connection_fails(hass, source, data): @pytest.mark.parametrize( - "source, data, device, bulb_type, extended_white_range, name", + "source, data, bulb_type, extended_white_range, name", [ ( config_entries.SOURCE_DHCP, DHCP_DISCOVERY, - FAKE_BULB_CONFIG, FAKE_DIMMABLE_BULB, FAKE_EXTENDED_WHITE_RANGE, "WiZ Dimmable White ABCABC", @@ -197,7 +194,6 @@ async def test_discovered_by_dhcp_connection_fails(hass, source, data): ( config_entries.SOURCE_INTEGRATION_DISCOVERY, INTEGRATION_DISCOVERY, - FAKE_BULB_CONFIG, FAKE_DIMMABLE_BULB, FAKE_EXTENDED_WHITE_RANGE, "WiZ Dimmable White ABCABC", @@ -205,7 +201,6 @@ async def test_discovered_by_dhcp_connection_fails(hass, source, data): ( config_entries.SOURCE_DHCP, DHCP_DISCOVERY, - FAKE_BULB_CONFIG, FAKE_RGBW_BULB, FAKE_EXTENDED_WHITE_RANGE, "WiZ RGBW Tunable ABCABC", @@ -213,7 +208,6 @@ async def test_discovered_by_dhcp_connection_fails(hass, source, data): ( config_entries.SOURCE_INTEGRATION_DISCOVERY, INTEGRATION_DISCOVERY, - FAKE_BULB_CONFIG, FAKE_RGBW_BULB, FAKE_EXTENDED_WHITE_RANGE, "WiZ RGBW Tunable ABCABC", @@ -221,7 +215,6 @@ async def test_discovered_by_dhcp_connection_fails(hass, source, data): ( config_entries.SOURCE_DHCP, DHCP_DISCOVERY, - FAKE_BULB_CONFIG, FAKE_RGBWW_BULB, FAKE_EXTENDED_WHITE_RANGE, "WiZ RGBWW Tunable ABCABC", @@ -229,7 +222,6 @@ async def test_discovered_by_dhcp_connection_fails(hass, source, data): ( config_entries.SOURCE_INTEGRATION_DISCOVERY, INTEGRATION_DISCOVERY, - FAKE_BULB_CONFIG, FAKE_RGBWW_BULB, FAKE_EXTENDED_WHITE_RANGE, "WiZ RGBWW Tunable ABCABC", @@ -237,7 +229,6 @@ async def test_discovered_by_dhcp_connection_fails(hass, source, data): ( config_entries.SOURCE_DHCP, DHCP_DISCOVERY, - FAKE_SOCKET_CONFIG, FAKE_SOCKET, None, "WiZ Socket ABCABC", @@ -245,7 +236,6 @@ async def test_discovered_by_dhcp_connection_fails(hass, source, data): ( config_entries.SOURCE_INTEGRATION_DISCOVERY, INTEGRATION_DISCOVERY, - FAKE_SOCKET_CONFIG, FAKE_SOCKET, None, "WiZ Socket ABCABC", @@ -253,11 +243,11 @@ async def test_discovered_by_dhcp_connection_fails(hass, source, data): ], ) async def test_discovered_by_dhcp_or_integration_discovery( - hass, source, data, device, bulb_type, extended_white_range, name + hass, source, data, bulb_type, extended_white_range, name ): """Test we can configure when discovered from dhcp or discovery.""" with _patch_wizlight( - device=device, extended_white_range=extended_white_range, bulb_type=bulb_type + device=None, extended_white_range=extended_white_range, bulb_type=bulb_type ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": source}, data=data @@ -268,7 +258,7 @@ async def test_discovered_by_dhcp_or_integration_discovery( assert result["step_id"] == "discovery_confirm" with _patch_wizlight( - device=device, extended_white_range=extended_white_range, bulb_type=bulb_type + device=None, extended_white_range=extended_white_range, bulb_type=bulb_type ), patch( "homeassistant.components.wiz.async_setup_entry", return_value=True, @@ -423,7 +413,7 @@ async def test_setup_via_discovery_cannot_connect(hass): async def test_discovery_with_firmware_update(hass): """Test we check the device again between first discovery and config entry creation.""" with _patch_wizlight( - device=FAKE_BULB_CONFIG, + device=None, extended_white_range=FAKE_EXTENDED_WHITE_RANGE, bulb_type=FAKE_RGBW_BULB, ): @@ -447,7 +437,7 @@ async def test_discovery_with_firmware_update(hass): ) as mock_setup_entry, patch( "homeassistant.components.wiz.async_setup", return_value=True ) as mock_setup, _patch_wizlight( - device=FAKE_BULB_CONFIG, + device=None, extended_white_range=FAKE_EXTENDED_WHITE_RANGE, bulb_type=FAKE_RGBWW_BULB, ): From db73ce92faa1f89abe83577287cda4aec9fcbf4d Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Mon, 14 Feb 2022 15:30:04 +0200 Subject: [PATCH 0613/1098] Increase switcher_kis timeouts (#66465) --- homeassistant/components/switcher_kis/const.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/switcher_kis/const.py b/homeassistant/components/switcher_kis/const.py index 88b6e447446902..fdd5b02fe9b2e4 100644 --- a/homeassistant/components/switcher_kis/const.py +++ b/homeassistant/components/switcher_kis/const.py @@ -8,7 +8,7 @@ DATA_DEVICE = "device" DATA_DISCOVERY = "discovery" -DISCOVERY_TIME_SEC = 6 +DISCOVERY_TIME_SEC = 12 SIGNAL_DEVICE_ADD = "switcher_device_add" @@ -19,4 +19,4 @@ SERVICE_TURN_ON_WITH_TIMER_NAME = "turn_on_with_timer" # Defines the maximum interval device must send an update before it marked unavailable -MAX_UPDATE_INTERVAL_SEC = 20 +MAX_UPDATE_INTERVAL_SEC = 30 From 4c5d64762b8510a887f5c270c966c788ea09f9d6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 14 Feb 2022 05:38:46 -0800 Subject: [PATCH 0614/1098] Fix cast turn on image (#66500) --- homeassistant/components/cast/media_player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 565961e1b8c539..74ca65ade06d5c 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -411,7 +411,7 @@ def turn_on(self): # The only way we can turn the Chromecast is on is by launching an app if self._chromecast.cast_type == pychromecast.const.CAST_TYPE_CHROMECAST: - self._chromecast.play_media(CAST_SPLASH, pychromecast.STREAM_TYPE_BUFFERED) + self._chromecast.play_media(CAST_SPLASH, "image/png") else: self._chromecast.start_app(pychromecast.config.APP_MEDIA_RECEIVER) From 795d249c8ca007fac816f1854eab79fbf860f869 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 14 Feb 2022 15:06:33 +0100 Subject: [PATCH 0615/1098] Small improvement of cast test (#66513) --- tests/components/cast/test_media_player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 36ee2ce818a2cb..cf72cf38827477 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -1120,7 +1120,7 @@ async def test_entity_control(hass: HomeAssistant): # Turn on await common.async_turn_on(hass, entity_id) chromecast.play_media.assert_called_once_with( - "https://www.home-assistant.io/images/cast/splash.png", ANY + "https://www.home-assistant.io/images/cast/splash.png", "image/png" ) chromecast.quit_app.reset_mock() From 45251e693621454a24f99f2f393e0bf909e93419 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 14 Feb 2022 15:23:20 +0100 Subject: [PATCH 0616/1098] Update sentry-dsk to 1.5.5 (#66515) --- homeassistant/components/sentry/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sentry/manifest.json b/homeassistant/components/sentry/manifest.json index c74860c7f6409e..52f7cba7a19230 100644 --- a/homeassistant/components/sentry/manifest.json +++ b/homeassistant/components/sentry/manifest.json @@ -3,7 +3,7 @@ "name": "Sentry", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sentry", - "requirements": ["sentry-sdk==1.5.4"], + "requirements": ["sentry-sdk==1.5.5"], "codeowners": ["@dcramer", "@frenck"], "iot_class": "cloud_polling" } diff --git a/requirements_all.txt b/requirements_all.txt index 89a326e672dbdf..388f335b96df0f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2178,7 +2178,7 @@ sense-hat==2.2.0 sense_energy==0.9.6 # homeassistant.components.sentry -sentry-sdk==1.5.4 +sentry-sdk==1.5.5 # homeassistant.components.sharkiq sharkiqpy==0.1.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c12edbf285047a..0464e14c2a322c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1343,7 +1343,7 @@ screenlogicpy==0.5.4 sense_energy==0.9.6 # homeassistant.components.sentry -sentry-sdk==1.5.4 +sentry-sdk==1.5.5 # homeassistant.components.sharkiq sharkiqpy==0.1.8 From 94cfc89df9e44678d9eb1a45889bcd7c1bdc457d Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 14 Feb 2022 15:28:52 +0100 Subject: [PATCH 0617/1098] Improve `DiscoveryFlowHandler` typing (#66511) --- homeassistant/components/rpi_power/config_flow.py | 5 +++-- homeassistant/components/sonos/config_flow.py | 3 ++- homeassistant/helpers/config_entry_flow.py | 13 +++++++------ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/rpi_power/config_flow.py b/homeassistant/components/rpi_power/config_flow.py index 82457d5b296079..ed8a45822b0a8e 100644 --- a/homeassistant/components/rpi_power/config_flow.py +++ b/homeassistant/components/rpi_power/config_flow.py @@ -1,6 +1,7 @@ """Config flow for Raspberry Pi Power Supply Checker.""" from __future__ import annotations +from collections.abc import Awaitable from typing import Any from rpi_bad_power import new_under_voltage @@ -18,7 +19,7 @@ async def _async_supported(hass: HomeAssistant) -> bool: return under_voltage is not None -class RPiPowerFlow(DiscoveryFlowHandler, domain=DOMAIN): +class RPiPowerFlow(DiscoveryFlowHandler[Awaitable[bool]], domain=DOMAIN): """Discovery flow handler.""" VERSION = 1 @@ -35,7 +36,7 @@ async def async_step_onboarding( self, data: dict[str, Any] | None = None ) -> FlowResult: """Handle a flow initialized by onboarding.""" - has_devices = await self._discovery_function(self.hass) # type: ignore + has_devices = await self._discovery_function(self.hass) if not has_devices: return self.async_abort(reason="no_devices_found") diff --git a/homeassistant/components/sonos/config_flow.py b/homeassistant/components/sonos/config_flow.py index 07add8e6d7c786..30778edc493bfb 100644 --- a/homeassistant/components/sonos/config_flow.py +++ b/homeassistant/components/sonos/config_flow.py @@ -1,4 +1,5 @@ """Config flow for SONOS.""" +from collections.abc import Awaitable import dataclasses from homeassistant import config_entries @@ -16,7 +17,7 @@ async def _async_has_devices(hass: HomeAssistant) -> bool: return bool(await ssdp.async_get_discovery_info_by_st(hass, UPNP_ST)) -class SonosDiscoveryFlowHandler(DiscoveryFlowHandler): +class SonosDiscoveryFlowHandler(DiscoveryFlowHandler[Awaitable[bool]]): """Sonos discovery flow that callsback zeroconf updates.""" def __init__(self) -> None: diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py index d7920f809410ea..fddc5c82725919 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -3,7 +3,7 @@ from collections.abc import Awaitable, Callable import logging -from typing import TYPE_CHECKING, Any, Union, cast +from typing import TYPE_CHECKING, Any, Generic, TypeVar, Union, cast from homeassistant import config_entries from homeassistant.components import dhcp, mqtt, ssdp, zeroconf @@ -15,12 +15,13 @@ if TYPE_CHECKING: import asyncio -DiscoveryFunctionType = Callable[[HomeAssistant], Union[Awaitable[bool], bool]] +_R = TypeVar("_R", bound="Awaitable[bool] | bool") +DiscoveryFunctionType = Callable[[HomeAssistant], _R] _LOGGER = logging.getLogger(__name__) -class DiscoveryFlowHandler(config_entries.ConfigFlow): +class DiscoveryFlowHandler(config_entries.ConfigFlow, Generic[_R]): """Handle a discovery config flow.""" VERSION = 1 @@ -29,7 +30,7 @@ def __init__( self, domain: str, title: str, - discovery_function: DiscoveryFunctionType, + discovery_function: DiscoveryFunctionType[_R], ) -> None: """Initialize the discovery config flow.""" self._domain = domain @@ -153,7 +154,7 @@ async def async_step_import(self, _: dict[str, Any] | None) -> FlowResult: def register_discovery_flow( domain: str, title: str, - discovery_function: DiscoveryFunctionType, + discovery_function: DiscoveryFunctionType[Awaitable[bool] | bool], connection_class: str | UndefinedType = UNDEFINED, ) -> None: """Register flow for discovered integrations that not require auth.""" @@ -172,7 +173,7 @@ def register_discovery_flow( domain, ) - class DiscoveryFlow(DiscoveryFlowHandler): + class DiscoveryFlow(DiscoveryFlowHandler[Union[Awaitable[bool], bool]]): """Discovery flow handler.""" def __init__(self) -> None: From 80394e3de6fd4fcd09eeb155778100cacc1af1e6 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 14 Feb 2022 15:41:09 +0100 Subject: [PATCH 0618/1098] Improve `util.async_` typing (#66510) --- .strict-typing | 1 + homeassistant/util/async_.py | 22 ++++++++++++++-------- mypy.ini | 3 +++ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/.strict-typing b/.strict-typing index a206f562c17709..364a3d0db6aecd 100644 --- a/.strict-typing +++ b/.strict-typing @@ -20,6 +20,7 @@ homeassistant.helpers.entity_values homeassistant.helpers.reload homeassistant.helpers.script_variables homeassistant.helpers.translation +homeassistant.util.async_ homeassistant.util.color homeassistant.util.process homeassistant.util.unit_system diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py index b898efe49fe56b..b27ddbda3827a2 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -11,14 +11,20 @@ from traceback import extract_stack from typing import Any, TypeVar +from typing_extensions import ParamSpec + _LOGGER = logging.getLogger(__name__) _SHUTDOWN_RUN_CALLBACK_THREADSAFE = "_shutdown_run_callback_threadsafe" -T = TypeVar("T") +_T = TypeVar("_T") +_R = TypeVar("_R") +_P = ParamSpec("_P") -def fire_coroutine_threadsafe(coro: Coroutine, loop: AbstractEventLoop) -> None: +def fire_coroutine_threadsafe( + coro: Coroutine[Any, Any, Any], loop: AbstractEventLoop +) -> None: """Submit a coroutine object to a given event loop. This method does not provide a way to retrieve the result and @@ -40,8 +46,8 @@ def callback() -> None: def run_callback_threadsafe( - loop: AbstractEventLoop, callback: Callable[..., T], *args: Any -) -> concurrent.futures.Future[T]: + loop: AbstractEventLoop, callback: Callable[..., _T], *args: Any +) -> concurrent.futures.Future[_T]: """Submit a callback object to a given event loop. Return a concurrent.futures.Future to access the result. @@ -50,7 +56,7 @@ def run_callback_threadsafe( if ident is not None and ident == threading.get_ident(): raise RuntimeError("Cannot be called from within the event loop") - future: concurrent.futures.Future = concurrent.futures.Future() + future: concurrent.futures.Future[_T] = concurrent.futures.Future() def run_callback() -> None: """Run callback and store result.""" @@ -88,7 +94,7 @@ def run_callback() -> None: return future -def check_loop(func: Callable, strict: bool = True) -> None: +def check_loop(func: Callable[..., Any], strict: bool = True) -> None: """Warn if called inside the event loop. Raise if `strict` is True.""" try: get_running_loop() @@ -159,11 +165,11 @@ def check_loop(func: Callable, strict: bool = True) -> None: ) -def protect_loop(func: Callable, strict: bool = True) -> Callable: +def protect_loop(func: Callable[_P, _R], strict: bool = True) -> Callable[_P, _R]: """Protect function from running in event loop.""" @functools.wraps(func) - def protected_loop_func(*args, **kwargs): # type: ignore + def protected_loop_func(*args: _P.args, **kwargs: _P.kwargs) -> _R: check_loop(func, strict=strict) return func(*args, **kwargs) diff --git a/mypy.ini b/mypy.ini index bbab7d20f80c99..8372a98d332306 100644 --- a/mypy.ini +++ b/mypy.ini @@ -70,6 +70,9 @@ disallow_any_generics = true [mypy-homeassistant.helpers.translation] disallow_any_generics = true +[mypy-homeassistant.util.async_] +disallow_any_generics = true + [mypy-homeassistant.util.color] disallow_any_generics = true From 707f112f511cd41cbdb9c2ee57b5b211c0736ee6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 14 Feb 2022 06:41:53 -0800 Subject: [PATCH 0619/1098] Improve raised exception consistency for media source (#66497) --- homeassistant/components/media_source/__init__.py | 13 +++++++++++-- .../components/media_source/local_source.py | 2 +- tests/components/media_source/test_init.py | 8 +++++++- tests/components/netatmo/test_media_source.py | 4 ++-- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/media_source/__init__.py b/homeassistant/components/media_source/__init__.py index 81c629529df887..3be5bf040d7964 100644 --- a/homeassistant/components/media_source/__init__.py +++ b/homeassistant/components/media_source/__init__.py @@ -102,7 +102,10 @@ async def async_browse_media( if DOMAIN not in hass.data: raise BrowseError("Media Source not loaded") - item = await _get_media_item(hass, media_content_id).async_browse() + try: + item = await _get_media_item(hass, media_content_id).async_browse() + except ValueError as err: + raise BrowseError("Not a media source item") from err if content_filter is None or item.children is None: return item @@ -118,7 +121,13 @@ async def async_resolve_media(hass: HomeAssistant, media_content_id: str) -> Pla """Get info to play media.""" if DOMAIN not in hass.data: raise Unresolvable("Media Source not loaded") - return await _get_media_item(hass, media_content_id).async_resolve() + + try: + item = _get_media_item(hass, media_content_id) + except ValueError as err: + raise Unresolvable("Not a media source item") from err + + return await item.async_resolve() @websocket_api.websocket_command( diff --git a/homeassistant/components/media_source/local_source.py b/homeassistant/components/media_source/local_source.py index 66e6bdd8379e60..d5e1671c135eb3 100644 --- a/homeassistant/components/media_source/local_source.py +++ b/homeassistant/components/media_source/local_source.py @@ -276,7 +276,7 @@ async def post(self, request: web.Request) -> web.Response: uploaded_file: FileField = data["file"] - if not uploaded_file.content_type.startswith(("image/", "video/")): + if not uploaded_file.content_type.startswith(("image/", "video/", "audio/")): LOGGER.error("Content type not allowed") raise vol.Invalid("Only images and video are allowed") diff --git a/tests/components/media_source/test_init.py b/tests/components/media_source/test_init.py index 5b25e878e5ae72..7aae72475cb3f5 100644 --- a/tests/components/media_source/test_init.py +++ b/tests/components/media_source/test_init.py @@ -60,7 +60,7 @@ async def test_async_browse_media(hass): media.children[0].title = "Epic Sax Guy 10 Hours" # Test invalid media content - with pytest.raises(ValueError): + with pytest.raises(BrowseError): await media_source.async_browse_media(hass, "invalid") # Test base URI returns all domains @@ -80,6 +80,8 @@ async def test_async_resolve_media(hass): media_source.generate_media_source_id(media_source.DOMAIN, "local/test.mp3"), ) assert isinstance(media, media_source.models.PlayMedia) + assert media.url == "/media/local/test.mp3" + assert media.mime_type == "audio/mpeg" async def test_async_unresolve_media(hass): @@ -91,6 +93,10 @@ async def test_async_unresolve_media(hass): with pytest.raises(media_source.Unresolvable): await media_source.async_resolve_media(hass, "") + # Test invalid media content + with pytest.raises(media_source.Unresolvable): + await media_source.async_resolve_media(hass, "invalid") + async def test_websocket_browse_media(hass, hass_ws_client): """Test browse media websocket.""" diff --git a/tests/components/netatmo/test_media_source.py b/tests/components/netatmo/test_media_source.py index 2ba70ca9489fa6..c47416721869aa 100644 --- a/tests/components/netatmo/test_media_source.py +++ b/tests/components/netatmo/test_media_source.py @@ -52,9 +52,9 @@ async def test_async_browse_media(hass): assert str(excinfo.value) == "Unknown source directory." # Test invalid base - with pytest.raises(ValueError) as excinfo: + with pytest.raises(media_source.BrowseError) as excinfo: await media_source.async_browse_media(hass, f"{const.URI_SCHEME}{DOMAIN}/") - assert str(excinfo.value) == "Invalid media source URI" + assert str(excinfo.value) == "Not a media source item" # Test successful listing media = await media_source.async_browse_media( From 4522a5698c9f065ca2c224a7617348344fbad63b Mon Sep 17 00:00:00 2001 From: Bart Janssens Date: Mon, 14 Feb 2022 15:42:40 +0100 Subject: [PATCH 0620/1098] Fix vicare program presets (#66476) --- homeassistant/components/vicare/climate.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/vicare/climate.py b/homeassistant/components/vicare/climate.py index c0c0db85cf3a1c..64df7260c5be08 100644 --- a/homeassistant/components/vicare/climate.py +++ b/homeassistant/components/vicare/climate.py @@ -19,6 +19,7 @@ HVAC_MODE_OFF, PRESET_COMFORT, PRESET_ECO, + PRESET_NONE, SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, ) @@ -87,11 +88,13 @@ VICARE_TO_HA_PRESET_HEATING = { VICARE_PROGRAM_COMFORT: PRESET_COMFORT, VICARE_PROGRAM_ECO: PRESET_ECO, + VICARE_PROGRAM_NORMAL: PRESET_NONE, } HA_TO_VICARE_PRESET_HEATING = { PRESET_COMFORT: VICARE_PROGRAM_COMFORT, PRESET_ECO: VICARE_PROGRAM_ECO, + PRESET_NONE: VICARE_PROGRAM_NORMAL, } @@ -322,7 +325,7 @@ def preset_mode(self): @property def preset_modes(self): """Return the available preset mode.""" - return list(VICARE_TO_HA_PRESET_HEATING) + return list(HA_TO_VICARE_PRESET_HEATING) def set_preset_mode(self, preset_mode): """Set new preset mode and deactivate any existing programs.""" @@ -333,8 +336,12 @@ def set_preset_mode(self, preset_mode): ) _LOGGER.debug("Setting preset to %s / %s", preset_mode, vicare_program) - self._circuit.deactivateProgram(self._current_program) - self._circuit.activateProgram(vicare_program) + if self._current_program != VICARE_PROGRAM_NORMAL: + # We can't deactivate "normal" + self._circuit.deactivateProgram(self._current_program) + if vicare_program != VICARE_PROGRAM_NORMAL: + # And we can't explicitly activate normal, either + self._circuit.activateProgram(vicare_program) @property def extra_state_attributes(self): From 445ad1d592588fd91442ef45fe196ec3272dec44 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Feb 2022 09:31:26 -0600 Subject: [PATCH 0621/1098] Add test coverage for WiZ lights and switches (#66387) --- .coveragerc | 6 - homeassistant/components/wiz/discovery.py | 12 +- homeassistant/components/wiz/light.py | 16 +- homeassistant/components/wiz/manifest.json | 1 + tests/components/wiz/__init__.py | 4 +- tests/components/wiz/test_config_flow.py | 21 +++ tests/components/wiz/test_init.py | 32 ++++ tests/components/wiz/test_light.py | 171 +++++++++++++++++++-- tests/components/wiz/test_switch.py | 66 ++++++++ 9 files changed, 287 insertions(+), 42 deletions(-) create mode 100644 tests/components/wiz/test_init.py create mode 100644 tests/components/wiz/test_switch.py diff --git a/.coveragerc b/.coveragerc index de48567eb6c176..e85278586ddc2c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1398,12 +1398,6 @@ omit = homeassistant/components/wiffi/sensor.py homeassistant/components/wiffi/wiffi_strings.py homeassistant/components/wirelesstag/* - homeassistant/components/wiz/__init__.py - homeassistant/components/wiz/const.py - homeassistant/components/wiz/discovery.py - homeassistant/components/wiz/entity.py - homeassistant/components/wiz/light.py - homeassistant/components/wiz/switch.py homeassistant/components/wolflink/__init__.py homeassistant/components/wolflink/sensor.py homeassistant/components/wolflink/const.py diff --git a/homeassistant/components/wiz/discovery.py b/homeassistant/components/wiz/discovery.py index c7ee612c6a9364..0b7015643ff588 100644 --- a/homeassistant/components/wiz/discovery.py +++ b/homeassistant/components/wiz/discovery.py @@ -17,17 +17,11 @@ async def async_discover_devices( - hass: HomeAssistant, timeout: int, address: str | None = None + hass: HomeAssistant, timeout: int ) -> list[DiscoveredBulb]: """Discover wiz devices.""" - if address: - targets = [address] - else: - targets = [ - str(address) - for address in await network.async_get_ipv4_broadcast_addresses(hass) - ] - + broadcast_addrs = await network.async_get_ipv4_broadcast_addresses(hass) + targets = [str(address) for address in broadcast_addrs] combined_discoveries: dict[str, DiscoveredBulb] = {} for idx, discovered in enumerate( await asyncio.gather( diff --git a/homeassistant/components/wiz/light.py b/homeassistant/components/wiz/light.py index ef60deea956b9e..9b2d7e6fab45d8 100644 --- a/homeassistant/components/wiz/light.py +++ b/homeassistant/components/wiz/light.py @@ -32,6 +32,8 @@ from .entity import WizToggleEntity from .models import WizData +RGB_WHITE_CHANNELS_COLOR_MODE = {1: COLOR_MODE_RGBW, 2: COLOR_MODE_RGBWW} + def _async_pilot_builder(**kwargs: Any) -> PilotBuilder: """Create the PilotBuilder for turn on.""" @@ -79,10 +81,7 @@ def __init__(self, wiz_data: WizData, name: str) -> None: features: Features = bulb_type.features color_modes = set() if features.color: - if bulb_type.white_channels == 2: - color_modes.add(COLOR_MODE_RGBWW) - else: - color_modes.add(COLOR_MODE_RGBW) + color_modes.add(RGB_WHITE_CHANNELS_COLOR_MODE[bulb_type.white_channels]) if features.color_tmp: color_modes.add(COLOR_MODE_COLOR_TEMP) if not color_modes and features.brightness: @@ -90,12 +89,9 @@ def __init__(self, wiz_data: WizData, name: str) -> None: self._attr_supported_color_modes = color_modes self._attr_effect_list = wiz_data.scenes if bulb_type.bulb_type != BulbClass.DW: - self._attr_min_mireds = color_temperature_kelvin_to_mired( - bulb_type.kelvin_range.max - ) - self._attr_max_mireds = color_temperature_kelvin_to_mired( - bulb_type.kelvin_range.min - ) + kelvin = bulb_type.kelvin_range + self._attr_min_mireds = color_temperature_kelvin_to_mired(kelvin.max) + self._attr_max_mireds = color_temperature_kelvin_to_mired(kelvin.min) if bulb_type.features.effect: self._attr_supported_features = SUPPORT_EFFECT self._async_update_attrs() diff --git a/homeassistant/components/wiz/manifest.json b/homeassistant/components/wiz/manifest.json index 3aa137f24607e2..e333691d20cdd7 100644 --- a/homeassistant/components/wiz/manifest.json +++ b/homeassistant/components/wiz/manifest.json @@ -7,6 +7,7 @@ {"hostname":"wiz_*"} ], "dependencies": ["network"], + "quality_scale": "platinum", "documentation": "https://www.home-assistant.io/integrations/wiz", "requirements": ["pywizlight==0.5.8"], "iot_class": "local_push", diff --git a/tests/components/wiz/__init__.py b/tests/components/wiz/__init__.py index 931dd5ec18cea0..e553593bf2f568 100644 --- a/tests/components/wiz/__init__.py +++ b/tests/components/wiz/__init__.py @@ -204,7 +204,7 @@ def _patcher(): async def async_setup_integration( - hass, device=None, extended_white_range=None, bulb_type=None + hass, wizlight=None, device=None, extended_white_range=None, bulb_type=None ): """Set up the integration with a mock device.""" entry = MockConfigEntry( @@ -213,7 +213,7 @@ async def async_setup_integration( data={CONF_HOST: FAKE_IP}, ) entry.add_to_hass(hass) - bulb = _mocked_wizlight(device, extended_white_range, bulb_type) + bulb = wizlight or _mocked_wizlight(device, extended_white_range, bulb_type) with _patch_discovery(), _patch_wizlight(device=bulb): await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() diff --git a/tests/components/wiz/test_config_flow.py b/tests/components/wiz/test_config_flow.py index f87f1b7543799b..f8426ece56db7f 100644 --- a/tests/components/wiz/test_config_flow.py +++ b/tests/components/wiz/test_config_flow.py @@ -410,6 +410,27 @@ async def test_setup_via_discovery_cannot_connect(hass): assert result3["reason"] == "cannot_connect" +async def test_setup_via_discovery_exception_finds_nothing(hass): + """Test we do not find anything if discovery throws.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + await hass.async_block_till_done() + assert result["type"] == "form" + assert result["step_id"] == "user" + assert not result["errors"] + + with patch( + "homeassistant.components.wiz.discovery.find_wizlights", + side_effect=OSError, + ): + result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_ABORT + assert result2["reason"] == "no_devices_found" + + async def test_discovery_with_firmware_update(hass): """Test we check the device again between first discovery and config entry creation.""" with _patch_wizlight( diff --git a/tests/components/wiz/test_init.py b/tests/components/wiz/test_init.py new file mode 100644 index 00000000000000..6411146d162617 --- /dev/null +++ b/tests/components/wiz/test_init.py @@ -0,0 +1,32 @@ +"""Tests for wiz integration.""" +import datetime +from unittest.mock import AsyncMock + +from homeassistant import config_entries +from homeassistant.core import HomeAssistant +from homeassistant.util.dt import utcnow + +from . import ( + FAKE_MAC, + FAKE_SOCKET, + _mocked_wizlight, + _patch_discovery, + _patch_wizlight, + async_setup_integration, +) + +from tests.common import async_fire_time_changed + + +async def test_setup_retry(hass: HomeAssistant) -> None: + """Test setup is retried on error.""" + bulb = _mocked_wizlight(None, None, FAKE_SOCKET) + bulb.getMac = AsyncMock(side_effect=OSError) + _, entry = await async_setup_integration(hass, wizlight=bulb) + assert entry.state == config_entries.ConfigEntryState.SETUP_RETRY + bulb.getMac = AsyncMock(return_value=FAKE_MAC) + + with _patch_discovery(), _patch_wizlight(device=bulb): + async_fire_time_changed(hass, utcnow() + datetime.timedelta(minutes=15)) + await hass.async_block_till_done() + assert entry.state == config_entries.ConfigEntryState.LOADED diff --git a/tests/components/wiz/test_light.py b/tests/components/wiz/test_light.py index 16d7c6a0a5d251..c79cf74e1308c5 100644 --- a/tests/components/wiz/test_light.py +++ b/tests/components/wiz/test_light.py @@ -1,30 +1,171 @@ """Tests for light platform.""" -from homeassistant.components import wiz -from homeassistant.const import CONF_HOST, STATE_ON +from pywizlight import PilotBuilder + +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + ATTR_COLOR_TEMP, + ATTR_EFFECT, + ATTR_RGBW_COLOR, + ATTR_RGBWW_COLOR, + DOMAIN as LIGHT_DOMAIN, +) +from homeassistant.const import ( + ATTR_ENTITY_ID, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, +) from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er -from homeassistant.setup import async_setup_component - -from . import FAKE_IP, FAKE_MAC, _patch_discovery, _patch_wizlight -from tests.common import MockConfigEntry +from . import ( + FAKE_MAC, + FAKE_RGBW_BULB, + FAKE_RGBWW_BULB, + FAKE_TURNABLE_BULB, + async_push_update, + async_setup_integration, +) async def test_light_unique_id(hass: HomeAssistant) -> None: """Test a light unique id.""" - entry = MockConfigEntry( - domain=wiz.DOMAIN, - unique_id=FAKE_MAC, - data={CONF_HOST: FAKE_IP}, - ) - entry.add_to_hass(hass) - with _patch_discovery(), _patch_wizlight(): - await async_setup_component(hass, wiz.DOMAIN, {wiz.DOMAIN: {}}) - await hass.async_block_till_done() + await async_setup_integration(hass) + entity_id = "light.mock_title" + entity_registry = er.async_get(hass) + assert entity_registry.async_get(entity_id).unique_id == FAKE_MAC + state = hass.states.get(entity_id) + assert state.state == STATE_ON + +async def test_light_operation(hass: HomeAssistant) -> None: + """Test a light operation.""" + bulb, _ = await async_setup_integration(hass) entity_id = "light.mock_title" entity_registry = er.async_get(hass) assert entity_registry.async_get(entity_id).unique_id == FAKE_MAC state = hass.states.get(entity_id) assert state.state == STATE_ON + + await hass.services.async_call( + LIGHT_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + bulb.turn_off.assert_called_once() + + await async_push_update(hass, bulb, {"mac": FAKE_MAC, "state": False}) + assert hass.states.get(entity_id).state == STATE_OFF + + await hass.services.async_call( + LIGHT_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + bulb.turn_on.assert_called_once() + + await async_push_update(hass, bulb, {"mac": FAKE_MAC, "state": True}) + assert hass.states.get(entity_id).state == STATE_ON + + +async def test_rgbww_light(hass: HomeAssistant) -> None: + """Test a light operation with a rgbww light.""" + bulb, _ = await async_setup_integration(hass, bulb_type=FAKE_RGBWW_BULB) + entity_id = "light.mock_title" + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity_id, ATTR_RGBWW_COLOR: (1, 2, 3, 4, 5)}, + blocking=True, + ) + pilot: PilotBuilder = bulb.turn_on.mock_calls[0][1][0] + assert pilot.pilot_params == {"b": 3, "c": 4, "g": 2, "r": 1, "state": True, "w": 5} + + await async_push_update(hass, bulb, {"mac": FAKE_MAC, **pilot.pilot_params}) + state = hass.states.get(entity_id) + assert state.state == STATE_ON + assert state.attributes[ATTR_RGBWW_COLOR] == (1, 2, 3, 4, 5) + + bulb.turn_on.reset_mock() + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity_id, ATTR_COLOR_TEMP: 153, ATTR_BRIGHTNESS: 128}, + blocking=True, + ) + pilot: PilotBuilder = bulb.turn_on.mock_calls[0][1][0] + assert pilot.pilot_params == {"dimming": 50, "temp": 6535, "state": True} + await async_push_update(hass, bulb, {"mac": FAKE_MAC, **pilot.pilot_params}) + state = hass.states.get(entity_id) + assert state.state == STATE_ON + assert state.attributes[ATTR_COLOR_TEMP] == 153 + + bulb.turn_on.reset_mock() + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity_id, ATTR_EFFECT: "Ocean"}, + blocking=True, + ) + pilot: PilotBuilder = bulb.turn_on.mock_calls[0][1][0] + assert pilot.pilot_params == {"sceneId": 1, "state": True} + await async_push_update(hass, bulb, {"mac": FAKE_MAC, **pilot.pilot_params}) + state = hass.states.get(entity_id) + assert state.state == STATE_ON + assert state.attributes[ATTR_EFFECT] == "Ocean" + + bulb.turn_on.reset_mock() + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity_id, ATTR_EFFECT: "Rhythm"}, + blocking=True, + ) + pilot: PilotBuilder = bulb.turn_on.mock_calls[0][1][0] + assert pilot.pilot_params == {"state": True} + + +async def test_rgbw_light(hass: HomeAssistant) -> None: + """Test a light operation with a rgbww light.""" + bulb, _ = await async_setup_integration(hass, bulb_type=FAKE_RGBW_BULB) + entity_id = "light.mock_title" + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity_id, ATTR_RGBW_COLOR: (1, 2, 3, 4)}, + blocking=True, + ) + pilot: PilotBuilder = bulb.turn_on.mock_calls[0][1][0] + assert pilot.pilot_params == {"b": 3, "g": 2, "r": 1, "state": True, "w": 4} + + await async_push_update(hass, bulb, {"mac": FAKE_MAC, **pilot.pilot_params}) + state = hass.states.get(entity_id) + assert state.state == STATE_ON + assert state.attributes[ATTR_RGBW_COLOR] == (1, 2, 3, 4) + + bulb.turn_on.reset_mock() + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity_id, ATTR_COLOR_TEMP: 153, ATTR_BRIGHTNESS: 128}, + blocking=True, + ) + pilot: PilotBuilder = bulb.turn_on.mock_calls[0][1][0] + assert pilot.pilot_params == {"dimming": 50, "temp": 6535, "state": True} + + +async def test_turnable_light(hass: HomeAssistant) -> None: + """Test a light operation with a turnable light.""" + bulb, _ = await async_setup_integration(hass, bulb_type=FAKE_TURNABLE_BULB) + entity_id = "light.mock_title" + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity_id, ATTR_COLOR_TEMP: 153, ATTR_BRIGHTNESS: 128}, + blocking=True, + ) + pilot: PilotBuilder = bulb.turn_on.mock_calls[0][1][0] + assert pilot.pilot_params == {"dimming": 50, "temp": 6535, "state": True} + + await async_push_update(hass, bulb, {"mac": FAKE_MAC, **pilot.pilot_params}) + state = hass.states.get(entity_id) + assert state.state == STATE_ON + assert state.attributes[ATTR_COLOR_TEMP] == 153 diff --git a/tests/components/wiz/test_switch.py b/tests/components/wiz/test_switch.py new file mode 100644 index 00000000000000..e728ff4a645e55 --- /dev/null +++ b/tests/components/wiz/test_switch.py @@ -0,0 +1,66 @@ +"""Tests for switch platform.""" + +import datetime + +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN +from homeassistant.const import ( + ATTR_ENTITY_ID, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, + STATE_UNAVAILABLE, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er +from homeassistant.util.dt import utcnow + +from . import FAKE_MAC, FAKE_SOCKET, async_push_update, async_setup_integration + +from tests.common import async_fire_time_changed + + +async def test_switch_operation(hass: HomeAssistant) -> None: + """Test switch operation.""" + switch, _ = await async_setup_integration(hass, bulb_type=FAKE_SOCKET) + entity_id = "switch.mock_title" + entity_registry = er.async_get(hass) + assert entity_registry.async_get(entity_id).unique_id == FAKE_MAC + assert hass.states.get(entity_id).state == STATE_ON + + await hass.services.async_call( + SWITCH_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + switch.turn_off.assert_called_once() + + await async_push_update(hass, switch, {"mac": FAKE_MAC, "state": False}) + assert hass.states.get(entity_id).state == STATE_OFF + + await hass.services.async_call( + SWITCH_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + switch.turn_on.assert_called_once() + + await async_push_update(hass, switch, {"mac": FAKE_MAC, "state": True}) + assert hass.states.get(entity_id).state == STATE_ON + + +async def test_update_fails(hass: HomeAssistant) -> None: + """Test switch update fails when push updates are not working.""" + switch, _ = await async_setup_integration(hass, bulb_type=FAKE_SOCKET) + entity_id = "switch.mock_title" + entity_registry = er.async_get(hass) + assert entity_registry.async_get(entity_id).unique_id == FAKE_MAC + assert hass.states.get(entity_id).state == STATE_ON + + await hass.services.async_call( + SWITCH_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + switch.turn_off.assert_called_once() + + switch.updateState.side_effect = OSError + + async_fire_time_changed(hass, utcnow() + datetime.timedelta(seconds=15)) + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == STATE_UNAVAILABLE From 63cb79ec29899a6efdf84d8c076b8d232f490f94 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Feb 2022 09:49:43 -0600 Subject: [PATCH 0622/1098] Add support for setting the effect speed in WiZ (#66457) --- homeassistant/components/wiz/__init__.py | 2 +- homeassistant/components/wiz/number.py | 58 ++++++++++++++++++++++++ tests/components/wiz/__init__.py | 3 +- tests/components/wiz/test_number.py | 32 +++++++++++++ 4 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/wiz/number.py create mode 100644 tests/components/wiz/test_number.py diff --git a/homeassistant/components/wiz/__init__.py b/homeassistant/components/wiz/__init__.py index 40dc4cf70d1a21..1bed875d02fd90 100644 --- a/homeassistant/components/wiz/__init__.py +++ b/homeassistant/components/wiz/__init__.py @@ -30,7 +30,7 @@ _LOGGER = logging.getLogger(__name__) -PLATFORMS = [Platform.BINARY_SENSOR, Platform.LIGHT, Platform.SWITCH] +PLATFORMS = [Platform.BINARY_SENSOR, Platform.LIGHT, Platform.NUMBER, Platform.SWITCH] REQUEST_REFRESH_DELAY = 0.35 diff --git a/homeassistant/components/wiz/number.py b/homeassistant/components/wiz/number.py new file mode 100644 index 00000000000000..eed9bd9280356a --- /dev/null +++ b/homeassistant/components/wiz/number.py @@ -0,0 +1,58 @@ +"""Support for WiZ effect speed numbers.""" +from __future__ import annotations + +from pywizlight.bulblibrary import BulbClass + +from homeassistant.components.number import NumberEntity, NumberMode +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .entity import WizEntity +from .models import WizData + +EFFECT_SPEED_UNIQUE_ID = "{}_effect_speed" + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the wiz speed number.""" + wiz_data: WizData = hass.data[DOMAIN][entry.entry_id] + if wiz_data.bulb.bulbtype.bulb_type != BulbClass.SOCKET: + async_add_entities([WizSpeedNumber(wiz_data, entry.title)]) + + +class WizSpeedNumber(WizEntity, NumberEntity): + """Defines a WiZ speed number.""" + + _attr_min_value = 10 + _attr_max_value = 200 + _attr_step = 1 + _attr_mode = NumberMode.SLIDER + _attr_icon = "mdi:speedometer" + + def __init__(self, wiz_data: WizData, name: str) -> None: + """Initialize an WiZ device.""" + super().__init__(wiz_data, name) + self._attr_unique_id = EFFECT_SPEED_UNIQUE_ID.format(self._device.mac) + self._attr_name = f"{name} Effect Speed" + self._async_update_attrs() + + @property + def available(self) -> bool: + """Return if entity is available.""" + return super().available and self._device.state.get_speed() is not None + + @callback + def _async_update_attrs(self) -> None: + """Handle updating _attr values.""" + self._attr_value = self._device.state.get_speed() + + async def async_set_value(self, value: float) -> None: + """Set the speed value.""" + await self._device.set_speed(int(value)) + await self.coordinator.async_request_refresh() diff --git a/tests/components/wiz/__init__.py b/tests/components/wiz/__init__.py index e553593bf2f568..8d187e9b476be5 100644 --- a/tests/components/wiz/__init__.py +++ b/tests/components/wiz/__init__.py @@ -170,6 +170,7 @@ async def _save_setup_callback(callback: Callable) -> None: bulb.getSupportedScenes = AsyncMock(return_value=list(SCENES)) bulb.start_push = AsyncMock(side_effect=_save_setup_callback) bulb.async_close = AsyncMock() + bulb.set_speed = AsyncMock() bulb.state = FAKE_STATE bulb.mac = FAKE_MAC bulb.bulbtype = bulb_type or FAKE_DIMMABLE_BULB @@ -223,6 +224,6 @@ async def async_setup_integration( async def async_push_update(hass, device, params): """Push an update to the device.""" device.state = PilotParser(params) - device.status = params["state"] + device.status = params.get("state") device.push_callback(device.state) await hass.async_block_till_done() diff --git a/tests/components/wiz/test_number.py b/tests/components/wiz/test_number.py new file mode 100644 index 00000000000000..a1ab5e6bbae0a2 --- /dev/null +++ b/tests/components/wiz/test_number.py @@ -0,0 +1,32 @@ +"""Tests for the number platform.""" + +from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN +from homeassistant.components.number.const import ATTR_VALUE, SERVICE_SET_VALUE +from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import FAKE_MAC, async_push_update, async_setup_integration + + +async def test_speed_operation(hass: HomeAssistant) -> None: + """Test changing a speed.""" + bulb, _ = await async_setup_integration(hass) + await async_push_update(hass, bulb, {"mac": FAKE_MAC}) + entity_id = "number.mock_title_effect_speed" + entity_registry = er.async_get(hass) + assert entity_registry.async_get(entity_id).unique_id == f"{FAKE_MAC}_effect_speed" + assert hass.states.get(entity_id).state == STATE_UNAVAILABLE + + await async_push_update(hass, bulb, {"mac": FAKE_MAC, "speed": 50}) + assert hass.states.get(entity_id).state == "50" + + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + {ATTR_ENTITY_ID: entity_id, ATTR_VALUE: 30}, + blocking=True, + ) + bulb.set_speed.assert_called_with(30) + await async_push_update(hass, bulb, {"mac": FAKE_MAC, "speed": 30}) + assert hass.states.get(entity_id).state == "30" From fec8c2ab822c824837cbe7dc640ab064c4248822 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 14 Feb 2022 08:04:19 -0800 Subject: [PATCH 0623/1098] Add support for MJPEG cameras to camera media source (#66499) --- .../components/camera/media_source.py | 21 ++++++++--- tests/components/camera/test_media_source.py | 35 ++++++++++++++----- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/camera/media_source.py b/homeassistant/components/camera/media_source.py index 841a936532035d..b0161a5825142f 100644 --- a/homeassistant/components/camera/media_source.py +++ b/homeassistant/components/camera/media_source.py @@ -47,8 +47,18 @@ async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia: if not camera: raise Unresolvable(f"Could not resolve media item: {item.identifier}") + stream_type = camera.frontend_stream_type + + if stream_type is None: + return PlayMedia( + f"/api/camera_proxy_stream/{camera.entity_id}", camera.content_type + ) + if camera.frontend_stream_type != STREAM_TYPE_HLS: - raise Unresolvable("Camera does not support HLS streaming.") + raise Unresolvable("Camera does not support MJPEG or HLS streaming.") + + if "stream" not in self.hass.config.components: + raise Unresolvable("Stream integration not loaded") try: url = await _async_stream_endpoint_url(self.hass, camera, HLS_PROVIDER) @@ -65,16 +75,19 @@ async def async_browse_media( if item.identifier: raise BrowseError("Unknown item") - if "stream" not in self.hass.config.components: - raise BrowseError("Stream integration is not loaded") + supported_stream_types: list[str | None] = [None] + + if "stream" in self.hass.config.components: + supported_stream_types.append(STREAM_TYPE_HLS) # Root. List cameras. component: EntityComponent = self.hass.data[DOMAIN] children = [] for camera in component.entities: camera = cast(Camera, camera) + stream_type = camera.frontend_stream_type - if camera.frontend_stream_type != STREAM_TYPE_HLS: + if stream_type not in supported_stream_types: continue children.append( diff --git a/tests/components/camera/test_media_source.py b/tests/components/camera/test_media_source.py index d5d65296e65e75..3a3558419e5c76 100644 --- a/tests/components/camera/test_media_source.py +++ b/tests/components/camera/test_media_source.py @@ -15,17 +15,17 @@ async def setup_media_source(hass): assert await async_setup_component(hass, "media_source", {}) -@pytest.fixture(autouse=True) -async def mock_stream(hass): - """Mock stream.""" - hass.config.components.add("stream") - - async def test_browsing(hass, mock_camera_hls): """Test browsing camera media source.""" item = await media_source.async_browse_media(hass, "media-source://camera") assert item is not None assert item.title == "Camera" + assert len(item.children) == 0 + + # Adding stream enables HLS camera + hass.config.components.add("stream") + + item = await media_source.async_browse_media(hass, "media-source://camera") assert len(item.children) == 2 @@ -39,6 +39,9 @@ async def test_browsing_filter_non_hls(hass, mock_camera_web_rtc): async def test_resolving(hass, mock_camera_hls): """Test resolving.""" + # Adding stream enables HLS camera + hass.config.components.add("stream") + with patch( "homeassistant.components.camera.media_source._async_stream_endpoint_url", return_value="http://example.com/stream", @@ -53,20 +56,34 @@ async def test_resolving(hass, mock_camera_hls): async def test_resolving_errors(hass, mock_camera_hls): """Test resolving.""" - with pytest.raises(media_source.Unresolvable): + + with pytest.raises(media_source.Unresolvable) as exc_info: + await media_source.async_resolve_media( + hass, "media-source://camera/camera.demo_camera" + ) + assert str(exc_info.value) == "Stream integration not loaded" + + hass.config.components.add("stream") + + with pytest.raises(media_source.Unresolvable) as exc_info: await media_source.async_resolve_media( hass, "media-source://camera/camera.non_existing" ) + assert str(exc_info.value) == "Could not resolve media item: camera.non_existing" - with pytest.raises(media_source.Unresolvable), patch( + with pytest.raises(media_source.Unresolvable) as exc_info, patch( "homeassistant.components.camera.Camera.frontend_stream_type", new_callable=PropertyMock(return_value=STREAM_TYPE_WEB_RTC), ): await media_source.async_resolve_media( hass, "media-source://camera/camera.demo_camera" ) + assert str(exc_info.value) == "Camera does not support MJPEG or HLS streaming." - with pytest.raises(media_source.Unresolvable): + with pytest.raises(media_source.Unresolvable) as exc_info: await media_source.async_resolve_media( hass, "media-source://camera/camera.demo_camera" ) + assert ( + str(exc_info.value) == "camera.demo_camera does not support play stream service" + ) From 9691128e968105f82b6a14e6dbf8aea40768e954 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Feb 2022 10:12:24 -0600 Subject: [PATCH 0624/1098] Fix ImportError when discovery deps change (#66518) --- homeassistant/bootstrap.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index a58280158d8692..fbe7a6b005fd31 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -59,7 +59,7 @@ MAX_LOAD_CONCURRENTLY = 6 DEBUGGER_INTEGRATIONS = {"debugpy"} -CORE_INTEGRATIONS = ("homeassistant", "persistent_notification") +CORE_INTEGRATIONS = {"homeassistant", "persistent_notification"} LOGGING_INTEGRATIONS = { # Set log levels "logger", @@ -69,7 +69,14 @@ # To record data "recorder", } +DISCOVERY_INTEGRATIONS = ("dhcp", "ssdp", "usb", "zeroconf") STAGE_1_INTEGRATIONS = { + # We need to make sure discovery integrations + # update their deps before stage 2 integrations + # load them inadvertently before their deps have + # been updated which leads to using an old version + # of the dep, or worse (import errors). + *DISCOVERY_INTEGRATIONS, # To make sure we forward data to other instances "mqtt_eventstream", # To provide account link implementations From 013d22711330c2248e24121c1a087fdcb4683ab1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Feb 2022 10:32:34 -0600 Subject: [PATCH 0625/1098] Ensure WiZ cleans up on shutdown and failed setup (#66520) --- homeassistant/components/wiz/__init__.py | 19 ++++++++++++++++--- tests/components/wiz/test_init.py | 20 ++++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/wiz/__init__.py b/homeassistant/components/wiz/__init__.py index 1bed875d02fd90..7bea86d323ceac 100644 --- a/homeassistant/components/wiz/__init__.py +++ b/homeassistant/components/wiz/__init__.py @@ -8,8 +8,8 @@ from pywizlight.bulb import PIR_SOURCE from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, Platform -from homeassistant.core import HomeAssistant, callback +from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP, Platform +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -57,6 +57,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: scenes = await bulb.getSupportedScenes() await bulb.getMac() except WIZ_CONNECT_EXCEPTIONS as err: + await bulb.async_close() raise ConfigEntryNotReady(f"{ip_address}: {err}") from err async def _async_update() -> None: @@ -79,6 +80,19 @@ async def _async_update() -> None: ), ) + try: + await coordinator.async_config_entry_first_refresh() + except ConfigEntryNotReady as err: + await bulb.async_close() + raise err + + async def _async_shutdown_on_stop(event: Event) -> None: + await bulb.async_close() + + entry.async_on_unload( + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_shutdown_on_stop) + ) + @callback def _async_push_update(state: PilotParser) -> None: """Receive a push update.""" @@ -89,7 +103,6 @@ def _async_push_update(state: PilotParser) -> None: await bulb.start_push(_async_push_update) bulb.set_discovery_callback(lambda bulb: async_trigger_discovery(hass, [bulb])) - await coordinator.async_config_entry_first_refresh() hass.data.setdefault(DOMAIN, {})[entry.entry_id] = WizData( coordinator=coordinator, bulb=bulb, scenes=scenes diff --git a/tests/components/wiz/test_init.py b/tests/components/wiz/test_init.py index 6411146d162617..fb21e930efdd8f 100644 --- a/tests/components/wiz/test_init.py +++ b/tests/components/wiz/test_init.py @@ -3,6 +3,7 @@ from unittest.mock import AsyncMock from homeassistant import config_entries +from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant from homeassistant.util.dt import utcnow @@ -30,3 +31,22 @@ async def test_setup_retry(hass: HomeAssistant) -> None: async_fire_time_changed(hass, utcnow() + datetime.timedelta(minutes=15)) await hass.async_block_till_done() assert entry.state == config_entries.ConfigEntryState.LOADED + + +async def test_cleanup_on_shutdown(hass: HomeAssistant) -> None: + """Test the socket is cleaned up on shutdown.""" + bulb = _mocked_wizlight(None, None, FAKE_SOCKET) + _, entry = await async_setup_integration(hass, wizlight=bulb) + assert entry.state == config_entries.ConfigEntryState.LOADED + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + bulb.async_close.assert_called_once() + + +async def test_cleanup_on_failed_first_update(hass: HomeAssistant) -> None: + """Test the socket is cleaned up on failed first update.""" + bulb = _mocked_wizlight(None, None, FAKE_SOCKET) + bulb.updateState = AsyncMock(side_effect=OSError) + _, entry = await async_setup_integration(hass, wizlight=bulb) + assert entry.state == config_entries.ConfigEntryState.SETUP_RETRY + bulb.async_close.assert_called_once() From 8456c6416e2dfb8888eeffa2f56be315e9e88a6b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 14 Feb 2022 08:54:12 -0800 Subject: [PATCH 0626/1098] Add a media source to TTS (#66483) --- homeassistant/components/tts/__init__.py | 15 ++- homeassistant/components/tts/const.py | 3 + homeassistant/components/tts/media_source.py | 109 ++++++++++++++++++ tests/components/google_translate/test_tts.py | 2 +- tests/components/tts/conftest.py | 55 +++++++++ tests/components/tts/test_init.py | 59 +--------- tests/components/tts/test_media_source.py | 99 ++++++++++++++++ tests/components/tts/test_notify.py | 12 -- tests/components/voicerss/test_tts.py | 2 +- tests/components/yandextts/test_tts.py | 2 +- 10 files changed, 283 insertions(+), 75 deletions(-) create mode 100644 homeassistant/components/tts/const.py create mode 100644 homeassistant/components/tts/media_source.py create mode 100644 tests/components/tts/test_media_source.py diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index 515dc25899ad10..abe3d29c607ffd 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -9,6 +9,7 @@ import logging import mimetypes import os +from pathlib import Path import re from typing import TYPE_CHECKING, Optional, cast @@ -39,10 +40,11 @@ from homeassistant.helpers.network import get_url from homeassistant.helpers.service import async_set_service_schema from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from homeassistant.loader import async_get_integration from homeassistant.setup import async_prepare_setup_platform from homeassistant.util.yaml import load_yaml +from .const import DOMAIN + # mypy: allow-untyped-defs, no-check-untyped-defs _LOGGER = logging.getLogger(__name__) @@ -69,7 +71,6 @@ DEFAULT_CACHE = True DEFAULT_CACHE_DIR = "tts" DEFAULT_TIME_MEMORY = 300 -DOMAIN = "tts" MEM_CACHE_FILENAME = "filename" MEM_CACHE_VOICE = "voice" @@ -135,12 +136,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: _LOGGER.exception("Error on cache init") return False + hass.data[DOMAIN] = tts hass.http.register_view(TextToSpeechView(tts)) hass.http.register_view(TextToSpeechUrlView(tts)) # Load service descriptions from tts/services.yaml - integration = await async_get_integration(hass, DOMAIN) - services_yaml = integration.file_path / "services.yaml" + services_yaml = Path(__file__).parent / "services.yaml" services_dict = cast( dict, await hass.async_add_executor_job(load_yaml, str(services_yaml)) ) @@ -343,7 +344,11 @@ async def async_get_url_path( This method is a coroutine. """ - provider = self.providers[engine] + provider = self.providers.get(engine) + + if provider is None: + raise HomeAssistantError(f"Provider {engine} not found") + msg_hash = hashlib.sha1(bytes(message, "utf-8")).hexdigest() use_cache = cache if cache is not None else self.use_cache diff --git a/homeassistant/components/tts/const.py b/homeassistant/components/tts/const.py new file mode 100644 index 00000000000000..492e995b87f0ba --- /dev/null +++ b/homeassistant/components/tts/const.py @@ -0,0 +1,3 @@ +"""Text-to-speech constants.""" + +DOMAIN = "tts" diff --git a/homeassistant/components/tts/media_source.py b/homeassistant/components/tts/media_source.py new file mode 100644 index 00000000000000..2398a6203ad976 --- /dev/null +++ b/homeassistant/components/tts/media_source.py @@ -0,0 +1,109 @@ +"""Text-to-speech media source.""" +from __future__ import annotations + +import mimetypes +from typing import TYPE_CHECKING + +from yarl import URL + +from homeassistant.components.media_player.const import MEDIA_CLASS_APP +from homeassistant.components.media_player.errors import BrowseError +from homeassistant.components.media_source.error import Unresolvable +from homeassistant.components.media_source.models import ( + BrowseMediaSource, + MediaSource, + MediaSourceItem, + PlayMedia, +) +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError + +from .const import DOMAIN + +if TYPE_CHECKING: + from . import SpeechManager + + +async def async_get_media_source(hass: HomeAssistant) -> TTSMediaSource: + """Set up tts media source.""" + return TTSMediaSource(hass) + + +class TTSMediaSource(MediaSource): + """Provide text-to-speech providers as media sources.""" + + name: str = "Text to Speech" + + def __init__(self, hass: HomeAssistant) -> None: + """Initialize TTSMediaSource.""" + super().__init__(DOMAIN) + self.hass = hass + + async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia: + """Resolve media to a url.""" + parsed = URL(item.identifier) + if "message" not in parsed.query: + raise Unresolvable("No message specified.") + + options = dict(parsed.query) + kwargs = { + "engine": parsed.name, + "message": options.pop("message"), + "language": options.pop("language", None), + "options": options, + } + + manager: SpeechManager = self.hass.data[DOMAIN] + + try: + url = await manager.async_get_url_path(**kwargs) # type: ignore + except HomeAssistantError as err: + raise Unresolvable(str(err)) from err + + mime_type = mimetypes.guess_type(url)[0] or "audio/mpeg" + + return PlayMedia(url, mime_type) + + async def async_browse_media( + self, + item: MediaSourceItem, + ) -> BrowseMediaSource: + """Return media.""" + if item.identifier: + provider, _, _ = item.identifier.partition("?") + return self._provider_item(provider) + + # Root. List providers. + manager: SpeechManager = self.hass.data[DOMAIN] + children = [self._provider_item(provider) for provider in manager.providers] + return BrowseMediaSource( + domain=DOMAIN, + identifier=None, + media_class=MEDIA_CLASS_APP, + media_content_type="", + title=self.name, + can_play=False, + can_expand=True, + children_media_class=MEDIA_CLASS_APP, + children=children, + ) + + @callback + def _provider_item(self, provider_domain: str) -> BrowseMediaSource: + """Return provider item.""" + manager: SpeechManager = self.hass.data[DOMAIN] + provider = manager.providers.get(provider_domain) + + if provider is None: + raise BrowseError("Unknown provider") + + return BrowseMediaSource( + domain=DOMAIN, + identifier=provider_domain, + media_class=MEDIA_CLASS_APP, + media_content_type="provider", + title=provider.name, + thumbnail=f"https://brands.home-assistant.io/_/{provider_domain}/logo.png", + can_play=False, + can_expand=True, + ) diff --git a/tests/components/google_translate/test_tts.py b/tests/components/google_translate/test_tts.py index 5690591ccd2189..9e09ebb9ff2a76 100644 --- a/tests/components/google_translate/test_tts.py +++ b/tests/components/google_translate/test_tts.py @@ -16,7 +16,7 @@ from homeassistant.setup import async_setup_component from tests.common import async_mock_service -from tests.components.tts.test_init import mutagen_mock # noqa: F401 +from tests.components.tts.conftest import mutagen_mock # noqa: F401 @pytest.fixture(autouse=True) diff --git a/tests/components/tts/conftest.py b/tests/components/tts/conftest.py index 3580880fedbce8..6d995978391680 100644 --- a/tests/components/tts/conftest.py +++ b/tests/components/tts/conftest.py @@ -2,9 +2,12 @@ From http://doc.pytest.org/en/latest/example/simple.html#making-test-result-information-available-in-fixtures """ +from unittest.mock import patch import pytest +from homeassistant.components.tts import _get_cache_files + @pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_runtest_makereport(item, call): @@ -16,3 +19,55 @@ def pytest_runtest_makereport(item, call): # set a report attribute for each phase of a call, which can # be "setup", "call", "teardown" setattr(item, f"rep_{rep.when}", rep) + + +@pytest.fixture(autouse=True) +def mock_get_cache_files(): + """Mock the list TTS cache function.""" + with patch( + "homeassistant.components.tts._get_cache_files", return_value={} + ) as mock_cache_files: + yield mock_cache_files + + +@pytest.fixture(autouse=True) +def mock_init_cache_dir(): + """Mock the TTS cache dir in memory.""" + with patch( + "homeassistant.components.tts._init_tts_cache_dir", + side_effect=lambda hass, cache_dir: hass.config.path(cache_dir), + ) as mock_cache_dir: + yield mock_cache_dir + + +@pytest.fixture +def empty_cache_dir(tmp_path, mock_init_cache_dir, mock_get_cache_files, request): + """Mock the TTS cache dir with empty dir.""" + mock_init_cache_dir.side_effect = None + mock_init_cache_dir.return_value = str(tmp_path) + + # Restore original get cache files behavior, we're working with a real dir. + mock_get_cache_files.side_effect = _get_cache_files + + yield tmp_path + + if request.node.rep_call.passed: + return + + # Print contents of dir if failed + print("Content of dir for", request.node.nodeid) + for fil in tmp_path.iterdir(): + print(fil.relative_to(tmp_path)) + + # To show the log. + assert False + + +@pytest.fixture(autouse=True) +def mutagen_mock(): + """Mock writing tags.""" + with patch( + "homeassistant.components.tts.SpeechManager.write_tags", + side_effect=lambda *args: args[1], + ) as mock_write_tags: + yield mock_write_tags diff --git a/tests/components/tts/test_init.py b/tests/components/tts/test_init.py index 3cbc1f0da00c71..9f1cc849a1f65f 100644 --- a/tests/components/tts/test_init.py +++ b/tests/components/tts/test_init.py @@ -5,6 +5,7 @@ import pytest import yarl +from homeassistant.components import tts from homeassistant.components.demo.tts import DemoProvider from homeassistant.components.media_player.const import ( ATTR_MEDIA_CONTENT_ID, @@ -13,13 +14,13 @@ MEDIA_TYPE_MUSIC, SERVICE_PLAY_MEDIA, ) -import homeassistant.components.tts as tts -from homeassistant.components.tts import _get_cache_files from homeassistant.config import async_process_ha_core_config from homeassistant.setup import async_setup_component from tests.common import assert_setup_component, async_mock_service +ORIG_WRITE_TAGS = tts.SpeechManager.write_tags + def relative_url(url): """Convert an absolute url to a relative one.""" @@ -32,58 +33,6 @@ def demo_provider(): return DemoProvider("en") -@pytest.fixture(autouse=True) -def mock_get_cache_files(): - """Mock the list TTS cache function.""" - with patch( - "homeassistant.components.tts._get_cache_files", return_value={} - ) as mock_cache_files: - yield mock_cache_files - - -@pytest.fixture(autouse=True) -def mock_init_cache_dir(): - """Mock the TTS cache dir in memory.""" - with patch( - "homeassistant.components.tts._init_tts_cache_dir", - side_effect=lambda hass, cache_dir: hass.config.path(cache_dir), - ) as mock_cache_dir: - yield mock_cache_dir - - -@pytest.fixture -def empty_cache_dir(tmp_path, mock_init_cache_dir, mock_get_cache_files, request): - """Mock the TTS cache dir with empty dir.""" - mock_init_cache_dir.side_effect = None - mock_init_cache_dir.return_value = str(tmp_path) - - # Restore original get cache files behavior, we're working with a real dir. - mock_get_cache_files.side_effect = _get_cache_files - - yield tmp_path - - if request.node.rep_call.passed: - return - - # Print contents of dir if failed - print("Content of dir for", request.node.nodeid) - for fil in tmp_path.iterdir(): - print(fil.relative_to(tmp_path)) - - # To show the log. - assert False - - -@pytest.fixture() -def mutagen_mock(): - """Mock writing tags.""" - with patch( - "homeassistant.components.tts.SpeechManager.write_tags", - side_effect=lambda *args: args[1], - ): - yield - - @pytest.fixture(autouse=True) async def internal_url_mock(hass): """Mock internal URL of the instance.""" @@ -730,7 +679,7 @@ async def test_tags_with_wave(hass, demo_provider): + "22 56 00 00 88 58 01 00 04 00 10 00 64 61 74 61 00 00 00 00" ) - tagged_data = tts.SpeechManager.write_tags( + tagged_data = ORIG_WRITE_TAGS( "42f18378fd4393d18c8dd11d03fa9563c1e54491_en_-_demo.wav", demo_data, demo_provider, diff --git a/tests/components/tts/test_media_source.py b/tests/components/tts/test_media_source.py new file mode 100644 index 00000000000000..3bfd204a228720 --- /dev/null +++ b/tests/components/tts/test_media_source.py @@ -0,0 +1,99 @@ +"""Tests for TTS media source.""" +from unittest.mock import patch + +import pytest + +from homeassistant.components import media_source +from homeassistant.components.media_player.errors import BrowseError +from homeassistant.setup import async_setup_component + + +@pytest.fixture(autouse=True) +async def mock_get_tts_audio(hass): + """Set up media source.""" + assert await async_setup_component(hass, "media_source", {}) + assert await async_setup_component( + hass, + "tts", + { + "tts": { + "platform": "demo", + } + }, + ) + + with patch( + "homeassistant.components.demo.tts.DemoProvider.get_tts_audio", + return_value=("mp3", b""), + ) as mock_get_tts: + yield mock_get_tts + + +async def test_browsing(hass): + """Test browsing TTS media source.""" + item = await media_source.async_browse_media(hass, "media-source://tts") + assert item is not None + assert item.title == "Text to Speech" + assert len(item.children) == 1 + assert item.can_play is False + assert item.can_expand is True + + item_child = await media_source.async_browse_media( + hass, item.children[0].media_content_id + ) + assert item_child is not None + assert item_child.title == "Demo" + assert item_child.children is None + assert item_child.can_play is False + assert item_child.can_expand is True + + with pytest.raises(BrowseError): + await media_source.async_browse_media(hass, "media-source://tts/non-existing") + + +async def test_resolving(hass, mock_get_tts_audio): + """Test resolving.""" + media = await media_source.async_resolve_media( + hass, "media-source://tts/demo?message=Hello%20World" + ) + assert media.url.startswith("/api/tts_proxy/") + assert media.mime_type == "audio/mpeg" + + assert len(mock_get_tts_audio.mock_calls) == 1 + message, language = mock_get_tts_audio.mock_calls[0][1] + assert message == "Hello World" + assert language == "en" + assert mock_get_tts_audio.mock_calls[0][2]["options"] is None + + # Pass language and options + mock_get_tts_audio.reset_mock() + media = await media_source.async_resolve_media( + hass, "media-source://tts/demo?message=Bye%20World&language=de&voice=Paulus" + ) + assert media.url.startswith("/api/tts_proxy/") + assert media.mime_type == "audio/mpeg" + + assert len(mock_get_tts_audio.mock_calls) == 1 + message, language = mock_get_tts_audio.mock_calls[0][1] + assert message == "Bye World" + assert language == "de" + assert mock_get_tts_audio.mock_calls[0][2]["options"] == {"voice": "Paulus"} + + +async def test_resolving_errors(hass): + """Test resolving.""" + # No message added + with pytest.raises(media_source.Unresolvable): + await media_source.async_resolve_media(hass, "media-source://tts/demo") + + # Non-existing provider + with pytest.raises(media_source.Unresolvable): + await media_source.async_resolve_media( + hass, "media-source://tts/non-existing?message=bla" + ) + + # Non-existing option + with pytest.raises(media_source.Unresolvable): + await media_source.async_resolve_media( + hass, "media-source://tts/non-existing?message=bla&non_existing_option=bla" + ) diff --git a/tests/components/tts/test_notify.py b/tests/components/tts/test_notify.py index 9989b1d349e34f..912896dd3e229e 100644 --- a/tests/components/tts/test_notify.py +++ b/tests/components/tts/test_notify.py @@ -1,6 +1,4 @@ """The tests for the TTS component.""" -from unittest.mock import patch - import pytest import yarl @@ -22,16 +20,6 @@ def relative_url(url): return str(yarl.URL(url).relative()) -@pytest.fixture(autouse=True) -def mutagen_mock(): - """Mock writing tags.""" - with patch( - "homeassistant.components.tts.SpeechManager.write_tags", - side_effect=lambda *args: args[1], - ): - yield - - @pytest.fixture(autouse=True) async def internal_url_mock(hass): """Mock internal URL of the instance.""" diff --git a/tests/components/voicerss/test_tts.py b/tests/components/voicerss/test_tts.py index 424bbe5e065b3f..4eab1868057eba 100644 --- a/tests/components/voicerss/test_tts.py +++ b/tests/components/voicerss/test_tts.py @@ -15,7 +15,7 @@ from homeassistant.setup import async_setup_component from tests.common import assert_setup_component, async_mock_service -from tests.components.tts.test_init import mutagen_mock # noqa: F401 +from tests.components.tts.conftest import mutagen_mock # noqa: F401 URL = "https://api.voicerss.org/" FORM_DATA = { diff --git a/tests/components/yandextts/test_tts.py b/tests/components/yandextts/test_tts.py index db4dbce4b8b8dc..495009eecf91e4 100644 --- a/tests/components/yandextts/test_tts.py +++ b/tests/components/yandextts/test_tts.py @@ -14,7 +14,7 @@ from homeassistant.setup import async_setup_component from tests.common import assert_setup_component, async_mock_service -from tests.components.tts.test_init import ( # noqa: F401, pylint: disable=unused-import +from tests.components.tts.conftest import ( # noqa: F401, pylint: disable=unused-import mutagen_mock, ) From ab67ba20f5ebd8c660bc0ebd9b7e38b3ee43f0bc Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 14 Feb 2022 18:10:50 +0100 Subject: [PATCH 0627/1098] Update pylint plugin to validate `_async_has_devices` (#66512) --- .core_files.yaml | 2 +- homeassistant/components/fjaraskupan/config_flow.py | 3 ++- homeassistant/components/gree/config_flow.py | 3 ++- .../components/hisense_aehw4a1/config_flow.py | 3 ++- homeassistant/components/ios/config_flow.py | 4 +++- homeassistant/components/izone/config_flow.py | 4 ++-- homeassistant/components/kulersky/config_flow.py | 3 ++- homeassistant/components/lifx/config_flow.py | 3 ++- homeassistant/components/zerproc/config_flow.py | 3 ++- pylint/plugins/hass_enforce_type_hints.py | 10 ++++++++++ 10 files changed, 28 insertions(+), 10 deletions(-) diff --git a/.core_files.yaml b/.core_files.yaml index b07dc04cd15bac..318a43875106d9 100644 --- a/.core_files.yaml +++ b/.core_files.yaml @@ -102,7 +102,7 @@ components: &components # Testing related files that affect the whole test/linting suite tests: &tests - codecov.yaml - - pylint/* + - pylint/** - requirements_test_pre_commit.txt - requirements_test.txt - tests/auth/** diff --git a/homeassistant/components/fjaraskupan/config_flow.py b/homeassistant/components/fjaraskupan/config_flow.py index 4d4d1882dcd239..da0a7f1dd2bbfa 100644 --- a/homeassistant/components/fjaraskupan/config_flow.py +++ b/homeassistant/components/fjaraskupan/config_flow.py @@ -9,6 +9,7 @@ from bleak.backends.scanner import AdvertisementData from fjaraskupan import UUID_SERVICE, device_filter +from homeassistant.core import HomeAssistant from homeassistant.helpers.config_entry_flow import register_discovery_flow from .const import DOMAIN @@ -16,7 +17,7 @@ CONST_WAIT_TIME = 5.0 -async def _async_has_devices(hass) -> bool: +async def _async_has_devices(hass: HomeAssistant) -> bool: """Return if there are devices that can be discovered.""" event = asyncio.Event() diff --git a/homeassistant/components/gree/config_flow.py b/homeassistant/components/gree/config_flow.py index bf6fc7cc3343af..d317fe6d8732c6 100644 --- a/homeassistant/components/gree/config_flow.py +++ b/homeassistant/components/gree/config_flow.py @@ -1,12 +1,13 @@ """Config flow for Gree.""" from greeclimate.discovery import Discovery +from homeassistant.core import HomeAssistant from homeassistant.helpers import config_entry_flow from .const import DISCOVERY_TIMEOUT, DOMAIN -async def _async_has_devices(hass) -> bool: +async def _async_has_devices(hass: HomeAssistant) -> bool: """Return if there are devices that can be discovered.""" gree_discovery = Discovery(DISCOVERY_TIMEOUT) devices = await gree_discovery.scan(wait_for=DISCOVERY_TIMEOUT) diff --git a/homeassistant/components/hisense_aehw4a1/config_flow.py b/homeassistant/components/hisense_aehw4a1/config_flow.py index 06eebb4594883f..8fd651d178236a 100644 --- a/homeassistant/components/hisense_aehw4a1/config_flow.py +++ b/homeassistant/components/hisense_aehw4a1/config_flow.py @@ -1,12 +1,13 @@ """Config flow for Hisense AEH-W4A1 integration.""" from pyaehw4a1.aehw4a1 import AehW4a1 +from homeassistant.core import HomeAssistant from homeassistant.helpers import config_entry_flow from .const import DOMAIN -async def _async_has_devices(hass): +async def _async_has_devices(hass: HomeAssistant) -> bool: """Return if there are devices that can be discovered.""" aehw4a1_ip_addresses = await AehW4a1().discovery() return len(aehw4a1_ip_addresses) > 0 diff --git a/homeassistant/components/ios/config_flow.py b/homeassistant/components/ios/config_flow.py index fc15cd833bc9e5..47959e34074f82 100644 --- a/homeassistant/components/ios/config_flow.py +++ b/homeassistant/components/ios/config_flow.py @@ -3,4 +3,6 @@ from .const import DOMAIN -config_entry_flow.register_discovery_flow(DOMAIN, "Home Assistant iOS", lambda *_: True) +config_entry_flow.register_discovery_flow( + DOMAIN, "Home Assistant iOS", lambda hass: True +) diff --git a/homeassistant/components/izone/config_flow.py b/homeassistant/components/izone/config_flow.py index 83a77e1257904d..bc4fb8ceddcc00 100644 --- a/homeassistant/components/izone/config_flow.py +++ b/homeassistant/components/izone/config_flow.py @@ -6,7 +6,7 @@ from async_timeout import timeout -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_entry_flow from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -16,7 +16,7 @@ _LOGGER = logging.getLogger(__name__) -async def _async_has_devices(hass): +async def _async_has_devices(hass: HomeAssistant) -> bool: controller_ready = asyncio.Event() diff --git a/homeassistant/components/kulersky/config_flow.py b/homeassistant/components/kulersky/config_flow.py index f56688e919ac0e..1f9c67b9aa1ff4 100644 --- a/homeassistant/components/kulersky/config_flow.py +++ b/homeassistant/components/kulersky/config_flow.py @@ -3,6 +3,7 @@ import pykulersky +from homeassistant.core import HomeAssistant from homeassistant.helpers import config_entry_flow from .const import DOMAIN @@ -10,7 +11,7 @@ _LOGGER = logging.getLogger(__name__) -async def _async_has_devices(hass) -> bool: +async def _async_has_devices(hass: HomeAssistant) -> bool: """Return if there are devices that can be discovered.""" # Check if there are any devices that can be discovered in the network. try: diff --git a/homeassistant/components/lifx/config_flow.py b/homeassistant/components/lifx/config_flow.py index 1713683b720d3e..c48bee9e4e7246 100644 --- a/homeassistant/components/lifx/config_flow.py +++ b/homeassistant/components/lifx/config_flow.py @@ -1,12 +1,13 @@ """Config flow flow LIFX.""" import aiolifx +from homeassistant.core import HomeAssistant from homeassistant.helpers import config_entry_flow from .const import DOMAIN -async def _async_has_devices(hass): +async def _async_has_devices(hass: HomeAssistant) -> bool: """Return if there are devices that can be discovered.""" lifx_ip_addresses = await aiolifx.LifxScan(hass.loop).scan() return len(lifx_ip_addresses) > 0 diff --git a/homeassistant/components/zerproc/config_flow.py b/homeassistant/components/zerproc/config_flow.py index fdf17c14e5a25d..e68c51cd7eb472 100644 --- a/homeassistant/components/zerproc/config_flow.py +++ b/homeassistant/components/zerproc/config_flow.py @@ -3,6 +3,7 @@ import pyzerproc +from homeassistant.core import HomeAssistant from homeassistant.helpers import config_entry_flow from .const import DOMAIN @@ -10,7 +11,7 @@ _LOGGER = logging.getLogger(__name__) -async def _async_has_devices(hass) -> bool: +async def _async_has_devices(hass: HomeAssistant) -> bool: """Return if there are devices that can be discovered.""" try: devices = await pyzerproc.discover() diff --git a/pylint/plugins/hass_enforce_type_hints.py b/pylint/plugins/hass_enforce_type_hints.py index 1264fa2c1df060..cb90499b6ca9a9 100644 --- a/pylint/plugins/hass_enforce_type_hints.py +++ b/pylint/plugins/hass_enforce_type_hints.py @@ -44,6 +44,8 @@ class TypeHintMatch: "device_tracker": re.compile(r"^homeassistant\.components\.\w+\.(device_tracker)$"), # diagnostics matches only in the package root (diagnostics.py) "diagnostics": re.compile(r"^homeassistant\.components\.\w+\.(diagnostics)$"), + # config_flow matches only in the package root (config_flow.py) + "config_flow": re.compile(r"^homeassistant\.components\.\w+\.(config_flow)$") } _METHOD_MATCH: list[TypeHintMatch] = [ @@ -192,6 +194,14 @@ class TypeHintMatch: }, return_type=UNDEFINED, ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["config_flow"], + function_name="_async_has_devices", + arg_types={ + 0: "HomeAssistant", + }, + return_type="bool", + ), ] From b6a3b012bb2f356aef9df0acc729b77f790c87d3 Mon Sep 17 00:00:00 2001 From: uSlackr Date: Mon, 14 Feb 2022 12:17:19 -0500 Subject: [PATCH 0628/1098] Correct modbus address limits (#66367) --- homeassistant/components/modbus/services.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/modbus/services.yaml b/homeassistant/components/modbus/services.yaml index 835927e4627099..87e8b98fa21cb9 100644 --- a/homeassistant/components/modbus/services.yaml +++ b/homeassistant/components/modbus/services.yaml @@ -8,8 +8,8 @@ write_coil: required: true selector: number: - min: 1 - max: 255 + min: 0 + max: 65535 state: name: State description: State to write. @@ -42,8 +42,8 @@ write_register: required: true selector: number: - min: 1 - max: 255 + min: 0 + max: 65535 unit: name: Unit description: Address of the modbus unit. From 3b3e12aaa297b64f1af44fb9093200a83ae0f365 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 14 Feb 2022 18:23:12 +0100 Subject: [PATCH 0629/1098] Fix `translation` typing (#66516) --- homeassistant/helpers/translation.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/translation.py b/homeassistant/helpers/translation.py index 2f20e1404d81a3..976c66dda56acf 100644 --- a/homeassistant/helpers/translation.py +++ b/homeassistant/helpers/translation.py @@ -3,6 +3,7 @@ import asyncio from collections import ChainMap +from collections.abc import Mapping import logging from typing import Any @@ -134,7 +135,7 @@ def _build_resources( translation_strings: dict[str, dict[str, Any]], components: set[str], category: str, -) -> dict[str, dict[str, Any]]: +) -> dict[str, dict[str, Any] | str]: """Build the resources response for the given components.""" # Build response return { @@ -251,6 +252,7 @@ def _build_category_cache( translation_strings: dict[str, dict[str, Any]], ) -> None: """Extract resources into the cache.""" + resource: dict[str, Any] | str cached = self.cache.setdefault(language, {}) categories: set[str] = set() for resource in translation_strings.values(): @@ -260,7 +262,8 @@ def _build_category_cache( resource_func = ( _merge_resources if category == "state" else _build_resources ) - new_resources = resource_func(translation_strings, components, category) + new_resources: Mapping[str, dict[str, Any] | str] + new_resources = resource_func(translation_strings, components, category) # type: ignore[assignment] for component, resource in new_resources.items(): category_cache: dict[str, Any] = cached.setdefault( From 2bc2f85b1b5aee19e5bd8d5168b53b3044ba663d Mon Sep 17 00:00:00 2001 From: Keilin Bickar Date: Mon, 14 Feb 2022 12:31:46 -0500 Subject: [PATCH 0630/1098] Support for lock domain in esphome (#65280) --- .coveragerc | 1 + .../components/esphome/entry_data.py | 2 + homeassistant/components/esphome/lock.py | 87 +++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 homeassistant/components/esphome/lock.py diff --git a/.coveragerc b/.coveragerc index e85278586ddc2c..5419a8247ade3b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -319,6 +319,7 @@ omit = homeassistant/components/esphome/entry_data.py homeassistant/components/esphome/fan.py homeassistant/components/esphome/light.py + homeassistant/components/esphome/lock.py homeassistant/components/esphome/number.py homeassistant/components/esphome/select.py homeassistant/components/esphome/sensor.py diff --git a/homeassistant/components/esphome/entry_data.py b/homeassistant/components/esphome/entry_data.py index e7bbc27141cb57..c00073b443202c 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -19,6 +19,7 @@ EntityState, FanInfo, LightInfo, + LockInfo, NumberInfo, SelectInfo, SensorInfo, @@ -44,6 +45,7 @@ CoverInfo: "cover", FanInfo: "fan", LightInfo: "light", + LockInfo: "lock", NumberInfo: "number", SelectInfo: "select", SensorInfo: "sensor", diff --git a/homeassistant/components/esphome/lock.py b/homeassistant/components/esphome/lock.py new file mode 100644 index 00000000000000..84c93d9df1343b --- /dev/null +++ b/homeassistant/components/esphome/lock.py @@ -0,0 +1,87 @@ +"""Support for ESPHome locks.""" +from __future__ import annotations + +from typing import Any + +from aioesphomeapi import LockCommand, LockEntityState, LockInfo, LockState + +from homeassistant.components.lock import SUPPORT_OPEN, LockEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ATTR_CODE +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import EsphomeEntity, esphome_state_property, platform_async_setup_entry + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up ESPHome switches based on a config entry.""" + await platform_async_setup_entry( + hass, + entry, + async_add_entities, + component_key="lock", + info_type=LockInfo, + entity_type=EsphomeLock, + state_type=LockEntityState, + ) + + +# https://github.com/PyCQA/pylint/issues/3150 for all @esphome_state_property +# pylint: disable=invalid-overridden-method + + +class EsphomeLock(EsphomeEntity[LockInfo, LockEntityState], LockEntity): + """A lock implementation for ESPHome.""" + + @property + def assumed_state(self) -> bool: + """Return True if unable to access real state of the entity.""" + return self._static_info.assumed_state + + @property + def supported_features(self) -> int: + """Flag supported features.""" + return SUPPORT_OPEN if self._static_info.supports_open else 0 + + @property + def code_format(self) -> str | None: + """Regex for code format or None if no code is required.""" + if self._static_info.requires_code: + return self._static_info.code_format + return None + + @esphome_state_property + def is_locked(self) -> bool | None: + """Return true if the lock is locked.""" + return self._state.state == LockState.LOCKED + + @esphome_state_property + def is_locking(self) -> bool | None: + """Return true if the lock is locking.""" + return self._state.state == LockState.LOCKING + + @esphome_state_property + def is_unlocking(self) -> bool | None: + """Return true if the lock is unlocking.""" + return self._state.state == LockState.UNLOCKING + + @esphome_state_property + def is_jammed(self) -> bool | None: + """Return true if the lock is jammed (incomplete locking).""" + return self._state.state == LockState.JAMMED + + async def async_lock(self, **kwargs: Any) -> None: + """Lock the lock.""" + await self._client.lock_command(self._static_info.key, LockCommand.LOCK) + + async def async_unlock(self, **kwargs: Any) -> None: + """Unlock the lock.""" + code = kwargs.get(ATTR_CODE, None) + await self._client.lock_command(self._static_info.key, LockCommand.UNLOCK, code) + + async def async_open(self, **kwargs: Any) -> None: + """Open the door latch.""" + await self._client.lock_command(self._static_info.key, LockCommand.OPEN) From 2bcc21ecbb7cd0208101f7f5f99b644e2563d4b6 Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Mon, 14 Feb 2022 19:25:14 +0100 Subject: [PATCH 0631/1098] Add velbus diagnostics (#65426) --- .coveragerc | 1 + .../components/velbus/diagnostics.py | 57 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 homeassistant/components/velbus/diagnostics.py diff --git a/.coveragerc b/.coveragerc index 5419a8247ade3b..b59986fbe64436 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1339,6 +1339,7 @@ omit = homeassistant/components/velbus/climate.py homeassistant/components/velbus/const.py homeassistant/components/velbus/cover.py + homeassistant/components/velbus/diagnostics.py homeassistant/components/velbus/light.py homeassistant/components/velbus/sensor.py homeassistant/components/velbus/switch.py diff --git a/homeassistant/components/velbus/diagnostics.py b/homeassistant/components/velbus/diagnostics.py new file mode 100644 index 00000000000000..f6015abd1f833d --- /dev/null +++ b/homeassistant/components/velbus/diagnostics.py @@ -0,0 +1,57 @@ +"""Diagnostics support for Velbus.""" +from __future__ import annotations + +from typing import Any + +from velbusaio.channels import Channel as VelbusChannel +from velbusaio.module import Module as VelbusModule + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.device_registry import DeviceEntry + +from .const import DOMAIN + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + controller = hass.data[DOMAIN][entry.entry_id]["cntrl"] + data: dict[str, Any] = {"entry": entry.as_dict(), "modules": []} + for module in controller.get_modules().values(): + data["modules"].append(_build_module_diagnostics_info(module)) + return data + + +async def async_get_device_diagnostics( + hass: HomeAssistant, entry: ConfigEntry, device: DeviceEntry +) -> dict[str, Any]: + """Return diagnostics for a device entry.""" + controller = hass.data[DOMAIN][entry.entry_id]["cntrl"] + channel = list(next(iter(device.identifiers)))[1] + modules = controller.get_modules() + return _build_module_diagnostics_info(modules[int(channel)]) + + +def _build_module_diagnostics_info(module: VelbusModule) -> dict[str, Any]: + """Build per module diagnostics info.""" + data: dict[str, Any] = { + "type": module.get_type_name(), + "address": module.get_addresses(), + "name": module.get_name(), + "sw_version": module.get_sw_version(), + "is_loaded": module.is_loaded(), + "channels": _build_channels_diagnostics_info(module.get_channels()), + } + return data + + +def _build_channels_diagnostics_info( + channels: dict[str, VelbusChannel] +) -> dict[str, Any]: + """Build diagnostics info for all channels.""" + data: dict[str, Any] = {} + for channel in channels.values(): + data[str(channel.get_channel_number())] = channel.get_channel_info() + return data From 9c5e0fc5e0790f60e59d65180cd0394cd23ba23c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 14 Feb 2022 20:10:18 +0100 Subject: [PATCH 0632/1098] Fix `auth` type comment (#66522) --- homeassistant/auth/mfa_modules/totp.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/auth/mfa_modules/totp.py b/homeassistant/auth/mfa_modules/totp.py index c979ba05b5aef0..a88aa19d742ec6 100644 --- a/homeassistant/auth/mfa_modules/totp.py +++ b/homeassistant/auth/mfa_modules/totp.py @@ -182,8 +182,8 @@ def __init__( self._auth_module: TotpAuthModule = auth_module self._user = user self._ota_secret: str = "" - self._url = None # type Optional[str] - self._image = None # type Optional[str] + self._url: str | None = None + self._image: str | None = None async def async_step_init( self, user_input: dict[str, str] | None = None @@ -218,7 +218,7 @@ async def async_step_init( self._url, self._image, ) = await hass.async_add_executor_job( - _generate_secret_and_qr_code, # type: ignore + _generate_secret_and_qr_code, str(self._user.name), ) From 09e59e5887e1d4939d4eb520f6dcce9d1f778bea Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Feb 2022 13:14:45 -0600 Subject: [PATCH 0633/1098] Fix flux_led turn on with slow responding devices (#66527) --- homeassistant/components/flux_led/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/flux_led/manifest.json b/homeassistant/components/flux_led/manifest.json index a41fead628c615..b3448d0b790f39 100644 --- a/homeassistant/components/flux_led/manifest.json +++ b/homeassistant/components/flux_led/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "dependencies": ["network"], "documentation": "https://www.home-assistant.io/integrations/flux_led", - "requirements": ["flux_led==0.28.22"], + "requirements": ["flux_led==0.28.26"], "quality_scale": "platinum", "codeowners": ["@icemanch", "@bdraco"], "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index 388f335b96df0f..ccc47cecb3fa50 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -684,7 +684,7 @@ fjaraskupan==1.0.2 flipr-api==1.4.1 # homeassistant.components.flux_led -flux_led==0.28.22 +flux_led==0.28.26 # homeassistant.components.homekit fnvhash==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0464e14c2a322c..8a4a99bef14e59 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -433,7 +433,7 @@ fjaraskupan==1.0.2 flipr-api==1.4.1 # homeassistant.components.flux_led -flux_led==0.28.22 +flux_led==0.28.26 # homeassistant.components.homekit fnvhash==0.1.0 From 5be5a014f3d3543b7e9b576e87a444bf857ae8ec Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 14 Feb 2022 20:15:09 +0100 Subject: [PATCH 0634/1098] Adjust Sonos for updated Spotify media browsing (#66508) --- homeassistant/components/sonos/media_browser.py | 16 ++-------------- homeassistant/components/sonos/media_player.py | 1 + 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/sonos/media_browser.py b/homeassistant/components/sonos/media_browser.py index 2d9714699284c8..2272ceb183fd7a 100644 --- a/homeassistant/components/sonos/media_browser.py +++ b/homeassistant/components/sonos/media_browser.py @@ -105,9 +105,6 @@ async def async_browse_media( hass, media_content_type, media_content_id, can_play_artist=False ) - if media_content_type == "spotify": - return await spotify.async_browse_media(hass, None, None, can_play_artist=False) - if media_content_type == "library": return await hass.async_add_executor_job( library_payload, @@ -303,17 +300,8 @@ async def root_payload( ) if "spotify" in hass.config.components: - children.append( - BrowseMedia( - title="Spotify", - media_class=MEDIA_CLASS_APP, - media_content_id="", - media_content_type="spotify", - thumbnail="https://brands.home-assistant.io/_/spotify/logo.png", - can_play=False, - can_expand=True, - ) - ) + result = await spotify.async_browse_media(hass, None, None) + children.extend(result.children) try: item = await media_source.async_browse_media( diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index e7ee76070a1ecb..2763bd3fe427bf 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -522,6 +522,7 @@ def play_media(self, media_type: str, media_id: str, **kwargs: Any) -> None: """ if spotify.is_spotify_media_type(media_type): media_type = spotify.resolve_spotify_media_type(media_type) + media_id = spotify.spotify_uri_from_media_browser_url(media_id) if media_source.is_media_source_id(media_id): media_type = MEDIA_TYPE_MUSIC From 7cb0ce0eec6c1883d0f8d9307d4cc9a4d1a52cd8 Mon Sep 17 00:00:00 2001 From: Simon Hansen <67142049+DurgNomis-drol@users.noreply.github.com> Date: Mon, 14 Feb 2022 21:07:50 +0100 Subject: [PATCH 0635/1098] Move config option to OptionsFlow in iss (#65303) Co-authored-by: Franck Nijhof --- homeassistant/components/iss/__init__.py | 7 +++ homeassistant/components/iss/binary_sensor.py | 4 +- homeassistant/components/iss/config_flow.py | 50 +++++++++++++++---- homeassistant/components/iss/strings.json | 14 ++++-- .../components/iss/translations/en.json | 12 +++-- tests/components/iss/test_config_flow.py | 42 ++++++++++++---- 6 files changed, 102 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/iss/__init__.py b/homeassistant/components/iss/__init__.py index af44e621a7fb41..29d6ced184b929 100644 --- a/homeassistant/components/iss/__init__.py +++ b/homeassistant/components/iss/__init__.py @@ -15,6 +15,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data.setdefault(DOMAIN, {}) + entry.async_on_unload(entry.add_update_listener(update_listener)) + hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True @@ -25,3 +27,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): del hass.data[DOMAIN] return unload_ok + + +async def update_listener(hass: HomeAssistant, entry: ConfigEntry): + """Handle options update.""" + await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/iss/binary_sensor.py b/homeassistant/components/iss/binary_sensor.py index 5272053a517073..196bdfd055eb50 100644 --- a/homeassistant/components/iss/binary_sensor.py +++ b/homeassistant/components/iss/binary_sensor.py @@ -72,8 +72,8 @@ async def async_setup_entry( ) -> None: """Set up the sensor platform.""" - name = entry.data.get(CONF_NAME, DEFAULT_NAME) - show_on_map = entry.data.get(CONF_SHOW_ON_MAP, False) + name = entry.title + show_on_map = entry.options.get(CONF_SHOW_ON_MAP, False) try: iss_data = IssData(hass.config.latitude, hass.config.longitude) diff --git a/homeassistant/components/iss/config_flow.py b/homeassistant/components/iss/config_flow.py index e1703b54acbead..dc80126bd140cf 100644 --- a/homeassistant/components/iss/config_flow.py +++ b/homeassistant/components/iss/config_flow.py @@ -4,8 +4,10 @@ from homeassistant import config_entries from homeassistant.const import CONF_NAME, CONF_SHOW_ON_MAP +from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult +from .binary_sensor import DEFAULT_NAME from .const import DOMAIN @@ -14,6 +16,14 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 + @staticmethod + @callback + def async_get_options_flow( + config_entry: config_entries.ConfigEntry, + ) -> config_entries.OptionsFlow: + """Get the options flow for this handler.""" + return OptionsFlowHandler(config_entry) + async def async_step_user(self, user_input=None) -> FlowResult: """Handle a flow initialized by the user.""" # Check if already configured @@ -26,17 +36,12 @@ async def async_step_user(self, user_input=None) -> FlowResult: if user_input is not None: return self.async_create_entry( - title="International Space Station", data=user_input + title=user_input.get(CONF_NAME, DEFAULT_NAME), + data={}, + options={CONF_SHOW_ON_MAP: user_input.get(CONF_SHOW_ON_MAP, False)}, ) - return self.async_show_form( - step_id="user", - data_schema=vol.Schema( - { - vol.Optional(CONF_SHOW_ON_MAP, default=False): bool, - } - ), - ) + return self.async_show_form(step_id="user") async def async_step_import(self, conf: dict) -> FlowResult: """Import a configuration from configuration.yaml.""" @@ -46,3 +51,30 @@ async def async_step_import(self, conf: dict) -> FlowResult: CONF_SHOW_ON_MAP: conf[CONF_SHOW_ON_MAP], } ) + + +class OptionsFlowHandler(config_entries.OptionsFlow): + """Config flow options handler for iss.""" + + def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + """Initialize options flow.""" + self.config_entry = config_entry + self.options = dict(config_entry.options) + + async def async_step_init(self, user_input=None) -> FlowResult: + """Manage the options.""" + if user_input is not None: + self.options.update(user_input) + return self.async_create_entry(title="", data=self.options) + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema( + { + vol.Optional( + CONF_SHOW_ON_MAP, + default=self.config_entry.options.get(CONF_SHOW_ON_MAP, False), + ): bool, + } + ), + ) diff --git a/homeassistant/components/iss/strings.json b/homeassistant/components/iss/strings.json index b9dd7c374d08dd..4a2da5f6556042 100644 --- a/homeassistant/components/iss/strings.json +++ b/homeassistant/components/iss/strings.json @@ -2,15 +2,21 @@ "config": { "step": { "user": { - "description": "Do you want to configure the International Space Station?", - "data": { - "show_on_map": "Show on map?" - } + "description": "Do you want to configure International Space Station (ISS)?" } }, "abort": { "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", "latitude_longitude_not_defined": "Latitude and longitude are not defined in Home Assistant." } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "Show on map" + } + } + } } } diff --git a/homeassistant/components/iss/translations/en.json b/homeassistant/components/iss/translations/en.json index f8ef8d27cd70fe..b90ff56964a571 100644 --- a/homeassistant/components/iss/translations/en.json +++ b/homeassistant/components/iss/translations/en.json @@ -6,10 +6,16 @@ }, "step": { "user": { + "description": "Do you want to configure International Space Station (ISS)?" + } + } + }, + "options": { + "step": { + "init": { "data": { - "show_on_map": "Show on map?" - }, - "description": "Do you want to configure the International Space Station?" + "show_on_map": "Show on map" + } } } } diff --git a/tests/components/iss/test_config_flow.py b/tests/components/iss/test_config_flow.py index a20a8729f55387..e47d977b96f169 100644 --- a/tests/components/iss/test_config_flow.py +++ b/tests/components/iss/test_config_flow.py @@ -7,11 +7,12 @@ from homeassistant.config import async_process_ha_core_config from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER from homeassistant.const import CONF_NAME, CONF_SHOW_ON_MAP +from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry -async def test_import(hass): +async def test_import(hass: HomeAssistant): """Test entry will be imported.""" imported_config = {CONF_NAME: DEFAULT_NAME, CONF_SHOW_ON_MAP: False} @@ -22,10 +23,11 @@ async def test_import(hass): DOMAIN, context={"source": SOURCE_IMPORT}, data=imported_config ) assert result.get("type") == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result.get("result").data == imported_config + assert result.get("result").title == DEFAULT_NAME + assert result.get("result").options == {CONF_SHOW_ON_MAP: False} -async def test_create_entry(hass): +async def test_create_entry(hass: HomeAssistant): """Test we can finish a config flow.""" result = await hass.config_entries.flow.async_init( @@ -39,14 +41,14 @@ async def test_create_entry(hass): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_SHOW_ON_MAP: True}, + {}, ) assert result.get("type") == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result.get("result").data[CONF_SHOW_ON_MAP] is True + assert result.get("result").data == {} -async def test_integration_already_exists(hass): +async def test_integration_already_exists(hass: HomeAssistant): """Test we only allow a single config flow.""" MockConfigEntry( @@ -55,14 +57,14 @@ async def test_integration_already_exists(hass): ).add_to_hass(hass) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data={CONF_SHOW_ON_MAP: False} + DOMAIN, context={"source": SOURCE_USER}, data={} ) assert result.get("type") == data_entry_flow.RESULT_TYPE_ABORT assert result.get("reason") == "single_instance_allowed" -async def test_abort_no_home(hass): +async def test_abort_no_home(hass: HomeAssistant): """Test we don't create an entry if no coordinates are set.""" await async_process_ha_core_config( @@ -71,8 +73,30 @@ async def test_abort_no_home(hass): ) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data={CONF_SHOW_ON_MAP: False} + DOMAIN, context={"source": SOURCE_USER}, data={} ) assert result.get("type") == data_entry_flow.RESULT_TYPE_ABORT assert result.get("reason") == "latitude_longitude_not_defined" + + +async def test_options(hass: HomeAssistant): + """Test options flow.""" + + config_entry = MockConfigEntry( + domain=DOMAIN, + data={}, + ) + + config_entry.add_to_hass(hass) + + optionflow = await hass.config_entries.options.async_init(config_entry.entry_id) + + configured = await hass.config_entries.options.async_configure( + optionflow["flow_id"], + user_input={ + CONF_SHOW_ON_MAP: True, + }, + ) + + assert configured.get("type") == "create_entry" From 113c3149c4b78aaf5ab79f4ac1cb49a2ce4c38cf Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 14 Feb 2022 15:14:02 -0500 Subject: [PATCH 0636/1098] Improve zwave_js device automation strings for config parameters (#66428) * Improve zwave_js device automation strings for config parameters * rename things to be more clear * Match config file format --- .../components/zwave_js/device_action.py | 3 ++- .../zwave_js/device_automation_helpers.py | 9 +++++++ .../components/zwave_js/device_condition.py | 24 ++++++++++--------- .../components/zwave_js/device_trigger.py | 8 +++++-- .../components/zwave_js/test_device_action.py | 11 ++++----- .../zwave_js/test_device_condition.py | 12 +++++----- .../zwave_js/test_device_trigger.py | 11 ++++----- 7 files changed, 45 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/zwave_js/device_action.py b/homeassistant/components/zwave_js/device_action.py index 9f6fa7fc35cf78..b81d675e6fd72a 100644 --- a/homeassistant/components/zwave_js/device_action.py +++ b/homeassistant/components/zwave_js/device_action.py @@ -53,6 +53,7 @@ from .device_automation_helpers import ( CONF_SUBTYPE, VALUE_ID_REGEX, + generate_config_parameter_subtype, get_config_parameter_value_schema, ) from .helpers import async_get_node_from_device_id @@ -165,7 +166,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: CONF_TYPE: SERVICE_SET_CONFIG_PARAMETER, ATTR_CONFIG_PARAMETER: config_value.property_, ATTR_CONFIG_PARAMETER_BITMASK: config_value.property_key, - CONF_SUBTYPE: f"{config_value.value_id} ({config_value.property_name})", + CONF_SUBTYPE: generate_config_parameter_subtype(config_value), } for config_value in node.get_configuration_values().values() ] diff --git a/homeassistant/components/zwave_js/device_automation_helpers.py b/homeassistant/components/zwave_js/device_automation_helpers.py index cfdb65a4b028c9..906efb2c4f9515 100644 --- a/homeassistant/components/zwave_js/device_automation_helpers.py +++ b/homeassistant/components/zwave_js/device_automation_helpers.py @@ -32,3 +32,12 @@ def get_config_parameter_value_schema(node: Node, value_id: str) -> vol.Schema | return vol.In({int(k): v for k, v in config_value.metadata.states.items()}) return None + + +def generate_config_parameter_subtype(config_value: ConfigurationValue) -> str: + """Generate the config parameter name used in a device automation subtype.""" + parameter = str(config_value.property_) + if config_value.property_key: + parameter = f"{parameter}[{hex(config_value.property_key)}]" + + return f"{parameter} ({config_value.property_name})" diff --git a/homeassistant/components/zwave_js/device_condition.py b/homeassistant/components/zwave_js/device_condition.py index fcd769dc8a4a2d..ec8538e2b36f02 100644 --- a/homeassistant/components/zwave_js/device_condition.py +++ b/homeassistant/components/zwave_js/device_condition.py @@ -29,6 +29,7 @@ CONF_SUBTYPE, CONF_VALUE_ID, NODE_STATUSES, + generate_config_parameter_subtype, get_config_parameter_value_schema, ) from .helpers import ( @@ -140,17 +141,18 @@ async def async_get_conditions( conditions.append({**base_condition, CONF_TYPE: NODE_STATUS_TYPE}) # Config parameter conditions - conditions.extend( - [ - { - **base_condition, - CONF_VALUE_ID: config_value.value_id, - CONF_TYPE: CONFIG_PARAMETER_TYPE, - CONF_SUBTYPE: f"{config_value.value_id} ({config_value.property_name})", - } - for config_value in node.get_configuration_values().values() - ] - ) + for config_value in node.get_configuration_values().values(): + conditions.extend( + [ + { + **base_condition, + CONF_VALUE_ID: config_value.value_id, + CONF_TYPE: CONFIG_PARAMETER_TYPE, + CONF_SUBTYPE: generate_config_parameter_subtype(config_value), + } + for config_value in node.get_configuration_values().values() + ] + ) return conditions diff --git a/homeassistant/components/zwave_js/device_trigger.py b/homeassistant/components/zwave_js/device_trigger.py index 888efbf2bfd197..0615668cccdd40 100644 --- a/homeassistant/components/zwave_js/device_trigger.py +++ b/homeassistant/components/zwave_js/device_trigger.py @@ -49,7 +49,11 @@ ZWAVE_JS_NOTIFICATION_EVENT, ZWAVE_JS_VALUE_NOTIFICATION_EVENT, ) -from .device_automation_helpers import CONF_SUBTYPE, NODE_STATUSES +from .device_automation_helpers import ( + CONF_SUBTYPE, + NODE_STATUSES, + generate_config_parameter_subtype, +) from .helpers import ( async_get_node_from_device_id, async_get_node_status_sensor_entity_id, @@ -353,7 +357,7 @@ async def async_get_triggers( ATTR_PROPERTY_KEY: config_value.property_key, ATTR_ENDPOINT: config_value.endpoint, ATTR_COMMAND_CLASS: config_value.command_class, - CONF_SUBTYPE: f"{config_value.value_id} ({config_value.property_name})", + CONF_SUBTYPE: generate_config_parameter_subtype(config_value), } for config_value in node.get_configuration_values().values() ] diff --git a/tests/components/zwave_js/test_device_action.py b/tests/components/zwave_js/test_device_action.py index 5377d420268c1a..07663ce9456924 100644 --- a/tests/components/zwave_js/test_device_action.py +++ b/tests/components/zwave_js/test_device_action.py @@ -67,7 +67,7 @@ async def test_get_actions( "device_id": device.id, "parameter": 3, "bitmask": None, - "subtype": f"{node.node_id}-112-0-3 (Beeper)", + "subtype": "3 (Beeper)", }, ] actions = await async_get_device_automations( @@ -161,7 +161,7 @@ async def test_actions( "device_id": device.id, "parameter": 1, "bitmask": None, - "subtype": "2-112-0-3 (Beeper)", + "subtype": "3 (Beeper)", "value": 1, }, }, @@ -328,7 +328,6 @@ async def test_get_action_capabilities( integration: ConfigEntry, ): """Test we get the expected action capabilities.""" - node = climate_radio_thermostat_ct100_plus dev_reg = device_registry.async_get(hass) device = device_registry.async_entries_for_config_entry( dev_reg, integration.entry_id @@ -423,7 +422,7 @@ async def test_get_action_capabilities( "type": "set_config_parameter", "parameter": 1, "bitmask": None, - "subtype": f"{node.node_id}-112-0-1 (Temperature Reporting Threshold)", + "subtype": "1 (Temperature Reporting Threshold)", }, ) assert capabilities and "extra_fields" in capabilities @@ -455,7 +454,7 @@ async def test_get_action_capabilities( "type": "set_config_parameter", "parameter": 10, "bitmask": None, - "subtype": f"{node.node_id}-112-0-10 (Temperature Reporting Filter)", + "subtype": "10 (Temperature Reporting Filter)", }, ) assert capabilities and "extra_fields" in capabilities @@ -482,7 +481,7 @@ async def test_get_action_capabilities( "type": "set_config_parameter", "parameter": 2, "bitmask": None, - "subtype": f"{node.node_id}-112-0-2 (HVAC Settings)", + "subtype": "2 (HVAC Settings)", }, ) assert not capabilities diff --git a/tests/components/zwave_js/test_device_condition.py b/tests/components/zwave_js/test_device_condition.py index 71a6865287c55c..da6b4b1545928e 100644 --- a/tests/components/zwave_js/test_device_condition.py +++ b/tests/components/zwave_js/test_device_condition.py @@ -52,7 +52,7 @@ async def test_get_conditions(hass, client, lock_schlage_be469, integration) -> "type": "config_parameter", "device_id": device.id, "value_id": value_id, - "subtype": f"{value_id} ({name})", + "subtype": f"{config_value.property_} ({name})", }, { "condition": "device", @@ -250,7 +250,7 @@ async def test_config_parameter_state( "device_id": device.id, "type": "config_parameter", "value_id": f"{lock_schlage_be469.node_id}-112-0-3", - "subtype": f"{lock_schlage_be469.node_id}-112-0-3 (Beeper)", + "subtype": "3 (Beeper)", "value": 255, } ], @@ -270,7 +270,7 @@ async def test_config_parameter_state( "device_id": device.id, "type": "config_parameter", "value_id": f"{lock_schlage_be469.node_id}-112-0-6", - "subtype": f"{lock_schlage_be469.node_id}-112-0-6 (User Slot Status)", + "subtype": "6 (User Slot Status)", "value": 1, } ], @@ -483,7 +483,7 @@ async def test_get_condition_capabilities_config_parameter( "device_id": device.id, "type": "config_parameter", "value_id": f"{node.node_id}-112-0-1", - "subtype": f"{node.node_id}-112-0-1 (Temperature Reporting Threshold)", + "subtype": "1 (Temperature Reporting Threshold)", }, ) assert capabilities and "extra_fields" in capabilities @@ -514,7 +514,7 @@ async def test_get_condition_capabilities_config_parameter( "device_id": device.id, "type": "config_parameter", "value_id": f"{node.node_id}-112-0-10", - "subtype": f"{node.node_id}-112-0-10 (Temperature Reporting Filter)", + "subtype": "10 (Temperature Reporting Filter)", }, ) assert capabilities and "extra_fields" in capabilities @@ -540,7 +540,7 @@ async def test_get_condition_capabilities_config_parameter( "device_id": device.id, "type": "config_parameter", "value_id": f"{node.node_id}-112-0-2", - "subtype": f"{node.node_id}-112-0-2 (HVAC Settings)", + "subtype": "2 (HVAC Settings)", }, ) assert not capabilities diff --git a/tests/components/zwave_js/test_device_trigger.py b/tests/components/zwave_js/test_device_trigger.py index bf3738a7fb3fd8..8a792706461920 100644 --- a/tests/components/zwave_js/test_device_trigger.py +++ b/tests/components/zwave_js/test_device_trigger.py @@ -1120,7 +1120,6 @@ async def test_get_value_updated_config_parameter_triggers( hass, client, lock_schlage_be469, integration ): """Test we get the zwave_js.value_updated.config_parameter trigger from a zwave_js device.""" - node = lock_schlage_be469 dev_reg = async_get_dev_reg(hass) device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] expected_trigger = { @@ -1132,7 +1131,7 @@ async def test_get_value_updated_config_parameter_triggers( "property_key": None, "endpoint": 0, "command_class": CommandClass.CONFIGURATION.value, - "subtype": f"{node.node_id}-112-0-3 (Beeper)", + "subtype": "3 (Beeper)", } triggers = await async_get_device_automations( hass, DeviceAutomationType.TRIGGER, device.id @@ -1163,7 +1162,7 @@ async def test_if_value_updated_config_parameter_fires( "property_key": None, "endpoint": 0, "command_class": CommandClass.CONFIGURATION.value, - "subtype": f"{node.node_id}-112-0-3 (Beeper)", + "subtype": "3 (Beeper)", "from": 255, }, "action": { @@ -1212,7 +1211,6 @@ async def test_get_trigger_capabilities_value_updated_config_parameter_range( hass, client, lock_schlage_be469, integration ): """Test we get the expected capabilities from a range zwave_js.value_updated.config_parameter trigger.""" - node = lock_schlage_be469 dev_reg = async_get_dev_reg(hass) device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] capabilities = await device_trigger.async_get_trigger_capabilities( @@ -1226,7 +1224,7 @@ async def test_get_trigger_capabilities_value_updated_config_parameter_range( "property_key": None, "endpoint": 0, "command_class": CommandClass.CONFIGURATION.value, - "subtype": f"{node.node_id}-112-0-6 (User Slot Status)", + "subtype": "6 (User Slot Status)", }, ) assert capabilities and "extra_fields" in capabilities @@ -1255,7 +1253,6 @@ async def test_get_trigger_capabilities_value_updated_config_parameter_enumerate hass, client, lock_schlage_be469, integration ): """Test we get the expected capabilities from an enumerated zwave_js.value_updated.config_parameter trigger.""" - node = lock_schlage_be469 dev_reg = async_get_dev_reg(hass) device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] capabilities = await device_trigger.async_get_trigger_capabilities( @@ -1269,7 +1266,7 @@ async def test_get_trigger_capabilities_value_updated_config_parameter_enumerate "property_key": None, "endpoint": 0, "command_class": CommandClass.CONFIGURATION.value, - "subtype": f"{node.node_id}-112-0-3 (Beeper)", + "subtype": "3 (Beeper)", }, ) assert capabilities and "extra_fields" in capabilities From 152dbfd2fe7e1e0562000c9f703b67810028a837 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 14 Feb 2022 15:38:22 -0500 Subject: [PATCH 0637/1098] Add button entity to ping zwave_js node (#66129) * Add button entity to ping zwave_js node * Fix docstring * Fix docstrings * Fix and simplify tests * Fix name * Update homeassistant/components/zwave_js/button.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/zwave_js/services.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/zwave_js/button.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/zwave_js/button.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/zwave_js/button.py Co-authored-by: Martin Hjelmare * review comments * Update homeassistant/components/zwave_js/button.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/zwave_js/button.py Co-authored-by: Martin Hjelmare * Remove self.client line * Add callback to remove entity * Add extra dispatch signal on replacement * Combine signals for valueless entities Co-authored-by: Martin Hjelmare --- homeassistant/components/zwave_js/__init__.py | 41 +++++------ homeassistant/components/zwave_js/button.py | 73 +++++++++++++++++++ homeassistant/components/zwave_js/helpers.py | 5 ++ homeassistant/components/zwave_js/sensor.py | 9 +-- homeassistant/components/zwave_js/services.py | 7 +- tests/components/zwave_js/conftest.py | 12 --- tests/components/zwave_js/test_button.py | 33 +++++++++ tests/components/zwave_js/test_init.py | 12 +-- 8 files changed, 147 insertions(+), 45 deletions(-) create mode 100644 homeassistant/components/zwave_js/button.py create mode 100644 tests/components/zwave_js/test_button.py diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 7fb785d429e595..0e1c6445a9f38d 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -15,6 +15,7 @@ ) from zwave_js_server.model.value import Value, ValueNotification +from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -93,6 +94,7 @@ get_device_id, get_device_id_ext, get_unique_id, + get_valueless_base_unique_id, ) from .migrate import async_migrate_discovered_value from .services import ZWaveServices @@ -171,11 +173,19 @@ async def async_setup_entry( # noqa: C901 entry_hass_data: dict = hass.data[DOMAIN].setdefault(entry.entry_id, {}) entry_hass_data[DATA_CLIENT] = client - entry_hass_data[DATA_PLATFORM_SETUP] = {} + platform_setup_tasks = entry_hass_data[DATA_PLATFORM_SETUP] = {} registered_unique_ids: dict[str, dict[str, set[str]]] = defaultdict(dict) discovered_value_ids: dict[str, set[str]] = defaultdict(set) + async def async_setup_platform(platform: str) -> None: + """Set up platform if needed.""" + if platform not in platform_setup_tasks: + platform_setup_tasks[platform] = hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, platform) + ) + await platform_setup_tasks[platform] + @callback def remove_device(device: device_registry.DeviceEntry) -> None: """Remove device from registry.""" @@ -202,13 +212,8 @@ async def async_handle_discovery_info( disc_info, ) - platform_setup_tasks = entry_hass_data[DATA_PLATFORM_SETUP] platform = disc_info.platform - if platform not in platform_setup_tasks: - platform_setup_tasks[platform] = hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, platform) - ) - await platform_setup_tasks[platform] + await async_setup_platform(platform) LOGGER.debug("Discovered entity: %s", disc_info) async_dispatcher_send( @@ -256,6 +261,12 @@ async def async_on_node_ready(node: ZwaveNode) -> None: ) ) + # Create a ping button for each device + await async_setup_platform(BUTTON_DOMAIN) + async_dispatcher_send( + hass, f"{DOMAIN}_{entry.entry_id}_add_ping_button_entity", node + ) + # add listeners to handle new values that get added later for event in ("value added", "value updated", "metadata updated"): entry.async_on_unload( @@ -284,19 +295,7 @@ async def async_on_node_ready(node: ZwaveNode) -> None: async def async_on_node_added(node: ZwaveNode) -> None: """Handle node added event.""" - platform_setup_tasks = entry_hass_data[DATA_PLATFORM_SETUP] - - # We need to set up the sensor platform if it hasn't already been setup in - # order to create the node status sensor - if SENSOR_DOMAIN not in platform_setup_tasks: - platform_setup_tasks[SENSOR_DOMAIN] = hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, SENSOR_DOMAIN) - ) - - # This guard ensures that concurrent runs of this function all await the - # platform setup task - if not platform_setup_tasks[SENSOR_DOMAIN].done(): - await platform_setup_tasks[SENSOR_DOMAIN] + await async_setup_platform(SENSOR_DOMAIN) # Create a node status sensor for each device async_dispatcher_send( @@ -358,7 +357,7 @@ def async_on_node_removed(event: dict) -> None: async_dispatcher_send( hass, - f"{DOMAIN}_{client.driver.controller.home_id}.{node.node_id}.node_status_remove_entity", + f"{DOMAIN}_{get_valueless_base_unique_id(client, node)}_remove_entity", ) else: remove_device(device) diff --git a/homeassistant/components/zwave_js/button.py b/homeassistant/components/zwave_js/button.py new file mode 100644 index 00000000000000..ef8572fedc3b25 --- /dev/null +++ b/homeassistant/components/zwave_js/button.py @@ -0,0 +1,73 @@ +"""Representation of Z-Wave buttons.""" +from __future__ import annotations + +from zwave_js_server.client import Client as ZwaveClient +from zwave_js_server.model.node import Node as ZwaveNode + +from homeassistant.components.button import ButtonEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity import DeviceInfo, EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DATA_CLIENT, DOMAIN +from .helpers import get_device_id, get_valueless_base_unique_id + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Z-Wave button from config entry.""" + client: ZwaveClient = hass.data[DOMAIN][config_entry.entry_id][DATA_CLIENT] + + @callback + def async_add_ping_button_entity(node: ZwaveNode) -> None: + """Add ping button entity.""" + async_add_entities([ZWaveNodePingButton(client, node)]) + + config_entry.async_on_unload( + async_dispatcher_connect( + hass, + f"{DOMAIN}_{config_entry.entry_id}_add_ping_button_entity", + async_add_ping_button_entity, + ) + ) + + +class ZWaveNodePingButton(ButtonEntity): + """Representation of a ping button entity.""" + + _attr_should_poll = False + _attr_entity_category = EntityCategory.CONFIG + + def __init__(self, client: ZwaveClient, node: ZwaveNode) -> None: + """Initialize a ping Z-Wave device button entity.""" + self.node = node + name: str = ( + node.name or node.device_config.description or f"Node {node.node_id}" + ) + # Entity class attributes + self._attr_name = f"{name}: Ping" + self._base_unique_id = get_valueless_base_unique_id(client, node) + self._attr_unique_id = f"{self._base_unique_id}.ping" + # device is precreated in main handler + self._attr_device_info = DeviceInfo( + identifiers={get_device_id(client, node)}, + ) + + async def async_added_to_hass(self) -> None: + """Call when entity is added.""" + self.async_on_remove( + async_dispatcher_connect( + self.hass, + f"{DOMAIN}_{self._base_unique_id}_remove_entity", + self.async_remove, + ) + ) + + async def async_press(self) -> None: + """Press the button.""" + self.hass.async_create_task(self.node.async_ping()) diff --git a/homeassistant/components/zwave_js/helpers.py b/homeassistant/components/zwave_js/helpers.py index 3deb75cf761e0b..2eb440cec9075c 100644 --- a/homeassistant/components/zwave_js/helpers.py +++ b/homeassistant/components/zwave_js/helpers.py @@ -55,6 +55,11 @@ def update_data_collection_preference( @callback +def get_valueless_base_unique_id(client: ZwaveClient, node: ZwaveNode) -> str: + """Return the base unique ID for an entity that is not based on a value.""" + return f"{client.driver.controller.home_id}.{node.node_id}" + + def get_unique_id(client: ZwaveClient, value_id: str) -> str: """Get unique ID from client and value ID.""" return f"{client.driver.controller.home_id}.{value_id}" diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index 76cb6fd22e911b..840c36b7fde65f 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -61,7 +61,7 @@ NumericSensorDataTemplateData, ) from .entity import ZWaveBaseEntity -from .helpers import get_device_id +from .helpers import get_device_id, get_valueless_base_unique_id LOGGER = logging.getLogger(__name__) @@ -477,9 +477,8 @@ def __init__( ) # Entity class attributes self._attr_name = f"{name}: Node Status" - self._attr_unique_id = ( - f"{self.client.driver.controller.home_id}.{node.node_id}.node_status" - ) + self._base_unique_id = get_valueless_base_unique_id(client, node) + self._attr_unique_id = f"{self._base_unique_id}.node_status" # device is precreated in main handler self._attr_device_info = DeviceInfo( identifiers={get_device_id(self.client, self.node)}, @@ -517,7 +516,7 @@ async def async_added_to_hass(self) -> None: self.async_on_remove( async_dispatcher_connect( self.hass, - f"{DOMAIN}_{self.unique_id}_remove_entity", + f"{DOMAIN}_{self._base_unique_id}_remove_entity", self.async_remove, ) ) diff --git a/homeassistant/components/zwave_js/services.py b/homeassistant/components/zwave_js/services.py index ac3f233ba4959c..767516cc17c010 100644 --- a/homeassistant/components/zwave_js/services.py +++ b/homeassistant/components/zwave_js/services.py @@ -457,7 +457,7 @@ async def async_multicast_set_value(self, service: ServiceCall) -> None: options = service.data.get(const.ATTR_OPTIONS) if not broadcast and len(nodes) == 1: - const.LOGGER.warning( + const.LOGGER.info( "Passing the zwave_js.multicast_set_value service call to the " "zwave_js.set_value service since only one node was targeted" ) @@ -520,5 +520,10 @@ async def async_multicast_set_value(self, service: ServiceCall) -> None: async def async_ping(self, service: ServiceCall) -> None: """Ping node(s).""" # pylint: disable=no-self-use + const.LOGGER.warning( + "This service is deprecated in favor of the ping button entity. Service " + "calls will still work for now but the service will be removed in a " + "future release" + ) nodes: set[ZwaveNode] = service.data[const.ATTR_NODES] await asyncio.gather(*(node.async_ping() for node in nodes)) diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 4f21f616ae131a..d8fef11269ccc5 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -737,18 +737,6 @@ def null_name_check_fixture(client, null_name_check_state): return node -@pytest.fixture(name="multiple_devices") -def multiple_devices_fixture( - client, climate_radio_thermostat_ct100_plus_state, lock_schlage_be469_state -): - """Mock a client with multiple devices.""" - node = Node(client, copy.deepcopy(climate_radio_thermostat_ct100_plus_state)) - client.driver.controller.nodes[node.node_id] = node - node = Node(client, copy.deepcopy(lock_schlage_be469_state)) - client.driver.controller.nodes[node.node_id] = node - return client.driver.controller.nodes - - @pytest.fixture(name="gdc_zw062") def motorized_barrier_cover_fixture(client, gdc_zw062_state): """Mock a motorized barrier node.""" diff --git a/tests/components/zwave_js/test_button.py b/tests/components/zwave_js/test_button.py new file mode 100644 index 00000000000000..deb95e5eef4dce --- /dev/null +++ b/tests/components/zwave_js/test_button.py @@ -0,0 +1,33 @@ +"""Test the Z-Wave JS button entities.""" +from homeassistant.components.button.const import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS +from homeassistant.const import ATTR_ENTITY_ID + + +async def test_ping_entity( + hass, + client, + climate_radio_thermostat_ct100_plus_different_endpoints, + integration, +): + """Test ping entity.""" + client.async_send_command.return_value = {"responded": True} + + # Test successful ping call + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + { + ATTR_ENTITY_ID: "button.z_wave_thermostat_ping", + }, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args_list[0][0][0] + assert args["command"] == "node.ping" + assert ( + args["nodeId"] + == climate_radio_thermostat_ct100_plus_different_endpoints.node_id + ) + + client.async_send_command.reset_mock() diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 7e39b7845337e3..d08c680dfe2d10 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -788,10 +788,10 @@ async def test_remove_entry( assert "Failed to uninstall the Z-Wave JS add-on" in caplog.text -async def test_removed_device(hass, client, multiple_devices, integration): +async def test_removed_device( + hass, client, climate_radio_thermostat_ct100_plus, lock_schlage_be469, integration +): """Test that the device registry gets updated when a device gets removed.""" - nodes = multiple_devices - # Verify how many nodes are available assert len(client.driver.controller.nodes) == 2 @@ -803,10 +803,10 @@ async def test_removed_device(hass, client, multiple_devices, integration): # Check how many entities there are ent_reg = er.async_get(hass) entity_entries = er.async_entries_for_config_entry(ent_reg, integration.entry_id) - assert len(entity_entries) == 26 + assert len(entity_entries) == 28 # Remove a node and reload the entry - old_node = nodes.pop(13) + old_node = client.driver.controller.nodes.pop(13) await hass.config_entries.async_reload(integration.entry_id) await hass.async_block_till_done() @@ -815,7 +815,7 @@ async def test_removed_device(hass, client, multiple_devices, integration): device_entries = dr.async_entries_for_config_entry(dev_reg, integration.entry_id) assert len(device_entries) == 1 entity_entries = er.async_entries_for_config_entry(ent_reg, integration.entry_id) - assert len(entity_entries) == 16 + assert len(entity_entries) == 17 assert dev_reg.async_get_device({get_device_id(client, old_node)}) is None From 759b01bb40feef6315bdf60243b08635b7b54c65 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Mon, 14 Feb 2022 22:34:33 +0100 Subject: [PATCH 0638/1098] Improve code quality season (#66449) --- homeassistant/components/season/sensor.py | 73 ++++++++++------------- 1 file changed, 31 insertions(+), 42 deletions(-) diff --git a/homeassistant/components/season/sensor.py b/homeassistant/components/season/sensor.py index 197654f489f932..23b50c0939f71b 100644 --- a/homeassistant/components/season/sensor.py +++ b/homeassistant/components/season/sensor.py @@ -1,13 +1,16 @@ """Support for tracking which astronomical or meteorological season it is.""" from __future__ import annotations -from datetime import datetime +from datetime import date, datetime import logging import ephem import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity +from homeassistant.components.sensor import ( + PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA, + SensorEntity, +) from homeassistant.const import CONF_NAME, CONF_TYPE from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv @@ -49,7 +52,7 @@ } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( +PLATFORM_SCHEMA = PARENT_PLATFORM_SCHEMA.extend( { vol.Optional(CONF_TYPE, default=TYPE_ASTRONOMICAL): vol.In(VALID_TYPES), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, @@ -64,8 +67,8 @@ def setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Display the current season.""" - _type = config.get(CONF_TYPE) - name = config.get(CONF_NAME) + _type: str = config[CONF_TYPE] + name: str = config[CONF_NAME] if hass.config.latitude < 0: hemisphere = SOUTHERN @@ -75,33 +78,35 @@ def setup_platform( hemisphere = EQUATOR _LOGGER.debug(_type) - add_entities([Season(hass, hemisphere, _type, name)], True) + add_entities([Season(hemisphere, _type, name)], True) -def get_season(date, hemisphere, season_tracking_type): +def get_season( + current_date: date, hemisphere: str, season_tracking_type: str +) -> str | None: """Calculate the current season.""" if hemisphere == "equator": return None if season_tracking_type == TYPE_ASTRONOMICAL: - spring_start = ephem.next_equinox(str(date.year)).datetime() - summer_start = ephem.next_solstice(str(date.year)).datetime() + spring_start = ephem.next_equinox(str(current_date.year)).datetime() + summer_start = ephem.next_solstice(str(current_date.year)).datetime() autumn_start = ephem.next_equinox(spring_start).datetime() winter_start = ephem.next_solstice(summer_start).datetime() else: - spring_start = datetime(2017, 3, 1).replace(year=date.year) + spring_start = datetime(2017, 3, 1).replace(year=current_date.year) summer_start = spring_start.replace(month=6) autumn_start = spring_start.replace(month=9) winter_start = spring_start.replace(month=12) - if spring_start <= date < summer_start: + if spring_start <= current_date < summer_start: season = STATE_SPRING - elif summer_start <= date < autumn_start: + elif summer_start <= current_date < autumn_start: season = STATE_SUMMER - elif autumn_start <= date < winter_start: + elif autumn_start <= current_date < winter_start: season = STATE_AUTUMN - elif winter_start <= date or spring_start > date: + elif winter_start <= current_date or spring_start > current_date: season = STATE_WINTER # If user is located in the southern hemisphere swap the season @@ -113,36 +118,20 @@ def get_season(date, hemisphere, season_tracking_type): class Season(SensorEntity): """Representation of the current season.""" - def __init__(self, hass, hemisphere, season_tracking_type, name): + _attr_device_class = "season__season" + + def __init__(self, hemisphere: str, season_tracking_type: str, name: str) -> None: """Initialize the season.""" - self.hass = hass - self._name = name + self._attr_name = name self.hemisphere = hemisphere - self.datetime = None self.type = season_tracking_type - self.season = None - - @property - def name(self): - """Return the name.""" - return self._name - - @property - def native_value(self): - """Return the current season.""" - return self.season - @property - def device_class(self): - """Return the device class.""" - return "season__season" - - @property - def icon(self): - """Icon to use in the frontend, if any.""" - return SEASON_ICONS.get(self.season, "mdi:cloud") - - def update(self): + def update(self) -> None: """Update season.""" - self.datetime = utcnow().replace(tzinfo=None) - self.season = get_season(self.datetime, self.hemisphere, self.type) + self._attr_native_value = get_season( + utcnow().replace(tzinfo=None), self.hemisphere, self.type + ) + + self._attr_icon = "mdi:cloud" + if self._attr_native_value: + self._attr_icon = SEASON_ICONS[self._attr_native_value] From 74a304cac7ec75e432c253eadfbf2696c1b1f60c Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Mon, 14 Feb 2022 13:38:41 -0800 Subject: [PATCH 0639/1098] Overkiz/address cover feedback (#65043) --- .../overkiz/cover_entities/awning.py | 7 ++- .../overkiz/cover_entities/generic_cover.py | 2 +- .../overkiz/cover_entities/vertical_cover.py | 50 ++++++++----------- 3 files changed, 24 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/overkiz/cover_entities/awning.py b/homeassistant/components/overkiz/cover_entities/awning.py index bbce2c985edd12..ebbff8710f3fc7 100644 --- a/homeassistant/components/overkiz/cover_entities/awning.py +++ b/homeassistant/components/overkiz/cover_entities/awning.py @@ -7,11 +7,11 @@ from homeassistant.components.cover import ( ATTR_POSITION, - DEVICE_CLASS_AWNING, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION, SUPPORT_STOP, + CoverDeviceClass, ) from .generic_cover import COMMANDS_STOP, OverkizGenericCover @@ -20,7 +20,7 @@ class Awning(OverkizGenericCover): """Representation of an Overkiz awning.""" - _attr_device_class = DEVICE_CLASS_AWNING + _attr_device_class = CoverDeviceClass.AWNING @property def supported_features(self) -> int: @@ -56,9 +56,8 @@ def current_cover_position(self) -> int | None: async def async_set_cover_position(self, **kwargs: Any) -> None: """Move the cover to a specific position.""" - position = kwargs.get(ATTR_POSITION, 0) await self.executor.async_execute_command( - OverkizCommand.SET_DEPLOYMENT, position + OverkizCommand.SET_DEPLOYMENT, kwargs[ATTR_POSITION] ) async def async_open_cover(self, **kwargs: Any) -> None: diff --git a/homeassistant/components/overkiz/cover_entities/generic_cover.py b/homeassistant/components/overkiz/cover_entities/generic_cover.py index 60484620df1727..c25cd1ab8067b8 100644 --- a/homeassistant/components/overkiz/cover_entities/generic_cover.py +++ b/homeassistant/components/overkiz/cover_entities/generic_cover.py @@ -64,7 +64,7 @@ async def async_set_cover_tilt_position(self, **kwargs: Any) -> None: if command := self.executor.select_command(*COMMANDS_SET_TILT_POSITION): await self.executor.async_execute_command( command, - 100 - kwargs.get(ATTR_TILT_POSITION, 0), + 100 - kwargs[ATTR_TILT_POSITION], ) @property diff --git a/homeassistant/components/overkiz/cover_entities/vertical_cover.py b/homeassistant/components/overkiz/cover_entities/vertical_cover.py index 6e69f24f2f1667..12354f412417b4 100644 --- a/homeassistant/components/overkiz/cover_entities/vertical_cover.py +++ b/homeassistant/components/overkiz/cover_entities/vertical_cover.py @@ -1,23 +1,17 @@ """Support for Overkiz Vertical Covers.""" from __future__ import annotations -from typing import Any, Union, cast +from typing import Any, cast from pyoverkiz.enums import OverkizCommand, OverkizState, UIClass, UIWidget from homeassistant.components.cover import ( ATTR_POSITION, - DEVICE_CLASS_AWNING, - DEVICE_CLASS_BLIND, - DEVICE_CLASS_CURTAIN, - DEVICE_CLASS_GARAGE, - DEVICE_CLASS_GATE, - DEVICE_CLASS_SHUTTER, - DEVICE_CLASS_WINDOW, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION, SUPPORT_STOP, + CoverDeviceClass, ) from .generic_cover import COMMANDS_STOP, OverkizGenericCover @@ -26,16 +20,16 @@ COMMANDS_CLOSE = [OverkizCommand.CLOSE, OverkizCommand.DOWN, OverkizCommand.CYCLE] OVERKIZ_DEVICE_TO_DEVICE_CLASS = { - UIClass.CURTAIN: DEVICE_CLASS_CURTAIN, - UIClass.EXTERIOR_SCREEN: DEVICE_CLASS_BLIND, - UIClass.EXTERIOR_VENETIAN_BLIND: DEVICE_CLASS_BLIND, - UIClass.GARAGE_DOOR: DEVICE_CLASS_GARAGE, - UIClass.GATE: DEVICE_CLASS_GATE, - UIWidget.MY_FOX_SECURITY_CAMERA: DEVICE_CLASS_SHUTTER, - UIClass.PERGOLA: DEVICE_CLASS_AWNING, - UIClass.ROLLER_SHUTTER: DEVICE_CLASS_SHUTTER, - UIClass.SWINGING_SHUTTER: DEVICE_CLASS_SHUTTER, - UIClass.WINDOW: DEVICE_CLASS_WINDOW, + UIClass.CURTAIN: CoverDeviceClass.CURTAIN, + UIClass.EXTERIOR_SCREEN: CoverDeviceClass.BLIND, + UIClass.EXTERIOR_VENETIAN_BLIND: CoverDeviceClass.BLIND, + UIClass.GARAGE_DOOR: CoverDeviceClass.GARAGE, + UIClass.GATE: CoverDeviceClass.GATE, + UIWidget.MY_FOX_SECURITY_CAMERA: CoverDeviceClass.SHUTTER, + UIClass.PERGOLA: CoverDeviceClass.AWNING, + UIClass.ROLLER_SHUTTER: CoverDeviceClass.SHUTTER, + UIClass.SWINGING_SHUTTER: CoverDeviceClass.SHUTTER, + UIClass.WINDOW: CoverDeviceClass.WINDOW, } @@ -69,7 +63,7 @@ def device_class(self) -> str: ( OVERKIZ_DEVICE_TO_DEVICE_CLASS.get(self.device.widget) or OVERKIZ_DEVICE_TO_DEVICE_CLASS.get(self.device.ui_class) - or DEVICE_CLASS_BLIND + or CoverDeviceClass.BLIND ), ) @@ -80,24 +74,20 @@ def current_cover_position(self) -> int | None: None is unknown, 0 is closed, 100 is fully open. """ - position = cast( - Union[int, None], - self.executor.select_state( - OverkizState.CORE_CLOSURE, - OverkizState.CORE_CLOSURE_OR_ROCKER_POSITION, - OverkizState.CORE_PEDESTRIAN_POSITION, - ), + position = self.executor.select_state( + OverkizState.CORE_CLOSURE, + OverkizState.CORE_CLOSURE_OR_ROCKER_POSITION, + OverkizState.CORE_PEDESTRIAN_POSITION, ) - # Uno devices can have a position not in 0 to 100 range when unknown - if position is None or position < 0 or position > 100: + if position is None: return None - return 100 - position + return 100 - cast(int, position) async def async_set_cover_position(self, **kwargs: Any) -> None: """Move the cover to a specific position.""" - position = 100 - kwargs.get(ATTR_POSITION, 0) + position = 100 - kwargs[ATTR_POSITION] await self.executor.async_execute_command(OverkizCommand.SET_CLOSURE, position) async def async_open_cover(self, **kwargs: Any) -> None: From f81b6d61b9f470e3b014b82adb4457d1b0a9b429 Mon Sep 17 00:00:00 2001 From: Jarod Wilson Date: Mon, 14 Feb 2022 17:01:15 -0500 Subject: [PATCH 0640/1098] Create unique_id for sleepiq sensors (#65227) --- homeassistant/components/sleepiq/__init__.py | 5 +++++ homeassistant/components/sleepiq/binary_sensor.py | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sleepiq/__init__.py b/homeassistant/components/sleepiq/__init__.py index 60614fcf97f012..f6ba5a0393f2f9 100644 --- a/homeassistant/components/sleepiq/__init__.py +++ b/homeassistant/components/sleepiq/__init__.py @@ -99,6 +99,11 @@ def name(self): self.bed.name, self.side.sleeper.first_name, self._name ) + @property + def unique_id(self): + """Return a unique ID for the bed.""" + return f"{self._bed_id}-{self._side}-{self.type}" + def update(self): """Get the latest data from SleepIQ and updates the states.""" # Call the API for new sleepiq data. Each sensor will re-trigger this diff --git a/homeassistant/components/sleepiq/binary_sensor.py b/homeassistant/components/sleepiq/binary_sensor.py index f901851c0b5c21..f821a569254bf3 100644 --- a/homeassistant/components/sleepiq/binary_sensor.py +++ b/homeassistant/components/sleepiq/binary_sensor.py @@ -41,7 +41,8 @@ def __init__(self, sleepiq_data, bed_id, side): """Initialize the sensor.""" super().__init__(sleepiq_data, bed_id, side) self._state = None - self._name = SENSOR_TYPES[IS_IN_BED] + self.type = IS_IN_BED + self._name = SENSOR_TYPES[self.type] self.update() @property From ffe821a1f7602a5c3e5bc4ca395358d4838873ff Mon Sep 17 00:00:00 2001 From: EtienneMD Date: Mon, 14 Feb 2022 17:06:48 -0500 Subject: [PATCH 0641/1098] Fix HVAC modes for zha Stelpro fan heater (#66293) --- homeassistant/components/zha/climate.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/homeassistant/components/zha/climate.py b/homeassistant/components/zha/climate.py index b892fc9a67f21b..d7e36c52517a18 100644 --- a/homeassistant/components/zha/climate.py +++ b/homeassistant/components/zha/climate.py @@ -756,3 +756,18 @@ async def async_preset_handler(self, preset: str, enable: bool = False) -> bool: ) return False + + +@MULTI_MATCH( + channel_names=CHANNEL_THERMOSTAT, + manufacturers="Stelpro", + models={"SORB"}, + stop_on_match_group=CHANNEL_THERMOSTAT, +) +class StelproFanHeater(Thermostat): + """Stelpro Fan Heater implementation.""" + + @property + def hvac_modes(self) -> tuple[str, ...]: + """Return only the heat mode, because the device can't be turned off.""" + return (HVAC_MODE_HEAT,) From b51866b1c4896e4a8acd8f3c29e851c218c39d2f Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Tue, 15 Feb 2022 01:12:34 +0100 Subject: [PATCH 0642/1098] Revert "Fix raspihats callbacks (#64122)" (#66517) Co-authored-by: epenet --- .../components/raspihats/binary_sensor.py | 20 +++--------- homeassistant/components/raspihats/switch.py | 32 +++++++------------ 2 files changed, 15 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/raspihats/binary_sensor.py b/homeassistant/components/raspihats/binary_sensor.py index 2c0ce10a5f3511..f8fbc0d010f5b0 100644 --- a/homeassistant/components/raspihats/binary_sensor.py +++ b/homeassistant/components/raspihats/binary_sensor.py @@ -2,7 +2,6 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING import voluptuous as vol @@ -109,20 +108,12 @@ def __init__(self, address, channel, name, invert_logic, device_class): self._device_class = device_class self._state = self.I2C_HATS_MANAGER.read_di(self._address, self._channel) - async def async_added_to_hass(self) -> None: - """Register callbacks.""" - if TYPE_CHECKING: - assert self.I2C_HATS_MANAGER - def online_callback(): """Call fired when board is online.""" self.schedule_update_ha_state() - await self.hass.async_add_executor_job( - self.I2C_HATS_MANAGER.register_online_callback, - self._address, - self._channel, - online_callback, + self.I2C_HATS_MANAGER.register_online_callback( + self._address, self._channel, online_callback ) def edge_callback(state): @@ -130,11 +121,8 @@ def edge_callback(state): self._state = state self.schedule_update_ha_state() - await self.hass.async_add_executor_job( - self.I2C_HATS_MANAGER.register_di_callback, - self._address, - self._channel, - edge_callback, + self.I2C_HATS_MANAGER.register_di_callback( + self._address, self._channel, edge_callback ) @property diff --git a/homeassistant/components/raspihats/switch.py b/homeassistant/components/raspihats/switch.py index 0e05e376ed4d17..8ca88528543f16 100644 --- a/homeassistant/components/raspihats/switch.py +++ b/homeassistant/components/raspihats/switch.py @@ -2,7 +2,6 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING import voluptuous as vol @@ -101,7 +100,6 @@ def __init__(self, board, address, channel, name, invert_logic, initial_state): self._channel = channel self._name = name or DEVICE_DEFAULT_NAME self._invert_logic = invert_logic - self._state = initial_state if initial_state is not None: if self._invert_logic: state = not initial_state @@ -109,27 +107,14 @@ def __init__(self, board, address, channel, name, invert_logic, initial_state): state = initial_state self.I2C_HATS_MANAGER.write_dq(self._address, self._channel, state) - async def async_added_to_hass(self) -> None: - """Register callbacks.""" - if TYPE_CHECKING: - assert self.I2C_HATS_MANAGER + def online_callback(): + """Call fired when board is online.""" + self.schedule_update_ha_state() - await self.hass.async_add_executor_job( - self.I2C_HATS_MANAGER.register_online_callback, - self._address, - self._channel, - self.online_callback, + self.I2C_HATS_MANAGER.register_online_callback( + self._address, self._channel, online_callback ) - def online_callback(self): - """Call fired when board is online.""" - try: - self._state = self.I2C_HATS_MANAGER.read_dq(self._address, self._channel) - except I2CHatsException as ex: - _LOGGER.error(self._log_message(f"Is ON check failed, {ex!s}")) - self._state = False - self.schedule_update_ha_state() - def _log_message(self, message): """Create log message.""" string = f"{self._name} " @@ -150,7 +135,12 @@ def should_poll(self): @property def is_on(self): """Return true if device is on.""" - return self._state != self._invert_logic + try: + state = self.I2C_HATS_MANAGER.read_dq(self._address, self._channel) + return state != self._invert_logic + except I2CHatsException as ex: + _LOGGER.error(self._log_message(f"Is ON check failed, {ex!s}")) + return False def turn_on(self, **kwargs): """Turn the device on.""" From d69d0e88172dd7d960269d1f299082a169cec9a5 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 15 Feb 2022 00:14:48 +0000 Subject: [PATCH 0643/1098] [ci skip] Translation update --- .../components/broadlink/translations/pt-BR.json | 2 +- .../components/cpuspeed/translations/bg.json | 1 + homeassistant/components/elkm1/translations/ja.json | 2 ++ homeassistant/components/fivem/translations/ja.json | 1 + homeassistant/components/github/translations/bg.json | 5 +++++ homeassistant/components/homekit/translations/bg.json | 3 ++- homeassistant/components/homekit/translations/ja.json | 2 ++ .../components/homekit/translations/zh-Hans.json | 8 ++++---- .../homekit_controller/translations/zh-Hans.json | 2 +- .../components/homewizard/translations/bg.json | 1 + homeassistant/components/iss/translations/de.json | 11 ++++++++++- homeassistant/components/iss/translations/en.json | 3 +++ homeassistant/components/iss/translations/pt-BR.json | 11 ++++++++++- .../components/netgear/translations/zh-Hans.json | 4 ++-- .../components/overkiz/translations/sensor.bg.json | 3 ++- homeassistant/components/picnic/translations/no.json | 4 +++- homeassistant/components/picnic/translations/pl.json | 4 +++- homeassistant/components/senseme/translations/bg.json | 3 +++ .../components/tuya/translations/select.bg.json | 3 ++- .../components/unifiprotect/translations/ja.json | 1 + homeassistant/components/webostv/translations/bg.json | 1 + .../components/zwave_me/translations/ja.json | 3 ++- 22 files changed, 62 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/broadlink/translations/pt-BR.json b/homeassistant/components/broadlink/translations/pt-BR.json index e5a372176aa12c..82257805283411 100644 --- a/homeassistant/components/broadlink/translations/pt-BR.json +++ b/homeassistant/components/broadlink/translations/pt-BR.json @@ -13,7 +13,7 @@ "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido", "unknown": "Erro inesperado" }, - "flow_title": "{name} ({model} at {host})", + "flow_title": "{name} ( {model} em {host} )", "step": { "auth": { "title": "Autenticar no dispositivo" diff --git a/homeassistant/components/cpuspeed/translations/bg.json b/homeassistant/components/cpuspeed/translations/bg.json index fe7f44123e9f71..df41c1d7b0ac71 100644 --- a/homeassistant/components/cpuspeed/translations/bg.json +++ b/homeassistant/components/cpuspeed/translations/bg.json @@ -6,6 +6,7 @@ }, "step": { "user": { + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u0437\u0430\u043f\u043e\u0447\u043d\u0435\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u0432\u0430\u043d\u0435\u0442\u043e?", "title": "\u0421\u043a\u043e\u0440\u043e\u0441\u0442 \u043d\u0430 CPU" } } diff --git a/homeassistant/components/elkm1/translations/ja.json b/homeassistant/components/elkm1/translations/ja.json index c5f8f422b27967..f280d6d6276cf6 100644 --- a/homeassistant/components/elkm1/translations/ja.json +++ b/homeassistant/components/elkm1/translations/ja.json @@ -27,10 +27,12 @@ "data": { "address": "IP\u30a2\u30c9\u30ec\u30b9\u307e\u305f\u306f\u30c9\u30e1\u30a4\u30f3\u3001\u3082\u3057\u304f\u306f\u30b7\u30ea\u30a2\u30eb\u3067\u63a5\u7d9a\u3059\u308b\u5834\u5408\u306b\u306f\u30b7\u30ea\u30a2\u30eb\u30dd\u30fc\u30c8\u3002", "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", + "prefix": "\u30e6\u30cb\u30fc\u30af(\u4e00\u610f)\u306a\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9(\u63a5\u982d\u8f9e)(ElkM1\u304c1\u3064\u306e\u5834\u5408\u306f\u7a7a\u767d\u306e\u307e\u307e)", "protocol": "\u30d7\u30ed\u30c8\u30b3\u30eb", "temperature_unit": "ElkM1\u304c\u4f7f\u7528\u3059\u308b\u6e29\u5ea6\u5358\u4f4d\u3002", "username": "\u30e6\u30fc\u30b6\u30fc\u540d" }, + "description": "\u30a2\u30c9\u30ec\u30b9\u6587\u5b57\u5217\u306f\u3001 '\u30bb\u30ad\u30e5\u30a2 '\u304a\u3088\u3073 '\u975e\u30bb\u30ad\u30e5\u30a2 '\u306e\u5834\u5408\u306f\u3001'address[:port]'\u306e\u5f62\u5f0f\u3067\u3042\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002\u4f8b: '192.168.1.1'\u3002\u30dd\u30fc\u30c8\u306f\u30aa\u30d7\u30b7\u30e7\u30f3\u3067\u3001\u30c7\u30d5\u30a9\u30eb\u30c8\u306f'\u975e\u30bb\u30ad\u30e5\u30a2'\u306e\u5834\u5408\u306f\u30012101 \u3067'\u30bb\u30ad\u30e5\u30a2'\u306e\u5834\u5408\u306f\u30012601 \u3067\u3059\u3002\u30b7\u30ea\u30a2\u30eb\u30d7\u30ed\u30c8\u30b3\u30eb\u306e\u5834\u5408\u3001\u30a2\u30c9\u30ec\u30b9\u306f\u3001'tty[:baud]' \u306e\u5f62\u5f0f\u3067\u3042\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002\u4f8b: '/dev/ttyS1'\u3002\u30dc\u30fc(baud)\u306f\u30aa\u30d7\u30b7\u30e7\u30f3\u3067\u3001\u30c7\u30d5\u30a9\u30eb\u30c8\u306f115200\u3067\u3059\u3002", "title": "Elk-M1 Control\u306b\u63a5\u7d9a" }, "user": { diff --git a/homeassistant/components/fivem/translations/ja.json b/homeassistant/components/fivem/translations/ja.json index 939d07c27aae6e..eb398cccff4626 100644 --- a/homeassistant/components/fivem/translations/ja.json +++ b/homeassistant/components/fivem/translations/ja.json @@ -4,6 +4,7 @@ "already_configured": "FiveM\u30b5\u30fc\u30d0\u30fc\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" }, "error": { + "cannot_connect": "\u63a5\u7d9a\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u30db\u30b9\u30c8\u3068\u30dd\u30fc\u30c8\u3092\u78ba\u8a8d\u3057\u3066\u3001\u3082\u3046\u4e00\u5ea6\u3084\u308a\u76f4\u3057\u3066\u304f\u3060\u3055\u3044\u3002\u307e\u305f\u3001\u6700\u65b0\u306eFiveM\u30b5\u30fc\u30d0\u30fc\u3092\u5b9f\u884c\u3057\u3066\u3044\u308b\u3053\u3068\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002", "invalid_game_name": "\u63a5\u7d9a\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u308b\u30b2\u30fc\u30e0\u306eAPI\u306f\u3001FiveM\u306e\u30b2\u30fc\u30e0\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002", "invalid_gamename": "\u63a5\u7d9a\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u308b\u30b2\u30fc\u30e0\u306eAPI\u306f\u3001FiveM\u306e\u30b2\u30fc\u30e0\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002", "unknown_error": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" diff --git a/homeassistant/components/github/translations/bg.json b/homeassistant/components/github/translations/bg.json index 80a7cc489a9b0c..1a52d69dc2dca1 100644 --- a/homeassistant/components/github/translations/bg.json +++ b/homeassistant/components/github/translations/bg.json @@ -2,6 +2,11 @@ "config": { "abort": { "already_configured": "\u0423\u0441\u043b\u0443\u0433\u0430\u0442\u0430 \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u0430" + }, + "step": { + "repositories": { + "title": "\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430" + } } } } \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/bg.json b/homeassistant/components/homekit/translations/bg.json index 9eb419288a87a6..a7aa5bb792b9c6 100644 --- a/homeassistant/components/homekit/translations/bg.json +++ b/homeassistant/components/homekit/translations/bg.json @@ -9,7 +9,8 @@ "exclude": { "data": { "entities": "\u041e\u0431\u0435\u043a\u0442\u0438" - } + }, + "title": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u043e\u0431\u0435\u043a\u0442\u0438\u0442\u0435, \u043a\u043e\u0438\u0442\u043e \u0434\u0430 \u0431\u044a\u0434\u0430\u0442 \u0438\u0437\u043a\u043b\u044e\u0447\u0435\u043d\u0438" }, "include": { "data": { diff --git a/homeassistant/components/homekit/translations/ja.json b/homeassistant/components/homekit/translations/ja.json index 05f6c83ad73a31..6d6b441484fc89 100644 --- a/homeassistant/components/homekit/translations/ja.json +++ b/homeassistant/components/homekit/translations/ja.json @@ -45,12 +45,14 @@ "data": { "entities": "\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3" }, + "description": "\u9664\u5916\u3055\u308c\u3066\u3044\u308b\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u3068\u5206\u985e\u3055\u308c\u3066\u3044\u308b\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u3092\u9664\u304d\u3001\u3059\u3079\u3066\u306e \u201c{domains}\u201d \u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u304c\u542b\u307e\u308c\u307e\u3059\u3002", "title": "\u9664\u5916\u3059\u308b\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u3092\u9078\u629e" }, "include": { "data": { "entities": "\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3" }, + "description": "\u7279\u5b9a\u306e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u304c\u9078\u629e\u3055\u308c\u3066\u3044\u306a\u3044\u9650\u308a\u3001\u3059\u3079\u3066\u306e \u201c{domains}\u201d \u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u304c\u542b\u307e\u308c\u307e\u3059\u3002", "title": "\u542b\u3081\u308b\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u3092\u9078\u629e" }, "include_exclude": { diff --git a/homeassistant/components/homekit/translations/zh-Hans.json b/homeassistant/components/homekit/translations/zh-Hans.json index cf009baed45cbb..3f6d005f0459b4 100644 --- a/homeassistant/components/homekit/translations/zh-Hans.json +++ b/homeassistant/components/homekit/translations/zh-Hans.json @@ -45,14 +45,14 @@ "data": { "entities": "\u5b9e\u4f53" }, - "description": "\u9664\u6392\u9664\u6307\u5b9a\u7684\u5b9e\u4f53\u548c\u5206\u7c7b\uff0c\u6240\u6709\u5728 \u201c{domains}\u201d \u4e0b\u7684\u5b9e\u4f53\u548c\u5206\u7c7b\u5c06\u88ab\u7eb3\u5165", + "description": "\u9664\u4e86\u6307\u5b9a\u7684\u5b9e\u4f53\u548c\u5206\u7c7b\uff0c\u6240\u6709\u201c{domains}\u201d\u7c7b\u578b\u7684\u5b9e\u4f53\u90fd\u5c06\u88ab\u5305\u542b\u3002", "title": "\u9009\u62e9\u8981\u6392\u9664\u7684\u5b9e\u4f53" }, "include": { "data": { "entities": "\u5b9e\u4f53" }, - "description": "\u9664\u975e\u5df2\u9009\u62e9\u4e86\u6307\u5b9a\u7684\u5b9e\u4f53\uff0c\u5426\u5219\u6240\u6709\u5728 \u201c{domains}\u201d \u4e0b\u7684\u5b9e\u4f53\u5c06\u88ab\u7eb3\u5165", + "description": "\u9664\u975e\u5df2\u9009\u62e9\u4e86\u6307\u5b9a\u7684\u5b9e\u4f53\uff0c\u5426\u5219\u6240\u6709\u201c{domains}\u201d\u7c7b\u578b\u7684\u5b9e\u4f53\u90fd\u5c06\u88ab\u5305\u542b\u3002", "title": "\u9009\u62e9\u8981\u5305\u542b\u7684\u5b9e\u4f53" }, "include_exclude": { @@ -60,12 +60,12 @@ "entities": "\u5b9e\u4f53", "mode": "\u6a21\u5f0f" }, - "description": "\u9009\u62e9\u8981\u5f00\u653e\u7684\u5b9e\u4f53\u3002\n\u5728\u9644\u4ef6\u6a21\u5f0f\u4e2d\uff0c\u53ea\u80fd\u5f00\u653e\u4e00\u4e2a\u5b9e\u4f53\u3002\u5728\u6865\u63a5\u5305\u542b\u6a21\u5f0f\u4e2d\uff0c\u5982\u679c\u4e0d\u9009\u62e9\u5305\u542b\u7684\u5b9e\u4f53\uff0c\u57df\u4e2d\u6240\u6709\u5b9e\u4f53\u90fd\u4f1a\u5f00\u653e\u3002\u5728\u6865\u63a5\u6392\u9664\u6a21\u5f0f\u4e2d\uff0c\u5982\u679c\u4e0d\u9009\u62e9\u6392\u9664\u7684\u5b9e\u4f53\uff0c\u57df\u4e2d\u6240\u6709\u5b9e\u4f53\u4e5f\u90fd\u4f1a\u5f00\u653e\u3002\n\u4e3a\u83b7\u5f97\u6700\u4f73\u4f53\u9a8c\uff0c\u5c06\u4f1a\u4e3a\u6bcf\u4e2a\u7535\u89c6\u5a92\u4f53\u64ad\u653e\u5668\u3001\u57fa\u4e8e\u6d3b\u52a8\u7684\u9065\u63a7\u5668\u3001\u9501\u548c\u6444\u50cf\u5934\u521b\u5efa\u4e00\u4e2a\u5355\u72ec\u7684 HomeKit \u914d\u4ef6\u3002", + "description": "\u9009\u62e9\u8981\u5f00\u653e\u7684\u5b9e\u4f53\u3002\n\u5728\u914d\u4ef6\u6a21\u5f0f\u4e2d\uff0c\u53ea\u80fd\u5f00\u653e\u4e00\u4e2a\u5b9e\u4f53\u3002\u5728\u6865\u63a5\u5305\u542b\u6a21\u5f0f\u4e2d\uff0c\u5982\u679c\u4e0d\u9009\u62e9\u5305\u542b\u7684\u5b9e\u4f53\uff0c\u57df\u4e2d\u6240\u6709\u5b9e\u4f53\u90fd\u4f1a\u5f00\u653e\u3002\u5728\u6865\u63a5\u6392\u9664\u6a21\u5f0f\u4e2d\uff0c\u5982\u679c\u4e0d\u9009\u62e9\u6392\u9664\u7684\u5b9e\u4f53\uff0c\u57df\u4e2d\u6240\u6709\u5b9e\u4f53\u4e5f\u90fd\u4f1a\u5f00\u653e\u3002\n\u4e3a\u83b7\u5f97\u6700\u4f73\u4f53\u9a8c\uff0c\u5c06\u4f1a\u4e3a\u6bcf\u4e2a\u7535\u89c6\u5a92\u4f53\u64ad\u653e\u5668\u3001\u57fa\u4e8e\u6d3b\u52a8\u7684\u9065\u63a7\u5668\u3001\u9501\u548c\u6444\u50cf\u5934\u521b\u5efa\u5355\u72ec\u7684 HomeKit \u914d\u4ef6\u3002", "title": "\u9009\u62e9\u8981\u5305\u542b\u7684\u5b9e\u4f53" }, "init": { "data": { - "domains": "\u8981\u5305\u542b\u7684\u57df\u540d", + "domains": "\u8981\u5305\u542b\u7684\u57df", "include_domains": "\u8981\u5305\u542b\u7684\u57df", "include_exclude_mode": "\u5305\u542b\u6a21\u5f0f", "mode": "HomeKit \u6a21\u5f0f" diff --git a/homeassistant/components/homekit_controller/translations/zh-Hans.json b/homeassistant/components/homekit_controller/translations/zh-Hans.json index 7bf46c79e924a4..f0d8fdec84c634 100644 --- a/homeassistant/components/homekit_controller/translations/zh-Hans.json +++ b/homeassistant/components/homekit_controller/translations/zh-Hans.json @@ -5,7 +5,7 @@ "already_configured": "\u914d\u4ef6\u5df2\u901a\u8fc7\u6b64\u63a7\u5236\u5668\u914d\u7f6e\u5b8c\u6210\u3002", "already_in_progress": "\u6b64\u8bbe\u5907\u7684\u914d\u7f6e\u6d41\u7a0b\u5df2\u5728\u8fdb\u884c\u4e2d\u3002", "already_paired": "\u6b64\u914d\u4ef6\u5df2\u4e0e\u53e6\u4e00\u53f0\u8bbe\u5907\u914d\u5bf9\u3002\u8bf7\u91cd\u7f6e\u914d\u4ef6\uff0c\u7136\u540e\u91cd\u8bd5\u3002", - "ignored_model": "HomeKit \u5bf9\u6b64\u8bbe\u5907\u7684\u652f\u6301\u5df2\u88ab\u963b\u6b62\uff0c\u56e0\u4e3a\u6709\u529f\u80fd\u66f4\u5b8c\u6574\u7684\u539f\u751f\u96c6\u6210\u53ef\u4ee5\u66ff\u4ee3\u4f7f\u7528\u3002", + "ignored_model": "HomeKit \u5bf9\u6b64\u8bbe\u5907\u7684\u652f\u6301\u5df2\u88ab\u963b\u6b62\uff0c\u56e0\u4e3a\u6709\u529f\u80fd\u66f4\u5b8c\u6574\u7684\u539f\u751f\u96c6\u6210\u53ef\u4ee5\u4f7f\u7528\u3002", "invalid_config_entry": "\u6b64\u8bbe\u5907\u5df2\u51c6\u5907\u597d\u914d\u5bf9\uff0c\u4f46\u662f Home Assistant \u4e2d\u5b58\u5728\u4e0e\u4e4b\u51b2\u7a81\u7684\u914d\u7f6e\uff0c\u5fc5\u987b\u5148\u5c06\u5176\u5220\u9664\u3002", "invalid_properties": "\u8bbe\u5907\u901a\u544a\u7684\u5c5e\u6027\u65e0\u6548\u3002", "no_devices": "\u6ca1\u6709\u627e\u5230\u672a\u914d\u5bf9\u7684\u8bbe\u5907" diff --git a/homeassistant/components/homewizard/translations/bg.json b/homeassistant/components/homewizard/translations/bg.json index 9ecd510ae99a19..dedf6ca570b515 100644 --- a/homeassistant/components/homewizard/translations/bg.json +++ b/homeassistant/components/homewizard/translations/bg.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e", "device_not_supported": "\u0422\u043e\u0432\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 \u0441\u0435 \u043f\u043e\u0434\u0434\u044a\u0440\u0436\u0430", + "invalid_discovery_parameters": "\u041e\u0442\u043a\u0440\u0438\u0442\u0430 \u0435 \u043d\u0435\u043f\u043e\u0434\u0434\u044a\u0440\u0436\u0430\u043d\u0430 \u0432\u0435\u0440\u0441\u0438\u044f \u043d\u0430 API", "unknown_error": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" }, "step": { diff --git a/homeassistant/components/iss/translations/de.json b/homeassistant/components/iss/translations/de.json index 7e1a9be8e795a0..04ae0f6e9d536a 100644 --- a/homeassistant/components/iss/translations/de.json +++ b/homeassistant/components/iss/translations/de.json @@ -9,7 +9,16 @@ "data": { "show_on_map": "Auf der Karte anzeigen?" }, - "description": "Willst du die Internationale Raumstation konfigurieren?" + "description": "M\u00f6chtest du die Internationale Raumstation (ISS) konfigurieren?" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "Auf Karte anzeigen" + } } } } diff --git a/homeassistant/components/iss/translations/en.json b/homeassistant/components/iss/translations/en.json index b90ff56964a571..56f9bd79e880e2 100644 --- a/homeassistant/components/iss/translations/en.json +++ b/homeassistant/components/iss/translations/en.json @@ -6,6 +6,9 @@ }, "step": { "user": { + "data": { + "show_on_map": "Show on map?" + }, "description": "Do you want to configure International Space Station (ISS)?" } } diff --git a/homeassistant/components/iss/translations/pt-BR.json b/homeassistant/components/iss/translations/pt-BR.json index b4257ea668c53f..c1a78517fa8941 100644 --- a/homeassistant/components/iss/translations/pt-BR.json +++ b/homeassistant/components/iss/translations/pt-BR.json @@ -9,7 +9,16 @@ "data": { "show_on_map": "Mostrar no mapa?" }, - "description": "Deseja configurar a Esta\u00e7\u00e3o Espacial Internacional?" + "description": "Deseja configurar a Esta\u00e7\u00e3o Espacial Internacional (ISS)?" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "Mostrar no mapa" + } } } } diff --git a/homeassistant/components/netgear/translations/zh-Hans.json b/homeassistant/components/netgear/translations/zh-Hans.json index c7296d1b565919..dd7b165d2d4cd1 100644 --- a/homeassistant/components/netgear/translations/zh-Hans.json +++ b/homeassistant/components/netgear/translations/zh-Hans.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u8bbe\u5907\u5df2\u88ab\u914d\u7f6e" + "already_configured": "\u8bbe\u5907\u5df2\u7ecf\u914d\u7f6e\u8fc7\u4e86" }, "error": { "config": "\u8fde\u63a5\u9519\u8bef\uff1a\u8bf7\u68c0\u67e5\u60a8\u7684\u914d\u7f6e\u662f\u5426\u6b63\u786e" @@ -15,7 +15,7 @@ "ssl": "\u4f7f\u7528 SSL \u51ed\u8bc1\u767b\u5f55", "username": "\u7528\u6237\u540d (\u53ef\u9009)" }, - "description": "\u9ed8\u8ba4\u914d\u7f6e\uff1a\n\u9ed8\u8ba4\u4e3b\u673a\u5730\u5740: {host}\n\u9ed8\u8ba4\u7528\u6237\u540d: {username}", + "description": "\u9ed8\u8ba4\u4e3b\u673a\u5730\u5740: {host}\n\u9ed8\u8ba4\u7528\u6237\u540d: {username}", "title": "\u7f51\u4ef6\u8def\u7531\u5668" } } diff --git a/homeassistant/components/overkiz/translations/sensor.bg.json b/homeassistant/components/overkiz/translations/sensor.bg.json index 0c74eb8b640099..b62b4383fc6700 100644 --- a/homeassistant/components/overkiz/translations/sensor.bg.json +++ b/homeassistant/components/overkiz/translations/sensor.bg.json @@ -10,7 +10,8 @@ "user": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b" }, "overkiz__sensor_defect": { - "low_battery": "\u0418\u0437\u0442\u043e\u0449\u0435\u043d\u0430 \u0431\u0430\u0442\u0435\u0440\u0438\u044f" + "low_battery": "\u0418\u0437\u0442\u043e\u0449\u0435\u043d\u0430 \u0431\u0430\u0442\u0435\u0440\u0438\u044f", + "maintenance_required": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0435 \u043f\u043e\u0434\u0434\u0440\u044a\u0436\u043a\u0430" } } } \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/no.json b/homeassistant/components/picnic/translations/no.json index 45e3bcbb5487bd..ffd38bce705156 100644 --- a/homeassistant/components/picnic/translations/no.json +++ b/homeassistant/components/picnic/translations/no.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Enheten er allerede konfigurert" + "already_configured": "Enheten er allerede konfigurert", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" }, "error": { "cannot_connect": "Tilkobling mislyktes", + "different_account": "Kontoen skal v\u00e6re den samme som brukes til \u00e5 sette opp integrasjonen", "invalid_auth": "Ugyldig godkjenning", "unknown": "Uventet feil" }, diff --git a/homeassistant/components/picnic/translations/pl.json b/homeassistant/components/picnic/translations/pl.json index c278f29d13cf58..abf8a4f9469c05 100644 --- a/homeassistant/components/picnic/translations/pl.json +++ b/homeassistant/components/picnic/translations/pl.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "different_account": "Konto powinno by\u0107 takie samo jak przy konfigurowaniu integracji.", "invalid_auth": "Niepoprawne uwierzytelnienie", "unknown": "Nieoczekiwany b\u0142\u0105d" }, diff --git a/homeassistant/components/senseme/translations/bg.json b/homeassistant/components/senseme/translations/bg.json index e7125511ec3912..4ae9d109df4d1b 100644 --- a/homeassistant/components/senseme/translations/bg.json +++ b/homeassistant/components/senseme/translations/bg.json @@ -10,6 +10,9 @@ }, "flow_title": "{name} - {model} ({host})", "step": { + "discovery_confirm": { + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 {name} - {model} ({host})?" + }, "manual": { "data": { "host": "\u0425\u043e\u0441\u0442" diff --git a/homeassistant/components/tuya/translations/select.bg.json b/homeassistant/components/tuya/translations/select.bg.json index 12be88bbd8c72b..4e46bd55033d4c 100644 --- a/homeassistant/components/tuya/translations/select.bg.json +++ b/homeassistant/components/tuya/translations/select.bg.json @@ -15,7 +15,8 @@ "3h": "3 \u0447\u0430\u0441\u0430", "4h": "4 \u0447\u0430\u0441\u0430", "5h": "5 \u0447\u0430\u0441\u0430", - "6h": "6 \u0447\u0430\u0441\u0430" + "6h": "6 \u0447\u0430\u0441\u0430", + "cancel": "\u041e\u0442\u043a\u0430\u0437" }, "tuya__curtain_mode": { "morning": "\u0421\u0443\u0442\u0440\u0438\u043d", diff --git a/homeassistant/components/unifiprotect/translations/ja.json b/homeassistant/components/unifiprotect/translations/ja.json index 7e46c32c859f7a..e4ad1b3f2317b3 100644 --- a/homeassistant/components/unifiprotect/translations/ja.json +++ b/homeassistant/components/unifiprotect/translations/ja.json @@ -38,6 +38,7 @@ "username": "\u30e6\u30fc\u30b6\u30fc\u540d", "verify_ssl": "SSL\u8a3c\u660e\u66f8\u3092\u78ba\u8a8d\u3059\u308b" }, + "description": "UniFi OS\u30b3\u30f3\u30bd\u30fc\u30eb\u3067\u4f5c\u6210\u3057\u305f\u30ed\u30fc\u30ab\u30eb\u30e6\u30fc\u30b6\u30fc\u3067\u30ed\u30b0\u30a4\u30f3\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002Ubiquiti Cloud Users\u3067\u306f\u52d5\u4f5c\u3057\u307e\u305b\u3093\u3002\u8a73\u7d30\u306b\u3064\u3044\u3066\u306f\u3001{local_user_documentation_url} \u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002", "title": "UniFi Protect\u306e\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7" } } diff --git a/homeassistant/components/webostv/translations/bg.json b/homeassistant/components/webostv/translations/bg.json index cb9aea4f85dbe7..28092bd8b8c384 100644 --- a/homeassistant/components/webostv/translations/bg.json +++ b/homeassistant/components/webostv/translations/bg.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e" }, + "flow_title": "LG webOS Smart TV", "step": { "user": { "data": { diff --git a/homeassistant/components/zwave_me/translations/ja.json b/homeassistant/components/zwave_me/translations/ja.json index f46f1f6c452deb..2816ea011e4faf 100644 --- a/homeassistant/components/zwave_me/translations/ja.json +++ b/homeassistant/components/zwave_me/translations/ja.json @@ -12,7 +12,8 @@ "data": { "token": "\u30c8\u30fc\u30af\u30f3", "url": "URL" - } + }, + "description": "Z-Way\u30b5\u30fc\u30d0\u30fc\u306eIP\u30a2\u30c9\u30ec\u30b9\u3068Z-Way\u30a2\u30af\u30bb\u30b9\u30c8\u30fc\u30af\u30f3\u3092\u5165\u529b\u3057\u307e\u3059\u3002HTTP\u306e\u4ee3\u308f\u308a\u306bHTTPS\u3092\u4f7f\u7528\u3059\u308b\u5fc5\u8981\u304c\u3042\u308b\u5834\u5408\u306f\u3001IP\u30a2\u30c9\u30ec\u30b9\u306e\u524d\u306b\u3001wss://\u3092\u4ed8\u3051\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002\u30c8\u30fc\u30af\u30f3\u3092\u53d6\u5f97\u3059\u308b\u306b\u306f\u3001Z-Way user interface > Menu > Settings > User > API token \u306b\u79fb\u52d5\u3057\u307e\u3059\u3002Home Assistant\u306e\u65b0\u3057\u3044\u30e6\u30fc\u30b6\u30fc\u3092\u4f5c\u6210\u3057\u3001Home Assistant\u304b\u3089\u5236\u5fa1\u3059\u308b\u5fc5\u8981\u306e\u3042\u308b\u30c7\u30d0\u30a4\u30b9\u3078\u306e\u30a2\u30af\u30bb\u30b9\u3092\u8a31\u53ef\u3059\u308b\u3053\u3068\u3092\u304a\u52e7\u3081\u3057\u307e\u3059\u3002find.z-wave.me\u3092\u4ecb\u3057\u305f\u30ea\u30e2\u30fc\u30c8\u30a2\u30af\u30bb\u30b9\u3092\u4f7f\u7528\u3057\u3066\u3001\u30ea\u30e2\u30fc\u30c8Z-Way\u3092\u63a5\u7d9a\u3059\u308b\u3053\u3068\u3082\u3067\u304d\u307e\u3059\u3002IP\u30d5\u30a3\u30fc\u30eb\u30c9\u306b\u3001wss://find.z-wave.me \u3092\u5165\u529b\u3057\u3001\u30b0\u30ed\u30fc\u30d0\u30eb\u30b9\u30b3\u30fc\u30d7\u3067\u30c8\u30fc\u30af\u30f3\u3092\u30b3\u30d4\u30fc\u3057\u307e\u3059\uff08\u3053\u308c\u306b\u3064\u3044\u3066\u306f\u3001find.z-wave.me\u3092\u4ecb\u3057\u3066Z-Way\u306b\u30ed\u30b0\u30a4\u30f3\u3057\u307e\u3059\uff09\u3002" } } } From 6d10bd094f8fa27bc47a687349717c5a4efa41b7 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 14 Feb 2022 16:23:37 -0800 Subject: [PATCH 0644/1098] Bump frontend to 20220214.0 (#66535) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index e29b27e90269ed..654f8f2ee1b2bd 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20220203.0" + "home-assistant-frontend==20220214.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index fc0292523c25cb..23ef98366e4354 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ ciso8601==2.2.0 cryptography==35.0.0 emoji==1.6.3 hass-nabucasa==0.52.0 -home-assistant-frontend==20220203.0 +home-assistant-frontend==20220214.0 httpx==0.21.3 ifaddr==0.1.7 jinja2==3.0.3 diff --git a/requirements_all.txt b/requirements_all.txt index ccc47cecb3fa50..4ed3c2ea7e0693 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -842,7 +842,7 @@ hole==0.7.0 holidays==0.12 # homeassistant.components.frontend -home-assistant-frontend==20220203.0 +home-assistant-frontend==20220214.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8a4a99bef14e59..3b90ad1991361b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -549,7 +549,7 @@ hole==0.7.0 holidays==0.12 # homeassistant.components.frontend -home-assistant-frontend==20220203.0 +home-assistant-frontend==20220214.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 4dbd9b21b7ced03de58157b35cf35711028e4b11 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 15 Feb 2022 01:47:39 +0100 Subject: [PATCH 0645/1098] Adjust Plugwise debouncer to not refresh immediately (#66521) --- homeassistant/components/plugwise/coordinator.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/homeassistant/components/plugwise/coordinator.py b/homeassistant/components/plugwise/coordinator.py index b0b2c4b12941e1..1c8de0c6544347 100644 --- a/homeassistant/components/plugwise/coordinator.py +++ b/homeassistant/components/plugwise/coordinator.py @@ -6,6 +6,7 @@ from plugwise.exceptions import PlugwiseException, XMLDataMissingError from homeassistant.core import HomeAssistant +from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER @@ -30,6 +31,14 @@ def __init__(self, hass: HomeAssistant, api: Smile) -> None: update_interval=DEFAULT_SCAN_INTERVAL.get( str(api.smile_type), timedelta(seconds=60) ), + # Don't refresh immediately, give the device time to process + # the change in state before we query it. + request_refresh_debouncer=Debouncer( + hass, + LOGGER, + cooldown=1.5, + immediate=False, + ), ) self.api = api From b211a1faa7992e077d706cdd8557655d1dff704c Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Tue, 15 Feb 2022 01:16:30 +0000 Subject: [PATCH 0646/1098] Fix utility meter restore state (#66490) * Address #63874 * avoid setting _last_period to None * name is always set in discovery * ValueError never happens only DecimalException * async_tariff_change tracks state change - state machine will not pass a None * test we only reset one utility_meter * test corrupted restored state * pretty sure _current_tariff doesn't change from init until here * missing assert * Revert "async_tariff_change tracks state change - state machine will not pass a None" This reverts commit 24fc04a964139e5cfecbfa20f91e2d30ab145d77. * address review comment * always a Decimal --- .../components/utility_meter/__init__.py | 2 -- .../components/utility_meter/sensor.py | 19 ++++++++----------- tests/components/utility_meter/test_init.py | 11 ++++++++++- tests/components/utility_meter/test_sensor.py | 11 +++++++++-- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/utility_meter/__init__.py b/homeassistant/components/utility_meter/__init__.py index bf9beae060c900..525b4f3b43c360 100644 --- a/homeassistant/components/utility_meter/__init__.py +++ b/homeassistant/components/utility_meter/__init__.py @@ -182,8 +182,6 @@ def __init__(self, name, tariffs): async def async_added_to_hass(self): """Run when entity about to be added.""" await super().async_added_to_hass() - if self._current_tariff is not None: - return state = await self.async_get_last_state() if not state or state.state not in self._tariffs: diff --git a/homeassistant/components/utility_meter/sensor.py b/homeassistant/components/utility_meter/sensor.py index b65628d5f0b612..ec137968bc5a27 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -32,6 +32,7 @@ async_track_state_change_event, ) from homeassistant.helpers.restore_state import RestoreEntity +from homeassistant.helpers.template import is_number from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType import homeassistant.util.dt as dt_util @@ -166,13 +167,10 @@ def __init__( self._parent_meter = parent_meter self._sensor_source_id = source_entity self._state = None - self._last_period = 0 + self._last_period = Decimal(0) self._last_reset = dt_util.utcnow() self._collecting = None - if name: - self._name = name - else: - self._name = f"{source_entity} meter" + self._name = name self._unit_of_measurement = None self._period = meter_type if meter_type is not None: @@ -231,8 +229,6 @@ def async_reading(self, event): return self._state += adjustment - except ValueError as err: - _LOGGER.warning("While processing state changes: %s", err) except DecimalException as err: _LOGGER.warning( "Invalid state (%s > %s): %s", old_state.state, new_state.state, err @@ -282,7 +278,7 @@ async def async_reset_meter(self, entity_id): return _LOGGER.debug("Reset utility meter <%s>", self.entity_id) self._last_reset = dt_util.utcnow() - self._last_period = str(self._state) + self._last_period = Decimal(self._state) if self._state else Decimal(0) self._state = 0 self.async_write_ha_state() @@ -319,9 +315,10 @@ async def async_added_to_hass(self): ATTR_UNIT_OF_MEASUREMENT ) self._last_period = ( - float(state.attributes.get(ATTR_LAST_PERIOD)) + Decimal(state.attributes[ATTR_LAST_PERIOD]) if state.attributes.get(ATTR_LAST_PERIOD) - else 0 + and is_number(state.attributes[ATTR_LAST_PERIOD]) + else Decimal(0) ) self._last_reset = dt_util.as_utc( dt_util.parse_datetime(state.attributes.get(ATTR_LAST_RESET)) @@ -399,7 +396,7 @@ def extra_state_attributes(self): state_attr = { ATTR_SOURCE_ID: self._sensor_source_id, ATTR_STATUS: PAUSED if self._collecting is None else COLLECTING, - ATTR_LAST_PERIOD: self._last_period, + ATTR_LAST_PERIOD: str(self._last_period), } if self._period is not None: state_attr[ATTR_PERIOD] = self._period diff --git a/tests/components/utility_meter/test_init.py b/tests/components/utility_meter/test_init.py index 61e6fc4dae8edf..3297c696ca1097 100644 --- a/tests/components/utility_meter/test_init.py +++ b/tests/components/utility_meter/test_init.py @@ -62,7 +62,12 @@ async def test_services(hass): "source": "sensor.energy", "cycle": "hourly", "tariffs": ["peak", "offpeak"], - } + }, + "energy_bill2": { + "source": "sensor.energy", + "cycle": "hourly", + "tariffs": ["peak", "offpeak"], + }, } } @@ -153,6 +158,10 @@ async def test_services(hass): state = hass.states.get("sensor.energy_bill_offpeak") assert state.state == "0" + # meanwhile energy_bill2_peak accumulated all kWh + state = hass.states.get("sensor.energy_bill2_peak") + assert state.state == "4" + async def test_cron(hass, legacy_patchable_time): """Test cron pattern and offset fails.""" diff --git a/tests/components/utility_meter/test_sensor.py b/tests/components/utility_meter/test_sensor.py index 51212580aaf87d..fbaf795f9e2fdc 100644 --- a/tests/components/utility_meter/test_sensor.py +++ b/tests/components/utility_meter/test_sensor.py @@ -304,6 +304,10 @@ async def test_restore_state(hass): ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR, }, ), + State( + "sensor.energy_bill_midpeak", + "error", + ), State( "sensor.energy_bill_offpeak", "6", @@ -326,6 +330,9 @@ async def test_restore_state(hass): assert state.attributes.get("last_reset") == last_reset assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == ENERGY_KILO_WATT_HOUR + state = hass.states.get("sensor.energy_bill_midpeak") + assert state.state == STATE_UNKNOWN + state = hass.states.get("sensor.energy_bill_offpeak") assert state.state == "6" assert state.attributes.get("status") == COLLECTING @@ -530,7 +537,7 @@ async def _test_self_reset(hass, config, start_time, expect_reset=True): assert state.attributes.get("last_reset") == now.isoformat() assert state.state == "3" else: - assert state.attributes.get("last_period") == 0 + assert state.attributes.get("last_period") == "0" assert state.state == "5" start_time_str = dt_util.parse_datetime(start_time).isoformat() assert state.attributes.get("last_reset") == start_time_str @@ -559,7 +566,7 @@ async def _test_self_reset(hass, config, start_time, expect_reset=True): assert state.attributes.get("last_period") == "2" assert state.state == "7" else: - assert state.attributes.get("last_period") == 0 + assert state.attributes.get("last_period") == "0" assert state.state == "9" From 6b6f50e28bdc6f24b4735bde64aae976405feae7 Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Tue, 15 Feb 2022 13:02:40 +1000 Subject: [PATCH 0647/1098] Bump pyaussiebb in Aussie Broadband (#65754) Co-authored-by: Shay Levy --- .../components/aussie_broadband/__init__.py | 12 +++++++--- .../components/aussie_broadband/manifest.json | 6 +++-- .../components/aussie_broadband/sensor.py | 4 ++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/aussie_broadband/common.py | 20 ++++++++++++---- .../components/aussie_broadband/test_init.py | 8 ++++++- .../aussie_broadband/test_sensor.py | 24 +++++++++++++++++++ 8 files changed, 64 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/aussie_broadband/__init__.py b/homeassistant/components/aussie_broadband/__init__.py index 45ae6f90e6d18c..af2969f6f7aebc 100644 --- a/homeassistant/components/aussie_broadband/__init__.py +++ b/homeassistant/components/aussie_broadband/__init__.py @@ -5,14 +5,15 @@ import logging from aiohttp import ClientError -from aussiebb.asyncio import AussieBB, AuthenticationException +from aussiebb.asyncio import AussieBB +from aussiebb.exceptions import AuthenticationException, UnrecognisedServiceType from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import CONF_SERVICES, DEFAULT_UPDATE_INTERVAL, DOMAIN, SERVICE_ID @@ -44,7 +45,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # Create an appropriate refresh function def update_data_factory(service_id): async def async_update_data(): - return await client.get_usage(service_id) + try: + return await client.get_usage(service_id) + except UnrecognisedServiceType as err: + raise UpdateFailed( + f"Service {service_id} of type '{services[service_id]['type']}' was unrecognised" + ) from err return async_update_data diff --git a/homeassistant/components/aussie_broadband/manifest.json b/homeassistant/components/aussie_broadband/manifest.json index fcec645127f751..5476371f755b63 100644 --- a/homeassistant/components/aussie_broadband/manifest.json +++ b/homeassistant/components/aussie_broadband/manifest.json @@ -4,12 +4,14 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/aussie_broadband", "requirements": [ - "pyaussiebb==0.0.9" + "pyaussiebb==0.0.11" ], "codeowners": [ "@nickw444", "@Bre77" ], "iot_class": "cloud_polling", - "loggers": ["aussiebb"] + "loggers": [ + "aussiebb" + ] } \ No newline at end of file diff --git a/homeassistant/components/aussie_broadband/sensor.py b/homeassistant/components/aussie_broadband/sensor.py index 2ce8aaca9c470d..04c1cff97b5e3c 100644 --- a/homeassistant/components/aussie_broadband/sensor.py +++ b/homeassistant/components/aussie_broadband/sensor.py @@ -143,7 +143,7 @@ def __init__( def native_value(self): """Return the state of the sensor.""" if self.entity_description.key == "internet": - return self.coordinator.data[self.entity_description.key]["kbytes"] + return self.coordinator.data[self.entity_description.key].get("kbytes") if self.entity_description.key in ("national", "mobile", "sms"): - return self.coordinator.data[self.entity_description.key]["calls"] + return self.coordinator.data[self.entity_description.key].get("calls") return self.coordinator.data[self.entity_description.key] diff --git a/requirements_all.txt b/requirements_all.txt index 4ed3c2ea7e0693..8453c327c2fc6d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1407,7 +1407,7 @@ pyatome==0.1.1 pyatv==0.10.0 # homeassistant.components.aussie_broadband -pyaussiebb==0.0.9 +pyaussiebb==0.0.11 # homeassistant.components.balboa pybalboa==0.13 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3b90ad1991361b..d1d53c75756606 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -887,7 +887,7 @@ pyatmo==6.2.4 pyatv==0.10.0 # homeassistant.components.aussie_broadband -pyaussiebb==0.0.9 +pyaussiebb==0.0.11 # homeassistant.components.balboa pybalboa==0.13 diff --git a/tests/components/aussie_broadband/common.py b/tests/components/aussie_broadband/common.py index abb4bce042d352..abb99355ef3f64 100644 --- a/tests/components/aussie_broadband/common.py +++ b/tests/components/aussie_broadband/common.py @@ -5,7 +5,7 @@ CONF_SERVICES, DOMAIN as AUSSIE_BROADBAND_DOMAIN, ) -from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from tests.common import MockConfigEntry @@ -22,6 +22,12 @@ "type": "PhoneMobile", "name": "Mobile", }, + { + "service_id": "23456789", + "description": "Fake ABB VOIP Service", + "type": "VOIP", + "name": "VOIP", + }, ] FAKE_DATA = { @@ -30,12 +36,16 @@ } -async def setup_platform(hass, platforms=[], side_effect=None, usage={}): +async def setup_platform( + hass, platforms=[], side_effect=None, usage={}, usage_effect=None +): """Set up the Aussie Broadband platform.""" mock_entry = MockConfigEntry( domain=AUSSIE_BROADBAND_DOMAIN, data=FAKE_DATA, - options={CONF_SERVICES: ["12345678", "87654321"], CONF_SCAN_INTERVAL: 30}, + options={ + CONF_SERVICES: ["12345678", "87654321", "23456789", "98765432"], + }, ) mock_entry.add_to_hass(hass) @@ -50,7 +60,9 @@ async def setup_platform(hass, platforms=[], side_effect=None, usage={}): return_value=FAKE_SERVICES, side_effect=side_effect, ), patch( - "aussiebb.asyncio.AussieBB.get_usage", return_value=usage + "aussiebb.asyncio.AussieBB.get_usage", + return_value=usage, + side_effect=usage_effect, ): await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/aussie_broadband/test_init.py b/tests/components/aussie_broadband/test_init.py index 9e31aa9b737d8d..9e2e0b7cccc067 100644 --- a/tests/components/aussie_broadband/test_init.py +++ b/tests/components/aussie_broadband/test_init.py @@ -2,7 +2,7 @@ from unittest.mock import patch from aiohttp import ClientConnectionError -from aussiebb.asyncio import AuthenticationException +from aussiebb.exceptions import AuthenticationException, UnrecognisedServiceType from homeassistant import data_entry_flow from homeassistant.config_entries import ConfigEntryState @@ -33,3 +33,9 @@ async def test_net_failure(hass: HomeAssistant) -> None: """Test init with a network failure.""" entry = await setup_platform(hass, side_effect=ClientConnectionError()) assert entry.state is ConfigEntryState.SETUP_RETRY + + +async def test_service_failure(hass: HomeAssistant) -> None: + """Test init with a invalid service.""" + entry = await setup_platform(hass, usage_effect=UnrecognisedServiceType()) + assert entry.state is ConfigEntryState.SETUP_RETRY diff --git a/tests/components/aussie_broadband/test_sensor.py b/tests/components/aussie_broadband/test_sensor.py index 30fac808a2780d..c99c52d5c86ce6 100644 --- a/tests/components/aussie_broadband/test_sensor.py +++ b/tests/components/aussie_broadband/test_sensor.py @@ -1,5 +1,6 @@ """Aussie Broadband sensor platform tests.""" from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.const import STATE_UNKNOWN from .common import setup_platform @@ -24,6 +25,19 @@ "historical": [], } +MOCK_VOIP_USAGE = { + "national": {"calls": 1, "cost": 0}, + "mobile": {"calls": 2, "cost": 0}, + "international": {"calls": 3, "cost": 0}, + "sms": {}, + "internet": {}, + "voicemail": {"calls": 6, "cost": 0}, + "other": {"calls": 7, "cost": 0}, + "daysTotal": 31, + "daysRemaining": 30, + "historical": [], +} + async def test_nbn_sensor_states(hass): """Tests that the sensors are correct.""" @@ -48,3 +62,13 @@ async def test_phone_sensor_states(hass): assert hass.states.get("sensor.mobile_data_used").state == "512" assert hass.states.get("sensor.mobile_billing_cycle_length").state == "31" assert hass.states.get("sensor.mobile_billing_cycle_remaining").state == "30" + + +async def test_voip_sensor_states(hass): + """Tests that the sensors are correct.""" + + await setup_platform(hass, [SENSOR_DOMAIN], usage=MOCK_VOIP_USAGE) + + assert hass.states.get("sensor.mobile_national_calls").state == "1" + assert hass.states.get("sensor.mobile_sms_sent").state == STATE_UNKNOWN + assert hass.states.get("sensor.mobile_data_used").state == STATE_UNKNOWN From 94980399cff9881e64600bacdc916498881f9aac Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 14 Feb 2022 20:46:32 -0800 Subject: [PATCH 0648/1098] Bump hass-nabucas to 0.52.1 (#66536) --- homeassistant/components/cloud/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index 3e55f6359c6db1..9a3a88dfc95902 100644 --- a/homeassistant/components/cloud/manifest.json +++ b/homeassistant/components/cloud/manifest.json @@ -2,7 +2,7 @@ "domain": "cloud", "name": "Home Assistant Cloud", "documentation": "https://www.home-assistant.io/integrations/cloud", - "requirements": ["hass-nabucasa==0.52.0"], + "requirements": ["hass-nabucasa==0.52.1"], "dependencies": ["http", "webhook"], "after_dependencies": ["google_assistant", "alexa"], "codeowners": ["@home-assistant/cloud"], diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 23ef98366e4354..e26dcbe925ab1d 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -14,7 +14,7 @@ certifi>=2021.5.30 ciso8601==2.2.0 cryptography==35.0.0 emoji==1.6.3 -hass-nabucasa==0.52.0 +hass-nabucasa==0.52.1 home-assistant-frontend==20220214.0 httpx==0.21.3 ifaddr==0.1.7 diff --git a/requirements_all.txt b/requirements_all.txt index 8453c327c2fc6d..29248f86cbcdb4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -809,7 +809,7 @@ habitipy==0.2.0 hangups==0.4.17 # homeassistant.components.cloud -hass-nabucasa==0.52.0 +hass-nabucasa==0.52.1 # homeassistant.components.splunk hass_splunk==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d1d53c75756606..f5a2fbf7a62a5c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -528,7 +528,7 @@ habitipy==0.2.0 hangups==0.4.17 # homeassistant.components.cloud -hass-nabucasa==0.52.0 +hass-nabucasa==0.52.1 # homeassistant.components.tasmota hatasmota==0.3.1 From 572fa7d0552c2726d919067db72abe1efdfb19c4 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 14 Feb 2022 21:45:09 -0800 Subject: [PATCH 0649/1098] Update nest camera to pull still images from stream component (#66427) * Update nest to use stream thumbnail when it exists * Update nest camera to always pull still image from stream Update nest camera to always pull the still iamge from the stream component, removing the use of the separate ffmpeg call, and removing use of the nest event image. Image for events can now be pulled using the media source APIs, rather than relying on the camera snapshot. * Simplify a comment * Remove more unused variables * Simplify comments, image, and test code * Remove assertions for placeholder images --- homeassistant/components/nest/camera_sdm.py | 38 +-- tests/components/nest/test_camera_sdm.py | 254 ++++---------------- 2 files changed, 55 insertions(+), 237 deletions(-) diff --git a/homeassistant/components/nest/camera_sdm.py b/homeassistant/components/nest/camera_sdm.py index fca79bde0402eb..2bd454fbe119e6 100644 --- a/homeassistant/components/nest/camera_sdm.py +++ b/homeassistant/components/nest/camera_sdm.py @@ -8,20 +8,16 @@ from pathlib import Path from google_nest_sdm.camera_traits import ( - CameraEventImageTrait, CameraImageTrait, CameraLiveStreamTrait, RtspStream, StreamingProtocol, ) from google_nest_sdm.device import Device -from google_nest_sdm.event_media import EventMedia from google_nest_sdm.exceptions import ApiException -from haffmpeg.tools import IMAGE_JPEG from homeassistant.components.camera import SUPPORT_STREAM, Camera from homeassistant.components.camera.const import STREAM_TYPE_WEB_RTC -from homeassistant.components.ffmpeg import async_get_image from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError, PlatformNotReady @@ -207,30 +203,16 @@ async def async_camera_image( self, width: int | None = None, height: int | None = None ) -> bytes | None: """Return bytes of camera image.""" - if CameraEventImageTrait.NAME in self._device.traits: - # Returns the snapshot of the last event for ~30 seconds after the event - event_media: EventMedia | None = None - try: - event_media = ( - await self._device.event_media_manager.get_active_event_media() - ) - except ApiException as err: - _LOGGER.debug("Failure while getting image for event: %s", err) - if event_media: - return event_media.media.contents - # Fetch still image from the live stream - stream_url = await self.stream_source() - if not stream_url: - if self.frontend_stream_type != STREAM_TYPE_WEB_RTC: - return None - # Nest Web RTC cams only have image previews for events, and not - # for "now" by design to save batter, and need a placeholder. - if not self._placeholder_image: - self._placeholder_image = await self.hass.async_add_executor_job( - PLACEHOLDER.read_bytes - ) - return self._placeholder_image - return await async_get_image(self.hass, stream_url, output_format=IMAGE_JPEG) + # Use the thumbnail from RTSP stream, or a placeholder if stream is + # not supported (e.g. WebRTC) + stream = await self.async_create_stream() + if stream: + return await stream.async_get_image(width, height) + if not self._placeholder_image: + self._placeholder_image = await self.hass.async_add_executor_job( + PLACEHOLDER.read_bytes + ) + return self._placeholder_image async def async_handle_web_rtc_offer(self, offer_sdp: str) -> str | None: """Return the source of the stream.""" diff --git a/tests/components/nest/test_camera_sdm.py b/tests/components/nest/test_camera_sdm.py index a4539cf9f8172f..81b4a7cf0a6e65 100644 --- a/tests/components/nest/test_camera_sdm.py +++ b/tests/components/nest/test_camera_sdm.py @@ -7,7 +7,7 @@ import datetime from http import HTTPStatus -from unittest.mock import patch +from unittest.mock import AsyncMock, Mock, patch import aiohttp from google_nest_sdm.device import Device @@ -22,7 +22,6 @@ STREAM_TYPE_WEB_RTC, ) from homeassistant.components.websocket_api.const import TYPE_RESULT -from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow @@ -54,9 +53,6 @@ MOTION_EVENT_ID = "FWWVQVUdGNUlTU2V4MGV2aTNXV..." EVENT_SESSION_ID = "CjY5Y3VKaTZwR3o4Y19YbTVfMF..." -# Tests can assert that image bytes came from an event or was decoded -# from the live stream. -IMAGE_BYTES_FROM_EVENT = b"test url image bytes" IMAGE_BYTES_FROM_STREAM = b"test stream image bytes" TEST_IMAGE_URL = "https://domain/sdm_event_snapshot/dGTZwR3o4Y1..." @@ -116,6 +112,30 @@ def make_stream_url_response( ) +@pytest.fixture +async def mock_create_stream(hass) -> Mock: + """Fixture to mock out the create stream call.""" + assert await async_setup_component(hass, "stream", {}) + with patch( + "homeassistant.components.camera.create_stream", autospec=True + ) as mock_stream: + mock_stream.return_value.endpoint_url.return_value = ( + "http://home.assistant/playlist.m3u8" + ) + mock_stream.return_value.async_get_image = AsyncMock() + mock_stream.return_value.async_get_image.return_value = IMAGE_BYTES_FROM_STREAM + yield mock_stream + + +async def async_get_image(hass, width=None, height=None): + """Get the camera image.""" + image = await camera.async_get_image( + hass, "camera.my_camera", width=width, height=height + ) + assert image.content_type == "image/jpeg" + return image.content + + async def async_setup_camera(hass, traits={}, auth=None): """Set up the platform and prerequisites.""" devices = {} @@ -138,21 +158,6 @@ async def fire_alarm(hass, point_in_time): await hass.async_block_till_done() -async def async_get_image(hass, width=None, height=None): - """Get image from the camera, a wrapper around camera.async_get_image.""" - # Note: this patches ImageFrame to simulate decoding an image from a live - # stream, however the test may not use it. Tests assert on the image - # contents to determine if the image came from the live stream or event. - with patch( - "homeassistant.components.ffmpeg.ImageFrame.get_image", - autopatch=True, - return_value=IMAGE_BYTES_FROM_STREAM, - ): - return await camera.async_get_image( - hass, "camera.my_camera", width=width, height=height - ) - - async def test_no_devices(hass): """Test configuration that returns no devices.""" await async_setup_camera(hass) @@ -194,7 +199,7 @@ async def test_camera_device(hass): assert device.identifiers == {("nest", DEVICE_ID)} -async def test_camera_stream(hass, auth): +async def test_camera_stream(hass, auth, mock_create_stream): """Test a basic camera and fetch its live stream.""" auth.responses = [make_stream_url_response()] await async_setup_camera(hass, DEVICE_TRAITS, auth=auth) @@ -208,11 +213,10 @@ async def test_camera_stream(hass, auth): stream_source = await camera.async_get_stream_source(hass, "camera.my_camera") assert stream_source == "rtsp://some/url?auth=g.0.streamingToken" - image = await async_get_image(hass) - assert image.content == IMAGE_BYTES_FROM_STREAM + assert await async_get_image(hass) == IMAGE_BYTES_FROM_STREAM -async def test_camera_ws_stream(hass, auth, hass_ws_client): +async def test_camera_ws_stream(hass, auth, hass_ws_client, mock_create_stream): """Test a basic camera that supports web rtc.""" auth.responses = [make_stream_url_response()] await async_setup_camera(hass, DEVICE_TRAITS, auth=auth) @@ -223,23 +227,23 @@ async def test_camera_ws_stream(hass, auth, hass_ws_client): assert cam.state == STATE_STREAMING assert cam.attributes["frontend_stream_type"] == STREAM_TYPE_HLS - with patch("homeassistant.components.camera.create_stream") as mock_stream: - mock_stream().endpoint_url.return_value = "http://home.assistant/playlist.m3u8" - client = await hass_ws_client(hass) - await client.send_json( - { - "id": 2, - "type": "camera/stream", - "entity_id": "camera.my_camera", - } - ) - msg = await client.receive_json() + client = await hass_ws_client(hass) + await client.send_json( + { + "id": 2, + "type": "camera/stream", + "entity_id": "camera.my_camera", + } + ) + msg = await client.receive_json() assert msg["id"] == 2 assert msg["type"] == TYPE_RESULT assert msg["success"] assert msg["result"]["url"] == "http://home.assistant/playlist.m3u8" + assert await async_get_image(hass) == IMAGE_BYTES_FROM_STREAM + async def test_camera_ws_stream_failure(hass, auth, hass_ws_client): """Test a basic camera that supports web rtc.""" @@ -292,9 +296,8 @@ async def test_camera_stream_missing_trait(hass, auth): stream_source = await camera.async_get_stream_source(hass, "camera.my_camera") assert stream_source is None - # Unable to get an image from the live stream - with pytest.raises(HomeAssistantError): - await async_get_image(hass) + # Fallback to placeholder image + await async_get_image(hass) async def test_refresh_expired_stream_token(hass, auth): @@ -422,16 +425,6 @@ async def test_camera_removed(hass, auth): stream_source = await camera.async_get_stream_source(hass, "camera.my_camera") assert stream_source == "rtsp://some/url?auth=g.0.streamingToken" - # Fetch an event image, exercising cleanup on remove - await subscriber.async_receive_event(make_motion_event()) - await hass.async_block_till_done() - auth.responses = [ - aiohttp.web.json_response(GENERATE_IMAGE_URL_RESPONSE), - aiohttp.web.Response(body=IMAGE_BYTES_FROM_EVENT), - ] - image = await async_get_image(hass) - assert image.content == IMAGE_BYTES_FROM_EVENT - for config_entry in hass.config_entries.async_entries(DOMAIN): await hass.config_entries.async_remove(config_entry.entry_id) await hass.async_block_till_done() @@ -517,160 +510,6 @@ async def test_refresh_expired_stream_failure(hass, auth): assert create_stream.called -async def test_camera_image_from_last_event(hass, auth): - """Test an image generated from an event.""" - # The subscriber receives a message related to an image event. The camera - # holds on to the event message. When the test asks for a capera snapshot - # it exchanges the event id for an image url and fetches the image. - subscriber = await async_setup_camera(hass, DEVICE_TRAITS, auth=auth) - assert len(hass.states.async_all()) == 1 - assert hass.states.get("camera.my_camera") - - # Simulate a pubsub message received by the subscriber with a motion event. - await subscriber.async_receive_event(make_motion_event()) - await hass.async_block_till_done() - - auth.responses = [ - # Fake response from API that returns url image - aiohttp.web.json_response(GENERATE_IMAGE_URL_RESPONSE), - # Fake response for the image content fetch - aiohttp.web.Response(body=IMAGE_BYTES_FROM_EVENT), - ] - - image = await async_get_image(hass) - assert image.content == IMAGE_BYTES_FROM_EVENT - # Verify expected image fetch request was captured - assert auth.url == TEST_IMAGE_URL - assert auth.headers == IMAGE_AUTHORIZATION_HEADERS - - # An additional fetch uses the cache and does not send another RPC - image = await async_get_image(hass) - assert image.content == IMAGE_BYTES_FROM_EVENT - # Verify expected image fetch request was captured - assert auth.url == TEST_IMAGE_URL - assert auth.headers == IMAGE_AUTHORIZATION_HEADERS - - -async def test_camera_image_from_event_not_supported(hass, auth): - """Test fallback to stream image when event images are not supported.""" - # Create a device that does not support the CameraEventImgae trait - traits = DEVICE_TRAITS.copy() - del traits["sdm.devices.traits.CameraEventImage"] - subscriber = await async_setup_camera(hass, traits, auth=auth) - assert len(hass.states.async_all()) == 1 - assert hass.states.get("camera.my_camera") - - await subscriber.async_receive_event(make_motion_event()) - await hass.async_block_till_done() - - # Camera fetches a stream url since CameraEventImage is not supported - auth.responses = [make_stream_url_response()] - - image = await async_get_image(hass) - assert image.content == IMAGE_BYTES_FROM_STREAM - - -async def test_generate_event_image_url_failure(hass, auth): - """Test fallback to stream on failure to create an image url.""" - subscriber = await async_setup_camera(hass, DEVICE_TRAITS, auth=auth) - assert len(hass.states.async_all()) == 1 - assert hass.states.get("camera.my_camera") - - await subscriber.async_receive_event(make_motion_event()) - await hass.async_block_till_done() - - auth.responses = [ - # Fail to generate the image url - aiohttp.web.Response(status=HTTPStatus.INTERNAL_SERVER_ERROR), - # Camera fetches a stream url as a fallback - make_stream_url_response(), - ] - - image = await async_get_image(hass) - assert image.content == IMAGE_BYTES_FROM_STREAM - - -async def test_fetch_event_image_failure(hass, auth): - """Test fallback to a stream on image download failure.""" - subscriber = await async_setup_camera(hass, DEVICE_TRAITS, auth=auth) - assert len(hass.states.async_all()) == 1 - assert hass.states.get("camera.my_camera") - - await subscriber.async_receive_event(make_motion_event()) - await hass.async_block_till_done() - - auth.responses = [ - # Fake response from API that returns url image - aiohttp.web.json_response(GENERATE_IMAGE_URL_RESPONSE), - # Fail to download the image - aiohttp.web.Response(status=HTTPStatus.INTERNAL_SERVER_ERROR), - # Camera fetches a stream url as a fallback - make_stream_url_response(), - ] - - image = await async_get_image(hass) - assert image.content == IMAGE_BYTES_FROM_STREAM - - -async def test_event_image_expired(hass, auth): - """Test fallback for an event event image that has expired.""" - subscriber = await async_setup_camera(hass, DEVICE_TRAITS, auth=auth) - assert len(hass.states.async_all()) == 1 - assert hass.states.get("camera.my_camera") - - # Simulate a pubsub message has already expired - event_timestamp = utcnow() - datetime.timedelta(seconds=40) - await subscriber.async_receive_event(make_motion_event(timestamp=event_timestamp)) - await hass.async_block_till_done() - - # Fallback to a stream url since the event message is expired. - auth.responses = [make_stream_url_response()] - - image = await async_get_image(hass) - assert image.content == IMAGE_BYTES_FROM_STREAM - - -async def test_multiple_event_images(hass, auth): - """Test fallback for an event event image that has been cleaned up on expiration.""" - subscriber = await async_setup_camera(hass, DEVICE_TRAITS, auth=auth) - # Simplify test setup - subscriber.cache_policy.fetch = False - assert len(hass.states.async_all()) == 1 - assert hass.states.get("camera.my_camera") - - event_timestamp = utcnow() - await subscriber.async_receive_event( - make_motion_event(event_session_id="event-session-1", timestamp=event_timestamp) - ) - await hass.async_block_till_done() - - auth.responses = [ - # Fake response from API that returns url image - aiohttp.web.json_response(GENERATE_IMAGE_URL_RESPONSE), - # Fake response for the image content fetch - aiohttp.web.Response(body=IMAGE_BYTES_FROM_EVENT), - # Image is refetched after being cleared by expiration alarm - aiohttp.web.json_response(GENERATE_IMAGE_URL_RESPONSE), - aiohttp.web.Response(body=b"updated image bytes"), - ] - - image = await async_get_image(hass) - assert image.content == IMAGE_BYTES_FROM_EVENT - - next_event_timestamp = event_timestamp + datetime.timedelta(seconds=25) - await subscriber.async_receive_event( - make_motion_event( - event_id="updated-event-id", - event_session_id="event-session-2", - timestamp=next_event_timestamp, - ) - ) - await hass.async_block_till_done() - - image = await async_get_image(hass) - assert image.content == b"updated image bytes" - - async def test_camera_web_rtc(hass, auth, hass_ws_client): """Test a basic camera that supports web rtc.""" expiration = utcnow() + datetime.timedelta(seconds=100) @@ -724,10 +563,8 @@ async def test_camera_web_rtc(hass, auth, hass_ws_client): assert msg["result"]["answer"] == "v=0\r\ns=-\r\n" # Nest WebRTC cameras return a placeholder - content = await async_get_image(hass) - assert content.content_type == "image/jpeg" - content = await async_get_image(hass, width=1024, height=768) - assert content.content_type == "image/jpeg" + await async_get_image(hass) + await async_get_image(hass, width=1024, height=768) async def test_camera_web_rtc_unsupported(hass, auth, hass_ws_client): @@ -802,7 +639,7 @@ async def test_camera_web_rtc_offer_failure(hass, auth, hass_ws_client): assert msg["error"]["message"].startswith("Nest API error") -async def test_camera_multiple_streams(hass, auth, hass_ws_client): +async def test_camera_multiple_streams(hass, auth, hass_ws_client, mock_create_stream): """Test a camera supporting multiple stream types.""" expiration = utcnow() + datetime.timedelta(seconds=100) auth.responses = [ @@ -846,8 +683,7 @@ async def test_camera_multiple_streams(hass, auth, hass_ws_client): stream_source = await camera.async_get_stream_source(hass, "camera.my_camera") assert stream_source == "rtsp://some/url?auth=g.0.streamingToken" - image = await async_get_image(hass) - assert image.content == IMAGE_BYTES_FROM_STREAM + assert await async_get_image(hass) == IMAGE_BYTES_FROM_STREAM # WebRTC stream client = await hass_ws_client(hass) From 334a8ab13f706268221c611102be564063b15a2e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 15 Feb 2022 01:24:35 -0600 Subject: [PATCH 0650/1098] Fix missing abort strings in wiz (#66538) --- homeassistant/components/wiz/strings.json | 2 ++ homeassistant/components/wiz/translations/en.json | 7 ++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/wiz/strings.json b/homeassistant/components/wiz/strings.json index 548e79e915787d..d9b2a19d752e12 100644 --- a/homeassistant/components/wiz/strings.json +++ b/homeassistant/components/wiz/strings.json @@ -25,6 +25,8 @@ "no_ip": "Not a valid IP address." }, "abort": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } } diff --git a/homeassistant/components/wiz/translations/en.json b/homeassistant/components/wiz/translations/en.json index ab4b7929739e02..97bb7fc25ba968 100644 --- a/homeassistant/components/wiz/translations/en.json +++ b/homeassistant/components/wiz/translations/en.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Device is already configured", + "cannot_connect": "Failed to connect", "no_devices_found": "No devices found on the network" }, "error": { @@ -13,9 +14,6 @@ }, "flow_title": "{name} ({host})", "step": { - "confirm": { - "description": "Do you want to start set up?" - }, "discovery_confirm": { "description": "Do you want to setup {name} ({host})?" }, @@ -26,8 +24,7 @@ }, "user": { "data": { - "host": "IP Address", - "name": "Name" + "host": "IP Address" }, "description": "If you leave the IP Address empty, discovery will be used to find devices." } From 1bc936ca8d3a4ec36a59067ce55475a57eba1fa5 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 15 Feb 2022 08:32:56 +0100 Subject: [PATCH 0651/1098] Improve Deconz sensors (#65259) --- homeassistant/components/deconz/sensor.py | 376 ++++---- tests/components/deconz/test_climate.py | 8 +- tests/components/deconz/test_deconz_event.py | 6 +- .../components/deconz/test_device_trigger.py | 2 +- tests/components/deconz/test_sensor.py | 900 +++++++++++++----- 5 files changed, 839 insertions(+), 453 deletions(-) diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index 5c870ffd937820..b0df644f1bdaef 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -3,10 +3,10 @@ from collections.abc import Callable, ValuesView from dataclasses import dataclass +from datetime import datetime from pydeconz.sensor import ( AirQuality, - Battery, Consumption, Daylight, DeconzSensor as PydeconzSensor, @@ -17,7 +17,6 @@ Pressure, Switch, Temperature, - Thermostat, Time, ) @@ -48,22 +47,21 @@ from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType +import homeassistant.util.dt as dt_util from .const import ATTR_DARK, ATTR_ON from .deconz_device import DeconzDevice from .gateway import DeconzGateway, get_gateway_from_config_entry -DECONZ_SENSORS = ( - AirQuality, - Consumption, - Daylight, - GenericStatus, - Humidity, - LightLevel, - Power, - Pressure, - Temperature, - Time, +PROVIDES_EXTRA_ATTRIBUTES = ( + "battery", + "consumption", + "status", + "humidity", + "light_level", + "power", + "pressure", + "temperature", ) ATTR_CURRENT = "current" @@ -76,9 +74,7 @@ class DeconzSensorDescriptionMixin: """Required values when describing secondary sensor attributes.""" - suffix: str update_key: str - required_attr: str value_fn: Callable[[PydeconzSensor], float | int | None] @@ -89,78 +85,133 @@ class DeconzSensorDescription( ): """Class describing deCONZ binary sensor entities.""" + suffix: str = "" + ENTITY_DESCRIPTIONS = { - Battery: SensorEntityDescription( + AirQuality: [ + DeconzSensorDescription( + key="air_quality", + value_fn=lambda device: device.air_quality, # type: ignore[no-any-return] + update_key="airquality", + state_class=SensorStateClass.MEASUREMENT, + ), + DeconzSensorDescription( + key="air_quality_ppb", + value_fn=lambda device: device.air_quality_ppb, # type: ignore[no-any-return] + suffix="PPB", + update_key="airqualityppb", + device_class=SensorDeviceClass.AQI, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION, + ), + ], + Consumption: [ + DeconzSensorDescription( + key="consumption", + value_fn=lambda device: device.scaled_consumption, # type: ignore[no-any-return] + update_key="consumption", + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + ) + ], + Daylight: [ + DeconzSensorDescription( + key="status", + value_fn=lambda device: device.status, # type: ignore[no-any-return] + update_key="status", + icon="mdi:white-balance-sunny", + entity_registry_enabled_default=False, + ) + ], + GenericStatus: [ + DeconzSensorDescription( + key="status", + value_fn=lambda device: device.status, # type: ignore[no-any-return] + update_key="status", + ) + ], + Humidity: [ + DeconzSensorDescription( + key="humidity", + value_fn=lambda device: device.scaled_humidity, # type: ignore[no-any-return] + update_key="humidity", + device_class=SensorDeviceClass.HUMIDITY, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + ) + ], + LightLevel: [ + DeconzSensorDescription( + key="light_level", + value_fn=lambda device: device.scaled_light_level, # type: ignore[no-any-return] + update_key="lightlevel", + device_class=SensorDeviceClass.ILLUMINANCE, + native_unit_of_measurement=LIGHT_LUX, + ) + ], + Power: [ + DeconzSensorDescription( + key="power", + value_fn=lambda device: device.power, # type: ignore[no-any-return] + update_key="power", + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=POWER_WATT, + ) + ], + Pressure: [ + DeconzSensorDescription( + key="pressure", + value_fn=lambda device: device.pressure, # type: ignore[no-any-return] + update_key="pressure", + device_class=SensorDeviceClass.PRESSURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PRESSURE_HPA, + ) + ], + Temperature: [ + DeconzSensorDescription( + key="temperature", + value_fn=lambda device: device.temperature, # type: ignore[no-any-return] + update_key="temperature", + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=TEMP_CELSIUS, + ) + ], + Time: [ + DeconzSensorDescription( + key="last_set", + value_fn=lambda device: device.last_set, # type: ignore[no-any-return] + update_key="lastset", + device_class=SensorDeviceClass.TIMESTAMP, + state_class=SensorStateClass.TOTAL_INCREASING, + ) + ], +} + +SENSOR_DESCRIPTIONS = [ + DeconzSensorDescription( key="battery", + value_fn=lambda device: device.battery, # type: ignore[no-any-return] + suffix="Battery", + update_key="battery", device_class=SensorDeviceClass.BATTERY, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=PERCENTAGE, entity_category=EntityCategory.DIAGNOSTIC, ), - Consumption: SensorEntityDescription( - key="consumption", - device_class=SensorDeviceClass.ENERGY, - state_class=SensorStateClass.TOTAL_INCREASING, - native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, - ), - Daylight: SensorEntityDescription( - key="daylight", - icon="mdi:white-balance-sunny", - entity_registry_enabled_default=False, - ), - Humidity: SensorEntityDescription( - key="humidity", - device_class=SensorDeviceClass.HUMIDITY, - state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=PERCENTAGE, - ), - LightLevel: SensorEntityDescription( - key="lightlevel", - device_class=SensorDeviceClass.ILLUMINANCE, - native_unit_of_measurement=LIGHT_LUX, - ), - Power: SensorEntityDescription( - key="power", - device_class=SensorDeviceClass.POWER, - state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=POWER_WATT, - ), - Pressure: SensorEntityDescription( - key="pressure", - device_class=SensorDeviceClass.PRESSURE, - state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=PRESSURE_HPA, - ), - Temperature: SensorEntityDescription( - key="temperature", - device_class=SensorDeviceClass.TEMPERATURE, - state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=TEMP_CELSIUS, - ), -} - -SENSOR_DESCRIPTIONS = [ DeconzSensorDescription( - key="temperature", - required_attr="secondary_temperature", - value_fn=lambda device: device.secondary_temperature, + key="secondary_temperature", + value_fn=lambda device: device.secondary_temperature, # type: ignore[no-any-return] suffix="Temperature", update_key="temperature", device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=TEMP_CELSIUS, ), - DeconzSensorDescription( - key="air_quality_ppb", - required_attr="air_quality_ppb", - value_fn=lambda device: device.air_quality_ppb, - suffix="PPB", - update_key="airqualityppb", - device_class=SensorDeviceClass.AQI, - state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION, - ), ] @@ -185,42 +236,33 @@ def async_add_sensor( Create DeconzBattery if sensor has a battery attribute. Create DeconzSensor if not a battery, switch or thermostat and not a binary sensor. """ - entities: list[DeconzBattery | DeconzSensor | DeconzPropertySensor] = [] + entities: list[DeconzSensor] = [] for sensor in sensors: if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"): continue - if sensor.battery is not None: - battery_handler.remove_tracker(sensor) - - known_batteries = set(gateway.entities[DOMAIN]) - new_battery = DeconzBattery(sensor, gateway) - if new_battery.unique_id not in known_batteries: - entities.append(new_battery) - - else: + if sensor.battery is None: battery_handler.create_tracker(sensor) - if ( - isinstance(sensor, DECONZ_SENSORS) - and not isinstance(sensor, Thermostat) - and sensor.unique_id not in gateway.entities[DOMAIN] + known_entities = set(gateway.entities[DOMAIN]) + for description in ( + ENTITY_DESCRIPTIONS.get(type(sensor), []) + SENSOR_DESCRIPTIONS ): - entities.append(DeconzSensor(sensor, gateway)) - - known_sensor_entities = set(gateway.entities[DOMAIN]) - for sensor_description in SENSOR_DESCRIPTIONS: - if not hasattr( - sensor, sensor_description.required_attr - ) or not sensor_description.value_fn(sensor): + if ( + not hasattr(sensor, description.key) + or description.value_fn(sensor) is None + ): continue - new_sensor = DeconzPropertySensor(sensor, gateway, sensor_description) - if new_sensor.unique_id not in known_sensor_entities: - entities.append(new_sensor) + new_entity = DeconzSensor(sensor, gateway, description) + if new_entity.unique_id not in known_entities: + entities.append(new_entity) + + if description.key == "battery": + battery_handler.remove_tracker(sensor) if entities: async_add_entities(entities) @@ -243,30 +285,66 @@ class DeconzSensor(DeconzDevice, SensorEntity): TYPE = DOMAIN _device: PydeconzSensor + entity_description: DeconzSensorDescription - def __init__(self, device: PydeconzSensor, gateway: DeconzGateway) -> None: - """Initialize deCONZ binary sensor.""" + def __init__( + self, + device: PydeconzSensor, + gateway: DeconzGateway, + description: DeconzSensorDescription, + ) -> None: + """Initialize deCONZ sensor.""" + self.entity_description = description super().__init__(device, gateway) - if entity_description := ENTITY_DESCRIPTIONS.get(type(device)): - self.entity_description = entity_description + if description.suffix: + self._attr_name = f"{device.name} {description.suffix}" + + self._update_keys = {description.update_key, "reachable"} + if self.entity_description.key in PROVIDES_EXTRA_ATTRIBUTES: + self._update_keys.update({"on", "state"}) + + @property + def unique_id(self) -> str: + """Return a unique identifier for this device.""" + if ( + self.entity_description.key == "battery" + and self._device.manufacturer == "Danfoss" + and self._device.model_id + in [ + "0x8030", + "0x8031", + "0x8034", + "0x8035", + ] + ): + return f"{super().unique_id}-battery" + if self.entity_description.suffix: + return f"{self.serial}-{self.entity_description.suffix.lower()}" + return super().unique_id @callback def async_update_callback(self) -> None: """Update the sensor's state.""" - keys = {"on", "reachable", "state"} - if self._device.changed_keys.intersection(keys): + if self._device.changed_keys.intersection(self._update_keys): super().async_update_callback() @property - def native_value(self) -> StateType: + def native_value(self) -> StateType | datetime: """Return the state of the sensor.""" - return self._device.state # type: ignore[no-any-return] + if self.entity_description.device_class is SensorDeviceClass.TIMESTAMP: + return dt_util.parse_datetime( + self.entity_description.value_fn(self._device) + ) + return self.entity_description.value_fn(self._device) @property def extra_state_attributes(self) -> dict[str, bool | float | int | None]: """Return the state attributes of the sensor.""" - attr = {} + attr: dict[str, bool | float | int | None] = {} + + if self.entity_description.key not in PROVIDES_EXTRA_ATTRIBUTES: + return attr if self._device.on is not None: attr[ATTR_ON] = self._device.on @@ -292,93 +370,7 @@ def extra_state_attributes(self) -> dict[str, bool | float | int | None]: attr[ATTR_CURRENT] = self._device.current attr[ATTR_VOLTAGE] = self._device.voltage - return attr - - -class DeconzPropertySensor(DeconzDevice, SensorEntity): - """Representation of a deCONZ secondary attribute sensor.""" - - TYPE = DOMAIN - _device: PydeconzSensor - entity_description: DeconzSensorDescription - - def __init__( - self, - device: PydeconzSensor, - gateway: DeconzGateway, - description: DeconzSensorDescription, - ) -> None: - """Initialize deCONZ sensor.""" - self.entity_description = description - super().__init__(device, gateway) - - self._attr_name = f"{self._device.name} {description.suffix}" - self._update_keys = {description.update_key, "reachable"} - - @property - def unique_id(self) -> str: - """Return a unique identifier for this device.""" - return f"{self.serial}-{self.entity_description.suffix.lower()}" - - @callback - def async_update_callback(self) -> None: - """Update the sensor's state.""" - if self._device.changed_keys.intersection(self._update_keys): - super().async_update_callback() - - @property - def native_value(self) -> StateType: - """Return the state of the sensor.""" - return self.entity_description.value_fn(self._device) - - -class DeconzBattery(DeconzDevice, SensorEntity): - """Battery class for when a device is only represented as an event.""" - - TYPE = DOMAIN - _device: PydeconzSensor - - def __init__(self, device: PydeconzSensor, gateway: DeconzGateway) -> None: - """Initialize deCONZ battery level sensor.""" - super().__init__(device, gateway) - - self.entity_description = ENTITY_DESCRIPTIONS[Battery] - self._attr_name = f"{self._device.name} Battery Level" - - @callback - def async_update_callback(self) -> None: - """Update the battery's state, if needed.""" - keys = {"battery", "reachable"} - if self._device.changed_keys.intersection(keys): - super().async_update_callback() - - @property - def unique_id(self) -> str: - """Return a unique identifier for this device. - - Normally there should only be one battery sensor per device from deCONZ. - With specific Danfoss devices each endpoint can report its own battery state. - """ - if self._device.manufacturer == "Danfoss" and self._device.model_id in [ - "0x8030", - "0x8031", - "0x8034", - "0x8035", - ]: - return f"{super().unique_id}-battery" - return f"{self.serial}-battery" - - @property - def native_value(self) -> StateType: - """Return the state of the battery.""" - return self._device.battery # type: ignore[no-any-return] - - @property - def extra_state_attributes(self) -> dict[str, str]: - """Return the state attributes of the battery.""" - attr = {} - - if isinstance(self._device, Switch): + elif isinstance(self._device, Switch): for event in self.gateway.events: if self._device == event.device: attr[ATTR_EVENT_ID] = event.event_id diff --git a/tests/components/deconz/test_climate.py b/tests/components/deconz/test_climate.py index 3febbae510b390..a21d981900c88c 100644 --- a/tests/components/deconz/test_climate.py +++ b/tests/components/deconz/test_climate.py @@ -111,7 +111,7 @@ async def test_simple_climate_device(hass, aioclient_mock, mock_deconz_websocket assert climate_thermostat.attributes["current_temperature"] == 21.0 assert climate_thermostat.attributes["temperature"] == 21.0 assert climate_thermostat.attributes["locked"] is True - assert hass.states.get("sensor.thermostat_battery_level").state == "59" + assert hass.states.get("sensor.thermostat_battery").state == "59" # Event signals thermostat configured off @@ -211,7 +211,7 @@ async def test_climate_device_without_cooling_support( assert climate_thermostat.attributes["current_temperature"] == 22.6 assert climate_thermostat.attributes["temperature"] == 22.0 assert hass.states.get("sensor.thermostat") is None - assert hass.states.get("sensor.thermostat_battery_level").state == "100" + assert hass.states.get("sensor.thermostat_battery").state == "100" assert hass.states.get("climate.presence_sensor") is None assert hass.states.get("climate.clip_thermostat") is None @@ -385,7 +385,7 @@ async def test_climate_device_with_cooling_support( ] assert climate_thermostat.attributes["current_temperature"] == 23.2 assert climate_thermostat.attributes["temperature"] == 22.2 - assert hass.states.get("sensor.zen_01_battery_level").state == "25" + assert hass.states.get("sensor.zen_01_battery").state == "25" # Event signals thermostat state cool @@ -787,4 +787,4 @@ async def test_add_new_climate_device(hass, aioclient_mock, mock_deconz_websocke assert len(hass.states.async_all()) == 2 assert hass.states.get("climate.thermostat").state == HVAC_MODE_AUTO - assert hass.states.get("sensor.thermostat_battery_level").state == "100" + assert hass.states.get("sensor.thermostat_battery").state == "100" diff --git a/tests/components/deconz/test_deconz_event.py b/tests/components/deconz/test_deconz_event.py index 76babab36bebab..1d3c4f7a811f57 100644 --- a/tests/components/deconz/test_deconz_event.py +++ b/tests/components/deconz/test_deconz_event.py @@ -80,9 +80,9 @@ async def test_deconz_events(hass, aioclient_mock, mock_deconz_websocket): assert ( len(async_entries_for_config_entry(device_registry, config_entry.entry_id)) == 7 ) - assert hass.states.get("sensor.switch_2_battery_level").state == "100" - assert hass.states.get("sensor.switch_3_battery_level").state == "100" - assert hass.states.get("sensor.switch_4_battery_level").state == "100" + assert hass.states.get("sensor.switch_2_battery").state == "100" + assert hass.states.get("sensor.switch_3_battery").state == "100" + assert hass.states.get("sensor.switch_4_battery").state == "100" captured_events = async_capture_events(hass, CONF_DECONZ_EVENT) diff --git a/tests/components/deconz/test_device_trigger.py b/tests/components/deconz/test_device_trigger.py index 15e63a6a81f22b..4ae8fd32e45bd1 100644 --- a/tests/components/deconz/test_device_trigger.py +++ b/tests/components/deconz/test_device_trigger.py @@ -120,7 +120,7 @@ async def test_get_triggers(hass, aioclient_mock): { CONF_DEVICE_ID: device.id, CONF_DOMAIN: SENSOR_DOMAIN, - ATTR_ENTITY_ID: "sensor.tradfri_on_off_switch_battery_level", + ATTR_ENTITY_ID: "sensor.tradfri_on_off_switch_battery", CONF_PLATFORM: "device", CONF_TYPE: ATTR_BATTERY_LEVEL, }, diff --git a/tests/components/deconz/test_sensor.py b/tests/components/deconz/test_sensor.py index ee66a159c1802d..bd51bab44e4e25 100644 --- a/tests/components/deconz/test_sensor.py +++ b/tests/components/deconz/test_sensor.py @@ -3,12 +3,17 @@ from datetime import timedelta from unittest.mock import patch +import pytest + from homeassistant.components.deconz.const import CONF_ALLOW_CLIP_SENSOR -from homeassistant.components.deconz.sensor import ATTR_DAYLIGHT -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, SensorDeviceClass +from homeassistant.components.sensor import ( + DOMAIN as SENSOR_DOMAIN, + SensorDeviceClass, + SensorStateClass, +) from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY from homeassistant.const import ATTR_DEVICE_CLASS, STATE_UNAVAILABLE -from homeassistant.helpers import entity_registry as er +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.entity import EntityCategory from homeassistant.util import dt @@ -23,159 +28,639 @@ async def test_no_sensors(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_sensors(hass, aioclient_mock, mock_deconz_websocket): - """Test successful creation of sensor entities.""" - data = { - "sensors": { - "1": { - "name": "Light level sensor", - "type": "ZHALightLevel", - "state": {"daylight": 6955, "lightlevel": 30000, "dark": False}, - "config": {"on": True, "reachable": True, "temperature": 10}, - "uniqueid": "00:00:00:00:00:00:00:00-00", +TEST_DATA = [ + ( # Air quality sensor + { + "config": { + "on": True, + "reachable": True, }, - "2": { - "name": "Presence sensor", - "type": "ZHAPresence", - "state": {"presence": False}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:01-00", + "ep": 2, + "etag": "c2d2e42396f7c78e11e46c66e2ec0200", + "lastseen": "2020-11-20T22:48Z", + "manufacturername": "BOSCH", + "modelid": "AIR", + "name": "BOSCH Air quality sensor", + "state": { + "airquality": "poor", + "airqualityppb": 809, + "lastupdated": "2020-11-20T22:48:00.209", }, - "3": { - "name": "Switch 1", - "type": "ZHASwitch", - "state": {"buttonevent": 1000}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:02-00", + "swversion": "20200402", + "type": "ZHAAirQuality", + "uniqueid": "00:12:4b:00:14:4d:00:07-02-fdef", + }, + { + "entity_count": 2, + "device_count": 3, + "entity_id": "sensor.bosch_air_quality_sensor", + "unique_id": "00:12:4b:00:14:4d:00:07-02-fdef", + "state": "poor", + "entity_category": None, + "device_class": None, + "state_class": SensorStateClass.MEASUREMENT, + "attributes": { + "state_class": "measurement", + "friendly_name": "BOSCH Air quality sensor", }, - "4": { - "name": "Switch 2", - "type": "ZHASwitch", - "state": {"buttonevent": 1000}, - "config": {"battery": 100}, - "uniqueid": "00:00:00:00:00:00:00:03-00", + "websocket_event": {"state": {"airquality": "excellent"}}, + "next_state": "excellent", + }, + ), + ( # Air quality PPB sensor + { + "config": { + "on": True, + "reachable": True, }, - "5": { - "name": "Power sensor", - "type": "ZHAPower", - "state": {"current": 2, "power": 6, "voltage": 3}, - "config": {"reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:05-00", + "ep": 2, + "etag": "c2d2e42396f7c78e11e46c66e2ec0200", + "lastseen": "2020-11-20T22:48Z", + "manufacturername": "BOSCH", + "modelid": "AIR", + "name": "BOSCH Air quality sensor", + "state": { + "airquality": "poor", + "airqualityppb": 809, + "lastupdated": "2020-11-20T22:48:00.209", }, - "6": { - "name": "Consumption sensor", - "type": "ZHAConsumption", - "state": {"consumption": 2, "power": 6}, - "config": {"reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:06-00", + "swversion": "20200402", + "type": "ZHAAirQuality", + "uniqueid": "00:12:4b:00:14:4d:00:07-02-fdef", + }, + { + "entity_count": 2, + "device_count": 3, + "entity_id": "sensor.bosch_air_quality_sensor_ppb", + "unique_id": "00:12:4b:00:14:4d:00:07-ppb", + "state": "809", + "entity_category": None, + "device_class": SensorDeviceClass.AQI, + "state_class": SensorStateClass.MEASUREMENT, + "attributes": { + "state_class": "measurement", + "unit_of_measurement": "ppb", + "device_class": "aqi", + "friendly_name": "BOSCH Air quality sensor PPB", }, - "7": { - "id": "CLIP light sensor id", - "name": "CLIP light level sensor", - "type": "CLIPLightLevel", - "state": {"lightlevel": 30000}, - "config": {"reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:07-00", + "websocket_event": {"state": {"airqualityppb": 1000}}, + "next_state": "1000", + }, + ), + ( # Battery sensor + { + "config": { + "alert": "none", + "on": True, + "reachable": True, }, - } - } - - with patch.dict(DECONZ_WEB_REQUEST, data): - config_entry = await setup_deconz_integration(hass, aioclient_mock) + "ep": 1, + "etag": "23a8659f1cb22df2f51bc2da0e241bb4", + "manufacturername": "IKEA of Sweden", + "modelid": "FYRTUR block-out roller blind", + "name": "FYRTUR block-out roller blind", + "state": { + "battery": 100, + "lastupdated": "none", + }, + "swversion": "2.2.007", + "type": "ZHABattery", + "uniqueid": "00:0d:6f:ff:fe:01:23:45-01-0001", + }, + { + "entity_count": 1, + "device_count": 3, + "entity_id": "sensor.fyrtur_block_out_roller_blind_battery", + "unique_id": "00:0d:6f:ff:fe:01:23:45-battery", + "state": "100", + "entity_category": EntityCategory.DIAGNOSTIC, + "device_class": SensorDeviceClass.BATTERY, + "state_class": SensorStateClass.MEASUREMENT, + "attributes": { + "state_class": "measurement", + "on": True, + "unit_of_measurement": "%", + "device_class": "battery", + "friendly_name": "FYRTUR block-out roller blind Battery", + }, + "websocket_event": {"state": {"battery": 50}}, + "next_state": "50", + }, + ), + ( # Consumption sensor + { + "config": {"on": True, "reachable": True}, + "ep": 1, + "etag": "a99e5bc463d15c23af7e89946e784cca", + "manufacturername": "Heiman", + "modelid": "SmartPlug", + "name": "Consumption 15", + "state": { + "consumption": 11342, + "lastupdated": "2018-03-12T19:19:08", + "power": 123, + }, + "type": "ZHAConsumption", + "uniqueid": "00:0d:6f:00:0b:7a:64:29-01-0702", + }, + { + "entity_count": 1, + "device_count": 3, + "entity_id": "sensor.consumption_15", + "unique_id": "00:0d:6f:00:0b:7a:64:29-01-0702", + "state": "11.342", + "entity_category": None, + "device_class": SensorDeviceClass.ENERGY, + "state_class": SensorStateClass.TOTAL_INCREASING, + "attributes": { + "state_class": "total_increasing", + "on": True, + "power": 123, + "unit_of_measurement": "kWh", + "device_class": "energy", + "friendly_name": "Consumption 15", + }, + "websocket_event": {"state": {"consumption": 10000}}, + "next_state": "10.0", + }, + ), + ( # Daylight sensor + { + "config": { + "configured": True, + "on": True, + "sunriseoffset": 30, + "sunsetoffset": -30, + }, + "etag": "55047cf652a7e594d0ee7e6fae01dd38", + "manufacturername": "Philips", + "modelid": "PHDL00", + "name": "Daylight", + "state": { + "daylight": True, + "lastupdated": "2018-03-24T17:26:12", + "status": 170, + }, + "swversion": "1.0", + "type": "Daylight", + }, + { + "enable_entity": True, + "entity_count": 1, + "device_count": 2, + "entity_id": "sensor.daylight", + "unique_id": "", + "state": "solar_noon", + "entity_category": None, + "device_class": None, + "state_class": None, + "attributes": { + "on": True, + "daylight": True, + "icon": "mdi:white-balance-sunny", + "friendly_name": "Daylight", + }, + "websocket_event": {"state": {"status": 210}}, + "next_state": "dusk", + }, + ), + ( # Generic status sensor + { + "config": { + "on": True, + "reachable": True, + }, + "etag": "aacc83bc7d6e4af7e44014e9f776b206", + "manufacturername": "Phoscon", + "modelid": "PHOSCON_FSM_STATE", + "name": "FSM_STATE Motion stair", + "state": { + "lastupdated": "2019-04-24T00:00:25", + "status": 0, + }, + "swversion": "1.0", + "type": "CLIPGenericStatus", + "uniqueid": "fsm-state-1520195376277", + }, + { + "entity_count": 1, + "device_count": 2, + "entity_id": "sensor.fsm_state_motion_stair", + "unique_id": "fsm-state-1520195376277", + "state": "0", + "entity_category": None, + "device_class": None, + "state_class": None, + "attributes": { + "on": True, + "friendly_name": "FSM_STATE Motion stair", + }, + "websocket_event": {"state": {"status": 1}}, + "next_state": "1", + }, + ), + ( # Humidity sensor + { + "config": { + "battery": 100, + "offset": 0, + "on": True, + "reachable": True, + }, + "ep": 1, + "etag": "1220e5d026493b6e86207993703a8a71", + "manufacturername": "LUMI", + "modelid": "lumi.weather", + "name": "Mi temperature 1", + "state": { + "humidity": 3555, + "lastupdated": "2019-05-05T14:39:00", + }, + "swversion": "20161129", + "type": "ZHAHumidity", + "uniqueid": "00:15:8d:00:02:45:dc:53-01-0405", + }, + { + "entity_count": 2, + "device_count": 3, + "entity_id": "sensor.mi_temperature_1", + "unique_id": "00:15:8d:00:02:45:dc:53-01-0405", + "state": "35.5", + "entity_category": None, + "device_class": SensorDeviceClass.HUMIDITY, + "state_class": SensorStateClass.MEASUREMENT, + "attributes": { + "state_class": "measurement", + "on": True, + "unit_of_measurement": "%", + "device_class": "humidity", + "friendly_name": "Mi temperature 1", + }, + "websocket_event": {"state": {"humidity": 1000}}, + "next_state": "10.0", + }, + ), + ( # Light level sensor + { + "config": { + "alert": "none", + "battery": 100, + "ledindication": False, + "on": True, + "pending": [], + "reachable": True, + "tholddark": 12000, + "tholdoffset": 7000, + "usertest": False, + }, + "ep": 2, + "etag": "5cfb81765e86aa53ace427cfd52c6d52", + "manufacturername": "Philips", + "modelid": "SML001", + "name": "Motion sensor 4", + "state": { + "dark": True, + "daylight": False, + "lastupdated": "2019-05-05T14:37:06", + "lightlevel": 6955, + "lux": 5, + }, + "swversion": "6.1.0.18912", + "type": "ZHALightLevel", + "uniqueid": "00:17:88:01:03:28:8c:9b-02-0400", + }, + { + "entity_count": 2, + "device_count": 3, + "entity_id": "sensor.motion_sensor_4", + "unique_id": "00:17:88:01:03:28:8c:9b-02-0400", + "state": "5.0", + "entity_category": None, + "device_class": SensorDeviceClass.ILLUMINANCE, + "state_class": None, + "attributes": { + "on": True, + "dark": True, + "daylight": False, + "unit_of_measurement": "lx", + "device_class": "illuminance", + "friendly_name": "Motion sensor 4", + }, + "websocket_event": {"state": {"lightlevel": 1000}}, + "next_state": "1.3", + }, + ), + ( # Power sensor + { + "config": { + "on": True, + "reachable": True, + }, + "ep": 1, + "etag": "96e71c7db4685b334d3d0decc3f11868", + "manufacturername": "Heiman", + "modelid": "SmartPlug", + "name": "Power 16", + "state": { + "current": 34, + "lastupdated": "2018-03-12T19:22:13", + "power": 64, + "voltage": 231, + }, + "type": "ZHAPower", + "uniqueid": "00:0d:6f:00:0b:7a:64:29-01-0b04", + }, + { + "entity_count": 1, + "device_count": 3, + "entity_id": "sensor.power_16", + "unique_id": "00:0d:6f:00:0b:7a:64:29-01-0b04", + "state": "64", + "entity_category": None, + "device_class": SensorDeviceClass.POWER, + "state_class": SensorStateClass.MEASUREMENT, + "attributes": { + "state_class": "measurement", + "on": True, + "current": 34, + "voltage": 231, + "unit_of_measurement": "W", + "device_class": "power", + "friendly_name": "Power 16", + }, + "websocket_event": {"state": {"power": 1000}}, + "next_state": "1000", + }, + ), + ( # Pressure sensor + { + "config": { + "battery": 100, + "on": True, + "reachable": True, + }, + "ep": 1, + "etag": "1220e5d026493b6e86207993703a8a71", + "manufacturername": "LUMI", + "modelid": "lumi.weather", + "name": "Mi temperature 1", + "state": { + "lastupdated": "2019-05-05T14:39:00", + "pressure": 1010, + }, + "swversion": "20161129", + "type": "ZHAPressure", + "uniqueid": "00:15:8d:00:02:45:dc:53-01-0403", + }, + { + "entity_count": 2, + "device_count": 3, + "entity_id": "sensor.mi_temperature_1", + "unique_id": "00:15:8d:00:02:45:dc:53-01-0403", + "state": "1010", + "entity_category": None, + "device_class": SensorDeviceClass.PRESSURE, + "state_class": SensorStateClass.MEASUREMENT, + "attributes": { + "state_class": "measurement", + "on": True, + "unit_of_measurement": "hPa", + "device_class": "pressure", + "friendly_name": "Mi temperature 1", + }, + "websocket_event": {"state": {"pressure": 500}}, + "next_state": "500", + }, + ), + ( # Temperature sensor + { + "config": { + "battery": 100, + "offset": 0, + "on": True, + "reachable": True, + }, + "ep": 1, + "etag": "1220e5d026493b6e86207993703a8a71", + "manufacturername": "LUMI", + "modelid": "lumi.weather", + "name": "Mi temperature 1", + "state": { + "lastupdated": "2019-05-05T14:39:00", + "temperature": 2182, + }, + "swversion": "20161129", + "type": "ZHATemperature", + "uniqueid": "00:15:8d:00:02:45:dc:53-01-0402", + }, + { + "entity_count": 2, + "device_count": 3, + "entity_id": "sensor.mi_temperature_1", + "unique_id": "00:15:8d:00:02:45:dc:53-01-0402", + "state": "21.8", + "entity_category": None, + "device_class": SensorDeviceClass.TEMPERATURE, + "state_class": SensorStateClass.MEASUREMENT, + "attributes": { + "state_class": "measurement", + "on": True, + "unit_of_measurement": "°C", + "device_class": "temperature", + "friendly_name": "Mi temperature 1", + }, + "websocket_event": {"state": {"temperature": 1800}}, + "next_state": "18.0", + }, + ), + ( # Time sensor + { + "config": { + "battery": 40, + "on": True, + "reachable": True, + }, + "ep": 1, + "etag": "28e796678d9a24712feef59294343bb6", + "lastseen": "2020-11-22T11:26Z", + "manufacturername": "Danfoss", + "modelid": "eTRV0100", + "name": "eTRV Séjour", + "state": { + "lastset": "2020-11-19T08:07:08Z", + "lastupdated": "2020-11-22T10:51:03.444", + "localtime": "2020-11-22T10:51:01", + "utc": "2020-11-22T10:51:01Z", + }, + "swversion": "20200429", + "type": "ZHATime", + "uniqueid": "cc:cc:cc:ff:fe:38:4d:b3-01-000a", + }, + { + "entity_count": 2, + "device_count": 3, + "entity_id": "sensor.etrv_sejour", + "unique_id": "cc:cc:cc:ff:fe:38:4d:b3-01-000a", + "state": "2020-11-19T08:07:08+00:00", + "entity_category": None, + "device_class": SensorDeviceClass.TIMESTAMP, + "state_class": SensorStateClass.TOTAL_INCREASING, + "attributes": { + "state_class": "total_increasing", + "device_class": "timestamp", + "friendly_name": "eTRV Séjour", + }, + "websocket_event": {"state": {"lastset": "2020-12-14T10:12:14Z"}}, + "next_state": "2020-12-14T10:12:14+00:00", + }, + ), + ( # Secondary temperature sensor + { + "config": { + "battery": 100, + "on": True, + "reachable": True, + "temperature": 2600, + }, + "ep": 1, + "etag": "18c0f3c2100904e31a7f938db2ba9ba9", + "manufacturername": "dresden elektronik", + "modelid": "lumi.sensor_motion.aq2", + "name": "Alarm 10", + "state": { + "alarm": False, + "lastupdated": "none", + "lowbattery": None, + "tampered": None, + }, + "swversion": "20170627", + "type": "ZHAAlarm", + "uniqueid": "00:15:8d:00:02:b5:d1:80-01-0500", + }, + { + "entity_count": 3, + "device_count": 3, + "entity_id": "sensor.alarm_10_temperature", + "unique_id": "00:15:8d:00:02:b5:d1:80-temperature", + "state": "26.0", + "entity_category": None, + "device_class": SensorDeviceClass.TEMPERATURE, + "state_class": SensorStateClass.MEASUREMENT, + "attributes": { + "state_class": "measurement", + "unit_of_measurement": "°C", + "device_class": "temperature", + "friendly_name": "Alarm 10 Temperature", + }, + "websocket_event": {"state": {"temperature": 1800}}, + "next_state": "26.0", + }, + ), + ( # Battery from switch + { + "config": { + "battery": 90, + "group": "201", + "on": True, + "reachable": True, + }, + "ep": 2, + "etag": "233ae541bbb7ac98c42977753884b8d2", + "manufacturername": "Philips", + "mode": 1, + "modelid": "RWL021", + "name": "Dimmer switch 3", + "state": { + "buttonevent": 1002, + "lastupdated": "2019-04-28T20:29:13", + }, + "swversion": "5.45.1.17846", + "type": "ZHASwitch", + "uniqueid": "00:17:88:01:02:0e:32:a3-02-fc00", + }, + { + "entity_count": 1, + "device_count": 3, + "entity_id": "sensor.dimmer_switch_3_battery", + "unique_id": "00:17:88:01:02:0e:32:a3-battery", + "state": "90", + "entity_category": EntityCategory.DIAGNOSTIC, + "device_class": SensorDeviceClass.BATTERY, + "state_class": SensorStateClass.MEASUREMENT, + "attributes": { + "state_class": "measurement", + "on": True, + "event_id": "dimmer_switch_3", + "unit_of_measurement": "%", + "device_class": "battery", + "friendly_name": "Dimmer switch 3 Battery", + }, + "websocket_event": {"config": {"battery": 80}}, + "next_state": "80", + }, + ), +] - assert len(hass.states.async_all()) == 6 +@pytest.mark.parametrize("sensor_data, expected", TEST_DATA) +async def test_sensors( + hass, aioclient_mock, mock_deconz_websocket, sensor_data, expected +): + """Test successful creation of sensor entities.""" ent_reg = er.async_get(hass) + dev_reg = dr.async_get(hass) - light_level_sensor = hass.states.get("sensor.light_level_sensor") - assert light_level_sensor.state == "999.8" - assert ( - light_level_sensor.attributes[ATTR_DEVICE_CLASS] - == SensorDeviceClass.ILLUMINANCE - ) - assert light_level_sensor.attributes[ATTR_DAYLIGHT] == 6955 + with patch.dict(DECONZ_WEB_REQUEST, {"sensors": {"1": sensor_data}}): + config_entry = await setup_deconz_integration( + hass, aioclient_mock, options={CONF_ALLOW_CLIP_SENSOR: True} + ) - light_level_temp = hass.states.get("sensor.light_level_sensor_temperature") - assert light_level_temp.state == "0.1" - assert ( - light_level_temp.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.TEMPERATURE - ) + # Enable in entity registry + if expected.get("enable_entity"): + ent_reg.async_update_entity(entity_id=expected["entity_id"], disabled_by=None) + await hass.async_block_till_done() + + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(seconds=RELOAD_AFTER_UPDATE_DELAY + 1), + ) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == expected["entity_count"] - assert not hass.states.get("sensor.presence_sensor") - assert not hass.states.get("sensor.switch_1") - assert not hass.states.get("sensor.switch_1_battery_level") - assert not hass.states.get("sensor.switch_2") + # Verify entity state + sensor = hass.states.get(expected["entity_id"]) + assert sensor.state == expected["state"] + assert sensor.attributes.get(ATTR_DEVICE_CLASS) == expected["device_class"] + assert sensor.attributes == expected["attributes"] - switch_2_battery_level = hass.states.get("sensor.switch_2_battery_level") - assert switch_2_battery_level.state == "100" + # Verify entity registry assert ( - switch_2_battery_level.attributes[ATTR_DEVICE_CLASS] - == SensorDeviceClass.BATTERY + ent_reg.async_get(expected["entity_id"]).entity_category + is expected["entity_category"] ) + ent_reg_entry = ent_reg.async_get(expected["entity_id"]) + assert ent_reg_entry.entity_category is expected["entity_category"] + assert ent_reg_entry.unique_id == expected["unique_id"] + + # Verify device registry assert ( - ent_reg.async_get("sensor.switch_2_battery_level").entity_category - == EntityCategory.DIAGNOSTIC + len(dr.async_entries_for_config_entry(dev_reg, config_entry.entry_id)) + == expected["device_count"] ) - assert not hass.states.get("sensor.daylight_sensor") - - power_sensor = hass.states.get("sensor.power_sensor") - assert power_sensor.state == "6" - assert power_sensor.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.POWER - - consumption_sensor = hass.states.get("sensor.consumption_sensor") - assert consumption_sensor.state == "0.002" - assert consumption_sensor.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.ENERGY - - assert not hass.states.get("sensor.clip_light_level_sensor") - - # Event signals new light level - - event_changed_sensor = { - "t": "event", - "e": "changed", - "r": "sensors", - "id": "1", - "state": {"lightlevel": 2000}, - } - await mock_deconz_websocket(data=event_changed_sensor) + # Change state - assert hass.states.get("sensor.light_level_sensor").state == "1.6" - - # Event signals new temperature value - - event_changed_sensor = { - "t": "event", - "e": "changed", - "r": "sensors", - "id": "1", - "config": {"temperature": 100}, - } - await mock_deconz_websocket(data=event_changed_sensor) - - assert hass.states.get("sensor.light_level_sensor_temperature").state == "1.0" - - # Event signals new battery level - - event_changed_sensor = { - "t": "event", - "e": "changed", - "r": "sensors", - "id": "4", - "config": {"battery": 75}, - } + event_changed_sensor = {"t": "event", "e": "changed", "r": "sensors", "id": "1"} + event_changed_sensor |= expected["websocket_event"] await mock_deconz_websocket(data=event_changed_sensor) - - assert hass.states.get("sensor.switch_2_battery_level").state == "75" + await hass.async_block_till_done() + assert hass.states.get(expected["entity_id"]).state == expected["next_state"] # Unload entry await hass.config_entries.async_unload(config_entry.entry_id) - - states = hass.states.async_all() - assert len(states) == 6 - for state in states: - assert state.state == STATE_UNAVAILABLE + assert hass.states.get(expected["entity_id"]).state == STATE_UNAVAILABLE # Remove entry @@ -184,6 +669,28 @@ async def test_sensors(hass, aioclient_mock, mock_deconz_websocket): assert len(hass.states.async_all()) == 0 +async def test_not_allow_clip_sensor(hass, aioclient_mock): + """Test that CLIP sensors are not allowed.""" + data = { + "sensors": { + "1": { + "name": "CLIP temperature sensor", + "type": "CLIPTemperature", + "state": {"temperature": 2600}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:02-00", + }, + } + } + + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration( + hass, aioclient_mock, options={CONF_ALLOW_CLIP_SENSOR: False} + ) + + assert len(hass.states.async_all()) == 0 + + async def test_allow_clip_sensors(hass, aioclient_mock): """Test that CLIP sensors can be allowed.""" data = { @@ -295,7 +802,7 @@ async def test_add_battery_later(hass, aioclient_mock, mock_deconz_websocket): await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 0 - assert not hass.states.get("sensor.switch_1_battery_level") + assert not hass.states.get("sensor.switch_1_battery") event_changed_sensor = { "t": "event", @@ -309,10 +816,11 @@ async def test_add_battery_later(hass, aioclient_mock, mock_deconz_websocket): assert len(hass.states.async_all()) == 1 - assert hass.states.get("sensor.switch_1_battery_level").state == "50" + assert hass.states.get("sensor.switch_1_battery").state == "50" -async def test_special_danfoss_battery_creation(hass, aioclient_mock): +@pytest.mark.parametrize("model_id", ["0x8030", "0x8031", "0x8034", "0x8035"]) +async def test_special_danfoss_battery_creation(hass, aioclient_mock, model_id): """Test the special Danfoss battery creation works. Normally there should only be one battery sensor per device from deCONZ. @@ -334,7 +842,7 @@ async def test_special_danfoss_battery_creation(hass, aioclient_mock): "etag": "982d9acc38bee5b251e24a9be26558e4", "lastseen": "2021-02-15T12:23Z", "manufacturername": "Danfoss", - "modelid": "0x8030", + "modelid": model_id, "name": "0x8030", "state": { "lastupdated": "2021-02-15T12:23:07.994", @@ -359,7 +867,7 @@ async def test_special_danfoss_battery_creation(hass, aioclient_mock): "etag": "62f12749f9f51c950086aff37dd02b61", "lastseen": "2021-02-15T12:23Z", "manufacturername": "Danfoss", - "modelid": "0x8030", + "modelid": model_id, "name": "0x8030", "state": { "lastupdated": "2021-02-15T12:23:22.399", @@ -384,7 +892,7 @@ async def test_special_danfoss_battery_creation(hass, aioclient_mock): "etag": "f50061174bb7f18a3d95789bab8b646d", "lastseen": "2021-02-15T12:23Z", "manufacturername": "Danfoss", - "modelid": "0x8030", + "modelid": model_id, "name": "0x8030", "state": { "lastupdated": "2021-02-15T12:23:25.466", @@ -409,7 +917,7 @@ async def test_special_danfoss_battery_creation(hass, aioclient_mock): "etag": "eea97adf8ce1b971b8b6a3a31793f96b", "lastseen": "2021-02-15T12:23Z", "manufacturername": "Danfoss", - "modelid": "0x8030", + "modelid": model_id, "name": "0x8030", "state": { "lastupdated": "2021-02-15T12:23:41.939", @@ -434,7 +942,7 @@ async def test_special_danfoss_battery_creation(hass, aioclient_mock): "etag": "1f7cd1a5d66dc27ac5eb44b8c47362fb", "lastseen": "2021-02-15T12:23Z", "manufacturername": "Danfoss", - "modelid": "0x8030", + "modelid": model_id, "name": "0x8030", "state": {"lastupdated": "none", "on": False, "temperature": 2325}, "swversion": "YYYYMMDD", @@ -450,120 +958,6 @@ async def test_special_danfoss_battery_creation(hass, aioclient_mock): assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 5 -async def test_air_quality_sensor(hass, aioclient_mock): - """Test successful creation of air quality sensor entities.""" - data = { - "sensors": { - "0": { - "config": {"on": True, "reachable": True}, - "ep": 2, - "etag": "c2d2e42396f7c78e11e46c66e2ec0200", - "lastseen": "2020-11-20T22:48Z", - "manufacturername": "BOSCH", - "modelid": "AIR", - "name": "Air quality", - "state": { - "airquality": "poor", - "airqualityppb": 809, - "lastupdated": "2020-11-20T22:48:00.209", - }, - "swversion": "20200402", - "type": "ZHAAirQuality", - "uniqueid": "00:12:4b:00:14:4d:00:07-02-fdef", - } - } - } - with patch.dict(DECONZ_WEB_REQUEST, data): - await setup_deconz_integration(hass, aioclient_mock) - - assert len(hass.states.async_all()) == 2 - assert hass.states.get("sensor.air_quality").state == "poor" - assert hass.states.get("sensor.air_quality_ppb").state == "809" - - -async def test_daylight_sensor(hass, aioclient_mock): - """Test daylight sensor is disabled by default and when created has expected attributes.""" - data = { - "sensors": { - "0": { - "config": { - "configured": True, - "on": True, - "sunriseoffset": 30, - "sunsetoffset": -30, - }, - "etag": "55047cf652a7e594d0ee7e6fae01dd38", - "manufacturername": "Philips", - "modelid": "PHDL00", - "name": "Daylight sensor", - "state": { - "daylight": True, - "lastupdated": "2018-03-24T17:26:12", - "status": 170, - }, - "swversion": "1.0", - "type": "Daylight", - "uniqueid": "00:00:00:00:00:00:00:00-00", - } - } - } - with patch.dict(DECONZ_WEB_REQUEST, data): - await setup_deconz_integration(hass, aioclient_mock) - - assert len(hass.states.async_all()) == 0 - assert not hass.states.get("sensor.daylight_sensor") - - # Enable in entity registry - - entity_registry = er.async_get(hass) - entity_registry.async_update_entity( - entity_id="sensor.daylight_sensor", disabled_by=None - ) - await hass.async_block_till_done() - - async_fire_time_changed( - hass, - dt.utcnow() + timedelta(seconds=RELOAD_AFTER_UPDATE_DELAY + 1), - ) - await hass.async_block_till_done() - - assert len(hass.states.async_all()) == 1 - assert hass.states.get("sensor.daylight_sensor") - assert hass.states.get("sensor.daylight_sensor").attributes[ATTR_DAYLIGHT] - - -async def test_time_sensor(hass, aioclient_mock): - """Test successful creation of time sensor entities.""" - data = { - "sensors": { - "0": { - "config": {"battery": 40, "on": True, "reachable": True}, - "ep": 1, - "etag": "28e796678d9a24712feef59294343bb6", - "lastseen": "2020-11-22T11:26Z", - "manufacturername": "Danfoss", - "modelid": "eTRV0100", - "name": "Time", - "state": { - "lastset": "2020-11-19T08:07:08Z", - "lastupdated": "2020-11-22T10:51:03.444", - "localtime": "2020-11-22T10:51:01", - "utc": "2020-11-22T10:51:01Z", - }, - "swversion": "20200429", - "type": "ZHATime", - "uniqueid": "cc:cc:cc:ff:fe:38:4d:b3-01-000a", - } - } - } - with patch.dict(DECONZ_WEB_REQUEST, data): - await setup_deconz_integration(hass, aioclient_mock) - - assert len(hass.states.async_all()) == 2 - assert hass.states.get("sensor.time").state == "2020-11-19T08:07:08Z" - assert hass.states.get("sensor.time_battery_level").state == "40" - - async def test_unsupported_sensor(hass, aioclient_mock): """Test that unsupported sensors doesn't break anything.""" data = { From 23a22d18609498a73a01ded2a6eafcd67581a821 Mon Sep 17 00:00:00 2001 From: Amos Yuen Date: Tue, 15 Feb 2022 00:55:13 -0800 Subject: [PATCH 0652/1098] Override iotawatt coordinator request_refresh_debouncer to allow updates every 5s (#64892) Co-authored-by: J. Nick Koston --- homeassistant/components/iotawatt/coordinator.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/homeassistant/components/iotawatt/coordinator.py b/homeassistant/components/iotawatt/coordinator.py index 46a0ac81d90e83..6c97fc99169e21 100644 --- a/homeassistant/components/iotawatt/coordinator.py +++ b/homeassistant/components/iotawatt/coordinator.py @@ -10,12 +10,16 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.helpers import httpx_client +from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import CONNECTION_ERRORS _LOGGER = logging.getLogger(__name__) +# Matches iotwatt data log interval +REQUEST_REFRESH_DEFAULT_COOLDOWN = 5 + class IotawattUpdater(DataUpdateCoordinator): """Class to manage fetching update data from the IoTaWatt Energy Device.""" @@ -30,6 +34,12 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: logger=_LOGGER, name=entry.title, update_interval=timedelta(seconds=30), + request_refresh_debouncer=Debouncer( + hass, + _LOGGER, + cooldown=REQUEST_REFRESH_DEFAULT_COOLDOWN, + immediate=True, + ), ) self._last_run: datetime | None = None From cb03db8df4bf8b50945b36a4b0debcaaed1190a8 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 15 Feb 2022 10:37:41 +0100 Subject: [PATCH 0653/1098] Replace discord.py with nextcord (#66540) * Replace discord.py with nextcord * Typing tweak * Another pip check decrease :) --- .../components/discord/manifest.json | 2 +- homeassistant/components/discord/notify.py | 25 +++++++++++-------- requirements_all.txt | 6 ++--- script/pip_check | 2 +- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/discord/manifest.json b/homeassistant/components/discord/manifest.json index 40c176c5f82a58..02b31a3aa999af 100644 --- a/homeassistant/components/discord/manifest.json +++ b/homeassistant/components/discord/manifest.json @@ -2,7 +2,7 @@ "domain": "discord", "name": "Discord", "documentation": "https://www.home-assistant.io/integrations/discord", - "requirements": ["discord.py==1.7.3"], + "requirements": ["nextcord==2.0.0a8"], "codeowners": [], "iot_class": "cloud_push", "loggers": ["discord"] diff --git a/homeassistant/components/discord/notify.py b/homeassistant/components/discord/notify.py index e8b084a01ab3cc..41137e1a32cfe2 100644 --- a/homeassistant/components/discord/notify.py +++ b/homeassistant/components/discord/notify.py @@ -1,8 +1,10 @@ """Discord platform for notify component.""" +from __future__ import annotations + import logging import os.path -import discord +import nextcord import voluptuous as vol from homeassistant.components.notify import ( @@ -48,8 +50,8 @@ def file_exists(self, filename): async def async_send_message(self, message, **kwargs): """Login to Discord, send message to channel(s) and log out.""" - discord.VoiceClient.warn_nacl = False - discord_bot = discord.Client() + nextcord.VoiceClient.warn_nacl = False + discord_bot = nextcord.Client() images = None embedding = None @@ -59,13 +61,13 @@ async def async_send_message(self, message, **kwargs): data = kwargs.get(ATTR_DATA) or {} - embed = None + embeds: list[nextcord.Embed] = [] if ATTR_EMBED in data: embedding = data[ATTR_EMBED] fields = embedding.get(ATTR_EMBED_FIELDS) or [] if embedding: - embed = discord.Embed(**embedding) + embed = nextcord.Embed(**embedding) for field in fields: embed.add_field(**field) if ATTR_EMBED_FOOTER in embedding: @@ -74,11 +76,12 @@ async def async_send_message(self, message, **kwargs): embed.set_author(**embedding[ATTR_EMBED_AUTHOR]) if ATTR_EMBED_THUMBNAIL in embedding: embed.set_thumbnail(**embedding[ATTR_EMBED_THUMBNAIL]) + embeds.append(embed) if ATTR_IMAGES in data: images = [] - for image in data.get(ATTR_IMAGES): + for image in data.get(ATTR_IMAGES, []): image_exists = await self.hass.async_add_executor_job( self.file_exists, image ) @@ -95,15 +98,15 @@ async def async_send_message(self, message, **kwargs): channelid = int(channelid) try: channel = await discord_bot.fetch_channel(channelid) - except discord.NotFound: + except nextcord.NotFound: try: channel = await discord_bot.fetch_user(channelid) - except discord.NotFound: + except nextcord.NotFound: _LOGGER.warning("Channel not found for ID: %s", channelid) continue # Must create new instances of File for each channel. - files = [discord.File(image) for image in images] if images else None - await channel.send(message, files=files, embed=embed) - except (discord.HTTPException, discord.NotFound) as error: + files = [nextcord.File(image) for image in images] if images else [] + await channel.send(message, files=files, embeds=embeds) + except (nextcord.HTTPException, nextcord.NotFound) as error: _LOGGER.warning("Communication error: %s", error) await discord_bot.close() diff --git a/requirements_all.txt b/requirements_all.txt index 29248f86cbcdb4..b8a86347aa147f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -565,9 +565,6 @@ directv==0.4.0 # homeassistant.components.discogs discogs_client==2.3.0 -# homeassistant.components.discord -discord.py==1.7.3 - # homeassistant.components.steamist discovery30303==0.2.1 @@ -1099,6 +1096,9 @@ nexia==0.9.13 # homeassistant.components.nextcloud nextcloudmonitor==1.1.0 +# homeassistant.components.discord +nextcord==2.0.0a8 + # homeassistant.components.niko_home_control niko-home-control==0.2.1 diff --git a/script/pip_check b/script/pip_check index c30a7382f27335..af47f101fbba5b 100755 --- a/script/pip_check +++ b/script/pip_check @@ -3,7 +3,7 @@ PIP_CACHE=$1 # Number of existing dependency conflicts # Update if a PR resolve one! -DEPENDENCY_CONFLICTS=10 +DEPENDENCY_CONFLICTS=9 PIP_CHECK=$(pip check --cache-dir=$PIP_CACHE) LINE_COUNT=$(echo "$PIP_CHECK" | wc -l) From 2538af4b06c81bf331803c62a46eb6dca671cf00 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 15 Feb 2022 10:49:14 +0100 Subject: [PATCH 0654/1098] Add workaround for python bug to HAQueueHandler (#66541) --- homeassistant/util/logging.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/homeassistant/util/logging.py b/homeassistant/util/logging.py index 9216993eb5359b..8dba52ebe9dd16 100644 --- a/homeassistant/util/logging.py +++ b/homeassistant/util/logging.py @@ -33,6 +33,15 @@ def filter(self, record: logging.LogRecord) -> bool: class HomeAssistantQueueHandler(logging.handlers.QueueHandler): """Process the log in another thread.""" + def prepare(self, record: logging.LogRecord) -> logging.LogRecord: + """Prepare a record for queuing. + + This is added as a workaround for https://bugs.python.org/issue46755 + """ + record = super().prepare(record) + record.stack_info = None + return record + def handle(self, record: logging.LogRecord) -> Any: """ Conditionally emit the specified logging record. From 7eed4af6ae007b427eaf43d735802bfed22e8943 Mon Sep 17 00:00:00 2001 From: Robert Hillis Date: Tue, 15 Feb 2022 04:51:55 -0500 Subject: [PATCH 0655/1098] Use enums in vizio (#61996) Co-authored-by: Raman Gupta <7243222+raman325@users.noreply.github.com> --- homeassistant/components/vizio/__init__.py | 12 ++++++------ homeassistant/components/vizio/config_flow.py | 14 ++++++++++---- homeassistant/components/vizio/media_player.py | 7 +++---- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/vizio/__init__.py b/homeassistant/components/vizio/__init__.py index e66a8f3a55422f..1e0d5a322fb690 100644 --- a/homeassistant/components/vizio/__init__.py +++ b/homeassistant/components/vizio/__init__.py @@ -9,7 +9,7 @@ from pyvizio.util import gen_apps_list_from_url import voluptuous as vol -from homeassistant.components.media_player import DEVICE_CLASS_TV +from homeassistant.components.media_player import MediaPlayerDeviceClass from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry, ConfigEntryState from homeassistant.const import Platform from homeassistant.core import HomeAssistant @@ -24,13 +24,13 @@ def validate_apps(config: ConfigType) -> ConfigType: - """Validate CONF_APPS is only used when CONF_DEVICE_CLASS == DEVICE_CLASS_TV.""" + """Validate CONF_APPS is only used when CONF_DEVICE_CLASS is MediaPlayerDeviceClass.TV.""" if ( config.get(CONF_APPS) is not None - and config[CONF_DEVICE_CLASS] != DEVICE_CLASS_TV + and config[CONF_DEVICE_CLASS] != MediaPlayerDeviceClass.TV ): raise vol.Invalid( - f"'{CONF_APPS}' can only be used if {CONF_DEVICE_CLASS}' is '{DEVICE_CLASS_TV}'" + f"'{CONF_APPS}' can only be used if {CONF_DEVICE_CLASS}' is '{MediaPlayerDeviceClass.TV}'" ) return config @@ -63,7 +63,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data.setdefault(DOMAIN, {}) if ( CONF_APPS not in hass.data[DOMAIN] - and entry.data[CONF_DEVICE_CLASS] == DEVICE_CLASS_TV + and entry.data[CONF_DEVICE_CLASS] == MediaPlayerDeviceClass.TV ): coordinator = VizioAppsDataUpdateCoordinator(hass) await coordinator.async_refresh() @@ -83,7 +83,7 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> if not any( entry.state is ConfigEntryState.LOADED and entry.entry_id != config_entry.entry_id - and entry.data[CONF_DEVICE_CLASS] == DEVICE_CLASS_TV + and entry.data[CONF_DEVICE_CLASS] == MediaPlayerDeviceClass.TV for entry in hass.config_entries.async_entries(DOMAIN) ): hass.data[DOMAIN].pop(CONF_APPS, None) diff --git a/homeassistant/components/vizio/config_flow.py b/homeassistant/components/vizio/config_flow.py index 9cca89f77aa742..019d016eb2a54f 100644 --- a/homeassistant/components/vizio/config_flow.py +++ b/homeassistant/components/vizio/config_flow.py @@ -12,7 +12,7 @@ from homeassistant import config_entries from homeassistant.components import zeroconf -from homeassistant.components.media_player import DEVICE_CLASS_SPEAKER, DEVICE_CLASS_TV +from homeassistant.components.media_player import MediaPlayerDeviceClass from homeassistant.config_entries import ( SOURCE_IGNORE, SOURCE_IMPORT, @@ -68,7 +68,11 @@ def _get_config_schema(input_dict: dict[str, Any] = None) -> vol.Schema: vol.Required( CONF_DEVICE_CLASS, default=input_dict.get(CONF_DEVICE_CLASS, DEFAULT_DEVICE_CLASS), - ): vol.All(str, vol.Lower, vol.In([DEVICE_CLASS_TV, DEVICE_CLASS_SPEAKER])), + ): vol.All( + str, + vol.Lower, + vol.In([MediaPlayerDeviceClass.TV, MediaPlayerDeviceClass.SPEAKER]), + ), vol.Optional( CONF_ACCESS_TOKEN, default=input_dict.get(CONF_ACCESS_TOKEN, "") ): str, @@ -134,7 +138,7 @@ async def async_step_init(self, user_input: dict[str, Any] = None) -> FlowResult } ) - if self.config_entry.data[CONF_DEVICE_CLASS] == DEVICE_CLASS_TV: + if self.config_entry.data[CONF_DEVICE_CLASS] == MediaPlayerDeviceClass.TV: default_include_or_exclude = ( CONF_EXCLUDE if self.config_entry.options @@ -233,7 +237,9 @@ async def async_step_user(self, user_input: dict[str, Any] = None) -> FlowResult self._must_show_form = False elif user_input[ CONF_DEVICE_CLASS - ] == DEVICE_CLASS_SPEAKER or user_input.get(CONF_ACCESS_TOKEN): + ] == MediaPlayerDeviceClass.SPEAKER or user_input.get( + CONF_ACCESS_TOKEN + ): # Ensure config is valid for a device if not await VizioAsync.validate_ha_config( user_input[CONF_HOST], diff --git a/homeassistant/components/vizio/media_player.py b/homeassistant/components/vizio/media_player.py index 664a8ae7da86e0..a076a995f7bd04 100644 --- a/homeassistant/components/vizio/media_player.py +++ b/homeassistant/components/vizio/media_player.py @@ -10,9 +10,8 @@ from pyvizio.const import APP_HOME, INPUT_APPS, NO_APP_RUNNING, UNKNOWN_APP from homeassistant.components.media_player import ( - DEVICE_CLASS_SPEAKER, - DEVICE_CLASS_TV, SUPPORT_SELECT_SOUND_MODE, + MediaPlayerDeviceClass, MediaPlayerEntity, ) from homeassistant.config_entries import ConfigEntry @@ -252,7 +251,7 @@ async def async_update(self) -> None: self._available_inputs = [input_.name for input_ in inputs] # Return before setting app variables if INPUT_APPS isn't in available inputs - if self._attr_device_class == DEVICE_CLASS_SPEAKER or not any( + if self._attr_device_class == MediaPlayerDeviceClass.SPEAKER or not any( app for app in INPUT_APPS if app in self._available_inputs ): return @@ -329,7 +328,7 @@ def apps_list_update(): self._all_apps = self._apps_coordinator.data self.async_write_ha_state() - if self._attr_device_class == DEVICE_CLASS_TV: + if self._attr_device_class == MediaPlayerDeviceClass.TV: self.async_on_remove( self._apps_coordinator.async_add_listener(apps_list_update) ) From 02e21502703f509694687c7a1c84cfad17b17471 Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Tue, 15 Feb 2022 04:53:35 -0500 Subject: [PATCH 0656/1098] Fix econet spelling (#64254) Co-authored-by: Josh Soref --- homeassistant/components/econet/sensor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/econet/sensor.py b/homeassistant/components/econet/sensor.py index 9dbe46ab989b73..e39f55423d42a6 100644 --- a/homeassistant/components/econet/sensor.py +++ b/homeassistant/components/econet/sensor.py @@ -13,9 +13,9 @@ ENERGY_KILO_BRITISH_THERMAL_UNIT = "kBtu" TANK_HEALTH = "tank_health" -AVAILIBLE_HOT_WATER = "availible_hot_water" +AVAILABLE_HOT_WATER = "available_hot_water" COMPRESSOR_HEALTH = "compressor_health" -OVERRIDE_STATUS = "oveerride_status" +OVERRIDE_STATUS = "override_status" WATER_USAGE_TODAY = "water_usage_today" POWER_USAGE_TODAY = "power_usage_today" ALERT_COUNT = "alert_count" @@ -24,7 +24,7 @@ SENSOR_NAMES_TO_ATTRIBUTES = { TANK_HEALTH: "tank_health", - AVAILIBLE_HOT_WATER: "tank_hot_water_availability", + AVAILABLE_HOT_WATER: "tank_hot_water_availability", COMPRESSOR_HEALTH: "compressor_health", OVERRIDE_STATUS: "override_status", WATER_USAGE_TODAY: "todays_water_usage", @@ -36,7 +36,7 @@ SENSOR_NAMES_TO_UNIT_OF_MEASUREMENT = { TANK_HEALTH: PERCENTAGE, - AVAILIBLE_HOT_WATER: PERCENTAGE, + AVAILABLE_HOT_WATER: PERCENTAGE, COMPRESSOR_HEALTH: PERCENTAGE, OVERRIDE_STATUS: None, WATER_USAGE_TODAY: VOLUME_GALLONS, From c5dfe2b5a82f3eb59d1c94cf73e8b97100d27e2f Mon Sep 17 00:00:00 2001 From: Jeef Date: Tue, 15 Feb 2022 03:04:26 -0700 Subject: [PATCH 0657/1098] Bump intellifire4py to 0.9.8 (#66531) --- homeassistant/components/intellifire/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/intellifire/manifest.json b/homeassistant/components/intellifire/manifest.json index a71a93b970e628..aaae49afb64893 100644 --- a/homeassistant/components/intellifire/manifest.json +++ b/homeassistant/components/intellifire/manifest.json @@ -3,7 +3,7 @@ "name": "IntelliFire", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/intellifire", - "requirements": ["intellifire4py==0.6"], + "requirements": ["intellifire4py==0.9.8"], "dependencies": [], "codeowners": ["@jeeftor"], "iot_class": "local_polling", diff --git a/requirements_all.txt b/requirements_all.txt index b8a86347aa147f..746011521e8d08 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -911,7 +911,7 @@ influxdb-client==1.24.0 influxdb==5.3.1 # homeassistant.components.intellifire -intellifire4py==0.6 +intellifire4py==0.9.8 # homeassistant.components.iotawatt iotawattpy==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f5a2fbf7a62a5c..815d290118a3cb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -592,7 +592,7 @@ influxdb-client==1.24.0 influxdb==5.3.1 # homeassistant.components.intellifire -intellifire4py==0.6 +intellifire4py==0.9.8 # homeassistant.components.iotawatt iotawattpy==0.1.0 From 389653dc0199eca13eb21dc6ec597bafbf330383 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 15 Feb 2022 11:19:28 +0100 Subject: [PATCH 0658/1098] Add a asset name for CAS / official_image (#66276) --- .github/workflows/builder.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 1ede9c62e16543..cdff1bac634831 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -57,6 +57,7 @@ jobs: uses: home-assistant/actions/helpers/codenotary@master with: source: file://${{ github.workspace }}/OFFICIAL_IMAGE + asset: OFFICIAL_IMAGE-${{ steps.version.outputs.version }} token: ${{ secrets.CAS_TOKEN }} build_python: From 00221f1d66519b88df9d54ec760b2885bf5a7159 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 15 Feb 2022 12:32:14 +0100 Subject: [PATCH 0659/1098] Cleanup and strict typing for MJPEG integration (#66526) --- .strict-typing | 1 + homeassistant/components/mjpeg/camera.py | 63 ++++++++++++++++-------- mypy.ini | 11 +++++ 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/.strict-typing b/.strict-typing index 364a3d0db6aecd..afd8c0fb93eb69 100644 --- a/.strict-typing +++ b/.strict-typing @@ -119,6 +119,7 @@ homeassistant.components.lookin.* homeassistant.components.luftdaten.* homeassistant.components.mailbox.* homeassistant.components.media_player.* +homeassistant.components.mjpeg.* homeassistant.components.modbus.* homeassistant.components.modem_callerid.* homeassistant.components.media_source.* diff --git a/homeassistant/components/mjpeg/camera.py b/homeassistant/components/mjpeg/camera.py index 1d60206f2d8c8e..0de303455b9f1f 100644 --- a/homeassistant/components/mjpeg/camera.py +++ b/homeassistant/components/mjpeg/camera.py @@ -2,10 +2,12 @@ from __future__ import annotations import asyncio +from collections.abc import Iterable from contextlib import closing import logging import aiohttp +from aiohttp import web import async_timeout import requests from requests.auth import HTTPBasicAuth, HTTPDigestAuth @@ -65,17 +67,30 @@ async def async_setup_platform( if discovery_info: config = PLATFORM_SCHEMA(discovery_info) - async_add_entities([MjpegCamera(config)]) + + async_add_entities( + [ + MjpegCamera( + config[CONF_NAME], + config[CONF_AUTHENTICATION], + config.get(CONF_USERNAME), + config.get(CONF_PASSWORD), + config[CONF_MJPEG_URL], + config.get(CONF_STILL_IMAGE_URL), + config[CONF_VERIFY_SSL], + ) + ] + ) -def filter_urllib3_logging(): +def filter_urllib3_logging() -> None: """Filter header errors from urllib3 due to a urllib3 bug.""" urllib3_logger = logging.getLogger("urllib3.connectionpool") if not any(isinstance(x, NoHeaderErrorFilter) for x in urllib3_logger.filters): urllib3_logger.addFilter(NoHeaderErrorFilter()) -def extract_image_from_mjpeg(stream): +def extract_image_from_mjpeg(stream: Iterable[bytes]) -> bytes | None: """Take in a MJPEG stream object, return the jpg from it.""" data = b"" @@ -93,19 +108,30 @@ def extract_image_from_mjpeg(stream): return data[jpg_start : jpg_end + 2] + return None + class MjpegCamera(Camera): """An implementation of an IP camera that is reachable over a URL.""" - def __init__(self, device_info): + def __init__( + self, + name: str, + authentication: str, + username: str | None, + password: str | None, + mjpeg_url: str, + still_image_url: str | None, + verify_ssl: bool, + ) -> None: """Initialize a MJPEG camera.""" super().__init__() - self._name = device_info.get(CONF_NAME) - self._authentication = device_info.get(CONF_AUTHENTICATION) - self._username = device_info.get(CONF_USERNAME) - self._password = device_info.get(CONF_PASSWORD) - self._mjpeg_url = device_info[CONF_MJPEG_URL] - self._still_image_url = device_info.get(CONF_STILL_IMAGE_URL) + self._attr_name = name + self._authentication = authentication + self._username = username + self._password = password + self._mjpeg_url = mjpeg_url + self._still_image_url = still_image_url self._auth = None if ( @@ -114,7 +140,7 @@ def __init__(self, device_info): and self._authentication == HTTP_BASIC_AUTHENTICATION ): self._auth = aiohttp.BasicAuth(self._username, password=self._password) - self._verify_ssl = device_info.get(CONF_VERIFY_SSL) + self._verify_ssl = verify_ssl async def async_camera_image( self, width: int | None = None, height: int | None = None @@ -137,10 +163,10 @@ async def async_camera_image( return image except asyncio.TimeoutError: - _LOGGER.error("Timeout getting camera image from %s", self._name) + _LOGGER.error("Timeout getting camera image from %s", self.name) except aiohttp.ClientError as err: - _LOGGER.error("Error getting new camera image from %s: %s", self._name, err) + _LOGGER.error("Error getting new camera image from %s: %s", self.name, err) return None @@ -168,7 +194,9 @@ def camera_image( with closing(req) as response: return extract_image_from_mjpeg(response.iter_content(102400)) - async def handle_async_mjpeg_stream(self, request): + async def handle_async_mjpeg_stream( + self, request: web.Request + ) -> web.StreamResponse | None: """Generate an HTTP MJPEG stream from the camera.""" # aiohttp don't support DigestAuth -> Fallback if self._authentication == HTTP_DIGEST_AUTHENTICATION: @@ -180,15 +208,10 @@ async def handle_async_mjpeg_stream(self, request): return await async_aiohttp_proxy_web(self.hass, request, stream_coro) - @property - def name(self): - """Return the name of this camera.""" - return self._name - class NoHeaderErrorFilter(logging.Filter): """Filter out urllib3 Header Parsing Errors due to a urllib3 bug.""" - def filter(self, record): + def filter(self, record: logging.LogRecord) -> bool: """Filter out Header Parsing Errors.""" return "Failed to parse headers" not in record.getMessage() diff --git a/mypy.ini b/mypy.ini index 8372a98d332306..18d951cbe306d8 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1118,6 +1118,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.mjpeg.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.modbus.*] check_untyped_defs = true disallow_incomplete_defs = true From 52ebe58b14bd8cd3bebb34621aad422d92071d77 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Tue, 15 Feb 2022 15:24:13 +0100 Subject: [PATCH 0660/1098] Add tests for samsungtv diagnostics (#66563) * Add tests for samsungtv diagnostics * Adjust coveragerc * Adjust type hints Co-authored-by: epenet --- .coveragerc | 1 - .../components/samsungtv/test_diagnostics.py | 58 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 tests/components/samsungtv/test_diagnostics.py diff --git a/.coveragerc b/.coveragerc index b59986fbe64436..aa64e660a83317 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1007,7 +1007,6 @@ omit = homeassistant/components/sabnzbd/* homeassistant/components/saj/sensor.py homeassistant/components/samsungtv/bridge.py - homeassistant/components/samsungtv/diagnostics.py homeassistant/components/satel_integra/* homeassistant/components/schluter/* homeassistant/components/screenlogic/__init__.py diff --git a/tests/components/samsungtv/test_diagnostics.py b/tests/components/samsungtv/test_diagnostics.py new file mode 100644 index 00000000000000..ba3d03d5702cce --- /dev/null +++ b/tests/components/samsungtv/test_diagnostics.py @@ -0,0 +1,58 @@ +"""Test samsungtv diagnostics.""" +from aiohttp import ClientSession +import pytest + +from homeassistant.components.diagnostics import REDACTED +from homeassistant.components.samsungtv import DOMAIN +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry +from tests.components.diagnostics import get_diagnostics_for_config_entry +from tests.components.samsungtv.test_media_player import MOCK_ENTRY_WS_WITH_MAC + + +@pytest.fixture(name="config_entry") +def get_config_entry(hass: HomeAssistant) -> ConfigEntry: + """Create and register mock config entry.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data=MOCK_ENTRY_WS_WITH_MAC, + entry_id="123456", + unique_id="any", + ) + config_entry.add_to_hass(hass) + return config_entry + + +@pytest.mark.usefixtures("remotews") +async def test_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry, hass_client: ClientSession +) -> None: + """Test config entry diagnostics.""" + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { + "entry": { + "data": { + "host": "fake_host", + "ip_address": "test", + "mac": "aa:bb:cc:dd:ee:ff", + "method": "websocket", + "name": "fake", + "port": 8002, + "token": REDACTED, + }, + "disabled_by": None, + "domain": "samsungtv", + "entry_id": "123456", + "options": {}, + "pref_disable_new_entities": False, + "pref_disable_polling": False, + "source": "user", + "title": "Mock Title", + "unique_id": "any", + "version": 2, + } + } From cbdbb6647546f20d957aa3a7807158310712880b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 15 Feb 2022 15:24:52 +0100 Subject: [PATCH 0661/1098] Fix integrations building on top of mjpeg (#66557) --- .core_files.yaml | 1 + homeassistant/components/agent_dvr/camera.py | 20 ++++------- homeassistant/components/axis/camera.py | 33 +++++++------------ homeassistant/components/mjpeg/camera.py | 25 +++++++------- homeassistant/components/motioneye/camera.py | 16 +++++---- homeassistant/components/zoneminder/camera.py | 21 ++++-------- 6 files changed, 48 insertions(+), 68 deletions(-) diff --git a/.core_files.yaml b/.core_files.yaml index 318a43875106d9..b6c20bf7542f12 100644 --- a/.core_files.yaml +++ b/.core_files.yaml @@ -76,6 +76,7 @@ components: &components - homeassistant/components/logger/* - homeassistant/components/lovelace/* - homeassistant/components/media_source/* + - homeassistant/components/mjpeg/* - homeassistant/components/mqtt/* - homeassistant/components/network/* - homeassistant/components/onboarding/* diff --git a/homeassistant/components/agent_dvr/camera.py b/homeassistant/components/agent_dvr/camera.py index 474d1f08b80dbb..bad90efee8f416 100644 --- a/homeassistant/components/agent_dvr/camera.py +++ b/homeassistant/components/agent_dvr/camera.py @@ -5,13 +5,8 @@ from agent import AgentError from homeassistant.components.camera import SUPPORT_ON_OFF -from homeassistant.components.mjpeg.camera import ( - CONF_MJPEG_URL, - CONF_STILL_IMAGE_URL, - MjpegCamera, - filter_urllib3_logging, -) -from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME +from homeassistant.components.mjpeg.camera import MjpegCamera, filter_urllib3_logging +from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.helpers import entity_platform from homeassistant.helpers.entity import DeviceInfo @@ -70,16 +65,15 @@ class AgentCamera(MjpegCamera): def __init__(self, device): """Initialize as a subclass of MjpegCamera.""" - device_info = { - CONF_NAME: device.name, - CONF_MJPEG_URL: f"{device.client._server_url}{device.mjpeg_image_url}&size={device.mjpegStreamWidth}x{device.mjpegStreamHeight}", - CONF_STILL_IMAGE_URL: f"{device.client._server_url}{device.still_image_url}&size={device.mjpegStreamWidth}x{device.mjpegStreamHeight}", - } self.device = device self._removed = False self._attr_name = f"{device.client.name} {device.name}" self._attr_unique_id = f"{device._client.unique}_{device.typeID}_{device.id}" - super().__init__(device_info) + super().__init__( + name=device.name, + mjpeg_url=f"{device.client._server_url}{device.mjpeg_image_url}&size={device.mjpegStreamWidth}x{device.mjpegStreamHeight}", + still_image_url=f"{device.client._server_url}{device.still_image_url}&size={device.mjpegStreamWidth}x{device.mjpegStreamHeight}", + ) self._attr_device_info = DeviceInfo( identifiers={(AGENT_DOMAIN, self.unique_id)}, manufacturer="Agent", diff --git a/homeassistant/components/axis/camera.py b/homeassistant/components/axis/camera.py index bb8b072fb061f0..c52aac37a02b12 100644 --- a/homeassistant/components/axis/camera.py +++ b/homeassistant/components/axis/camera.py @@ -2,20 +2,9 @@ from urllib.parse import urlencode from homeassistant.components.camera import SUPPORT_STREAM -from homeassistant.components.mjpeg.camera import ( - CONF_MJPEG_URL, - CONF_STILL_IMAGE_URL, - MjpegCamera, - filter_urllib3_logging, -) +from homeassistant.components.mjpeg.camera import MjpegCamera, filter_urllib3_logging from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - CONF_AUTHENTICATION, - CONF_NAME, - CONF_PASSWORD, - CONF_USERNAME, - HTTP_DIGEST_AUTHENTICATION, -) +from homeassistant.const import HTTP_DIGEST_AUTHENTICATION from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -47,15 +36,15 @@ def __init__(self, device): """Initialize Axis Communications camera component.""" AxisEntityBase.__init__(self, device) - config = { - CONF_NAME: device.name, - CONF_USERNAME: device.username, - CONF_PASSWORD: device.password, - CONF_MJPEG_URL: self.mjpeg_source, - CONF_STILL_IMAGE_URL: self.image_source, - CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION, - } - MjpegCamera.__init__(self, config) + MjpegCamera.__init__( + self, + name=device.name, + username=device.username, + password=device.password, + mjpeg_url=self.mjpeg_source, + still_image_url=self.image_source, + authentication=HTTP_DIGEST_AUTHENTICATION, + ) self._attr_unique_id = f"{device.unique_id}-camera" diff --git a/homeassistant/components/mjpeg/camera.py b/homeassistant/components/mjpeg/camera.py index 0de303455b9f1f..3a9c1a6cf91fbe 100644 --- a/homeassistant/components/mjpeg/camera.py +++ b/homeassistant/components/mjpeg/camera.py @@ -49,7 +49,7 @@ [HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION] ), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PASSWORD): cv.string, + vol.Optional(CONF_PASSWORD, default=""): cv.string, vol.Optional(CONF_USERNAME): cv.string, vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, } @@ -71,13 +71,13 @@ async def async_setup_platform( async_add_entities( [ MjpegCamera( - config[CONF_NAME], - config[CONF_AUTHENTICATION], - config.get(CONF_USERNAME), - config.get(CONF_PASSWORD), - config[CONF_MJPEG_URL], - config.get(CONF_STILL_IMAGE_URL), - config[CONF_VERIFY_SSL], + name=config[CONF_NAME], + authentication=config[CONF_AUTHENTICATION], + username=config.get(CONF_USERNAME), + password=config[CONF_PASSWORD], + mjpeg_url=config[CONF_MJPEG_URL], + still_image_url=config.get(CONF_STILL_IMAGE_URL), + verify_ssl=config[CONF_VERIFY_SSL], ) ] ) @@ -116,13 +116,14 @@ class MjpegCamera(Camera): def __init__( self, + *, name: str, - authentication: str, - username: str | None, - password: str | None, mjpeg_url: str, still_image_url: str | None, - verify_ssl: bool, + authentication: str | None = None, + username: str | None = None, + password: str = "", + verify_ssl: bool = True, ) -> None: """Initialize a MJPEG camera.""" super().__init__() diff --git a/homeassistant/components/motioneye/camera.py b/homeassistant/components/motioneye/camera.py index b9b7f6d42c8def..acf170472da26d 100644 --- a/homeassistant/components/motioneye/camera.py +++ b/homeassistant/components/motioneye/camera.py @@ -27,7 +27,6 @@ from homeassistant.components.mjpeg.camera import ( CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, - CONF_VERIFY_SSL, MjpegCamera, ) from homeassistant.config_entries import ConfigEntry @@ -144,6 +143,8 @@ def camera_add(camera: dict[str, Any]) -> None: class MotionEyeMjpegCamera(MotionEyeEntity, MjpegCamera): """motionEye mjpeg camera.""" + _name: str + def __init__( self, config_entry_id: str, @@ -173,10 +174,8 @@ def __init__( ) MjpegCamera.__init__( self, - { - CONF_VERIFY_SSL: False, - **self._get_mjpeg_camera_properties_for_camera(camera), - }, + verify_ssl=False, + **self._get_mjpeg_camera_properties_for_camera(camera), ) @callback @@ -207,7 +206,7 @@ def _get_mjpeg_camera_properties_for_camera( return { CONF_NAME: camera[KEY_NAME], CONF_USERNAME: self._surveillance_username if auth is not None else None, - CONF_PASSWORD: self._surveillance_password if auth is not None else None, + CONF_PASSWORD: self._surveillance_password if auth is not None else "", CONF_MJPEG_URL: streaming_url or "", CONF_STILL_IMAGE_URL: self._client.get_camera_snapshot_url(camera), CONF_AUTHENTICATION: auth, @@ -227,7 +226,10 @@ def _set_mjpeg_camera_state_for_camera(self, camera: dict[str, Any]) -> None: self._still_image_url = properties[CONF_STILL_IMAGE_URL] self._authentication = properties[CONF_AUTHENTICATION] - if self._authentication == HTTP_BASIC_AUTHENTICATION: + if ( + self._authentication == HTTP_BASIC_AUTHENTICATION + and self._username is not None + ): self._auth = aiohttp.BasicAuth(self._username, password=self._password) def _is_acceptable_streaming_camera(self) -> bool: diff --git a/homeassistant/components/zoneminder/camera.py b/homeassistant/components/zoneminder/camera.py index 70e9548414eede..5e262ff690314d 100644 --- a/homeassistant/components/zoneminder/camera.py +++ b/homeassistant/components/zoneminder/camera.py @@ -3,13 +3,7 @@ import logging -from homeassistant.components.mjpeg.camera import ( - CONF_MJPEG_URL, - CONF_STILL_IMAGE_URL, - MjpegCamera, - filter_urllib3_logging, -) -from homeassistant.const import CONF_NAME, CONF_VERIFY_SSL +from homeassistant.components.mjpeg.camera import MjpegCamera, filter_urllib3_logging from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -44,13 +38,12 @@ class ZoneMinderCamera(MjpegCamera): def __init__(self, monitor, verify_ssl): """Initialize as a subclass of MjpegCamera.""" - device_info = { - CONF_NAME: monitor.name, - CONF_MJPEG_URL: monitor.mjpeg_image_url, - CONF_STILL_IMAGE_URL: monitor.still_image_url, - CONF_VERIFY_SSL: verify_ssl, - } - super().__init__(device_info) + super().__init__( + name=monitor.name, + mjpeg_url=monitor.mjpeg_image_url, + still_image_url=monitor.still_image_url, + verify_ssl=verify_ssl, + ) self._is_recording = None self._is_available = None self._monitor = monitor From 430162fa5fa4fe5664589d1f341efd202b7b0d0e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 15 Feb 2022 15:25:36 +0100 Subject: [PATCH 0662/1098] Fix Tuya Covers without state in their control data point (#66564) --- homeassistant/components/tuya/cover.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/tuya/cover.py b/homeassistant/components/tuya/cover.py index 6a9e37670659f4..de277e6151096f 100644 --- a/homeassistant/components/tuya/cover.py +++ b/homeassistant/components/tuya/cover.py @@ -158,7 +158,10 @@ def async_discover_device(device_ids: list[str]) -> None: device = hass_data.device_manager.device_map[device_id] if descriptions := COVERS.get(device.category): for description in descriptions: - if description.key in device.status: + if ( + description.key in device.function + or description.key in device.status_range + ): entities.append( TuyaCoverEntity( device, hass_data.device_manager, description From af4e37339a39badd5596e8bc9ba86d6c1994aa1b Mon Sep 17 00:00:00 2001 From: Eduard van Valkenburg Date: Tue, 15 Feb 2022 15:53:38 +0100 Subject: [PATCH 0663/1098] Add Connectivity sensor to SIA (#64305) * implemented connectivity sensor * further cleanup off update code * cleanup and tighter behaviour for attributes * added seperate connectivity class to binary sensor * callbacks and keys * redid name and unique_id logic, non-breaking result * using entry more in inits * Fix import * fix ping_interval in sia_entity_base * added ping_interval default to next * fixed next Co-authored-by: Martin Hjelmare --- .../components/sia/alarm_control_panel.py | 41 ++----- homeassistant/components/sia/binary_sensor.py | 97 ++++++---------- homeassistant/components/sia/const.py | 8 +- .../components/sia/sia_entity_base.py | 105 ++++++++++++------ homeassistant/components/sia/utils.py | 32 +++++- 5 files changed, 143 insertions(+), 140 deletions(-) diff --git a/homeassistant/components/sia/alarm_control_panel.py b/homeassistant/components/sia/alarm_control_panel.py index 4743c5f0401442..0a2a17db2005dc 100644 --- a/homeassistant/components/sia/alarm_control_panel.py +++ b/homeassistant/components/sia/alarm_control_panel.py @@ -12,7 +12,6 @@ ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( - CONF_PORT, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_CUSTOM_BYPASS, STATE_ALARM_ARMED_NIGHT, @@ -24,16 +23,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType -from .const import ( - CONF_ACCOUNT, - CONF_ACCOUNTS, - CONF_PING_INTERVAL, - CONF_ZONES, - KEY_ALARM, - PREVIOUS_STATE, - SIA_NAME_FORMAT, - SIA_UNIQUE_ID_FORMAT_ALARM, -) +from .const import CONF_ACCOUNT, CONF_ACCOUNTS, CONF_ZONES, KEY_ALARM, PREVIOUS_STATE from .sia_entity_base import SIABaseEntity, SIAEntityDescription _LOGGER = logging.getLogger(__name__) @@ -86,17 +76,7 @@ async def async_setup_entry( """Set up SIA alarm_control_panel(s) from a config entry.""" async_add_entities( SIAAlarmControlPanel( - port=entry.data[CONF_PORT], - account=account_data[CONF_ACCOUNT], - zone=zone, - ping_interval=account_data[CONF_PING_INTERVAL], - entity_description=ENTITY_DESCRIPTION_ALARM, - unique_id=SIA_UNIQUE_ID_FORMAT_ALARM.format( - entry.entry_id, account_data[CONF_ACCOUNT], zone - ), - name=SIA_NAME_FORMAT.format( - entry.data[CONF_PORT], account_data[CONF_ACCOUNT], zone, "alarm" - ), + entry, account_data[CONF_ACCOUNT], zone, ENTITY_DESCRIPTION_ALARM ) for account_data in entry.data[CONF_ACCOUNTS] for zone in range( @@ -114,23 +94,17 @@ class SIAAlarmControlPanel(SIABaseEntity, AlarmControlPanelEntity): def __init__( self, - port: int, + entry: ConfigEntry, account: str, - zone: int | None, - ping_interval: int, + zone: int, entity_description: SIAAlarmControlPanelEntityDescription, - unique_id: str, - name: str, ) -> None: """Create SIAAlarmControlPanel object.""" super().__init__( - port, + entry, account, zone, - ping_interval, entity_description, - unique_id, - name, ) self._attr_state: StateType = None @@ -144,7 +118,10 @@ def handle_last_state(self, last_state: State | None) -> None: self._attr_available = False def update_state(self, sia_event: SIAEvent) -> bool: - """Update the state of the alarm control panel.""" + """Update the state of the alarm control panel. + + Return True if the event was relevant for this entity. + """ new_state = self.entity_description.code_consequences.get(sia_event.code) if new_state is None: return False diff --git a/homeassistant/components/sia/binary_sensor.py b/homeassistant/components/sia/binary_sensor.py index e26e26cc0b7e76..f23bd2885a67d3 100644 --- a/homeassistant/components/sia/binary_sensor.py +++ b/homeassistant/components/sia/binary_sensor.py @@ -13,23 +13,20 @@ BinarySensorEntityDescription, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_PORT, STATE_OFF, STATE_ON, STATE_UNAVAILABLE -from homeassistant.core import HomeAssistant, State +from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE +from homeassistant.core import HomeAssistant, State, callback from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( CONF_ACCOUNT, CONF_ACCOUNTS, - CONF_PING_INTERVAL, CONF_ZONES, + KEY_CONNECTIVITY, KEY_MOISTURE, KEY_POWER, KEY_SMOKE, SIA_HUB_ZONE, - SIA_NAME_FORMAT, - SIA_NAME_FORMAT_HUB, - SIA_UNIQUE_ID_FORMAT_BINARY, ) from .sia_entity_base import SIABaseEntity, SIAEntityDescription @@ -78,72 +75,31 @@ class SIABinarySensorEntityDescription( entity_registry_enabled_default=False, ) +ENTITY_DESCRIPTION_CONNECTIVITY = SIABinarySensorEntityDescription( + key=KEY_CONNECTIVITY, + device_class=BinarySensorDeviceClass.CONNECTIVITY, + entity_category=EntityCategory.DIAGNOSTIC, + code_consequences={"RP": True}, +) -def generate_binary_sensors(entry) -> Iterable[SIABinarySensor]: + +def generate_binary_sensors(entry: ConfigEntry) -> Iterable[SIABinarySensor]: """Generate binary sensors. For each Account there is one power sensor with zone == 0. For each Zone in each Account there is one smoke and one moisture sensor. """ for account_data in entry.data[CONF_ACCOUNTS]: - yield SIABinarySensor( - port=entry.data[CONF_PORT], - account=account_data[CONF_ACCOUNT], - zone=SIA_HUB_ZONE, - ping_interval=account_data[CONF_PING_INTERVAL], - entity_description=ENTITY_DESCRIPTION_POWER, - unique_id=SIA_UNIQUE_ID_FORMAT_BINARY.format( - entry.entry_id, - account_data[CONF_ACCOUNT], - SIA_HUB_ZONE, - ENTITY_DESCRIPTION_POWER.device_class, - ), - name=SIA_NAME_FORMAT_HUB.format( - entry.data[CONF_PORT], - account_data[CONF_ACCOUNT], - ENTITY_DESCRIPTION_POWER.device_class, - ), + account = account_data[CONF_ACCOUNT] + zones = entry.options[CONF_ACCOUNTS][account][CONF_ZONES] + + yield SIABinarySensorConnectivity( + entry, account, SIA_HUB_ZONE, ENTITY_DESCRIPTION_CONNECTIVITY ) - zones = entry.options[CONF_ACCOUNTS][account_data[CONF_ACCOUNT]][CONF_ZONES] + yield SIABinarySensor(entry, account, SIA_HUB_ZONE, ENTITY_DESCRIPTION_POWER) for zone in range(1, zones + 1): - yield SIABinarySensor( - port=entry.data[CONF_PORT], - account=account_data[CONF_ACCOUNT], - zone=zone, - ping_interval=account_data[CONF_PING_INTERVAL], - entity_description=ENTITY_DESCRIPTION_SMOKE, - unique_id=SIA_UNIQUE_ID_FORMAT_BINARY.format( - entry.entry_id, - account_data[CONF_ACCOUNT], - zone, - ENTITY_DESCRIPTION_SMOKE.device_class, - ), - name=SIA_NAME_FORMAT.format( - entry.data[CONF_PORT], - account_data[CONF_ACCOUNT], - zone, - ENTITY_DESCRIPTION_SMOKE.device_class, - ), - ) - yield SIABinarySensor( - port=entry.data[CONF_PORT], - account=account_data[CONF_ACCOUNT], - zone=zone, - ping_interval=account_data[CONF_PING_INTERVAL], - entity_description=ENTITY_DESCRIPTION_MOISTURE, - unique_id=SIA_UNIQUE_ID_FORMAT_BINARY.format( - entry.entry_id, - account_data[CONF_ACCOUNT], - zone, - ENTITY_DESCRIPTION_MOISTURE.device_class, - ), - name=SIA_NAME_FORMAT.format( - entry.data[CONF_PORT], - account_data[CONF_ACCOUNT], - zone, - ENTITY_DESCRIPTION_MOISTURE.device_class, - ), - ) + yield SIABinarySensor(entry, account, zone, ENTITY_DESCRIPTION_SMOKE) + yield SIABinarySensor(entry, account, zone, ENTITY_DESCRIPTION_MOISTURE) async def async_setup_entry( @@ -171,10 +127,23 @@ def handle_last_state(self, last_state: State | None) -> None: self._attr_available = False def update_state(self, sia_event: SIAEvent) -> bool: - """Update the state of the binary sensor.""" + """Update the state of the binary sensor. + + Return True if the event was relevant for this entity. + """ new_state = self.entity_description.code_consequences.get(sia_event.code) if new_state is None: return False _LOGGER.debug("New state will be %s", new_state) self._attr_is_on = bool(new_state) return True + + +class SIABinarySensorConnectivity(SIABinarySensor): + """Class for Connectivity Sensor.""" + + @callback + def async_post_interval_update(self, _) -> None: + """Update state after a ping interval. Overwritten from sia entity base.""" + self._attr_is_on = False + self.async_write_ha_state() diff --git a/homeassistant/components/sia/const.py b/homeassistant/components/sia/const.py index 537c106fefa41f..82783611e070ff 100644 --- a/homeassistant/components/sia/const.py +++ b/homeassistant/components/sia/const.py @@ -24,18 +24,14 @@ CONF_PING_INTERVAL: Final = "ping_interval" CONF_ZONES: Final = "zones" -SIA_NAME_FORMAT: Final = "{} - {} - zone {} - {}" -SIA_NAME_FORMAT_HUB: Final = "{} - {} - {}" -SIA_UNIQUE_ID_FORMAT_ALARM: Final = "{}_{}_{}" -SIA_UNIQUE_ID_FORMAT_BINARY: Final = "{}_{}_{}_{}" -SIA_UNIQUE_ID_FORMAT_HUB: Final = "{}_{}_{}" SIA_HUB_ZONE: Final = 0 SIA_EVENT: Final = "sia_event_{}_{}" -KEY_ALARM: Final = "alarm_control_panel" +KEY_ALARM: Final = "alarm" KEY_SMOKE: Final = "smoke" KEY_MOISTURE: Final = "moisture" KEY_POWER: Final = "power" +KEY_CONNECTIVITY: Final = "connectivity" PREVIOUS_STATE: Final = "previous_state" AVAILABILITY_EVENT_CODE: Final = "RP" diff --git a/homeassistant/components/sia/sia_entity_base.py b/homeassistant/components/sia/sia_entity_base.py index 311728ad578b7b..8627dea28bc6c1 100644 --- a/homeassistant/components/sia/sia_entity_base.py +++ b/homeassistant/components/sia/sia_entity_base.py @@ -7,6 +7,8 @@ from pysiaalarm import SIAEvent +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_PORT from homeassistant.core import CALLBACK_TYPE, State, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo, EntityDescription @@ -14,8 +16,20 @@ from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import StateType -from .const import AVAILABILITY_EVENT_CODE, DOMAIN, SIA_EVENT, SIA_HUB_ZONE -from .utils import get_attr_from_sia_event, get_unavailability_interval +from .const import ( + AVAILABILITY_EVENT_CODE, + CONF_ACCOUNT, + CONF_ACCOUNTS, + CONF_PING_INTERVAL, + DOMAIN, + SIA_EVENT, + SIA_HUB_ZONE, +) +from .utils import ( + get_attr_from_sia_event, + get_unavailability_interval, + get_unique_id_and_name, +) _LOGGER = logging.getLogger(__name__) @@ -39,29 +53,32 @@ class SIABaseEntity(RestoreEntity): def __init__( self, - port: int, + entry: ConfigEntry, account: str, - zone: int | None, - ping_interval: int, + zone: int, entity_description: SIAEntityDescription, - unique_id: str, - name: str, ) -> None: """Create SIABaseEntity object.""" - self.port = port + self.port = entry.data[CONF_PORT] self.account = account self.zone = zone - self.ping_interval = ping_interval self.entity_description = entity_description - self._attr_unique_id = unique_id - self._attr_name = name + + self.ping_interval: int = next( + acc[CONF_PING_INTERVAL] + for acc in entry.data[CONF_ACCOUNTS] + if acc[CONF_ACCOUNT] == account + ) + self._attr_unique_id, self._attr_name = get_unique_id_and_name( + entry.entry_id, entry.data[CONF_PORT], account, zone, entity_description.key + ) self._attr_device_info = DeviceInfo( - name=name, - identifiers={(DOMAIN, unique_id)}, - via_device=(DOMAIN, f"{port}_{account}"), + name=self._attr_name, + identifiers={(DOMAIN, self._attr_unique_id)}, + via_device=(DOMAIN, f"{entry.data[CONF_PORT]}_{account}"), ) - self._cancel_availability_cb: CALLBACK_TYPE | None = None + self._post_interval_update_cb_canceller: CALLBACK_TYPE | None = None self._attr_extra_state_attributes = {} self._attr_should_poll = False @@ -83,7 +100,7 @@ async def async_added_to_hass(self) -> None: ) self.handle_last_state(await self.async_get_last_state()) if self._attr_available: - self.async_create_availability_cb() + self.async_create_post_interval_update_cb() @abstractmethod def handle_last_state(self, last_state: State | None) -> None: @@ -94,43 +111,57 @@ async def async_will_remove_from_hass(self) -> None: Overridden from Entity. """ - if self._cancel_availability_cb: - self._cancel_availability_cb() + self._cancel_post_interval_update_cb() @callback def async_handle_event(self, sia_event: SIAEvent) -> None: - """Listen to dispatcher events for this port and account and update state and attributes.""" + """Listen to dispatcher events for this port and account and update state and attributes. + + If the event is for either the zone or the 0 zone (hub zone), then handle it further. + If the event had a code that was relevant for the entity, then update the attributes. + If the event had a code that was relevant or it was a availability event then update the availability and schedule the next unavailability check. + """ _LOGGER.debug("Received event: %s", sia_event) if int(sia_event.ri) not in (self.zone, SIA_HUB_ZONE): return - self._attr_extra_state_attributes.update(get_attr_from_sia_event(sia_event)) - state_changed = self.update_state(sia_event) - if state_changed or sia_event.code == AVAILABILITY_EVENT_CODE: - self.async_reset_availability_cb() + + relevant_event = self.update_state(sia_event) + + if relevant_event: + self._attr_extra_state_attributes.update(get_attr_from_sia_event(sia_event)) + + if relevant_event or sia_event.code == AVAILABILITY_EVENT_CODE: + self._attr_available = True + self._cancel_post_interval_update_cb() + self.async_create_post_interval_update_cb() + self.async_write_ha_state() @abstractmethod def update_state(self, sia_event: SIAEvent) -> bool: - """Do the entity specific state updates.""" + """Do the entity specific state updates. + + Return True if the event was relevant for this entity. + """ @callback - def async_reset_availability_cb(self) -> None: - """Reset availability cb by cancelling the current and creating a new one.""" - self._attr_available = True - if self._cancel_availability_cb: - self._cancel_availability_cb() - self.async_create_availability_cb() - - def async_create_availability_cb(self) -> None: - """Create a availability cb and return the callback.""" - self._cancel_availability_cb = async_call_later( + def async_create_post_interval_update_cb(self) -> None: + """Create a port interval update cb and store the callback.""" + self._post_interval_update_cb_canceller = async_call_later( self.hass, get_unavailability_interval(self.ping_interval), - self.async_set_unavailable, + self.async_post_interval_update, ) @callback - def async_set_unavailable(self, _) -> None: - """Set unavailable.""" + def async_post_interval_update(self, _) -> None: + """Set unavailable after a ping interval.""" self._attr_available = False self.async_write_ha_state() + + @callback + def _cancel_post_interval_update_cb(self) -> None: + """Cancel the callback.""" + if self._post_interval_update_cb_canceller: + self._post_interval_update_cb_canceller() + self._post_interval_update_cb_canceller = None diff --git a/homeassistant/components/sia/utils.py b/homeassistant/components/sia/utils.py index 9150099656c9e2..cf52122a4999d9 100644 --- a/homeassistant/components/sia/utils.py +++ b/homeassistant/components/sia/utils.py @@ -8,11 +8,41 @@ from homeassistant.util.dt import utcnow -from .const import ATTR_CODE, ATTR_ID, ATTR_MESSAGE, ATTR_TIMESTAMP, ATTR_ZONE +from .const import ( + ATTR_CODE, + ATTR_ID, + ATTR_MESSAGE, + ATTR_TIMESTAMP, + ATTR_ZONE, + KEY_ALARM, + SIA_HUB_ZONE, +) PING_INTERVAL_MARGIN = 30 +def get_unique_id_and_name( + entry_id: str, + port: int, + account: str, + zone: int, + entity_key: str, +) -> tuple[str, str]: + """Return the unique_id and name for an entity.""" + return ( + ( + f"{entry_id}_{account}_{zone}" + if entity_key == KEY_ALARM + else f"{entry_id}_{account}_{zone}_{entity_key}" + ), + ( + f"{port} - {account} - {entity_key}" + if zone == SIA_HUB_ZONE + else f"{port} - {account} - zone {zone} - {entity_key}" + ), + ) + + def get_unavailability_interval(ping: int) -> float: """Return the interval to the next unavailability check.""" return timedelta(minutes=ping, seconds=PING_INTERVAL_MARGIN).total_seconds() From 0d2712e436d7120e62f310ee23442f67c8f65b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Tue, 15 Feb 2022 16:39:34 +0100 Subject: [PATCH 0664/1098] Add binary_sensor to Version integration (#66539) * Add binary_sensor to version integration * Add test to check we not create for local * Move _attr_icon to sensor * Update homeassistant/components/version/binary_sensor.py Co-authored-by: Martin Hjelmare Co-authored-by: Martin Hjelmare --- .../components/version/binary_sensor.py | 61 +++++++++++++++++++ homeassistant/components/version/const.py | 2 +- homeassistant/components/version/entity.py | 33 ++++++++++ homeassistant/components/version/sensor.py | 27 +------- tests/components/version/common.py | 13 +++- .../components/version/test_binary_sensor.py | 23 +++++++ 6 files changed, 130 insertions(+), 29 deletions(-) create mode 100644 homeassistant/components/version/binary_sensor.py create mode 100644 homeassistant/components/version/entity.py create mode 100644 tests/components/version/test_binary_sensor.py diff --git a/homeassistant/components/version/binary_sensor.py b/homeassistant/components/version/binary_sensor.py new file mode 100644 index 00000000000000..0e60b1b856cc49 --- /dev/null +++ b/homeassistant/components/version/binary_sensor.py @@ -0,0 +1,61 @@ +"""Binary sensor platform for Version.""" +from __future__ import annotations + +from awesomeversion import AwesomeVersion + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, + BinarySensorEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_NAME, __version__ as HA_VERSION +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import CONF_SOURCE, DEFAULT_NAME, DOMAIN +from .coordinator import VersionDataUpdateCoordinator +from .entity import VersionEntity + +HA_VERSION_OBJECT = AwesomeVersion(HA_VERSION) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up version binary_sensors.""" + coordinator: VersionDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] + if (source := config_entry.data[CONF_SOURCE]) == "local": + return + + if (entity_name := config_entry.data[CONF_NAME]) == DEFAULT_NAME: + entity_name = config_entry.title + + entities: list[VersionBinarySensor] = [ + VersionBinarySensor( + coordinator=coordinator, + entity_description=BinarySensorEntityDescription( + key=str(source), + name=f"{entity_name} Update Available", + device_class=BinarySensorDeviceClass.UPDATE, + entity_category=EntityCategory.DIAGNOSTIC, + ), + ) + ] + + async_add_entities(entities) + + +class VersionBinarySensor(VersionEntity, BinarySensorEntity): + """Binary sensor for version entities.""" + + entity_description: BinarySensorEntityDescription + + @property + def is_on(self) -> bool: + """Return true if the binary sensor is on.""" + version = self.coordinator.version + return version is not None and (version > HA_VERSION_OBJECT) diff --git a/homeassistant/components/version/const.py b/homeassistant/components/version/const.py index 8f1005961e83fc..9ee556c6b7ff2f 100644 --- a/homeassistant/components/version/const.py +++ b/homeassistant/components/version/const.py @@ -11,7 +11,7 @@ DOMAIN: Final = "version" LOGGER: Final[Logger] = getLogger(__package__) -PLATFORMS: Final[list[Platform]] = [Platform.SENSOR] +PLATFORMS: Final[list[Platform]] = [Platform.BINARY_SENSOR, Platform.SENSOR] UPDATE_COORDINATOR_UPDATE_INTERVAL: Final[timedelta] = timedelta(minutes=5) ENTRY_TYPE_SERVICE: Final = "service" diff --git a/homeassistant/components/version/entity.py b/homeassistant/components/version/entity.py new file mode 100644 index 00000000000000..1dcdc23fa9faca --- /dev/null +++ b/homeassistant/components/version/entity.py @@ -0,0 +1,33 @@ +"""Common entity class for Version integration.""" + +from homeassistant.helpers.device_registry import DeviceEntryType +from homeassistant.helpers.entity import DeviceInfo, EntityDescription +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN, HOME_ASSISTANT +from .coordinator import VersionDataUpdateCoordinator + + +class VersionEntity(CoordinatorEntity): + """Common entity class for Version integration.""" + + _attr_device_info = DeviceInfo( + name=f"{HOME_ASSISTANT} {DOMAIN.title()}", + identifiers={(HOME_ASSISTANT, DOMAIN)}, + manufacturer=HOME_ASSISTANT, + entry_type=DeviceEntryType.SERVICE, + ) + + coordinator: VersionDataUpdateCoordinator + + def __init__( + self, + coordinator: VersionDataUpdateCoordinator, + entity_description: EntityDescription, + ) -> None: + """Initialize version entities.""" + super().__init__(coordinator) + self.entity_description = entity_description + self._attr_unique_id = ( + f"{coordinator.config_entry.entry_id}_{entity_description.key}" + ) diff --git a/homeassistant/components/version/sensor.py b/homeassistant/components/version/sensor.py index 8b09d893afdf7c..f0583a190685de 100644 --- a/homeassistant/components/version/sensor.py +++ b/homeassistant/components/version/sensor.py @@ -15,11 +15,8 @@ from homeassistant.const import CONF_NAME from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.device_registry import DeviceEntryType -from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType -from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import ( ATTR_SOURCE, @@ -31,12 +28,12 @@ DEFAULT_NAME, DEFAULT_SOURCE, DOMAIN, - HOME_ASSISTANT, LOGGER, VALID_IMAGES, VALID_SOURCES, ) from .coordinator import VersionDataUpdateCoordinator +from .entity import VersionEntity PLATFORM_SCHEMA: Final[Schema] = SENSOR_PLATFORM_SCHEMA.extend( { @@ -91,30 +88,10 @@ async def async_setup_entry( async_add_entities(version_sensor_entities) -class VersionSensorEntity(CoordinatorEntity, SensorEntity): +class VersionSensorEntity(VersionEntity, SensorEntity): """Version sensor entity class.""" _attr_icon = "mdi:package-up" - _attr_device_info = DeviceInfo( - name=f"{HOME_ASSISTANT} {DOMAIN.title()}", - identifiers={(HOME_ASSISTANT, DOMAIN)}, - manufacturer=HOME_ASSISTANT, - entry_type=DeviceEntryType.SERVICE, - ) - - coordinator: VersionDataUpdateCoordinator - - def __init__( - self, - coordinator: VersionDataUpdateCoordinator, - entity_description: SensorEntityDescription, - ) -> None: - """Initialize version sensor entities.""" - super().__init__(coordinator) - self.entity_description = entity_description - self._attr_unique_id = ( - f"{coordinator.config_entry.entry_id}_{entity_description.key}" - ) @property def native_value(self) -> StateType: diff --git a/tests/components/version/common.py b/tests/components/version/common.py index 17d72d6de72d5b..b210a8600b8b2e 100644 --- a/tests/components/version/common.py +++ b/tests/components/version/common.py @@ -52,9 +52,17 @@ async def mock_get_version_update( await hass.async_block_till_done() -async def setup_version_integration(hass: HomeAssistant) -> MockConfigEntry: +async def setup_version_integration( + hass: HomeAssistant, + entry_data: dict[str, Any] | None = None, +) -> MockConfigEntry: """Set up the Version integration.""" - mock_entry = MockConfigEntry(**MOCK_VERSION_CONFIG_ENTRY_DATA) + mock_entry = MockConfigEntry( + **{ + **MOCK_VERSION_CONFIG_ENTRY_DATA, + "data": entry_data or MOCK_VERSION_CONFIG_ENTRY_DATA["data"], + } + ) mock_entry.add_to_hass(hass) with patch( @@ -65,7 +73,6 @@ async def setup_version_integration(hass: HomeAssistant) -> MockConfigEntry: assert await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() - assert hass.states.get("sensor.local_installation").state == MOCK_VERSION assert mock_entry.state == config_entries.ConfigEntryState.LOADED return mock_entry diff --git a/tests/components/version/test_binary_sensor.py b/tests/components/version/test_binary_sensor.py new file mode 100644 index 00000000000000..c9551ad4415698 --- /dev/null +++ b/tests/components/version/test_binary_sensor.py @@ -0,0 +1,23 @@ +"""The test for the version binary sensor platform.""" +from __future__ import annotations + +from homeassistant.components.version.const import DEFAULT_CONFIGURATION +from homeassistant.core import HomeAssistant + +from .common import setup_version_integration + + +async def test_version_binary_sensor_local_source(hass: HomeAssistant): + """Test the Version binary sensor with local source.""" + await setup_version_integration(hass) + + state = hass.states.get("binary_sensor.local_installation_update_available") + assert not state + + +async def test_version_binary_sensor(hass: HomeAssistant): + """Test the Version binary sensor.""" + await setup_version_integration(hass, {**DEFAULT_CONFIGURATION, "source": "pypi"}) + + state = hass.states.get("binary_sensor.local_installation_update_available") + assert state From 2b43293363cd3a45f5f729e14a8743ccdbe93193 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 15 Feb 2022 10:02:33 -0600 Subject: [PATCH 0665/1098] Switch unifiprotect to use integration discovery (#66569) Backstory: https://github.com/home-assistant/core/pull/65752#discussion_r800068914 --- .../components/unifiprotect/config_flow.py | 4 ++-- .../components/unifiprotect/discovery.py | 2 +- .../unifiprotect/test_config_flow.py | 24 +++++++++---------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/unifiprotect/config_flow.py b/homeassistant/components/unifiprotect/config_flow.py index 39e255bb715169..3039d5153e5b2d 100644 --- a/homeassistant/components/unifiprotect/config_flow.py +++ b/homeassistant/components/unifiprotect/config_flow.py @@ -82,10 +82,10 @@ async def _async_discovery_handoff(self) -> FlowResult: async_start_discovery(self.hass) return self.async_abort(reason="discovery_started") - async def async_step_discovery( + async def async_step_integration_discovery( self, discovery_info: DiscoveryInfoType ) -> FlowResult: - """Handle discovery.""" + """Handle integration discovery.""" self._discovered_device = discovery_info mac = _async_unifi_mac_from_hass(discovery_info["hw_addr"]) await self.async_set_unique_id(mac) diff --git a/homeassistant/components/unifiprotect/discovery.py b/homeassistant/components/unifiprotect/discovery.py index 4613aee954dae8..537e2fa11216f8 100644 --- a/homeassistant/components/unifiprotect/discovery.py +++ b/homeassistant/components/unifiprotect/discovery.py @@ -57,7 +57,7 @@ def async_trigger_discovery( hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data=asdict(device), ) ) diff --git a/tests/components/unifiprotect/test_config_flow.py b/tests/components/unifiprotect/test_config_flow.py index 68d90ff82eb120..4c7f12a69fa029 100644 --- a/tests/components/unifiprotect/test_config_flow.py +++ b/tests/components/unifiprotect/test_config_flow.py @@ -302,7 +302,7 @@ async def test_discovered_by_unifi_discovery_direct_connect( with _patch_discovery(): result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data=UNIFI_DISCOVERY_DICT, ) await hass.async_block_till_done() @@ -371,7 +371,7 @@ async def test_discovered_by_unifi_discovery_direct_connect_updated( ) as mock_setup_entry: result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data=UNIFI_DISCOVERY_DICT, ) await hass.async_block_till_done() @@ -407,7 +407,7 @@ async def test_discovered_by_unifi_discovery_direct_connect_updated_but_not_usin ) as mock_setup_entry: result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data=UNIFI_DISCOVERY_DICT, ) await hass.async_block_till_done() @@ -439,7 +439,7 @@ async def test_discovered_host_not_updated_if_existing_is_a_hostname( with _patch_discovery(): result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data=UNIFI_DISCOVERY_DICT, ) await hass.async_block_till_done() @@ -457,7 +457,7 @@ async def test_discovered_by_unifi_discovery( with _patch_discovery(): result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data=UNIFI_DISCOVERY_DICT, ) await hass.async_block_till_done() @@ -509,7 +509,7 @@ async def test_discovered_by_unifi_discovery_partial( with _patch_discovery(): result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data=UNIFI_DISCOVERY_DICT_PARTIAL, ) await hass.async_block_till_done() @@ -574,7 +574,7 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa with _patch_discovery(): result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data=UNIFI_DISCOVERY_DICT, ) await hass.async_block_till_done() @@ -604,7 +604,7 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa with _patch_discovery(): result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data=UNIFI_DISCOVERY_DICT, ) await hass.async_block_till_done() @@ -642,7 +642,7 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa ): result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data=other_ip_dict, ) await hass.async_block_till_done() @@ -678,7 +678,7 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa ): result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data=other_ip_dict, ) await hass.async_block_till_done() @@ -747,7 +747,7 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa with _patch_discovery(), patch.object(hass.loop, "getaddrinfo", return_value=[]): result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data=other_ip_dict, ) await hass.async_block_till_done() @@ -768,7 +768,7 @@ async def test_discovery_can_be_ignored(hass: HomeAssistant, mock_nvr: NVR) -> N with _patch_discovery(): result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data=UNIFI_DISCOVERY_DICT, ) await hass.async_block_till_done() From f069a37f7da0864f760710269eb3567aa33d5d56 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 15 Feb 2022 11:02:52 -0600 Subject: [PATCH 0666/1098] Allow integrations to request dhcp discovery flows for registered devices (#66528) --- homeassistant/components/dhcp/__init__.py | 31 +- homeassistant/generated/dhcp.py | 780 ++++------------------ homeassistant/loader.py | 8 +- script/hassfest/dhcp.py | 15 +- script/hassfest/manifest.py | 1 + tests/components/dhcp/test_init.py | 49 +- tests/test_loader.py | 2 + 7 files changed, 239 insertions(+), 647 deletions(-) diff --git a/homeassistant/components/dhcp/__init__.py b/homeassistant/components/dhcp/__init__.py index dd247c4cab9e23..ff67f77257b901 100644 --- a/homeassistant/components/dhcp/__init__.py +++ b/homeassistant/components/dhcp/__init__.py @@ -1,4 +1,5 @@ """The dhcp integration.""" +from __future__ import annotations from dataclasses import dataclass from datetime import timedelta @@ -35,7 +36,12 @@ from homeassistant.core import Event, HomeAssistant, State, callback from homeassistant.data_entry_flow import BaseServiceInfo from homeassistant.helpers import discovery_flow -from homeassistant.helpers.device_registry import format_mac +from homeassistant.helpers.device_registry import ( + CONNECTION_NETWORK_MAC, + DeviceRegistry, + async_get, + format_mac, +) from homeassistant.helpers.event import ( async_track_state_added_domain, async_track_time_interval, @@ -54,9 +60,11 @@ HOSTNAME: Final = "hostname" MAC_ADDRESS: Final = "macaddress" IP_ADDRESS: Final = "ip" +REGISTERED_DEVICES: Final = "registered_devices" DHCP_REQUEST = 3 SCAN_INTERVAL = timedelta(minutes=60) + _LOGGER = logging.getLogger(__name__) @@ -180,7 +188,20 @@ def async_process_client(self, ip_address, hostname, mac_address): ) matched_domains = set() + device_domains = set() + + dev_reg: DeviceRegistry = async_get(self.hass) + if device := dev_reg.async_get_device( + identifiers=set(), connections={(CONNECTION_NETWORK_MAC, uppercase_mac)} + ): + for entry_id in device.config_entries: + if entry := self.hass.config_entries.async_get_entry(entry_id): + device_domains.add(entry.domain) + for entry in self._integration_matchers: + if entry.get(REGISTERED_DEVICES) and not entry["domain"] in device_domains: + continue + if MAC_ADDRESS in entry and not fnmatch.fnmatch( uppercase_mac, entry[MAC_ADDRESS] ): @@ -192,14 +213,12 @@ def async_process_client(self, ip_address, hostname, mac_address): continue _LOGGER.debug("Matched %s against %s", data, entry) - if entry["domain"] in matched_domains: - # Only match once per domain - continue - matched_domains.add(entry["domain"]) + + for domain in matched_domains: discovery_flow.async_create_flow( self.hass, - entry["domain"], + domain, {"source": config_entries.SOURCE_DHCP}, DhcpServiceInfo( ip=ip_address, diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 2fbefe9bbca2ec..a10a2334c739c2 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -2,639 +2,153 @@ To update, run python3 -m script.hassfest """ +from __future__ import annotations # fmt: off -DHCP = [ - { - "domain": "august", - "hostname": "connect", - "macaddress": "D86162*" - }, - { - "domain": "august", - "hostname": "connect", - "macaddress": "B8B7F1*" - }, - { - "domain": "august", - "hostname": "connect", - "macaddress": "2C9FFB*" - }, - { - "domain": "august", - "hostname": "august*", - "macaddress": "E076D0*" - }, - { - "domain": "axis", - "hostname": "axis-00408c*", - "macaddress": "00408C*" - }, - { - "domain": "axis", - "hostname": "axis-accc8e*", - "macaddress": "ACCC8E*" - }, - { - "domain": "axis", - "hostname": "axis-b8a44f*", - "macaddress": "B8A44F*" - }, - { - "domain": "blink", - "hostname": "blink*", - "macaddress": "B85F98*" - }, - { - "domain": "blink", - "hostname": "blink*", - "macaddress": "00037F*" - }, - { - "domain": "blink", - "hostname": "blink*", - "macaddress": "20A171*" - }, - { - "domain": "broadlink", - "macaddress": "34EA34*" - }, - { - "domain": "broadlink", - "macaddress": "24DFA7*" - }, - { - "domain": "broadlink", - "macaddress": "A043B0*" - }, - { - "domain": "broadlink", - "macaddress": "B4430D*" - }, - { - "domain": "elkm1", - "macaddress": "00409D*" - }, - { - "domain": "emonitor", - "hostname": "emonitor*", - "macaddress": "0090C2*" - }, - { - "domain": "flume", - "hostname": "flume-gw-*" - }, - { - "domain": "flux_led", - "macaddress": "18B905*", - "hostname": "[ba][lk]*" - }, - { - "domain": "flux_led", - "macaddress": "249494*", - "hostname": "[ba][lk]*" - }, - { - "domain": "flux_led", - "macaddress": "7CB94C*", - "hostname": "[ba][lk]*" - }, - { - "domain": "flux_led", - "macaddress": "ACCF23*", - "hostname": "[hba][flk]*" - }, - { - "domain": "flux_led", - "macaddress": "B4E842*", - "hostname": "[ba][lk]*" - }, - { - "domain": "flux_led", - "macaddress": "F0FE6B*", - "hostname": "[hba][flk]*" - }, - { - "domain": "flux_led", - "macaddress": "8CCE4E*", - "hostname": "lwip*" - }, - { - "domain": "flux_led", - "hostname": "zengge_[0-9a-f][0-9a-f]_*" - }, - { - "domain": "flux_led", - "macaddress": "C82E47*", - "hostname": "sta*" - }, - { - "domain": "fronius", - "macaddress": "0003AC*" - }, - { - "domain": "goalzero", - "hostname": "yeti*" - }, - { - "domain": "gogogate2", - "hostname": "ismartgate*" - }, - { - "domain": "guardian", - "hostname": "gvc*", - "macaddress": "30AEA4*" - }, - { - "domain": "guardian", - "hostname": "gvc*", - "macaddress": "B4E62D*" - }, - { - "domain": "guardian", - "hostname": "guardian*", - "macaddress": "30AEA4*" - }, - { - "domain": "hunterdouglas_powerview", - "hostname": "hunter*", - "macaddress": "002674*" - }, - { - "domain": "isy994", - "hostname": "isy*", - "macaddress": "0021B9*" - }, - { - "domain": "lyric", - "hostname": "lyric-*", - "macaddress": "48A2E6*" - }, - { - "domain": "lyric", - "hostname": "lyric-*", - "macaddress": "B82CA0*" - }, - { - "domain": "lyric", - "hostname": "lyric-*", - "macaddress": "00D02D*" - }, - { - "domain": "myq", - "macaddress": "645299*" - }, - { - "domain": "nest", - "macaddress": "18B430*" - }, - { - "domain": "nest", - "macaddress": "641666*" - }, - { - "domain": "nest", - "macaddress": "D8EB46*" - }, - { - "domain": "nest", - "macaddress": "1C53F9*" - }, - { - "domain": "nexia", - "hostname": "xl857-*", - "macaddress": "000231*" - }, - { - "domain": "nuheat", - "hostname": "nuheat", - "macaddress": "002338*" - }, - { - "domain": "nuki", - "hostname": "nuki_bridge_*" - }, - { - "domain": "oncue", - "hostname": "kohlergen*", - "macaddress": "00146F*" - }, - { - "domain": "overkiz", - "hostname": "gateway*", - "macaddress": "F8811A*" - }, - { - "domain": "powerwall", - "hostname": "1118431-*" - }, - { - "domain": "rachio", - "hostname": "rachio-*", - "macaddress": "009D6B*" - }, - { - "domain": "rachio", - "hostname": "rachio-*", - "macaddress": "F0038C*" - }, - { - "domain": "rachio", - "hostname": "rachio-*", - "macaddress": "74C63B*" - }, - { - "domain": "rainforest_eagle", - "macaddress": "D8D5B9*" - }, - { - "domain": "ring", - "hostname": "ring*", - "macaddress": "0CAE7D*" - }, - { - "domain": "roomba", - "hostname": "irobot-*", - "macaddress": "501479*" - }, - { - "domain": "roomba", - "hostname": "roomba-*", - "macaddress": "80A589*" - }, - { - "domain": "roomba", - "hostname": "roomba-*", - "macaddress": "DCF505*" - }, - { - "domain": "samsungtv", - "hostname": "tizen*" - }, - { - "domain": "samsungtv", - "macaddress": "8CC8CD*" - }, - { - "domain": "samsungtv", - "macaddress": "606BBD*" - }, - { - "domain": "samsungtv", - "macaddress": "F47B5E*" - }, - { - "domain": "samsungtv", - "macaddress": "4844F7*" - }, - { - "domain": "screenlogic", - "hostname": "pentair: *", - "macaddress": "00C033*" - }, - { - "domain": "sense", - "hostname": "sense-*", - "macaddress": "009D6B*" - }, - { - "domain": "sense", - "hostname": "sense-*", - "macaddress": "DCEFCA*" - }, - { - "domain": "sense", - "hostname": "sense-*", - "macaddress": "A4D578*" - }, - { - "domain": "senseme", - "macaddress": "20F85E*" - }, - { - "domain": "sensibo", - "hostname": "sensibo*" - }, - { - "domain": "simplisafe", - "hostname": "simplisafe*", - "macaddress": "30AEA4*" - }, - { - "domain": "smartthings", - "hostname": "st*", - "macaddress": "24FD5B*" - }, - { - "domain": "smartthings", - "hostname": "smartthings*", - "macaddress": "24FD5B*" - }, - { - "domain": "smartthings", - "hostname": "hub*", - "macaddress": "24FD5B*" - }, - { - "domain": "smartthings", - "hostname": "hub*", - "macaddress": "D052A8*" - }, - { - "domain": "smartthings", - "hostname": "hub*", - "macaddress": "286D97*" - }, - { - "domain": "solaredge", - "hostname": "target", - "macaddress": "002702*" - }, - { - "domain": "somfy_mylink", - "hostname": "somfy_*", - "macaddress": "B8B7F1*" - }, - { - "domain": "squeezebox", - "hostname": "squeezebox*", - "macaddress": "000420*" - }, - { - "domain": "steamist", - "macaddress": "001E0C*", - "hostname": "my[45]50*" - }, - { - "domain": "tado", - "hostname": "tado*" - }, - { - "domain": "tesla_wall_connector", - "hostname": "teslawallconnector_*", - "macaddress": "DC44271*" - }, - { - "domain": "tesla_wall_connector", - "hostname": "teslawallconnector_*", - "macaddress": "98ED5C*" - }, - { - "domain": "tesla_wall_connector", - "hostname": "teslawallconnector_*", - "macaddress": "4CFCAA*" - }, - { - "domain": "tolo", - "hostname": "usr-tcp232-ed2" - }, - { - "domain": "toon", - "hostname": "eneco-*", - "macaddress": "74C63B*" - }, - { - "domain": "tplink", - "hostname": "k[lp]*", - "macaddress": "60A4B7*" - }, - { - "domain": "tplink", - "hostname": "k[lp]*", - "macaddress": "005F67*" - }, - { - "domain": "tplink", - "hostname": "k[lp]*", - "macaddress": "1027F5*" - }, - { - "domain": "tplink", - "hostname": "k[lp]*", - "macaddress": "403F8C*" - }, - { - "domain": "tplink", - "hostname": "k[lp]*", - "macaddress": "C0C9E3*" - }, - { - "domain": "tplink", - "hostname": "ep*", - "macaddress": "E848B8*" - }, - { - "domain": "tplink", - "hostname": "k[lp]*", - "macaddress": "E848B8*" - }, - { - "domain": "tplink", - "hostname": "k[lp]*", - "macaddress": "909A4A*" - }, - { - "domain": "tplink", - "hostname": "hs*", - "macaddress": "1C3BF3*" - }, - { - "domain": "tplink", - "hostname": "hs*", - "macaddress": "50C7BF*" - }, - { - "domain": "tplink", - "hostname": "hs*", - "macaddress": "68FF7B*" - }, - { - "domain": "tplink", - "hostname": "hs*", - "macaddress": "98DAC4*" - }, - { - "domain": "tplink", - "hostname": "hs*", - "macaddress": "B09575*" - }, - { - "domain": "tplink", - "hostname": "hs*", - "macaddress": "C006C3*" - }, - { - "domain": "tplink", - "hostname": "ep*", - "macaddress": "003192*" - }, - { - "domain": "tplink", - "hostname": "k[lp]*", - "macaddress": "003192*" - }, - { - "domain": "tplink", - "hostname": "k[lp]*", - "macaddress": "1C3BF3*" - }, - { - "domain": "tplink", - "hostname": "k[lp]*", - "macaddress": "50C7BF*" - }, - { - "domain": "tplink", - "hostname": "k[lp]*", - "macaddress": "68FF7B*" - }, - { - "domain": "tplink", - "hostname": "k[lp]*", - "macaddress": "98DAC4*" - }, - { - "domain": "tplink", - "hostname": "k[lp]*", - "macaddress": "B09575*" - }, - { - "domain": "tplink", - "hostname": "k[lp]*", - "macaddress": "C006C3*" - }, - { - "domain": "tplink", - "hostname": "lb*", - "macaddress": "1C3BF3*" - }, - { - "domain": "tplink", - "hostname": "lb*", - "macaddress": "50C7BF*" - }, - { - "domain": "tplink", - "hostname": "lb*", - "macaddress": "68FF7B*" - }, - { - "domain": "tplink", - "hostname": "lb*", - "macaddress": "98DAC4*" - }, - { - "domain": "tplink", - "hostname": "lb*", - "macaddress": "B09575*" - }, - { - "domain": "tuya", - "macaddress": "105A17*" - }, - { - "domain": "tuya", - "macaddress": "10D561*" - }, - { - "domain": "tuya", - "macaddress": "1869D8*" - }, - { - "domain": "tuya", - "macaddress": "381F8D*" - }, - { - "domain": "tuya", - "macaddress": "508A06*" - }, - { - "domain": "tuya", - "macaddress": "68572D*" - }, - { - "domain": "tuya", - "macaddress": "708976*" - }, - { - "domain": "tuya", - "macaddress": "7CF666*" - }, - { - "domain": "tuya", - "macaddress": "84E342*" - }, - { - "domain": "tuya", - "macaddress": "D4A651*" - }, - { - "domain": "tuya", - "macaddress": "D81F12*" - }, - { - "domain": "twinkly", - "hostname": "twinkly_*" - }, - { - "domain": "unifiprotect", - "macaddress": "B4FBE4*" - }, - { - "domain": "unifiprotect", - "macaddress": "802AA8*" - }, - { - "domain": "unifiprotect", - "macaddress": "F09FC2*" - }, - { - "domain": "unifiprotect", - "macaddress": "68D79A*" - }, - { - "domain": "unifiprotect", - "macaddress": "18E829*" - }, - { - "domain": "unifiprotect", - "macaddress": "245A4C*" - }, - { - "domain": "unifiprotect", - "macaddress": "784558*" - }, - { - "domain": "unifiprotect", - "macaddress": "E063DA*" - }, - { - "domain": "unifiprotect", - "macaddress": "265A4C*" - }, - { - "domain": "unifiprotect", - "macaddress": "74ACB9*" - }, - { - "domain": "verisure", - "macaddress": "0023C1*" - }, - { - "domain": "vicare", - "macaddress": "B87424*" - }, - { - "domain": "wiz", - "macaddress": "A8BB50*" - }, - { - "domain": "wiz", - "hostname": "wiz_*" - }, - { - "domain": "yeelight", - "hostname": "yeelink-*" - } -] +DHCP: list[dict[str, str | bool]] = [ + {'domain': 'august', 'hostname': 'connect', 'macaddress': 'D86162*'}, + {'domain': 'august', 'hostname': 'connect', 'macaddress': 'B8B7F1*'}, + {'domain': 'august', 'hostname': 'connect', 'macaddress': '2C9FFB*'}, + {'domain': 'august', 'hostname': 'august*', 'macaddress': 'E076D0*'}, + {'domain': 'axis', 'hostname': 'axis-00408c*', 'macaddress': '00408C*'}, + {'domain': 'axis', 'hostname': 'axis-accc8e*', 'macaddress': 'ACCC8E*'}, + {'domain': 'axis', 'hostname': 'axis-b8a44f*', 'macaddress': 'B8A44F*'}, + {'domain': 'blink', 'hostname': 'blink*', 'macaddress': 'B85F98*'}, + {'domain': 'blink', 'hostname': 'blink*', 'macaddress': '00037F*'}, + {'domain': 'blink', 'hostname': 'blink*', 'macaddress': '20A171*'}, + {'domain': 'broadlink', 'macaddress': '34EA34*'}, + {'domain': 'broadlink', 'macaddress': '24DFA7*'}, + {'domain': 'broadlink', 'macaddress': 'A043B0*'}, + {'domain': 'broadlink', 'macaddress': 'B4430D*'}, + {'domain': 'elkm1', 'macaddress': '00409D*'}, + {'domain': 'emonitor', 'hostname': 'emonitor*', 'macaddress': '0090C2*'}, + {'domain': 'flume', 'hostname': 'flume-gw-*'}, + {'domain': 'flux_led', 'hostname': '[ba][lk]*', 'macaddress': '18B905*'}, + {'domain': 'flux_led', 'hostname': '[ba][lk]*', 'macaddress': '249494*'}, + {'domain': 'flux_led', 'hostname': '[ba][lk]*', 'macaddress': '7CB94C*'}, + {'domain': 'flux_led', 'hostname': '[hba][flk]*', 'macaddress': 'ACCF23*'}, + {'domain': 'flux_led', 'hostname': '[ba][lk]*', 'macaddress': 'B4E842*'}, + {'domain': 'flux_led', 'hostname': '[hba][flk]*', 'macaddress': 'F0FE6B*'}, + {'domain': 'flux_led', 'hostname': 'lwip*', 'macaddress': '8CCE4E*'}, + {'domain': 'flux_led', 'hostname': 'zengge_[0-9a-f][0-9a-f]_*'}, + {'domain': 'flux_led', 'hostname': 'sta*', 'macaddress': 'C82E47*'}, + {'domain': 'fronius', 'macaddress': '0003AC*'}, + {'domain': 'goalzero', 'hostname': 'yeti*'}, + {'domain': 'gogogate2', 'hostname': 'ismartgate*'}, + {'domain': 'guardian', 'hostname': 'gvc*', 'macaddress': '30AEA4*'}, + {'domain': 'guardian', 'hostname': 'gvc*', 'macaddress': 'B4E62D*'}, + {'domain': 'guardian', 'hostname': 'guardian*', 'macaddress': '30AEA4*'}, + {'domain': 'hunterdouglas_powerview', + 'hostname': 'hunter*', + 'macaddress': '002674*'}, + {'domain': 'isy994', 'hostname': 'isy*', 'macaddress': '0021B9*'}, + {'domain': 'lyric', 'hostname': 'lyric-*', 'macaddress': '48A2E6*'}, + {'domain': 'lyric', 'hostname': 'lyric-*', 'macaddress': 'B82CA0*'}, + {'domain': 'lyric', 'hostname': 'lyric-*', 'macaddress': '00D02D*'}, + {'domain': 'myq', 'macaddress': '645299*'}, + {'domain': 'nest', 'macaddress': '18B430*'}, + {'domain': 'nest', 'macaddress': '641666*'}, + {'domain': 'nest', 'macaddress': 'D8EB46*'}, + {'domain': 'nest', 'macaddress': '1C53F9*'}, + {'domain': 'nexia', 'hostname': 'xl857-*', 'macaddress': '000231*'}, + {'domain': 'nuheat', 'hostname': 'nuheat', 'macaddress': '002338*'}, + {'domain': 'nuki', 'hostname': 'nuki_bridge_*'}, + {'domain': 'oncue', 'hostname': 'kohlergen*', 'macaddress': '00146F*'}, + {'domain': 'overkiz', 'hostname': 'gateway*', 'macaddress': 'F8811A*'}, + {'domain': 'powerwall', 'hostname': '1118431-*'}, + {'domain': 'rachio', 'hostname': 'rachio-*', 'macaddress': '009D6B*'}, + {'domain': 'rachio', 'hostname': 'rachio-*', 'macaddress': 'F0038C*'}, + {'domain': 'rachio', 'hostname': 'rachio-*', 'macaddress': '74C63B*'}, + {'domain': 'rainforest_eagle', 'macaddress': 'D8D5B9*'}, + {'domain': 'ring', 'hostname': 'ring*', 'macaddress': '0CAE7D*'}, + {'domain': 'roomba', 'hostname': 'irobot-*', 'macaddress': '501479*'}, + {'domain': 'roomba', 'hostname': 'roomba-*', 'macaddress': '80A589*'}, + {'domain': 'roomba', 'hostname': 'roomba-*', 'macaddress': 'DCF505*'}, + {'domain': 'samsungtv', 'hostname': 'tizen*'}, + {'domain': 'samsungtv', 'macaddress': '8CC8CD*'}, + {'domain': 'samsungtv', 'macaddress': '606BBD*'}, + {'domain': 'samsungtv', 'macaddress': 'F47B5E*'}, + {'domain': 'samsungtv', 'macaddress': '4844F7*'}, + {'domain': 'screenlogic', 'hostname': 'pentair: *', 'macaddress': '00C033*'}, + {'domain': 'sense', 'hostname': 'sense-*', 'macaddress': '009D6B*'}, + {'domain': 'sense', 'hostname': 'sense-*', 'macaddress': 'DCEFCA*'}, + {'domain': 'sense', 'hostname': 'sense-*', 'macaddress': 'A4D578*'}, + {'domain': 'senseme', 'macaddress': '20F85E*'}, + {'domain': 'sensibo', 'hostname': 'sensibo*'}, + {'domain': 'simplisafe', 'hostname': 'simplisafe*', 'macaddress': '30AEA4*'}, + {'domain': 'smartthings', 'hostname': 'st*', 'macaddress': '24FD5B*'}, + {'domain': 'smartthings', 'hostname': 'smartthings*', 'macaddress': '24FD5B*'}, + {'domain': 'smartthings', 'hostname': 'hub*', 'macaddress': '24FD5B*'}, + {'domain': 'smartthings', 'hostname': 'hub*', 'macaddress': 'D052A8*'}, + {'domain': 'smartthings', 'hostname': 'hub*', 'macaddress': '286D97*'}, + {'domain': 'solaredge', 'hostname': 'target', 'macaddress': '002702*'}, + {'domain': 'somfy_mylink', 'hostname': 'somfy_*', 'macaddress': 'B8B7F1*'}, + {'domain': 'squeezebox', 'hostname': 'squeezebox*', 'macaddress': '000420*'}, + {'domain': 'steamist', 'hostname': 'my[45]50*', 'macaddress': '001E0C*'}, + {'domain': 'tado', 'hostname': 'tado*'}, + {'domain': 'tesla_wall_connector', + 'hostname': 'teslawallconnector_*', + 'macaddress': 'DC44271*'}, + {'domain': 'tesla_wall_connector', + 'hostname': 'teslawallconnector_*', + 'macaddress': '98ED5C*'}, + {'domain': 'tesla_wall_connector', + 'hostname': 'teslawallconnector_*', + 'macaddress': '4CFCAA*'}, + {'domain': 'tolo', 'hostname': 'usr-tcp232-ed2'}, + {'domain': 'toon', 'hostname': 'eneco-*', 'macaddress': '74C63B*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '60A4B7*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '005F67*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '1027F5*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '403F8C*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': 'C0C9E3*'}, + {'domain': 'tplink', 'hostname': 'ep*', 'macaddress': 'E848B8*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': 'E848B8*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '909A4A*'}, + {'domain': 'tplink', 'hostname': 'hs*', 'macaddress': '1C3BF3*'}, + {'domain': 'tplink', 'hostname': 'hs*', 'macaddress': '50C7BF*'}, + {'domain': 'tplink', 'hostname': 'hs*', 'macaddress': '68FF7B*'}, + {'domain': 'tplink', 'hostname': 'hs*', 'macaddress': '98DAC4*'}, + {'domain': 'tplink', 'hostname': 'hs*', 'macaddress': 'B09575*'}, + {'domain': 'tplink', 'hostname': 'hs*', 'macaddress': 'C006C3*'}, + {'domain': 'tplink', 'hostname': 'ep*', 'macaddress': '003192*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '003192*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '1C3BF3*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '50C7BF*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '68FF7B*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '98DAC4*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': 'B09575*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': 'C006C3*'}, + {'domain': 'tplink', 'hostname': 'lb*', 'macaddress': '1C3BF3*'}, + {'domain': 'tplink', 'hostname': 'lb*', 'macaddress': '50C7BF*'}, + {'domain': 'tplink', 'hostname': 'lb*', 'macaddress': '68FF7B*'}, + {'domain': 'tplink', 'hostname': 'lb*', 'macaddress': '98DAC4*'}, + {'domain': 'tplink', 'hostname': 'lb*', 'macaddress': 'B09575*'}, + {'domain': 'tuya', 'macaddress': '105A17*'}, + {'domain': 'tuya', 'macaddress': '10D561*'}, + {'domain': 'tuya', 'macaddress': '1869D8*'}, + {'domain': 'tuya', 'macaddress': '381F8D*'}, + {'domain': 'tuya', 'macaddress': '508A06*'}, + {'domain': 'tuya', 'macaddress': '68572D*'}, + {'domain': 'tuya', 'macaddress': '708976*'}, + {'domain': 'tuya', 'macaddress': '7CF666*'}, + {'domain': 'tuya', 'macaddress': '84E342*'}, + {'domain': 'tuya', 'macaddress': 'D4A651*'}, + {'domain': 'tuya', 'macaddress': 'D81F12*'}, + {'domain': 'twinkly', 'hostname': 'twinkly_*'}, + {'domain': 'unifiprotect', 'macaddress': 'B4FBE4*'}, + {'domain': 'unifiprotect', 'macaddress': '802AA8*'}, + {'domain': 'unifiprotect', 'macaddress': 'F09FC2*'}, + {'domain': 'unifiprotect', 'macaddress': '68D79A*'}, + {'domain': 'unifiprotect', 'macaddress': '18E829*'}, + {'domain': 'unifiprotect', 'macaddress': '245A4C*'}, + {'domain': 'unifiprotect', 'macaddress': '784558*'}, + {'domain': 'unifiprotect', 'macaddress': 'E063DA*'}, + {'domain': 'unifiprotect', 'macaddress': '265A4C*'}, + {'domain': 'unifiprotect', 'macaddress': '74ACB9*'}, + {'domain': 'verisure', 'macaddress': '0023C1*'}, + {'domain': 'vicare', 'macaddress': 'B87424*'}, + {'domain': 'wiz', 'macaddress': 'A8BB50*'}, + {'domain': 'wiz', 'hostname': 'wiz_*'}, + {'domain': 'yeelight', 'hostname': 'yeelink-*'}] diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 7217fd5940bf15..c02fa18eefbb5f 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -82,7 +82,7 @@ class Manifest(TypedDict, total=False): mqtt: list[str] ssdp: list[dict[str, str]] zeroconf: list[str | dict[str, str]] - dhcp: list[dict[str, str]] + dhcp: list[dict[str, bool | str]] usb: list[dict[str, str]] homekit: dict[str, list[str]] is_built_in: bool @@ -228,9 +228,9 @@ async def async_get_zeroconf( return zeroconf -async def async_get_dhcp(hass: HomeAssistant) -> list[dict[str, str]]: +async def async_get_dhcp(hass: HomeAssistant) -> list[dict[str, str | bool]]: """Return cached list of dhcp types.""" - dhcp: list[dict[str, str]] = DHCP.copy() + dhcp: list[dict[str, str | bool]] = DHCP.copy() integrations = await async_get_custom_components(hass) for integration in integrations.values(): @@ -474,7 +474,7 @@ def zeroconf(self) -> list[str | dict[str, str]] | None: return self.manifest.get("zeroconf") @property - def dhcp(self) -> list[dict[str, str]] | None: + def dhcp(self) -> list[dict[str, str | bool]] | None: """Return Integration dhcp entries.""" return self.manifest.get("dhcp") diff --git a/script/hassfest/dhcp.py b/script/hassfest/dhcp.py index c746c64e46f0d8..1aca6a1f68da11 100644 --- a/script/hassfest/dhcp.py +++ b/script/hassfest/dhcp.py @@ -1,7 +1,8 @@ """Generate dhcp file.""" from __future__ import annotations -import json +import pprint +import re from .model import Config, Integration @@ -10,10 +11,11 @@ To update, run python3 -m script.hassfest \"\"\" +from __future__ import annotations # fmt: off -DHCP = {} +DHCP: list[dict[str, str | bool]] = {} """.strip() @@ -35,7 +37,14 @@ def generate_and_validate(integrations: list[dict[str, str]]): for entry in match_types: match_list.append({"domain": domain, **entry}) - return BASE.format(json.dumps(match_list, indent=4)) + # JSON will format `True` as `true` + # re.sub for flake8 E128 + formatted = pprint.pformat(match_list) + formatted_aligned_continuation = re.sub(r"^\[\{", "[\n {", formatted) + formatted_align_indent = re.sub( + r"(?m)^ ", " ", formatted_aligned_continuation, flags=re.MULTILINE, count=0 + ) + return BASE.format(formatted_align_indent) def validate(integrations: dict[str, Integration], config: Config): diff --git a/script/hassfest/manifest.py b/script/hassfest/manifest.py index 55cfd44bae9f5c..d146621b416912 100644 --- a/script/hassfest/manifest.py +++ b/script/hassfest/manifest.py @@ -218,6 +218,7 @@ def verify_wildcard(value: str): str, verify_uppercase, verify_wildcard ), vol.Optional("hostname"): vol.All(str, verify_lowercase), + vol.Optional("registered_devices"): cv.boolean, } ) ], diff --git a/tests/components/dhcp/test_init.py b/tests/components/dhcp/test_init.py index 0956230d7871c5..3650ed3298763e 100644 --- a/tests/components/dhcp/test_init.py +++ b/tests/components/dhcp/test_init.py @@ -25,10 +25,11 @@ STATE_HOME, STATE_NOT_HOME, ) +import homeassistant.helpers.device_registry as dr from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -from tests.common import async_fire_time_changed +from tests.common import MockConfigEntry, async_fire_time_changed # connect b8:b7:f1:6d:b5:33 192.168.210.56 RAW_DHCP_REQUEST = ( @@ -207,6 +208,52 @@ async def test_dhcp_renewal_match_hostname_and_macaddress(hass): ) +async def test_registered_devices(hass): + """Test discovery flows are created for registered devices.""" + integration_matchers = [ + {"domain": "not-matching", "registered_devices": True}, + {"domain": "mock-domain", "registered_devices": True}, + ] + + packet = Ether(RAW_DHCP_RENEWAL) + + registry = dr.async_get(hass) + config_entry = MockConfigEntry(domain="mock-domain", data={}) + config_entry.add_to_hass(hass) + registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={(dr.CONNECTION_NETWORK_MAC, "50147903852c")}, + name="name", + ) + # Not enabled should not get flows + config_entry2 = MockConfigEntry(domain="mock-domain-2", data={}) + config_entry2.add_to_hass(hass) + registry.async_get_or_create( + config_entry_id=config_entry2.entry_id, + connections={(dr.CONNECTION_NETWORK_MAC, "50147903852c")}, + name="name", + ) + + async_handle_dhcp_packet = await _async_get_handle_dhcp_packet( + hass, integration_matchers + ) + with patch.object(hass.config_entries.flow, "async_init") as mock_init: + await async_handle_dhcp_packet(packet) + # Ensure no change is ignored + await async_handle_dhcp_packet(packet) + + assert len(mock_init.mock_calls) == 1 + assert mock_init.mock_calls[0][1][0] == "mock-domain" + assert mock_init.mock_calls[0][2]["context"] == { + "source": config_entries.SOURCE_DHCP + } + assert mock_init.mock_calls[0][2]["data"] == dhcp.DhcpServiceInfo( + ip="192.168.1.120", + hostname="irobot-ae9ec12dd3b04885bcbfa36afb01e1cc", + macaddress="50147903852c", + ) + + async def test_dhcp_match_hostname(hass): """Test matching based on hostname only.""" integration_matchers = [{"domain": "mock-domain", "hostname": "connect"}] diff --git a/tests/test_loader.py b/tests/test_loader.py index 8cc923840c2698..9f2aaff58b7b80 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -203,6 +203,7 @@ def test_integration_properties(hass): {"hostname": "tesla_*", "macaddress": "4CFCAA*"}, {"hostname": "tesla_*", "macaddress": "044EAF*"}, {"hostname": "tesla_*", "macaddress": "98ED5C*"}, + {"registered_devices": True}, ], "usb": [ {"vid": "10C4", "pid": "EA60"}, @@ -233,6 +234,7 @@ def test_integration_properties(hass): {"hostname": "tesla_*", "macaddress": "4CFCAA*"}, {"hostname": "tesla_*", "macaddress": "044EAF*"}, {"hostname": "tesla_*", "macaddress": "98ED5C*"}, + {"registered_devices": True}, ] assert integration.usb == [ {"vid": "10C4", "pid": "EA60"}, From 9f1c58cda3b45758dad68f568e42e2ad8c9f6ac9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 15 Feb 2022 13:33:45 -0600 Subject: [PATCH 0667/1098] Enable dhcp flows for tplink registered devices (#66592) References: https://github.com/home-assistant/developers.home-assistant/pull/1212 https://github.com/home-assistant/core/pull/66528 I am doing these one at a time to make sure codeowners are aware and do not glance over the PR because it has a lot of integrations in it --- homeassistant/components/tplink/manifest.json | 1 + homeassistant/generated/dhcp.py | 1 + 2 files changed, 2 insertions(+) diff --git a/homeassistant/components/tplink/manifest.json b/homeassistant/components/tplink/manifest.json index 4d4738c39f9f20..20f2e9dc171efa 100644 --- a/homeassistant/components/tplink/manifest.json +++ b/homeassistant/components/tplink/manifest.json @@ -9,6 +9,7 @@ "quality_scale": "platinum", "iot_class": "local_polling", "dhcp": [ + {"registered_devices": true}, { "hostname": "k[lp]*", "macaddress": "60A4B7*" diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index a10a2334c739c2..d49ab1b2891090 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -98,6 +98,7 @@ 'macaddress': '4CFCAA*'}, {'domain': 'tolo', 'hostname': 'usr-tcp232-ed2'}, {'domain': 'toon', 'hostname': 'eneco-*', 'macaddress': '74C63B*'}, + {'domain': 'tplink', 'registered_devices': True}, {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '60A4B7*'}, {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '005F67*'}, {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '1027F5*'}, From 30528e0de0b6df017e0fdce2643e997a866c9875 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Tue, 15 Feb 2022 12:24:33 -0800 Subject: [PATCH 0668/1098] Add extra entity descriptions to Overkiz integration (#66093) --- homeassistant/components/overkiz/number.py | 28 ++++++++++++++++++++++ homeassistant/components/overkiz/select.py | 11 +++++++++ homeassistant/components/overkiz/sensor.py | 7 ++++++ 3 files changed, 46 insertions(+) diff --git a/homeassistant/components/overkiz/number.py b/homeassistant/components/overkiz/number.py index d0543eefd5e49a..40d04b9bf71a43 100644 --- a/homeassistant/components/overkiz/number.py +++ b/homeassistant/components/overkiz/number.py @@ -48,6 +48,34 @@ class OverkizNumberDescription(NumberEntityDescription, OverkizNumberDescription max_value=4, entity_category=EntityCategory.CONFIG, ), + # SomfyHeatingTemperatureInterface + OverkizNumberDescription( + key=OverkizState.CORE_ECO_ROOM_TEMPERATURE, + name="Eco Room Temperature", + icon="mdi:thermometer", + command=OverkizCommand.SET_ECO_TEMPERATURE, + min_value=6, + max_value=29, + entity_category=EntityCategory.CONFIG, + ), + OverkizNumberDescription( + key=OverkizState.CORE_COMFORT_ROOM_TEMPERATURE, + name="Comfort Room Temperature", + icon="mdi:home-thermometer-outline", + command=OverkizCommand.SET_COMFORT_TEMPERATURE, + min_value=7, + max_value=30, + entity_category=EntityCategory.CONFIG, + ), + OverkizNumberDescription( + key=OverkizState.CORE_SECURED_POSITION_TEMPERATURE, + name="Freeze Protection Temperature", + icon="mdi:sun-thermometer-outline", + command=OverkizCommand.SET_SECURED_POSITION_TEMPERATURE, + min_value=5, + max_value=15, + entity_category=EntityCategory.CONFIG, + ), ] SUPPORTED_STATES = {description.key: description for description in NUMBER_DESCRIPTIONS} diff --git a/homeassistant/components/overkiz/select.py b/homeassistant/components/overkiz/select.py index f1e48d83469f72..c097b04d4ebde6 100644 --- a/homeassistant/components/overkiz/select.py +++ b/homeassistant/components/overkiz/select.py @@ -73,6 +73,17 @@ def _select_option_memorized_simple_volume( entity_category=EntityCategory.CONFIG, device_class=OverkizDeviceClass.MEMORIZED_SIMPLE_VOLUME, ), + # SomfyHeatingTemperatureInterface + OverkizSelectDescription( + key=OverkizState.OVP_HEATING_TEMPERATURE_INTERFACE_OPERATING_MODE, + name="Operating Mode", + icon="mdi:sun-snowflake", + options=[OverkizCommandParam.HEATING, OverkizCommandParam.COOLING], + select_option=lambda option, execute_command: execute_command( + OverkizCommand.SET_OPERATING_MODE, option + ), + entity_category=EntityCategory.CONFIG, + ), ] SUPPORTED_STATES = {description.key: description for description in SELECT_DESCRIPTIONS} diff --git a/homeassistant/components/overkiz/sensor.py b/homeassistant/components/overkiz/sensor.py index 1e5eb0881824c2..10de6f699dde01 100644 --- a/homeassistant/components/overkiz/sensor.py +++ b/homeassistant/components/overkiz/sensor.py @@ -359,6 +359,13 @@ class OverkizSensorDescription(SensorEntityDescription): key=OverkizState.IO_ELECTRIC_BOOSTER_OPERATING_TIME, name="Electric Booster Operating Time", ), + # Cover + OverkizSensorDescription( + key=OverkizState.CORE_TARGET_CLOSURE, + name="Target Closure", + native_unit_of_measurement=PERCENTAGE, + entity_registry_enabled_default=False, + ), ] SUPPORTED_STATES = {description.key: description for description in SENSOR_DESCRIPTIONS} From c5ae43144d941f75ec156b21d9a9a613c3730d11 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 15 Feb 2022 14:32:10 -0600 Subject: [PATCH 0669/1098] Enable dhcp flows for axis registered devices (#66581) References: https://github.com/home-assistant/developers.home-assistant/pull/1212 https://github.com/home-assistant/core/pull/66528 I am doing these one at a time to make sure codeowners are aware and do not glance over the PR because it has a lot of integrations in it --- homeassistant/components/axis/manifest.json | 1 + homeassistant/generated/dhcp.py | 1 + 2 files changed, 2 insertions(+) diff --git a/homeassistant/components/axis/manifest.json b/homeassistant/components/axis/manifest.json index 41580aa39d01cb..c07db62a04f873 100644 --- a/homeassistant/components/axis/manifest.json +++ b/homeassistant/components/axis/manifest.json @@ -5,6 +5,7 @@ "documentation": "https://www.home-assistant.io/integrations/axis", "requirements": ["axis==44"], "dhcp": [ + {"registered_devices": true}, { "hostname": "axis-00408c*", "macaddress": "00408C*" diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index d49ab1b2891090..a4dd8b092c869e 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -11,6 +11,7 @@ {'domain': 'august', 'hostname': 'connect', 'macaddress': 'B8B7F1*'}, {'domain': 'august', 'hostname': 'connect', 'macaddress': '2C9FFB*'}, {'domain': 'august', 'hostname': 'august*', 'macaddress': 'E076D0*'}, + {'domain': 'axis', 'registered_devices': True}, {'domain': 'axis', 'hostname': 'axis-00408c*', 'macaddress': '00408C*'}, {'domain': 'axis', 'hostname': 'axis-accc8e*', 'macaddress': 'ACCC8E*'}, {'domain': 'axis', 'hostname': 'axis-b8a44f*', 'macaddress': 'B8A44F*'}, From 5568531f74438b4f014d028cd5913517b6dc6d3b Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Tue, 15 Feb 2022 12:44:53 -0800 Subject: [PATCH 0670/1098] Improve exception catching and handling in Overkiz integration (#66604) --- homeassistant/components/overkiz/coordinator.py | 6 ++++++ homeassistant/components/overkiz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/overkiz/coordinator.py b/homeassistant/components/overkiz/coordinator.py index ff7cd429ea5fe0..cbf83a90963cfd 100644 --- a/homeassistant/components/overkiz/coordinator.py +++ b/homeassistant/components/overkiz/coordinator.py @@ -9,8 +9,10 @@ from pyoverkiz.enums import EventName, ExecutionState from pyoverkiz.exceptions import ( BadCredentialsException, + InvalidEventListenerIdException, MaintenanceException, NotAuthenticatedException, + TooManyConcurrentRequestsException, TooManyRequestsException, ) from pyoverkiz.models import Device, Event, Place @@ -67,10 +69,14 @@ async def _async_update_data(self) -> dict[str, Device]: events = await self.client.fetch_events() except BadCredentialsException as exception: raise ConfigEntryAuthFailed("Invalid authentication.") from exception + except TooManyConcurrentRequestsException as exception: + raise UpdateFailed("Too many concurrent requests.") from exception except TooManyRequestsException as exception: raise UpdateFailed("Too many requests, try again later.") from exception except MaintenanceException as exception: raise UpdateFailed("Server is down for maintenance.") from exception + except InvalidEventListenerIdException as exception: + raise UpdateFailed(exception) from exception except TimeoutError as exception: raise UpdateFailed("Failed to connect.") from exception except (ServerDisconnectedError, NotAuthenticatedException): diff --git a/homeassistant/components/overkiz/manifest.json b/homeassistant/components/overkiz/manifest.json index 1c08d47622d347..7ad11809e6a70b 100644 --- a/homeassistant/components/overkiz/manifest.json +++ b/homeassistant/components/overkiz/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/overkiz", "requirements": [ - "pyoverkiz==1.3.4" + "pyoverkiz==1.3.5" ], "zeroconf": [ { diff --git a/requirements_all.txt b/requirements_all.txt index 746011521e8d08..ab13c9020b20da 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1749,7 +1749,7 @@ pyotgw==1.1b1 pyotp==2.6.0 # homeassistant.components.overkiz -pyoverkiz==1.3.4 +pyoverkiz==1.3.5 # homeassistant.components.openweathermap pyowm==3.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 815d290118a3cb..e2d7f2ec6f398d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1118,7 +1118,7 @@ pyotgw==1.1b1 pyotp==2.6.0 # homeassistant.components.overkiz -pyoverkiz==1.3.4 +pyoverkiz==1.3.5 # homeassistant.components.openweathermap pyowm==3.2.0 From 98ae7e106ca8e9fd12fe3b653e128ffbee8033c5 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Tue, 15 Feb 2022 12:46:52 -0800 Subject: [PATCH 0671/1098] Always create a new session in ConfigFlow in Overkiz integration (#66602) --- .../components/overkiz/config_flow.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/overkiz/config_flow.py b/homeassistant/components/overkiz/config_flow.py index 2f8dcc189211bb..f788d12747d4d9 100644 --- a/homeassistant/components/overkiz/config_flow.py +++ b/homeassistant/components/overkiz/config_flow.py @@ -19,7 +19,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.data_entry_flow import FlowResult -from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.aiohttp_client import async_create_clientsession from .const import CONF_HUB, DEFAULT_HUB, DOMAIN, LOGGER @@ -46,18 +46,17 @@ async def async_validate_input(self, user_input: dict[str, Any]) -> None: username = user_input[CONF_USERNAME] password = user_input[CONF_PASSWORD] server = SUPPORTED_SERVERS[user_input[CONF_HUB]] - session = async_get_clientsession(self.hass) + session = async_create_clientsession(self.hass) - client = OverkizClient( + async with OverkizClient( username=username, password=password, server=server, session=session - ) - - await client.login() + ) as client: + await client.login() - # Set first gateway id as unique id - if gateways := await client.get_gateways(): - gateway_id = gateways[0].id - await self.async_set_unique_id(gateway_id) + # Set first gateway id as unique id + if gateways := await client.get_gateways(): + gateway_id = gateways[0].id + await self.async_set_unique_id(gateway_id) async def async_step_user( self, user_input: dict[str, Any] | None = None From bc856ea24d9e4d1bc3730e5ee5c6349e5391d3e1 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Tue, 15 Feb 2022 22:42:18 +0100 Subject: [PATCH 0672/1098] Add fallback for serialnumber (#66553) --- homeassistant/components/philips_js/__init__.py | 9 ++++++--- homeassistant/components/philips_js/config_flow.py | 6 +++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/philips_js/__init__.py b/homeassistant/components/philips_js/__init__.py index 1292310f134f82..9a317726768151 100644 --- a/homeassistant/components/philips_js/__init__.py +++ b/homeassistant/components/philips_js/__init__.py @@ -148,9 +148,12 @@ def system(self) -> SystemType: @property def unique_id(self) -> str: """Return the system descriptor.""" - assert self.config_entry - assert self.config_entry.unique_id - return self.config_entry.unique_id + entry: ConfigEntry = self.config_entry + assert entry + if entry.unique_id: + return entry.unique_id + assert entry.entry_id + return entry.entry_id @property def _notify_wanted(self): diff --git a/homeassistant/components/philips_js/config_flow.py b/homeassistant/components/philips_js/config_flow.py index 89f13ffadbf0fd..29abbe5dd71eb0 100644 --- a/homeassistant/components/philips_js/config_flow.py +++ b/homeassistant/components/philips_js/config_flow.py @@ -122,9 +122,9 @@ async def async_step_user(self, user_input: dict | None = None) -> dict: LOGGER.exception("Unexpected exception") errors["base"] = "unknown" else: - - await self.async_set_unique_id(hub.system["serialnumber"]) - self._abort_if_unique_id_configured() + if serialnumber := hub.system.get("serialnumber"): + await self.async_set_unique_id(serialnumber) + self._abort_if_unique_id_configured() self._current[CONF_SYSTEM] = hub.system self._current[CONF_API_VERSION] = hub.api_version From 734fdf7bff631ae850aaa71767878f7c05621810 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Tue, 15 Feb 2022 13:43:31 -0800 Subject: [PATCH 0673/1098] Override and disable nest stream `unavailable` behavior (#66571) --- homeassistant/components/nest/camera_sdm.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/homeassistant/components/nest/camera_sdm.py b/homeassistant/components/nest/camera_sdm.py index 2bd454fbe119e6..d7a9ed299487f0 100644 --- a/homeassistant/components/nest/camera_sdm.py +++ b/homeassistant/components/nest/camera_sdm.py @@ -123,6 +123,15 @@ def frontend_stream_type(self) -> str | None: return STREAM_TYPE_WEB_RTC return super().frontend_stream_type + @property + def available(self) -> bool: + """Return True if entity is available.""" + # Cameras are marked unavailable on stream errors in #54659 however nest streams have + # a high error rate (#60353). Given nest streams are so flaky, marking the stream + # unavailable has other side effects like not showing the camera image which sometimes + # are still able to work. Until the streams are fixed, just leave the streams as available. + return True + async def stream_source(self) -> str | None: """Return the source of the stream.""" if not self.supported_features & SUPPORT_STREAM: From d64ef2ba73a3322e62e592252b38af1040c6f187 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 15 Feb 2022 15:44:35 -0600 Subject: [PATCH 0674/1098] Deduplicate flux_led title and CONF_NAME (#66598) --- homeassistant/components/flux_led/button.py | 2 +- homeassistant/components/flux_led/config_flow.py | 7 ++----- homeassistant/components/flux_led/discovery.py | 9 ++++++--- homeassistant/components/flux_led/entity.py | 2 +- homeassistant/components/flux_led/light.py | 2 +- homeassistant/components/flux_led/number.py | 2 +- homeassistant/components/flux_led/select.py | 2 +- homeassistant/components/flux_led/sensor.py | 2 +- homeassistant/components/flux_led/switch.py | 4 ++-- tests/components/flux_led/test_config_flow.py | 14 +++----------- tests/components/flux_led/test_init.py | 3 +-- 11 files changed, 20 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/flux_led/button.py b/homeassistant/components/flux_led/button.py index fcd4ecc3adcafa..bfbe63cf02e0d2 100644 --- a/homeassistant/components/flux_led/button.py +++ b/homeassistant/components/flux_led/button.py @@ -63,7 +63,7 @@ def __init__( """Initialize the button.""" self.entity_description = description super().__init__(device, entry) - self._attr_name = f"{entry.data[CONF_NAME]} {description.name}" + self._attr_name = f"{entry.data.get(CONF_NAME, entry.title)} {description.name}" base_unique_id = entry.unique_id or entry.entry_id self._attr_unique_id = f"{base_unique_id}_{description.key}" diff --git a/homeassistant/components/flux_led/config_flow.py b/homeassistant/components/flux_led/config_flow.py index c6f14e929d6ce0..9b1cda2dea1253 100644 --- a/homeassistant/components/flux_led/config_flow.py +++ b/homeassistant/components/flux_led/config_flow.py @@ -17,7 +17,7 @@ from homeassistant import config_entries from homeassistant.components import dhcp -from homeassistant.const import CONF_HOST, CONF_NAME +from homeassistant.const import CONF_HOST from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import device_registry as dr @@ -145,10 +145,7 @@ def _async_create_entry_from_device(self, device: FluxLEDDiscovery) -> FlowResul """Create a config entry from a device.""" self._async_abort_entries_match({CONF_HOST: device[ATTR_IPADDR]}) name = async_name_from_discovery(device) - data: dict[str, Any] = { - CONF_HOST: device[ATTR_IPADDR], - CONF_NAME: name, - } + data: dict[str, Any] = {CONF_HOST: device[ATTR_IPADDR]} async_populate_data_from_discovery(data, data, device) return self.async_create_entry( title=name, diff --git a/homeassistant/components/flux_led/discovery.py b/homeassistant/components/flux_led/discovery.py index 0f65c7c17974ad..c30418fa8e4747 100644 --- a/homeassistant/components/flux_led/discovery.py +++ b/homeassistant/components/flux_led/discovery.py @@ -125,10 +125,13 @@ def async_update_entry_from_discovery( if model_num and entry.data.get(CONF_MODEL_NUM) != model_num: data_updates[CONF_MODEL_NUM] = model_num async_populate_data_from_discovery(entry.data, data_updates, device) - if not entry.data.get(CONF_NAME) or is_ip_address(entry.data[CONF_NAME]): - updates["title"] = data_updates[CONF_NAME] = async_name_from_discovery(device) - if data_updates: + if is_ip_address(entry.title): + updates["title"] = async_name_from_discovery(device) + title_matches_name = entry.title == entry.data.get(CONF_NAME) + if data_updates or title_matches_name: updates["data"] = {**entry.data, **data_updates} + if title_matches_name: + del updates["data"][CONF_NAME] if updates: return hass.config_entries.async_update_entry(entry, **updates) return False diff --git a/homeassistant/components/flux_led/entity.py b/homeassistant/components/flux_led/entity.py index 5946ab817de20a..da92931d1e6856 100644 --- a/homeassistant/components/flux_led/entity.py +++ b/homeassistant/components/flux_led/entity.py @@ -40,7 +40,7 @@ def _async_device_info( ATTR_IDENTIFIERS: {(DOMAIN, entry.entry_id)}, ATTR_MANUFACTURER: "Zengge", ATTR_MODEL: device.model, - ATTR_NAME: entry.data[CONF_NAME], + ATTR_NAME: entry.data.get(CONF_NAME, entry.title), ATTR_SW_VERSION: sw_version_str, } if hw_model := entry.data.get(CONF_MODEL): diff --git a/homeassistant/components/flux_led/light.py b/homeassistant/components/flux_led/light.py index 4534c45e22824b..0d179cd2b77201 100644 --- a/homeassistant/components/flux_led/light.py +++ b/homeassistant/components/flux_led/light.py @@ -178,7 +178,7 @@ async def async_setup_entry( FluxLight( coordinator, entry.unique_id or entry.entry_id, - entry.data[CONF_NAME], + entry.data.get(CONF_NAME, entry.title), list(custom_effect_colors), options.get(CONF_CUSTOM_EFFECT_SPEED_PCT, DEFAULT_EFFECT_SPEED), options.get(CONF_CUSTOM_EFFECT_TRANSITION, TRANSITION_GRADUAL), diff --git a/homeassistant/components/flux_led/number.py b/homeassistant/components/flux_led/number.py index d7fad9cf0e6f4a..b4e6e87a829f99 100644 --- a/homeassistant/components/flux_led/number.py +++ b/homeassistant/components/flux_led/number.py @@ -50,7 +50,7 @@ async def async_setup_entry( | FluxMusicPixelsPerSegmentNumber | FluxMusicSegmentsNumber ] = [] - name = entry.data[CONF_NAME] + name = entry.data.get(CONF_NAME, entry.title) base_unique_id = entry.unique_id or entry.entry_id if device.pixels_per_segment is not None: diff --git a/homeassistant/components/flux_led/select.py b/homeassistant/components/flux_led/select.py index 3b78baa782b77f..7edf0ef50f88b4 100644 --- a/homeassistant/components/flux_led/select.py +++ b/homeassistant/components/flux_led/select.py @@ -53,7 +53,7 @@ async def async_setup_entry( | FluxRemoteConfigSelect | FluxWhiteChannelSelect ] = [] - name = entry.data[CONF_NAME] + name = entry.data.get(CONF_NAME, entry.title) base_unique_id = entry.unique_id or entry.entry_id if device.device_type == DeviceType.Switch: diff --git a/homeassistant/components/flux_led/sensor.py b/homeassistant/components/flux_led/sensor.py index 18d1aac55067a5..a4266e55fa8911 100644 --- a/homeassistant/components/flux_led/sensor.py +++ b/homeassistant/components/flux_led/sensor.py @@ -26,7 +26,7 @@ async def async_setup_entry( FluxPairedRemotes( coordinator, entry.unique_id or entry.entry_id, - f"{entry.data[CONF_NAME]} Paired Remotes", + f"{entry.data.get(CONF_NAME, entry.title)} Paired Remotes", "paired_remotes", ) ] diff --git a/homeassistant/components/flux_led/switch.py b/homeassistant/components/flux_led/switch.py index ee004fc2250ffd..e8c34f12b118c0 100644 --- a/homeassistant/components/flux_led/switch.py +++ b/homeassistant/components/flux_led/switch.py @@ -35,7 +35,7 @@ async def async_setup_entry( coordinator: FluxLedUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] entities: list[FluxSwitch | FluxRemoteAccessSwitch | FluxMusicSwitch] = [] base_unique_id = entry.unique_id or entry.entry_id - name = entry.data[CONF_NAME] + name = entry.data.get(CONF_NAME, entry.title) if coordinator.device.device_type == DeviceType.Switch: entities.append(FluxSwitch(coordinator, base_unique_id, name, None)) @@ -73,7 +73,7 @@ def __init__( ) -> None: """Initialize the light.""" super().__init__(device, entry) - self._attr_name = f"{entry.data[CONF_NAME]} Remote Access" + self._attr_name = f"{entry.data.get(CONF_NAME, entry.title)} Remote Access" base_unique_id = entry.unique_id or entry.entry_id self._attr_unique_id = f"{base_unique_id}_remote_access" diff --git a/tests/components/flux_led/test_config_flow.py b/tests/components/flux_led/test_config_flow.py index dcab5cc01add36..0ff8180a761ca2 100644 --- a/tests/components/flux_led/test_config_flow.py +++ b/tests/components/flux_led/test_config_flow.py @@ -23,7 +23,7 @@ TRANSITION_JUMP, TRANSITION_STROBE, ) -from homeassistant.const import CONF_DEVICE, CONF_HOST, CONF_NAME +from homeassistant.const import CONF_DEVICE, CONF_HOST from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import RESULT_TYPE_ABORT, RESULT_TYPE_FORM @@ -94,7 +94,6 @@ async def test_discovery(hass: HomeAssistant): assert result3["data"] == { CONF_MINOR_VERSION: 4, CONF_HOST: IP_ADDRESS, - CONF_NAME: DEFAULT_ENTRY_TITLE, CONF_MODEL: MODEL, CONF_MODEL_NUM: MODEL_NUM, CONF_MODEL_INFO: MODEL, @@ -170,7 +169,6 @@ async def test_discovery_legacy(hass: HomeAssistant): assert result3["data"] == { CONF_MINOR_VERSION: 4, CONF_HOST: IP_ADDRESS, - CONF_NAME: DEFAULT_ENTRY_TITLE, CONF_MODEL: MODEL, CONF_MODEL_NUM: MODEL_NUM, CONF_MODEL_INFO: MODEL, @@ -253,7 +251,6 @@ async def test_discovery_with_existing_device_present(hass: HomeAssistant): assert result3["data"] == { CONF_MINOR_VERSION: 4, CONF_HOST: IP_ADDRESS, - CONF_NAME: DEFAULT_ENTRY_TITLE, CONF_MODEL: MODEL, CONF_MODEL_NUM: MODEL_NUM, CONF_MODEL_INFO: MODEL, @@ -330,7 +327,6 @@ async def test_manual_working_discovery(hass: HomeAssistant): assert result4["data"] == { CONF_MINOR_VERSION: 4, CONF_HOST: IP_ADDRESS, - CONF_NAME: DEFAULT_ENTRY_TITLE, CONF_MODEL: MODEL, CONF_MODEL_NUM: MODEL_NUM, CONF_MODEL_INFO: MODEL, @@ -377,7 +373,6 @@ async def test_manual_no_discovery_data(hass: HomeAssistant): CONF_HOST: IP_ADDRESS, CONF_MODEL_NUM: MODEL_NUM, CONF_MODEL_DESCRIPTION: MODEL_DESCRIPTION, - CONF_NAME: IP_ADDRESS, } @@ -445,7 +440,6 @@ async def test_discovered_by_discovery(hass): assert result2["data"] == { CONF_MINOR_VERSION: 4, CONF_HOST: IP_ADDRESS, - CONF_NAME: DEFAULT_ENTRY_TITLE, CONF_MODEL: MODEL, CONF_MODEL_NUM: MODEL_NUM, CONF_MODEL_INFO: MODEL, @@ -483,7 +477,6 @@ async def test_discovered_by_dhcp_udp_responds(hass): assert result2["data"] == { CONF_MINOR_VERSION: 4, CONF_HOST: IP_ADDRESS, - CONF_NAME: DEFAULT_ENTRY_TITLE, CONF_MODEL: MODEL, CONF_MODEL_NUM: MODEL_NUM, CONF_MODEL_INFO: MODEL, @@ -522,7 +515,6 @@ async def test_discovered_by_dhcp_no_udp_response(hass): CONF_HOST: IP_ADDRESS, CONF_MODEL_NUM: MODEL_NUM, CONF_MODEL_DESCRIPTION: MODEL_DESCRIPTION, - CONF_NAME: DEFAULT_ENTRY_TITLE, } assert mock_async_setup.called assert mock_async_setup_entry.called @@ -553,7 +545,6 @@ async def test_discovered_by_dhcp_partial_udp_response_fallback_tcp(hass): CONF_HOST: IP_ADDRESS, CONF_MODEL_NUM: MODEL_NUM, CONF_MODEL_DESCRIPTION: MODEL_DESCRIPTION, - CONF_NAME: DEFAULT_ENTRY_TITLE, } assert mock_async_setup.called assert mock_async_setup_entry.called @@ -630,7 +621,8 @@ async def test_options(hass: HomeAssistant): """Test options flow.""" config_entry = MockConfigEntry( domain=DOMAIN, - data={CONF_HOST: IP_ADDRESS, CONF_NAME: DEFAULT_ENTRY_TITLE}, + data={CONF_HOST: IP_ADDRESS}, + title=IP_ADDRESS, options={ CONF_CUSTOM_EFFECT_COLORS: "[255,0,0], [0,0,255]", CONF_CUSTOM_EFFECT_SPEED_PCT: 30, diff --git a/tests/components/flux_led/test_init.py b/tests/components/flux_led/test_init.py index de655c2e6adb72..489f6c932c2992 100644 --- a/tests/components/flux_led/test_init.py +++ b/tests/components/flux_led/test_init.py @@ -117,7 +117,7 @@ async def test_config_entry_fills_unique_id_with_directed_discovery( ) -> None: """Test that the unique id is added if its missing via directed (not broadcast) discovery.""" config_entry = MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: IP_ADDRESS}, unique_id=None + domain=DOMAIN, data={CONF_HOST: IP_ADDRESS}, unique_id=None, title=IP_ADDRESS ) config_entry.add_to_hass(hass) last_address = None @@ -144,7 +144,6 @@ def _mock_getBulbInfo(*args, **kwargs): assert config_entry.state == ConfigEntryState.LOADED assert config_entry.unique_id == MAC_ADDRESS - assert config_entry.data[CONF_NAME] == title assert config_entry.title == title From 6134a224dd73869faede5226826671af876a25ee Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 15 Feb 2022 16:14:52 -0600 Subject: [PATCH 0675/1098] Enable dhcp flows for wiz registered devices (#66595) --- homeassistant/components/wiz/manifest.json | 1 + homeassistant/generated/dhcp.py | 1 + 2 files changed, 2 insertions(+) diff --git a/homeassistant/components/wiz/manifest.json b/homeassistant/components/wiz/manifest.json index e333691d20cdd7..7ee19346958976 100644 --- a/homeassistant/components/wiz/manifest.json +++ b/homeassistant/components/wiz/manifest.json @@ -3,6 +3,7 @@ "name": "WiZ", "config_flow": true, "dhcp": [ + {"registered_devices": true}, {"macaddress":"A8BB50*"}, {"hostname":"wiz_*"} ], diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index a4dd8b092c869e..687eea81dcf72a 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -151,6 +151,7 @@ {'domain': 'unifiprotect', 'macaddress': '74ACB9*'}, {'domain': 'verisure', 'macaddress': '0023C1*'}, {'domain': 'vicare', 'macaddress': 'B87424*'}, + {'domain': 'wiz', 'registered_devices': True}, {'domain': 'wiz', 'macaddress': 'A8BB50*'}, {'domain': 'wiz', 'hostname': 'wiz_*'}, {'domain': 'yeelight', 'hostname': 'yeelink-*'}] From c6a4139716db5b15f44f2c858ebe66195e27a22e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 15 Feb 2022 16:17:06 -0600 Subject: [PATCH 0676/1098] Enable dhcp flows for emonitor registered devices (#66584) --- homeassistant/components/emonitor/manifest.json | 5 ++++- homeassistant/generated/dhcp.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/emonitor/manifest.json b/homeassistant/components/emonitor/manifest.json index c8ebdc415ecc2e..6548c71171c135 100644 --- a/homeassistant/components/emonitor/manifest.json +++ b/homeassistant/components/emonitor/manifest.json @@ -4,7 +4,10 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/emonitor", "requirements": ["aioemonitor==1.0.5"], - "dhcp": [{ "hostname": "emonitor*", "macaddress": "0090C2*" }], + "dhcp": [ + {"hostname": "emonitor*", "macaddress": "0090C2*"}, + {"registered_devices": true} + ], "codeowners": ["@bdraco"], "iot_class": "local_polling", "loggers": ["aioemonitor"] diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 687eea81dcf72a..b876797ee40aa3 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -24,6 +24,7 @@ {'domain': 'broadlink', 'macaddress': 'B4430D*'}, {'domain': 'elkm1', 'macaddress': '00409D*'}, {'domain': 'emonitor', 'hostname': 'emonitor*', 'macaddress': '0090C2*'}, + {'domain': 'emonitor', 'registered_devices': True}, {'domain': 'flume', 'hostname': 'flume-gw-*'}, {'domain': 'flux_led', 'hostname': '[ba][lk]*', 'macaddress': '18B905*'}, {'domain': 'flux_led', 'hostname': '[ba][lk]*', 'macaddress': '249494*'}, From b28754e5fef1450e3b0e20a6a160cd1257cceea3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 15 Feb 2022 16:18:13 -0600 Subject: [PATCH 0677/1098] Switch steamist to use integration discovery (#66578) --- homeassistant/components/steamist/config_flow.py | 4 ++-- homeassistant/components/steamist/discovery.py | 2 +- tests/components/steamist/test_config_flow.py | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/steamist/config_flow.py b/homeassistant/components/steamist/config_flow.py index 00d87bf98371b9..c0ec18157b6e43 100644 --- a/homeassistant/components/steamist/config_flow.py +++ b/homeassistant/components/steamist/config_flow.py @@ -48,10 +48,10 @@ async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowRes ) return await self._async_handle_discovery() - async def async_step_discovery( + async def async_step_integration_discovery( self, discovery_info: DiscoveryInfoType ) -> FlowResult: - """Handle discovery.""" + """Handle integration discovery.""" self._discovered_device = Device30303( ipaddress=discovery_info["ipaddress"], name=discovery_info["name"], diff --git a/homeassistant/components/steamist/discovery.py b/homeassistant/components/steamist/discovery.py index 2ecd2a1d681040..773e56d6612fa5 100644 --- a/homeassistant/components/steamist/discovery.py +++ b/homeassistant/components/steamist/discovery.py @@ -125,7 +125,7 @@ def async_trigger_discovery( hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data={ "ipaddress": device.ipaddress, "name": device.name, diff --git a/tests/components/steamist/test_config_flow.py b/tests/components/steamist/test_config_flow.py index 7876272368afe4..ed887bb6049ff4 100644 --- a/tests/components/steamist/test_config_flow.py +++ b/tests/components/steamist/test_config_flow.py @@ -211,7 +211,7 @@ async def test_discovered_by_discovery_and_dhcp(hass: HomeAssistant) -> None: with _patch_discovery(), _patch_status(MOCK_ASYNC_GET_STATUS_INACTIVE): result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data=DISCOVERY_30303, ) await hass.async_block_till_done() @@ -249,7 +249,7 @@ async def test_discovered_by_discovery(hass: HomeAssistant) -> None: with _patch_discovery(), _patch_status(MOCK_ASYNC_GET_STATUS_INACTIVE): result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data=DISCOVERY_30303, ) await hass.async_block_till_done() @@ -339,7 +339,7 @@ async def test_discovered_by_dhcp_discovery_finds_non_steamist_device( "source, data", [ (config_entries.SOURCE_DHCP, DHCP_DISCOVERY), - (config_entries.SOURCE_DISCOVERY, DISCOVERY_30303), + (config_entries.SOURCE_INTEGRATION_DISCOVERY, DISCOVERY_30303), ], ) async def test_discovered_by_dhcp_or_discovery_adds_missing_unique_id( @@ -371,7 +371,7 @@ async def test_discovered_by_dhcp_or_discovery_adds_missing_unique_id( "source, data", [ (config_entries.SOURCE_DHCP, DHCP_DISCOVERY), - (config_entries.SOURCE_DISCOVERY, DISCOVERY_30303), + (config_entries.SOURCE_INTEGRATION_DISCOVERY, DISCOVERY_30303), ], ) async def test_discovered_by_dhcp_or_discovery_existing_unique_id_does_not_reload( From d79d775d92d540292d63d5f71f0ee24830750a42 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 15 Feb 2022 16:18:55 -0600 Subject: [PATCH 0678/1098] Switch elkm1 to use integration discovery (#66572) --- homeassistant/components/elkm1/config_flow.py | 4 ++-- homeassistant/components/elkm1/discovery.py | 2 +- tests/components/elkm1/test_config_flow.py | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/elkm1/config_flow.py b/homeassistant/components/elkm1/config_flow.py index 19a3cf88473a18..2453958b3deff1 100644 --- a/homeassistant/components/elkm1/config_flow.py +++ b/homeassistant/components/elkm1/config_flow.py @@ -126,10 +126,10 @@ async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowRes ) return await self._async_handle_discovery() - async def async_step_discovery( + async def async_step_integration_discovery( self, discovery_info: DiscoveryInfoType ) -> FlowResult: - """Handle discovery.""" + """Handle integration discovery.""" self._discovered_device = ElkSystem( discovery_info["mac_address"], discovery_info["ip_address"], diff --git a/homeassistant/components/elkm1/discovery.py b/homeassistant/components/elkm1/discovery.py index 10d9f7b6e405ee..7055f3958e9d6f 100644 --- a/homeassistant/components/elkm1/discovery.py +++ b/homeassistant/components/elkm1/discovery.py @@ -88,7 +88,7 @@ def async_trigger_discovery( hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data=asdict(device), ) ) diff --git a/tests/components/elkm1/test_config_flow.py b/tests/components/elkm1/test_config_flow.py index 76db04944b5837..d8a0feea670a34 100644 --- a/tests/components/elkm1/test_config_flow.py +++ b/tests/components/elkm1/test_config_flow.py @@ -656,7 +656,7 @@ async def test_form_import_device_discovered(hass): "source, data", [ (config_entries.SOURCE_DHCP, DHCP_DISCOVERY), - (config_entries.SOURCE_DISCOVERY, ELK_DISCOVERY_INFO), + (config_entries.SOURCE_INTEGRATION_DISCOVERY, ELK_DISCOVERY_INFO), ], ) async def test_discovered_by_dhcp_or_discovery_mac_address_mismatch_host_already_configured( @@ -686,7 +686,7 @@ async def test_discovered_by_dhcp_or_discovery_mac_address_mismatch_host_already "source, data", [ (config_entries.SOURCE_DHCP, DHCP_DISCOVERY), - (config_entries.SOURCE_DISCOVERY, ELK_DISCOVERY_INFO), + (config_entries.SOURCE_INTEGRATION_DISCOVERY, ELK_DISCOVERY_INFO), ], ) async def test_discovered_by_dhcp_or_discovery_adds_missing_unique_id( @@ -717,7 +717,7 @@ async def test_discovered_by_discovery_and_dhcp(hass): with _patch_discovery(), _patch_elk(): result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data=ELK_DISCOVERY_INFO, ) await hass.async_block_till_done() @@ -755,7 +755,7 @@ async def test_discovered_by_discovery(hass): with _patch_discovery(), _patch_elk(): result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data=ELK_DISCOVERY_INFO, ) await hass.async_block_till_done() @@ -806,7 +806,7 @@ async def test_discovered_by_discovery_url_already_configured(hass): with _patch_discovery(), _patch_elk(): result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data=ELK_DISCOVERY_INFO, ) await hass.async_block_till_done() From dd9992bfd6597d48b9277798f92da74d1f2c1d5d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 15 Feb 2022 23:29:30 +0100 Subject: [PATCH 0679/1098] Update plugwise 0.16.4 (#66613) --- homeassistant/components/plugwise/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/plugwise/manifest.json b/homeassistant/components/plugwise/manifest.json index 977838546145be..6acb8c7fca58d0 100644 --- a/homeassistant/components/plugwise/manifest.json +++ b/homeassistant/components/plugwise/manifest.json @@ -2,7 +2,7 @@ "domain": "plugwise", "name": "Plugwise", "documentation": "https://www.home-assistant.io/integrations/plugwise", - "requirements": ["plugwise==0.16.2"], + "requirements": ["plugwise==0.16.4"], "codeowners": ["@CoMPaTech", "@bouwew", "@brefra", "@frenck"], "zeroconf": ["_plugwise._tcp.local."], "config_flow": true, diff --git a/requirements_all.txt b/requirements_all.txt index ab13c9020b20da..9be38d29cc595e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1267,7 +1267,7 @@ plexauth==0.0.6 plexwebsocket==0.0.13 # homeassistant.components.plugwise -plugwise==0.16.2 +plugwise==0.16.4 # homeassistant.components.plum_lightpad plumlightpad==0.0.11 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e2d7f2ec6f398d..9f90d498f35c1d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -792,7 +792,7 @@ plexauth==0.0.6 plexwebsocket==0.0.13 # homeassistant.components.plugwise -plugwise==0.16.2 +plugwise==0.16.4 # homeassistant.components.plum_lightpad plumlightpad==0.0.11 From 6ae23318054282b3029706a68f186329a1ce8eed Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 15 Feb 2022 17:47:01 -0600 Subject: [PATCH 0680/1098] Switch senseme to use integration discovery (#66576) --- homeassistant/components/senseme/config_flow.py | 4 ++-- homeassistant/components/senseme/discovery.py | 2 +- tests/components/senseme/test_config_flow.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/senseme/config_flow.py b/homeassistant/components/senseme/config_flow.py index 151a251c8a2c50..6e2f10c1b36e28 100644 --- a/homeassistant/components/senseme/config_flow.py +++ b/homeassistant/components/senseme/config_flow.py @@ -42,10 +42,10 @@ async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowRes self._discovered_device = device return await self.async_step_discovery_confirm() - async def async_step_discovery( + async def async_step_integration_discovery( self, discovery_info: DiscoveryInfoType ) -> FlowResult: - """Handle discovery.""" + """Handle integration discovery.""" uuid = discovery_info[CONF_ID] device = async_get_discovered_device(self.hass, discovery_info[CONF_ID]) host = device.address diff --git a/homeassistant/components/senseme/discovery.py b/homeassistant/components/senseme/discovery.py index 85674f069e12af..624b18a8761fba 100644 --- a/homeassistant/components/senseme/discovery.py +++ b/homeassistant/components/senseme/discovery.py @@ -58,7 +58,7 @@ def async_trigger_discovery( hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data={CONF_ID: device.uuid}, ) ) diff --git a/tests/components/senseme/test_config_flow.py b/tests/components/senseme/test_config_flow.py index 93a42d1e8ab11e..e85845dcace40e 100644 --- a/tests/components/senseme/test_config_flow.py +++ b/tests/components/senseme/test_config_flow.py @@ -211,7 +211,7 @@ async def test_discovery(hass: HomeAssistant) -> None: ) as mock_setup_entry: result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data={CONF_ID: MOCK_UUID}, ) assert result["type"] == RESULT_TYPE_FORM @@ -254,7 +254,7 @@ async def test_discovery_existing_device_no_ip_change(hass: HomeAssistant) -> No with _patch_discovery(): result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data={CONF_ID: MOCK_UUID}, ) assert result["type"] == RESULT_TYPE_ABORT @@ -281,7 +281,7 @@ async def test_discovery_existing_device_ip_change(hass: HomeAssistant) -> None: result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data={CONF_ID: MOCK_UUID}, ) await hass.async_block_till_done() From 208671418ed3d8069fa7d74b028569bb65d65b55 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 16 Feb 2022 00:14:09 +0000 Subject: [PATCH 0681/1098] [ci skip] Translation update --- .../components/abode/translations/el.json | 8 ++++++ .../components/adax/translations/el.json | 6 +++-- .../components/adax/translations/lv.json | 11 ++++++++ .../components/adguard/translations/el.json | 1 + .../advantage_air/translations/el.json | 3 +++ .../components/airvisual/translations/el.json | 3 +++ .../components/androidtv/translations/el.json | 4 ++- .../aseko_pool_live/translations/el.json | 12 +++++++++ .../components/asuswrt/translations/el.json | 2 ++ .../components/atag/translations/el.json | 3 ++- .../aussie_broadband/translations/el.json | 10 +++++++ .../components/awair/translations/el.json | 6 +++++ .../components/axis/translations/el.json | 2 ++ .../binary_sensor/translations/el.json | 1 + .../components/blink/translations/el.json | 3 +++ .../bmw_connected_drive/translations/el.json | 1 + .../components/brunt/translations/el.json | 4 +++ .../components/bsblan/translations/el.json | 1 + .../components/button/translations/el.json | 3 ++- .../cert_expiry/translations/el.json | 3 ++- .../climacell/translations/sensor.lv.json | 27 +++++++++++++++++++ .../components/control4/translations/el.json | 3 +++ .../crownstone/translations/el.json | 4 +++ .../components/daikin/translations/el.json | 3 +++ .../components/denonavr/translations/el.json | 1 + .../devolo_home_control/translations/el.json | 2 ++ .../components/dexcom/translations/el.json | 1 + .../components/doorbird/translations/el.json | 3 ++- .../components/dsmr/translations/el.json | 3 ++- .../components/econet/translations/el.json | 4 +++ .../components/elgato/translations/el.json | 3 +++ .../components/elkm1/translations/el.json | 3 +++ .../components/elmax/translations/el.json | 1 + .../components/elmax/translations/lv.json | 11 ++++++++ .../emulated_roku/translations/el.json | 1 + .../enphase_envoy/translations/el.json | 1 + .../components/ezviz/translations/el.json | 9 +++++++ .../fireservicerota/translations/el.json | 4 +++ .../components/fivem/translations/el.json | 3 ++- .../flick_electric/translations/el.json | 3 ++- .../components/flipr/translations/el.json | 4 +++ .../components/flume/translations/el.json | 6 ++++- .../components/foscam/translations/el.json | 2 ++ .../components/freebox/translations/el.json | 3 +++ .../components/fritz/translations/el.json | 14 ++++++++-- .../components/fritzbox/translations/el.json | 7 ++++- .../fritzbox_callmonitor/translations/el.json | 2 ++ .../components/glances/translations/el.json | 2 ++ .../components/gogogate2/translations/el.json | 3 +++ .../components/goodwe/translations/el.json | 3 +++ .../growatt_server/translations/el.json | 3 +++ .../components/hangouts/translations/el.json | 4 ++- .../homewizard/translations/lv.json | 12 +++++++++ .../components/honeywell/translations/el.json | 3 +++ .../huawei_lte/translations/el.json | 1 + .../huisbaasje/translations/el.json | 1 + .../hvv_departures/translations/el.json | 3 ++- .../components/hyperion/translations/el.json | 5 ++++ .../components/ialarm/translations/el.json | 3 ++- .../components/iaqualink/translations/el.json | 1 + .../components/icloud/translations/el.json | 2 ++ .../intellifire/translations/el.json | 3 +++ .../components/ipp/translations/el.json | 4 ++- .../components/iss/translations/ca.json | 11 +++++++- .../components/iss/translations/el.json | 9 +++++++ .../components/iss/translations/et.json | 11 +++++++- .../components/iss/translations/hu.json | 9 +++++++ .../components/iss/translations/it.json | 11 +++++++- .../components/iss/translations/no.json | 11 +++++++- .../components/iss/translations/pl.json | 11 +++++++- .../components/iss/translations/ru.json | 11 +++++++- .../components/iss/translations/zh-Hant.json | 11 +++++++- .../components/isy994/translations/el.json | 1 + .../components/jellyfin/translations/el.json | 1 + .../keenetic_ndms2/translations/el.json | 2 ++ .../components/kmtronic/translations/el.json | 1 + .../components/knx/translations/el.json | 2 ++ .../components/konnected/translations/el.json | 3 +++ .../kostal_plenticore/translations/el.json | 3 ++- .../components/life360/translations/el.json | 1 + .../components/litejet/translations/el.json | 3 +++ .../litterrobot/translations/el.json | 1 + .../components/luftdaten/translations/hu.json | 2 +- .../components/luftdaten/translations/ru.json | 2 +- .../components/mazda/translations/el.json | 2 ++ .../components/melcloud/translations/el.json | 4 +++ .../components/mikrotik/translations/el.json | 2 ++ .../components/mill/translations/el.json | 4 ++- .../modem_callerid/translations/el.json | 3 ++- .../moehlenhoff_alpha2/translations/el.json | 3 +++ .../moehlenhoff_alpha2/translations/lv.json | 3 +++ .../components/monoprice/translations/el.json | 1 + .../motion_blinds/translations/el.json | 9 ++++++- .../components/motioneye/translations/el.json | 5 +++- .../components/motioneye/translations/it.json | 4 +-- .../components/mqtt/translations/el.json | 5 +++- .../components/myq/translations/el.json | 6 +++++ .../components/nam/translations/el.json | 2 ++ .../components/nest/translations/el.json | 1 + .../components/netatmo/translations/hu.json | 2 +- .../components/netatmo/translations/ru.json | 2 +- .../components/netgear/translations/el.json | 1 + .../components/nexia/translations/el.json | 3 ++- .../components/notion/translations/el.json | 4 +++ .../components/nuheat/translations/el.json | 1 + .../components/nuki/translations/el.json | 3 ++- .../components/nut/translations/el.json | 4 ++- .../components/oncue/translations/el.json | 1 + .../components/onewire/translations/el.json | 3 +++ .../components/onvif/translations/el.json | 4 +++ .../open_meteo/translations/lv.json | 11 ++++++++ .../opengarage/translations/el.json | 3 ++- .../components/overkiz/translations/el.json | 2 ++ .../overkiz/translations/sensor.lv.json | 20 ++++++++++++++ .../ovo_energy/translations/el.json | 6 +++++ .../components/picnic/translations/ca.json | 2 +- .../components/picnic/translations/el.json | 3 ++- .../components/plugwise/translations/el.json | 1 + .../plum_lightpad/translations/el.json | 12 +++++++++ .../components/poolsense/translations/el.json | 4 +++ .../components/powerwall/translations/el.json | 6 +++++ .../components/prosegur/translations/el.json | 2 ++ .../components/pvoutput/translations/lv.json | 11 ++++++++ .../rainmachine/translations/el.json | 4 ++- .../components/renault/translations/el.json | 7 ++++- .../components/ridwell/translations/el.json | 4 +++ .../components/ring/translations/el.json | 1 + .../translations/el.json | 4 +++ .../components/roomba/translations/el.json | 6 ++++- .../components/sense/translations/el.json | 2 ++ .../components/senseme/translations/lv.json | 11 ++++++++ .../components/sia/translations/el.json | 1 + .../simplisafe/translations/el.json | 4 ++- .../components/sma/translations/el.json | 3 ++- .../components/smarthab/translations/el.json | 4 +++ .../components/smarttub/translations/el.json | 4 +++ .../components/solax/translations/el.json | 12 +++++++++ .../components/soma/translations/el.json | 3 +++ .../somfy_mylink/translations/el.json | 1 + .../squeezebox/translations/el.json | 3 ++- .../srp_energy/translations/el.json | 1 + .../components/starline/translations/el.json | 1 + .../components/steamist/translations/el.json | 3 +++ .../surepetcare/translations/el.json | 1 + .../components/switchbot/translations/el.json | 3 ++- .../synology_dsm/translations/el.json | 12 ++++++++- .../system_bridge/translations/el.json | 3 ++- .../components/tado/translations/el.json | 3 +++ .../components/tile/translations/el.json | 9 +++++++ .../totalconnect/translations/el.json | 3 +++ .../components/tractive/translations/el.json | 3 ++- .../transmission/translations/el.json | 2 ++ .../components/tuya/translations/el.json | 1 + .../tuya/translations/select.el.json | 3 +++ .../tuya/translations/select.lv.json | 22 +++++++++++++++ .../components/unifi/translations/el.json | 2 ++ .../unifiprotect/translations/el.json | 5 ++++ .../components/upcloud/translations/el.json | 1 + .../components/vallox/translations/lv.json | 9 +++++++ .../components/venstar/translations/el.json | 1 + .../components/verisure/translations/el.json | 4 ++- .../components/version/translations/lv.json | 16 +++++++++++ .../components/vesync/translations/el.json | 4 +++ .../components/vicare/translations/el.json | 4 ++- .../vlc_telnet/translations/el.json | 7 ++++- .../components/wallbox/translations/el.json | 2 ++ .../components/watttime/translations/el.json | 4 +++ .../components/webostv/translations/lv.json | 11 ++++++++ .../components/whirlpool/translations/el.json | 1 + .../components/wiffi/translations/lv.json | 7 +++++ .../components/wiz/translations/ca.json | 1 + .../components/wiz/translations/de.json | 1 + .../components/wiz/translations/el.json | 3 +++ .../components/wiz/translations/en.json | 6 ++++- .../components/wiz/translations/et.json | 1 + .../components/wiz/translations/hu.json | 1 + .../components/wiz/translations/it.json | 1 + .../components/wiz/translations/lv.json | 11 ++++++++ .../components/wiz/translations/no.json | 1 + .../components/wiz/translations/pl.json | 3 ++- .../components/wiz/translations/pt-BR.json | 1 + .../components/wiz/translations/ru.json | 1 + .../components/wiz/translations/zh-Hant.json | 1 + .../yale_smart_alarm/translations/el.json | 9 +++++-- .../translations/select.lv.json | 13 +++++++++ 185 files changed, 766 insertions(+), 60 deletions(-) create mode 100644 homeassistant/components/adax/translations/lv.json create mode 100644 homeassistant/components/aseko_pool_live/translations/el.json create mode 100644 homeassistant/components/climacell/translations/sensor.lv.json create mode 100644 homeassistant/components/elmax/translations/lv.json create mode 100644 homeassistant/components/homewizard/translations/lv.json create mode 100644 homeassistant/components/moehlenhoff_alpha2/translations/lv.json create mode 100644 homeassistant/components/open_meteo/translations/lv.json create mode 100644 homeassistant/components/overkiz/translations/sensor.lv.json create mode 100644 homeassistant/components/plum_lightpad/translations/el.json create mode 100644 homeassistant/components/pvoutput/translations/lv.json create mode 100644 homeassistant/components/senseme/translations/lv.json create mode 100644 homeassistant/components/solax/translations/el.json create mode 100644 homeassistant/components/tuya/translations/select.lv.json create mode 100644 homeassistant/components/vallox/translations/lv.json create mode 100644 homeassistant/components/version/translations/lv.json create mode 100644 homeassistant/components/webostv/translations/lv.json create mode 100644 homeassistant/components/wiffi/translations/lv.json create mode 100644 homeassistant/components/wiz/translations/lv.json create mode 100644 homeassistant/components/yamaha_musiccast/translations/select.lv.json diff --git a/homeassistant/components/abode/translations/el.json b/homeassistant/components/abode/translations/el.json index 9212c7d424750d..9f8043ebd853cf 100644 --- a/homeassistant/components/abode/translations/el.json +++ b/homeassistant/components/abode/translations/el.json @@ -13,9 +13,17 @@ "title": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc MFA \u03b3\u03b9\u03b1 \u03c4\u03bf Abode" }, "reauth_confirm": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "Email" + }, "title": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Abode" }, "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "Email" + }, "title": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Abode" } } diff --git a/homeassistant/components/adax/translations/el.json b/homeassistant/components/adax/translations/el.json index 024d61ad8d49bd..2f96095a97c760 100644 --- a/homeassistant/components/adax/translations/el.json +++ b/homeassistant/components/adax/translations/el.json @@ -7,7 +7,8 @@ "step": { "cloud": { "data": { - "account_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd" + "account_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" } }, "local": { @@ -20,7 +21,8 @@ "user": { "data": { "account_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd", - "connection_type": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c4\u03cd\u03c0\u03bf\u03c5 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "connection_type": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c4\u03cd\u03c0\u03bf\u03c5 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03cd\u03c0\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2. \u03a4\u03bf\u03c0\u03b9\u03ba\u03ae \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03b8\u03b5\u03c1\u03bc\u03ac\u03c3\u03c4\u03c1\u03b5\u03c2 \u03bc\u03b5 bluetooth" } diff --git a/homeassistant/components/adax/translations/lv.json b/homeassistant/components/adax/translations/lv.json new file mode 100644 index 00000000000000..3ae3e819b7edda --- /dev/null +++ b/homeassistant/components/adax/translations/lv.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "cloud": { + "data": { + "account_id": "Konta ID" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/adguard/translations/el.json b/homeassistant/components/adguard/translations/el.json index fff34120dcf8d9..7d0f716a1c7565 100644 --- a/homeassistant/components/adguard/translations/el.json +++ b/homeassistant/components/adguard/translations/el.json @@ -14,6 +14,7 @@ "user": { "data": { "host": "\u0394\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "port": "\u0398\u03cd\u03c1\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, diff --git a/homeassistant/components/advantage_air/translations/el.json b/homeassistant/components/advantage_air/translations/el.json index e4dc05db74a6c4..3513cc855dd004 100644 --- a/homeassistant/components/advantage_air/translations/el.json +++ b/homeassistant/components/advantage_air/translations/el.json @@ -2,6 +2,9 @@ "config": { "step": { "user": { + "data": { + "port": "\u0398\u03cd\u03c1\u03b1" + }, "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf API \u03c4\u03bf\u03c5 \u03b5\u03c0\u03af\u03c4\u03bf\u03b9\u03c7\u03bf\u03c5 tablet Advantage Air.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7" } diff --git a/homeassistant/components/airvisual/translations/el.json b/homeassistant/components/airvisual/translations/el.json index 6fae2369fd3e87..997a547e34f9d6 100644 --- a/homeassistant/components/airvisual/translations/el.json +++ b/homeassistant/components/airvisual/translations/el.json @@ -23,6 +23,9 @@ "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03af\u03b1\u03c2" }, "node_pro": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c9\u03c0\u03b9\u03ba\u03ae \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 AirVisual. \u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b1\u03bd\u03b1\u03ba\u03c4\u03b7\u03b8\u03b5\u03af \u03b1\u03c0\u03cc \u03c4\u03bf UI \u03c4\u03b7\u03c2 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1\u03c2.", "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03bd\u03cc\u03c2 \u03ba\u03cc\u03bc\u03b2\u03bf\u03c5 AirVisual Node/Pro" }, diff --git a/homeassistant/components/androidtv/translations/el.json b/homeassistant/components/androidtv/translations/el.json index 26cc502643a7b2..147d0f2e8640f7 100644 --- a/homeassistant/components/androidtv/translations/el.json +++ b/homeassistant/components/androidtv/translations/el.json @@ -5,6 +5,7 @@ }, "error": { "adbkey_not_file": "\u03a4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd ADB \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "key_and_server": "\u03a0\u03b1\u03c1\u03ad\u03c7\u03b5\u03c4\u03b5 \u03bc\u03cc\u03bd\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af ADB \u03ae \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae ADB" }, "step": { @@ -14,7 +15,8 @@ "adb_server_port": "\u0398\u03cd\u03c1\u03b1 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae ADB", "adbkey": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd ADB (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1)", "device_class": "\u039f \u03c4\u03cd\u03c0\u03bf\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" }, "description": "\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b5\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03bf\u03c5\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c3\u03b1\u03c2 Android TV", "title": "Android TV" diff --git a/homeassistant/components/aseko_pool_live/translations/el.json b/homeassistant/components/aseko_pool_live/translations/el.json new file mode 100644 index 00000000000000..51d7e3a7308bf2 --- /dev/null +++ b/homeassistant/components/aseko_pool_live/translations/el.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "email": "Email", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/el.json b/homeassistant/components/asuswrt/translations/el.json index a1f7f6508218e2..c704dd81b7fd64 100644 --- a/homeassistant/components/asuswrt/translations/el.json +++ b/homeassistant/components/asuswrt/translations/el.json @@ -10,6 +10,8 @@ "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1", "protocol": "\u03a0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf \u03b5\u03c0\u03b9\u03ba\u03bf\u03b9\u03bd\u03c9\u03bd\u03af\u03b1\u03c2 \u03c0\u03c1\u03bf\u03c2 \u03c7\u03c1\u03ae\u03c3\u03b7", "ssh_key": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd SSH (\u03b1\u03bd\u03c4\u03af \u03c4\u03bf\u03c5 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2)", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" diff --git a/homeassistant/components/atag/translations/el.json b/homeassistant/components/atag/translations/el.json index 93e4bfa4262229..073676503ebce5 100644 --- a/homeassistant/components/atag/translations/el.json +++ b/homeassistant/components/atag/translations/el.json @@ -6,7 +6,8 @@ "step": { "user": { "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" } diff --git a/homeassistant/components/aussie_broadband/translations/el.json b/homeassistant/components/aussie_broadband/translations/el.json index c90ecfda92549a..0b08d9bbc69579 100644 --- a/homeassistant/components/aussie_broadband/translations/el.json +++ b/homeassistant/components/aussie_broadband/translations/el.json @@ -3,8 +3,14 @@ "abort": { "no_services_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc" }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "reauth": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 {username}" }, "service": { @@ -15,12 +21,16 @@ }, "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } } }, "options": { + "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/awair/translations/el.json b/homeassistant/components/awair/translations/el.json index 3f3bbcac514e29..dde9b024ecde69 100644 --- a/homeassistant/components/awair/translations/el.json +++ b/homeassistant/components/awair/translations/el.json @@ -2,9 +2,15 @@ "config": { "step": { "reauth": { + "data": { + "email": "Email" + }, "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c0\u03c1\u03bf\u03b3\u03c1\u03b1\u03bc\u03bc\u03b1\u03c4\u03b9\u03c3\u03c4\u03ae Awair." }, "user": { + "data": { + "email": "Email" + }, "description": "\u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03b5\u03af\u03c4\u03b5 \u03b3\u03b9\u03b1 \u03ad\u03bd\u03b1 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c0\u03c1\u03bf\u03b3\u03c1\u03b1\u03bc\u03bc\u03b1\u03c4\u03b9\u03c3\u03c4\u03ae Awair \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7: https://developer.getawair.com/onboard/login" } } diff --git a/homeassistant/components/axis/translations/el.json b/homeassistant/components/axis/translations/el.json index 610a5a4f586d6e..7255fe1779496b 100644 --- a/homeassistant/components/axis/translations/el.json +++ b/homeassistant/components/axis/translations/el.json @@ -8,6 +8,8 @@ "step": { "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 Axis" diff --git a/homeassistant/components/binary_sensor/translations/el.json b/homeassistant/components/binary_sensor/translations/el.json index 0fdcc207646fd1..6688159ee93ec7 100644 --- a/homeassistant/components/binary_sensor/translations/el.json +++ b/homeassistant/components/binary_sensor/translations/el.json @@ -47,6 +47,7 @@ "is_running": "{entity_name} \u03b5\u03ba\u03c4\u03b5\u03bb\u03b5\u03af\u03c4\u03b1\u03b9", "is_smoke": "{entity_name} \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03ba\u03b1\u03c0\u03bd\u03cc", "is_sound": "{entity_name} \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03ae\u03c7\u03bf", + "is_tampered": "{entity_name} \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03c0\u03b1\u03c1\u03b1\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", "is_unsafe": "{entity_name} \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c3\u03c6\u03b1\u03bb\u03ad\u03c2", "is_update": "{entity_name} \u03ad\u03c7\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7", "is_vibration": "{entity_name} \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03b4\u03cc\u03bd\u03b7\u03c3\u03b7" diff --git a/homeassistant/components/blink/translations/el.json b/homeassistant/components/blink/translations/el.json index b439c443e124c1..430cd76e408d94 100644 --- a/homeassistant/components/blink/translations/el.json +++ b/homeassistant/components/blink/translations/el.json @@ -9,6 +9,9 @@ "title": "\u0388\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b4\u03cd\u03bf \u03c0\u03b1\u03c1\u03b1\u03b3\u03cc\u03bd\u03c4\u03c9\u03bd" }, "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc Blink" } } diff --git a/homeassistant/components/bmw_connected_drive/translations/el.json b/homeassistant/components/bmw_connected_drive/translations/el.json index 6e3d2669c08377..72fc4ad7cd3a77 100644 --- a/homeassistant/components/bmw_connected_drive/translations/el.json +++ b/homeassistant/components/bmw_connected_drive/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "region": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae ConnectedDrive", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } diff --git a/homeassistant/components/brunt/translations/el.json b/homeassistant/components/brunt/translations/el.json index 69e1d1f1327404..fe92f1e0da8977 100644 --- a/homeassistant/components/brunt/translations/el.json +++ b/homeassistant/components/brunt/translations/el.json @@ -2,10 +2,14 @@ "config": { "step": { "reauth_confirm": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd: {username}" }, "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 Brunt" diff --git a/homeassistant/components/bsblan/translations/el.json b/homeassistant/components/bsblan/translations/el.json index 995c16ed994ce9..e77c2da80bdb1b 100644 --- a/homeassistant/components/bsblan/translations/el.json +++ b/homeassistant/components/bsblan/translations/el.json @@ -9,6 +9,7 @@ "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "passkey": "\u03a3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae BSB-Lan \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Home Assistant.", diff --git a/homeassistant/components/button/translations/el.json b/homeassistant/components/button/translations/el.json index 9cf028f4c831db..bfbd1c2d210b49 100644 --- a/homeassistant/components/button/translations/el.json +++ b/homeassistant/components/button/translations/el.json @@ -6,5 +6,6 @@ "trigger_type": { "pressed": "{entity_name} \u03ad\u03c7\u03b5\u03b9 \u03c0\u03b1\u03c4\u03b7\u03b8\u03b5\u03af" } - } + }, + "title": "\u039a\u03bf\u03c5\u03bc\u03c0\u03af" } \ No newline at end of file diff --git a/homeassistant/components/cert_expiry/translations/el.json b/homeassistant/components/cert_expiry/translations/el.json index 728fd2c3db746b..c640d7e0411947 100644 --- a/homeassistant/components/cert_expiry/translations/el.json +++ b/homeassistant/components/cert_expiry/translations/el.json @@ -11,7 +11,8 @@ "step": { "user": { "data": { - "name": "\u03a4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c4\u03bf\u03c5 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03bf\u03cd" + "name": "\u03a4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c4\u03bf\u03c5 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03bf\u03cd", + "port": "\u0398\u03cd\u03c1\u03b1" }, "title": "\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03bf\u03c2 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ae" } diff --git a/homeassistant/components/climacell/translations/sensor.lv.json b/homeassistant/components/climacell/translations/sensor.lv.json new file mode 100644 index 00000000000000..a0010b4e4a897f --- /dev/null +++ b/homeassistant/components/climacell/translations/sensor.lv.json @@ -0,0 +1,27 @@ +{ + "state": { + "climacell__health_concern": { + "good": "Labs", + "hazardous": "B\u012bstams", + "moderate": "M\u0113rens", + "unhealthy": "Nevesel\u012bgs", + "unhealthy_for_sensitive_groups": "Nevesel\u012bgs jut\u012bg\u0101m grup\u0101m", + "very_unhealthy": "\u013boti nevesel\u012bgs" + }, + "climacell__pollen_index": { + "high": "Augsts", + "low": "Zems", + "medium": "Vid\u0113js", + "none": "Nav", + "very_high": "\u013boti augsts", + "very_low": "\u013boti zems" + }, + "climacell__precipitation_type": { + "freezing_rain": "Sasalsto\u0161s lietus", + "ice_pellets": "Krusa", + "none": "Nav", + "rain": "Lietus", + "snow": "Sniegs" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/control4/translations/el.json b/homeassistant/components/control4/translations/el.json index a80a1248eb5e9d..92cea7966a3626 100644 --- a/homeassistant/components/control4/translations/el.json +++ b/homeassistant/components/control4/translations/el.json @@ -2,6 +2,9 @@ "config": { "step": { "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c4\u03bf\u03c5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03c3\u03b1\u03c2 Control4 \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03bf\u03c5 \u03c4\u03bf\u03c0\u03b9\u03ba\u03bf\u03cd \u03c3\u03b1\u03c2 \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ae." } } diff --git a/homeassistant/components/crownstone/translations/el.json b/homeassistant/components/crownstone/translations/el.json index d2990459f4ff1d..2deebf72442bbd 100644 --- a/homeassistant/components/crownstone/translations/el.json +++ b/homeassistant/components/crownstone/translations/el.json @@ -24,6 +24,10 @@ "title": "Crownstone USB Sphere" }, "user": { + "data": { + "email": "Email", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "title": "\u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 Crownstone" } } diff --git a/homeassistant/components/daikin/translations/el.json b/homeassistant/components/daikin/translations/el.json index 3148940ef05d4a..ae089710c8a194 100644 --- a/homeassistant/components/daikin/translations/el.json +++ b/homeassistant/components/daikin/translations/el.json @@ -5,6 +5,9 @@ }, "step": { "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03bf\u03c5 Daikin AC. \n\n \u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03c4\u03b1 \u039a\u03bb\u03b5\u03b9\u03b4\u03af API \u03ba\u03b1\u03b9 \u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03bf\u03cd\u03bd\u03c4\u03b1\u03b9 \u03bc\u03cc\u03bd\u03bf \u03b1\u03c0\u03cc \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 BRP072Cxx \u03ba\u03b1\u03b9 SKYFi \u03b1\u03bd\u03c4\u03af\u03c3\u03c4\u03bf\u03b9\u03c7\u03b1.", "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Daikin AC" } diff --git a/homeassistant/components/denonavr/translations/el.json b/homeassistant/components/denonavr/translations/el.json index 12f69b931c8247..16e0c74c2064e1 100644 --- a/homeassistant/components/denonavr/translations/el.json +++ b/homeassistant/components/denonavr/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cannot_connect": "\u0397 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac, \u03b7 \u03b1\u03c0\u03bf\u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c4\u03c9\u03bd \u03ba\u03b1\u03bb\u03c9\u03b4\u03af\u03c9\u03bd \u03c1\u03b5\u03cd\u03bc\u03b1\u03c4\u03bf\u03c2 \u03ba\u03b1\u03b9 ethernet \u03ba\u03b1\u03b9 \u03b7 \u03b5\u03c0\u03b1\u03bd\u03b1\u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03ae \u03c4\u03bf\u03c5\u03c2 \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b2\u03bf\u03b7\u03b8\u03ae\u03c3\u03b5\u03b9", "not_denonavr_manufacturer": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03ad\u03ba\u03c4\u03b7\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 Denon AVR, \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b5 \u03cc\u03c4\u03b9 \u03bf \u03ba\u03b1\u03c4\u03b1\u03c3\u03ba\u03b5\u03c5\u03b1\u03c3\u03c4\u03ae\u03c2 \u03b4\u03b5\u03bd \u03c4\u03b1\u03b9\u03c1\u03b9\u03ac\u03b6\u03b5\u03b9", "not_denonavr_missing": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03ad\u03ba\u03c4\u03b7\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 Denon AVR, \u03bf\u03b9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03bc\u03bf\u03cd \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03bb\u03ae\u03c1\u03b5\u03b9\u03c2" }, diff --git a/homeassistant/components/devolo_home_control/translations/el.json b/homeassistant/components/devolo_home_control/translations/el.json index 907bb2b0d76bb9..768d7dce0180fd 100644 --- a/homeassistant/components/devolo_home_control/translations/el.json +++ b/homeassistant/components/devolo_home_control/translations/el.json @@ -7,12 +7,14 @@ "user": { "data": { "mydevolo_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c4\u03bf\u03c5 mydevolo", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "Email / devolo ID" } }, "zeroconf_confirm": { "data": { "mydevolo_url": "mydevolo \u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "Email / \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc devolo" } } diff --git a/homeassistant/components/dexcom/translations/el.json b/homeassistant/components/dexcom/translations/el.json index 0012898808cb70..6097753fb1febc 100644 --- a/homeassistant/components/dexcom/translations/el.json +++ b/homeassistant/components/dexcom/translations/el.json @@ -7,6 +7,7 @@ "step": { "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "server": "\u0394\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 Dexcom Share", diff --git a/homeassistant/components/doorbird/translations/el.json b/homeassistant/components/doorbird/translations/el.json index 822a75ad7c77f5..64aae4a89f203c 100644 --- a/homeassistant/components/doorbird/translations/el.json +++ b/homeassistant/components/doorbird/translations/el.json @@ -13,7 +13,8 @@ "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", - "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" + "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf DoorBird" } diff --git a/homeassistant/components/dsmr/translations/el.json b/homeassistant/components/dsmr/translations/el.json index bb8a94df0cbc72..212db9b3f2f8fc 100644 --- a/homeassistant/components/dsmr/translations/el.json +++ b/homeassistant/components/dsmr/translations/el.json @@ -10,7 +10,8 @@ "setup_network": { "data": { "dsmr_version": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7\u03c2 DSMR", - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" }, "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, diff --git a/homeassistant/components/econet/translations/el.json b/homeassistant/components/econet/translations/el.json index 8e1e8eae41d7ad..2185a71ed75a91 100644 --- a/homeassistant/components/econet/translations/el.json +++ b/homeassistant/components/econet/translations/el.json @@ -2,6 +2,10 @@ "config": { "step": { "user": { + "data": { + "email": "Email", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd Rheem EcoNet" } } diff --git a/homeassistant/components/elgato/translations/el.json b/homeassistant/components/elgato/translations/el.json index ae27270b52fd7a..57300096d97c05 100644 --- a/homeassistant/components/elgato/translations/el.json +++ b/homeassistant/components/elgato/translations/el.json @@ -9,6 +9,9 @@ "flow_title": "{serial_number}", "step": { "user": { + "data": { + "port": "\u0398\u03cd\u03c1\u03b1" + }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf Elgato Light \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Home Assistant." }, "zeroconf_confirm": { diff --git a/homeassistant/components/elkm1/translations/el.json b/homeassistant/components/elkm1/translations/el.json index 46d31db307a7a2..6e95bc0cb3684c 100644 --- a/homeassistant/components/elkm1/translations/el.json +++ b/homeassistant/components/elkm1/translations/el.json @@ -8,6 +8,7 @@ "step": { "discovered_connection": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03c3\u03cd\u03c3\u03c4\u03b7\u03bc\u03b1 \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5: {mac_address} ({host})" @@ -15,6 +16,7 @@ "manual_connection": { "data": { "address": "\u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03ae \u03bf \u03c4\u03bf\u03bc\u03ad\u03b1\u03c2 \u03ae \u03b7 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1 \u03b5\u03ac\u03bd \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03b3\u03af\u03bd\u03b5\u03c4\u03b1\u03b9 \u03bc\u03ad\u03c3\u03c9 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2.", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "prefix": "\u0388\u03bd\u03b1 \u03bc\u03bf\u03bd\u03b1\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b1\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03bc\u03cc\u03bd\u03bf \u03ad\u03bd\u03b1 ElkM1).", "protocol": "\u03a0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf", "temperature_unit": "\u0397 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03c4\u03bf ElkM1.", @@ -26,6 +28,7 @@ "data": { "address": "\u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03ae \u03bf \u03c4\u03bf\u03bc\u03ad\u03b1\u03c2 \u03ae \u03b7 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1 \u03b5\u03ac\u03bd \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03b3\u03af\u03bd\u03b5\u03c4\u03b1\u03b9 \u03bc\u03ad\u03c3\u03c9 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2.", "device": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "prefix": "\u0388\u03bd\u03b1 \u03bc\u03bf\u03bd\u03b1\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b1\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03bc\u03cc\u03bd\u03bf \u03ad\u03bd\u03b1 ElkM1).", "protocol": "\u03a0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf", "temperature_unit": "\u0397 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03c4\u03bf ElkM1." diff --git a/homeassistant/components/elmax/translations/el.json b/homeassistant/components/elmax/translations/el.json index 3cdd4500930eb2..792d5297a9076c 100644 --- a/homeassistant/components/elmax/translations/el.json +++ b/homeassistant/components/elmax/translations/el.json @@ -18,6 +18,7 @@ }, "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf cloud \u03c4\u03b7\u03c2 Elmax \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ce\u03bd\u03c4\u03b1\u03c2 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2", diff --git a/homeassistant/components/elmax/translations/lv.json b/homeassistant/components/elmax/translations/lv.json new file mode 100644 index 00000000000000..9cac0f2bb8ecca --- /dev/null +++ b/homeassistant/components/elmax/translations/lv.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "panels": { + "data": { + "panel_pin": "PIN kods" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/translations/el.json b/homeassistant/components/emulated_roku/translations/el.json index 4ac528bc7ef757..c6de5f984fee54 100644 --- a/homeassistant/components/emulated_roku/translations/el.json +++ b/homeassistant/components/emulated_roku/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "advertise_port": "\u0398\u03cd\u03c1\u03b1 \u03b1\u03ba\u03c1\u03cc\u03b1\u03c3\u03b7\u03c2", "host_ip": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae", "listen_port": "\u0391\u03ba\u03c1\u03cc\u03b1\u03c3\u03b7 \u03b8\u03cd\u03c1\u03b1\u03c2", "upnp_bind_multicast": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ae\u03c2 \u03b5\u03ba\u03c0\u03bf\u03bc\u03c0\u03ae\u03c2 (\u03a3\u03c9\u03c3\u03c4\u03cc/\u039b\u03ac\u03b8\u03bf\u03c2)" diff --git a/homeassistant/components/enphase_envoy/translations/el.json b/homeassistant/components/enphase_envoy/translations/el.json index c467239ea82f39..caa16f5f8a5e62 100644 --- a/homeassistant/components/enphase_envoy/translations/el.json +++ b/homeassistant/components/enphase_envoy/translations/el.json @@ -5,6 +5,7 @@ "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0393\u03b9\u03b1 \u03bd\u03b5\u03cc\u03c4\u03b5\u03c1\u03b1 \u03bc\u03bf\u03bd\u03c4\u03ad\u03bb\u03b1, \u03c0\u03bb\u03b7\u03ba\u03c4\u03c1\u03bf\u03bb\u03bf\u03b3\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 `envoy` \u03c7\u03c9\u03c1\u03af\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2. \u0393\u03b9\u03b1 \u03c0\u03b1\u03bb\u03b1\u03b9\u03cc\u03c4\u03b5\u03c1\u03b1 \u03bc\u03bf\u03bd\u03c4\u03ad\u03bb\u03b1, \u03c0\u03bb\u03b7\u03ba\u03c4\u03c1\u03bf\u03bb\u03bf\u03b3\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 `installer` \u03c7\u03c9\u03c1\u03af\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2. \u0393\u03b9\u03b1 \u03cc\u03bb\u03b1 \u03c4\u03b1 \u03ac\u03bb\u03bb\u03b1 \u03bc\u03bf\u03bd\u03c4\u03ad\u03bb\u03b1, \u03c0\u03bb\u03b7\u03ba\u03c4\u03c1\u03bf\u03bb\u03bf\u03b3\u03ae\u03c3\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2." diff --git a/homeassistant/components/ezviz/translations/el.json b/homeassistant/components/ezviz/translations/el.json index 50724b72c5821e..1c6e2fd9809ced 100644 --- a/homeassistant/components/ezviz/translations/el.json +++ b/homeassistant/components/ezviz/translations/el.json @@ -6,13 +6,22 @@ "flow_title": "{serial}", "step": { "confirm": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 RTSP \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03ba\u03ac\u03bc\u03b5\u03c1\u03b1 Ezviz {serial} \u03bc\u03b5 IP {ip_address}", "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03ba\u03ac\u03bc\u03b5\u03c1\u03b1 Ezviz" }, "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf Ezviz Cloud" }, "user_custom_url": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03bf\u03c2 \u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03c4\u03b7\u03c2 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae\u03c2 \u03c3\u03b1\u03c2", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03b5 \u03c0\u03c1\u03bf\u03c3\u03b1\u03c1\u03bc\u03bf\u03c3\u03bc\u03ad\u03bd\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c4\u03bf\u03c5 Ezviz" } diff --git a/homeassistant/components/fireservicerota/translations/el.json b/homeassistant/components/fireservicerota/translations/el.json index 3e8ec8ece2aab0..9a0c110347f563 100644 --- a/homeassistant/components/fireservicerota/translations/el.json +++ b/homeassistant/components/fireservicerota/translations/el.json @@ -2,10 +2,14 @@ "config": { "step": { "reauth": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u03a4\u03b1 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03ac \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ad\u03b3\u03b9\u03bd\u03b1\u03bd \u03ac\u03ba\u03c5\u03c1\u03b1, \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c4\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac." }, "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "url": "\u0399\u03c3\u03c4\u03bf\u03c3\u03b5\u03bb\u03af\u03b4\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } diff --git a/homeassistant/components/fivem/translations/el.json b/homeassistant/components/fivem/translations/el.json index 9fad6c40d948d4..47ae0dc726ac6e 100644 --- a/homeassistant/components/fivem/translations/el.json +++ b/homeassistant/components/fivem/translations/el.json @@ -9,7 +9,8 @@ "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", - "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", + "port": "\u0398\u03cd\u03c1\u03b1" } } } diff --git a/homeassistant/components/flick_electric/translations/el.json b/homeassistant/components/flick_electric/translations/el.json index 136c083b7fb261..6dd1e8d2834255 100644 --- a/homeassistant/components/flick_electric/translations/el.json +++ b/homeassistant/components/flick_electric/translations/el.json @@ -4,7 +4,8 @@ "user": { "data": { "client_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03bb\u03ac\u03c4\u03b7 (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", - "client_secret": "\u039c\u03c5\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03bb\u03ac\u03c4\u03b7 (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)" + "client_secret": "\u039c\u03c5\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03bb\u03ac\u03c4\u03b7 (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "title": "\u0394\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 Flick" } diff --git a/homeassistant/components/flipr/translations/el.json b/homeassistant/components/flipr/translations/el.json index 18ef9d6890a8a8..57a48c4bb8025a 100644 --- a/homeassistant/components/flipr/translations/el.json +++ b/homeassistant/components/flipr/translations/el.json @@ -12,6 +12,10 @@ "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf Flipr \u03c3\u03b1\u03c2" }, "user": { + "data": { + "email": "Email", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ce\u03bd\u03c4\u03b1\u03c2 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Flipr.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf Flipr" } diff --git a/homeassistant/components/flume/translations/el.json b/homeassistant/components/flume/translations/el.json index 301be92c3b1dd6..1c271c60797aa9 100644 --- a/homeassistant/components/flume/translations/el.json +++ b/homeassistant/components/flume/translations/el.json @@ -2,13 +2,17 @@ "config": { "step": { "reauth_confirm": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username} \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03bb\u03ad\u03bf\u03bd \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2.", "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Flume" }, "user": { "data": { "client_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03bb\u03ac\u03c4\u03b7", - "client_secret": "\u039c\u03c5\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03bb\u03ac\u03c4\u03b7" + "client_secret": "\u039c\u03c5\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03bb\u03ac\u03c4\u03b7", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03c0\u03bf\u03ba\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7 \u03c3\u03c4\u03bf \u03c0\u03c1\u03bf\u03c3\u03c9\u03c0\u03b9\u03ba\u03cc API \u03c4\u03bf\u03c5 Flume, \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b6\u03b7\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 'Client ID' \u03ba\u03b1\u03b9 \u03ad\u03bd\u03b1 'Client Secret' \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://portal.flumetech.com/settings#token.", "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 Flume" diff --git a/homeassistant/components/foscam/translations/el.json b/homeassistant/components/foscam/translations/el.json index 43fdcaa476480c..774739eea7b67d 100644 --- a/homeassistant/components/foscam/translations/el.json +++ b/homeassistant/components/foscam/translations/el.json @@ -7,6 +7,8 @@ "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1", "rtsp_port": "\u0398\u03cd\u03c1\u03b1 RTSP", "stream": "\u03a1\u03bf\u03ae", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" diff --git a/homeassistant/components/freebox/translations/el.json b/homeassistant/components/freebox/translations/el.json index aa0a9b037371e6..ffca8cab545b0f 100644 --- a/homeassistant/components/freebox/translations/el.json +++ b/homeassistant/components/freebox/translations/el.json @@ -9,6 +9,9 @@ "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae Freebox" }, "user": { + "data": { + "port": "\u0398\u03cd\u03c1\u03b1" + }, "title": "Freebox" } } diff --git a/homeassistant/components/fritz/translations/el.json b/homeassistant/components/fritz/translations/el.json index 86343321e1858a..5f448a2e52962f 100644 --- a/homeassistant/components/fritz/translations/el.json +++ b/homeassistant/components/fritz/translations/el.json @@ -3,23 +3,33 @@ "flow_title": "{name}", "step": { "confirm": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03c4\u03bf FRITZ!Box: {name} \n\n \u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf FRITZ!Box Tools \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03bf\u03c5 {name}", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 FRITZ!Box Tools" }, "reauth_confirm": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u0395\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03c4\u03bf\u03c5 FRITZ!Box Tools \u03b3\u03b9\u03b1: {host} . \n\n \u03a4\u03bf FRITZ!Box Tools \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03c3\u03c4\u03bf FRITZ!Box \u03c3\u03b1\u03c2.", "title": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 FRITZ!Box Tools - \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1" }, "start_config": { "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf FRITZ!Box Tools \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03b5\u03c4\u03b5 \u03c4\u03bf FRITZ!Box \u03c3\u03b1\u03c2.\n \u0395\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03bf: \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7, \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2.", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 FRITZ!Box Tools - \u03c5\u03c0\u03bf\u03c7\u03c1\u03b5\u03c9\u03c4\u03b9\u03ba\u03cc" }, "user": { "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf FRITZ!Box Tools \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03b5\u03c4\u03b5 \u03c4\u03bf FRITZ!Box \u03c3\u03b1\u03c2.\n \u0395\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03bf: \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7, \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2.", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 FRITZ!Box Tools" diff --git a/homeassistant/components/fritzbox/translations/el.json b/homeassistant/components/fritzbox/translations/el.json index 21db3ef18e0c0e..d02ad4396d0bbc 100644 --- a/homeassistant/components/fritzbox/translations/el.json +++ b/homeassistant/components/fritzbox/translations/el.json @@ -6,17 +6,22 @@ "flow_title": "{name}", "step": { "confirm": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name};" }, "reauth_confirm": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0395\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {name}." }, "user": { "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf AVM FRITZ!Box." } diff --git a/homeassistant/components/fritzbox_callmonitor/translations/el.json b/homeassistant/components/fritzbox_callmonitor/translations/el.json index 62d94fbb68b97c..cac360f47a8eee 100644 --- a/homeassistant/components/fritzbox_callmonitor/translations/el.json +++ b/homeassistant/components/fritzbox_callmonitor/translations/el.json @@ -13,6 +13,8 @@ "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } diff --git a/homeassistant/components/glances/translations/el.json b/homeassistant/components/glances/translations/el.json index 4b2d98674c5062..662258abf462ea 100644 --- a/homeassistant/components/glances/translations/el.json +++ b/homeassistant/components/glances/translations/el.json @@ -6,6 +6,8 @@ "step": { "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", "version": "\u0388\u03ba\u03b4\u03bf\u03c3\u03b7 API Glances (2 \u03ae 3)" }, diff --git a/homeassistant/components/gogogate2/translations/el.json b/homeassistant/components/gogogate2/translations/el.json index dc8a810dbe3f35..2ff8f1a2c5acb0 100644 --- a/homeassistant/components/gogogate2/translations/el.json +++ b/homeassistant/components/gogogate2/translations/el.json @@ -3,6 +3,9 @@ "flow_title": "{device} ({ip_address})", "step": { "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u0394\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b1\u03c0\u03b1\u03c1\u03b1\u03af\u03c4\u03b7\u03c4\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9.", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 Gogogate2 \u03ae ismartgate" } diff --git a/homeassistant/components/goodwe/translations/el.json b/homeassistant/components/goodwe/translations/el.json index 7afcd76e1b381d..d16fc316b41329 100644 --- a/homeassistant/components/goodwe/translations/el.json +++ b/homeassistant/components/goodwe/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "connection_error": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "description": "\u03a3\u03c5\u03bd\u03b4\u03ad\u03c3\u03c4\u03b5 \u03c3\u03c4\u03bf \u03bc\u03b5\u03c4\u03b1\u03c4\u03c1\u03bf\u03c0\u03ad\u03b1", diff --git a/homeassistant/components/growatt_server/translations/el.json b/homeassistant/components/growatt_server/translations/el.json index fe012d53d8a048..6310540cb399e1 100644 --- a/homeassistant/components/growatt_server/translations/el.json +++ b/homeassistant/components/growatt_server/translations/el.json @@ -2,6 +2,9 @@ "config": { "step": { "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "title": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 Growatt \u03c3\u03b1\u03c2" } } diff --git a/homeassistant/components/hangouts/translations/el.json b/homeassistant/components/hangouts/translations/el.json index b53f8bd127dcf8..6baa206871db8e 100644 --- a/homeassistant/components/hangouts/translations/el.json +++ b/homeassistant/components/hangouts/translations/el.json @@ -14,7 +14,9 @@ }, "user": { "data": { - "authorization_code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2 (\u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03bf \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2)" + "authorization_code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2 (\u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03bf \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2)", + "email": "Email", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "description": "\u039a\u03b5\u03bd\u03cc", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 Google Hangouts" diff --git a/homeassistant/components/homewizard/translations/lv.json b/homeassistant/components/homewizard/translations/lv.json new file mode 100644 index 00000000000000..2f9c5d4ac2029e --- /dev/null +++ b/homeassistant/components/homewizard/translations/lv.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "discovery_confirm": { + "title": "Apstiprin\u0101t" + }, + "user": { + "title": "Konfigur\u0113t ier\u012bci" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/honeywell/translations/el.json b/homeassistant/components/honeywell/translations/el.json index 06ba7d17aca639..907a98fe73a7fe 100644 --- a/homeassistant/components/honeywell/translations/el.json +++ b/homeassistant/components/honeywell/translations/el.json @@ -2,6 +2,9 @@ "config": { "step": { "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b1\u03c4\u03b5 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf mytotalconnectcomfort.com.", "title": "Honeywell Total Connect Comfort (\u0397\u03a0\u0391)" } diff --git a/homeassistant/components/huawei_lte/translations/el.json b/homeassistant/components/huawei_lte/translations/el.json index 34e61e0a1ae30c..3277ea6fdad911 100644 --- a/homeassistant/components/huawei_lte/translations/el.json +++ b/homeassistant/components/huawei_lte/translations/el.json @@ -17,6 +17,7 @@ "step": { "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2.", diff --git a/homeassistant/components/huisbaasje/translations/el.json b/homeassistant/components/huisbaasje/translations/el.json index 18c2f0869bd1c2..bab52704f790ea 100644 --- a/homeassistant/components/huisbaasje/translations/el.json +++ b/homeassistant/components/huisbaasje/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } diff --git a/homeassistant/components/hvv_departures/translations/el.json b/homeassistant/components/hvv_departures/translations/el.json index 00d9a26488325b..595e17d1698139 100644 --- a/homeassistant/components/hvv_departures/translations/el.json +++ b/homeassistant/components/hvv_departures/translations/el.json @@ -18,7 +18,8 @@ }, "user": { "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf HVV API" } diff --git a/homeassistant/components/hyperion/translations/el.json b/homeassistant/components/hyperion/translations/el.json index dab904f0160f57..b61660d41c4d5d 100644 --- a/homeassistant/components/hyperion/translations/el.json +++ b/homeassistant/components/hyperion/translations/el.json @@ -24,6 +24,11 @@ }, "create_token_external": { "title": "\u0391\u03c0\u03bf\u03b4\u03bf\u03c7\u03ae \u03bd\u03ad\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03bf\u03cd \u03c3\u03c4\u03bf Hyperion UI" + }, + "user": { + "data": { + "port": "\u0398\u03cd\u03c1\u03b1" + } } } }, diff --git a/homeassistant/components/ialarm/translations/el.json b/homeassistant/components/ialarm/translations/el.json index b1115d5bf2c1e6..618e1c4e0d22c7 100644 --- a/homeassistant/components/ialarm/translations/el.json +++ b/homeassistant/components/ialarm/translations/el.json @@ -3,7 +3,8 @@ "step": { "user": { "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" } } } diff --git a/homeassistant/components/iaqualink/translations/el.json b/homeassistant/components/iaqualink/translations/el.json index 765a9f5d3b68cc..7f6732b282e09c 100644 --- a/homeassistant/components/iaqualink/translations/el.json +++ b/homeassistant/components/iaqualink/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf iAqualink.", diff --git a/homeassistant/components/icloud/translations/el.json b/homeassistant/components/icloud/translations/el.json index a7fb32ce7a0285..bb29747330ea81 100644 --- a/homeassistant/components/icloud/translations/el.json +++ b/homeassistant/components/icloud/translations/el.json @@ -20,6 +20,8 @@ }, "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "Email", "with_family": "\u039c\u03b5 \u03c4\u03b7\u03bd \u03bf\u03b9\u03ba\u03bf\u03b3\u03ad\u03bd\u03b5\u03b9\u03b1" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2", diff --git a/homeassistant/components/intellifire/translations/el.json b/homeassistant/components/intellifire/translations/el.json index b1115d5bf2c1e6..2ac7bbe8ac4688 100644 --- a/homeassistant/components/intellifire/translations/el.json +++ b/homeassistant/components/intellifire/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/ipp/translations/el.json b/homeassistant/components/ipp/translations/el.json index 3fd7a86e99dceb..ad89d55c511ecf 100644 --- a/homeassistant/components/ipp/translations/el.json +++ b/homeassistant/components/ipp/translations/el.json @@ -2,6 +2,7 @@ "config": { "abort": { "connection_upgrade": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae \u03b5\u03c0\u03b5\u03b9\u03b4\u03ae \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03bd\u03b1\u03b2\u03ac\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2.", + "ipp_version_error": "\u0397 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 IPP \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae.", "parse_error": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b1\u03bd\u03ac\u03bb\u03c5\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b1\u03c0\u03ac\u03bd\u03c4\u03b7\u03c3\u03b7\u03c2 \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae.", "unique_id_required": "\u039b\u03b5\u03af\u03c0\u03b5\u03b9 \u03b7 \u03bc\u03bf\u03bd\u03b1\u03b4\u03b9\u03ba\u03ae \u03b1\u03bd\u03b1\u03b3\u03bd\u03ce\u03c1\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03c0\u03bf\u03c5 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7." }, @@ -13,7 +14,8 @@ "user": { "data": { "base_path": "\u03a3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ae \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae", - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae \u03c3\u03b1\u03c2 \u03bc\u03ad\u03c3\u03c9 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03c9\u03c4\u03bf\u03ba\u03cc\u03bb\u03bb\u03bf\u03c5 \u03b5\u03ba\u03c4\u03cd\u03c0\u03c9\u03c3\u03b7\u03c2 Internet (IPP) \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Home Assistant.", "title": "\u03a3\u03c5\u03bd\u03b4\u03ad\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae \u03c3\u03b1\u03c2" diff --git a/homeassistant/components/iss/translations/ca.json b/homeassistant/components/iss/translations/ca.json index 218bebc5a98fe2..23e4ff4eabdd6f 100644 --- a/homeassistant/components/iss/translations/ca.json +++ b/homeassistant/components/iss/translations/ca.json @@ -9,7 +9,16 @@ "data": { "show_on_map": "Mostrar al mapa?" }, - "description": "Vols configurar Estaci\u00f3 Espacial Internacional?" + "description": "Vols configurar Estaci\u00f3 Espacial Internacional (ISS)?" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "Mostra al mapa" + } } } } diff --git a/homeassistant/components/iss/translations/el.json b/homeassistant/components/iss/translations/el.json index 4049e41ea24082..c260b24ceeb96a 100644 --- a/homeassistant/components/iss/translations/el.json +++ b/homeassistant/components/iss/translations/el.json @@ -11,5 +11,14 @@ "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u0394\u03b9\u03b5\u03b8\u03bd\u03ae \u0394\u03b9\u03b1\u03c3\u03c4\u03b7\u03bc\u03b9\u03ba\u03cc \u03a3\u03c4\u03b1\u03b8\u03bc\u03cc;" } } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03c3\u03c4\u03bf \u03c7\u03ac\u03c1\u03c4\u03b7" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/iss/translations/et.json b/homeassistant/components/iss/translations/et.json index 09104143492702..60385881b19e75 100644 --- a/homeassistant/components/iss/translations/et.json +++ b/homeassistant/components/iss/translations/et.json @@ -9,7 +9,16 @@ "data": { "show_on_map": "Kas n\u00e4idata kaardil?" }, - "description": "Kas soovid seadistada rahvusvahelist kosmosejaama?" + "description": "Kas seadistada rahvusvahelise kosmosejaama (ISS) sidumist?" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "Kuva kaardil" + } } } } diff --git a/homeassistant/components/iss/translations/hu.json b/homeassistant/components/iss/translations/hu.json index 23841f8325966b..1d75dd9ed3f72b 100644 --- a/homeassistant/components/iss/translations/hu.json +++ b/homeassistant/components/iss/translations/hu.json @@ -12,5 +12,14 @@ "description": "Szeretn\u00e9 konfigur\u00e1lni a Nemzetk\u00f6zi \u0170r\u00e1llom\u00e1st?" } } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "Megjelen\u00edt\u00e9s a t\u00e9rk\u00e9pen" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/iss/translations/it.json b/homeassistant/components/iss/translations/it.json index 148e4b91c01098..c95ea1f5082105 100644 --- a/homeassistant/components/iss/translations/it.json +++ b/homeassistant/components/iss/translations/it.json @@ -9,7 +9,16 @@ "data": { "show_on_map": "Mostrare sulla mappa?" }, - "description": "Vuoi configurare la Stazione Spaziale Internazionale?" + "description": "Vuoi configurare la stazione spaziale internazionale (ISS)?" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "Mostra sulla mappa" + } } } } diff --git a/homeassistant/components/iss/translations/no.json b/homeassistant/components/iss/translations/no.json index aec142c37cfade..c204f5a90125e1 100644 --- a/homeassistant/components/iss/translations/no.json +++ b/homeassistant/components/iss/translations/no.json @@ -9,7 +9,16 @@ "data": { "show_on_map": "Vis p\u00e5 kart?" }, - "description": "Vil du konfigurere den internasjonale romstasjonen?" + "description": "Vil du konfigurere den internasjonale romstasjonen (ISS)?" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "Vis p\u00e5 kart" + } } } } diff --git a/homeassistant/components/iss/translations/pl.json b/homeassistant/components/iss/translations/pl.json index b7ab8eebbafa6d..2cdf8ef48631c5 100644 --- a/homeassistant/components/iss/translations/pl.json +++ b/homeassistant/components/iss/translations/pl.json @@ -9,7 +9,16 @@ "data": { "show_on_map": "Pokaza\u0107 na mapie?" }, - "description": "Czy chcesz skonfigurowa\u0107 Mi\u0119dzynarodow\u0105 Stacj\u0119 Kosmiczn\u0105?" + "description": "Czy chcesz skonfigurowa\u0107 Mi\u0119dzynarodow\u0105 Stacj\u0119 Kosmiczn\u0105 (ISS)?" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "Poka\u017c na mapie" + } } } } diff --git a/homeassistant/components/iss/translations/ru.json b/homeassistant/components/iss/translations/ru.json index 277046d209d28e..8808b02f5b86b2 100644 --- a/homeassistant/components/iss/translations/ru.json +++ b/homeassistant/components/iss/translations/ru.json @@ -7,10 +7,19 @@ "step": { "user": { "data": { - "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043d\u0430 \u043a\u0430\u0440\u0442\u0435" + "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0430 \u043a\u0430\u0440\u0442\u0435" }, "description": "\u041d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 International Space Station?" } } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0430 \u043a\u0430\u0440\u0442\u0435" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/iss/translations/zh-Hant.json b/homeassistant/components/iss/translations/zh-Hant.json index e59aa3a3be587a..f50730ff9a47d0 100644 --- a/homeassistant/components/iss/translations/zh-Hant.json +++ b/homeassistant/components/iss/translations/zh-Hant.json @@ -9,7 +9,16 @@ "data": { "show_on_map": "\u65bc\u5730\u5716\u986f\u793a\uff1f" }, - "description": "\u662f\u5426\u8981\u8a2d\u5b9a\u570b\u969b\u592a\u7a7a\u7ad9\uff1f" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a\u570b\u969b\u592a\u7a7a\u7ad9\uff08ISS\uff09\uff1f" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "\u65bc\u5730\u5716\u986f\u793a" + } } } } diff --git a/homeassistant/components/isy994/translations/el.json b/homeassistant/components/isy994/translations/el.json index d7732fb29f9599..e9b7350a2fe1c3 100644 --- a/homeassistant/components/isy994/translations/el.json +++ b/homeassistant/components/isy994/translations/el.json @@ -7,6 +7,7 @@ "step": { "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "tls": "\u0397 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 TLS \u03c4\u03bf\u03c5 \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ae ISY." }, "description": "\u0397 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03b5 \u03c0\u03bb\u03ae\u03c1\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae URL, \u03c0.\u03c7. http://192.168.10.100:80", diff --git a/homeassistant/components/jellyfin/translations/el.json b/homeassistant/components/jellyfin/translations/el.json index 18c2f0869bd1c2..bab52704f790ea 100644 --- a/homeassistant/components/jellyfin/translations/el.json +++ b/homeassistant/components/jellyfin/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } diff --git a/homeassistant/components/keenetic_ndms2/translations/el.json b/homeassistant/components/keenetic_ndms2/translations/el.json index 99d75f26cc0112..c76b5ba725201d 100644 --- a/homeassistant/components/keenetic_ndms2/translations/el.json +++ b/homeassistant/components/keenetic_ndms2/translations/el.json @@ -9,6 +9,8 @@ "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae Keenetic NDMS2" diff --git a/homeassistant/components/kmtronic/translations/el.json b/homeassistant/components/kmtronic/translations/el.json index c6929ecbbf2330..2646eb1883ec8f 100644 --- a/homeassistant/components/kmtronic/translations/el.json +++ b/homeassistant/components/kmtronic/translations/el.json @@ -4,6 +4,7 @@ "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } diff --git a/homeassistant/components/knx/translations/el.json b/homeassistant/components/knx/translations/el.json index f08f75f2da9bfc..57714e8ee956b3 100644 --- a/homeassistant/components/knx/translations/el.json +++ b/homeassistant/components/knx/translations/el.json @@ -6,6 +6,7 @@ "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "individual_address": "\u0391\u03c4\u03bf\u03bc\u03b9\u03ba\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7", "local_ip": "\u03a4\u03bf\u03c0\u03b9\u03ba\u03ae IP \u03c4\u03bf\u03c5 Home Assistant (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03b5\u03bd\u03ae \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7)", + "port": "\u0398\u03cd\u03c1\u03b1", "route_back": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 Route Back / NAT", "tunneling_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03ae\u03c1\u03b1\u03b3\u03b3\u03b1\u03c2 KNX" }, @@ -51,6 +52,7 @@ "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "local_ip": "\u03a4\u03bf\u03c0\u03b9\u03ba\u03ae IP (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03b5\u03bd\u03ae \u03b1\u03bd \u03b4\u03b5\u03bd \u03b5\u03af\u03c3\u03c4\u03b5 \u03c3\u03af\u03b3\u03bf\u03c5\u03c1\u03bf\u03b9)", + "port": "\u0398\u03cd\u03c1\u03b1", "route_back": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 Route Back / NAT", "tunneling_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03ae\u03c1\u03b1\u03b3\u03b3\u03b1\u03c2 KNX" } diff --git a/homeassistant/components/konnected/translations/el.json b/homeassistant/components/konnected/translations/el.json index 13afb78596ffe6..fa290b1bfdca82 100644 --- a/homeassistant/components/konnected/translations/el.json +++ b/homeassistant/components/konnected/translations/el.json @@ -13,6 +13,9 @@ "title": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 Konnected" }, "user": { + "data": { + "port": "\u0398\u03cd\u03c1\u03b1" + }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03b3\u03b9\u03b1 \u03c4\u03bf Konnected Panel \u03c3\u03b1\u03c2." } } diff --git a/homeassistant/components/kostal_plenticore/translations/el.json b/homeassistant/components/kostal_plenticore/translations/el.json index aa6c808eec74b9..d927982617cde4 100644 --- a/homeassistant/components/kostal_plenticore/translations/el.json +++ b/homeassistant/components/kostal_plenticore/translations/el.json @@ -3,7 +3,8 @@ "step": { "user": { "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" } } } diff --git a/homeassistant/components/life360/translations/el.json b/homeassistant/components/life360/translations/el.json index 26ad68c7d5b35d..d49ed2e70d5b59 100644 --- a/homeassistant/components/life360/translations/el.json +++ b/homeassistant/components/life360/translations/el.json @@ -9,6 +9,7 @@ "step": { "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03bf\u03c1\u03af\u03c3\u03b5\u03c4\u03b5 \u03c0\u03c1\u03bf\u03b7\u03b3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2, \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7 Life360]({docs_url}).\n\u039c\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c4\u03bf \u03ba\u03ac\u03bd\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c0\u03c1\u03b9\u03bd \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd\u03c2.", diff --git a/homeassistant/components/litejet/translations/el.json b/homeassistant/components/litejet/translations/el.json index 98eaae46a3fc7c..929bcbbc24841d 100644 --- a/homeassistant/components/litejet/translations/el.json +++ b/homeassistant/components/litejet/translations/el.json @@ -5,6 +5,9 @@ }, "step": { "user": { + "data": { + "port": "\u0398\u03cd\u03c1\u03b1" + }, "description": "\u03a3\u03c5\u03bd\u03b4\u03ad\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03b8\u03cd\u03c1\u03b1 RS232-2 \u03c4\u03bf\u03c5 LiteJet \u03c3\u03c4\u03bf\u03bd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03c3\u03b1\u03c2 \u03ba\u03b1\u03b9 \u03b5\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c0\u03c1\u03bf\u03c2 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae\u03c2 \u03b8\u03cd\u03c1\u03b1\u03c2.\n\n\u03a4\u03bf LiteJet MCP \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af \u03b3\u03b9\u03b1 19,2 K baud, 8 bit \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd, 1 stop bit, \u03c7\u03c9\u03c1\u03af\u03c2 \u03b9\u03c3\u03bf\u03c4\u03b9\u03bc\u03af\u03b1 \u03ba\u03b1\u03b9 \u03bd\u03b1 \u03bc\u03b5\u03c4\u03b1\u03b4\u03af\u03b4\u03b5\u03b9 \u03ad\u03bd\u03b1 'CR' \u03bc\u03b5\u03c4\u03ac \u03b1\u03c0\u03cc \u03ba\u03ac\u03b8\u03b5 \u03b1\u03c0\u03ac\u03bd\u03c4\u03b7\u03c3\u03b7.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf LiteJet" } diff --git a/homeassistant/components/litterrobot/translations/el.json b/homeassistant/components/litterrobot/translations/el.json index 18c2f0869bd1c2..bab52704f790ea 100644 --- a/homeassistant/components/litterrobot/translations/el.json +++ b/homeassistant/components/litterrobot/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } diff --git a/homeassistant/components/luftdaten/translations/hu.json b/homeassistant/components/luftdaten/translations/hu.json index 2fa90c23ca8d25..f7a241533da02a 100644 --- a/homeassistant/components/luftdaten/translations/hu.json +++ b/homeassistant/components/luftdaten/translations/hu.json @@ -8,7 +8,7 @@ "step": { "user": { "data": { - "show_on_map": "Mutasd a t\u00e9rk\u00e9pen", + "show_on_map": "Megjelen\u00edt\u00e9s a t\u00e9rk\u00e9pen", "station_id": "Luftdaten \u00e9rz\u00e9kel\u0151 ID" }, "title": "Luftdaten be\u00e1ll\u00edt\u00e1sa" diff --git a/homeassistant/components/luftdaten/translations/ru.json b/homeassistant/components/luftdaten/translations/ru.json index 5891bb1e3ddbac..82813ee3a53376 100644 --- a/homeassistant/components/luftdaten/translations/ru.json +++ b/homeassistant/components/luftdaten/translations/ru.json @@ -8,7 +8,7 @@ "step": { "user": { "data": { - "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043d\u0430 \u043a\u0430\u0440\u0442\u0435", + "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0430 \u043a\u0430\u0440\u0442\u0435", "station_id": "ID \u0434\u0430\u0442\u0447\u0438\u043a\u0430" }, "title": "Luftdaten" diff --git a/homeassistant/components/mazda/translations/el.json b/homeassistant/components/mazda/translations/el.json index 1bafcdf8e44415..d998a1a9d74dd5 100644 --- a/homeassistant/components/mazda/translations/el.json +++ b/homeassistant/components/mazda/translations/el.json @@ -6,6 +6,8 @@ "step": { "user": { "data": { + "email": "Email", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "region": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03b7\u03bb\u03b5\u03ba\u03c4\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03c4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b5\u03af\u03bf\u03c5 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae MyMazda \u03b3\u03b9\u03b1 \u03ba\u03b9\u03bd\u03b7\u03c4\u03ac.", diff --git a/homeassistant/components/melcloud/translations/el.json b/homeassistant/components/melcloud/translations/el.json index 15d48e9c63cf54..d6b6c51e6f53c9 100644 --- a/homeassistant/components/melcloud/translations/el.json +++ b/homeassistant/components/melcloud/translations/el.json @@ -5,6 +5,10 @@ }, "step": { "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "Email" + }, "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ce\u03bd\u03c4\u03b1\u03c2 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf MELCloud.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf MELCloud" } diff --git a/homeassistant/components/mikrotik/translations/el.json b/homeassistant/components/mikrotik/translations/el.json index 0304a1423f6746..2a94c2fa87da6b 100644 --- a/homeassistant/components/mikrotik/translations/el.json +++ b/homeassistant/components/mikrotik/translations/el.json @@ -6,6 +6,8 @@ "step": { "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", "verify_ssl": "\u03a7\u03c1\u03ae\u03c3\u03b7 ssl" }, diff --git a/homeassistant/components/mill/translations/el.json b/homeassistant/components/mill/translations/el.json index 29ffda9a5e9b5d..37b91a3dce8ce7 100644 --- a/homeassistant/components/mill/translations/el.json +++ b/homeassistant/components/mill/translations/el.json @@ -3,6 +3,7 @@ "step": { "cloud": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } }, @@ -11,7 +12,8 @@ }, "user": { "data": { - "connection_type": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c4\u03cd\u03c0\u03bf\u03c5 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "connection_type": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c4\u03cd\u03c0\u03bf\u03c5 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03cd\u03c0\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2. \u0397 \u03c4\u03bf\u03c0\u03b9\u03ba\u03ae \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03b8\u03b5\u03c1\u03bc\u03b1\u03bd\u03c4\u03ae\u03c1\u03b5\u03c2 3\u03b7\u03c2 \u03b3\u03b5\u03bd\u03b9\u03ac\u03c2" } diff --git a/homeassistant/components/modem_callerid/translations/el.json b/homeassistant/components/modem_callerid/translations/el.json index 447231bcb0c540..3abe8e270afeb1 100644 --- a/homeassistant/components/modem_callerid/translations/el.json +++ b/homeassistant/components/modem_callerid/translations/el.json @@ -10,7 +10,8 @@ }, "user": { "data": { - "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", + "port": "\u0398\u03cd\u03c1\u03b1" }, "description": "\u03a0\u03c1\u03cc\u03ba\u03b5\u03b9\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03ba\u03bb\u03ae\u03c3\u03b5\u03b9\u03c2 \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae\u03c2 \u03c4\u03b7\u03bb\u03b5\u03c6\u03c9\u03bd\u03af\u03b1\u03c2 \u03bc\u03b5 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c6\u03c9\u03bd\u03b7\u03c4\u03b9\u03ba\u03bf\u03cd \u03bc\u03cc\u03bd\u03c4\u03b5\u03bc CX93001. \u0391\u03c5\u03c4\u03cc \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b1\u03bd\u03b1\u03ba\u03c4\u03ae\u03c3\u03b5\u03b9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ba\u03b1\u03bb\u03bf\u03cd\u03bd\u03c4\u03bf\u03c2 \u03bc\u03b5 \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b1\u03c0\u03cc\u03c1\u03c1\u03b9\u03c8\u03b7\u03c2 \u03bc\u03b9\u03b1\u03c2 \u03b5\u03b9\u03c3\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03b7\u03c2 \u03ba\u03bb\u03ae\u03c3\u03b7\u03c2.", "title": "\u039c\u03cc\u03bd\u03c4\u03b5\u03bc \u03c4\u03b7\u03bb\u03b5\u03c6\u03ce\u03bd\u03bf\u03c5" diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/el.json b/homeassistant/components/moehlenhoff_alpha2/translations/el.json index a044d731d173be..7750317d34dc43 100644 --- a/homeassistant/components/moehlenhoff_alpha2/translations/el.json +++ b/homeassistant/components/moehlenhoff_alpha2/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/lv.json b/homeassistant/components/moehlenhoff_alpha2/translations/lv.json new file mode 100644 index 00000000000000..d15111e97b0d19 --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/translations/lv.json @@ -0,0 +1,3 @@ +{ + "title": "M\u00f6hlenhoff Alpha2" +} \ No newline at end of file diff --git a/homeassistant/components/monoprice/translations/el.json b/homeassistant/components/monoprice/translations/el.json index 28c82949398743..57a99233d13757 100644 --- a/homeassistant/components/monoprice/translations/el.json +++ b/homeassistant/components/monoprice/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "port": "\u0398\u03cd\u03c1\u03b1", "source_1": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b7\u03b3\u03ae\u03c2 #1", "source_2": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b7\u03b3\u03ae\u03c2 #2", "source_3": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b7\u03b3\u03ae\u03c2 #3", diff --git a/homeassistant/components/motion_blinds/translations/el.json b/homeassistant/components/motion_blinds/translations/el.json index 50851e08956823..f820e4ef03ea7d 100644 --- a/homeassistant/components/motion_blinds/translations/el.json +++ b/homeassistant/components/motion_blinds/translations/el.json @@ -1,10 +1,14 @@ { "config": { "error": { - "discovery_error": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03c0\u03cd\u03bb\u03b7 \u03ba\u03af\u03bd\u03b7\u03c3\u03b7\u03c2" + "discovery_error": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03c0\u03cd\u03bb\u03b7 \u03ba\u03af\u03bd\u03b7\u03c3\u03b7\u03c2", + "invalid_interface": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03c0\u03b1\u03c6\u03ae \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5" }, "step": { "connect": { + "data": { + "interface": "\u0397 \u03b4\u03b9\u03b1\u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af" + }, "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API 16 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03c9\u03bd, \u03b4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key \u03b3\u03b9\u03b1 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2.", "title": "Motion Blinds" }, @@ -17,6 +21,9 @@ "options": { "step": { "init": { + "data": { + "wait_for_push": "\u0391\u03bd\u03b1\u03bc\u03bf\u03bd\u03ae \u03b3\u03b9\u03b1 multicast push \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7" + }, "description": "\u039a\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ce\u03bd \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c9\u03bd", "title": "Motion Blinds" } diff --git a/homeassistant/components/motioneye/translations/el.json b/homeassistant/components/motioneye/translations/el.json index 110dc2a5c0513b..de36fc19681242 100644 --- a/homeassistant/components/motioneye/translations/el.json +++ b/homeassistant/components/motioneye/translations/el.json @@ -9,7 +9,9 @@ }, "user": { "data": { + "admin_password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b4\u03b9\u03b1\u03c7\u03b5\u03b9\u03c1\u03b9\u03c3\u03c4\u03ae", "admin_username": "\u0394\u03b9\u03b1\u03c7\u03b5\u03b9\u03c1\u03b9\u03c3\u03c4\u03ae\u03c2 \u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", + "surveillance_password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03c0\u03b9\u03c4\u03ae\u03c1\u03b7\u03c3\u03b7\u03c2", "surveillance_username": "\u0395\u03c0\u03b9\u03c4\u03ae\u03c1\u03b7\u03c3\u03b7 \u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } @@ -19,7 +21,8 @@ "step": { "init": { "data": { - "stream_url_template": "\u03a0\u03c1\u03cc\u03c4\u03c5\u03c0\u03bf URL \u03c1\u03bf\u03ae\u03c2" + "stream_url_template": "\u03a0\u03c1\u03cc\u03c4\u03c5\u03c0\u03bf URL \u03c1\u03bf\u03ae\u03c2", + "webhook_set": "\u0394\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 webhooks motionEye \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03bd\u03b1\u03c6\u03ad\u03c1\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03b1 \u03c3\u03c4\u03bf\u03bd Home Assistant" } } } diff --git a/homeassistant/components/motioneye/translations/it.json b/homeassistant/components/motioneye/translations/it.json index 4bc75878b2b2e5..f8b99b0c09b095 100644 --- a/homeassistant/components/motioneye/translations/it.json +++ b/homeassistant/components/motioneye/translations/it.json @@ -17,9 +17,9 @@ }, "user": { "data": { - "admin_password": "Amministratore Password", + "admin_password": "Password di amministrazione", "admin_username": "Amministratore Nome utente", - "surveillance_password": "Sorveglianza Password", + "surveillance_password": "Password di sorveglianza", "surveillance_username": "Sorveglianza Nome utente", "url": "URL" } diff --git a/homeassistant/components/mqtt/translations/el.json b/homeassistant/components/mqtt/translations/el.json index 4cf45560e8eb13..5b06a6c3d0104f 100644 --- a/homeassistant/components/mqtt/translations/el.json +++ b/homeassistant/components/mqtt/translations/el.json @@ -8,6 +8,8 @@ "data": { "broker": "\u039c\u03b5\u03c3\u03af\u03c4\u03b7\u03c2", "discovery": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 MQTT broker." @@ -45,7 +47,8 @@ "step": { "broker": { "data": { - "broker": "\u039c\u03b5\u03c3\u03af\u03c4\u03b7\u03c2" + "broker": "\u039c\u03b5\u03c3\u03af\u03c4\u03b7\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03bc\u03b5\u03c3\u03af\u03c4\u03b7 MQTT.", "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 broker" diff --git a/homeassistant/components/myq/translations/el.json b/homeassistant/components/myq/translations/el.json index 24b945708d3b2d..e0ad2d03cff68e 100644 --- a/homeassistant/components/myq/translations/el.json +++ b/homeassistant/components/myq/translations/el.json @@ -2,10 +2,16 @@ "config": { "step": { "reauth_confirm": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username} \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03bb\u03ad\u03bf\u03bd \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2.", "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03c3\u03b1\u03c2 MyQ" }, "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b7\u03bd \u03c0\u03cd\u03bb\u03b7 MyQ" } } diff --git a/homeassistant/components/nam/translations/el.json b/homeassistant/components/nam/translations/el.json index cc054a26d97dd8..e1e7e8ff07900d 100644 --- a/homeassistant/components/nam/translations/el.json +++ b/homeassistant/components/nam/translations/el.json @@ -11,12 +11,14 @@ }, "credentials": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2." }, "reauth_confirm": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03c3\u03c9\u03c3\u03c4\u03cc \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae: {host}" diff --git a/homeassistant/components/nest/translations/el.json b/homeassistant/components/nest/translations/el.json index b507bda970d03a..cdd1dc4b21569d 100644 --- a/homeassistant/components/nest/translations/el.json +++ b/homeassistant/components/nest/translations/el.json @@ -9,6 +9,7 @@ }, "step": { "auth": { + "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Google, [\u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2]({url}).\n\n\u039c\u03b5\u03c4\u03ac \u03c4\u03b7\u03bd \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7, \u03b1\u03bd\u03c4\u03b9\u03b3\u03c1\u03ac\u03c8\u03c4\u03b5-\u03b5\u03c0\u03b9\u03ba\u03bf\u03bb\u03bb\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9 \u03c0\u03b1\u03c1\u03b5\u03c7\u03cc\u03bc\u03b5\u03bd\u03bf \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc Auth Token.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd Google" }, "init": { diff --git a/homeassistant/components/netatmo/translations/hu.json b/homeassistant/components/netatmo/translations/hu.json index c2a702277feded..ae781d86ca8a9e 100644 --- a/homeassistant/components/netatmo/translations/hu.json +++ b/homeassistant/components/netatmo/translations/hu.json @@ -52,7 +52,7 @@ "lon_ne": "\u00c9szakkeleti sarok hossz\u00fas\u00e1gi fok", "lon_sw": "D\u00e9lnyugati sarok hossz\u00fas\u00e1ggi fok", "mode": "Sz\u00e1m\u00edt\u00e1s", - "show_on_map": "Mutasd a t\u00e9rk\u00e9pen" + "show_on_map": "Megjelen\u00edt\u00e9s a t\u00e9rk\u00e9pen" }, "description": "\u00c1ll\u00edtson be egy nyilv\u00e1nos id\u0151j\u00e1r\u00e1s-\u00e9rz\u00e9kel\u0151t egy ter\u00fclethez.", "title": "Netatmo nyilv\u00e1nos id\u0151j\u00e1r\u00e1s-\u00e9rz\u00e9kel\u0151" diff --git a/homeassistant/components/netatmo/translations/ru.json b/homeassistant/components/netatmo/translations/ru.json index 1bb004b5464939..ff089be667f779 100644 --- a/homeassistant/components/netatmo/translations/ru.json +++ b/homeassistant/components/netatmo/translations/ru.json @@ -52,7 +52,7 @@ "lon_ne": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430 (\u0441\u0435\u0432\u0435\u0440\u043e-\u0432\u043e\u0441\u0442\u043e\u0447\u043d\u044b\u0439 \u0443\u0433\u043e\u043b)", "lon_sw": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430 (\u044e\u0433\u043e-\u0437\u0430\u043f\u0430\u0434\u043d\u044b\u0439 \u0443\u0433\u043e\u043b)", "mode": "\u0420\u0430\u0441\u0447\u0435\u0442", - "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043d\u0430 \u043a\u0430\u0440\u0442\u0435" + "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0430 \u043a\u0430\u0440\u0442\u0435" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043e\u0431\u0449\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0433\u043e \u0434\u0430\u0442\u0447\u0438\u043a\u0430 \u043f\u043e\u0433\u043e\u0434\u044b \u0434\u043b\u044f \u043e\u0431\u043b\u0430\u0441\u0442\u0438.", "title": "\u041e\u0431\u0449\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0439 \u0434\u0430\u0442\u0447\u0438\u043a \u043f\u043e\u0433\u043e\u0434\u044b Netatmo" diff --git a/homeassistant/components/netgear/translations/el.json b/homeassistant/components/netgear/translations/el.json index 59a86b9dcef8f5..3ad2637a5d2e3e 100644 --- a/homeassistant/components/netgear/translations/el.json +++ b/homeassistant/components/netgear/translations/el.json @@ -7,6 +7,7 @@ "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2 (\u03a0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "port": "\u0398\u03cd\u03c1\u03b1 (\u03a0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 (\u03a0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)" }, diff --git a/homeassistant/components/nexia/translations/el.json b/homeassistant/components/nexia/translations/el.json index bfd24267091b87..87e5fd2cd8d8b2 100644 --- a/homeassistant/components/nexia/translations/el.json +++ b/homeassistant/components/nexia/translations/el.json @@ -3,7 +3,8 @@ "step": { "user": { "data": { - "brand": "\u039c\u03ac\u03c1\u03ba\u03b1" + "brand": "\u039c\u03ac\u03c1\u03ba\u03b1", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 mynexia.com" } diff --git a/homeassistant/components/notion/translations/el.json b/homeassistant/components/notion/translations/el.json index 24e5bde93c5f44..af900a992a648e 100644 --- a/homeassistant/components/notion/translations/el.json +++ b/homeassistant/components/notion/translations/el.json @@ -5,10 +5,14 @@ }, "step": { "reauth_confirm": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username}." }, "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03b1\u03c2" diff --git a/homeassistant/components/nuheat/translations/el.json b/homeassistant/components/nuheat/translations/el.json index d398d5895137aa..7723b5bbdb9237 100644 --- a/homeassistant/components/nuheat/translations/el.json +++ b/homeassistant/components/nuheat/translations/el.json @@ -6,6 +6,7 @@ "step": { "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "serial_number": "\u03a3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03c4\u03bf\u03c5 \u03b8\u03b5\u03c1\u03bc\u03bf\u03c3\u03c4\u03ac\u03c4\u03b7." }, "description": "\u0398\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03b7\u03c4\u03b9\u03ba\u03cc \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03ae \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03bf\u03c5 \u03b8\u03b5\u03c1\u03bc\u03bf\u03c3\u03c4\u03ac\u03c4\u03b7 \u03c3\u03b1\u03c2, \u03c3\u03c5\u03bd\u03b4\u03b5\u03cc\u03bc\u03b5\u03bd\u03bf\u03b9 \u03c3\u03c4\u03bf https://MyNuHeat.com \u03ba\u03b1\u03b9 \u03b5\u03c0\u03b9\u03bb\u03ad\u03b3\u03bf\u03bd\u03c4\u03b1\u03c2 \u03c4\u03bf\u03bd/\u03c4\u03bf\u03c5\u03c2 \u03b8\u03b5\u03c1\u03bc\u03bf\u03c3\u03c4\u03ac\u03c4\u03b5\u03c2 \u03c3\u03b1\u03c2.", diff --git a/homeassistant/components/nuki/translations/el.json b/homeassistant/components/nuki/translations/el.json index cbe07cce0f74db..db3aa9c03d38e1 100644 --- a/homeassistant/components/nuki/translations/el.json +++ b/homeassistant/components/nuki/translations/el.json @@ -6,7 +6,8 @@ }, "user": { "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" } } } diff --git a/homeassistant/components/nut/translations/el.json b/homeassistant/components/nut/translations/el.json index 80d36689553f1a..6d28f2d1842a4d 100644 --- a/homeassistant/components/nut/translations/el.json +++ b/homeassistant/components/nut/translations/el.json @@ -16,7 +16,9 @@ }, "user": { "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" }, "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae NUT" } diff --git a/homeassistant/components/oncue/translations/el.json b/homeassistant/components/oncue/translations/el.json index 18c2f0869bd1c2..bab52704f790ea 100644 --- a/homeassistant/components/oncue/translations/el.json +++ b/homeassistant/components/oncue/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } diff --git a/homeassistant/components/onewire/translations/el.json b/homeassistant/components/onewire/translations/el.json index ae7c5ea3f307ee..5b17c464742874 100644 --- a/homeassistant/components/onewire/translations/el.json +++ b/homeassistant/components/onewire/translations/el.json @@ -5,6 +5,9 @@ }, "step": { "owserver": { + "data": { + "port": "\u0398\u03cd\u03c1\u03b1" + }, "title": "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03b5\u03c1\u03b5\u03b9\u03ce\u03bd owserver" }, "user": { diff --git a/homeassistant/components/onvif/translations/el.json b/homeassistant/components/onvif/translations/el.json index dedd4eb3def0f6..9933194812094f 100644 --- a/homeassistant/components/onvif/translations/el.json +++ b/homeassistant/components/onvif/translations/el.json @@ -15,6 +15,10 @@ "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "configure": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" + }, "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 ONVIF" }, "configure_profile": { diff --git a/homeassistant/components/open_meteo/translations/lv.json b/homeassistant/components/open_meteo/translations/lv.json new file mode 100644 index 00000000000000..84524b20786606 --- /dev/null +++ b/homeassistant/components/open_meteo/translations/lv.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "zone": "Zona" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/opengarage/translations/el.json b/homeassistant/components/opengarage/translations/el.json index e63383e4d7cac2..ac63fdd0fbfa38 100644 --- a/homeassistant/components/opengarage/translations/el.json +++ b/homeassistant/components/opengarage/translations/el.json @@ -3,7 +3,8 @@ "step": { "user": { "data": { - "device_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" + "device_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" } } } diff --git a/homeassistant/components/overkiz/translations/el.json b/homeassistant/components/overkiz/translations/el.json index b81293d60a6526..58a68ae1aa4367 100644 --- a/homeassistant/components/overkiz/translations/el.json +++ b/homeassistant/components/overkiz/translations/el.json @@ -4,6 +4,7 @@ "reauth_wrong_account": "\u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03bc\u03cc\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf\u03bd \u03af\u03b4\u03b9\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03af\u03b4\u03b9\u03bf \u03ba\u03cc\u03bc\u03b2\u03bf Overkiz." }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "server_in_maintenance": "\u039f \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03ba\u03c4\u03cc\u03c2 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c3\u03c5\u03bd\u03c4\u03ae\u03c1\u03b7\u03c3\u03b7", "too_many_requests": "\u03a0\u03ac\u03c1\u03b1 \u03c0\u03bf\u03bb\u03bb\u03ac \u03b1\u03b9\u03c4\u03ae\u03bc\u03b1\u03c4\u03b1, \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1." }, @@ -13,6 +14,7 @@ "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "hub": "\u039a\u03cc\u03bc\u03b2\u03bf\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0397 \u03c0\u03bb\u03b1\u03c4\u03c6\u03cc\u03c1\u03bc\u03b1 Overkiz \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03b4\u03b9\u03ac\u03c6\u03bf\u03c1\u03bf\u03c5\u03c2 \u03c0\u03c1\u03bf\u03bc\u03b7\u03b8\u03b5\u03c5\u03c4\u03ad\u03c2 \u03cc\u03c0\u03c9\u03c2 \u03b7 Somfy (Connexoon / TaHoma), \u03b7 Hitachi (Hi Kumo), \u03b7 Rexel (Energeasy Connect) \u03ba\u03b1\u03b9 \u03b7 Atlantic (Cozytouch). \u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03c4\u03b7\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03ba\u03b1\u03b9 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03cc\u03bc\u03b2\u03bf \u03c3\u03b1\u03c2." diff --git a/homeassistant/components/overkiz/translations/sensor.lv.json b/homeassistant/components/overkiz/translations/sensor.lv.json new file mode 100644 index 00000000000000..6dda6c1414544a --- /dev/null +++ b/homeassistant/components/overkiz/translations/sensor.lv.json @@ -0,0 +1,20 @@ +{ + "state": { + "overkiz__priority_lock_originator": { + "external_gateway": "\u0100r\u0113j\u0101 v\u0101rteja", + "local_user": "Viet\u0113jais lietot\u0101js", + "lsc": "LSC", + "myself": "Es pats", + "rain": "Lietus", + "saac": "SAAC", + "security": "Dro\u0161\u012bba", + "sfc": "SFC", + "temperature": "Temperat\u016bra", + "ups": "UPS" + }, + "overkiz__sensor_room": { + "clean": "T\u012brs", + "dirty": "Net\u012brs" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ovo_energy/translations/el.json b/homeassistant/components/ovo_energy/translations/el.json index d1981d80c7e676..c56dbc9bc45bb2 100644 --- a/homeassistant/components/ovo_energy/translations/el.json +++ b/homeassistant/components/ovo_energy/translations/el.json @@ -2,6 +2,12 @@ "config": { "flow_title": "{username}", "step": { + "reauth": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, + "description": "\u039f \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b3\u03b9\u03b1 \u03c4\u03bf OVO Energy. \u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c4\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2." + }, "user": { "data": { "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" diff --git a/homeassistant/components/picnic/translations/ca.json b/homeassistant/components/picnic/translations/ca.json index 292ae6a8539e35..83c0b75f9d3c08 100644 --- a/homeassistant/components/picnic/translations/ca.json +++ b/homeassistant/components/picnic/translations/ca.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "El dispositiu ja est\u00e0 configurat", - "reauth_successful": "La re-autenticaci\u00f3 ha estat satisfact\u00f2ria" + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3", diff --git a/homeassistant/components/picnic/translations/el.json b/homeassistant/components/picnic/translations/el.json index 206fbdab2a2675..32c00287a347d6 100644 --- a/homeassistant/components/picnic/translations/el.json +++ b/homeassistant/components/picnic/translations/el.json @@ -9,7 +9,8 @@ "step": { "user": { "data": { - "country_code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c7\u03ce\u03c1\u03b1\u03c2" + "country_code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c7\u03ce\u03c1\u03b1\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" } } } diff --git a/homeassistant/components/plugwise/translations/el.json b/homeassistant/components/plugwise/translations/el.json index 45247108dc690d..82588fd8951c7c 100644 --- a/homeassistant/components/plugwise/translations/el.json +++ b/homeassistant/components/plugwise/translations/el.json @@ -11,6 +11,7 @@ }, "user_gateway": { "data": { + "port": "\u0398\u03cd\u03c1\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 Smile" } } diff --git a/homeassistant/components/plum_lightpad/translations/el.json b/homeassistant/components/plum_lightpad/translations/el.json new file mode 100644 index 00000000000000..5561ddd5d2a4a7 --- /dev/null +++ b/homeassistant/components/plum_lightpad/translations/el.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/poolsense/translations/el.json b/homeassistant/components/poolsense/translations/el.json index 8b1e7f9d28205c..f6af3b500f22ec 100644 --- a/homeassistant/components/poolsense/translations/el.json +++ b/homeassistant/components/poolsense/translations/el.json @@ -2,6 +2,10 @@ "config": { "step": { "user": { + "data": { + "email": "Email", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "title": "PoolSense" } } diff --git a/homeassistant/components/powerwall/translations/el.json b/homeassistant/components/powerwall/translations/el.json index d3263b9c849ce3..eba13432262f1a 100644 --- a/homeassistant/components/powerwall/translations/el.json +++ b/homeassistant/components/powerwall/translations/el.json @@ -9,9 +9,15 @@ "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ( {ip_address});" }, "reauth_confim": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 powerwall" }, "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03ae\u03b8\u03c9\u03c2 \u03bf\u03b9 \u03c4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03bf\u03b9 5 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b5\u03c2 \u03c4\u03bf\u03c5 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03bf\u03cd \u03b1\u03c1\u03b9\u03b8\u03bc\u03bf\u03cd \u03b3\u03b9\u03b1 \u03c4\u03bf Backup Gateway \u03ba\u03b1\u03b9 \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b2\u03c1\u03b5\u03b8\u03b5\u03af \u03c3\u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae Tesla \u03ae \u03bf\u03b9 \u03c4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03bf\u03b9 5 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b5\u03c2 \u03c4\u03bf\u03c5 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c0\u03bf\u03c5 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03bf \u03b5\u03c3\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03cc \u03c4\u03b7\u03c2 \u03c0\u03cc\u03c1\u03c4\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf Backup Gateway 2.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf powerwall" } diff --git a/homeassistant/components/prosegur/translations/el.json b/homeassistant/components/prosegur/translations/el.json index a24f4954f97f73..cd3d44f62e432b 100644 --- a/homeassistant/components/prosegur/translations/el.json +++ b/homeassistant/components/prosegur/translations/el.json @@ -4,12 +4,14 @@ "reauth_confirm": { "data": { "description": "\u0395\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc Prosegur.", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } }, "user": { "data": { "country": "\u03a7\u03ce\u03c1\u03b1", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } diff --git a/homeassistant/components/pvoutput/translations/lv.json b/homeassistant/components/pvoutput/translations/lv.json new file mode 100644 index 00000000000000..eea9a1e042d0fd --- /dev/null +++ b/homeassistant/components/pvoutput/translations/lv.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "system_id": "Sist\u0113mas ID" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rainmachine/translations/el.json b/homeassistant/components/rainmachine/translations/el.json index 4fb06d067dc2a0..e7aea4fbd89aad 100644 --- a/homeassistant/components/rainmachine/translations/el.json +++ b/homeassistant/components/rainmachine/translations/el.json @@ -4,7 +4,9 @@ "step": { "user": { "data": { - "ip_address": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + "ip_address": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" }, "title": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03b1\u03c2" } diff --git a/homeassistant/components/renault/translations/el.json b/homeassistant/components/renault/translations/el.json index 23a73311f71034..e7e994b2a5607e 100644 --- a/homeassistant/components/renault/translations/el.json +++ b/homeassistant/components/renault/translations/el.json @@ -11,11 +11,16 @@ "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd Kamereon" }, "reauth_confirm": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username}" }, "user": { "data": { - "locale": "\u03a4\u03bf\u03c0\u03b9\u03ba\u03ae \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1" + "locale": "\u03a4\u03bf\u03c0\u03b9\u03ba\u03ae \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "Email" }, "title": "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03b7\u03c1\u03af\u03c9\u03bd Renault" } diff --git a/homeassistant/components/ridwell/translations/el.json b/homeassistant/components/ridwell/translations/el.json index 50eb1dfd177330..54e03b32b8acb8 100644 --- a/homeassistant/components/ridwell/translations/el.json +++ b/homeassistant/components/ridwell/translations/el.json @@ -2,10 +2,14 @@ "config": { "step": { "reauth_confirm": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username}:" }, "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2:" diff --git a/homeassistant/components/ring/translations/el.json b/homeassistant/components/ring/translations/el.json index 4ac4f3e8445a9b..0c83d2de63754e 100644 --- a/homeassistant/components/ring/translations/el.json +++ b/homeassistant/components/ring/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc Ring" diff --git a/homeassistant/components/rituals_perfume_genie/translations/el.json b/homeassistant/components/rituals_perfume_genie/translations/el.json index 2efdf57dbcb4a9..490ed36838d54b 100644 --- a/homeassistant/components/rituals_perfume_genie/translations/el.json +++ b/homeassistant/components/rituals_perfume_genie/translations/el.json @@ -2,6 +2,10 @@ "config": { "step": { "user": { + "data": { + "email": "Email", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 Rituals" } } diff --git a/homeassistant/components/roomba/translations/el.json b/homeassistant/components/roomba/translations/el.json index cedcd1691cf81b..fe1e0e9a73d40b 100644 --- a/homeassistant/components/roomba/translations/el.json +++ b/homeassistant/components/roomba/translations/el.json @@ -15,6 +15,9 @@ "title": "\u0391\u03bd\u03ac\u03ba\u03c4\u03b7\u03c3\u03b7 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd" }, "link_manual": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b4\u03b5\u03bd \u03bc\u03c0\u03cc\u03c1\u03b5\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b1\u03ba\u03c4\u03b7\u03b8\u03b5\u03af \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae. \u0391\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03b2\u03ae\u03bc\u03b1\u03c4\u03b1 \u03c0\u03bf\u03c5 \u03c0\u03b5\u03c1\u03b9\u03b3\u03c1\u03ac\u03c6\u03bf\u03bd\u03c4\u03b1\u03b9 \u03c3\u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7: {auth_help_url}", "title": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, @@ -31,7 +34,8 @@ "blid": "BLID", "continuous": "\u03a3\u03c5\u03bd\u03b5\u03c7\u03ae\u03c2", "delay": "\u039a\u03b1\u03b8\u03c5\u03c3\u03c4\u03ad\u03c1\u03b7\u03c3\u03b7", - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 Roomba \u03ae Braava.", "title": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" diff --git a/homeassistant/components/sense/translations/el.json b/homeassistant/components/sense/translations/el.json index 5cec565d287ee0..e8d227cdedcb53 100644 --- a/homeassistant/components/sense/translations/el.json +++ b/homeassistant/components/sense/translations/el.json @@ -3,6 +3,8 @@ "step": { "user": { "data": { + "email": "Email", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "timeout": "\u03a7\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03cc\u03c1\u03b9\u03bf" }, "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf Sense Energy Monitor" diff --git a/homeassistant/components/senseme/translations/lv.json b/homeassistant/components/senseme/translations/lv.json new file mode 100644 index 00000000000000..35d9add569f700 --- /dev/null +++ b/homeassistant/components/senseme/translations/lv.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "device": "Ier\u012bce" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sia/translations/el.json b/homeassistant/components/sia/translations/el.json index 06c6565c719ff5..6a6f656db76e37 100644 --- a/homeassistant/components/sia/translations/el.json +++ b/homeassistant/components/sia/translations/el.json @@ -16,6 +16,7 @@ "additional_account": "\u03a0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03b9 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03af", "encryption_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03ba\u03c1\u03c5\u03c0\u03c4\u03bf\u03b3\u03c1\u03ac\u03c6\u03b7\u03c3\u03b7\u03c2", "ping_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 ping (\u03bb\u03b5\u03c0\u03c4\u03ac)", + "port": "\u0398\u03cd\u03c1\u03b1", "protocol": "\u03a0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf", "zones": "\u0391\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03b6\u03c9\u03bd\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc" }, diff --git a/homeassistant/components/simplisafe/translations/el.json b/homeassistant/components/simplisafe/translations/el.json index 3ba3fdb4330dac..c749d7f7def660 100644 --- a/homeassistant/components/simplisafe/translations/el.json +++ b/homeassistant/components/simplisafe/translations/el.json @@ -26,7 +26,9 @@ "user": { "data": { "auth_code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2", - "code": "\u039a\u03ce\u03b4\u03b9\u03ba\u03b1\u03c2 (\u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03c3\u03c4\u03bf UI \u03c4\u03bf\u03c5 Home Assistant)" + "code": "\u039a\u03ce\u03b4\u03b9\u03ba\u03b1\u03c2 (\u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03c3\u03c4\u03bf UI \u03c4\u03bf\u03c5 Home Assistant)", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "Email" }, "description": "\u03a4\u03bf SimpliSafe \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03c4\u03bf Home Assistant \u03bc\u03ad\u03c3\u03c9 \u03c4\u03b7\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2 SimpliSafe web. \u039b\u03cc\u03b3\u03c9 \u03c4\u03b5\u03c7\u03bd\u03b9\u03ba\u03ce\u03bd \u03c0\u03b5\u03c1\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03ce\u03bd, \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03ad\u03bd\u03b1 \u03c7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03bf \u03b2\u03ae\u03bc\u03b1 \u03c3\u03c4\u03bf \u03c4\u03ad\u03bb\u03bf\u03c2 \u03b1\u03c5\u03c4\u03ae\u03c2 \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03b9\u03ba\u03b1\u03c3\u03af\u03b1\u03c2- \u03b2\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03ad\u03c7\u03b5\u03c4\u03b5 \u03b4\u03b9\u03b1\u03b2\u03ac\u03c3\u03b5\u03b9 \u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]({docs_url}) \u03c0\u03c1\u03b9\u03bd \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5.\n\n1. \u039a\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf [\u03b5\u03b4\u03ce]({url}) \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03bd\u03bf\u03af\u03be\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae SimpliSafe web \u03ba\u03b1\u03b9 \u03bd\u03b1 \u03b5\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2.\n\n2. \u038c\u03c4\u03b1\u03bd \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03c9\u03b8\u03b5\u03af \u03b7 \u03b4\u03b9\u03b1\u03b4\u03b9\u03ba\u03b1\u03c3\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2, \u03b5\u03c0\u03b9\u03c3\u03c4\u03c1\u03ad\u03c8\u03c4\u03b5 \u03b5\u03b4\u03ce \u03ba\u03b1\u03b9 \u03b5\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2.", "title": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03b1\u03c2" diff --git a/homeassistant/components/sma/translations/el.json b/homeassistant/components/sma/translations/el.json index 742ad113bb7297..929c1cdde065ee 100644 --- a/homeassistant/components/sma/translations/el.json +++ b/homeassistant/components/sma/translations/el.json @@ -7,7 +7,8 @@ "user": { "data": { "group": "\u039f\u03bc\u03ac\u03b4\u03b1", - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 SMA.", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 SMA Solar" diff --git a/homeassistant/components/smarthab/translations/el.json b/homeassistant/components/smarthab/translations/el.json index 143386f4703a89..ef7089357ab47e 100644 --- a/homeassistant/components/smarthab/translations/el.json +++ b/homeassistant/components/smarthab/translations/el.json @@ -2,6 +2,10 @@ "config": { "step": { "user": { + "data": { + "email": "Email", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u0393\u03b9\u03b1 \u03c4\u03b5\u03c7\u03bd\u03b9\u03ba\u03bf\u03cd\u03c2 \u03bb\u03cc\u03b3\u03bf\u03c5\u03c2, \u03b2\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03b5\u03cd\u03bf\u03bd\u03c4\u03b1 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03b5\u03b9\u03b4\u03b9\u03ba\u03ac \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Home Assistant. \u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae SmartHab.", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 SmartHab" } diff --git a/homeassistant/components/smarttub/translations/el.json b/homeassistant/components/smarttub/translations/el.json index bf55da881cecbd..667512e722b7ab 100644 --- a/homeassistant/components/smarttub/translations/el.json +++ b/homeassistant/components/smarttub/translations/el.json @@ -5,6 +5,10 @@ "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 SmartTub \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2" }, "user": { + "data": { + "email": "Email", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 email \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf SmartTub \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7" } diff --git a/homeassistant/components/solax/translations/el.json b/homeassistant/components/solax/translations/el.json new file mode 100644 index 00000000000000..a3ea32fadc34c1 --- /dev/null +++ b/homeassistant/components/solax/translations/el.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/soma/translations/el.json b/homeassistant/components/soma/translations/el.json index 35980b52f406a0..61fd165644201e 100644 --- a/homeassistant/components/soma/translations/el.json +++ b/homeassistant/components/soma/translations/el.json @@ -6,6 +6,9 @@ }, "step": { "user": { + "data": { + "port": "\u0398\u03cd\u03c1\u03b1" + }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 SOMA Connect.", "title": "SOMA Connect" } diff --git a/homeassistant/components/somfy_mylink/translations/el.json b/homeassistant/components/somfy_mylink/translations/el.json index cb96280313692d..e2fa45d91904e8 100644 --- a/homeassistant/components/somfy_mylink/translations/el.json +++ b/homeassistant/components/somfy_mylink/translations/el.json @@ -4,6 +4,7 @@ "step": { "user": { "data": { + "port": "\u0398\u03cd\u03c1\u03b1", "system_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c3\u03c5\u03c3\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2" }, "description": "\u03a4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c3\u03c5\u03c3\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2 \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03bb\u03b7\u03c6\u03b8\u03b5\u03af \u03c3\u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae MyLink \u03c3\u03c4\u03b7\u03bd \u03b5\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u0395\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03ad\u03b3\u03bf\u03bd\u03c4\u03b1\u03c2 \u03bf\u03c0\u03bf\u03b9\u03b1\u03b4\u03ae\u03c0\u03bf\u03c4\u03b5 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03c0\u03bf\u03c5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 Cloud." diff --git a/homeassistant/components/squeezebox/translations/el.json b/homeassistant/components/squeezebox/translations/el.json index 7e626db563b8e1..9608dfe0cc1cc7 100644 --- a/homeassistant/components/squeezebox/translations/el.json +++ b/homeassistant/components/squeezebox/translations/el.json @@ -10,7 +10,8 @@ "step": { "edit": { "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" } }, "user": { diff --git a/homeassistant/components/srp_energy/translations/el.json b/homeassistant/components/srp_energy/translations/el.json index 7f7e163cfbbf70..bee37751f76005 100644 --- a/homeassistant/components/srp_energy/translations/el.json +++ b/homeassistant/components/srp_energy/translations/el.json @@ -8,6 +8,7 @@ "data": { "id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd", "is_tou": "\u0395\u03af\u03bd\u03b1\u03b9 \u03bf \u03c7\u03c1\u03cc\u03bd\u03bf\u03c2 \u03c7\u03c1\u03ae\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03c3\u03c7\u03b5\u03b4\u03af\u03bf\u03c5", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } diff --git a/homeassistant/components/starline/translations/el.json b/homeassistant/components/starline/translations/el.json index deb451383f6788..254bd2db7754f5 100644 --- a/homeassistant/components/starline/translations/el.json +++ b/homeassistant/components/starline/translations/el.json @@ -30,6 +30,7 @@ }, "auth_user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "Email \u03ba\u03b1\u03b9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd StarLine", diff --git a/homeassistant/components/steamist/translations/el.json b/homeassistant/components/steamist/translations/el.json index 1df0b2de9c3d2f..749746bd52a8e0 100644 --- a/homeassistant/components/steamist/translations/el.json +++ b/homeassistant/components/steamist/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "flow_title": "{name} ({ipaddress})", "step": { "discovery_confirm": { diff --git a/homeassistant/components/surepetcare/translations/el.json b/homeassistant/components/surepetcare/translations/el.json index 18c2f0869bd1c2..bab52704f790ea 100644 --- a/homeassistant/components/surepetcare/translations/el.json +++ b/homeassistant/components/surepetcare/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } diff --git a/homeassistant/components/switchbot/translations/el.json b/homeassistant/components/switchbot/translations/el.json index 15d2736c2691f8..b24b965d00471a 100644 --- a/homeassistant/components/switchbot/translations/el.json +++ b/homeassistant/components/switchbot/translations/el.json @@ -9,7 +9,8 @@ "user": { "data": { "mac": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 MAC \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", - "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 Switchbot" } diff --git a/homeassistant/components/synology_dsm/translations/el.json b/homeassistant/components/synology_dsm/translations/el.json index a675c021cd32ec..abc00e150fbf1a 100644 --- a/homeassistant/components/synology_dsm/translations/el.json +++ b/homeassistant/components/synology_dsm/translations/el.json @@ -16,22 +16,32 @@ "title": "Synology DSM: \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b4\u03cd\u03bf \u03b2\u03b7\u03bc\u03ac\u03c4\u03c9\u03bd" }, "link": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" + }, "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ({host});", "title": "Synology DSM" }, "reauth": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u0391\u03b9\u03c4\u03af\u03b1: {details}", "title": "Synology DSM \u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, "reauth_confirm": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "Synology DSM \u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, "user": { "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" }, "title": "Synology DSM" } diff --git a/homeassistant/components/system_bridge/translations/el.json b/homeassistant/components/system_bridge/translations/el.json index 274b4b7d11a020..555cbf3406db4c 100644 --- a/homeassistant/components/system_bridge/translations/el.json +++ b/homeassistant/components/system_bridge/translations/el.json @@ -7,7 +7,8 @@ }, "user": { "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c4\u03b7\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2." } diff --git a/homeassistant/components/tado/translations/el.json b/homeassistant/components/tado/translations/el.json index 03e8b3f1513635..9d5c561fb7ac58 100644 --- a/homeassistant/components/tado/translations/el.json +++ b/homeassistant/components/tado/translations/el.json @@ -5,6 +5,9 @@ }, "step": { "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 Tado" } } diff --git a/homeassistant/components/tile/translations/el.json b/homeassistant/components/tile/translations/el.json index 35f0bd1867ed8d..1e427f465db8f5 100644 --- a/homeassistant/components/tile/translations/el.json +++ b/homeassistant/components/tile/translations/el.json @@ -1,7 +1,16 @@ { "config": { "step": { + "reauth_confirm": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + } + }, "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "Email" + }, "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Tile" } } diff --git a/homeassistant/components/totalconnect/translations/el.json b/homeassistant/components/totalconnect/translations/el.json index b61c256ef18547..180acc83776898 100644 --- a/homeassistant/components/totalconnect/translations/el.json +++ b/homeassistant/components/totalconnect/translations/el.json @@ -16,6 +16,9 @@ "description": "\u03a4\u03bf Total Connect \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2" }, "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "title": "Total Connect" } } diff --git a/homeassistant/components/tractive/translations/el.json b/homeassistant/components/tractive/translations/el.json index 15ba157f55ca23..0e2e754f90c225 100644 --- a/homeassistant/components/tractive/translations/el.json +++ b/homeassistant/components/tractive/translations/el.json @@ -6,7 +6,8 @@ "step": { "user": { "data": { - "email": "\u03b7\u03bb\u03b5\u03ba\u03c4\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03c4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b5\u03af\u03bf" + "email": "\u03b7\u03bb\u03b5\u03ba\u03c4\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03c4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b5\u03af\u03bf", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" } } } diff --git a/homeassistant/components/transmission/translations/el.json b/homeassistant/components/transmission/translations/el.json index fc1a4535178308..498610b5896b15 100644 --- a/homeassistant/components/transmission/translations/el.json +++ b/homeassistant/components/transmission/translations/el.json @@ -6,6 +6,8 @@ "step": { "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03c1\u03bf\u03b3\u03c1\u03ac\u03bc\u03bc\u03b1\u03c4\u03bf\u03c2-\u03c0\u03b5\u03bb\u03ac\u03c4\u03b7 \u03bc\u03b5\u03c4\u03ac\u03b4\u03bf\u03c3\u03b7\u03c2" diff --git a/homeassistant/components/tuya/translations/el.json b/homeassistant/components/tuya/translations/el.json index e69a3c00eedd01..a77dd8851fe2b1 100644 --- a/homeassistant/components/tuya/translations/el.json +++ b/homeassistant/components/tuya/translations/el.json @@ -21,6 +21,7 @@ "access_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 Tuya IoT", "access_secret": "\u039c\u03c5\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 Tuya IoT", "country_code": "\u03a7\u03ce\u03c1\u03b1", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "platform": "\u0397 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u03c3\u03c4\u03b7\u03bd \u03bf\u03c0\u03bf\u03af\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03b3\u03b3\u03b5\u03b3\u03c1\u03b1\u03bc\u03bc\u03ad\u03bd\u03bf\u03c2 \u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03c3\u03b1\u03c2", "region": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae", "username": "\u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2" diff --git a/homeassistant/components/tuya/translations/select.el.json b/homeassistant/components/tuya/translations/select.el.json index 654095e2cfda34..1cb4117e79968d 100644 --- a/homeassistant/components/tuya/translations/select.el.json +++ b/homeassistant/components/tuya/translations/select.el.json @@ -25,6 +25,9 @@ "back": "\u03a0\u03af\u03c3\u03c9", "forward": "\u0395\u03bc\u03c0\u03c1\u03cc\u03c2" }, + "tuya__decibel_sensitivity": { + "0": "\u03a7\u03b1\u03bc\u03b7\u03bb\u03ae \u03b5\u03c5\u03b1\u03b9\u03c3\u03b8\u03b7\u03c3\u03af\u03b1" + }, "tuya__fan_angle": { "30": "30\u00b0", "60": "60\u00b0", diff --git a/homeassistant/components/tuya/translations/select.lv.json b/homeassistant/components/tuya/translations/select.lv.json new file mode 100644 index 00000000000000..4c86485bcc2897 --- /dev/null +++ b/homeassistant/components/tuya/translations/select.lv.json @@ -0,0 +1,22 @@ +{ + "state": { + "tuya__countdown": { + "1h": "1 stunda", + "2h": "2 stundas", + "3h": "3 stundas", + "4h": "4 stundas", + "5h": "5 stundas", + "6h": "6 stundas", + "cancel": "Atcelt" + }, + "tuya__fan_angle": { + "30": "30\u00b0", + "60": "60\u00b0", + "90": "90\u00b0" + }, + "tuya__vacuum_mode": { + "wall_follow": "Sekot sienai", + "zone": "Zona" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/translations/el.json b/homeassistant/components/unifi/translations/el.json index a8efc9d1e953b4..5087afff1bd717 100644 --- a/homeassistant/components/unifi/translations/el.json +++ b/homeassistant/components/unifi/translations/el.json @@ -12,6 +12,8 @@ "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1", "site": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, diff --git a/homeassistant/components/unifiprotect/translations/el.json b/homeassistant/components/unifiprotect/translations/el.json index a6782ec138ac14..e332f005aa5a61 100644 --- a/homeassistant/components/unifiprotect/translations/el.json +++ b/homeassistant/components/unifiprotect/translations/el.json @@ -10,6 +10,7 @@ "step": { "discovery_confirm": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ( {ip_address});", @@ -18,6 +19,8 @@ "reauth_confirm": { "data": { "host": "IP/Host \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae UniFi Protect", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "UniFi Protect Reauth" @@ -25,6 +28,8 @@ "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03c4\u03bf\u03c0\u03b9\u03ba\u03cc \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03c0\u03bf\u03c5 \u03ad\u03c7\u03b5\u03b9 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03b7\u03b8\u03b5\u03af \u03c3\u03c4\u03b7\u03bd \u039a\u03bf\u03bd\u03c3\u03cc\u03bb\u03b1 UniFi OS \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5. \u039f\u03b9 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b5\u03c2 \u03c4\u03bf\u03c5 Ubiquiti Cloud \u03b4\u03b5\u03bd \u03b8\u03b1 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03bf\u03c5\u03bd. \u0393\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2: {local_user_documentation_url}", diff --git a/homeassistant/components/upcloud/translations/el.json b/homeassistant/components/upcloud/translations/el.json index ed87fbbdf78186..c99e65e247cb87 100644 --- a/homeassistant/components/upcloud/translations/el.json +++ b/homeassistant/components/upcloud/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } diff --git a/homeassistant/components/vallox/translations/lv.json b/homeassistant/components/vallox/translations/lv.json new file mode 100644 index 00000000000000..cee9047f155ece --- /dev/null +++ b/homeassistant/components/vallox/translations/lv.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "title": "Vallox" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/venstar/translations/el.json b/homeassistant/components/venstar/translations/el.json index 5105f19e2bc0fb..594bd343ae1292 100644 --- a/homeassistant/components/venstar/translations/el.json +++ b/homeassistant/components/venstar/translations/el.json @@ -4,6 +4,7 @@ "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03b8\u03b5\u03c1\u03bc\u03bf\u03c3\u03c4\u03ac\u03c4\u03b7 Venstar" diff --git a/homeassistant/components/verisure/translations/el.json b/homeassistant/components/verisure/translations/el.json index 9f3915dd39bba2..e8036c15205e5e 100644 --- a/homeassistant/components/verisure/translations/el.json +++ b/homeassistant/components/verisure/translations/el.json @@ -16,7 +16,9 @@ }, "user": { "data": { - "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03bc\u03b5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 Verisure My Pages." + "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03bc\u03b5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 Verisure My Pages.", + "email": "Email", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" } } } diff --git a/homeassistant/components/version/translations/lv.json b/homeassistant/components/version/translations/lv.json new file mode 100644 index 00000000000000..da8048f13fb110 --- /dev/null +++ b/homeassistant/components/version/translations/lv.json @@ -0,0 +1,16 @@ +{ + "config": { + "step": { + "user": { + "data": { + "version_source": "Versijas avots" + } + }, + "version_source": { + "data": { + "beta": "Iek\u013caut beta versijas" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vesync/translations/el.json b/homeassistant/components/vesync/translations/el.json index 22e45ca54fa510..e055c1c1eb99ff 100644 --- a/homeassistant/components/vesync/translations/el.json +++ b/homeassistant/components/vesync/translations/el.json @@ -2,6 +2,10 @@ "config": { "step": { "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "Email" + }, "title": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" } } diff --git a/homeassistant/components/vicare/translations/el.json b/homeassistant/components/vicare/translations/el.json index 7d43245e6176d2..a5facf43b79789 100644 --- a/homeassistant/components/vicare/translations/el.json +++ b/homeassistant/components/vicare/translations/el.json @@ -6,7 +6,9 @@ "data": { "heating_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03b8\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1", - "scan_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7\u03c2 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "scan_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7\u03c2 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)", + "username": "Email" }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 ViCare. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://developer.viessmann.com", "title": "{name}" diff --git a/homeassistant/components/vlc_telnet/translations/el.json b/homeassistant/components/vlc_telnet/translations/el.json index a5bd6ebdebecdb..b40afa3ffcdd3f 100644 --- a/homeassistant/components/vlc_telnet/translations/el.json +++ b/homeassistant/components/vlc_telnet/translations/el.json @@ -6,12 +6,17 @@ "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03bc\u03b5 \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf {addon};" }, "reauth_confirm": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c3\u03c9\u03c3\u03c4\u03cc \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae: {host}" }, "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", - "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" } } } diff --git a/homeassistant/components/wallbox/translations/el.json b/homeassistant/components/wallbox/translations/el.json index 376a62b9ff094e..8680df5adc5108 100644 --- a/homeassistant/components/wallbox/translations/el.json +++ b/homeassistant/components/wallbox/translations/el.json @@ -6,11 +6,13 @@ "step": { "reauth_confirm": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } }, "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "station": "\u03a3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd" } } diff --git a/homeassistant/components/watttime/translations/el.json b/homeassistant/components/watttime/translations/el.json index e1fd6af43bc4cc..85b0863bd7e74d 100644 --- a/homeassistant/components/watttime/translations/el.json +++ b/homeassistant/components/watttime/translations/el.json @@ -15,10 +15,14 @@ "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03b3\u03b9\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7:" }, "reauth_confirm": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username}:" }, "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2:" diff --git a/homeassistant/components/webostv/translations/lv.json b/homeassistant/components/webostv/translations/lv.json new file mode 100644 index 00000000000000..676af9e30aaf75 --- /dev/null +++ b/homeassistant/components/webostv/translations/lv.json @@ -0,0 +1,11 @@ +{ + "options": { + "step": { + "init": { + "data": { + "sources": "Avotu saraksts" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/whirlpool/translations/el.json b/homeassistant/components/whirlpool/translations/el.json index 18c2f0869bd1c2..bab52704f790ea 100644 --- a/homeassistant/components/whirlpool/translations/el.json +++ b/homeassistant/components/whirlpool/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } diff --git a/homeassistant/components/wiffi/translations/lv.json b/homeassistant/components/wiffi/translations/lv.json new file mode 100644 index 00000000000000..b6f2cec8396fa3 --- /dev/null +++ b/homeassistant/components/wiffi/translations/lv.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Servera ports jau ir konfigur\u0113ts." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/ca.json b/homeassistant/components/wiz/translations/ca.json index 2c244b1bfc07f3..60ca2a8a884add 100644 --- a/homeassistant/components/wiz/translations/ca.json +++ b/homeassistant/components/wiz/translations/ca.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "El dispositiu ja est\u00e0 configurat", + "cannot_connect": "Ha fallat la connexi\u00f3", "no_devices_found": "No s'han trobat dispositius a la xarxa" }, "error": { diff --git a/homeassistant/components/wiz/translations/de.json b/homeassistant/components/wiz/translations/de.json index ad8951c92c5156..73b933fefcf908 100644 --- a/homeassistant/components/wiz/translations/de.json +++ b/homeassistant/components/wiz/translations/de.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden" }, "error": { diff --git a/homeassistant/components/wiz/translations/el.json b/homeassistant/components/wiz/translations/el.json index 3741ef6a8f36dd..a251696484044c 100644 --- a/homeassistant/components/wiz/translations/el.json +++ b/homeassistant/components/wiz/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "error": { "bulb_time_out": "\u0394\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf\u03bd \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b1. \u038a\u03c3\u03c9\u03c2 \u03bf \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03ba\u03c4\u03cc\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03ae \u03ad\u03c7\u03b5\u03b9 \u03b5\u03b9\u03c3\u03b1\u03c7\u03b8\u03b5\u03af \u03bb\u03ac\u03b8\u03bf\u03c2 IP/host. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03bd\u03ac\u03c8\u03c4\u03b5 \u03c4\u03bf \u03c6\u03c9\u03c2 \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac!", "no_ip": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP.", diff --git a/homeassistant/components/wiz/translations/en.json b/homeassistant/components/wiz/translations/en.json index 97bb7fc25ba968..a612ea165a422d 100644 --- a/homeassistant/components/wiz/translations/en.json +++ b/homeassistant/components/wiz/translations/en.json @@ -14,6 +14,9 @@ }, "flow_title": "{name} ({host})", "step": { + "confirm": { + "description": "Do you want to start set up?" + }, "discovery_confirm": { "description": "Do you want to setup {name} ({host})?" }, @@ -24,7 +27,8 @@ }, "user": { "data": { - "host": "IP Address" + "host": "IP Address", + "name": "Name" }, "description": "If you leave the IP Address empty, discovery will be used to find devices." } diff --git a/homeassistant/components/wiz/translations/et.json b/homeassistant/components/wiz/translations/et.json index f398c1a49bdd65..9cb84a0e7bd747 100644 --- a/homeassistant/components/wiz/translations/et.json +++ b/homeassistant/components/wiz/translations/et.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "cannot_connect": "\u00dchendamine nurjus", "no_devices_found": "V\u00f5rgust seadmeid ei leitud" }, "error": { diff --git a/homeassistant/components/wiz/translations/hu.json b/homeassistant/components/wiz/translations/hu.json index d26658e666ac84..7c99571a9ae147 100644 --- a/homeassistant/components/wiz/translations/hu.json +++ b/homeassistant/components/wiz/translations/hu.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton" }, "error": { diff --git a/homeassistant/components/wiz/translations/it.json b/homeassistant/components/wiz/translations/it.json index 3b0781bbc731eb..ebd093c22f7e95 100644 --- a/homeassistant/components/wiz/translations/it.json +++ b/homeassistant/components/wiz/translations/it.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "cannot_connect": "Impossibile connettersi", "no_devices_found": "Nessun dispositivo trovato sulla rete" }, "error": { diff --git a/homeassistant/components/wiz/translations/lv.json b/homeassistant/components/wiz/translations/lv.json new file mode 100644 index 00000000000000..dcf6c75a65372e --- /dev/null +++ b/homeassistant/components/wiz/translations/lv.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "pick_device": { + "data": { + "device": "Ier\u012bce" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/no.json b/homeassistant/components/wiz/translations/no.json index bf6563a3f48366..7f2090b5b71fe2 100644 --- a/homeassistant/components/wiz/translations/no.json +++ b/homeassistant/components/wiz/translations/no.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Enheten er allerede konfigurert", + "cannot_connect": "Tilkobling mislyktes", "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket" }, "error": { diff --git a/homeassistant/components/wiz/translations/pl.json b/homeassistant/components/wiz/translations/pl.json index 60de0710e40af4..30d1455c470a2e 100644 --- a/homeassistant/components/wiz/translations/pl.json +++ b/homeassistant/components/wiz/translations/pl.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "no_devices_found": "Nie znaleziono urz\u0105dze\u0144 w sieci" }, "error": { @@ -26,7 +27,7 @@ }, "user": { "data": { - "host": "[%key::common::config_flow::data::ip%]", + "host": "Adres IP", "name": "Nazwa" }, "description": "Je\u015bli nie podasz adresu IP, zostanie u\u017cyte wykrywanie do odnalezienia urz\u0105dze\u0144." diff --git a/homeassistant/components/wiz/translations/pt-BR.json b/homeassistant/components/wiz/translations/pt-BR.json index 7d5485e354ee8c..e0a95b88daf500 100644 --- a/homeassistant/components/wiz/translations/pt-BR.json +++ b/homeassistant/components/wiz/translations/pt-BR.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falhou ao conectar", "no_devices_found": "Nenhum dispositivo encontrado na rede" }, "error": { diff --git a/homeassistant/components/wiz/translations/ru.json b/homeassistant/components/wiz/translations/ru.json index bef8eae0d3274a..dd422f29fea023 100644 --- a/homeassistant/components/wiz/translations/ru.json +++ b/homeassistant/components/wiz/translations/ru.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0441\u0435\u0442\u0438." }, "error": { diff --git a/homeassistant/components/wiz/translations/zh-Hant.json b/homeassistant/components/wiz/translations/zh-Hant.json index ce08826710677d..b677427996fbef 100644 --- a/homeassistant/components/wiz/translations/zh-Hant.json +++ b/homeassistant/components/wiz/translations/zh-Hant.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e" }, "error": { diff --git a/homeassistant/components/yale_smart_alarm/translations/el.json b/homeassistant/components/yale_smart_alarm/translations/el.json index 2af575a32a1f7b..463745b6bbf44a 100644 --- a/homeassistant/components/yale_smart_alarm/translations/el.json +++ b/homeassistant/components/yale_smart_alarm/translations/el.json @@ -1,16 +1,21 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "reauth_confirm": { "data": { "area_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae\u03c2", - "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" } }, "user": { "data": { "area_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae\u03c2", - "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" } } } diff --git a/homeassistant/components/yamaha_musiccast/translations/select.lv.json b/homeassistant/components/yamaha_musiccast/translations/select.lv.json new file mode 100644 index 00000000000000..17d5aa4c832a1d --- /dev/null +++ b/homeassistant/components/yamaha_musiccast/translations/select.lv.json @@ -0,0 +1,13 @@ +{ + "state": { + "yamaha_musiccast__zone_link_control": { + "speed": "\u0100trums" + }, + "yamaha_musiccast__zone_sleep": { + "120 min": "120 min\u016btes", + "30 min": "30 min\u016btes", + "60 min": "60 min\u016btes", + "90 min": "90 min\u016btes" + } + } +} \ No newline at end of file From 99568b133f3aef057cc03fbfa7b4538a47f32ce7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 15 Feb 2022 18:25:43 -0600 Subject: [PATCH 0682/1098] Switch flux_led to use integration discovery (#66574) --- homeassistant/components/flux_led/config_flow.py | 4 ++-- homeassistant/components/flux_led/discovery.py | 2 +- tests/components/flux_led/test_config_flow.py | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/flux_led/config_flow.py b/homeassistant/components/flux_led/config_flow.py index 9b1cda2dea1253..ee8da5bd66a75e 100644 --- a/homeassistant/components/flux_led/config_flow.py +++ b/homeassistant/components/flux_led/config_flow.py @@ -81,10 +81,10 @@ async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowRes ) return await self._async_handle_discovery() - async def async_step_discovery( + async def async_step_integration_discovery( self, discovery_info: DiscoveryInfoType ) -> FlowResult: - """Handle discovery.""" + """Handle integration discovery.""" self._discovered_device = cast(FluxLEDDiscovery, discovery_info) return await self._async_handle_discovery() diff --git a/homeassistant/components/flux_led/discovery.py b/homeassistant/components/flux_led/discovery.py index c30418fa8e4747..cd0d9424ead6b9 100644 --- a/homeassistant/components/flux_led/discovery.py +++ b/homeassistant/components/flux_led/discovery.py @@ -213,7 +213,7 @@ def async_trigger_discovery( hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data={**device}, ) ) diff --git a/tests/components/flux_led/test_config_flow.py b/tests/components/flux_led/test_config_flow.py index 0ff8180a761ca2..b858f6d995a85a 100644 --- a/tests/components/flux_led/test_config_flow.py +++ b/tests/components/flux_led/test_config_flow.py @@ -382,7 +382,7 @@ async def test_discovered_by_discovery_and_dhcp(hass): with _patch_discovery(), _patch_wifibulb(): result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data=FLUX_DISCOVERY, ) await hass.async_block_till_done() @@ -420,7 +420,7 @@ async def test_discovered_by_discovery(hass): with _patch_discovery(), _patch_wifibulb(): result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data=FLUX_DISCOVERY, ) await hass.async_block_till_done() @@ -567,7 +567,7 @@ async def test_discovered_by_dhcp_no_udp_response_or_tcp_response(hass): "source, data", [ (config_entries.SOURCE_DHCP, DHCP_DISCOVERY), - (config_entries.SOURCE_DISCOVERY, FLUX_DISCOVERY), + (config_entries.SOURCE_INTEGRATION_DISCOVERY, FLUX_DISCOVERY), ], ) async def test_discovered_by_dhcp_or_discovery_adds_missing_unique_id( @@ -593,7 +593,7 @@ async def test_discovered_by_dhcp_or_discovery_adds_missing_unique_id( "source, data", [ (config_entries.SOURCE_DHCP, DHCP_DISCOVERY), - (config_entries.SOURCE_DISCOVERY, FLUX_DISCOVERY), + (config_entries.SOURCE_INTEGRATION_DISCOVERY, FLUX_DISCOVERY), ], ) async def test_discovered_by_dhcp_or_discovery_mac_address_mismatch_host_already_configured( From 491f8d0f0b82f16d77b3998ccb29eccd31505fbd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 15 Feb 2022 18:26:17 -0600 Subject: [PATCH 0683/1098] Enable dhcp flows for steamist registered devices (#66593) --- homeassistant/components/steamist/manifest.json | 1 + homeassistant/generated/dhcp.py | 1 + 2 files changed, 2 insertions(+) diff --git a/homeassistant/components/steamist/manifest.json b/homeassistant/components/steamist/manifest.json index 2856fe94240048..e3095ada47a332 100644 --- a/homeassistant/components/steamist/manifest.json +++ b/homeassistant/components/steamist/manifest.json @@ -8,6 +8,7 @@ "codeowners": ["@bdraco"], "iot_class": "local_polling", "dhcp": [ + {"registered_devices": true}, { "macaddress": "001E0C*", "hostname": "my[45]50*" diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index b876797ee40aa3..f78c8003457eba 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -87,6 +87,7 @@ {'domain': 'solaredge', 'hostname': 'target', 'macaddress': '002702*'}, {'domain': 'somfy_mylink', 'hostname': 'somfy_*', 'macaddress': 'B8B7F1*'}, {'domain': 'squeezebox', 'hostname': 'squeezebox*', 'macaddress': '000420*'}, + {'domain': 'steamist', 'registered_devices': True}, {'domain': 'steamist', 'hostname': 'my[45]50*', 'macaddress': '001E0C*'}, {'domain': 'tado', 'hostname': 'tado*'}, {'domain': 'tesla_wall_connector', From 7c508c2a44208b1ee03b663d257504e44019018d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 15 Feb 2022 18:30:52 -0600 Subject: [PATCH 0684/1098] Enable dhcp flows for hunterdouglas_powerview registered devices (#66587) --- homeassistant/components/hunterdouglas_powerview/manifest.json | 1 + homeassistant/generated/dhcp.py | 1 + 2 files changed, 2 insertions(+) diff --git a/homeassistant/components/hunterdouglas_powerview/manifest.json b/homeassistant/components/hunterdouglas_powerview/manifest.json index 29b260c2fa3da6..4e1ece9a3b008e 100644 --- a/homeassistant/components/hunterdouglas_powerview/manifest.json +++ b/homeassistant/components/hunterdouglas_powerview/manifest.json @@ -9,6 +9,7 @@ "models": ["PowerView"] }, "dhcp": [ + {"registered_devices": true}, { "hostname": "hunter*", "macaddress": "002674*" diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index f78c8003457eba..26b2aa61aeb9e4 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -41,6 +41,7 @@ {'domain': 'guardian', 'hostname': 'gvc*', 'macaddress': '30AEA4*'}, {'domain': 'guardian', 'hostname': 'gvc*', 'macaddress': 'B4E62D*'}, {'domain': 'guardian', 'hostname': 'guardian*', 'macaddress': '30AEA4*'}, + {'domain': 'hunterdouglas_powerview', 'registered_devices': True}, {'domain': 'hunterdouglas_powerview', 'hostname': 'hunter*', 'macaddress': '002674*'}, From 6b7eea5454f2a2fd5f5b6982abc7f0c3c81de9da Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 15 Feb 2022 18:45:41 -0600 Subject: [PATCH 0685/1098] Enable dhcp flows for senseme registered devices (#66590) --- homeassistant/components/senseme/manifest.json | 5 ++++- homeassistant/generated/dhcp.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/senseme/manifest.json b/homeassistant/components/senseme/manifest.json index 9e2a9363effb47..97a73434b266e9 100644 --- a/homeassistant/components/senseme/manifest.json +++ b/homeassistant/components/senseme/manifest.json @@ -9,7 +9,10 @@ "codeowners": [ "@mikelawrence", "@bdraco" ], - "dhcp": [{"macaddress":"20F85E*"}], + "dhcp": [ + {"registered_devices": true}, + {"macaddress":"20F85E*"} + ], "iot_class": "local_push", "loggers": ["aiosenseme"] } diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 26b2aa61aeb9e4..a92e9b4dd92d6e 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -77,6 +77,7 @@ {'domain': 'sense', 'hostname': 'sense-*', 'macaddress': '009D6B*'}, {'domain': 'sense', 'hostname': 'sense-*', 'macaddress': 'DCEFCA*'}, {'domain': 'sense', 'hostname': 'sense-*', 'macaddress': 'A4D578*'}, + {'domain': 'senseme', 'registered_devices': True}, {'domain': 'senseme', 'macaddress': '20F85E*'}, {'domain': 'sensibo', 'hostname': 'sensibo*'}, {'domain': 'simplisafe', 'hostname': 'simplisafe*', 'macaddress': '30AEA4*'}, From 6a690b41b132194d8eb8daaa00962b39d9a054cd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 15 Feb 2022 18:45:53 -0600 Subject: [PATCH 0686/1098] Enable dhcp flows for screenlogic registered devices (#66591) --- homeassistant/components/screenlogic/manifest.json | 1 + homeassistant/generated/dhcp.py | 1 + 2 files changed, 2 insertions(+) diff --git a/homeassistant/components/screenlogic/manifest.json b/homeassistant/components/screenlogic/manifest.json index 016ade188f4dd2..98129e24f0146e 100644 --- a/homeassistant/components/screenlogic/manifest.json +++ b/homeassistant/components/screenlogic/manifest.json @@ -6,6 +6,7 @@ "requirements": ["screenlogicpy==0.5.4"], "codeowners": ["@dieselrabbit", "@bdraco"], "dhcp": [ + {"registered_devices": true}, { "hostname": "pentair: *", "macaddress": "00C033*" diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index a92e9b4dd92d6e..1cf62e9375c39d 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -73,6 +73,7 @@ {'domain': 'samsungtv', 'macaddress': '606BBD*'}, {'domain': 'samsungtv', 'macaddress': 'F47B5E*'}, {'domain': 'samsungtv', 'macaddress': '4844F7*'}, + {'domain': 'screenlogic', 'registered_devices': True}, {'domain': 'screenlogic', 'hostname': 'pentair: *', 'macaddress': '00C033*'}, {'domain': 'sense', 'hostname': 'sense-*', 'macaddress': '009D6B*'}, {'domain': 'sense', 'hostname': 'sense-*', 'macaddress': 'DCEFCA*'}, From dad4cdb45dc44c3fd3101ebdd79bec0059ae761b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 15 Feb 2022 18:46:09 -0600 Subject: [PATCH 0687/1098] Enable dhcp flows for isy994 registered devices (#66588) --- homeassistant/components/isy994/manifest.json | 5 ++++- homeassistant/generated/dhcp.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/isy994/manifest.json b/homeassistant/components/isy994/manifest.json index 23a2f07b970425..fe0fa9720caf94 100644 --- a/homeassistant/components/isy994/manifest.json +++ b/homeassistant/components/isy994/manifest.json @@ -11,7 +11,10 @@ "deviceType": "urn:udi-com:device:X_Insteon_Lighting_Device:1" } ], - "dhcp": [{ "hostname": "isy*", "macaddress": "0021B9*" }], + "dhcp": [ + {"registered_devices": true}, + {"hostname": "isy*", "macaddress": "0021B9*"} + ], "iot_class": "local_push", "loggers": ["pyisy"] } diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 1cf62e9375c39d..ead49a36b6706f 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -45,6 +45,7 @@ {'domain': 'hunterdouglas_powerview', 'hostname': 'hunter*', 'macaddress': '002674*'}, + {'domain': 'isy994', 'registered_devices': True}, {'domain': 'isy994', 'hostname': 'isy*', 'macaddress': '0021B9*'}, {'domain': 'lyric', 'hostname': 'lyric-*', 'macaddress': '48A2E6*'}, {'domain': 'lyric', 'hostname': 'lyric-*', 'macaddress': 'B82CA0*'}, From 8f4ec89be6c2505d8a59eee44de335abe308ac9f Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Wed, 16 Feb 2022 02:26:13 +0100 Subject: [PATCH 0688/1098] Bump aiohue to version 4.1.2 (#66609) --- homeassistant/components/hue/manifest.json | 2 +- homeassistant/components/hue/scene.py | 8 +++- homeassistant/components/hue/switch.py | 10 ++++- .../components/hue/v2/binary_sensor.py | 12 +++--- homeassistant/components/hue/v2/entity.py | 19 +++++++-- homeassistant/components/hue/v2/group.py | 4 +- homeassistant/components/hue/v2/sensor.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/hue/conftest.py | 3 +- tests/components/hue/test_light_v2.py | 39 +++++++++++++++---- tests/components/hue/test_switch.py | 7 +++- 12 files changed, 80 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/hue/manifest.json b/homeassistant/components/hue/manifest.json index 231c00e3d24f24..9039018da6edcf 100644 --- a/homeassistant/components/hue/manifest.json +++ b/homeassistant/components/hue/manifest.json @@ -3,7 +3,7 @@ "name": "Philips Hue", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/hue", - "requirements": ["aiohue==4.0.1"], + "requirements": ["aiohue==4.1.2"], "ssdp": [ { "manufacturer": "Royal Philips Electronics", diff --git a/homeassistant/components/hue/scene.py b/homeassistant/components/hue/scene.py index 3d1967ecff31b1..8e894b4e2957ef 100644 --- a/homeassistant/components/hue/scene.py +++ b/homeassistant/components/hue/scene.py @@ -5,7 +5,11 @@ from aiohue.v2 import HueBridgeV2 from aiohue.v2.controllers.events import EventType -from aiohue.v2.controllers.scenes import Scene as HueScene, ScenesController +from aiohue.v2.controllers.scenes import ( + Scene as HueScene, + ScenePut as HueScenePut, + ScenesController, +) import voluptuous as vol from homeassistant.components.scene import ATTR_TRANSITION, Scene as SceneEntity @@ -131,7 +135,7 @@ async def async_activate(self, **kwargs: Any) -> None: await self.bridge.async_request_call( self.controller.update, self.resource.id, - HueScene(self.resource.id, speed=speed / 100), + HueScenePut(speed=speed / 100), ) await self.bridge.async_request_call( diff --git a/homeassistant/components/hue/switch.py b/homeassistant/components/hue/switch.py index 7f8e048d692343..7fb40cba38f555 100644 --- a/homeassistant/components/hue/switch.py +++ b/homeassistant/components/hue/switch.py @@ -5,8 +5,12 @@ from aiohue.v2 import HueBridgeV2 from aiohue.v2.controllers.events import EventType -from aiohue.v2.controllers.sensors import LightLevelController, MotionController -from aiohue.v2.models.resource import SensingService +from aiohue.v2.controllers.sensors import ( + LightLevel, + LightLevelController, + Motion, + MotionController, +) from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity from homeassistant.config_entries import ConfigEntry @@ -20,6 +24,8 @@ ControllerType = Union[LightLevelController, MotionController] +SensingService = Union[LightLevel, Motion] + async def async_setup_entry( hass: HomeAssistant, diff --git a/homeassistant/components/hue/v2/binary_sensor.py b/homeassistant/components/hue/v2/binary_sensor.py index 47617b45af654d..a7077ccf765ac4 100644 --- a/homeassistant/components/hue/v2/binary_sensor.py +++ b/homeassistant/components/hue/v2/binary_sensor.py @@ -4,13 +4,13 @@ from typing import Any, Union from aiohue.v2 import HueBridgeV2 -from aiohue.v2.controllers.config import EntertainmentConfigurationController -from aiohue.v2.controllers.events import EventType -from aiohue.v2.controllers.sensors import MotionController -from aiohue.v2.models.entertainment import ( +from aiohue.v2.controllers.config import ( EntertainmentConfiguration, - EntertainmentStatus, + EntertainmentConfigurationController, ) +from aiohue.v2.controllers.events import EventType +from aiohue.v2.controllers.sensors import MotionController +from aiohue.v2.models.entertainment_configuration import EntertainmentStatus from aiohue.v2.models.motion import Motion from homeassistant.components.binary_sensor import ( @@ -109,4 +109,4 @@ def is_on(self) -> bool | None: def name(self) -> str: """Return sensor name.""" type_title = self.resource.type.value.replace("_", " ").title() - return f"{self.resource.name}: {type_title}" + return f"{self.resource.metadata.name}: {type_title}" diff --git a/homeassistant/components/hue/v2/entity.py b/homeassistant/components/hue/v2/entity.py index c8c2f9e423bac8..721425606bcd29 100644 --- a/homeassistant/components/hue/v2/entity.py +++ b/homeassistant/components/hue/v2/entity.py @@ -1,11 +1,12 @@ """Generic Hue Entity Model.""" from __future__ import annotations +from typing import TYPE_CHECKING, Union + from aiohue.v2.controllers.base import BaseResourcesController from aiohue.v2.controllers.events import EventType -from aiohue.v2.models.clip import CLIPResource -from aiohue.v2.models.connectivity import ConnectivityServiceStatus from aiohue.v2.models.resource import ResourceTypes +from aiohue.v2.models.zigbee_connectivity import ConnectivityServiceStatus from homeassistant.core import callback from homeassistant.helpers.entity import DeviceInfo, Entity @@ -14,6 +15,16 @@ from ..bridge import HueBridge from ..const import CONF_IGNORE_AVAILABILITY, DOMAIN +if TYPE_CHECKING: + from aiohue.v2.models.device_power import DevicePower + from aiohue.v2.models.grouped_light import GroupedLight + from aiohue.v2.models.light import Light + from aiohue.v2.models.light_level import LightLevel + from aiohue.v2.models.motion import Motion + + HueResource = Union[Light, DevicePower, GroupedLight, LightLevel, Motion] + + RESOURCE_TYPE_NAMES = { # a simple mapping of hue resource type to Hass name ResourceTypes.LIGHT_LEVEL: "Illuminance", @@ -30,7 +41,7 @@ def __init__( self, bridge: HueBridge, controller: BaseResourcesController, - resource: CLIPResource, + resource: HueResource, ) -> None: """Initialize a generic Hue resource entity.""" self.bridge = bridge @@ -122,7 +133,7 @@ def on_update(self) -> None: # used in subclasses @callback - def _handle_event(self, event_type: EventType, resource: CLIPResource) -> None: + def _handle_event(self, event_type: EventType, resource: HueResource) -> None: """Handle status event for this resource (or it's parent).""" if event_type == EventType.RESOURCE_DELETED and resource.id == self.resource.id: self.logger.debug("Received delete for %s", self.entity_id) diff --git a/homeassistant/components/hue/v2/group.py b/homeassistant/components/hue/v2/group.py index 300f08727ba699..31c5a502853021 100644 --- a/homeassistant/components/hue/v2/group.py +++ b/homeassistant/components/hue/v2/group.py @@ -7,7 +7,7 @@ from aiohue.v2 import HueBridgeV2 from aiohue.v2.controllers.events import EventType from aiohue.v2.controllers.groups import GroupedLight, Room, Zone -from aiohue.v2.models.feature import DynamicsFeatureStatus +from aiohue.v2.models.feature import DynamicStatus from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -283,7 +283,7 @@ def _update_values(self) -> None: total_brightness += dimming.brightness if ( light.dynamics - and light.dynamics.status == DynamicsFeatureStatus.DYNAMIC_PALETTE + and light.dynamics.status == DynamicStatus.DYNAMIC_PALETTE ): lights_in_dynamic_mode += 1 diff --git a/homeassistant/components/hue/v2/sensor.py b/homeassistant/components/hue/v2/sensor.py index ff2b7b78e7daa0..d331393d29b6bc 100644 --- a/homeassistant/components/hue/v2/sensor.py +++ b/homeassistant/components/hue/v2/sensor.py @@ -12,10 +12,10 @@ TemperatureController, ZigbeeConnectivityController, ) -from aiohue.v2.models.connectivity import ZigbeeConnectivity from aiohue.v2.models.device_power import DevicePower from aiohue.v2.models.light_level import LightLevel from aiohue.v2.models.temperature import Temperature +from aiohue.v2.models.zigbee_connectivity import ZigbeeConnectivity from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.components.sensor import ( diff --git a/requirements_all.txt b/requirements_all.txt index 9be38d29cc595e..e49ada0b8ce8ed 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -191,7 +191,7 @@ aiohomekit==0.7.14 aiohttp_cors==0.7.0 # homeassistant.components.hue -aiohue==4.0.1 +aiohue==4.1.2 # homeassistant.components.homewizard aiohwenergy==0.8.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9f90d498f35c1d..5b9a5db5f7b3c7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -141,7 +141,7 @@ aiohomekit==0.7.14 aiohttp_cors==0.7.0 # homeassistant.components.hue -aiohue==4.0.1 +aiohue==4.1.2 # homeassistant.components.homewizard aiohwenergy==0.8.0 diff --git a/tests/components/hue/conftest.py b/tests/components/hue/conftest.py index 9e9ed9af31b36d..d0d15d320e0ac9 100644 --- a/tests/components/hue/conftest.py +++ b/tests/components/hue/conftest.py @@ -8,7 +8,6 @@ import aiohue.v1 as aiohue_v1 import aiohue.v2 as aiohue_v2 from aiohue.v2.controllers.events import EventType -from aiohue.v2.models.clip import parse_clip_resource import pytest from homeassistant.components import hue @@ -187,7 +186,7 @@ async def load_test_data(data): def emit_event(event_type, data): """Emit an event from a (hue resource) dict.""" - api.events.emit(EventType(event_type), parse_clip_resource(data)) + api.events.emit(EventType(event_type), data) api.load_test_data = load_test_data api.emit_event = emit_event diff --git a/tests/components/hue/test_light_v2.py b/tests/components/hue/test_light_v2.py index c7578df3a49127..f0265233e4ebe0 100644 --- a/tests/components/hue/test_light_v2.py +++ b/tests/components/hue/test_light_v2.py @@ -97,8 +97,12 @@ async def test_light_turn_on_service(hass, mock_bridge_v2, v2_resources_test_dat assert mock_bridge_v2.mock_requests[0]["json"]["color_temperature"]["mirek"] == 300 # Now generate update event by emitting the json we've sent as incoming event - mock_bridge_v2.mock_requests[0]["json"]["color_temperature"].pop("mirek_valid") - mock_bridge_v2.api.emit_event("update", mock_bridge_v2.mock_requests[0]["json"]) + event = { + "id": "3a6710fa-4474-4eba-b533-5e6e72968feb", + "type": "light", + **mock_bridge_v2.mock_requests[0]["json"], + } + mock_bridge_v2.api.emit_event("update", event) await hass.async_block_till_done() # the light should now be on @@ -186,7 +190,12 @@ async def test_light_turn_off_service(hass, mock_bridge_v2, v2_resources_test_da assert mock_bridge_v2.mock_requests[0]["json"]["on"]["on"] is False # Now generate update event by emitting the json we've sent as incoming event - mock_bridge_v2.api.emit_event("update", mock_bridge_v2.mock_requests[0]["json"]) + event = { + "id": "02cba059-9c2c-4d45-97e4-4f79b1bfbaa1", + "type": "light", + **mock_bridge_v2.mock_requests[0]["json"], + } + mock_bridge_v2.api.emit_event("update", event) await hass.async_block_till_done() # the light should now be off @@ -377,10 +386,20 @@ async def test_grouped_lights(hass, mock_bridge_v2, v2_resources_test_data): ) # Now generate update events by emitting the json we've sent as incoming events - for index in range(0, 3): - mock_bridge_v2.api.emit_event( - "update", mock_bridge_v2.mock_requests[index]["json"] - ) + for index, light_id in enumerate( + [ + "02cba059-9c2c-4d45-97e4-4f79b1bfbaa1", + "b3fe71ef-d0ef-48de-9355-d9e604377df0", + "8015b17f-8336-415b-966a-b364bd082397", + ] + ): + event = { + "id": light_id, + "type": "light", + **mock_bridge_v2.mock_requests[index]["json"], + } + mock_bridge_v2.api.emit_event("update", event) + await hass.async_block_till_done() await hass.async_block_till_done() # the light should now be on and have the properties we've set @@ -406,6 +425,12 @@ async def test_grouped_lights(hass, mock_bridge_v2, v2_resources_test_data): assert mock_bridge_v2.mock_requests[0]["json"]["on"]["on"] is False # Now generate update event by emitting the json we've sent as incoming event + event = { + "id": "f2416154-9607-43ab-a684-4453108a200e", + "type": "grouped_light", + **mock_bridge_v2.mock_requests[0]["json"], + } + mock_bridge_v2.api.emit_event("update", event) mock_bridge_v2.api.emit_event("update", mock_bridge_v2.mock_requests[0]["json"]) await hass.async_block_till_done() diff --git a/tests/components/hue/test_switch.py b/tests/components/hue/test_switch.py index 257f1a253c34a5..e8086709705086 100644 --- a/tests/components/hue/test_switch.py +++ b/tests/components/hue/test_switch.py @@ -69,7 +69,12 @@ async def test_switch_turn_off_service(hass, mock_bridge_v2, v2_resources_test_d assert mock_bridge_v2.mock_requests[0]["json"]["enabled"] is False # Now generate update event by emitting the json we've sent as incoming event - mock_bridge_v2.api.emit_event("update", mock_bridge_v2.mock_requests[0]["json"]) + event = { + "id": "b6896534-016d-4052-8cb4-ef04454df62c", + "type": "motion", + **mock_bridge_v2.mock_requests[0]["json"], + } + mock_bridge_v2.api.emit_event("update", event) await hass.async_block_till_done() # the switch should now be off From d29acadebddef982434a619edf591cd40cff290b Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 15 Feb 2022 23:05:12 -0500 Subject: [PATCH 0689/1098] Fix zwave_js device condition bug (#66626) --- .../components/zwave_js/device_condition.py | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/zwave_js/device_condition.py b/homeassistant/components/zwave_js/device_condition.py index ec8538e2b36f02..8bb151199d774f 100644 --- a/homeassistant/components/zwave_js/device_condition.py +++ b/homeassistant/components/zwave_js/device_condition.py @@ -141,18 +141,17 @@ async def async_get_conditions( conditions.append({**base_condition, CONF_TYPE: NODE_STATUS_TYPE}) # Config parameter conditions - for config_value in node.get_configuration_values().values(): - conditions.extend( - [ - { - **base_condition, - CONF_VALUE_ID: config_value.value_id, - CONF_TYPE: CONFIG_PARAMETER_TYPE, - CONF_SUBTYPE: generate_config_parameter_subtype(config_value), - } - for config_value in node.get_configuration_values().values() - ] - ) + conditions.extend( + [ + { + **base_condition, + CONF_VALUE_ID: config_value.value_id, + CONF_TYPE: CONFIG_PARAMETER_TYPE, + CONF_SUBTYPE: generate_config_parameter_subtype(config_value), + } + for config_value in node.get_configuration_values().values() + ] + ) return conditions From cf5652737a5c2f03a51846f91d27afb98767be3c Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Wed, 16 Feb 2022 09:29:52 +0100 Subject: [PATCH 0690/1098] Cleanup samsungtv tests (#66570) * Drop unused init method * Add type hints to media_player tests * Adjust test_init * Adjust media_player * Add type hints to conftest * Use Mock in test_media_player * Use lowercase in test_init * Use relative import in diagnostics * Add type hints to config_flow * Adjust coveragerc * Make gethostbyname autouse * Cleanup gethostbyname and remote fixtures * Drop unused fixtures * Undo type hints and usefixtures on media_player * Undo type hints and usefixtures in test_init * Undo type hints in conftest * Undo usefixtures in test_config_flow * Format Co-authored-by: epenet --- .coveragerc | 1 - tests/components/samsungtv/__init__.py | 13 -- tests/components/samsungtv/conftest.py | 33 ++--- .../components/samsungtv/test_config_flow.py | 134 ++++++------------ .../components/samsungtv/test_diagnostics.py | 3 +- tests/components/samsungtv/test_init.py | 49 +++---- .../components/samsungtv/test_media_player.py | 18 ++- 7 files changed, 84 insertions(+), 167 deletions(-) diff --git a/.coveragerc b/.coveragerc index aa64e660a83317..32cb61a4921b96 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1006,7 +1006,6 @@ omit = homeassistant/components/russound_rnet/media_player.py homeassistant/components/sabnzbd/* homeassistant/components/saj/sensor.py - homeassistant/components/samsungtv/bridge.py homeassistant/components/satel_integra/* homeassistant/components/schluter/* homeassistant/components/screenlogic/__init__.py diff --git a/tests/components/samsungtv/__init__.py b/tests/components/samsungtv/__init__.py index 89768221665774..4ad1622c6cae7a 100644 --- a/tests/components/samsungtv/__init__.py +++ b/tests/components/samsungtv/__init__.py @@ -1,14 +1 @@ """Tests for the samsungtv component.""" -from homeassistant.components.samsungtv.const import DOMAIN as SAMSUNGTV_DOMAIN -from homeassistant.core import HomeAssistant - -from tests.common import MockConfigEntry - - -async def setup_samsungtv(hass: HomeAssistant, config: dict): - """Set up mock Samsung TV.""" - - entry = MockConfigEntry(domain=SAMSUNGTV_DOMAIN, data=config) - entry.add_to_hass(hass) - assert await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() diff --git a/tests/components/samsungtv/conftest.py b/tests/components/samsungtv/conftest.py index 05c51fdf5911ce..f14a0f71bf1c41 100644 --- a/tests/components/samsungtv/conftest.py +++ b/tests/components/samsungtv/conftest.py @@ -5,19 +5,21 @@ import homeassistant.util.dt as dt_util -RESULT_ALREADY_CONFIGURED = "already_configured" -RESULT_ALREADY_IN_PROGRESS = "already_in_progress" - -@pytest.fixture(name="remote") -def remote_fixture(): - """Patch the samsungctl Remote.""" +@pytest.fixture(autouse=True) +def fake_host_fixture() -> None: + """Patch gethostbyname.""" with patch( - "homeassistant.components.samsungtv.bridge.Remote" - ) as remote_class, patch( "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", return_value="fake_host", ): + yield + + +@pytest.fixture(name="remote") +def remote_fixture(): + """Patch the samsungctl Remote.""" + with patch("homeassistant.components.samsungtv.bridge.Remote") as remote_class: remote = Mock() remote.__enter__ = Mock() remote.__exit__ = Mock() @@ -31,10 +33,7 @@ def remotews_fixture(): """Patch the samsungtvws SamsungTVWS.""" with patch( "homeassistant.components.samsungtv.bridge.SamsungTVWS" - ) as remotews_class, patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): + ) as remotews_class: remotews = Mock() remotews.__enter__ = Mock() remotews.__exit__ = Mock() @@ -59,10 +58,7 @@ def remotews_no_device_info_fixture(): """Patch the samsungtvws SamsungTVWS.""" with patch( "homeassistant.components.samsungtv.bridge.SamsungTVWS" - ) as remotews_class, patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): + ) as remotews_class: remotews = Mock() remotews.__enter__ = Mock() remotews.__exit__ = Mock() @@ -77,10 +73,7 @@ def remotews_soundbar_fixture(): """Patch the samsungtvws SamsungTVWS.""" with patch( "homeassistant.components.samsungtv.bridge.SamsungTVWS" - ) as remotews_class, patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): + ) as remotews_class: remotews = Mock() remotews.__enter__ = Mock() remotews.__exit__ = Mock() diff --git a/tests/components/samsungtv/test_config_flow.py b/tests/components/samsungtv/test_config_flow.py index 199c7d87eed4d6..57bb99354dc091 100644 --- a/tests/components/samsungtv/test_config_flow.py +++ b/tests/components/samsungtv/test_config_flow.py @@ -43,10 +43,9 @@ from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry -from tests.components.samsungtv.conftest import ( - RESULT_ALREADY_CONFIGURED, - RESULT_ALREADY_IN_PROGRESS, -) + +RESULT_ALREADY_CONFIGURED = "already_configured" +RESULT_ALREADY_IN_PROGRESS = "already_in_progress" MOCK_IMPORT_DATA = { CONF_HOST: "fake_host", @@ -233,9 +232,7 @@ async def test_user_websocket(hass: HomeAssistant, remotews: Mock): assert result["result"].unique_id == "be9554b9-c9fb-41f4-8920-22da015376a4" -async def test_user_legacy_missing_auth( - hass: HomeAssistant, remote: Mock, remotews: Mock -): +async def test_user_legacy_missing_auth(hass: HomeAssistant, remotews: Mock): """Test starting a flow by user with authentication.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -249,7 +246,7 @@ async def test_user_legacy_missing_auth( assert result["reason"] == RESULT_AUTH_MISSING -async def test_user_legacy_not_supported(hass: HomeAssistant, remote: Mock): +async def test_user_legacy_not_supported(hass: HomeAssistant): """Test starting a flow by user for not supported device.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -263,7 +260,7 @@ async def test_user_legacy_not_supported(hass: HomeAssistant, remote: Mock): assert result["reason"] == RESULT_NOT_SUPPORTED -async def test_user_websocket_not_supported(hass: HomeAssistant, remotews: Mock): +async def test_user_websocket_not_supported(hass: HomeAssistant): """Test starting a flow by user for not supported device.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -280,7 +277,7 @@ async def test_user_websocket_not_supported(hass: HomeAssistant, remotews: Mock) assert result["reason"] == RESULT_NOT_SUPPORTED -async def test_user_not_successful(hass: HomeAssistant, remotews: Mock): +async def test_user_not_successful(hass: HomeAssistant): """Test starting a flow by user but no connection found.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -296,7 +293,7 @@ async def test_user_not_successful(hass: HomeAssistant, remotews: Mock): assert result["reason"] == RESULT_CANNOT_CONNECT -async def test_user_not_successful_2(hass: HomeAssistant, remotews: Mock): +async def test_user_not_successful_2(hass: HomeAssistant): """Test starting a flow by user but no connection found.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -375,9 +372,7 @@ async def test_ssdp_noprefix(hass: HomeAssistant, remote: Mock, no_mac_address: assert result["result"].unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172df" -async def test_ssdp_legacy_missing_auth( - hass: HomeAssistant, remote: Mock, remotews: Mock -): +async def test_ssdp_legacy_missing_auth(hass: HomeAssistant, remotews: Mock): """Test starting a flow from discovery with authentication.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -453,7 +448,7 @@ async def test_ssdp_websocket_success_populates_mac_address( assert result["result"].unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172de" -async def test_ssdp_websocket_not_supported(hass: HomeAssistant, remote: Mock): +async def test_ssdp_websocket_not_supported(hass: HomeAssistant): """Test starting a flow from discovery for not supported device.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -483,9 +478,7 @@ async def test_ssdp_model_not_supported(hass: HomeAssistant, remote: Mock): assert result["reason"] == RESULT_NOT_SUPPORTED -async def test_ssdp_not_successful( - hass: HomeAssistant, remote: Mock, no_mac_address: Mock -): +async def test_ssdp_not_successful(hass: HomeAssistant, no_mac_address: Mock): """Test starting a flow from discovery but no device found.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -513,9 +506,7 @@ async def test_ssdp_not_successful( assert result["reason"] == RESULT_CANNOT_CONNECT -async def test_ssdp_not_successful_2( - hass: HomeAssistant, remote: Mock, no_mac_address: Mock -): +async def test_ssdp_not_successful_2(hass: HomeAssistant, no_mac_address: Mock): """Test starting a flow from discovery but no device found.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -605,15 +596,11 @@ async def test_import_legacy(hass: HomeAssistant, remote: Mock, no_mac_address: """Test importing from yaml with hostname.""" no_mac_address.return_value = "aa:bb:cc:dd:ee:ff" - with patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=MOCK_IMPORT_DATA, - ) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=MOCK_IMPORT_DATA, + ) await hass.async_block_till_done() assert result["type"] == "create_entry" assert result["title"] == "fake" @@ -635,15 +622,11 @@ async def test_import_legacy_without_name( no_mac_address: Mock, ): """Test importing from yaml without a name.""" - with patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=MOCK_IMPORT_DATA_WITHOUT_NAME, - ) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=MOCK_IMPORT_DATA_WITHOUT_NAME, + ) await hass.async_block_till_done() assert result["type"] == "create_entry" assert result["title"] == "fake_host" @@ -659,15 +642,11 @@ async def test_import_legacy_without_name( async def test_import_websocket(hass: HomeAssistant, remotews: Mock): """Test importing from yaml with hostname.""" - with patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=MOCK_IMPORT_WSDATA, - ) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=MOCK_IMPORT_WSDATA, + ) await hass.async_block_till_done() assert result["type"] == "create_entry" assert result["title"] == "fake" @@ -681,15 +660,11 @@ async def test_import_websocket(hass: HomeAssistant, remotews: Mock): async def test_import_websocket_without_port(hass: HomeAssistant, remotews: Mock): """Test importing from yaml with hostname by no port.""" - with patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=MOCK_IMPORT_WSDATA, - ) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=MOCK_IMPORT_WSDATA, + ) await hass.async_block_till_done() assert result["type"] == "create_entry" assert result["title"] == "fake" @@ -818,17 +793,12 @@ async def test_zeroconf_and_dhcp_same_time(hass: HomeAssistant, remotews: Mock): assert result2["reason"] == "already_in_progress" -async def test_autodetect_websocket(hass: HomeAssistant, remote: Mock, remotews: Mock): +async def test_autodetect_websocket(hass: HomeAssistant): """Test for send key with autodetection of protocol.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError("Boom"), - ), patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWS" - ) as remotews: + ), patch("homeassistant.components.samsungtv.bridge.SamsungTVWS") as remotews: enter = Mock() type(enter).token = PropertyMock(return_value="123456789") remote = Mock() @@ -866,14 +836,11 @@ async def test_autodetect_websocket(hass: HomeAssistant, remote: Mock, remotews: assert entries[0].data[CONF_MAC] == "aa:bb:cc:dd:ee:ff" -async def test_websocket_no_mac(hass: HomeAssistant, remote: Mock, remotews: Mock): +async def test_websocket_no_mac(hass: HomeAssistant): """Test for send key with autodetection of protocol.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError("Boom"), - ), patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", ), patch( "homeassistant.components.samsungtv.bridge.SamsungTVWS" ) as remotews, patch( @@ -915,15 +882,12 @@ async def test_websocket_no_mac(hass: HomeAssistant, remote: Mock, remotews: Moc assert entries[0].data[CONF_MAC] == "gg:hh:ii:ll:mm:nn" -async def test_autodetect_auth_missing(hass: HomeAssistant, remote: Mock): +async def test_autodetect_auth_missing(hass: HomeAssistant): """Test for send key with autodetection of protocol.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", side_effect=[AccessDenied("Boom")], - ) as remote, patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): + ) as remote: result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA ) @@ -933,15 +897,12 @@ async def test_autodetect_auth_missing(hass: HomeAssistant, remote: Mock): assert remote.call_args_list == [call(AUTODETECT_LEGACY)] -async def test_autodetect_not_supported(hass: HomeAssistant, remote: Mock): +async def test_autodetect_not_supported(hass: HomeAssistant): """Test for send key with autodetection of protocol.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", side_effect=[UnhandledResponse("Boom")], - ) as remote, patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): + ) as remote: result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA ) @@ -963,7 +924,7 @@ async def test_autodetect_legacy(hass: HomeAssistant, remote: Mock): assert result["data"][CONF_PORT] == LEGACY_PORT -async def test_autodetect_none(hass: HomeAssistant, remote: Mock, remotews: Mock): +async def test_autodetect_none(hass: HomeAssistant): """Test for send key with autodetection of protocol.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -971,10 +932,7 @@ async def test_autodetect_none(hass: HomeAssistant, remote: Mock, remotews: Mock ) as remote, patch( "homeassistant.components.samsungtv.bridge.SamsungTVWS", side_effect=OSError("Boom"), - ) as remotews, patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): + ) as remotews: result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA ) @@ -991,7 +949,7 @@ async def test_autodetect_none(hass: HomeAssistant, remote: Mock, remotews: Mock ] -async def test_update_old_entry(hass: HomeAssistant, remote: Mock, remotews: Mock): +async def test_update_old_entry(hass: HomeAssistant, remotews: Mock): """Test update of old entry.""" with patch("homeassistant.components.samsungtv.bridge.Remote") as remote: remote().rest_device_info.return_value = { @@ -1267,9 +1225,6 @@ async def test_form_reauth_websocket_cannot_connect(hass, remotews: Mock): with patch( "homeassistant.components.samsungtv.bridge.SamsungTVWS", side_effect=ConnectionFailure, - ), patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -1290,7 +1245,7 @@ async def test_form_reauth_websocket_cannot_connect(hass, remotews: Mock): assert result3["reason"] == "reauth_successful" -async def test_form_reauth_websocket_not_supported(hass, remotews: Mock): +async def test_form_reauth_websocket_not_supported(hass): """Test reauthenticate websocket when the device is not supported.""" entry = MockConfigEntry(domain=DOMAIN, data=MOCK_WS_ENTRY) entry.add_to_hass(hass) @@ -1305,9 +1260,6 @@ async def test_form_reauth_websocket_not_supported(hass, remotews: Mock): with patch( "homeassistant.components.samsungtv.bridge.SamsungTVWS", side_effect=WebSocketException, - ), patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/samsungtv/test_diagnostics.py b/tests/components/samsungtv/test_diagnostics.py index ba3d03d5702cce..990c25c8f3e8f9 100644 --- a/tests/components/samsungtv/test_diagnostics.py +++ b/tests/components/samsungtv/test_diagnostics.py @@ -7,9 +7,10 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant +from .test_media_player import MOCK_ENTRY_WS_WITH_MAC + from tests.common import MockConfigEntry from tests.components.diagnostics import get_diagnostics_for_config_entry -from tests.components.samsungtv.test_media_player import MOCK_ENTRY_WS_WITH_MAC @pytest.fixture(name="config_entry") diff --git a/tests/components/samsungtv/test_init.py b/tests/components/samsungtv/test_init.py index bd3e2a512564a2..e49b8fdc5ee97d 100644 --- a/tests/components/samsungtv/test_init.py +++ b/tests/components/samsungtv/test_init.py @@ -55,27 +55,21 @@ async def test_setup(hass: HomeAssistant, remotews: Mock, no_mac_address: Mock): """Test Samsung TV integration is setup.""" - with patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): - - await async_setup_component(hass, SAMSUNGTV_DOMAIN, MOCK_CONFIG) - await hass.async_block_till_done() - state = hass.states.get(ENTITY_ID) + await async_setup_component(hass, SAMSUNGTV_DOMAIN, MOCK_CONFIG) + await hass.async_block_till_done() + state = hass.states.get(ENTITY_ID) - # test name and turn_on - assert state - assert state.name == "fake_name" - assert ( - state.attributes[ATTR_SUPPORTED_FEATURES] - == SUPPORT_SAMSUNGTV | SUPPORT_TURN_ON - ) + # test name and turn_on + assert state + assert state.name == "fake_name" + assert ( + state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_SAMSUNGTV | SUPPORT_TURN_ON + ) - # test host and port - assert await hass.services.async_call( - DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID}, True - ) + # test host and port + assert await hass.services.async_call( + DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID}, True + ) async def test_setup_from_yaml_without_port_device_offline(hass: HomeAssistant): @@ -88,9 +82,6 @@ async def test_setup_from_yaml_without_port_device_offline(hass: HomeAssistant): ), patch( "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.device_info", return_value=None, - ), patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", ): await async_setup_component(hass, SAMSUNGTV_DOMAIN, MOCK_CONFIG) await hass.async_block_till_done() @@ -104,12 +95,8 @@ async def test_setup_from_yaml_without_port_device_online( hass: HomeAssistant, remotews: Mock ): """Test import from yaml when the device is online.""" - with patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): - await async_setup_component(hass, SAMSUNGTV_DOMAIN, MOCK_CONFIG) - await hass.async_block_till_done() + await async_setup_component(hass, SAMSUNGTV_DOMAIN, MOCK_CONFIG) + await hass.async_block_till_done() config_entries_domain = hass.config_entries.async_entries(SAMSUNGTV_DOMAIN) assert len(config_entries_domain) == 1 @@ -118,13 +105,13 @@ async def test_setup_from_yaml_without_port_device_online( async def test_setup_duplicate_config(hass: HomeAssistant, remote: Mock, caplog): """Test duplicate setup of platform.""" - DUPLICATE = { + duplicate = { SAMSUNGTV_DOMAIN: [ MOCK_CONFIG[SAMSUNGTV_DOMAIN][0], MOCK_CONFIG[SAMSUNGTV_DOMAIN][0], ] } - await async_setup_component(hass, SAMSUNGTV_DOMAIN, DUPLICATE) + await async_setup_component(hass, SAMSUNGTV_DOMAIN, duplicate) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID) is None assert len(hass.states.async_all("media_player")) == 0 @@ -132,7 +119,7 @@ async def test_setup_duplicate_config(hass: HomeAssistant, remote: Mock, caplog) async def test_setup_duplicate_entries( - hass: HomeAssistant, remote: Mock, remotews: Mock, no_mac_address: Mock, caplog + hass: HomeAssistant, remote: Mock, remotews: Mock, no_mac_address: Mock ): """Test duplicate setup of platform.""" await async_setup_component(hass, SAMSUNGTV_DOMAIN, MOCK_CONFIG) diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py index dca7e4366f3e11..2d8c07c22cf9c8 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -117,6 +117,9 @@ ] } +# Fake mac address in all mediaplayer tests. +pytestmark = pytest.mark.usefixtures("no_mac_address") + @pytest.fixture(name="delay") def delay_fixture(): @@ -127,11 +130,6 @@ def delay_fixture(): yield delay -@pytest.fixture(autouse=True) -def mock_no_mac_address(no_mac_address): - """Fake mac address in all mediaplayer tests.""" - - async def setup_samsungtv(hass, config): """Set up mock Samsung TV.""" await async_setup_component(hass, SAMSUNGTV_DOMAIN, config) @@ -150,7 +148,7 @@ async def test_setup_without_turnon(hass, remote): assert hass.states.get(ENTITY_ID_NOTURNON) -async def test_setup_websocket(hass, remotews, mock_now): +async def test_setup_websocket(hass, remotews): """Test setup of platform.""" with patch("homeassistant.components.samsungtv.bridge.SamsungTVWS") as remote_class: enter = Mock() @@ -742,7 +740,7 @@ async def sleep(duration): assert len(sleeps) == 3 -async def test_play_media_invalid_type(hass, remote): +async def test_play_media_invalid_type(hass): """Test for play_media with invalid media type.""" with patch("homeassistant.components.samsungtv.bridge.Remote") as remote: url = "https://example.com" @@ -764,7 +762,7 @@ async def test_play_media_invalid_type(hass, remote): assert remote.call_count == 1 -async def test_play_media_channel_as_string(hass, remote): +async def test_play_media_channel_as_string(hass): """Test for play_media with invalid channel as string.""" with patch("homeassistant.components.samsungtv.bridge.Remote") as remote: url = "https://example.com" @@ -786,7 +784,7 @@ async def test_play_media_channel_as_string(hass, remote): assert remote.call_count == 1 -async def test_play_media_channel_as_non_positive(hass, remote): +async def test_play_media_channel_as_non_positive(hass): """Test for play_media with invalid channel as non positive integer.""" with patch("homeassistant.components.samsungtv.bridge.Remote") as remote: await setup_samsungtv(hass, MOCK_CONFIG) @@ -823,7 +821,7 @@ async def test_select_source(hass, remote): assert remote.close.call_args_list == [call()] -async def test_select_source_invalid_source(hass, remote): +async def test_select_source_invalid_source(hass): """Test for select_source with invalid source.""" with patch("homeassistant.components.samsungtv.bridge.Remote") as remote: await setup_samsungtv(hass, MOCK_CONFIG) From ce4daab8333a82fb879995f8c9d0bdd472da3254 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 16 Feb 2022 04:59:57 -0600 Subject: [PATCH 0691/1098] Enable dhcp flows for goalzero registered devices (#66586) --- homeassistant/components/goalzero/manifest.json | 1 + homeassistant/generated/dhcp.py | 1 + 2 files changed, 2 insertions(+) diff --git a/homeassistant/components/goalzero/manifest.json b/homeassistant/components/goalzero/manifest.json index 04bd538322e1a3..5cb00c11191af5 100644 --- a/homeassistant/components/goalzero/manifest.json +++ b/homeassistant/components/goalzero/manifest.json @@ -5,6 +5,7 @@ "documentation": "https://www.home-assistant.io/integrations/goalzero", "requirements": ["goalzero==0.2.1"], "dhcp": [ + {"registered_devices": true}, {"hostname": "yeti*"} ], "codeowners": ["@tkdrob"], diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index ead49a36b6706f..b4536f924262a0 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -36,6 +36,7 @@ {'domain': 'flux_led', 'hostname': 'zengge_[0-9a-f][0-9a-f]_*'}, {'domain': 'flux_led', 'hostname': 'sta*', 'macaddress': 'C82E47*'}, {'domain': 'fronius', 'macaddress': '0003AC*'}, + {'domain': 'goalzero', 'registered_devices': True}, {'domain': 'goalzero', 'hostname': 'yeti*'}, {'domain': 'gogogate2', 'hostname': 'ismartgate*'}, {'domain': 'guardian', 'hostname': 'gvc*', 'macaddress': '30AEA4*'}, From 59cb1444a5c679af25ff36dc5414ccf1a9531b09 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 16 Feb 2022 05:04:46 -0600 Subject: [PATCH 0692/1098] Enable dhcp flows for broadlink registered devices (#66582) --- homeassistant/components/broadlink/manifest.json | 1 + homeassistant/generated/dhcp.py | 1 + 2 files changed, 2 insertions(+) diff --git a/homeassistant/components/broadlink/manifest.json b/homeassistant/components/broadlink/manifest.json index 63f09e3dfb3c8e..db1601edd67f85 100644 --- a/homeassistant/components/broadlink/manifest.json +++ b/homeassistant/components/broadlink/manifest.json @@ -6,6 +6,7 @@ "codeowners": ["@danielhiversen", "@felipediel", "@L-I-Am"], "config_flow": true, "dhcp": [ + {"registered_devices": true}, { "macaddress": "34EA34*" }, diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index b4536f924262a0..d895468d559f8e 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -18,6 +18,7 @@ {'domain': 'blink', 'hostname': 'blink*', 'macaddress': 'B85F98*'}, {'domain': 'blink', 'hostname': 'blink*', 'macaddress': '00037F*'}, {'domain': 'blink', 'hostname': 'blink*', 'macaddress': '20A171*'}, + {'domain': 'broadlink', 'registered_devices': True}, {'domain': 'broadlink', 'macaddress': '34EA34*'}, {'domain': 'broadlink', 'macaddress': '24DFA7*'}, {'domain': 'broadlink', 'macaddress': 'A043B0*'}, From 38b9bea9a307ec4421ea736c70dc51723cdccecc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 16 Feb 2022 05:06:58 -0600 Subject: [PATCH 0693/1098] Switch tplink to use integration discovery (#66575) --- homeassistant/components/tplink/__init__.py | 2 +- homeassistant/components/tplink/config_flow.py | 4 ++-- tests/components/tplink/test_config_flow.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/tplink/__init__.py b/homeassistant/components/tplink/__init__.py index e6b4c4aceab775..83f4b820523aeb 100644 --- a/homeassistant/components/tplink/__init__.py +++ b/homeassistant/components/tplink/__init__.py @@ -39,7 +39,7 @@ def async_trigger_discovery( hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data={ CONF_NAME: device.alias, CONF_HOST: device.host, diff --git a/homeassistant/components/tplink/config_flow.py b/homeassistant/components/tplink/config_flow.py index 5cb351989bea03..8b05f90041ab22 100644 --- a/homeassistant/components/tplink/config_flow.py +++ b/homeassistant/components/tplink/config_flow.py @@ -35,10 +35,10 @@ async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowRes discovery_info.ip, discovery_info.macaddress ) - async def async_step_discovery( + async def async_step_integration_discovery( self, discovery_info: DiscoveryInfoType ) -> FlowResult: - """Handle discovery.""" + """Handle integration discovery.""" return await self._async_handle_discovery( discovery_info[CONF_HOST], discovery_info[CONF_MAC] ) diff --git a/tests/components/tplink/test_config_flow.py b/tests/components/tplink/test_config_flow.py index fdc9fcea83ec87..a3792238fb2815 100644 --- a/tests/components/tplink/test_config_flow.py +++ b/tests/components/tplink/test_config_flow.py @@ -252,7 +252,7 @@ async def test_discovered_by_discovery_and_dhcp(hass): with _patch_discovery(), _patch_single_discovery(): result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data={CONF_HOST: IP_ADDRESS, CONF_MAC: MAC_ADDRESS, CONF_NAME: ALIAS}, ) await hass.async_block_till_done() @@ -304,7 +304,7 @@ async def test_discovered_by_discovery_and_dhcp(hass): dhcp.DhcpServiceInfo(ip=IP_ADDRESS, macaddress=MAC_ADDRESS, hostname=ALIAS), ), ( - config_entries.SOURCE_DISCOVERY, + config_entries.SOURCE_INTEGRATION_DISCOVERY, {CONF_HOST: IP_ADDRESS, CONF_MAC: MAC_ADDRESS, CONF_NAME: ALIAS}, ), ], @@ -345,7 +345,7 @@ async def test_discovered_by_dhcp_or_discovery(hass, source, data): dhcp.DhcpServiceInfo(ip=IP_ADDRESS, macaddress=MAC_ADDRESS, hostname=ALIAS), ), ( - config_entries.SOURCE_DISCOVERY, + config_entries.SOURCE_INTEGRATION_DISCOVERY, {CONF_HOST: IP_ADDRESS, CONF_MAC: MAC_ADDRESS, CONF_NAME: ALIAS}, ), ], From e0cee22b8a950dd94e81b07ecfee30bd43cf3a97 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 16 Feb 2022 05:07:36 -0600 Subject: [PATCH 0694/1098] Switch ezviz to use integration discovery (#66579) --- homeassistant/components/ezviz/camera.py | 4 ++-- homeassistant/components/ezviz/config_flow.py | 2 +- tests/components/ezviz/test_config_flow.py | 16 ++++++++++------ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/ezviz/camera.py b/homeassistant/components/ezviz/camera.py index 67c4302e6de80b..6680466ecf083c 100644 --- a/homeassistant/components/ezviz/camera.py +++ b/homeassistant/components/ezviz/camera.py @@ -10,9 +10,9 @@ from homeassistant.components.camera import PLATFORM_SCHEMA, SUPPORT_STREAM, Camera from homeassistant.components.ffmpeg import get_ffmpeg_manager from homeassistant.config_entries import ( - SOURCE_DISCOVERY, SOURCE_IGNORE, SOURCE_IMPORT, + SOURCE_INTEGRATION_DISCOVERY, ConfigEntry, ) from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_USERNAME @@ -151,7 +151,7 @@ async def async_setup_entry( hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, - context={"source": SOURCE_DISCOVERY}, + context={"source": SOURCE_INTEGRATION_DISCOVERY}, data={ ATTR_SERIAL: camera, CONF_IP_ADDRESS: value["local_ip"], diff --git a/homeassistant/components/ezviz/config_flow.py b/homeassistant/components/ezviz/config_flow.py index a5a3444c0dd302..780d06383f917a 100644 --- a/homeassistant/components/ezviz/config_flow.py +++ b/homeassistant/components/ezviz/config_flow.py @@ -259,7 +259,7 @@ async def async_step_user_custom_url(self, user_input=None): step_id="user_custom_url", data_schema=data_schema_custom_url, errors=errors ) - async def async_step_discovery(self, discovery_info): + async def async_step_integration_discovery(self, discovery_info): """Handle a flow for discovered camera without rtsp config entry.""" await self.async_set_unique_id(discovery_info[ATTR_SERIAL]) diff --git a/tests/components/ezviz/test_config_flow.py b/tests/components/ezviz/test_config_flow.py index 4dffe1d7e255a7..9a3129b7dd6b90 100644 --- a/tests/components/ezviz/test_config_flow.py +++ b/tests/components/ezviz/test_config_flow.py @@ -19,7 +19,11 @@ DEFAULT_TIMEOUT, DOMAIN, ) -from homeassistant.config_entries import SOURCE_DISCOVERY, SOURCE_IMPORT, SOURCE_USER +from homeassistant.config_entries import ( + SOURCE_IMPORT, + SOURCE_INTEGRATION_DISCOVERY, + SOURCE_USER, +) from homeassistant.const import ( CONF_CUSTOMIZE, CONF_IP_ADDRESS, @@ -175,7 +179,7 @@ async def test_step_discovery_abort_if_cloud_account_missing(hass): """Test discovery and confirm step, abort if cloud account was removed.""" result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_DISCOVERY}, data=DISCOVERY_INFO + DOMAIN, context={"source": SOURCE_INTEGRATION_DISCOVERY}, data=DISCOVERY_INFO ) assert result["type"] == RESULT_TYPE_FORM assert result["step_id"] == "confirm" @@ -194,7 +198,7 @@ async def test_step_discovery_abort_if_cloud_account_missing(hass): assert result["reason"] == "ezviz_cloud_account_missing" -async def test_async_step_discovery( +async def test_async_step_integration_discovery( hass, ezviz_config_flow, ezviz_test_rtsp_config_flow ): """Test discovery and confirm step.""" @@ -202,7 +206,7 @@ async def test_async_step_discovery( await init_integration(hass) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_DISCOVERY}, data=DISCOVERY_INFO + DOMAIN, context={"source": SOURCE_INTEGRATION_DISCOVERY}, data=DISCOVERY_INFO ) assert result["type"] == RESULT_TYPE_FORM assert result["step_id"] == "confirm" @@ -353,7 +357,7 @@ async def test_discover_exception_step1( result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": SOURCE_DISCOVERY}, + context={"source": SOURCE_INTEGRATION_DISCOVERY}, data={ATTR_SERIAL: "C66666", CONF_IP_ADDRESS: "test-ip"}, ) assert result["type"] == RESULT_TYPE_FORM @@ -428,7 +432,7 @@ async def test_discover_exception_step3( result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": SOURCE_DISCOVERY}, + context={"source": SOURCE_INTEGRATION_DISCOVERY}, data={ATTR_SERIAL: "C66666", CONF_IP_ADDRESS: "test-ip"}, ) assert result["type"] == RESULT_TYPE_FORM From fd96d81563e51715bf06704646b241a7f2c193c6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 16 Feb 2022 05:09:00 -0600 Subject: [PATCH 0695/1098] Enable dhcp flows for flux_led registered devices (#66585) --- homeassistant/components/flux_led/manifest.json | 1 + homeassistant/generated/dhcp.py | 1 + 2 files changed, 2 insertions(+) diff --git a/homeassistant/components/flux_led/manifest.json b/homeassistant/components/flux_led/manifest.json index b3448d0b790f39..9f1d307c14a9d3 100644 --- a/homeassistant/components/flux_led/manifest.json +++ b/homeassistant/components/flux_led/manifest.json @@ -9,6 +9,7 @@ "codeowners": ["@icemanch", "@bdraco"], "iot_class": "local_push", "dhcp": [ + {"registered_devices": true}, { "macaddress": "18B905*", "hostname": "[ba][lk]*" diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index d895468d559f8e..b0edb218b7b756 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -27,6 +27,7 @@ {'domain': 'emonitor', 'hostname': 'emonitor*', 'macaddress': '0090C2*'}, {'domain': 'emonitor', 'registered_devices': True}, {'domain': 'flume', 'hostname': 'flume-gw-*'}, + {'domain': 'flux_led', 'registered_devices': True}, {'domain': 'flux_led', 'hostname': '[ba][lk]*', 'macaddress': '18B905*'}, {'domain': 'flux_led', 'hostname': '[ba][lk]*', 'macaddress': '249494*'}, {'domain': 'flux_led', 'hostname': '[ba][lk]*', 'macaddress': '7CB94C*'}, From b322c6dafcccb0748512287c760b4b848509934c Mon Sep 17 00:00:00 2001 From: tschnilo <97060950+tschnilo@users.noreply.github.com> Date: Wed, 16 Feb 2022 12:11:37 +0100 Subject: [PATCH 0696/1098] Add vicare sensors (#63339) * Add sensors for: - solar collector - power consumption * Update sensor.py * Update sensor.py * Update sensor.py * Update sensor.py --- homeassistant/components/vicare/sensor.py | 70 ++++++++++++++++++++++- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/vicare/sensor.py b/homeassistant/components/vicare/sensor.py index a0d7208f1b2d49..6f1b6097c4fe99 100644 --- a/homeassistant/components/vicare/sensor.py +++ b/homeassistant/components/vicare/sensor.py @@ -189,10 +189,74 @@ class ViCareSensorEntityDescription(SensorEntityDescription, ViCareRequiredKeysM state_class=SensorStateClass.MEASUREMENT, ), ViCareSensorEntityDescription( - key="solar power production", - name="Solar Power Production", + key="solar power production today", + name="Solar power production today", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, - value_getter=lambda api: api.getSolarPowerProduction(), + value_getter=lambda api: api.getSolarPowerProductionToday(), + unit_getter=lambda api: api.getSolarPowerProductionUnit(), + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + ViCareSensorEntityDescription( + key="solar power production this week", + name="Solar power production this week", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + value_getter=lambda api: api.getSolarPowerProductionThisWeek(), + unit_getter=lambda api: api.getSolarPowerProductionUnit(), + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + ViCareSensorEntityDescription( + key="solar power production this month", + name="Solar power production this month", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + value_getter=lambda api: api.getSolarPowerProductionThisMonth(), + unit_getter=lambda api: api.getSolarPowerProductionUnit(), + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + ViCareSensorEntityDescription( + key="solar power production this year", + name="Solar power production this year", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + value_getter=lambda api: api.getSolarPowerProductionThisYear(), + unit_getter=lambda api: api.getSolarPowerProductionUnit(), + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + ViCareSensorEntityDescription( + key="power consumption today", + name="Power consumption today", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + value_getter=lambda api: api.getPowerConsumptionToday(), + unit_getter=lambda api: api.getPowerConsumptionUnit(), + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + ViCareSensorEntityDescription( + key="power consumption this week", + name="Power consumption this week", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + value_getter=lambda api: api.getPowerConsumptionThisWeek(), + unit_getter=lambda api: api.getPowerConsumptionUnit(), + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + ViCareSensorEntityDescription( + key="power consumption this month", + name="Power consumption this month", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + value_getter=lambda api: api.getPowerConsumptionThisMonth(), + unit_getter=lambda api: api.getPowerConsumptionUnit(), + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + ViCareSensorEntityDescription( + key="power consumption this year", + name="Power consumption this year", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + value_getter=lambda api: api.getPowerConsumptionThisYear(), + unit_getter=lambda api: api.getPowerConsumptionUnit(), device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, ), From 21f2c664d9db9a80440443bf1314d920cecd6db0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 16 Feb 2022 05:13:16 -0600 Subject: [PATCH 0697/1098] Enable dhcp flows for elkm1 registered devices (#66583) --- homeassistant/components/elkm1/__init__.py | 9 ++++++++- homeassistant/components/elkm1/manifest.json | 5 ++++- homeassistant/generated/dhcp.py | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/elkm1/__init__.py b/homeassistant/components/elkm1/__init__.py index 8ab9c0ac73f269..8b0dd26fc32684 100644 --- a/homeassistant/components/elkm1/__init__.py +++ b/homeassistant/components/elkm1/__init__.py @@ -14,6 +14,7 @@ from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( + ATTR_CONNECTIONS, CONF_EXCLUDE, CONF_HOST, CONF_INCLUDE, @@ -28,6 +29,7 @@ from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType @@ -286,6 +288,7 @@ def _element_changed(element, changeset): hass.data[DOMAIN][entry.entry_id] = { "elk": elk, "prefix": conf[CONF_PREFIX], + "mac": entry.unique_id, "auto_configure": conf[CONF_AUTO_CONFIGURE], "config": config, "keypads": {}, @@ -420,6 +423,7 @@ def __init__(self, element, elk, elk_data): """Initialize the base of all Elk devices.""" self._elk = elk self._element = element + self._mac = elk_data["mac"] self._prefix = elk_data["prefix"] self._name_prefix = f"{self._prefix} " if self._prefix else "" self._temperature_unit = elk_data["config"]["temperature_unit"] @@ -499,10 +503,13 @@ def device_info(self) -> DeviceInfo: device_name = "ElkM1" if self._prefix: device_name += f" {self._prefix}" - return DeviceInfo( + device_info = DeviceInfo( identifiers={(DOMAIN, f"{self._prefix}_system")}, manufacturer="ELK Products, Inc.", model="M1", name=device_name, sw_version=self._elk.panel.elkm1_version, ) + if self._mac: + device_info[ATTR_CONNECTIONS] = {(CONNECTION_NETWORK_MAC, self._mac)} + return device_info diff --git a/homeassistant/components/elkm1/manifest.json b/homeassistant/components/elkm1/manifest.json index a72a02219074ad..909bfa3bd02f7d 100644 --- a/homeassistant/components/elkm1/manifest.json +++ b/homeassistant/components/elkm1/manifest.json @@ -3,7 +3,10 @@ "name": "Elk-M1 Control", "documentation": "https://www.home-assistant.io/integrations/elkm1", "requirements": ["elkm1-lib==1.2.0"], - "dhcp": [{"macaddress":"00409D*"}], + "dhcp": [ + {"registered_devices": true}, + {"macaddress":"00409D*"} + ], "codeowners": ["@gwww", "@bdraco"], "dependencies": ["network"], "config_flow": true, diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index b0edb218b7b756..25b8222022363f 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -23,6 +23,7 @@ {'domain': 'broadlink', 'macaddress': '24DFA7*'}, {'domain': 'broadlink', 'macaddress': 'A043B0*'}, {'domain': 'broadlink', 'macaddress': 'B4430D*'}, + {'domain': 'elkm1', 'registered_devices': True}, {'domain': 'elkm1', 'macaddress': '00409D*'}, {'domain': 'emonitor', 'hostname': 'emonitor*', 'macaddress': '0090C2*'}, {'domain': 'emonitor', 'registered_devices': True}, From dcb3fc49c9619844a963734428c973ad70e597f7 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 16 Feb 2022 12:29:08 +0100 Subject: [PATCH 0698/1098] Include changes in EVENT_DEVICE_REGISTRY_UPDATED (#66641) --- homeassistant/helpers/device_registry.py | 41 ++++++---- tests/helpers/test_device_registry.py | 100 +++++++++++++++++++++-- 2 files changed, 119 insertions(+), 22 deletions(-) diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 96425b2ea93bc7..cb009efeb074d6 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -11,7 +11,7 @@ from homeassistant.backports.enum import StrEnum from homeassistant.const import EVENT_HOMEASSISTANT_STARTED from homeassistant.core import Event, HomeAssistant, callback -from homeassistant.exceptions import RequiredParameterMissing +from homeassistant.exceptions import HomeAssistantError, RequiredParameterMissing from homeassistant.loader import bind_hass import homeassistant.util.uuid as uuid_util @@ -420,10 +420,14 @@ def async_update_device( """Update device attributes.""" old = self.devices[device_id] - changes: dict[str, Any] = {} + new_values: dict[str, Any] = {} # Dict with new key/value pairs + old_values: dict[str, Any] = {} # Dict with old key/value pairs config_entries = old.config_entries + if merge_identifiers is not UNDEFINED and new_identifiers is not UNDEFINED: + raise HomeAssistantError() + if isinstance(disabled_by, str) and not isinstance( disabled_by, DeviceEntryDisabler ): @@ -462,7 +466,8 @@ def async_update_device( config_entries = config_entries - {remove_config_entry_id} if config_entries != old.config_entries: - changes["config_entries"] = config_entries + new_values["config_entries"] = config_entries + old_values["config_entries"] = old.config_entries for attr_name, setvalue in ( ("connections", merge_connections), @@ -471,10 +476,12 @@ def async_update_device( old_value = getattr(old, attr_name) # If not undefined, check if `value` contains new items. if setvalue is not UNDEFINED and not setvalue.issubset(old_value): - changes[attr_name] = old_value | setvalue + new_values[attr_name] = old_value | setvalue + old_values[attr_name] = old_value if new_identifiers is not UNDEFINED: - changes["identifiers"] = new_identifiers + new_values["identifiers"] = new_identifiers + old_values["identifiers"] = old.identifiers for attr_name, value in ( ("configuration_url", configuration_url), @@ -491,25 +498,27 @@ def async_update_device( ("via_device_id", via_device_id), ): if value is not UNDEFINED and value != getattr(old, attr_name): - changes[attr_name] = value + new_values[attr_name] = value + old_values[attr_name] = getattr(old, attr_name) if old.is_new: - changes["is_new"] = False + new_values["is_new"] = False - if not changes: + if not new_values: return old - new = attr.evolve(old, **changes) + new = attr.evolve(old, **new_values) self._update_device(old, new) self.async_schedule_save() - self.hass.bus.async_fire( - EVENT_DEVICE_REGISTRY_UPDATED, - { - "action": "create" if "is_new" in changes else "update", - "device_id": new.id, - }, - ) + data: dict[str, Any] = { + "action": "create" if old.is_new else "update", + "device_id": new.id, + } + if not old.is_new: + data["changes"] = old_values + + self.hass.bus.async_fire(EVENT_DEVICE_REGISTRY_UPDATED, data) return new diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index a0949bad03ce01..4e4150fe504cc2 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -96,8 +96,12 @@ async def test_get_or_create_returns_same_entry( assert len(update_events) == 2 assert update_events[0]["action"] == "create" assert update_events[0]["device_id"] == entry.id + assert "changes" not in update_events[0] assert update_events[1]["action"] == "update" assert update_events[1]["device_id"] == entry.id + assert update_events[1]["changes"] == { + "connections": {("mac", "12:34:56:ab:cd:ef")} + } async def test_requirement_for_identifier_or_connection(registry): @@ -518,14 +522,19 @@ async def test_removing_config_entries(hass, registry, update_events): assert len(update_events) == 5 assert update_events[0]["action"] == "create" assert update_events[0]["device_id"] == entry.id + assert "changes" not in update_events[0] assert update_events[1]["action"] == "update" assert update_events[1]["device_id"] == entry2.id + assert update_events[1]["changes"] == {"config_entries": {"123"}} assert update_events[2]["action"] == "create" assert update_events[2]["device_id"] == entry3.id + assert "changes" not in update_events[2] assert update_events[3]["action"] == "update" assert update_events[3]["device_id"] == entry.id + assert update_events[3]["changes"] == {"config_entries": {"456", "123"}} assert update_events[4]["action"] == "remove" assert update_events[4]["device_id"] == entry3.id + assert "changes" not in update_events[4] async def test_deleted_device_removing_config_entries(hass, registry, update_events): @@ -568,14 +577,19 @@ async def test_deleted_device_removing_config_entries(hass, registry, update_eve assert len(update_events) == 5 assert update_events[0]["action"] == "create" assert update_events[0]["device_id"] == entry.id + assert "changes" not in update_events[0] assert update_events[1]["action"] == "update" assert update_events[1]["device_id"] == entry2.id + assert update_events[1]["changes"] == {"config_entries": {"123"}} assert update_events[2]["action"] == "create" assert update_events[2]["device_id"] == entry3.id + assert "changes" not in update_events[2]["device_id"] assert update_events[3]["action"] == "remove" assert update_events[3]["device_id"] == entry.id + assert "changes" not in update_events[3] assert update_events[4]["action"] == "remove" assert update_events[4]["device_id"] == entry3.id + assert "changes" not in update_events[4] registry.async_clear_config_entry("123") assert len(registry.devices) == 0 @@ -892,7 +906,7 @@ async def test_format_mac(registry): assert list(invalid_mac_entry.connections)[0][1] == invalid -async def test_update(registry): +async def test_update(hass, registry, update_events): """Verify that we can update some attributes of a device.""" entry = registry.async_get_or_create( config_entry_id="1234", @@ -940,6 +954,24 @@ async def test_update(registry): assert registry.async_get(updated_entry.id) is not None + await hass.async_block_till_done() + + assert len(update_events) == 2 + assert update_events[0]["action"] == "create" + assert update_events[0]["device_id"] == entry.id + assert "changes" not in update_events[0] + assert update_events[1]["action"] == "update" + assert update_events[1]["device_id"] == entry.id + assert update_events[1]["changes"] == { + "area_id": None, + "disabled_by": None, + "identifiers": {("bla", "123"), ("hue", "456")}, + "manufacturer": None, + "model": None, + "name_by_user": None, + "via_device_id": None, + } + async def test_update_remove_config_entries(hass, registry, update_events): """Make sure we do not get duplicate entries.""" @@ -989,17 +1021,22 @@ async def test_update_remove_config_entries(hass, registry, update_events): assert len(update_events) == 5 assert update_events[0]["action"] == "create" assert update_events[0]["device_id"] == entry.id + assert "changes" not in update_events[0] assert update_events[1]["action"] == "update" assert update_events[1]["device_id"] == entry2.id + assert update_events[1]["changes"] == {"config_entries": {"123"}} assert update_events[2]["action"] == "create" assert update_events[2]["device_id"] == entry3.id + assert "changes" not in update_events[2] assert update_events[3]["action"] == "update" assert update_events[3]["device_id"] == entry.id + assert update_events[3]["changes"] == {"config_entries": {"456", "123"}} assert update_events[4]["action"] == "remove" assert update_events[4]["device_id"] == entry3.id + assert "changes" not in update_events[4] -async def test_update_sw_version(registry): +async def test_update_sw_version(hass, registry, update_events): """Verify that we can update software version of a device.""" entry = registry.async_get_or_create( config_entry_id="1234", @@ -1016,8 +1053,18 @@ async def test_update_sw_version(registry): assert updated_entry != entry assert updated_entry.sw_version == sw_version + await hass.async_block_till_done() -async def test_update_hw_version(registry): + assert len(update_events) == 2 + assert update_events[0]["action"] == "create" + assert update_events[0]["device_id"] == entry.id + assert "changes" not in update_events[0] + assert update_events[1]["action"] == "update" + assert update_events[1]["device_id"] == entry.id + assert update_events[1]["changes"] == {"sw_version": None} + + +async def test_update_hw_version(hass, registry, update_events): """Verify that we can update hardware version of a device.""" entry = registry.async_get_or_create( config_entry_id="1234", @@ -1034,8 +1081,18 @@ async def test_update_hw_version(registry): assert updated_entry != entry assert updated_entry.hw_version == hw_version + await hass.async_block_till_done() + + assert len(update_events) == 2 + assert update_events[0]["action"] == "create" + assert update_events[0]["device_id"] == entry.id + assert "changes" not in update_events[0] + assert update_events[1]["action"] == "update" + assert update_events[1]["device_id"] == entry.id + assert update_events[1]["changes"] == {"hw_version": None} + -async def test_update_suggested_area(registry, area_registry): +async def test_update_suggested_area(hass, registry, area_registry, update_events): """Verify that we can update the suggested area version of a device.""" entry = registry.async_get_or_create( config_entry_id="1234", @@ -1061,6 +1118,16 @@ async def test_update_suggested_area(registry, area_registry): assert updated_entry.area_id == pool_area.id assert len(area_registry.areas) == 1 + await hass.async_block_till_done() + + assert len(update_events) == 2 + assert update_events[0]["action"] == "create" + assert update_events[0]["device_id"] == entry.id + assert "changes" not in update_events[0] + assert update_events[1]["action"] == "update" + assert update_events[1]["device_id"] == entry.id + assert update_events[1]["changes"] == {"area_id": None, "suggested_area": None} + async def test_cleanup_device_registry(hass, registry): """Test cleanup works.""" @@ -1221,12 +1288,16 @@ async def test_restore_device(hass, registry, update_events): assert len(update_events) == 4 assert update_events[0]["action"] == "create" assert update_events[0]["device_id"] == entry.id + assert "changes" not in update_events[0] assert update_events[1]["action"] == "remove" assert update_events[1]["device_id"] == entry.id + assert "changes" not in update_events[1] assert update_events[2]["action"] == "create" assert update_events[2]["device_id"] == entry2.id + assert "changes" not in update_events[2] assert update_events[3]["action"] == "create" assert update_events[3]["device_id"] == entry3.id + assert "changes" not in update_events[3] async def test_restore_simple_device(hass, registry, update_events): @@ -1266,12 +1337,16 @@ async def test_restore_simple_device(hass, registry, update_events): assert len(update_events) == 4 assert update_events[0]["action"] == "create" assert update_events[0]["device_id"] == entry.id + assert "changes" not in update_events[0] assert update_events[1]["action"] == "remove" assert update_events[1]["device_id"] == entry.id + assert "changes" not in update_events[1] assert update_events[2]["action"] == "create" assert update_events[2]["device_id"] == entry2.id + assert "changes" not in update_events[2] assert update_events[3]["action"] == "create" assert update_events[3]["device_id"] == entry3.id + assert "changes" not in update_events[3] async def test_restore_shared_device(hass, registry, update_events): @@ -1358,18 +1433,31 @@ async def test_restore_shared_device(hass, registry, update_events): assert len(update_events) == 7 assert update_events[0]["action"] == "create" assert update_events[0]["device_id"] == entry.id + assert "changes" not in update_events[0] assert update_events[1]["action"] == "update" assert update_events[1]["device_id"] == entry.id + assert update_events[1]["changes"] == { + "config_entries": {"123"}, + "identifiers": {("entry_123", "0123")}, + } assert update_events[2]["action"] == "remove" assert update_events[2]["device_id"] == entry.id + assert "changes" not in update_events[2] assert update_events[3]["action"] == "create" assert update_events[3]["device_id"] == entry.id + assert "changes" not in update_events[3] assert update_events[4]["action"] == "remove" assert update_events[4]["device_id"] == entry.id + assert "changes" not in update_events[4] assert update_events[5]["action"] == "create" assert update_events[5]["device_id"] == entry.id - assert update_events[1]["action"] == "update" - assert update_events[1]["device_id"] == entry.id + assert "changes" not in update_events[5] + assert update_events[6]["action"] == "update" + assert update_events[6]["device_id"] == entry.id + assert update_events[6]["changes"] == { + "config_entries": {"234"}, + "identifiers": {("entry_234", "2345")}, + } async def test_get_or_create_empty_then_set_default_values(hass, registry): From 470936a63a5aae77f4e36d41bec0785fdc1c951a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 16 Feb 2022 06:01:44 -0600 Subject: [PATCH 0699/1098] Enable dhcp flows for samsungtv registered devices (#66589) --- homeassistant/components/samsungtv/manifest.json | 1 + homeassistant/generated/dhcp.py | 1 + 2 files changed, 2 insertions(+) diff --git a/homeassistant/components/samsungtv/manifest.json b/homeassistant/components/samsungtv/manifest.json index 9621e18bf17a1d..f6046986158698 100644 --- a/homeassistant/components/samsungtv/manifest.json +++ b/homeassistant/components/samsungtv/manifest.json @@ -17,6 +17,7 @@ {"type":"_airplay._tcp.local.","properties":{"manufacturer":"samsung*"}} ], "dhcp": [ + {"registered_devices": true}, { "hostname": "tizen*" }, diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 25b8222022363f..11c1331a72d568 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -73,6 +73,7 @@ {'domain': 'roomba', 'hostname': 'irobot-*', 'macaddress': '501479*'}, {'domain': 'roomba', 'hostname': 'roomba-*', 'macaddress': '80A589*'}, {'domain': 'roomba', 'hostname': 'roomba-*', 'macaddress': 'DCF505*'}, + {'domain': 'samsungtv', 'registered_devices': True}, {'domain': 'samsungtv', 'hostname': 'tizen*'}, {'domain': 'samsungtv', 'macaddress': '8CC8CD*'}, {'domain': 'samsungtv', 'macaddress': '606BBD*'}, From 19d8b8a6ff2e312b1698ce05a9bd543c6cbe2b1a Mon Sep 17 00:00:00 2001 From: Milan Meulemans Date: Wed, 16 Feb 2022 13:06:11 +0100 Subject: [PATCH 0700/1098] Add binary sensor platform to Aseko (#66643) --- .coveragerc | 1 + .../components/aseko_pool_live/__init__.py | 2 +- .../aseko_pool_live/binary_sensor.py | 95 +++++++++++++++++++ 3 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/aseko_pool_live/binary_sensor.py diff --git a/.coveragerc b/.coveragerc index 32cb61a4921b96..a419628d50a649 100644 --- a/.coveragerc +++ b/.coveragerc @@ -77,6 +77,7 @@ omit = homeassistant/components/aruba/device_tracker.py homeassistant/components/arwn/sensor.py homeassistant/components/aseko_pool_live/__init__.py + homeassistant/components/aseko_pool_live/binary_sensor.py homeassistant/components/aseko_pool_live/entity.py homeassistant/components/aseko_pool_live/sensor.py homeassistant/components/asterisk_cdr/mailbox.py diff --git a/homeassistant/components/aseko_pool_live/__init__.py b/homeassistant/components/aseko_pool_live/__init__.py index 697157bd866867..213d0dabc91a9e 100644 --- a/homeassistant/components/aseko_pool_live/__init__.py +++ b/homeassistant/components/aseko_pool_live/__init__.py @@ -17,7 +17,7 @@ _LOGGER = logging.getLogger(__name__) -PLATFORMS: list[str] = [Platform.SENSOR] +PLATFORMS: list[str] = [Platform.BINARY_SENSOR, Platform.SENSOR] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/aseko_pool_live/binary_sensor.py b/homeassistant/components/aseko_pool_live/binary_sensor.py new file mode 100644 index 00000000000000..f67ea58bfc4d24 --- /dev/null +++ b/homeassistant/components/aseko_pool_live/binary_sensor.py @@ -0,0 +1,95 @@ +"""Support for Aseko Pool Live binary sensors.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass + +from aioaseko import Unit + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, + BinarySensorEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import AsekoDataUpdateCoordinator +from .const import DOMAIN +from .entity import AsekoEntity + + +@dataclass +class AsekoBinarySensorDescriptionMixin: + """Mixin for required keys.""" + + value_fn: Callable[[Unit], bool] + + +@dataclass +class AsekoBinarySensorEntityDescription( + BinarySensorEntityDescription, AsekoBinarySensorDescriptionMixin +): + """Describes a Aseko binary sensor entity.""" + + +UNIT_BINARY_SENSORS: tuple[AsekoBinarySensorEntityDescription, ...] = ( + AsekoBinarySensorEntityDescription( + key="water_flow", + name="Water Flow", + icon="mdi:waves-arrow-right", + value_fn=lambda unit: unit.water_flow, + ), + AsekoBinarySensorEntityDescription( + key="has_alarm", + name="Alarm", + value_fn=lambda unit: unit.has_alarm, + device_class=BinarySensorDeviceClass.SAFETY, + ), + AsekoBinarySensorEntityDescription( + key="has_error", + name="Error", + value_fn=lambda unit: unit.has_error, + device_class=BinarySensorDeviceClass.PROBLEM, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the Aseko Pool Live binary sensors.""" + data: list[tuple[Unit, AsekoDataUpdateCoordinator]] = hass.data[DOMAIN][ + config_entry.entry_id + ] + entities: list[BinarySensorEntity] = [] + for unit, coordinator in data: + for description in UNIT_BINARY_SENSORS: + entities.append(AsekoUnitBinarySensorEntity(unit, coordinator, description)) + async_add_entities(entities) + + +class AsekoUnitBinarySensorEntity(AsekoEntity, BinarySensorEntity): + """Representation of a unit water flow binary sensor entity.""" + + entity_description: AsekoBinarySensorEntityDescription + + def __init__( + self, + unit: Unit, + coordinator: AsekoDataUpdateCoordinator, + entity_description: AsekoBinarySensorEntityDescription, + ) -> None: + """Initialize the unit binary sensor.""" + super().__init__(unit, coordinator) + self.entity_description = entity_description + self._attr_name = f"{self._device_name} {entity_description.name}" + self._attr_unique_id = f"{self._unit.serial_number}_{entity_description.key}" + + @property + def is_on(self) -> bool: + """Return the state of the sensor.""" + return self.entity_description.value_fn(self._unit) From 4051e2f518964dbdbbf326c984a656e0ca188fbf Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 16 Feb 2022 13:22:21 +0100 Subject: [PATCH 0701/1098] Fix Plugwise auto HVAC mode (#66639) * Fix Plugwise auto hvac mode * Clean up set HVAC --- homeassistant/components/plugwise/climate.py | 13 +++---------- tests/components/plugwise/test_climate.py | 2 +- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index a3354007868b0b..abf08b70dadb55 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -140,20 +140,13 @@ async def async_set_temperature(self, **kwargs: Any) -> None: @plugwise_command async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set the hvac mode.""" - if hvac_mode == HVAC_MODE_AUTO: - if ( - schedule_temperature := self.device.get("schedule_temperature") - ) is None: - raise ValueError("Cannot set HVAC mode to Auto: No schedule available") - - await self.coordinator.api.set_temperature( - self.device["location"], schedule_temperature - ) + if hvac_mode == HVAC_MODE_AUTO and not self.device.get("schedule_temperature"): + raise ValueError("Cannot set HVAC mode to Auto: No schedule available") await self.coordinator.api.set_schedule_state( self.device["location"], self.device.get("last_used"), - "true" if hvac_mode == HVAC_MODE_AUTO else "false", + "on" if hvac_mode == HVAC_MODE_AUTO else "off", ) @plugwise_command diff --git a/tests/components/plugwise/test_climate.py b/tests/components/plugwise/test_climate.py index 6f9a258bb381f2..c40ad32c078020 100644 --- a/tests/components/plugwise/test_climate.py +++ b/tests/components/plugwise/test_climate.py @@ -206,7 +206,7 @@ async def test_anna_climate_entity_climate_changes( assert mock_smile_anna.set_temperature.call_count == 1 assert mock_smile_anna.set_schedule_state.call_count == 1 mock_smile_anna.set_schedule_state.assert_called_with( - "c784ee9fdab44e1395b8dee7d7a497d5", None, "false" + "c784ee9fdab44e1395b8dee7d7a497d5", None, "off" ) # Auto mode is not available, no schedules From dbc445c2fa1feb2abaa7bd40c13196665ce468eb Mon Sep 17 00:00:00 2001 From: Sascha Sander Date: Wed, 16 Feb 2022 14:33:08 +0100 Subject: [PATCH 0702/1098] Fix scaling of numeric Tuya values (#66644) Co-authored-by: Franck Nijhof --- homeassistant/components/tuya/base.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/tuya/base.py b/homeassistant/components/tuya/base.py index 22764f810805a0..553b3cc95903d8 100644 --- a/homeassistant/components/tuya/base.py +++ b/homeassistant/components/tuya/base.py @@ -41,15 +41,15 @@ def min_scaled(self) -> float: @property def step_scaled(self) -> float: """Return the step scaled.""" - return self.scale_value(self.step) + return self.step / (10**self.scale) def scale_value(self, value: float | int) -> float: """Scale a value.""" - return value * 1.0 / (10**self.scale) + return value * self.step / (10**self.scale) def scale_value_back(self, value: float | int) -> int: """Return raw value for scaled.""" - return int(value * (10**self.scale)) + return int((value * (10**self.scale)) / self.step) def remap_value_to( self, @@ -82,7 +82,7 @@ def from_json(cls, dpcode: DPCode, data: str) -> IntegerTypeData | None: min=int(parsed["min"]), max=int(parsed["max"]), scale=float(parsed["scale"]), - step=float(parsed["step"]), + step=max(float(parsed["step"]), 1), unit=parsed.get("unit"), type=parsed.get("type"), ) From 0bd0b4766e8221584a74bffc7c2f0430c23169df Mon Sep 17 00:00:00 2001 From: Mike Fugate Date: Wed, 16 Feb 2022 09:51:29 -0500 Subject: [PATCH 0703/1098] Refactor sleepiq as async with config flow (#64850) Co-authored-by: J. Nick Koston --- .strict-typing | 1 + CODEOWNERS | 2 + homeassistant/components/sleepiq/__init__.py | 118 ++++++----------- .../components/sleepiq/binary_sensor.py | 78 +++++------- .../components/sleepiq/config_flow.py | 85 +++++++++++++ homeassistant/components/sleepiq/const.py | 5 + .../components/sleepiq/coordinator.py | 40 ++++++ homeassistant/components/sleepiq/entity.py | 43 +++++++ .../components/sleepiq/manifest.json | 6 +- homeassistant/components/sleepiq/sensor.py | 79 +++++------- homeassistant/components/sleepiq/strings.json | 19 +++ .../components/sleepiq/translations/en.json | 19 +++ homeassistant/generated/config_flows.py | 1 + homeassistant/generated/dhcp.py | 1 + mypy.ini | 11 ++ tests/components/sleepiq/conftest.py | 75 +++++++++++ .../sleepiq/fixtures/bed-single.json | 27 ++++ tests/components/sleepiq/fixtures/bed.json | 27 ++++ .../sleepiq/fixtures/familystatus-single.json | 17 +++ .../sleepiq/fixtures/familystatus.json | 24 ++++ tests/components/sleepiq/fixtures/login.json | 7 ++ .../components/sleepiq/fixtures/sleeper.json | 54 ++++++++ .../components/sleepiq/test_binary_sensor.py | 77 +++++------- tests/components/sleepiq/test_config_flow.py | 80 ++++++++++++ tests/components/sleepiq/test_init.py | 119 ++++++++---------- tests/components/sleepiq/test_sensor.py | 81 +++++------- tests/fixtures/sleepiq-bed-single.json | 27 ---- tests/fixtures/sleepiq-bed.json | 28 ----- .../fixtures/sleepiq-familystatus-single.json | 17 --- tests/fixtures/sleepiq-familystatus.json | 24 ---- tests/fixtures/sleepiq-login-failed.json | 1 - tests/fixtures/sleepiq-login.json | 7 -- tests/fixtures/sleepiq-sleeper.json | 55 -------- 33 files changed, 765 insertions(+), 490 deletions(-) create mode 100644 homeassistant/components/sleepiq/config_flow.py create mode 100644 homeassistant/components/sleepiq/coordinator.py create mode 100644 homeassistant/components/sleepiq/entity.py create mode 100644 homeassistant/components/sleepiq/strings.json create mode 100644 homeassistant/components/sleepiq/translations/en.json create mode 100644 tests/components/sleepiq/conftest.py create mode 100644 tests/components/sleepiq/fixtures/bed-single.json create mode 100644 tests/components/sleepiq/fixtures/bed.json create mode 100644 tests/components/sleepiq/fixtures/familystatus-single.json create mode 100644 tests/components/sleepiq/fixtures/familystatus.json create mode 100644 tests/components/sleepiq/fixtures/login.json create mode 100644 tests/components/sleepiq/fixtures/sleeper.json create mode 100644 tests/components/sleepiq/test_config_flow.py delete mode 100644 tests/fixtures/sleepiq-bed-single.json delete mode 100644 tests/fixtures/sleepiq-bed.json delete mode 100644 tests/fixtures/sleepiq-familystatus-single.json delete mode 100644 tests/fixtures/sleepiq-familystatus.json delete mode 100644 tests/fixtures/sleepiq-login-failed.json delete mode 100644 tests/fixtures/sleepiq-login.json delete mode 100644 tests/fixtures/sleepiq-sleeper.json diff --git a/.strict-typing b/.strict-typing index afd8c0fb93eb69..bd8553359cf483 100644 --- a/.strict-typing +++ b/.strict-typing @@ -166,6 +166,7 @@ homeassistant.components.senseme.* homeassistant.components.shelly.* homeassistant.components.simplisafe.* homeassistant.components.slack.* +homeassistant.components.sleepiq.* homeassistant.components.smhi.* homeassistant.components.ssdp.* homeassistant.components.stookalert.* diff --git a/CODEOWNERS b/CODEOWNERS index ceefad2bad42a5..c0dd609233afc9 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -850,6 +850,8 @@ homeassistant/components/sisyphus/* @jkeljo homeassistant/components/sky_hub/* @rogerselwyn homeassistant/components/slack/* @bachya tests/components/slack/* @bachya +homeassistant/components/sleepiq/* @mfugate1 +tests/components/sleepiq/* @mfugate1 homeassistant/components/slide/* @ualex73 homeassistant/components/sma/* @kellerza @rklomp tests/components/sma/* @kellerza @rklomp diff --git a/homeassistant/components/sleepiq/__init__.py b/homeassistant/components/sleepiq/__init__.py index f6ba5a0393f2f9..5a69cfacd1158a 100644 --- a/homeassistant/components/sleepiq/__init__.py +++ b/homeassistant/components/sleepiq/__init__.py @@ -1,115 +1,73 @@ """Support for SleepIQ from SleepNumber.""" -from datetime import timedelta import logging from sleepyq import Sleepyq import voluptuous as vol +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType -from homeassistant.util import Throttle from .const import DOMAIN - -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) +from .coordinator import SleepIQDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema( { - vol.Required(DOMAIN): vol.Schema( - { - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - } - ) + DOMAIN: { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + } }, extra=vol.ALLOW_EXTRA, ) -def setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up the SleepIQ component. +PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] - Will automatically load sensor components to support - devices discovered on the account. - """ - username = config[DOMAIN][CONF_USERNAME] - password = config[DOMAIN][CONF_PASSWORD] - client = Sleepyq(username, password) - try: - data = SleepIQData(client) - data.update() - except ValueError: - message = """ - SleepIQ failed to login, double check your username and password" - """ - _LOGGER.error(message) - return False - hass.data[DOMAIN] = data - discovery.load_platform(hass, Platform.SENSOR, DOMAIN, {}, config) - discovery.load_platform(hass, Platform.BINARY_SENSOR, DOMAIN, {}, config) +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up sleepiq component.""" + if DOMAIN in config: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=config[DOMAIN] + ) + ) return True -class SleepIQData: - """Get the latest data from SleepIQ.""" - - def __init__(self, client): - """Initialize the data object.""" - self._client = client - self.beds = {} - - self.update() - - @Throttle(MIN_TIME_BETWEEN_UPDATES) - def update(self): - """Get the latest data from SleepIQ.""" - self._client.login() - beds = self._client.beds_with_sleeper_status() - - self.beds = {bed.bed_id: bed for bed in beds} - +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up the SleepIQ config entry.""" + client = Sleepyq(entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD]) + try: + await hass.async_add_executor_job(client.login) + except ValueError: + _LOGGER.error("SleepIQ login failed, double check your username and password") + return False -class SleepIQSensor(Entity): - """Implementation of a SleepIQ sensor.""" + coordinator = SleepIQDataUpdateCoordinator( + hass, + client=client, + username=entry.data[CONF_USERNAME], + ) - def __init__(self, sleepiq_data, bed_id, side): - """Initialize the sensor.""" - self._bed_id = bed_id - self._side = side - self.sleepiq_data = sleepiq_data - self.side = None - self.bed = None + # Call the SleepIQ API to refresh data + await coordinator.async_config_entry_first_refresh() - # added by subclass - self._name = None - self.type = None + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator - @property - def name(self): - """Return the name of the sensor.""" - return "SleepNumber {} {} {}".format( - self.bed.name, self.side.sleeper.first_name, self._name - ) + hass.config_entries.async_setup_platforms(entry, PLATFORMS) - @property - def unique_id(self): - """Return a unique ID for the bed.""" - return f"{self._bed_id}-{self._side}-{self.type}" + return True - def update(self): - """Get the latest data from SleepIQ and updates the states.""" - # Call the API for new sleepiq data. Each sensor will re-trigger this - # same exact call, but that's fine. We cache results for a short period - # of time to prevent hitting API limits. - self.sleepiq_data.update() - self.bed = self.sleepiq_data.beds[self._bed_id] - self.side = getattr(self.bed, self._side) +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload the config entry.""" + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + hass.data[DOMAIN].pop(entry.entry_id) + return unload_ok diff --git a/homeassistant/components/sleepiq/binary_sensor.py b/homeassistant/components/sleepiq/binary_sensor.py index f821a569254bf3..890cd6711a6b14 100644 --- a/homeassistant/components/sleepiq/binary_sensor.py +++ b/homeassistant/components/sleepiq/binary_sensor.py @@ -1,61 +1,49 @@ """Support for SleepIQ sensors.""" -from __future__ import annotations - from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, ) -from homeassistant.core import HomeAssistant +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import SleepIQSensor -from .const import DOMAIN, IS_IN_BED, SENSOR_TYPES, SIDES +from .const import BED, DOMAIN, ICON_EMPTY, ICON_OCCUPIED, IS_IN_BED, SIDES +from .coordinator import SleepIQDataUpdateCoordinator +from .entity import SleepIQSensor -def setup_platform( +async def async_setup_entry( hass: HomeAssistant, - config: ConfigType, - add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, ) -> None: - """Set up the SleepIQ sensors.""" - if discovery_info is None: - return - - data = hass.data[DOMAIN] - data.update() - - dev = [] - for bed_id, bed in data.beds.items(): - for side in SIDES: - if getattr(bed, side) is not None: - dev.append(IsInBedBinarySensor(data, bed_id, side)) - add_entities(dev) + """Set up the SleepIQ bed binary sensors.""" + coordinator: SleepIQDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + IsInBedBinarySensor(coordinator, bed_id, side) + for side in SIDES + for bed_id in coordinator.data + if getattr(coordinator.data[bed_id][BED], side) is not None + ) class IsInBedBinarySensor(SleepIQSensor, BinarySensorEntity): """Implementation of a SleepIQ presence sensor.""" - def __init__(self, sleepiq_data, bed_id, side): - """Initialize the sensor.""" - super().__init__(sleepiq_data, bed_id, side) - self._state = None - self.type = IS_IN_BED - self._name = SENSOR_TYPES[self.type] - self.update() - - @property - def is_on(self): - """Return the status of the sensor.""" - return self._state is True - - @property - def device_class(self) -> BinarySensorDeviceClass: - """Return the class of this sensor.""" - return BinarySensorDeviceClass.OCCUPANCY - - def update(self): - """Get the latest data from SleepIQ and updates the states.""" - super().update() - self._state = self.side.is_in_bed + _attr_device_class = BinarySensorDeviceClass.OCCUPANCY + + def __init__( + self, + coordinator: SleepIQDataUpdateCoordinator, + bed_id: str, + side: str, + ) -> None: + """Initialize the SleepIQ bed side binary sensor.""" + super().__init__(coordinator, bed_id, side, IS_IN_BED) + + @callback + def _async_update_attrs(self) -> None: + """Update sensor attributes.""" + super()._async_update_attrs() + self._attr_is_on = getattr(self.side_data, IS_IN_BED) + self._attr_icon = ICON_OCCUPIED if self.is_on else ICON_EMPTY diff --git a/homeassistant/components/sleepiq/config_flow.py b/homeassistant/components/sleepiq/config_flow.py new file mode 100644 index 00000000000000..aff4d7e8dc7417 --- /dev/null +++ b/homeassistant/components/sleepiq/config_flow.py @@ -0,0 +1,85 @@ +"""Config flow to configure SleepIQ component.""" +from __future__ import annotations + +from typing import Any + +from sleepyq import Sleepyq +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.data_entry_flow import FlowResult + +from .const import DOMAIN, SLEEPYQ_INVALID_CREDENTIALS_MESSAGE + + +class SleepIQFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a SleepIQ config flow.""" + + VERSION = 1 + + async def async_step_import(self, import_config: dict[str, Any]) -> FlowResult: + """Import a SleepIQ account as a config entry. + + This flow is triggered by 'async_setup' for configured accounts. + """ + await self.async_set_unique_id(import_config[CONF_USERNAME].lower()) + self._abort_if_unique_id_configured() + + return self.async_create_entry( + title=import_config[CONF_USERNAME], data=import_config + ) + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle a flow initialized by the user.""" + errors = {} + + if user_input is not None: + # Don't allow multiple instances with the same username + await self.async_set_unique_id(user_input[CONF_USERNAME].lower()) + self._abort_if_unique_id_configured() + + login_error = await self.hass.async_add_executor_job( + try_connection, user_input + ) + if not login_error: + return self.async_create_entry( + title=user_input[CONF_USERNAME], data=user_input + ) + + if SLEEPYQ_INVALID_CREDENTIALS_MESSAGE in login_error: + errors["base"] = "invalid_auth" + else: + errors["base"] = "cannot_connect" + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required( + CONF_USERNAME, + default=user_input.get(CONF_USERNAME) + if user_input is not None + else "", + ): str, + vol.Required(CONF_PASSWORD): str, + } + ), + errors=errors, + last_step=True, + ) + + +def try_connection(user_input: dict[str, Any]) -> str: + """Test if the given credentials can successfully login to SleepIQ.""" + + client = Sleepyq(user_input[CONF_USERNAME], user_input[CONF_PASSWORD]) + + try: + client.login() + except ValueError as error: + return str(error) + + return "" diff --git a/homeassistant/components/sleepiq/const.py b/homeassistant/components/sleepiq/const.py index 64f508167e1861..3fc0ae999fdc61 100644 --- a/homeassistant/components/sleepiq/const.py +++ b/homeassistant/components/sleepiq/const.py @@ -1,7 +1,12 @@ """Define constants for the SleepIQ component.""" +DATA_SLEEPIQ = "data_sleepiq" DOMAIN = "sleepiq" +SLEEPYQ_INVALID_CREDENTIALS_MESSAGE = "username or password" +BED = "bed" +ICON_EMPTY = "mdi:bed-empty" +ICON_OCCUPIED = "mdi:bed" IS_IN_BED = "is_in_bed" SLEEP_NUMBER = "sleep_number" SENSOR_TYPES = {SLEEP_NUMBER: "SleepNumber", IS_IN_BED: "Is In Bed"} diff --git a/homeassistant/components/sleepiq/coordinator.py b/homeassistant/components/sleepiq/coordinator.py new file mode 100644 index 00000000000000..467238e907ea03 --- /dev/null +++ b/homeassistant/components/sleepiq/coordinator.py @@ -0,0 +1,40 @@ +"""Coordinator for SleepIQ.""" +from datetime import timedelta +import logging + +from sleepyq import Sleepyq + +from homeassistant.core import HomeAssistant +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + +from .const import BED + +_LOGGER = logging.getLogger(__name__) + +UPDATE_INTERVAL = timedelta(seconds=60) + + +class SleepIQDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict]]): + """SleepIQ data update coordinator.""" + + def __init__( + self, + hass: HomeAssistant, + *, + client: Sleepyq, + username: str, + ) -> None: + """Initialize coordinator.""" + super().__init__( + hass, _LOGGER, name=f"{username}@SleepIQ", update_interval=UPDATE_INTERVAL + ) + self.client = client + + async def _async_update_data(self) -> dict[str, dict]: + return await self.hass.async_add_executor_job(self.update_data) + + def update_data(self) -> dict[str, dict]: + """Get latest data from the client.""" + return { + bed.bed_id: {BED: bed} for bed in self.client.beds_with_sleeper_status() + } diff --git a/homeassistant/components/sleepiq/entity.py b/homeassistant/components/sleepiq/entity.py new file mode 100644 index 00000000000000..350435573f198e --- /dev/null +++ b/homeassistant/components/sleepiq/entity.py @@ -0,0 +1,43 @@ +"""Entity for the SleepIQ integration.""" +from homeassistant.core import callback +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import BED, ICON_OCCUPIED, SENSOR_TYPES +from .coordinator import SleepIQDataUpdateCoordinator + + +class SleepIQSensor(CoordinatorEntity): + """Implementation of a SleepIQ sensor.""" + + _attr_icon = ICON_OCCUPIED + + def __init__( + self, + coordinator: SleepIQDataUpdateCoordinator, + bed_id: str, + side: str, + name: str, + ) -> None: + """Initialize the SleepIQ side entity.""" + super().__init__(coordinator) + self.bed_id = bed_id + self.side = side + + self._async_update_attrs() + + self._attr_name = f"SleepNumber {self.bed_data.name} {self.side_data.sleeper.first_name} {SENSOR_TYPES[name]}" + self._attr_unique_id = ( + f"{self.bed_id}_{self.side_data.sleeper.first_name}_{name}" + ) + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self._async_update_attrs() + super()._handle_coordinator_update() + + @callback + def _async_update_attrs(self) -> None: + """Update sensor attributes.""" + self.bed_data = self.coordinator.data[self.bed_id][BED] + self.side_data = getattr(self.bed_data, self.side) diff --git a/homeassistant/components/sleepiq/manifest.json b/homeassistant/components/sleepiq/manifest.json index ac734393197634..a516bd7545b5c9 100644 --- a/homeassistant/components/sleepiq/manifest.json +++ b/homeassistant/components/sleepiq/manifest.json @@ -1,9 +1,13 @@ { "domain": "sleepiq", "name": "SleepIQ", + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sleepiq", "requirements": ["sleepyq==0.8.1"], - "codeowners": [], + "codeowners": ["@mfugate1"], + "dhcp": [ + {"macaddress": "64DBA0*"} + ], "iot_class": "cloud_polling", "loggers": ["sleepyq"] } diff --git a/homeassistant/components/sleepiq/sensor.py b/homeassistant/components/sleepiq/sensor.py index 523014cf8cf284..52ded76762dff7 100644 --- a/homeassistant/components/sleepiq/sensor.py +++ b/homeassistant/components/sleepiq/sensor.py @@ -1,62 +1,43 @@ """Support for SleepIQ sensors.""" -from __future__ import annotations - from homeassistant.components.sensor import SensorEntity -from homeassistant.core import HomeAssistant +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType - -from . import SleepIQSensor -from .const import DOMAIN, SENSOR_TYPES, SIDES, SLEEP_NUMBER -ICON = "mdi:bed" +from .const import BED, DOMAIN, SIDES, SLEEP_NUMBER +from .coordinator import SleepIQDataUpdateCoordinator +from .entity import SleepIQSensor -def setup_platform( +async def async_setup_entry( hass: HomeAssistant, - config: ConfigType, - add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, ) -> None: - """Set up the SleepIQ sensors.""" - if discovery_info is None: - return - - data = hass.data[DOMAIN] - data.update() - - dev = [] - for bed_id, bed in data.beds.items(): - for side in SIDES: - if getattr(bed, side) is not None: - dev.append(SleepNumberSensor(data, bed_id, side)) - add_entities(dev) + """Set up the SleepIQ bed sensors.""" + coordinator: SleepIQDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + SleepNumberSensor(coordinator, bed_id, side) + for side in SIDES + for bed_id in coordinator.data + if getattr(coordinator.data[bed_id][BED], side) is not None + ) class SleepNumberSensor(SleepIQSensor, SensorEntity): """Implementation of a SleepIQ sensor.""" - def __init__(self, sleepiq_data, bed_id, side): - """Initialize the sensor.""" - SleepIQSensor.__init__(self, sleepiq_data, bed_id, side) - - self._state = None - self.type = SLEEP_NUMBER - self._name = SENSOR_TYPES[self.type] - - self.update() - - @property - def native_value(self): - """Return the state of the sensor.""" - return self._state - - @property - def icon(self): - """Icon to use in the frontend, if any.""" - return ICON - - def update(self): - """Get the latest data from SleepIQ and updates the states.""" - SleepIQSensor.update(self) - self._state = self.side.sleep_number + def __init__( + self, + coordinator: SleepIQDataUpdateCoordinator, + bed_id: str, + side: str, + ) -> None: + """Initialize the SleepIQ sleep number sensor.""" + super().__init__(coordinator, bed_id, side, SLEEP_NUMBER) + + @callback + def _async_update_attrs(self) -> None: + """Update sensor attributes.""" + super()._async_update_attrs() + self._attr_native_value = self.side_data.sleep_number diff --git a/homeassistant/components/sleepiq/strings.json b/homeassistant/components/sleepiq/strings.json new file mode 100644 index 00000000000000..21ceead3d0a881 --- /dev/null +++ b/homeassistant/components/sleepiq/strings.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" + }, + "step": { + "user": { + "data": { + "password": "[%key:common::config_flow::data::password%]", + "username": "[%key:common::config_flow::data::username%]" + } + } + } + } +} diff --git a/homeassistant/components/sleepiq/translations/en.json b/homeassistant/components/sleepiq/translations/en.json new file mode 100644 index 00000000000000..31de29c8690af4 --- /dev/null +++ b/homeassistant/components/sleepiq/translations/en.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Account is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication" + }, + "step": { + "user": { + "data": { + "password": "Password", + "username": "Username" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index be18826bedba45..72037428e684e0 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -286,6 +286,7 @@ "shopping_list", "sia", "simplisafe", + "sleepiq", "sma", "smappee", "smart_meter_texas", diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 11c1331a72d568..8809dd45f4a3e8 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -88,6 +88,7 @@ {'domain': 'senseme', 'macaddress': '20F85E*'}, {'domain': 'sensibo', 'hostname': 'sensibo*'}, {'domain': 'simplisafe', 'hostname': 'simplisafe*', 'macaddress': '30AEA4*'}, + {'domain': 'sleepiq', 'macaddress': '64DBA0*'}, {'domain': 'smartthings', 'hostname': 'st*', 'macaddress': '24FD5B*'}, {'domain': 'smartthings', 'hostname': 'smartthings*', 'macaddress': '24FD5B*'}, {'domain': 'smartthings', 'hostname': 'hub*', 'macaddress': '24FD5B*'}, diff --git a/mypy.ini b/mypy.ini index 18d951cbe306d8..6187f296ec2b6d 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1635,6 +1635,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.sleepiq.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.smhi.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/tests/components/sleepiq/conftest.py b/tests/components/sleepiq/conftest.py new file mode 100644 index 00000000000000..707fc436c15b53 --- /dev/null +++ b/tests/components/sleepiq/conftest.py @@ -0,0 +1,75 @@ +"""Common fixtures for sleepiq tests.""" +import json +from unittest.mock import patch + +import pytest +from sleepyq import Bed, FamilyStatus, Sleeper + +from homeassistant.components.sleepiq.const import DOMAIN +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME + +from tests.common import MockConfigEntry, load_fixture + + +def mock_beds(account_type): + """Mock sleepnumber bed data.""" + return [ + Bed(bed) + for bed in json.loads(load_fixture(f"bed{account_type}.json", "sleepiq"))[ + "beds" + ] + ] + + +def mock_sleepers(): + """Mock sleeper data.""" + return [ + Sleeper(sleeper) + for sleeper in json.loads(load_fixture("sleeper.json", "sleepiq"))["sleepers"] + ] + + +def mock_bed_family_status(account_type): + """Mock family status data.""" + return [ + FamilyStatus(status) + for status in json.loads( + load_fixture(f"familystatus{account_type}.json", "sleepiq") + )["beds"] + ] + + +@pytest.fixture +def config_data(): + """Provide configuration data for tests.""" + return { + CONF_USERNAME: "username", + CONF_PASSWORD: "password", + } + + +@pytest.fixture +def config_entry(config_data): + """Create a mock config entry.""" + return MockConfigEntry( + domain=DOMAIN, + data=config_data, + options={}, + ) + + +@pytest.fixture(params=["-single", ""]) +async def setup_entry(hass, request, config_entry): + """Initialize the config entry.""" + with patch("sleepyq.Sleepyq.beds", return_value=mock_beds(request.param)), patch( + "sleepyq.Sleepyq.sleepers", return_value=mock_sleepers() + ), patch( + "sleepyq.Sleepyq.bed_family_status", + return_value=mock_bed_family_status(request.param), + ), patch( + "sleepyq.Sleepyq.login" + ): + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + return {"account_type": request.param, "mock_entry": config_entry} diff --git a/tests/components/sleepiq/fixtures/bed-single.json b/tests/components/sleepiq/fixtures/bed-single.json new file mode 100644 index 00000000000000..f1e59f5ad2d9ed --- /dev/null +++ b/tests/components/sleepiq/fixtures/bed-single.json @@ -0,0 +1,27 @@ +{ + "beds" : [ + { + "dualSleep" : false, + "base" : "FlexFit", + "sku" : "AILE", + "model" : "ILE", + "size" : "KING", + "isKidsBed" : false, + "sleeperRightId" : "-80", + "accountId" : "-32", + "bedId" : "-31", + "registrationDate" : "2016-07-22T14:00:58Z", + "serial" : null, + "reference" : "95000794555-1", + "macAddress" : "CD13A384BA51", + "version" : null, + "purchaseDate" : "2016-06-22T00:00:00Z", + "sleeperLeftId" : "0", + "zipcode" : "12345", + "returnRequestStatus" : 0, + "name" : "ILE", + "status" : 1, + "timezone" : "US/Eastern" + } + ] + } \ No newline at end of file diff --git a/tests/components/sleepiq/fixtures/bed.json b/tests/components/sleepiq/fixtures/bed.json new file mode 100644 index 00000000000000..5fb12da05070bc --- /dev/null +++ b/tests/components/sleepiq/fixtures/bed.json @@ -0,0 +1,27 @@ +{ + "beds" : [ + { + "dualSleep" : true, + "base" : "FlexFit", + "sku" : "AILE", + "model" : "ILE", + "size" : "KING", + "isKidsBed" : false, + "sleeperRightId" : "-80", + "accountId" : "-32", + "bedId" : "-31", + "registrationDate" : "2016-07-22T14:00:58Z", + "serial" : null, + "reference" : "95000794555-1", + "macAddress" : "CD13A384BA51", + "version" : null, + "purchaseDate" : "2016-06-22T00:00:00Z", + "sleeperLeftId" : "-92", + "zipcode" : "12345", + "returnRequestStatus" : 0, + "name" : "ILE", + "status" : 1, + "timezone" : "US/Eastern" + } + ] + } diff --git a/tests/components/sleepiq/fixtures/familystatus-single.json b/tests/components/sleepiq/fixtures/familystatus-single.json new file mode 100644 index 00000000000000..1d5c0d89943a3a --- /dev/null +++ b/tests/components/sleepiq/fixtures/familystatus-single.json @@ -0,0 +1,17 @@ +{ + "beds" : [ + { + "bedId" : "-31", + "rightSide" : { + "alertId" : 0, + "lastLink" : "00:00:00", + "isInBed" : true, + "sleepNumber" : 40, + "alertDetailedMessage" : "No Alert", + "pressure" : -16 + }, + "status" : 1, + "leftSide" : null + } + ] + } \ No newline at end of file diff --git a/tests/components/sleepiq/fixtures/familystatus.json b/tests/components/sleepiq/fixtures/familystatus.json new file mode 100644 index 00000000000000..c9b6082411503f --- /dev/null +++ b/tests/components/sleepiq/fixtures/familystatus.json @@ -0,0 +1,24 @@ +{ + "beds" : [ + { + "bedId" : "-31", + "rightSide" : { + "alertId" : 0, + "lastLink" : "00:00:00", + "isInBed" : true, + "sleepNumber" : 40, + "alertDetailedMessage" : "No Alert", + "pressure" : -16 + }, + "status" : 1, + "leftSide" : { + "alertId" : 0, + "lastLink" : "00:00:00", + "sleepNumber" : 80, + "alertDetailedMessage" : "No Alert", + "isInBed" : false, + "pressure" : 2191 + } + } + ] + } \ No newline at end of file diff --git a/tests/components/sleepiq/fixtures/login.json b/tests/components/sleepiq/fixtures/login.json new file mode 100644 index 00000000000000..a665db7de29775 --- /dev/null +++ b/tests/components/sleepiq/fixtures/login.json @@ -0,0 +1,7 @@ +{ + "edpLoginStatus" : 200, + "userId" : "-42", + "registrationState" : 13, + "key" : "0987", + "edpLoginMessage" : "not used" + } \ No newline at end of file diff --git a/tests/components/sleepiq/fixtures/sleeper.json b/tests/components/sleepiq/fixtures/sleeper.json new file mode 100644 index 00000000000000..c009e6842209a4 --- /dev/null +++ b/tests/components/sleepiq/fixtures/sleeper.json @@ -0,0 +1,54 @@ +{ + "sleepers" : [ + { + "timezone" : "US/Eastern", + "firstName" : "Test1", + "weight" : 150, + "birthMonth" : 12, + "birthYear" : "1990", + "active" : true, + "lastLogin" : "2016-08-26 21:43:27 CDT", + "side" : 1, + "accountId" : "-32", + "height" : 60, + "bedId" : "-31", + "username" : "test1@example.com", + "sleeperId" : "-80", + "avatar" : "", + "emailValidated" : true, + "licenseVersion" : 6, + "duration" : null, + "email" : "test1@example.com", + "isAccountOwner" : true, + "sleepGoal" : 480, + "zipCode" : "12345", + "isChild" : false, + "isMale" : true + }, + { + "email" : "test2@example.com", + "duration" : null, + "emailValidated" : true, + "licenseVersion" : 5, + "isChild" : false, + "isMale" : false, + "zipCode" : "12345", + "isAccountOwner" : false, + "sleepGoal" : 480, + "side" : 0, + "lastLogin" : "2016-07-17 15:37:30 CDT", + "birthMonth" : 1, + "birthYear" : "1991", + "active" : true, + "weight" : 151, + "firstName" : "Test2", + "timezone" : "US/Eastern", + "avatar" : "", + "username" : "test2@example.com", + "sleeperId" : "-92", + "bedId" : "-31", + "height" : 65, + "accountId" : "-32" + } + ] + } diff --git a/tests/components/sleepiq/test_binary_sensor.py b/tests/components/sleepiq/test_binary_sensor.py index 9b4092d9d48370..ca9bf3c84fcdc1 100644 --- a/tests/components/sleepiq/test_binary_sensor.py +++ b/tests/components/sleepiq/test_binary_sensor.py @@ -1,45 +1,34 @@ """The tests for SleepIQ binary sensor platform.""" -from unittest.mock import MagicMock - -from homeassistant.components.sleepiq import binary_sensor as sleepiq -from homeassistant.setup import async_setup_component - -from tests.components.sleepiq.test_init import mock_responses - -CONFIG = {"username": "foo", "password": "bar"} - - -async def test_sensor_setup(hass, requests_mock): - """Test for successfully setting up the SleepIQ platform.""" - mock_responses(requests_mock) - - await async_setup_component(hass, "sleepiq", {"sleepiq": CONFIG}) - - device_mock = MagicMock() - sleepiq.setup_platform(hass, CONFIG, device_mock, MagicMock()) - devices = device_mock.call_args[0][0] - assert len(devices) == 2 - - left_side = devices[1] - assert left_side.name == "SleepNumber ILE Test1 Is In Bed" - assert left_side.state == "on" - - right_side = devices[0] - assert right_side.name == "SleepNumber ILE Test2 Is In Bed" - assert right_side.state == "off" - - -async def test_setup_single(hass, requests_mock): - """Test for successfully setting up the SleepIQ platform.""" - mock_responses(requests_mock, single=True) - - await async_setup_component(hass, "sleepiq", {"sleepiq": CONFIG}) - - device_mock = MagicMock() - sleepiq.setup_platform(hass, CONFIG, device_mock, MagicMock()) - devices = device_mock.call_args[0][0] - assert len(devices) == 1 - - right_side = devices[0] - assert right_side.name == "SleepNumber ILE Test1 Is In Bed" - assert right_side.state == "on" +from homeassistant.components.binary_sensor import BinarySensorDeviceClass +from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_FRIENDLY_NAME, ATTR_ICON +from homeassistant.helpers import entity_registry as er + + +async def test_binary_sensors(hass, setup_entry): + """Test the SleepIQ binary sensors.""" + entity_registry = er.async_get(hass) + + state = hass.states.get("binary_sensor.sleepnumber_ile_test1_is_in_bed") + assert state.state == "on" + assert state.attributes.get(ATTR_ICON) == "mdi:bed" + assert state.attributes.get(ATTR_DEVICE_CLASS) == BinarySensorDeviceClass.OCCUPANCY + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "SleepNumber ILE Test1 Is In Bed" + + entry = entity_registry.async_get("binary_sensor.sleepnumber_ile_test1_is_in_bed") + assert entry + assert entry.unique_id == "-31_Test1_is_in_bed" + + # If account type is set, only a single bed account was created and there will + # not be a second entity + if setup_entry["account_type"]: + return + + entry = entity_registry.async_get("binary_sensor.sleepnumber_ile_test2_is_in_bed") + assert entry + assert entry.unique_id == "-31_Test2_is_in_bed" + + state = hass.states.get("binary_sensor.sleepnumber_ile_test2_is_in_bed") + assert state.state == "off" + assert state.attributes.get(ATTR_ICON) == "mdi:bed-empty" + assert state.attributes.get(ATTR_DEVICE_CLASS) == BinarySensorDeviceClass.OCCUPANCY + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "SleepNumber ILE Test2 Is In Bed" diff --git a/tests/components/sleepiq/test_config_flow.py b/tests/components/sleepiq/test_config_flow.py new file mode 100644 index 00000000000000..e4a422c888fbdf --- /dev/null +++ b/tests/components/sleepiq/test_config_flow.py @@ -0,0 +1,80 @@ +"""Tests for the SleepIQ config flow.""" +from unittest.mock import patch + +from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.components.sleepiq.const import ( + DOMAIN, + SLEEPYQ_INVALID_CREDENTIALS_MESSAGE, +) +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant + +SLEEPIQ_CONFIG = { + CONF_USERNAME: "username", + CONF_PASSWORD: "password", +} + + +async def test_import(hass: HomeAssistant) -> None: + """Test that we can import a config entry.""" + with patch("sleepyq.Sleepyq.login"): + assert await setup.async_setup_component(hass, DOMAIN, {DOMAIN: SLEEPIQ_CONFIG}) + await hass.async_block_till_done() + + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + entry = hass.config_entries.async_entries(DOMAIN)[0] + assert entry.data[CONF_USERNAME] == SLEEPIQ_CONFIG[CONF_USERNAME] + assert entry.data[CONF_PASSWORD] == SLEEPIQ_CONFIG[CONF_PASSWORD] + + +async def test_show_set_form(hass: HomeAssistant) -> None: + """Test that the setup form is served.""" + with patch("sleepyq.Sleepyq.login"): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=None + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + + +async def test_login_invalid_auth(hass: HomeAssistant) -> None: + """Test we show user form with appropriate error on login failure.""" + with patch( + "sleepyq.Sleepyq.login", + side_effect=ValueError(SLEEPYQ_INVALID_CREDENTIALS_MESSAGE), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=SLEEPIQ_CONFIG + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["errors"] == {"base": "invalid_auth"} + + +async def test_login_cannot_connect(hass: HomeAssistant) -> None: + """Test we show user form with appropriate error on login failure.""" + with patch( + "sleepyq.Sleepyq.login", + side_effect=ValueError("Unexpected response code"), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=SLEEPIQ_CONFIG + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["errors"] == {"base": "cannot_connect"} + + +async def test_success(hass: HomeAssistant) -> None: + """Test successful flow provides entry creation data.""" + with patch("sleepyq.Sleepyq.login"): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=SLEEPIQ_CONFIG + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"][CONF_USERNAME] == SLEEPIQ_CONFIG[CONF_USERNAME] + assert result["data"][CONF_PASSWORD] == SLEEPIQ_CONFIG[CONF_PASSWORD] diff --git a/tests/components/sleepiq/test_init.py b/tests/components/sleepiq/test_init.py index 68ca876504f915..15af03e14ce932 100644 --- a/tests/components/sleepiq/test_init.py +++ b/tests/components/sleepiq/test_init.py @@ -1,65 +1,54 @@ -"""The tests for the SleepIQ component.""" -from http import HTTPStatus -from unittest.mock import MagicMock, patch - -from homeassistant import setup -import homeassistant.components.sleepiq as sleepiq - -from tests.common import load_fixture - -CONFIG = {"sleepiq": {"username": "foo", "password": "bar"}} - - -def mock_responses(mock, single=False): - """Mock responses for SleepIQ.""" - base_url = "https://prod-api.sleepiq.sleepnumber.com/rest/" - if single: - suffix = "-single" - else: - suffix = "" - mock.put(base_url + "login", text=load_fixture("sleepiq-login.json")) - mock.get(base_url + "bed?_k=0987", text=load_fixture(f"sleepiq-bed{suffix}.json")) - mock.get(base_url + "sleeper?_k=0987", text=load_fixture("sleepiq-sleeper.json")) - mock.get( - base_url + "bed/familyStatus?_k=0987", - text=load_fixture(f"sleepiq-familystatus{suffix}.json"), - ) - - -async def test_setup(hass, requests_mock): - """Test the setup.""" - mock_responses(requests_mock) - - # We're mocking the load_platform discoveries or else the platforms - # will be setup during tear down when blocking till done, but the mocks - # are no longer active. - with patch("homeassistant.helpers.discovery.load_platform", MagicMock()): - assert sleepiq.setup(hass, CONFIG) - - -async def test_setup_login_failed(hass, requests_mock): - """Test the setup if a bad username or password is given.""" - mock_responses(requests_mock) - requests_mock.put( - "https://prod-api.sleepiq.sleepnumber.com/rest/login", - status_code=HTTPStatus.UNAUTHORIZED, - json=load_fixture("sleepiq-login-failed.json"), - ) - - response = sleepiq.setup(hass, CONFIG) - assert not response - - -async def test_setup_component_no_login(hass): - """Test the setup when no login is configured.""" - conf = CONFIG.copy() - del conf["sleepiq"]["username"] - assert not await setup.async_setup_component(hass, sleepiq.DOMAIN, conf) - - -async def test_setup_component_no_password(hass): - """Test the setup when no password is configured.""" - conf = CONFIG.copy() - del conf["sleepiq"]["password"] - - assert not await setup.async_setup_component(hass, sleepiq.DOMAIN, conf) +"""Tests for the SleepIQ integration.""" +from unittest.mock import patch + +from homeassistant.components.sleepiq.const import DOMAIN +from homeassistant.components.sleepiq.coordinator import UPDATE_INTERVAL +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant +from homeassistant.util.dt import utcnow + +from tests.common import async_fire_time_changed +from tests.components.sleepiq.conftest import ( + mock_bed_family_status, + mock_beds, + mock_sleepers, +) + + +async def test_unload_entry(hass: HomeAssistant, setup_entry) -> None: + """Test unloading the SleepIQ entry.""" + entry = setup_entry["mock_entry"] + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state is ConfigEntryState.NOT_LOADED + assert not hass.data.get(DOMAIN) + + +async def test_entry_setup_login_error(hass: HomeAssistant, config_entry) -> None: + """Test when sleepyq client is unable to login.""" + with patch("sleepyq.Sleepyq.login", side_effect=ValueError): + config_entry.add_to_hass(hass) + assert not await hass.config_entries.async_setup(config_entry.entry_id) + + +async def test_update_interval(hass: HomeAssistant, setup_entry) -> None: + """Test update interval.""" + with patch("sleepyq.Sleepyq.beds", return_value=mock_beds("")) as beds, patch( + "sleepyq.Sleepyq.sleepers", return_value=mock_sleepers() + ) as sleepers, patch( + "sleepyq.Sleepyq.bed_family_status", + return_value=mock_bed_family_status(""), + ) as bed_family_status, patch( + "sleepyq.Sleepyq.login", return_value=True + ): + assert beds.call_count == 0 + assert sleepers.call_count == 0 + assert bed_family_status.call_count == 0 + + async_fire_time_changed(hass, utcnow() + UPDATE_INTERVAL) + await hass.async_block_till_done() + + assert beds.call_count == 1 + assert sleepers.call_count == 1 + assert bed_family_status.call_count == 1 diff --git a/tests/components/sleepiq/test_sensor.py b/tests/components/sleepiq/test_sensor.py index 7a7e47f03fa73b..baa8732365b19b 100644 --- a/tests/components/sleepiq/test_sensor.py +++ b/tests/components/sleepiq/test_sensor.py @@ -1,48 +1,35 @@ """The tests for SleepIQ sensor platform.""" -from unittest.mock import MagicMock - -import homeassistant.components.sleepiq.sensor as sleepiq -from homeassistant.setup import async_setup_component - -from tests.components.sleepiq.test_init import mock_responses - -CONFIG = {"username": "foo", "password": "bar"} - - -async def test_setup(hass, requests_mock): - """Test for successfully setting up the SleepIQ platform.""" - mock_responses(requests_mock) - - assert await async_setup_component(hass, "sleepiq", {"sleepiq": CONFIG}) - - device_mock = MagicMock() - sleepiq.setup_platform(hass, CONFIG, device_mock, MagicMock()) - devices = device_mock.call_args[0][0] - assert len(devices) == 2 - - left_side = devices[1] - left_side.hass = hass - assert left_side.name == "SleepNumber ILE Test1 SleepNumber" - assert left_side.state == 40 - - right_side = devices[0] - right_side.hass = hass - assert right_side.name == "SleepNumber ILE Test2 SleepNumber" - assert right_side.state == 80 - - -async def test_setup_single(hass, requests_mock): - """Test for successfully setting up the SleepIQ platform.""" - mock_responses(requests_mock, single=True) - - assert await async_setup_component(hass, "sleepiq", {"sleepiq": CONFIG}) - - device_mock = MagicMock() - sleepiq.setup_platform(hass, CONFIG, device_mock, MagicMock()) - devices = device_mock.call_args[0][0] - assert len(devices) == 1 - - right_side = devices[0] - right_side.hass = hass - assert right_side.name == "SleepNumber ILE Test1 SleepNumber" - assert right_side.state == 40 +from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_ICON +from homeassistant.helpers import entity_registry as er + + +async def test_sensors(hass, setup_entry): + """Test the SleepIQ binary sensors for a bed with two sides.""" + entity_registry = er.async_get(hass) + + state = hass.states.get("sensor.sleepnumber_ile_test1_sleepnumber") + assert state.state == "40" + assert state.attributes.get(ATTR_ICON) == "mdi:bed" + assert ( + state.attributes.get(ATTR_FRIENDLY_NAME) == "SleepNumber ILE Test1 SleepNumber" + ) + + entry = entity_registry.async_get("sensor.sleepnumber_ile_test1_sleepnumber") + assert entry + assert entry.unique_id == "-31_Test1_sleep_number" + + # If account type is set, only a single bed account was created and there will + # not be a second entity + if setup_entry["account_type"]: + return + + state = hass.states.get("sensor.sleepnumber_ile_test2_sleepnumber") + assert state.state == "80" + assert state.attributes.get(ATTR_ICON) == "mdi:bed" + assert ( + state.attributes.get(ATTR_FRIENDLY_NAME) == "SleepNumber ILE Test2 SleepNumber" + ) + + entry = entity_registry.async_get("sensor.sleepnumber_ile_test2_sleepnumber") + assert entry + assert entry.unique_id == "-31_Test2_sleep_number" diff --git a/tests/fixtures/sleepiq-bed-single.json b/tests/fixtures/sleepiq-bed-single.json deleted file mode 100644 index 512f36c0e6afd8..00000000000000 --- a/tests/fixtures/sleepiq-bed-single.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "beds" : [ - { - "dualSleep" : false, - "base" : "FlexFit", - "sku" : "AILE", - "model" : "ILE", - "size" : "KING", - "isKidsBed" : false, - "sleeperRightId" : "-80", - "accountId" : "-32", - "bedId" : "-31", - "registrationDate" : "2016-07-22T14:00:58Z", - "serial" : null, - "reference" : "95000794555-1", - "macAddress" : "CD13A384BA51", - "version" : null, - "purchaseDate" : "2016-06-22T00:00:00Z", - "sleeperLeftId" : "0", - "zipcode" : "12345", - "returnRequestStatus" : 0, - "name" : "ILE", - "status" : 1, - "timezone" : "US/Eastern" - } - ] -} diff --git a/tests/fixtures/sleepiq-bed.json b/tests/fixtures/sleepiq-bed.json deleted file mode 100644 index d03fb6e329f877..00000000000000 --- a/tests/fixtures/sleepiq-bed.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "beds" : [ - { - "dualSleep" : true, - "base" : "FlexFit", - "sku" : "AILE", - "model" : "ILE", - "size" : "KING", - "isKidsBed" : false, - "sleeperRightId" : "-80", - "accountId" : "-32", - "bedId" : "-31", - "registrationDate" : "2016-07-22T14:00:58Z", - "serial" : null, - "reference" : "95000794555-1", - "macAddress" : "CD13A384BA51", - "version" : null, - "purchaseDate" : "2016-06-22T00:00:00Z", - "sleeperLeftId" : "-92", - "zipcode" : "12345", - "returnRequestStatus" : 0, - "name" : "ILE", - "status" : 1, - "timezone" : "US/Eastern" - } - ] -} - diff --git a/tests/fixtures/sleepiq-familystatus-single.json b/tests/fixtures/sleepiq-familystatus-single.json deleted file mode 100644 index 08c9569c4dc716..00000000000000 --- a/tests/fixtures/sleepiq-familystatus-single.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "beds" : [ - { - "bedId" : "-31", - "rightSide" : { - "alertId" : 0, - "lastLink" : "00:00:00", - "isInBed" : true, - "sleepNumber" : 40, - "alertDetailedMessage" : "No Alert", - "pressure" : -16 - }, - "status" : 1, - "leftSide" : null - } - ] -} diff --git a/tests/fixtures/sleepiq-familystatus.json b/tests/fixtures/sleepiq-familystatus.json deleted file mode 100644 index 0c93d74d35fa76..00000000000000 --- a/tests/fixtures/sleepiq-familystatus.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "beds" : [ - { - "bedId" : "-31", - "rightSide" : { - "alertId" : 0, - "lastLink" : "00:00:00", - "isInBed" : true, - "sleepNumber" : 40, - "alertDetailedMessage" : "No Alert", - "pressure" : -16 - }, - "status" : 1, - "leftSide" : { - "alertId" : 0, - "lastLink" : "00:00:00", - "sleepNumber" : 80, - "alertDetailedMessage" : "No Alert", - "isInBed" : false, - "pressure" : 2191 - } - } - ] -} diff --git a/tests/fixtures/sleepiq-login-failed.json b/tests/fixtures/sleepiq-login-failed.json deleted file mode 100644 index 227609154b5b72..00000000000000 --- a/tests/fixtures/sleepiq-login-failed.json +++ /dev/null @@ -1 +0,0 @@ -{"Error":{"Code":401,"Message":"Authentication token of type [class org.apache.shiro.authc.UsernamePasswordToken] could not be authenticated by any configured realms. Please ensure that at least one realm can authenticate these tokens."}} diff --git a/tests/fixtures/sleepiq-login.json b/tests/fixtures/sleepiq-login.json deleted file mode 100644 index fdd8943574f802..00000000000000 --- a/tests/fixtures/sleepiq-login.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "edpLoginStatus" : 200, - "userId" : "-42", - "registrationState" : 13, - "key" : "0987", - "edpLoginMessage" : "not used" -} diff --git a/tests/fixtures/sleepiq-sleeper.json b/tests/fixtures/sleepiq-sleeper.json deleted file mode 100644 index 4089e1b1d95e2e..00000000000000 --- a/tests/fixtures/sleepiq-sleeper.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "sleepers" : [ - { - "timezone" : "US/Eastern", - "firstName" : "Test1", - "weight" : 150, - "birthMonth" : 12, - "birthYear" : "1990", - "active" : true, - "lastLogin" : "2016-08-26 21:43:27 CDT", - "side" : 1, - "accountId" : "-32", - "height" : 60, - "bedId" : "-31", - "username" : "test1@example.com", - "sleeperId" : "-80", - "avatar" : "", - "emailValidated" : true, - "licenseVersion" : 6, - "duration" : null, - "email" : "test1@example.com", - "isAccountOwner" : true, - "sleepGoal" : 480, - "zipCode" : "12345", - "isChild" : false, - "isMale" : true - }, - { - "email" : "test2@example.com", - "duration" : null, - "emailValidated" : true, - "licenseVersion" : 5, - "isChild" : false, - "isMale" : false, - "zipCode" : "12345", - "isAccountOwner" : false, - "sleepGoal" : 480, - "side" : 0, - "lastLogin" : "2016-07-17 15:37:30 CDT", - "birthMonth" : 1, - "birthYear" : "1991", - "active" : true, - "weight" : 151, - "firstName" : "Test2", - "timezone" : "US/Eastern", - "avatar" : "", - "username" : "test2@example.com", - "sleeperId" : "-92", - "bedId" : "-31", - "height" : 65, - "accountId" : "-32" - } - ] -} - From 0911eb1fba57f691779b7617802ffe51049a0b51 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Wed, 16 Feb 2022 16:12:07 +0100 Subject: [PATCH 0704/1098] Add epenet to samsungtv codeowners (#66654) Co-authored-by: epenet --- CODEOWNERS | 4 ++-- homeassistant/components/samsungtv/manifest.json | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index c0dd609233afc9..e27c9488f0af13 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -802,8 +802,8 @@ tests/components/ruckus_unleashed/* @gabe565 homeassistant/components/safe_mode/* @home-assistant/core tests/components/safe_mode/* @home-assistant/core homeassistant/components/saj/* @fredericvl -homeassistant/components/samsungtv/* @escoand @chemelli74 -tests/components/samsungtv/* @escoand @chemelli74 +homeassistant/components/samsungtv/* @escoand @chemelli74 @epenet +tests/components/samsungtv/* @escoand @chemelli74 @epenet homeassistant/components/scene/* @home-assistant/core tests/components/scene/* @home-assistant/core homeassistant/components/schluter/* @prairieapps diff --git a/homeassistant/components/samsungtv/manifest.json b/homeassistant/components/samsungtv/manifest.json index f6046986158698..b4aa313748265f 100644 --- a/homeassistant/components/samsungtv/manifest.json +++ b/homeassistant/components/samsungtv/manifest.json @@ -28,7 +28,8 @@ ], "codeowners": [ "@escoand", - "@chemelli74" + "@chemelli74", + "@epenet" ], "config_flow": true, "iot_class": "local_polling", From 3b87c01af9e294c49068d31e311619a8d9157a76 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Wed, 16 Feb 2022 16:17:11 +0100 Subject: [PATCH 0705/1098] Fix try_connect in samsungtv (#66653) * Fix try_connect in samsungtv * Use try...else * Adjust try...else * Undo try...else Co-authored-by: epenet --- homeassistant/components/samsungtv/bridge.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/samsungtv/bridge.py b/homeassistant/components/samsungtv/bridge.py index d509da91304884..54f1cc422a33d0 100644 --- a/homeassistant/components/samsungtv/bridge.py +++ b/homeassistant/components/samsungtv/bridge.py @@ -303,8 +303,8 @@ def try_connect(self) -> str: self.token = remote.token if self.token is None: config[CONF_TOKEN] = "*****" - LOGGER.debug("Working config: %s", config) - return RESULT_SUCCESS + LOGGER.debug("Working config: %s", config) + return RESULT_SUCCESS except WebSocketException as err: LOGGER.debug( "Working but unsupported config: %s, error: %s", config, err From de2734bd0ee7ebc77431e823cdd50c7a721f762c Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Wed, 16 Feb 2022 15:34:21 +0000 Subject: [PATCH 0706/1098] add entity_category (#66377) --- homeassistant/components/mqtt/abbreviations.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/mqtt/abbreviations.py b/homeassistant/components/mqtt/abbreviations.py index 4b4c6fb7af9d4f..c98dbbd270a5fd 100644 --- a/homeassistant/components/mqtt/abbreviations.py +++ b/homeassistant/components/mqtt/abbreviations.py @@ -50,6 +50,7 @@ "dock_tpl": "docked_template", "e": "encoding", "en": "enabled_by_default", + "ent_cat": "entity_category", "err_t": "error_topic", "err_tpl": "error_template", "fanspd_t": "fan_speed_topic", From 3d1cad9f6764bb97bd593629ebf088995a3c2781 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Wed, 16 Feb 2022 16:42:45 +0100 Subject: [PATCH 0707/1098] Improve handling of cloud hook registration (#65664) Signed-off-by: cgtobi --- homeassistant/components/cloud/__init__.py | 7 +- homeassistant/components/netatmo/__init__.py | 87 +++++++++----------- tests/components/netatmo/test_init.py | 5 ++ 3 files changed, 49 insertions(+), 50 deletions(-) diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index 07c2898f20431a..360e726c89ef67 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -1,6 +1,8 @@ """Component to integrate the Home Assistant cloud.""" +from __future__ import annotations + import asyncio -from collections.abc import Callable +from collections.abc import Awaitable, Callable from enum import Enum from hass_nabucasa import Cloud @@ -152,7 +154,8 @@ def async_is_connected(hass: HomeAssistant) -> bool: @callback def async_listen_connection_change( - hass: HomeAssistant, target: Callable[[CloudConnectionState], None] + hass: HomeAssistant, + target: Callable[[CloudConnectionState], Awaitable[None] | None], ) -> Callable[[], None]: """Notify on connection state changes.""" return async_dispatcher_connect(hass, SIGNAL_CLOUD_CONNECTION_STATE, target) diff --git a/homeassistant/components/netatmo/__init__.py b/homeassistant/components/netatmo/__init__.py index ac11ee554a8984..f6e43b296537ae 100644 --- a/homeassistant/components/netatmo/__init__.py +++ b/homeassistant/components/netatmo/__init__.py @@ -31,10 +31,7 @@ config_entry_oauth2_flow, config_validation as cv, ) -from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, - async_dispatcher_send, -) +from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_call_later from homeassistant.helpers.typing import ConfigType @@ -54,7 +51,6 @@ OAUTH2_AUTHORIZE, OAUTH2_TOKEN, PLATFORMS, - WEBHOOK_ACTIVATION, WEBHOOK_DEACTIVATION, WEBHOOK_PUSH_TYPE, ) @@ -150,8 +146,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.config_entries.async_setup_platforms(entry, PLATFORMS) - _webhook_retries = 0 - async def unregister_webhook( call_or_event_or_dt: ServiceCall | Event | datetime | None, ) -> None: @@ -171,11 +165,6 @@ async def unregister_webhook( "No webhook to be dropped for %s", entry.data[CONF_WEBHOOK_ID] ) - nonlocal _webhook_retries - if _webhook_retries < MAX_WEBHOOK_RETRIES: - _webhook_retries += 1 - async_call_later(hass, 30, register_webhook) - async def register_webhook( call_or_event_or_dt: ServiceCall | Event | datetime | None, ) -> None: @@ -184,14 +173,7 @@ async def register_webhook( hass.config_entries.async_update_entry(entry, data=data) if cloud.async_active_subscription(hass): - if CONF_CLOUDHOOK_URL not in entry.data: - webhook_url = await cloud.async_create_cloudhook( - hass, entry.data[CONF_WEBHOOK_ID] - ) - data = {**entry.data, CONF_CLOUDHOOK_URL: webhook_url} - hass.config_entries.async_update_entry(entry, data=data) - else: - webhook_url = entry.data[CONF_CLOUDHOOK_URL] + webhook_url = await async_cloudhook_generate_url(hass, entry) else: webhook_url = webhook_generate_url(hass, entry.data[CONF_WEBHOOK_ID]) @@ -204,32 +186,15 @@ async def register_webhook( ) return - try: - webhook_register( - hass, - DOMAIN, - "Netatmo", - entry.data[CONF_WEBHOOK_ID], - async_handle_webhook, - ) - - async def handle_event(event: dict) -> None: - """Handle webhook events.""" - if event["data"][WEBHOOK_PUSH_TYPE] == WEBHOOK_ACTIVATION: - if activation_listener is not None: - activation_listener() - - if activation_timeout is not None: - activation_timeout() - - activation_listener = async_dispatcher_connect( - hass, - f"signal-{DOMAIN}-webhook-None", - handle_event, - ) - - activation_timeout = async_call_later(hass, 30, unregister_webhook) + webhook_register( + hass, + DOMAIN, + "Netatmo", + entry.data[CONF_WEBHOOK_ID], + async_handle_webhook, + ) + try: await hass.data[DOMAIN][entry.entry_id][AUTH].async_addwebhook(webhook_url) _LOGGER.info("Register Netatmo webhook: %s", webhook_url) except pyatmo.ApiError as err: @@ -239,10 +204,24 @@ async def handle_event(event: dict) -> None: hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, unregister_webhook) ) - if hass.state == CoreState.running: - await register_webhook(None) + async def manage_cloudhook(state: cloud.CloudConnectionState) -> None: + if state is cloud.CloudConnectionState.CLOUD_CONNECTED: + await register_webhook(None) + + if state is cloud.CloudConnectionState.CLOUD_DISCONNECTED: + await unregister_webhook(None) + async_call_later(hass, 30, register_webhook) + + if cloud.async_active_subscription(hass): + if cloud.async_is_connected(hass): + await register_webhook(None) + cloud.async_listen_connection_change(hass, manage_cloudhook) + else: - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, register_webhook) + if hass.state == CoreState.running: + await register_webhook(None) + else: + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, register_webhook) hass.services.async_register(DOMAIN, "register_webhook", register_webhook) hass.services.async_register(DOMAIN, "unregister_webhook", unregister_webhook) @@ -252,6 +231,18 @@ async def handle_event(event: dict) -> None: return True +async def async_cloudhook_generate_url(hass: HomeAssistant, entry: ConfigEntry) -> str: + """Generate the full URL for a webhook_id.""" + if CONF_CLOUDHOOK_URL not in entry.data: + webhook_url = await cloud.async_create_cloudhook( + hass, entry.data[CONF_WEBHOOK_ID] + ) + data = {**entry.data, CONF_CLOUDHOOK_URL: webhook_url} + hass.config_entries.async_update_entry(entry, data=data) + return webhook_url + return str(entry.data[CONF_CLOUDHOOK_URL]) + + async def async_config_entry_updated(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle signals of config entry being updated.""" async_dispatcher_send(hass, f"signal-{DOMAIN}-public-update-{entry.entry_id}") diff --git a/tests/components/netatmo/test_init.py b/tests/components/netatmo/test_init.py index ffa68d75011c75..911fd6c309a237 100644 --- a/tests/components/netatmo/test_init.py +++ b/tests/components/netatmo/test_init.py @@ -182,6 +182,8 @@ async def test_setup_with_cloud(hass, config_entry): with patch( "homeassistant.components.cloud.async_is_logged_in", return_value=True + ), patch( + "homeassistant.components.cloud.async_is_connected", return_value=True ), patch( "homeassistant.components.cloud.async_active_subscription", return_value=True ), patch( @@ -203,6 +205,7 @@ async def test_setup_with_cloud(hass, config_entry): hass, "netatmo", {"netatmo": {"client_id": "123", "client_secret": "abc"}} ) assert hass.components.cloud.async_active_subscription() is True + assert hass.components.cloud.async_is_connected() is True fake_create_cloudhook.assert_called_once() assert ( @@ -245,6 +248,8 @@ async def test_setup_with_cloudhook(hass): with patch( "homeassistant.components.cloud.async_is_logged_in", return_value=True + ), patch( + "homeassistant.components.cloud.async_is_connected", return_value=True ), patch( "homeassistant.components.cloud.async_active_subscription", return_value=True ), patch( From cd5b69d02ee4a5fad5e2aa64e9708c89765591c7 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 16 Feb 2022 07:54:59 -0800 Subject: [PATCH 0708/1098] Add Google local indicator (#66610) --- homeassistant/components/cloud/http_api.py | 1 + .../components/google_assistant/helpers.py | 13 +++++++++++++ .../components/google_assistant/smart_home.py | 2 +- tests/components/cloud/test_http_api.py | 1 + tests/components/google_assistant/test_helpers.py | 10 ++++++++++ 5 files changed, 26 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/cloud/http_api.py b/homeassistant/components/cloud/http_api.py index 517d6b2bb9b1d6..f51266e45dcf5a 100644 --- a/homeassistant/components/cloud/http_api.py +++ b/homeassistant/components/cloud/http_api.py @@ -441,6 +441,7 @@ async def _account_data(hass: HomeAssistant, cloud: Cloud): "email": claims["email"], "google_entities": client.google_user_config["filter"].config, "google_registered": google_config.has_registered_user_agent, + "google_local_connected": google_config.is_local_connected, "logged_in": True, "prefs": client.prefs.as_dict(), "remote_certificate": certificate, diff --git a/homeassistant/components/google_assistant/helpers.py b/homeassistant/components/google_assistant/helpers.py index b2201196917733..a348299e282217 100644 --- a/homeassistant/components/google_assistant/helpers.py +++ b/homeassistant/components/google_assistant/helpers.py @@ -4,6 +4,7 @@ from abc import ABC, abstractmethod from asyncio import gather from collections.abc import Mapping +from datetime import datetime, timedelta from http import HTTPStatus import logging import pprint @@ -26,6 +27,7 @@ from homeassistant.helpers.event import async_call_later from homeassistant.helpers.network import get_url from homeassistant.helpers.storage import Store +from homeassistant.util.dt import utcnow from . import trait from .const import ( @@ -104,6 +106,7 @@ def __init__(self, hass): self._store = None self._google_sync_unsub = {} self._local_sdk_active = False + self._local_last_active: datetime | None = None async def async_initialize(self): """Perform async initialization of config.""" @@ -149,6 +152,15 @@ def should_report_state(self): """Return if states should be proactively reported.""" return False + @property + def is_local_connected(self) -> bool: + """Return if local is connected.""" + return ( + self._local_last_active is not None + # We get a reachable devices intent every minute. + and self._local_last_active > utcnow() - timedelta(seconds=70) + ) + def get_local_agent_user_id(self, webhook_id): """Return the user ID to be used for actions received via the local SDK. @@ -336,6 +348,7 @@ async def _handle_local_webhook(self, hass, webhook_id, request): # pylint: disable=import-outside-toplevel from . import smart_home + self._local_last_active = utcnow() payload = await request.json() if _LOGGER.isEnabledFor(logging.DEBUG): diff --git a/homeassistant/components/google_assistant/smart_home.py b/homeassistant/components/google_assistant/smart_home.py index 31fd02544ff199..80bc61cc61dba9 100644 --- a/homeassistant/components/google_assistant/smart_home.py +++ b/homeassistant/components/google_assistant/smart_home.py @@ -281,7 +281,7 @@ async def async_devices_identify(hass, data: RequestData, payload): async def async_devices_reachable(hass, data: RequestData, payload): """Handle action.devices.REACHABLE_DEVICES request. - https://developers.google.com/actions/smarthome/create#actiondevicesdisconnect + https://developers.google.com/assistant/smarthome/develop/local#implement_the_reachable_devices_handler_hub_integrations_only """ google_ids = {dev["id"] for dev in (data.devices or [])} diff --git a/tests/components/cloud/test_http_api.py b/tests/components/cloud/test_http_api.py index 07b87632f19071..e06344827a7859 100644 --- a/tests/components/cloud/test_http_api.py +++ b/tests/components/cloud/test_http_api.py @@ -424,6 +424,7 @@ async def test_websocket_status( "exclude_entities": [], }, "google_registered": False, + "google_local_connected": False, "remote_domain": None, "remote_connected": False, "remote_certificate": None, diff --git a/tests/components/google_assistant/test_helpers.py b/tests/components/google_assistant/test_helpers.py index ff441e44f25bc6..dc29e5df4ab803 100644 --- a/tests/components/google_assistant/test_helpers.py +++ b/tests/components/google_assistant/test_helpers.py @@ -94,7 +94,9 @@ async def test_config_local_sdk(hass, hass_client): client = await hass_client() + assert config.is_local_connected is False config.async_enable_local_sdk() + assert config.is_local_connected is False resp = await client.post( "/api/webhook/mock-webhook-id", @@ -122,6 +124,14 @@ async def test_config_local_sdk(hass, hass_client): "requestId": "mock-req-id", }, ) + + assert config.is_local_connected is True + with patch( + "homeassistant.components.google_assistant.helpers.utcnow", + return_value=dt.utcnow() + timedelta(seconds=90), + ): + assert config.is_local_connected is False + assert resp.status == HTTPStatus.OK result = await resp.json() assert result["requestId"] == "mock-req-id" From 39cf250a8e5ffba68eae03bb3b34095855e65daa Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 16 Feb 2022 17:41:43 +0100 Subject: [PATCH 0709/1098] Add current temp fallback in Tuya climate (#66664) --- homeassistant/components/tuya/climate.py | 8 ++++++-- homeassistant/components/tuya/const.py | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/tuya/climate.py b/homeassistant/components/tuya/climate.py index 12241a00513ef2..b4def6fb3797e0 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -167,8 +167,12 @@ def __init__( self._attr_temperature_unit = TEMP_CELSIUS # Figure out current temperature, use preferred unit or what is available - celsius_type = self.find_dpcode(DPCode.TEMP_CURRENT, dptype=DPType.INTEGER) - farhenheit_type = self.find_dpcode(DPCode.TEMP_CURRENT_F, dptype=DPType.INTEGER) + celsius_type = self.find_dpcode( + (DPCode.TEMP_CURRENT, DPCode.UPPER_TEMP), dptype=DPType.INTEGER + ) + farhenheit_type = self.find_dpcode( + (DPCode.TEMP_CURRENT_F, DPCode.UPPER_TEMP_F), dptype=DPType.INTEGER + ) if farhenheit_type and ( prefered_temperature_unit == TEMP_FAHRENHEIT or (prefered_temperature_unit == TEMP_CELSIUS and not celsius_type) diff --git a/homeassistant/components/tuya/const.py b/homeassistant/components/tuya/const.py index d976bee279265d..ee653534fc8acd 100644 --- a/homeassistant/components/tuya/const.py +++ b/homeassistant/components/tuya/const.py @@ -371,6 +371,8 @@ class DPCode(StrEnum): TOTAL_TIME = "total_time" TOTAL_PM = "total_pm" TVOC = "tvoc" + UPPER_TEMP = "upper_temp" + UPPER_TEMP_F = "upper_temp_f" UV = "uv" # UV sterilization VA_BATTERY = "va_battery" VA_HUMIDITY = "va_humidity" From a9390908ea54d285dd1b636b63f9a82414255e22 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 16 Feb 2022 17:54:57 +0100 Subject: [PATCH 0710/1098] Keep TTS media browser params in identifier (#66663) Co-authored-by: Paulus Schoutsen --- homeassistant/components/tts/media_source.py | 15 +++++++++++---- tests/components/tts/test_media_source.py | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/tts/media_source.py b/homeassistant/components/tts/media_source.py index 2398a6203ad976..43c3998849c897 100644 --- a/homeassistant/components/tts/media_source.py +++ b/homeassistant/components/tts/media_source.py @@ -70,8 +70,8 @@ async def async_browse_media( ) -> BrowseMediaSource: """Return media.""" if item.identifier: - provider, _, _ = item.identifier.partition("?") - return self._provider_item(provider) + provider, _, params = item.identifier.partition("?") + return self._provider_item(provider, params) # Root. List providers. manager: SpeechManager = self.hass.data[DOMAIN] @@ -89,7 +89,9 @@ async def async_browse_media( ) @callback - def _provider_item(self, provider_domain: str) -> BrowseMediaSource: + def _provider_item( + self, provider_domain: str, params: str | None = None + ) -> BrowseMediaSource: """Return provider item.""" manager: SpeechManager = self.hass.data[DOMAIN] provider = manager.providers.get(provider_domain) @@ -97,9 +99,14 @@ def _provider_item(self, provider_domain: str) -> BrowseMediaSource: if provider is None: raise BrowseError("Unknown provider") + if params: + params = f"?{params}" + else: + params = "" + return BrowseMediaSource( domain=DOMAIN, - identifier=provider_domain, + identifier=f"{provider_domain}{params}", media_class=MEDIA_CLASS_APP, media_content_type="provider", title=provider.name, diff --git a/tests/components/tts/test_media_source.py b/tests/components/tts/test_media_source.py index 3bfd204a228720..22edfef535855a 100644 --- a/tests/components/tts/test_media_source.py +++ b/tests/components/tts/test_media_source.py @@ -42,6 +42,20 @@ async def test_browsing(hass): hass, item.children[0].media_content_id ) assert item_child is not None + assert item_child.media_content_id == item.children[0].media_content_id + assert item_child.title == "Demo" + assert item_child.children is None + assert item_child.can_play is False + assert item_child.can_expand is True + + item_child = await media_source.async_browse_media( + hass, item.children[0].media_content_id + "?message=bla" + ) + assert item_child is not None + assert ( + item_child.media_content_id + == item.children[0].media_content_id + "?message=bla" + ) assert item_child.title == "Demo" assert item_child.children is None assert item_child.can_play is False From dd9b14d5c9f114108bf0e7304e24b1ef0e515d33 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Wed, 16 Feb 2022 17:55:30 +0100 Subject: [PATCH 0711/1098] Add Button platform to deCONZ integration (#65700) * Improve scene platform * Add button platform, tests and fix tests affected by new entities existing * Remove unnused property * Bump dependency to v87 --- homeassistant/components/deconz/button.py | 115 ++++++++++++++++++ homeassistant/components/deconz/const.py | 1 + .../components/deconz/deconz_device.py | 38 ++++++ homeassistant/components/deconz/light.py | 2 +- homeassistant/components/deconz/manifest.json | 6 +- homeassistant/components/deconz/scene.py | 40 +++--- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/deconz/test_button.py | 106 ++++++++++++++++ tests/components/deconz/test_diagnostics.py | 1 + tests/components/deconz/test_gateway.py | 22 ++-- tests/components/deconz/test_scene.py | 94 ++++++++++---- tests/components/deconz/test_services.py | 4 +- 13 files changed, 365 insertions(+), 68 deletions(-) create mode 100644 homeassistant/components/deconz/button.py create mode 100644 tests/components/deconz/test_button.py diff --git a/homeassistant/components/deconz/button.py b/homeassistant/components/deconz/button.py new file mode 100644 index 00000000000000..2ad53d8ad63ec7 --- /dev/null +++ b/homeassistant/components/deconz/button.py @@ -0,0 +1,115 @@ +"""Support for deCONZ buttons.""" + +from __future__ import annotations + +from collections.abc import ValuesView +from dataclasses import dataclass + +from pydeconz.group import Scene as PydeconzScene + +from homeassistant.components.button import ( + DOMAIN, + ButtonEntity, + ButtonEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .deconz_device import DeconzSceneMixin +from .gateway import DeconzGateway, get_gateway_from_config_entry + + +@dataclass +class DeconzButtonDescriptionMixin: + """Required values when describing deCONZ button entities.""" + + suffix: str + button_fn: str + + +@dataclass +class DeconzButtonDescription(ButtonEntityDescription, DeconzButtonDescriptionMixin): + """Class describing deCONZ button entities.""" + + +ENTITY_DESCRIPTIONS = { + PydeconzScene: [ + DeconzButtonDescription( + key="store", + button_fn="store", + suffix="Store Current Scene", + icon="mdi:inbox-arrow-down", + entity_category=EntityCategory.CONFIG, + ) + ] +} + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the deCONZ button entity.""" + gateway = get_gateway_from_config_entry(hass, config_entry) + gateway.entities[DOMAIN] = set() + + @callback + def async_add_scene( + scenes: list[PydeconzScene] + | ValuesView[PydeconzScene] = gateway.api.scenes.values(), + ) -> None: + """Add scene button from deCONZ.""" + entities = [] + + for scene in scenes: + + known_entities = set(gateway.entities[DOMAIN]) + for description in ENTITY_DESCRIPTIONS.get(PydeconzScene, []): + + new_entity = DeconzButton(scene, gateway, description) + if new_entity.unique_id not in known_entities: + entities.append(new_entity) + + if entities: + async_add_entities(entities) + + config_entry.async_on_unload( + async_dispatcher_connect( + hass, + gateway.signal_new_scene, + async_add_scene, + ) + ) + + async_add_scene() + + +class DeconzButton(DeconzSceneMixin, ButtonEntity): + """Representation of a deCONZ button entity.""" + + TYPE = DOMAIN + + def __init__( + self, + device: PydeconzScene, + gateway: DeconzGateway, + description: DeconzButtonDescription, + ) -> None: + """Initialize deCONZ number entity.""" + self.entity_description: DeconzButtonDescription = description + super().__init__(device, gateway) + + self._attr_name = f"{self._attr_name} {description.suffix}" + + async def async_press(self) -> None: + """Store light states into scene.""" + async_button_fn = getattr(self._device, self.entity_description.button_fn) + await async_button_fn() + + def get_device_identifier(self) -> str: + """Return a unique identifier for this scene.""" + return f"{super().get_device_identifier()}-{self.entity_description.key}" diff --git a/homeassistant/components/deconz/const.py b/homeassistant/components/deconz/const.py index 5f6d77a69fda26..ca2e791f9e9fbc 100644 --- a/homeassistant/components/deconz/const.py +++ b/homeassistant/components/deconz/const.py @@ -25,6 +25,7 @@ PLATFORMS = [ Platform.ALARM_CONTROL_PANEL, Platform.BINARY_SENSOR, + Platform.BUTTON, Platform.CLIMATE, Platform.COVER, Platform.FAN, diff --git a/homeassistant/components/deconz/deconz_device.py b/homeassistant/components/deconz/deconz_device.py index bbd4051c177e52..45f57729a6f340 100644 --- a/homeassistant/components/deconz/deconz_device.py +++ b/homeassistant/components/deconz/deconz_device.py @@ -1,6 +1,8 @@ """Base class for deCONZ devices.""" from __future__ import annotations +from pydeconz.group import Scene as PydeconzScene + from homeassistant.core import callback from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -96,3 +98,39 @@ def async_update_callback(self): def available(self): """Return True if device is available.""" return self.gateway.available and self._device.reachable + + +class DeconzSceneMixin(DeconzDevice): + """Representation of a deCONZ scene.""" + + _device: PydeconzScene + + def __init__(self, device, gateway) -> None: + """Set up a scene.""" + super().__init__(device, gateway) + + self._attr_name = device.full_name + self._group_identifier = self.get_parent_identifier() + + def get_device_identifier(self) -> str: + """Describe a unique identifier for this scene.""" + return f"{self.gateway.bridgeid}{self._device.deconz_id}" + + def get_parent_identifier(self) -> str: + """Describe a unique identifier for group this scene belongs to.""" + return f"{self.gateway.bridgeid}-{self._device.group_deconz_id}" + + @property + def available(self) -> bool: + """Return True if scene is available.""" + return self.gateway.available + + @property + def unique_id(self) -> str: + """Return a unique identifier for this scene.""" + return self.get_device_identifier() + + @property + def device_info(self) -> DeviceInfo: + """Return a device description for device registry.""" + return DeviceInfo(identifiers={(DECONZ_DOMAIN, self._group_identifier)}) diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index 5330fdb32261e8..e3cf6442079986 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -5,7 +5,7 @@ from collections.abc import ValuesView from typing import Any -from pydeconz.group import DeconzGroup as Group +from pydeconz.group import Group from pydeconz.light import ( ALERT_LONG, ALERT_SHORT, diff --git a/homeassistant/components/deconz/manifest.json b/homeassistant/components/deconz/manifest.json index 6fb6bbce87a1c8..bbbafffed7a64c 100644 --- a/homeassistant/components/deconz/manifest.json +++ b/homeassistant/components/deconz/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/deconz", "requirements": [ - "pydeconz==86" + "pydeconz==87" ], "ssdp": [ { @@ -16,5 +16,7 @@ ], "quality_scale": "platinum", "iot_class": "local_push", - "loggers": ["pydeconz"] + "loggers": [ + "pydeconz" + ] } \ No newline at end of file diff --git a/homeassistant/components/deconz/scene.py b/homeassistant/components/deconz/scene.py index 9fcccc523864b9..c188d7faffa947 100644 --- a/homeassistant/components/deconz/scene.py +++ b/homeassistant/components/deconz/scene.py @@ -5,7 +5,7 @@ from collections.abc import ValuesView from typing import Any -from pydeconz.group import DeconzScene as PydeconzScene +from pydeconz.group import Scene as PydeconzScene from homeassistant.components.scene import DOMAIN, Scene from homeassistant.config_entries import ConfigEntry @@ -13,7 +13,8 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .gateway import DeconzGateway, get_gateway_from_config_entry +from .deconz_device import DeconzSceneMixin +from .gateway import get_gateway_from_config_entry async def async_setup_entry( @@ -31,11 +32,14 @@ def async_add_scene( | ValuesView[PydeconzScene] = gateway.api.scenes.values(), ) -> None: """Add scene from deCONZ.""" - entities = [ - DeconzScene(scene, gateway) - for scene in scenes - if scene.deconz_id not in gateway.entities[DOMAIN] - ] + entities = [] + + for scene in scenes: + + known_entities = set(gateway.entities[DOMAIN]) + new_entity = DeconzScene(scene, gateway) + if new_entity.unique_id not in known_entities: + entities.append(new_entity) if entities: async_add_entities(entities) @@ -51,27 +55,11 @@ def async_add_scene( async_add_scene() -class DeconzScene(Scene): +class DeconzScene(DeconzSceneMixin, Scene): """Representation of a deCONZ scene.""" - def __init__(self, scene: PydeconzScene, gateway: DeconzGateway) -> None: - """Set up a scene.""" - self._scene = scene - self.gateway = gateway - - self._attr_name = scene.full_name - - async def async_added_to_hass(self) -> None: - """Subscribe to sensors events.""" - self.gateway.deconz_ids[self.entity_id] = self._scene.deconz_id - self.gateway.entities[DOMAIN].add(self._scene.deconz_id) - - async def async_will_remove_from_hass(self) -> None: - """Disconnect scene object when removed.""" - del self.gateway.deconz_ids[self.entity_id] - self.gateway.entities[DOMAIN].remove(self._scene.deconz_id) - self._scene = None + TYPE = DOMAIN async def async_activate(self, **kwargs: Any) -> None: """Activate the scene.""" - await self._scene.recall() + await self._device.recall() diff --git a/requirements_all.txt b/requirements_all.txt index e49ada0b8ce8ed..34598e82c6955f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1467,7 +1467,7 @@ pydaikin==2.7.0 pydanfossair==0.1.0 # homeassistant.components.deconz -pydeconz==86 +pydeconz==87 # homeassistant.components.delijn pydelijn==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5b9a5db5f7b3c7..aa830cb97c92ab 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -917,7 +917,7 @@ pycoolmasternet-async==0.1.2 pydaikin==2.7.0 # homeassistant.components.deconz -pydeconz==86 +pydeconz==87 # homeassistant.components.dexcom pydexcom==0.2.2 diff --git a/tests/components/deconz/test_button.py b/tests/components/deconz/test_button.py new file mode 100644 index 00000000000000..804a93d5ea40d7 --- /dev/null +++ b/tests/components/deconz/test_button.py @@ -0,0 +1,106 @@ +"""deCONZ button platform tests.""" + +from unittest.mock import patch + +import pytest + +from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS +from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE +from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers.entity import EntityCategory + +from .test_gateway import ( + DECONZ_WEB_REQUEST, + mock_deconz_put_request, + setup_deconz_integration, +) + + +async def test_no_binary_sensors(hass, aioclient_mock): + """Test that no sensors in deconz results in no sensor entities.""" + await setup_deconz_integration(hass, aioclient_mock) + assert len(hass.states.async_all()) == 0 + + +TEST_DATA = [ + ( # Store scene button + { + "groups": { + "1": { + "id": "Light group id", + "name": "Light group", + "type": "LightGroup", + "state": {"all_on": False, "any_on": True}, + "action": {}, + "scenes": [{"id": "1", "name": "Scene"}], + "lights": [], + } + } + }, + { + "entity_count": 2, + "device_count": 3, + "entity_id": "button.light_group_scene_store_current_scene", + "unique_id": "01234E56789A/groups/1/scenes/1-store", + "entity_category": EntityCategory.CONFIG, + "attributes": { + "icon": "mdi:inbox-arrow-down", + "friendly_name": "Light group Scene Store Current Scene", + }, + "request": "/groups/1/scenes/1/store", + }, + ), +] + + +@pytest.mark.parametrize("raw_data, expected", TEST_DATA) +async def test_button(hass, aioclient_mock, raw_data, expected): + """Test successful creation of button entities.""" + ent_reg = er.async_get(hass) + dev_reg = dr.async_get(hass) + + with patch.dict(DECONZ_WEB_REQUEST, raw_data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) + + assert len(hass.states.async_all()) == expected["entity_count"] + + # Verify state data + + button = hass.states.get(expected["entity_id"]) + assert button.attributes == expected["attributes"] + + # Verify entity registry data + + ent_reg_entry = ent_reg.async_get(expected["entity_id"]) + assert ent_reg_entry.entity_category is expected["entity_category"] + assert ent_reg_entry.unique_id == expected["unique_id"] + + # Verify device registry data + + assert ( + len(dr.async_entries_for_config_entry(dev_reg, config_entry.entry_id)) + == expected["device_count"] + ) + + # Verify button press + + mock_deconz_put_request(aioclient_mock, config_entry.data, expected["request"]) + + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: expected["entity_id"]}, + blocking=True, + ) + assert aioclient_mock.mock_calls[1][2] == {} + + # Unload entry + + await hass.config_entries.async_unload(config_entry.entry_id) + assert hass.states.get(expected["entity_id"]).state == STATE_UNAVAILABLE + + # Remove entry + + await hass.config_entries.async_remove(config_entry.entry_id) + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 0 diff --git a/tests/components/deconz/test_diagnostics.py b/tests/components/deconz/test_diagnostics.py index 17da9f1141a22c..d0905f5ba5fc44 100644 --- a/tests/components/deconz/test_diagnostics.py +++ b/tests/components/deconz/test_diagnostics.py @@ -49,6 +49,7 @@ async def test_entry_diagnostics( "entities": { str(Platform.ALARM_CONTROL_PANEL): [], str(Platform.BINARY_SENSOR): [], + str(Platform.BUTTON): [], str(Platform.CLIMATE): [], str(Platform.COVER): [], str(Platform.FAN): [], diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index 30473814f26473..8a449456fde205 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -12,6 +12,7 @@ DOMAIN as ALARM_CONTROL_PANEL_DOMAIN, ) from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN +from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN from homeassistant.components.cover import DOMAIN as COVER_DOMAIN from homeassistant.components.deconz.config_flow import DECONZ_MANUFACTURERURL @@ -159,16 +160,17 @@ async def test_gateway_setup(hass, aioclient_mock): config_entry, BINARY_SENSOR_DOMAIN, ) - assert forward_entry_setup.mock_calls[2][1] == (config_entry, CLIMATE_DOMAIN) - assert forward_entry_setup.mock_calls[3][1] == (config_entry, COVER_DOMAIN) - assert forward_entry_setup.mock_calls[4][1] == (config_entry, FAN_DOMAIN) - assert forward_entry_setup.mock_calls[5][1] == (config_entry, LIGHT_DOMAIN) - assert forward_entry_setup.mock_calls[6][1] == (config_entry, LOCK_DOMAIN) - assert forward_entry_setup.mock_calls[7][1] == (config_entry, NUMBER_DOMAIN) - assert forward_entry_setup.mock_calls[8][1] == (config_entry, SCENE_DOMAIN) - assert forward_entry_setup.mock_calls[9][1] == (config_entry, SENSOR_DOMAIN) - assert forward_entry_setup.mock_calls[10][1] == (config_entry, SIREN_DOMAIN) - assert forward_entry_setup.mock_calls[11][1] == (config_entry, SWITCH_DOMAIN) + assert forward_entry_setup.mock_calls[2][1] == (config_entry, BUTTON_DOMAIN) + assert forward_entry_setup.mock_calls[3][1] == (config_entry, CLIMATE_DOMAIN) + assert forward_entry_setup.mock_calls[4][1] == (config_entry, COVER_DOMAIN) + assert forward_entry_setup.mock_calls[5][1] == (config_entry, FAN_DOMAIN) + assert forward_entry_setup.mock_calls[6][1] == (config_entry, LIGHT_DOMAIN) + assert forward_entry_setup.mock_calls[7][1] == (config_entry, LOCK_DOMAIN) + assert forward_entry_setup.mock_calls[8][1] == (config_entry, NUMBER_DOMAIN) + assert forward_entry_setup.mock_calls[9][1] == (config_entry, SCENE_DOMAIN) + assert forward_entry_setup.mock_calls[10][1] == (config_entry, SENSOR_DOMAIN) + assert forward_entry_setup.mock_calls[11][1] == (config_entry, SIREN_DOMAIN) + assert forward_entry_setup.mock_calls[12][1] == (config_entry, SWITCH_DOMAIN) device_registry = dr.async_get(hass) gateway_entry = device_registry.async_get_device( diff --git a/tests/components/deconz/test_scene.py b/tests/components/deconz/test_scene.py index e6f74cd0529f42..f28e83d3f39828 100644 --- a/tests/components/deconz/test_scene.py +++ b/tests/components/deconz/test_scene.py @@ -2,9 +2,12 @@ from unittest.mock import patch +import pytest + from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN, SERVICE_TURN_ON -from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send from .test_gateway import ( @@ -20,45 +23,86 @@ async def test_no_scenes(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_scenes(hass, aioclient_mock): - """Test that scenes works.""" - data = { - "groups": { - "1": { - "id": "Light group id", - "name": "Light group", - "type": "LightGroup", - "state": {"all_on": False, "any_on": True}, - "action": {}, - "scenes": [{"id": "1", "name": "Scene"}], - "lights": [], +TEST_DATA = [ + ( # Scene + { + "groups": { + "1": { + "id": "Light group id", + "name": "Light group", + "type": "LightGroup", + "state": {"all_on": False, "any_on": True}, + "action": {}, + "scenes": [{"id": "1", "name": "Scene"}], + "lights": [], + } } - } - } - with patch.dict(DECONZ_WEB_REQUEST, data): + }, + { + "entity_count": 2, + "device_count": 3, + "entity_id": "scene.light_group_scene", + "unique_id": "01234E56789A/groups/1/scenes/1", + "entity_category": None, + "attributes": { + "friendly_name": "Light group Scene", + }, + "request": "/groups/1/scenes/1/recall", + }, + ), +] + + +@pytest.mark.parametrize("raw_data, expected", TEST_DATA) +async def test_scenes(hass, aioclient_mock, raw_data, expected): + """Test successful creation of scene entities.""" + ent_reg = er.async_get(hass) + dev_reg = dr.async_get(hass) + + with patch.dict(DECONZ_WEB_REQUEST, raw_data): config_entry = await setup_deconz_integration(hass, aioclient_mock) - assert len(hass.states.async_all()) == 1 - assert hass.states.get("scene.light_group_scene") + assert len(hass.states.async_all()) == expected["entity_count"] + + # Verify state data + + scene = hass.states.get(expected["entity_id"]) + assert scene.attributes == expected["attributes"] + + # Verify entity registry data - # Verify service calls + ent_reg_entry = ent_reg.async_get(expected["entity_id"]) + assert ent_reg_entry.entity_category is expected["entity_category"] + assert ent_reg_entry.unique_id == expected["unique_id"] - mock_deconz_put_request( - aioclient_mock, config_entry.data, "/groups/1/scenes/1/recall" + # Verify device registry data + + assert ( + len(dr.async_entries_for_config_entry(dev_reg, config_entry.entry_id)) + == expected["device_count"] ) - # Service turn on scene + # Verify button press + + mock_deconz_put_request(aioclient_mock, config_entry.data, expected["request"]) await hass.services.async_call( SCENE_DOMAIN, SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "scene.light_group_scene"}, + {ATTR_ENTITY_ID: expected["entity_id"]}, blocking=True, ) assert aioclient_mock.mock_calls[1][2] == {} + # Unload entry + await hass.config_entries.async_unload(config_entry.entry_id) + assert hass.states.get(expected["entity_id"]).state == STATE_UNAVAILABLE + # Remove entry + + await hass.config_entries.async_remove(config_entry.entry_id) + await hass.async_block_till_done() assert len(hass.states.async_all()) == 0 @@ -80,10 +124,10 @@ async def test_only_new_scenes_are_created(hass, aioclient_mock): with patch.dict(DECONZ_WEB_REQUEST, data): config_entry = await setup_deconz_integration(hass, aioclient_mock) - assert len(hass.states.async_all()) == 1 + assert len(hass.states.async_all()) == 2 gateway = get_gateway_from_config_entry(hass, config_entry) async_dispatcher_send(hass, gateway.signal_new_scene) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 1 + assert len(hass.states.async_all()) == 2 diff --git a/tests/components/deconz/test_services.py b/tests/components/deconz/test_services.py index 5cdd36440eaa65..086da5e24c433a 100644 --- a/tests/components/deconz/test_services.py +++ b/tests/components/deconz/test_services.py @@ -254,7 +254,7 @@ async def test_service_refresh_devices(hass, aioclient_mock): ) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 4 + assert len(hass.states.async_all()) == 5 async def test_service_refresh_devices_trigger_no_state_update(hass, aioclient_mock): @@ -317,7 +317,7 @@ async def test_service_refresh_devices_trigger_no_state_update(hass, aioclient_m ) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 4 + assert len(hass.states.async_all()) == 5 assert len(captured_events) == 0 From 65999227aefc67e03a93554d50024a9b257ad3b1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 16 Feb 2022 11:11:31 -0600 Subject: [PATCH 0712/1098] Fix missing effects on dimmable WiZ bulbs (#66665) --- homeassistant/components/wiz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/wiz/manifest.json b/homeassistant/components/wiz/manifest.json index 7ee19346958976..7794162d32c012 100644 --- a/homeassistant/components/wiz/manifest.json +++ b/homeassistant/components/wiz/manifest.json @@ -10,7 +10,7 @@ "dependencies": ["network"], "quality_scale": "platinum", "documentation": "https://www.home-assistant.io/integrations/wiz", - "requirements": ["pywizlight==0.5.8"], + "requirements": ["pywizlight==0.5.9"], "iot_class": "local_push", "codeowners": ["@sbidy"] } diff --git a/requirements_all.txt b/requirements_all.txt index 34598e82c6955f..37d68deecfc8d7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2057,7 +2057,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.5.8 +pywizlight==0.5.9 # homeassistant.components.xeoma pyxeoma==1.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index aa830cb97c92ab..30166e622eae23 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1285,7 +1285,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.5.8 +pywizlight==0.5.9 # homeassistant.components.zerproc pyzerproc==0.4.8 From 0ec89ae5da532257db2dc64b5751046d25c27e56 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 16 Feb 2022 11:15:31 -0600 Subject: [PATCH 0713/1098] Teach _async_abort_entries_match about entry options (#66662) --- homeassistant/config_entries.py | 6 +++++- tests/test_config_entries.py | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index a0017c36684461..400cea3f78ca26 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from collections import ChainMap from collections.abc import Awaitable, Callable, Iterable, Mapping from contextvars import ContextVar import dataclasses @@ -1211,7 +1212,10 @@ def _async_abort_entries_match( if match_dict is None: match_dict = {} # Match any entry for entry in self._async_current_entries(include_ignore=False): - if all(item in entry.data.items() for item in match_dict.items()): + if all( + item in ChainMap(entry.options, entry.data).items() # type: ignore + for item in match_dict.items() + ): raise data_entry_flow.AbortFlow("already_configured") @callback diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index cad92a5d92d888..b62e9bffbce405 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -2893,12 +2893,23 @@ async def test_setup_retrying_during_shutdown(hass): [ ({}, "already_configured"), ({"host": "3.3.3.3"}, "no_match"), + ({"vendor": "no_match"}, "no_match"), ({"host": "3.4.5.6"}, "already_configured"), ({"host": "3.4.5.6", "ip": "3.4.5.6"}, "no_match"), ({"host": "3.4.5.6", "ip": "1.2.3.4"}, "already_configured"), ({"host": "3.4.5.6", "ip": "1.2.3.4", "port": 23}, "already_configured"), + ( + {"host": "9.9.9.9", "ip": "6.6.6.6", "port": 12, "vendor": "zoo"}, + "already_configured", + ), + ({"vendor": "zoo"}, "already_configured"), ({"ip": "9.9.9.9"}, "already_configured"), ({"ip": "7.7.7.7"}, "no_match"), # ignored + ({"vendor": "data"}, "no_match"), + ( + {"vendor": "options"}, + "already_configured", + ), # ensure options takes precedence over data ], ) async def test__async_abort_entries_match(hass, manager, matchers, reason): @@ -2917,6 +2928,16 @@ async def test__async_abort_entries_match(hass, manager, matchers, reason): source=config_entries.SOURCE_IGNORE, data={"ip": "7.7.7.7", "host": "4.5.6.7", "port": 23}, ).add_to_hass(hass) + MockConfigEntry( + domain="comp", + data={"ip": "6.6.6.6", "host": "9.9.9.9", "port": 12}, + options={"vendor": "zoo"}, + ).add_to_hass(hass) + MockConfigEntry( + domain="comp", + data={"vendor": "data"}, + options={"vendor": "options"}, + ).add_to_hass(hass) mock_setup_entry = AsyncMock(return_value=True) From 0b541978c7dc3de1d80aefc1f8cc55ed5b69af88 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 16 Feb 2022 09:24:01 -0800 Subject: [PATCH 0714/1098] Bump aiohue to 4.2.0 (#66670) --- homeassistant/components/hue/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/hue/manifest.json b/homeassistant/components/hue/manifest.json index 9039018da6edcf..d82359613f7ef2 100644 --- a/homeassistant/components/hue/manifest.json +++ b/homeassistant/components/hue/manifest.json @@ -3,7 +3,7 @@ "name": "Philips Hue", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/hue", - "requirements": ["aiohue==4.1.2"], + "requirements": ["aiohue==4.2.0"], "ssdp": [ { "manufacturer": "Royal Philips Electronics", diff --git a/requirements_all.txt b/requirements_all.txt index 37d68deecfc8d7..b4df475312ed88 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -191,7 +191,7 @@ aiohomekit==0.7.14 aiohttp_cors==0.7.0 # homeassistant.components.hue -aiohue==4.1.2 +aiohue==4.2.0 # homeassistant.components.homewizard aiohwenergy==0.8.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 30166e622eae23..0bea264aa25986 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -141,7 +141,7 @@ aiohomekit==0.7.14 aiohttp_cors==0.7.0 # homeassistant.components.hue -aiohue==4.1.2 +aiohue==4.2.0 # homeassistant.components.homewizard aiohwenergy==0.8.0 From b42676370c38740d9f865892bf4f6f07a7ad9ba8 Mon Sep 17 00:00:00 2001 From: "Craig J. Midwinter" Date: Wed, 16 Feb 2022 12:43:02 -0600 Subject: [PATCH 0715/1098] Bump pysher to 1.0.7 (#59445) * Fix Goalfeed integration See https://github.com/deepbrook/Pysher/issues/62 * update requirements * Update pysher, remove websocket-client requirement for goalfeed integration --- homeassistant/components/goalfeed/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/goalfeed/manifest.json b/homeassistant/components/goalfeed/manifest.json index a8d90d87ac3392..68e12887daf72b 100644 --- a/homeassistant/components/goalfeed/manifest.json +++ b/homeassistant/components/goalfeed/manifest.json @@ -2,7 +2,7 @@ "domain": "goalfeed", "name": "Goalfeed", "documentation": "https://www.home-assistant.io/integrations/goalfeed", - "requirements": ["pysher==1.0.1"], + "requirements": ["pysher==1.0.7"], "codeowners": [], "iot_class": "cloud_push", "loggers": ["pysher"] diff --git a/requirements_all.txt b/requirements_all.txt index b4df475312ed88..c98fb222b5f78a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1834,7 +1834,7 @@ pyserial==3.5 pysesame2==1.0.1 # homeassistant.components.goalfeed -pysher==1.0.1 +pysher==1.0.7 # homeassistant.components.sia pysiaalarm==3.0.2 From 499081df86ce3888d0a23e6d18e5f9df93823ac8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 16 Feb 2022 12:10:26 -0800 Subject: [PATCH 0716/1098] Cloud to avoid setting up Alexa/Google during setup phase (#66676) --- homeassistant/components/cloud/alexa_config.py | 6 +++++- homeassistant/components/cloud/google_config.py | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/cloud/alexa_config.py b/homeassistant/components/cloud/alexa_config.py index 56f49307662248..11b122e2b5a071 100644 --- a/homeassistant/components/cloud/alexa_config.py +++ b/homeassistant/components/cloud/alexa_config.py @@ -187,7 +187,11 @@ async def _async_prefs_updated(self, prefs): self._alexa_sync_unsub = None return - if ALEXA_DOMAIN not in self.hass.config.components and self.enabled: + if ( + ALEXA_DOMAIN not in self.hass.config.components + and self.enabled + and self.hass.is_running + ): await async_setup_component(self.hass, ALEXA_DOMAIN, {}) if self.should_report_state != self.is_reporting_states: diff --git a/homeassistant/components/cloud/google_config.py b/homeassistant/components/cloud/google_config.py index 4ae3b44f1feb8a..7988a648901899 100644 --- a/homeassistant/components/cloud/google_config.py +++ b/homeassistant/components/cloud/google_config.py @@ -181,7 +181,11 @@ async def _async_prefs_updated(self, prefs): self.async_disable_local_sdk() return - if self.enabled and GOOGLE_DOMAIN not in self.hass.config.components: + if ( + self.enabled + and GOOGLE_DOMAIN not in self.hass.config.components + and self.hass.is_running + ): await async_setup_component(self.hass, GOOGLE_DOMAIN, {}) if self.should_report_state != self.is_reporting_state: From 88482d73e3093f4a747721b655853d9031d7a9bc Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 16 Feb 2022 21:11:01 +0100 Subject: [PATCH 0717/1098] Do not pass client session to Brunt (#66671) --- homeassistant/components/brunt/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/homeassistant/components/brunt/__init__.py b/homeassistant/components/brunt/__init__.py index 988a96ce08e8bc..5fe3f7d0012ae9 100644 --- a/homeassistant/components/brunt/__init__.py +++ b/homeassistant/components/brunt/__init__.py @@ -11,7 +11,6 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady -from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import DATA_BAPI, DATA_COOR, DOMAIN, PLATFORMS, REGULAR_INTERVAL @@ -21,11 +20,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Brunt using config flow.""" - session = async_get_clientsession(hass) bapi = BruntClientAsync( username=entry.data[CONF_USERNAME], password=entry.data[CONF_PASSWORD], - session=session, ) try: await bapi.async_login() From 41c43fe639d986b84a4df1194000152b4c0b0215 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Wed, 16 Feb 2022 21:11:50 +0100 Subject: [PATCH 0718/1098] Fix type of value in MQTT binary sensor (#66675) --- homeassistant/components/mqtt/binary_sensor.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index 166b7cda34c5e6..e3c3f10b98fbdd 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -20,6 +20,7 @@ CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_VALUE_TEMPLATE, + STATE_ON, STATE_UNAVAILABLE, STATE_UNKNOWN, ) @@ -106,7 +107,7 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity, RestoreEntity): def __init__(self, hass, config, config_entry, discovery_data): """Initialize the MQTT binary sensor.""" - self._state = None + self._state: bool | None = None self._expiration_trigger = None self._delay_listener = None expire_after = config.get(CONF_EXPIRE_AFTER) @@ -132,7 +133,7 @@ async def async_added_to_hass(self) -> None: _LOGGER.debug("Skip state recovery after reload for %s", self.entity_id) return self._expired = False - self._state = last_state.state + self._state = last_state.state == STATE_ON if self._expiration_trigger: # We might have set up a trigger already after subscribing from From 14e48bac3a4b658f8b81f348428e0ed4b886ca9d Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Wed, 16 Feb 2022 21:13:11 +0100 Subject: [PATCH 0719/1098] Fix SamsungTVWS mocking in samsungtv tests (#66650) Co-authored-by: epenet --- tests/components/samsungtv/conftest.py | 24 +++++++++---------- .../components/samsungtv/test_config_flow.py | 17 +++++++------ .../components/samsungtv/test_media_player.py | 17 +++++++------ 3 files changed, 28 insertions(+), 30 deletions(-) diff --git a/tests/components/samsungtv/conftest.py b/tests/components/samsungtv/conftest.py index f14a0f71bf1c41..14f33524f52865 100644 --- a/tests/components/samsungtv/conftest.py +++ b/tests/components/samsungtv/conftest.py @@ -2,6 +2,8 @@ from unittest.mock import Mock, patch import pytest +from samsungctl import Remote +from samsungtvws import SamsungTVWS import homeassistant.util.dt as dt_util @@ -20,10 +22,9 @@ def fake_host_fixture() -> None: def remote_fixture(): """Patch the samsungctl Remote.""" with patch("homeassistant.components.samsungtv.bridge.Remote") as remote_class: - remote = Mock() + remote = Mock(Remote) remote.__enter__ = Mock() remote.__exit__ = Mock() - remote.port.return_value = 55000 remote_class.return_value = remote yield remote @@ -34,10 +35,9 @@ def remotews_fixture(): with patch( "homeassistant.components.samsungtv.bridge.SamsungTVWS" ) as remotews_class: - remotews = Mock() - remotews.__enter__ = Mock() + remotews = Mock(SamsungTVWS) + remotews.__enter__ = Mock(return_value=remotews) remotews.__exit__ = Mock() - remotews.port.return_value = 8002 remotews.rest_device_info.return_value = { "id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4", "device": { @@ -48,8 +48,8 @@ def remotews_fixture(): "networkType": "wireless", }, } + remotews.token = "FAKE_TOKEN" remotews_class.return_value = remotews - remotews_class().__enter__().token = "FAKE_TOKEN" yield remotews @@ -59,12 +59,12 @@ def remotews_no_device_info_fixture(): with patch( "homeassistant.components.samsungtv.bridge.SamsungTVWS" ) as remotews_class: - remotews = Mock() - remotews.__enter__ = Mock() + remotews = Mock(SamsungTVWS) + remotews.__enter__ = Mock(return_value=remotews) remotews.__exit__ = Mock() remotews.rest_device_info.return_value = None + remotews.token = "FAKE_TOKEN" remotews_class.return_value = remotews - remotews_class().__enter__().token = "FAKE_TOKEN" yield remotews @@ -74,8 +74,8 @@ def remotews_soundbar_fixture(): with patch( "homeassistant.components.samsungtv.bridge.SamsungTVWS" ) as remotews_class: - remotews = Mock() - remotews.__enter__ = Mock() + remotews = Mock(SamsungTVWS) + remotews.__enter__ = Mock(return_value=remotews) remotews.__exit__ = Mock() remotews.rest_device_info.return_value = { "id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4", @@ -87,8 +87,8 @@ def remotews_soundbar_fixture(): "type": "Samsung SoundBar", }, } + remotews.token = "FAKE_TOKEN" remotews_class.return_value = remotews - remotews_class().__enter__().token = "FAKE_TOKEN" yield remotews diff --git a/tests/components/samsungtv/test_config_flow.py b/tests/components/samsungtv/test_config_flow.py index 57bb99354dc091..a3e16ba81c3e37 100644 --- a/tests/components/samsungtv/test_config_flow.py +++ b/tests/components/samsungtv/test_config_flow.py @@ -1,8 +1,9 @@ """Tests for Samsung TV config flow.""" import socket -from unittest.mock import Mock, PropertyMock, call, patch +from unittest.mock import Mock, call, patch from samsungctl.exceptions import AccessDenied, UnhandledResponse +from samsungtvws import SamsungTVWS from samsungtvws.exceptions import ConnectionFailure, HttpApiError from websocket import WebSocketException, WebSocketProtocolException @@ -799,10 +800,8 @@ async def test_autodetect_websocket(hass: HomeAssistant): "homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError("Boom"), ), patch("homeassistant.components.samsungtv.bridge.SamsungTVWS") as remotews: - enter = Mock() - type(enter).token = PropertyMock(return_value="123456789") - remote = Mock() - remote.__enter__ = Mock(return_value=enter) + remote = Mock(SamsungTVWS) + remote.__enter__ = Mock(return_value=remote) remote.__exit__ = Mock(return_value=False) remote.rest_device_info.return_value = { "id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4", @@ -816,6 +815,7 @@ async def test_autodetect_websocket(hass: HomeAssistant): "type": "Samsung SmartTV", }, } + remote.token = "123456789" remotews.return_value = remote result = await hass.config_entries.flow.async_init( @@ -846,10 +846,8 @@ async def test_websocket_no_mac(hass: HomeAssistant): ) as remotews, patch( "getmac.get_mac_address", return_value="gg:hh:ii:ll:mm:nn" ): - enter = Mock() - type(enter).token = PropertyMock(return_value="123456789") - remote = Mock() - remote.__enter__ = Mock(return_value=enter) + remote = Mock(SamsungTVWS) + remote.__enter__ = Mock(return_value=remote) remote.__exit__ = Mock(return_value=False) remote.rest_device_info.return_value = { "id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4", @@ -861,6 +859,7 @@ async def test_websocket_no_mac(hass: HomeAssistant): "type": "Samsung SmartTV", }, } + remote.token = "123456789" remotews.return_value = remote result = await hass.config_entries.flow.async_init( diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py index 2d8c07c22cf9c8..aab137a9a2c6b3 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -2,10 +2,11 @@ import asyncio from datetime import timedelta import logging -from unittest.mock import DEFAULT as DEFAULT_MOCK, Mock, PropertyMock, call, patch +from unittest.mock import DEFAULT as DEFAULT_MOCK, Mock, call, patch import pytest from samsungctl import exceptions +from samsungtvws import SamsungTVWS from samsungtvws.exceptions import ConnectionFailure from websocket import WebSocketException @@ -151,10 +152,8 @@ async def test_setup_without_turnon(hass, remote): async def test_setup_websocket(hass, remotews): """Test setup of platform.""" with patch("homeassistant.components.samsungtv.bridge.SamsungTVWS") as remote_class: - enter = Mock() - type(enter).token = PropertyMock(return_value="987654321") - remote = Mock() - remote.__enter__ = Mock(return_value=enter) + remote = Mock(SamsungTVWS) + remote.__enter__ = Mock(return_value=remote) remote.__exit__ = Mock() remote.rest_device_info.return_value = { "id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4", @@ -166,6 +165,7 @@ async def test_setup_websocket(hass, remotews): "networkType": "wireless", }, } + remote.token = "987654321" remote_class.return_value = remote await setup_samsungtv(hass, MOCK_CONFIGWS) @@ -200,10 +200,8 @@ async def test_setup_websocket_2(hass, mock_now): assert entry is config_entries[0] with patch("homeassistant.components.samsungtv.bridge.SamsungTVWS") as remote_class: - enter = Mock() - type(enter).token = PropertyMock(return_value="987654321") - remote = Mock() - remote.__enter__ = Mock(return_value=enter) + remote = Mock(SamsungTVWS) + remote.__enter__ = Mock(return_value=remote) remote.__exit__ = Mock() remote.rest_device_info.return_value = { "id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4", @@ -215,6 +213,7 @@ async def test_setup_websocket_2(hass, mock_now): "networkType": "wireless", }, } + remote.token = "987654321" remote_class.return_value = remote assert await async_setup_component(hass, SAMSUNGTV_DOMAIN, {}) await hass.async_block_till_done() From ef34f070ee181c8d7e3b1242909c16167ba78f83 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 16 Feb 2022 21:13:36 +0100 Subject: [PATCH 0720/1098] Allow metadata in service call data (#66672) Co-authored-by: Paulus Schoutsen Co-authored-by: Erik --- homeassistant/helpers/config_validation.py | 2 ++ tests/helpers/test_config_validation.py | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index ed3c50cdb0020a..cbd1f4ffabaf1d 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1058,6 +1058,8 @@ def script_action(value: Any) -> dict: ), vol.Optional(CONF_ENTITY_ID): comp_entity_ids, vol.Optional(CONF_TARGET): vol.Any(TARGET_SERVICE_FIELDS, dynamic_template), + # The frontend stores data here. Don't use in core. + vol.Remove("metadata"): dict, } ), has_at_least_one_key(CONF_SERVICE, CONF_SERVICE_TEMPLATE), diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 62ae79ec5cc49e..daa8d11d601337 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -410,10 +410,16 @@ def test_service_schema(): "entity_id": "all", "alias": "turn on kitchen lights", }, + {"service": "scene.turn_on", "metadata": {}}, ) for value in options: cv.SERVICE_SCHEMA(value) + # Check metadata is removed from the validated output + assert cv.SERVICE_SCHEMA({"service": "scene.turn_on", "metadata": {}}) == { + "service": "scene.turn_on" + } + def test_entity_service_schema(): """Test make_entity_service_schema validation.""" From 8357dc0f3f1f28cf6c85bab5a090b7cdd86508e0 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Wed, 16 Feb 2022 21:18:38 +0100 Subject: [PATCH 0721/1098] Fix last_activated timestamp on Hue scenes (#66679) --- homeassistant/components/hue/scene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/hue/scene.py b/homeassistant/components/hue/scene.py index 8e894b4e2957ef..c21a96e4d9a760 100644 --- a/homeassistant/components/hue/scene.py +++ b/homeassistant/components/hue/scene.py @@ -72,7 +72,7 @@ def async_add_entity(event_type: EventType, resource: HueScene) -> None: vol.Coerce(int), vol.Range(min=0, max=255) ), }, - "async_activate", + "_async_activate", ) From 2d33e435b9e4e128d13bcb57f4e5c9057ce4e4a8 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Wed, 16 Feb 2022 21:28:01 +0100 Subject: [PATCH 0722/1098] Fix token refresh in samsungtv (#66533) --- homeassistant/components/samsungtv/bridge.py | 7 +++++++ tests/components/samsungtv/test_media_player.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/samsungtv/bridge.py b/homeassistant/components/samsungtv/bridge.py index 54f1cc422a33d0..ed520acc1f7d5f 100644 --- a/homeassistant/components/samsungtv/bridge.py +++ b/homeassistant/components/samsungtv/bridge.py @@ -358,6 +358,13 @@ def _get_remote(self, avoid_open: bool = False) -> Remote: self._notify_callback() except (WebSocketException, OSError): self._remote = None + else: + if self.token != self._remote.token: + LOGGER.debug( + "SamsungTVWSBridge has provided a new token %s", + self._remote.token, + ) + self.token = self._remote.token return self._remote def stop(self) -> None: diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py index aab137a9a2c6b3..a274e0bbc9870f 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -165,7 +165,7 @@ async def test_setup_websocket(hass, remotews): "networkType": "wireless", }, } - remote.token = "987654321" + remote.token = "123456789" remote_class.return_value = remote await setup_samsungtv(hass, MOCK_CONFIGWS) From 0138caa277f96414d89d544e8e350357ecd2f53c Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Wed, 16 Feb 2022 21:40:11 +0100 Subject: [PATCH 0723/1098] Fix side_effect patching in samsungtv tests (#66651) Co-authored-by: epenet --- .../components/samsungtv/test_config_flow.py | 26 ++++++++++--------- .../components/samsungtv/test_media_player.py | 5 +--- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/tests/components/samsungtv/test_config_flow.py b/tests/components/samsungtv/test_config_flow.py index a3e16ba81c3e37..c33424ed45bbae 100644 --- a/tests/components/samsungtv/test_config_flow.py +++ b/tests/components/samsungtv/test_config_flow.py @@ -267,7 +267,7 @@ async def test_user_websocket_not_supported(hass: HomeAssistant): "homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError("Boom"), ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWS", + "homeassistant.components.samsungtv.bridge.SamsungTVWS.open", side_effect=WebSocketProtocolException("Boom"), ): # websocket device not supported @@ -284,7 +284,7 @@ async def test_user_not_successful(hass: HomeAssistant): "homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError("Boom"), ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWS", + "homeassistant.components.samsungtv.bridge.SamsungTVWS.open", side_effect=OSError("Boom"), ): result = await hass.config_entries.flow.async_init( @@ -300,7 +300,7 @@ async def test_user_not_successful_2(hass: HomeAssistant): "homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError("Boom"), ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWS", + "homeassistant.components.samsungtv.bridge.SamsungTVWS.open", side_effect=ConnectionFailure("Boom"), ): result = await hass.config_entries.flow.async_init( @@ -455,7 +455,7 @@ async def test_ssdp_websocket_not_supported(hass: HomeAssistant): "homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError("Boom"), ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWS", + "homeassistant.components.samsungtv.bridge.SamsungTVWS.open", side_effect=WebSocketProtocolException("Boom"), ): # device not supported @@ -485,7 +485,7 @@ async def test_ssdp_not_successful(hass: HomeAssistant, no_mac_address: Mock): "homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError("Boom"), ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWS", + "homeassistant.components.samsungtv.bridge.SamsungTVWS.open", side_effect=OSError("Boom"), ), patch( "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.device_info", @@ -513,7 +513,7 @@ async def test_ssdp_not_successful_2(hass: HomeAssistant, no_mac_address: Mock): "homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError("Boom"), ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWS", + "homeassistant.components.samsungtv.bridge.SamsungTVWS.open", side_effect=ConnectionFailure("Boom"), ), patch( "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.device_info", @@ -925,12 +925,17 @@ async def test_autodetect_legacy(hass: HomeAssistant, remote: Mock): async def test_autodetect_none(hass: HomeAssistant): """Test for send key with autodetection of protocol.""" + mock_remotews = Mock() + mock_remotews.__enter__ = Mock(return_value=mock_remotews) + mock_remotews.__exit__ = Mock() + mock_remotews.open = Mock(side_effect=OSError("Boom")) + with patch( "homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError("Boom"), ) as remote, patch( "homeassistant.components.samsungtv.bridge.SamsungTVWS", - side_effect=OSError("Boom"), + return_value=mock_remotews, ) as remotews: result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA @@ -1221,10 +1226,7 @@ async def test_form_reauth_websocket_cannot_connect(hass, remotews: Mock): assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWS", - side_effect=ConnectionFailure, - ): + with patch.object(remotews, "open", side_effect=ConnectionFailure): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {}, @@ -1257,7 +1259,7 @@ async def test_form_reauth_websocket_not_supported(hass): assert result["errors"] == {} with patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWS", + "homeassistant.components.samsungtv.bridge.SamsungTVWS.open", side_effect=WebSocketException, ): result2 = await hass.config_entries.flow.async_configure( diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py index a274e0bbc9870f..ed09d9e33e0657 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -296,10 +296,7 @@ async def test_update_connection_failure(hass, remotews, mock_now): ): await setup_samsungtv(hass, MOCK_CONFIGWS) - with patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWS", - side_effect=ConnectionFailure("Boom"), - ): + with patch.object(remotews, "open", side_effect=ConnectionFailure("Boom")): next_update = mock_now + timedelta(minutes=5) with patch("homeassistant.util.dt.utcnow", return_value=next_update): async_fire_time_changed(hass, next_update) From eaf73318e124df0b8bf6626a1f4de6b04a8502b9 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 16 Feb 2022 22:04:49 +0100 Subject: [PATCH 0724/1098] Remove duplicated options from input_select (#66680) --- .../components/input_select/__init__.py | 42 ++++- tests/components/input_select/test_init.py | 163 +++++++++++++++++- 2 files changed, 200 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/input_select/__init__.py b/homeassistant/components/input_select/__init__.py index 4f974d2c182691..288c6d6e588bcc 100644 --- a/homeassistant/components/input_select/__init__.py +++ b/homeassistant/components/input_select/__init__.py @@ -16,6 +16,7 @@ SERVICE_RELOAD, ) from homeassistant.core import HomeAssistant, ServiceCall, callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import collection import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_component import EntityComponent @@ -42,6 +43,7 @@ SERVICE_SET_OPTIONS = "set_options" STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 +STORAGE_VERSION_MINOR = 2 CREATE_FIELDS = { vol.Required(CONF_NAME): vol.All(str, vol.Length(min=1)), @@ -57,6 +59,20 @@ } +def _remove_duplicates(options: list[str], name: str | None) -> list[str]: + """Remove duplicated options.""" + unique_options = list(dict.fromkeys(options)) + # This check was added in 2022.3 + # Reject YAML configured input_select with duplicates from 2022.6 + if len(unique_options) != len(options): + _LOGGER.warning( + "Input select '%s' with options %s had duplicated options, the duplicates have been removed", + name or "", + options, + ) + return unique_options + + def _cv_input_select(cfg: dict[str, Any]) -> dict[str, Any]: """Configure validation helper for input select (voluptuous).""" options = cfg[CONF_OPTIONS] @@ -65,6 +81,7 @@ def _cv_input_select(cfg: dict[str, Any]) -> dict[str, Any]: raise vol.Invalid( f"initial state {initial} is not part of the options: {','.join(options)}" ) + cfg[CONF_OPTIONS] = _remove_duplicates(options, cfg.get(CONF_NAME)) return cfg @@ -89,6 +106,23 @@ def _cv_input_select(cfg: dict[str, Any]) -> dict[str, Any]: RELOAD_SERVICE_SCHEMA = vol.Schema({}) +class InputSelectStore(Store): + """Store entity registry data.""" + + async def _async_migrate_func( + self, old_major_version: int, old_minor_version: int, old_data: dict[str, Any] + ) -> dict[str, Any]: + """Migrate to the new version.""" + if old_major_version == 1: + if old_minor_version < 2: + for item in old_data["items"]: + options = item[ATTR_OPTIONS] + item[ATTR_OPTIONS] = _remove_duplicates( + options, item.get(CONF_NAME) + ) + return old_data + + async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up an input select.""" component = EntityComponent(_LOGGER, DOMAIN, hass) @@ -102,7 +136,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ) storage_collection = InputSelectStorageCollection( - Store(hass, STORAGE_VERSION, STORAGE_KEY), + InputSelectStore( + hass, STORAGE_VERSION, STORAGE_KEY, minor_version=STORAGE_VERSION_MINOR + ), logging.getLogger(f"{__name__}.storage_collection"), id_manager, ) @@ -301,6 +337,10 @@ def async_previous(self, cycle: bool) -> None: async def async_set_options(self, options: list[str]) -> None: """Set options.""" + unique_options = list(dict.fromkeys(options)) + if len(unique_options) != len(options): + raise HomeAssistantError(f"Duplicated options: {options}") + self._attr_options = options if self.current_option not in self.options: diff --git a/tests/components/input_select/test_init.py b/tests/components/input_select/test_init.py index 783c4c2b9e4ba8..7a239bac45b003 100644 --- a/tests/components/input_select/test_init.py +++ b/tests/components/input_select/test_init.py @@ -15,6 +15,8 @@ SERVICE_SELECT_OPTION, SERVICE_SELECT_PREVIOUS, SERVICE_SET_OPTIONS, + STORAGE_VERSION, + STORAGE_VERSION_MINOR, ) from homeassistant.const import ( ATTR_EDITABLE, @@ -25,7 +27,7 @@ SERVICE_RELOAD, ) from homeassistant.core import Context, State -from homeassistant.exceptions import Unauthorized +from homeassistant.exceptions import HomeAssistantError, Unauthorized from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component @@ -36,11 +38,12 @@ def storage_setup(hass, hass_storage): """Storage setup.""" - async def _storage(items=None, config=None): + async def _storage(items=None, config=None, minor_version=STORAGE_VERSION_MINOR): if items is None: hass_storage[DOMAIN] = { "key": DOMAIN, - "version": 1, + "version": STORAGE_VERSION, + "minor_version": minor_version, "data": { "items": [ { @@ -55,6 +58,7 @@ async def _storage(items=None, config=None): hass_storage[DOMAIN] = { "key": DOMAIN, "version": 1, + "minor_version": minor_version, "data": {"items": items}, } if config is None: @@ -320,6 +324,46 @@ async def test_set_options_service(hass): assert state.state == "test2" +async def test_set_options_service_duplicate(hass): + """Test set_options service with duplicates.""" + assert await async_setup_component( + hass, + DOMAIN, + { + DOMAIN: { + "test_1": { + "options": ["first option", "middle option", "last option"], + "initial": "middle option", + } + } + }, + ) + entity_id = "input_select.test_1" + + state = hass.states.get(entity_id) + assert state.state == "middle option" + assert state.attributes[ATTR_OPTIONS] == [ + "first option", + "middle option", + "last option", + ] + + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + DOMAIN, + SERVICE_SET_OPTIONS, + {ATTR_OPTIONS: ["option1", "option1"], ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) + state = hass.states.get(entity_id) + assert state.state == "middle option" + assert state.attributes[ATTR_OPTIONS] == [ + "first option", + "middle option", + "last option", + ] + + async def test_restore_state(hass): """Ensure states are restored on startup.""" mock_restore_cache( @@ -488,6 +532,34 @@ async def test_load_from_storage(hass, storage_setup): assert state.state == "storage option 1" assert state.attributes.get(ATTR_FRIENDLY_NAME) == "from storage" assert state.attributes.get(ATTR_EDITABLE) + assert state.attributes.get(ATTR_OPTIONS) == [ + "storage option 1", + "storage option 2", + ] + + +async def test_load_from_storage_duplicate(hass, storage_setup, caplog): + """Test set up from old storage with duplicates.""" + items = [ + { + "id": "from_storage", + "name": "from storage", + "options": ["yaml update 1", "yaml update 2", "yaml update 2"], + } + ] + assert await storage_setup(items, minor_version=1) + + assert ( + "Input select 'from storage' with options " + "['yaml update 1', 'yaml update 2', 'yaml update 2'] " + "had duplicated options, the duplicates have been removed" + ) in caplog.text + + state = hass.states.get(f"{DOMAIN}.from_storage") + assert state.state == "yaml update 1" + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "from storage" + assert state.attributes.get(ATTR_EDITABLE) + assert state.attributes.get(ATTR_OPTIONS) == ["yaml update 1", "yaml update 2"] async def test_editable_state_attribute(hass, storage_setup): @@ -554,7 +626,7 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup): async def test_update(hass, hass_ws_client, storage_setup): - """Test updating min/max updates the state.""" + """Test updating options updates the state.""" items = [ { @@ -590,6 +662,7 @@ async def test_update(hass, hass_ws_client, storage_setup): state = hass.states.get(input_entity_id) assert state.attributes[ATTR_OPTIONS] == ["new option", "newer option"] + # Should fail because the initial state is now invalid await client.send_json( { "id": 7, @@ -602,6 +675,50 @@ async def test_update(hass, hass_ws_client, storage_setup): assert not resp["success"] +async def test_update_duplicates(hass, hass_ws_client, storage_setup, caplog): + """Test updating options updates the state.""" + + items = [ + { + "id": "from_storage", + "name": "from storage", + "options": ["yaml update 1", "yaml update 2"], + } + ] + assert await storage_setup(items) + + input_id = "from_storage" + input_entity_id = f"{DOMAIN}.{input_id}" + ent_reg = er.async_get(hass) + + state = hass.states.get(input_entity_id) + assert state.attributes[ATTR_OPTIONS] == ["yaml update 1", "yaml update 2"] + assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, input_id) is not None + + client = await hass_ws_client(hass) + + await client.send_json( + { + "id": 6, + "type": f"{DOMAIN}/update", + f"{DOMAIN}_id": f"{input_id}", + "options": ["new option", "newer option", "newer option"], + CONF_INITIAL: "newer option", + } + ) + resp = await client.receive_json() + assert resp["success"] + + assert ( + "Input select 'from storage' with options " + "['new option', 'newer option', 'newer option'] " + "had duplicated options, the duplicates have been removed" + ) in caplog.text + + state = hass.states.get(input_entity_id) + assert state.attributes[ATTR_OPTIONS] == ["new option", "newer option"] + + async def test_ws_create(hass, hass_ws_client, storage_setup): """Test create WS.""" assert await storage_setup(items=[]) @@ -630,6 +747,44 @@ async def test_ws_create(hass, hass_ws_client, storage_setup): state = hass.states.get(input_entity_id) assert state.state == "even newer option" + assert state.attributes[ATTR_OPTIONS] == ["new option", "even newer option"] + + +async def test_ws_create_duplicates(hass, hass_ws_client, storage_setup, caplog): + """Test create WS with duplicates.""" + assert await storage_setup(items=[]) + + input_id = "new_input" + input_entity_id = f"{DOMAIN}.{input_id}" + ent_reg = er.async_get(hass) + + state = hass.states.get(input_entity_id) + assert state is None + assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, input_id) is None + + client = await hass_ws_client(hass) + + await client.send_json( + { + "id": 6, + "type": f"{DOMAIN}/create", + "name": "New Input", + "options": ["new option", "even newer option", "even newer option"], + "initial": "even newer option", + } + ) + resp = await client.receive_json() + assert resp["success"] + + assert ( + "Input select 'New Input' with options " + "['new option', 'even newer option', 'even newer option'] " + "had duplicated options, the duplicates have been removed" + ) in caplog.text + + state = hass.states.get(input_entity_id) + assert state.state == "even newer option" + assert state.attributes[ATTR_OPTIONS] == ["new option", "even newer option"] async def test_setup_no_config(hass, hass_admin_user): From 58742f8be654d5be5233de6408bb2cd47ba7b5ec Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 16 Feb 2022 22:35:51 +0100 Subject: [PATCH 0725/1098] Update plugwise 0.16.5 (#66684) --- homeassistant/components/plugwise/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- script/pip_check | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/plugwise/manifest.json b/homeassistant/components/plugwise/manifest.json index 6acb8c7fca58d0..b7db3880a5e048 100644 --- a/homeassistant/components/plugwise/manifest.json +++ b/homeassistant/components/plugwise/manifest.json @@ -2,7 +2,7 @@ "domain": "plugwise", "name": "Plugwise", "documentation": "https://www.home-assistant.io/integrations/plugwise", - "requirements": ["plugwise==0.16.4"], + "requirements": ["plugwise==0.16.5"], "codeowners": ["@CoMPaTech", "@bouwew", "@brefra", "@frenck"], "zeroconf": ["_plugwise._tcp.local."], "config_flow": true, diff --git a/requirements_all.txt b/requirements_all.txt index c98fb222b5f78a..be3a0db1d4bdf0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1267,7 +1267,7 @@ plexauth==0.0.6 plexwebsocket==0.0.13 # homeassistant.components.plugwise -plugwise==0.16.4 +plugwise==0.16.5 # homeassistant.components.plum_lightpad plumlightpad==0.0.11 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0bea264aa25986..e35ee84bb8510f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -792,7 +792,7 @@ plexauth==0.0.6 plexwebsocket==0.0.13 # homeassistant.components.plugwise -plugwise==0.16.4 +plugwise==0.16.5 # homeassistant.components.plum_lightpad plumlightpad==0.0.11 diff --git a/script/pip_check b/script/pip_check index af47f101fbba5b..c30a7382f27335 100755 --- a/script/pip_check +++ b/script/pip_check @@ -3,7 +3,7 @@ PIP_CACHE=$1 # Number of existing dependency conflicts # Update if a PR resolve one! -DEPENDENCY_CONFLICTS=9 +DEPENDENCY_CONFLICTS=10 PIP_CHECK=$(pip check --cache-dir=$PIP_CACHE) LINE_COUNT=$(echo "$PIP_CHECK" | wc -l) From 2687f61428d804ed719f7ec98653992ef415f913 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Wed, 16 Feb 2022 23:04:50 +0100 Subject: [PATCH 0726/1098] Fix slow samsungtv test (#66696) --- tests/components/samsungtv/test_config_flow.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/components/samsungtv/test_config_flow.py b/tests/components/samsungtv/test_config_flow.py index c33424ed45bbae..690e47ea816886 100644 --- a/tests/components/samsungtv/test_config_flow.py +++ b/tests/components/samsungtv/test_config_flow.py @@ -455,8 +455,9 @@ async def test_ssdp_websocket_not_supported(hass: HomeAssistant): "homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError("Boom"), ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWS.open", - side_effect=WebSocketProtocolException("Boom"), + "homeassistant.components.samsungtv.bridge.SamsungTVWS", + ) as remotews, patch.object( + remotews, "open", side_effect=WebSocketProtocolException("Boom") ): # device not supported result = await hass.config_entries.flow.async_init( From be0ef5ad6c437ece3443cf4dfda1f719b299d0f0 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 16 Feb 2022 23:07:21 +0100 Subject: [PATCH 0727/1098] Correct MQTT binary_sensor and sensor state restoring (#66690) --- homeassistant/components/mqtt/binary_sensor.py | 7 +++---- homeassistant/components/mqtt/sensor.py | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index e3c3f10b98fbdd..40d5c876c111d4 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -126,6 +126,9 @@ async def async_added_to_hass(self) -> None: and expire_after > 0 and (last_state := await self.async_get_last_state()) is not None and last_state.state not in [STATE_UNKNOWN, STATE_UNAVAILABLE] + # We might have set up a trigger already after subscribing from + # super().async_added_to_hass(), then we should not restore state + and not self._expiration_trigger ): expiration_at = last_state.last_changed + timedelta(seconds=expire_after) if expiration_at < (time_now := dt_util.utcnow()): @@ -135,10 +138,6 @@ async def async_added_to_hass(self) -> None: self._expired = False self._state = last_state.state == STATE_ON - if self._expiration_trigger: - # We might have set up a trigger already after subscribing from - # super().async_added_to_hass() - self._expiration_trigger() self._expiration_trigger = async_track_point_in_utc_time( self.hass, self._value_is_expired, expiration_at ) diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index 8dd5390175567a..31a784a259e9f9 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -172,6 +172,9 @@ async def async_added_to_hass(self) -> None: and expire_after > 0 and (last_state := await self.async_get_last_state()) is not None and last_state.state not in [STATE_UNKNOWN, STATE_UNAVAILABLE] + # We might have set up a trigger already after subscribing from + # super().async_added_to_hass(), then we should not restore state + and not self._expiration_trigger ): expiration_at = last_state.last_changed + timedelta(seconds=expire_after) if expiration_at < (time_now := dt_util.utcnow()): @@ -181,10 +184,6 @@ async def async_added_to_hass(self) -> None: self._expired = False self._state = last_state.state - if self._expiration_trigger: - # We might have set up a trigger already after subscribing from - # super().async_added_to_hass() - self._expiration_trigger() self._expiration_trigger = async_track_point_in_utc_time( self.hass, self._value_is_expired, expiration_at ) From bccfaceedbbf1c8018d9bdde5f0fb28719b0d40d Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Thu, 17 Feb 2022 08:38:05 +1000 Subject: [PATCH 0728/1098] Code Quality improvements for Aussie Broadband (#65408) Co-authored-by: Martin Hjelmare --- .../components/aussie_broadband/__init__.py | 15 +-- .../aussie_broadband/config_flow.py | 99 +++------------ .../components/aussie_broadband/sensor.py | 58 +++++---- .../components/aussie_broadband/strings.json | 2 +- .../aussie_broadband/translations/en.json | 2 +- .../aussie_broadband/test_config_flow.py | 115 +----------------- 6 files changed, 62 insertions(+), 229 deletions(-) diff --git a/homeassistant/components/aussie_broadband/__init__.py b/homeassistant/components/aussie_broadband/__init__.py index af2969f6f7aebc..f3a07616d93ed7 100644 --- a/homeassistant/components/aussie_broadband/__init__.py +++ b/homeassistant/components/aussie_broadband/__init__.py @@ -15,7 +15,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import CONF_SERVICES, DEFAULT_UPDATE_INTERVAL, DOMAIN, SERVICE_ID +from .const import DEFAULT_UPDATE_INTERVAL, DOMAIN, SERVICE_ID _LOGGER = logging.getLogger(__name__) PLATFORMS = [Platform.SENSOR] @@ -31,17 +31,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) try: await client.login() - all_services = await client.get_services() + services = await client.get_services() except AuthenticationException as exc: raise ConfigEntryAuthFailed() from exc except ClientError as exc: raise ConfigEntryNotReady() from exc - # Filter the service list to those that are enabled in options - services = [ - s for s in all_services if str(s["service_id"]) in entry.options[CONF_SERVICES] - ] - # Create an appropriate refresh function def update_data_factory(service_id): async def async_update_data(): @@ -71,16 +66,10 @@ async def async_update_data(): "services": services, } hass.config_entries.async_setup_platforms(entry, PLATFORMS) - entry.async_on_unload(entry.add_update_listener(update_listener)) return True -async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Reload to update options.""" - await hass.config_entries.async_reload(entry.entry_id) - - async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload the config entry.""" unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/aussie_broadband/config_flow.py b/homeassistant/components/aussie_broadband/config_flow.py index c18e808e6ad24d..5eaf39853b5a86 100644 --- a/homeassistant/components/aussie_broadband/config_flow.py +++ b/homeassistant/components/aussie_broadband/config_flow.py @@ -9,12 +9,10 @@ from homeassistant import config_entries from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession -import homeassistant.helpers.config_validation as cv -from .const import CONF_SERVICES, DOMAIN, SERVICE_ID +from .const import CONF_SERVICES, DOMAIN class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -39,11 +37,11 @@ async def async_auth(self, user_input: dict[str, str]) -> dict[str, str] | None: ) try: await self.client.login() - return None except AuthenticationException: return {"base": "invalid_auth"} except ClientError: return {"base": "cannot_connect"} + return None async def async_step_user( self, user_input: dict[str, Any] | None = None @@ -61,15 +59,10 @@ async def async_step_user( if not self.services: return self.async_abort(reason="no_services_found") - if len(self.services) == 1: - return self.async_create_entry( - title=self.data[CONF_USERNAME], - data=self.data, - options={CONF_SERVICES: [str(self.services[0][SERVICE_ID])]}, - ) - - # Account has more than one service, select service to add - return await self.async_step_service() + return self.async_create_entry( + title=self.data[CONF_USERNAME], + data=self.data, + ) return self.async_show_form( step_id="user", @@ -82,37 +75,20 @@ async def async_step_user( errors=errors, ) - async def async_step_service( - self, user_input: dict[str, Any] | None = None - ) -> FlowResult: - """Handle the optional service selection step.""" - if user_input is not None: - return self.async_create_entry( - title=self.data[CONF_USERNAME], data=self.data, options=user_input - ) + async def async_step_reauth(self, user_input: dict[str, str]) -> FlowResult: + """Handle reauth on credential failure.""" + self._reauth_username = user_input[CONF_USERNAME] - service_options = {str(s[SERVICE_ID]): s["description"] for s in self.services} - return self.async_show_form( - step_id="service", - data_schema=vol.Schema( - { - vol.Required( - CONF_SERVICES, default=list(service_options.keys()) - ): cv.multi_select(service_options) - } - ), - errors=None, - ) + return await self.async_step_reauth_confirm() - async def async_step_reauth( - self, user_input: dict[str, Any] | None = None + async def async_step_reauth_confirm( + self, user_input: dict[str, str] | None = None ) -> FlowResult: - """Handle reauth.""" + """Handle users reauth credentials.""" + errors: dict[str, str] | None = None - if user_input and user_input.get(CONF_USERNAME): - self._reauth_username = user_input[CONF_USERNAME] - elif self._reauth_username and user_input and user_input.get(CONF_PASSWORD): + if user_input and self._reauth_username: data = { CONF_USERNAME: self._reauth_username, CONF_PASSWORD: user_input[CONF_PASSWORD], @@ -130,7 +106,7 @@ async def async_step_reauth( return self.async_create_entry(title=self._reauth_username, data=data) return self.async_show_form( - step_id="reauth", + step_id="reauth_confirm", description_placeholders={"username": self._reauth_username}, data_schema=vol.Schema( { @@ -139,46 +115,3 @@ async def async_step_reauth( ), errors=errors, ) - - @staticmethod - @callback - def async_get_options_flow( - config_entry: config_entries.ConfigEntry, - ) -> config_entries.OptionsFlow: - """Get the options flow for this handler.""" - return OptionsFlowHandler(config_entry) - - -class OptionsFlowHandler(config_entries.OptionsFlow): - """Options flow for picking services.""" - - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: - """Initialize options flow.""" - self.config_entry = config_entry - - async def async_step_init(self, user_input=None): - """Manage the options.""" - if user_input is not None: - return self.async_create_entry(title="", data=user_input) - - if self.config_entry.state != config_entries.ConfigEntryState.LOADED: - return self.async_abort(reason="unknown") - data = self.hass.data[DOMAIN][self.config_entry.entry_id] - try: - services = await data["client"].get_services() - except AuthenticationException: - return self.async_abort(reason="invalid_auth") - except ClientError: - return self.async_abort(reason="cannot_connect") - service_options = {str(s[SERVICE_ID]): s["description"] for s in services} - return self.async_show_form( - step_id="init", - data_schema=vol.Schema( - { - vol.Required( - CONF_SERVICES, - default=self.config_entry.options.get(CONF_SERVICES), - ): cv.multi_select(service_options), - } - ), - ) diff --git a/homeassistant/components/aussie_broadband/sensor.py b/homeassistant/components/aussie_broadband/sensor.py index 04c1cff97b5e3c..09946cef03d23d 100644 --- a/homeassistant/components/aussie_broadband/sensor.py +++ b/homeassistant/components/aussie_broadband/sensor.py @@ -1,7 +1,9 @@ """Support for Aussie Broadband metric sensors.""" from __future__ import annotations -from typing import Any +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any, cast from homeassistant.components.sensor import ( SensorEntity, @@ -14,27 +16,36 @@ from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import StateType from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN, SERVICE_ID -SENSOR_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = ( + +@dataclass +class SensorValueEntityDescription(SensorEntityDescription): + """Class describing Aussie Broadband sensor entities.""" + + value: Callable = lambda x: x + + +SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = ( # Internet Services sensors - SensorEntityDescription( + SensorValueEntityDescription( key="usedMb", name="Data Used", state_class=SensorStateClass.TOTAL_INCREASING, native_unit_of_measurement=DATA_MEGABYTES, icon="mdi:network", ), - SensorEntityDescription( + SensorValueEntityDescription( key="downloadedMb", name="Downloaded", state_class=SensorStateClass.TOTAL_INCREASING, native_unit_of_measurement=DATA_MEGABYTES, icon="mdi:download-network", ), - SensorEntityDescription( + SensorValueEntityDescription( key="uploadedMb", name="Uploaded", state_class=SensorStateClass.TOTAL_INCREASING, @@ -42,46 +53,50 @@ icon="mdi:upload-network", ), # Mobile Phone Services sensors - SensorEntityDescription( + SensorValueEntityDescription( key="national", name="National Calls", state_class=SensorStateClass.TOTAL_INCREASING, icon="mdi:phone", + value=lambda x: x.get("calls"), ), - SensorEntityDescription( + SensorValueEntityDescription( key="mobile", name="Mobile Calls", state_class=SensorStateClass.TOTAL_INCREASING, icon="mdi:phone", + value=lambda x: x.get("calls"), ), - SensorEntityDescription( + SensorValueEntityDescription( key="international", name="International Calls", entity_registry_enabled_default=False, state_class=SensorStateClass.TOTAL_INCREASING, icon="mdi:phone-plus", ), - SensorEntityDescription( + SensorValueEntityDescription( key="sms", name="SMS Sent", state_class=SensorStateClass.TOTAL_INCREASING, icon="mdi:message-processing", + value=lambda x: x.get("calls"), ), - SensorEntityDescription( + SensorValueEntityDescription( key="internet", name="Data Used", state_class=SensorStateClass.TOTAL_INCREASING, native_unit_of_measurement=DATA_KILOBYTES, icon="mdi:network", + value=lambda x: x.get("kbytes"), ), - SensorEntityDescription( + SensorValueEntityDescription( key="voicemail", name="Voicemail Calls", entity_registry_enabled_default=False, state_class=SensorStateClass.TOTAL_INCREASING, icon="mdi:phone", ), - SensorEntityDescription( + SensorValueEntityDescription( key="other", name="Other Calls", entity_registry_enabled_default=False, @@ -89,13 +104,13 @@ icon="mdi:phone", ), # Generic sensors - SensorEntityDescription( + SensorValueEntityDescription( key="daysTotal", name="Billing Cycle Length", native_unit_of_measurement=TIME_DAYS, icon="mdi:calendar-range", ), - SensorEntityDescription( + SensorValueEntityDescription( key="daysRemaining", name="Billing Cycle Remaining", native_unit_of_measurement=TIME_DAYS, @@ -122,8 +137,10 @@ async def async_setup_entry( class AussieBroadandSensorEntity(CoordinatorEntity, SensorEntity): """Base class for Aussie Broadband metric sensors.""" + entity_description: SensorValueEntityDescription + def __init__( - self, service: dict[str, Any], description: SensorEntityDescription + self, service: dict[str, Any], description: SensorValueEntityDescription ) -> None: """Initialize the sensor.""" super().__init__(service["coordinator"]) @@ -134,16 +151,13 @@ def __init__( entry_type=DeviceEntryType.SERVICE, identifiers={(DOMAIN, service[SERVICE_ID])}, manufacturer="Aussie Broadband", - configuration_url=f"https://my.aussiebroadband.com.au/#/{service['name']}/{service[SERVICE_ID]}/", + configuration_url=f"https://my.aussiebroadband.com.au/#/{service['name'].lower()}/{service[SERVICE_ID]}/", name=service["description"], model=service["name"], ) @property - def native_value(self): + def native_value(self) -> StateType: """Return the state of the sensor.""" - if self.entity_description.key == "internet": - return self.coordinator.data[self.entity_description.key].get("kbytes") - if self.entity_description.key in ("national", "mobile", "sms"): - return self.coordinator.data[self.entity_description.key].get("calls") - return self.coordinator.data[self.entity_description.key] + parent = self.coordinator.data[self.entity_description.key] + return cast(StateType, self.entity_description.value(parent)) diff --git a/homeassistant/components/aussie_broadband/strings.json b/homeassistant/components/aussie_broadband/strings.json index 42ebcbbdbe8238..d5b7d2f1fa1b07 100644 --- a/homeassistant/components/aussie_broadband/strings.json +++ b/homeassistant/components/aussie_broadband/strings.json @@ -13,7 +13,7 @@ "services": "Services" } }, - "reauth": { + "reauth_confirm": { "title": "[%key:common::config_flow::title::reauth%]", "description": "Update password for {username}", "data": { diff --git a/homeassistant/components/aussie_broadband/translations/en.json b/homeassistant/components/aussie_broadband/translations/en.json index a59a297c2651e3..4d18251f27020a 100644 --- a/homeassistant/components/aussie_broadband/translations/en.json +++ b/homeassistant/components/aussie_broadband/translations/en.json @@ -11,7 +11,7 @@ "unknown": "Unexpected error" }, "step": { - "reauth": { + "reauth_confirm": { "data": { "password": "Password" }, diff --git a/tests/components/aussie_broadband/test_config_flow.py b/tests/components/aussie_broadband/test_config_flow.py index 7e919636b09b24..8a5f6b6763f83f 100644 --- a/tests/components/aussie_broadband/test_config_flow.py +++ b/tests/components/aussie_broadband/test_config_flow.py @@ -4,8 +4,8 @@ from aiohttp import ClientConnectionError from aussiebb.asyncio import AuthenticationException -from homeassistant import config_entries, setup -from homeassistant.components.aussie_broadband.const import CONF_SERVICES, DOMAIN +from homeassistant import config_entries +from homeassistant.components.aussie_broadband.const import DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import ( @@ -14,7 +14,7 @@ RESULT_TYPE_FORM, ) -from .common import FAKE_DATA, FAKE_SERVICES, setup_platform +from .common import FAKE_DATA, FAKE_SERVICES TEST_USERNAME = FAKE_DATA[CONF_USERNAME] TEST_PASSWORD = FAKE_DATA[CONF_PASSWORD] @@ -31,7 +31,7 @@ async def test_form(hass: HomeAssistant) -> None: with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch( "aussiebb.asyncio.AussieBB.login", return_value=True ), patch( - "aussiebb.asyncio.AussieBB.get_services", return_value=[FAKE_SERVICES[0]] + "aussiebb.asyncio.AussieBB.get_services", return_value=FAKE_SERVICES ), patch( "homeassistant.components.aussie_broadband.async_setup_entry", return_value=True, @@ -45,7 +45,6 @@ async def test_form(hass: HomeAssistant) -> None: assert result2["type"] == RESULT_TYPE_CREATE_ENTRY assert result2["title"] == TEST_USERNAME assert result2["data"] == FAKE_DATA - assert result2["options"] == {CONF_SERVICES: ["12345678"]} assert len(mock_setup_entry.mock_calls) == 1 @@ -117,46 +116,6 @@ async def test_no_services(hass: HomeAssistant) -> None: assert len(mock_setup_entry.mock_calls) == 0 -async def test_form_multiple_services(hass: HomeAssistant) -> None: - """Test the config flow with multiple services.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - assert result["type"] == RESULT_TYPE_FORM - assert result["errors"] is None - - with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch( - "aussiebb.asyncio.AussieBB.login", return_value=True - ), patch("aussiebb.asyncio.AussieBB.get_services", return_value=FAKE_SERVICES): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - FAKE_DATA, - ) - await hass.async_block_till_done() - - assert result2["type"] == RESULT_TYPE_FORM - assert result2["step_id"] == "service" - assert result2["errors"] is None - - with patch( - "homeassistant.components.aussie_broadband.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - result3 = await hass.config_entries.flow.async_configure( - result["flow_id"], - {CONF_SERVICES: [FAKE_SERVICES[1]["service_id"]]}, - ) - await hass.async_block_till_done() - - assert result3["type"] == RESULT_TYPE_CREATE_ENTRY - assert result3["title"] == TEST_USERNAME - assert result3["data"] == FAKE_DATA - assert result3["options"] == { - CONF_SERVICES: [FAKE_SERVICES[1]["service_id"]], - } - assert len(mock_setup_entry.mock_calls) == 1 - - async def test_form_invalid_auth(hass: HomeAssistant) -> None: """Test invalid auth is handled.""" result1 = await hass.config_entries.flow.async_init( @@ -196,8 +155,6 @@ async def test_form_network_issue(hass: HomeAssistant) -> None: async def test_reauth(hass: HomeAssistant) -> None: """Test reauth flow.""" - await setup.async_setup_component(hass, "persistent_notification", {}) - # Test reauth but the entry doesn't exist result1 = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data=FAKE_DATA @@ -229,7 +186,7 @@ async def test_reauth(hass: HomeAssistant) -> None: context={"source": config_entries.SOURCE_REAUTH}, data=FAKE_DATA, ) - assert result5["step_id"] == "reauth" + assert result5["step_id"] == "reauth_confirm" with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch( "aussiebb.asyncio.AussieBB.login", side_effect=AuthenticationException() @@ -243,7 +200,7 @@ async def test_reauth(hass: HomeAssistant) -> None: ) await hass.async_block_till_done() - assert result6["step_id"] == "reauth" + assert result6["step_id"] == "reauth_confirm" # Test successful reauth with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch( @@ -260,63 +217,3 @@ async def test_reauth(hass: HomeAssistant) -> None: assert result7["type"] == "abort" assert result7["reason"] == "reauth_successful" - - -async def test_options_flow(hass): - """Test options flow.""" - entry = await setup_platform(hass) - - with patch("aussiebb.asyncio.AussieBB.get_services", return_value=FAKE_SERVICES): - - result1 = await hass.config_entries.options.async_init(entry.entry_id) - assert result1["type"] == RESULT_TYPE_FORM - assert result1["step_id"] == "init" - - result2 = await hass.config_entries.options.async_configure( - result1["flow_id"], - user_input={CONF_SERVICES: []}, - ) - assert result2["type"] == RESULT_TYPE_CREATE_ENTRY - assert entry.options == {CONF_SERVICES: []} - - -async def test_options_flow_auth_failure(hass): - """Test options flow with auth failure.""" - - entry = await setup_platform(hass) - - with patch( - "aussiebb.asyncio.AussieBB.get_services", side_effect=AuthenticationException() - ): - - result1 = await hass.config_entries.options.async_init(entry.entry_id) - assert result1["type"] == RESULT_TYPE_ABORT - assert result1["reason"] == "invalid_auth" - - -async def test_options_flow_network_failure(hass): - """Test options flow with connectivity failure.""" - - entry = await setup_platform(hass) - - with patch( - "aussiebb.asyncio.AussieBB.get_services", side_effect=ClientConnectionError() - ): - - result1 = await hass.config_entries.options.async_init(entry.entry_id) - assert result1["type"] == RESULT_TYPE_ABORT - assert result1["reason"] == "cannot_connect" - - -async def test_options_flow_not_loaded(hass): - """Test the options flow aborts when the entry has unloaded due to a reauth.""" - - entry = await setup_platform(hass) - - with patch( - "aussiebb.asyncio.AussieBB.get_services", side_effect=AuthenticationException() - ): - entry.state = config_entries.ConfigEntryState.NOT_LOADED - result1 = await hass.config_entries.options.async_init(entry.entry_id) - assert result1["type"] == RESULT_TYPE_ABORT - assert result1["reason"] == "unknown" From f89de613d9449f1edd717f00f333dd0546985fb4 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 17 Feb 2022 00:06:42 +0100 Subject: [PATCH 0729/1098] Improve MQTT binary_sensor test (#66688) --- tests/components/mqtt/test_binary_sensor.py | 24 ++++++++++++--------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/tests/components/mqtt/test_binary_sensor.py b/tests/components/mqtt/test_binary_sensor.py index 5fa71d73632ed1..5055550be7ce76 100644 --- a/tests/components/mqtt/test_binary_sensor.py +++ b/tests/components/mqtt/test_binary_sensor.py @@ -887,8 +887,12 @@ async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) +@pytest.mark.parametrize( + "payload1, state1, payload2, state2", + [("ON", "on", "OFF", "off"), ("OFF", "off", "ON", "on")], +) async def test_cleanup_triggers_and_restoring_state( - hass, mqtt_mock, caplog, tmp_path, freezer + hass, mqtt_mock, caplog, tmp_path, freezer, payload1, state1, payload2, state2 ): """Test cleanup old triggers at reloading and restoring the state.""" domain = binary_sensor.DOMAIN @@ -909,13 +913,13 @@ async def test_cleanup_triggers_and_restoring_state( {binary_sensor.DOMAIN: [config1, config2]}, ) await hass.async_block_till_done() - async_fire_mqtt_message(hass, "test-topic1", "ON") + async_fire_mqtt_message(hass, "test-topic1", payload1) state = hass.states.get("binary_sensor.test1") - assert state.state == "on" + assert state.state == state1 - async_fire_mqtt_message(hass, "test-topic2", "ON") + async_fire_mqtt_message(hass, "test-topic2", payload1) state = hass.states.get("binary_sensor.test2") - assert state.state == "on" + assert state.state == state1 freezer.move_to("2022-02-02 12:01:10+01:00") @@ -931,18 +935,18 @@ async def test_cleanup_triggers_and_restoring_state( assert "State recovered after reload for binary_sensor.test2" not in caplog.text state = hass.states.get("binary_sensor.test1") - assert state.state == "on" + assert state.state == state1 state = hass.states.get("binary_sensor.test2") assert state.state == STATE_UNAVAILABLE - async_fire_mqtt_message(hass, "test-topic1", "OFF") + async_fire_mqtt_message(hass, "test-topic1", payload2) state = hass.states.get("binary_sensor.test1") - assert state.state == "off" + assert state.state == state2 - async_fire_mqtt_message(hass, "test-topic2", "OFF") + async_fire_mqtt_message(hass, "test-topic2", payload2) state = hass.states.get("binary_sensor.test2") - assert state.state == "off" + assert state.state == state2 async def test_skip_restoring_state_with_over_due_expire_trigger( From 855076fed90b0e4f506a69f01723bfeea8c4b502 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 17 Feb 2022 00:14:03 +0000 Subject: [PATCH 0730/1098] [ci skip] Translation update --- .../aussie_broadband/translations/en.json | 7 ++++ .../aussie_broadband/translations/es.json | 18 ++++++++ .../aussie_broadband/translations/pt-BR.json | 7 ++++ .../diagnostics/translations/es.json | 3 ++ .../components/dnsip/translations/es.json | 27 ++++++++++++ .../components/elkm1/translations/cs.json | 19 ++++++++- .../components/elkm1/translations/es.json | 13 ++++++ .../components/fan/translations/es.json | 1 + .../components/fivem/translations/cs.json | 19 +++++++++ .../components/fivem/translations/es.json | 9 ++++ .../humidifier/translations/es.json | 1 + .../components/iss/translations/cs.json | 7 ++++ .../components/iss/translations/es.json | 11 +++++ .../components/iss/translations/he.json | 9 ++++ .../components/iss/translations/id.json | 11 ++++- .../components/iss/translations/ru.json | 2 +- .../components/light/translations/es.json | 1 + .../media_player/translations/es.json | 1 + .../moehlenhoff_alpha2/translations/cs.json | 18 ++++++++ .../moehlenhoff_alpha2/translations/es.json | 3 ++ .../components/overkiz/translations/es.json | 1 + .../overkiz/translations/sensor.es.json | 41 +++++++++++++++++++ .../components/picnic/translations/cs.json | 3 +- .../components/picnic/translations/es.json | 1 + .../components/picnic/translations/he.json | 4 +- .../components/powerwall/translations/cs.json | 11 ++++- .../components/remote/translations/es.json | 1 + .../components/sleepiq/translations/ca.json | 19 +++++++++ .../components/sleepiq/translations/he.json | 19 +++++++++ .../sleepiq/translations/pt-BR.json | 19 +++++++++ .../components/sleepiq/translations/ru.json | 19 +++++++++ .../sleepiq/translations/zh-Hant.json | 19 +++++++++ .../components/switch/translations/es.json | 1 + .../tuya/translations/select.ru.json | 4 +- .../components/webostv/translations/es.json | 41 +++++++++++++++++++ .../components/wiz/translations/cs.json | 24 +++++++++++ .../components/wiz/translations/es.json | 7 ++++ .../components/wiz/translations/he.json | 1 + .../components/wiz/translations/id.json | 1 + .../components/zwave_me/translations/cs.json | 14 +++++++ .../components/zwave_me/translations/es.json | 18 ++++++++ 41 files changed, 448 insertions(+), 7 deletions(-) create mode 100644 homeassistant/components/aussie_broadband/translations/es.json create mode 100644 homeassistant/components/diagnostics/translations/es.json create mode 100644 homeassistant/components/dnsip/translations/es.json create mode 100644 homeassistant/components/fivem/translations/cs.json create mode 100644 homeassistant/components/fivem/translations/es.json create mode 100644 homeassistant/components/iss/translations/cs.json create mode 100644 homeassistant/components/iss/translations/es.json create mode 100644 homeassistant/components/moehlenhoff_alpha2/translations/cs.json create mode 100644 homeassistant/components/moehlenhoff_alpha2/translations/es.json create mode 100644 homeassistant/components/overkiz/translations/sensor.es.json create mode 100644 homeassistant/components/sleepiq/translations/ca.json create mode 100644 homeassistant/components/sleepiq/translations/he.json create mode 100644 homeassistant/components/sleepiq/translations/pt-BR.json create mode 100644 homeassistant/components/sleepiq/translations/ru.json create mode 100644 homeassistant/components/sleepiq/translations/zh-Hant.json create mode 100644 homeassistant/components/webostv/translations/es.json create mode 100644 homeassistant/components/wiz/translations/cs.json create mode 100644 homeassistant/components/wiz/translations/es.json create mode 100644 homeassistant/components/zwave_me/translations/cs.json create mode 100644 homeassistant/components/zwave_me/translations/es.json diff --git a/homeassistant/components/aussie_broadband/translations/en.json b/homeassistant/components/aussie_broadband/translations/en.json index 4d18251f27020a..2843916df2e43d 100644 --- a/homeassistant/components/aussie_broadband/translations/en.json +++ b/homeassistant/components/aussie_broadband/translations/en.json @@ -11,6 +11,13 @@ "unknown": "Unexpected error" }, "step": { + "reauth": { + "data": { + "password": "Password" + }, + "description": "Update password for {username}", + "title": "Reauthenticate Integration" + }, "reauth_confirm": { "data": { "password": "Password" diff --git a/homeassistant/components/aussie_broadband/translations/es.json b/homeassistant/components/aussie_broadband/translations/es.json new file mode 100644 index 00000000000000..19640de6aa72ad --- /dev/null +++ b/homeassistant/components/aussie_broadband/translations/es.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "no_services_found": "No se han encontrado servicios para esta cuenta" + }, + "step": { + "reauth": { + "description": "Actualizar la contrase\u00f1a de {username}" + }, + "service": { + "data": { + "services": "Servicios" + }, + "title": "Seleccionar Servicios" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aussie_broadband/translations/pt-BR.json b/homeassistant/components/aussie_broadband/translations/pt-BR.json index 9dbd275d2bcc03..efe1fca7c80d9c 100644 --- a/homeassistant/components/aussie_broadband/translations/pt-BR.json +++ b/homeassistant/components/aussie_broadband/translations/pt-BR.json @@ -18,6 +18,13 @@ "description": "Atualizar senha para {username}", "title": "Reautenticar Integra\u00e7\u00e3o" }, + "reauth_confirm": { + "data": { + "password": "Senha" + }, + "description": "Atualizar senha para {username}", + "title": "Reautenticar Integra\u00e7\u00e3o" + }, "service": { "data": { "services": "Servi\u00e7os" diff --git a/homeassistant/components/diagnostics/translations/es.json b/homeassistant/components/diagnostics/translations/es.json new file mode 100644 index 00000000000000..2ae994c70c9975 --- /dev/null +++ b/homeassistant/components/diagnostics/translations/es.json @@ -0,0 +1,3 @@ +{ + "title": "Diagn\u00f3sticos" +} \ No newline at end of file diff --git a/homeassistant/components/dnsip/translations/es.json b/homeassistant/components/dnsip/translations/es.json new file mode 100644 index 00000000000000..6952329f20e2ee --- /dev/null +++ b/homeassistant/components/dnsip/translations/es.json @@ -0,0 +1,27 @@ +{ + "config": { + "error": { + "invalid_hostname": "Nombre de host inv\u00e1lido" + }, + "step": { + "user": { + "data": { + "hostname": "El nombre de host para el que se realiza la consulta DNS" + } + } + } + }, + "options": { + "error": { + "invalid_resolver": "Direcci\u00f3n IP no v\u00e1lida para resolver" + }, + "step": { + "init": { + "data": { + "resolver": "Resolver para la b\u00fasqueda de IPV4", + "resolver_ipv6": "Resolver para la b\u00fasqueda de IPV6" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elkm1/translations/cs.json b/homeassistant/components/elkm1/translations/cs.json index 2b84b802b6b502..f2f4c17af9ccde 100644 --- a/homeassistant/components/elkm1/translations/cs.json +++ b/homeassistant/components/elkm1/translations/cs.json @@ -2,14 +2,31 @@ "config": { "abort": { "address_already_configured": "ElkM1 s touto adresou je ji\u017e nastaven", - "already_configured": "ElkM1 s t\u00edmto prefixem je ji\u017e nastaven" + "already_configured": "ElkM1 s t\u00edmto prefixem je ji\u017e nastaven", + "already_in_progress": "Konfigurace ji\u017e prob\u00edh\u00e1", + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" }, "error": { "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" }, + "flow_title": "{mac_address} ({host})", "step": { + "discovered_connection": { + "data": { + "password": "Heslo", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + }, + "title": "P\u0159ipojen\u00ed k ovlada\u010di Elk-M1" + }, + "manual_connection": { + "data": { + "password": "Heslo", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + }, + "title": "P\u0159ipojen\u00ed k ovlada\u010di Elk-M1" + }, "user": { "data": { "password": "Heslo", diff --git a/homeassistant/components/elkm1/translations/es.json b/homeassistant/components/elkm1/translations/es.json index eaf987f95e65b0..ecda52c8c5b305 100644 --- a/homeassistant/components/elkm1/translations/es.json +++ b/homeassistant/components/elkm1/translations/es.json @@ -10,9 +10,22 @@ "unknown": "Error inesperado" }, "step": { + "discovered_connection": { + "description": "Con\u00e9ctese al sistema detectado: {mac_address} ({host})" + }, + "manual_connection": { + "data": { + "address": "La direcci\u00f3n IP o el dominio o el puerto serie si se conecta a trav\u00e9s de serie.", + "prefix": "Un prefijo \u00fanico (dejar en blanco si solo tiene un ElkM1).", + "protocol": "Protocolo", + "temperature_unit": "La unidad de temperatura que utiliza ElkM1." + }, + "description": "Conecte un M\u00f3dulo de Interfaz Universal Powerline Bus Powerline (UPB PIM). La cadena de direcci\u00f3n debe tener el formato 'direcci\u00f3n [: puerto]' para 'tcp'. El puerto es opcional y el valor predeterminado es 2101. Ejemplo: '192.168.1.42'. Para el protocolo serie, la direcci\u00f3n debe estar en la forma 'tty [: baudios]'. El baud es opcional y el valor predeterminado es 4800. Ejemplo: '/ dev / ttyS1'." + }, "user": { "data": { "address": "La direcci\u00f3n IP o dominio o puerto serie si se conecta a trav\u00e9s de serie.", + "device": "Dispositivo", "password": "Contrase\u00f1a", "prefix": "Un prefijo \u00fanico (d\u00e9jalo en blanco si s\u00f3lo tienes un Elk-M1).", "protocol": "Protocolo", diff --git a/homeassistant/components/fan/translations/es.json b/homeassistant/components/fan/translations/es.json index 3b7cb4a4f5685d..c4edae6f9ee31a 100644 --- a/homeassistant/components/fan/translations/es.json +++ b/homeassistant/components/fan/translations/es.json @@ -9,6 +9,7 @@ "is_on": "{entity_name} est\u00e1 activado" }, "trigger_type": { + "changed_states": "{entity_name} activado o desactivado", "toggled": "{entity_name} activado o desactivado", "turned_off": "{entity_name} desactivado", "turned_on": "{entity_name} activado" diff --git a/homeassistant/components/fivem/translations/cs.json b/homeassistant/components/fivem/translations/cs.json new file mode 100644 index 00000000000000..2455bf8695da8e --- /dev/null +++ b/homeassistant/components/fivem/translations/cs.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Slu\u017eba je ji\u017e nastavena" + }, + "error": { + "unknown_error": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "host": "Hostitel", + "name": "Jm\u00e9no", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/es.json b/homeassistant/components/fivem/translations/es.json new file mode 100644 index 00000000000000..be6eaaee4360af --- /dev/null +++ b/homeassistant/components/fivem/translations/es.json @@ -0,0 +1,9 @@ +{ + "config": { + "error": { + "cannot_connect": "Error al conectarse. Compruebe el host y el puerto e int\u00e9ntelo de nuevo. Aseg\u00farese tambi\u00e9n de que est\u00e1 ejecutando el servidor FiveM m\u00e1s reciente.", + "invalid_game_name": "La API del juego al que intentas conectarte no es un juego de FiveM.", + "invalid_gamename": "La API del juego al que intentas conectarte no es un juego de FiveM." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/humidifier/translations/es.json b/homeassistant/components/humidifier/translations/es.json index d01479fbd8764c..e9c8bb02df9783 100644 --- a/homeassistant/components/humidifier/translations/es.json +++ b/homeassistant/components/humidifier/translations/es.json @@ -13,6 +13,7 @@ "is_on": "{entity_name} est\u00e1 activado" }, "trigger_type": { + "changed_states": "{entity_name} activado o desactivado", "target_humidity_changed": "La humedad objetivo ha cambiado en {entity_name}", "toggled": "{entity_name} activado o desactivado", "turned_off": "{entity_name} desactivado", diff --git a/homeassistant/components/iss/translations/cs.json b/homeassistant/components/iss/translations/cs.json new file mode 100644 index 00000000000000..19f5d1e1587578 --- /dev/null +++ b/homeassistant/components/iss/translations/cs.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iss/translations/es.json b/homeassistant/components/iss/translations/es.json new file mode 100644 index 00000000000000..91bbd571ab9ace --- /dev/null +++ b/homeassistant/components/iss/translations/es.json @@ -0,0 +1,11 @@ +{ + "options": { + "step": { + "init": { + "data": { + "show_on_map": "Mostrar en el mapa" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iss/translations/he.json b/homeassistant/components/iss/translations/he.json index d0c3523da94e2a..eaea05d0779ec7 100644 --- a/homeassistant/components/iss/translations/he.json +++ b/homeassistant/components/iss/translations/he.json @@ -3,5 +3,14 @@ "abort": { "single_instance_allowed": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea." } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "\u05d4\u05e6\u05d2 \u05d1\u05de\u05e4\u05d4" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/iss/translations/id.json b/homeassistant/components/iss/translations/id.json index c533197ca840c4..c53287164eec7e 100644 --- a/homeassistant/components/iss/translations/id.json +++ b/homeassistant/components/iss/translations/id.json @@ -9,7 +9,16 @@ "data": { "show_on_map": "Tampilkan di peta?" }, - "description": "Ingin mengonfigurasi Stasiun Luar Angkasa Internasional?" + "description": "Ingin mengonfigurasi Stasiun Luar Angkasa Internasional (ISS)?" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "Tampilkan di peta" + } } } } diff --git a/homeassistant/components/iss/translations/ru.json b/homeassistant/components/iss/translations/ru.json index 8808b02f5b86b2..64604c1f460d08 100644 --- a/homeassistant/components/iss/translations/ru.json +++ b/homeassistant/components/iss/translations/ru.json @@ -9,7 +9,7 @@ "data": { "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0430 \u043a\u0430\u0440\u0442\u0435" }, - "description": "\u041d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 International Space Station?" + "description": "\u041d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 International Space Station (ISS)?" } } }, diff --git a/homeassistant/components/light/translations/es.json b/homeassistant/components/light/translations/es.json index 1eb2914d110f3e..10e8dfa3d17f77 100644 --- a/homeassistant/components/light/translations/es.json +++ b/homeassistant/components/light/translations/es.json @@ -13,6 +13,7 @@ "is_on": "{entity_name} est\u00e1 encendida" }, "trigger_type": { + "changed_states": "{entity_name} activado o desactivado", "toggled": "{entity_name} activado o desactivado", "turned_off": "{entity_name} apagada", "turned_on": "{entity_name} encendida" diff --git a/homeassistant/components/media_player/translations/es.json b/homeassistant/components/media_player/translations/es.json index f1ffc44957eefb..0dfc063a0356a6 100644 --- a/homeassistant/components/media_player/translations/es.json +++ b/homeassistant/components/media_player/translations/es.json @@ -8,6 +8,7 @@ "is_playing": "{entity_name} est\u00e1 reproduciendo" }, "trigger_type": { + "changed_states": "{entity_name} ha cambiado de estado", "idle": "{entity_name} est\u00e1 inactivo", "paused": "{entity_name} est\u00e1 en pausa", "playing": "{entity_name} comienza a reproducirse", diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/cs.json b/homeassistant/components/moehlenhoff_alpha2/translations/cs.json new file mode 100644 index 00000000000000..5eac883adf06fa --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/translations/cs.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "host": "Hostitel" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/es.json b/homeassistant/components/moehlenhoff_alpha2/translations/es.json new file mode 100644 index 00000000000000..d15111e97b0d19 --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/translations/es.json @@ -0,0 +1,3 @@ +{ + "title": "M\u00f6hlenhoff Alpha2" +} \ No newline at end of file diff --git a/homeassistant/components/overkiz/translations/es.json b/homeassistant/components/overkiz/translations/es.json index 7b03a8287182ca..f1702a6d703212 100644 --- a/homeassistant/components/overkiz/translations/es.json +++ b/homeassistant/components/overkiz/translations/es.json @@ -10,6 +10,7 @@ "too_many_requests": "Demasiadas solicitudes, int\u00e9ntalo de nuevo m\u00e1s tarde.", "unknown": "Error inesperado" }, + "flow_title": "Puerta de enlace: {gateway_id}", "step": { "user": { "data": { diff --git a/homeassistant/components/overkiz/translations/sensor.es.json b/homeassistant/components/overkiz/translations/sensor.es.json new file mode 100644 index 00000000000000..8d0c475586b8bc --- /dev/null +++ b/homeassistant/components/overkiz/translations/sensor.es.json @@ -0,0 +1,41 @@ +{ + "state": { + "overkiz__battery": { + "full": "Completo", + "low": "Bajo", + "normal": "Normal", + "verylow": "Muy bajo" + }, + "overkiz__discrete_rssi_level": { + "good": "Bien", + "low": "Bajo", + "normal": "Normal", + "verylow": "Muy bajo" + }, + "overkiz__priority_lock_originator": { + "external_gateway": "Puerta de enlace externa", + "local_user": "Usuario local", + "lsc": "LSC", + "myself": "Yo mismo", + "rain": "Lluvia", + "saac": "SAAC", + "security": "Seguridad", + "sfc": "SFC", + "temperature": "Temperatura", + "timer": "Temporizador", + "ups": "SAI", + "user": "Usuario", + "wind": "Viento" + }, + "overkiz__sensor_defect": { + "dead": "Muerto", + "low_battery": "Bater\u00eda baja", + "maintenance_required": "Mantenimiento necesario", + "no_defect": "Ning\u00fan defecto" + }, + "overkiz__sensor_room": { + "clean": "Limpiar", + "dirty": "Sucio" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/cs.json b/homeassistant/components/picnic/translations/cs.json index dc27752e93594f..ce11c538997b76 100644 --- a/homeassistant/components/picnic/translations/cs.json +++ b/homeassistant/components/picnic/translations/cs.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", + "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9" }, "error": { "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", diff --git a/homeassistant/components/picnic/translations/es.json b/homeassistant/components/picnic/translations/es.json index 848f72e62d6d2c..3974c507fd4015 100644 --- a/homeassistant/components/picnic/translations/es.json +++ b/homeassistant/components/picnic/translations/es.json @@ -5,6 +5,7 @@ }, "error": { "cannot_connect": "No se pudo conectar", + "different_account": "La cuenta debe ser la misma que se utiliz\u00f3 para configurar la integraci\u00f3n", "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "unknown": "Error inesperado" }, diff --git a/homeassistant/components/picnic/translations/he.json b/homeassistant/components/picnic/translations/he.json index f668538909b4a1..856ac220d64249 100644 --- a/homeassistant/components/picnic/translations/he.json +++ b/homeassistant/components/picnic/translations/he.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4" + "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4", + "reauth_successful": "\u05d4\u05d0\u05d9\u05de\u05d5\u05ea \u05de\u05d7\u05d3\u05e9 \u05d4\u05e6\u05dc\u05d9\u05d7" }, "error": { "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", + "different_account": "\u05d4\u05d7\u05e9\u05d1\u05d5\u05df \u05e6\u05e8\u05d9\u05da \u05dc\u05d4\u05d9\u05d5\u05ea \u05d6\u05d4\u05d4 \u05dc\u05d7\u05e9\u05d1\u05d5\u05df \u05d4\u05de\u05e9\u05de\u05e9 \u05dc\u05d4\u05d2\u05d3\u05e8\u05ea \u05d4\u05e9\u05d9\u05dc\u05d5\u05d1", "invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9", "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" }, diff --git a/homeassistant/components/powerwall/translations/cs.json b/homeassistant/components/powerwall/translations/cs.json index d6e5cd5904b004..6f6ffccd4777fd 100644 --- a/homeassistant/components/powerwall/translations/cs.json +++ b/homeassistant/components/powerwall/translations/cs.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9" }, "error": { @@ -10,8 +11,16 @@ "unknown": "Neo\u010dek\u00e1van\u00e1 chyba", "wrong_version": "Powerwall pou\u017e\u00edv\u00e1 verzi softwaru, kter\u00e1 nen\u00ed podporov\u00e1na. Zva\u017ete upgrade nebo nahlaste probl\u00e9m, aby mohl b\u00fdt vy\u0159e\u0161en." }, - "flow_title": "Tesla Powerwall ({ip_address})", + "flow_title": "{name} ({ip_address})", "step": { + "confirm_discovery": { + "title": "P\u0159ipojen\u00ed k powerwall" + }, + "reauth_confim": { + "data": { + "password": "Heslo" + } + }, "user": { "data": { "ip_address": "IP adresa", diff --git a/homeassistant/components/remote/translations/es.json b/homeassistant/components/remote/translations/es.json index f14e786aab661c..dfa90cb1cc8104 100644 --- a/homeassistant/components/remote/translations/es.json +++ b/homeassistant/components/remote/translations/es.json @@ -10,6 +10,7 @@ "is_on": "{entity_name} est\u00e1 activado" }, "trigger_type": { + "changed_states": "{entity_name} activado o desactivado", "toggled": "{entity_name} activado o desactivado", "turned_off": "{entity_name} desactivado", "turned_on": "{entity_name} activado" diff --git a/homeassistant/components/sleepiq/translations/ca.json b/homeassistant/components/sleepiq/translations/ca.json new file mode 100644 index 00000000000000..9c37f37a0ef563 --- /dev/null +++ b/homeassistant/components/sleepiq/translations/ca.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "El compte ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" + }, + "step": { + "user": { + "data": { + "password": "Contrasenya", + "username": "Nom d'usuari" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sleepiq/translations/he.json b/homeassistant/components/sleepiq/translations/he.json new file mode 100644 index 00000000000000..49f37a267d0a66 --- /dev/null +++ b/homeassistant/components/sleepiq/translations/he.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d7\u05e9\u05d1\u05d5\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4" + }, + "error": { + "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", + "invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9" + }, + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sleepiq/translations/pt-BR.json b/homeassistant/components/sleepiq/translations/pt-BR.json new file mode 100644 index 00000000000000..82754ec08659bb --- /dev/null +++ b/homeassistant/components/sleepiq/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 est\u00e1 configurada" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "username": "Nome de usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sleepiq/translations/ru.json b/homeassistant/components/sleepiq/translations/ru.json new file mode 100644 index 00000000000000..f74355cdc7d9bd --- /dev/null +++ b/homeassistant/components/sleepiq/translations/ru.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u0430 \u0443\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sleepiq/translations/zh-Hant.json b/homeassistant/components/sleepiq/translations/zh-Hant.json new file mode 100644 index 00000000000000..d93bfe0fa683e9 --- /dev/null +++ b/homeassistant/components/sleepiq/translations/zh-Hant.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" + }, + "step": { + "user": { + "data": { + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/switch/translations/es.json b/homeassistant/components/switch/translations/es.json index 413ab93839a600..95a60ab55eaa0b 100644 --- a/homeassistant/components/switch/translations/es.json +++ b/homeassistant/components/switch/translations/es.json @@ -10,6 +10,7 @@ "is_on": "{entity_name} est\u00e1 encendida" }, "trigger_type": { + "changed_states": "{entity_name} activado o desactivado", "toggled": "{entity_name} activado o desactivado", "turned_off": "{entity_name} apagado", "turned_on": "{entity_name} encendido" diff --git a/homeassistant/components/tuya/translations/select.ru.json b/homeassistant/components/tuya/translations/select.ru.json index 4755e9f5d09a9a..99f7f02771d30b 100644 --- a/homeassistant/components/tuya/translations/select.ru.json +++ b/homeassistant/components/tuya/translations/select.ru.json @@ -53,7 +53,9 @@ "level_9": "\u0423\u0440\u043e\u0432\u0435\u043d\u044c 9" }, "tuya__humidifier_spray_mode": { - "auto": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438" + "auto": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438", + "sleep": "\u0421\u043e\u043d", + "work": "\u0420\u0430\u0431\u043e\u0442\u0430" }, "tuya__ipc_work_mode": { "0": "\u0420\u0435\u0436\u0438\u043c \u043d\u0438\u0437\u043a\u043e\u0433\u043e \u044d\u043d\u0435\u0440\u0433\u043e\u043f\u043e\u0442\u0440\u0435\u0431\u043b\u0435\u043d\u0438\u044f", diff --git a/homeassistant/components/webostv/translations/es.json b/homeassistant/components/webostv/translations/es.json new file mode 100644 index 00000000000000..d15f31b514c536 --- /dev/null +++ b/homeassistant/components/webostv/translations/es.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "error_pairing": "Conectado a LG webOS TV pero no emparejado" + }, + "error": { + "cannot_connect": "No se ha podido conectar, por favor, encienda el televisor o compruebe la direcci\u00f3n IP" + }, + "flow_title": "LG webOS Smart TV", + "step": { + "pairing": { + "description": "Haz clic en enviar y acepta la solicitud de emparejamiento en tu televisor.\n\n![Image](/static/images/config_webos.png)", + "title": "Emparejamiento de webOS TV" + }, + "user": { + "description": "Encienda la televisi\u00f3n, rellene los siguientes campos y haga clic en enviar", + "title": "Conectarse a webOS TV" + } + } + }, + "device_automation": { + "trigger_type": { + "webostv.turn_on": "Se solicita el encendido del dispositivo" + } + }, + "options": { + "error": { + "cannot_retrieve": "No se puede recuperar la lista de fuentes. Aseg\u00farese de que el dispositivo est\u00e1 encendido", + "script_not_found": "Script no encontrado" + }, + "step": { + "init": { + "data": { + "sources": "Lista de fuentes" + }, + "description": "Seleccionar fuentes habilitadas", + "title": "Opciones para webOS Smart TV" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/cs.json b/homeassistant/components/wiz/translations/cs.json new file mode 100644 index 00000000000000..0656e47c8bef07 --- /dev/null +++ b/homeassistant/components/wiz/translations/cs.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "no_devices_found": "V s\u00edti nebyla nalezena \u017e\u00e1dn\u00e1 za\u0159\u00edzen\u00ed" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "confirm": { + "description": "Chcete za\u010d\u00edt nastavovat?" + }, + "user": { + "data": { + "host": "IP adresa", + "name": "Jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/es.json b/homeassistant/components/wiz/translations/es.json new file mode 100644 index 00000000000000..1e0dbb1f7dd858 --- /dev/null +++ b/homeassistant/components/wiz/translations/es.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "no_ip": "No es una direcci\u00f3n IP v\u00e1lida." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/he.json b/homeassistant/components/wiz/translations/he.json index dd091bd10eb0bd..8d4e41401c80af 100644 --- a/homeassistant/components/wiz/translations/he.json +++ b/homeassistant/components/wiz/translations/he.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4", + "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", "no_devices_found": "\u05dc\u05d0 \u05e0\u05de\u05e6\u05d0\u05d5 \u05de\u05db\u05e9\u05d9\u05e8\u05d9\u05dd \u05d1\u05e8\u05e9\u05ea" }, "error": { diff --git a/homeassistant/components/wiz/translations/id.json b/homeassistant/components/wiz/translations/id.json index d4b1fc92ed8d2d..694973f8ffab07 100644 --- a/homeassistant/components/wiz/translations/id.json +++ b/homeassistant/components/wiz/translations/id.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan" }, "error": { diff --git a/homeassistant/components/zwave_me/translations/cs.json b/homeassistant/components/zwave_me/translations/cs.json new file mode 100644 index 00000000000000..f069ef7b324ed4 --- /dev/null +++ b/homeassistant/components/zwave_me/translations/cs.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" + }, + "step": { + "user": { + "data": { + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave_me/translations/es.json b/homeassistant/components/zwave_me/translations/es.json new file mode 100644 index 00000000000000..f55937b4ec7dd1 --- /dev/null +++ b/homeassistant/components/zwave_me/translations/es.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "no_valid_uuid_set": "No se ha establecido un UUID v\u00e1lido" + }, + "error": { + "no_valid_uuid_set": "No se ha establecido un UUID v\u00e1lido" + }, + "step": { + "user": { + "data": { + "token": "Token" + }, + "description": "Direcci\u00f3n IP de entrada del servidor Z-Way y token de acceso Z-Way. La direcci\u00f3n IP se puede prefijar con wss:// si se debe usar HTTPS en lugar de HTTP. Para obtener el token, vaya a la interfaz de usuario de Z-Way > Configuraci\u00f3n de > de men\u00fa > token de API de > de usuario. Se sugiere crear un nuevo usuario para Home Assistant y conceder acceso a los dispositivos que necesita controlar desde Home Assistant. Tambi\u00e9n es posible utilizar el acceso remoto a trav\u00e9s de find.z-wave.me para conectar un Z-Way remoto. Ingrese wss://find.z-wave.me en el campo IP y copie el token con alcance global (inicie sesi\u00f3n en Z-Way a trav\u00e9s de find.z-wave.me para esto)." + } + } + } +} \ No newline at end of file From bcdd8238496af34e3d50f7895eb82cd7ed89de07 Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Thu, 17 Feb 2022 13:15:19 +1000 Subject: [PATCH 0731/1098] Bump Advantage Air to 0.3.1 (#66699) --- homeassistant/components/advantage_air/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/advantage_air/manifest.json b/homeassistant/components/advantage_air/manifest.json index cbc5df6449622a..6f1d811c291d40 100644 --- a/homeassistant/components/advantage_air/manifest.json +++ b/homeassistant/components/advantage_air/manifest.json @@ -7,7 +7,7 @@ "@Bre77" ], "requirements": [ - "advantage_air==0.3.0" + "advantage_air==0.3.1" ], "quality_scale": "platinum", "iot_class": "local_polling", diff --git a/requirements_all.txt b/requirements_all.txt index be3a0db1d4bdf0..2898404400eee9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -114,7 +114,7 @@ adext==0.4.2 adguardhome==0.5.1 # homeassistant.components.advantage_air -advantage_air==0.3.0 +advantage_air==0.3.1 # homeassistant.components.frontier_silicon afsapi==0.0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e35ee84bb8510f..ae1bcff2847f51 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -70,7 +70,7 @@ adext==0.4.2 adguardhome==0.5.1 # homeassistant.components.advantage_air -advantage_air==0.3.0 +advantage_air==0.3.1 # homeassistant.components.agent_dvr agent-py==0.0.23 From a131b78a704d568509937600bf5cf872086d7dbc Mon Sep 17 00:00:00 2001 From: Rob Borkowski Date: Wed, 16 Feb 2022 19:40:24 -0800 Subject: [PATCH 0732/1098] Bump pyeconet version for Gen 5 Water Heater Support (#66691) * Bump pyeconet version for Gen 5 Water Heater Support * Update requirements files --- homeassistant/components/econet/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/econet/manifest.json b/homeassistant/components/econet/manifest.json index 8a494d193b7260..f8df1a4134ec4c 100644 --- a/homeassistant/components/econet/manifest.json +++ b/homeassistant/components/econet/manifest.json @@ -3,7 +3,7 @@ "name": "Rheem EcoNet Products", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/econet", - "requirements": ["pyeconet==0.1.14"], + "requirements": ["pyeconet==0.1.15"], "codeowners": ["@vangorra", "@w1ll1am23"], "iot_class": "cloud_push", "loggers": ["paho_mqtt", "pyeconet"] diff --git a/requirements_all.txt b/requirements_all.txt index 2898404400eee9..1b8e6616d593e2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1488,7 +1488,7 @@ pydroid-ipcam==0.8 pyebox==1.1.4 # homeassistant.components.econet -pyeconet==0.1.14 +pyeconet==0.1.15 # homeassistant.components.edimax pyedimax==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ae1bcff2847f51..a21bfb336e5119 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -926,7 +926,7 @@ pydexcom==0.2.2 pydispatcher==2.0.5 # homeassistant.components.econet -pyeconet==0.1.14 +pyeconet==0.1.15 # homeassistant.components.efergy pyefergy==22.1.1 From 714daebfb9feef6a45dd34e99529f3a873fb03c2 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 17 Feb 2022 09:41:21 +0100 Subject: [PATCH 0733/1098] Netgear add reboot button (#65706) Co-authored-by: Franck Nijhof Co-authored-by: Martin Hjelmare Co-authored-by: Franck Nijhof --- .coveragerc | 1 + homeassistant/components/netgear/button.py | 82 ++++++++++++++++++++++ homeassistant/components/netgear/const.py | 2 +- homeassistant/components/netgear/router.py | 5 ++ 4 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/netgear/button.py diff --git a/.coveragerc b/.coveragerc index a419628d50a649..d5c9f48ee47e48 100644 --- a/.coveragerc +++ b/.coveragerc @@ -779,6 +779,7 @@ omit = homeassistant/components/nest/legacy/* homeassistant/components/netdata/sensor.py homeassistant/components/netgear/__init__.py + homeassistant/components/netgear/button.py homeassistant/components/netgear/device_tracker.py homeassistant/components/netgear/router.py homeassistant/components/netgear/sensor.py diff --git a/homeassistant/components/netgear/button.py b/homeassistant/components/netgear/button.py new file mode 100644 index 00000000000000..e14600ff52b40e --- /dev/null +++ b/homeassistant/components/netgear/button.py @@ -0,0 +1,82 @@ +"""Support for Netgear Button.""" +from collections.abc import Callable, Coroutine +from dataclasses import dataclass +from typing import Any + +from homeassistant.components.button import ( + ButtonDeviceClass, + ButtonEntity, + ButtonEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + +from .const import DOMAIN, KEY_COORDINATOR, KEY_ROUTER +from .router import NetgearRouter, NetgearRouterEntity + + +@dataclass +class NetgearButtonEntityDescriptionRequired: + """Required attributes of NetgearButtonEntityDescription.""" + + action: Callable[[NetgearRouter], Callable[[], Coroutine[Any, Any, None]]] + + +@dataclass +class NetgearButtonEntityDescription( + ButtonEntityDescription, NetgearButtonEntityDescriptionRequired +): + """Class describing Netgear button entities.""" + + +BUTTONS = [ + NetgearButtonEntityDescription( + key="reboot", + name="Reboot", + device_class=ButtonDeviceClass.RESTART, + entity_category=EntityCategory.CONFIG, + action=lambda router: router.async_reboot, + ) +] + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up button for Netgear component.""" + router = hass.data[DOMAIN][entry.entry_id][KEY_ROUTER] + coordinator = hass.data[DOMAIN][entry.entry_id][KEY_COORDINATOR] + async_add_entities( + NetgearRouterButtonEntity(coordinator, router, entity_description) + for entity_description in BUTTONS + ) + + +class NetgearRouterButtonEntity(NetgearRouterEntity, ButtonEntity): + """Netgear Router button entity.""" + + entity_description: NetgearButtonEntityDescription + + def __init__( + self, + coordinator: DataUpdateCoordinator, + router: NetgearRouter, + entity_description: NetgearButtonEntityDescription, + ) -> None: + """Initialize a Netgear device.""" + super().__init__(coordinator, router) + self.entity_description = entity_description + self._name = f"{router.device_name} {entity_description.name}" + self._unique_id = f"{router.serial_number}-{entity_description.key}" + + async def async_press(self) -> None: + """Triggers the button press service.""" + async_action = self.entity_description.action(self._router) + await async_action() + + @callback + def async_update_device(self) -> None: + """Update the Netgear device.""" diff --git a/homeassistant/components/netgear/const.py b/homeassistant/components/netgear/const.py index d366ae3996140b..f2e0263a4e469b 100644 --- a/homeassistant/components/netgear/const.py +++ b/homeassistant/components/netgear/const.py @@ -5,7 +5,7 @@ DOMAIN = "netgear" -PLATFORMS = [Platform.DEVICE_TRACKER, Platform.SENSOR, Platform.SWITCH] +PLATFORMS = [Platform.BUTTON, Platform.DEVICE_TRACKER, Platform.SENSOR, Platform.SWITCH] CONF_CONSIDER_HOME = "consider_home" diff --git a/homeassistant/components/netgear/router.py b/homeassistant/components/netgear/router.py index 3778c36d81a527..9e44495aa624e2 100644 --- a/homeassistant/components/netgear/router.py +++ b/homeassistant/components/netgear/router.py @@ -209,6 +209,11 @@ async def async_allow_block_device(self, mac: str, allow_block: str) -> None: self._api.allow_block_device, mac, allow_block ) + async def async_reboot(self) -> None: + """Reboot the router.""" + async with self._api_lock: + await self.hass.async_add_executor_job(self._api.reboot) + @property def port(self) -> int: """Port used by the API.""" From 42b5ce184c43675609a7cf134caf2b0fb2800525 Mon Sep 17 00:00:00 2001 From: Eduard van Valkenburg Date: Thu, 17 Feb 2022 11:03:22 +0100 Subject: [PATCH 0734/1098] Brunt package to 1.2.0 (#66722) --- homeassistant/components/brunt/__init__.py | 3 +++ homeassistant/components/brunt/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/brunt/__init__.py b/homeassistant/components/brunt/__init__.py index 5fe3f7d0012ae9..988a96ce08e8bc 100644 --- a/homeassistant/components/brunt/__init__.py +++ b/homeassistant/components/brunt/__init__.py @@ -11,6 +11,7 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady +from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import DATA_BAPI, DATA_COOR, DOMAIN, PLATFORMS, REGULAR_INTERVAL @@ -20,9 +21,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Brunt using config flow.""" + session = async_get_clientsession(hass) bapi = BruntClientAsync( username=entry.data[CONF_USERNAME], password=entry.data[CONF_PASSWORD], + session=session, ) try: await bapi.async_login() diff --git a/homeassistant/components/brunt/manifest.json b/homeassistant/components/brunt/manifest.json index 72277a820e4d84..11bafbca07b509 100644 --- a/homeassistant/components/brunt/manifest.json +++ b/homeassistant/components/brunt/manifest.json @@ -3,7 +3,7 @@ "name": "Brunt Blind Engine", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/brunt", - "requirements": ["brunt==1.1.1"], + "requirements": ["brunt==1.2.0"], "codeowners": ["@eavanvalkenburg"], "iot_class": "cloud_polling", "loggers": ["brunt"] diff --git a/requirements_all.txt b/requirements_all.txt index 1b8e6616d593e2..ac972d24fb58c8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -457,7 +457,7 @@ brother==1.1.0 brottsplatskartan==0.0.1 # homeassistant.components.brunt -brunt==1.1.1 +brunt==1.2.0 # homeassistant.components.bsblan bsblan==0.5.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a21bfb336e5119..7a1d16c0e87ba3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -306,7 +306,7 @@ broadlink==0.18.0 brother==1.1.0 # homeassistant.components.brunt -brunt==1.1.1 +brunt==1.2.0 # homeassistant.components.bsblan bsblan==0.5.0 From a1b81b2de4172bc8affa74a87e35a8b20a74eac4 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 17 Feb 2022 05:38:20 -0500 Subject: [PATCH 0735/1098] Add inclusion state to zwave_js/network_status WS API cmd (#65398) --- homeassistant/components/zwave_js/api.py | 2 ++ .../zwave_js/fixtures/controller_state.json | 3 ++- tests/components/zwave_js/test_api.py | 13 ++++++++----- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index f347a5187ea977..4e68cc2e2dd1c4 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -367,6 +367,7 @@ async def websocket_network_status( ) -> None: """Get the status of the Z-Wave JS network.""" controller = client.driver.controller + await controller.async_get_state() data = { "client": { "ws_server_url": client.ws_server_url, @@ -393,6 +394,7 @@ async def websocket_network_status( "suc_node_id": controller.suc_node_id, "supports_timers": controller.supports_timers, "is_heal_network_active": controller.is_heal_network_active, + "inclusion_state": controller.inclusion_state, "nodes": list(client.driver.controller.nodes), }, } diff --git a/tests/components/zwave_js/fixtures/controller_state.json b/tests/components/zwave_js/fixtures/controller_state.json index d4bf58a53ceb78..ac0cedcffef9b8 100644 --- a/tests/components/zwave_js/fixtures/controller_state.json +++ b/tests/components/zwave_js/fixtures/controller_state.json @@ -92,7 +92,8 @@ ], "sucNodeId": 1, "supportsTimers": false, - "isHealNetworkActive": false + "isHealNetworkActive": false, + "inclusionState": 0 }, "nodes": [ ] diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index 3f29c3a2e67240..8362854a5f6c0f 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -6,6 +6,7 @@ import pytest from zwave_js_server.const import ( + InclusionState, InclusionStrategy, LogLevel, Protocols, @@ -77,14 +78,16 @@ async def test_network_status(hass, integration, hass_ws_client): entry = integration ws_client = await hass_ws_client(hass) - await ws_client.send_json( - {ID: 2, TYPE: "zwave_js/network_status", ENTRY_ID: entry.entry_id} - ) - msg = await ws_client.receive_json() - result = msg["result"] + with patch("zwave_js_server.model.controller.Controller.async_get_state"): + await ws_client.send_json( + {ID: 2, TYPE: "zwave_js/network_status", ENTRY_ID: entry.entry_id} + ) + msg = await ws_client.receive_json() + result = msg["result"] assert result["client"]["ws_server_url"] == "ws://test:3000/zjs" assert result["client"]["server_version"] == "1.0.0" + assert result["controller"]["inclusion_state"] == InclusionState.IDLE # Test sending command with not loaded entry fails await hass.config_entries.async_unload(entry.entry_id) From 72fad87aefd65d3e7acf5fe700355b3e0784240c Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 17 Feb 2022 12:06:49 +0100 Subject: [PATCH 0736/1098] Update google-cloud-texttospeech to 2.10.0 (#66726) --- .../components/google_cloud/manifest.json | 2 +- homeassistant/components/google_cloud/tts.py | 29 ++++++++++--------- requirements_all.txt | 2 +- script/pip_check | 2 +- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/google_cloud/manifest.json b/homeassistant/components/google_cloud/manifest.json index 90c5eebaeb2376..83801d50354c73 100644 --- a/homeassistant/components/google_cloud/manifest.json +++ b/homeassistant/components/google_cloud/manifest.json @@ -2,7 +2,7 @@ "domain": "google_cloud", "name": "Google Cloud Platform", "documentation": "https://www.home-assistant.io/integrations/google_cloud", - "requirements": ["google-cloud-texttospeech==0.4.0"], + "requirements": ["google-cloud-texttospeech==2.10.0"], "codeowners": ["@lufton"], "iot_class": "cloud_push" } diff --git a/homeassistant/components/google_cloud/tts.py b/homeassistant/components/google_cloud/tts.py index 3d65f4eb2972dc..0de580ef7b7a8c 100644 --- a/homeassistant/components/google_cloud/tts.py +++ b/homeassistant/components/google_cloud/tts.py @@ -122,13 +122,9 @@ CONF_TEXT_TYPE, ] -GENDER_SCHEMA = vol.All( - vol.Upper, vol.In(texttospeech.enums.SsmlVoiceGender.__members__) -) +GENDER_SCHEMA = vol.All(vol.Upper, vol.In(texttospeech.SsmlVoiceGender.__members__)) VOICE_SCHEMA = cv.matches_regex(VOICE_REGEX) -SCHEMA_ENCODING = vol.All( - vol.Upper, vol.In(texttospeech.enums.AudioEncoding.__members__) -) +SCHEMA_ENCODING = vol.All(vol.Upper, vol.In(texttospeech.AudioEncoding.__members__)) SPEED_SCHEMA = vol.All(vol.Coerce(float), vol.Clamp(min=MIN_SPEED, max=MAX_SPEED)) PITCH_SCHEMA = vol.All(vol.Coerce(float), vol.Clamp(min=MIN_PITCH, max=MAX_PITCH)) GAIN_SCHEMA = vol.All(vol.Coerce(float), vol.Clamp(min=MIN_GAIN, max=MAX_GAIN)) @@ -263,27 +259,32 @@ async def async_get_tts_audio(self, message, language, options=None): try: params = {options[CONF_TEXT_TYPE]: message} - # pylint: disable=no-member - synthesis_input = texttospeech.types.SynthesisInput(**params) + synthesis_input = texttospeech.SynthesisInput(**params) - voice = texttospeech.types.VoiceSelectionParams( + voice = texttospeech.VoiceSelectionParams( language_code=language, - ssml_gender=texttospeech.enums.SsmlVoiceGender[options[CONF_GENDER]], + ssml_gender=texttospeech.SsmlVoiceGender[options[CONF_GENDER]], name=_voice, ) - audio_config = texttospeech.types.AudioConfig( - audio_encoding=texttospeech.enums.AudioEncoding[_encoding], + audio_config = texttospeech.AudioConfig( + audio_encoding=texttospeech.AudioEncoding[_encoding], speaking_rate=options[CONF_SPEED], pitch=options[CONF_PITCH], volume_gain_db=options[CONF_GAIN], effects_profile_id=options[CONF_PROFILES], ) - # pylint: enable=no-member + + request = { + "voice": voice, + "audio_config": audio_config, + "input": synthesis_input, + } async with async_timeout.timeout(10): + assert self.hass response = await self.hass.async_add_executor_job( - self._client.synthesize_speech, synthesis_input, voice, audio_config + self._client.synthesize_speech, request ) return _encoding, response.audio_content diff --git a/requirements_all.txt b/requirements_all.txt index ac972d24fb58c8..d963262b712b56 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -758,7 +758,7 @@ google-api-python-client==1.6.4 google-cloud-pubsub==2.9.0 # homeassistant.components.google_cloud -google-cloud-texttospeech==0.4.0 +google-cloud-texttospeech==2.10.0 # homeassistant.components.nest google-nest-sdm==1.7.1 diff --git a/script/pip_check b/script/pip_check index c30a7382f27335..af47f101fbba5b 100755 --- a/script/pip_check +++ b/script/pip_check @@ -3,7 +3,7 @@ PIP_CACHE=$1 # Number of existing dependency conflicts # Update if a PR resolve one! -DEPENDENCY_CONFLICTS=10 +DEPENDENCY_CONFLICTS=9 PIP_CHECK=$(pip check --cache-dir=$PIP_CACHE) LINE_COUNT=$(echo "$PIP_CHECK" | wc -l) From a9aefb66b53cd0d01ba7a090ab7ce7a8b2f5fee0 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 17 Feb 2022 12:35:02 +0100 Subject: [PATCH 0737/1098] Add device info to samsungtv diagnostics (#66728) * Add device-info to samsungtv diagnostics * Adjust tests Co-authored-by: epenet --- .../components/samsungtv/diagnostics.py | 17 +++++++++++++---- tests/components/samsungtv/test_diagnostics.py | 12 +++++++++++- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/samsungtv/diagnostics.py b/homeassistant/components/samsungtv/diagnostics.py index 18d2325f38c914..324a3d1b32fb40 100644 --- a/homeassistant/components/samsungtv/diagnostics.py +++ b/homeassistant/components/samsungtv/diagnostics.py @@ -1,18 +1,27 @@ """Diagnostics support for SamsungTV.""" from __future__ import annotations +from typing import Any + from homeassistant.components.diagnostics import async_redact_data from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_TOKEN from homeassistant.core import HomeAssistant +from .bridge import SamsungTVLegacyBridge, SamsungTVWSBridge +from .const import DOMAIN + TO_REDACT = {CONF_TOKEN} async def async_get_config_entry_diagnostics( hass: HomeAssistant, entry: ConfigEntry -) -> dict: +) -> dict[str, Any]: """Return diagnostics for a config entry.""" - diag_data = {"entry": async_redact_data(entry.as_dict(), TO_REDACT)} - - return diag_data + bridge: SamsungTVLegacyBridge | SamsungTVWSBridge = hass.data[DOMAIN][ + entry.entry_id + ] + return { + "entry": async_redact_data(entry.as_dict(), TO_REDACT), + "device_info": await hass.async_add_executor_job(bridge.device_info), + } diff --git a/tests/components/samsungtv/test_diagnostics.py b/tests/components/samsungtv/test_diagnostics.py index 990c25c8f3e8f9..67bc012ced1aaa 100644 --- a/tests/components/samsungtv/test_diagnostics.py +++ b/tests/components/samsungtv/test_diagnostics.py @@ -55,5 +55,15 @@ async def test_entry_diagnostics( "title": "Mock Title", "unique_id": "any", "version": 2, - } + }, + "device_info": { + "id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4", + "device": { + "modelName": "82GXARRS", + "name": "[TV] Living Room", + "networkType": "wireless", + "type": "Samsung SmartTV", + "wifiMac": "aa:bb:cc:dd:ee:ff", + }, + }, } From 4236764fd5f8d9fa118532b4a643a12e88d78a4b Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 17 Feb 2022 13:11:49 +0100 Subject: [PATCH 0738/1098] Don't allow creating or updating input_select with duplicates (#66718) * Don't allow creating or updating input_select with duplicates * Simplify error message * Improve error message --- .../components/input_select/__init__.py | 16 ++++++++++-- tests/components/input_select/test_init.py | 26 ++++++------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/input_select/__init__.py b/homeassistant/components/input_select/__init__.py index 288c6d6e588bcc..ae5fc9d251e740 100644 --- a/homeassistant/components/input_select/__init__.py +++ b/homeassistant/components/input_select/__init__.py @@ -45,15 +45,27 @@ STORAGE_VERSION = 1 STORAGE_VERSION_MINOR = 2 + +def _unique(options: Any) -> Any: + try: + return vol.Unique()(options) + except vol.Invalid as exc: + raise HomeAssistantError("Duplicate options are not allowed") from exc + + CREATE_FIELDS = { vol.Required(CONF_NAME): vol.All(str, vol.Length(min=1)), - vol.Required(CONF_OPTIONS): vol.All(cv.ensure_list, vol.Length(min=1), [cv.string]), + vol.Required(CONF_OPTIONS): vol.All( + cv.ensure_list, vol.Length(min=1), _unique, [cv.string] + ), vol.Optional(CONF_INITIAL): cv.string, vol.Optional(CONF_ICON): cv.icon, } UPDATE_FIELDS = { vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_OPTIONS): vol.All(cv.ensure_list, vol.Length(min=1), [cv.string]), + vol.Optional(CONF_OPTIONS): vol.All( + cv.ensure_list, vol.Length(min=1), _unique, [cv.string] + ), vol.Optional(CONF_INITIAL): cv.string, vol.Optional(CONF_ICON): cv.icon, } diff --git a/tests/components/input_select/test_init.py b/tests/components/input_select/test_init.py index 7a239bac45b003..d65140dcbf9a94 100644 --- a/tests/components/input_select/test_init.py +++ b/tests/components/input_select/test_init.py @@ -707,16 +707,12 @@ async def test_update_duplicates(hass, hass_ws_client, storage_setup, caplog): } ) resp = await client.receive_json() - assert resp["success"] - - assert ( - "Input select 'from storage' with options " - "['new option', 'newer option', 'newer option'] " - "had duplicated options, the duplicates have been removed" - ) in caplog.text + assert not resp["success"] + assert resp["error"]["code"] == "unknown_error" + assert resp["error"]["message"] == "Duplicate options are not allowed" state = hass.states.get(input_entity_id) - assert state.attributes[ATTR_OPTIONS] == ["new option", "newer option"] + assert state.attributes[ATTR_OPTIONS] == ["yaml update 1", "yaml update 2"] async def test_ws_create(hass, hass_ws_client, storage_setup): @@ -774,17 +770,11 @@ async def test_ws_create_duplicates(hass, hass_ws_client, storage_setup, caplog) } ) resp = await client.receive_json() - assert resp["success"] - - assert ( - "Input select 'New Input' with options " - "['new option', 'even newer option', 'even newer option'] " - "had duplicated options, the duplicates have been removed" - ) in caplog.text + assert not resp["success"] + assert resp["error"]["code"] == "unknown_error" + assert resp["error"]["message"] == "Duplicate options are not allowed" - state = hass.states.get(input_entity_id) - assert state.state == "even newer option" - assert state.attributes[ATTR_OPTIONS] == ["new option", "even newer option"] + assert not hass.states.get(input_entity_id) async def test_setup_no_config(hass, hass_admin_user): From 83846bb5cc7513ec832a75b810c263b2b8fe027e Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Thu, 17 Feb 2022 13:51:35 +0100 Subject: [PATCH 0739/1098] MQTT climate preset_modes rework (#66062) * MQTT climate preset_modes rework * Set deprection date to 2022.9 (6 months) * add valid_preset_mode_configuration for discovery * Update deprecation date --- homeassistant/components/mqtt/climate.py | 173 +++++++++++-- tests/components/mqtt/test_climate.py | 313 +++++++++++++++++++---- 2 files changed, 427 insertions(+), 59 deletions(-) diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index 043a291f159e41..e145edde7d774a 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -80,6 +80,7 @@ CONF_AUX_COMMAND_TOPIC = "aux_command_topic" CONF_AUX_STATE_TEMPLATE = "aux_state_template" CONF_AUX_STATE_TOPIC = "aux_state_topic" +# AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 CONF_AWAY_MODE_COMMAND_TOPIC = "away_mode_command_topic" CONF_AWAY_MODE_STATE_TEMPLATE = "away_mode_state_template" CONF_AWAY_MODE_STATE_TOPIC = "away_mode_state_topic" @@ -90,6 +91,7 @@ CONF_FAN_MODE_LIST = "fan_modes" CONF_FAN_MODE_STATE_TEMPLATE = "fan_mode_state_template" CONF_FAN_MODE_STATE_TOPIC = "fan_mode_state_topic" +# AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 CONF_HOLD_COMMAND_TEMPLATE = "hold_command_template" CONF_HOLD_COMMAND_TOPIC = "hold_command_topic" CONF_HOLD_STATE_TEMPLATE = "hold_state_template" @@ -104,7 +106,12 @@ CONF_POWER_STATE_TEMPLATE = "power_state_template" CONF_POWER_STATE_TOPIC = "power_state_topic" CONF_PRECISION = "precision" -# CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.4 +CONF_PRESET_MODE_STATE_TOPIC = "preset_mode_state_topic" +CONF_PRESET_MODE_COMMAND_TOPIC = "preset_mode_command_topic" +CONF_PRESET_MODE_VALUE_TEMPLATE = "preset_mode_value_template" +CONF_PRESET_MODE_COMMAND_TEMPLATE = "preset_mode_command_template" +CONF_PRESET_MODES_LIST = "preset_modes" +# CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9 CONF_SEND_IF_OFF = "send_if_off" CONF_SWING_MODE_COMMAND_TEMPLATE = "swing_mode_command_template" CONF_SWING_MODE_COMMAND_TOPIC = "swing_mode_command_topic" @@ -155,13 +162,16 @@ VALUE_TEMPLATE_KEYS = ( CONF_AUX_STATE_TEMPLATE, + # AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 CONF_AWAY_MODE_STATE_TEMPLATE, CONF_CURRENT_TEMP_TEMPLATE, CONF_FAN_MODE_STATE_TEMPLATE, + # AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 CONF_HOLD_STATE_TEMPLATE, CONF_MODE_STATE_TEMPLATE, CONF_POWER_STATE_TEMPLATE, CONF_ACTION_TEMPLATE, + CONF_PRESET_MODE_VALUE_TEMPLATE, CONF_SWING_MODE_STATE_TEMPLATE, CONF_TEMP_HIGH_STATE_TEMPLATE, CONF_TEMP_LOW_STATE_TEMPLATE, @@ -170,29 +180,48 @@ COMMAND_TEMPLATE_KEYS = { CONF_FAN_MODE_COMMAND_TEMPLATE, + # AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 CONF_HOLD_COMMAND_TEMPLATE, CONF_MODE_COMMAND_TEMPLATE, + CONF_PRESET_MODE_COMMAND_TEMPLATE, CONF_SWING_MODE_COMMAND_TEMPLATE, CONF_TEMP_COMMAND_TEMPLATE, CONF_TEMP_HIGH_COMMAND_TEMPLATE, CONF_TEMP_LOW_COMMAND_TEMPLATE, } +# AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 +DEPRECATED_INVALID = [ + CONF_AWAY_MODE_COMMAND_TOPIC, + CONF_AWAY_MODE_STATE_TEMPLATE, + CONF_AWAY_MODE_STATE_TOPIC, + CONF_HOLD_COMMAND_TEMPLATE, + CONF_HOLD_COMMAND_TOPIC, + CONF_HOLD_STATE_TEMPLATE, + CONF_HOLD_STATE_TOPIC, + CONF_HOLD_LIST, +] + + TOPIC_KEYS = ( + CONF_ACTION_TOPIC, CONF_AUX_COMMAND_TOPIC, CONF_AUX_STATE_TOPIC, + # AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 CONF_AWAY_MODE_COMMAND_TOPIC, CONF_AWAY_MODE_STATE_TOPIC, CONF_CURRENT_TEMP_TOPIC, CONF_FAN_MODE_COMMAND_TOPIC, CONF_FAN_MODE_STATE_TOPIC, + # AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 CONF_HOLD_COMMAND_TOPIC, CONF_HOLD_STATE_TOPIC, CONF_MODE_COMMAND_TOPIC, CONF_MODE_STATE_TOPIC, CONF_POWER_COMMAND_TOPIC, CONF_POWER_STATE_TOPIC, - CONF_ACTION_TOPIC, + CONF_PRESET_MODE_COMMAND_TOPIC, + CONF_PRESET_MODE_STATE_TOPIC, CONF_SWING_MODE_COMMAND_TOPIC, CONF_SWING_MODE_STATE_TOPIC, CONF_TEMP_COMMAND_TOPIC, @@ -203,12 +232,27 @@ CONF_TEMP_STATE_TOPIC, ) + +def valid_preset_mode_configuration(config): + """Validate that the preset mode reset payload is not one of the preset modes.""" + if PRESET_NONE in config.get(CONF_PRESET_MODES_LIST): + raise ValueError("preset_modes must not include preset mode 'none'") + if config.get(CONF_PRESET_MODE_COMMAND_TOPIC): + for config_parameter in DEPRECATED_INVALID: + if config.get(config_parameter): + raise vol.MultipleInvalid( + "preset_modes cannot be used with deprecated away or hold mode config options" + ) + return config + + SCHEMA_BASE = CLIMATE_PLATFORM_SCHEMA.extend(MQTT_BASE_PLATFORM_SCHEMA.schema) _PLATFORM_SCHEMA_BASE = SCHEMA_BASE.extend( { vol.Optional(CONF_AUX_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_AUX_STATE_TEMPLATE): cv.template, vol.Optional(CONF_AUX_STATE_TOPIC): mqtt.valid_subscribe_topic, + # AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 vol.Optional(CONF_AWAY_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_AWAY_MODE_STATE_TEMPLATE): cv.template, vol.Optional(CONF_AWAY_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, @@ -222,6 +266,7 @@ ): cv.ensure_list, vol.Optional(CONF_FAN_MODE_STATE_TEMPLATE): cv.template, vol.Optional(CONF_FAN_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, + # AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 vol.Optional(CONF_HOLD_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_HOLD_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_HOLD_STATE_TEMPLATE): cv.template, @@ -252,10 +297,20 @@ [PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE] ), vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean, - # CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.4 + # CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9 vol.Optional(CONF_SEND_IF_OFF, default=True): cv.boolean, vol.Optional(CONF_ACTION_TEMPLATE): cv.template, vol.Optional(CONF_ACTION_TOPIC): mqtt.valid_subscribe_topic, + # CONF_PRESET_MODE_COMMAND_TOPIC and CONF_PRESET_MODES_LIST must be used together + vol.Inclusive( + CONF_PRESET_MODE_COMMAND_TOPIC, "preset_modes" + ): mqtt.valid_publish_topic, + vol.Inclusive( + CONF_PRESET_MODES_LIST, "preset_modes", default=[] + ): cv.ensure_list, + vol.Optional(CONF_PRESET_MODE_COMMAND_TEMPLATE): cv.template, + vol.Optional(CONF_PRESET_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_PRESET_MODE_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_SWING_MODE_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_SWING_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional( @@ -285,17 +340,37 @@ ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) PLATFORM_SCHEMA = vol.All( - # CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.4 - cv.deprecated(CONF_SEND_IF_OFF), _PLATFORM_SCHEMA_BASE, + # CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9 + cv.deprecated(CONF_SEND_IF_OFF), + # AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 + cv.deprecated(CONF_AWAY_MODE_COMMAND_TOPIC), + cv.deprecated(CONF_AWAY_MODE_STATE_TEMPLATE), + cv.deprecated(CONF_AWAY_MODE_STATE_TOPIC), + cv.deprecated(CONF_HOLD_COMMAND_TEMPLATE), + cv.deprecated(CONF_HOLD_COMMAND_TOPIC), + cv.deprecated(CONF_HOLD_STATE_TEMPLATE), + cv.deprecated(CONF_HOLD_STATE_TOPIC), + cv.deprecated(CONF_HOLD_LIST), + valid_preset_mode_configuration, ) _DISCOVERY_SCHEMA_BASE = _PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA) DISCOVERY_SCHEMA = vol.All( - # CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.4 - cv.deprecated(CONF_SEND_IF_OFF), _DISCOVERY_SCHEMA_BASE, + # CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9 + cv.deprecated(CONF_SEND_IF_OFF), + # AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 + cv.deprecated(CONF_AWAY_MODE_COMMAND_TOPIC), + cv.deprecated(CONF_AWAY_MODE_STATE_TEMPLATE), + cv.deprecated(CONF_AWAY_MODE_STATE_TOPIC), + cv.deprecated(CONF_HOLD_COMMAND_TEMPLATE), + cv.deprecated(CONF_HOLD_COMMAND_TOPIC), + cv.deprecated(CONF_HOLD_STATE_TEMPLATE), + cv.deprecated(CONF_HOLD_STATE_TOPIC), + cv.deprecated(CONF_HOLD_LIST), + valid_preset_mode_configuration, ) @@ -346,12 +421,15 @@ def __init__(self, hass, config, config_entry, discovery_data): self._current_swing_mode = None self._current_temp = None self._hold = None + self._preset_mode = None self._target_temp = None self._target_temp_high = None self._target_temp_low = None self._topic = None self._value_templates = None self._command_templates = None + self._feature_preset_mode = False + self._optimistic_preset_mode = None MqttEntity.__init__(self, hass, config, config_entry, discovery_data) @@ -384,7 +462,14 @@ def _setup_from_config(self, config): self._current_swing_mode = HVAC_MODE_OFF if self._topic[CONF_MODE_STATE_TOPIC] is None: self._current_operation = HVAC_MODE_OFF + self._feature_preset_mode = CONF_PRESET_MODE_COMMAND_TOPIC in config + if self._feature_preset_mode: + self._preset_modes = config[CONF_PRESET_MODES_LIST] + else: + self._preset_modes = [] + self._optimistic_preset_mode = CONF_PRESET_MODE_STATE_TOPIC not in config self._action = None + # AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 self._away = False self._hold = None self._aux = False @@ -582,6 +667,7 @@ def handle_onoff_mode_received(msg, template_name, attr): self.async_write_ha_state() + # AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 @callback @log_messages(self.hass, self.entity_id) def handle_away_mode_received(msg): @@ -598,6 +684,7 @@ def handle_aux_mode_received(msg): add_subscription(topics, CONF_AUX_STATE_TOPIC, handle_aux_mode_received) + # AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 @callback @log_messages(self.hass, self.entity_id) def handle_hold_mode_received(msg): @@ -608,10 +695,38 @@ def handle_hold_mode_received(msg): payload = None self._hold = payload + self._preset_mode = None self.async_write_ha_state() add_subscription(topics, CONF_HOLD_STATE_TOPIC, handle_hold_mode_received) + @callback + @log_messages(self.hass, self.entity_id) + def handle_preset_mode_received(msg): + """Handle receiving preset mode via MQTT.""" + preset_mode = render_template(msg, CONF_PRESET_MODE_VALUE_TEMPLATE) + if preset_mode in [PRESET_NONE, PAYLOAD_NONE]: + self._preset_mode = None + self.async_write_ha_state() + return + if not preset_mode: + _LOGGER.debug("Ignoring empty preset_mode from '%s'", msg.topic) + return + if preset_mode not in self._preset_modes: + _LOGGER.warning( + "'%s' received on topic %s. '%s' is not a valid preset mode", + msg.payload, + msg.topic, + preset_mode, + ) + else: + self._preset_mode = preset_mode + self.async_write_ha_state() + + add_subscription( + topics, CONF_PRESET_MODE_STATE_TOPIC, handle_preset_mode_received + ) + self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, self._sub_state, topics ) @@ -668,8 +783,11 @@ def target_temperature_step(self): return self._config[CONF_TEMP_STEP] @property - def preset_mode(self): + def preset_mode(self) -> str | None: """Return preset mode.""" + if self._feature_preset_mode and self._preset_mode is not None: + return self._preset_mode + # AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 if self._hold: return self._hold if self._away: @@ -677,10 +795,12 @@ def preset_mode(self): return PRESET_NONE @property - def preset_modes(self): + def preset_modes(self) -> list: """Return preset modes.""" presets = [] + presets.extend(self._preset_modes) + # AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 if (self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None) or ( self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None ): @@ -726,7 +846,7 @@ async def _set_temperature( # optimistic mode setattr(self, attr, temp) - # CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.4 + # CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9 if ( self._config[CONF_SEND_IF_OFF] or self._current_operation != HVAC_MODE_OFF @@ -769,7 +889,7 @@ async def async_set_temperature(self, **kwargs): async def async_set_swing_mode(self, swing_mode): """Set new swing mode.""" - # CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.4 + # CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9 if self._config[CONF_SEND_IF_OFF] or self._current_operation != HVAC_MODE_OFF: payload = self._command_templates[CONF_SWING_MODE_COMMAND_TEMPLATE]( swing_mode @@ -782,7 +902,7 @@ async def async_set_swing_mode(self, swing_mode): async def async_set_fan_mode(self, fan_mode): """Set new target temperature.""" - # CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.4 + # CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9 if self._config[CONF_SEND_IF_OFF] or self._current_operation != HVAC_MODE_OFF: payload = self._command_templates[CONF_FAN_MODE_COMMAND_TEMPLATE](fan_mode) await self._publish(CONF_FAN_MODE_COMMAND_TOPIC, payload) @@ -817,11 +937,29 @@ def swing_modes(self): """List of available swing modes.""" return self._config[CONF_SWING_MODE_LIST] - async def async_set_preset_mode(self, preset_mode): + async def async_set_preset_mode(self, preset_mode: str) -> None: """Set a preset mode.""" - # Track if we should optimistic update the state + if self._feature_preset_mode: + if preset_mode not in self.preset_modes and preset_mode is not PRESET_NONE: + _LOGGER.warning("'%s' is not a valid preset mode", preset_mode) + return + mqtt_payload = self._command_templates[CONF_PRESET_MODE_COMMAND_TEMPLATE]( + preset_mode + ) + await self._publish( + CONF_PRESET_MODE_COMMAND_TOPIC, + mqtt_payload, + ) + + if self._optimistic_preset_mode: + self._preset_mode = preset_mode if preset_mode != PRESET_NONE else None + self.async_write_ha_state() + + return + + # Update hold or away mode: Track if we should optimistic update the state optimistic_update = await self._set_away_mode(preset_mode == PRESET_AWAY) - hold_mode = preset_mode + hold_mode: str | None = preset_mode if preset_mode in [PRESET_NONE, PRESET_AWAY]: hold_mode = None optimistic_update = await self._set_hold_mode(hold_mode) or optimistic_update @@ -829,6 +967,7 @@ async def async_set_preset_mode(self, preset_mode): if optimistic_update: self.async_write_ha_state() + # AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 async def _set_away_mode(self, state): """Set away mode. @@ -909,8 +1048,10 @@ def supported_features(self): ): support |= SUPPORT_SWING_MODE + # AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 if ( - (self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None) + self._feature_preset_mode + or (self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None) or (self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None) or (self._topic[CONF_HOLD_STATE_TOPIC] is not None) or (self._topic[CONF_HOLD_COMMAND_TOPIC] is not None) diff --git a/tests/components/mqtt/test_climate.py b/tests/components/mqtt/test_climate.py index 624823e0ebb321..c3501267e12956 100644 --- a/tests/components/mqtt/test_climate.py +++ b/tests/components/mqtt/test_climate.py @@ -82,9 +82,34 @@ "temperature_high_command_topic": "temperature-high-topic", "fan_mode_command_topic": "fan-mode-topic", "swing_mode_command_topic": "swing-mode-topic", + "aux_command_topic": "aux-topic", + "preset_mode_command_topic": "preset-mode-topic", + "preset_modes": [ + "eco", + "away", + "boost", + "comfort", + "home", + "sleep", + "activity", + ], + } +} + +# AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 +DEFAULT_LEGACY_CONFIG = { + CLIMATE_DOMAIN: { + "platform": "mqtt", + "name": "test", + "mode_command_topic": "mode-topic", + "temperature_command_topic": "temperature-topic", + "temperature_low_command_topic": "temperature-low-topic", + "temperature_high_command_topic": "temperature-high-topic", + "fan_mode_command_topic": "fan-mode-topic", + "swing_mode_command_topic": "swing-mode-topic", + "aux_command_topic": "aux-topic", "away_mode_command_topic": "away-mode-topic", "hold_command_topic": "hold-topic", - "aux_command_topic": "aux-topic", } } @@ -103,6 +128,42 @@ async def test_setup_params(hass, mqtt_mock): assert state.attributes.get("max_temp") == DEFAULT_MAX_TEMP +async def test_preset_none_in_preset_modes(hass, mqtt_mock, caplog): + """Test the preset mode payload reset configuration.""" + config = copy.deepcopy(DEFAULT_CONFIG[CLIMATE_DOMAIN]) + config["preset_modes"].append("none") + assert await async_setup_component(hass, CLIMATE_DOMAIN, {CLIMATE_DOMAIN: config}) + await hass.async_block_till_done() + assert "Invalid config for [climate.mqtt]: not a valid value" in caplog.text + state = hass.states.get(ENTITY_CLIMATE) + assert state is None + + +# AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 +@pytest.mark.parametrize( + "parameter,config_value", + [ + ("away_mode_command_topic", "away-mode-command-topic"), + ("away_mode_state_topic", "away-mode-state-topic"), + ("away_mode_state_template", "{{ value_json }}"), + ("hold_mode_command_topic", "hold-mode-command-topic"), + ("hold_mode_command_template", "hold-mode-command-template"), + ("hold_mode_state_topic", "hold-mode-state-topic"), + ("hold_mode_state_template", "{{ value_json }}"), + ], +) +async def test_preset_modes_deprecation_guard( + hass, mqtt_mock, caplog, parameter, config_value +): + """Test the configuration for invalid legacy parameters.""" + config = copy.deepcopy(DEFAULT_CONFIG[CLIMATE_DOMAIN]) + config[parameter] = config_value + assert await async_setup_component(hass, CLIMATE_DOMAIN, {CLIMATE_DOMAIN: config}) + await hass.async_block_till_done() + state = hass.states.get(ENTITY_CLIMATE) + assert state is None + + async def test_supported_features(hass, mqtt_mock): """Test the supported_features.""" assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG) @@ -469,9 +530,99 @@ async def test_handle_action_received(hass, mqtt_mock): assert hvac_action == action +async def test_set_preset_mode_optimistic(hass, mqtt_mock, caplog): + """Test setting of the preset mode.""" + config = copy.deepcopy(DEFAULT_CONFIG) + assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("preset_mode") == "none" + + await common.async_set_preset_mode(hass, "away", ENTITY_CLIMATE) + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-topic", "away", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("preset_mode") == "away" + + await common.async_set_preset_mode(hass, "eco", ENTITY_CLIMATE) + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-topic", "eco", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("preset_mode") == "eco" + + await common.async_set_preset_mode(hass, "none", ENTITY_CLIMATE) + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-topic", "none", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("preset_mode") == "none" + + await common.async_set_preset_mode(hass, "comfort", ENTITY_CLIMATE) + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-topic", "comfort", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("preset_mode") == "comfort" + + await common.async_set_preset_mode(hass, "invalid", ENTITY_CLIMATE) + assert "'invalid' is not a valid preset mode" in caplog.text + + +async def test_set_preset_mode_pessimistic(hass, mqtt_mock, caplog): + """Test setting of the preset mode.""" + config = copy.deepcopy(DEFAULT_CONFIG) + config["climate"]["preset_mode_state_topic"] = "preset-mode-state" + assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("preset_mode") == "none" + + async_fire_mqtt_message(hass, "preset-mode-state", "away") + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("preset_mode") == "away" + + async_fire_mqtt_message(hass, "preset-mode-state", "eco") + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("preset_mode") == "eco" + + async_fire_mqtt_message(hass, "preset-mode-state", "none") + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("preset_mode") == "none" + + async_fire_mqtt_message(hass, "preset-mode-state", "comfort") + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("preset_mode") == "comfort" + + async_fire_mqtt_message(hass, "preset-mode-state", "None") + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("preset_mode") == "none" + + async_fire_mqtt_message(hass, "preset-mode-state", "home") + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("preset_mode") == "home" + + async_fire_mqtt_message(hass, "preset-mode-state", "nonsense") + assert ( + "'nonsense' received on topic preset-mode-state. 'nonsense' is not a valid preset mode" + in caplog.text + ) + + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("preset_mode") == "home" + + +# AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 async def test_set_away_mode_pessimistic(hass, mqtt_mock): """Test setting of the away mode.""" - config = copy.deepcopy(DEFAULT_CONFIG) + config = copy.deepcopy(DEFAULT_LEGACY_CONFIG) config["climate"]["away_mode_state_topic"] = "away-state" assert await async_setup_component(hass, CLIMATE_DOMAIN, config) await hass.async_block_till_done() @@ -496,9 +647,10 @@ async def test_set_away_mode_pessimistic(hass, mqtt_mock): assert state.attributes.get("preset_mode") == "none" +# AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 async def test_set_away_mode(hass, mqtt_mock): """Test setting of the away mode.""" - config = copy.deepcopy(DEFAULT_CONFIG) + config = copy.deepcopy(DEFAULT_LEGACY_CONFIG) config["climate"]["payload_on"] = "AN" config["climate"]["payload_off"] = "AUS" @@ -537,9 +689,10 @@ async def test_set_away_mode(hass, mqtt_mock): assert state.attributes.get("preset_mode") == "away" +# AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 async def test_set_hold_pessimistic(hass, mqtt_mock): """Test setting the hold mode in pessimistic mode.""" - config = copy.deepcopy(DEFAULT_CONFIG) + config = copy.deepcopy(DEFAULT_LEGACY_CONFIG) config["climate"]["hold_state_topic"] = "hold-state" assert await async_setup_component(hass, CLIMATE_DOMAIN, config) await hass.async_block_till_done() @@ -560,9 +713,10 @@ async def test_set_hold_pessimistic(hass, mqtt_mock): assert state.attributes.get("preset_mode") == "none" +# AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 async def test_set_hold(hass, mqtt_mock): """Test setting the hold mode.""" - assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG) + assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_LEGACY_CONFIG) await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) @@ -591,9 +745,10 @@ async def test_set_hold(hass, mqtt_mock): assert state.attributes.get("preset_mode") == "none" +# AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 async def test_set_preset_away(hass, mqtt_mock): """Test setting the hold mode and away mode.""" - assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG) + assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_LEGACY_CONFIG) await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) @@ -624,9 +779,10 @@ async def test_set_preset_away(hass, mqtt_mock): assert state.attributes.get("preset_mode") == "hold-on-again" +# AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 async def test_set_preset_away_pessimistic(hass, mqtt_mock): """Test setting the hold mode and away mode in pessimistic mode.""" - config = copy.deepcopy(DEFAULT_CONFIG) + config = copy.deepcopy(DEFAULT_LEGACY_CONFIG) config["climate"]["hold_state_topic"] = "hold-state" config["climate"]["away_mode_state_topic"] = "away-state" assert await async_setup_component(hass, CLIMATE_DOMAIN, config) @@ -674,9 +830,10 @@ async def test_set_preset_away_pessimistic(hass, mqtt_mock): assert state.attributes.get("preset_mode") == "hold-on-again" +# AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 async def test_set_preset_mode_twice(hass, mqtt_mock): """Test setting of the same mode twice only publishes once.""" - assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG) + assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_LEGACY_CONFIG) await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) @@ -804,21 +961,19 @@ async def test_get_with_templates(hass, mqtt_mock, caplog): # By default, just unquote the JSON-strings config["climate"]["value_template"] = "{{ value_json }}" config["climate"]["action_template"] = "{{ value_json }}" - # Something more complicated for hold mode - config["climate"]["hold_state_template"] = "{{ value_json.attribute }}" # Rendering to a bool for aux heat config["climate"]["aux_state_template"] = "{{ value == 'switchmeon' }}" + # Rendering preset_mode + config["climate"]["preset_mode_value_template"] = "{{ value_json.attribute }}" config["climate"]["action_topic"] = "action" config["climate"]["mode_state_topic"] = "mode-state" config["climate"]["fan_mode_state_topic"] = "fan-state" config["climate"]["swing_mode_state_topic"] = "swing-state" config["climate"]["temperature_state_topic"] = "temperature-state" - config["climate"]["away_mode_state_topic"] = "away-state" - config["climate"]["hold_state_topic"] = "hold-state" config["climate"]["aux_state_topic"] = "aux-state" config["climate"]["current_temperature_topic"] = "current-temperature" - + config["climate"]["preset_mode_state_topic"] = "current-preset-mode" assert await async_setup_component(hass, CLIMATE_DOMAIN, config) await hass.async_block_till_done() @@ -854,31 +1009,18 @@ async def test_get_with_templates(hass, mqtt_mock, caplog): # ... but the actual value stays unchanged. assert state.attributes.get("temperature") == 1031 - # Away Mode + # Preset Mode assert state.attributes.get("preset_mode") == "none" - async_fire_mqtt_message(hass, "away-state", '"ON"') - state = hass.states.get(ENTITY_CLIMATE) - assert state.attributes.get("preset_mode") == "away" - - # Away Mode with JSON values - async_fire_mqtt_message(hass, "away-state", "false") + async_fire_mqtt_message(hass, "current-preset-mode", '{"attribute": "eco"}') state = hass.states.get(ENTITY_CLIMATE) - assert state.attributes.get("preset_mode") == "none" - - async_fire_mqtt_message(hass, "away-state", "true") - state = hass.states.get(ENTITY_CLIMATE) - assert state.attributes.get("preset_mode") == "away" - - # Hold Mode + assert state.attributes.get("preset_mode") == "eco" + # Test with an empty json async_fire_mqtt_message( - hass, - "hold-state", - """ - { "attribute": "somemode" } - """, + hass, "current-preset-mode", '{"other_attribute": "some_value"}' ) state = hass.states.get(ENTITY_CLIMATE) - assert state.attributes.get("preset_mode") == "somemode" + assert "Ignoring empty preset_mode from 'current-preset-mode'" + assert state.attributes.get("preset_mode") == "eco" # Aux mode assert state.attributes.get("aux_heat") == "off" @@ -911,12 +1053,60 @@ async def test_get_with_templates(hass, mqtt_mock, caplog): ) -async def test_set_with_templates(hass, mqtt_mock, caplog): +# AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 +async def test_get_with_hold_and_away_mode_and_templates(hass, mqtt_mock, caplog): + """Test getting various for hold and away mode attributes with templates.""" + config = copy.deepcopy(DEFAULT_LEGACY_CONFIG) + config["climate"]["mode_state_topic"] = "mode-state" + # By default, just unquote the JSON-strings + config["climate"]["value_template"] = "{{ value_json }}" + # Something more complicated for hold mode + config["climate"]["hold_state_template"] = "{{ value_json.attribute }}" + config["climate"]["away_mode_state_topic"] = "away-state" + config["climate"]["hold_state_topic"] = "hold-state" + + assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() + + # Operation Mode + state = hass.states.get(ENTITY_CLIMATE) + async_fire_mqtt_message(hass, "mode-state", '"cool"') + state = hass.states.get(ENTITY_CLIMATE) + assert state.state == "cool" + + # Away Mode + assert state.attributes.get("preset_mode") == "none" + async_fire_mqtt_message(hass, "away-state", '"ON"') + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("preset_mode") == "away" + + # Away Mode with JSON values + async_fire_mqtt_message(hass, "away-state", "false") + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("preset_mode") == "none" + + async_fire_mqtt_message(hass, "away-state", "true") + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("preset_mode") == "away" + + # Hold Mode + async_fire_mqtt_message( + hass, + "hold-state", + """ + { "attribute": "somemode" } + """, + ) + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("preset_mode") == "somemode" + + +async def test_set_and_templates(hass, mqtt_mock, caplog): """Test setting various attributes with templates.""" config = copy.deepcopy(DEFAULT_CONFIG) # Create simple templates config["climate"]["fan_mode_command_template"] = "fan_mode: {{ value }}" - config["climate"]["hold_command_template"] = "hold: {{ value }}" + config["climate"]["preset_mode_command_template"] = "preset_mode: {{ value }}" config["climate"]["mode_command_template"] = "mode: {{ value }}" config["climate"]["swing_mode_command_template"] = "swing_mode: {{ value }}" config["climate"]["temperature_command_template"] = "temp: {{ value }}" @@ -935,11 +1125,12 @@ async def test_set_with_templates(hass, mqtt_mock, caplog): state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("fan_mode") == "high" - # Hold Mode + # Preset Mode await common.async_set_preset_mode(hass, PRESET_ECO, ENTITY_CLIMATE) - mqtt_mock.async_publish.call_count == 2 - mqtt_mock.async_publish.assert_any_call("away-mode-topic", "OFF", 0, False) - mqtt_mock.async_publish.assert_any_call("hold-topic", "hold: eco", 0, False) + mqtt_mock.async_publish.call_count == 1 + mqtt_mock.async_publish.assert_any_call( + "preset-mode-topic", "preset_mode: eco", 0, False + ) mqtt_mock.async_publish.reset_mock() state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("preset_mode") == PRESET_ECO @@ -987,6 +1178,26 @@ async def test_set_with_templates(hass, mqtt_mock, caplog): assert state.attributes.get("target_temp_high") == 23 +# AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 +async def test_set_with_away_and_hold_modes_and_templates(hass, mqtt_mock, caplog): + """Test setting various attributes on hold and away mode with templates.""" + config = copy.deepcopy(DEFAULT_LEGACY_CONFIG) + # Create simple templates + config["climate"]["hold_command_template"] = "hold: {{ value }}" + + assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() + + # Hold Mode + await common.async_set_preset_mode(hass, PRESET_ECO, ENTITY_CLIMATE) + mqtt_mock.async_publish.call_count == 2 + mqtt_mock.async_publish.assert_any_call("away-mode-topic", "OFF", 0, False) + mqtt_mock.async_publish.assert_any_call("hold-topic", "hold: eco", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("preset_mode") == PRESET_ECO + + async def test_min_temp_custom(hass, mqtt_mock): """Test a custom min temp.""" config = copy.deepcopy(DEFAULT_CONFIG) @@ -1118,9 +1329,11 @@ async def test_unique_id(hass, mqtt_mock): ("action_topic", "heating", ATTR_HVAC_ACTION, "heating"), ("action_topic", "cooling", ATTR_HVAC_ACTION, "cooling"), ("aux_state_topic", "ON", ATTR_AUX_HEAT, "on"), + # AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 ("away_mode_state_topic", "ON", ATTR_PRESET_MODE, "away"), ("current_temperature_topic", "22.1", ATTR_CURRENT_TEMPERATURE, 22.1), ("fan_mode_state_topic", "low", ATTR_FAN_MODE, "low"), + # AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 ("hold_state_topic", "mode1", ATTR_PRESET_MODE, "mode1"), ("mode_state_topic", "cool", None, None), ("mode_state_topic", "fan_only", None, None), @@ -1135,7 +1348,11 @@ async def test_encoding_subscribable_topics( ): """Test handling of incoming encoded payload.""" config = copy.deepcopy(DEFAULT_CONFIG[CLIMATE_DOMAIN]) - config["hold_modes"] = ["mode1", "mode2"] + # AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 + if topic in ["hold_state_topic", "away_mode_state_topic"]: + config["hold_modes"] = ["mode1", "mode2"] + del config["preset_modes"] + del config["preset_mode_command_topic"] await help_test_encoding_subscribable_topics( hass, mqtt_mock, @@ -1317,6 +1534,13 @@ async def test_precision_whole(hass, mqtt_mock): "cool", "mode_command_template", ), + ( + climate.SERVICE_SET_PRESET_MODE, + "preset_mode_command_topic", + {"preset_mode": "sleep"}, + "sleep", + "preset_mode_command_template", + ), ( climate.SERVICE_SET_PRESET_MODE, "away_mode_command_topic", @@ -1334,8 +1558,8 @@ async def test_precision_whole(hass, mqtt_mock): ( climate.SERVICE_SET_PRESET_MODE, "hold_command_topic", - {"preset_mode": "some_hold_mode"}, - "some_hold_mode", + {"preset_mode": "comfort"}, + "comfort", "hold_command_template", ), ( @@ -1402,7 +1626,10 @@ async def test_publishing_with_custom_encoding( ): """Test publishing MQTT payload with different encoding.""" domain = climate.DOMAIN - config = DEFAULT_CONFIG[domain] + config = copy.deepcopy(DEFAULT_CONFIG[domain]) + if topic != "preset_mode_command_topic": + del config["preset_mode_command_topic"] + del config["preset_modes"] await help_test_publishing_with_custom_encoding( hass, From 1a9fda96c315077546b691977a5c4b1927ca6fa6 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 17 Feb 2022 14:05:07 +0100 Subject: [PATCH 0740/1098] Revert "Update google-cloud-texttospeech to 2.10.0" (#66736) --- .../components/google_cloud/manifest.json | 2 +- homeassistant/components/google_cloud/tts.py | 29 +++++++++---------- requirements_all.txt | 2 +- script/pip_check | 2 +- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/google_cloud/manifest.json b/homeassistant/components/google_cloud/manifest.json index 83801d50354c73..90c5eebaeb2376 100644 --- a/homeassistant/components/google_cloud/manifest.json +++ b/homeassistant/components/google_cloud/manifest.json @@ -2,7 +2,7 @@ "domain": "google_cloud", "name": "Google Cloud Platform", "documentation": "https://www.home-assistant.io/integrations/google_cloud", - "requirements": ["google-cloud-texttospeech==2.10.0"], + "requirements": ["google-cloud-texttospeech==0.4.0"], "codeowners": ["@lufton"], "iot_class": "cloud_push" } diff --git a/homeassistant/components/google_cloud/tts.py b/homeassistant/components/google_cloud/tts.py index 0de580ef7b7a8c..3d65f4eb2972dc 100644 --- a/homeassistant/components/google_cloud/tts.py +++ b/homeassistant/components/google_cloud/tts.py @@ -122,9 +122,13 @@ CONF_TEXT_TYPE, ] -GENDER_SCHEMA = vol.All(vol.Upper, vol.In(texttospeech.SsmlVoiceGender.__members__)) +GENDER_SCHEMA = vol.All( + vol.Upper, vol.In(texttospeech.enums.SsmlVoiceGender.__members__) +) VOICE_SCHEMA = cv.matches_regex(VOICE_REGEX) -SCHEMA_ENCODING = vol.All(vol.Upper, vol.In(texttospeech.AudioEncoding.__members__)) +SCHEMA_ENCODING = vol.All( + vol.Upper, vol.In(texttospeech.enums.AudioEncoding.__members__) +) SPEED_SCHEMA = vol.All(vol.Coerce(float), vol.Clamp(min=MIN_SPEED, max=MAX_SPEED)) PITCH_SCHEMA = vol.All(vol.Coerce(float), vol.Clamp(min=MIN_PITCH, max=MAX_PITCH)) GAIN_SCHEMA = vol.All(vol.Coerce(float), vol.Clamp(min=MIN_GAIN, max=MAX_GAIN)) @@ -259,32 +263,27 @@ async def async_get_tts_audio(self, message, language, options=None): try: params = {options[CONF_TEXT_TYPE]: message} - synthesis_input = texttospeech.SynthesisInput(**params) + # pylint: disable=no-member + synthesis_input = texttospeech.types.SynthesisInput(**params) - voice = texttospeech.VoiceSelectionParams( + voice = texttospeech.types.VoiceSelectionParams( language_code=language, - ssml_gender=texttospeech.SsmlVoiceGender[options[CONF_GENDER]], + ssml_gender=texttospeech.enums.SsmlVoiceGender[options[CONF_GENDER]], name=_voice, ) - audio_config = texttospeech.AudioConfig( - audio_encoding=texttospeech.AudioEncoding[_encoding], + audio_config = texttospeech.types.AudioConfig( + audio_encoding=texttospeech.enums.AudioEncoding[_encoding], speaking_rate=options[CONF_SPEED], pitch=options[CONF_PITCH], volume_gain_db=options[CONF_GAIN], effects_profile_id=options[CONF_PROFILES], ) - - request = { - "voice": voice, - "audio_config": audio_config, - "input": synthesis_input, - } + # pylint: enable=no-member async with async_timeout.timeout(10): - assert self.hass response = await self.hass.async_add_executor_job( - self._client.synthesize_speech, request + self._client.synthesize_speech, synthesis_input, voice, audio_config ) return _encoding, response.audio_content diff --git a/requirements_all.txt b/requirements_all.txt index d963262b712b56..ac972d24fb58c8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -758,7 +758,7 @@ google-api-python-client==1.6.4 google-cloud-pubsub==2.9.0 # homeassistant.components.google_cloud -google-cloud-texttospeech==2.10.0 +google-cloud-texttospeech==0.4.0 # homeassistant.components.nest google-nest-sdm==1.7.1 diff --git a/script/pip_check b/script/pip_check index af47f101fbba5b..c30a7382f27335 100755 --- a/script/pip_check +++ b/script/pip_check @@ -3,7 +3,7 @@ PIP_CACHE=$1 # Number of existing dependency conflicts # Update if a PR resolve one! -DEPENDENCY_CONFLICTS=9 +DEPENDENCY_CONFLICTS=10 PIP_CHECK=$(pip check --cache-dir=$PIP_CACHE) LINE_COUNT=$(echo "$PIP_CHECK" | wc -l) From de24d00a1c04a0714619ca71b052733f9cefc685 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 17 Feb 2022 14:11:47 +0100 Subject: [PATCH 0741/1098] Use min/max/step from thermostat in Plugwise (#66618) --- homeassistant/components/plugwise/climate.py | 8 ++++++-- .../plugwise/fixtures/anna_heatpump/all_data.json | 3 +++ tests/components/plugwise/test_climate.py | 3 +++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index abf08b70dadb55..c4c444441c28f2 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -51,8 +51,6 @@ async def async_setup_entry( class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): """Representation of an Plugwise thermostat.""" - _attr_max_temp = DEFAULT_MAX_TEMP - _attr_min_temp = DEFAULT_MIN_TEMP _attr_temperature_unit = TEMP_CELSIUS def __init__( @@ -79,6 +77,12 @@ def __init__( if self.device.get("available_schedules") != ["None"]: self._attr_hvac_modes.append(HVAC_MODE_AUTO) + self._attr_min_temp = self.device.get("lower_bound", DEFAULT_MIN_TEMP) + self._attr_max_temp = self.device.get("upper_bound", DEFAULT_MAX_TEMP) + if resolution := self.device.get("resolution"): + # Ensure we don't drop below 0.1 + self._attr_target_temperature_step = max(resolution, 0.1) + @property def current_temperature(self) -> float | None: """Return the current temperature.""" diff --git a/tests/components/plugwise/fixtures/anna_heatpump/all_data.json b/tests/components/plugwise/fixtures/anna_heatpump/all_data.json index 017d1ce41e8244..ee0f60d92ceec7 100644 --- a/tests/components/plugwise/fixtures/anna_heatpump/all_data.json +++ b/tests/components/plugwise/fixtures/anna_heatpump/all_data.json @@ -53,6 +53,9 @@ "model": "Anna", "name": "Anna", "vendor": "Plugwise", + "lower_bound": 5, + "upper_bound": 31, + "resolution": 0.1, "preset_modes": ["no_frost", "home", "away", "asleep", "vacation"], "active_preset": "home", "presets": { diff --git a/tests/components/plugwise/test_climate.py b/tests/components/plugwise/test_climate.py index c40ad32c078020..1a8b7815be48d2 100644 --- a/tests/components/plugwise/test_climate.py +++ b/tests/components/plugwise/test_climate.py @@ -166,6 +166,9 @@ async def test_anna_climate_entity_attributes( assert state.attributes["preset_mode"] == "home" assert state.attributes["supported_features"] == 17 assert state.attributes["temperature"] == 21.0 + assert state.attributes["min_temp"] == 5.0 + assert state.attributes["max_temp"] == 31.0 + assert state.attributes["target_temp_step"] == 0.1 async def test_anna_climate_entity_climate_changes( From 7012375bf14c5b68dc45a850f75469e5ac2af7d3 Mon Sep 17 00:00:00 2001 From: Nenad Bogojevic Date: Thu, 17 Feb 2022 14:18:33 +0100 Subject: [PATCH 0742/1098] Bump withings-api 2.3.2->2.4.0 (#66723) --- .../components/withings/manifest.json | 19 ++++++++++++++----- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/withings/manifest.json b/homeassistant/components/withings/manifest.json index f9ec5321c620ee..f15045b98da07a 100644 --- a/homeassistant/components/withings/manifest.json +++ b/homeassistant/components/withings/manifest.json @@ -3,9 +3,18 @@ "name": "Withings", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/withings", - "requirements": ["withings-api==2.3.2"], - "dependencies": ["http", "webhook"], - "codeowners": ["@vangorra"], + "requirements": [ + "withings-api==2.4.0" + ], + "dependencies": [ + "http", + "webhook" + ], + "codeowners": [ + "@vangorra" + ], "iot_class": "cloud_polling", - "loggers": ["withings_api"] -} + "loggers": [ + "withings_api" + ] +} \ No newline at end of file diff --git a/requirements_all.txt b/requirements_all.txt index ac972d24fb58c8..150d7b289498be 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2485,7 +2485,7 @@ wiffi==1.1.0 wirelesstagpy==0.8.1 # homeassistant.components.withings -withings-api==2.3.2 +withings-api==2.4.0 # homeassistant.components.wled wled==0.13.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7a1d16c0e87ba3..8f10a13b1458d0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1531,7 +1531,7 @@ whois==0.9.13 wiffi==1.1.0 # homeassistant.components.withings -withings-api==2.3.2 +withings-api==2.4.0 # homeassistant.components.wled wled==0.13.0 From 276fd4f42ce1cdbf5cc8ce25e8c034b88d6da5ca Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 17 Feb 2022 14:58:24 +0100 Subject: [PATCH 0743/1098] Add Python 3.10 to CI (#59729) --- .github/workflows/ci.yaml | 10 +- .github/workflows/wheels.yml | 1 + Dockerfile | 3 +- homeassistant/components/apcupsd/__init__.py | 1 + .../components/apcupsd/manifest.json | 1 + homeassistant/components/apcupsd/sensor.py | 1 + homeassistant/components/apns/manifest.json | 1 + homeassistant/components/apns/notify.py | 1 + homeassistant/components/xbee/__init__.py | 1 + homeassistant/components/xbee/manifest.json | 1 + homeassistant/components/xbee/sensor.py | 1 + homeassistant/components/zwave/__init__.py | 3 +- homeassistant/components/zwave/config_flow.py | 1 + homeassistant/components/zwave/node_entity.py | 1 + homeassistant/package_constraints.txt | 8 +- requirements_all.txt | 11 +- requirements_test_all.txt | 5 +- script/gen_requirements_all.py | 9 +- tests/components/apns/__init__.py | 1 - tests/components/apns/test_notify.py | 395 ------------------ ...g_flow.py => disabled_test_config_flow.py} | 9 +- tests/components/zwave/test_binary_sensor.py | 5 + tests/components/zwave/test_climate.py | 3 + tests/components/zwave/test_cover.py | 5 + tests/components/zwave/test_fan.py | 5 + tests/components/zwave/test_init.py | 3 + tests/components/zwave/test_light.py | 5 + tests/components/zwave/test_lock.py | 5 + tests/components/zwave/test_node_entity.py | 5 + tests/components/zwave/test_sensor.py | 5 + tests/components/zwave/test_switch.py | 5 + tests/components/zwave/test_websocket_api.py | 6 + tests/components/zwave/test_workaround.py | 5 + tests/components/zwave_js/test_migrate.py | 1 + tests/mock/zwave.py | 4 +- 35 files changed, 99 insertions(+), 428 deletions(-) delete mode 100644 tests/components/apns/__init__.py delete mode 100644 tests/components/apns/test_notify.py rename tests/components/icloud/{test_config_flow.py => disabled_test_config_flow.py} (98%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 321cb7f622b2d8..a2345081b507bb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -10,8 +10,8 @@ on: pull_request: ~ env: - CACHE_VERSION: 7 - PIP_CACHE_VERSION: 1 + CACHE_VERSION: 9 + PIP_CACHE_VERSION: 3 HA_SHORT_VERSION: 2022.3 DEFAULT_PYTHON: 3.9 PRE_COMMIT_CACHE: ~/.cache/pre-commit @@ -524,10 +524,10 @@ jobs: prepare-tests: name: Prepare tests for Python ${{ matrix.python-version }} runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 60 strategy: matrix: - python-version: [3.9] + python-version: ["3.9", "3.10"] outputs: python-key: ${{ steps.generate-python-key.outputs.key }} container: homeassistant/ci-azure:${{ matrix.python-version }} @@ -721,7 +721,7 @@ jobs: fail-fast: false matrix: group: ${{ fromJson(needs.changes.outputs.test_groups) }} - python-version: [3.9] + python-version: ["3.9", "3.10"] name: >- Run tests Python ${{ matrix.python-version }} (${{ matrix.group }}) container: homeassistant/ci-azure:${{ matrix.python-version }} diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 6c9a7759c3d50b..a60ac651e31802 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -154,6 +154,7 @@ jobs: sed -i "s|# face_recognition|face_recognition|g" ${requirement_file} sed -i "s|# bme680|bme680|g" ${requirement_file} sed -i "s|# python-gammu|python-gammu|g" ${requirement_file} + sed -i "s|# homeassistant-pyozw|homeassistant-pyozw|g" ${requirement_file} done - name: Build wheels diff --git a/Dockerfile b/Dockerfile index 1d6ce675e74da5..7193d706b8995f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,8 @@ RUN \ -r homeassistant/requirements.txt --use-deprecated=legacy-resolver COPY requirements_all.txt homeassistant/ RUN \ - pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \ + sed -i "s|# homeassistant-pyozw|homeassistant-pyozw|g" homeassistant/requirements_all.txt \ + && pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \ -r homeassistant/requirements_all.txt --use-deprecated=legacy-resolver ## Setup Home Assistant Core diff --git a/homeassistant/components/apcupsd/__init__.py b/homeassistant/components/apcupsd/__init__.py index 7cbf33f8b47aee..a032430e1bc061 100644 --- a/homeassistant/components/apcupsd/__init__.py +++ b/homeassistant/components/apcupsd/__init__.py @@ -1,4 +1,5 @@ """Support for APCUPSd via its Network Information Server (NIS).""" +# pylint: disable=import-error from datetime import timedelta import logging diff --git a/homeassistant/components/apcupsd/manifest.json b/homeassistant/components/apcupsd/manifest.json index 13a08685c68af3..18d5549ef9a594 100644 --- a/homeassistant/components/apcupsd/manifest.json +++ b/homeassistant/components/apcupsd/manifest.json @@ -1,4 +1,5 @@ { + "disabled": "Integration library not compatible with Python 3.10", "domain": "apcupsd", "name": "apcupsd", "documentation": "https://www.home-assistant.io/integrations/apcupsd", diff --git a/homeassistant/components/apcupsd/sensor.py b/homeassistant/components/apcupsd/sensor.py index b7e7366796b834..2fae17ac922e68 100644 --- a/homeassistant/components/apcupsd/sensor.py +++ b/homeassistant/components/apcupsd/sensor.py @@ -1,4 +1,5 @@ """Support for APCUPSd sensors.""" +# pylint: disable=import-error from __future__ import annotations import logging diff --git a/homeassistant/components/apns/manifest.json b/homeassistant/components/apns/manifest.json index 2ea4e495a2b245..bcefdcf0639456 100644 --- a/homeassistant/components/apns/manifest.json +++ b/homeassistant/components/apns/manifest.json @@ -1,4 +1,5 @@ { + "disabled": "Integration library not compatible with Python 3.10", "domain": "apns", "name": "Apple Push Notification Service (APNS)", "documentation": "https://www.home-assistant.io/integrations/apns", diff --git a/homeassistant/components/apns/notify.py b/homeassistant/components/apns/notify.py index 4cc13a3057fb63..8d0dcc334e94d5 100644 --- a/homeassistant/components/apns/notify.py +++ b/homeassistant/components/apns/notify.py @@ -1,4 +1,5 @@ """APNS Notification platform.""" +# pylint: disable=import-error from contextlib import suppress import logging diff --git a/homeassistant/components/xbee/__init__.py b/homeassistant/components/xbee/__init__.py index 17d861d6432216..6a7aba16b95319 100644 --- a/homeassistant/components/xbee/__init__.py +++ b/homeassistant/components/xbee/__init__.py @@ -1,4 +1,5 @@ """Support for XBee Zigbee devices.""" +# pylint: disable=import-error from binascii import hexlify, unhexlify import logging diff --git a/homeassistant/components/xbee/manifest.json b/homeassistant/components/xbee/manifest.json index bd1a0d2a1e153b..150036129d2b54 100644 --- a/homeassistant/components/xbee/manifest.json +++ b/homeassistant/components/xbee/manifest.json @@ -1,4 +1,5 @@ { + "disabled": "Integration library not compatible with Python 3.10", "domain": "xbee", "name": "XBee", "documentation": "https://www.home-assistant.io/integrations/xbee", diff --git a/homeassistant/components/xbee/sensor.py b/homeassistant/components/xbee/sensor.py index 1d1a4b99705ddd..9cea60ade8caeb 100644 --- a/homeassistant/components/xbee/sensor.py +++ b/homeassistant/components/xbee/sensor.py @@ -1,4 +1,5 @@ """Support for XBee Zigbee sensors.""" +# pylint: disable=import-error from __future__ import annotations from binascii import hexlify diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index cd0bda6735ff24..3424aa11a87e8d 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -1,4 +1,5 @@ """Support for Z-Wave.""" +# pylint: disable=import-error # pylint: disable=import-outside-toplevel from __future__ import annotations @@ -355,8 +356,6 @@ async def async_setup_entry( # noqa: C901 from openzwave.group import ZWaveGroup from openzwave.network import ZWaveNetwork from openzwave.option import ZWaveOption - - # pylint: enable=import-error from pydispatch import dispatcher if async_is_ozw_migrated(hass) or async_is_zwave_js_migrated(hass): diff --git a/homeassistant/components/zwave/config_flow.py b/homeassistant/components/zwave/config_flow.py index ce7aebd801a133..f29f2e6f6d0960 100644 --- a/homeassistant/components/zwave/config_flow.py +++ b/homeassistant/components/zwave/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure Z-Wave.""" +# pylint: disable=import-error # pylint: disable=import-outside-toplevel from collections import OrderedDict diff --git a/homeassistant/components/zwave/node_entity.py b/homeassistant/components/zwave/node_entity.py index b17034e0e8a7a4..ade6828431326b 100644 --- a/homeassistant/components/zwave/node_entity.py +++ b/homeassistant/components/zwave/node_entity.py @@ -1,4 +1,5 @@ """Entity class that represents Z-Wave node.""" +# pylint: disable=import-error # pylint: disable=import-outside-toplevel from itertools import count diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index e26dcbe925ab1d..4d3670ac9b4242 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -67,10 +67,6 @@ enum34==1000000000.0.0 typing==1000000000.0.0 uuid==1000000000.0.0 -# Temporary constraint on pandas, to unblock 2021.7 releases -# until we have fixed the wheels builds for newer versions. -pandas==1.3.0 - # regex causes segfault with version 2021.8.27 # https://bitbucket.org/mrabarnett/mrab-regex/issues/421/2021827-results-in-fatal-python-error # This is fixed in 2021.8.28 @@ -84,6 +80,10 @@ anyio==3.5.0 h11==0.12.0 httpcore==0.14.5 +# Ensure we have a hyperframe version that works in Python 3.10 +# 5.2.0 fixed a collections abc deprecation +hyperframe>=5.2.0 + # pytest_asyncio breaks our test suite. We rely on pytest-aiohttp instead pytest_asyncio==1000000000.0.0 diff --git a/requirements_all.txt b/requirements_all.txt index 150d7b289498be..e0c0e4c960c051 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -319,12 +319,6 @@ anel_pwrctrl-homeassistant==0.0.1.dev2 # homeassistant.components.anthemav anthemav==1.2.0 -# homeassistant.components.apcupsd -apcaccess==0.0.13 - -# homeassistant.components.apns -apns2==0.3.0 - # homeassistant.components.apprise apprise==0.9.7 @@ -842,7 +836,7 @@ holidays==0.12 home-assistant-frontend==20220214.0 # homeassistant.components.zwave -homeassistant-pyozw==0.1.10 +# homeassistant-pyozw==0.1.10 # homeassistant.components.home_connect homeconnect==0.6.3 @@ -2493,9 +2487,6 @@ wled==0.13.0 # homeassistant.components.wolflink wolf_smartset==0.1.11 -# homeassistant.components.xbee -xbee-helper==0.0.7 - # homeassistant.components.xbox xbox-webapi==2.0.11 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8f10a13b1458d0..f6d7d898eb3b65 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -239,9 +239,6 @@ ambiclimate==0.2.1 # homeassistant.components.androidtv androidtv[async]==0.0.63 -# homeassistant.components.apns -apns2==0.3.0 - # homeassistant.components.apprise apprise==0.9.7 @@ -552,7 +549,7 @@ holidays==0.12 home-assistant-frontend==20220214.0 # homeassistant.components.zwave -homeassistant-pyozw==0.1.10 +# homeassistant-pyozw==0.1.10 # homeassistant.components.home_connect homeconnect==0.6.3 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 872f2d0c7a8ec3..eed5a5a5946b29 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -27,6 +27,7 @@ "envirophat", "evdev", "face_recognition", + "homeassistant-pyozw", "i2csense", "opencv-python-headless", "pybluez", @@ -94,10 +95,6 @@ typing==1000000000.0.0 uuid==1000000000.0.0 -# Temporary constraint on pandas, to unblock 2021.7 releases -# until we have fixed the wheels builds for newer versions. -pandas==1.3.0 - # regex causes segfault with version 2021.8.27 # https://bitbucket.org/mrabarnett/mrab-regex/issues/421/2021827-results-in-fatal-python-error # This is fixed in 2021.8.28 @@ -111,6 +108,10 @@ h11==0.12.0 httpcore==0.14.5 +# Ensure we have a hyperframe version that works in Python 3.10 +# 5.2.0 fixed a collections abc deprecation +hyperframe>=5.2.0 + # pytest_asyncio breaks our test suite. We rely on pytest-aiohttp instead pytest_asyncio==1000000000.0.0 diff --git a/tests/components/apns/__init__.py b/tests/components/apns/__init__.py deleted file mode 100644 index 42c980a62a7d1d..00000000000000 --- a/tests/components/apns/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for the apns component.""" diff --git a/tests/components/apns/test_notify.py b/tests/components/apns/test_notify.py deleted file mode 100644 index ad55a5697ad2bd..00000000000000 --- a/tests/components/apns/test_notify.py +++ /dev/null @@ -1,395 +0,0 @@ -"""The tests for the APNS component.""" -import io -from unittest.mock import Mock, mock_open, patch - -from apns2.errors import Unregistered -import pytest -import yaml - -import homeassistant.components.apns.notify as apns -import homeassistant.components.notify as notify -from homeassistant.core import State -from homeassistant.setup import async_setup_component - -from tests.common import assert_setup_component - -CONFIG = { - notify.DOMAIN: { - "platform": "apns", - "name": "test_app", - "topic": "testapp.appname", - "cert_file": "test_app.pem", - } -} - - -@pytest.fixture(scope="module", autouse=True) -def mock_apns_notify_open(): - """Mock builtins.open for apns.notify.""" - with patch("homeassistant.components.apns.notify.open", mock_open(), create=True): - yield - - -@patch("os.path.isfile", Mock(return_value=True)) -@patch("os.access", Mock(return_value=True)) -async def _setup_notify(hass_): - assert isinstance(apns.load_yaml_config_file, Mock), "Found unmocked load_yaml" - - with assert_setup_component(1) as handle_config: - assert await async_setup_component(hass_, notify.DOMAIN, CONFIG) - assert handle_config[notify.DOMAIN] - - -@patch("os.path.isfile", return_value=True) -@patch("os.access", return_value=True) -async def test_apns_setup_full(mock_access, mock_isfile, hass): - """Test setup with all data.""" - config = { - "notify": { - "platform": "apns", - "name": "test_app", - "sandbox": "True", - "topic": "testapp.appname", - "cert_file": "test_app.pem", - } - } - - with assert_setup_component(1) as handle_config: - assert await async_setup_component(hass, notify.DOMAIN, config) - assert handle_config[notify.DOMAIN] - - -async def test_apns_setup_missing_name(hass): - """Test setup with missing name.""" - config = { - "notify": { - "platform": "apns", - "topic": "testapp.appname", - "cert_file": "test_app.pem", - } - } - with assert_setup_component(0) as handle_config: - assert await async_setup_component(hass, notify.DOMAIN, config) - assert not handle_config[notify.DOMAIN] - - -async def test_apns_setup_missing_certificate(hass): - """Test setup with missing certificate.""" - config = { - "notify": { - "platform": "apns", - "name": "test_app", - "topic": "testapp.appname", - } - } - with assert_setup_component(0) as handle_config: - assert await async_setup_component(hass, notify.DOMAIN, config) - assert not handle_config[notify.DOMAIN] - - -async def test_apns_setup_missing_topic(hass): - """Test setup with missing topic.""" - config = { - "notify": { - "platform": "apns", - "name": "test_app", - "cert_file": "test_app.pem", - } - } - with assert_setup_component(0) as handle_config: - assert await async_setup_component(hass, notify.DOMAIN, config) - assert not handle_config[notify.DOMAIN] - - -@patch("homeassistant.components.apns.notify._write_device") -async def test_register_new_device(mock_write, hass): - """Test registering a new device with a name.""" - yaml_file = {5678: {"name": "test device 2"}} - - written_devices = [] - - def fake_write(_out, device): - """Fake write_device.""" - written_devices.append(device) - - mock_write.side_effect = fake_write - - with patch( - "homeassistant.components.apns.notify.load_yaml_config_file", - Mock(return_value=yaml_file), - ): - await _setup_notify(hass) - - assert await hass.services.async_call( - apns.DOMAIN, - "apns_test_app", - {"push_id": "1234", "name": "test device"}, - blocking=True, - ) - - assert len(written_devices) == 1 - assert written_devices[0].name == "test device" - - -@patch("homeassistant.components.apns.notify._write_device") -async def test_register_device_without_name(mock_write, hass): - """Test registering a without a name.""" - yaml_file = { - 1234: {"name": "test device 1", "tracking_device_id": "tracking123"}, - 5678: {"name": "test device 2", "tracking_device_id": "tracking456"}, - } - - written_devices = [] - - def fake_write(_out, device): - """Fake write_device.""" - written_devices.append(device) - - mock_write.side_effect = fake_write - - with patch( - "homeassistant.components.apns.notify.load_yaml_config_file", - Mock(return_value=yaml_file), - ): - await _setup_notify(hass) - - assert await hass.services.async_call( - apns.DOMAIN, "apns_test_app", {"push_id": "1234"}, blocking=True - ) - - devices = {dev.push_id: dev for dev in written_devices} - - test_device = devices.get("1234") - - assert test_device is not None - assert test_device.name is None - - -@patch("homeassistant.components.apns.notify._write_device") -async def test_update_existing_device(mock_write, hass): - """Test updating an existing device.""" - yaml_file = {1234: {"name": "test device 1"}, 5678: {"name": "test device 2"}} - - written_devices = [] - - def fake_write(_out, device): - """Fake write_device.""" - written_devices.append(device) - - mock_write.side_effect = fake_write - - with patch( - "homeassistant.components.apns.notify.load_yaml_config_file", - Mock(return_value=yaml_file), - ): - await _setup_notify(hass) - - assert await hass.services.async_call( - apns.DOMAIN, - "apns_test_app", - {"push_id": "1234", "name": "updated device 1"}, - blocking=True, - ) - - devices = {dev.push_id: dev for dev in written_devices} - - test_device_1 = devices.get("1234") - test_device_2 = devices.get("5678") - - assert test_device_1 is not None - assert test_device_2 is not None - - assert test_device_1.name == "updated device 1" - - -@patch("homeassistant.components.apns.notify._write_device") -async def test_update_existing_device_with_tracking_id(mock_write, hass): - """Test updating an existing device that has a tracking id.""" - yaml_file = { - 1234: {"name": "test device 1", "tracking_device_id": "tracking123"}, - 5678: {"name": "test device 2", "tracking_device_id": "tracking456"}, - } - - written_devices = [] - - def fake_write(_out, device): - """Fake write_device.""" - written_devices.append(device) - - mock_write.side_effect = fake_write - - with patch( - "homeassistant.components.apns.notify.load_yaml_config_file", - Mock(return_value=yaml_file), - ): - await _setup_notify(hass) - - assert await hass.services.async_call( - apns.DOMAIN, - "apns_test_app", - {"push_id": "1234", "name": "updated device 1"}, - blocking=True, - ) - - devices = {dev.push_id: dev for dev in written_devices} - - test_device_1 = devices.get("1234") - test_device_2 = devices.get("5678") - - assert test_device_1 is not None - assert test_device_2 is not None - - assert test_device_1.tracking_device_id == "tracking123" - assert test_device_2.tracking_device_id == "tracking456" - - -@patch("homeassistant.components.apns.notify.APNsClient") -async def test_send(mock_client, hass): - """Test updating an existing device.""" - send = mock_client.return_value.send_notification - - yaml_file = {1234: {"name": "test device 1"}} - - with patch( - "homeassistant.components.apns.notify.load_yaml_config_file", - Mock(return_value=yaml_file), - ): - await _setup_notify(hass) - - assert await hass.services.async_call( - "notify", - "test_app", - { - "message": "Hello", - "data": {"badge": 1, "sound": "test.mp3", "category": "testing"}, - }, - blocking=True, - ) - - assert send.called - assert len(send.mock_calls) == 1 - - target = send.mock_calls[0][1][0] - payload = send.mock_calls[0][1][1] - - assert target == "1234" - assert payload.alert == "Hello" - assert payload.badge == 1 - assert payload.sound == "test.mp3" - assert payload.category == "testing" - - -@patch("homeassistant.components.apns.notify.APNsClient") -async def test_send_when_disabled(mock_client, hass): - """Test updating an existing device.""" - send = mock_client.return_value.send_notification - - yaml_file = {1234: {"name": "test device 1", "disabled": True}} - - with patch( - "homeassistant.components.apns.notify.load_yaml_config_file", - Mock(return_value=yaml_file), - ): - await _setup_notify(hass) - - assert await hass.services.async_call( - "notify", - "test_app", - { - "message": "Hello", - "data": {"badge": 1, "sound": "test.mp3", "category": "testing"}, - }, - blocking=True, - ) - - assert not send.called - - -@patch("homeassistant.components.apns.notify.APNsClient") -async def test_send_with_state(mock_client, hass): - """Test updating an existing device.""" - send = mock_client.return_value.send_notification - - yaml_file = { - 1234: {"name": "test device 1", "tracking_device_id": "tracking123"}, - 5678: {"name": "test device 2", "tracking_device_id": "tracking456"}, - } - - with patch( - "homeassistant.components.apns.notify.load_yaml_config_file", - Mock(return_value=yaml_file), - ), patch("os.path.isfile", Mock(return_value=True)): - notify_service = await hass.async_add_executor_job( - apns.ApnsNotificationService, - hass, - "test_app", - "testapp.appname", - False, - "test_app.pem", - ) - - notify_service.device_state_changed_listener( - "device_tracker.tracking456", - State("device_tracker.tracking456", None), - State("device_tracker.tracking456", "home"), - ) - - notify_service.send_message(message="Hello", target="home") - - assert send.called - assert len(send.mock_calls) == 1 - - target = send.mock_calls[0][1][0] - payload = send.mock_calls[0][1][1] - - assert target == "5678" - assert payload.alert == "Hello" - - -@patch("homeassistant.components.apns.notify.APNsClient") -@patch("homeassistant.components.apns.notify._write_device") -async def test_disable_when_unregistered(mock_write, mock_client, hass): - """Test disabling a device when it is unregistered.""" - send = mock_client.return_value.send_notification - send.side_effect = Unregistered() - - yaml_file = { - 1234: {"name": "test device 1", "tracking_device_id": "tracking123"}, - 5678: {"name": "test device 2", "tracking_device_id": "tracking456"}, - } - - written_devices = [] - - def fake_write(_out, device): - """Fake write_device.""" - written_devices.append(device) - - mock_write.side_effect = fake_write - - with patch( - "homeassistant.components.apns.notify.load_yaml_config_file", - Mock(return_value=yaml_file), - ): - await _setup_notify(hass) - - assert await hass.services.async_call( - "notify", "test_app", {"message": "Hello"}, blocking=True - ) - - devices = {dev.push_id: dev for dev in written_devices} - - test_device_1 = devices.get("1234") - assert test_device_1 is not None - assert test_device_1.disabled is True - - -async def test_write_device(): - """Test writing device.""" - out = io.StringIO() - device = apns.ApnsDevice("123", "name", "track_id", True) - - apns._write_device(out, device) - data = yaml.safe_load(out.getvalue()) - assert data == { - 123: {"name": "name", "tracking_device_id": "track_id", "disabled": True} - } diff --git a/tests/components/icloud/test_config_flow.py b/tests/components/icloud/disabled_test_config_flow.py similarity index 98% rename from tests/components/icloud/test_config_flow.py rename to tests/components/icloud/disabled_test_config_flow.py index 59c5ebf24a9414..1f7e411003ad9f 100644 --- a/tests/components/icloud/test_config_flow.py +++ b/tests/components/icloud/disabled_test_config_flow.py @@ -1,4 +1,11 @@ -"""Tests for the iCloud config flow.""" +"""Tests for the iCloud config flow. + +This integration is temporary disabled, as the library is incompatible +with the Python versions we currently support. + +This file has been renamed (instead of skipped), simply because its easier +to prevent library imports from happening that way. +""" from unittest.mock import MagicMock, Mock, patch from pyicloud.exceptions import PyiCloudFailedLoginException diff --git a/tests/components/zwave/test_binary_sensor.py b/tests/components/zwave/test_binary_sensor.py index 731e413caf819a..265ec6f2d1ecee 100644 --- a/tests/components/zwave/test_binary_sensor.py +++ b/tests/components/zwave/test_binary_sensor.py @@ -2,10 +2,15 @@ import datetime from unittest.mock import patch +import pytest + from homeassistant.components.zwave import binary_sensor, const from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed +# Integration is disabled +pytest.skip("Integration has been disabled in the manifest", allow_module_level=True) + def test_get_device_detects_none(mock_openzwave): """Test device is not returned.""" diff --git a/tests/components/zwave/test_climate.py b/tests/components/zwave/test_climate.py index 1afe961709745d..a9ad182c4b16dd 100644 --- a/tests/components/zwave/test_climate.py +++ b/tests/components/zwave/test_climate.py @@ -31,6 +31,9 @@ from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed +# Integration is disabled +pytest.skip("Integration has been disabled in the manifest", allow_module_level=True) + @pytest.fixture def device(hass, mock_openzwave): diff --git a/tests/components/zwave/test_cover.py b/tests/components/zwave/test_cover.py index e8b784feefe730..e7283de25b4698 100644 --- a/tests/components/zwave/test_cover.py +++ b/tests/components/zwave/test_cover.py @@ -1,6 +1,8 @@ """Test Z-Wave cover devices.""" from unittest.mock import MagicMock +import pytest + from homeassistant.components.cover import SUPPORT_CLOSE, SUPPORT_OPEN from homeassistant.components.zwave import ( CONF_INVERT_OPENCLOSE_BUTTONS, @@ -11,6 +13,9 @@ from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed +# Integration is disabled +pytest.skip("Integration has been disabled in the manifest", allow_module_level=True) + def test_get_device_detects_none(hass, mock_openzwave): """Test device returns none.""" diff --git a/tests/components/zwave/test_fan.py b/tests/components/zwave/test_fan.py index 18188cefcd6304..d71ba0713d2d8a 100644 --- a/tests/components/zwave/test_fan.py +++ b/tests/components/zwave/test_fan.py @@ -1,4 +1,6 @@ """Test Z-Wave fans.""" +import pytest + from homeassistant.components.fan import ( SPEED_HIGH, SPEED_LOW, @@ -10,6 +12,9 @@ from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed +# Integration is disabled +pytest.skip("Integration has been disabled in the manifest", allow_module_level=True) + def test_get_device_detects_fan(mock_openzwave): """Test get_device returns a zwave fan.""" diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index b0114d087ad313..745d6d8ce57341 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -22,6 +22,9 @@ from tests.common import async_fire_time_changed, mock_registry from tests.mock.zwave import MockEntityValues, MockNetwork, MockNode, MockValue +# Integration is disabled +pytest.skip("Integration has been disabled in the manifest", allow_module_level=True) + @pytest.fixture(autouse=True) def mock_storage(hass_storage): diff --git a/tests/components/zwave/test_light.py b/tests/components/zwave/test_light.py index 74c541f4d5a0f3..87bfb1ec726127 100644 --- a/tests/components/zwave/test_light.py +++ b/tests/components/zwave/test_light.py @@ -1,6 +1,8 @@ """Test Z-Wave lights.""" from unittest.mock import MagicMock, patch +import pytest + from homeassistant.components import zwave from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -18,6 +20,9 @@ from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed +# Integration is disabled +pytest.skip("Integration has been disabled in the manifest", allow_module_level=True) + class MockLightValues(MockEntityValues): """Mock Z-Wave light values.""" diff --git a/tests/components/zwave/test_lock.py b/tests/components/zwave/test_lock.py index 04d46620013a21..575df9491adcfb 100644 --- a/tests/components/zwave/test_lock.py +++ b/tests/components/zwave/test_lock.py @@ -1,11 +1,16 @@ """Test Z-Wave locks.""" from unittest.mock import MagicMock, patch +import pytest + from homeassistant import config_entries from homeassistant.components.zwave import const, lock from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed +# Integration is disabled +pytest.skip("Integration has been disabled in the manifest", allow_module_level=True) + def test_get_device_detects_lock(mock_openzwave): """Test get_device returns a Z-Wave lock.""" diff --git a/tests/components/zwave/test_node_entity.py b/tests/components/zwave/test_node_entity.py index c47201fb1682a4..56ae0d61d41fc7 100644 --- a/tests/components/zwave/test_node_entity.py +++ b/tests/components/zwave/test_node_entity.py @@ -1,11 +1,16 @@ """Test Z-Wave node entity.""" from unittest.mock import MagicMock, patch +import pytest + from homeassistant.components.zwave import const, node_entity from homeassistant.const import ATTR_ENTITY_ID import tests.mock.zwave as mock_zwave +# Integration is disabled +pytest.skip("Integration has been disabled in the manifest", allow_module_level=True) + async def test_maybe_schedule_update(hass, mock_openzwave): """Test maybe schedule update.""" diff --git a/tests/components/zwave/test_sensor.py b/tests/components/zwave/test_sensor.py index 83ebcaa3a4a4d4..21944fe8f7ef2c 100644 --- a/tests/components/zwave/test_sensor.py +++ b/tests/components/zwave/test_sensor.py @@ -1,10 +1,15 @@ """Test Z-Wave sensor.""" +import pytest + from homeassistant.components.sensor import SensorDeviceClass from homeassistant.components.zwave import const, sensor import homeassistant.const from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed +# Integration is disabled +pytest.skip("Integration has been disabled in the manifest", allow_module_level=True) + def test_get_device_detects_none(mock_openzwave): """Test get_device returns None.""" diff --git a/tests/components/zwave/test_switch.py b/tests/components/zwave/test_switch.py index 4293a4a23fd04a..4c3efbe61fd510 100644 --- a/tests/components/zwave/test_switch.py +++ b/tests/components/zwave/test_switch.py @@ -1,10 +1,15 @@ """Test Z-Wave switches.""" from unittest.mock import patch +import pytest + from homeassistant.components.zwave import switch from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed +# Integration is disabled +pytest.skip("Integration has been disabled in the manifest", allow_module_level=True) + def test_get_device_detects_switch(mock_openzwave): """Test get_device returns a Z-Wave switch.""" diff --git a/tests/components/zwave/test_websocket_api.py b/tests/components/zwave/test_websocket_api.py index 2ad94d29b0e79a..2ffe5d617153d1 100644 --- a/tests/components/zwave/test_websocket_api.py +++ b/tests/components/zwave/test_websocket_api.py @@ -1,6 +1,8 @@ """Test Z-Wave Websocket API.""" from unittest.mock import call, patch +import pytest + from homeassistant import config_entries from homeassistant.bootstrap import async_setup_component from homeassistant.components.zwave.const import ( @@ -14,6 +16,10 @@ NETWORK_KEY = "0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST" +# Integration is disabled +pytest.skip("Integration has been disabled in the manifest", allow_module_level=True) + + async def test_zwave_ws_api(hass, mock_openzwave, hass_ws_client): """Test Z-Wave websocket API.""" diff --git a/tests/components/zwave/test_workaround.py b/tests/components/zwave/test_workaround.py index ec708d38e43dbc..8f84fd6b949197 100644 --- a/tests/components/zwave/test_workaround.py +++ b/tests/components/zwave/test_workaround.py @@ -1,8 +1,13 @@ """Test Z-Wave workarounds.""" +import pytest + from homeassistant.components.zwave import const, workaround from tests.mock.zwave import MockNode, MockValue +# Integration is disabled +pytest.skip("Integration has been disabled in the manifest", allow_module_level=True) + def test_get_device_no_component_mapping(): """Test that None is returned.""" diff --git a/tests/components/zwave_js/test_migrate.py b/tests/components/zwave_js/test_migrate.py index ff3712b607e7ac..3479638b387149 100644 --- a/tests/components/zwave_js/test_migrate.py +++ b/tests/components/zwave_js/test_migrate.py @@ -317,6 +317,7 @@ async def test_migrate_zwave( assert not await hass.config_entries.async_setup(zwave_config_entry.entry_id) +@pytest.mark.skip(reason="The old zwave integration has been disabled.") async def test_migrate_zwave_dry_run( hass, zwave_integration, diff --git a/tests/mock/zwave.py b/tests/mock/zwave.py index 5565b43a78e13b..89c70eaf83cf9d 100644 --- a/tests/mock/zwave.py +++ b/tests/mock/zwave.py @@ -1,7 +1,9 @@ """Mock helpers for Z-Wave component.""" from unittest.mock import MagicMock -from pydispatch import dispatcher +# Integration & integration tests are disabled +# from pydispatch import dispatcher +dispatcher = MagicMock() def value_changed(value): From bcc5ce142f87bcf1a24ef0cdc7e5d725052f6db8 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 17 Feb 2022 16:19:51 +0100 Subject: [PATCH 0744/1098] Fix trigger of full CI on dependency bumps (#66738) --- .core_files.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.core_files.yaml b/.core_files.yaml index b6c20bf7542f12..2b9f1563d99b25 100644 --- a/.core_files.yaml +++ b/.core_files.yaml @@ -124,7 +124,7 @@ other: &other - .github/workflows/* - homeassistant/scripts/** -requirements: +requirements: &requirements - .github/workflows/* - homeassistant/package_constraints.txt - requirements*.txt @@ -135,4 +135,5 @@ any: - *components - *core - *other + - *requirements - *tests From f8d38a10253eab163c6527f395e6a82ff752a9f5 Mon Sep 17 00:00:00 2001 From: Tom Date: Thu, 17 Feb 2022 17:03:18 +0100 Subject: [PATCH 0745/1098] Plugwise: Update fixtures (#66749) --- .../all_data.json | 328 +++++++++++++++--- .../fixtures/anna_heatpump/all_data.json | 69 +++- .../fixtures/p1v3_full_option/all_data.json | 6 +- .../fixtures/stretch_v31/all_data.json | 121 +++---- tests/components/plugwise/test_climate.py | 10 +- tests/components/plugwise/test_diagnostics.py | 101 +++++- 6 files changed, 486 insertions(+), 149 deletions(-) diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json index 7fc843e4aaedb0..3dbe9f5d8a3bf7 100644 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json +++ b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json @@ -1,7 +1,7 @@ [ { "active_device": true, - "cooling_present": null, + "cooling_present": false, "gateway_id": "fe799307f1624099878210aa0b9f1475", "heater_id": "90986d591dcd426cae3ec3e8111ff730", "single_master_thermostat": false, @@ -16,18 +16,44 @@ "df4a4a8169904cdb9c03d61a21f42140": { "class": "zone_thermostat", "fw": "2016-10-27T02:00:00+02:00", + "hw": "255", "location": "12493538af164a409c6a1c79e38afe1c", + "mac_address": null, "model": "Lisa", "name": "Zone Lisa Bios", "vendor": "Plugwise", - "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "lower_bound": 0, + "upper_bound": 99.9, + "resolution": 0.01, + "preset_modes": [ + "home", + "asleep", + "away", + "vacation", + "no_frost" + ], "active_preset": "away", "presets": { - "home": [20.0, 22.0], - "asleep": [17.0, 24.0], - "away": [15.0, 25.0], - "vacation": [15.0, 28.0], - "no_frost": [10.0, 30.0] + "home": [ + 20.0, + 22.0 + ], + "asleep": [ + 17.0, + 24.0 + ], + "away": [ + 15.0, + 25.0 + ], + "vacation": [ + 15.0, + 28.0 + ], + "no_frost": [ + 10.0, + 30.0 + ] }, "available_schedules": [ "CV Roan", @@ -37,18 +63,27 @@ "CV Jessie" ], "selected_schedule": "None", - "schedule_temperature": 15.0, "last_used": "Badkamer Schema", - "mode": "off", - "sensors": { "temperature": 16.5, "setpoint": 13.0, "battery": 67 } + "schedule_temperature": 15.0, + "mode": "heat", + "sensors": { + "temperature": 16.5, + "setpoint": 13, + "battery": 67 + } }, "b310b72a0e354bfab43089919b9a88bf": { "class": "thermo_sensor", "fw": "2019-03-27T01:00:00+01:00", + "hw": "1", "location": "c50f167537524366a5af7aa3942feb1e", + "mac_address": null, "model": "Tom/Floor", "name": "Floor kraan", "vendor": "Plugwise", + "lower_bound": 0, + "upper_bound": 100.0, + "resolution": 0.01, "sensors": { "temperature": 26.0, "setpoint": 21.5, @@ -59,13 +94,18 @@ "a2c3583e0a6349358998b760cea82d2a": { "class": "thermo_sensor", "fw": "2019-03-27T01:00:00+01:00", + "hw": "1", "location": "12493538af164a409c6a1c79e38afe1c", + "mac_address": null, "model": "Tom/Floor", "name": "Bios Cv Thermostatic Radiator ", "vendor": "Plugwise", + "lower_bound": 0, + "upper_bound": 100.0, + "resolution": 0.01, "sensors": { "temperature": 17.2, - "setpoint": 13.0, + "setpoint": 13, "battery": 62, "temperature_difference": -0.2, "valve_position": 0.0 @@ -74,18 +114,44 @@ "b59bcebaf94b499ea7d46e4a66fb62d8": { "class": "zone_thermostat", "fw": "2016-08-02T02:00:00+02:00", + "hw": "255", "location": "c50f167537524366a5af7aa3942feb1e", + "mac_address": null, "model": "Lisa", "name": "Zone Lisa WK", "vendor": "Plugwise", - "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "lower_bound": 0, + "upper_bound": 99.9, + "resolution": 0.01, + "preset_modes": [ + "home", + "asleep", + "away", + "vacation", + "no_frost" + ], "active_preset": "home", "presets": { - "home": [20.0, 22.0], - "asleep": [17.0, 24.0], - "away": [15.0, 25.0], - "vacation": [15.0, 28.0], - "no_frost": [10.0, 30.0] + "home": [ + 20.0, + 22.0 + ], + "asleep": [ + 17.0, + 24.0 + ], + "away": [ + 15.0, + 25.0 + ], + "vacation": [ + 15.0, + 28.0 + ], + "no_frost": [ + 10.0, + 30.0 + ] }, "available_schedules": [ "CV Roan", @@ -95,31 +161,47 @@ "CV Jessie" ], "selected_schedule": "GF7 Woonkamer", - "schedule_temperature": 20.0, "last_used": "GF7 Woonkamer", + "schedule_temperature": 20.0, "mode": "auto", - "sensors": { "temperature": 20.9, "setpoint": 21.5, "battery": 34 } + "sensors": { + "temperature": 20.9, + "setpoint": 21.5, + "battery": 34 + } }, "fe799307f1624099878210aa0b9f1475": { "class": "gateway", "fw": "3.0.15", + "hw": "AME Smile 2.0 board", "location": "1f9dcf83fd4e4b66b72ff787957bfe5d", + "mac_address": "012345670001", "model": "Adam", "name": "Adam", "vendor": "Plugwise B.V.", - "binary_sensors": { "plugwise_notification": true }, - "sensors": { "outdoor_temperature": 7.81 } + "zigbee_mac_address": "012345670101", + "binary_sensors": { + "plugwise_notification": true + }, + "sensors": { + "outdoor_temperature": 7.81 + } }, "d3da73bde12a47d5a6b8f9dad971f2ec": { "class": "thermo_sensor", "fw": "2019-03-27T01:00:00+01:00", + "hw": "1", "location": "82fa13f017d240daa0d0ea1775420f24", + "mac_address": null, "model": "Tom/Floor", "name": "Thermostatic Radiator Jessie", "vendor": "Plugwise", + "lower_bound": 0, + "upper_bound": 100.0, + "resolution": 0.01, "sensors": { "temperature": 17.1, - "setpoint": 15.0, + "setpoint": 15, "battery": 62, "temperature_difference": 0.1, "valve_position": 0.0 @@ -128,124 +210,189 @@ "21f2b542c49845e6bb416884c55778d6": { "class": "game_console", "fw": "2019-06-21T02:00:00+02:00", + "hw": null, "location": "cd143c07248f491493cea0533bc3d669", + "mac_address": null, "model": "Plug", "name": "Playstation Smart Plug", "vendor": "Plugwise", + "zigbee_mac_address": "012345670A12", "sensors": { "electricity_consumed": 82.6, "electricity_consumed_interval": 8.6, "electricity_produced": 0.0, "electricity_produced_interval": 0.0 }, - "switches": { "relay": true, "lock": false } + "switches": { + "relay": true, + "lock": false + } }, "78d1126fc4c743db81b61c20e88342a7": { "class": "central_heating_pump", "fw": "2019-06-21T02:00:00+02:00", + "hw": null, "location": "c50f167537524366a5af7aa3942feb1e", + "mac_address": null, "model": "Plug", "name": "CV Pomp", "vendor": "Plugwise", + "zigbee_mac_address": "012345670A05", "sensors": { "electricity_consumed": 35.6, "electricity_consumed_interval": 7.37, "electricity_produced": 0.0, "electricity_produced_interval": 0.0 }, - "switches": { "relay": true } + "switches": { + "relay": true + } }, "90986d591dcd426cae3ec3e8111ff730": { "class": "heater_central", "fw": null, + "hw": null, "location": "1f9dcf83fd4e4b66b72ff787957bfe5d", + "mac_address": null, "model": "Unknown", "name": "OnOff", "vendor": null, + "lower_bound": 10, + "upper_bound": 90, + "resolution": 1, "cooling_active": false, "heating_state": true, "sensors": { "water_temperature": 70.0, "intended_boiler_temperature": 70.0, - "modulation_level": 1, - "device_state": "heating" + "modulation_level": 1 } }, "cd0ddb54ef694e11ac18ed1cbce5dbbd": { "class": "vcr", "fw": "2019-06-21T02:00:00+02:00", + "hw": null, "location": "cd143c07248f491493cea0533bc3d669", + "mac_address": null, "model": "Plug", "name": "NAS", "vendor": "Plugwise", + "zigbee_mac_address": "012345670A14", "sensors": { "electricity_consumed": 16.5, "electricity_consumed_interval": 0.5, "electricity_produced": 0.0, "electricity_produced_interval": 0.0 }, - "switches": { "relay": true, "lock": true } + "switches": { + "relay": true, + "lock": true + } }, "4a810418d5394b3f82727340b91ba740": { "class": "router", "fw": "2019-06-21T02:00:00+02:00", + "hw": null, "location": "cd143c07248f491493cea0533bc3d669", + "mac_address": null, "model": "Plug", "name": "USG Smart Plug", "vendor": "Plugwise", + "zigbee_mac_address": "012345670A16", "sensors": { "electricity_consumed": 8.5, "electricity_consumed_interval": 0.0, "electricity_produced": 0.0, "electricity_produced_interval": 0.0 }, - "switches": { "relay": true, "lock": true } + "switches": { + "relay": true, + "lock": true + } }, "02cf28bfec924855854c544690a609ef": { "class": "vcr", "fw": "2019-06-21T02:00:00+02:00", + "hw": null, "location": "cd143c07248f491493cea0533bc3d669", + "mac_address": null, "model": "Plug", "name": "NVR", "vendor": "Plugwise", + "zigbee_mac_address": "012345670A15", "sensors": { "electricity_consumed": 34.0, "electricity_consumed_interval": 9.15, "electricity_produced": 0.0, "electricity_produced_interval": 0.0 }, - "switches": { "relay": true, "lock": true } + "switches": { + "relay": true, + "lock": true + } }, "a28f588dc4a049a483fd03a30361ad3a": { "class": "settop", "fw": "2019-06-21T02:00:00+02:00", + "hw": null, "location": "cd143c07248f491493cea0533bc3d669", + "mac_address": null, "model": "Plug", "name": "Fibaro HC2", "vendor": "Plugwise", + "zigbee_mac_address": "012345670A13", "sensors": { "electricity_consumed": 12.5, "electricity_consumed_interval": 3.8, "electricity_produced": 0.0, "electricity_produced_interval": 0.0 }, - "switches": { "relay": true, "lock": true } + "switches": { + "relay": true, + "lock": true + } }, "6a3bf693d05e48e0b460c815a4fdd09d": { "class": "zone_thermostat", "fw": "2016-10-27T02:00:00+02:00", + "hw": "255", "location": "82fa13f017d240daa0d0ea1775420f24", + "mac_address": null, "model": "Lisa", "name": "Zone Thermostat Jessie", "vendor": "Plugwise", - "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "lower_bound": 0, + "upper_bound": 99.9, + "resolution": 0.01, + "preset_modes": [ + "home", + "asleep", + "away", + "vacation", + "no_frost" + ], "active_preset": "asleep", "presets": { - "home": [20.0, 22.0], - "asleep": [17.0, 24.0], - "away": [15.0, 25.0], - "vacation": [15.0, 28.0], - "no_frost": [10.0, 30.0] + "home": [ + 20.0, + 22.0 + ], + "asleep": [ + 17.0, + 24.0 + ], + "away": [ + 15.0, + 25.0 + ], + "vacation": [ + 15.0, + 28.0 + ], + "no_frost": [ + 10.0, + 30.0 + ] }, "available_schedules": [ "CV Roan", @@ -255,21 +402,30 @@ "CV Jessie" ], "selected_schedule": "CV Jessie", - "schedule_temperature": 15.0, "last_used": "CV Jessie", + "schedule_temperature": 15.0, "mode": "auto", - "sensors": { "temperature": 17.2, "setpoint": 15.0, "battery": 37 } + "sensors": { + "temperature": 17.2, + "setpoint": 15, + "battery": 37 + } }, "680423ff840043738f42cc7f1ff97a36": { "class": "thermo_sensor", "fw": "2019-03-27T01:00:00+01:00", + "hw": "1", "location": "08963fec7c53423ca5680aa4cb502c63", + "mac_address": null, "model": "Tom/Floor", "name": "Thermostatic Radiator Badkamer", "vendor": "Plugwise", + "lower_bound": 0, + "upper_bound": 100.0, + "resolution": 0.01, "sensors": { "temperature": 19.1, - "setpoint": 14.0, + "setpoint": 14, "battery": 51, "temperature_difference": -0.4, "valve_position": 0.0 @@ -278,18 +434,44 @@ "f1fee6043d3642a9b0a65297455f008e": { "class": "zone_thermostat", "fw": "2016-10-27T02:00:00+02:00", + "hw": "255", "location": "08963fec7c53423ca5680aa4cb502c63", + "mac_address": null, "model": "Lisa", "name": "Zone Thermostat Badkamer", "vendor": "Plugwise", - "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "lower_bound": 0, + "upper_bound": 99.9, + "resolution": 0.01, + "preset_modes": [ + "home", + "asleep", + "away", + "vacation", + "no_frost" + ], "active_preset": "away", "presets": { - "home": [20.0, 22.0], - "asleep": [17.0, 24.0], - "away": [15.0, 25.0], - "vacation": [15.0, 28.0], - "no_frost": [10.0, 30.0] + "home": [ + 20.0, + 22.0 + ], + "asleep": [ + 17.0, + 24.0 + ], + "away": [ + 15.0, + 25.0 + ], + "vacation": [ + 15.0, + 28.0 + ], + "no_frost": [ + 10.0, + 30.0 + ] }, "available_schedules": [ "CV Roan", @@ -299,41 +481,77 @@ "CV Jessie" ], "selected_schedule": "Badkamer Schema", - "schedule_temperature": 15.0, "last_used": "Badkamer Schema", + "schedule_temperature": 15.0, "mode": "auto", - "sensors": { "temperature": 18.9, "setpoint": 14.0, "battery": 92 } + "sensors": { + "temperature": 18.9, + "setpoint": 14, + "battery": 92 + } }, "675416a629f343c495449970e2ca37b5": { "class": "router", "fw": "2019-06-21T02:00:00+02:00", + "hw": null, "location": "cd143c07248f491493cea0533bc3d669", + "mac_address": null, "model": "Plug", "name": "Ziggo Modem", "vendor": "Plugwise", + "zigbee_mac_address": "012345670A01", "sensors": { "electricity_consumed": 12.2, "electricity_consumed_interval": 2.97, "electricity_produced": 0.0, "electricity_produced_interval": 0.0 }, - "switches": { "relay": true, "lock": true } + "switches": { + "relay": true, + "lock": true + } }, "e7693eb9582644e5b865dba8d4447cf1": { "class": "thermostatic_radiator_valve", "fw": "2019-03-27T01:00:00+01:00", + "hw": "1", "location": "446ac08dd04d4eff8ac57489757b7314", + "mac_address": null, "model": "Tom/Floor", "name": "CV Kraan Garage", "vendor": "Plugwise", - "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "lower_bound": 0, + "upper_bound": 100.0, + "resolution": 0.01, + "preset_modes": [ + "home", + "asleep", + "away", + "vacation", + "no_frost" + ], "active_preset": "no_frost", "presets": { - "home": [20.0, 22.0], - "asleep": [17.0, 24.0], - "away": [15.0, 25.0], - "vacation": [15.0, 28.0], - "no_frost": [10.0, 30.0] + "home": [ + 20.0, + 22.0 + ], + "asleep": [ + 17.0, + 24.0 + ], + "away": [ + 15.0, + 25.0 + ], + "vacation": [ + 15.0, + 28.0 + ], + "no_frost": [ + 10.0, + 30.0 + ] }, "available_schedules": [ "CV Roan", @@ -343,8 +561,8 @@ "CV Jessie" ], "selected_schedule": "None", - "schedule_temperature": 15.0, "last_used": "Badkamer Schema", + "schedule_temperature": 15.0, "mode": "heat", "sensors": { "temperature": 15.6, diff --git a/tests/components/plugwise/fixtures/anna_heatpump/all_data.json b/tests/components/plugwise/fixtures/anna_heatpump/all_data.json index ee0f60d92ceec7..49b02b87f504eb 100644 --- a/tests/components/plugwise/fixtures/anna_heatpump/all_data.json +++ b/tests/components/plugwise/fixtures/anna_heatpump/all_data.json @@ -12,10 +12,15 @@ "1cbf783bb11e4a7c8a6843dee3a86927": { "class": "heater_central", "fw": null, + "hw": null, "location": "a57efe5f145f498c9be62a9b63626fbf", + "mac_address": null, "model": "Generic heater", "name": "OpenTherm", "vendor": "Techneco", + "lower_bound": -10, + "upper_bound": 40, + "resolution": 1, "heating_state": true, "compressor_state": true, "cooling_state": false, @@ -31,48 +36,80 @@ "intended_boiler_temperature": 0.0, "modulation_level": 52, "return_temperature": 25.1, - "water_pressure": 1.57, - "device_state": "heating" + "water_pressure": 1.57 }, - "switches": { "dhw_cm_switch": false } + "switches": { + "dhw_cm_switch": false + } }, "015ae9ea3f964e668e490fa39da3870b": { "class": "gateway", "fw": "4.0.15", + "hw": "AME Smile 2.0 board", "location": "a57efe5f145f498c9be62a9b63626fbf", + "mac_address": "012345670001", "model": "Anna", "name": "Anna", "vendor": "Plugwise B.V.", - "binary_sensors": { "plugwise_notification": false }, - "sensors": { "outdoor_temperature": 20.2 } + "binary_sensors": { + "plugwise_notification": false + }, + "sensors": { + "outdoor_temperature": 20.2 + } }, "3cb70739631c4d17a86b8b12e8a5161b": { "class": "thermostat", "fw": "2018-02-08T11:15:53+01:00", + "hw": "6539-1301-5002", "location": "c784ee9fdab44e1395b8dee7d7a497d5", + "mac_address": null, "model": "Anna", "name": "Anna", "vendor": "Plugwise", - "lower_bound": 5, - "upper_bound": 31, + "lower_bound": 4, + "upper_bound": 30, "resolution": 0.1, - "preset_modes": ["no_frost", "home", "away", "asleep", "vacation"], + "preset_modes": [ + "no_frost", + "home", + "away", + "asleep", + "vacation" + ], "active_preset": "home", "presets": { - "no_frost": [10.0, 30.0], - "home": [21.0, 22.0], - "away": [20.0, 25.0], - "asleep": [20.5, 24.0], - "vacation": [17.0, 28.0] + "no_frost": [ + 10.0, + 30.0 + ], + "home": [ + 21.0, + 22.0 + ], + "away": [ + 20.0, + 25.0 + ], + "asleep": [ + 20.5, + 24.0 + ], + "vacation": [ + 17.0, + 28.0 + ] }, - "available_schedules": ["None"], + "available_schedules": [ + "None" + ], "selected_schedule": "None", - "schedule_temperature": null, "last_used": null, + "schedule_temperature": null, "mode": "heat", "sensors": { "temperature": 19.3, - "setpoint": 21.0, + "setpoint": 21, "illuminance": 86.0, "cooling_activation_outdoor_temperature": 21.0, "cooling_deactivation_threshold": 4 diff --git a/tests/components/plugwise/fixtures/p1v3_full_option/all_data.json b/tests/components/plugwise/fixtures/p1v3_full_option/all_data.json index 43c47c2b5e0ba5..a7ad6140fa7331 100644 --- a/tests/components/plugwise/fixtures/p1v3_full_option/all_data.json +++ b/tests/components/plugwise/fixtures/p1v3_full_option/all_data.json @@ -1,10 +1,10 @@ [ { "active_device": false, - "cooling_present": null, + "cooling_present": false, "gateway_id": "e950c7d5e1ee407a858e2a8b5016c8b3", "heater_id": null, - "single_master_thermostat": null, + "single_master_thermostat": false, "smile_name": "P1", "notifications": {} }, @@ -12,7 +12,9 @@ "e950c7d5e1ee407a858e2a8b5016c8b3": { "class": "gateway", "fw": "3.3.9", + "hw": "AME Smile 2.0 board", "location": "cd3e822288064775a7c4afcdd70bdda2", + "mac_address": "012345670001", "model": "P1", "name": "P1", "vendor": "Plugwise B.V.", diff --git a/tests/components/plugwise/fixtures/stretch_v31/all_data.json b/tests/components/plugwise/fixtures/stretch_v31/all_data.json index 3834eb516def9b..b168923b8b2360 100644 --- a/tests/components/plugwise/fixtures/stretch_v31/all_data.json +++ b/tests/components/plugwise/fixtures/stretch_v31/all_data.json @@ -1,10 +1,10 @@ [ { "active_device": false, - "cooling_present": null, + "cooling_present": false, "gateway_id": "0000aaaa0000aaaa0000aaaa0000aa00", "heater_id": null, - "single_master_thermostat": null, + "single_master_thermostat": false, "smile_name": "Stretch", "notifications": {} }, @@ -12,44 +12,40 @@ "0000aaaa0000aaaa0000aaaa0000aa00": { "class": "gateway", "fw": "3.1.11", + "hw": null, + "mac_address": "01:23:45:67:89:AB", "location": "0000aaaa0000aaaa0000aaaa0000aa00", "vendor": "Plugwise B.V.", "model": "Stretch", - "name": "Stretch" - }, - "5ca521ac179d468e91d772eeeb8a2117": { - "class": "zz_misc", - "fw": null, - "location": "0000aaaa0000aaaa0000aaaa0000aa00", - "model": null, - "name": "Oven (793F84)", - "vendor": null, - "sensors": { - "electricity_consumed": 0.0, - "electricity_consumed_interval": 0.0, - "electricity_produced": 0.0, - "electricity_produced_interval": 0.0 - }, - "switches": { "relay": true, "lock": false } + "name": "Stretch", + "zigbee_mac_address": "012345670101" }, "5871317346d045bc9f6b987ef25ee638": { "class": "water_heater_vessel", "fw": "2011-06-27T10:52:18+02:00", + "hw": "6539-0701-4028", "location": "0000aaaa0000aaaa0000aaaa0000aa00", + "mac_address": null, "model": "Circle type F", "name": "Boiler (1EB31)", "vendor": "Plugwise", + "zigbee_mac_address": "012345670A07", "sensors": { "electricity_consumed": 1.19, "electricity_consumed_interval": 0.0, "electricity_produced": 0.0 }, - "switches": { "relay": true, "lock": false } + "switches": { + "relay": true, + "lock": false + } }, "e1c884e7dede431dadee09506ec4f859": { "class": "refrigerator", "fw": "2011-06-27T10:47:37+02:00", + "hw": "6539-0700-7330", "location": "0000aaaa0000aaaa0000aaaa0000aa00", + "mac_address": null, "model": "Circle+ type F", "name": "Koelkast (92C4A)", "vendor": "Plugwise", @@ -58,79 +54,70 @@ "electricity_consumed_interval": 0.08, "electricity_produced": 0.0 }, - "switches": { "relay": true, "lock": false } + "switches": { + "relay": true, + "lock": false + } }, "aac7b735042c4832ac9ff33aae4f453b": { "class": "dishwasher", "fw": "2011-06-27T10:52:18+02:00", + "hw": "6539-0701-4022", "location": "0000aaaa0000aaaa0000aaaa0000aa00", + "mac_address": null, "model": "Circle type F", "name": "Vaatwasser (2a1ab)", "vendor": "Plugwise", + "zigbee_mac_address": "012345670A02", "sensors": { "electricity_consumed": 0.0, "electricity_consumed_interval": 0.71, "electricity_produced": 0.0 }, - "switches": { "relay": true, "lock": false } + "switches": { + "relay": true, + "lock": false + } }, "cfe95cf3de1948c0b8955125bf754614": { "class": "dryer", "fw": "2011-06-27T10:52:18+02:00", + "hw": "0000-0440-0107", "location": "0000aaaa0000aaaa0000aaaa0000aa00", + "mac_address": null, "model": "Circle type F", "name": "Droger (52559)", "vendor": "Plugwise", + "zigbee_mac_address": "012345670A04", "sensors": { "electricity_consumed": 0.0, "electricity_consumed_interval": 0.0, "electricity_produced": 0.0 }, - "switches": { "relay": true, "lock": false } - }, - "99f89d097be34fca88d8598c6dbc18ea": { - "class": "router", - "fw": null, - "location": "0000aaaa0000aaaa0000aaaa0000aa00", - "model": null, - "name": "Meterkast (787BFB)", - "vendor": null, - "sensors": { - "electricity_consumed": 27.6, - "electricity_consumed_interval": 28.2, - "electricity_produced": 0.0, - "electricity_produced_interval": 0.0 - }, - "switches": { "relay": true, "lock": true } + "switches": { + "relay": true, + "lock": false + } }, "059e4d03c7a34d278add5c7a4a781d19": { "class": "washingmachine", "fw": "2011-06-27T10:52:18+02:00", + "hw": "0000-0440-0107", "location": "0000aaaa0000aaaa0000aaaa0000aa00", + "mac_address": null, "model": "Circle type F", "name": "Wasmachine (52AC1)", "vendor": "Plugwise", + "zigbee_mac_address": "012345670A01", "sensors": { "electricity_consumed": 0.0, "electricity_consumed_interval": 0.0, "electricity_produced": 0.0 }, - "switches": { "relay": true, "lock": false } - }, - "e309b52ea5684cf1a22f30cf0cd15051": { - "class": "computer_desktop", - "fw": null, - "location": "0000aaaa0000aaaa0000aaaa0000aa00", - "model": null, - "name": "Computer (788618)", - "vendor": null, - "sensors": { - "electricity_consumed": 156, - "electricity_consumed_interval": 163, - "electricity_produced": 0.0, - "electricity_produced_interval": 0.0 - }, - "switches": { "relay": true, "lock": true } + "switches": { + "relay": true, + "lock": false + } }, "71e1944f2a944b26ad73323e399efef0": { "class": "switching", @@ -138,10 +125,16 @@ "location": null, "model": "Switchgroup", "name": "Test", - "members": ["5ca521ac179d468e91d772eeeb8a2117"], - "types": ["switch_group"], + "members": [ + "5ca521ac179d468e91d772eeeb8a2117" + ], + "types": [ + "switch_group" + ], "vendor": null, - "switches": { "relay": true } + "switches": { + "relay": true + } }, "d950b314e9d8499f968e6db8d82ef78c": { "class": "report", @@ -156,9 +149,13 @@ "cfe95cf3de1948c0b8955125bf754614", "e1c884e7dede431dadee09506ec4f859" ], - "types": ["switch_group"], + "types": [ + "switch_group" + ], "vendor": null, - "switches": { "relay": true } + "switches": { + "relay": true + } }, "d03738edfcc947f7b8f4573571d90d2d": { "class": "switching", @@ -170,9 +167,13 @@ "059e4d03c7a34d278add5c7a4a781d19", "cfe95cf3de1948c0b8955125bf754614" ], - "types": ["switch_group"], + "types": [ + "switch_group" + ], "vendor": null, - "switches": { "relay": true } + "switches": { + "relay": true + } } } ] diff --git a/tests/components/plugwise/test_climate.py b/tests/components/plugwise/test_climate.py index 1a8b7815be48d2..a52e4a955a68b6 100644 --- a/tests/components/plugwise/test_climate.py +++ b/tests/components/plugwise/test_climate.py @@ -38,6 +38,9 @@ async def test_adam_climate_entity_attributes( assert state.attributes["preset_mode"] == "home" assert state.attributes["supported_features"] == 17 assert state.attributes["temperature"] == 21.5 + assert state.attributes["min_temp"] == 0.0 + assert state.attributes["max_temp"] == 99.9 + assert state.attributes["target_temp_step"] == 0.1 state = hass.states.get("climate.zone_thermostat_jessie") assert state @@ -55,6 +58,9 @@ async def test_adam_climate_entity_attributes( assert state.attributes["current_temperature"] == 17.2 assert state.attributes["preset_mode"] == "asleep" assert state.attributes["temperature"] == 15.0 + assert state.attributes["min_temp"] == 0.0 + assert state.attributes["max_temp"] == 99.9 + assert state.attributes["target_temp_step"] == 0.1 async def test_adam_climate_adjust_negative_testing( @@ -166,8 +172,8 @@ async def test_anna_climate_entity_attributes( assert state.attributes["preset_mode"] == "home" assert state.attributes["supported_features"] == 17 assert state.attributes["temperature"] == 21.0 - assert state.attributes["min_temp"] == 5.0 - assert state.attributes["max_temp"] == 31.0 + assert state.attributes["min_temp"] == 4.0 + assert state.attributes["max_temp"] == 30.0 assert state.attributes["target_temp_step"] == 0.1 diff --git a/tests/components/plugwise/test_diagnostics.py b/tests/components/plugwise/test_diagnostics.py index 673e77f1630462..5fdef0112a1222 100644 --- a/tests/components/plugwise/test_diagnostics.py +++ b/tests/components/plugwise/test_diagnostics.py @@ -21,7 +21,7 @@ async def test_diagnostics( ) == { "gateway": { "active_device": True, - "cooling_present": None, + "cooling_present": False, "gateway_id": "fe799307f1624099878210aa0b9f1475", "heater_id": "90986d591dcd426cae3ec3e8111ff730", "single_master_thermostat": False, @@ -36,10 +36,15 @@ async def test_diagnostics( "df4a4a8169904cdb9c03d61a21f42140": { "class": "zone_thermostat", "fw": "2016-10-27T02:00:00+02:00", + "hw": "255", "location": "12493538af164a409c6a1c79e38afe1c", + "mac_address": None, "model": "Lisa", "name": "Zone Lisa Bios", "vendor": "Plugwise", + "lower_bound": 0, + "upper_bound": 99.9, + "resolution": 0.01, "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], "active_preset": "away", "presets": { @@ -57,18 +62,23 @@ async def test_diagnostics( "CV Jessie", ], "selected_schedule": "None", - "schedule_temperature": 15.0, "last_used": "Badkamer Schema", - "mode": "off", - "sensors": {"temperature": 16.5, "setpoint": 13.0, "battery": 67}, + "schedule_temperature": 15.0, + "mode": "heat", + "sensors": {"temperature": 16.5, "setpoint": 13, "battery": 67}, }, "b310b72a0e354bfab43089919b9a88bf": { "class": "thermo_sensor", "fw": "2019-03-27T01:00:00+01:00", + "hw": "1", "location": "c50f167537524366a5af7aa3942feb1e", + "mac_address": None, "model": "Tom/Floor", "name": "Floor kraan", "vendor": "Plugwise", + "lower_bound": 0, + "upper_bound": 100.0, + "resolution": 0.01, "sensors": { "temperature": 26.0, "setpoint": 21.5, @@ -79,13 +89,18 @@ async def test_diagnostics( "a2c3583e0a6349358998b760cea82d2a": { "class": "thermo_sensor", "fw": "2019-03-27T01:00:00+01:00", + "hw": "1", "location": "12493538af164a409c6a1c79e38afe1c", + "mac_address": None, "model": "Tom/Floor", "name": "Bios Cv Thermostatic Radiator ", "vendor": "Plugwise", + "lower_bound": 0, + "upper_bound": 100.0, + "resolution": 0.01, "sensors": { "temperature": 17.2, - "setpoint": 13.0, + "setpoint": 13, "battery": 62, "temperature_difference": -0.2, "valve_position": 0.0, @@ -94,10 +109,15 @@ async def test_diagnostics( "b59bcebaf94b499ea7d46e4a66fb62d8": { "class": "zone_thermostat", "fw": "2016-08-02T02:00:00+02:00", + "hw": "255", "location": "c50f167537524366a5af7aa3942feb1e", + "mac_address": None, "model": "Lisa", "name": "Zone Lisa WK", "vendor": "Plugwise", + "lower_bound": 0, + "upper_bound": 99.9, + "resolution": 0.01, "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], "active_preset": "home", "presets": { @@ -115,31 +135,39 @@ async def test_diagnostics( "CV Jessie", ], "selected_schedule": "GF7 Woonkamer", - "schedule_temperature": 20.0, "last_used": "GF7 Woonkamer", + "schedule_temperature": 20.0, "mode": "auto", "sensors": {"temperature": 20.9, "setpoint": 21.5, "battery": 34}, }, "fe799307f1624099878210aa0b9f1475": { "class": "gateway", "fw": "3.0.15", + "hw": "AME Smile 2.0 board", "location": "1f9dcf83fd4e4b66b72ff787957bfe5d", + "mac_address": "012345670001", "model": "Adam", "name": "Adam", "vendor": "Plugwise B.V.", + "zigbee_mac_address": "012345670101", "binary_sensors": {"plugwise_notification": True}, "sensors": {"outdoor_temperature": 7.81}, }, "d3da73bde12a47d5a6b8f9dad971f2ec": { "class": "thermo_sensor", "fw": "2019-03-27T01:00:00+01:00", + "hw": "1", "location": "82fa13f017d240daa0d0ea1775420f24", + "mac_address": None, "model": "Tom/Floor", "name": "Thermostatic Radiator Jessie", "vendor": "Plugwise", + "lower_bound": 0, + "upper_bound": 100.0, + "resolution": 0.01, "sensors": { "temperature": 17.1, - "setpoint": 15.0, + "setpoint": 15, "battery": 62, "temperature_difference": 0.1, "valve_position": 0.0, @@ -148,10 +176,13 @@ async def test_diagnostics( "21f2b542c49845e6bb416884c55778d6": { "class": "game_console", "fw": "2019-06-21T02:00:00+02:00", + "hw": None, "location": "cd143c07248f491493cea0533bc3d669", + "mac_address": None, "model": "Plug", "name": "Playstation Smart Plug", "vendor": "Plugwise", + "zigbee_mac_address": "012345670A12", "sensors": { "electricity_consumed": 82.6, "electricity_consumed_interval": 8.6, @@ -163,10 +194,13 @@ async def test_diagnostics( "78d1126fc4c743db81b61c20e88342a7": { "class": "central_heating_pump", "fw": "2019-06-21T02:00:00+02:00", + "hw": None, "location": "c50f167537524366a5af7aa3942feb1e", + "mac_address": None, "model": "Plug", "name": "CV Pomp", "vendor": "Plugwise", + "zigbee_mac_address": "012345670A05", "sensors": { "electricity_consumed": 35.6, "electricity_consumed_interval": 7.37, @@ -178,26 +212,33 @@ async def test_diagnostics( "90986d591dcd426cae3ec3e8111ff730": { "class": "heater_central", "fw": None, + "hw": None, "location": "1f9dcf83fd4e4b66b72ff787957bfe5d", + "mac_address": None, "model": "Unknown", "name": "OnOff", "vendor": None, + "lower_bound": 10, + "upper_bound": 90, + "resolution": 1, "cooling_active": False, "heating_state": True, "sensors": { "water_temperature": 70.0, "intended_boiler_temperature": 70.0, "modulation_level": 1, - "device_state": "heating", }, }, "cd0ddb54ef694e11ac18ed1cbce5dbbd": { "class": "vcr", "fw": "2019-06-21T02:00:00+02:00", + "hw": None, "location": "cd143c07248f491493cea0533bc3d669", + "mac_address": None, "model": "Plug", "name": "NAS", "vendor": "Plugwise", + "zigbee_mac_address": "012345670A14", "sensors": { "electricity_consumed": 16.5, "electricity_consumed_interval": 0.5, @@ -209,10 +250,13 @@ async def test_diagnostics( "4a810418d5394b3f82727340b91ba740": { "class": "router", "fw": "2019-06-21T02:00:00+02:00", + "hw": None, "location": "cd143c07248f491493cea0533bc3d669", + "mac_address": None, "model": "Plug", "name": "USG Smart Plug", "vendor": "Plugwise", + "zigbee_mac_address": "012345670A16", "sensors": { "electricity_consumed": 8.5, "electricity_consumed_interval": 0.0, @@ -224,10 +268,13 @@ async def test_diagnostics( "02cf28bfec924855854c544690a609ef": { "class": "vcr", "fw": "2019-06-21T02:00:00+02:00", + "hw": None, "location": "cd143c07248f491493cea0533bc3d669", + "mac_address": None, "model": "Plug", "name": "NVR", "vendor": "Plugwise", + "zigbee_mac_address": "012345670A15", "sensors": { "electricity_consumed": 34.0, "electricity_consumed_interval": 9.15, @@ -239,10 +286,13 @@ async def test_diagnostics( "a28f588dc4a049a483fd03a30361ad3a": { "class": "settop", "fw": "2019-06-21T02:00:00+02:00", + "hw": None, "location": "cd143c07248f491493cea0533bc3d669", + "mac_address": None, "model": "Plug", "name": "Fibaro HC2", "vendor": "Plugwise", + "zigbee_mac_address": "012345670A13", "sensors": { "electricity_consumed": 12.5, "electricity_consumed_interval": 3.8, @@ -254,10 +304,15 @@ async def test_diagnostics( "6a3bf693d05e48e0b460c815a4fdd09d": { "class": "zone_thermostat", "fw": "2016-10-27T02:00:00+02:00", + "hw": "255", "location": "82fa13f017d240daa0d0ea1775420f24", + "mac_address": None, "model": "Lisa", "name": "Zone Thermostat Jessie", "vendor": "Plugwise", + "lower_bound": 0, + "upper_bound": 99.9, + "resolution": 0.01, "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], "active_preset": "asleep", "presets": { @@ -275,21 +330,26 @@ async def test_diagnostics( "CV Jessie", ], "selected_schedule": "CV Jessie", - "schedule_temperature": 15.0, "last_used": "CV Jessie", + "schedule_temperature": 15.0, "mode": "auto", - "sensors": {"temperature": 17.2, "setpoint": 15.0, "battery": 37}, + "sensors": {"temperature": 17.2, "setpoint": 15, "battery": 37}, }, "680423ff840043738f42cc7f1ff97a36": { "class": "thermo_sensor", "fw": "2019-03-27T01:00:00+01:00", + "hw": "1", "location": "08963fec7c53423ca5680aa4cb502c63", + "mac_address": None, "model": "Tom/Floor", "name": "Thermostatic Radiator Badkamer", "vendor": "Plugwise", + "lower_bound": 0, + "upper_bound": 100.0, + "resolution": 0.01, "sensors": { "temperature": 19.1, - "setpoint": 14.0, + "setpoint": 14, "battery": 51, "temperature_difference": -0.4, "valve_position": 0.0, @@ -298,10 +358,15 @@ async def test_diagnostics( "f1fee6043d3642a9b0a65297455f008e": { "class": "zone_thermostat", "fw": "2016-10-27T02:00:00+02:00", + "hw": "255", "location": "08963fec7c53423ca5680aa4cb502c63", + "mac_address": None, "model": "Lisa", "name": "Zone Thermostat Badkamer", "vendor": "Plugwise", + "lower_bound": 0, + "upper_bound": 99.9, + "resolution": 0.01, "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], "active_preset": "away", "presets": { @@ -319,18 +384,21 @@ async def test_diagnostics( "CV Jessie", ], "selected_schedule": "Badkamer Schema", - "schedule_temperature": 15.0, "last_used": "Badkamer Schema", + "schedule_temperature": 15.0, "mode": "auto", - "sensors": {"temperature": 18.9, "setpoint": 14.0, "battery": 92}, + "sensors": {"temperature": 18.9, "setpoint": 14, "battery": 92}, }, "675416a629f343c495449970e2ca37b5": { "class": "router", "fw": "2019-06-21T02:00:00+02:00", + "hw": None, "location": "cd143c07248f491493cea0533bc3d669", + "mac_address": None, "model": "Plug", "name": "Ziggo Modem", "vendor": "Plugwise", + "zigbee_mac_address": "012345670A01", "sensors": { "electricity_consumed": 12.2, "electricity_consumed_interval": 2.97, @@ -342,10 +410,15 @@ async def test_diagnostics( "e7693eb9582644e5b865dba8d4447cf1": { "class": "thermostatic_radiator_valve", "fw": "2019-03-27T01:00:00+01:00", + "hw": "1", "location": "446ac08dd04d4eff8ac57489757b7314", + "mac_address": None, "model": "Tom/Floor", "name": "CV Kraan Garage", "vendor": "Plugwise", + "lower_bound": 0, + "upper_bound": 100.0, + "resolution": 0.01, "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], "active_preset": "no_frost", "presets": { @@ -363,8 +436,8 @@ async def test_diagnostics( "CV Jessie", ], "selected_schedule": "None", - "schedule_temperature": 15.0, "last_used": "Badkamer Schema", + "schedule_temperature": 15.0, "mode": "heat", "sensors": { "temperature": 15.6, From cdd5d22b388bc5a8d4b146b674ca29a2dc8c16e8 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 17 Feb 2022 17:39:33 +0100 Subject: [PATCH 0746/1098] Remove ThreadPoolExecutor `shutdown` backport (#66735) --- homeassistant/util/executor.py | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/homeassistant/util/executor.py b/homeassistant/util/executor.py index 5a8f15f434f6e0..145c2ba4f04906 100644 --- a/homeassistant/util/executor.py +++ b/homeassistant/util/executor.py @@ -4,11 +4,11 @@ from concurrent.futures import ThreadPoolExecutor import contextlib import logging -import queue import sys from threading import Thread import time import traceback +from typing import Any from .thread import async_raise @@ -62,29 +62,9 @@ def join_or_interrupt_threads( class InterruptibleThreadPoolExecutor(ThreadPoolExecutor): """A ThreadPoolExecutor instance that will not deadlock on shutdown.""" - def shutdown(self, *args, **kwargs) -> None: # type: ignore - """Shutdown backport from cpython 3.9 with interrupt support added.""" - with self._shutdown_lock: - self._shutdown = True - # Drain all work items from the queue, and then cancel their - # associated futures. - while True: - try: - work_item = self._work_queue.get_nowait() - except queue.Empty: - break - if work_item is not None: - work_item.future.cancel() - # Send a wake-up to prevent threads calling - # _work_queue.get(block=True) from permanently blocking. - self._work_queue.put(None) # type: ignore[arg-type] - - # The above code is backported from python 3.9 - # - # For maintainability join_threads_or_timeout is - # a separate function since it is not a backport from - # cpython itself - # + def shutdown(self, *args: Any, **kwargs: Any) -> None: + """Shutdown with interrupt support added.""" + super().shutdown(wait=False, cancel_futures=True) self.join_threads_or_timeout() def join_threads_or_timeout(self) -> None: From 9d5dc2ce246a46a285aa8066971bdf8131a18053 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Thu, 17 Feb 2022 12:19:01 -0600 Subject: [PATCH 0747/1098] Improve roku play media handling (#66429) Co-authored-by: Paulus Schoutsen --- homeassistant/components/roku/const.py | 2 + homeassistant/components/roku/manifest.json | 2 +- homeassistant/components/roku/media_player.py | 118 ++++++++++--- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/roku/test_media_player.py | 167 +++++++++++++++--- 6 files changed, 242 insertions(+), 51 deletions(-) diff --git a/homeassistant/components/roku/const.py b/homeassistant/components/roku/const.py index e399f6e0558be9..f098483e0c63f8 100644 --- a/homeassistant/components/roku/const.py +++ b/homeassistant/components/roku/const.py @@ -2,10 +2,12 @@ DOMAIN = "roku" # Attributes +ATTR_ARTIST_NAME = "artist_name" ATTR_CONTENT_ID = "content_id" ATTR_FORMAT = "format" ATTR_KEYWORD = "keyword" ATTR_MEDIA_TYPE = "media_type" +ATTR_THUMBNAIL = "thumbnail" # Default Values DEFAULT_PORT = 8060 diff --git a/homeassistant/components/roku/manifest.json b/homeassistant/components/roku/manifest.json index d68f2b4b24212c..d03aa9846c3c55 100644 --- a/homeassistant/components/roku/manifest.json +++ b/homeassistant/components/roku/manifest.json @@ -2,7 +2,7 @@ "domain": "roku", "name": "Roku", "documentation": "https://www.home-assistant.io/integrations/roku", - "requirements": ["rokuecp==0.13.2"], + "requirements": ["rokuecp==0.14.0"], "homekit": { "models": ["3810X", "4660X", "7820X", "C105X", "C135X"] }, diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index ff9e034e5d434e..9cf17d890a4d50 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -3,9 +3,12 @@ import datetime as dt import logging +import mimetypes from typing import Any +from rokuecp.helpers import guess_stream_format import voluptuous as vol +import yarl from homeassistant.components import media_source from homeassistant.components.media_player import ( @@ -18,7 +21,9 @@ ATTR_MEDIA_EXTRA, MEDIA_TYPE_APP, MEDIA_TYPE_CHANNEL, + MEDIA_TYPE_MUSIC, MEDIA_TYPE_URL, + MEDIA_TYPE_VIDEO, SUPPORT_BROWSE_MEDIA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, @@ -49,10 +54,12 @@ from . import roku_exception_handler from .browse_media import async_browse_media from .const import ( + ATTR_ARTIST_NAME, ATTR_CONTENT_ID, ATTR_FORMAT, ATTR_KEYWORD, ATTR_MEDIA_TYPE, + ATTR_THUMBNAIL, DOMAIN, SERVICE_SEARCH, ) @@ -76,21 +83,36 @@ | SUPPORT_BROWSE_MEDIA ) + +STREAM_FORMAT_TO_MEDIA_TYPE = { + "dash": MEDIA_TYPE_VIDEO, + "hls": MEDIA_TYPE_VIDEO, + "ism": MEDIA_TYPE_VIDEO, + "m4a": MEDIA_TYPE_MUSIC, + "m4v": MEDIA_TYPE_VIDEO, + "mka": MEDIA_TYPE_MUSIC, + "mkv": MEDIA_TYPE_VIDEO, + "mks": MEDIA_TYPE_VIDEO, + "mp3": MEDIA_TYPE_MUSIC, + "mp4": MEDIA_TYPE_VIDEO, +} + ATTRS_TO_LAUNCH_PARAMS = { ATTR_CONTENT_ID: "contentID", - ATTR_MEDIA_TYPE: "MediaType", + ATTR_MEDIA_TYPE: "mediaType", } -PLAY_MEDIA_SUPPORTED_TYPES = ( - MEDIA_TYPE_APP, - MEDIA_TYPE_CHANNEL, - MEDIA_TYPE_URL, - FORMAT_CONTENT_TYPE[HLS_PROVIDER], -) - -ATTRS_TO_PLAY_VIDEO_PARAMS = { +ATTRS_TO_PLAY_ON_ROKU_PARAMS = { ATTR_NAME: "videoName", ATTR_FORMAT: "videoFormat", + ATTR_THUMBNAIL: "k", +} + +ATTRS_TO_PLAY_ON_ROKU_AUDIO_PARAMS = { + ATTR_NAME: "songName", + ATTR_FORMAT: "songFormat", + ATTR_ARTIST_NAME: "artistName", + ATTR_THUMBNAIL: "albumArtUrl", } SEARCH_SCHEMA = {vol.Required(ATTR_KEYWORD): str} @@ -366,25 +388,67 @@ async def async_play_media( ) -> None: """Play media from a URL or file, launch an application, or tune to a channel.""" extra: dict[str, Any] = kwargs.get(ATTR_MEDIA_EXTRA) or {} + original_media_type: str = media_type + original_media_id: str = media_id + mime_type: str | None = None + stream_name: str | None = None + stream_format: str | None = extra.get(ATTR_FORMAT) # Handle media_source if media_source.is_media_source_id(media_id): sourced_media = await media_source.async_resolve_media(self.hass, media_id) media_type = MEDIA_TYPE_URL media_id = sourced_media.url + mime_type = sourced_media.mime_type + stream_name = original_media_id + stream_format = guess_stream_format(media_id, mime_type) # If media ID is a relative URL, we serve it from HA. media_id = async_process_play_media_url(self.hass, media_id) - if media_type not in PLAY_MEDIA_SUPPORTED_TYPES: - _LOGGER.error( - "Invalid media type %s. Only %s, %s, %s, and camera HLS streams are supported", - media_type, - MEDIA_TYPE_APP, - MEDIA_TYPE_CHANNEL, - MEDIA_TYPE_URL, - ) - return + if media_type == FORMAT_CONTENT_TYPE[HLS_PROVIDER]: + media_type = MEDIA_TYPE_VIDEO + mime_type = FORMAT_CONTENT_TYPE[HLS_PROVIDER] + stream_name = "Camera Stream" + stream_format = "hls" + + if media_type in (MEDIA_TYPE_MUSIC, MEDIA_TYPE_URL, MEDIA_TYPE_VIDEO): + parsed = yarl.URL(media_id) + + if mime_type is None: + mime_type, _ = mimetypes.guess_type(parsed.path) + + if stream_format is None: + stream_format = guess_stream_format(media_id, mime_type) + + if extra.get(ATTR_FORMAT) is None: + extra[ATTR_FORMAT] = stream_format + + if extra[ATTR_FORMAT] not in STREAM_FORMAT_TO_MEDIA_TYPE: + _LOGGER.error( + "Media type %s is not supported with format %s (mime: %s)", + original_media_type, + extra[ATTR_FORMAT], + mime_type, + ) + return + + if ( + media_type == MEDIA_TYPE_URL + and STREAM_FORMAT_TO_MEDIA_TYPE[extra[ATTR_FORMAT]] == MEDIA_TYPE_MUSIC + ): + media_type = MEDIA_TYPE_MUSIC + + if media_type == MEDIA_TYPE_MUSIC and "tts_proxy" in media_id: + stream_name = "Text to Speech" + elif stream_name is None: + if stream_format == "ism": + stream_name = parsed.parts[-2] + else: + stream_name = parsed.name + + if extra.get(ATTR_NAME) is None: + extra[ATTR_NAME] = stream_name if media_type == MEDIA_TYPE_APP: params = { @@ -396,20 +460,30 @@ async def async_play_media( await self.coordinator.roku.launch(media_id, params) elif media_type == MEDIA_TYPE_CHANNEL: await self.coordinator.roku.tune(media_id) - elif media_type == MEDIA_TYPE_URL: + elif media_type == MEDIA_TYPE_MUSIC: + if extra.get(ATTR_ARTIST_NAME) is None: + extra[ATTR_ARTIST_NAME] = "Home Assistant" + params = { param: extra[attr] - for (attr, param) in ATTRS_TO_PLAY_VIDEO_PARAMS.items() + for (attr, param) in ATTRS_TO_PLAY_ON_ROKU_AUDIO_PARAMS.items() if attr in extra } + params = {"t": "a", **params} + await self.coordinator.roku.play_on_roku(media_id, params) - elif media_type == FORMAT_CONTENT_TYPE[HLS_PROVIDER]: + elif media_type in (MEDIA_TYPE_URL, MEDIA_TYPE_VIDEO): params = { - "MediaType": "hls", + param: extra[attr] + for (attr, param) in ATTRS_TO_PLAY_ON_ROKU_PARAMS.items() + if attr in extra } await self.coordinator.roku.play_on_roku(media_id, params) + else: + _LOGGER.error("Media type %s is not supported", original_media_type) + return await self.coordinator.async_request_refresh() diff --git a/requirements_all.txt b/requirements_all.txt index e0c0e4c960c051..38629f5e8bd488 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2111,7 +2111,7 @@ rjpl==0.3.6 rocketchat-API==0.6.1 # homeassistant.components.roku -rokuecp==0.13.2 +rokuecp==0.14.0 # homeassistant.components.roomba roombapy==1.6.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f6d7d898eb3b65..4b082aa600990a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1306,7 +1306,7 @@ rflink==0.0.62 ring_doorbell==0.7.2 # homeassistant.components.roku -rokuecp==0.13.2 +rokuecp==0.14.0 # homeassistant.components.roomba roombapy==1.6.5 diff --git a/tests/components/roku/test_media_player.py b/tests/components/roku/test_media_player.py index 2686a281dba235..79b996530e308f 100644 --- a/tests/components/roku/test_media_player.py +++ b/tests/components/roku/test_media_player.py @@ -27,7 +27,9 @@ MEDIA_TYPE_APPS, MEDIA_TYPE_CHANNEL, MEDIA_TYPE_CHANNELS, + MEDIA_TYPE_MUSIC, MEDIA_TYPE_URL, + MEDIA_TYPE_VIDEO, SERVICE_PLAY_MEDIA, SERVICE_SELECT_SOURCE, SUPPORT_BROWSE_MEDIA, @@ -459,72 +461,181 @@ async def test_services( "291097", { "contentID": "8e06a8b7-d667-4e31-939d-f40a6dd78a88", - "MediaType": "movie", + "mediaType": "movie", }, ) + await hass.services.async_call( + MP_DOMAIN, + SERVICE_SELECT_SOURCE, + {ATTR_ENTITY_ID: MAIN_ENTITY_ID, ATTR_INPUT_SOURCE: "Netflix"}, + blocking=True, + ) + + assert mock_roku.launch.call_count == 3 + mock_roku.launch.assert_called_with("12") + + await hass.services.async_call( + MP_DOMAIN, + SERVICE_SELECT_SOURCE, + {ATTR_ENTITY_ID: MAIN_ENTITY_ID, ATTR_INPUT_SOURCE: 12}, + blocking=True, + ) + + assert mock_roku.launch.call_count == 4 + mock_roku.launch.assert_called_with("12") + + +async def test_services_play_media( + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_roku: MagicMock, +) -> None: + """Test the media player services related to playing media.""" await hass.services.async_call( MP_DOMAIN, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: MAIN_ENTITY_ID, - ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_URL, - ATTR_MEDIA_CONTENT_ID: "https://awesome.tld/media.mp4", + ATTR_MEDIA_CONTENT_TYPE: "blah", + ATTR_MEDIA_CONTENT_ID: "https://localhost/media.m4a", ATTR_MEDIA_EXTRA: { - ATTR_NAME: "Sent from HA", - ATTR_FORMAT: "mp4", + ATTR_NAME: "Test", }, }, blocking=True, ) - assert mock_roku.play_on_roku.call_count == 1 - mock_roku.play_on_roku.assert_called_with( - "https://awesome.tld/media.mp4", + assert mock_roku.play_on_roku.call_count == 0 + + await hass.services.async_call( + MP_DOMAIN, + SERVICE_PLAY_MEDIA, { - "videoName": "Sent from HA", - "videoFormat": "mp4", + ATTR_ENTITY_ID: MAIN_ENTITY_ID, + ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_MUSIC, + ATTR_MEDIA_CONTENT_ID: "https://localhost/media.m4a", + ATTR_MEDIA_EXTRA: {ATTR_FORMAT: "blah"}, }, + blocking=True, ) + assert mock_roku.play_on_roku.call_count == 0 + + +@pytest.mark.parametrize( + "content_type, content_id, resolved_name, resolved_format", + [ + (MEDIA_TYPE_URL, "http://localhost/media.m4a", "media.m4a", "m4a"), + (MEDIA_TYPE_MUSIC, "http://localhost/media.m4a", "media.m4a", "m4a"), + (MEDIA_TYPE_MUSIC, "http://localhost/media.mka", "media.mka", "mka"), + ( + MEDIA_TYPE_MUSIC, + "http://localhost/api/tts_proxy/generated.mp3", + "Text to Speech", + "mp3", + ), + ], +) +async def test_services_play_media_audio( + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_roku: MagicMock, + content_type: str, + content_id: str, + resolved_name: str, + resolved_format: str, +) -> None: + """Test the media player services related to playing media.""" await hass.services.async_call( MP_DOMAIN, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: MAIN_ENTITY_ID, - ATTR_MEDIA_CONTENT_TYPE: FORMAT_CONTENT_TYPE[HLS_PROVIDER], - ATTR_MEDIA_CONTENT_ID: "https://awesome.tld/api/hls/api_token/master_playlist.m3u8", + ATTR_MEDIA_CONTENT_TYPE: content_type, + ATTR_MEDIA_CONTENT_ID: content_id, }, blocking=True, ) - - assert mock_roku.play_on_roku.call_count == 2 - mock_roku.play_on_roku.assert_called_with( - "https://awesome.tld/api/hls/api_token/master_playlist.m3u8", + mock_roku.play_on_roku.assert_called_once_with( + content_id, { - "MediaType": "hls", + "t": "a", + "songName": resolved_name, + "songFormat": resolved_format, + "artistName": "Home Assistant", }, ) + +@pytest.mark.parametrize( + "content_type, content_id, resolved_name, resolved_format", + [ + (MEDIA_TYPE_URL, "http://localhost/media.mp4", "media.mp4", "mp4"), + (MEDIA_TYPE_VIDEO, "http://localhost/media.m4v", "media.m4v", "mp4"), + (MEDIA_TYPE_VIDEO, "http://localhost/media.mov", "media.mov", "mp4"), + (MEDIA_TYPE_VIDEO, "http://localhost/media.mkv", "media.mkv", "mkv"), + (MEDIA_TYPE_VIDEO, "http://localhost/media.mks", "media.mks", "mks"), + (MEDIA_TYPE_VIDEO, "http://localhost/media.m3u8", "media.m3u8", "hls"), + (MEDIA_TYPE_VIDEO, "http://localhost/media.dash", "media.dash", "dash"), + (MEDIA_TYPE_VIDEO, "http://localhost/media.mpd", "media.mpd", "dash"), + (MEDIA_TYPE_VIDEO, "http://localhost/media.ism/manifest", "media.ism", "ism"), + ], +) +async def test_services_play_media_video( + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_roku: MagicMock, + content_type: str, + content_id: str, + resolved_name: str, + resolved_format: str, +) -> None: + """Test the media player services related to playing media.""" await hass.services.async_call( MP_DOMAIN, - SERVICE_SELECT_SOURCE, - {ATTR_ENTITY_ID: MAIN_ENTITY_ID, ATTR_INPUT_SOURCE: "Netflix"}, + SERVICE_PLAY_MEDIA, + { + ATTR_ENTITY_ID: MAIN_ENTITY_ID, + ATTR_MEDIA_CONTENT_TYPE: content_type, + ATTR_MEDIA_CONTENT_ID: content_id, + }, blocking=True, ) + mock_roku.play_on_roku.assert_called_once_with( + content_id, + { + "videoName": resolved_name, + "videoFormat": resolved_format, + }, + ) - assert mock_roku.launch.call_count == 3 - mock_roku.launch.assert_called_with("12") +async def test_services_camera_play_stream( + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_roku: MagicMock, +) -> None: + """Test the media player services related to playing camera stream.""" await hass.services.async_call( MP_DOMAIN, - SERVICE_SELECT_SOURCE, - {ATTR_ENTITY_ID: MAIN_ENTITY_ID, ATTR_INPUT_SOURCE: 12}, + SERVICE_PLAY_MEDIA, + { + ATTR_ENTITY_ID: MAIN_ENTITY_ID, + ATTR_MEDIA_CONTENT_TYPE: FORMAT_CONTENT_TYPE[HLS_PROVIDER], + ATTR_MEDIA_CONTENT_ID: "https://awesome.tld/api/hls/api_token/master_playlist.m3u8", + }, blocking=True, ) - assert mock_roku.launch.call_count == 4 - mock_roku.launch.assert_called_with("12") + assert mock_roku.play_on_roku.call_count == 1 + mock_roku.play_on_roku.assert_called_with( + "https://awesome.tld/api/hls/api_token/master_playlist.m3u8", + { + "videoName": "Camera Stream", + "videoFormat": "hls", + }, + ) async def test_services_play_media_local_source( @@ -556,7 +667,11 @@ async def test_services_play_media_local_source( assert mock_roku.play_on_roku.call_count == 1 assert mock_roku.play_on_roku.call_args call_args = mock_roku.play_on_roku.call_args.args - assert "/media/local/Epic%20Sax%20Guy%2010%20Hours.mp4?authSig=" in call_args[0] + assert "/local/Epic%20Sax%20Guy%2010%20Hours.mp4?authSig=" in call_args[0] + assert call_args[1] == { + "videoFormat": "mp4", + "videoName": "media-source://media_source/local/Epic Sax Guy 10 Hours.mp4", + } @pytest.mark.parametrize("mock_device", ["roku/rokutv-7820x.json"], indirect=True) From 750b48dcaf109b854be804371d4bdef23bd7fc1a Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 17 Feb 2022 20:12:12 +0100 Subject: [PATCH 0748/1098] Use pylint disable-next in MQTT (#66758) --- homeassistant/components/mqtt/__init__.py | 8 ++++---- homeassistant/components/mqtt/config_flow.py | 2 +- homeassistant/components/mqtt/discovery.py | 4 ++-- homeassistant/components/mqtt/mixins.py | 7 ++----- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 7c3bc63779b092..3da9a17c3aaf6c 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -895,7 +895,7 @@ async def async_publish( async def async_connect(self) -> None: """Connect to the host. Does not process messages yet.""" - # pylint: disable=import-outside-toplevel + # pylint: disable-next=import-outside-toplevel import paho.mqtt.client as mqtt result: int | None = None @@ -1000,7 +1000,7 @@ def _mqtt_on_connect(self, _mqttc, _userdata, _flags, result_code: int) -> None: Resubscribe to all topics we were subscribed to and publish birth message. """ - # pylint: disable=import-outside-toplevel + # pylint: disable-next=import-outside-toplevel import paho.mqtt.client as mqtt if result_code != mqtt.CONNACK_ACCEPTED: @@ -1159,7 +1159,7 @@ async def _discovery_cooldown(self): def _raise_on_error(result_code: int | None) -> None: """Raise error if error result.""" - # pylint: disable=import-outside-toplevel + # pylint: disable-next=import-outside-toplevel import paho.mqtt.client as mqtt if result_code is not None and result_code != 0: @@ -1169,7 +1169,7 @@ def _raise_on_error(result_code: int | None) -> None: def _matcher_for_topic(subscription: str) -> Any: - # pylint: disable=import-outside-toplevel + # pylint: disable-next=import-outside-toplevel from paho.mqtt.matcher import MQTTMatcher matcher = MQTTMatcher() diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index a26e62b62272b4..23e2a0d1e81cfe 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -313,7 +313,7 @@ async def async_step_options(self, user_input=None): def try_connection(broker, port, username, password, protocol="3.1"): """Test if we can connect to an MQTT broker.""" - # pylint: disable=import-outside-toplevel + # pylint: disable-next=import-outside-toplevel import paho.mqtt.client as mqtt if protocol == "3.1": diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index b31d90c76f8ca9..11bc0f6839a29c 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -228,13 +228,13 @@ async def discovery_done(_): if config_entries_key not in hass.data[CONFIG_ENTRY_IS_SETUP]: if component == "device_automation": # Local import to avoid circular dependencies - # pylint: disable=import-outside-toplevel + # pylint: disable-next=import-outside-toplevel from . import device_automation await device_automation.async_setup_entry(hass, config_entry) elif component == "tag": # Local import to avoid circular dependencies - # pylint: disable=import-outside-toplevel + # pylint: disable-next=import-outside-toplevel from . import tag await tag.async_setup_entry(hass, config_entry) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 421ad3203b3617..fb25fa1e1b65d7 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -420,10 +420,7 @@ def _availability_setup_from_config(self, config): CONF_AVAILABILITY_TEMPLATE: avail.get(CONF_VALUE_TEMPLATE), } - for ( - topic, # pylint: disable=unused-variable - avail_topic_conf, - ) in self._avail_topics.items(): + for avail_topic_conf in self._avail_topics.values(): avail_topic_conf[CONF_AVAILABILITY_TEMPLATE] = MqttValueTemplate( avail_topic_conf[CONF_AVAILABILITY_TEMPLATE], entity=self, @@ -502,7 +499,7 @@ def available(self) -> bool: async def cleanup_device_registry(hass, device_id): """Remove device registry entry if there are no remaining entities or triggers.""" # Local import to avoid circular dependencies - # pylint: disable=import-outside-toplevel + # pylint: disable-next=import-outside-toplevel from . import device_trigger, tag device_registry = dr.async_get(hass) From e79348f952577d3769e2759fcccb75f947f49eb4 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Thu, 17 Feb 2022 21:13:09 +0200 Subject: [PATCH 0749/1098] Fix webostv notify service (#66760) --- homeassistant/components/webostv/notify.py | 4 ++-- tests/components/webostv/test_notify.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/webostv/notify.py b/homeassistant/components/webostv/notify.py index df2ed7e50634c1..7348e978d026f7 100644 --- a/homeassistant/components/webostv/notify.py +++ b/homeassistant/components/webostv/notify.py @@ -46,8 +46,8 @@ async def async_send_message(self, message: str = "", **kwargs: Any) -> None: if not self._client.is_connected(): await self._client.connect() - data = kwargs.get(ATTR_DATA) - icon_path = data.get(CONF_ICON, "") if data else None + data = kwargs.get(ATTR_DATA, {}) + icon_path = data.get(CONF_ICON) await self._client.send_message(message, icon_path=icon_path) except WebOsTvPairError: _LOGGER.error("Pairing with TV failed") diff --git a/tests/components/webostv/test_notify.py b/tests/components/webostv/test_notify.py index a518854573762c..7e150c6eb7828d 100644 --- a/tests/components/webostv/test_notify.py +++ b/tests/components/webostv/test_notify.py @@ -36,6 +36,21 @@ async def test_notify(hass, client): assert client.connect.call_count == 1 client.send_message.assert_called_with(MESSAGE, icon_path=ICON_PATH) + await hass.services.async_call( + NOTIFY_DOMAIN, + TV_NAME, + { + ATTR_MESSAGE: MESSAGE, + CONF_SERVICE_DATA: { + "OTHER_DATA": "not_used", + }, + }, + blocking=True, + ) + assert client.mock_calls[0] == call.connect() + assert client.connect.call_count == 1 + client.send_message.assert_called_with(MESSAGE, icon_path=None) + async def test_notify_not_connected(hass, client, monkeypatch): """Test sending a message when client is not connected.""" From dacc2b1ab0acadecbd5bac55c01f7b7e5a60bb4f Mon Sep 17 00:00:00 2001 From: Tom Date: Thu, 17 Feb 2022 20:58:06 +0100 Subject: [PATCH 0750/1098] Plugwise update Zigbee addressing fixture data to 64bits (#66761) --- .../all_data.json | 18 +++++++++--------- .../fixtures/anna_heatpump/all_data.json | 2 +- .../fixtures/p1v3_full_option/all_data.json | 2 +- .../fixtures/stretch_v31/all_data.json | 12 ++++++------ tests/components/plugwise/test_diagnostics.py | 16 ++++++++-------- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json index 3dbe9f5d8a3bf7..925571908c537b 100644 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json +++ b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json @@ -179,7 +179,7 @@ "model": "Adam", "name": "Adam", "vendor": "Plugwise B.V.", - "zigbee_mac_address": "012345670101", + "zigbee_mac_address": "ABCD012345670101", "binary_sensors": { "plugwise_notification": true }, @@ -216,7 +216,7 @@ "model": "Plug", "name": "Playstation Smart Plug", "vendor": "Plugwise", - "zigbee_mac_address": "012345670A12", + "zigbee_mac_address": "ABCD012345670A12", "sensors": { "electricity_consumed": 82.6, "electricity_consumed_interval": 8.6, @@ -237,7 +237,7 @@ "model": "Plug", "name": "CV Pomp", "vendor": "Plugwise", - "zigbee_mac_address": "012345670A05", + "zigbee_mac_address": "ABCD012345670A05", "sensors": { "electricity_consumed": 35.6, "electricity_consumed_interval": 7.37, @@ -277,7 +277,7 @@ "model": "Plug", "name": "NAS", "vendor": "Plugwise", - "zigbee_mac_address": "012345670A14", + "zigbee_mac_address": "ABCD012345670A14", "sensors": { "electricity_consumed": 16.5, "electricity_consumed_interval": 0.5, @@ -298,7 +298,7 @@ "model": "Plug", "name": "USG Smart Plug", "vendor": "Plugwise", - "zigbee_mac_address": "012345670A16", + "zigbee_mac_address": "ABCD012345670A16", "sensors": { "electricity_consumed": 8.5, "electricity_consumed_interval": 0.0, @@ -319,7 +319,7 @@ "model": "Plug", "name": "NVR", "vendor": "Plugwise", - "zigbee_mac_address": "012345670A15", + "zigbee_mac_address": "ABCD012345670A15", "sensors": { "electricity_consumed": 34.0, "electricity_consumed_interval": 9.15, @@ -340,7 +340,7 @@ "model": "Plug", "name": "Fibaro HC2", "vendor": "Plugwise", - "zigbee_mac_address": "012345670A13", + "zigbee_mac_address": "ABCD012345670A13", "sensors": { "electricity_consumed": 12.5, "electricity_consumed_interval": 3.8, @@ -499,7 +499,7 @@ "model": "Plug", "name": "Ziggo Modem", "vendor": "Plugwise", - "zigbee_mac_address": "012345670A01", + "zigbee_mac_address": "ABCD012345670A01", "sensors": { "electricity_consumed": 12.2, "electricity_consumed_interval": 2.97, @@ -573,4 +573,4 @@ } } } -] +] \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/anna_heatpump/all_data.json b/tests/components/plugwise/fixtures/anna_heatpump/all_data.json index 49b02b87f504eb..9585c8630afdaa 100644 --- a/tests/components/plugwise/fixtures/anna_heatpump/all_data.json +++ b/tests/components/plugwise/fixtures/anna_heatpump/all_data.json @@ -116,4 +116,4 @@ } } } -] +] \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/p1v3_full_option/all_data.json b/tests/components/plugwise/fixtures/p1v3_full_option/all_data.json index a7ad6140fa7331..e65c012da85487 100644 --- a/tests/components/plugwise/fixtures/p1v3_full_option/all_data.json +++ b/tests/components/plugwise/fixtures/p1v3_full_option/all_data.json @@ -38,4 +38,4 @@ } } } -] +] \ No newline at end of file diff --git a/tests/components/plugwise/fixtures/stretch_v31/all_data.json b/tests/components/plugwise/fixtures/stretch_v31/all_data.json index b168923b8b2360..494ff28b118104 100644 --- a/tests/components/plugwise/fixtures/stretch_v31/all_data.json +++ b/tests/components/plugwise/fixtures/stretch_v31/all_data.json @@ -18,7 +18,7 @@ "vendor": "Plugwise B.V.", "model": "Stretch", "name": "Stretch", - "zigbee_mac_address": "012345670101" + "zigbee_mac_address": "ABCD012345670101" }, "5871317346d045bc9f6b987ef25ee638": { "class": "water_heater_vessel", @@ -29,7 +29,7 @@ "model": "Circle type F", "name": "Boiler (1EB31)", "vendor": "Plugwise", - "zigbee_mac_address": "012345670A07", + "zigbee_mac_address": "ABCD012345670A07", "sensors": { "electricity_consumed": 1.19, "electricity_consumed_interval": 0.0, @@ -68,7 +68,7 @@ "model": "Circle type F", "name": "Vaatwasser (2a1ab)", "vendor": "Plugwise", - "zigbee_mac_address": "012345670A02", + "zigbee_mac_address": "ABCD012345670A02", "sensors": { "electricity_consumed": 0.0, "electricity_consumed_interval": 0.71, @@ -88,7 +88,7 @@ "model": "Circle type F", "name": "Droger (52559)", "vendor": "Plugwise", - "zigbee_mac_address": "012345670A04", + "zigbee_mac_address": "ABCD012345670A04", "sensors": { "electricity_consumed": 0.0, "electricity_consumed_interval": 0.0, @@ -108,7 +108,7 @@ "model": "Circle type F", "name": "Wasmachine (52AC1)", "vendor": "Plugwise", - "zigbee_mac_address": "012345670A01", + "zigbee_mac_address": "ABCD012345670A01", "sensors": { "electricity_consumed": 0.0, "electricity_consumed_interval": 0.0, @@ -176,4 +176,4 @@ } } } -] +] \ No newline at end of file diff --git a/tests/components/plugwise/test_diagnostics.py b/tests/components/plugwise/test_diagnostics.py index 5fdef0112a1222..6f4e7124d7034d 100644 --- a/tests/components/plugwise/test_diagnostics.py +++ b/tests/components/plugwise/test_diagnostics.py @@ -149,7 +149,7 @@ async def test_diagnostics( "model": "Adam", "name": "Adam", "vendor": "Plugwise B.V.", - "zigbee_mac_address": "012345670101", + "zigbee_mac_address": "ABCD012345670101", "binary_sensors": {"plugwise_notification": True}, "sensors": {"outdoor_temperature": 7.81}, }, @@ -182,7 +182,7 @@ async def test_diagnostics( "model": "Plug", "name": "Playstation Smart Plug", "vendor": "Plugwise", - "zigbee_mac_address": "012345670A12", + "zigbee_mac_address": "ABCD012345670A12", "sensors": { "electricity_consumed": 82.6, "electricity_consumed_interval": 8.6, @@ -200,7 +200,7 @@ async def test_diagnostics( "model": "Plug", "name": "CV Pomp", "vendor": "Plugwise", - "zigbee_mac_address": "012345670A05", + "zigbee_mac_address": "ABCD012345670A05", "sensors": { "electricity_consumed": 35.6, "electricity_consumed_interval": 7.37, @@ -238,7 +238,7 @@ async def test_diagnostics( "model": "Plug", "name": "NAS", "vendor": "Plugwise", - "zigbee_mac_address": "012345670A14", + "zigbee_mac_address": "ABCD012345670A14", "sensors": { "electricity_consumed": 16.5, "electricity_consumed_interval": 0.5, @@ -256,7 +256,7 @@ async def test_diagnostics( "model": "Plug", "name": "USG Smart Plug", "vendor": "Plugwise", - "zigbee_mac_address": "012345670A16", + "zigbee_mac_address": "ABCD012345670A16", "sensors": { "electricity_consumed": 8.5, "electricity_consumed_interval": 0.0, @@ -274,7 +274,7 @@ async def test_diagnostics( "model": "Plug", "name": "NVR", "vendor": "Plugwise", - "zigbee_mac_address": "012345670A15", + "zigbee_mac_address": "ABCD012345670A15", "sensors": { "electricity_consumed": 34.0, "electricity_consumed_interval": 9.15, @@ -292,7 +292,7 @@ async def test_diagnostics( "model": "Plug", "name": "Fibaro HC2", "vendor": "Plugwise", - "zigbee_mac_address": "012345670A13", + "zigbee_mac_address": "ABCD012345670A13", "sensors": { "electricity_consumed": 12.5, "electricity_consumed_interval": 3.8, @@ -398,7 +398,7 @@ async def test_diagnostics( "model": "Plug", "name": "Ziggo Modem", "vendor": "Plugwise", - "zigbee_mac_address": "012345670A01", + "zigbee_mac_address": "ABCD012345670A01", "sensors": { "electricity_consumed": 12.2, "electricity_consumed_interval": 2.97, From 0bebf14e45f377ce315991c3b256106dc46914ce Mon Sep 17 00:00:00 2001 From: Vaclav <44951610+bruxy70@users.noreply.github.com> Date: Thu, 17 Feb 2022 20:59:50 +0100 Subject: [PATCH 0751/1098] Bump holidays to 0.13 (#66612) --- homeassistant/components/workday/binary_sensor.py | 15 ++++++--------- homeassistant/components/workday/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/workday/binary_sensor.py b/homeassistant/components/workday/binary_sensor.py index c6ac7aceae16b1..3119a7b667b659 100644 --- a/homeassistant/components/workday/binary_sensor.py +++ b/homeassistant/components/workday/binary_sensor.py @@ -96,16 +96,13 @@ def setup_platform( obj_holidays = getattr(holidays, country)(years=year) if province: - # 'state' and 'prov' are not interchangeable, so need to make - # sure we use the right one - if hasattr(obj_holidays, "PROVINCES") and province in obj_holidays.PROVINCES: - obj_holidays = getattr(holidays, country)(prov=province, years=year) - elif hasattr(obj_holidays, "STATES") and province in obj_holidays.STATES: - obj_holidays = getattr(holidays, country)(state=province, years=year) + if ( + hasattr(obj_holidays, "subdivisions") + and province in obj_holidays.subdivisions + ): + obj_holidays = getattr(holidays, country)(subdiv=province, years=year) else: - _LOGGER.error( - "There is no province/state %s in country %s", province, country - ) + _LOGGER.error("There is no subdivision %s in country %s", province, country) return # Add custom holidays diff --git a/homeassistant/components/workday/manifest.json b/homeassistant/components/workday/manifest.json index cdf9fa5567b0ef..ca95065e1a98e7 100644 --- a/homeassistant/components/workday/manifest.json +++ b/homeassistant/components/workday/manifest.json @@ -2,7 +2,7 @@ "domain": "workday", "name": "Workday", "documentation": "https://www.home-assistant.io/integrations/workday", - "requirements": ["holidays==0.12"], + "requirements": ["holidays==0.13"], "codeowners": ["@fabaff"], "quality_scale": "internal", "iot_class": "local_polling", diff --git a/requirements_all.txt b/requirements_all.txt index 38629f5e8bd488..7fdf253ccc06ef 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -830,7 +830,7 @@ hlk-sw16==0.0.9 hole==0.7.0 # homeassistant.components.workday -holidays==0.12 +holidays==0.13 # homeassistant.components.frontend home-assistant-frontend==20220214.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4b082aa600990a..d32f2192f5ed03 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -543,7 +543,7 @@ hlk-sw16==0.0.9 hole==0.7.0 # homeassistant.components.workday -holidays==0.12 +holidays==0.13 # homeassistant.components.frontend home-assistant-frontend==20220214.0 From d6100abc7cbeb72ad5e73429316cd3b872c85f46 Mon Sep 17 00:00:00 2001 From: Andre Richter Date: Thu, 17 Feb 2022 21:06:35 +0100 Subject: [PATCH 0752/1098] Remove deprecated way of setting fan preset in Vallox (#66655) --- homeassistant/components/vallox/__init__.py | 33 ------------------- homeassistant/components/vallox/services.yaml | 16 --------- 2 files changed, 49 deletions(-) diff --git a/homeassistant/components/vallox/__init__.py b/homeassistant/components/vallox/__init__.py index 8a23f7d8df9339..fcda7227945097 100644 --- a/homeassistant/components/vallox/__init__.py +++ b/homeassistant/components/vallox/__init__.py @@ -29,7 +29,6 @@ METRIC_KEY_PROFILE_FAN_SPEED_BOOST, METRIC_KEY_PROFILE_FAN_SPEED_HOME, STATE_SCAN_INTERVAL, - STR_TO_VALLOX_PROFILE_SETTABLE, ) _LOGGER = logging.getLogger(__name__) @@ -55,17 +54,8 @@ Platform.BINARY_SENSOR, ] -ATTR_PROFILE = "profile" ATTR_PROFILE_FAN_SPEED = "fan_speed" -SERVICE_SCHEMA_SET_PROFILE = vol.Schema( - { - vol.Required(ATTR_PROFILE): vol.All( - cv.string, vol.In(STR_TO_VALLOX_PROFILE_SETTABLE) - ) - } -) - SERVICE_SCHEMA_SET_PROFILE_FAN_SPEED = vol.Schema( { vol.Required(ATTR_PROFILE_FAN_SPEED): vol.All( @@ -82,16 +72,11 @@ class ServiceMethodDetails(NamedTuple): schema: vol.Schema -SERVICE_SET_PROFILE = "set_profile" SERVICE_SET_PROFILE_FAN_SPEED_HOME = "set_profile_fan_speed_home" SERVICE_SET_PROFILE_FAN_SPEED_AWAY = "set_profile_fan_speed_away" SERVICE_SET_PROFILE_FAN_SPEED_BOOST = "set_profile_fan_speed_boost" SERVICE_TO_METHOD = { - SERVICE_SET_PROFILE: ServiceMethodDetails( - method="async_set_profile", - schema=SERVICE_SCHEMA_SET_PROFILE, - ), SERVICE_SET_PROFILE_FAN_SPEED_HOME: ServiceMethodDetails( method="async_set_profile_fan_speed_home", schema=SERVICE_SCHEMA_SET_PROFILE_FAN_SPEED, @@ -229,24 +214,6 @@ def __init__( self._client = client self._coordinator = coordinator - async def async_set_profile(self, profile: str = "Home") -> bool: - """Set the ventilation profile.""" - _LOGGER.debug("Setting ventilation profile to: %s", profile) - - _LOGGER.warning( - "Attention: The service 'vallox.set_profile' is superseded by the " - "'fan.set_preset_mode' service. It will be removed in the future, please migrate to " - "'fan.set_preset_mode' to prevent breakage" - ) - - try: - await self._client.set_profile(STR_TO_VALLOX_PROFILE_SETTABLE[profile]) - return True - - except (OSError, ValloxApiException) as err: - _LOGGER.error("Error setting ventilation profile: %s", err) - return False - async def async_set_profile_fan_speed_home( self, fan_speed: int = DEFAULT_FAN_SPEED_HOME ) -> bool: diff --git a/homeassistant/components/vallox/services.yaml b/homeassistant/components/vallox/services.yaml index 5cfa1dae4b5cc1..d6a0ec238c3786 100644 --- a/homeassistant/components/vallox/services.yaml +++ b/homeassistant/components/vallox/services.yaml @@ -1,19 +1,3 @@ -set_profile: - name: Set profile - description: Set the ventilation profile. - fields: - profile: - name: Profile - description: "Set profile." - required: true - selector: - select: - options: - - 'Away' - - 'Boost' - - 'Fireplace' - - 'Home' - set_profile_fan_speed_home: name: Set profile fan speed home description: Set the fan speed of the Home profile. From 44befe5f11390365e2ff0a7ce03133c1edd838a9 Mon Sep 17 00:00:00 2001 From: Felipe Santos Date: Thu, 17 Feb 2022 17:38:31 -0300 Subject: [PATCH 0753/1098] Fix Twilio webhook content type (#66561) --- homeassistant/components/twilio/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/twilio/__init__.py b/homeassistant/components/twilio/__init__.py index da063698bd3454..e71f3181b55474 100644 --- a/homeassistant/components/twilio/__init__.py +++ b/homeassistant/components/twilio/__init__.py @@ -1,6 +1,6 @@ """Support for Twilio.""" +from aiohttp import web from twilio.rest import Client -from twilio.twiml import TwiML import voluptuous as vol from homeassistant.components import webhook @@ -51,7 +51,7 @@ async def handle_webhook(hass, webhook_id, request): data["webhook_id"] = webhook_id hass.bus.async_fire(RECEIVED_DATA, dict(data)) - return TwiML().to_xml() + return web.Response(text="") async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: From 6d0a06c57af64e286da82c4fb52f89b45390fd83 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 17 Feb 2022 21:47:58 +0100 Subject: [PATCH 0754/1098] Add type hints in samsungtv tests (#66632) Co-authored-by: epenet --- tests/components/samsungtv/conftest.py | 15 +- .../components/samsungtv/test_config_flow.py | 150 +++++++++++------- tests/components/samsungtv/test_init.py | 24 +-- .../components/samsungtv/test_media_player.py | 115 +++++++++----- 4 files changed, 185 insertions(+), 119 deletions(-) diff --git a/tests/components/samsungtv/conftest.py b/tests/components/samsungtv/conftest.py index 14f33524f52865..de554462b42f40 100644 --- a/tests/components/samsungtv/conftest.py +++ b/tests/components/samsungtv/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Samsung TV.""" +from datetime import datetime from unittest.mock import Mock, patch import pytest @@ -19,7 +20,7 @@ def fake_host_fixture() -> None: @pytest.fixture(name="remote") -def remote_fixture(): +def remote_fixture() -> Mock: """Patch the samsungctl Remote.""" with patch("homeassistant.components.samsungtv.bridge.Remote") as remote_class: remote = Mock(Remote) @@ -30,7 +31,7 @@ def remote_fixture(): @pytest.fixture(name="remotews") -def remotews_fixture(): +def remotews_fixture() -> Mock: """Patch the samsungtvws SamsungTVWS.""" with patch( "homeassistant.components.samsungtv.bridge.SamsungTVWS" @@ -54,7 +55,7 @@ def remotews_fixture(): @pytest.fixture(name="remotews_no_device_info") -def remotews_no_device_info_fixture(): +def remotews_no_device_info_fixture() -> Mock: """Patch the samsungtvws SamsungTVWS.""" with patch( "homeassistant.components.samsungtv.bridge.SamsungTVWS" @@ -69,7 +70,7 @@ def remotews_no_device_info_fixture(): @pytest.fixture(name="remotews_soundbar") -def remotews_soundbar_fixture(): +def remotews_soundbar_fixture() -> Mock: """Patch the samsungtvws SamsungTVWS.""" with patch( "homeassistant.components.samsungtv.bridge.SamsungTVWS" @@ -93,7 +94,7 @@ def remotews_soundbar_fixture(): @pytest.fixture(name="delay") -def delay_fixture(): +def delay_fixture() -> Mock: """Patch the delay script function.""" with patch( "homeassistant.components.samsungtv.media_player.Script.async_run" @@ -102,13 +103,13 @@ def delay_fixture(): @pytest.fixture -def mock_now(): +def mock_now() -> datetime: """Fixture for dtutil.now.""" return dt_util.utcnow() @pytest.fixture(name="no_mac_address") -def mac_address_fixture(): +def mac_address_fixture() -> Mock: """Patch getmac.get_mac_address.""" with patch("getmac.get_mac_address", return_value=None) as mac: yield mac diff --git a/tests/components/samsungtv/test_config_flow.py b/tests/components/samsungtv/test_config_flow.py index 690e47ea816886..ae5c5be8082d48 100644 --- a/tests/components/samsungtv/test_config_flow.py +++ b/tests/components/samsungtv/test_config_flow.py @@ -2,6 +2,7 @@ import socket from unittest.mock import Mock, call, patch +import pytest from samsungctl.exceptions import AccessDenied, UnhandledResponse from samsungtvws import SamsungTVWS from samsungtvws.exceptions import ConnectionFailure, HttpApiError @@ -182,7 +183,8 @@ } -async def test_user_legacy(hass: HomeAssistant, remote: Mock): +@pytest.mark.usefixtures("remote") +async def test_user_legacy(hass: HomeAssistant) -> None: """Test starting a flow by user.""" # show form result = await hass.config_entries.flow.async_init( @@ -206,7 +208,8 @@ async def test_user_legacy(hass: HomeAssistant, remote: Mock): assert result["result"].unique_id is None -async def test_user_websocket(hass: HomeAssistant, remotews: Mock): +@pytest.mark.usefixtures("remotews") +async def test_user_websocket(hass: HomeAssistant) -> None: """Test starting a flow by user.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError("Boom") @@ -233,7 +236,8 @@ async def test_user_websocket(hass: HomeAssistant, remotews: Mock): assert result["result"].unique_id == "be9554b9-c9fb-41f4-8920-22da015376a4" -async def test_user_legacy_missing_auth(hass: HomeAssistant, remotews: Mock): +@pytest.mark.usefixtures("remotews") +async def test_user_legacy_missing_auth(hass: HomeAssistant) -> None: """Test starting a flow by user with authentication.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -247,7 +251,7 @@ async def test_user_legacy_missing_auth(hass: HomeAssistant, remotews: Mock): assert result["reason"] == RESULT_AUTH_MISSING -async def test_user_legacy_not_supported(hass: HomeAssistant): +async def test_user_legacy_not_supported(hass: HomeAssistant) -> None: """Test starting a flow by user for not supported device.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -261,7 +265,7 @@ async def test_user_legacy_not_supported(hass: HomeAssistant): assert result["reason"] == RESULT_NOT_SUPPORTED -async def test_user_websocket_not_supported(hass: HomeAssistant): +async def test_user_websocket_not_supported(hass: HomeAssistant) -> None: """Test starting a flow by user for not supported device.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -278,7 +282,7 @@ async def test_user_websocket_not_supported(hass: HomeAssistant): assert result["reason"] == RESULT_NOT_SUPPORTED -async def test_user_not_successful(hass: HomeAssistant): +async def test_user_not_successful(hass: HomeAssistant) -> None: """Test starting a flow by user but no connection found.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -294,7 +298,7 @@ async def test_user_not_successful(hass: HomeAssistant): assert result["reason"] == RESULT_CANNOT_CONNECT -async def test_user_not_successful_2(hass: HomeAssistant): +async def test_user_not_successful_2(hass: HomeAssistant) -> None: """Test starting a flow by user but no connection found.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -310,7 +314,8 @@ async def test_user_not_successful_2(hass: HomeAssistant): assert result["reason"] == RESULT_CANNOT_CONNECT -async def test_ssdp(hass: HomeAssistant, remote: Mock, no_mac_address: Mock): +@pytest.mark.usefixtures("remote") +async def test_ssdp(hass: HomeAssistant, no_mac_address: Mock) -> None: """Test starting a flow from discovery.""" no_mac_address.return_value = "aa:bb:cc:dd:ee:ff" @@ -338,7 +343,8 @@ async def test_ssdp(hass: HomeAssistant, remote: Mock, no_mac_address: Mock): assert result["result"].unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172de" -async def test_ssdp_noprefix(hass: HomeAssistant, remote: Mock, no_mac_address: Mock): +@pytest.mark.usefixtures("remote") +async def test_ssdp_noprefix(hass: HomeAssistant, no_mac_address: Mock) -> None: """Test starting a flow from discovery without prefixes.""" no_mac_address.return_value = "aa:bb:cc:dd:ee:ff" @@ -373,7 +379,8 @@ async def test_ssdp_noprefix(hass: HomeAssistant, remote: Mock, no_mac_address: assert result["result"].unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172df" -async def test_ssdp_legacy_missing_auth(hass: HomeAssistant, remotews: Mock): +@pytest.mark.usefixtures("remotews") +async def test_ssdp_legacy_missing_auth(hass: HomeAssistant) -> None: """Test starting a flow from discovery with authentication.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -400,9 +407,8 @@ async def test_ssdp_legacy_missing_auth(hass: HomeAssistant, remotews: Mock): assert result["reason"] == RESULT_AUTH_MISSING -async def test_ssdp_legacy_not_supported( - hass: HomeAssistant, remote: Mock, remotews: Mock -): +@pytest.mark.usefixtures("remote", "remotews") +async def test_ssdp_legacy_not_supported(hass: HomeAssistant) -> None: """Test starting a flow from discovery for not supported device.""" # confirm to add the entry @@ -424,11 +430,10 @@ async def test_ssdp_legacy_not_supported( assert result["reason"] == RESULT_NOT_SUPPORTED +@pytest.mark.usefixtures("remote", "remotews") async def test_ssdp_websocket_success_populates_mac_address( hass: HomeAssistant, - remote: Mock, - remotews: Mock, -): +) -> None: """Test starting a flow from ssdp for a supported device populates the mac.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_SSDP}, data=MOCK_SSDP_DATA @@ -449,7 +454,7 @@ async def test_ssdp_websocket_success_populates_mac_address( assert result["result"].unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172de" -async def test_ssdp_websocket_not_supported(hass: HomeAssistant): +async def test_ssdp_websocket_not_supported(hass: HomeAssistant) -> None: """Test starting a flow from discovery for not supported device.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -467,7 +472,8 @@ async def test_ssdp_websocket_not_supported(hass: HomeAssistant): assert result["reason"] == RESULT_NOT_SUPPORTED -async def test_ssdp_model_not_supported(hass: HomeAssistant, remote: Mock): +@pytest.mark.usefixtures("remote") +async def test_ssdp_model_not_supported(hass: HomeAssistant) -> None: """Test starting a flow from discovery.""" # confirm to add the entry @@ -480,7 +486,8 @@ async def test_ssdp_model_not_supported(hass: HomeAssistant, remote: Mock): assert result["reason"] == RESULT_NOT_SUPPORTED -async def test_ssdp_not_successful(hass: HomeAssistant, no_mac_address: Mock): +@pytest.mark.usefixtures("no_mac_address") +async def test_ssdp_not_successful(hass: HomeAssistant) -> None: """Test starting a flow from discovery but no device found.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -508,7 +515,8 @@ async def test_ssdp_not_successful(hass: HomeAssistant, no_mac_address: Mock): assert result["reason"] == RESULT_CANNOT_CONNECT -async def test_ssdp_not_successful_2(hass: HomeAssistant, no_mac_address: Mock): +@pytest.mark.usefixtures("no_mac_address") +async def test_ssdp_not_successful_2(hass: HomeAssistant) -> None: """Test starting a flow from discovery but no device found.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -536,9 +544,10 @@ async def test_ssdp_not_successful_2(hass: HomeAssistant, no_mac_address: Mock): assert result["reason"] == RESULT_CANNOT_CONNECT +@pytest.mark.usefixtures("remote") async def test_ssdp_already_in_progress( - hass: HomeAssistant, remote: Mock, no_mac_address: Mock -): + hass: HomeAssistant, no_mac_address: Mock +) -> None: """Test starting a flow from discovery twice.""" no_mac_address.return_value = "aa:bb:cc:dd:ee:ff" @@ -562,9 +571,10 @@ async def test_ssdp_already_in_progress( assert result["reason"] == RESULT_ALREADY_IN_PROGRESS +@pytest.mark.usefixtures("remote") async def test_ssdp_already_configured( - hass: HomeAssistant, remote: Mock, no_mac_address: Mock -): + hass: HomeAssistant, no_mac_address: Mock +) -> None: """Test starting a flow from discovery when already configured.""" no_mac_address.return_value = "aa:bb:cc:dd:ee:ff" @@ -594,7 +604,8 @@ async def test_ssdp_already_configured( assert entry.unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172de" -async def test_import_legacy(hass: HomeAssistant, remote: Mock, no_mac_address: Mock): +@pytest.mark.usefixtures("remote") +async def test_import_legacy(hass: HomeAssistant, no_mac_address: Mock) -> None: """Test importing from yaml with hostname.""" no_mac_address.return_value = "aa:bb:cc:dd:ee:ff" @@ -617,12 +628,8 @@ async def test_import_legacy(hass: HomeAssistant, remote: Mock, no_mac_address: assert entries[0].data[CONF_PORT] == LEGACY_PORT -async def test_import_legacy_without_name( - hass: HomeAssistant, - remote: Mock, - remotews_no_device_info: Mock, - no_mac_address: Mock, -): +@pytest.mark.usefixtures("remote", "remotews_no_device_info", "no_mac_address") +async def test_import_legacy_without_name(hass: HomeAssistant) -> None: """Test importing from yaml without a name.""" result = await hass.config_entries.flow.async_init( DOMAIN, @@ -642,7 +649,8 @@ async def test_import_legacy_without_name( assert entries[0].data[CONF_PORT] == LEGACY_PORT -async def test_import_websocket(hass: HomeAssistant, remotews: Mock): +@pytest.mark.usefixtures("remotews") +async def test_import_websocket(hass: HomeAssistant): """Test importing from yaml with hostname.""" result = await hass.config_entries.flow.async_init( DOMAIN, @@ -660,7 +668,8 @@ async def test_import_websocket(hass: HomeAssistant, remotews: Mock): assert result["result"].unique_id is None -async def test_import_websocket_without_port(hass: HomeAssistant, remotews: Mock): +@pytest.mark.usefixtures("remotews") +async def test_import_websocket_without_port(hass: HomeAssistant): """Test importing from yaml with hostname by no port.""" result = await hass.config_entries.flow.async_init( DOMAIN, @@ -681,7 +690,8 @@ async def test_import_websocket_without_port(hass: HomeAssistant, remotews: Mock assert entries[0].data[CONF_PORT] == 8002 -async def test_import_unknown_host(hass: HomeAssistant, remotews: Mock): +@pytest.mark.usefixtures("remotews") +async def test_import_unknown_host(hass: HomeAssistant): """Test importing from yaml with hostname that does not resolve.""" with patch( "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", @@ -697,7 +707,8 @@ async def test_import_unknown_host(hass: HomeAssistant, remotews: Mock): assert result["reason"] == RESULT_UNKNOWN_HOST -async def test_dhcp(hass: HomeAssistant, remote: Mock, remotews: Mock): +@pytest.mark.usefixtures("remote", "remotews") +async def test_dhcp(hass: HomeAssistant) -> None: """Test starting a flow from dhcp.""" # confirm to add the entry result = await hass.config_entries.flow.async_init( @@ -723,7 +734,8 @@ async def test_dhcp(hass: HomeAssistant, remote: Mock, remotews: Mock): assert result["result"].unique_id == "be9554b9-c9fb-41f4-8920-22da015376a4" -async def test_zeroconf(hass: HomeAssistant, remote: Mock, remotews: Mock): +@pytest.mark.usefixtures("remote", "remotews") +async def test_zeroconf(hass: HomeAssistant) -> None: """Test starting a flow from zeroconf.""" result = await hass.config_entries.flow.async_init( DOMAIN, @@ -748,7 +760,8 @@ async def test_zeroconf(hass: HomeAssistant, remote: Mock, remotews: Mock): assert result["result"].unique_id == "be9554b9-c9fb-41f4-8920-22da015376a4" -async def test_zeroconf_ignores_soundbar(hass: HomeAssistant, remotews_soundbar: Mock): +@pytest.mark.usefixtures("remotews_soundbar") +async def test_zeroconf_ignores_soundbar(hass: HomeAssistant) -> None: """Test starting a flow from zeroconf where the device is actually a soundbar.""" result = await hass.config_entries.flow.async_init( DOMAIN, @@ -760,9 +773,8 @@ async def test_zeroconf_ignores_soundbar(hass: HomeAssistant, remotews_soundbar: assert result["reason"] == "not_supported" -async def test_zeroconf_no_device_info( - hass: HomeAssistant, remote: Mock, remotews_no_device_info: Mock -): +@pytest.mark.usefixtures("remote", "remotews_no_device_info") +async def test_zeroconf_no_device_info(hass: HomeAssistant) -> None: """Test starting a flow from zeroconf where device_info returns None.""" result = await hass.config_entries.flow.async_init( DOMAIN, @@ -774,7 +786,8 @@ async def test_zeroconf_no_device_info( assert result["reason"] == "not_supported" -async def test_zeroconf_and_dhcp_same_time(hass: HomeAssistant, remotews: Mock): +@pytest.mark.usefixtures("remotews") +async def test_zeroconf_and_dhcp_same_time(hass: HomeAssistant) -> None: """Test starting a flow from zeroconf and dhcp.""" result = await hass.config_entries.flow.async_init( DOMAIN, @@ -795,7 +808,7 @@ async def test_zeroconf_and_dhcp_same_time(hass: HomeAssistant, remotews: Mock): assert result2["reason"] == "already_in_progress" -async def test_autodetect_websocket(hass: HomeAssistant): +async def test_autodetect_websocket(hass: HomeAssistant) -> None: """Test for send key with autodetection of protocol.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -837,7 +850,7 @@ async def test_autodetect_websocket(hass: HomeAssistant): assert entries[0].data[CONF_MAC] == "aa:bb:cc:dd:ee:ff" -async def test_websocket_no_mac(hass: HomeAssistant): +async def test_websocket_no_mac(hass: HomeAssistant) -> None: """Test for send key with autodetection of protocol.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -882,7 +895,7 @@ async def test_websocket_no_mac(hass: HomeAssistant): assert entries[0].data[CONF_MAC] == "gg:hh:ii:ll:mm:nn" -async def test_autodetect_auth_missing(hass: HomeAssistant): +async def test_autodetect_auth_missing(hass: HomeAssistant) -> None: """Test for send key with autodetection of protocol.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -897,7 +910,7 @@ async def test_autodetect_auth_missing(hass: HomeAssistant): assert remote.call_args_list == [call(AUTODETECT_LEGACY)] -async def test_autodetect_not_supported(hass: HomeAssistant): +async def test_autodetect_not_supported(hass: HomeAssistant) -> None: """Test for send key with autodetection of protocol.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -912,7 +925,8 @@ async def test_autodetect_not_supported(hass: HomeAssistant): assert remote.call_args_list == [call(AUTODETECT_LEGACY)] -async def test_autodetect_legacy(hass: HomeAssistant, remote: Mock): +@pytest.mark.usefixtures("remote") +async def test_autodetect_legacy(hass: HomeAssistant) -> None: """Test for send key with autodetection of protocol.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA @@ -924,7 +938,7 @@ async def test_autodetect_legacy(hass: HomeAssistant, remote: Mock): assert result["data"][CONF_PORT] == LEGACY_PORT -async def test_autodetect_none(hass: HomeAssistant): +async def test_autodetect_none(hass: HomeAssistant) -> None: """Test for send key with autodetection of protocol.""" mock_remotews = Mock() mock_remotews.__enter__ = Mock(return_value=mock_remotews) @@ -954,7 +968,8 @@ async def test_autodetect_none(hass: HomeAssistant): ] -async def test_update_old_entry(hass: HomeAssistant, remotews: Mock): +@pytest.mark.usefixtures("remotews") +async def test_update_old_entry(hass: HomeAssistant) -> None: """Test update of old entry.""" with patch("homeassistant.components.samsungtv.bridge.Remote") as remote: remote().rest_device_info.return_value = { @@ -995,7 +1010,10 @@ async def test_update_old_entry(hass: HomeAssistant, remotews: Mock): assert entry2.unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172de" -async def test_update_missing_mac_unique_id_added_from_dhcp(hass, remotews: Mock): +@pytest.mark.usefixtures("remotews") +async def test_update_missing_mac_unique_id_added_from_dhcp( + hass: HomeAssistant, +) -> None: """Test missing mac and unique id added.""" entry = MockConfigEntry(domain=DOMAIN, data=MOCK_OLD_ENTRY, unique_id=None) entry.add_to_hass(hass) @@ -1021,7 +1039,10 @@ async def test_update_missing_mac_unique_id_added_from_dhcp(hass, remotews: Mock assert entry.unique_id == "be9554b9-c9fb-41f4-8920-22da015376a4" -async def test_update_missing_mac_unique_id_added_from_zeroconf(hass, remotews: Mock): +@pytest.mark.usefixtures("remotews") +async def test_update_missing_mac_unique_id_added_from_zeroconf( + hass: HomeAssistant, +) -> None: """Test missing mac and unique id added.""" entry = MockConfigEntry(domain=DOMAIN, data=MOCK_OLD_ENTRY, unique_id=None) entry.add_to_hass(hass) @@ -1046,7 +1067,10 @@ async def test_update_missing_mac_unique_id_added_from_zeroconf(hass, remotews: assert entry.unique_id == "be9554b9-c9fb-41f4-8920-22da015376a4" -async def test_update_missing_mac_unique_id_added_from_ssdp(hass, remotews: Mock): +@pytest.mark.usefixtures("remotews") +async def test_update_missing_mac_unique_id_added_from_ssdp( + hass: HomeAssistant, +) -> None: """Test missing mac and unique id added via ssdp.""" entry = MockConfigEntry(domain=DOMAIN, data=MOCK_OLD_ENTRY, unique_id=None) entry.add_to_hass(hass) @@ -1072,9 +1096,10 @@ async def test_update_missing_mac_unique_id_added_from_ssdp(hass, remotews: Mock assert entry.unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172de" +@pytest.mark.usefixtures("remotews") async def test_update_missing_mac_added_unique_id_preserved_from_zeroconf( - hass, remotews: Mock -): + hass: HomeAssistant, +) -> None: """Test missing mac and unique id added.""" entry = MockConfigEntry( domain=DOMAIN, @@ -1103,7 +1128,8 @@ async def test_update_missing_mac_added_unique_id_preserved_from_zeroconf( assert entry.unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172de" -async def test_update_legacy_missing_mac_from_dhcp(hass, remote: Mock): +@pytest.mark.usefixtures("remote") +async def test_update_legacy_missing_mac_from_dhcp(hass: HomeAssistant) -> None: """Test missing mac added.""" entry = MockConfigEntry( domain=DOMAIN, @@ -1134,7 +1160,10 @@ async def test_update_legacy_missing_mac_from_dhcp(hass, remote: Mock): assert entry.unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172de" -async def test_update_legacy_missing_mac_from_dhcp_no_unique_id(hass, remote: Mock): +@pytest.mark.usefixtures("remote") +async def test_update_legacy_missing_mac_from_dhcp_no_unique_id( + hass: HomeAssistant, +) -> None: """Test missing mac added when there is no unique id.""" entry = MockConfigEntry( domain=DOMAIN, @@ -1170,7 +1199,8 @@ async def test_update_legacy_missing_mac_from_dhcp_no_unique_id(hass, remote: Mo assert entry.unique_id is None -async def test_form_reauth_legacy(hass, remote: Mock): +@pytest.mark.usefixtures("remote") +async def test_form_reauth_legacy(hass: HomeAssistant) -> None: """Test reauthenticate legacy.""" entry = MockConfigEntry(domain=DOMAIN, data=MOCK_OLD_ENTRY) entry.add_to_hass(hass) @@ -1191,7 +1221,8 @@ async def test_form_reauth_legacy(hass, remote: Mock): assert result2["reason"] == "reauth_successful" -async def test_form_reauth_websocket(hass, remotews: Mock): +@pytest.mark.usefixtures("remotews") +async def test_form_reauth_websocket(hass: HomeAssistant) -> None: """Test reauthenticate websocket.""" entry = MockConfigEntry(domain=DOMAIN, data=MOCK_WS_ENTRY) entry.add_to_hass(hass) @@ -1215,7 +1246,8 @@ async def test_form_reauth_websocket(hass, remotews: Mock): assert entry.state == config_entries.ConfigEntryState.LOADED -async def test_form_reauth_websocket_cannot_connect(hass, remotews: Mock): +@pytest.mark.usefixtures("remotews") +async def test_form_reauth_websocket_cannot_connect(hass: HomeAssistant) -> None: """Test reauthenticate websocket when we cannot connect on the first attempt.""" entry = MockConfigEntry(domain=DOMAIN, data=MOCK_WS_ENTRY) entry.add_to_hass(hass) @@ -1247,7 +1279,7 @@ async def test_form_reauth_websocket_cannot_connect(hass, remotews: Mock): assert result3["reason"] == "reauth_successful" -async def test_form_reauth_websocket_not_supported(hass): +async def test_form_reauth_websocket_not_supported(hass: HomeAssistant) -> None: """Test reauthenticate websocket when the device is not supported.""" entry = MockConfigEntry(domain=DOMAIN, data=MOCK_WS_ENTRY) entry.add_to_hass(hass) diff --git a/tests/components/samsungtv/test_init.py b/tests/components/samsungtv/test_init.py index e49b8fdc5ee97d..12419b21fe8fdc 100644 --- a/tests/components/samsungtv/test_init.py +++ b/tests/components/samsungtv/test_init.py @@ -1,5 +1,7 @@ """Tests for the Samsung TV Integration.""" -from unittest.mock import Mock, patch +from unittest.mock import patch + +import pytest from homeassistant.components.media_player.const import DOMAIN, SUPPORT_TURN_ON from homeassistant.components.samsungtv.const import ( @@ -53,7 +55,8 @@ } -async def test_setup(hass: HomeAssistant, remotews: Mock, no_mac_address: Mock): +@pytest.mark.usefixtures("remotews", "no_mac_address") +async def test_setup(hass: HomeAssistant) -> None: """Test Samsung TV integration is setup.""" await async_setup_component(hass, SAMSUNGTV_DOMAIN, MOCK_CONFIG) await hass.async_block_till_done() @@ -72,7 +75,7 @@ async def test_setup(hass: HomeAssistant, remotews: Mock, no_mac_address: Mock): ) -async def test_setup_from_yaml_without_port_device_offline(hass: HomeAssistant): +async def test_setup_from_yaml_without_port_device_offline(hass: HomeAssistant) -> None: """Test import from yaml when the device is offline.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError @@ -91,9 +94,8 @@ async def test_setup_from_yaml_without_port_device_offline(hass: HomeAssistant): assert config_entries_domain[0].state == ConfigEntryState.SETUP_RETRY -async def test_setup_from_yaml_without_port_device_online( - hass: HomeAssistant, remotews: Mock -): +@pytest.mark.usefixtures("remotews") +async def test_setup_from_yaml_without_port_device_online(hass: HomeAssistant) -> None: """Test import from yaml when the device is online.""" await async_setup_component(hass, SAMSUNGTV_DOMAIN, MOCK_CONFIG) await hass.async_block_till_done() @@ -103,7 +105,10 @@ async def test_setup_from_yaml_without_port_device_online( assert config_entries_domain[0].data[CONF_MAC] == "aa:bb:cc:dd:ee:ff" -async def test_setup_duplicate_config(hass: HomeAssistant, remote: Mock, caplog): +@pytest.mark.usefixtures("remote") +async def test_setup_duplicate_config( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: """Test duplicate setup of platform.""" duplicate = { SAMSUNGTV_DOMAIN: [ @@ -118,9 +123,8 @@ async def test_setup_duplicate_config(hass: HomeAssistant, remote: Mock, caplog) assert "duplicate host entries found" in caplog.text -async def test_setup_duplicate_entries( - hass: HomeAssistant, remote: Mock, remotews: Mock, no_mac_address: Mock -): +@pytest.mark.usefixtures("remote", "remotews", "no_mac_address") +async def test_setup_duplicate_entries(hass: HomeAssistant) -> None: """Test duplicate setup of platform.""" await async_setup_component(hass, SAMSUNGTV_DOMAIN, MOCK_CONFIG) await hass.async_block_till_done() diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py index ed09d9e33e0657..fe270fed8de6bc 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -1,6 +1,6 @@ """Tests for samsungtv component.""" import asyncio -from datetime import timedelta +from datetime import datetime, timedelta import logging from unittest.mock import DEFAULT as DEFAULT_MOCK, Mock, call, patch @@ -56,6 +56,8 @@ STATE_ON, STATE_UNAVAILABLE, ) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.typing import ConfigType from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -131,25 +133,28 @@ def delay_fixture(): yield delay -async def setup_samsungtv(hass, config): +async def setup_samsungtv(hass: HomeAssistant, config: ConfigType) -> None: """Set up mock Samsung TV.""" await async_setup_component(hass, SAMSUNGTV_DOMAIN, config) await hass.async_block_till_done() -async def test_setup_with_turnon(hass, remote): +@pytest.mark.usefixtures("remote") +async def test_setup_with_turnon(hass: HomeAssistant) -> None: """Test setup of platform.""" await setup_samsungtv(hass, MOCK_CONFIG) assert hass.states.get(ENTITY_ID) -async def test_setup_without_turnon(hass, remote): +@pytest.mark.usefixtures("remote") +async def test_setup_without_turnon(hass: HomeAssistant) -> None: """Test setup of platform.""" await setup_samsungtv(hass, MOCK_CONFIG_NOTURNON) assert hass.states.get(ENTITY_ID_NOTURNON) -async def test_setup_websocket(hass, remotews): +@pytest.mark.usefixtures("remotews") +async def test_setup_websocket(hass: HomeAssistant) -> None: """Test setup of platform.""" with patch("homeassistant.components.samsungtv.bridge.SamsungTVWS") as remote_class: remote = Mock(SamsungTVWS) @@ -184,7 +189,7 @@ async def test_setup_websocket(hass, remotews): assert config_entries[0].data[CONF_MAC] == "aa:bb:cc:dd:ee:ff" -async def test_setup_websocket_2(hass, mock_now): +async def test_setup_websocket_2(hass: HomeAssistant, mock_now: datetime) -> None: """Test setup of platform from config entry.""" entity_id = f"{DOMAIN}.fake" @@ -231,7 +236,8 @@ async def test_setup_websocket_2(hass, mock_now): assert remote_class.call_args_list[0] == call(**MOCK_CALLS_WS) -async def test_update_on(hass, remote, mock_now): +@pytest.mark.usefixtures("remote") +async def test_update_on(hass: HomeAssistant, mock_now: datetime) -> None: """Testing update tv on.""" await setup_samsungtv(hass, MOCK_CONFIG) @@ -244,7 +250,8 @@ async def test_update_on(hass, remote, mock_now): assert state.state == STATE_ON -async def test_update_off(hass, remote, mock_now): +@pytest.mark.usefixtures("remote") +async def test_update_off(hass: HomeAssistant, mock_now: datetime) -> None: """Testing update tv off.""" await setup_samsungtv(hass, MOCK_CONFIG) @@ -262,7 +269,8 @@ async def test_update_off(hass, remote, mock_now): assert state.state == STATE_OFF -async def test_update_access_denied(hass, remote, mock_now): +@pytest.mark.usefixtures("remote") +async def test_update_access_denied(hass: HomeAssistant, mock_now: datetime) -> None: """Testing update tv access denied exception.""" await setup_samsungtv(hass, MOCK_CONFIG) @@ -288,7 +296,10 @@ async def test_update_access_denied(hass, remote, mock_now): assert state.state == STATE_UNAVAILABLE -async def test_update_connection_failure(hass, remotews, mock_now): +@pytest.mark.usefixtures("remotews") +async def test_update_connection_failure( + hass: HomeAssistant, mock_now: datetime +) -> None: """Testing update tv connection failure exception.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -315,7 +326,10 @@ async def test_update_connection_failure(hass, remotews, mock_now): assert state.state == STATE_UNAVAILABLE -async def test_update_unhandled_response(hass, remote, mock_now): +@pytest.mark.usefixtures("remote") +async def test_update_unhandled_response( + hass: HomeAssistant, mock_now: datetime +) -> None: """Testing update tv unhandled response exception.""" await setup_samsungtv(hass, MOCK_CONFIG) @@ -333,7 +347,10 @@ async def test_update_unhandled_response(hass, remote, mock_now): assert state.state == STATE_ON -async def test_connection_closed_during_update_can_recover(hass, remote, mock_now): +@pytest.mark.usefixtures("remote") +async def test_connection_closed_during_update_can_recover( + hass: HomeAssistant, mock_now: datetime +) -> None: """Testing update tv connection closed exception can recover.""" await setup_samsungtv(hass, MOCK_CONFIG) @@ -359,7 +376,7 @@ async def test_connection_closed_during_update_can_recover(hass, remote, mock_no assert state.state == STATE_ON -async def test_send_key(hass, remote): +async def test_send_key(hass: HomeAssistant, remote: Mock) -> None: """Test for send key.""" await setup_samsungtv(hass, MOCK_CONFIG) assert await hass.services.async_call( @@ -374,7 +391,7 @@ async def test_send_key(hass, remote): assert state.state == STATE_ON -async def test_send_key_broken_pipe(hass, remote): +async def test_send_key_broken_pipe(hass: HomeAssistant, remote: Mock) -> None: """Testing broken pipe Exception.""" await setup_samsungtv(hass, MOCK_CONFIG) remote.control = Mock(side_effect=BrokenPipeError("Boom")) @@ -385,7 +402,9 @@ async def test_send_key_broken_pipe(hass, remote): assert state.state == STATE_ON -async def test_send_key_connection_closed_retry_succeed(hass, remote): +async def test_send_key_connection_closed_retry_succeed( + hass: HomeAssistant, remote: Mock +) -> None: """Test retry on connection closed.""" await setup_samsungtv(hass, MOCK_CONFIG) remote.control = Mock( @@ -406,7 +425,7 @@ async def test_send_key_connection_closed_retry_succeed(hass, remote): assert state.state == STATE_ON -async def test_send_key_unhandled_response(hass, remote): +async def test_send_key_unhandled_response(hass: HomeAssistant, remote: Mock) -> None: """Testing unhandled response exception.""" await setup_samsungtv(hass, MOCK_CONFIG) remote.control = Mock(side_effect=exceptions.UnhandledResponse("Boom")) @@ -417,7 +436,7 @@ async def test_send_key_unhandled_response(hass, remote): assert state.state == STATE_ON -async def test_send_key_websocketexception(hass, remote): +async def test_send_key_websocketexception(hass: HomeAssistant, remote: Mock) -> None: """Testing unhandled response exception.""" await setup_samsungtv(hass, MOCK_CONFIG) remote.control = Mock(side_effect=WebSocketException("Boom")) @@ -428,7 +447,7 @@ async def test_send_key_websocketexception(hass, remote): assert state.state == STATE_ON -async def test_send_key_os_error(hass, remote): +async def test_send_key_os_error(hass: HomeAssistant, remote: Mock) -> None: """Testing broken pipe Exception.""" await setup_samsungtv(hass, MOCK_CONFIG) remote.control = Mock(side_effect=OSError("Boom")) @@ -439,14 +458,16 @@ async def test_send_key_os_error(hass, remote): assert state.state == STATE_ON -async def test_name(hass, remote): +@pytest.mark.usefixtures("remote") +async def test_name(hass: HomeAssistant) -> None: """Test for name property.""" await setup_samsungtv(hass, MOCK_CONFIG) state = hass.states.get(ENTITY_ID) assert state.attributes[ATTR_FRIENDLY_NAME] == "fake" -async def test_state_with_turnon(hass, remote, delay): +@pytest.mark.usefixtures("remote") +async def test_state_with_turnon(hass: HomeAssistant, delay: Mock) -> None: """Test for state property.""" await setup_samsungtv(hass, MOCK_CONFIG) assert await hass.services.async_call( @@ -463,7 +484,8 @@ async def test_state_with_turnon(hass, remote, delay): assert state.state == STATE_OFF -async def test_state_without_turnon(hass, remote): +@pytest.mark.usefixtures("remote") +async def test_state_without_turnon(hass: HomeAssistant) -> None: """Test for state property.""" await setup_samsungtv(hass, MOCK_CONFIG_NOTURNON) assert await hass.services.async_call( @@ -491,7 +513,8 @@ async def test_state_without_turnon(hass, remote): assert state.state == STATE_UNAVAILABLE -async def test_supported_features_with_turnon(hass, remote): +@pytest.mark.usefixtures("remote") +async def test_supported_features_with_turnon(hass: HomeAssistant) -> None: """Test for supported_features property.""" await setup_samsungtv(hass, MOCK_CONFIG) state = hass.states.get(ENTITY_ID) @@ -500,21 +523,23 @@ async def test_supported_features_with_turnon(hass, remote): ) -async def test_supported_features_without_turnon(hass, remote): +@pytest.mark.usefixtures("remote") +async def test_supported_features_without_turnon(hass: HomeAssistant) -> None: """Test for supported_features property.""" await setup_samsungtv(hass, MOCK_CONFIG_NOTURNON) state = hass.states.get(ENTITY_ID_NOTURNON) assert state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_SAMSUNGTV -async def test_device_class(hass, remote): +@pytest.mark.usefixtures("remote") +async def test_device_class(hass: HomeAssistant) -> None: """Test for device_class property.""" await setup_samsungtv(hass, MOCK_CONFIG) state = hass.states.get(ENTITY_ID) assert state.attributes[ATTR_DEVICE_CLASS] is MediaPlayerDeviceClass.TV.value -async def test_turn_off_websocket(hass, remotews): +async def test_turn_off_websocket(hass: HomeAssistant, remotews: Mock) -> None: """Test for turn_off.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -529,7 +554,7 @@ async def test_turn_off_websocket(hass, remotews): assert remotews.send_key.call_args_list == [call("KEY_POWER")] -async def test_turn_off_legacy(hass, remote): +async def test_turn_off_legacy(hass: HomeAssistant, remote: Mock) -> None: """Test for turn_off.""" await setup_samsungtv(hass, MOCK_CONFIG_NOTURNON) assert await hass.services.async_call( @@ -540,7 +565,9 @@ async def test_turn_off_legacy(hass, remote): assert remote.control.call_args_list == [call("KEY_POWEROFF")] -async def test_turn_off_os_error(hass, remote, caplog): +async def test_turn_off_os_error( + hass: HomeAssistant, remote: Mock, caplog: pytest.LogCaptureFixture +) -> None: """Test for turn_off with OSError.""" caplog.set_level(logging.DEBUG) await setup_samsungtv(hass, MOCK_CONFIG) @@ -551,7 +578,7 @@ async def test_turn_off_os_error(hass, remote, caplog): assert "Could not establish connection" in caplog.text -async def test_volume_up(hass, remote): +async def test_volume_up(hass: HomeAssistant, remote: Mock) -> None: """Test for volume_up.""" await setup_samsungtv(hass, MOCK_CONFIG) assert await hass.services.async_call( @@ -564,7 +591,7 @@ async def test_volume_up(hass, remote): assert remote.close.call_args_list == [call()] -async def test_volume_down(hass, remote): +async def test_volume_down(hass: HomeAssistant, remote: Mock) -> None: """Test for volume_down.""" await setup_samsungtv(hass, MOCK_CONFIG) assert await hass.services.async_call( @@ -577,7 +604,7 @@ async def test_volume_down(hass, remote): assert remote.close.call_args_list == [call()] -async def test_mute_volume(hass, remote): +async def test_mute_volume(hass: HomeAssistant, remote: Mock) -> None: """Test for mute_volume.""" await setup_samsungtv(hass, MOCK_CONFIG) assert await hass.services.async_call( @@ -593,7 +620,7 @@ async def test_mute_volume(hass, remote): assert remote.close.call_args_list == [call()] -async def test_media_play(hass, remote): +async def test_media_play(hass: HomeAssistant, remote: Mock) -> None: """Test for media_play.""" await setup_samsungtv(hass, MOCK_CONFIG) assert await hass.services.async_call( @@ -615,7 +642,7 @@ async def test_media_play(hass, remote): assert remote.close.call_args_list == [call(), call()] -async def test_media_pause(hass, remote): +async def test_media_pause(hass: HomeAssistant, remote: Mock) -> None: """Test for media_pause.""" await setup_samsungtv(hass, MOCK_CONFIG) assert await hass.services.async_call( @@ -637,7 +664,7 @@ async def test_media_pause(hass, remote): assert remote.close.call_args_list == [call(), call()] -async def test_media_next_track(hass, remote): +async def test_media_next_track(hass: HomeAssistant, remote: Mock) -> None: """Test for media_next_track.""" await setup_samsungtv(hass, MOCK_CONFIG) assert await hass.services.async_call( @@ -650,7 +677,7 @@ async def test_media_next_track(hass, remote): assert remote.close.call_args_list == [call()] -async def test_media_previous_track(hass, remote): +async def test_media_previous_track(hass: HomeAssistant, remote: Mock) -> None: """Test for media_previous_track.""" await setup_samsungtv(hass, MOCK_CONFIG) assert await hass.services.async_call( @@ -663,7 +690,8 @@ async def test_media_previous_track(hass, remote): assert remote.close.call_args_list == [call()] -async def test_turn_on_with_turnon(hass, remote, delay): +@pytest.mark.usefixtures("remote") +async def test_turn_on_with_turnon(hass: HomeAssistant, delay: Mock) -> None: """Test turn on.""" await setup_samsungtv(hass, MOCK_CONFIG) assert await hass.services.async_call( @@ -672,7 +700,8 @@ async def test_turn_on_with_turnon(hass, remote, delay): assert delay.call_count == 1 -async def test_turn_on_wol(hass, remotews): +@pytest.mark.usefixtures("remotews") +async def test_turn_on_wol(hass: HomeAssistant) -> None: """Test turn on.""" entry = MockConfigEntry( domain=SAMSUNGTV_DOMAIN, @@ -692,7 +721,7 @@ async def test_turn_on_wol(hass, remotews): assert mock_send_magic_packet.called -async def test_turn_on_without_turnon(hass, remote): +async def test_turn_on_without_turnon(hass: HomeAssistant, remote: Mock) -> None: """Test turn on.""" await setup_samsungtv(hass, MOCK_CONFIG_NOTURNON) assert await hass.services.async_call( @@ -702,7 +731,7 @@ async def test_turn_on_without_turnon(hass, remote): assert remote.control.call_count == 0 -async def test_play_media(hass, remote): +async def test_play_media(hass: HomeAssistant, remote: Mock) -> None: """Test for play_media.""" asyncio_sleep = asyncio.sleep sleeps = [] @@ -736,7 +765,7 @@ async def sleep(duration): assert len(sleeps) == 3 -async def test_play_media_invalid_type(hass): +async def test_play_media_invalid_type(hass: HomeAssistant) -> None: """Test for play_media with invalid media type.""" with patch("homeassistant.components.samsungtv.bridge.Remote") as remote: url = "https://example.com" @@ -758,7 +787,7 @@ async def test_play_media_invalid_type(hass): assert remote.call_count == 1 -async def test_play_media_channel_as_string(hass): +async def test_play_media_channel_as_string(hass: HomeAssistant) -> None: """Test for play_media with invalid channel as string.""" with patch("homeassistant.components.samsungtv.bridge.Remote") as remote: url = "https://example.com" @@ -780,7 +809,7 @@ async def test_play_media_channel_as_string(hass): assert remote.call_count == 1 -async def test_play_media_channel_as_non_positive(hass): +async def test_play_media_channel_as_non_positive(hass: HomeAssistant) -> None: """Test for play_media with invalid channel as non positive integer.""" with patch("homeassistant.components.samsungtv.bridge.Remote") as remote: await setup_samsungtv(hass, MOCK_CONFIG) @@ -801,7 +830,7 @@ async def test_play_media_channel_as_non_positive(hass): assert remote.call_count == 1 -async def test_select_source(hass, remote): +async def test_select_source(hass: HomeAssistant, remote: Mock) -> None: """Test for select_source.""" await setup_samsungtv(hass, MOCK_CONFIG) assert await hass.services.async_call( @@ -817,7 +846,7 @@ async def test_select_source(hass, remote): assert remote.close.call_args_list == [call()] -async def test_select_source_invalid_source(hass): +async def test_select_source_invalid_source(hass: HomeAssistant) -> None: """Test for select_source with invalid source.""" with patch("homeassistant.components.samsungtv.bridge.Remote") as remote: await setup_samsungtv(hass, MOCK_CONFIG) From 92ce25529356e472075e786e4a5d9596b45d1924 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 17 Feb 2022 21:49:01 +0100 Subject: [PATCH 0755/1098] Ensure new samsungtv token is updated in the config_entry (#66731) Co-authored-by: epenet --- .../components/samsungtv/__init__.py | 14 +++++++++++ homeassistant/components/samsungtv/bridge.py | 25 +++++++++++++------ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/samsungtv/__init__.py b/homeassistant/components/samsungtv/__init__.py index 212ef6c23cac20..515e5c0de96b09 100644 --- a/homeassistant/components/samsungtv/__init__.py +++ b/homeassistant/components/samsungtv/__init__.py @@ -24,6 +24,7 @@ from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType +from homeassistant.util.async_ import run_callback_threadsafe from .bridge import ( SamsungTVBridge, @@ -117,6 +118,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # Initialize bridge bridge = await _async_create_bridge_with_updated_data(hass, entry) + # Ensure new token gets saved against the config_entry + def _update_token() -> None: + """Update config entry with the new token.""" + hass.config_entries.async_update_entry( + entry, data={**entry.data, CONF_TOKEN: bridge.token} + ) + + def new_token_callback() -> None: + """Update config entry with the new token.""" + run_callback_threadsafe(hass.loop, _update_token) + + bridge.register_new_token_callback(new_token_callback) + def stop_bridge(event: Event) -> None: """Stop SamsungTV bridge connection.""" bridge.stop() diff --git a/homeassistant/components/samsungtv/bridge.py b/homeassistant/components/samsungtv/bridge.py index ed520acc1f7d5f..37a725ee5c9335 100644 --- a/homeassistant/components/samsungtv/bridge.py +++ b/homeassistant/components/samsungtv/bridge.py @@ -98,11 +98,16 @@ def __init__(self, method: str, host: str, port: int | None = None) -> None: self.host = host self.token: str | None = None self._remote: Remote | None = None - self._callback: CALLBACK_TYPE | None = None + self._reauth_callback: CALLBACK_TYPE | None = None + self._new_token_callback: CALLBACK_TYPE | None = None def register_reauth_callback(self, func: CALLBACK_TYPE) -> None: """Register a callback function.""" - self._callback = func + self._reauth_callback = func + + def register_new_token_callback(self, func: CALLBACK_TYPE) -> None: + """Register a callback function.""" + self._new_token_callback = func @abstractmethod def try_connect(self) -> str | None: @@ -176,10 +181,15 @@ def close_remote(self) -> None: except OSError: LOGGER.debug("Could not establish connection") - def _notify_callback(self) -> None: + def _notify_reauth_callback(self) -> None: """Notify access denied callback.""" - if self._callback is not None: - self._callback() + if self._reauth_callback is not None: + self._reauth_callback() + + def _notify_new_token_callback(self) -> None: + """Notify new token callback.""" + if self._new_token_callback is not None: + self._new_token_callback() class SamsungTVLegacyBridge(SamsungTVBridge): @@ -245,7 +255,7 @@ def _get_remote(self, avoid_open: bool = False) -> Remote: # This is only happening when the auth was switched to DENY # A removed auth will lead to socket timeout because waiting for auth popup is just an open socket except AccessDenied: - self._notify_callback() + self._notify_reauth_callback() raise except (ConnectionClosed, OSError): pass @@ -355,7 +365,7 @@ def _get_remote(self, avoid_open: bool = False) -> Remote: # This is only happening when the auth was switched to DENY # A removed auth will lead to socket timeout because waiting for auth popup is just an open socket except ConnectionFailure: - self._notify_callback() + self._notify_reauth_callback() except (WebSocketException, OSError): self._remote = None else: @@ -365,6 +375,7 @@ def _get_remote(self, avoid_open: bool = False) -> Remote: self._remote.token, ) self.token = self._remote.token + self._notify_new_token_callback() return self._remote def stop(self) -> None: From d7619d2302963afeb1bfd70991299ecd3f5ffec5 Mon Sep 17 00:00:00 2001 From: Tom Harris Date: Thu, 17 Feb 2022 15:52:06 -0500 Subject: [PATCH 0756/1098] Bump pyinsteon to 1.0.16 (#66759) --- homeassistant/components/insteon/manifest.json | 9 ++++++--- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/insteon/manifest.json b/homeassistant/components/insteon/manifest.json index 595afd061cc16a..7abff39113ba54 100644 --- a/homeassistant/components/insteon/manifest.json +++ b/homeassistant/components/insteon/manifest.json @@ -3,12 +3,15 @@ "name": "Insteon", "documentation": "https://www.home-assistant.io/integrations/insteon", "requirements": [ - "pyinsteon==1.0.14" + "pyinsteon==1.0.16" ], "codeowners": [ "@teharris1" ], "config_flow": true, "iot_class": "local_push", - "loggers": ["pyinsteon", "pypubsub"] -} + "loggers": [ + "pyinsteon", + "pypubsub" + ] +} \ No newline at end of file diff --git a/requirements_all.txt b/requirements_all.txt index 7fdf253ccc06ef..3f343d5d23f7f5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1579,7 +1579,7 @@ pyialarm==1.9.0 pyicloud==0.10.2 # homeassistant.components.insteon -pyinsteon==1.0.14 +pyinsteon==1.0.16 # homeassistant.components.intesishome pyintesishome==1.7.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d32f2192f5ed03..faeba577532307 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -993,7 +993,7 @@ pyialarm==1.9.0 pyicloud==0.10.2 # homeassistant.components.insteon -pyinsteon==1.0.14 +pyinsteon==1.0.16 # homeassistant.components.ipma pyipma==2.0.5 From 8bf19655f1003eac89c8af47d6b02dd0d9e3c787 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 17 Feb 2022 22:04:48 +0100 Subject: [PATCH 0757/1098] Fix samsung mocks (#66765) Co-authored-by: epenet --- tests/components/samsungtv/test_config_flow.py | 5 +++-- tests/components/samsungtv/test_media_player.py | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/components/samsungtv/test_config_flow.py b/tests/components/samsungtv/test_config_flow.py index ae5c5be8082d48..bf1587e40dcb28 100644 --- a/tests/components/samsungtv/test_config_flow.py +++ b/tests/components/samsungtv/test_config_flow.py @@ -1246,8 +1246,9 @@ async def test_form_reauth_websocket(hass: HomeAssistant) -> None: assert entry.state == config_entries.ConfigEntryState.LOADED -@pytest.mark.usefixtures("remotews") -async def test_form_reauth_websocket_cannot_connect(hass: HomeAssistant) -> None: +async def test_form_reauth_websocket_cannot_connect( + hass: HomeAssistant, remotews: Mock +) -> None: """Test reauthenticate websocket when we cannot connect on the first attempt.""" entry = MockConfigEntry(domain=DOMAIN, data=MOCK_WS_ENTRY) entry.add_to_hass(hass) diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py index fe270fed8de6bc..76479dea836e1c 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -296,9 +296,8 @@ async def test_update_access_denied(hass: HomeAssistant, mock_now: datetime) -> assert state.state == STATE_UNAVAILABLE -@pytest.mark.usefixtures("remotews") async def test_update_connection_failure( - hass: HomeAssistant, mock_now: datetime + hass: HomeAssistant, mock_now: datetime, remotews: Mock ) -> None: """Testing update tv connection failure exception.""" with patch( From 1a247f7d1b958b5264e5b40d40483e99fb9bd76c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 17 Feb 2022 22:08:43 +0100 Subject: [PATCH 0758/1098] Improve `device_automation` typing (#66621) --- .../components/device_automation/__init__.py | 126 +++++++++++++++++- .../components/device_automation/trigger.py | 25 +++- homeassistant/helpers/condition.py | 16 +-- 3 files changed, 148 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/device_automation/__init__.py b/homeassistant/components/device_automation/__init__.py index 603e88fd8c8e03..5cbc8a1e678c12 100644 --- a/homeassistant/components/device_automation/__init__.py +++ b/homeassistant/components/device_automation/__init__.py @@ -7,14 +7,14 @@ from functools import wraps import logging from types import ModuleType -from typing import Any, NamedTuple +from typing import TYPE_CHECKING, Any, Literal, NamedTuple, Protocol, Union, overload import voluptuous as vol import voluptuous_serialize from homeassistant.components import websocket_api from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM -from homeassistant.core import HomeAssistant +from homeassistant.core import CALLBACK_TYPE, Context, HomeAssistant from homeassistant.helpers import ( config_validation as cv, device_registry as dr, @@ -27,6 +27,13 @@ from .exceptions import DeviceNotFound, InvalidDeviceAutomationConfig +if TYPE_CHECKING: + from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, + ) + from homeassistant.helpers import condition + # mypy: allow-untyped-calls, allow-untyped-defs DOMAIN = "device_automation" @@ -76,6 +83,77 @@ class DeviceAutomationType(Enum): } +class DeviceAutomationTriggerProtocol(Protocol): + """Define the format of device_trigger modules. + + Each module must define either TRIGGER_SCHEMA or async_validate_trigger_config. + """ + + TRIGGER_SCHEMA: vol.Schema + + async def async_validate_trigger_config( + self, hass: HomeAssistant, config: ConfigType + ) -> ConfigType: + """Validate config.""" + raise NotImplementedError + + async def async_attach_trigger( + self, + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, + ) -> CALLBACK_TYPE: + """Attach a trigger.""" + raise NotImplementedError + + +class DeviceAutomationConditionProtocol(Protocol): + """Define the format of device_condition modules. + + Each module must define either CONDITION_SCHEMA or async_validate_condition_config. + """ + + CONDITION_SCHEMA: vol.Schema + + async def async_validate_condition_config( + self, hass: HomeAssistant, config: ConfigType + ) -> ConfigType: + """Validate config.""" + raise NotImplementedError + + def async_condition_from_config( + self, hass: HomeAssistant, config: ConfigType + ) -> condition.ConditionCheckerType: + """Evaluate state based on configuration.""" + raise NotImplementedError + + +class DeviceAutomationActionProtocol(Protocol): + """Define the format of device_action modules. + + Each module must define either ACTION_SCHEMA or async_validate_action_config. + """ + + ACTION_SCHEMA: vol.Schema + + async def async_validate_action_config( + self, hass: HomeAssistant, config: ConfigType + ) -> ConfigType: + """Validate config.""" + raise NotImplementedError + + async def async_call_action_from_config( + self, + hass: HomeAssistant, + config: ConfigType, + variables: dict[str, Any], + context: Context | None, + ) -> None: + """Execute a device action.""" + raise NotImplementedError + + @bind_hass async def async_get_device_automations( hass: HomeAssistant, @@ -115,9 +193,51 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: return True +DeviceAutomationPlatformType = Union[ + ModuleType, + DeviceAutomationTriggerProtocol, + DeviceAutomationConditionProtocol, + DeviceAutomationActionProtocol, +] + + +@overload +async def async_get_device_automation_platform( # noqa: D103 + hass: HomeAssistant, + domain: str, + automation_type: Literal[DeviceAutomationType.TRIGGER], +) -> DeviceAutomationTriggerProtocol: + ... + + +@overload +async def async_get_device_automation_platform( # noqa: D103 + hass: HomeAssistant, + domain: str, + automation_type: Literal[DeviceAutomationType.CONDITION], +) -> DeviceAutomationConditionProtocol: + ... + + +@overload +async def async_get_device_automation_platform( # noqa: D103 + hass: HomeAssistant, + domain: str, + automation_type: Literal[DeviceAutomationType.ACTION], +) -> DeviceAutomationActionProtocol: + ... + + +@overload +async def async_get_device_automation_platform( # noqa: D103 + hass: HomeAssistant, domain: str, automation_type: DeviceAutomationType | str +) -> DeviceAutomationPlatformType: + ... + + async def async_get_device_automation_platform( hass: HomeAssistant, domain: str, automation_type: DeviceAutomationType | str -) -> ModuleType: +) -> DeviceAutomationPlatformType: """Load device automation platform for integration. Throws InvalidDeviceAutomationConfig if the integration is not found or does not support device automation. diff --git a/homeassistant/components/device_automation/trigger.py b/homeassistant/components/device_automation/trigger.py index 008a7603dba608..f2962d6544e5df 100644 --- a/homeassistant/components/device_automation/trigger.py +++ b/homeassistant/components/device_automation/trigger.py @@ -1,7 +1,15 @@ """Offer device oriented automation.""" +from typing import cast + import voluptuous as vol +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) from homeassistant.const import CONF_DOMAIN +from homeassistant.core import CALLBACK_TYPE, HomeAssistant +from homeassistant.helpers.typing import ConfigType from . import ( DEVICE_TRIGGER_BASE_SCHEMA, @@ -10,26 +18,31 @@ ) from .exceptions import InvalidDeviceAutomationConfig -# mypy: allow-untyped-defs, no-check-untyped-defs - TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA) -async def async_validate_trigger_config(hass, config): +async def async_validate_trigger_config( + hass: HomeAssistant, config: ConfigType +) -> ConfigType: """Validate config.""" platform = await async_get_device_automation_platform( hass, config[CONF_DOMAIN], DeviceAutomationType.TRIGGER ) if not hasattr(platform, "async_validate_trigger_config"): - return platform.TRIGGER_SCHEMA(config) + return cast(ConfigType, platform.TRIGGER_SCHEMA(config)) try: - return await getattr(platform, "async_validate_trigger_config")(hass, config) + return await platform.async_validate_trigger_config(hass, config) except InvalidDeviceAutomationConfig as err: raise vol.Invalid(str(err) or "Invalid trigger configuration") from err -async def async_attach_trigger(hass, config, action, automation_info): +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, +) -> CALLBACK_TYPE: """Listen for trigger.""" platform = await async_get_device_automation_platform( hass, config[CONF_DOMAIN], DeviceAutomationType.TRIGGER diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 80bed9137d0a0b..06853dd945064f 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -875,12 +875,7 @@ async def async_device_from_config( platform = await async_get_device_automation_platform( hass, config[CONF_DOMAIN], DeviceAutomationType.CONDITION ) - return trace_condition_function( - cast( - ConditionCheckerType, - platform.async_condition_from_config(hass, config), - ) - ) + return trace_condition_function(platform.async_condition_from_config(hass, config)) async def async_trigger_from_config( @@ -943,14 +938,15 @@ async def async_validate_condition_config( hass, config[CONF_DOMAIN], DeviceAutomationType.CONDITION ) if hasattr(platform, "async_validate_condition_config"): - return await platform.async_validate_condition_config(hass, config) # type: ignore + return await platform.async_validate_condition_config(hass, config) return cast(ConfigType, platform.CONDITION_SCHEMA(config)) if condition in ("numeric_state", "state"): - validator = getattr( - sys.modules[__name__], VALIDATE_CONFIG_FORMAT.format(condition) + validator = cast( + Callable[[HomeAssistant, ConfigType], ConfigType], + getattr(sys.modules[__name__], VALIDATE_CONFIG_FORMAT.format(condition)), ) - return validator(hass, config) # type: ignore + return validator(hass, config) return config From cd0046428553e2a302e3d4da272e1a93cefde75f Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 17 Feb 2022 23:18:03 +0100 Subject: [PATCH 0759/1098] Remove use of hass.helpers from MQTT (#66757) * Remove use of hass.helpers from MQTT * Tweak --- homeassistant/components/mqtt/__init__.py | 21 ++++++++++++------- homeassistant/components/mqtt/debug_info.py | 2 +- .../components/mqtt/device_trigger.py | 12 +++++------ homeassistant/components/mqtt/tag.py | 11 +++++----- 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 3da9a17c3aaf6c..b97a0bc8770f59 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -50,7 +50,12 @@ ) from homeassistant.data_entry_flow import BaseServiceInfo from homeassistant.exceptions import HomeAssistantError, TemplateError, Unauthorized -from homeassistant.helpers import config_validation as cv, event, template +from homeassistant.helpers import ( + config_validation as cv, + device_registry as dr, + event, + template, +) from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send from homeassistant.helpers.entity import Entity from homeassistant.helpers.frame import report @@ -1181,8 +1186,8 @@ def _matcher_for_topic(subscription: str) -> Any: @websocket_api.websocket_command( {vol.Required("type"): "mqtt/device/debug_info", vol.Required("device_id"): str} ) -@websocket_api.async_response -async def websocket_mqtt_info(hass, connection, msg): +@callback +def websocket_mqtt_info(hass, connection, msg): """Get MQTT debug info for device.""" device_id = msg["device_id"] mqtt_info = debug_info.info_for_device(hass, device_id) @@ -1193,13 +1198,13 @@ async def websocket_mqtt_info(hass, connection, msg): @websocket_api.websocket_command( {vol.Required("type"): "mqtt/device/remove", vol.Required("device_id"): str} ) -@websocket_api.async_response -async def websocket_remove_device(hass, connection, msg): +@callback +def websocket_remove_device(hass, connection, msg): """Delete device.""" device_id = msg["device_id"] - dev_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) - if not (device := dev_registry.async_get(device_id)): + if not (device := device_registry.async_get(device_id)): connection.send_error( msg["id"], websocket_api.const.ERR_NOT_FOUND, "Device not found" ) @@ -1209,7 +1214,7 @@ async def websocket_remove_device(hass, connection, msg): config_entry = hass.config_entries.async_get_entry(config_entry) # Only delete the device if it belongs to an MQTT device entry if config_entry.domain == DOMAIN: - dev_registry.async_remove_device(device_id) + device_registry.async_remove_device(device_id) connection.send_message(websocket_api.result_message(msg["id"])) return diff --git a/homeassistant/components/mqtt/debug_info.py b/homeassistant/components/mqtt/debug_info.py index ca9e56f8efb4f7..3bf07db183238c 100644 --- a/homeassistant/components/mqtt/debug_info.py +++ b/homeassistant/components/mqtt/debug_info.py @@ -162,7 +162,7 @@ def info_for_device(hass, device_id): mqtt_info = {"entities": [], "triggers": []} entity_registry = er.async_get(hass) - entries = hass.helpers.entity_registry.async_entries_for_device( + entries = er.async_entries_for_device( entity_registry, device_id, include_disabled_entities=True ) mqtt_debug_info = hass.data[DATA_MQTT_DEBUG_INFO] diff --git a/homeassistant/components/mqtt/device_trigger.py b/homeassistant/components/mqtt/device_trigger.py index 78f52e5872614e..f621021e124dc7 100644 --- a/homeassistant/components/mqtt/device_trigger.py +++ b/homeassistant/components/mqtt/device_trigger.py @@ -23,7 +23,7 @@ ) from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, @@ -190,9 +190,9 @@ def detach_trigger(self): trig.remove = None -async def _update_device(hass, config_entry, config): +def _update_device(hass, config_entry, config): """Update device registry.""" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) config_entry_id = config_entry.entry_id device_info = device_info_from_config(config[CONF_DEVICE]) @@ -228,7 +228,7 @@ async def discovery_update(payload): _LOGGER.info("Updating trigger: %s", discovery_hash) debug_info.update_trigger_discovery_data(hass, discovery_hash, payload) config = TRIGGER_DISCOVERY_SCHEMA(payload) - await _update_device(hass, config_entry, config) + _update_device(hass, config_entry, config) device_trigger = hass.data[DEVICE_TRIGGERS][discovery_id] await device_trigger.update_trigger(config, discovery_hash, remove_signal) async_dispatcher_send(hass, MQTT_DISCOVERY_DONE.format(discovery_hash), None) @@ -237,9 +237,9 @@ async def discovery_update(payload): hass, MQTT_DISCOVERY_UPDATED.format(discovery_hash), discovery_update ) - await _update_device(hass, config_entry, config) + _update_device(hass, config_entry, config) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get_device( {(DOMAIN, id_) for id_ in config[CONF_DEVICE][CONF_IDENTIFIERS]}, {tuple(x) for x in config[CONF_DEVICE][CONF_CONNECTIONS]}, diff --git a/homeassistant/components/mqtt/tag.py b/homeassistant/components/mqtt/tag.py index e415225080238e..186f11534b970a 100644 --- a/homeassistant/components/mqtt/tag.py +++ b/homeassistant/components/mqtt/tag.py @@ -5,6 +5,7 @@ import voluptuous as vol from homeassistant.const import CONF_DEVICE, CONF_PLATFORM, CONF_VALUE_TEMPLATE +from homeassistant.helpers import device_registry as dr import homeassistant.helpers.config_validation as cv from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED from homeassistant.helpers.dispatcher import ( @@ -61,9 +62,9 @@ async def async_setup_tag(hass, config, config_entry, discovery_data): device_id = None if CONF_DEVICE in config: - await _update_device(hass, config_entry, config) + _update_device(hass, config_entry, config) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get_device( {(DOMAIN, id_) for id_ in config[CONF_DEVICE][CONF_IDENTIFIERS]}, {tuple(x) for x in config[CONF_DEVICE][CONF_CONNECTIONS]}, @@ -134,7 +135,7 @@ async def discovery_update(self, payload): config = PLATFORM_SCHEMA(payload) self._config = config if self.device_id: - await _update_device(self.hass, self._config_entry, config) + _update_device(self.hass, self._config_entry, config) self._setup_from_config(config) await self.subscribe_topics() @@ -215,9 +216,9 @@ async def tear_down(self): self.hass.data[TAGS][self.device_id].pop(discovery_id) -async def _update_device(hass, config_entry, config): +def _update_device(hass, config_entry, config): """Update device registry.""" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) config_entry_id = config_entry.entry_id device_info = device_info_from_config(config[CONF_DEVICE]) From 90a0d5518d77e6e28d0f4708cd5deeffea08f624 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Fri, 18 Feb 2022 00:46:18 +0200 Subject: [PATCH 0760/1098] Handle default notify data in webostv (#66770) --- homeassistant/components/webostv/notify.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/webostv/notify.py b/homeassistant/components/webostv/notify.py index 7348e978d026f7..46f0086e0f6a0c 100644 --- a/homeassistant/components/webostv/notify.py +++ b/homeassistant/components/webostv/notify.py @@ -46,8 +46,8 @@ async def async_send_message(self, message: str = "", **kwargs: Any) -> None: if not self._client.is_connected(): await self._client.connect() - data = kwargs.get(ATTR_DATA, {}) - icon_path = data.get(CONF_ICON) + data = kwargs.get(ATTR_DATA) + icon_path = data.get(CONF_ICON) if data else None await self._client.send_message(message, icon_path=icon_path) except WebOsTvPairError: _LOGGER.error("Pairing with TV failed") From 64277058b5ba6fb10029553422695964204f0ebb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 17 Feb 2022 17:03:20 -0600 Subject: [PATCH 0761/1098] Ensure lutron caseta imports set the unique id (#66754) --- .../components/lutron_caseta/config_flow.py | 30 ++++++++++++------- .../lutron_caseta/test_config_flow.py | 6 ++++ 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/lutron_caseta/config_flow.py b/homeassistant/components/lutron_caseta/config_flow.py index b198d5ddbee6e6..74819e25e8edc0 100644 --- a/homeassistant/components/lutron_caseta/config_flow.py +++ b/homeassistant/components/lutron_caseta/config_flow.py @@ -1,4 +1,6 @@ """Config flow for Lutron Caseta.""" +from __future__ import annotations + import asyncio import logging import os @@ -17,6 +19,7 @@ from .const import ( ABORT_REASON_CANNOT_CONNECT, + BRIDGE_DEVICE_ID, BRIDGE_TIMEOUT, CONF_CA_CERTS, CONF_CERTFILE, @@ -101,7 +104,7 @@ async def async_step_link(self, user_input=None): if ( not self.attempted_tls_validation and await self.hass.async_add_executor_job(self._tls_assets_exist) - and await self.async_validate_connectable_bridge_config() + and await self.async_get_lutron_id() ): self.tls_assets_validated = True self.attempted_tls_validation = True @@ -177,7 +180,7 @@ async def async_step_import(self, import_info): self.data[CONF_CERTFILE] = import_info[CONF_CERTFILE] self.data[CONF_CA_CERTS] = import_info[CONF_CA_CERTS] - if not await self.async_validate_connectable_bridge_config(): + if not (lutron_id := await self.async_get_lutron_id()): # Ultimately we won't have a dedicated step for import failure, but # in order to keep configuration.yaml-based configs transparently # working without requiring further actions from the user, we don't @@ -189,6 +192,8 @@ async def async_step_import(self, import_info): # will require users to go through a confirmation flow for imports). return await self.async_step_import_failed() + await self.async_set_unique_id(lutron_id, raise_on_progress=False) + self._abort_if_unique_id_configured() return self.async_create_entry(title=ENTRY_DEFAULT_TITLE, data=self.data) async def async_step_import_failed(self, user_input=None): @@ -204,10 +209,8 @@ async def async_step_import_failed(self, user_input=None): return self.async_abort(reason=ABORT_REASON_CANNOT_CONNECT) - async def async_validate_connectable_bridge_config(self): + async def async_get_lutron_id(self) -> str | None: """Check if we can connect to the bridge with the current config.""" - bridge = None - try: bridge = Smartbridge.create_tls( hostname=self.data[CONF_HOST], @@ -220,18 +223,23 @@ async def async_validate_connectable_bridge_config(self): "Invalid certificate used to connect to bridge at %s", self.data[CONF_HOST], ) - return False + return None - connected_ok = False try: async with async_timeout.timeout(BRIDGE_TIMEOUT): await bridge.connect() - connected_ok = bridge.is_connected() except asyncio.TimeoutError: _LOGGER.error( "Timeout while trying to connect to bridge at %s", self.data[CONF_HOST], ) - - await bridge.close() - return connected_ok + else: + if not bridge.is_connected(): + return None + devices = bridge.get_devices() + bridge_device = devices[BRIDGE_DEVICE_ID] + return hex(bridge_device["serial"])[2:].zfill(8) + finally: + await bridge.close() + + return None diff --git a/tests/components/lutron_caseta/test_config_flow.py b/tests/components/lutron_caseta/test_config_flow.py index 47956e270028bd..9dbedeacf5bf8e 100644 --- a/tests/components/lutron_caseta/test_config_flow.py +++ b/tests/components/lutron_caseta/test_config_flow.py @@ -56,6 +56,10 @@ def is_connected(self): """Return whether the mock bridge is connected.""" return self.is_currently_connected + def get_devices(self): + """Return devices on the bridge.""" + return {"1": {"serial": 1234}} + async def close(self): """Close the mock bridge connection.""" self.is_currently_connected = False @@ -90,6 +94,8 @@ async def test_bridge_import_flow(hass): assert result["type"] == "create_entry" assert result["title"] == CasetaConfigFlow.ENTRY_DEFAULT_TITLE assert result["data"] == entry_mock_data + assert result["result"].unique_id == "000004d2" + await hass.async_block_till_done() assert len(mock_setup_entry.mock_calls) == 1 From 8b557884b7914247285e968bc56ec7485bb883d0 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 18 Feb 2022 00:24:29 +0000 Subject: [PATCH 0762/1098] [ci skip] Translation update --- .../aussie_broadband/translations/bg.json | 6 ++++++ .../aussie_broadband/translations/ca.json | 7 +++++++ .../aussie_broadband/translations/de.json | 7 +++++++ .../aussie_broadband/translations/et.json | 7 +++++++ .../aussie_broadband/translations/it.json | 7 +++++++ .../aussie_broadband/translations/no.json | 7 +++++++ .../aussie_broadband/translations/ru.json | 7 +++++++ .../translations/zh-Hant.json | 7 +++++++ .../components/homekit/translations/it.json | 2 +- .../components/iss/translations/bg.json | 9 +++++++++ .../components/sleepiq/translations/bg.json | 19 +++++++++++++++++++ .../components/sleepiq/translations/cs.json | 7 +++++++ .../components/sleepiq/translations/de.json | 19 +++++++++++++++++++ .../components/sleepiq/translations/et.json | 19 +++++++++++++++++++ .../components/sleepiq/translations/it.json | 19 +++++++++++++++++++ .../components/sleepiq/translations/no.json | 19 +++++++++++++++++++ .../twentemilieu/translations/it.json | 2 +- .../components/wiz/translations/bg.json | 3 ++- 18 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/sleepiq/translations/bg.json create mode 100644 homeassistant/components/sleepiq/translations/cs.json create mode 100644 homeassistant/components/sleepiq/translations/de.json create mode 100644 homeassistant/components/sleepiq/translations/et.json create mode 100644 homeassistant/components/sleepiq/translations/it.json create mode 100644 homeassistant/components/sleepiq/translations/no.json diff --git a/homeassistant/components/aussie_broadband/translations/bg.json b/homeassistant/components/aussie_broadband/translations/bg.json index 508b940a541650..74687550820fa7 100644 --- a/homeassistant/components/aussie_broadband/translations/bg.json +++ b/homeassistant/components/aussie_broadband/translations/bg.json @@ -18,6 +18,12 @@ "description": "\u0410\u043a\u0442\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u043f\u0430\u0440\u043e\u043b\u0430\u0442\u0430 \u0437\u0430 {username}", "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435 \u043d\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f\u0442\u0430" }, + "reauth_confirm": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430" + }, + "description": "\u0410\u043a\u0442\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u043f\u0430\u0440\u043e\u043b\u0430\u0442\u0430 \u0437\u0430 {username}" + }, "service": { "data": { "services": "\u0423\u0441\u043b\u0443\u0433\u0438" diff --git a/homeassistant/components/aussie_broadband/translations/ca.json b/homeassistant/components/aussie_broadband/translations/ca.json index 6ef46a791018bc..83ec53ad5a44c9 100644 --- a/homeassistant/components/aussie_broadband/translations/ca.json +++ b/homeassistant/components/aussie_broadband/translations/ca.json @@ -18,6 +18,13 @@ "description": "Actualitza la contrasenya de {username}", "title": "Reautenticaci\u00f3 de la integraci\u00f3" }, + "reauth_confirm": { + "data": { + "password": "Contrasenya" + }, + "description": "Actualitza la contrasenya de {username}", + "title": "Reautenticaci\u00f3 de la integraci\u00f3" + }, "service": { "data": { "services": "Serveis" diff --git a/homeassistant/components/aussie_broadband/translations/de.json b/homeassistant/components/aussie_broadband/translations/de.json index 6ab2d4d873aee5..ed5cd5fd02c2d1 100644 --- a/homeassistant/components/aussie_broadband/translations/de.json +++ b/homeassistant/components/aussie_broadband/translations/de.json @@ -18,6 +18,13 @@ "description": "Passwort f\u00fcr {username} aktualisieren", "title": "Integration erneut authentifizieren" }, + "reauth_confirm": { + "data": { + "password": "Passwort" + }, + "description": "Passwort f\u00fcr {username} aktualisieren", + "title": "Integration erneut authentifizieren" + }, "service": { "data": { "services": "Dienste" diff --git a/homeassistant/components/aussie_broadband/translations/et.json b/homeassistant/components/aussie_broadband/translations/et.json index 408b4d6adcdc61..7dbf2d26b59fea 100644 --- a/homeassistant/components/aussie_broadband/translations/et.json +++ b/homeassistant/components/aussie_broadband/translations/et.json @@ -18,6 +18,13 @@ "description": "{username} salas\u00f5na v\u00e4rskendamine", "title": "Taastuvasta sidumine" }, + "reauth_confirm": { + "data": { + "password": "Salas\u00f5na" + }, + "description": "{username} salas\u00f5na v\u00e4rskendamine", + "title": "Taastuvasta sidumine" + }, "service": { "data": { "services": "Teenused" diff --git a/homeassistant/components/aussie_broadband/translations/it.json b/homeassistant/components/aussie_broadband/translations/it.json index 3325c33c349f0b..a29e10db60a8f8 100644 --- a/homeassistant/components/aussie_broadband/translations/it.json +++ b/homeassistant/components/aussie_broadband/translations/it.json @@ -18,6 +18,13 @@ "description": "Aggiorna la password per {username}", "title": "Autentica nuovamente l'integrazione" }, + "reauth_confirm": { + "data": { + "password": "Password" + }, + "description": "Aggiorna la password per {username}", + "title": "Autentica nuovamente l'integrazione" + }, "service": { "data": { "services": "Servizi" diff --git a/homeassistant/components/aussie_broadband/translations/no.json b/homeassistant/components/aussie_broadband/translations/no.json index 193c51f5914079..8129a8c9cc261e 100644 --- a/homeassistant/components/aussie_broadband/translations/no.json +++ b/homeassistant/components/aussie_broadband/translations/no.json @@ -18,6 +18,13 @@ "description": "Oppdater passordet for {username}", "title": "Godkjenne integrering p\u00e5 nytt" }, + "reauth_confirm": { + "data": { + "password": "Passord" + }, + "description": "Oppdater passordet for {username}", + "title": "Godkjenne integrering p\u00e5 nytt" + }, "service": { "data": { "services": "Tjenester" diff --git a/homeassistant/components/aussie_broadband/translations/ru.json b/homeassistant/components/aussie_broadband/translations/ru.json index ad203bd266e8d5..15a9f44a98ac06 100644 --- a/homeassistant/components/aussie_broadband/translations/ru.json +++ b/homeassistant/components/aussie_broadband/translations/ru.json @@ -18,6 +18,13 @@ "description": "\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u044c \u0434\u043b\u044f {username}.", "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f" }, + "reauth_confirm": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u044c \u0434\u043b\u044f {username}.", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f" + }, "service": { "data": { "services": "\u0421\u043b\u0443\u0436\u0431\u044b" diff --git a/homeassistant/components/aussie_broadband/translations/zh-Hant.json b/homeassistant/components/aussie_broadband/translations/zh-Hant.json index 282549cdeafcc5..f7beefb2962757 100644 --- a/homeassistant/components/aussie_broadband/translations/zh-Hant.json +++ b/homeassistant/components/aussie_broadband/translations/zh-Hant.json @@ -18,6 +18,13 @@ "description": "\u66f4\u65b0 {username} \u5bc6\u78bc", "title": "\u91cd\u65b0\u8a8d\u8b49\u6574\u5408" }, + "reauth_confirm": { + "data": { + "password": "\u5bc6\u78bc" + }, + "description": "\u66f4\u65b0 {username} \u5bc6\u78bc", + "title": "\u91cd\u65b0\u8a8d\u8b49\u6574\u5408" + }, "service": { "data": { "services": "\u670d\u52d9" diff --git a/homeassistant/components/homekit/translations/it.json b/homeassistant/components/homekit/translations/it.json index 46c1a8063fdc7f..186bf989a4ecd7 100644 --- a/homeassistant/components/homekit/translations/it.json +++ b/homeassistant/components/homekit/translations/it.json @@ -31,7 +31,7 @@ "devices": "Dispositivi (Attivatori)" }, "description": "Gli interruttori programmabili vengono creati per ogni dispositivo selezionato. Quando si attiva un trigger del dispositivo, HomeKit pu\u00f2 essere configurato per eseguire un'automazione o una scena.", - "title": "Configurazione Avanzata" + "title": "Configurazione avanzata" }, "cameras": { "data": { diff --git a/homeassistant/components/iss/translations/bg.json b/homeassistant/components/iss/translations/bg.json index 7d004af7b54f33..05945056fb496a 100644 --- a/homeassistant/components/iss/translations/bg.json +++ b/homeassistant/components/iss/translations/bg.json @@ -12,5 +12,14 @@ "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u0442\u0435 \u041c\u0435\u0436\u0434\u0443\u043d\u0430\u0440\u043e\u0434\u043d\u0430\u0442\u0430 \u043a\u043e\u0441\u043c\u0438\u0447\u0435\u0441\u043a\u0430 \u0441\u0442\u0430\u043d\u0446\u0438\u044f?" } } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u0432\u0430\u043d\u0435 \u043d\u0430 \u043a\u0430\u0440\u0442\u0430\u0442\u0430" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/sleepiq/translations/bg.json b/homeassistant/components/sleepiq/translations/bg.json new file mode 100644 index 00000000000000..b8fb3b61a771c2 --- /dev/null +++ b/homeassistant/components/sleepiq/translations/bg.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u0410\u043a\u0430\u0443\u043d\u0442\u044a\u0442 \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d" + }, + "error": { + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", + "invalid_auth": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sleepiq/translations/cs.json b/homeassistant/components/sleepiq/translations/cs.json new file mode 100644 index 00000000000000..efbe2a91eb1cc1 --- /dev/null +++ b/homeassistant/components/sleepiq/translations/cs.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sleepiq/translations/de.json b/homeassistant/components/sleepiq/translations/de.json new file mode 100644 index 00000000000000..12a870b4cc9181 --- /dev/null +++ b/homeassistant/components/sleepiq/translations/de.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, + "step": { + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sleepiq/translations/et.json b/homeassistant/components/sleepiq/translations/et.json new file mode 100644 index 00000000000000..db09683450a602 --- /dev/null +++ b/homeassistant/components/sleepiq/translations/et.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Konto on juba seadistatud" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Tuvastamine nurjus" + }, + "step": { + "user": { + "data": { + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sleepiq/translations/it.json b/homeassistant/components/sleepiq/translations/it.json new file mode 100644 index 00000000000000..7ae1601843e57c --- /dev/null +++ b/homeassistant/components/sleepiq/translations/it.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "L'account \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida" + }, + "step": { + "user": { + "data": { + "password": "Password", + "username": "Nome utente" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sleepiq/translations/no.json b/homeassistant/components/sleepiq/translations/no.json new file mode 100644 index 00000000000000..51f351fb83322d --- /dev/null +++ b/homeassistant/components/sleepiq/translations/no.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Kontoen er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning" + }, + "step": { + "user": { + "data": { + "password": "Passord", + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twentemilieu/translations/it.json b/homeassistant/components/twentemilieu/translations/it.json index d8d9570d8ca275..a374885e7aa194 100644 --- a/homeassistant/components/twentemilieu/translations/it.json +++ b/homeassistant/components/twentemilieu/translations/it.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "house_letter": "Edificio, Scala, Interno, ecc. / Informazioni aggiuntive", + "house_letter": "Lettera aggiuntiva", "house_number": "Numero civico", "post_code": "CAP" }, diff --git a/homeassistant/components/wiz/translations/bg.json b/homeassistant/components/wiz/translations/bg.json index f9a0ae7bd8c4b7..eb0697ca13d6b7 100644 --- a/homeassistant/components/wiz/translations/bg.json +++ b/homeassistant/components/wiz/translations/bg.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e" + "already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e", + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" }, "error": { "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", From 736a1ca0a367b2b455576b3fbedc37e539165024 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 17 Feb 2022 20:14:08 -0600 Subject: [PATCH 0763/1098] Fix merge conflict resolution error in flux_led (#66775) --- homeassistant/components/flux_led/select.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/flux_led/select.py b/homeassistant/components/flux_led/select.py index 7edf0ef50f88b4..4067110e336b71 100644 --- a/homeassistant/components/flux_led/select.py +++ b/homeassistant/components/flux_led/select.py @@ -110,7 +110,7 @@ def __init__( ) -> None: """Initialize the power state select.""" super().__init__(device, entry) - self._attr_name = f"{entry.data[CONF_NAME]} Power Restored" + self._attr_name = f"{entry.data.get(CONF_NAME, entry.title)} Power Restored" base_unique_id = entry.unique_id or entry.entry_id self._attr_unique_id = f"{base_unique_id}_power_restored" self._async_set_current_option_from_device() @@ -237,7 +237,7 @@ def __init__( ) -> None: """Initialize the white channel select.""" super().__init__(device, entry) - self._attr_name = f"{entry.data[CONF_NAME]} White Channel" + self._attr_name = f"{entry.data.get(CONF_NAME, entry.title)} White Channel" base_unique_id = entry.unique_id or entry.entry_id self._attr_unique_id = f"{base_unique_id}_white_channel" From 58551ec66da1344bcbd8fd25712dd9074ea4e1e7 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Thu, 17 Feb 2022 23:08:29 -0800 Subject: [PATCH 0764/1098] Update nest camera tests to use common test fixture (#66192) --- tests/components/nest/test_camera_sdm.py | 279 +++++++++++++---------- 1 file changed, 160 insertions(+), 119 deletions(-) diff --git a/tests/components/nest/test_camera_sdm.py b/tests/components/nest/test_camera_sdm.py index 81b4a7cf0a6e65..b64e251bcf0b41 100644 --- a/tests/components/nest/test_camera_sdm.py +++ b/tests/components/nest/test_camera_sdm.py @@ -10,7 +10,6 @@ from unittest.mock import AsyncMock, Mock, patch import aiohttp -from google_nest_sdm.device import Device from google_nest_sdm.event import EventMessage import pytest @@ -21,18 +20,20 @@ STREAM_TYPE_HLS, STREAM_TYPE_WEB_RTC, ) +from homeassistant.components.nest.const import DOMAIN from homeassistant.components.websocket_api.const import TYPE_RESULT +from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow -from .common import async_setup_sdm_platform +from .common import DEVICE_ID, CreateDevice, FakeSubscriber, PlatformSetup +from .conftest import FakeAuth from tests.common import async_fire_time_changed PLATFORM = "camera" CAMERA_DEVICE_TYPE = "sdm.devices.types.CAMERA" -DEVICE_ID = "some-device-id" DEVICE_TRAITS = { "sdm.devices.traits.Info": { "customName": "My Camera", @@ -49,7 +50,6 @@ "sdm.devices.traits.CameraMotion": {}, } DATETIME_FORMAT = "YY-MM-DDTHH:MM:SS" -DOMAIN = "nest" MOTION_EVENT_ID = "FWWVQVUdGNUlTU2V4MGV2aTNXV..." EVENT_SESSION_ID = "CjY5Y3VKaTZwR3o4Y19YbTVfMF..." @@ -65,6 +65,45 @@ IMAGE_AUTHORIZATION_HEADERS = {"Authorization": "Basic g.0.eventToken"} +@pytest.fixture +def platforms() -> list[str]: + """Fixture to set platforms used in the test.""" + return ["camera"] + + +@pytest.fixture +async def device_type() -> str: + """Fixture to set default device type used when creating devices.""" + return "sdm.devices.types.CAMERA" + + +@pytest.fixture +def camera_device(create_device: CreateDevice) -> None: + """Fixture to create a basic camera device.""" + create_device.create(DEVICE_TRAITS) + + +@pytest.fixture +def webrtc_camera_device(create_device: CreateDevice) -> None: + """Fixture to create a WebRTC camera device.""" + create_device.create( + { + "sdm.devices.traits.Info": { + "customName": "My Camera", + }, + "sdm.devices.traits.CameraLiveStream": { + "maxVideoResolution": { + "width": 640, + "height": 480, + }, + "videoCodecs": ["H264"], + "audioCodecs": ["AAC"], + "supportedProtocols": ["WEB_RTC"], + }, + } + ) + + def make_motion_event( event_id: str = MOTION_EVENT_ID, event_session_id: str = EVENT_SESSION_ID, @@ -136,21 +175,6 @@ async def async_get_image(hass, width=None, height=None): return image.content -async def async_setup_camera(hass, traits={}, auth=None): - """Set up the platform and prerequisites.""" - devices = {} - if traits: - devices[DEVICE_ID] = Device.MakeDevice( - { - "name": DEVICE_ID, - "type": CAMERA_DEVICE_TYPE, - "traits": traits, - }, - auth=auth, - ) - return await async_setup_sdm_platform(hass, PLATFORM, devices) - - async def fire_alarm(hass, point_in_time): """Fire an alarm and wait for callbacks to run.""" with patch("homeassistant.util.dt.utcnow", return_value=point_in_time): @@ -158,28 +182,33 @@ async def fire_alarm(hass, point_in_time): await hass.async_block_till_done() -async def test_no_devices(hass): +async def test_no_devices(hass: HomeAssistant, setup_platform: PlatformSetup): """Test configuration that returns no devices.""" - await async_setup_camera(hass) + await setup_platform() assert len(hass.states.async_all()) == 0 -async def test_ineligible_device(hass): +async def test_ineligible_device( + hass: HomeAssistant, setup_platform: PlatformSetup, create_device: CreateDevice +): """Test configuration with devices that do not support cameras.""" - await async_setup_camera( - hass, + create_device.create( { "sdm.devices.traits.Info": { "customName": "My Camera", }, - }, + } ) + + await setup_platform() assert len(hass.states.async_all()) == 0 -async def test_camera_device(hass): +async def test_camera_device( + hass: HomeAssistant, setup_platform: PlatformSetup, camera_device: None +): """Test a basic camera with a live stream.""" - await async_setup_camera(hass, DEVICE_TRAITS) + await setup_platform() assert len(hass.states.async_all()) == 1 camera = hass.states.get("camera.my_camera") @@ -188,7 +217,7 @@ async def test_camera_device(hass): registry = er.async_get(hass) entry = registry.async_get("camera.my_camera") - assert entry.unique_id == "some-device-id-camera" + assert entry.unique_id == f"{DEVICE_ID}-camera" assert entry.original_name == "My Camera" assert entry.domain == "camera" @@ -199,10 +228,16 @@ async def test_camera_device(hass): assert device.identifiers == {("nest", DEVICE_ID)} -async def test_camera_stream(hass, auth, mock_create_stream): +async def test_camera_stream( + hass: HomeAssistant, + setup_platform: PlatformSetup, + camera_device: None, + auth: FakeAuth, + mock_create_stream: Mock, +): """Test a basic camera and fetch its live stream.""" auth.responses = [make_stream_url_response()] - await async_setup_camera(hass, DEVICE_TRAITS, auth=auth) + await setup_platform() assert len(hass.states.async_all()) == 1 cam = hass.states.get("camera.my_camera") @@ -216,10 +251,17 @@ async def test_camera_stream(hass, auth, mock_create_stream): assert await async_get_image(hass) == IMAGE_BYTES_FROM_STREAM -async def test_camera_ws_stream(hass, auth, hass_ws_client, mock_create_stream): +async def test_camera_ws_stream( + hass, + setup_platform, + camera_device, + hass_ws_client, + auth, + mock_create_stream, +): """Test a basic camera that supports web rtc.""" auth.responses = [make_stream_url_response()] - await async_setup_camera(hass, DEVICE_TRAITS, auth=auth) + await setup_platform() assert len(hass.states.async_all()) == 1 cam = hass.states.get("camera.my_camera") @@ -245,10 +287,12 @@ async def test_camera_ws_stream(hass, auth, hass_ws_client, mock_create_stream): assert await async_get_image(hass) == IMAGE_BYTES_FROM_STREAM -async def test_camera_ws_stream_failure(hass, auth, hass_ws_client): +async def test_camera_ws_stream_failure( + hass, setup_platform, camera_device, hass_ws_client, auth +): """Test a basic camera that supports web rtc.""" auth.responses = [aiohttp.web.Response(status=HTTPStatus.BAD_REQUEST)] - await async_setup_camera(hass, DEVICE_TRAITS, auth=auth) + await setup_platform() assert len(hass.states.async_all()) == 1 cam = hass.states.get("camera.my_camera") @@ -272,21 +316,22 @@ async def test_camera_ws_stream_failure(hass, auth, hass_ws_client): assert msg["error"]["message"].startswith("Nest API error") -async def test_camera_stream_missing_trait(hass, auth): +async def test_camera_stream_missing_trait(hass, setup_platform, create_device): """Test fetching a video stream when not supported by the API.""" - traits = { - "sdm.devices.traits.Info": { - "customName": "My Camera", - }, - "sdm.devices.traits.CameraImage": { - "maxImageResolution": { - "width": 800, - "height": 600, - } - }, - } - - await async_setup_camera(hass, traits, auth=auth) + create_device.create( + { + "sdm.devices.traits.Info": { + "customName": "My Camera", + }, + "sdm.devices.traits.CameraImage": { + "maxImageResolution": { + "width": 800, + "height": 600, + } + }, + } + ) + await setup_platform() assert len(hass.states.async_all()) == 1 cam = hass.states.get("camera.my_camera") @@ -300,7 +345,12 @@ async def test_camera_stream_missing_trait(hass, auth): await async_get_image(hass) -async def test_refresh_expired_stream_token(hass, auth): +async def test_refresh_expired_stream_token( + hass: HomeAssistant, + setup_platform: PlatformSetup, + auth: FakeAuth, + camera_device: None, +): """Test a camera stream expiration and refresh.""" now = utcnow() stream_1_expiration = now + datetime.timedelta(seconds=90) @@ -314,11 +364,7 @@ async def test_refresh_expired_stream_token(hass, auth): # Stream URL #3 make_stream_url_response(stream_3_expiration, token_num=3), ] - await async_setup_camera( - hass, - DEVICE_TRAITS, - auth=auth, - ) + await setup_platform() assert await async_setup_component(hass, "stream", {}) assert len(hass.states.async_all()) == 1 @@ -375,7 +421,12 @@ async def test_refresh_expired_stream_token(hass, auth): assert hls_url == hls_url2 -async def test_stream_response_already_expired(hass, auth): +async def test_stream_response_already_expired( + hass: HomeAssistant, + auth: FakeAuth, + setup_platform: PlatformSetup, + camera_device: None, +): """Test a API response returning an expired stream url.""" now = utcnow() stream_1_expiration = now + datetime.timedelta(seconds=-90) @@ -384,7 +435,7 @@ async def test_stream_response_already_expired(hass, auth): make_stream_url_response(stream_1_expiration, token_num=1), make_stream_url_response(stream_2_expiration, token_num=2), ] - await async_setup_camera(hass, DEVICE_TRAITS, auth=auth) + await setup_platform() assert len(hass.states.async_all()) == 1 cam = hass.states.get("camera.my_camera") @@ -402,13 +453,15 @@ async def test_stream_response_already_expired(hass, auth): assert stream_source == "rtsp://some/url?auth=g.2.streamingToken" -async def test_camera_removed(hass, auth): +async def test_camera_removed( + hass: HomeAssistant, + auth: FakeAuth, + camera_device: None, + subscriber: FakeSubscriber, + setup_platform: PlatformSetup, +): """Test case where entities are removed and stream tokens revoked.""" - subscriber = await async_setup_camera( - hass, - DEVICE_TRAITS, - auth=auth, - ) + await setup_platform() # Simplify test setup subscriber.cache_policy.fetch = False @@ -431,13 +484,14 @@ async def test_camera_removed(hass, auth): assert len(hass.states.async_all()) == 0 -async def test_camera_remove_failure(hass, auth): +async def test_camera_remove_failure( + hass: HomeAssistant, + auth: FakeAuth, + camera_device: None, + setup_platform: PlatformSetup, +): """Test case where revoking the stream token fails on unload.""" - await async_setup_camera( - hass, - DEVICE_TRAITS, - auth=auth, - ) + await setup_platform() assert len(hass.states.async_all()) == 1 cam = hass.states.get("camera.my_camera") @@ -460,7 +514,12 @@ async def test_camera_remove_failure(hass, auth): assert len(hass.states.async_all()) == 0 -async def test_refresh_expired_stream_failure(hass, auth): +async def test_refresh_expired_stream_failure( + hass: HomeAssistant, + auth: FakeAuth, + setup_platform: PlatformSetup, + camera_device: None, +): """Tests a failure when refreshing the stream.""" now = utcnow() stream_1_expiration = now + datetime.timedelta(seconds=90) @@ -472,7 +531,7 @@ async def test_refresh_expired_stream_failure(hass, auth): # Next attempt to get a stream fetches a new url make_stream_url_response(expiration=stream_2_expiration, token_num=2), ] - await async_setup_camera(hass, DEVICE_TRAITS, auth=auth) + await setup_platform() assert await async_setup_component(hass, "stream", {}) assert len(hass.states.async_all()) == 1 @@ -510,7 +569,9 @@ async def test_refresh_expired_stream_failure(hass, auth): assert create_stream.called -async def test_camera_web_rtc(hass, auth, hass_ws_client): +async def test_camera_web_rtc( + hass, auth, hass_ws_client, webrtc_camera_device, setup_platform +): """Test a basic camera that supports web rtc.""" expiration = utcnow() + datetime.timedelta(seconds=100) auth.responses = [ @@ -524,21 +585,7 @@ async def test_camera_web_rtc(hass, auth, hass_ws_client): } ) ] - device_traits = { - "sdm.devices.traits.Info": { - "customName": "My Camera", - }, - "sdm.devices.traits.CameraLiveStream": { - "maxVideoResolution": { - "width": 640, - "height": 480, - }, - "videoCodecs": ["H264"], - "audioCodecs": ["AAC"], - "supportedProtocols": ["WEB_RTC"], - }, - } - await async_setup_camera(hass, device_traits, auth=auth) + await setup_platform() assert len(hass.states.async_all()) == 1 cam = hass.states.get("camera.my_camera") @@ -567,9 +614,11 @@ async def test_camera_web_rtc(hass, auth, hass_ws_client): await async_get_image(hass, width=1024, height=768) -async def test_camera_web_rtc_unsupported(hass, auth, hass_ws_client): +async def test_camera_web_rtc_unsupported( + hass, auth, hass_ws_client, camera_device, setup_platform +): """Test a basic camera that supports web rtc.""" - await async_setup_camera(hass, DEVICE_TRAITS, auth=auth) + await setup_platform() assert len(hass.states.async_all()) == 1 cam = hass.states.get("camera.my_camera") @@ -595,26 +644,14 @@ async def test_camera_web_rtc_unsupported(hass, auth, hass_ws_client): assert msg["error"]["message"].startswith("Camera does not support WebRTC") -async def test_camera_web_rtc_offer_failure(hass, auth, hass_ws_client): +async def test_camera_web_rtc_offer_failure( + hass, auth, hass_ws_client, webrtc_camera_device, setup_platform +): """Test a basic camera that supports web rtc.""" auth.responses = [ aiohttp.web.Response(status=HTTPStatus.BAD_REQUEST), ] - device_traits = { - "sdm.devices.traits.Info": { - "customName": "My Camera", - }, - "sdm.devices.traits.CameraLiveStream": { - "maxVideoResolution": { - "width": 640, - "height": 480, - }, - "videoCodecs": ["H264"], - "audioCodecs": ["AAC"], - "supportedProtocols": ["WEB_RTC"], - }, - } - await async_setup_camera(hass, device_traits, auth=auth) + await setup_platform() assert len(hass.states.async_all()) == 1 cam = hass.states.get("camera.my_camera") @@ -639,7 +676,9 @@ async def test_camera_web_rtc_offer_failure(hass, auth, hass_ws_client): assert msg["error"]["message"].startswith("Nest API error") -async def test_camera_multiple_streams(hass, auth, hass_ws_client, mock_create_stream): +async def test_camera_multiple_streams( + hass, auth, hass_ws_client, create_device, setup_platform, mock_create_stream +): """Test a camera supporting multiple stream types.""" expiration = utcnow() + datetime.timedelta(seconds=100) auth.responses = [ @@ -656,21 +695,23 @@ async def test_camera_multiple_streams(hass, auth, hass_ws_client, mock_create_s } ), ] - device_traits = { - "sdm.devices.traits.Info": { - "customName": "My Camera", - }, - "sdm.devices.traits.CameraLiveStream": { - "maxVideoResolution": { - "width": 640, - "height": 480, + create_device.create( + { + "sdm.devices.traits.Info": { + "customName": "My Camera", }, - "videoCodecs": ["H264"], - "audioCodecs": ["AAC"], - "supportedProtocols": ["WEB_RTC", "RTSP"], - }, - } - await async_setup_camera(hass, device_traits, auth=auth) + "sdm.devices.traits.CameraLiveStream": { + "maxVideoResolution": { + "width": 640, + "height": 480, + }, + "videoCodecs": ["H264"], + "audioCodecs": ["AAC"], + "supportedProtocols": ["WEB_RTC", "RTSP"], + }, + } + ) + await setup_platform() assert len(hass.states.async_all()) == 1 cam = hass.states.get("camera.my_camera") From 8d2fb72cc3f820550c1cf880a24186687e844bdc Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 18 Feb 2022 08:09:22 +0100 Subject: [PATCH 0765/1098] Add type ignore error codes [core] (#66773) --- homeassistant/block_async_io.py | 2 +- homeassistant/bootstrap.py | 2 +- homeassistant/config.py | 4 ++-- homeassistant/config_entries.py | 2 +- homeassistant/core.py | 8 ++++---- homeassistant/data_entry_flow.py | 8 ++++---- homeassistant/loader.py | 2 +- homeassistant/runner.py | 8 ++++---- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/homeassistant/block_async_io.py b/homeassistant/block_async_io.py index 9358fe73110935..29e31ae4a88f2a 100644 --- a/homeassistant/block_async_io.py +++ b/homeassistant/block_async_io.py @@ -8,7 +8,7 @@ def enable() -> None: """Enable the detection of blocking calls in the event loop.""" # Prevent urllib3 and requests doing I/O in event loop - HTTPConnection.putrequest = protect_loop(HTTPConnection.putrequest) # type: ignore + HTTPConnection.putrequest = protect_loop(HTTPConnection.putrequest) # type: ignore[assignment] # Prevent sleeping in event loop. Non-strict since 2022.02 time.sleep = protect_loop(time.sleep, strict=False) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index fbe7a6b005fd31..b1b638f844aa27 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -321,7 +321,7 @@ def async_enable_logging( logging.getLogger("aiohttp.access").setLevel(logging.WARNING) sys.excepthook = lambda *args: logging.getLogger(None).exception( - "Uncaught exception", exc_info=args # type: ignore + "Uncaught exception", exc_info=args # type: ignore[arg-type] ) threading.excepthook = lambda args: logging.getLogger(None).exception( "Uncaught thread exception", diff --git a/homeassistant/config.py b/homeassistant/config.py index 74a8055e97188b..17a1c0fbfa1e0a 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -816,7 +816,7 @@ async def async_process_component_config( # noqa: C901 config_validator, "async_validate_config" ): try: - return await config_validator.async_validate_config( # type: ignore + return await config_validator.async_validate_config( # type: ignore[no-any-return] hass, config ) except (vol.Invalid, HomeAssistantError) as ex: @@ -829,7 +829,7 @@ async def async_process_component_config( # noqa: C901 # No custom config validator, proceed with schema validation if hasattr(component, "CONFIG_SCHEMA"): try: - return component.CONFIG_SCHEMA(config) # type: ignore + return component.CONFIG_SCHEMA(config) # type: ignore[no-any-return] except vol.Invalid as ex: async_log_exception(ex, domain, config, hass, integration.documentation) return None diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 400cea3f78ca26..57c837178a48ef 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1213,7 +1213,7 @@ def _async_abort_entries_match( match_dict = {} # Match any entry for entry in self._async_current_entries(include_ignore=False): if all( - item in ChainMap(entry.options, entry.data).items() # type: ignore + item in ChainMap(entry.options, entry.data).items() # type: ignore[arg-type] for item in match_dict.items() ): raise data_entry_flow.AbortFlow("already_configured") diff --git a/homeassistant/core.py b/homeassistant/core.py index 5685b479d1c3f1..38a0bbeb73cc44 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -238,8 +238,8 @@ class HomeAssistant: """Root object of the Home Assistant home automation.""" auth: AuthManager - http: HomeAssistantHTTP = None # type: ignore - config_entries: ConfigEntries = None # type: ignore + http: HomeAssistantHTTP = None # type: ignore[assignment] + config_entries: ConfigEntries = None # type: ignore[assignment] def __init__(self) -> None: """Initialize new Home Assistant object.""" @@ -765,7 +765,7 @@ def __repr__(self) -> str: def __eq__(self, other: Any) -> bool: """Return the comparison.""" - return ( # type: ignore + return ( # type: ignore[no-any-return] self.__class__ == other.__class__ and self.event_type == other.event_type and self.data == other.data @@ -1125,7 +1125,7 @@ def from_dict(cls: type[_StateT], json_dict: dict[str, Any]) -> _StateT | None: def __eq__(self, other: Any) -> bool: """Return the comparison of the state.""" - return ( # type: ignore + return ( # type: ignore[no-any-return] self.__class__ == other.__class__ and self.entity_id == other.entity_id and self.state == other.state diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index 734a568ce4e1f1..b69cf44dc6c319 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -378,11 +378,11 @@ class FlowHandler: # While not purely typed, it makes typehinting more useful for us # and removes the need for constant None checks or asserts. - flow_id: str = None # type: ignore - hass: HomeAssistant = None # type: ignore - handler: str = None # type: ignore + flow_id: str = None # type: ignore[assignment] + hass: HomeAssistant = None # type: ignore[assignment] + handler: str = None # type: ignore[assignment] # Ensure the attribute has a subscriptable, but immutable, default value. - context: dict[str, Any] = MappingProxyType({}) # type: ignore + context: dict[str, Any] = MappingProxyType({}) # type: ignore[assignment] # Set by _async_create_flow callback init_step = "init" diff --git a/homeassistant/loader.py b/homeassistant/loader.py index c02fa18eefbb5f..f5c68897e2e25f 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -681,7 +681,7 @@ def _load_file( Async friendly. """ with suppress(KeyError): - return hass.data[DATA_COMPONENTS][comp_or_platform] # type: ignore + return hass.data[DATA_COMPONENTS][comp_or_platform] # type: ignore[no-any-return] if (cache := hass.data.get(DATA_COMPONENTS)) is None: if not _async_mount_config_dir(hass): diff --git a/homeassistant/runner.py b/homeassistant/runner.py index 6ee0b8fefe1923..472d399713dba8 100644 --- a/homeassistant/runner.py +++ b/homeassistant/runner.py @@ -59,7 +59,7 @@ def __init__(self, debug: bool) -> None: @property def loop_name(self) -> str: """Return name of the loop.""" - return self._loop_factory.__name__ # type: ignore + return self._loop_factory.__name__ # type: ignore[no-any-return] def new_event_loop(self) -> asyncio.AbstractEventLoop: """Get the event loop.""" @@ -72,7 +72,7 @@ def new_event_loop(self) -> asyncio.AbstractEventLoop: thread_name_prefix="SyncWorker", max_workers=MAX_EXECUTOR_WORKERS ) loop.set_default_executor(executor) - loop.set_default_executor = warn_use( # type: ignore + loop.set_default_executor = warn_use( # type: ignore[assignment] loop.set_default_executor, "sets default executor on the event loop" ) return loop @@ -89,11 +89,11 @@ def _async_loop_exception_handler(_: Any, context: dict[str, Any]) -> None: if source_traceback := context.get("source_traceback"): stack_summary = "".join(traceback.format_list(source_traceback)) logger.error( - "Error doing job: %s: %s", context["message"], stack_summary, **kwargs # type: ignore + "Error doing job: %s: %s", context["message"], stack_summary, **kwargs # type: ignore[arg-type] ) return - logger.error("Error doing job: %s", context["message"], **kwargs) # type: ignore + logger.error("Error doing job: %s", context["message"], **kwargs) # type: ignore[arg-type] async def setup_and_run_hass(runtime_config: RuntimeConfig) -> int: From ac502489382ab310fe17942b1f07b657d491d2a0 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 18 Feb 2022 08:10:25 +0100 Subject: [PATCH 0766/1098] Add type ignore error codes [other] (#66781) --- homeassistant/components/diagnostics/util.py | 2 +- homeassistant/components/energy/data.py | 2 +- homeassistant/components/energy/sensor.py | 6 +++--- homeassistant/components/group/media_player.py | 6 +++--- homeassistant/components/sensor/__init__.py | 2 +- homeassistant/components/sensor/recorder.py | 4 ++-- homeassistant/components/tts/media_source.py | 2 +- homeassistant/components/zeroconf/usage.py | 4 ++-- homeassistant/components/zone/__init__.py | 2 +- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/diagnostics/util.py b/homeassistant/components/diagnostics/util.py index 84971ba89f1c24..ba4f3d20f9a0cf 100644 --- a/homeassistant/components/diagnostics/util.py +++ b/homeassistant/components/diagnostics/util.py @@ -12,7 +12,7 @@ @overload -def async_redact_data(data: Mapping, to_redact: Iterable[Any]) -> dict: # type: ignore +def async_redact_data(data: Mapping, to_redact: Iterable[Any]) -> dict: # type: ignore[misc] ... diff --git a/homeassistant/components/energy/data.py b/homeassistant/components/energy/data.py index f8c14ed8b73b2e..d07d3406073647 100644 --- a/homeassistant/components/energy/data.py +++ b/homeassistant/components/energy/data.py @@ -291,7 +291,7 @@ async def async_update(self, update: EnergyPreferencesUpdate) -> None: "device_consumption", ): if key in update: - data[key] = update[key] # type: ignore + data[key] = update[key] # type: ignore[misc] self.data = data self._store.async_delay_save(lambda: cast(dict, self.data), 60) diff --git a/homeassistant/components/energy/sensor.py b/homeassistant/components/energy/sensor.py index c0d9ffcea4a3e6..f8591e5c23f781 100644 --- a/homeassistant/components/energy/sensor.py +++ b/homeassistant/components/energy/sensor.py @@ -148,17 +148,17 @@ async def finish() -> None: self._process_sensor_data( adapter, # Opting out of the type complexity because can't get it to work - energy_source, # type: ignore + energy_source, # type: ignore[arg-type] to_add, to_remove, ) continue - for flow in energy_source[adapter.flow_type]: # type: ignore + for flow in energy_source[adapter.flow_type]: # type: ignore[typeddict-item] self._process_sensor_data( adapter, # Opting out of the type complexity because can't get it to work - flow, # type: ignore + flow, # type: ignore[arg-type] to_add, to_remove, ) diff --git a/homeassistant/components/group/media_player.py b/homeassistant/components/group/media_player.py index 976b8cc69f8066..509c0cb40835f3 100644 --- a/homeassistant/components/group/media_player.py +++ b/homeassistant/components/group/media_player.py @@ -123,7 +123,7 @@ def async_on_state_change(self, event: EventType) -> None: """Update supported features and state when a new state is received.""" self.async_set_context(event.context) self.async_update_supported_features( - event.data.get("entity_id"), event.data.get("new_state") # type: ignore + event.data.get("entity_id"), event.data.get("new_state") # type: ignore[arg-type] ) self.async_update_state() @@ -361,14 +361,14 @@ async def async_turn_off(self) -> None: async def async_volume_up(self) -> None: """Turn volume up for media player(s).""" for entity in self._features[KEY_VOLUME]: - volume_level = self.hass.states.get(entity).attributes["volume_level"] # type: ignore + volume_level = self.hass.states.get(entity).attributes["volume_level"] # type: ignore[union-attr] if volume_level < 1: await self.async_set_volume_level(min(1, volume_level + 0.1)) async def async_volume_down(self) -> None: """Turn volume down for media player(s).""" for entity in self._features[KEY_VOLUME]: - volume_level = self.hass.states.get(entity).attributes["volume_level"] # type: ignore + volume_level = self.hass.states.get(entity).attributes["volume_level"] # type: ignore[union-attr] if volume_level > 0: await self.async_set_volume_level(max(0, volume_level - 0.1)) diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index c6c4d18d21d897..f374ebaeb38126 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -355,7 +355,7 @@ def unit_of_measurement(self) -> str | None: hasattr(self, "_attr_unit_of_measurement") and self._attr_unit_of_measurement is not None ): - return self._attr_unit_of_measurement # type: ignore + return self._attr_unit_of_measurement # type: ignore[unreachable] native_unit_of_measurement = self.native_unit_of_measurement diff --git a/homeassistant/components/sensor/recorder.py b/homeassistant/components/sensor/recorder.py index 50d09b207a01bf..635c5af62422f6 100644 --- a/homeassistant/components/sensor/recorder.py +++ b/homeassistant/components/sensor/recorder.py @@ -418,7 +418,7 @@ def _compile_statistics( # noqa: C901 ] history_list = {} if entities_full_history: - history_list = history.get_significant_states_with_session( # type: ignore + history_list = history.get_significant_states_with_session( # type: ignore[no-untyped-call] hass, session, start - datetime.timedelta.resolution, @@ -432,7 +432,7 @@ def _compile_statistics( # noqa: C901 if "sum" not in wanted_statistics[i.entity_id] ] if entities_significant_history: - _history_list = history.get_significant_states_with_session( # type: ignore + _history_list = history.get_significant_states_with_session( # type: ignore[no-untyped-call] hass, session, start - datetime.timedelta.resolution, diff --git a/homeassistant/components/tts/media_source.py b/homeassistant/components/tts/media_source.py index 43c3998849c897..5e595ca42b75c6 100644 --- a/homeassistant/components/tts/media_source.py +++ b/homeassistant/components/tts/media_source.py @@ -56,7 +56,7 @@ async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia: manager: SpeechManager = self.hass.data[DOMAIN] try: - url = await manager.async_get_url_path(**kwargs) # type: ignore + url = await manager.async_get_url_path(**kwargs) # type: ignore[arg-type] except HomeAssistantError as err: raise Unresolvable(str(err)) from err diff --git a/homeassistant/components/zeroconf/usage.py b/homeassistant/components/zeroconf/usage.py index ab0a0eaf9a77cd..47798be3def4e4 100644 --- a/homeassistant/components/zeroconf/usage.py +++ b/homeassistant/components/zeroconf/usage.py @@ -23,5 +23,5 @@ def new_zeroconf_new(self: zeroconf.Zeroconf, *k: Any, **kw: Any) -> HaZeroconf: def new_zeroconf_init(self: zeroconf.Zeroconf, *k: Any, **kw: Any) -> None: return - zeroconf.Zeroconf.__new__ = new_zeroconf_new # type: ignore - zeroconf.Zeroconf.__init__ = new_zeroconf_init # type: ignore + zeroconf.Zeroconf.__new__ = new_zeroconf_new # type: ignore[assignment] + zeroconf.Zeroconf.__init__ = new_zeroconf_init # type: ignore[assignment] diff --git a/homeassistant/components/zone/__init__.py b/homeassistant/components/zone/__init__.py index dd327acbf7582d..ef2d21281d19e9 100644 --- a/homeassistant/components/zone/__init__.py +++ b/homeassistant/components/zone/__init__.py @@ -122,7 +122,7 @@ def async_active_zone( continue within_zone = zone_dist - radius < zone.attributes[ATTR_RADIUS] - closer_zone = closest is None or zone_dist < min_dist # type: ignore + closer_zone = closest is None or zone_dist < min_dist # type: ignore[unreachable] smaller_zone = ( zone_dist == min_dist and zone.attributes[ATTR_RADIUS] From 703d01e7720bec4a42c995e8985adf3812d28c72 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Thu, 17 Feb 2022 23:12:05 -0800 Subject: [PATCH 0767/1098] Bump grpcio to 1.44.0 (#66787) --- homeassistant/package_constraints.txt | 2 +- script/gen_requirements_all.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 4d3670ac9b4242..a2ad73e96c5f31 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -49,7 +49,7 @@ httplib2>=0.19.0 # gRPC is an implicit dependency that we want to make explicit so we manage # upgrades intentionally. It is a large package to build from source and we # want to ensure we have wheels built. -grpcio==1.43.0 +grpcio==1.44.0 # libcst >=0.4.0 requires a newer Rust than we currently have available, # thus our wheels builds fail. This pins it to the last working version, diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index eed5a5a5946b29..95a5999aaf11ab 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -77,7 +77,7 @@ # gRPC is an implicit dependency that we want to make explicit so we manage # upgrades intentionally. It is a large package to build from source and we # want to ensure we have wheels built. -grpcio==1.43.0 +grpcio==1.44.0 # libcst >=0.4.0 requires a newer Rust than we currently have available, # thus our wheels builds fail. This pins it to the last working version, From 82ebb7047f54347982531f16f009f618dc06fcc2 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 18 Feb 2022 02:36:27 -0500 Subject: [PATCH 0768/1098] Bump zwave-js-server-python to 0.35.0 (#66785) * Bump zwave-js-server-python to 0.35.0 * Remove support for new event type which should go in a separate PR --- .../components/zwave_js/diagnostics.py | 9 +- .../components/zwave_js/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../fixtures/aeon_smart_switch_6_state.json | 6 +- .../aeotec_radiator_thermostat_state.json | 2 +- .../fixtures/bulb_6_multi_color_state.json | 2 +- .../fixtures/chain_actuator_zws12_state.json | 2 +- .../climate_eurotronic_spirit_z_state.json | 2 +- .../fixtures/climate_heatit_z_trm3_state.json | 2 +- ...ate_radio_thermostat_ct100_plus_state.json | 254 +++++++++--------- .../fixtures/cover_iblinds_v2_state.json | 2 +- .../zwave_js/fixtures/cover_zw062_state.json | 2 +- .../fixtures/eaton_rf9640_dimmer_state.json | 2 +- .../fixtures/ecolink_door_sensor_state.json | 2 +- .../zwave_js/fixtures/fan_ge_12730_state.json | 2 +- .../zwave_js/fixtures/fan_generic_state.json | 2 +- .../ge_in_wall_dimmer_switch_state.json | 162 +++++------ .../fixtures/hank_binary_switch_state.json | 6 +- .../fixtures/lock_august_asl03_state.json | 2 +- .../fixtures/lock_schlage_be469_state.json | 128 ++++----- .../fixtures/multisensor_6_state.json | 2 +- .../nortek_thermostat_added_event.json | 2 +- .../nortek_thermostat_removed_event.json | 5 +- .../fixtures/nortek_thermostat_state.json | 2 +- .../wallmote_central_scene_state.json | 174 ++++++------ tests/components/zwave_js/test_api.py | 30 ++- .../zwave_js/test_device_condition.py | 11 - tests/components/zwave_js/test_diagnostics.py | 18 +- tests/components/zwave_js/test_init.py | 27 +- tests/components/zwave_js/test_sensor.py | 10 +- 31 files changed, 447 insertions(+), 429 deletions(-) diff --git a/homeassistant/components/zwave_js/diagnostics.py b/homeassistant/components/zwave_js/diagnostics.py index 080fffe2107fd4..8b59c38d405b5c 100644 --- a/homeassistant/components/zwave_js/diagnostics.py +++ b/homeassistant/components/zwave_js/diagnostics.py @@ -8,8 +8,8 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_URL from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.device_registry import DeviceEntry from .const import DATA_CLIENT, DOMAIN from .helpers import get_home_and_node_id_from_device_entry @@ -26,7 +26,7 @@ async def async_get_config_entry_diagnostics( async def async_get_device_diagnostics( - hass: HomeAssistant, config_entry: ConfigEntry, device: DeviceEntry + hass: HomeAssistant, config_entry: ConfigEntry, device: dr.DeviceEntry ) -> NodeDataType: """Return diagnostics for a device.""" client: Client = hass.data[DOMAIN][config_entry.entry_id][DATA_CLIENT] @@ -42,8 +42,5 @@ async def async_get_device_diagnostics( "minSchemaVersion": client.version.min_schema_version, "maxSchemaVersion": client.version.max_schema_version, }, - "state": { - **node.data, - "values": [value.data for value in node.values.values()], - }, + "state": node.data, } diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index d6cc9938eba19f..e6975ddb47b0f7 100644 --- a/homeassistant/components/zwave_js/manifest.json +++ b/homeassistant/components/zwave_js/manifest.json @@ -3,7 +3,7 @@ "name": "Z-Wave JS", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zwave_js", - "requirements": ["zwave-js-server-python==0.34.0"], + "requirements": ["zwave-js-server-python==0.35.0"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["usb", "http", "websocket_api"], "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index 3f343d5d23f7f5..caa36d4dc46c2b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2559,7 +2559,7 @@ zigpy==0.43.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.34.0 +zwave-js-server-python==0.35.0 # homeassistant.components.zwave_me zwave_me_ws==0.1.23 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index faeba577532307..912e41fcd7d58f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1584,7 +1584,7 @@ zigpy-znp==0.7.0 zigpy==0.43.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.34.0 +zwave-js-server-python==0.35.0 # homeassistant.components.zwave_me zwave_me_ws==0.1.23 diff --git a/tests/components/zwave_js/fixtures/aeon_smart_switch_6_state.json b/tests/components/zwave_js/fixtures/aeon_smart_switch_6_state.json index c8d8f878c0b00e..1eb2baf3a0252a 100644 --- a/tests/components/zwave_js/fixtures/aeon_smart_switch_6_state.json +++ b/tests/components/zwave_js/fixtures/aeon_smart_switch_6_state.json @@ -10,7 +10,7 @@ "generic": {"key": 16, "label":"Binary Switch"}, "specific": {"key": 1, "label":"Binary Power Switch"}, "mandatorySupportedCCs": [], - "mandatoryControlCCs": [] + "mandatoryControlledCCs": [] }, "isListening": true, "isFrequentListening": false, @@ -50,10 +50,10 @@ "nodeId": 102, "index": 0, "installerIcon": 1792, - "userIcon": 1792 + "userIcon": 1792, + "commandClasses": [] } ], - "commandClasses": [], "values": [ { "endpoint": 0, diff --git a/tests/components/zwave_js/fixtures/aeotec_radiator_thermostat_state.json b/tests/components/zwave_js/fixtures/aeotec_radiator_thermostat_state.json index 27c3f991d33334..646363e331336e 100644 --- a/tests/components/zwave_js/fixtures/aeotec_radiator_thermostat_state.json +++ b/tests/components/zwave_js/fixtures/aeotec_radiator_thermostat_state.json @@ -10,7 +10,7 @@ "generic": {"key": 8, "label":"Thermostat"}, "specific": {"key": 6, "label":"Thermostat General V2"}, "mandatorySupportedCCs": [], - "mandatoryControlCCs": [] + "mandatoryControlledCCs": [] }, "isListening": false, "isFrequentListening": true, diff --git a/tests/components/zwave_js/fixtures/bulb_6_multi_color_state.json b/tests/components/zwave_js/fixtures/bulb_6_multi_color_state.json index 58608131e905f5..668d1b9dce9980 100644 --- a/tests/components/zwave_js/fixtures/bulb_6_multi_color_state.json +++ b/tests/components/zwave_js/fixtures/bulb_6_multi_color_state.json @@ -10,7 +10,7 @@ "generic": {"key": 17, "label":"Multilevel Switch"}, "specific": {"key": 1, "label":"Multilevel Power Switch"}, "mandatorySupportedCCs": [], - "mandatoryControlCCs": [] + "mandatoryControlledCCs": [] }, "isListening": true, "isFrequentListening": false, diff --git a/tests/components/zwave_js/fixtures/chain_actuator_zws12_state.json b/tests/components/zwave_js/fixtures/chain_actuator_zws12_state.json index cf7adddc21e1cc..a726175fa38e1a 100644 --- a/tests/components/zwave_js/fixtures/chain_actuator_zws12_state.json +++ b/tests/components/zwave_js/fixtures/chain_actuator_zws12_state.json @@ -10,7 +10,7 @@ "generic": {"key": 17, "label":"Multilevel Switch"}, "specific": {"key": 7, "label":"Motor Control Class C"}, "mandatorySupportedCCs": [], - "mandatoryControlCCs": [] + "mandatoryControlledCCs": [] }, "isListening": true, "isFrequentListening": false, diff --git a/tests/components/zwave_js/fixtures/climate_eurotronic_spirit_z_state.json b/tests/components/zwave_js/fixtures/climate_eurotronic_spirit_z_state.json index 8dff31a52256a8..afa216cac32acc 100644 --- a/tests/components/zwave_js/fixtures/climate_eurotronic_spirit_z_state.json +++ b/tests/components/zwave_js/fixtures/climate_eurotronic_spirit_z_state.json @@ -25,7 +25,7 @@ "Thermostat Setpoint", "Version" ], - "mandatoryControlCCs": [] + "mandatoryControlledCCs": [] }, "isListening": false, "isFrequentListening": true, diff --git a/tests/components/zwave_js/fixtures/climate_heatit_z_trm3_state.json b/tests/components/zwave_js/fixtures/climate_heatit_z_trm3_state.json index b26b69be9ad5e4..98c185fd8d5b9d 100644 --- a/tests/components/zwave_js/fixtures/climate_heatit_z_trm3_state.json +++ b/tests/components/zwave_js/fixtures/climate_heatit_z_trm3_state.json @@ -10,7 +10,7 @@ "generic": {"key": 8, "label":"Thermostat"}, "specific": {"key": 6, "label":"Thermostat General V2"}, "mandatorySupportedCCs": [], - "mandatoryControlCCs": [] + "mandatoryControlledCCs": [] }, "isListening": true, "isFrequentListening": false, diff --git a/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct100_plus_state.json b/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct100_plus_state.json index cd5a6bd4abe6e8..3805394dbce9b3 100644 --- a/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct100_plus_state.json +++ b/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct100_plus_state.json @@ -6,11 +6,11 @@ "status": 4, "ready": true, "deviceClass": { - "basic": {"key": 2, "label":"Static Controller"}, - "generic": {"key": 8, "label":"Thermostat"}, - "specific": {"key": 6, "label":"Thermostat General V2"}, + "basic": { "key": 2, "label": "Static Controller" }, + "generic": { "key": 8, "label": "Thermostat" }, + "specific": { "key": 6, "label": "Thermostat General V2" }, "mandatorySupportedCCs": [], - "mandatoryControlCCs": [] + "mandatoryControlledCCs": [] }, "isListening": true, "isFrequentListening": false, @@ -47,7 +47,129 @@ "nodeId": 13, "index": 0, "installerIcon": 4608, - "userIcon": 4608 + "userIcon": 4608, + "commandClasses": [ + { + "id": 49, + "name": "Multilevel Sensor", + "version": 5, + "isSecure": false + }, + { + "id": 64, + "name": "Thermostat Mode", + "version": 2, + "isSecure": false + }, + { + "id": 66, + "name": "Thermostat Operating State", + "version": 2, + "isSecure": false + }, + { + "id": 67, + "name": "Thermostat Setpoint", + "version": 2, + "isSecure": false + }, + { + "id": 68, + "name": "Thermostat Fan Mode", + "version": 1, + "isSecure": false + }, + { + "id": 69, + "name": "Thermostat Fan State", + "version": 1, + "isSecure": false + }, + { + "id": 89, + "name": "Association Group Information", + "version": 1, + "isSecure": false + }, + { + "id": 90, + "name": "Device Reset Locally", + "version": 1, + "isSecure": false + }, + { + "id": 94, + "name": "Z-Wave Plus Info", + "version": 2, + "isSecure": false + }, + { + "id": 96, + "name": "Multi Channel", + "version": 4, + "isSecure": false + }, + { + "id": 112, + "name": "Configuration", + "version": 1, + "isSecure": false + }, + { + "id": 114, + "name": "Manufacturer Specific", + "version": 2, + "isSecure": false + }, + { + "id": 115, + "name": "Powerlevel", + "version": 1, + "isSecure": false + }, + { + "id": 122, + "name": "Firmware Update Meta Data", + "version": 3, + "isSecure": false + }, + { + "id": 128, + "name": "Battery", + "version": 1, + "isSecure": false + }, + { + "id": 129, + "name": "Clock", + "version": 1, + "isSecure": false + }, + { + "id": 133, + "name": "Association", + "version": 2, + "isSecure": false + }, + { + "id": 134, + "name": "Version", + "version": 2, + "isSecure": false + }, + { + "id": 135, + "name": "Indicator", + "version": 1, + "isSecure": false + }, + { + "id": 142, + "name": "Multi Channel Association", + "version": 3, + "isSecure": false + } + ] }, { "nodeId": 13, @@ -57,128 +179,6 @@ }, { "nodeId": 13, "index": 2 } ], - "commandClasses": [ - { - "id": 49, - "name": "Multilevel Sensor", - "version": 5, - "isSecure": false - }, - { - "id": 64, - "name": "Thermostat Mode", - "version": 2, - "isSecure": false - }, - { - "id": 66, - "name": "Thermostat Operating State", - "version": 2, - "isSecure": false - }, - { - "id": 67, - "name": "Thermostat Setpoint", - "version": 2, - "isSecure": false - }, - { - "id": 68, - "name": "Thermostat Fan Mode", - "version": 1, - "isSecure": false - }, - { - "id": 69, - "name": "Thermostat Fan State", - "version": 1, - "isSecure": false - }, - { - "id": 89, - "name": "Association Group Information", - "version": 1, - "isSecure": false - }, - { - "id": 90, - "name": "Device Reset Locally", - "version": 1, - "isSecure": false - }, - { - "id": 94, - "name": "Z-Wave Plus Info", - "version": 2, - "isSecure": false - }, - { - "id": 96, - "name": "Multi Channel", - "version": 4, - "isSecure": false - }, - { - "id": 112, - "name": "Configuration", - "version": 1, - "isSecure": false - }, - { - "id": 114, - "name": "Manufacturer Specific", - "version": 2, - "isSecure": false - }, - { - "id": 115, - "name": "Powerlevel", - "version": 1, - "isSecure": false - }, - { - "id": 122, - "name": "Firmware Update Meta Data", - "version": 3, - "isSecure": false - }, - { - "id": 128, - "name": "Battery", - "version": 1, - "isSecure": false - }, - { - "id": 129, - "name": "Clock", - "version": 1, - "isSecure": false - }, - { - "id": 133, - "name": "Association", - "version": 2, - "isSecure": false - }, - { - "id": 134, - "name": "Version", - "version": 2, - "isSecure": false - }, - { - "id": 135, - "name": "Indicator", - "version": 1, - "isSecure": false - }, - { - "id": 142, - "name": "Multi Channel Association", - "version": 3, - "isSecure": false - } - ], "values": [ { "commandClassName": "Manufacturer Specific", diff --git a/tests/components/zwave_js/fixtures/cover_iblinds_v2_state.json b/tests/components/zwave_js/fixtures/cover_iblinds_v2_state.json index 35ce70f617aa76..42200c2f1d64d7 100644 --- a/tests/components/zwave_js/fixtures/cover_iblinds_v2_state.json +++ b/tests/components/zwave_js/fixtures/cover_iblinds_v2_state.json @@ -10,7 +10,7 @@ "generic": {"key": 17, "label":"Routing Slave"}, "specific": {"key": 0, "label":"Unused"}, "mandatorySupportedCCs": [], - "mandatoryControlCCs": [] + "mandatoryControlledCCs": [] }, "isListening": false, "isFrequentListening": true, diff --git a/tests/components/zwave_js/fixtures/cover_zw062_state.json b/tests/components/zwave_js/fixtures/cover_zw062_state.json index 9e7b05adc34bf9..a0ccd4de9c5da0 100644 --- a/tests/components/zwave_js/fixtures/cover_zw062_state.json +++ b/tests/components/zwave_js/fixtures/cover_zw062_state.json @@ -10,7 +10,7 @@ "generic": {"key": 64, "label":"Entry Control"}, "specific": {"key": 7, "label":"Secure Barrier Add-on"}, "mandatorySupportedCCs": [], - "mandatoryControlCCs": [] + "mandatoryControlledCCs": [] }, "isListening": true, "isFrequentListening": false, diff --git a/tests/components/zwave_js/fixtures/eaton_rf9640_dimmer_state.json b/tests/components/zwave_js/fixtures/eaton_rf9640_dimmer_state.json index b11d2bfd180898..3edb0494c37fc4 100644 --- a/tests/components/zwave_js/fixtures/eaton_rf9640_dimmer_state.json +++ b/tests/components/zwave_js/fixtures/eaton_rf9640_dimmer_state.json @@ -10,7 +10,7 @@ "generic": {"key": 17, "label":"Routing Slave"}, "specific": {"key": 1, "label":"Multilevel Power Switch"}, "mandatorySupportedCCs": [], - "mandatoryControlCCs": [] + "mandatoryControlledCCs": [] }, "isListening": true, "isFrequentListening": false, diff --git a/tests/components/zwave_js/fixtures/ecolink_door_sensor_state.json b/tests/components/zwave_js/fixtures/ecolink_door_sensor_state.json index 9c2befdf5e8dfb..ac32a9f99bb8e2 100644 --- a/tests/components/zwave_js/fixtures/ecolink_door_sensor_state.json +++ b/tests/components/zwave_js/fixtures/ecolink_door_sensor_state.json @@ -8,7 +8,7 @@ "generic": {"key": 32, "label":"Binary Sensor"}, "specific": {"key": 1, "label":"Routing Binary Sensor"}, "mandatorySupportedCCs": [], - "mandatoryControlCCs": [] + "mandatoryControlledCCs": [] }, "isListening": false, "isFrequentListening": false, diff --git a/tests/components/zwave_js/fixtures/fan_ge_12730_state.json b/tests/components/zwave_js/fixtures/fan_ge_12730_state.json index b6cf59b4226050..ddbff0f3ffa518 100644 --- a/tests/components/zwave_js/fixtures/fan_ge_12730_state.json +++ b/tests/components/zwave_js/fixtures/fan_ge_12730_state.json @@ -8,7 +8,7 @@ "generic": {"key": 17, "label":"Multilevel Switch"}, "specific": {"key": 1, "label":"Multilevel Power Switch"}, "mandatorySupportedCCs": [], - "mandatoryControlCCs": [] + "mandatoryControlledCCs": [] }, "isListening": true, "isFrequentListening": false, diff --git a/tests/components/zwave_js/fixtures/fan_generic_state.json b/tests/components/zwave_js/fixtures/fan_generic_state.json index 1f4f55dd22017c..d09848fb759f7b 100644 --- a/tests/components/zwave_js/fixtures/fan_generic_state.json +++ b/tests/components/zwave_js/fixtures/fan_generic_state.json @@ -10,7 +10,7 @@ "generic": {"key": 17, "label":"Multilevel Switch"}, "specific": {"key": 8, "label":"Fan Switch"}, "mandatorySupportedCCs": [], - "mandatoryControlCCs": [] + "mandatoryControlledCCs": [] }, "isListening": true, "isFrequentListening": false, diff --git a/tests/components/zwave_js/fixtures/ge_in_wall_dimmer_switch_state.json b/tests/components/zwave_js/fixtures/ge_in_wall_dimmer_switch_state.json index 58d3f0d06eca0c..d47896a980aaa0 100644 --- a/tests/components/zwave_js/fixtures/ge_in_wall_dimmer_switch_state.json +++ b/tests/components/zwave_js/fixtures/ge_in_wall_dimmer_switch_state.json @@ -64,7 +64,87 @@ }, "mandatorySupportedCCs": [32, 38, 39], "mandatoryControlledCCs": [] - } + }, + "commandClasses": [ + { + "id": 32, + "name": "Basic", + "version": 1, + "isSecure": false + }, + { + "id": 38, + "name": "Multilevel Switch", + "version": 2, + "isSecure": false + }, + { + "id": 43, + "name": "Scene Activation", + "version": 1, + "isSecure": false + }, + { + "id": 44, + "name": "Scene Actuator Configuration", + "version": 1, + "isSecure": false + }, + { + "id": 86, + "name": "CRC-16 Encapsulation", + "version": 1, + "isSecure": false + }, + { + "id": 89, + "name": "Association Group Information", + "version": 1, + "isSecure": false + }, + { + "id": 90, + "name": "Device Reset Locally", + "version": 1, + "isSecure": false + }, + { + "id": 94, + "name": "Z-Wave Plus Info", + "version": 2, + "isSecure": false + }, + { + "id": 112, + "name": "Configuration", + "version": 1, + "isSecure": false + }, + { + "id": 114, + "name": "Manufacturer Specific", + "version": 2, + "isSecure": false + }, + { + "id": 122, + "name": "Firmware Update Meta Data", + "version": 2, + "isSecure": false + }, + { + "id": 133, + "name": "Association", + "version": 2, + "isSecure": false + }, + { + "id": 134, + "name": "Version", + "version": 2, + "isSecure": false + } + ] } ], "values": [ @@ -557,86 +637,6 @@ "mandatorySupportedCCs": [32, 38, 39], "mandatoryControlledCCs": [] }, - "commandClasses": [ - { - "id": 32, - "name": "Basic", - "version": 1, - "isSecure": false - }, - { - "id": 38, - "name": "Multilevel Switch", - "version": 2, - "isSecure": false - }, - { - "id": 43, - "name": "Scene Activation", - "version": 1, - "isSecure": false - }, - { - "id": 44, - "name": "Scene Actuator Configuration", - "version": 1, - "isSecure": false - }, - { - "id": 86, - "name": "CRC-16 Encapsulation", - "version": 1, - "isSecure": false - }, - { - "id": 89, - "name": "Association Group Information", - "version": 1, - "isSecure": false - }, - { - "id": 90, - "name": "Device Reset Locally", - "version": 1, - "isSecure": false - }, - { - "id": 94, - "name": "Z-Wave Plus Info", - "version": 2, - "isSecure": false - }, - { - "id": 112, - "name": "Configuration", - "version": 1, - "isSecure": false - }, - { - "id": 114, - "name": "Manufacturer Specific", - "version": 2, - "isSecure": false - }, - { - "id": 122, - "name": "Firmware Update Meta Data", - "version": 2, - "isSecure": false - }, - { - "id": 133, - "name": "Association", - "version": 2, - "isSecure": false - }, - { - "id": 134, - "name": "Version", - "version": 2, - "isSecure": false - } - ], "interviewStage": "Complete", "deviceDatabaseUrl": "https://devices.zwave-js.io/?jumpTo=0x0063:0x4944:0x3038:5.26" } diff --git a/tests/components/zwave_js/fixtures/hank_binary_switch_state.json b/tests/components/zwave_js/fixtures/hank_binary_switch_state.json index e5f739d63a5f16..d27338db5a9222 100644 --- a/tests/components/zwave_js/fixtures/hank_binary_switch_state.json +++ b/tests/components/zwave_js/fixtures/hank_binary_switch_state.json @@ -10,7 +10,7 @@ "generic": {"key": 16, "label":"Binary Switch"}, "specific": {"key": 1, "label":"Binary Power Switch"}, "mandatorySupportedCCs": [], - "mandatoryControlCCs": [] + "mandatoryControlledCCs": [] }, "isListening": true, "isFrequentListening": false, @@ -60,10 +60,10 @@ "nodeId": 32, "index": 0, "installerIcon": 1792, - "userIcon": 1792 + "userIcon": 1792, + "commandClasses": [] } ], - "commandClasses": [], "values": [ { "commandClassName": "Binary Switch", diff --git a/tests/components/zwave_js/fixtures/lock_august_asl03_state.json b/tests/components/zwave_js/fixtures/lock_august_asl03_state.json index 2b218cd915b277..b22c21e4777219 100644 --- a/tests/components/zwave_js/fixtures/lock_august_asl03_state.json +++ b/tests/components/zwave_js/fixtures/lock_august_asl03_state.json @@ -10,7 +10,7 @@ "generic": {"key": 64, "label":"Entry Control"}, "specific": {"key": 3, "label":"Secure Keypad Door Lock"}, "mandatorySupportedCCs": [], - "mandatoryControlCCs": [] + "mandatoryControlledCCs": [] }, "isListening": false, "isFrequentListening": true, diff --git a/tests/components/zwave_js/fixtures/lock_schlage_be469_state.json b/tests/components/zwave_js/fixtures/lock_schlage_be469_state.json index f85a8e6b0056a6..fedee0f9cf1db8 100644 --- a/tests/components/zwave_js/fixtures/lock_schlage_be469_state.json +++ b/tests/components/zwave_js/fixtures/lock_schlage_be469_state.json @@ -8,7 +8,7 @@ "generic": {"key": 64, "label":"Entry Control"}, "specific": {"key": 3, "label":"Secure Keypad Door Lock"}, "mandatorySupportedCCs": [], - "mandatoryControlCCs": [] + "mandatoryControlledCCs": [] }, "isListening": false, "isFrequentListening": true, @@ -47,71 +47,71 @@ "endpoints": [ { "nodeId": 20, - "index": 0 + "index": 0, + "commandClasses": [ + { + "id": 98, + "name": "Door Lock", + "version": 1, + "isSecure": true + }, + { + "id": 99, + "name": "User Code", + "version": 1, + "isSecure": true + }, + { + "id": 112, + "name": "Configuration", + "version": 1, + "isSecure": true + }, + { + "id": 113, + "name": "Notification", + "version": 1, + "isSecure": true + }, + { + "id": 114, + "name": "Manufacturer Specific", + "version": 1, + "isSecure": false + }, + { + "id": 122, + "name": "Firmware Update Meta Data", + "version": 1, + "isSecure": false + }, + { + "id": 128, + "name": "Battery", + "version": 1, + "isSecure": true + }, + { + "id": 133, + "name": "Association", + "version": 1, + "isSecure": true + }, + { + "id": 134, + "name": "Version", + "version": 1, + "isSecure": false + }, + { + "id": 152, + "name": "Security", + "version": 1, + "isSecure": true + } + ] } ], - "commandClasses": [ - { - "id": 98, - "name": "Door Lock", - "version": 1, - "isSecure": true - }, - { - "id": 99, - "name": "User Code", - "version": 1, - "isSecure": true - }, - { - "id": 112, - "name": "Configuration", - "version": 1, - "isSecure": true - }, - { - "id": 113, - "name": "Notification", - "version": 1, - "isSecure": true - }, - { - "id": 114, - "name": "Manufacturer Specific", - "version": 1, - "isSecure": false - }, - { - "id": 122, - "name": "Firmware Update Meta Data", - "version": 1, - "isSecure": false - }, - { - "id": 128, - "name": "Battery", - "version": 1, - "isSecure": true - }, - { - "id": 133, - "name": "Association", - "version": 1, - "isSecure": true - }, - { - "id": 134, - "name": "Version", - "version": 1, - "isSecure": false - }, - { - "id": 152, - "name": "Security", - "version": 1, - "isSecure": true - } - ], "values": [ { "commandClassName": "Door Lock", diff --git a/tests/components/zwave_js/fixtures/multisensor_6_state.json b/tests/components/zwave_js/fixtures/multisensor_6_state.json index 2646fecfd3711b..634d8ec916990e 100644 --- a/tests/components/zwave_js/fixtures/multisensor_6_state.json +++ b/tests/components/zwave_js/fixtures/multisensor_6_state.json @@ -10,7 +10,7 @@ "generic": {"key": 21, "label":"Multilevel Sensor"}, "specific": {"key": 1, "label":"Routing Multilevel Sensor"}, "mandatorySupportedCCs": [], - "mandatoryControlCCs": [] + "mandatoryControlledCCs": [] }, "isListening": true, "isFrequentListening": false, diff --git a/tests/components/zwave_js/fixtures/nortek_thermostat_added_event.json b/tests/components/zwave_js/fixtures/nortek_thermostat_added_event.json index 98ae03afbf2e4c..598650b863c610 100644 --- a/tests/components/zwave_js/fixtures/nortek_thermostat_added_event.json +++ b/tests/components/zwave_js/fixtures/nortek_thermostat_added_event.json @@ -11,7 +11,7 @@ "generic": {"key": 8, "label":"Thermostat"}, "specific": {"key": 6, "label":"Thermostat General V2"}, "mandatorySupportedCCs": [], - "mandatoryControlCCs": [] + "mandatoryControlledCCs": [] }, "neighbors": [], "interviewAttempts": 1, diff --git a/tests/components/zwave_js/fixtures/nortek_thermostat_removed_event.json b/tests/components/zwave_js/fixtures/nortek_thermostat_removed_event.json index 01bad6c4a8fb18..44b1379ca82e42 100644 --- a/tests/components/zwave_js/fixtures/nortek_thermostat_removed_event.json +++ b/tests/components/zwave_js/fixtures/nortek_thermostat_removed_event.json @@ -11,7 +11,7 @@ "generic": {"key": 8, "label":"Thermostat"}, "specific": {"key": 6, "label":"Thermostat General V2"}, "mandatorySupportedCCs": [], - "mandatoryControlCCs": [] + "mandatoryControlledCCs": [] }, "isListening": false, "isFrequentListening": true, @@ -274,5 +274,6 @@ } } ] - } + }, + "replaced": false } \ No newline at end of file diff --git a/tests/components/zwave_js/fixtures/nortek_thermostat_state.json b/tests/components/zwave_js/fixtures/nortek_thermostat_state.json index 4e6ca17e01386f..a3b34aeedf0c7a 100644 --- a/tests/components/zwave_js/fixtures/nortek_thermostat_state.json +++ b/tests/components/zwave_js/fixtures/nortek_thermostat_state.json @@ -10,7 +10,7 @@ "generic": {"key": 8, "label":"Thermostat"}, "specific": {"key": 6, "label":"Thermostat General V2"}, "mandatorySupportedCCs": [], - "mandatoryControlCCs": [] + "mandatoryControlledCCs": [] }, "isListening": false, "isFrequentListening": true, diff --git a/tests/components/zwave_js/fixtures/wallmote_central_scene_state.json b/tests/components/zwave_js/fixtures/wallmote_central_scene_state.json index 22eb05c9ce5d2f..f5560dd7e78d89 100644 --- a/tests/components/zwave_js/fixtures/wallmote_central_scene_state.json +++ b/tests/components/zwave_js/fixtures/wallmote_central_scene_state.json @@ -80,98 +80,98 @@ "aggregatedEndpointCount": 0, "interviewAttempts": 1, "interviewStage": "NodeInfo", - "commandClasses": [ - { - "id": 89, - "name": "Association Group Information", - "version": 1, - "isSecure": false - }, - { - "id": 90, - "name": "Device Reset Locally", - "version": 1, - "isSecure": false - }, - { - "id": 91, - "name": "Central Scene", - "version": 2, - "isSecure": false - }, - { - "id": 94, - "name": "Z-Wave Plus Info", - "version": 2, - "isSecure": false - }, - { - "id": 96, - "name": "Multi Channel", - "version": 4, - "isSecure": false - }, - { - "id": 112, - "name": "Configuration", - "version": 1, - "isSecure": false - }, - { - "id": 113, - "name": "Notification", - "version": 4, - "isSecure": false - }, - { - "id": 114, - "name": "Manufacturer Specific", - "version": 2, - "isSecure": false - }, - { - "id": 122, - "name": "Firmware Update Meta Data", - "version": 2, - "isSecure": false - }, - { - "id": 128, - "name": "Battery", - "version": 1, - "isSecure": false - }, - { - "id": 132, - "name": "Wake Up", - "version": 1, - "isSecure": false - }, - { - "id": 133, - "name": "Association", - "version": 2, - "isSecure": false - }, - { - "id": 134, - "name": "Version", - "version": 2, - "isSecure": false - }, - { - "id": 142, - "name": "Multi Channel Association", - "version": 3, - "isSecure": false - } - ], "endpoints": [ { "nodeId": 35, "index": 0, "installerIcon": 7172, - "userIcon": 7172 + "userIcon": 7172, + "commandClasses": [ + { + "id": 89, + "name": "Association Group Information", + "version": 1, + "isSecure": false + }, + { + "id": 90, + "name": "Device Reset Locally", + "version": 1, + "isSecure": false + }, + { + "id": 91, + "name": "Central Scene", + "version": 2, + "isSecure": false + }, + { + "id": 94, + "name": "Z-Wave Plus Info", + "version": 2, + "isSecure": false + }, + { + "id": 96, + "name": "Multi Channel", + "version": 4, + "isSecure": false + }, + { + "id": 112, + "name": "Configuration", + "version": 1, + "isSecure": false + }, + { + "id": 113, + "name": "Notification", + "version": 4, + "isSecure": false + }, + { + "id": 114, + "name": "Manufacturer Specific", + "version": 2, + "isSecure": false + }, + { + "id": 122, + "name": "Firmware Update Meta Data", + "version": 2, + "isSecure": false + }, + { + "id": 128, + "name": "Battery", + "version": 1, + "isSecure": false + }, + { + "id": 132, + "name": "Wake Up", + "version": 1, + "isSecure": false + }, + { + "id": 133, + "name": "Association", + "version": 2, + "isSecure": false + }, + { + "id": 134, + "name": "Version", + "version": 2, + "isSecure": false + }, + { + "id": 142, + "name": "Multi Channel Association", + "version": 3, + "isSecure": false + } + ] }, { "nodeId": 35, diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index 8362854a5f6c0f..1596b099ab1adf 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -478,7 +478,15 @@ async def test_add_node( event = Event( type="interview failed", - data={"source": "node", "event": "interview failed", "nodeId": 67}, + data={ + "source": "node", + "event": "interview failed", + "nodeId": 67, + "args": { + "errorMessage": "error", + "isFinal": True, + }, + }, ) client.driver.receive_event(event) @@ -1610,7 +1618,15 @@ async def test_replace_failed_node( event = Event( type="interview failed", - data={"source": "node", "event": "interview failed", "nodeId": 67}, + data={ + "source": "node", + "event": "interview failed", + "nodeId": 67, + "args": { + "errorMessage": "error", + "isFinal": True, + }, + }, ) client.driver.receive_event(event) @@ -2193,7 +2209,15 @@ async def test_refresh_node_info( event = Event( type="interview failed", - data={"source": "node", "event": "interview failed", "nodeId": 52}, + data={ + "source": "node", + "event": "interview failed", + "nodeId": 52, + "args": { + "errorMessage": "error", + "isFinal": True, + }, + }, ) client.driver.receive_event(event) diff --git a/tests/components/zwave_js/test_device_condition.py b/tests/components/zwave_js/test_device_condition.py index da6b4b1545928e..2161c8e6fe4ecf 100644 --- a/tests/components/zwave_js/test_device_condition.py +++ b/tests/components/zwave_js/test_device_condition.py @@ -215,17 +215,6 @@ async def test_node_status_state( assert len(calls) == 4 assert calls[3].data["some"] == "dead - event - test_event4" - event = Event( - "unknown", - data={ - "source": "node", - "event": "unknown", - "nodeId": lock_schlage_be469.node_id, - }, - ) - lock_schlage_be469.receive_event(event) - await hass.async_block_till_done() - async def test_config_parameter_state( hass, client, lock_schlage_be469, integration, calls diff --git a/tests/components/zwave_js/test_diagnostics.py b/tests/components/zwave_js/test_diagnostics.py index b41292a15fc1ba..332c8c846353e2 100644 --- a/tests/components/zwave_js/test_diagnostics.py +++ b/tests/components/zwave_js/test_diagnostics.py @@ -2,9 +2,7 @@ from unittest.mock import patch import pytest -from zwave_js_server.const import CommandClass from zwave_js_server.event import Event -from zwave_js_server.model.value import _get_value_id_from_dict, get_value_id from homeassistant.components.zwave_js.diagnostics import async_get_device_diagnostics from homeassistant.components.zwave_js.helpers import get_device_id @@ -43,9 +41,6 @@ async def test_device_diagnostics( assert device # Update a value and ensure it is reflected in the node state - value_id = get_value_id( - multisensor_6, CommandClass.SENSOR_MULTILEVEL, PROPERTY_ULTRAVIOLET - ) event = Event( type="value updated", data={ @@ -75,18 +70,7 @@ async def test_device_diagnostics( "maxSchemaVersion": 0, } - # Assert that the data returned doesn't match the stale node state data - assert diagnostics_data["state"] != multisensor_6.data - - # Replace data for the value we updated and assert the new node data is the same - # as what's returned - updated_node_data = multisensor_6.data.copy() - for idx, value in enumerate(updated_node_data["values"]): - if _get_value_id_from_dict(multisensor_6, value) == value_id: - updated_node_data["values"][idx] = multisensor_6.values[ - value_id - ].data.copy() - assert diagnostics_data["state"] == updated_node_data + assert diagnostics_data["state"] == multisensor_6.data async def test_device_diagnostics_error(hass, integration): diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index d08c680dfe2d10..3780b2654f9487 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -196,12 +196,16 @@ async def test_on_node_added_not_ready( assert len(hass.states.async_all()) == 0 assert not dev_reg.devices + node_state = deepcopy(zp3111_not_ready_state) + node_state["isSecure"] = False + event = Event( type="node added", data={ "source": "controller", "event": "node added", - "node": deepcopy(zp3111_not_ready_state), + "node": node_state, + "result": {}, }, ) client.driver.receive_event(event) @@ -317,12 +321,16 @@ async def test_existing_node_not_replaced_when_not_ready( assert state.name == "Custom Entity Name" assert not hass.states.get(motion_entity) + node_state = deepcopy(zp3111_not_ready_state) + node_state["isSecure"] = False + event = Event( type="node added", data={ "source": "controller", "event": "node added", - "node": deepcopy(zp3111_not_ready_state), + "node": node_state, + "result": {}, }, ) client.driver.receive_event(event) @@ -838,9 +846,14 @@ async def test_node_removed(hass, multisensor_6_state, client, integration): dev_reg = dr.async_get(hass) node = Node(client, deepcopy(multisensor_6_state)) device_id = f"{client.driver.controller.home_id}-{node.node_id}" - event = {"node": node} + event = { + "source": "controller", + "event": "node added", + "node": node.data, + "result": {}, + } - client.driver.controller.emit("node added", event) + client.driver.controller.receive_event(Event("node added", event)) await hass.async_block_till_done() old_device = dev_reg.async_get_device(identifiers={(DOMAIN, device_id)}) assert old_device.id @@ -907,7 +920,7 @@ async def test_replace_same_node( "index": 0, "status": 4, "ready": False, - "isSecure": "unknown", + "isSecure": False, "interviewAttempts": 1, "endpoints": [{"nodeId": node_id, "index": 0, "deviceClass": None}], "values": [], @@ -922,6 +935,7 @@ async def test_replace_same_node( "timeoutResponse": 0, }, }, + "result": {}, }, ) @@ -1022,7 +1036,7 @@ async def test_replace_different_node( "index": 0, "status": 4, "ready": False, - "isSecure": "unknown", + "isSecure": False, "interviewAttempts": 1, "endpoints": [ {"nodeId": multisensor_6.node_id, "index": 0, "deviceClass": None} @@ -1039,6 +1053,7 @@ async def test_replace_different_node( "timeoutResponse": 0, }, }, + "result": {}, }, ) diff --git a/tests/components/zwave_js/test_sensor.py b/tests/components/zwave_js/test_sensor.py index 2d120411513ca3..c632f0fca177d8 100644 --- a/tests/components/zwave_js/test_sensor.py +++ b/tests/components/zwave_js/test_sensor.py @@ -220,7 +220,15 @@ async def test_node_status_sensor_not_ready( assert hass.states.get(NODE_STATUS_ENTITY).state == "alive" # Mark node as ready - event = Event("ready", {"nodeState": lock_id_lock_as_id150_state}) + event = Event( + "ready", + { + "source": "node", + "event": "ready", + "nodeId": node.node_id, + "nodeState": lock_id_lock_as_id150_state, + }, + ) node.receive_event(event) assert node.ready assert hass.states.get(NODE_STATUS_ENTITY) From e26488b1caf41aa820ac0db5c6b17dbce4928e5e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 18 Feb 2022 09:03:41 +0100 Subject: [PATCH 0769/1098] Add config flow to MJPEG IP Camera (#66607) --- .coveragerc | 1 + homeassistant/components/agent_dvr/camera.py | 2 +- .../components/android_ip_webcam/__init__.py | 2 +- homeassistant/components/axis/camera.py | 2 +- homeassistant/components/mjpeg/__init__.py | 43 +- homeassistant/components/mjpeg/camera.py | 76 +-- homeassistant/components/mjpeg/config_flow.py | 240 ++++++++++ homeassistant/components/mjpeg/const.py | 14 + homeassistant/components/mjpeg/manifest.json | 3 +- homeassistant/components/mjpeg/strings.json | 42 ++ .../components/mjpeg/translations/en.json | 42 ++ homeassistant/components/mjpeg/util.py | 18 + homeassistant/components/motioneye/camera.py | 2 +- homeassistant/components/zoneminder/camera.py | 2 +- homeassistant/generated/config_flows.py | 1 + tests/components/mjpeg/__init__.py | 1 + tests/components/mjpeg/conftest.py | 79 ++++ tests/components/mjpeg/test_config_flow.py | 441 ++++++++++++++++++ tests/components/mjpeg/test_init.py | 99 ++++ 19 files changed, 1073 insertions(+), 37 deletions(-) create mode 100644 homeassistant/components/mjpeg/config_flow.py create mode 100644 homeassistant/components/mjpeg/const.py create mode 100644 homeassistant/components/mjpeg/strings.json create mode 100644 homeassistant/components/mjpeg/translations/en.json create mode 100644 homeassistant/components/mjpeg/util.py create mode 100644 tests/components/mjpeg/__init__.py create mode 100644 tests/components/mjpeg/conftest.py create mode 100644 tests/components/mjpeg/test_config_flow.py create mode 100644 tests/components/mjpeg/test_init.py diff --git a/.coveragerc b/.coveragerc index d5c9f48ee47e48..8783dd64ab89b2 100644 --- a/.coveragerc +++ b/.coveragerc @@ -722,6 +722,7 @@ omit = homeassistant/components/minio/* homeassistant/components/mitemp_bt/sensor.py homeassistant/components/mjpeg/camera.py + homeassistant/components/mjpeg/util.py homeassistant/components/mochad/* homeassistant/components/modbus/climate.py homeassistant/components/modem_callerid/sensor.py diff --git a/homeassistant/components/agent_dvr/camera.py b/homeassistant/components/agent_dvr/camera.py index bad90efee8f416..e82bbeaea1bed4 100644 --- a/homeassistant/components/agent_dvr/camera.py +++ b/homeassistant/components/agent_dvr/camera.py @@ -5,7 +5,7 @@ from agent import AgentError from homeassistant.components.camera import SUPPORT_ON_OFF -from homeassistant.components.mjpeg.camera import MjpegCamera, filter_urllib3_logging +from homeassistant.components.mjpeg import MjpegCamera, filter_urllib3_logging from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.helpers import entity_platform from homeassistant.helpers.entity import DeviceInfo diff --git a/homeassistant/components/android_ip_webcam/__init__.py b/homeassistant/components/android_ip_webcam/__init__.py index d5bc3db45f88be..ca4af7fd68a541 100644 --- a/homeassistant/components/android_ip_webcam/__init__.py +++ b/homeassistant/components/android_ip_webcam/__init__.py @@ -5,7 +5,7 @@ from pydroid_ipcam import PyDroidIPCam import voluptuous as vol -from homeassistant.components.mjpeg.camera import CONF_MJPEG_URL, CONF_STILL_IMAGE_URL +from homeassistant.components.mjpeg import CONF_MJPEG_URL, CONF_STILL_IMAGE_URL from homeassistant.const import ( CONF_HOST, CONF_NAME, diff --git a/homeassistant/components/axis/camera.py b/homeassistant/components/axis/camera.py index c52aac37a02b12..2c9a6a52b9e1e5 100644 --- a/homeassistant/components/axis/camera.py +++ b/homeassistant/components/axis/camera.py @@ -2,7 +2,7 @@ from urllib.parse import urlencode from homeassistant.components.camera import SUPPORT_STREAM -from homeassistant.components.mjpeg.camera import MjpegCamera, filter_urllib3_logging +from homeassistant.components.mjpeg import MjpegCamera, filter_urllib3_logging from homeassistant.config_entries import ConfigEntry from homeassistant.const import HTTP_DIGEST_AUTHENTICATION from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/mjpeg/__init__.py b/homeassistant/components/mjpeg/__init__.py index 3e7469cff004e6..632156b7adc315 100644 --- a/homeassistant/components/mjpeg/__init__.py +++ b/homeassistant/components/mjpeg/__init__.py @@ -1 +1,42 @@ -"""The mjpeg component.""" +"""The MJPEG IP Camera integration.""" + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.typing import ConfigType + +from .camera import MjpegCamera +from .const import CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, PLATFORMS +from .util import filter_urllib3_logging + +__all__ = [ + "CONF_MJPEG_URL", + "CONF_STILL_IMAGE_URL", + "MjpegCamera", + "filter_urllib3_logging", +] + + +def setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up the MJPEG IP Camera integration.""" + filter_urllib3_logging() + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up from a config entry.""" + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + + # Reload entry when its updated. + entry.async_on_unload(entry.add_update_listener(async_reload_entry)) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + + +async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Reload the config entry when it changed.""" + await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/mjpeg/camera.py b/homeassistant/components/mjpeg/camera.py index 3a9c1a6cf91fbe..69588c1b670e07 100644 --- a/homeassistant/components/mjpeg/camera.py +++ b/homeassistant/components/mjpeg/camera.py @@ -4,7 +4,6 @@ import asyncio from collections.abc import Iterable from contextlib import closing -import logging import aiohttp from aiohttp import web @@ -14,6 +13,7 @@ import voluptuous as vol from homeassistant.components.camera import PLATFORM_SCHEMA, Camera +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( CONF_AUTHENTICATION, CONF_NAME, @@ -29,13 +29,12 @@ async_aiohttp_proxy_web, async_get_clientsession, ) +from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -_LOGGER = logging.getLogger(__name__) +from .const import CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, DOMAIN, LOGGER -CONF_MJPEG_URL = "mjpeg_url" -CONF_STILL_IMAGE_URL = "still_image_url" CONTENT_TYPE_HEADER = "Content-Type" DEFAULT_NAME = "Mjpeg Camera" @@ -62,34 +61,52 @@ async def async_setup_platform( async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up a MJPEG IP Camera.""" - filter_urllib3_logging() + """Set up the MJPEG IP camera from platform.""" + LOGGER.warning( + "Configuration of the MJPEG IP Camera platform in YAML is deprecated " + "and will be removed in Home Assistant 2022.5; Your existing " + "configuration has been imported into the UI automatically and can be " + "safely removed from your configuration.yaml file" + ) if discovery_info: config = PLATFORM_SCHEMA(discovery_info) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, + ) + ) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up a MJPEG IP Camera based on a config entry.""" async_add_entities( [ MjpegCamera( - name=config[CONF_NAME], - authentication=config[CONF_AUTHENTICATION], - username=config.get(CONF_USERNAME), - password=config[CONF_PASSWORD], - mjpeg_url=config[CONF_MJPEG_URL], - still_image_url=config.get(CONF_STILL_IMAGE_URL), - verify_ssl=config[CONF_VERIFY_SSL], + name=entry.title, + authentication=entry.options[CONF_AUTHENTICATION], + username=entry.options.get(CONF_USERNAME), + password=entry.options[CONF_PASSWORD], + mjpeg_url=entry.options[CONF_MJPEG_URL], + still_image_url=entry.options.get(CONF_STILL_IMAGE_URL), + verify_ssl=entry.options[CONF_VERIFY_SSL], + unique_id=entry.entry_id, + device_info=DeviceInfo( + name=entry.title, + identifiers={(DOMAIN, entry.entry_id)}, + ), ) ] ) -def filter_urllib3_logging() -> None: - """Filter header errors from urllib3 due to a urllib3 bug.""" - urllib3_logger = logging.getLogger("urllib3.connectionpool") - if not any(isinstance(x, NoHeaderErrorFilter) for x in urllib3_logger.filters): - urllib3_logger.addFilter(NoHeaderErrorFilter()) - - def extract_image_from_mjpeg(stream: Iterable[bytes]) -> bytes | None: """Take in a MJPEG stream object, return the jpg from it.""" data = b"" @@ -124,6 +141,8 @@ def __init__( username: str | None = None, password: str = "", verify_ssl: bool = True, + unique_id: str | None = None, + device_info: DeviceInfo | None = None, ) -> None: """Initialize a MJPEG camera.""" super().__init__() @@ -143,6 +162,11 @@ def __init__( self._auth = aiohttp.BasicAuth(self._username, password=self._password) self._verify_ssl = verify_ssl + if unique_id is not None: + self._attr_unique_id = unique_id + if device_info is not None: + self._attr_device_info = device_info + async def async_camera_image( self, width: int | None = None, height: int | None = None ) -> bytes | None: @@ -164,10 +188,10 @@ async def async_camera_image( return image except asyncio.TimeoutError: - _LOGGER.error("Timeout getting camera image from %s", self.name) + LOGGER.error("Timeout getting camera image from %s", self.name) except aiohttp.ClientError as err: - _LOGGER.error("Error getting new camera image from %s: %s", self.name, err) + LOGGER.error("Error getting new camera image from %s: %s", self.name, err) return None @@ -208,11 +232,3 @@ async def handle_async_mjpeg_stream( stream_coro = websession.get(self._mjpeg_url, auth=self._auth) return await async_aiohttp_proxy_web(self.hass, request, stream_coro) - - -class NoHeaderErrorFilter(logging.Filter): - """Filter out urllib3 Header Parsing Errors due to a urllib3 bug.""" - - def filter(self, record: logging.LogRecord) -> bool: - """Filter out Header Parsing Errors.""" - return "Failed to parse headers" not in record.getMessage() diff --git a/homeassistant/components/mjpeg/config_flow.py b/homeassistant/components/mjpeg/config_flow.py new file mode 100644 index 00000000000000..2eecdd84d7889c --- /dev/null +++ b/homeassistant/components/mjpeg/config_flow.py @@ -0,0 +1,240 @@ +"""Config flow to configure the MJPEG IP Camera integration.""" +from __future__ import annotations + +from http import HTTPStatus +from types import MappingProxyType +from typing import Any + +import requests +from requests.auth import HTTPBasicAuth, HTTPDigestAuth +from requests.exceptions import HTTPError, Timeout +import voluptuous as vol + +from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.const import ( + CONF_AUTHENTICATION, + CONF_NAME, + CONF_PASSWORD, + CONF_USERNAME, + CONF_VERIFY_SSL, + HTTP_BASIC_AUTHENTICATION, + HTTP_DIGEST_AUTHENTICATION, +) +from homeassistant.core import HomeAssistant, callback +from homeassistant.data_entry_flow import FlowResult +from homeassistant.exceptions import HomeAssistantError + +from .const import CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, DOMAIN, LOGGER + + +@callback +def async_get_schema( + defaults: dict[str, Any] | MappingProxyType[str, Any], show_name: bool = False +) -> vol.Schema: + """Return MJPEG IP Camera schema.""" + schema = { + vol.Required(CONF_MJPEG_URL, default=defaults.get(CONF_MJPEG_URL)): str, + vol.Optional( + CONF_STILL_IMAGE_URL, + description={"suggested_value": defaults.get(CONF_STILL_IMAGE_URL)}, + ): str, + vol.Optional( + CONF_USERNAME, + description={"suggested_value": defaults.get(CONF_USERNAME)}, + ): str, + vol.Optional( + CONF_PASSWORD, + default=defaults.get(CONF_PASSWORD, ""), + ): str, + vol.Optional( + CONF_VERIFY_SSL, + default=defaults.get(CONF_VERIFY_SSL, True), + ): bool, + } + + if show_name: + schema = { + vol.Optional(CONF_NAME, default=defaults.get(CONF_NAME)): str, + **schema, + } + + return vol.Schema(schema) + + +def validate_url( + url: str, + username: str | None, + password: str, + verify_ssl: bool, + authentication: str = HTTP_BASIC_AUTHENTICATION, +) -> str: + """Test if the given setting works as expected.""" + auth: HTTPDigestAuth | HTTPBasicAuth | None = None + if username and password: + if authentication == HTTP_DIGEST_AUTHENTICATION: + auth = HTTPDigestAuth(username, password) + else: + auth = HTTPBasicAuth(username, password) + + response = requests.get( + url, + auth=auth, + stream=True, + timeout=10, + verify=verify_ssl, + ) + + if response.status_code == HTTPStatus.UNAUTHORIZED: + # If unauthorized, try again using digest auth + if authentication == HTTP_BASIC_AUTHENTICATION: + return validate_url( + url, username, password, verify_ssl, HTTP_DIGEST_AUTHENTICATION + ) + raise InvalidAuth + + response.raise_for_status() + response.close() + + return authentication + + +async def async_validate_input( + hass: HomeAssistant, user_input: dict[str, Any] +) -> tuple[dict[str, str], str]: + """Manage MJPEG IP Camera options.""" + errors = {} + field = "base" + authentication = HTTP_BASIC_AUTHENTICATION + try: + for field in (CONF_MJPEG_URL, CONF_STILL_IMAGE_URL): + if not (url := user_input.get(field)): + continue + authentication = await hass.async_add_executor_job( + validate_url, + url, + user_input.get(CONF_USERNAME), + user_input[CONF_PASSWORD], + user_input[CONF_VERIFY_SSL], + ) + except InvalidAuth: + errors["username"] = "invalid_auth" + except (OSError, HTTPError, Timeout): + LOGGER.exception("Cannot connect to %s", user_input[CONF_MJPEG_URL]) + errors[field] = "cannot_connect" + + return (errors, authentication) + + +class MJPEGFlowHandler(ConfigFlow, domain=DOMAIN): + """Config flow for MJPEG IP Camera.""" + + VERSION = 1 + + @staticmethod + @callback + def async_get_options_flow( + config_entry: ConfigEntry, + ) -> MJPEGOptionsFlowHandler: + """Get the options flow for this handler.""" + return MJPEGOptionsFlowHandler(config_entry) + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle a flow initialized by the user.""" + errors: dict[str, str] = {} + + if user_input is not None: + errors, authentication = await async_validate_input(self.hass, user_input) + if not errors: + self._async_abort_entries_match( + {CONF_MJPEG_URL: user_input[CONF_MJPEG_URL]} + ) + + # Storing data in option, to allow for changing them later + # using an options flow. + return self.async_create_entry( + title=user_input.get(CONF_NAME, user_input[CONF_MJPEG_URL]), + data={}, + options={ + CONF_AUTHENTICATION: authentication, + CONF_MJPEG_URL: user_input[CONF_MJPEG_URL], + CONF_PASSWORD: user_input[CONF_PASSWORD], + CONF_STILL_IMAGE_URL: user_input.get(CONF_STILL_IMAGE_URL), + CONF_USERNAME: user_input.get(CONF_USERNAME), + CONF_VERIFY_SSL: user_input[CONF_VERIFY_SSL], + }, + ) + else: + user_input = {} + + return self.async_show_form( + step_id="user", + data_schema=async_get_schema(user_input, show_name=True), + errors=errors, + ) + + async def async_step_import(self, config: dict[str, Any]) -> FlowResult: + """Handle a flow initialized by importing a config.""" + self._async_abort_entries_match({CONF_MJPEG_URL: config[CONF_MJPEG_URL]}) + return self.async_create_entry( + title=config[CONF_NAME], + data={}, + options={ + CONF_AUTHENTICATION: config[CONF_AUTHENTICATION], + CONF_MJPEG_URL: config[CONF_MJPEG_URL], + CONF_PASSWORD: config[CONF_PASSWORD], + CONF_STILL_IMAGE_URL: config.get(CONF_STILL_IMAGE_URL), + CONF_USERNAME: config.get(CONF_USERNAME), + CONF_VERIFY_SSL: config[CONF_VERIFY_SSL], + }, + ) + + +class MJPEGOptionsFlowHandler(OptionsFlow): + """Handle MJPEG IP Camera options.""" + + def __init__(self, config_entry: ConfigEntry) -> None: + """Initialize MJPEG IP Camera options flow.""" + self.config_entry = config_entry + + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Manage MJPEG IP Camera options.""" + errors: dict[str, str] = {} + + if user_input is not None: + errors, authentication = await async_validate_input(self.hass, user_input) + if not errors: + for entry in self.hass.config_entries.async_entries(DOMAIN): + if ( + entry.entry_id != self.config_entry.entry_id + and entry.options[CONF_MJPEG_URL] == user_input[CONF_MJPEG_URL] + ): + errors = {CONF_MJPEG_URL: "already_configured"} + + if not errors: + return self.async_create_entry( + title=user_input.get(CONF_NAME, user_input[CONF_MJPEG_URL]), + data={ + CONF_AUTHENTICATION: authentication, + CONF_MJPEG_URL: user_input[CONF_MJPEG_URL], + CONF_PASSWORD: user_input[CONF_PASSWORD], + CONF_STILL_IMAGE_URL: user_input.get(CONF_STILL_IMAGE_URL), + CONF_USERNAME: user_input.get(CONF_USERNAME), + CONF_VERIFY_SSL: user_input[CONF_VERIFY_SSL], + }, + ) + else: + user_input = {} + + return self.async_show_form( + step_id="init", + data_schema=async_get_schema(user_input or self.config_entry.options), + errors=errors, + ) + + +class InvalidAuth(HomeAssistantError): + """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/mjpeg/const.py b/homeassistant/components/mjpeg/const.py new file mode 100644 index 00000000000000..94cd01676ecbc7 --- /dev/null +++ b/homeassistant/components/mjpeg/const.py @@ -0,0 +1,14 @@ +"""Constants for the MJPEG integration.""" + +import logging +from typing import Final + +from homeassistant.const import Platform + +DOMAIN: Final = "mjpeg" +PLATFORMS: Final = [Platform.CAMERA] + +LOGGER = logging.getLogger(__package__) + +CONF_MJPEG_URL: Final = "mjpeg_url" +CONF_STILL_IMAGE_URL: Final = "still_image_url" diff --git a/homeassistant/components/mjpeg/manifest.json b/homeassistant/components/mjpeg/manifest.json index 88e4cdba356280..02726c3bb3fe1f 100644 --- a/homeassistant/components/mjpeg/manifest.json +++ b/homeassistant/components/mjpeg/manifest.json @@ -3,5 +3,6 @@ "name": "MJPEG IP Camera", "documentation": "https://www.home-assistant.io/integrations/mjpeg", "codeowners": [], - "iot_class": "local_push" + "iot_class": "local_push", + "config_flow": true } diff --git a/homeassistant/components/mjpeg/strings.json b/homeassistant/components/mjpeg/strings.json new file mode 100644 index 00000000000000..73e6a150a09feb --- /dev/null +++ b/homeassistant/components/mjpeg/strings.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "mjpeg_url": "MJPEG URL", + "name": "[%key:common::config_flow::data::name%]", + "password": "[%key:common::config_flow::data::password%]", + "still_image_url": "Still Image URL", + "username": "[%key:common::config_flow::data::username%]", + "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + } + }, + "options": { + "step": { + "init": { + "data": { + "mjpeg_url": "MJPEG URL", + "name": "[%key:common::config_flow::data::name%]", + "password": "[%key:common::config_flow::data::password%]", + "still_image_url": "Still Image URL", + "username": "[%key:common::config_flow::data::username%]", + "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" + } + } + }, + "error": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" + } + } +} diff --git a/homeassistant/components/mjpeg/translations/en.json b/homeassistant/components/mjpeg/translations/en.json new file mode 100644 index 00000000000000..e389850a360bc8 --- /dev/null +++ b/homeassistant/components/mjpeg/translations/en.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication" + }, + "step": { + "user": { + "data": { + "mjpeg_url": "MJPEG URL", + "name": "Name", + "password": "Password", + "still_image_url": "Still Image URL", + "username": "Username", + "verify_ssl": "Verify SSL certificate" + } + } + } + }, + "options": { + "error": { + "already_configured": "Device is already configured", + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication" + }, + "step": { + "init": { + "data": { + "mjpeg_url": "MJPEG URL", + "name": "Name", + "password": "Password", + "still_image_url": "Still Image URL", + "username": "Username", + "verify_ssl": "Verify SSL certificate" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mjpeg/util.py b/homeassistant/components/mjpeg/util.py new file mode 100644 index 00000000000000..068d0aafc7e529 --- /dev/null +++ b/homeassistant/components/mjpeg/util.py @@ -0,0 +1,18 @@ +"""Utilities for MJPEG IP Camera.""" + +import logging + + +class NoHeaderErrorFilter(logging.Filter): + """Filter out urllib3 Header Parsing Errors due to a urllib3 bug.""" + + def filter(self, record: logging.LogRecord) -> bool: + """Filter out Header Parsing Errors.""" + return "Failed to parse headers" not in record.getMessage() + + +def filter_urllib3_logging() -> None: + """Filter header errors from urllib3 due to a urllib3 bug.""" + urllib3_logger = logging.getLogger("urllib3.connectionpool") + if not any(isinstance(x, NoHeaderErrorFilter) for x in urllib3_logger.filters): + urllib3_logger.addFilter(NoHeaderErrorFilter()) diff --git a/homeassistant/components/motioneye/camera.py b/homeassistant/components/motioneye/camera.py index acf170472da26d..e5e4f224fe6da4 100644 --- a/homeassistant/components/motioneye/camera.py +++ b/homeassistant/components/motioneye/camera.py @@ -24,7 +24,7 @@ ) import voluptuous as vol -from homeassistant.components.mjpeg.camera import ( +from homeassistant.components.mjpeg import ( CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera, diff --git a/homeassistant/components/zoneminder/camera.py b/homeassistant/components/zoneminder/camera.py index 5e262ff690314d..a7f0cf1d7fae52 100644 --- a/homeassistant/components/zoneminder/camera.py +++ b/homeassistant/components/zoneminder/camera.py @@ -3,7 +3,7 @@ import logging -from homeassistant.components.mjpeg.camera import MjpegCamera, filter_urllib3_logging +from homeassistant.components.mjpeg import MjpegCamera, filter_urllib3_logging from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 72037428e684e0..a4f774e64a72d2 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -196,6 +196,7 @@ "mikrotik", "mill", "minecraft_server", + "mjpeg", "mobile_app", "modem_callerid", "modern_forms", diff --git a/tests/components/mjpeg/__init__.py b/tests/components/mjpeg/__init__.py new file mode 100644 index 00000000000000..b3b796dc3b18c4 --- /dev/null +++ b/tests/components/mjpeg/__init__.py @@ -0,0 +1 @@ +"""Tests for the MJPEG IP Camera integration.""" diff --git a/tests/components/mjpeg/conftest.py b/tests/components/mjpeg/conftest.py new file mode 100644 index 00000000000000..c09bbde2f1d2ea --- /dev/null +++ b/tests/components/mjpeg/conftest.py @@ -0,0 +1,79 @@ +"""Fixtures for MJPEG IP Camera integration tests.""" +from __future__ import annotations + +from collections.abc import Generator +from unittest.mock import AsyncMock, patch + +import pytest +from requests_mock import Mocker + +from homeassistant.components.mjpeg.const import ( + CONF_MJPEG_URL, + CONF_STILL_IMAGE_URL, + DOMAIN, +) +from homeassistant.const import ( + CONF_AUTHENTICATION, + CONF_PASSWORD, + CONF_USERNAME, + CONF_VERIFY_SSL, + HTTP_BASIC_AUTHENTICATION, +) +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Return the default mocked config entry.""" + return MockConfigEntry( + title="My MJPEG Camera", + domain=DOMAIN, + data={}, + options={ + CONF_AUTHENTICATION: HTTP_BASIC_AUTHENTICATION, + CONF_MJPEG_URL: "https://example.com/mjpeg", + CONF_PASSWORD: "supersecret", + CONF_STILL_IMAGE_URL: "http://example.com/still", + CONF_USERNAME: "frenck", + CONF_VERIFY_SSL: True, + }, + ) + + +@pytest.fixture +def mock_setup_entry() -> Generator[AsyncMock, None, None]: + """Mock setting up a config entry.""" + with patch( + "homeassistant.components.mjpeg.async_setup_entry", return_value=True + ) as mock_setup: + yield mock_setup + + +@pytest.fixture +def mock_reload_entry() -> Generator[AsyncMock, None, None]: + """Mock setting up a config entry.""" + with patch("homeassistant.components.mjpeg.async_reload_entry") as mock_reload: + yield mock_reload + + +@pytest.fixture +def mock_mjpeg_requests(requests_mock: Mocker) -> Generator[Mocker, None, None]: + """Fixture to provide a requests mocker.""" + requests_mock.get("https://example.com/mjpeg", text="resp") + requests_mock.get("https://example.com/still", text="resp") + yield requests_mock + + +@pytest.fixture +async def init_integration( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_mjpeg_requests: Mocker +) -> MockConfigEntry: + """Set up the MJPEG IP Camera integration for testing.""" + mock_config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + return mock_config_entry diff --git a/tests/components/mjpeg/test_config_flow.py b/tests/components/mjpeg/test_config_flow.py new file mode 100644 index 00000000000000..0678353cf6dbb5 --- /dev/null +++ b/tests/components/mjpeg/test_config_flow.py @@ -0,0 +1,441 @@ +"""Tests for the MJPEG IP Camera config flow.""" + +from unittest.mock import AsyncMock + +import requests +from requests_mock import Mocker + +from homeassistant.components.mjpeg.const import ( + CONF_MJPEG_URL, + CONF_STILL_IMAGE_URL, + DOMAIN, +) +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from homeassistant.const import ( + CONF_AUTHENTICATION, + CONF_NAME, + CONF_PASSWORD, + CONF_USERNAME, + CONF_VERIFY_SSL, + HTTP_BASIC_AUTHENTICATION, + HTTP_DIGEST_AUTHENTICATION, +) +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import ( + RESULT_TYPE_ABORT, + RESULT_TYPE_CREATE_ENTRY, + RESULT_TYPE_FORM, +) + +from tests.common import MockConfigEntry + + +async def test_full_user_flow( + hass: HomeAssistant, + mock_mjpeg_requests: Mocker, + mock_setup_entry: AsyncMock, +) -> None: + """Test the full user configuration flow.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + + assert result.get("type") == RESULT_TYPE_FORM + assert result.get("step_id") == SOURCE_USER + assert "flow_id" in result + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_NAME: "Spy cam", + CONF_MJPEG_URL: "https://example.com/mjpeg", + CONF_STILL_IMAGE_URL: "https://example.com/still", + CONF_USERNAME: "frenck", + CONF_PASSWORD: "omgpuppies", + CONF_VERIFY_SSL: False, + }, + ) + + assert result2.get("type") == RESULT_TYPE_CREATE_ENTRY + assert result2.get("title") == "Spy cam" + assert result2.get("data") == {} + assert result2.get("options") == { + CONF_AUTHENTICATION: HTTP_BASIC_AUTHENTICATION, + CONF_MJPEG_URL: "https://example.com/mjpeg", + CONF_PASSWORD: "omgpuppies", + CONF_STILL_IMAGE_URL: "https://example.com/still", + CONF_USERNAME: "frenck", + CONF_VERIFY_SSL: False, + } + + assert len(mock_setup_entry.mock_calls) == 1 + assert mock_mjpeg_requests.call_count == 2 + + +async def test_full_flow_with_authentication_error( + hass: HomeAssistant, + mock_mjpeg_requests: Mocker, + mock_setup_entry: AsyncMock, +) -> None: + """Test the full user configuration flow with invalid credentials. + + This tests tests a full config flow, with a case the user enters an invalid + credentials, but recovers by entering the correct ones. + """ + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + + assert result.get("type") == RESULT_TYPE_FORM + assert result.get("step_id") == SOURCE_USER + assert "flow_id" in result + + mock_mjpeg_requests.get( + "https://example.com/mjpeg", text="Access Denied!", status_code=401 + ) + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_NAME: "Sky cam", + CONF_MJPEG_URL: "https://example.com/mjpeg", + CONF_PASSWORD: "omgpuppies", + CONF_USERNAME: "frenck", + }, + ) + + assert result2.get("type") == RESULT_TYPE_FORM + assert result2.get("step_id") == SOURCE_USER + assert result2.get("errors") == {"username": "invalid_auth"} + assert "flow_id" in result2 + + assert len(mock_setup_entry.mock_calls) == 0 + assert mock_mjpeg_requests.call_count == 2 + + mock_mjpeg_requests.get("https://example.com/mjpeg", text="resp") + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + user_input={ + CONF_NAME: "Sky cam", + CONF_MJPEG_URL: "https://example.com/mjpeg", + CONF_PASSWORD: "supersecret", + CONF_USERNAME: "frenck", + }, + ) + + assert result3.get("type") == RESULT_TYPE_CREATE_ENTRY + assert result3.get("title") == "Sky cam" + assert result3.get("data") == {} + assert result3.get("options") == { + CONF_AUTHENTICATION: HTTP_BASIC_AUTHENTICATION, + CONF_MJPEG_URL: "https://example.com/mjpeg", + CONF_PASSWORD: "supersecret", + CONF_STILL_IMAGE_URL: None, + CONF_USERNAME: "frenck", + CONF_VERIFY_SSL: True, + } + + assert len(mock_setup_entry.mock_calls) == 1 + assert mock_mjpeg_requests.call_count == 3 + + +async def test_connection_error( + hass: HomeAssistant, + mock_mjpeg_requests: Mocker, + mock_setup_entry: AsyncMock, +) -> None: + """Test connection error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + + assert result.get("type") == RESULT_TYPE_FORM + assert result.get("step_id") == SOURCE_USER + assert "flow_id" in result + + # Test connectione error on MJPEG url + mock_mjpeg_requests.get( + "https://example.com/mjpeg", exc=requests.exceptions.ConnectionError + ) + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_NAME: "My cam", + CONF_MJPEG_URL: "https://example.com/mjpeg", + CONF_STILL_IMAGE_URL: "https://example.com/still", + }, + ) + + assert result2.get("type") == RESULT_TYPE_FORM + assert result2.get("step_id") == SOURCE_USER + assert result2.get("errors") == {"mjpeg_url": "cannot_connect"} + assert "flow_id" in result2 + + assert len(mock_setup_entry.mock_calls) == 0 + assert mock_mjpeg_requests.call_count == 1 + + # Reset + mock_mjpeg_requests.get("https://example.com/mjpeg", text="resp") + + # Test connectione error on still url + mock_mjpeg_requests.get( + "https://example.com/still", exc=requests.exceptions.ConnectionError + ) + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + user_input={ + CONF_NAME: "My cam", + CONF_MJPEG_URL: "https://example.com/mjpeg", + CONF_STILL_IMAGE_URL: "https://example.com/still", + }, + ) + + assert result3.get("type") == RESULT_TYPE_FORM + assert result3.get("step_id") == SOURCE_USER + assert result3.get("errors") == {"still_image_url": "cannot_connect"} + assert "flow_id" in result3 + + assert len(mock_setup_entry.mock_calls) == 0 + assert mock_mjpeg_requests.call_count == 3 + + # Reset + mock_mjpeg_requests.get("https://example.com/still", text="resp") + + # Finish + result4 = await hass.config_entries.flow.async_configure( + result3["flow_id"], + user_input={ + CONF_NAME: "My cam", + CONF_MJPEG_URL: "https://example.com/mjpeg", + CONF_STILL_IMAGE_URL: "https://example.com/still", + }, + ) + + assert result4.get("type") == RESULT_TYPE_CREATE_ENTRY + assert result4.get("title") == "My cam" + assert result4.get("data") == {} + assert result4.get("options") == { + CONF_AUTHENTICATION: HTTP_BASIC_AUTHENTICATION, + CONF_MJPEG_URL: "https://example.com/mjpeg", + CONF_PASSWORD: "", + CONF_STILL_IMAGE_URL: "https://example.com/still", + CONF_USERNAME: None, + CONF_VERIFY_SSL: True, + } + + assert len(mock_setup_entry.mock_calls) == 1 + assert mock_mjpeg_requests.call_count == 5 + + +async def test_already_configured( + hass: HomeAssistant, + mock_mjpeg_requests: Mocker, + mock_config_entry: MockConfigEntry, + mock_setup_entry: AsyncMock, +) -> None: + """Test we abort if the MJPEG IP Camera is already configured.""" + mock_config_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert "flow_id" in result + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_NAME: "My cam", + CONF_MJPEG_URL: "https://example.com/mjpeg", + }, + ) + + assert result2.get("type") == RESULT_TYPE_ABORT + assert result2.get("reason") == "already_configured" + + +async def test_import_flow( + hass: HomeAssistant, + mock_mjpeg_requests: Mocker, + mock_setup_entry: AsyncMock, +) -> None: + """Test the import configuration flow.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={ + CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION, + CONF_MJPEG_URL: "http://example.com/mjpeg", + CONF_NAME: "Imported Camera", + CONF_PASSWORD: "omgpuppies", + CONF_STILL_IMAGE_URL: "http://example.com/still", + CONF_USERNAME: "frenck", + CONF_VERIFY_SSL: False, + }, + ) + + assert result.get("type") == RESULT_TYPE_CREATE_ENTRY + assert result.get("title") == "Imported Camera" + assert result.get("data") == {} + assert result.get("options") == { + CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION, + CONF_MJPEG_URL: "http://example.com/mjpeg", + CONF_PASSWORD: "omgpuppies", + CONF_STILL_IMAGE_URL: "http://example.com/still", + CONF_USERNAME: "frenck", + CONF_VERIFY_SSL: False, + } + + assert len(mock_setup_entry.mock_calls) == 1 + assert mock_mjpeg_requests.call_count == 0 + + +async def test_import_flow_already_configured( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_setup_entry: AsyncMock, +) -> None: + """Test the import configuration flow for an already configured entry.""" + mock_config_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={ + CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION, + CONF_MJPEG_URL: "https://example.com/mjpeg", + CONF_NAME: "Imported Camera", + CONF_PASSWORD: "omgpuppies", + CONF_STILL_IMAGE_URL: "https://example.com/still", + CONF_USERNAME: "frenck", + CONF_VERIFY_SSL: False, + }, + ) + + assert result.get("type") == RESULT_TYPE_ABORT + assert result.get("reason") == "already_configured" + + assert len(mock_setup_entry.mock_calls) == 0 + + +async def test_options_flow( + hass: HomeAssistant, + mock_mjpeg_requests: Mocker, + init_integration: MockConfigEntry, +) -> None: + """Test options config flow.""" + result = await hass.config_entries.options.async_init(init_integration.entry_id) + + assert result.get("type") == RESULT_TYPE_FORM + assert result.get("step_id") == "init" + assert "flow_id" in result + + # Register a second camera + mock_mjpeg_requests.get("https://example.com/second_camera", text="resp") + mock_second_config_entry = MockConfigEntry( + title="Another Camera", + domain=DOMAIN, + data={}, + options={ + CONF_AUTHENTICATION: HTTP_BASIC_AUTHENTICATION, + CONF_MJPEG_URL: "https://example.com/second_camera", + CONF_PASSWORD: "", + CONF_STILL_IMAGE_URL: None, + CONF_USERNAME: None, + CONF_VERIFY_SSL: True, + }, + ) + mock_second_config_entry.add_to_hass(hass) + + # Try updating options to already existing secondary camera + result2 = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_MJPEG_URL: "https://example.com/second_camera", + }, + ) + + assert result2.get("type") == RESULT_TYPE_FORM + assert result2.get("step_id") == "init" + assert result2.get("errors") == {"mjpeg_url": "already_configured"} + assert "flow_id" in result2 + + assert mock_mjpeg_requests.call_count == 1 + + # Test connectione error on MJPEG url + mock_mjpeg_requests.get( + "https://example.com/invalid_mjpeg", exc=requests.exceptions.ConnectionError + ) + result3 = await hass.config_entries.options.async_configure( + result2["flow_id"], + user_input={ + CONF_MJPEG_URL: "https://example.com/invalid_mjpeg", + CONF_STILL_IMAGE_URL: "https://example.com/still", + }, + ) + + assert result3.get("type") == RESULT_TYPE_FORM + assert result3.get("step_id") == "init" + assert result3.get("errors") == {"mjpeg_url": "cannot_connect"} + assert "flow_id" in result3 + + assert mock_mjpeg_requests.call_count == 2 + + # Test connectione error on still url + mock_mjpeg_requests.get( + "https://example.com/invalid_still", exc=requests.exceptions.ConnectionError + ) + result4 = await hass.config_entries.options.async_configure( + result3["flow_id"], + user_input={ + CONF_MJPEG_URL: "https://example.com/mjpeg", + CONF_STILL_IMAGE_URL: "https://example.com/invalid_still", + }, + ) + + assert result4.get("type") == RESULT_TYPE_FORM + assert result4.get("step_id") == "init" + assert result4.get("errors") == {"still_image_url": "cannot_connect"} + assert "flow_id" in result4 + + assert mock_mjpeg_requests.call_count == 4 + + # Invalid credentials + mock_mjpeg_requests.get( + "https://example.com/invalid_auth", text="Access Denied!", status_code=401 + ) + result5 = await hass.config_entries.options.async_configure( + result4["flow_id"], + user_input={ + CONF_MJPEG_URL: "https://example.com/invalid_auth", + CONF_PASSWORD: "omgpuppies", + CONF_USERNAME: "frenck", + }, + ) + + assert result5.get("type") == RESULT_TYPE_FORM + assert result5.get("step_id") == "init" + assert result5.get("errors") == {"username": "invalid_auth"} + assert "flow_id" in result5 + + assert mock_mjpeg_requests.call_count == 6 + + # Finish + result6 = await hass.config_entries.options.async_configure( + result5["flow_id"], + user_input={ + CONF_MJPEG_URL: "https://example.com/mjpeg", + CONF_PASSWORD: "evenmorepuppies", + CONF_USERNAME: "newuser", + }, + ) + + assert result6.get("type") == RESULT_TYPE_CREATE_ENTRY + assert result6.get("data") == { + CONF_AUTHENTICATION: HTTP_BASIC_AUTHENTICATION, + CONF_MJPEG_URL: "https://example.com/mjpeg", + CONF_PASSWORD: "evenmorepuppies", + CONF_STILL_IMAGE_URL: None, + CONF_USERNAME: "newuser", + CONF_VERIFY_SSL: True, + } + + assert mock_mjpeg_requests.call_count == 7 diff --git a/tests/components/mjpeg/test_init.py b/tests/components/mjpeg/test_init.py new file mode 100644 index 00000000000000..853e0feb687cd4 --- /dev/null +++ b/tests/components/mjpeg/test_init.py @@ -0,0 +1,99 @@ +"""Tests for the MJPEG IP Camera integration.""" +from unittest.mock import AsyncMock, MagicMock + +import pytest + +from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN +from homeassistant.components.mjpeg.const import ( + CONF_MJPEG_URL, + CONF_STILL_IMAGE_URL, + DOMAIN, +) +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import ( + CONF_AUTHENTICATION, + CONF_NAME, + CONF_PASSWORD, + CONF_USERNAME, + CONF_VERIFY_SSL, + HTTP_BASIC_AUTHENTICATION, +) +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from tests.common import MockConfigEntry + + +async def test_load_unload_config_entry( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_mjpeg_requests: MagicMock, +) -> None: + """Test the MJPEG IP Camera configuration entry loading/unloading.""" + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert mock_config_entry.state is ConfigEntryState.LOADED + + await hass.config_entries.async_unload(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert not hass.data.get(DOMAIN) + assert mock_config_entry.state is ConfigEntryState.NOT_LOADED + + +async def test_reload_config_entry( + hass: HomeAssistant, + mock_reload_entry: AsyncMock, + init_integration: MockConfigEntry, +) -> None: + """Test the MJPEG IP Camera configuration entry is reloaded on change.""" + assert len(mock_reload_entry.mock_calls) == 0 + hass.config_entries.async_update_entry( + init_integration, options={"something": "else"} + ) + assert len(mock_reload_entry.mock_calls) == 1 + + +async def test_import_config( + hass: HomeAssistant, + mock_mjpeg_requests: MagicMock, + mock_setup_entry: AsyncMock, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test MJPEG IP Camera being set up from config via import.""" + assert await async_setup_component( + hass, + CAMERA_DOMAIN, + { + CAMERA_DOMAIN: { + "platform": DOMAIN, + CONF_MJPEG_URL: "http://example.com/mjpeg", + CONF_NAME: "Random Camera", + CONF_PASSWORD: "supersecret", + CONF_STILL_IMAGE_URL: "http://example.com/still", + CONF_USERNAME: "frenck", + CONF_VERIFY_SSL: False, + } + }, + ) + await hass.async_block_till_done() + + config_entries = hass.config_entries.async_entries(DOMAIN) + assert len(config_entries) == 1 + + assert "the MJPEG IP Camera platform in YAML is deprecated" in caplog.text + + entry = config_entries[0] + assert entry.title == "Random Camera" + assert entry.unique_id is None + assert entry.data == {} + assert entry.options == { + CONF_AUTHENTICATION: HTTP_BASIC_AUTHENTICATION, + CONF_MJPEG_URL: "http://example.com/mjpeg", + CONF_PASSWORD: "supersecret", + CONF_STILL_IMAGE_URL: "http://example.com/still", + CONF_USERNAME: "frenck", + CONF_VERIFY_SSL: False, + } From 7e3d87a146e8196e9b5540ab593b7b5c410e8129 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Fri, 18 Feb 2022 08:05:38 +0000 Subject: [PATCH 0770/1098] Increase helpers.frame test coverage (#65137) Co-authored-by: Erik Montnemery --- tests/helpers/test_frame.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/helpers/test_frame.py b/tests/helpers/test_frame.py index 37f3e7ec95f23f..936940869d6b56 100644 --- a/tests/helpers/test_frame.py +++ b/tests/helpers/test_frame.py @@ -92,3 +92,16 @@ async def test_prevent_flooding(caplog): assert what not in caplog.text assert key in frame._REPORTED_INTEGRATIONS assert len(frame._REPORTED_INTEGRATIONS) == 1 + + +async def test_report_missing_integration_frame(caplog): + """Test reporting when no integration is detected.""" + + what = "teststring" + with patch( + "homeassistant.helpers.frame.get_integration_frame", + side_effect=frame.MissingIntegrationFrame, + ): + frame.report(what, error_if_core=False) + assert what in caplog.text + assert caplog.text.count(what) == 1 From 4f2be58fe4bdaa0a39b25e7300920ec91d4e8761 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Fri, 18 Feb 2022 09:13:36 +0100 Subject: [PATCH 0771/1098] Fix wifi switches name for Fritz (#66529) --- homeassistant/components/fritz/const.py | 2 + homeassistant/components/fritz/switch.py | 57 +++++++++++++++--------- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/fritz/const.py b/homeassistant/components/fritz/const.py index 0a4e9fd6cd84fb..635460db15fb0a 100644 --- a/homeassistant/components/fritz/const.py +++ b/homeassistant/components/fritz/const.py @@ -64,3 +64,5 @@ class MeshRoles(StrEnum): FritzServiceError, FritzLookUpError, ) + +WIFI_STANDARD = {1: "2.4Ghz", 2: "5Ghz", 3: "5Ghz", 4: "Guest"} diff --git a/homeassistant/components/fritz/switch.py b/homeassistant/components/fritz/switch.py index d168878c5da66f..07eb2eb437f8c4 100644 --- a/homeassistant/components/fritz/switch.py +++ b/homeassistant/components/fritz/switch.py @@ -31,6 +31,7 @@ SWITCH_TYPE_DEFLECTION, SWITCH_TYPE_PORTFORWARD, SWITCH_TYPE_WIFINETWORK, + WIFI_STANDARD, MeshRoles, ) @@ -141,31 +142,43 @@ def wifi_entities_list( ) -> list[FritzBoxWifiSwitch]: """Get list of wifi entities.""" _LOGGER.debug("Setting up %s switches", SWITCH_TYPE_WIFINETWORK) - std_table = {"ax": "Wifi6", "ac": "5Ghz", "n": "2.4Ghz"} - if avm_wrapper.model == "FRITZ!Box 7390": - std_table = {"n": "5Ghz"} + # + # https://avm.de/fileadmin/user_upload/Global/Service/Schnittstellen/wlanconfigSCPD.pdf + # + wifi_count = len( + [ + s + for s in avm_wrapper.connection.services + if s.startswith("WLANConfiguration") + ] + ) + _LOGGER.debug("WiFi networks count: %s", wifi_count) networks: dict = {} - for i in range(4): - if not ("WLANConfiguration" + str(i)) in avm_wrapper.connection.services: - continue - - network_info = avm_wrapper.get_wlan_configuration(i) - if network_info: - ssid = network_info["NewSSID"] - _LOGGER.debug("SSID from device: <%s>", ssid) - if slugify( - ssid, - ) in [slugify(v) for v in networks.values()]: - _LOGGER.debug("SSID duplicated, adding suffix") - networks[i] = f'{ssid} {std_table[network_info["NewStandard"]]}' - else: - networks[i] = ssid - _LOGGER.debug("SSID normalized: <%s>", networks[i]) - + for i in range(1, wifi_count + 1): + network_info = avm_wrapper.connection.call_action( + f"WLANConfiguration{i}", "GetInfo" + ) + # Devices with 4 WLAN services, use the 2nd for internal communications + if not (wifi_count == 4 and i == 2): + networks[i] = { + "ssid": network_info["NewSSID"], + "bssid": network_info["NewBSSID"], + "standard": network_info["NewStandard"], + "enabled": network_info["NewEnable"], + "status": network_info["NewStatus"], + } + for i, network in networks.copy().items(): + networks[i]["switch_name"] = network["ssid"] + if len([j for j, n in networks.items() if n["ssid"] == network["ssid"]]) > 1: + networks[i]["switch_name"] += f" ({WIFI_STANDARD[i]})" + + _LOGGER.debug("WiFi networks list: %s", networks) return [ - FritzBoxWifiSwitch(avm_wrapper, device_friendly_name, net, network_name) - for net, network_name in networks.items() + FritzBoxWifiSwitch( + avm_wrapper, device_friendly_name, index, data["switch_name"] + ) + for index, data in networks.items() ] From 98982c86e48149915c94c4a5f1de203274ee327a Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 18 Feb 2022 09:28:49 +0100 Subject: [PATCH 0772/1098] Add MQTT diagnostics (#66730) * Add MQTT diagnostics * Redact device tracker location * Adjust tests * Address comments from code review --- homeassistant/components/mqtt/debug_info.py | 132 ++++++---- homeassistant/components/mqtt/diagnostics.py | 126 +++++++++ tests/components/mqtt/test_diagnostics.py | 263 +++++++++++++++++++ tests/components/mqtt/test_init.py | 30 ++- tests/conftest.py | 1 + 5 files changed, 494 insertions(+), 58 deletions(-) create mode 100644 homeassistant/components/mqtt/diagnostics.py create mode 100644 tests/components/mqtt/test_diagnostics.py diff --git a/homeassistant/components/mqtt/debug_info.py b/homeassistant/components/mqtt/debug_info.py index 3bf07db183238c..17dbc27f0c4ba0 100644 --- a/homeassistant/components/mqtt/debug_info.py +++ b/homeassistant/components/mqtt/debug_info.py @@ -154,7 +154,81 @@ def update_trigger_discovery_data(hass, discovery_hash, discovery_payload): def remove_trigger_discovery_data(hass, discovery_hash): """Remove discovery data.""" - hass.data[DATA_MQTT_DEBUG_INFO]["triggers"][discovery_hash]["discovery_data"] = None + hass.data[DATA_MQTT_DEBUG_INFO]["triggers"].pop(discovery_hash) + + +def _info_for_entity(hass: HomeAssistant, entity_id: str) -> dict[str, Any]: + mqtt_debug_info = hass.data[DATA_MQTT_DEBUG_INFO] + entity_info = mqtt_debug_info["entities"][entity_id] + subscriptions = [ + { + "topic": topic, + "messages": [ + { + "payload": str(msg.payload), + "qos": msg.qos, + "retain": msg.retain, + "time": msg.timestamp, + "topic": msg.topic, + } + for msg in subscription["messages"] + ], + } + for topic, subscription in entity_info["subscriptions"].items() + ] + transmitted = [ + { + "topic": topic, + "messages": [ + { + "payload": str(msg.payload), + "qos": msg.qos, + "retain": msg.retain, + "time": msg.timestamp, + "topic": msg.topic, + } + for msg in subscription["messages"] + ], + } + for topic, subscription in entity_info["transmitted"].items() + ] + discovery_data = { + "topic": entity_info["discovery_data"].get(ATTR_DISCOVERY_TOPIC, ""), + "payload": entity_info["discovery_data"].get(ATTR_DISCOVERY_PAYLOAD, ""), + } + + return { + "entity_id": entity_id, + "subscriptions": subscriptions, + "discovery_data": discovery_data, + "transmitted": transmitted, + } + + +def _info_for_trigger(hass: HomeAssistant, trigger_key: str) -> dict[str, Any]: + mqtt_debug_info = hass.data[DATA_MQTT_DEBUG_INFO] + trigger = mqtt_debug_info["triggers"][trigger_key] + discovery_data = None + if trigger["discovery_data"] is not None: + discovery_data = { + "topic": trigger["discovery_data"][ATTR_DISCOVERY_TOPIC], + "payload": trigger["discovery_data"][ATTR_DISCOVERY_PAYLOAD], + } + return {"discovery_data": discovery_data, "trigger_key": trigger_key} + + +def info_for_config_entry(hass): + """Get debug info for all entities and triggers.""" + mqtt_info = {"entities": [], "triggers": []} + mqtt_debug_info = hass.data[DATA_MQTT_DEBUG_INFO] + + for entity_id in mqtt_debug_info["entities"]: + mqtt_info["entities"].append(_info_for_entity(hass, entity_id)) + + for trigger_key in mqtt_debug_info["triggers"]: + mqtt_info["triggers"].append(_info_for_trigger(hass, trigger_key)) + + return mqtt_info def info_for_device(hass, device_id): @@ -170,60 +244,12 @@ def info_for_device(hass, device_id): if entry.entity_id not in mqtt_debug_info["entities"]: continue - entity_info = mqtt_debug_info["entities"][entry.entity_id] - subscriptions = [ - { - "topic": topic, - "messages": [ - { - "payload": str(msg.payload), - "qos": msg.qos, - "retain": msg.retain, - "time": msg.timestamp, - "topic": msg.topic, - } - for msg in list(subscription["messages"]) - ], - } - for topic, subscription in entity_info["subscriptions"].items() - ] - transmitted = [ - { - "topic": topic, - "messages": [ - { - "payload": str(msg.payload), - "qos": msg.qos, - "retain": msg.retain, - "time": msg.timestamp, - "topic": msg.topic, - } - for msg in list(subscription["messages"]) - ], - } - for topic, subscription in entity_info["transmitted"].items() - ] - discovery_data = { - "topic": entity_info["discovery_data"].get(ATTR_DISCOVERY_TOPIC, ""), - "payload": entity_info["discovery_data"].get(ATTR_DISCOVERY_PAYLOAD, ""), - } - mqtt_info["entities"].append( - { - "entity_id": entry.entity_id, - "subscriptions": subscriptions, - "discovery_data": discovery_data, - "transmitted": transmitted, - } - ) + mqtt_info["entities"].append(_info_for_entity(hass, entry.entity_id)) - for trigger in mqtt_debug_info["triggers"].values(): - if trigger["device_id"] != device_id or trigger["discovery_data"] is None: + for trigger_key, trigger in mqtt_debug_info["triggers"].items(): + if trigger["device_id"] != device_id: continue - discovery_data = { - "topic": trigger["discovery_data"][ATTR_DISCOVERY_TOPIC], - "payload": trigger["discovery_data"][ATTR_DISCOVERY_PAYLOAD], - } - mqtt_info["triggers"].append({"discovery_data": discovery_data}) + mqtt_info["triggers"].append(_info_for_trigger(hass, trigger_key)) return mqtt_info diff --git a/homeassistant/components/mqtt/diagnostics.py b/homeassistant/components/mqtt/diagnostics.py new file mode 100644 index 00000000000000..ea490783fc09f1 --- /dev/null +++ b/homeassistant/components/mqtt/diagnostics.py @@ -0,0 +1,126 @@ +"""Diagnostics support for MQTT.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components import device_tracker +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + ATTR_LATITUDE, + ATTR_LONGITUDE, + CONF_PASSWORD, + CONF_USERNAME, +) +from homeassistant.core import HomeAssistant, callback, split_entity_id +from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers.device_registry import DeviceEntry + +from . import DATA_MQTT, MQTT, debug_info, is_connected + +REDACT_CONFIG = {CONF_PASSWORD, CONF_USERNAME} +REDACT_STATE_DEVICE_TRACKER = {ATTR_LATITUDE, ATTR_LONGITUDE} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + return _async_get_diagnostics(hass, entry) + + +async def async_get_device_diagnostics( + hass: HomeAssistant, entry: ConfigEntry, device: DeviceEntry +) -> dict[str, Any]: + """Return diagnostics for a device entry.""" + return _async_get_diagnostics(hass, entry, device) + + +@callback +def _async_get_diagnostics( + hass: HomeAssistant, + entry: ConfigEntry, + device: DeviceEntry | None = None, +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + mqtt_instance: MQTT = hass.data[DATA_MQTT] + + redacted_config = async_redact_data(mqtt_instance.conf, REDACT_CONFIG) + + data = { + "connected": is_connected(hass), + "mqtt_config": redacted_config, + } + + if device: + data["device"] = _async_device_as_dict(hass, device) + data["mqtt_debug_info"] = debug_info.info_for_device(hass, device.id) + else: + device_registry = dr.async_get(hass) + data.update( + devices=[ + _async_device_as_dict(hass, device) + for device in dr.async_entries_for_config_entry( + device_registry, entry.entry_id + ) + ], + mqtt_debug_info=debug_info.info_for_config_entry(hass), + ) + + return data + + +@callback +def _async_device_as_dict(hass: HomeAssistant, device: DeviceEntry) -> dict[str, Any]: + """Represent an MQTT device as a dictionary.""" + + # Gather information how this MQTT device is represented in Home Assistant + entity_registry = er.async_get(hass) + data: dict[str, Any] = { + "id": device.id, + "name": device.name, + "name_by_user": device.name_by_user, + "disabled": device.disabled, + "disabled_by": device.disabled_by, + "entities": [], + } + + entities = er.async_entries_for_device( + entity_registry, + device_id=device.id, + include_disabled_entities=True, + ) + + for entity_entry in entities: + state = hass.states.get(entity_entry.entity_id) + state_dict = None + if state: + state_dict = dict(state.as_dict()) + + # The context doesn't provide useful information in this case. + state_dict.pop("context", None) + + entity_domain = split_entity_id(state.entity_id)[0] + + # Retract some sensitive state attributes + if entity_domain == device_tracker.DOMAIN: + state_dict["attributes"] = async_redact_data( + state_dict["attributes"], REDACT_STATE_DEVICE_TRACKER + ) + + data["entities"].append( + { + "device_class": entity_entry.device_class, + "disabled_by": entity_entry.disabled_by, + "disabled": entity_entry.disabled, + "entity_category": entity_entry.entity_category, + "entity_id": entity_entry.entity_id, + "icon": entity_entry.icon, + "original_device_class": entity_entry.original_device_class, + "original_icon": entity_entry.original_icon, + "state": state_dict, + "unit_of_measurement": entity_entry.unit_of_measurement, + } + ) + + return data diff --git a/tests/components/mqtt/test_diagnostics.py b/tests/components/mqtt/test_diagnostics.py new file mode 100644 index 00000000000000..bbd42a20c87be2 --- /dev/null +++ b/tests/components/mqtt/test_diagnostics.py @@ -0,0 +1,263 @@ +"""Test MQTT diagnostics.""" + +import json +from unittest.mock import ANY + +import pytest + +from homeassistant.components import mqtt + +from tests.common import async_fire_mqtt_message, mock_device_registry +from tests.components.diagnostics import ( + get_diagnostics_for_config_entry, + get_diagnostics_for_device, +) + +default_config = { + "birth_message": {}, + "broker": "mock-broker", + "discovery": True, + "discovery_prefix": "homeassistant", + "keepalive": 60, + "port": 1883, + "protocol": "3.1.1", + "tls_version": "auto", + "will_message": { + "payload": "offline", + "qos": 0, + "retain": False, + "topic": "homeassistant/status", + }, +} + + +@pytest.fixture +def device_reg(hass): + """Return an empty, loaded, registry.""" + return mock_device_registry(hass) + + +async def test_entry_diagnostics(hass, device_reg, hass_client, mqtt_mock): + """Test config entry diagnostics.""" + config_entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0] + mqtt_mock.connected = True + + assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { + "connected": True, + "devices": [], + "mqtt_config": default_config, + "mqtt_debug_info": {"entities": [], "triggers": []}, + } + + # Discover a device with an entity and a trigger + config_sensor = { + "device": {"identifiers": ["0AFFD2"]}, + "platform": "mqtt", + "state_topic": "foobar/sensor", + "unique_id": "unique", + } + config_trigger = { + "automation_type": "trigger", + "device": {"identifiers": ["0AFFD2"]}, + "platform": "mqtt", + "topic": "test-topic1", + "type": "foo", + "subtype": "bar", + } + data_sensor = json.dumps(config_sensor) + data_trigger = json.dumps(config_trigger) + + async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data_sensor) + async_fire_mqtt_message( + hass, "homeassistant/device_automation/bla/config", data_trigger + ) + await hass.async_block_till_done() + + device_entry = device_reg.async_get_device({("mqtt", "0AFFD2")}) + + expected_debug_info = { + "entities": [ + { + "entity_id": "sensor.mqtt_sensor", + "subscriptions": [{"topic": "foobar/sensor", "messages": []}], + "discovery_data": { + "payload": config_sensor, + "topic": "homeassistant/sensor/bla/config", + }, + "transmitted": [], + } + ], + "triggers": [ + { + "discovery_data": { + "payload": config_trigger, + "topic": "homeassistant/device_automation/bla/config", + }, + "trigger_key": ["device_automation", "bla"], + } + ], + } + + expected_device = { + "disabled": False, + "disabled_by": None, + "entities": [ + { + "device_class": None, + "disabled": False, + "disabled_by": None, + "entity_category": None, + "entity_id": "sensor.mqtt_sensor", + "icon": None, + "original_device_class": None, + "original_icon": None, + "state": { + "attributes": {"friendly_name": "MQTT Sensor"}, + "entity_id": "sensor.mqtt_sensor", + "last_changed": ANY, + "last_updated": ANY, + "state": "unknown", + }, + "unit_of_measurement": None, + } + ], + "id": device_entry.id, + "name": None, + "name_by_user": None, + } + + assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { + "connected": True, + "devices": [expected_device], + "mqtt_config": default_config, + "mqtt_debug_info": expected_debug_info, + } + + assert await get_diagnostics_for_device( + hass, hass_client, config_entry, device_entry + ) == { + "connected": True, + "device": expected_device, + "mqtt_config": default_config, + "mqtt_debug_info": expected_debug_info, + } + + +@pytest.mark.parametrize( + "mqtt_config", + [ + { + mqtt.CONF_BROKER: "mock-broker", + mqtt.CONF_BIRTH_MESSAGE: {}, + mqtt.CONF_PASSWORD: "hunter2", + mqtt.CONF_USERNAME: "my_user", + } + ], +) +async def test_redact_diagnostics(hass, device_reg, hass_client, mqtt_mock): + """Test redacting diagnostics.""" + expected_config = dict(default_config) + expected_config["password"] = "**REDACTED**" + expected_config["username"] = "**REDACTED**" + + config_entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0] + mqtt_mock.connected = True + + # Discover a device with a device tracker + config_tracker = { + "device": {"identifiers": ["0AFFD2"]}, + "platform": "mqtt", + "state_topic": "foobar/device_tracker", + "json_attributes_topic": "attributes-topic", + "unique_id": "unique", + } + data_tracker = json.dumps(config_tracker) + + async_fire_mqtt_message( + hass, "homeassistant/device_tracker/bla/config", data_tracker + ) + await hass.async_block_till_done() + + location_data = '{"latitude":32.87336,"longitude": -117.22743, "gps_accuracy":1.5}' + async_fire_mqtt_message(hass, "attributes-topic", location_data) + await hass.async_block_till_done() + + device_entry = device_reg.async_get_device({("mqtt", "0AFFD2")}) + + expected_debug_info = { + "entities": [ + { + "entity_id": "device_tracker.mqtt_unique", + "subscriptions": [ + { + "topic": "attributes-topic", + "messages": [ + { + "payload": location_data, + "qos": 0, + "retain": False, + "time": ANY, + "topic": "attributes-topic", + } + ], + }, + {"topic": "foobar/device_tracker", "messages": []}, + ], + "discovery_data": { + "payload": config_tracker, + "topic": "homeassistant/device_tracker/bla/config", + }, + "transmitted": [], + } + ], + "triggers": [], + } + + expected_device = { + "disabled": False, + "disabled_by": None, + "entities": [ + { + "device_class": None, + "disabled": False, + "disabled_by": None, + "entity_category": None, + "entity_id": "device_tracker.mqtt_unique", + "icon": None, + "original_device_class": None, + "original_icon": None, + "state": { + "attributes": { + "gps_accuracy": 1.5, + "latitude": "**REDACTED**", + "longitude": "**REDACTED**", + "source_type": None, + }, + "entity_id": "device_tracker.mqtt_unique", + "last_changed": ANY, + "last_updated": ANY, + "state": "home", + }, + "unit_of_measurement": None, + } + ], + "id": device_entry.id, + "name": None, + "name_by_user": None, + } + + assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { + "connected": True, + "devices": [expected_device], + "mqtt_config": expected_config, + "mqtt_debug_info": expected_debug_info, + } + + assert await get_diagnostics_for_device( + hass, hass_client, config_entry, device_entry + ) == { + "connected": True, + "device": expected_device, + "mqtt_config": expected_config, + "mqtt_debug_info": expected_debug_info, + } diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 92884dcef93fe3..a9a96df4f8fdff 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -1529,15 +1529,27 @@ async def test_mqtt_ws_get_device_debug_info( hass, device_reg, hass_ws_client, mqtt_mock ): """Test MQTT websocket device debug info.""" - config = { + config_sensor = { "device": {"identifiers": ["0AFFD2"]}, "platform": "mqtt", "state_topic": "foobar/sensor", "unique_id": "unique", } - data = json.dumps(config) + config_trigger = { + "automation_type": "trigger", + "device": {"identifiers": ["0AFFD2"]}, + "platform": "mqtt", + "topic": "test-topic1", + "type": "foo", + "subtype": "bar", + } + data_sensor = json.dumps(config_sensor) + data_trigger = json.dumps(config_trigger) - async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data) + async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data_sensor) + async_fire_mqtt_message( + hass, "homeassistant/device_automation/bla/config", data_trigger + ) await hass.async_block_till_done() # Verify device entry is created @@ -1556,13 +1568,21 @@ async def test_mqtt_ws_get_device_debug_info( "entity_id": "sensor.mqtt_sensor", "subscriptions": [{"topic": "foobar/sensor", "messages": []}], "discovery_data": { - "payload": config, + "payload": config_sensor, "topic": "homeassistant/sensor/bla/config", }, "transmitted": [], } ], - "triggers": [], + "triggers": [ + { + "discovery_data": { + "payload": config_trigger, + "topic": "homeassistant/device_automation/bla/config", + }, + "trigger_key": ["device_automation", "bla"], + } + ], } assert response["result"] == expected_result diff --git a/tests/conftest.py b/tests/conftest.py index 564480a0e91386..baac9ac19eed95 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -609,6 +609,7 @@ async def mqtt_mock(hass, mqtt_client_mock, mqtt_config): spec_set=hass.data["mqtt"], wraps=hass.data["mqtt"], ) + mqtt_component_mock.conf = hass.data["mqtt"].conf # For diagnostics mqtt_component_mock._mqttc = mqtt_client_mock hass.data["mqtt"] = mqtt_component_mock From d7170f43c3c3b8c7e0b217f839b1fad494cef74f Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 18 Feb 2022 09:38:15 +0100 Subject: [PATCH 0773/1098] Add type ignore error codes [A-L] (#66778) --- homeassistant/components/adguard/__init__.py | 2 +- homeassistant/components/arcam_fmj/device_trigger.py | 2 +- homeassistant/components/azure_devops/__init__.py | 2 +- homeassistant/components/azure_event_hub/client.py | 2 +- homeassistant/components/hue/v2/device_trigger.py | 2 +- homeassistant/components/hue/v2/hue_event.py | 2 +- homeassistant/components/insteon/ipdb.py | 2 +- homeassistant/components/iqvia/sensor.py | 2 +- homeassistant/components/knx/config_flow.py | 2 +- homeassistant/components/lookin/climate.py | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/adguard/__init__.py b/homeassistant/components/adguard/__init__.py index 8c90efcd44c8f0..1f2645e227c580 100644 --- a/homeassistant/components/adguard/__init__.py +++ b/homeassistant/components/adguard/__init__.py @@ -205,7 +205,7 @@ def device_info(self) -> DeviceInfo: return DeviceInfo( entry_type=DeviceEntryType.SERVICE, identifiers={ - (DOMAIN, self.adguard.host, self.adguard.port, self.adguard.base_path) # type: ignore + (DOMAIN, self.adguard.host, self.adguard.port, self.adguard.base_path) # type: ignore[arg-type] }, manufacturer="AdGuard Team", name="AdGuard Home", diff --git a/homeassistant/components/arcam_fmj/device_trigger.py b/homeassistant/components/arcam_fmj/device_trigger.py index b33710bf936b33..2b1a3bf3a19583 100644 --- a/homeassistant/components/arcam_fmj/device_trigger.py +++ b/homeassistant/components/arcam_fmj/device_trigger.py @@ -76,7 +76,7 @@ def _handle_event(event: Event): job, { "trigger": { - **trigger_data, # type: ignore # https://github.com/python/mypy/issues/9117 + **trigger_data, # type: ignore[arg-type] # https://github.com/python/mypy/issues/9117 **config, "description": f"{DOMAIN} - {entity_id}", } diff --git a/homeassistant/components/azure_devops/__init__.py b/homeassistant/components/azure_devops/__init__.py index 213dc19ff9eee5..1b1c65ae6d14e3 100644 --- a/homeassistant/components/azure_devops/__init__.py +++ b/homeassistant/components/azure_devops/__init__.py @@ -123,7 +123,7 @@ def device_info(self) -> DeviceInfo: """Return device information about this Azure DevOps instance.""" return DeviceInfo( entry_type=DeviceEntryType.SERVICE, - identifiers={(DOMAIN, self._organization, self._project_name)}, # type: ignore + identifiers={(DOMAIN, self._organization, self._project_name)}, # type: ignore[arg-type] manufacturer=self._organization, name=self._project_name, ) diff --git a/homeassistant/components/azure_event_hub/client.py b/homeassistant/components/azure_event_hub/client.py index 1a5aa330cc8a2f..27a4eabf535c73 100644 --- a/homeassistant/components/azure_event_hub/client.py +++ b/homeassistant/components/azure_event_hub/client.py @@ -64,7 +64,7 @@ def client(self) -> EventHubProducerClient: return EventHubProducerClient( fully_qualified_namespace=f"{self.event_hub_namespace}.servicebus.windows.net", eventhub_name=self.event_hub_instance_name, - credential=EventHubSharedKeyCredential( # type: ignore + credential=EventHubSharedKeyCredential( # type: ignore[arg-type] policy=self.event_hub_sas_policy, key=self.event_hub_sas_key ), **ADDITIONAL_ARGS, diff --git a/homeassistant/components/hue/v2/device_trigger.py b/homeassistant/components/hue/v2/device_trigger.py index 3f474cdf70b149..cab21b63d6d5fa 100644 --- a/homeassistant/components/hue/v2/device_trigger.py +++ b/homeassistant/components/hue/v2/device_trigger.py @@ -72,7 +72,7 @@ def check_invalid_device_trigger( "Please manually fix the outdated automation(s) once to fix this issue." ) if automation_info: - automation_id = automation_info["variables"]["this"]["attributes"]["id"] # type: ignore + automation_id = automation_info["variables"]["this"]["attributes"]["id"] # type: ignore[index] msg += f"\n\n[Check it out](/config/automation/edit/{automation_id})." persistent_notification.async_create( bridge.hass, diff --git a/homeassistant/components/hue/v2/hue_event.py b/homeassistant/components/hue/v2/hue_event.py index 1d45293012cfd5..4b9adf16226469 100644 --- a/homeassistant/components/hue/v2/hue_event.py +++ b/homeassistant/components/hue/v2/hue_event.py @@ -47,7 +47,7 @@ def handle_button_event(evt_type: EventType, hue_resource: Button) -> None: data = { # send slugified entity name as id = backwards compatibility with previous version CONF_ID: slugify(f"{hue_device.metadata.name} Button"), - CONF_DEVICE_ID: device.id, # type: ignore + CONF_DEVICE_ID: device.id, # type: ignore[union-attr] CONF_UNIQUE_ID: hue_resource.id, CONF_TYPE: hue_resource.button.last_event.value, CONF_SUBTYPE: hue_resource.metadata.control_id, diff --git a/homeassistant/components/insteon/ipdb.py b/homeassistant/components/insteon/ipdb.py index 9b32bc400438c4..6866e0523684d4 100644 --- a/homeassistant/components/insteon/ipdb.py +++ b/homeassistant/components/insteon/ipdb.py @@ -110,4 +110,4 @@ def get_device_platforms(device): def get_platform_groups(device, domain) -> dict: """Return the platforms that a device belongs in.""" - return DEVICE_PLATFORM.get(type(device), {}).get(domain, {}) # type: ignore + return DEVICE_PLATFORM.get(type(device), {}).get(domain, {}) # type: ignore[attr-defined] diff --git a/homeassistant/components/iqvia/sensor.py b/homeassistant/components/iqvia/sensor.py index 46da1aea0aa439..51f2969e9fe46d 100644 --- a/homeassistant/components/iqvia/sensor.py +++ b/homeassistant/components/iqvia/sensor.py @@ -161,7 +161,7 @@ def calculate_trend(indices: list[float]) -> str: """Calculate the "moving average" of a set of indices.""" index_range = np.arange(0, len(indices)) index_array = np.array(indices) - linear_fit = np.polyfit(index_range, index_array, 1) # type: ignore + linear_fit = np.polyfit(index_range, index_array, 1) # type: ignore[no-untyped-call] slope = round(linear_fit[0], 2) if slope > 0: diff --git a/homeassistant/components/knx/config_flow.py b/homeassistant/components/knx/config_flow.py index 4f7a9d6723cdbf..6bc6085d0e56a9 100644 --- a/homeassistant/components/knx/config_flow.py +++ b/homeassistant/components/knx/config_flow.py @@ -317,7 +317,7 @@ async def async_step_init( CONF_KNX_TUNNELING, CONF_KNX_ROUTING, ] - self.current_config = self.config_entry.data # type: ignore + self.current_config = self.config_entry.data # type: ignore[assignment] data_schema = { vol.Required( diff --git a/homeassistant/components/lookin/climate.py b/homeassistant/components/lookin/climate.py index ab6b53978befa2..e661c14a15135b 100644 --- a/homeassistant/components/lookin/climate.py +++ b/homeassistant/components/lookin/climate.py @@ -100,7 +100,7 @@ async def async_setup_entry( class ConditionerEntity(LookinCoordinatorEntity, ClimateEntity): """An aircon or heat pump.""" - _attr_current_humidity: float | None = None # type: ignore + _attr_current_humidity: float | None = None # type: ignore[assignment] _attr_temperature_unit = TEMP_CELSIUS _attr_supported_features: int = SUPPORT_FLAGS _attr_fan_modes: list[str] = LOOKIN_FAN_MODE_IDX_TO_HASS From 67e94f2b4ba614a37544f54ccb85984f0d600376 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 18 Feb 2022 09:41:12 +0100 Subject: [PATCH 0774/1098] Add type ignore error codes [N-Z] (#66779) --- homeassistant/components/nest/config_flow.py | 2 +- homeassistant/components/norway_air/air_quality.py | 10 +++++----- homeassistant/components/notify/legacy.py | 4 ++-- homeassistant/components/tibber/sensor.py | 2 +- homeassistant/components/tplink/__init__.py | 2 +- homeassistant/components/zwave_js/climate.py | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/nest/config_flow.py b/homeassistant/components/nest/config_flow.py index bccac112d55c3a..aac8c263ef13f9 100644 --- a/homeassistant/components/nest/config_flow.py +++ b/homeassistant/components/nest/config_flow.py @@ -489,7 +489,7 @@ async def async_step_import(self, info: dict[str, Any]) -> FlowResult: config_path = info["nest_conf_path"] if not await self.hass.async_add_executor_job(os.path.isfile, config_path): - self.flow_impl = DOMAIN # type: ignore + self.flow_impl = DOMAIN # type: ignore[assignment] return await self.async_step_link() flow = self.hass.data[DATA_FLOW_IMPL][DOMAIN] diff --git a/homeassistant/components/norway_air/air_quality.py b/homeassistant/components/norway_air/air_quality.py index 146f4b2ff27ed4..b6182d7ed843cc 100644 --- a/homeassistant/components/norway_air/air_quality.py +++ b/homeassistant/components/norway_air/air_quality.py @@ -106,31 +106,31 @@ def name(self) -> str: """Return the name of the sensor.""" return self._name - @property # type: ignore + @property # type: ignore[misc] @round_state def air_quality_index(self): """Return the Air Quality Index (AQI).""" return self._api.data.get("aqi") - @property # type: ignore + @property # type: ignore[misc] @round_state def nitrogen_dioxide(self): """Return the NO2 (nitrogen dioxide) level.""" return self._api.data.get("no2_concentration") - @property # type: ignore + @property # type: ignore[misc] @round_state def ozone(self): """Return the O3 (ozone) level.""" return self._api.data.get("o3_concentration") - @property # type: ignore + @property # type: ignore[misc] @round_state def particulate_matter_2_5(self): """Return the particulate matter 2.5 level.""" return self._api.data.get("pm25_concentration") - @property # type: ignore + @property # type: ignore[misc] @round_state def particulate_matter_10(self): """Return the particulate matter 10 level.""" diff --git a/homeassistant/components/notify/legacy.py b/homeassistant/components/notify/legacy.py index 5f26c952b31d54..af29a9fba99f96 100644 --- a/homeassistant/components/notify/legacy.py +++ b/homeassistant/components/notify/legacy.py @@ -178,7 +178,7 @@ class BaseNotificationService: # While not purely typed, it makes typehinting more useful for us # and removes the need for constant None checks or asserts. - hass: HomeAssistant = None # type: ignore + hass: HomeAssistant = None # type: ignore[assignment] # Name => target registered_targets: dict[str, str] @@ -246,7 +246,7 @@ async def async_register_services(self) -> None: if hasattr(self, "targets"): stale_targets = set(self.registered_targets) - for name, target in self.targets.items(): # type: ignore + for name, target in self.targets.items(): # type: ignore[attr-defined] target_name = slugify(f"{self._target_service_name_prefix}_{name}") if target_name in stale_targets: stale_targets.remove(target_name) diff --git a/homeassistant/components/tibber/sensor.py b/homeassistant/components/tibber/sensor.py index ebb986d6a7ed1e..12bcec295d06ee 100644 --- a/homeassistant/components/tibber/sensor.py +++ b/homeassistant/components/tibber/sensor.py @@ -464,7 +464,7 @@ def _handle_coordinator_update(self) -> None: ts_local = dt_util.parse_datetime(live_measurement["timestamp"]) if ts_local is not None: if self.last_reset is None or ( - state < 0.5 * self.native_value # type: ignore # native_value is float + state < 0.5 * self.native_value # type: ignore[operator] # native_value is float and ( ts_local.hour == 0 or (ts_local - self.last_reset) > timedelta(hours=24) diff --git a/homeassistant/components/tplink/__init__.py b/homeassistant/components/tplink/__init__.py index 83f4b820523aeb..33b03109cd8aec 100644 --- a/homeassistant/components/tplink/__init__.py +++ b/homeassistant/components/tplink/__init__.py @@ -96,7 +96,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: device: SmartDevice = hass_data[entry.entry_id].device if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): hass_data.pop(entry.entry_id) - await device.protocol.close() # type: ignore + await device.protocol.close() # type: ignore[no-untyped-call] return unload_ok diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 6df95d9bbfce0d..88c96feea88bfc 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -242,7 +242,7 @@ def _current_mode_setpoint_enums(self) -> list[ThermostatSetpointType | None]: if self._current_mode is None: # Thermostat(valve) with no support for setting a mode is considered heating-only return [ThermostatSetpointType.HEATING] - return THERMOSTAT_MODE_SETPOINT_MAP.get(int(self._current_mode.value), []) # type: ignore + return THERMOSTAT_MODE_SETPOINT_MAP.get(int(self._current_mode.value), []) # type: ignore[no-any-return] @property def temperature_unit(self) -> str: From 2b807bd07dd98cd9736828d8f83409237f13cb8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Feb 2022 09:53:38 +0100 Subject: [PATCH 0775/1098] Bump docker/login-action from 1.12.0 to 1.13.0 (#66788) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/builder.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index cdff1bac634831..126deba34943ca 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -123,13 +123,13 @@ jobs: echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE - name: Login to DockerHub - uses: docker/login-action@v1.12.0 + uses: docker/login-action@v1.13.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@v1.12.0 + uses: docker/login-action@v1.13.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -188,13 +188,13 @@ jobs: fi - name: Login to DockerHub - uses: docker/login-action@v1.12.0 + uses: docker/login-action@v1.13.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@v1.12.0 + uses: docker/login-action@v1.13.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -253,13 +253,13 @@ jobs: uses: actions/checkout@v2.4.0 - name: Login to DockerHub - uses: docker/login-action@v1.12.0 + uses: docker/login-action@v1.13.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@v1.12.0 + uses: docker/login-action@v1.13.0 with: registry: ghcr.io username: ${{ github.repository_owner }} From 483545eeaac712bcae2df536b7c49a391433753a Mon Sep 17 00:00:00 2001 From: Teemu R Date: Fri, 18 Feb 2022 10:31:46 +0100 Subject: [PATCH 0776/1098] Bump python-songpal dependency to 0.14 (#66769) * Bump python-songpal dependency to 0.14 * Fix tests * pip_check -1 Co-authored-by: Franck Nijhof --- homeassistant/components/songpal/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- script/pip_check | 2 +- tests/components/songpal/__init__.py | 3 +++ 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/songpal/manifest.json b/homeassistant/components/songpal/manifest.json index 97647d8710628e..80a26a56b2281a 100644 --- a/homeassistant/components/songpal/manifest.json +++ b/homeassistant/components/songpal/manifest.json @@ -3,7 +3,7 @@ "name": "Sony Songpal", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/songpal", - "requirements": ["python-songpal==0.12"], + "requirements": ["python-songpal==0.14"], "codeowners": ["@rytilahti", "@shenxn"], "ssdp": [ { diff --git a/requirements_all.txt b/requirements_all.txt index caa36d4dc46c2b..c60fb3c8b5b293 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1972,7 +1972,7 @@ python-smarttub==0.0.29 python-sochain-api==0.0.2 # homeassistant.components.songpal -python-songpal==0.12 +python-songpal==0.14 # homeassistant.components.tado python-tado==0.12.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 912e41fcd7d58f..77f59d29676ecb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1227,7 +1227,7 @@ python-picnic-api==1.1.0 python-smarttub==0.0.29 # homeassistant.components.songpal -python-songpal==0.12 +python-songpal==0.14 # homeassistant.components.tado python-tado==0.12.0 diff --git a/script/pip_check b/script/pip_check index c30a7382f27335..af47f101fbba5b 100755 --- a/script/pip_check +++ b/script/pip_check @@ -3,7 +3,7 @@ PIP_CACHE=$1 # Number of existing dependency conflicts # Update if a PR resolve one! -DEPENDENCY_CONFLICTS=10 +DEPENDENCY_CONFLICTS=9 PIP_CHECK=$(pip check --cache-dir=$PIP_CACHE) LINE_COUNT=$(echo "$PIP_CHECK" | wc -l) diff --git a/tests/components/songpal/__init__.py b/tests/components/songpal/__init__.py index a9ca62ecb09143..d98ec4175fc243 100644 --- a/tests/components/songpal/__init__.py +++ b/tests/components/songpal/__init__.py @@ -44,6 +44,9 @@ def _create_mocked_device(throw_exception=False, wired_mac=MAC, wireless_mac=Non bssid=None, ssid=None, bleID=None, + serialNumber=None, + generation=None, + model=None, version=SW_VERSION, ) type(mocked_device).get_system_info = AsyncMock(return_value=sys_info) From c8ae0d3bbe8eedf9050f4e5e59f9e168efe3ff41 Mon Sep 17 00:00:00 2001 From: Andre Lengwenus Date: Fri, 18 Feb 2022 10:37:00 +0100 Subject: [PATCH 0777/1098] Bump pypck to 0.7.14 (#66794) --- homeassistant/components/lcn/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/lcn/manifest.json b/homeassistant/components/lcn/manifest.json index 2bb9111b269d44..412ef74e3b82c9 100644 --- a/homeassistant/components/lcn/manifest.json +++ b/homeassistant/components/lcn/manifest.json @@ -3,7 +3,7 @@ "name": "LCN", "config_flow": false, "documentation": "https://www.home-assistant.io/integrations/lcn", - "requirements": ["pypck==0.7.13"], + "requirements": ["pypck==0.7.14"], "codeowners": ["@alengwenus"], "iot_class": "local_push", "loggers": ["pypck"] diff --git a/requirements_all.txt b/requirements_all.txt index c60fb3c8b5b293..34e32e0d792f33 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1755,7 +1755,7 @@ pyownet==0.10.0.post1 pypca==0.0.7 # homeassistant.components.lcn -pypck==0.7.13 +pypck==0.7.14 # homeassistant.components.pjlink pypjlink2==1.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 77f59d29676ecb..ef1240537ecbc9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1124,7 +1124,7 @@ pyowm==3.2.0 pyownet==0.10.0.post1 # homeassistant.components.lcn -pypck==0.7.13 +pypck==0.7.14 # homeassistant.components.plaato pyplaato==0.0.15 From cb736eaeaf2dbebc6f1f23f52e3445a0b643f01a Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 18 Feb 2022 10:37:38 +0100 Subject: [PATCH 0778/1098] Add type ignore error codes [recorder] (#66780) --- homeassistant/components/recorder/models.py | 26 +++++++++---------- homeassistant/components/recorder/purge.py | 6 ++--- .../components/recorder/statistics.py | 20 +++++++------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py index 55d6f73108c99a..579f47ed4a7d16 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -77,7 +77,7 @@ ) -class Events(Base): # type: ignore +class Events(Base): # type: ignore[misc,valid-type] """Event history data.""" __table_args__ = ( @@ -141,7 +141,7 @@ def to_native(self, validate_entity_id=True): return None -class States(Base): # type: ignore +class States(Base): # type: ignore[misc,valid-type] """State change history.""" __table_args__ = ( @@ -276,13 +276,13 @@ def metadata_id(self): @classmethod def from_stats(cls, metadata_id: int, stats: StatisticData): """Create object from a statistics.""" - return cls( # type: ignore + return cls( # type: ignore[call-arg,misc] metadata_id=metadata_id, **stats, ) -class Statistics(Base, StatisticsBase): # type: ignore +class Statistics(Base, StatisticsBase): # type: ignore[misc,valid-type] """Long term statistics.""" duration = timedelta(hours=1) @@ -294,7 +294,7 @@ class Statistics(Base, StatisticsBase): # type: ignore __tablename__ = TABLE_STATISTICS -class StatisticsShortTerm(Base, StatisticsBase): # type: ignore +class StatisticsShortTerm(Base, StatisticsBase): # type: ignore[misc,valid-type] """Short term statistics.""" duration = timedelta(minutes=5) @@ -322,7 +322,7 @@ class StatisticMetaData(TypedDict): unit_of_measurement: str | None -class StatisticsMeta(Base): # type: ignore +class StatisticsMeta(Base): # type: ignore[misc,valid-type] """Statistics meta data.""" __table_args__ = ( @@ -343,7 +343,7 @@ def from_meta(meta: StatisticMetaData) -> StatisticsMeta: return StatisticsMeta(**meta) -class RecorderRuns(Base): # type: ignore +class RecorderRuns(Base): # type: ignore[misc,valid-type] """Representation of recorder run.""" __table_args__ = (Index("ix_recorder_runs_start_end", "start", "end"),) @@ -393,7 +393,7 @@ def to_native(self, validate_entity_id=True): return self -class SchemaChanges(Base): # type: ignore +class SchemaChanges(Base): # type: ignore[misc,valid-type] """Representation of schema version changes.""" __tablename__ = TABLE_SCHEMA_CHANGES @@ -411,7 +411,7 @@ def __repr__(self) -> str: ) -class StatisticsRuns(Base): # type: ignore +class StatisticsRuns(Base): # type: ignore[misc,valid-type] """Representation of statistics run.""" __tablename__ = TABLE_STATISTICS_RUNS @@ -491,7 +491,7 @@ def __init__(self, row): # pylint: disable=super-init-not-called self._last_updated = None self._context = None - @property # type: ignore + @property # type: ignore[override] def attributes(self): """State attributes.""" if not self._attributes: @@ -508,7 +508,7 @@ def attributes(self, value): """Set attributes.""" self._attributes = value - @property # type: ignore + @property # type: ignore[override] def context(self): """State context.""" if not self._context: @@ -520,7 +520,7 @@ def context(self, value): """Set context.""" self._context = value - @property # type: ignore + @property # type: ignore[override] def last_changed(self): """Last changed datetime.""" if not self._last_changed: @@ -532,7 +532,7 @@ def last_changed(self, value): """Set last changed datetime.""" self._last_changed = value - @property # type: ignore + @property # type: ignore[override] def last_updated(self): """Last updated datetime.""" if not self._last_updated: diff --git a/homeassistant/components/recorder/purge.py b/homeassistant/components/recorder/purge.py index e44ae9aafff3ea..dd80fb15479d52 100644 --- a/homeassistant/components/recorder/purge.py +++ b/homeassistant/components/recorder/purge.py @@ -34,7 +34,7 @@ def purge_old_data( purge_before.isoformat(sep=" ", timespec="seconds"), ) - with session_scope(session=instance.get_session()) as session: # type: ignore + with session_scope(session=instance.get_session()) as session: # type: ignore[misc] # Purge a max of MAX_ROWS_TO_PURGE, based on the oldest states or events record event_ids = _select_event_ids_to_purge(session, purge_before) state_ids = _select_state_ids_to_purge(session, purge_before, event_ids) @@ -267,7 +267,7 @@ def _purge_filtered_states( "Selected %s state_ids to remove that should be filtered", len(state_ids) ) _purge_state_ids(instance, session, set(state_ids)) - _purge_event_ids(session, event_ids) # type: ignore # type of event_ids already narrowed to 'list[int]' + _purge_event_ids(session, event_ids) # type: ignore[arg-type] # type of event_ids already narrowed to 'list[int]' def _purge_filtered_events( @@ -295,7 +295,7 @@ def _purge_filtered_events( @retryable_database_job("purge") def purge_entity_data(instance: Recorder, entity_filter: Callable[[str], bool]) -> bool: """Purge states and events of specified entities.""" - with session_scope(session=instance.get_session()) as session: # type: ignore + with session_scope(session=instance.get_session()) as session: # type: ignore[misc] selected_entity_ids: list[str] = [ entity_id for (entity_id,) in session.query(distinct(States.entity_id)).all() diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index 6c305242f5fd0d..4154ae830555f7 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -488,7 +488,7 @@ def compile_hourly_statistics( ) if stats: - for metadata_id, group in groupby(stats, lambda stat: stat["metadata_id"]): # type: ignore + for metadata_id, group in groupby(stats, lambda stat: stat["metadata_id"]): # type: ignore[no-any-return] ( metadata_id, last_reset, @@ -527,7 +527,7 @@ def compile_statistics(instance: Recorder, start: datetime) -> bool: end = start + timedelta(minutes=5) # Return if we already have 5-minute statistics for the requested period - with session_scope(session=instance.get_session()) as session: # type: ignore + with session_scope(session=instance.get_session()) as session: # type: ignore[misc] if session.query(StatisticsRuns).filter_by(start=start).first(): _LOGGER.debug("Statistics already compiled for %s-%s", start, end) return True @@ -546,7 +546,7 @@ def compile_statistics(instance: Recorder, start: datetime) -> bool: # Insert collected statistics in the database with session_scope( - session=instance.get_session(), # type: ignore + session=instance.get_session(), # type: ignore[misc] exception_filter=_filter_unique_constraint_integrity_error(instance), ) as session: for stats in platform_stats: @@ -700,7 +700,7 @@ def _configured_unit(unit: str, units: UnitSystem) -> str: def clear_statistics(instance: Recorder, statistic_ids: list[str]) -> None: """Clear statistics for a list of statistic_ids.""" - with session_scope(session=instance.get_session()) as session: # type: ignore + with session_scope(session=instance.get_session()) as session: # type: ignore[misc] session.query(StatisticsMeta).filter( StatisticsMeta.statistic_id.in_(statistic_ids) ).delete(synchronize_session=False) @@ -710,7 +710,7 @@ def update_statistics_metadata( instance: Recorder, statistic_id: str, unit_of_measurement: str | None ) -> None: """Update statistics metadata for a statistic_id.""" - with session_scope(session=instance.get_session()) as session: # type: ignore + with session_scope(session=instance.get_session()) as session: # type: ignore[misc] session.query(StatisticsMeta).filter( StatisticsMeta.statistic_id == statistic_id ).update({StatisticsMeta.unit_of_measurement: unit_of_measurement}) @@ -1093,7 +1093,7 @@ def _sorted_statistics_to_dict( def no_conversion(val: Any, _: Any) -> float | None: """Return x.""" - return val # type: ignore + return val # type: ignore[no-any-return] # Set all statistic IDs to empty lists in result set to maintain the order if statistic_ids is not None: @@ -1101,7 +1101,7 @@ def no_conversion(val: Any, _: Any) -> float | None: result[stat_id] = [] # Identify metadata IDs for which no data was available at the requested start time - for meta_id, group in groupby(stats, lambda stat: stat.metadata_id): # type: ignore + for meta_id, group in groupby(stats, lambda stat: stat.metadata_id): # type: ignore[no-any-return] first_start_time = process_timestamp(next(group).start) if start_time and first_start_time > start_time: need_stat_at_start_time.add(meta_id) @@ -1115,12 +1115,12 @@ def no_conversion(val: Any, _: Any) -> float | None: stats_at_start_time[stat.metadata_id] = (stat,) # Append all statistic entries, and optionally do unit conversion - for meta_id, group in groupby(stats, lambda stat: stat.metadata_id): # type: ignore + for meta_id, group in groupby(stats, lambda stat: stat.metadata_id): # type: ignore[no-any-return] unit = metadata[meta_id]["unit_of_measurement"] statistic_id = metadata[meta_id]["statistic_id"] convert: Callable[[Any, Any], float | None] if convert_units: - convert = UNIT_CONVERSIONS.get(unit, lambda x, units: x) # type: ignore + convert = UNIT_CONVERSIONS.get(unit, lambda x, units: x) # type: ignore[arg-type,no-any-return] else: convert = no_conversion ent_results = result[meta_id] @@ -1249,7 +1249,7 @@ def add_external_statistics( """Process an add_statistics job.""" with session_scope( - session=instance.get_session(), # type: ignore + session=instance.get_session(), # type: ignore[misc] exception_filter=_filter_unique_constraint_integrity_error(instance), ) as session: metadata_id = _update_or_add_metadata(instance.hass, session, metadata) From 046c0ae61bdfca245118918924be2e1e8878bcc1 Mon Sep 17 00:00:00 2001 From: Teemu R Date: Fri, 18 Feb 2022 10:44:31 +0100 Subject: [PATCH 0779/1098] Bump python-miio dependency to 0.5.10 (#66782) --- homeassistant/components/xiaomi_miio/fan.py | 7 +++---- homeassistant/components/xiaomi_miio/manifest.json | 2 +- homeassistant/components/xiaomi_miio/select.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index 1337aa05895d7a..e4b79ed77a8292 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -8,12 +8,11 @@ from miio.airfresh_t2017 import OperationMode as AirfreshOperationModeT2017 from miio.airpurifier import OperationMode as AirpurifierOperationMode from miio.airpurifier_miot import OperationMode as AirpurifierMiotOperationMode -from miio.fan import ( +from miio.fan_common import ( MoveDirection as FanMoveDirection, OperationMode as FanOperationMode, ) -from miio.fan_miot import ( - OperationMode as FanMiotOperationMode, +from miio.integrations.fan.zhimi.zhimi_miot import ( OperationModeFanZA5 as FanZA5OperationMode, ) import voluptuous as vol @@ -1035,7 +1034,7 @@ class XiaomiFanMiot(XiaomiGenericFan): @property def operation_mode_class(self): """Hold operation mode class.""" - return FanMiotOperationMode + return FanOperationMode @property def preset_mode(self): diff --git a/homeassistant/components/xiaomi_miio/manifest.json b/homeassistant/components/xiaomi_miio/manifest.json index 239e8c289102a9..0091d58e1e2f85 100644 --- a/homeassistant/components/xiaomi_miio/manifest.json +++ b/homeassistant/components/xiaomi_miio/manifest.json @@ -3,7 +3,7 @@ "name": "Xiaomi Miio", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/xiaomi_miio", - "requirements": ["construct==2.10.56", "micloud==0.5", "python-miio==0.5.9.2"], + "requirements": ["construct==2.10.56", "micloud==0.5", "python-miio==0.5.10"], "codeowners": ["@rytilahti", "@syssi", "@starkillerOG", "@bieniu"], "zeroconf": ["_miio._udp.local."], "iot_class": "local_polling", diff --git a/homeassistant/components/xiaomi_miio/select.py b/homeassistant/components/xiaomi_miio/select.py index a0ff320e228f23..2b5f6f3d5fd201 100644 --- a/homeassistant/components/xiaomi_miio/select.py +++ b/homeassistant/components/xiaomi_miio/select.py @@ -8,7 +8,7 @@ from miio.airhumidifier_miot import LedBrightness as AirhumidifierMiotLedBrightness from miio.airpurifier import LedBrightness as AirpurifierLedBrightness from miio.airpurifier_miot import LedBrightness as AirpurifierMiotLedBrightness -from miio.fan import LedBrightness as FanLedBrightness +from miio.fan_common import LedBrightness as FanLedBrightness from homeassistant.components.select import SelectEntity, SelectEntityDescription from homeassistant.config_entries import ConfigEntry diff --git a/requirements_all.txt b/requirements_all.txt index 34e32e0d792f33..33aed1f238c835 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1942,7 +1942,7 @@ python-kasa==0.4.1 # python-lirc==1.2.3 # homeassistant.components.xiaomi_miio -python-miio==0.5.9.2 +python-miio==0.5.10 # homeassistant.components.mpd python-mpd2==3.0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ef1240537ecbc9..8dad410b49cf31 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1212,7 +1212,7 @@ python-juicenet==1.0.2 python-kasa==0.4.1 # homeassistant.components.xiaomi_miio -python-miio==0.5.9.2 +python-miio==0.5.10 # homeassistant.components.nest python-nest==4.2.0 From 0ac9376ee48562fa9995faab9793a5447e0f93b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Fri, 18 Feb 2022 11:07:14 +0100 Subject: [PATCH 0780/1098] Add list to async_delay_save typing (#66795) --- homeassistant/helpers/storage.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/storage.py b/homeassistant/helpers/storage.py index af0c50ec5fa2a7..554a88f4ad5651 100644 --- a/homeassistant/helpers/storage.py +++ b/homeassistant/helpers/storage.py @@ -194,7 +194,11 @@ async def async_save(self, data: dict | list) -> None: await self._async_handle_write_data() @callback - def async_delay_save(self, data_func: Callable[[], dict], delay: float = 0) -> None: + def async_delay_save( + self, + data_func: Callable[[], dict | list], + delay: float = 0, + ) -> None: """Save data with an optional delay.""" # pylint: disable-next=import-outside-toplevel from .event import async_call_later From 0188e8b319b6c82ff4ea9233d609cd2e9e3a2d11 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 18 Feb 2022 11:30:59 +0100 Subject: [PATCH 0781/1098] Add type ignore error codes [util] (#66777) --- homeassistant/util/__init__.py | 4 ++-- homeassistant/util/json.py | 2 +- homeassistant/util/package.py | 2 +- homeassistant/util/unit_system.py | 10 +++++----- homeassistant/util/yaml/dumper.py | 2 +- homeassistant/util/yaml/loader.py | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py index 5c2882ec2e212f..15e9254e9d8757 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -137,7 +137,7 @@ async def throttled_value() -> None: else: - def throttled_value() -> None: # type: ignore + def throttled_value() -> None: # type: ignore[misc] """Stand-in function for when real func is being throttled.""" return None @@ -191,7 +191,7 @@ def wrapper(*args: Any, **kwargs: Any) -> Callable | Coroutine: if force or utcnow() - throttle[1] > self.min_time: result = method(*args, **kwargs) throttle[1] = utcnow() - return result # type: ignore + return result # type: ignore[no-any-return] return throttled_value() finally: diff --git a/homeassistant/util/json.py b/homeassistant/util/json.py index 9c98691c605edd..fdee7a7a90f296 100644 --- a/homeassistant/util/json.py +++ b/homeassistant/util/json.py @@ -30,7 +30,7 @@ def load_json(filename: str, default: list | dict | None = None) -> list | dict: """ try: with open(filename, encoding="utf-8") as fdesc: - return json.loads(fdesc.read()) # type: ignore + return json.loads(fdesc.read()) # type: ignore[no-any-return] except FileNotFoundError: # This is not a fatal error _LOGGER.debug("JSON file not found: %s", filename) diff --git a/homeassistant/util/package.py b/homeassistant/util/package.py index a1ee2b9f584534..aad93e375424fd 100644 --- a/homeassistant/util/package.py +++ b/homeassistant/util/package.py @@ -50,7 +50,7 @@ def is_installed(package: str) -> bool: # was aborted while in progress see # https://github.com/home-assistant/core/issues/47699 if installed_version is None: - _LOGGER.error("Installed version for %s resolved to None", req.project_name) # type: ignore + _LOGGER.error("Installed version for %s resolved to None", req.project_name) # type: ignore[unreachable] return False return installed_version in req except PackageNotFoundError: diff --git a/homeassistant/util/unit_system.py b/homeassistant/util/unit_system.py index dfe73b0e937ae9..e964fee798c892 100644 --- a/homeassistant/util/unit_system.py +++ b/homeassistant/util/unit_system.py @@ -134,7 +134,7 @@ def length(self, length: float | None, from_unit: str) -> float: raise TypeError(f"{length!s} is not a numeric value.") # type ignore: https://github.com/python/mypy/issues/7207 - return distance_util.convert( # type: ignore + return distance_util.convert( # type: ignore[unreachable] length, from_unit, self.length_unit ) @@ -144,7 +144,7 @@ def accumulated_precipitation(self, precip: float | None, from_unit: str) -> flo raise TypeError(f"{precip!s} is not a numeric value.") # type ignore: https://github.com/python/mypy/issues/7207 - return distance_util.convert( # type: ignore + return distance_util.convert( # type: ignore[unreachable] precip, from_unit, self.accumulated_precipitation_unit ) @@ -154,7 +154,7 @@ def pressure(self, pressure: float | None, from_unit: str) -> float: raise TypeError(f"{pressure!s} is not a numeric value.") # type ignore: https://github.com/python/mypy/issues/7207 - return pressure_util.convert( # type: ignore + return pressure_util.convert( # type: ignore[unreachable] pressure, from_unit, self.pressure_unit ) @@ -164,7 +164,7 @@ def wind_speed(self, wind_speed: float | None, from_unit: str) -> float: raise TypeError(f"{wind_speed!s} is not a numeric value.") # type ignore: https://github.com/python/mypy/issues/7207 - return speed_util.convert(wind_speed, from_unit, self.wind_speed_unit) # type: ignore + return speed_util.convert(wind_speed, from_unit, self.wind_speed_unit) # type: ignore[unreachable] def volume(self, volume: float | None, from_unit: str) -> float: """Convert the given volume to this unit system.""" @@ -172,7 +172,7 @@ def volume(self, volume: float | None, from_unit: str) -> float: raise TypeError(f"{volume!s} is not a numeric value.") # type ignore: https://github.com/python/mypy/issues/7207 - return volume_util.convert(volume, from_unit, self.volume_unit) # type: ignore + return volume_util.convert(volume, from_unit, self.volume_unit) # type: ignore[unreachable] def as_dict(self) -> dict[str, str]: """Convert the unit system to a dictionary.""" diff --git a/homeassistant/util/yaml/dumper.py b/homeassistant/util/yaml/dumper.py index 8e9cb382b6c369..3eafc8abdd7cb3 100644 --- a/homeassistant/util/yaml/dumper.py +++ b/homeassistant/util/yaml/dumper.py @@ -24,7 +24,7 @@ def save_yaml(path: str, data: dict) -> None: # From: https://gist.github.com/miracle2k/3184458 -def represent_odict( # type: ignore +def represent_odict( # type: ignore[no-untyped-def] dumper, tag, mapping, flow_style=None ) -> yaml.MappingNode: """Like BaseRepresenter.represent_mapping but does not issue the sort().""" diff --git a/homeassistant/util/yaml/loader.py b/homeassistant/util/yaml/loader.py index e6ac5fd364a8e2..84349ef91a93df 100644 --- a/homeassistant/util/yaml/loader.py +++ b/homeassistant/util/yaml/loader.py @@ -100,7 +100,7 @@ def compose_node(self, parent: yaml.nodes.Node, index: int) -> yaml.nodes.Node: """Annotate a node with the first line it was seen.""" last_line: int = self.line node: yaml.nodes.Node = super().compose_node(parent, index) # type: ignore[assignment] - node.__line__ = last_line + 1 # type: ignore + node.__line__ = last_line + 1 # type: ignore[attr-defined] return node @@ -149,7 +149,7 @@ def _add_reference( ... -def _add_reference(obj, loader: SafeLineLoader, node: yaml.nodes.Node): # type: ignore +def _add_reference(obj, loader: SafeLineLoader, node: yaml.nodes.Node): # type: ignore[no-untyped-def] """Add file reference information to an object.""" if isinstance(obj, list): obj = NodeListClass(obj) From bfb1abd3a231ebeee04fbcdb602ef7045f954cdc Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 18 Feb 2022 11:31:37 +0100 Subject: [PATCH 0782/1098] Add type ignore error codes [helpers] (#66776) --- homeassistant/helpers/aiohttp_client.py | 4 ++-- homeassistant/helpers/config_entry_oauth2_flow.py | 2 +- homeassistant/helpers/config_validation.py | 14 ++++++++------ homeassistant/helpers/data_entry_flow.py | 2 +- homeassistant/helpers/debounce.py | 2 +- homeassistant/helpers/entity.py | 6 +++--- homeassistant/helpers/httpx_client.py | 2 +- homeassistant/helpers/intent.py | 2 +- homeassistant/helpers/location.py | 6 +++--- homeassistant/helpers/restore_state.py | 2 +- homeassistant/helpers/service.py | 4 ++-- homeassistant/helpers/template.py | 2 +- 12 files changed, 25 insertions(+), 23 deletions(-) diff --git a/homeassistant/helpers/aiohttp_client.py b/homeassistant/helpers/aiohttp_client.py index 65b1b657ef451f..eaabb002b0a4dd 100644 --- a/homeassistant/helpers/aiohttp_client.py +++ b/homeassistant/helpers/aiohttp_client.py @@ -104,9 +104,9 @@ def _async_create_clientsession( # If a package requires a different user agent, override it by passing a headers # dictionary to the request method. # pylint: disable=protected-access - clientsession._default_headers = MappingProxyType({USER_AGENT: SERVER_SOFTWARE}) # type: ignore + clientsession._default_headers = MappingProxyType({USER_AGENT: SERVER_SOFTWARE}) # type: ignore[assignment] - clientsession.close = warn_use(clientsession.close, WARN_CLOSE_MSG) # type: ignore + clientsession.close = warn_use(clientsession.close, WARN_CLOSE_MSG) # type: ignore[assignment] if auto_cleanup_method: auto_cleanup_method(hass, clientsession) diff --git a/homeassistant/helpers/config_entry_oauth2_flow.py b/homeassistant/helpers/config_entry_oauth2_flow.py index 04e11ab99be55d..cf2d73715dec31 100644 --- a/homeassistant/helpers/config_entry_oauth2_flow.py +++ b/homeassistant/helpers/config_entry_oauth2_flow.py @@ -217,7 +217,7 @@ def __init__(self) -> None: ) self.external_data: Any = None - self.flow_impl: AbstractOAuth2Implementation = None # type: ignore + self.flow_impl: AbstractOAuth2Implementation = None # type: ignore[assignment] @property @abstractmethod diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index cbd1f4ffabaf1d..05f16bc06adf90 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -15,7 +15,9 @@ from numbers import Number import os import re -from socket import _GLOBAL_DEFAULT_TIMEOUT # type: ignore # private, not in typeshed +from socket import ( # type: ignore[attr-defined] # private, not in typeshed + _GLOBAL_DEFAULT_TIMEOUT, +) from typing import Any, TypeVar, cast, overload from urllib.parse import urlparse from uuid import UUID @@ -163,7 +165,7 @@ def boolean(value: Any) -> bool: return False elif isinstance(value, Number): # type ignore: https://github.com/python/mypy/issues/3186 - return value != 0 # type: ignore + return value != 0 # type: ignore[comparison-overlap] raise vol.Invalid(f"invalid boolean value {value}") @@ -421,7 +423,7 @@ def date(value: Any) -> date_sys: def time_period_str(value: str) -> timedelta: """Validate and transform time offset.""" - if isinstance(value, int): # type: ignore + if isinstance(value, int): # type: ignore[unreachable] raise vol.Invalid("Make sure you wrap time values in quotes") if not isinstance(value, str): raise vol.Invalid(TIME_PERIOD_ERROR.format(value)) @@ -585,7 +587,7 @@ def template(value: Any | None) -> template_helper.Template: if isinstance(value, (list, dict, template_helper.Template)): raise vol.Invalid("template value should be a string") - template_value = template_helper.Template(str(value)) # type: ignore + template_value = template_helper.Template(str(value)) # type: ignore[no-untyped-call] try: template_value.ensure_valid() @@ -603,7 +605,7 @@ def dynamic_template(value: Any | None) -> template_helper.Template: if not template_helper.is_template_string(str(value)): raise vol.Invalid("template value does not contain a dynamic template") - template_value = template_helper.Template(str(value)) # type: ignore + template_value = template_helper.Template(str(value)) # type: ignore[no-untyped-call] try: template_value.ensure_valid() return template_value @@ -796,7 +798,7 @@ def validator(config: dict) -> dict: """Check if key is in config and log warning or error.""" if key in config: try: - near = f"near {config.__config_file__}:{config.__line__} " # type: ignore + near = f"near {config.__config_file__}:{config.__line__} " # type: ignore[attr-defined] except AttributeError: near = "" arguments: tuple[str, ...] diff --git a/homeassistant/helpers/data_entry_flow.py b/homeassistant/helpers/data_entry_flow.py index 09345bf51bfd11..07f5e640ea3f79 100644 --- a/homeassistant/helpers/data_entry_flow.py +++ b/homeassistant/helpers/data_entry_flow.py @@ -70,7 +70,7 @@ async def post(self, request: web.Request, data: dict[str, Any]) -> web.Response try: result = await self._flow_mgr.async_init( - handler, # type: ignore + handler, # type: ignore[arg-type] context={ "source": config_entries.SOURCE_USER, "show_advanced_options": data["show_advanced_options"], diff --git a/homeassistant/helpers/debounce.py b/homeassistant/helpers/debounce.py index e3f13e3ad16c01..7937459b50cb8e 100644 --- a/homeassistant/helpers/debounce.py +++ b/homeassistant/helpers/debounce.py @@ -97,7 +97,7 @@ async def _handle_timer_finish(self) -> None: async with self._execute_lock: # Abort if timer got set while we're waiting for the lock. if self._timer_task: - return # type: ignore + return # type: ignore[unreachable] try: task = self.hass.async_run_hass_job(self._job) diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index bf2e13c1e24805..bb600556991bd1 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -255,12 +255,12 @@ class Entity(ABC): # SAFE TO OVERWRITE # The properties and methods here are safe to overwrite when inheriting # this class. These may be used to customize the behavior of the entity. - entity_id: str = None # type: ignore + entity_id: str = None # type: ignore[assignment] # Owning hass instance. Will be set by EntityPlatform # While not purely typed, it makes typehinting more useful for us # and removes the need for constant None checks or asserts. - hass: HomeAssistant = None # type: ignore + hass: HomeAssistant = None # type: ignore[assignment] # Owning platform instance. Will be set by EntityPlatform platform: EntityPlatform | None = None @@ -770,7 +770,7 @@ def add_to_platform_start( @callback def add_to_platform_abort(self) -> None: """Abort adding an entity to a platform.""" - self.hass = None # type: ignore + self.hass = None # type: ignore[assignment] self.platform = None self.parallel_updates = None self._added = False diff --git a/homeassistant/helpers/httpx_client.py b/homeassistant/helpers/httpx_client.py index d1dc11aae4d2cf..e2ebbd31dacd50 100644 --- a/homeassistant/helpers/httpx_client.py +++ b/homeassistant/helpers/httpx_client.py @@ -71,7 +71,7 @@ def create_async_httpx_client( original_aclose = client.aclose - client.aclose = warn_use( # type: ignore + client.aclose = warn_use( # type: ignore[assignment] client.aclose, "closes the Home Assistant httpx client" ) diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 13cc32a35b60a5..44dd21d7fa39c3 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -152,7 +152,7 @@ def async_validate_slots(self, slots: _SlotsType) -> _SlotsType: extra=vol.ALLOW_EXTRA, ) - return self._slot_schema(slots) # type: ignore + return self._slot_schema(slots) # type: ignore[no-any-return] async def async_handle(self, intent_obj: Intent) -> IntentResponse: """Handle the intent.""" diff --git a/homeassistant/helpers/location.py b/homeassistant/helpers/location.py index 06fb07608185f8..bbc32145706ce8 100644 --- a/homeassistant/helpers/location.py +++ b/homeassistant/helpers/location.py @@ -68,11 +68,11 @@ def find_coordinates( # Check if entity_state is a zone zone_entity = hass.states.get(f"zone.{entity_state.state}") - if has_location(zone_entity): # type: ignore + if has_location(zone_entity): # type: ignore[arg-type] _LOGGER.debug( - "%s is in %s, getting zone location", name, zone_entity.entity_id # type: ignore + "%s is in %s, getting zone location", name, zone_entity.entity_id # type: ignore[union-attr] ) - return _get_location_from_attributes(zone_entity) # type: ignore + return _get_location_from_attributes(zone_entity) # type: ignore[arg-type] # Check if entity_state is a friendly name of a zone if (zone_coords := resolve_zone(hass, entity_state.state)) is not None: diff --git a/homeassistant/helpers/restore_state.py b/homeassistant/helpers/restore_state.py index 79d46f8ec2e6c4..b8262d3a53320e 100644 --- a/homeassistant/helpers/restore_state.py +++ b/homeassistant/helpers/restore_state.py @@ -258,7 +258,7 @@ def _encode(value: Any) -> Any: """Little helper to JSON encode a value.""" try: return JSONEncoder.default( - None, # type: ignore + None, # type: ignore[arg-type] value, ) except TypeError: diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 3cf11453e20f1e..e638288a58cec5 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -485,7 +485,7 @@ async def async_get_all_descriptions( # Cache missing descriptions if description is None: domain_yaml = loaded[domain] - yaml_description = domain_yaml.get(service, {}) # type: ignore + yaml_description = domain_yaml.get(service, {}) # type: ignore[union-attr] # Don't warn for missing services, because it triggers false # positives for things like scripts, that register as a service @@ -696,7 +696,7 @@ async def _handle_entity_call( entity.async_set_context(context) if isinstance(func, str): - result = hass.async_run_job(partial(getattr(entity, func), **data)) # type: ignore + result = hass.async_run_job(partial(getattr(entity, func), **data)) # type: ignore[arg-type] else: result = hass.async_run_job(func, entity, data) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index d371017a475090..2b93b69fe3795b 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -836,7 +836,7 @@ def _state_generator(hass: HomeAssistant, domain: str | None) -> Generator: def _get_state_if_valid(hass: HomeAssistant, entity_id: str) -> TemplateState | None: state = hass.states.get(entity_id) if state is None and not valid_entity_id(entity_id): - raise TemplateError(f"Invalid entity ID '{entity_id}'") # type: ignore + raise TemplateError(f"Invalid entity ID '{entity_id}'") # type: ignore[arg-type] return _get_template_state_from_state(hass, entity_id, state) From 2abcd7cd947e487fbeb1817fb5e6653617e03069 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 18 Feb 2022 11:35:44 +0100 Subject: [PATCH 0783/1098] Correct state restoring for MQTT temperature sensors (#66741) * Correct state restoring for MQTT temperature sensors * Adjust test * Adjust test --- homeassistant/components/mqtt/sensor.py | 9 +++---- tests/components/mqtt/test_sensor.py | 32 +++++++++++++++++++------ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index 31a784a259e9f9..c24535ebd1faef 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -13,8 +13,8 @@ DEVICE_CLASSES_SCHEMA, ENTITY_ID_FORMAT, STATE_CLASSES_SCHEMA, + RestoreSensor, SensorDeviceClass, - SensorEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -30,7 +30,6 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_track_point_in_utc_time -from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util import dt as dt_util @@ -144,7 +143,7 @@ async def _async_setup_entity( async_add_entities([MqttSensor(hass, config, config_entry, discovery_data)]) -class MqttSensor(MqttEntity, SensorEntity, RestoreEntity): +class MqttSensor(MqttEntity, RestoreSensor): """Representation of a sensor that can be updated using MQTT.""" _entity_id_format = ENTITY_ID_FORMAT @@ -172,6 +171,8 @@ async def async_added_to_hass(self) -> None: and expire_after > 0 and (last_state := await self.async_get_last_state()) is not None and last_state.state not in [STATE_UNKNOWN, STATE_UNAVAILABLE] + and (last_sensor_data := await self.async_get_last_sensor_data()) + is not None # We might have set up a trigger already after subscribing from # super().async_added_to_hass(), then we should not restore state and not self._expiration_trigger @@ -182,7 +183,7 @@ async def async_added_to_hass(self) -> None: _LOGGER.debug("Skip state recovery after reload for %s", self.entity_id) return self._expired = False - self._state = last_state.state + self._state = last_sensor_data.native_value self._expiration_trigger = async_track_point_in_utc_time( self.hass, self._value_is_expired, expiration_at diff --git a/tests/components/mqtt/test_sensor.py b/tests/components/mqtt/test_sensor.py index 8a1be6b11e2860..b653e04c82ee41 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -2,13 +2,19 @@ import copy from datetime import datetime, timedelta import json -from unittest.mock import patch +from unittest.mock import MagicMock, patch import pytest from homeassistant.components.mqtt.sensor import MQTT_SENSOR_ATTRIBUTES_BLOCKED import homeassistant.components.sensor as sensor -from homeassistant.const import EVENT_STATE_CHANGED, STATE_UNAVAILABLE, STATE_UNKNOWN +from homeassistant.const import ( + EVENT_STATE_CHANGED, + STATE_UNAVAILABLE, + STATE_UNKNOWN, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, +) import homeassistant.core as ha from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component @@ -989,10 +995,15 @@ async def test_cleanup_triggers_and_restoring_state( config1["name"] = "test1" config1["expire_after"] = 30 config1["state_topic"] = "test-topic1" + config1["device_class"] = "temperature" + config1["unit_of_measurement"] = TEMP_FAHRENHEIT + config2 = copy.deepcopy(DEFAULT_CONFIG[domain]) config2["name"] = "test2" config2["expire_after"] = 5 config2["state_topic"] = "test-topic2" + config2["device_class"] = "temperature" + config2["unit_of_measurement"] = TEMP_CELSIUS freezer.move_to("2022-02-02 12:01:00+01:00") @@ -1004,7 +1015,7 @@ async def test_cleanup_triggers_and_restoring_state( await hass.async_block_till_done() async_fire_mqtt_message(hass, "test-topic1", "100") state = hass.states.get("sensor.test1") - assert state.state == "100" + assert state.state == "38" # 100 °F -> 38 °C async_fire_mqtt_message(hass, "test-topic2", "200") state = hass.states.get("sensor.test2") @@ -1026,14 +1037,14 @@ async def test_cleanup_triggers_and_restoring_state( assert "State recovered after reload for sensor.test2" not in caplog.text state = hass.states.get("sensor.test1") - assert state.state == "100" + assert state.state == "38" # 100 °F -> 38 °C state = hass.states.get("sensor.test2") assert state.state == STATE_UNAVAILABLE - async_fire_mqtt_message(hass, "test-topic1", "101") + async_fire_mqtt_message(hass, "test-topic1", "80") state = hass.states.get("sensor.test1") - assert state.state == "101" + assert state.state == "27" # 80 °F -> 27 °C async_fire_mqtt_message(hass, "test-topic2", "201") state = hass.states.get("sensor.test2") @@ -1057,10 +1068,16 @@ async def test_skip_restoring_state_with_over_due_expire_trigger( {}, last_changed=datetime.fromisoformat("2022-02-02 12:01:35+01:00"), ) + fake_extra_data = MagicMock() with patch( "homeassistant.helpers.restore_state.RestoreEntity.async_get_last_state", return_value=fake_state, - ), assert_setup_component(1, domain): + ), patch( + "homeassistant.helpers.restore_state.RestoreEntity.async_get_last_extra_data", + return_value=fake_extra_data, + ), assert_setup_component( + 1, domain + ): assert await async_setup_component(hass, domain, {domain: config3}) await hass.async_block_till_done() assert "Skip state recovery after reload for sensor.test3" in caplog.text @@ -1087,4 +1104,5 @@ async def test_encoding_subscribable_topics( value, attribute, attribute_value, + skip_raw_test=True, ) From cb1efa54bbc4db80dd465b65d60c51e18de4783c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 18 Feb 2022 11:54:44 +0100 Subject: [PATCH 0784/1098] Add `workflow_dispatch` ci trigger (#66697) --- .github/workflows/ci.yaml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a2345081b507bb..8492a15a8a342c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -8,6 +8,16 @@ on: - rc - master pull_request: ~ + workflow_dispatch: + inputs: + full: + description: 'Full run (regardless of changes)' + default: false + type: boolean + lint-only: + description: 'Skip pytest' + default: false + type: boolean env: CACHE_VERSION: 9 @@ -108,7 +118,8 @@ jobs: if [[ "${{ github.ref }}" == "refs/heads/dev" ]] \ || [[ "${{ github.ref }}" == "refs/heads/master" ]] \ || [[ "${{ github.ref }}" == "refs/heads/rc" ]] \ - || [[ "${{ steps.core.outputs.any }}" == "true" ]]; + || [[ "${{ steps.core.outputs.any }}" == "true" ]] \ + || [[ "${{ github.event.inputs.full }}" == "true" ]]; then test_groups="[1, 2, 3, 4, 5, 6]" test_group_count=6 @@ -707,7 +718,8 @@ jobs: pytest: runs-on: ubuntu-latest - if: needs.changes.outputs.test_full_suite == 'true' || needs.changes.outputs.tests_glob + if: github.event.inputs.lint-only != 'true' && ( + needs.changes.outputs.test_full_suite == 'true' || needs.changes.outputs.tests_glob) needs: - changes - gen-requirements-all From ba6d1976dff8df2aa32726ff2acbf0ba61e5c550 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 18 Feb 2022 13:45:25 +0100 Subject: [PATCH 0785/1098] Improve MQTT device removal (#66766) * Improve MQTT device removal * Update homeassistant/components/mqtt/mixins.py Co-authored-by: Martin Hjelmare * Adjust tests * Improve test coverage Co-authored-by: Martin Hjelmare --- homeassistant/components/mqtt/__init__.py | 21 +- .../components/mqtt/device_automation.py | 14 +- .../components/mqtt/device_trigger.py | 6 +- homeassistant/components/mqtt/mixins.py | 40 ++- homeassistant/components/mqtt/tag.py | 30 ++- .../mqtt/test_device_tracker_discovery.py | 19 +- tests/components/mqtt/test_device_trigger.py | 33 ++- tests/components/mqtt/test_discovery.py | 232 +++++++++++++++++- tests/components/mqtt/test_tag.py | 87 +++++-- 9 files changed, 427 insertions(+), 55 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index b97a0bc8770f59..23a1fcc579e04a 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -56,6 +56,7 @@ event, template, ) +from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send from homeassistant.helpers.entity import Entity from homeassistant.helpers.frame import report @@ -1198,8 +1199,8 @@ def websocket_mqtt_info(hass, connection, msg): @websocket_api.websocket_command( {vol.Required("type"): "mqtt/device/remove", vol.Required("device_id"): str} ) -@callback -def websocket_remove_device(hass, connection, msg): +@websocket_api.async_response +async def websocket_remove_device(hass, connection, msg): """Delete device.""" device_id = msg["device_id"] device_registry = dr.async_get(hass) @@ -1214,7 +1215,10 @@ def websocket_remove_device(hass, connection, msg): config_entry = hass.config_entries.async_get_entry(config_entry) # Only delete the device if it belongs to an MQTT device entry if config_entry.domain == DOMAIN: - device_registry.async_remove_device(device_id) + await async_remove_config_entry_device(hass, config_entry, device) + device_registry.async_update_device( + device_id, remove_config_entry_id=config_entry.entry_id + ) connection.send_message(websocket_api.result_message(msg["id"])) return @@ -1292,3 +1296,14 @@ def unsubscribe(): def is_connected(hass: HomeAssistant) -> bool: """Return if MQTT client is connected.""" return hass.data[DATA_MQTT].connected + + +async def async_remove_config_entry_device( + hass: HomeAssistant, config_entry: ConfigEntry, device_entry: DeviceEntry +) -> bool: + """Remove MQTT config entry from a device.""" + # pylint: disable-next=import-outside-toplevel + from . import device_automation + + await device_automation.async_removed_from_device(hass, device_entry.id) + return True diff --git a/homeassistant/components/mqtt/device_automation.py b/homeassistant/components/mqtt/device_automation.py index 50d6a6e4d19587..cafbd66b098634 100644 --- a/homeassistant/components/mqtt/device_automation.py +++ b/homeassistant/components/mqtt/device_automation.py @@ -3,8 +3,6 @@ import voluptuous as vol -from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED - from . import device_trigger from .. import mqtt from .mixins import async_setup_entry_helper @@ -23,15 +21,8 @@ async def async_setup_entry(hass, config_entry): """Set up MQTT device automation dynamically through MQTT discovery.""" - async def async_device_removed(event): - """Handle the removal of a device.""" - if event.data["action"] != "remove": - return - await device_trigger.async_device_removed(hass, event.data["device_id"]) - setup = functools.partial(_async_setup_automation, hass, config_entry=config_entry) await async_setup_entry_helper(hass, "device_automation", setup, PLATFORM_SCHEMA) - hass.bus.async_listen(EVENT_DEVICE_REGISTRY_UPDATED, async_device_removed) async def _async_setup_automation(hass, config, config_entry, discovery_data): @@ -40,3 +31,8 @@ async def _async_setup_automation(hass, config, config_entry, discovery_data): await device_trigger.async_setup_trigger( hass, config, config_entry, discovery_data ) + + +async def async_removed_from_device(hass, device_id): + """Handle Mqtt removed from a device.""" + await device_trigger.async_removed_from_device(hass, device_id) diff --git a/homeassistant/components/mqtt/device_trigger.py b/homeassistant/components/mqtt/device_trigger.py index f621021e124dc7..71c0a9f93645bf 100644 --- a/homeassistant/components/mqtt/device_trigger.py +++ b/homeassistant/components/mqtt/device_trigger.py @@ -222,7 +222,7 @@ async def discovery_update(payload): device_trigger.detach_trigger() clear_discovery_hash(hass, discovery_hash) remove_signal() - await cleanup_device_registry(hass, device.id) + await cleanup_device_registry(hass, device.id, config_entry.entry_id) else: # Non-empty payload: Update trigger _LOGGER.info("Updating trigger: %s", discovery_hash) @@ -275,8 +275,8 @@ async def discovery_update(payload): async_dispatcher_send(hass, MQTT_DISCOVERY_DONE.format(discovery_hash), None) -async def async_device_removed(hass: HomeAssistant, device_id: str): - """Handle the removal of a device.""" +async def async_removed_from_device(hass: HomeAssistant, device_id: str): + """Handle Mqtt removed from a device.""" triggers = await async_get_triggers(hass, device_id) for trig in triggers: device_trigger = hass.data[DEVICE_TRIGGERS].pop(trig[CONF_DISCOVERY_ID]) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index fb25fa1e1b65d7..6f881a7069093d 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -25,7 +25,7 @@ CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import ( config_validation as cv, device_registry as dr, @@ -496,7 +496,7 @@ def available(self) -> bool: return self._available_latest -async def cleanup_device_registry(hass, device_id): +async def cleanup_device_registry(hass, device_id, config_entry_id): """Remove device registry entry if there are no remaining entities or triggers.""" # Local import to avoid circular dependencies # pylint: disable-next=import-outside-toplevel @@ -512,7 +512,9 @@ async def cleanup_device_registry(hass, device_id): and not await device_trigger.async_get_triggers(hass, device_id) and not tag.async_has_tags(hass, device_id) ): - device_registry.async_remove_device(device_id) + device_registry.async_update_device( + device_id, remove_config_entry_id=config_entry_id + ) class MqttDiscoveryUpdate(Entity): @@ -542,7 +544,9 @@ async def _async_remove_state_and_registry_entry(self) -> None: entity_registry = er.async_get(self.hass) if entity_entry := entity_registry.async_get(self.entity_id): entity_registry.async_remove(self.entity_id) - await cleanup_device_registry(self.hass, entity_entry.device_id) + await cleanup_device_registry( + self.hass, entity_entry.device_id, entity_entry.config_entry_id + ) else: await self.async_remove(force_remove=True) @@ -817,3 +821,31 @@ def should_poll(self): def unique_id(self): """Return a unique ID.""" return self._unique_id + + +@callback +def async_removed_from_device( + hass: HomeAssistant, event: Event, mqtt_device_id: str, config_entry_id: str +) -> bool: + """Check if the passed event indicates MQTT was removed from a device.""" + device_id = event.data["device_id"] + if event.data["action"] not in ("remove", "update"): + return False + + if device_id != mqtt_device_id: + return False + + if event.data["action"] == "update": + if "config_entries" not in event.data["changes"]: + return False + device_registry = dr.async_get(hass) + device_entry = device_registry.async_get(mqtt_device_id) + if not device_entry: + # The device is already removed, do cleanup when we get "remove" event + return False + entry_id = config_entry_id + if entry_id in device_entry.config_entries: + # Not removed from device + return False + + return True diff --git a/homeassistant/components/mqtt/tag.py b/homeassistant/components/mqtt/tag.py index 186f11534b970a..4f6f380e47db9e 100644 --- a/homeassistant/components/mqtt/tag.py +++ b/homeassistant/components/mqtt/tag.py @@ -27,6 +27,7 @@ CONF_CONNECTIONS, CONF_IDENTIFIERS, MQTT_ENTITY_DEVICE_INFO_SCHEMA, + async_removed_from_device, async_setup_entry_helper, cleanup_device_registry, device_info_from_config, @@ -126,9 +127,11 @@ async def discovery_update(self, payload): if not payload: # Empty payload: Remove tag scanner _LOGGER.info("Removing tag scanner: %s", discovery_hash) - await self.tear_down() + self.tear_down() if self.device_id: - await cleanup_device_registry(self.hass, self.device_id) + await cleanup_device_registry( + self.hass, self.device_id, self._config_entry.entry_id + ) else: # Non-empty payload: Update tag scanner _LOGGER.info("Updating tag scanner: %s", discovery_hash) @@ -155,7 +158,7 @@ async def setup(self): await self.subscribe_topics() if self.device_id: self._remove_device_updated = self.hass.bus.async_listen( - EVENT_DEVICE_REGISTRY_UPDATED, self.device_removed + EVENT_DEVICE_REGISTRY_UPDATED, self.device_updated ) self._remove_discovery = async_dispatcher_connect( self.hass, @@ -189,26 +192,31 @@ async def tag_scanned(msg): ) await subscription.async_subscribe_topics(self.hass, self._sub_state) - async def device_removed(self, event): - """Handle the removal of a device.""" - device_id = event.data["device_id"] - if event.data["action"] != "remove" or device_id != self.device_id: + async def device_updated(self, event): + """Handle the update or removal of a device.""" + if not async_removed_from_device( + self.hass, event, self.device_id, self._config_entry.entry_id + ): return - await self.tear_down() + # Stop subscribing to discovery updates to not trigger when we clear the + # discovery topic + self.tear_down() - async def tear_down(self): + # Clear the discovery topic so the entity is not rediscovered after a restart + discovery_topic = self.discovery_data[ATTR_DISCOVERY_TOPIC] + mqtt.publish(self.hass, discovery_topic, "", retain=True) + + def tear_down(self): """Cleanup tag scanner.""" discovery_hash = self.discovery_data[ATTR_DISCOVERY_HASH] discovery_id = discovery_hash[1] - discovery_topic = self.discovery_data[ATTR_DISCOVERY_TOPIC] clear_discovery_hash(self.hass, discovery_hash) if self.device_id: self._remove_device_updated() self._remove_discovery() - mqtt.publish(self.hass, discovery_topic, "", retain=True) self._sub_state = subscription.async_unsubscribe_topics( self.hass, self._sub_state ) diff --git a/tests/components/mqtt/test_device_tracker_discovery.py b/tests/components/mqtt/test_device_tracker_discovery.py index 4020c2beaebdb5..3b83581b86aa5f 100644 --- a/tests/components/mqtt/test_device_tracker_discovery.py +++ b/tests/components/mqtt/test_device_tracker_discovery.py @@ -5,6 +5,7 @@ from homeassistant.components import device_tracker from homeassistant.components.mqtt.discovery import ALREADY_DISCOVERED from homeassistant.const import STATE_HOME, STATE_NOT_HOME, STATE_UNKNOWN +from homeassistant.setup import async_setup_component from .test_common import help_test_setting_blocked_attribute_via_mqtt_json_message @@ -183,8 +184,13 @@ async def test_device_tracker_discovery_update(hass, mqtt_mock, caplog): assert state.name == "Cider" -async def test_cleanup_device_tracker(hass, device_reg, entity_reg, mqtt_mock): +async def test_cleanup_device_tracker( + hass, hass_ws_client, device_reg, entity_reg, mqtt_mock +): """Test discvered device is cleaned up when removed from registry.""" + assert await async_setup_component(hass, "config", {}) + ws_client = await hass_ws_client(hass) + async_fire_mqtt_message( hass, "homeassistant/device_tracker/bla/config", @@ -203,7 +209,16 @@ async def test_cleanup_device_tracker(hass, device_reg, entity_reg, mqtt_mock): state = hass.states.get("device_tracker.mqtt_unique") assert state is not None - device_reg.async_remove_device(device_entry.id) + # Remove MQTT from the device + await ws_client.send_json( + { + "id": 6, + "type": "mqtt/device/remove", + "device_id": device_entry.id, + } + ) + response = await ws_client.receive_json() + assert response["success"] await hass.async_block_till_done() await hass.async_block_till_done() diff --git a/tests/components/mqtt/test_device_trigger.py b/tests/components/mqtt/test_device_trigger.py index 972b0678ed276b..8a3719f1707b80 100644 --- a/tests/components/mqtt/test_device_trigger.py +++ b/tests/components/mqtt/test_device_trigger.py @@ -646,9 +646,12 @@ async def test_not_fires_on_mqtt_message_after_remove_by_mqtt( async def test_not_fires_on_mqtt_message_after_remove_from_registry( - hass, device_reg, calls, mqtt_mock + hass, hass_ws_client, device_reg, calls, mqtt_mock ): """Test triggers not firing after removal.""" + assert await async_setup_component(hass, "config", {}) + ws_client = await hass_ws_client(hass) + data1 = ( '{ "automation_type":"trigger",' ' "device":{"identifiers":["0AFFD2"]},' @@ -688,8 +691,16 @@ async def test_not_fires_on_mqtt_message_after_remove_from_registry( await hass.async_block_till_done() assert len(calls) == 1 - # Remove the device - device_reg.async_remove_device(device_entry.id) + # Remove MQTT from the device + await ws_client.send_json( + { + "id": 6, + "type": "mqtt/device/remove", + "device_id": device_entry.id, + } + ) + response = await ws_client.receive_json() + assert response["success"] await hass.async_block_till_done() async_fire_mqtt_message(hass, "foobar/triggers/button1", "short_press") @@ -967,8 +978,11 @@ async def test_entity_device_info_update(hass, mqtt_mock): assert device.name == "Milk" -async def test_cleanup_trigger(hass, device_reg, entity_reg, mqtt_mock): +async def test_cleanup_trigger(hass, hass_ws_client, device_reg, entity_reg, mqtt_mock): """Test trigger discovery topic is cleaned when device is removed from registry.""" + assert await async_setup_component(hass, "config", {}) + ws_client = await hass_ws_client(hass) + config = { "automation_type": "trigger", "topic": "test-topic", @@ -990,7 +1004,16 @@ async def test_cleanup_trigger(hass, device_reg, entity_reg, mqtt_mock): ) assert triggers[0]["type"] == "foo" - device_reg.async_remove_device(device_entry.id) + # Remove MQTT from the device + await ws_client.send_json( + { + "id": 6, + "type": "mqtt/device/remove", + "device_id": device_entry.id, + } + ) + response = await ws_client.receive_json() + assert response["success"] await hass.async_block_till_done() await hass.async_block_till_done() diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index 5d94f349c580e4..463f3d03fff10d 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -1,7 +1,8 @@ """The tests for the MQTT discovery.""" +import json from pathlib import Path import re -from unittest.mock import AsyncMock, patch +from unittest.mock import AsyncMock, call, patch import pytest @@ -19,8 +20,10 @@ STATE_UNKNOWN, ) import homeassistant.core as ha +from homeassistant.setup import async_setup_component from tests.common import ( + MockConfigEntry, async_fire_mqtt_message, mock_device_registry, mock_entity_platform, @@ -565,8 +568,11 @@ async def test_duplicate_removal(hass, mqtt_mock, caplog): assert "Component has already been discovered: binary_sensor bla" not in caplog.text -async def test_cleanup_device(hass, device_reg, entity_reg, mqtt_mock): - """Test discvered device is cleaned up when removed from registry.""" +async def test_cleanup_device(hass, hass_ws_client, device_reg, entity_reg, mqtt_mock): + """Test discvered device is cleaned up when entry removed from device.""" + assert await async_setup_component(hass, "config", {}) + ws_client = await hass_ws_client(hass) + data = ( '{ "device":{"identifiers":["0AFFD2"]},' ' "state_topic": "foobar/sensor",' @@ -585,7 +591,16 @@ async def test_cleanup_device(hass, device_reg, entity_reg, mqtt_mock): state = hass.states.get("sensor.mqtt_sensor") assert state is not None - device_reg.async_remove_device(device_entry.id) + # Remove MQTT from the device + await ws_client.send_json( + { + "id": 6, + "type": "mqtt/device/remove", + "device_id": device_entry.id, + } + ) + response = await ws_client.receive_json() + assert response["success"] await hass.async_block_till_done() await hass.async_block_till_done() @@ -606,6 +621,215 @@ async def test_cleanup_device(hass, device_reg, entity_reg, mqtt_mock): ) +async def test_cleanup_device_mqtt(hass, device_reg, entity_reg, mqtt_mock): + """Test discvered device is cleaned up when removed through MQTT.""" + data = ( + '{ "device":{"identifiers":["0AFFD2"]},' + ' "state_topic": "foobar/sensor",' + ' "unique_id": "unique" }' + ) + + async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data) + await hass.async_block_till_done() + + # Verify device and registry entries are created + device_entry = device_reg.async_get_device({("mqtt", "0AFFD2")}) + assert device_entry is not None + entity_entry = entity_reg.async_get("sensor.mqtt_sensor") + assert entity_entry is not None + + state = hass.states.get("sensor.mqtt_sensor") + assert state is not None + + async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", "") + await hass.async_block_till_done() + await hass.async_block_till_done() + + # Verify device and registry entries are cleared + device_entry = device_reg.async_get_device({("mqtt", "0AFFD2")}) + assert device_entry is None + entity_entry = entity_reg.async_get("sensor.mqtt_sensor") + assert entity_entry is None + + # Verify state is removed + state = hass.states.get("sensor.mqtt_sensor") + assert state is None + await hass.async_block_till_done() + + # Verify retained discovery topics have not been cleared again + mqtt_mock.async_publish.assert_not_called() + + +async def test_cleanup_device_multiple_config_entries( + hass, hass_ws_client, device_reg, entity_reg, mqtt_mock +): + """Test discovered device is cleaned up when entry removed from device.""" + assert await async_setup_component(hass, "config", {}) + ws_client = await hass_ws_client(hass) + + config_entry = MockConfigEntry(domain="test", data={}) + config_entry.add_to_hass(hass) + device_entry = device_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={("mac", "12:34:56:AB:CD:EF")}, + ) + + mqtt_config_entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0] + + sensor_config = { + "device": {"connections": [["mac", "12:34:56:AB:CD:EF"]]}, + "state_topic": "foobar/sensor", + "unique_id": "unique", + } + tag_config = { + "device": {"connections": [["mac", "12:34:56:AB:CD:EF"]]}, + "topic": "test-topic", + } + trigger_config = { + "automation_type": "trigger", + "topic": "test-topic", + "type": "foo", + "subtype": "bar", + "device": {"connections": [["mac", "12:34:56:AB:CD:EF"]]}, + } + + sensor_data = json.dumps(sensor_config) + tag_data = json.dumps(tag_config) + trigger_data = json.dumps(trigger_config) + async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", sensor_data) + async_fire_mqtt_message(hass, "homeassistant/tag/bla/config", tag_data) + async_fire_mqtt_message( + hass, "homeassistant/device_automation/bla/config", trigger_data + ) + await hass.async_block_till_done() + + # Verify device and registry entries are created + device_entry = device_reg.async_get_device(set(), {("mac", "12:34:56:AB:CD:EF")}) + assert device_entry is not None + assert device_entry.config_entries == { + mqtt_config_entry.entry_id, + config_entry.entry_id, + } + entity_entry = entity_reg.async_get("sensor.mqtt_sensor") + assert entity_entry is not None + + state = hass.states.get("sensor.mqtt_sensor") + assert state is not None + + # Remove MQTT from the device + await ws_client.send_json( + { + "id": 6, + "type": "mqtt/device/remove", + "device_id": device_entry.id, + } + ) + response = await ws_client.receive_json() + assert response["success"] + + await hass.async_block_till_done() + await hass.async_block_till_done() + + # Verify device is still there but entity is cleared + device_entry = device_reg.async_get_device(set(), {("mac", "12:34:56:AB:CD:EF")}) + assert device_entry is not None + entity_entry = entity_reg.async_get("sensor.mqtt_sensor") + assert device_entry.config_entries == {config_entry.entry_id} + assert entity_entry is None + + # Verify state is removed + state = hass.states.get("sensor.mqtt_sensor") + assert state is None + await hass.async_block_till_done() + + # Verify retained discovery topic has been cleared + mqtt_mock.async_publish.assert_has_calls( + [ + call("homeassistant/sensor/bla/config", "", 0, True), + call("homeassistant/tag/bla/config", "", 0, True), + call("homeassistant/device_automation/bla/config", "", 0, True), + ], + any_order=True, + ) + + +async def test_cleanup_device_multiple_config_entries_mqtt( + hass, device_reg, entity_reg, mqtt_mock +): + """Test discovered device is cleaned up when removed through MQTT.""" + config_entry = MockConfigEntry(domain="test", data={}) + config_entry.add_to_hass(hass) + device_entry = device_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={("mac", "12:34:56:AB:CD:EF")}, + ) + + mqtt_config_entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0] + + sensor_config = { + "device": {"connections": [["mac", "12:34:56:AB:CD:EF"]]}, + "state_topic": "foobar/sensor", + "unique_id": "unique", + } + tag_config = { + "device": {"connections": [["mac", "12:34:56:AB:CD:EF"]]}, + "topic": "test-topic", + } + trigger_config = { + "automation_type": "trigger", + "topic": "test-topic", + "type": "foo", + "subtype": "bar", + "device": {"connections": [["mac", "12:34:56:AB:CD:EF"]]}, + } + + sensor_data = json.dumps(sensor_config) + tag_data = json.dumps(tag_config) + trigger_data = json.dumps(trigger_config) + async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", sensor_data) + async_fire_mqtt_message(hass, "homeassistant/tag/bla/config", tag_data) + async_fire_mqtt_message( + hass, "homeassistant/device_automation/bla/config", trigger_data + ) + await hass.async_block_till_done() + + # Verify device and registry entries are created + device_entry = device_reg.async_get_device(set(), {("mac", "12:34:56:AB:CD:EF")}) + assert device_entry is not None + assert device_entry.config_entries == { + mqtt_config_entry.entry_id, + config_entry.entry_id, + } + entity_entry = entity_reg.async_get("sensor.mqtt_sensor") + assert entity_entry is not None + + state = hass.states.get("sensor.mqtt_sensor") + assert state is not None + + # Send MQTT messages to remove + async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", "") + async_fire_mqtt_message(hass, "homeassistant/tag/bla/config", "") + async_fire_mqtt_message(hass, "homeassistant/device_automation/bla/config", "") + + await hass.async_block_till_done() + await hass.async_block_till_done() + + # Verify device is still there but entity is cleared + device_entry = device_reg.async_get_device(set(), {("mac", "12:34:56:AB:CD:EF")}) + assert device_entry is not None + entity_entry = entity_reg.async_get("sensor.mqtt_sensor") + assert device_entry.config_entries == {config_entry.entry_id} + assert entity_entry is None + + # Verify state is removed + state = hass.states.get("sensor.mqtt_sensor") + assert state is None + await hass.async_block_till_done() + + # Verify retained discovery topics have not been cleared again + mqtt_mock.async_publish.assert_not_called() + + async def test_discovery_expansion(hass, mqtt_mock, caplog): """Test expansion of abbreviated discovery payload.""" data = ( diff --git a/tests/components/mqtt/test_tag.py b/tests/components/mqtt/test_tag.py index e1f3de83a0d05d..7d3b4f2e1b2050 100644 --- a/tests/components/mqtt/test_tag.py +++ b/tests/components/mqtt/test_tag.py @@ -7,8 +7,10 @@ from homeassistant.components.device_automation import DeviceAutomationType from homeassistant.helpers import device_registry as dr +from homeassistant.setup import async_setup_component from tests.common import ( + MockConfigEntry, async_fire_mqtt_message, async_get_device_automations, mock_device_registry, @@ -355,11 +357,15 @@ async def test_not_fires_on_mqtt_message_after_remove_by_mqtt_without_device( async def test_not_fires_on_mqtt_message_after_remove_from_registry( hass, + hass_ws_client, device_reg, mqtt_mock, tag_mock, ): """Test tag scanning after removal.""" + assert await async_setup_component(hass, "config", {}) + ws_client = await hass_ws_client(hass) + config = copy.deepcopy(DEFAULT_CONFIG_DEVICE) async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config)) @@ -371,9 +377,16 @@ async def test_not_fires_on_mqtt_message_after_remove_from_registry( await hass.async_block_till_done() tag_mock.assert_called_once_with(ANY, DEFAULT_TAG_ID, device_entry.id) - # Remove the device - device_reg.async_remove_device(device_entry.id) - await hass.async_block_till_done() + # Remove MQTT from the device + await ws_client.send_json( + { + "id": 6, + "type": "mqtt/device/remove", + "device_id": device_entry.id, + } + ) + response = await ws_client.receive_json() + assert response["success"] tag_mock.reset_mock() async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN) @@ -473,32 +486,78 @@ async def test_entity_device_info_update(hass, mqtt_mock): assert device.name == "Milk" -async def test_cleanup_tag(hass, device_reg, entity_reg, mqtt_mock): +async def test_cleanup_tag(hass, hass_ws_client, device_reg, entity_reg, mqtt_mock): """Test tag discovery topic is cleaned when device is removed from registry.""" - config = { + assert await async_setup_component(hass, "config", {}) + ws_client = await hass_ws_client(hass) + + mqtt_entry = hass.config_entries.async_entries("mqtt")[0] + + config_entry = MockConfigEntry(domain="test") + config_entry.add_to_hass(hass) + + device_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections=set(), + identifiers={("mqtt", "helloworld")}, + ) + + config1 = { "topic": "test-topic", "device": {"identifiers": ["helloworld"]}, } + config2 = { + "topic": "test-topic", + "device": {"identifiers": ["hejhopp"]}, + } - data = json.dumps(config) - async_fire_mqtt_message(hass, "homeassistant/tag/bla/config", data) + data1 = json.dumps(config1) + data2 = json.dumps(config2) + async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", data1) + await hass.async_block_till_done() + async_fire_mqtt_message(hass, "homeassistant/tag/bla2/config", data2) await hass.async_block_till_done() - # Verify device registry entry is created - device_entry = device_reg.async_get_device({("mqtt", "helloworld")}) - assert device_entry is not None + # Verify device registry entries are created + device_entry1 = device_reg.async_get_device({("mqtt", "helloworld")}) + assert device_entry1 is not None + assert device_entry1.config_entries == {config_entry.entry_id, mqtt_entry.entry_id} + device_entry2 = device_reg.async_get_device({("mqtt", "hejhopp")}) + assert device_entry2 is not None - device_reg.async_remove_device(device_entry.id) + # Remove other config entry from the device + device_reg.async_update_device( + device_entry1.id, remove_config_entry_id=config_entry.entry_id + ) + device_entry1 = device_reg.async_get_device({("mqtt", "helloworld")}) + assert device_entry1 is not None + assert device_entry1.config_entries == {mqtt_entry.entry_id} + device_entry2 = device_reg.async_get_device({("mqtt", "hejhopp")}) + assert device_entry2 is not None + mqtt_mock.async_publish.assert_not_called() + + # Remove MQTT from the device + await ws_client.send_json( + { + "id": 6, + "type": "mqtt/device/remove", + "device_id": device_entry1.id, + } + ) + response = await ws_client.receive_json() + assert response["success"] await hass.async_block_till_done() await hass.async_block_till_done() # Verify device registry entry is cleared - device_entry = device_reg.async_get_device({("mqtt", "helloworld")}) - assert device_entry is None + device_entry1 = device_reg.async_get_device({("mqtt", "helloworld")}) + assert device_entry1 is None + device_entry2 = device_reg.async_get_device({("mqtt", "hejhopp")}) + assert device_entry2 is not None # Verify retained discovery topic has been cleared mqtt_mock.async_publish.assert_called_once_with( - "homeassistant/tag/bla/config", "", 0, True + "homeassistant/tag/bla1/config", "", 0, True ) From 56d45c49e92e30f74ec912f091a1ffaddee8f03e Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 18 Feb 2022 14:03:05 +0100 Subject: [PATCH 0786/1098] Clean webostv notify (#66803) * Replace conf with attr * Test notify service without data parameter * Clean kwargs access * Replace icon constant * Use data from notify --- homeassistant/components/webostv/notify.py | 6 ++-- tests/components/webostv/test_notify.py | 40 ++++++++++++++++------ 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/webostv/notify.py b/homeassistant/components/webostv/notify.py index 46f0086e0f6a0c..82e61856187905 100644 --- a/homeassistant/components/webostv/notify.py +++ b/homeassistant/components/webostv/notify.py @@ -7,7 +7,7 @@ from aiowebostv import WebOsClient, WebOsTvPairError from homeassistant.components.notify import ATTR_DATA, BaseNotificationService -from homeassistant.const import CONF_ICON +from homeassistant.const import ATTR_ICON from homeassistant.core import HomeAssistant from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -46,8 +46,8 @@ async def async_send_message(self, message: str = "", **kwargs: Any) -> None: if not self._client.is_connected(): await self._client.connect() - data = kwargs.get(ATTR_DATA) - icon_path = data.get(CONF_ICON) if data else None + data = kwargs[ATTR_DATA] + icon_path = data.get(ATTR_ICON) if data else None await self._client.send_message(message, icon_path=icon_path) except WebOsTvPairError: _LOGGER.error("Pairing with TV failed") diff --git a/tests/components/webostv/test_notify.py b/tests/components/webostv/test_notify.py index 7e150c6eb7828d..92fb151c1b34e6 100644 --- a/tests/components/webostv/test_notify.py +++ b/tests/components/webostv/test_notify.py @@ -4,9 +4,13 @@ from aiowebostv import WebOsTvPairError import pytest -from homeassistant.components.notify import ATTR_MESSAGE, DOMAIN as NOTIFY_DOMAIN +from homeassistant.components.notify import ( + ATTR_DATA, + ATTR_MESSAGE, + DOMAIN as NOTIFY_DOMAIN, +) from homeassistant.components.webostv import DOMAIN -from homeassistant.const import CONF_ICON, CONF_SERVICE_DATA +from homeassistant.const import ATTR_ICON from homeassistant.setup import async_setup_component from . import setup_webostv @@ -26,8 +30,8 @@ async def test_notify(hass, client): TV_NAME, { ATTR_MESSAGE: MESSAGE, - CONF_SERVICE_DATA: { - CONF_ICON: ICON_PATH, + ATTR_DATA: { + ATTR_ICON: ICON_PATH, }, }, blocking=True, @@ -41,7 +45,7 @@ async def test_notify(hass, client): TV_NAME, { ATTR_MESSAGE: MESSAGE, - CONF_SERVICE_DATA: { + ATTR_DATA: { "OTHER_DATA": "not_used", }, }, @@ -51,6 +55,20 @@ async def test_notify(hass, client): assert client.connect.call_count == 1 client.send_message.assert_called_with(MESSAGE, icon_path=None) + await hass.services.async_call( + NOTIFY_DOMAIN, + TV_NAME, + { + ATTR_MESSAGE: "only message, no data", + }, + blocking=True, + ) + + assert client.connect.call_count == 1 + assert client.send_message.call_args == call( + "only message, no data", icon_path=None + ) + async def test_notify_not_connected(hass, client, monkeypatch): """Test sending a message when client is not connected.""" @@ -63,8 +81,8 @@ async def test_notify_not_connected(hass, client, monkeypatch): TV_NAME, { ATTR_MESSAGE: MESSAGE, - CONF_SERVICE_DATA: { - CONF_ICON: ICON_PATH, + ATTR_DATA: { + ATTR_ICON: ICON_PATH, }, }, blocking=True, @@ -85,8 +103,8 @@ async def test_icon_not_found(hass, caplog, client, monkeypatch): TV_NAME, { ATTR_MESSAGE: MESSAGE, - CONF_SERVICE_DATA: { - CONF_ICON: ICON_PATH, + ATTR_DATA: { + ATTR_ICON: ICON_PATH, }, }, blocking=True, @@ -116,8 +134,8 @@ async def test_connection_errors(hass, caplog, client, monkeypatch, side_effect, TV_NAME, { ATTR_MESSAGE: MESSAGE, - CONF_SERVICE_DATA: { - CONF_ICON: ICON_PATH, + ATTR_DATA: { + ATTR_ICON: ICON_PATH, }, }, blocking=True, From 9389d1e5611f6e4607b44b881a45140b8040ab04 Mon Sep 17 00:00:00 2001 From: Sascha Sander Date: Fri, 18 Feb 2022 15:00:49 +0100 Subject: [PATCH 0787/1098] Correct current temperature for tuya thermostats (#66715) Co-authored-by: Franck Nijhof --- homeassistant/components/tuya/climate.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homeassistant/components/tuya/climate.py b/homeassistant/components/tuya/climate.py index b4def6fb3797e0..b70f81bc4d584a 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -365,6 +365,13 @@ def current_temperature(self) -> float | None: if temperature is None: return None + if self._current_temperature.scale == 0 and self._current_temperature.step != 1: + # The current temperature can have a scale of 0 or 1 and is used for + # rounding, Home Assistant doesn't need to round but we will always + # need to divide the value by 10^1 in case of 0 as scale. + # https://developer.tuya.com/en/docs/iot/shift-temperature-scale-follow-the-setting-of-app-account-center?id=Ka9qo7so58efq#title-7-Round%20values + temperature = temperature / 10 + return self._current_temperature.scale_value(temperature) @property From f1648960f5a4d6cdcec6c969c3ecfcec38964664 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 18 Feb 2022 15:05:14 +0100 Subject: [PATCH 0788/1098] Improve cleanup of Google Cast entities (#66801) --- homeassistant/components/cast/media_player.py | 42 +++++++++---------- tests/components/cast/test_media_player.py | 41 +++++++++++++++++- 2 files changed, 59 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 74ca65ade06d5c..a3b4dea8424e45 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -212,6 +212,10 @@ async def async_added_to_hass(self): async def async_will_remove_from_hass(self) -> None: """Disconnect Chromecast object when removed.""" await self._async_disconnect() + if self._cast_info.uuid is not None: + # Remove the entity from the added casts so that it can dynamically + # be re-added again. + self.hass.data[ADDED_CAST_DEVICES_KEY].remove(self._cast_info.uuid) if self._add_remove_handler: self._add_remove_handler() self._add_remove_handler = None @@ -253,21 +257,16 @@ async def async_connect_to_chromecast(self): async def _async_disconnect(self): """Disconnect Chromecast object if it is set.""" - if self._chromecast is None: - # Can't disconnect if not connected. - return - _LOGGER.debug( - "[%s %s] Disconnecting from chromecast socket", - self.entity_id, - self._cast_info.friendly_name, - ) - self._attr_available = False - self.async_write_ha_state() - - await self.hass.async_add_executor_job(self._chromecast.disconnect) + if self._chromecast is not None: + _LOGGER.debug( + "[%s %s] Disconnecting from chromecast socket", + self.entity_id, + self._cast_info.friendly_name, + ) + await self.hass.async_add_executor_job(self._chromecast.disconnect) + self._attr_available = False self._invalidate() - self.async_write_ha_state() def _invalidate(self): @@ -904,16 +903,13 @@ async def async_connect_to_chromecast(self): async def _async_disconnect(self): """Disconnect Chromecast object if it is set.""" - if self._chromecast is None: - # Can't disconnect if not connected. - return - _LOGGER.debug( - "[%s %s] Disconnecting from chromecast socket", - "Dynamic group", - self._cast_info.friendly_name, - ) - - await self.hass.async_add_executor_job(self._chromecast.disconnect) + if self._chromecast is not None: + _LOGGER.debug( + "[%s %s] Disconnecting from chromecast socket", + "Dynamic group", + self._cast_info.friendly_name, + ) + await self.hass.async_add_executor_job(self._chromecast.disconnect) self._invalidate() diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index cf72cf38827477..1c2da93f0a175c 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -38,7 +38,7 @@ EVENT_HOMEASSISTANT_STOP, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er, network +from homeassistant.helpers import device_registry as dr, entity_registry as er, network from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.setup import async_setup_component @@ -606,6 +606,45 @@ async def test_entity_availability(hass: HomeAssistant): assert state.state == "unavailable" +@pytest.mark.parametrize("port,entry_type", ((8009, None),)) +async def test_device_registry(hass: HomeAssistant, port, entry_type): + """Test device registry integration.""" + entity_id = "media_player.speaker" + reg = er.async_get(hass) + dev_reg = dr.async_get(hass) + + info = get_fake_chromecast_info(port=port) + + chromecast, _ = await async_setup_media_player_cast(hass, info) + chromecast.cast_type = pychromecast.const.CAST_TYPE_CHROMECAST + _, conn_status_cb, _ = get_status_callbacks(chromecast) + cast_entry = hass.config_entries.async_entries("cast")[0] + + connection_status = MagicMock() + connection_status.status = "CONNECTED" + conn_status_cb(connection_status) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state is not None + assert state.name == "Speaker" + assert state.state == "off" + assert entity_id == reg.async_get_entity_id("media_player", "cast", str(info.uuid)) + entity_entry = reg.async_get(entity_id) + assert entity_entry.device_id is not None + device_entry = dev_reg.async_get(entity_entry.device_id) + assert device_entry.entry_type == entry_type + + # Check that the chromecast object is torn down when the device is removed + chromecast.disconnect.assert_not_called() + dev_reg.async_update_device( + device_entry.id, remove_config_entry_id=cast_entry.entry_id + ) + await hass.async_block_till_done() + await hass.async_block_till_done() + chromecast.disconnect.assert_called_once() + + async def test_entity_cast_status(hass: HomeAssistant): """Test handling of cast status.""" entity_id = "media_player.speaker" From faf854190db91379bd2f1a2458672aceb4833012 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 18 Feb 2022 16:25:28 +0100 Subject: [PATCH 0789/1098] Add support for removing Google Cast devices (#66808) --- homeassistant/components/cast/__init__.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/cast/__init__.py b/homeassistant/components/cast/__init__.py index 86e5557160cb4f..d0b7cfc4158500 100644 --- a/homeassistant/components/cast/__init__.py +++ b/homeassistant/components/cast/__init__.py @@ -12,7 +12,7 @@ from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.integration_platform import ( async_process_integration_platforms, ) @@ -113,3 +113,13 @@ async def _register_cast_platform( async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: """Remove Home Assistant Cast user.""" await home_assistant_cast.async_remove_user(hass, entry) + + +async def async_remove_config_entry_device( + hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry +) -> bool: + """Remove cast config entry from a device. + + The actual cleanup is done in CastMediaPlayerEntity.async_will_remove_from_hass. + """ + return True From fcf774ecfcb8e3c049d8f213633ce0361789c00d Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 18 Feb 2022 16:51:14 +0100 Subject: [PATCH 0790/1098] Small cleanup of MQTT mixins (#66812) --- homeassistant/components/mqtt/mixins.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 6f881a7069093d..ba67339a5d49ca 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -839,12 +839,11 @@ def async_removed_from_device( if "config_entries" not in event.data["changes"]: return False device_registry = dr.async_get(hass) - device_entry = device_registry.async_get(mqtt_device_id) + device_entry = device_registry.async_get(device_id) if not device_entry: # The device is already removed, do cleanup when we get "remove" event return False - entry_id = config_entry_id - if entry_id in device_entry.config_entries: + if config_entry_id in device_entry.config_entries: # Not removed from device return False From 30e24117614f7ecb832ec72ba9d8a258de949b8f Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 18 Feb 2022 18:15:57 +0100 Subject: [PATCH 0791/1098] Add type ignore error codes [last ones] (#66816) --- homeassistant/components/automation/logbook.py | 4 ++-- homeassistant/components/sensor/__init__.py | 7 +++++-- homeassistant/helpers/collection.py | 4 ++-- homeassistant/helpers/entity.py | 5 +++-- homeassistant/helpers/entity_platform.py | 4 ++-- homeassistant/helpers/event.py | 8 ++++---- homeassistant/helpers/state.py | 4 ++-- homeassistant/helpers/sun.py | 2 +- homeassistant/helpers/trigger.py | 4 ++-- homeassistant/setup.py | 4 ++-- homeassistant/util/logging.py | 4 ++-- 11 files changed, 27 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/automation/logbook.py b/homeassistant/components/automation/logbook.py index 901972595e4ddf..86fb797ea311bd 100644 --- a/homeassistant/components/automation/logbook.py +++ b/homeassistant/components/automation/logbook.py @@ -8,11 +8,11 @@ @callback -def async_describe_events(hass: HomeAssistant, async_describe_event): # type: ignore +def async_describe_events(hass: HomeAssistant, async_describe_event): # type: ignore[no-untyped-def] """Describe logbook events.""" @callback - def async_describe_logbook_event(event: LazyEventPartialState): # type: ignore + def async_describe_logbook_event(event: LazyEventPartialState): # type: ignore[no-untyped-def] """Describe a logbook event.""" data = event.data message = "has been triggered" diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index f374ebaeb38126..3414f13268ff67 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -397,7 +397,10 @@ def state(self) -> Any: # Received a date value if value is not None and device_class == DEVICE_CLASS_DATE: try: - return value.isoformat() # type: ignore + # We cast the value, to avoid using isinstance, but satisfy + # typechecking. The errors are guarded in this try. + value = cast(date, value) + return value.isoformat() except (AttributeError, TypeError) as err: raise ValueError( f"Invalid date: {self.entity_id} has a date device class " @@ -434,7 +437,7 @@ def state(self) -> Any: prec = len(value_s) - value_s.index(".") - 1 if "." in value_s else 0 # Suppress ValueError (Could not convert sensor_value to float) with suppress(ValueError): - temp = units.temperature(float(value), unit_of_measurement) # type: ignore + temp = units.temperature(float(value), unit_of_measurement) # type: ignore[arg-type] value = round(temp) if prec == 0 else round(temp, prec) return value diff --git a/homeassistant/helpers/collection.py b/homeassistant/helpers/collection.py index f6f9c968f104b2..9017c60c23fabb 100644 --- a/homeassistant/helpers/collection.py +++ b/homeassistant/helpers/collection.py @@ -330,7 +330,7 @@ def sync_entity_lifecycle( create_entity: Callable[[dict], Entity], ) -> None: """Map a collection to an entity component.""" - entities = {} + entities: dict[str, Entity] = {} ent_reg = entity_registry.async_get(hass) async def _add_entity(change_set: CollectionChangeSet) -> Entity: @@ -348,7 +348,7 @@ async def _remove_entity(change_set: CollectionChangeSet) -> None: entities.pop(change_set.item_id) async def _update_entity(change_set: CollectionChangeSet) -> None: - await entities[change_set.item_id].async_update_config(change_set.item) # type: ignore + await entities[change_set.item_id].async_update_config(change_set.item) # type: ignore[attr-defined] _func_map: dict[ str, Callable[[CollectionChangeSet], Coroutine[Any, Any, Entity | None]] diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index bb600556991bd1..8e4f6bc8b58856 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -707,10 +707,11 @@ async def async_device_update(self, warning: bool = True) -> None: await self.parallel_updates.acquire() try: + task: asyncio.Future[None] if hasattr(self, "async_update"): - task = self.hass.async_create_task(self.async_update()) # type: ignore + task = self.hass.async_create_task(self.async_update()) # type: ignore[attr-defined] elif hasattr(self, "update"): - task = self.hass.async_add_executor_job(self.update) # type: ignore + task = self.hass.async_add_executor_job(self.update) # type: ignore[attr-defined] else: return diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 799b209f16e3c5..cc252f82782171 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -172,7 +172,7 @@ async def async_setup( def async_create_setup_task() -> Coroutine: """Get task to set up platform.""" if getattr(platform, "async_setup_platform", None): - return platform.async_setup_platform( # type: ignore + return platform.async_setup_platform( # type: ignore[no-any-return,union-attr] hass, platform_config, self._async_schedule_add_entities, @@ -183,7 +183,7 @@ def async_create_setup_task() -> Coroutine: # we don't want to track this task in case it blocks startup. return hass.loop.run_in_executor( # type: ignore[return-value] None, - platform.setup_platform, # type: ignore + platform.setup_platform, # type: ignore[union-attr] hass, platform_config, self._schedule_add_entities, diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index cbafd2e7e959cf..71b2cf1a585722 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -1328,8 +1328,8 @@ def async_track_time_interval( interval: timedelta, ) -> CALLBACK_TYPE: """Add a listener that fires repetitively at every timedelta interval.""" - remove = None - interval_listener_job = None + remove: CALLBACK_TYPE + interval_listener_job: HassJob[None] job = HassJob(action) @@ -1344,7 +1344,7 @@ def interval_listener(now: datetime) -> None: nonlocal interval_listener_job remove = async_track_point_in_utc_time( - hass, interval_listener_job, next_interval() # type: ignore + hass, interval_listener_job, next_interval() ) hass.async_run_hass_job(job, now) @@ -1353,7 +1353,7 @@ def interval_listener(now: datetime) -> None: def remove_listener() -> None: """Remove interval listener.""" - remove() # type: ignore + remove() return remove_listener diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index 38647792b7a0ca..75ca96ea2468f4 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -102,12 +102,12 @@ async def worker(domain: str, states_by_domain: list[State]) -> None: return try: - platform: ModuleType | None = integration.get_platform("reproduce_state") + platform: ModuleType = integration.get_platform("reproduce_state") except ImportError: _LOGGER.warning("Integration %s does not support reproduce state", domain) return - await platform.async_reproduce_states( # type: ignore + await platform.async_reproduce_states( hass, states_by_domain, context=context, reproduce_options=reproduce_options ) diff --git a/homeassistant/helpers/sun.py b/homeassistant/helpers/sun.py index 3c18dcc32784a5..09a329cd2752ba 100644 --- a/homeassistant/helpers/sun.py +++ b/homeassistant/helpers/sun.py @@ -116,7 +116,7 @@ def get_astral_event_date( kwargs["observer_elevation"] = elevation try: - return getattr(location, event)(date, **kwargs) # type: ignore + return getattr(location, event)(date, **kwargs) # type: ignore[no-any-return] except ValueError: # Event never occurs for specified date. return None diff --git a/homeassistant/helpers/trigger.py b/homeassistant/helpers/trigger.py index 175a29fcc5da1f..0b18ad9aa421d4 100644 --- a/homeassistant/helpers/trigger.py +++ b/homeassistant/helpers/trigger.py @@ -83,7 +83,7 @@ async def async_initialize_triggers( triggers.append(platform.async_attach_trigger(hass, conf, action, info)) attach_results = await asyncio.gather(*triggers, return_exceptions=True) - removes = [] + removes: list[Callable[[], None]] = [] for result in attach_results: if isinstance(result, HomeAssistantError): @@ -103,7 +103,7 @@ async def async_initialize_triggers( log_cb(logging.INFO, "Initialized trigger") @callback - def remove_triggers(): # type: ignore + def remove_triggers() -> None: """Remove triggers.""" for remove in removes: remove() diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 7b2f963102e528..36292989dce9b2 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -66,10 +66,10 @@ async def async_setup_component( if domain in hass.config.components: return True - setup_tasks = hass.data.setdefault(DATA_SETUP, {}) + setup_tasks: dict[str, asyncio.Task[bool]] = hass.data.setdefault(DATA_SETUP, {}) if domain in setup_tasks: - return await setup_tasks[domain] # type: ignore + return await setup_tasks[domain] task = setup_tasks[domain] = hass.async_create_task( _async_setup_component(hass, domain, config) diff --git a/homeassistant/util/logging.py b/homeassistant/util/logging.py index 8dba52ebe9dd16..d09feec52379ba 100644 --- a/homeassistant/util/logging.py +++ b/homeassistant/util/logging.py @@ -69,11 +69,11 @@ def async_activate_log_queue_handler(hass: HomeAssistant) -> None: This allows us to avoid blocking I/O and formatting messages in the event loop as log messages are written in another thread. """ - simple_queue = queue.SimpleQueue() # type: ignore + simple_queue: queue.SimpleQueue[logging.Handler] = queue.SimpleQueue() queue_handler = HomeAssistantQueueHandler(simple_queue) logging.root.addHandler(queue_handler) - migrated_handlers = [] + migrated_handlers: list[logging.Handler] = [] for handler in logging.root.handlers[:]: if handler is queue_handler: continue From 2d2101528c7b211c3d742ade4207c8ae0926b7cb Mon Sep 17 00:00:00 2001 From: Jeef Date: Fri, 18 Feb 2022 10:31:23 -0700 Subject: [PATCH 0792/1098] Intellifire Diagnostic Sensors (#66597) --- .../components/intellifire/sensor.py | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/intellifire/sensor.py b/homeassistant/components/intellifire/sensor.py index 7c52581498b78f..b61ea4437287bc 100644 --- a/homeassistant/components/intellifire/sensor.py +++ b/homeassistant/components/intellifire/sensor.py @@ -16,11 +16,12 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import TEMP_CELSIUS from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util.dt import utcnow -from . import IntellifireDataUpdateCoordinator from .const import DOMAIN +from .coordinator import IntellifireDataUpdateCoordinator from .entity import IntellifireEntity @@ -46,6 +47,13 @@ def _time_remaining_to_timestamp(data: IntellifirePollData) -> datetime | None: return utcnow() + timedelta(seconds=seconds_offset) +def _downtime_to_timestamp(data: IntellifirePollData) -> datetime | None: + """Define a sensor that takes into account a timezone.""" + if not (seconds_offset := data.downtime): + return None + return utcnow() - timedelta(seconds=seconds_offset) + + INTELLIFIRE_SENSORS: tuple[IntellifireSensorEntityDescription, ...] = ( IntellifireSensorEntityDescription( key="flame_height", @@ -85,6 +93,40 @@ def _time_remaining_to_timestamp(data: IntellifirePollData) -> datetime | None: device_class=SensorDeviceClass.TIMESTAMP, value_fn=_time_remaining_to_timestamp, ), + IntellifireSensorEntityDescription( + key="downtime", + name="Downtime", + entity_category=EntityCategory.DIAGNOSTIC, + device_class=SensorDeviceClass.TIMESTAMP, + value_fn=_downtime_to_timestamp, + ), + IntellifireSensorEntityDescription( + key="uptime", + name="Uptime", + entity_category=EntityCategory.DIAGNOSTIC, + device_class=SensorDeviceClass.TIMESTAMP, + value_fn=lambda data: utcnow() - timedelta(seconds=data.uptime), + ), + IntellifireSensorEntityDescription( + key="connection_quality", + name="Connection Quality", + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda data: data.connection_quality, + entity_registry_enabled_default=False, + ), + IntellifireSensorEntityDescription( + key="ecm_latency", + name="ECM Latency", + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda data: data.ecm_latency, + entity_registry_enabled_default=False, + ), + IntellifireSensorEntityDescription( + key="ipv4_address", + name="IP", + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda data: data.ipv4_address, + ), ) From 011c645ee27a3c9528554577bbb6b797ca9c3d52 Mon Sep 17 00:00:00 2001 From: Jonathan Keljo Date: Fri, 18 Feb 2022 10:03:03 -0800 Subject: [PATCH 0793/1098] Silence sisyphus chatty logging from dependencies (#66711) --- homeassistant/components/sisyphus/__init__.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/sisyphus/__init__.py b/homeassistant/components/sisyphus/__init__.py index cda8f0da1f2350..721c51ca9644cb 100644 --- a/homeassistant/components/sisyphus/__init__.py +++ b/homeassistant/components/sisyphus/__init__.py @@ -29,19 +29,15 @@ {DOMAIN: vol.Any(AUTODETECT_SCHEMA, TABLES_SCHEMA)}, extra=vol.ALLOW_EXTRA ) +# Silence these loggers by default. Their INFO level is super chatty and we +# only need error-level logging from the integration itself by default. +logging.getLogger("socketio.client").setLevel(logging.CRITICAL + 1) +logging.getLogger("engineio.client").setLevel(logging.CRITICAL + 1) + async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the sisyphus component.""" - class SocketIONoiseFilter(logging.Filter): - """Filters out excessively verbose logs from SocketIO.""" - - def filter(self, record): - if "waiting for connection" in record.msg: - return False - return True - - logging.getLogger("socketIO-client").addFilter(SocketIONoiseFilter()) tables = hass.data.setdefault(DATA_SISYPHUS, {}) table_configs = config[DOMAIN] session = async_get_clientsession(hass) From beb30a1ff199596163c655e8ae745a0f1649b78a Mon Sep 17 00:00:00 2001 From: Kevin Stillhammer Date: Fri, 18 Feb 2022 19:21:28 +0100 Subject: [PATCH 0794/1098] Add google_travel_time sensor tests (#66568) Co-authored-by: Paulus Schoutsen --- .coveragerc | 3 - tests/components/google_travel_time/const.py | 14 ++ .../google_travel_time/test_config_flow.py | 25 +- .../google_travel_time/test_sensor.py | 234 ++++++++++++++++++ 4 files changed, 253 insertions(+), 23 deletions(-) create mode 100644 tests/components/google_travel_time/const.py create mode 100644 tests/components/google_travel_time/test_sensor.py diff --git a/.coveragerc b/.coveragerc index 8783dd64ab89b2..90437cac34e7f8 100644 --- a/.coveragerc +++ b/.coveragerc @@ -433,9 +433,6 @@ omit = homeassistant/components/google_cloud/tts.py homeassistant/components/google_maps/device_tracker.py homeassistant/components/google_pubsub/__init__.py - homeassistant/components/google_travel_time/__init__.py - homeassistant/components/google_travel_time/helpers.py - homeassistant/components/google_travel_time/sensor.py homeassistant/components/gpmdp/media_player.py homeassistant/components/gpsd/sensor.py homeassistant/components/greenwave/light.py diff --git a/tests/components/google_travel_time/const.py b/tests/components/google_travel_time/const.py new file mode 100644 index 00000000000000..844766ceffaa0f --- /dev/null +++ b/tests/components/google_travel_time/const.py @@ -0,0 +1,14 @@ +"""Constants for google_travel_time tests.""" + + +from homeassistant.components.google_travel_time.const import ( + CONF_DESTINATION, + CONF_ORIGIN, +) +from homeassistant.const import CONF_API_KEY + +MOCK_CONFIG = { + CONF_API_KEY: "api_key", + CONF_ORIGIN: "location1", + CONF_DESTINATION: "location2", +} diff --git a/tests/components/google_travel_time/test_config_flow.py b/tests/components/google_travel_time/test_config_flow.py index 9b615afbbe134b..d81d63e3af11ea 100644 --- a/tests/components/google_travel_time/test_config_flow.py +++ b/tests/components/google_travel_time/test_config_flow.py @@ -26,6 +26,7 @@ ) from tests.common import MockConfigEntry +from tests.components.google_travel_time.const import MOCK_CONFIG async def test_minimum_fields(hass, validate_config_entry, bypass_setup): @@ -38,11 +39,7 @@ async def test_minimum_fields(hass, validate_config_entry, bypass_setup): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - { - CONF_API_KEY: "api_key", - CONF_ORIGIN: "location1", - CONF_DESTINATION: "location2", - }, + MOCK_CONFIG, ) assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY @@ -64,11 +61,7 @@ async def test_invalid_config_entry(hass, invalidate_config_entry): assert result["errors"] == {} result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - { - CONF_API_KEY: "api_key", - CONF_ORIGIN: "location1", - CONF_DESTINATION: "location2", - }, + MOCK_CONFIG, ) assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM @@ -79,11 +72,7 @@ async def test_options_flow(hass, validate_config_entry, bypass_update): """Test options flow.""" entry = MockConfigEntry( domain=DOMAIN, - data={ - CONF_API_KEY: "api_key", - CONF_ORIGIN: "location1", - CONF_DESTINATION: "location2", - }, + data=MOCK_CONFIG, options={ CONF_MODE: "driving", CONF_ARRIVAL_TIME: "test", @@ -142,11 +131,7 @@ async def test_options_flow_departure_time(hass, validate_config_entry, bypass_u """Test options flow wiith departure time.""" entry = MockConfigEntry( domain=DOMAIN, - data={ - CONF_API_KEY: "api_key", - CONF_ORIGIN: "location1", - CONF_DESTINATION: "location2", - }, + data=MOCK_CONFIG, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) diff --git a/tests/components/google_travel_time/test_sensor.py b/tests/components/google_travel_time/test_sensor.py new file mode 100644 index 00000000000000..6b203f51e98709 --- /dev/null +++ b/tests/components/google_travel_time/test_sensor.py @@ -0,0 +1,234 @@ +"""Test the Google Maps Travel Time sensors.""" + +from unittest.mock import patch + +import pytest + +from homeassistant.components.google_travel_time.const import ( + CONF_ARRIVAL_TIME, + CONF_DEPARTURE_TIME, + CONF_TRAVEL_MODE, + DOMAIN, +) + +from .const import MOCK_CONFIG + +from tests.common import MockConfigEntry + + +@pytest.fixture(name="mock_config") +async def mock_config_fixture(hass, data, options): + """Mock a Google Travel Time config entry.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data=data, + options=options, + entry_id="test", + ) + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + +@pytest.fixture(name="mock_update") +def mock_update_fixture(): + """Mock an update to the sensor.""" + with patch("homeassistant.components.google_travel_time.sensor.Client"), patch( + "homeassistant.components.google_travel_time.sensor.distance_matrix" + ) as distance_matrix_mock: + distance_matrix_mock.return_value = { + "rows": [ + { + "elements": [ + { + "duration_in_traffic": { + "value": 1620, + "text": "27 mins", + }, + "duration": { + "value": 1560, + "text": "26 mins", + }, + "distance": {"text": "21.3 km"}, + } + ] + } + ] + } + yield distance_matrix_mock + + +@pytest.fixture(name="mock_update_duration") +def mock_update_duration_fixture(mock_update): + """Mock an update to the sensor returning no duration_in_traffic.""" + mock_update.return_value = { + "rows": [ + { + "elements": [ + { + "duration": { + "value": 1560, + "text": "26 mins", + }, + "distance": {"text": "21.3 km"}, + } + ] + } + ] + } + yield mock_update + + +@pytest.fixture(name="mock_update_empty") +def mock_update_empty_fixture(mock_update): + """Mock an update to the sensor with an empty response.""" + mock_update.return_value = None + yield mock_update + + +@pytest.mark.parametrize( + "data,options", + [(MOCK_CONFIG, {})], +) +@pytest.mark.usefixtures("mock_update", "mock_config") +async def test_sensor(hass): + """Test that sensor works.""" + assert hass.states.get("sensor.google_travel_time").state == "27" + assert ( + hass.states.get("sensor.google_travel_time").attributes["attribution"] + == "Powered by Google" + ) + assert ( + hass.states.get("sensor.google_travel_time").attributes["duration"] == "26 mins" + ) + assert ( + hass.states.get("sensor.google_travel_time").attributes["duration_in_traffic"] + == "27 mins" + ) + assert ( + hass.states.get("sensor.google_travel_time").attributes["distance"] == "21.3 km" + ) + assert ( + hass.states.get("sensor.google_travel_time").attributes["origin"] == "location1" + ) + assert ( + hass.states.get("sensor.google_travel_time").attributes["destination"] + == "location2" + ) + assert ( + hass.states.get("sensor.google_travel_time").attributes["unit_of_measurement"] + == "min" + ) + + +@pytest.mark.parametrize( + "data,options", + [(MOCK_CONFIG, {})], +) +@pytest.mark.usefixtures("mock_update_duration", "mock_config") +async def test_sensor_duration(hass): + """Test that sensor works with no duration_in_traffic in response.""" + assert hass.states.get("sensor.google_travel_time").state == "26" + + +@pytest.mark.parametrize( + "data,options", + [(MOCK_CONFIG, {})], +) +@pytest.mark.usefixtures("mock_update_empty", "mock_config") +async def test_sensor_empty_response(hass): + """Test that sensor works for an empty response.""" + assert hass.states.get("sensor.google_travel_time").state == "unknown" + + +@pytest.mark.parametrize( + "data,options", + [ + ( + MOCK_CONFIG, + { + CONF_DEPARTURE_TIME: "10:00", + }, + ), + ], +) +@pytest.mark.usefixtures("mock_update", "mock_config") +async def test_sensor_departure_time(hass): + """Test that sensor works for departure time.""" + assert hass.states.get("sensor.google_travel_time").state == "27" + + +@pytest.mark.parametrize( + "data,options", + [ + ( + MOCK_CONFIG, + { + CONF_DEPARTURE_TIME: "custom_timestamp", + }, + ), + ], +) +@pytest.mark.usefixtures("mock_update", "mock_config") +async def test_sensor_departure_time_custom_timestamp(hass): + """Test that sensor works for departure time with a custom timestamp.""" + assert hass.states.get("sensor.google_travel_time").state == "27" + + +@pytest.mark.parametrize( + "data,options", + [ + ( + MOCK_CONFIG, + { + CONF_ARRIVAL_TIME: "10:00", + }, + ), + ], +) +@pytest.mark.usefixtures("mock_update", "mock_config") +async def test_sensor_arrival_time(hass): + """Test that sensor works for arrival time.""" + assert hass.states.get("sensor.google_travel_time").state == "27" + + +@pytest.mark.parametrize( + "data,options", + [ + ( + MOCK_CONFIG, + { + CONF_ARRIVAL_TIME: "custom_timestamp", + }, + ), + ], +) +@pytest.mark.usefixtures("mock_update", "mock_config") +async def test_sensor_arrival_time_custom_timestamp(hass): + """Test that sensor works for arrival time with a custom timestamp.""" + assert hass.states.get("sensor.google_travel_time").state == "27" + + +@pytest.mark.usefixtures("mock_update") +async def test_sensor_deprecation_warning(hass, caplog): + """Test that sensor setup prints a deprecating warning for old configs. + + The mock_config fixture does not work with caplog. + """ + data = MOCK_CONFIG.copy() + data[CONF_TRAVEL_MODE] = "driving" + config_entry = MockConfigEntry( + domain=DOMAIN, + data=data, + entry_id="test", + ) + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert hass.states.get("sensor.google_travel_time").state == "27" + wstr = ( + "Google Travel Time: travel_mode is deprecated, please " + "add mode to the options dictionary instead!" + ) + assert wstr in caplog.text From a367d2be40c3ad4b963770901e2257a006c4e63a Mon Sep 17 00:00:00 2001 From: Keilin Bickar Date: Fri, 18 Feb 2022 13:50:44 -0500 Subject: [PATCH 0795/1098] Modernize Sleepiq and add new entities (#66336) Co-authored-by: J. Nick Koston --- CODEOWNERS | 4 +- homeassistant/components/sleepiq/__init__.py | 45 ++++++-- .../components/sleepiq/binary_sensor.py | 27 ++--- .../components/sleepiq/config_flow.py | 34 +++--- homeassistant/components/sleepiq/const.py | 3 + .../components/sleepiq/coordinator.py | 22 ++-- homeassistant/components/sleepiq/entity.py | 32 +++--- .../components/sleepiq/manifest.json | 10 +- homeassistant/components/sleepiq/sensor.py | 35 +++--- requirements_all.txt | 6 +- requirements_test_all.txt | 6 +- tests/components/sleepiq/conftest.py | 108 ++++++++---------- .../sleepiq/fixtures/bed-single.json | 27 ----- tests/components/sleepiq/fixtures/bed.json | 27 ----- .../sleepiq/fixtures/familystatus-single.json | 17 --- .../sleepiq/fixtures/familystatus.json | 24 ---- tests/components/sleepiq/fixtures/login.json | 7 -- .../components/sleepiq/fixtures/sleeper.json | 54 --------- .../components/sleepiq/test_binary_sensor.py | 69 +++++++---- tests/components/sleepiq/test_config_flow.py | 43 ++++--- tests/components/sleepiq/test_init.py | 76 ++++++------ tests/components/sleepiq/test_sensor.py | 46 +++++--- 22 files changed, 326 insertions(+), 396 deletions(-) delete mode 100644 tests/components/sleepiq/fixtures/bed-single.json delete mode 100644 tests/components/sleepiq/fixtures/bed.json delete mode 100644 tests/components/sleepiq/fixtures/familystatus-single.json delete mode 100644 tests/components/sleepiq/fixtures/familystatus.json delete mode 100644 tests/components/sleepiq/fixtures/login.json delete mode 100644 tests/components/sleepiq/fixtures/sleeper.json diff --git a/CODEOWNERS b/CODEOWNERS index e27c9488f0af13..dabc32c0e11109 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -850,8 +850,8 @@ homeassistant/components/sisyphus/* @jkeljo homeassistant/components/sky_hub/* @rogerselwyn homeassistant/components/slack/* @bachya tests/components/slack/* @bachya -homeassistant/components/sleepiq/* @mfugate1 -tests/components/sleepiq/* @mfugate1 +homeassistant/components/sleepiq/* @mfugate1 @kbickar +tests/components/sleepiq/* @mfugate1 @kbickar homeassistant/components/slide/* @ualex73 homeassistant/components/sma/* @kellerza @rklomp tests/components/sma/* @kellerza @rklomp diff --git a/homeassistant/components/sleepiq/__init__.py b/homeassistant/components/sleepiq/__init__.py index 5a69cfacd1158a..2fc0c52c706744 100644 --- a/homeassistant/components/sleepiq/__init__.py +++ b/homeassistant/components/sleepiq/__init__.py @@ -1,12 +1,19 @@ """Support for SleepIQ from SleepNumber.""" import logging -from sleepyq import Sleepyq +from asyncsleepiq import ( + AsyncSleepIQ, + SleepIQAPIException, + SleepIQLoginException, + SleepIQTimeoutException, +) import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType @@ -15,6 +22,8 @@ _LOGGER = logging.getLogger(__name__) +PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] + CONFIG_SCHEMA = vol.Schema( { DOMAIN: { @@ -43,18 +52,34 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up the SleepIQ config entry.""" - client = Sleepyq(entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD]) + conf = entry.data + email = conf[CONF_USERNAME] + password = conf[CONF_PASSWORD] + + client_session = async_get_clientsession(hass) + + gateway = AsyncSleepIQ(client_session=client_session) + try: - await hass.async_add_executor_job(client.login) - except ValueError: - _LOGGER.error("SleepIQ login failed, double check your username and password") + await gateway.login(email, password) + except SleepIQLoginException: + _LOGGER.error("Could not authenticate with SleepIQ server") return False + except SleepIQTimeoutException as err: + raise ConfigEntryNotReady( + str(err) or "Timed out during authentication" + ) from err - coordinator = SleepIQDataUpdateCoordinator( - hass, - client=client, - username=entry.data[CONF_USERNAME], - ) + try: + await gateway.init_beds() + except SleepIQTimeoutException as err: + raise ConfigEntryNotReady( + str(err) or "Timed out during initialization" + ) from err + except SleepIQAPIException as err: + raise ConfigEntryNotReady(str(err) or "Error reading from SleepIQ API") from err + + coordinator = SleepIQDataUpdateCoordinator(hass, gateway, email) # Call the SleepIQ API to refresh data await coordinator.async_config_entry_first_refresh() diff --git a/homeassistant/components/sleepiq/binary_sensor.py b/homeassistant/components/sleepiq/binary_sensor.py index 890cd6711a6b14..d2aeae06e8a25b 100644 --- a/homeassistant/components/sleepiq/binary_sensor.py +++ b/homeassistant/components/sleepiq/binary_sensor.py @@ -1,4 +1,6 @@ """Support for SleepIQ sensors.""" +from asyncsleepiq import SleepIQBed, SleepIQSleeper + from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, @@ -6,8 +8,9 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from .const import BED, DOMAIN, ICON_EMPTY, ICON_OCCUPIED, IS_IN_BED, SIDES +from .const import DOMAIN, ICON_EMPTY, ICON_OCCUPIED, IS_IN_BED from .coordinator import SleepIQDataUpdateCoordinator from .entity import SleepIQSensor @@ -20,10 +23,9 @@ async def async_setup_entry( """Set up the SleepIQ bed binary sensors.""" coordinator: SleepIQDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] async_add_entities( - IsInBedBinarySensor(coordinator, bed_id, side) - for side in SIDES - for bed_id in coordinator.data - if getattr(coordinator.data[bed_id][BED], side) is not None + IsInBedBinarySensor(coordinator, bed, sleeper) + for bed in coordinator.client.beds.values() + for sleeper in bed.sleepers ) @@ -34,16 +36,15 @@ class IsInBedBinarySensor(SleepIQSensor, BinarySensorEntity): def __init__( self, - coordinator: SleepIQDataUpdateCoordinator, - bed_id: str, - side: str, + coordinator: DataUpdateCoordinator, + bed: SleepIQBed, + sleeper: SleepIQSleeper, ) -> None: - """Initialize the SleepIQ bed side binary sensor.""" - super().__init__(coordinator, bed_id, side, IS_IN_BED) + """Initialize the sensor.""" + super().__init__(coordinator, bed, sleeper, IS_IN_BED) @callback def _async_update_attrs(self) -> None: """Update sensor attributes.""" - super()._async_update_attrs() - self._attr_is_on = getattr(self.side_data, IS_IN_BED) - self._attr_icon = ICON_OCCUPIED if self.is_on else ICON_EMPTY + self._attr_is_on = self.sleeper.in_bed + self._attr_icon = ICON_OCCUPIED if self.sleeper.in_bed else ICON_EMPTY diff --git a/homeassistant/components/sleepiq/config_flow.py b/homeassistant/components/sleepiq/config_flow.py index aff4d7e8dc7417..dffb30f39d7755 100644 --- a/homeassistant/components/sleepiq/config_flow.py +++ b/homeassistant/components/sleepiq/config_flow.py @@ -3,14 +3,16 @@ from typing import Any -from sleepyq import Sleepyq +from asyncsleepiq import AsyncSleepIQ, SleepIQLoginException, SleepIQTimeoutException import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers.aiohttp_client import async_get_clientsession -from .const import DOMAIN, SLEEPYQ_INVALID_CREDENTIALS_MESSAGE +from .const import DOMAIN class SleepIQFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @@ -41,19 +43,17 @@ async def async_step_user( await self.async_set_unique_id(user_input[CONF_USERNAME].lower()) self._abort_if_unique_id_configured() - login_error = await self.hass.async_add_executor_job( - try_connection, user_input - ) - if not login_error: + try: + await try_connection(self.hass, user_input) + except SleepIQLoginException: + errors["base"] = "invalid_auth" + except SleepIQTimeoutException: + errors["base"] = "cannot_connect" + else: return self.async_create_entry( title=user_input[CONF_USERNAME], data=user_input ) - if SLEEPYQ_INVALID_CREDENTIALS_MESSAGE in login_error: - errors["base"] = "invalid_auth" - else: - errors["base"] = "cannot_connect" - return self.async_show_form( step_id="user", data_schema=vol.Schema( @@ -72,14 +72,10 @@ async def async_step_user( ) -def try_connection(user_input: dict[str, Any]) -> str: +async def try_connection(hass: HomeAssistant, user_input: dict[str, Any]) -> None: """Test if the given credentials can successfully login to SleepIQ.""" - client = Sleepyq(user_input[CONF_USERNAME], user_input[CONF_PASSWORD]) - - try: - client.login() - except ValueError as error: - return str(error) + client_session = async_get_clientsession(hass) - return "" + gateway = AsyncSleepIQ(client_session=client_session) + await gateway.login(user_input[CONF_USERNAME], user_input[CONF_PASSWORD]) diff --git a/homeassistant/components/sleepiq/const.py b/homeassistant/components/sleepiq/const.py index 3fc0ae999fdc61..63e86270925238 100644 --- a/homeassistant/components/sleepiq/const.py +++ b/homeassistant/components/sleepiq/const.py @@ -14,3 +14,6 @@ LEFT = "left" RIGHT = "right" SIDES = [LEFT, RIGHT] + +SLEEPIQ_DATA = "sleepiq_data" +SLEEPIQ_STATUS_COORDINATOR = "sleepiq_status" diff --git a/homeassistant/components/sleepiq/coordinator.py b/homeassistant/components/sleepiq/coordinator.py index 467238e907ea03..ca664f99426c42 100644 --- a/homeassistant/components/sleepiq/coordinator.py +++ b/homeassistant/components/sleepiq/coordinator.py @@ -2,13 +2,11 @@ from datetime import timedelta import logging -from sleepyq import Sleepyq +from asyncsleepiq import AsyncSleepIQ from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from .const import BED - _LOGGER = logging.getLogger(__name__) UPDATE_INTERVAL = timedelta(seconds=60) @@ -20,21 +18,15 @@ class SleepIQDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict]]): def __init__( self, hass: HomeAssistant, - *, - client: Sleepyq, + client: AsyncSleepIQ, username: str, ) -> None: """Initialize coordinator.""" super().__init__( - hass, _LOGGER, name=f"{username}@SleepIQ", update_interval=UPDATE_INTERVAL + hass, + _LOGGER, + name=f"{username}@SleepIQ", + update_method=client.fetch_bed_statuses, + update_interval=UPDATE_INTERVAL, ) self.client = client - - async def _async_update_data(self) -> dict[str, dict]: - return await self.hass.async_add_executor_job(self.update_data) - - def update_data(self) -> dict[str, dict]: - """Get latest data from the client.""" - return { - bed.bed_id: {BED: bed} for bed in self.client.beds_with_sleeper_status() - } diff --git a/homeassistant/components/sleepiq/entity.py b/homeassistant/components/sleepiq/entity.py index 350435573f198e..424584720574c8 100644 --- a/homeassistant/components/sleepiq/entity.py +++ b/homeassistant/components/sleepiq/entity.py @@ -1,9 +1,15 @@ """Entity for the SleepIQ integration.""" +from abc import abstractmethod + +from asyncsleepiq import SleepIQBed, SleepIQSleeper + from homeassistant.core import callback -from homeassistant.helpers.update_coordinator import CoordinatorEntity +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, +) -from .const import BED, ICON_OCCUPIED, SENSOR_TYPES -from .coordinator import SleepIQDataUpdateCoordinator +from .const import ICON_OCCUPIED, SENSOR_TYPES class SleepIQSensor(CoordinatorEntity): @@ -13,22 +19,19 @@ class SleepIQSensor(CoordinatorEntity): def __init__( self, - coordinator: SleepIQDataUpdateCoordinator, - bed_id: str, - side: str, + coordinator: DataUpdateCoordinator, + bed: SleepIQBed, + sleeper: SleepIQSleeper, name: str, ) -> None: """Initialize the SleepIQ side entity.""" super().__init__(coordinator) - self.bed_id = bed_id - self.side = side - + self.bed = bed + self.sleeper = sleeper self._async_update_attrs() - self._attr_name = f"SleepNumber {self.bed_data.name} {self.side_data.sleeper.first_name} {SENSOR_TYPES[name]}" - self._attr_unique_id = ( - f"{self.bed_id}_{self.side_data.sleeper.first_name}_{name}" - ) + self._attr_name = f"SleepNumber {bed.name} {sleeper.name} {SENSOR_TYPES[name]}" + self._attr_unique_id = f"{bed.id}_{sleeper.name}_{name}" @callback def _handle_coordinator_update(self) -> None: @@ -37,7 +40,6 @@ def _handle_coordinator_update(self) -> None: super()._handle_coordinator_update() @callback + @abstractmethod def _async_update_attrs(self) -> None: """Update sensor attributes.""" - self.bed_data = self.coordinator.data[self.bed_id][BED] - self.side_data = getattr(self.bed_data, self.side) diff --git a/homeassistant/components/sleepiq/manifest.json b/homeassistant/components/sleepiq/manifest.json index a516bd7545b5c9..48ada7b14a2d6e 100644 --- a/homeassistant/components/sleepiq/manifest.json +++ b/homeassistant/components/sleepiq/manifest.json @@ -3,11 +3,13 @@ "name": "SleepIQ", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sleepiq", - "requirements": ["sleepyq==0.8.1"], - "codeowners": ["@mfugate1"], + "requirements": ["asyncsleepiq==1.0.0"], + "codeowners": ["@mfugate1", "@kbickar"], "dhcp": [ - {"macaddress": "64DBA0*"} + { + "macaddress": "64DBA0*" + } ], "iot_class": "cloud_polling", - "loggers": ["sleepyq"] + "loggers": ["asyncsleepiq"] } diff --git a/homeassistant/components/sleepiq/sensor.py b/homeassistant/components/sleepiq/sensor.py index 52ded76762dff7..dd7fdabcfb38bd 100644 --- a/homeassistant/components/sleepiq/sensor.py +++ b/homeassistant/components/sleepiq/sensor.py @@ -1,10 +1,15 @@ -"""Support for SleepIQ sensors.""" +"""Support for SleepIQ Sensor.""" +from __future__ import annotations + +from asyncsleepiq import SleepIQBed, SleepIQSleeper + from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from .const import BED, DOMAIN, SIDES, SLEEP_NUMBER +from .const import DOMAIN, SLEEP_NUMBER from .coordinator import SleepIQDataUpdateCoordinator from .entity import SleepIQSensor @@ -17,27 +22,27 @@ async def async_setup_entry( """Set up the SleepIQ bed sensors.""" coordinator: SleepIQDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] async_add_entities( - SleepNumberSensor(coordinator, bed_id, side) - for side in SIDES - for bed_id in coordinator.data - if getattr(coordinator.data[bed_id][BED], side) is not None + SleepNumberSensorEntity(coordinator, bed, sleeper) + for bed in coordinator.client.beds.values() + for sleeper in bed.sleepers ) -class SleepNumberSensor(SleepIQSensor, SensorEntity): - """Implementation of a SleepIQ sensor.""" +class SleepNumberSensorEntity(SleepIQSensor, SensorEntity): + """Representation of an SleepIQ Entity with CoordinatorEntity.""" + + _attr_icon = "mdi:bed" def __init__( self, - coordinator: SleepIQDataUpdateCoordinator, - bed_id: str, - side: str, + coordinator: DataUpdateCoordinator, + bed: SleepIQBed, + sleeper: SleepIQSleeper, ) -> None: - """Initialize the SleepIQ sleep number sensor.""" - super().__init__(coordinator, bed_id, side, SLEEP_NUMBER) + """Initialize the sensor.""" + super().__init__(coordinator, bed, sleeper, SLEEP_NUMBER) @callback def _async_update_attrs(self) -> None: """Update sensor attributes.""" - super()._async_update_attrs() - self._attr_native_value = self.side_data.sleep_number + self._attr_native_value = self.sleeper.sleep_number diff --git a/requirements_all.txt b/requirements_all.txt index 33aed1f238c835..9f41bd500ebe65 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -349,6 +349,9 @@ async-upnp-client==0.23.5 # homeassistant.components.supla asyncpysupla==0.0.5 +# homeassistant.components.sleepiq +asyncsleepiq==1.0.0 + # homeassistant.components.aten_pe atenpdu==0.3.2 @@ -2201,9 +2204,6 @@ skybellpy==0.6.3 # homeassistant.components.slack slackclient==2.5.0 -# homeassistant.components.sleepiq -sleepyq==0.8.1 - # homeassistant.components.xmpp slixmpp==1.7.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8dad410b49cf31..4a93ab379f6a1c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -254,6 +254,9 @@ arcam-fmj==0.12.0 # homeassistant.components.yeelight async-upnp-client==0.23.5 +# homeassistant.components.sleepiq +asyncsleepiq==1.0.0 + # homeassistant.components.aurora auroranoaa==0.0.2 @@ -1354,9 +1357,6 @@ simplisafe-python==2022.02.1 # homeassistant.components.slack slackclient==2.5.0 -# homeassistant.components.sleepiq -sleepyq==0.8.1 - # homeassistant.components.smart_meter_texas smart-meter-texas==0.4.7 diff --git a/tests/components/sleepiq/conftest.py b/tests/components/sleepiq/conftest.py index 707fc436c15b53..b694928a042db2 100644 --- a/tests/components/sleepiq/conftest.py +++ b/tests/components/sleepiq/conftest.py @@ -1,75 +1,67 @@ -"""Common fixtures for sleepiq tests.""" -import json -from unittest.mock import patch +"""Common methods for SleepIQ.""" +from unittest.mock import MagicMock, patch import pytest -from sleepyq import Bed, FamilyStatus, Sleeper -from homeassistant.components.sleepiq.const import DOMAIN +from homeassistant.components.sleepiq import DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry, load_fixture +from tests.common import MockConfigEntry +BED_ID = "123456" +BED_NAME = "Test Bed" +BED_NAME_LOWER = BED_NAME.lower().replace(" ", "_") +SLEEPER_L_NAME = "SleeperL" +SLEEPER_R_NAME = "Sleeper R" +SLEEPER_L_NAME_LOWER = SLEEPER_L_NAME.lower().replace(" ", "_") +SLEEPER_R_NAME_LOWER = SLEEPER_R_NAME.lower().replace(" ", "_") -def mock_beds(account_type): - """Mock sleepnumber bed data.""" - return [ - Bed(bed) - for bed in json.loads(load_fixture(f"bed{account_type}.json", "sleepiq"))[ - "beds" - ] - ] - - -def mock_sleepers(): - """Mock sleeper data.""" - return [ - Sleeper(sleeper) - for sleeper in json.loads(load_fixture("sleeper.json", "sleepiq"))["sleepers"] - ] +@pytest.fixture +def mock_asyncsleepiq(): + """Mock an AsyncSleepIQ object.""" + with patch("homeassistant.components.sleepiq.AsyncSleepIQ", autospec=True) as mock: + client = mock.return_value + bed = MagicMock() + client.beds = {BED_ID: bed} + bed.name = BED_NAME + bed.id = BED_ID + bed.mac_addr = "12:34:56:78:AB:CD" + bed.model = "C10" + bed.paused = False + sleeper_l = MagicMock() + sleeper_r = MagicMock() + bed.sleepers = [sleeper_l, sleeper_r] -def mock_bed_family_status(account_type): - """Mock family status data.""" - return [ - FamilyStatus(status) - for status in json.loads( - load_fixture(f"familystatus{account_type}.json", "sleepiq") - )["beds"] - ] + sleeper_l.side = "L" + sleeper_l.name = SLEEPER_L_NAME + sleeper_l.in_bed = True + sleeper_l.sleep_number = 40 + sleeper_r.side = "R" + sleeper_r.name = SLEEPER_R_NAME + sleeper_r.in_bed = False + sleeper_r.sleep_number = 80 -@pytest.fixture -def config_data(): - """Provide configuration data for tests.""" - return { - CONF_USERNAME: "username", - CONF_PASSWORD: "password", - } + yield client -@pytest.fixture -def config_entry(config_data): - """Create a mock config entry.""" - return MockConfigEntry( +async def setup_platform(hass: HomeAssistant, platform) -> MockConfigEntry: + """Set up the SleepIQ platform.""" + mock_entry = MockConfigEntry( domain=DOMAIN, - data=config_data, - options={}, + data={ + CONF_USERNAME: "user@email.com", + CONF_PASSWORD: "password", + }, ) + mock_entry.add_to_hass(hass) - -@pytest.fixture(params=["-single", ""]) -async def setup_entry(hass, request, config_entry): - """Initialize the config entry.""" - with patch("sleepyq.Sleepyq.beds", return_value=mock_beds(request.param)), patch( - "sleepyq.Sleepyq.sleepers", return_value=mock_sleepers() - ), patch( - "sleepyq.Sleepyq.bed_family_status", - return_value=mock_bed_family_status(request.param), - ), patch( - "sleepyq.Sleepyq.login" - ): - config_entry.add_to_hass(hass) - await hass.config_entries.async_setup(config_entry.entry_id) + if platform: + with patch("homeassistant.components.sleepiq.PLATFORMS", [platform]): + assert await async_setup_component(hass, DOMAIN, {}) await hass.async_block_till_done() - return {"account_type": request.param, "mock_entry": config_entry} + + return mock_entry diff --git a/tests/components/sleepiq/fixtures/bed-single.json b/tests/components/sleepiq/fixtures/bed-single.json deleted file mode 100644 index f1e59f5ad2d9ed..00000000000000 --- a/tests/components/sleepiq/fixtures/bed-single.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "beds" : [ - { - "dualSleep" : false, - "base" : "FlexFit", - "sku" : "AILE", - "model" : "ILE", - "size" : "KING", - "isKidsBed" : false, - "sleeperRightId" : "-80", - "accountId" : "-32", - "bedId" : "-31", - "registrationDate" : "2016-07-22T14:00:58Z", - "serial" : null, - "reference" : "95000794555-1", - "macAddress" : "CD13A384BA51", - "version" : null, - "purchaseDate" : "2016-06-22T00:00:00Z", - "sleeperLeftId" : "0", - "zipcode" : "12345", - "returnRequestStatus" : 0, - "name" : "ILE", - "status" : 1, - "timezone" : "US/Eastern" - } - ] - } \ No newline at end of file diff --git a/tests/components/sleepiq/fixtures/bed.json b/tests/components/sleepiq/fixtures/bed.json deleted file mode 100644 index 5fb12da05070bc..00000000000000 --- a/tests/components/sleepiq/fixtures/bed.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "beds" : [ - { - "dualSleep" : true, - "base" : "FlexFit", - "sku" : "AILE", - "model" : "ILE", - "size" : "KING", - "isKidsBed" : false, - "sleeperRightId" : "-80", - "accountId" : "-32", - "bedId" : "-31", - "registrationDate" : "2016-07-22T14:00:58Z", - "serial" : null, - "reference" : "95000794555-1", - "macAddress" : "CD13A384BA51", - "version" : null, - "purchaseDate" : "2016-06-22T00:00:00Z", - "sleeperLeftId" : "-92", - "zipcode" : "12345", - "returnRequestStatus" : 0, - "name" : "ILE", - "status" : 1, - "timezone" : "US/Eastern" - } - ] - } diff --git a/tests/components/sleepiq/fixtures/familystatus-single.json b/tests/components/sleepiq/fixtures/familystatus-single.json deleted file mode 100644 index 1d5c0d89943a3a..00000000000000 --- a/tests/components/sleepiq/fixtures/familystatus-single.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "beds" : [ - { - "bedId" : "-31", - "rightSide" : { - "alertId" : 0, - "lastLink" : "00:00:00", - "isInBed" : true, - "sleepNumber" : 40, - "alertDetailedMessage" : "No Alert", - "pressure" : -16 - }, - "status" : 1, - "leftSide" : null - } - ] - } \ No newline at end of file diff --git a/tests/components/sleepiq/fixtures/familystatus.json b/tests/components/sleepiq/fixtures/familystatus.json deleted file mode 100644 index c9b6082411503f..00000000000000 --- a/tests/components/sleepiq/fixtures/familystatus.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "beds" : [ - { - "bedId" : "-31", - "rightSide" : { - "alertId" : 0, - "lastLink" : "00:00:00", - "isInBed" : true, - "sleepNumber" : 40, - "alertDetailedMessage" : "No Alert", - "pressure" : -16 - }, - "status" : 1, - "leftSide" : { - "alertId" : 0, - "lastLink" : "00:00:00", - "sleepNumber" : 80, - "alertDetailedMessage" : "No Alert", - "isInBed" : false, - "pressure" : 2191 - } - } - ] - } \ No newline at end of file diff --git a/tests/components/sleepiq/fixtures/login.json b/tests/components/sleepiq/fixtures/login.json deleted file mode 100644 index a665db7de29775..00000000000000 --- a/tests/components/sleepiq/fixtures/login.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "edpLoginStatus" : 200, - "userId" : "-42", - "registrationState" : 13, - "key" : "0987", - "edpLoginMessage" : "not used" - } \ No newline at end of file diff --git a/tests/components/sleepiq/fixtures/sleeper.json b/tests/components/sleepiq/fixtures/sleeper.json deleted file mode 100644 index c009e6842209a4..00000000000000 --- a/tests/components/sleepiq/fixtures/sleeper.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "sleepers" : [ - { - "timezone" : "US/Eastern", - "firstName" : "Test1", - "weight" : 150, - "birthMonth" : 12, - "birthYear" : "1990", - "active" : true, - "lastLogin" : "2016-08-26 21:43:27 CDT", - "side" : 1, - "accountId" : "-32", - "height" : 60, - "bedId" : "-31", - "username" : "test1@example.com", - "sleeperId" : "-80", - "avatar" : "", - "emailValidated" : true, - "licenseVersion" : 6, - "duration" : null, - "email" : "test1@example.com", - "isAccountOwner" : true, - "sleepGoal" : 480, - "zipCode" : "12345", - "isChild" : false, - "isMale" : true - }, - { - "email" : "test2@example.com", - "duration" : null, - "emailValidated" : true, - "licenseVersion" : 5, - "isChild" : false, - "isMale" : false, - "zipCode" : "12345", - "isAccountOwner" : false, - "sleepGoal" : 480, - "side" : 0, - "lastLogin" : "2016-07-17 15:37:30 CDT", - "birthMonth" : 1, - "birthYear" : "1991", - "active" : true, - "weight" : 151, - "firstName" : "Test2", - "timezone" : "US/Eastern", - "avatar" : "", - "username" : "test2@example.com", - "sleeperId" : "-92", - "bedId" : "-31", - "height" : 65, - "accountId" : "-32" - } - ] - } diff --git a/tests/components/sleepiq/test_binary_sensor.py b/tests/components/sleepiq/test_binary_sensor.py index ca9bf3c84fcdc1..2b265e1962679e 100644 --- a/tests/components/sleepiq/test_binary_sensor.py +++ b/tests/components/sleepiq/test_binary_sensor.py @@ -1,34 +1,61 @@ """The tests for SleepIQ binary sensor platform.""" -from homeassistant.components.binary_sensor import BinarySensorDeviceClass -from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_FRIENDLY_NAME, ATTR_ICON +from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDeviceClass +from homeassistant.const import ( + ATTR_DEVICE_CLASS, + ATTR_FRIENDLY_NAME, + ATTR_ICON, + STATE_OFF, + STATE_ON, +) from homeassistant.helpers import entity_registry as er +from tests.components.sleepiq.conftest import ( + BED_ID, + BED_NAME, + BED_NAME_LOWER, + SLEEPER_L_NAME, + SLEEPER_L_NAME_LOWER, + SLEEPER_R_NAME, + SLEEPER_R_NAME_LOWER, + setup_platform, +) -async def test_binary_sensors(hass, setup_entry): + +async def test_binary_sensors(hass, mock_asyncsleepiq): """Test the SleepIQ binary sensors.""" + await setup_platform(hass, DOMAIN) entity_registry = er.async_get(hass) - state = hass.states.get("binary_sensor.sleepnumber_ile_test1_is_in_bed") - assert state.state == "on" + state = hass.states.get( + f"binary_sensor.sleepnumber_{BED_NAME_LOWER}_{SLEEPER_L_NAME_LOWER}_is_in_bed" + ) + assert state.state == STATE_ON assert state.attributes.get(ATTR_ICON) == "mdi:bed" assert state.attributes.get(ATTR_DEVICE_CLASS) == BinarySensorDeviceClass.OCCUPANCY - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "SleepNumber ILE Test1 Is In Bed" - - entry = entity_registry.async_get("binary_sensor.sleepnumber_ile_test1_is_in_bed") - assert entry - assert entry.unique_id == "-31_Test1_is_in_bed" + assert ( + state.attributes.get(ATTR_FRIENDLY_NAME) + == f"SleepNumber {BED_NAME} {SLEEPER_L_NAME} Is In Bed" + ) - # If account type is set, only a single bed account was created and there will - # not be a second entity - if setup_entry["account_type"]: - return + entity = entity_registry.async_get( + f"binary_sensor.sleepnumber_{BED_NAME_LOWER}_{SLEEPER_L_NAME_LOWER}_is_in_bed" + ) + assert entity + assert entity.unique_id == f"{BED_ID}_{SLEEPER_L_NAME}_is_in_bed" - entry = entity_registry.async_get("binary_sensor.sleepnumber_ile_test2_is_in_bed") - assert entry - assert entry.unique_id == "-31_Test2_is_in_bed" - - state = hass.states.get("binary_sensor.sleepnumber_ile_test2_is_in_bed") - assert state.state == "off" + state = hass.states.get( + f"binary_sensor.sleepnumber_{BED_NAME_LOWER}_{SLEEPER_R_NAME_LOWER}_is_in_bed" + ) + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ICON) == "mdi:bed-empty" assert state.attributes.get(ATTR_DEVICE_CLASS) == BinarySensorDeviceClass.OCCUPANCY - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "SleepNumber ILE Test2 Is In Bed" + assert ( + state.attributes.get(ATTR_FRIENDLY_NAME) + == f"SleepNumber {BED_NAME} {SLEEPER_R_NAME} Is In Bed" + ) + + entity = entity_registry.async_get( + f"binary_sensor.sleepnumber_{BED_NAME_LOWER}_{SLEEPER_R_NAME_LOWER}_is_in_bed" + ) + assert entity + assert entity.unique_id == f"{BED_ID}_{SLEEPER_R_NAME}_is_in_bed" diff --git a/tests/components/sleepiq/test_config_flow.py b/tests/components/sleepiq/test_config_flow.py index e4a422c888fbdf..b2554ea968e63d 100644 --- a/tests/components/sleepiq/test_config_flow.py +++ b/tests/components/sleepiq/test_config_flow.py @@ -1,11 +1,10 @@ """Tests for the SleepIQ config flow.""" from unittest.mock import patch +from asyncsleepiq import SleepIQLoginException, SleepIQTimeoutException + from homeassistant import config_entries, data_entry_flow, setup -from homeassistant.components.sleepiq.const import ( - DOMAIN, - SLEEPYQ_INVALID_CREDENTIALS_MESSAGE, -) +from homeassistant.components.sleepiq.const import DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant @@ -17,7 +16,7 @@ async def test_import(hass: HomeAssistant) -> None: """Test that we can import a config entry.""" - with patch("sleepyq.Sleepyq.login"): + with patch("asyncsleepiq.AsyncSleepIQ.login"): assert await setup.async_setup_component(hass, DOMAIN, {DOMAIN: SLEEPIQ_CONFIG}) await hass.async_block_till_done() @@ -29,7 +28,7 @@ async def test_import(hass: HomeAssistant) -> None: async def test_show_set_form(hass: HomeAssistant) -> None: """Test that the setup form is served.""" - with patch("sleepyq.Sleepyq.login"): + with patch("asyncsleepiq.AsyncSleepIQ.login"): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, data=None ) @@ -41,8 +40,8 @@ async def test_show_set_form(hass: HomeAssistant) -> None: async def test_login_invalid_auth(hass: HomeAssistant) -> None: """Test we show user form with appropriate error on login failure.""" with patch( - "sleepyq.Sleepyq.login", - side_effect=ValueError(SLEEPYQ_INVALID_CREDENTIALS_MESSAGE), + "asyncsleepiq.AsyncSleepIQ.login", + side_effect=SleepIQLoginException, ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, data=SLEEPIQ_CONFIG @@ -56,8 +55,8 @@ async def test_login_invalid_auth(hass: HomeAssistant) -> None: async def test_login_cannot_connect(hass: HomeAssistant) -> None: """Test we show user form with appropriate error on login failure.""" with patch( - "sleepyq.Sleepyq.login", - side_effect=ValueError("Unexpected response code"), + "asyncsleepiq.AsyncSleepIQ.login", + side_effect=SleepIQTimeoutException, ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, data=SLEEPIQ_CONFIG @@ -70,11 +69,23 @@ async def test_login_cannot_connect(hass: HomeAssistant) -> None: async def test_success(hass: HomeAssistant) -> None: """Test successful flow provides entry creation data.""" - with patch("sleepyq.Sleepyq.login"): - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER}, data=SLEEPIQ_CONFIG + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + with patch("asyncsleepiq.AsyncSleepIQ.login", return_value=True), patch( + "homeassistant.components.sleepiq.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], SLEEPIQ_CONFIG ) + await hass.async_block_till_done() - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["data"][CONF_USERNAME] == SLEEPIQ_CONFIG[CONF_USERNAME] - assert result["data"][CONF_PASSWORD] == SLEEPIQ_CONFIG[CONF_PASSWORD] + assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result2["data"][CONF_USERNAME] == SLEEPIQ_CONFIG[CONF_USERNAME] + assert result2["data"][CONF_PASSWORD] == SLEEPIQ_CONFIG[CONF_PASSWORD] + assert len(mock_setup_entry.mock_calls) == 1 diff --git a/tests/components/sleepiq/test_init.py b/tests/components/sleepiq/test_init.py index 15af03e14ce932..0aed23c4c50bb8 100644 --- a/tests/components/sleepiq/test_init.py +++ b/tests/components/sleepiq/test_init.py @@ -1,5 +1,9 @@ """Tests for the SleepIQ integration.""" -from unittest.mock import patch +from asyncsleepiq import ( + SleepIQAPIException, + SleepIQLoginException, + SleepIQTimeoutException, +) from homeassistant.components.sleepiq.const import DOMAIN from homeassistant.components.sleepiq.coordinator import UPDATE_INTERVAL @@ -8,16 +12,12 @@ from homeassistant.util.dt import utcnow from tests.common import async_fire_time_changed -from tests.components.sleepiq.conftest import ( - mock_bed_family_status, - mock_beds, - mock_sleepers, -) +from tests.components.sleepiq.conftest import setup_platform -async def test_unload_entry(hass: HomeAssistant, setup_entry) -> None: +async def test_unload_entry(hass: HomeAssistant, mock_asyncsleepiq) -> None: """Test unloading the SleepIQ entry.""" - entry = setup_entry["mock_entry"] + entry = await setup_platform(hass, "sensor") assert await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done() @@ -25,30 +25,42 @@ async def test_unload_entry(hass: HomeAssistant, setup_entry) -> None: assert not hass.data.get(DOMAIN) -async def test_entry_setup_login_error(hass: HomeAssistant, config_entry) -> None: - """Test when sleepyq client is unable to login.""" - with patch("sleepyq.Sleepyq.login", side_effect=ValueError): - config_entry.add_to_hass(hass) - assert not await hass.config_entries.async_setup(config_entry.entry_id) +async def test_entry_setup_login_error(hass: HomeAssistant, mock_asyncsleepiq) -> None: + """Test when sleepiq client is unable to login.""" + mock_asyncsleepiq.login.side_effect = SleepIQLoginException + entry = await setup_platform(hass, None) + assert not await hass.config_entries.async_setup(entry.entry_id) + +async def test_entry_setup_timeout_error( + hass: HomeAssistant, mock_asyncsleepiq +) -> None: + """Test when sleepiq client timeout.""" + mock_asyncsleepiq.login.side_effect = SleepIQTimeoutException + entry = await setup_platform(hass, None) + assert not await hass.config_entries.async_setup(entry.entry_id) -async def test_update_interval(hass: HomeAssistant, setup_entry) -> None: + +async def test_update_interval(hass: HomeAssistant, mock_asyncsleepiq) -> None: """Test update interval.""" - with patch("sleepyq.Sleepyq.beds", return_value=mock_beds("")) as beds, patch( - "sleepyq.Sleepyq.sleepers", return_value=mock_sleepers() - ) as sleepers, patch( - "sleepyq.Sleepyq.bed_family_status", - return_value=mock_bed_family_status(""), - ) as bed_family_status, patch( - "sleepyq.Sleepyq.login", return_value=True - ): - assert beds.call_count == 0 - assert sleepers.call_count == 0 - assert bed_family_status.call_count == 0 - - async_fire_time_changed(hass, utcnow() + UPDATE_INTERVAL) - await hass.async_block_till_done() - - assert beds.call_count == 1 - assert sleepers.call_count == 1 - assert bed_family_status.call_count == 1 + await setup_platform(hass, "sensor") + assert mock_asyncsleepiq.fetch_bed_statuses.call_count == 1 + + async_fire_time_changed(hass, utcnow() + UPDATE_INTERVAL) + await hass.async_block_till_done() + + assert mock_asyncsleepiq.fetch_bed_statuses.call_count == 2 + + +async def test_api_error(hass: HomeAssistant, mock_asyncsleepiq) -> None: + """Test when sleepiq client is unable to login.""" + mock_asyncsleepiq.init_beds.side_effect = SleepIQAPIException + entry = await setup_platform(hass, None) + assert not await hass.config_entries.async_setup(entry.entry_id) + + +async def test_api_timeout(hass: HomeAssistant, mock_asyncsleepiq) -> None: + """Test when sleepiq client timeout.""" + mock_asyncsleepiq.init_beds.side_effect = SleepIQTimeoutException + entry = await setup_platform(hass, None) + assert not await hass.config_entries.async_setup(entry.entry_id) diff --git a/tests/components/sleepiq/test_sensor.py b/tests/components/sleepiq/test_sensor.py index baa8732365b19b..26ddc9aa485b20 100644 --- a/tests/components/sleepiq/test_sensor.py +++ b/tests/components/sleepiq/test_sensor.py @@ -1,35 +1,53 @@ """The tests for SleepIQ sensor platform.""" +from homeassistant.components.sensor import DOMAIN from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_ICON from homeassistant.helpers import entity_registry as er +from tests.components.sleepiq.conftest import ( + BED_ID, + BED_NAME, + BED_NAME_LOWER, + SLEEPER_L_NAME, + SLEEPER_L_NAME_LOWER, + SLEEPER_R_NAME, + SLEEPER_R_NAME_LOWER, + setup_platform, +) -async def test_sensors(hass, setup_entry): + +async def test_sensors(hass, mock_asyncsleepiq): """Test the SleepIQ binary sensors for a bed with two sides.""" + entry = await setup_platform(hass, DOMAIN) entity_registry = er.async_get(hass) - state = hass.states.get("sensor.sleepnumber_ile_test1_sleepnumber") + state = hass.states.get( + f"sensor.sleepnumber_{BED_NAME_LOWER}_{SLEEPER_L_NAME_LOWER}_sleepnumber" + ) assert state.state == "40" assert state.attributes.get(ATTR_ICON) == "mdi:bed" assert ( - state.attributes.get(ATTR_FRIENDLY_NAME) == "SleepNumber ILE Test1 SleepNumber" + state.attributes.get(ATTR_FRIENDLY_NAME) + == f"SleepNumber {BED_NAME} {SLEEPER_L_NAME} SleepNumber" ) - entry = entity_registry.async_get("sensor.sleepnumber_ile_test1_sleepnumber") + entry = entity_registry.async_get( + f"sensor.sleepnumber_{BED_NAME_LOWER}_{SLEEPER_L_NAME_LOWER}_sleepnumber" + ) assert entry - assert entry.unique_id == "-31_Test1_sleep_number" + assert entry.unique_id == f"{BED_ID}_{SLEEPER_L_NAME}_sleep_number" - # If account type is set, only a single bed account was created and there will - # not be a second entity - if setup_entry["account_type"]: - return - - state = hass.states.get("sensor.sleepnumber_ile_test2_sleepnumber") + state = hass.states.get( + f"sensor.sleepnumber_{BED_NAME_LOWER}_{SLEEPER_R_NAME_LOWER}_sleepnumber" + ) assert state.state == "80" assert state.attributes.get(ATTR_ICON) == "mdi:bed" assert ( - state.attributes.get(ATTR_FRIENDLY_NAME) == "SleepNumber ILE Test2 SleepNumber" + state.attributes.get(ATTR_FRIENDLY_NAME) + == f"SleepNumber {BED_NAME} {SLEEPER_R_NAME} SleepNumber" ) - entry = entity_registry.async_get("sensor.sleepnumber_ile_test2_sleepnumber") + entry = entity_registry.async_get( + f"sensor.sleepnumber_{BED_NAME_LOWER}_{SLEEPER_R_NAME_LOWER}_sleepnumber" + ) assert entry - assert entry.unique_id == "-31_Test2_sleep_number" + assert entry.unique_id == f"{BED_ID}_{SLEEPER_R_NAME}_sleep_number" From 92834cfd7a4884aed189c39c69cf215d94fa864b Mon Sep 17 00:00:00 2001 From: Jeef Date: Fri, 18 Feb 2022 12:12:40 -0700 Subject: [PATCH 0796/1098] Dependency Bump on Intellifire Lib (#66814) --- homeassistant/components/intellifire/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/intellifire/manifest.json b/homeassistant/components/intellifire/manifest.json index aaae49afb64893..03862a6ef5ff39 100644 --- a/homeassistant/components/intellifire/manifest.json +++ b/homeassistant/components/intellifire/manifest.json @@ -3,7 +3,7 @@ "name": "IntelliFire", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/intellifire", - "requirements": ["intellifire4py==0.9.8"], + "requirements": ["intellifire4py==0.9.9"], "dependencies": [], "codeowners": ["@jeeftor"], "iot_class": "local_polling", diff --git a/requirements_all.txt b/requirements_all.txt index 9f41bd500ebe65..b3ef6eb2448e86 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -908,7 +908,7 @@ influxdb-client==1.24.0 influxdb==5.3.1 # homeassistant.components.intellifire -intellifire4py==0.9.8 +intellifire4py==0.9.9 # homeassistant.components.iotawatt iotawattpy==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4a93ab379f6a1c..1c23b20c7ea011 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -592,7 +592,7 @@ influxdb-client==1.24.0 influxdb==5.3.1 # homeassistant.components.intellifire -intellifire4py==0.9.8 +intellifire4py==0.9.9 # homeassistant.components.iotawatt iotawattpy==0.1.0 From 90d6172fd02ae5fdaab6ecf465b7f039616ee860 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 18 Feb 2022 11:40:56 -0800 Subject: [PATCH 0797/1098] Bump aiohue to 4.2.1 (#66823) --- homeassistant/components/hue/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/hue/manifest.json b/homeassistant/components/hue/manifest.json index d82359613f7ef2..193d2b7fa81591 100644 --- a/homeassistant/components/hue/manifest.json +++ b/homeassistant/components/hue/manifest.json @@ -3,7 +3,7 @@ "name": "Philips Hue", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/hue", - "requirements": ["aiohue==4.2.0"], + "requirements": ["aiohue==4.2.1"], "ssdp": [ { "manufacturer": "Royal Philips Electronics", diff --git a/requirements_all.txt b/requirements_all.txt index b3ef6eb2448e86..4b820df42f06fb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -191,7 +191,7 @@ aiohomekit==0.7.14 aiohttp_cors==0.7.0 # homeassistant.components.hue -aiohue==4.2.0 +aiohue==4.2.1 # homeassistant.components.homewizard aiohwenergy==0.8.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1c23b20c7ea011..c2091f02ff9ac8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -141,7 +141,7 @@ aiohomekit==0.7.14 aiohttp_cors==0.7.0 # homeassistant.components.hue -aiohue==4.2.0 +aiohue==4.2.1 # homeassistant.components.homewizard aiohwenergy==0.8.0 From d3bb622a3c2fe4f9570fa788cf9a7684b4f6d9a1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 18 Feb 2022 11:53:02 -0800 Subject: [PATCH 0798/1098] Bump hass-nabucasa to 0.53.0 (#66826) --- homeassistant/components/cloud/http_api.py | 9 +++++++++ homeassistant/components/cloud/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/cloud/test_http_api.py | 1 + 6 files changed, 14 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/cloud/http_api.py b/homeassistant/components/cloud/http_api.py index f51266e45dcf5a..0ea1fc1d3f31d9 100644 --- a/homeassistant/components/cloud/http_api.py +++ b/homeassistant/components/cloud/http_api.py @@ -1,5 +1,6 @@ """The HTTP api to control the cloud integration.""" import asyncio +import dataclasses from functools import wraps from http import HTTPStatus import logging @@ -434,10 +435,18 @@ async def _account_data(hass: HomeAssistant, cloud: Cloud): else: certificate = None + if cloud.iot.last_disconnect_reason: + cloud_last_disconnect_reason = dataclasses.asdict( + cloud.iot.last_disconnect_reason + ) + else: + cloud_last_disconnect_reason = None + return { "alexa_entities": client.alexa_user_config["filter"].config, "alexa_registered": alexa_config.authorized, "cloud": cloud.iot.state, + "cloud_last_disconnect_reason": cloud_last_disconnect_reason, "email": claims["email"], "google_entities": client.google_user_config["filter"].config, "google_registered": google_config.has_registered_user_agent, diff --git a/homeassistant/components/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index 9a3a88dfc95902..6431160f696530 100644 --- a/homeassistant/components/cloud/manifest.json +++ b/homeassistant/components/cloud/manifest.json @@ -2,7 +2,7 @@ "domain": "cloud", "name": "Home Assistant Cloud", "documentation": "https://www.home-assistant.io/integrations/cloud", - "requirements": ["hass-nabucasa==0.52.1"], + "requirements": ["hass-nabucasa==0.53.0"], "dependencies": ["http", "webhook"], "after_dependencies": ["google_assistant", "alexa"], "codeowners": ["@home-assistant/cloud"], diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index a2ad73e96c5f31..62e64066708a7c 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -14,7 +14,7 @@ certifi>=2021.5.30 ciso8601==2.2.0 cryptography==35.0.0 emoji==1.6.3 -hass-nabucasa==0.52.1 +hass-nabucasa==0.53.0 home-assistant-frontend==20220214.0 httpx==0.21.3 ifaddr==0.1.7 diff --git a/requirements_all.txt b/requirements_all.txt index 4b820df42f06fb..566f72548e3d6f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -803,7 +803,7 @@ habitipy==0.2.0 hangups==0.4.17 # homeassistant.components.cloud -hass-nabucasa==0.52.1 +hass-nabucasa==0.53.0 # homeassistant.components.splunk hass_splunk==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c2091f02ff9ac8..fbd8b2e8982561 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -528,7 +528,7 @@ habitipy==0.2.0 hangups==0.4.17 # homeassistant.components.cloud -hass-nabucasa==0.52.1 +hass-nabucasa==0.53.0 # homeassistant.components.tasmota hatasmota==0.3.1 diff --git a/tests/components/cloud/test_http_api.py b/tests/components/cloud/test_http_api.py index e06344827a7859..2360526864947b 100644 --- a/tests/components/cloud/test_http_api.py +++ b/tests/components/cloud/test_http_api.py @@ -392,6 +392,7 @@ async def test_websocket_status( "logged_in": True, "email": "hello@home-assistant.io", "cloud": "connected", + "cloud_last_disconnect_reason": None, "prefs": { "alexa_enabled": True, "cloudhooks": {}, From 48e3f9584bd6623e35be658669fd12a5c5c59e53 Mon Sep 17 00:00:00 2001 From: Mathew Verdouw Date: Sat, 19 Feb 2022 06:39:29 +1000 Subject: [PATCH 0799/1098] Add broadlink lb2 support (#63530) * Update const.py * Update to support LB2 version smart bulbs in Broadlink integration * Update const.py Added Space. * Update updater.py Updated so that LB2 lights use the LB1 update manager. --- homeassistant/components/broadlink/const.py | 2 +- homeassistant/components/broadlink/light.py | 2 +- homeassistant/components/broadlink/updater.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/broadlink/const.py b/homeassistant/components/broadlink/const.py index 3f7744ecbb445c..c1ccc5ec954af1 100644 --- a/homeassistant/components/broadlink/const.py +++ b/homeassistant/components/broadlink/const.py @@ -31,7 +31,7 @@ "SP4", "SP4B", }, - Platform.LIGHT: {"LB1"}, + Platform.LIGHT: {"LB1", "LB2"}, } DEVICE_TYPES = set.union(*DOMAINS_AND_TYPES.values()) diff --git a/homeassistant/components/broadlink/light.py b/homeassistant/components/broadlink/light.py index 9880d0b3c22376..9af50a345fb46d 100644 --- a/homeassistant/components/broadlink/light.py +++ b/homeassistant/components/broadlink/light.py @@ -37,7 +37,7 @@ async def async_setup_entry( device = hass.data[DOMAIN].devices[config_entry.entry_id] lights = [] - if device.api.type == "LB1": + if device.api.type in {"LB1", "LB2"}: lights.append(BroadlinkLight(device)) async_add_entities(lights) diff --git a/homeassistant/components/broadlink/updater.py b/homeassistant/components/broadlink/updater.py index 29020b1e905620..2b98b757fbd37c 100644 --- a/homeassistant/components/broadlink/updater.py +++ b/homeassistant/components/broadlink/updater.py @@ -17,6 +17,7 @@ def get_update_manager(device): "A1": BroadlinkA1UpdateManager, "BG1": BroadlinkBG1UpdateManager, "LB1": BroadlinkLB1UpdateManager, + "LB2": BroadlinkLB1UpdateManager, "MP1": BroadlinkMP1UpdateManager, "RM4MINI": BroadlinkRMUpdateManager, "RM4PRO": BroadlinkRMUpdateManager, From fa8238bc04ae7128dbe68b3810a5ea10a0f321c3 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 18 Feb 2022 21:43:25 +0100 Subject: [PATCH 0800/1098] Downgrade log warning->info for unregistered webhook message (#66830) --- homeassistant/components/webhook/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/webhook/__init__.py b/homeassistant/components/webhook/__init__.py index 46fdc89871f26d..95233cca9ca835 100644 --- a/homeassistant/components/webhook/__init__.py +++ b/homeassistant/components/webhook/__init__.py @@ -95,7 +95,7 @@ async def async_handle_webhook( else: received_from = request.remote - _LOGGER.warning( + _LOGGER.info( "Received message for unregistered webhook %s from %s", webhook_id, received_from, From abc73ff2e12c5b398df384e902cef2ab56385db7 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Fri, 18 Feb 2022 22:04:19 +0100 Subject: [PATCH 0801/1098] Improve code quality workday (#66446) * Code quality workday * Modify from review * Modify from review 2 * Fix mypy --- .../components/workday/binary_sensor.py | 118 +++++++++--------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/homeassistant/components/workday/binary_sensor.py b/homeassistant/components/workday/binary_sensor.py index 3119a7b667b659..44fb0f871de219 100644 --- a/homeassistant/components/workday/binary_sensor.py +++ b/homeassistant/components/workday/binary_sensor.py @@ -1,14 +1,18 @@ """Sensor to indicate whether the current day is a workday.""" from __future__ import annotations -from datetime import timedelta +from datetime import date, timedelta import logging from typing import Any import holidays +from holidays import HolidayBase import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity +from homeassistant.components.binary_sensor import ( + PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA, + BinarySensorEntity, +) from homeassistant.const import CONF_NAME, WEEKDAYS from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv @@ -54,7 +58,7 @@ def valid_country(value: Any) -> str: return value -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( +PLATFORM_SCHEMA = PARENT_PLATFORM_SCHEMA.extend( { vol.Required(CONF_COUNTRY): valid_country, vol.Optional(CONF_EXCLUDES, default=DEFAULT_EXCLUDES): vol.All( @@ -83,17 +87,17 @@ def setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the Workday sensor.""" - add_holidays = config[CONF_ADD_HOLIDAYS] - remove_holidays = config[CONF_REMOVE_HOLIDAYS] - country = config[CONF_COUNTRY] - days_offset = config[CONF_OFFSET] - excludes = config[CONF_EXCLUDES] - province = config.get(CONF_PROVINCE) - sensor_name = config[CONF_NAME] - workdays = config[CONF_WORKDAYS] - - year = (get_date(dt.now()) + timedelta(days=days_offset)).year - obj_holidays = getattr(holidays, country)(years=year) + add_holidays: list[str] = config[CONF_ADD_HOLIDAYS] + remove_holidays: list[str] = config[CONF_REMOVE_HOLIDAYS] + country: str = config[CONF_COUNTRY] + days_offset: int = config[CONF_OFFSET] + excludes: list[str] = config[CONF_EXCLUDES] + province: str | None = config.get(CONF_PROVINCE) + sensor_name: str = config[CONF_NAME] + workdays: list[str] = config[CONF_WORKDAYS] + + year: int = (get_date(dt.now()) + timedelta(days=days_offset)).year + obj_holidays: HolidayBase = getattr(holidays, country)(years=year) if province: if ( @@ -113,27 +117,29 @@ def setup_platform( # Remove holidays try: - for date in remove_holidays: + for remove_holiday in remove_holidays: try: # is this formatted as a date? - if dt.parse_date(date): + if dt.parse_date(remove_holiday): # remove holiday by date - removed = obj_holidays.pop(date) - _LOGGER.debug("Removed %s", date) + removed = obj_holidays.pop(remove_holiday) + _LOGGER.debug("Removed %s", remove_holiday) else: # remove holiday by name - _LOGGER.debug("Treating '%s' as named holiday", date) - removed = obj_holidays.pop_named(date) + _LOGGER.debug("Treating '%s' as named holiday", remove_holiday) + removed = obj_holidays.pop_named(remove_holiday) for holiday in removed: - _LOGGER.debug("Removed %s by name '%s'", holiday, date) + _LOGGER.debug( + "Removed %s by name '%s'", holiday, remove_holiday + ) except KeyError as unmatched: _LOGGER.warning("No holiday found matching %s", unmatched) except TypeError: _LOGGER.debug("No holidays to remove or invalid holidays") _LOGGER.debug("Found the following holidays for your configuration:") - for date, name in sorted(obj_holidays.items()): - _LOGGER.debug("%s %s", date, name) + for remove_holiday, name in sorted(obj_holidays.items()): + _LOGGER.debug("%s %s", remove_holiday, name) add_entities( [IsWorkdaySensor(obj_holidays, workdays, excludes, days_offset, sensor_name)], @@ -141,7 +147,7 @@ def setup_platform( ) -def day_to_string(day): +def day_to_string(day: int) -> str | None: """Convert day index 0 - 7 to string.""" try: return ALLOWED_DAYS[day] @@ -149,34 +155,35 @@ def day_to_string(day): return None -def get_date(date): +def get_date(input_date: date) -> date: """Return date. Needed for testing.""" - return date + return input_date class IsWorkdaySensor(BinarySensorEntity): """Implementation of a Workday sensor.""" - def __init__(self, obj_holidays, workdays, excludes, days_offset, name): + def __init__( + self, + obj_holidays: HolidayBase, + workdays: list[str], + excludes: list[str], + days_offset: int, + name: str, + ) -> None: """Initialize the Workday sensor.""" - self._name = name + self._attr_name = name self._obj_holidays = obj_holidays self._workdays = workdays self._excludes = excludes self._days_offset = days_offset - self._state = None - - @property - def name(self): - """Return the name of the sensor.""" - return self._name - - @property - def is_on(self): - """Return the state of the device.""" - return self._state + self._attr_extra_state_attributes = { + CONF_WORKDAYS: workdays, + CONF_EXCLUDES: excludes, + CONF_OFFSET: days_offset, + } - def is_include(self, day, now): + def is_include(self, day: str, now: date) -> bool: """Check if given day is in the includes list.""" if day in self._workdays: return True @@ -185,7 +192,7 @@ def is_include(self, day, now): return False - def is_exclude(self, day, now): + def is_exclude(self, day: str, now: date) -> bool: """Check if given day is in the excludes list.""" if day in self._excludes: return True @@ -194,28 +201,21 @@ def is_exclude(self, day, now): return False - @property - def extra_state_attributes(self): - """Return the attributes of the entity.""" - # return self._attributes - return { - CONF_WORKDAYS: self._workdays, - CONF_EXCLUDES: self._excludes, - CONF_OFFSET: self._days_offset, - } - - async def async_update(self): + async def async_update(self) -> None: """Get date and look whether it is a holiday.""" # Default is no workday - self._state = False + self._attr_is_on = False # Get ISO day of the week (1 = Monday, 7 = Sunday) - date = get_date(dt.now()) + timedelta(days=self._days_offset) - day = date.isoweekday() - 1 + adjusted_date = get_date(dt.now()) + timedelta(days=self._days_offset) + day = adjusted_date.isoweekday() - 1 day_of_week = day_to_string(day) - if self.is_include(day_of_week, date): - self._state = True + if day_of_week is None: + return + + if self.is_include(day_of_week, adjusted_date): + self._attr_is_on = True - if self.is_exclude(day_of_week, date): - self._state = False + if self.is_exclude(day_of_week, adjusted_date): + self._attr_is_on = False From cfd908218d357bb69be0ef4f605879b81312a93e Mon Sep 17 00:00:00 2001 From: jjlawren Date: Fri, 18 Feb 2022 15:32:04 -0600 Subject: [PATCH 0802/1098] Initialize AlarmDecoder binary_sensor state as off instead of unknown (#65926) --- homeassistant/components/alarmdecoder/binary_sensor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/alarmdecoder/binary_sensor.py b/homeassistant/components/alarmdecoder/binary_sensor.py index 12e97e16c62a6a..87b6c86fc332de 100644 --- a/homeassistant/components/alarmdecoder/binary_sensor.py +++ b/homeassistant/components/alarmdecoder/binary_sensor.py @@ -77,6 +77,7 @@ def __init__( self._zone_number = int(zone_number) self._zone_type = zone_type self._attr_name = zone_name + self._attr_is_on = False self._rfid = zone_rfid self._loop = zone_loop self._relay_addr = relay_addr From 3aa18ea5d8c81dc80892a4b68996919a9fd41109 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Fri, 18 Feb 2022 22:33:49 +0100 Subject: [PATCH 0803/1098] Add installed apps to samsungtv sources (#66752) Co-authored-by: epenet --- homeassistant/components/samsungtv/bridge.py | 36 +++++++++++++++---- .../components/samsungtv/media_player.py | 31 +++++++++++++--- tests/components/samsungtv/conftest.py | 3 ++ tests/components/samsungtv/const.py | 24 +++++++++++++ .../components/samsungtv/test_config_flow.py | 4 +++ .../components/samsungtv/test_media_player.py | 36 +++++++++++++++++++ 6 files changed, 123 insertions(+), 11 deletions(-) create mode 100644 tests/components/samsungtv/const.py diff --git a/homeassistant/components/samsungtv/bridge.py b/homeassistant/components/samsungtv/bridge.py index 37a725ee5c9335..ee5e44f626c59d 100644 --- a/homeassistant/components/samsungtv/bridge.py +++ b/homeassistant/components/samsungtv/bridge.py @@ -121,6 +121,10 @@ def device_info(self) -> dict[str, Any] | None: def mac_from_device(self) -> str | None: """Try to fetch the mac address of the TV.""" + @abstractmethod + def get_app_list(self) -> dict[str, str] | None: + """Get installed app list.""" + def is_on(self) -> bool: """Tells if the TV is on.""" if self._remote is not None: @@ -139,14 +143,14 @@ def is_on(self) -> bool: # Different reasons, e.g. hostname not resolveable return False - def send_key(self, key: str) -> None: + def send_key(self, key: str, key_type: str | None = None) -> None: """Send a key to the tv and handles exceptions.""" try: # recreate connection if connection was dead retry_count = 1 for _ in range(retry_count + 1): try: - self._send_key(key) + self._send_key(key, key_type) break except ( ConnectionClosed, @@ -164,7 +168,7 @@ def send_key(self, key: str) -> None: pass @abstractmethod - def _send_key(self, key: str) -> None: + def _send_key(self, key: str, key_type: str | None = None) -> None: """Send the key.""" @abstractmethod @@ -212,6 +216,10 @@ def mac_from_device(self) -> None: """Try to fetch the mac address of the TV.""" return None + def get_app_list(self) -> dict[str, str]: + """Get installed app list.""" + return {} + def try_connect(self) -> str: """Try to connect to the Legacy TV.""" config = { @@ -261,7 +269,7 @@ def _get_remote(self, avoid_open: bool = False) -> Remote: pass return self._remote - def _send_key(self, key: str) -> None: + def _send_key(self, key: str, key_type: str | None = None) -> None: """Send the key using legacy protocol.""" if remote := self._get_remote(): remote.control(key) @@ -281,12 +289,25 @@ def __init__( """Initialize Bridge.""" super().__init__(method, host, port) self.token = token + self._app_list: dict[str, str] | None = None def mac_from_device(self) -> str | None: """Try to fetch the mac address of the TV.""" info = self.device_info() return mac_from_device_info(info) if info else None + def get_app_list(self) -> dict[str, str] | None: + """Get installed app list.""" + if self._app_list is None: + if remote := self._get_remote(): + raw_app_list: list[dict[str, str]] = remote.app_list() + self._app_list = { + app["name"]: app["appId"] + for app in sorted(raw_app_list, key=lambda app: app["name"]) + } + + return self._app_list + def try_connect(self) -> str: """Try to connect to the Websocket TV.""" for self.port in WEBSOCKET_PORTS: @@ -338,12 +359,15 @@ def device_info(self) -> dict[str, Any] | None: return None - def _send_key(self, key: str) -> None: + def _send_key(self, key: str, key_type: str | None = None) -> None: """Send the key using websocket protocol.""" if key == "KEY_POWEROFF": key = "KEY_POWER" if remote := self._get_remote(): - remote.send_key(key) + if key_type == "run_app": + remote.run_app(key) + else: + remote.send_key(key) def _get_remote(self, avoid_open: bool = False) -> Remote: """Create or return a remote control instance.""" diff --git a/homeassistant/components/samsungtv/media_player.py b/homeassistant/components/samsungtv/media_player.py index e856d746b3d507..421b88d50ad32a 100644 --- a/homeassistant/components/samsungtv/media_player.py +++ b/homeassistant/components/samsungtv/media_player.py @@ -13,6 +13,7 @@ MediaPlayerEntity, ) from homeassistant.components.media_player.const import ( + MEDIA_TYPE_APP, MEDIA_TYPE_CHANNEL, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, @@ -89,6 +90,8 @@ async def async_setup_entry( class SamsungTVDevice(MediaPlayerEntity): """Representation of a Samsung TV.""" + _attr_source_list: list[str] + def __init__( self, bridge: SamsungTVLegacyBridge | SamsungTVWSBridge, @@ -109,6 +112,7 @@ def __init__( self._attr_is_volume_muted: bool = False self._attr_device_class = MediaPlayerDeviceClass.TV self._attr_source_list = list(SOURCES) + self._app_list: dict[str, str] | None = None if self._on_script or self._mac: self._attr_supported_features = SUPPORT_SAMSUNGTV | SUPPORT_TURN_ON @@ -158,12 +162,21 @@ def update(self) -> None: else: self._attr_state = STATE_ON if self._bridge.is_on() else STATE_OFF - def send_key(self, key: str) -> None: + if self._attr_state == STATE_ON and self._app_list is None: + self._app_list = {} # Ensure that we don't update it twice in parallel + self.hass.async_add_job(self._update_app_list) + + def _update_app_list(self) -> None: + self._app_list = self._bridge.get_app_list() + if self._app_list is not None: + self._attr_source_list.extend(self._app_list) + + def send_key(self, key: str, key_type: str | None = None) -> None: """Send a key to the tv and handles exceptions.""" if self._power_off_in_progress() and key != "KEY_POWEROFF": LOGGER.info("TV is powering off, not sending command: %s", key) return - self._bridge.send_key(key) + self._bridge.send_key(key, key_type) def _power_off_in_progress(self) -> bool: return ( @@ -232,6 +245,10 @@ async def async_play_media( self, media_type: str, media_id: str, **kwargs: Any ) -> None: """Support changing a channel.""" + if media_type == MEDIA_TYPE_APP: + await self.hass.async_add_executor_job(self.send_key, media_id, "run_app") + return + if media_type != MEDIA_TYPE_CHANNEL: LOGGER.error("Unsupported media type") return @@ -264,8 +281,12 @@ async def async_turn_on(self) -> None: def select_source(self, source: str) -> None: """Select input source.""" - if source not in SOURCES: - LOGGER.error("Unsupported source") + if self._app_list and source in self._app_list: + self.send_key(self._app_list[source], "run_app") + return + + if source in SOURCES: + self.send_key(SOURCES[source]) return - self.send_key(SOURCES[source]) + LOGGER.error("Unsupported source") diff --git a/tests/components/samsungtv/conftest.py b/tests/components/samsungtv/conftest.py index de554462b42f40..e1cb4f86082168 100644 --- a/tests/components/samsungtv/conftest.py +++ b/tests/components/samsungtv/conftest.py @@ -8,6 +8,8 @@ import homeassistant.util.dt as dt_util +from .const import SAMPLE_APP_LIST + @pytest.fixture(autouse=True) def fake_host_fixture() -> None: @@ -49,6 +51,7 @@ def remotews_fixture() -> Mock: "networkType": "wireless", }, } + remotews.app_list.return_value = SAMPLE_APP_LIST remotews.token = "FAKE_TOKEN" remotews_class.return_value = remotews yield remotews diff --git a/tests/components/samsungtv/const.py b/tests/components/samsungtv/const.py new file mode 100644 index 00000000000000..d56e540e64b325 --- /dev/null +++ b/tests/components/samsungtv/const.py @@ -0,0 +1,24 @@ +"""Constants for the samsungtv tests.""" +SAMPLE_APP_LIST = [ + { + "appId": "111299001912", + "app_type": 2, + "icon": "/opt/share/webappservice/apps_icon/FirstScreen/111299001912/250x250.png", + "is_lock": 0, + "name": "YouTube", + }, + { + "appId": "3201608010191", + "app_type": 2, + "icon": "/opt/share/webappservice/apps_icon/FirstScreen/3201608010191/250x250.png", + "is_lock": 0, + "name": "Deezer", + }, + { + "appId": "3201606009684", + "app_type": 2, + "icon": "/opt/share/webappservice/apps_icon/FirstScreen/3201606009684/250x250.png", + "is_lock": 0, + "name": "Spotify - Music and Podcasts", + }, +] diff --git a/tests/components/samsungtv/test_config_flow.py b/tests/components/samsungtv/test_config_flow.py index bf1587e40dcb28..2aea4b22595b0d 100644 --- a/tests/components/samsungtv/test_config_flow.py +++ b/tests/components/samsungtv/test_config_flow.py @@ -44,6 +44,8 @@ from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component +from .const import SAMPLE_APP_LIST + from tests.common import MockConfigEntry RESULT_ALREADY_CONFIGURED = "already_configured" @@ -817,6 +819,7 @@ async def test_autodetect_websocket(hass: HomeAssistant) -> None: remote = Mock(SamsungTVWS) remote.__enter__ = Mock(return_value=remote) remote.__exit__ = Mock(return_value=False) + remote.app_list.return_value = SAMPLE_APP_LIST remote.rest_device_info.return_value = { "id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4", "device": { @@ -863,6 +866,7 @@ async def test_websocket_no_mac(hass: HomeAssistant) -> None: remote = Mock(SamsungTVWS) remote.__enter__ = Mock(return_value=remote) remote.__exit__ = Mock(return_value=False) + remote.app_list.return_value = SAMPLE_APP_LIST remote.rest_device_info.return_value = { "id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4", "device": { diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py index 76479dea836e1c..55d68453a3814e 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -17,6 +17,7 @@ ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_VOLUME_MUTED, DOMAIN, + MEDIA_TYPE_APP, MEDIA_TYPE_CHANNEL, MEDIA_TYPE_URL, SERVICE_PLAY_MEDIA, @@ -61,6 +62,8 @@ from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from .const import SAMPLE_APP_LIST + from tests.common import MockConfigEntry, async_fire_time_changed ENTITY_ID = f"{DOMAIN}.fake" @@ -160,6 +163,7 @@ async def test_setup_websocket(hass: HomeAssistant) -> None: remote = Mock(SamsungTVWS) remote.__enter__ = Mock(return_value=remote) remote.__exit__ = Mock() + remote.app_list.return_value = SAMPLE_APP_LIST remote.rest_device_info.return_value = { "id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4", "device": { @@ -208,6 +212,7 @@ async def test_setup_websocket_2(hass: HomeAssistant, mock_now: datetime) -> Non remote = Mock(SamsungTVWS) remote.__enter__ = Mock(return_value=remote) remote.__exit__ = Mock() + remote.app_list.return_value = SAMPLE_APP_LIST remote.rest_device_info.return_value = { "id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4", "device": { @@ -860,3 +865,34 @@ async def test_select_source_invalid_source(hass: HomeAssistant) -> None: assert remote.control.call_count == 0 assert remote.close.call_count == 0 assert remote.call_count == 1 + + +async def test_play_media_app(hass: HomeAssistant, remotews: Mock) -> None: + """Test for play_media.""" + await setup_samsungtv(hass, MOCK_CONFIGWS) + + assert await hass.services.async_call( + DOMAIN, + SERVICE_PLAY_MEDIA, + { + ATTR_ENTITY_ID: ENTITY_ID, + ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_APP, + ATTR_MEDIA_CONTENT_ID: "3201608010191", + }, + True, + ) + assert remotews.run_app.call_count == 1 + assert remotews.run_app.call_args_list == [call("3201608010191")] + + +async def test_select_source_app(hass: HomeAssistant, remotews: Mock) -> None: + """Test for select_source.""" + await setup_samsungtv(hass, MOCK_CONFIGWS) + assert await hass.services.async_call( + DOMAIN, + SERVICE_SELECT_SOURCE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_INPUT_SOURCE: "Deezer"}, + True, + ) + assert remotews.run_app.call_count == 1 + assert remotews.run_app.call_args_list == [call("3201608010191")] From 3bfc6cc756b315098752b8c0a8bd66fa70087115 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Fri, 18 Feb 2022 15:55:55 -0600 Subject: [PATCH 0804/1098] Bump SoCo to 0.26.3 (#66834) --- homeassistant/components/sonos/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonos/manifest.json b/homeassistant/components/sonos/manifest.json index 87bfc7f89bc198..4bb8623acb23de 100644 --- a/homeassistant/components/sonos/manifest.json +++ b/homeassistant/components/sonos/manifest.json @@ -3,7 +3,7 @@ "name": "Sonos", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sonos", - "requirements": ["soco==0.26.2"], + "requirements": ["soco==0.26.3"], "dependencies": ["ssdp"], "after_dependencies": ["plex", "spotify", "zeroconf", "media_source"], "zeroconf": ["_sonos._tcp.local."], diff --git a/requirements_all.txt b/requirements_all.txt index 566f72548e3d6f..1d7f31af4881ba 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2228,7 +2228,7 @@ smhi-pkg==1.0.15 snapcast==2.1.3 # homeassistant.components.sonos -soco==0.26.2 +soco==0.26.3 # homeassistant.components.solaredge_local solaredge-local==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fbd8b2e8982561..b721d5d712a39f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1367,7 +1367,7 @@ smarthab==0.21 smhi-pkg==1.0.15 # homeassistant.components.sonos -soco==0.26.2 +soco==0.26.3 # homeassistant.components.solaredge solaredge==0.0.2 From 2ca6ec02901c4d199f1f753a58b7e3044fb186ae Mon Sep 17 00:00:00 2001 From: bvweerd Date: Fri, 18 Feb 2022 23:19:18 +0100 Subject: [PATCH 0805/1098] Fix eq3btsmart setting HVAC modes (#66394) * Partly reverse preset incompatibility It seems like some presets are unsupported by the native climate control of Home Assistant core. This change reverts the previous preset changes causing issues. It worked perfect with simple-thermostat custom lovelace card. * Remove priority of preset above HVAC mode If a preset was available of the given command, the hvac mode change was ignored. This can result in HVAC settings are ignored. By removing the check for a preset, the preset does not supersede the HVAC mode anymore * Revert "Partly reverse preset incompatibility" This reverts commit 10fdc8eef457c369a042c631376ed33f29d0d8bf. --- homeassistant/components/eq3btsmart/climate.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/eq3btsmart/climate.py b/homeassistant/components/eq3btsmart/climate.py index 44329c95eb1c05..d514d54aa66625 100644 --- a/homeassistant/components/eq3btsmart/climate.py +++ b/homeassistant/components/eq3btsmart/climate.py @@ -167,8 +167,6 @@ def hvac_modes(self): def set_hvac_mode(self, hvac_mode): """Set operation mode.""" - if self.preset_mode: - return self._thermostat.mode = HA_TO_EQ_HVAC[hvac_mode] @property From ec67dcb62099a6fd23323c82556019cc4b8d088a Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 18 Feb 2022 23:24:08 +0100 Subject: [PATCH 0806/1098] Add support for validating and serializing selectors (#66565) Co-authored-by: Paulus Schoutsen --- homeassistant/helpers/config_validation.py | 5 + homeassistant/helpers/selector.py | 168 +++++++++--- tests/helpers/test_selector.py | 300 ++++++++++++++------- 3 files changed, 342 insertions(+), 131 deletions(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 05f16bc06adf90..30ce647132e2a7 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -939,6 +939,8 @@ def validator(value: dict[Hashable, Any]) -> dict[Hashable, Any]: def custom_serializer(schema: Any) -> Any: """Serialize additional types for voluptuous_serialize.""" + from . import selector # pylint: disable=import-outside-toplevel + if schema is positive_time_period_dict: return {"type": "positive_time_period_dict"} @@ -951,6 +953,9 @@ def custom_serializer(schema: Any) -> Any: if isinstance(schema, multi_select): return {"type": "multi_select", "options": schema.options} + if isinstance(schema, selector.Selector): + return schema.serialize() + return voluptuous_serialize.UNSUPPORTED diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index f17b610ff23eea..38fe621f96c9d1 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -2,18 +2,22 @@ from __future__ import annotations from collections.abc import Callable +from datetime import time as time_sys from typing import Any, cast import voluptuous as vol from homeassistant.const import CONF_MODE, CONF_UNIT_OF_MEASUREMENT +from homeassistant.core import split_entity_id from homeassistant.util import decorator +from . import config_validation as cv + SELECTORS = decorator.Registry() -def validate_selector(config: Any) -> dict: - """Validate a selector.""" +def _get_selector_class(config: Any) -> type[Selector]: + """Get selector class type.""" if not isinstance(config, dict): raise vol.Invalid("Expected a dictionary") @@ -25,6 +29,26 @@ def validate_selector(config: Any) -> dict: if (selector_class := SELECTORS.get(selector_type)) is None: raise vol.Invalid(f"Unknown selector type {selector_type} found") + return cast(type[Selector], selector_class) + + +def selector(config: Any) -> Selector: + """Instantiate a selector.""" + selector_class = _get_selector_class(config) + selector_type = list(config)[0] + + # Selectors can be empty + if config[selector_type] is None: + return selector_class({selector_type: {}}) + + return selector_class(config) + + +def validate_selector(config: Any) -> dict: + """Validate a selector.""" + selector_class = _get_selector_class(config) + selector_type = list(config)[0] + # Selectors can be empty if config[selector_type] is None: return {selector_type: {}} @@ -38,12 +62,24 @@ class Selector: """Base class for selectors.""" CONFIG_SCHEMA: Callable + config: Any + selector_type: str + + def __init__(self, config: Any) -> None: + """Instantiate a selector.""" + self.config = self.CONFIG_SCHEMA(config[self.selector_type]) + + def serialize(self) -> Any: + """Serialize Selector for voluptuous_serialize.""" + return {"selector": {self.selector_type: self.config}} @SELECTORS.register("entity") class EntitySelector(Selector): """Selector of a single entity.""" + selector_type = "entity" + CONFIG_SCHEMA = vol.Schema( { # Integration that provided the entity @@ -55,11 +91,30 @@ class EntitySelector(Selector): } ) + def __call__(self, data: Any) -> str: + """Validate the passed selection.""" + try: + entity_id = cv.entity_id(data) + domain = split_entity_id(entity_id)[0] + except vol.Invalid: + # Not a valid entity_id, maybe it's an entity entry id + return cv.entity_id_or_uuid(cv.string(data)) + else: + if "domain" in self.config and domain != self.config["domain"]: + raise vol.Invalid( + f"Entity {entity_id} belongs to domain {domain}, " + f"expected {self.config['domain']}" + ) + + return entity_id + @SELECTORS.register("device") class DeviceSelector(Selector): """Selector of a single device.""" + selector_type = "device" + CONFIG_SCHEMA = vol.Schema( { # Integration linked to it with a config entry @@ -73,35 +128,35 @@ class DeviceSelector(Selector): } ) + def __call__(self, data: Any) -> str: + """Validate the passed selection.""" + return cv.string(data) + @SELECTORS.register("area") class AreaSelector(Selector): """Selector of a single area.""" + selector_type = "area" + CONFIG_SCHEMA = vol.Schema( { - vol.Optional("entity"): vol.Schema( - { - vol.Optional("domain"): str, - vol.Optional("device_class"): str, - vol.Optional("integration"): str, - } - ), - vol.Optional("device"): vol.Schema( - { - vol.Optional("integration"): str, - vol.Optional("manufacturer"): str, - vol.Optional("model"): str, - } - ), + vol.Optional("entity"): EntitySelector.CONFIG_SCHEMA, + vol.Optional("device"): DeviceSelector.CONFIG_SCHEMA, } ) + def __call__(self, data: Any) -> str: + """Validate the passed selection.""" + return cv.string(data) + @SELECTORS.register("number") class NumberSelector(Selector): """Selector of a numeric value.""" + selector_type = "number" + CONFIG_SCHEMA = vol.Schema( { vol.Required("min"): vol.Coerce(float), @@ -114,80 +169,131 @@ class NumberSelector(Selector): } ) + def __call__(self, data: Any) -> float: + """Validate the passed selection.""" + value: float = vol.Coerce(float)(data) + + if not self.config["min"] <= value <= self.config["max"]: + raise vol.Invalid(f"Value {value} is too small or too large") + + return value + @SELECTORS.register("addon") class AddonSelector(Selector): """Selector of a add-on.""" + selector_type = "addon" + CONFIG_SCHEMA = vol.Schema({}) + def __call__(self, data: Any) -> str: + """Validate the passed selection.""" + return cv.string(data) + @SELECTORS.register("boolean") class BooleanSelector(Selector): """Selector of a boolean value.""" + selector_type = "boolean" + CONFIG_SCHEMA = vol.Schema({}) + def __call__(self, data: Any) -> bool: + """Validate the passed selection.""" + value: bool = vol.Coerce(bool)(data) + return value + @SELECTORS.register("time") class TimeSelector(Selector): """Selector of a time value.""" + selector_type = "time" + CONFIG_SCHEMA = vol.Schema({}) + def __call__(self, data: Any) -> time_sys: + """Validate the passed selection.""" + return cv.time(data) + @SELECTORS.register("target") class TargetSelector(Selector): """Selector of a target value (area ID, device ID, entity ID etc). - Value should follow cv.ENTITY_SERVICE_FIELDS format. + Value should follow cv.TARGET_SERVICE_FIELDS format. """ + selector_type = "target" + CONFIG_SCHEMA = vol.Schema( { - vol.Optional("entity"): vol.Schema( - { - vol.Optional("domain"): str, - vol.Optional("device_class"): str, - vol.Optional("integration"): str, - } - ), - vol.Optional("device"): vol.Schema( - { - vol.Optional("integration"): str, - vol.Optional("manufacturer"): str, - vol.Optional("model"): str, - } - ), + vol.Optional("entity"): EntitySelector.CONFIG_SCHEMA, + vol.Optional("device"): DeviceSelector.CONFIG_SCHEMA, } ) + TARGET_SELECTION_SCHEMA = vol.Schema(cv.TARGET_SERVICE_FIELDS) + + def __call__(self, data: Any) -> dict[str, list[str]]: + """Validate the passed selection.""" + target: dict[str, list[str]] = self.TARGET_SELECTION_SCHEMA(data) + return target + @SELECTORS.register("action") class ActionSelector(Selector): """Selector of an action sequence (script syntax).""" + selector_type = "action" + CONFIG_SCHEMA = vol.Schema({}) + def __call__(self, data: Any) -> Any: + """Validate the passed selection.""" + return data + @SELECTORS.register("object") class ObjectSelector(Selector): """Selector for an arbitrary object.""" + selector_type = "object" + CONFIG_SCHEMA = vol.Schema({}) + def __call__(self, data: Any) -> Any: + """Validate the passed selection.""" + return data + @SELECTORS.register("text") class StringSelector(Selector): """Selector for a multi-line text string.""" + selector_type = "text" + CONFIG_SCHEMA = vol.Schema({vol.Optional("multiline", default=False): bool}) + def __call__(self, data: Any) -> str: + """Validate the passed selection.""" + text = cv.string(data) + return text + @SELECTORS.register("select") class SelectSelector(Selector): """Selector for an single-choice input select.""" + selector_type = "select" + CONFIG_SCHEMA = vol.Schema( {vol.Required("options"): vol.All([str], vol.Length(min=1))} ) + + def __call__(self, data: Any) -> Any: + """Validate the passed selection.""" + selected_option = vol.In(self.config["options"])(cv.string(data)) + return selected_option diff --git a/tests/helpers/test_selector.py b/tests/helpers/test_selector.py index 23d8200be23c73..edf856d68432b2 100644 --- a/tests/helpers/test_selector.py +++ b/tests/helpers/test_selector.py @@ -2,7 +2,10 @@ import pytest import voluptuous as vol -from homeassistant.helpers import selector +from homeassistant.helpers import config_validation as cv, selector +from homeassistant.util import dt as dt_util + +FAKE_UUID = "a266a680b608c32770e6c45bfe6b8411" @pytest.mark.parametrize( @@ -20,6 +23,8 @@ def test_valid_base_schema(schema): @pytest.mark.parametrize( "schema", ( + None, + "not_a_dict", {}, {"non_existing": {}}, # Two keys @@ -38,173 +43,268 @@ def test_validate_selector(): assert schema == selector.validate_selector(schema) +def _test_selector( + selector_type, schema, valid_selections, invalid_selections, converter=None +): + """Help test a selector.""" + + def default_converter(x): + return x + + if converter is None: + converter = default_converter + + # Validate selector configuration + selector.validate_selector({selector_type: schema}) + + # Use selector in schema and validate + vol_schema = vol.Schema({"selection": selector.selector({selector_type: schema})}) + for selection in valid_selections: + assert vol_schema({"selection": selection}) == { + "selection": converter(selection) + } + for selection in invalid_selections: + with pytest.raises(vol.Invalid): + vol_schema({"selection": selection}) + + # Serialize selector + selector_instance = selector.selector({selector_type: schema}) + assert cv.custom_serializer(selector_instance) == { + "selector": {selector_type: selector_instance.config} + } + + @pytest.mark.parametrize( - "schema", + "schema,valid_selections,invalid_selections", ( - {}, - {"integration": "zha"}, - {"manufacturer": "mock-manuf"}, - {"model": "mock-model"}, - {"manufacturer": "mock-manuf", "model": "mock-model"}, - {"integration": "zha", "manufacturer": "mock-manuf", "model": "mock-model"}, - {"entity": {"device_class": "motion"}}, - { - "integration": "zha", - "manufacturer": "mock-manuf", - "model": "mock-model", - "entity": {"domain": "binary_sensor", "device_class": "motion"}, - }, + (None, ("abc123",), (None,)), + ({}, ("abc123",), (None,)), + ({"integration": "zha"}, ("abc123",), (None,)), + ({"manufacturer": "mock-manuf"}, ("abc123",), (None,)), + ({"model": "mock-model"}, ("abc123",), (None,)), + ({"manufacturer": "mock-manuf", "model": "mock-model"}, ("abc123",), (None,)), + ( + {"integration": "zha", "manufacturer": "mock-manuf", "model": "mock-model"}, + ("abc123",), + (None,), + ), + ({"entity": {"device_class": "motion"}}, ("abc123",), (None,)), + ( + { + "integration": "zha", + "manufacturer": "mock-manuf", + "model": "mock-model", + "entity": {"domain": "binary_sensor", "device_class": "motion"}, + }, + ("abc123",), + (None,), + ), ), ) -def test_device_selector_schema(schema): +def test_device_selector_schema(schema, valid_selections, invalid_selections): """Test device selector.""" - selector.validate_selector({"device": schema}) + _test_selector("device", schema, valid_selections, invalid_selections) @pytest.mark.parametrize( - "schema", + "schema,valid_selections,invalid_selections", ( - {}, - {"integration": "zha"}, - {"domain": "light"}, - {"device_class": "motion"}, - {"integration": "zha", "domain": "light"}, - {"integration": "zha", "domain": "binary_sensor", "device_class": "motion"}, + ({}, ("sensor.abc123", FAKE_UUID), (None, "abc123")), + ({"integration": "zha"}, ("sensor.abc123", FAKE_UUID), (None, "abc123")), + ({"domain": "light"}, ("light.abc123", FAKE_UUID), (None, "sensor.abc123")), + ({"device_class": "motion"}, ("sensor.abc123", FAKE_UUID), (None, "abc123")), + ( + {"integration": "zha", "domain": "light"}, + ("light.abc123", FAKE_UUID), + (None, "sensor.abc123"), + ), + ( + {"integration": "zha", "domain": "binary_sensor", "device_class": "motion"}, + ("binary_sensor.abc123", FAKE_UUID), + (None, "sensor.abc123"), + ), ), ) -def test_entity_selector_schema(schema): +def test_entity_selector_schema(schema, valid_selections, invalid_selections): """Test entity selector.""" - selector.validate_selector({"entity": schema}) + _test_selector("entity", schema, valid_selections, invalid_selections) @pytest.mark.parametrize( - "schema", + "schema,valid_selections,invalid_selections", ( - {}, - {"entity": {}}, - {"entity": {"domain": "light"}}, - {"entity": {"domain": "binary_sensor", "device_class": "motion"}}, - { - "entity": { - "domain": "binary_sensor", - "device_class": "motion", - "integration": "demo", - } - }, - {"device": {"integration": "demo", "model": "mock-model"}}, - { - "entity": {"domain": "binary_sensor", "device_class": "motion"}, - "device": {"integration": "demo", "model": "mock-model"}, - }, + ({}, ("abc123",), (None,)), + ({"entity": {}}, ("abc123",), (None,)), + ({"entity": {"domain": "light"}}, ("abc123",), (None,)), + ( + {"entity": {"domain": "binary_sensor", "device_class": "motion"}}, + ("abc123",), + (None,), + ), + ( + { + "entity": { + "domain": "binary_sensor", + "device_class": "motion", + "integration": "demo", + } + }, + ("abc123",), + (None,), + ), + ( + {"device": {"integration": "demo", "model": "mock-model"}}, + ("abc123",), + (None,), + ), + ( + { + "entity": {"domain": "binary_sensor", "device_class": "motion"}, + "device": {"integration": "demo", "model": "mock-model"}, + }, + ("abc123",), + (None,), + ), ), ) -def test_area_selector_schema(schema): +def test_area_selector_schema(schema, valid_selections, invalid_selections): """Test area selector.""" - selector.validate_selector({"area": schema}) + _test_selector("area", schema, valid_selections, invalid_selections) @pytest.mark.parametrize( - "schema", + "schema,valid_selections,invalid_selections", ( - {"min": 10, "max": 50}, - {"min": -100, "max": 100, "step": 5}, - {"min": -20, "max": -10, "mode": "box"}, - {"min": 0, "max": 100, "unit_of_measurement": "seconds", "mode": "slider"}, - {"min": 10, "max": 1000, "mode": "slider", "step": 0.5}, + ( + {"min": 10, "max": 50}, + ( + 10, + 50, + ), + (9, 51), + ), + ({"min": -100, "max": 100, "step": 5}, (), ()), + ({"min": -20, "max": -10, "mode": "box"}, (), ()), + ( + {"min": 0, "max": 100, "unit_of_measurement": "seconds", "mode": "slider"}, + (), + (), + ), + ({"min": 10, "max": 1000, "mode": "slider", "step": 0.5}, (), ()), ), ) -def test_number_selector_schema(schema): +def test_number_selector_schema(schema, valid_selections, invalid_selections): """Test number selector.""" - selector.validate_selector({"number": schema}) + _test_selector("number", schema, valid_selections, invalid_selections) @pytest.mark.parametrize( - "schema", - ({},), + "schema,valid_selections,invalid_selections", + (({}, ("abc123",), (None,)),), ) -def test_addon_selector_schema(schema): +def test_addon_selector_schema(schema, valid_selections, invalid_selections): """Test add-on selector.""" - selector.validate_selector({"addon": schema}) + _test_selector("addon", schema, valid_selections, invalid_selections) @pytest.mark.parametrize( - "schema", - ({},), + "schema,valid_selections,invalid_selections", + (({}, (1, "one", None), ()),), # Everything can be coarced to bool ) -def test_boolean_selector_schema(schema): +def test_boolean_selector_schema(schema, valid_selections, invalid_selections): """Test boolean selector.""" - selector.validate_selector({"boolean": schema}) + _test_selector("boolean", schema, valid_selections, invalid_selections, bool) @pytest.mark.parametrize( - "schema", - ({},), + "schema,valid_selections,invalid_selections", + (({}, ("00:00:00",), ("blah", None)),), ) -def test_time_selector_schema(schema): +def test_time_selector_schema(schema, valid_selections, invalid_selections): """Test time selector.""" - selector.validate_selector({"time": schema}) + _test_selector( + "time", schema, valid_selections, invalid_selections, dt_util.parse_time + ) @pytest.mark.parametrize( - "schema", + "schema,valid_selections,invalid_selections", ( - {}, - {"entity": {}}, - {"entity": {"domain": "light"}}, - {"entity": {"domain": "binary_sensor", "device_class": "motion"}}, - { - "entity": { - "domain": "binary_sensor", - "device_class": "motion", - "integration": "demo", - } - }, - {"device": {"integration": "demo", "model": "mock-model"}}, - { - "entity": {"domain": "binary_sensor", "device_class": "motion"}, - "device": {"integration": "demo", "model": "mock-model"}, - }, + ({}, ({"entity_id": ["sensor.abc123"]},), ("abc123", None)), + ({"entity": {}}, (), ()), + ({"entity": {"domain": "light"}}, (), ()), + ({"entity": {"domain": "binary_sensor", "device_class": "motion"}}, (), ()), + ( + { + "entity": { + "domain": "binary_sensor", + "device_class": "motion", + "integration": "demo", + } + }, + (), + (), + ), + ({"device": {"integration": "demo", "model": "mock-model"}}, (), ()), + ( + { + "entity": {"domain": "binary_sensor", "device_class": "motion"}, + "device": {"integration": "demo", "model": "mock-model"}, + }, + (), + (), + ), ), ) -def test_target_selector_schema(schema): +def test_target_selector_schema(schema, valid_selections, invalid_selections): """Test target selector.""" - selector.validate_selector({"target": schema}) + _test_selector("target", schema, valid_selections, invalid_selections) @pytest.mark.parametrize( - "schema", - ({},), + "schema,valid_selections,invalid_selections", + (({}, ("abc123",), ()),), ) -def test_action_selector_schema(schema): +def test_action_selector_schema(schema, valid_selections, invalid_selections): """Test action sequence selector.""" - selector.validate_selector({"action": schema}) + _test_selector("action", schema, valid_selections, invalid_selections) @pytest.mark.parametrize( - "schema", - ({},), + "schema,valid_selections,invalid_selections", + (({}, ("abc123",), ()),), ) -def test_object_selector_schema(schema): +def test_object_selector_schema(schema, valid_selections, invalid_selections): """Test object selector.""" - selector.validate_selector({"object": schema}) + _test_selector("object", schema, valid_selections, invalid_selections) @pytest.mark.parametrize( - "schema", - ({}, {"multiline": True}, {"multiline": False}), + "schema,valid_selections,invalid_selections", + ( + ({}, ("abc123",), (None,)), + ({"multiline": True}, (), ()), + ({"multiline": False}, (), ()), + ), ) -def test_text_selector_schema(schema): +def test_text_selector_schema(schema, valid_selections, invalid_selections): """Test text selector.""" - selector.validate_selector({"text": schema}) + _test_selector("text", schema, valid_selections, invalid_selections) @pytest.mark.parametrize( - "schema", - ({"options": ["red", "green", "blue"]},), + "schema,valid_selections,invalid_selections", + ( + ( + {"options": ["red", "green", "blue"]}, + ("red", "green", "blue"), + ("cat", 0, None), + ), + ), ) -def test_select_selector_schema(schema): +def test_select_selector_schema(schema, valid_selections, invalid_selections): """Test select selector.""" - selector.validate_selector({"select": schema}) + _test_selector("select", schema, valid_selections, invalid_selections) @pytest.mark.parametrize( From 1ad023a63f680de052d56c3eb128d19054a18c0a Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 18 Feb 2022 23:29:56 +0100 Subject: [PATCH 0807/1098] Add type ignore error codes [auth] (#66774) --- homeassistant/auth/__init__.py | 2 +- homeassistant/auth/mfa_modules/__init__.py | 4 ++-- homeassistant/auth/mfa_modules/notify.py | 2 +- homeassistant/auth/mfa_modules/totp.py | 8 ++++---- homeassistant/auth/providers/__init__.py | 6 +++--- homeassistant/auth/providers/homeassistant.py | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/homeassistant/auth/__init__.py b/homeassistant/auth/__init__.py index b98dc07deb6ed7..b3f57656dd70f5 100644 --- a/homeassistant/auth/__init__.py +++ b/homeassistant/auth/__init__.py @@ -354,7 +354,7 @@ async def async_remove_credentials(self, credentials: models.Credentials) -> Non if provider is not None and hasattr(provider, "async_will_remove_credentials"): # https://github.com/python/mypy/issues/1424 - await provider.async_will_remove_credentials(credentials) # type: ignore + await provider.async_will_remove_credentials(credentials) # type: ignore[attr-defined] await self._store.async_remove_credentials(credentials) diff --git a/homeassistant/auth/mfa_modules/__init__.py b/homeassistant/auth/mfa_modules/__init__.py index 60f790fa4907b0..bb81d1fb04f94f 100644 --- a/homeassistant/auth/mfa_modules/__init__.py +++ b/homeassistant/auth/mfa_modules/__init__.py @@ -55,7 +55,7 @@ def id(self) -> str: @property def type(self) -> str: """Return type of the module.""" - return self.config[CONF_TYPE] # type: ignore + return self.config[CONF_TYPE] # type: ignore[no-any-return] @property def name(self) -> str: @@ -142,7 +142,7 @@ async def auth_mfa_module_from_config( ) raise - return MULTI_FACTOR_AUTH_MODULES[module_name](hass, config) # type: ignore + return MULTI_FACTOR_AUTH_MODULES[module_name](hass, config) # type: ignore[no-any-return] async def _load_mfa_module(hass: HomeAssistant, module_name: str) -> types.ModuleType: diff --git a/homeassistant/auth/mfa_modules/notify.py b/homeassistant/auth/mfa_modules/notify.py index 71fdbadbb9007a..37b35a5087d9b9 100644 --- a/homeassistant/auth/mfa_modules/notify.py +++ b/homeassistant/auth/mfa_modules/notify.py @@ -251,7 +251,7 @@ async def async_notify_user(self, user_id: str, code: str) -> None: await self.async_notify( code, - notify_setting.notify_service, # type: ignore + notify_setting.notify_service, # type: ignore[arg-type] notify_setting.target, ) diff --git a/homeassistant/auth/mfa_modules/totp.py b/homeassistant/auth/mfa_modules/totp.py index a88aa19d742ec6..bb5fc47469fbf8 100644 --- a/homeassistant/auth/mfa_modules/totp.py +++ b/homeassistant/auth/mfa_modules/totp.py @@ -107,7 +107,7 @@ def _add_ota_secret(self, user_id: str, secret: str | None = None) -> str: ota_secret: str = secret or pyotp.random_base32() - self._users[user_id] = ota_secret # type: ignore + self._users[user_id] = ota_secret # type: ignore[index] return ota_secret async def async_setup_flow(self, user_id: str) -> SetupFlow: @@ -136,7 +136,7 @@ async def async_depose_user(self, user_id: str) -> None: if self._users is None: await self._async_load() - if self._users.pop(user_id, None): # type: ignore + if self._users.pop(user_id, None): # type: ignore[union-attr] await self._async_save() async def async_is_user_setup(self, user_id: str) -> bool: @@ -144,7 +144,7 @@ async def async_is_user_setup(self, user_id: str) -> bool: if self._users is None: await self._async_load() - return user_id in self._users # type: ignore + return user_id in self._users # type: ignore[operator] async def async_validate(self, user_id: str, user_input: dict[str, Any]) -> bool: """Return True if validation passed.""" @@ -161,7 +161,7 @@ def _validate_2fa(self, user_id: str, code: str) -> bool: """Validate two factor authentication code.""" import pyotp # pylint: disable=import-outside-toplevel - if (ota_secret := self._users.get(user_id)) is None: # type: ignore + if (ota_secret := self._users.get(user_id)) is None: # type: ignore[union-attr] # even we cannot find user, we still do verify # to make timing the same as if user was found. pyotp.TOTP(DUMMY_SECRET).verify(code, valid_window=1) diff --git a/homeassistant/auth/providers/__init__.py b/homeassistant/auth/providers/__init__.py index b209f6b2f05a8b..d80d7a5273b430 100644 --- a/homeassistant/auth/providers/__init__.py +++ b/homeassistant/auth/providers/__init__.py @@ -62,7 +62,7 @@ def id(self) -> str | None: @property def type(self) -> str: """Return type of the provider.""" - return self.config[CONF_TYPE] # type: ignore + return self.config[CONF_TYPE] # type: ignore[no-any-return] @property def name(self) -> str: @@ -149,7 +149,7 @@ async def auth_provider_from_config( ) raise - return AUTH_PROVIDERS[provider_name](hass, store, config) # type: ignore + return AUTH_PROVIDERS[provider_name](hass, store, config) # type: ignore[no-any-return] async def load_auth_provider_module( @@ -250,7 +250,7 @@ async def async_step_mfa( auth_module, "async_initialize_login_mfa_step" ): try: - await auth_module.async_initialize_login_mfa_step( # type: ignore + await auth_module.async_initialize_login_mfa_step( # type: ignore[attr-defined] self.user.id ) except HomeAssistantError: diff --git a/homeassistant/auth/providers/homeassistant.py b/homeassistant/auth/providers/homeassistant.py index 4fb12e3e764201..a4797dd8c10549 100644 --- a/homeassistant/auth/providers/homeassistant.py +++ b/homeassistant/auth/providers/homeassistant.py @@ -120,7 +120,7 @@ async def async_load(self) -> None: @property def users(self) -> list[dict[str, str]]: """Return users.""" - return self._data["users"] # type: ignore + return self._data["users"] # type: ignore[index,no-any-return] def validate_login(self, username: str, password: str) -> None: """Validate a username and password. From aa00bd9b965e05c4a5b1e5dbc76c8a2a68d71f38 Mon Sep 17 00:00:00 2001 From: Keilin Bickar Date: Fri, 18 Feb 2022 18:42:35 -0500 Subject: [PATCH 0808/1098] Add SleepIQ device type (#66833) Co-authored-by: J. Nick Koston --- homeassistant/components/sleepiq/entity.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/homeassistant/components/sleepiq/entity.py b/homeassistant/components/sleepiq/entity.py index 424584720574c8..78c2045bcb6a78 100644 --- a/homeassistant/components/sleepiq/entity.py +++ b/homeassistant/components/sleepiq/entity.py @@ -4,6 +4,8 @@ from asyncsleepiq import SleepIQBed, SleepIQSleeper from homeassistant.core import callback +from homeassistant.helpers import device_registry +from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, @@ -32,6 +34,12 @@ def __init__( self._attr_name = f"SleepNumber {bed.name} {sleeper.name} {SENSOR_TYPES[name]}" self._attr_unique_id = f"{bed.id}_{sleeper.name}_{name}" + self._attr_device_info = DeviceInfo( + connections={(device_registry.CONNECTION_NETWORK_MAC, bed.mac_addr)}, + manufacturer="SleepNumber", + name=bed.name, + model=bed.model, + ) @callback def _handle_coordinator_update(self) -> None: From 0269ad47388de6ba9681176c1d8252f418b16819 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 18 Feb 2022 16:04:54 -0800 Subject: [PATCH 0809/1098] Bump hass-nabucasa to 0.53.1 (#66845) --- homeassistant/components/cloud/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index 6431160f696530..e9548c03ba6e4b 100644 --- a/homeassistant/components/cloud/manifest.json +++ b/homeassistant/components/cloud/manifest.json @@ -2,7 +2,7 @@ "domain": "cloud", "name": "Home Assistant Cloud", "documentation": "https://www.home-assistant.io/integrations/cloud", - "requirements": ["hass-nabucasa==0.53.0"], + "requirements": ["hass-nabucasa==0.53.1"], "dependencies": ["http", "webhook"], "after_dependencies": ["google_assistant", "alexa"], "codeowners": ["@home-assistant/cloud"], diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 62e64066708a7c..f542bf408c10e8 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -14,7 +14,7 @@ certifi>=2021.5.30 ciso8601==2.2.0 cryptography==35.0.0 emoji==1.6.3 -hass-nabucasa==0.53.0 +hass-nabucasa==0.53.1 home-assistant-frontend==20220214.0 httpx==0.21.3 ifaddr==0.1.7 diff --git a/requirements_all.txt b/requirements_all.txt index 1d7f31af4881ba..073e3c6e819175 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -803,7 +803,7 @@ habitipy==0.2.0 hangups==0.4.17 # homeassistant.components.cloud -hass-nabucasa==0.53.0 +hass-nabucasa==0.53.1 # homeassistant.components.splunk hass_splunk==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b721d5d712a39f..c65b43a032eeb5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -528,7 +528,7 @@ habitipy==0.2.0 hangups==0.4.17 # homeassistant.components.cloud -hass-nabucasa==0.53.0 +hass-nabucasa==0.53.1 # homeassistant.components.tasmota hatasmota==0.3.1 From 3bf2be1765f7a33fbce06cbabeb2e2115f2f07c7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Feb 2022 18:08:26 -0600 Subject: [PATCH 0810/1098] Startup with an emergency self signed cert if the ssl certificate cannot be loaded (#66707) --- homeassistant/bootstrap.py | 3 + homeassistant/components/http/__init__.py | 122 ++++++++-- tests/components/http/test_init.py | 270 ++++++++++++++++++++-- 3 files changed, 358 insertions(+), 37 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index b1b638f844aa27..986171cbee79e9 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -158,8 +158,11 @@ async def async_setup_hass( safe_mode = True old_config = hass.config + old_logging = hass.data.get(DATA_LOGGING) hass = core.HomeAssistant() + if old_logging: + hass.data[DATA_LOGGING] = old_logging hass.config.skip_pip = old_config.skip_pip hass.config.internal_url = old_config.internal_url hass.config.external_url = old_config.external_url diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 764138ca5f3a6e..a41329a1548917 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -1,22 +1,31 @@ """Support to serve the Home Assistant API as WSGI application.""" from __future__ import annotations +import datetime from ipaddress import IPv4Network, IPv6Network, ip_network import logging import os import ssl +from tempfile import NamedTemporaryFile from typing import Any, Final, Optional, TypedDict, Union, cast from aiohttp import web from aiohttp.typedefs import StrOrURL from aiohttp.web_exceptions import HTTPMovedPermanently, HTTPRedirection +from cryptography import x509 +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.x509.oid import NameOID import voluptuous as vol +from yarl import URL from homeassistant.components.network import async_get_source_ip from homeassistant.const import EVENT_HOMEASSISTANT_STOP, SERVER_PORT from homeassistant.core import Event, HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import storage import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.network import NoURLAvailableError, get_url from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass from homeassistant.setup import async_start_setup, async_when_setup_or_start @@ -231,6 +240,7 @@ def __init__( self.ssl_profile = ssl_profile self.runner: web.AppRunner | None = None self.site: HomeAssistantTCPSite | None = None + self.context: ssl.SSLContext | None = None async def async_initialize( self, @@ -258,6 +268,11 @@ async def async_initialize( setup_cors(self.app, cors_origins) + if self.ssl_certificate: + self.context = await self.hass.async_add_executor_job( + self._create_ssl_context + ) + def register_view(self, view: HomeAssistantView | type[HomeAssistantView]) -> None: """Register a view with the WSGI server. @@ -329,35 +344,100 @@ async def serve_file(request: web.Request) -> web.FileResponse: self.app.router.add_route("GET", url_path, serve_file) ) - async def start(self) -> None: - """Start the aiohttp server.""" - context: ssl.SSLContext | None - if self.ssl_certificate: + def _create_ssl_context(self) -> ssl.SSLContext | None: + context: ssl.SSLContext | None = None + assert self.ssl_certificate is not None + try: + if self.ssl_profile == SSL_INTERMEDIATE: + context = ssl_util.server_context_intermediate() + else: + context = ssl_util.server_context_modern() + context.load_cert_chain(self.ssl_certificate, self.ssl_key) + except OSError as error: + if not self.hass.config.safe_mode: + raise HomeAssistantError( + f"Could not use SSL certificate from {self.ssl_certificate}: {error}" + ) from error + _LOGGER.error( + "Could not read SSL certificate from %s: %s", + self.ssl_certificate, + error, + ) try: - if self.ssl_profile == SSL_INTERMEDIATE: - context = ssl_util.server_context_intermediate() - else: - context = ssl_util.server_context_modern() - await self.hass.async_add_executor_job( - context.load_cert_chain, self.ssl_certificate, self.ssl_key - ) + context = self._create_emergency_ssl_context() except OSError as error: _LOGGER.error( - "Could not read SSL certificate from %s: %s", - self.ssl_certificate, + "Could not create an emergency self signed ssl certificate: %s", error, ) - return + context = None + else: + _LOGGER.critical( + "Home Assistant is running in safe mode with an emergency self signed ssl certificate because the configured SSL certificate was not usable" + ) + return context - if self.ssl_peer_certificate: - context.verify_mode = ssl.CERT_REQUIRED - await self.hass.async_add_executor_job( - context.load_verify_locations, self.ssl_peer_certificate + if self.ssl_peer_certificate: + if context is None: + raise HomeAssistantError( + "Failed to create ssl context, no fallback available because a peer certificate is required." ) - else: - context = None + context.verify_mode = ssl.CERT_REQUIRED + context.load_verify_locations(self.ssl_peer_certificate) + + return context + + def _create_emergency_ssl_context(self) -> ssl.SSLContext: + """Create an emergency ssl certificate so we can still startup.""" + context = ssl_util.server_context_modern() + host: str + try: + host = cast(str, URL(get_url(self.hass, prefer_external=True)).host) + except NoURLAvailableError: + host = "homeassistant.local" + key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + ) + subject = issuer = x509.Name( + [ + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, "Home Assistant Emergency Certificate" + ), + x509.NameAttribute(NameOID.COMMON_NAME, host), + ] + ) + cert = ( + x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(issuer) + .public_key(key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(datetime.datetime.utcnow()) + .not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=30)) + .add_extension( + x509.SubjectAlternativeName([x509.DNSName(host)]), + critical=False, + ) + .sign(key, hashes.SHA256()) + ) + with NamedTemporaryFile() as cert_pem, NamedTemporaryFile() as key_pem: + cert_pem.write(cert.public_bytes(serialization.Encoding.PEM)) + key_pem.write( + key.private_bytes( + serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + ) + ) + cert_pem.flush() + key_pem.flush() + context.load_cert_chain(cert_pem.name, key_pem.name) + return context + async def start(self) -> None: + """Start the aiohttp server.""" # Aiohttp freezes apps after start so that no changes can be made. # However in Home Assistant components can be discovered after boot. # This will now raise a RunTimeError. @@ -369,7 +449,7 @@ async def start(self) -> None: await self.runner.setup() self.site = HomeAssistantTCPSite( - self.runner, self.server_host, self.server_port, ssl_context=context + self.runner, self.server_host, self.server_port, ssl_context=self.context ) try: await self.site.start() diff --git a/tests/components/http/test_init.py b/tests/components/http/test_init.py index c03c8143baddab..79d0a6c47916ec 100644 --- a/tests/components/http/test_init.py +++ b/tests/components/http/test_init.py @@ -3,11 +3,13 @@ from http import HTTPStatus from ipaddress import ip_network import logging +import pathlib from unittest.mock import Mock, patch import pytest import homeassistant.components.http as http +from homeassistant.helpers.network import NoURLAvailableError from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util from homeassistant.util.ssl import server_context_intermediate, server_context_modern @@ -15,6 +17,26 @@ from tests.common import async_fire_time_changed +def _setup_broken_ssl_pem_files(tmpdir): + test_dir = tmpdir.mkdir("test_broken_ssl") + cert_path = pathlib.Path(test_dir) / "cert.pem" + cert_path.write_text("garbage") + key_path = pathlib.Path(test_dir) / "key.pem" + key_path.write_text("garbage") + return cert_path, key_path + + +def _setup_empty_ssl_pem_files(tmpdir): + test_dir = tmpdir.mkdir("test_empty_ssl") + cert_path = pathlib.Path(test_dir) / "cert.pem" + cert_path.write_text("-") + peer_cert_path = pathlib.Path(test_dir) / "peer_cert.pem" + peer_cert_path.write_text("-") + key_path = pathlib.Path(test_dir) / "key.pem" + key_path.write_text("-") + return cert_path, key_path, peer_cert_path + + @pytest.fixture def mock_stack(): """Mock extract stack.""" @@ -118,60 +140,276 @@ async def test_proxy_config_only_trust_proxies(hass): ) -async def test_ssl_profile_defaults_modern(hass): +async def test_ssl_profile_defaults_modern(hass, tmpdir): """Test default ssl profile.""" - assert await async_setup_component(hass, "http", {}) is True - hass.http.ssl_certificate = "bla" + cert_path, key_path, _ = await hass.async_add_executor_job( + _setup_empty_ssl_pem_files, tmpdir + ) with patch("ssl.SSLContext.load_cert_chain"), patch( "homeassistant.util.ssl.server_context_modern", side_effect=server_context_modern, ) as mock_context: + assert ( + await async_setup_component( + hass, + "http", + {"http": {"ssl_certificate": cert_path, "ssl_key": key_path}}, + ) + is True + ) await hass.async_start() await hass.async_block_till_done() assert len(mock_context.mock_calls) == 1 -async def test_ssl_profile_change_intermediate(hass): +async def test_ssl_profile_change_intermediate(hass, tmpdir): """Test setting ssl profile to intermediate.""" - assert ( - await async_setup_component( - hass, "http", {"http": {"ssl_profile": "intermediate"}} - ) - is True - ) - hass.http.ssl_certificate = "bla" + cert_path, key_path, _ = await hass.async_add_executor_job( + _setup_empty_ssl_pem_files, tmpdir + ) with patch("ssl.SSLContext.load_cert_chain"), patch( "homeassistant.util.ssl.server_context_intermediate", side_effect=server_context_intermediate, ) as mock_context: + assert ( + await async_setup_component( + hass, + "http", + { + "http": { + "ssl_profile": "intermediate", + "ssl_certificate": cert_path, + "ssl_key": key_path, + } + }, + ) + is True + ) await hass.async_start() await hass.async_block_till_done() assert len(mock_context.mock_calls) == 1 -async def test_ssl_profile_change_modern(hass): +async def test_ssl_profile_change_modern(hass, tmpdir): """Test setting ssl profile to modern.""" - assert ( - await async_setup_component(hass, "http", {"http": {"ssl_profile": "modern"}}) - is True + + cert_path, key_path, _ = await hass.async_add_executor_job( + _setup_empty_ssl_pem_files, tmpdir ) - hass.http.ssl_certificate = "bla" + with patch("ssl.SSLContext.load_cert_chain"), patch( + "homeassistant.util.ssl.server_context_modern", + side_effect=server_context_modern, + ) as mock_context: + assert ( + await async_setup_component( + hass, + "http", + { + "http": { + "ssl_profile": "modern", + "ssl_certificate": cert_path, + "ssl_key": key_path, + } + }, + ) + is True + ) + await hass.async_start() + await hass.async_block_till_done() + + assert len(mock_context.mock_calls) == 1 + + +async def test_peer_cert(hass, tmpdir): + """Test required peer cert.""" + cert_path, key_path, peer_cert_path = await hass.async_add_executor_job( + _setup_empty_ssl_pem_files, tmpdir + ) with patch("ssl.SSLContext.load_cert_chain"), patch( + "ssl.SSLContext.load_verify_locations" + ) as mock_load_verify_locations, patch( "homeassistant.util.ssl.server_context_modern", side_effect=server_context_modern, ) as mock_context: + assert ( + await async_setup_component( + hass, + "http", + { + "http": { + "ssl_peer_certificate": peer_cert_path, + "ssl_profile": "modern", + "ssl_certificate": cert_path, + "ssl_key": key_path, + } + }, + ) + is True + ) await hass.async_start() await hass.async_block_till_done() assert len(mock_context.mock_calls) == 1 + assert len(mock_load_verify_locations.mock_calls) == 1 + + +async def test_emergency_ssl_certificate_when_invalid(hass, tmpdir, caplog): + """Test http can startup with an emergency self signed cert when the current one is broken.""" + + cert_path, key_path = await hass.async_add_executor_job( + _setup_broken_ssl_pem_files, tmpdir + ) + + hass.config.safe_mode = True + assert ( + await async_setup_component( + hass, + "http", + { + "http": {"ssl_certificate": cert_path, "ssl_key": key_path}, + }, + ) + is True + ) + + await hass.async_start() + await hass.async_block_till_done() + assert ( + "Home Assistant is running in safe mode with an emergency self signed ssl certificate because the configured SSL certificate was not usable" + in caplog.text + ) + + assert hass.http.site is not None + + +async def test_emergency_ssl_certificate_not_used_when_not_safe_mode( + hass, tmpdir, caplog +): + """Test an emergency cert is only used in safe mode.""" + + cert_path, key_path = await hass.async_add_executor_job( + _setup_broken_ssl_pem_files, tmpdir + ) + + assert ( + await async_setup_component( + hass, "http", {"http": {"ssl_certificate": cert_path, "ssl_key": key_path}} + ) + is False + ) + + +async def test_emergency_ssl_certificate_when_invalid_get_url_fails( + hass, tmpdir, caplog +): + """Test http falls back to no ssl when an emergency cert cannot be created when the configured one is broken. + + Ensure we can still start of we cannot determine the external url as well. + """ + cert_path, key_path = await hass.async_add_executor_job( + _setup_broken_ssl_pem_files, tmpdir + ) + hass.config.safe_mode = True + + with patch( + "homeassistant.components.http.get_url", side_effect=NoURLAvailableError + ) as mock_get_url: + assert ( + await async_setup_component( + hass, + "http", + { + "http": {"ssl_certificate": cert_path, "ssl_key": key_path}, + }, + ) + is True + ) + await hass.async_start() + await hass.async_block_till_done() + + assert len(mock_get_url.mock_calls) == 1 + assert ( + "Home Assistant is running in safe mode with an emergency self signed ssl certificate because the configured SSL certificate was not usable" + in caplog.text + ) + + assert hass.http.site is not None + + +async def test_invalid_ssl_and_cannot_create_emergency_cert(hass, tmpdir, caplog): + """Test http falls back to no ssl when an emergency cert cannot be created when the configured one is broken.""" + + cert_path, key_path = await hass.async_add_executor_job( + _setup_broken_ssl_pem_files, tmpdir + ) + hass.config.safe_mode = True + + with patch( + "homeassistant.components.http.x509.CertificateBuilder", side_effect=OSError + ) as mock_builder: + assert ( + await async_setup_component( + hass, + "http", + { + "http": {"ssl_certificate": cert_path, "ssl_key": key_path}, + }, + ) + is True + ) + await hass.async_start() + await hass.async_block_till_done() + assert "Could not create an emergency self signed ssl certificate" in caplog.text + assert len(mock_builder.mock_calls) == 1 + + assert hass.http.site is not None + + +async def test_invalid_ssl_and_cannot_create_emergency_cert_with_ssl_peer_cert( + hass, tmpdir, caplog +): + """Test http falls back to no ssl when an emergency cert cannot be created when the configured one is broken. + + When there is a peer cert verification and we cannot create + an emergency cert (probably will never happen since this means + the system is very broken), we do not want to startup http + as it would allow connections that are not verified by the cert. + """ + + cert_path, key_path = await hass.async_add_executor_job( + _setup_broken_ssl_pem_files, tmpdir + ) + hass.config.safe_mode = True + + with patch( + "homeassistant.components.http.x509.CertificateBuilder", side_effect=OSError + ) as mock_builder: + assert ( + await async_setup_component( + hass, + "http", + { + "http": { + "ssl_certificate": cert_path, + "ssl_key": key_path, + "ssl_peer_certificate": cert_path, + }, + }, + ) + is False + ) + await hass.async_start() + await hass.async_block_till_done() + assert "Could not create an emergency self signed ssl certificate" in caplog.text + assert len(mock_builder.mock_calls) == 1 async def test_cors_defaults(hass): From 1bbc1f5f55de29bef86edbf7e504298c3d51bdc8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 18 Feb 2022 16:11:17 -0800 Subject: [PATCH 0811/1098] Validate in split_entity_id (#66835) --- homeassistant/components/logbook/__init__.py | 4 ++-- homeassistant/core.py | 7 +++++-- tests/helpers/test_entity_registry.py | 18 +++++++++--------- tests/test_core.py | 12 +++++++++++- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py index 1af100397723d2..28b0460ac7a184 100644 --- a/homeassistant/components/logbook/__init__.py +++ b/homeassistant/components/logbook/__init__.py @@ -71,7 +71,7 @@ EMPTY_JSON_OBJECT = "{}" UNIT_OF_MEASUREMENT_JSON = '"unit_of_measurement":' -HA_DOMAIN_ENTITY_ID = f"{HA_DOMAIN}." +HA_DOMAIN_ENTITY_ID = f"{HA_DOMAIN}._" CONFIG_SCHEMA = vol.Schema( {DOMAIN: INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA}, extra=vol.ALLOW_EXTRA @@ -598,7 +598,7 @@ def _keep_event(hass, event, entities_filter): if domain is None: return False - return entities_filter is None or entities_filter(f"{domain}.") + return entities_filter is None or entities_filter(f"{domain}._") def _augment_data_with_context( diff --git a/homeassistant/core.py b/homeassistant/core.py index 38a0bbeb73cc44..27dba3cbc52210 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -141,9 +141,12 @@ class ConfigSource(StrEnum): _LOGGER = logging.getLogger(__name__) -def split_entity_id(entity_id: str) -> list[str]: +def split_entity_id(entity_id: str) -> tuple[str, str]: """Split a state entity ID into domain and object ID.""" - return entity_id.split(".", 1) + domain, _, object_id = entity_id.partition(".") + if not domain or not object_id: + raise ValueError(f"Invalid entity ID {entity_id}") + return domain, object_id VALID_ENTITY_ID = re.compile(r"^(?!.+__)(?!_)[\da-z_]+(? Date: Sat, 19 Feb 2022 00:19:24 +0000 Subject: [PATCH 0812/1098] [ci skip] Translation update --- .../aussie_broadband/translations/bg.json | 3 +- .../aussie_broadband/translations/hu.json | 7 ++++ .../aussie_broadband/translations/id.json | 7 ++++ .../aussie_broadband/translations/ja.json | 7 ++++ .../broadlink/translations/pt-BR.json | 2 +- .../components/deconz/translations/it.json | 2 +- .../components/iss/translations/it.json | 2 +- .../components/iss/translations/ja.json | 9 ++++ .../components/iss/translations/pt-BR.json | 2 +- .../luftdaten/translations/pt-BR.json | 2 +- .../components/mjpeg/translations/bg.json | 38 +++++++++++++++++ .../components/mjpeg/translations/ca.json | 42 +++++++++++++++++++ .../components/mjpeg/translations/de.json | 42 +++++++++++++++++++ .../components/mjpeg/translations/el.json | 11 +++++ .../components/mjpeg/translations/et.json | 42 +++++++++++++++++++ .../components/mjpeg/translations/hu.json | 42 +++++++++++++++++++ .../components/mjpeg/translations/id.json | 42 +++++++++++++++++++ .../components/mjpeg/translations/it.json | 42 +++++++++++++++++++ .../components/mjpeg/translations/ja.json | 42 +++++++++++++++++++ .../components/mjpeg/translations/no.json | 42 +++++++++++++++++++ .../components/mjpeg/translations/pt-BR.json | 42 +++++++++++++++++++ .../components/mjpeg/translations/ru.json | 42 +++++++++++++++++++ .../components/nest/translations/it.json | 2 +- .../netatmo/translations/pt-BR.json | 2 +- .../components/sleepiq/translations/hu.json | 19 +++++++++ .../components/sleepiq/translations/id.json | 19 +++++++++ .../components/sleepiq/translations/ja.json | 19 +++++++++ .../sleepiq/translations/pt-BR.json | 4 +- .../components/wiz/translations/ja.json | 1 + .../components/wiz/translations/pt-BR.json | 2 +- 30 files changed, 569 insertions(+), 11 deletions(-) create mode 100644 homeassistant/components/mjpeg/translations/bg.json create mode 100644 homeassistant/components/mjpeg/translations/ca.json create mode 100644 homeassistant/components/mjpeg/translations/de.json create mode 100644 homeassistant/components/mjpeg/translations/el.json create mode 100644 homeassistant/components/mjpeg/translations/et.json create mode 100644 homeassistant/components/mjpeg/translations/hu.json create mode 100644 homeassistant/components/mjpeg/translations/id.json create mode 100644 homeassistant/components/mjpeg/translations/it.json create mode 100644 homeassistant/components/mjpeg/translations/ja.json create mode 100644 homeassistant/components/mjpeg/translations/no.json create mode 100644 homeassistant/components/mjpeg/translations/pt-BR.json create mode 100644 homeassistant/components/mjpeg/translations/ru.json create mode 100644 homeassistant/components/sleepiq/translations/hu.json create mode 100644 homeassistant/components/sleepiq/translations/id.json create mode 100644 homeassistant/components/sleepiq/translations/ja.json diff --git a/homeassistant/components/aussie_broadband/translations/bg.json b/homeassistant/components/aussie_broadband/translations/bg.json index 74687550820fa7..8c36ef66120fd0 100644 --- a/homeassistant/components/aussie_broadband/translations/bg.json +++ b/homeassistant/components/aussie_broadband/translations/bg.json @@ -22,7 +22,8 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u0430" }, - "description": "\u0410\u043a\u0442\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u043f\u0430\u0440\u043e\u043b\u0430\u0442\u0430 \u0437\u0430 {username}" + "description": "\u0410\u043a\u0442\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u043f\u0430\u0440\u043e\u043b\u0430\u0442\u0430 \u0437\u0430 {username}", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435 \u043d\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f\u0442\u0430" }, "service": { "data": { diff --git a/homeassistant/components/aussie_broadband/translations/hu.json b/homeassistant/components/aussie_broadband/translations/hu.json index 11e42eaaa09114..a8c3543873d282 100644 --- a/homeassistant/components/aussie_broadband/translations/hu.json +++ b/homeassistant/components/aussie_broadband/translations/hu.json @@ -18,6 +18,13 @@ "description": "Jelsz\u00f3 friss\u00edt\u00e9se {username} sz\u00e1m\u00e1ra", "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" }, + "reauth_confirm": { + "data": { + "password": "Jelsz\u00f3" + }, + "description": "Jelsz\u00f3 friss\u00edt\u00e9se {username} sz\u00e1m\u00e1ra", + "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" + }, "service": { "data": { "services": "Szolg\u00e1ltat\u00e1sok" diff --git a/homeassistant/components/aussie_broadband/translations/id.json b/homeassistant/components/aussie_broadband/translations/id.json index ff62d60dafe5f6..18020014268721 100644 --- a/homeassistant/components/aussie_broadband/translations/id.json +++ b/homeassistant/components/aussie_broadband/translations/id.json @@ -18,6 +18,13 @@ "description": "Perbarui kata sandi untuk {username}", "title": "Autentikasi Ulang Integrasi" }, + "reauth_confirm": { + "data": { + "password": "Kata Sandi" + }, + "description": "Perbarui kata sandi untuk {username}", + "title": "Autentikasi Ulang Integrasi" + }, "service": { "data": { "services": "Layanan" diff --git a/homeassistant/components/aussie_broadband/translations/ja.json b/homeassistant/components/aussie_broadband/translations/ja.json index 0fa739c6623e7c..f08e02f73c1400 100644 --- a/homeassistant/components/aussie_broadband/translations/ja.json +++ b/homeassistant/components/aussie_broadband/translations/ja.json @@ -18,6 +18,13 @@ "description": "{username} \u306e\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u66f4\u65b0\u3057\u307e\u3059", "title": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u518d\u8a8d\u8a3c" }, + "reauth_confirm": { + "data": { + "password": "\u30d1\u30b9\u30ef\u30fc\u30c9" + }, + "description": "{username} \u306e\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u66f4\u65b0\u3057\u307e\u3059", + "title": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u518d\u8a8d\u8a3c" + }, "service": { "data": { "services": "\u30b5\u30fc\u30d3\u30b9" diff --git a/homeassistant/components/broadlink/translations/pt-BR.json b/homeassistant/components/broadlink/translations/pt-BR.json index 82257805283411..3872752bb61ca0 100644 --- a/homeassistant/components/broadlink/translations/pt-BR.json +++ b/homeassistant/components/broadlink/translations/pt-BR.json @@ -13,7 +13,7 @@ "invalid_host": "Nome de host ou endere\u00e7o IP inv\u00e1lido", "unknown": "Erro inesperado" }, - "flow_title": "{name} ( {model} em {host} )", + "flow_title": "{name} ({model} em {host})", "step": { "auth": { "title": "Autenticar no dispositivo" diff --git a/homeassistant/components/deconz/translations/it.json b/homeassistant/components/deconz/translations/it.json index 61e5e3b5e96a4e..2c3e42adcc8988 100644 --- a/homeassistant/components/deconz/translations/it.json +++ b/homeassistant/components/deconz/translations/it.json @@ -9,7 +9,7 @@ "updated_instance": "Istanza deCONZ aggiornata con nuovo indirizzo host" }, "error": { - "no_key": "Impossibile ottenere una API key" + "no_key": "Impossibile ottenere una chiave API" }, "flow_title": "{host}", "step": { diff --git a/homeassistant/components/iss/translations/it.json b/homeassistant/components/iss/translations/it.json index c95ea1f5082105..b3ec1329eaeed9 100644 --- a/homeassistant/components/iss/translations/it.json +++ b/homeassistant/components/iss/translations/it.json @@ -9,7 +9,7 @@ "data": { "show_on_map": "Mostrare sulla mappa?" }, - "description": "Vuoi configurare la stazione spaziale internazionale (ISS)?" + "description": "Vuoi configurare la Stazione Spaziale Internazionale (ISS)?" } } }, diff --git a/homeassistant/components/iss/translations/ja.json b/homeassistant/components/iss/translations/ja.json index 6b76fb0e6bcbcd..40178b203f9379 100644 --- a/homeassistant/components/iss/translations/ja.json +++ b/homeassistant/components/iss/translations/ja.json @@ -12,5 +12,14 @@ "description": "\u56fd\u969b\u5b87\u5b99\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u306e\u8a2d\u5b9a\u3092\u3057\u307e\u3059\u304b\uff1f" } } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "\u5730\u56f3\u306b\u8868\u793a" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/iss/translations/pt-BR.json b/homeassistant/components/iss/translations/pt-BR.json index c1a78517fa8941..5e34b2eec1bf45 100644 --- a/homeassistant/components/iss/translations/pt-BR.json +++ b/homeassistant/components/iss/translations/pt-BR.json @@ -17,7 +17,7 @@ "step": { "init": { "data": { - "show_on_map": "Mostrar no mapa" + "show_on_map": "Mostrar no mapa?" } } } diff --git a/homeassistant/components/luftdaten/translations/pt-BR.json b/homeassistant/components/luftdaten/translations/pt-BR.json index 82b1f09735b2f3..b4cdaf000ab2df 100644 --- a/homeassistant/components/luftdaten/translations/pt-BR.json +++ b/homeassistant/components/luftdaten/translations/pt-BR.json @@ -8,7 +8,7 @@ "step": { "user": { "data": { - "show_on_map": "Mostrar no mapa", + "show_on_map": "Mostrar no mapa?", "station_id": "ID do Sensor Luftdaten" }, "title": "Definir Luftdaten" diff --git a/homeassistant/components/mjpeg/translations/bg.json b/homeassistant/components/mjpeg/translations/bg.json new file mode 100644 index 00000000000000..0e88f5081913d5 --- /dev/null +++ b/homeassistant/components/mjpeg/translations/bg.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e" + }, + "error": { + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", + "invalid_auth": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435" + }, + "step": { + "user": { + "data": { + "mjpeg_url": "MJPEG URL", + "name": "\u0418\u043c\u0435", + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + } + } + } + }, + "options": { + "error": { + "already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e", + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", + "invalid_auth": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435" + }, + "step": { + "init": { + "data": { + "mjpeg_url": "MJPEG URL", + "name": "\u0418\u043c\u0435", + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mjpeg/translations/ca.json b/homeassistant/components/mjpeg/translations/ca.json new file mode 100644 index 00000000000000..5d94ca078734a3 --- /dev/null +++ b/homeassistant/components/mjpeg/translations/ca.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" + }, + "step": { + "user": { + "data": { + "mjpeg_url": "URL MJPEG", + "name": "Nom", + "password": "Contrasenya", + "still_image_url": "URL d'imatge fixa", + "username": "Nom d'usuari", + "verify_ssl": "Verifica el certificat SSL" + } + } + } + }, + "options": { + "error": { + "already_configured": "El dispositiu ja est\u00e0 configurat", + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" + }, + "step": { + "init": { + "data": { + "mjpeg_url": "URL MJPEG", + "name": "Nom", + "password": "Contrasenya", + "still_image_url": "URL d'imatge fixa", + "username": "Nom d'usuari", + "verify_ssl": "Verifica el certificat SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mjpeg/translations/de.json b/homeassistant/components/mjpeg/translations/de.json new file mode 100644 index 00000000000000..3023dd5bdf170d --- /dev/null +++ b/homeassistant/components/mjpeg/translations/de.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, + "step": { + "user": { + "data": { + "mjpeg_url": "MJPEG-URL", + "name": "Name", + "password": "Passwort", + "still_image_url": "Standbild-URL", + "username": "Benutzername", + "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" + } + } + } + }, + "options": { + "error": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, + "step": { + "init": { + "data": { + "mjpeg_url": "MJPEG-URL", + "name": "Name", + "password": "Passwort", + "still_image_url": "Standbild-URL", + "username": "Benutzername", + "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mjpeg/translations/el.json b/homeassistant/components/mjpeg/translations/el.json new file mode 100644 index 00000000000000..db0dd06dbbf262 --- /dev/null +++ b/homeassistant/components/mjpeg/translations/el.json @@ -0,0 +1,11 @@ +{ + "options": { + "step": { + "init": { + "data": { + "still_image_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae\u03c2 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1\u03c2" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mjpeg/translations/et.json b/homeassistant/components/mjpeg/translations/et.json new file mode 100644 index 00000000000000..d2aa02fe0f4880 --- /dev/null +++ b/homeassistant/components/mjpeg/translations/et.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Tuvastamine nurjus" + }, + "step": { + "user": { + "data": { + "mjpeg_url": "MJPEG URL", + "name": "Nimi", + "password": "Salas\u00f5na", + "still_image_url": "Pildi URL", + "username": "Kasutajanimi", + "verify_ssl": "Kontrolli SSL serti" + } + } + } + }, + "options": { + "error": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Tuvastamine nurjus" + }, + "step": { + "init": { + "data": { + "mjpeg_url": "MJPEG URL", + "name": "Nimi", + "password": "Salas\u00f5na", + "still_image_url": "Pildi URL", + "username": "Kasutajanimi", + "verify_ssl": "Kontrolli SSL serti" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mjpeg/translations/hu.json b/homeassistant/components/mjpeg/translations/hu.json new file mode 100644 index 00000000000000..0a87f4848872c4 --- /dev/null +++ b/homeassistant/components/mjpeg/translations/hu.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "step": { + "user": { + "data": { + "mjpeg_url": "MJPEG URL-c\u00edme", + "name": "N\u00e9v", + "password": "Jelsz\u00f3", + "still_image_url": "\u00c1ll\u00f3k\u00e9p URL-c\u00edme", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" + } + } + } + }, + "options": { + "error": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "step": { + "init": { + "data": { + "mjpeg_url": "MJPEG URL-c\u00edme", + "name": "N\u00e9v", + "password": "Jelsz\u00f3", + "still_image_url": "\u00c1ll\u00f3k\u00e9p URL-c\u00edme", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mjpeg/translations/id.json b/homeassistant/components/mjpeg/translations/id.json new file mode 100644 index 00000000000000..d38dc06f748841 --- /dev/null +++ b/homeassistant/components/mjpeg/translations/id.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "mjpeg_url": "URL MJPEG", + "name": "Nama", + "password": "Kata Sandi", + "still_image_url": "URL Gambar Diam", + "username": "Nama Pengguna", + "verify_ssl": "Verifikasi sertifikat SSL" + } + } + } + }, + "options": { + "error": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "init": { + "data": { + "mjpeg_url": "URL MJPEG", + "name": "Nama", + "password": "Kata Sandi", + "still_image_url": "URL Gambar Diam", + "username": "Nama Pengguna", + "verify_ssl": "Verifikasi sertifikat SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mjpeg/translations/it.json b/homeassistant/components/mjpeg/translations/it.json new file mode 100644 index 00000000000000..09eab73359b06b --- /dev/null +++ b/homeassistant/components/mjpeg/translations/it.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida" + }, + "step": { + "user": { + "data": { + "mjpeg_url": "URL MJPEG", + "name": "Nome", + "password": "Password", + "still_image_url": "URL dell'immagine fissa", + "username": "Nome utente", + "verify_ssl": "Verifica il certificato SSL" + } + } + } + }, + "options": { + "error": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida" + }, + "step": { + "init": { + "data": { + "mjpeg_url": "URL MJPEG", + "name": "Nome", + "password": "Password", + "still_image_url": "URL dell'immagine fissa", + "username": "Nome utente", + "verify_ssl": "Verifica il certificato SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mjpeg/translations/ja.json b/homeassistant/components/mjpeg/translations/ja.json new file mode 100644 index 00000000000000..622087ad5e50f6 --- /dev/null +++ b/homeassistant/components/mjpeg/translations/ja.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" + }, + "error": { + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c" + }, + "step": { + "user": { + "data": { + "mjpeg_url": "MJPEG URL", + "name": "\u540d\u524d", + "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", + "still_image_url": "\u9759\u6b62\u753b\u306eURL", + "username": "\u30e6\u30fc\u30b6\u30fc\u540d", + "verify_ssl": "SSL\u8a3c\u660e\u66f8\u3092\u78ba\u8a8d\u3059\u308b" + } + } + } + }, + "options": { + "error": { + "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c" + }, + "step": { + "init": { + "data": { + "mjpeg_url": "MJPEG URL", + "name": "\u540d\u524d", + "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", + "still_image_url": "\u9759\u6b62\u753b\u306eURL", + "username": "\u30e6\u30fc\u30b6\u30fc\u540d", + "verify_ssl": "SSL\u8a3c\u660e\u66f8\u3092\u78ba\u8a8d\u3059\u308b" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mjpeg/translations/no.json b/homeassistant/components/mjpeg/translations/no.json new file mode 100644 index 00000000000000..cf03121d761913 --- /dev/null +++ b/homeassistant/components/mjpeg/translations/no.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning" + }, + "step": { + "user": { + "data": { + "mjpeg_url": "URL-adresse for MJPEG", + "name": "Navn", + "password": "Passord", + "still_image_url": "URL-adresse for stillbilde", + "username": "Brukernavn", + "verify_ssl": "Verifisere SSL-sertifikat" + } + } + } + }, + "options": { + "error": { + "already_configured": "Enheten er allerede konfigurert", + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning" + }, + "step": { + "init": { + "data": { + "mjpeg_url": "URL-adresse for MJPEG", + "name": "Navn", + "password": "Passord", + "still_image_url": "URL-adresse for stillbilde", + "username": "Brukernavn", + "verify_ssl": "Verifisere SSL-sertifikat" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mjpeg/translations/pt-BR.json b/homeassistant/components/mjpeg/translations/pt-BR.json new file mode 100644 index 00000000000000..f54828ea2246bc --- /dev/null +++ b/homeassistant/components/mjpeg/translations/pt-BR.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falhou ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "mjpeg_url": "URL MJPEG", + "name": "Nome", + "password": "Senha", + "still_image_url": "URL da imagem est\u00e1tica", + "username": "Nome de usu\u00e1rio", + "verify_ssl": "Verificar certificado SSL" + } + } + } + }, + "options": { + "error": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falhou ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "init": { + "data": { + "mjpeg_url": "URL MJPEG", + "name": "Nome", + "password": "Senha", + "still_image_url": "URL da imagem est\u00e1tica", + "username": "Nome de usu\u00e1rio", + "verify_ssl": "Verificar certificado SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mjpeg/translations/ru.json b/homeassistant/components/mjpeg/translations/ru.json new file mode 100644 index 00000000000000..80e624f3d01d9f --- /dev/null +++ b/homeassistant/components/mjpeg/translations/ru.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." + }, + "step": { + "user": { + "data": { + "mjpeg_url": "URL-\u0430\u0434\u0440\u0435\u0441 MJPEG", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "still_image_url": "URL-\u0430\u0434\u0440\u0435\u0441 \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u043e\u0433\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", + "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" + } + } + } + }, + "options": { + "error": { + "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." + }, + "step": { + "init": { + "data": { + "mjpeg_url": "URL-\u0430\u0434\u0440\u0435\u0441 MJPEG", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "still_image_url": "URL-\u0430\u0434\u0440\u0435\u0441 \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u043e\u0433\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", + "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nest/translations/it.json b/homeassistant/components/nest/translations/it.json index 682ab1728eaa8a..7c92631351ef52 100644 --- a/homeassistant/components/nest/translations/it.json +++ b/homeassistant/components/nest/translations/it.json @@ -34,7 +34,7 @@ "flow_impl": "Provider" }, "description": "Scegli il metodo di autenticazione", - "title": "Fornitore di autenticazione" + "title": "Provider di autenticazione" }, "link": { "data": { diff --git a/homeassistant/components/netatmo/translations/pt-BR.json b/homeassistant/components/netatmo/translations/pt-BR.json index b47c0ea36466d4..32cc610f596937 100644 --- a/homeassistant/components/netatmo/translations/pt-BR.json +++ b/homeassistant/components/netatmo/translations/pt-BR.json @@ -52,7 +52,7 @@ "lon_ne": "Longitude nordeste", "lon_sw": "Longitude sudoeste", "mode": "C\u00e1lculo", - "show_on_map": "Mostrar no mapa" + "show_on_map": "Mostrar no mapa?" }, "description": "Configure um sensor meteorol\u00f3gico p\u00fablico para uma \u00e1rea.", "title": "Sensor meteorol\u00f3gico p\u00fablico Netatmo" diff --git a/homeassistant/components/sleepiq/translations/hu.json b/homeassistant/components/sleepiq/translations/hu.json new file mode 100644 index 00000000000000..c4adcb1bd9ec79 --- /dev/null +++ b/homeassistant/components/sleepiq/translations/hu.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sleepiq/translations/id.json b/homeassistant/components/sleepiq/translations/id.json new file mode 100644 index 00000000000000..a974f44967e4b4 --- /dev/null +++ b/homeassistant/components/sleepiq/translations/id.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sleepiq/translations/ja.json b/homeassistant/components/sleepiq/translations/ja.json new file mode 100644 index 00000000000000..35b6807586d9e7 --- /dev/null +++ b/homeassistant/components/sleepiq/translations/ja.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u30a2\u30ab\u30a6\u30f3\u30c8\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" + }, + "error": { + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c" + }, + "step": { + "user": { + "data": { + "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", + "username": "\u30e6\u30fc\u30b6\u30fc\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sleepiq/translations/pt-BR.json b/homeassistant/components/sleepiq/translations/pt-BR.json index 82754ec08659bb..86cf9781d3a90c 100644 --- a/homeassistant/components/sleepiq/translations/pt-BR.json +++ b/homeassistant/components/sleepiq/translations/pt-BR.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "A conta j\u00e1 est\u00e1 configurada" + "already_configured": "A conta j\u00e1 foi configurada" }, "error": { "cannot_connect": "Falha ao conectar", @@ -11,7 +11,7 @@ "user": { "data": { "password": "Senha", - "username": "Nome de usu\u00e1rio" + "username": "Usu\u00e1rio" } } } diff --git a/homeassistant/components/wiz/translations/ja.json b/homeassistant/components/wiz/translations/ja.json index 21a6adca8545c9..8d9cbd0c05522d 100644 --- a/homeassistant/components/wiz/translations/ja.json +++ b/homeassistant/components/wiz/translations/ja.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", "no_devices_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u4e0a\u306b\u30c7\u30d0\u30a4\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093" }, "error": { diff --git a/homeassistant/components/wiz/translations/pt-BR.json b/homeassistant/components/wiz/translations/pt-BR.json index e0a95b88daf500..ce27ea82abc022 100644 --- a/homeassistant/components/wiz/translations/pt-BR.json +++ b/homeassistant/components/wiz/translations/pt-BR.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", - "cannot_connect": "Falhou ao conectar", + "cannot_connect": "Falha ao conectar", "no_devices_found": "Nenhum dispositivo encontrado na rede" }, "error": { From 4ff1f5c7886b7ac2e4a6b582b4a2df6f380e2e23 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Sat, 19 Feb 2022 05:16:14 -0500 Subject: [PATCH 0813/1098] Create zwave_js ping button at the right time (#66848) * Create zwave_js ping button at the right time * fix tests --- homeassistant/components/zwave_js/__init__.py | 15 +++++++-------- tests/components/zwave_js/test_init.py | 8 ++++---- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 0e1c6445a9f38d..1682edb66a305b 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -261,12 +261,6 @@ async def async_on_node_ready(node: ZwaveNode) -> None: ) ) - # Create a ping button for each device - await async_setup_platform(BUTTON_DOMAIN) - async_dispatcher_send( - hass, f"{DOMAIN}_{entry.entry_id}_add_ping_button_entity", node - ) - # add listeners to handle new values that get added later for event in ("value added", "value updated", "metadata updated"): entry.async_on_unload( @@ -295,13 +289,18 @@ async def async_on_node_ready(node: ZwaveNode) -> None: async def async_on_node_added(node: ZwaveNode) -> None: """Handle node added event.""" - await async_setup_platform(SENSOR_DOMAIN) - # Create a node status sensor for each device + await async_setup_platform(SENSOR_DOMAIN) async_dispatcher_send( hass, f"{DOMAIN}_{entry.entry_id}_add_node_status_sensor", node ) + # Create a ping button for each device + await async_setup_platform(BUTTON_DOMAIN) + async_dispatcher_send( + hass, f"{DOMAIN}_{entry.entry_id}_add_ping_button_entity", node + ) + # we only want to run discovery when the node has reached ready state, # otherwise we'll have all kinds of missing info issues. if node.ready: diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 3780b2654f9487..1003316f1e50da 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -211,8 +211,8 @@ async def test_on_node_added_not_ready( client.driver.receive_event(event) await hass.async_block_till_done() - # the only entity is the node status sensor - assert len(hass.states.async_all()) == 1 + # the only entities are the node status sensor and ping button + assert len(hass.states.async_all()) == 2 device = dev_reg.async_get_device(identifiers={(DOMAIN, device_id)}) assert device @@ -254,8 +254,8 @@ async def test_existing_node_not_ready(hass, zp3111_not_ready, client, integrati assert not device.model assert not device.sw_version - # the only entity is the node status sensor - assert len(hass.states.async_all()) == 1 + # the only entities are the node status sensor and ping button + assert len(hass.states.async_all()) == 2 device = dev_reg.async_get_device(identifiers={(DOMAIN, device_id)}) assert device From 52ca1a3d475fdd96e6998d8c8b0be9a423b4dd06 Mon Sep 17 00:00:00 2001 From: Simon Hansen <67142049+DurgNomis-drol@users.noreply.github.com> Date: Sat, 19 Feb 2022 11:38:10 +0100 Subject: [PATCH 0814/1098] Code enhancements for ISS (#66813) * Code enhancements for ISS * Assert options --- homeassistant/components/iss/__init__.py | 3 +-- homeassistant/components/iss/binary_sensor.py | 1 - tests/components/iss/test_config_flow.py | 20 +++++++++++-------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/iss/__init__.py b/homeassistant/components/iss/__init__.py index 29d6ced184b929..997c3fff2a3a3d 100644 --- a/homeassistant/components/iss/__init__.py +++ b/homeassistant/components/iss/__init__.py @@ -12,7 +12,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up this integration using UI.""" - hass.data.setdefault(DOMAIN, {}) entry.async_on_unload(entry.add_update_listener(update_listener)) @@ -29,6 +28,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -async def update_listener(hass: HomeAssistant, entry: ConfigEntry): +async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/iss/binary_sensor.py b/homeassistant/components/iss/binary_sensor.py index 196bdfd055eb50..12a8a7514b2c40 100644 --- a/homeassistant/components/iss/binary_sensor.py +++ b/homeassistant/components/iss/binary_sensor.py @@ -71,7 +71,6 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the sensor platform.""" - name = entry.title show_on_map = entry.options.get(CONF_SHOW_ON_MAP, False) diff --git a/tests/components/iss/test_config_flow.py b/tests/components/iss/test_config_flow.py index e47d977b96f169..09f7f391b890ab 100644 --- a/tests/components/iss/test_config_flow.py +++ b/tests/components/iss/test_config_flow.py @@ -90,13 +90,17 @@ async def test_options(hass: HomeAssistant): config_entry.add_to_hass(hass) - optionflow = await hass.config_entries.options.async_init(config_entry.entry_id) + with patch("homeassistant.components.iss.async_setup_entry", return_value=True): + assert await hass.config_entries.async_setup(config_entry.entry_id) - configured = await hass.config_entries.options.async_configure( - optionflow["flow_id"], - user_input={ - CONF_SHOW_ON_MAP: True, - }, - ) + optionflow = await hass.config_entries.options.async_init(config_entry.entry_id) + + configured = await hass.config_entries.options.async_configure( + optionflow["flow_id"], + user_input={ + CONF_SHOW_ON_MAP: True, + }, + ) - assert configured.get("type") == "create_entry" + assert configured.get("type") == "create_entry" + assert config_entry.options == {CONF_SHOW_ON_MAP: True} From 8e39ba387d0fcbd8462fff76da4d64890bc4ec57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Sat, 19 Feb 2022 12:22:00 +0100 Subject: [PATCH 0815/1098] Add missing hass argument in async_request_config call (#66864) --- homeassistant/components/sabnzbd/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/sabnzbd/__init__.py b/homeassistant/components/sabnzbd/__init__.py index 3cebd37bf5d4d7..e8da8738b5b41b 100644 --- a/homeassistant/components/sabnzbd/__init__.py +++ b/homeassistant/components/sabnzbd/__init__.py @@ -297,6 +297,7 @@ def success(): async_setup_sabnzbd(hass, sab_api, config, config.get(CONF_NAME, DEFAULT_NAME)) _CONFIGURING[host] = configurator.async_request_config( + hass, DEFAULT_NAME, async_configuration_callback, description="Enter the API Key", From 728dfa258176aa129a2034389535a837f414b014 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 19 Feb 2022 15:18:40 +0100 Subject: [PATCH 0816/1098] Don't run pytest CI jobs on push to forks (#66870) --- .github/workflows/ci.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8492a15a8a342c..b1ff5dca8d1d83 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -718,8 +718,10 @@ jobs: pytest: runs-on: ubuntu-latest - if: github.event.inputs.lint-only != 'true' && ( - needs.changes.outputs.test_full_suite == 'true' || needs.changes.outputs.tests_glob) + if: | + (github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core') + && github.event.inputs.lint-only != 'true' + && (needs.changes.outputs.test_full_suite == 'true' || needs.changes.outputs.tests_glob) needs: - changes - gen-requirements-all From bcec4a5827d283c04a72a79fdd55fb848337617a Mon Sep 17 00:00:00 2001 From: Greg Sheremeta Date: Sat, 19 Feb 2022 09:30:07 -0500 Subject: [PATCH 0817/1098] typo fix networrk --> network (#66878) --- homeassistant/components/zwave_js/services.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/services.yaml b/homeassistant/components/zwave_js/services.yaml index b41a893c7e4e5a..206af776a6107b 100644 --- a/homeassistant/components/zwave_js/services.yaml +++ b/homeassistant/components/zwave_js/services.yaml @@ -176,7 +176,7 @@ multicast_set_value: fields: broadcast: name: Broadcast? - description: Whether command should be broadcast to all devices on the networrk. + description: Whether command should be broadcast to all devices on the network. example: true required: false selector: From 6cd3b45b74d0d8cda2a6514ce8e1bb4d3ffdcbeb Mon Sep 17 00:00:00 2001 From: Anil Daoud Date: Sat, 19 Feb 2022 22:44:18 +0800 Subject: [PATCH 0818/1098] Kaiterra type issue (#66867) --- homeassistant/components/kaiterra/api_data.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/kaiterra/api_data.py b/homeassistant/components/kaiterra/api_data.py index f34ae161c6dd1c..53cc89e708e89a 100644 --- a/homeassistant/components/kaiterra/api_data.py +++ b/homeassistant/components/kaiterra/api_data.py @@ -99,5 +99,7 @@ async def async_update(self) -> None: self.data[self._devices_ids[i]] = device except IndexError as err: _LOGGER.error("Parsing error %s", err) + except TypeError as err: + _LOGGER.error("Type error %s", err) async_dispatcher_send(self._hass, DISPATCHER_KAITERRA) From 3770f4da5c86de78543602622ee4a8f6b239c8cf Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 19 Feb 2022 15:46:22 +0100 Subject: [PATCH 0819/1098] Fix braviatv typing (#66871) --- homeassistant/components/braviatv/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/braviatv/__init__.py b/homeassistant/components/braviatv/__init__.py index 38dbc4f0ebc771..3962e9535207a5 100644 --- a/homeassistant/components/braviatv/__init__.py +++ b/homeassistant/components/braviatv/__init__.py @@ -110,7 +110,7 @@ def __init__( ), ) - def _send_command(self, command: str, repeats: int = 1) -> None: + def _send_command(self, command: Iterable[str], repeats: int = 1) -> None: """Send a command to the TV.""" for _ in range(repeats): for cmd in command: From c46728c2b24e92f2cf881b86b5850de83b94743b Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 19 Feb 2022 15:48:05 +0100 Subject: [PATCH 0820/1098] Fix modbus typing (#66872) --- homeassistant/components/modbus/modbus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/modbus/modbus.py b/homeassistant/components/modbus/modbus.py index 0ea4c57d4d955e..20083bb3d1cefb 100644 --- a/homeassistant/components/modbus/modbus.py +++ b/homeassistant/components/modbus/modbus.py @@ -358,7 +358,7 @@ def _pymodbus_connect(self) -> bool: return True def _pymodbus_call( - self, unit: int, address: int, value: int | list[int], use_call: str + self, unit: int | None, address: int, value: int | list[int], use_call: str ) -> ModbusResponse: """Call sync. pymodbus.""" kwargs = {"unit": unit} if unit else {} From a18d4c51ff3ab9afd13ee08fe8c65e2f9b77f3b1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 19 Feb 2022 09:01:34 -0600 Subject: [PATCH 0821/1098] Ensure dhcp can still discover new devices from device trackers (#66822) Co-authored-by: Martin Hjelmare --- .../components/device_tracker/config_entry.py | 49 +++++++++- .../components/device_tracker/const.py | 2 + homeassistant/components/dhcp/__init__.py | 67 +++++++++++-- .../device_tracker/test_config_entry.py | 93 ++++++++++++++++++- tests/components/dhcp/test_init.py | 33 +++++++ 5 files changed, 232 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/device_tracker/config_entry.py b/homeassistant/components/device_tracker/config_entry.py index 18d769df07f09e..c83ca669d6d911 100644 --- a/homeassistant/components/device_tracker/config_entry.py +++ b/homeassistant/components/device_tracker/config_entry.py @@ -16,12 +16,21 @@ ) from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.entity import DeviceInfo, Entity, EntityCategory from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_platform import EntityPlatform from homeassistant.helpers.typing import StateType -from .const import ATTR_HOST_NAME, ATTR_IP, ATTR_MAC, ATTR_SOURCE_TYPE, DOMAIN, LOGGER +from .const import ( + ATTR_HOST_NAME, + ATTR_IP, + ATTR_MAC, + ATTR_SOURCE_TYPE, + CONNECTED_DEVICE_REGISTERED, + DOMAIN, + LOGGER, +) async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -64,9 +73,33 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return await component.async_unload_entry(entry) +@callback +def _async_connected_device_registered( + hass: HomeAssistant, mac: str, ip_address: str | None, hostname: str | None +) -> None: + """Register a newly seen connected device. + + This is currently used by the dhcp integration + to listen for newly registered connected devices + for discovery. + """ + async_dispatcher_send( + hass, + CONNECTED_DEVICE_REGISTERED, + { + ATTR_IP: ip_address, + ATTR_MAC: mac, + ATTR_HOST_NAME: hostname, + }, + ) + + @callback def _async_register_mac( - hass: HomeAssistant, domain: str, mac: str, unique_id: str + hass: HomeAssistant, + domain: str, + mac: str, + unique_id: str, ) -> None: """Register a mac address with a unique ID.""" data_key = "device_tracker_mac" @@ -297,8 +330,18 @@ def add_to_platform_start( super().add_to_platform_start(hass, platform, parallel_updates) if self.mac_address and self.unique_id: _async_register_mac( - hass, platform.platform_name, self.mac_address, self.unique_id + hass, + platform.platform_name, + self.mac_address, + self.unique_id, ) + if self.is_connected: + _async_connected_device_registered( + hass, + self.mac_address, + self.ip_address, + self.hostname, + ) @callback def find_device_entry(self) -> dr.DeviceEntry | None: diff --git a/homeassistant/components/device_tracker/const.py b/homeassistant/components/device_tracker/const.py index 216255b9cb6cb5..c52241ae51f39b 100644 --- a/homeassistant/components/device_tracker/const.py +++ b/homeassistant/components/device_tracker/const.py @@ -37,3 +37,5 @@ ATTR_SOURCE_TYPE: Final = "source_type" ATTR_CONSIDER_HOME: Final = "consider_home" ATTR_IP: Final = "ip" + +CONNECTED_DEVICE_REGISTERED: Final = "device_tracker_connected_device_registered" diff --git a/homeassistant/components/dhcp/__init__.py b/homeassistant/components/dhcp/__init__.py index ff67f77257b901..a3de0e5170839b 100644 --- a/homeassistant/components/dhcp/__init__.py +++ b/homeassistant/components/dhcp/__init__.py @@ -1,6 +1,7 @@ """The dhcp integration.""" from __future__ import annotations +from abc import abstractmethod from dataclasses import dataclass from datetime import timedelta import fnmatch @@ -25,6 +26,7 @@ ATTR_IP, ATTR_MAC, ATTR_SOURCE_TYPE, + CONNECTED_DEVICE_REGISTERED, DOMAIN as DEVICE_TRACKER_DOMAIN, SOURCE_TYPE_ROUTER, ) @@ -42,6 +44,7 @@ async_get, format_mac, ) +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.event import ( async_track_state_added_domain, async_track_time_interval, @@ -109,16 +112,23 @@ def get(self, name: str, default: Any = None) -> Any: async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the dhcp component.""" + watchers: list[WatcherBase] = [] + address_data: dict[str, dict[str, str]] = {} + integration_matchers = await async_get_dhcp(hass) + + # For the passive classes we need to start listening + # for state changes and connect the dispatchers before + # everything else starts up or we will miss events + for passive_cls in (DeviceTrackerRegisteredWatcher, DeviceTrackerWatcher): + passive_watcher = passive_cls(hass, address_data, integration_matchers) + await passive_watcher.async_start() + watchers.append(passive_watcher) async def _initialize(_): - address_data = {} - integration_matchers = await async_get_dhcp(hass) - watchers = [] - - for cls in (DHCPWatcher, DeviceTrackerWatcher, NetworkWatcher): - watcher = cls(hass, address_data, integration_matchers) - await watcher.async_start() - watchers.append(watcher) + for active_cls in (DHCPWatcher, NetworkWatcher): + active_watcher = active_cls(hass, address_data, integration_matchers) + await active_watcher.async_start() + watchers.append(active_watcher) async def _async_stop(*_): for watcher in watchers: @@ -141,6 +151,14 @@ def __init__(self, hass, address_data, integration_matchers): self._integration_matchers = integration_matchers self._address_data = address_data + @abstractmethod + async def async_stop(self): + """Stop the watcher.""" + + @abstractmethod + async def async_start(self): + """Start the watcher.""" + def process_client(self, ip_address, hostname, mac_address): """Process a client.""" return run_callback_threadsafe( @@ -320,6 +338,39 @@ def _async_process_device_state(self, state: State): self.async_process_client(ip_address, hostname, _format_mac(mac_address)) +class DeviceTrackerRegisteredWatcher(WatcherBase): + """Class to watch data from device tracker registrations.""" + + def __init__(self, hass, address_data, integration_matchers): + """Initialize class.""" + super().__init__(hass, address_data, integration_matchers) + self._unsub = None + + async def async_stop(self): + """Stop watching for device tracker registrations.""" + if self._unsub: + self._unsub() + self._unsub = None + + async def async_start(self): + """Stop watching for device tracker registrations.""" + self._unsub = async_dispatcher_connect( + self.hass, CONNECTED_DEVICE_REGISTERED, self._async_process_device_state + ) + + @callback + def _async_process_device_state(self, data: dict[str, Any]) -> None: + """Process a device tracker state.""" + ip_address = data.get(ATTR_IP) + hostname = data.get(ATTR_HOST_NAME, "") + mac_address = data.get(ATTR_MAC) + + if ip_address is None or mac_address is None: + return + + self.async_process_client(ip_address, hostname, _format_mac(mac_address)) + + class DHCPWatcher(WatcherBase): """Class to watch dhcp requests.""" diff --git a/tests/components/device_tracker/test_config_entry.py b/tests/components/device_tracker/test_config_entry.py index 3c8efad5b05f35..5134123074ef00 100644 --- a/tests/components/device_tracker/test_config_entry.py +++ b/tests/components/device_tracker/test_config_entry.py @@ -1,8 +1,15 @@ """Test Device Tracker config entry things.""" from homeassistant.components.device_tracker import DOMAIN, config_entry as ce +from homeassistant.core import callback from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers.dispatcher import async_dispatcher_connect -from tests.common import MockConfigEntry +from tests.common import ( + MockConfigEntry, + MockEntityPlatform, + MockPlatform, + mock_registry, +) def test_tracker_entity(): @@ -128,3 +135,87 @@ async def test_register_mac(hass): entity_entry_1 = ent_reg.async_get(entity_entry_1.entity_id) assert entity_entry_1.disabled_by is None + + +async def test_connected_device_registered(hass): + """Test dispatch on connected device being registered.""" + + registry = mock_registry(hass) + dispatches = [] + + @callback + def _save_dispatch(msg): + dispatches.append(msg) + + unsub = async_dispatcher_connect( + hass, ce.CONNECTED_DEVICE_REGISTERED, _save_dispatch + ) + + class MockScannerEntity(ce.ScannerEntity): + """Mock a scanner entity.""" + + @property + def ip_address(self) -> str: + return "5.4.3.2" + + @property + def unique_id(self) -> str: + return self.mac_address + + class MockDisconnectedScannerEntity(MockScannerEntity): + """Mock a disconnected scanner entity.""" + + @property + def mac_address(self) -> str: + return "aa:bb:cc:dd:ee:ff" + + @property + def is_connected(self) -> bool: + return True + + @property + def hostname(self) -> str: + return "connected" + + class MockConnectedScannerEntity(MockScannerEntity): + """Mock a disconnected scanner entity.""" + + @property + def mac_address(self) -> str: + return "aa:bb:cc:dd:ee:00" + + @property + def is_connected(self) -> bool: + return False + + @property + def hostname(self) -> str: + return "disconnected" + + async def async_setup_entry(hass, config_entry, async_add_entities): + """Mock setup entry method.""" + async_add_entities( + [MockConnectedScannerEntity(), MockDisconnectedScannerEntity()] + ) + return True + + platform = MockPlatform(async_setup_entry=async_setup_entry) + config_entry = MockConfigEntry(entry_id="super-mock-id") + entity_platform = MockEntityPlatform( + hass, platform_name=config_entry.domain, platform=platform + ) + + assert await entity_platform.async_setup_entry(config_entry) + await hass.async_block_till_done() + full_name = f"{entity_platform.domain}.{config_entry.domain}" + assert full_name in hass.config.components + assert len(hass.states.async_entity_ids()) == 0 # should be disabled + assert len(registry.entities) == 2 + assert ( + registry.entities["test_domain.test_aa_bb_cc_dd_ee_ff"].config_entry_id + == "super-mock-id" + ) + unsub() + assert dispatches == [ + {"ip": "5.4.3.2", "mac": "aa:bb:cc:dd:ee:ff", "host_name": "connected"} + ] diff --git a/tests/components/dhcp/test_init.py b/tests/components/dhcp/test_init.py index 3650ed3298763e..d1b8d72be67702 100644 --- a/tests/components/dhcp/test_init.py +++ b/tests/components/dhcp/test_init.py @@ -16,6 +16,7 @@ ATTR_IP, ATTR_MAC, ATTR_SOURCE_TYPE, + CONNECTED_DEVICE_REGISTERED, SOURCE_TYPE_ROUTER, ) from homeassistant.components.dhcp.const import DOMAIN @@ -26,6 +27,7 @@ STATE_NOT_HOME, ) import homeassistant.helpers.device_registry as dr +from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -630,6 +632,37 @@ async def test_device_tracker_hostname_and_macaddress_exists_before_start(hass): ) +async def test_device_tracker_registered(hass): + """Test matching based on hostname and macaddress when registered.""" + with patch.object(hass.config_entries.flow, "async_init") as mock_init: + device_tracker_watcher = dhcp.DeviceTrackerRegisteredWatcher( + hass, + {}, + [{"domain": "mock-domain", "hostname": "connect", "macaddress": "B8B7F1*"}], + ) + await device_tracker_watcher.async_start() + await hass.async_block_till_done() + async_dispatcher_send( + hass, + CONNECTED_DEVICE_REGISTERED, + {"ip": "192.168.210.56", "mac": "b8b7f16db533", "host_name": "connect"}, + ) + await hass.async_block_till_done() + + assert len(mock_init.mock_calls) == 1 + assert mock_init.mock_calls[0][1][0] == "mock-domain" + assert mock_init.mock_calls[0][2]["context"] == { + "source": config_entries.SOURCE_DHCP + } + assert mock_init.mock_calls[0][2]["data"] == dhcp.DhcpServiceInfo( + ip="192.168.210.56", + hostname="connect", + macaddress="b8b7f16db533", + ) + await device_tracker_watcher.async_stop() + await hass.async_block_till_done() + + async def test_device_tracker_hostname_and_macaddress_after_start(hass): """Test matching based on hostname and macaddress after start.""" From 596644d715ff206db293725c3e9e441a27b1e918 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 19 Feb 2022 16:04:20 +0100 Subject: [PATCH 0822/1098] Fix typo [recorder] (#66879) --- homeassistant/components/recorder/pool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/recorder/pool.py b/homeassistant/components/recorder/pool.py index 9ee89d248cced3..b30237f98da94c 100644 --- a/homeassistant/components/recorder/pool.py +++ b/homeassistant/components/recorder/pool.py @@ -5,7 +5,7 @@ class RecorderPool(StaticPool, NullPool): - """A hybird of NullPool and StaticPool. + """A hybrid of NullPool and StaticPool. When called from the creating thread acts like StaticPool When called from any other thread, acts like NullPool From d76687d672eb3e0b40be42fe71e28ef632bd6c9f Mon Sep 17 00:00:00 2001 From: jan iversen Date: Sat, 19 Feb 2022 17:17:29 +0100 Subject: [PATCH 0823/1098] Add support for INT8 and UINT8 in Modbus (#66889) --- homeassistant/components/modbus/__init__.py | 2 ++ homeassistant/components/modbus/const.py | 2 ++ homeassistant/components/modbus/validators.py | 2 ++ 3 files changed, 6 insertions(+) diff --git a/homeassistant/components/modbus/__init__.py b/homeassistant/components/modbus/__init__.py index cfe0aa370fec31..56edf39311c49d 100644 --- a/homeassistant/components/modbus/__init__.py +++ b/homeassistant/components/modbus/__init__.py @@ -139,9 +139,11 @@ vol.Optional(CONF_COUNT): cv.positive_int, vol.Optional(CONF_DATA_TYPE, default=DataType.INT): vol.In( [ + DataType.INT8, DataType.INT16, DataType.INT32, DataType.INT64, + DataType.UINT8, DataType.UINT16, DataType.UINT32, DataType.UINT64, diff --git a/homeassistant/components/modbus/const.py b/homeassistant/components/modbus/const.py index dccd2eb49906b9..d4f0fa6d9eae9b 100644 --- a/homeassistant/components/modbus/const.py +++ b/homeassistant/components/modbus/const.py @@ -81,9 +81,11 @@ class DataType(str, Enum): INT = "int" # deprecated UINT = "uint" # deprecated STRING = "string" + INT8 = "int8" INT16 = "int16" INT32 = "int32" INT64 = "int64" + UINT8 = "uint8" UINT16 = "uint16" UINT32 = "uint32" UINT64 = "uint64" diff --git a/homeassistant/components/modbus/validators.py b/homeassistant/components/modbus/validators.py index ca0f370b562693..74cd2a498613d2 100644 --- a/homeassistant/components/modbus/validators.py +++ b/homeassistant/components/modbus/validators.py @@ -58,9 +58,11 @@ } ENTRY = namedtuple("ENTRY", ["struct_id", "register_count"]) DEFAULT_STRUCT_FORMAT = { + DataType.INT8: ENTRY("b", 1), DataType.INT16: ENTRY("h", 1), DataType.INT32: ENTRY("i", 2), DataType.INT64: ENTRY("q", 4), + DataType.UINT8: ENTRY("c", 1), DataType.UINT16: ENTRY("H", 1), DataType.UINT32: ENTRY("I", 2), DataType.UINT64: ENTRY("Q", 4), From 6e49b0e12251ea9f32436f484b7dd2c884ab6de9 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 19 Feb 2022 17:19:46 +0100 Subject: [PATCH 0824/1098] Use assignment expressions [K-Z] (#66881) --- homeassistant/components/knx/__init__.py | 3 +-- homeassistant/components/kostal_plenticore/helper.py | 12 +++--------- homeassistant/components/lcn/device_trigger.py | 3 +-- homeassistant/components/lookin/climate.py | 3 +-- homeassistant/components/lutron_caseta/__init__.py | 4 +--- .../components/moehlenhoff_alpha2/climate.py | 3 +-- homeassistant/components/mold_indicator/sensor.py | 3 +-- homeassistant/components/motioneye/media_source.py | 3 +-- homeassistant/components/nest/__init__.py | 3 +-- homeassistant/components/nest/config_flow.py | 3 +-- homeassistant/components/nest/media_source.py | 3 +-- homeassistant/components/open_meteo/__init__.py | 3 +-- homeassistant/components/picnic/coordinator.py | 4 +--- homeassistant/components/synology_dsm/service.py | 3 +-- homeassistant/components/tolo/climate.py | 3 +-- homeassistant/components/venstar/sensor.py | 3 +-- homeassistant/components/webostv/device_trigger.py | 4 +--- homeassistant/components/webostv/helpers.py | 4 +--- homeassistant/components/webostv/media_player.py | 3 +-- .../components/xiaomi_aqara/binary_sensor.py | 3 +-- 20 files changed, 22 insertions(+), 51 deletions(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index 02e54c9dd732e2..5c2d7e3b68c59f 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -225,9 +225,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Load a config entry.""" - conf = hass.data.get(DATA_KNX_CONFIG) # `conf` is None when reloading the integration or no `knx` key in configuration.yaml - if conf is None: + if (conf := hass.data.get(DATA_KNX_CONFIG)) is None: _conf = await async_integration_yaml_config(hass, DOMAIN) if not _conf or DOMAIN not in _conf: _LOGGER.warning( diff --git a/homeassistant/components/kostal_plenticore/helper.py b/homeassistant/components/kostal_plenticore/helper.py index 6dd72412fd8dba..e047c0dafba422 100644 --- a/homeassistant/components/kostal_plenticore/helper.py +++ b/homeassistant/components/kostal_plenticore/helper.py @@ -123,9 +123,7 @@ class DataUpdateCoordinatorMixin: async def async_read_data(self, module_id: str, data_id: str) -> list[str, bool]: """Write settings back to Plenticore.""" - client = self._plenticore.client - - if client is None: + if (client := self._plenticore.client) is None: return False try: @@ -137,9 +135,7 @@ async def async_read_data(self, module_id: str, data_id: str) -> list[str, bool] async def async_write_data(self, module_id: str, value: dict[str, str]) -> bool: """Write settings back to Plenticore.""" - client = self._plenticore.client - - if client is None: + if (client := self._plenticore.client) is None: return False try: @@ -272,9 +268,7 @@ class SelectDataUpdateCoordinator( """Implementation of PlenticoreUpdateCoordinator for select data.""" async def _async_update_data(self) -> dict[str, dict[str, str]]: - client = self._plenticore.client - - if client is None: + if self._plenticore.client is None: return {} _LOGGER.debug("Fetching select %s for %s", self.name, self._fetch) diff --git a/homeassistant/components/lcn/device_trigger.py b/homeassistant/components/lcn/device_trigger.py index b82724f05d6a2a..35575a442b2010 100644 --- a/homeassistant/components/lcn/device_trigger.py +++ b/homeassistant/components/lcn/device_trigger.py @@ -56,8 +56,7 @@ async def async_get_triggers( ) -> list[dict[str, Any]]: """List device triggers for LCN devices.""" device_registry = dr.async_get(hass) - device = device_registry.async_get(device_id) - if device is None: + if (device := device_registry.async_get(device_id)) is None: return [] identifier = next(iter(device.identifiers)) diff --git a/homeassistant/components/lookin/climate.py b/homeassistant/components/lookin/climate.py index e661c14a15135b..79cac79cb1743b 100644 --- a/homeassistant/components/lookin/climate.py +++ b/homeassistant/components/lookin/climate.py @@ -152,8 +152,7 @@ async def async_set_temperature(self, **kwargs: Any) -> None: # an educated guess. # meteo_data: MeteoSensor = self._meteo_coordinator.data - current_temp = meteo_data.temperature - if not current_temp: + if not (current_temp := meteo_data.temperature): self._climate.hvac_mode = lookin_index.index(HVAC_MODE_AUTO) elif current_temp >= self._climate.temp_celsius: self._climate.hvac_mode = lookin_index.index(HVAC_MODE_COOL) diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index 0408f547f25460..ebd9e041332212 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -221,9 +221,7 @@ def _async_subscribe_pico_remote_events( @callback def _async_button_event(button_id, event_type): - device = button_devices_by_id.get(button_id) - - if not device: + if not (device := button_devices_by_id.get(button_id)): return if event_type == BUTTON_STATUS_PRESSED: diff --git a/homeassistant/components/moehlenhoff_alpha2/climate.py b/homeassistant/components/moehlenhoff_alpha2/climate.py index da536c4bd06534..d99eb0e4c8c6a2 100644 --- a/homeassistant/components/moehlenhoff_alpha2/climate.py +++ b/homeassistant/components/moehlenhoff_alpha2/climate.py @@ -101,8 +101,7 @@ def target_temperature(self) -> float: async def async_set_temperature(self, **kwargs) -> None: """Set new target temperatures.""" - target_temperature = kwargs.get(ATTR_TEMPERATURE) - if target_temperature is None: + if (target_temperature := kwargs.get(ATTR_TEMPERATURE)) is None: return await self.coordinator.async_set_target_temperature( diff --git a/homeassistant/components/mold_indicator/sensor.py b/homeassistant/components/mold_indicator/sensor.py index fe8a7c2431d301..bbd609f5fb8a3b 100644 --- a/homeassistant/components/mold_indicator/sensor.py +++ b/homeassistant/components/mold_indicator/sensor.py @@ -205,9 +205,8 @@ def _update_temp_sensor(state): return None unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) - temp = util.convert(state.state, float) - if temp is None: + if (temp := util.convert(state.state, float)) is None: _LOGGER.error( "Unable to parse temperature sensor %s with state: %s", state.entity_id, diff --git a/homeassistant/components/motioneye/media_source.py b/homeassistant/components/motioneye/media_source.py index 8c7b86ca173219..915cc30897ec2f 100644 --- a/homeassistant/components/motioneye/media_source.py +++ b/homeassistant/components/motioneye/media_source.py @@ -134,8 +134,7 @@ def _get_config_or_raise(self, config_id: str) -> ConfigEntry: def _get_device_or_raise(self, device_id: str) -> dr.DeviceEntry: """Get a config entry from a URL.""" device_registry = dr.async_get(self.hass) - device = device_registry.async_get(device_id) - if not device: + if not (device := device_registry.async_get(device_id)): raise MediaSourceError(f"Unable to find device with id: {device_id}") return device diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py index 1083b80ac478b6..2a5f3850fe44f6 100644 --- a/homeassistant/components/nest/__init__.py +++ b/homeassistant/components/nest/__init__.py @@ -356,8 +356,7 @@ async def load_media(self, nest_device: Device, event_token: str) -> Media | Non async def handle_media(self, media: Media) -> web.StreamResponse: """Start a GET request.""" contents = media.contents - content_type = media.content_type - if content_type == "image/jpeg": + if (content_type := media.content_type) == "image/jpeg": image = Image(media.event_image_type.content_type, contents) contents = img_util.scale_jpeg_camera_image( image, THUMBNAIL_SIZE_PX, THUMBNAIL_SIZE_PX diff --git a/homeassistant/components/nest/config_flow.py b/homeassistant/components/nest/config_flow.py index aac8c263ef13f9..b257c7b51bb947 100644 --- a/homeassistant/components/nest/config_flow.py +++ b/homeassistant/components/nest/config_flow.py @@ -319,8 +319,7 @@ async def async_step_pubsub( if user_input is not None and not errors: # Create the subscriber id and/or verify it already exists. Note that # the existing id is used, and create call below is idempotent - subscriber_id = data.get(CONF_SUBSCRIBER_ID, "") - if not subscriber_id: + if not (subscriber_id := data.get(CONF_SUBSCRIBER_ID, "")): subscriber_id = _generate_subscription_id(cloud_project_id) _LOGGER.debug("Creating subscriber id '%s'", subscriber_id) # Create a placeholder ConfigEntry to use since with the auth we've already created. diff --git a/homeassistant/components/nest/media_source.py b/homeassistant/components/nest/media_source.py index 2676929f2ded18..af8a4af8ba5188 100644 --- a/homeassistant/components/nest/media_source.py +++ b/homeassistant/components/nest/media_source.py @@ -134,8 +134,7 @@ async def async_load(self) -> dict | None: """Load data.""" if self._data is None: self._devices = await self._get_devices() - data = await self._store.async_load() - if data is None: + if (data := await self._store.async_load()) is None: _LOGGER.debug("Loaded empty event store") self._data = {} elif isinstance(data, dict): diff --git a/homeassistant/components/open_meteo/__init__.py b/homeassistant/components/open_meteo/__init__.py index 653ccff69804eb..de42d19d8c9590 100644 --- a/homeassistant/components/open_meteo/__init__.py +++ b/homeassistant/components/open_meteo/__init__.py @@ -28,8 +28,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: open_meteo = OpenMeteo(session=session) async def async_update_forecast() -> Forecast: - zone = hass.states.get(entry.data[CONF_ZONE]) - if zone is None: + if (zone := hass.states.get(entry.data[CONF_ZONE])) is None: raise UpdateFailed(f"Zone '{entry.data[CONF_ZONE]}' not found") try: diff --git a/homeassistant/components/picnic/coordinator.py b/homeassistant/components/picnic/coordinator.py index 71a6559975c19f..24f3086134f869 100644 --- a/homeassistant/components/picnic/coordinator.py +++ b/homeassistant/components/picnic/coordinator.py @@ -59,9 +59,7 @@ async def _async_update_data(self) -> dict: def fetch_data(self): """Fetch the data from the Picnic API and return a flat dict with only needed sensor data.""" # Fetch from the API and pre-process the data - cart = self.picnic_api_client.get_cart() - - if not cart: + if not (cart := self.picnic_api_client.get_cart()): raise UpdateFailed("API response doesn't contain expected data.") last_order = self._get_last_order() diff --git a/homeassistant/components/synology_dsm/service.py b/homeassistant/components/synology_dsm/service.py index a7a336e0c1b07b..130ad110b46258 100644 --- a/homeassistant/components/synology_dsm/service.py +++ b/homeassistant/components/synology_dsm/service.py @@ -45,8 +45,7 @@ async def service_handler(call: ServiceCall) -> None: return if call.service in [SERVICE_REBOOT, SERVICE_SHUTDOWN]: - dsm_device = hass.data[DOMAIN].get(serial) - if not dsm_device: + if not (dsm_device := hass.data[DOMAIN].get(serial)): LOGGER.error("DSM with specified serial %s not found", serial) return LOGGER.debug("%s DSM with serial %s", call.service, serial) diff --git a/homeassistant/components/tolo/climate.py b/homeassistant/components/tolo/climate.py index 659dfcbda16818..4c02cdb74dce08 100644 --- a/homeassistant/components/tolo/climate.py +++ b/homeassistant/components/tolo/climate.py @@ -144,8 +144,7 @@ def set_humidity(self, humidity: float) -> None: def set_temperature(self, **kwargs: Any) -> None: """Set desired target temperature.""" - temperature = kwargs.get(ATTR_TEMPERATURE) - if temperature is None: + if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None: return self.coordinator.client.set_target_temperature(round(temperature)) diff --git a/homeassistant/components/venstar/sensor.py b/homeassistant/components/venstar/sensor.py index 68d5bad27d6660..824774f3e31ae2 100644 --- a/homeassistant/components/venstar/sensor.py +++ b/homeassistant/components/venstar/sensor.py @@ -75,8 +75,7 @@ async def async_setup_entry( coordinator = hass.data[DOMAIN][config_entry.entry_id] entities: list[Entity] = [] - sensors = coordinator.client.get_sensor_list() - if not sensors: + if not (sensors := coordinator.client.get_sensor_list()): return for sensor_name in sensors: diff --git a/homeassistant/components/webostv/device_trigger.py b/homeassistant/components/webostv/device_trigger.py index 47cdf974cc76e6..feb5bae98fe68b 100644 --- a/homeassistant/components/webostv/device_trigger.py +++ b/homeassistant/components/webostv/device_trigger.py @@ -79,9 +79,7 @@ async def async_attach_trigger( automation_info: AutomationTriggerInfo, ) -> CALLBACK_TYPE | None: """Attach a trigger.""" - trigger_type = config[CONF_TYPE] - - if trigger_type == TURN_ON_PLATFORM_TYPE: + if (trigger_type := config[CONF_TYPE]) == TURN_ON_PLATFORM_TYPE: trigger_config = { CONF_PLATFORM: trigger_type, CONF_DEVICE_ID: config[CONF_DEVICE_ID], diff --git a/homeassistant/components/webostv/helpers.py b/homeassistant/components/webostv/helpers.py index 70a253d5cebd84..0ee3805f42f31e 100644 --- a/homeassistant/components/webostv/helpers.py +++ b/homeassistant/components/webostv/helpers.py @@ -20,9 +20,7 @@ def async_get_device_entry_by_device_id( Raises ValueError if device ID is invalid. """ device_reg = dr.async_get(hass) - device = device_reg.async_get(device_id) - - if device is None: + if (device := device_reg.async_get(device_id)) is None: raise ValueError(f"Device {device_id} is not a valid {DOMAIN} device.") return device diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index 95e0b059538794..67125c45ef5e08 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -248,8 +248,7 @@ def _update_states(self) -> None: if maj_v and min_v: self._attr_device_info["sw_version"] = f"{maj_v}.{min_v}" - model = self._client.system_info.get("modelName") - if model: + if model := self._client.system_info.get("modelName"): self._attr_device_info["model"] = model self._attr_extra_state_attributes = {} diff --git a/homeassistant/components/xiaomi_aqara/binary_sensor.py b/homeassistant/components/xiaomi_aqara/binary_sensor.py index ae4059728fe6b6..0a21bb37d44f0c 100644 --- a/homeassistant/components/xiaomi_aqara/binary_sensor.py +++ b/homeassistant/components/xiaomi_aqara/binary_sensor.py @@ -333,8 +333,7 @@ def extra_state_attributes(self): async def async_added_to_hass(self) -> None: """Handle entity which will be added.""" await super().async_added_to_hass() - state = await self.async_get_last_state() - if state is None: + if (state := await self.async_get_last_state()) is None: return self._state = state.state == "on" From 4f20a8023b51ad8f66fc1c4332e00437d23e7750 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 19 Feb 2022 17:21:26 +0100 Subject: [PATCH 0825/1098] Use assignment expressions [A-I] (#66880) --- homeassistant/components/adax/climate.py | 3 +-- homeassistant/components/androidtv/__init__.py | 3 +-- .../components/aurora_abb_powerone/aurora_device.py | 3 +-- homeassistant/components/balboa/climate.py | 3 +-- homeassistant/components/broadlink/switch.py | 3 +-- homeassistant/components/edl21/sensor.py | 4 +--- homeassistant/components/flux_led/__init__.py | 3 +-- homeassistant/components/flux_led/config_flow.py | 3 +-- homeassistant/components/flux_led/discovery.py | 3 +-- homeassistant/components/fritz/switch.py | 4 +--- homeassistant/components/heos/__init__.py | 3 +-- homeassistant/components/hive/alarm_control_panel.py | 3 +-- homeassistant/components/homekit/util.py | 3 +-- .../components/homekit_controller/diagnostics.py | 3 +-- homeassistant/components/homematic/sensor.py | 3 +-- homeassistant/components/hue/device_trigger.py | 9 +++------ homeassistant/components/hue/migration.py | 3 +-- homeassistant/components/iaqualink/__init__.py | 4 +--- 18 files changed, 20 insertions(+), 43 deletions(-) diff --git a/homeassistant/components/adax/climate.py b/homeassistant/components/adax/climate.py index 48cbc9b270c76d..ea8e35547469fc 100644 --- a/homeassistant/components/adax/climate.py +++ b/homeassistant/components/adax/climate.py @@ -146,8 +146,7 @@ def __init__(self, adax_data_handler, unique_id): async def async_set_temperature(self, **kwargs): """Set new target temperature.""" - temperature = kwargs.get(ATTR_TEMPERATURE) - if temperature is None: + if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None: return await self._adax_data_handler.set_target_temperature(temperature) diff --git a/homeassistant/components/androidtv/__init__.py b/homeassistant/components/androidtv/__init__.py index 9b9683856028d4..157b618a2641dc 100644 --- a/homeassistant/components/androidtv/__init__.py +++ b/homeassistant/components/androidtv/__init__.py @@ -125,8 +125,7 @@ def _migrate_aftv_entity(hass, aftv, entry_unique_id): # entity already exist, nothing to do return - old_unique_id = aftv.device_properties.get(PROP_SERIALNO) - if not old_unique_id: + if not (old_unique_id := aftv.device_properties.get(PROP_SERIALNO)): # serial no not found, exit return diff --git a/homeassistant/components/aurora_abb_powerone/aurora_device.py b/homeassistant/components/aurora_abb_powerone/aurora_device.py index d9cfb7442314e7..5a524851bdfbbc 100644 --- a/homeassistant/components/aurora_abb_powerone/aurora_device.py +++ b/homeassistant/components/aurora_abb_powerone/aurora_device.py @@ -35,8 +35,7 @@ def __init__(self, client: AuroraSerialClient, data: Mapping[str, Any]) -> None: @property def unique_id(self) -> str | None: """Return the unique id for this device.""" - serial = self._data.get(ATTR_SERIAL_NUMBER) - if serial is None: + if (serial := self._data.get(ATTR_SERIAL_NUMBER)) is None: return None return f"{serial}_{self.entity_description.key}" diff --git a/homeassistant/components/balboa/climate.py b/homeassistant/components/balboa/climate.py index 4145ea8d80712e..81016bbeb33962 100644 --- a/homeassistant/components/balboa/climate.py +++ b/homeassistant/components/balboa/climate.py @@ -99,8 +99,7 @@ def hvac_mode(self) -> str: @property def hvac_action(self) -> str: """Return the current operation mode.""" - state = self._client.get_heatstate() - if state >= self._client.ON: + if self._client.get_heatstate() >= self._client.ON: return CURRENT_HVAC_HEAT return CURRENT_HVAC_IDLE diff --git a/homeassistant/components/broadlink/switch.py b/homeassistant/components/broadlink/switch.py index aa295fd0d99a89..6a015748bd0edb 100644 --- a/homeassistant/components/broadlink/switch.py +++ b/homeassistant/components/broadlink/switch.py @@ -78,9 +78,8 @@ async def async_setup_platform( """ mac_addr = config[CONF_MAC] host = config.get(CONF_HOST) - switches = config.get(CONF_SWITCHES) - if switches: + if switches := config.get(CONF_SWITCHES): platform_data = hass.data[DOMAIN].platforms.setdefault(Platform.SWITCH, {}) platform_data.setdefault(mac_addr, []).extend(switches) diff --git a/homeassistant/components/edl21/sensor.py b/homeassistant/components/edl21/sensor.py index f96f9d828bb175..278ac004121cff 100644 --- a/homeassistant/components/edl21/sensor.py +++ b/homeassistant/components/edl21/sensor.py @@ -450,9 +450,7 @@ def extra_state_attributes(self): @property def native_unit_of_measurement(self): """Return the unit of measurement.""" - unit = self._telegram.get("unit") - - if unit is None: + if (unit := self._telegram.get("unit")) is None: return None return SENSOR_UNIT_MAPPING[unit] diff --git a/homeassistant/components/flux_led/__init__.py b/homeassistant/components/flux_led/__init__.py index ff1962aed1bd45..997f053aa3c109 100644 --- a/homeassistant/components/flux_led/__init__.py +++ b/homeassistant/components/flux_led/__init__.py @@ -90,8 +90,7 @@ async def _async_discovery(*_: Any) -> None: async def _async_migrate_unique_ids(hass: HomeAssistant, entry: ConfigEntry) -> None: """Migrate entities when the mac address gets discovered.""" - unique_id = entry.unique_id - if not unique_id: + if not (unique_id := entry.unique_id): return entry_id = entry.entry_id diff --git a/homeassistant/components/flux_led/config_flow.py b/homeassistant/components/flux_led/config_flow.py index ee8da5bd66a75e..5bdd18d1dbdf08 100644 --- a/homeassistant/components/flux_led/config_flow.py +++ b/homeassistant/components/flux_led/config_flow.py @@ -165,8 +165,7 @@ async def async_step_user( except FLUX_LED_EXCEPTIONS: errors["base"] = "cannot_connect" else: - mac_address = device[ATTR_ID] - if mac_address is not None: + if (mac_address := device[ATTR_ID]) is not None: await self.async_set_unique_id( dr.format_mac(mac_address), raise_on_progress=False ) diff --git a/homeassistant/components/flux_led/discovery.py b/homeassistant/components/flux_led/discovery.py index cd0d9424ead6b9..62b80243c8b850 100644 --- a/homeassistant/components/flux_led/discovery.py +++ b/homeassistant/components/flux_led/discovery.py @@ -82,8 +82,7 @@ def async_build_cached_discovery(entry: ConfigEntry) -> FluxLEDDiscovery: @callback def async_name_from_discovery(device: FluxLEDDiscovery) -> str: """Convert a flux_led discovery to a human readable name.""" - mac_address = device[ATTR_ID] - if mac_address is None: + if (mac_address := device[ATTR_ID]) is None: return device[ATTR_IPADDR] short_mac = mac_address[-6:] if device[ATTR_MODEL_DESCRIPTION]: diff --git a/homeassistant/components/fritz/switch.py b/homeassistant/components/fritz/switch.py index 07eb2eb437f8c4..3b01ce618b863f 100644 --- a/homeassistant/components/fritz/switch.py +++ b/homeassistant/components/fritz/switch.py @@ -60,9 +60,7 @@ def deflection_entities_list( _LOGGER.debug("The FRITZ!Box has no %s options", SWITCH_TYPE_DEFLECTION) return [] - deflection_list = avm_wrapper.get_ontel_deflections() - - if not deflection_list: + if not (deflection_list := avm_wrapper.get_ontel_deflections()): return [] items = xmltodict.parse(deflection_list["NewDeflectionList"])["List"]["Item"] diff --git a/homeassistant/components/heos/__init__.py b/homeassistant/components/heos/__init__.py index bbe611c1db9de9..dbd66e28307bd5 100644 --- a/homeassistant/components/heos/__init__.py +++ b/homeassistant/components/heos/__init__.py @@ -331,8 +331,7 @@ async def async_update_groups(self, event, data=None): heos_const.EVENT_CONNECTED, SIGNAL_HEOS_PLAYER_ADDED, ): - groups = await self.async_get_group_membership() - if groups: + if groups := await self.async_get_group_membership(): self._group_membership = groups _LOGGER.debug("Groups updated due to change event") # Let players know to update diff --git a/homeassistant/components/hive/alarm_control_panel.py b/homeassistant/components/hive/alarm_control_panel.py index 4f0520eaa72f6f..4a0ad577f90f88 100644 --- a/homeassistant/components/hive/alarm_control_panel.py +++ b/homeassistant/components/hive/alarm_control_panel.py @@ -36,8 +36,7 @@ async def async_setup_entry( """Set up Hive thermostat based on a config entry.""" hive = hass.data[DOMAIN][entry.entry_id] - devices = hive.session.deviceList.get("alarm_control_panel") - if devices: + if devices := hive.session.deviceList.get("alarm_control_panel"): async_add_entities( [HiveAlarmControlPanelEntity(hive, dev) for dev in devices], True ) diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index 3165280a37b7bb..8c64b9b04432d0 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -424,8 +424,7 @@ def format_version(version): """Extract the version string in a format homekit can consume.""" split_ver = str(version).replace("-", ".") num_only = NUMBERS_ONLY_RE.sub("", split_ver) - match = VERSION_RE.search(num_only) - if match: + if match := VERSION_RE.search(num_only): return match.group(0) return None diff --git a/homeassistant/components/homekit_controller/diagnostics.py b/homeassistant/components/homekit_controller/diagnostics.py index fd404636a220af..f83ce7604cf746 100644 --- a/homeassistant/components/homekit_controller/diagnostics.py +++ b/homeassistant/components/homekit_controller/diagnostics.py @@ -122,8 +122,7 @@ def _async_get_diagnostics( devices = data["devices"] = [] for device_id in connection.devices.values(): - device = device_registry.async_get(device_id) - if not device: + if not (device := device_registry.async_get(device_id)): continue devices.append(_async_get_diagnostics_for_device(hass, device)) diff --git a/homeassistant/components/homematic/sensor.py b/homeassistant/components/homematic/sensor.py index c8dc86c334832f..456a10b7630207 100644 --- a/homeassistant/components/homematic/sensor.py +++ b/homeassistant/components/homematic/sensor.py @@ -272,8 +272,7 @@ def setup_platform( devices = [] for conf in discovery_info[ATTR_DISCOVER_DEVICES]: state = conf.get(ATTR_PARAM) - entity_desc = SENSOR_DESCRIPTIONS.get(state) - if entity_desc is None: + if (entity_desc := SENSOR_DESCRIPTIONS.get(state)) is None: name = conf.get(ATTR_NAME) _LOGGER.warning( "Sensor (%s) entity description is missing. Sensor state (%s) needs to be maintained", diff --git a/homeassistant/components/hue/device_trigger.py b/homeassistant/components/hue/device_trigger.py index ee0453c9da6841..a4b545aa141310 100644 --- a/homeassistant/components/hue/device_trigger.py +++ b/homeassistant/components/hue/device_trigger.py @@ -41,8 +41,7 @@ async def async_validate_trigger_config(hass: "HomeAssistant", config: ConfigTyp device_id = config[CONF_DEVICE_ID] # lookup device in HASS DeviceRegistry dev_reg: dr.DeviceRegistry = dr.async_get(hass) - device_entry = dev_reg.async_get(device_id) - if device_entry is None: + if (device_entry := dev_reg.async_get(device_id)) is None: raise InvalidDeviceAutomationConfig(f"Device ID {device_id} is not valid") for conf_entry_id in device_entry.config_entries: @@ -64,8 +63,7 @@ async def async_attach_trigger( device_id = config[CONF_DEVICE_ID] # lookup device in HASS DeviceRegistry dev_reg: dr.DeviceRegistry = dr.async_get(hass) - device_entry = dev_reg.async_get(device_id) - if device_entry is None: + if (device_entry := dev_reg.async_get(device_id)) is None: raise InvalidDeviceAutomationConfig(f"Device ID {device_id} is not valid") for conf_entry_id in device_entry.config_entries: @@ -90,8 +88,7 @@ async def async_get_triggers(hass: "HomeAssistant", device_id: str): return [] # lookup device in HASS DeviceRegistry dev_reg: dr.DeviceRegistry = dr.async_get(hass) - device_entry = dev_reg.async_get(device_id) - if device_entry is None: + if (device_entry := dev_reg.async_get(device_id)) is None: raise ValueError(f"Device ID {device_id} is not valid") # Iterate all config entries for this device diff --git a/homeassistant/components/hue/migration.py b/homeassistant/components/hue/migration.py index f779fccdb3b6ac..1d56d493785db2 100644 --- a/homeassistant/components/hue/migration.py +++ b/homeassistant/components/hue/migration.py @@ -39,8 +39,7 @@ async def check_migration(hass: core.HomeAssistant, entry: ConfigEntry) -> None: data[CONF_API_KEY] = data.pop(CONF_USERNAME) hass.config_entries.async_update_entry(entry, data=data) - conf_api_version = entry.data.get(CONF_API_VERSION, 1) - if conf_api_version == 1: + if (conf_api_version := entry.data.get(CONF_API_VERSION, 1)) == 1: # a bridge might have upgraded firmware since last run so # we discover its capabilities at every startup websession = aiohttp_client.async_get_clientsession(hass) diff --git a/homeassistant/components/iaqualink/__init__.py b/homeassistant/components/iaqualink/__init__.py index 73aa6f188677fa..030bb8cdcc8fe4 100644 --- a/homeassistant/components/iaqualink/__init__.py +++ b/homeassistant/components/iaqualink/__init__.py @@ -71,9 +71,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Aqualink component.""" - conf = config.get(DOMAIN) - - if conf is not None: + if (conf := config.get(DOMAIN)) is not None: hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, From 45d8d04c4064cf14d7686317e95f91dbdbdf333c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 19 Feb 2022 17:22:51 +0100 Subject: [PATCH 0826/1098] Use assignment expressions [other] (#66882) --- homeassistant/components/camera/media_source.py | 6 ++---- homeassistant/components/config/config_entries.py | 3 +-- .../components/device_tracker/config_entry.py | 7 ++----- homeassistant/components/diagnostics/__init__.py | 13 ++++--------- homeassistant/components/mqtt/__init__.py | 4 +--- homeassistant/components/mqtt/mixins.py | 3 +-- homeassistant/components/tts/__init__.py | 4 +--- homeassistant/components/tts/media_source.py | 4 +--- homeassistant/components/zone/trigger.py | 3 +-- homeassistant/components/zwave_js/helpers.py | 3 +-- 10 files changed, 15 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/camera/media_source.py b/homeassistant/components/camera/media_source.py index b0161a5825142f..c61cbef146afaf 100644 --- a/homeassistant/components/camera/media_source.py +++ b/homeassistant/components/camera/media_source.py @@ -47,14 +47,12 @@ async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia: if not camera: raise Unresolvable(f"Could not resolve media item: {item.identifier}") - stream_type = camera.frontend_stream_type - - if stream_type is None: + if (stream_type := camera.frontend_stream_type) is None: return PlayMedia( f"/api/camera_proxy_stream/{camera.entity_id}", camera.content_type ) - if camera.frontend_stream_type != STREAM_TYPE_HLS: + if stream_type != STREAM_TYPE_HLS: raise Unresolvable("Camera does not support MJPEG or HLS streaming.") if "stream" not in self.hass.config.components: diff --git a/homeassistant/components/config/config_entries.py b/homeassistant/components/config/config_entries.py index 2cf7005cb66121..e5bf9e9b93db00 100644 --- a/homeassistant/components/config/config_entries.py +++ b/homeassistant/components/config/config_entries.py @@ -317,8 +317,7 @@ async def config_entry_update(hass, connection, msg): @websocket_api.async_response async def config_entry_disable(hass, connection, msg): """Disable config entry.""" - disabled_by = msg["disabled_by"] - if disabled_by is not None: + if (disabled_by := msg["disabled_by"]) is not None: disabled_by = config_entries.ConfigEntryDisabler(disabled_by) result = False diff --git a/homeassistant/components/device_tracker/config_entry.py b/homeassistant/components/device_tracker/config_entry.py index c83ca669d6d911..adabd297c551e9 100644 --- a/homeassistant/components/device_tracker/config_entry.py +++ b/homeassistant/components/device_tracker/config_entry.py @@ -141,14 +141,11 @@ def handle_device_event(ev: Event) -> None: return ent_reg = er.async_get(hass) - entity_id = ent_reg.async_get_entity_id(DOMAIN, *unique_id) - if entity_id is None: + if (entity_id := ent_reg.async_get_entity_id(DOMAIN, *unique_id)) is None: return - entity_entry = ent_reg.async_get(entity_id) - - if entity_entry is None: + if (entity_entry := ent_reg.async_get(entity_id)) is None: return # Make sure entity has a config entry and was disabled by the diff --git a/homeassistant/components/diagnostics/__init__.py b/homeassistant/components/diagnostics/__init__.py index b08c521537d6c6..a3f1e5fe272040 100644 --- a/homeassistant/components/diagnostics/__init__.py +++ b/homeassistant/components/diagnostics/__init__.py @@ -106,9 +106,8 @@ def handle_get( ): """List all possible diagnostic handlers.""" domain = msg["domain"] - info = hass.data[DOMAIN].get(domain) - if info is None: + if (info := hass.data[DOMAIN].get(domain)) is None: connection.send_error( msg["id"], websocket_api.ERR_NOT_FOUND, "Domain not supported" ) @@ -197,14 +196,11 @@ async def get( # pylint: disable=no-self-use return web.Response(status=HTTPStatus.BAD_REQUEST) hass = request.app["hass"] - config_entry = hass.config_entries.async_get_entry(d_id) - if config_entry is None: + if (config_entry := hass.config_entries.async_get_entry(d_id)) is None: return web.Response(status=HTTPStatus.NOT_FOUND) - info = hass.data[DOMAIN].get(config_entry.domain) - - if info is None: + if (info := hass.data[DOMAIN].get(config_entry.domain)) is None: return web.Response(status=HTTPStatus.NOT_FOUND) filename = f"{config_entry.domain}-{config_entry.entry_id}" @@ -226,9 +222,8 @@ async def get( # pylint: disable=no-self-use dev_reg = async_get(hass) assert sub_id - device = dev_reg.async_get(sub_id) - if device is None: + if (device := dev_reg.async_get(sub_id)) is None: return web.Response(status=HTTPStatus.NOT_FOUND) filename += f"-{device.name}-{device.id}" diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 23a1fcc579e04a..197cf26b41fdcf 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -610,10 +610,8 @@ def _merge_config(entry, conf): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Load a config entry.""" - conf = hass.data.get(DATA_MQTT_CONFIG) - # If user didn't have configuration.yaml config, generate defaults - if conf is None: + if (conf := hass.data.get(DATA_MQTT_CONFIG)) is None: conf = CONFIG_SCHEMA({DOMAIN: dict(entry.data)})[DOMAIN] elif any(key in conf for key in entry.data): shared_keys = conf.keys() & entry.data.keys() diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index ba67339a5d49ca..9f3722a8f31743 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -839,8 +839,7 @@ def async_removed_from_device( if "config_entries" not in event.data["changes"]: return False device_registry = dr.async_get(hass) - device_entry = device_registry.async_get(device_id) - if not device_entry: + if not (device_entry := device_registry.async_get(device_id)): # The device is already removed, do cleanup when we get "remove" event return False if config_entry_id in device_entry.config_entries: diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index abe3d29c607ffd..5e6629ca2a274a 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -344,9 +344,7 @@ async def async_get_url_path( This method is a coroutine. """ - provider = self.providers.get(engine) - - if provider is None: + if (provider := self.providers.get(engine)) is None: raise HomeAssistantError(f"Provider {engine} not found") msg_hash = hashlib.sha1(bytes(message, "utf-8")).hexdigest() diff --git a/homeassistant/components/tts/media_source.py b/homeassistant/components/tts/media_source.py index 5e595ca42b75c6..48bb43990efbe2 100644 --- a/homeassistant/components/tts/media_source.py +++ b/homeassistant/components/tts/media_source.py @@ -94,9 +94,7 @@ def _provider_item( ) -> BrowseMediaSource: """Return provider item.""" manager: SpeechManager = self.hass.data[DOMAIN] - provider = manager.providers.get(provider_domain) - - if provider is None: + if (provider := manager.providers.get(provider_domain)) is None: raise BrowseError("Unknown provider") if params: diff --git a/homeassistant/components/zone/trigger.py b/homeassistant/components/zone/trigger.py index a008a30007abe4..5a11bf2068dcc7 100644 --- a/homeassistant/components/zone/trigger.py +++ b/homeassistant/components/zone/trigger.py @@ -79,8 +79,7 @@ def zone_automation_listener(zone_event): ): return - zone_state = hass.states.get(zone_entity_id) - if not zone_state: + if not (zone_state := hass.states.get(zone_entity_id)): _LOGGER.warning( "Automation '%s' is referencing non-existing zone '%s' in a zone trigger", automation_info["name"], diff --git a/homeassistant/components/zwave_js/helpers.py b/homeassistant/components/zwave_js/helpers.py index 2eb440cec9075c..05df480a4876ee 100644 --- a/homeassistant/components/zwave_js/helpers.py +++ b/homeassistant/components/zwave_js/helpers.py @@ -302,8 +302,7 @@ def async_is_device_config_entry_not_loaded( ) -> bool: """Return whether device's config entries are not loaded.""" dev_reg = dr.async_get(hass) - device = dev_reg.async_get(device_id) - if device is None: + if (device := dev_reg.async_get(device_id)) is None: raise ValueError(f"Device {device_id} not found") return any( (entry := hass.config_entries.async_get_entry(entry_id)) From 8f0b6eac417b109dec518a4b9c98a144fdfa3748 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 19 Feb 2022 10:24:50 -0600 Subject: [PATCH 0827/1098] Fix yeelight config flow ip update and timeout (#66883) --- .../components/yeelight/config_flow.py | 11 +++-- tests/components/yeelight/test_config_flow.py | 44 +++++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/yeelight/config_flow.py b/homeassistant/components/yeelight/config_flow.py index 0419824492afa9..8dd127502e2a61 100644 --- a/homeassistant/components/yeelight/config_flow.py +++ b/homeassistant/components/yeelight/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Yeelight integration.""" +import asyncio import logging from urllib.parse import urlparse @@ -86,11 +87,13 @@ async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowRes async def _async_handle_discovery_with_unique_id(self): """Handle any discovery with a unique id.""" - for entry in self._async_current_entries(): - if entry.unique_id != self.unique_id: + for entry in self._async_current_entries(include_ignore=False): + if entry.unique_id != self.unique_id and self.unique_id != entry.data.get( + CONF_ID + ): continue reload = entry.state == ConfigEntryState.SETUP_RETRY - if entry.data[CONF_HOST] != self._discovered_ip: + if entry.data.get(CONF_HOST) != self._discovered_ip: self.hass.config_entries.async_update_entry( entry, data={**entry.data, CONF_HOST: self._discovered_ip} ) @@ -261,7 +264,7 @@ async def _async_try_connect(self, host, raise_on_progress=True): await bulb.async_listen(lambda _: True) await bulb.async_get_properties() await bulb.async_stop_listening() - except yeelight.BulbException as err: + except (asyncio.TimeoutError, yeelight.BulbException) as err: _LOGGER.error("Failed to get properties from %s: %s", host, err) raise CannotConnect from err _LOGGER.debug("Get properties: %s", bulb.last_properties) diff --git a/tests/components/yeelight/test_config_flow.py b/tests/components/yeelight/test_config_flow.py index dd6c8fe1cbd192..205b5ddfa611d9 100644 --- a/tests/components/yeelight/test_config_flow.py +++ b/tests/components/yeelight/test_config_flow.py @@ -737,3 +737,47 @@ async def test_discovered_zeroconf(hass): assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "already_configured" + + +async def test_discovery_updates_ip(hass: HomeAssistant): + """Test discovery updtes ip.""" + config_entry = MockConfigEntry( + domain=DOMAIN, data={CONF_HOST: "1.2.2.3"}, unique_id=ID + ) + config_entry.add_to_hass(hass) + + mocked_bulb = _mocked_bulb() + with _patch_discovery(), _patch_discovery_interval(), patch( + f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data=ZEROCONF_DATA, + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + assert config_entry.data[CONF_HOST] == IP_ADDRESS + + +async def test_discovery_adds_missing_ip_id_only(hass: HomeAssistant): + """Test discovery adds missing ip.""" + config_entry = MockConfigEntry(domain=DOMAIN, data={CONF_ID: ID}) + config_entry.add_to_hass(hass) + + mocked_bulb = _mocked_bulb() + with _patch_discovery(), _patch_discovery_interval(), patch( + f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data=ZEROCONF_DATA, + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + assert config_entry.data[CONF_HOST] == IP_ADDRESS From 5359050afce044c97d3129518e5d225940d6c241 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Sat, 19 Feb 2022 18:51:01 +0200 Subject: [PATCH 0828/1098] Add Shelly gen2 error sensors (#66825) --- .../components/shelly/binary_sensor.py | 27 +++++++++++++++++++ homeassistant/components/shelly/entity.py | 9 +++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/shelly/binary_sensor.py b/homeassistant/components/shelly/binary_sensor.py index 80ac8133415bef..a6cde0c4670d05 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -188,6 +188,33 @@ class RestBinarySensorDescription(RestEntityDescription, BinarySensorEntityDescr }, entity_category=EntityCategory.DIAGNOSTIC, ), + "overtemp": RpcBinarySensorDescription( + key="switch", + sub_key="errors", + name="Overheating", + device_class=BinarySensorDeviceClass.PROBLEM, + value=lambda status, _: False if status is None else "overtemp" in status, + entity_category=EntityCategory.DIAGNOSTIC, + supported=lambda status: status.get("apower") is not None, + ), + "overpower": RpcBinarySensorDescription( + key="switch", + sub_key="errors", + name="Overpowering", + device_class=BinarySensorDeviceClass.PROBLEM, + value=lambda status, _: False if status is None else "overpower" in status, + entity_category=EntityCategory.DIAGNOSTIC, + supported=lambda status: status.get("apower") is not None, + ), + "overvoltage": RpcBinarySensorDescription( + key="switch", + sub_key="errors", + name="Overvoltage", + device_class=BinarySensorDeviceClass.PROBLEM, + value=lambda status, _: False if status is None else "overvoltage" in status, + entity_category=EntityCategory.DIAGNOSTIC, + supported=lambda status: status.get("apower") is not None, + ), } diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index 12f82016a1c084..51e0711b035b59 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -183,7 +183,9 @@ async def async_setup_entry_rpc( for key in key_instances: # Filter non-existing sensors - if description.sub_key not in wrapper.device.status[key]: + if description.sub_key not in wrapper.device.status[ + key + ] and not description.supported(wrapper.device.status[key]): continue # Filter and remove entities that according to settings should not create an entity @@ -266,6 +268,7 @@ class RpcEntityDescription(EntityDescription, RpcEntityRequiredKeysMixin): removal_condition: Callable[[dict, str], bool] | None = None extra_state_attributes: Callable[[dict, dict], dict | None] | None = None use_polling_wrapper: bool = False + supported: Callable = lambda _: False @dataclass @@ -505,7 +508,9 @@ def attribute_value(self) -> StateType: """Value of sensor.""" if callable(self.entity_description.value): self._last_value = self.entity_description.value( - self.wrapper.device.status[self.key][self.entity_description.sub_key], + self.wrapper.device.status[self.key].get( + self.entity_description.sub_key + ), self._last_value, ) else: From 6c2d6fde66b468c747e2ebbbdbd8b5f92d3fdeff Mon Sep 17 00:00:00 2001 From: Klaas Schoute Date: Sat, 19 Feb 2022 17:53:25 +0100 Subject: [PATCH 0829/1098] Add Pure Energie integration (#66846) --- .strict-typing | 1 + CODEOWNERS | 2 + .../components/pure_energie/__init__.py | 76 +++++++++++ .../components/pure_energie/config_flow.py | 107 +++++++++++++++ .../components/pure_energie/const.py | 10 ++ .../components/pure_energie/manifest.json | 16 +++ .../components/pure_energie/sensor.py | 113 ++++++++++++++++ .../components/pure_energie/strings.json | 23 ++++ .../pure_energie/translations/en.json | 23 ++++ homeassistant/generated/config_flows.py | 1 + homeassistant/generated/zeroconf.py | 4 + mypy.ini | 11 ++ requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/pure_energie/__init__.py | 1 + tests/components/pure_energie/conftest.py | 83 ++++++++++++ .../pure_energie/fixtures/device.json | 1 + .../pure_energie/fixtures/smartbridge.json | 1 + .../pure_energie/test_config_flow.py | 123 ++++++++++++++++++ tests/components/pure_energie/test_init.py | 52 ++++++++ tests/components/pure_energie/test_sensor.py | 75 +++++++++++ 21 files changed, 729 insertions(+) create mode 100644 homeassistant/components/pure_energie/__init__.py create mode 100644 homeassistant/components/pure_energie/config_flow.py create mode 100644 homeassistant/components/pure_energie/const.py create mode 100644 homeassistant/components/pure_energie/manifest.json create mode 100644 homeassistant/components/pure_energie/sensor.py create mode 100644 homeassistant/components/pure_energie/strings.json create mode 100644 homeassistant/components/pure_energie/translations/en.json create mode 100644 tests/components/pure_energie/__init__.py create mode 100644 tests/components/pure_energie/conftest.py create mode 100644 tests/components/pure_energie/fixtures/device.json create mode 100644 tests/components/pure_energie/fixtures/smartbridge.json create mode 100644 tests/components/pure_energie/test_config_flow.py create mode 100644 tests/components/pure_energie/test_init.py create mode 100644 tests/components/pure_energie/test_sensor.py diff --git a/.strict-typing b/.strict-typing index bd8553359cf483..cd148430340e5d 100644 --- a/.strict-typing +++ b/.strict-typing @@ -145,6 +145,7 @@ homeassistant.components.persistent_notification.* homeassistant.components.pi_hole.* homeassistant.components.proximity.* homeassistant.components.pvoutput.* +homeassistant.components.pure_energie.* homeassistant.components.rainmachine.* homeassistant.components.rdw.* homeassistant.components.recollect_waste.* diff --git a/CODEOWNERS b/CODEOWNERS index dabc32c0e11109..d80ca44f894f25 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -738,6 +738,8 @@ tests/components/prosegur/* @dgomes homeassistant/components/proxmoxve/* @jhollowe @Corbeno homeassistant/components/ps4/* @ktnrg45 tests/components/ps4/* @ktnrg45 +homeassistant/components/pure_energie/* @klaasnicolaas +tests/components/pure_energie/* @klaasnicolaas homeassistant/components/push/* @dgomes tests/components/push/* @dgomes homeassistant/components/pvoutput/* @fabaff @frenck diff --git a/homeassistant/components/pure_energie/__init__.py b/homeassistant/components/pure_energie/__init__.py new file mode 100644 index 00000000000000..4e86726ccc82da --- /dev/null +++ b/homeassistant/components/pure_energie/__init__.py @@ -0,0 +1,76 @@ +"""The Pure Energie integration.""" +from __future__ import annotations + +from typing import NamedTuple + +from gridnet import Device, GridNet, SmartBridge + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_HOST, Platform +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + +from .const import DOMAIN, LOGGER, SCAN_INTERVAL + +PLATFORMS = [Platform.SENSOR] + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Pure Energie from a config entry.""" + + coordinator = PureEnergieDataUpdateCoordinator(hass) + try: + await coordinator.async_config_entry_first_refresh() + except ConfigEntryNotReady: + await coordinator.gridnet.close() + raise + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator + + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload Pure Energie config entry.""" + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + del hass.data[DOMAIN][entry.entry_id] + return unload_ok + + +class PureEnergieData(NamedTuple): + """Class for defining data in dict.""" + + device: Device + smartbridge: SmartBridge + + +class PureEnergieDataUpdateCoordinator(DataUpdateCoordinator[PureEnergieData]): + """Class to manage fetching Pure Energie data from single eindpoint.""" + + config_entry: ConfigEntry + + def __init__( + self, + hass: HomeAssistant, + ) -> None: + """Initialize global Pure Energie data updater.""" + super().__init__( + hass, + LOGGER, + name=DOMAIN, + update_interval=SCAN_INTERVAL, + ) + + self.gridnet = GridNet( + self.config_entry.data[CONF_HOST], session=async_get_clientsession(hass) + ) + + async def _async_update_data(self) -> PureEnergieData: + """Fetch data from SmartBridge.""" + return PureEnergieData( + device=await self.gridnet.device(), + smartbridge=await self.gridnet.smartbridge(), + ) diff --git a/homeassistant/components/pure_energie/config_flow.py b/homeassistant/components/pure_energie/config_flow.py new file mode 100644 index 00000000000000..526bf004fd54ea --- /dev/null +++ b/homeassistant/components/pure_energie/config_flow.py @@ -0,0 +1,107 @@ +"""Config flow for Pure Energie integration.""" +from __future__ import annotations + +from typing import Any + +from gridnet import Device, GridNet, GridNetConnectionError +import voluptuous as vol + +from homeassistant.components import zeroconf +from homeassistant.config_entries import ConfigFlow +from homeassistant.const import CONF_HOST, CONF_NAME +from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from .const import DOMAIN + + +class PureEnergieFlowHandler(ConfigFlow, domain=DOMAIN): + """Config flow for Pure Energie integration.""" + + VERSION = 1 + discovered_host: str + discovered_device: Device + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle a flow initialized by the user.""" + + errors = {} + + if user_input is not None: + try: + device = await self._async_get_device(user_input[CONF_HOST]) + except GridNetConnectionError: + errors["base"] = "cannot_connect" + else: + await self.async_set_unique_id(device.n2g_id) + self._abort_if_unique_id_configured( + updates={CONF_HOST: user_input[CONF_HOST]} + ) + return self.async_create_entry( + title="Pure Energie Meter", + data={ + CONF_HOST: user_input[CONF_HOST], + }, + ) + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required(CONF_HOST): str, + } + ), + errors=errors or {}, + ) + + async def async_step_zeroconf( + self, discovery_info: zeroconf.ZeroconfServiceInfo + ) -> FlowResult: + """Handle zeroconf discovery.""" + self.discovered_host = discovery_info.host + try: + self.discovered_device = await self._async_get_device(discovery_info.host) + except GridNetConnectionError: + return self.async_abort(reason="cannot_connect") + + await self.async_set_unique_id(self.discovered_device.n2g_id) + self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.host}) + + self.context.update( + { + "title_placeholders": { + CONF_NAME: "Pure Energie Meter", + CONF_HOST: self.discovered_host, + "model": self.discovered_device.model, + }, + } + ) + return await self.async_step_zeroconf_confirm() + + async def async_step_zeroconf_confirm( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle a flow initiated by zeroconf.""" + if user_input is not None: + return self.async_create_entry( + title="Pure Energie Meter", + data={ + CONF_HOST: self.discovered_host, + }, + ) + + return self.async_show_form( + step_id="zeroconf_confirm", + description_placeholders={ + CONF_NAME: "Pure Energie Meter", + "model": self.discovered_device.model, + }, + ) + + async def _async_get_device(self, host: str) -> Device: + """Get device information from Pure Energie device.""" + session = async_get_clientsession(self.hass) + gridnet = GridNet(host, session=session) + return await gridnet.device() diff --git a/homeassistant/components/pure_energie/const.py b/homeassistant/components/pure_energie/const.py new file mode 100644 index 00000000000000..9c908da606891e --- /dev/null +++ b/homeassistant/components/pure_energie/const.py @@ -0,0 +1,10 @@ +"""Constants for the Pure Energie integration.""" +from __future__ import annotations + +from datetime import timedelta +import logging +from typing import Final + +DOMAIN: Final = "pure_energie" +LOGGER = logging.getLogger(__package__) +SCAN_INTERVAL = timedelta(seconds=30) diff --git a/homeassistant/components/pure_energie/manifest.json b/homeassistant/components/pure_energie/manifest.json new file mode 100644 index 00000000000000..7997e9c4b5d2ac --- /dev/null +++ b/homeassistant/components/pure_energie/manifest.json @@ -0,0 +1,16 @@ +{ + "domain": "pure_energie", + "name": "Pure Energie", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/pure_energie", + "requirements": ["gridnet==4.0.0"], + "codeowners": ["@klaasnicolaas"], + "quality_scale": "platinum", + "iot_class": "local_polling", + "zeroconf": [ + { + "type": "_http._tcp.local.", + "name": "smartbridge*" + } + ] +} \ No newline at end of file diff --git a/homeassistant/components/pure_energie/sensor.py b/homeassistant/components/pure_energie/sensor.py new file mode 100644 index 00000000000000..64ada3925f3058 --- /dev/null +++ b/homeassistant/components/pure_energie/sensor.py @@ -0,0 +1,113 @@ +"""Support for Pure Energie sensors.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass + +from homeassistant.components.sensor import ( + DOMAIN as SENSOR_DOMAIN, + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_HOST, ENERGY_KILO_WATT_HOUR, POWER_WATT +from homeassistant.core import HomeAssistant +from homeassistant.helpers.device_registry import DeviceEntryType +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from . import PureEnergieData, PureEnergieDataUpdateCoordinator +from .const import DOMAIN + + +@dataclass +class PureEnergieSensorEntityDescriptionMixin: + """Mixin for required keys.""" + + value_fn: Callable[[PureEnergieData], int | float] + + +@dataclass +class PureEnergieSensorEntityDescription( + SensorEntityDescription, PureEnergieSensorEntityDescriptionMixin +): + """Describes a Pure Energie sensor entity.""" + + +SENSORS: tuple[PureEnergieSensorEntityDescription, ...] = ( + PureEnergieSensorEntityDescription( + key="power_flow", + name="Power Flow", + native_unit_of_measurement=POWER_WATT, + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, + value_fn=lambda data: data.smartbridge.power_flow, + ), + PureEnergieSensorEntityDescription( + key="energy_consumption_total", + name="Energy Consumption", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + value_fn=lambda data: data.smartbridge.energy_consumption_total, + ), + PureEnergieSensorEntityDescription( + key="energy_production_total", + name="Energy Production", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + value_fn=lambda data: data.smartbridge.energy_production_total, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up Pure Energie Sensors based on a config entry.""" + async_add_entities( + PureEnergieSensorEntity( + coordinator=hass.data[DOMAIN][entry.entry_id], + description=description, + entry=entry, + ) + for description in SENSORS + ) + + +class PureEnergieSensorEntity(CoordinatorEntity[PureEnergieData], SensorEntity): + """Defines an Pure Energie sensor.""" + + coordinator: PureEnergieDataUpdateCoordinator + entity_description: PureEnergieSensorEntityDescription + + def __init__( + self, + *, + coordinator: PureEnergieDataUpdateCoordinator, + description: PureEnergieSensorEntityDescription, + entry: ConfigEntry, + ) -> None: + """Initialize Pure Energie sensor.""" + super().__init__(coordinator=coordinator) + self.entity_id = f"{SENSOR_DOMAIN}.pem_{description.key}" + self.entity_description = description + self._attr_unique_id = f"{coordinator.data.device.n2g_id}_{description.key}" + self._attr_device_info = DeviceInfo( + entry_type=DeviceEntryType.SERVICE, + identifiers={(DOMAIN, coordinator.data.device.n2g_id)}, + configuration_url=f"http://{coordinator.config_entry.data[CONF_HOST]}", + sw_version=coordinator.data.device.firmware, + manufacturer=coordinator.data.device.manufacturer, + model=coordinator.data.device.model, + name=entry.title, + ) + + @property + def native_value(self) -> int | float: + """Return the state of the sensor.""" + return self.entity_description.value_fn(self.coordinator.data) diff --git a/homeassistant/components/pure_energie/strings.json b/homeassistant/components/pure_energie/strings.json new file mode 100644 index 00000000000000..356d161f006de2 --- /dev/null +++ b/homeassistant/components/pure_energie/strings.json @@ -0,0 +1,23 @@ +{ + "config": { + "flow_title": "{model} ({host})", + "step": { + "user": { + "data": { + "host": "[%key:common::config_flow::data::host%]" + } + }, + "zeroconf_confirm": { + "description": "Do you want to add Pure Energie Meter (`{model}`) to Home Assistant?", + "title": "Discovered Pure Energie Meter device" + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pure_energie/translations/en.json b/homeassistant/components/pure_energie/translations/en.json new file mode 100644 index 00000000000000..16986efc206c85 --- /dev/null +++ b/homeassistant/components/pure_energie/translations/en.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured", + "cannot_connect": "Failed to connect" + }, + "error": { + "cannot_connect": "Failed to connect" + }, + "flow_title": "{name} ({host})", + "step": { + "user": { + "data": { + "host": "Host" + } + }, + "zeroconf_confirm": { + "description": "Do you want to add Pure Energie Meter (`{model}`) to Home Assistant?", + "title": "Discovered Pure Energie Meter device" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index a4f774e64a72d2..aa773be82f08cf 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -257,6 +257,7 @@ "progettihwsw", "prosegur", "ps4", + "pure_energie", "pvoutput", "pvpc_hourly_pricing", "rachio", diff --git a/homeassistant/generated/zeroconf.py b/homeassistant/generated/zeroconf.py index 93a24520497b09..9c3776155e18fc 100644 --- a/homeassistant/generated/zeroconf.py +++ b/homeassistant/generated/zeroconf.py @@ -175,6 +175,10 @@ "manufacturer": "nettigo" } }, + { + "domain": "pure_energie", + "name": "smartbridge*" + }, { "domain": "rachio", "name": "rachio*" diff --git a/mypy.ini b/mypy.ini index 6187f296ec2b6d..95e2090f0616c2 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1404,6 +1404,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.pure_energie.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.rainmachine.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/requirements_all.txt b/requirements_all.txt index 073e3c6e819175..989addfe7309c6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -781,6 +781,9 @@ greeneye_monitor==3.0.1 # homeassistant.components.greenwave greenwavereality==0.5.1 +# homeassistant.components.pure_energie +gridnet==4.0.0 + # homeassistant.components.growatt_server growattServer==1.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c65b43a032eeb5..47217c2c530947 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -509,6 +509,9 @@ greeclimate==1.0.2 # homeassistant.components.greeneye_monitor greeneye_monitor==3.0.1 +# homeassistant.components.pure_energie +gridnet==4.0.0 + # homeassistant.components.growatt_server growattServer==1.1.0 diff --git a/tests/components/pure_energie/__init__.py b/tests/components/pure_energie/__init__.py new file mode 100644 index 00000000000000..ee7ccbfb483a15 --- /dev/null +++ b/tests/components/pure_energie/__init__.py @@ -0,0 +1 @@ +"""Tests for the Pure Energie integration.""" diff --git a/tests/components/pure_energie/conftest.py b/tests/components/pure_energie/conftest.py new file mode 100644 index 00000000000000..4bb89860ce3d84 --- /dev/null +++ b/tests/components/pure_energie/conftest.py @@ -0,0 +1,83 @@ +"""Fixtures for Pure Energie integration tests.""" +from collections.abc import Generator +import json +from unittest.mock import AsyncMock, MagicMock, patch + +from gridnet import Device as GridNetDevice, SmartBridge +import pytest + +from homeassistant.components.pure_energie.const import DOMAIN +from homeassistant.const import CONF_HOST +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry, load_fixture + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Return the default mocked config entry.""" + return MockConfigEntry( + title="home", + domain=DOMAIN, + data={CONF_HOST: "192.168.1.123"}, + unique_id="unique_thingy", + ) + + +@pytest.fixture +def mock_setup_entry() -> Generator[None, None, None]: + """Mock setting up a config entry.""" + with patch( + "homeassistant.components.pure_energie.async_setup_entry", return_value=True + ): + yield + + +@pytest.fixture +def mock_pure_energie_config_flow( + request: pytest.FixtureRequest, +) -> Generator[None, MagicMock, None]: + """Return a mocked Pure Energie client.""" + with patch( + "homeassistant.components.pure_energie.config_flow.GridNet", autospec=True + ) as pure_energie_mock: + pure_energie = pure_energie_mock.return_value + pure_energie.device.return_value = GridNetDevice.from_dict( + json.loads(load_fixture("device.json", DOMAIN)) + ) + yield pure_energie + + +@pytest.fixture +def mock_pure_energie(): + """Return a mocked Pure Energie client.""" + with patch( + "homeassistant.components.pure_energie.GridNet", autospec=True + ) as pure_energie_mock: + pure_energie = pure_energie_mock.return_value + pure_energie.smartbridge = AsyncMock( + return_value=SmartBridge.from_dict( + json.loads(load_fixture("pure_energie/smartbridge.json")) + ) + ) + pure_energie.device = AsyncMock( + return_value=GridNetDevice.from_dict( + json.loads(load_fixture("pure_energie/device.json")) + ) + ) + yield pure_energie_mock + + +@pytest.fixture +async def init_integration( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_pure_energie: MagicMock, +) -> MockConfigEntry: + """Set up the Pure Energie integration for testing.""" + mock_config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + return mock_config_entry diff --git a/tests/components/pure_energie/fixtures/device.json b/tests/components/pure_energie/fixtures/device.json new file mode 100644 index 00000000000000..3580d4066ac2b9 --- /dev/null +++ b/tests/components/pure_energie/fixtures/device.json @@ -0,0 +1 @@ +{"id":"aabbccddeeff","mf":"NET2GRID","model":"SBWF3102","fw":"1.6.16","hw":1,"batch":"SBP-HMX-210318"} \ No newline at end of file diff --git a/tests/components/pure_energie/fixtures/smartbridge.json b/tests/components/pure_energie/fixtures/smartbridge.json new file mode 100644 index 00000000000000..a0268d666ba89d --- /dev/null +++ b/tests/components/pure_energie/fixtures/smartbridge.json @@ -0,0 +1 @@ +{"status":"ok","elec":{"power":{"now":{"value":338,"unit":"W","time":1634749148},"min":{"value":-7345,"unit":"W","time":1631360893},"max":{"value":13725,"unit":"W","time":1633749513}},"import":{"now":{"value":17762055,"unit":"Wh","time":1634749148}},"export":{"now":{"value":21214589,"unit":"Wh","time":1634749148}}},"gas":{}} \ No newline at end of file diff --git a/tests/components/pure_energie/test_config_flow.py b/tests/components/pure_energie/test_config_flow.py new file mode 100644 index 00000000000000..441a5977a2db91 --- /dev/null +++ b/tests/components/pure_energie/test_config_flow.py @@ -0,0 +1,123 @@ +"""Test the Pure Energie config flow.""" +from unittest.mock import MagicMock + +from gridnet import GridNetConnectionError + +from homeassistant.components import zeroconf +from homeassistant.components.pure_energie.const import DOMAIN +from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF +from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import ( + RESULT_TYPE_ABORT, + RESULT_TYPE_CREATE_ENTRY, + RESULT_TYPE_FORM, +) + + +async def test_full_user_flow_implementation( + hass: HomeAssistant, + mock_pure_energie_config_flow: MagicMock, + mock_setup_entry: None, +) -> None: + """Test the full manual user flow from start to finish.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + ) + + assert result.get("step_id") == SOURCE_USER + assert result.get("type") == RESULT_TYPE_FORM + assert "flow_id" in result + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_HOST: "192.168.1.123"} + ) + + assert result.get("title") == "Pure Energie Meter" + assert result.get("type") == RESULT_TYPE_CREATE_ENTRY + assert "data" in result + assert result["data"][CONF_HOST] == "192.168.1.123" + assert "result" in result + assert result["result"].unique_id == "aabbccddeeff" + + +async def test_full_zeroconf_flow_implementationn( + hass: HomeAssistant, + mock_pure_energie_config_flow: MagicMock, + mock_setup_entry: None, +) -> None: + """Test the full manual user flow from start to finish.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_ZEROCONF}, + data=zeroconf.ZeroconfServiceInfo( + host="192.168.1.123", + addresses=["192.168.1.123"], + hostname="example.local.", + name="mock_name", + port=None, + properties={CONF_MAC: "aabbccddeeff"}, + type="mock_type", + ), + ) + + assert result.get("description_placeholders") == { + "model": "SBWF3102", + CONF_NAME: "Pure Energie Meter", + } + assert result.get("step_id") == "zeroconf_confirm" + assert result.get("type") == RESULT_TYPE_FORM + assert "flow_id" in result + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + + assert result2.get("title") == "Pure Energie Meter" + assert result2.get("type") == RESULT_TYPE_CREATE_ENTRY + + assert "data" in result2 + assert result2["data"][CONF_HOST] == "192.168.1.123" + assert "result" in result2 + assert result2["result"].unique_id == "aabbccddeeff" + + +async def test_connection_error( + hass: HomeAssistant, mock_pure_energie_config_flow: MagicMock +) -> None: + """Test we show user form on Pure Energie connection error.""" + mock_pure_energie_config_flow.device.side_effect = GridNetConnectionError + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={CONF_HOST: "example.com"}, + ) + + assert result.get("type") == RESULT_TYPE_FORM + assert result.get("step_id") == "user" + assert result.get("errors") == {"base": "cannot_connect"} + + +async def test_zeroconf_connection_error( + hass: HomeAssistant, mock_pure_energie_config_flow: MagicMock +) -> None: + """Test we abort zeroconf flow on Pure Energie connection error.""" + mock_pure_energie_config_flow.device.side_effect = GridNetConnectionError + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_ZEROCONF}, + data=zeroconf.ZeroconfServiceInfo( + host="192.168.1.123", + addresses=["192.168.1.123"], + hostname="example.local.", + name="mock_name", + port=None, + properties={CONF_MAC: "aabbccddeeff"}, + type="mock_type", + ), + ) + + assert result.get("type") == RESULT_TYPE_ABORT + assert result.get("reason") == "cannot_connect" diff --git a/tests/components/pure_energie/test_init.py b/tests/components/pure_energie/test_init.py new file mode 100644 index 00000000000000..c5ffc19c640975 --- /dev/null +++ b/tests/components/pure_energie/test_init.py @@ -0,0 +1,52 @@ +"""Tests for the Pure Energie integration.""" +from unittest.mock import AsyncMock, MagicMock, patch + +from gridnet import GridNetConnectionError +import pytest + +from homeassistant.components.pure_energie.const import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +@pytest.mark.parametrize( + "mock_pure_energie", ["pure_energie/device.json"], indirect=True +) +async def test_load_unload_config_entry( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_pure_energie: AsyncMock, +) -> None: + """Test the Pure Energie configuration entry loading/unloading.""" + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert mock_config_entry.state is ConfigEntryState.LOADED + assert mock_config_entry.unique_id == "unique_thingy" + assert len(mock_pure_energie.mock_calls) == 3 + + await hass.config_entries.async_unload(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert not hass.data.get(DOMAIN) + assert mock_config_entry.state is ConfigEntryState.NOT_LOADED + + +@patch( + "homeassistant.components.pure_energie.GridNet.request", + side_effect=GridNetConnectionError, +) +async def test_config_entry_not_ready( + mock_request: MagicMock, + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, +) -> None: + """Test the Pure Energie configuration entry not ready.""" + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY diff --git a/tests/components/pure_energie/test_sensor.py b/tests/components/pure_energie/test_sensor.py new file mode 100644 index 00000000000000..dddafa3c24b9ed --- /dev/null +++ b/tests/components/pure_energie/test_sensor.py @@ -0,0 +1,75 @@ +"""Tests for the sensors provided by the Pure Energie integration.""" + +from homeassistant.components.pure_energie.const import DOMAIN +from homeassistant.components.sensor import ( + ATTR_STATE_CLASS, + SensorDeviceClass, + SensorStateClass, +) +from homeassistant.const import ( + ATTR_DEVICE_CLASS, + ATTR_FRIENDLY_NAME, + ATTR_ICON, + ATTR_UNIT_OF_MEASUREMENT, + ENERGY_KILO_WATT_HOUR, + POWER_WATT, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, entity_registry as er + +from tests.common import MockConfigEntry + + +async def test_sensors( + hass: HomeAssistant, + init_integration: MockConfigEntry, +) -> None: + """Test the Pure Energie - SmartBridge sensors.""" + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) + + state = hass.states.get("sensor.pem_energy_consumption_total") + entry = entity_registry.async_get("sensor.pem_energy_consumption_total") + assert entry + assert state + assert entry.unique_id == "aabbccddeeff_energy_consumption_total" + assert state.state == "17762.1" + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Energy Consumption" + assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.TOTAL_INCREASING + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == ENERGY_KILO_WATT_HOUR + assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.ENERGY + assert ATTR_ICON not in state.attributes + + state = hass.states.get("sensor.pem_energy_production_total") + entry = entity_registry.async_get("sensor.pem_energy_production_total") + assert entry + assert state + assert entry.unique_id == "aabbccddeeff_energy_production_total" + assert state.state == "21214.6" + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Energy Production" + assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.TOTAL_INCREASING + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == ENERGY_KILO_WATT_HOUR + assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.ENERGY + assert ATTR_ICON not in state.attributes + + state = hass.states.get("sensor.pem_power_flow") + entry = entity_registry.async_get("sensor.pem_power_flow") + assert entry + assert state + assert entry.unique_id == "aabbccddeeff_power_flow" + assert state.state == "338" + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Power Flow" + assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == POWER_WATT + assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.POWER + assert ATTR_ICON not in state.attributes + + assert entry.device_id + device_entry = device_registry.async_get(entry.device_id) + assert device_entry + assert device_entry.identifiers == {(DOMAIN, "aabbccddeeff")} + assert device_entry.name == "home" + assert device_entry.manufacturer == "NET2GRID" + assert device_entry.entry_type is dr.DeviceEntryType.SERVICE + assert device_entry.model == "SBWF3102" + assert device_entry.sw_version == "1.6.16" From 18f26d312a770aaa4ade014169621a35dc6fea7e Mon Sep 17 00:00:00 2001 From: Rob Wolinski Date: Sat, 19 Feb 2022 10:08:27 -0700 Subject: [PATCH 0830/1098] Update srpenergy dependency to 1.3.6 (#66821) --- homeassistant/components/srp_energy/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/srp_energy/manifest.json b/homeassistant/components/srp_energy/manifest.json index fc5c8a598ccf81..f5d38f4d0738c7 100644 --- a/homeassistant/components/srp_energy/manifest.json +++ b/homeassistant/components/srp_energy/manifest.json @@ -3,7 +3,7 @@ "name": "SRP Energy", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/srp_energy", - "requirements": ["srpenergy==1.3.2"], + "requirements": ["srpenergy==1.3.6"], "codeowners": ["@briglx"], "iot_class": "cloud_polling", "loggers": ["srpenergy"] diff --git a/requirements_all.txt b/requirements_all.txt index 989addfe7309c6..9adb28cc68553c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2269,7 +2269,7 @@ spotipy==2.19.0 sqlalchemy==1.4.27 # homeassistant.components.srp_energy -srpenergy==1.3.2 +srpenergy==1.3.6 # homeassistant.components.starline starline==0.1.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 47217c2c530947..1afbb1adff846a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1405,7 +1405,7 @@ spotipy==2.19.0 sqlalchemy==1.4.27 # homeassistant.components.srp_energy -srpenergy==1.3.2 +srpenergy==1.3.6 # homeassistant.components.starline starline==0.1.5 From d59dbbe859c660ed4fbd8444b70336b010e8f0a5 Mon Sep 17 00:00:00 2001 From: Keilin Bickar Date: Sat, 19 Feb 2022 12:54:52 -0500 Subject: [PATCH 0831/1098] Create button entities for SleepIQ (#66849) --- homeassistant/components/sleepiq/__init__.py | 5 +- homeassistant/components/sleepiq/button.py | 81 ++++++++++++++++++++ homeassistant/components/sleepiq/entity.py | 28 +++++-- tests/components/sleepiq/conftest.py | 9 ++- tests/components/sleepiq/test_button.py | 61 +++++++++++++++ 5 files changed, 169 insertions(+), 15 deletions(-) create mode 100644 homeassistant/components/sleepiq/button.py create mode 100644 tests/components/sleepiq/test_button.py diff --git a/homeassistant/components/sleepiq/__init__.py b/homeassistant/components/sleepiq/__init__.py index 2fc0c52c706744..26557ca6dafada 100644 --- a/homeassistant/components/sleepiq/__init__.py +++ b/homeassistant/components/sleepiq/__init__.py @@ -22,7 +22,7 @@ _LOGGER = logging.getLogger(__name__) -PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] +PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.SENSOR] CONFIG_SCHEMA = vol.Schema( { @@ -35,9 +35,6 @@ ) -PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] - - async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up sleepiq component.""" if DOMAIN in config: diff --git a/homeassistant/components/sleepiq/button.py b/homeassistant/components/sleepiq/button.py new file mode 100644 index 00000000000000..8cdc0398c2dd08 --- /dev/null +++ b/homeassistant/components/sleepiq/button.py @@ -0,0 +1,81 @@ +"""Support for SleepIQ buttons.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any + +from asyncsleepiq import SleepIQBed + +from homeassistant.components.button import ButtonEntity, ButtonEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .coordinator import SleepIQDataUpdateCoordinator +from .entity import SleepIQEntity + + +@dataclass +class SleepIQButtonEntityDescriptionMixin: + """Describes a SleepIQ Button entity.""" + + press_action: Callable[[SleepIQBed], Any] + + +@dataclass +class SleepIQButtonEntityDescription( + ButtonEntityDescription, SleepIQButtonEntityDescriptionMixin +): + """Class to describe a Button entity.""" + + +ENTITY_DESCRIPTIONS = [ + SleepIQButtonEntityDescription( + key="calibrate", + name="Calibrate", + press_action=lambda client: client.calibrate(), + icon="mdi:target", + ), + SleepIQButtonEntityDescription( + key="stop-pump", + name="Stop Pump", + press_action=lambda client: client.stop_pump(), + icon="mdi:stop", + ), +] + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the sleep number buttons.""" + coordinator: SleepIQDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + async_add_entities( + SleepNumberButton(bed, ed) + for bed in coordinator.client.beds.values() + for ed in ENTITY_DESCRIPTIONS + ) + + +class SleepNumberButton(SleepIQEntity, ButtonEntity): + """Representation of an SleepIQ button.""" + + entity_description: SleepIQButtonEntityDescription + + def __init__( + self, bed: SleepIQBed, entity_description: SleepIQButtonEntityDescription + ) -> None: + """Initialize the Button.""" + super().__init__(bed) + self._attr_name = f"SleepNumber {bed.name} {entity_description.name}" + self._attr_unique_id = f"{bed.id}-{entity_description.key}" + self.entity_description = entity_description + + async def async_press(self) -> None: + """Press the button.""" + await self.entity_description.press_action(self.bed) diff --git a/homeassistant/components/sleepiq/entity.py b/homeassistant/components/sleepiq/entity.py index 78c2045bcb6a78..141b94fa72db56 100644 --- a/homeassistant/components/sleepiq/entity.py +++ b/homeassistant/components/sleepiq/entity.py @@ -5,7 +5,7 @@ from homeassistant.core import callback from homeassistant.helpers import device_registry -from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, @@ -14,6 +14,20 @@ from .const import ICON_OCCUPIED, SENSOR_TYPES +class SleepIQEntity(Entity): + """Implementation of a SleepIQ entity.""" + + def __init__(self, bed: SleepIQBed) -> None: + """Initialize the SleepIQ entity.""" + self.bed = bed + self._attr_device_info = DeviceInfo( + connections={(device_registry.CONNECTION_NETWORK_MAC, bed.mac_addr)}, + manufacturer="SleepNumber", + name=bed.name, + model=bed.model, + ) + + class SleepIQSensor(CoordinatorEntity): """Implementation of a SleepIQ sensor.""" @@ -26,14 +40,10 @@ def __init__( sleeper: SleepIQSleeper, name: str, ) -> None: - """Initialize the SleepIQ side entity.""" + """Initialize the SleepIQ sensor entity.""" super().__init__(coordinator) - self.bed = bed self.sleeper = sleeper - self._async_update_attrs() - - self._attr_name = f"SleepNumber {bed.name} {sleeper.name} {SENSOR_TYPES[name]}" - self._attr_unique_id = f"{bed.id}_{sleeper.name}_{name}" + self.bed = bed self._attr_device_info = DeviceInfo( connections={(device_registry.CONNECTION_NETWORK_MAC, bed.mac_addr)}, manufacturer="SleepNumber", @@ -41,6 +51,10 @@ def __init__( model=bed.model, ) + self._attr_name = f"SleepNumber {bed.name} {sleeper.name} {SENSOR_TYPES[name]}" + self._attr_unique_id = f"{bed.id}_{sleeper.name}_{name}" + self._async_update_attrs() + @callback def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator.""" diff --git a/tests/components/sleepiq/conftest.py b/tests/components/sleepiq/conftest.py index b694928a042db2..3669fd5a7fcaff 100644 --- a/tests/components/sleepiq/conftest.py +++ b/tests/components/sleepiq/conftest.py @@ -1,6 +1,7 @@ """Common methods for SleepIQ.""" -from unittest.mock import MagicMock, patch +from unittest.mock import create_autospec, patch +from asyncsleepiq import SleepIQBed, SleepIQSleeper import pytest from homeassistant.components.sleepiq import DOMAIN @@ -24,15 +25,15 @@ def mock_asyncsleepiq(): """Mock an AsyncSleepIQ object.""" with patch("homeassistant.components.sleepiq.AsyncSleepIQ", autospec=True) as mock: client = mock.return_value - bed = MagicMock() + bed = create_autospec(SleepIQBed) client.beds = {BED_ID: bed} bed.name = BED_NAME bed.id = BED_ID bed.mac_addr = "12:34:56:78:AB:CD" bed.model = "C10" bed.paused = False - sleeper_l = MagicMock() - sleeper_r = MagicMock() + sleeper_l = create_autospec(SleepIQSleeper) + sleeper_r = create_autospec(SleepIQSleeper) bed.sleepers = [sleeper_l, sleeper_r] sleeper_l.side = "L" diff --git a/tests/components/sleepiq/test_button.py b/tests/components/sleepiq/test_button.py new file mode 100644 index 00000000000000..cab3f36d73fba5 --- /dev/null +++ b/tests/components/sleepiq/test_button.py @@ -0,0 +1,61 @@ +"""The tests for SleepIQ binary sensor platform.""" +from homeassistant.components.button import DOMAIN +from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME +from homeassistant.helpers import entity_registry as er + +from tests.components.sleepiq.conftest import ( + BED_ID, + BED_NAME, + BED_NAME_LOWER, + setup_platform, +) + + +async def test_button_calibrate(hass, mock_asyncsleepiq): + """Test the SleepIQ calibrate button.""" + await setup_platform(hass, DOMAIN) + entity_registry = er.async_get(hass) + + state = hass.states.get(f"button.sleepnumber_{BED_NAME_LOWER}_calibrate") + assert ( + state.attributes.get(ATTR_FRIENDLY_NAME) == f"SleepNumber {BED_NAME} Calibrate" + ) + + entity = entity_registry.async_get(f"button.sleepnumber_{BED_NAME_LOWER}_calibrate") + assert entity + assert entity.unique_id == f"{BED_ID}-calibrate" + + await hass.services.async_call( + DOMAIN, + "press", + {ATTR_ENTITY_ID: f"button.sleepnumber_{BED_NAME_LOWER}_calibrate"}, + blocking=True, + ) + await hass.async_block_till_done() + + mock_asyncsleepiq.beds[BED_ID].calibrate.assert_called_once() + + +async def test_button_stop_pump(hass, mock_asyncsleepiq): + """Test the SleepIQ stop pump button.""" + await setup_platform(hass, DOMAIN) + entity_registry = er.async_get(hass) + + state = hass.states.get(f"button.sleepnumber_{BED_NAME_LOWER}_stop_pump") + assert ( + state.attributes.get(ATTR_FRIENDLY_NAME) == f"SleepNumber {BED_NAME} Stop Pump" + ) + + entity = entity_registry.async_get(f"button.sleepnumber_{BED_NAME_LOWER}_stop_pump") + assert entity + assert entity.unique_id == f"{BED_ID}-stop-pump" + + await hass.services.async_call( + DOMAIN, + "press", + {ATTR_ENTITY_ID: f"button.sleepnumber_{BED_NAME_LOWER}_stop_pump"}, + blocking=True, + ) + await hass.async_block_till_done() + + mock_asyncsleepiq.beds[BED_ID].stop_pump.assert_called_once() From 6464ab8356b4beb80a1d7341a9ef38422728f4ce Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 19 Feb 2022 19:00:49 +0100 Subject: [PATCH 0832/1098] Bump pysensibo to 1.0.4 (#66886) --- homeassistant/components/sensibo/climate.py | 3 ++- homeassistant/components/sensibo/config_flow.py | 4 +++- homeassistant/components/sensibo/coordinator.py | 7 ++++--- homeassistant/components/sensibo/manifest.json | 4 ++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/sensibo/test_config_flow.py | 3 ++- 7 files changed, 15 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index 0963a4f927e977..a9629ba42f4dcc 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -5,7 +5,7 @@ from aiohttp.client_exceptions import ClientConnectionError import async_timeout -from pysensibo import SensiboError +from pysensibo.exceptions import AuthenticationError, SensiboError import voluptuous as vol from homeassistant.components.climate import ( @@ -318,6 +318,7 @@ async def _async_set_ac_state_property( except ( ClientConnectionError, asyncio.TimeoutError, + AuthenticationError, SensiboError, ) as err: raise HomeAssistantError( diff --git a/homeassistant/components/sensibo/config_flow.py b/homeassistant/components/sensibo/config_flow.py index 8544972baeed77..f970581e2a89a7 100644 --- a/homeassistant/components/sensibo/config_flow.py +++ b/homeassistant/components/sensibo/config_flow.py @@ -6,7 +6,8 @@ import aiohttp import async_timeout -from pysensibo import SensiboClient, SensiboError +from pysensibo import SensiboClient +from pysensibo.exceptions import AuthenticationError, SensiboError import voluptuous as vol from homeassistant import config_entries @@ -42,6 +43,7 @@ async def async_validate_api(hass: HomeAssistant, api_key: str) -> bool: except ( aiohttp.ClientConnectionError, asyncio.TimeoutError, + AuthenticationError, SensiboError, ) as err: _LOGGER.error("Failed to get devices from Sensibo servers %s", err) diff --git a/homeassistant/components/sensibo/coordinator.py b/homeassistant/components/sensibo/coordinator.py index 41c44e741e9881..c79fb5b7bb5852 100644 --- a/homeassistant/components/sensibo/coordinator.py +++ b/homeassistant/components/sensibo/coordinator.py @@ -4,7 +4,8 @@ from datetime import timedelta from typing import Any -import pysensibo +from pysensibo import SensiboClient +from pysensibo.exceptions import AuthenticationError, SensiboError from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_API_KEY @@ -20,7 +21,7 @@ class SensiboDataUpdateCoordinator(DataUpdateCoordinator): def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: """Initialize the Sensibo coordinator.""" - self.client = pysensibo.SensiboClient( + self.client = SensiboClient( entry.data[CONF_API_KEY], session=async_get_clientsession(hass), timeout=TIMEOUT, @@ -39,7 +40,7 @@ async def _async_update_data(self) -> dict[str, dict[str, Any]]: try: for dev in await self.client.async_get_devices(): devices.append(dev) - except (pysensibo.SensiboError) as error: + except (AuthenticationError, SensiboError) as error: raise UpdateFailed from error device_data: dict[str, dict[str, Any]] = {} diff --git a/homeassistant/components/sensibo/manifest.json b/homeassistant/components/sensibo/manifest.json index 71fd3e1c241da2..e17c5339d67d41 100644 --- a/homeassistant/components/sensibo/manifest.json +++ b/homeassistant/components/sensibo/manifest.json @@ -2,7 +2,7 @@ "domain": "sensibo", "name": "Sensibo", "documentation": "https://www.home-assistant.io/integrations/sensibo", - "requirements": ["pysensibo==1.0.3"], + "requirements": ["pysensibo==1.0.4"], "config_flow": true, "codeowners": ["@andrey-git", "@gjohansson-ST"], "iot_class": "cloud_polling", @@ -11,6 +11,6 @@ }, "dhcp": [ {"hostname":"sensibo*"} - ], + ], "loggers": ["pysensibo"] } diff --git a/requirements_all.txt b/requirements_all.txt index 9adb28cc68553c..bea0716741fb95 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1818,7 +1818,7 @@ pysaj==0.0.16 pysdcp==1 # homeassistant.components.sensibo -pysensibo==1.0.3 +pysensibo==1.0.4 # homeassistant.components.serial # homeassistant.components.zha diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1afbb1adff846a..7acb27ca3441b9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1160,7 +1160,7 @@ pyrituals==0.0.6 pyruckus==0.12 # homeassistant.components.sensibo -pysensibo==1.0.3 +pysensibo==1.0.4 # homeassistant.components.serial # homeassistant.components.zha diff --git a/tests/components/sensibo/test_config_flow.py b/tests/components/sensibo/test_config_flow.py index cf3716f09e4046..9c59fc70763a6f 100644 --- a/tests/components/sensibo/test_config_flow.py +++ b/tests/components/sensibo/test_config_flow.py @@ -5,7 +5,7 @@ from unittest.mock import patch import aiohttp -from pysensibo import SensiboError +from pysensibo import AuthenticationError, SensiboError import pytest from homeassistant import config_entries @@ -123,6 +123,7 @@ async def test_import_flow_already_exist(hass: HomeAssistant) -> None: [ (aiohttp.ClientConnectionError), (asyncio.TimeoutError), + (AuthenticationError), (SensiboError), ], ) From e8fc4cc627593473cdc2865854af02e6963db292 Mon Sep 17 00:00:00 2001 From: jingsno <20334786+jingsno@users.noreply.github.com> Date: Sat, 19 Feb 2022 19:21:10 +0100 Subject: [PATCH 0833/1098] Fix Mill Gen1 Climate Control (#66899) Fixes Mill Gen1 Climate Control, so it correctly returns the current status of the heating element. --- homeassistant/components/mill/climate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/mill/climate.py b/homeassistant/components/mill/climate.py index 897afc96665681..4a06e597c3608a 100644 --- a/homeassistant/components/mill/climate.py +++ b/homeassistant/components/mill/climate.py @@ -184,7 +184,7 @@ def _update_attr(self, heater): self._attr_target_temperature = heater.set_temp self._attr_current_temperature = heater.current_temp self._attr_fan_mode = FAN_ON if heater.fan_status == 1 else HVAC_MODE_OFF - if heater.is_gen1 or heater.is_heating == 1: + if heater.is_heating == 1: self._attr_hvac_action = CURRENT_HVAC_HEAT else: self._attr_hvac_action = CURRENT_HVAC_IDLE From 88b7a9fccc2750829721c017bd590de6d3ed9114 Mon Sep 17 00:00:00 2001 From: Teemu R Date: Sat, 19 Feb 2022 20:22:49 +0100 Subject: [PATCH 0834/1098] Enable consumable sensors per default for xiaomi_miio vacuums (#66843) Co-authored-by: Franck Nijhof --- homeassistant/components/xiaomi_miio/sensor.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index 6095bfe5647f1f..36a8cd24213b05 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -520,7 +520,6 @@ class XiaomiMiioSensorDescription(SensorEntityDescription): key=ATTR_CONSUMABLE_STATUS_MAIN_BRUSH_LEFT, parent_key=VacuumCoordinatorDataAttributes.consumable_status, name="Main Brush Left", - entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, ), f"consumable_{ATTR_CONSUMABLE_STATUS_SIDE_BRUSH_LEFT}": XiaomiMiioSensorDescription( @@ -529,7 +528,6 @@ class XiaomiMiioSensorDescription(SensorEntityDescription): key=ATTR_CONSUMABLE_STATUS_SIDE_BRUSH_LEFT, parent_key=VacuumCoordinatorDataAttributes.consumable_status, name="Side Brush Left", - entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, ), f"consumable_{ATTR_CONSUMABLE_STATUS_FILTER_LEFT}": XiaomiMiioSensorDescription( @@ -538,7 +536,6 @@ class XiaomiMiioSensorDescription(SensorEntityDescription): key=ATTR_CONSUMABLE_STATUS_FILTER_LEFT, parent_key=VacuumCoordinatorDataAttributes.consumable_status, name="Filter Left", - entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, ), f"consumable_{ATTR_CONSUMABLE_STATUS_SENSOR_DIRTY_LEFT}": XiaomiMiioSensorDescription( @@ -547,7 +544,6 @@ class XiaomiMiioSensorDescription(SensorEntityDescription): key=ATTR_CONSUMABLE_STATUS_SENSOR_DIRTY_LEFT, parent_key=VacuumCoordinatorDataAttributes.consumable_status, name="Sensor Dirty Left", - entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, ), } From 69ce03465d674ae5cdc2f4403bfa83a75c36e9b9 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Sat, 19 Feb 2022 13:25:33 -0600 Subject: [PATCH 0835/1098] Proxy Plex thumbnail images in media browser (#66702) Co-authored-by: Paulus Schoutsen --- .coveragerc | 1 + .../components/media_player/__init__.py | 45 +++++++++-------- homeassistant/components/plex/__init__.py | 3 ++ .../components/plex/media_browser.py | 20 +++++++- homeassistant/components/plex/media_player.py | 14 ------ homeassistant/components/plex/view.py | 48 +++++++++++++++++++ 6 files changed, 97 insertions(+), 34 deletions(-) create mode 100644 homeassistant/components/plex/view.py diff --git a/.coveragerc b/.coveragerc index 90437cac34e7f8..da4bdaede00b1d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -919,6 +919,7 @@ omit = homeassistant/components/plaato/entity.py homeassistant/components/plaato/sensor.py homeassistant/components/plex/media_player.py + homeassistant/components/plex/view.py homeassistant/components/plum_lightpad/light.py homeassistant/components/pocketcasts/sensor.py homeassistant/components/point/__init__.py diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 2de42c05dde983..99d493a75c4fc9 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -999,25 +999,7 @@ async def _async_fetch_image_from_cache( async def _async_fetch_image(self, url: str) -> tuple[bytes | None, str | None]: """Retrieve an image.""" - content, content_type = (None, None) - websession = async_get_clientsession(self.hass) - with suppress(asyncio.TimeoutError), async_timeout.timeout(10): - response = await websession.get(url) - if response.status == HTTPStatus.OK: - content = await response.read() - if content_type := response.headers.get(CONTENT_TYPE): - content_type = content_type.split(";")[0] - - if content is None: - url_parts = URL(url) - if url_parts.user is not None: - url_parts = url_parts.with_user("xxxx") - if url_parts.password is not None: - url_parts = url_parts.with_password("xxxxxxxx") - url = str(url_parts) - _LOGGER.warning("Error retrieving proxied image from %s", url) - - return content, content_type + return await async_fetch_image(_LOGGER, self.hass, url) def get_browse_image_url( self, @@ -1205,3 +1187,28 @@ async def websocket_browse_media(hass, connection, msg): _LOGGER.warning("Browse Media should use new BrowseMedia class") connection.send_result(msg["id"], payload) + + +async def async_fetch_image( + logger: logging.Logger, hass: HomeAssistant, url: str +) -> tuple[bytes | None, str | None]: + """Retrieve an image.""" + content, content_type = (None, None) + websession = async_get_clientsession(hass) + with suppress(asyncio.TimeoutError), async_timeout.timeout(10): + response = await websession.get(url) + if response.status == HTTPStatus.OK: + content = await response.read() + if content_type := response.headers.get(CONTENT_TYPE): + content_type = content_type.split(";")[0] + + if content is None: + url_parts = URL(url) + if url_parts.user is not None: + url_parts = url_parts.with_user("xxxx") + if url_parts.password is not None: + url_parts = url_parts.with_password("xxxxxxxx") + url = str(url_parts) + logger.warning("Error retrieving proxied image from %s", url) + + return content, content_type diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index 26a158d240f25f..44bca818333d47 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -48,6 +48,7 @@ from .media_browser import browse_media from .server import PlexServer from .services import async_setup_services +from .view import PlexImageView _LOGGER = logging.getLogger(__package__) @@ -84,6 +85,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: await async_setup_services(hass) + hass.http.register_view(PlexImageView()) + gdm = hass.data[PLEX_DOMAIN][GDM_SCANNER] = GDM() def gdm_scan(): diff --git a/homeassistant/components/plex/media_browser.py b/homeassistant/components/plex/media_browser.py index 0bff5cfb5cd9f3..115990861799ad 100644 --- a/homeassistant/components/plex/media_browser.py +++ b/homeassistant/components/plex/media_browser.py @@ -1,4 +1,6 @@ """Support to interface with the Plex API.""" +from __future__ import annotations + import logging from homeassistant.components.media_player import BrowseMedia @@ -73,7 +75,15 @@ def item_payload(item, short_name=False): "can_expand": item.type in EXPANDABLES, } if hasattr(item, "thumbUrl"): - payload["thumbnail"] = item.thumbUrl + plex_server.thumbnail_cache.setdefault(str(item.ratingKey), item.thumbUrl) + if is_internal: + thumbnail = item.thumbUrl + else: + thumbnail = get_proxy_image_url( + plex_server.machine_identifier, + item.ratingKey, + ) + payload["thumbnail"] = thumbnail return BrowseMedia(**payload) @@ -321,3 +331,11 @@ def station_payload(station): can_play=True, can_expand=False, ) + + +def get_proxy_image_url( + server_id: str, + media_content_id: str, +) -> str: + """Generate an url for a Plex media browser image.""" + return f"/api/plex_image_proxy/{server_id}/{media_content_id}" diff --git a/homeassistant/components/plex/media_player.py b/homeassistant/components/plex/media_player.py index 1ff58ed468d0c9..9b8b0df14c7941 100644 --- a/homeassistant/components/plex/media_player.py +++ b/homeassistant/components/plex/media_player.py @@ -585,17 +585,3 @@ async def async_browse_media(self, media_content_type=None, media_content_id=Non media_content_type, media_content_id, ) - - async def async_get_browse_image( - self, - media_content_type: str, - media_content_id: str, - media_image_id: str | None = None, - ) -> tuple[bytes | None, str | None]: - """Get media image from Plex server.""" - image_url = self.plex_server.thumbnail_cache.get(media_content_id) - if image_url: - result = await self._async_fetch_image(image_url) - return result - - return (None, None) diff --git a/homeassistant/components/plex/view.py b/homeassistant/components/plex/view.py new file mode 100644 index 00000000000000..3cb7d40b2def0f --- /dev/null +++ b/homeassistant/components/plex/view.py @@ -0,0 +1,48 @@ +"""Implement a view to provide proxied Plex thumbnails to the media browser.""" +from __future__ import annotations + +from http import HTTPStatus +import logging + +from aiohttp import web +from aiohttp.hdrs import CACHE_CONTROL +from aiohttp.typedefs import LooseHeaders + +from homeassistant.components.http import KEY_AUTHENTICATED, HomeAssistantView +from homeassistant.components.media_player import async_fetch_image + +from .const import DOMAIN as PLEX_DOMAIN, SERVERS + +_LOGGER = logging.getLogger(__name__) + + +class PlexImageView(HomeAssistantView): + """Media player view to serve a Plex image.""" + + name = "api:plex:image" + url = "/api/plex_image_proxy/{server_id}/{media_content_id}" + + async def get( # pylint: disable=no-self-use + self, + request: web.Request, + server_id: str, + media_content_id: str, + ) -> web.Response: + """Start a get request.""" + if not request[KEY_AUTHENTICATED]: + return web.Response(status=HTTPStatus.UNAUTHORIZED) + + hass = request.app["hass"] + if (server := hass.data[PLEX_DOMAIN][SERVERS].get(server_id)) is None: + return web.Response(status=HTTPStatus.NOT_FOUND) + + if (image_url := server.thumbnail_cache.get(media_content_id)) is None: + return web.Response(status=HTTPStatus.NOT_FOUND) + + data, content_type = await async_fetch_image(_LOGGER, hass, image_url) + + if data is None: + return web.Response(status=HTTPStatus.SERVICE_UNAVAILABLE) + + headers: LooseHeaders = {CACHE_CONTROL: "max-age=3600"} + return web.Response(body=data, content_type=content_type, headers=headers) From a58fd8796446fd44ab2b62222876cfb88fc630f8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 19 Feb 2022 13:26:33 -0600 Subject: [PATCH 0836/1098] Bump aiodiscover to 1.4.8 (#66892) --- homeassistant/components/dhcp/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/dhcp/manifest.json b/homeassistant/components/dhcp/manifest.json index c61b4b24a3099f..fb9ebc70408be0 100644 --- a/homeassistant/components/dhcp/manifest.json +++ b/homeassistant/components/dhcp/manifest.json @@ -2,7 +2,7 @@ "domain": "dhcp", "name": "DHCP Discovery", "documentation": "https://www.home-assistant.io/integrations/dhcp", - "requirements": ["scapy==2.4.5", "aiodiscover==1.4.7"], + "requirements": ["scapy==2.4.5", "aiodiscover==1.4.8"], "codeowners": ["@bdraco"], "quality_scale": "internal", "iot_class": "local_push", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index f542bf408c10e8..cf95644867bf18 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -1,6 +1,6 @@ PyJWT==2.1.0 PyNaCl==1.4.0 -aiodiscover==1.4.7 +aiodiscover==1.4.8 aiohttp==3.8.1 aiohttp_cors==0.7.0 astral==2.2 diff --git a/requirements_all.txt b/requirements_all.txt index bea0716741fb95..19a3d64dfaef08 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -150,7 +150,7 @@ aioazuredevops==1.3.5 aiobotocore==2.1.0 # homeassistant.components.dhcp -aiodiscover==1.4.7 +aiodiscover==1.4.8 # homeassistant.components.dnsip # homeassistant.components.minecraft_server diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7acb27ca3441b9..f0aae26d5f0263 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -103,7 +103,7 @@ aioazuredevops==1.3.5 aiobotocore==2.1.0 # homeassistant.components.dhcp -aiodiscover==1.4.7 +aiodiscover==1.4.8 # homeassistant.components.dnsip # homeassistant.components.minecraft_server From 27038fda272af81ce02ce013790d13d79b4f9d10 Mon Sep 17 00:00:00 2001 From: James Taylor Date: Sat, 19 Feb 2022 19:26:42 +0000 Subject: [PATCH 0837/1098] Update RSS feed template (#62966) --- .../components/rss_feed_template/__init__.py | 19 +++++++++++++------ .../components/rss_feed_template/test_init.py | 9 ++++++--- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/rss_feed_template/__init__.py b/homeassistant/components/rss_feed_template/__init__.py index 3b88f9c02883c3..4dcbf7fe048e7e 100644 --- a/homeassistant/components/rss_feed_template/__init__.py +++ b/homeassistant/components/rss_feed_template/__init__.py @@ -81,24 +81,31 @@ async def get(self, request, entity_id=None): """Generate the RSS view XML.""" response = '\n\n' - response += "\n" + response += '\n' + response += " \n" if self._title is not None: - response += " %s\n" % escape( + response += " %s\n" % escape( self._title.async_render(parse_result=False) ) + else: + response += " Home Assistant\n" + + response += " https://www.home-assistant.io/integrations/rss_feed_template/\n" + response += " Home automation feed\n" for item in self._items: - response += " \n" + response += " \n" if "title" in item: - response += " " + response += " <title>" response += escape(item["title"].async_render(parse_result=False)) response += "\n" if "description" in item: - response += " " + response += " " response += escape(item["description"].async_render(parse_result=False)) response += "\n" - response += " \n" + response += " \n" + response += " \n" response += "\n" return web.Response(body=response, content_type=CONTENT_TYPE_XML) diff --git a/tests/components/rss_feed_template/test_init.py b/tests/components/rss_feed_template/test_init.py index bdc894c334335e..ffdb4e5ba9af80 100644 --- a/tests/components/rss_feed_template/test_init.py +++ b/tests/components/rss_feed_template/test_init.py @@ -46,6 +46,9 @@ async def test_get_rss_feed(mock_http_client, hass): text = await resp.text() xml = ElementTree.fromstring(text) - assert xml[0].text == "feed title is a_state_1" - assert xml[1][0].text == "item title is a_state_2" - assert xml[1][1].text == "desc a_state_3" + feed_title = xml.find("./channel/title").text + item_title = xml.find("./channel/item/title").text + item_description = xml.find("./channel/item/description").text + assert feed_title == "feed title is a_state_1" + assert item_title == "item title is a_state_2" + assert item_description == "desc a_state_3" From 496583bca576485e946711f9c1ed9095b99fddc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Klomp?= Date: Sat, 19 Feb 2022 20:27:06 +0100 Subject: [PATCH 0838/1098] Prefix sma sensor name (#65234) Co-authored-by: Franck Nijhof --- homeassistant/components/sma/sensor.py | 7 ++++++- tests/components/sma/test_sensor.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sma/sensor.py b/homeassistant/components/sma/sensor.py index 0d90d89ce985ad..b06ec499a24cdb 100644 --- a/homeassistant/components/sma/sensor.py +++ b/homeassistant/components/sma/sensor.py @@ -84,7 +84,12 @@ def __init__( @property def name(self) -> str: """Return the name of the sensor.""" - return self._sensor.name + if self._attr_device_info is None or not ( + name_prefix := self._attr_device_info.get("name") + ): + name_prefix = "SMA" + + return f"{name_prefix} {self._sensor.name}" @property def native_value(self) -> StateType: diff --git a/tests/components/sma/test_sensor.py b/tests/components/sma/test_sensor.py index 58fafe930c7a67..9e4149b0720356 100644 --- a/tests/components/sma/test_sensor.py +++ b/tests/components/sma/test_sensor.py @@ -4,6 +4,6 @@ async def test_sensors(hass, init_integration): """Test states of the sensors.""" - state = hass.states.get("sensor.grid_power") + state = hass.states.get("sensor.sma_device_grid_power") assert state assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == POWER_WATT From 1c9f05e6d8bea7dfc7f3914fb02f6425e8aae6d1 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 19 Feb 2022 21:52:33 +0100 Subject: [PATCH 0839/1098] Bump pysensibo to v1.0.5 (#66906) --- homeassistant/components/sensibo/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensibo/manifest.json b/homeassistant/components/sensibo/manifest.json index e17c5339d67d41..4d41a6e3ca05c6 100644 --- a/homeassistant/components/sensibo/manifest.json +++ b/homeassistant/components/sensibo/manifest.json @@ -2,7 +2,7 @@ "domain": "sensibo", "name": "Sensibo", "documentation": "https://www.home-assistant.io/integrations/sensibo", - "requirements": ["pysensibo==1.0.4"], + "requirements": ["pysensibo==1.0.5"], "config_flow": true, "codeowners": ["@andrey-git", "@gjohansson-ST"], "iot_class": "cloud_polling", diff --git a/requirements_all.txt b/requirements_all.txt index 19a3d64dfaef08..36acde013781dc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1818,7 +1818,7 @@ pysaj==0.0.16 pysdcp==1 # homeassistant.components.sensibo -pysensibo==1.0.4 +pysensibo==1.0.5 # homeassistant.components.serial # homeassistant.components.zha diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f0aae26d5f0263..b4716b5928b316 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1160,7 +1160,7 @@ pyrituals==0.0.6 pyruckus==0.12 # homeassistant.components.sensibo -pysensibo==1.0.4 +pysensibo==1.0.5 # homeassistant.components.serial # homeassistant.components.zha From 82950dc037eccbca92641c552e1b20e579b5125c Mon Sep 17 00:00:00 2001 From: Austin Mroczek Date: Sat, 19 Feb 2022 12:53:52 -0800 Subject: [PATCH 0840/1098] bump total_connect_client to 2022.2.1 (#66907) --- homeassistant/components/totalconnect/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/totalconnect/manifest.json b/homeassistant/components/totalconnect/manifest.json index 530d45750f5c05..d2a77080672544 100644 --- a/homeassistant/components/totalconnect/manifest.json +++ b/homeassistant/components/totalconnect/manifest.json @@ -2,7 +2,7 @@ "domain": "totalconnect", "name": "Total Connect", "documentation": "https://www.home-assistant.io/integrations/totalconnect", - "requirements": ["total_connect_client==2022.2"], + "requirements": ["total_connect_client==2022.2.1"], "dependencies": [], "codeowners": ["@austinmroczek"], "config_flow": true, diff --git a/requirements_all.txt b/requirements_all.txt index 36acde013781dc..df323cbd74bedf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2374,7 +2374,7 @@ tololib==0.1.0b3 toonapi==0.2.1 # homeassistant.components.totalconnect -total_connect_client==2022.2 +total_connect_client==2022.2.1 # homeassistant.components.tplink_lte tp-connected==0.0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b4716b5928b316..201c2361c0f2ac 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1453,7 +1453,7 @@ tololib==0.1.0b3 toonapi==0.2.1 # homeassistant.components.totalconnect -total_connect_client==2022.2 +total_connect_client==2022.2.1 # homeassistant.components.transmission transmissionrpc==0.11 From 273b8de9941c3488853f735ed2d079b4adb530ca Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Sat, 19 Feb 2022 16:42:57 -0600 Subject: [PATCH 0841/1098] Update rokuecp to 0.14.1 (#66894) --- homeassistant/components/roku/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/roku/manifest.json b/homeassistant/components/roku/manifest.json index d03aa9846c3c55..4918e7742be175 100644 --- a/homeassistant/components/roku/manifest.json +++ b/homeassistant/components/roku/manifest.json @@ -2,7 +2,7 @@ "domain": "roku", "name": "Roku", "documentation": "https://www.home-assistant.io/integrations/roku", - "requirements": ["rokuecp==0.14.0"], + "requirements": ["rokuecp==0.14.1"], "homekit": { "models": ["3810X", "4660X", "7820X", "C105X", "C135X"] }, diff --git a/requirements_all.txt b/requirements_all.txt index df323cbd74bedf..b743bc8a55dcad 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2117,7 +2117,7 @@ rjpl==0.3.6 rocketchat-API==0.6.1 # homeassistant.components.roku -rokuecp==0.14.0 +rokuecp==0.14.1 # homeassistant.components.roomba roombapy==1.6.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 201c2361c0f2ac..21aca60f3bd2a9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1312,7 +1312,7 @@ rflink==0.0.62 ring_doorbell==0.7.2 # homeassistant.components.roku -rokuecp==0.14.0 +rokuecp==0.14.1 # homeassistant.components.roomba roombapy==1.6.5 From c4cc6ca0ba512d63c7647e77a97db6b3ec337cea Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 20 Feb 2022 00:20:09 +0000 Subject: [PATCH 0842/1098] [ci skip] Translation update --- .../components/abode/translations/sk.json | 22 +++++++ .../accuweather/translations/sensor.el.json | 9 +++ .../accuweather/translations/sk.json | 17 ++++++ .../components/adax/translations/el.json | 1 + .../components/adax/translations/sk.json | 10 ++++ .../components/adguard/translations/sk.json | 11 ++++ .../advantage_air/translations/el.json | 1 + .../advantage_air/translations/sk.json | 11 ++++ .../components/aemet/translations/sk.json | 16 ++++++ .../components/agent_dvr/translations/sk.json | 14 +++++ .../components/airly/translations/sk.json | 17 ++++++ .../components/airnow/translations/el.json | 5 ++ .../components/airnow/translations/sk.json | 16 ++++++ .../components/airthings/translations/sk.json | 7 +++ .../components/airtouch4/translations/el.json | 3 + .../components/airvisual/translations/sk.json | 29 ++++++++++ .../alarm_control_panel/translations/el.json | 4 ++ .../alarmdecoder/translations/sk.json | 11 ++++ .../components/ambee/translations/cs.json | 15 +++++ .../components/ambee/translations/sk.json | 25 ++++++++ .../amberelectric/translations/el.json | 6 +- .../ambiclimate/translations/sk.json | 7 +++ .../ambient_station/translations/sk.json | 14 +++++ .../components/androidtv/translations/sk.json | 14 +++++ .../components/apple_tv/translations/sk.json | 11 ++++ .../components/arcam_fmj/translations/sk.json | 14 +++++ .../aseko_pool_live/translations/sk.json | 14 +++++ .../components/asuswrt/translations/sk.json | 12 ++++ .../components/atag/translations/sk.json | 11 ++++ .../components/august/translations/sk.json | 10 ++++ .../components/aurora/translations/sk.json | 13 +++++ .../aurora_abb_powerone/translations/cs.json | 7 +++ .../aussie_broadband/translations/el.json | 3 + .../aussie_broadband/translations/he.json | 6 ++ .../aussie_broadband/translations/pl.json | 7 +++ .../aussie_broadband/translations/sk.json | 15 +++++ .../components/awair/translations/sk.json | 21 +++++++ .../components/axis/translations/el.json | 1 + .../components/axis/translations/sk.json | 15 +++++ .../azure_devops/translations/sk.json | 10 ++++ .../components/balboa/translations/cs.json | 7 +++ .../components/blebox/translations/el.json | 3 + .../components/blebox/translations/sk.json | 11 ++++ .../components/blink/translations/sk.json | 7 +++ .../bmw_connected_drive/translations/sk.json | 7 +++ .../components/bond/translations/cs.json | 2 +- .../components/bond/translations/sk.json | 19 +++++++ .../components/bosch_shc/translations/cs.json | 11 ++++ .../components/bosch_shc/translations/sk.json | 10 ++++ .../components/broadlink/translations/el.json | 1 + .../components/broadlink/translations/sk.json | 14 +++++ .../components/brother/translations/el.json | 1 + .../components/brunt/translations/cs.json | 11 ++++ .../components/brunt/translations/sk.json | 10 ++++ .../components/bsblan/translations/sk.json | 11 ++++ .../buienradar/translations/cs.json | 26 +++++++++ .../buienradar/translations/sk.json | 21 +++++++ .../components/cast/translations/sk.json | 9 +++ .../cert_expiry/translations/el.json | 1 + .../cert_expiry/translations/sk.json | 11 ++++ .../components/climacell/translations/sk.json | 17 ++++++ .../cloudflare/translations/cs.json | 5 +- .../cloudflare/translations/sk.json | 41 +++++++++++++ .../components/co2signal/translations/cs.json | 8 ++- .../components/co2signal/translations/sk.json | 34 +++++++++++ .../components/coinbase/translations/sk.json | 14 +++++ .../components/control4/translations/el.json | 10 ++++ .../components/control4/translations/sk.json | 7 +++ .../coolmaster/translations/el.json | 1 + .../crownstone/translations/cs.json | 3 +- .../crownstone/translations/sk.json | 14 +++++ .../components/daikin/translations/el.json | 1 + .../components/daikin/translations/sk.json | 15 +++++ .../components/deconz/translations/sk.json | 14 +++++ .../components/denonavr/translations/el.json | 7 +++ .../components/denonavr/translations/sk.json | 7 +++ .../devolo_home_control/translations/sk.json | 22 +++++++ .../devolo_home_network/translations/cs.json | 7 +++ .../devolo_home_network/translations/el.json | 5 ++ .../components/dexcom/translations/sk.json | 7 +++ .../dialogflow/translations/el.json | 1 + .../components/directv/translations/el.json | 5 ++ .../components/dnsip/translations/el.json | 3 +- .../components/doorbird/translations/sk.json | 7 +++ .../components/dsmr/translations/cs.json | 5 ++ .../components/dsmr/translations/sk.json | 11 ++++ .../components/ecobee/translations/sk.json | 11 ++++ .../components/econet/translations/sk.json | 17 ++++++ .../components/efergy/translations/sk.json | 17 ++++++ .../components/elgato/translations/sk.json | 11 ++++ .../components/elkm1/translations/sk.json | 10 ++++ .../components/elmax/translations/el.json | 1 + .../components/elmax/translations/sk.json | 8 +++ .../emulated_roku/translations/el.json | 1 + .../emulated_roku/translations/sk.json | 11 ++++ .../enphase_envoy/translations/sk.json | 10 ++++ .../environment_canada/translations/sk.json | 12 ++++ .../components/epson/translations/sk.json | 11 ++++ .../components/esphome/translations/cs.json | 15 ++++- .../components/esphome/translations/sk.json | 46 +++++++++++++++ .../evil_genius_labs/translations/cs.json | 7 +++ .../components/ezviz/translations/sk.json | 7 +++ .../fireservicerota/translations/sk.json | 13 +++++ .../components/fivem/translations/sk.json | 12 ++++ .../flick_electric/translations/sk.json | 7 +++ .../components/flipr/translations/sk.json | 14 +++++ .../components/flo/translations/el.json | 1 + .../components/flo/translations/sk.json | 7 +++ .../components/flume/translations/cs.json | 3 +- .../components/flume/translations/sk.json | 10 ++++ .../flunearyou/translations/sk.json | 12 ++++ .../components/flux_led/translations/sk.json | 7 +++ .../forecast_solar/translations/cs.json | 20 ++++++- .../forecast_solar/translations/sk.json | 31 ++++++++++ .../components/foscam/translations/sk.json | 18 ++++++ .../components/freebox/translations/el.json | 1 + .../components/freebox/translations/sk.json | 11 ++++ .../freedompro/translations/el.json | 10 ++++ .../freedompro/translations/sk.json | 14 +++++ .../components/fritz/translations/cs.json | 5 ++ .../components/fritz/translations/sk.json | 24 ++++++++ .../components/fritzbox/translations/sk.json | 11 ++++ .../fritzbox_callmonitor/translations/sk.json | 14 +++++ .../components/fronius/translations/cs.json | 3 + .../garages_amsterdam/translations/cs.json | 7 +++ .../components/geofency/translations/el.json | 1 + .../components/gios/translations/sk.json | 11 ++++ .../components/github/translations/sk.json | 7 +++ .../components/glances/translations/el.json | 1 + .../components/glances/translations/sk.json | 12 ++++ .../components/goalzero/translations/cs.json | 3 +- .../components/goalzero/translations/sk.json | 11 ++++ .../components/gogogate2/translations/el.json | 1 + .../components/gogogate2/translations/sk.json | 7 +++ .../components/goodwe/translations/el.json | 3 + .../components/goodwe/translations/sk.json | 7 +++ .../google_travel_time/translations/sk.json | 15 +++++ .../components/gpslogger/translations/el.json | 1 + .../growatt_server/translations/cs.json | 4 ++ .../growatt_server/translations/sk.json | 14 +++++ .../components/guardian/translations/el.json | 3 + .../components/guardian/translations/sk.json | 14 +++++ .../components/habitica/translations/sk.json | 14 +++++ .../components/hangouts/translations/sk.json | 12 ++++ .../components/harmony/translations/el.json | 1 + .../components/heos/translations/el.json | 3 + .../components/hive/translations/sk.json | 7 +++ .../components/hlk_sw16/translations/cs.json | 2 +- .../components/hlk_sw16/translations/el.json | 1 + .../components/hlk_sw16/translations/sk.json | 7 +++ .../home_connect/translations/sk.json | 7 +++ .../home_plus_control/translations/sk.json | 10 ++++ .../homekit_controller/translations/sk.json | 7 +++ .../homematicip_cloud/translations/sk.json | 11 ++++ .../components/honeywell/translations/sk.json | 7 +++ .../huawei_lte/translations/sk.json | 10 ++++ .../components/hue/translations/cs.json | 15 ++++- .../components/hue/translations/el.json | 2 + .../components/hue/translations/sk.json | 15 ++++- .../huisbaasje/translations/sk.json | 7 +++ .../translations/el.json | 3 + .../hvv_departures/translations/sk.json | 7 +++ .../components/hyperion/translations/el.json | 1 + .../components/hyperion/translations/sk.json | 15 +++++ .../components/ialarm/translations/sk.json | 11 ++++ .../components/iaqualink/translations/sk.json | 7 +++ .../components/icloud/translations/sk.json | 17 ++++++ .../components/ifttt/translations/el.json | 1 + .../components/insteon/translations/el.json | 2 + .../components/insteon/translations/sk.json | 25 ++++++++ .../components/ios/translations/sk.json | 9 +++ .../components/iotawatt/translations/el.json | 5 ++ .../components/iotawatt/translations/sk.json | 7 +++ .../components/ipma/translations/sk.json | 14 +++++ .../components/ipp/translations/el.json | 1 + .../components/ipp/translations/sk.json | 11 ++++ .../components/isy994/translations/sk.json | 7 +++ .../components/jellyfin/translations/cs.json | 8 +++ .../components/jellyfin/translations/sk.json | 7 +++ .../components/juicenet/translations/sk.json | 7 +++ .../keenetic_ndms2/translations/sk.json | 11 ++++ .../components/kmtronic/translations/sk.json | 7 +++ .../components/knx/translations/cs.json | 23 ++++++++ .../components/knx/translations/sk.json | 23 ++++++++ .../components/kodi/translations/el.json | 1 + .../components/kodi/translations/sk.json | 28 +++++++++ .../components/konnected/translations/sk.json | 33 +++++++++++ .../kostal_plenticore/translations/sk.json | 7 +++ .../components/life360/translations/sk.json | 10 ++++ .../components/litejet/translations/sk.json | 11 ++++ .../litterrobot/translations/sk.json | 7 +++ .../components/locative/translations/el.json | 1 + .../logi_circle/translations/sk.json | 7 +++ .../components/lookin/translations/cs.json | 17 ++++++ .../components/lookin/translations/el.json | 5 ++ .../components/lookin/translations/sk.json | 14 +++++ .../lutron_caseta/translations/el.json | 5 ++ .../components/lyric/translations/sk.json | 10 ++++ .../components/mailgun/translations/el.json | 1 + .../components/mazda/translations/el.json | 3 +- .../components/mazda/translations/sk.json | 17 ++++++ .../components/melcloud/translations/sk.json | 14 +++++ .../components/met/translations/cs.json | 3 + .../components/met/translations/sk.json | 22 +++++++ .../met_eireann/translations/sk.json | 15 +++++ .../meteo_france/translations/el.json | 9 +++ .../meteoclimatic/translations/cs.json | 7 +++ .../components/metoffice/translations/sk.json | 13 +++++ .../components/mikrotik/translations/sk.json | 15 +++++ .../components/mill/translations/el.json | 3 + .../minecraft_server/translations/el.json | 3 + .../minecraft_server/translations/sk.json | 11 ++++ .../components/mjpeg/translations/cs.json | 26 +++++++++ .../components/mjpeg/translations/el.json | 11 ++++ .../components/mjpeg/translations/he.json | 38 +++++++++++++ .../components/mjpeg/translations/pl.json | 42 ++++++++++++++ .../components/mjpeg/translations/sk.json | 26 +++++++++ .../mjpeg/translations/zh-Hant.json | 42 ++++++++++++++ .../mobile_app/translations/cs.json | 3 +- .../mobile_app/translations/sk.json | 18 ++++++ .../modem_callerid/translations/sk.json | 15 +++++ .../components/monoprice/translations/sk.json | 11 ++++ .../motion_blinds/translations/el.json | 9 +++ .../motion_blinds/translations/sk.json | 19 +++++++ .../components/motioneye/translations/el.json | 3 +- .../components/motioneye/translations/sk.json | 10 ++++ .../components/mqtt/translations/el.json | 6 ++ .../components/mqtt/translations/sk.json | 26 +++++++++ .../components/myq/translations/sk.json | 10 ++++ .../components/mysensors/translations/sk.json | 10 ++++ .../components/nam/translations/cs.json | 11 ++++ .../components/nam/translations/sk.json | 10 ++++ .../components/nanoleaf/translations/el.json | 5 ++ .../components/nanoleaf/translations/sk.json | 7 +++ .../components/neato/translations/sk.json | 10 ++++ .../components/nest/translations/cs.json | 5 ++ .../components/nest/translations/sk.json | 17 ++++++ .../components/netatmo/translations/cs.json | 15 +++++ .../components/netatmo/translations/el.json | 1 + .../components/netatmo/translations/sk.json | 27 +++++++++ .../components/netgear/translations/sk.json | 11 ++++ .../components/nexia/translations/sk.json | 7 +++ .../nfandroidtv/translations/el.json | 3 + .../nfandroidtv/translations/sk.json | 11 ++++ .../nightscout/translations/sk.json | 14 +++++ .../nmap_tracker/translations/el.json | 1 + .../components/notion/translations/sk.json | 10 ++++ .../components/nuheat/translations/sk.json | 7 +++ .../components/nuki/translations/sk.json | 23 ++++++++ .../components/nut/translations/sk.json | 49 ++++++++++++++++ .../components/nws/translations/sk.json | 13 +++++ .../components/nzbget/translations/el.json | 1 + .../components/nzbget/translations/sk.json | 12 ++++ .../components/octoprint/translations/cs.json | 6 ++ .../components/omnilogic/translations/sk.json | 7 +++ .../components/oncue/translations/sk.json | 7 +++ .../ondilo_ico/translations/sk.json | 7 +++ .../components/onewire/translations/sk.json | 25 ++++++++ .../components/onvif/translations/el.json | 9 ++- .../components/onvif/translations/sk.json | 28 +++++++++ .../opengarage/translations/el.json | 1 + .../opengarage/translations/sk.json | 14 +++++ .../opentherm_gw/translations/sk.json | 11 ++++ .../components/openuv/translations/cs.json | 11 ++++ .../components/openuv/translations/sk.json | 32 +++++++++++ .../openweathermap/translations/sk.json | 19 +++++++ .../components/overkiz/translations/cs.json | 3 +- .../components/overkiz/translations/sk.json | 10 ++++ .../ovo_energy/translations/sk.json | 7 +++ .../components/owntracks/translations/el.json | 1 + .../components/ozw/translations/sk.json | 7 +++ .../p1_monitor/translations/el.json | 1 + .../p1_monitor/translations/sk.json | 11 ++++ .../panasonic_viera/translations/el.json | 1 + .../panasonic_viera/translations/sk.json | 11 ++++ .../philips_js/translations/sk.json | 2 +- .../components/pi_hole/translations/sk.json | 22 +++++++ .../components/picnic/translations/cs.json | 1 + .../components/picnic/translations/sk.json | 17 ++++++ .../components/plaato/translations/el.json | 1 + .../components/plex/translations/cs.json | 6 +- .../components/plex/translations/sk.json | 19 +++++++ .../components/plugwise/translations/el.json | 4 +- .../components/plugwise/translations/sk.json | 14 +++++ .../plum_lightpad/translations/sk.json | 11 ++++ .../components/point/translations/sk.json | 7 +++ .../components/poolsense/translations/sk.json | 14 +++++ .../components/powerwall/translations/sk.json | 10 ++++ .../progettihwsw/translations/el.json | 1 + .../progettihwsw/translations/sk.json | 11 ++++ .../components/prosegur/translations/sk.json | 10 ++++ .../components/ps4/translations/sk.json | 14 +++++ .../pure_energie/translations/el.json | 16 ++++++ .../pure_energie/translations/en.json | 2 +- .../pure_energie/translations/et.json | 23 ++++++++ .../pure_energie/translations/pt-BR.json | 23 ++++++++ .../pure_energie/translations/ru.json | 23 ++++++++ .../components/pvoutput/translations/sk.json | 22 +++++++ .../components/rachio/translations/sk.json | 14 +++++ .../rainforest_eagle/translations/el.json | 1 + .../rainforest_eagle/translations/sk.json | 7 +++ .../rainmachine/translations/sk.json | 15 +++++ .../components/renault/translations/sk.json | 17 ++++++ .../components/rfxtrx/translations/sk.json | 11 ++++ .../components/ridwell/translations/cs.json | 11 ++++ .../components/ridwell/translations/sk.json | 10 ++++ .../components/ring/translations/el.json | 6 ++ .../components/ring/translations/sk.json | 7 +++ .../components/risco/translations/sk.json | 7 +++ .../translations/sk.json | 14 +++++ .../components/roku/translations/el.json | 4 ++ .../components/roku/translations/sk.json | 7 +++ .../components/roomba/translations/el.json | 3 + .../components/roon/translations/el.json | 3 + .../components/roon/translations/sk.json | 7 +++ .../components/rpi_power/translations/cs.json | 4 +- .../components/rpi_power/translations/sk.json | 13 +++++ .../ruckus_unleashed/translations/sk.json | 7 +++ .../components/samsungtv/translations/el.json | 3 + .../components/samsungtv/translations/sk.json | 15 +++++ .../screenlogic/translations/sk.json | 11 ++++ .../components/sense/translations/sk.json | 14 +++++ .../components/sensibo/translations/sk.json | 12 ++++ .../components/sharkiq/translations/sk.json | 10 ++++ .../components/shelly/translations/cs.json | 7 ++- .../components/shelly/translations/el.json | 3 + .../components/shelly/translations/sk.json | 50 ++++++++++++++++ .../components/sia/translations/cs.json | 14 +++++ .../components/sia/translations/el.json | 2 + .../components/sia/translations/sk.json | 11 ++++ .../simplisafe/translations/sk.json | 18 ++++++ .../components/sleepiq/translations/pl.json | 19 +++++++ .../components/sleepiq/translations/sk.json | 7 +++ .../components/sma/translations/sk.json | 10 ++++ .../smart_meter_texas/translations/sk.json | 7 +++ .../components/smarthab/translations/sk.json | 14 +++++ .../smartthings/translations/el.json | 3 + .../smartthings/translations/sk.json | 16 ++++++ .../components/smarttub/translations/sk.json | 17 ++++++ .../components/smhi/translations/sk.json | 13 +++++ .../components/sms/translations/el.json | 12 ++++ .../components/solaredge/translations/sk.json | 14 +++++ .../components/solarlog/translations/el.json | 1 + .../components/solax/translations/cs.json | 14 +++++ .../components/solax/translations/el.json | 1 + .../components/solax/translations/sk.json | 11 ++++ .../components/soma/translations/el.json | 1 + .../components/soma/translations/sk.json | 14 +++++ .../components/somfy/translations/sk.json | 7 +++ .../somfy_mylink/translations/el.json | 1 + .../somfy_mylink/translations/sk.json | 14 +++++ .../components/sonarr/translations/sk.json | 18 ++++++ .../components/spider/translations/sk.json | 7 +++ .../components/spotify/translations/cs.json | 8 ++- .../components/spotify/translations/sk.json | 12 ++++ .../squeezebox/translations/el.json | 3 +- .../squeezebox/translations/sk.json | 14 +++++ .../srp_energy/translations/sk.json | 7 +++ .../components/steamist/translations/sk.json | 7 +++ .../components/subaru/translations/sk.json | 7 +++ .../surepetcare/translations/sk.json | 7 +++ .../components/switchbot/translations/sk.json | 11 ++++ .../components/syncthing/translations/cs.json | 10 ++++ .../components/syncthing/translations/sk.json | 7 +++ .../components/syncthru/translations/sk.json | 16 ++++++ .../synology_dsm/translations/sk.json | 22 +++++++ .../system_bridge/translations/cs.json | 19 +++++++ .../system_bridge/translations/sk.json | 23 ++++++++ .../components/tado/translations/sk.json | 7 +++ .../components/tailscale/translations/cs.json | 10 ++++ .../components/tailscale/translations/sk.json | 22 +++++++ .../tellduslive/translations/sk.json | 7 +++ .../tesla_wall_connector/translations/cs.json | 7 +++ .../components/tibber/translations/sk.json | 11 ++++ .../components/tile/translations/sk.json | 17 ++++++ .../totalconnect/translations/sk.json | 17 ++++++ .../components/tplink/translations/el.json | 3 + .../components/traccar/translations/el.json | 1 + .../components/tractive/translations/sk.json | 17 ++++++ .../components/tradfri/translations/sk.json | 8 +++ .../translations/sk.json | 14 +++++ .../transmission/translations/cs.json | 2 +- .../transmission/translations/el.json | 1 + .../transmission/translations/sk.json | 16 ++++++ .../components/tuya/translations/cs.json | 5 ++ .../components/tuya/translations/el.json | 9 ++- .../components/tuya/translations/sk.json | 12 ++++ .../components/twilio/translations/el.json | 1 + .../components/unifi/translations/sk.json | 19 +++++++ .../unifiprotect/translations/ru.json | 2 +- .../unifiprotect/translations/sk.json | 19 +++++++ .../components/upcloud/translations/sk.json | 7 +++ .../components/upnp/translations/sk.json | 7 +++ .../uptimerobot/translations/sk.json | 22 +++++++ .../components/vallox/translations/cs.json | 8 +++ .../components/vallox/translations/sk.json | 11 ++++ .../components/venstar/translations/cs.json | 7 +++ .../components/vera/translations/el.json | 5 ++ .../components/verisure/translations/sk.json | 22 +++++++ .../components/vesync/translations/sk.json | 14 +++++ .../components/vicare/translations/sk.json | 16 ++++++ .../components/vilfo/translations/el.json | 3 + .../components/vilfo/translations/sk.json | 14 +++++ .../components/vizio/translations/cs.json | 2 +- .../components/vizio/translations/el.json | 9 ++- .../components/vizio/translations/sk.json | 12 ++++ .../vlc_telnet/translations/sk.json | 19 +++++++ .../components/volumio/translations/el.json | 5 ++ .../components/volumio/translations/ru.json | 2 +- .../components/volumio/translations/sk.json | 11 ++++ .../components/wallbox/translations/cs.json | 11 ++++ .../components/wallbox/translations/sk.json | 10 ++++ .../components/watttime/translations/sk.json | 23 ++++++++ .../waze_travel_time/translations/sk.json | 14 +++++ .../components/webostv/translations/cs.json | 41 ++++++++++++- .../components/webostv/translations/sk.json | 46 +++++++++++++++ .../components/whirlpool/translations/sk.json | 7 +++ .../components/wiffi/translations/sk.json | 11 ++++ .../components/withings/translations/el.json | 3 + .../components/wiz/translations/el.json | 1 + .../components/wiz/translations/sk.json | 11 ++++ .../components/wolflink/translations/el.json | 9 ++- .../wolflink/translations/sensor.el.json | 12 ++++ .../components/wolflink/translations/sk.json | 7 +++ .../components/xbox/translations/sk.json | 7 +++ .../xiaomi_aqara/translations/el.json | 3 + .../xiaomi_aqara/translations/sk.json | 8 +++ .../xiaomi_miio/translations/cs.json | 57 ++++++++++++++++++- .../xiaomi_miio/translations/el.json | 4 ++ .../xiaomi_miio/translations/sk.json | 26 +++++++++ .../yale_smart_alarm/translations/sk.json | 22 +++++++ .../translations/select.el.json | 44 +++++++++++++- .../components/yeelight/translations/sk.json | 7 +++ .../components/youless/translations/el.json | 1 + .../components/youless/translations/sk.json | 11 ++++ .../components/zone/translations/sk.json | 12 ++++ .../zoneminder/translations/sk.json | 10 ++++ .../components/zwave/translations/sk.json | 5 ++ .../components/zwave_js/translations/el.json | 6 ++ .../components/zwave_js/translations/sk.json | 13 +++++ 440 files changed, 4791 insertions(+), 46 deletions(-) create mode 100644 homeassistant/components/abode/translations/sk.json create mode 100644 homeassistant/components/accuweather/translations/sensor.el.json create mode 100644 homeassistant/components/accuweather/translations/sk.json create mode 100644 homeassistant/components/adax/translations/sk.json create mode 100644 homeassistant/components/adguard/translations/sk.json create mode 100644 homeassistant/components/advantage_air/translations/sk.json create mode 100644 homeassistant/components/aemet/translations/sk.json create mode 100644 homeassistant/components/agent_dvr/translations/sk.json create mode 100644 homeassistant/components/airly/translations/sk.json create mode 100644 homeassistant/components/airnow/translations/sk.json create mode 100644 homeassistant/components/airthings/translations/sk.json create mode 100644 homeassistant/components/airvisual/translations/sk.json create mode 100644 homeassistant/components/alarmdecoder/translations/sk.json create mode 100644 homeassistant/components/ambee/translations/cs.json create mode 100644 homeassistant/components/ambee/translations/sk.json create mode 100644 homeassistant/components/ambiclimate/translations/sk.json create mode 100644 homeassistant/components/ambient_station/translations/sk.json create mode 100644 homeassistant/components/androidtv/translations/sk.json create mode 100644 homeassistant/components/apple_tv/translations/sk.json create mode 100644 homeassistant/components/arcam_fmj/translations/sk.json create mode 100644 homeassistant/components/aseko_pool_live/translations/sk.json create mode 100644 homeassistant/components/asuswrt/translations/sk.json create mode 100644 homeassistant/components/atag/translations/sk.json create mode 100644 homeassistant/components/august/translations/sk.json create mode 100644 homeassistant/components/aurora/translations/sk.json create mode 100644 homeassistant/components/aurora_abb_powerone/translations/cs.json create mode 100644 homeassistant/components/aussie_broadband/translations/sk.json create mode 100644 homeassistant/components/awair/translations/sk.json create mode 100644 homeassistant/components/axis/translations/sk.json create mode 100644 homeassistant/components/azure_devops/translations/sk.json create mode 100644 homeassistant/components/balboa/translations/cs.json create mode 100644 homeassistant/components/blebox/translations/sk.json create mode 100644 homeassistant/components/blink/translations/sk.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/sk.json create mode 100644 homeassistant/components/bond/translations/sk.json create mode 100644 homeassistant/components/bosch_shc/translations/cs.json create mode 100644 homeassistant/components/bosch_shc/translations/sk.json create mode 100644 homeassistant/components/broadlink/translations/sk.json create mode 100644 homeassistant/components/brunt/translations/cs.json create mode 100644 homeassistant/components/brunt/translations/sk.json create mode 100644 homeassistant/components/bsblan/translations/sk.json create mode 100644 homeassistant/components/buienradar/translations/cs.json create mode 100644 homeassistant/components/buienradar/translations/sk.json create mode 100644 homeassistant/components/cast/translations/sk.json create mode 100644 homeassistant/components/cert_expiry/translations/sk.json create mode 100644 homeassistant/components/climacell/translations/sk.json create mode 100644 homeassistant/components/cloudflare/translations/sk.json create mode 100644 homeassistant/components/co2signal/translations/sk.json create mode 100644 homeassistant/components/coinbase/translations/sk.json create mode 100644 homeassistant/components/control4/translations/sk.json create mode 100644 homeassistant/components/crownstone/translations/sk.json create mode 100644 homeassistant/components/daikin/translations/sk.json create mode 100644 homeassistant/components/deconz/translations/sk.json create mode 100644 homeassistant/components/denonavr/translations/sk.json create mode 100644 homeassistant/components/devolo_home_control/translations/sk.json create mode 100644 homeassistant/components/devolo_home_network/translations/cs.json create mode 100644 homeassistant/components/dexcom/translations/sk.json create mode 100644 homeassistant/components/doorbird/translations/sk.json create mode 100644 homeassistant/components/dsmr/translations/sk.json create mode 100644 homeassistant/components/ecobee/translations/sk.json create mode 100644 homeassistant/components/econet/translations/sk.json create mode 100644 homeassistant/components/efergy/translations/sk.json create mode 100644 homeassistant/components/elgato/translations/sk.json create mode 100644 homeassistant/components/elkm1/translations/sk.json create mode 100644 homeassistant/components/elmax/translations/sk.json create mode 100644 homeassistant/components/emulated_roku/translations/sk.json create mode 100644 homeassistant/components/enphase_envoy/translations/sk.json create mode 100644 homeassistant/components/environment_canada/translations/sk.json create mode 100644 homeassistant/components/epson/translations/sk.json create mode 100644 homeassistant/components/esphome/translations/sk.json create mode 100644 homeassistant/components/evil_genius_labs/translations/cs.json create mode 100644 homeassistant/components/ezviz/translations/sk.json create mode 100644 homeassistant/components/fireservicerota/translations/sk.json create mode 100644 homeassistant/components/fivem/translations/sk.json create mode 100644 homeassistant/components/flick_electric/translations/sk.json create mode 100644 homeassistant/components/flipr/translations/sk.json create mode 100644 homeassistant/components/flo/translations/sk.json create mode 100644 homeassistant/components/flume/translations/sk.json create mode 100644 homeassistant/components/flunearyou/translations/sk.json create mode 100644 homeassistant/components/flux_led/translations/sk.json create mode 100644 homeassistant/components/forecast_solar/translations/sk.json create mode 100644 homeassistant/components/foscam/translations/sk.json create mode 100644 homeassistant/components/freebox/translations/sk.json create mode 100644 homeassistant/components/freedompro/translations/el.json create mode 100644 homeassistant/components/freedompro/translations/sk.json create mode 100644 homeassistant/components/fritz/translations/sk.json create mode 100644 homeassistant/components/fritzbox/translations/sk.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/sk.json create mode 100644 homeassistant/components/garages_amsterdam/translations/cs.json create mode 100644 homeassistant/components/gios/translations/sk.json create mode 100644 homeassistant/components/github/translations/sk.json create mode 100644 homeassistant/components/glances/translations/sk.json create mode 100644 homeassistant/components/goalzero/translations/sk.json create mode 100644 homeassistant/components/gogogate2/translations/sk.json create mode 100644 homeassistant/components/goodwe/translations/sk.json create mode 100644 homeassistant/components/google_travel_time/translations/sk.json create mode 100644 homeassistant/components/growatt_server/translations/sk.json create mode 100644 homeassistant/components/guardian/translations/sk.json create mode 100644 homeassistant/components/habitica/translations/sk.json create mode 100644 homeassistant/components/hangouts/translations/sk.json create mode 100644 homeassistant/components/hive/translations/sk.json create mode 100644 homeassistant/components/hlk_sw16/translations/sk.json create mode 100644 homeassistant/components/home_connect/translations/sk.json create mode 100644 homeassistant/components/home_plus_control/translations/sk.json create mode 100644 homeassistant/components/homekit_controller/translations/sk.json create mode 100644 homeassistant/components/homematicip_cloud/translations/sk.json create mode 100644 homeassistant/components/honeywell/translations/sk.json create mode 100644 homeassistant/components/huawei_lte/translations/sk.json create mode 100644 homeassistant/components/huisbaasje/translations/sk.json create mode 100644 homeassistant/components/hvv_departures/translations/sk.json create mode 100644 homeassistant/components/hyperion/translations/sk.json create mode 100644 homeassistant/components/ialarm/translations/sk.json create mode 100644 homeassistant/components/iaqualink/translations/sk.json create mode 100644 homeassistant/components/icloud/translations/sk.json create mode 100644 homeassistant/components/insteon/translations/sk.json create mode 100644 homeassistant/components/ios/translations/sk.json create mode 100644 homeassistant/components/iotawatt/translations/sk.json create mode 100644 homeassistant/components/ipma/translations/sk.json create mode 100644 homeassistant/components/ipp/translations/sk.json create mode 100644 homeassistant/components/isy994/translations/sk.json create mode 100644 homeassistant/components/jellyfin/translations/cs.json create mode 100644 homeassistant/components/jellyfin/translations/sk.json create mode 100644 homeassistant/components/juicenet/translations/sk.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/sk.json create mode 100644 homeassistant/components/kmtronic/translations/sk.json create mode 100644 homeassistant/components/knx/translations/cs.json create mode 100644 homeassistant/components/knx/translations/sk.json create mode 100644 homeassistant/components/kodi/translations/sk.json create mode 100644 homeassistant/components/konnected/translations/sk.json create mode 100644 homeassistant/components/kostal_plenticore/translations/sk.json create mode 100644 homeassistant/components/life360/translations/sk.json create mode 100644 homeassistant/components/litejet/translations/sk.json create mode 100644 homeassistant/components/litterrobot/translations/sk.json create mode 100644 homeassistant/components/logi_circle/translations/sk.json create mode 100644 homeassistant/components/lookin/translations/cs.json create mode 100644 homeassistant/components/lookin/translations/sk.json create mode 100644 homeassistant/components/lyric/translations/sk.json create mode 100644 homeassistant/components/mazda/translations/sk.json create mode 100644 homeassistant/components/melcloud/translations/sk.json create mode 100644 homeassistant/components/met/translations/sk.json create mode 100644 homeassistant/components/met_eireann/translations/sk.json create mode 100644 homeassistant/components/meteoclimatic/translations/cs.json create mode 100644 homeassistant/components/metoffice/translations/sk.json create mode 100644 homeassistant/components/mikrotik/translations/sk.json create mode 100644 homeassistant/components/minecraft_server/translations/sk.json create mode 100644 homeassistant/components/mjpeg/translations/cs.json create mode 100644 homeassistant/components/mjpeg/translations/he.json create mode 100644 homeassistant/components/mjpeg/translations/pl.json create mode 100644 homeassistant/components/mjpeg/translations/sk.json create mode 100644 homeassistant/components/mjpeg/translations/zh-Hant.json create mode 100644 homeassistant/components/mobile_app/translations/sk.json create mode 100644 homeassistant/components/modem_callerid/translations/sk.json create mode 100644 homeassistant/components/monoprice/translations/sk.json create mode 100644 homeassistant/components/motion_blinds/translations/sk.json create mode 100644 homeassistant/components/motioneye/translations/sk.json create mode 100644 homeassistant/components/mqtt/translations/sk.json create mode 100644 homeassistant/components/myq/translations/sk.json create mode 100644 homeassistant/components/mysensors/translations/sk.json create mode 100644 homeassistant/components/nam/translations/cs.json create mode 100644 homeassistant/components/nam/translations/sk.json create mode 100644 homeassistant/components/nanoleaf/translations/sk.json create mode 100644 homeassistant/components/neato/translations/sk.json create mode 100644 homeassistant/components/nest/translations/sk.json create mode 100644 homeassistant/components/netatmo/translations/sk.json create mode 100644 homeassistant/components/netgear/translations/sk.json create mode 100644 homeassistant/components/nexia/translations/sk.json create mode 100644 homeassistant/components/nfandroidtv/translations/sk.json create mode 100644 homeassistant/components/nightscout/translations/sk.json create mode 100644 homeassistant/components/notion/translations/sk.json create mode 100644 homeassistant/components/nuheat/translations/sk.json create mode 100644 homeassistant/components/nuki/translations/sk.json create mode 100644 homeassistant/components/nut/translations/sk.json create mode 100644 homeassistant/components/nws/translations/sk.json create mode 100644 homeassistant/components/nzbget/translations/sk.json create mode 100644 homeassistant/components/omnilogic/translations/sk.json create mode 100644 homeassistant/components/oncue/translations/sk.json create mode 100644 homeassistant/components/ondilo_ico/translations/sk.json create mode 100644 homeassistant/components/onewire/translations/sk.json create mode 100644 homeassistant/components/onvif/translations/sk.json create mode 100644 homeassistant/components/opengarage/translations/sk.json create mode 100644 homeassistant/components/opentherm_gw/translations/sk.json create mode 100644 homeassistant/components/openuv/translations/sk.json create mode 100644 homeassistant/components/openweathermap/translations/sk.json create mode 100644 homeassistant/components/overkiz/translations/sk.json create mode 100644 homeassistant/components/ovo_energy/translations/sk.json create mode 100644 homeassistant/components/ozw/translations/sk.json create mode 100644 homeassistant/components/p1_monitor/translations/sk.json create mode 100644 homeassistant/components/panasonic_viera/translations/sk.json create mode 100644 homeassistant/components/pi_hole/translations/sk.json create mode 100644 homeassistant/components/picnic/translations/sk.json create mode 100644 homeassistant/components/plex/translations/sk.json create mode 100644 homeassistant/components/plugwise/translations/sk.json create mode 100644 homeassistant/components/plum_lightpad/translations/sk.json create mode 100644 homeassistant/components/point/translations/sk.json create mode 100644 homeassistant/components/poolsense/translations/sk.json create mode 100644 homeassistant/components/powerwall/translations/sk.json create mode 100644 homeassistant/components/progettihwsw/translations/sk.json create mode 100644 homeassistant/components/prosegur/translations/sk.json create mode 100644 homeassistant/components/ps4/translations/sk.json create mode 100644 homeassistant/components/pure_energie/translations/el.json create mode 100644 homeassistant/components/pure_energie/translations/et.json create mode 100644 homeassistant/components/pure_energie/translations/pt-BR.json create mode 100644 homeassistant/components/pure_energie/translations/ru.json create mode 100644 homeassistant/components/pvoutput/translations/sk.json create mode 100644 homeassistant/components/rachio/translations/sk.json create mode 100644 homeassistant/components/rainforest_eagle/translations/sk.json create mode 100644 homeassistant/components/rainmachine/translations/sk.json create mode 100644 homeassistant/components/renault/translations/sk.json create mode 100644 homeassistant/components/rfxtrx/translations/sk.json create mode 100644 homeassistant/components/ridwell/translations/cs.json create mode 100644 homeassistant/components/ridwell/translations/sk.json create mode 100644 homeassistant/components/ring/translations/sk.json create mode 100644 homeassistant/components/risco/translations/sk.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/sk.json create mode 100644 homeassistant/components/roku/translations/sk.json create mode 100644 homeassistant/components/roon/translations/sk.json create mode 100644 homeassistant/components/rpi_power/translations/sk.json create mode 100644 homeassistant/components/ruckus_unleashed/translations/sk.json create mode 100644 homeassistant/components/samsungtv/translations/sk.json create mode 100644 homeassistant/components/screenlogic/translations/sk.json create mode 100644 homeassistant/components/sense/translations/sk.json create mode 100644 homeassistant/components/sensibo/translations/sk.json create mode 100644 homeassistant/components/sharkiq/translations/sk.json create mode 100644 homeassistant/components/shelly/translations/sk.json create mode 100644 homeassistant/components/sia/translations/cs.json create mode 100644 homeassistant/components/sia/translations/sk.json create mode 100644 homeassistant/components/simplisafe/translations/sk.json create mode 100644 homeassistant/components/sleepiq/translations/pl.json create mode 100644 homeassistant/components/sleepiq/translations/sk.json create mode 100644 homeassistant/components/sma/translations/sk.json create mode 100644 homeassistant/components/smart_meter_texas/translations/sk.json create mode 100644 homeassistant/components/smarthab/translations/sk.json create mode 100644 homeassistant/components/smartthings/translations/sk.json create mode 100644 homeassistant/components/smarttub/translations/sk.json create mode 100644 homeassistant/components/smhi/translations/sk.json create mode 100644 homeassistant/components/sms/translations/el.json create mode 100644 homeassistant/components/solaredge/translations/sk.json create mode 100644 homeassistant/components/solax/translations/cs.json create mode 100644 homeassistant/components/solax/translations/sk.json create mode 100644 homeassistant/components/soma/translations/sk.json create mode 100644 homeassistant/components/somfy/translations/sk.json create mode 100644 homeassistant/components/somfy_mylink/translations/sk.json create mode 100644 homeassistant/components/sonarr/translations/sk.json create mode 100644 homeassistant/components/spider/translations/sk.json create mode 100644 homeassistant/components/spotify/translations/sk.json create mode 100644 homeassistant/components/squeezebox/translations/sk.json create mode 100644 homeassistant/components/srp_energy/translations/sk.json create mode 100644 homeassistant/components/steamist/translations/sk.json create mode 100644 homeassistant/components/subaru/translations/sk.json create mode 100644 homeassistant/components/surepetcare/translations/sk.json create mode 100644 homeassistant/components/switchbot/translations/sk.json create mode 100644 homeassistant/components/syncthing/translations/cs.json create mode 100644 homeassistant/components/syncthing/translations/sk.json create mode 100644 homeassistant/components/syncthru/translations/sk.json create mode 100644 homeassistant/components/synology_dsm/translations/sk.json create mode 100644 homeassistant/components/system_bridge/translations/cs.json create mode 100644 homeassistant/components/system_bridge/translations/sk.json create mode 100644 homeassistant/components/tado/translations/sk.json create mode 100644 homeassistant/components/tailscale/translations/cs.json create mode 100644 homeassistant/components/tailscale/translations/sk.json create mode 100644 homeassistant/components/tellduslive/translations/sk.json create mode 100644 homeassistant/components/tesla_wall_connector/translations/cs.json create mode 100644 homeassistant/components/tibber/translations/sk.json create mode 100644 homeassistant/components/tile/translations/sk.json create mode 100644 homeassistant/components/totalconnect/translations/sk.json create mode 100644 homeassistant/components/tractive/translations/sk.json create mode 100644 homeassistant/components/tradfri/translations/sk.json create mode 100644 homeassistant/components/trafikverket_weatherstation/translations/sk.json create mode 100644 homeassistant/components/transmission/translations/sk.json create mode 100644 homeassistant/components/unifi/translations/sk.json create mode 100644 homeassistant/components/unifiprotect/translations/sk.json create mode 100644 homeassistant/components/upcloud/translations/sk.json create mode 100644 homeassistant/components/upnp/translations/sk.json create mode 100644 homeassistant/components/uptimerobot/translations/sk.json create mode 100644 homeassistant/components/vallox/translations/sk.json create mode 100644 homeassistant/components/venstar/translations/cs.json create mode 100644 homeassistant/components/verisure/translations/sk.json create mode 100644 homeassistant/components/vesync/translations/sk.json create mode 100644 homeassistant/components/vicare/translations/sk.json create mode 100644 homeassistant/components/vilfo/translations/sk.json create mode 100644 homeassistant/components/vizio/translations/sk.json create mode 100644 homeassistant/components/vlc_telnet/translations/sk.json create mode 100644 homeassistant/components/volumio/translations/sk.json create mode 100644 homeassistant/components/wallbox/translations/cs.json create mode 100644 homeassistant/components/wallbox/translations/sk.json create mode 100644 homeassistant/components/watttime/translations/sk.json create mode 100644 homeassistant/components/waze_travel_time/translations/sk.json create mode 100644 homeassistant/components/webostv/translations/sk.json create mode 100644 homeassistant/components/whirlpool/translations/sk.json create mode 100644 homeassistant/components/wiffi/translations/sk.json create mode 100644 homeassistant/components/wiz/translations/sk.json create mode 100644 homeassistant/components/wolflink/translations/sk.json create mode 100644 homeassistant/components/xbox/translations/sk.json create mode 100644 homeassistant/components/xiaomi_aqara/translations/sk.json create mode 100644 homeassistant/components/xiaomi_miio/translations/sk.json create mode 100644 homeassistant/components/yale_smart_alarm/translations/sk.json create mode 100644 homeassistant/components/yeelight/translations/sk.json create mode 100644 homeassistant/components/youless/translations/sk.json create mode 100644 homeassistant/components/zone/translations/sk.json create mode 100644 homeassistant/components/zoneminder/translations/sk.json create mode 100644 homeassistant/components/zwave_js/translations/sk.json diff --git a/homeassistant/components/abode/translations/sk.json b/homeassistant/components/abode/translations/sk.json new file mode 100644 index 00000000000000..2230fa979b4594 --- /dev/null +++ b/homeassistant/components/abode/translations/sk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "reauth_confirm": { + "data": { + "username": "Email" + } + }, + "user": { + "data": { + "username": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/sensor.el.json b/homeassistant/components/accuweather/translations/sensor.el.json new file mode 100644 index 00000000000000..2e90f28e92ae3a --- /dev/null +++ b/homeassistant/components/accuweather/translations/sensor.el.json @@ -0,0 +1,9 @@ +{ + "state": { + "accuweather__pressure_tendency": { + "falling": "\u03a0\u03c4\u03ce\u03c3\u03b7", + "rising": "\u0391\u03c5\u03be\u03b1\u03bd\u03cc\u03bc\u03b5\u03bd\u03b7", + "steady": "\u03a3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/sk.json b/homeassistant/components/accuweather/translations/sk.json new file mode 100644 index 00000000000000..8e0bc629a1328a --- /dev/null +++ b/homeassistant/components/accuweather/translations/sk.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "invalid_api_key": "Neplatn\u00fd API k\u013e\u00fa\u010d" + }, + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d", + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka", + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/adax/translations/el.json b/homeassistant/components/adax/translations/el.json index 2f96095a97c760..328bfae922020a 100644 --- a/homeassistant/components/adax/translations/el.json +++ b/homeassistant/components/adax/translations/el.json @@ -22,6 +22,7 @@ "data": { "account_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd", "connection_type": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c4\u03cd\u03c0\u03bf\u03c5 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03cd\u03c0\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2. \u03a4\u03bf\u03c0\u03b9\u03ba\u03ae \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03b8\u03b5\u03c1\u03bc\u03ac\u03c3\u03c4\u03c1\u03b5\u03c2 \u03bc\u03b5 bluetooth" diff --git a/homeassistant/components/adax/translations/sk.json b/homeassistant/components/adax/translations/sk.json new file mode 100644 index 00000000000000..2c3ed1dd93049d --- /dev/null +++ b/homeassistant/components/adax/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/adguard/translations/sk.json b/homeassistant/components/adguard/translations/sk.json new file mode 100644 index 00000000000000..892b8b2cd91240 --- /dev/null +++ b/homeassistant/components/adguard/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/advantage_air/translations/el.json b/homeassistant/components/advantage_air/translations/el.json index 3513cc855dd004..146c37f16d1244 100644 --- a/homeassistant/components/advantage_air/translations/el.json +++ b/homeassistant/components/advantage_air/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "ip_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", "port": "\u0398\u03cd\u03c1\u03b1" }, "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf API \u03c4\u03bf\u03c5 \u03b5\u03c0\u03af\u03c4\u03bf\u03b9\u03c7\u03bf\u03c5 tablet Advantage Air.", diff --git a/homeassistant/components/advantage_air/translations/sk.json b/homeassistant/components/advantage_air/translations/sk.json new file mode 100644 index 00000000000000..892b8b2cd91240 --- /dev/null +++ b/homeassistant/components/advantage_air/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aemet/translations/sk.json b/homeassistant/components/aemet/translations/sk.json new file mode 100644 index 00000000000000..3c287c2d9d28ab --- /dev/null +++ b/homeassistant/components/aemet/translations/sk.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "invalid_api_key": "Neplatn\u00fd API k\u013e\u00fa\u010d" + }, + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d", + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/agent_dvr/translations/sk.json b/homeassistant/components/agent_dvr/translations/sk.json new file mode 100644 index 00000000000000..ba2680ac75e21c --- /dev/null +++ b/homeassistant/components/agent_dvr/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + }, + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airly/translations/sk.json b/homeassistant/components/airly/translations/sk.json new file mode 100644 index 00000000000000..8e0bc629a1328a --- /dev/null +++ b/homeassistant/components/airly/translations/sk.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "invalid_api_key": "Neplatn\u00fd API k\u013e\u00fa\u010d" + }, + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d", + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka", + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/el.json b/homeassistant/components/airnow/translations/el.json index e8968158682e69..8ca060f720e514 100644 --- a/homeassistant/components/airnow/translations/el.json +++ b/homeassistant/components/airnow/translations/el.json @@ -2,6 +2,11 @@ "config": { "error": { "invalid_location": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03b1\u03c0\u03bf\u03c4\u03b5\u03bb\u03ad\u03c3\u03bc\u03b1\u03c4\u03b1 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7\u03bd \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1" + }, + "step": { + "user": { + "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 AirNow \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c0\u03bf\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1 \u03c4\u03bf\u03c5 \u03b1\u03ad\u03c1\u03b1. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://docs.airnowapi.org/account/request/" + } } } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/sk.json b/homeassistant/components/airnow/translations/sk.json new file mode 100644 index 00000000000000..df686b2a565784 --- /dev/null +++ b/homeassistant/components/airnow/translations/sk.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d", + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airthings/translations/sk.json b/homeassistant/components/airthings/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/airthings/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airtouch4/translations/el.json b/homeassistant/components/airtouch4/translations/el.json index a54eab486e6668..7a94a9c6dfa5eb 100644 --- a/homeassistant/components/airtouch4/translations/el.json +++ b/homeassistant/components/airtouch4/translations/el.json @@ -5,6 +5,9 @@ }, "step": { "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "title": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 {intergration}." } } diff --git a/homeassistant/components/airvisual/translations/sk.json b/homeassistant/components/airvisual/translations/sk.json new file mode 100644 index 00000000000000..22c02bbfec39fb --- /dev/null +++ b/homeassistant/components/airvisual/translations/sk.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_api_key": "Neplatn\u00fd API k\u013e\u00fa\u010d" + }, + "step": { + "geography_by_coords": { + "data": { + "api_key": "API k\u013e\u00fa\u010d", + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka" + } + }, + "geography_by_name": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + }, + "reauth_confirm": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarm_control_panel/translations/el.json b/homeassistant/components/alarm_control_panel/translations/el.json index b0aeb95fcb9ea3..d01fe947a8e4d1 100644 --- a/homeassistant/components/alarm_control_panel/translations/el.json +++ b/homeassistant/components/alarm_control_panel/translations/el.json @@ -4,6 +4,7 @@ "arm_away": "\u039f\u03c0\u03bb\u03af\u03c3\u03c4\u03b5 \u03c3\u03b5 \u03b5\u03ba\u03c4\u03cc\u03c2 \u03c3\u03c0\u03b9\u03c4\u03b9\u03bf\u03cd \u03c4\u03bf {entity_name}", "arm_home": "\u039f\u03c0\u03bb\u03af\u03c3\u03c4\u03b5 \u03c3\u03b5 \u03c3\u03c0\u03af\u03c4\u03b9 \u03c4\u03bf {entity_name}", "arm_night": "\u039f\u03c0\u03bb\u03af\u03c3\u03c4\u03b5 \u03c3\u03b5 \u03b2\u03c1\u03ac\u03b4\u03c5 \u03c4\u03bf {entity_name}", + "arm_vacation": "\u039f\u03c0\u03bb\u03b9\u03c3\u03bc\u03cc\u03c2 {entity_name} \u03c3\u03b5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03c0\u03ad\u03c2", "disarm": "\u0391\u03c6\u03bf\u03c0\u03bb\u03b9\u03c3\u03bc\u03cc\u03c2 {entity_name}", "trigger": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 {entity_name}" }, @@ -11,6 +12,7 @@ "is_armed_away": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03bf\u03c0\u03bb\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf \u03b5\u03ba\u03c4\u03cc\u03c2", "is_armed_home": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03bf\u03c0\u03bb\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf \u03c3\u03b5 \u03c3\u03c0\u03af\u03c4\u03b9", "is_armed_night": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03bf\u03c0\u03bb\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf \u03c3\u03b5 \u03bd\u03cd\u03c7\u03c4\u03b1", + "is_armed_vacation": "{entity_name} \u03bf\u03c0\u03bb\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03c3\u03b5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03c0\u03ad\u03c2", "is_disarmed": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c6\u03bf\u03c0\u03bb\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf", "is_triggered": "{entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5" }, @@ -18,6 +20,7 @@ "armed_away": "{entity_name} \u03bf\u03c0\u03bb\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03b3\u03b9\u03b1 \u03b5\u03ba\u03c4\u03cc\u03c2 \u03c3\u03c0\u03b9\u03c4\u03b9\u03bf\u03cd", "armed_home": "{entity_name} \u03bf\u03c0\u03bb\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03b3\u03b9\u03b1 \u03c3\u03c0\u03af\u03c4\u03b9", "armed_night": "{entity_name} \u03bf\u03c0\u03bb\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03b3\u03b9\u03b1 \u03bd\u03cd\u03c7\u03c4\u03b1", + "armed_vacation": "{entity_name} \u03bf\u03c0\u03bb\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03c3\u03b5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03c0\u03ad\u03c2", "disarmed": "{entity_name} \u03b1\u03c6\u03bf\u03c0\u03bb\u03af\u03c3\u03c4\u03b7\u03ba\u03b5", "triggered": "{entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5" } @@ -29,6 +32,7 @@ "armed_custom_bypass": "\u03a0\u03c1\u03bf\u03c3\u03b1\u03c1\u03bc\u03bf\u03c3\u03bc\u03ad\u03bd\u03b7 \u03c0\u03b1\u03c1\u03ac\u03ba\u03b1\u03bc\u03c8\u03b7 \u03b5\u03bd\u03b5\u03c1\u03b3\u03ae", "armed_home": "\u03a3\u03c0\u03af\u03c4\u03b9 \u039f\u03c0\u03bb\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf", "armed_night": "\u039f\u03c0\u03bb\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf \u03b2\u03c1\u03ac\u03b4\u03c5", + "armed_vacation": "\u039f\u03c0\u03bb\u03b9\u03c3\u03bc\u03cc\u03c2 \u03b4\u03b9\u03b1\u03ba\u03bf\u03c0\u03ce\u03bd", "arming": "\u038c\u03c0\u03bb\u03b9\u03c3\u03b7", "disarmed": "\u0391\u03c6\u03bf\u03c0\u03bb\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2", "disarming": "\u0391\u03c6\u03cc\u03c0\u03bb\u03b9\u03c3\u03b7", diff --git a/homeassistant/components/alarmdecoder/translations/sk.json b/homeassistant/components/alarmdecoder/translations/sk.json new file mode 100644 index 00000000000000..9b801344831e38 --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "protocol": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambee/translations/cs.json b/homeassistant/components/ambee/translations/cs.json new file mode 100644 index 00000000000000..6459ddb3ba0a6c --- /dev/null +++ b/homeassistant/components/ambee/translations/cs.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9" + }, + "step": { + "user": { + "data": { + "longitude": "Zem\u011bpisn\u00e1 d\u00e9lka", + "name": "Jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambee/translations/sk.json b/homeassistant/components/ambee/translations/sk.json new file mode 100644 index 00000000000000..a474631a7f8582 --- /dev/null +++ b/homeassistant/components/ambee/translations/sk.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_api_key": "Neplatn\u00fd API k\u013e\u00fa\u010d" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + }, + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d", + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka", + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/amberelectric/translations/el.json b/homeassistant/components/amberelectric/translations/el.json index 6ca86ce05427a6..0157f30c0f049f 100644 --- a/homeassistant/components/amberelectric/translations/el.json +++ b/homeassistant/components/amberelectric/translations/el.json @@ -4,7 +4,11 @@ "site": { "data": { "site_name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2" - } + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf NMI \u03c4\u03b7\u03c2 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5." + }, + "user": { + "description": "\u039c\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf {api_url} \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API" } } } diff --git a/homeassistant/components/ambiclimate/translations/sk.json b/homeassistant/components/ambiclimate/translations/sk.json new file mode 100644 index 00000000000000..c19b1a0b70c707 --- /dev/null +++ b/homeassistant/components/ambiclimate/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "create_entry": { + "default": "\u00daspe\u0161ne overen\u00e9" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambient_station/translations/sk.json b/homeassistant/components/ambient_station/translations/sk.json new file mode 100644 index 00000000000000..01c13a4f11e897 --- /dev/null +++ b/homeassistant/components/ambient_station/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_key": "Neplatn\u00fd API k\u013e\u00fa\u010d" + }, + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/androidtv/translations/sk.json b/homeassistant/components/androidtv/translations/sk.json new file mode 100644 index 00000000000000..86bba63ac49673 --- /dev/null +++ b/homeassistant/components/androidtv/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "Zariadenie u\u017e je nakonfigurovan\u00e9" + }, + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/sk.json b/homeassistant/components/apple_tv/translations/sk.json new file mode 100644 index 00000000000000..e0e6b1c5bda916 --- /dev/null +++ b/homeassistant/components/apple_tv/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha", + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/sk.json b/homeassistant/components/arcam_fmj/translations/sk.json new file mode 100644 index 00000000000000..b41d6edbd4b1ef --- /dev/null +++ b/homeassistant/components/arcam_fmj/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + }, + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aseko_pool_live/translations/sk.json b/homeassistant/components/aseko_pool_live/translations/sk.json new file mode 100644 index 00000000000000..72b0304f1c3bd8 --- /dev/null +++ b/homeassistant/components/aseko_pool_live/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "email": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/sk.json b/homeassistant/components/asuswrt/translations/sk.json new file mode 100644 index 00000000000000..39d2e182c40bee --- /dev/null +++ b/homeassistant/components/asuswrt/translations/sk.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "N\u00e1zov", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/atag/translations/sk.json b/homeassistant/components/atag/translations/sk.json new file mode 100644 index 00000000000000..892b8b2cd91240 --- /dev/null +++ b/homeassistant/components/atag/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/august/translations/sk.json b/homeassistant/components/august/translations/sk.json new file mode 100644 index 00000000000000..71a7aea5018f3f --- /dev/null +++ b/homeassistant/components/august/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aurora/translations/sk.json b/homeassistant/components/aurora/translations/sk.json new file mode 100644 index 00000000000000..81532ef4801935 --- /dev/null +++ b/homeassistant/components/aurora/translations/sk.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka", + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aurora_abb_powerone/translations/cs.json b/homeassistant/components/aurora_abb_powerone/translations/cs.json new file mode 100644 index 00000000000000..e1bf8e7f45f3c1 --- /dev/null +++ b/homeassistant/components/aurora_abb_powerone/translations/cs.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aussie_broadband/translations/el.json b/homeassistant/components/aussie_broadband/translations/el.json index 0b08d9bbc69579..94332a74183c4b 100644 --- a/homeassistant/components/aussie_broadband/translations/el.json +++ b/homeassistant/components/aussie_broadband/translations/el.json @@ -13,6 +13,9 @@ }, "description": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 {username}" }, + "reauth_confirm": { + "description": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 {username}" + }, "service": { "data": { "services": "\u03a5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b5\u03c2" diff --git a/homeassistant/components/aussie_broadband/translations/he.json b/homeassistant/components/aussie_broadband/translations/he.json index 0a66d494813d1f..5861357a6d4235 100644 --- a/homeassistant/components/aussie_broadband/translations/he.json +++ b/homeassistant/components/aussie_broadband/translations/he.json @@ -16,6 +16,12 @@ }, "title": "\u05d0\u05d9\u05de\u05d5\u05ea \u05de\u05d7\u05d3\u05e9 \u05e9\u05dc \u05e9\u05d9\u05dc\u05d5\u05d1" }, + "reauth_confirm": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + }, + "title": "\u05d0\u05d9\u05de\u05d5\u05ea \u05de\u05d7\u05d3\u05e9 \u05e9\u05dc \u05e9\u05d9\u05dc\u05d5\u05d1" + }, "user": { "data": { "password": "\u05e1\u05d9\u05e1\u05de\u05d4", diff --git a/homeassistant/components/aussie_broadband/translations/pl.json b/homeassistant/components/aussie_broadband/translations/pl.json index 7fa1e0d7c46e96..c2f686c11dcc8e 100644 --- a/homeassistant/components/aussie_broadband/translations/pl.json +++ b/homeassistant/components/aussie_broadband/translations/pl.json @@ -18,6 +18,13 @@ "description": "Zaktualizuj has\u0142o dla {username}", "title": "Ponownie uwierzytelnij integracj\u0119" }, + "reauth_confirm": { + "data": { + "password": "Has\u0142o" + }, + "description": "Zaktualizuj has\u0142o dla {username}", + "title": "Ponownie uwierzytelnij integracj\u0119" + }, "service": { "data": { "services": "Us\u0142ugi" diff --git a/homeassistant/components/aussie_broadband/translations/sk.json b/homeassistant/components/aussie_broadband/translations/sk.json new file mode 100644 index 00000000000000..a8cf7db8bbf5eb --- /dev/null +++ b/homeassistant/components/aussie_broadband/translations/sk.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + }, + "options": { + "abort": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/awair/translations/sk.json b/homeassistant/components/awair/translations/sk.json new file mode 100644 index 00000000000000..dabf3e7e9339d5 --- /dev/null +++ b/homeassistant/components/awair/translations/sk.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "step": { + "reauth": { + "data": { + "access_token": "Pr\u00edstupov\u00fd token", + "email": "Email" + } + }, + "user": { + "data": { + "access_token": "Pr\u00edstupov\u00fd token", + "email": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/translations/el.json b/homeassistant/components/axis/translations/el.json index 7255fe1779496b..79f3f80eca4d8c 100644 --- a/homeassistant/components/axis/translations/el.json +++ b/homeassistant/components/axis/translations/el.json @@ -8,6 +8,7 @@ "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "port": "\u0398\u03cd\u03c1\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" diff --git a/homeassistant/components/axis/translations/sk.json b/homeassistant/components/axis/translations/sk.json new file mode 100644 index 00000000000000..53eb88bf838a41 --- /dev/null +++ b/homeassistant/components/axis/translations/sk.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha", + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/azure_devops/translations/sk.json b/homeassistant/components/azure_devops/translations/sk.json new file mode 100644 index 00000000000000..71a7aea5018f3f --- /dev/null +++ b/homeassistant/components/azure_devops/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/balboa/translations/cs.json b/homeassistant/components/balboa/translations/cs.json new file mode 100644 index 00000000000000..e1bf8e7f45f3c1 --- /dev/null +++ b/homeassistant/components/balboa/translations/cs.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blebox/translations/el.json b/homeassistant/components/blebox/translations/el.json index 85d796ff6ddac0..14320019471e9d 100644 --- a/homeassistant/components/blebox/translations/el.json +++ b/homeassistant/components/blebox/translations/el.json @@ -9,6 +9,9 @@ "flow_title": "{name} ({host})", "step": { "user": { + "data": { + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf BleBox \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Home Assistant.", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 BleBox" } diff --git a/homeassistant/components/blebox/translations/sk.json b/homeassistant/components/blebox/translations/sk.json new file mode 100644 index 00000000000000..892b8b2cd91240 --- /dev/null +++ b/homeassistant/components/blebox/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blink/translations/sk.json b/homeassistant/components/blink/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/blink/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/sk.json b/homeassistant/components/bmw_connected_drive/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bond/translations/cs.json b/homeassistant/components/bond/translations/cs.json index 6ee951350caf1f..1d8e9c720e8945 100644 --- a/homeassistant/components/bond/translations/cs.json +++ b/homeassistant/components/bond/translations/cs.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nakofigurovan\u00e9" + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" }, "error": { "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", diff --git a/homeassistant/components/bond/translations/sk.json b/homeassistant/components/bond/translations/sk.json new file mode 100644 index 00000000000000..e237bd34b0a682 --- /dev/null +++ b/homeassistant/components/bond/translations/sk.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "confirm": { + "data": { + "access_token": "Pr\u00edstupov\u00fd token" + } + }, + "user": { + "data": { + "access_token": "Pr\u00edstupov\u00fd token" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/cs.json b/homeassistant/components/bosch_shc/translations/cs.json new file mode 100644 index 00000000000000..72df4a968182fc --- /dev/null +++ b/homeassistant/components/bosch_shc/translations/cs.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/sk.json b/homeassistant/components/bosch_shc/translations/sk.json new file mode 100644 index 00000000000000..71a7aea5018f3f --- /dev/null +++ b/homeassistant/components/bosch_shc/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/broadlink/translations/el.json b/homeassistant/components/broadlink/translations/el.json index 360b9c91d680ac..e0dc11ae470d9b 100644 --- a/homeassistant/components/broadlink/translations/el.json +++ b/homeassistant/components/broadlink/translations/el.json @@ -29,6 +29,7 @@ }, "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "timeout": "\u03a7\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03cc\u03c1\u03b9\u03bf" }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" diff --git a/homeassistant/components/broadlink/translations/sk.json b/homeassistant/components/broadlink/translations/sk.json new file mode 100644 index 00000000000000..358fdc848ff096 --- /dev/null +++ b/homeassistant/components/broadlink/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + }, + "step": { + "finish": { + "data": { + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/brother/translations/el.json b/homeassistant/components/brother/translations/el.json index 935a234be85272..95124984c3d149 100644 --- a/homeassistant/components/brother/translations/el.json +++ b/homeassistant/components/brother/translations/el.json @@ -12,6 +12,7 @@ "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c4\u03bf\u03c5 \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae" }, "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae Brother. \u0395\u03ac\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03c0\u03c1\u03bf\u03b2\u03bb\u03ae\u03bc\u03b1\u03c4\u03b1 \u03bc\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7: https://www.home-assistant.io/integrations/brother" diff --git a/homeassistant/components/brunt/translations/cs.json b/homeassistant/components/brunt/translations/cs.json new file mode 100644 index 00000000000000..72df4a968182fc --- /dev/null +++ b/homeassistant/components/brunt/translations/cs.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/brunt/translations/sk.json b/homeassistant/components/brunt/translations/sk.json new file mode 100644 index 00000000000000..71a7aea5018f3f --- /dev/null +++ b/homeassistant/components/brunt/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bsblan/translations/sk.json b/homeassistant/components/bsblan/translations/sk.json new file mode 100644 index 00000000000000..892b8b2cd91240 --- /dev/null +++ b/homeassistant/components/bsblan/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/buienradar/translations/cs.json b/homeassistant/components/buienradar/translations/cs.json new file mode 100644 index 00000000000000..31db40bd1607e0 --- /dev/null +++ b/homeassistant/components/buienradar/translations/cs.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Um\u00edst\u011bn\u00ed je ji\u017e nastaveno" + }, + "error": { + "already_configured": "Um\u00edst\u011bn\u00ed je ji\u017e nastaveno" + }, + "step": { + "user": { + "data": { + "longitude": "Zem\u011bpisn\u00e1 d\u00e9lka" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "country_code": "K\u00f3d zem\u011b" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/buienradar/translations/sk.json b/homeassistant/components/buienradar/translations/sk.json new file mode 100644 index 00000000000000..d77712e768a3a2 --- /dev/null +++ b/homeassistant/components/buienradar/translations/sk.json @@ -0,0 +1,21 @@ +{ + "config": { + "step": { + "user": { + "data": { + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "country_code": "K\u00f3d krajiny" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cast/translations/sk.json b/homeassistant/components/cast/translations/sk.json new file mode 100644 index 00000000000000..e227301685bbd0 --- /dev/null +++ b/homeassistant/components/cast/translations/sk.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "confirm": { + "description": "Chcete za\u010da\u0165 nastavova\u0165?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cert_expiry/translations/el.json b/homeassistant/components/cert_expiry/translations/el.json index c640d7e0411947..a130ea4b90b2a6 100644 --- a/homeassistant/components/cert_expiry/translations/el.json +++ b/homeassistant/components/cert_expiry/translations/el.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "name": "\u03a4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c4\u03bf\u03c5 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03bf\u03cd", "port": "\u0398\u03cd\u03c1\u03b1" }, diff --git a/homeassistant/components/cert_expiry/translations/sk.json b/homeassistant/components/cert_expiry/translations/sk.json new file mode 100644 index 00000000000000..892b8b2cd91240 --- /dev/null +++ b/homeassistant/components/cert_expiry/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/sk.json b/homeassistant/components/climacell/translations/sk.json new file mode 100644 index 00000000000000..8e0bc629a1328a --- /dev/null +++ b/homeassistant/components/climacell/translations/sk.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "invalid_api_key": "Neplatn\u00fd API k\u013e\u00fa\u010d" + }, + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d", + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka", + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloudflare/translations/cs.json b/homeassistant/components/cloudflare/translations/cs.json index 8f88377860b55b..2f087600355ddd 100644 --- a/homeassistant/components/cloudflare/translations/cs.json +++ b/homeassistant/components/cloudflare/translations/cs.json @@ -14,7 +14,8 @@ "step": { "reauth_confirm": { "data": { - "api_token": "API token" + "api_token": "API token", + "description": "Op\u011bt ov\u011b\u0159te sv\u016fj Cloudflare \u00fa\u010det." } }, "records": { @@ -27,7 +28,7 @@ "data": { "api_token": "API token" }, - "description": "Tato integrace vy\u017eaduje API token vytvo\u0159en\u00fd s opravn\u011bn\u00edmi Z\u00f3na:Z\u00f3na:\u010c\u00edst a Z\u00f3na:DNS: Upravit pro v\u0161echny z\u00f3ny ve va\u0161em \u00fa\u010dtu.", + "description": "Tato integrace vy\u017eaduje API token vytvo\u0159en\u00fd s opravn\u011bn\u00edmi Zone:Zone:Read a Zone:DNS:Edit pro v\u0161echny z\u00f3ny ve va\u0161em \u00fa\u010dtu.", "title": "P\u0159ipojen\u00ed ke Cloudflare" }, "zone": { diff --git a/homeassistant/components/cloudflare/translations/sk.json b/homeassistant/components/cloudflare/translations/sk.json new file mode 100644 index 00000000000000..4af875cd1ab0f7 --- /dev/null +++ b/homeassistant/components/cloudflare/translations/sk.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9", + "unknown": "Neo\u010dak\u00e1van\u00e1 chyba" + }, + "error": { + "cannot_connect": "Nepodarilo sa pripoji\u0165", + "invalid_auth": "Neplatn\u00e9 overenie", + "invalid_zone": "Neplatn\u00e1 z\u00f3na" + }, + "flow_title": "{name}", + "step": { + "reauth_confirm": { + "data": { + "api_token": "API token", + "description": "Znovu overte svoj Cloudflare \u00fa\u010det." + } + }, + "records": { + "data": { + "records": "Z\u00e1znamy" + }, + "title": "Vyberte z\u00e1znamy, ktor\u00e9 chcete aktualizova\u0165" + }, + "user": { + "data": { + "api_token": "API token" + }, + "description": "T\u00e1to integr\u00e1cia vy\u017eaduje API token vytvoren\u00fd s opr\u00e1vneniami Zone:Zone:Read a Zone:DNS:Edit pre v\u0161etky z\u00f3ny vo va\u0161om \u00fa\u010dte.", + "title": "Pripoji\u0165 k slu\u017ebe Cloudflare" + }, + "zone": { + "data": { + "zone": "Z\u00f3na" + }, + "title": "Vyberte z\u00f3nu, ktor\u00fa chcete aktualizova\u0165" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/co2signal/translations/cs.json b/homeassistant/components/co2signal/translations/cs.json index 954168d1ee21ba..e8a60b65e82160 100644 --- a/homeassistant/components/co2signal/translations/cs.json +++ b/homeassistant/components/co2signal/translations/cs.json @@ -2,9 +2,11 @@ "config": { "abort": { "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", + "api_ratelimit": "P\u0159ekro\u010den limit vol\u00e1n\u00ed API", "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" }, "error": { + "api_ratelimit": "P\u0159ekro\u010den limit vol\u00e1n\u00ed API", "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" }, @@ -22,8 +24,10 @@ }, "user": { "data": { - "api_key": "P\u0159\u00edstupov\u00fd token" - } + "api_key": "P\u0159\u00edstupov\u00fd token", + "location": "Z\u00edskat data pro" + }, + "description": "O token m\u016f\u017eete po\u017e\u00e1dat na adrese https://co2signal.com/." } } } diff --git a/homeassistant/components/co2signal/translations/sk.json b/homeassistant/components/co2signal/translations/sk.json new file mode 100644 index 00000000000000..915531f8a35f75 --- /dev/null +++ b/homeassistant/components/co2signal/translations/sk.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "Zariadenie u\u017e je nakonfigurovan\u00e9", + "api_ratelimit": "Prekro\u010den\u00fd limit API", + "unknown": "Neo\u010dak\u00e1van\u00e1 chyba" + }, + "error": { + "api_ratelimit": "Prekro\u010den\u00fd limit API", + "invalid_auth": "Neplatn\u00e9 overenie", + "unknown": "Neo\u010dak\u00e1van\u00e1 chyba" + }, + "step": { + "coordinates": { + "data": { + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka" + } + }, + "country": { + "data": { + "country_code": "K\u00f3d krajiny" + } + }, + "user": { + "data": { + "api_key": "Pr\u00edstupov\u00fd token", + "location": "Z\u00edska\u0165 \u00fadaje pre" + }, + "description": "Ak chcete po\u017eiada\u0165 o token, nav\u0161t\u00edvte https://co2signal.com/." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/coinbase/translations/sk.json b/homeassistant/components/coinbase/translations/sk.json new file mode 100644 index 00000000000000..ff85312780312a --- /dev/null +++ b/homeassistant/components/coinbase/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/control4/translations/el.json b/homeassistant/components/control4/translations/el.json index 92cea7966a3626..0ffffeb9f04a8a 100644 --- a/homeassistant/components/control4/translations/el.json +++ b/homeassistant/components/control4/translations/el.json @@ -3,10 +3,20 @@ "step": { "user": { "data": { + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c4\u03bf\u03c5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03c3\u03b1\u03c2 Control4 \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03bf\u03c5 \u03c4\u03bf\u03c0\u03b9\u03ba\u03bf\u03cd \u03c3\u03b1\u03c2 \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ae." } } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u0394\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1 \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03b5\u03c9\u03bd" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/control4/translations/sk.json b/homeassistant/components/control4/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/control4/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/coolmaster/translations/el.json b/homeassistant/components/coolmaster/translations/el.json index b1621652c95c3b..9cdc9fe005449c 100644 --- a/homeassistant/components/coolmaster/translations/el.json +++ b/homeassistant/components/coolmaster/translations/el.json @@ -12,6 +12,7 @@ "fan_only": "\u03a5\u03c0\u03bf\u03c3\u03c4\u03ae\u03c1\u03b9\u03be\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03bc\u03cc\u03bd\u03bf \u03bc\u03b5 \u03b1\u03bd\u03b5\u03bc\u03b9\u03c3\u03c4\u03ae\u03c1\u03b1", "heat": "\u03a5\u03c0\u03bf\u03c3\u03c4\u03ae\u03c1\u03b9\u03be\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b8\u03b5\u03c1\u03bc\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "heat_cool": "\u03a5\u03c0\u03bf\u03c3\u03c4\u03ae\u03c1\u03b9\u03be\u03b7 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7\u03c2 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b8\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7\u03c2/\u03c8\u03cd\u03be\u03b7\u03c2", + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "off": "\u039c\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af" }, "title": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 CoolMasterNet." diff --git a/homeassistant/components/crownstone/translations/cs.json b/homeassistant/components/crownstone/translations/cs.json index f1e209b21d8f28..1f14cdd8eff147 100644 --- a/homeassistant/components/crownstone/translations/cs.json +++ b/homeassistant/components/crownstone/translations/cs.json @@ -4,7 +4,8 @@ "already_configured": "\u00da\u010det je ji\u017e nastaven" }, "error": { - "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" }, "step": { "usb_manual_config": { diff --git a/homeassistant/components/crownstone/translations/sk.json b/homeassistant/components/crownstone/translations/sk.json new file mode 100644 index 00000000000000..72b0304f1c3bd8 --- /dev/null +++ b/homeassistant/components/crownstone/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "email": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/daikin/translations/el.json b/homeassistant/components/daikin/translations/el.json index ae089710c8a194..c4113f251c1432 100644 --- a/homeassistant/components/daikin/translations/el.json +++ b/homeassistant/components/daikin/translations/el.json @@ -6,6 +6,7 @@ "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03bf\u03c5 Daikin AC. \n\n \u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03c4\u03b1 \u039a\u03bb\u03b5\u03b9\u03b4\u03af API \u03ba\u03b1\u03b9 \u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03bf\u03cd\u03bd\u03c4\u03b1\u03b9 \u03bc\u03cc\u03bd\u03bf \u03b1\u03c0\u03cc \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 BRP072Cxx \u03ba\u03b1\u03b9 SKYFi \u03b1\u03bd\u03c4\u03af\u03c3\u03c4\u03bf\u03b9\u03c7\u03b1.", diff --git a/homeassistant/components/daikin/translations/sk.json b/homeassistant/components/daikin/translations/sk.json new file mode 100644 index 00000000000000..a122282648160f --- /dev/null +++ b/homeassistant/components/daikin/translations/sk.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "api_password": "Neplatn\u00e9 overenie, pou\u017eite bu\u010f API k\u013e\u00fa\u010d alebo heslo.", + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deconz/translations/sk.json b/homeassistant/components/deconz/translations/sk.json new file mode 100644 index 00000000000000..81684739474886 --- /dev/null +++ b/homeassistant/components/deconz/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + }, + "step": { + "manual_input": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/denonavr/translations/el.json b/homeassistant/components/denonavr/translations/el.json index 16e0c74c2064e1..8bfb96a65bbc21 100644 --- a/homeassistant/components/denonavr/translations/el.json +++ b/homeassistant/components/denonavr/translations/el.json @@ -10,6 +10,10 @@ }, "flow_title": "{name}", "step": { + "confirm": { + "description": "\u0395\u03c0\u03b9\u03b2\u03b5\u03b2\u03b1\u03b9\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03ad\u03ba\u03c4\u03b7", + "title": "\u0394\u03ad\u03ba\u03c4\u03b5\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 Denon AVR" + }, "select": { "data": { "select_host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03b4\u03ad\u03ba\u03c4\u03b7" @@ -18,6 +22,9 @@ "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf \u03b4\u03ad\u03ba\u03c4\u03b7 \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5" }, "user": { + "data": { + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + }, "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b4\u03ad\u03ba\u03c4\u03b7 \u03c3\u03b1\u03c2, \u03b5\u03ac\u03bd \u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7.", "title": "\u0394\u03ad\u03ba\u03c4\u03b5\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 Denon AVR" } diff --git a/homeassistant/components/denonavr/translations/sk.json b/homeassistant/components/denonavr/translations/sk.json new file mode 100644 index 00000000000000..bee0999420fbf7 --- /dev/null +++ b/homeassistant/components/denonavr/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/devolo_home_control/translations/sk.json b/homeassistant/components/devolo_home_control/translations/sk.json new file mode 100644 index 00000000000000..9273954369f8d2 --- /dev/null +++ b/homeassistant/components/devolo_home_control/translations/sk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "username": "Email / devolo ID" + } + }, + "zeroconf_confirm": { + "data": { + "username": "Email / devolo ID" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/devolo_home_network/translations/cs.json b/homeassistant/components/devolo_home_network/translations/cs.json new file mode 100644 index 00000000000000..e1bf8e7f45f3c1 --- /dev/null +++ b/homeassistant/components/devolo_home_network/translations/cs.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/devolo_home_network/translations/el.json b/homeassistant/components/devolo_home_network/translations/el.json index 5d07abe3507d4f..68d64178efe681 100644 --- a/homeassistant/components/devolo_home_network/translations/el.json +++ b/homeassistant/components/devolo_home_network/translations/el.json @@ -5,6 +5,11 @@ }, "flow_title": "{product} ({name})", "step": { + "user": { + "data": { + "ip_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + } + }, "zeroconf_confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bf\u03b9\u03ba\u03b9\u03b1\u03ba\u03bf\u03cd \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 devolo \u03bc\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae `{host_name}` \u03c3\u03c4\u03bf Home Assistant;", "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bf\u03b9\u03ba\u03b9\u03b1\u03ba\u03bf\u03cd \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 devolo" diff --git a/homeassistant/components/dexcom/translations/sk.json b/homeassistant/components/dexcom/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/dexcom/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/translations/el.json b/homeassistant/components/dialogflow/translations/el.json index 381078f6d8dcde..c0695ac82b082e 100644 --- a/homeassistant/components/dialogflow/translations/el.json +++ b/homeassistant/components/dialogflow/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf Home Assistant Cloud.", "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, "create_entry": { diff --git a/homeassistant/components/directv/translations/el.json b/homeassistant/components/directv/translations/el.json index 6c5446fe9f8833..7bdf0e12aa7c67 100644 --- a/homeassistant/components/directv/translations/el.json +++ b/homeassistant/components/directv/translations/el.json @@ -4,6 +4,11 @@ "step": { "ssdp_confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name};" + }, + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + } } } } diff --git a/homeassistant/components/dnsip/translations/el.json b/homeassistant/components/dnsip/translations/el.json index 1faba4734f5f89..20dd21e72e21c3 100644 --- a/homeassistant/components/dnsip/translations/el.json +++ b/homeassistant/components/dnsip/translations/el.json @@ -20,7 +20,8 @@ "step": { "init": { "data": { - "resolver": "\u0395\u03c0\u03b9\u03bb\u03cd\u03c4\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7 IPV4" + "resolver": "\u0395\u03c0\u03b9\u03bb\u03cd\u03c4\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7 IPV4", + "resolver_ipv6": "\u0395\u03c0\u03b9\u03bb\u03cd\u03c4\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7 IPV6" } } } diff --git a/homeassistant/components/doorbird/translations/sk.json b/homeassistant/components/doorbird/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/doorbird/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dsmr/translations/cs.json b/homeassistant/components/dsmr/translations/cs.json index 8078da1b1a213d..9ab3eefa6a689f 100644 --- a/homeassistant/components/dsmr/translations/cs.json +++ b/homeassistant/components/dsmr/translations/cs.json @@ -4,6 +4,11 @@ "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" }, "step": { + "setup_network": { + "data": { + "port": "Port" + } + }, "setup_serial": { "data": { "port": "Vyberte za\u0159\u00edzen\u00ed" diff --git a/homeassistant/components/dsmr/translations/sk.json b/homeassistant/components/dsmr/translations/sk.json new file mode 100644 index 00000000000000..e343d2e8b3188f --- /dev/null +++ b/homeassistant/components/dsmr/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "setup_network": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ecobee/translations/sk.json b/homeassistant/components/ecobee/translations/sk.json new file mode 100644 index 00000000000000..9d5ee388dc325e --- /dev/null +++ b/homeassistant/components/ecobee/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/sk.json b/homeassistant/components/econet/translations/sk.json new file mode 100644 index 00000000000000..1a3e5d67caad34 --- /dev/null +++ b/homeassistant/components/econet/translations/sk.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "email": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/efergy/translations/sk.json b/homeassistant/components/efergy/translations/sk.json new file mode 100644 index 00000000000000..64731388e98c05 --- /dev/null +++ b/homeassistant/components/efergy/translations/sk.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elgato/translations/sk.json b/homeassistant/components/elgato/translations/sk.json new file mode 100644 index 00000000000000..892b8b2cd91240 --- /dev/null +++ b/homeassistant/components/elgato/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elkm1/translations/sk.json b/homeassistant/components/elkm1/translations/sk.json new file mode 100644 index 00000000000000..0b7bf878ea9887 --- /dev/null +++ b/homeassistant/components/elkm1/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/el.json b/homeassistant/components/elmax/translations/el.json index 792d5297a9076c..a32716878daf03 100644 --- a/homeassistant/components/elmax/translations/el.json +++ b/homeassistant/components/elmax/translations/el.json @@ -1,6 +1,7 @@ { "config": { "error": { + "bad_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "invalid_pin": "\u03a4\u03bf \u03c0\u03b1\u03c1\u03b5\u03c7\u03cc\u03bc\u03b5\u03bd\u03bf pin \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf", "network_error": "\u03a0\u03b1\u03c1\u03bf\u03c5\u03c3\u03b9\u03ac\u03c3\u03c4\u03b7\u03ba\u03b5 \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5", "no_panel_online": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03b7\u03bb\u03b5\u03ba\u03c4\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc\u03c2 \u03c0\u03af\u03bd\u03b1\u03ba\u03b1\u03c2 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 Elmax.", diff --git a/homeassistant/components/elmax/translations/sk.json b/homeassistant/components/elmax/translations/sk.json new file mode 100644 index 00000000000000..ae37c2ed275da1 --- /dev/null +++ b/homeassistant/components/elmax/translations/sk.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie", + "unknown_error": "Vyskytla sa neo\u010dak\u00e1van\u00e1 chyba" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/translations/el.json b/homeassistant/components/emulated_roku/translations/el.json index c6de5f984fee54..a8d3fa8e922f61 100644 --- a/homeassistant/components/emulated_roku/translations/el.json +++ b/homeassistant/components/emulated_roku/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "advertise_ip": "\u0394\u03b9\u03b1\u03c6\u03ae\u03bc\u03b9\u03c3\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 IP", "advertise_port": "\u0398\u03cd\u03c1\u03b1 \u03b1\u03ba\u03c1\u03cc\u03b1\u03c3\u03b7\u03c2", "host_ip": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae", "listen_port": "\u0391\u03ba\u03c1\u03cc\u03b1\u03c3\u03b7 \u03b8\u03cd\u03c1\u03b1\u03c2", diff --git a/homeassistant/components/emulated_roku/translations/sk.json b/homeassistant/components/emulated_roku/translations/sk.json new file mode 100644 index 00000000000000..af15f92c2f27a7 --- /dev/null +++ b/homeassistant/components/emulated_roku/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/enphase_envoy/translations/sk.json b/homeassistant/components/enphase_envoy/translations/sk.json new file mode 100644 index 00000000000000..71a7aea5018f3f --- /dev/null +++ b/homeassistant/components/enphase_envoy/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/environment_canada/translations/sk.json b/homeassistant/components/environment_canada/translations/sk.json new file mode 100644 index 00000000000000..e6945904d9030a --- /dev/null +++ b/homeassistant/components/environment_canada/translations/sk.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/epson/translations/sk.json b/homeassistant/components/epson/translations/sk.json new file mode 100644 index 00000000000000..af15f92c2f27a7 --- /dev/null +++ b/homeassistant/components/epson/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/esphome/translations/cs.json b/homeassistant/components/esphome/translations/cs.json index fc4a7d5bf8c454..c6885e06851266 100644 --- a/homeassistant/components/esphome/translations/cs.json +++ b/homeassistant/components/esphome/translations/cs.json @@ -8,6 +8,7 @@ "error": { "connection_error": "Nelze se p\u0159ipojit k ESP. Zkontrolujte, zda va\u0161e YAML konfigurace obsahuje \u0159\u00e1dek 'api:'.", "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "invalid_psk": "Transportn\u00ed \u0161ifrovac\u00ed kl\u00ed\u010d je neplatn\u00fd. Ujist\u011bte se, \u017ee odpov\u00edd\u00e1 tomu, co m\u00e1te ve sv\u00e9 konfiguraci", "resolve_error": "Nelze naj\u00edt IP adresu uzlu ESP. Pokud tato chyba p\u0159etrv\u00e1v\u00e1, nastavte statickou adresu IP: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" }, "flow_title": "ESPHome: {name}", @@ -16,12 +17,24 @@ "data": { "password": "Heslo" }, - "description": "Zadejte heslo, kter\u00e9 jste nastavili ve va\u0161\u00ed konfiguraci pro {name} ." + "description": "Zadejte heslo, kter\u00e9 jste nastavili ve va\u0161\u00ed konfiguraci pro {name}." }, "discovery_confirm": { "description": "Chcete p\u0159idat uzel ESPHome `{name}` do Home Assistant?", "title": "Nalezen uzel ESPHome" }, + "encryption_key": { + "data": { + "noise_psk": "\u0160ifrovac\u00ed kl\u00ed\u010d" + }, + "description": "Pros\u00edm vlo\u017ete \u0161ifrovac\u00ed kl\u00ed\u010d, kter\u00fd jste nastavili ve va\u0161\u00ed konfiguraci pro {name}." + }, + "reauth_confirm": { + "data": { + "noise_psk": "\u0160ifrovac\u00ed kl\u00ed\u010d" + }, + "description": "Za\u0159\u00edzen\u00ed ESPHome {name} povolilo transportn\u00ed \u0161ifrov\u00e1n\u00ed nebo zm\u011bnilo \u0161ifrovac\u00ed kl\u00ed\u010d. Pros\u00edm, zadejte aktu\u00e1ln\u00ed kl\u00ed\u010d." + }, "user": { "data": { "host": "Hostitel", diff --git a/homeassistant/components/esphome/translations/sk.json b/homeassistant/components/esphome/translations/sk.json new file mode 100644 index 00000000000000..ca12462f388ff5 --- /dev/null +++ b/homeassistant/components/esphome/translations/sk.json @@ -0,0 +1,46 @@ +{ + "config": { + "abort": { + "already_configured": "Zariadenie u\u017e je nakonfigurovan\u00e9", + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha", + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "connection_error": "Ned\u00e1 sa pripoji\u0165 k ESP. Uistite sa, \u017ee v\u00e1\u0161 s\u00fabor YAML obsahuje riadok \u201eapi:\u201c.", + "invalid_auth": "Neplatn\u00e9 overenie", + "invalid_psk": "Transportn\u00fd \u0161ifrovac\u00ed k\u013e\u00fa\u010d je neplatn\u00fd. Pros\u00edm, uistite sa, \u017ee sa zhoduje s t\u00fdm, \u010do m\u00e1te vo svojej konfigur\u00e1cii", + "resolve_error": "Nie je mo\u017en\u00e9 zisti\u0165 adresu ESP. Ak t\u00e1to chyba pretrv\u00e1va, nastavte statick\u00fa IP adresu: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" + }, + "flow_title": "{name}", + "step": { + "authenticate": { + "data": { + "password": "Heslo" + }, + "description": "Pros\u00edm, zadajte heslo, ktor\u00e9 ste nastavili v konfigur\u00e1cii pre {name}." + }, + "discovery_confirm": { + "description": "Chcete prida\u0165 uzol ESPHome `{name}` do Home Assistant?", + "title": "Objaven\u00fd uzol ESPHome" + }, + "encryption_key": { + "data": { + "noise_psk": "\u0160ifrovac\u00ed k\u013e\u00fa\u010d" + }, + "description": "Pros\u00edm, zadajte \u0161ifrovac\u00ed k\u013e\u00fa\u010d, ktor\u00fd ste nastavili v konfigur\u00e1cii pre {name}." + }, + "reauth_confirm": { + "data": { + "noise_psk": "\u0160ifrovac\u00ed k\u013e\u00fa\u010d" + }, + "description": "Zariadenie ESPHome {name} povolilo transportn\u00e9 \u0161ifrovanie alebo zmenilo \u0161ifrovac\u00ed k\u013e\u00fa\u010d. Pros\u00edm, zadajte aktualizovan\u00fd k\u013e\u00fa\u010d." + }, + "user": { + "data": { + "port": "Port" + }, + "description": "Pros\u00edm, zadajte nastavenia pripojenia v\u00e1\u0161ho uzla [ESPHome](https://esphomelib.com/)." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/evil_genius_labs/translations/cs.json b/homeassistant/components/evil_genius_labs/translations/cs.json new file mode 100644 index 00000000000000..e1bf8e7f45f3c1 --- /dev/null +++ b/homeassistant/components/evil_genius_labs/translations/cs.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ezviz/translations/sk.json b/homeassistant/components/ezviz/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/ezviz/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fireservicerota/translations/sk.json b/homeassistant/components/fireservicerota/translations/sk.json new file mode 100644 index 00000000000000..879d148fd130be --- /dev/null +++ b/homeassistant/components/fireservicerota/translations/sk.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "create_entry": { + "default": "\u00daspe\u0161ne overen\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/sk.json b/homeassistant/components/fivem/translations/sk.json new file mode 100644 index 00000000000000..39d2e182c40bee --- /dev/null +++ b/homeassistant/components/fivem/translations/sk.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "N\u00e1zov", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flick_electric/translations/sk.json b/homeassistant/components/flick_electric/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/flick_electric/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flipr/translations/sk.json b/homeassistant/components/flipr/translations/sk.json new file mode 100644 index 00000000000000..72b0304f1c3bd8 --- /dev/null +++ b/homeassistant/components/flipr/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "email": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flo/translations/el.json b/homeassistant/components/flo/translations/el.json index 18c2f0869bd1c2..54b55bb8324647 100644 --- a/homeassistant/components/flo/translations/el.json +++ b/homeassistant/components/flo/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } diff --git a/homeassistant/components/flo/translations/sk.json b/homeassistant/components/flo/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/flo/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flume/translations/cs.json b/homeassistant/components/flume/translations/cs.json index 23aa89e12f2650..e3f6cf1d39e681 100644 --- a/homeassistant/components/flume/translations/cs.json +++ b/homeassistant/components/flume/translations/cs.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u00da\u010det je ji\u017e nastaven" + "already_configured": "\u00da\u010det je ji\u017e nastaven", + "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9" }, "error": { "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", diff --git a/homeassistant/components/flume/translations/sk.json b/homeassistant/components/flume/translations/sk.json new file mode 100644 index 00000000000000..71a7aea5018f3f --- /dev/null +++ b/homeassistant/components/flume/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flunearyou/translations/sk.json b/homeassistant/components/flunearyou/translations/sk.json new file mode 100644 index 00000000000000..e6945904d9030a --- /dev/null +++ b/homeassistant/components/flunearyou/translations/sk.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flux_led/translations/sk.json b/homeassistant/components/flux_led/translations/sk.json new file mode 100644 index 00000000000000..bee0999420fbf7 --- /dev/null +++ b/homeassistant/components/flux_led/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forecast_solar/translations/cs.json b/homeassistant/components/forecast_solar/translations/cs.json index 0b970643bbef25..d1c9b95470f818 100644 --- a/homeassistant/components/forecast_solar/translations/cs.json +++ b/homeassistant/components/forecast_solar/translations/cs.json @@ -3,10 +3,28 @@ "step": { "user": { "data": { + "azimuth": "Azimut (360\u02da, 0 = sever, 90 = v\u00fdchod, 180 = jih, 270 = z\u00e1pad)", + "declination": "Deklinace (0 = horizont\u00e1ln\u00ed, 90 = vertik\u00e1ln\u00ed)", "latitude": "Zem\u011bpisn\u00e1 \u0161\u00ed\u0159ka", "longitude": "Zem\u011bpisn\u00e1 d\u00e9lka", + "modules power": "Celkov\u00fd \u0161pi\u010dkov\u00fd v\u00fdkon sol\u00e1rn\u00edch modul\u016f", "name": "Jm\u00e9no" - } + }, + "description": "Vypl\u0148te \u00fadaje o sv\u00fdch sol\u00e1rn\u00edch panelech. Pokud je n\u011bkter\u00e9 pole nejasn\u00e9, nahl\u00e9dn\u011bte do dokumentace." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "api_key": "Forecast.Solar API kl\u00ed\u010d (voliteln\u00fd)", + "azimuth": "Azimut (360\u02da, 0 = sever, 90 = v\u00fdchod, 180 = jih, 270 = z\u00e1pad)", + "damping": "\u010cinitel tlumen\u00ed: upravuje v\u00fdsledky r\u00e1no a ve\u010der", + "declination": "Deklinace (0 = horizont\u00e1ln\u00ed, 90 = vertik\u00e1ln\u00ed)", + "modules power": "Celkov\u00fd \u0161pi\u010dkov\u00fd v\u00fdkon sol\u00e1rn\u00edch modul\u016f" + }, + "description": "Tyto hodnoty umo\u017e\u0148uj\u00ed vyladit v\u00fdsledn\u00e9 hodnoty Solar.Forecast. Pokud n\u011bkter\u00e9 pole nen\u00ed jasn\u00e9, nahl\u00e9dn\u011bte do dokumentace." } } } diff --git a/homeassistant/components/forecast_solar/translations/sk.json b/homeassistant/components/forecast_solar/translations/sk.json new file mode 100644 index 00000000000000..939157fb837694 --- /dev/null +++ b/homeassistant/components/forecast_solar/translations/sk.json @@ -0,0 +1,31 @@ +{ + "config": { + "step": { + "user": { + "data": { + "azimuth": "Azimut (360\u02da, 0 = sever, 90 = v\u00fdchod, 180 = juh, 270 = z\u00e1pad)", + "declination": "Deklin\u00e1cia (0 = horizont\u00e1lna, 90 = vertik\u00e1lna)", + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka", + "modules power": "Celkov\u00fd \u0161pi\u010dkov\u00fd v\u00fdkon sol\u00e1rnych modulov", + "name": "N\u00e1zov" + }, + "description": "Vypl\u0148te \u00fadaje o svojich sol\u00e1rnych paneloch. Ak je niektor\u00e9 pole nejasn\u00e9, pozrite si dokument\u00e1ciu." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "api_key": "Forecast.Solar API k\u013e\u00fa\u010d (volite\u013en\u00fd)", + "azimuth": "Azimut (360\u02da, 0 = sever, 90 = v\u00fdchod, 180 = juh, 270 = z\u00e1pad)", + "damping": "\u010cinite\u013e tlmenia: upravuje v\u00fdsledky r\u00e1no a ve\u010der", + "declination": "Deklin\u00e1cia (0 = horizont\u00e1lna, 90 = vertik\u00e1lna)", + "modules power": "Celkov\u00fd \u0161pi\u010dkov\u00fd v\u00fdkon sol\u00e1rnych modulov" + }, + "description": "Tieto hodnoty umo\u017e\u0148uj\u00fa upravi\u0165 v\u00fdsledn\u00e9 hodnoty Solar.Forecast. Ak je niektor\u00e9 pole nejasn\u00e9, pozrite si dokument\u00e1ciu." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/sk.json b/homeassistant/components/foscam/translations/sk.json new file mode 100644 index 00000000000000..8bbcb516b5626e --- /dev/null +++ b/homeassistant/components/foscam/translations/sk.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Zariadenie u\u017e je nakonfigurovan\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "port": "Port", + "username": "Pou\u017e\u00edvate\u013esk\u00e9 meno" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/freebox/translations/el.json b/homeassistant/components/freebox/translations/el.json index ffca8cab545b0f..3ee7643c936dc1 100644 --- a/homeassistant/components/freebox/translations/el.json +++ b/homeassistant/components/freebox/translations/el.json @@ -10,6 +10,7 @@ }, "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "port": "\u0398\u03cd\u03c1\u03b1" }, "title": "Freebox" diff --git a/homeassistant/components/freebox/translations/sk.json b/homeassistant/components/freebox/translations/sk.json new file mode 100644 index 00000000000000..892b8b2cd91240 --- /dev/null +++ b/homeassistant/components/freebox/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/freedompro/translations/el.json b/homeassistant/components/freedompro/translations/el.json new file mode 100644 index 00000000000000..31baab7883dbdd --- /dev/null +++ b/homeassistant/components/freedompro/translations/el.json @@ -0,0 +1,10 @@ +{ + "config": { + "step": { + "user": { + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03c0\u03bf\u03c5 \u03bb\u03ac\u03b2\u03b1\u03c4\u03b5 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://home.freedompro.eu", + "title": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API Freedompro" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/freedompro/translations/sk.json b/homeassistant/components/freedompro/translations/sk.json new file mode 100644 index 00000000000000..ff85312780312a --- /dev/null +++ b/homeassistant/components/freedompro/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritz/translations/cs.json b/homeassistant/components/fritz/translations/cs.json index 75ad51d8a1eb0c..9afc3d85536321 100644 --- a/homeassistant/components/fritz/translations/cs.json +++ b/homeassistant/components/fritz/translations/cs.json @@ -31,6 +31,11 @@ "port": "Port", "username": "U\u017eivatelsk\u00e9 jm\u00e9no" } + }, + "user": { + "data": { + "port": "Port" + } } } } diff --git a/homeassistant/components/fritz/translations/sk.json b/homeassistant/components/fritz/translations/sk.json new file mode 100644 index 00000000000000..9d83bc4b75642f --- /dev/null +++ b/homeassistant/components/fritz/translations/sk.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha", + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha", + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "start_config": { + "data": { + "port": "Port" + } + }, + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/sk.json b/homeassistant/components/fritzbox/translations/sk.json new file mode 100644 index 00000000000000..e0e6b1c5bda916 --- /dev/null +++ b/homeassistant/components/fritzbox/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha", + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/sk.json b/homeassistant/components/fritzbox_callmonitor/translations/sk.json new file mode 100644 index 00000000000000..1145b3bb9f844c --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fronius/translations/cs.json b/homeassistant/components/fronius/translations/cs.json index 4fcafe6fcced05..773ee67a7cf500 100644 --- a/homeassistant/components/fronius/translations/cs.json +++ b/homeassistant/components/fronius/translations/cs.json @@ -3,6 +3,9 @@ "abort": { "invalid_host": "Neplatn\u00fd hostitel nebo IP adresa" }, + "error": { + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, "flow_title": "{device}" } } \ No newline at end of file diff --git a/homeassistant/components/garages_amsterdam/translations/cs.json b/homeassistant/components/garages_amsterdam/translations/cs.json new file mode 100644 index 00000000000000..3b814303e69583 --- /dev/null +++ b/homeassistant/components/garages_amsterdam/translations/cs.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/translations/el.json b/homeassistant/components/geofency/translations/el.json index 1fc438ae03fe58..558729d4040b08 100644 --- a/homeassistant/components/geofency/translations/el.json +++ b/homeassistant/components/geofency/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf Home Assistant Cloud.", "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, "create_entry": { diff --git a/homeassistant/components/gios/translations/sk.json b/homeassistant/components/gios/translations/sk.json new file mode 100644 index 00000000000000..af15f92c2f27a7 --- /dev/null +++ b/homeassistant/components/gios/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/github/translations/sk.json b/homeassistant/components/github/translations/sk.json new file mode 100644 index 00000000000000..f04d4a327f48de --- /dev/null +++ b/homeassistant/components/github/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Slu\u017eba u\u017e je nakonfigurovan\u00e1" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/glances/translations/el.json b/homeassistant/components/glances/translations/el.json index 662258abf462ea..ab50cc0e5e865b 100644 --- a/homeassistant/components/glances/translations/el.json +++ b/homeassistant/components/glances/translations/el.json @@ -6,6 +6,7 @@ "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "port": "\u0398\u03cd\u03c1\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", diff --git a/homeassistant/components/glances/translations/sk.json b/homeassistant/components/glances/translations/sk.json new file mode 100644 index 00000000000000..39d2e182c40bee --- /dev/null +++ b/homeassistant/components/glances/translations/sk.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "N\u00e1zov", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/goalzero/translations/cs.json b/homeassistant/components/goalzero/translations/cs.json index 4d39a29a7c3796..2f2735f3c45ed9 100644 --- a/homeassistant/components/goalzero/translations/cs.json +++ b/homeassistant/components/goalzero/translations/cs.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u00da\u010det je ji\u017e nastaven" + "already_configured": "\u00da\u010det je ji\u017e nastaven", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" }, "error": { "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", diff --git a/homeassistant/components/goalzero/translations/sk.json b/homeassistant/components/goalzero/translations/sk.json new file mode 100644 index 00000000000000..af15f92c2f27a7 --- /dev/null +++ b/homeassistant/components/goalzero/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/el.json b/homeassistant/components/gogogate2/translations/el.json index 2ff8f1a2c5acb0..3d80794d682f71 100644 --- a/homeassistant/components/gogogate2/translations/el.json +++ b/homeassistant/components/gogogate2/translations/el.json @@ -4,6 +4,7 @@ "step": { "user": { "data": { + "ip_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "description": "\u0394\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b1\u03c0\u03b1\u03c1\u03b1\u03af\u03c4\u03b7\u03c4\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9.", diff --git a/homeassistant/components/gogogate2/translations/sk.json b/homeassistant/components/gogogate2/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/gogogate2/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/goodwe/translations/el.json b/homeassistant/components/goodwe/translations/el.json index d16fc316b41329..7137da9d868af1 100644 --- a/homeassistant/components/goodwe/translations/el.json +++ b/homeassistant/components/goodwe/translations/el.json @@ -5,6 +5,9 @@ }, "step": { "user": { + "data": { + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + }, "description": "\u03a3\u03c5\u03bd\u03b4\u03ad\u03c3\u03c4\u03b5 \u03c3\u03c4\u03bf \u03bc\u03b5\u03c4\u03b1\u03c4\u03c1\u03bf\u03c0\u03ad\u03b1", "title": "\u039c\u03b5\u03c4\u03b1\u03c4\u03c1\u03bf\u03c0\u03ad\u03b1\u03c2 GoodWe" } diff --git a/homeassistant/components/goodwe/translations/sk.json b/homeassistant/components/goodwe/translations/sk.json new file mode 100644 index 00000000000000..bee0999420fbf7 --- /dev/null +++ b/homeassistant/components/goodwe/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google_travel_time/translations/sk.json b/homeassistant/components/google_travel_time/translations/sk.json new file mode 100644 index 00000000000000..52d93a1a18e497 --- /dev/null +++ b/homeassistant/components/google_travel_time/translations/sk.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "Umiestnenie u\u017e je nakonfigurovan\u00e9" + }, + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d", + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/translations/el.json b/homeassistant/components/gpslogger/translations/el.json index 57cf824fd44c02..cb9c7a2788b3bc 100644 --- a/homeassistant/components/gpslogger/translations/el.json +++ b/homeassistant/components/gpslogger/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf Home Assistant Cloud.", "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, "create_entry": { diff --git a/homeassistant/components/growatt_server/translations/cs.json b/homeassistant/components/growatt_server/translations/cs.json index 02c83a6e9167b4..31a4cdfdf0357e 100644 --- a/homeassistant/components/growatt_server/translations/cs.json +++ b/homeassistant/components/growatt_server/translations/cs.json @@ -1,8 +1,12 @@ { "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" + }, "step": { "user": { "data": { + "name": "Jm\u00e9no", "url": "URL" } } diff --git a/homeassistant/components/growatt_server/translations/sk.json b/homeassistant/components/growatt_server/translations/sk.json new file mode 100644 index 00000000000000..d2e4793a68b4d4 --- /dev/null +++ b/homeassistant/components/growatt_server/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/guardian/translations/el.json b/homeassistant/components/guardian/translations/el.json index ed7ca36d8c2638..3f3d255645639f 100644 --- a/homeassistant/components/guardian/translations/el.json +++ b/homeassistant/components/guardian/translations/el.json @@ -8,6 +8,9 @@ "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Guardian;" }, "user": { + "data": { + "ip_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + }, "description": "\u0394\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c4\u03bf\u03c0\u03b9\u03ba\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Elexa Guardian." }, "zeroconf_confirm": { diff --git a/homeassistant/components/guardian/translations/sk.json b/homeassistant/components/guardian/translations/sk.json new file mode 100644 index 00000000000000..b41d6edbd4b1ef --- /dev/null +++ b/homeassistant/components/guardian/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + }, + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/sk.json b/homeassistant/components/habitica/translations/sk.json new file mode 100644 index 00000000000000..bcfc3880d99cc6 --- /dev/null +++ b/homeassistant/components/habitica/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_credentials": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hangouts/translations/sk.json b/homeassistant/components/hangouts/translations/sk.json new file mode 100644 index 00000000000000..45123261c43d91 --- /dev/null +++ b/homeassistant/components/hangouts/translations/sk.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "email": "Email", + "password": "Heslo" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/harmony/translations/el.json b/homeassistant/components/harmony/translations/el.json index d17c5f3a43c0cf..738b64c64096a9 100644 --- a/homeassistant/components/harmony/translations/el.json +++ b/homeassistant/components/harmony/translations/el.json @@ -8,6 +8,7 @@ }, "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03ba\u03cc\u03bc\u03b2\u03bf\u03c5" }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Logitech Harmony Hub" diff --git a/homeassistant/components/heos/translations/el.json b/homeassistant/components/heos/translations/el.json index e6c5379e30ed1f..5e1483813a8b23 100644 --- a/homeassistant/components/heos/translations/el.json +++ b/homeassistant/components/heos/translations/el.json @@ -2,6 +2,9 @@ "config": { "step": { "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03bc\u03b9\u03b1\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 Heos (\u03ba\u03b1\u03c4\u03ac \u03c0\u03c1\u03bf\u03c4\u03af\u03bc\u03b7\u03c3\u03b7 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03b7\u03c2 \u03bc\u03b5 \u03ba\u03b1\u03bb\u03ce\u03b4\u03b9\u03bf \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf).", "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf Heos" } diff --git a/homeassistant/components/hive/translations/sk.json b/homeassistant/components/hive/translations/sk.json new file mode 100644 index 00000000000000..c2f015fe339a0e --- /dev/null +++ b/homeassistant/components/hive/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hlk_sw16/translations/cs.json b/homeassistant/components/hlk_sw16/translations/cs.json index 1801c0d3b92f6d..a4bad4b7c9f585 100644 --- a/homeassistant/components/hlk_sw16/translations/cs.json +++ b/homeassistant/components/hlk_sw16/translations/cs.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Za\u0159\u00edzen\u00ed ji\u017e je nastaveno" + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" }, "error": { "cannot_connect": "Nelze se p\u0159ipojit", diff --git a/homeassistant/components/hlk_sw16/translations/el.json b/homeassistant/components/hlk_sw16/translations/el.json index 18c2f0869bd1c2..54b55bb8324647 100644 --- a/homeassistant/components/hlk_sw16/translations/el.json +++ b/homeassistant/components/hlk_sw16/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } diff --git a/homeassistant/components/hlk_sw16/translations/sk.json b/homeassistant/components/hlk_sw16/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/hlk_sw16/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/home_connect/translations/sk.json b/homeassistant/components/home_connect/translations/sk.json new file mode 100644 index 00000000000000..c19b1a0b70c707 --- /dev/null +++ b/homeassistant/components/home_connect/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "create_entry": { + "default": "\u00daspe\u0161ne overen\u00e9" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/sk.json b/homeassistant/components/home_plus_control/translations/sk.json new file mode 100644 index 00000000000000..97ac5f20ed2b01 --- /dev/null +++ b/homeassistant/components/home_plus_control/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + }, + "create_entry": { + "default": "\u00daspe\u0161ne overen\u00e9" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/sk.json b/homeassistant/components/homekit_controller/translations/sk.json new file mode 100644 index 00000000000000..bee0999420fbf7 --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homematicip_cloud/translations/sk.json b/homeassistant/components/homematicip_cloud/translations/sk.json new file mode 100644 index 00000000000000..48638e0878737d --- /dev/null +++ b/homeassistant/components/homematicip_cloud/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "init": { + "data": { + "name": "N\u00e1zov (volite\u013en\u00e9, pou\u017e\u00edva sa ako predpona pre v\u0161etky zariadenia)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/honeywell/translations/sk.json b/homeassistant/components/honeywell/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/honeywell/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huawei_lte/translations/sk.json b/homeassistant/components/huawei_lte/translations/sk.json new file mode 100644 index 00000000000000..0b7bf878ea9887 --- /dev/null +++ b/homeassistant/components/huawei_lte/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hue/translations/cs.json b/homeassistant/components/hue/translations/cs.json index 1708abfe750173..cee67307991d3e 100644 --- a/homeassistant/components/hue/translations/cs.json +++ b/homeassistant/components/hue/translations/cs.json @@ -35,6 +35,10 @@ }, "device_automation": { "trigger_subtype": { + "1": "Prvn\u00ed tla\u010d\u00edtko", + "2": "Druh\u00e9 tla\u010d\u00edtko", + "3": "T\u0159et\u00ed tla\u010d\u00edtko", + "4": "\u010ctvrt\u00e9 tla\u010d\u00edtko", "button_1": "Prvn\u00ed tla\u010d\u00edtko", "button_2": "Druh\u00e9 tla\u010d\u00edtko", "button_3": "T\u0159et\u00ed tla\u010d\u00edtko", @@ -47,11 +51,16 @@ "turn_on": "Zapnout" }, "trigger_type": { + "double_short_release": "Oba \"{subtype}\" uvoln\u011bny", + "initial_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto", + "long_release": "Tla\u010d\u00edtko \"{subtype}\" uvoln\u011bno po dlouh\u00e9m stisku", "remote_button_long_release": "Tla\u010d\u00edtko \"{subtype}\" uvoln\u011bno po dlouh\u00e9m stisku", "remote_button_short_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto", "remote_button_short_release": "Uvoln\u011bno tla\u010d\u00edtko \"{subtype}\"", "remote_double_button_long_press": "Oba \"{subtype}\" uvoln\u011bny po dlouh\u00e9m stisku", - "remote_double_button_short_press": "Oba \"{subtype}\" uvoln\u011bny" + "remote_double_button_short_press": "Oba \"{subtype}\" uvoln\u011bny", + "repeat": "Tla\u010d\u00edtko \"{subtype}\" podr\u017eeno", + "short_release": "Tla\u010d\u00edtko \"{subtype}\" uvoln\u011bno po kr\u00e1tk\u00e9m stisku" } }, "options": { @@ -59,7 +68,9 @@ "init": { "data": { "allow_hue_groups": "Povolit skupiny Hue", - "allow_unreachable": "Povolit nedostupn\u00fdm \u017e\u00e1rovk\u00e1m spr\u00e1vn\u011b hl\u00e1sit jejich stav" + "allow_hue_scenes": "Povolit sc\u00e9ny Hue", + "allow_unreachable": "Povolit nedostupn\u00fdm \u017e\u00e1rovk\u00e1m spr\u00e1vn\u011b hl\u00e1sit jejich stav", + "ignore_availability": "Ignorovat stav spojen\u00ed pro dan\u00e9 za\u0159\u00edzen\u00ed" } } } diff --git a/homeassistant/components/hue/translations/el.json b/homeassistant/components/hue/translations/el.json index 7da30f4a1be8e1..b53e31046b66cb 100644 --- a/homeassistant/components/hue/translations/el.json +++ b/homeassistant/components/hue/translations/el.json @@ -50,6 +50,8 @@ "remote_button_long_release": "\u03a4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03c4\u03bf\u03c5 \"{subtype}\" \u03b1\u03c0\u03b5\u03bb\u03b5\u03c5\u03b8\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03bc\u03b5\u03c4\u03ac \u03b1\u03c0\u03cc \u03c0\u03b1\u03c1\u03b1\u03c4\u03b5\u03c4\u03b1\u03bc\u03ad\u03bd\u03bf \u03c0\u03ac\u03c4\u03b7\u03bc\u03b1", "remote_button_short_press": "\u03a0\u03b1\u03c4\u03ae\u03b8\u03b7\u03ba\u03b5 \u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03c4\u03bf\u03c5 \"{subtype}\"", "remote_button_short_release": "\u0391\u03c6\u03ad\u03b8\u03b7\u03ba\u03b5 \u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03c4\u03bf\u03c5 \"{subtype}\"", + "remote_double_button_long_press": "\u039a\u03b1\u03b9 \u03c4\u03b1 \u03b4\u03cd\u03bf \"{subtype}\" \u03b1\u03c0\u03b5\u03bb\u03b5\u03c5\u03b8\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b1\u03bd \u03bc\u03b5\u03c4\u03ac \u03b1\u03c0\u03cc \u03c0\u03b1\u03c1\u03b1\u03c4\u03b5\u03c4\u03b1\u03bc\u03ad\u03bd\u03bf \u03c0\u03ac\u03c4\u03b7\u03bc\u03b1", + "remote_double_button_short_press": "\u039a\u03b1\u03b9 \u03c4\u03b1 \u03b4\u03cd\u03bf \"{subtype}\" \u03b1\u03c0\u03b5\u03bb\u03b5\u03c5\u03b8\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b1\u03bd", "repeat": "\u03a4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \"{subtype}\" \u03ba\u03c1\u03b1\u03c4\u03ae\u03b8\u03b7\u03ba\u03b5 \u03c0\u03b1\u03c4\u03b7\u03bc\u03ad\u03bd\u03bf", "short_release": "\u03a4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \" {subtype} \" \u03b1\u03c0\u03b5\u03bb\u03b5\u03c5\u03b8\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03bc\u03b5\u03c4\u03ac \u03b1\u03c0\u03cc \u03c3\u03cd\u03bd\u03c4\u03bf\u03bc\u03bf \u03c0\u03ac\u03c4\u03b7\u03bc\u03b1" } diff --git a/homeassistant/components/hue/translations/sk.json b/homeassistant/components/hue/translations/sk.json index 3912c15c86c66d..424ac0d9252ba7 100644 --- a/homeassistant/components/hue/translations/sk.json +++ b/homeassistant/components/hue/translations/sk.json @@ -2,11 +2,22 @@ "config": { "abort": { "all_configured": "V\u0161etky Philips Hue bridge u\u017e boli nakonfigurovan\u00e9", - "no_bridges": "Neboli objaven\u00fd \u017eiaden Philips Hue bridge" + "already_configured": "Zariadenie u\u017e je nakonfigurovan\u00e9", + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha", + "cannot_connect": "Nepodarilo sa pripoji\u0165", + "no_bridges": "Neboli objaven\u00fd \u017eiaden Philips Hue bridge", + "unknown": "Neo\u010dak\u00e1van\u00e1 chyba" + }, + "error": { + "linking": "Neo\u010dak\u00e1van\u00e1 chyba", + "register_failed": "Registr\u00e1cia zlyhala, pros\u00edm, sk\u00faste znova" }, "step": { + "init": { + "title": "Vyberte Hue bridge" + }, "link": { - "description": "Stla\u010dte tla\u010didlo na Philips Hue bridge pre registr\u00e1ciu Philips Hue s Home Assistant.\n\n![Location of button on bridge](/static/images/config_philips_hue.jpg)" + "description": "Pre registr\u00e1ciu Philips Hue s Home Assistant stla\u010dte tla\u010didlo na Philips Hue bridge.\n\n![Location of button on bridge](/static/images/config_philips_hue.jpg)" } } } diff --git a/homeassistant/components/huisbaasje/translations/sk.json b/homeassistant/components/huisbaasje/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/el.json b/homeassistant/components/hunterdouglas_powerview/translations/el.json index 12cde31ea6d50c..2b4ce98a901613 100644 --- a/homeassistant/components/hunterdouglas_powerview/translations/el.json +++ b/homeassistant/components/hunterdouglas_powerview/translations/el.json @@ -7,6 +7,9 @@ "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf PowerView Hub" }, "user": { + "data": { + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf PowerView Hub" } } diff --git a/homeassistant/components/hvv_departures/translations/sk.json b/homeassistant/components/hvv_departures/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/hvv_departures/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hyperion/translations/el.json b/homeassistant/components/hyperion/translations/el.json index b61660d41c4d5d..4b712187d4c538 100644 --- a/homeassistant/components/hyperion/translations/el.json +++ b/homeassistant/components/hyperion/translations/el.json @@ -27,6 +27,7 @@ }, "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "port": "\u0398\u03cd\u03c1\u03b1" } } diff --git a/homeassistant/components/hyperion/translations/sk.json b/homeassistant/components/hyperion/translations/sk.json new file mode 100644 index 00000000000000..a8223b5b2e362c --- /dev/null +++ b/homeassistant/components/hyperion/translations/sk.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha", + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ialarm/translations/sk.json b/homeassistant/components/ialarm/translations/sk.json new file mode 100644 index 00000000000000..892b8b2cd91240 --- /dev/null +++ b/homeassistant/components/ialarm/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iaqualink/translations/sk.json b/homeassistant/components/iaqualink/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/iaqualink/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/icloud/translations/sk.json b/homeassistant/components/icloud/translations/sk.json new file mode 100644 index 00000000000000..d30ed436a4fd27 --- /dev/null +++ b/homeassistant/components/icloud/translations/sk.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "username": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ifttt/translations/el.json b/homeassistant/components/ifttt/translations/el.json index 77ccacd89d6fdd..e6180bc7eadf46 100644 --- a/homeassistant/components/ifttt/translations/el.json +++ b/homeassistant/components/ifttt/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf Home Assistant Cloud.", "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, "create_entry": { diff --git a/homeassistant/components/insteon/translations/el.json b/homeassistant/components/insteon/translations/el.json index 30d1c5e01e7041..a3635ae5e2eb9b 100644 --- a/homeassistant/components/insteon/translations/el.json +++ b/homeassistant/components/insteon/translations/el.json @@ -6,6 +6,7 @@ "step": { "hubv1": { "data": { + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", "port": "\u0398\u03cd\u03c1\u03b1" }, "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd \u03c4\u03bf\u03c5 Insteon Hub Version 1 (\u03c0\u03c1\u03b9\u03bd \u03b1\u03c0\u03cc \u03c4\u03bf 2014).", @@ -61,6 +62,7 @@ }, "change_hub_config": { "data": { + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0391\u03bb\u03bb\u03ac\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 Insteon Hub. \u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Home Assistant \u03bc\u03b5\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03b1\u03b3\u03bc\u03b1\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b1\u03c5\u03c4\u03ae\u03c2 \u03c4\u03b7\u03c2 \u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2. \u0391\u03c5\u03c4\u03cc \u03b4\u03b5\u03bd \u03b1\u03bb\u03bb\u03ac\u03b6\u03b5\u03b9 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03af\u03b4\u03b9\u03bf\u03c5 \u03c4\u03bf\u03c5 Hub. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03bb\u03bb\u03ac\u03be\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c3\u03c4\u03bf Hub \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae Hub.", diff --git a/homeassistant/components/insteon/translations/sk.json b/homeassistant/components/insteon/translations/sk.json new file mode 100644 index 00000000000000..c563a509f07176 --- /dev/null +++ b/homeassistant/components/insteon/translations/sk.json @@ -0,0 +1,25 @@ +{ + "config": { + "step": { + "hubv1": { + "data": { + "port": "Port" + } + }, + "hubv2": { + "data": { + "port": "Port" + } + } + } + }, + "options": { + "step": { + "change_hub_config": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ios/translations/sk.json b/homeassistant/components/ios/translations/sk.json new file mode 100644 index 00000000000000..e227301685bbd0 --- /dev/null +++ b/homeassistant/components/ios/translations/sk.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "confirm": { + "description": "Chcete za\u010da\u0165 nastavova\u0165?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iotawatt/translations/el.json b/homeassistant/components/iotawatt/translations/el.json index 4499676487351b..f25c2c4dcbcaa4 100644 --- a/homeassistant/components/iotawatt/translations/el.json +++ b/homeassistant/components/iotawatt/translations/el.json @@ -11,6 +11,11 @@ "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae IoTawatt \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2. \u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03ba\u03b1\u03b9 \u03ba\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03a5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae." + }, + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + } } } } diff --git a/homeassistant/components/iotawatt/translations/sk.json b/homeassistant/components/iotawatt/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/iotawatt/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ipma/translations/sk.json b/homeassistant/components/ipma/translations/sk.json new file mode 100644 index 00000000000000..e5a635afe105f9 --- /dev/null +++ b/homeassistant/components/ipma/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "step": { + "user": { + "data": { + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka", + "name": "N\u00e1zov" + }, + "title": "Umiestnenie" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ipp/translations/el.json b/homeassistant/components/ipp/translations/el.json index ad89d55c511ecf..82ab5db44526ae 100644 --- a/homeassistant/components/ipp/translations/el.json +++ b/homeassistant/components/ipp/translations/el.json @@ -2,6 +2,7 @@ "config": { "abort": { "connection_upgrade": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae \u03b5\u03c0\u03b5\u03b9\u03b4\u03ae \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03bd\u03b1\u03b2\u03ac\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2.", + "ipp_error": "\u03a0\u03b1\u03c1\u03bf\u03c5\u03c3\u03b9\u03ac\u03c3\u03c4\u03b7\u03ba\u03b5 \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 IPP.", "ipp_version_error": "\u0397 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 IPP \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae.", "parse_error": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b1\u03bd\u03ac\u03bb\u03c5\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b1\u03c0\u03ac\u03bd\u03c4\u03b7\u03c3\u03b7\u03c2 \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae.", "unique_id_required": "\u039b\u03b5\u03af\u03c0\u03b5\u03b9 \u03b7 \u03bc\u03bf\u03bd\u03b1\u03b4\u03b9\u03ba\u03ae \u03b1\u03bd\u03b1\u03b3\u03bd\u03ce\u03c1\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03c0\u03bf\u03c5 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7." diff --git a/homeassistant/components/ipp/translations/sk.json b/homeassistant/components/ipp/translations/sk.json new file mode 100644 index 00000000000000..892b8b2cd91240 --- /dev/null +++ b/homeassistant/components/ipp/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/isy994/translations/sk.json b/homeassistant/components/isy994/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/isy994/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/jellyfin/translations/cs.json b/homeassistant/components/jellyfin/translations/cs.json new file mode 100644 index 00000000000000..c9a6f8f2462e5c --- /dev/null +++ b/homeassistant/components/jellyfin/translations/cs.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/jellyfin/translations/sk.json b/homeassistant/components/jellyfin/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/jellyfin/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/juicenet/translations/sk.json b/homeassistant/components/juicenet/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/juicenet/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/keenetic_ndms2/translations/sk.json b/homeassistant/components/keenetic_ndms2/translations/sk.json new file mode 100644 index 00000000000000..892b8b2cd91240 --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/sk.json b/homeassistant/components/kmtronic/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/knx/translations/cs.json b/homeassistant/components/knx/translations/cs.json new file mode 100644 index 00000000000000..31c65f915ddde4 --- /dev/null +++ b/homeassistant/components/knx/translations/cs.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Slu\u017eba je ji\u017e nastavena" + }, + "step": { + "manual_tunnel": { + "data": { + "port": "Port" + } + } + } + }, + "options": { + "step": { + "tunnel": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/knx/translations/sk.json b/homeassistant/components/knx/translations/sk.json new file mode 100644 index 00000000000000..6668aaa92fb563 --- /dev/null +++ b/homeassistant/components/knx/translations/sk.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Slu\u017eba u\u017e je nakonfigurovan\u00e1" + }, + "step": { + "manual_tunnel": { + "data": { + "port": "Port" + } + } + } + }, + "options": { + "step": { + "tunnel": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/el.json b/homeassistant/components/kodi/translations/el.json index d037c2e07fd1a8..d5a3c1815e13ed 100644 --- a/homeassistant/components/kodi/translations/el.json +++ b/homeassistant/components/kodi/translations/el.json @@ -14,6 +14,7 @@ }, "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "ssl": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03ad\u03c3\u03c9 SSL" }, "description": "\u03a0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 Kodi. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b2\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03ad\u03c7\u03b5\u03c4\u03b5 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9 \u03c4\u03bf \"\u039d\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03b5\u03c4\u03b1\u03b9 \u03bf \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03bf\u03c5 Kodi \u03bc\u03ad\u03c3\u03c9 HTTP\" \u03c3\u03c4\u03bf \u03a3\u03cd\u03c3\u03c4\u03b7\u03bc\u03b1/\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2/\u0394\u03af\u03ba\u03c4\u03c5\u03bf/\u03a5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b5\u03c2." diff --git a/homeassistant/components/kodi/translations/sk.json b/homeassistant/components/kodi/translations/sk.json new file mode 100644 index 00000000000000..ab39cbe9c5e0ff --- /dev/null +++ b/homeassistant/components/kodi/translations/sk.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "Zariadenie u\u017e je nakonfigurovan\u00e9", + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "credentials": { + "data": { + "username": "Pou\u017e\u00edvate\u013esk\u00e9 meno" + } + }, + "user": { + "data": { + "port": "Port" + } + }, + "ws_port": { + "data": { + "ws_port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/konnected/translations/sk.json b/homeassistant/components/konnected/translations/sk.json new file mode 100644 index 00000000000000..51eaba460d8762 --- /dev/null +++ b/homeassistant/components/konnected/translations/sk.json @@ -0,0 +1,33 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + }, + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + }, + "options": { + "step": { + "options_binary": { + "data": { + "name": "N\u00e1zov (volite\u013en\u00fd)" + } + }, + "options_digital": { + "data": { + "name": "N\u00e1zov (volite\u013en\u00fd)" + } + }, + "options_switch": { + "data": { + "name": "N\u00e1zov (volite\u013en\u00fd)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kostal_plenticore/translations/sk.json b/homeassistant/components/kostal_plenticore/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/kostal_plenticore/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/life360/translations/sk.json b/homeassistant/components/life360/translations/sk.json new file mode 100644 index 00000000000000..2c3ed1dd93049d --- /dev/null +++ b/homeassistant/components/life360/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/sk.json b/homeassistant/components/litejet/translations/sk.json new file mode 100644 index 00000000000000..892b8b2cd91240 --- /dev/null +++ b/homeassistant/components/litejet/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sk.json b/homeassistant/components/litterrobot/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/translations/el.json b/homeassistant/components/locative/translations/el.json index a1cdfb27918692..ee01e31d21c5a0 100644 --- a/homeassistant/components/locative/translations/el.json +++ b/homeassistant/components/locative/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf Home Assistant Cloud.", "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, "create_entry": { diff --git a/homeassistant/components/logi_circle/translations/sk.json b/homeassistant/components/logi_circle/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/logi_circle/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lookin/translations/cs.json b/homeassistant/components/lookin/translations/cs.json new file mode 100644 index 00000000000000..50dcaf1b95f792 --- /dev/null +++ b/homeassistant/components/lookin/translations/cs.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigurace ji\u017e prob\u00edh\u00e1" + }, + "error": { + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "device_name": { + "data": { + "name": "Jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lookin/translations/el.json b/homeassistant/components/lookin/translations/el.json index 0188d8032e6a8b..316feac28c6cc6 100644 --- a/homeassistant/components/lookin/translations/el.json +++ b/homeassistant/components/lookin/translations/el.json @@ -9,6 +9,11 @@ }, "discovery_confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ({host});" + }, + "user": { + "data": { + "ip_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + } } } } diff --git a/homeassistant/components/lookin/translations/sk.json b/homeassistant/components/lookin/translations/sk.json new file mode 100644 index 00000000000000..561644de2dd7f1 --- /dev/null +++ b/homeassistant/components/lookin/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + }, + "step": { + "device_name": { + "data": { + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/el.json b/homeassistant/components/lutron_caseta/translations/el.json index 803b5e1231ab99..63db1d8237c17a 100644 --- a/homeassistant/components/lutron_caseta/translations/el.json +++ b/homeassistant/components/lutron_caseta/translations/el.json @@ -50,6 +50,11 @@ "open_3": "\u0386\u03bd\u03bf\u03b9\u03b3\u03bc\u03b1 3", "open_4": "\u0386\u03bd\u03bf\u03b9\u03b3\u03bc\u03b1 4", "open_all": "\u0386\u03bd\u03bf\u03b9\u03b3\u03bc\u03b1 \u03cc\u03bb\u03c9\u03bd", + "raise": "\u0391\u03cd\u03be\u03b7\u03c3\u03b7", + "raise_1": "\u0391\u03cd\u03be\u03b7\u03c3\u03b7 1", + "raise_2": "\u0391\u03cd\u03be\u03b7\u03c3\u03b7 2", + "raise_3": "\u0391\u03cd\u03be\u03b7\u03c3\u03b7 3", + "raise_4": "\u0391\u03cd\u03be\u03b7\u03c3\u03b7 4", "raise_all": "\u03a3\u03b7\u03ba\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03cc\u03bb\u03b1", "stop": "\u0394\u03b9\u03b1\u03ba\u03bf\u03c0\u03ae (\u03b1\u03b3\u03b1\u03c0\u03b7\u03bc\u03ad\u03bd\u03bf)", "stop_1": "\u0394\u03b9\u03b1\u03ba\u03bf\u03c0\u03ae 1", diff --git a/homeassistant/components/lyric/translations/sk.json b/homeassistant/components/lyric/translations/sk.json new file mode 100644 index 00000000000000..520a3afd6d921d --- /dev/null +++ b/homeassistant/components/lyric/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "create_entry": { + "default": "\u00daspe\u0161ne overen\u00e9" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/translations/el.json b/homeassistant/components/mailgun/translations/el.json index 385d9d8973de53..263ce28f293910 100644 --- a/homeassistant/components/mailgun/translations/el.json +++ b/homeassistant/components/mailgun/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf Home Assistant Cloud.", "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, "create_entry": { diff --git a/homeassistant/components/mazda/translations/el.json b/homeassistant/components/mazda/translations/el.json index d998a1a9d74dd5..5f30894b381b69 100644 --- a/homeassistant/components/mazda/translations/el.json +++ b/homeassistant/components/mazda/translations/el.json @@ -1,7 +1,8 @@ { "config": { "error": { - "account_locked": "\u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ba\u03bb\u03b5\u03b9\u03b4\u03c9\u03bc\u03ad\u03bd\u03bf\u03c2. \u03a0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1." + "account_locked": "\u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ba\u03bb\u03b5\u03b9\u03b4\u03c9\u03bc\u03ad\u03bd\u03bf\u03c2. \u03a0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1.", + "unknown": "\u039c\u03b7 \u03b1\u03bd\u03b1\u03bc\u03b5\u03bd\u03cc\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/mazda/translations/sk.json b/homeassistant/components/mazda/translations/sk.json new file mode 100644 index 00000000000000..f8b6dfeea813ef --- /dev/null +++ b/homeassistant/components/mazda/translations/sk.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "email": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/melcloud/translations/sk.json b/homeassistant/components/melcloud/translations/sk.json new file mode 100644 index 00000000000000..c043ef9ff19d26 --- /dev/null +++ b/homeassistant/components/melcloud/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "username": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/met/translations/cs.json b/homeassistant/components/met/translations/cs.json index 6ee3d3c4dc138b..0a3c7ec802e28f 100644 --- a/homeassistant/components/met/translations/cs.json +++ b/homeassistant/components/met/translations/cs.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "no_home": "V konfiguraci Home Assistant nejsou nastaveny \u017e\u00e1dn\u00e9 domovsk\u00e9 sou\u0159adnice" + }, "error": { "already_configured": "Slu\u017eba je ji\u017e nastavena" }, diff --git a/homeassistant/components/met/translations/sk.json b/homeassistant/components/met/translations/sk.json new file mode 100644 index 00000000000000..55d4920e30abc5 --- /dev/null +++ b/homeassistant/components/met/translations/sk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "no_home": "V konfigur\u00e1cii Home Assistant nie s\u00fa nastaven\u00e9 \u017eiadne dom\u00e1ce s\u00faradnice" + }, + "error": { + "already_configured": "Slu\u017eba u\u017e je nakonfigurovan\u00e1" + }, + "step": { + "user": { + "data": { + "elevation": "Nadmorsk\u00e1 v\u00fd\u0161ka", + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka", + "name": "N\u00e1zov" + }, + "description": "Meteorologick\u00fd \u00fastav", + "title": "Umiestnenie" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/met_eireann/translations/sk.json b/homeassistant/components/met_eireann/translations/sk.json new file mode 100644 index 00000000000000..492ab052d9a5d6 --- /dev/null +++ b/homeassistant/components/met_eireann/translations/sk.json @@ -0,0 +1,15 @@ +{ + "config": { + "step": { + "user": { + "data": { + "elevation": "Nadmorsk\u00e1 v\u00fd\u0161ka", + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka", + "name": "N\u00e1zov" + }, + "title": "Umiestnenie" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meteo_france/translations/el.json b/homeassistant/components/meteo_france/translations/el.json index a1adaa91c18241..330cd15fb59212 100644 --- a/homeassistant/components/meteo_france/translations/el.json +++ b/homeassistant/components/meteo_france/translations/el.json @@ -19,5 +19,14 @@ "title": "M\u00e9t\u00e9o-France" } } + }, + "options": { + "step": { + "init": { + "data": { + "mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c0\u03c1\u03cc\u03b2\u03bb\u03b5\u03c8\u03b7\u03c2" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/meteoclimatic/translations/cs.json b/homeassistant/components/meteoclimatic/translations/cs.json new file mode 100644 index 00000000000000..3b814303e69583 --- /dev/null +++ b/homeassistant/components/meteoclimatic/translations/cs.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/metoffice/translations/sk.json b/homeassistant/components/metoffice/translations/sk.json new file mode 100644 index 00000000000000..abb3969f6b4ac0 --- /dev/null +++ b/homeassistant/components/metoffice/translations/sk.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d", + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mikrotik/translations/sk.json b/homeassistant/components/mikrotik/translations/sk.json new file mode 100644 index 00000000000000..6f753f20966542 --- /dev/null +++ b/homeassistant/components/mikrotik/translations/sk.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "name": "N\u00e1zov", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mill/translations/el.json b/homeassistant/components/mill/translations/el.json index 37b91a3dce8ce7..7b04113d7a2336 100644 --- a/homeassistant/components/mill/translations/el.json +++ b/homeassistant/components/mill/translations/el.json @@ -8,6 +8,9 @@ } }, "local": { + "data": { + "ip_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + }, "description": "\u03a4\u03bf\u03c0\u03b9\u03ba\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2." }, "user": { diff --git a/homeassistant/components/minecraft_server/translations/el.json b/homeassistant/components/minecraft_server/translations/el.json index e5dc88173d40ec..d6315fef3d1a65 100644 --- a/homeassistant/components/minecraft_server/translations/el.json +++ b/homeassistant/components/minecraft_server/translations/el.json @@ -7,6 +7,9 @@ }, "step": { "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae Minecraft \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03b5\u03c4\u03b1\u03b9 \u03b7 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae Minecraft" } diff --git a/homeassistant/components/minecraft_server/translations/sk.json b/homeassistant/components/minecraft_server/translations/sk.json new file mode 100644 index 00000000000000..af15f92c2f27a7 --- /dev/null +++ b/homeassistant/components/minecraft_server/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mjpeg/translations/cs.json b/homeassistant/components/mjpeg/translations/cs.json new file mode 100644 index 00000000000000..00616fbdd504a8 --- /dev/null +++ b/homeassistant/components/mjpeg/translations/cs.json @@ -0,0 +1,26 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" + }, + "step": { + "user": { + "data": { + "name": "Jm\u00e9no" + } + } + } + }, + "options": { + "error": { + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" + }, + "step": { + "init": { + "data": { + "name": "Jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mjpeg/translations/el.json b/homeassistant/components/mjpeg/translations/el.json index db0dd06dbbf262..404adec7a94f7b 100644 --- a/homeassistant/components/mjpeg/translations/el.json +++ b/homeassistant/components/mjpeg/translations/el.json @@ -1,8 +1,19 @@ { + "config": { + "step": { + "user": { + "data": { + "mjpeg_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 MJPEG URL", + "still_image_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae\u03c2 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1\u03c2" + } + } + } + }, "options": { "step": { "init": { "data": { + "mjpeg_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 MJPEG URL", "still_image_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae\u03c2 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1\u03c2" } } diff --git a/homeassistant/components/mjpeg/translations/he.json b/homeassistant/components/mjpeg/translations/he.json new file mode 100644 index 00000000000000..2d53457d2778b5 --- /dev/null +++ b/homeassistant/components/mjpeg/translations/he.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4" + }, + "error": { + "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", + "invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9" + }, + "step": { + "user": { + "data": { + "name": "\u05e9\u05dd", + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9", + "verify_ssl": "\u05d0\u05d9\u05de\u05d5\u05ea \u05d0\u05d9\u05e9\u05d5\u05e8 SSL" + } + } + } + }, + "options": { + "error": { + "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4", + "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", + "invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9" + }, + "step": { + "init": { + "data": { + "name": "\u05e9\u05dd", + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9", + "verify_ssl": "\u05d0\u05d9\u05de\u05d5\u05ea \u05d0\u05d9\u05e9\u05d5\u05e8 SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mjpeg/translations/pl.json b/homeassistant/components/mjpeg/translations/pl.json new file mode 100644 index 00000000000000..701049ceac44b1 --- /dev/null +++ b/homeassistant/components/mjpeg/translations/pl.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie" + }, + "step": { + "user": { + "data": { + "mjpeg_url": "Adres URL dla MJPEG", + "name": "Nazwa", + "password": "Has\u0142o", + "still_image_url": "Adres URL dla obrazu nieruchomego (still image)", + "username": "Nazwa u\u017cytkownika", + "verify_ssl": "Weryfikacja certyfikatu SSL" + } + } + } + }, + "options": { + "error": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie" + }, + "step": { + "init": { + "data": { + "mjpeg_url": "Adres URL dla MJPEG", + "name": "Nazwa", + "password": "Has\u0142o", + "still_image_url": "Adres URL dla obrazu nieruchomego (still image)", + "username": "Nazwa u\u017cytkownika", + "verify_ssl": "Weryfikacja certyfikatu SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mjpeg/translations/sk.json b/homeassistant/components/mjpeg/translations/sk.json new file mode 100644 index 00000000000000..4a2050c2353b55 --- /dev/null +++ b/homeassistant/components/mjpeg/translations/sk.json @@ -0,0 +1,26 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "name": "N\u00e1zov" + } + } + } + }, + "options": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "init": { + "data": { + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mjpeg/translations/zh-Hant.json b/homeassistant/components/mjpeg/translations/zh-Hant.json new file mode 100644 index 00000000000000..1416bc3ca0038f --- /dev/null +++ b/homeassistant/components/mjpeg/translations/zh-Hant.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" + }, + "step": { + "user": { + "data": { + "mjpeg_url": "MJPEG URL", + "name": "\u540d\u7a31", + "password": "\u5bc6\u78bc", + "still_image_url": "\u975c\u614b\u5716\u50cf URL", + "username": "\u4f7f\u7528\u8005\u540d\u7a31", + "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" + } + } + } + }, + "options": { + "error": { + "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" + }, + "step": { + "init": { + "data": { + "mjpeg_url": "MJPEG URL", + "name": "\u540d\u7a31", + "password": "\u5bc6\u78bc", + "still_image_url": "\u975c\u614b\u5716\u50cf URL", + "username": "\u4f7f\u7528\u8005\u540d\u7a31", + "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/cs.json b/homeassistant/components/mobile_app/translations/cs.json index 467536cc5ec687..2e39d1ab16c262 100644 --- a/homeassistant/components/mobile_app/translations/cs.json +++ b/homeassistant/components/mobile_app/translations/cs.json @@ -13,5 +13,6 @@ "action_type": { "notify": "Odeslat ozn\u00e1men\u00ed" } - } + }, + "title": "Mobiln\u00ed aplikace" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/sk.json b/homeassistant/components/mobile_app/translations/sk.json new file mode 100644 index 00000000000000..3c056bf4f85400 --- /dev/null +++ b/homeassistant/components/mobile_app/translations/sk.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "install_app": "Pre nastavenie integr\u00e1cie s Home Assistant otvorte mobiln\u00fa aplik\u00e1ciu. Zoznam kompatibiln\u00fdch aplik\u00e1ci\u00ed n\u00e1jdete v [dokument\u00e1cii]({apps_url})." + }, + "step": { + "confirm": { + "description": "Chcete nastavi\u0165 komponentu Mobilnej aplik\u00e1cie?" + } + } + }, + "device_automation": { + "action_type": { + "notify": "Odosla\u0165 ozn\u00e1menie" + } + }, + "title": "Mobiln\u00e1 aplik\u00e1cia" +} \ No newline at end of file diff --git a/homeassistant/components/modem_callerid/translations/sk.json b/homeassistant/components/modem_callerid/translations/sk.json new file mode 100644 index 00000000000000..f7ef4cd289d5fc --- /dev/null +++ b/homeassistant/components/modem_callerid/translations/sk.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + }, + "step": { + "user": { + "data": { + "name": "N\u00e1zov", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/monoprice/translations/sk.json b/homeassistant/components/monoprice/translations/sk.json new file mode 100644 index 00000000000000..892b8b2cd91240 --- /dev/null +++ b/homeassistant/components/monoprice/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/motion_blinds/translations/el.json b/homeassistant/components/motion_blinds/translations/el.json index f820e4ef03ea7d..edd1051671cf23 100644 --- a/homeassistant/components/motion_blinds/translations/el.json +++ b/homeassistant/components/motion_blinds/translations/el.json @@ -13,8 +13,17 @@ "title": "Motion Blinds" }, "select": { + "data": { + "select_ip": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + }, "description": "\u0395\u03ba\u03c4\u03b5\u03bb\u03ad\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b1\u03bd \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03b5\u03c0\u03b9\u03c0\u03bb\u03ad\u03bf\u03bd \u03c0\u03cd\u03bb\u03b5\u03c2 \u03ba\u03af\u03bd\u03b7\u03c3\u03b7\u03c2.", "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03cd\u03bb\u03b7 \u03ba\u03af\u03bd\u03b7\u03c3\u03b7\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5" + }, + "user": { + "data": { + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + }, + "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf Motion Gateway, \u03b5\u03ac\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b7 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7" } } }, diff --git a/homeassistant/components/motion_blinds/translations/sk.json b/homeassistant/components/motion_blinds/translations/sk.json new file mode 100644 index 00000000000000..e58538162d7ac5 --- /dev/null +++ b/homeassistant/components/motion_blinds/translations/sk.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + }, + "step": { + "connect": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + }, + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/motioneye/translations/el.json b/homeassistant/components/motioneye/translations/el.json index de36fc19681242..3ecf4413e11a8b 100644 --- a/homeassistant/components/motioneye/translations/el.json +++ b/homeassistant/components/motioneye/translations/el.json @@ -22,7 +22,8 @@ "init": { "data": { "stream_url_template": "\u03a0\u03c1\u03cc\u03c4\u03c5\u03c0\u03bf URL \u03c1\u03bf\u03ae\u03c2", - "webhook_set": "\u0394\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 webhooks motionEye \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03bd\u03b1\u03c6\u03ad\u03c1\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03b1 \u03c3\u03c4\u03bf\u03bd Home Assistant" + "webhook_set": "\u0394\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 webhooks motionEye \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03bd\u03b1\u03c6\u03ad\u03c1\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03b1 \u03c3\u03c4\u03bf\u03bd Home Assistant", + "webhook_set_overwrite": "\u0391\u03bd\u03c4\u03b9\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03bc\u03b7 \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03c9\u03bd webhooks" } } } diff --git a/homeassistant/components/motioneye/translations/sk.json b/homeassistant/components/motioneye/translations/sk.json new file mode 100644 index 00000000000000..71a7aea5018f3f --- /dev/null +++ b/homeassistant/components/motioneye/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mqtt/translations/el.json b/homeassistant/components/mqtt/translations/el.json index 5b06a6c3d0104f..ffffd249c09420 100644 --- a/homeassistant/components/mqtt/translations/el.json +++ b/homeassistant/components/mqtt/translations/el.json @@ -36,6 +36,8 @@ }, "trigger_type": { "button_double_press": "\u0394\u03b9\u03c0\u03bb\u03cc \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf \"{subtype}\"", + "button_long_press": "\"{subtype}\" \u03c0\u03b9\u03ad\u03c3\u03c4\u03b7\u03ba\u03b5 \u03c3\u03c5\u03bd\u03b5\u03c7\u03ce\u03c2", + "button_long_release": "\"{subtype}\" \u03b1\u03c0\u03b5\u03bb\u03b5\u03c5\u03b8\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03bc\u03b5\u03c4\u03ac \u03b1\u03c0\u03cc \u03c0\u03b1\u03c1\u03b1\u03c4\u03b5\u03c4\u03b1\u03bc\u03ad\u03bd\u03bf \u03c0\u03ac\u03c4\u03b7\u03bc\u03b1", "button_quadruple_press": "\u03a4\u03b5\u03c4\u03c1\u03b1\u03c0\u03bb\u03cc \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf \"{subtype}\"", "button_quintuple_press": "\u03a0\u03b5\u03bd\u03c4\u03b1\u03c0\u03bb\u03cc \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf \"{subtype}\"", "button_short_press": "\u03a0\u03b1\u03c4\u03ae\u03b8\u03b7\u03ba\u03b5 \u03c4\u03bf \"{subtype}\"", @@ -54,6 +56,10 @@ "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 broker" }, "options": { + "data": { + "birth_enable": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03bf\u03c2 birth", + "will_enable": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03bf\u03c2 will" + }, "description": "\u0391\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7 - \u0395\u03ac\u03bd \u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 (\u03c3\u03c5\u03bd\u03b9\u03c3\u03c4\u03ac\u03c4\u03b1\u03b9), \u03c4\u03bf Home Assistant \u03b8\u03b1 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c8\u03b5\u03b9 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03ba\u03b1\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03c0\u03bf\u03c5 \u03b4\u03b7\u03bc\u03bf\u03c3\u03b9\u03b5\u03cd\u03bf\u03c5\u03bd \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03ae \u03c4\u03bf\u03c5\u03c2 \u03c3\u03c4\u03bf\u03bd \u03bc\u03b5\u03c3\u03af\u03c4\u03b7 MQTT. \u0395\u03ac\u03bd \u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7, \u03cc\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b3\u03af\u03bd\u03bf\u03c5\u03bd \u03c7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b1.\nBirth message (\u039c\u03ae\u03bd\u03c5\u03bc\u03b1 \u03b3\u03ad\u03bd\u03bd\u03b7\u03c3\u03b7\u03c2) - \u03a4\u03bf \u03bc\u03ae\u03bd\u03c5\u03bc\u03b1 \u03b3\u03ad\u03bd\u03bd\u03b7\u03c3\u03b7\u03c2 \u03b8\u03b1 \u03b1\u03c0\u03bf\u03c3\u03c4\u03ad\u03bb\u03bb\u03b5\u03c4\u03b1\u03b9 \u03ba\u03ac\u03b8\u03b5 \u03c6\u03bf\u03c1\u03ac \u03c0\u03bf\u03c5 \u03c4\u03bf Home Assistant (\u03b5\u03c0\u03b1\u03bd\u03b1)\u03c3\u03c5\u03bd\u03b4\u03ad\u03b5\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03bc\u03b5\u03c3\u03af\u03c4\u03b7 MQTT.\nWill message - \u03a4\u03bf \u03bc\u03ae\u03bd\u03c5\u03bc\u03b1 will \u03b8\u03b1 \u03b1\u03c0\u03bf\u03c3\u03c4\u03ad\u03bb\u03bb\u03b5\u03c4\u03b1\u03b9 \u03ba\u03ac\u03b8\u03b5 \u03c6\u03bf\u03c1\u03ac \u03c0\u03bf\u03c5 \u03c4\u03bf Home Assistant \u03c7\u03ac\u03bd\u03b5\u03b9 \u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03ae \u03c4\u03bf\u03c5 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03bc\u03b5\u03c3\u03af\u03c4\u03b7, \u03c4\u03cc\u03c3\u03bf \u03c3\u03b5 \u03c0\u03b5\u03c1\u03af\u03c0\u03c4\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b8\u03b1\u03c1\u03ae\u03c2 (\u03c0.\u03c7. \u03c4\u03b5\u03c1\u03bc\u03b1\u03c4\u03b9\u03c3\u03bc\u03cc\u03c2 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03c4\u03bf\u03c5 Home Assistant) \u03cc\u03c3\u03bf \u03ba\u03b1\u03b9 \u03c3\u03b5 \u03c0\u03b5\u03c1\u03af\u03c0\u03c4\u03c9\u03c3\u03b7 \u03bc\u03b7 \u03ba\u03b1\u03b8\u03b1\u03c1\u03ae\u03c2 (\u03c0.\u03c7. \u03c3\u03c5\u03bd\u03c4\u03c1\u03b9\u03b2\u03ae \u03c4\u03bf\u03c5 Home Assistant \u03ae \u03b1\u03c0\u03ce\u03bb\u03b5\u03b9\u03b1 \u03c4\u03b7\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5) \u03b1\u03c0\u03bf\u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2.", "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 MQTT" } diff --git a/homeassistant/components/mqtt/translations/sk.json b/homeassistant/components/mqtt/translations/sk.json new file mode 100644 index 00000000000000..e01295844ec07f --- /dev/null +++ b/homeassistant/components/mqtt/translations/sk.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Slu\u017eba u\u017e je nakonfigurovan\u00e1" + }, + "step": { + "broker": { + "data": { + "password": "Heslo", + "port": "Port", + "username": "Pou\u017e\u00edvate\u013esk\u00e9 meno" + } + } + } + }, + "options": { + "step": { + "broker": { + "data": { + "port": "Port", + "username": "Pou\u017e\u00edvate\u013esk\u00e9 meno" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/myq/translations/sk.json b/homeassistant/components/myq/translations/sk.json new file mode 100644 index 00000000000000..71a7aea5018f3f --- /dev/null +++ b/homeassistant/components/myq/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/sk.json b/homeassistant/components/mysensors/translations/sk.json new file mode 100644 index 00000000000000..2c3ed1dd93049d --- /dev/null +++ b/homeassistant/components/mysensors/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nam/translations/cs.json b/homeassistant/components/nam/translations/cs.json new file mode 100644 index 00000000000000..72df4a968182fc --- /dev/null +++ b/homeassistant/components/nam/translations/cs.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nam/translations/sk.json b/homeassistant/components/nam/translations/sk.json new file mode 100644 index 00000000000000..71a7aea5018f3f --- /dev/null +++ b/homeassistant/components/nam/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nanoleaf/translations/el.json b/homeassistant/components/nanoleaf/translations/el.json index 5112f61ef9ffd7..1829e51815dea8 100644 --- a/homeassistant/components/nanoleaf/translations/el.json +++ b/homeassistant/components/nanoleaf/translations/el.json @@ -16,6 +16,11 @@ "link": { "description": "\u03a0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c0\u03b1\u03c1\u03b1\u03c4\u03b5\u03c4\u03b1\u03bc\u03ad\u03bd\u03b1 \u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03c3\u03c4\u03bf Nanoleaf \u03b3\u03b9\u03b1 5 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1 \u03bc\u03ad\u03c7\u03c1\u03b9 \u03bd\u03b1 \u03b1\u03c1\u03c7\u03af\u03c3\u03bf\u03c5\u03bd \u03bd\u03b1 \u03b1\u03bd\u03b1\u03b2\u03bf\u03c3\u03b2\u03ae\u03bd\u03bf\u03c5\u03bd \u03bf\u03b9 \u03bb\u03c5\u03c7\u03bd\u03af\u03b5\u03c2 LED \u03c4\u03bf\u03c5 \u03ba\u03bf\u03c5\u03bc\u03c0\u03b9\u03bf\u03cd \u03ba\u03b1\u03b9, \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03ba\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af **SUBMIT** \u03bc\u03ad\u03c3\u03b1 \u03c3\u03b5 30 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1.", "title": "\u0394\u03b9\u03ac\u03b6\u03b5\u03c5\u03be\u03b7 Nanoleaf" + }, + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + } } } } diff --git a/homeassistant/components/nanoleaf/translations/sk.json b/homeassistant/components/nanoleaf/translations/sk.json new file mode 100644 index 00000000000000..c2f015fe339a0e --- /dev/null +++ b/homeassistant/components/nanoleaf/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/neato/translations/sk.json b/homeassistant/components/neato/translations/sk.json new file mode 100644 index 00000000000000..520a3afd6d921d --- /dev/null +++ b/homeassistant/components/neato/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "create_entry": { + "default": "\u00daspe\u0161ne overen\u00e9" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nest/translations/cs.json b/homeassistant/components/nest/translations/cs.json index cbba19dac1d329..a0fe869cd36577 100644 --- a/homeassistant/components/nest/translations/cs.json +++ b/homeassistant/components/nest/translations/cs.json @@ -18,6 +18,11 @@ "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" }, "step": { + "auth": { + "data": { + "code": "P\u0159\u00edstupov\u00fd token" + } + }, "init": { "data": { "flow_impl": "Poskytovatel" diff --git a/homeassistant/components/nest/translations/sk.json b/homeassistant/components/nest/translations/sk.json new file mode 100644 index 00000000000000..029432864bf1bb --- /dev/null +++ b/homeassistant/components/nest/translations/sk.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "create_entry": { + "default": "\u00daspe\u0161ne overen\u00e9" + }, + "step": { + "auth": { + "data": { + "code": "Pr\u00edstupov\u00fd token" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/cs.json b/homeassistant/components/netatmo/translations/cs.json index 7857e345165b0a..5233fc3e2397be 100644 --- a/homeassistant/components/netatmo/translations/cs.json +++ b/homeassistant/components/netatmo/translations/cs.json @@ -4,6 +4,7 @@ "authorize_url_timeout": "\u010casov\u00fd limit autoriza\u010dn\u00edho URL vypr\u0161el", "missing_configuration": "Komponenta nen\u00ed nastavena. Postupujte podle dokumentace.", "no_url_available": "Nen\u00ed k dispozici \u017e\u00e1dn\u00e1 adresa URL. Informace o t\u00e9to chyb\u011b naleznete [v sekci n\u00e1pov\u011bdy]({docs_url})", + "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9", "single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace." }, "create_entry": { @@ -12,9 +13,23 @@ "step": { "pick_implementation": { "title": "Vyberte metodu ov\u011b\u0159en\u00ed" + }, + "reauth_confirm": { + "description": "Integrace Netatmo pot\u0159ebuje znovu ov\u011b\u0159it v\u00e1\u0161 \u00fa\u010det", + "title": "Znovu ov\u011b\u0159it integraci" } } }, + "device_automation": { + "trigger_subtype": { + "away": "pry\u010d", + "hg": "ochrana proti mrazu", + "schedule": "pl\u00e1n" + }, + "trigger_type": { + "set_point": "C\u00edlov\u00e1 teplota {entity_name} nastavena ru\u010dn\u011b" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/el.json b/homeassistant/components/netatmo/translations/el.json index 640cf82fba1239..fb93783d124961 100644 --- a/homeassistant/components/netatmo/translations/el.json +++ b/homeassistant/components/netatmo/translations/el.json @@ -48,6 +48,7 @@ "new_area": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae\u03c2", "weather_areas": "\u039a\u03b1\u03b9\u03c1\u03b9\u03ba\u03ad\u03c2 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ad\u03c2" }, + "description": "\u0394\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf\u03c5\u03c2 \u03b4\u03b7\u03bc\u03cc\u03c3\u03b9\u03bf\u03c5\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b5\u03c2 \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd.", "title": "\u0394\u03b7\u03bc\u03cc\u03c3\u03b9\u03bf\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd Netatmo" } } diff --git a/homeassistant/components/netatmo/translations/sk.json b/homeassistant/components/netatmo/translations/sk.json new file mode 100644 index 00000000000000..0f73e9340d9d4b --- /dev/null +++ b/homeassistant/components/netatmo/translations/sk.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "create_entry": { + "default": "\u00daspe\u0161ne overen\u00e9" + }, + "step": { + "pick_implementation": { + "title": "Vyberte met\u00f3du overenia" + } + } + }, + "options": { + "step": { + "public_weather": { + "data": { + "lat_ne": "Zemepisn\u00e1 \u0161\u00edrka: severov\u00fdchodn\u00fd roh", + "lat_sw": "Zemepisn\u00e1 \u0161\u00edrka: juhoz\u00e1padn\u00fd roh", + "lon_ne": "Zemepisn\u00e1 d\u013a\u017eka: severov\u00fdchodn\u00fd roh", + "lon_sw": "Zemepisn\u00e1 d\u013a\u017eka: juhoz\u00e1padn\u00fd roh" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netgear/translations/sk.json b/homeassistant/components/netgear/translations/sk.json new file mode 100644 index 00000000000000..ea85ad39b4ad76 --- /dev/null +++ b/homeassistant/components/netgear/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port (volite\u013en\u00fd)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nexia/translations/sk.json b/homeassistant/components/nexia/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/nexia/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nfandroidtv/translations/el.json b/homeassistant/components/nfandroidtv/translations/el.json index 26e4eed8900ee8..7effe92ee63c62 100644 --- a/homeassistant/components/nfandroidtv/translations/el.json +++ b/homeassistant/components/nfandroidtv/translations/el.json @@ -2,6 +2,9 @@ "config": { "step": { "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u0395\u03b9\u03b4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9\u03c2 \u03b3\u03b9\u03b1 Android TV. \n\n \u0393\u03b9\u03b1 Android TV: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\n \u0393\u03b9\u03b1 Fire TV: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK \n\n \u0398\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03b5\u03af\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03c1\u03ac\u03c4\u03b7\u03c3\u03b7 DHCP \u03c3\u03c4\u03bf \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2 (\u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b5\u03b3\u03c7\u03b5\u03b9\u03c1\u03af\u03b4\u03b9\u03bf \u03c7\u03c1\u03ae\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2) \u03b5\u03af\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c4\u03b1\u03c4\u03b9\u03ba\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae. \u0395\u03ac\u03bd \u03cc\u03c7\u03b9, \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b8\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03b5\u03af \u03c4\u03b5\u03bb\u03b9\u03ba\u03ac \u03bc\u03b7 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7.", "title": "\u0395\u03b9\u03b4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9\u03c2 \u03b3\u03b9\u03b1 Android TV / Fire TV" } diff --git a/homeassistant/components/nfandroidtv/translations/sk.json b/homeassistant/components/nfandroidtv/translations/sk.json new file mode 100644 index 00000000000000..af15f92c2f27a7 --- /dev/null +++ b/homeassistant/components/nfandroidtv/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nightscout/translations/sk.json b/homeassistant/components/nightscout/translations/sk.json new file mode 100644 index 00000000000000..ff85312780312a --- /dev/null +++ b/homeassistant/components/nightscout/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nmap_tracker/translations/el.json b/homeassistant/components/nmap_tracker/translations/el.json index 370bbd900c2f71..1ddcf47929517a 100644 --- a/homeassistant/components/nmap_tracker/translations/el.json +++ b/homeassistant/components/nmap_tracker/translations/el.json @@ -20,6 +20,7 @@ "init": { "data": { "consider_home": "\u0394\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1 \u03b1\u03bd\u03b1\u03bc\u03bf\u03bd\u03ae\u03c2 \u03bc\u03ad\u03c7\u03c1\u03b9 \u03bd\u03b1 \u03b5\u03c0\u03b9\u03c3\u03b7\u03bc\u03b1\u03bd\u03b8\u03b5\u03af \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03bc\u03bf\u03cd \u03c9\u03c2 \u03cc\u03c7\u03b9 \u03c3\u03c0\u03af\u03c4\u03b9, \u03b1\u03c6\u03bf\u03cd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b5\u03bc\u03c6\u03b1\u03bd\u03b9\u03c3\u03c4\u03b5\u03af.", + "interval_seconds": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7\u03c2", "track_new_devices": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03bd\u03ad\u03c9\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd" } } diff --git a/homeassistant/components/notion/translations/sk.json b/homeassistant/components/notion/translations/sk.json new file mode 100644 index 00000000000000..71a7aea5018f3f --- /dev/null +++ b/homeassistant/components/notion/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuheat/translations/sk.json b/homeassistant/components/nuheat/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/nuheat/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/sk.json b/homeassistant/components/nuki/translations/sk.json new file mode 100644 index 00000000000000..16e7623680598a --- /dev/null +++ b/homeassistant/components/nuki/translations/sk.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "reauth_confirm": { + "data": { + "token": "Pr\u00edstupov\u00fd token" + } + }, + "user": { + "data": { + "port": "Port", + "token": "Pr\u00edstupov\u00fd token" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nut/translations/sk.json b/homeassistant/components/nut/translations/sk.json new file mode 100644 index 00000000000000..434ad4a26b20d9 --- /dev/null +++ b/homeassistant/components/nut/translations/sk.json @@ -0,0 +1,49 @@ +{ + "config": { + "abort": { + "already_configured": "Zariadenie u\u017e je nakonfigurovan\u00e9" + }, + "error": { + "cannot_connect": "Nepodarilo sa pripoji\u0165", + "unknown": "Neo\u010dak\u00e1van\u00e1 chyba" + }, + "step": { + "resources": { + "data": { + "resources": "Prostriedky" + }, + "title": "Vyberte prostriedky, ktor\u00e9 chcete monitorova\u0165" + }, + "ups": { + "data": { + "alias": "Alias", + "resources": "Prostriedky" + }, + "title": "Vyberte UPS, ktor\u00fa chcete monitorova\u0165" + }, + "user": { + "data": { + "password": "Heslo", + "port": "Port", + "username": "Pou\u017e\u00edvate\u013esk\u00e9 meno" + }, + "title": "Pripoji\u0165 k serveru NUT" + } + } + }, + "options": { + "error": { + "cannot_connect": "Nepodarilo sa pripoji\u0165", + "unknown": "Neo\u010dak\u00e1van\u00e1 chyba" + }, + "step": { + "init": { + "data": { + "resources": "Prostriedky", + "scan_interval": "Skenovac\u00ed interval (v sekund\u00e1ch)" + }, + "description": "Vyberte senzory." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nws/translations/sk.json b/homeassistant/components/nws/translations/sk.json new file mode 100644 index 00000000000000..abb3969f6b4ac0 --- /dev/null +++ b/homeassistant/components/nws/translations/sk.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d", + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nzbget/translations/el.json b/homeassistant/components/nzbget/translations/el.json index f8d2b4b460ea54..2d28d795e04b99 100644 --- a/homeassistant/components/nzbget/translations/el.json +++ b/homeassistant/components/nzbget/translations/el.json @@ -10,6 +10,7 @@ "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, diff --git a/homeassistant/components/nzbget/translations/sk.json b/homeassistant/components/nzbget/translations/sk.json new file mode 100644 index 00000000000000..39d2e182c40bee --- /dev/null +++ b/homeassistant/components/nzbget/translations/sk.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "N\u00e1zov", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/octoprint/translations/cs.json b/homeassistant/components/octoprint/translations/cs.json index 4134a04508f529..fa519dfe6e1e6e 100644 --- a/homeassistant/components/octoprint/translations/cs.json +++ b/homeassistant/components/octoprint/translations/cs.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "error": { + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/omnilogic/translations/sk.json b/homeassistant/components/omnilogic/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/omnilogic/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/oncue/translations/sk.json b/homeassistant/components/oncue/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/oncue/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/sk.json b/homeassistant/components/ondilo_ico/translations/sk.json new file mode 100644 index 00000000000000..c19b1a0b70c707 --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "create_entry": { + "default": "\u00daspe\u0161ne overen\u00e9" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onewire/translations/sk.json b/homeassistant/components/onewire/translations/sk.json new file mode 100644 index 00000000000000..bd43098e5553cf --- /dev/null +++ b/homeassistant/components/onewire/translations/sk.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Zariadenie u\u017e je nakonfigurovan\u00e9" + }, + "error": { + "cannot_connect": "Nepodarilo sa pripoji\u0165", + "invalid_path": "Adres\u00e1r sa nena\u0161iel." + }, + "step": { + "owserver": { + "data": { + "port": "Port" + }, + "title": "Nastavenie owserver" + }, + "user": { + "data": { + "type": "Typ pripojenia" + }, + "title": "Nastavenie 1-Wire" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/el.json b/homeassistant/components/onvif/translations/el.json index 9933194812094f..8bc93c4a3d3a68 100644 --- a/homeassistant/components/onvif/translations/el.json +++ b/homeassistant/components/onvif/translations/el.json @@ -2,10 +2,14 @@ "config": { "abort": { "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03af\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03bc\u03ad\u03bd\u03b7", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", "no_h264": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ae\u03c1\u03c7\u03b1\u03bd \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b5\u03c2 \u03c1\u03bf\u03ad\u03c2 H264. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03bf\u03c6\u03af\u03bb \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c3\u03b1\u03c2.", "no_mac": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03bc\u03bf\u03bd\u03b1\u03b4\u03b9\u03ba\u03bf\u03cd \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03bf\u03cd \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae ONVIF.", "onvif_error": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 ONVIF. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b1 \u03b1\u03c1\u03c7\u03b5\u03af\u03b1 \u03ba\u03b1\u03c4\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2 \u03b3\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2." }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "auth": { "data": { @@ -16,8 +20,11 @@ }, "configure": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "port": "\u0398\u03cd\u03c1\u03b1" + "port": "\u0398\u03cd\u03c1\u03b1", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 ONVIF" }, diff --git a/homeassistant/components/onvif/translations/sk.json b/homeassistant/components/onvif/translations/sk.json new file mode 100644 index 00000000000000..f9a7d7d07bf97f --- /dev/null +++ b/homeassistant/components/onvif/translations/sk.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "Zariadenie u\u017e je nakonfigurovan\u00e9", + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + }, + "step": { + "auth": { + "data": { + "username": "Pou\u017e\u00edvate\u013esk\u00e9 meno" + } + }, + "configure": { + "data": { + "name": "N\u00e1zov", + "port": "Port", + "username": "Pou\u017e\u00edvate\u013esk\u00e9 meno" + } + }, + "manual_input": { + "data": { + "name": "N\u00e1zov", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/opengarage/translations/el.json b/homeassistant/components/opengarage/translations/el.json index ac63fdd0fbfa38..99defca3e95eb8 100644 --- a/homeassistant/components/opengarage/translations/el.json +++ b/homeassistant/components/opengarage/translations/el.json @@ -4,6 +4,7 @@ "user": { "data": { "device_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "port": "\u0398\u03cd\u03c1\u03b1" } } diff --git a/homeassistant/components/opengarage/translations/sk.json b/homeassistant/components/opengarage/translations/sk.json new file mode 100644 index 00000000000000..1145b3bb9f844c --- /dev/null +++ b/homeassistant/components/opengarage/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/opentherm_gw/translations/sk.json b/homeassistant/components/opentherm_gw/translations/sk.json new file mode 100644 index 00000000000000..e7a2eaabb7b13d --- /dev/null +++ b/homeassistant/components/opentherm_gw/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "init": { + "data": { + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openuv/translations/cs.json b/homeassistant/components/openuv/translations/cs.json index 0a674b6aff66b6..5e7d7b2d7cc047 100644 --- a/homeassistant/components/openuv/translations/cs.json +++ b/homeassistant/components/openuv/translations/cs.json @@ -17,5 +17,16 @@ "title": "Vypl\u0148te va\u0161e \u00fadaje" } } + }, + "options": { + "step": { + "init": { + "data": { + "from_window": "Po\u010d\u00e1te\u010dn\u00ed UV index pro ochrann\u00e9 okno", + "to_window": "Kone\u010dn\u00fd UV index pro ochrann\u00e9 okno" + }, + "title": "Nastaven\u00ed OpenUV" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/openuv/translations/sk.json b/homeassistant/components/openuv/translations/sk.json new file mode 100644 index 00000000000000..19eed2a3fe15fe --- /dev/null +++ b/homeassistant/components/openuv/translations/sk.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Umiestnenie u\u017e je nakonfigurovan\u00e9" + }, + "error": { + "invalid_api_key": "Neplatn\u00fd API k\u013e\u00fa\u010d" + }, + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d", + "elevation": "Nadmorsk\u00e1 v\u00fd\u0161ka", + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka" + }, + "title": "Vypl\u0148te svoje \u00fadaje" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "from_window": "Po\u010diato\u010dn\u00fd UV index pre ochrann\u00e9 okno", + "to_window": "Kone\u010dn\u00fd UV index pre ochrann\u00e9 okno" + }, + "title": "Konfigur\u00e1cia OpenUV" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openweathermap/translations/sk.json b/homeassistant/components/openweathermap/translations/sk.json new file mode 100644 index 00000000000000..f14039f810f8b2 --- /dev/null +++ b/homeassistant/components/openweathermap/translations/sk.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Umiestnenie u\u017e je nakonfigurovan\u00e9" + }, + "error": { + "invalid_api_key": "Neplatn\u00fd API k\u013e\u00fa\u010d" + }, + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d", + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/overkiz/translations/cs.json b/homeassistant/components/overkiz/translations/cs.json index 253dac9764dc14..95baa6367202f4 100644 --- a/homeassistant/components/overkiz/translations/cs.json +++ b/homeassistant/components/overkiz/translations/cs.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u00da\u010det je ji\u017e nastaven" + "already_configured": "\u00da\u010det je ji\u017e nastaven", + "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9" }, "error": { "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", diff --git a/homeassistant/components/overkiz/translations/sk.json b/homeassistant/components/overkiz/translations/sk.json new file mode 100644 index 00000000000000..71a7aea5018f3f --- /dev/null +++ b/homeassistant/components/overkiz/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ovo_energy/translations/sk.json b/homeassistant/components/ovo_energy/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/ovo_energy/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/owntracks/translations/el.json b/homeassistant/components/owntracks/translations/el.json index bf45bc1b864304..6db4a0fbcba07b 100644 --- a/homeassistant/components/owntracks/translations/el.json +++ b/homeassistant/components/owntracks/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf Home Assistant Cloud.", "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, "create_entry": { diff --git a/homeassistant/components/ozw/translations/sk.json b/homeassistant/components/ozw/translations/sk.json new file mode 100644 index 00000000000000..bee0999420fbf7 --- /dev/null +++ b/homeassistant/components/ozw/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/p1_monitor/translations/el.json b/homeassistant/components/p1_monitor/translations/el.json index b512655a7d7e00..82c79d53acc16b 100644 --- a/homeassistant/components/p1_monitor/translations/el.json +++ b/homeassistant/components/p1_monitor/translations/el.json @@ -7,6 +7,7 @@ "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1" }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf {intergration} \u03b3\u03b9\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf Home Assistant." diff --git a/homeassistant/components/p1_monitor/translations/sk.json b/homeassistant/components/p1_monitor/translations/sk.json new file mode 100644 index 00000000000000..af15f92c2f27a7 --- /dev/null +++ b/homeassistant/components/p1_monitor/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/panasonic_viera/translations/el.json b/homeassistant/components/panasonic_viera/translations/el.json index d3e7ea45706098..27de1a7fb0d664 100644 --- a/homeassistant/components/panasonic_viera/translations/el.json +++ b/homeassistant/components/panasonic_viera/translations/el.json @@ -13,6 +13,7 @@ }, "user": { "data": { + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", "name": "\u038c\u03bd\u03bf\u03bc\u03b1" }, "description": "\u03a0\u03bb\u03b7\u03ba\u03c4\u03c1\u03bf\u03bb\u03bf\u03b3\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c4\u03b7\u03c2 Panasonic Viera TV \u03c3\u03b1\u03c2 \u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", diff --git a/homeassistant/components/panasonic_viera/translations/sk.json b/homeassistant/components/panasonic_viera/translations/sk.json new file mode 100644 index 00000000000000..af15f92c2f27a7 --- /dev/null +++ b/homeassistant/components/panasonic_viera/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/sk.json b/homeassistant/components/philips_js/translations/sk.json index 8332248a6c6f45..623af989f30785 100644 --- a/homeassistant/components/philips_js/translations/sk.json +++ b/homeassistant/components/philips_js/translations/sk.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Zariadenie je u\u017e nastaven\u00e9" + "already_configured": "Zariadenie u\u017e je nakonfigurovan\u00e9" }, "error": { "cannot_connect": "Nepodarilo sa pripoji\u0165", diff --git a/homeassistant/components/pi_hole/translations/sk.json b/homeassistant/components/pi_hole/translations/sk.json new file mode 100644 index 00000000000000..4d37397c800387 --- /dev/null +++ b/homeassistant/components/pi_hole/translations/sk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Slu\u017eba u\u017e je nakonfigurovan\u00e1" + }, + "step": { + "api_key": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + }, + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d", + "location": "Umiestnenie", + "name": "N\u00e1zov", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/cs.json b/homeassistant/components/picnic/translations/cs.json index ce11c538997b76..988637d0977297 100644 --- a/homeassistant/components/picnic/translations/cs.json +++ b/homeassistant/components/picnic/translations/cs.json @@ -12,6 +12,7 @@ "step": { "user": { "data": { + "country_code": "K\u00f3d zem\u011b", "password": "Heslo", "username": "U\u017eivatelsk\u00e9 jm\u00e9no" } diff --git a/homeassistant/components/picnic/translations/sk.json b/homeassistant/components/picnic/translations/sk.json new file mode 100644 index 00000000000000..1c63a1923bdcdd --- /dev/null +++ b/homeassistant/components/picnic/translations/sk.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "country_code": "K\u00f3d krajiny" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plaato/translations/el.json b/homeassistant/components/plaato/translations/el.json index bd62f226b2e394..bd5e517481fd4a 100644 --- a/homeassistant/components/plaato/translations/el.json +++ b/homeassistant/components/plaato/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf Home Assistant Cloud.", "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, "create_entry": { diff --git a/homeassistant/components/plex/translations/cs.json b/homeassistant/components/plex/translations/cs.json index de85391a7d9892..f5e7e538f84976 100644 --- a/homeassistant/components/plex/translations/cs.json +++ b/homeassistant/components/plex/translations/cs.json @@ -5,6 +5,7 @@ "already_configured": "Tento server Plex je ji\u017e nastaven", "already_in_progress": "Konfigurace ji\u017e prob\u00edh\u00e1", "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9", + "token_request_timeout": "\u010casov\u00fd limit pro z\u00edsk\u00e1n\u00ed tokenu vypr\u0161el", "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" }, "error": { @@ -34,6 +35,7 @@ "title": "Vyberte server Plex" }, "user": { + "description": "Pro propojen\u00ed Plex serveru, pokra\u010dujte na [plex.tv](https://plex.tv).", "title": "Plex Media Server" }, "user_advanced": { @@ -48,8 +50,10 @@ "step": { "plex_mp_settings": { "data": { + "ignore_new_shared_users": "Ignorovat nov\u00e9 spravovan\u00e9/sd\u00edlen\u00e9 u\u017eivatele", "ignore_plex_web_clients": "Ignorujte webov\u00e9 klienty Plex", - "monitored_users": "Sledovan\u00ed u\u017eivatel\u00e9" + "monitored_users": "Sledovan\u00ed u\u017eivatel\u00e9", + "use_episode_art": "Pou\u017e\u00edvat plak\u00e1ty epizod" }, "description": "Mo\u017enosti pro p\u0159ehr\u00e1va\u010de m\u00e9di\u00ed Plex" } diff --git a/homeassistant/components/plex/translations/sk.json b/homeassistant/components/plex/translations/sk.json new file mode 100644 index 00000000000000..68438cbdfb0a2f --- /dev/null +++ b/homeassistant/components/plex/translations/sk.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Tento Plex server u\u017e je nakonfigurovan\u00fd", + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha", + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9", + "unknown": "Neo\u010dak\u00e1van\u00e1 chyba" + }, + "step": { + "manual_setup": { + "data": { + "port": "Port", + "ssl": "Pou\u017e\u00edva SSL certifik\u00e1t", + "verify_ssl": "Overi\u0165 SSL certifik\u00e1t" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plugwise/translations/el.json b/homeassistant/components/plugwise/translations/el.json index 82588fd8951c7c..a01a22cda23a32 100644 --- a/homeassistant/components/plugwise/translations/el.json +++ b/homeassistant/components/plugwise/translations/el.json @@ -11,9 +11,11 @@ }, "user_gateway": { "data": { + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", "port": "\u0398\u03cd\u03c1\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 Smile" - } + }, + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5" } } }, diff --git a/homeassistant/components/plugwise/translations/sk.json b/homeassistant/components/plugwise/translations/sk.json new file mode 100644 index 00000000000000..7124f1e5e28d93 --- /dev/null +++ b/homeassistant/components/plugwise/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user_gateway": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plum_lightpad/translations/sk.json b/homeassistant/components/plum_lightpad/translations/sk.json new file mode 100644 index 00000000000000..ee5407aae192a1 --- /dev/null +++ b/homeassistant/components/plum_lightpad/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/point/translations/sk.json b/homeassistant/components/point/translations/sk.json new file mode 100644 index 00000000000000..c19b1a0b70c707 --- /dev/null +++ b/homeassistant/components/point/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "create_entry": { + "default": "\u00daspe\u0161ne overen\u00e9" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/poolsense/translations/sk.json b/homeassistant/components/poolsense/translations/sk.json new file mode 100644 index 00000000000000..72b0304f1c3bd8 --- /dev/null +++ b/homeassistant/components/poolsense/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "email": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/sk.json b/homeassistant/components/powerwall/translations/sk.json new file mode 100644 index 00000000000000..71a7aea5018f3f --- /dev/null +++ b/homeassistant/components/powerwall/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/progettihwsw/translations/el.json b/homeassistant/components/progettihwsw/translations/el.json index e2d0eeae56103b..e2d1540a3c103e 100644 --- a/homeassistant/components/progettihwsw/translations/el.json +++ b/homeassistant/components/progettihwsw/translations/el.json @@ -24,6 +24,7 @@ }, "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "port": "\u0398\u03cd\u03c1\u03b1" }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03af\u03bd\u03b1\u03ba\u03b1" diff --git a/homeassistant/components/progettihwsw/translations/sk.json b/homeassistant/components/progettihwsw/translations/sk.json new file mode 100644 index 00000000000000..892b8b2cd91240 --- /dev/null +++ b/homeassistant/components/progettihwsw/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/prosegur/translations/sk.json b/homeassistant/components/prosegur/translations/sk.json new file mode 100644 index 00000000000000..71a7aea5018f3f --- /dev/null +++ b/homeassistant/components/prosegur/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ps4/translations/sk.json b/homeassistant/components/ps4/translations/sk.json new file mode 100644 index 00000000000000..fa12207330bca1 --- /dev/null +++ b/homeassistant/components/ps4/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "Zariadenie u\u017e je nakonfigurovan\u00e9" + }, + "step": { + "link": { + "data": { + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pure_energie/translations/el.json b/homeassistant/components/pure_energie/translations/el.json new file mode 100644 index 00000000000000..088aa6f754bdf5 --- /dev/null +++ b/homeassistant/components/pure_energie/translations/el.json @@ -0,0 +1,16 @@ +{ + "config": { + "flow_title": "{model} ({host})", + "step": { + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + } + }, + "zeroconf_confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Pure Energie Meter (`{model}`) \u03c3\u03c4\u03bf Home Assistant;", + "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Pure Energie Meter" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pure_energie/translations/en.json b/homeassistant/components/pure_energie/translations/en.json index 16986efc206c85..6773cf51478d0e 100644 --- a/homeassistant/components/pure_energie/translations/en.json +++ b/homeassistant/components/pure_energie/translations/en.json @@ -7,7 +7,7 @@ "error": { "cannot_connect": "Failed to connect" }, - "flow_title": "{name} ({host})", + "flow_title": "{model} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/pure_energie/translations/et.json b/homeassistant/components/pure_energie/translations/et.json new file mode 100644 index 00000000000000..4df06e2ca041d7 --- /dev/null +++ b/homeassistant/components/pure_energie/translations/et.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "cannot_connect": "\u00dchendamine nurjus" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus" + }, + "flow_title": "{model} ( {host} )", + "step": { + "user": { + "data": { + "host": "Host" + } + }, + "zeroconf_confirm": { + "description": "Kas lisada Home Assistantile Pure Energie Meteri(`{model}`)?", + "title": "Leiti Pure Energie Meter seade" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pure_energie/translations/pt-BR.json b/homeassistant/components/pure_energie/translations/pt-BR.json new file mode 100644 index 00000000000000..148cd129376f2d --- /dev/null +++ b/homeassistant/components/pure_energie/translations/pt-BR.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falhou em conectar" + }, + "error": { + "cannot_connect": "Falhou em conectar" + }, + "flow_title": "{model} ( {host} )", + "step": { + "user": { + "data": { + "host": "Host" + } + }, + "zeroconf_confirm": { + "description": "Deseja adicionar medidor Pure Energie (` {model} `) ao Home Assistant?", + "title": "Descoberto o dispositivo medidor Pure Energie" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pure_energie/translations/ru.json b/homeassistant/components/pure_energie/translations/ru.json new file mode 100644 index 00000000000000..7673757b245ca7 --- /dev/null +++ b/homeassistant/components/pure_energie/translations/ru.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." + }, + "flow_title": "{model} ({host})", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + } + }, + "zeroconf_confirm": { + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c Pure Energie Meter (`{model}`)?", + "title": "\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e Pure Energie Meter" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pvoutput/translations/sk.json b/homeassistant/components/pvoutput/translations/sk.json new file mode 100644 index 00000000000000..4eba3bdc8bb9d4 --- /dev/null +++ b/homeassistant/components/pvoutput/translations/sk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + }, + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rachio/translations/sk.json b/homeassistant/components/rachio/translations/sk.json new file mode 100644 index 00000000000000..ff85312780312a --- /dev/null +++ b/homeassistant/components/rachio/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rainforest_eagle/translations/el.json b/homeassistant/components/rainforest_eagle/translations/el.json index 686a0d72c440e7..eab8417f7fac9c 100644 --- a/homeassistant/components/rainforest_eagle/translations/el.json +++ b/homeassistant/components/rainforest_eagle/translations/el.json @@ -10,6 +10,7 @@ "user": { "data": { "cloud_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bd\u03ad\u03c6\u03bf\u03c5\u03c2", + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "install_code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2" } } diff --git a/homeassistant/components/rainforest_eagle/translations/sk.json b/homeassistant/components/rainforest_eagle/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/rainforest_eagle/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rainmachine/translations/sk.json b/homeassistant/components/rainmachine/translations/sk.json new file mode 100644 index 00000000000000..7fd0d4942e853c --- /dev/null +++ b/homeassistant/components/rainmachine/translations/sk.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "password": "Heslo", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/renault/translations/sk.json b/homeassistant/components/renault/translations/sk.json new file mode 100644 index 00000000000000..d1d6ad7289806f --- /dev/null +++ b/homeassistant/components/renault/translations/sk.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_credentials": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "username": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/sk.json b/homeassistant/components/rfxtrx/translations/sk.json new file mode 100644 index 00000000000000..e343d2e8b3188f --- /dev/null +++ b/homeassistant/components/rfxtrx/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "setup_network": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ridwell/translations/cs.json b/homeassistant/components/ridwell/translations/cs.json new file mode 100644 index 00000000000000..72df4a968182fc --- /dev/null +++ b/homeassistant/components/ridwell/translations/cs.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ridwell/translations/sk.json b/homeassistant/components/ridwell/translations/sk.json new file mode 100644 index 00000000000000..71a7aea5018f3f --- /dev/null +++ b/homeassistant/components/ridwell/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ring/translations/el.json b/homeassistant/components/ring/translations/el.json index 0c83d2de63754e..ed809f65e7c78b 100644 --- a/homeassistant/components/ring/translations/el.json +++ b/homeassistant/components/ring/translations/el.json @@ -1,6 +1,12 @@ { "config": { "step": { + "2fa": { + "data": { + "2fa": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b4\u03cd\u03bf \u03c0\u03b1\u03c1\u03b1\u03b3\u03cc\u03bd\u03c4\u03c9\u03bd" + }, + "title": "\u0388\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b4\u03cd\u03bf \u03c0\u03b1\u03c1\u03b1\u03b3\u03cc\u03bd\u03c4\u03c9\u03bd" + }, "user": { "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", diff --git a/homeassistant/components/ring/translations/sk.json b/homeassistant/components/ring/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/ring/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/risco/translations/sk.json b/homeassistant/components/risco/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/risco/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rituals_perfume_genie/translations/sk.json b/homeassistant/components/rituals_perfume_genie/translations/sk.json new file mode 100644 index 00000000000000..72b0304f1c3bd8 --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "email": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/el.json b/homeassistant/components/roku/translations/el.json index 204ada67d7c360..02e2d48ac9c1bb 100644 --- a/homeassistant/components/roku/translations/el.json +++ b/homeassistant/components/roku/translations/el.json @@ -1,5 +1,6 @@ { "config": { + "flow_title": "{name}", "step": { "discovery_confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name};", @@ -10,6 +11,9 @@ "title": "Roku" }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 Roku \u03c3\u03b1\u03c2." } } diff --git a/homeassistant/components/roku/translations/sk.json b/homeassistant/components/roku/translations/sk.json new file mode 100644 index 00000000000000..bee0999420fbf7 --- /dev/null +++ b/homeassistant/components/roku/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/el.json b/homeassistant/components/roomba/translations/el.json index fe1e0e9a73d40b..3192242fd21db7 100644 --- a/homeassistant/components/roomba/translations/el.json +++ b/homeassistant/components/roomba/translations/el.json @@ -7,6 +7,9 @@ "flow_title": "{name} ({host})", "step": { "init": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 Roomba \u03ae Braava.", "title": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" }, diff --git a/homeassistant/components/roon/translations/el.json b/homeassistant/components/roon/translations/el.json index 873f82d4f68b48..ab245da07eef7c 100644 --- a/homeassistant/components/roon/translations/el.json +++ b/homeassistant/components/roon/translations/el.json @@ -6,6 +6,9 @@ "title": "\u0395\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf HomeAssistant \u03c3\u03c4\u03bf Roon" }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03c4\u03b7\u03bd IP \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae Roon." } } diff --git a/homeassistant/components/roon/translations/sk.json b/homeassistant/components/roon/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/roon/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/cs.json b/homeassistant/components/rpi_power/translations/cs.json index 86d49fd1f60a62..cc203d3a8a5e7b 100644 --- a/homeassistant/components/rpi_power/translations/cs.json +++ b/homeassistant/components/rpi_power/translations/cs.json @@ -2,11 +2,11 @@ "config": { "abort": { "no_devices_found": "Nelze naj\u00edt t\u0159\u00eddu syst\u00e9mu pot\u0159ebnou pro tuto komponentu, ujist\u011bte se, \u017ee je va\u0161e j\u00e1dro aktu\u00e1ln\u00ed a hardware podporov\u00e1n", - "single_instance_allowed": "Ji\u017e je nastaveno. Je mo\u017en\u00e1 pouze jedna konfigurace." + "single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace." }, "step": { "confirm": { - "description": "Chcete zah\u00e1jit nastaven\u00ed?" + "description": "Chcete za\u010d\u00edt nastavovat?" } } }, diff --git a/homeassistant/components/rpi_power/translations/sk.json b/homeassistant/components/rpi_power/translations/sk.json new file mode 100644 index 00000000000000..d19ecb226982c6 --- /dev/null +++ b/homeassistant/components/rpi_power/translations/sk.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Nie je mo\u017en\u00e9 n\u00e1js\u0165 syst\u00e9mov\u00fa triedu pre t\u00fato komponentu, uistite sa \u017ee v\u00e1\u0161 kernel je aktu\u00e1lny a hardv\u00e9r je podporovan\u00fd" + }, + "step": { + "confirm": { + "description": "Chcete za\u010da\u0165 nastavova\u0165?" + } + } + }, + "title": "Kontrola nap\u00e1jacieho zdroja Raspberry Pi" +} \ No newline at end of file diff --git a/homeassistant/components/ruckus_unleashed/translations/sk.json b/homeassistant/components/ruckus_unleashed/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/ruckus_unleashed/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/samsungtv/translations/el.json b/homeassistant/components/samsungtv/translations/el.json index 74db188f67af6a..5ede2b35303e8c 100644 --- a/homeassistant/components/samsungtv/translations/el.json +++ b/homeassistant/components/samsungtv/translations/el.json @@ -16,6 +16,9 @@ "description": "\u039c\u03b5\u03c4\u03ac \u03c4\u03b7\u03bd \u03c5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae, \u03b1\u03c0\u03bf\u03b4\u03b5\u03c7\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b4\u03c5\u03cc\u03bc\u03b5\u03bd\u03bf \u03c0\u03b1\u03c1\u03ac\u03b8\u03c5\u03c1\u03bf \u03c3\u03c4\u03b7 {device} \u03c0\u03bf\u03c5 \u03b6\u03b7\u03c4\u03ac \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7 \u03b5\u03bd\u03c4\u03cc\u03c2 30 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03bf\u03bb\u03ad\u03c0\u03c4\u03c9\u03bd." }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c4\u03b7\u03c2 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 Samsung. \u0395\u03ac\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03b9 \u03c0\u03bf\u03c4\u03ad \u03c0\u03c1\u03b9\u03bd \u03c4\u03bf Home Assistant, \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b4\u03b5\u03af\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03b1\u03bd\u03b1\u03b4\u03c5\u03cc\u03bc\u03b5\u03bd\u03bf \u03c0\u03b1\u03c1\u03ac\u03b8\u03c5\u03c1\u03bf \u03c3\u03c4\u03b7\u03bd \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae \u03c3\u03b1\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03c3\u03b1\u03c2 \u03b6\u03b7\u03c4\u03ac \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7." } } diff --git a/homeassistant/components/samsungtv/translations/sk.json b/homeassistant/components/samsungtv/translations/sk.json new file mode 100644 index 00000000000000..d4a3e2e9fbb3c2 --- /dev/null +++ b/homeassistant/components/samsungtv/translations/sk.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha", + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "step": { + "user": { + "data": { + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/translations/sk.json b/homeassistant/components/screenlogic/translations/sk.json new file mode 100644 index 00000000000000..f547d5e3a90c02 --- /dev/null +++ b/homeassistant/components/screenlogic/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "gateway_entry": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sense/translations/sk.json b/homeassistant/components/sense/translations/sk.json new file mode 100644 index 00000000000000..72b0304f1c3bd8 --- /dev/null +++ b/homeassistant/components/sense/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "email": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sensibo/translations/sk.json b/homeassistant/components/sensibo/translations/sk.json new file mode 100644 index 00000000000000..694f006218bd96 --- /dev/null +++ b/homeassistant/components/sensibo/translations/sk.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d", + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sharkiq/translations/sk.json b/homeassistant/components/sharkiq/translations/sk.json new file mode 100644 index 00000000000000..71a7aea5018f3f --- /dev/null +++ b/homeassistant/components/sharkiq/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/cs.json b/homeassistant/components/shelly/translations/cs.json index e3f1215d6f2e2e..d7b817eb99425a 100644 --- a/homeassistant/components/shelly/translations/cs.json +++ b/homeassistant/components/shelly/translations/cs.json @@ -12,7 +12,7 @@ "flow_title": "{name}", "step": { "confirm_discovery": { - "description": "Chcete nastavit {model} na {host}?\n\nP\u0159ed nastaven\u00edm mus\u00ed b\u00fdt za\u0159\u00edzen\u00ed nap\u00e1jen\u00e9 z baterie probuzeno stisknut\u00edm tla\u010d\u00edtka na dan\u00e9m za\u0159\u00edzen\u00ed." + "description": "Chcete nastavit {model} na adrese {host}? \n\nBateriemi nap\u00e1jen\u00e1 za\u0159\u00edzen\u00ed, kter\u00e1 jsou chr\u00e1n\u011bna heslem, je nutn\u00e9 p\u0159ed pokra\u010dov\u00e1n\u00edm probudit.\nBateriemi nap\u00e1jen\u00e1 za\u0159\u00edzen\u00ed, kter\u00e1 nejsou chr\u00e1n\u011bna heslem, budou p\u0159id\u00e1na po probuzen\u00ed za\u0159\u00edzen\u00ed. Nyn\u00ed m\u016f\u017eete za\u0159\u00edzen\u00ed probudit ru\u010dn\u011b pomoc\u00ed tla\u010d\u00edtka na n\u011bm nebo po\u010dkat na dal\u0161\u00ed aktualizaci dat ze za\u0159\u00edzen\u00ed." }, "credentials": { "data": { @@ -37,11 +37,16 @@ "button4": "\u010ctvrt\u00e9 tla\u010d\u00edtko" }, "trigger_type": { + "btn_down": "\"{subtype}\" stisknuto dol\u016f", + "btn_up": "\"{subtype}\" stisknuto nahoru", "double": "\"{subtype}\" stisknuto dvakr\u00e1t", + "double_push": "\"{subtype}\" stisknuto dvakr\u00e1t", "long": "\"{subtype}\" stisknuto dlouze", + "long_push": "\"{subtype}\" stisknuto dlouze", "long_single": "\"{subtype}\" stisknuto dlouze a pak jednou", "single": "\"{subtype}\" stisknuto jednou", "single_long": "\"{subtype}\" stisknuto jednou a pak dlouze", + "single_push": "\"{subtype}\" stisknuto jednou", "triple": "\"{subtype}\" stisknuto t\u0159ikr\u00e1t" } } diff --git a/homeassistant/components/shelly/translations/el.json b/homeassistant/components/shelly/translations/el.json index e8f1b53c6ea60f..975011bf88e816 100644 --- a/homeassistant/components/shelly/translations/el.json +++ b/homeassistant/components/shelly/translations/el.json @@ -14,6 +14,9 @@ } }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u03a0\u03c1\u03b9\u03bd \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7, \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bc\u03b5 \u03bc\u03c0\u03b1\u03c4\u03b1\u03c1\u03af\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03be\u03c5\u03c0\u03bd\u03ae\u03c3\u03b5\u03b9 \u03c0\u03b1\u03c4\u03ce\u03bd\u03c4\u03b1\u03c2 \u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae." } } diff --git a/homeassistant/components/shelly/translations/sk.json b/homeassistant/components/shelly/translations/sk.json new file mode 100644 index 00000000000000..a019d22d2641e3 --- /dev/null +++ b/homeassistant/components/shelly/translations/sk.json @@ -0,0 +1,50 @@ +{ + "config": { + "abort": { + "already_configured": "Zariadenie u\u017e je nakonfigurovan\u00e9", + "unsupported_firmware": "Zariadenie pou\u017e\u00edva nepodporovan\u00fa verziu firmv\u00e9ru." + }, + "error": { + "cannot_connect": "Nepodarilo sa pripoji\u0165", + "invalid_auth": "Neplatn\u00e9 overenie", + "unknown": "Neo\u010dak\u00e1van\u00e1 chyba" + }, + "flow_title": "{name}", + "step": { + "confirm_discovery": { + "description": "Chcete nastavi\u0165 {model} na {host}? \n\nZariadenia nap\u00e1jan\u00e9 z bat\u00e9rie, ktor\u00e9 s\u00fa chr\u00e1nen\u00e9 heslom, sa musia prebudi\u0165 ne\u017e budete pokra\u010dova\u0165.\nZariadenia nap\u00e1jan\u00e9 z bat\u00e9rie, ktor\u00e9 nie s\u00fa chr\u00e1nen\u00e9 heslom, sa pridaj\u00fa po prebuden\u00ed zariadenia. Teraz m\u00f4\u017eete zariadenie zobudi\u0165 pomocou tla\u010didla na \u0148om alebo po\u010dka\u0165 na \u010fal\u0161iu aktualiz\u00e1ciu \u00fadajov zo zariadenia." + }, + "credentials": { + "data": { + "password": "Heslo", + "username": "Pou\u017e\u00edvate\u013esk\u00e9 meno" + } + }, + "user": { + "description": "Pred nastaven\u00edm musia by\u0165 zariadenia nap\u00e1jan\u00e9 z bat\u00e9rie zobuden\u00e9. Zobu\u010fte zariadenie pomocou tla\u010didla na \u0148om." + } + } + }, + "device_automation": { + "trigger_subtype": { + "button": "Tla\u010didlo", + "button1": "Prv\u00e9 tla\u010didlo", + "button2": "Druh\u00e9 tla\u010didlo", + "button3": "Tretie tla\u010didlo", + "button4": "\u0160tvrt\u00e9 tla\u010didlo" + }, + "trigger_type": { + "btn_down": "{subtype} stla\u010den\u00e9 dole", + "btn_up": "{subtype} stla\u010den\u00e9 hore", + "double": "{subtype} stla\u010den\u00e9 dvakr\u00e1t", + "double_push": "{subtype} stla\u010den\u00e9 dvakr\u00e1t", + "long": "{subtype} stla\u010den\u00e9 dlho", + "long_push": "{subtype} stla\u010den\u00e9 dlho", + "long_single": "{subtype} stla\u010den\u00e9 dlho a potom raz", + "single": "{subtype} stla\u010den\u00e9 raz", + "single_long": "{subtype} stla\u010den\u00e9 raz a potom dlho", + "single_push": "{subtype} stla\u010den\u00e9 raz", + "triple": "{subtype} stla\u010den\u00e9 trikr\u00e1t" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sia/translations/cs.json b/homeassistant/components/sia/translations/cs.json new file mode 100644 index 00000000000000..7940c6378fecab --- /dev/null +++ b/homeassistant/components/sia/translations/cs.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sia/translations/el.json b/homeassistant/components/sia/translations/el.json index 6a6f656db76e37..be8651c1043107 100644 --- a/homeassistant/components/sia/translations/el.json +++ b/homeassistant/components/sia/translations/el.json @@ -1,6 +1,8 @@ { "config": { "error": { + "invalid_account_format": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b5\u03ba\u03b1\u03b5\u03be\u03b1\u03b4\u03b9\u03ba\u03ae \u03c4\u03b9\u03bc\u03ae, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03bc\u03cc\u03bd\u03bf 0-9 \u03ba\u03b1\u03b9 A-F.", + "invalid_account_length": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c4\u03bf \u03c3\u03c9\u03c3\u03c4\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2, \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03bc\u03b5\u03c4\u03b1\u03be\u03cd 3 \u03ba\u03b1\u03b9 16 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03c9\u03bd.", "invalid_key_format": "\u03a4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b5\u03ba\u03b1\u03b5\u03be\u03b1\u03b4\u03b9\u03ba\u03ae \u03c4\u03b9\u03bc\u03ae, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03bc\u03cc\u03bd\u03bf 0-9 \u03ba\u03b1\u03b9 A-F.", "invalid_key_length": "\u03a4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c4\u03bf \u03c3\u03c9\u03c3\u03c4\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2, \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 16, 24 \u03ae 32 \u03b4\u03b5\u03ba\u03b1\u03b5\u03be\u03b1\u03b4\u03b9\u03ba\u03bf\u03af \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b5\u03c2.", "invalid_ping": "\u03a4\u03bf \u03b4\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 ping \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03bc\u03b5\u03c4\u03b1\u03be\u03cd 1 \u03ba\u03b1\u03b9 1440 \u03bb\u03b5\u03c0\u03c4\u03ce\u03bd.", diff --git a/homeassistant/components/sia/translations/sk.json b/homeassistant/components/sia/translations/sk.json new file mode 100644 index 00000000000000..892b8b2cd91240 --- /dev/null +++ b/homeassistant/components/sia/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/simplisafe/translations/sk.json b/homeassistant/components/simplisafe/translations/sk.json new file mode 100644 index 00000000000000..f59069125d0a15 --- /dev/null +++ b/homeassistant/components/simplisafe/translations/sk.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "password": "Heslo", + "username": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sleepiq/translations/pl.json b/homeassistant/components/sleepiq/translations/pl.json new file mode 100644 index 00000000000000..49be6d1efde280 --- /dev/null +++ b/homeassistant/components/sleepiq/translations/pl.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Konto jest ju\u017c skonfigurowane" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie" + }, + "step": { + "user": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sleepiq/translations/sk.json b/homeassistant/components/sleepiq/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/sleepiq/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sma/translations/sk.json b/homeassistant/components/sma/translations/sk.json new file mode 100644 index 00000000000000..0b7bf878ea9887 --- /dev/null +++ b/homeassistant/components/sma/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smart_meter_texas/translations/sk.json b/homeassistant/components/smart_meter_texas/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/smart_meter_texas/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarthab/translations/sk.json b/homeassistant/components/smarthab/translations/sk.json new file mode 100644 index 00000000000000..72b0304f1c3bd8 --- /dev/null +++ b/homeassistant/components/smarthab/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "email": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smartthings/translations/el.json b/homeassistant/components/smartthings/translations/el.json index f7da38143eafca..ff993f6176ebfe 100644 --- a/homeassistant/components/smartthings/translations/el.json +++ b/homeassistant/components/smartthings/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "invalid_webhook_url": "\u03a4\u03bf Home Assistant \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af \u03c3\u03c9\u03c3\u03c4\u03ac \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03b5\u03b9 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03b5\u03b9\u03c2 \u03b1\u03c0\u03cc \u03c4\u03bf SmartThings. \u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c4\u03bf\u03c5 webhook \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7:\n > {webhook_url} \n\n \u0395\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03ae \u03c3\u03b1\u03c2 \u03c3\u03cd\u03bc\u03c6\u03c9\u03bd\u03b1 \u03bc\u03b5 \u03c4\u03b9\u03c2 [\u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2]( {component_url} ), \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf Home Assistant \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac." + }, "error": { "app_setup_error": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 SmartApp. \u03a0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", "token_forbidden": "\u03a4\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c4\u03b1 \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b1 \u03c0\u03b5\u03b4\u03af\u03b1 OAuth.", diff --git a/homeassistant/components/smartthings/translations/sk.json b/homeassistant/components/smartthings/translations/sk.json new file mode 100644 index 00000000000000..534c1e859ee6e6 --- /dev/null +++ b/homeassistant/components/smartthings/translations/sk.json @@ -0,0 +1,16 @@ +{ + "config": { + "step": { + "pat": { + "data": { + "access_token": "Pr\u00edstupov\u00fd token" + } + }, + "select_location": { + "data": { + "location_id": "Umiestnenie" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarttub/translations/sk.json b/homeassistant/components/smarttub/translations/sk.json new file mode 100644 index 00000000000000..f8b6dfeea813ef --- /dev/null +++ b/homeassistant/components/smarttub/translations/sk.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "email": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smhi/translations/sk.json b/homeassistant/components/smhi/translations/sk.json new file mode 100644 index 00000000000000..81532ef4801935 --- /dev/null +++ b/homeassistant/components/smhi/translations/sk.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka", + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sms/translations/el.json b/homeassistant/components/sms/translations/el.json new file mode 100644 index 00000000000000..73452c7fea7230 --- /dev/null +++ b/homeassistant/components/sms/translations/el.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "device": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" + }, + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf \u03bc\u03cc\u03bd\u03c4\u03b5\u03bc" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solaredge/translations/sk.json b/homeassistant/components/solaredge/translations/sk.json new file mode 100644 index 00000000000000..7c6659c2e0b42b --- /dev/null +++ b/homeassistant/components/solaredge/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_api_key": "Neplatn\u00fd API k\u013e\u00fa\u010d" + }, + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solarlog/translations/el.json b/homeassistant/components/solarlog/translations/el.json index 9970910c04dd73..53300521d3faa0 100644 --- a/homeassistant/components/solarlog/translations/el.json +++ b/homeassistant/components/solarlog/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "name": "\u03a4\u03bf \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 \u03c0\u03bf\u03c5 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c4\u03bf\u03c5\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b5\u03c2 Solar-Log" }, "title": "\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 Solar-Log" diff --git a/homeassistant/components/solax/translations/cs.json b/homeassistant/components/solax/translations/cs.json new file mode 100644 index 00000000000000..7940c6378fecab --- /dev/null +++ b/homeassistant/components/solax/translations/cs.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solax/translations/el.json b/homeassistant/components/solax/translations/el.json index a3ea32fadc34c1..f4add9bb2400ec 100644 --- a/homeassistant/components/solax/translations/el.json +++ b/homeassistant/components/solax/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "ip_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "port": "\u0398\u03cd\u03c1\u03b1" } diff --git a/homeassistant/components/solax/translations/sk.json b/homeassistant/components/solax/translations/sk.json new file mode 100644 index 00000000000000..892b8b2cd91240 --- /dev/null +++ b/homeassistant/components/solax/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/soma/translations/el.json b/homeassistant/components/soma/translations/el.json index 61fd165644201e..9f2e620391a4e8 100644 --- a/homeassistant/components/soma/translations/el.json +++ b/homeassistant/components/soma/translations/el.json @@ -7,6 +7,7 @@ "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "port": "\u0398\u03cd\u03c1\u03b1" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 SOMA Connect.", diff --git a/homeassistant/components/soma/translations/sk.json b/homeassistant/components/soma/translations/sk.json new file mode 100644 index 00000000000000..91a46a2787f8c3 --- /dev/null +++ b/homeassistant/components/soma/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "create_entry": { + "default": "\u00daspe\u0161ne overen\u00e9" + }, + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy/translations/sk.json b/homeassistant/components/somfy/translations/sk.json new file mode 100644 index 00000000000000..c19b1a0b70c707 --- /dev/null +++ b/homeassistant/components/somfy/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "create_entry": { + "default": "\u00daspe\u0161ne overen\u00e9" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/el.json b/homeassistant/components/somfy_mylink/translations/el.json index e2fa45d91904e8..90dc6fee033fa0 100644 --- a/homeassistant/components/somfy_mylink/translations/el.json +++ b/homeassistant/components/somfy_mylink/translations/el.json @@ -4,6 +4,7 @@ "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "port": "\u0398\u03cd\u03c1\u03b1", "system_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c3\u03c5\u03c3\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2" }, diff --git a/homeassistant/components/somfy_mylink/translations/sk.json b/homeassistant/components/somfy_mylink/translations/sk.json new file mode 100644 index 00000000000000..1145b3bb9f844c --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/sk.json b/homeassistant/components/sonarr/translations/sk.json new file mode 100644 index 00000000000000..6e1cd075aa9323 --- /dev/null +++ b/homeassistant/components/sonarr/translations/sk.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/spider/translations/sk.json b/homeassistant/components/spider/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/spider/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/spotify/translations/cs.json b/homeassistant/components/spotify/translations/cs.json index 69cd1b1623ada0..3b1ea17e15a0a1 100644 --- a/homeassistant/components/spotify/translations/cs.json +++ b/homeassistant/components/spotify/translations/cs.json @@ -3,7 +3,8 @@ "abort": { "authorize_url_timeout": "\u010casov\u00fd limit autoriza\u010dn\u00edho URL vypr\u0161el.", "missing_configuration": "Integrace Spotify nen\u00ed nastavena. Postupujte podle dokumentace.", - "no_url_available": "Nen\u00ed k dispozici \u017e\u00e1dn\u00e1 adresa URL. Informace o t\u00e9to chyb\u011b naleznete [v sekci n\u00e1pov\u011bdy]({docs_url})" + "no_url_available": "Nen\u00ed k dispozici \u017e\u00e1dn\u00e1 adresa URL. Informace o t\u00e9to chyb\u011b naleznete [v sekci n\u00e1pov\u011bdy]({docs_url})", + "reauth_account_mismatch": "Ov\u011b\u0159en\u00fd \u00fa\u010det Spotify neodpov\u00edd\u00e1 \u00fa\u010dtu, kter\u00fd vy\u017eaduje op\u011btovn\u00e9 ov\u011b\u0159en\u00ed." }, "create_entry": { "default": "\u00dasp\u011b\u0161n\u011b ov\u011b\u0159eno pomoc\u00ed Spotify." @@ -17,5 +18,10 @@ "title": "Znovu ov\u011b\u0159it integraci" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "Spotify API je dostupn\u00e9" + } } } \ No newline at end of file diff --git a/homeassistant/components/spotify/translations/sk.json b/homeassistant/components/spotify/translations/sk.json new file mode 100644 index 00000000000000..63ae49fe7273fd --- /dev/null +++ b/homeassistant/components/spotify/translations/sk.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "pick_implementation": { + "title": "Vyberte met\u00f3du overenia" + }, + "reauth_confirm": { + "title": "Znova overi\u0165 integr\u00e1ciu" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/squeezebox/translations/el.json b/homeassistant/components/squeezebox/translations/el.json index 9608dfe0cc1cc7..806a63c257a324 100644 --- a/homeassistant/components/squeezebox/translations/el.json +++ b/homeassistant/components/squeezebox/translations/el.json @@ -12,7 +12,8 @@ "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" - } + }, + "title": "\u0395\u03c0\u03b5\u03be\u03b5\u03c1\u03b3\u03b1\u03c3\u03af\u03b1 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03b9\u03ce\u03bd \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "user": { "data": { diff --git a/homeassistant/components/squeezebox/translations/sk.json b/homeassistant/components/squeezebox/translations/sk.json new file mode 100644 index 00000000000000..85b770fe2ed89e --- /dev/null +++ b/homeassistant/components/squeezebox/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "edit": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/sk.json b/homeassistant/components/srp_energy/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/srp_energy/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steamist/translations/sk.json b/homeassistant/components/steamist/translations/sk.json new file mode 100644 index 00000000000000..bee0999420fbf7 --- /dev/null +++ b/homeassistant/components/steamist/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/sk.json b/homeassistant/components/subaru/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/subaru/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/surepetcare/translations/sk.json b/homeassistant/components/surepetcare/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/surepetcare/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/switchbot/translations/sk.json b/homeassistant/components/switchbot/translations/sk.json new file mode 100644 index 00000000000000..af15f92c2f27a7 --- /dev/null +++ b/homeassistant/components/switchbot/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/cs.json b/homeassistant/components/syncthing/translations/cs.json new file mode 100644 index 00000000000000..a679dc35fe3ae2 --- /dev/null +++ b/homeassistant/components/syncthing/translations/cs.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "already_configured": "Slu\u017eba je ji\u017e nastavena" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/sk.json b/homeassistant/components/syncthing/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/syncthing/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/syncthru/translations/sk.json b/homeassistant/components/syncthru/translations/sk.json new file mode 100644 index 00000000000000..3d28cc36f74b59 --- /dev/null +++ b/homeassistant/components/syncthru/translations/sk.json @@ -0,0 +1,16 @@ +{ + "config": { + "step": { + "confirm": { + "data": { + "name": "N\u00e1zov" + } + }, + "user": { + "data": { + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/sk.json b/homeassistant/components/synology_dsm/translations/sk.json new file mode 100644 index 00000000000000..e9c37059842de1 --- /dev/null +++ b/homeassistant/components/synology_dsm/translations/sk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "link": { + "data": { + "port": "Port" + } + }, + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/system_bridge/translations/cs.json b/homeassistant/components/system_bridge/translations/cs.json new file mode 100644 index 00000000000000..372a54786bd061 --- /dev/null +++ b/homeassistant/components/system_bridge/translations/cs.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/system_bridge/translations/sk.json b/homeassistant/components/system_bridge/translations/sk.json new file mode 100644 index 00000000000000..276eac51dd4605 --- /dev/null +++ b/homeassistant/components/system_bridge/translations/sk.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "authenticate": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + }, + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tado/translations/sk.json b/homeassistant/components/tado/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/tado/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tailscale/translations/cs.json b/homeassistant/components/tailscale/translations/cs.json new file mode 100644 index 00000000000000..3bfe94e68dddae --- /dev/null +++ b/homeassistant/components/tailscale/translations/cs.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tailscale/translations/sk.json b/homeassistant/components/tailscale/translations/sk.json new file mode 100644 index 00000000000000..4eba3bdc8bb9d4 --- /dev/null +++ b/homeassistant/components/tailscale/translations/sk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + }, + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tellduslive/translations/sk.json b/homeassistant/components/tellduslive/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/tellduslive/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla_wall_connector/translations/cs.json b/homeassistant/components/tesla_wall_connector/translations/cs.json new file mode 100644 index 00000000000000..e1bf8e7f45f3c1 --- /dev/null +++ b/homeassistant/components/tesla_wall_connector/translations/cs.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tibber/translations/sk.json b/homeassistant/components/tibber/translations/sk.json new file mode 100644 index 00000000000000..13ca7333f5c2ca --- /dev/null +++ b/homeassistant/components/tibber/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "access_token": "Pr\u00edstupov\u00fd token" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tile/translations/sk.json b/homeassistant/components/tile/translations/sk.json new file mode 100644 index 00000000000000..d30ed436a4fd27 --- /dev/null +++ b/homeassistant/components/tile/translations/sk.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "username": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/sk.json b/homeassistant/components/totalconnect/translations/sk.json new file mode 100644 index 00000000000000..59d045e76037b4 --- /dev/null +++ b/homeassistant/components/totalconnect/translations/sk.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "locations": { + "data": { + "location": "Umiestnenie" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tplink/translations/el.json b/homeassistant/components/tplink/translations/el.json index ccbc6049794f6f..3e05e3e9e7a521 100644 --- a/homeassistant/components/tplink/translations/el.json +++ b/homeassistant/components/tplink/translations/el.json @@ -14,6 +14,9 @@ } }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u0395\u03ac\u03bd \u03b1\u03c6\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ba\u03b5\u03bd\u03cc, \u03bf \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03bc\u03cc\u03c2 \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd." } } diff --git a/homeassistant/components/traccar/translations/el.json b/homeassistant/components/traccar/translations/el.json index d82d7a5500cfa3..4373918fcad45c 100644 --- a/homeassistant/components/traccar/translations/el.json +++ b/homeassistant/components/traccar/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf Home Assistant Cloud.", "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, "create_entry": { diff --git a/homeassistant/components/tractive/translations/sk.json b/homeassistant/components/tractive/translations/sk.json new file mode 100644 index 00000000000000..f8b6dfeea813ef --- /dev/null +++ b/homeassistant/components/tractive/translations/sk.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "email": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tradfri/translations/sk.json b/homeassistant/components/tradfri/translations/sk.json new file mode 100644 index 00000000000000..299acb612fbab2 --- /dev/null +++ b/homeassistant/components/tradfri/translations/sk.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "already_configured": "Zariadenie u\u017e je nakonfigurovan\u00e9", + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_weatherstation/translations/sk.json b/homeassistant/components/trafikverket_weatherstation/translations/sk.json new file mode 100644 index 00000000000000..ff85312780312a --- /dev/null +++ b/homeassistant/components/trafikverket_weatherstation/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/transmission/translations/cs.json b/homeassistant/components/transmission/translations/cs.json index 8ad9e051064188..c2c0bf5ead01a2 100644 --- a/homeassistant/components/transmission/translations/cs.json +++ b/homeassistant/components/transmission/translations/cs.json @@ -6,7 +6,7 @@ "error": { "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", - "name_exists": "Jm\u00e9no already exists" + "name_exists": "Jm\u00e9no ji\u017e existuje" }, "step": { "user": { diff --git a/homeassistant/components/transmission/translations/el.json b/homeassistant/components/transmission/translations/el.json index 498610b5896b15..134104c26d61e3 100644 --- a/homeassistant/components/transmission/translations/el.json +++ b/homeassistant/components/transmission/translations/el.json @@ -6,6 +6,7 @@ "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "port": "\u0398\u03cd\u03c1\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" diff --git a/homeassistant/components/transmission/translations/sk.json b/homeassistant/components/transmission/translations/sk.json new file mode 100644 index 00000000000000..731004b0ebc323 --- /dev/null +++ b/homeassistant/components/transmission/translations/sk.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie", + "name_exists": "N\u00e1zov u\u017e existuje" + }, + "step": { + "user": { + "data": { + "name": "N\u00e1zov", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/cs.json b/homeassistant/components/tuya/translations/cs.json index 7f23e4091658cd..9a406ffcb4be73 100644 --- a/homeassistant/components/tuya/translations/cs.json +++ b/homeassistant/components/tuya/translations/cs.json @@ -10,6 +10,11 @@ }, "flow_title": "Konfigurace Tuya", "step": { + "login": { + "data": { + "country_code": "K\u00f3d zem\u011b" + } + }, "user": { "data": { "country_code": "Zem\u011b", diff --git a/homeassistant/components/tuya/translations/el.json b/homeassistant/components/tuya/translations/el.json index a77dd8851fe2b1..a7d7fec3633e8f 100644 --- a/homeassistant/components/tuya/translations/el.json +++ b/homeassistant/components/tuya/translations/el.json @@ -14,7 +14,8 @@ "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "tuya_app_type": "Mobile App", "username": "\u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2" - } + }, + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2 Tuya" }, "user": { "data": { @@ -50,13 +51,17 @@ "support_color": "\u0391\u03bd\u03b1\u03b3\u03ba\u03b1\u03c3\u03c4\u03b9\u03ba\u03ae \u03c5\u03c0\u03bf\u03c3\u03c4\u03ae\u03c1\u03b9\u03be\u03b7 \u03c7\u03c1\u03ce\u03bc\u03b1\u03c4\u03bf\u03c2", "temp_divider": "\u0394\u03b9\u03b1\u03b9\u03c1\u03ad\u03c4\u03b7\u03c2 \u03c4\u03b9\u03bc\u03ce\u03bd \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 (0 = \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae\u03c2)", "temp_step_override": "\u0392\u03ae\u03bc\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03c3\u03c4\u03cc\u03c7\u03bf\u03c5", - "tuya_max_coltemp": "\u039c\u03ad\u03b3\u03b9\u03c3\u03c4\u03b7 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1 \u03c7\u03c1\u03ce\u03bc\u03b1\u03c4\u03bf\u03c2 \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03c6\u03ad\u03c1\u03b5\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" + "tuya_max_coltemp": "\u039c\u03ad\u03b3\u03b9\u03c3\u03c4\u03b7 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1 \u03c7\u03c1\u03ce\u03bc\u03b1\u03c4\u03bf\u03c2 \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03c6\u03ad\u03c1\u03b5\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae", + "unit_of_measurement": "\u039c\u03bf\u03bd\u03ac\u03b4\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" }, "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u03c4\u03c9\u03bd \u03b5\u03bc\u03c6\u03b1\u03bd\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03c9\u03bd \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03b9\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae {device_type} `{device_name}`", "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 Tuya" }, "init": { "data": { + "discovery_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03b4\u03b7\u03bc\u03bf\u03c3\u03ba\u03cc\u03c0\u03b7\u03c3\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03bc\u03bf\u03cd \u03c3\u03b5 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1", + "list_devices": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03b5\u03c4\u03b5 \u03ae \u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03ba\u03b5\u03bd\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03cd\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7", + "query_device": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03c4\u03b7 \u03bc\u03ad\u03b8\u03bf\u03b4\u03bf \u03b5\u03c1\u03c9\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b1\u03c7\u03cd\u03c4\u03b5\u03c1\u03b7 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2", "query_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03b4\u03b7\u03bc\u03bf\u03c3\u03ba\u03cc\u03c0\u03b7\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03c3\u03b5 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1" }, "description": "\u039c\u03b7\u03bd \u03bf\u03c1\u03af\u03b6\u03b5\u03c4\u03b5 \u03c0\u03bf\u03bb\u03cd \u03c7\u03b1\u03bc\u03b7\u03bb\u03ad\u03c2 \u03c4\u03b9\u03bc\u03ad\u03c2 \u03b4\u03b9\u03b1\u03c3\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2 \u03b4\u03b7\u03bc\u03bf\u03c3\u03ba\u03bf\u03c0\u03ae\u03c3\u03b5\u03c9\u03bd, \u03b1\u03bb\u03bb\u03b9\u03ce\u03c2 \u03bf\u03b9 \u03ba\u03bb\u03ae\u03c3\u03b5\u03b9\u03c2 \u03b8\u03b1 \u03b1\u03c0\u03bf\u03c4\u03cd\u03c7\u03bf\u03c5\u03bd \u03ba\u03b1\u03b9 \u03b8\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03b7\u03b8\u03b5\u03af \u03bc\u03ae\u03bd\u03c5\u03bc\u03b1 \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1\u03c4\u03bf\u03c2 \u03c3\u03c4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03ba\u03b1\u03c4\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2.", diff --git a/homeassistant/components/tuya/translations/sk.json b/homeassistant/components/tuya/translations/sk.json index 2724fad6898732..23d8822116cc03 100644 --- a/homeassistant/components/tuya/translations/sk.json +++ b/homeassistant/components/tuya/translations/sk.json @@ -1,8 +1,20 @@ { "config": { + "abort": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, "step": { + "login": { + "data": { + "country_code": "K\u00f3d krajiny" + } + }, "user": { "data": { + "country_code": "Krajina", "region": "Oblas\u0165" } } diff --git a/homeassistant/components/twilio/translations/el.json b/homeassistant/components/twilio/translations/el.json index 9f791196b75964..df951b1e617c90 100644 --- a/homeassistant/components/twilio/translations/el.json +++ b/homeassistant/components/twilio/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf Home Assistant Cloud.", "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, "create_entry": { diff --git a/homeassistant/components/unifi/translations/sk.json b/homeassistant/components/unifi/translations/sk.json new file mode 100644 index 00000000000000..da71ce60d66d61 --- /dev/null +++ b/homeassistant/components/unifi/translations/sk.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "faulty_credentials": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "password": "Heslo", + "port": "Port", + "username": "Pou\u017e\u00edvate\u013esk\u00e9 meno" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifiprotect/translations/ru.json b/homeassistant/components/unifiprotect/translations/ru.json index b4ef19257a2990..ea9810962f8f04 100644 --- a/homeassistant/components/unifiprotect/translations/ru.json +++ b/homeassistant/components/unifiprotect/translations/ru.json @@ -19,7 +19,7 @@ "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" }, "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name} ({ip_address})? \u0414\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c, \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 UniFi OS \u0434\u043b\u044f \u0432\u0445\u043e\u0434\u0430 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443. \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 Ubiquiti Cloud \u043d\u0435 \u0431\u0443\u0434\u0443\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c. \u0414\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438: {local_user_documentation_url}", - "title": "\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0439 UniFi Protect" + "title": "\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e UniFi Protect" }, "reauth_confirm": { "data": { diff --git a/homeassistant/components/unifiprotect/translations/sk.json b/homeassistant/components/unifiprotect/translations/sk.json new file mode 100644 index 00000000000000..3b59b1ff2134a8 --- /dev/null +++ b/homeassistant/components/unifiprotect/translations/sk.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "reauth_confirm": { + "data": { + "port": "Port" + } + }, + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upcloud/translations/sk.json b/homeassistant/components/upcloud/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/upcloud/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/sk.json b/homeassistant/components/upnp/translations/sk.json new file mode 100644 index 00000000000000..793f8eff278722 --- /dev/null +++ b/homeassistant/components/upnp/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Zariadenie u\u017e je nakonfigurovan\u00e9" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/uptimerobot/translations/sk.json b/homeassistant/components/uptimerobot/translations/sk.json new file mode 100644 index 00000000000000..a41b646034bef1 --- /dev/null +++ b/homeassistant/components/uptimerobot/translations/sk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_api_key": "Neplatn\u00fd API k\u013e\u00fa\u010d" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + }, + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vallox/translations/cs.json b/homeassistant/components/vallox/translations/cs.json index ccfe59404c386a..2a364830dbf9b0 100644 --- a/homeassistant/components/vallox/translations/cs.json +++ b/homeassistant/components/vallox/translations/cs.json @@ -1,12 +1,20 @@ { "config": { "abort": { + "already_configured": "Slu\u017eba je ji\u017e nastavena", "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", "invalid_host": "Neplatn\u00fd hostitel nebo IP adresa", "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" }, "error": { "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "name": "Jm\u00e9no" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/vallox/translations/sk.json b/homeassistant/components/vallox/translations/sk.json new file mode 100644 index 00000000000000..af15f92c2f27a7 --- /dev/null +++ b/homeassistant/components/vallox/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/venstar/translations/cs.json b/homeassistant/components/venstar/translations/cs.json new file mode 100644 index 00000000000000..e1bf8e7f45f3c1 --- /dev/null +++ b/homeassistant/components/venstar/translations/cs.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vera/translations/el.json b/homeassistant/components/vera/translations/el.json index fcd56ed621e27c..1039fb01ccd535 100644 --- a/homeassistant/components/vera/translations/el.json +++ b/homeassistant/components/vera/translations/el.json @@ -5,6 +5,11 @@ }, "step": { "user": { + "data": { + "exclude": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03ac \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 Vera \u03c0\u03c1\u03bf\u03c2 \u03b5\u03be\u03b1\u03af\u03c1\u03b5\u03c3\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf Home Assistant.", + "lights": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03ac \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03bc\u03b5\u03c4\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2 Vera \u03b3\u03b9\u03b1 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c9\u03c2 \u03c6\u03ce\u03c4\u03b1 \u03c3\u03c4\u03bf Home Assistant.", + "vera_controller_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ae" + }, "description": "\u0394\u03ce\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c4\u03bf\u03c5 \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ae Vera \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9. \u0398\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03bc\u03bf\u03b9\u03ac\u03b6\u03b5\u03b9 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc: http://192.168.1.161:3480.", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ae Vera" } diff --git a/homeassistant/components/verisure/translations/sk.json b/homeassistant/components/verisure/translations/sk.json new file mode 100644 index 00000000000000..0f898b977eea9e --- /dev/null +++ b/homeassistant/components/verisure/translations/sk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "reauth_confirm": { + "data": { + "email": "Email" + } + }, + "user": { + "data": { + "email": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vesync/translations/sk.json b/homeassistant/components/vesync/translations/sk.json new file mode 100644 index 00000000000000..c043ef9ff19d26 --- /dev/null +++ b/homeassistant/components/vesync/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "username": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vicare/translations/sk.json b/homeassistant/components/vicare/translations/sk.json new file mode 100644 index 00000000000000..d68d88fd2cdae2 --- /dev/null +++ b/homeassistant/components/vicare/translations/sk.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "client_id": "API k\u013e\u00fa\u010d", + "name": "N\u00e1zov", + "username": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vilfo/translations/el.json b/homeassistant/components/vilfo/translations/el.json index d92df8c0341ec1..b7bc1745d7dc49 100644 --- a/homeassistant/components/vilfo/translations/el.json +++ b/homeassistant/components/vilfo/translations/el.json @@ -2,6 +2,9 @@ "config": { "step": { "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae Vilfo. \u03a7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c3\u03c4\u03b5 \u03c4\u03bf hostname/IP \u03c4\u03bf\u03c5 Vilfo Router \u03ba\u03b1\u03b9 \u03ad\u03bd\u03b1 \u03ba\u03bf\u03c5\u03c0\u03cc\u03bd\u03b9 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 API. \u0393\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03c0\u03ce\u03c2 \u03bd\u03b1 \u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ad\u03c2 \u03c4\u03b9\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2, \u03b5\u03c0\u03b9\u03c3\u03ba\u03b5\u03c6\u03b8\u03b5\u03af\u03c4\u03b5: https://www.home-assistant.io/integrations/vilfo", "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae Vilfo" } diff --git a/homeassistant/components/vilfo/translations/sk.json b/homeassistant/components/vilfo/translations/sk.json new file mode 100644 index 00000000000000..7afa1eaea6e5d7 --- /dev/null +++ b/homeassistant/components/vilfo/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "access_token": "Pr\u00edstupov\u00fd token" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vizio/translations/cs.json b/homeassistant/components/vizio/translations/cs.json index cf0c66749ddb39..f2b8c74c25ab2d 100644 --- a/homeassistant/components/vizio/translations/cs.json +++ b/homeassistant/components/vizio/translations/cs.json @@ -32,7 +32,7 @@ "host": "Hostitel", "name": "Jm\u00e9no" }, - "description": "P\u0159\u00edstupov\u00fd token je pot\u0159eba pouze pro televizory. Pokud konfigurujete televizor a nem\u00e1te [%key:common:common::config_flow::data::access_token%], ponechte jej pr\u00e1zdn\u00e9, abyste mohli proj\u00edt procesem p\u00e1rov\u00e1n\u00ed.", + "description": "P\u0159\u00edstupov\u00fd token je pot\u0159eba pouze pro televizory. Pokud konfigurujete televizor a nem\u00e1te P\u0159\u00edstupov\u00fd token, ponechte jej pr\u00e1zdn\u00e9, abyste mohli proj\u00edt procesem p\u00e1rov\u00e1n\u00ed.", "title": "Za\u0159\u00edzen\u00ed VIZIO SmartCast" } } diff --git a/homeassistant/components/vizio/translations/el.json b/homeassistant/components/vizio/translations/el.json index f4d134979cee46..a67e2504cf692e 100644 --- a/homeassistant/components/vizio/translations/el.json +++ b/homeassistant/components/vizio/translations/el.json @@ -22,7 +22,8 @@ }, "user": { "data": { - "device_class": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" + "device_class": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" }, "description": "\u0388\u03bd\u03b1 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03bc\u03cc\u03bd\u03bf \u03b3\u03b9\u03b1 \u03c4\u03b9\u03c2 \u03c4\u03b7\u03bb\u03b5\u03bf\u03c1\u03ac\u03c3\u03b5\u03b9\u03c2. \u0395\u03ac\u03bd \u03c1\u03c5\u03b8\u03bc\u03af\u03b6\u03b5\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03b1\u03ba\u03cc\u03bc\u03b7 \u03ad\u03bd\u03b1 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2, \u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03b5\u03c1\u03ac\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c0\u03cc \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b1\u03b4\u03b9\u03ba\u03b1\u03c3\u03af\u03b1 \u03b1\u03bd\u03c4\u03b9\u03c3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7\u03c2.", "title": "VIZIO SmartCast \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" @@ -34,9 +35,11 @@ "init": { "data": { "apps_to_include_or_exclude": "\u0395\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03af\u03bb\u03b7\u03c8\u03b7 \u03ae \u03b5\u03be\u03b1\u03af\u03c1\u03b5\u03c3\u03b7", - "include_or_exclude": "\u03a3\u03c5\u03bc\u03c0\u03b5\u03c1\u03af\u03bb\u03b7\u03c8\u03b7 \u03ae \u03b5\u03be\u03b1\u03af\u03c1\u03b5\u03c3\u03b7 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ce\u03bd;" + "include_or_exclude": "\u03a3\u03c5\u03bc\u03c0\u03b5\u03c1\u03af\u03bb\u03b7\u03c8\u03b7 \u03ae \u03b5\u03be\u03b1\u03af\u03c1\u03b5\u03c3\u03b7 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ce\u03bd;", + "volume_step": "\u039c\u03ad\u03b3\u03b5\u03b8\u03bf\u03c2 \u03b2\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2 \u03ad\u03bd\u03c4\u03b1\u03c3\u03b7\u03c2" }, - "description": "\u0395\u03ac\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 Smart TV, \u03bc\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ac \u03bd\u03b1 \u03c6\u03b9\u03bb\u03c4\u03c1\u03ac\u03c1\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03bb\u03af\u03c3\u03c4\u03b1 \u03c0\u03b7\u03b3\u03ce\u03bd \u03c3\u03b1\u03c2 \u03b5\u03c0\u03b9\u03bb\u03ad\u03b3\u03bf\u03bd\u03c4\u03b1\u03c2 \u03c0\u03bf\u03b9\u03b5\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ad\u03c2 \u03b8\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03ae \u03b8\u03b1 \u03b5\u03be\u03b1\u03b9\u03c1\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03bb\u03af\u03c3\u03c4\u03b1 \u03c0\u03b7\u03b3\u03ce\u03bd \u03c3\u03b1\u03c2." + "description": "\u0395\u03ac\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 Smart TV, \u03bc\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ac \u03bd\u03b1 \u03c6\u03b9\u03bb\u03c4\u03c1\u03ac\u03c1\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03bb\u03af\u03c3\u03c4\u03b1 \u03c0\u03b7\u03b3\u03ce\u03bd \u03c3\u03b1\u03c2 \u03b5\u03c0\u03b9\u03bb\u03ad\u03b3\u03bf\u03bd\u03c4\u03b1\u03c2 \u03c0\u03bf\u03b9\u03b5\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ad\u03c2 \u03b8\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03ae \u03b8\u03b1 \u03b5\u03be\u03b1\u03b9\u03c1\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03bb\u03af\u03c3\u03c4\u03b1 \u03c0\u03b7\u03b3\u03ce\u03bd \u03c3\u03b1\u03c2.", + "title": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 VIZIO SmartCast \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd" } } } diff --git a/homeassistant/components/vizio/translations/sk.json b/homeassistant/components/vizio/translations/sk.json new file mode 100644 index 00000000000000..171a5a2c70896a --- /dev/null +++ b/homeassistant/components/vizio/translations/sk.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "access_token": "Pr\u00edstupov\u00fd token", + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vlc_telnet/translations/sk.json b/homeassistant/components/vlc_telnet/translations/sk.json new file mode 100644 index 00000000000000..d3bc93c4168abd --- /dev/null +++ b/homeassistant/components/vlc_telnet/translations/sk.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "invalid_auth": "Neplatn\u00e9 overenie", + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "name": "N\u00e1zov", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/volumio/translations/el.json b/homeassistant/components/volumio/translations/el.json index c0cd55577314ba..8a6bf7c8ddd99c 100644 --- a/homeassistant/components/volumio/translations/el.json +++ b/homeassistant/components/volumio/translations/el.json @@ -7,6 +7,11 @@ "discovery_confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Volumio (`{name}`) \u03c3\u03c4\u03bf Home Assistant;", "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03c4\u03bf Volumio" + }, + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + } } } } diff --git a/homeassistant/components/volumio/translations/ru.json b/homeassistant/components/volumio/translations/ru.json index 905ecbe8e4b696..9ce0080b218501 100644 --- a/homeassistant/components/volumio/translations/ru.json +++ b/homeassistant/components/volumio/translations/ru.json @@ -11,7 +11,7 @@ "step": { "discovery_confirm": { "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c Volumio `{name}`?", - "title": "\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0439 Volumio" + "title": "\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d Volumio" }, "user": { "data": { diff --git a/homeassistant/components/volumio/translations/sk.json b/homeassistant/components/volumio/translations/sk.json new file mode 100644 index 00000000000000..892b8b2cd91240 --- /dev/null +++ b/homeassistant/components/volumio/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/cs.json b/homeassistant/components/wallbox/translations/cs.json new file mode 100644 index 00000000000000..72df4a968182fc --- /dev/null +++ b/homeassistant/components/wallbox/translations/cs.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/sk.json b/homeassistant/components/wallbox/translations/sk.json new file mode 100644 index 00000000000000..71a7aea5018f3f --- /dev/null +++ b/homeassistant/components/wallbox/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/watttime/translations/sk.json b/homeassistant/components/watttime/translations/sk.json new file mode 100644 index 00000000000000..1d9ecbee3fccdb --- /dev/null +++ b/homeassistant/components/watttime/translations/sk.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "coordinates": { + "data": { + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka" + } + }, + "location": { + "data": { + "location_type": "Umiestnenie" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/waze_travel_time/translations/sk.json b/homeassistant/components/waze_travel_time/translations/sk.json new file mode 100644 index 00000000000000..ce32d575ee2e0b --- /dev/null +++ b/homeassistant/components/waze_travel_time/translations/sk.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "Umiestnenie u\u017e je nakonfigurovan\u00e9" + }, + "step": { + "user": { + "data": { + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/webostv/translations/cs.json b/homeassistant/components/webostv/translations/cs.json index ef9650f28ae993..4ab388e95df23a 100644 --- a/homeassistant/components/webostv/translations/cs.json +++ b/homeassistant/components/webostv/translations/cs.json @@ -2,7 +2,46 @@ "config": { "abort": { "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", - "already_in_progress": "Konfigurace ji\u017e prob\u00edh\u00e1" + "already_in_progress": "Konfigurace ji\u017e prob\u00edh\u00e1", + "error_pairing": "P\u0159ipojeno k televizoru LG se syst\u00e9mem webOS, ale nesp\u00e1rov\u00e1no" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit, zapn\u011bte pros\u00edm televizor nebo zkontrolujte IP adresu" + }, + "flow_title": "LG webOS Smart TV", + "step": { + "pairing": { + "description": "Klikn\u011bte na tla\u010d\u00edtko Odeslat a p\u0159ijm\u011bte \u017e\u00e1dost o sp\u00e1rov\u00e1n\u00ed na va\u0161em televizoru.\n\n![Image] (/static/images/config_webos.png)", + "title": "P\u00e1rov\u00e1n\u00ed televize se syst\u00e9mem webOS" + }, + "user": { + "data": { + "host": "Hostitel", + "name": "Jm\u00e9no" + }, + "description": "Zapn\u011bte televizi, vypl\u0148te n\u00e1sleduj\u00edc\u00ed pole, klikn\u011bte na Odeslat", + "title": "P\u0159ipojen\u00ed k televizoru se syst\u00e9mem webOS" + } + } + }, + "device_automation": { + "trigger_type": { + "webostv.turn_on": "Za\u0159\u00edzen\u00ed se m\u00e1 zapnout" + } + }, + "options": { + "error": { + "cannot_retrieve": "Nelze na\u010d\u00edst seznam zdroj\u016f. Zkontrolujte, zda je za\u0159\u00edzen\u00ed zapnut\u00e9", + "script_not_found": "Skript nebyl nalezen" + }, + "step": { + "init": { + "data": { + "sources": "Seznam zdroj\u016f" + }, + "description": "V\u00fdb\u011br povolen\u00fdch zdroj\u016f", + "title": "Mo\u017enosti pro chytrou televizi se syst\u00e9mem webOS" + } } } } \ No newline at end of file diff --git a/homeassistant/components/webostv/translations/sk.json b/homeassistant/components/webostv/translations/sk.json new file mode 100644 index 00000000000000..5768568606545a --- /dev/null +++ b/homeassistant/components/webostv/translations/sk.json @@ -0,0 +1,46 @@ +{ + "config": { + "abort": { + "already_configured": "Zariadenie u\u017e je nakonfigurovan\u00e9", + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha", + "error_pairing": "Pripojen\u00e9 k telev\u00edzoru LG so syst\u00e9mom webOS, ale nesp\u00e1rovan\u00e9" + }, + "error": { + "cannot_connect": "Nepodarilo sa pripoji\u0165, pros\u00edm, zapnite telev\u00edzor alebo skontrolujte IP adresu" + }, + "flow_title": "LG webOS Smart TV", + "step": { + "pairing": { + "description": "Kliknite na Odosla\u0165 a prijmite \u017eiados\u0165 o p\u00e1rovanie na telev\u00edzore.\n\n![Image](/static/images/config_webos.png)", + "title": "Sp\u00e1rovanie TV so syst\u00e9mom webOS" + }, + "user": { + "data": { + "name": "N\u00e1zov" + }, + "description": "Zapnite TV, vypl\u0148te nasleduj\u00face polia, kliknite na Odosla\u0165", + "title": "Pripojenie k TV so syst\u00e9mom webOS" + } + } + }, + "device_automation": { + "trigger_type": { + "webostv.turn_on": "Zariadenie sa m\u00e1 zapn\u00fa\u0165" + } + }, + "options": { + "error": { + "cannot_retrieve": "Nie je mo\u017en\u00e9 na\u010d\u00edta\u0165 zoznam zdrojov. Skontrolujte, \u010di je zariadenie zapnut\u00e9", + "script_not_found": "Skript sa nena\u0161iel" + }, + "step": { + "init": { + "data": { + "sources": "Zoznam zdrojov" + }, + "description": "V\u00fdber povolen\u00fdch zdrojov", + "title": "Mo\u017enosti pre Smart TV so syst\u00e9mom webOS" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/whirlpool/translations/sk.json b/homeassistant/components/whirlpool/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/whirlpool/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/sk.json b/homeassistant/components/wiffi/translations/sk.json new file mode 100644 index 00000000000000..892b8b2cd91240 --- /dev/null +++ b/homeassistant/components/wiffi/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/withings/translations/el.json b/homeassistant/components/withings/translations/el.json index a7d70b00a21a82..d983f0aa48d0b0 100644 --- a/homeassistant/components/withings/translations/el.json +++ b/homeassistant/components/withings/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03b3\u03b9\u03b1 \u03c4\u03bf \u03c0\u03c1\u03bf\u03c6\u03af\u03bb." + }, "create_entry": { "default": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03c4\u03bf Withings." }, diff --git a/homeassistant/components/wiz/translations/el.json b/homeassistant/components/wiz/translations/el.json index a251696484044c..dd48cf243c23b7 100644 --- a/homeassistant/components/wiz/translations/el.json +++ b/homeassistant/components/wiz/translations/el.json @@ -20,6 +20,7 @@ }, "user": { "data": { + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", "name": "\u038c\u03bd\u03bf\u03bc\u03b1" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03ba\u03b1\u03b9 \u03ad\u03bd\u03b1 \u03cc\u03bd\u03bf\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03bd\u03ad\u03bf \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b1:" diff --git a/homeassistant/components/wiz/translations/sk.json b/homeassistant/components/wiz/translations/sk.json new file mode 100644 index 00000000000000..af15f92c2f27a7 --- /dev/null +++ b/homeassistant/components/wiz/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/el.json b/homeassistant/components/wolflink/translations/el.json index 18c2f0869bd1c2..7f545aa35b4961 100644 --- a/homeassistant/components/wolflink/translations/el.json +++ b/homeassistant/components/wolflink/translations/el.json @@ -1,10 +1,17 @@ { "config": { "step": { + "device": { + "data": { + "device_name": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 WOLF" + }, "user": { "data": { "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" - } + }, + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 WOLF SmartSet" } } } diff --git a/homeassistant/components/wolflink/translations/sensor.el.json b/homeassistant/components/wolflink/translations/sensor.el.json index d752c023c7247a..24c5868d0e9407 100644 --- a/homeassistant/components/wolflink/translations/sensor.el.json +++ b/homeassistant/components/wolflink/translations/sensor.el.json @@ -1,6 +1,7 @@ { "state": { "wolflink__state": { + "1_x_warmwasser": "1 x DHW", "abgasklappe": "\u0391\u03c0\u03bf\u03c3\u03b2\u03b5\u03c3\u03c4\u03ae\u03c1\u03b1\u03c2 \u03ba\u03b1\u03c5\u03c3\u03b1\u03b5\u03c1\u03af\u03c9\u03bd", "absenkbetrieb": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03bf\u03c0\u03b9\u03c3\u03b8\u03bf\u03b4\u03c1\u03cc\u03bc\u03b7\u03c3\u03b7\u03c2", "absenkstop": "\u0394\u03b9\u03b1\u03ba\u03bf\u03c0\u03ae \u03bf\u03c0\u03b9\u03c3\u03b8\u03bf\u03b4\u03c1\u03cc\u03bc\u03b7\u03c3\u03b7\u03c2", @@ -21,6 +22,7 @@ "dhw_prior": "DHWPrior", "eco": "Eco", "ein": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf", + "estrichtrocknung": "\u03a3\u03c4\u03ad\u03b3\u03bd\u03c9\u03bc\u03b1 \u03b5\u03c0\u03af\u03c3\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2", "externe_deaktivierung": "\u0395\u03be\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03ae \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", "fernschalter_ein": "\u0391\u03c0\u03bf\u03bc\u03b1\u03ba\u03c1\u03c5\u03c3\u03bc\u03ad\u03bd\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u0395\u03bd\u03b5\u03c1\u03b3\u03ae", "frost_heizkreis": "\u03a0\u03b1\u03b3\u03b5\u03c4\u03cc\u03c2 \u03c3\u03c4\u03bf \u03ba\u03cd\u03ba\u03bb\u03c9\u03bc\u03b1 \u03b8\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7\u03c2", @@ -32,6 +34,14 @@ "heizbetrieb": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b8\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7\u03c2", "heizgerat_mit_speicher": "\u039b\u03ad\u03b2\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03ba\u03cd\u03bb\u03b9\u03bd\u03b4\u03c1\u03bf", "heizung": "\u0398\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7", + "initialisierung": "\u0391\u03c1\u03c7\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", + "kalibration": "\u0392\u03b1\u03b8\u03bc\u03bf\u03bd\u03cc\u03bc\u03b7\u03c3\u03b7", + "kalibration_heizbetrieb": "\u0392\u03b1\u03b8\u03bc\u03bf\u03bd\u03cc\u03bc\u03b7\u03c3\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b8\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7\u03c2", + "kombibetrieb": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 Combi", + "kombigerat": "\u039c\u03c0\u03cc\u03b9\u03bb\u03b5\u03c1 Combi", + "kombigerat_mit_solareinbindung": "\u039c\u03c0\u03cc\u03b9\u03bb\u03b5\u03c1 Combi \u03bc\u03b5 \u03b7\u03bb\u03b9\u03b1\u03ba\u03ae \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7", + "mindest_kombizeit": "\u0395\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf\u03c2 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c2 \u03c3\u03c5\u03bd\u03b4\u03c5\u03b1\u03c3\u03bc\u03bf\u03cd", + "nachlauf_heizkreispumpe": "\u0391\u03bd\u03c4\u03bb\u03af\u03b1 \u03ba\u03c5\u03ba\u03bb\u03ce\u03bc\u03b1\u03c4\u03bf\u03c2 \u03b8\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7\u03c2 \u03c3\u03b5 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1", "nur_heizgerat": "\u039c\u03cc\u03bd\u03bf \u03bb\u03ad\u03b2\u03b7\u03c4\u03b1\u03c2", "parallelbetrieb": "\u03a0\u03b1\u03c1\u03ac\u03bb\u03bb\u03b7\u03bb\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1", "partymodus": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c0\u03ac\u03c1\u03c4\u03b9", @@ -59,10 +69,12 @@ "test": "\u0394\u03bf\u03ba\u03b9\u03bc\u03ae", "tpw": "TPW", "urlaubsmodus": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b4\u03b9\u03b1\u03ba\u03bf\u03c0\u03ce\u03bd", + "ventilprufung": "\u0394\u03bf\u03ba\u03b9\u03bc\u03ae \u03b2\u03b1\u03bb\u03b2\u03af\u03b4\u03b1\u03c2", "vorspulen": "\u039e\u03ad\u03b2\u03b3\u03b1\u03bb\u03bc\u03b1 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5", "warmwasser": "DHW", "warmwasser_schnellstart": "\u0393\u03c1\u03ae\u03b3\u03bf\u03c1\u03b7 \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7 DHW", "warmwasserbetrieb": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 DHW", + "warmwassernachlauf": "\u0395\u03ba\u03c4\u03ad\u03bb\u03b5\u03c3\u03b7 DHW", "warmwasservorrang": "\u03a0\u03c1\u03bf\u03c4\u03b5\u03c1\u03b1\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1 DHW", "zunden": "\u0391\u03bd\u03ac\u03c6\u03bb\u03b5\u03be\u03b7" } diff --git a/homeassistant/components/wolflink/translations/sk.json b/homeassistant/components/wolflink/translations/sk.json new file mode 100644 index 00000000000000..5ada995aa6ea96 --- /dev/null +++ b/homeassistant/components/wolflink/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xbox/translations/sk.json b/homeassistant/components/xbox/translations/sk.json new file mode 100644 index 00000000000000..c19b1a0b70c707 --- /dev/null +++ b/homeassistant/components/xbox/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "create_entry": { + "default": "\u00daspe\u0161ne overen\u00e9" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_aqara/translations/el.json b/homeassistant/components/xiaomi_aqara/translations/el.json index 3d7f30e39dfc7f..74252f1b493211 100644 --- a/homeassistant/components/xiaomi_aqara/translations/el.json +++ b/homeassistant/components/xiaomi_aqara/translations/el.json @@ -13,6 +13,9 @@ "flow_title": "{name}", "step": { "select": { + "data": { + "select_ip": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + }, "description": "\u0395\u03ba\u03c4\u03b5\u03bb\u03ad\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b5\u03ac\u03bd \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03b5\u03c0\u03b9\u03c0\u03bb\u03ad\u03bf\u03bd \u03c0\u03cd\u03bb\u03b5\u03c2", "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03cd\u03bb\u03b7 Xiaomi Aqara \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5" }, diff --git a/homeassistant/components/xiaomi_aqara/translations/sk.json b/homeassistant/components/xiaomi_aqara/translations/sk.json new file mode 100644 index 00000000000000..299acb612fbab2 --- /dev/null +++ b/homeassistant/components/xiaomi_aqara/translations/sk.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "already_configured": "Zariadenie u\u017e je nakonfigurovan\u00e9", + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/cs.json b/homeassistant/components/xiaomi_miio/translations/cs.json index ec275b9333036b..69b6cd221ee0f8 100644 --- a/homeassistant/components/xiaomi_miio/translations/cs.json +++ b/homeassistant/components/xiaomi_miio/translations/cs.json @@ -2,19 +2,41 @@ "config": { "abort": { "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", - "already_in_progress": "Konfigurace ji\u017e prob\u00edh\u00e1" + "already_in_progress": "Konfigurace ji\u017e prob\u00edh\u00e1", + "incomplete_info": "Ne\u00fapln\u00e9 informace pro nastaven\u00ed za\u0159\u00edzen\u00ed, chyb\u00ed hostitel nebo token.", + "not_xiaomi_miio": "Za\u0159\u00edzen\u00ed (zat\u00edm) nen\u00ed podporov\u00e1no integrac\u00ed Xiaomi Miio.", + "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9" }, "error": { "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", - "no_device_selected": "Nebylo vybr\u00e1no \u017e\u00e1dn\u00e9 za\u0159\u00edzen\u00ed, vyberte jedno za\u0159\u00edzen\u00ed." + "no_device_selected": "Nebylo vybr\u00e1no \u017e\u00e1dn\u00e9 za\u0159\u00edzen\u00ed, vyberte jedno za\u0159\u00edzen\u00ed.", + "unknown_device": "Model za\u0159\u00edzen\u00ed nen\u00ed zn\u00e1m, nastaven\u00ed za\u0159\u00edzen\u00ed nen\u00ed mo\u017en\u00e9 dokon\u010dit.", + "wrong_token": "Chyba kontroln\u00edho sou\u010dtu, \u0161patn\u00fd token" }, "flow_title": "Xiaomi Miio: {name}", "step": { + "cloud": { + "data": { + "manual": "Nastavit ru\u010dn\u011b (nedoporu\u010deno)" + }, + "title": "P\u0159ipojen\u00ed k za\u0159\u00edzen\u00ed Xiaomi Miio nebo k br\u00e1n\u011b Xiaomi" + }, + "connect": { + "data": { + "model": "Model za\u0159\u00edzen\u00ed" + }, + "description": "Ru\u010dn\u011b vyberte model za\u0159\u00edzen\u00ed ze seznamu podporovan\u00fdch za\u0159\u00edzen\u00ed.", + "title": "P\u0159ipojen\u00ed k za\u0159\u00edzen\u00ed Xiaomi Miio nebo k br\u00e1n\u011b Xiaomi" + }, "device": { "data": { "host": "IP adresa", + "model": "Model za\u0159\u00edzen\u00ed (voliteln\u00e9)", + "name": "Jm\u00e9no za\u0159\u00edzen\u00ed", "token": "API token" - } + }, + "description": "Budete pot\u0159ebovat 32 znakov\u00fd API token, pokyny naleznete na https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token. Upozor\u0148ujeme, \u017ee tento token se li\u0161\u00ed od kl\u00ed\u010de pou\u017e\u00edvan\u00e9ho v integraci Xiaomi Aqara.", + "title": "P\u0159ipojen\u00ed k za\u0159\u00edzen\u00ed Xiaomi Miio nebo k br\u00e1n\u011b Xiaomi" }, "gateway": { "data": { @@ -25,6 +47,24 @@ "description": "Budete pot\u0159ebovat 32 znakov\u00fd API token, pokyny naleznete na https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token. Upozor\u0148ujeme, \u017ee tento token se li\u0161\u00ed od kl\u00ed\u010de pou\u017e\u00edvan\u00e9ho v integraci Xiaomi Aqara.", "title": "P\u0159ipojen\u00ed k br\u00e1n\u011b Xiaomi" }, + "manual": { + "data": { + "host": "IP adresa", + "token": "API token" + }, + "description": "Budete pot\u0159ebovat 32 znakov\u00fd API token, pokyny naleznete na https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token. Upozor\u0148ujeme, \u017ee tento token se li\u0161\u00ed od kl\u00ed\u010de pou\u017e\u00edvan\u00e9ho v integraci Xiaomi Aqara.", + "title": "P\u0159ipojen\u00ed k za\u0159\u00edzen\u00ed Xiaomi Miio nebo k br\u00e1n\u011b Xiaomi" + }, + "reauth_confirm": { + "title": "Znovu ov\u011b\u0159it integraci" + }, + "select": { + "data": { + "select_device": "Za\u0159\u00edzen\u00ed Miio" + }, + "description": "Vyberte Xiaomi Miio za\u0159\u00edzen\u00ed, kter\u00e9 chcete nastavit.", + "title": "P\u0159ipojen\u00ed k za\u0159\u00edzen\u00ed Xiaomi Miio nebo k br\u00e1n\u011b Xiaomi" + }, "user": { "data": { "gateway": "P\u0159ipojen\u00ed k br\u00e1n\u011b Xiaomi" @@ -33,5 +73,16 @@ "title": "Xiaomi Miio" } } + }, + "options": { + "step": { + "init": { + "data": { + "cloud_subdevices": "Pou\u017e\u00edt cloud pro z\u00edsk\u00e1n\u00ed p\u0159ipojen\u00fdch podru\u017en\u00fdch za\u0159\u00edzen\u00ed" + }, + "description": "Zadejte voliteln\u00e9 nastaven\u00ed", + "title": "Xiaomi Miio" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/el.json b/homeassistant/components/xiaomi_miio/translations/el.json index 3f6dcb8b015af9..d8802b2bfb8ae6 100644 --- a/homeassistant/components/xiaomi_miio/translations/el.json +++ b/homeassistant/components/xiaomi_miio/translations/el.json @@ -41,12 +41,16 @@ }, "gateway": { "data": { + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c4\u03b7\u03c2 \u03c0\u03cd\u03bb\u03b7\u03c2" }, "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf\u03c5\u03c2 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b5\u03c2 32 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API , \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u03b3\u03b9\u03b1 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2. \u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03b1\u03c0\u03cc \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Xiaomi Aqara.", "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03cd\u03bb\u03b7 Xiaomi" }, "manual": { + "data": { + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + }, "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf 32 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03c9\u03bd \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API, \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \u03b3\u03b9\u03b1 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2. \u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03b1\u03c0\u03cc \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Xiaomi Aqara.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Xiaomi Miio \u03ae \u03c0\u03cd\u03bb\u03b7 Xiaomi" }, diff --git a/homeassistant/components/xiaomi_miio/translations/sk.json b/homeassistant/components/xiaomi_miio/translations/sk.json new file mode 100644 index 00000000000000..022e4103fb7b9b --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/sk.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Zariadenie u\u017e je nakonfigurovan\u00e9", + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha", + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "step": { + "device": { + "data": { + "token": "API token" + } + }, + "gateway": { + "data": { + "token": "API token" + } + }, + "manual": { + "data": { + "token": "API token" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yale_smart_alarm/translations/sk.json b/homeassistant/components/yale_smart_alarm/translations/sk.json new file mode 100644 index 00000000000000..00dddc88d1d736 --- /dev/null +++ b/homeassistant/components/yale_smart_alarm/translations/sk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "reauth_confirm": { + "data": { + "name": "N\u00e1zov" + } + }, + "user": { + "data": { + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yamaha_musiccast/translations/select.el.json b/homeassistant/components/yamaha_musiccast/translations/select.el.json index 68bca5c452faea..6caacd91b64885 100644 --- a/homeassistant/components/yamaha_musiccast/translations/select.el.json +++ b/homeassistant/components/yamaha_musiccast/translations/select.el.json @@ -4,7 +4,49 @@ "auto": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03bf" }, "yamaha_musiccast__zone_equalizer_mode": { - "auto": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03bf" + "auto": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03bf", + "bypass": "\u03a0\u03b1\u03c1\u03ac\u03ba\u03b1\u03bc\u03c8\u03b7", + "manual": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03bf" + }, + "yamaha_musiccast__zone_link_audio_delay": { + "audio_sync": "\u03a3\u03c5\u03b3\u03c7\u03c1\u03bf\u03bd\u03b9\u03c3\u03bc\u03cc\u03c2 \u03ae\u03c7\u03bf\u03c5", + "audio_sync_off": "\u0391\u03bd\u03b5\u03bd\u03b5\u03c1\u03b3\u03cc\u03c2 \u03c3\u03c5\u03b3\u03c7\u03c1\u03bf\u03bd\u03b9\u03c3\u03bc\u03cc\u03c2 \u03ae\u03c7\u03bf\u03c5", + "audio_sync_on": "\u0395\u03bd\u03b5\u03c1\u03b3\u03cc\u03c2 \u03c3\u03c5\u03b3\u03c7\u03c1\u03bf\u03bd\u03b9\u03c3\u03bc\u03cc\u03c2 \u03ae\u03c7\u03bf\u03c5", + "balanced": "\u0399\u03c3\u03bf\u03c1\u03c1\u03bf\u03c0\u03b7\u03bc\u03ad\u03bd\u03bf", + "lip_sync": "\u03a3\u03c5\u03b3\u03c7\u03c1\u03bf\u03bd\u03b9\u03c3\u03bc\u03cc\u03c2 \u03c7\u03b5\u03b9\u03bb\u03b9\u03ce\u03bd" + }, + "yamaha_musiccast__zone_link_audio_quality": { + "compressed": "\u03a3\u03c5\u03bc\u03c0\u03b9\u03b5\u03c3\u03bc\u03ad\u03bd\u03bf", + "uncompressed": "\u039c\u03b7 \u03c3\u03c5\u03bc\u03c0\u03b9\u03b5\u03c3\u03bc\u03ad\u03bd\u03bf" + }, + "yamaha_musiccast__zone_link_control": { + "speed": "\u03a4\u03b1\u03c7\u03cd\u03c4\u03b7\u03c4\u03b1", + "stability": "\u03a3\u03c4\u03b1\u03b8\u03b5\u03c1\u03cc\u03c4\u03b7\u03c4\u03b1", + "standard": "\u03a4\u03c5\u03c0\u03b9\u03ba\u03cc" + }, + "yamaha_musiccast__zone_sleep": { + "120 min": "120 \u03bb\u03b5\u03c0\u03c4\u03ac", + "30 min": "30 \u03bb\u03b5\u03c0\u03c4\u03ac", + "60 min": "60 \u03bb\u03b5\u03c0\u03c4\u03ac", + "90 min": "90 \u03bb\u03b5\u03c0\u03c4\u03ac", + "off": "\u0391\u03bd\u03b5\u03bd\u03b5\u03c1\u03b3\u03cc" + }, + "yamaha_musiccast__zone_surr_decoder_type": { + "auto": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03bf", + "dolby_pl": "Dolby ProLogic", + "dolby_pl2x_game": "Dolby ProLogic 2x \u03a0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9", + "dolby_pl2x_movie": "Dolby ProLogic 2x \u03a4\u03b1\u03b9\u03bd\u03af\u03b1", + "dolby_pl2x_music": "Dolby ProLogic 2x \u039c\u03bf\u03c5\u03c3\u03b9\u03ba\u03ae", + "dolby_surround": "Dolby Surround", + "dts_neo6_cinema": "DTS Neo:6 \u039a\u03b9\u03bd\u03b7\u03bc\u03b1\u03c4\u03bf\u03b3\u03c1\u03ac\u03c6\u03bf\u03c2", + "dts_neo6_music": "DTS Neo:6 \u039c\u03bf\u03c5\u03c3\u03b9\u03ba\u03ae", + "dts_neural_x": "DTS Neural:X", + "toggle": "\u0395\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae" + }, + "yamaha_musiccast__zone_tone_control_mode": { + "auto": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03bf", + "bypass": "\u03a0\u03b1\u03c1\u03ac\u03ba\u03b1\u03bc\u03c8\u03b7", + "manual": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03bf" } } } \ No newline at end of file diff --git a/homeassistant/components/yeelight/translations/sk.json b/homeassistant/components/yeelight/translations/sk.json new file mode 100644 index 00000000000000..793f8eff278722 --- /dev/null +++ b/homeassistant/components/yeelight/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Zariadenie u\u017e je nakonfigurovan\u00e9" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/youless/translations/el.json b/homeassistant/components/youless/translations/el.json index 1dbdef83eea4e3..91ade7d132b8c3 100644 --- a/homeassistant/components/youless/translations/el.json +++ b/homeassistant/components/youless/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1" } } diff --git a/homeassistant/components/youless/translations/sk.json b/homeassistant/components/youless/translations/sk.json new file mode 100644 index 00000000000000..af15f92c2f27a7 --- /dev/null +++ b/homeassistant/components/youless/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "N\u00e1zov" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zone/translations/sk.json b/homeassistant/components/zone/translations/sk.json new file mode 100644 index 00000000000000..5272ec1315a203 --- /dev/null +++ b/homeassistant/components/zone/translations/sk.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "init": { + "data": { + "latitude": "Zemepisn\u00e1 \u0161\u00edrka", + "longitude": "Zemepisn\u00e1 d\u013a\u017eka" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/sk.json b/homeassistant/components/zoneminder/translations/sk.json new file mode 100644 index 00000000000000..2c3ed1dd93049d --- /dev/null +++ b/homeassistant/components/zoneminder/translations/sk.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "invalid_auth": "Neplatn\u00e9 overenie" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 overenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave/translations/sk.json b/homeassistant/components/zwave/translations/sk.json index f53db0f9721a7e..9819295ee1f74c 100644 --- a/homeassistant/components/zwave/translations/sk.json +++ b/homeassistant/components/zwave/translations/sk.json @@ -1,4 +1,9 @@ { + "config": { + "abort": { + "already_configured": "Zariadenie u\u017e je nakonfigurovan\u00e9" + } + }, "state": { "_": { "dead": "Nereaguje", diff --git a/homeassistant/components/zwave_js/translations/el.json b/homeassistant/components/zwave_js/translations/el.json index 2cf46d541659e5..663137ffa49e2d 100644 --- a/homeassistant/components/zwave_js/translations/el.json +++ b/homeassistant/components/zwave_js/translations/el.json @@ -29,6 +29,12 @@ "description": "\u03a4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf \u03b8\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03b9 \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03ac \u03b1\u03c3\u03c6\u03b1\u03bb\u03b5\u03af\u03b1\u03c2 \u03b5\u03ac\u03bd \u03c4\u03b1 \u03c0\u03b5\u03b4\u03af\u03b1 \u03b1\u03c5\u03c4\u03ac \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03af\u03bd\u03bf\u03c5\u03bd \u03ba\u03b5\u03bd\u03ac.", "title": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS" }, + "hassio_confirm": { + "title": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Z-Wave JS \u03bc\u03b5 \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf Z-Wave JS" + }, + "install_addon": { + "title": "\u0397 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS \u03ad\u03c7\u03b5\u03b9 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03b9" + }, "on_supervisor": { "data": { "use_addon": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf Z-Wave JS Supervisor" diff --git a/homeassistant/components/zwave_js/translations/sk.json b/homeassistant/components/zwave_js/translations/sk.json new file mode 100644 index 00000000000000..833d18faafbea6 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/sk.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "already_configured": "Zariadenie u\u017e je nakonfigurovan\u00e9", + "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + } + }, + "options": { + "abort": { + "already_configured": "Zariadenie u\u017e je nakonfigurovan\u00e9" + } + } +} \ No newline at end of file From e9ca7c251644825f8462a430683944eab4ef30b8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 19 Feb 2022 22:54:12 -0600 Subject: [PATCH 0843/1098] Add support for WiZ diagnostics (#66817) --- homeassistant/components/wiz/diagnostics.py | 27 +++++++++++++++++++++ homeassistant/components/wiz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/wiz/__init__.py | 5 ++++ tests/components/wiz/test_diagnostics.py | 19 +++++++++++++++ 6 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/wiz/diagnostics.py create mode 100644 tests/components/wiz/test_diagnostics.py diff --git a/homeassistant/components/wiz/diagnostics.py b/homeassistant/components/wiz/diagnostics.py new file mode 100644 index 00000000000000..4fdf62b3c8ca43 --- /dev/null +++ b/homeassistant/components/wiz/diagnostics.py @@ -0,0 +1,27 @@ +"""Diagnostics support for WiZ.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import DOMAIN +from .models import WizData + +TO_REDACT = {"roomId", "homeId"} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + wiz_data: WizData = hass.data[DOMAIN][entry.entry_id] + return { + "entry": { + "title": entry.title, + "data": dict(entry.data), + }, + "data": async_redact_data(wiz_data.bulb.diagnostics, TO_REDACT), + } diff --git a/homeassistant/components/wiz/manifest.json b/homeassistant/components/wiz/manifest.json index 7794162d32c012..021b986f82ec93 100644 --- a/homeassistant/components/wiz/manifest.json +++ b/homeassistant/components/wiz/manifest.json @@ -10,7 +10,7 @@ "dependencies": ["network"], "quality_scale": "platinum", "documentation": "https://www.home-assistant.io/integrations/wiz", - "requirements": ["pywizlight==0.5.9"], + "requirements": ["pywizlight==0.5.10"], "iot_class": "local_push", "codeowners": ["@sbidy"] } diff --git a/requirements_all.txt b/requirements_all.txt index b743bc8a55dcad..8796d7556f55ae 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2057,7 +2057,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.5.9 +pywizlight==0.5.10 # homeassistant.components.xeoma pyxeoma==1.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 21aca60f3bd2a9..6399e9611f22f1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1288,7 +1288,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.5.9 +pywizlight==0.5.10 # homeassistant.components.zerproc pyzerproc==0.4.8 diff --git a/tests/components/wiz/__init__.py b/tests/components/wiz/__init__.py index 8d187e9b476be5..ca4d04601735c1 100644 --- a/tests/components/wiz/__init__.py +++ b/tests/components/wiz/__init__.py @@ -171,6 +171,11 @@ async def _save_setup_callback(callback: Callable) -> None: bulb.start_push = AsyncMock(side_effect=_save_setup_callback) bulb.async_close = AsyncMock() bulb.set_speed = AsyncMock() + bulb.diagnostics = { + "mocked": "mocked", + "roomId": 123, + "homeId": 34, + } bulb.state = FAKE_STATE bulb.mac = FAKE_MAC bulb.bulbtype = bulb_type or FAKE_DIMMABLE_BULB diff --git a/tests/components/wiz/test_diagnostics.py b/tests/components/wiz/test_diagnostics.py new file mode 100644 index 00000000000000..c993072bc073e1 --- /dev/null +++ b/tests/components/wiz/test_diagnostics.py @@ -0,0 +1,19 @@ +"""Test WiZ diagnostics.""" +from . import async_setup_integration + +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_diagnostics(hass, hass_client): + """Test generating diagnostics for a config entry.""" + _, entry = await async_setup_integration(hass) + diag = await get_diagnostics_for_config_entry(hass, hass_client, entry) + + assert diag == { + "data": { + "homeId": "**REDACTED**", + "mocked": "mocked", + "roomId": "**REDACTED**", + }, + "entry": {"data": {"host": "1.1.1.1"}, "title": "Mock Title"}, + } From f3add292d50a4309be4cb698b6694e92edcca4a2 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sat, 19 Feb 2022 20:57:29 -0800 Subject: [PATCH 0844/1098] Update nest climate set_temperature to allow hvac_mode (#66909) --- homeassistant/components/nest/climate_sdm.py | 11 +++- tests/components/nest/test_climate_sdm.py | 59 ++++++++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/nest/climate_sdm.py b/homeassistant/components/nest/climate_sdm.py index 77b3b24331be02..ff8cffcf7fa02d 100644 --- a/homeassistant/components/nest/climate_sdm.py +++ b/homeassistant/components/nest/climate_sdm.py @@ -16,6 +16,7 @@ from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( + ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_COOL, @@ -311,18 +312,22 @@ async def async_set_hvac_mode(self, hvac_mode: str) -> None: async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" + hvac_mode = self.hvac_mode + if kwargs.get(ATTR_HVAC_MODE) is not None: + hvac_mode = kwargs[ATTR_HVAC_MODE] + await self.async_set_hvac_mode(hvac_mode) low_temp = kwargs.get(ATTR_TARGET_TEMP_LOW) high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH) temp = kwargs.get(ATTR_TEMPERATURE) if ThermostatTemperatureSetpointTrait.NAME not in self._device.traits: return trait = self._device.traits[ThermostatTemperatureSetpointTrait.NAME] - if self.preset_mode == PRESET_ECO or self.hvac_mode == HVAC_MODE_HEAT_COOL: + if self.preset_mode == PRESET_ECO or hvac_mode == HVAC_MODE_HEAT_COOL: if low_temp and high_temp: await trait.set_range(low_temp, high_temp) - elif self.hvac_mode == HVAC_MODE_COOL and temp: + elif hvac_mode == HVAC_MODE_COOL and temp: await trait.set_cool(temp) - elif self.hvac_mode == HVAC_MODE_HEAT and temp: + elif hvac_mode == HVAC_MODE_HEAT and temp: await trait.set_heat(temp) async def async_set_preset_mode(self, preset_mode: str) -> None: diff --git a/tests/components/nest/test_climate_sdm.py b/tests/components/nest/test_climate_sdm.py index 6b100969ea97a8..5f3efa362b35ba 100644 --- a/tests/components/nest/test_climate_sdm.py +++ b/tests/components/nest/test_climate_sdm.py @@ -677,6 +677,65 @@ async def test_thermostat_set_heat( } +async def test_thermostat_set_temperature_hvac_mode( + hass: HomeAssistant, + setup_platform: PlatformSetup, + auth: FakeAuth, + create_device: CreateDevice, +) -> None: + """Test setting HVAC mode while setting temperature.""" + create_device.create( + { + "sdm.devices.traits.ThermostatHvac": {"status": "OFF"}, + "sdm.devices.traits.ThermostatMode": { + "availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"], + "mode": "OFF", + }, + "sdm.devices.traits.ThermostatTemperatureSetpoint": { + "coolCelsius": 25.0, + }, + }, + ) + await setup_platform() + + assert len(hass.states.async_all()) == 1 + thermostat = hass.states.get("climate.my_thermostat") + assert thermostat is not None + assert thermostat.state == HVAC_MODE_OFF + + await common.async_set_temperature(hass, temperature=24.0, hvac_mode=HVAC_MODE_COOL) + await hass.async_block_till_done() + + assert auth.method == "post" + assert auth.url == DEVICE_COMMAND + assert auth.json == { + "command": "sdm.devices.commands.ThermostatTemperatureSetpoint.SetCool", + "params": {"coolCelsius": 24.0}, + } + + await common.async_set_temperature(hass, temperature=26.0, hvac_mode=HVAC_MODE_HEAT) + await hass.async_block_till_done() + + assert auth.method == "post" + assert auth.url == DEVICE_COMMAND + assert auth.json == { + "command": "sdm.devices.commands.ThermostatTemperatureSetpoint.SetHeat", + "params": {"heatCelsius": 26.0}, + } + + await common.async_set_temperature( + hass, target_temp_low=20.0, target_temp_high=24.0, hvac_mode=HVAC_MODE_HEAT_COOL + ) + await hass.async_block_till_done() + + assert auth.method == "post" + assert auth.url == DEVICE_COMMAND + assert auth.json == { + "command": "sdm.devices.commands.ThermostatTemperatureSetpoint.SetRange", + "params": {"heatCelsius": 20.0, "coolCelsius": 24.0}, + } + + async def test_thermostat_set_heat_cool( hass: HomeAssistant, setup_platform: PlatformSetup, From 6a7872fc1bec1b3eec8fe3b3c3887be69e209b13 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Sun, 20 Feb 2022 06:00:14 +0100 Subject: [PATCH 0845/1098] Remove async_setup_component() from tests (#66905) --- tests/components/modbus/test_fan.py | 35 +++++------ tests/components/modbus/test_light.py | 35 +++++------ tests/components/modbus/test_switch.py | 85 ++++++++++---------------- 3 files changed, 61 insertions(+), 94 deletions(-) diff --git a/tests/components/modbus/test_fan.py b/tests/components/modbus/test_fan.py index 9ffa48a032a397..f766f816109353 100644 --- a/tests/components/modbus/test_fan.py +++ b/tests/components/modbus/test_fan.py @@ -16,26 +16,21 @@ CONF_VERIFY, CONF_WRITE_TYPE, MODBUS_DOMAIN, - TCP, ) from homeassistant.const import ( CONF_ADDRESS, CONF_COMMAND_OFF, CONF_COMMAND_ON, - CONF_HOST, CONF_NAME, - CONF_PORT, CONF_SCAN_INTERVAL, CONF_SLAVE, - CONF_TYPE, STATE_OFF, STATE_ON, STATE_UNAVAILABLE, ) from homeassistant.core import State -from homeassistant.setup import async_setup_component -from .conftest import TEST_ENTITY_NAME, TEST_MODBUS_HOST, TEST_PORT_TCP, ReadResult +from .conftest import TEST_ENTITY_NAME, ReadResult ENTITY_ID = f"{FAN_DOMAIN}.{TEST_ENTITY_NAME}" ENTITY_ID2 = f"{ENTITY_ID}2" @@ -221,14 +216,10 @@ async def test_restore_state_fan(hass, mock_test_state, mock_modbus): assert hass.states.get(ENTITY_ID).state == STATE_ON -async def test_fan_service_turn(hass, caplog, mock_pymodbus): - """Run test for service turn_on/turn_off.""" - - config = { - MODBUS_DOMAIN: { - CONF_TYPE: TCP, - CONF_HOST: TEST_MODBUS_HOST, - CONF_PORT: TEST_PORT_TCP, +@pytest.mark.parametrize( + "do_config", + [ + { CONF_FANS: [ { CONF_NAME: TEST_ENTITY_NAME, @@ -245,9 +236,11 @@ async def test_fan_service_turn(hass, caplog, mock_pymodbus): }, ], }, - } - assert await async_setup_component(hass, MODBUS_DOMAIN, config) is True - await hass.async_block_till_done() + ], +) +async def test_fan_service_turn(hass, caplog, mock_modbus): + """Run test for service turn_on/turn_off.""" + assert MODBUS_DOMAIN in hass.config.components assert hass.states.get(ENTITY_ID).state == STATE_OFF @@ -262,27 +255,27 @@ async def test_fan_service_turn(hass, caplog, mock_pymodbus): await hass.async_block_till_done() assert hass.states.get(ENTITY_ID).state == STATE_OFF - mock_pymodbus.read_holding_registers.return_value = ReadResult([0x01]) + mock_modbus.read_holding_registers.return_value = ReadResult([0x01]) assert hass.states.get(ENTITY_ID2).state == STATE_OFF await hass.services.async_call( "fan", "turn_on", service_data={"entity_id": ENTITY_ID2} ) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID2).state == STATE_ON - mock_pymodbus.read_holding_registers.return_value = ReadResult([0x00]) + mock_modbus.read_holding_registers.return_value = ReadResult([0x00]) await hass.services.async_call( "fan", "turn_off", service_data={"entity_id": ENTITY_ID2} ) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID2).state == STATE_OFF - mock_pymodbus.write_register.side_effect = ModbusException("fail write_") + mock_modbus.write_register.side_effect = ModbusException("fail write_") await hass.services.async_call( "fan", "turn_on", service_data={"entity_id": ENTITY_ID2} ) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID2).state == STATE_UNAVAILABLE - mock_pymodbus.write_coil.side_effect = ModbusException("fail write_") + mock_modbus.write_coil.side_effect = ModbusException("fail write_") await hass.services.async_call( "fan", "turn_off", service_data={"entity_id": ENTITY_ID} ) diff --git a/tests/components/modbus/test_light.py b/tests/components/modbus/test_light.py index 451d0beca13911..220924733ad3b6 100644 --- a/tests/components/modbus/test_light.py +++ b/tests/components/modbus/test_light.py @@ -15,27 +15,22 @@ CONF_VERIFY, CONF_WRITE_TYPE, MODBUS_DOMAIN, - TCP, ) from homeassistant.const import ( CONF_ADDRESS, CONF_COMMAND_OFF, CONF_COMMAND_ON, - CONF_HOST, CONF_LIGHTS, CONF_NAME, - CONF_PORT, CONF_SCAN_INTERVAL, CONF_SLAVE, - CONF_TYPE, STATE_OFF, STATE_ON, STATE_UNAVAILABLE, ) from homeassistant.core import State -from homeassistant.setup import async_setup_component -from .conftest import TEST_ENTITY_NAME, TEST_MODBUS_HOST, TEST_PORT_TCP, ReadResult +from .conftest import TEST_ENTITY_NAME, ReadResult ENTITY_ID = f"{LIGHT_DOMAIN}.{TEST_ENTITY_NAME}" ENTITY_ID2 = f"{ENTITY_ID}2" @@ -221,14 +216,10 @@ async def test_restore_state_light(hass, mock_test_state, mock_modbus): assert hass.states.get(ENTITY_ID).state == mock_test_state[0].state -async def test_light_service_turn(hass, caplog, mock_pymodbus): - """Run test for service turn_on/turn_off.""" - - config = { - MODBUS_DOMAIN: { - CONF_TYPE: TCP, - CONF_HOST: TEST_MODBUS_HOST, - CONF_PORT: TEST_PORT_TCP, +@pytest.mark.parametrize( + "do_config", + [ + { CONF_LIGHTS: [ { CONF_NAME: TEST_ENTITY_NAME, @@ -245,9 +236,11 @@ async def test_light_service_turn(hass, caplog, mock_pymodbus): }, ], }, - } - assert await async_setup_component(hass, MODBUS_DOMAIN, config) is True - await hass.async_block_till_done() + ], +) +async def test_light_service_turn(hass, caplog, mock_modbus): + """Run test for service turn_on/turn_off.""" + assert MODBUS_DOMAIN in hass.config.components assert hass.states.get(ENTITY_ID).state == STATE_OFF @@ -262,27 +255,27 @@ async def test_light_service_turn(hass, caplog, mock_pymodbus): await hass.async_block_till_done() assert hass.states.get(ENTITY_ID).state == STATE_OFF - mock_pymodbus.read_holding_registers.return_value = ReadResult([0x01]) + mock_modbus.read_holding_registers.return_value = ReadResult([0x01]) assert hass.states.get(ENTITY_ID2).state == STATE_OFF await hass.services.async_call( "light", "turn_on", service_data={"entity_id": ENTITY_ID2} ) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID2).state == STATE_ON - mock_pymodbus.read_holding_registers.return_value = ReadResult([0x00]) + mock_modbus.read_holding_registers.return_value = ReadResult([0x00]) await hass.services.async_call( "light", "turn_off", service_data={"entity_id": ENTITY_ID2} ) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID2).state == STATE_OFF - mock_pymodbus.write_register.side_effect = ModbusException("fail write_") + mock_modbus.write_register.side_effect = ModbusException("fail write_") await hass.services.async_call( "light", "turn_on", service_data={"entity_id": ENTITY_ID2} ) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID2).state == STATE_UNAVAILABLE - mock_pymodbus.write_coil.side_effect = ModbusException("fail write_") + mock_modbus.write_coil.side_effect = ModbusException("fail write_") await hass.services.async_call( "light", "turn_off", service_data={"entity_id": ENTITY_ID} ) diff --git a/tests/components/modbus/test_switch.py b/tests/components/modbus/test_switch.py index 15a41956d3fec5..86a26e187428db 100644 --- a/tests/components/modbus/test_switch.py +++ b/tests/components/modbus/test_switch.py @@ -17,7 +17,6 @@ CONF_VERIFY, CONF_WRITE_TYPE, MODBUS_DOMAIN, - TCP, ) from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.const import ( @@ -26,28 +25,18 @@ CONF_COMMAND_ON, CONF_DELAY, CONF_DEVICE_CLASS, - CONF_HOST, CONF_NAME, - CONF_PORT, CONF_SCAN_INTERVAL, CONF_SLAVE, CONF_SWITCHES, - CONF_TYPE, STATE_OFF, STATE_ON, STATE_UNAVAILABLE, ) from homeassistant.core import State -from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -from .conftest import ( - TEST_ENTITY_NAME, - TEST_MODBUS_HOST, - TEST_PORT_TCP, - ReadResult, - do_next_cycle, -) +from .conftest import TEST_ENTITY_NAME, ReadResult, do_next_cycle from tests.common import async_fire_time_changed @@ -280,14 +269,10 @@ async def test_restore_state_switch(hass, mock_test_state, mock_modbus): assert hass.states.get(ENTITY_ID).state == mock_test_state[0].state -async def test_switch_service_turn(hass, caplog, mock_pymodbus): - """Run test for service turn_on/turn_off.""" - - config = { - MODBUS_DOMAIN: { - CONF_TYPE: TCP, - CONF_HOST: TEST_MODBUS_HOST, - CONF_PORT: TEST_PORT_TCP, +@pytest.mark.parametrize( + "do_config", + [ + { CONF_SWITCHES: [ { CONF_NAME: TEST_ENTITY_NAME, @@ -304,9 +289,10 @@ async def test_switch_service_turn(hass, caplog, mock_pymodbus): }, ], }, - } - assert await async_setup_component(hass, MODBUS_DOMAIN, config) is True - await hass.async_block_till_done() + ], +) +async def test_switch_service_turn(hass, caplog, mock_modbus): + """Run test for service turn_on/turn_off.""" assert MODBUS_DOMAIN in hass.config.components assert hass.states.get(ENTITY_ID).state == STATE_OFF @@ -321,27 +307,27 @@ async def test_switch_service_turn(hass, caplog, mock_pymodbus): await hass.async_block_till_done() assert hass.states.get(ENTITY_ID).state == STATE_OFF - mock_pymodbus.read_holding_registers.return_value = ReadResult([0x01]) + mock_modbus.read_holding_registers.return_value = ReadResult([0x01]) assert hass.states.get(ENTITY_ID2).state == STATE_OFF await hass.services.async_call( "switch", "turn_on", service_data={"entity_id": ENTITY_ID2} ) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID2).state == STATE_ON - mock_pymodbus.read_holding_registers.return_value = ReadResult([0x00]) + mock_modbus.read_holding_registers.return_value = ReadResult([0x00]) await hass.services.async_call( "switch", "turn_off", service_data={"entity_id": ENTITY_ID2} ) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID2).state == STATE_OFF - mock_pymodbus.write_register.side_effect = ModbusException("fail write_") + mock_modbus.write_register.side_effect = ModbusException("fail write_") await hass.services.async_call( "switch", "turn_on", service_data={"entity_id": ENTITY_ID2} ) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID2).state == STATE_UNAVAILABLE - mock_pymodbus.write_coil.side_effect = ModbusException("fail write_") + mock_modbus.write_coil.side_effect = ModbusException("fail write_") await hass.services.async_call( "switch", "turn_off", service_data={"entity_id": ENTITY_ID} ) @@ -377,33 +363,28 @@ async def test_service_switch_update(hass, mock_modbus, mock_ha): assert hass.states.get(ENTITY_ID).state == STATE_ON -async def test_delay_switch(hass, mock_pymodbus): +@pytest.mark.parametrize( + "do_config", + [ + { + CONF_SWITCHES: [ + { + CONF_NAME: TEST_ENTITY_NAME, + CONF_ADDRESS: 51, + CONF_SCAN_INTERVAL: 0, + CONF_VERIFY: { + CONF_DELAY: 1, + CONF_INPUT_TYPE: CALL_TYPE_REGISTER_HOLDING, + }, + } + ], + }, + ], +) +async def test_delay_switch(hass, mock_modbus): """Run test for switch verify delay.""" - config = { - MODBUS_DOMAIN: [ - { - CONF_TYPE: TCP, - CONF_HOST: TEST_MODBUS_HOST, - CONF_PORT: TEST_PORT_TCP, - CONF_SWITCHES: [ - { - CONF_NAME: TEST_ENTITY_NAME, - CONF_ADDRESS: 51, - CONF_SCAN_INTERVAL: 0, - CONF_VERIFY: { - CONF_DELAY: 1, - CONF_INPUT_TYPE: CALL_TYPE_REGISTER_HOLDING, - }, - } - ], - } - ] - } - mock_pymodbus.read_holding_registers.return_value = ReadResult([0x01]) + mock_modbus.read_holding_registers.return_value = ReadResult([0x01]) now = dt_util.utcnow() - with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now): - assert await async_setup_component(hass, MODBUS_DOMAIN, config) is True - await hass.async_block_till_done() await hass.services.async_call( "switch", "turn_on", service_data={"entity_id": ENTITY_ID} ) From 3c15fe85873b03f4c1b4167493bc5f7e7766f170 Mon Sep 17 00:00:00 2001 From: Michael Chisholm Date: Sun, 20 Feb 2022 16:07:38 +1100 Subject: [PATCH 0846/1098] Add media browser support to dlna_dmr (#66425) --- homeassistant/components/dlna_dmr/const.py | 5 + .../components/dlna_dmr/manifest.json | 1 + .../components/dlna_dmr/media_player.py | 102 +++++++++-- .../components/dlna_dmr/test_media_player.py | 171 +++++++++++++++++- 4 files changed, 265 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/dlna_dmr/const.py b/homeassistant/components/dlna_dmr/const.py index 20a978f9fda2eb..a4118a0ce78f66 100644 --- a/homeassistant/components/dlna_dmr/const.py +++ b/homeassistant/components/dlna_dmr/const.py @@ -21,6 +21,11 @@ CONNECT_TIMEOUT: Final = 10 +PROTOCOL_HTTP: Final = "http-get" +PROTOCOL_RTSP: Final = "rtsp-rtp-udp" +PROTOCOL_ANY: Final = "*" +STREAMABLE_PROTOCOLS: Final = [PROTOCOL_HTTP, PROTOCOL_RTSP, PROTOCOL_ANY] + # Map UPnP class to media_player media_content_type MEDIA_TYPE_MAP: Mapping[str, str] = { "object": _mp_const.MEDIA_TYPE_URL, diff --git a/homeassistant/components/dlna_dmr/manifest.json b/homeassistant/components/dlna_dmr/manifest.json index 885b7e3c65aa41..4001fc9dddce76 100644 --- a/homeassistant/components/dlna_dmr/manifest.json +++ b/homeassistant/components/dlna_dmr/manifest.json @@ -5,6 +5,7 @@ "documentation": "https://www.home-assistant.io/integrations/dlna_dmr", "requirements": ["async-upnp-client==0.23.5"], "dependencies": ["ssdp"], + "after_dependencies": ["media_source"], "ssdp": [ { "deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1", diff --git a/homeassistant/components/dlna_dmr/media_player.py b/homeassistant/components/dlna_dmr/media_player.py index fd89c5be2d0dce..265c6e9dde6d3f 100644 --- a/homeassistant/components/dlna_dmr/media_player.py +++ b/homeassistant/components/dlna_dmr/media_player.py @@ -13,16 +13,22 @@ from async_upnp_client.exceptions import UpnpError, UpnpResponseError from async_upnp_client.profiles.dlna import DmrDevice, PlayMode, TransportState from async_upnp_client.utils import async_get_local_ip +from didl_lite import didl_lite from typing_extensions import Concatenate, ParamSpec from homeassistant import config_entries -from homeassistant.components import ssdp -from homeassistant.components.media_player import MediaPlayerEntity +from homeassistant.components import media_source, ssdp +from homeassistant.components.media_player import ( + BrowseMedia, + MediaPlayerEntity, + async_process_play_media_url, +) from homeassistant.components.media_player.const import ( ATTR_MEDIA_EXTRA, REPEAT_MODE_ALL, REPEAT_MODE_OFF, REPEAT_MODE_ONE, + SUPPORT_BROWSE_MEDIA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, @@ -61,6 +67,7 @@ MEDIA_UPNP_CLASS_MAP, REPEAT_PLAY_MODES, SHUFFLE_PLAY_MODES, + STREAMABLE_PROTOCOLS, ) from .data import EventListenAddr, get_domain_data @@ -512,7 +519,7 @@ def supported_features(self) -> int: if self._device.can_next: supported_features |= SUPPORT_NEXT_TRACK if self._device.has_play_media: - supported_features |= SUPPORT_PLAY_MEDIA + supported_features |= SUPPORT_PLAY_MEDIA | SUPPORT_BROWSE_MEDIA if self._device.can_seek_rel_time: supported_features |= SUPPORT_SEEK @@ -586,10 +593,30 @@ async def async_play_media( """Play a piece of media.""" _LOGGER.debug("Playing media: %s, %s, %s", media_type, media_id, kwargs) assert self._device is not None + + didl_metadata: str | None = None + title: str = "" + + # If media is media_source, resolve it to url and MIME type, and maybe metadata + if media_source.is_media_source_id(media_id): + sourced_media = await media_source.async_resolve_media(self.hass, media_id) + media_type = sourced_media.mime_type + media_id = sourced_media.url + _LOGGER.debug("sourced_media is %s", sourced_media) + if sourced_metadata := getattr(sourced_media, "didl_metadata", None): + didl_metadata = didl_lite.to_xml_string(sourced_metadata).decode( + "utf-8" + ) + title = sourced_metadata.title + + # If media ID is a relative URL, we serve it from HA. + media_id = async_process_play_media_url(self.hass, media_id) + extra: dict[str, Any] = kwargs.get(ATTR_MEDIA_EXTRA) or {} metadata: dict[str, Any] = extra.get("metadata") or {} - title = extra.get("title") or metadata.get("title") or "Home Assistant" + if not title: + title = extra.get("title") or metadata.get("title") or "Home Assistant" if thumb := extra.get("thumb"): metadata["album_art_uri"] = thumb @@ -598,15 +625,16 @@ async def async_play_media( if hass_key in metadata: metadata[didl_key] = metadata.pop(hass_key) - # Create metadata specific to the given media type; different fields are - # available depending on what the upnp_class is. - upnp_class = MEDIA_UPNP_CLASS_MAP.get(media_type) - didl_metadata = await self._device.construct_play_media_metadata( - media_url=media_id, - media_title=title, - override_upnp_class=upnp_class, - meta_data=metadata, - ) + if not didl_metadata: + # Create metadata specific to the given media type; different fields are + # available depending on what the upnp_class is. + upnp_class = MEDIA_UPNP_CLASS_MAP.get(media_type) + didl_metadata = await self._device.construct_play_media_metadata( + media_url=media_id, + media_title=title, + override_upnp_class=upnp_class, + meta_data=metadata, + ) # Stop current playing media if self._device.can_stop: @@ -726,6 +754,54 @@ async def async_select_sound_mode(self, sound_mode: str) -> None: assert self._device is not None await self._device.async_select_preset(sound_mode) + async def async_browse_media( + self, + media_content_type: str | None = None, + media_content_id: str | None = None, + ) -> BrowseMedia: + """Implement the websocket media browsing helper. + + Browses all available media_sources by default. Filters content_type + based on the DMR's sink_protocol_info. + """ + _LOGGER.debug( + "async_browse_media(%s, %s)", media_content_type, media_content_id + ) + + # media_content_type is ignored; it's the content_type of the current + # media_content_id, not the desired content_type of whomever is calling. + + content_filter = self._get_content_filter() + + return await media_source.async_browse_media( + self.hass, media_content_id, content_filter=content_filter + ) + + def _get_content_filter(self) -> Callable[[BrowseMedia], bool]: + """Return a function that filters media based on what the renderer can play.""" + if not self._device or not self._device.sink_protocol_info: + # Nothing is specified by the renderer, so show everything + _LOGGER.debug("Get content filter with no device or sink protocol info") + return lambda _: True + + _LOGGER.debug("Get content filter for %s", self._device.sink_protocol_info) + if self._device.sink_protocol_info[0] == "*": + # Renderer claims it can handle everything, so show everything + return lambda _: True + + # Convert list of things like "http-get:*:audio/mpeg:*" to just "audio/mpeg" + content_types: list[str] = [] + for protocol_info in self._device.sink_protocol_info: + protocol, _, content_format, _ = protocol_info.split(":", 3) + if protocol in STREAMABLE_PROTOCOLS: + content_types.append(content_format) + + def _content_type_filter(item: BrowseMedia) -> bool: + """Filter media items by their content_type.""" + return item.media_content_type in content_types + + return _content_type_filter + @property def media_title(self) -> str | None: """Title of current playing media.""" diff --git a/tests/components/dlna_dmr/test_media_player.py b/tests/components/dlna_dmr/test_media_player.py index 3cb4b2a726a442..0abda9e1ed3c54 100644 --- a/tests/components/dlna_dmr/test_media_player.py +++ b/tests/components/dlna_dmr/test_media_player.py @@ -3,6 +3,7 @@ import asyncio from collections.abc import AsyncIterable, Mapping +from dataclasses import dataclass from datetime import timedelta from types import MappingProxyType from typing import Any @@ -15,6 +16,7 @@ UpnpResponseError, ) from async_upnp_client.profiles.dlna import PlayMode, TransportState +from didl_lite import didl_lite import pytest from homeassistant import const as ha_const @@ -29,6 +31,8 @@ from homeassistant.components.dlna_dmr.data import EventListenAddr from homeassistant.components.media_player import ATTR_TO_PROPERTY, const as mp_const from homeassistant.components.media_player.const import DOMAIN as MP_DOMAIN +from homeassistant.components.media_source.const import DOMAIN as MS_DOMAIN +from homeassistant.components.media_source.models import PlayMedia from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import async_get as async_get_dr @@ -418,7 +422,7 @@ async def test_feature_flags( ("can_stop", mp_const.SUPPORT_STOP), ("can_previous", mp_const.SUPPORT_PREVIOUS_TRACK), ("can_next", mp_const.SUPPORT_NEXT_TRACK), - ("has_play_media", mp_const.SUPPORT_PLAY_MEDIA), + ("has_play_media", mp_const.SUPPORT_PLAY_MEDIA | mp_const.SUPPORT_BROWSE_MEDIA), ("can_seek_rel_time", mp_const.SUPPORT_SEEK), ("has_presets", mp_const.SUPPORT_SELECT_SOUND_MODE), ] @@ -760,6 +764,89 @@ async def test_play_media_metadata( ) +async def test_play_media_local_source( + hass: HomeAssistant, dmr_device_mock: Mock, mock_entity_id: str +) -> None: + """Test play_media with a media_id from a local media_source.""" + # Based on roku's test_services_play_media_local_source and cast's + # test_entity_browse_media + await async_setup_component(hass, MS_DOMAIN, {MS_DOMAIN: {}}) + await hass.async_block_till_done() + + await hass.services.async_call( + MP_DOMAIN, + mp_const.SERVICE_PLAY_MEDIA, + { + ATTR_ENTITY_ID: mock_entity_id, + mp_const.ATTR_MEDIA_CONTENT_TYPE: "video/mp4", + mp_const.ATTR_MEDIA_CONTENT_ID: "media-source://media_source/local/Epic Sax Guy 10 Hours.mp4", + }, + blocking=True, + ) + + assert dmr_device_mock.construct_play_media_metadata.await_count == 1 + assert ( + "/media/local/Epic%20Sax%20Guy%2010%20Hours.mp4?authSig=" + in dmr_device_mock.construct_play_media_metadata.call_args.kwargs["media_url"] + ) + assert dmr_device_mock.async_set_transport_uri.await_count == 1 + assert dmr_device_mock.async_play.await_count == 1 + call_args = dmr_device_mock.async_set_transport_uri.call_args.args + assert "/media/local/Epic%20Sax%20Guy%2010%20Hours.mp4?authSig=" in call_args[0] + + +async def test_play_media_didl_metadata( + hass: HomeAssistant, dmr_device_mock: Mock, mock_entity_id: str +) -> None: + """Test play_media passes available DIDL-Lite metadata to the DMR.""" + + @dataclass + class DidlPlayMedia(PlayMedia): + """Playable media with DIDL metadata.""" + + didl_metadata: didl_lite.DidlObject + + didl_metadata = didl_lite.VideoItem( + id="120$22$33", + restricted="false", + title="Epic Sax Guy 10 Hours", + res=[ + didl_lite.Resource(uri="unused-URI", protocol_info="http-get:*:video/mp4:") + ], + ) + + play_media = DidlPlayMedia( + url="/media/local/Epic Sax Guy 10 Hours.mp4", + mime_type="video/mp4", + didl_metadata=didl_metadata, + ) + + await async_setup_component(hass, MS_DOMAIN, {MS_DOMAIN: {}}) + await hass.async_block_till_done() + local_source = hass.data[MS_DOMAIN][MS_DOMAIN] + + with patch.object(local_source, "async_resolve_media", return_value=play_media): + + await hass.services.async_call( + MP_DOMAIN, + mp_const.SERVICE_PLAY_MEDIA, + { + ATTR_ENTITY_ID: mock_entity_id, + mp_const.ATTR_MEDIA_CONTENT_TYPE: "video/mp4", + mp_const.ATTR_MEDIA_CONTENT_ID: "media-source://media_source/local/Epic Sax Guy 10 Hours.mp4", + }, + blocking=True, + ) + + assert dmr_device_mock.construct_play_media_metadata.await_count == 0 + assert dmr_device_mock.async_set_transport_uri.await_count == 1 + assert dmr_device_mock.async_play.await_count == 1 + call_args = dmr_device_mock.async_set_transport_uri.call_args.args + assert "/media/local/Epic%20Sax%20Guy%2010%20Hours.mp4?authSig=" in call_args[0] + assert call_args[1] == "Epic Sax Guy 10 Hours" + assert call_args[2] == didl_lite.to_xml_string(didl_metadata).decode() + + async def test_shuffle_repeat_modes( hass: HomeAssistant, dmr_device_mock: Mock, mock_entity_id: str ) -> None: @@ -844,6 +931,88 @@ async def test_shuffle_repeat_modes( dmr_device_mock.async_set_play_mode.assert_not_awaited() +async def test_browse_media( + hass: HomeAssistant, hass_ws_client, dmr_device_mock: Mock, mock_entity_id: str +) -> None: + """Test the async_browse_media method.""" + # Based on cast's test_entity_browse_media + await async_setup_component(hass, MS_DOMAIN, {MS_DOMAIN: {}}) + await hass.async_block_till_done() + + # DMR can play all media types + dmr_device_mock.sink_protocol_info = ["*"] + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "media_player/browse_media", + "entity_id": mock_entity_id, + } + ) + response = await client.receive_json() + assert response["success"] + expected_child_video = { + "title": "Epic Sax Guy 10 Hours.mp4", + "media_class": "video", + "media_content_type": "video/mp4", + "media_content_id": "media-source://media_source/local/Epic Sax Guy 10 Hours.mp4", + "can_play": True, + "can_expand": False, + "children_media_class": None, + "thumbnail": None, + } + assert expected_child_video in response["result"]["children"] + + expected_child_audio = { + "title": "test.mp3", + "media_class": "music", + "media_content_type": "audio/mpeg", + "media_content_id": "media-source://media_source/local/test.mp3", + "can_play": True, + "can_expand": False, + "children_media_class": None, + "thumbnail": None, + } + assert expected_child_audio in response["result"]["children"] + + # Device can only play MIME type audio/mpeg and audio/vorbis + dmr_device_mock.sink_protocol_info = [ + "http-get:*:audio/mpeg:*", + "http-get:*:audio/vorbis:*", + ] + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "media_player/browse_media", + "entity_id": mock_entity_id, + } + ) + response = await client.receive_json() + assert response["success"] + # Video file should not be shown + assert expected_child_video not in response["result"]["children"] + # Audio file should appear + assert expected_child_audio in response["result"]["children"] + + # Device does not specify what it can play + dmr_device_mock.sink_protocol_info = [] + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "media_player/browse_media", + "entity_id": mock_entity_id, + } + ) + response = await client.receive_json() + assert response["success"] + # All files should be returned + assert expected_child_video in response["result"]["children"] + assert expected_child_audio in response["result"]["children"] + + async def test_playback_update_state( hass: HomeAssistant, dmr_device_mock: Mock, mock_entity_id: str ) -> None: From f0fbc7bb2cda244aabe12f93b5c4be86f95f7739 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Sun, 20 Feb 2022 06:07:40 +0100 Subject: [PATCH 0847/1098] Fritz: fix unbound topology (#66877) Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com> Co-authored-by: Paulus Schoutsen --- homeassistant/components/fritz/common.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index d5410bf232cc43..5706ca1948699b 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -331,6 +331,7 @@ def scan_devices(self, now: datetime | None = None) -> None: _LOGGER.debug("Checking host info for FRITZ!Box device %s", self.host) self._update_available, self._latest_firmware = self._update_device_info() + topology: dict = {} if ( "Hosts1" not in self.connection.services or "X_AVM-DE_GetMeshListPath" @@ -372,7 +373,7 @@ def scan_devices(self, now: datetime | None = None) -> None: mesh_intf = {} # first get all meshed devices - for node in topology["nodes"]: + for node in topology.get("nodes", []): if not node["is_meshed"]: continue @@ -389,7 +390,7 @@ def scan_devices(self, now: datetime | None = None) -> None: self.mesh_role = MeshRoles(node["mesh_role"]) # second get all client devices - for node in topology["nodes"]: + for node in topology.get("nodes", []): if node["is_meshed"]: continue From c582aecc10f82c2f528bd8ae630445a07bcfb615 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 20 Feb 2022 06:14:31 +0100 Subject: [PATCH 0848/1098] Deduplicate code in cast media_player (#66815) Co-authored-by: Paulus Schoutsen --- homeassistant/components/cast/helpers.py | 4 +- homeassistant/components/cast/media_player.py | 295 ++++++++---------- tests/components/cast/test_media_player.py | 26 ++ 3 files changed, 151 insertions(+), 174 deletions(-) diff --git a/homeassistant/components/cast/helpers.py b/homeassistant/components/cast/helpers.py index ba7380bcaa2083..ad36fb4e339760 100644 --- a/homeassistant/components/cast/helpers.py +++ b/homeassistant/components/cast/helpers.py @@ -81,8 +81,8 @@ def get_zeroconf(cls): class CastStatusListener: """Helper class to handle pychromecast status callbacks. - Necessary because a CastDevice entity can create a new socket client - and therefore callbacks from multiple chromecast connections can + Necessary because a CastDevice entity or dynamic group can create a new + socket client and therefore callbacks from multiple chromecast connections can potentially arrive. This class allows invalidating past chromecast objects. """ diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index a3b4dea8424e45..bfbe4f84d600d2 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from collections.abc import Callable from contextlib import suppress from datetime import datetime import json @@ -96,7 +97,7 @@ @callback def _async_create_cast_device(hass: HomeAssistant, info: ChromecastInfo): - """Create a CastDevice Entity from the chromecast object. + """Create a CastDevice entity or dynamic group from the chromecast object. Returns None if the cast device has already been added. """ @@ -120,7 +121,7 @@ def _async_create_cast_device(hass: HomeAssistant, info: ChromecastInfo): group.async_setup() return None - return CastDevice(info) + return CastMediaPlayerEntity(hass, info) async def async_setup_entry( @@ -154,63 +155,46 @@ def async_cast_discovered(discover: ChromecastInfo) -> None: hass.async_add_executor_job(setup_internal_discovery, hass, config_entry) -class CastDevice(MediaPlayerEntity): - """Representation of a Cast device on the network. +class CastDevice: + """Representation of a Cast device or dynamic group on the network. This class is the holder of the pychromecast.Chromecast object and its - socket client. It therefore handles all reconnects and audio group changing + socket client. It therefore handles all reconnects and audio groups changing "elected leader" itself. """ - _attr_should_poll = False - _attr_media_image_remotely_accessible = True + _mz_only: bool - def __init__(self, cast_info: ChromecastInfo) -> None: + def __init__(self, hass: HomeAssistant, cast_info: ChromecastInfo) -> None: """Initialize the cast device.""" + self.hass: HomeAssistant = hass self._cast_info = cast_info self._chromecast: pychromecast.Chromecast | None = None - self.cast_status = None - self.media_status = None - self.media_status_received = None - self.mz_media_status: dict[str, pychromecast.controllers.media.MediaStatus] = {} - self.mz_media_status_received: dict[str, datetime] = {} self.mz_mgr = None - self._attr_available = False self._status_listener: CastStatusListener | None = None - self._hass_cast_controller: HomeAssistantController | None = None - - self._add_remove_handler = None - self._cast_view_remove_handler = None - self._attr_unique_id = str(cast_info.uuid) - self._attr_name = cast_info.friendly_name - if cast_info.cast_info.model_name != "Google Cast Group": - self._attr_device_info = DeviceInfo( - identifiers={(CAST_DOMAIN, str(cast_info.uuid).replace("-", ""))}, - manufacturer=str(cast_info.cast_info.manufacturer), - model=cast_info.cast_info.model_name, - name=str(cast_info.friendly_name), - ) + self._add_remove_handler: Callable[[], None] | None = None + self._del_remove_handler: Callable[[], None] | None = None + self._name: str | None = None - async def async_added_to_hass(self): - """Create chromecast object when added to hass.""" + def _async_setup(self, name: str) -> None: + """Create chromecast object.""" + self._name = name self._add_remove_handler = async_dispatcher_connect( self.hass, SIGNAL_CAST_DISCOVERED, self._async_cast_discovered ) + self._del_remove_handler = async_dispatcher_connect( + self.hass, SIGNAL_CAST_REMOVED, self._async_cast_removed + ) self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self._async_stop) - self.async_set_cast_info(self._cast_info) # asyncio.create_task is used to avoid delaying startup wrapup if the device # is discovered already during startup but then fails to respond asyncio.create_task( - async_create_catching_coro(self.async_connect_to_chromecast()) + async_create_catching_coro(self._async_connect_to_chromecast()) ) - self._cast_view_remove_handler = async_dispatcher_connect( - self.hass, SIGNAL_HASS_CAST_SHOW_VIEW, self._handle_signal_show_view - ) - - async def async_will_remove_from_hass(self) -> None: - """Disconnect Chromecast object when removed.""" + async def _async_tear_down(self) -> None: + """Disconnect chromecast object and remove listeners.""" await self._async_disconnect() if self._cast_info.uuid is not None: # Remove the entity from the added casts so that it can dynamically @@ -219,20 +203,15 @@ async def async_will_remove_from_hass(self) -> None: if self._add_remove_handler: self._add_remove_handler() self._add_remove_handler = None - if self._cast_view_remove_handler: - self._cast_view_remove_handler() - self._cast_view_remove_handler = None - - def async_set_cast_info(self, cast_info): - """Set the cast information.""" - self._cast_info = cast_info + if self._del_remove_handler: + self._del_remove_handler() + self._del_remove_handler = None - async def async_connect_to_chromecast(self): + async def _async_connect_to_chromecast(self): """Set up the chromecast object.""" - _LOGGER.debug( "[%s %s] Connecting to cast device by service %s", - self.entity_id, + self._name, self._cast_info.friendly_name, self._cast_info.cast_info.services, ) @@ -248,40 +227,121 @@ async def async_connect_to_chromecast(self): self.mz_mgr = self.hass.data[CAST_MULTIZONE_MANAGER_KEY] - self._status_listener = CastStatusListener(self, chromecast, self.mz_mgr) - self._attr_available = False - self.cast_status = chromecast.status - self.media_status = chromecast.media_controller.status + self._status_listener = CastStatusListener( + self, chromecast, self.mz_mgr, self._mz_only + ) self._chromecast.start() - self.async_write_ha_state() async def _async_disconnect(self): """Disconnect Chromecast object if it is set.""" if self._chromecast is not None: _LOGGER.debug( "[%s %s] Disconnecting from chromecast socket", - self.entity_id, + self._name, self._cast_info.friendly_name, ) await self.hass.async_add_executor_job(self._chromecast.disconnect) - self._attr_available = False self._invalidate() - self.async_write_ha_state() def _invalidate(self): """Invalidate some attributes.""" self._chromecast = None + self.mz_mgr = None + if self._status_listener is not None: + self._status_listener.invalidate() + self._status_listener = None + + async def _async_cast_discovered(self, discover: ChromecastInfo): + """Handle discovery of new Chromecast.""" + if self._cast_info.uuid != discover.uuid: + # Discovered is not our device. + return + + _LOGGER.debug("Discovered chromecast with same UUID: %s", discover) + self._cast_info = discover + + async def _async_cast_removed(self, discover: ChromecastInfo): + """Handle removal of Chromecast.""" + + async def _async_stop(self, event): + """Disconnect socket on Home Assistant stop.""" + await self._async_disconnect() + + +class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity): + """Representation of a Cast device on the network.""" + + _attr_should_poll = False + _attr_media_image_remotely_accessible = True + _mz_only = False + + def __init__(self, hass: HomeAssistant, cast_info: ChromecastInfo) -> None: + """Initialize the cast device.""" + + CastDevice.__init__(self, hass, cast_info) + + self.cast_status = None + self.media_status = None + self.media_status_received = None + self.mz_media_status: dict[str, pychromecast.controllers.media.MediaStatus] = {} + self.mz_media_status_received: dict[str, datetime] = {} + self._attr_available = False + self._hass_cast_controller: HomeAssistantController | None = None + + self._cast_view_remove_handler = None + self._attr_unique_id = str(cast_info.uuid) + self._attr_name = cast_info.friendly_name + if cast_info.cast_info.model_name != "Google Cast Group": + self._attr_device_info = DeviceInfo( + identifiers={(CAST_DOMAIN, str(cast_info.uuid).replace("-", ""))}, + manufacturer=str(cast_info.cast_info.manufacturer), + model=cast_info.cast_info.model_name, + name=str(cast_info.friendly_name), + ) + + async def async_added_to_hass(self): + """Create chromecast object when added to hass.""" + self._async_setup(self.entity_id) + + self._cast_view_remove_handler = async_dispatcher_connect( + self.hass, SIGNAL_HASS_CAST_SHOW_VIEW, self._handle_signal_show_view + ) + + async def async_will_remove_from_hass(self) -> None: + """Disconnect Chromecast object when removed.""" + await self._async_tear_down() + + if self._cast_view_remove_handler: + self._cast_view_remove_handler() + self._cast_view_remove_handler = None + + async def _async_connect_to_chromecast(self): + """Set up the chromecast object.""" + await super()._async_connect_to_chromecast() + + self._attr_available = False + self.cast_status = self._chromecast.status + self.media_status = self._chromecast.media_controller.status + self.async_write_ha_state() + + async def _async_disconnect(self): + """Disconnect Chromecast object if it is set.""" + await super()._async_disconnect() + + self._attr_available = False + self.async_write_ha_state() + + def _invalidate(self): + """Invalidate some attributes.""" + super()._invalidate() + self.cast_status = None self.media_status = None self.media_status_received = None self.mz_media_status = {} self.mz_media_status_received = {} - self.mz_mgr = None self._hass_cast_controller = None - if self._status_listener is not None: - self._status_listener.invalidate() - self._status_listener = None # ========== Callbacks ========== def new_cast_status(self, cast_status): @@ -798,19 +858,6 @@ def media_position_updated_at(self): return None return self._media_status()[1] - async def _async_cast_discovered(self, discover: ChromecastInfo): - """Handle discovery of new Chromecast.""" - if self._cast_info.uuid != discover.uuid: - # Discovered is not our device. - return - - _LOGGER.debug("Discovered chromecast with same UUID: %s", discover) - self.async_set_cast_info(discover) - - async def _async_stop(self, event): - """Disconnect socket on Home Assistant stop.""" - await self._async_disconnect() - def _handle_signal_show_view( self, controller: HomeAssistantController, @@ -829,106 +876,14 @@ def _handle_signal_show_view( self._hass_cast_controller.show_lovelace_view(view_path, url_path) -class DynamicCastGroup: +class DynamicCastGroup(CastDevice): """Representation of a Cast device on the network - for dynamic cast groups.""" - def __init__(self, hass, cast_info: ChromecastInfo): - """Initialize the cast device.""" - - self.hass = hass - self._cast_info = cast_info - self._chromecast: pychromecast.Chromecast | None = None - self.mz_mgr = None - self._status_listener: CastStatusListener | None = None - - self._add_remove_handler = None - self._del_remove_handler = None + _mz_only = True def async_setup(self): """Create chromecast object.""" - self._add_remove_handler = async_dispatcher_connect( - self.hass, SIGNAL_CAST_DISCOVERED, self._async_cast_discovered - ) - self._del_remove_handler = async_dispatcher_connect( - self.hass, SIGNAL_CAST_REMOVED, self._async_cast_removed - ) - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self._async_stop) - self.async_set_cast_info(self._cast_info) - self.hass.async_create_task( - async_create_catching_coro(self.async_connect_to_chromecast()) - ) - - async def async_tear_down(self) -> None: - """Disconnect Chromecast object.""" - await self._async_disconnect() - if self._cast_info.uuid is not None: - # Remove the entity from the added casts so that it can dynamically - # be re-added again. - self.hass.data[ADDED_CAST_DEVICES_KEY].remove(self._cast_info.uuid) - if self._add_remove_handler: - self._add_remove_handler() - self._add_remove_handler = None - if self._del_remove_handler: - self._del_remove_handler() - self._del_remove_handler = None - - def async_set_cast_info(self, cast_info): - """Set the cast information and set up the chromecast object.""" - - self._cast_info = cast_info - - async def async_connect_to_chromecast(self): - """Set the cast information and set up the chromecast object.""" - - _LOGGER.debug( - "[%s %s] Connecting to cast device by service %s", - "Dynamic group", - self._cast_info.friendly_name, - self._cast_info.cast_info.services, - ) - chromecast = await self.hass.async_add_executor_job( - pychromecast.get_chromecast_from_cast_info, - self._cast_info.cast_info, - ChromeCastZeroconf.get_zeroconf(), - ) - self._chromecast = chromecast - - if CAST_MULTIZONE_MANAGER_KEY not in self.hass.data: - self.hass.data[CAST_MULTIZONE_MANAGER_KEY] = MultizoneManager() - - self.mz_mgr = self.hass.data[CAST_MULTIZONE_MANAGER_KEY] - - self._status_listener = CastStatusListener(self, chromecast, self.mz_mgr, True) - self._chromecast.start() - - async def _async_disconnect(self): - """Disconnect Chromecast object if it is set.""" - if self._chromecast is not None: - _LOGGER.debug( - "[%s %s] Disconnecting from chromecast socket", - "Dynamic group", - self._cast_info.friendly_name, - ) - await self.hass.async_add_executor_job(self._chromecast.disconnect) - - self._invalidate() - - def _invalidate(self): - """Invalidate some attributes.""" - self._chromecast = None - self.mz_mgr = None - if self._status_listener is not None: - self._status_listener.invalidate() - self._status_listener = None - - async def _async_cast_discovered(self, discover: ChromecastInfo): - """Handle discovery of new Chromecast.""" - if self._cast_info.uuid != discover.uuid: - # Discovered is not our device. - return - - _LOGGER.debug("Discovered dynamic group with same UUID: %s", discover) - self.async_set_cast_info(discover) + self._async_setup("Dynamic group") async def _async_cast_removed(self, discover: ChromecastInfo): """Handle removal of Chromecast.""" @@ -939,8 +894,4 @@ async def _async_cast_removed(self, discover: ChromecastInfo): if not discover.cast_info.services: # Clean up the dynamic group _LOGGER.debug("Clean up dynamic group: %s", discover) - await self.async_tear_down() - - async def _async_stop(self, event): - """Disconnect socket on Home Assistant stop.""" - await self._async_disconnect() + await self._async_tear_down() diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 1c2da93f0a175c..2299389f12b51d 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -2,6 +2,7 @@ # pylint: disable=protected-access from __future__ import annotations +import asyncio import json from unittest.mock import ANY, AsyncMock, MagicMock, Mock, patch from uuid import UUID @@ -469,10 +470,19 @@ async def test_discover_dynamic_group( hass ) + tasks = [] + real_create_task = asyncio.create_task + + def create_task(*args, **kwargs): + tasks.append(real_create_task(*args, **kwargs)) + # Discover cast service with patch( "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", return_value=zconf_1, + ), patch( + "homeassistant.components.cast.media_player.asyncio.create_task", + wraps=create_task, ): discover_cast( pychromecast.discovery.ServiceInfo( @@ -482,6 +492,10 @@ async def test_discover_dynamic_group( ) await hass.async_block_till_done() await hass.async_block_till_done() # having tasks that add jobs + + assert len(tasks) == 1 + await asyncio.gather(*tasks) + tasks.clear() get_chromecast_mock.assert_called() get_chromecast_mock.reset_mock() assert add_dev1.call_count == 0 @@ -491,6 +505,9 @@ async def test_discover_dynamic_group( with patch( "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", return_value=zconf_2, + ), patch( + "homeassistant.components.cast.media_player.asyncio.create_task", + wraps=create_task, ): discover_cast( pychromecast.discovery.ServiceInfo( @@ -500,6 +517,10 @@ async def test_discover_dynamic_group( ) await hass.async_block_till_done() await hass.async_block_till_done() # having tasks that add jobs + + assert len(tasks) == 1 + await asyncio.gather(*tasks) + tasks.clear() get_chromecast_mock.assert_called() get_chromecast_mock.reset_mock() assert add_dev1.call_count == 0 @@ -509,6 +530,9 @@ async def test_discover_dynamic_group( with patch( "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", return_value=zconf_1, + ), patch( + "homeassistant.components.cast.media_player.asyncio.create_task", + wraps=create_task, ): discover_cast( pychromecast.discovery.ServiceInfo( @@ -518,6 +542,8 @@ async def test_discover_dynamic_group( ) await hass.async_block_till_done() await hass.async_block_till_done() # having tasks that add jobs + + assert len(tasks) == 0 get_chromecast_mock.assert_not_called() assert add_dev1.call_count == 0 assert reg.async_get_entity_id("media_player", "cast", cast_1.uuid) is None From 10ad97a5e22b08dfd0ac10bea0230624c7ea6497 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Sun, 20 Feb 2022 00:15:54 -0500 Subject: [PATCH 0849/1098] Improve zwave_js notification event handling (#66790) --- homeassistant/components/zwave_js/__init__.py | 21 +++++++- homeassistant/components/zwave_js/const.py | 3 ++ tests/components/zwave_js/test_events.py | 48 +++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 1682edb66a305b..5a0a7bbf29ff71 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -12,6 +12,7 @@ from zwave_js_server.model.notification import ( EntryControlNotification, NotificationNotification, + PowerLevelNotification, ) from zwave_js_server.model.value import Value, ValueNotification @@ -41,6 +42,7 @@ from .addon import AddonError, AddonManager, AddonState, get_addon_manager from .api import async_register_api from .const import ( + ATTR_ACKNOWLEDGED_FRAMES, ATTR_COMMAND_CLASS, ATTR_COMMAND_CLASS_NAME, ATTR_DATA_TYPE, @@ -57,6 +59,8 @@ ATTR_PROPERTY_KEY, ATTR_PROPERTY_KEY_NAME, ATTR_PROPERTY_NAME, + ATTR_STATUS, + ATTR_TEST_NODE_ID, ATTR_TYPE, ATTR_VALUE, ATTR_VALUE_RAW, @@ -392,7 +396,9 @@ def async_on_value_notification(notification: ValueNotification) -> None: @callback def async_on_notification( - notification: EntryControlNotification | NotificationNotification, + notification: EntryControlNotification + | NotificationNotification + | PowerLevelNotification, ) -> None: """Relay stateless notification events from Z-Wave nodes to hass.""" device = dev_reg.async_get_device({get_device_id(client, notification.node)}) @@ -415,7 +421,7 @@ def async_on_notification( ATTR_EVENT_DATA: notification.event_data, } ) - else: + elif isinstance(notification, NotificationNotification): event_data.update( { ATTR_COMMAND_CLASS_NAME: "Notification", @@ -426,6 +432,17 @@ def async_on_notification( ATTR_PARAMETERS: notification.parameters, } ) + elif isinstance(notification, PowerLevelNotification): + event_data.update( + { + ATTR_COMMAND_CLASS_NAME: "Power Level", + ATTR_TEST_NODE_ID: notification.test_node_id, + ATTR_STATUS: notification.status, + ATTR_ACKNOWLEDGED_FRAMES: notification.acknowledged_frames, + } + ) + else: + raise TypeError(f"Unhandled notification type: {notification}") hass.bus.async_fire(ZWAVE_JS_NOTIFICATION_EVENT, event_data) diff --git a/homeassistant/components/zwave_js/const.py b/homeassistant/components/zwave_js/const.py index 2d16c0113c926b..bd46497f62924d 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -56,6 +56,9 @@ ATTR_DATA_TYPE = "data_type" ATTR_WAIT_FOR_RESULT = "wait_for_result" ATTR_OPTIONS = "options" +ATTR_TEST_NODE_ID = "test_node_id" +ATTR_STATUS = "status" +ATTR_ACKNOWLEDGED_FRAMES = "acknowledged_frames" ATTR_NODE = "node" ATTR_ZWAVE_VALUE = "zwave_value" diff --git a/tests/components/zwave_js/test_events.py b/tests/components/zwave_js/test_events.py index d66cd52be40212..32859ae3c37dbd 100644 --- a/tests/components/zwave_js/test_events.py +++ b/tests/components/zwave_js/test_events.py @@ -1,4 +1,7 @@ """Test Z-Wave JS (value notification) events.""" +from unittest.mock import AsyncMock + +import pytest from zwave_js_server.const import CommandClass from zwave_js_server.event import Event @@ -259,3 +262,48 @@ async def test_value_updated(hass, vision_security_zl7432, integration, client): await hass.async_block_till_done() # We should only still have captured one event assert len(events) == 1 + + +async def test_power_level_notification(hass, hank_binary_switch, integration, client): + """Test power level notification events.""" + # just pick a random node to fake the notification event + node = hank_binary_switch + events = async_capture_events(hass, "zwave_js_notification") + + event = Event( + type="notification", + data={ + "source": "node", + "event": "notification", + "nodeId": 7, + "ccId": 115, + "args": { + "commandClassName": "Powerlevel", + "commandClass": 115, + "testNodeId": 1, + "status": 0, + "acknowledgedFrames": 2, + }, + }, + ) + node.receive_event(event) + await hass.async_block_till_done() + assert len(events) == 1 + assert events[0].data["command_class_name"] == "Power Level" + assert events[0].data["command_class"] == 115 + assert events[0].data["test_node_id"] == 1 + assert events[0].data["status"] == 0 + assert events[0].data["acknowledged_frames"] == 2 + + +async def test_unknown_notification(hass, hank_binary_switch, integration, client): + """Test behavior of unknown notification type events.""" + # just pick a random node to fake the notification event + node = hank_binary_switch + + # We emit the event directly so we can skip any validation and event handling + # by the lib. We will use a class that is guaranteed not to be recognized + notification_obj = AsyncMock() + notification_obj.node = node + with pytest.raises(TypeError): + node.emit("notification", {"notification": notification_obj}) From 833cba71d717509e53e9bfee176e4dd3cd21a00f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 19 Feb 2022 22:13:52 -0800 Subject: [PATCH 0850/1098] Bump frontend to 20220220.0 (#66919) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 654f8f2ee1b2bd..f596a64e5b1a29 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20220214.0" + "home-assistant-frontend==20220220.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index cf95644867bf18..1b065833adcfdb 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ ciso8601==2.2.0 cryptography==35.0.0 emoji==1.6.3 hass-nabucasa==0.53.1 -home-assistant-frontend==20220214.0 +home-assistant-frontend==20220220.0 httpx==0.21.3 ifaddr==0.1.7 jinja2==3.0.3 diff --git a/requirements_all.txt b/requirements_all.txt index 8796d7556f55ae..3867c326759d0d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -839,7 +839,7 @@ hole==0.7.0 holidays==0.13 # homeassistant.components.frontend -home-assistant-frontend==20220214.0 +home-assistant-frontend==20220220.0 # homeassistant.components.zwave # homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6399e9611f22f1..b37bb1cfd1232c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -552,7 +552,7 @@ hole==0.7.0 holidays==0.13 # homeassistant.components.frontend -home-assistant-frontend==20220214.0 +home-assistant-frontend==20220220.0 # homeassistant.components.zwave # homeassistant-pyozw==0.1.10 From 48dd77f3416f16bdece092bc8a5164de297540a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Sun, 20 Feb 2022 11:25:04 +0100 Subject: [PATCH 0851/1098] Bump aiogithubapi from 22.2.0 to 22.2.3 (#66924) --- homeassistant/components/github/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/github/manifest.json b/homeassistant/components/github/manifest.json index 79e792aa7d87e0..196095a5b6e154 100644 --- a/homeassistant/components/github/manifest.json +++ b/homeassistant/components/github/manifest.json @@ -3,7 +3,7 @@ "name": "GitHub", "documentation": "https://www.home-assistant.io/integrations/github", "requirements": [ - "aiogithubapi==22.2.0" + "aiogithubapi==22.2.3" ], "codeowners": [ "@timmo001", diff --git a/requirements_all.txt b/requirements_all.txt index 3867c326759d0d..a5bd4ad853cc3b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -175,7 +175,7 @@ aioflo==2021.11.0 aioftp==0.12.0 # homeassistant.components.github -aiogithubapi==22.2.0 +aiogithubapi==22.2.3 # homeassistant.components.guardian aioguardian==2021.11.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b37bb1cfd1232c..5c70c20bf3348f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -125,7 +125,7 @@ aioesphomeapi==10.8.2 aioflo==2021.11.0 # homeassistant.components.github -aiogithubapi==22.2.0 +aiogithubapi==22.2.3 # homeassistant.components.guardian aioguardian==2021.11.0 From 94a0f1c7b3ace35ed102136f6cc3b7c396efee65 Mon Sep 17 00:00:00 2001 From: Hans Oischinger Date: Sun, 20 Feb 2022 11:47:05 +0100 Subject: [PATCH 0852/1098] Add service configuration URL for vicare (#66927) --- homeassistant/components/vicare/binary_sensor.py | 1 + homeassistant/components/vicare/button.py | 1 + homeassistant/components/vicare/climate.py | 1 + homeassistant/components/vicare/sensor.py | 1 + homeassistant/components/vicare/water_heater.py | 1 + 5 files changed, 5 insertions(+) diff --git a/homeassistant/components/vicare/binary_sensor.py b/homeassistant/components/vicare/binary_sensor.py index 543b5116760975..01cfff593573da 100644 --- a/homeassistant/components/vicare/binary_sensor.py +++ b/homeassistant/components/vicare/binary_sensor.py @@ -205,6 +205,7 @@ def device_info(self): "name": self._device_config.getModel(), "manufacturer": "Viessmann", "model": (DOMAIN, self._device_config.getModel()), + "configuration_url": "https://developer.viessmann.com/", } @property diff --git a/homeassistant/components/vicare/button.py b/homeassistant/components/vicare/button.py index e924a735e0f7f8..e1d6bc4223c4fb 100644 --- a/homeassistant/components/vicare/button.py +++ b/homeassistant/components/vicare/button.py @@ -101,6 +101,7 @@ def device_info(self): "name": self._device_config.getModel(), "manufacturer": "Viessmann", "model": (DOMAIN, self._device_config.getModel()), + "configuration_url": "https://developer.viessmann.com/", } @property diff --git a/homeassistant/components/vicare/climate.py b/homeassistant/components/vicare/climate.py index 64df7260c5be08..8320db73ad4eaf 100644 --- a/homeassistant/components/vicare/climate.py +++ b/homeassistant/components/vicare/climate.py @@ -174,6 +174,7 @@ def device_info(self): "name": self._device_config.getModel(), "manufacturer": "Viessmann", "model": (DOMAIN, self._device_config.getModel()), + "configuration_url": "https://developer.viessmann.com/", } def update(self): diff --git a/homeassistant/components/vicare/sensor.py b/homeassistant/components/vicare/sensor.py index 6f1b6097c4fe99..249cadaee866ff 100644 --- a/homeassistant/components/vicare/sensor.py +++ b/homeassistant/components/vicare/sensor.py @@ -467,6 +467,7 @@ def device_info(self): "name": self._device_config.getModel(), "manufacturer": "Viessmann", "model": (DOMAIN, self._device_config.getModel()), + "configuration_url": "https://developer.viessmann.com/", } @property diff --git a/homeassistant/components/vicare/water_heater.py b/homeassistant/components/vicare/water_heater.py index 8a7169333bfe9a..139c8f75e7f934 100644 --- a/homeassistant/components/vicare/water_heater.py +++ b/homeassistant/components/vicare/water_heater.py @@ -147,6 +147,7 @@ def device_info(self): "name": self._device_config.getModel(), "manufacturer": "Viessmann", "model": (DOMAIN, self._device_config.getModel()), + "configuration_url": "https://developer.viessmann.com/", } @property From ddedaf6f7032df128a16895a7a9f49852a780c12 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 20 Feb 2022 11:47:36 +0100 Subject: [PATCH 0853/1098] Introduce const file in LaMetric (#66929) --- homeassistant/components/lametric/__init__.py | 17 +++----- homeassistant/components/lametric/const.py | 16 +++++++ homeassistant/components/lametric/notify.py | 43 +++++++++---------- 3 files changed, 42 insertions(+), 34 deletions(-) create mode 100644 homeassistant/components/lametric/const.py diff --git a/homeassistant/components/lametric/__init__.py b/homeassistant/components/lametric/__init__.py index 1c84c4f551047e..970bdd4b3b69c1 100644 --- a/homeassistant/components/lametric/__init__.py +++ b/homeassistant/components/lametric/__init__.py @@ -1,6 +1,4 @@ """Support for LaMetric time.""" -import logging - from lmnotify import LaMetricManager import voluptuous as vol @@ -9,12 +7,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType -_LOGGER = logging.getLogger(__name__) - - -DOMAIN = "lametric" - -LAMETRIC_DEVICES = "LAMETRIC_DEVICES" +from .const import DOMAIN, LOGGER CONFIG_SCHEMA = vol.Schema( { @@ -31,18 +24,18 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the LaMetricManager.""" - _LOGGER.debug("Setting up LaMetric platform") + LOGGER.debug("Setting up LaMetric platform") conf = config[DOMAIN] hlmn = HassLaMetricManager( client_id=conf[CONF_CLIENT_ID], client_secret=conf[CONF_CLIENT_SECRET] ) if not (devices := hlmn.manager.get_devices()): - _LOGGER.error("No LaMetric devices found") + LOGGER.error("No LaMetric devices found") return False hass.data[DOMAIN] = hlmn for dev in devices: - _LOGGER.debug("Discovered LaMetric device: %s", dev) + LOGGER.debug("Discovered LaMetric device: %s", dev) return True @@ -53,7 +46,7 @@ class HassLaMetricManager: def __init__(self, client_id: str, client_secret: str) -> None: """Initialize HassLaMetricManager and connect to LaMetric.""" - _LOGGER.debug("Connecting to LaMetric") + LOGGER.debug("Connecting to LaMetric") self.manager = LaMetricManager(client_id, client_secret) self._client_id = client_id self._client_secret = client_secret diff --git a/homeassistant/components/lametric/const.py b/homeassistant/components/lametric/const.py new file mode 100644 index 00000000000000..85e61cd8d9ae3d --- /dev/null +++ b/homeassistant/components/lametric/const.py @@ -0,0 +1,16 @@ +"""Constants for the LaMetric integration.""" + +import logging +from typing import Final + +DOMAIN: Final = "lametric" + +LOGGER = logging.getLogger(__package__) + +AVAILABLE_PRIORITIES: Final = ["info", "warning", "critical"] +AVAILABLE_ICON_TYPES: Final = ["none", "info", "alert"] + +CONF_CYCLES: Final = "cycles" +CONF_LIFETIME: Final = "lifetime" +CONF_PRIORITY: Final = "priority" +CONF_ICON_TYPE: Final = "icon_type" diff --git a/homeassistant/components/lametric/notify.py b/homeassistant/components/lametric/notify.py index 034deb911d1647..f3c098a841e6a8 100644 --- a/homeassistant/components/lametric/notify.py +++ b/homeassistant/components/lametric/notify.py @@ -1,7 +1,6 @@ """Support for LaMetric notifications.""" from __future__ import annotations -import logging from typing import Any from lmnotify import Model, SimpleFrame, Sound @@ -20,17 +19,17 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import DOMAIN, HassLaMetricManager - -_LOGGER = logging.getLogger(__name__) - -AVAILABLE_PRIORITIES = ["info", "warning", "critical"] -AVAILABLE_ICON_TYPES = ["none", "info", "alert"] - -CONF_CYCLES = "cycles" -CONF_LIFETIME = "lifetime" -CONF_PRIORITY = "priority" -CONF_ICON_TYPE = "icon_type" +from . import HassLaMetricManager +from .const import ( + AVAILABLE_ICON_TYPES, + AVAILABLE_PRIORITIES, + CONF_CYCLES, + CONF_ICON_TYPE, + CONF_LIFETIME, + CONF_PRIORITY, + DOMAIN, + LOGGER, +) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -85,7 +84,7 @@ def send_message(self, message: str = "", **kwargs: Any) -> None: targets = kwargs.get(ATTR_TARGET) data = kwargs.get(ATTR_DATA) - _LOGGER.debug("Targets/Data: %s/%s", targets, data) + LOGGER.debug("Targets/Data: %s/%s", targets, data) icon = self._icon cycles = self._cycles sound = None @@ -99,16 +98,16 @@ def send_message(self, message: str = "", **kwargs: Any) -> None: if "sound" in data: try: sound = Sound(category="notifications", sound_id=data["sound"]) - _LOGGER.debug("Adding notification sound %s", data["sound"]) + LOGGER.debug("Adding notification sound %s", data["sound"]) except AssertionError: - _LOGGER.error("Sound ID %s unknown, ignoring", data["sound"]) + LOGGER.error("Sound ID %s unknown, ignoring", data["sound"]) if "cycles" in data: cycles = int(data["cycles"]) if "icon_type" in data: if data["icon_type"] in AVAILABLE_ICON_TYPES: icon_type = data["icon_type"] else: - _LOGGER.warning( + LOGGER.warning( "Priority %s invalid, using default %s", data["priority"], priority, @@ -117,13 +116,13 @@ def send_message(self, message: str = "", **kwargs: Any) -> None: if data["priority"] in AVAILABLE_PRIORITIES: priority = data["priority"] else: - _LOGGER.warning( + LOGGER.warning( "Priority %s invalid, using default %s", data["priority"], priority, ) text_frame = SimpleFrame(icon, message) - _LOGGER.debug( + LOGGER.debug( "Icon/Message/Cycles/Lifetime: %s, %s, %d, %d", icon, message, @@ -138,11 +137,11 @@ def send_message(self, message: str = "", **kwargs: Any) -> None: try: self._devices = lmn.get_devices() except TokenExpiredError: - _LOGGER.debug("Token expired, fetching new token") + LOGGER.debug("Token expired, fetching new token") lmn.get_token() self._devices = lmn.get_devices() except RequestsConnectionError: - _LOGGER.warning( + LOGGER.warning( "Problem connecting to LaMetric, using cached devices instead" ) for dev in self._devices: @@ -155,6 +154,6 @@ def send_message(self, message: str = "", **kwargs: Any) -> None: priority=priority, icon_type=icon_type, ) - _LOGGER.debug("Sent notification to LaMetric %s", dev["name"]) + LOGGER.debug("Sent notification to LaMetric %s", dev["name"]) except OSError: - _LOGGER.warning("Cannot connect to LaMetric %s", dev["name"]) + LOGGER.warning("Cannot connect to LaMetric %s", dev["name"]) From 6e5ae3e2e444d985218e3fed89f1c26e779e6dba Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Sun, 20 Feb 2022 05:53:03 -0500 Subject: [PATCH 0854/1098] Add zwave_js.event automation trigger (#62828) Co-authored-by: Martin Hjelmare --- homeassistant/components/zwave_js/const.py | 3 + homeassistant/components/zwave_js/trigger.py | 3 +- .../components/zwave_js/triggers/event.py | 232 ++++++++++ .../zwave_js/triggers/value_updated.py | 2 +- tests/components/zwave_js/test_trigger.py | 434 ++++++++++++++++++ 5 files changed, 672 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/zwave_js/triggers/event.py diff --git a/homeassistant/components/zwave_js/const.py b/homeassistant/components/zwave_js/const.py index bd46497f62924d..8f6fada2106ac2 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -69,6 +69,9 @@ ATTR_CURRENT_VALUE = "current_value" ATTR_CURRENT_VALUE_RAW = "current_value_raw" ATTR_DESCRIPTION = "description" +ATTR_EVENT_SOURCE = "event_source" +ATTR_CONFIG_ENTRY_ID = "config_entry_id" +ATTR_PARTIAL_DICT_MATCH = "partial_dict_match" # service constants SERVICE_SET_LOCK_USERCODE = "set_lock_usercode" diff --git a/homeassistant/components/zwave_js/trigger.py b/homeassistant/components/zwave_js/trigger.py index ca9bd7d24a2971..07f89388e678b3 100644 --- a/homeassistant/components/zwave_js/trigger.py +++ b/homeassistant/components/zwave_js/trigger.py @@ -12,10 +12,11 @@ from homeassistant.core import CALLBACK_TYPE, HomeAssistant from homeassistant.helpers.typing import ConfigType -from .triggers import value_updated +from .triggers import event, value_updated TRIGGERS = { "value_updated": value_updated, + "event": event, } diff --git a/homeassistant/components/zwave_js/triggers/event.py b/homeassistant/components/zwave_js/triggers/event.py new file mode 100644 index 00000000000000..110cd21294f10f --- /dev/null +++ b/homeassistant/components/zwave_js/triggers/event.py @@ -0,0 +1,232 @@ +"""Offer Z-Wave JS event listening automation trigger.""" +from __future__ import annotations + +import functools + +from pydantic import ValidationError +import voluptuous as vol +from zwave_js_server.client import Client +from zwave_js_server.model.controller import CONTROLLER_EVENT_MODEL_MAP +from zwave_js_server.model.driver import DRIVER_EVENT_MODEL_MAP +from zwave_js_server.model.node import NODE_EVENT_MODEL_MAP, Node + +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) +from homeassistant.components.zwave_js.const import ( + ATTR_CONFIG_ENTRY_ID, + ATTR_EVENT, + ATTR_EVENT_DATA, + ATTR_EVENT_SOURCE, + ATTR_NODE_ID, + ATTR_PARTIAL_DICT_MATCH, + DATA_CLIENT, + DOMAIN, +) +from homeassistant.components.zwave_js.helpers import ( + async_get_node_from_device_id, + async_get_node_from_entity_id, + get_device_id, + get_home_and_node_id_from_device_entry, +) +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import ATTR_DEVICE_ID, ATTR_ENTITY_ID, CONF_PLATFORM +from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback +from homeassistant.helpers import config_validation as cv, device_registry as dr +from homeassistant.helpers.typing import ConfigType + +# Platform type should be . +PLATFORM_TYPE = f"{DOMAIN}.{__name__.rsplit('.', maxsplit=1)[-1]}" + +EVENT_MODEL_MAP = { + "controller": CONTROLLER_EVENT_MODEL_MAP, + "driver": DRIVER_EVENT_MODEL_MAP, + "node": NODE_EVENT_MODEL_MAP, +} + + +def validate_non_node_event_source(obj: dict) -> dict: + """Validate that a trigger for a non node event source has a config entry.""" + if obj[ATTR_EVENT_SOURCE] != "node" and ATTR_CONFIG_ENTRY_ID in obj: + return obj + raise vol.Invalid(f"Non node event triggers must contain {ATTR_CONFIG_ENTRY_ID}.") + + +def validate_event_name(obj: dict) -> dict: + """Validate that a trigger has a valid event name.""" + event_source = obj[ATTR_EVENT_SOURCE] + event_name = obj[ATTR_EVENT] + # the keys to the event source's model map are the event names + vol.In(EVENT_MODEL_MAP[event_source])(event_name) + return obj + + +def validate_event_data(obj: dict) -> dict: + """Validate that a trigger has a valid event data.""" + # Return if there's no event data to validate + if ATTR_EVENT_DATA not in obj: + return obj + + event_source = obj[ATTR_EVENT_SOURCE] + event_name = obj[ATTR_EVENT] + event_data = obj[ATTR_EVENT_DATA] + try: + EVENT_MODEL_MAP[event_source][event_name](**event_data) + except ValidationError as exc: + # Filter out required field errors if keys can be missing, and if there are + # still errors, raise an exception + if errors := [ + error for error in exc.errors() if error["type"] != "value_error.missing" + ]: + raise vol.MultipleInvalid(errors) from exc + return obj + + +TRIGGER_SCHEMA = vol.All( + cv.TRIGGER_BASE_SCHEMA.extend( + { + vol.Required(CONF_PLATFORM): PLATFORM_TYPE, + vol.Optional(ATTR_CONFIG_ENTRY_ID): str, + vol.Optional(ATTR_DEVICE_ID): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_EVENT_SOURCE): vol.In(EVENT_MODEL_MAP), + vol.Required(ATTR_EVENT): cv.string, + vol.Optional(ATTR_EVENT_DATA): dict, + vol.Optional(ATTR_PARTIAL_DICT_MATCH, default=False): bool, + }, + ), + validate_event_name, + validate_event_data, + vol.Any( + validate_non_node_event_source, + cv.has_at_least_one_key(ATTR_DEVICE_ID, ATTR_ENTITY_ID), + ), +) + + +async def async_validate_trigger_config( + hass: HomeAssistant, config: ConfigType +) -> ConfigType: + """Validate config.""" + config = TRIGGER_SCHEMA(config) + + if ATTR_CONFIG_ENTRY_ID not in config: + return config + + entry_id = config[ATTR_CONFIG_ENTRY_ID] + if (entry := hass.config_entries.async_get_entry(entry_id)) is None: + raise vol.Invalid(f"Config entry '{entry_id}' not found") + + if entry.state is not ConfigEntryState.LOADED: + raise vol.Invalid(f"Config entry '{entry_id}' not loaded") + + return config + + +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, + *, + platform_type: str = PLATFORM_TYPE, +) -> CALLBACK_TYPE: + """Listen for state changes based on configuration.""" + nodes: set[Node] = set() + if ATTR_DEVICE_ID in config: + nodes.update( + { + async_get_node_from_device_id(hass, device_id) + for device_id in config[ATTR_DEVICE_ID] + } + ) + if ATTR_ENTITY_ID in config: + nodes.update( + { + async_get_node_from_entity_id(hass, entity_id) + for entity_id in config[ATTR_ENTITY_ID] + } + ) + + event_source = config[ATTR_EVENT_SOURCE] + event_name = config[ATTR_EVENT] + event_data_filter = config.get(ATTR_EVENT_DATA, {}) + + unsubs = [] + job = HassJob(action) + + trigger_data = automation_info["trigger_data"] + + @callback + def async_on_event(event_data: dict, device: dr.DeviceEntry | None = None) -> None: + """Handle event.""" + for key, val in event_data_filter.items(): + if key not in event_data: + return + if ( + config[ATTR_PARTIAL_DICT_MATCH] + and isinstance(event_data[key], dict) + and isinstance(event_data_filter[key], dict) + ): + for key2, val2 in event_data_filter[key].items(): + if key2 not in event_data[key] or event_data[key][key2] != val2: + return + continue + if event_data[key] != val: + return + + payload = { + **trigger_data, + CONF_PLATFORM: platform_type, + ATTR_EVENT_SOURCE: event_source, + ATTR_EVENT: event_name, + ATTR_EVENT_DATA: event_data, + } + + primary_desc = f"Z-Wave JS '{event_source}' event '{event_name}' was emitted" + + if device: + device_name = device.name_by_user or device.name + payload[ATTR_DEVICE_ID] = device.id + home_and_node_id = get_home_and_node_id_from_device_entry(device) + assert home_and_node_id + payload[ATTR_NODE_ID] = home_and_node_id[1] + payload["description"] = f"{primary_desc} on {device_name}" + else: + payload["description"] = primary_desc + + payload[ + "description" + ] = f"{payload['description']} with event data: {event_data}" + + hass.async_run_hass_job(job, {"trigger": payload}) + + dev_reg = dr.async_get(hass) + + if not nodes: + entry_id = config[ATTR_CONFIG_ENTRY_ID] + client: Client = hass.data[DOMAIN][entry_id][DATA_CLIENT] + if event_source == "controller": + source = client.driver.controller + else: + source = client.driver + unsubs.append(source.on(event_name, async_on_event)) + + for node in nodes: + device_identifier = get_device_id(node.client, node) + device = dev_reg.async_get_device({device_identifier}) + assert device + # We need to store the device for the callback + unsubs.append( + node.on(event_name, functools.partial(async_on_event, device=device)) + ) + + @callback + def async_remove() -> None: + """Remove state listeners async.""" + for unsub in unsubs: + unsub() + unsubs.clear() + + return async_remove diff --git a/homeassistant/components/zwave_js/triggers/value_updated.py b/homeassistant/components/zwave_js/triggers/value_updated.py index 7ebdb4f3748d40..71223c4ef1ed7f 100644 --- a/homeassistant/components/zwave_js/triggers/value_updated.py +++ b/homeassistant/components/zwave_js/triggers/value_updated.py @@ -1,4 +1,4 @@ -"""Offer Z-Wave JS value updated listening automation rules.""" +"""Offer Z-Wave JS value updated listening automation trigger.""" from __future__ import annotations import functools diff --git a/tests/components/zwave_js/test_trigger.py b/tests/components/zwave_js/test_trigger.py index 33f6205c7b9867..5dbeff87a541a2 100644 --- a/tests/components/zwave_js/test_trigger.py +++ b/tests/components/zwave_js/test_trigger.py @@ -264,6 +264,440 @@ def clear_events(): await hass.services.async_call(automation.DOMAIN, SERVICE_RELOAD, blocking=True) +async def test_zwave_js_event(hass, client, lock_schlage_be469, integration): + """Test for zwave_js.event automation trigger.""" + trigger_type = f"{DOMAIN}.event" + node: Node = lock_schlage_be469 + dev_reg = async_get_dev_reg(hass) + device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + + node_no_event_data_filter = async_capture_events(hass, "node_no_event_data_filter") + node_event_data_filter = async_capture_events(hass, "node_event_data_filter") + controller_no_event_data_filter = async_capture_events( + hass, "controller_no_event_data_filter" + ) + controller_event_data_filter = async_capture_events( + hass, "controller_event_data_filter" + ) + driver_no_event_data_filter = async_capture_events( + hass, "driver_no_event_data_filter" + ) + driver_event_data_filter = async_capture_events(hass, "driver_event_data_filter") + node_event_data_no_partial_dict_match_filter = async_capture_events( + hass, "node_event_data_no_partial_dict_match_filter" + ) + node_event_data_partial_dict_match_filter = async_capture_events( + hass, "node_event_data_partial_dict_match_filter" + ) + + def clear_events(): + """Clear all events in the event list.""" + node_no_event_data_filter.clear() + node_event_data_filter.clear() + controller_no_event_data_filter.clear() + controller_event_data_filter.clear() + driver_no_event_data_filter.clear() + driver_event_data_filter.clear() + node_event_data_no_partial_dict_match_filter.clear() + node_event_data_partial_dict_match_filter.clear() + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + # node filter: no event data + { + "trigger": { + "platform": trigger_type, + "entity_id": SCHLAGE_BE469_LOCK_ENTITY, + "event_source": "node", + "event": "interview stage completed", + }, + "action": { + "event": "node_no_event_data_filter", + }, + }, + # node filter: event data + { + "trigger": { + "platform": trigger_type, + "device_id": device.id, + "event_source": "node", + "event": "interview stage completed", + "event_data": {"stageName": "ProtocolInfo"}, + }, + "action": { + "event": "node_event_data_filter", + }, + }, + # controller filter: no event data + { + "trigger": { + "platform": trigger_type, + "config_entry_id": integration.entry_id, + "event_source": "controller", + "event": "inclusion started", + }, + "action": { + "event": "controller_no_event_data_filter", + }, + }, + # controller filter: event data + { + "trigger": { + "platform": trigger_type, + "config_entry_id": integration.entry_id, + "event_source": "controller", + "event": "inclusion started", + "event_data": {"secure": True}, + }, + "action": { + "event": "controller_event_data_filter", + }, + }, + # driver filter: no event data + { + "trigger": { + "platform": trigger_type, + "config_entry_id": integration.entry_id, + "event_source": "driver", + "event": "logging", + }, + "action": { + "event": "driver_no_event_data_filter", + }, + }, + # driver filter: event data + { + "trigger": { + "platform": trigger_type, + "config_entry_id": integration.entry_id, + "event_source": "driver", + "event": "logging", + "event_data": {"message": "test"}, + }, + "action": { + "event": "driver_event_data_filter", + }, + }, + # node filter: event data, no partial dict match + { + "trigger": { + "platform": trigger_type, + "entity_id": SCHLAGE_BE469_LOCK_ENTITY, + "event_source": "node", + "event": "value updated", + "event_data": {"args": {"commandClassName": "Door Lock"}}, + }, + "action": { + "event": "node_event_data_no_partial_dict_match_filter", + }, + }, + # node filter: event data, partial dict match + { + "trigger": { + "platform": trigger_type, + "entity_id": SCHLAGE_BE469_LOCK_ENTITY, + "event_source": "node", + "event": "value updated", + "event_data": {"args": {"commandClassName": "Door Lock"}}, + "partial_dict_match": True, + }, + "action": { + "event": "node_event_data_partial_dict_match_filter", + }, + }, + ] + }, + ) + + # Test that `node no event data filter` is triggered and `node event data filter` is not + event = Event( + type="interview stage completed", + data={ + "source": "node", + "event": "interview stage completed", + "stageName": "NodeInfo", + "nodeId": node.node_id, + }, + ) + node.receive_event(event) + await hass.async_block_till_done() + + assert len(node_no_event_data_filter) == 1 + assert len(node_event_data_filter) == 0 + assert len(controller_no_event_data_filter) == 0 + assert len(controller_event_data_filter) == 0 + assert len(driver_no_event_data_filter) == 0 + assert len(driver_event_data_filter) == 0 + assert len(node_event_data_no_partial_dict_match_filter) == 0 + assert len(node_event_data_partial_dict_match_filter) == 0 + + clear_events() + + # Test that `node no event data filter` and `node event data filter` are triggered + event = Event( + type="interview stage completed", + data={ + "source": "node", + "event": "interview stage completed", + "stageName": "ProtocolInfo", + "nodeId": node.node_id, + }, + ) + node.receive_event(event) + await hass.async_block_till_done() + + assert len(node_no_event_data_filter) == 1 + assert len(node_event_data_filter) == 1 + assert len(controller_no_event_data_filter) == 0 + assert len(controller_event_data_filter) == 0 + assert len(driver_no_event_data_filter) == 0 + assert len(driver_event_data_filter) == 0 + assert len(node_event_data_no_partial_dict_match_filter) == 0 + assert len(node_event_data_partial_dict_match_filter) == 0 + + clear_events() + + # Test that `controller no event data filter` is triggered and `controller event data filter` is not + event = Event( + type="inclusion started", + data={ + "source": "controller", + "event": "inclusion started", + "secure": False, + }, + ) + client.driver.controller.receive_event(event) + await hass.async_block_till_done() + + assert len(node_no_event_data_filter) == 0 + assert len(node_event_data_filter) == 0 + assert len(controller_no_event_data_filter) == 1 + assert len(controller_event_data_filter) == 0 + assert len(driver_no_event_data_filter) == 0 + assert len(driver_event_data_filter) == 0 + assert len(node_event_data_no_partial_dict_match_filter) == 0 + assert len(node_event_data_partial_dict_match_filter) == 0 + + clear_events() + + # Test that both `controller no event data filter` and `controller event data filter` are triggered + event = Event( + type="inclusion started", + data={ + "source": "controller", + "event": "inclusion started", + "secure": True, + }, + ) + client.driver.controller.receive_event(event) + await hass.async_block_till_done() + + assert len(node_no_event_data_filter) == 0 + assert len(node_event_data_filter) == 0 + assert len(controller_no_event_data_filter) == 1 + assert len(controller_event_data_filter) == 1 + assert len(driver_no_event_data_filter) == 0 + assert len(driver_event_data_filter) == 0 + assert len(node_event_data_no_partial_dict_match_filter) == 0 + assert len(node_event_data_partial_dict_match_filter) == 0 + + clear_events() + + # Test that `driver no event data filter` is triggered and `driver event data filter` is not + event = Event( + type="logging", + data={ + "source": "driver", + "event": "logging", + "message": "no test", + "formattedMessage": "test", + "direction": ">", + "level": "debug", + "primaryTags": "tag", + "secondaryTags": "tag2", + "secondaryTagPadding": 0, + "multiline": False, + "timestamp": "time", + "label": "label", + }, + ) + client.driver.receive_event(event) + await hass.async_block_till_done() + + assert len(node_no_event_data_filter) == 0 + assert len(node_event_data_filter) == 0 + assert len(controller_no_event_data_filter) == 0 + assert len(controller_event_data_filter) == 0 + assert len(driver_no_event_data_filter) == 1 + assert len(driver_event_data_filter) == 0 + assert len(node_event_data_no_partial_dict_match_filter) == 0 + assert len(node_event_data_partial_dict_match_filter) == 0 + + clear_events() + + # Test that both `driver no event data filter` and `driver event data filter` are triggered + event = Event( + type="logging", + data={ + "source": "driver", + "event": "logging", + "message": "test", + "formattedMessage": "test", + "direction": ">", + "level": "debug", + "primaryTags": "tag", + "secondaryTags": "tag2", + "secondaryTagPadding": 0, + "multiline": False, + "timestamp": "time", + "label": "label", + }, + ) + client.driver.receive_event(event) + await hass.async_block_till_done() + + assert len(node_no_event_data_filter) == 0 + assert len(node_event_data_filter) == 0 + assert len(controller_no_event_data_filter) == 0 + assert len(controller_event_data_filter) == 0 + assert len(driver_no_event_data_filter) == 1 + assert len(driver_event_data_filter) == 1 + assert len(node_event_data_no_partial_dict_match_filter) == 0 + assert len(node_event_data_partial_dict_match_filter) == 0 + + clear_events() + + # Test that only `node with event data and partial match dict filter` is triggered + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": node.node_id, + "args": { + "commandClassName": "Door Lock", + "commandClass": 49, + "endpoint": 0, + "property": "latchStatus", + "newValue": "closed", + "prevValue": "open", + "propertyName": "latchStatus", + }, + }, + ) + node.receive_event(event) + await hass.async_block_till_done() + + assert len(node_no_event_data_filter) == 0 + assert len(node_event_data_filter) == 0 + assert len(controller_no_event_data_filter) == 0 + assert len(controller_event_data_filter) == 0 + assert len(driver_no_event_data_filter) == 0 + assert len(driver_event_data_filter) == 0 + assert len(node_event_data_no_partial_dict_match_filter) == 0 + assert len(node_event_data_partial_dict_match_filter) == 1 + + clear_events() + + # Test that `node with event data and partial match dict filter` is not triggered + # when partial dict doesn't match + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": node.node_id, + "args": { + "commandClassName": "fake command class name", + "commandClass": 49, + "endpoint": 0, + "property": "latchStatus", + "newValue": "closed", + "prevValue": "open", + "propertyName": "latchStatus", + }, + }, + ) + node.receive_event(event) + await hass.async_block_till_done() + + assert len(node_no_event_data_filter) == 0 + assert len(node_event_data_filter) == 0 + assert len(controller_no_event_data_filter) == 0 + assert len(controller_event_data_filter) == 0 + assert len(driver_no_event_data_filter) == 0 + assert len(driver_event_data_filter) == 0 + assert len(node_event_data_no_partial_dict_match_filter) == 0 + assert len(node_event_data_partial_dict_match_filter) == 0 + + clear_events() + + with patch("homeassistant.config.load_yaml", return_value={}): + await hass.services.async_call(automation.DOMAIN, SERVICE_RELOAD, blocking=True) + + +async def test_zwave_js_event_invalid_config_entry_id( + hass, client, integration, caplog +): + """Test zwave_js.event automation trigger fails when config entry ID is invalid.""" + trigger_type = f"{DOMAIN}.event" + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": trigger_type, + "config_entry_id": "not_real_entry_id", + "event_source": "controller", + "event": "inclusion started", + }, + "action": { + "event": "node_no_event_data_filter", + }, + } + ] + }, + ) + + assert "Config entry 'not_real_entry_id' not found" in caplog.text + caplog.clear() + + +async def test_zwave_js_event_unloaded_config_entry(hass, client, integration, caplog): + """Test zwave_js.event automation trigger fails when config entry is unloaded.""" + trigger_type = f"{DOMAIN}.event" + + await hass.config_entries.async_unload(integration.entry_id) + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": trigger_type, + "config_entry_id": integration.entry_id, + "event_source": "controller", + "event": "inclusion started", + }, + "action": { + "event": "node_no_event_data_filter", + }, + } + ] + }, + ) + + assert f"Config entry '{integration.entry_id}' not loaded" in caplog.text + + async def test_async_validate_trigger_config(hass): """Test async_validate_trigger_config.""" mock_platform = AsyncMock() From 4ca339c5b1b5315c855ccd79385d812d9179894f Mon Sep 17 00:00:00 2001 From: jan iversen Date: Sun, 20 Feb 2022 11:56:38 +0100 Subject: [PATCH 0855/1098] Set slave default to 0, as already documented in Modbus (#66921) --- homeassistant/components/modbus/__init__.py | 2 +- homeassistant/components/modbus/validators.py | 3 +-- tests/components/modbus/conftest.py | 9 ++++++--- tests/components/modbus/test_init.py | 10 ++++++++++ 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/modbus/__init__.py b/homeassistant/components/modbus/__init__.py index 56edf39311c49d..4d33e819a8f034 100644 --- a/homeassistant/components/modbus/__init__.py +++ b/homeassistant/components/modbus/__init__.py @@ -118,7 +118,7 @@ { vol.Required(CONF_NAME): cv.string, vol.Required(CONF_ADDRESS): cv.positive_int, - vol.Optional(CONF_SLAVE): cv.positive_int, + vol.Optional(CONF_SLAVE, default=0): cv.positive_int, vol.Optional( CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL ): cv.positive_int, diff --git a/homeassistant/components/modbus/validators.py b/homeassistant/components/modbus/validators.py index 74cd2a498613d2..4f910043d12c0f 100644 --- a/homeassistant/components/modbus/validators.py +++ b/homeassistant/components/modbus/validators.py @@ -209,8 +209,7 @@ def duplicate_entity_validator(config: dict) -> dict: addr += "_" + str(entry[CONF_COMMAND_ON]) if CONF_COMMAND_OFF in entry: addr += "_" + str(entry[CONF_COMMAND_OFF]) - if CONF_SLAVE in entry: - addr += "_" + str(entry[CONF_SLAVE]) + addr += "_" + str(entry[CONF_SLAVE]) if addr in addresses: err = f"Modbus {component}/{name} address {addr} is duplicate, second entry not loaded!" _LOGGER.warning(err) diff --git a/tests/components/modbus/conftest.py b/tests/components/modbus/conftest.py index 91327b4e2a23a8..75d4d6099c94ad 100644 --- a/tests/components/modbus/conftest.py +++ b/tests/components/modbus/conftest.py @@ -9,7 +9,7 @@ import pytest from homeassistant.components.modbus.const import MODBUS_DOMAIN as DOMAIN, TCP -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, CONF_TYPE +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, CONF_SLAVE, CONF_TYPE from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -82,9 +82,12 @@ async def mock_modbus_fixture( ): """Load integration modbus using mocked pymodbus.""" conf = copy.deepcopy(do_config) - if config_addon: - for key in conf.keys(): + for key in conf.keys(): + if config_addon: conf[key][0].update(config_addon) + for entity in conf[key]: + if CONF_SLAVE not in entity: + entity[CONF_SLAVE] = 0 caplog.set_level(logging.WARNING) config = { DOMAIN: [ diff --git a/tests/components/modbus/test_init.py b/tests/components/modbus/test_init.py index 7d9ab3e3471867..c9beba694e89d6 100644 --- a/tests/components/modbus/test_init.py +++ b/tests/components/modbus/test_init.py @@ -76,6 +76,7 @@ CONF_PORT, CONF_SCAN_INTERVAL, CONF_SENSORS, + CONF_SLAVE, CONF_STRUCTURE, CONF_TIMEOUT, CONF_TYPE, @@ -272,10 +273,12 @@ async def test_duplicate_modbus_validator(do_config): { CONF_NAME: TEST_ENTITY_NAME, CONF_ADDRESS: 117, + CONF_SLAVE: 0, }, { CONF_NAME: TEST_ENTITY_NAME, CONF_ADDRESS: 119, + CONF_SLAVE: 0, }, ], } @@ -290,10 +293,12 @@ async def test_duplicate_modbus_validator(do_config): { CONF_NAME: TEST_ENTITY_NAME, CONF_ADDRESS: 117, + CONF_SLAVE: 0, }, { CONF_NAME: TEST_ENTITY_NAME + "2", CONF_ADDRESS: 117, + CONF_SLAVE: 0, }, ], } @@ -409,6 +414,7 @@ async def test_duplicate_entity_validator(do_config): { CONF_NAME: TEST_ENTITY_NAME, CONF_ADDRESS: 117, + CONF_SLAVE: 0, CONF_SCAN_INTERVAL: 0, } ], @@ -544,6 +550,7 @@ async def mock_modbus_read_pymodbus_fixture( CONF_INPUT_TYPE: do_type, CONF_NAME: TEST_ENTITY_NAME, CONF_ADDRESS: 51, + CONF_SLAVE: 0, CONF_SCAN_INTERVAL: do_scan_interval, } ], @@ -688,6 +695,7 @@ async def test_delay(hass, mock_pymodbus): CONF_INPUT_TYPE: CALL_TYPE_COIL, CONF_NAME: TEST_ENTITY_NAME, CONF_ADDRESS: 52, + CONF_SLAVE: 0, CONF_SCAN_INTERVAL: set_scan_interval, }, ], @@ -736,6 +744,7 @@ async def test_delay(hass, mock_pymodbus): { CONF_NAME: TEST_ENTITY_NAME, CONF_ADDRESS: 117, + CONF_SLAVE: 0, CONF_SCAN_INTERVAL: 0, } ], @@ -759,6 +768,7 @@ async def test_shutdown(hass, caplog, mock_pymodbus, mock_modbus_with_pymodbus): { CONF_NAME: TEST_ENTITY_NAME, CONF_ADDRESS: 51, + CONF_SLAVE: 0, } ] }, From 9f57ce504bc1299d34a2149b2e8a5c4ef2c93ea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Sun, 20 Feb 2022 11:59:11 +0100 Subject: [PATCH 0856/1098] Use GraphQL for GitHub integration (#66928) --- .pre-commit-config.yaml | 2 +- homeassistant/components/github/__init__.py | 35 +- homeassistant/components/github/const.py | 12 - .../components/github/coordinator.py | 243 +++----- .../components/github/diagnostics.py | 9 +- homeassistant/components/github/sensor.py | 86 ++- tests/components/github/common.py | 11 +- tests/components/github/fixtures/commits.json | 80 --- tests/components/github/fixtures/graphql.json | 49 ++ tests/components/github/fixtures/issues.json | 159 ------ tests/components/github/fixtures/pulls.json | 520 ------------------ .../components/github/fixtures/releases.json | 76 --- tests/components/github/test_diagnostics.py | 16 +- tests/components/github/test_sensor.py | 57 +- 14 files changed, 206 insertions(+), 1149 deletions(-) delete mode 100644 tests/components/github/fixtures/commits.json create mode 100644 tests/components/github/fixtures/graphql.json delete mode 100644 tests/components/github/fixtures/issues.json delete mode 100644 tests/components/github/fixtures/pulls.json delete mode 100644 tests/components/github/fixtures/releases.json diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f16a65fb4c0784..7cdd28cd6e97f0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: hooks: - id: codespell args: - - --ignore-words-list=hass,alot,datas,dof,dur,ether,farenheit,hist,iff,iif,ines,ist,lightsensor,mut,nd,pres,referer,rime,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing,iam,incomfort,ba,haa + - --ignore-words-list=hass,alot,datas,dof,dur,ether,farenheit,hist,iff,iif,ines,ist,lightsensor,mut,nd,pres,referer,rime,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing,iam,incomfort,ba,haa,pullrequests - --skip="./.*,*.csv,*.json" - --quiet-level=2 exclude_types: [csv, json] diff --git a/homeassistant/components/github/__init__.py b/homeassistant/components/github/__init__.py index ae13e8df9590e8..4ecff6e9648b8a 100644 --- a/homeassistant/components/github/__init__.py +++ b/homeassistant/components/github/__init__.py @@ -13,13 +13,7 @@ ) from .const import CONF_REPOSITORIES, DOMAIN, LOGGER -from .coordinator import ( - DataUpdateCoordinators, - RepositoryCommitDataUpdateCoordinator, - RepositoryInformationDataUpdateCoordinator, - RepositoryIssueDataUpdateCoordinator, - RepositoryReleaseDataUpdateCoordinator, -) +from .coordinator import GitHubDataUpdateCoordinator PLATFORMS: list[Platform] = [Platform.SENSOR] @@ -37,24 +31,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: repositories: list[str] = entry.options[CONF_REPOSITORIES] for repository in repositories: - coordinators: DataUpdateCoordinators = { - "information": RepositoryInformationDataUpdateCoordinator( - hass=hass, entry=entry, client=client, repository=repository - ), - "release": RepositoryReleaseDataUpdateCoordinator( - hass=hass, entry=entry, client=client, repository=repository - ), - "issue": RepositoryIssueDataUpdateCoordinator( - hass=hass, entry=entry, client=client, repository=repository - ), - "commit": RepositoryCommitDataUpdateCoordinator( - hass=hass, entry=entry, client=client, repository=repository - ), - } - - await coordinators["information"].async_config_entry_first_refresh() - - hass.data[DOMAIN][repository] = coordinators + coordinator = GitHubDataUpdateCoordinator( + hass=hass, + client=client, + repository=repository, + ) + + await coordinator.async_config_entry_first_refresh() + + hass.data[DOMAIN][repository] = coordinator async_cleanup_device_registry(hass=hass, entry=entry) diff --git a/homeassistant/components/github/const.py b/homeassistant/components/github/const.py index 7a0c471ab036ff..efe9d7baa5e626 100644 --- a/homeassistant/components/github/const.py +++ b/homeassistant/components/github/const.py @@ -3,9 +3,6 @@ from datetime import timedelta from logging import Logger, getLogger -from typing import NamedTuple - -from aiogithubapi import GitHubIssueModel LOGGER: Logger = getLogger(__package__) @@ -18,12 +15,3 @@ CONF_ACCESS_TOKEN = "access_token" CONF_REPOSITORIES = "repositories" - - -class IssuesPulls(NamedTuple): - """Issues and pull requests.""" - - issues_count: int - issue_last: GitHubIssueModel | None - pulls_count: int - pull_last: GitHubIssueModel | None diff --git a/homeassistant/components/github/coordinator.py b/homeassistant/components/github/coordinator.py index 32f0df98e1f467..9769c944f16218 100644 --- a/homeassistant/components/github/coordinator.py +++ b/homeassistant/components/github/coordinator.py @@ -1,44 +1,92 @@ -"""Custom data update coordinators for the GitHub integration.""" +"""Custom data update coordinator for the GitHub integration.""" from __future__ import annotations -from typing import Literal, TypedDict +from typing import Any from aiogithubapi import ( GitHubAPI, - GitHubCommitModel, GitHubConnectionException, GitHubException, - GitHubNotModifiedException, GitHubRatelimitException, - GitHubReleaseModel, - GitHubRepositoryModel, GitHubResponseModel, ) -from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant, T +from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import DEFAULT_UPDATE_INTERVAL, DOMAIN, LOGGER, IssuesPulls - -CoordinatorKeyType = Literal["information", "release", "issue", "commit"] - - -class GitHubBaseDataUpdateCoordinator(DataUpdateCoordinator[T]): - """Base class for GitHub data update coordinators.""" +from .const import DEFAULT_UPDATE_INTERVAL, DOMAIN, LOGGER + +GRAPHQL_REPOSITORY_QUERY = """ +query ($owner: String!, $repository: String!) { + rateLimit { + cost + remaining + } + repository(owner: $owner, name: $repository) { + default_branch_ref: defaultBranchRef { + commit: target { + ... on Commit { + message: messageHeadline + url + sha: oid + } + } + } + stargazers_count: stargazerCount + forks_count: forkCount + full_name: nameWithOwner + id: databaseId + watchers(first: 1) { + total: totalCount + } + issue: issues( + first: 1 + states: OPEN + orderBy: {field: CREATED_AT, direction: DESC} + ) { + total: totalCount + issues: nodes { + title + url + number + } + } + pull_request: pullRequests( + first: 1 + states: OPEN + orderBy: {field: CREATED_AT, direction: DESC} + ) { + total: totalCount + pull_requests: nodes { + title + url + number + } + } + release: latestRelease { + name + url + tag: tagName + } + } +} +""" + + +class GitHubDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): + """Data update coordinator for the GitHub integration.""" def __init__( self, hass: HomeAssistant, - entry: ConfigEntry, client: GitHubAPI, repository: str, ) -> None: """Initialize GitHub data update coordinator base class.""" - self.config_entry = entry self.repository = repository self._client = client - self._last_response: GitHubResponseModel[T] | None = None + self._last_response: GitHubResponseModel[dict[str, Any]] | None = None + self.data = {} super().__init__( hass, @@ -47,30 +95,14 @@ def __init__( update_interval=DEFAULT_UPDATE_INTERVAL, ) - @property - def _etag(self) -> str: - """Return the ETag of the last response.""" - return self._last_response.etag if self._last_response is not None else None - - async def fetch_data(self) -> GitHubResponseModel[T]: - """Fetch data from GitHub API.""" - - @staticmethod - def _parse_response(response: GitHubResponseModel[T]) -> T: - """Parse the response from GitHub API.""" - return response.data - - async def _async_update_data(self) -> T: + async def _async_update_data(self) -> GitHubResponseModel[dict[str, Any]]: + """Update data.""" + owner, repository = self.repository.split("/") try: - response = await self.fetch_data() - except GitHubNotModifiedException: - LOGGER.debug( - "Content for %s with %s not modified", - self.repository, - self.__class__.__name__, + response = await self._client.graphql( + query=GRAPHQL_REPOSITORY_QUERY, + variables={"owner": owner, "repository": repository}, ) - # Return the last known data if the request result was not modified - return self.data except (GitHubConnectionException, GitHubRatelimitException) as exception: # These are expected and we dont log anything extra raise UpdateFailed(exception) from exception @@ -80,133 +112,4 @@ async def _async_update_data(self) -> T: raise UpdateFailed(exception) from exception else: self._last_response = response - return self._parse_response(response) - - -class RepositoryInformationDataUpdateCoordinator( - GitHubBaseDataUpdateCoordinator[GitHubRepositoryModel] -): - """Data update coordinator for repository information.""" - - async def fetch_data(self) -> GitHubResponseModel[GitHubRepositoryModel]: - """Get the latest data from GitHub.""" - return await self._client.repos.get(self.repository, **{"etag": self._etag}) - - -class RepositoryReleaseDataUpdateCoordinator( - GitHubBaseDataUpdateCoordinator[GitHubReleaseModel] -): - """Data update coordinator for repository release.""" - - @staticmethod - def _parse_response( - response: GitHubResponseModel[GitHubReleaseModel | None], - ) -> GitHubReleaseModel | None: - """Parse the response from GitHub API.""" - if not response.data: - return None - - for release in response.data: - if not release.prerelease and not release.draft: - return release - - # Fall back to the latest release if no non-prerelease release is found - return response.data[0] - - async def fetch_data(self) -> GitHubReleaseModel | None: - """Get the latest data from GitHub.""" - return await self._client.repos.releases.list( - self.repository, **{"etag": self._etag} - ) - - -class RepositoryIssueDataUpdateCoordinator( - GitHubBaseDataUpdateCoordinator[IssuesPulls] -): - """Data update coordinator for repository issues.""" - - _issue_etag: str | None = None - _pull_etag: str | None = None - - @staticmethod - def _parse_response(response: IssuesPulls) -> IssuesPulls: - """Parse the response from GitHub API.""" - return response - - async def fetch_data(self) -> IssuesPulls: - """Get the latest data from GitHub.""" - pulls_count = 0 - pull_last = None - issues_count = 0 - issue_last = None - try: - pull_response = await self._client.repos.pulls.list( - self.repository, - **{"params": {"per_page": 1}, "etag": self._pull_etag}, - ) - except GitHubNotModifiedException: - # Return the last known data if the request result was not modified - pulls_count = self.data.pulls_count - pull_last = self.data.pull_last - else: - self._pull_etag = pull_response.etag - pulls_count = pull_response.last_page_number or len(pull_response.data) - pull_last = pull_response.data[0] if pull_response.data else None - - try: - issue_response = await self._client.repos.issues.list( - self.repository, - **{"params": {"per_page": 1}, "etag": self._issue_etag}, - ) - except GitHubNotModifiedException: - # Return the last known data if the request result was not modified - issues_count = self.data.issues_count - issue_last = self.data.issue_last - else: - self._issue_etag = issue_response.etag - issues_count = ( - issue_response.last_page_number or len(issue_response.data) - ) - pulls_count - issue_last = issue_response.data[0] if issue_response.data else None - - if issue_last is not None and issue_last.pull_request: - issue_response = await self._client.repos.issues.list(self.repository) - for issue in issue_response.data: - if not issue.pull_request: - issue_last = issue - break - - return IssuesPulls( - issues_count=issues_count, - issue_last=issue_last, - pulls_count=pulls_count, - pull_last=pull_last, - ) - - -class RepositoryCommitDataUpdateCoordinator( - GitHubBaseDataUpdateCoordinator[GitHubCommitModel] -): - """Data update coordinator for repository commit.""" - - @staticmethod - def _parse_response( - response: GitHubResponseModel[GitHubCommitModel | None], - ) -> GitHubCommitModel | None: - """Parse the response from GitHub API.""" - return response.data[0] if response.data else None - - async def fetch_data(self) -> GitHubCommitModel | None: - """Get the latest data from GitHub.""" - return await self._client.repos.list_commits( - self.repository, **{"params": {"per_page": 1}, "etag": self._etag} - ) - - -class DataUpdateCoordinators(TypedDict): - """Custom data update coordinators for the GitHub integration.""" - - information: RepositoryInformationDataUpdateCoordinator - release: RepositoryReleaseDataUpdateCoordinator - issue: RepositoryIssueDataUpdateCoordinator - commit: RepositoryCommitDataUpdateCoordinator + return response.data["data"]["repository"] diff --git a/homeassistant/components/github/diagnostics.py b/homeassistant/components/github/diagnostics.py index 101bf642c91b90..c2546d636b8281 100644 --- a/homeassistant/components/github/diagnostics.py +++ b/homeassistant/components/github/diagnostics.py @@ -13,7 +13,7 @@ ) from .const import CONF_ACCESS_TOKEN, DOMAIN -from .coordinator import DataUpdateCoordinators +from .coordinator import GitHubDataUpdateCoordinator async def async_get_config_entry_diagnostics( @@ -35,11 +35,10 @@ async def async_get_config_entry_diagnostics( else: data["rate_limit"] = rate_limit_response.data.as_dict - repositories: dict[str, DataUpdateCoordinators] = hass.data[DOMAIN] + repositories: dict[str, GitHubDataUpdateCoordinator] = hass.data[DOMAIN] data["repositories"] = {} - for repository, coordinators in repositories.items(): - info = coordinators["information"].data - data["repositories"][repository] = info.as_dict if info else None + for repository, coordinator in repositories.items(): + data["repositories"][repository] = coordinator.data return data diff --git a/homeassistant/components/github/sensor.py b/homeassistant/components/github/sensor.py index 18aaa43d18d5bc..7fad114a2ae610 100644 --- a/homeassistant/components/github/sensor.py +++ b/homeassistant/components/github/sensor.py @@ -19,19 +19,14 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN -from .coordinator import ( - CoordinatorKeyType, - DataUpdateCoordinators, - GitHubBaseDataUpdateCoordinator, -) +from .coordinator import GitHubDataUpdateCoordinator @dataclass class BaseEntityDescriptionMixin: """Mixin for required GitHub base description keys.""" - coordinator_key: CoordinatorKeyType - value_fn: Callable[[Any], StateType] + value_fn: Callable[[dict[str, Any]], StateType] @dataclass @@ -40,8 +35,8 @@ class BaseEntityDescription(SensorEntityDescription): icon: str = "mdi:github" entity_registry_enabled_default: bool = False - attr_fn: Callable[[Any], Mapping[str, Any] | None] = lambda data: None - avabl_fn: Callable[[Any], bool] = lambda data: True + attr_fn: Callable[[dict[str, Any]], Mapping[str, Any] | None] = lambda data: None + avabl_fn: Callable[[dict[str, Any]], bool] = lambda data: True @dataclass @@ -57,8 +52,7 @@ class GitHubSensorEntityDescription(BaseEntityDescription, BaseEntityDescription native_unit_of_measurement="Stars", entity_category=EntityCategory.DIAGNOSTIC, state_class=SensorStateClass.MEASUREMENT, - value_fn=lambda data: data.stargazers_count, - coordinator_key="information", + value_fn=lambda data: data["stargazers_count"], ), GitHubSensorEntityDescription( key="subscribers_count", @@ -67,9 +61,7 @@ class GitHubSensorEntityDescription(BaseEntityDescription, BaseEntityDescription native_unit_of_measurement="Watchers", entity_category=EntityCategory.DIAGNOSTIC, state_class=SensorStateClass.MEASUREMENT, - # The API returns a watcher_count, but subscribers_count is more accurate - value_fn=lambda data: data.subscribers_count, - coordinator_key="information", + value_fn=lambda data: data["watchers"]["total"], ), GitHubSensorEntityDescription( key="forks_count", @@ -78,8 +70,7 @@ class GitHubSensorEntityDescription(BaseEntityDescription, BaseEntityDescription native_unit_of_measurement="Forks", entity_category=EntityCategory.DIAGNOSTIC, state_class=SensorStateClass.MEASUREMENT, - value_fn=lambda data: data.forks_count, - coordinator_key="information", + value_fn=lambda data: data["forks_count"], ), GitHubSensorEntityDescription( key="issues_count", @@ -87,8 +78,7 @@ class GitHubSensorEntityDescription(BaseEntityDescription, BaseEntityDescription native_unit_of_measurement="Issues", entity_category=EntityCategory.DIAGNOSTIC, state_class=SensorStateClass.MEASUREMENT, - value_fn=lambda data: data.issues_count, - coordinator_key="issue", + value_fn=lambda data: data["issue"]["total"], ), GitHubSensorEntityDescription( key="pulls_count", @@ -96,50 +86,46 @@ class GitHubSensorEntityDescription(BaseEntityDescription, BaseEntityDescription native_unit_of_measurement="Pull Requests", entity_category=EntityCategory.DIAGNOSTIC, state_class=SensorStateClass.MEASUREMENT, - value_fn=lambda data: data.pulls_count, - coordinator_key="issue", + value_fn=lambda data: data["pull_request"]["total"], ), GitHubSensorEntityDescription( - coordinator_key="commit", key="latest_commit", name="Latest Commit", - value_fn=lambda data: data.commit.message.splitlines()[0][:255], + value_fn=lambda data: data["default_branch_ref"]["commit"]["message"][:255], attr_fn=lambda data: { - "sha": data.sha, - "url": data.html_url, + "sha": data["default_branch_ref"]["commit"]["sha"], + "url": data["default_branch_ref"]["commit"]["url"], }, ), GitHubSensorEntityDescription( - coordinator_key="release", key="latest_release", name="Latest Release", entity_registry_enabled_default=True, - value_fn=lambda data: data.name[:255], + avabl_fn=lambda data: data["release"] is not None, + value_fn=lambda data: data["release"]["name"][:255], attr_fn=lambda data: { - "url": data.html_url, - "tag": data.tag_name, + "url": data["release"]["url"], + "tag": data["release"]["tag"], }, ), GitHubSensorEntityDescription( - coordinator_key="issue", key="latest_issue", name="Latest Issue", - value_fn=lambda data: data.issue_last.title[:255], - avabl_fn=lambda data: data.issue_last is not None, + avabl_fn=lambda data: data["issue"]["issues"], + value_fn=lambda data: data["issue"]["issues"][0]["title"][:255], attr_fn=lambda data: { - "url": data.issue_last.html_url, - "number": data.issue_last.number, + "url": data["issue"]["issues"][0]["url"], + "number": data["issue"]["issues"][0]["number"], }, ), GitHubSensorEntityDescription( - coordinator_key="issue", key="latest_pull_request", name="Latest Pull Request", - value_fn=lambda data: data.pull_last.title[:255], - avabl_fn=lambda data: data.pull_last is not None, + avabl_fn=lambda data: data["pull_request"]["pull_requests"], + value_fn=lambda data: data["pull_request"]["pull_requests"][0]["title"][:255], attr_fn=lambda data: { - "url": data.pull_last.html_url, - "number": data.pull_last.number, + "url": data["pull_request"]["pull_requests"][0]["url"], + "number": data["pull_request"]["pull_requests"][0]["number"], }, ), ) @@ -151,43 +137,41 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up GitHub sensor based on a config entry.""" - repositories: dict[str, DataUpdateCoordinators] = hass.data[DOMAIN] + repositories: dict[str, GitHubDataUpdateCoordinator] = hass.data[DOMAIN] async_add_entities( ( - GitHubSensorEntity(coordinators, description) + GitHubSensorEntity(coordinator, description) for description in SENSOR_DESCRIPTIONS - for coordinators in repositories.values() + for coordinator in repositories.values() ), - update_before_add=True, ) -class GitHubSensorEntity(CoordinatorEntity, SensorEntity): +class GitHubSensorEntity(CoordinatorEntity[dict[str, Any]], SensorEntity): """Defines a GitHub sensor entity.""" _attr_attribution = "Data provided by the GitHub API" - coordinator: GitHubBaseDataUpdateCoordinator + coordinator: GitHubDataUpdateCoordinator entity_description: GitHubSensorEntityDescription def __init__( self, - coordinators: DataUpdateCoordinators, + coordinator: GitHubDataUpdateCoordinator, entity_description: GitHubSensorEntityDescription, ) -> None: """Initialize the sensor.""" - coordinator = coordinators[entity_description.coordinator_key] - _information = coordinators["information"].data - super().__init__(coordinator=coordinator) self.entity_description = entity_description - self._attr_name = f"{_information.full_name} {entity_description.name}" - self._attr_unique_id = f"{_information.id}_{entity_description.key}" + self._attr_name = ( + f"{coordinator.data.get('full_name')} {entity_description.name}" + ) + self._attr_unique_id = f"{coordinator.data.get('id')}_{entity_description.key}" self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, coordinator.repository)}, - name=_information.full_name, + name=coordinator.data.get("full_name"), manufacturer="GitHub", configuration_url=f"https://github.com/{coordinator.repository}", entry_type=DeviceEntryType.SERVICE, diff --git a/tests/components/github/common.py b/tests/components/github/common.py index a99834f0cbdf13..a75a8cfaa78502 100644 --- a/tests/components/github/common.py +++ b/tests/components/github/common.py @@ -31,12 +31,11 @@ async def setup_github_integration( }, headers=headers, ) - for endpoint in ("issues", "pulls", "releases", "commits"): - aioclient_mock.get( - f"https://api.github.com/repos/{repository}/{endpoint}", - json=json.loads(load_fixture(f"{endpoint}.json", DOMAIN)), - headers=headers, - ) + aioclient_mock.post( + "https://api.github.com/graphql", + json=json.loads(load_fixture("graphql.json", DOMAIN)), + headers=headers, + ) mock_config_entry.add_to_hass(hass) setup_result = await hass.config_entries.async_setup(mock_config_entry.entry_id) diff --git a/tests/components/github/fixtures/commits.json b/tests/components/github/fixtures/commits.json deleted file mode 100644 index c0deeaf51eaf2c..00000000000000 --- a/tests/components/github/fixtures/commits.json +++ /dev/null @@ -1,80 +0,0 @@ -[ - { - "url": "https://api.github.com/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e", - "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", - "node_id": "MDY6Q29tbWl0NmRjYjA5YjViNTc4NzVmMzM0ZjYxYWViZWQ2OTVlMmU0MTkzZGI1ZQ==", - "html_url": "https://github.com/octocat/Hello-World/commit/6dcb09b5b57875f334f61aebed695e2e4193db5e", - "comments_url": "https://api.github.com/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e/comments", - "commit": { - "url": "https://api.github.com/repos/octocat/Hello-World/git/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e", - "author": { - "name": "Monalisa Octocat", - "email": "support@github.com", - "date": "2011-04-14T16:00:49Z" - }, - "committer": { - "name": "Monalisa Octocat", - "email": "support@github.com", - "date": "2011-04-14T16:00:49Z" - }, - "message": "Fix all the bugs", - "tree": { - "url": "https://api.github.com/repos/octocat/Hello-World/tree/6dcb09b5b57875f334f61aebed695e2e4193db5e", - "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e" - }, - "comment_count": 0, - "verification": { - "verified": false, - "reason": "unsigned", - "signature": null, - "payload": null - } - }, - "author": { - "login": "octocat", - "id": 1, - "node_id": "MDQ6VXNlcjE=", - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "User", - "site_admin": false - }, - "committer": { - "login": "octocat", - "id": 1, - "node_id": "MDQ6VXNlcjE=", - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "User", - "site_admin": false - }, - "parents": [ - { - "url": "https://api.github.com/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e", - "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e" - } - ] - } -] \ No newline at end of file diff --git a/tests/components/github/fixtures/graphql.json b/tests/components/github/fixtures/graphql.json new file mode 100644 index 00000000000000..d8fe86f6c955fa --- /dev/null +++ b/tests/components/github/fixtures/graphql.json @@ -0,0 +1,49 @@ +{ + "data": { + "rateLimit": { + "cost": 1, + "remaining": 4999 + }, + "repository": { + "default_branch_ref": { + "commit": { + "message": "Fix all the bugs", + "url": "https://github.com/octocat/Hello-World/commit/6dcb09b5b57875f334f61aebed695e2e4193db5e", + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e" + } + }, + "stargazers_count": 9, + "forks_count": 9, + "full_name": "octocat/Hello-World", + "id": 1296269, + "watchers": { + "total": 9 + }, + "issue": { + "total": 1, + "issues": [ + { + "title": "Found a bug", + "url": "https://github.com/octocat/Hello-World/issues/1347", + "number": 1347 + } + ] + }, + "pull_request": { + "total": 1, + "pull_requests": [ + { + "title": "Amazing new feature", + "url": "https://github.com/octocat/Hello-World/pull/1347", + "number": 1347 + } + ] + }, + "release": { + "name": "v1.0.0", + "url": "https://github.com/octocat/Hello-World/releases/v1.0.0", + "tag": "v1.0.0" + } + } + } +} \ No newline at end of file diff --git a/tests/components/github/fixtures/issues.json b/tests/components/github/fixtures/issues.json deleted file mode 100644 index d59f1f5c79675c..00000000000000 --- a/tests/components/github/fixtures/issues.json +++ /dev/null @@ -1,159 +0,0 @@ -[ - { - "id": 1, - "node_id": "MDU6SXNzdWUx", - "url": "https://api.github.com/repos/octocat/Hello-World/issues/1347", - "repository_url": "https://api.github.com/repos/octocat/Hello-World", - "labels_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/labels{/name}", - "comments_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/comments", - "events_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/events", - "html_url": "https://github.com/octocat/Hello-World/issues/1347", - "number": 1347, - "state": "open", - "title": "Found a bug", - "body": "I'm having a problem with this.", - "user": { - "login": "octocat", - "id": 1, - "node_id": "MDQ6VXNlcjE=", - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "User", - "site_admin": false - }, - "labels": [ - { - "id": 208045946, - "node_id": "MDU6TGFiZWwyMDgwNDU5NDY=", - "url": "https://api.github.com/repos/octocat/Hello-World/labels/bug", - "name": "bug", - "description": "Something isn't working", - "color": "f29513", - "default": true - } - ], - "assignee": { - "login": "octocat", - "id": 1, - "node_id": "MDQ6VXNlcjE=", - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "User", - "site_admin": false - }, - "assignees": [ - { - "login": "octocat", - "id": 1, - "node_id": "MDQ6VXNlcjE=", - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "User", - "site_admin": false - } - ], - "milestone": { - "url": "https://api.github.com/repos/octocat/Hello-World/milestones/1", - "html_url": "https://github.com/octocat/Hello-World/milestones/v1.0", - "labels_url": "https://api.github.com/repos/octocat/Hello-World/milestones/1/labels", - "id": 1002604, - "node_id": "MDk6TWlsZXN0b25lMTAwMjYwNA==", - "number": 1, - "state": "open", - "title": "v1.0", - "description": "Tracking milestone for version 1.0", - "creator": { - "login": "octocat", - "id": 1, - "node_id": "MDQ6VXNlcjE=", - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "User", - "site_admin": false - }, - "open_issues": 4, - "closed_issues": 8, - "created_at": "2011-04-10T20:09:31Z", - "updated_at": "2014-03-03T18:58:10Z", - "closed_at": "2013-02-12T13:22:01Z", - "due_on": "2012-10-09T23:39:01Z" - }, - "locked": true, - "active_lock_reason": "too heated", - "comments": 0, - "pull_request": { - "url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347", - "html_url": "https://github.com/octocat/Hello-World/pull/1347", - "diff_url": "https://github.com/octocat/Hello-World/pull/1347.diff", - "patch_url": "https://github.com/octocat/Hello-World/pull/1347.patch" - }, - "closed_at": null, - "created_at": "2011-04-22T13:33:48Z", - "updated_at": "2011-04-22T13:33:48Z", - "closed_by": { - "login": "octocat", - "id": 1, - "node_id": "MDQ6VXNlcjE=", - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "User", - "site_admin": false - }, - "author_association": "COLLABORATOR" - } -] \ No newline at end of file diff --git a/tests/components/github/fixtures/pulls.json b/tests/components/github/fixtures/pulls.json deleted file mode 100644 index a42763b18d8a90..00000000000000 --- a/tests/components/github/fixtures/pulls.json +++ /dev/null @@ -1,520 +0,0 @@ -[ - { - "url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347", - "id": 1, - "node_id": "MDExOlB1bGxSZXF1ZXN0MQ==", - "html_url": "https://github.com/octocat/Hello-World/pull/1347", - "diff_url": "https://github.com/octocat/Hello-World/pull/1347.diff", - "patch_url": "https://github.com/octocat/Hello-World/pull/1347.patch", - "issue_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347", - "commits_url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347/commits", - "review_comments_url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347/comments", - "review_comment_url": "https://api.github.com/repos/octocat/Hello-World/pulls/comments{/number}", - "comments_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/comments", - "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e", - "number": 1347, - "state": "open", - "locked": true, - "title": "Amazing new feature", - "user": { - "login": "octocat", - "id": 1, - "node_id": "MDQ6VXNlcjE=", - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "User", - "site_admin": false - }, - "body": "Please pull these awesome changes in!", - "labels": [ - { - "id": 208045946, - "node_id": "MDU6TGFiZWwyMDgwNDU5NDY=", - "url": "https://api.github.com/repos/octocat/Hello-World/labels/bug", - "name": "bug", - "description": "Something isn't working", - "color": "f29513", - "default": true - } - ], - "milestone": { - "url": "https://api.github.com/repos/octocat/Hello-World/milestones/1", - "html_url": "https://github.com/octocat/Hello-World/milestones/v1.0", - "labels_url": "https://api.github.com/repos/octocat/Hello-World/milestones/1/labels", - "id": 1002604, - "node_id": "MDk6TWlsZXN0b25lMTAwMjYwNA==", - "number": 1, - "state": "open", - "title": "v1.0", - "description": "Tracking milestone for version 1.0", - "creator": { - "login": "octocat", - "id": 1, - "node_id": "MDQ6VXNlcjE=", - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "User", - "site_admin": false - }, - "open_issues": 4, - "closed_issues": 8, - "created_at": "2011-04-10T20:09:31Z", - "updated_at": "2014-03-03T18:58:10Z", - "closed_at": "2013-02-12T13:22:01Z", - "due_on": "2012-10-09T23:39:01Z" - }, - "active_lock_reason": "too heated", - "created_at": "2011-01-26T19:01:12Z", - "updated_at": "2011-01-26T19:01:12Z", - "closed_at": "2011-01-26T19:01:12Z", - "merged_at": "2011-01-26T19:01:12Z", - "merge_commit_sha": "e5bd3914e2e596debea16f433f57875b5b90bcd6", - "assignee": { - "login": "octocat", - "id": 1, - "node_id": "MDQ6VXNlcjE=", - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "User", - "site_admin": false - }, - "assignees": [ - { - "login": "octocat", - "id": 1, - "node_id": "MDQ6VXNlcjE=", - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "User", - "site_admin": false - }, - { - "login": "hubot", - "id": 1, - "node_id": "MDQ6VXNlcjE=", - "avatar_url": "https://github.com/images/error/hubot_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/hubot", - "html_url": "https://github.com/hubot", - "followers_url": "https://api.github.com/users/hubot/followers", - "following_url": "https://api.github.com/users/hubot/following{/other_user}", - "gists_url": "https://api.github.com/users/hubot/gists{/gist_id}", - "starred_url": "https://api.github.com/users/hubot/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/hubot/subscriptions", - "organizations_url": "https://api.github.com/users/hubot/orgs", - "repos_url": "https://api.github.com/users/hubot/repos", - "events_url": "https://api.github.com/users/hubot/events{/privacy}", - "received_events_url": "https://api.github.com/users/hubot/received_events", - "type": "User", - "site_admin": true - } - ], - "requested_reviewers": [ - { - "login": "other_user", - "id": 1, - "node_id": "MDQ6VXNlcjE=", - "avatar_url": "https://github.com/images/error/other_user_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/other_user", - "html_url": "https://github.com/other_user", - "followers_url": "https://api.github.com/users/other_user/followers", - "following_url": "https://api.github.com/users/other_user/following{/other_user}", - "gists_url": "https://api.github.com/users/other_user/gists{/gist_id}", - "starred_url": "https://api.github.com/users/other_user/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/other_user/subscriptions", - "organizations_url": "https://api.github.com/users/other_user/orgs", - "repos_url": "https://api.github.com/users/other_user/repos", - "events_url": "https://api.github.com/users/other_user/events{/privacy}", - "received_events_url": "https://api.github.com/users/other_user/received_events", - "type": "User", - "site_admin": false - } - ], - "requested_teams": [ - { - "id": 1, - "node_id": "MDQ6VGVhbTE=", - "url": "https://api.github.com/teams/1", - "html_url": "https://github.com/orgs/github/teams/justice-league", - "name": "Justice League", - "slug": "justice-league", - "description": "A great team.", - "privacy": "closed", - "permission": "admin", - "members_url": "https://api.github.com/teams/1/members{/member}", - "repositories_url": "https://api.github.com/teams/1/repos", - "parent": null - } - ], - "head": { - "label": "octocat:new-topic", - "ref": "new-topic", - "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", - "user": { - "login": "octocat", - "id": 1, - "node_id": "MDQ6VXNlcjE=", - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "User", - "site_admin": false - }, - "repo": { - "id": 1296269, - "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5", - "name": "Hello-World", - "full_name": "octocat/Hello-World", - "owner": { - "login": "octocat", - "id": 1, - "node_id": "MDQ6VXNlcjE=", - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "User", - "site_admin": false - }, - "private": false, - "html_url": "https://github.com/octocat/Hello-World", - "description": "This your first repo!", - "fork": false, - "url": "https://api.github.com/repos/octocat/Hello-World", - "archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}", - "assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}", - "blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}", - "branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}", - "collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}", - "comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}", - "commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}", - "compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}", - "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}", - "contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors", - "deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments", - "downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads", - "events_url": "https://api.github.com/repos/octocat/Hello-World/events", - "forks_url": "https://api.github.com/repos/octocat/Hello-World/forks", - "git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}", - "git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}", - "git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}", - "git_url": "git:github.com/octocat/Hello-World.git", - "issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}", - "issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}", - "issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}", - "keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}", - "labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}", - "languages_url": "https://api.github.com/repos/octocat/Hello-World/languages", - "merges_url": "https://api.github.com/repos/octocat/Hello-World/merges", - "milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}", - "notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}", - "pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}", - "releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}", - "ssh_url": "git@github.com:octocat/Hello-World.git", - "stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers", - "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}", - "subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers", - "subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription", - "tags_url": "https://api.github.com/repos/octocat/Hello-World/tags", - "teams_url": "https://api.github.com/repos/octocat/Hello-World/teams", - "trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}", - "clone_url": "https://github.com/octocat/Hello-World.git", - "mirror_url": "git:git.example.com/octocat/Hello-World", - "hooks_url": "https://api.github.com/repos/octocat/Hello-World/hooks", - "svn_url": "https://svn.github.com/octocat/Hello-World", - "homepage": "https://github.com", - "language": null, - "forks_count": 9, - "stargazers_count": 80, - "watchers_count": 80, - "size": 108, - "default_branch": "master", - "open_issues_count": 0, - "is_template": true, - "topics": [ - "octocat", - "atom", - "electron", - "api" - ], - "has_issues": true, - "has_projects": true, - "has_wiki": true, - "has_pages": false, - "has_downloads": true, - "archived": false, - "disabled": false, - "visibility": "public", - "pushed_at": "2011-01-26T19:06:43Z", - "created_at": "2011-01-26T19:01:12Z", - "updated_at": "2011-01-26T19:14:43Z", - "permissions": { - "admin": false, - "push": false, - "pull": true - }, - "allow_rebase_merge": true, - "template_repository": null, - "temp_clone_token": "ABTLWHOULUVAXGTRYU7OC2876QJ2O", - "allow_squash_merge": true, - "allow_auto_merge": false, - "delete_branch_on_merge": true, - "allow_merge_commit": true, - "subscribers_count": 42, - "network_count": 0, - "license": { - "key": "mit", - "name": "MIT License", - "url": "https://api.github.com/licenses/mit", - "spdx_id": "MIT", - "node_id": "MDc6TGljZW5zZW1pdA==", - "html_url": "https://github.com/licenses/mit" - }, - "forks": 1, - "open_issues": 1, - "watchers": 1 - } - }, - "base": { - "label": "octocat:master", - "ref": "master", - "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", - "user": { - "login": "octocat", - "id": 1, - "node_id": "MDQ6VXNlcjE=", - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "User", - "site_admin": false - }, - "repo": { - "id": 1296269, - "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5", - "name": "Hello-World", - "full_name": "octocat/Hello-World", - "owner": { - "login": "octocat", - "id": 1, - "node_id": "MDQ6VXNlcjE=", - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "User", - "site_admin": false - }, - "private": false, - "html_url": "https://github.com/octocat/Hello-World", - "description": "This your first repo!", - "fork": false, - "url": "https://api.github.com/repos/octocat/Hello-World", - "archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}", - "assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}", - "blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}", - "branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}", - "collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}", - "comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}", - "commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}", - "compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}", - "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}", - "contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors", - "deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments", - "downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads", - "events_url": "https://api.github.com/repos/octocat/Hello-World/events", - "forks_url": "https://api.github.com/repos/octocat/Hello-World/forks", - "git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}", - "git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}", - "git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}", - "git_url": "git:github.com/octocat/Hello-World.git", - "issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}", - "issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}", - "issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}", - "keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}", - "labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}", - "languages_url": "https://api.github.com/repos/octocat/Hello-World/languages", - "merges_url": "https://api.github.com/repos/octocat/Hello-World/merges", - "milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}", - "notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}", - "pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}", - "releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}", - "ssh_url": "git@github.com:octocat/Hello-World.git", - "stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers", - "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}", - "subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers", - "subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription", - "tags_url": "https://api.github.com/repos/octocat/Hello-World/tags", - "teams_url": "https://api.github.com/repos/octocat/Hello-World/teams", - "trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}", - "clone_url": "https://github.com/octocat/Hello-World.git", - "mirror_url": "git:git.example.com/octocat/Hello-World", - "hooks_url": "https://api.github.com/repos/octocat/Hello-World/hooks", - "svn_url": "https://svn.github.com/octocat/Hello-World", - "homepage": "https://github.com", - "language": null, - "forks_count": 9, - "stargazers_count": 80, - "watchers_count": 80, - "size": 108, - "default_branch": "master", - "open_issues_count": 0, - "is_template": true, - "topics": [ - "octocat", - "atom", - "electron", - "api" - ], - "has_issues": true, - "has_projects": true, - "has_wiki": true, - "has_pages": false, - "has_downloads": true, - "archived": false, - "disabled": false, - "visibility": "public", - "pushed_at": "2011-01-26T19:06:43Z", - "created_at": "2011-01-26T19:01:12Z", - "updated_at": "2011-01-26T19:14:43Z", - "permissions": { - "admin": false, - "push": false, - "pull": true - }, - "allow_rebase_merge": true, - "template_repository": null, - "temp_clone_token": "ABTLWHOULUVAXGTRYU7OC2876QJ2O", - "allow_squash_merge": true, - "allow_auto_merge": false, - "delete_branch_on_merge": true, - "allow_merge_commit": true, - "subscribers_count": 42, - "network_count": 0, - "license": { - "key": "mit", - "name": "MIT License", - "url": "https://api.github.com/licenses/mit", - "spdx_id": "MIT", - "node_id": "MDc6TGljZW5zZW1pdA==", - "html_url": "https://github.com/licenses/mit" - }, - "forks": 1, - "open_issues": 1, - "watchers": 1 - } - }, - "_links": { - "self": { - "href": "https://api.github.com/repos/octocat/Hello-World/pulls/1347" - }, - "html": { - "href": "https://github.com/octocat/Hello-World/pull/1347" - }, - "issue": { - "href": "https://api.github.com/repos/octocat/Hello-World/issues/1347" - }, - "comments": { - "href": "https://api.github.com/repos/octocat/Hello-World/issues/1347/comments" - }, - "review_comments": { - "href": "https://api.github.com/repos/octocat/Hello-World/pulls/1347/comments" - }, - "review_comment": { - "href": "https://api.github.com/repos/octocat/Hello-World/pulls/comments{/number}" - }, - "commits": { - "href": "https://api.github.com/repos/octocat/Hello-World/pulls/1347/commits" - }, - "statuses": { - "href": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e" - } - }, - "author_association": "OWNER", - "auto_merge": null, - "draft": false - } -] \ No newline at end of file diff --git a/tests/components/github/fixtures/releases.json b/tests/components/github/fixtures/releases.json deleted file mode 100644 index e69206ae78401a..00000000000000 --- a/tests/components/github/fixtures/releases.json +++ /dev/null @@ -1,76 +0,0 @@ -[ - { - "url": "https://api.github.com/repos/octocat/Hello-World/releases/1", - "html_url": "https://github.com/octocat/Hello-World/releases/v1.0.0", - "assets_url": "https://api.github.com/repos/octocat/Hello-World/releases/1/assets", - "upload_url": "https://uploads.github.com/repos/octocat/Hello-World/releases/1/assets{?name,label}", - "tarball_url": "https://api.github.com/repos/octocat/Hello-World/tarball/v1.0.0", - "zipball_url": "https://api.github.com/repos/octocat/Hello-World/zipball/v1.0.0", - "id": 1, - "node_id": "MDc6UmVsZWFzZTE=", - "tag_name": "v1.0.0", - "target_commitish": "master", - "name": "v1.0.0", - "body": "Description of the release", - "draft": false, - "prerelease": false, - "created_at": "2013-02-27T19:35:32Z", - "published_at": "2013-02-27T19:35:32Z", - "author": { - "login": "octocat", - "id": 1, - "node_id": "MDQ6VXNlcjE=", - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "User", - "site_admin": false - }, - "assets": [ - { - "url": "https://api.github.com/repos/octocat/Hello-World/releases/assets/1", - "browser_download_url": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/example.zip", - "id": 1, - "node_id": "MDEyOlJlbGVhc2VBc3NldDE=", - "name": "example.zip", - "label": "short description", - "state": "uploaded", - "content_type": "application/zip", - "size": 1024, - "download_count": 42, - "created_at": "2013-02-27T19:35:32Z", - "updated_at": "2013-02-27T19:35:32Z", - "uploader": { - "login": "octocat", - "id": 1, - "node_id": "MDQ6VXNlcjE=", - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "User", - "site_admin": false - } - } - ] - } -] \ No newline at end of file diff --git a/tests/components/github/test_diagnostics.py b/tests/components/github/test_diagnostics.py index 6e5e6e13fa44d6..80dfaec2445973 100644 --- a/tests/components/github/test_diagnostics.py +++ b/tests/components/github/test_diagnostics.py @@ -1,14 +1,16 @@ """Test GitHub diagnostics.""" +import json + from aiogithubapi import GitHubException from aiohttp import ClientSession -from homeassistant.components.github.const import CONF_REPOSITORIES +from homeassistant.components.github.const import CONF_REPOSITORIES, DOMAIN from homeassistant.core import HomeAssistant from .common import setup_github_integration -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, load_fixture from tests.components.diagnostics import get_diagnostics_for_config_entry from tests.test_util.aiohttp import AiohttpClientMocker @@ -21,13 +23,21 @@ async def test_entry_diagnostics( ) -> None: """Test config entry diagnostics.""" mock_config_entry.options = {CONF_REPOSITORIES: ["home-assistant/core"]} - await setup_github_integration(hass, mock_config_entry, aioclient_mock) + response_json = json.loads(load_fixture("graphql.json", DOMAIN)) + response_json["data"]["repository"]["full_name"] = "home-assistant/core" + + aioclient_mock.post( + "https://api.github.com/graphql", + json=response_json, + headers=json.loads(load_fixture("base_headers.json", DOMAIN)), + ) aioclient_mock.get( "https://api.github.com/rate_limit", json={"resources": {"core": {"remaining": 100, "limit": 100}}}, headers={"Content-Type": "application/json"}, ) + await setup_github_integration(hass, mock_config_entry, aioclient_mock) result = await get_diagnostics_for_config_entry( hass, hass_client, diff --git a/tests/components/github/test_sensor.py b/tests/components/github/test_sensor.py index cea3edc6b47b88..cba787cbc2873b 100644 --- a/tests/components/github/test_sensor.py +++ b/tests/components/github/test_sensor.py @@ -1,62 +1,37 @@ """Test GitHub sensor.""" -from unittest.mock import MagicMock, patch +import json -from aiogithubapi import GitHubNotModifiedException -import pytest - -from homeassistant.components.github.const import DEFAULT_UPDATE_INTERVAL +from homeassistant.components.github.const import DEFAULT_UPDATE_INTERVAL, DOMAIN from homeassistant.core import HomeAssistant from homeassistant.util import dt -from tests.common import MockConfigEntry, async_fire_time_changed +from tests.common import MockConfigEntry, async_fire_time_changed, load_fixture +from tests.test_util.aiohttp import AiohttpClientMocker TEST_SENSOR_ENTITY = "sensor.octocat_hello_world_latest_release" -async def test_sensor_updates_with_not_modified_content( - hass: HomeAssistant, - init_integration: MockConfigEntry, - caplog: pytest.LogCaptureFixture, -) -> None: - """Test the sensor updates by default GitHub sensors.""" - state = hass.states.get(TEST_SENSOR_ENTITY) - assert state.state == "v1.0.0" - assert ( - "Content for octocat/Hello-World with RepositoryReleaseDataUpdateCoordinator not modified" - not in caplog.text - ) - - with patch( - "aiogithubapi.namespaces.releases.GitHubReleasesNamespace.list", - side_effect=GitHubNotModifiedException, - ): - - async_fire_time_changed(hass, dt.utcnow() + DEFAULT_UPDATE_INTERVAL) - await hass.async_block_till_done() - - assert ( - "Content for octocat/Hello-World with RepositoryReleaseDataUpdateCoordinator not modified" - in caplog.text - ) - new_state = hass.states.get(TEST_SENSOR_ENTITY) - assert state.state == new_state.state - - async def test_sensor_updates_with_empty_release_array( hass: HomeAssistant, init_integration: MockConfigEntry, + aioclient_mock: AiohttpClientMocker, ) -> None: """Test the sensor updates by default GitHub sensors.""" state = hass.states.get(TEST_SENSOR_ENTITY) assert state.state == "v1.0.0" - with patch( - "aiogithubapi.namespaces.releases.GitHubReleasesNamespace.list", - return_value=MagicMock(data=[]), - ): + response_json = json.loads(load_fixture("graphql.json", DOMAIN)) + response_json["data"]["repository"]["release"] = None + + aioclient_mock.clear_requests() + aioclient_mock.post( + "https://api.github.com/graphql", + json=response_json, + headers=json.loads(load_fixture("base_headers.json", DOMAIN)), + ) - async_fire_time_changed(hass, dt.utcnow() + DEFAULT_UPDATE_INTERVAL) - await hass.async_block_till_done() + async_fire_time_changed(hass, dt.utcnow() + DEFAULT_UPDATE_INTERVAL) + await hass.async_block_till_done() new_state = hass.states.get(TEST_SENSOR_ENTITY) assert new_state.state == "unavailable" From bce033cfc726edad608a0db24dfd68b4e9b71711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Sun, 20 Feb 2022 12:06:27 +0100 Subject: [PATCH 0857/1098] Enable all GitHub sensors by default (#66931) --- homeassistant/components/github/sensor.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/github/sensor.py b/homeassistant/components/github/sensor.py index 7fad114a2ae610..08e8a49af1fb69 100644 --- a/homeassistant/components/github/sensor.py +++ b/homeassistant/components/github/sensor.py @@ -34,7 +34,6 @@ class BaseEntityDescription(SensorEntityDescription): """Describes GitHub sensor entity default overrides.""" icon: str = "mdi:github" - entity_registry_enabled_default: bool = False attr_fn: Callable[[dict[str, Any]], Mapping[str, Any] | None] = lambda data: None avabl_fn: Callable[[dict[str, Any]], bool] = lambda data: True @@ -100,7 +99,6 @@ class GitHubSensorEntityDescription(BaseEntityDescription, BaseEntityDescription GitHubSensorEntityDescription( key="latest_release", name="Latest Release", - entity_registry_enabled_default=True, avabl_fn=lambda data: data["release"] is not None, value_fn=lambda data: data["release"]["name"][:255], attr_fn=lambda data: { From 3cbbf90f23bbdf61f892362b7ad498d01e6ded44 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sun, 20 Feb 2022 12:32:12 +0100 Subject: [PATCH 0858/1098] Bump pysensibo to v1.0.6 (#66930) --- homeassistant/components/sensibo/climate.py | 4 ++-- homeassistant/components/sensibo/coordinator.py | 3 ++- homeassistant/components/sensibo/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index a9629ba42f4dcc..10927fc3a06c70 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -325,12 +325,12 @@ async def _async_set_ac_state_property( f"Failed to set AC state for device {self.name} to Sensibo servers: {err}" ) from err LOGGER.debug("Result: %s", result) - if result["status"] == "Success": + if result["result"]["status"] == "Success": self.coordinator.data[self.unique_id][AC_STATE_TO_DATA[name]] = value self.async_write_ha_state() return - failure = result["failureReason"] + failure = result["result"]["failureReason"] raise HomeAssistantError( f"Could not set state for device {self.name} due to reason {failure}" ) diff --git a/homeassistant/components/sensibo/coordinator.py b/homeassistant/components/sensibo/coordinator.py index c79fb5b7bb5852..bf2c1aca17f320 100644 --- a/homeassistant/components/sensibo/coordinator.py +++ b/homeassistant/components/sensibo/coordinator.py @@ -38,7 +38,8 @@ async def _async_update_data(self) -> dict[str, dict[str, Any]]: devices = [] try: - for dev in await self.client.async_get_devices(): + data = await self.client.async_get_devices() + for dev in data["result"]: devices.append(dev) except (AuthenticationError, SensiboError) as error: raise UpdateFailed from error diff --git a/homeassistant/components/sensibo/manifest.json b/homeassistant/components/sensibo/manifest.json index 4d41a6e3ca05c6..758dfca4b97313 100644 --- a/homeassistant/components/sensibo/manifest.json +++ b/homeassistant/components/sensibo/manifest.json @@ -2,7 +2,7 @@ "domain": "sensibo", "name": "Sensibo", "documentation": "https://www.home-assistant.io/integrations/sensibo", - "requirements": ["pysensibo==1.0.5"], + "requirements": ["pysensibo==1.0.6"], "config_flow": true, "codeowners": ["@andrey-git", "@gjohansson-ST"], "iot_class": "cloud_polling", diff --git a/requirements_all.txt b/requirements_all.txt index a5bd4ad853cc3b..bed3ce9000ed99 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1818,7 +1818,7 @@ pysaj==0.0.16 pysdcp==1 # homeassistant.components.sensibo -pysensibo==1.0.5 +pysensibo==1.0.6 # homeassistant.components.serial # homeassistant.components.zha diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5c70c20bf3348f..4189c4dca8b79d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1160,7 +1160,7 @@ pyrituals==0.0.6 pyruckus==0.12 # homeassistant.components.sensibo -pysensibo==1.0.5 +pysensibo==1.0.6 # homeassistant.components.serial # homeassistant.components.zha From 2d52aca9eb58a2003d9b4252c8e4700a2d81584e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Sun, 20 Feb 2022 13:00:01 +0100 Subject: [PATCH 0859/1098] Add Latest Tag sensor to GitHub integration (#66932) --- homeassistant/components/github/coordinator.py | 12 ++++++++++++ homeassistant/components/github/sensor.py | 9 +++++++++ tests/components/github/fixtures/graphql.json | 10 ++++++++++ 3 files changed, 31 insertions(+) diff --git a/homeassistant/components/github/coordinator.py b/homeassistant/components/github/coordinator.py index 9769c944f16218..ae3c9b5dc8717a 100644 --- a/homeassistant/components/github/coordinator.py +++ b/homeassistant/components/github/coordinator.py @@ -68,6 +68,18 @@ url tag: tagName } + refs( + first: 1 + refPrefix: "refs/tags/" + orderBy: {field: TAG_COMMIT_DATE, direction: DESC} + ) { + tags: nodes { + name + target { + url: commitUrl + } + } + } } } """ diff --git a/homeassistant/components/github/sensor.py b/homeassistant/components/github/sensor.py index 08e8a49af1fb69..75284b2ccd3518 100644 --- a/homeassistant/components/github/sensor.py +++ b/homeassistant/components/github/sensor.py @@ -126,6 +126,15 @@ class GitHubSensorEntityDescription(BaseEntityDescription, BaseEntityDescription "number": data["pull_request"]["pull_requests"][0]["number"], }, ), + GitHubSensorEntityDescription( + key="latest_tag", + name="Latest Tag", + avabl_fn=lambda data: data["refs"]["tags"], + value_fn=lambda data: data["refs"]["tags"][0]["name"][:255], + attr_fn=lambda data: { + "url": data["refs"]["tags"][0]["target"]["url"], + }, + ), ) diff --git a/tests/components/github/fixtures/graphql.json b/tests/components/github/fixtures/graphql.json index d8fe86f6c955fa..8fe1b17022a57d 100644 --- a/tests/components/github/fixtures/graphql.json +++ b/tests/components/github/fixtures/graphql.json @@ -43,6 +43,16 @@ "name": "v1.0.0", "url": "https://github.com/octocat/Hello-World/releases/v1.0.0", "tag": "v1.0.0" + }, + "refs": { + "tags": [ + { + "name": "v1.0.0", + "target": { + "url": "https://github.com/octocat/Hello-World/commit/6dcb09b5b57875f334f61aebed695e2e4193db5e" + } + } + ] } } } From 5b28e2d9832c4cd90eb9f1204f4f5b1002bf7450 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Sun, 20 Feb 2022 12:32:24 +0000 Subject: [PATCH 0860/1098] Add discussions sensors to GitHub (#66937) --- homeassistant/components/github/coordinator.py | 11 +++++++++++ homeassistant/components/github/sensor.py | 18 ++++++++++++++++++ tests/components/github/fixtures/graphql.json | 10 ++++++++++ 3 files changed, 39 insertions(+) diff --git a/homeassistant/components/github/coordinator.py b/homeassistant/components/github/coordinator.py index ae3c9b5dc8717a..10f30bb1006380 100644 --- a/homeassistant/components/github/coordinator.py +++ b/homeassistant/components/github/coordinator.py @@ -39,6 +39,17 @@ watchers(first: 1) { total: totalCount } + discussion: discussions( + first: 1 + orderBy: {field: CREATED_AT, direction: DESC} + ) { + total: totalCount + discussions: nodes { + title + url + number + } + } issue: issues( first: 1 states: OPEN diff --git a/homeassistant/components/github/sensor.py b/homeassistant/components/github/sensor.py index 75284b2ccd3518..a09e440e2ce63b 100644 --- a/homeassistant/components/github/sensor.py +++ b/homeassistant/components/github/sensor.py @@ -44,6 +44,14 @@ class GitHubSensorEntityDescription(BaseEntityDescription, BaseEntityDescription SENSOR_DESCRIPTIONS: tuple[GitHubSensorEntityDescription, ...] = ( + GitHubSensorEntityDescription( + key="discussions_count", + name="Discussions", + native_unit_of_measurement="Discussions", + entity_category=EntityCategory.DIAGNOSTIC, + state_class=SensorStateClass.MEASUREMENT, + value_fn=lambda data: data["discussion"]["total"], + ), GitHubSensorEntityDescription( key="stargazers_count", name="Stars", @@ -96,6 +104,16 @@ class GitHubSensorEntityDescription(BaseEntityDescription, BaseEntityDescription "url": data["default_branch_ref"]["commit"]["url"], }, ), + GitHubSensorEntityDescription( + key="latest_discussion", + name="Latest Discussion", + avabl_fn=lambda data: data["discussion"]["discussions"], + value_fn=lambda data: data["discussion"]["discussions"][0]["title"][:255], + attr_fn=lambda data: { + "url": data["discussion"]["discussions"][0]["url"], + "number": data["discussion"]["discussions"][0]["number"], + }, + ), GitHubSensorEntityDescription( key="latest_release", name="Latest Release", diff --git a/tests/components/github/fixtures/graphql.json b/tests/components/github/fixtures/graphql.json index 8fe1b17022a57d..52b0e6ccfd60e7 100644 --- a/tests/components/github/fixtures/graphql.json +++ b/tests/components/github/fixtures/graphql.json @@ -19,6 +19,16 @@ "watchers": { "total": 9 }, + "discussion": { + "total": 1, + "discussions": [ + { + "title": "First discussion", + "url": "https://github.com/octocat/Hello-World/discussions/1347", + "number": 1347 + } + ] + }, "issue": { "total": 1, "issues": [ From 3d5790aaad4bf8c6d5f51f92830fd0e659b91bda Mon Sep 17 00:00:00 2001 From: Michael Chisholm Date: Mon, 21 Feb 2022 00:14:53 +1100 Subject: [PATCH 0861/1098] Avoid accessing hass.data in test_play_media_didl_metadata (#66939) --- tests/components/dlna_dmr/test_media_player.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/components/dlna_dmr/test_media_player.py b/tests/components/dlna_dmr/test_media_player.py index 0abda9e1ed3c54..c68a311d2b677e 100644 --- a/tests/components/dlna_dmr/test_media_player.py +++ b/tests/components/dlna_dmr/test_media_player.py @@ -823,10 +823,11 @@ class DidlPlayMedia(PlayMedia): await async_setup_component(hass, MS_DOMAIN, {MS_DOMAIN: {}}) await hass.async_block_till_done() - local_source = hass.data[MS_DOMAIN][MS_DOMAIN] - - with patch.object(local_source, "async_resolve_media", return_value=play_media): + with patch( + "homeassistant.components.media_source.async_resolve_media", + return_value=play_media, + ): await hass.services.async_call( MP_DOMAIN, mp_const.SERVICE_PLAY_MEDIA, From efd0e898f94f39fbaac7d9a8a5892995c41eb890 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Sun, 20 Feb 2022 16:25:33 +0200 Subject: [PATCH 0862/1098] Bump aiowebostv to 0.1.3 (#66942) --- homeassistant/components/webostv/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/webostv/manifest.json b/homeassistant/components/webostv/manifest.json index afba19c82d9f62..a60a12aba30b45 100644 --- a/homeassistant/components/webostv/manifest.json +++ b/homeassistant/components/webostv/manifest.json @@ -3,7 +3,7 @@ "name": "LG webOS Smart TV", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/webostv", - "requirements": ["aiowebostv==0.1.2", "sqlalchemy==1.4.27"], + "requirements": ["aiowebostv==0.1.3", "sqlalchemy==1.4.27"], "codeowners": ["@bendavid", "@thecode"], "ssdp": [{"st": "urn:lge-com:service:webos-second-screen:1"}], "quality_scale": "platinum", diff --git a/requirements_all.txt b/requirements_all.txt index bed3ce9000ed99..6d5aa6c24aa927 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -278,7 +278,7 @@ aiovlc==0.1.0 aiowatttime==0.1.1 # homeassistant.components.webostv -aiowebostv==0.1.2 +aiowebostv==0.1.3 # homeassistant.components.yandex_transport aioymaps==1.2.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4189c4dca8b79d..749d17373be110 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -213,7 +213,7 @@ aiovlc==0.1.0 aiowatttime==0.1.1 # homeassistant.components.webostv -aiowebostv==0.1.2 +aiowebostv==0.1.3 # homeassistant.components.yandex_transport aioymaps==1.2.2 From dbb2c64d862387e19723f37c4900ede3ea940c18 Mon Sep 17 00:00:00 2001 From: dewdrop <81049573+dewdropawoo@users.noreply.github.com> Date: Sun, 20 Feb 2022 06:33:19 -0800 Subject: [PATCH 0863/1098] Fix broken aftership sensor after pyaftership 21.11.0 bump (#66855) * Fix update to pyaftership 21.11.0 Variable name collision and a missing property access was causing this sensor to always return zero elements. * Move subscripting out of awaited statement --- homeassistant/components/aftership/sensor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/aftership/sensor.py b/homeassistant/components/aftership/sensor.py index 82b844bbd01c18..62d138d212362a 100644 --- a/homeassistant/components/aftership/sensor.py +++ b/homeassistant/components/aftership/sensor.py @@ -149,10 +149,10 @@ async def async_update(self, **kwargs: Any) -> None: status_to_ignore = {"delivered"} status_counts: dict[str, int] = {} - trackings = [] + parsed_trackings = [] not_delivered_count = 0 - for track in trackings: + for track in trackings["trackings"]: status = track["tag"].lower() name = ( track["tracking_number"] if track["title"] is None else track["title"] @@ -163,7 +163,7 @@ async def async_update(self, **kwargs: Any) -> None: else track["checkpoints"][-1] ) status_counts[status] = status_counts.get(status, 0) + 1 - trackings.append( + parsed_trackings.append( { "name": name, "tracking_number": track["tracking_number"], @@ -183,7 +183,7 @@ async def async_update(self, **kwargs: Any) -> None: self._attributes = { **status_counts, - ATTR_TRACKINGS: trackings, + ATTR_TRACKINGS: parsed_trackings, } self._state = not_delivered_count From 7c8f4a4262fbeca675ef8bdff066f8904dcedf35 Mon Sep 17 00:00:00 2001 From: Klaas Schoute Date: Sun, 20 Feb 2022 16:58:21 +0100 Subject: [PATCH 0864/1098] Update Pure Energie integration (#66946) * Remove service entry_type * Set raise on progress --- homeassistant/components/pure_energie/config_flow.py | 2 +- homeassistant/components/pure_energie/sensor.py | 2 -- tests/components/pure_energie/test_sensor.py | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/homeassistant/components/pure_energie/config_flow.py b/homeassistant/components/pure_energie/config_flow.py index 526bf004fd54ea..2b1e20d645ef62 100644 --- a/homeassistant/components/pure_energie/config_flow.py +++ b/homeassistant/components/pure_energie/config_flow.py @@ -35,7 +35,7 @@ async def async_step_user( except GridNetConnectionError: errors["base"] = "cannot_connect" else: - await self.async_set_unique_id(device.n2g_id) + await self.async_set_unique_id(device.n2g_id, raise_on_progress=False) self._abort_if_unique_id_configured( updates={CONF_HOST: user_input[CONF_HOST]} ) diff --git a/homeassistant/components/pure_energie/sensor.py b/homeassistant/components/pure_energie/sensor.py index 64ada3925f3058..fffbfd7c7bb248 100644 --- a/homeassistant/components/pure_energie/sensor.py +++ b/homeassistant/components/pure_energie/sensor.py @@ -14,7 +14,6 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, ENERGY_KILO_WATT_HOUR, POWER_WATT from homeassistant.core import HomeAssistant -from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -98,7 +97,6 @@ def __init__( self.entity_description = description self._attr_unique_id = f"{coordinator.data.device.n2g_id}_{description.key}" self._attr_device_info = DeviceInfo( - entry_type=DeviceEntryType.SERVICE, identifiers={(DOMAIN, coordinator.data.device.n2g_id)}, configuration_url=f"http://{coordinator.config_entry.data[CONF_HOST]}", sw_version=coordinator.data.device.firmware, diff --git a/tests/components/pure_energie/test_sensor.py b/tests/components/pure_energie/test_sensor.py index dddafa3c24b9ed..60894ac09f8773 100644 --- a/tests/components/pure_energie/test_sensor.py +++ b/tests/components/pure_energie/test_sensor.py @@ -70,6 +70,5 @@ async def test_sensors( assert device_entry.identifiers == {(DOMAIN, "aabbccddeeff")} assert device_entry.name == "home" assert device_entry.manufacturer == "NET2GRID" - assert device_entry.entry_type is dr.DeviceEntryType.SERVICE assert device_entry.model == "SBWF3102" assert device_entry.sw_version == "1.6.16" From 8c96f1457d6b746d057cd58968e2995043fb6462 Mon Sep 17 00:00:00 2001 From: rikroe <42204099+rikroe@users.noreply.github.com> Date: Sun, 20 Feb 2022 18:17:08 +0100 Subject: [PATCH 0865/1098] Bump bimmer_connected to 0.8.11 (#66951) Co-authored-by: rikroe --- homeassistant/components/bmw_connected_drive/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/bmw_connected_drive/manifest.json b/homeassistant/components/bmw_connected_drive/manifest.json index 3e437f9932f62a..ef5a376034a42a 100644 --- a/homeassistant/components/bmw_connected_drive/manifest.json +++ b/homeassistant/components/bmw_connected_drive/manifest.json @@ -2,7 +2,7 @@ "domain": "bmw_connected_drive", "name": "BMW Connected Drive", "documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive", - "requirements": ["bimmer_connected==0.8.10"], + "requirements": ["bimmer_connected==0.8.11"], "codeowners": ["@gerard33", "@rikroe"], "config_flow": true, "iot_class": "cloud_polling", diff --git a/requirements_all.txt b/requirements_all.txt index 6d5aa6c24aa927..08c4a43bef93d3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -401,7 +401,7 @@ beautifulsoup4==4.10.0 bellows==0.29.0 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.8.10 +bimmer_connected==0.8.11 # homeassistant.components.bizkaibus bizkaibus==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 749d17373be110..ff80c8a83662ab 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -282,7 +282,7 @@ beautifulsoup4==4.10.0 bellows==0.29.0 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.8.10 +bimmer_connected==0.8.11 # homeassistant.components.blebox blebox_uniapi==1.3.3 From 620b653d76c0b5ee68b4f96b2fea07f4d20dbbad Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 20 Feb 2022 20:45:19 +0100 Subject: [PATCH 0866/1098] Plugwise bump module version to fix heating-state and OnOff devices (#66936) --- homeassistant/components/plugwise/climate.py | 7 ++++--- homeassistant/components/plugwise/const.py | 1 - homeassistant/components/plugwise/gateway.py | 15 ++------------- homeassistant/components/plugwise/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../adam_multiple_devices_per_zone/all_data.json | 11 +++++------ .../plugwise/fixtures/anna_heatpump/all_data.json | 14 ++++++-------- .../fixtures/p1v3_full_option/all_data.json | 6 +----- .../plugwise/fixtures/stretch_v31/all_data.json | 6 +----- tests/components/plugwise/test_diagnostics.py | 9 +++------ 11 files changed, 25 insertions(+), 50 deletions(-) diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index c4c444441c28f2..e22f12c35265ff 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -107,15 +107,16 @@ def hvac_action(self) -> str: if "control_state" in self.device: if self.device.get("control_state") == "cooling": return CURRENT_HVAC_COOL - if self.device.get("control_state") == "heating": + # Support preheating state as heating, until preheating is added as a separate state + if self.device.get("control_state") in ["heating", "preheating"]: return CURRENT_HVAC_HEAT else: heater_central_data = self.coordinator.data.devices[ self.coordinator.data.gateway["heater_id"] ] - if heater_central_data.get("heating_state"): + if heater_central_data["binary_sensors"].get("heating_state"): return CURRENT_HVAC_HEAT - if heater_central_data.get("cooling_state"): + if heater_central_data["binary_sensors"].get("cooling_state"): return CURRENT_HVAC_COOL return CURRENT_HVAC_IDLE diff --git a/homeassistant/components/plugwise/const.py b/homeassistant/components/plugwise/const.py index ab4bde47d9114e..adcd68ed50e8bd 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -25,7 +25,6 @@ Platform.SENSOR, Platform.SWITCH, ] -SENSOR_PLATFORMS = [Platform.SENSOR, Platform.SWITCH] ZEROCONF_MAP = { "smile": "P1", "smile_thermo": "Anna", diff --git a/homeassistant/components/plugwise/gateway.py b/homeassistant/components/plugwise/gateway.py index 05ef937c6fdc3b..71ca2af9537357 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -16,14 +16,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.entity_registry import RegistryEntry, async_migrate_entries -from .const import ( - DEFAULT_PORT, - DEFAULT_USERNAME, - DOMAIN, - LOGGER, - PLATFORMS_GATEWAY, - SENSOR_PLATFORMS, -) +from .const import DEFAULT_PORT, DEFAULT_USERNAME, DOMAIN, LOGGER, PLATFORMS_GATEWAY from .coordinator import PlugwiseDataUpdateCoordinator @@ -77,11 +70,7 @@ async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: sw_version=api.smile_version[0], ) - platforms = PLATFORMS_GATEWAY - if coordinator.data.gateway["single_master_thermostat"] is None: - platforms = SENSOR_PLATFORMS - - hass.config_entries.async_setup_platforms(entry, platforms) + hass.config_entries.async_setup_platforms(entry, PLATFORMS_GATEWAY) return True diff --git a/homeassistant/components/plugwise/manifest.json b/homeassistant/components/plugwise/manifest.json index b7db3880a5e048..4f1417ae018066 100644 --- a/homeassistant/components/plugwise/manifest.json +++ b/homeassistant/components/plugwise/manifest.json @@ -2,7 +2,7 @@ "domain": "plugwise", "name": "Plugwise", "documentation": "https://www.home-assistant.io/integrations/plugwise", - "requirements": ["plugwise==0.16.5"], + "requirements": ["plugwise==0.16.6"], "codeowners": ["@CoMPaTech", "@bouwew", "@brefra", "@frenck"], "zeroconf": ["_plugwise._tcp.local."], "config_flow": true, diff --git a/requirements_all.txt b/requirements_all.txt index 08c4a43bef93d3..daf0559cde62bb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1267,7 +1267,7 @@ plexauth==0.0.6 plexwebsocket==0.0.13 # homeassistant.components.plugwise -plugwise==0.16.5 +plugwise==0.16.6 # homeassistant.components.plum_lightpad plumlightpad==0.0.11 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ff80c8a83662ab..a79ce5ce6b7594 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -795,7 +795,7 @@ plexauth==0.0.6 plexwebsocket==0.0.13 # homeassistant.components.plugwise -plugwise==0.16.5 +plugwise==0.16.6 # homeassistant.components.plum_lightpad plumlightpad==0.0.11 diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json index 925571908c537b..65b96074cc0860 100644 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json +++ b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json @@ -1,11 +1,9 @@ [ { - "active_device": true, - "cooling_present": false, + "smile_name": "Adam", "gateway_id": "fe799307f1624099878210aa0b9f1475", "heater_id": "90986d591dcd426cae3ec3e8111ff730", - "single_master_thermostat": false, - "smile_name": "Adam", + "cooling_present": false, "notifications": { "af82e4ccf9c548528166d38e560662a4": { "warning": "Node Plug (with MAC address 000D6F000D13CB01, in room 'n.a.') has been unreachable since 23:03 2020-01-18. Please check the connection and restart the device." @@ -260,8 +258,9 @@ "lower_bound": 10, "upper_bound": 90, "resolution": 1, - "cooling_active": false, - "heating_state": true, + "binary_sensors": { + "heating_state": true + }, "sensors": { "water_temperature": 70.0, "intended_boiler_temperature": 70.0, diff --git a/tests/components/plugwise/fixtures/anna_heatpump/all_data.json b/tests/components/plugwise/fixtures/anna_heatpump/all_data.json index 9585c8630afdaa..e9e62b77bb02bc 100644 --- a/tests/components/plugwise/fixtures/anna_heatpump/all_data.json +++ b/tests/components/plugwise/fixtures/anna_heatpump/all_data.json @@ -1,11 +1,9 @@ [ { - "active_device": true, - "cooling_present": true, + "smile_name": "Anna", "gateway_id": "015ae9ea3f964e668e490fa39da3870b", "heater_id": "1cbf783bb11e4a7c8a6843dee3a86927", - "single_master_thermostat": true, - "smile_name": "Anna", + "cooling_present": true, "notifications": {} }, { @@ -21,12 +19,11 @@ "lower_bound": -10, "upper_bound": 40, "resolution": 1, - "heating_state": true, "compressor_state": true, - "cooling_state": false, - "cooling_active": false, "binary_sensors": { "dhw_state": false, + "heating_state": true, + "cooling_state": false, "slave_boiler_state": false, "flame_state": false }, @@ -40,7 +37,8 @@ }, "switches": { "dhw_cm_switch": false - } + }, + "cooling_active": false }, "015ae9ea3f964e668e490fa39da3870b": { "class": "gateway", diff --git a/tests/components/plugwise/fixtures/p1v3_full_option/all_data.json b/tests/components/plugwise/fixtures/p1v3_full_option/all_data.json index e65c012da85487..fc565ef604007c 100644 --- a/tests/components/plugwise/fixtures/p1v3_full_option/all_data.json +++ b/tests/components/plugwise/fixtures/p1v3_full_option/all_data.json @@ -1,11 +1,7 @@ [ { - "active_device": false, - "cooling_present": false, - "gateway_id": "e950c7d5e1ee407a858e2a8b5016c8b3", - "heater_id": null, - "single_master_thermostat": false, "smile_name": "P1", + "gateway_id": "e950c7d5e1ee407a858e2a8b5016c8b3", "notifications": {} }, { diff --git a/tests/components/plugwise/fixtures/stretch_v31/all_data.json b/tests/components/plugwise/fixtures/stretch_v31/all_data.json index 494ff28b118104..21d7a888dd59f6 100644 --- a/tests/components/plugwise/fixtures/stretch_v31/all_data.json +++ b/tests/components/plugwise/fixtures/stretch_v31/all_data.json @@ -1,11 +1,7 @@ [ { - "active_device": false, - "cooling_present": false, - "gateway_id": "0000aaaa0000aaaa0000aaaa0000aa00", - "heater_id": null, - "single_master_thermostat": false, "smile_name": "Stretch", + "gateway_id": "0000aaaa0000aaaa0000aaaa0000aa00", "notifications": {} }, { diff --git a/tests/components/plugwise/test_diagnostics.py b/tests/components/plugwise/test_diagnostics.py index 6f4e7124d7034d..67ab7728d1c270 100644 --- a/tests/components/plugwise/test_diagnostics.py +++ b/tests/components/plugwise/test_diagnostics.py @@ -20,12 +20,10 @@ async def test_diagnostics( hass, hass_client, init_integration ) == { "gateway": { - "active_device": True, - "cooling_present": False, + "smile_name": "Adam", "gateway_id": "fe799307f1624099878210aa0b9f1475", "heater_id": "90986d591dcd426cae3ec3e8111ff730", - "single_master_thermostat": False, - "smile_name": "Adam", + "cooling_present": False, "notifications": { "af82e4ccf9c548528166d38e560662a4": { "warning": "Node Plug (with MAC address 000D6F000D13CB01, in room 'n.a.') has been unreachable since 23:03 2020-01-18. Please check the connection and restart the device." @@ -221,8 +219,7 @@ async def test_diagnostics( "lower_bound": 10, "upper_bound": 90, "resolution": 1, - "cooling_active": False, - "heating_state": True, + "binary_sensors": {"heating_state": True}, "sensors": { "water_temperature": 70.0, "intended_boiler_temperature": 70.0, From f921856f5fbea79717d8b2a10f5bf8e6e1bb8eb1 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 21 Feb 2022 00:17:31 +0000 Subject: [PATCH 0867/1098] [ci skip] Translation update --- .../components/airly/translations/el.json | 1 + .../components/airnow/translations/el.json | 1 + .../amberelectric/translations/el.json | 13 ++++-- .../aussie_broadband/translations/el.json | 3 ++ .../aussie_broadband/translations/es.json | 7 ++++ .../aussie_broadband/translations/tr.json | 7 ++++ .../azure_event_hub/translations/el.json | 3 ++ .../components/balboa/translations/el.json | 3 ++ .../bmw_connected_drive/translations/el.json | 3 ++ .../components/brunt/translations/el.json | 3 ++ .../components/bsblan/translations/el.json | 3 ++ .../components/climacell/translations/el.json | 1 + .../devolo_home_network/translations/el.json | 3 ++ .../components/dnsip/translations/es.json | 4 +- .../components/dnsip/translations/tr.json | 4 +- .../components/econet/translations/el.json | 6 +++ .../components/elkm1/translations/el.json | 3 +- .../components/elkm1/translations/es.json | 21 ++++++++-- .../components/elkm1/translations/tr.json | 30 ++++++++++++- .../components/emonitor/translations/el.json | 3 ++ .../evil_genius_labs/translations/el.json | 3 ++ .../components/ezviz/translations/el.json | 9 +++- .../faa_delays/translations/el.json | 1 + .../components/fivem/translations/es.json | 15 ++++++- .../components/fivem/translations/tr.json | 22 ++++++++++ .../components/flo/translations/el.json | 1 + .../components/foscam/translations/el.json | 1 + .../components/fronius/translations/el.json | 3 ++ .../components/hlk_sw16/translations/el.json | 1 + .../huisbaasje/translations/el.json | 3 ++ .../components/ialarm/translations/el.json | 3 ++ .../components/icloud/translations/el.json | 3 ++ .../components/insteon/translations/el.json | 1 + .../components/iss/translations/tr.json | 11 ++++- .../components/jellyfin/translations/el.json | 3 ++ .../keenetic_ndms2/translations/el.json | 3 ++ .../components/kmtronic/translations/el.json | 3 ++ .../components/knx/translations/el.json | 3 ++ .../components/kodi/translations/el.json | 1 + .../components/konnected/translations/el.json | 1 + .../kostal_plenticore/translations/el.json | 4 ++ .../litterrobot/translations/el.json | 3 ++ .../components/lookin/translations/el.json | 3 ++ .../components/mazda/translations/el.json | 1 + .../components/mjpeg/translations/el.json | 8 ++++ .../components/mjpeg/translations/es.json | 42 +++++++++++++++++++ .../components/mjpeg/translations/tr.json | 42 +++++++++++++++++++ .../moehlenhoff_alpha2/translations/es.json | 16 +++++++ .../moehlenhoff_alpha2/translations/tr.json | 19 +++++++++ .../motion_blinds/translations/el.json | 3 +- .../components/motioneye/translations/el.json | 3 +- .../components/mysensors/translations/el.json | 2 + .../components/netgear/translations/tr.json | 2 +- .../components/nuki/translations/el.json | 3 ++ .../components/nut/translations/el.json | 3 ++ .../components/nzbget/translations/el.json | 1 + .../components/overkiz/translations/tr.json | 1 + .../ovo_energy/translations/el.json | 1 + .../components/picnic/translations/es.json | 3 +- .../components/picnic/translations/tr.json | 4 +- .../components/plugwise/translations/el.json | 4 +- .../components/powerwall/translations/el.json | 3 ++ .../components/powerwall/translations/es.json | 3 ++ .../components/powerwall/translations/tr.json | 14 ++++++- .../pure_energie/translations/bg.json | 23 ++++++++++ .../pure_energie/translations/ca.json | 23 ++++++++++ .../pure_energie/translations/es.json | 23 ++++++++++ .../pure_energie/translations/hu.json | 23 ++++++++++ .../pure_energie/translations/id.json | 23 ++++++++++ .../pure_energie/translations/it.json | 23 ++++++++++ .../pure_energie/translations/ja.json | 23 ++++++++++ .../pure_energie/translations/pl.json | 23 ++++++++++ .../pure_energie/translations/tr.json | 23 ++++++++++ .../pure_energie/translations/zh-Hant.json | 23 ++++++++++ .../components/pvoutput/translations/el.json | 3 ++ .../components/rdw/translations/el.json | 1 + .../translations/el.json | 3 ++ .../components/roomba/translations/el.json | 1 + .../ruckus_unleashed/translations/el.json | 1 + .../components/senseme/translations/el.json | 3 ++ .../components/sensibo/translations/el.json | 3 ++ .../components/sharkiq/translations/el.json | 1 + .../components/shelly/translations/el.json | 1 + .../components/shelly/translations/fa.json | 11 +++++ .../simplisafe/translations/el.json | 3 ++ .../components/sleepiq/translations/el.json | 14 +++++++ .../components/sleepiq/translations/es.json | 16 +++++++ .../components/sleepiq/translations/tr.json | 19 +++++++++ .../smart_meter_texas/translations/el.json | 1 + .../components/solaredge/translations/el.json | 4 ++ .../components/solax/translations/el.json | 3 ++ .../somfy_mylink/translations/el.json | 6 +++ .../components/spider/translations/el.json | 1 + .../components/subaru/translations/el.json | 4 ++ .../tesla_wall_connector/translations/el.json | 3 ++ .../components/tolo/translations/el.json | 3 ++ .../totalconnect/translations/el.json | 3 ++ .../components/tuya/translations/el.json | 7 +++- .../tuya/translations/select.el.json | 3 +- .../tuya/translations/select.es.json | 19 +++++++++ .../tuya/translations/select.tr.json | 35 ++++++++++++++++ .../tuya/translations/sensor.es.json | 6 +++ .../tuya/translations/sensor.tr.json | 6 +++ .../unifiprotect/translations/el.json | 1 + .../components/vallox/translations/el.json | 6 +++ .../waze_travel_time/translations/el.json | 3 ++ .../components/wiz/translations/el.json | 1 + .../components/wiz/translations/es.json | 32 +++++++++++++- .../components/wiz/translations/tr.json | 37 ++++++++++++++++ .../components/wolflink/translations/el.json | 1 + .../components/zha/translations/el.json | 2 + .../components/zwave_js/translations/el.json | 4 ++ .../components/zwave_me/translations/es.json | 4 +- .../components/zwave_me/translations/tr.json | 20 +++++++++ 114 files changed, 899 insertions(+), 27 deletions(-) create mode 100644 homeassistant/components/fivem/translations/tr.json create mode 100644 homeassistant/components/mjpeg/translations/es.json create mode 100644 homeassistant/components/mjpeg/translations/tr.json create mode 100644 homeassistant/components/moehlenhoff_alpha2/translations/tr.json create mode 100644 homeassistant/components/pure_energie/translations/bg.json create mode 100644 homeassistant/components/pure_energie/translations/ca.json create mode 100644 homeassistant/components/pure_energie/translations/es.json create mode 100644 homeassistant/components/pure_energie/translations/hu.json create mode 100644 homeassistant/components/pure_energie/translations/id.json create mode 100644 homeassistant/components/pure_energie/translations/it.json create mode 100644 homeassistant/components/pure_energie/translations/ja.json create mode 100644 homeassistant/components/pure_energie/translations/pl.json create mode 100644 homeassistant/components/pure_energie/translations/tr.json create mode 100644 homeassistant/components/pure_energie/translations/zh-Hant.json create mode 100644 homeassistant/components/shelly/translations/fa.json create mode 100644 homeassistant/components/sleepiq/translations/el.json create mode 100644 homeassistant/components/sleepiq/translations/es.json create mode 100644 homeassistant/components/sleepiq/translations/tr.json create mode 100644 homeassistant/components/wiz/translations/tr.json create mode 100644 homeassistant/components/zwave_me/translations/tr.json diff --git a/homeassistant/components/airly/translations/el.json b/homeassistant/components/airly/translations/el.json index 79013ab88a42f2..ce9ec6fc8500e3 100644 --- a/homeassistant/components/airly/translations/el.json +++ b/homeassistant/components/airly/translations/el.json @@ -13,6 +13,7 @@ }, "system_health": { "info": { + "can_reach_server": "\u03a0\u03c1\u03bf\u03c3\u03ad\u03b3\u03b3\u03b9\u03c3\u03b7 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae Airly", "requests_per_day": "\u0395\u03c0\u03b9\u03c4\u03c1\u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03b1 \u03b1\u03b9\u03c4\u03ae\u03bc\u03b1\u03c4\u03b1 \u03b1\u03bd\u03ac \u03b7\u03bc\u03ad\u03c1\u03b1", "requests_remaining": "\u03a5\u03c0\u03bf\u03bb\u03b5\u03b9\u03c0\u03cc\u03bc\u03b5\u03bd\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03b1 \u03b1\u03b9\u03c4\u03ae\u03bc\u03b1\u03c4\u03b1" } diff --git a/homeassistant/components/airnow/translations/el.json b/homeassistant/components/airnow/translations/el.json index 8ca060f720e514..1ddb64a0420555 100644 --- a/homeassistant/components/airnow/translations/el.json +++ b/homeassistant/components/airnow/translations/el.json @@ -1,6 +1,7 @@ { "config": { "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "invalid_location": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03b1\u03c0\u03bf\u03c4\u03b5\u03bb\u03ad\u03c3\u03bc\u03b1\u03c4\u03b1 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7\u03bd \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1" }, "step": { diff --git a/homeassistant/components/amberelectric/translations/el.json b/homeassistant/components/amberelectric/translations/el.json index 0157f30c0f049f..b6e939ea46689a 100644 --- a/homeassistant/components/amberelectric/translations/el.json +++ b/homeassistant/components/amberelectric/translations/el.json @@ -3,12 +3,19 @@ "step": { "site": { "data": { - "site_name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2" + "site_name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2", + "site_nmi": "\u03a4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 NMI" }, - "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf NMI \u03c4\u03b7\u03c2 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5." + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf NMI \u03c4\u03b7\u03c2 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5.", + "title": "Amber Electric" }, "user": { - "description": "\u039c\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf {api_url} \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API" + "data": { + "api_token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API", + "site_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2" + }, + "description": "\u039c\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf {api_url} \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API", + "title": "Amber Electric" } } } diff --git a/homeassistant/components/aussie_broadband/translations/el.json b/homeassistant/components/aussie_broadband/translations/el.json index 94332a74183c4b..217746da084303 100644 --- a/homeassistant/components/aussie_broadband/translations/el.json +++ b/homeassistant/components/aussie_broadband/translations/el.json @@ -14,6 +14,9 @@ "description": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 {username}" }, "reauth_confirm": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 {username}" }, "service": { diff --git a/homeassistant/components/aussie_broadband/translations/es.json b/homeassistant/components/aussie_broadband/translations/es.json index 19640de6aa72ad..ff13c88c598587 100644 --- a/homeassistant/components/aussie_broadband/translations/es.json +++ b/homeassistant/components/aussie_broadband/translations/es.json @@ -7,6 +7,13 @@ "reauth": { "description": "Actualizar la contrase\u00f1a de {username}" }, + "reauth_confirm": { + "data": { + "password": "Contrase\u00f1a" + }, + "description": "Actualice la contrase\u00f1a para {username}", + "title": "Reautenticar Integraci\u00f3n" + }, "service": { "data": { "services": "Servicios" diff --git a/homeassistant/components/aussie_broadband/translations/tr.json b/homeassistant/components/aussie_broadband/translations/tr.json index 93c96064d43717..28eae33719db55 100644 --- a/homeassistant/components/aussie_broadband/translations/tr.json +++ b/homeassistant/components/aussie_broadband/translations/tr.json @@ -18,6 +18,13 @@ "description": "{username} i\u00e7in \u015fifreyi g\u00fcncelleyin", "title": "Entegrasyonu Yeniden Do\u011frula" }, + "reauth_confirm": { + "data": { + "password": "Parola" + }, + "description": "{username} i\u00e7in \u015fifreyi g\u00fcncelleyin", + "title": "Entegrasyonu Yeniden Do\u011frula" + }, "service": { "data": { "services": "Hizmetler" diff --git a/homeassistant/components/azure_event_hub/translations/el.json b/homeassistant/components/azure_event_hub/translations/el.json index c0011b9e240190..5ce9391d92a7b3 100644 --- a/homeassistant/components/azure_event_hub/translations/el.json +++ b/homeassistant/components/azure_event_hub/translations/el.json @@ -4,6 +4,9 @@ "cannot_connect": "\u0397 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03b1\u03c0\u03cc \u03c4\u03bf configuration.yaml \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5, \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03c6\u03b1\u03b9\u03c1\u03ad\u03c3\u03c4\u03b5 \u03b1\u03c0\u03cc \u03c4\u03bf yaml \u03ba\u03b1\u03b9 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03bf\u03ae \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c9\u03bd.", "unknown": "\u0397 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03b1\u03c0\u03cc \u03c4\u03bf configuration.yaml \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03bc\u03b5 \u03ac\u03b3\u03bd\u03c9\u03c3\u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1, \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03c6\u03b1\u03b9\u03c1\u03ad\u03c3\u03c4\u03b5 \u03c4\u03bf yaml \u03ba\u03b1\u03b9 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03bf\u03ae config." }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "conn_string": { "data": { diff --git a/homeassistant/components/balboa/translations/el.json b/homeassistant/components/balboa/translations/el.json index ba3d5b988037ec..df96ad6e341f78 100644 --- a/homeassistant/components/balboa/translations/el.json +++ b/homeassistant/components/balboa/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/bmw_connected_drive/translations/el.json b/homeassistant/components/bmw_connected_drive/translations/el.json index 72fc4ad7cd3a77..8c40d8bf1935ad 100644 --- a/homeassistant/components/bmw_connected_drive/translations/el.json +++ b/homeassistant/components/bmw_connected_drive/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/brunt/translations/el.json b/homeassistant/components/brunt/translations/el.json index fe92f1e0da8977..26b40b939f6be9 100644 --- a/homeassistant/components/brunt/translations/el.json +++ b/homeassistant/components/brunt/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "reauth_confirm": { "data": { diff --git a/homeassistant/components/bsblan/translations/el.json b/homeassistant/components/bsblan/translations/el.json index e77c2da80bdb1b..3918983f74630c 100644 --- a/homeassistant/components/bsblan/translations/el.json +++ b/homeassistant/components/bsblan/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, diff --git a/homeassistant/components/climacell/translations/el.json b/homeassistant/components/climacell/translations/el.json index 85dc0e7f4fe57c..7779ac3c5a5fd9 100644 --- a/homeassistant/components/climacell/translations/el.json +++ b/homeassistant/components/climacell/translations/el.json @@ -1,6 +1,7 @@ { "config": { "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "rate_limited": "\u0391\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7 \u03c3\u03c4\u03b9\u03b3\u03bc\u03ae \u03b7 \u03c4\u03b9\u03bc\u03ae \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1.", "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, diff --git a/homeassistant/components/devolo_home_network/translations/el.json b/homeassistant/components/devolo_home_network/translations/el.json index 68d64178efe681..628373910b5e43 100644 --- a/homeassistant/components/devolo_home_network/translations/el.json +++ b/homeassistant/components/devolo_home_network/translations/el.json @@ -3,6 +3,9 @@ "abort": { "home_control": "\u0397 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03ae \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 Home \u03c4\u03b7\u03c2 devolo \u03b4\u03b5\u03bd \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03b5\u03af \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7." }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "flow_title": "{product} ({name})", "step": { "user": { diff --git a/homeassistant/components/dnsip/translations/es.json b/homeassistant/components/dnsip/translations/es.json index 6952329f20e2ee..a5a51b76746db7 100644 --- a/homeassistant/components/dnsip/translations/es.json +++ b/homeassistant/components/dnsip/translations/es.json @@ -6,7 +6,9 @@ "step": { "user": { "data": { - "hostname": "El nombre de host para el que se realiza la consulta DNS" + "hostname": "El nombre de host para el que se realiza la consulta DNS", + "resolver": "Conversor para la b\u00fasqueda de IPV4", + "resolver_ipv6": "Conversor para la b\u00fasqueda de IPV6" } } } diff --git a/homeassistant/components/dnsip/translations/tr.json b/homeassistant/components/dnsip/translations/tr.json index 95f0c45bb6b17e..d53abc4fc135cd 100644 --- a/homeassistant/components/dnsip/translations/tr.json +++ b/homeassistant/components/dnsip/translations/tr.json @@ -6,7 +6,9 @@ "step": { "user": { "data": { - "hostname": "DNS sorgusunun ger\u00e7ekle\u015ftirilece\u011fi ana bilgisayar ad\u0131" + "hostname": "DNS sorgusunun ger\u00e7ekle\u015ftirilece\u011fi ana bilgisayar ad\u0131", + "resolver": "IPV4 aramas\u0131 i\u00e7in \u00e7\u00f6z\u00fcmleyici", + "resolver_ipv6": "IPV6 aramas\u0131 i\u00e7in \u00e7\u00f6z\u00fcmleyici" } } } diff --git a/homeassistant/components/econet/translations/el.json b/homeassistant/components/econet/translations/el.json index 2185a71ed75a91..402d8354fdbce6 100644 --- a/homeassistant/components/econet/translations/el.json +++ b/homeassistant/components/econet/translations/el.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/elkm1/translations/el.json b/homeassistant/components/elkm1/translations/el.json index 6e95bc0cb3684c..645c2a57097ef5 100644 --- a/homeassistant/components/elkm1/translations/el.json +++ b/homeassistant/components/elkm1/translations/el.json @@ -2,7 +2,8 @@ "config": { "abort": { "address_already_configured": "\u0388\u03bd\u03b1 ElkM1 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", - "already_configured": "\u0388\u03bd\u03b1 ElkM1 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + "already_configured": "\u0388\u03bd\u03b1 ElkM1 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "flow_title": "{mac_address} ({host})", "step": { diff --git a/homeassistant/components/elkm1/translations/es.json b/homeassistant/components/elkm1/translations/es.json index ecda52c8c5b305..06c0d9e257e64e 100644 --- a/homeassistant/components/elkm1/translations/es.json +++ b/homeassistant/components/elkm1/translations/es.json @@ -2,25 +2,38 @@ "config": { "abort": { "address_already_configured": "Ya est\u00e1 configurado un Elk-M1 con esta direcci\u00f3n", - "already_configured": "Ya est\u00e1 configurado un Elk-M1 con este prefijo" + "already_configured": "Ya est\u00e1 configurado un Elk-M1 con este prefijo", + "already_in_progress": "La configuraci\u00f3n ya se encuentra en proceso", + "cannot_connect": "Error al conectar" }, "error": { "cannot_connect": "No se pudo conectar", "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "unknown": "Error inesperado" }, + "flow_title": "{mac_address} ({host})", "step": { "discovered_connection": { - "description": "Con\u00e9ctese al sistema detectado: {mac_address} ({host})" + "data": { + "password": "Contrase\u00f1a", + "protocol": "Protocolo", + "temperature_unit": "La unidad de temperatura que el ElkM1 usa.", + "username": "Usuario" + }, + "description": "Con\u00e9ctese al sistema detectado: {mac_address} ({host})", + "title": "Conectar con Control Elk-M1" }, "manual_connection": { "data": { "address": "La direcci\u00f3n IP o el dominio o el puerto serie si se conecta a trav\u00e9s de serie.", + "password": "Contrase\u00f1a", "prefix": "Un prefijo \u00fanico (dejar en blanco si solo tiene un ElkM1).", "protocol": "Protocolo", - "temperature_unit": "La unidad de temperatura que utiliza ElkM1." + "temperature_unit": "La unidad de temperatura que utiliza ElkM1.", + "username": "Usuario" }, - "description": "Conecte un M\u00f3dulo de Interfaz Universal Powerline Bus Powerline (UPB PIM). La cadena de direcci\u00f3n debe tener el formato 'direcci\u00f3n [: puerto]' para 'tcp'. El puerto es opcional y el valor predeterminado es 2101. Ejemplo: '192.168.1.42'. Para el protocolo serie, la direcci\u00f3n debe estar en la forma 'tty [: baudios]'. El baud es opcional y el valor predeterminado es 4800. Ejemplo: '/ dev / ttyS1'." + "description": "Conecte un M\u00f3dulo de Interfaz Universal Powerline Bus Powerline (UPB PIM). La cadena de direcci\u00f3n debe tener el formato 'direcci\u00f3n [: puerto]' para 'tcp'. El puerto es opcional y el valor predeterminado es 2101. Ejemplo: '192.168.1.42'. Para el protocolo serie, la direcci\u00f3n debe estar en la forma 'tty [: baudios]'. El baud es opcional y el valor predeterminado es 4800. Ejemplo: '/ dev / ttyS1'.", + "title": "Conectar con Control Elk-M1" }, "user": { "data": { diff --git a/homeassistant/components/elkm1/translations/tr.json b/homeassistant/components/elkm1/translations/tr.json index 3b62ad5e079914..e8f147c8d5798c 100644 --- a/homeassistant/components/elkm1/translations/tr.json +++ b/homeassistant/components/elkm1/translations/tr.json @@ -2,15 +2,28 @@ "config": { "abort": { "address_already_configured": "Bu adrese sahip bir ElkM1 zaten yap\u0131land\u0131r\u0131lm\u0131\u015ft\u0131r", - "already_configured": "Bu \u00f6nek ile bir ElkM1 zaten yap\u0131land\u0131r\u0131lm\u0131\u015ft\u0131r" + "already_configured": "Bu \u00f6nek ile bir ElkM1 zaten yap\u0131land\u0131r\u0131lm\u0131\u015ft\u0131r", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "cannot_connect": "Ba\u011flanma hatas\u0131" }, "error": { "cannot_connect": "Ba\u011flanma hatas\u0131", "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", "unknown": "Beklenmeyen hata" }, + "flow_title": "{mac_address} ({host})", "step": { - "user": { + "discovered_connection": { + "data": { + "password": "Parola", + "protocol": "Protokol", + "temperature_unit": "ElkM1'in kulland\u0131\u011f\u0131 s\u0131cakl\u0131k birimi.", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "description": "Ke\u015ffedilen sisteme ba\u011flan\u0131n: {mac_address} ( {host} )", + "title": "Elk-M1 Kontrol\u00fcne Ba\u011flan\u0131n" + }, + "manual_connection": { "data": { "address": "Seri yoluyla ba\u011flan\u0131l\u0131yorsa IP adresi veya etki alan\u0131 veya seri ba\u011flant\u0131 noktas\u0131.", "password": "Parola", @@ -21,6 +34,19 @@ }, "description": "Adres dizesi, 'g\u00fcvenli' ve 'g\u00fcvenli olmayan' i\u00e7in 'adres[:port]' bi\u00e7iminde olmal\u0131d\u0131r. \u00d6rnek: '192.168.1.1'. Ba\u011flant\u0131 noktas\u0131 iste\u011fe ba\u011fl\u0131d\u0131r ve varsay\u0131lan olarak 'g\u00fcvenli olmayan' i\u00e7in 2101 ve 'g\u00fcvenli' i\u00e7in 2601'dir. Seri protokol i\u00e7in adres 'tty[:baud]' bi\u00e7iminde olmal\u0131d\u0131r. \u00d6rnek: '/dev/ttyS1'. Baud iste\u011fe ba\u011fl\u0131d\u0131r ve varsay\u0131lan olarak 115200'd\u00fcr.", "title": "Elk-M1 Kontrol\u00fcne Ba\u011flan\u0131n" + }, + "user": { + "data": { + "address": "Seri yoluyla ba\u011flan\u0131l\u0131yorsa IP adresi veya etki alan\u0131 veya seri ba\u011flant\u0131 noktas\u0131.", + "device": "Cihaz", + "password": "Parola", + "prefix": "Benzersiz bir \u00f6nek (yaln\u0131zca bir ElkM1'iniz varsa bo\u015f b\u0131rak\u0131n).", + "protocol": "Protokol", + "temperature_unit": "ElkM1'in kulland\u0131\u011f\u0131 s\u0131cakl\u0131k birimi.", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "description": "Ke\u015ffedilen bir sistem veya ke\u015ffedilmemi\u015fse 'Manuel Giri\u015f' se\u00e7in.", + "title": "Elk-M1 Kontrol\u00fcne Ba\u011flan\u0131n" } } } diff --git a/homeassistant/components/emonitor/translations/el.json b/homeassistant/components/emonitor/translations/el.json index 436a00cbfda444..64f4b88cecdb54 100644 --- a/homeassistant/components/emonitor/translations/el.json +++ b/homeassistant/components/emonitor/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "flow_title": "{name}", "step": { "confirm": { diff --git a/homeassistant/components/evil_genius_labs/translations/el.json b/homeassistant/components/evil_genius_labs/translations/el.json index b1115d5bf2c1e6..2ac7bbe8ac4688 100644 --- a/homeassistant/components/evil_genius_labs/translations/el.json +++ b/homeassistant/components/evil_genius_labs/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/ezviz/translations/el.json b/homeassistant/components/ezviz/translations/el.json index 1c6e2fd9809ced..d81c3fb19fdae7 100644 --- a/homeassistant/components/ezviz/translations/el.json +++ b/homeassistant/components/ezviz/translations/el.json @@ -3,11 +3,15 @@ "abort": { "ezviz_cloud_account_missing": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 Ezviz cloud \u03bb\u03b5\u03af\u03c0\u03b5\u03b9. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc Ezviz cloud" }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "flow_title": "{serial}", "step": { "confirm": { "data": { - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 RTSP \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03ba\u03ac\u03bc\u03b5\u03c1\u03b1 Ezviz {serial} \u03bc\u03b5 IP {ip_address}", "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03ba\u03ac\u03bc\u03b5\u03c1\u03b1 Ezviz" @@ -20,7 +24,8 @@ }, "user_custom_url": { "data": { - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03bf\u03c2 \u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03c4\u03b7\u03c2 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae\u03c2 \u03c3\u03b1\u03c2", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03b5 \u03c0\u03c1\u03bf\u03c3\u03b1\u03c1\u03bc\u03bf\u03c3\u03bc\u03ad\u03bd\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c4\u03bf\u03c5 Ezviz" diff --git a/homeassistant/components/faa_delays/translations/el.json b/homeassistant/components/faa_delays/translations/el.json index fd575836741bc1..7aa5269fc2bd74 100644 --- a/homeassistant/components/faa_delays/translations/el.json +++ b/homeassistant/components/faa_delays/translations/el.json @@ -4,6 +4,7 @@ "already_configured": "\u0391\u03c5\u03c4\u03cc \u03c4\u03bf \u03b1\u03b5\u03c1\u03bf\u03b4\u03c1\u03cc\u03bc\u03b9\u03bf \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af." }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "invalid_airport": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b1\u03b5\u03c1\u03bf\u03b4\u03c1\u03bf\u03bc\u03af\u03bf\u03c5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2" }, "step": { diff --git a/homeassistant/components/fivem/translations/es.json b/homeassistant/components/fivem/translations/es.json index be6eaaee4360af..d8b3f3c6e1cb7b 100644 --- a/homeassistant/components/fivem/translations/es.json +++ b/homeassistant/components/fivem/translations/es.json @@ -1,9 +1,22 @@ { "config": { + "abort": { + "already_configured": "El servicio ya est\u00e1 configurado" + }, "error": { "cannot_connect": "Error al conectarse. Compruebe el host y el puerto e int\u00e9ntelo de nuevo. Aseg\u00farese tambi\u00e9n de que est\u00e1 ejecutando el servidor FiveM m\u00e1s reciente.", "invalid_game_name": "La API del juego al que intentas conectarte no es un juego de FiveM.", - "invalid_gamename": "La API del juego al que intentas conectarte no es un juego de FiveM." + "invalid_gamename": "La API del juego al que intentas conectarte no es un juego de FiveM.", + "unknown_error": "Error inesperado" + }, + "step": { + "user": { + "data": { + "host": "Anfitri\u00f3n", + "name": "Nombre", + "port": "Puerto" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/tr.json b/homeassistant/components/fivem/translations/tr.json new file mode 100644 index 00000000000000..46921dd33c01fa --- /dev/null +++ b/homeassistant/components/fivem/translations/tr.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131. L\u00fctfen ana bilgisayar\u0131 ve ba\u011flant\u0131 noktas\u0131n\u0131 kontrol edin ve tekrar deneyin. Ayr\u0131ca en son FiveM sunucusunu \u00e7al\u0131\u015ft\u0131rd\u0131\u011f\u0131n\u0131zdan emin olun.", + "invalid_game_name": "Ba\u011flanmaya \u00e7al\u0131\u015ft\u0131\u011f\u0131n\u0131z oyunun api'si bir FiveM oyunu de\u011fil.", + "invalid_gamename": "Ba\u011flanmaya \u00e7al\u0131\u015ft\u0131\u011f\u0131n\u0131z oyunun api'si bir FiveM oyunu de\u011fil.", + "unknown_error": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "host": "Sunucu", + "name": "Ad", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flo/translations/el.json b/homeassistant/components/flo/translations/el.json index 54b55bb8324647..5260b0f7170c52 100644 --- a/homeassistant/components/flo/translations/el.json +++ b/homeassistant/components/flo/translations/el.json @@ -4,6 +4,7 @@ "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } diff --git a/homeassistant/components/foscam/translations/el.json b/homeassistant/components/foscam/translations/el.json index 774739eea7b67d..ef456c825d0fef 100644 --- a/homeassistant/components/foscam/translations/el.json +++ b/homeassistant/components/foscam/translations/el.json @@ -1,6 +1,7 @@ { "config": { "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "invalid_response": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03c0\u03ac\u03bd\u03c4\u03b7\u03c3\u03b7 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" }, "step": { diff --git a/homeassistant/components/fronius/translations/el.json b/homeassistant/components/fronius/translations/el.json index 611a3ebce8e55c..cce92f7794aaf6 100644 --- a/homeassistant/components/fronius/translations/el.json +++ b/homeassistant/components/fronius/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "flow_title": "{device}", "step": { "confirm_discovery": { diff --git a/homeassistant/components/hlk_sw16/translations/el.json b/homeassistant/components/hlk_sw16/translations/el.json index 54b55bb8324647..5260b0f7170c52 100644 --- a/homeassistant/components/hlk_sw16/translations/el.json +++ b/homeassistant/components/hlk_sw16/translations/el.json @@ -4,6 +4,7 @@ "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } diff --git a/homeassistant/components/huisbaasje/translations/el.json b/homeassistant/components/huisbaasje/translations/el.json index bab52704f790ea..118803f915bcd0 100644 --- a/homeassistant/components/huisbaasje/translations/el.json +++ b/homeassistant/components/huisbaasje/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/ialarm/translations/el.json b/homeassistant/components/ialarm/translations/el.json index 618e1c4e0d22c7..07740adf2e1b0e 100644 --- a/homeassistant/components/ialarm/translations/el.json +++ b/homeassistant/components/ialarm/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/icloud/translations/el.json b/homeassistant/components/icloud/translations/el.json index bb29747330ea81..19d983fd718182 100644 --- a/homeassistant/components/icloud/translations/el.json +++ b/homeassistant/components/icloud/translations/el.json @@ -9,6 +9,9 @@ }, "step": { "reauth": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c0\u03bf\u03c5 \u03b5\u03af\u03c7\u03b1\u03c4\u03b5 \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03b9 \u03c0\u03c1\u03bf\u03b7\u03b3\u03bf\u03c5\u03bc\u03ad\u03bd\u03c9\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username} \u03b4\u03b5\u03bd \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03b5\u03af \u03c0\u03bb\u03ad\u03bf\u03bd. \u0395\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7." }, "trusted_device": { diff --git a/homeassistant/components/insteon/translations/el.json b/homeassistant/components/insteon/translations/el.json index a3635ae5e2eb9b..fbdb51209a0b68 100644 --- a/homeassistant/components/insteon/translations/el.json +++ b/homeassistant/components/insteon/translations/el.json @@ -63,6 +63,7 @@ "change_hub_config": { "data": { "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0391\u03bb\u03bb\u03ac\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 Insteon Hub. \u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Home Assistant \u03bc\u03b5\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03b1\u03b3\u03bc\u03b1\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b1\u03c5\u03c4\u03ae\u03c2 \u03c4\u03b7\u03c2 \u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2. \u0391\u03c5\u03c4\u03cc \u03b4\u03b5\u03bd \u03b1\u03bb\u03bb\u03ac\u03b6\u03b5\u03b9 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03af\u03b4\u03b9\u03bf\u03c5 \u03c4\u03bf\u03c5 Hub. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03bb\u03bb\u03ac\u03be\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c3\u03c4\u03bf Hub \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae Hub.", diff --git a/homeassistant/components/iss/translations/tr.json b/homeassistant/components/iss/translations/tr.json index 07f374b8a17c45..3cb92db229c276 100644 --- a/homeassistant/components/iss/translations/tr.json +++ b/homeassistant/components/iss/translations/tr.json @@ -9,7 +9,16 @@ "data": { "show_on_map": "Haritada g\u00f6sterilsin mi?" }, - "description": "Uluslararas\u0131 Uzay \u0130stasyonunu yap\u0131land\u0131rmak istiyor musunuz?" + "description": "Uluslararas\u0131 Uzay \u0130stasyonunu (ISS) yap\u0131land\u0131rmak istiyor musunuz?" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "Haritada g\u00f6ster" + } } } } diff --git a/homeassistant/components/jellyfin/translations/el.json b/homeassistant/components/jellyfin/translations/el.json index bab52704f790ea..118803f915bcd0 100644 --- a/homeassistant/components/jellyfin/translations/el.json +++ b/homeassistant/components/jellyfin/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/keenetic_ndms2/translations/el.json b/homeassistant/components/keenetic_ndms2/translations/el.json index c76b5ba725201d..071014416e3175 100644 --- a/homeassistant/components/keenetic_ndms2/translations/el.json +++ b/homeassistant/components/keenetic_ndms2/translations/el.json @@ -4,6 +4,9 @@ "no_udn": "\u039f\u03b9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7\u03c2 SSDP \u03b4\u03b5\u03bd \u03ad\u03c7\u03bf\u03c5\u03bd UDN", "not_keenetic_ndms2": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae\u03c2 Keenetic" }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "flow_title": "{name} ({host})", "step": { "user": { diff --git a/homeassistant/components/kmtronic/translations/el.json b/homeassistant/components/kmtronic/translations/el.json index 2646eb1883ec8f..e17dece727749a 100644 --- a/homeassistant/components/kmtronic/translations/el.json +++ b/homeassistant/components/kmtronic/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/knx/translations/el.json b/homeassistant/components/knx/translations/el.json index 57714e8ee956b3..59687ef8ad6558 100644 --- a/homeassistant/components/knx/translations/el.json +++ b/homeassistant/components/knx/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "manual_tunnel": { "data": { diff --git a/homeassistant/components/kodi/translations/el.json b/homeassistant/components/kodi/translations/el.json index d5a3c1815e13ed..0b9c8c36f08f95 100644 --- a/homeassistant/components/kodi/translations/el.json +++ b/homeassistant/components/kodi/translations/el.json @@ -4,6 +4,7 @@ "step": { "credentials": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 Kodi. \u0391\u03c5\u03c4\u03ac \u03bc\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03b2\u03c1\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03a3\u03cd\u03c3\u03c4\u03b7\u03bc\u03b1 / \u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 / \u0394\u03af\u03ba\u03c4\u03c5\u03bf / \u03a5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b5\u03c2." diff --git a/homeassistant/components/konnected/translations/el.json b/homeassistant/components/konnected/translations/el.json index fa290b1bfdca82..df3f20ee792e64 100644 --- a/homeassistant/components/konnected/translations/el.json +++ b/homeassistant/components/konnected/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "not_konn_panel": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Konnected.io" }, "step": { diff --git a/homeassistant/components/kostal_plenticore/translations/el.json b/homeassistant/components/kostal_plenticore/translations/el.json index d927982617cde4..0dd1218ec1ae25 100644 --- a/homeassistant/components/kostal_plenticore/translations/el.json +++ b/homeassistant/components/kostal_plenticore/translations/el.json @@ -1,5 +1,9 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/litterrobot/translations/el.json b/homeassistant/components/litterrobot/translations/el.json index bab52704f790ea..118803f915bcd0 100644 --- a/homeassistant/components/litterrobot/translations/el.json +++ b/homeassistant/components/litterrobot/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/lookin/translations/el.json b/homeassistant/components/lookin/translations/el.json index 316feac28c6cc6..9c4482a2e00991 100644 --- a/homeassistant/components/lookin/translations/el.json +++ b/homeassistant/components/lookin/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "flow_title": "{name} ({host})", "step": { "device_name": { diff --git a/homeassistant/components/mazda/translations/el.json b/homeassistant/components/mazda/translations/el.json index 5f30894b381b69..80e09569cf52da 100644 --- a/homeassistant/components/mazda/translations/el.json +++ b/homeassistant/components/mazda/translations/el.json @@ -2,6 +2,7 @@ "config": { "error": { "account_locked": "\u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ba\u03bb\u03b5\u03b9\u03b4\u03c9\u03bc\u03ad\u03bd\u03bf\u03c2. \u03a0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1.", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "unknown": "\u039c\u03b7 \u03b1\u03bd\u03b1\u03bc\u03b5\u03bd\u03cc\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { diff --git a/homeassistant/components/mjpeg/translations/el.json b/homeassistant/components/mjpeg/translations/el.json index 404adec7a94f7b..2dbff2ebe2cc96 100644 --- a/homeassistant/components/mjpeg/translations/el.json +++ b/homeassistant/components/mjpeg/translations/el.json @@ -1,19 +1,27 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { "mjpeg_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 MJPEG URL", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "still_image_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae\u03c2 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1\u03c2" } } } }, "options": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "init": { "data": { "mjpeg_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 MJPEG URL", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "still_image_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae\u03c2 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1\u03c2" } } diff --git a/homeassistant/components/mjpeg/translations/es.json b/homeassistant/components/mjpeg/translations/es.json new file mode 100644 index 00000000000000..113193e3832ec9 --- /dev/null +++ b/homeassistant/components/mjpeg/translations/es.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya se encuentra configurado" + }, + "error": { + "cannot_connect": "Error al conectar", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "mjpeg_url": "URL MJPEG", + "name": "Nombre", + "password": "Contrase\u00f1a", + "still_image_url": "URL de imagen est\u00e1tica", + "username": "Usuario", + "verify_ssl": "Verifique el certificado SSL" + } + } + } + }, + "options": { + "error": { + "already_configured": "El dispositivo ya se encuentra configurado", + "cannot_connect": "Error al conectar", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida" + }, + "step": { + "init": { + "data": { + "mjpeg_url": "URL MJPEG", + "name": "Nombre", + "password": "Contrase\u00f1a", + "still_image_url": "URL de imagen est\u00e1tica", + "username": "Nombre de Usuario", + "verify_ssl": "Verifique el certificado SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mjpeg/translations/tr.json b/homeassistant/components/mjpeg/translations/tr.json new file mode 100644 index 00000000000000..b0c5d3b814dac4 --- /dev/null +++ b/homeassistant/components/mjpeg/translations/tr.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "mjpeg_url": "MJPEG URL'si", + "name": "Ad", + "password": "Parola", + "still_image_url": "Sabit Resim URL'si", + "username": "Kullan\u0131c\u0131 Ad\u0131", + "verify_ssl": "SSL sertifikas\u0131n\u0131 do\u011frulay\u0131n" + } + } + } + }, + "options": { + "error": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "init": { + "data": { + "mjpeg_url": "MJPEG URL'si", + "name": "Ad", + "password": "Parola", + "still_image_url": "Sabit Resim URL'si", + "username": "Kullan\u0131c\u0131 Ad\u0131", + "verify_ssl": "SSL sertifikas\u0131n\u0131 do\u011frulay\u0131n" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/es.json b/homeassistant/components/moehlenhoff_alpha2/translations/es.json index d15111e97b0d19..81e3d96b7bc520 100644 --- a/homeassistant/components/moehlenhoff_alpha2/translations/es.json +++ b/homeassistant/components/moehlenhoff_alpha2/translations/es.json @@ -1,3 +1,19 @@ { + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Error al conectar", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "host": "Anfitri\u00f3n" + } + } + } + }, "title": "M\u00f6hlenhoff Alpha2" } \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/tr.json b/homeassistant/components/moehlenhoff_alpha2/translations/tr.json new file mode 100644 index 00000000000000..ff0498a01a1149 --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "host": "Sunucu" + } + } + } + }, + "title": "M\u00f6hlenhoff Alpha2" +} \ No newline at end of file diff --git a/homeassistant/components/motion_blinds/translations/el.json b/homeassistant/components/motion_blinds/translations/el.json index edd1051671cf23..4271012e41ca36 100644 --- a/homeassistant/components/motion_blinds/translations/el.json +++ b/homeassistant/components/motion_blinds/translations/el.json @@ -23,7 +23,8 @@ "data": { "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" }, - "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf Motion Gateway, \u03b5\u03ac\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b7 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7" + "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf Motion Gateway, \u03b5\u03ac\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b7 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7", + "title": "Motion Blinds" } } }, diff --git a/homeassistant/components/motioneye/translations/el.json b/homeassistant/components/motioneye/translations/el.json index 3ecf4413e11a8b..5f47bf22e5e8db 100644 --- a/homeassistant/components/motioneye/translations/el.json +++ b/homeassistant/components/motioneye/translations/el.json @@ -5,7 +5,8 @@ }, "step": { "hassio_confirm": { - "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03bf\u03c5\u03c2 \u03c4\u03bf\u03c5 Home Assistant \u03ce\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03b5\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03c4\u03b7\u03bd \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 motionEye \u03c0\u03bf\u03c5 \u03c0\u03b1\u03c1\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf: {addon};" + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03bf\u03c5\u03c2 \u03c4\u03bf\u03c5 Home Assistant \u03ce\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03b5\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03c4\u03b7\u03bd \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 motionEye \u03c0\u03bf\u03c5 \u03c0\u03b1\u03c1\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf: {addon};", + "title": "motionEye \u03bc\u03ad\u03c3\u03c9 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Home Assistant" }, "user": { "data": { diff --git a/homeassistant/components/mysensors/translations/el.json b/homeassistant/components/mysensors/translations/el.json index 1edd6c06fc2e47..d706ff53238ebc 100644 --- a/homeassistant/components/mysensors/translations/el.json +++ b/homeassistant/components/mysensors/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "duplicate_persistence_file": "\u03a4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf persistence \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7", "duplicate_topic": "\u03a4\u03bf \u03b8\u03ad\u03bc\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7", "invalid_device": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae", @@ -16,6 +17,7 @@ "same_topic": "\u03a4\u03b1 \u03b8\u03ad\u03bc\u03b1\u03c4\u03b1 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2 \u03ba\u03b1\u03b9 \u03b4\u03b7\u03bc\u03bf\u03c3\u03af\u03b5\u03c5\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c4\u03b1 \u03af\u03b4\u03b9\u03b1" }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "duplicate_persistence_file": "\u03a4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf persistence \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7", "duplicate_topic": "\u03a4\u03bf \u03b8\u03ad\u03bc\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7", "invalid_device": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae", diff --git a/homeassistant/components/netgear/translations/tr.json b/homeassistant/components/netgear/translations/tr.json index 07066485015cc6..b29a9f075aa488 100644 --- a/homeassistant/components/netgear/translations/tr.json +++ b/homeassistant/components/netgear/translations/tr.json @@ -15,7 +15,7 @@ "ssl": "SSL sertifikas\u0131 kullan\u0131r", "username": "Kullan\u0131c\u0131 Ad\u0131 (\u0130ste\u011fe ba\u011fl\u0131)" }, - "description": "Varsay\u0131lan ana bilgisayar: {host}\n Varsay\u0131lan ba\u011flant\u0131 noktas\u0131: {port}\n Varsay\u0131lan kullan\u0131c\u0131 ad\u0131: {username}", + "description": "Varsay\u0131lan sunucu: {host}\nVarsay\u0131lan kullan\u0131c\u0131 ad\u0131: {username}", "title": "Netgear" } } diff --git a/homeassistant/components/nuki/translations/el.json b/homeassistant/components/nuki/translations/el.json index db3aa9c03d38e1..237fb7e22a4ab5 100644 --- a/homeassistant/components/nuki/translations/el.json +++ b/homeassistant/components/nuki/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "reauth_confirm": { "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Nuki \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03bc\u03b5 \u03c4\u03b7 \u03b3\u03ad\u03c6\u03c5\u03c1\u03ac \u03c3\u03b1\u03c2." diff --git a/homeassistant/components/nut/translations/el.json b/homeassistant/components/nut/translations/el.json index 6d28f2d1842a4d..1e6a773c02d119 100644 --- a/homeassistant/components/nut/translations/el.json +++ b/homeassistant/components/nut/translations/el.json @@ -25,6 +25,9 @@ } }, "options": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/nzbget/translations/el.json b/homeassistant/components/nzbget/translations/el.json index 2d28d795e04b99..9a00d825777429 100644 --- a/homeassistant/components/nzbget/translations/el.json +++ b/homeassistant/components/nzbget/translations/el.json @@ -12,6 +12,7 @@ "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf NZBGet" diff --git a/homeassistant/components/overkiz/translations/tr.json b/homeassistant/components/overkiz/translations/tr.json index 166b4dc48af44a..8e6a232db7f298 100644 --- a/homeassistant/components/overkiz/translations/tr.json +++ b/homeassistant/components/overkiz/translations/tr.json @@ -12,6 +12,7 @@ "too_many_requests": "\u00c7ok fazla istek var, daha sonra tekrar deneyin", "unknown": "Beklenmeyen hata" }, + "flow_title": "A\u011f ge\u00e7idi: {gateway_id}", "step": { "user": { "data": { diff --git a/homeassistant/components/ovo_energy/translations/el.json b/homeassistant/components/ovo_energy/translations/el.json index c56dbc9bc45bb2..9d11ed632734e2 100644 --- a/homeassistant/components/ovo_energy/translations/el.json +++ b/homeassistant/components/ovo_energy/translations/el.json @@ -10,6 +10,7 @@ }, "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1 OVO Energy \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03c0\u03bf\u03ba\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03b5\u03bd\u03ad\u03c1\u03b3\u03b5\u03b9\u03b1\u03c2.", diff --git a/homeassistant/components/picnic/translations/es.json b/homeassistant/components/picnic/translations/es.json index 3974c507fd4015..f7a170871efaea 100644 --- a/homeassistant/components/picnic/translations/es.json +++ b/homeassistant/components/picnic/translations/es.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "El dispositivo ya est\u00e1 configurado" + "already_configured": "El dispositivo ya est\u00e1 configurado", + "reauth_successful": "Reauntenticaci\u00f3n exitosa" }, "error": { "cannot_connect": "No se pudo conectar", diff --git a/homeassistant/components/picnic/translations/tr.json b/homeassistant/components/picnic/translations/tr.json index 242b4ae4e6a976..b689f65ff9631f 100644 --- a/homeassistant/components/picnic/translations/tr.json +++ b/homeassistant/components/picnic/translations/tr.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" }, "error": { "cannot_connect": "Ba\u011flanma hatas\u0131", + "different_account": "Hesap, entegrasyonu ayarlamak i\u00e7in kullan\u0131lanla ayn\u0131 olmal\u0131d\u0131r", "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", "unknown": "Beklenmeyen hata" }, diff --git a/homeassistant/components/plugwise/translations/el.json b/homeassistant/components/plugwise/translations/el.json index a01a22cda23a32..b52262e8602753 100644 --- a/homeassistant/components/plugwise/translations/el.json +++ b/homeassistant/components/plugwise/translations/el.json @@ -12,10 +12,12 @@ "user_gateway": { "data": { "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", + "password": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc Smile", "port": "\u0398\u03cd\u03c1\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 Smile" }, - "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5" + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5", + "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf Smile" } } }, diff --git a/homeassistant/components/powerwall/translations/el.json b/homeassistant/components/powerwall/translations/el.json index eba13432262f1a..59bd52f3b0e06b 100644 --- a/homeassistant/components/powerwall/translations/el.json +++ b/homeassistant/components/powerwall/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "error": { "wrong_version": "\u03a4\u03bf powerwall \u03c3\u03b1\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b9\u03c3\u03bc\u03b9\u03ba\u03bf\u03cd \u03c0\u03bf\u03c5 \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9. \u03a3\u03ba\u03b5\u03c6\u03c4\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b1\u03b2\u03b1\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03ae \u03bd\u03b1 \u03b1\u03bd\u03b1\u03c6\u03ad\u03c1\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c0\u03c1\u03cc\u03b2\u03bb\u03b7\u03bc\u03b1 \u03ce\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03c0\u03b9\u03bb\u03c5\u03b8\u03b5\u03af." }, diff --git a/homeassistant/components/powerwall/translations/es.json b/homeassistant/components/powerwall/translations/es.json index f2beb19d5dac98..767f77e58bdd85 100644 --- a/homeassistant/components/powerwall/translations/es.json +++ b/homeassistant/components/powerwall/translations/es.json @@ -12,6 +12,9 @@ }, "flow_title": "Powerwall de Tesla ({ip_address})", "step": { + "reauth_confim": { + "title": "Reautorizar la powerwall" + }, "user": { "data": { "ip_address": "Direcci\u00f3n IP", diff --git a/homeassistant/components/powerwall/translations/tr.json b/homeassistant/components/powerwall/translations/tr.json index a243e22b566102..48c076d8eedac0 100644 --- a/homeassistant/components/powerwall/translations/tr.json +++ b/homeassistant/components/powerwall/translations/tr.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131", "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" }, "error": { @@ -10,8 +11,19 @@ "unknown": "Beklenmeyen hata", "wrong_version": "G\u00fc\u00e7 duvar\u0131n\u0131z desteklenmeyen bir yaz\u0131l\u0131m s\u00fcr\u00fcm\u00fc kullan\u0131yor. \u00c7\u00f6z\u00fclebilmesi i\u00e7in l\u00fctfen bu sorunu y\u00fckseltmeyi veya bildirmeyi d\u00fc\u015f\u00fcn\u00fcn." }, - "flow_title": "{ip_address}", + "flow_title": "{name} ({ip_address})", "step": { + "confirm_discovery": { + "description": "{name} ( {ip_address} ) kurulumu yapmak istiyor musunuz?", + "title": "Powerwall'a ba\u011flan\u0131n" + }, + "reauth_confim": { + "data": { + "password": "Parola" + }, + "description": "Parola genellikle Backup Gateway i\u00e7in seri numaras\u0131n\u0131n son 5 karakteridir ve Tesla uygulamas\u0131nda veya Backup Gateway 2 i\u00e7in kap\u0131n\u0131n i\u00e7inde bulunan parolan\u0131n son 5 karakterinde bulunabilir.", + "title": "Powerwall'\u0131 yeniden do\u011frulay\u0131n" + }, "user": { "data": { "ip_address": "\u0130p Adresi", diff --git a/homeassistant/components/pure_energie/translations/bg.json b/homeassistant/components/pure_energie/translations/bg.json new file mode 100644 index 00000000000000..93c06fc23630c7 --- /dev/null +++ b/homeassistant/components/pure_energie/translations/bg.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e", + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" + }, + "error": { + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" + }, + "flow_title": "{model} ({host})", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + } + }, + "zeroconf_confirm": { + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u0435 Pure Energie Meter (`{model}`) \u043a\u044a\u043c Home Assistant?", + "title": "\u041e\u0442\u043a\u0440\u0438\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e Pure Energie Meter" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pure_energie/translations/ca.json b/homeassistant/components/pure_energie/translations/ca.json new file mode 100644 index 00000000000000..cb725e87646a58 --- /dev/null +++ b/homeassistant/components/pure_energie/translations/ca.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat", + "cannot_connect": "Ha fallat la connexi\u00f3" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3" + }, + "flow_title": "{model} ({host})", + "step": { + "user": { + "data": { + "host": "Amfitri\u00f3" + } + }, + "zeroconf_confirm": { + "description": "Vols afegir Pure Energie Meter (`{model}`) a Home Assistant?", + "title": "Dispositiu Pure Energie Meter descobert" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pure_energie/translations/es.json b/homeassistant/components/pure_energie/translations/es.json new file mode 100644 index 00000000000000..eb5d98c7ebf8c5 --- /dev/null +++ b/homeassistant/components/pure_energie/translations/es.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya se encuentra configurado", + "cannot_connect": "Error al conectar" + }, + "error": { + "cannot_connect": "Error al conectar" + }, + "flow_title": "{model} ({host})", + "step": { + "user": { + "data": { + "host": "Anfitri\u00f3n" + } + }, + "zeroconf_confirm": { + "description": "\u00bfQuieres a\u00f1adir el Medidor Pure Energie (`{name}`) a Home Assistant?", + "title": "Medidor Pure Energie encontrado" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pure_energie/translations/hu.json b/homeassistant/components/pure_energie/translations/hu.json new file mode 100644 index 00000000000000..d4bd60def2c978 --- /dev/null +++ b/homeassistant/components/pure_energie/translations/hu.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "flow_title": "{model} ({host})", + "step": { + "user": { + "data": { + "host": "C\u00edm" + } + }, + "zeroconf_confirm": { + "description": "Szeretn\u00e9 hozz\u00e1adni a Pure Energie Metert(`{model}`) term\u00e9ket Home Assistanthoz?", + "title": "Felfedezett Pure Energie Meter eszk\u00f6z" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pure_energie/translations/id.json b/homeassistant/components/pure_energie/translations/id.json new file mode 100644 index 00000000000000..9557e4bc08f4cf --- /dev/null +++ b/homeassistant/components/pure_energie/translations/id.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "{model} ({host})", + "step": { + "user": { + "data": { + "host": "Host" + } + }, + "zeroconf_confirm": { + "description": "Ingin menambahkan Pure Energie Meter (`{name}`) ke Home Assistant?", + "title": "Peranti Pure Energie Meter yang ditemukan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pure_energie/translations/it.json b/homeassistant/components/pure_energie/translations/it.json new file mode 100644 index 00000000000000..457f7cfebc0f3a --- /dev/null +++ b/homeassistant/components/pure_energie/translations/it.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "cannot_connect": "Connessione fallita" + }, + "error": { + "cannot_connect": "Connessione fallita" + }, + "flow_title": "{model} ({host})", + "step": { + "user": { + "data": { + "host": "Host" + } + }, + "zeroconf_confirm": { + "description": "Vuoi aggiungere Pure Energie Meter (`{model}`) a Home Assistant?", + "title": "Scoperto dispositivo Pure Energie Meter" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pure_energie/translations/ja.json b/homeassistant/components/pure_energie/translations/ja.json new file mode 100644 index 00000000000000..bb7b3fe9f13215 --- /dev/null +++ b/homeassistant/components/pure_energie/translations/ja.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f" + }, + "error": { + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f" + }, + "flow_title": "{model} ({host})", + "step": { + "user": { + "data": { + "host": "\u30db\u30b9\u30c8" + } + }, + "zeroconf_confirm": { + "description": "Pure Energie Meter (`{model}`) \u3092Home Assistant\u306b\u8ffd\u52a0\u3057\u307e\u3059\u304b\uff1f", + "title": "Pure Energie Meter device\u3092\u767a\u898b" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pure_energie/translations/pl.json b/homeassistant/components/pure_energie/translations/pl.json new file mode 100644 index 00000000000000..526326fccc1c72 --- /dev/null +++ b/homeassistant/components/pure_energie/translations/pl.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" + }, + "flow_title": "{model} ({host})", + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP" + } + }, + "zeroconf_confirm": { + "description": "Czy chcesz doda\u0107 Pure Energie Meter (`{model}`) do Home Assistanta?", + "title": "Wykryto urz\u0105dzenie Pure Energie Meter" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pure_energie/translations/tr.json b/homeassistant/components/pure_energie/translations/tr.json new file mode 100644 index 00000000000000..8c2a8402124831 --- /dev/null +++ b/homeassistant/components/pure_energie/translations/tr.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "flow_title": "{model} ({host})", + "step": { + "user": { + "data": { + "host": "Sunucu" + } + }, + "zeroconf_confirm": { + "description": "Home Assistant'a Pure Energie Meter (` {model} `) eklemek istiyor musunuz?", + "title": "Ke\u015ffedilen Pure Energie Meter cihaz\u0131" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pure_energie/translations/zh-Hant.json b/homeassistant/components/pure_energie/translations/zh-Hant.json new file mode 100644 index 00000000000000..7fa7144f91452e --- /dev/null +++ b/homeassistant/components/pure_energie/translations/zh-Hant.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "cannot_connect": "\u9023\u7dda\u5931\u6557" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557" + }, + "flow_title": "{model} ({host})", + "step": { + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef" + } + }, + "zeroconf_confirm": { + "description": "\u662f\u5426\u8981\u65b0\u589e Pure Energie Meter (`{model}`) \u81f3 Home Assistant\uff1f", + "title": "\u81ea\u52d5\u63a2\u7d22\u5230 Pure Energie Meter \u88dd\u7f6e" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pvoutput/translations/el.json b/homeassistant/components/pvoutput/translations/el.json index 00c0e1ee3bf76c..a884d21ce4af7a 100644 --- a/homeassistant/components/pvoutput/translations/el.json +++ b/homeassistant/components/pvoutput/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "reauth_confirm": { "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03c1\u03b1\u03b3\u03bc\u03b1\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03c4\u03bf PVOutput, \u03b8\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af \u03bd\u03b1 \u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 {account_url}." diff --git a/homeassistant/components/rdw/translations/el.json b/homeassistant/components/rdw/translations/el.json index f301eb5bb8c356..607a99c87bf7c5 100644 --- a/homeassistant/components/rdw/translations/el.json +++ b/homeassistant/components/rdw/translations/el.json @@ -1,6 +1,7 @@ { "config": { "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "unknown_license_plate": "\u0386\u03b3\u03bd\u03c9\u03c3\u03c4\u03b7 \u03c0\u03b9\u03bd\u03b1\u03ba\u03af\u03b4\u03b1 \u03ba\u03c5\u03ba\u03bb\u03bf\u03c6\u03bf\u03c1\u03af\u03b1\u03c2" }, "step": { diff --git a/homeassistant/components/rituals_perfume_genie/translations/el.json b/homeassistant/components/rituals_perfume_genie/translations/el.json index 490ed36838d54b..ce96a2c2ab6bea 100644 --- a/homeassistant/components/rituals_perfume_genie/translations/el.json +++ b/homeassistant/components/rituals_perfume_genie/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/roomba/translations/el.json b/homeassistant/components/roomba/translations/el.json index 3192242fd21db7..1c490c0ea473cc 100644 --- a/homeassistant/components/roomba/translations/el.json +++ b/homeassistant/components/roomba/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "not_irobot_device": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae iRobot", "short_blid": "\u03a4\u03bf BLID \u03c0\u03b5\u03c1\u03b9\u03ba\u03cc\u03c0\u03b7\u03ba\u03b5" }, diff --git a/homeassistant/components/ruckus_unleashed/translations/el.json b/homeassistant/components/ruckus_unleashed/translations/el.json index 18c2f0869bd1c2..bab52704f790ea 100644 --- a/homeassistant/components/ruckus_unleashed/translations/el.json +++ b/homeassistant/components/ruckus_unleashed/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } diff --git a/homeassistant/components/senseme/translations/el.json b/homeassistant/components/senseme/translations/el.json index 026160f21e1133..f1d52c417a8abc 100644 --- a/homeassistant/components/senseme/translations/el.json +++ b/homeassistant/components/senseme/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "flow_title": "{name} - {model} ({host})", "step": { "discovery_confirm": { diff --git a/homeassistant/components/sensibo/translations/el.json b/homeassistant/components/sensibo/translations/el.json index 1dbdef83eea4e3..34312fd03a1939 100644 --- a/homeassistant/components/sensibo/translations/el.json +++ b/homeassistant/components/sensibo/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/sharkiq/translations/el.json b/homeassistant/components/sharkiq/translations/el.json index e146efbb749630..3f7c4990cd012e 100644 --- a/homeassistant/components/sharkiq/translations/el.json +++ b/homeassistant/components/sharkiq/translations/el.json @@ -12,6 +12,7 @@ }, "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } diff --git a/homeassistant/components/shelly/translations/el.json b/homeassistant/components/shelly/translations/el.json index 975011bf88e816..912cdfe64aa252 100644 --- a/homeassistant/components/shelly/translations/el.json +++ b/homeassistant/components/shelly/translations/el.json @@ -10,6 +10,7 @@ }, "credentials": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } }, diff --git a/homeassistant/components/shelly/translations/fa.json b/homeassistant/components/shelly/translations/fa.json new file mode 100644 index 00000000000000..e2a8e761bfc2e8 --- /dev/null +++ b/homeassistant/components/shelly/translations/fa.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "\u0641\u0627\u0631\u0633\u06cc" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/simplisafe/translations/el.json b/homeassistant/components/simplisafe/translations/el.json index c749d7f7def660..f6a55f9897c7ae 100644 --- a/homeassistant/components/simplisafe/translations/el.json +++ b/homeassistant/components/simplisafe/translations/el.json @@ -21,6 +21,9 @@ "title": "\u03a0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ce\u03bd \u03c0\u03b1\u03c1\u03b1\u03b3\u03cc\u03bd\u03c4\u03c9\u03bd SimpliSafe" }, "reauth_confirm": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u0397 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae \u03c3\u03b1\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03bb\u03ae\u03be\u03b5\u03b9 \u03ae \u03b1\u03bd\u03b1\u03ba\u03bb\u03b7\u03b8\u03b5\u03af. \u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2." }, "user": { diff --git a/homeassistant/components/sleepiq/translations/el.json b/homeassistant/components/sleepiq/translations/el.json new file mode 100644 index 00000000000000..6a74424c88834e --- /dev/null +++ b/homeassistant/components/sleepiq/translations/el.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "step": { + "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sleepiq/translations/es.json b/homeassistant/components/sleepiq/translations/es.json new file mode 100644 index 00000000000000..6b8cd4ac64213a --- /dev/null +++ b/homeassistant/components/sleepiq/translations/es.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "cannot_connect": "Error al conectar", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Usuario" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sleepiq/translations/tr.json b/homeassistant/components/sleepiq/translations/tr.json new file mode 100644 index 00000000000000..153aa4126b0668 --- /dev/null +++ b/homeassistant/components/sleepiq/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smart_meter_texas/translations/el.json b/homeassistant/components/smart_meter_texas/translations/el.json index 18c2f0869bd1c2..bab52704f790ea 100644 --- a/homeassistant/components/smart_meter_texas/translations/el.json +++ b/homeassistant/components/smart_meter_texas/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } diff --git a/homeassistant/components/solaredge/translations/el.json b/homeassistant/components/solaredge/translations/el.json index de1c6594003abc..27721f76d80ed0 100644 --- a/homeassistant/components/solaredge/translations/el.json +++ b/homeassistant/components/solaredge/translations/el.json @@ -1,5 +1,9 @@ { "config": { + "error": { + "could_not_connect": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf API \u03c4\u03bf\u03c5 solarage", + "site_not_active": "\u039f \u03b9\u03c3\u03c4\u03cc\u03c4\u03bf\u03c0\u03bf\u03c2 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03cc\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/solax/translations/el.json b/homeassistant/components/solax/translations/el.json index f4add9bb2400ec..3b724d830f2900 100644 --- a/homeassistant/components/solax/translations/el.json +++ b/homeassistant/components/solax/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/somfy_mylink/translations/el.json b/homeassistant/components/somfy_mylink/translations/el.json index 90dc6fee033fa0..4b3af37a706698 100644 --- a/homeassistant/components/somfy_mylink/translations/el.json +++ b/homeassistant/components/somfy_mylink/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "flow_title": "{mac} ({ip})", "step": { "user": { @@ -13,6 +16,9 @@ } }, "options": { + "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "entity_config": { "data": { diff --git a/homeassistant/components/spider/translations/el.json b/homeassistant/components/spider/translations/el.json index 2a1f19d55fc84f..f9bbee6975761b 100644 --- a/homeassistant/components/spider/translations/el.json +++ b/homeassistant/components/spider/translations/el.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc mijn.ithodaalderop.nl" diff --git a/homeassistant/components/subaru/translations/el.json b/homeassistant/components/subaru/translations/el.json index 78b2a8f80889a8..205f1ea320dd83 100644 --- a/homeassistant/components/subaru/translations/el.json +++ b/homeassistant/components/subaru/translations/el.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "error": { "bad_pin_format": "\u03a4\u03bf PIN \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 4 \u03c8\u03b7\u03c6\u03af\u03b1", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "incorrect_pin": "\u039b\u03b1\u03bd\u03b8\u03b1\u03c3\u03bc\u03ad\u03bd\u03bf PIN" }, "step": { diff --git a/homeassistant/components/tesla_wall_connector/translations/el.json b/homeassistant/components/tesla_wall_connector/translations/el.json index 65a346ccfbfcbd..3c5f96d7002a0e 100644 --- a/homeassistant/components/tesla_wall_connector/translations/el.json +++ b/homeassistant/components/tesla_wall_connector/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "flow_title": "{serial_number} ({host})", "step": { "user": { diff --git a/homeassistant/components/tolo/translations/el.json b/homeassistant/components/tolo/translations/el.json index df42b4400489ad..f325bdfa640167 100644 --- a/homeassistant/components/tolo/translations/el.json +++ b/homeassistant/components/tolo/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "flow_title": "{name}", "step": { "user": { diff --git a/homeassistant/components/totalconnect/translations/el.json b/homeassistant/components/totalconnect/translations/el.json index 180acc83776898..9909277deed5c3 100644 --- a/homeassistant/components/totalconnect/translations/el.json +++ b/homeassistant/components/totalconnect/translations/el.json @@ -9,6 +9,9 @@ }, "step": { "locations": { + "data": { + "usercode": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03c3\u03c4\u03b7\u03bd \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 {location_id}", "title": "\u039a\u03c9\u03b4\u03b9\u03ba\u03bf\u03af \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2" }, diff --git a/homeassistant/components/tuya/translations/el.json b/homeassistant/components/tuya/translations/el.json index a7d7fec3633e8f..c370d68d1264e5 100644 --- a/homeassistant/components/tuya/translations/el.json +++ b/homeassistant/components/tuya/translations/el.json @@ -15,7 +15,8 @@ "tuya_app_type": "Mobile App", "username": "\u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2" }, - "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2 Tuya" + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2 Tuya", + "title": "Tuya" }, "user": { "data": { @@ -25,6 +26,7 @@ "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "platform": "\u0397 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u03c3\u03c4\u03b7\u03bd \u03bf\u03c0\u03bf\u03af\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03b3\u03b3\u03b5\u03b3\u03c1\u03b1\u03bc\u03bc\u03ad\u03bd\u03bf\u03c2 \u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03c3\u03b1\u03c2", "region": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae", + "tuya_project_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03ad\u03c1\u03b3\u03bf\u03c5 Tuya cloud", "username": "\u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2 Tuya", @@ -33,6 +35,9 @@ } }, "options": { + "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "error": { "dev_multi_type": "\u03a0\u03bf\u03bb\u03bb\u03ad\u03c2 \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ad\u03c7\u03bf\u03c5\u03bd \u03c4\u03bf\u03bd \u03af\u03b4\u03b9\u03bf \u03c4\u03cd\u03c0\u03bf", "dev_not_config": "\u039f \u03c4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", diff --git a/homeassistant/components/tuya/translations/select.el.json b/homeassistant/components/tuya/translations/select.el.json index 1cb4117e79968d..0b1641b60ec95b 100644 --- a/homeassistant/components/tuya/translations/select.el.json +++ b/homeassistant/components/tuya/translations/select.el.json @@ -26,7 +26,8 @@ "forward": "\u0395\u03bc\u03c0\u03c1\u03cc\u03c2" }, "tuya__decibel_sensitivity": { - "0": "\u03a7\u03b1\u03bc\u03b7\u03bb\u03ae \u03b5\u03c5\u03b1\u03b9\u03c3\u03b8\u03b7\u03c3\u03af\u03b1" + "0": "\u03a7\u03b1\u03bc\u03b7\u03bb\u03ae \u03b5\u03c5\u03b1\u03b9\u03c3\u03b8\u03b7\u03c3\u03af\u03b1", + "1": "\u03a5\u03c8\u03b7\u03bb\u03ae \u03b5\u03c5\u03b1\u03b9\u03c3\u03b8\u03b7\u03c3\u03af\u03b1" }, "tuya__fan_angle": { "30": "30\u00b0", diff --git a/homeassistant/components/tuya/translations/select.es.json b/homeassistant/components/tuya/translations/select.es.json index d0552cb6d33c8d..adc306feae47b5 100644 --- a/homeassistant/components/tuya/translations/select.es.json +++ b/homeassistant/components/tuya/translations/select.es.json @@ -10,6 +10,15 @@ "1": "Apagado", "2": "Encendido" }, + "tuya__countdown": { + "1h": "1 hora", + "2h": "2 horas", + "3h": "3 horas", + "4h": "4 horas", + "5h": "5 horas", + "6h": "6 horas", + "cancel": "Cancelar" + }, "tuya__decibel_sensitivity": { "0": "Sensibilidad baja", "1": "Sensibilidad alta" @@ -18,6 +27,16 @@ "click": "Push", "switch": "Interruptor" }, + "tuya__humidifier_level": { + "level_1": "Nivel 1", + "level_10": "Nivel 10" + }, + "tuya__humidifier_spray_mode": { + "health": "Salud", + "humidity": "Humedad", + "sleep": "Dormir", + "work": "Trabajo" + }, "tuya__ipc_work_mode": { "0": "Modo de bajo consumo", "1": "Modo de trabajo continuo" diff --git a/homeassistant/components/tuya/translations/select.tr.json b/homeassistant/components/tuya/translations/select.tr.json index 009b8f622452b0..8b9f26b27cd661 100644 --- a/homeassistant/components/tuya/translations/select.tr.json +++ b/homeassistant/components/tuya/translations/select.tr.json @@ -10,6 +10,15 @@ "1": "Kapal\u0131", "2": "A\u00e7\u0131k" }, + "tuya__countdown": { + "1h": "1 saat", + "2h": "2 saat", + "3h": "3 saat", + "4h": "4 saat", + "5h": "5 saat", + "6h": "6 saat", + "cancel": "\u0130ptal" + }, "tuya__curtain_mode": { "morning": "Sabah", "night": "Gece" @@ -31,6 +40,32 @@ "click": "Bildirim", "switch": "Anahtar" }, + "tuya__humidifier_level": { + "level_1": "Seviye 1", + "level_10": "Seviye 10", + "level_2": "Seviye 2", + "level_3": "Seviye 3", + "level_4": "Seviye 4", + "level_5": "Seviye 5", + "level_6": "Seviye 6", + "level_7": "Seviye 7", + "level_8": "Seviye 8", + "level_9": "Seviye 9" + }, + "tuya__humidifier_moodlighting": { + "1": "Mod 1", + "2": "Mod 2", + "3": "Mod 3", + "4": "Mod 4", + "5": "Mod 5" + }, + "tuya__humidifier_spray_mode": { + "auto": "Otomatik", + "health": "Sa\u011fl\u0131k", + "humidity": "Nem", + "sleep": "Uyku", + "work": "\u0130\u015f" + }, "tuya__ipc_work_mode": { "0": "D\u00fc\u015f\u00fck g\u00fc\u00e7 modu", "1": "S\u00fcrekli \u00e7al\u0131\u015fma modu" diff --git a/homeassistant/components/tuya/translations/sensor.es.json b/homeassistant/components/tuya/translations/sensor.es.json index d625d4504c3c2f..7dad02bdf7d746 100644 --- a/homeassistant/components/tuya/translations/sensor.es.json +++ b/homeassistant/components/tuya/translations/sensor.es.json @@ -1,5 +1,11 @@ { "state": { + "tuya__air_quality": { + "good": "Bueno", + "great": "Genial", + "mild": "Moderado", + "severe": "Severo" + }, "tuya__status": { "boiling_temp": "Temperatura de ebullici\u00f3n", "cooling": "Enfriamiento", diff --git a/homeassistant/components/tuya/translations/sensor.tr.json b/homeassistant/components/tuya/translations/sensor.tr.json index 3a3088f51f54c2..c8e9954660ddbb 100644 --- a/homeassistant/components/tuya/translations/sensor.tr.json +++ b/homeassistant/components/tuya/translations/sensor.tr.json @@ -1,5 +1,11 @@ { "state": { + "tuya__air_quality": { + "good": "\u0130yi", + "great": "B\u00fcy\u00fck", + "mild": "Hafif", + "severe": "\u015eiddetli" + }, "tuya__status": { "boiling_temp": "Kaynama s\u0131cakl\u0131\u011f\u0131", "cooling": "So\u011futma", diff --git a/homeassistant/components/unifiprotect/translations/el.json b/homeassistant/components/unifiprotect/translations/el.json index e332f005aa5a61..32ac668aa29153 100644 --- a/homeassistant/components/unifiprotect/translations/el.json +++ b/homeassistant/components/unifiprotect/translations/el.json @@ -4,6 +4,7 @@ "discovery_started": "\u0397 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7 \u03be\u03b5\u03ba\u03af\u03bd\u03b7\u03c3\u03b5" }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "protect_version": "\u0397 \u03b5\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03b7 \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 v1.20.0. \u0391\u03bd\u03b1\u03b2\u03b1\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf UniFi Protect \u03ba\u03b1\u03b9 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac." }, "flow_title": "{name} ( {ip_address} )", diff --git a/homeassistant/components/vallox/translations/el.json b/homeassistant/components/vallox/translations/el.json index ec9ff8394b611f..d564f7d754f5c1 100644 --- a/homeassistant/components/vallox/translations/el.json +++ b/homeassistant/components/vallox/translations/el.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/waze_travel_time/translations/el.json b/homeassistant/components/waze_travel_time/translations/el.json index c1d7d1676a99e2..87024302a4562f 100644 --- a/homeassistant/components/waze_travel_time/translations/el.json +++ b/homeassistant/components/waze_travel_time/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/wiz/translations/el.json b/homeassistant/components/wiz/translations/el.json index dd48cf243c23b7..2665de72b156f7 100644 --- a/homeassistant/components/wiz/translations/el.json +++ b/homeassistant/components/wiz/translations/el.json @@ -5,6 +5,7 @@ }, "error": { "bulb_time_out": "\u0394\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf\u03bd \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b1. \u038a\u03c3\u03c9\u03c2 \u03bf \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03ba\u03c4\u03cc\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03ae \u03ad\u03c7\u03b5\u03b9 \u03b5\u03b9\u03c3\u03b1\u03c7\u03b8\u03b5\u03af \u03bb\u03ac\u03b8\u03bf\u03c2 IP/host. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03bd\u03ac\u03c8\u03c4\u03b5 \u03c4\u03bf \u03c6\u03c9\u03c2 \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac!", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "no_ip": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP.", "no_wiz_light": "\u039f \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03bc\u03ad\u03c3\u03c9 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c0\u03bb\u03b1\u03c4\u03c6\u03cc\u03c1\u03bc\u03b1\u03c2 WiZ." }, diff --git a/homeassistant/components/wiz/translations/es.json b/homeassistant/components/wiz/translations/es.json index 1e0dbb1f7dd858..bc06bff80535b3 100644 --- a/homeassistant/components/wiz/translations/es.json +++ b/homeassistant/components/wiz/translations/es.json @@ -1,7 +1,37 @@ { "config": { + "abort": { + "already_configured": "Dispositivo ya configurado", + "cannot_connect": "Error al conectar", + "no_devices_found": "Ning\u00fan dispositivo encontrado en la red." + }, "error": { - "no_ip": "No es una direcci\u00f3n IP v\u00e1lida." + "bulb_time_out": "No se puede conectar a la bombilla. Tal vez la bombilla est\u00e1 desconectada o se ingres\u00f3 una IP incorrecta. \u00a1Por favor encienda la luz y vuelve a intentarlo!", + "cannot_connect": "Error al conectar", + "no_ip": "No es una direcci\u00f3n IP v\u00e1lida.", + "no_wiz_light": "La bombilla no se puede conectar a trav\u00e9s de la integraci\u00f3n de WiZ Platform.", + "unknown": "Error inesperado" + }, + "flow_title": "{name} ({host})", + "step": { + "confirm": { + "description": "\u00bfDesea iniciar la configuraci\u00f3n?" + }, + "discovery_confirm": { + "description": "\u00bfDesea configurar {name} ({host})?" + }, + "pick_device": { + "data": { + "device": "Dispositivo" + } + }, + "user": { + "data": { + "host": "Direcci\u00f3n IP", + "name": "Nombre" + }, + "description": "Si deja la direcci\u00f3n IP vac\u00eda, la detecci\u00f3n se utilizar\u00e1 para buscar dispositivos." + } } } } \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/tr.json b/homeassistant/components/wiz/translations/tr.json new file mode 100644 index 00000000000000..3f6b1f68dc5ec8 --- /dev/null +++ b/homeassistant/components/wiz/translations/tr.json @@ -0,0 +1,37 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "no_devices_found": "A\u011fda cihaz bulunamad\u0131" + }, + "error": { + "bulb_time_out": "Ampul ba\u011flanam\u0131yor. Belki ampul \u00e7evrimd\u0131\u015f\u0131d\u0131r veya yanl\u0131\u015f bir IP girilmi\u015ftir. L\u00fctfen \u0131\u015f\u0131\u011f\u0131 a\u00e7\u0131n ve tekrar deneyin!", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "no_ip": "Ge\u00e7erli bir IP adresi de\u011fil.", + "no_wiz_light": "Ampul WiZ Platform entegrasyonu ile ba\u011flanamaz.", + "unknown": "Beklenmeyen hata" + }, + "flow_title": "{name} ({host})", + "step": { + "confirm": { + "description": "Kuruluma ba\u015flamak ister misiniz?" + }, + "discovery_confirm": { + "description": "{name} ( {host} ) kurulumu yapmak istiyor musunuz?" + }, + "pick_device": { + "data": { + "device": "Cihaz" + } + }, + "user": { + "data": { + "host": "IP Adresi", + "name": "Ad" + }, + "description": "IP Adresini bo\u015f b\u0131rak\u0131rsan\u0131z, cihazlar\u0131 bulmak i\u00e7in ke\u015fif kullan\u0131lacakt\u0131r." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/el.json b/homeassistant/components/wolflink/translations/el.json index 7f545aa35b4961..fdad6c16b611fc 100644 --- a/homeassistant/components/wolflink/translations/el.json +++ b/homeassistant/components/wolflink/translations/el.json @@ -9,6 +9,7 @@ }, "user": { "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 WOLF SmartSet" diff --git a/homeassistant/components/zha/translations/el.json b/homeassistant/components/zha/translations/el.json index 13dac9bdf42b06..4266ec99ffb92f 100644 --- a/homeassistant/components/zha/translations/el.json +++ b/homeassistant/components/zha/translations/el.json @@ -42,6 +42,8 @@ "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03c0\u03af\u03bd\u03b1\u03ba\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c3\u03c5\u03bd\u03b1\u03b3\u03b5\u03c1\u03bc\u03bf\u03cd" }, "zha_options": { + "consider_unavailable_battery": "\u0398\u03b5\u03c9\u03c1\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03bc\u03b5 \u03bc\u03c0\u03b1\u03c4\u03b1\u03c1\u03af\u03b1 \u03c9\u03c2 \u03bc\u03b7 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b5\u03c2 \u03bc\u03b5\u03c4\u03ac \u03b1\u03c0\u03cc (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)", + "consider_unavailable_mains": "\u0398\u03b5\u03c9\u03c1\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c0\u03bf\u03c5 \u03c4\u03c1\u03bf\u03c6\u03bf\u03b4\u03bf\u03c4\u03bf\u03cd\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf \u03c9\u03c2 \u03bc\u03b7 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b5\u03c2 \u03bc\u03b5\u03c4\u03ac \u03b1\u03c0\u03cc (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)", "default_light_transition": "\u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf\u03c2 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c2 \u03bc\u03b5\u03c4\u03ac\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c6\u03c9\u03c4\u03cc\u03c2 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)", "enable_identify_on_join": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03c6\u03ad \u03b1\u03bd\u03b1\u03b3\u03bd\u03ce\u03c1\u03b9\u03c3\u03b7\u03c2 \u03cc\u03c4\u03b1\u03bd \u03bf\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c5\u03bd\u03b4\u03ad\u03bf\u03bd\u03c4\u03b1\u03b9 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", "title": "\u039a\u03b1\u03b8\u03bf\u03bb\u03b9\u03ba\u03ad\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2" diff --git a/homeassistant/components/zwave_js/translations/el.json b/homeassistant/components/zwave_js/translations/el.json index 663137ffa49e2d..4d431a31479377 100644 --- a/homeassistant/components/zwave_js/translations/el.json +++ b/homeassistant/components/zwave_js/translations/el.json @@ -6,11 +6,13 @@ "addon_install_failed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS.", "addon_set_config_failed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd Z-Wave JS.", "addon_start_failed": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS.", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "discovery_requires_supervisor": "\u0397 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03c4\u03bf\u03bd \u03b5\u03c0\u03cc\u03c0\u03c4\u03b7.", "not_zwave_device": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Z-Wave." }, "error": { "addon_start_failed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7.", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "invalid_ws_url": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL websocket" }, "flow_title": "{name}", @@ -22,6 +24,7 @@ "configure_addon": { "data": { "network_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5", + "s0_legacy_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af S0 (\u03c0\u03b1\u03bb\u03b1\u03b9\u03bf\u03cd \u03c4\u03cd\u03c0\u03bf\u03c5)", "s2_access_control_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 S2", "s2_authenticated_key": "\u03a0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af S2", "s2_unauthenticated_key": "\u039c\u03b7 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af S2" @@ -52,6 +55,7 @@ }, "device_automation": { "action_type": { + "clear_lock_usercode": "\u039a\u03b1\u03b8\u03b1\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c7\u03c1\u03ae\u03c3\u03b7\u03c2 \u03c3\u03c4\u03bf {entity_name}", "refresh_value": "\u0391\u03bd\u03b1\u03bd\u03b5\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c4\u03b9\u03bc\u03ad\u03c2 \u03b3\u03b9\u03b1 {entity_name}", "reset_meter": "\u0395\u03c0\u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac \u03bc\u03b5\u03c4\u03c1\u03b7\u03c4\u03ce\u03bd \u03c3\u03c4\u03bf {subtype}", "set_config_parameter": "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03c4\u03b9\u03bc\u03ae\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03bf\u03c5 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 {subtype}", diff --git a/homeassistant/components/zwave_me/translations/es.json b/homeassistant/components/zwave_me/translations/es.json index f55937b4ec7dd1..eab23bbd0fffd9 100644 --- a/homeassistant/components/zwave_me/translations/es.json +++ b/homeassistant/components/zwave_me/translations/es.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado", "no_valid_uuid_set": "No se ha establecido un UUID v\u00e1lido" }, "error": { @@ -9,7 +10,8 @@ "step": { "user": { "data": { - "token": "Token" + "token": "Token", + "url": "URL" }, "description": "Direcci\u00f3n IP de entrada del servidor Z-Way y token de acceso Z-Way. La direcci\u00f3n IP se puede prefijar con wss:// si se debe usar HTTPS en lugar de HTTP. Para obtener el token, vaya a la interfaz de usuario de Z-Way > Configuraci\u00f3n de > de men\u00fa > token de API de > de usuario. Se sugiere crear un nuevo usuario para Home Assistant y conceder acceso a los dispositivos que necesita controlar desde Home Assistant. Tambi\u00e9n es posible utilizar el acceso remoto a trav\u00e9s de find.z-wave.me para conectar un Z-Way remoto. Ingrese wss://find.z-wave.me en el campo IP y copie el token con alcance global (inicie sesi\u00f3n en Z-Way a trav\u00e9s de find.z-wave.me para esto)." } diff --git a/homeassistant/components/zwave_me/translations/tr.json b/homeassistant/components/zwave_me/translations/tr.json new file mode 100644 index 00000000000000..39d25423fab99a --- /dev/null +++ b/homeassistant/components/zwave_me/translations/tr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "no_valid_uuid_set": "Ge\u00e7erli UUID seti yok" + }, + "error": { + "no_valid_uuid_set": "Ge\u00e7erli UUID seti yok" + }, + "step": { + "user": { + "data": { + "token": "Anahtar", + "url": "URL" + }, + "description": "Z-Way sunucusunun ve Z-Way eri\u015fim belirtecinin IP adresini girin. HTTP yerine HTTPS kullan\u0131lmas\u0131 gerekiyorsa, IP adresinin \u00f6n\u00fcne wss:// eklenebilir. Belirteci almak i\u00e7in Z-Way kullan\u0131c\u0131 aray\u00fcz\u00fc > Men\u00fc > Ayarlar > Kullan\u0131c\u0131 > API belirtecine gidin. Home Assistant i\u00e7in yeni bir kullan\u0131c\u0131 olu\u015fturman\u0131z ve Home Assistant'tan kontrol etmeniz gereken cihazlara eri\u015fim izni vermeniz \u00f6nerilir. Uzak bir Z-Way'i ba\u011flamak i\u00e7in find.z-wave.me arac\u0131l\u0131\u011f\u0131yla uzaktan eri\u015fimi kullanmak da m\u00fcmk\u00fcnd\u00fcr. IP alan\u0131na wss://find.z-wave.me yaz\u0131n ve belirteci Global kapsamla kopyalay\u0131n (bunun i\u00e7in find.z-wave.me arac\u0131l\u0131\u011f\u0131yla Z-Way'de oturum a\u00e7\u0131n)." + } + } + } +} \ No newline at end of file From 865159781fe24b297526dbc293fa9cfbcc719ebb Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Mon, 21 Feb 2022 07:28:56 +0000 Subject: [PATCH 0868/1098] Use new enums in vizio tests (#62710) * Use new enums in vizio tests * Code review: revert wrong conftest changes * Code review: Revert incorrect removal of vizio const * Reinstate wrongly reverted files * Fix double line * Fix new test after rebase --- tests/components/vizio/const.py | 23 ++++++++++----------- tests/components/vizio/test_config_flow.py | 20 +++++++++--------- tests/components/vizio/test_media_player.py | 17 ++++++++------- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/tests/components/vizio/const.py b/tests/components/vizio/const.py index e39864a61576ef..119443962fced3 100644 --- a/tests/components/vizio/const.py +++ b/tests/components/vizio/const.py @@ -1,9 +1,8 @@ """Constants for the Vizio integration tests.""" from homeassistant.components import zeroconf from homeassistant.components.media_player import ( - DEVICE_CLASS_SPEAKER, - DEVICE_CLASS_TV, DOMAIN as MP_DOMAIN, + MediaPlayerDeviceClass, ) from homeassistant.components.vizio.const import ( CONF_ADDITIONAL_CONFIGS, @@ -102,7 +101,7 @@ def __init__(self, auth_token: str) -> None: MOCK_USER_VALID_TV_CONFIG = { CONF_NAME: NAME, CONF_HOST: HOST, - CONF_DEVICE_CLASS: DEVICE_CLASS_TV, + CONF_DEVICE_CLASS: MediaPlayerDeviceClass.TV, CONF_ACCESS_TOKEN: ACCESS_TOKEN, } @@ -113,7 +112,7 @@ def __init__(self, auth_token: str) -> None: MOCK_IMPORT_VALID_TV_CONFIG = { CONF_NAME: NAME, CONF_HOST: HOST, - CONF_DEVICE_CLASS: DEVICE_CLASS_TV, + CONF_DEVICE_CLASS: MediaPlayerDeviceClass.TV, CONF_ACCESS_TOKEN: ACCESS_TOKEN, CONF_VOLUME_STEP: VOLUME_STEP, } @@ -121,7 +120,7 @@ def __init__(self, auth_token: str) -> None: MOCK_TV_WITH_INCLUDE_CONFIG = { CONF_NAME: NAME, CONF_HOST: HOST, - CONF_DEVICE_CLASS: DEVICE_CLASS_TV, + CONF_DEVICE_CLASS: MediaPlayerDeviceClass.TV, CONF_ACCESS_TOKEN: ACCESS_TOKEN, CONF_VOLUME_STEP: VOLUME_STEP, CONF_APPS: {CONF_INCLUDE: [CURRENT_APP]}, @@ -130,7 +129,7 @@ def __init__(self, auth_token: str) -> None: MOCK_TV_WITH_EXCLUDE_CONFIG = { CONF_NAME: NAME, CONF_HOST: HOST, - CONF_DEVICE_CLASS: DEVICE_CLASS_TV, + CONF_DEVICE_CLASS: MediaPlayerDeviceClass.TV, CONF_ACCESS_TOKEN: ACCESS_TOKEN, CONF_VOLUME_STEP: VOLUME_STEP, CONF_APPS: {CONF_EXCLUDE: ["Netflix"]}, @@ -139,7 +138,7 @@ def __init__(self, auth_token: str) -> None: MOCK_TV_WITH_ADDITIONAL_APPS_CONFIG = { CONF_NAME: NAME, CONF_HOST: HOST, - CONF_DEVICE_CLASS: DEVICE_CLASS_TV, + CONF_DEVICE_CLASS: MediaPlayerDeviceClass.TV, CONF_ACCESS_TOKEN: ACCESS_TOKEN, CONF_VOLUME_STEP: VOLUME_STEP, CONF_APPS: {CONF_ADDITIONAL_CONFIGS: [ADDITIONAL_APP_CONFIG]}, @@ -148,7 +147,7 @@ def __init__(self, auth_token: str) -> None: MOCK_SPEAKER_APPS_FAILURE = { CONF_NAME: NAME, CONF_HOST: HOST, - CONF_DEVICE_CLASS: DEVICE_CLASS_SPEAKER, + CONF_DEVICE_CLASS: MediaPlayerDeviceClass.SPEAKER, CONF_ACCESS_TOKEN: ACCESS_TOKEN, CONF_VOLUME_STEP: VOLUME_STEP, CONF_APPS: {CONF_ADDITIONAL_CONFIGS: [ADDITIONAL_APP_CONFIG]}, @@ -157,7 +156,7 @@ def __init__(self, auth_token: str) -> None: MOCK_TV_APPS_FAILURE = { CONF_NAME: NAME, CONF_HOST: HOST, - CONF_DEVICE_CLASS: DEVICE_CLASS_TV, + CONF_DEVICE_CLASS: MediaPlayerDeviceClass.TV, CONF_ACCESS_TOKEN: ACCESS_TOKEN, CONF_VOLUME_STEP: VOLUME_STEP, CONF_APPS: None, @@ -165,7 +164,7 @@ def __init__(self, auth_token: str) -> None: MOCK_TV_APPS_WITH_VALID_APPS_CONFIG = { CONF_HOST: HOST, - CONF_DEVICE_CLASS: DEVICE_CLASS_TV, + CONF_DEVICE_CLASS: MediaPlayerDeviceClass.TV, CONF_ACCESS_TOKEN: ACCESS_TOKEN, CONF_APPS: {CONF_INCLUDE: [CURRENT_APP]}, } @@ -173,13 +172,13 @@ def __init__(self, auth_token: str) -> None: MOCK_TV_CONFIG_NO_TOKEN = { CONF_NAME: NAME, CONF_HOST: HOST, - CONF_DEVICE_CLASS: DEVICE_CLASS_TV, + CONF_DEVICE_CLASS: MediaPlayerDeviceClass.TV, } MOCK_SPEAKER_CONFIG = { CONF_NAME: NAME, CONF_HOST: HOST, - CONF_DEVICE_CLASS: DEVICE_CLASS_SPEAKER, + CONF_DEVICE_CLASS: MediaPlayerDeviceClass.SPEAKER, } MOCK_INCLUDE_APPS = { diff --git a/tests/components/vizio/test_config_flow.py b/tests/components/vizio/test_config_flow.py index 817f23d52c53e0..3250163ef8e7fa 100644 --- a/tests/components/vizio/test_config_flow.py +++ b/tests/components/vizio/test_config_flow.py @@ -5,7 +5,7 @@ import voluptuous as vol from homeassistant import data_entry_flow -from homeassistant.components.media_player import DEVICE_CLASS_SPEAKER, DEVICE_CLASS_TV +from homeassistant.components.media_player import MediaPlayerDeviceClass from homeassistant.components.vizio.config_flow import _get_config_schema from homeassistant.components.vizio.const import ( CONF_APPS, @@ -77,7 +77,7 @@ async def test_user_flow_minimum_fields( assert result["title"] == NAME assert result["data"][CONF_NAME] == NAME assert result["data"][CONF_HOST] == HOST - assert result["data"][CONF_DEVICE_CLASS] == DEVICE_CLASS_SPEAKER + assert result["data"][CONF_DEVICE_CLASS] == MediaPlayerDeviceClass.SPEAKER async def test_user_flow_all_fields( @@ -102,7 +102,7 @@ async def test_user_flow_all_fields( assert result["title"] == NAME assert result["data"][CONF_NAME] == NAME assert result["data"][CONF_HOST] == HOST - assert result["data"][CONF_DEVICE_CLASS] == DEVICE_CLASS_TV + assert result["data"][CONF_DEVICE_CLASS] == MediaPlayerDeviceClass.TV assert result["data"][CONF_ACCESS_TOKEN] == ACCESS_TOKEN assert CONF_APPS not in result["data"] @@ -339,7 +339,7 @@ async def test_user_tv_pairing_no_apps( assert result["title"] == NAME assert result["data"][CONF_NAME] == NAME assert result["data"][CONF_HOST] == HOST - assert result["data"][CONF_DEVICE_CLASS] == DEVICE_CLASS_TV + assert result["data"][CONF_DEVICE_CLASS] == MediaPlayerDeviceClass.TV assert CONF_APPS not in result["data"] @@ -412,7 +412,7 @@ async def test_import_flow_minimum_fields( DOMAIN, context={"source": SOURCE_IMPORT}, data=vol.Schema(VIZIO_SCHEMA)( - {CONF_HOST: HOST, CONF_DEVICE_CLASS: DEVICE_CLASS_SPEAKER} + {CONF_HOST: HOST, CONF_DEVICE_CLASS: MediaPlayerDeviceClass.SPEAKER} ), ) @@ -420,7 +420,7 @@ async def test_import_flow_minimum_fields( assert result["title"] == DEFAULT_NAME assert result["data"][CONF_NAME] == DEFAULT_NAME assert result["data"][CONF_HOST] == HOST - assert result["data"][CONF_DEVICE_CLASS] == DEVICE_CLASS_SPEAKER + assert result["data"][CONF_DEVICE_CLASS] == MediaPlayerDeviceClass.SPEAKER assert result["data"][CONF_VOLUME_STEP] == DEFAULT_VOLUME_STEP @@ -440,7 +440,7 @@ async def test_import_flow_all_fields( assert result["title"] == NAME assert result["data"][CONF_NAME] == NAME assert result["data"][CONF_HOST] == HOST - assert result["data"][CONF_DEVICE_CLASS] == DEVICE_CLASS_TV + assert result["data"][CONF_DEVICE_CLASS] == MediaPlayerDeviceClass.TV assert result["data"][CONF_ACCESS_TOKEN] == ACCESS_TOKEN assert result["data"][CONF_VOLUME_STEP] == VOLUME_STEP @@ -599,7 +599,7 @@ async def test_import_needs_pairing( assert result["title"] == NAME assert result["data"][CONF_NAME] == NAME assert result["data"][CONF_HOST] == HOST - assert result["data"][CONF_DEVICE_CLASS] == DEVICE_CLASS_TV + assert result["data"][CONF_DEVICE_CLASS] == MediaPlayerDeviceClass.TV async def test_import_with_apps_needs_pairing( @@ -641,7 +641,7 @@ async def test_import_with_apps_needs_pairing( assert result["title"] == NAME assert result["data"][CONF_NAME] == NAME assert result["data"][CONF_HOST] == HOST - assert result["data"][CONF_DEVICE_CLASS] == DEVICE_CLASS_TV + assert result["data"][CONF_DEVICE_CLASS] == MediaPlayerDeviceClass.TV assert result["data"][CONF_APPS][CONF_INCLUDE] == [CURRENT_APP] @@ -756,7 +756,7 @@ async def test_zeroconf_flow( assert result["title"] == NAME assert result["data"][CONF_HOST] == HOST assert result["data"][CONF_NAME] == NAME - assert result["data"][CONF_DEVICE_CLASS] == DEVICE_CLASS_SPEAKER + assert result["data"][CONF_DEVICE_CLASS] == MediaPlayerDeviceClass.SPEAKER async def test_zeroconf_flow_already_configured( diff --git a/tests/components/vizio/test_media_player.py b/tests/components/vizio/test_media_player.py index 80f722809511ea..1c6377339276d5 100644 --- a/tests/components/vizio/test_media_player.py +++ b/tests/components/vizio/test_media_player.py @@ -24,8 +24,6 @@ ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, ATTR_SOUND_MODE, - DEVICE_CLASS_SPEAKER, - DEVICE_CLASS_TV, DOMAIN as MP_DOMAIN, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, @@ -37,6 +35,7 @@ SERVICE_VOLUME_MUTE, SERVICE_VOLUME_SET, SERVICE_VOLUME_UP, + MediaPlayerDeviceClass, ) from homeassistant.components.media_player.const import ATTR_INPUT_SOURCE_LIST from homeassistant.components.vizio import validate_apps @@ -158,7 +157,9 @@ async def _test_setup_tv(hass: HomeAssistant, vizio_power_state: bool | None) -> ): await _add_config_entry_to_hass(hass, config_entry) - attr = _get_attr_and_assert_base_attr(hass, DEVICE_CLASS_TV, ha_power_state) + attr = _get_attr_and_assert_base_attr( + hass, MediaPlayerDeviceClass.TV, ha_power_state + ) if ha_power_state == STATE_ON: _assert_sources_and_volume(attr, VIZIO_DEVICE_CLASS_TV) assert "sound_mode" not in attr @@ -192,7 +193,7 @@ async def _test_setup_speaker( await _add_config_entry_to_hass(hass, config_entry) attr = _get_attr_and_assert_base_attr( - hass, DEVICE_CLASS_SPEAKER, ha_power_state + hass, MediaPlayerDeviceClass.SPEAKER, ha_power_state ) if ha_power_state == STATE_ON: _assert_sources_and_volume(attr, VIZIO_DEVICE_CLASS_SPEAKER) @@ -219,7 +220,9 @@ async def _cm_for_test_setup_tv_with_apps( ): await _add_config_entry_to_hass(hass, config_entry) - attr = _get_attr_and_assert_base_attr(hass, DEVICE_CLASS_TV, STATE_ON) + attr = _get_attr_and_assert_base_attr( + hass, MediaPlayerDeviceClass.TV, STATE_ON + ) assert ( attr["volume_level"] == float(int(MAX_VOLUME[VIZIO_DEVICE_CLASS_TV] / 2)) @@ -714,7 +717,7 @@ async def test_setup_tv_without_mute( ): await _add_config_entry_to_hass(hass, config_entry) - attr = _get_attr_and_assert_base_attr(hass, DEVICE_CLASS_TV, STATE_ON) + attr = _get_attr_and_assert_base_attr(hass, MediaPlayerDeviceClass.TV, STATE_ON) _assert_sources_and_volume(attr, VIZIO_DEVICE_CLASS_TV) assert "sound_mode" not in attr assert "is_volume_muted" not in attr @@ -763,6 +766,6 @@ async def test_vizio_update_with_apps_on_input( unique_id=UNIQUE_ID, ) await _add_config_entry_to_hass(hass, config_entry) - attr = _get_attr_and_assert_base_attr(hass, DEVICE_CLASS_TV, STATE_ON) + attr = _get_attr_and_assert_base_attr(hass, MediaPlayerDeviceClass.TV, STATE_ON) # app ID should not be in the attributes assert "app_id" not in attr From 3b146d8e9b15650c2a35577ddecfd318b7034986 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 21 Feb 2022 09:11:29 +0100 Subject: [PATCH 0869/1098] Use hass.add_job in samsungtv (#66976) Co-authored-by: epenet --- homeassistant/components/samsungtv/__init__.py | 4 ++-- homeassistant/components/samsungtv/media_player.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/samsungtv/__init__.py b/homeassistant/components/samsungtv/__init__.py index 515e5c0de96b09..45be07585d7a9b 100644 --- a/homeassistant/components/samsungtv/__init__.py +++ b/homeassistant/components/samsungtv/__init__.py @@ -24,7 +24,6 @@ from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType -from homeassistant.util.async_ import run_callback_threadsafe from .bridge import ( SamsungTVBridge, @@ -119,6 +118,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: bridge = await _async_create_bridge_with_updated_data(hass, entry) # Ensure new token gets saved against the config_entry + @callback def _update_token() -> None: """Update config entry with the new token.""" hass.config_entries.async_update_entry( @@ -127,7 +127,7 @@ def _update_token() -> None: def new_token_callback() -> None: """Update config entry with the new token.""" - run_callback_threadsafe(hass.loop, _update_token) + hass.add_job(_update_token) bridge.register_new_token_callback(new_token_callback) diff --git a/homeassistant/components/samsungtv/media_player.py b/homeassistant/components/samsungtv/media_player.py index 421b88d50ad32a..db99726f20ca8e 100644 --- a/homeassistant/components/samsungtv/media_player.py +++ b/homeassistant/components/samsungtv/media_player.py @@ -164,7 +164,7 @@ def update(self) -> None: if self._attr_state == STATE_ON and self._app_list is None: self._app_list = {} # Ensure that we don't update it twice in parallel - self.hass.async_add_job(self._update_app_list) + self._update_app_list() def _update_app_list(self) -> None: self._app_list = self._bridge.get_app_list() From 7a39c769f0b1681face96c4b5a33d44181f98fdd Mon Sep 17 00:00:00 2001 From: Julien Date: Mon, 21 Feb 2022 09:50:14 +0100 Subject: [PATCH 0870/1098] Fix typo in const.py (#66856) --- homeassistant/components/version/config_flow.py | 4 ++-- homeassistant/components/version/const.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/version/config_flow.py b/homeassistant/components/version/config_flow.py index f37fa1c3da2a41..292b194eea114c 100644 --- a/homeassistant/components/version/config_flow.py +++ b/homeassistant/components/version/config_flow.py @@ -26,7 +26,7 @@ DEFAULT_SOURCE, DOMAIN, POSTFIX_CONTAINER_NAME, - SOURCE_DOKCER, + SOURCE_DOCKER, SOURCE_HASSIO, STEP_USER, STEP_VERSION_SOURCE, @@ -171,7 +171,7 @@ def _convert_imported_configuration(config: dict[str, Any]) -> Any: if source == SOURCE_HASSIO: data[CONF_SOURCE] = "supervisor" data[CONF_VERSION_SOURCE] = VERSION_SOURCE_VERSIONS - elif source == SOURCE_DOKCER: + elif source == SOURCE_DOCKER: data[CONF_SOURCE] = "container" data[CONF_VERSION_SOURCE] = VERSION_SOURCE_DOCKER_HUB else: diff --git a/homeassistant/components/version/const.py b/homeassistant/components/version/const.py index 9ee556c6b7ff2f..9f480c25cc5342 100644 --- a/homeassistant/components/version/const.py +++ b/homeassistant/components/version/const.py @@ -30,7 +30,7 @@ ATTR_VERSION_SOURCE: Final = CONF_VERSION_SOURCE ATTR_SOURCE: Final = CONF_SOURCE -SOURCE_DOKCER: Final = "docker" # Kept to not break existing configurations +SOURCE_DOCKER: Final = "docker" # Kept to not break existing configurations SOURCE_HASSIO: Final = "hassio" # Kept to not break existing configurations VERSION_SOURCE_DOCKER_HUB: Final = "Docker Hub" From c496748125b811ef5437ad666d21c09025e0967f Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 21 Feb 2022 10:11:18 +0100 Subject: [PATCH 0871/1098] Add WS API for removing a config entry from a device (#66188) * Add WS API for removing a config entry from a device * Address review comments * Address review comments * Remove entity cleanup from ConfigEntries * Update + add tests * Improve comments in test * Add negative test * Refactor according to review comments * Add back async_remove_config_entry_device * Remove unnecessary error handling * Simplify error handling --- .../components/config/config_entries.py | 1 + .../components/config/device_registry.py | 80 ++++- homeassistant/config_entries.py | 14 + tests/common.py | 4 + .../components/config/test_config_entries.py | 7 +- .../components/config/test_device_registry.py | 273 +++++++++++++++++- 6 files changed, 362 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/config/config_entries.py b/homeassistant/components/config/config_entries.py index e5bf9e9b93db00..c3d20fb0f169ad 100644 --- a/homeassistant/components/config/config_entries.py +++ b/homeassistant/components/config/config_entries.py @@ -387,6 +387,7 @@ def entry_json(entry: config_entries.ConfigEntry) -> dict: "source": entry.source, "state": entry.state.value, "supports_options": supports_options, + "supports_remove_device": entry.supports_remove_device, "supports_unload": entry.supports_unload, "pref_disable_new_entities": entry.pref_disable_new_entities, "pref_disable_polling": entry.pref_disable_polling, diff --git a/homeassistant/components/config/device_registry.py b/homeassistant/components/config/device_registry.py index 686fffec252431..e811d43d502ee8 100644 --- a/homeassistant/components/config/device_registry.py +++ b/homeassistant/components/config/device_registry.py @@ -1,16 +1,12 @@ """HTTP views to interact with the device registry.""" import voluptuous as vol +from homeassistant import loader from homeassistant.components import websocket_api -from homeassistant.components.websocket_api.decorators import ( - async_response, - require_admin, -) -from homeassistant.core import callback -from homeassistant.helpers.device_registry import ( - DeviceEntryDisabler, - async_get_registry, -) +from homeassistant.components.websocket_api.decorators import require_admin +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.device_registry import DeviceEntryDisabler, async_get WS_TYPE_LIST = "config/device_registry/list" SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( @@ -39,13 +35,16 @@ async def async_setup(hass): websocket_api.async_register_command( hass, WS_TYPE_UPDATE, websocket_update_device, SCHEMA_WS_UPDATE ) + websocket_api.async_register_command( + hass, websocket_remove_config_entry_from_device + ) return True -@async_response -async def websocket_list_devices(hass, connection, msg): +@callback +def websocket_list_devices(hass, connection, msg): """Handle list devices command.""" - registry = await async_get_registry(hass) + registry = async_get(hass) connection.send_message( websocket_api.result_message( msg["id"], [_entry_dict(entry) for entry in registry.devices.values()] @@ -54,10 +53,10 @@ async def websocket_list_devices(hass, connection, msg): @require_admin -@async_response -async def websocket_update_device(hass, connection, msg): +@callback +def websocket_update_device(hass, connection, msg): """Handle update area websocket command.""" - registry = await async_get_registry(hass) + registry = async_get(hass) msg.pop("type") msg_id = msg.pop("id") @@ -70,6 +69,57 @@ async def websocket_update_device(hass, connection, msg): connection.send_message(websocket_api.result_message(msg_id, _entry_dict(entry))) +@websocket_api.require_admin +@websocket_api.websocket_command( + { + "type": "config/device_registry/remove_config_entry", + "device_id": str, + "config_entry_id": str, + } +) +@websocket_api.async_response +async def websocket_remove_config_entry_from_device( + hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict +) -> None: + """Remove config entry from a device.""" + registry = async_get(hass) + config_entry_id = msg["config_entry_id"] + device_id = msg["device_id"] + + if (config_entry := hass.config_entries.async_get_entry(config_entry_id)) is None: + raise HomeAssistantError("Unknown config entry") + + if not config_entry.supports_remove_device: + raise HomeAssistantError("Config entry does not support device removal") + + if (device_entry := registry.async_get(device_id)) is None: + raise HomeAssistantError("Unknown device") + + if config_entry_id not in device_entry.config_entries: + raise HomeAssistantError("Config entry not in device") + + try: + integration = await loader.async_get_integration(hass, config_entry.domain) + component = integration.get_component() + except (ImportError, loader.IntegrationNotFound) as exc: + raise HomeAssistantError("Integration not found") from exc + + if not await component.async_remove_config_entry_device( + hass, config_entry, device_entry + ): + raise HomeAssistantError( + "Failed to remove device entry, rejected by integration" + ) + + entry = registry.async_update_device( + device_id, remove_config_entry_id=config_entry_id + ) + + entry_as_dict = _entry_dict(entry) if entry else None + + connection.send_message(websocket_api.result_message(msg["id"], entry_as_dict)) + + @callback def _entry_dict(entry): """Convert entry to API format.""" diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 57c837178a48ef..af04ee032dc0e8 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -175,6 +175,7 @@ class ConfigEntry: "options", "unique_id", "supports_unload", + "supports_remove_device", "pref_disable_new_entities", "pref_disable_polling", "source", @@ -257,6 +258,9 @@ def __init__( # Supports unload self.supports_unload = False + # Supports remove device + self.supports_remove_device = False + # Listeners to call on update self.update_listeners: list[ weakref.ReferenceType[UpdateListenerType] | weakref.WeakMethod @@ -287,6 +291,9 @@ async def async_setup( integration = await loader.async_get_integration(hass, self.domain) self.supports_unload = await support_entry_unload(hass, self.domain) + self.supports_remove_device = await support_remove_from_device( + hass, self.domain + ) try: component = integration.get_component() @@ -1615,3 +1622,10 @@ async def support_entry_unload(hass: HomeAssistant, domain: str) -> bool: integration = await loader.async_get_integration(hass, domain) component = integration.get_component() return hasattr(component, "async_unload_entry") + + +async def support_remove_from_device(hass: HomeAssistant, domain: str) -> bool: + """Test if a domain supports being removed from a device.""" + integration = await loader.async_get_integration(hass, domain) + component = integration.get_component() + return hasattr(component, "async_remove_config_entry_device") diff --git a/tests/common.py b/tests/common.py index c8dfb3ed841dfe..bdebc7217a7ae2 100644 --- a/tests/common.py +++ b/tests/common.py @@ -583,6 +583,7 @@ def __init__( async_migrate_entry=None, async_remove_entry=None, partial_manifest=None, + async_remove_config_entry_device=None, ): """Initialize the mock module.""" self.__name__ = f"homeassistant.components.{domain}" @@ -624,6 +625,9 @@ def __init__( if async_remove_entry is not None: self.async_remove_entry = async_remove_entry + if async_remove_config_entry_device is not None: + self.async_remove_config_entry_device = async_remove_config_entry_device + def mock_manifest(self): """Generate a mock manifest to represent this module.""" return { diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index 6608bf3471dc98..cfc6d8d49077c3 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -12,7 +12,7 @@ from homeassistant.config_entries import HANDLERS from homeassistant.core import callback from homeassistant.generated import config_flows -import homeassistant.helpers.config_validation as cv +from homeassistant.helpers import config_validation as cv from homeassistant.setup import async_setup_component from tests.common import ( @@ -94,6 +94,7 @@ def async_supports_options_flow(cls, config_entry): "source": "bla", "state": core_ce.ConfigEntryState.NOT_LOADED.value, "supports_options": True, + "supports_remove_device": False, "supports_unload": True, "pref_disable_new_entities": False, "pref_disable_polling": False, @@ -106,6 +107,7 @@ def async_supports_options_flow(cls, config_entry): "source": "bla2", "state": core_ce.ConfigEntryState.SETUP_ERROR.value, "supports_options": False, + "supports_remove_device": False, "supports_unload": False, "pref_disable_new_entities": False, "pref_disable_polling": False, @@ -118,6 +120,7 @@ def async_supports_options_flow(cls, config_entry): "source": "bla3", "state": core_ce.ConfigEntryState.NOT_LOADED.value, "supports_options": False, + "supports_remove_device": False, "supports_unload": False, "pref_disable_new_entities": False, "pref_disable_polling": False, @@ -370,6 +373,7 @@ async def async_step_user(self, user_input=None): "source": core_ce.SOURCE_USER, "state": core_ce.ConfigEntryState.LOADED.value, "supports_options": False, + "supports_remove_device": False, "supports_unload": False, "pref_disable_new_entities": False, "pref_disable_polling": False, @@ -443,6 +447,7 @@ async def async_step_account(self, user_input=None): "source": core_ce.SOURCE_USER, "state": core_ce.ConfigEntryState.LOADED.value, "supports_options": False, + "supports_remove_device": False, "supports_unload": False, "pref_disable_new_entities": False, "pref_disable_polling": False, diff --git a/tests/components/config/test_device_registry.py b/tests/components/config/test_device_registry.py index f43f9a4d8ce9ae..f923b326100987 100644 --- a/tests/components/config/test_device_registry.py +++ b/tests/components/config/test_device_registry.py @@ -3,8 +3,14 @@ from homeassistant.components.config import device_registry from homeassistant.helpers import device_registry as helpers_dr +from homeassistant.setup import async_setup_component -from tests.common import mock_device_registry +from tests.common import ( + MockConfigEntry, + MockModule, + mock_device_registry, + mock_integration, +) from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @@ -126,3 +132,268 @@ async def test_update_device(hass, client, registry, payload_key, payload_value) assert getattr(device, payload_key) == payload_value assert isinstance(device.disabled_by, (helpers_dr.DeviceEntryDisabler, type(None))) + + +async def test_remove_config_entry_from_device(hass, hass_ws_client): + """Test removing config entry from device.""" + assert await async_setup_component(hass, "config", {}) + ws_client = await hass_ws_client(hass) + device_registry = mock_device_registry(hass) + + can_remove = False + + async def async_remove_config_entry_device(hass, config_entry, device_entry): + return can_remove + + mock_integration( + hass, + MockModule( + "comp1", async_remove_config_entry_device=async_remove_config_entry_device + ), + ) + mock_integration( + hass, + MockModule( + "comp2", async_remove_config_entry_device=async_remove_config_entry_device + ), + ) + + entry_1 = MockConfigEntry( + domain="comp1", + title="Test 1", + source="bla", + ) + entry_1.supports_remove_device = True + entry_1.add_to_hass(hass) + + entry_2 = MockConfigEntry( + domain="comp1", + title="Test 1", + source="bla", + ) + entry_2.supports_remove_device = True + entry_2.add_to_hass(hass) + + device_registry.async_get_or_create( + config_entry_id=entry_1.entry_id, + connections={(helpers_dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + device_entry = device_registry.async_get_or_create( + config_entry_id=entry_2.entry_id, + connections={(helpers_dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + assert device_entry.config_entries == {entry_1.entry_id, entry_2.entry_id} + + # Try removing a config entry from the device, it should fail because + # async_remove_config_entry_device returns False + await ws_client.send_json( + { + "id": 5, + "type": "config/device_registry/remove_config_entry", + "config_entry_id": entry_1.entry_id, + "device_id": device_entry.id, + } + ) + response = await ws_client.receive_json() + + assert not response["success"] + assert response["error"]["code"] == "unknown_error" + + # Make async_remove_config_entry_device return True + can_remove = True + + # Remove the 1st config entry + await ws_client.send_json( + { + "id": 6, + "type": "config/device_registry/remove_config_entry", + "config_entry_id": entry_1.entry_id, + "device_id": device_entry.id, + } + ) + response = await ws_client.receive_json() + + assert response["success"] + assert response["result"]["config_entries"] == [entry_2.entry_id] + + # Check that the config entry was removed from the device + assert device_registry.async_get(device_entry.id).config_entries == { + entry_2.entry_id + } + + # Remove the 2nd config entry + await ws_client.send_json( + { + "id": 7, + "type": "config/device_registry/remove_config_entry", + "config_entry_id": entry_2.entry_id, + "device_id": device_entry.id, + } + ) + response = await ws_client.receive_json() + + assert response["success"] + assert response["result"] is None + + # This was the last config entry, the device is removed + assert not device_registry.async_get(device_entry.id) + + +async def test_remove_config_entry_from_device_fails(hass, hass_ws_client): + """Test removing config entry from device failing cases.""" + assert await async_setup_component(hass, "config", {}) + ws_client = await hass_ws_client(hass) + device_registry = mock_device_registry(hass) + + async def async_remove_config_entry_device(hass, config_entry, device_entry): + return True + + mock_integration( + hass, + MockModule("comp1"), + ) + mock_integration( + hass, + MockModule( + "comp2", async_remove_config_entry_device=async_remove_config_entry_device + ), + ) + + entry_1 = MockConfigEntry( + domain="comp1", + title="Test 1", + source="bla", + ) + entry_1.add_to_hass(hass) + + entry_2 = MockConfigEntry( + domain="comp2", + title="Test 1", + source="bla", + ) + entry_2.supports_remove_device = True + entry_2.add_to_hass(hass) + + entry_3 = MockConfigEntry( + domain="comp3", + title="Test 1", + source="bla", + ) + entry_3.supports_remove_device = True + entry_3.add_to_hass(hass) + + device_registry.async_get_or_create( + config_entry_id=entry_1.entry_id, + connections={(helpers_dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + device_registry.async_get_or_create( + config_entry_id=entry_2.entry_id, + connections={(helpers_dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + device_entry = device_registry.async_get_or_create( + config_entry_id=entry_3.entry_id, + connections={(helpers_dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + assert device_entry.config_entries == { + entry_1.entry_id, + entry_2.entry_id, + entry_3.entry_id, + } + + fake_entry_id = "abc123" + assert entry_1.entry_id != fake_entry_id + fake_device_id = "abc123" + assert device_entry.id != fake_device_id + + # Try removing a non existing config entry from the device + await ws_client.send_json( + { + "id": 5, + "type": "config/device_registry/remove_config_entry", + "config_entry_id": fake_entry_id, + "device_id": device_entry.id, + } + ) + response = await ws_client.receive_json() + + assert not response["success"] + assert response["error"]["code"] == "unknown_error" + assert response["error"]["message"] == "Unknown config entry" + + # Try removing a config entry which does not support removal from the device + await ws_client.send_json( + { + "id": 6, + "type": "config/device_registry/remove_config_entry", + "config_entry_id": entry_1.entry_id, + "device_id": device_entry.id, + } + ) + response = await ws_client.receive_json() + + assert not response["success"] + assert response["error"]["code"] == "unknown_error" + assert ( + response["error"]["message"] == "Config entry does not support device removal" + ) + + # Try removing a config entry from a device which does not exist + await ws_client.send_json( + { + "id": 7, + "type": "config/device_registry/remove_config_entry", + "config_entry_id": entry_2.entry_id, + "device_id": fake_device_id, + } + ) + response = await ws_client.receive_json() + + assert not response["success"] + assert response["error"]["code"] == "unknown_error" + assert response["error"]["message"] == "Unknown device" + + # Try removing a config entry from a device which it's not connected to + await ws_client.send_json( + { + "id": 8, + "type": "config/device_registry/remove_config_entry", + "config_entry_id": entry_2.entry_id, + "device_id": device_entry.id, + } + ) + response = await ws_client.receive_json() + + assert response["success"] + assert set(response["result"]["config_entries"]) == { + entry_1.entry_id, + entry_3.entry_id, + } + + await ws_client.send_json( + { + "id": 9, + "type": "config/device_registry/remove_config_entry", + "config_entry_id": entry_2.entry_id, + "device_id": device_entry.id, + } + ) + response = await ws_client.receive_json() + + assert not response["success"] + assert response["error"]["code"] == "unknown_error" + assert response["error"]["message"] == "Config entry not in device" + + # Try removing a config entry which can't be loaded from a device - allowed + await ws_client.send_json( + { + "id": 10, + "type": "config/device_registry/remove_config_entry", + "config_entry_id": entry_3.entry_id, + "device_id": device_entry.id, + } + ) + response = await ws_client.receive_json() + + assert not response["success"] + assert response["error"]["code"] == "unknown_error" + assert response["error"]["message"] == "Integration not found" From 39c1209e1cfee40a5122cf1ad2476a9383e9f7eb Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 21 Feb 2022 12:42:54 +0100 Subject: [PATCH 0872/1098] Bump samsungtvws to 1.7.0 (#66978) Co-authored-by: epenet --- homeassistant/components/samsungtv/bridge.py | 4 ++-- homeassistant/components/samsungtv/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/samsungtv/bridge.py b/homeassistant/components/samsungtv/bridge.py index ee5e44f626c59d..83df8952278b89 100644 --- a/homeassistant/components/samsungtv/bridge.py +++ b/homeassistant/components/samsungtv/bridge.py @@ -330,7 +330,7 @@ def try_connect(self) -> str: timeout=config[CONF_TIMEOUT], name=config[CONF_NAME], ) as remote: - remote.open() + remote.open("samsung.remote.control") self.token = remote.token if self.token is None: config[CONF_TOKEN] = "*****" @@ -385,7 +385,7 @@ def _get_remote(self, avoid_open: bool = False) -> Remote: name=VALUE_CONF_NAME, ) if not avoid_open: - self._remote.open() + self._remote.open("samsung.remote.control") # This is only happening when the auth was switched to DENY # A removed auth will lead to socket timeout because waiting for auth popup is just an open socket except ConnectionFailure: diff --git a/homeassistant/components/samsungtv/manifest.json b/homeassistant/components/samsungtv/manifest.json index b4aa313748265f..ef0e99001c91b8 100644 --- a/homeassistant/components/samsungtv/manifest.json +++ b/homeassistant/components/samsungtv/manifest.json @@ -5,7 +5,7 @@ "requirements": [ "getmac==0.8.2", "samsungctl[websocket]==0.7.1", - "samsungtvws==1.6.0", + "samsungtvws==1.7.0", "wakeonlan==2.0.1" ], "ssdp": [ diff --git a/requirements_all.txt b/requirements_all.txt index daf0559cde62bb..e90f95a409adc6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2150,7 +2150,7 @@ rxv==0.7.0 samsungctl[websocket]==0.7.1 # homeassistant.components.samsungtv -samsungtvws==1.6.0 +samsungtvws==1.7.0 # homeassistant.components.satel_integra satel_integra==0.3.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a79ce5ce6b7594..86ce8921fd600b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1333,7 +1333,7 @@ rxv==0.7.0 samsungctl[websocket]==0.7.1 # homeassistant.components.samsungtv -samsungtvws==1.6.0 +samsungtvws==1.7.0 # homeassistant.components.dhcp scapy==2.4.5 From b560909b311cd1d7a83435a0620020bff034dd49 Mon Sep 17 00:00:00 2001 From: Garrett <7310260+G-Two@users.noreply.github.com> Date: Mon, 21 Feb 2022 07:09:36 -0500 Subject: [PATCH 0873/1098] Bump to subarulink 0.4.2 (#66403) --- homeassistant/components/subaru/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/subaru/manifest.json b/homeassistant/components/subaru/manifest.json index b08b2381211571..6e1151cdccbc19 100644 --- a/homeassistant/components/subaru/manifest.json +++ b/homeassistant/components/subaru/manifest.json @@ -3,7 +3,7 @@ "name": "Subaru", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/subaru", - "requirements": ["subarulink==0.3.12"], + "requirements": ["subarulink==0.4.2"], "codeowners": ["@G-Two"], "iot_class": "cloud_polling", "loggers": ["stdiomask", "subarulink"] diff --git a/requirements_all.txt b/requirements_all.txt index e90f95a409adc6..e47d2aa18f8e58 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2296,7 +2296,7 @@ streamlabswater==1.0.1 stringcase==1.2.0 # homeassistant.components.subaru -subarulink==0.3.12 +subarulink==0.4.2 # homeassistant.components.ecovacs sucks==0.9.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 86ce8921fd600b..af9498981814f9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1423,7 +1423,7 @@ stookalert==0.1.4 stringcase==1.2.0 # homeassistant.components.subaru -subarulink==0.3.12 +subarulink==0.4.2 # homeassistant.components.solarlog sunwatcher==0.2.1 From 4b28025a71e3c0a3100926f9ea11eb363864075f Mon Sep 17 00:00:00 2001 From: Jonathan Keljo Date: Mon, 21 Feb 2022 08:06:07 -0800 Subject: [PATCH 0874/1098] Bump greeneye_monitor to v3.0.3 (#66973) --- homeassistant/components/greeneye_monitor/manifest.json | 6 ++++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/greeneye_monitor/manifest.json b/homeassistant/components/greeneye_monitor/manifest.json index 0f4bef2b38f897..4640d062ac7887 100644 --- a/homeassistant/components/greeneye_monitor/manifest.json +++ b/homeassistant/components/greeneye_monitor/manifest.json @@ -3,11 +3,13 @@ "name": "GreenEye Monitor (GEM)", "documentation": "https://www.home-assistant.io/integrations/greeneye_monitor", "requirements": [ - "greeneye_monitor==3.0.1" + "greeneye_monitor==3.0.3" ], "codeowners": [ "@jkeljo" ], "iot_class": "local_push", - "loggers": ["greeneye"] + "loggers": [ + "greeneye" + ] } \ No newline at end of file diff --git a/requirements_all.txt b/requirements_all.txt index e47d2aa18f8e58..132ece5a55f1b9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -776,7 +776,7 @@ gps3==0.33.3 greeclimate==1.0.2 # homeassistant.components.greeneye_monitor -greeneye_monitor==3.0.1 +greeneye_monitor==3.0.3 # homeassistant.components.greenwave greenwavereality==0.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index af9498981814f9..a7881f1b8c33fc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -507,7 +507,7 @@ googlemaps==2.5.1 greeclimate==1.0.2 # homeassistant.components.greeneye_monitor -greeneye_monitor==3.0.1 +greeneye_monitor==3.0.3 # homeassistant.components.pure_energie gridnet==4.0.0 From fe1229a7d9ed162376f5290869238208dbdf9366 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 21 Feb 2022 17:42:52 +0100 Subject: [PATCH 0875/1098] Motion blinds add VerticalBlindLeft support (#66961) --- homeassistant/components/motion_blinds/cover.py | 1 + homeassistant/components/motion_blinds/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/motion_blinds/cover.py b/homeassistant/components/motion_blinds/cover.py index f5eff45539e596..9bc952a21eaf34 100644 --- a/homeassistant/components/motion_blinds/cover.py +++ b/homeassistant/components/motion_blinds/cover.py @@ -51,6 +51,7 @@ BlindType.ShangriLaBlind: CoverDeviceClass.BLIND, BlindType.DoubleRoller: CoverDeviceClass.SHADE, BlindType.VerticalBlind: CoverDeviceClass.BLIND, + BlindType.VerticalBlindLeft: CoverDeviceClass.BLIND, } TDBU_DEVICE_MAP = { diff --git a/homeassistant/components/motion_blinds/manifest.json b/homeassistant/components/motion_blinds/manifest.json index 47efe9bf18e8b1..c904320d9af778 100644 --- a/homeassistant/components/motion_blinds/manifest.json +++ b/homeassistant/components/motion_blinds/manifest.json @@ -3,7 +3,7 @@ "name": "Motion Blinds", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/motion_blinds", - "requirements": ["motionblinds==0.5.12"], + "requirements": ["motionblinds==0.5.13"], "dependencies": ["network"], "codeowners": ["@starkillerOG"], "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index 132ece5a55f1b9..2f1367834a2d3c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1049,7 +1049,7 @@ mitemp_bt==0.0.5 moehlenhoff-alpha2==1.1.2 # homeassistant.components.motion_blinds -motionblinds==0.5.12 +motionblinds==0.5.13 # homeassistant.components.motioneye motioneye-client==0.3.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a7881f1b8c33fc..564fe93158eec8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -667,7 +667,7 @@ minio==5.0.10 moehlenhoff-alpha2==1.1.2 # homeassistant.components.motion_blinds -motionblinds==0.5.12 +motionblinds==0.5.13 # homeassistant.components.motioneye motioneye-client==0.3.12 From a82d4d1b7b2b2531d215ac6133f31ba62d37ab81 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 21 Feb 2022 06:50:42 -1000 Subject: [PATCH 0876/1098] Add support for dual head WiZ devices (#66955) --- homeassistant/components/wiz/manifest.json | 2 +- homeassistant/components/wiz/number.py | 93 ++++++++++++++++++---- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/wiz/__init__.py | 34 ++++++-- tests/components/wiz/test_number.py | 38 ++++++++- 6 files changed, 143 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/wiz/manifest.json b/homeassistant/components/wiz/manifest.json index 021b986f82ec93..104f8db5ba0b01 100644 --- a/homeassistant/components/wiz/manifest.json +++ b/homeassistant/components/wiz/manifest.json @@ -10,7 +10,7 @@ "dependencies": ["network"], "quality_scale": "platinum", "documentation": "https://www.home-assistant.io/integrations/wiz", - "requirements": ["pywizlight==0.5.10"], + "requirements": ["pywizlight==0.5.11"], "iot_class": "local_push", "codeowners": ["@sbidy"] } diff --git a/homeassistant/components/wiz/number.py b/homeassistant/components/wiz/number.py index eed9bd9280356a..f7d827534b3879 100644 --- a/homeassistant/components/wiz/number.py +++ b/homeassistant/components/wiz/number.py @@ -1,9 +1,17 @@ """Support for WiZ effect speed numbers.""" from __future__ import annotations -from pywizlight.bulblibrary import BulbClass +from collections.abc import Callable, Coroutine +from dataclasses import dataclass +from typing import Optional, cast -from homeassistant.components.number import NumberEntity, NumberMode +from pywizlight import wizlight + +from homeassistant.components.number import ( + NumberEntity, + NumberEntityDescription, + NumberMode, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -12,7 +20,55 @@ from .entity import WizEntity from .models import WizData -EFFECT_SPEED_UNIQUE_ID = "{}_effect_speed" + +@dataclass +class WizNumberEntityDescriptionMixin: + """Mixin to describe a WiZ number entity.""" + + value_fn: Callable[[wizlight], int | None] + set_value_fn: Callable[[wizlight, int], Coroutine[None, None, None]] + required_feature: str + + +@dataclass +class WizNumberEntityDescription( + NumberEntityDescription, WizNumberEntityDescriptionMixin +): + """Class to describe a WiZ number entity.""" + + +async def _async_set_speed(device: wizlight, speed: int) -> None: + await device.set_speed(speed) + + +async def _async_set_ratio(device: wizlight, ratio: int) -> None: + await device.set_ratio(ratio) + + +NUMBERS: tuple[WizNumberEntityDescription, ...] = ( + WizNumberEntityDescription( + key="effect_speed", + min_value=10, + max_value=200, + step=1, + icon="mdi:speedometer", + name="Effect Speed", + value_fn=lambda device: cast(Optional[int], device.state.get_speed()), + set_value_fn=_async_set_speed, + required_feature="effect", + ), + WizNumberEntityDescription( + key="dual_head_ratio", + min_value=0, + max_value=100, + step=1, + icon="mdi:floor-lamp-dual", + name="Dual Head Ratio", + value_fn=lambda device: cast(Optional[int], device.state.get_ratio()), + set_value_fn=_async_set_ratio, + required_feature="dual_head", + ), +) async def async_setup_entry( @@ -22,37 +78,44 @@ async def async_setup_entry( ) -> None: """Set up the wiz speed number.""" wiz_data: WizData = hass.data[DOMAIN][entry.entry_id] - if wiz_data.bulb.bulbtype.bulb_type != BulbClass.SOCKET: - async_add_entities([WizSpeedNumber(wiz_data, entry.title)]) + async_add_entities( + WizSpeedNumber(wiz_data, entry.title, description) + for description in NUMBERS + if getattr(wiz_data.bulb.bulbtype.features, description.required_feature) + ) class WizSpeedNumber(WizEntity, NumberEntity): """Defines a WiZ speed number.""" - _attr_min_value = 10 - _attr_max_value = 200 - _attr_step = 1 + entity_description: WizNumberEntityDescription _attr_mode = NumberMode.SLIDER - _attr_icon = "mdi:speedometer" - def __init__(self, wiz_data: WizData, name: str) -> None: + def __init__( + self, wiz_data: WizData, name: str, description: WizNumberEntityDescription + ) -> None: """Initialize an WiZ device.""" super().__init__(wiz_data, name) - self._attr_unique_id = EFFECT_SPEED_UNIQUE_ID.format(self._device.mac) - self._attr_name = f"{name} Effect Speed" + self.entity_description = description + self._attr_unique_id = f"{self._device.mac}_{description.key}" + self._attr_name = f"{name} {description.name}" self._async_update_attrs() @property def available(self) -> bool: """Return if entity is available.""" - return super().available and self._device.state.get_speed() is not None + return ( + super().available + and self.entity_description.value_fn(self._device) is not None + ) @callback def _async_update_attrs(self) -> None: """Handle updating _attr values.""" - self._attr_value = self._device.state.get_speed() + if (value := self.entity_description.value_fn(self._device)) is not None: + self._attr_value = float(value) async def async_set_value(self, value: float) -> None: """Set the speed value.""" - await self._device.set_speed(int(value)) + await self.entity_description.set_value_fn(self._device, int(value)) await self.coordinator.async_request_refresh() diff --git a/requirements_all.txt b/requirements_all.txt index 2f1367834a2d3c..bec28e688e7e06 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2057,7 +2057,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.5.10 +pywizlight==0.5.11 # homeassistant.components.xeoma pyxeoma==1.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 564fe93158eec8..5787a59ea5792e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1288,7 +1288,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.5.10 +pywizlight==0.5.11 # homeassistant.components.zerproc pyzerproc==0.4.8 diff --git a/tests/components/wiz/__init__.py b/tests/components/wiz/__init__.py index ca4d04601735c1..cb23b3eef34ea3 100644 --- a/tests/components/wiz/__init__.py +++ b/tests/components/wiz/__init__.py @@ -7,7 +7,7 @@ from unittest.mock import AsyncMock, MagicMock, patch from pywizlight import SCENES, BulbType, PilotParser, wizlight -from pywizlight.bulblibrary import FEATURE_MAP, BulbClass, KelvinRange +from pywizlight.bulblibrary import BulbClass, Features, KelvinRange from pywizlight.discovery import DiscoveredBulb from homeassistant.components.wiz.const import DOMAIN @@ -84,10 +84,23 @@ "ewfHex":"ff00ffff000000",\ "ping":0}}' ) +FAKE_DUAL_HEAD_RGBWW_BULB = BulbType( + bulb_type=BulbClass.RGB, + name="ESP01_DHRGB_03", + features=Features( + color=True, color_tmp=True, effect=True, brightness=True, dual_head=True + ), + kelvin_range=KelvinRange(2700, 6500), + fw_version="1.0.0", + white_channels=2, + white_to_color_ratio=80, +) FAKE_RGBWW_BULB = BulbType( bulb_type=BulbClass.RGB, name="ESP01_SHRGB_03", - features=FEATURE_MAP[BulbClass.RGB], + features=Features( + color=True, color_tmp=True, effect=True, brightness=True, dual_head=False + ), kelvin_range=KelvinRange(2700, 6500), fw_version="1.0.0", white_channels=2, @@ -96,7 +109,9 @@ FAKE_RGBW_BULB = BulbType( bulb_type=BulbClass.RGB, name="ESP01_SHRGB_03", - features=FEATURE_MAP[BulbClass.RGB], + features=Features( + color=True, color_tmp=True, effect=True, brightness=True, dual_head=False + ), kelvin_range=KelvinRange(2700, 6500), fw_version="1.0.0", white_channels=1, @@ -105,7 +120,9 @@ FAKE_DIMMABLE_BULB = BulbType( bulb_type=BulbClass.DW, name="ESP01_DW_03", - features=FEATURE_MAP[BulbClass.DW], + features=Features( + color=False, color_tmp=False, effect=True, brightness=True, dual_head=False + ), kelvin_range=KelvinRange(2700, 6500), fw_version="1.0.0", white_channels=1, @@ -114,7 +131,9 @@ FAKE_TURNABLE_BULB = BulbType( bulb_type=BulbClass.TW, name="ESP01_TW_03", - features=FEATURE_MAP[BulbClass.TW], + features=Features( + color=False, color_tmp=True, effect=True, brightness=True, dual_head=False + ), kelvin_range=KelvinRange(2700, 6500), fw_version="1.0.0", white_channels=1, @@ -123,7 +142,9 @@ FAKE_SOCKET = BulbType( bulb_type=BulbClass.SOCKET, name="ESP01_SOCKET_03", - features=FEATURE_MAP[BulbClass.SOCKET], + features=Features( + color=False, color_tmp=False, effect=False, brightness=False, dual_head=False + ), kelvin_range=KelvinRange(2700, 6500), fw_version="1.0.0", white_channels=2, @@ -171,6 +192,7 @@ async def _save_setup_callback(callback: Callable) -> None: bulb.start_push = AsyncMock(side_effect=_save_setup_callback) bulb.async_close = AsyncMock() bulb.set_speed = AsyncMock() + bulb.set_ratio = AsyncMock() bulb.diagnostics = { "mocked": "mocked", "roomId": 123, diff --git a/tests/components/wiz/test_number.py b/tests/components/wiz/test_number.py index a1ab5e6bbae0a2..1d45be9b8cfdcf 100644 --- a/tests/components/wiz/test_number.py +++ b/tests/components/wiz/test_number.py @@ -6,12 +6,17 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er -from . import FAKE_MAC, async_push_update, async_setup_integration +from . import ( + FAKE_DUAL_HEAD_RGBWW_BULB, + FAKE_MAC, + async_push_update, + async_setup_integration, +) async def test_speed_operation(hass: HomeAssistant) -> None: """Test changing a speed.""" - bulb, _ = await async_setup_integration(hass) + bulb, _ = await async_setup_integration(hass, bulb_type=FAKE_DUAL_HEAD_RGBWW_BULB) await async_push_update(hass, bulb, {"mac": FAKE_MAC}) entity_id = "number.mock_title_effect_speed" entity_registry = er.async_get(hass) @@ -19,7 +24,7 @@ async def test_speed_operation(hass: HomeAssistant) -> None: assert hass.states.get(entity_id).state == STATE_UNAVAILABLE await async_push_update(hass, bulb, {"mac": FAKE_MAC, "speed": 50}) - assert hass.states.get(entity_id).state == "50" + assert hass.states.get(entity_id).state == "50.0" await hass.services.async_call( NUMBER_DOMAIN, @@ -29,4 +34,29 @@ async def test_speed_operation(hass: HomeAssistant) -> None: ) bulb.set_speed.assert_called_with(30) await async_push_update(hass, bulb, {"mac": FAKE_MAC, "speed": 30}) - assert hass.states.get(entity_id).state == "30" + assert hass.states.get(entity_id).state == "30.0" + + +async def test_ratio_operation(hass: HomeAssistant) -> None: + """Test changing a dual head ratio.""" + bulb, _ = await async_setup_integration(hass, bulb_type=FAKE_DUAL_HEAD_RGBWW_BULB) + await async_push_update(hass, bulb, {"mac": FAKE_MAC}) + entity_id = "number.mock_title_dual_head_ratio" + entity_registry = er.async_get(hass) + assert ( + entity_registry.async_get(entity_id).unique_id == f"{FAKE_MAC}_dual_head_ratio" + ) + assert hass.states.get(entity_id).state == STATE_UNAVAILABLE + + await async_push_update(hass, bulb, {"mac": FAKE_MAC, "ratio": 50}) + assert hass.states.get(entity_id).state == "50.0" + + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + {ATTR_ENTITY_ID: entity_id, ATTR_VALUE: 30}, + blocking=True, + ) + bulb.set_ratio.assert_called_with(30) + await async_push_update(hass, bulb, {"mac": FAKE_MAC, "ratio": 30}) + assert hass.states.get(entity_id).state == "30.0" From 5c5f9418eee30cc124f9f5e880d1abaddfefc44b Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 21 Feb 2022 17:53:58 +0100 Subject: [PATCH 0877/1098] Remove `setup.py` (#66023) --- setup.py | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 setup.py diff --git a/setup.py b/setup.py deleted file mode 100644 index 69bf65dd8a4bde..00000000000000 --- a/setup.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Entry point for setuptools. Required for editable installs. -TODO: Remove file after updating to pip 21.3 -""" -from setuptools import setup - -setup() From a4ba51127629afebe53972651df0d5dab9b7e307 Mon Sep 17 00:00:00 2001 From: Igor Pakhomov Date: Mon, 21 Feb 2022 18:56:34 +0200 Subject: [PATCH 0878/1098] Add aditional sensors for dmaker.airfresh.a1/t2017 to xiaomi_miio (#66370) --- .../components/xiaomi_miio/sensor.py | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index 36a8cd24213b05..cbab107994bb0e 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -34,6 +34,7 @@ POWER_WATT, PRESSURE_HPA, TEMP_CELSIUS, + TIME_DAYS, TIME_HOURS, TIME_SECONDS, VOLUME_CUBIC_METERS, @@ -93,9 +94,15 @@ ATTR_BATTERY = "battery" ATTR_CARBON_DIOXIDE = "co2" ATTR_CHARGING = "charging" +ATTR_CONTROL_SPEED = "control_speed" ATTR_DISPLAY_CLOCK = "display_clock" +ATTR_FAVORITE_SPEED = "favorite_speed" ATTR_FILTER_LIFE_REMAINING = "filter_life_remaining" ATTR_FILTER_HOURS_USED = "filter_hours_used" +ATTR_DUST_FILTER_LIFE_REMAINING = "dust_filter_life_remaining" +ATTR_DUST_FILTER_LIFE_REMAINING_DAYS = "dust_filter_life_remaining_days" +ATTR_UPPER_FILTER_LIFE_REMAINING = "upper_filter_life_remaining" +ATTR_UPPER_FILTER_LIFE_REMAINING_DAYS = "upper_filter_life_remaining_days" ATTR_FILTER_USE = "filter_use" ATTR_HUMIDITY = "humidity" ATTR_ILLUMINANCE = "illuminance" @@ -107,6 +114,7 @@ ATTR_NIGHT_TIME_BEGIN = "night_time_begin" ATTR_NIGHT_TIME_END = "night_time_end" ATTR_PM25 = "pm25" +ATTR_PM25_2 = "pm25_2" ATTR_POWER = "power" ATTR_PRESSURE = "pressure" ATTR_PURIFY_VOLUME = "purify_volume" @@ -183,6 +191,22 @@ class XiaomiMiioSensorDescription(SensorEntityDescription): state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, ), + ATTR_CONTROL_SPEED: XiaomiMiioSensorDescription( + key=ATTR_CONTROL_SPEED, + name="Control Speed", + native_unit_of_measurement="rpm", + icon="mdi:fast-forward", + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + ), + ATTR_FAVORITE_SPEED: XiaomiMiioSensorDescription( + key=ATTR_FAVORITE_SPEED, + name="Favorite Speed", + native_unit_of_measurement="rpm", + icon="mdi:fast-forward", + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + ), ATTR_MOTOR_SPEED: XiaomiMiioSensorDescription( key=ATTR_MOTOR_SPEED, name="Motor Speed", @@ -235,6 +259,13 @@ class XiaomiMiioSensorDescription(SensorEntityDescription): device_class=SensorDeviceClass.PM25, state_class=SensorStateClass.MEASUREMENT, ), + ATTR_PM25_2: XiaomiMiioSensorDescription( + key=ATTR_PM25, + name="PM2.5", + native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + device_class=SensorDeviceClass.PM25, + state_class=SensorStateClass.MEASUREMENT, + ), ATTR_FILTER_LIFE_REMAINING: XiaomiMiioSensorDescription( key=ATTR_FILTER_LIFE_REMAINING, name="Filter Life Remaining", @@ -252,6 +283,40 @@ class XiaomiMiioSensorDescription(SensorEntityDescription): state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, ), + ATTR_DUST_FILTER_LIFE_REMAINING: XiaomiMiioSensorDescription( + key=ATTR_DUST_FILTER_LIFE_REMAINING, + name="Dust filter life remaining", + native_unit_of_measurement=PERCENTAGE, + icon="mdi:air-filter", + state_class=SensorStateClass.MEASUREMENT, + attributes=("filter_type",), + entity_category=EntityCategory.DIAGNOSTIC, + ), + ATTR_DUST_FILTER_LIFE_REMAINING_DAYS: XiaomiMiioSensorDescription( + key=ATTR_DUST_FILTER_LIFE_REMAINING_DAYS, + name="Dust filter life remaining days", + native_unit_of_measurement=TIME_DAYS, + icon="mdi:clock-outline", + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + ), + ATTR_UPPER_FILTER_LIFE_REMAINING: XiaomiMiioSensorDescription( + key=ATTR_UPPER_FILTER_LIFE_REMAINING, + name="Upper filter life remaining", + native_unit_of_measurement=PERCENTAGE, + icon="mdi:air-filter", + state_class=SensorStateClass.MEASUREMENT, + attributes=("filter_type",), + entity_category=EntityCategory.DIAGNOSTIC, + ), + ATTR_UPPER_FILTER_LIFE_REMAINING_DAYS: XiaomiMiioSensorDescription( + key=ATTR_UPPER_FILTER_LIFE_REMAINING_DAYS, + name="Upper filter life remaining days", + native_unit_of_measurement=TIME_DAYS, + icon="mdi:clock-outline", + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + ), ATTR_CARBON_DIOXIDE: XiaomiMiioSensorDescription( key=ATTR_CARBON_DIOXIDE, name="Carbon Dioxide", @@ -379,11 +444,23 @@ class XiaomiMiioSensorDescription(SensorEntityDescription): ) AIRFRESH_SENSORS_A1 = ( ATTR_CARBON_DIOXIDE, + ATTR_DUST_FILTER_LIFE_REMAINING, + ATTR_DUST_FILTER_LIFE_REMAINING_DAYS, + ATTR_PM25_2, ATTR_TEMPERATURE, + ATTR_CONTROL_SPEED, + ATTR_FAVORITE_SPEED, ) AIRFRESH_SENSORS_T2017 = ( ATTR_CARBON_DIOXIDE, + ATTR_DUST_FILTER_LIFE_REMAINING, + ATTR_DUST_FILTER_LIFE_REMAINING_DAYS, + ATTR_UPPER_FILTER_LIFE_REMAINING, + ATTR_UPPER_FILTER_LIFE_REMAINING_DAYS, + ATTR_PM25_2, ATTR_TEMPERATURE, + ATTR_CONTROL_SPEED, + ATTR_FAVORITE_SPEED, ) FAN_V2_V3_SENSORS = ( ATTR_BATTERY, From 4efada7db0eaffe18cf5bd576c53ccc7c82f05a2 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Tue, 22 Feb 2022 00:58:15 +0800 Subject: [PATCH 0879/1098] Allow stream log level to change at runtime (#66153) --- homeassistant/components/stream/__init__.py | 4 +- homeassistant/components/stream/manifest.json | 3 +- tests/components/stream/test_init.py | 46 +++++++++++++++++++ 3 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 tests/components/stream/test_init.py diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index 66929fff79c32f..e22e06df7e29c5 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -120,10 +120,8 @@ def create_stream( def filter_libav_logging() -> None: """Filter libav logging to only log when the stream logger is at DEBUG.""" - stream_debug_enabled = logging.getLogger(__name__).isEnabledFor(logging.DEBUG) - def libav_filter(record: logging.LogRecord) -> bool: - return stream_debug_enabled + return logging.getLogger(__name__).isEnabledFor(logging.DEBUG) for logging_namespace in ( "libav.mp4", diff --git a/homeassistant/components/stream/manifest.json b/homeassistant/components/stream/manifest.json index 5f6dd4e61aaf87..1fe64defe36961 100644 --- a/homeassistant/components/stream/manifest.json +++ b/homeassistant/components/stream/manifest.json @@ -6,6 +6,5 @@ "dependencies": ["http"], "codeowners": ["@hunterjm", "@uvjustin", "@allenporter"], "quality_scale": "internal", - "iot_class": "local_push", - "loggers": ["av"] + "iot_class": "local_push" } diff --git a/tests/components/stream/test_init.py b/tests/components/stream/test_init.py new file mode 100644 index 00000000000000..92b2848caef9ab --- /dev/null +++ b/tests/components/stream/test_init.py @@ -0,0 +1,46 @@ +"""Test stream init.""" + +import logging + +import av + +from homeassistant.components.stream import __name__ as stream_name +from homeassistant.setup import async_setup_component + + +async def test_log_levels(hass, caplog): + """Test that the worker logs the url without username and password.""" + + logging.getLogger(stream_name).setLevel(logging.INFO) + + await async_setup_component(hass, "stream", {"stream": {}}) + + # These namespaces should only pass log messages when the stream logger + # is at logging.DEBUG or below + namespaces_to_toggle = ( + "mp4", + "h264", + "hevc", + "rtsp", + "tcp", + "tls", + "mpegts", + "NULL", + ) + + # Since logging is at INFO, these should not pass + for namespace in namespaces_to_toggle: + av.logging.log(av.logging.ERROR, namespace, "SHOULD NOT PASS") + + logging.getLogger(stream_name).setLevel(logging.DEBUG) + + # Since logging is now at DEBUG, these should now pass + for namespace in namespaces_to_toggle: + av.logging.log(av.logging.ERROR, namespace, "SHOULD PASS") + + # Even though logging is at DEBUG, these should not pass + av.logging.log(av.logging.WARNING, "mp4", "SHOULD NOT PASS") + av.logging.log(av.logging.WARNING, "swscaler", "SHOULD NOT PASS") + + assert "SHOULD PASS" in caplog.text + assert "SHOULD NOT PASS" not in caplog.text From 7b334d17554f27fac175feaf6916712ab5468d24 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 21 Feb 2022 07:02:09 -1000 Subject: [PATCH 0880/1098] Add additional WiZ OUIs (#66991) --- homeassistant/components/wiz/manifest.json | 3 +++ homeassistant/generated/dhcp.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/homeassistant/components/wiz/manifest.json b/homeassistant/components/wiz/manifest.json index 104f8db5ba0b01..2dacb32c094c1b 100644 --- a/homeassistant/components/wiz/manifest.json +++ b/homeassistant/components/wiz/manifest.json @@ -5,6 +5,9 @@ "dhcp": [ {"registered_devices": true}, {"macaddress":"A8BB50*"}, + {"macaddress":"D8A011*"}, + {"macaddress":"444F8E*"}, + {"macaddress":"6C2990*"}, {"hostname":"wiz_*"} ], "dependencies": ["network"], diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 8809dd45f4a3e8..60a8e50c523c07 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -165,5 +165,8 @@ {'domain': 'vicare', 'macaddress': 'B87424*'}, {'domain': 'wiz', 'registered_devices': True}, {'domain': 'wiz', 'macaddress': 'A8BB50*'}, + {'domain': 'wiz', 'macaddress': 'D8A011*'}, + {'domain': 'wiz', 'macaddress': '444F8E*'}, + {'domain': 'wiz', 'macaddress': '6C2990*'}, {'domain': 'wiz', 'hostname': 'wiz_*'}, {'domain': 'yeelight', 'hostname': 'yeelink-*'}] From 660fb393f03a4949f3f565e1834dc5d715aea3c1 Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Mon, 21 Feb 2022 18:03:38 +0100 Subject: [PATCH 0881/1098] Enable sensors based on wan scenario in Fritz!Tools (#66944) --- homeassistant/components/fritz/sensor.py | 39 +++++++++++++++--------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/fritz/sensor.py b/homeassistant/components/fritz/sensor.py index 5e4b18eebcaf1d..7874fb87b004ee 100644 --- a/homeassistant/components/fritz/sensor.py +++ b/homeassistant/components/fritz/sensor.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from datetime import datetime, timedelta import logging -from typing import Any, Literal +from typing import Any from fritzconnection.core.exceptions import FritzConnectionException from fritzconnection.lib.fritzstatus import FritzStatus @@ -134,6 +134,15 @@ def _retrieve_link_attenuation_received_state( return status.attenuation[1] / 10 # type: ignore[no-any-return] +@dataclass +class ConnectionInfo: + """Fritz sensor connection information class.""" + + connection: str + mesh_role: MeshRoles + wan_enabled: bool + + @dataclass class FritzRequireKeysMixin: """Fritz sensor data class.""" @@ -145,8 +154,7 @@ class FritzRequireKeysMixin: class FritzSensorEntityDescription(SensorEntityDescription, FritzRequireKeysMixin): """Describes Fritz sensor entity.""" - connection_type: Literal["dsl"] | None = None - exclude_mesh_role: MeshRoles = MeshRoles.SLAVE + is_suitable: Callable[[ConnectionInfo], bool] = lambda info: info.wan_enabled SENSOR_TYPES: tuple[FritzSensorEntityDescription, ...] = ( @@ -162,7 +170,7 @@ class FritzSensorEntityDescription(SensorEntityDescription, FritzRequireKeysMixi device_class=SensorDeviceClass.TIMESTAMP, entity_category=EntityCategory.DIAGNOSTIC, value_fn=_retrieve_device_uptime_state, - exclude_mesh_role=MeshRoles.NONE, + is_suitable=lambda info: info.mesh_role != MeshRoles.NONE, ), FritzSensorEntityDescription( key="connection_uptime", @@ -225,7 +233,6 @@ class FritzSensorEntityDescription(SensorEntityDescription, FritzRequireKeysMixi native_unit_of_measurement=DATA_RATE_KILOBITS_PER_SECOND, icon="mdi:upload", value_fn=_retrieve_link_kb_s_sent_state, - connection_type=DSL_CONNECTION, ), FritzSensorEntityDescription( key="link_kb_s_received", @@ -233,7 +240,6 @@ class FritzSensorEntityDescription(SensorEntityDescription, FritzRequireKeysMixi native_unit_of_measurement=DATA_RATE_KILOBITS_PER_SECOND, icon="mdi:download", value_fn=_retrieve_link_kb_s_received_state, - connection_type=DSL_CONNECTION, ), FritzSensorEntityDescription( key="link_noise_margin_sent", @@ -241,7 +247,7 @@ class FritzSensorEntityDescription(SensorEntityDescription, FritzRequireKeysMixi native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS, icon="mdi:upload", value_fn=_retrieve_link_noise_margin_sent_state, - connection_type=DSL_CONNECTION, + is_suitable=lambda info: info.wan_enabled and info.connection == DSL_CONNECTION, ), FritzSensorEntityDescription( key="link_noise_margin_received", @@ -249,7 +255,7 @@ class FritzSensorEntityDescription(SensorEntityDescription, FritzRequireKeysMixi native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS, icon="mdi:download", value_fn=_retrieve_link_noise_margin_received_state, - connection_type=DSL_CONNECTION, + is_suitable=lambda info: info.wan_enabled and info.connection == DSL_CONNECTION, ), FritzSensorEntityDescription( key="link_attenuation_sent", @@ -257,7 +263,7 @@ class FritzSensorEntityDescription(SensorEntityDescription, FritzRequireKeysMixi native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS, icon="mdi:upload", value_fn=_retrieve_link_attenuation_sent_state, - connection_type=DSL_CONNECTION, + is_suitable=lambda info: info.wan_enabled and info.connection == DSL_CONNECTION, ), FritzSensorEntityDescription( key="link_attenuation_received", @@ -265,7 +271,7 @@ class FritzSensorEntityDescription(SensorEntityDescription, FritzRequireKeysMixi native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS, icon="mdi:download", value_fn=_retrieve_link_attenuation_received_state, - connection_type=DSL_CONNECTION, + is_suitable=lambda info: info.wan_enabled and info.connection == DSL_CONNECTION, ), ) @@ -278,19 +284,22 @@ async def async_setup_entry( avm_wrapper: AvmWrapper = hass.data[DOMAIN][entry.entry_id] link_properties = await avm_wrapper.async_get_wan_link_properties() - dsl: bool = link_properties.get("NewWANAccessType") == "DSL" + connection_info = ConnectionInfo( + connection=link_properties.get("NewWANAccessType", "").lower(), + mesh_role=avm_wrapper.mesh_role, + wan_enabled=avm_wrapper.device_is_router, + ) _LOGGER.debug( - "WANAccessType of FritzBox %s is '%s'", + "ConnectionInfo for FritzBox %s: %s", avm_wrapper.host, - link_properties.get("NewWANAccessType"), + connection_info, ) entities = [ FritzBoxSensor(avm_wrapper, entry.title, description) for description in SENSOR_TYPES - if (dsl or description.connection_type != DSL_CONNECTION) - and description.exclude_mesh_role != avm_wrapper.mesh_role + if description.is_suitable(connection_info) ] async_add_entities(entities, True) From d839febbe7520253d10603c1754c6fcc25ca9872 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 21 Feb 2022 18:13:02 +0100 Subject: [PATCH 0882/1098] Add Radio Browser integration (#66950) --- .coveragerc | 2 + CODEOWNERS | 2 + homeassistant/components/onboarding/views.py | 17 +- .../components/radio_browser/__init__.py | 36 +++ .../components/radio_browser/config_flow.py | 33 ++ .../components/radio_browser/const.py | 7 + .../components/radio_browser/manifest.json | 9 + .../components/radio_browser/media_source.py | 286 ++++++++++++++++++ .../components/radio_browser/strings.json | 12 + .../radio_browser/translations/en.json | 12 + homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/onboarding/test_views.py | 17 ++ tests/components/radio_browser/__init__.py | 1 + tests/components/radio_browser/conftest.py | 30 ++ .../radio_browser/test_config_flow.py | 65 ++++ 17 files changed, 531 insertions(+), 5 deletions(-) create mode 100644 homeassistant/components/radio_browser/__init__.py create mode 100644 homeassistant/components/radio_browser/config_flow.py create mode 100644 homeassistant/components/radio_browser/const.py create mode 100644 homeassistant/components/radio_browser/manifest.json create mode 100644 homeassistant/components/radio_browser/media_source.py create mode 100644 homeassistant/components/radio_browser/strings.json create mode 100644 homeassistant/components/radio_browser/translations/en.json create mode 100644 tests/components/radio_browser/__init__.py create mode 100644 tests/components/radio_browser/conftest.py create mode 100644 tests/components/radio_browser/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index da4bdaede00b1d..1b48888ac412c8 100644 --- a/.coveragerc +++ b/.coveragerc @@ -955,6 +955,8 @@ omit = homeassistant/components/rachio/switch.py homeassistant/components/rachio/webhooks.py homeassistant/components/radarr/sensor.py + homeassistant/components/radio_browser/__init__.py + homeassistant/components/radio_browser/media_source.py homeassistant/components/radiotherm/climate.py homeassistant/components/rainbird/* homeassistant/components/raincloud/* diff --git a/CODEOWNERS b/CODEOWNERS index d80ca44f894f25..a8d24fa03a3a46 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -755,6 +755,8 @@ homeassistant/components/qwikswitch/* @kellerza tests/components/qwikswitch/* @kellerza homeassistant/components/rachio/* @bdraco tests/components/rachio/* @bdraco +homeassistant/components/radio_browser/* @frenck +tests/components/radio_browser/* @frenck homeassistant/components/radiotherm/* @vinnyfuria homeassistant/components/rainbird/* @konikvranik homeassistant/components/raincloud/* @vanstinator diff --git a/homeassistant/components/onboarding/views.py b/homeassistant/components/onboarding/views.py index 0d041463d112e1..b277bd97edf877 100644 --- a/homeassistant/components/onboarding/views.py +++ b/homeassistant/components/onboarding/views.py @@ -188,9 +188,8 @@ async def post(self, request): await self._async_mark_done(hass) - await hass.config_entries.flow.async_init( - "met", context={"source": "onboarding"} - ) + # Integrations to set up when finishing onboarding + onboard_integrations = ["met", "radio_browser"] # pylint: disable=import-outside-toplevel from homeassistant.components import hassio @@ -199,9 +198,17 @@ async def post(self, request): hassio.is_hassio(hass) and "raspberrypi" in hassio.get_core_info(hass)["machine"] ): - await hass.config_entries.flow.async_init( - "rpi_power", context={"source": "onboarding"} + onboard_integrations.append("rpi_power") + + # Set up integrations after onboarding + await asyncio.gather( + *( + hass.config_entries.flow.async_init( + domain, context={"source": "onboarding"} + ) + for domain in onboard_integrations ) + ) return self.json({}) diff --git a/homeassistant/components/radio_browser/__init__.py b/homeassistant/components/radio_browser/__init__.py new file mode 100644 index 00000000000000..89c2f22015941c --- /dev/null +++ b/homeassistant/components/radio_browser/__init__.py @@ -0,0 +1,36 @@ +"""The Radio Browser integration.""" +from __future__ import annotations + +from radios import RadioBrowser, RadioBrowserError + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import __version__ +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from .const import DOMAIN + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Radio Browser from a config entry. + + This integration doesn't set up any enitites, as it provides a media source + only. + """ + session = async_get_clientsession(hass) + radios = RadioBrowser(session=session, user_agent=f"HomeAssistant/{__version__}") + + try: + await radios.stats() + except RadioBrowserError as err: + raise ConfigEntryNotReady("Could not connect to Radio Browser API") from err + + hass.data[DOMAIN] = radios + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + del hass.data[DOMAIN] + return True diff --git a/homeassistant/components/radio_browser/config_flow.py b/homeassistant/components/radio_browser/config_flow.py new file mode 100644 index 00000000000000..1c6964d0715da0 --- /dev/null +++ b/homeassistant/components/radio_browser/config_flow.py @@ -0,0 +1,33 @@ +"""Config flow for Radio Browser integration.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.config_entries import ConfigFlow +from homeassistant.data_entry_flow import FlowResult + +from .const import DOMAIN + + +class RadioBrowserConfigFlow(ConfigFlow, domain=DOMAIN): + """Handle a config flow for Radio Browser.""" + + VERSION = 1 + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the initial step.""" + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + + if user_input is not None: + return self.async_create_entry(title="Radio Browser", data={}) + + return self.async_show_form(step_id="user") + + async def async_step_onboarding( + self, data: dict[str, Any] | None = None + ) -> FlowResult: + """Handle a flow initialized by onboarding.""" + return self.async_create_entry(title="Radio Browser", data={}) diff --git a/homeassistant/components/radio_browser/const.py b/homeassistant/components/radio_browser/const.py new file mode 100644 index 00000000000000..eb456db08e8433 --- /dev/null +++ b/homeassistant/components/radio_browser/const.py @@ -0,0 +1,7 @@ +"""Constants for the Radio Browser integration.""" +import logging +from typing import Final + +DOMAIN: Final = "radio_browser" + +LOGGER = logging.getLogger(__package__) diff --git a/homeassistant/components/radio_browser/manifest.json b/homeassistant/components/radio_browser/manifest.json new file mode 100644 index 00000000000000..865d8b25ab1e75 --- /dev/null +++ b/homeassistant/components/radio_browser/manifest.json @@ -0,0 +1,9 @@ +{ + "domain": "radio_browser", + "name": "Radio Browser", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/radio", + "requirements": ["radios==0.1.0"], + "codeowners": ["@frenck"], + "iot_class": "cloud_polling" +} diff --git a/homeassistant/components/radio_browser/media_source.py b/homeassistant/components/radio_browser/media_source.py new file mode 100644 index 00000000000000..952b0e67e25c56 --- /dev/null +++ b/homeassistant/components/radio_browser/media_source.py @@ -0,0 +1,286 @@ +"""Expose Radio Browser as a media source.""" +from __future__ import annotations + +import mimetypes + +from radios import FilterBy, Order, RadioBrowser, Station + +from homeassistant.components.media_player.const import ( + MEDIA_CLASS_CHANNEL, + MEDIA_CLASS_DIRECTORY, + MEDIA_CLASS_MUSIC, + MEDIA_TYPE_MUSIC, +) +from homeassistant.components.media_player.errors import BrowseError +from homeassistant.components.media_source.models import ( + BrowseMediaSource, + MediaSource, + MediaSourceItem, + PlayMedia, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback + +from .const import DOMAIN + +CODEC_TO_MIMETYPE = { + "MP3": "audio/mpeg", + "AAC": "audio/aac", + "AAC+": "audio/aac", + "OGG": "application/ogg", +} + + +async def async_get_media_source(hass: HomeAssistant) -> RadioMediaSource: + """Set up Radio Browser media source.""" + # Radio browser support only a single config entry + entry = hass.config_entries.async_entries(DOMAIN)[0] + radios = hass.data[DOMAIN] + + return RadioMediaSource(hass, radios, entry) + + +class RadioMediaSource(MediaSource): + """Provide Radio stations as media sources.""" + + def __init__( + self, hass: HomeAssistant, radios: RadioBrowser, entry: ConfigEntry + ) -> None: + """Initialize CameraMediaSource.""" + super().__init__(DOMAIN) + self.hass = hass + self.entry = entry + self.radios = radios + + async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia: + """Resolve selected Radio station to a streaming URL.""" + station = await self.radios.station(uuid=item.identifier) + if not station: + raise BrowseError("Radio station is no longer available") + + if not (mime_type := self._async_get_station_mime_type(station)): + raise BrowseError("Could not determine stream type of radio station") + + # Register "click" with Radio Browser + await self.radios.station_click(uuid=station.uuid) + + return PlayMedia(station.url, mime_type) + + async def async_browse_media( + self, + item: MediaSourceItem, + ) -> BrowseMediaSource: + """Return media.""" + return BrowseMediaSource( + domain=DOMAIN, + identifier=None, + media_class=MEDIA_CLASS_CHANNEL, + media_content_type=MEDIA_TYPE_MUSIC, + title=self.entry.title, + can_play=False, + can_expand=True, + children_media_class=MEDIA_CLASS_DIRECTORY, + children=[ + *await self._async_build_popular(item), + *await self._async_build_by_tag(item), + *await self._async_build_by_language(item), + *await self._async_build_by_country(item), + ], + ) + + @callback + @staticmethod + def _async_get_station_mime_type(station: Station) -> str | None: + """Determine mime type of a radio station.""" + mime_type = CODEC_TO_MIMETYPE.get(station.codec) + if not mime_type: + mime_type, _ = mimetypes.guess_type(station.url) + return mime_type + + @callback + def _async_build_stations(self, stations: list[Station]) -> list[BrowseMediaSource]: + """Build list of media sources from radio stations.""" + items: list[BrowseMediaSource] = [] + + for station in stations: + if station.codec == "UNKNOWN" or not ( + mime_type := self._async_get_station_mime_type(station) + ): + continue + + items.append( + BrowseMediaSource( + domain=DOMAIN, + identifier=station.uuid, + media_class=MEDIA_CLASS_MUSIC, + media_content_type=mime_type, + title=station.name, + can_play=True, + can_expand=False, + thumbnail=station.favicon, + ) + ) + + return items + + async def _async_build_by_country( + self, item: MediaSourceItem + ) -> list[BrowseMediaSource]: + """Handle browsing radio stations by country.""" + category, _, country_code = (item.identifier or "").partition("/") + if country_code: + stations = await self.radios.stations( + filter_by=FilterBy.COUNTRY_CODE_EXACT, + filter_term=country_code, + hide_broken=True, + order=Order.NAME, + reverse=False, + ) + return self._async_build_stations(stations) + + # We show country in the root additionally, when there is no item + if not item.identifier or category == "country": + countries = await self.radios.countries(order=Order.NAME) + return [ + BrowseMediaSource( + domain=DOMAIN, + identifier=f"country/{country.code}", + media_class=MEDIA_CLASS_DIRECTORY, + media_content_type=MEDIA_TYPE_MUSIC, + title=country.name, + can_play=False, + can_expand=True, + thumbnail=country.favicon, + ) + for country in countries + ] + + return [] + + async def _async_build_by_language( + self, item: MediaSourceItem + ) -> list[BrowseMediaSource]: + """Handle browsing radio stations by language.""" + category, _, language = (item.identifier or "").partition("/") + if category == "language" and language: + stations = await self.radios.stations( + filter_by=FilterBy.LANGUAGE_EXACT, + filter_term=language, + hide_broken=True, + order=Order.NAME, + reverse=False, + ) + return self._async_build_stations(stations) + + if category == "language": + languages = await self.radios.languages(order=Order.NAME, hide_broken=True) + return [ + BrowseMediaSource( + domain=DOMAIN, + identifier=f"language/{language.code}", + media_class=MEDIA_CLASS_DIRECTORY, + media_content_type=MEDIA_TYPE_MUSIC, + title=language.name, + can_play=False, + can_expand=True, + thumbnail=language.favicon, + ) + for language in languages + ] + + if not item.identifier: + return [ + BrowseMediaSource( + domain=DOMAIN, + identifier="language", + media_class=MEDIA_CLASS_DIRECTORY, + media_content_type=MEDIA_TYPE_MUSIC, + title="By Language", + can_play=False, + can_expand=True, + ) + ] + + return [] + + async def _async_build_popular( + self, item: MediaSourceItem + ) -> list[BrowseMediaSource]: + """Handle browsing popular radio stations.""" + if item.identifier == "popular": + stations = await self.radios.stations( + hide_broken=True, + limit=250, + order=Order.CLICK_COUNT, + reverse=True, + ) + return self._async_build_stations(stations) + + if not item.identifier: + return [ + BrowseMediaSource( + domain=DOMAIN, + identifier="popular", + media_class=MEDIA_CLASS_DIRECTORY, + media_content_type=MEDIA_TYPE_MUSIC, + title="Popular", + can_play=False, + can_expand=True, + ) + ] + + return [] + + async def _async_build_by_tag( + self, item: MediaSourceItem + ) -> list[BrowseMediaSource]: + """Handle browsing radio stations by tags.""" + category, _, tag = (item.identifier or "").partition("/") + if category == "tag" and tag: + stations = await self.radios.stations( + filter_by=FilterBy.TAG_EXACT, + filter_term=tag, + hide_broken=True, + order=Order.NAME, + reverse=False, + ) + return self._async_build_stations(stations) + + if category == "tag": + tags = await self.radios.tags( + hide_broken=True, + limit=100, + order=Order.STATION_COUNT, + reverse=True, + ) + + # Now we have the top tags, reorder them by name + tags.sort(key=lambda tag: tag.name) + + return [ + BrowseMediaSource( + domain=DOMAIN, + identifier=f"tag/{tag.name}", + media_class=MEDIA_CLASS_DIRECTORY, + media_content_type=MEDIA_TYPE_MUSIC, + title=tag.name.title(), + can_play=False, + can_expand=True, + ) + for tag in tags + ] + + if not item.identifier: + return [ + BrowseMediaSource( + domain=DOMAIN, + identifier="tag", + media_class=MEDIA_CLASS_DIRECTORY, + media_content_type=MEDIA_TYPE_MUSIC, + title="By Category", + can_play=False, + can_expand=True, + ) + ] + + return [] diff --git a/homeassistant/components/radio_browser/strings.json b/homeassistant/components/radio_browser/strings.json new file mode 100644 index 00000000000000..7bf9bc9ca66f7f --- /dev/null +++ b/homeassistant/components/radio_browser/strings.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "description": "Do you want to add Radio Browser to Home Assistant?" + } + }, + "abort": { + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/radio_browser/translations/en.json b/homeassistant/components/radio_browser/translations/en.json new file mode 100644 index 00000000000000..5f89dd9447c89f --- /dev/null +++ b/homeassistant/components/radio_browser/translations/en.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Already configured. Only a single configuration possible." + }, + "step": { + "user": { + "description": "Do you want to add Radio Browser to Home Assistant?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index aa773be82f08cf..ddec2deb65e34d 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -261,6 +261,7 @@ "pvoutput", "pvpc_hourly_pricing", "rachio", + "radio_browser", "rainforest_eagle", "rainmachine", "rdw", diff --git a/requirements_all.txt b/requirements_all.txt index bec28e688e7e06..cca10c6e92eebd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2077,6 +2077,9 @@ quantum-gateway==0.0.6 # homeassistant.components.rachio rachiopy==1.0.3 +# homeassistant.components.radio_browser +radios==0.1.0 + # homeassistant.components.radiotherm radiotherm==2.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5787a59ea5792e..602c6c3c2cb906 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1296,6 +1296,9 @@ pyzerproc==0.4.8 # homeassistant.components.rachio rachiopy==1.0.3 +# homeassistant.components.radio_browser +radios==0.1.0 + # homeassistant.components.rainmachine regenmaschine==2022.01.0 diff --git a/tests/components/onboarding/test_views.py b/tests/components/onboarding/test_views.py index 9605fb9e71cd4b..976e2b84c68eb0 100644 --- a/tests/components/onboarding/test_views.py +++ b/tests/components/onboarding/test_views.py @@ -381,6 +381,23 @@ async def test_onboarding_core_sets_up_met(hass, hass_storage, hass_client): assert len(hass.states.async_entity_ids("weather")) == 1 +async def test_onboarding_core_sets_up_radio_browser(hass, hass_storage, hass_client): + """Test finishing the core step set up the radio browser.""" + mock_storage(hass_storage, {"done": [const.STEP_USER]}) + + assert await async_setup_component(hass, "onboarding", {}) + await hass.async_block_till_done() + + client = await hass_client() + + resp = await client.post("/api/onboarding/core_config") + + assert resp.status == 200 + + await hass.async_block_till_done() + assert len(hass.config_entries.async_entries("radio_browser")) == 1 + + async def test_onboarding_core_sets_up_rpi_power( hass, hass_storage, hass_client, aioclient_mock, rpi ): diff --git a/tests/components/radio_browser/__init__.py b/tests/components/radio_browser/__init__.py new file mode 100644 index 00000000000000..708e07fefe6593 --- /dev/null +++ b/tests/components/radio_browser/__init__.py @@ -0,0 +1 @@ +"""Tests for the Radio Browser integration.""" diff --git a/tests/components/radio_browser/conftest.py b/tests/components/radio_browser/conftest.py new file mode 100644 index 00000000000000..5a5b888d944a6d --- /dev/null +++ b/tests/components/radio_browser/conftest.py @@ -0,0 +1,30 @@ +"""Fixtures for the Radio Browser integration tests.""" +from __future__ import annotations + +from collections.abc import Generator +from unittest.mock import AsyncMock, patch + +import pytest + +from homeassistant.components.radio_browser.const import DOMAIN + +from tests.common import MockConfigEntry + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Return the default mocked config entry.""" + return MockConfigEntry( + title="My Radios", + domain=DOMAIN, + data={}, + ) + + +@pytest.fixture +def mock_setup_entry() -> Generator[AsyncMock, None, None]: + """Mock setting up a config entry.""" + with patch( + "homeassistant.components.radio_browser.async_setup_entry", return_value=True + ) as mock_setup: + yield mock_setup diff --git a/tests/components/radio_browser/test_config_flow.py b/tests/components/radio_browser/test_config_flow.py new file mode 100644 index 00000000000000..8a5a3d9ccce9dd --- /dev/null +++ b/tests/components/radio_browser/test_config_flow.py @@ -0,0 +1,65 @@ +"""Test the Radio Browser config flow.""" +from unittest.mock import AsyncMock + +from homeassistant.components.radio_browser.const import DOMAIN +from homeassistant.config_entries import SOURCE_USER +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import ( + RESULT_TYPE_ABORT, + RESULT_TYPE_CREATE_ENTRY, + RESULT_TYPE_FORM, +) + +from tests.common import MockConfigEntry + + +async def test_full_user_flow(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None: + """Test the full user configuration flow.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert result.get("type") == RESULT_TYPE_FORM + assert result.get("errors") is None + assert "flow_id" in result + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={}, + ) + + assert result2.get("type") == RESULT_TYPE_CREATE_ENTRY + assert result2.get("title") == "Radio Browser" + assert result2.get("data") == {} + + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_already_configured( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_setup_entry: AsyncMock, +) -> None: + """Test we abort if the Radio Browser is already configured.""" + mock_config_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + + assert result.get("type") == RESULT_TYPE_ABORT + assert result.get("reason") == "single_instance_allowed" + + +async def test_onboarding_flow( + hass: HomeAssistant, mock_setup_entry: AsyncMock +) -> None: + """Test the onboarding configuration flow.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "onboarding"} + ) + + assert result.get("type") == RESULT_TYPE_CREATE_ENTRY + assert result.get("title") == "Radio Browser" + assert result.get("data") == {} + + assert len(mock_setup_entry.mock_calls) == 1 From 75b5ef45d86b64eead2ea9d67c1ebe6ee2b6aa49 Mon Sep 17 00:00:00 2001 From: Maximilian <43999966+DeerMaximum@users.noreply.github.com> Date: Mon, 21 Feb 2022 18:22:54 +0100 Subject: [PATCH 0883/1098] Fix nina warnings in city states (#65914) --- homeassistant/components/nina/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/nina/manifest.json b/homeassistant/components/nina/manifest.json index c3a0b43f7defa1..3f40668fdd548d 100644 --- a/homeassistant/components/nina/manifest.json +++ b/homeassistant/components/nina/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/nina", "requirements": [ - "pynina==0.1.4" + "pynina==0.1.5" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index cca10c6e92eebd..c6c0267de34183 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1705,7 +1705,7 @@ pynetgear==0.9.1 pynetio==0.1.9.1 # homeassistant.components.nina -pynina==0.1.4 +pynina==0.1.5 # homeassistant.components.nuki pynuki==1.5.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 602c6c3c2cb906..f82e28776c06cb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1086,7 +1086,7 @@ pymysensors==0.22.1 pynetgear==0.9.1 # homeassistant.components.nina -pynina==0.1.4 +pynina==0.1.5 # homeassistant.components.nuki pynuki==1.5.2 From 0dfc4ec9bed7ca205bad36be45ea7041834aa834 Mon Sep 17 00:00:00 2001 From: avee87 <6134677+avee87@users.noreply.github.com> Date: Mon, 21 Feb 2022 17:24:02 +0000 Subject: [PATCH 0884/1098] Rename manual alarm integrations (#66979) --- homeassistant/components/manual/manifest.json | 2 +- homeassistant/components/manual_mqtt/manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/manual/manifest.json b/homeassistant/components/manual/manifest.json index 832631878ebeb5..fb72ef8b1db665 100644 --- a/homeassistant/components/manual/manifest.json +++ b/homeassistant/components/manual/manifest.json @@ -1,6 +1,6 @@ { "domain": "manual", - "name": "Manual", + "name": "Manual Alarm Control Panel", "documentation": "https://www.home-assistant.io/integrations/manual", "codeowners": [], "quality_scale": "internal", diff --git a/homeassistant/components/manual_mqtt/manifest.json b/homeassistant/components/manual_mqtt/manifest.json index 56b13ce90a7fc8..6e0cc30e207c66 100644 --- a/homeassistant/components/manual_mqtt/manifest.json +++ b/homeassistant/components/manual_mqtt/manifest.json @@ -1,6 +1,6 @@ { "domain": "manual_mqtt", - "name": "Manual MQTT", + "name": "Manual MQTT Alarm Control Panel", "documentation": "https://www.home-assistant.io/integrations/manual_mqtt", "dependencies": ["mqtt"], "codeowners": [], From 8ea6cbc257a1d68054cff5028caece9dd9684f45 Mon Sep 17 00:00:00 2001 From: Mike Degatano Date: Mon, 21 Feb 2022 12:56:20 -0500 Subject: [PATCH 0885/1098] Support variables in templates with timeout (#66990) --- .../components/websocket_api/commands.py | 2 +- .../components/websocket_api/test_commands.py | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index 4b64e028f9710c..0b7e355ef24a21 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -346,7 +346,7 @@ async def handle_render_template( if timeout: try: timed_out = await template_obj.async_render_will_timeout( - timeout, strict=msg["strict"] + timeout, variables, strict=msg["strict"] ) except TemplateError as ex: connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR, str(ex)) diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index 58c9b414d5a16b..8304b093a148ba 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -706,6 +706,38 @@ async def test_render_template_renders_template(hass, websocket_client): } +async def test_render_template_with_timeout_and_variables(hass, websocket_client): + """Test a template with a timeout and variables renders without error.""" + await websocket_client.send_json( + { + "id": 5, + "type": "render_template", + "timeout": 10, + "variables": {"test": {"value": "hello"}}, + "template": "{{ test.value }}", + } + ) + + msg = await websocket_client.receive_json() + assert msg["id"] == 5 + assert msg["type"] == const.TYPE_RESULT + assert msg["success"] + + msg = await websocket_client.receive_json() + assert msg["id"] == 5 + assert msg["type"] == "event" + event = msg["event"] + assert event == { + "result": "hello", + "listeners": { + "all": False, + "domains": [], + "entities": [], + "time": False, + }, + } + + async def test_render_template_manual_entity_ids_no_longer_needed( hass, websocket_client ): From c6114f26317c8de5c59755f5f8f460c06a1422e9 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 21 Feb 2022 10:01:04 -0800 Subject: [PATCH 0886/1098] Simplify nest placeholder image loading and share across all cameras (#66580) --- homeassistant/components/nest/camera_sdm.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/nest/camera_sdm.py b/homeassistant/components/nest/camera_sdm.py index d7a9ed299487f0..0def2e493c2f90 100644 --- a/homeassistant/components/nest/camera_sdm.py +++ b/homeassistant/components/nest/camera_sdm.py @@ -4,6 +4,7 @@ import asyncio from collections.abc import Callable import datetime +import functools import logging from pathlib import Path @@ -72,7 +73,6 @@ def __init__(self, device: Device) -> None: self._create_stream_url_lock = asyncio.Lock() self._stream_refresh_unsub: Callable[[], None] | None = None self._attr_is_streaming = CameraLiveStreamTrait.NAME in self._device.traits - self._placeholder_image: bytes | None = None @property def should_poll(self) -> bool: @@ -217,11 +217,13 @@ async def async_camera_image( stream = await self.async_create_stream() if stream: return await stream.async_get_image(width, height) - if not self._placeholder_image: - self._placeholder_image = await self.hass.async_add_executor_job( - PLACEHOLDER.read_bytes - ) - return self._placeholder_image + return await self.hass.async_add_executor_job(self.placeholder_image) + + @classmethod + @functools.cache + def placeholder_image(cls) -> bytes: + """Return placeholder image to use when no stream is available.""" + return PLACEHOLDER.read_bytes() async def async_handle_web_rtc_offer(self, offer_sdp: str) -> str | None: """Return the source of the stream.""" From 16cc2b790b7cc84f6dbe97d85e708c5c2da545bb Mon Sep 17 00:00:00 2001 From: Teemu R Date: Mon, 21 Feb 2022 19:02:11 +0100 Subject: [PATCH 0887/1098] Create LED switches for tplink dimmers (#66839) --- homeassistant/components/tplink/switch.py | 4 +- tests/components/tplink/__init__.py | 11 ++-- tests/components/tplink/test_switch.py | 63 ++++++++--------------- 3 files changed, 29 insertions(+), 49 deletions(-) diff --git a/homeassistant/components/tplink/switch.py b/homeassistant/components/tplink/switch.py index 823d37267d6329..451ec6d5f8b05c 100644 --- a/homeassistant/components/tplink/switch.py +++ b/homeassistant/components/tplink/switch.py @@ -28,7 +28,7 @@ async def async_setup_entry( """Set up switches.""" coordinator: TPLinkDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] device = cast(SmartPlug, coordinator.device) - if not device.is_plug and not device.is_strip: + if not device.is_plug and not device.is_strip and not device.is_dimmer: return entities: list = [] if device.is_strip: @@ -36,7 +36,7 @@ async def async_setup_entry( _LOGGER.debug("Initializing strip with %s sockets", len(device.children)) for child in device.children: entities.append(SmartPlugSwitch(child, coordinator)) - else: + elif device.is_plug: entities.append(SmartPlugSwitch(device, coordinator)) entities.append(SmartPlugLedSwitch(device, coordinator)) diff --git a/tests/components/tplink/__init__.py b/tests/components/tplink/__init__.py index beeaa21bf27540..f9422d60669cfa 100644 --- a/tests/components/tplink/__init__.py +++ b/tests/components/tplink/__init__.py @@ -28,7 +28,7 @@ def _mock_protocol() -> TPLinkSmartHomeProtocol: def _mocked_bulb() -> SmartBulb: - bulb = MagicMock(auto_spec=SmartBulb) + bulb = MagicMock(auto_spec=SmartBulb, name="Mocked bulb") bulb.update = AsyncMock() bulb.mac = MAC_ADDRESS bulb.alias = ALIAS @@ -55,10 +55,10 @@ def _mocked_bulb() -> SmartBulb: def _mocked_dimmer() -> SmartDimmer: - dimmer = MagicMock(auto_spec=SmartDimmer) + dimmer = MagicMock(auto_spec=SmartDimmer, name="Mocked dimmer") dimmer.update = AsyncMock() dimmer.mac = MAC_ADDRESS - dimmer.alias = ALIAS + dimmer.alias = "My Dimmer" dimmer.model = MODEL dimmer.host = IP_ADDRESS dimmer.brightness = 50 @@ -77,12 +77,13 @@ def _mocked_dimmer() -> SmartDimmer: dimmer.set_brightness = AsyncMock() dimmer.set_hsv = AsyncMock() dimmer.set_color_temp = AsyncMock() + dimmer.set_led = AsyncMock() dimmer.protocol = _mock_protocol() return dimmer def _mocked_plug() -> SmartPlug: - plug = MagicMock(auto_spec=SmartPlug) + plug = MagicMock(auto_spec=SmartPlug, name="Mocked plug") plug.update = AsyncMock() plug.mac = MAC_ADDRESS plug.alias = "My Plug" @@ -103,7 +104,7 @@ def _mocked_plug() -> SmartPlug: def _mocked_strip() -> SmartStrip: - strip = MagicMock(auto_spec=SmartStrip) + strip = MagicMock(auto_spec=SmartStrip, name="Mocked strip") strip.update = AsyncMock() strip.mac = MAC_ADDRESS strip.alias = "My Strip" diff --git a/tests/components/tplink/test_switch.py b/tests/components/tplink/test_switch.py index 03dc98f97991c4..cafdcc6a54ff05 100644 --- a/tests/components/tplink/test_switch.py +++ b/tests/components/tplink/test_switch.py @@ -4,6 +4,7 @@ from unittest.mock import AsyncMock from kasa import SmartDeviceException +import pytest from homeassistant.components import tplink from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN @@ -12,10 +13,11 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component -from homeassistant.util import dt as dt_util +from homeassistant.util import dt as dt_util, slugify from . import ( MAC_ADDRESS, + _mocked_dimmer, _mocked_plug, _mocked_strip, _patch_discovery, @@ -53,36 +55,42 @@ async def test_plug(hass: HomeAssistant) -> None: plug.turn_on.reset_mock() -async def test_plug_led(hass: HomeAssistant) -> None: - """Test a smart plug LED.""" +@pytest.mark.parametrize( + "dev, domain", + [ + (_mocked_plug(), "switch"), + (_mocked_strip(), "switch"), + (_mocked_dimmer(), "light"), + ], +) +async def test_led_switch(hass: HomeAssistant, dev, domain: str) -> None: + """Test LED setting for plugs, strips and dimmers.""" already_migrated_config_entry = MockConfigEntry( domain=DOMAIN, data={}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) - plug = _mocked_plug() - with _patch_discovery(device=plug), _patch_single_discovery(device=plug): + with _patch_discovery(device=dev), _patch_single_discovery(device=dev): await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}}) await hass.async_block_till_done() - entity_id = "switch.my_plug" - state = hass.states.get(entity_id) + entity_name = slugify(dev.alias) - led_entity_id = f"{entity_id}_led" + led_entity_id = f"switch.{entity_name}_led" led_state = hass.states.get(led_entity_id) assert led_state.state == STATE_ON - assert led_state.name == f"{state.name} LED" + assert led_state.name == f"{dev.alias} LED" await hass.services.async_call( SWITCH_DOMAIN, "turn_off", {ATTR_ENTITY_ID: led_entity_id}, blocking=True ) - plug.set_led.assert_called_once_with(False) - plug.set_led.reset_mock() + dev.set_led.assert_called_once_with(False) + dev.set_led.reset_mock() await hass.services.async_call( SWITCH_DOMAIN, "turn_on", {ATTR_ENTITY_ID: led_entity_id}, blocking=True ) - plug.set_led.assert_called_once_with(True) - plug.set_led.reset_mock() + dev.set_led.assert_called_once_with(True) + dev.set_led.reset_mock() async def test_plug_unique_id(hass: HomeAssistant) -> None: @@ -156,35 +164,6 @@ async def test_strip(hass: HomeAssistant) -> None: strip.children[plug_id].turn_on.reset_mock() -async def test_strip_led(hass: HomeAssistant) -> None: - """Test a smart strip LED.""" - already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS - ) - already_migrated_config_entry.add_to_hass(hass) - strip = _mocked_strip() - with _patch_discovery(device=strip), _patch_single_discovery(device=strip): - await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}}) - await hass.async_block_till_done() - - # We should have a LED entity for the strip - led_entity_id = "switch.my_strip_led" - led_state = hass.states.get(led_entity_id) - assert led_state.state == STATE_ON - - await hass.services.async_call( - SWITCH_DOMAIN, "turn_off", {ATTR_ENTITY_ID: led_entity_id}, blocking=True - ) - strip.set_led.assert_called_once_with(False) - strip.set_led.reset_mock() - - await hass.services.async_call( - SWITCH_DOMAIN, "turn_on", {ATTR_ENTITY_ID: led_entity_id}, blocking=True - ) - strip.set_led.assert_called_once_with(True) - strip.set_led.reset_mock() - - async def test_strip_unique_ids(hass: HomeAssistant) -> None: """Test a strip unique id.""" already_migrated_config_entry = MockConfigEntry( From f2f2a08966101b44a7e4c1c821779e53f9af8f72 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 21 Feb 2022 08:08:09 -1000 Subject: [PATCH 0888/1098] Add support for auto target fan state in HomeKit fans (#66383) --- homeassistant/components/homekit/const.py | 1 + homeassistant/components/homekit/type_fans.py | 62 +++++++++++--- tests/components/homekit/test_type_fans.py | 85 ++++++++++++++++++- 3 files changed, 132 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index 7eca4ae4c661e3..ed7b3d6b293da2 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -215,6 +215,7 @@ CHAR_TARGET_DOOR_STATE = "TargetDoorState" CHAR_TARGET_HEATING_COOLING = "TargetHeatingCoolingState" CHAR_TARGET_POSITION = "TargetPosition" +CHAR_TARGET_FAN_STATE = "TargetFanState" CHAR_TARGET_HUMIDIFIER_DEHUMIDIFIER = "TargetHumidifierDehumidifierState" CHAR_TARGET_HUMIDITY = "TargetRelativeHumidity" CHAR_TARGET_SECURITY_STATE = "SecuritySystemTargetState" diff --git a/homeassistant/components/homekit/type_fans.py b/homeassistant/components/homekit/type_fans.py index 54d6424e8d5dce..82d254cb9a53e2 100644 --- a/homeassistant/components/homekit/type_fans.py +++ b/homeassistant/components/homekit/type_fans.py @@ -39,6 +39,7 @@ CHAR_ROTATION_DIRECTION, CHAR_ROTATION_SPEED, CHAR_SWING_MODE, + CHAR_TARGET_FAN_STATE, PROP_MIN_STEP, SERV_FANV2, SERV_SWITCH, @@ -58,35 +59,38 @@ class Fan(HomeAccessory): def __init__(self, *args): """Initialize a new Fan accessory object.""" super().__init__(*args, category=CATEGORY_FAN) - chars = [] + self.chars = [] state = self.hass.states.get(self.entity_id) features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) percentage_step = state.attributes.get(ATTR_PERCENTAGE_STEP, 1) - preset_modes = state.attributes.get(ATTR_PRESET_MODES) + self.preset_modes = state.attributes.get(ATTR_PRESET_MODES) if features & SUPPORT_DIRECTION: - chars.append(CHAR_ROTATION_DIRECTION) + self.chars.append(CHAR_ROTATION_DIRECTION) if features & SUPPORT_OSCILLATE: - chars.append(CHAR_SWING_MODE) + self.chars.append(CHAR_SWING_MODE) if features & SUPPORT_SET_SPEED: - chars.append(CHAR_ROTATION_SPEED) + self.chars.append(CHAR_ROTATION_SPEED) + if self.preset_modes and len(self.preset_modes) == 1: + self.chars.append(CHAR_TARGET_FAN_STATE) - serv_fan = self.add_preload_service(SERV_FANV2, chars) + serv_fan = self.add_preload_service(SERV_FANV2, self.chars) self.set_primary_service(serv_fan) self.char_active = serv_fan.configure_char(CHAR_ACTIVE, value=0) self.char_direction = None self.char_speed = None self.char_swing = None + self.char_target_fan_state = None self.preset_mode_chars = {} - if CHAR_ROTATION_DIRECTION in chars: + if CHAR_ROTATION_DIRECTION in self.chars: self.char_direction = serv_fan.configure_char( CHAR_ROTATION_DIRECTION, value=0 ) - if CHAR_ROTATION_SPEED in chars: + if CHAR_ROTATION_SPEED in self.chars: # Initial value is set to 100 because 0 is a special value (off). 100 is # an arbitrary non-zero value. It is updated immediately by async_update_state # to set to the correct initial value. @@ -96,8 +100,13 @@ def __init__(self, *args): properties={PROP_MIN_STEP: percentage_step}, ) - if preset_modes: - for preset_mode in preset_modes: + if self.preset_modes and len(self.preset_modes) == 1: + self.char_target_fan_state = serv_fan.configure_char( + CHAR_TARGET_FAN_STATE, + value=0, + ) + elif self.preset_modes: + for preset_mode in self.preset_modes: preset_serv = self.add_preload_service(SERV_SWITCH, CHAR_NAME) serv_fan.add_linked_service(preset_serv) preset_serv.configure_char( @@ -115,7 +124,7 @@ def __init__(self, *args): ), ) - if CHAR_SWING_MODE in chars: + if CHAR_SWING_MODE in self.chars: self.char_swing = serv_fan.configure_char(CHAR_SWING_MODE, value=0) self.async_update_state(state) serv_fan.setter_callback = self._set_chars @@ -148,6 +157,24 @@ def _set_chars(self, char_values): # get the speed they asked for if CHAR_ROTATION_SPEED in char_values: self.set_percentage(char_values[CHAR_ROTATION_SPEED]) + if CHAR_TARGET_FAN_STATE in char_values: + self.set_single_preset_mode(char_values[CHAR_TARGET_FAN_STATE]) + + def set_single_preset_mode(self, value): + """Set auto call came from HomeKit.""" + params = {ATTR_ENTITY_ID: self.entity_id} + if value: + _LOGGER.debug( + "%s: Set auto to 1 (%s)", self.entity_id, self.preset_modes[0] + ) + params[ATTR_PRESET_MODE] = self.preset_modes[0] + self.async_call_service(DOMAIN, SERVICE_SET_PRESET_MODE, params) + else: + current_state = self.hass.states.get(self.entity_id) + percentage = current_state.attributes.get(ATTR_PERCENTAGE) or 50 + params[ATTR_PERCENTAGE] = percentage + _LOGGER.debug("%s: Set auto to 0", self.entity_id) + self.async_call_service(DOMAIN, SERVICE_TURN_ON, params) def set_preset_mode(self, value, preset_mode): """Set preset_mode if call came from HomeKit.""" @@ -193,6 +220,7 @@ def async_update_state(self, new_state): """Update fan after state change.""" # Handle State state = new_state.state + attributes = new_state.attributes if state in (STATE_ON, STATE_OFF): self._state = 1 if state == STATE_ON else 0 self.char_active.set_value(self._state) @@ -208,7 +236,7 @@ def async_update_state(self, new_state): if self.char_speed is not None and state != STATE_OFF: # We do not change the homekit speed when turning off # as it will clear the restore state - percentage = new_state.attributes.get(ATTR_PERCENTAGE) + percentage = attributes.get(ATTR_PERCENTAGE) # If the homeassistant component reports its speed as the first entry # in its speed list but is not off, the hk_speed_value is 0. But 0 # is a special value in homekit. When you turn on a homekit accessory @@ -227,12 +255,18 @@ def async_update_state(self, new_state): # Handle Oscillating if self.char_swing is not None: - oscillating = new_state.attributes.get(ATTR_OSCILLATING) + oscillating = attributes.get(ATTR_OSCILLATING) if isinstance(oscillating, bool): hk_oscillating = 1 if oscillating else 0 self.char_swing.set_value(hk_oscillating) - current_preset_mode = new_state.attributes.get(ATTR_PRESET_MODE) + current_preset_mode = attributes.get(ATTR_PRESET_MODE) + if self.char_target_fan_state is not None: + # Handle single preset mode + self.char_target_fan_state.set_value(int(current_preset_mode is not None)) + return + + # Handle multiple preset modes for preset_mode, char in self.preset_mode_chars.items(): hk_value = 1 if preset_mode == current_preset_mode else 0 char.set_value(hk_value) diff --git a/tests/components/homekit/test_type_fans.py b/tests/components/homekit/test_type_fans.py index c1ce1ffaddba34..b520eb7f874486 100644 --- a/tests/components/homekit/test_type_fans.py +++ b/tests/components/homekit/test_type_fans.py @@ -567,8 +567,8 @@ async def test_fan_restore(hass, hk_driver, events): assert acc.char_swing is not None -async def test_fan_preset_modes(hass, hk_driver, events): - """Test fan with direction.""" +async def test_fan_multiple_preset_modes(hass, hk_driver, events): + """Test fan with multiple preset modes.""" entity_id = "fan.demo" hass.states.async_set( @@ -645,3 +645,84 @@ async def test_fan_preset_modes(hass, hk_driver, events): assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id assert events[-1].data["service"] == "turn_on" assert len(events) == 2 + + +async def test_fan_single_preset_mode(hass, hk_driver, events): + """Test fan with a single preset mode.""" + entity_id = "fan.demo" + + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_PRESET_MODE | SUPPORT_SET_SPEED, + ATTR_PERCENTAGE: 42, + ATTR_PRESET_MODE: "smart", + ATTR_PRESET_MODES: ["smart"], + }, + ) + await hass.async_block_till_done() + acc = Fan(hass, hk_driver, "Fan", entity_id, 1, None) + hk_driver.add_accessory(acc) + + assert acc.char_target_fan_state.value == 1 + + await acc.run() + await hass.async_block_till_done() + + # Set from HomeKit + call_set_preset_mode = async_mock_service(hass, DOMAIN, "set_preset_mode") + call_turn_on = async_mock_service(hass, DOMAIN, "turn_on") + + char_target_fan_state_iid = acc.char_target_fan_state.to_HAP()[HAP_REPR_IID] + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_target_fan_state_iid, + HAP_REPR_VALUE: 0, + }, + ] + }, + "mock_addr", + ) + await hass.async_block_till_done() + assert call_turn_on[0] + assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id + assert call_turn_on[0].data[ATTR_PERCENTAGE] == 42 + assert len(events) == 1 + assert events[-1].data["service"] == "turn_on" + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_target_fan_state_iid, + HAP_REPR_VALUE: 1, + }, + ] + }, + "mock_addr", + ) + await hass.async_block_till_done() + assert call_set_preset_mode[0] + assert call_set_preset_mode[0].data[ATTR_ENTITY_ID] == entity_id + assert call_set_preset_mode[0].data[ATTR_PRESET_MODE] == "smart" + assert events[-1].data["service"] == "set_preset_mode" + assert len(events) == 2 + + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_PRESET_MODE | SUPPORT_SET_SPEED, + ATTR_PERCENTAGE: 42, + ATTR_PRESET_MODE: None, + ATTR_PRESET_MODES: ["smart"], + }, + ) + await hass.async_block_till_done() + assert acc.char_target_fan_state.value == 0 From 7947866962358296d9b69a08014ce46ee2ab1f30 Mon Sep 17 00:00:00 2001 From: Robert Hillis Date: Mon, 21 Feb 2022 13:08:19 -0500 Subject: [PATCH 0889/1098] Refactor tests for modem_callerid (#59691) * Refactor tests for modem_callerid * uno mas * uno mas * uno mas --- tests/components/modem_callerid/__init__.py | 31 ++++++---- .../modem_callerid/test_config_flow.py | 59 +++++++------------ tests/components/modem_callerid/test_init.py | 32 +++++----- 3 files changed, 55 insertions(+), 67 deletions(-) diff --git a/tests/components/modem_callerid/__init__.py b/tests/components/modem_callerid/__init__.py index 9564f2b662fce9..419f5bc50ff9ab 100644 --- a/tests/components/modem_callerid/__init__.py +++ b/tests/components/modem_callerid/__init__.py @@ -3,24 +3,29 @@ from unittest.mock import patch from phone_modem import DEFAULT_PORT +from serial.tools.list_ports_common import ListPortInfo -from homeassistant.const import CONF_DEVICE -CONF_DATA = {CONF_DEVICE: DEFAULT_PORT} - -IMPORT_DATA = {"sensor": {"platform": "modem_callerid"}} - - -def _patch_init_modem(): +def patch_init_modem(): + """Mock modem.""" return patch( - "homeassistant.components.modem_callerid.PhoneModem", - autospec=True, + "homeassistant.components.modem_callerid.PhoneModem.initialize", ) -def _patch_config_flow_modem(mocked_modem): +def patch_config_flow_modem(): + """Mock modem config flow.""" return patch( - "homeassistant.components.modem_callerid.config_flow.PhoneModem", - autospec=True, - return_value=mocked_modem, + "homeassistant.components.modem_callerid.config_flow.PhoneModem.test", ) + + +def com_port(): + """Mock of a serial port.""" + port = ListPortInfo(DEFAULT_PORT) + port.serial_number = "1234" + port.manufacturer = "Virtual serial port" + port.device = DEFAULT_PORT + port.description = "Some serial port" + + return port diff --git a/tests/components/modem_callerid/test_config_flow.py b/tests/components/modem_callerid/test_config_flow.py index 0956a8fe1b7953..19a98106c63ed5 100644 --- a/tests/components/modem_callerid/test_config_flow.py +++ b/tests/components/modem_callerid/test_config_flow.py @@ -1,21 +1,16 @@ """Test Modem Caller ID config flow.""" -from unittest.mock import AsyncMock, MagicMock, patch +from unittest.mock import MagicMock, patch import phone_modem -import serial.tools.list_ports +from homeassistant import data_entry_flow from homeassistant.components import usb from homeassistant.components.modem_callerid.const import DOMAIN from homeassistant.config_entries import SOURCE_USB, SOURCE_USER from homeassistant.const import CONF_DEVICE, CONF_SOURCE from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import ( - RESULT_TYPE_ABORT, - RESULT_TYPE_CREATE_ENTRY, - RESULT_TYPE_FORM, -) -from . import _patch_config_flow_modem +from . import com_port, patch_config_flow_modem DISCOVERY_INFO = usb.UsbServiceInfo( device=phone_modem.DEFAULT_PORT, @@ -30,51 +25,38 @@ def _patch_setup(): return patch( "homeassistant.components.modem_callerid.async_setup_entry", - return_value=True, ) -def com_port(): - """Mock of a serial port.""" - port = serial.tools.list_ports_common.ListPortInfo(phone_modem.DEFAULT_PORT) - port.serial_number = "1234" - port.manufacturer = "Virtual serial port" - port.device = phone_modem.DEFAULT_PORT - port.description = "Some serial port" - - return port - - @patch("serial.tools.list_ports.comports", MagicMock(return_value=[com_port()])) async def test_flow_usb(hass: HomeAssistant): """Test usb discovery flow.""" - port = com_port() - with _patch_config_flow_modem(AsyncMock()), _patch_setup(): + with patch_config_flow_modem(), _patch_setup(): result = await hass.config_entries.flow.async_init( DOMAIN, context={CONF_SOURCE: SOURCE_USB}, data=DISCOVERY_INFO, ) - assert result["type"] == RESULT_TYPE_FORM + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "usb_confirm" result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_DEVICE: phone_modem.DEFAULT_PORT}, ) - assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["data"] == {CONF_DEVICE: port.device} + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"] == {CONF_DEVICE: com_port().device} @patch("serial.tools.list_ports.comports", MagicMock(return_value=[com_port()])) async def test_flow_usb_cannot_connect(hass: HomeAssistant): """Test usb flow connection error.""" - with _patch_config_flow_modem(AsyncMock()) as modemmock: + with patch_config_flow_modem() as modemmock: modemmock.side_effect = phone_modem.exceptions.SerialError result = await hass.config_entries.flow.async_init( DOMAIN, context={CONF_SOURCE: SOURCE_USB}, data=DISCOVERY_INFO ) - assert result["type"] == RESULT_TYPE_ABORT + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "cannot_connect" @@ -90,14 +72,13 @@ async def test_flow_user(hass: HomeAssistant): port.vid, port.pid, ) - mocked_modem = AsyncMock() - with _patch_config_flow_modem(mocked_modem), _patch_setup(): + with patch_config_flow_modem(), _patch_setup(): result = await hass.config_entries.flow.async_init( DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data={CONF_DEVICE: port_select}, ) - assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["data"] == {CONF_DEVICE: port.device} result = await hass.config_entries.flow.async_init( @@ -105,7 +86,7 @@ async def test_flow_user(hass: HomeAssistant): context={CONF_SOURCE: SOURCE_USER}, data={CONF_DEVICE: port_select}, ) - assert result["type"] == RESULT_TYPE_ABORT + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "no_devices_found" @@ -121,12 +102,12 @@ async def test_flow_user_error(hass: HomeAssistant): port.vid, port.pid, ) - with _patch_config_flow_modem(AsyncMock()) as modemmock: + with patch_config_flow_modem() as modemmock: modemmock.side_effect = phone_modem.exceptions.SerialError result = await hass.config_entries.flow.async_init( DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data={CONF_DEVICE: port_select} ) - assert result["type"] == RESULT_TYPE_FORM + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "user" assert result["errors"] == {"base": "cannot_connect"} @@ -135,32 +116,32 @@ async def test_flow_user_error(hass: HomeAssistant): result["flow_id"], user_input={CONF_DEVICE: port_select}, ) - assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["data"] == {CONF_DEVICE: port.device} @patch("serial.tools.list_ports.comports", MagicMock()) async def test_flow_user_no_port_list(hass: HomeAssistant): """Test user with no list of ports.""" - with _patch_config_flow_modem(AsyncMock()): + with patch_config_flow_modem(): result = await hass.config_entries.flow.async_init( DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data={CONF_DEVICE: phone_modem.DEFAULT_PORT}, ) - assert result["type"] == RESULT_TYPE_ABORT + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "no_devices_found" async def test_abort_user_with_existing_flow(hass: HomeAssistant): """Test user flow is aborted when another discovery has happened.""" - with _patch_config_flow_modem(AsyncMock()): + with patch_config_flow_modem(): result = await hass.config_entries.flow.async_init( DOMAIN, context={CONF_SOURCE: SOURCE_USB}, data=DISCOVERY_INFO, ) - assert result["type"] == RESULT_TYPE_FORM + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "usb_confirm" result2 = await hass.config_entries.flow.async_init( @@ -169,5 +150,5 @@ async def test_abort_user_with_existing_flow(hass: HomeAssistant): data={}, ) - assert result2["type"] == RESULT_TYPE_ABORT + assert result2["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result2["reason"] == "already_in_progress" diff --git a/tests/components/modem_callerid/test_init.py b/tests/components/modem_callerid/test_init.py index f467ca5af514ba..0465fb24a07522 100644 --- a/tests/components/modem_callerid/test_init.py +++ b/tests/components/modem_callerid/test_init.py @@ -1,25 +1,29 @@ """Test Modem Caller ID integration.""" -from unittest.mock import patch +from unittest.mock import AsyncMock, patch from phone_modem import exceptions from homeassistant.components.modem_callerid.const import DOMAIN from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import CONF_DEVICE from homeassistant.core import HomeAssistant -from . import CONF_DATA, _patch_init_modem +from . import com_port, patch_init_modem from tests.common import MockConfigEntry -async def test_setup_config(hass: HomeAssistant): - """Test Modem Caller ID setup.""" +async def test_setup_entry(hass: HomeAssistant): + """Test Modem Caller ID entry setup.""" entry = MockConfigEntry( domain=DOMAIN, - data=CONF_DATA, + data={CONF_DEVICE: com_port().device}, ) entry.add_to_hass(hass) - with _patch_init_modem(): + with patch("aioserial.AioSerial", return_value=AsyncMock()), patch( + "homeassistant.components.modem_callerid.PhoneModem._get_response", + return_value="OK", + ), patch("phone_modem.PhoneModem._modem_sm"): await hass.config_entries.async_setup(entry.entry_id) assert entry.state == ConfigEntryState.LOADED @@ -28,28 +32,26 @@ async def test_async_setup_entry_not_ready(hass: HomeAssistant): """Test that it throws ConfigEntryNotReady when exception occurs during setup.""" entry = MockConfigEntry( domain=DOMAIN, - data=CONF_DATA, + data={CONF_DEVICE: com_port().device}, ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.modem_callerid.PhoneModem", - side_effect=exceptions.SerialError(), - ): + with patch_init_modem() as modemmock: + modemmock.side_effect = exceptions.SerialError await hass.config_entries.async_setup(entry.entry_id) assert len(hass.config_entries.async_entries(DOMAIN)) == 1 - assert entry.state == ConfigEntryState.SETUP_ERROR + assert entry.state == ConfigEntryState.SETUP_RETRY assert not hass.data.get(DOMAIN) -async def test_unload_config_entry(hass: HomeAssistant): +async def test_unload_entry(hass: HomeAssistant): """Test unload.""" entry = MockConfigEntry( domain=DOMAIN, - data=CONF_DATA, + data={CONF_DEVICE: com_port().device}, ) entry.add_to_hass(hass) - with _patch_init_modem(): + with patch_init_modem(): await hass.config_entries.async_setup(entry.entry_id) assert len(hass.config_entries.async_entries(DOMAIN)) == 1 assert entry.state is ConfigEntryState.LOADED From cd38878a4c167899f03c3081ce97ebd446e1fa0e Mon Sep 17 00:00:00 2001 From: javicalle <31999997+javicalle@users.noreply.github.com> Date: Mon, 21 Feb 2022 19:11:05 +0100 Subject: [PATCH 0890/1098] Restore states for RFLink binary_sensors (#65716) --- .../components/rflink/binary_sensor.py | 13 +++++- tests/components/rflink/test_binary_sensor.py | 45 +++++++++++++++---- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/rflink/binary_sensor.py b/homeassistant/components/rflink/binary_sensor.py index 9b6b7c6b6c60f3..ce723e84b8cc32 100644 --- a/homeassistant/components/rflink/binary_sensor.py +++ b/homeassistant/components/rflink/binary_sensor.py @@ -13,11 +13,13 @@ CONF_DEVICES, CONF_FORCE_UPDATE, CONF_NAME, + STATE_ON, ) from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback import homeassistant.helpers.event as evt +from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from . import CONF_ALIASES, RflinkDevice @@ -67,7 +69,7 @@ async def async_setup_platform( async_add_entities(devices_from_config(config)) -class RflinkBinarySensor(RflinkDevice, BinarySensorEntity): +class RflinkBinarySensor(RflinkDevice, BinarySensorEntity, RestoreEntity): """Representation of an Rflink binary sensor.""" def __init__( @@ -81,6 +83,15 @@ def __init__( self._delay_listener = None super().__init__(device_id, **kwargs) + async def async_added_to_hass(self): + """Restore RFLink BinarySensor state.""" + await super().async_added_to_hass() + if (old_state := await self.async_get_last_state()) is not None: + if self._off_delay is None: + self._state = old_state.state == STATE_ON + else: + self._state = False + def _handle_event(self, event): """Domain specific event handler.""" command = event["command"] diff --git a/tests/components/rflink/test_binary_sensor.py b/tests/components/rflink/test_binary_sensor.py index 4b6acfb43946f0..00873e9d176668 100644 --- a/tests/components/rflink/test_binary_sensor.py +++ b/tests/components/rflink/test_binary_sensor.py @@ -15,10 +15,10 @@ STATE_UNAVAILABLE, STATE_UNKNOWN, ) -import homeassistant.core as ha +from homeassistant.core import CoreState, State, callback import homeassistant.util.dt as dt_util -from tests.common import async_fire_time_changed +from tests.common import async_fire_time_changed, mock_restore_cache from tests.components.rflink.test_init import mock_rflink DOMAIN = "binary_sensor" @@ -91,13 +91,19 @@ async def test_entity_availability(hass, monkeypatch): config[CONF_RECONNECT_INTERVAL] = 60 # Create platform and entities - _, _, _, disconnect_callback = await mock_rflink( + event_callback, _, _, disconnect_callback = await mock_rflink( hass, config, DOMAIN, monkeypatch, failures=failures ) - # Entities are available by default + # Entities are unknown by default assert hass.states.get("binary_sensor.test").state == STATE_UNKNOWN + # test binary_sensor status change + event_callback({"id": "test", "command": "on"}) + await hass.async_block_till_done() + + assert hass.states.get("binary_sensor.test").state == STATE_ON + # Mock a disconnect of the Rflink device disconnect_callback() @@ -113,8 +119,8 @@ async def test_entity_availability(hass, monkeypatch): # Wait for dispatch events to propagate await hass.async_block_till_done() - # Entities should be available again - assert hass.states.get("binary_sensor.test").state == STATE_UNKNOWN + # Entities should restore its status + assert hass.states.get("binary_sensor.test").state == STATE_ON async def test_off_delay(hass, legacy_patchable_time, monkeypatch): @@ -129,12 +135,12 @@ async def test_off_delay(hass, legacy_patchable_time, monkeypatch): on_event = {"id": "test2", "command": "on"} - @ha.callback - def callback(event): + @callback + def listener(event): """Verify event got called.""" events.append(event) - hass.bus.async_listen(EVENT_STATE_CHANGED, callback) + hass.bus.async_listen(EVENT_STATE_CHANGED, listener) now = dt_util.utcnow() # fake time and turn on sensor @@ -178,3 +184,24 @@ def callback(event): state = hass.states.get("binary_sensor.test2") assert state.state == STATE_OFF assert len(events) == 3 + + +async def test_restore_state(hass, monkeypatch): + """Ensure states are restored on startup.""" + mock_restore_cache( + hass, (State(f"{DOMAIN}.test", STATE_ON), State(f"{DOMAIN}.test2", STATE_ON)) + ) + + hass.state = CoreState.starting + + # setup mocking rflink module + _, _, _, _ = await mock_rflink(hass, CONFIG, DOMAIN, monkeypatch) + + state = hass.states.get(f"{DOMAIN}.test") + assert state + assert state.state == STATE_ON + + # off_delay config must restore to off + state = hass.states.get(f"{DOMAIN}.test2") + assert state + assert state.state == STATE_OFF From 14a7ee5d0bca935a9d260bb9ecf773326cce0bff Mon Sep 17 00:00:00 2001 From: Robert Hillis Date: Mon, 21 Feb 2022 13:12:09 -0500 Subject: [PATCH 0891/1098] Deprecate "wanted" sensor in radarr (#63818) * Remove invalid "wanted" sensor from radarr * uno mas --- homeassistant/components/radarr/sensor.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/homeassistant/components/radarr/sensor.py b/homeassistant/components/radarr/sensor.py index 0244318035818f..d190a2e96eac62 100644 --- a/homeassistant/components/radarr/sensor.py +++ b/homeassistant/components/radarr/sensor.py @@ -138,10 +138,16 @@ def setup_platform( ) -> None: """Set up the Radarr platform.""" conditions = config[CONF_MONITORED_CONDITIONS] + # deprecated in 2022.3 + if "wanted" in conditions: + _LOGGER.warning( + "Wanted is not a valid condition option. Please remove it from your config" + ) entities = [ RadarrSensor(hass, config, description) for description in SENSOR_TYPES if description.key in conditions + if description.key != "wanted" ] add_entities(entities, True) From abaf284ef2e3fb6f1734fd61383d8fadbb9a89ae Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Mon, 21 Feb 2022 18:14:23 +0000 Subject: [PATCH 0892/1098] Cast string back to datetime in Sensor Filter (#65396) --- homeassistant/components/filter/sensor.py | 6 ++++- tests/components/filter/test_sensor.py | 31 +++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/filter/sensor.py b/homeassistant/components/filter/sensor.py index 8a75615e617e70..46d142fc9625e6 100644 --- a/homeassistant/components/filter/sensor.py +++ b/homeassistant/components/filter/sensor.py @@ -3,7 +3,7 @@ from collections import Counter, deque from copy import copy -from datetime import timedelta +from datetime import datetime, timedelta from functools import partial import logging from numbers import Number @@ -19,6 +19,7 @@ DEVICE_CLASSES as SENSOR_DEVICE_CLASSES, DOMAIN as SENSOR_DOMAIN, PLATFORM_SCHEMA, + SensorDeviceClass, SensorEntity, ) from homeassistant.const import ( @@ -346,6 +347,9 @@ def name(self): @property def native_value(self): """Return the state of the sensor.""" + if self._device_class == SensorDeviceClass.TIMESTAMP: + return datetime.fromisoformat(self._state) + return self._state @property diff --git a/tests/components/filter/test_sensor.py b/tests/components/filter/test_sensor.py index c2fc8cbdd06b2d..b42fc3fa9fe08d 100644 --- a/tests/components/filter/test_sensor.py +++ b/tests/components/filter/test_sensor.py @@ -318,6 +318,37 @@ async def test_invalid_state(hass): assert state.state == STATE_UNAVAILABLE +async def test_timestamp_state(hass): + """Test if filter state is a datetime.""" + config = { + "sensor": { + "platform": "filter", + "name": "test", + "entity_id": "sensor.test_monitored", + "filters": [ + {"filter": "time_throttle", "window_size": "00:02"}, + ], + } + } + + await async_init_recorder_component(hass) + + with assert_setup_component(1, "sensor"): + assert await async_setup_component(hass, "sensor", config) + await hass.async_block_till_done() + + hass.states.async_set( + "sensor.test_monitored", + "2022-02-01T23:04:05+00:00", + {ATTR_DEVICE_CLASS: SensorDeviceClass.TIMESTAMP}, + ) + await hass.async_block_till_done() + + state = hass.states.get("sensor.test") + assert state.state == "2022-02-01T23:04:05+00:00" + assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TIMESTAMP + + async def test_outlier(values): """Test if outlier filter works.""" filt = OutlierFilter(window_size=3, precision=2, entity=None, radius=4.0) From 8080aab98e415f598a42e30583e9ab06aaba8c13 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 21 Feb 2022 10:14:42 -0800 Subject: [PATCH 0893/1098] Allow deleting files from media source (#66975) --- .../components/media_source/local_source.py | 56 ++++++++- .../media_source/test_local_source.py | 109 +++++++++++++++++- 2 files changed, 161 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/media_source/local_source.py b/homeassistant/components/media_source/local_source.py index d5e1671c135eb3..76598eac963d65 100644 --- a/homeassistant/components/media_source/local_source.py +++ b/homeassistant/components/media_source/local_source.py @@ -10,7 +10,7 @@ from aiohttp.web_request import FileField import voluptuous as vol -from homeassistant.components.http import HomeAssistantView +from homeassistant.components import http, websocket_api from homeassistant.components.media_player.const import MEDIA_CLASS_DIRECTORY from homeassistant.components.media_player.errors import BrowseError from homeassistant.core import HomeAssistant, callback @@ -32,6 +32,7 @@ def async_setup(hass: HomeAssistant) -> None: hass.data[DOMAIN][DOMAIN] = source hass.http.register_view(LocalMediaView(hass, source)) hass.http.register_view(UploadMediaView(hass, source)) + websocket_api.async_register_command(hass, websocket_remove_media) class LocalSource(MediaSource): @@ -190,7 +191,7 @@ def _build_item_response( return media -class LocalMediaView(HomeAssistantView): +class LocalMediaView(http.HomeAssistantView): """ Local Media Finder View. @@ -231,7 +232,7 @@ async def get( return web.FileResponse(media_path) -class UploadMediaView(HomeAssistantView): +class UploadMediaView(http.HomeAssistantView): """View to upload images.""" url = "/api/media_source/local_source/upload" @@ -314,3 +315,52 @@ def _move_file( # pylint: disable=no-self-use with target_path.open("wb") as target_fp: shutil.copyfileobj(uploaded_file.file, target_fp) + + +@websocket_api.websocket_command( + { + vol.Required("type"): "media_source/local_source/remove", + vol.Required("media_content_id"): str, + } +) +@websocket_api.require_admin +@websocket_api.async_response +async def websocket_remove_media( + hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict +) -> None: + """Remove media.""" + try: + item = MediaSourceItem.from_uri(hass, msg["media_content_id"]) + except ValueError as err: + connection.send_error(msg["id"], websocket_api.ERR_INVALID_FORMAT, str(err)) + return + + source: LocalSource = hass.data[DOMAIN][DOMAIN] + + try: + source_dir_id, location = source.async_parse_identifier(item) + except Unresolvable as err: + connection.send_error(msg["id"], websocket_api.ERR_INVALID_FORMAT, str(err)) + return + + item_path = source.async_full_path(source_dir_id, location) + + def _do_delete() -> tuple[str, str] | None: + if not item_path.exists(): + return websocket_api.ERR_NOT_FOUND, "Path does not exist" + + if not item_path.is_file(): + return websocket_api.ERR_NOT_SUPPORTED, "Path is not a file" + + item_path.unlink() + return None + + try: + error = await hass.async_add_executor_job(_do_delete) + except OSError as err: + error = (websocket_api.ERR_UNKNOWN_ERROR, str(err)) + + if error: + connection.send_error(msg["id"], *error) + else: + connection.send_result(msg["id"]) diff --git a/tests/components/media_source/test_local_source.py b/tests/components/media_source/test_local_source.py index f9ee560620c5c5..de36566fb567fe 100644 --- a/tests/components/media_source/test_local_source.py +++ b/tests/components/media_source/test_local_source.py @@ -7,7 +7,7 @@ import pytest -from homeassistant.components import media_source +from homeassistant.components import media_source, websocket_api from homeassistant.components.media_source import const from homeassistant.config import async_process_ha_core_config from homeassistant.setup import async_setup_component @@ -224,3 +224,110 @@ def get_file(name): assert res.status == 401 assert not (Path(temp_dir) / "no-admin-test.png").is_file() + + +async def test_remove_file(hass, hass_ws_client, temp_dir, hass_admin_user): + """Allow uploading media.""" + + msg_count = 0 + file_count = 0 + + def msgid(): + nonlocal msg_count + msg_count += 1 + return msg_count + + def create_file(): + nonlocal file_count + file_count += 1 + to_delete_path = Path(temp_dir) / f"to_delete_{file_count}.txt" + to_delete_path.touch() + return to_delete_path + + client = await hass_ws_client(hass) + to_delete = create_file() + + await client.send_json( + { + "id": msgid(), + "type": "media_source/local_source/remove", + "media_content_id": f"media-source://media_source/test_dir/{to_delete.name}", + } + ) + + msg = await client.receive_json() + + assert msg["success"] + + assert not to_delete.exists() + + # Test with bad media source ID + extra_id_file = create_file() + for bad_id, err in ( + # Not exists + ( + "media-source://media_source/test_dir/not_exist.txt", + websocket_api.ERR_NOT_FOUND, + ), + # Only a dir + ("media-source://media_source/test_dir", websocket_api.ERR_NOT_SUPPORTED), + # File with extra identifiers + ( + f"media-source://media_source/test_dir/bla/../{extra_id_file.name}", + websocket_api.ERR_INVALID_FORMAT, + ), + # Location is invalid + ("media-source://media_source/test_dir/..", websocket_api.ERR_INVALID_FORMAT), + # Domain != media_source + ("media-source://nest/test_dir/.", websocket_api.ERR_INVALID_FORMAT), + # Completely something else + ("http://bla", websocket_api.ERR_INVALID_FORMAT), + ): + await client.send_json( + { + "id": msgid(), + "type": "media_source/local_source/remove", + "media_content_id": bad_id, + } + ) + + msg = await client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == err + + assert extra_id_file.exists() + + # Test error deleting + to_delete_2 = create_file() + + with patch("pathlib.Path.unlink", side_effect=OSError): + await client.send_json( + { + "id": msgid(), + "type": "media_source/local_source/remove", + "media_content_id": f"media-source://media_source/test_dir/{to_delete_2.name}", + } + ) + + msg = await client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == websocket_api.ERR_UNKNOWN_ERROR + + # Test requires admin access + to_delete_3 = create_file() + hass_admin_user.groups = [] + + await client.send_json( + { + "id": msgid(), + "type": "media_source/local_source/remove", + "media_content_id": f"media-source://media_source/test_dir/{to_delete_3.name}", + } + ) + + msg = await client.receive_json() + + assert not msg["success"] + assert to_delete_3.is_file() From 0f580af1d3d1a7eceeefc5964f1967b1af3daed6 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Mon, 21 Feb 2022 19:15:03 +0100 Subject: [PATCH 0894/1098] Correct switch verify to handle discret_read in Modbus (#66890) --- homeassistant/components/modbus/base_platform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/modbus/base_platform.py b/homeassistant/components/modbus/base_platform.py index 615e86ae920e5b..0727fa81e44301 100644 --- a/homeassistant/components/modbus/base_platform.py +++ b/homeassistant/components/modbus/base_platform.py @@ -312,7 +312,7 @@ async def async_update(self, now: datetime | None = None) -> None: self._lazy_errors = self._lazy_error_count self._attr_available = True - if self._verify_type == CALL_TYPE_COIL: + if self._verify_type in (CALL_TYPE_COIL, CALL_TYPE_DISCRETE): self._attr_is_on = bool(result.bits[0] & 1) else: value = int(result.registers[0]) From cb877adb6a8b537bdfa12e09c23ac5e5b44d0d4a Mon Sep 17 00:00:00 2001 From: jan iversen Date: Mon, 21 Feb 2022 19:22:50 +0100 Subject: [PATCH 0895/1098] Allow multiread in modbus binary_sensor (#59886) --- .coveragerc | 1 + homeassistant/components/modbus/__init__.py | 2 + .../components/modbus/binary_sensor.py | 103 +++++++++++++++-- homeassistant/components/modbus/const.py | 1 + tests/components/modbus/test_binary_sensor.py | 109 +++++++++++++++++- 5 files changed, 203 insertions(+), 13 deletions(-) diff --git a/.coveragerc b/.coveragerc index 1b48888ac412c8..654e1a3e4d7f3f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -722,6 +722,7 @@ omit = homeassistant/components/mjpeg/util.py homeassistant/components/mochad/* homeassistant/components/modbus/climate.py + homeassistant/components/modbus/binary_sensor.py homeassistant/components/modem_callerid/sensor.py homeassistant/components/moehlenhoff_alpha2/__init__.py homeassistant/components/moehlenhoff_alpha2/climate.py diff --git a/homeassistant/components/modbus/__init__.py b/homeassistant/components/modbus/__init__.py index 4d33e819a8f034..a5ad05a471195d 100644 --- a/homeassistant/components/modbus/__init__.py +++ b/homeassistant/components/modbus/__init__.py @@ -74,6 +74,7 @@ CONF_RETRY_ON_EMPTY, CONF_REVERSE_ORDER, CONF_SCALE, + CONF_SLAVE_COUNT, CONF_STATE_CLOSED, CONF_STATE_CLOSING, CONF_STATE_OFF, @@ -270,6 +271,7 @@ vol.Optional(CONF_INPUT_TYPE, default=CALL_TYPE_COIL): vol.In( [CALL_TYPE_COIL, CALL_TYPE_DISCRETE] ), + vol.Optional(CONF_SLAVE_COUNT, default=0): cv.positive_int, } ) diff --git a/homeassistant/components/modbus/binary_sensor.py b/homeassistant/components/modbus/binary_sensor.py index 07756b0f20781f..f65d0ad034894d 100644 --- a/homeassistant/components/modbus/binary_sensor.py +++ b/homeassistant/components/modbus/binary_sensor.py @@ -2,16 +2,31 @@ from __future__ import annotations from datetime import datetime +import logging +from typing import Any from homeassistant.components.binary_sensor import BinarySensorEntity -from homeassistant.const import CONF_BINARY_SENSORS, CONF_NAME, STATE_ON -from homeassistant.core import HomeAssistant +from homeassistant.const import ( + CONF_BINARY_SENSORS, + CONF_DEVICE_CLASS, + CONF_NAME, + STATE_ON, +) +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, +) from . import get_hub from .base_platform import BasePlatform +from .const import CONF_SLAVE_COUNT +from .modbus import ModbusHub + +_LOGGER = logging.getLogger(__name__) PARALLEL_UPDATES = 1 @@ -23,21 +38,51 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the Modbus binary sensors.""" - sensors = [] if discovery_info is None: # pragma: no cover return + sensors: list[ModbusBinarySensor | SlaveSensor] = [] + hub = get_hub(hass, discovery_info[CONF_NAME]) for entry in discovery_info[CONF_BINARY_SENSORS]: - hub = get_hub(hass, discovery_info[CONF_NAME]) - sensors.append(ModbusBinarySensor(hub, entry)) - + slave_count = entry.get(CONF_SLAVE_COUNT, 0) + sensor = ModbusBinarySensor(hub, entry, slave_count) + if slave_count > 0: + sensors.extend(await sensor.async_setup_slaves(hass, slave_count, entry)) + sensors.append(sensor) async_add_entities(sensors) class ModbusBinarySensor(BasePlatform, RestoreEntity, BinarySensorEntity): """Modbus binary sensor.""" + def __init__(self, hub: ModbusHub, entry: dict[str, Any], slave_count: int) -> None: + """Initialize the Modbus binary sensor.""" + self._count = slave_count + 1 + self._coordinator: DataUpdateCoordinator[Any] | None = None + self._result = None + super().__init__(hub, entry) + + async def async_setup_slaves( + self, hass: HomeAssistant, slave_count: int, entry: dict[str, Any] + ) -> list[SlaveSensor]: + """Add slaves as needed (1 read for multiple sensors).""" + + # Add a dataCoordinator for each sensor that have slaves + # this ensures that idx = bit position of value in result + # polling is done with the base class + name = self._attr_name if self._attr_name else "modbus_sensor" + self._coordinator = DataUpdateCoordinator( + hass, + _LOGGER, + name=name, + ) + + slaves: list[SlaveSensor] = [] + for idx in range(0, slave_count): + slaves.append(SlaveSensor(self._coordinator, idx, entry)) + return slaves + async def async_added_to_hass(self) -> None: """Handle entity which will be added.""" await self.async_base_added_to_hass() @@ -52,7 +97,7 @@ async def async_update(self, now: datetime | None = None) -> None: return self._call_active = True result = await self._hub.async_pymodbus_call( - self._slave, self._address, 1, self._input_type + self._slave, self._address, self._count, self._input_type ) self._call_active = False if result is None: @@ -61,10 +106,44 @@ async def async_update(self, now: datetime | None = None) -> None: return self._lazy_errors = self._lazy_error_count self._attr_available = False - self.async_write_ha_state() - return + self._result = None + else: + self._lazy_errors = self._lazy_error_count + self._attr_is_on = result.bits[0] & 1 + self._attr_available = True + self._result = result - self._lazy_errors = self._lazy_error_count - self._attr_is_on = result.bits[0] & 1 - self._attr_available = True self.async_write_ha_state() + if self._coordinator: + self._coordinator.async_set_updated_data(self._result) + + +class SlaveSensor(CoordinatorEntity, RestoreEntity, BinarySensorEntity): + """Modbus slave binary sensor.""" + + def __init__( + self, coordinator: DataUpdateCoordinator[Any], idx: int, entry: dict[str, Any] + ) -> None: + """Initialize the Modbus binary sensor.""" + idx += 1 + self._attr_name = f"{entry[CONF_NAME]}_{idx}" + self._attr_device_class = entry.get(CONF_DEVICE_CLASS) + self._attr_available = False + self._result_inx = int(idx / 8) + self._result_bit = 2 ** (idx % 8) + super().__init__(coordinator) + + async def async_added_to_hass(self) -> None: + """Handle entity which will be added.""" + if state := await self.async_get_last_state(): + self._attr_is_on = state.state == STATE_ON + self.async_write_ha_state() + await super().async_added_to_hass() + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + result = self.coordinator.data + if result: + self._attr_is_on = result.bits[self._result_inx] & self._result_bit + super()._handle_coordinator_update() diff --git a/homeassistant/components/modbus/const.py b/homeassistant/components/modbus/const.py index d4f0fa6d9eae9b..934d14012f8155 100644 --- a/homeassistant/components/modbus/const.py +++ b/homeassistant/components/modbus/const.py @@ -37,6 +37,7 @@ CONF_REVERSE_ORDER = "reverse_order" CONF_PRECISION = "precision" CONF_SCALE = "scale" +CONF_SLAVE_COUNT = "slave_count" CONF_STATE_CLOSED = "state_closed" CONF_STATE_CLOSING = "state_closing" CONF_STATE_OFF = "state_off" diff --git a/tests/components/modbus/test_binary_sensor.py b/tests/components/modbus/test_binary_sensor.py index 5127bd55ad1c47..fbe0003b78a744 100644 --- a/tests/components/modbus/test_binary_sensor.py +++ b/tests/components/modbus/test_binary_sensor.py @@ -7,6 +7,7 @@ CALL_TYPE_DISCRETE, CONF_INPUT_TYPE, CONF_LAZY_ERROR, + CONF_SLAVE_COUNT, ) from homeassistant.const import ( CONF_ADDRESS, @@ -188,9 +189,17 @@ async def test_service_binary_sensor_update(hass, mock_modbus, mock_ha): assert hass.states.get(ENTITY_ID).state == STATE_ON +ENTITY_ID2 = f"{ENTITY_ID}_1" + + @pytest.mark.parametrize( "mock_test_state", - [(State(ENTITY_ID, STATE_ON),)], + [ + ( + State(ENTITY_ID, STATE_ON), + State(ENTITY_ID2, STATE_OFF), + ) + ], indirect=True, ) @pytest.mark.parametrize( @@ -202,6 +211,7 @@ async def test_service_binary_sensor_update(hass, mock_modbus, mock_ha): CONF_NAME: TEST_ENTITY_NAME, CONF_ADDRESS: 51, CONF_SCAN_INTERVAL: 0, + CONF_SLAVE_COUNT: 1, } ] }, @@ -210,3 +220,100 @@ async def test_service_binary_sensor_update(hass, mock_modbus, mock_ha): async def test_restore_state_binary_sensor(hass, mock_test_state, mock_modbus): """Run test for binary sensor restore state.""" assert hass.states.get(ENTITY_ID).state == mock_test_state[0].state + assert hass.states.get(ENTITY_ID2).state == mock_test_state[1].state + + +TEST_NAME = "test_sensor" + + +@pytest.mark.parametrize( + "do_config", + [ + { + CONF_BINARY_SENSORS: [ + { + CONF_NAME: TEST_ENTITY_NAME, + CONF_ADDRESS: 51, + CONF_SLAVE_COUNT: 3, + } + ] + }, + ], +) +async def test_config_slave_binary_sensor(hass, mock_modbus): + """Run config test for binary sensor.""" + assert SENSOR_DOMAIN in hass.config.components + + for addon in ["", "_1", "_2", "_3"]: + entity_id = f"{SENSOR_DOMAIN}.{TEST_ENTITY_NAME}{addon}" + assert hass.states.get(entity_id) is not None + + +@pytest.mark.parametrize( + "do_config", + [ + { + CONF_BINARY_SENSORS: [ + { + CONF_NAME: TEST_ENTITY_NAME, + CONF_ADDRESS: 51, + CONF_SLAVE_COUNT: 8, + } + ] + }, + ], +) +@pytest.mark.parametrize( + "register_words,expected, slaves", + [ + ( + [0x01, 0x00], + STATE_ON, + [ + STATE_OFF, + STATE_OFF, + STATE_OFF, + STATE_OFF, + STATE_OFF, + STATE_OFF, + STATE_OFF, + STATE_OFF, + ], + ), + ( + [0x02, 0x00], + STATE_OFF, + [ + STATE_ON, + STATE_OFF, + STATE_OFF, + STATE_OFF, + STATE_OFF, + STATE_OFF, + STATE_OFF, + STATE_OFF, + ], + ), + ( + [0x01, 0x01], + STATE_ON, + [ + STATE_OFF, + STATE_OFF, + STATE_OFF, + STATE_OFF, + STATE_OFF, + STATE_OFF, + STATE_OFF, + STATE_ON, + ], + ), + ], +) +async def test_slave_binary_sensor(hass, expected, slaves, mock_do_cycle): + """Run test for given config.""" + assert hass.states.get(ENTITY_ID).state == expected + + for i in range(8): + entity_id = f"{SENSOR_DOMAIN}.{TEST_ENTITY_NAME}_{i+1}" + assert hass.states.get(entity_id).state == slaves[i] From 9a5eec561a18cd0bffbbd65afe68f70c0893d28c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 21 Feb 2022 08:27:23 -1000 Subject: [PATCH 0896/1098] Only set require_restart on config entry reload if its not recoverable (#66994) --- .../components/config/config_entries.py | 10 +++--- .../components/config/test_config_entries.py | 33 ++++++++++++++++++- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/config/config_entries.py b/homeassistant/components/config/config_entries.py index c3d20fb0f169ad..07bdc794128b10 100644 --- a/homeassistant/components/config/config_entries.py +++ b/homeassistant/components/config/config_entries.py @@ -88,15 +88,17 @@ async def post(self, request, entry_id): raise Unauthorized(config_entry_id=entry_id, permission="remove") hass = request.app["hass"] + entry = hass.config_entries.async_get_entry(entry_id) + if not entry: + return self.json_message("Invalid entry specified", HTTPStatus.NOT_FOUND) + assert isinstance(entry, config_entries.ConfigEntry) try: - result = await hass.config_entries.async_reload(entry_id) + await hass.config_entries.async_reload(entry_id) except config_entries.OperationNotAllowed: return self.json_message("Entry cannot be reloaded", HTTPStatus.FORBIDDEN) - except config_entries.UnknownEntry: - return self.json_message("Invalid entry specified", HTTPStatus.NOT_FOUND) - return self.json({"require_restart": not result}) + return self.json({"require_restart": not entry.state.recoverable}) def _prepare_config_flow_result_json(result, prepare_result_json): diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index cfc6d8d49077c3..06b9f1ae7f6c37 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -9,7 +9,7 @@ from homeassistant import config_entries as core_ce, data_entry_flow from homeassistant.components.config import config_entries -from homeassistant.config_entries import HANDLERS +from homeassistant.config_entries import HANDLERS, ConfigFlow from homeassistant.core import callback from homeassistant.generated import config_flows from homeassistant.helpers import config_validation as cv @@ -193,6 +193,37 @@ async def test_reload_entry_in_failed_state(hass, client, hass_admin_user): assert len(hass.config_entries.async_entries()) == 1 +async def test_reload_entry_in_setup_retry(hass, client, hass_admin_user): + """Test reloading an entry via the API that is in setup retry.""" + mock_setup_entry = AsyncMock(return_value=True) + mock_unload_entry = AsyncMock(return_value=True) + mock_migrate_entry = AsyncMock(return_value=True) + + mock_integration( + hass, + MockModule( + "comp", + async_setup_entry=mock_setup_entry, + async_unload_entry=mock_unload_entry, + async_migrate_entry=mock_migrate_entry, + ), + ) + mock_entity_platform(hass, "config_flow.comp", None) + entry = MockConfigEntry(domain="comp", state=core_ce.ConfigEntryState.SETUP_RETRY) + entry.supports_unload = True + entry.add_to_hass(hass) + + with patch.dict(HANDLERS, {"comp": ConfigFlow, "test": ConfigFlow}): + resp = await client.post( + f"/api/config/config_entries/entry/{entry.entry_id}/reload" + ) + await hass.async_block_till_done() + assert resp.status == HTTPStatus.OK + data = await resp.json() + assert data == {"require_restart": False} + assert len(hass.config_entries.async_entries()) == 1 + + async def test_available_flows(hass, client): """Test querying the available flows.""" with patch.object(config_flows, "FLOWS", ["hello", "world"]): From 5af406858328b6d292ea61e2d92139a9dce3cb61 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Mon, 21 Feb 2022 10:34:38 -0800 Subject: [PATCH 0897/1098] Fix binary sensor translations for carbon_monoxide (#66891) Co-authored-by: Paulus Schoutsen --- homeassistant/components/binary_sensor/strings.json | 6 +++--- .../components/binary_sensor/translations/en.json | 4 +--- script/translations/develop.py | 8 +++++++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/binary_sensor/strings.json b/homeassistant/components/binary_sensor/strings.json index 62a73036238174..62ff81877311ff 100644 --- a/homeassistant/components/binary_sensor/strings.json +++ b/homeassistant/components/binary_sensor/strings.json @@ -115,9 +115,9 @@ "off": "Not charging", "on": "Charging" }, - "co": { - "off": "Clear", - "on": "Detected" + "carbon_monoxide": { + "off": "[%key:component::binary_sensor::state::gas::off%]", + "on": "[%key:component::binary_sensor::state::gas::on%]" }, "cold": { "off": "[%key:component::binary_sensor::state::battery::off%]", diff --git a/homeassistant/components/binary_sensor/translations/en.json b/homeassistant/components/binary_sensor/translations/en.json index fd69ab678a7840..1dc6cf2caa1b8e 100644 --- a/homeassistant/components/binary_sensor/translations/en.json +++ b/homeassistant/components/binary_sensor/translations/en.json @@ -59,8 +59,6 @@ "connected": "{entity_name} connected", "gas": "{entity_name} started detecting gas", "hot": "{entity_name} became hot", - "is_not_tampered": "{entity_name} stopped detecting tampering", - "is_tampered": "{entity_name} started detecting tampering", "light": "{entity_name} started detecting light", "locked": "{entity_name} locked", "moist": "{entity_name} became moist", @@ -134,7 +132,7 @@ "off": "Not charging", "on": "Charging" }, - "co": { + "carbon_monoxide": { "off": "Clear", "on": "Detected" }, diff --git a/script/translations/develop.py b/script/translations/develop.py index f59b9b9c7cb638..2f9966afc29644 100644 --- a/script/translations/develop.py +++ b/script/translations/develop.py @@ -75,7 +75,13 @@ def substitute_reference(value, flattened_translations): new = value for key in matches: if key in flattened_translations: - new = new.replace(f"[%key:{key}%]", flattened_translations[key]) + new = new.replace( + f"[%key:{key}%]", + # New value can also be a substitution reference + substitute_reference( + flattened_translations[key], flattened_translations + ), + ) else: print(f"Invalid substitution key '{key}' found in string '{value}'") sys.exit(1) From 4811b510eba2fda5c47e91bb2c5597afe98ecc66 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 21 Feb 2022 08:42:54 -1000 Subject: [PATCH 0898/1098] Ensure WiZ can still setup with old firmwares (#66968) --- homeassistant/components/wiz/config_flow.py | 6 ++++ homeassistant/components/wiz/entity.py | 14 +++++---- homeassistant/components/wiz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/wiz/__init__.py | 11 +++++++ tests/components/wiz/test_light.py | 32 +++++++++++++++++++++ 7 files changed, 61 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/wiz/config_flow.py b/homeassistant/components/wiz/config_flow.py index 924e88793e47ff..7d86502784c242 100644 --- a/homeassistant/components/wiz/config_flow.py +++ b/homeassistant/components/wiz/config_flow.py @@ -71,6 +71,12 @@ async def _async_connect_discovered_or_abort(self) -> None: try: bulbtype = await bulb.get_bulbtype() except WIZ_CONNECT_EXCEPTIONS as ex: + _LOGGER.debug( + "Failed to connect to %s during discovery: %s", + device.ip_address, + ex, + exc_info=True, + ) raise AbortFlow("cannot_connect") from ex self._name = name_from_bulb_type_and_mac(bulbtype, device.mac_address) diff --git a/homeassistant/components/wiz/entity.py b/homeassistant/components/wiz/entity.py index 82f19a61002e5b..9b22d35de7d566 100644 --- a/homeassistant/components/wiz/entity.py +++ b/homeassistant/components/wiz/entity.py @@ -6,6 +6,7 @@ from pywizlight.bulblibrary import BulbType +from homeassistant.const import ATTR_HW_VERSION, ATTR_MODEL from homeassistant.core import callback from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.entity import DeviceInfo, Entity, ToggleEntity @@ -24,17 +25,20 @@ def __init__(self, wiz_data: WizData, name: str) -> None: bulb_type: BulbType = self._device.bulbtype self._attr_unique_id = self._device.mac self._attr_name = name - hw_data = bulb_type.name.split("_") - board = hw_data.pop(0) - model = hw_data.pop(0) self._attr_device_info = DeviceInfo( connections={(CONNECTION_NETWORK_MAC, self._device.mac)}, name=name, manufacturer="WiZ", - model=model, - hw_version=f"{board} {hw_data[0]}" if hw_data else board, sw_version=bulb_type.fw_version, ) + if bulb_type.name is None: + return + hw_data = bulb_type.name.split("_") + board = hw_data.pop(0) + model = hw_data.pop(0) + hw_version = f"{board} {hw_data[0]}" if hw_data else board + self._attr_device_info[ATTR_HW_VERSION] = hw_version + self._attr_device_info[ATTR_MODEL] = model @callback def _handle_coordinator_update(self) -> None: diff --git a/homeassistant/components/wiz/manifest.json b/homeassistant/components/wiz/manifest.json index 2dacb32c094c1b..df780d7b39c61d 100644 --- a/homeassistant/components/wiz/manifest.json +++ b/homeassistant/components/wiz/manifest.json @@ -13,7 +13,7 @@ "dependencies": ["network"], "quality_scale": "platinum", "documentation": "https://www.home-assistant.io/integrations/wiz", - "requirements": ["pywizlight==0.5.11"], + "requirements": ["pywizlight==0.5.12"], "iot_class": "local_push", "codeowners": ["@sbidy"] } diff --git a/requirements_all.txt b/requirements_all.txt index c6c0267de34183..1d3e91cd65d73f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2057,7 +2057,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.5.11 +pywizlight==0.5.12 # homeassistant.components.xeoma pyxeoma==1.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f82e28776c06cb..1ad42575a89a97 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1288,7 +1288,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.5.11 +pywizlight==0.5.12 # homeassistant.components.zerproc pyzerproc==0.4.8 diff --git a/tests/components/wiz/__init__.py b/tests/components/wiz/__init__.py index cb23b3eef34ea3..c4a31b0a394102 100644 --- a/tests/components/wiz/__init__.py +++ b/tests/components/wiz/__init__.py @@ -150,6 +150,17 @@ white_channels=2, white_to_color_ratio=80, ) +FAKE_OLD_FIRMWARE_DIMMABLE_BULB = BulbType( + bulb_type=BulbClass.DW, + name=None, + features=Features( + color=False, color_tmp=False, effect=True, brightness=True, dual_head=False + ), + kelvin_range=None, + fw_version="1.8.0", + white_channels=1, + white_to_color_ratio=80, +) async def setup_integration( diff --git a/tests/components/wiz/test_light.py b/tests/components/wiz/test_light.py index c79cf74e1308c5..48166e941d4f12 100644 --- a/tests/components/wiz/test_light.py +++ b/tests/components/wiz/test_light.py @@ -22,6 +22,7 @@ from . import ( FAKE_MAC, + FAKE_OLD_FIRMWARE_DIMMABLE_BULB, FAKE_RGBW_BULB, FAKE_RGBWW_BULB, FAKE_TURNABLE_BULB, @@ -169,3 +170,34 @@ async def test_turnable_light(hass: HomeAssistant) -> None: state = hass.states.get(entity_id) assert state.state == STATE_ON assert state.attributes[ATTR_COLOR_TEMP] == 153 + + +async def test_old_firmware_dimmable_light(hass: HomeAssistant) -> None: + """Test a light operation with a dimmable light with old firmware.""" + bulb, _ = await async_setup_integration( + hass, bulb_type=FAKE_OLD_FIRMWARE_DIMMABLE_BULB + ) + entity_id = "light.mock_title" + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 128}, + blocking=True, + ) + pilot: PilotBuilder = bulb.turn_on.mock_calls[0][1][0] + assert pilot.pilot_params == {"dimming": 50, "state": True} + + await async_push_update(hass, bulb, {"mac": FAKE_MAC, **pilot.pilot_params}) + state = hass.states.get(entity_id) + assert state.state == STATE_ON + assert state.attributes[ATTR_BRIGHTNESS] == 128 + + bulb.turn_on.reset_mock() + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 255}, + blocking=True, + ) + pilot: PilotBuilder = bulb.turn_on.mock_calls[0][1][0] + assert pilot.pilot_params == {"dimming": 100, "state": True} From 3644740786c95f2a06c8f25059cfc68f07ab332f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 21 Feb 2022 19:45:04 +0100 Subject: [PATCH 0899/1098] Extend Plugwise DeviceInfo (#66619) --- homeassistant/components/plugwise/entity.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/homeassistant/components/plugwise/entity.py b/homeassistant/components/plugwise/entity.py index d57681ae504983..b172b5468b05a9 100644 --- a/homeassistant/components/plugwise/entity.py +++ b/homeassistant/components/plugwise/entity.py @@ -4,6 +4,10 @@ from typing import Any from homeassistant.const import ATTR_NAME, ATTR_VIA_DEVICE, CONF_HOST +from homeassistant.helpers.device_registry import ( + CONNECTION_NETWORK_MAC, + CONNECTION_ZIGBEE, +) from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -30,13 +34,21 @@ def __init__( configuration_url = f"http://{entry.data[CONF_HOST]}" data = coordinator.data.devices[device_id] + connections = set() + if mac := data.get("mac_address"): + connections.add((CONNECTION_NETWORK_MAC, mac)) + if mac := data.get("zigbee_mac_address"): + connections.add((CONNECTION_ZIGBEE, mac)) + self._attr_device_info = DeviceInfo( configuration_url=configuration_url, identifiers={(DOMAIN, device_id)}, + connections=connections, manufacturer=data.get("vendor"), model=data.get("model"), name=f"Smile {coordinator.data.gateway['smile_name']}", sw_version=data.get("fw"), + hw_version=data.get("hw"), ) if device_id != coordinator.data.gateway["gateway_id"]: From 76149876ab286517bca575c0efdcb24b175d7ae7 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Mon, 21 Feb 2022 12:46:20 -0600 Subject: [PATCH 0900/1098] Enable fallback polling for Sonos microphone binary_sensor (#66299) --- homeassistant/components/sonos/binary_sensor.py | 9 ++++++++- homeassistant/components/sonos/speaker.py | 4 ++++ tests/components/sonos/conftest.py | 1 + tests/components/sonos/test_sensor.py | 7 +++++-- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonos/binary_sensor.py b/homeassistant/components/sonos/binary_sensor.py index 534e5f5dd02c20..4eaa75f92ae347 100644 --- a/homeassistant/components/sonos/binary_sensor.py +++ b/homeassistant/components/sonos/binary_sensor.py @@ -16,6 +16,7 @@ from .const import SONOS_CREATE_BATTERY, SONOS_CREATE_MIC_SENSOR from .entity import SonosEntity +from .helpers import soco_error from .speaker import SonosSpeaker ATTR_BATTERY_POWER_SOURCE = "power_source" @@ -99,7 +100,13 @@ def __init__(self, speaker: SonosSpeaker) -> None: self._attr_name = f"{self.speaker.zone_name} Microphone" async def _async_fallback_poll(self) -> None: - """Stub for abstract class implementation. Not a pollable attribute.""" + """Handle polling when subscription fails.""" + await self.hass.async_add_executor_job(self.poll_state) + + @soco_error() + def poll_state(self) -> None: + """Poll the current state of the microphone.""" + self.speaker.mic_enabled = self.soco.mic_enabled @property def is_on(self) -> bool: diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index 16ca58448e20f4..018eab4ca04bbe 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -263,6 +263,10 @@ def setup(self, entry: ConfigEntry) -> None: ) dispatcher_send(self.hass, SONOS_CREATE_BATTERY, self) + if (mic_enabled := self.soco.mic_enabled) is not None: + self.mic_enabled = mic_enabled + dispatcher_send(self.hass, SONOS_CREATE_MIC_SENSOR, self) + if new_alarms := [ alarm.alarm_id for alarm in self.alarms if alarm.zone.uid == self.soco.uid ]: diff --git a/tests/components/sonos/conftest.py b/tests/components/sonos/conftest.py index bc49c12ed81564..70061e88692564 100644 --- a/tests/components/sonos/conftest.py +++ b/tests/components/sonos/conftest.py @@ -112,6 +112,7 @@ def soco_fixture( mock_soco.audio_delay = 2 mock_soco.bass = 1 mock_soco.treble = -1 + mock_soco.mic_enabled = False mock_soco.sub_enabled = False mock_soco.surround_enabled = True mock_soco.soundbar_audio_input_format = "Dolby 5.1" diff --git a/tests/components/sonos/test_sensor.py b/tests/components/sonos/test_sensor.py index bda4e08cd25b9d..8a51ea5b2e6d46 100644 --- a/tests/components/sonos/test_sensor.py +++ b/tests/components/sonos/test_sensor.py @@ -165,13 +165,16 @@ async def test_microphone_binary_sensor( ): """Test microphone binary sensor.""" entity_registry = ent_reg.async_get(hass) - assert "binary_sensor.zone_a_microphone" not in entity_registry.entities + assert "binary_sensor.zone_a_microphone" in entity_registry.entities + + mic_binary_sensor = entity_registry.entities["binary_sensor.zone_a_microphone"] + mic_binary_sensor_state = hass.states.get(mic_binary_sensor.entity_id) + assert mic_binary_sensor_state.state == STATE_OFF # Update the speaker with a callback event subscription = soco.deviceProperties.subscribe.return_value subscription.callback(device_properties_event) await hass.async_block_till_done() - mic_binary_sensor = entity_registry.entities["binary_sensor.zone_a_microphone"] mic_binary_sensor_state = hass.states.get(mic_binary_sensor.entity_id) assert mic_binary_sensor_state.state == STATE_ON From 2456d8a401644ebda33e3c60c32b0a137c44758b Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Mon, 21 Feb 2022 10:56:20 -0800 Subject: [PATCH 0901/1098] Remember user and hub after input in ConfigFlow (#66608) --- homeassistant/components/overkiz/config_flow.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/overkiz/config_flow.py b/homeassistant/components/overkiz/config_flow.py index f788d12747d4d9..f35941d677326a 100644 --- a/homeassistant/components/overkiz/config_flow.py +++ b/homeassistant/components/overkiz/config_flow.py @@ -65,6 +65,9 @@ async def async_step_user( errors = {} if user_input: + self._default_user = user_input[CONF_USERNAME] + self._default_hub = user_input[CONF_HUB] + try: await self.async_validate_input(user_input) except TooManyRequestsException: From 9ed4bcf9659f5e7169306c0a19d1e4f976ee02c8 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Mon, 21 Feb 2022 19:00:09 +0000 Subject: [PATCH 0902/1098] Add unique_id to the filter component (#65010) * Optional manually defined uniqueid * move to _attr --- homeassistant/components/filter/sensor.py | 8 ++++++-- tests/components/filter/test_sensor.py | 8 ++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/filter/sensor.py b/homeassistant/components/filter/sensor.py index 46d142fc9625e6..d2ad3ec313cb31 100644 --- a/homeassistant/components/filter/sensor.py +++ b/homeassistant/components/filter/sensor.py @@ -29,6 +29,7 @@ ATTR_UNIT_OF_MEASUREMENT, CONF_ENTITY_ID, CONF_NAME, + CONF_UNIQUE_ID, STATE_UNAVAILABLE, STATE_UNKNOWN, ) @@ -150,6 +151,7 @@ cv.entity_domain(INPUT_NUMBER_DOMAIN), ), vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Required(CONF_FILTERS): vol.All( cv.ensure_list, [ @@ -178,6 +180,7 @@ async def async_setup_platform( await async_setup_reload_service(hass, DOMAIN, PLATFORMS) name = config.get(CONF_NAME) + unique_id = config.get(CONF_UNIQUE_ID) entity_id = config.get(CONF_ENTITY_ID) filters = [ @@ -185,15 +188,16 @@ async def async_setup_platform( for _filter in config[CONF_FILTERS] ] - async_add_entities([SensorFilter(name, entity_id, filters)]) + async_add_entities([SensorFilter(name, unique_id, entity_id, filters)]) class SensorFilter(SensorEntity): """Representation of a Filter Sensor.""" - def __init__(self, name, entity_id, filters): + def __init__(self, name, unique_id, entity_id, filters): """Initialize the sensor.""" self._name = name + self._attr_unique_id = unique_id self._entity = entity_id self._unit_of_measurement = None self._state = None diff --git a/tests/components/filter/test_sensor.py b/tests/components/filter/test_sensor.py index b42fc3fa9fe08d..b044b2c08fa63c 100644 --- a/tests/components/filter/test_sensor.py +++ b/tests/components/filter/test_sensor.py @@ -26,6 +26,7 @@ STATE_UNKNOWN, ) import homeassistant.core as ha +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -256,6 +257,7 @@ async def test_setup(hass): "sensor": { "platform": "filter", "name": "test", + "unique_id": "uniqueid_sensor_test", "entity_id": "sensor.test_monitored", "filters": [ {"filter": "outlier", "window_size": 10, "radius": 4.0}, @@ -285,6 +287,12 @@ async def test_setup(hass): assert state.attributes[ATTR_STATE_CLASS] is SensorStateClass.TOTAL_INCREASING assert state.state == "1.0" + entity_reg = er.async_get(hass) + entity_id = entity_reg.async_get_entity_id( + "sensor", DOMAIN, "uniqueid_sensor_test" + ) + assert entity_id == "sensor.test" + async def test_invalid_state(hass): """Test if filter attributes are inherited.""" From d49029e9fcd01561d4ccf1df786da7ad0b4ccec8 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 21 Feb 2022 20:07:43 +0100 Subject: [PATCH 0903/1098] Add door and lock status to Renault integration (#66698) * Add coordinator for lock status * Add fixture for lock status * Add lock status binary sensor * Add to test constants * Adjust conftest * Fix inverted state * Add door status Co-authored-by: epenet --- .../components/renault/binary_sensor.py | 82 ++++++++---- .../components/renault/renault_vehicle.py | 5 + tests/components/renault/conftest.py | 14 +++ tests/components/renault/const.py | 117 +++++++++++++++++- .../renault/fixtures/lock_status.1.json | 15 +++ 5 files changed, 207 insertions(+), 26 deletions(-) create mode 100644 tests/components/renault/fixtures/lock_status.1.json diff --git a/homeassistant/components/renault/binary_sensor.py b/homeassistant/components/renault/binary_sensor.py index 3d96624e628082..a24c9be4e6d582 100644 --- a/homeassistant/components/renault/binary_sensor.py +++ b/homeassistant/components/renault/binary_sensor.py @@ -79,29 +79,61 @@ def icon(self) -> str | None: return None -BINARY_SENSOR_TYPES: tuple[RenaultBinarySensorEntityDescription, ...] = ( - RenaultBinarySensorEntityDescription( - key="plugged_in", - coordinator="battery", - device_class=BinarySensorDeviceClass.PLUG, - name="Plugged In", - on_key="plugStatus", - on_value=PlugState.PLUGGED.value, - ), - RenaultBinarySensorEntityDescription( - key="charging", - coordinator="battery", - device_class=BinarySensorDeviceClass.BATTERY_CHARGING, - name="Charging", - on_key="chargingStatus", - on_value=ChargeState.CHARGE_IN_PROGRESS.value, - ), - RenaultBinarySensorEntityDescription( - key="hvac_status", - coordinator="hvac_status", - icon_fn=lambda e: "mdi:fan" if e.is_on else "mdi:fan-off", - name="HVAC", - on_key="hvacStatus", - on_value="on", - ), +BINARY_SENSOR_TYPES: tuple[RenaultBinarySensorEntityDescription, ...] = tuple( + [ + RenaultBinarySensorEntityDescription( + key="plugged_in", + coordinator="battery", + device_class=BinarySensorDeviceClass.PLUG, + name="Plugged In", + on_key="plugStatus", + on_value=PlugState.PLUGGED.value, + ), + RenaultBinarySensorEntityDescription( + key="charging", + coordinator="battery", + device_class=BinarySensorDeviceClass.BATTERY_CHARGING, + name="Charging", + on_key="chargingStatus", + on_value=ChargeState.CHARGE_IN_PROGRESS.value, + ), + RenaultBinarySensorEntityDescription( + key="hvac_status", + coordinator="hvac_status", + icon_fn=lambda e: "mdi:fan" if e.is_on else "mdi:fan-off", + name="HVAC", + on_key="hvacStatus", + on_value="on", + ), + RenaultBinarySensorEntityDescription( + key="lock_status", + coordinator="lock_status", + # lock: on means open (unlocked), off means closed (locked) + device_class=BinarySensorDeviceClass.LOCK, + name="Lock", + on_key="lockStatus", + on_value="unlocked", + ), + RenaultBinarySensorEntityDescription( + key="hatch_status", + coordinator="lock_status", + # On means open, Off means closed + device_class=BinarySensorDeviceClass.DOOR, + name="Hatch", + on_key="hatchStatus", + on_value="open", + ), + ] + + [ + RenaultBinarySensorEntityDescription( + key=f"{door.replace(' ','_').lower()}_door_status", + coordinator="lock_status", + # On means open, Off means closed + device_class=BinarySensorDeviceClass.DOOR, + name=f"{door} Door", + on_key=f"doorStatus{door.replace(' ','')}", + on_value="open", + ) + for door in ("Rear Left", "Rear Right", "Driver", "Passenger") + ], ) diff --git a/homeassistant/components/renault/renault_vehicle.py b/homeassistant/components/renault/renault_vehicle.py index 462c5bbc2397d9..c4e42a7be5becc 100644 --- a/homeassistant/components/renault/renault_vehicle.py +++ b/homeassistant/components/renault/renault_vehicle.py @@ -148,4 +148,9 @@ async def async_initialise(self) -> None: requires_electricity=True, update_method=lambda x: x.get_charge_mode, ), + RenaultCoordinatorDescription( + endpoint="lock-status", + key="lock_status", + update_method=lambda x: x.get_lock_status, + ), ) diff --git a/tests/components/renault/conftest.py b/tests/components/renault/conftest.py index a1f3b42167c1ef..da86b41e3b05f8 100644 --- a/tests/components/renault/conftest.py +++ b/tests/components/renault/conftest.py @@ -102,6 +102,11 @@ def _get_fixtures(vehicle_type: str) -> MappingProxyType: if "location" in mock_vehicle["endpoints"] else load_fixture("renault/no_data.json") ).get_attributes(schemas.KamereonVehicleLocationDataSchema), + "lock_status": schemas.KamereonVehicleDataResponseSchema.loads( + load_fixture(f"renault/{mock_vehicle['endpoints']['lock_status']}") + if "lock_status" in mock_vehicle["endpoints"] + else load_fixture("renault/no_data.json") + ).get_attributes(schemas.KamereonVehicleLockStatusDataSchema), } @@ -125,6 +130,9 @@ def patch_fixtures_with_data(vehicle_type: str): ), patch( "renault_api.renault_vehicle.RenaultVehicle.get_location", return_value=mock_fixtures["location"], + ), patch( + "renault_api.renault_vehicle.RenaultVehicle.get_lock_status", + return_value=mock_fixtures["lock_status"], ): yield @@ -149,6 +157,9 @@ def patch_fixtures_with_no_data(): ), patch( "renault_api.renault_vehicle.RenaultVehicle.get_location", return_value=mock_fixtures["location"], + ), patch( + "renault_api.renault_vehicle.RenaultVehicle.get_lock_status", + return_value=mock_fixtures["lock_status"], ): yield @@ -171,6 +182,9 @@ def _patch_fixtures_with_side_effect(side_effect: Any): ), patch( "renault_api.renault_vehicle.RenaultVehicle.get_location", side_effect=side_effect, + ), patch( + "renault_api.renault_vehicle.RenaultVehicle.get_lock_status", + side_effect=side_effect, ): yield diff --git a/tests/components/renault/const.py b/tests/components/renault/const.py index 90a1665b75b978..d0fadc54030436 100644 --- a/tests/components/renault/const.py +++ b/tests/components/renault/const.py @@ -252,6 +252,7 @@ True, # location True, # battery-status True, # charge-mode + True, # lock-status ], "endpoints": { "battery_status": "battery_status_not_charging.json", @@ -259,6 +260,7 @@ "cockpit": "cockpit_ev.json", "hvac_status": "hvac_status.2.json", "location": "location.json", + "lock_status": "lock_status.1.json", }, Platform.BINARY_SENSOR: [ { @@ -279,6 +281,42 @@ ATTR_STATE: STATE_OFF, ATTR_UNIQUE_ID: "vf1aaaaa555777999_hvac_status", }, + { + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.LOCK, + ATTR_ENTITY_ID: "binary_sensor.reg_number_lock", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "vf1aaaaa555777999_lock_status", + }, + { + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.DOOR, + ATTR_ENTITY_ID: "binary_sensor.reg_number_rear_left_door", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "vf1aaaaa555777999_rear_left_door_status", + }, + { + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.DOOR, + ATTR_ENTITY_ID: "binary_sensor.reg_number_rear_right_door", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "vf1aaaaa555777999_rear_right_door_status", + }, + { + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.DOOR, + ATTR_ENTITY_ID: "binary_sensor.reg_number_driver_door", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "vf1aaaaa555777999_driver_door_status", + }, + { + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.DOOR, + ATTR_ENTITY_ID: "binary_sensor.reg_number_passenger_door", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "vf1aaaaa555777999_passenger_door_status", + }, + { + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.DOOR, + ATTR_ENTITY_ID: "binary_sensor.reg_number_hatch", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "vf1aaaaa555777999_hatch_status", + }, ], Platform.BUTTON: [ { @@ -434,12 +472,14 @@ True, # location True, # battery-status True, # charge-mode + True, # lock-status ], "endpoints": { "battery_status": "battery_status_charging.json", "charge_mode": "charge_mode_always.json", "cockpit": "cockpit_fuel.json", "location": "location.json", + "lock_status": "lock_status.1.json", }, Platform.BINARY_SENSOR: [ { @@ -454,6 +494,42 @@ ATTR_STATE: STATE_ON, ATTR_UNIQUE_ID: "vf1aaaaa555777123_charging", }, + { + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.LOCK, + ATTR_ENTITY_ID: "binary_sensor.reg_number_lock", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "vf1aaaaa555777123_lock_status", + }, + { + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.DOOR, + ATTR_ENTITY_ID: "binary_sensor.reg_number_rear_left_door", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "vf1aaaaa555777123_rear_left_door_status", + }, + { + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.DOOR, + ATTR_ENTITY_ID: "binary_sensor.reg_number_rear_right_door", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "vf1aaaaa555777123_rear_right_door_status", + }, + { + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.DOOR, + ATTR_ENTITY_ID: "binary_sensor.reg_number_driver_door", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "vf1aaaaa555777123_driver_door_status", + }, + { + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.DOOR, + ATTR_ENTITY_ID: "binary_sensor.reg_number_passenger_door", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "vf1aaaaa555777123_passenger_door_status", + }, + { + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.DOOR, + ATTR_ENTITY_ID: "binary_sensor.reg_number_hatch", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "vf1aaaaa555777123_hatch_status", + }, ], Platform.BUTTON: [ { @@ -604,12 +680,51 @@ True, # location # Ignore, # battery-status # Ignore, # charge-mode + True, # lock-status ], "endpoints": { "cockpit": "cockpit_fuel.json", "location": "location.json", + "lock_status": "lock_status.1.json", }, - Platform.BINARY_SENSOR: [], + Platform.BINARY_SENSOR: [ + { + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.LOCK, + ATTR_ENTITY_ID: "binary_sensor.reg_number_lock", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "vf1aaaaa555777123_lock_status", + }, + { + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.DOOR, + ATTR_ENTITY_ID: "binary_sensor.reg_number_rear_left_door", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "vf1aaaaa555777123_rear_left_door_status", + }, + { + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.DOOR, + ATTR_ENTITY_ID: "binary_sensor.reg_number_rear_right_door", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "vf1aaaaa555777123_rear_right_door_status", + }, + { + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.DOOR, + ATTR_ENTITY_ID: "binary_sensor.reg_number_driver_door", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "vf1aaaaa555777123_driver_door_status", + }, + { + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.DOOR, + ATTR_ENTITY_ID: "binary_sensor.reg_number_passenger_door", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "vf1aaaaa555777123_passenger_door_status", + }, + { + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.DOOR, + ATTR_ENTITY_ID: "binary_sensor.reg_number_hatch", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "vf1aaaaa555777123_hatch_status", + }, + ], Platform.BUTTON: [ { ATTR_ENTITY_ID: "button.reg_number_start_air_conditioner", diff --git a/tests/components/renault/fixtures/lock_status.1.json b/tests/components/renault/fixtures/lock_status.1.json new file mode 100644 index 00000000000000..9cda30d5a626ed --- /dev/null +++ b/tests/components/renault/fixtures/lock_status.1.json @@ -0,0 +1,15 @@ +{ + "data": { + "type": "Car", + "id": "VF1AAAAA555777999", + "attributes": { + "lockStatus": "locked", + "doorStatusRearLeft": "closed", + "doorStatusRearRight": "closed", + "doorStatusDriver": "closed", + "doorStatusPassenger": "closed", + "hatchStatus": "closed", + "lastUpdateTime": "2022-02-02T13:51:13Z" + } + } + } \ No newline at end of file From 0606b4a843ce6868c574ca8ef17d84365bcc71a9 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Mon, 21 Feb 2022 21:35:24 +0100 Subject: [PATCH 0904/1098] add apparent and reactive power DeviceClass (#65938) --- homeassistant/components/fronius/sensor.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/fronius/sensor.py b/homeassistant/components/fronius/sensor.py index 2674778583773f..a266998a9b5f03 100644 --- a/homeassistant/components/fronius/sensor.py +++ b/homeassistant/components/fronius/sensor.py @@ -24,6 +24,7 @@ FREQUENCY_HERTZ, PERCENTAGE, POWER_VOLT_AMPERE, + POWER_VOLT_AMPERE_REACTIVE, POWER_WATT, TEMP_CELSIUS, ) @@ -52,7 +53,6 @@ ELECTRIC_CHARGE_AMPERE_HOURS: Final = "Ah" ENERGY_VOLT_AMPERE_REACTIVE_HOUR: Final = "varh" -POWER_VOLT_AMPERE_REACTIVE: Final = "var" PLATFORM_SCHEMA = vol.All( PLATFORM_SCHEMA.extend( @@ -338,6 +338,7 @@ async def async_setup_entry( key="power_apparent_phase_1", name="Power apparent phase 1", native_unit_of_measurement=POWER_VOLT_AMPERE, + device_class=SensorDeviceClass.APPARENT_POWER, state_class=SensorStateClass.MEASUREMENT, icon="mdi:flash-outline", entity_registry_enabled_default=False, @@ -346,6 +347,7 @@ async def async_setup_entry( key="power_apparent_phase_2", name="Power apparent phase 2", native_unit_of_measurement=POWER_VOLT_AMPERE, + device_class=SensorDeviceClass.APPARENT_POWER, state_class=SensorStateClass.MEASUREMENT, icon="mdi:flash-outline", entity_registry_enabled_default=False, @@ -354,6 +356,7 @@ async def async_setup_entry( key="power_apparent_phase_3", name="Power apparent phase 3", native_unit_of_measurement=POWER_VOLT_AMPERE, + device_class=SensorDeviceClass.APPARENT_POWER, state_class=SensorStateClass.MEASUREMENT, icon="mdi:flash-outline", entity_registry_enabled_default=False, @@ -362,6 +365,7 @@ async def async_setup_entry( key="power_apparent", name="Power apparent", native_unit_of_measurement=POWER_VOLT_AMPERE, + device_class=SensorDeviceClass.APPARENT_POWER, state_class=SensorStateClass.MEASUREMENT, icon="mdi:flash-outline", entity_registry_enabled_default=False, @@ -397,6 +401,7 @@ async def async_setup_entry( key="power_reactive_phase_1", name="Power reactive phase 1", native_unit_of_measurement=POWER_VOLT_AMPERE_REACTIVE, + device_class=SensorDeviceClass.REACTIVE_POWER, state_class=SensorStateClass.MEASUREMENT, icon="mdi:flash-outline", entity_registry_enabled_default=False, @@ -405,6 +410,7 @@ async def async_setup_entry( key="power_reactive_phase_2", name="Power reactive phase 2", native_unit_of_measurement=POWER_VOLT_AMPERE_REACTIVE, + device_class=SensorDeviceClass.REACTIVE_POWER, state_class=SensorStateClass.MEASUREMENT, icon="mdi:flash-outline", entity_registry_enabled_default=False, @@ -413,6 +419,7 @@ async def async_setup_entry( key="power_reactive_phase_3", name="Power reactive phase 3", native_unit_of_measurement=POWER_VOLT_AMPERE_REACTIVE, + device_class=SensorDeviceClass.REACTIVE_POWER, state_class=SensorStateClass.MEASUREMENT, icon="mdi:flash-outline", entity_registry_enabled_default=False, @@ -421,6 +428,7 @@ async def async_setup_entry( key="power_reactive", name="Power reactive", native_unit_of_measurement=POWER_VOLT_AMPERE_REACTIVE, + device_class=SensorDeviceClass.REACTIVE_POWER, state_class=SensorStateClass.MEASUREMENT, icon="mdi:flash-outline", entity_registry_enabled_default=False, From d15acaf747c63256a6825ab2232736d3e3188a28 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Mon, 21 Feb 2022 22:50:50 +0100 Subject: [PATCH 0905/1098] Implement number platform for Sensibo (#66898) --- .coveragerc | 1 + homeassistant/components/sensibo/const.py | 13 ++- homeassistant/components/sensibo/number.py | 130 +++++++++++++++++++++ 3 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/sensibo/number.py diff --git a/.coveragerc b/.coveragerc index 654e1a3e4d7f3f..7bee7669e1670a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1038,6 +1038,7 @@ omit = homeassistant/components/sensibo/climate.py homeassistant/components/sensibo/coordinator.py homeassistant/components/sensibo/diagnostics.py + homeassistant/components/sensibo/number.py homeassistant/components/serial/sensor.py homeassistant/components/serial_pm/sensor.py homeassistant/components/sesame/lock.py diff --git a/homeassistant/components/sensibo/const.py b/homeassistant/components/sensibo/const.py index e6d5bebff426aa..683a403cb082e2 100644 --- a/homeassistant/components/sensibo/const.py +++ b/homeassistant/components/sensibo/const.py @@ -1,14 +1,25 @@ """Constants for Sensibo.""" +import asyncio import logging +from aiohttp.client_exceptions import ClientConnectionError +from pysensibo.exceptions import AuthenticationError, SensiboError + from homeassistant.const import Platform LOGGER = logging.getLogger(__package__) DEFAULT_SCAN_INTERVAL = 60 DOMAIN = "sensibo" -PLATFORMS = [Platform.CLIMATE] +PLATFORMS = [Platform.CLIMATE, Platform.NUMBER] ALL = ["all"] DEFAULT_NAME = "Sensibo" TIMEOUT = 8 + +SENSIBO_ERRORS = ( + ClientConnectionError, + asyncio.TimeoutError, + AuthenticationError, + SensiboError, +) diff --git a/homeassistant/components/sensibo/number.py b/homeassistant/components/sensibo/number.py new file mode 100644 index 00000000000000..eff953592ac426 --- /dev/null +++ b/homeassistant/components/sensibo/number.py @@ -0,0 +1,130 @@ +"""Number platform for Sensibo integration.""" +from __future__ import annotations + +from dataclasses import dataclass + +import async_timeout + +from homeassistant.components.number import NumberEntity, NumberEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.entity import DeviceInfo, EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN, LOGGER, SENSIBO_ERRORS, TIMEOUT +from .coordinator import SensiboDataUpdateCoordinator + + +@dataclass +class SensiboEntityDescriptionMixin: + """Mixin values for Sensibo entities.""" + + remote_key: str + + +@dataclass +class SensiboNumberEntityDescription( + NumberEntityDescription, SensiboEntityDescriptionMixin +): + """Class describing Sensibo Number entities.""" + + +NUMBER_TYPES = ( + SensiboNumberEntityDescription( + key="calibration_temp", + remote_key="temperature", + name="Temperature calibration", + icon="mdi:thermometer", + entity_category=EntityCategory.CONFIG, + entity_registry_enabled_default=False, + min_value=-10, + max_value=10, + step=0.1, + ), + SensiboNumberEntityDescription( + key="calibration_hum", + remote_key="humidity", + name="Humidity calibration", + icon="mdi:water", + entity_category=EntityCategory.CONFIG, + entity_registry_enabled_default=False, + min_value=-10, + max_value=10, + step=0.1, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up Sensibo number platform.""" + + coordinator: SensiboDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + async_add_entities( + SensiboNumber(coordinator, device_id, description) + for device_id, device_data in coordinator.data.items() + for description in NUMBER_TYPES + if device_data["hvac_modes"] and device_data["temp"] + ) + + +class SensiboNumber(CoordinatorEntity, NumberEntity): + """Representation of a Sensibo numbers.""" + + coordinator: SensiboDataUpdateCoordinator + entity_description: SensiboNumberEntityDescription + + def __init__( + self, + coordinator: SensiboDataUpdateCoordinator, + device_id: str, + entity_description: SensiboNumberEntityDescription, + ) -> None: + """Initiate Sensibo Number.""" + super().__init__(coordinator) + self.entity_description = entity_description + self._device_id = device_id + self._client = coordinator.client + self._attr_unique_id = f"{device_id}-{entity_description.key}" + self._attr_name = ( + f"{coordinator.data[device_id]['name']} {entity_description.name}" + ) + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, coordinator.data[device_id]["id"])}, + name=coordinator.data[device_id]["name"], + manufacturer="Sensibo", + configuration_url="https://home.sensibo.com/", + model=coordinator.data[device_id]["model"], + sw_version=coordinator.data[device_id]["fw_ver"], + hw_version=coordinator.data[device_id]["fw_type"], + suggested_area=coordinator.data[device_id]["name"], + ) + + @property + def value(self) -> float: + """Return the value from coordinator data.""" + return self.coordinator.data[self._device_id][self.entity_description.key] + + async def async_set_value(self, value: float) -> None: + """Set value for calibration.""" + data = {self.entity_description.remote_key: value} + try: + async with async_timeout.timeout(TIMEOUT): + result = await self._client.async_set_calibration( + self._device_id, + data, + ) + except SENSIBO_ERRORS as err: + raise HomeAssistantError( + f"Failed to set calibration for device {self.name} to Sensibo servers: {err}" + ) from err + LOGGER.debug("Result: %s", result) + if result["status"] == "success": + self.coordinator.data[self._device_id][self.entity_description.key] = value + self.async_write_ha_state() + return + raise HomeAssistantError(f"Could not set calibration for device {self.name}") From d45921622a13d632f4d9bc562b5cdd885a3efa44 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Mon, 21 Feb 2022 13:57:28 -0800 Subject: [PATCH 0906/1098] Update pyoverkiz to 1.3.6 (#66997) --- homeassistant/components/overkiz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/overkiz/manifest.json b/homeassistant/components/overkiz/manifest.json index 7ad11809e6a70b..833441442b2f67 100644 --- a/homeassistant/components/overkiz/manifest.json +++ b/homeassistant/components/overkiz/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/overkiz", "requirements": [ - "pyoverkiz==1.3.5" + "pyoverkiz==1.3.6" ], "zeroconf": [ { diff --git a/requirements_all.txt b/requirements_all.txt index 1d3e91cd65d73f..52eccbb12711e3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1749,7 +1749,7 @@ pyotgw==1.1b1 pyotp==2.6.0 # homeassistant.components.overkiz -pyoverkiz==1.3.5 +pyoverkiz==1.3.6 # homeassistant.components.openweathermap pyowm==3.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1ad42575a89a97..f7c226536e20c2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1121,7 +1121,7 @@ pyotgw==1.1b1 pyotp==2.6.0 # homeassistant.components.overkiz -pyoverkiz==1.3.5 +pyoverkiz==1.3.6 # homeassistant.components.openweathermap pyowm==3.2.0 From ba2bc975f46dad10b200ad10584a54d9fdb7587c Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Tue, 22 Feb 2022 00:03:22 +0200 Subject: [PATCH 0907/1098] Fix Shelly event handling (#67000) --- homeassistant/components/shelly/__init__.py | 24 ++++++++++----------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index d60a8aabb1a946..b29079affcf030 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -681,19 +681,17 @@ def _async_device_updates_handler(self) -> None: ENTRY_RELOAD_COOLDOWN, ) self.hass.async_create_task(self._debounced_reload.async_call()) - elif event_type not in RPC_INPUTS_EVENTS_TYPES: - continue - - self.hass.bus.async_fire( - EVENT_SHELLY_CLICK, - { - ATTR_DEVICE_ID: self.device_id, - ATTR_DEVICE: self.device.hostname, - ATTR_CHANNEL: event["id"] + 1, - ATTR_CLICK_TYPE: event["event"], - ATTR_GENERATION: 2, - }, - ) + elif event_type in RPC_INPUTS_EVENTS_TYPES: + self.hass.bus.async_fire( + EVENT_SHELLY_CLICK, + { + ATTR_DEVICE_ID: self.device_id, + ATTR_DEVICE: self.device.hostname, + ATTR_CHANNEL: event["id"] + 1, + ATTR_CLICK_TYPE: event["event"], + ATTR_GENERATION: 2, + }, + ) async def _async_update_data(self) -> None: """Fetch data.""" From e6af7847fc6106f069d556e6d76a55e1a8f600b7 Mon Sep 17 00:00:00 2001 From: Keilin Bickar Date: Mon, 21 Feb 2022 17:05:12 -0500 Subject: [PATCH 0908/1098] Add Multi factor authentication support for Sense (#66498) Co-authored-by: J. Nick Koston --- .../components/emulated_kasa/manifest.json | 2 +- homeassistant/components/sense/__init__.py | 39 ++-- homeassistant/components/sense/config_flow.py | 127 ++++++++--- homeassistant/components/sense/manifest.json | 2 +- homeassistant/components/sense/strings.json | 16 +- .../components/sense/translations/en.json | 16 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/sense/test_config_flow.py | 206 +++++++++++++++++- 9 files changed, 357 insertions(+), 55 deletions(-) diff --git a/homeassistant/components/emulated_kasa/manifest.json b/homeassistant/components/emulated_kasa/manifest.json index 8506ad75e3f6df..c11fecb3ff16d7 100644 --- a/homeassistant/components/emulated_kasa/manifest.json +++ b/homeassistant/components/emulated_kasa/manifest.json @@ -2,7 +2,7 @@ "domain": "emulated_kasa", "name": "Emulated Kasa", "documentation": "https://www.home-assistant.io/integrations/emulated_kasa", - "requirements": ["sense_energy==0.9.6"], + "requirements": ["sense_energy==0.10.2"], "codeowners": ["@kbickar"], "quality_scale": "internal", "iot_class": "local_push", diff --git a/homeassistant/components/sense/__init__.py b/homeassistant/components/sense/__init__.py index edc5cd0823e01d..aaf3630ae19460 100644 --- a/homeassistant/components/sense/__init__.py +++ b/homeassistant/components/sense/__init__.py @@ -3,18 +3,21 @@ from datetime import timedelta import logging -from sense_energy import ASyncSenseable, SenseAuthenticationException +from sense_energy import ( + ASyncSenseable, + SenseAuthenticationException, + SenseMFARequiredException, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_EMAIL, - CONF_PASSWORD, CONF_TIMEOUT, EVENT_HOMEASSISTANT_STOP, Platform, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_track_time_interval @@ -58,9 +61,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entry_data = entry.data email = entry_data[CONF_EMAIL] - password = entry_data[CONF_PASSWORD] timeout = entry_data[CONF_TIMEOUT] + access_token = entry_data.get("access_token", "") + user_id = entry_data.get("user_id", "") + monitor_id = entry_data.get("monitor_id", "") + client_session = async_get_clientsession(hass) gateway = ASyncSenseable( @@ -69,16 +75,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: gateway.rate_limit = ACTIVE_UPDATE_RATE try: - await gateway.authenticate(email, password) - except SenseAuthenticationException: - _LOGGER.error("Could not authenticate with sense server") - return False - except SENSE_TIMEOUT_EXCEPTIONS as err: - raise ConfigEntryNotReady( - str(err) or "Timed out during authentication" - ) from err - except SENSE_EXCEPTIONS as err: - raise ConfigEntryNotReady(str(err) or "Error during authentication") from err + gateway.load_auth(access_token, user_id, monitor_id) + await gateway.get_monitor_data() + except (SenseAuthenticationException, SenseMFARequiredException) as err: + _LOGGER.warning("Sense authentication expired") + raise ConfigEntryAuthFailed(err) from err sense_devices_data = SenseDevicesData() try: @@ -91,11 +92,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: except SENSE_EXCEPTIONS as err: raise ConfigEntryNotReady(str(err) or "Error during realtime update") from err + async def _async_update_trend(): + """Update the trend data.""" + try: + await gateway.update_trend_data() + except (SenseAuthenticationException, SenseMFARequiredException) as err: + _LOGGER.warning("Sense authentication expired") + raise ConfigEntryAuthFailed(err) from err + trends_coordinator: DataUpdateCoordinator[None] = DataUpdateCoordinator( hass, _LOGGER, name=f"Sense Trends {email}", - update_method=gateway.update_trend_data, + update_method=_async_update_trend, update_interval=timedelta(seconds=300), ) # Start out as unavailable so we do not report 0 data diff --git a/homeassistant/components/sense/config_flow.py b/homeassistant/components/sense/config_flow.py index 6bd33291d7f5c4..eea36424662443 100644 --- a/homeassistant/components/sense/config_flow.py +++ b/homeassistant/components/sense/config_flow.py @@ -1,11 +1,15 @@ """Config flow for Sense integration.""" import logging -from sense_energy import ASyncSenseable, SenseAuthenticationException +from sense_energy import ( + ASyncSenseable, + SenseAuthenticationException, + SenseMFARequiredException, +) import voluptuous as vol -from homeassistant import config_entries, core -from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_TIMEOUT +from homeassistant import config_entries +from homeassistant.const import CONF_CODE, CONF_EMAIL, CONF_PASSWORD, CONF_TIMEOUT from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ACTIVE_UPDATE_RATE, DEFAULT_TIMEOUT, DOMAIN, SENSE_TIMEOUT_EXCEPTIONS @@ -21,37 +25,74 @@ ) -async def validate_input(hass: core.HomeAssistant, data): - """Validate the user input allows us to connect. +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Sense.""" - Data has the keys from DATA_SCHEMA with values provided by the user. - """ - timeout = data[CONF_TIMEOUT] - client_session = async_get_clientsession(hass) + VERSION = 1 - gateway = ASyncSenseable( - api_timeout=timeout, wss_timeout=timeout, client_session=client_session - ) - gateway.rate_limit = ACTIVE_UPDATE_RATE - await gateway.authenticate(data[CONF_EMAIL], data[CONF_PASSWORD]) + def __init__(self): + """Init Config .""" + self._gateway = None + self._auth_data = {} + super().__init__() - # Return info that you want to store in the config entry. - return {"title": data[CONF_EMAIL]} + async def validate_input(self, data): + """Validate the user input allows us to connect. + Data has the keys from DATA_SCHEMA with values provided by the user. + """ + self._auth_data.update(dict(data)) + timeout = self._auth_data[CONF_TIMEOUT] + client_session = async_get_clientsession(self.hass) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): - """Handle a config flow for Sense.""" + self._gateway = ASyncSenseable( + api_timeout=timeout, wss_timeout=timeout, client_session=client_session + ) + self._gateway.rate_limit = ACTIVE_UPDATE_RATE + await self._gateway.authenticate( + self._auth_data[CONF_EMAIL], self._auth_data[CONF_PASSWORD] + ) - VERSION = 1 + async def create_entry_from_data(self): + """Create the entry from the config data.""" + self._auth_data["access_token"] = self._gateway.sense_access_token + self._auth_data["user_id"] = self._gateway.sense_user_id + self._auth_data["monitor_id"] = self._gateway.sense_monitor_id + existing_entry = await self.async_set_unique_id(self._auth_data[CONF_EMAIL]) + if not existing_entry: + return self.async_create_entry( + title=self._auth_data[CONF_EMAIL], data=self._auth_data + ) - async def async_step_user(self, user_input=None): - """Handle the initial step.""" + self.hass.config_entries.async_update_entry( + existing_entry, data=self._auth_data + ) + await self.hass.config_entries.async_reload(existing_entry.entry_id) + return self.async_abort(reason="reauth_successful") + + async def validate_input_and_create_entry(self, user_input, errors): + """Validate the input and create the entry from the data.""" + try: + await self.validate_input(user_input) + except SenseMFARequiredException: + return await self.async_step_validation() + except SENSE_TIMEOUT_EXCEPTIONS: + errors["base"] = "cannot_connect" + except SenseAuthenticationException: + errors["base"] = "invalid_auth" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + else: + return await self.create_entry_from_data() + return None + + async def async_step_validation(self, user_input=None): + """Handle validation (2fa) step.""" errors = {} - if user_input is not None: + if user_input: try: - info = await validate_input(self.hass, user_input) - await self.async_set_unique_id(user_input[CONF_EMAIL]) - return self.async_create_entry(title=info["title"], data=user_input) + await self._gateway.validate_mfa(user_input[CONF_CODE]) except SENSE_TIMEOUT_EXCEPTIONS: errors["base"] = "cannot_connect" except SenseAuthenticationException: @@ -59,7 +100,43 @@ async def async_step_user(self, user_input=None): except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" + else: + return await self.create_entry_from_data() + + return self.async_show_form( + step_id="validation", + data_schema=vol.Schema({vol.Required(CONF_CODE): vol.All(str, vol.Strip)}), + errors=errors, + ) + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + errors = {} + if user_input is not None: + if result := await self.validate_input_and_create_entry(user_input, errors): + return result return self.async_show_form( step_id="user", data_schema=DATA_SCHEMA, errors=errors ) + + async def async_step_reauth(self, data): + """Handle configuration by re-auth.""" + self._auth_data = dict(data) + return await self.async_step_reauth_validate(data) + + async def async_step_reauth_validate(self, user_input=None): + """Handle reauth and validation.""" + errors = {} + if user_input is not None: + if result := await self.validate_input_and_create_entry(user_input, errors): + return result + + return self.async_show_form( + step_id="reauth_validate", + data_schema=vol.Schema({vol.Required(CONF_PASSWORD): str}), + errors=errors, + description_placeholders={ + CONF_EMAIL: self._auth_data[CONF_EMAIL], + }, + ) diff --git a/homeassistant/components/sense/manifest.json b/homeassistant/components/sense/manifest.json index a7ec66d8b83e83..30de722a7bc60a 100644 --- a/homeassistant/components/sense/manifest.json +++ b/homeassistant/components/sense/manifest.json @@ -2,7 +2,7 @@ "domain": "sense", "name": "Sense", "documentation": "https://www.home-assistant.io/integrations/sense", - "requirements": ["sense_energy==0.9.6"], + "requirements": ["sense_energy==0.10.2"], "codeowners": ["@kbickar"], "config_flow": true, "dhcp": [ diff --git a/homeassistant/components/sense/strings.json b/homeassistant/components/sense/strings.json index 29e85c98fc2402..a519155bee1354 100644 --- a/homeassistant/components/sense/strings.json +++ b/homeassistant/components/sense/strings.json @@ -8,6 +8,19 @@ "password": "[%key:common::config_flow::data::password%]", "timeout": "Timeout" } + }, + "validation": { + "title": "Sense Multi-factor authentication", + "data": { + "code": "Verification code" + } + }, + "reauth_validate": { + "title": "[%key:common::config_flow::title::reauth%]", + "description": "The Sense integration needs to re-authenticate your account {email}.", + "data": { + "password": "[%key:common::config_flow::data::password%]" + } } }, "error": { @@ -16,7 +29,8 @@ "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } } } diff --git a/homeassistant/components/sense/translations/en.json b/homeassistant/components/sense/translations/en.json index 24cde7411a8904..fd9a7ade4bfb07 100644 --- a/homeassistant/components/sense/translations/en.json +++ b/homeassistant/components/sense/translations/en.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Device is already configured" + "already_configured": "Device is already configured", + "reauth_successful": "Re-authentication was successful" }, "error": { "cannot_connect": "Failed to connect", @@ -9,6 +10,13 @@ "unknown": "Unexpected error" }, "step": { + "reauth_validate": { + "data": { + "password": "Password" + }, + "description": "The Sense integration needs to re-authenticate your account {email}.", + "title": "Reauthenticate Integration" + }, "user": { "data": { "email": "Email", @@ -16,6 +24,12 @@ "timeout": "Timeout" }, "title": "Connect to your Sense Energy Monitor" + }, + "validation": { + "data": { + "code": "Verification code" + }, + "title": "Sense Multi-factor authentication" } } } diff --git a/requirements_all.txt b/requirements_all.txt index 52eccbb12711e3..93c5a9072ac505 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2178,7 +2178,7 @@ sense-hat==2.2.0 # homeassistant.components.emulated_kasa # homeassistant.components.sense -sense_energy==0.9.6 +sense_energy==0.10.2 # homeassistant.components.sentry sentry-sdk==1.5.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f7c226536e20c2..723daaca0cd80b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1346,7 +1346,7 @@ screenlogicpy==0.5.4 # homeassistant.components.emulated_kasa # homeassistant.components.sense -sense_energy==0.9.6 +sense_energy==0.10.2 # homeassistant.components.sentry sentry-sdk==1.5.5 diff --git a/tests/components/sense/test_config_flow.py b/tests/components/sense/test_config_flow.py index 0058c05bf80842..f939142aee435e 100644 --- a/tests/components/sense/test_config_flow.py +++ b/tests/components/sense/test_config_flow.py @@ -1,13 +1,44 @@ """Test the Sense config flow.""" -from unittest.mock import patch +from unittest.mock import AsyncMock, patch -from sense_energy import SenseAPITimeoutException, SenseAuthenticationException +import pytest +from sense_energy import ( + SenseAPITimeoutException, + SenseAuthenticationException, + SenseMFARequiredException, +) from homeassistant import config_entries from homeassistant.components.sense.const import DOMAIN +from homeassistant.const import CONF_CODE +from tests.common import MockConfigEntry -async def test_form(hass): +MOCK_CONFIG = { + "timeout": 6, + "email": "test-email", + "password": "test-password", + "access_token": "ABC", + "user_id": "123", + "monitor_id": "456", +} + + +@pytest.fixture(name="mock_sense") +def mock_sense(): + """Mock Sense object for authenticatation.""" + with patch( + "homeassistant.components.sense.config_flow.ASyncSenseable" + ) as mock_sense: + mock_sense.return_value.authenticate = AsyncMock(return_value=True) + mock_sense.return_value.validate_mfa = AsyncMock(return_value=True) + mock_sense.return_value.sense_access_token = "ABC" + mock_sense.return_value.sense_user_id = "123" + mock_sense.return_value.sense_monitor_id = "456" + yield mock_sense + + +async def test_form(hass, mock_sense): """Test we get the form.""" result = await hass.config_entries.flow.async_init( @@ -16,7 +47,7 @@ async def test_form(hass): assert result["type"] == "form" assert result["errors"] == {} - with patch("sense_energy.ASyncSenseable.authenticate", return_value=True,), patch( + with patch( "homeassistant.components.sense.async_setup_entry", return_value=True, ) as mock_setup_entry: @@ -28,11 +59,7 @@ async def test_form(hass): assert result2["type"] == "create_entry" assert result2["title"] == "test-email" - assert result2["data"] == { - "timeout": 6, - "email": "test-email", - "password": "test-password", - } + assert result2["data"] == MOCK_CONFIG assert len(mock_setup_entry.mock_calls) == 1 @@ -55,6 +82,113 @@ async def test_form_invalid_auth(hass): assert result2["errors"] == {"base": "invalid_auth"} +async def test_form_mfa_required(hass, mock_sense): + """Test we handle invalid auth.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + mock_sense.return_value.authenticate.side_effect = SenseMFARequiredException + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"timeout": "6", "email": "test-email", "password": "test-password"}, + ) + + assert result2["type"] == "form" + assert result2["step_id"] == "validation" + + mock_sense.return_value.validate_mfa.side_effect = None + result3 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_CODE: "012345"}, + ) + + assert result3["type"] == "create_entry" + assert result3["title"] == "test-email" + assert result3["data"] == MOCK_CONFIG + + +async def test_form_mfa_required_wrong(hass, mock_sense): + """Test we handle invalid auth.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + mock_sense.return_value.authenticate.side_effect = SenseMFARequiredException + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"timeout": "6", "email": "test-email", "password": "test-password"}, + ) + + assert result2["type"] == "form" + assert result2["step_id"] == "validation" + + mock_sense.return_value.validate_mfa.side_effect = SenseAuthenticationException + # Try with the WRONG verification code give us the form back again + result3 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_CODE: "000000"}, + ) + + assert result3["type"] == "form" + assert result3["errors"] == {"base": "invalid_auth"} + assert result3["step_id"] == "validation" + + +async def test_form_mfa_required_timeout(hass, mock_sense): + """Test we handle invalid auth.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + mock_sense.return_value.authenticate.side_effect = SenseMFARequiredException + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"timeout": "6", "email": "test-email", "password": "test-password"}, + ) + + assert result2["type"] == "form" + assert result2["step_id"] == "validation" + + mock_sense.return_value.validate_mfa.side_effect = SenseAPITimeoutException + result3 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_CODE: "000000"}, + ) + + assert result3["type"] == "form" + assert result3["errors"] == {"base": "cannot_connect"} + + +async def test_form_mfa_required_exception(hass, mock_sense): + """Test we handle invalid auth.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + mock_sense.return_value.authenticate.side_effect = SenseMFARequiredException + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"timeout": "6", "email": "test-email", "password": "test-password"}, + ) + + assert result2["type"] == "form" + assert result2["step_id"] == "validation" + + mock_sense.return_value.validate_mfa.side_effect = Exception + result3 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_CODE: "000000"}, + ) + + assert result3["type"] == "form" + assert result3["errors"] == {"base": "unknown"} + + async def test_form_cannot_connect(hass): """Test we handle cannot connect error.""" result = await hass.config_entries.flow.async_init( @@ -91,3 +225,57 @@ async def test_form_unknown_exception(hass): assert result2["type"] == "form" assert result2["errors"] == {"base": "unknown"} + + +async def test_reauth_no_form(hass, mock_sense): + """Test reauth where no form needed.""" + + # set up initially + entry = MockConfigEntry( + domain=DOMAIN, + data=MOCK_CONFIG, + unique_id="test-email", + ) + entry.add_to_hass(hass) + with patch( + "homeassistant.config_entries.ConfigEntries.async_reload", + return_value=True, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data=MOCK_CONFIG + ) + assert result["type"] == "abort" + assert result["reason"] == "reauth_successful" + + +async def test_reauth_password(hass, mock_sense): + """Test reauth form.""" + + # set up initially + entry = MockConfigEntry( + domain=DOMAIN, + data=MOCK_CONFIG, + unique_id="test-email", + ) + entry.add_to_hass(hass) + mock_sense.return_value.authenticate.side_effect = SenseAuthenticationException + + # Reauth success without user input + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data=entry.data + ) + assert result["type"] == "form" + + mock_sense.return_value.authenticate.side_effect = None + with patch( + "homeassistant.components.sense.async_setup_entry", + return_value=True, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"password": "test-password"}, + ) + await hass.async_block_till_done() + + assert result2["type"] == "abort" + assert result2["reason"] == "reauth_successful" From 137793c06748b3914ae4906c9d11599dbd83d1fd Mon Sep 17 00:00:00 2001 From: corneyl Date: Mon, 21 Feb 2022 23:45:30 +0100 Subject: [PATCH 0909/1098] Add sensors for next Picnic deliveries (#66474) --- homeassistant/components/picnic/const.py | 69 +++++++---- .../components/picnic/coordinator.py | 43 ++++--- tests/components/picnic/test_sensor.py | 110 ++++++++++++++---- 3 files changed, 162 insertions(+), 60 deletions(-) diff --git a/homeassistant/components/picnic/const.py b/homeassistant/components/picnic/const.py index a97d46e0ad07a7..f33f58c0eb9088 100644 --- a/homeassistant/components/picnic/const.py +++ b/homeassistant/components/picnic/const.py @@ -22,6 +22,7 @@ ADDRESS = "address" CART_DATA = "cart_data" SLOT_DATA = "slot_data" +NEXT_DELIVERY_DATA = "next_delivery_data" LAST_ORDER_DATA = "last_order_data" SENSOR_CART_ITEMS_COUNT = "cart_items_count" @@ -33,18 +34,22 @@ SENSOR_LAST_ORDER_SLOT_START = "last_order_slot_start" SENSOR_LAST_ORDER_SLOT_END = "last_order_slot_end" SENSOR_LAST_ORDER_STATUS = "last_order_status" -SENSOR_LAST_ORDER_ETA_START = "last_order_eta_start" -SENSOR_LAST_ORDER_ETA_END = "last_order_eta_end" SENSOR_LAST_ORDER_MAX_ORDER_TIME = "last_order_max_order_time" SENSOR_LAST_ORDER_DELIVERY_TIME = "last_order_delivery_time" SENSOR_LAST_ORDER_TOTAL_PRICE = "last_order_total_price" +SENSOR_NEXT_DELIVERY_ETA_START = "next_delivery_eta_start" +SENSOR_NEXT_DELIVERY_ETA_END = "next_delivery_eta_end" +SENSOR_NEXT_DELIVERY_SLOT_START = "next_delivery_slot_start" +SENSOR_NEXT_DELIVERY_SLOT_END = "next_delivery_slot_end" @dataclass class PicnicRequiredKeysMixin: """Mixin for required keys.""" - data_type: Literal["cart_data", "slot_data", "last_order_data"] + data_type: Literal[ + "cart_data", "slot_data", "next_delivery_data", "last_order_data" + ] value_fn: Callable[[Any], StateType | datetime] @@ -130,26 +135,6 @@ class PicnicSensorEntityDescription(SensorEntityDescription, PicnicRequiredKeysM data_type="last_order_data", value_fn=lambda last_order: last_order.get("status"), ), - PicnicSensorEntityDescription( - key=SENSOR_LAST_ORDER_ETA_START, - device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:clock-start", - entity_registry_enabled_default=True, - data_type="last_order_data", - value_fn=lambda last_order: dt_util.parse_datetime( - str(last_order.get("eta", {}).get("start")) - ), - ), - PicnicSensorEntityDescription( - key=SENSOR_LAST_ORDER_ETA_END, - device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:clock-end", - entity_registry_enabled_default=True, - data_type="last_order_data", - value_fn=lambda last_order: dt_util.parse_datetime( - str(last_order.get("eta", {}).get("end")) - ), - ), PicnicSensorEntityDescription( key=SENSOR_LAST_ORDER_MAX_ORDER_TIME, device_class=SensorDeviceClass.TIMESTAMP, @@ -177,4 +162,42 @@ class PicnicSensorEntityDescription(SensorEntityDescription, PicnicRequiredKeysM data_type="last_order_data", value_fn=lambda last_order: last_order.get("total_price", 0) / 100, ), + PicnicSensorEntityDescription( + key=SENSOR_NEXT_DELIVERY_ETA_START, + device_class=SensorDeviceClass.TIMESTAMP, + icon="mdi:clock-start", + entity_registry_enabled_default=True, + data_type="next_delivery_data", + value_fn=lambda next_delivery: dt_util.parse_datetime( + str(next_delivery.get("eta", {}).get("start")) + ), + ), + PicnicSensorEntityDescription( + key=SENSOR_NEXT_DELIVERY_ETA_END, + device_class=SensorDeviceClass.TIMESTAMP, + icon="mdi:clock-end", + entity_registry_enabled_default=True, + data_type="next_delivery_data", + value_fn=lambda next_delivery: dt_util.parse_datetime( + str(next_delivery.get("eta", {}).get("end")) + ), + ), + PicnicSensorEntityDescription( + key=SENSOR_NEXT_DELIVERY_SLOT_START, + device_class=SensorDeviceClass.TIMESTAMP, + icon="mdi:calendar-start", + data_type="next_delivery_data", + value_fn=lambda next_delivery: dt_util.parse_datetime( + str(next_delivery.get("slot", {}).get("window_start")) + ), + ), + PicnicSensorEntityDescription( + key=SENSOR_NEXT_DELIVERY_SLOT_END, + device_class=SensorDeviceClass.TIMESTAMP, + icon="mdi:calendar-end", + data_type="next_delivery_data", + value_fn=lambda next_delivery: dt_util.parse_datetime( + str(next_delivery.get("slot", {}).get("window_end")) + ), + ), ) diff --git a/homeassistant/components/picnic/coordinator.py b/homeassistant/components/picnic/coordinator.py index 24f3086134f869..773142a01093e9 100644 --- a/homeassistant/components/picnic/coordinator.py +++ b/homeassistant/components/picnic/coordinator.py @@ -13,7 +13,7 @@ from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import ADDRESS, CART_DATA, LAST_ORDER_DATA, SLOT_DATA +from .const import ADDRESS, CART_DATA, LAST_ORDER_DATA, NEXT_DELIVERY_DATA, SLOT_DATA class PicnicUpdateCoordinator(DataUpdateCoordinator): @@ -62,13 +62,14 @@ def fetch_data(self): if not (cart := self.picnic_api_client.get_cart()): raise UpdateFailed("API response doesn't contain expected data.") - last_order = self._get_last_order() + next_delivery, last_order = self._get_order_data() slot_data = self._get_slot_data(cart) return { ADDRESS: self._get_address(), CART_DATA: cart, SLOT_DATA: slot_data, + NEXT_DELIVERY_DATA: next_delivery, LAST_ORDER_DATA: last_order, } @@ -96,47 +97,55 @@ def _get_slot_data(cart: dict) -> dict: return {} - def _get_last_order(self) -> dict: + def _get_order_data(self) -> tuple[dict, dict]: """Get data of the last order from the list of deliveries.""" # Get the deliveries deliveries = self.picnic_api_client.get_deliveries(summary=True) # Determine the last order and return an empty dict if there is none try: + # Filter on status CURRENT and select the last on the list which is the first one to be delivered + # Make a deepcopy because some references are local + next_deliveries = list( + filter(lambda d: d["status"] == "CURRENT", deliveries) + ) + next_delivery = ( + copy.deepcopy(next_deliveries[-1]) if next_deliveries else {} + ) last_order = copy.deepcopy(deliveries[0]) - except KeyError: - return {} + except (KeyError, TypeError): + # A KeyError or TypeError indicate that the response contains unexpected data + return {}, {} - # Get the position details if the order is not delivered yet + # Get the next order's position details if there is an undelivered order delivery_position = {} - if not last_order.get("delivery_time"): + if next_delivery and not next_delivery.get("delivery_time"): try: delivery_position = self.picnic_api_client.get_delivery_position( - last_order["delivery_id"] + next_delivery["delivery_id"] ) except ValueError: # No information yet can mean an empty response pass # Determine the ETA, if available, the one from the delivery position API is more precise - # but it's only available shortly before the actual delivery. - last_order["eta"] = delivery_position.get( - "eta_window", last_order.get("eta2", {}) + # but, it's only available shortly before the actual delivery. + next_delivery["eta"] = delivery_position.get( + "eta_window", next_delivery.get("eta2", {}) ) + if "eta2" in next_delivery: + del next_delivery["eta2"] # Determine the total price by adding up the total price of all sub-orders total_price = 0 for order in last_order.get("orders", []): total_price += order.get("total_price", 0) - - # Sanitise the object last_order["total_price"] = total_price + + # Make sure delivery_time is a dict last_order.setdefault("delivery_time", {}) - if "eta2" in last_order: - del last_order["eta2"] - # Make a copy because some references are local - return last_order + return next_delivery, last_order @callback def _update_auth_token(self): diff --git a/tests/components/picnic/test_sensor.py b/tests/components/picnic/test_sensor.py index a4a52e50453eb8..7b1bdeb1d129d0 100644 --- a/tests/components/picnic/test_sensor.py +++ b/tests/components/picnic/test_sensor.py @@ -239,27 +239,37 @@ async def test_sensors_setup(self): ) self._assert_sensor("sensor.picnic_last_order_status", "COMPLETED") self._assert_sensor( - "sensor.picnic_last_order_eta_start", - "2021-02-26T19:54:00+00:00", + "sensor.picnic_last_order_max_order_time", + "2021-02-25T21:00:00+00:00", cls=SensorDeviceClass.TIMESTAMP, ) self._assert_sensor( - "sensor.picnic_last_order_eta_end", - "2021-02-26T20:14:00+00:00", + "sensor.picnic_last_order_delivery_time", + "2021-02-26T19:54:05+00:00", cls=SensorDeviceClass.TIMESTAMP, ) self._assert_sensor( - "sensor.picnic_last_order_max_order_time", - "2021-02-25T21:00:00+00:00", + "sensor.picnic_last_order_total_price", "41.33", unit=CURRENCY_EURO + ) + self._assert_sensor( + "sensor.picnic_next_delivery_eta_start", + "unknown", cls=SensorDeviceClass.TIMESTAMP, ) self._assert_sensor( - "sensor.picnic_last_order_delivery_time", - "2021-02-26T19:54:05+00:00", + "sensor.picnic_next_delivery_eta_end", + "unknown", cls=SensorDeviceClass.TIMESTAMP, ) self._assert_sensor( - "sensor.picnic_last_order_total_price", "41.33", unit=CURRENCY_EURO + "sensor.picnic_next_delivery_slot_start", + "unknown", + cls=SensorDeviceClass.TIMESTAMP, + ) + self._assert_sensor( + "sensor.picnic_next_delivery_slot_end", + "unknown", + cls=SensorDeviceClass.TIMESTAMP, ) async def test_sensors_setup_disabled_by_default(self): @@ -271,6 +281,8 @@ async def test_sensors_setup_disabled_by_default(self): self._assert_sensor("sensor.picnic_last_order_slot_end", disabled=True) self._assert_sensor("sensor.picnic_last_order_status", disabled=True) self._assert_sensor("sensor.picnic_last_order_total_price", disabled=True) + self._assert_sensor("sensor.picnic_next_delivery_slot_start", disabled=True) + self._assert_sensor("sensor.picnic_next_delivery_slot_end", disabled=True) async def test_sensors_no_selected_time_slot(self): """Test sensor states with no explicit selected time slot.""" @@ -295,11 +307,12 @@ async def test_sensors_no_selected_time_slot(self): "sensor.picnic_selected_slot_min_order_value", STATE_UNKNOWN ) - async def test_sensors_last_order_in_future(self): + async def test_next_delivery_sensors(self): """Test sensor states when last order is not yet delivered.""" # Adjust default delivery response delivery_response = copy.deepcopy(DEFAULT_DELIVERY_RESPONSE) del delivery_response["delivery_time"] + delivery_response["status"] = "CURRENT" # Set mock responses self.picnic_mock().get_user.return_value = copy.deepcopy(DEFAULT_USER_RESPONSE) @@ -311,10 +324,16 @@ async def test_sensors_last_order_in_future(self): # Assert delivery time is not available, but eta is self._assert_sensor("sensor.picnic_last_order_delivery_time", STATE_UNKNOWN) self._assert_sensor( - "sensor.picnic_last_order_eta_start", "2021-02-26T19:54:00+00:00" + "sensor.picnic_next_delivery_eta_start", "2021-02-26T19:54:00+00:00" ) self._assert_sensor( - "sensor.picnic_last_order_eta_end", "2021-02-26T20:14:00+00:00" + "sensor.picnic_next_delivery_eta_end", "2021-02-26T20:14:00+00:00" + ) + self._assert_sensor( + "sensor.picnic_next_delivery_slot_start", "2021-02-26T19:15:00+00:00" + ) + self._assert_sensor( + "sensor.picnic_next_delivery_slot_end", "2021-02-26T20:15:00+00:00" ) async def test_sensors_eta_date_malformed(self): @@ -329,12 +348,13 @@ async def test_sensors_eta_date_malformed(self): } delivery_response = copy.deepcopy(DEFAULT_DELIVERY_RESPONSE) delivery_response["eta2"] = eta_dates + delivery_response["status"] = "CURRENT" self.picnic_mock().get_deliveries.return_value = [delivery_response] await self._coordinator.async_refresh() # Assert eta times are not available due to malformed date strings - self._assert_sensor("sensor.picnic_last_order_eta_start", STATE_UNKNOWN) - self._assert_sensor("sensor.picnic_last_order_eta_end", STATE_UNKNOWN) + self._assert_sensor("sensor.picnic_next_delivery_eta_start", STATE_UNKNOWN) + self._assert_sensor("sensor.picnic_next_delivery_eta_end", STATE_UNKNOWN) async def test_sensors_use_detailed_eta_if_available(self): """Test sensor states when last order is not yet delivered.""" @@ -344,6 +364,7 @@ async def test_sensors_use_detailed_eta_if_available(self): # Provide a delivery position response with different ETA and remove delivery time from response delivery_response = copy.deepcopy(DEFAULT_DELIVERY_RESPONSE) del delivery_response["delivery_time"] + delivery_response["status"] = "CURRENT" self.picnic_mock().get_deliveries.return_value = [delivery_response] self.picnic_mock().get_delivery_position.return_value = { "eta_window": { @@ -358,10 +379,10 @@ async def test_sensors_use_detailed_eta_if_available(self): delivery_response["delivery_id"] ) self._assert_sensor( - "sensor.picnic_last_order_eta_start", "2021-03-05T10:19:20+00:00" + "sensor.picnic_next_delivery_eta_start", "2021-03-05T10:19:20+00:00" ) self._assert_sensor( - "sensor.picnic_last_order_eta_end", "2021-03-05T10:39:20+00:00" + "sensor.picnic_next_delivery_eta_end", "2021-03-05T10:39:20+00:00" ) async def test_sensors_no_data(self): @@ -387,12 +408,12 @@ async def test_sensors_no_data(self): self._assert_sensor( "sensor.picnic_selected_slot_min_order_value", STATE_UNAVAILABLE ) - self._assert_sensor("sensor.picnic_last_order_eta_start", STATE_UNAVAILABLE) - self._assert_sensor("sensor.picnic_last_order_eta_end", STATE_UNAVAILABLE) self._assert_sensor( "sensor.picnic_last_order_max_order_time", STATE_UNAVAILABLE ) self._assert_sensor("sensor.picnic_last_order_delivery_time", STATE_UNAVAILABLE) + self._assert_sensor("sensor.picnic_next_delivery_eta_start", STATE_UNAVAILABLE) + self._assert_sensor("sensor.picnic_next_delivery_eta_end", STATE_UNAVAILABLE) async def test_sensors_malformed_delivery_data(self): """Test sensor states when the delivery api returns not a list.""" @@ -405,10 +426,10 @@ async def test_sensors_malformed_delivery_data(self): # Assert all last-order sensors have STATE_UNAVAILABLE because the delivery info fetch failed assert self._coordinator.last_update_success is True - self._assert_sensor("sensor.picnic_last_order_eta_start", STATE_UNKNOWN) - self._assert_sensor("sensor.picnic_last_order_eta_end", STATE_UNKNOWN) self._assert_sensor("sensor.picnic_last_order_max_order_time", STATE_UNKNOWN) self._assert_sensor("sensor.picnic_last_order_delivery_time", STATE_UNKNOWN) + self._assert_sensor("sensor.picnic_next_delivery_eta_start", STATE_UNKNOWN) + self._assert_sensor("sensor.picnic_next_delivery_eta_end", STATE_UNKNOWN) async def test_sensors_malformed_response(self): """Test coordinator update fails when API yields ValueError.""" @@ -423,6 +444,55 @@ async def test_sensors_malformed_response(self): # Assert coordinator update failed assert self._coordinator.last_update_success is False + async def test_multiple_active_orders(self): + """Test that the sensors get the right values when there are multiple active orders.""" + # Create 2 undelivered orders + undelivered_order = copy.deepcopy(DEFAULT_DELIVERY_RESPONSE) + del undelivered_order["delivery_time"] + undelivered_order["status"] = "CURRENT" + undelivered_order["slot"]["window_start"] = "2022-03-01T09:15:00.000+01:00" + undelivered_order["slot"]["window_end"] = "2022-03-01T10:15:00.000+01:00" + undelivered_order["eta2"]["start"] = "2022-03-01T09:30:00.000+01:00" + undelivered_order["eta2"]["end"] = "2022-03-01T09:45:00.000+01:00" + + undelivered_order_2 = copy.deepcopy(undelivered_order) + undelivered_order_2["slot"]["window_start"] = "2022-03-08T13:15:00.000+01:00" + undelivered_order_2["slot"]["window_end"] = "2022-03-08T14:15:00.000+01:00" + undelivered_order_2["eta2"]["start"] = "2022-03-08T13:30:00.000+01:00" + undelivered_order_2["eta2"]["end"] = "2022-03-08T13:45:00.000+01:00" + + deliveries_response = [ + undelivered_order_2, + undelivered_order, + copy.deepcopy(DEFAULT_DELIVERY_RESPONSE), + ] + + # Set mock responses + self.picnic_mock().get_user.return_value = copy.deepcopy(DEFAULT_USER_RESPONSE) + self.picnic_mock().get_cart.return_value = copy.deepcopy(DEFAULT_CART_RESPONSE) + self.picnic_mock().get_deliveries.return_value = deliveries_response + self.picnic_mock().get_delivery_position.return_value = {} + await self._setup_platform() + + self._assert_sensor( + "sensor.picnic_last_order_slot_start", "2022-03-08T12:15:00+00:00" + ) + self._assert_sensor( + "sensor.picnic_last_order_slot_end", "2022-03-08T13:15:00+00:00" + ) + self._assert_sensor( + "sensor.picnic_next_delivery_slot_start", "2022-03-01T08:15:00+00:00" + ) + self._assert_sensor( + "sensor.picnic_next_delivery_slot_end", "2022-03-01T09:15:00+00:00" + ) + self._assert_sensor( + "sensor.picnic_next_delivery_eta_start", "2022-03-01T08:30:00+00:00" + ) + self._assert_sensor( + "sensor.picnic_next_delivery_eta_end", "2022-03-01T08:45:00+00:00" + ) + async def test_device_registry_entry(self): """Test if device registry entry is populated correctly.""" # Setup platform and default mock responses From 8741ff068489d382309774ce58eaf2ce71f3b15c Mon Sep 17 00:00:00 2001 From: jan iversen Date: Mon, 21 Feb 2022 23:56:31 +0100 Subject: [PATCH 0910/1098] Diferentiate between attr_name and entity_id in Modbus tests (#66999) --- homeassistant/components/modbus/binary_sensor.py | 2 +- tests/components/modbus/conftest.py | 2 +- tests/components/modbus/test_binary_sensor.py | 8 ++++---- tests/components/modbus/test_climate.py | 2 +- tests/components/modbus/test_cover.py | 6 +++--- tests/components/modbus/test_fan.py | 6 +++--- tests/components/modbus/test_init.py | 16 ++++++++-------- tests/components/modbus/test_light.py | 6 +++--- tests/components/modbus/test_sensor.py | 2 +- tests/components/modbus/test_switch.py | 6 +++--- 10 files changed, 28 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/modbus/binary_sensor.py b/homeassistant/components/modbus/binary_sensor.py index f65d0ad034894d..50281bd2b2921a 100644 --- a/homeassistant/components/modbus/binary_sensor.py +++ b/homeassistant/components/modbus/binary_sensor.py @@ -126,7 +126,7 @@ def __init__( ) -> None: """Initialize the Modbus binary sensor.""" idx += 1 - self._attr_name = f"{entry[CONF_NAME]}_{idx}" + self._attr_name = f"{entry[CONF_NAME]} {idx}" self._attr_device_class = entry.get(CONF_DEVICE_CLASS) self._attr_available = False self._result_inx = int(idx / 8) diff --git a/tests/components/modbus/conftest.py b/tests/components/modbus/conftest.py index 75d4d6099c94ad..b00f9700f7f285 100644 --- a/tests/components/modbus/conftest.py +++ b/tests/components/modbus/conftest.py @@ -16,7 +16,7 @@ from tests.common import async_fire_time_changed, mock_restore_cache TEST_MODBUS_NAME = "modbusTest" -TEST_ENTITY_NAME = "test_entity" +TEST_ENTITY_NAME = "test entity" TEST_MODBUS_HOST = "modbusHost" TEST_PORT_TCP = 5501 TEST_PORT_SERIAL = "usb01" diff --git a/tests/components/modbus/test_binary_sensor.py b/tests/components/modbus/test_binary_sensor.py index fbe0003b78a744..15a03b76927fde 100644 --- a/tests/components/modbus/test_binary_sensor.py +++ b/tests/components/modbus/test_binary_sensor.py @@ -25,7 +25,7 @@ from .conftest import TEST_ENTITY_NAME, ReadResult, do_next_cycle -ENTITY_ID = f"{SENSOR_DOMAIN}.{TEST_ENTITY_NAME}" +ENTITY_ID = f"{SENSOR_DOMAIN}.{TEST_ENTITY_NAME}".replace(" ", "_") @pytest.mark.parametrize( @@ -244,8 +244,8 @@ async def test_config_slave_binary_sensor(hass, mock_modbus): """Run config test for binary sensor.""" assert SENSOR_DOMAIN in hass.config.components - for addon in ["", "_1", "_2", "_3"]: - entity_id = f"{SENSOR_DOMAIN}.{TEST_ENTITY_NAME}{addon}" + for addon in ["", " 1", " 2", " 3"]: + entity_id = f"{SENSOR_DOMAIN}.{TEST_ENTITY_NAME}{addon}".replace(" ", "_") assert hass.states.get(entity_id) is not None @@ -315,5 +315,5 @@ async def test_slave_binary_sensor(hass, expected, slaves, mock_do_cycle): assert hass.states.get(ENTITY_ID).state == expected for i in range(8): - entity_id = f"{SENSOR_DOMAIN}.{TEST_ENTITY_NAME}_{i+1}" + entity_id = f"{SENSOR_DOMAIN}.{TEST_ENTITY_NAME}_{i+1}".replace(" ", "_") assert hass.states.get(entity_id).state == slaves[i] diff --git a/tests/components/modbus/test_climate.py b/tests/components/modbus/test_climate.py index db471709c10b5c..80c8590f7ccdbe 100644 --- a/tests/components/modbus/test_climate.py +++ b/tests/components/modbus/test_climate.py @@ -22,7 +22,7 @@ from .conftest import TEST_ENTITY_NAME, ReadResult -ENTITY_ID = f"{CLIMATE_DOMAIN}.{TEST_ENTITY_NAME}" +ENTITY_ID = f"{CLIMATE_DOMAIN}.{TEST_ENTITY_NAME}".replace(" ", "_") @pytest.mark.parametrize( diff --git a/tests/components/modbus/test_cover.py b/tests/components/modbus/test_cover.py index cc879b2c1689a2..6797dc8713cab2 100644 --- a/tests/components/modbus/test_cover.py +++ b/tests/components/modbus/test_cover.py @@ -32,8 +32,8 @@ from .conftest import TEST_ENTITY_NAME, ReadResult, do_next_cycle -ENTITY_ID = f"{COVER_DOMAIN}.{TEST_ENTITY_NAME}" -ENTITY_ID2 = f"{ENTITY_ID}2" +ENTITY_ID = f"{COVER_DOMAIN}.{TEST_ENTITY_NAME}".replace(" ", "_") +ENTITY_ID2 = f"{ENTITY_ID}_2" @pytest.mark.parametrize( @@ -270,7 +270,7 @@ async def test_restore_state_cover(hass, mock_test_state, mock_modbus): CONF_SCAN_INTERVAL: 0, }, { - CONF_NAME: f"{TEST_ENTITY_NAME}2", + CONF_NAME: f"{TEST_ENTITY_NAME} 2", CONF_INPUT_TYPE: CALL_TYPE_COIL, CONF_ADDRESS: 1235, CONF_SCAN_INTERVAL: 0, diff --git a/tests/components/modbus/test_fan.py b/tests/components/modbus/test_fan.py index f766f816109353..9b0564504d92a6 100644 --- a/tests/components/modbus/test_fan.py +++ b/tests/components/modbus/test_fan.py @@ -32,8 +32,8 @@ from .conftest import TEST_ENTITY_NAME, ReadResult -ENTITY_ID = f"{FAN_DOMAIN}.{TEST_ENTITY_NAME}" -ENTITY_ID2 = f"{ENTITY_ID}2" +ENTITY_ID = f"{FAN_DOMAIN}.{TEST_ENTITY_NAME}".replace(" ", "_") +ENTITY_ID2 = f"{ENTITY_ID}_2" @pytest.mark.parametrize( @@ -228,7 +228,7 @@ async def test_restore_state_fan(hass, mock_test_state, mock_modbus): CONF_SCAN_INTERVAL: 0, }, { - CONF_NAME: f"{TEST_ENTITY_NAME}2", + CONF_NAME: f"{TEST_ENTITY_NAME} 2", CONF_ADDRESS: 18, CONF_WRITE_TYPE: CALL_TYPE_REGISTER_HOLDING, CONF_SCAN_INTERVAL: 0, diff --git a/tests/components/modbus/test_init.py b/tests/components/modbus/test_init.py index c9beba694e89d6..9bd0a2caa6d3b9 100644 --- a/tests/components/modbus/test_init.py +++ b/tests/components/modbus/test_init.py @@ -234,7 +234,7 @@ async def test_exception_struct_validator(do_config): { CONF_NAME: TEST_MODBUS_NAME, CONF_TYPE: TCP, - CONF_HOST: TEST_MODBUS_HOST + "2", + CONF_HOST: TEST_MODBUS_HOST + " 2", CONF_PORT: TEST_PORT_TCP, }, ], @@ -246,7 +246,7 @@ async def test_exception_struct_validator(do_config): CONF_PORT: TEST_PORT_TCP, }, { - CONF_NAME: TEST_MODBUS_NAME + "2", + CONF_NAME: TEST_MODBUS_NAME + " 2", CONF_TYPE: TCP, CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, @@ -296,7 +296,7 @@ async def test_duplicate_modbus_validator(do_config): CONF_SLAVE: 0, }, { - CONF_NAME: TEST_ENTITY_NAME + "2", + CONF_NAME: TEST_ENTITY_NAME + " 2", CONF_ADDRESS: 117, CONF_SLAVE: 0, }, @@ -392,7 +392,7 @@ async def test_duplicate_entity_validator(do_config): CONF_TYPE: TCP, CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, - CONF_NAME: f"{TEST_MODBUS_NAME}2", + CONF_NAME: f"{TEST_MODBUS_NAME} 2", }, { CONF_TYPE: SERIAL, @@ -402,7 +402,7 @@ async def test_duplicate_entity_validator(do_config): CONF_PORT: TEST_PORT_SERIAL, CONF_PARITY: "E", CONF_STOPBITS: 1, - CONF_NAME: f"{TEST_MODBUS_NAME}3", + CONF_NAME: f"{TEST_MODBUS_NAME} 3", }, ], { @@ -599,7 +599,7 @@ async def test_pb_read( """Run test for different read.""" # Check state - entity_id = f"{do_domain}.{TEST_ENTITY_NAME}" + entity_id = f"{do_domain}.{TEST_ENTITY_NAME}".replace(" ", "_") state = hass.states.get(entity_id).state assert hass.states.get(entity_id).state @@ -681,7 +681,7 @@ async def test_delay(hass, mock_pymodbus): # We "hijiack" a binary_sensor to make a proper blackbox test. set_delay = 15 set_scan_interval = 5 - entity_id = f"{BINARY_SENSOR_DOMAIN}.{TEST_ENTITY_NAME}" + entity_id = f"{BINARY_SENSOR_DOMAIN}.{TEST_ENTITY_NAME}".replace(" ", "_") config = { DOMAIN: [ { @@ -778,7 +778,7 @@ async def test_stop_restart(hass, caplog, mock_modbus): """Run test for service stop.""" caplog.set_level(logging.INFO) - entity_id = f"{SENSOR_DOMAIN}.{TEST_ENTITY_NAME}" + entity_id = f"{SENSOR_DOMAIN}.{TEST_ENTITY_NAME}".replace(" ", "_") assert hass.states.get(entity_id).state == STATE_UNKNOWN hass.states.async_set(entity_id, 17) await hass.async_block_till_done() diff --git a/tests/components/modbus/test_light.py b/tests/components/modbus/test_light.py index 220924733ad3b6..f98f1105fa0024 100644 --- a/tests/components/modbus/test_light.py +++ b/tests/components/modbus/test_light.py @@ -32,8 +32,8 @@ from .conftest import TEST_ENTITY_NAME, ReadResult -ENTITY_ID = f"{LIGHT_DOMAIN}.{TEST_ENTITY_NAME}" -ENTITY_ID2 = f"{ENTITY_ID}2" +ENTITY_ID = f"{LIGHT_DOMAIN}.{TEST_ENTITY_NAME}".replace(" ", "_") +ENTITY_ID2 = f"{ENTITY_ID}_2" @pytest.mark.parametrize( @@ -228,7 +228,7 @@ async def test_restore_state_light(hass, mock_test_state, mock_modbus): CONF_SCAN_INTERVAL: 0, }, { - CONF_NAME: f"{TEST_ENTITY_NAME}2", + CONF_NAME: f"{TEST_ENTITY_NAME} 2", CONF_ADDRESS: 18, CONF_WRITE_TYPE: CALL_TYPE_REGISTER_HOLDING, CONF_SCAN_INTERVAL: 0, diff --git a/tests/components/modbus/test_sensor.py b/tests/components/modbus/test_sensor.py index 0edd9bdc945e72..053dc46c6ba352 100644 --- a/tests/components/modbus/test_sensor.py +++ b/tests/components/modbus/test_sensor.py @@ -37,7 +37,7 @@ from .conftest import TEST_ENTITY_NAME, ReadResult, do_next_cycle -ENTITY_ID = f"{SENSOR_DOMAIN}.{TEST_ENTITY_NAME}" +ENTITY_ID = f"{SENSOR_DOMAIN}.{TEST_ENTITY_NAME}".replace(" ", "_") @pytest.mark.parametrize( diff --git a/tests/components/modbus/test_switch.py b/tests/components/modbus/test_switch.py index 86a26e187428db..006e7ee8d15b8d 100644 --- a/tests/components/modbus/test_switch.py +++ b/tests/components/modbus/test_switch.py @@ -40,8 +40,8 @@ from tests.common import async_fire_time_changed -ENTITY_ID = f"{SWITCH_DOMAIN}.{TEST_ENTITY_NAME}" -ENTITY_ID2 = f"{ENTITY_ID}2" +ENTITY_ID = f"{SWITCH_DOMAIN}.{TEST_ENTITY_NAME}".replace(" ", "_") +ENTITY_ID2 = f"{ENTITY_ID}_2" @pytest.mark.parametrize( @@ -281,7 +281,7 @@ async def test_restore_state_switch(hass, mock_test_state, mock_modbus): CONF_SCAN_INTERVAL: 0, }, { - CONF_NAME: f"{TEST_ENTITY_NAME}2", + CONF_NAME: f"{TEST_ENTITY_NAME} 2", CONF_ADDRESS: 18, CONF_WRITE_TYPE: CALL_TYPE_REGISTER_HOLDING, CONF_SCAN_INTERVAL: 0, From 95de1dd4464ed25725703686c209d22486835ed7 Mon Sep 17 00:00:00 2001 From: rubenverhoef Date: Tue, 22 Feb 2022 00:00:49 +0100 Subject: [PATCH 0911/1098] Additional MQTT light command templates (#63361) Co-authored-by: jbouwh --- .../components/mqtt/abbreviations.py | 2 + .../components/mqtt/light/schema_basic.py | 12 +- tests/components/mqtt/test_light.py | 121 +++++++++++++++++- 3 files changed, 128 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/mqtt/abbreviations.py b/homeassistant/components/mqtt/abbreviations.py index c98dbbd270a5fd..ddbced5286daeb 100644 --- a/homeassistant/components/mqtt/abbreviations.py +++ b/homeassistant/components/mqtt/abbreviations.py @@ -16,6 +16,7 @@ "away_mode_stat_tpl": "away_mode_state_template", "away_mode_stat_t": "away_mode_state_topic", "b_tpl": "blue_template", + "bri_cmd_tpl": "brightness_command_template", "bri_cmd_t": "brightness_command_topic", "bri_scl": "brightness_scale", "bri_stat_t": "brightness_state_topic", @@ -58,6 +59,7 @@ "fanspd_lst": "fan_speed_list", "flsh_tlng": "flash_time_long", "flsh_tsht": "flash_time_short", + "fx_cmd_tpl": "effect_command_template", "fx_cmd_t": "effect_command_topic", "fx_list": "effect_list", "fx_stat_t": "effect_state_topic", diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index d5665a1beff77e..09b23029b0e7f8 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -68,6 +68,7 @@ _LOGGER = logging.getLogger(__name__) +CONF_BRIGHTNESS_COMMAND_TEMPLATE = "brightness_command_template" CONF_BRIGHTNESS_COMMAND_TOPIC = "brightness_command_topic" CONF_BRIGHTNESS_SCALE = "brightness_scale" CONF_BRIGHTNESS_STATE_TOPIC = "brightness_state_topic" @@ -78,6 +79,7 @@ CONF_COLOR_TEMP_COMMAND_TOPIC = "color_temp_command_topic" CONF_COLOR_TEMP_STATE_TOPIC = "color_temp_state_topic" CONF_COLOR_TEMP_VALUE_TEMPLATE = "color_temp_value_template" +CONF_EFFECT_COMMAND_TEMPLATE = "effect_command_template" CONF_EFFECT_COMMAND_TOPIC = "effect_command_topic" CONF_EFFECT_LIST = "effect_list" CONF_EFFECT_STATE_TOPIC = "effect_state_topic" @@ -141,7 +143,9 @@ VALUES_ON_COMMAND_TYPE = ["first", "last", "brightness"] COMMAND_TEMPLATE_KEYS = [ + CONF_BRIGHTNESS_COMMAND_TEMPLATE, CONF_COLOR_TEMP_COMMAND_TEMPLATE, + CONF_EFFECT_COMMAND_TEMPLATE, CONF_RGB_COMMAND_TEMPLATE, CONF_RGBW_COMMAND_TEMPLATE, CONF_RGBWW_COMMAND_TEMPLATE, @@ -163,6 +167,7 @@ _PLATFORM_SCHEMA_BASE = ( mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( { + vol.Optional(CONF_BRIGHTNESS_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_BRIGHTNESS_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional( CONF_BRIGHTNESS_SCALE, default=DEFAULT_BRIGHTNESS_SCALE @@ -175,6 +180,7 @@ vol.Optional(CONF_COLOR_TEMP_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_COLOR_TEMP_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_COLOR_TEMP_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_EFFECT_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_EFFECT_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]), vol.Optional(CONF_EFFECT_STATE_TOPIC): mqtt.valid_subscribe_topic, @@ -970,6 +976,8 @@ def set_optimistic(attribute, value, color_mode=None, condition_attribute=None): ) # Make sure the brightness is not rounded down to 0 device_brightness = max(device_brightness, 1) + if tpl := self._command_templates[CONF_BRIGHTNESS_COMMAND_TEMPLATE]: + device_brightness = tpl(variables={"value": device_brightness}) await publish(CONF_BRIGHTNESS_COMMAND_TOPIC, device_brightness) should_update |= set_optimistic(ATTR_BRIGHTNESS, kwargs[ATTR_BRIGHTNESS]) elif ( @@ -1038,8 +1046,10 @@ def set_optimistic(attribute, value, color_mode=None, condition_attribute=None): if ATTR_EFFECT in kwargs and self._topic[CONF_EFFECT_COMMAND_TOPIC] is not None: effect = kwargs[ATTR_EFFECT] if effect in self._config.get(CONF_EFFECT_LIST): + if tpl := self._command_templates[CONF_EFFECT_COMMAND_TEMPLATE]: + effect = tpl(variables={"value": effect}) await publish(CONF_EFFECT_COMMAND_TOPIC, effect) - should_update |= set_optimistic(ATTR_EFFECT, effect) + should_update |= set_optimistic(ATTR_EFFECT, kwargs[ATTR_EFFECT]) if ATTR_WHITE in kwargs and self._topic[CONF_WHITE_COMMAND_TOPIC] is not None: percent_white = float(kwargs[ATTR_WHITE]) / 255 diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index 782ee40f0c8573..d6125729191b61 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -152,6 +152,37 @@ payload_on: "on" payload_off: "off" +Configuration with brightness command template: + +light: + platform: mqtt + name: "Office Light" + state_topic: "office/rgb1/light/status" + command_topic: "office/rgb1/light/switch" + brightness_state_topic: "office/rgb1/brightness/status" + brightness_command_topic: "office/rgb1/brightness/set" + brightness_command_template: '{ "brightness": "{{ value }}" }' + qos: 0 + payload_on: "on" + payload_off: "off" + +Configuration with effect command template: + +light: + platform: mqtt + name: "Office Light Color Temp" + state_topic: "office/rgb1/light/status" + command_topic: "office/rgb1/light/switch" + effect_state_topic: "office/rgb1/effect/status" + effect_command_topic: "office/rgb1/effect/set" + effect_command_template: '{ "effect": "{{ value }}" }' + effect_list: + - rainbow + - colorloop + qos: 0 + payload_on: "on" + payload_off: "off" + """ import copy from unittest.mock import call, patch @@ -3394,18 +3425,18 @@ async def test_max_mireds(hass, mqtt_mock): "brightness_command_topic", {"color_temp": "200", "brightness": "50"}, 50, - None, - None, - None, + "brightness_command_template", + "value", + b"5", ), ( light.SERVICE_TURN_ON, "effect_command_topic", {"rgb_color": [255, 128, 0], "effect": "color_loop"}, "color_loop", - None, - None, - None, + "effect_command_template", + "value", + b"c", ), ( light.SERVICE_TURN_ON, @@ -3565,3 +3596,81 @@ async def test_encoding_subscribable_topics( attribute_value, init_payload, ) + + +async def test_sending_mqtt_brightness_command_with_template(hass, mqtt_mock): + """Test the sending of Brightness command with template.""" + config = { + light.DOMAIN: { + "platform": "mqtt", + "name": "test", + "command_topic": "test_light_brightness/set", + "brightness_command_topic": "test_light_brightness/brightness/set", + "brightness_command_template": "{{ (1000 / value) | round(0) }}", + "payload_on": "on", + "payload_off": "off", + "qos": 0, + } + } + + assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() + + state = hass.states.get("light.test") + assert state.state == STATE_OFF + + await common.async_turn_on(hass, "light.test", brightness=100) + + mqtt_mock.async_publish.assert_has_calls( + [ + call("test_light_brightness/set", "on", 0, False), + call("test_light_brightness/brightness/set", "10", 0, False), + ], + any_order=True, + ) + + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes["brightness"] == 100 + + +async def test_sending_mqtt_effect_command_with_template(hass, mqtt_mock): + """Test the sending of Effect command with template.""" + config = { + light.DOMAIN: { + "platform": "mqtt", + "name": "test", + "command_topic": "test_light_brightness/set", + "brightness_command_topic": "test_light_brightness/brightness/set", + "effect_command_topic": "test_light_brightness/effect/set", + "effect_command_template": '{ "effect": "{{ value }}" }', + "effect_list": ["colorloop", "random"], + "payload_on": "on", + "payload_off": "off", + "qos": 0, + } + } + + assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() + + state = hass.states.get("light.test") + assert state.state == STATE_OFF + + await common.async_turn_on(hass, "light.test", effect="colorloop") + + mqtt_mock.async_publish.assert_has_calls( + [ + call("test_light_brightness/set", "on", 0, False), + call( + "test_light_brightness/effect/set", + '{ "effect": "colorloop" }', + 0, + False, + ), + ], + any_order=True, + ) + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("effect") == "colorloop" From b19bf9b147f4321e89d1f7f01e68337f2102f460 Mon Sep 17 00:00:00 2001 From: Michael Chisholm Date: Tue, 22 Feb 2022 10:14:08 +1100 Subject: [PATCH 0912/1098] Add dlna_dms integration to support DLNA Digital Media Servers (#66437) --- CODEOWNERS | 2 + homeassistant/components/dlna_dms/__init__.py | 28 + .../components/dlna_dms/config_flow.py | 177 ++++ homeassistant/components/dlna_dms/const.py | 78 ++ homeassistant/components/dlna_dms/dms.py | 726 +++++++++++++++ .../components/dlna_dms/manifest.json | 29 + .../components/dlna_dms/media_source.py | 126 +++ .../components/dlna_dms/strings.json | 24 + .../components/dlna_dms/translations/en.json | 24 + homeassistant/generated/config_flows.py | 1 + homeassistant/generated/ssdp.py | 18 + requirements_all.txt | 1 + requirements_test_all.txt | 1 + tests/components/dlna_dms/__init__.py | 1 + tests/components/dlna_dms/conftest.py | 131 +++ tests/components/dlna_dms/test_config_flow.py | 346 +++++++ .../dlna_dms/test_device_availability.py | 705 ++++++++++++++ .../dlna_dms/test_dms_device_source.py | 878 ++++++++++++++++++ tests/components/dlna_dms/test_init.py | 59 ++ .../components/dlna_dms/test_media_source.py | 255 +++++ 20 files changed, 3610 insertions(+) create mode 100644 homeassistant/components/dlna_dms/__init__.py create mode 100644 homeassistant/components/dlna_dms/config_flow.py create mode 100644 homeassistant/components/dlna_dms/const.py create mode 100644 homeassistant/components/dlna_dms/dms.py create mode 100644 homeassistant/components/dlna_dms/manifest.json create mode 100644 homeassistant/components/dlna_dms/media_source.py create mode 100644 homeassistant/components/dlna_dms/strings.json create mode 100644 homeassistant/components/dlna_dms/translations/en.json create mode 100644 tests/components/dlna_dms/__init__.py create mode 100644 tests/components/dlna_dms/conftest.py create mode 100644 tests/components/dlna_dms/test_config_flow.py create mode 100644 tests/components/dlna_dms/test_device_availability.py create mode 100644 tests/components/dlna_dms/test_dms_device_source.py create mode 100644 tests/components/dlna_dms/test_init.py create mode 100644 tests/components/dlna_dms/test_media_source.py diff --git a/CODEOWNERS b/CODEOWNERS index a8d24fa03a3a46..075c8abbc659f2 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -214,6 +214,8 @@ homeassistant/components/digital_ocean/* @fabaff homeassistant/components/discogs/* @thibmaek homeassistant/components/dlna_dmr/* @StevenLooman @chishm tests/components/dlna_dmr/* @StevenLooman @chishm +homeassistant/components/dlna_dms/* @chishm +tests/components/dlna_dms/* @chishm homeassistant/components/dnsip/* @gjohansson-ST tests/components/dnsip/* @gjohansson-ST homeassistant/components/doorbird/* @oblogic7 @bdraco @flacjacket diff --git a/homeassistant/components/dlna_dms/__init__.py b/homeassistant/components/dlna_dms/__init__.py new file mode 100644 index 00000000000000..b09547e07c8140 --- /dev/null +++ b/homeassistant/components/dlna_dms/__init__.py @@ -0,0 +1,28 @@ +"""The DLNA Digital Media Server integration. + +A single config entry is used, with SSDP discovery for media servers. Each +server is wrapped in a DmsEntity, and the server's USN is used as the unique_id. +""" +from __future__ import annotations + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import LOGGER +from .dms import get_domain_data + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up DLNA DMS device from a config entry.""" + LOGGER.debug("Setting up config entry: %s", entry.unique_id) + + # Forward setup to this domain's data manager + return await get_domain_data(hass).async_setup_entry(entry) + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + LOGGER.debug("Unloading config entry: %s", entry.unique_id) + + # Forward unload to this domain's data manager + return await get_domain_data(hass).async_unload_entry(entry) diff --git a/homeassistant/components/dlna_dms/config_flow.py b/homeassistant/components/dlna_dms/config_flow.py new file mode 100644 index 00000000000000..7ae3a104fc1bae --- /dev/null +++ b/homeassistant/components/dlna_dms/config_flow.py @@ -0,0 +1,177 @@ +"""Config flow for DLNA DMS.""" +from __future__ import annotations + +import logging +from pprint import pformat +from typing import Any, cast +from urllib.parse import urlparse + +from async_upnp_client.profiles.dlna import DmsDevice +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.components import ssdp +from homeassistant.const import CONF_DEVICE_ID, CONF_HOST, CONF_URL +from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.exceptions import IntegrationError + +from .const import DEFAULT_NAME, DOMAIN + +LOGGER = logging.getLogger(__name__) + + +class ConnectError(IntegrationError): + """Error occurred when trying to connect to a device.""" + + +class DlnaDmsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a DLNA DMS config flow. + + The Unique Service Name (USN) of the DMS device is used as the unique_id for + config entries and for entities. This USN may differ from the root USN if + the DMS is an embedded device. + """ + + VERSION = 1 + + def __init__(self) -> None: + """Initialize flow.""" + self._discoveries: dict[str, ssdp.SsdpServiceInfo] = {} + self._location: str | None = None + self._usn: str | None = None + self._name: str | None = None + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle a flow initialized by the user by listing unconfigured devices.""" + LOGGER.debug("async_step_user: user_input: %s", user_input) + + if user_input is not None and (host := user_input.get(CONF_HOST)): + # User has chosen a device + discovery = self._discoveries[host] + await self._async_parse_discovery(discovery) + return self._create_entry() + + if not (discoveries := await self._async_get_discoveries()): + # Nothing found, abort configuration + return self.async_abort(reason="no_devices_found") + + self._discoveries = { + cast(str, urlparse(discovery.ssdp_location).hostname): discovery + for discovery in discoveries + } + + discovery_choices = { + host: f"{discovery.upnp.get(ssdp.ATTR_UPNP_FRIENDLY_NAME)} ({host})" + for host, discovery in self._discoveries.items() + } + data_schema = vol.Schema({vol.Optional(CONF_HOST): vol.In(discovery_choices)}) + return self.async_show_form(step_id="user", data_schema=data_schema) + + async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + """Handle a flow initialized by SSDP discovery.""" + LOGGER.debug("async_step_ssdp: discovery_info %s", pformat(discovery_info)) + + await self._async_parse_discovery(discovery_info) + + # Abort if the device doesn't support all services required for a DmsDevice. + # Use the discovery_info instead of DmsDevice.is_profile_device to avoid + # contacting the device again. + discovery_service_list = discovery_info.upnp.get(ssdp.ATTR_UPNP_SERVICE_LIST) + if not discovery_service_list: + return self.async_abort(reason="not_dms") + discovery_service_ids = { + service.get("serviceId") + for service in discovery_service_list.get("service") or [] + } + if not DmsDevice.SERVICE_IDS.issubset(discovery_service_ids): + return self.async_abort(reason="not_dms") + + # Abort if another config entry has the same location, in case the + # device doesn't have a static and unique UDN (breaking the UPnP spec). + self._async_abort_entries_match({CONF_URL: self._location}) + + self.context["title_placeholders"] = {"name": self._name} + + return await self.async_step_confirm() + + async def async_step_confirm( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Allow the user to confirm adding the device.""" + LOGGER.debug("async_step_confirm: %s", user_input) + + if user_input is not None: + return self._create_entry() + + self._set_confirm_only() + return self.async_show_form(step_id="confirm") + + def _create_entry(self) -> FlowResult: + """Create a config entry, assuming all required information is now known.""" + LOGGER.debug( + "_async_create_entry: location: %s, USN: %s", self._location, self._usn + ) + assert self._name + assert self._location + assert self._usn + + data = {CONF_URL: self._location, CONF_DEVICE_ID: self._usn} + return self.async_create_entry(title=self._name, data=data) + + async def _async_parse_discovery( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> None: + """Get required details from an SSDP discovery. + + Aborts if a device matching the SSDP USN has already been configured. + """ + LOGGER.debug( + "_async_parse_discovery: location: %s, USN: %s", + discovery_info.ssdp_location, + discovery_info.ssdp_usn, + ) + + if not discovery_info.ssdp_location or not discovery_info.ssdp_usn: + raise AbortFlow("bad_ssdp") + + if not self._location: + self._location = discovery_info.ssdp_location + + self._usn = discovery_info.ssdp_usn + await self.async_set_unique_id(self._usn) + + # Abort if already configured, but update the last-known location + self._abort_if_unique_id_configured( + updates={CONF_URL: self._location}, reload_on_update=False + ) + + self._name = ( + discovery_info.upnp.get(ssdp.ATTR_UPNP_FRIENDLY_NAME) + or urlparse(self._location).hostname + or DEFAULT_NAME + ) + + async def _async_get_discoveries(self) -> list[ssdp.SsdpServiceInfo]: + """Get list of unconfigured DLNA devices discovered by SSDP.""" + LOGGER.debug("_get_discoveries") + + # Get all compatible devices from ssdp's cache + discoveries: list[ssdp.SsdpServiceInfo] = [] + for udn_st in DmsDevice.DEVICE_TYPES: + st_discoveries = await ssdp.async_get_discovery_info_by_st( + self.hass, udn_st + ) + discoveries.extend(st_discoveries) + + # Filter out devices already configured + current_unique_ids = { + entry.unique_id + for entry in self._async_current_entries(include_ignore=False) + } + discoveries = [ + disc for disc in discoveries if disc.ssdp_udn not in current_unique_ids + ] + + return discoveries diff --git a/homeassistant/components/dlna_dms/const.py b/homeassistant/components/dlna_dms/const.py new file mode 100644 index 00000000000000..8c260272d5fa78 --- /dev/null +++ b/homeassistant/components/dlna_dms/const.py @@ -0,0 +1,78 @@ +"""Constants for the DLNA MediaServer integration.""" +from __future__ import annotations + +from collections.abc import Mapping +import logging +from typing import Final + +from homeassistant.components.media_player import const as _mp_const + +LOGGER = logging.getLogger(__package__) + +DOMAIN: Final = "dlna_dms" +DEFAULT_NAME: Final = "DLNA Media Server" + +SOURCE_SEP: Final = "/" +ROOT_OBJECT_ID: Final = "0" +PATH_SEP: Final = "/" +PATH_SEARCH_FLAG: Final = "?" +PATH_OBJECT_ID_FLAG: Final = ":" +# Only request the metadata needed to build a browse response +DLNA_BROWSE_FILTER: Final = [ + "id", + "upnp:class", + "dc:title", + "res", + "@childCount", + "upnp:albumArtURI", +] +# Get all metadata when resolving, for the use of media_players +DLNA_RESOLVE_FILTER: Final = "*" +# Metadata needed to resolve a path +DLNA_PATH_FILTER: Final = ["id", "upnp:class", "dc:title"] +DLNA_SORT_CRITERIA: Final = ["+upnp:class", "+upnp:originalTrackNumber", "+dc:title"] + +PROTOCOL_HTTP: Final = "http-get" +PROTOCOL_RTSP: Final = "rtsp-rtp-udp" +PROTOCOL_ANY: Final = "*" +STREAMABLE_PROTOCOLS: Final = [PROTOCOL_HTTP, PROTOCOL_RTSP, PROTOCOL_ANY] + +# Map UPnP object class to media_player media class +MEDIA_CLASS_MAP: Mapping[str, str] = { + "object": _mp_const.MEDIA_CLASS_URL, + "object.item": _mp_const.MEDIA_CLASS_URL, + "object.item.imageItem": _mp_const.MEDIA_CLASS_IMAGE, + "object.item.imageItem.photo": _mp_const.MEDIA_CLASS_IMAGE, + "object.item.audioItem": _mp_const.MEDIA_CLASS_MUSIC, + "object.item.audioItem.musicTrack": _mp_const.MEDIA_CLASS_MUSIC, + "object.item.audioItem.audioBroadcast": _mp_const.MEDIA_CLASS_MUSIC, + "object.item.audioItem.audioBook": _mp_const.MEDIA_CLASS_PODCAST, + "object.item.videoItem": _mp_const.MEDIA_CLASS_VIDEO, + "object.item.videoItem.movie": _mp_const.MEDIA_CLASS_MOVIE, + "object.item.videoItem.videoBroadcast": _mp_const.MEDIA_CLASS_TV_SHOW, + "object.item.videoItem.musicVideoClip": _mp_const.MEDIA_CLASS_VIDEO, + "object.item.playlistItem": _mp_const.MEDIA_CLASS_TRACK, + "object.item.textItem": _mp_const.MEDIA_CLASS_URL, + "object.item.bookmarkItem": _mp_const.MEDIA_CLASS_URL, + "object.item.epgItem": _mp_const.MEDIA_CLASS_EPISODE, + "object.item.epgItem.audioProgram": _mp_const.MEDIA_CLASS_MUSIC, + "object.item.epgItem.videoProgram": _mp_const.MEDIA_CLASS_VIDEO, + "object.container": _mp_const.MEDIA_CLASS_DIRECTORY, + "object.container.person": _mp_const.MEDIA_CLASS_ARTIST, + "object.container.person.musicArtist": _mp_const.MEDIA_CLASS_ARTIST, + "object.container.playlistContainer": _mp_const.MEDIA_CLASS_PLAYLIST, + "object.container.album": _mp_const.MEDIA_CLASS_ALBUM, + "object.container.album.musicAlbum": _mp_const.MEDIA_CLASS_ALBUM, + "object.container.album.photoAlbum": _mp_const.MEDIA_CLASS_ALBUM, + "object.container.genre": _mp_const.MEDIA_CLASS_GENRE, + "object.container.genre.musicGenre": _mp_const.MEDIA_CLASS_GENRE, + "object.container.genre.movieGenre": _mp_const.MEDIA_CLASS_GENRE, + "object.container.channelGroup": _mp_const.MEDIA_CLASS_CHANNEL, + "object.container.channelGroup.audioChannelGroup": _mp_const.MEDIA_TYPE_CHANNELS, + "object.container.channelGroup.videoChannelGroup": _mp_const.MEDIA_TYPE_CHANNELS, + "object.container.epgContainer": _mp_const.MEDIA_CLASS_DIRECTORY, + "object.container.storageSystem": _mp_const.MEDIA_CLASS_DIRECTORY, + "object.container.storageVolume": _mp_const.MEDIA_CLASS_DIRECTORY, + "object.container.storageFolder": _mp_const.MEDIA_CLASS_DIRECTORY, + "object.container.bookmarkFolder": _mp_const.MEDIA_CLASS_DIRECTORY, +} diff --git a/homeassistant/components/dlna_dms/dms.py b/homeassistant/components/dlna_dms/dms.py new file mode 100644 index 00000000000000..d3a65448f84fb4 --- /dev/null +++ b/homeassistant/components/dlna_dms/dms.py @@ -0,0 +1,726 @@ +"""Wrapper for media_source around async_upnp_client's DmsDevice .""" +from __future__ import annotations + +import asyncio +from collections.abc import Callable, Coroutine +from dataclasses import dataclass +import functools +from typing import Any, TypeVar, cast + +from async_upnp_client import UpnpEventHandler, UpnpFactory, UpnpRequester +from async_upnp_client.aiohttp import AiohttpSessionRequester +from async_upnp_client.const import NotificationSubType +from async_upnp_client.exceptions import UpnpActionError, UpnpConnectionError, UpnpError +from async_upnp_client.profiles.dlna import ContentDirectoryErrorCode, DmsDevice +from didl_lite import didl_lite + +from homeassistant.backports.enum import StrEnum +from homeassistant.components import ssdp +from homeassistant.components.media_player.const import MEDIA_CLASS_DIRECTORY +from homeassistant.components.media_player.errors import BrowseError +from homeassistant.components.media_source.error import Unresolvable +from homeassistant.components.media_source.models import BrowseMediaSource, PlayMedia +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_DEVICE_ID, CONF_URL +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import aiohttp_client +from homeassistant.util import slugify + +from .const import ( + DLNA_BROWSE_FILTER, + DLNA_PATH_FILTER, + DLNA_RESOLVE_FILTER, + DLNA_SORT_CRITERIA, + DOMAIN, + LOGGER, + MEDIA_CLASS_MAP, + PATH_OBJECT_ID_FLAG, + PATH_SEARCH_FLAG, + PATH_SEP, + ROOT_OBJECT_ID, + STREAMABLE_PROTOCOLS, +) + +_DlnaDmsDeviceMethod = TypeVar("_DlnaDmsDeviceMethod", bound="DmsDeviceSource") +_RetType = TypeVar("_RetType") + + +class DlnaDmsData: + """Storage class for domain global data.""" + + hass: HomeAssistant + lock: asyncio.Lock + requester: UpnpRequester + upnp_factory: UpnpFactory + event_handler: UpnpEventHandler + devices: dict[str, DmsDeviceSource] # Indexed by config_entry.unique_id + sources: dict[str, DmsDeviceSource] # Indexed by source_id + + def __init__( + self, + hass: HomeAssistant, + ) -> None: + """Initialize global data.""" + self.hass = hass + self.lock = asyncio.Lock() + session = aiohttp_client.async_get_clientsession(hass, verify_ssl=False) + self.requester = AiohttpSessionRequester(session, with_sleep=True) + self.upnp_factory = UpnpFactory(self.requester, non_strict=True) + # NOTE: event_handler is not actually used, and is only created to + # satisfy the DmsDevice.__init__ signature + self.event_handler = UpnpEventHandler("", self.requester) + self.devices = {} + self.sources = {} + + async def async_setup_entry(self, config_entry: ConfigEntry) -> bool: + """Create a DMS device connection from a config entry.""" + assert config_entry.unique_id + async with self.lock: + source_id = self._generate_source_id(config_entry.title) + device = DmsDeviceSource(self.hass, config_entry, source_id) + self.devices[config_entry.unique_id] = device + self.sources[device.source_id] = device + + # Update the device when the associated config entry is modified + config_entry.async_on_unload( + config_entry.add_update_listener(self.async_update_entry) + ) + + await device.async_added_to_hass() + return True + + async def async_unload_entry(self, config_entry: ConfigEntry) -> bool: + """Unload a config entry and disconnect the corresponding DMS device.""" + assert config_entry.unique_id + async with self.lock: + device = self.devices.pop(config_entry.unique_id) + del self.sources[device.source_id] + await device.async_will_remove_from_hass() + return True + + async def async_update_entry( + self, hass: HomeAssistant, config_entry: ConfigEntry + ) -> None: + """Update a DMS device when the config entry changes.""" + assert config_entry.unique_id + async with self.lock: + device = self.devices[config_entry.unique_id] + # Update the source_id to match the new name + del self.sources[device.source_id] + device.source_id = self._generate_source_id(config_entry.title) + self.sources[device.source_id] = device + + def _generate_source_id(self, name: str) -> str: + """Generate a unique source ID. + + Caller should hold self.lock when calling this method. + """ + source_id_base = slugify(name) + if source_id_base not in self.sources: + return source_id_base + + tries = 1 + while (suggested_source_id := f"{source_id_base}_{tries}") in self.sources: + tries += 1 + + return suggested_source_id + + +@callback +def get_domain_data(hass: HomeAssistant) -> DlnaDmsData: + """Obtain this integration's domain data, creating it if needed.""" + if DOMAIN in hass.data: + return cast(DlnaDmsData, hass.data[DOMAIN]) + + data = DlnaDmsData(hass) + hass.data[DOMAIN] = data + return data + + +@dataclass +class DidlPlayMedia(PlayMedia): + """Playable media with DIDL metadata.""" + + didl_metadata: didl_lite.DidlObject + + +class DlnaDmsDeviceError(BrowseError, Unresolvable): + """Base for errors raised by DmsDeviceSource. + + Caught by both media_player (BrowseError) and media_source (Unresolvable), + so DmsDeviceSource methods can be used for both browse and resolve + functionality. + """ + + +class DeviceConnectionError(DlnaDmsDeviceError): + """Error occurred with the connection to the server.""" + + +class ActionError(DlnaDmsDeviceError): + """Error when calling a UPnP Action on the device.""" + + +def catch_request_errors( + func: Callable[[_DlnaDmsDeviceMethod, str], Coroutine[Any, Any, _RetType]] +) -> Callable[[_DlnaDmsDeviceMethod, str], Coroutine[Any, Any, _RetType]]: + """Catch UpnpError errors.""" + + @functools.wraps(func) + async def wrapper(self: _DlnaDmsDeviceMethod, req_param: str) -> _RetType: + """Catch UpnpError errors and check availability before and after request.""" + if not self.available: + LOGGER.warning("Device disappeared when trying to call %s", func.__name__) + raise DeviceConnectionError("DMS is not connected") + + try: + return await func(self, req_param) + except UpnpActionError as err: + LOGGER.debug("Server failure", exc_info=err) + if err.error_code == ContentDirectoryErrorCode.NO_SUCH_OBJECT: + LOGGER.debug("No such object: %s", req_param) + raise ActionError(f"No such object: {req_param}") from err + if err.error_code == ContentDirectoryErrorCode.INVALID_SEARCH_CRITERIA: + LOGGER.debug("Invalid query: %s", req_param) + raise ActionError(f"Invalid query: {req_param}") from err + raise DeviceConnectionError(f"Server failure: {err!r}") from err + except UpnpConnectionError as err: + LOGGER.debug("Server disconnected", exc_info=err) + await self.device_disconnect() + raise DeviceConnectionError(f"Server disconnected: {err!r}") from err + except UpnpError as err: + LOGGER.debug("Server communication failure", exc_info=err) + raise DeviceConnectionError( + f"Server communication failure: {err!r}" + ) from err + + return wrapper + + +class DmsDeviceSource: + """DMS Device wrapper, providing media files as a media_source.""" + + hass: HomeAssistant + config_entry: ConfigEntry + + # Unique slug used for media-source URIs + source_id: str + + # Last known URL for the device, used when adding this wrapper to hass to + # try to connect before SSDP has rediscovered it, or when SSDP discovery + # fails. + location: str | None + + _device_lock: asyncio.Lock # Held when connecting or disconnecting the device + _device: DmsDevice | None = None + + # Only try to connect once when an ssdp:alive advertisement is received + _ssdp_connect_failed: bool = False + + # Track BOOTID in SSDP advertisements for device changes + _bootid: int | None = None + + def __init__( + self, hass: HomeAssistant, config_entry: ConfigEntry, source_id: str + ) -> None: + """Initialize a DMS Source.""" + self.hass = hass + self.config_entry = config_entry + self.source_id = source_id + self.location = self.config_entry.data[CONF_URL] + self._device_lock = asyncio.Lock() + + # Callbacks and events + + async def async_added_to_hass(self) -> None: + """Handle addition of this source.""" + + # Try to connect to the last known location, but don't worry if not available + if not self._device and self.location: + try: + await self.device_connect() + except UpnpError as err: + LOGGER.debug("Couldn't connect immediately: %r", err) + + # Get SSDP notifications for only this device + self.config_entry.async_on_unload( + await ssdp.async_register_callback( + self.hass, self.async_ssdp_callback, {"USN": self.usn} + ) + ) + + # async_upnp_client.SsdpListener only reports byebye once for each *UDN* + # (device name) which often is not the USN (service within the device) + # that we're interested in. So also listen for byebye advertisements for + # the UDN, which is reported in the _udn field of the combined_headers. + self.config_entry.async_on_unload( + await ssdp.async_register_callback( + self.hass, + self.async_ssdp_callback, + {"_udn": self.udn, "NTS": NotificationSubType.SSDP_BYEBYE}, + ) + ) + + async def async_will_remove_from_hass(self) -> None: + """Handle removal of this source.""" + await self.device_disconnect() + + async def async_ssdp_callback( + self, info: ssdp.SsdpServiceInfo, change: ssdp.SsdpChange + ) -> None: + """Handle notification from SSDP of device state change.""" + LOGGER.debug( + "SSDP %s notification of device %s at %s", + change, + info.ssdp_usn, + info.ssdp_location, + ) + + try: + bootid_str = info.ssdp_headers[ssdp.ATTR_SSDP_BOOTID] + bootid: int | None = int(bootid_str, 10) + except (KeyError, ValueError): + bootid = None + + if change == ssdp.SsdpChange.UPDATE: + # This is an announcement that bootid is about to change + if self._bootid is not None and self._bootid == bootid: + # Store the new value (because our old value matches) so that we + # can ignore subsequent ssdp:alive messages + try: + next_bootid_str = info.ssdp_headers[ssdp.ATTR_SSDP_NEXTBOOTID] + self._bootid = int(next_bootid_str, 10) + except (KeyError, ValueError): + pass + # Nothing left to do until ssdp:alive comes through + return + + if self._bootid is not None and self._bootid != bootid: + # Device has rebooted + # Maybe connection will succeed now + self._ssdp_connect_failed = False + if self._device: + # Drop existing connection and maybe reconnect + await self.device_disconnect() + self._bootid = bootid + + if change == ssdp.SsdpChange.BYEBYE: + # Device is going away + if self._device: + # Disconnect from gone device + await self.device_disconnect() + # Maybe the next alive message will result in a successful connection + self._ssdp_connect_failed = False + + if ( + change == ssdp.SsdpChange.ALIVE + and not self._device + and not self._ssdp_connect_failed + ): + assert info.ssdp_location + self.location = info.ssdp_location + try: + await self.device_connect() + except UpnpError as err: + self._ssdp_connect_failed = True + LOGGER.warning( + "Failed connecting to recently alive device at %s: %r", + self.location, + err, + ) + + # Device connection/disconnection + + async def device_connect(self) -> None: + """Connect to the device now that it's available.""" + LOGGER.debug("Connecting to device at %s", self.location) + + async with self._device_lock: + if self._device: + LOGGER.debug("Trying to connect when device already connected") + return + + if not self.location: + LOGGER.debug("Not connecting because location is not known") + return + + domain_data = get_domain_data(self.hass) + + # Connect to the base UPNP device + upnp_device = await domain_data.upnp_factory.async_create_device( + self.location + ) + + # Create profile wrapper + self._device = DmsDevice(upnp_device, domain_data.event_handler) + + # Update state variables. We don't care if they change, so this is + # only done once, here. + await self._device.async_update() + + async def device_disconnect(self) -> None: + """Destroy connections to the device now that it's not available. + + Also call when removing this device wrapper from hass to clean up connections. + """ + async with self._device_lock: + if not self._device: + LOGGER.debug("Disconnecting from device that's not connected") + return + + LOGGER.debug("Disconnecting from %s", self._device.name) + + self._device = None + + # Device properties + + @property + def available(self) -> bool: + """Device is available when we have a connection to it.""" + return self._device is not None and self._device.profile_device.available + + @property + def usn(self) -> str: + """Get the USN (Unique Service Name) for the wrapped UPnP device end-point.""" + return self.config_entry.data[CONF_DEVICE_ID] + + @property + def udn(self) -> str: + """Get the UDN (Unique Device Name) based on the USN.""" + return self.usn.partition("::")[0] + + @property + def name(self) -> str: + """Return a name for the media server.""" + return self.config_entry.title + + @property + def icon(self) -> str | None: + """Return an URL to an icon for the media server.""" + if not self._device: + return None + + return self._device.icon + + # MediaSource methods + + async def async_resolve_media(self, identifier: str) -> DidlPlayMedia: + """Resolve a media item to a playable item.""" + LOGGER.debug("async_resolve_media(%s)", identifier) + action, parameters = _parse_identifier(identifier) + + if action is Action.OBJECT: + return await self.async_resolve_object(parameters) + + if action is Action.PATH: + object_id = await self.async_resolve_path(parameters) + return await self.async_resolve_object(object_id) + + if action is Action.SEARCH: + return await self.async_resolve_search(parameters) + + LOGGER.debug("Invalid identifier %s", identifier) + raise Unresolvable(f"Invalid identifier {identifier}") + + async def async_browse_media(self, identifier: str | None) -> BrowseMediaSource: + """Browse media.""" + LOGGER.debug("async_browse_media(%s)", identifier) + action, parameters = _parse_identifier(identifier) + + if action is Action.OBJECT: + return await self.async_browse_object(parameters) + + if action is Action.PATH: + object_id = await self.async_resolve_path(parameters) + return await self.async_browse_object(object_id) + + if action is Action.SEARCH: + return await self.async_browse_search(parameters) + + return await self.async_browse_object(ROOT_OBJECT_ID) + + # DMS methods + + @catch_request_errors + async def async_resolve_object(self, object_id: str) -> DidlPlayMedia: + """Return a playable media item specified by ObjectID.""" + assert self._device + + item = await self._device.async_browse_metadata( + object_id, metadata_filter=DLNA_RESOLVE_FILTER + ) + + # Use the first playable resource + return self._didl_to_play_media(item) + + @catch_request_errors + async def async_resolve_path(self, path: str) -> str: + """Return an Object ID resolved from a path string.""" + assert self._device + + # Iterate through the path, searching for a matching title within the + # DLNA object hierarchy. + object_id = ROOT_OBJECT_ID + for node in path.split(PATH_SEP): + if not node: + # Skip empty names, for when multiple slashes are involved, e.g // + continue + + criteria = ( + f'@parentID="{_esc_quote(object_id)}" and dc:title="{_esc_quote(node)}"' + ) + try: + result = await self._device.async_search_directory( + object_id, + search_criteria=criteria, + metadata_filter=DLNA_PATH_FILTER, + requested_count=1, + ) + except UpnpActionError as err: + LOGGER.debug("Error in call to async_search_directory: %r", err) + if err.error_code == ContentDirectoryErrorCode.NO_SUCH_CONTAINER: + raise Unresolvable(f"No such container: {object_id}") from err + # Search failed, but can still try browsing children + else: + if result.total_matches > 1: + raise Unresolvable(f"Too many items found for {node} in {path}") + + if result.result: + object_id = result.result[0].id + continue + + # Nothing was found via search, fall back to iterating children + result = await self._device.async_browse_direct_children( + object_id, metadata_filter=DLNA_PATH_FILTER + ) + + if result.total_matches == 0 or not result.result: + raise Unresolvable(f"No contents for {node} in {path}") + + node_lower = node.lower() + for child in result.result: + if child.title.lower() == node_lower: + object_id = child.id + break + else: + # Examining all direct children failed too + raise Unresolvable(f"Nothing found for {node} in {path}") + return object_id + + @catch_request_errors + async def async_resolve_search(self, query: str) -> DidlPlayMedia: + """Return first playable media item found by the query string.""" + assert self._device + + result = await self._device.async_search_directory( + container_id=ROOT_OBJECT_ID, + search_criteria=query, + metadata_filter=DLNA_RESOLVE_FILTER, + requested_count=1, + ) + + if result.total_matches == 0 or not result.result: + raise Unresolvable(f"Nothing found for {query}") + + # Use the first result, even if it doesn't have a playable resource + item = result.result[0] + + if not isinstance(item, didl_lite.DidlObject): + raise Unresolvable(f"{item} is not a DidlObject") + + return self._didl_to_play_media(item) + + @catch_request_errors + async def async_browse_object(self, object_id: str) -> BrowseMediaSource: + """Return the contents of a DLNA container by ObjectID.""" + assert self._device + + base_object = await self._device.async_browse_metadata( + object_id, metadata_filter=DLNA_BROWSE_FILTER + ) + + children = await self._device.async_browse_direct_children( + object_id, + metadata_filter=DLNA_BROWSE_FILTER, + sort_criteria=DLNA_SORT_CRITERIA, + ) + + return self._didl_to_media_source(base_object, children) + + @catch_request_errors + async def async_browse_search(self, query: str) -> BrowseMediaSource: + """Return all media items found by the query string.""" + assert self._device + + result = await self._device.async_search_directory( + container_id=ROOT_OBJECT_ID, + search_criteria=query, + metadata_filter=DLNA_BROWSE_FILTER, + ) + + children = [ + self._didl_to_media_source(child) + for child in result.result + if isinstance(child, didl_lite.DidlObject) + ] + + media_source = BrowseMediaSource( + domain=DOMAIN, + identifier=self._make_identifier(Action.SEARCH, query), + media_class=MEDIA_CLASS_DIRECTORY, + media_content_type="", + title="Search results", + can_play=False, + can_expand=True, + children=children, + ) + + media_source.calculate_children_class() + + return media_source + + def _didl_to_play_media(self, item: didl_lite.DidlObject) -> DidlPlayMedia: + """Return the first playable resource from a DIDL-Lite object.""" + assert self._device + + if not item.res: + LOGGER.debug("Object %s has no resources", item.id) + raise Unresolvable("Object has no resources") + + for resource in item.res: + if not resource.uri: + continue + if mime_type := _resource_mime_type(resource): + url = self._device.get_absolute_url(resource.uri) + LOGGER.debug("Resolved to url %s MIME %s", url, mime_type) + return DidlPlayMedia(url, mime_type, item) + + LOGGER.debug("Object %s has no playable resources", item.id) + raise Unresolvable("Object has no playable resources") + + def _didl_to_media_source( + self, + item: didl_lite.DidlObject, + browsed_children: DmsDevice.BrowseResult | None = None, + ) -> BrowseMediaSource: + """Convert a DIDL-Lite object to a browse media source.""" + children: list[BrowseMediaSource] | None = None + + if browsed_children: + children = [ + self._didl_to_media_source(child) + for child in browsed_children.result + if isinstance(child, didl_lite.DidlObject) + ] + + # Can expand if it has children (even if we don't have them yet), or its + # a container type. Otherwise the front-end will try to play it (even if + # can_play is False). + try: + child_count = int(item.child_count) + except (AttributeError, TypeError, ValueError): + child_count = 0 + can_expand = ( + bool(children) or child_count > 0 or isinstance(item, didl_lite.Container) + ) + + # Can play if item has any resource that can be streamed over the network + can_play = any(_resource_is_streaming(res) for res in item.res) + + # Use server name for root object, not "root" + title = self.name if item.id == ROOT_OBJECT_ID else item.title + + mime_type = _resource_mime_type(item.res[0]) if item.res else None + media_content_type = mime_type or item.upnp_class + + media_source = BrowseMediaSource( + domain=DOMAIN, + identifier=self._make_identifier(Action.OBJECT, item.id), + media_class=MEDIA_CLASS_MAP.get(item.upnp_class, ""), + media_content_type=media_content_type, + title=title, + can_play=can_play, + can_expand=can_expand, + children=children, + thumbnail=self._didl_thumbnail_url(item), + ) + + media_source.calculate_children_class() + + return media_source + + def _didl_thumbnail_url(self, item: didl_lite.DidlObject) -> str | None: + """Return absolute URL of a thumbnail for a DIDL-Lite object. + + Some objects have the thumbnail in albumArtURI, others in an image + resource. + """ + assert self._device + + # Based on DmrDevice.media_image_url from async_upnp_client. + if album_art_uri := getattr(item, "album_art_uri", None): + return self._device.get_absolute_url(album_art_uri) + + for resource in item.res: + if not resource.protocol_info or not resource.uri: + continue + if resource.protocol_info.startswith("http-get:*:image/"): + return self._device.get_absolute_url(resource.uri) + + return None + + def _make_identifier(self, action: Action, object_id: str) -> str: + """Make an identifier for BrowseMediaSource.""" + return f"{self.source_id}/{action}{object_id}" + + +class Action(StrEnum): + """Actions that can be specified in a DMS media-source identifier.""" + + OBJECT = PATH_OBJECT_ID_FLAG + PATH = PATH_SEP + SEARCH = PATH_SEARCH_FLAG + + +def _parse_identifier(identifier: str | None) -> tuple[Action | None, str]: + """Parse the media identifier component of a media-source URI.""" + if not identifier: + return None, "" + if identifier.startswith(PATH_OBJECT_ID_FLAG): + return Action.OBJECT, identifier[1:] + if identifier.startswith(PATH_SEP): + return Action.PATH, identifier[1:] + if identifier.startswith(PATH_SEARCH_FLAG): + return Action.SEARCH, identifier[1:] + return Action.PATH, identifier + + +def _resource_is_streaming(resource: didl_lite.Resource) -> bool: + """Determine if a resource can be streamed across a network.""" + # Err on the side of "True" if the protocol info is not available + if not resource.protocol_info: + return True + protocol = resource.protocol_info.split(":")[0].lower() + return protocol.lower() in STREAMABLE_PROTOCOLS + + +def _resource_mime_type(resource: didl_lite.Resource) -> str | None: + """Return the MIME type of a resource, if specified.""" + # This is the contentFormat portion of the ProtocolInfo for an http-get stream + if not resource.protocol_info: + return None + try: + protocol, _, content_format, _ = resource.protocol_info.split(":", 3) + except ValueError: + return None + if protocol.lower() in STREAMABLE_PROTOCOLS: + return content_format + return None + + +def _esc_quote(contents: str) -> str: + """Escape string contents for DLNA search quoted values. + + See ContentDirectory:v4, section 4.1.2. + """ + return contents.replace("\\", "\\\\").replace('"', '\\"') diff --git a/homeassistant/components/dlna_dms/manifest.json b/homeassistant/components/dlna_dms/manifest.json new file mode 100644 index 00000000000000..feee4b6e9031a7 --- /dev/null +++ b/homeassistant/components/dlna_dms/manifest.json @@ -0,0 +1,29 @@ +{ + "domain": "dlna_dms", + "name": "DLNA Digital Media Server", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/dlna_dms", + "requirements": ["async-upnp-client==0.23.5"], + "dependencies": ["media_source", "ssdp"], + "ssdp": [ + { + "deviceType": "urn:schemas-upnp-org:device:MediaServer:1", + "st": "urn:schemas-upnp-org:device:MediaServer:1" + }, + { + "deviceType": "urn:schemas-upnp-org:device:MediaServer:2", + "st": "urn:schemas-upnp-org:device:MediaServer:2" + }, + { + "deviceType": "urn:schemas-upnp-org:device:MediaServer:3", + "st": "urn:schemas-upnp-org:device:MediaServer:3" + }, + { + "deviceType": "urn:schemas-upnp-org:device:MediaServer:4", + "st": "urn:schemas-upnp-org:device:MediaServer:4" + } + ], + "codeowners": ["@chishm"], + "iot_class": "local_polling", + "quality_scale": "platinum" +} \ No newline at end of file diff --git a/homeassistant/components/dlna_dms/media_source.py b/homeassistant/components/dlna_dms/media_source.py new file mode 100644 index 00000000000000..84910b7ff67ef1 --- /dev/null +++ b/homeassistant/components/dlna_dms/media_source.py @@ -0,0 +1,126 @@ +"""Implementation of DLNA DMS as a media source. + +URIs look like "media-source://dlna_dms//" + +Media identifiers can look like: +* `/path/to/file`: slash-separated path through the Content Directory +* `:ObjectID`: colon followed by a server-assigned ID for an object +* `?query`: question mark followed by a query string to search for, + see [DLNA ContentDirectory SearchCriteria](http://www.upnp.org/specs/av/UPnP-av-ContentDirectory-v1-Service.pdf) + for the syntax. +""" + +from __future__ import annotations + +from homeassistant.components.media_player.const import ( + MEDIA_CLASS_CHANNEL, + MEDIA_CLASS_DIRECTORY, + MEDIA_TYPE_CHANNEL, + MEDIA_TYPE_CHANNELS, +) +from homeassistant.components.media_player.errors import BrowseError +from homeassistant.components.media_source.error import Unresolvable +from homeassistant.components.media_source.models import ( + BrowseMediaSource, + MediaSource, + MediaSourceItem, +) +from homeassistant.core import HomeAssistant + +from .const import DOMAIN, LOGGER, PATH_OBJECT_ID_FLAG, ROOT_OBJECT_ID, SOURCE_SEP +from .dms import DidlPlayMedia, get_domain_data + + +async def async_get_media_source(hass: HomeAssistant): + """Set up DLNA DMS media source.""" + LOGGER.debug("Setting up DLNA media sources") + return DmsMediaSource(hass) + + +class DmsMediaSource(MediaSource): + """Provide DLNA Media Servers as media sources.""" + + name = "DLNA Servers" + + def __init__(self, hass: HomeAssistant) -> None: + """Initialize DLNA source.""" + super().__init__(DOMAIN) + + self.hass = hass + + async def async_resolve_media(self, item: MediaSourceItem) -> DidlPlayMedia: + """Resolve a media item to a playable item.""" + dms_data = get_domain_data(self.hass) + if not dms_data.sources: + raise Unresolvable("No sources have been configured") + + source_id, media_id = _parse_identifier(item) + if not source_id: + raise Unresolvable(f"No source ID in {item.identifier}") + if not media_id: + raise Unresolvable(f"No media ID in {item.identifier}") + + try: + source = dms_data.sources[source_id] + except KeyError as err: + raise Unresolvable(f"Unknown source ID: {source_id}") from err + + return await source.async_resolve_media(media_id) + + async def async_browse_media(self, item: MediaSourceItem) -> BrowseMediaSource: + """Browse media.""" + dms_data = get_domain_data(self.hass) + if not dms_data.sources: + raise BrowseError("No sources have been configured") + + source_id, media_id = _parse_identifier(item) + LOGGER.debug("Browsing for %s / %s", source_id, media_id) + + if not source_id and len(dms_data.sources) > 1: + # Browsing the root of dlna_dms with more than one server, return + # all known servers. + base = BrowseMediaSource( + domain=DOMAIN, + identifier="", + media_class=MEDIA_CLASS_DIRECTORY, + media_content_type=MEDIA_TYPE_CHANNELS, + title=self.name, + can_play=False, + can_expand=True, + children_media_class=MEDIA_CLASS_CHANNEL, + ) + + base.children = [ + BrowseMediaSource( + domain=DOMAIN, + identifier=f"{source_id}/{PATH_OBJECT_ID_FLAG}{ROOT_OBJECT_ID}", + media_class=MEDIA_CLASS_CHANNEL, + media_content_type=MEDIA_TYPE_CHANNEL, + title=source.name, + can_play=False, + can_expand=True, + thumbnail=source.icon, + ) + for source_id, source in dms_data.sources.items() + ] + + return base + + if not source_id: + # No source specified, default to the first registered + source_id = next(iter(dms_data.sources)) + + try: + source = dms_data.sources[source_id] + except KeyError as err: + raise BrowseError(f"Unknown source ID: {source_id}") from err + + return await source.async_browse_media(media_id) + + +def _parse_identifier(item: MediaSourceItem) -> tuple[str | None, str | None]: + """Parse the source_id and media identifier from a media source item.""" + if not item.identifier: + return None, None + source_id, _, media_id = item.identifier.partition(SOURCE_SEP) + return source_id or None, media_id or None diff --git a/homeassistant/components/dlna_dms/strings.json b/homeassistant/components/dlna_dms/strings.json new file mode 100644 index 00000000000000..9b59960a78af42 --- /dev/null +++ b/homeassistant/components/dlna_dms/strings.json @@ -0,0 +1,24 @@ +{ + "config": { + "flow_title": "{name}", + "step": { + "user": { + "title": "Discovered DLNA DMA devices", + "description": "Choose a device to configure", + "data": { + "host": "[%key:common::config_flow::data::host%]" + } + }, + "confirm": { + "description": "[%key:common::config_flow::description::confirm_setup%]" + } + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", + "bad_ssdp": "SSDP data is missing a required value", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "not_dms": "Device is not a supported Media Server" + } + } +} diff --git a/homeassistant/components/dlna_dms/translations/en.json b/homeassistant/components/dlna_dms/translations/en.json new file mode 100644 index 00000000000000..6d07a25a27d275 --- /dev/null +++ b/homeassistant/components/dlna_dms/translations/en.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured", + "already_in_progress": "Configuration flow is already in progress", + "bad_ssdp": "SSDP data is missing a required value", + "no_devices_found": "No devices found on the network", + "not_dms": "Device is not a supported Media Server" + }, + "flow_title": "{name}", + "step": { + "confirm": { + "description": "Do you want to start set up?" + }, + "user": { + "data": { + "host": "Host" + }, + "description": "Choose a device to configure", + "title": "Discovered DLNA DMA devices" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index ddec2deb65e34d..5530c73ed5098f 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -72,6 +72,7 @@ "dialogflow", "directv", "dlna_dmr", + "dlna_dms", "dnsip", "doorbird", "dsmr", diff --git a/homeassistant/generated/ssdp.py b/homeassistant/generated/ssdp.py index 1a243d954b9f0e..f0117e2a9c2ad2 100644 --- a/homeassistant/generated/ssdp.py +++ b/homeassistant/generated/ssdp.py @@ -97,6 +97,24 @@ "st": "urn:schemas-upnp-org:device:MediaRenderer:3" } ], + "dlna_dms": [ + { + "deviceType": "urn:schemas-upnp-org:device:MediaServer:1", + "st": "urn:schemas-upnp-org:device:MediaServer:1" + }, + { + "deviceType": "urn:schemas-upnp-org:device:MediaServer:2", + "st": "urn:schemas-upnp-org:device:MediaServer:2" + }, + { + "deviceType": "urn:schemas-upnp-org:device:MediaServer:3", + "st": "urn:schemas-upnp-org:device:MediaServer:3" + }, + { + "deviceType": "urn:schemas-upnp-org:device:MediaServer:4", + "st": "urn:schemas-upnp-org:device:MediaServer:4" + } + ], "fritz": [ { "st": "urn:schemas-upnp-org:device:fritzbox:1" diff --git a/requirements_all.txt b/requirements_all.txt index 93c5a9072ac505..511f8abb7c52d0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -341,6 +341,7 @@ asmog==0.0.6 asterisk_mbox==0.5.0 # homeassistant.components.dlna_dmr +# homeassistant.components.dlna_dms # homeassistant.components.ssdp # homeassistant.components.upnp # homeassistant.components.yeelight diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 723daaca0cd80b..926d5e0d6ee569 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -249,6 +249,7 @@ aprslib==0.7.0 arcam-fmj==0.12.0 # homeassistant.components.dlna_dmr +# homeassistant.components.dlna_dms # homeassistant.components.ssdp # homeassistant.components.upnp # homeassistant.components.yeelight diff --git a/tests/components/dlna_dms/__init__.py b/tests/components/dlna_dms/__init__.py new file mode 100644 index 00000000000000..ed53881052161d --- /dev/null +++ b/tests/components/dlna_dms/__init__.py @@ -0,0 +1 @@ +"""Tests for the DLNA MediaServer integration.""" diff --git a/tests/components/dlna_dms/conftest.py b/tests/components/dlna_dms/conftest.py new file mode 100644 index 00000000000000..6764001be31d6d --- /dev/null +++ b/tests/components/dlna_dms/conftest.py @@ -0,0 +1,131 @@ +"""Fixtures for DLNA DMS tests.""" +from __future__ import annotations + +from collections.abc import AsyncGenerator, Iterable +from typing import Final +from unittest.mock import Mock, create_autospec, patch, seal + +from async_upnp_client import UpnpDevice, UpnpService +from async_upnp_client.utils import absolute_url +import pytest + +from homeassistant.components.dlna_dms.const import DOMAIN +from homeassistant.components.dlna_dms.dms import DlnaDmsData, get_domain_data +from homeassistant.const import CONF_DEVICE_ID, CONF_URL +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + +MOCK_DEVICE_HOST: Final = "192.88.99.21" +MOCK_DEVICE_BASE_URL: Final = f"http://{MOCK_DEVICE_HOST}" +MOCK_DEVICE_LOCATION: Final = MOCK_DEVICE_BASE_URL + "/dms_description.xml" +MOCK_DEVICE_NAME: Final = "Test Server Device" +MOCK_DEVICE_TYPE: Final = "urn:schemas-upnp-org:device:MediaServer:1" +MOCK_DEVICE_UDN: Final = "uuid:7bf34520-f034-4fa2-8d2d-2f709d4221ef" +MOCK_DEVICE_USN: Final = f"{MOCK_DEVICE_UDN}::{MOCK_DEVICE_TYPE}" +MOCK_SOURCE_ID: Final = "test_server_device" + +LOCAL_IP: Final = "192.88.99.1" +EVENT_CALLBACK_URL: Final = "http://192.88.99.1/notify" + +NEW_DEVICE_LOCATION: Final = "http://192.88.99.7" + "/dmr_description.xml" + + +@pytest.fixture +def upnp_factory_mock() -> Iterable[Mock]: + """Mock the UpnpFactory class to construct DMS-style UPnP devices.""" + with patch( + "homeassistant.components.dlna_dms.dms.UpnpFactory", + autospec=True, + spec_set=True, + ) as upnp_factory: + upnp_device = create_autospec(UpnpDevice, instance=True) + upnp_device.name = MOCK_DEVICE_NAME + upnp_device.udn = MOCK_DEVICE_UDN + upnp_device.device_url = MOCK_DEVICE_LOCATION + upnp_device.device_type = MOCK_DEVICE_TYPE + upnp_device.available = True + upnp_device.parent_device = None + upnp_device.root_device = upnp_device + upnp_device.all_devices = [upnp_device] + upnp_device.services = { + "urn:schemas-upnp-org:service:ContentDirectory:1": create_autospec( + UpnpService, + instance=True, + service_type="urn:schemas-upnp-org:service:ContentDirectory:1", + service_id="urn:upnp-org:serviceId:ContentDirectory", + ), + "urn:schemas-upnp-org:service:ConnectionManager:1": create_autospec( + UpnpService, + instance=True, + service_type="urn:schemas-upnp-org:service:ConnectionManager:1", + service_id="urn:upnp-org:serviceId:ConnectionManager", + ), + } + seal(upnp_device) + upnp_factory_instance = upnp_factory.return_value + upnp_factory_instance.async_create_device.return_value = upnp_device + + yield upnp_factory_instance + + +@pytest.fixture +async def domain_data_mock( + hass: HomeAssistant, aioclient_mock, upnp_factory_mock +) -> AsyncGenerator[DlnaDmsData, None]: + """Mock some global data used by this component. + + This includes network clients and library object factories. Mocking it + prevents network use. + + Yields the actual domain data, for ease of access + """ + with patch( + "homeassistant.components.dlna_dms.dms.AiohttpSessionRequester", autospec=True + ): + yield get_domain_data(hass) + + +@pytest.fixture +def config_entry_mock() -> MockConfigEntry: + """Mock a config entry for this platform.""" + mock_entry = MockConfigEntry( + unique_id=MOCK_DEVICE_USN, + domain=DOMAIN, + data={ + CONF_URL: MOCK_DEVICE_LOCATION, + CONF_DEVICE_ID: MOCK_DEVICE_USN, + }, + title=MOCK_DEVICE_NAME, + ) + return mock_entry + + +@pytest.fixture +def dms_device_mock(upnp_factory_mock: Mock) -> Iterable[Mock]: + """Mock the async_upnp_client DMS device, initially connected.""" + with patch( + "homeassistant.components.dlna_dms.dms.DmsDevice", autospec=True + ) as constructor: + device = constructor.return_value + device.on_event = None + device.profile_device = upnp_factory_mock.async_create_device.return_value + device.icon = MOCK_DEVICE_BASE_URL + "/icon.jpg" + device.udn = "device_udn" + device.manufacturer = "device_manufacturer" + device.model_name = "device_model_name" + device.name = "device_name" + device.get_absolute_url.side_effect = lambda url: absolute_url( + MOCK_DEVICE_BASE_URL, url + ) + + yield device + + +@pytest.fixture(autouse=True) +def ssdp_scanner_mock() -> Iterable[Mock]: + """Mock the SSDP module.""" + with patch("homeassistant.components.ssdp.Scanner", autospec=True) as mock_scanner: + reg_callback = mock_scanner.return_value.async_register_callback + reg_callback.return_value = Mock(return_value=None) + yield mock_scanner.return_value diff --git a/tests/components/dlna_dms/test_config_flow.py b/tests/components/dlna_dms/test_config_flow.py new file mode 100644 index 00000000000000..df8d55dbc250d6 --- /dev/null +++ b/tests/components/dlna_dms/test_config_flow.py @@ -0,0 +1,346 @@ +"""Test the DLNA DMS config flow.""" +from __future__ import annotations + +import dataclasses +from typing import Final +from unittest.mock import Mock + +from async_upnp_client import UpnpError +import pytest + +from homeassistant import config_entries, data_entry_flow +from homeassistant.components import ssdp +from homeassistant.components.dlna_dms.const import DOMAIN +from homeassistant.const import CONF_DEVICE_ID, CONF_HOST, CONF_URL +from homeassistant.core import HomeAssistant + +from .conftest import ( + MOCK_DEVICE_HOST, + MOCK_DEVICE_LOCATION, + MOCK_DEVICE_NAME, + MOCK_DEVICE_TYPE, + MOCK_DEVICE_UDN, + MOCK_DEVICE_USN, + NEW_DEVICE_LOCATION, +) + +from tests.common import MockConfigEntry + +# Auto-use the domain_data_mock and dms_device_mock fixtures for every test in this module +pytestmark = [ + pytest.mark.usefixtures("domain_data_mock"), + pytest.mark.usefixtures("dms_device_mock"), +] + +WRONG_DEVICE_TYPE: Final = "urn:schemas-upnp-org:device:InternetGatewayDevice:1" + +MOCK_ROOT_DEVICE_UDN: Final = "ROOT_DEVICE" + +MOCK_DISCOVERY: Final = ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_location=MOCK_DEVICE_LOCATION, + ssdp_udn=MOCK_DEVICE_UDN, + ssdp_st=MOCK_DEVICE_TYPE, + upnp={ + ssdp.ATTR_UPNP_UDN: MOCK_ROOT_DEVICE_UDN, + ssdp.ATTR_UPNP_DEVICE_TYPE: MOCK_DEVICE_TYPE, + ssdp.ATTR_UPNP_FRIENDLY_NAME: MOCK_DEVICE_NAME, + ssdp.ATTR_UPNP_SERVICE_LIST: { + "service": [ + { + "SCPDURL": "/ContentDirectory/scpd.xml", + "controlURL": "/ContentDirectory/control.xml", + "eventSubURL": "/ContentDirectory/event.xml", + "serviceId": "urn:upnp-org:serviceId:ContentDirectory", + "serviceType": "urn:schemas-upnp-org:service:ContentDirectory:1", + }, + { + "SCPDURL": "/ConnectionManager/scpd.xml", + "controlURL": "/ConnectionManager/control.xml", + "eventSubURL": "/ConnectionManager/event.xml", + "serviceId": "urn:upnp-org:serviceId:ConnectionManager", + "serviceType": "urn:schemas-upnp-org:service:ConnectionManager:1", + }, + ] + }, + }, + x_homeassistant_matching_domains={DOMAIN}, +) + + +async def test_user_flow(hass: HomeAssistant, ssdp_scanner_mock: Mock) -> None: + """Test user-init'd flow, user selects discovered device.""" + ssdp_scanner_mock.async_get_discovery_info_by_st.side_effect = [ + [MOCK_DISCOVERY], + [], + [], + [], + ] + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] is None + assert result["step_id"] == "user" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_HOST: MOCK_DEVICE_HOST} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == MOCK_DEVICE_NAME + assert result["data"] == { + CONF_URL: MOCK_DEVICE_LOCATION, + CONF_DEVICE_ID: MOCK_DEVICE_USN, + } + assert result["options"] == {} + + await hass.async_block_till_done() + + +async def test_user_flow_no_devices( + hass: HomeAssistant, ssdp_scanner_mock: Mock +) -> None: + """Test user-init'd flow, there's really no devices to choose from.""" + ssdp_scanner_mock.async_get_discovery_info_by_st.side_effect = [ + [], + [], + [], + [], + ] + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "no_devices_found" + + +async def test_ssdp_flow_success(hass: HomeAssistant) -> None: + """Test that SSDP discovery with an available device works.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_SSDP}, + data=MOCK_DISCOVERY, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "confirm" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == MOCK_DEVICE_NAME + assert result["data"] == { + CONF_URL: MOCK_DEVICE_LOCATION, + CONF_DEVICE_ID: MOCK_DEVICE_USN, + } + assert result["options"] == {} + + +async def test_ssdp_flow_unavailable( + hass: HomeAssistant, domain_data_mock: Mock +) -> None: + """Test that SSDP discovery with an unavailable device still succeeds. + + All the required information for configuration is obtained from the SSDP + message, there's no need to connect to the device to configure it. + """ + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_SSDP}, + data=MOCK_DISCOVERY, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "confirm" + + domain_data_mock.upnp_factory.async_create_device.side_effect = UpnpError + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == MOCK_DEVICE_NAME + assert result["data"] == { + CONF_URL: MOCK_DEVICE_LOCATION, + CONF_DEVICE_ID: MOCK_DEVICE_USN, + } + assert result["options"] == {} + + +async def test_ssdp_flow_existing( + hass: HomeAssistant, config_entry_mock: MockConfigEntry +) -> None: + """Test that SSDP discovery of existing config entry updates the URL.""" + config_entry_mock.add_to_hass(hass) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_SSDP}, + data=ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_st="mock_st", + ssdp_location=NEW_DEVICE_LOCATION, + ssdp_udn=MOCK_DEVICE_UDN, + upnp={ + ssdp.ATTR_UPNP_UDN: MOCK_ROOT_DEVICE_UDN, + ssdp.ATTR_UPNP_DEVICE_TYPE: MOCK_DEVICE_TYPE, + ssdp.ATTR_UPNP_FRIENDLY_NAME: MOCK_DEVICE_NAME, + }, + ), + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + assert config_entry_mock.data[CONF_URL] == NEW_DEVICE_LOCATION + + +async def test_ssdp_flow_duplicate_location( + hass: HomeAssistant, config_entry_mock: MockConfigEntry +) -> None: + """Test that discovery of device with URL matching existing entry gets aborted.""" + config_entry_mock.add_to_hass(hass) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_SSDP}, + data=MOCK_DISCOVERY, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + assert config_entry_mock.data[CONF_URL] == MOCK_DEVICE_LOCATION + + +async def test_ssdp_flow_bad_data( + hass: HomeAssistant, config_entry_mock: MockConfigEntry +) -> None: + """Test bad SSDP discovery information is rejected cleanly.""" + # Missing location + discovery = dataclasses.replace(MOCK_DISCOVERY, ssdp_location="") + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_SSDP}, + data=discovery, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "bad_ssdp" + + # Missing USN + discovery = dataclasses.replace(MOCK_DISCOVERY, ssdp_usn="") + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_SSDP}, + data=discovery, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "bad_ssdp" + + +async def test_duplicate_name( + hass: HomeAssistant, config_entry_mock: MockConfigEntry +) -> None: + """Test device with name same as another results in no error.""" + config_entry_mock.add_to_hass(hass) + + mock_entry_1 = MockConfigEntry( + unique_id="mock_entry_1", + domain=DOMAIN, + data={ + CONF_URL: "not-important", + CONF_DEVICE_ID: "not-important", + }, + title=MOCK_DEVICE_NAME, + ) + mock_entry_1.add_to_hass(hass) + + # New UDN, USN, and location to be sure it's a new device + new_device_udn = "uuid:7bf34520-f034-4fa2-8d2d-2f709d422000" + new_device_usn = f"{new_device_udn}::{MOCK_DEVICE_TYPE}" + new_device_location = "http://192.88.99.22/dms_description.xml" + discovery = dataclasses.replace( + MOCK_DISCOVERY, + ssdp_usn=new_device_usn, + ssdp_location=new_device_location, + ssdp_udn=new_device_udn, + ) + discovery.upnp = dict(discovery.upnp) + discovery.upnp[ssdp.ATTR_UPNP_UDN] = new_device_udn + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_SSDP}, + data=discovery, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "confirm" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == MOCK_DEVICE_NAME + assert result["data"] == { + CONF_URL: new_device_location, + CONF_DEVICE_ID: new_device_usn, + } + assert result["options"] == {} + + +async def test_ssdp_flow_upnp_udn( + hass: HomeAssistant, config_entry_mock: MockConfigEntry +) -> None: + """Test that SSDP discovery ignores the root device's UDN.""" + config_entry_mock.add_to_hass(hass) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_SSDP}, + data=ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_location=NEW_DEVICE_LOCATION, + ssdp_udn=MOCK_DEVICE_UDN, + ssdp_st=MOCK_DEVICE_TYPE, + upnp={ + ssdp.ATTR_UPNP_UDN: "DIFFERENT_ROOT_DEVICE", + ssdp.ATTR_UPNP_DEVICE_TYPE: MOCK_DEVICE_TYPE, + ssdp.ATTR_UPNP_FRIENDLY_NAME: MOCK_DEVICE_NAME, + }, + ), + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + assert config_entry_mock.data[CONF_URL] == NEW_DEVICE_LOCATION + + +async def test_ssdp_missing_services(hass: HomeAssistant) -> None: + """Test SSDP ignores devices that are missing required services.""" + # No services defined at all + discovery = dataclasses.replace(MOCK_DISCOVERY) + discovery.upnp = dict(discovery.upnp) + del discovery.upnp[ssdp.ATTR_UPNP_SERVICE_LIST] + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_SSDP}, + data=discovery, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "not_dms" + + # ContentDirectory service is missing + discovery = dataclasses.replace(MOCK_DISCOVERY) + discovery.upnp = dict(discovery.upnp) + discovery.upnp[ssdp.ATTR_UPNP_SERVICE_LIST] = { + "service": [ + service + for service in discovery.upnp[ssdp.ATTR_UPNP_SERVICE_LIST]["service"] + if service.get("serviceId") != "urn:upnp-org:serviceId:ContentDirectory" + ] + } + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_SSDP}, data=discovery + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "not_dms" diff --git a/tests/components/dlna_dms/test_device_availability.py b/tests/components/dlna_dms/test_device_availability.py new file mode 100644 index 00000000000000..a0cfb3ab2d2ffa --- /dev/null +++ b/tests/components/dlna_dms/test_device_availability.py @@ -0,0 +1,705 @@ +"""Test how the DmsDeviceSource handles available and unavailable devices.""" +from __future__ import annotations + +import asyncio +from collections.abc import AsyncIterable +import logging +from unittest.mock import ANY, DEFAULT, Mock, patch + +from async_upnp_client.exceptions import UpnpConnectionError, UpnpError +from didl_lite import didl_lite +import pytest + +from homeassistant.components import ssdp +from homeassistant.components.dlna_dms.const import DOMAIN +from homeassistant.components.dlna_dms.dms import DmsDeviceSource, get_domain_data +from homeassistant.components.media_player.errors import BrowseError +from homeassistant.components.media_source.error import Unresolvable +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from .conftest import ( + MOCK_DEVICE_LOCATION, + MOCK_DEVICE_NAME, + MOCK_DEVICE_TYPE, + MOCK_DEVICE_UDN, + MOCK_DEVICE_USN, + NEW_DEVICE_LOCATION, +) + +from tests.common import MockConfigEntry + +# Auto-use the domain_data_mock for every test in this module +pytestmark = [ + pytest.mark.usefixtures("domain_data_mock"), +] + + +async def setup_mock_component( + hass: HomeAssistant, mock_entry: MockConfigEntry +) -> DmsDeviceSource: + """Set up a mock DlnaDmrEntity with the given configuration.""" + mock_entry.add_to_hass(hass) + assert await async_setup_component(hass, DOMAIN, {}) is True + await hass.async_block_till_done() + + domain_data = get_domain_data(hass) + return next(iter(domain_data.devices.values())) + + +@pytest.fixture +async def connected_source_mock( + hass: HomeAssistant, + config_entry_mock: MockConfigEntry, + ssdp_scanner_mock: Mock, + dms_device_mock: Mock, +) -> AsyncIterable[DmsDeviceSource]: + """Fixture to set up a mock DmsDeviceSource in a connected state. + + Yields the entity. Cleans up the entity after the test is complete. + """ + entity = await setup_mock_component(hass, config_entry_mock) + + # Check the entity has registered all needed listeners + assert len(config_entry_mock.update_listeners) == 1 + assert ssdp_scanner_mock.async_register_callback.await_count == 2 + assert ssdp_scanner_mock.async_register_callback.return_value.call_count == 0 + + # Run the test + yield entity + + # Unload config entry to clean up + assert await hass.config_entries.async_remove(config_entry_mock.entry_id) == { + "require_restart": False + } + + # Check entity has cleaned up its resources + assert not config_entry_mock.update_listeners + assert ( + ssdp_scanner_mock.async_register_callback.await_count + == ssdp_scanner_mock.async_register_callback.return_value.call_count + ) + + +@pytest.fixture +async def disconnected_source_mock( + hass: HomeAssistant, + upnp_factory_mock: Mock, + config_entry_mock: MockConfigEntry, + ssdp_scanner_mock: Mock, + dms_device_mock: Mock, +) -> AsyncIterable[DmsDeviceSource]: + """Fixture to set up a mock DmsDeviceSource in a disconnected state. + + Yields the entity. Cleans up the entity after the test is complete. + """ + # Cause the connection attempt to fail + upnp_factory_mock.async_create_device.side_effect = UpnpConnectionError + + entity = await setup_mock_component(hass, config_entry_mock) + + # Check the entity has registered all needed listeners + assert len(config_entry_mock.update_listeners) == 1 + assert ssdp_scanner_mock.async_register_callback.await_count == 2 + assert ssdp_scanner_mock.async_register_callback.return_value.call_count == 0 + + # Run the test + yield entity + + # Unload config entry to clean up + assert await hass.config_entries.async_remove(config_entry_mock.entry_id) == { + "require_restart": False + } + + # Check entity has cleaned up its resources + assert not config_entry_mock.update_listeners + assert ( + ssdp_scanner_mock.async_register_callback.await_count + == ssdp_scanner_mock.async_register_callback.return_value.call_count + ) + + +async def test_unavailable_device( + hass: HomeAssistant, + upnp_factory_mock: Mock, + ssdp_scanner_mock: Mock, + config_entry_mock: MockConfigEntry, +) -> None: + """Test a DlnaDmsEntity with out a connected DmsDevice.""" + # Cause connection attempts to fail + upnp_factory_mock.async_create_device.side_effect = UpnpConnectionError + + with patch( + "homeassistant.components.dlna_dms.dms.DmsDevice", autospec=True + ) as dms_device_constructor_mock: + connected_source_mock = await setup_mock_component(hass, config_entry_mock) + + # Check device is not created + dms_device_constructor_mock.assert_not_called() + + # Check attempt was made to create a device from the supplied URL + upnp_factory_mock.async_create_device.assert_awaited_once_with(MOCK_DEVICE_LOCATION) + # Check SSDP notifications are registered + ssdp_scanner_mock.async_register_callback.assert_any_call( + ANY, {"USN": MOCK_DEVICE_USN} + ) + ssdp_scanner_mock.async_register_callback.assert_any_call( + ANY, {"_udn": MOCK_DEVICE_UDN, "NTS": "ssdp:byebye"} + ) + # Quick check of the state to verify the entity has no connected DmsDevice + assert not connected_source_mock.available + # Check the name matches that supplied + assert connected_source_mock.name == MOCK_DEVICE_NAME + + # Check attempts to browse and resolve media give errors + with pytest.raises(BrowseError): + await connected_source_mock.async_browse_media("/browse_path") + with pytest.raises(BrowseError): + await connected_source_mock.async_browse_media(":browse_object") + with pytest.raises(BrowseError): + await connected_source_mock.async_browse_media("?browse_search") + with pytest.raises(Unresolvable): + await connected_source_mock.async_resolve_media("/resolve_path") + with pytest.raises(Unresolvable): + await connected_source_mock.async_resolve_media(":resolve_object") + with pytest.raises(Unresolvable): + await connected_source_mock.async_resolve_media("?resolve_search") + + # Unload config entry to clean up + assert await hass.config_entries.async_remove(config_entry_mock.entry_id) == { + "require_restart": False + } + + # Confirm SSDP notifications unregistered + assert ssdp_scanner_mock.async_register_callback.return_value.call_count == 2 + + +async def test_become_available( + hass: HomeAssistant, + upnp_factory_mock: Mock, + ssdp_scanner_mock: Mock, + config_entry_mock: MockConfigEntry, + dms_device_mock: Mock, +) -> None: + """Test a device becoming available after the entity is constructed.""" + # Cause connection attempts to fail before adding the entity + upnp_factory_mock.async_create_device.side_effect = UpnpConnectionError + connected_source_mock = await setup_mock_component(hass, config_entry_mock) + assert not connected_source_mock.available + + # Mock device is now available. + upnp_factory_mock.async_create_device.side_effect = None + upnp_factory_mock.async_create_device.reset_mock() + + # Send an SSDP notification from the now alive device + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + await ssdp_callback( + ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_location=NEW_DEVICE_LOCATION, + ssdp_st=MOCK_DEVICE_TYPE, + upnp={}, + ), + ssdp.SsdpChange.ALIVE, + ) + await hass.async_block_till_done() + + # Check device was created from the supplied URL + upnp_factory_mock.async_create_device.assert_awaited_once_with(NEW_DEVICE_LOCATION) + # Quick check of the state to verify the entity has a connected DmsDevice + assert connected_source_mock.available + + # Unload config entry to clean up + assert await hass.config_entries.async_remove(config_entry_mock.entry_id) == { + "require_restart": False + } + + # Confirm SSDP notifications unregistered + assert ssdp_scanner_mock.async_register_callback.return_value.call_count == 2 + + +async def test_alive_but_gone( + hass: HomeAssistant, + upnp_factory_mock: Mock, + ssdp_scanner_mock: Mock, + disconnected_source_mock: DmsDeviceSource, +) -> None: + """Test a device sending an SSDP alive announcement, but not being connectable.""" + upnp_factory_mock.async_create_device.side_effect = UpnpError + + # Send an SSDP notification from the still missing device + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + await ssdp_callback( + ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_location=NEW_DEVICE_LOCATION, + ssdp_st=MOCK_DEVICE_TYPE, + ssdp_headers={ssdp.ATTR_SSDP_BOOTID: "1"}, + upnp={}, + ), + ssdp.SsdpChange.ALIVE, + ) + await hass.async_block_till_done() + + # There should be a connection attempt to the device + upnp_factory_mock.async_create_device.assert_awaited() + + # Device should still be unavailable + assert not disconnected_source_mock.available + + # Send the same SSDP notification, expecting no extra connection attempts + upnp_factory_mock.async_create_device.reset_mock() + await ssdp_callback( + ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_location=NEW_DEVICE_LOCATION, + ssdp_st=MOCK_DEVICE_TYPE, + ssdp_headers={ssdp.ATTR_SSDP_BOOTID: "1"}, + upnp={}, + ), + ssdp.SsdpChange.ALIVE, + ) + await hass.async_block_till_done() + upnp_factory_mock.async_create_device.assert_not_called() + upnp_factory_mock.async_create_device.assert_not_awaited() + assert not disconnected_source_mock.available + + # Send an SSDP notification with a new BOOTID, indicating the device has rebooted + upnp_factory_mock.async_create_device.reset_mock() + await ssdp_callback( + ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_location=NEW_DEVICE_LOCATION, + ssdp_st=MOCK_DEVICE_TYPE, + ssdp_headers={ssdp.ATTR_SSDP_BOOTID: "2"}, + upnp={}, + ), + ssdp.SsdpChange.ALIVE, + ) + await hass.async_block_till_done() + + # Rebooted device (seen via BOOTID) should mean a new connection attempt + upnp_factory_mock.async_create_device.assert_awaited() + assert not disconnected_source_mock.available + + # Send byebye message to indicate device is going away. Next alive message + # should result in a reconnect attempt even with same BOOTID. + upnp_factory_mock.async_create_device.reset_mock() + await ssdp_callback( + ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_st=MOCK_DEVICE_TYPE, + upnp={}, + ), + ssdp.SsdpChange.BYEBYE, + ) + await ssdp_callback( + ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_location=NEW_DEVICE_LOCATION, + ssdp_st=MOCK_DEVICE_TYPE, + ssdp_headers={ssdp.ATTR_SSDP_BOOTID: "2"}, + upnp={}, + ), + ssdp.SsdpChange.ALIVE, + ) + await hass.async_block_till_done() + + # Rebooted device (seen via byebye/alive) should mean a new connection attempt + upnp_factory_mock.async_create_device.assert_awaited() + assert not disconnected_source_mock.available + + +async def test_multiple_ssdp_alive( + hass: HomeAssistant, + upnp_factory_mock: Mock, + ssdp_scanner_mock: Mock, + disconnected_source_mock: DmsDeviceSource, +) -> None: + """Test multiple SSDP alive notifications is ok, only connects to device once.""" + upnp_factory_mock.async_create_device.reset_mock() + + # Contacting the device takes long enough that 2 simultaneous attempts could be made + async def create_device_delayed(_location): + """Delay before continuing with async_create_device. + + This gives a chance for parallel calls to `device_connect` to occur. + """ + await asyncio.sleep(0.1) + return DEFAULT + + upnp_factory_mock.async_create_device.side_effect = create_device_delayed + + # Send two SSDP notifications with the new device URL + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + await ssdp_callback( + ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_location=NEW_DEVICE_LOCATION, + ssdp_st=MOCK_DEVICE_TYPE, + upnp={}, + ), + ssdp.SsdpChange.ALIVE, + ) + await ssdp_callback( + ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_location=NEW_DEVICE_LOCATION, + ssdp_st=MOCK_DEVICE_TYPE, + upnp={}, + ), + ssdp.SsdpChange.ALIVE, + ) + await hass.async_block_till_done() + + # Check device is contacted exactly once + upnp_factory_mock.async_create_device.assert_awaited_once_with(NEW_DEVICE_LOCATION) + + # Device should be available + assert disconnected_source_mock.available + + +async def test_ssdp_byebye( + hass: HomeAssistant, + ssdp_scanner_mock: Mock, + connected_source_mock: DmsDeviceSource, +) -> None: + """Test device is disconnected when byebye is received.""" + # First byebye will cause a disconnect + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + await ssdp_callback( + ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_udn=MOCK_DEVICE_UDN, + ssdp_headers={"NTS": "ssdp:byebye"}, + ssdp_st=MOCK_DEVICE_TYPE, + upnp={}, + ), + ssdp.SsdpChange.BYEBYE, + ) + + # Device should be gone + assert not connected_source_mock.available + + # Second byebye will do nothing + await ssdp_callback( + ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_udn=MOCK_DEVICE_UDN, + ssdp_headers={"NTS": "ssdp:byebye"}, + ssdp_st=MOCK_DEVICE_TYPE, + upnp={}, + ), + ssdp.SsdpChange.BYEBYE, + ) + + +async def test_ssdp_update_seen_bootid( + hass: HomeAssistant, + ssdp_scanner_mock: Mock, + upnp_factory_mock: Mock, + disconnected_source_mock: DmsDeviceSource, +) -> None: + """Test device does not reconnect when it gets ssdp:update with next bootid.""" + # Start with a disconnected device + entity = disconnected_source_mock + assert not entity.available + + # "Reconnect" the device + upnp_factory_mock.async_create_device.reset_mock() + upnp_factory_mock.async_create_device.side_effect = None + + # Send SSDP alive with boot ID + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + await ssdp_callback( + ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_location=MOCK_DEVICE_LOCATION, + ssdp_headers={ssdp.ATTR_SSDP_BOOTID: "1"}, + ssdp_st=MOCK_DEVICE_TYPE, + upnp={}, + ), + ssdp.SsdpChange.ALIVE, + ) + await hass.async_block_till_done() + + # Device should be connected + assert entity.available + assert upnp_factory_mock.async_create_device.await_count == 1 + + # Send SSDP update with next boot ID + await ssdp_callback( + ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_udn=MOCK_DEVICE_UDN, + ssdp_headers={ + "NTS": "ssdp:update", + ssdp.ATTR_SSDP_BOOTID: "1", + ssdp.ATTR_SSDP_NEXTBOOTID: "2", + }, + ssdp_st=MOCK_DEVICE_TYPE, + upnp={}, + ), + ssdp.SsdpChange.UPDATE, + ) + await hass.async_block_till_done() + + # Device was not reconnected, even with a new boot ID + assert entity.available + assert upnp_factory_mock.async_create_device.await_count == 1 + + # Send SSDP update with same next boot ID, again + await ssdp_callback( + ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_udn=MOCK_DEVICE_UDN, + ssdp_headers={ + "NTS": "ssdp:update", + ssdp.ATTR_SSDP_BOOTID: "1", + ssdp.ATTR_SSDP_NEXTBOOTID: "2", + }, + ssdp_st=MOCK_DEVICE_TYPE, + upnp={}, + ), + ssdp.SsdpChange.UPDATE, + ) + await hass.async_block_till_done() + + # Nothing should change + assert entity.available + assert upnp_factory_mock.async_create_device.await_count == 1 + + # Send SSDP update with bad next boot ID + await ssdp_callback( + ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_udn=MOCK_DEVICE_UDN, + ssdp_headers={ + "NTS": "ssdp:update", + ssdp.ATTR_SSDP_BOOTID: "2", + ssdp.ATTR_SSDP_NEXTBOOTID: "7c848375-a106-4bd1-ac3c-8e50427c8e4f", + }, + ssdp_st=MOCK_DEVICE_TYPE, + upnp={}, + ), + ssdp.SsdpChange.UPDATE, + ) + await hass.async_block_till_done() + + # Nothing should change + assert entity.available + assert upnp_factory_mock.async_create_device.await_count == 1 + + # Send a new SSDP alive with the new boot ID, device should not reconnect + await ssdp_callback( + ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_location=MOCK_DEVICE_LOCATION, + ssdp_headers={ssdp.ATTR_SSDP_BOOTID: "2"}, + ssdp_st=MOCK_DEVICE_TYPE, + upnp={}, + ), + ssdp.SsdpChange.ALIVE, + ) + await hass.async_block_till_done() + + assert entity.available + assert upnp_factory_mock.async_create_device.await_count == 1 + + +async def test_ssdp_update_missed_bootid( + hass: HomeAssistant, + ssdp_scanner_mock: Mock, + upnp_factory_mock: Mock, + disconnected_source_mock: DmsDeviceSource, +) -> None: + """Test device disconnects when it gets ssdp:update bootid it wasn't expecting.""" + # Start with a disconnected device + entity = disconnected_source_mock + assert not entity.available + + # "Reconnect" the device + upnp_factory_mock.async_create_device.reset_mock() + upnp_factory_mock.async_create_device.side_effect = None + + # Send SSDP alive with boot ID + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + await ssdp_callback( + ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_location=MOCK_DEVICE_LOCATION, + ssdp_headers={ssdp.ATTR_SSDP_BOOTID: "1"}, + ssdp_st=MOCK_DEVICE_TYPE, + upnp={}, + ), + ssdp.SsdpChange.ALIVE, + ) + await hass.async_block_till_done() + + # Device should be connected + assert entity.available + assert upnp_factory_mock.async_create_device.await_count == 1 + + # Send SSDP update with skipped boot ID (not previously seen) + await ssdp_callback( + ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_udn=MOCK_DEVICE_UDN, + ssdp_headers={ + "NTS": "ssdp:update", + ssdp.ATTR_SSDP_BOOTID: "2", + ssdp.ATTR_SSDP_NEXTBOOTID: "3", + }, + ssdp_st=MOCK_DEVICE_TYPE, + upnp={}, + ), + ssdp.SsdpChange.UPDATE, + ) + await hass.async_block_till_done() + + # Device should not *re*-connect yet + assert entity.available + assert upnp_factory_mock.async_create_device.await_count == 1 + + # Send a new SSDP alive with the new boot ID, device should reconnect + await ssdp_callback( + ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_location=MOCK_DEVICE_LOCATION, + ssdp_headers={ssdp.ATTR_SSDP_BOOTID: "3"}, + ssdp_st=MOCK_DEVICE_TYPE, + upnp={}, + ), + ssdp.SsdpChange.ALIVE, + ) + await hass.async_block_till_done() + + assert entity.available + assert upnp_factory_mock.async_create_device.await_count == 2 + + +async def test_ssdp_bootid( + hass: HomeAssistant, + upnp_factory_mock: Mock, + ssdp_scanner_mock: Mock, + disconnected_source_mock: DmsDeviceSource, +) -> None: + """Test an alive with a new BOOTID.UPNP.ORG header causes a reconnect.""" + # Start with a disconnected device + entity = disconnected_source_mock + assert not entity.available + + # "Reconnect" the device + upnp_factory_mock.async_create_device.side_effect = None + upnp_factory_mock.async_create_device.reset_mock() + + # Send SSDP alive with boot ID + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + await ssdp_callback( + ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_location=MOCK_DEVICE_LOCATION, + ssdp_headers={ssdp.ATTR_SSDP_BOOTID: "1"}, + ssdp_st=MOCK_DEVICE_TYPE, + upnp={}, + ), + ssdp.SsdpChange.ALIVE, + ) + await hass.async_block_till_done() + + assert entity.available + assert upnp_factory_mock.async_create_device.await_count == 1 + + # Send SSDP alive with same boot ID, nothing should happen + await ssdp_callback( + ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_location=MOCK_DEVICE_LOCATION, + ssdp_headers={ssdp.ATTR_SSDP_BOOTID: "1"}, + ssdp_st=MOCK_DEVICE_TYPE, + upnp={}, + ), + ssdp.SsdpChange.ALIVE, + ) + await hass.async_block_till_done() + + assert entity.available + assert upnp_factory_mock.async_create_device.await_count == 1 + + # Send a new SSDP alive with an incremented boot ID, device should be dis/reconnected + await ssdp_callback( + ssdp.SsdpServiceInfo( + ssdp_usn=MOCK_DEVICE_USN, + ssdp_location=MOCK_DEVICE_LOCATION, + ssdp_headers={ssdp.ATTR_SSDP_BOOTID: "2"}, + ssdp_st=MOCK_DEVICE_TYPE, + upnp={}, + ), + ssdp.SsdpChange.ALIVE, + ) + await hass.async_block_till_done() + + assert entity.available + assert upnp_factory_mock.async_create_device.await_count == 2 + + +async def test_repeated_connect( + caplog: pytest.LogCaptureFixture, + connected_source_mock: DmsDeviceSource, + upnp_factory_mock: Mock, +) -> None: + """Test trying to connect an already connected device is safely ignored.""" + upnp_factory_mock.async_create_device.reset_mock() + # Calling internal function directly to skip trying to time 2 SSDP messages carefully + with caplog.at_level(logging.DEBUG): + await connected_source_mock.device_connect() + assert ( + "Trying to connect when device already connected" == caplog.records[-1].message + ) + assert not upnp_factory_mock.async_create_device.await_count + + +async def test_connect_no_location( + caplog: pytest.LogCaptureFixture, + disconnected_source_mock: DmsDeviceSource, + upnp_factory_mock: Mock, +) -> None: + """Test trying to connect without a location is safely ignored.""" + disconnected_source_mock.location = "" + upnp_factory_mock.async_create_device.reset_mock() + # Calling internal function directly to skip trying to time 2 SSDP messages carefully + with caplog.at_level(logging.DEBUG): + await disconnected_source_mock.device_connect() + assert "Not connecting because location is not known" == caplog.records[-1].message + assert not upnp_factory_mock.async_create_device.await_count + + +async def test_become_unavailable( + hass: HomeAssistant, + connected_source_mock: DmsDeviceSource, + dms_device_mock: Mock, +) -> None: + """Test a device becoming unavailable.""" + # Mock a good resolve result + dms_device_mock.async_browse_metadata.return_value = didl_lite.Item( + id="object_id", + restricted=False, + title="Object", + res=[didl_lite.Resource(uri="foo", protocol_info="http-get:*:audio/mpeg:")], + ) + + # Check async_resolve_object currently works + await connected_source_mock.async_resolve_media(":object_id") + + # Now break the network connection + dms_device_mock.async_browse_metadata.side_effect = UpnpConnectionError + + # The device should be considered available until next contacted + assert connected_source_mock.available + + # async_resolve_object should fail + with pytest.raises(Unresolvable): + await connected_source_mock.async_resolve_media(":object_id") + + # The device should now be unavailable + assert not connected_source_mock.available diff --git a/tests/components/dlna_dms/test_dms_device_source.py b/tests/components/dlna_dms/test_dms_device_source.py new file mode 100644 index 00000000000000..4ee9cce91ba90d --- /dev/null +++ b/tests/components/dlna_dms/test_dms_device_source.py @@ -0,0 +1,878 @@ +"""Test the interface methods of DmsDeviceSource, except availability.""" +from collections.abc import AsyncIterable +from typing import Final, Union +from unittest.mock import ANY, Mock, call + +from async_upnp_client.exceptions import UpnpActionError, UpnpConnectionError, UpnpError +from async_upnp_client.profiles.dlna import ContentDirectoryErrorCode, DmsDevice +from didl_lite import didl_lite +import pytest + +from homeassistant.components.dlna_dms.const import DOMAIN +from homeassistant.components.dlna_dms.dms import ( + ActionError, + DeviceConnectionError, + DlnaDmsData, + DmsDeviceSource, +) +from homeassistant.components.media_player.errors import BrowseError +from homeassistant.components.media_source.error import Unresolvable +from homeassistant.components.media_source.models import BrowseMediaSource +from homeassistant.const import CONF_DEVICE_ID, CONF_URL +from homeassistant.core import HomeAssistant + +from .conftest import ( + MOCK_DEVICE_BASE_URL, + MOCK_DEVICE_NAME, + MOCK_DEVICE_TYPE, + MOCK_DEVICE_USN, + MOCK_SOURCE_ID, +) + +from tests.common import MockConfigEntry + +BrowseResultList = list[Union[didl_lite.DidlObject, didl_lite.Descriptor]] + + +@pytest.fixture +async def device_source_mock( + hass: HomeAssistant, + config_entry_mock: MockConfigEntry, + ssdp_scanner_mock: Mock, + dms_device_mock: Mock, + domain_data_mock: DlnaDmsData, +) -> AsyncIterable[DmsDeviceSource]: + """Fixture to set up a DmsDeviceSource in a connected state and cleanup at completion.""" + await hass.config_entries.async_add(config_entry_mock) + await hass.async_block_till_done() + + mock_entity = domain_data_mock.devices[MOCK_DEVICE_USN] + + # Check the DmsDeviceSource has registered all needed listeners + assert len(config_entry_mock.update_listeners) == 1 + assert ssdp_scanner_mock.async_register_callback.await_count == 2 + assert ssdp_scanner_mock.async_register_callback.return_value.call_count == 0 + + # Run the test + yield mock_entity + + # Unload config entry to clean up + assert await hass.config_entries.async_remove(config_entry_mock.entry_id) == { + "require_restart": False + } + + # Check DmsDeviceSource has cleaned up its resources + assert not config_entry_mock.update_listeners + assert ( + ssdp_scanner_mock.async_register_callback.await_count + == ssdp_scanner_mock.async_register_callback.return_value.call_count + ) + assert MOCK_DEVICE_USN not in domain_data_mock.devices + assert MOCK_SOURCE_ID not in domain_data_mock.sources + + +async def test_update_source_id( + hass: HomeAssistant, + config_entry_mock: MockConfigEntry, + device_source_mock: DmsDeviceSource, + domain_data_mock: DlnaDmsData, +) -> None: + """Test the config listener updates the source_id and source list upon title change.""" + new_title: Final = "New Name" + new_source_id: Final = "new_name" + assert domain_data_mock.sources.keys() == {MOCK_SOURCE_ID} + hass.config_entries.async_update_entry(config_entry_mock, title=new_title) + await hass.async_block_till_done() + + assert device_source_mock.source_id == new_source_id + assert domain_data_mock.sources.keys() == {new_source_id} + + +async def test_update_existing_source_id( + caplog: pytest.LogCaptureFixture, + hass: HomeAssistant, + config_entry_mock: MockConfigEntry, + device_source_mock: DmsDeviceSource, + domain_data_mock: DlnaDmsData, +) -> None: + """Test the config listener gracefully handles colliding source_id.""" + new_title: Final = "New Name" + new_source_id: Final = "new_name" + new_source_id_2: Final = "new_name_1" + # Set up another config entry to collide with the new source_id + colliding_entry = MockConfigEntry( + unique_id=f"different-udn::{MOCK_DEVICE_TYPE}", + domain=DOMAIN, + data={ + CONF_URL: "http://192.88.99.22/dms_description.xml", + CONF_DEVICE_ID: f"different-udn::{MOCK_DEVICE_TYPE}", + }, + title=new_title, + ) + await hass.config_entries.async_add(colliding_entry) + await hass.async_block_till_done() + + assert device_source_mock.source_id == MOCK_SOURCE_ID + assert domain_data_mock.sources.keys() == {MOCK_SOURCE_ID, new_source_id} + assert domain_data_mock.sources[MOCK_SOURCE_ID] is device_source_mock + + # Update the existing entry to match the other entry's name + hass.config_entries.async_update_entry(config_entry_mock, title=new_title) + await hass.async_block_till_done() + + # The existing device's source ID should be a newly generated slug + assert device_source_mock.source_id == new_source_id_2 + assert domain_data_mock.sources.keys() == {new_source_id, new_source_id_2} + assert domain_data_mock.sources[new_source_id_2] is device_source_mock + + # Changing back to the old name should not cause issues + hass.config_entries.async_update_entry(config_entry_mock, title=MOCK_DEVICE_NAME) + await hass.async_block_till_done() + + assert device_source_mock.source_id == MOCK_SOURCE_ID + assert domain_data_mock.sources.keys() == {MOCK_SOURCE_ID, new_source_id} + assert domain_data_mock.sources[MOCK_SOURCE_ID] is device_source_mock + + # Remove the collision and try again + await hass.config_entries.async_remove(colliding_entry.entry_id) + assert domain_data_mock.sources.keys() == {MOCK_SOURCE_ID} + + hass.config_entries.async_update_entry(config_entry_mock, title=new_title) + await hass.async_block_till_done() + + assert device_source_mock.source_id == new_source_id + assert domain_data_mock.sources.keys() == {new_source_id} + + +async def test_catch_request_error_unavailable( + device_source_mock: DmsDeviceSource, +) -> None: + """Test the device is checked for availability before trying requests.""" + device_source_mock._device = None + + with pytest.raises(DeviceConnectionError): + await device_source_mock.async_resolve_object("id") + with pytest.raises(DeviceConnectionError): + await device_source_mock.async_resolve_path("path") + with pytest.raises(DeviceConnectionError): + await device_source_mock.async_resolve_search("query") + with pytest.raises(DeviceConnectionError): + await device_source_mock.async_browse_object("object_id") + with pytest.raises(DeviceConnectionError): + await device_source_mock.async_browse_search("query") + + +async def test_catch_request_error( + device_source_mock: DmsDeviceSource, dms_device_mock: Mock +) -> None: + """Test errors when making requests to the device are handled.""" + dms_device_mock.async_browse_metadata.side_effect = UpnpActionError( + error_code=ContentDirectoryErrorCode.NO_SUCH_OBJECT + ) + with pytest.raises(ActionError, match="No such object: bad_id"): + await device_source_mock.async_resolve_media(":bad_id") + + dms_device_mock.async_search_directory.side_effect = UpnpActionError( + error_code=ContentDirectoryErrorCode.INVALID_SEARCH_CRITERIA + ) + with pytest.raises(ActionError, match="Invalid query: bad query"): + await device_source_mock.async_resolve_media("?bad query") + + dms_device_mock.async_browse_metadata.side_effect = UpnpActionError( + error_code=ContentDirectoryErrorCode.CANNOT_PROCESS_REQUEST + ) + with pytest.raises(DeviceConnectionError, match="Server failure: "): + await device_source_mock.async_resolve_media(":good_id") + + dms_device_mock.async_browse_metadata.side_effect = UpnpError + with pytest.raises( + DeviceConnectionError, match="Server communication failure: UpnpError(.*)" + ): + await device_source_mock.async_resolve_media(":bad_id") + + # UpnpConnectionErrors will cause the device_source_mock to disconnect from the device + assert device_source_mock.available + dms_device_mock.async_browse_metadata.side_effect = UpnpConnectionError + with pytest.raises( + DeviceConnectionError, match="Server disconnected: UpnpConnectionError(.*)" + ): + await device_source_mock.async_resolve_media(":bad_id") + assert not device_source_mock.available + + +async def test_icon(device_source_mock: DmsDeviceSource, dms_device_mock: Mock) -> None: + """Test the device's icon URL is returned.""" + assert device_source_mock.icon == dms_device_mock.icon + + device_source_mock._device = None + assert device_source_mock.icon is None + + +async def test_resolve_media_invalid(device_source_mock: DmsDeviceSource) -> None: + """Test async_resolve_media will raise Unresolvable when an identifier isn't supplied.""" + with pytest.raises(Unresolvable, match="Invalid identifier.*"): + await device_source_mock.async_resolve_media("") + + +async def test_resolve_media_object( + device_source_mock: DmsDeviceSource, dms_device_mock: Mock +) -> None: + """Test the async_resolve_object method via async_resolve_media.""" + object_id: Final = "123" + res_url: Final = "foo/bar" + res_abs_url: Final = f"{MOCK_DEVICE_BASE_URL}/{res_url}" + res_mime: Final = "audio/mpeg" + # Success case: one resource + didl_item = didl_lite.Item( + id=object_id, + restricted="false", + title="Object", + res=[didl_lite.Resource(uri=res_url, protocol_info=f"http-get:*:{res_mime}:")], + ) + dms_device_mock.async_browse_metadata.return_value = didl_item + result = await device_source_mock.async_resolve_media(f":{object_id}") + dms_device_mock.async_browse_metadata.assert_awaited_once_with( + object_id, metadata_filter="*" + ) + assert result.url == res_abs_url + assert result.mime_type == res_mime + assert result.didl_metadata is didl_item + + # Success case: two resources, first is playable + didl_item = didl_lite.Item( + id=object_id, + restricted="false", + title="Object", + res=[ + didl_lite.Resource(uri=res_url, protocol_info=f"http-get:*:{res_mime}:"), + didl_lite.Resource( + uri="thumbnail.png", protocol_info="http-get:*:image/png:" + ), + ], + ) + dms_device_mock.async_browse_metadata.return_value = didl_item + result = await device_source_mock.async_resolve_media(f":{object_id}") + assert result.url == res_abs_url + assert result.mime_type == res_mime + assert result.didl_metadata is didl_item + + # Success case: three resources, only third is playable + didl_item = didl_lite.Item( + id=object_id, + restricted="false", + title="Object", + res=[ + didl_lite.Resource(uri="", protocol_info=""), + didl_lite.Resource(uri="internal:thing", protocol_info="internal:*::"), + didl_lite.Resource(uri=res_url, protocol_info=f"http-get:*:{res_mime}:"), + ], + ) + dms_device_mock.async_browse_metadata.return_value = didl_item + result = await device_source_mock.async_resolve_media(f":{object_id}") + assert result.url == res_abs_url + assert result.mime_type == res_mime + assert result.didl_metadata is didl_item + + # Failure case: no resources + didl_item = didl_lite.Item( + id=object_id, + restricted="false", + title="Object", + res=[], + ) + dms_device_mock.async_browse_metadata.return_value = didl_item + with pytest.raises(Unresolvable, match="Object has no resources"): + await device_source_mock.async_resolve_media(f":{object_id}") + + # Failure case: resources are not playable + didl_item = didl_lite.Item( + id=object_id, + restricted="false", + title="Object", + res=[didl_lite.Resource(uri="internal:thing", protocol_info="internal:*::")], + ) + dms_device_mock.async_browse_metadata.return_value = didl_item + with pytest.raises(Unresolvable, match="Object has no playable resources"): + await device_source_mock.async_resolve_media(f":{object_id}") + + +async def test_resolve_media_path( + device_source_mock: DmsDeviceSource, dms_device_mock: Mock +) -> None: + """Test the async_resolve_path method via async_resolve_media.""" + path: Final = "path/to/thing" + object_ids: Final = ["path_id", "to_id", "thing_id"] + res_url: Final = "foo/bar" + res_abs_url: Final = f"{MOCK_DEVICE_BASE_URL}/{res_url}" + res_mime: Final = "audio/mpeg" + + search_directory_result = [] + for ob_id, ob_title in zip(object_ids, path.split("/")): + didl_item = didl_lite.Item( + id=ob_id, + restricted="false", + title=ob_title, + res=[], + ) + search_directory_result.append(DmsDevice.BrowseResult([didl_item], 1, 1, 0)) + + # Test that path is resolved correctly + dms_device_mock.async_search_directory.side_effect = search_directory_result + dms_device_mock.async_browse_metadata.return_value = didl_lite.Item( + id=object_ids[-1], + restricted="false", + title="thing", + res=[didl_lite.Resource(uri=res_url, protocol_info=f"http-get:*:{res_mime}:")], + ) + result = await device_source_mock.async_resolve_media(f"/{path}") + assert dms_device_mock.async_search_directory.await_args_list == [ + call( + parent_id, + search_criteria=f'@parentID="{parent_id}" and dc:title="{title}"', + metadata_filter=["id", "upnp:class", "dc:title"], + requested_count=1, + ) + for parent_id, title in zip(["0"] + object_ids[:-1], path.split("/")) + ] + assert result.url == res_abs_url + assert result.mime_type == res_mime + + # Test a path starting with a / (first / is path action, second / is root of path) + dms_device_mock.async_search_directory.reset_mock() + dms_device_mock.async_search_directory.side_effect = search_directory_result + result = await device_source_mock.async_resolve_media(f"//{path}") + assert dms_device_mock.async_search_directory.await_args_list == [ + call( + parent_id, + search_criteria=f'@parentID="{parent_id}" and dc:title="{title}"', + metadata_filter=["id", "upnp:class", "dc:title"], + requested_count=1, + ) + for parent_id, title in zip(["0"] + object_ids[:-1], path.split("/")) + ] + assert result.url == res_abs_url + assert result.mime_type == res_mime + + +async def test_resolve_path_simple( + device_source_mock: DmsDeviceSource, dms_device_mock: Mock +) -> None: + """Test async_resolve_path for simple success as for test_resolve_media_path.""" + path: Final = "path/to/thing" + object_ids: Final = ["path_id", "to_id", "thing_id"] + search_directory_result = [] + for ob_id, ob_title in zip(object_ids, path.split("/")): + didl_item = didl_lite.Item( + id=ob_id, + restricted="false", + title=ob_title, + res=[], + ) + search_directory_result.append(DmsDevice.BrowseResult([didl_item], 1, 1, 0)) + + dms_device_mock.async_search_directory.side_effect = search_directory_result + result = await device_source_mock.async_resolve_path(path) + assert dms_device_mock.async_search_directory.call_args_list == [ + call( + parent_id, + search_criteria=f'@parentID="{parent_id}" and dc:title="{title}"', + metadata_filter=["id", "upnp:class", "dc:title"], + requested_count=1, + ) + for parent_id, title in zip(["0"] + object_ids[:-1], path.split("/")) + ] + assert result == object_ids[-1] + assert not dms_device_mock.async_browse_direct_children.await_count + + +async def test_resolve_path_browsed( + device_source_mock: DmsDeviceSource, dms_device_mock: Mock +) -> None: + """Test async_resolve_path: action error results in browsing.""" + path: Final = "path/to/thing" + object_ids: Final = ["path_id", "to_id", "thing_id"] + + search_directory_result = [] + for ob_id, ob_title in zip(object_ids, path.split("/")): + didl_item = didl_lite.Item( + id=ob_id, + restricted="false", + title=ob_title, + res=[], + ) + search_directory_result.append(DmsDevice.BrowseResult([didl_item], 1, 1, 0)) + dms_device_mock.async_search_directory.side_effect = [ + search_directory_result[0], + # 2nd level can't be searched (this happens with Kodi) + UpnpActionError(), + search_directory_result[2], + ] + + browse_children_result: BrowseResultList = [] + for title in ("Irrelevant", "to", "Ignored"): + browse_children_result.append( + didl_lite.Item(id=f"{title}_id", restricted="false", title=title, res=[]) + ) + dms_device_mock.async_browse_direct_children.side_effect = [ + DmsDevice.BrowseResult(browse_children_result, 3, 3, 0) + ] + + result = await device_source_mock.async_resolve_path(path) + # All levels should have an attempted search + assert dms_device_mock.async_search_directory.await_args_list == [ + call( + parent_id, + search_criteria=f'@parentID="{parent_id}" and dc:title="{title}"', + metadata_filter=["id", "upnp:class", "dc:title"], + requested_count=1, + ) + for parent_id, title in zip(["0"] + object_ids[:-1], path.split("/")) + ] + assert result == object_ids[-1] + # 2nd level should also be browsed + assert dms_device_mock.async_browse_direct_children.await_args_list == [ + call("path_id", metadata_filter=["id", "upnp:class", "dc:title"]) + ] + + +async def test_resolve_path_browsed_nothing( + device_source_mock: DmsDeviceSource, dms_device_mock: Mock +) -> None: + """Test async_resolve_path: action error results in browsing, but nothing found.""" + dms_device_mock.async_search_directory.side_effect = UpnpActionError() + # No children + dms_device_mock.async_browse_direct_children.side_effect = [ + DmsDevice.BrowseResult([], 0, 0, 0) + ] + with pytest.raises(Unresolvable, match="No contents for thing in thing/other"): + await device_source_mock.async_resolve_path(r"thing/other") + + # There are children, but they don't match + dms_device_mock.async_browse_direct_children.side_effect = [ + DmsDevice.BrowseResult( + [ + didl_lite.Item( + id="nothingid", restricted="false", title="not thing", res=[] + ) + ], + 1, + 1, + 0, + ) + ] + with pytest.raises(Unresolvable, match="Nothing found for thing in thing/other"): + await device_source_mock.async_resolve_path(r"thing/other") + + +async def test_resolve_path_quoted( + device_source_mock: DmsDeviceSource, dms_device_mock: Mock +) -> None: + """Test async_resolve_path: quotes and backslashes in the path get escaped correctly.""" + dms_device_mock.async_search_directory.side_effect = [ + DmsDevice.BrowseResult( + [ + didl_lite.Item( + id=r'id_with quote" and back\slash', + restricted="false", + title="path", + res=[], + ) + ], + 1, + 1, + 0, + ), + UpnpError("Quick abort"), + ] + with pytest.raises(DeviceConnectionError): + await device_source_mock.async_resolve_path(r'path/quote"back\slash') + assert dms_device_mock.async_search_directory.await_args_list == [ + call( + "0", + search_criteria='@parentID="0" and dc:title="path"', + metadata_filter=["id", "upnp:class", "dc:title"], + requested_count=1, + ), + call( + r'id_with quote" and back\slash', + search_criteria=r'@parentID="id_with quote\" and back\\slash" and dc:title="quote\"back\\slash"', + metadata_filter=["id", "upnp:class", "dc:title"], + requested_count=1, + ), + ] + + +async def test_resolve_path_ambiguous( + device_source_mock: DmsDeviceSource, dms_device_mock: Mock +) -> None: + """Test async_resolve_path: ambiguous results (too many matches) gives error.""" + dms_device_mock.async_search_directory.side_effect = [ + DmsDevice.BrowseResult( + [ + didl_lite.Item( + id=r"thing 1", + restricted="false", + title="thing", + res=[], + ), + didl_lite.Item( + id=r"thing 2", + restricted="false", + title="thing", + res=[], + ), + ], + 2, + 2, + 0, + ) + ] + with pytest.raises( + Unresolvable, match="Too many items found for thing in thing/other" + ): + await device_source_mock.async_resolve_path(r"thing/other") + + +async def test_resolve_path_no_such_container( + device_source_mock: DmsDeviceSource, dms_device_mock: Mock +) -> None: + """Test async_resolve_path: Explicit check for NO_SUCH_CONTAINER.""" + dms_device_mock.async_search_directory.side_effect = UpnpActionError( + error_code=ContentDirectoryErrorCode.NO_SUCH_CONTAINER + ) + with pytest.raises(Unresolvable, match="No such container: 0"): + await device_source_mock.async_resolve_path(r"thing/other") + + +async def test_resolve_media_search( + device_source_mock: DmsDeviceSource, dms_device_mock: Mock +) -> None: + """Test the async_resolve_search method via async_resolve_media.""" + res_url: Final = "foo/bar" + res_abs_url: Final = f"{MOCK_DEVICE_BASE_URL}/{res_url}" + res_mime: Final = "audio/mpeg" + + # No results + dms_device_mock.async_search_directory.return_value = DmsDevice.BrowseResult( + [], 0, 0, 0 + ) + with pytest.raises(Unresolvable, match='Nothing found for dc:title="thing"'): + await device_source_mock.async_resolve_media('?dc:title="thing"') + assert dms_device_mock.async_search_directory.await_args_list == [ + call( + container_id="0", + search_criteria='dc:title="thing"', + metadata_filter="*", + requested_count=1, + ) + ] + + # One result + dms_device_mock.async_search_directory.reset_mock() + didl_item = didl_lite.Item( + id="thing's id", + restricted="false", + title="thing", + res=[didl_lite.Resource(uri=res_url, protocol_info=f"http-get:*:{res_mime}:")], + ) + dms_device_mock.async_search_directory.return_value = DmsDevice.BrowseResult( + [didl_item], 1, 1, 0 + ) + result = await device_source_mock.async_resolve_media('?dc:title="thing"') + assert result.url == res_abs_url + assert result.mime_type == res_mime + assert result.didl_metadata is didl_item + assert dms_device_mock.async_search_directory.await_count == 1 + # Values should be taken from search result, not querying the item's metadata + assert dms_device_mock.async_browse_metadata.await_count == 0 + + # Two results - uses the first + dms_device_mock.async_search_directory.return_value = DmsDevice.BrowseResult( + [didl_item], 1, 2, 0 + ) + result = await device_source_mock.async_resolve_media('?dc:title="thing"') + assert result.url == res_abs_url + assert result.mime_type == res_mime + assert result.didl_metadata is didl_item + + # Bad result + dms_device_mock.async_search_directory.return_value = DmsDevice.BrowseResult( + [didl_lite.Descriptor("id", "namespace")], 1, 1, 0 + ) + with pytest.raises(Unresolvable, match="Descriptor.* is not a DidlObject"): + await device_source_mock.async_resolve_media('?dc:title="thing"') + + +async def test_browse_media_root( + device_source_mock: DmsDeviceSource, dms_device_mock: Mock +) -> None: + """Test async_browse_media with no identifier will browse the root of the device.""" + dms_device_mock.async_browse_metadata.return_value = didl_lite.DidlObject( + id="0", restricted="false", title="root" + ) + dms_device_mock.async_browse_direct_children.return_value = DmsDevice.BrowseResult( + [], 0, 0, 0 + ) + # No identifier (first opened in media browser) + result = await device_source_mock.async_browse_media(None) + assert result.identifier == f"{MOCK_SOURCE_ID}/:0" + assert result.title == MOCK_DEVICE_NAME + dms_device_mock.async_browse_metadata.assert_awaited_once_with( + "0", metadata_filter=ANY + ) + dms_device_mock.async_browse_direct_children.assert_awaited_once_with( + "0", metadata_filter=ANY, sort_criteria=ANY + ) + + dms_device_mock.async_browse_metadata.reset_mock() + dms_device_mock.async_browse_direct_children.reset_mock() + # Empty string identifier + result = await device_source_mock.async_browse_media("") + assert result.identifier == f"{MOCK_SOURCE_ID}/:0" + assert result.title == MOCK_DEVICE_NAME + dms_device_mock.async_browse_metadata.assert_awaited_once_with( + "0", metadata_filter=ANY + ) + dms_device_mock.async_browse_direct_children.assert_awaited_once_with( + "0", metadata_filter=ANY, sort_criteria=ANY + ) + + +async def test_browse_media_object( + device_source_mock: DmsDeviceSource, dms_device_mock: Mock +) -> None: + """Test async_browse_object via async_browse_media.""" + object_id = "1234" + child_titles = ("Item 1", "Thing", "Item 2") + dms_device_mock.async_browse_metadata.return_value = didl_lite.Container( + id=object_id, restricted="false", title="subcontainer" + ) + children_result = DmsDevice.BrowseResult([], 3, 3, 0) + for title in child_titles: + children_result.result.append( + didl_lite.Item( + id=title + "_id", + restricted="false", + title=title, + res=[ + didl_lite.Resource( + uri=title + "_url", protocol_info="http-get:*:audio/mpeg:" + ) + ], + ), + ) + dms_device_mock.async_browse_direct_children.return_value = children_result + + result = await device_source_mock.async_browse_media(f":{object_id}") + dms_device_mock.async_browse_metadata.assert_awaited_once_with( + object_id, metadata_filter=ANY + ) + dms_device_mock.async_browse_direct_children.assert_awaited_once_with( + object_id, metadata_filter=ANY, sort_criteria=ANY + ) + + assert result.domain == DOMAIN + assert result.identifier == f"{MOCK_SOURCE_ID}/:{object_id}" + assert result.title == "subcontainer" + assert not result.can_play + assert result.can_expand + assert result.children + for child, title in zip(result.children, child_titles): + assert isinstance(child, BrowseMediaSource) + assert child.identifier == f"{MOCK_SOURCE_ID}/:{title}_id" + assert child.title == title + assert child.can_play + assert not child.can_expand + assert not child.children + + +async def test_browse_media_path( + device_source_mock: DmsDeviceSource, dms_device_mock: Mock +) -> None: + """Test async_browse_media with a path.""" + title = "folder" + con_id = "123" + container = didl_lite.Container(id=con_id, restricted="false", title=title) + dms_device_mock.async_search_directory.return_value = DmsDevice.BrowseResult( + [container], 1, 1, 0 + ) + dms_device_mock.async_browse_metadata.return_value = container + dms_device_mock.async_browse_direct_children.return_value = DmsDevice.BrowseResult( + [], 0, 0, 0 + ) + + result = await device_source_mock.async_browse_media(f"{title}") + assert result.identifier == f"{MOCK_SOURCE_ID}/:{con_id}" + assert result.title == title + + dms_device_mock.async_search_directory.assert_awaited_once_with( + "0", + search_criteria=f'@parentID="0" and dc:title="{title}"', + metadata_filter=ANY, + requested_count=1, + ) + dms_device_mock.async_browse_metadata.assert_awaited_once_with( + con_id, metadata_filter=ANY + ) + dms_device_mock.async_browse_direct_children.assert_awaited_once_with( + con_id, metadata_filter=ANY, sort_criteria=ANY + ) + + +async def test_browse_media_search( + device_source_mock: DmsDeviceSource, dms_device_mock: Mock +) -> None: + """Test async_browse_media with a search query.""" + query = 'dc:title contains "FooBar"' + object_details = (("111", "FooBar baz"), ("432", "Not FooBar"), ("99", "FooBar")) + dms_device_mock.async_search_directory.return_value = DmsDevice.BrowseResult( + [ + didl_lite.DidlObject(id=ob_id, restricted="false", title=title) + for ob_id, title in object_details + ], + 3, + 3, + 0, + ) + # Test that descriptors are skipped + dms_device_mock.async_search_directory.return_value.result.insert( + 1, didl_lite.Descriptor("id", "name_space") + ) + + result = await device_source_mock.async_browse_media(f"?{query}") + assert result.identifier == f"{MOCK_SOURCE_ID}/?{query}" + assert result.title == "Search results" + assert result.children + + for obj, child in zip(object_details, result.children): + assert isinstance(child, BrowseMediaSource) + assert child.identifier == f"{MOCK_SOURCE_ID}/:{obj[0]}" + assert child.title == obj[1] + assert not child.children + + +async def test_browse_search_invalid( + device_source_mock: DmsDeviceSource, dms_device_mock: Mock +) -> None: + """Test searching with an invalid query gives a BrowseError.""" + query = "title == FooBar" + dms_device_mock.async_search_directory.side_effect = UpnpActionError( + error_code=ContentDirectoryErrorCode.INVALID_SEARCH_CRITERIA + ) + with pytest.raises(BrowseError, match=f"Invalid query: {query}"): + await device_source_mock.async_browse_media(f"?{query}") + + +async def test_browse_search_no_results( + device_source_mock: DmsDeviceSource, dms_device_mock: Mock +) -> None: + """Test a search with no results does not give an error.""" + query = 'dc:title contains "FooBar"' + dms_device_mock.async_search_directory.return_value = DmsDevice.BrowseResult( + [], 0, 0, 0 + ) + + result = await device_source_mock.async_browse_media(f"?{query}") + assert result.identifier == f"{MOCK_SOURCE_ID}/?{query}" + assert result.title == "Search results" + assert not result.children + + +async def test_thumbnail( + device_source_mock: DmsDeviceSource, dms_device_mock: Mock +) -> None: + """Test getting thumbnails URLs for items.""" + # Use browse_search to get multiple items at once for least effort + dms_device_mock.async_search_directory.return_value = DmsDevice.BrowseResult( + [ + # Thumbnail as albumArtURI property + didl_lite.MusicAlbum( + id="a", + restricted="false", + title="a", + res=[], + album_art_uri="a_thumb.jpg", + ), + # Thumbnail as resource (1st resource is media item, 2nd is missing + # a URI, 3rd is thumbnail) + didl_lite.MusicTrack( + id="b", + restricted="false", + title="b", + res=[ + didl_lite.Resource( + uri="b_track.mp3", protocol_info="http-get:*:audio/mpeg:" + ), + didl_lite.Resource(uri="", protocol_info="internal:*::"), + didl_lite.Resource( + uri="b_thumb.png", protocol_info="http-get:*:image/png:" + ), + ], + ), + # No thumbnail + didl_lite.MusicTrack( + id="c", + restricted="false", + title="c", + res=[ + didl_lite.Resource( + uri="c_track.mp3", protocol_info="http-get:*:audio/mpeg:" + ) + ], + ), + ], + 3, + 3, + 0, + ) + + result = await device_source_mock.async_browse_media("?query") + assert result.children + assert result.children[0].thumbnail == f"{MOCK_DEVICE_BASE_URL}/a_thumb.jpg" + assert result.children[1].thumbnail == f"{MOCK_DEVICE_BASE_URL}/b_thumb.png" + assert result.children[2].thumbnail is None + + +async def test_can_play( + device_source_mock: DmsDeviceSource, dms_device_mock: Mock +) -> None: + """Test determination of playability for items.""" + protocol_infos = [ + # No protocol info for resource + ("", True), + # Protocol info is poorly formatted but can play + ("http-get", True), + # Protocol info is poorly formatted and can't play + ("internal", False), + # Protocol is HTTP + ("http-get:*:audio/mpeg", True), + # Protocol is RTSP + ("rtsp-rtp-udp:*:MPA:", True), + # Protocol is something else + ("internal:*:audio/mpeg:", False), + ] + + search_results: BrowseResultList = [] + # No resources + search_results.append(didl_lite.DidlObject(id="", restricted="false", title="")) + search_results.extend( + didl_lite.MusicTrack( + id="", + restricted="false", + title="", + res=[didl_lite.Resource(uri="", protocol_info=protocol_info)], + ) + for protocol_info, _ in protocol_infos + ) + + # Use browse_search to get multiple items at once for least effort + dms_device_mock.async_search_directory.return_value = DmsDevice.BrowseResult( + search_results, len(search_results), len(search_results), 0 + ) + + result = await device_source_mock.async_browse_media("?query") + assert result.children + assert not result.children[0].can_play + for idx, info_can_play in enumerate(protocol_infos): + protocol_info, can_play = info_can_play + assert result.children[idx + 1].can_play is can_play, f"Checked {protocol_info}" diff --git a/tests/components/dlna_dms/test_init.py b/tests/components/dlna_dms/test_init.py new file mode 100644 index 00000000000000..16254adca89fa2 --- /dev/null +++ b/tests/components/dlna_dms/test_init.py @@ -0,0 +1,59 @@ +"""Test the DLNA DMS component setup, cleanup, and module-level functions.""" + +from unittest.mock import Mock + +from homeassistant.components.dlna_dms.const import DOMAIN +from homeassistant.components.dlna_dms.dms import DlnaDmsData +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from tests.common import MockConfigEntry + + +async def test_resource_lifecycle( + hass: HomeAssistant, + domain_data_mock: DlnaDmsData, + config_entry_mock: MockConfigEntry, + ssdp_scanner_mock: Mock, + dms_device_mock: Mock, +) -> None: + """Test that resources are acquired/released as the entity is setup/unloaded.""" + # Set up the config entry + config_entry_mock.add_to_hass(hass) + assert await async_setup_component(hass, DOMAIN, {}) is True + await hass.async_block_till_done() + + # Check the entity is created and working + assert len(domain_data_mock.devices) == 1 + assert len(domain_data_mock.sources) == 1 + entity = next(iter(domain_data_mock.devices.values())) + assert entity.available is True + + # Check update listeners are subscribed + assert len(config_entry_mock.update_listeners) == 1 + assert ssdp_scanner_mock.async_register_callback.await_count == 2 + assert ssdp_scanner_mock.async_register_callback.return_value.call_count == 0 + + # Check event notifiers are not subscribed - dlna_dms doesn't use them + assert dms_device_mock.async_subscribe_services.await_count == 0 + assert dms_device_mock.async_unsubscribe_services.await_count == 0 + assert dms_device_mock.on_event is None + + # Unload the config entry + assert await hass.config_entries.async_remove(config_entry_mock.entry_id) == { + "require_restart": False + } + + # Check update listeners are released + assert not config_entry_mock.update_listeners + assert ssdp_scanner_mock.async_register_callback.await_count == 2 + assert ssdp_scanner_mock.async_register_callback.return_value.call_count == 2 + + # Check event notifiers are still not subscribed + assert dms_device_mock.async_subscribe_services.await_count == 0 + assert dms_device_mock.async_unsubscribe_services.await_count == 0 + assert dms_device_mock.on_event is None + + # Check entity is gone + assert not domain_data_mock.devices + assert not domain_data_mock.sources diff --git a/tests/components/dlna_dms/test_media_source.py b/tests/components/dlna_dms/test_media_source.py new file mode 100644 index 00000000000000..4b43402ecbdcdb --- /dev/null +++ b/tests/components/dlna_dms/test_media_source.py @@ -0,0 +1,255 @@ +"""Tests for dlna_dms.media_source, mostly testing DmsMediaSource.""" +from unittest.mock import ANY, Mock + +from async_upnp_client.exceptions import UpnpError +from didl_lite import didl_lite +import pytest + +from homeassistant.components.dlna_dms.const import DOMAIN +from homeassistant.components.dlna_dms.dms import DlnaDmsData, DmsDeviceSource +from homeassistant.components.dlna_dms.media_source import ( + DmsMediaSource, + async_get_media_source, +) +from homeassistant.components.media_player.errors import BrowseError +from homeassistant.components.media_source.error import Unresolvable +from homeassistant.components.media_source.models import ( + BrowseMediaSource, + MediaSourceItem, +) +from homeassistant.const import CONF_DEVICE_ID, CONF_URL +from homeassistant.core import HomeAssistant + +from .conftest import ( + MOCK_DEVICE_BASE_URL, + MOCK_DEVICE_NAME, + MOCK_DEVICE_TYPE, + MOCK_DEVICE_USN, + MOCK_SOURCE_ID, +) + +from tests.common import MockConfigEntry + + +@pytest.fixture +async def entity( + hass: HomeAssistant, + config_entry_mock: MockConfigEntry, + dms_device_mock: Mock, + domain_data_mock: DlnaDmsData, +) -> DmsDeviceSource: + """Fixture to set up a DmsDeviceSource in a connected state and cleanup at completion.""" + await hass.config_entries.async_add(config_entry_mock) + await hass.async_block_till_done() + return domain_data_mock.devices[MOCK_DEVICE_USN] + + +@pytest.fixture +async def dms_source(hass: HomeAssistant, entity: DmsDeviceSource) -> DmsMediaSource: + """Fixture providing a pre-constructed DmsMediaSource with a single device.""" + return DmsMediaSource(hass) + + +async def test_get_media_source(hass: HomeAssistant) -> None: + """Test the async_get_media_source function and DmsMediaSource constructor.""" + source = await async_get_media_source(hass) + assert isinstance(source, DmsMediaSource) + assert source.domain == DOMAIN + + +async def test_resolve_media_unconfigured(hass: HomeAssistant) -> None: + """Test resolve_media without any devices being configured.""" + source = DmsMediaSource(hass) + item = MediaSourceItem(hass, DOMAIN, "source_id/media_id") + with pytest.raises(Unresolvable, match="No sources have been configured"): + await source.async_resolve_media(item) + + +async def test_resolve_media_bad_identifier( + hass: HomeAssistant, dms_source: DmsMediaSource +) -> None: + """Test trying to resolve an item that has an unresolvable identifier.""" + # Empty identifier + item = MediaSourceItem(hass, DOMAIN, "") + with pytest.raises(Unresolvable, match="No source ID.*"): + await dms_source.async_resolve_media(item) + + # Identifier has media_id but no source_id + item = MediaSourceItem(hass, DOMAIN, "/media_id") + with pytest.raises(Unresolvable, match="No source ID.*"): + await dms_source.async_resolve_media(item) + + # Identifier has source_id but no media_id + item = MediaSourceItem(hass, DOMAIN, "source_id/") + with pytest.raises(Unresolvable, match="No media ID.*"): + await dms_source.async_resolve_media(item) + + # Identifier is missing source_id/media_id separator + item = MediaSourceItem(hass, DOMAIN, "source_id") + with pytest.raises(Unresolvable, match="No media ID.*"): + await dms_source.async_resolve_media(item) + + # Identifier has an unknown source_id + item = MediaSourceItem(hass, DOMAIN, "unknown_source/media_id") + with pytest.raises(Unresolvable, match="Unknown source ID: unknown_source"): + await dms_source.async_resolve_media(item) + + +async def test_resolve_media_success( + hass: HomeAssistant, dms_source: DmsMediaSource, dms_device_mock: Mock +) -> None: + """Test resolving an item via a DmsDeviceSource.""" + object_id = "123" + item = MediaSourceItem(hass, DOMAIN, f"{MOCK_SOURCE_ID}/:{object_id}") + + res_url = "foo/bar" + res_mime = "audio/mpeg" + didl_item = didl_lite.Item( + id=object_id, + restricted=False, + title="Object", + res=[didl_lite.Resource(uri=res_url, protocol_info=f"http-get:*:{res_mime}:")], + ) + dms_device_mock.async_browse_metadata.return_value = didl_item + + result = await dms_source.async_resolve_media(item) + assert result.url == f"{MOCK_DEVICE_BASE_URL}/{res_url}" + assert result.mime_type == res_mime + assert result.didl_metadata is didl_item + + +async def test_browse_media_unconfigured(hass: HomeAssistant) -> None: + """Test browse_media without any devices being configured.""" + source = DmsMediaSource(hass) + item = MediaSourceItem(hass, DOMAIN, "source_id/media_id") + with pytest.raises(BrowseError, match="No sources have been configured"): + await source.async_browse_media(item) + + item = MediaSourceItem(hass, DOMAIN, "") + with pytest.raises(BrowseError, match="No sources have been configured"): + await source.async_browse_media(item) + + +async def test_browse_media_bad_identifier( + hass: HomeAssistant, dms_source: DmsMediaSource +) -> None: + """Test browse_media with a bad source_id.""" + item = MediaSourceItem(hass, DOMAIN, "bad-id/media_id") + with pytest.raises(BrowseError, match="Unknown source ID: bad-id"): + await dms_source.async_browse_media(item) + + +async def test_browse_media_single_source_no_identifier( + hass: HomeAssistant, dms_source: DmsMediaSource, dms_device_mock: Mock +) -> None: + """Test browse_media without a source_id, with a single device registered.""" + # Fast bail-out, mock will be checked after + dms_device_mock.async_browse_metadata.side_effect = UpnpError + + # No source_id nor media_id + item = MediaSourceItem(hass, DOMAIN, "") + with pytest.raises(BrowseError): + await dms_source.async_browse_media(item) + # Mock device should've been browsed for the root directory + dms_device_mock.async_browse_metadata.assert_awaited_once_with( + "0", metadata_filter=ANY + ) + + # No source_id but a media_id + item = MediaSourceItem(hass, DOMAIN, "/:media-item-id") + dms_device_mock.async_browse_metadata.reset_mock() + with pytest.raises(BrowseError): + await dms_source.async_browse_media(item) + # Mock device should've been browsed for the root directory + dms_device_mock.async_browse_metadata.assert_awaited_once_with( + "media-item-id", metadata_filter=ANY + ) + + +async def test_browse_media_multiple_sources( + hass: HomeAssistant, dms_source: DmsMediaSource, dms_device_mock: Mock +) -> None: + """Test browse_media without a source_id, with multiple devices registered.""" + # Set up a second source + other_source_id = "second_source" + other_source_title = "Second source" + other_config_entry = MockConfigEntry( + unique_id=f"different-udn::{MOCK_DEVICE_TYPE}", + domain=DOMAIN, + data={ + CONF_URL: "http://192.88.99.22/dms_description.xml", + CONF_DEVICE_ID: f"different-udn::{MOCK_DEVICE_TYPE}", + }, + title=other_source_title, + ) + await hass.config_entries.async_add(other_config_entry) + await hass.async_block_till_done() + + # No source_id nor media_id + item = MediaSourceItem(hass, DOMAIN, "") + result = await dms_source.async_browse_media(item) + # Mock device should not have been browsed + assert dms_device_mock.async_browse_metadata.await_count == 0 + # Result will be a list of available devices + assert result.title == "DLNA Servers" + assert result.children + assert isinstance(result.children[0], BrowseMediaSource) + assert result.children[0].identifier == f"{MOCK_SOURCE_ID}/:0" + assert result.children[0].title == MOCK_DEVICE_NAME + assert isinstance(result.children[1], BrowseMediaSource) + assert result.children[1].identifier == f"{other_source_id}/:0" + assert result.children[1].title == other_source_title + + # No source_id but a media_id - will give the exact same list of all devices + item = MediaSourceItem(hass, DOMAIN, "/:media-item-id") + result = await dms_source.async_browse_media(item) + # Mock device should not have been browsed + assert dms_device_mock.async_browse_metadata.await_count == 0 + # Result will be a list of available devices + assert result.title == "DLNA Servers" + assert result.children + assert isinstance(result.children[0], BrowseMediaSource) + assert result.children[0].identifier == f"{MOCK_SOURCE_ID}/:0" + assert result.children[0].title == MOCK_DEVICE_NAME + assert isinstance(result.children[1], BrowseMediaSource) + assert result.children[1].identifier == f"{other_source_id}/:0" + assert result.children[1].title == other_source_title + + +async def test_browse_media_source_id( + hass: HomeAssistant, + config_entry_mock: MockConfigEntry, + dms_device_mock: Mock, + domain_data_mock: DlnaDmsData, +) -> None: + """Test browse_media with an explicit source_id.""" + # Set up a second device first, then the primary mock device. + # This allows testing that the right source is chosen by source_id + other_source_title = "Second source" + other_config_entry = MockConfigEntry( + unique_id=f"different-udn::{MOCK_DEVICE_TYPE}", + domain=DOMAIN, + data={ + CONF_URL: "http://192.88.99.22/dms_description.xml", + CONF_DEVICE_ID: f"different-udn::{MOCK_DEVICE_TYPE}", + }, + title=other_source_title, + ) + await hass.config_entries.async_add(other_config_entry) + await hass.async_block_till_done() + + await hass.config_entries.async_add(config_entry_mock) + await hass.async_block_till_done() + + # Fast bail-out, mock will be checked after + dms_device_mock.async_browse_metadata.side_effect = UpnpError + + # Browse by source_id + item = MediaSourceItem(hass, DOMAIN, f"{MOCK_SOURCE_ID}/:media-item-id") + dms_source = DmsMediaSource(hass) + with pytest.raises(BrowseError): + await dms_source.async_browse_media(item) + # Mock device should've been browsed for the root directory + dms_device_mock.async_browse_metadata.assert_awaited_once_with( + "media-item-id", metadata_filter=ANY + ) From 744a2013cd4a9bf98935397a3262f15f35047b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20L=C3=B6vdahl?= Date: Tue, 22 Feb 2022 01:17:54 +0200 Subject: [PATCH 0913/1098] Improve Vallox filter remaining time sensor (#66763) --- homeassistant/components/vallox/__init__.py | 15 +- homeassistant/components/vallox/manifest.json | 2 +- homeassistant/components/vallox/sensor.py | 20 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/vallox/conftest.py | 65 +++++++ tests/components/vallox/test_sensor.py | 175 ++++++++++++++++++ 7 files changed, 265 insertions(+), 16 deletions(-) create mode 100644 tests/components/vallox/conftest.py create mode 100644 tests/components/vallox/test_sensor.py diff --git a/homeassistant/components/vallox/__init__.py b/homeassistant/components/vallox/__init__.py index fcda7227945097..aeb9e59e28682d 100644 --- a/homeassistant/components/vallox/__init__.py +++ b/homeassistant/components/vallox/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations from dataclasses import dataclass, field +from datetime import date import ipaddress import logging from typing import Any, NamedTuple @@ -9,7 +10,10 @@ from vallox_websocket_api import PROFILE as VALLOX_PROFILE, Vallox from vallox_websocket_api.exceptions import ValloxApiException -from vallox_websocket_api.vallox import get_uuid as calculate_uuid +from vallox_websocket_api.vallox import ( + get_next_filter_change_date as calculate_next_filter_change_date, + get_uuid as calculate_uuid, +) import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry @@ -117,6 +121,15 @@ def get_uuid(self) -> UUID | None: raise ValueError return uuid + def get_next_filter_change_date(self) -> date | None: + """Return the next filter change date.""" + next_filter_change_date = calculate_next_filter_change_date(self.metric_cache) + + if not isinstance(next_filter_change_date, date): + return None + + return next_filter_change_date + class ValloxDataUpdateCoordinator(DataUpdateCoordinator): """The DataUpdateCoordinator for Vallox.""" diff --git a/homeassistant/components/vallox/manifest.json b/homeassistant/components/vallox/manifest.json index aed87e9239d973..71b0750e2f2a44 100644 --- a/homeassistant/components/vallox/manifest.json +++ b/homeassistant/components/vallox/manifest.json @@ -2,7 +2,7 @@ "domain": "vallox", "name": "Vallox", "documentation": "https://www.home-assistant.io/integrations/vallox", - "requirements": ["vallox-websocket-api==2.9.0"], + "requirements": ["vallox-websocket-api==2.11.0"], "codeowners": ["@andre-richter", "@slovdahl", "@viiru-"], "config_flow": true, "iot_class": "local_polling", diff --git a/homeassistant/components/vallox/sensor.py b/homeassistant/components/vallox/sensor.py index 44dfb56fafc927..eece054c82e185 100644 --- a/homeassistant/components/vallox/sensor.py +++ b/homeassistant/components/vallox/sensor.py @@ -2,7 +2,7 @@ from __future__ import annotations from dataclasses import dataclass -from datetime import datetime, timedelta +from datetime import datetime, time from homeassistant.components.sensor import ( SensorDeviceClass, @@ -20,7 +20,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType from homeassistant.helpers.update_coordinator import CoordinatorEntity -from homeassistant.util import dt as dt_util +from homeassistant.util import dt from . import ValloxDataUpdateCoordinator from .const import ( @@ -95,18 +95,15 @@ class ValloxFilterRemainingSensor(ValloxSensor): @property def native_value(self) -> StateType | datetime: """Return the value reported by the sensor.""" - super_native_value = super().native_value + next_filter_change_date = self.coordinator.data.get_next_filter_change_date() - if not isinstance(super_native_value, (int, float)): + if next_filter_change_date is None: return None - # Since only a delta of days is received from the device, fix the time so the timestamp does - # not change with every update. - days_remaining = float(super_native_value) - days_remaining_delta = timedelta(days=days_remaining) - now = datetime.utcnow().replace(hour=13, minute=0, second=0, microsecond=0) - - return (now + days_remaining_delta).astimezone(dt_util.UTC) + return datetime.combine( + next_filter_change_date, + time(hour=13, minute=0, second=0, tzinfo=dt.DEFAULT_TIME_ZONE), + ) class ValloxCellStateSensor(ValloxSensor): @@ -150,7 +147,6 @@ class ValloxSensorEntityDescription(SensorEntityDescription): ValloxSensorEntityDescription( key="remaining_time_for_filter", name="Remaining Time For Filter", - metric_key="A_CYC_REMAINING_TIME_FOR_FILTER", device_class=SensorDeviceClass.TIMESTAMP, sensor_type=ValloxFilterRemainingSensor, ), diff --git a/requirements_all.txt b/requirements_all.txt index 511f8abb7c52d0..ef0210e3067f7a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2425,7 +2425,7 @@ uscisstatus==0.1.1 uvcclient==0.11.0 # homeassistant.components.vallox -vallox-websocket-api==2.9.0 +vallox-websocket-api==2.11.0 # homeassistant.components.rdw vehicle==0.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 926d5e0d6ee569..fe0f3a638bf69a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1495,7 +1495,7 @@ url-normalize==1.4.1 uvcclient==0.11.0 # homeassistant.components.vallox -vallox-websocket-api==2.9.0 +vallox-websocket-api==2.11.0 # homeassistant.components.rdw vehicle==0.3.1 diff --git a/tests/components/vallox/conftest.py b/tests/components/vallox/conftest.py new file mode 100644 index 00000000000000..e7ea6ee6d6efec --- /dev/null +++ b/tests/components/vallox/conftest.py @@ -0,0 +1,65 @@ +"""Common utilities for Vallox tests.""" + +import random +import string +from typing import Any +from unittest.mock import patch +from uuid import UUID + +import pytest +from vallox_websocket_api.vallox import PROFILE + +from homeassistant.components.vallox.const import DOMAIN +from homeassistant.const import CONF_HOST, CONF_NAME +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +@pytest.fixture +def mock_entry(hass: HomeAssistant) -> MockConfigEntry: + """Create mocked Vallox config entry.""" + vallox_mock_entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_HOST: "192.168.100.50", + CONF_NAME: "Vallox", + }, + ) + vallox_mock_entry.add_to_hass(hass) + + return vallox_mock_entry + + +def patch_metrics(metrics: dict[str, Any]): + """Patch the Vallox metrics response.""" + return patch( + "homeassistant.components.vallox.Vallox.fetch_metrics", + return_value=metrics, + ) + + +@pytest.fixture(autouse=True) +def patch_profile_home(): + """Patch the Vallox profile response.""" + with patch( + "homeassistant.components.vallox.Vallox.get_profile", + return_value=PROFILE.HOME, + ): + yield + + +@pytest.fixture(autouse=True) +def patch_uuid(): + """Patch the Vallox entity UUID.""" + with patch( + "homeassistant.components.vallox.calculate_uuid", + return_value=_random_uuid(), + ): + yield + + +def _random_uuid(): + """Generate a random UUID.""" + uuid = "".join(random.choices(string.hexdigits, k=32)) + return UUID(uuid) diff --git a/tests/components/vallox/test_sensor.py b/tests/components/vallox/test_sensor.py new file mode 100644 index 00000000000000..bd8ecbea905e5a --- /dev/null +++ b/tests/components/vallox/test_sensor.py @@ -0,0 +1,175 @@ +"""Tests for Vallox sensor platform.""" + +from datetime import datetime, timedelta, tzinfo +from unittest.mock import patch + +import pytest + +from homeassistant.core import HomeAssistant +from homeassistant.util import dt + +from .conftest import patch_metrics + +from tests.common import MockConfigEntry + +ORIG_TZ = dt.DEFAULT_TIME_ZONE + + +@pytest.fixture(autouse=True) +def reset_tz(): + """Restore the default TZ after test runs.""" + yield + dt.DEFAULT_TIME_ZONE = ORIG_TZ + + +@pytest.fixture +def set_tz(request): + """Set the default TZ to the one requested.""" + return request.getfixturevalue(request.param) + + +@pytest.fixture +def utc() -> tzinfo: + """Set the default TZ to UTC.""" + tz = dt.get_time_zone("UTC") + dt.set_default_time_zone(tz) + return tz + + +@pytest.fixture +def helsinki() -> tzinfo: + """Set the default TZ to Europe/Helsinki.""" + tz = dt.get_time_zone("Europe/Helsinki") + dt.set_default_time_zone(tz) + return tz + + +@pytest.fixture +def new_york() -> tzinfo: + """Set the default TZ to America/New_York.""" + tz = dt.get_time_zone("America/New_York") + dt.set_default_time_zone(tz) + return tz + + +def _sensor_to_datetime(sensor): + return datetime.fromisoformat(sensor.state) + + +def _now_at_13(): + return dt.now().timetz().replace(hour=13, minute=0, second=0, microsecond=0) + + +async def test_remaining_filter_returns_timestamp( + mock_entry: MockConfigEntry, hass: HomeAssistant +): + """Test that the remaining time for filter sensor returns a timestamp.""" + # Act + with patch( + "homeassistant.components.vallox.calculate_next_filter_change_date", + return_value=dt.now().date(), + ), patch_metrics(metrics={}): + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + + # Assert + sensor = hass.states.get("sensor.vallox_remaining_time_for_filter") + assert sensor.attributes["device_class"] == "timestamp" + + +async def test_remaining_time_for_filter_none_returned_from_vallox( + mock_entry: MockConfigEntry, hass: HomeAssistant +): + """Test that the remaining time for filter sensor returns 'unknown' when Vallox returns None.""" + # Act + with patch( + "homeassistant.components.vallox.calculate_next_filter_change_date", + return_value=None, + ), patch_metrics(metrics={}): + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + + # Assert + sensor = hass.states.get("sensor.vallox_remaining_time_for_filter") + assert sensor.state == "unknown" + + +@pytest.mark.parametrize( + "set_tz", + [ + "utc", + "helsinki", + "new_york", + ], + indirect=True, +) +async def test_remaining_time_for_filter_in_the_future( + mock_entry: MockConfigEntry, set_tz: tzinfo, hass: HomeAssistant +): + """Test remaining time for filter when Vallox returns a date in the future.""" + # Arrange + remaining_days = 112 + mocked_filter_end_date = dt.now().date() + timedelta(days=remaining_days) + + # Act + with patch( + "homeassistant.components.vallox.calculate_next_filter_change_date", + return_value=mocked_filter_end_date, + ), patch_metrics(metrics={}): + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + + # Assert + sensor = hass.states.get("sensor.vallox_remaining_time_for_filter") + assert _sensor_to_datetime(sensor) == datetime.combine( + mocked_filter_end_date, + _now_at_13(), + ) + + +async def test_remaining_time_for_filter_today( + mock_entry: MockConfigEntry, hass: HomeAssistant +): + """Test remaining time for filter when Vallox returns today.""" + # Arrange + remaining_days = 0 + mocked_filter_end_date = dt.now().date() + timedelta(days=remaining_days) + + # Act + with patch( + "homeassistant.components.vallox.calculate_next_filter_change_date", + return_value=mocked_filter_end_date, + ), patch_metrics(metrics={}): + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + + # Assert + sensor = hass.states.get("sensor.vallox_remaining_time_for_filter") + assert _sensor_to_datetime(sensor) == datetime.combine( + mocked_filter_end_date, + _now_at_13(), + ) + + +async def test_remaining_time_for_filter_in_the_past( + mock_entry: MockConfigEntry, hass: HomeAssistant +): + """Test remaining time for filter when Vallox returns a date in the past.""" + # Arrange + remaining_days = -3 + mocked_filter_end_date = dt.now().date() + timedelta(days=remaining_days) + + # Act + with patch( + "homeassistant.components.vallox.calculate_next_filter_change_date", + return_value=mocked_filter_end_date, + ), patch_metrics(metrics={}): + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + + # Assert + sensor = hass.states.get("sensor.vallox_remaining_time_for_filter") + assert _sensor_to_datetime(sensor) == datetime.combine( + mocked_filter_end_date, + _now_at_13(), + ) From 102ae9f0e3d07e8632fd5fc5933e9e883fe1060f Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 22 Feb 2022 00:17:23 +0000 Subject: [PATCH 0914/1098] [ci skip] Translation update --- .../components/abode/translations/el.json | 4 ++ .../accuweather/translations/el.json | 11 +++++ .../components/acmeda/translations/el.json | 3 ++ .../components/adax/translations/el.json | 8 +++- .../components/adguard/translations/el.json | 5 ++- .../advantage_air/translations/el.json | 6 +++ .../components/aemet/translations/el.json | 9 ++++ .../components/agent_dvr/translations/el.json | 10 ++++- .../components/airly/translations/el.json | 9 ++++ .../components/airnow/translations/el.json | 19 +++++++-- .../components/airthings/translations/el.json | 8 ++++ .../components/airtouch4/translations/el.json | 4 ++ .../components/airvisual/translations/el.json | 14 ++++++- .../alarmdecoder/translations/el.json | 3 ++ .../components/almond/translations/el.json | 9 ++++ .../components/ambee/translations/el.json | 14 +++++++ .../ambiclimate/translations/el.json | 7 +++- .../ambient_station/translations/el.json | 11 +++++ .../ambient_station/translations/it.json | 2 +- .../components/androidtv/translations/el.json | 5 ++- .../components/apple_tv/translations/el.json | 20 ++++++++- .../components/arcam_fmj/translations/el.json | 8 +++- .../aseko_pool_live/translations/el.json | 8 ++++ .../components/asuswrt/translations/el.json | 9 +++- .../components/atag/translations/el.json | 4 ++ .../components/august/translations/el.json | 9 ++++ .../components/aurora/translations/el.json | 14 +++++++ .../aurora_abb_powerone/translations/el.json | 4 +- .../aussie_broadband/translations/el.json | 18 +++++--- .../aussie_broadband/translations/nl.json | 7 ++++ .../components/awair/translations/el.json | 11 +++++ .../components/axis/translations/el.json | 7 ++++ .../azure_devops/translations/el.json | 6 +++ .../azure_event_hub/translations/el.json | 5 ++- .../components/balboa/translations/el.json | 6 ++- .../binary_sensor/translations/ca.json | 4 ++ .../binary_sensor/translations/el.json | 8 +++- .../binary_sensor/translations/en.json | 6 +++ .../binary_sensor/translations/it.json | 4 ++ .../binary_sensor/translations/nl.json | 4 ++ .../binary_sensor/translations/pt-BR.json | 4 ++ .../components/blebox/translations/el.json | 8 +++- .../components/blink/translations/el.json | 12 +++++- .../bmw_connected_drive/translations/el.json | 6 ++- .../components/bond/translations/el.json | 12 +++++- .../components/bosch_shc/translations/el.json | 12 +++++- .../components/braviatv/translations/el.json | 6 +++ .../components/broadlink/translations/el.json | 10 ++++- .../components/brother/translations/el.json | 1 + .../components/brunt/translations/el.json | 11 ++++- .../components/bsblan/translations/el.json | 2 + .../buienradar/translations/el.json | 16 +++++++ .../components/cast/translations/el.json | 6 +++ .../cert_expiry/translations/el.json | 1 + .../components/climacell/translations/el.json | 1 + .../cloudflare/translations/el.json | 11 +++++ .../components/co2signal/translations/el.json | 15 ++++++- .../components/coinbase/translations/el.json | 12 +++++- .../components/control4/translations/el.json | 11 ++++- .../coronavirus/translations/el.json | 4 ++ .../components/cover/translations/el.json | 4 +- .../components/cpuspeed/translations/el.json | 3 ++ .../crownstone/translations/el.json | 23 +++++++++- .../components/daikin/translations/el.json | 10 ++++- .../components/demo/translations/el.json | 1 + .../components/denonavr/translations/el.json | 2 + .../devolo_home_control/translations/el.json | 5 +++ .../devolo_home_network/translations/el.json | 7 +++- .../components/dexcom/translations/el.json | 9 +++- .../dialogflow/translations/el.json | 3 +- .../components/directv/translations/el.json | 7 ++++ .../components/dlna_dmr/translations/el.json | 12 +++++- .../dlna_dms/translations/pt-BR.json | 24 +++++++++++ .../components/doorbird/translations/el.json | 5 ++- .../components/dsmr/translations/el.json | 11 ++++- .../components/dunehd/translations/el.json | 8 ++++ .../components/eafm/translations/el.json | 1 + .../components/ecobee/translations/el.json | 6 +++ .../components/econet/translations/el.json | 7 +++- .../components/efergy/translations/el.json | 12 ++++++ .../components/elgato/translations/el.json | 2 + .../components/elkm1/translations/el.json | 19 +++++++-- .../components/elmax/translations/el.json | 5 +++ .../components/emonitor/translations/el.json | 6 ++- .../emulated_roku/translations/el.json | 4 ++ .../components/enocean/translations/el.json | 3 +- .../enphase_envoy/translations/el.json | 9 ++++ .../environment_canada/translations/el.json | 6 ++- .../components/epson/translations/el.json | 2 + .../evil_genius_labs/translations/el.json | 4 +- .../components/ezviz/translations/el.json | 13 ++++-- .../faa_delays/translations/el.json | 3 +- .../fireservicerota/translations/el.json | 10 +++++ .../components/firmata/translations/el.json | 7 ++++ .../components/fivem/translations/el.json | 6 ++- .../fjaraskupan/translations/el.json | 3 +- .../flick_electric/translations/el.json | 11 ++++- .../components/flipr/translations/el.json | 8 +++- .../components/flo/translations/el.json | 8 ++++ .../components/flume/translations/el.json | 12 +++++- .../flunearyou/translations/el.json | 10 +++++ .../components/flux_led/translations/el.json | 8 ++++ .../forecast_solar/translations/el.json | 5 ++- .../forked_daapd/translations/el.json | 2 + .../components/foscam/translations/el.json | 7 +++- .../components/freebox/translations/el.json | 7 +++- .../freedompro/translations/el.json | 10 +++++ .../components/fritz/translations/el.json | 24 +++++++++-- .../components/fritzbox/translations/el.json | 15 +++++-- .../fritzbox_callmonitor/translations/el.json | 7 +++- .../components/fronius/translations/el.json | 7 +++- .../garages_amsterdam/translations/el.json | 5 +++ .../components/gdacs/translations/el.json | 3 ++ .../components/geofency/translations/el.json | 3 +- .../geonetnz_quakes/translations/el.json | 3 ++ .../geonetnz_volcano/translations/el.json | 3 ++ .../components/gios/translations/el.json | 5 +++ .../components/github/translations/el.json | 1 + .../components/glances/translations/el.json | 7 ++++ .../components/goalzero/translations/el.json | 4 +- .../components/gogogate2/translations/el.json | 10 ++++- .../components/goodwe/translations/el.json | 4 ++ .../google_travel_time/translations/el.json | 7 ++++ .../components/gpslogger/translations/el.json | 3 +- .../components/gree/translations/el.json | 13 ++++++ .../growatt_server/translations/el.json | 17 +++++++- .../components/guardian/translations/el.json | 5 ++- .../components/habitica/translations/el.json | 8 +++- .../components/hangouts/translations/el.json | 4 ++ .../components/harmony/translations/el.json | 7 ++++ .../components/heos/translations/el.json | 6 +++ .../hisense_aehw4a1/translations/el.json | 4 ++ .../components/hlk_sw16/translations/el.json | 8 ++++ .../home_connect/translations/el.json | 16 +++++++ .../home_plus_control/translations/el.json | 18 ++++++++ .../components/homekit/translations/el.json | 8 +++- .../homekit_controller/translations/el.json | 1 + .../homematicip_cloud/translations/el.json | 8 +++- .../homewizard/translations/el.json | 4 +- .../components/honeywell/translations/el.json | 6 ++- .../huawei_lte/translations/el.json | 3 ++ .../components/hue/translations/el.json | 12 +++++- .../huisbaasje/translations/el.json | 7 +++- .../translations/el.json | 7 ++++ .../hvv_departures/translations/el.json | 8 +++- .../components/hyperion/translations/el.json | 10 ++++- .../components/ialarm/translations/el.json | 6 ++- .../components/iaqualink/translations/el.json | 7 ++++ .../components/icloud/translations/el.json | 8 +++- .../components/ifttt/translations/el.json | 3 +- .../components/insteon/translations/el.json | 10 +++++ .../intellifire/translations/el.json | 6 ++- .../components/ios/translations/el.json | 12 ++++++ .../components/iotawatt/translations/el.json | 1 + .../components/ipma/translations/el.json | 9 +++- .../components/ipp/translations/el.json | 7 +++- .../components/iqvia/translations/el.json | 3 ++ .../components/iss/translations/el.json | 3 +- .../components/iss/translations/nl.json | 9 ++++ .../components/isy994/translations/el.json | 12 +++++- .../components/izone/translations/el.json | 4 ++ .../components/jellyfin/translations/el.json | 8 +++- .../components/juicenet/translations/el.json | 11 +++++ .../keenetic_ndms2/translations/el.json | 1 + .../components/kmtronic/translations/el.json | 7 +++- .../components/knx/translations/el.json | 4 ++ .../components/kodi/translations/el.json | 13 ++++++ .../components/konnected/translations/el.json | 9 +++- .../kostal_plenticore/translations/el.json | 4 ++ .../components/kraken/translations/el.json | 10 +++++ .../components/kulersky/translations/el.json | 13 ++++++ .../launch_library/translations/el.json | 3 ++ .../components/lcn/translations/el.json | 5 ++- .../components/life360/translations/el.json | 9 +++- .../components/lifx/translations/el.json | 4 ++ .../components/litejet/translations/el.json | 3 ++ .../litterrobot/translations/el.json | 7 +++- .../components/local_ip/translations/el.json | 4 ++ .../components/locative/translations/el.json | 4 +- .../logi_circle/translations/el.json | 8 +++- .../components/lookin/translations/el.json | 10 ++++- .../components/luftdaten/translations/el.json | 2 + .../lutron_caseta/translations/el.json | 5 +++ .../components/lyric/translations/el.json | 14 ++++++- .../components/mailgun/translations/el.json | 3 +- .../components/mazda/translations/el.json | 5 +++ .../components/melcloud/translations/el.json | 5 +++ .../components/met/translations/el.json | 12 +++++- .../met_eireann/translations/el.json | 9 +++- .../meteo_france/translations/el.json | 4 ++ .../meteoclimatic/translations/el.json | 7 ++++ .../components/metoffice/translations/el.json | 12 ++++++ .../components/mikrotik/translations/el.json | 7 ++++ .../components/mill/translations/el.json | 9 +++- .../minecraft_server/translations/el.json | 6 ++- .../components/mjpeg/translations/el.json | 20 +++++++-- .../components/mjpeg/translations/nl.json | 42 +++++++++++++++++++ .../modem_callerid/translations/el.json | 5 +++ .../modern_forms/translations/el.json | 10 +++++ .../moehlenhoff_alpha2/translations/el.json | 6 ++- .../components/monoprice/translations/el.json | 7 ++++ .../moon/translations/sensor.el.json | 2 + .../motion_blinds/translations/el.json | 8 ++++ .../components/motioneye/translations/el.json | 12 +++++- .../components/mqtt/translations/el.json | 20 +++++++-- .../components/mqtt/translations/it.json | 4 +- .../components/mutesync/translations/el.json | 4 +- .../components/myq/translations/el.json | 12 +++++- .../components/mysensors/translations/el.json | 11 ++++- .../components/mysensors/translations/it.json | 2 +- .../components/nam/translations/el.json | 7 ++++ .../components/nanoleaf/translations/el.json | 1 + .../components/neato/translations/el.json | 20 +++++++++ .../components/nest/translations/el.json | 27 +++++++++++- .../components/netatmo/translations/el.json | 16 ++++++- .../components/netgear/translations/el.json | 4 ++ .../components/nexia/translations/el.json | 11 ++++- .../nfandroidtv/translations/el.json | 10 ++++- .../nightscout/translations/el.json | 11 ++++- .../components/nina/translations/el.json | 7 +++- .../nmap_tracker/translations/el.json | 13 +++++- .../components/notion/translations/el.json | 11 ++++- .../components/nuheat/translations/el.json | 11 ++++- .../components/nuki/translations/el.json | 16 +++++-- .../components/nut/translations/el.json | 13 +++++- .../components/nws/translations/el.json | 10 +++++ .../components/nzbget/translations/el.json | 6 ++- .../components/octoprint/translations/el.json | 12 +++++- .../components/oncue/translations/el.json | 8 ++++ .../ondilo_ico/translations/el.json | 16 +++++++ .../components/onewire/translations/el.json | 5 +++ .../opengarage/translations/el.json | 11 ++++- .../opentherm_gw/translations/el.json | 5 ++- .../components/openuv/translations/el.json | 12 ++++++ .../openweathermap/translations/el.json | 4 ++ .../components/overkiz/translations/el.json | 6 ++- .../ovo_energy/translations/el.json | 8 +++- .../components/ozw/translations/el.json | 5 ++- .../panasonic_viera/translations/el.json | 6 +++ .../philips_js/translations/el.json | 7 +++- .../components/pi_hole/translations/el.json | 18 +++++++- .../components/picnic/translations/el.json | 9 +++- .../components/picnic/translations/nl.json | 4 +- .../components/plaato/translations/el.json | 5 ++- .../components/plex/translations/el.json | 9 +++- .../components/plugwise/translations/el.json | 8 ++++ .../plum_lightpad/translations/el.json | 6 +++ .../components/point/translations/el.json | 16 +++++-- .../components/poolsense/translations/el.json | 7 ++++ .../components/powerwall/translations/el.json | 12 +++++- .../components/profiler/translations/el.json | 12 ++++++ .../progettihwsw/translations/el.json | 7 ++++ .../components/prosegur/translations/el.json | 9 ++++ .../components/ps4/translations/el.json | 6 +++ .../pure_energie/translations/el.json | 7 ++++ .../pure_energie/translations/he.json | 19 +++++++++ .../pure_energie/translations/nl.json | 23 ++++++++++ .../pure_energie/translations/no.json | 23 ++++++++++ .../components/pvoutput/translations/el.json | 10 ++++- .../pvpc_hourly_pricing/translations/el.json | 3 ++ .../components/rachio/translations/el.json | 11 +++++ .../radio_browser/translations/ca.json | 12 ++++++ .../radio_browser/translations/el.json | 12 ++++++ .../radio_browser/translations/et.json | 12 ++++++ .../radio_browser/translations/it.json | 12 ++++++ .../radio_browser/translations/nl.json | 12 ++++++ .../radio_browser/translations/pt-BR.json | 12 ++++++ .../radio_browser/translations/ru.json | 12 ++++++ .../rainforest_eagle/translations/el.json | 2 + .../rainmachine/translations/el.json | 6 +++ .../recollect_waste/translations/el.json | 3 ++ .../components/renault/translations/el.json | 10 ++++- .../components/rfxtrx/translations/el.json | 12 +++++- .../components/ridwell/translations/el.json | 11 ++++- .../components/ring/translations/el.json | 7 ++++ .../components/risco/translations/el.json | 5 +++ .../translations/el.json | 7 +++- .../components/roku/translations/el.json | 8 ++++ .../components/roomba/translations/el.json | 4 ++ .../components/roon/translations/el.json | 7 ++++ .../ruckus_unleashed/translations/el.json | 9 ++++ .../components/samsungtv/translations/el.json | 13 +++++- .../components/sense/translations/ca.json | 16 ++++++- .../components/sense/translations/el.json | 8 ++++ .../components/sense/translations/pt-BR.json | 16 ++++++- .../components/senseme/translations/el.json | 5 +++ .../components/sensibo/translations/el.json | 4 ++ .../components/sensor/translations/el.json | 16 +++++++ .../components/sensor/translations/it.json | 18 ++++---- .../components/sentry/translations/el.json | 6 ++- .../components/sharkiq/translations/el.json | 8 ++++ .../components/shelly/translations/el.json | 16 +++++++ .../components/shelly/translations/it.json | 2 +- .../shopping_list/translations/el.json | 3 ++ .../components/sia/translations/el.json | 13 +++++- .../simplisafe/translations/el.json | 8 +++- .../components/sleepiq/translations/el.json | 9 +++- .../components/sleepiq/translations/nl.json | 19 +++++++++ .../components/sma/translations/el.json | 13 +++++- .../components/smappee/translations/el.json | 13 +++++- .../smart_meter_texas/translations/el.json | 8 ++++ .../components/smarthab/translations/el.json | 5 +++ .../smartthings/translations/el.json | 9 +++- .../components/smarttub/translations/el.json | 10 ++++- .../components/smhi/translations/el.json | 5 +++ .../components/sms/translations/el.json | 8 ++++ .../components/solaredge/translations/el.json | 6 +++ .../components/solarlog/translations/el.json | 7 ++++ .../components/solax/translations/el.json | 3 +- .../components/soma/translations/el.json | 6 +++ .../components/somfy/translations/el.json | 11 +++++ .../somfy_mylink/translations/el.json | 7 +++- .../components/sonarr/translations/el.json | 14 ++++++- .../components/songpal/translations/el.json | 4 ++ .../components/sonos/translations/el.json | 4 +- .../speedtestdotnet/translations/el.json | 5 +++ .../components/spider/translations/el.json | 7 ++++ .../components/spotify/translations/el.json | 4 ++ .../squeezebox/translations/el.json | 10 ++++- .../srp_energy/translations/el.json | 8 +++- .../components/steamist/translations/el.json | 10 ++++- .../stookalert/translations/el.json | 3 ++ .../components/subaru/translations/el.json | 4 +- .../surepetcare/translations/el.json | 8 ++++ .../components/switchbot/translations/el.json | 8 +++- .../switcher_kis/translations/el.json | 13 ++++++ .../components/syncthing/translations/el.json | 14 ++++++- .../components/syncthru/translations/el.json | 8 +++- .../synology_dsm/translations/el.json | 20 +++++++-- .../system_bridge/translations/el.json | 14 +++++++ .../components/tado/translations/el.json | 11 ++++- .../components/tailscale/translations/el.json | 11 +++++ .../components/tasmota/translations/el.json | 3 ++ .../tellduslive/translations/el.json | 15 ++++++- .../tesla_wall_connector/translations/el.json | 6 ++- .../components/tibber/translations/el.json | 5 +++ .../components/tile/translations/el.json | 10 ++++- .../components/tolo/translations/el.json | 7 ++++ .../components/toon/translations/el.json | 6 ++- .../totalconnect/translations/el.json | 11 +++-- .../components/tplink/translations/el.json | 8 ++++ .../components/traccar/translations/el.json | 3 +- .../components/tractive/translations/el.json | 8 +++- .../components/tradfri/translations/el.json | 5 +++ .../translations/el.json | 6 +++ .../transmission/translations/el.json | 6 +++ .../components/tuya/translations/el.json | 6 +++ .../tuya/translations/select.el.json | 27 ++++++++++-- .../tuya/translations/sensor.el.json | 3 ++ .../twentemilieu/translations/el.json | 5 +++ .../components/twilio/translations/el.json | 4 +- .../components/twinkly/translations/el.json | 9 ++++ .../components/unifi/translations/el.json | 15 +++++-- .../unifiprotect/translations/el.json | 11 +++-- .../components/upb/translations/el.json | 7 +++- .../components/upcloud/translations/el.json | 4 ++ .../components/upnp/translations/el.json | 4 +- .../uptimerobot/translations/el.json | 19 +++++++-- .../components/vallox/translations/el.json | 9 +++- .../components/velbus/translations/el.json | 4 ++ .../components/venstar/translations/el.json | 9 ++++ .../components/verisure/translations/el.json | 8 ++++ .../components/version/translations/el.json | 3 ++ .../components/vesync/translations/el.json | 6 +++ .../components/vicare/translations/el.json | 8 ++++ .../components/vilfo/translations/el.json | 9 ++++ .../components/vizio/translations/el.json | 10 ++++- .../vlc_telnet/translations/el.json | 12 ++++++ .../components/volumio/translations/el.json | 8 +++- .../components/wallbox/translations/el.json | 12 +++++- .../components/watttime/translations/el.json | 12 +++++- .../waze_travel_time/translations/el.json | 4 ++ .../components/webostv/translations/el.json | 2 + .../components/wemo/translations/el.json | 4 ++ .../components/whirlpool/translations/el.json | 5 +++ .../components/whois/translations/el.json | 3 ++ .../components/wiffi/translations/el.json | 3 ++ .../components/wilight/translations/el.json | 1 + .../components/withings/translations/el.json | 14 ++++++- .../components/wiz/translations/el.json | 10 ++++- .../components/wiz/translations/nl.json | 1 + .../components/wled/translations/el.json | 4 ++ .../wled/translations/select.el.json | 2 + .../components/wolflink/translations/el.json | 8 ++++ .../wolflink/translations/sensor.el.json | 2 + .../components/xbox/translations/el.json | 17 ++++++++ .../xiaomi_aqara/translations/el.json | 2 + .../xiaomi_miio/translations/el.json | 19 ++++++--- .../yale_smart_alarm/translations/el.json | 13 ++++-- .../yamaha_musiccast/translations/el.json | 4 ++ .../components/yeelight/translations/el.json | 7 ++++ .../components/youless/translations/el.json | 3 ++ .../components/zerproc/translations/el.json | 13 ++++++ .../components/zha/translations/el.json | 8 ++++ .../components/zha/translations/it.json | 4 +- .../zoneminder/translations/el.json | 8 +++- .../components/zwave/translations/el.json | 7 +++- .../components/zwave_js/translations/el.json | 36 +++++++++++++--- .../components/zwave_me/translations/el.json | 4 +- 399 files changed, 3041 insertions(+), 286 deletions(-) create mode 100644 homeassistant/components/dlna_dms/translations/pt-BR.json create mode 100644 homeassistant/components/firmata/translations/el.json create mode 100644 homeassistant/components/gree/translations/el.json create mode 100644 homeassistant/components/home_connect/translations/el.json create mode 100644 homeassistant/components/ios/translations/el.json create mode 100644 homeassistant/components/kulersky/translations/el.json create mode 100644 homeassistant/components/mjpeg/translations/nl.json create mode 100644 homeassistant/components/ondilo_ico/translations/el.json create mode 100644 homeassistant/components/profiler/translations/el.json create mode 100644 homeassistant/components/pure_energie/translations/he.json create mode 100644 homeassistant/components/pure_energie/translations/nl.json create mode 100644 homeassistant/components/pure_energie/translations/no.json create mode 100644 homeassistant/components/radio_browser/translations/ca.json create mode 100644 homeassistant/components/radio_browser/translations/el.json create mode 100644 homeassistant/components/radio_browser/translations/et.json create mode 100644 homeassistant/components/radio_browser/translations/it.json create mode 100644 homeassistant/components/radio_browser/translations/nl.json create mode 100644 homeassistant/components/radio_browser/translations/pt-BR.json create mode 100644 homeassistant/components/radio_browser/translations/ru.json create mode 100644 homeassistant/components/sleepiq/translations/nl.json create mode 100644 homeassistant/components/switcher_kis/translations/el.json create mode 100644 homeassistant/components/xbox/translations/el.json create mode 100644 homeassistant/components/zerproc/translations/el.json diff --git a/homeassistant/components/abode/translations/el.json b/homeassistant/components/abode/translations/el.json index 9f8043ebd853cf..b11c3cb6dbe742 100644 --- a/homeassistant/components/abode/translations/el.json +++ b/homeassistant/components/abode/translations/el.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03c5\u03b8\u03b5\u03bd\u03c4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", diff --git a/homeassistant/components/accuweather/translations/el.json b/homeassistant/components/accuweather/translations/el.json index 74756efbf38828..4f7a23e1d6f6c6 100644 --- a/homeassistant/components/accuweather/translations/el.json +++ b/homeassistant/components/accuweather/translations/el.json @@ -1,10 +1,21 @@ { "config": { + "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_api_key": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API", "requests_exceeded": "\u0388\u03c7\u03b5\u03b9 \u03be\u03b5\u03c0\u03b5\u03c1\u03b1\u03c3\u03c4\u03b5\u03af \u03bf \u03b5\u03c0\u03b9\u03c4\u03c1\u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03bf\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03b1\u03b9\u03c4\u03ae\u03c3\u03b5\u03c9\u03bd \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf API \u03c4\u03bf\u03c5 Accuweather. \u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b5\u03c1\u03b9\u03bc\u03ad\u03bd\u03b5\u03c4\u03b5 \u03ae \u03bd\u03b1 \u03b1\u03bb\u03bb\u03ac\u03be\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API." }, "step": { "user": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + }, "description": "\u0391\u03bd \u03c7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c3\u03c4\u03b5 \u03b2\u03bf\u03ae\u03b8\u03b5\u03b9\u03b1 \u03bc\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7, \u03c1\u03af\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03bc\u03b1\u03c4\u03b9\u03ac \u03b5\u03b4\u03ce: https://www.home-assistant.io/integrations/accuweather/\n\n\u039f\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03b9 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b5\u03c2 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf\u03b9 \u03b1\u03c0\u03cc \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae. \u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c4\u03bf\u03c5\u03c2 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c3\u03c4\u03bf \u03bc\u03b7\u03c4\u03c1\u03ce\u03bf \u03bf\u03bd\u03c4\u03bf\u03c4\u03ae\u03c4\u03c9\u03bd \u03bc\u03b5\u03c4\u03ac \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2.\n\u0397 \u03c0\u03c1\u03cc\u03b3\u03bd\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 \u03b1\u03c0\u03cc \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae. \u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c4\u03b7\u03bd \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c3\u03c4\u03b9\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2.", "title": "AccuWeather" } diff --git a/homeassistant/components/acmeda/translations/el.json b/homeassistant/components/acmeda/translations/el.json index 314fa9941676d0..9ce98dbfca3d14 100644 --- a/homeassistant/components/acmeda/translations/el.json +++ b/homeassistant/components/acmeda/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/adax/translations/el.json b/homeassistant/components/adax/translations/el.json index 328bfae922020a..ead0eb7e4f181f 100644 --- a/homeassistant/components/adax/translations/el.json +++ b/homeassistant/components/adax/translations/el.json @@ -1,8 +1,14 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "heater_not_available": "\u039f \u03b8\u03b5\u03c1\u03bc\u03b1\u03bd\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03bf\u03c2. \u03a0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b1\u03c6\u03ad\u03c1\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b8\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7 \u03c0\u03b1\u03c4\u03ce\u03bd\u03c4\u03b1\u03c2 + \u03ba\u03b1\u03b9 OK \u03b3\u03b9\u03b1 \u03bc\u03b5\u03c1\u03b9\u03ba\u03ac \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1.", - "heater_not_found": "\u039f \u03b8\u03b5\u03c1\u03bc\u03b1\u03bd\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5. \u03a0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03bc\u03b5\u03c4\u03b1\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b8\u03b5\u03c1\u03bc\u03b1\u03bd\u03c4\u03ae\u03c1\u03b1 \u03c0\u03b9\u03bf \u03ba\u03bf\u03bd\u03c4\u03ac \u03c3\u03c4\u03bf\u03bd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae Home Assistant." + "heater_not_found": "\u039f \u03b8\u03b5\u03c1\u03bc\u03b1\u03bd\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5. \u03a0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03bc\u03b5\u03c4\u03b1\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b8\u03b5\u03c1\u03bc\u03b1\u03bd\u03c4\u03ae\u03c1\u03b1 \u03c0\u03b9\u03bf \u03ba\u03bf\u03bd\u03c4\u03ac \u03c3\u03c4\u03bf\u03bd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae Home Assistant.", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "step": { "cloud": { diff --git a/homeassistant/components/adguard/translations/el.json b/homeassistant/components/adguard/translations/el.json index 7d0f716a1c7565..37b242811babe9 100644 --- a/homeassistant/components/adguard/translations/el.json +++ b/homeassistant/components/adguard/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "existing_instance_updated": "\u0395\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03b7 \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03c3\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7." }, "error": { @@ -16,7 +17,9 @@ "host": "\u0394\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "port": "\u0398\u03cd\u03c1\u03b1", - "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + "ssl": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03ad\u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", + "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL" }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf AdGuard Home \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c8\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf." } diff --git a/homeassistant/components/advantage_air/translations/el.json b/homeassistant/components/advantage_air/translations/el.json index 146c37f16d1244..b49125616cf507 100644 --- a/homeassistant/components/advantage_air/translations/el.json +++ b/homeassistant/components/advantage_air/translations/el.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/aemet/translations/el.json b/homeassistant/components/aemet/translations/el.json index 5e66d06c6cae85..757fd41be703f3 100644 --- a/homeassistant/components/aemet/translations/el.json +++ b/homeassistant/components/aemet/translations/el.json @@ -1,8 +1,17 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "invalid_api_key": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API" + }, "step": { "user": { "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 AEMET OpenData. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://opendata.aemet.es/centrodedescargas/altaUsuario", diff --git a/homeassistant/components/agent_dvr/translations/el.json b/homeassistant/components/agent_dvr/translations/el.json index 84197d3ef01aa8..b9990eb1c04aa8 100644 --- a/homeassistant/components/agent_dvr/translations/el.json +++ b/homeassistant/components/agent_dvr/translations/el.json @@ -1,9 +1,17 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Agent DVR" } diff --git a/homeassistant/components/airly/translations/el.json b/homeassistant/components/airly/translations/el.json index ce9ec6fc8500e3..adb944a4259622 100644 --- a/homeassistant/components/airly/translations/el.json +++ b/homeassistant/components/airly/translations/el.json @@ -1,11 +1,20 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { "invalid_api_key": "\u0386\u03ba\u03c5\u03c1\u03bf API \u03ba\u03bb\u03b5\u03b9\u03b4\u03af", "wrong_location": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03bd \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03af \u03bc\u03ad\u03c4\u03c1\u03b7\u03c3\u03b7\u03c2 Airly \u03c3\u03c4\u03b7\u03bd \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae \u03b1\u03c5\u03c4\u03ae." }, "step": { "user": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c0\u03bf\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b1\u03ad\u03c1\u03b1 Airly. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API, \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://developer.airly.eu/register", "title": "Airly" } diff --git a/homeassistant/components/airnow/translations/el.json b/homeassistant/components/airnow/translations/el.json index 1ddb64a0420555..caaaafc9e9a383 100644 --- a/homeassistant/components/airnow/translations/el.json +++ b/homeassistant/components/airnow/translations/el.json @@ -1,13 +1,26 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", - "invalid_location": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03b1\u03c0\u03bf\u03c4\u03b5\u03bb\u03ad\u03c3\u03bc\u03b1\u03c4\u03b1 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7\u03bd \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1" + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "invalid_location": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03b1\u03c0\u03bf\u03c4\u03b5\u03bb\u03ad\u03c3\u03bc\u03b1\u03c4\u03b1 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7\u03bd \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { - "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 AirNow \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c0\u03bf\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1 \u03c4\u03bf\u03c5 \u03b1\u03ad\u03c1\u03b1. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://docs.airnowapi.org/account/request/" + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2", + "radius": "\u0391\u03ba\u03c4\u03af\u03bd\u03b1 \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd (\u03bc\u03af\u03bb\u03b9\u03b1, \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)" + }, + "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 AirNow \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c0\u03bf\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1 \u03c4\u03bf\u03c5 \u03b1\u03ad\u03c1\u03b1. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://docs.airnowapi.org/account/request/", + "title": "AirNow" } } - } + }, + "title": "AirNow" } \ No newline at end of file diff --git a/homeassistant/components/airthings/translations/el.json b/homeassistant/components/airthings/translations/el.json index 63bf11b8c07f0a..96ead315392885 100644 --- a/homeassistant/components/airthings/translations/el.json +++ b/homeassistant/components/airthings/translations/el.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/airtouch4/translations/el.json b/homeassistant/components/airtouch4/translations/el.json index 7a94a9c6dfa5eb..8790c6edab4ef5 100644 --- a/homeassistant/components/airtouch4/translations/el.json +++ b/homeassistant/components/airtouch4/translations/el.json @@ -1,6 +1,10 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "no_units": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03ba\u03b1\u03bc\u03af\u03b1 \u03bf\u03bc\u03ac\u03b4\u03b1 AirTouch 4." }, "step": { diff --git a/homeassistant/components/airvisual/translations/el.json b/homeassistant/components/airvisual/translations/el.json index 997a547e34f9d6..bb4268a3ffd975 100644 --- a/homeassistant/components/airvisual/translations/el.json +++ b/homeassistant/components/airvisual/translations/el.json @@ -1,20 +1,28 @@ { "config": { "abort": { - "already_configured": "\u0397 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af \u03ae \u03c4\u03bf Node/Pro ID \u03b5\u03af\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03ba\u03b1\u03c4\u03b1\u03c7\u03c9\u03c1\u03b7\u03bc\u03ad\u03bd\u03bf." + "already_configured": "\u0397 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af \u03ae \u03c4\u03bf Node/Pro ID \u03b5\u03af\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03ba\u03b1\u03c4\u03b1\u03c7\u03c9\u03c1\u03b7\u03bc\u03ad\u03bd\u03bf.", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "general_error": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1", "invalid_api_key": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API", "location_not_found": "\u0397 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5" }, "step": { "geography_by_coords": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2" + }, "description": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf AirVisual cloud API \u03b3\u03b9\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03bf\u03cd \u03c0\u03bb\u03ac\u03c4\u03bf\u03c5\u03c2/\u03bc\u03ae\u03ba\u03bf\u03c5\u03c2.", "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03af\u03b1\u03c2" }, "geography_by_name": { "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", "city": "\u03a0\u03cc\u03bb\u03b7", "country": "\u03a7\u03ce\u03c1\u03b1", "state": "\u03ba\u03c1\u03ac\u03c4\u03bf\u03c2" @@ -24,12 +32,16 @@ }, "node_pro": { "data": { + "ip_address": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c9\u03c0\u03b9\u03ba\u03ae \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 AirVisual. \u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b1\u03bd\u03b1\u03ba\u03c4\u03b7\u03b8\u03b5\u03af \u03b1\u03c0\u03cc \u03c4\u03bf UI \u03c4\u03b7\u03c2 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1\u03c2.", "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03bd\u03cc\u03c2 \u03ba\u03cc\u03bc\u03b2\u03bf\u03c5 AirVisual Node/Pro" }, "reauth_confirm": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API" + }, "title": "\u0395\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 AirVisual" }, "user": { diff --git a/homeassistant/components/alarmdecoder/translations/el.json b/homeassistant/components/alarmdecoder/translations/el.json index 7c3b0b6737c08b..4923b2eb0cd4f2 100644 --- a/homeassistant/components/alarmdecoder/translations/el.json +++ b/homeassistant/components/alarmdecoder/translations/el.json @@ -6,6 +6,9 @@ "create_entry": { "default": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf AlarmDecoder." }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "protocol": { "data": { diff --git a/homeassistant/components/almond/translations/el.json b/homeassistant/components/almond/translations/el.json index 716eb30c4a4156..ac3a8efd757cdc 100644 --- a/homeassistant/components/almond/translations/el.json +++ b/homeassistant/components/almond/translations/el.json @@ -1,9 +1,18 @@ { "config": { + "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "missing_configuration": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7.", + "no_url_available": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL. \u0393\u03b9\u03b1 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1, [\u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b2\u03bf\u03ae\u03b8\u03b5\u03b9\u03b1\u03c2] ( {docs_url} )", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, "step": { "hassio_confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03bf\u03c5\u03c2 \u03c4\u03bf\u03c5 Home Assistant \u03ce\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03b5\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03c4\u03bf Almond \u03c0\u03bf\u03c5 \u03c0\u03b1\u03c1\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf: {addon};", "title": "\u03a0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf Almond \u03bc\u03ad\u03c3\u03c9 Home Assistant" + }, + "pick_implementation": { + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03bc\u03b5\u03b8\u03cc\u03b4\u03bf\u03c5 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" } } } diff --git a/homeassistant/components/ambee/translations/el.json b/homeassistant/components/ambee/translations/el.json index a6db38ee103011..3576b6cd852fa4 100644 --- a/homeassistant/components/ambee/translations/el.json +++ b/homeassistant/components/ambee/translations/el.json @@ -1,12 +1,26 @@ { "config": { + "abort": { + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_api_key": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API" + }, "step": { "reauth_confirm": { "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", "description": "\u0395\u03c0\u03b1\u03bd\u03b1\u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 Ambee." } }, "user": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf Ambee \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Home Assistant." } } diff --git a/homeassistant/components/ambiclimate/translations/el.json b/homeassistant/components/ambiclimate/translations/el.json index f77e38ce6fa8c4..c2313d646f6d61 100644 --- a/homeassistant/components/ambiclimate/translations/el.json +++ b/homeassistant/components/ambiclimate/translations/el.json @@ -1,7 +1,12 @@ { "config": { "abort": { - "access_token": "\u0386\u03b3\u03bd\u03c9\u03c3\u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b5\u03bd\u03cc\u03c2 \u03c3\u03c5\u03bc\u03b2\u03cc\u03bb\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2." + "access_token": "\u0386\u03b3\u03bd\u03c9\u03c3\u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b5\u03bd\u03cc\u03c2 \u03c3\u03c5\u03bc\u03b2\u03cc\u03bb\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2.", + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "missing_configuration": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7." + }, + "create_entry": { + "default": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "error": { "follow_link": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf \u03ba\u03b1\u03b9 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af\u03c4\u03b5 \u03c0\u03c1\u03b9\u03bd \u03c0\u03b1\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03a5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae", diff --git a/homeassistant/components/ambient_station/translations/el.json b/homeassistant/components/ambient_station/translations/el.json index 1339995b647a63..0b69fc40b3ee80 100644 --- a/homeassistant/components/ambient_station/translations/el.json +++ b/homeassistant/components/ambient_station/translations/el.json @@ -1,7 +1,18 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, + "error": { + "invalid_key": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API", + "no_devices": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc" + }, "step": { "user": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", + "app_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2" + }, "title": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03b1\u03c2" } } diff --git a/homeassistant/components/ambient_station/translations/it.json b/homeassistant/components/ambient_station/translations/it.json index 8984314349c659..ea3b9dcb95b692 100644 --- a/homeassistant/components/ambient_station/translations/it.json +++ b/homeassistant/components/ambient_station/translations/it.json @@ -11,7 +11,7 @@ "user": { "data": { "api_key": "Chiave API", - "app_key": "Application Key" + "app_key": "Chiave dell'applicazione" }, "title": "Inserisci i tuoi dati" } diff --git a/homeassistant/components/androidtv/translations/el.json b/homeassistant/components/androidtv/translations/el.json index 147d0f2e8640f7..67db15ff17002b 100644 --- a/homeassistant/components/androidtv/translations/el.json +++ b/homeassistant/components/androidtv/translations/el.json @@ -1,12 +1,15 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "invalid_unique_id": "\u0391\u03b4\u03cd\u03bd\u03b1\u03c4\u03bf\u03c2 \u03bf \u03c0\u03c1\u03bf\u03c3\u03b4\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03b5\u03bd\u03cc\u03c2 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c5 \u03bc\u03bf\u03bd\u03b1\u03b4\u03b9\u03ba\u03bf\u03cd \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03bf\u03cd \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" }, "error": { "adbkey_not_file": "\u03a4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd ADB \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", - "key_and_server": "\u03a0\u03b1\u03c1\u03ad\u03c7\u03b5\u03c4\u03b5 \u03bc\u03cc\u03bd\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af ADB \u03ae \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae ADB" + "invalid_host": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", + "key_and_server": "\u03a0\u03b1\u03c1\u03ad\u03c7\u03b5\u03c4\u03b5 \u03bc\u03cc\u03bd\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af ADB \u03ae \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae ADB", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/apple_tv/translations/el.json b/homeassistant/components/apple_tv/translations/el.json index d446d618746b82..11a61899b84374 100644 --- a/homeassistant/components/apple_tv/translations/el.json +++ b/homeassistant/components/apple_tv/translations/el.json @@ -1,15 +1,25 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_configured_device": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", "backoff": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b4\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03b1\u03b9\u03c4\u03ae\u03bc\u03b1\u03c4\u03b1 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7\u03c2 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7 \u03c3\u03c4\u03b9\u03b3\u03bc\u03ae (\u03af\u03c3\u03c9\u03c2 \u03ad\u03c7\u03b5\u03c4\u03b5 \u03c0\u03bb\u03b7\u03ba\u03c4\u03c1\u03bf\u03bb\u03bf\u03b3\u03ae\u03c3\u03b5\u03b9 \u03ac\u03ba\u03c5\u03c1\u03bf \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc PIN \u03c0\u03ac\u03c1\u03b1 \u03c0\u03bf\u03bb\u03bb\u03ad\u03c2 \u03c6\u03bf\u03c1\u03ad\u03c2), \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1.", "device_did_not_pair": "\u0394\u03b5\u03bd \u03ad\u03b3\u03b9\u03bd\u03b5 \u03ba\u03b1\u03bc\u03af\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c0\u03ac\u03b8\u03b5\u03b9\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03ae\u03c1\u03c9\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03b9\u03ba\u03b1\u03c3\u03af\u03b1\u03c2 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae.", "device_not_found": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", "inconsistent_device": "\u03a4\u03b1 \u03b1\u03bd\u03b1\u03bc\u03b5\u03bd\u03cc\u03bc\u03b5\u03bd\u03b1 \u03c0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03b1 \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7. \u0391\u03c5\u03c4\u03cc \u03c3\u03c5\u03bd\u03ae\u03b8\u03c9\u03c2 \u03c5\u03c0\u03bf\u03b4\u03b7\u03bb\u03ce\u03bd\u03b5\u03b9 \u03c0\u03c1\u03cc\u03b2\u03bb\u03b7\u03bc\u03b1 \u03bc\u03b5 \u03c4\u03bf multicast DNS (Zeroconf). \u03a0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae.", "invalid_config": "\u0397 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bb\u03bb\u03b9\u03c0\u03ae\u03c2. \u03a0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", - "setup_failed": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2." + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2", + "setup_failed": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "error": { - "no_usable_service": "\u0392\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae, \u03b1\u03bb\u03bb\u03ac \u03b4\u03b5\u03bd \u03bc\u03c0\u03cc\u03c1\u03b5\u03c3\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03c4\u03b5\u03af \u03ba\u03b1\u03bd\u03ad\u03bd\u03b1\u03c2 \u03c4\u03c1\u03cc\u03c0\u03bf\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03b7\u03b8\u03b5\u03af \u03bc\u03b9\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ae\u03bd. \u0391\u03bd \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b2\u03bb\u03ad\u03c0\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03bc\u03ae\u03bd\u03c5\u03bc\u03b1, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b4\u03b9\u03bf\u03c1\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03ae \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Apple TV." + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", + "no_usable_service": "\u0392\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae, \u03b1\u03bb\u03bb\u03ac \u03b4\u03b5\u03bd \u03bc\u03c0\u03cc\u03c1\u03b5\u03c3\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03c4\u03b5\u03af \u03ba\u03b1\u03bd\u03ad\u03bd\u03b1\u03c2 \u03c4\u03c1\u03cc\u03c0\u03bf\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03b7\u03b8\u03b5\u03af \u03bc\u03b9\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ae\u03bd. \u0391\u03bd \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b2\u03bb\u03ad\u03c0\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03bc\u03ae\u03bd\u03c5\u03bc\u03b1, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b4\u03b9\u03bf\u03c1\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03ae \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Apple TV.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "flow_title": "{name} ({type})", "step": { @@ -22,6 +32,9 @@ "title": "\u03a3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7" }, "pair_with_pin": { + "data": { + "pin": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN" + }, "description": "\u0397 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c4\u03bf \u03c0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf `{protocol}`. \u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc PIN \u03c0\u03bf\u03c5 \u03b5\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03b7\u03bd \u03bf\u03b8\u03cc\u03bd\u03b7. \u03a4\u03b1 \u03c0\u03c1\u03ce\u03c4\u03b1 \u03bc\u03b7\u03b4\u03b5\u03bd\u03b9\u03ba\u03ac \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bb\u03b5\u03af\u03c0\u03bf\u03bd\u03c4\u03b1\u03b9, \u03c0.\u03c7. \u03c0\u03bb\u03b7\u03ba\u03c4\u03c1\u03bf\u03bb\u03bf\u03b3\u03ae\u03c3\u03c4\u03b5 123 \u03b5\u03ac\u03bd \u03bf \u03b5\u03bc\u03c6\u03b1\u03bd\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03bf\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 0123.", "title": "\u03a3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7" }, @@ -53,6 +66,9 @@ "options": { "step": { "init": { + "data": { + "start_off": "\u039c\u03b7\u03bd \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 Home Assistant" + }, "description": "\u0394\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b3\u03b5\u03bd\u03b9\u03ba\u03ad\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" } } diff --git a/homeassistant/components/arcam_fmj/translations/el.json b/homeassistant/components/arcam_fmj/translations/el.json index 214605b1aa9c09..bc639192d8604a 100644 --- a/homeassistant/components/arcam_fmj/translations/el.json +++ b/homeassistant/components/arcam_fmj/translations/el.json @@ -1,5 +1,10 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "flow_title": "{host}", "step": { "confirm": { @@ -7,7 +12,8 @@ }, "user": { "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2." } diff --git a/homeassistant/components/aseko_pool_live/translations/el.json b/homeassistant/components/aseko_pool_live/translations/el.json index 51d7e3a7308bf2..417f896375fb6c 100644 --- a/homeassistant/components/aseko_pool_live/translations/el.json +++ b/homeassistant/components/aseko_pool_live/translations/el.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/asuswrt/translations/el.json b/homeassistant/components/asuswrt/translations/el.json index c704dd81b7fd64..ea926f10bd275f 100644 --- a/homeassistant/components/asuswrt/translations/el.json +++ b/homeassistant/components/asuswrt/translations/el.json @@ -1,14 +1,21 @@ { "config": { + "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_host": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", "pwd_and_ssh": "\u03a0\u03b1\u03c1\u03ad\u03c7\u03b5\u03c4\u03b5 \u03bc\u03cc\u03bd\u03bf \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03ae \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd SSH", "pwd_or_ssh": "\u0394\u03ce\u03c3\u03c4\u03b5 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03ae \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd SSH", - "ssh_not_file": "\u03a4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd SSH \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5" + "ssh_not_file": "\u03a4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd SSH \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1", "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "port": "\u0398\u03cd\u03c1\u03b1", diff --git a/homeassistant/components/atag/translations/el.json b/homeassistant/components/atag/translations/el.json index 073676503ebce5..f3f47f1845074b 100644 --- a/homeassistant/components/atag/translations/el.json +++ b/homeassistant/components/atag/translations/el.json @@ -1,6 +1,10 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "unauthorized": "\u0397 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7 \u03b1\u03c0\u03bf\u03c1\u03c1\u03af\u03c6\u03b8\u03b7\u03ba\u03b5, \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b3\u03b9\u03b1 \u03b1\u03af\u03c4\u03b7\u03bc\u03b1 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2" }, "step": { diff --git a/homeassistant/components/august/translations/el.json b/homeassistant/components/august/translations/el.json index 1ee7a68fa87bef..a8516ec8041cb2 100644 --- a/homeassistant/components/august/translations/el.json +++ b/homeassistant/components/august/translations/el.json @@ -1,5 +1,14 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "reauth_validate": { "data": { diff --git a/homeassistant/components/aurora/translations/el.json b/homeassistant/components/aurora/translations/el.json index 491ef12d92006b..c93ed8f34e24f9 100644 --- a/homeassistant/components/aurora/translations/el.json +++ b/homeassistant/components/aurora/translations/el.json @@ -1,12 +1,26 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1" } } } }, + "options": { + "step": { + "init": { + "data": { + "threshold": "\u038c\u03c1\u03b9\u03bf (%)" + } + } + } + }, "title": "\u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 NOAA Aurora" } \ No newline at end of file diff --git a/homeassistant/components/aurora_abb_powerone/translations/el.json b/homeassistant/components/aurora_abb_powerone/translations/el.json index eec0a7cb3831d4..834c5794861b95 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/el.json +++ b/homeassistant/components/aurora_abb_powerone/translations/el.json @@ -1,12 +1,14 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "no_serial_ports": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03b8\u03cd\u03c1\u03b5\u03c2 com. \u03a7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae RS485 \u03b3\u03b9\u03b1 \u03b5\u03c0\u03b9\u03ba\u03bf\u03b9\u03bd\u03c9\u03bd\u03af\u03b1." }, "error": { "cannot_connect": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7, \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1, \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7, \u03c4\u03b7\u03bd \u03b7\u03bb\u03b5\u03ba\u03c4\u03c1\u03b9\u03ba\u03ae \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03cc\u03c4\u03b9 \u03bf \u03bc\u03b5\u03c4\u03b1\u03c4\u03c1\u03bf\u03c0\u03ad\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf\u03c2 (\u03c3\u03c4\u03bf \u03c6\u03c9\u03c2 \u03c4\u03b7\u03c2 \u03b7\u03bc\u03ad\u03c1\u03b1\u03c2)", "cannot_open_serial_port": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc \u03c4\u03bf \u03ac\u03bd\u03bf\u03b9\u03b3\u03bc\u03b1 \u03c4\u03b7\u03c2 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae\u03c2 \u03b8\u03cd\u03c1\u03b1\u03c2, \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac", - "invalid_serial_port": "\u0397 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ae \u03b4\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc \u03bd\u03b1 \u03b1\u03bd\u03bf\u03af\u03be\u03b5\u03b9" + "invalid_serial_port": "\u0397 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ae \u03b4\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc \u03bd\u03b1 \u03b1\u03bd\u03bf\u03af\u03be\u03b5\u03b9", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/aussie_broadband/translations/el.json b/homeassistant/components/aussie_broadband/translations/el.json index 217746da084303..0b78eacb8261ea 100644 --- a/homeassistant/components/aussie_broadband/translations/el.json +++ b/homeassistant/components/aussie_broadband/translations/el.json @@ -1,23 +1,29 @@ { "config": { "abort": { - "no_services_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc" + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "no_services_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "reauth": { "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, - "description": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 {username}" + "description": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 {username}", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, "reauth_confirm": { "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, - "description": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 {username}" + "description": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 {username}", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, "service": { "data": { @@ -35,7 +41,9 @@ }, "options": { "abort": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "init": { diff --git a/homeassistant/components/aussie_broadband/translations/nl.json b/homeassistant/components/aussie_broadband/translations/nl.json index c1ca5b7717acea..21da52666bc8d7 100644 --- a/homeassistant/components/aussie_broadband/translations/nl.json +++ b/homeassistant/components/aussie_broadband/translations/nl.json @@ -18,6 +18,13 @@ "description": "Update wachtwoord voor {username}", "title": "Verifieer de integratie opnieuw" }, + "reauth_confirm": { + "data": { + "password": "Wachtwoord" + }, + "description": "Update wachtwoord voor {username}", + "title": "Verifieer de integratie opnieuw" + }, "service": { "data": { "services": "Services" diff --git a/homeassistant/components/awair/translations/el.json b/homeassistant/components/awair/translations/el.json index dde9b024ecde69..0acefe23c02210 100644 --- a/homeassistant/components/awair/translations/el.json +++ b/homeassistant/components/awair/translations/el.json @@ -1,14 +1,25 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "invalid_access_token": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "reauth": { "data": { + "access_token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "email": "Email" }, "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c0\u03c1\u03bf\u03b3\u03c1\u03b1\u03bc\u03bc\u03b1\u03c4\u03b9\u03c3\u03c4\u03ae Awair." }, "user": { "data": { + "access_token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "email": "Email" }, "description": "\u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03b5\u03af\u03c4\u03b5 \u03b3\u03b9\u03b1 \u03ad\u03bd\u03b1 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c0\u03c1\u03bf\u03b3\u03c1\u03b1\u03bc\u03bc\u03b1\u03c4\u03b9\u03c3\u03c4\u03ae Awair \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7: https://developer.getawair.com/onboard/login" diff --git a/homeassistant/components/axis/translations/el.json b/homeassistant/components/axis/translations/el.json index 79f3f80eca4d8c..09ad867132906c 100644 --- a/homeassistant/components/axis/translations/el.json +++ b/homeassistant/components/axis/translations/el.json @@ -1,9 +1,16 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "link_local_address": "\u039f\u03b9 \u03c4\u03bf\u03c0\u03b9\u03ba\u03ad\u03c2 \u03b4\u03b9\u03b5\u03c5\u03b8\u03cd\u03bd\u03c3\u03b5\u03b9\u03c2 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03bc\u03bf\u03c5 \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03bf\u03bd\u03c4\u03b1\u03b9", "not_axis_device": "\u0397 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03c5\u03c6\u03b8\u03b5\u03af\u03c3\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Axis" }, + "error": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "flow_title": "{name} ({host})", "step": { "user": { diff --git a/homeassistant/components/azure_devops/translations/el.json b/homeassistant/components/azure_devops/translations/el.json index 5f8926f1a51d54..55197510f35966 100644 --- a/homeassistant/components/azure_devops/translations/el.json +++ b/homeassistant/components/azure_devops/translations/el.json @@ -1,6 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "project_error": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03bb\u03ae\u03c8\u03b7 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03b9\u03ce\u03bd \u03ad\u03c1\u03b3\u03bf\u03c5." }, "flow_title": "{project_url}", diff --git a/homeassistant/components/azure_event_hub/translations/el.json b/homeassistant/components/azure_event_hub/translations/el.json index 5ce9391d92a7b3..a68ac09a3fdeea 100644 --- a/homeassistant/components/azure_event_hub/translations/el.json +++ b/homeassistant/components/azure_event_hub/translations/el.json @@ -1,11 +1,14 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", "cannot_connect": "\u0397 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03b1\u03c0\u03cc \u03c4\u03bf configuration.yaml \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5, \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03c6\u03b1\u03b9\u03c1\u03ad\u03c3\u03c4\u03b5 \u03b1\u03c0\u03cc \u03c4\u03bf yaml \u03ba\u03b1\u03b9 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03bf\u03ae \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c9\u03bd.", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", "unknown": "\u0397 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03b1\u03c0\u03cc \u03c4\u03bf configuration.yaml \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03bc\u03b5 \u03ac\u03b3\u03bd\u03c9\u03c3\u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1, \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03c6\u03b1\u03b9\u03c1\u03ad\u03c3\u03c4\u03b5 \u03c4\u03bf yaml \u03ba\u03b1\u03b9 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03bf\u03ae config." }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "conn_string": { diff --git a/homeassistant/components/balboa/translations/el.json b/homeassistant/components/balboa/translations/el.json index df96ad6e341f78..e85920108d3f2a 100644 --- a/homeassistant/components/balboa/translations/el.json +++ b/homeassistant/components/balboa/translations/el.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/binary_sensor/translations/ca.json b/homeassistant/components/binary_sensor/translations/ca.json index e2450646ef854d..4c27c1e2966ffe 100644 --- a/homeassistant/components/binary_sensor/translations/ca.json +++ b/homeassistant/components/binary_sensor/translations/ca.json @@ -134,6 +134,10 @@ "off": "No carregant", "on": "Carregant" }, + "carbon_monoxide": { + "off": "Lliure", + "on": "Detectat" + }, "co": { "off": "Lliure", "on": "Detectat" diff --git a/homeassistant/components/binary_sensor/translations/el.json b/homeassistant/components/binary_sensor/translations/el.json index 6688159ee93ec7..1cfe13d694b1b9 100644 --- a/homeassistant/components/binary_sensor/translations/el.json +++ b/homeassistant/components/binary_sensor/translations/el.json @@ -75,6 +75,8 @@ "no_sound": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03ae\u03c7\u03bf", "no_update": "{entity_name} \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5", "no_vibration": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03b4\u03cc\u03bd\u03b7\u03c3\u03b7", + "not_bat_low": "\u0397 \u03bc\u03c0\u03b1\u03c4\u03b1\u03c1\u03af\u03b1 \u03c4\u03bf\u03c5 {entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03ba\u03b1\u03bd\u03bf\u03bd\u03b9\u03ba\u03ae", + "not_cold": "{entity_name} \u03bc\u03b5\u03c4\u03b5\u03c4\u03c1\u03ac\u03c0\u03b7 \u03c3\u03b5 \u03bc\u03b7 \u03ba\u03c1\u03cd\u03bf", "not_connected": "{entity_name} \u03b1\u03c0\u03bf\u03c3\u03c5\u03bd\u03b4\u03ad\u03b8\u03b7\u03ba\u03b5", "not_hot": "{entity_name} \u03ad\u03b3\u03b9\u03bd\u03b5 \u03bc\u03b7 \u03ba\u03b1\u03c5\u03c4\u03cc", "not_locked": "{entity_name} \u03be\u03b5\u03ba\u03bb\u03b5\u03b9\u03b4\u03ce\u03b8\u03b7\u03ba\u03b5", @@ -132,6 +134,10 @@ "off": "\u0394\u03b5 \u03c6\u03bf\u03c1\u03c4\u03af\u03b6\u03b5\u03b9", "on": "\u03a6\u03bf\u03c1\u03c4\u03af\u03b6\u03b5\u03b9" }, + "carbon_monoxide": { + "off": "\u0394\u03b5\u03bd \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5", + "on": "\u0395\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5" + }, "co": { "off": "\u0394\u03b5\u03bd \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5", "on": "\u0395\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5" @@ -153,7 +159,7 @@ "on": "\u0386\u03bd\u03bf\u03b9\u03b3\u03bc\u03b1" }, "gas": { - "off": "\u0394\u03b5\u03bd \u0395\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5", + "off": "\u0394\u03b5\u03bd \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5", "on": "\u0395\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5" }, "heat": { diff --git a/homeassistant/components/binary_sensor/translations/en.json b/homeassistant/components/binary_sensor/translations/en.json index 1dc6cf2caa1b8e..1d4f30fef52d2c 100644 --- a/homeassistant/components/binary_sensor/translations/en.json +++ b/homeassistant/components/binary_sensor/translations/en.json @@ -59,6 +59,8 @@ "connected": "{entity_name} connected", "gas": "{entity_name} started detecting gas", "hot": "{entity_name} became hot", + "is_not_tampered": "{entity_name} stopped detecting tampering", + "is_tampered": "{entity_name} started detecting tampering", "light": "{entity_name} started detecting light", "locked": "{entity_name} locked", "moist": "{entity_name} became moist", @@ -136,6 +138,10 @@ "off": "Clear", "on": "Detected" }, + "co": { + "off": "Clear", + "on": "Detected" + }, "cold": { "off": "Normal", "on": "Cold" diff --git a/homeassistant/components/binary_sensor/translations/it.json b/homeassistant/components/binary_sensor/translations/it.json index efe7d6da3a7d6c..5c81e8942e469c 100644 --- a/homeassistant/components/binary_sensor/translations/it.json +++ b/homeassistant/components/binary_sensor/translations/it.json @@ -134,6 +134,10 @@ "off": "Non in carica", "on": "In carica" }, + "carbon_monoxide": { + "off": "Assente", + "on": "Rilevato" + }, "co": { "off": "Non Rilevato", "on": "Rilevato" diff --git a/homeassistant/components/binary_sensor/translations/nl.json b/homeassistant/components/binary_sensor/translations/nl.json index 504b9a4dd48df9..7afbcaf616f7f4 100644 --- a/homeassistant/components/binary_sensor/translations/nl.json +++ b/homeassistant/components/binary_sensor/translations/nl.json @@ -134,6 +134,10 @@ "off": "Niet aan het opladen", "on": "Opladen" }, + "carbon_monoxide": { + "off": "Niet gedetecteerd", + "on": "Gedetecteerd" + }, "co": { "off": "Niet gedetecteerd", "on": "Gedetecteerd" diff --git a/homeassistant/components/binary_sensor/translations/pt-BR.json b/homeassistant/components/binary_sensor/translations/pt-BR.json index 2e052970d63fba..779830e0961bce 100644 --- a/homeassistant/components/binary_sensor/translations/pt-BR.json +++ b/homeassistant/components/binary_sensor/translations/pt-BR.json @@ -134,6 +134,10 @@ "off": "N\u00e3o est\u00e1 carregando", "on": "Carregando" }, + "carbon_monoxide": { + "off": "Remover", + "on": "Detectou" + }, "co": { "off": "Limpo", "on": "Detectado" diff --git a/homeassistant/components/blebox/translations/el.json b/homeassistant/components/blebox/translations/el.json index 14320019471e9d..c6c2f597f1d287 100644 --- a/homeassistant/components/blebox/translations/el.json +++ b/homeassistant/components/blebox/translations/el.json @@ -1,16 +1,20 @@ { "config": { "abort": { - "address_already_configured": "\u039c\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae BleBox \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 {address}." + "address_already_configured": "\u039c\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae BleBox \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 {address}.", + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1", "unsupported_version": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae BleBox \u03ad\u03c7\u03b5\u03b9 \u03c0\u03b1\u03bb\u03b9\u03cc \u03c5\u03bb\u03b9\u03ba\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03bc\u03b9\u03ba\u03cc. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b1\u03bd\u03b1\u03b2\u03b1\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03c1\u03ce\u03c4\u03b1." }, "flow_title": "{name} ({host})", "step": { "user": { "data": { - "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", + "port": "\u0398\u03cd\u03c1\u03b1" }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf BleBox \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Home Assistant.", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 BleBox" diff --git a/homeassistant/components/blink/translations/el.json b/homeassistant/components/blink/translations/el.json index 430cd76e408d94..690d2bf0539667 100644 --- a/homeassistant/components/blink/translations/el.json +++ b/homeassistant/components/blink/translations/el.json @@ -1,5 +1,14 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_access_token": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "2fa": { "data": { @@ -10,7 +19,8 @@ }, "user": { "data": { - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc Blink" } diff --git a/homeassistant/components/bmw_connected_drive/translations/el.json b/homeassistant/components/bmw_connected_drive/translations/el.json index 8c40d8bf1935ad..bb20b0a61a26b0 100644 --- a/homeassistant/components/bmw_connected_drive/translations/el.json +++ b/homeassistant/components/bmw_connected_drive/translations/el.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "step": { "user": { diff --git a/homeassistant/components/bond/translations/el.json b/homeassistant/components/bond/translations/el.json index 4a6d26cd73d7d6..8dad035dc3f1fd 100644 --- a/homeassistant/components/bond/translations/el.json +++ b/homeassistant/components/bond/translations/el.json @@ -1,15 +1,25 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { - "old_firmware": "\u039c\u03b7 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03bf \u03c0\u03b1\u03bb\u03b9\u03cc \u03c5\u03bb\u03b9\u03ba\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03bc\u03b9\u03ba\u03cc \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Bond - \u03b1\u03bd\u03b1\u03b2\u03b1\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03c1\u03b9\u03bd \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03c4\u03b5" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "old_firmware": "\u039c\u03b7 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03bf \u03c0\u03b1\u03bb\u03b9\u03cc \u03c5\u03bb\u03b9\u03ba\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03bc\u03b9\u03ba\u03cc \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Bond - \u03b1\u03bd\u03b1\u03b2\u03b1\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03c1\u03b9\u03bd \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03c4\u03b5", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "flow_title": "{name} ({host})", "step": { "confirm": { + "data": { + "access_token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name};" }, "user": { "data": { + "access_token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" } } diff --git a/homeassistant/components/bosch_shc/translations/el.json b/homeassistant/components/bosch_shc/translations/el.json index e36f0621cdcc90..f9b5f4ad3d0b2c 100644 --- a/homeassistant/components/bosch_shc/translations/el.json +++ b/homeassistant/components/bosch_shc/translations/el.json @@ -1,8 +1,15 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "pairing_failed": "\u0397 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5. \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03c4\u03bf Bosch Smart Home Controller \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03c3\u03b5 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7\u03c2 (\u03c4\u03bf LED \u03b1\u03bd\u03b1\u03b2\u03bf\u03c3\u03b2\u03ae\u03bd\u03b5\u03b9) \u03ba\u03b1\u03b8\u03ce\u03c2 \u03ba\u03b1\u03b9 \u03cc\u03c4\u03b9 \u03bf \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c9\u03c3\u03c4\u03cc\u03c2.", - "session_error": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03c3\u03c5\u03bd\u03b5\u03b4\u03c1\u03af\u03b1\u03c2: \u039c\u03b7 \u039f\u039a \u03b1\u03c0\u03bf\u03c4\u03ad\u03bb\u03b5\u03c3\u03bc\u03b1." + "session_error": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03c3\u03c5\u03bd\u03b5\u03b4\u03c1\u03af\u03b1\u03c2: \u039c\u03b7 \u039f\u039a \u03b1\u03c0\u03bf\u03c4\u03ad\u03bb\u03b5\u03c3\u03bc\u03b1.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "flow_title": "Bosch SHC: {name}", "step": { @@ -15,7 +22,8 @@ } }, "reauth_confirm": { - "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 bosch_shc \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2" + "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 bosch_shc \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, "user": { "data": { diff --git a/homeassistant/components/braviatv/translations/el.json b/homeassistant/components/braviatv/translations/el.json index 99acd65a994b93..489bf0a7c36d55 100644 --- a/homeassistant/components/braviatv/translations/el.json +++ b/homeassistant/components/braviatv/translations/el.json @@ -1,13 +1,19 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "no_ip_control": "\u039f \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 IP \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf\u03c2 \u03c3\u03c4\u03b7\u03bd \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae \u03c3\u03b1\u03c2 \u03ae \u03b7 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7 \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9." }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_host": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", "unsupported_model": "\u03a4\u03bf \u03bc\u03bf\u03bd\u03c4\u03ad\u03bb\u03bf \u03c4\u03b7\u03c2 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9." }, "step": { "authorize": { + "data": { + "pin": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN" + }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc PIN \u03c0\u03bf\u03c5 \u03b5\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03b7\u03bd \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7 Sony Bravia. \n\n\u0395\u03ac\u03bd \u03bf \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN \u03b4\u03b5\u03bd \u03b5\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03c4\u03b1\u03b9, \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae \u03c4\u03bf\u03c5 Home Assistant \u03c3\u03c4\u03b7\u03bd \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae \u03c3\u03b1\u03c2, \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7: \u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 -> \u0394\u03af\u03ba\u03c4\u03c5\u03bf -> \u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03b1\u03c0\u03bf\u03bc\u03b1\u03ba\u03c1\u03c5\u03c3\u03bc\u03ad\u03bd\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 -> \u0394\u03b9\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae \u03c4\u03b7\u03c2 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b9\u03c3\u03b7\u03c2 \u03b1\u03c0\u03bf\u03bc\u03b1\u03ba\u03c1\u03c5\u03c3\u03bc\u03ad\u03bd\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2.", "title": "\u0395\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7 Sony Bravia TV" }, diff --git a/homeassistant/components/broadlink/translations/el.json b/homeassistant/components/broadlink/translations/el.json index e0dc11ae470d9b..6db366dae32cd7 100644 --- a/homeassistant/components/broadlink/translations/el.json +++ b/homeassistant/components/broadlink/translations/el.json @@ -1,9 +1,17 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "already_in_progress": "\u03a5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03bc\u03b9\u03b1 \u03c1\u03bf\u03ae \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "invalid_host": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", - "not_supported": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9" + "not_supported": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_host": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "flow_title": "{name} ( {model} \u03c3\u03c4\u03bf {host} )", "step": { diff --git a/homeassistant/components/brother/translations/el.json b/homeassistant/components/brother/translations/el.json index 95124984c3d149..f8a27353a6af54 100644 --- a/homeassistant/components/brother/translations/el.json +++ b/homeassistant/components/brother/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "unsupported_model": "\u0391\u03c5\u03c4\u03cc \u03c4\u03bf \u03bc\u03bf\u03bd\u03c4\u03ad\u03bb\u03bf \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9." }, "error": { diff --git a/homeassistant/components/brunt/translations/el.json b/homeassistant/components/brunt/translations/el.json index 26b40b939f6be9..a7a676b4ed9c85 100644 --- a/homeassistant/components/brunt/translations/el.json +++ b/homeassistant/components/brunt/translations/el.json @@ -1,14 +1,21 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "reauth_confirm": { "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, - "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd: {username}" + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd: {username}", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, "user": { "data": { diff --git a/homeassistant/components/bsblan/translations/el.json b/homeassistant/components/bsblan/translations/el.json index 3918983f74630c..b3d9cbb2bfab6a 100644 --- a/homeassistant/components/bsblan/translations/el.json +++ b/homeassistant/components/bsblan/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "error": { @@ -13,6 +14,7 @@ "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "passkey": "\u03a3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae BSB-Lan \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Home Assistant.", diff --git a/homeassistant/components/buienradar/translations/el.json b/homeassistant/components/buienradar/translations/el.json index b96d49b8615e53..387ba0d7460399 100644 --- a/homeassistant/components/buienradar/translations/el.json +++ b/homeassistant/components/buienradar/translations/el.json @@ -1,4 +1,20 @@ { + "config": { + "abort": { + "already_configured": "\u0397 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "already_configured": "\u0397 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "step": { + "user": { + "data": { + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2" + } + } + } + }, "options": { "step": { "init": { diff --git a/homeassistant/components/cast/translations/el.json b/homeassistant/components/cast/translations/el.json index 7a6ecd18b9fd91..b3e45927b9b630 100644 --- a/homeassistant/components/cast/translations/el.json +++ b/homeassistant/components/cast/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, "error": { "invalid_known_hosts": "\u039f\u03b9 \u03b3\u03bd\u03c9\u03c3\u03c4\u03bf\u03af \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03af \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ad\u03c2 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03bc\u03b9\u03b1 \u03bb\u03af\u03c3\u03c4\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03ce\u03bd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ce\u03bd \u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7 \u03bc\u03b5 \u03ba\u03cc\u03bc\u03bc\u03b1." }, @@ -10,6 +13,9 @@ }, "description": "\u0393\u03bd\u03c9\u03c3\u03c4\u03bf\u03af \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03af \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ad\u03c2 - \u039c\u03b9\u03b1 \u03bb\u03af\u03c3\u03c4\u03b1 \u03bc\u03b5 \u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03cc \u03bc\u03b5 \u03ba\u03cc\u03bc\u03bc\u03b1 \u03c4\u03c9\u03bd \u03bf\u03bd\u03bf\u03bc\u03ac\u03c4\u03c9\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03ce\u03bd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ce\u03bd \u03ae \u03c4\u03c9\u03bd \u03b4\u03b9\u03b5\u03c5\u03b8\u03cd\u03bd\u03c3\u03b5\u03c9\u03bd IP \u03c4\u03c9\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd cast, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b1\u03bd \u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7 mDNS \u03b4\u03b5\u03bd \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03b5\u03af.", "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Google Cast" + }, + "confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;" } } }, diff --git a/homeassistant/components/cert_expiry/translations/el.json b/homeassistant/components/cert_expiry/translations/el.json index a130ea4b90b2a6..c731fb0834adab 100644 --- a/homeassistant/components/cert_expiry/translations/el.json +++ b/homeassistant/components/cert_expiry/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", "import_failed": "\u0397 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03b1\u03c0\u03cc \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5" }, "error": { diff --git a/homeassistant/components/climacell/translations/el.json b/homeassistant/components/climacell/translations/el.json index 7779ac3c5a5fd9..1f9956a75fa3ae 100644 --- a/homeassistant/components/climacell/translations/el.json +++ b/homeassistant/components/climacell/translations/el.json @@ -2,6 +2,7 @@ "config": { "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_api_key": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API", "rate_limited": "\u0391\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7 \u03c3\u03c4\u03b9\u03b3\u03bc\u03ae \u03b7 \u03c4\u03b9\u03bc\u03ae \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1.", "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, diff --git a/homeassistant/components/cloudflare/translations/el.json b/homeassistant/components/cloudflare/translations/el.json index b8f144831749f1..44159809e3c3ff 100644 --- a/homeassistant/components/cloudflare/translations/el.json +++ b/homeassistant/components/cloudflare/translations/el.json @@ -1,12 +1,20 @@ { "config": { + "abort": { + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "invalid_zone": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b6\u03ce\u03bd\u03b7" }, "flow_title": "{name}", "step": { "reauth_confirm": { "data": { + "api_token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API", "description": "\u0395\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Cloudflare." } }, @@ -17,6 +25,9 @@ "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ad\u03c2 \u03c0\u03c1\u03bf\u03c2 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7" }, "user": { + "data": { + "api_token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API" + }, "description": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03ad\u03bd\u03b1 Token API \u03c0\u03bf\u03c5 \u03ad\u03c7\u03b5\u03b9 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03b7\u03b8\u03b5\u03af \u03bc\u03b5 \u03b4\u03b9\u03ba\u03b1\u03b9\u03ce\u03bc\u03b1\u03c4\u03b1 Zone:Zone:Read \u03ba\u03b1\u03b9 Zone:DNS:Edit \u03b3\u03b9\u03b1 \u03cc\u03bb\u03b5\u03c2 \u03c4\u03b9\u03c2 \u03b6\u03ce\u03bd\u03b5\u03c2 \u03c4\u03bf\u03c5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03c3\u03b1\u03c2.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf Cloudflare" }, diff --git a/homeassistant/components/co2signal/translations/el.json b/homeassistant/components/co2signal/translations/el.json index 7defcb9708ea38..cab4466b293343 100644 --- a/homeassistant/components/co2signal/translations/el.json +++ b/homeassistant/components/co2signal/translations/el.json @@ -1,12 +1,22 @@ { "config": { "abort": { - "api_ratelimit": "\u03a5\u03c0\u03ad\u03c1\u03b2\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03bf\u03c1\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 API" + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "api_ratelimit": "\u03a5\u03c0\u03ad\u03c1\u03b2\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03bf\u03c1\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 API", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "error": { - "api_ratelimit": "\u03a5\u03c0\u03ad\u03c1\u03b2\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03bf\u03c1\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 API" + "api_ratelimit": "\u03a5\u03c0\u03ad\u03c1\u03b2\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03bf\u03c1\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 API", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { + "coordinates": { + "data": { + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2" + } + }, "country": { "data": { "country_code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c7\u03ce\u03c1\u03b1\u03c2" @@ -14,6 +24,7 @@ }, "user": { "data": { + "api_key": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "location": "\u039b\u03ae\u03c8\u03b7 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd \u03b3\u03b9\u03b1" }, "description": "\u0395\u03c0\u03b9\u03c3\u03ba\u03b5\u03c6\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://co2signal.com/ \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b6\u03b7\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc." diff --git a/homeassistant/components/coinbase/translations/el.json b/homeassistant/components/coinbase/translations/el.json index 13a43362f4af88..196aaad8643705 100644 --- a/homeassistant/components/coinbase/translations/el.json +++ b/homeassistant/components/coinbase/translations/el.json @@ -1,12 +1,19 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "invalid_auth_key": "\u03a4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 API \u03b1\u03c0\u03bf\u03c1\u03c1\u03af\u03c6\u03b8\u03b7\u03ba\u03b1\u03bd \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Coinbase \u03bb\u03cc\u03b3\u03c9 \u03bc\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c5 \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd API.", - "invalid_auth_secret": "\u03a4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 API \u03b1\u03c0\u03bf\u03c1\u03c1\u03af\u03c6\u03b8\u03b7\u03ba\u03b1\u03bd \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Coinbase \u03bb\u03cc\u03b3\u03c9 \u03bc\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c5 \u03bc\u03c5\u03c3\u03c4\u03b9\u03ba\u03bf\u03cd API." + "invalid_auth_secret": "\u03a4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 API \u03b1\u03c0\u03bf\u03c1\u03c1\u03af\u03c6\u03b8\u03b7\u03ba\u03b1\u03bd \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Coinbase \u03bb\u03cc\u03b3\u03c9 \u03bc\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c5 \u03bc\u03c5\u03c3\u03c4\u03b9\u03ba\u03bf\u03cd API.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", "api_token": "\u039c\u03c5\u03c3\u03c4\u03b9\u03ba\u03cc API", "currencies": "\u039d\u03bf\u03bc\u03af\u03c3\u03bc\u03b1\u03c4\u03b1 \u03c5\u03c0\u03bf\u03bb\u03bf\u03af\u03c0\u03bf\u03c5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd", "exchange_rates": "\u03a3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03bc\u03b1\u03c4\u03b9\u03ba\u03ad\u03c2 \u03b9\u03c3\u03bf\u03c4\u03b9\u03bc\u03af\u03b5\u03c2" @@ -21,7 +28,8 @@ "currency_unavailable": "\u0388\u03bd\u03b1 \u03ae \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b1 \u03b6\u03b7\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b1 \u03c5\u03c0\u03cc\u03bb\u03bf\u03b9\u03c0\u03b1 \u03bd\u03bf\u03bc\u03b9\u03c3\u03bc\u03ac\u03c4\u03c9\u03bd \u03b4\u03b5\u03bd \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf API \u03c4\u03b7\u03c2 Coinbase.", "currency_unavaliable": "\u0388\u03bd\u03b1 \u03ae \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b1 \u03b6\u03b7\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b1 \u03c5\u03c0\u03cc\u03bb\u03bf\u03b9\u03c0\u03b1 \u03bd\u03bf\u03bc\u03b9\u03c3\u03bc\u03ac\u03c4\u03c9\u03bd \u03b4\u03b5\u03bd \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf Coinbase API \u03c3\u03b1\u03c2.", "exchange_rate_unavailable": "\u039c\u03af\u03b1 \u03ae \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b9\u03c2 \u03b6\u03b7\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b5\u03c2 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03bc\u03b1\u03c4\u03b9\u03ba\u03ad\u03c2 \u03b9\u03c3\u03bf\u03c4\u03b9\u03bc\u03af\u03b5\u03c2 \u03b4\u03b5\u03bd \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Coinbase.", - "exchange_rate_unavaliable": "\u039c\u03af\u03b1 \u03ae \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b9\u03c2 \u03b6\u03b7\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b5\u03c2 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03bc\u03b1\u03c4\u03b9\u03ba\u03ad\u03c2 \u03b9\u03c3\u03bf\u03c4\u03b9\u03bc\u03af\u03b5\u03c2 \u03b4\u03b5\u03bd \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Coinbase." + "exchange_rate_unavaliable": "\u039c\u03af\u03b1 \u03ae \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b9\u03c2 \u03b6\u03b7\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b5\u03c2 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03bc\u03b1\u03c4\u03b9\u03ba\u03ad\u03c2 \u03b9\u03c3\u03bf\u03c4\u03b9\u03bc\u03af\u03b5\u03c2 \u03b4\u03b5\u03bd \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Coinbase.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "init": { diff --git a/homeassistant/components/control4/translations/el.json b/homeassistant/components/control4/translations/el.json index 0ffffeb9f04a8a..0c11a8b2d5686c 100644 --- a/homeassistant/components/control4/translations/el.json +++ b/homeassistant/components/control4/translations/el.json @@ -1,10 +1,19 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c4\u03bf\u03c5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03c3\u03b1\u03c2 Control4 \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03bf\u03c5 \u03c4\u03bf\u03c0\u03b9\u03ba\u03bf\u03cd \u03c3\u03b1\u03c2 \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ae." } diff --git a/homeassistant/components/coronavirus/translations/el.json b/homeassistant/components/coronavirus/translations/el.json index f7ef9b4844957d..eacc8de5fd7f06 100644 --- a/homeassistant/components/coronavirus/translations/el.json +++ b/homeassistant/components/coronavirus/translations/el.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/cover/translations/el.json b/homeassistant/components/cover/translations/el.json index 1dcade479c6e34..e02c8e3a97b5eb 100644 --- a/homeassistant/components/cover/translations/el.json +++ b/homeassistant/components/cover/translations/el.json @@ -13,7 +13,9 @@ "is_closed": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03ba\u03bb\u03b5\u03b9\u03c3\u03c4\u03cc", "is_closing": "{entity_name} \u03ba\u03bb\u03b5\u03af\u03bd\u03b5\u03b9", "is_open": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03bd\u03bf\u03b9\u03c7\u03c4\u03cc", - "is_opening": "{entity_name} \u03b1\u03bd\u03bf\u03af\u03b3\u03b5\u03b9" + "is_opening": "{entity_name} \u03b1\u03bd\u03bf\u03af\u03b3\u03b5\u03b9", + "is_position": "\u0397 \u03c4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03b8\u03ad\u03c3\u03b7 {entity_name} \u03b5\u03af\u03bd\u03b1\u03b9", + "is_tilt_position": "\u0397 \u03c4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03b8\u03ad\u03c3\u03b7 \u03ba\u03bb\u03af\u03c3\u03b7\u03c2 {entity_name} \u03b5\u03af\u03bd\u03b1\u03b9" }, "trigger_type": { "closed": "{entity_name} \u03ad\u03ba\u03bb\u03b5\u03b9\u03c3\u03b5", diff --git a/homeassistant/components/cpuspeed/translations/el.json b/homeassistant/components/cpuspeed/translations/el.json index ad0e081b16fb09..4c3541e2640bf0 100644 --- a/homeassistant/components/cpuspeed/translations/el.json +++ b/homeassistant/components/cpuspeed/translations/el.json @@ -1,10 +1,13 @@ { "config": { "abort": { + "alread_configured": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", + "already_configured": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", "not_compatible": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03bb\u03ae\u03c8\u03b7 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03b9\u03ce\u03bd CPU, \u03b1\u03c5\u03c4\u03ae \u03b7 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bc\u03b2\u03b1\u03c4\u03ae \u03bc\u03b5 \u03c4\u03bf \u03c3\u03cd\u03c3\u03c4\u03b7\u03bc\u03ac \u03c3\u03b1\u03c2" }, "step": { "user": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;", "title": "\u03a4\u03b1\u03c7\u03cd\u03c4\u03b7\u03c4\u03b1 CPU" } } diff --git a/homeassistant/components/crownstone/translations/el.json b/homeassistant/components/crownstone/translations/el.json index 2deebf72442bbd..81cfbd9ad390de 100644 --- a/homeassistant/components/crownstone/translations/el.json +++ b/homeassistant/components/crownstone/translations/el.json @@ -1,18 +1,27 @@ { "config": { "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "usb_setup_complete": "\u039f\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03b7 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 Crownstone USB.", "usb_setup_unsuccessful": "\u0397 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 Crownstone USB \u03ae\u03c4\u03b1\u03bd \u03b1\u03bd\u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2." }, "error": { - "account_not_verified": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03c5\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03bc\u03ad\u03c3\u03c9 \u03c4\u03bf\u03c5 email \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Crownstone." + "account_not_verified": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03c5\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03bc\u03ad\u03c3\u03c9 \u03c4\u03bf\u03c5 email \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Crownstone.", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "usb_config": { + "data": { + "usb_path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 USB" + }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1 \u03c4\u03bf\u03c5 dongle USB \u03c4\u03bf\u03c5 Crownstone \u03ae \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \"Don't use USB\" (\u039c\u03b7 \u03c7\u03c1\u03ae\u03c3\u03b7 USB), \u03b1\u03bd \u03b4\u03b5\u03bd \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03b3\u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 dongle USB.\n\n\u0391\u03bd\u03b1\u03b6\u03b7\u03c4\u03ae\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bc\u03b5 VID 10C4 \u03ba\u03b1\u03b9 PID EA60.", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Crownstone USB dongle" }, "usb_manual_config": { + "data": { + "usb_manual_path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 USB" + }, "description": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae\u03c2 \u03b5\u03bd\u03cc\u03c2 dongle USB Crownstone.", "title": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae\u03c2 USB dongle Crownstone" }, @@ -41,18 +50,30 @@ } }, "usb_config": { + "data": { + "usb_path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 USB" + }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1 \u03c4\u03bf\u03c5 Crownstone USB dongle.\n\n\u0391\u03bd\u03b1\u03b6\u03b7\u03c4\u03ae\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bc\u03b5 VID 10C4 \u03ba\u03b1\u03b9 PID EA60.", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Crownstone USB dongle" }, "usb_config_option": { + "data": { + "usb_path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 USB" + }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1 \u03c4\u03bf\u03c5 Crownstone USB dongle.\n\n\u0391\u03bd\u03b1\u03b6\u03b7\u03c4\u03ae\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bc\u03b5 VID 10C4 \u03ba\u03b1\u03b9 PID EA60.", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Crownstone USB dongle" }, "usb_manual_config": { + "data": { + "usb_manual_path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 USB" + }, "description": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae\u03c2 \u03b5\u03bd\u03cc\u03c2 dongle USB Crownstone.", "title": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae\u03c2 USB dongle Crownstone" }, "usb_manual_config_option": { + "data": { + "usb_manual_path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 USB" + }, "description": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae\u03c2 \u03b5\u03bd\u03cc\u03c2 dongle USB Crownstone.", "title": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae\u03c2 USB dongle Crownstone" }, diff --git a/homeassistant/components/daikin/translations/el.json b/homeassistant/components/daikin/translations/el.json index c4113f251c1432..cd7b4866aaebcb 100644 --- a/homeassistant/components/daikin/translations/el.json +++ b/homeassistant/components/daikin/translations/el.json @@ -1,11 +1,19 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "error": { - "api_password": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03b5\u03af\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2." + "api_password": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03b5\u03af\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2.", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, diff --git a/homeassistant/components/demo/translations/el.json b/homeassistant/components/demo/translations/el.json index b1bef5448b9481..34afbe9df01a5e 100644 --- a/homeassistant/components/demo/translations/el.json +++ b/homeassistant/components/demo/translations/el.json @@ -4,6 +4,7 @@ "options_1": { "data": { "bool": "\u03a0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc boolean", + "constant": "\u03a3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae", "int": "\u0391\u03c1\u03b9\u03b8\u03bc\u03b7\u03c4\u03b9\u03ba\u03ae \u03b5\u03af\u03c3\u03bf\u03b4\u03bf\u03c2" } }, diff --git a/homeassistant/components/denonavr/translations/el.json b/homeassistant/components/denonavr/translations/el.json index 8bfb96a65bbc21..180cacc245955c 100644 --- a/homeassistant/components/denonavr/translations/el.json +++ b/homeassistant/components/denonavr/translations/el.json @@ -1,6 +1,8 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", "cannot_connect": "\u0397 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac, \u03b7 \u03b1\u03c0\u03bf\u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c4\u03c9\u03bd \u03ba\u03b1\u03bb\u03c9\u03b4\u03af\u03c9\u03bd \u03c1\u03b5\u03cd\u03bc\u03b1\u03c4\u03bf\u03c2 \u03ba\u03b1\u03b9 ethernet \u03ba\u03b1\u03b9 \u03b7 \u03b5\u03c0\u03b1\u03bd\u03b1\u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03ae \u03c4\u03bf\u03c5\u03c2 \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b2\u03bf\u03b7\u03b8\u03ae\u03c3\u03b5\u03b9", "not_denonavr_manufacturer": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03ad\u03ba\u03c4\u03b7\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 Denon AVR, \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b5 \u03cc\u03c4\u03b9 \u03bf \u03ba\u03b1\u03c4\u03b1\u03c3\u03ba\u03b5\u03c5\u03b1\u03c3\u03c4\u03ae\u03c2 \u03b4\u03b5\u03bd \u03c4\u03b1\u03b9\u03c1\u03b9\u03ac\u03b6\u03b5\u03b9", "not_denonavr_missing": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03ad\u03ba\u03c4\u03b7\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 Denon AVR, \u03bf\u03b9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03bc\u03bf\u03cd \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03bb\u03ae\u03c1\u03b5\u03b9\u03c2" diff --git a/homeassistant/components/devolo_home_control/translations/el.json b/homeassistant/components/devolo_home_control/translations/el.json index 768d7dce0180fd..b79dfee9512997 100644 --- a/homeassistant/components/devolo_home_control/translations/el.json +++ b/homeassistant/components/devolo_home_control/translations/el.json @@ -1,6 +1,11 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "reauth_failed": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03af\u03b4\u03b9\u03bf \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 mydevolo \u03cc\u03c0\u03c9\u03c2 \u03ba\u03b1\u03b9 \u03c0\u03c1\u03b9\u03bd." }, "step": { diff --git a/homeassistant/components/devolo_home_network/translations/el.json b/homeassistant/components/devolo_home_network/translations/el.json index 628373910b5e43..45a54b59b00dd6 100644 --- a/homeassistant/components/devolo_home_network/translations/el.json +++ b/homeassistant/components/devolo_home_network/translations/el.json @@ -1,17 +1,20 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "home_control": "\u0397 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03ae \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 Home \u03c4\u03b7\u03c2 devolo \u03b4\u03b5\u03bd \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03b5\u03af \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7." }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "flow_title": "{product} ({name})", "step": { "user": { "data": { "ip_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" - } + }, + "description": "\u03c8" }, "zeroconf_confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bf\u03b9\u03ba\u03b9\u03b1\u03ba\u03bf\u03cd \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 devolo \u03bc\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae `{host_name}` \u03c3\u03c4\u03bf Home Assistant;", diff --git a/homeassistant/components/dexcom/translations/el.json b/homeassistant/components/dexcom/translations/el.json index 6097753fb1febc..29ca3b114caf48 100644 --- a/homeassistant/components/dexcom/translations/el.json +++ b/homeassistant/components/dexcom/translations/el.json @@ -1,14 +1,19 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", - "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03c5\u03b8\u03b5\u03bd\u03c4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7" + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03c5\u03b8\u03b5\u03bd\u03c4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "server": "\u0394\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2" + "server": "\u0394\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 Dexcom Share", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 Dexcom" diff --git a/homeassistant/components/dialogflow/translations/el.json b/homeassistant/components/dialogflow/translations/el.json index c0695ac82b082e..b2d36ed6235fa3 100644 --- a/homeassistant/components/dialogflow/translations/el.json +++ b/homeassistant/components/dialogflow/translations/el.json @@ -2,7 +2,8 @@ "config": { "abort": { "cloud_not_connected": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf Home Assistant Cloud.", - "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", + "webhook_not_internet_accessible": "\u0397 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1 \u03c4\u03bf\u03c5 Home Assistant \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c3\u03b2\u03ac\u03c3\u03b9\u03bc\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf \u03b4\u03b9\u03b1\u03b4\u03af\u03ba\u03c4\u03c5\u03bf \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03b5\u03b9 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03b1 webhook." }, "create_entry": { "default": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c4\u03b5\u03af\u03bb\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03b1 \u03c3\u03c4\u03bf\u03bd Home Assistant, \u03b8\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd [\u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 webhook \u03c4\u03bf\u03c5 Dialogflow]( {dialogflow_url} ). \n\n \u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2: \n\n - URL: ` {webhook_url} `\n - \u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2: POST\n - \u03a4\u03cd\u03c0\u03bf\u03c2 \u03c0\u03b5\u03c1\u03b9\u03b5\u03c7\u03bf\u03bc\u03ad\u03bd\u03bf\u03c5: \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae/json \n\n \u0394\u03b5\u03af\u03c4\u03b5 [\u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]( {docs_url} ) \u03b3\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2." diff --git a/homeassistant/components/directv/translations/el.json b/homeassistant/components/directv/translations/el.json index 7bdf0e12aa7c67..49c7f5aa0bc526 100644 --- a/homeassistant/components/directv/translations/el.json +++ b/homeassistant/components/directv/translations/el.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "flow_title": "{name}", "step": { "ssdp_confirm": { diff --git a/homeassistant/components/dlna_dmr/translations/el.json b/homeassistant/components/dlna_dmr/translations/el.json index 72b454e4e0bd52..91a1d353bccf29 100644 --- a/homeassistant/components/dlna_dmr/translations/el.json +++ b/homeassistant/components/dlna_dmr/translations/el.json @@ -1,7 +1,9 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "alternative_integration": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03ba\u03b1\u03bb\u03cd\u03c4\u03b5\u03c1\u03b1 \u03b1\u03c0\u03cc \u03ac\u03bb\u03bb\u03b7 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "could_not_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03bc\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae DLNA", "discovery_error": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7\u03c2 \u03bc\u03b9\u03b1\u03c2 \u03b1\u03bd\u03c4\u03af\u03c3\u03c4\u03bf\u03b9\u03c7\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 DLNA", "incomplete_config": "\u0391\u03c0\u03cc \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03bb\u03b5\u03af\u03c0\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7 \u03bc\u03b5\u03c4\u03b1\u03b2\u03bb\u03b7\u03c4\u03ae", @@ -9,21 +11,29 @@ "not_dmr": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03bd\u03b1\u03c2 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03bf\u03c2 \u03c8\u03b7\u03c6\u03b9\u03b1\u03ba\u03cc\u03c2 \u03b1\u03bd\u03b1\u03bc\u03b5\u03c4\u03b1\u03b4\u03cc\u03c4\u03b7\u03c2 \u03c0\u03bf\u03bb\u03c5\u03bc\u03ad\u03c3\u03c9\u03bd" }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "could_not_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03bc\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae DLNA", "not_dmr": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03bd\u03b1\u03c2 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03bf\u03c2 \u03c8\u03b7\u03c6\u03b9\u03b1\u03ba\u03cc\u03c2 \u03b1\u03bd\u03b1\u03bc\u03b5\u03c4\u03b1\u03b4\u03cc\u03c4\u03b7\u03c2 \u03c0\u03bf\u03bb\u03c5\u03bc\u03ad\u03c3\u03c9\u03bd" }, "flow_title": "{name}", "step": { + "confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;" + }, "import_turn_on": { "description": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ba\u03b1\u03b9 \u03ba\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03b7\u03bd \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03a5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03bc\u03b5\u03c4\u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7" }, "manual": { + "data": { + "url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL" + }, "description": "URL \u03c3\u03b5 \u03ad\u03bd\u03b1 \u03b1\u03c1\u03c7\u03b5\u03af\u03bf XML \u03c0\u03b5\u03c1\u03b9\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", "title": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 DLNA DMR" }, "user": { "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL" }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b3\u03b9\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03ae \u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03b5\u03bd\u03ae \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 DLNA DMR" diff --git a/homeassistant/components/dlna_dms/translations/pt-BR.json b/homeassistant/components/dlna_dms/translations/pt-BR.json new file mode 100644 index 00000000000000..125a31fe9b5fbd --- /dev/null +++ b/homeassistant/components/dlna_dms/translations/pt-BR.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "A configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "bad_ssdp": "Falta um valor obrigat\u00f3rio nos dados SSDP", + "no_devices_found": "Nenhum dispositivo encontrado na rede", + "not_dms": "O dispositivo n\u00e3o \u00e9 um servidor de m\u00eddia compat\u00edvel" + }, + "flow_title": "{name}", + "step": { + "confirm": { + "description": "Deseja iniciar a configura\u00e7\u00e3o?" + }, + "user": { + "data": { + "host": "Host" + }, + "description": "Escolha um dispositivo para configurar", + "title": "Dispositivos DLNA DMA descobertos" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/doorbird/translations/el.json b/homeassistant/components/doorbird/translations/el.json index 64aae4a89f203c..ffbf523e7d8c85 100644 --- a/homeassistant/components/doorbird/translations/el.json +++ b/homeassistant/components/doorbird/translations/el.json @@ -1,10 +1,12 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "link_local_address": "\u039f\u03b9 \u03c4\u03bf\u03c0\u03b9\u03ba\u03ad\u03c2 \u03b4\u03b9\u03b5\u03c5\u03b8\u03cd\u03bd\u03c3\u03b5\u03b9\u03c2 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03bc\u03bf\u03c5 \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03bf\u03bd\u03c4\u03b1\u03b9", "not_doorbird_device": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 DoorBird" }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", "unknown": "\u039c\u03b7 \u03b1\u03bd\u03b1\u03bc\u03b5\u03bd\u03cc\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, @@ -14,7 +16,8 @@ "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf DoorBird" } diff --git a/homeassistant/components/dsmr/translations/el.json b/homeassistant/components/dsmr/translations/el.json index 212db9b3f2f8fc..77f2ad8910d9b6 100644 --- a/homeassistant/components/dsmr/translations/el.json +++ b/homeassistant/components/dsmr/translations/el.json @@ -1,10 +1,14 @@ { "config": { "abort": { - "cannot_communicate": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b5\u03c0\u03b9\u03ba\u03bf\u03b9\u03bd\u03c9\u03bd\u03af\u03b1" + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_communicate": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b5\u03c0\u03b9\u03ba\u03bf\u03b9\u03bd\u03c9\u03bd\u03af\u03b1", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "error": { - "cannot_communicate": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b5\u03c0\u03b9\u03ba\u03bf\u03b9\u03bd\u03c9\u03bd\u03af\u03b1" + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_communicate": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b5\u03c0\u03b9\u03ba\u03bf\u03b9\u03bd\u03c9\u03bd\u03af\u03b1", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "step": { "setup_network": { @@ -23,6 +27,9 @@ "title": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" }, "setup_serial_manual_path": { + "data": { + "port": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 USB" + }, "title": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae" }, "user": { diff --git a/homeassistant/components/dunehd/translations/el.json b/homeassistant/components/dunehd/translations/el.json index 92b697b7f0064d..96ad16ac67f9ad 100644 --- a/homeassistant/components/dunehd/translations/el.json +++ b/homeassistant/components/dunehd/translations/el.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_host": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/eafm/translations/el.json b/homeassistant/components/eafm/translations/el.json index 9745cd934edefd..1854a65a7081e5 100644 --- a/homeassistant/components/eafm/translations/el.json +++ b/homeassistant/components/eafm/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "no_stations": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03af \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7\u03c2 \u03c0\u03bb\u03b7\u03bc\u03bc\u03c5\u03c1\u03ce\u03bd." }, "step": { diff --git a/homeassistant/components/ecobee/translations/el.json b/homeassistant/components/ecobee/translations/el.json index 2596a9c81a16ff..a28aef55374af7 100644 --- a/homeassistant/components/ecobee/translations/el.json +++ b/homeassistant/components/ecobee/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, "error": { "pin_request_failed": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03b1\u03af\u03c4\u03b7\u03c3\u03b7\u03c2 PIN \u03b1\u03c0\u03cc \u03c4\u03bf ecobee- \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c9\u03c3\u03c4\u03cc.", "token_request_failed": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03b1\u03af\u03c4\u03b7\u03c3\u03b7\u03c2 tokens \u03b1\u03c0\u03cc \u03c4\u03bf ecobee, \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac." @@ -10,6 +13,9 @@ "title": "\u0395\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u03c3\u03c4\u03bf ecobee.com" }, "user": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API" + }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03c0\u03bf\u03c5 \u03ad\u03c7\u03b5\u03c4\u03b5 \u03bb\u03ac\u03b2\u03b5\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd ecobee.com.", "title": "\u03ba\u03bb\u03b5\u03b9\u03b4\u03af API ecobee" } diff --git a/homeassistant/components/econet/translations/el.json b/homeassistant/components/econet/translations/el.json index 402d8354fdbce6..b68034484d35cb 100644 --- a/homeassistant/components/econet/translations/el.json +++ b/homeassistant/components/econet/translations/el.json @@ -1,10 +1,13 @@ { "config": { "abort": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "step": { "user": { diff --git a/homeassistant/components/efergy/translations/el.json b/homeassistant/components/efergy/translations/el.json index 896c5b50791049..f1206afd71bafd 100644 --- a/homeassistant/components/efergy/translations/el.json +++ b/homeassistant/components/efergy/translations/el.json @@ -1,7 +1,19 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API" + }, "title": "Efergy" } } diff --git a/homeassistant/components/elgato/translations/el.json b/homeassistant/components/elgato/translations/el.json index 57300096d97c05..5f6b487c58d505 100644 --- a/homeassistant/components/elgato/translations/el.json +++ b/homeassistant/components/elgato/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "error": { @@ -10,6 +11,7 @@ "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "port": "\u0398\u03cd\u03c1\u03b1" }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf Elgato Light \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Home Assistant." diff --git a/homeassistant/components/elkm1/translations/el.json b/homeassistant/components/elkm1/translations/el.json index 645c2a57097ef5..9e7fe27ce72609 100644 --- a/homeassistant/components/elkm1/translations/el.json +++ b/homeassistant/components/elkm1/translations/el.json @@ -3,16 +3,25 @@ "abort": { "address_already_configured": "\u0388\u03bd\u03b1 ElkM1 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "already_configured": "\u0388\u03bd\u03b1 ElkM1 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "flow_title": "{mac_address} ({host})", "step": { "discovered_connection": { "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "protocol": "\u03a0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf", + "temperature_unit": "\u0397 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03c4\u03bf ElkM1.", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, - "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03c3\u03cd\u03c3\u03c4\u03b7\u03bc\u03b1 \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5: {mac_address} ({host})" + "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03c3\u03cd\u03c3\u03c4\u03b7\u03bc\u03b1 \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5: {mac_address} ({host})", + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf Elk-M1 Control" }, "manual_connection": { "data": { @@ -20,10 +29,11 @@ "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "prefix": "\u0388\u03bd\u03b1 \u03bc\u03bf\u03bd\u03b1\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b1\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03bc\u03cc\u03bd\u03bf \u03ad\u03bd\u03b1 ElkM1).", "protocol": "\u03a0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf", - "temperature_unit": "\u0397 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03c4\u03bf ElkM1.", + "temperature_unit": "\u0397 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03c4\u03bf ElkM1.", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, - "description": "\u0397 \u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae \u00abaddress[:port]\u00bb \u03b3\u03b9\u03b1 \u00absecure\u00bb \u03ba\u03b1\u03b9 \u00abnon-secure\u00bb. \u03a0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3\u03bc\u03b1: '192.168.1.1'. \u0397 \u03b8\u03cd\u03c1\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ae \u03ba\u03b1\u03b9 \u03ad\u03c7\u03b5\u03b9 \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03c9\u03c2 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c3\u03b5 2101 \u03b3\u03b9\u03b1 \"non-secure\" \u03ba\u03b1\u03b9 2601 \u03b3\u03b9\u03b1 \"secure\". \u0393\u03b9\u03b1 \u03c4\u03bf \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc \u03c0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf, \u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae 'tty[:baud]'. \u03a0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3\u03bc\u03b1: '/dev/ttyS1'. \u03a4\u03bf baud \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03ba\u03b1\u03b9 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03b5\u03af\u03bd\u03b1\u03b9 115200." + "description": "\u0397 \u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae \u00abaddress[:port]\u00bb \u03b3\u03b9\u03b1 \u00absecure\u00bb \u03ba\u03b1\u03b9 \u00abnon-secure\u00bb. \u03a0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3\u03bc\u03b1: '192.168.1.1'. \u0397 \u03b8\u03cd\u03c1\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ae \u03ba\u03b1\u03b9 \u03ad\u03c7\u03b5\u03b9 \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03c9\u03c2 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c3\u03b5 2101 \u03b3\u03b9\u03b1 \"non-secure\" \u03ba\u03b1\u03b9 2601 \u03b3\u03b9\u03b1 \"secure\". \u0393\u03b9\u03b1 \u03c4\u03bf \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc \u03c0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf, \u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae 'tty[:baud]'. \u03a0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3\u03bc\u03b1: '/dev/ttyS1'. \u03a4\u03bf baud \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03ba\u03b1\u03b9 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03b5\u03af\u03bd\u03b1\u03b9 115200.", + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf Elk-M1 Control" }, "user": { "data": { @@ -32,7 +42,8 @@ "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "prefix": "\u0388\u03bd\u03b1 \u03bc\u03bf\u03bd\u03b1\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b1\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03bc\u03cc\u03bd\u03bf \u03ad\u03bd\u03b1 ElkM1).", "protocol": "\u03a0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf", - "temperature_unit": "\u0397 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03c4\u03bf ElkM1." + "temperature_unit": "\u0397 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03c4\u03bf ElkM1.", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0397 \u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae 'address[:port]' \u03b3\u03b9\u03b1 'secure' \u03ba\u03b1\u03b9 'non-secure'. \u03a0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3\u03bc\u03b1: '192.168.1.1'. \u0397 \u03b8\u03cd\u03c1\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ae \u03ba\u03b1\u03b9 \u03b7 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03c4\u03b9\u03bc\u03ae \u03b5\u03af\u03bd\u03b1\u03b9 2101 \u03b3\u03b9\u03b1 '\u03bc\u03b7 \u03b1\u03c3\u03c6\u03b1\u03bb\u03ae' \u03ba\u03b1\u03b9 2601 \u03b3\u03b9\u03b1 '\u03b1\u03c3\u03c6\u03b1\u03bb\u03ae'. \u0393\u03b9\u03b1 \u03c4\u03bf \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc \u03c0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf, \u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae 'tty[:baud]'. \u03a0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3\u03bc\u03b1: '/dev/ttyS1'. \u03a4\u03bf baud \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03ba\u03b1\u03b9 \u03b7 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03b5\u03af\u03bd\u03b1\u03b9 115200.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf Elk-M1 Control" diff --git a/homeassistant/components/elmax/translations/el.json b/homeassistant/components/elmax/translations/el.json index a32716878daf03..916ab58593507f 100644 --- a/homeassistant/components/elmax/translations/el.json +++ b/homeassistant/components/elmax/translations/el.json @@ -1,10 +1,15 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { "bad_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "invalid_pin": "\u03a4\u03bf \u03c0\u03b1\u03c1\u03b5\u03c7\u03cc\u03bc\u03b5\u03bd\u03bf pin \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf", "network_error": "\u03a0\u03b1\u03c1\u03bf\u03c5\u03c3\u03b9\u03ac\u03c3\u03c4\u03b7\u03ba\u03b5 \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5", "no_panel_online": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03b7\u03bb\u03b5\u03ba\u03c4\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc\u03c2 \u03c0\u03af\u03bd\u03b1\u03ba\u03b1\u03c2 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 Elmax.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1", "unknown_error": "\u03a0\u03b1\u03c1\u03bf\u03c5\u03c3\u03b9\u03ac\u03c3\u03c4\u03b7\u03ba\u03b5 \u03bc\u03b7 \u03b1\u03bd\u03b1\u03bc\u03b5\u03bd\u03cc\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { diff --git a/homeassistant/components/emonitor/translations/el.json b/homeassistant/components/emonitor/translations/el.json index 64f4b88cecdb54..7a5ed2b14821a4 100644 --- a/homeassistant/components/emonitor/translations/el.json +++ b/homeassistant/components/emonitor/translations/el.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "flow_title": "{name}", "step": { diff --git a/homeassistant/components/emulated_roku/translations/el.json b/homeassistant/components/emulated_roku/translations/el.json index a8d3fa8e922f61..7815ff977ef3b4 100644 --- a/homeassistant/components/emulated_roku/translations/el.json +++ b/homeassistant/components/emulated_roku/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "step": { "user": { "data": { @@ -7,6 +10,7 @@ "advertise_port": "\u0398\u03cd\u03c1\u03b1 \u03b1\u03ba\u03c1\u03cc\u03b1\u03c3\u03b7\u03c2", "host_ip": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae", "listen_port": "\u0391\u03ba\u03c1\u03cc\u03b1\u03c3\u03b7 \u03b8\u03cd\u03c1\u03b1\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "upnp_bind_multicast": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ae\u03c2 \u03b5\u03ba\u03c0\u03bf\u03bc\u03c0\u03ae\u03c2 (\u03a3\u03c9\u03c3\u03c4\u03cc/\u039b\u03ac\u03b8\u03bf\u03c2)" }, "title": "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae" diff --git a/homeassistant/components/enocean/translations/el.json b/homeassistant/components/enocean/translations/el.json index dfbcfc4711f7cd..1c1a8d1e2600bf 100644 --- a/homeassistant/components/enocean/translations/el.json +++ b/homeassistant/components/enocean/translations/el.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "invalid_dongle_path": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae dongle" + "invalid_dongle_path": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae dongle", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, "error": { "invalid_dongle_path": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf dongle \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae" diff --git a/homeassistant/components/enphase_envoy/translations/el.json b/homeassistant/components/enphase_envoy/translations/el.json index caa16f5f8a5e62..e2adc95e3aec4e 100644 --- a/homeassistant/components/enphase_envoy/translations/el.json +++ b/homeassistant/components/enphase_envoy/translations/el.json @@ -1,5 +1,14 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "flow_title": "{serial} ({host})", "step": { "user": { diff --git a/homeassistant/components/environment_canada/translations/el.json b/homeassistant/components/environment_canada/translations/el.json index ebb51349049d2c..8cb9d4c62b66da 100644 --- a/homeassistant/components/environment_canada/translations/el.json +++ b/homeassistant/components/environment_canada/translations/el.json @@ -2,13 +2,17 @@ "config": { "error": { "bad_station_id": "\u03a4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ac\u03ba\u03c5\u03c1\u03bf, \u03bb\u03b5\u03af\u03c0\u03b5\u03b9 \u03ae \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b2\u03c1\u03b5\u03b8\u03b5\u03af \u03c3\u03c4\u03b7 \u03b2\u03ac\u03c3\u03b7 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03ce\u03bd \u03c3\u03c4\u03b1\u03b8\u03bc\u03ce\u03bd", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "error_response": "\u0391\u03c0\u03ac\u03bd\u03c4\u03b7\u03c3\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf Environment Canada \u03ba\u03b1\u03c4\u03ac \u03bb\u03ac\u03b8\u03bf\u03c2", - "too_many_attempts": "\u039f\u03b9 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03b9\u03c2 \u03bc\u03b5 \u03c4\u03bf Environment Canada \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b5\u03c2. \u0394\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c3\u03b5 60 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1" + "too_many_attempts": "\u039f\u03b9 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03b9\u03c2 \u03bc\u03b5 \u03c4\u03bf Environment Canada \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b5\u03c2. \u0394\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c3\u03b5 60 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { "data": { "language": "\u0393\u03bb\u03ce\u03c3\u03c3\u03b1 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03b9\u03ce\u03bd \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd", + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2", "station": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bc\u03b5\u03c4\u03b5\u03c9\u03c1\u03bf\u03bb\u03bf\u03b3\u03b9\u03ba\u03bf\u03cd \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd" }, "description": "\u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03b5\u03af\u03c4\u03b5 \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd \u03b5\u03af\u03c4\u03b5 \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2/\u03bc\u03ae\u03ba\u03bf\u03c2. \u03a4\u03bf \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2/\u03bc\u03ae\u03ba\u03bf\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b5\u03af\u03bd\u03b1\u03b9 \u03bf\u03b9 \u03c4\u03b9\u03bc\u03ad\u03c2 \u03c0\u03bf\u03c5 \u03ad\u03c7\u03bf\u03c5\u03bd \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af \u03c3\u03c4\u03b7\u03bd \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 Home Assistant. \u039f \u03c0\u03bb\u03b7\u03c3\u03b9\u03ad\u03c3\u03c4\u03b5\u03c1\u03bf\u03c2 \u03bc\u03b5\u03c4\u03b5\u03c9\u03c1\u03bf\u03bb\u03bf\u03b3\u03b9\u03ba\u03cc\u03c2 \u03c3\u03c4\u03b1\u03b8\u03bc\u03cc\u03c2 \u03c3\u03c4\u03b9\u03c2 \u03c3\u03c5\u03bd\u03c4\u03b5\u03c4\u03b1\u03b3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b5\u03ac\u03bd \u03ba\u03b1\u03b8\u03bf\u03c1\u03af\u03b6\u03bf\u03bd\u03c4\u03b1\u03b9 \u03c3\u03c5\u03bd\u03c4\u03b5\u03c4\u03b1\u03b3\u03bc\u03ad\u03bd\u03b5\u03c2. \u0395\u03ac\u03bd \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd, \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03b5\u03af \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae: PP/\u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2, \u03cc\u03c0\u03bf\u03c5 PP \u03b5\u03af\u03bd\u03b1\u03b9 \u03b7 \u03b5\u03c0\u03b1\u03c1\u03c7\u03af\u03b1 \u03bc\u03b5 \u03b4\u03cd\u03bf \u03b3\u03c1\u03ac\u03bc\u03bc\u03b1\u03c4\u03b1 \u03ba\u03b1\u03b9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd. \u0397 \u03bb\u03af\u03c3\u03c4\u03b1 \u03c4\u03c9\u03bd \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03ce\u03bd \u03c3\u03c4\u03b1\u03b8\u03bc\u03ce\u03bd \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03b5\u03b4\u03ce: https://dd.weather.gc.ca/citypage_weather/docs/site_list_towns_en.csv. \u039f\u03b9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03ba\u03b1\u03b9\u03c1\u03cc \u03bc\u03c0\u03bf\u03c1\u03bf\u03cd\u03bd \u03bd\u03b1 \u03b1\u03bd\u03b1\u03ba\u03c4\u03b7\u03b8\u03bf\u03cd\u03bd \u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b1 \u03b1\u03b3\u03b3\u03bb\u03b9\u03ba\u03ac \u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b1 \u03b3\u03b1\u03bb\u03bb\u03b9\u03ba\u03ac.", diff --git a/homeassistant/components/epson/translations/el.json b/homeassistant/components/epson/translations/el.json index 85731fbc8cfd53..fbc101807eb5fc 100644 --- a/homeassistant/components/epson/translations/el.json +++ b/homeassistant/components/epson/translations/el.json @@ -1,11 +1,13 @@ { "config": { "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "powered_off": "\u0395\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf\u03c2 \u03bf \u03c0\u03c1\u03bf\u03b2\u03bf\u03bb\u03ad\u03b1\u03c2; \u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b2\u03b9\u03bd\u03c4\u03b5\u03bf\u03c0\u03c1\u03bf\u03b2\u03bf\u03bb\u03ad\u03b1 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b1\u03c1\u03c7\u03b9\u03ba\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7." }, "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1" } } diff --git a/homeassistant/components/evil_genius_labs/translations/el.json b/homeassistant/components/evil_genius_labs/translations/el.json index 2ac7bbe8ac4688..7c0975a66f40d4 100644 --- a/homeassistant/components/evil_genius_labs/translations/el.json +++ b/homeassistant/components/evil_genius_labs/translations/el.json @@ -1,7 +1,9 @@ { "config": { "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "timeout": "\u03a7\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03cc\u03c1\u03b9\u03bf \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/ezviz/translations/el.json b/homeassistant/components/ezviz/translations/el.json index d81c3fb19fdae7..1b9fd46f1264c9 100644 --- a/homeassistant/components/ezviz/translations/el.json +++ b/homeassistant/components/ezviz/translations/el.json @@ -1,10 +1,14 @@ { "config": { "abort": { - "ezviz_cloud_account_missing": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 Ezviz cloud \u03bb\u03b5\u03af\u03c0\u03b5\u03b9. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc Ezviz cloud" + "already_configured_account": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "ezviz_cloud_account_missing": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 Ezviz cloud \u03bb\u03b5\u03af\u03c0\u03b5\u03b9. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc Ezviz cloud", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "invalid_host": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" }, "flow_title": "{serial}", "step": { @@ -18,13 +22,16 @@ }, "user": { "data": { - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf Ezviz Cloud" }, "user_custom_url": { "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", "username": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03bf\u03c2 \u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03c4\u03b7\u03c2 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae\u03c2 \u03c3\u03b1\u03c2", diff --git a/homeassistant/components/faa_delays/translations/el.json b/homeassistant/components/faa_delays/translations/el.json index 7aa5269fc2bd74..245366d355fa32 100644 --- a/homeassistant/components/faa_delays/translations/el.json +++ b/homeassistant/components/faa_delays/translations/el.json @@ -5,7 +5,8 @@ }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", - "invalid_airport": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b1\u03b5\u03c1\u03bf\u03b4\u03c1\u03bf\u03bc\u03af\u03bf\u03c5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2" + "invalid_airport": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b1\u03b5\u03c1\u03bf\u03b4\u03c1\u03bf\u03bc\u03af\u03bf\u03c5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/fireservicerota/translations/el.json b/homeassistant/components/fireservicerota/translations/el.json index 9a0c110347f563..467a98a49ad41d 100644 --- a/homeassistant/components/fireservicerota/translations/el.json +++ b/homeassistant/components/fireservicerota/translations/el.json @@ -1,5 +1,15 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "create_entry": { + "default": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "step": { "reauth": { "data": { diff --git a/homeassistant/components/firmata/translations/el.json b/homeassistant/components/firmata/translations/el.json new file mode 100644 index 00000000000000..eb707f48797b1e --- /dev/null +++ b/homeassistant/components/firmata/translations/el.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fivem/translations/el.json b/homeassistant/components/fivem/translations/el.json index 47ae0dc726ac6e..29e798361fffb2 100644 --- a/homeassistant/components/fivem/translations/el.json +++ b/homeassistant/components/fivem/translations/el.json @@ -1,9 +1,13 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, "error": { "cannot_connect": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03b8\u03cd\u03c1\u03b1 \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac. \u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03b5\u03c0\u03af\u03c3\u03b7\u03c2 \u03cc\u03c4\u03b9 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c0\u03b9\u03bf \u03c0\u03c1\u03cc\u03c3\u03c6\u03b1\u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae FiveM.", "invalid_game_name": "\u03a4\u03bf api \u03c4\u03bf\u03c5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd \u03c3\u03c4\u03bf \u03bf\u03c0\u03bf\u03af\u03bf \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9 FiveM.", - "invalid_gamename": "\u03a4\u03bf api \u03c4\u03bf\u03c5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd \u03c3\u03c4\u03bf \u03bf\u03c0\u03bf\u03af\u03bf \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9 FiveM." + "invalid_gamename": "\u03a4\u03bf api \u03c4\u03bf\u03c5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd \u03c3\u03c4\u03bf \u03bf\u03c0\u03bf\u03af\u03bf \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9 FiveM.", + "unknown_error": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/fjaraskupan/translations/el.json b/homeassistant/components/fjaraskupan/translations/el.json index cb6a9ccddb2338..fccba7806716e0 100644 --- a/homeassistant/components/fjaraskupan/translations/el.json +++ b/homeassistant/components/fjaraskupan/translations/el.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf" + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, "step": { "confirm": { diff --git a/homeassistant/components/flick_electric/translations/el.json b/homeassistant/components/flick_electric/translations/el.json index 6dd1e8d2834255..7a237b4bf1c809 100644 --- a/homeassistant/components/flick_electric/translations/el.json +++ b/homeassistant/components/flick_electric/translations/el.json @@ -1,11 +1,20 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { "client_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03bb\u03ac\u03c4\u03b7 (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", "client_secret": "\u039c\u03c5\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03bb\u03ac\u03c4\u03b7 (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u0394\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 Flick" } diff --git a/homeassistant/components/flipr/translations/el.json b/homeassistant/components/flipr/translations/el.json index 57a48c4bb8025a..122721cfc8ce8e 100644 --- a/homeassistant/components/flipr/translations/el.json +++ b/homeassistant/components/flipr/translations/el.json @@ -1,7 +1,13 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { - "no_flipr_id_found": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc flipr \u03c0\u03bf\u03c5 \u03c3\u03c5\u03c3\u03c7\u03b5\u03c4\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf \u03c0\u03b1\u03c1\u03cc\u03bd. \u0398\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03c0\u03c1\u03ce\u03c4\u03b1 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03b5\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03b5\u03af \u03bc\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u03b3\u03b9\u03b1 \u03ba\u03b9\u03bd\u03b7\u03c4\u03ac \u03c4\u03bf\u03c5 Flipr." + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "no_flipr_id_found": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc flipr \u03c0\u03bf\u03c5 \u03c3\u03c5\u03c3\u03c7\u03b5\u03c4\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf \u03c0\u03b1\u03c1\u03cc\u03bd. \u0398\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03c0\u03c1\u03ce\u03c4\u03b1 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03b5\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03b5\u03af \u03bc\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u03b3\u03b9\u03b1 \u03ba\u03b9\u03bd\u03b7\u03c4\u03ac \u03c4\u03bf\u03c5 Flipr.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "flipr_id": { diff --git a/homeassistant/components/flo/translations/el.json b/homeassistant/components/flo/translations/el.json index 5260b0f7170c52..877622243c8a8f 100644 --- a/homeassistant/components/flo/translations/el.json +++ b/homeassistant/components/flo/translations/el.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/flume/translations/el.json b/homeassistant/components/flume/translations/el.json index 1c271c60797aa9..a15da4ed9612af 100644 --- a/homeassistant/components/flume/translations/el.json +++ b/homeassistant/components/flume/translations/el.json @@ -1,5 +1,14 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "reauth_confirm": { "data": { @@ -12,7 +21,8 @@ "data": { "client_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03bb\u03ac\u03c4\u03b7", "client_secret": "\u039c\u03c5\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03bb\u03ac\u03c4\u03b7", - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03c0\u03bf\u03ba\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7 \u03c3\u03c4\u03bf \u03c0\u03c1\u03bf\u03c3\u03c9\u03c0\u03b9\u03ba\u03cc API \u03c4\u03bf\u03c5 Flume, \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b6\u03b7\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 'Client ID' \u03ba\u03b1\u03b9 \u03ad\u03bd\u03b1 'Client Secret' \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://portal.flumetech.com/settings#token.", "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 Flume" diff --git a/homeassistant/components/flunearyou/translations/el.json b/homeassistant/components/flunearyou/translations/el.json index 1707156c71a787..972611ff6b1c10 100644 --- a/homeassistant/components/flunearyou/translations/el.json +++ b/homeassistant/components/flunearyou/translations/el.json @@ -1,7 +1,17 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { + "data": { + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2" + }, "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ce\u03bd \u03b2\u03ac\u03c3\u03b5\u03b9 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 CDC \u03b3\u03b9\u03b1 \u03ad\u03bd\u03b1 \u03b6\u03b5\u03cd\u03b3\u03bf\u03c2 \u03c3\u03c5\u03bd\u03c4\u03b5\u03c4\u03b1\u03b3\u03bc\u03ad\u03bd\u03c9\u03bd.", "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Flu Near You" } diff --git a/homeassistant/components/flux_led/translations/el.json b/homeassistant/components/flux_led/translations/el.json index 4c5dd46faf7661..903d73287b46a6 100644 --- a/homeassistant/components/flux_led/translations/el.json +++ b/homeassistant/components/flux_led/translations/el.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "flow_title": "{model} {id} ({ipaddr})", "step": { "discovery_confirm": { diff --git a/homeassistant/components/forecast_solar/translations/el.json b/homeassistant/components/forecast_solar/translations/el.json index 5230b14e1928ee..0f6603a622ba90 100644 --- a/homeassistant/components/forecast_solar/translations/el.json +++ b/homeassistant/components/forecast_solar/translations/el.json @@ -5,7 +5,10 @@ "data": { "azimuth": "\u0391\u03b6\u03b9\u03bc\u03bf\u03cd\u03b8\u03b9\u03bf (360 \u03bc\u03bf\u03af\u03c1\u03b5\u03c2, 0 = \u0392\u03bf\u03c1\u03c1\u03ac\u03c2, 90 = \u0391\u03bd\u03b1\u03c4\u03bf\u03bb\u03ae, 180 = \u039d\u03cc\u03c4\u03bf\u03c2, 270 = \u0394\u03cd\u03c3\u03b7)", "declination": "\u0391\u03c0\u03cc\u03ba\u03bb\u03b9\u03c3\u03b7 (0 = \u03bf\u03c1\u03b9\u03b6\u03cc\u03bd\u03c4\u03b9\u03b1, 90 = \u03ba\u03b1\u03c4\u03b1\u03ba\u03cc\u03c1\u03c5\u03c6\u03b7)", - "modules power": "\u03a3\u03c5\u03bd\u03bf\u03bb\u03b9\u03ba\u03ae \u03bc\u03ad\u03b3\u03b9\u03c3\u03c4\u03b7 \u03b9\u03c3\u03c7\u03cd\u03c2 Watt \u03c4\u03c9\u03bd \u03b7\u03bb\u03b9\u03b1\u03ba\u03ce\u03bd \u03c3\u03b1\u03c2 \u03bc\u03bf\u03bd\u03ac\u03b4\u03c9\u03bd" + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2", + "modules power": "\u03a3\u03c5\u03bd\u03bf\u03bb\u03b9\u03ba\u03ae \u03bc\u03ad\u03b3\u03b9\u03c3\u03c4\u03b7 \u03b9\u03c3\u03c7\u03cd\u03c2 Watt \u03c4\u03c9\u03bd \u03b7\u03bb\u03b9\u03b1\u03ba\u03ce\u03bd \u03c3\u03b1\u03c2 \u03bc\u03bf\u03bd\u03ac\u03b4\u03c9\u03bd", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" }, "description": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 \u03c4\u03c9\u03bd \u03b7\u03bb\u03b9\u03b1\u03ba\u03ce\u03bd \u03c3\u03b1\u03c2 \u03c3\u03c5\u03bb\u03bb\u03b5\u03ba\u03c4\u03ce\u03bd. \u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7 \u03b5\u03ac\u03bd \u03ad\u03bd\u03b1 \u03c0\u03b5\u03b4\u03af\u03bf \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c3\u03b1\u03c6\u03ad\u03c2." } diff --git a/homeassistant/components/forked_daapd/translations/el.json b/homeassistant/components/forked_daapd/translations/el.json index 9a87960381a538..ec29c3de039e1e 100644 --- a/homeassistant/components/forked_daapd/translations/el.json +++ b/homeassistant/components/forked_daapd/translations/el.json @@ -1,10 +1,12 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "not_forked_daapd": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 forked-daapd." }, "error": { "forbidden": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03ba\u03b1\u03b9\u03ce\u03bc\u03b1\u03c4\u03b1 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 \u03c4\u03bf\u03c5 forked-daapd.", + "unknown_error": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1", "websocket_not_enabled": "\u039f forked-daapd \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 websocket \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf\u03c2.", "wrong_host_or_port": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03b8\u03cd\u03c1\u03b1.", "wrong_password": "\u0395\u03c3\u03c6\u03b1\u03bb\u03bc\u03ad\u03bd\u03bf\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2.", diff --git a/homeassistant/components/foscam/translations/el.json b/homeassistant/components/foscam/translations/el.json index ef456c825d0fef..0e6c9b7c65dbde 100644 --- a/homeassistant/components/foscam/translations/el.json +++ b/homeassistant/components/foscam/translations/el.json @@ -1,8 +1,13 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", - "invalid_response": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03c0\u03ac\u03bd\u03c4\u03b7\u03c3\u03b7 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "invalid_response": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03c0\u03ac\u03bd\u03c4\u03b7\u03c3\u03b7 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/freebox/translations/el.json b/homeassistant/components/freebox/translations/el.json index 3ee7643c936dc1..e881230014b1d7 100644 --- a/homeassistant/components/freebox/translations/el.json +++ b/homeassistant/components/freebox/translations/el.json @@ -1,7 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { - "register_failed": "\u0397 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "register_failed": "\u0397 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "link": { diff --git a/homeassistant/components/freedompro/translations/el.json b/homeassistant/components/freedompro/translations/el.json index 31baab7883dbdd..cb39329ebab091 100644 --- a/homeassistant/components/freedompro/translations/el.json +++ b/homeassistant/components/freedompro/translations/el.json @@ -1,7 +1,17 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "step": { "user": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API" + }, "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03c0\u03bf\u03c5 \u03bb\u03ac\u03b2\u03b1\u03c4\u03b5 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://home.freedompro.eu", "title": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API Freedompro" } diff --git a/homeassistant/components/fritz/translations/el.json b/homeassistant/components/fritz/translations/el.json index 5f448a2e52962f..443f8b95c9359c 100644 --- a/homeassistant/components/fritz/translations/el.json +++ b/homeassistant/components/fritz/translations/el.json @@ -1,17 +1,31 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "connection_error": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "flow_title": "{name}", "step": { "confirm": { "data": { - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03c4\u03bf FRITZ!Box: {name} \n\n \u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf FRITZ!Box Tools \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03bf\u03c5 {name}", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 FRITZ!Box Tools" }, "reauth_confirm": { "data": { - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0395\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03c4\u03bf\u03c5 FRITZ!Box Tools \u03b3\u03b9\u03b1: {host} . \n\n \u03a4\u03bf FRITZ!Box Tools \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03c3\u03c4\u03bf FRITZ!Box \u03c3\u03b1\u03c2.", "title": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 FRITZ!Box Tools - \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1" @@ -20,7 +34,8 @@ "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "port": "\u0398\u03cd\u03c1\u03b1" + "port": "\u0398\u03cd\u03c1\u03b1", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf FRITZ!Box Tools \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03b5\u03c4\u03b5 \u03c4\u03bf FRITZ!Box \u03c3\u03b1\u03c2.\n \u0395\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03bf: \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7, \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2.", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 FRITZ!Box Tools - \u03c5\u03c0\u03bf\u03c7\u03c1\u03b5\u03c9\u03c4\u03b9\u03ba\u03cc" @@ -29,7 +44,8 @@ "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "port": "\u0398\u03cd\u03c1\u03b1" + "port": "\u0398\u03cd\u03c1\u03b1", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf FRITZ!Box Tools \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03b5\u03c4\u03b5 \u03c4\u03bf FRITZ!Box \u03c3\u03b1\u03c2.\n \u0395\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03bf: \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7, \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2.", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 FRITZ!Box Tools" diff --git a/homeassistant/components/fritzbox/translations/el.json b/homeassistant/components/fritzbox/translations/el.json index d02ad4396d0bbc..975126772d56d9 100644 --- a/homeassistant/components/fritzbox/translations/el.json +++ b/homeassistant/components/fritzbox/translations/el.json @@ -1,13 +1,21 @@ { "config": { "abort": { - "not_supported": "\u03a3\u03c5\u03bd\u03b4\u03ad\u03b8\u03b7\u03ba\u03b5 \u03c3\u03c4\u03bf AVM FRITZ!Box \u03b1\u03bb\u03bb\u03ac \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b5\u03bb\u03ad\u03b3\u03be\u03b5\u03b9 \u03c4\u03b9\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 Smart Home." + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", + "not_supported": "\u03a3\u03c5\u03bd\u03b4\u03ad\u03b8\u03b7\u03ba\u03b5 \u03c3\u03c4\u03bf AVM FRITZ!Box \u03b1\u03bb\u03bb\u03ac \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b5\u03bb\u03ad\u03b3\u03be\u03b5\u03b9 \u03c4\u03b9\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 Smart Home.", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "flow_title": "{name}", "step": { "confirm": { "data": { - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name};" }, @@ -21,7 +29,8 @@ "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf AVM FRITZ!Box." } diff --git a/homeassistant/components/fritzbox_callmonitor/translations/el.json b/homeassistant/components/fritzbox_callmonitor/translations/el.json index cac360f47a8eee..7f16643e4f8f0c 100644 --- a/homeassistant/components/fritzbox_callmonitor/translations/el.json +++ b/homeassistant/components/fritzbox_callmonitor/translations/el.json @@ -1,7 +1,12 @@ { "config": { "abort": { - "insufficient_permissions": "\u039f \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7\u03c2 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b5\u03c0\u03b1\u03c1\u03ba\u03ae \u03b4\u03b9\u03ba\u03b1\u03b9\u03ce\u03bc\u03b1\u03c4\u03b1 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c3\u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c4\u03bf\u03c5 AVM FRITZ!Box \u03ba\u03b1\u03b9 \u03c3\u03c4\u03bf\u03c5\u03c2 \u03c4\u03b7\u03bb\u03b5\u03c6\u03c9\u03bd\u03b9\u03ba\u03bf\u03cd\u03c2 \u03ba\u03b1\u03c4\u03b1\u03bb\u03cc\u03b3\u03bf\u03c5\u03c2 \u03c4\u03bf\u03c5." + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "insufficient_permissions": "\u039f \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7\u03c2 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b5\u03c0\u03b1\u03c1\u03ba\u03ae \u03b4\u03b9\u03ba\u03b1\u03b9\u03ce\u03bc\u03b1\u03c4\u03b1 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c3\u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c4\u03bf\u03c5 AVM FRITZ!Box \u03ba\u03b1\u03b9 \u03c3\u03c4\u03bf\u03c5\u03c2 \u03c4\u03b7\u03bb\u03b5\u03c6\u03c9\u03bd\u03b9\u03ba\u03bf\u03cd\u03c2 \u03ba\u03b1\u03c4\u03b1\u03bb\u03cc\u03b3\u03bf\u03c5\u03c2 \u03c4\u03bf\u03c5.", + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf" + }, + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "flow_title": "{name}", "step": { diff --git a/homeassistant/components/fronius/translations/el.json b/homeassistant/components/fronius/translations/el.json index cce92f7794aaf6..197838ae7dc574 100644 --- a/homeassistant/components/fronius/translations/el.json +++ b/homeassistant/components/fronius/translations/el.json @@ -1,7 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "invalid_host": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "flow_title": "{device}", "step": { diff --git a/homeassistant/components/garages_amsterdam/translations/el.json b/homeassistant/components/garages_amsterdam/translations/el.json index f6e364b044ceb4..3c3da7695e1db3 100644 --- a/homeassistant/components/garages_amsterdam/translations/el.json +++ b/homeassistant/components/garages_amsterdam/translations/el.json @@ -1,5 +1,10 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/gdacs/translations/el.json b/homeassistant/components/gdacs/translations/el.json index 215801cc7c5887..dcedef41f8c94a 100644 --- a/homeassistant/components/gdacs/translations/el.json +++ b/homeassistant/components/gdacs/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/geofency/translations/el.json b/homeassistant/components/geofency/translations/el.json index 558729d4040b08..cf51a439e06f35 100644 --- a/homeassistant/components/geofency/translations/el.json +++ b/homeassistant/components/geofency/translations/el.json @@ -2,7 +2,8 @@ "config": { "abort": { "cloud_not_connected": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf Home Assistant Cloud.", - "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", + "webhook_not_internet_accessible": "\u0397 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1 \u03c4\u03bf\u03c5 Home Assistant \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c3\u03b2\u03ac\u03c3\u03b9\u03bc\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf \u03b4\u03b9\u03b1\u03b4\u03af\u03ba\u03c4\u03c5\u03bf \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03b5\u03b9 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03b1 webhook." }, "create_entry": { "default": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c4\u03b5\u03af\u03bb\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03b1 \u03c3\u03c4\u03bf Home Assistant, \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 webhook \u03c3\u03c4\u03bf Geofency.\n\n\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b1\u03ba\u03cc\u03bb\u03bf\u03c5\u03b8\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2:\n\n- URL: `{webhook_url}`\n- \u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2: POST\n\n\u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]({docs_url}) \u03b3\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2." diff --git a/homeassistant/components/geonetnz_quakes/translations/el.json b/homeassistant/components/geonetnz_quakes/translations/el.json index a859f6fd3df199..be08ac36a4711a 100644 --- a/homeassistant/components/geonetnz_quakes/translations/el.json +++ b/homeassistant/components/geonetnz_quakes/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/geonetnz_volcano/translations/el.json b/homeassistant/components/geonetnz_volcano/translations/el.json index 215801cc7c5887..23fbdf2cc0da5c 100644 --- a/homeassistant/components/geonetnz_volcano/translations/el.json +++ b/homeassistant/components/geonetnz_volcano/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/gios/translations/el.json b/homeassistant/components/gios/translations/el.json index 0614fd18b68760..ae916d7066797e 100644 --- a/homeassistant/components/gios/translations/el.json +++ b/homeassistant/components/gios/translations/el.json @@ -1,12 +1,17 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "invalid_sensors_data": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b1 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03c9\u03bd \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03c3\u03c4\u03b1\u03b8\u03bc\u03cc \u03bc\u03ad\u03c4\u03c1\u03b7\u03c3\u03b7\u03c2.", "wrong_station_id": "\u03a4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03bf\u03c5 \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd \u03bc\u03ad\u03c4\u03c1\u03b7\u03c3\u03b7\u03c2 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c9\u03c3\u03c4\u03cc." }, "step": { "user": { "data": { + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "station_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03bf\u03c5 \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd \u03bc\u03ad\u03c4\u03c1\u03b7\u03c3\u03b7\u03c2" }, "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c0\u03bf\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03c4\u03bf\u03c5 \u03b1\u03ad\u03c1\u03b1 GIO\u015a (\u03a0\u03bf\u03bb\u03c9\u03bd\u03b9\u03ba\u03ae \u0395\u03c0\u03b9\u03ba\u03b5\u03c6\u03b1\u03bb\u03ae\u03c2 \u0395\u03c0\u03b9\u03b8\u03b5\u03ce\u03c1\u03b7\u03c3\u03b7\u03c2 \u03a0\u03c1\u03bf\u03c3\u03c4\u03b1\u03c3\u03af\u03b1\u03c2 \u03c4\u03bf\u03c5 \u03a0\u03b5\u03c1\u03b9\u03b2\u03ac\u03bb\u03bb\u03bf\u03bd\u03c4\u03bf\u03c2). \u0395\u03ac\u03bd \u03c7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c3\u03c4\u03b5 \u03b2\u03bf\u03ae\u03b8\u03b5\u03b9\u03b1 \u03bc\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7, \u03c1\u03af\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03bc\u03b1\u03c4\u03b9\u03ac \u03b5\u03b4\u03ce: https://www.home-assistant.io/integrations/gios", diff --git a/homeassistant/components/github/translations/el.json b/homeassistant/components/github/translations/el.json index cc5672a63b8e82..97abf5d3d0c9f4 100644 --- a/homeassistant/components/github/translations/el.json +++ b/homeassistant/components/github/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", "could_not_register": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03bc\u03b5 \u03c4\u03bf GitHub" }, "progress": { diff --git a/homeassistant/components/glances/translations/el.json b/homeassistant/components/glances/translations/el.json index ab50cc0e5e865b..f0f927fcc71752 100644 --- a/homeassistant/components/glances/translations/el.json +++ b/homeassistant/components/glances/translations/el.json @@ -1,15 +1,22 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "wrong_version": "\u0397 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 (\u03bc\u03cc\u03bd\u03bf 2 \u03ae 3)" }, "step": { "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "port": "\u0398\u03cd\u03c1\u03b1", + "ssl": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03ad\u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", + "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL", "version": "\u0388\u03ba\u03b4\u03bf\u03c3\u03b7 API Glances (2 \u03ae 3)" }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 Glances" diff --git a/homeassistant/components/goalzero/translations/el.json b/homeassistant/components/goalzero/translations/el.json index 4ad673ea7db315..31d089f53ec556 100644 --- a/homeassistant/components/goalzero/translations/el.json +++ b/homeassistant/components/goalzero/translations/el.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf\u03c2" + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf\u03c2", + "invalid_host": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", diff --git a/homeassistant/components/gogogate2/translations/el.json b/homeassistant/components/gogogate2/translations/el.json index 3d80794d682f71..373d36bb397a0d 100644 --- a/homeassistant/components/gogogate2/translations/el.json +++ b/homeassistant/components/gogogate2/translations/el.json @@ -1,11 +1,19 @@ { "config": { + "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "flow_title": "{device} ({ip_address})", "step": { "user": { "data": { "ip_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0394\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b1\u03c0\u03b1\u03c1\u03b1\u03af\u03c4\u03b7\u03c4\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9.", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 Gogogate2 \u03ae ismartgate" diff --git a/homeassistant/components/goodwe/translations/el.json b/homeassistant/components/goodwe/translations/el.json index 7137da9d868af1..431644cce7b418 100644 --- a/homeassistant/components/goodwe/translations/el.json +++ b/homeassistant/components/goodwe/translations/el.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7" + }, "error": { "connection_error": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, diff --git a/homeassistant/components/google_travel_time/translations/el.json b/homeassistant/components/google_travel_time/translations/el.json index 6d14401f970329..8180ef61c03cd9 100644 --- a/homeassistant/components/google_travel_time/translations/el.json +++ b/homeassistant/components/google_travel_time/translations/el.json @@ -1,8 +1,15 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", "destination": "\u03a0\u03c1\u03bf\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "origin": "\u03a0\u03c1\u03bf\u03ad\u03bb\u03b5\u03c5\u03c3\u03b7" diff --git a/homeassistant/components/gpslogger/translations/el.json b/homeassistant/components/gpslogger/translations/el.json index cb9c7a2788b3bc..74e1d5075aef01 100644 --- a/homeassistant/components/gpslogger/translations/el.json +++ b/homeassistant/components/gpslogger/translations/el.json @@ -2,7 +2,8 @@ "config": { "abort": { "cloud_not_connected": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf Home Assistant Cloud.", - "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", + "webhook_not_internet_accessible": "\u0397 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1 \u03c4\u03bf\u03c5 Home Assistant \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c3\u03b2\u03ac\u03c3\u03b9\u03bc\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf \u03b4\u03b9\u03b1\u03b4\u03af\u03ba\u03c4\u03c5\u03bf \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03b5\u03b9 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03b1 webhook." }, "create_entry": { "default": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c4\u03b5\u03af\u03bb\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03b1 \u03c3\u03c4\u03bf Home Assistant, \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 webhook \u03c3\u03c4\u03bf GPSLogger.\n\n\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b1\u03ba\u03cc\u03bb\u03bf\u03c5\u03b8\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2:\n\n- URL: `{webhook_url}`\n- \u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2: POST\n\n\u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]({docs_url}) \u03b3\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2." diff --git a/homeassistant/components/gree/translations/el.json b/homeassistant/components/gree/translations/el.json new file mode 100644 index 00000000000000..a13912159002b3 --- /dev/null +++ b/homeassistant/components/gree/translations/el.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "step": { + "confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/growatt_server/translations/el.json b/homeassistant/components/growatt_server/translations/el.json index 6310540cb399e1..1801a2b0861c5c 100644 --- a/homeassistant/components/growatt_server/translations/el.json +++ b/homeassistant/components/growatt_server/translations/el.json @@ -1,9 +1,24 @@ { "config": { + "abort": { + "no_plants": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03b5\u03c1\u03b3\u03bf\u03c3\u03c4\u03ac\u03c3\u03b9\u03b1 \u03c3\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc" + }, + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "step": { + "plant": { + "data": { + "plant_id": "\u0395\u03c1\u03b3\u03bf\u03c3\u03c4\u03ac\u03c3\u03b9\u03bf" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf \u03b5\u03c1\u03b3\u03bf\u03c3\u03c4\u03ac\u03c3\u03b9\u03cc \u03c3\u03b1\u03c2" + }, "user": { "data": { - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 Growatt \u03c3\u03b1\u03c2" } diff --git a/homeassistant/components/guardian/translations/el.json b/homeassistant/components/guardian/translations/el.json index 3f3d255645639f..8173f4d7fa04f5 100644 --- a/homeassistant/components/guardian/translations/el.json +++ b/homeassistant/components/guardian/translations/el.json @@ -1,6 +1,8 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "step": { @@ -9,7 +11,8 @@ }, "user": { "data": { - "ip_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + "ip_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", + "port": "\u0398\u03cd\u03c1\u03b1" }, "description": "\u0394\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c4\u03bf\u03c0\u03b9\u03ba\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Elexa Guardian." }, diff --git a/homeassistant/components/habitica/translations/el.json b/homeassistant/components/habitica/translations/el.json index 45e87d4625bd94..43257c239a954d 100644 --- a/homeassistant/components/habitica/translations/el.json +++ b/homeassistant/components/habitica/translations/el.json @@ -1,10 +1,16 @@ { "config": { + "error": { + "invalid_credentials": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", "api_user": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 API \u03c4\u03b7\u03c2 Habitica", - "name": "\u03a0\u03b1\u03c1\u03ac\u03ba\u03b1\u03bc\u03c8\u03b7 \u03b3\u03b9\u03b1 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03c4\u03b7\u03c2 Habitica. \u0398\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03ba\u03bb\u03ae\u03c3\u03b5\u03b9\u03c2 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03b9\u03ce\u03bd" + "name": "\u03a0\u03b1\u03c1\u03ac\u03ba\u03b1\u03bc\u03c8\u03b7 \u03b3\u03b9\u03b1 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03c4\u03b7\u03c2 Habitica. \u0398\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03ba\u03bb\u03ae\u03c3\u03b5\u03b9\u03c2 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03b9\u03ce\u03bd", + "url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL" }, "description": "\u03a3\u03c5\u03bd\u03b4\u03ad\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03c1\u03bf\u03c6\u03af\u03bb \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Habitica \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c8\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03bf\u03c6\u03af\u03bb \u03ba\u03b1\u03b9 \u03c4\u03c9\u03bd \u03b5\u03c1\u03b3\u03b1\u03c3\u03b9\u03ce\u03bd \u03c4\u03bf\u03c5 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03c3\u03b1\u03c2. \u03a3\u03b7\u03bc\u03b5\u03b9\u03ce\u03c3\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03c4\u03bf api_id \u03ba\u03b1\u03b9 \u03c4\u03bf api_key \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd \u03b1\u03c0\u03cc \u03c4\u03bf https://habitica.com/user/settings/api." } diff --git a/homeassistant/components/hangouts/translations/el.json b/homeassistant/components/hangouts/translations/el.json index 6baa206871db8e..c4e46912a51626 100644 --- a/homeassistant/components/hangouts/translations/el.json +++ b/homeassistant/components/hangouts/translations/el.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "error": { "invalid_2fa": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 2 \u03c0\u03b1\u03c1\u03b1\u03b3\u03cc\u03bd\u03c4\u03c9\u03bd, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", "invalid_2fa_method": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03bc\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2 2FA (\u03b5\u03c0\u03b1\u03bb\u03ae\u03b8\u03b5\u03c5\u03c3\u03b7 \u03c3\u03c4\u03bf \u03c4\u03b7\u03bb\u03ad\u03c6\u03c9\u03bd\u03bf).", diff --git a/homeassistant/components/harmony/translations/el.json b/homeassistant/components/harmony/translations/el.json index 738b64c64096a9..c8483acd34bc4e 100644 --- a/homeassistant/components/harmony/translations/el.json +++ b/homeassistant/components/harmony/translations/el.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "flow_title": "{name}", "step": { "link": { diff --git a/homeassistant/components/heos/translations/el.json b/homeassistant/components/heos/translations/el.json index 5e1483813a8b23..6eb00ad272388f 100644 --- a/homeassistant/components/heos/translations/el.json +++ b/homeassistant/components/heos/translations/el.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/hisense_aehw4a1/translations/el.json b/homeassistant/components/hisense_aehw4a1/translations/el.json index 421a29064f71a9..dbe14be152655f 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/el.json +++ b/homeassistant/components/hisense_aehw4a1/translations/el.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, "step": { "confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Hisense AEH-W4A1;" diff --git a/homeassistant/components/hlk_sw16/translations/el.json b/homeassistant/components/hlk_sw16/translations/el.json index 5260b0f7170c52..877622243c8a8f 100644 --- a/homeassistant/components/hlk_sw16/translations/el.json +++ b/homeassistant/components/hlk_sw16/translations/el.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/home_connect/translations/el.json b/homeassistant/components/home_connect/translations/el.json new file mode 100644 index 00000000000000..b82ab8fa03be21 --- /dev/null +++ b/homeassistant/components/home_connect/translations/el.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "missing_configuration": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7.", + "no_url_available": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL. \u0393\u03b9\u03b1 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1, [\u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b2\u03bf\u03ae\u03b8\u03b5\u03b9\u03b1\u03c2] ( {docs_url} )" + }, + "create_entry": { + "default": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, + "step": { + "pick_implementation": { + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03bc\u03b5\u03b8\u03cc\u03b4\u03bf\u03c5 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/el.json b/homeassistant/components/home_plus_control/translations/el.json index 788a8c5a11df96..724dafff28fee7 100644 --- a/homeassistant/components/home_plus_control/translations/el.json +++ b/homeassistant/components/home_plus_control/translations/el.json @@ -1,3 +1,21 @@ { + "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", + "authorize_url_timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2.", + "missing_configuration": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7.", + "no_url_available": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL. \u0393\u03b9\u03b1 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1, [\u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b2\u03bf\u03ae\u03b8\u03b5\u03b9\u03b1\u03c2] ( {docs_url} )", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "create_entry": { + "default": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, + "step": { + "pick_implementation": { + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03bc\u03b5\u03b8\u03cc\u03b4\u03bf\u03c5 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + } + } + }, "title": "\u0388\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 Legrand Home+" } \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/el.json b/homeassistant/components/homekit/translations/el.json index a412a0914c6499..632d9aecd764a5 100644 --- a/homeassistant/components/homekit/translations/el.json +++ b/homeassistant/components/homekit/translations/el.json @@ -42,6 +42,9 @@ "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03ba\u03ac\u03bc\u03b5\u03c1\u03b1\u03c2" }, "exclude": { + "data": { + "entities": "\u039f\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2" + }, "description": "\u0398\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd \u03cc\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \"{domains}\" \u03b5\u03ba\u03c4\u03cc\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b9\u03c2 \u03b5\u03be\u03b1\u03b9\u03c1\u03bf\u03cd\u03bc\u03b5\u03bd\u03b5\u03c2 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03ba\u03b1\u03b9 \u03c4\u03b9\u03c2 \u03ba\u03b1\u03c4\u03b7\u03b3\u03bf\u03c1\u03b9\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b5\u03c2 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2.", "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03b5\u03be\u03b1\u03b9\u03c1\u03b5\u03b8\u03bf\u03cd\u03bd" }, @@ -54,13 +57,16 @@ }, "include_exclude": { "data": { - "entities": "\u039f\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2" + "entities": "\u039f\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2", + "mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1" }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd. \u03a3\u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b1\u03be\u03b5\u03c3\u03bf\u03c5\u03ac\u03c1, \u03c0\u03b5\u03c1\u03b9\u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03b5\u03c4\u03b1\u03b9 \u03bc\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1. \u03a3\u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 include bridge, \u03b8\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd \u03cc\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03c4\u03bf\u03c5 \u03c4\u03bf\u03bc\u03ad\u03b1, \u03b5\u03ba\u03c4\u03cc\u03c2 \u03b5\u03ac\u03bd \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bf\u03cd\u03bd \u03c3\u03c5\u03b3\u03ba\u03b5\u03ba\u03c1\u03b9\u03bc\u03ad\u03bd\u03b5\u03c2 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2. \u03a3\u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b1\u03c0\u03bf\u03ba\u03bb\u03b5\u03b9\u03c3\u03bc\u03bf\u03cd \u03b3\u03ad\u03c6\u03c5\u03c1\u03b1\u03c2, \u03b8\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd \u03cc\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03c4\u03bf\u03c5 \u03c4\u03bf\u03bc\u03ad\u03b1 \u03b5\u03ba\u03c4\u03cc\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b9\u03c2 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03c0\u03bf\u03c5 \u03ad\u03c7\u03bf\u03c5\u03bd \u03b1\u03c0\u03bf\u03ba\u03bb\u03b5\u03b9\u03c3\u03c4\u03b5\u03af. \u0393\u03b9\u03b1 \u03ba\u03b1\u03bb\u03cd\u03c4\u03b5\u03c1\u03b7 \u03b1\u03c0\u03cc\u03b4\u03bf\u03c3\u03b7, \u03b8\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03b7\u03b8\u03b5\u03af \u03ad\u03bd\u03b1 \u03be\u03b5\u03c7\u03c9\u03c1\u03b9\u03c3\u03c4\u03cc \u03b1\u03be\u03b5\u03c3\u03bf\u03c5\u03ac\u03c1 HomeKit \u03b3\u03b9\u03b1 \u03ba\u03ac\u03b8\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2 \u03c0\u03bf\u03bb\u03c5\u03bc\u03ad\u03c3\u03c9\u03bd tv, \u03c4\u03b7\u03bb\u03b5\u03c7\u03b5\u03b9\u03c1\u03b9\u03c3\u03c4\u03ae\u03c1\u03b9\u03bf \u03bc\u03b5 \u03b2\u03ac\u03c3\u03b7 \u03c4\u03b7 \u03b4\u03c1\u03b1\u03c3\u03c4\u03b7\u03c1\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1, \u03ba\u03bb\u03b5\u03b9\u03b4\u03b1\u03c1\u03b9\u03ac \u03ba\u03b1\u03b9 \u03ba\u03ac\u03bc\u03b5\u03c1\u03b1.", "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03bf\u03bd\u03c4\u03bf\u03c4\u03ae\u03c4\u03c9\u03bd \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd" }, "init": { "data": { + "domains": "\u03a4\u03bf\u03bc\u03b5\u03af\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd", + "include_domains": "\u03a4\u03bf\u03bc\u03b5\u03af\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd", "include_exclude_mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03af\u03bb\u03b7\u03c8\u03b7\u03c2", "mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 HomeKit" }, diff --git a/homeassistant/components/homekit_controller/translations/el.json b/homeassistant/components/homekit_controller/translations/el.json index 0c40b77035daa6..ebfc1687301e30 100644 --- a/homeassistant/components/homekit_controller/translations/el.json +++ b/homeassistant/components/homekit_controller/translations/el.json @@ -3,6 +3,7 @@ "abort": { "accessory_not_found_error": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03b6\u03b5\u03cd\u03be\u03b7\u03c2 \u03ba\u03b1\u03b8\u03ce\u03c2 \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03c0\u03bb\u03ad\u03bf\u03bd \u03bd\u03b1 \u03b2\u03c1\u03b5\u03b8\u03b5\u03af.", "already_configured": "\u03a4\u03bf \u03b5\u03be\u03ac\u03c1\u03c4\u03b7\u03bc\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c7\u03b5\u03b9\u03c1\u03b9\u03c3\u03c4\u03ae\u03c1\u03b9\u03bf.", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", "already_paired": "\u0391\u03c5\u03c4\u03cc \u03c4\u03bf \u03b1\u03be\u03b5\u03c3\u03bf\u03c5\u03ac\u03c1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 \u03ac\u03bb\u03bb\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae. \u0395\u03c0\u03b1\u03bd\u03b1\u03c6\u03ad\u03c1\u03b5\u03c4\u03b5 \u03c4\u03bf \u03b1\u03be\u03b5\u03c3\u03bf\u03c5\u03ac\u03c1 \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", "ignored_model": "\u0397 \u03c5\u03c0\u03bf\u03c3\u03c4\u03ae\u03c1\u03b9\u03be\u03b7 \u03c4\u03bf\u03c5 HomeKit \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03bc\u03bf\u03bd\u03c4\u03ad\u03bb\u03bf \u03b5\u03af\u03bd\u03b1\u03b9 \u03bc\u03c0\u03bb\u03bf\u03ba\u03b1\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7, \u03ba\u03b1\u03b8\u03ce\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03bc\u03b9\u03b1 \u03c0\u03b9\u03bf \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03c9\u03bc\u03ad\u03bd\u03b7 \u03b5\u03b3\u03b3\u03b5\u03bd\u03ae\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7.", "invalid_config_entry": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c9\u03c2 \u03ad\u03c4\u03bf\u03b9\u03bc\u03b7 \u03b3\u03b9\u03b1 \u03b6\u03b5\u03cd\u03be\u03b7, \u03b1\u03bb\u03bb\u03ac \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03bc\u03b9\u03b1 \u03b1\u03bd\u03c4\u03b9\u03ba\u03c1\u03bf\u03c5\u03cc\u03bc\u03b5\u03bd\u03b7 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c3\u03c4\u03bf Home Assistant, \u03b7 \u03bf\u03c0\u03bf\u03af\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03c0\u03c1\u03ce\u03c4\u03b1 \u03bd\u03b1 \u03b1\u03c6\u03b1\u03b9\u03c1\u03b5\u03b8\u03b5\u03af.", diff --git a/homeassistant/components/homematicip_cloud/translations/el.json b/homeassistant/components/homematicip_cloud/translations/el.json index f7927ce0f96d11..dc13644ad0d0f3 100644 --- a/homeassistant/components/homematicip_cloud/translations/el.json +++ b/homeassistant/components/homematicip_cloud/translations/el.json @@ -1,5 +1,10 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "connection_aborted": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "error": { "invalid_sgtin_or_pin": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf SGTIN \u03ae \u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN, \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", "press_the_button": "\u03a0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03bc\u03c0\u03bb\u03b5 \u03ba\u03bf\u03c5\u03bc\u03c0\u03af.", @@ -10,7 +15,8 @@ "init": { "data": { "hapid": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c3\u03b7\u03bc\u03b5\u03af\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 (SGTIN)", - "name": "\u038c\u03bd\u03bf\u03bc\u03b1 (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03c9\u03c2 \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 \u03bf\u03bd\u03cc\u03bc\u03b1\u03c4\u03bf\u03c2 \u03b3\u03b9\u03b1 \u03cc\u03bb\u03b5\u03c2 \u03c4\u03b9\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2)" + "name": "\u038c\u03bd\u03bf\u03bc\u03b1 (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03c9\u03c2 \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 \u03bf\u03bd\u03cc\u03bc\u03b1\u03c4\u03bf\u03c2 \u03b3\u03b9\u03b1 \u03cc\u03bb\u03b5\u03c2 \u03c4\u03b9\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2)", + "pin": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN" }, "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c3\u03b7\u03bc\u03b5\u03af\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 HomematicIP" }, diff --git a/homeassistant/components/homewizard/translations/el.json b/homeassistant/components/homewizard/translations/el.json index 69796cec02d2e6..f3d7c392109dec 100644 --- a/homeassistant/components/homewizard/translations/el.json +++ b/homeassistant/components/homewizard/translations/el.json @@ -1,9 +1,11 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "api_not_enabled": "\u03a4\u03bf API \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf. \u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf API \u03c3\u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae HomeWizard Energy App \u03c3\u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2", "device_not_supported": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9", - "invalid_discovery_parameters": "unsupported_api_version" + "invalid_discovery_parameters": "unsupported_api_version", + "unknown_error": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "discovery_confirm": { diff --git a/homeassistant/components/honeywell/translations/el.json b/homeassistant/components/honeywell/translations/el.json index 907a98fe73a7fe..7ad0c5b0181a2b 100644 --- a/homeassistant/components/honeywell/translations/el.json +++ b/homeassistant/components/honeywell/translations/el.json @@ -1,9 +1,13 @@ { "config": { + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "step": { "user": { "data": { - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b1\u03c4\u03b5 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf mytotalconnectcomfort.com.", "title": "Honeywell Total Connect Comfort (\u0397\u03a0\u0391)" diff --git a/homeassistant/components/huawei_lte/translations/el.json b/homeassistant/components/huawei_lte/translations/el.json index 3277ea6fdad911..01526400165274 100644 --- a/homeassistant/components/huawei_lte/translations/el.json +++ b/homeassistant/components/huawei_lte/translations/el.json @@ -1,6 +1,8 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", "not_huawei_lte": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Huawei LTE" }, "error": { @@ -18,6 +20,7 @@ "user": { "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2.", diff --git a/homeassistant/components/hue/translations/el.json b/homeassistant/components/hue/translations/el.json index b53e31046b66cb..99bdc934929ae1 100644 --- a/homeassistant/components/hue/translations/el.json +++ b/homeassistant/components/hue/translations/el.json @@ -2,11 +2,16 @@ "config": { "abort": { "all_configured": "\u038c\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03b3\u03ad\u03c6\u03c5\u03c1\u03b5\u03c2 Philips Hue \u03ad\u03c7\u03bf\u03c5\u03bd \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "discover_timeout": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03bc\u03bf\u03cd \u03b3\u03b5\u03c6\u03c5\u03c1\u03ce\u03bd Hue", "no_bridges": "\u0394\u03b5\u03bd \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b1\u03bd \u03b3\u03ad\u03c6\u03c5\u03c1\u03b5\u03c2 Philips Hue", - "not_hue_bridge": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b3\u03ad\u03c6\u03c5\u03c1\u03b1 Hue" + "not_hue_bridge": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b3\u03ad\u03c6\u03c5\u03c1\u03b1 Hue", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "error": { + "linking": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1", "register_failed": "\u0397 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac" }, "step": { @@ -23,7 +28,8 @@ "manual": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" - } + }, + "title": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd \u03bc\u03b9\u03b1\u03c2 \u03b3\u03ad\u03c6\u03c5\u03c1\u03b1\u03c2 Hue" } } }, @@ -45,6 +51,7 @@ "turn_on": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7" }, "trigger_type": { + "double_short_release": "\u039a\u03b1\u03b9 \u03c4\u03b1 \u03b4\u03cd\u03bf \"{subtype}\" \u03b1\u03c0\u03b5\u03bb\u03b5\u03c5\u03b8\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b1\u03bd", "initial_press": "\u03a4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \"{subtype}\" \u03c0\u03b1\u03c4\u03ae\u03b8\u03b7\u03ba\u03b5 \u03b1\u03c1\u03c7\u03b9\u03ba\u03ac", "long_release": "\u03a4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \"{subtype}\" \u03b1\u03c0\u03b5\u03bb\u03b5\u03c5\u03b8\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03bc\u03b5\u03c4\u03ac \u03b1\u03c0\u03cc \u03c0\u03b1\u03c1\u03b1\u03c4\u03b5\u03c4\u03b1\u03bc\u03ad\u03bd\u03bf \u03c0\u03ac\u03c4\u03b7\u03bc\u03b1", "remote_button_long_release": "\u03a4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03c4\u03bf\u03c5 \"{subtype}\" \u03b1\u03c0\u03b5\u03bb\u03b5\u03c5\u03b8\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03bc\u03b5\u03c4\u03ac \u03b1\u03c0\u03cc \u03c0\u03b1\u03c1\u03b1\u03c4\u03b5\u03c4\u03b1\u03bc\u03ad\u03bd\u03bf \u03c0\u03ac\u03c4\u03b7\u03bc\u03b1", @@ -62,6 +69,7 @@ "data": { "allow_hue_groups": "\u039d\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03bf\u03bd\u03c4\u03b1\u03b9 \u03bf\u03b9 \u03bf\u03bc\u03ac\u03b4\u03b5\u03c2 Hue", "allow_hue_scenes": "\u039d\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03bf\u03bd\u03c4\u03b1\u03b9 \u03c3\u03ba\u03b7\u03bd\u03ad\u03c2 Hue", + "allow_unreachable": "\u0395\u03c0\u03b9\u03c4\u03c1\u03ad\u03c8\u03c4\u03b5 \u03c3\u03c4\u03bf\u03c5\u03c2 \u03bc\u03b7 \u03c0\u03c1\u03bf\u03c3\u03b2\u03ac\u03c3\u03b9\u03bc\u03bf\u03c5\u03c2 \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b5\u03c2 \u03bd\u03b1 \u03b1\u03bd\u03b1\u03c6\u03ad\u03c1\u03bf\u03c5\u03bd \u03c3\u03c9\u03c3\u03c4\u03ac \u03c4\u03b7\u03bd \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03ae \u03c4\u03bf\u03c5\u03c2", "ignore_availability": "\u03a0\u03b1\u03c1\u03ac\u03b2\u03bb\u03b5\u03c8\u03b7 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2 \u03c3\u03c5\u03bd\u03b4\u03b5\u03c3\u03b9\u03bc\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b9\u03c2 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2" } } diff --git a/homeassistant/components/huisbaasje/translations/el.json b/homeassistant/components/huisbaasje/translations/el.json index 118803f915bcd0..5b1861a0e4080f 100644 --- a/homeassistant/components/huisbaasje/translations/el.json +++ b/homeassistant/components/huisbaasje/translations/el.json @@ -1,7 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/hunterdouglas_powerview/translations/el.json b/homeassistant/components/hunterdouglas_powerview/translations/el.json index 2b4ce98a901613..40bdfa3c7d37cf 100644 --- a/homeassistant/components/hunterdouglas_powerview/translations/el.json +++ b/homeassistant/components/hunterdouglas_powerview/translations/el.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "flow_title": "{name} ({host})", "step": { "link": { diff --git a/homeassistant/components/hvv_departures/translations/el.json b/homeassistant/components/hvv_departures/translations/el.json index 595e17d1698139..91bf7e078689b3 100644 --- a/homeassistant/components/hvv_departures/translations/el.json +++ b/homeassistant/components/hvv_departures/translations/el.json @@ -1,6 +1,11 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "no_results": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03bd \u03b1\u03c0\u03bf\u03c4\u03b5\u03bb\u03ad\u03c3\u03bc\u03b1\u03c4\u03b1. \u0394\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03bc\u03b5 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03c3\u03c4\u03b1\u03b8\u03bc\u03cc/\u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7" }, "step": { @@ -19,7 +24,8 @@ "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf HVV API" } diff --git a/homeassistant/components/hyperion/translations/el.json b/homeassistant/components/hyperion/translations/el.json index 4b712187d4c538..6c55ac02969da8 100644 --- a/homeassistant/components/hyperion/translations/el.json +++ b/homeassistant/components/hyperion/translations/el.json @@ -1,10 +1,18 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", "auth_new_token_not_granted_error": "\u03a4\u03bf \u03c0\u03c1\u03cc\u03c3\u03c6\u03b1\u03c4\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03b7\u03bc\u03ad\u03bd\u03bf token \u03b4\u03b5\u03bd \u03b5\u03b3\u03ba\u03c1\u03af\u03b8\u03b7\u03ba\u03b5 \u03c3\u03c4\u03bf Hyperion UI", "auth_new_token_not_work_error": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03bf \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03bf\u03cd \u03c0\u03bf\u03c5 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03b8\u03b7\u03ba\u03b5 \u03c0\u03c1\u03cc\u03c3\u03c6\u03b1\u03c4\u03b1", "auth_required_error": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b4\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd \u03b5\u03ac\u03bd \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7", - "no_id": "\u0397 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1 Hyperion Ambilight \u03b4\u03b5\u03bd \u03b1\u03bd\u03ad\u03c6\u03b5\u03c1\u03b5 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "no_id": "\u0397 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1 Hyperion Ambilight \u03b4\u03b5\u03bd \u03b1\u03bd\u03ad\u03c6\u03b5\u03c1\u03b5 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03b7\u03c2", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_access_token": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "step": { "auth": { diff --git a/homeassistant/components/ialarm/translations/el.json b/homeassistant/components/ialarm/translations/el.json index 07740adf2e1b0e..4a3011c880b1fd 100644 --- a/homeassistant/components/ialarm/translations/el.json +++ b/homeassistant/components/ialarm/translations/el.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/iaqualink/translations/el.json b/homeassistant/components/iaqualink/translations/el.json index 7f6732b282e09c..e9dc0dfa39b464 100644 --- a/homeassistant/components/iaqualink/translations/el.json +++ b/homeassistant/components/iaqualink/translations/el.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/icloud/translations/el.json b/homeassistant/components/icloud/translations/el.json index 19d983fd718182..cc484bd5660d84 100644 --- a/homeassistant/components/icloud/translations/el.json +++ b/homeassistant/components/icloud/translations/el.json @@ -1,9 +1,12 @@ { "config": { "abort": { - "no_device": "\u039a\u03b1\u03bc\u03af\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b9\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03b1\u03c2 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 \u03c4\u03b7\u03bd \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \"Find my iPhone\"." + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "no_device": "\u039a\u03b1\u03bc\u03af\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b9\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03b1\u03c2 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 \u03c4\u03b7\u03bd \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \"Find my iPhone\".", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" }, "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "send_verification_code": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03b1\u03c0\u03bf\u03c3\u03c4\u03bf\u03bb\u03ae\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03b5\u03c0\u03b1\u03bb\u03ae\u03b8\u03b5\u03c5\u03c3\u03b7\u03c2", "validate_verification_code": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b5\u03c0\u03b1\u03bb\u03ae\u03b8\u03b5\u03c5\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03b5\u03c0\u03b1\u03bb\u03ae\u03b8\u03b5\u03c5\u03c3\u03b7\u03c2, \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac" }, @@ -12,7 +15,8 @@ "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, - "description": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c0\u03bf\u03c5 \u03b5\u03af\u03c7\u03b1\u03c4\u03b5 \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03b9 \u03c0\u03c1\u03bf\u03b7\u03b3\u03bf\u03c5\u03bc\u03ad\u03bd\u03c9\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username} \u03b4\u03b5\u03bd \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03b5\u03af \u03c0\u03bb\u03ad\u03bf\u03bd. \u0395\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7." + "description": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c0\u03bf\u03c5 \u03b5\u03af\u03c7\u03b1\u03c4\u03b5 \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03b9 \u03c0\u03c1\u03bf\u03b7\u03b3\u03bf\u03c5\u03bc\u03ad\u03bd\u03c9\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username} \u03b4\u03b5\u03bd \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03b5\u03af \u03c0\u03bb\u03ad\u03bf\u03bd. \u0395\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7.", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, "trusted_device": { "data": { diff --git a/homeassistant/components/ifttt/translations/el.json b/homeassistant/components/ifttt/translations/el.json index e6180bc7eadf46..0ef5f6df6a4ed1 100644 --- a/homeassistant/components/ifttt/translations/el.json +++ b/homeassistant/components/ifttt/translations/el.json @@ -2,7 +2,8 @@ "config": { "abort": { "cloud_not_connected": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf Home Assistant Cloud.", - "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", + "webhook_not_internet_accessible": "\u0397 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1 \u03c4\u03bf\u03c5 Home Assistant \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c3\u03b2\u03ac\u03c3\u03b9\u03bc\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf \u03b4\u03b9\u03b1\u03b4\u03af\u03ba\u03c4\u03c5\u03bf \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03b5\u03b9 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03b1 webhook." }, "create_entry": { "default": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c4\u03b5\u03af\u03bb\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03b1 \u03c3\u03c4\u03bf\u03bd Home Assistant, \u03b8\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03ad\u03c1\u03b3\u03b5\u03b9\u03b1 \"\u0394\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b1\u03b9\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2 \u0399\u03c3\u03c4\u03bf\u03cd\" \u03b1\u03c0\u03cc \u03c4\u03b7 [\u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae IFTTT Webhook]({applet_url}). \n\n \u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2: \n\n - URL: `{webhook_url}`\n - \u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2: POST\n - \u03a4\u03cd\u03c0\u03bf\u03c2 \u03c0\u03b5\u03c1\u03b9\u03b5\u03c7\u03bf\u03bc\u03ad\u03bd\u03bf\u03c5: \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae/json \n\n \u0394\u03b5\u03af\u03c4\u03b5 [\u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]({docs_url}) \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03c4\u03bf\u03bd \u03c4\u03c1\u03cc\u03c0\u03bf \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b1\u03c5\u03c4\u03bf\u03bc\u03b1\u03c4\u03b9\u03c3\u03bc\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03c7\u03b5\u03af\u03c1\u03b9\u03c3\u03b7 \u03c4\u03c9\u03bd \u03b5\u03b9\u03c3\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03c9\u03bd \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd." diff --git a/homeassistant/components/insteon/translations/el.json b/homeassistant/components/insteon/translations/el.json index fbdb51209a0b68..f26c9cba54c52f 100644 --- a/homeassistant/components/insteon/translations/el.json +++ b/homeassistant/components/insteon/translations/el.json @@ -1,6 +1,11 @@ { "config": { + "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "select_single": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae." }, "step": { @@ -23,6 +28,9 @@ "title": "Insteon Hub Version 2" }, "plm": { + "data": { + "device": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 USB" + }, "description": "\u0394\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf \u03bc\u03cc\u03bd\u03c4\u03b5\u03bc Insteon PowerLink (PLM).", "title": "Insteon PLM" }, @@ -37,6 +45,7 @@ }, "options": { "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "input_error": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b5\u03c2 \u03ba\u03b1\u03c4\u03b1\u03c7\u03c9\u03c1\u03ae\u03c3\u03b5\u03b9\u03c2, \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c4\u03b9\u03bc\u03ad\u03c2 \u03c3\u03b1\u03c2.", "select_single": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae." }, @@ -64,6 +73,7 @@ "data": { "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0391\u03bb\u03bb\u03ac\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 Insteon Hub. \u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Home Assistant \u03bc\u03b5\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03b1\u03b3\u03bc\u03b1\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b1\u03c5\u03c4\u03ae\u03c2 \u03c4\u03b7\u03c2 \u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2. \u0391\u03c5\u03c4\u03cc \u03b4\u03b5\u03bd \u03b1\u03bb\u03bb\u03ac\u03b6\u03b5\u03b9 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03af\u03b4\u03b9\u03bf\u03c5 \u03c4\u03bf\u03c5 Hub. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03bb\u03bb\u03ac\u03be\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c3\u03c4\u03bf Hub \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae Hub.", diff --git a/homeassistant/components/intellifire/translations/el.json b/homeassistant/components/intellifire/translations/el.json index 2ac7bbe8ac4688..c7b88a9b3d8538 100644 --- a/homeassistant/components/intellifire/translations/el.json +++ b/homeassistant/components/intellifire/translations/el.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/ios/translations/el.json b/homeassistant/components/ios/translations/el.json new file mode 100644 index 00000000000000..364238e98a7eb4 --- /dev/null +++ b/homeassistant/components/ios/translations/el.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "step": { + "confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iotawatt/translations/el.json b/homeassistant/components/iotawatt/translations/el.json index f25c2c4dcbcaa4..4a47ab1befa082 100644 --- a/homeassistant/components/iotawatt/translations/el.json +++ b/homeassistant/components/iotawatt/translations/el.json @@ -2,6 +2,7 @@ "config": { "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "unknown": "\u0391\u03bd\u03b5\u03c0\u03ac\u03bd\u03c4\u03b5\u03c7\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { diff --git a/homeassistant/components/ipma/translations/el.json b/homeassistant/components/ipma/translations/el.json index 932be8297f19dd..86b71fc8c92ae7 100644 --- a/homeassistant/components/ipma/translations/el.json +++ b/homeassistant/components/ipma/translations/el.json @@ -5,7 +5,14 @@ }, "step": { "user": { - "description": "Instituto Portugu\u00eas do Mar e Atmosfera" + "data": { + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2", + "mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + }, + "description": "Instituto Portugu\u00eas do Mar e Atmosfera", + "title": "\u03a4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1" } } }, diff --git a/homeassistant/components/ipp/translations/el.json b/homeassistant/components/ipp/translations/el.json index 82ab5db44526ae..ec29a112b6119e 100644 --- a/homeassistant/components/ipp/translations/el.json +++ b/homeassistant/components/ipp/translations/el.json @@ -1,6 +1,8 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "connection_upgrade": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae \u03b5\u03c0\u03b5\u03b9\u03b4\u03ae \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03bd\u03b1\u03b2\u03ac\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2.", "ipp_error": "\u03a0\u03b1\u03c1\u03bf\u03c5\u03c3\u03b9\u03ac\u03c3\u03c4\u03b7\u03ba\u03b5 \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 IPP.", "ipp_version_error": "\u0397 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 IPP \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae.", @@ -8,6 +10,7 @@ "unique_id_required": "\u039b\u03b5\u03af\u03c0\u03b5\u03b9 \u03b7 \u03bc\u03bf\u03bd\u03b1\u03b4\u03b9\u03ba\u03ae \u03b1\u03bd\u03b1\u03b3\u03bd\u03ce\u03c1\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03c0\u03bf\u03c5 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7." }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "connection_upgrade": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae. \u0394\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03bc\u03b5 \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03c4\u03b7\u03bd \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae SSL/TLS." }, "flow_title": "{name}", @@ -16,7 +19,9 @@ "data": { "base_path": "\u03a3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ae \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae", "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", - "port": "\u0398\u03cd\u03c1\u03b1" + "port": "\u0398\u03cd\u03c1\u03b1", + "ssl": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03ad\u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL", + "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL" }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae \u03c3\u03b1\u03c2 \u03bc\u03ad\u03c3\u03c9 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03c9\u03c4\u03bf\u03ba\u03cc\u03bb\u03bb\u03bf\u03c5 \u03b5\u03ba\u03c4\u03cd\u03c0\u03c9\u03c3\u03b7\u03c2 Internet (IPP) \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Home Assistant.", "title": "\u03a3\u03c5\u03bd\u03b4\u03ad\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae \u03c3\u03b1\u03c2" diff --git a/homeassistant/components/iqvia/translations/el.json b/homeassistant/components/iqvia/translations/el.json index baf8fbcbba4f3e..4a3045201e2787 100644 --- a/homeassistant/components/iqvia/translations/el.json +++ b/homeassistant/components/iqvia/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, "error": { "invalid_zip_code": "\u039f \u03c4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b9\u03ba\u03cc\u03c2 \u03ba\u03ce\u03b4\u03b9\u03ba\u03b1\u03c2 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2" }, diff --git a/homeassistant/components/iss/translations/el.json b/homeassistant/components/iss/translations/el.json index c260b24ceeb96a..b662dbea64c5f8 100644 --- a/homeassistant/components/iss/translations/el.json +++ b/homeassistant/components/iss/translations/el.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "latitude_longitude_not_defined": "\u03a4\u03bf \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2 \u03ba\u03b1\u03b9 \u03c4\u03bf \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2 \u03b4\u03b5\u03bd \u03bf\u03c1\u03af\u03b6\u03bf\u03bd\u03c4\u03b1\u03b9 \u03c3\u03c4\u03bf Home Assistant." + "latitude_longitude_not_defined": "\u03a4\u03bf \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2 \u03ba\u03b1\u03b9 \u03c4\u03bf \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2 \u03b4\u03b5\u03bd \u03bf\u03c1\u03af\u03b6\u03bf\u03bd\u03c4\u03b1\u03b9 \u03c3\u03c4\u03bf Home Assistant.", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, "step": { "user": { diff --git a/homeassistant/components/iss/translations/nl.json b/homeassistant/components/iss/translations/nl.json index e2ed58741bdb4d..c3e7ade05d995b 100644 --- a/homeassistant/components/iss/translations/nl.json +++ b/homeassistant/components/iss/translations/nl.json @@ -12,5 +12,14 @@ "description": "Wilt u het International Space Station configureren?" } } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "Toon op kaart" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/isy994/translations/el.json b/homeassistant/components/isy994/translations/el.json index e9b7350a2fe1c3..470975e2117672 100644 --- a/homeassistant/components/isy994/translations/el.json +++ b/homeassistant/components/isy994/translations/el.json @@ -1,14 +1,22 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { - "invalid_host": "\u0397 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03b4\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03c3\u03b5 \u03c0\u03bb\u03ae\u03c1\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae URL, \u03c0.\u03c7. http://192.168.10.100:80" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "v", + "invalid_host": "\u0397 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03b4\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03c3\u03b5 \u03c0\u03bb\u03ae\u03c1\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae URL, \u03c0.\u03c7. http://192.168.10.100:80", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "flow_title": "{name} ({host})", "step": { "user": { "data": { + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "tls": "\u0397 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 TLS \u03c4\u03bf\u03c5 \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ae ISY." + "tls": "\u0397 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 TLS \u03c4\u03bf\u03c5 \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ae ISY.", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0397 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03b5 \u03c0\u03bb\u03ae\u03c1\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae URL, \u03c0.\u03c7. http://192.168.10.100:80", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf ISY994" diff --git a/homeassistant/components/izone/translations/el.json b/homeassistant/components/izone/translations/el.json index eeceb373ce8729..341001a0898dd0 100644 --- a/homeassistant/components/izone/translations/el.json +++ b/homeassistant/components/izone/translations/el.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, "step": { "confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf iZone;" diff --git a/homeassistant/components/jellyfin/translations/el.json b/homeassistant/components/jellyfin/translations/el.json index 118803f915bcd0..415cc4256f33c6 100644 --- a/homeassistant/components/jellyfin/translations/el.json +++ b/homeassistant/components/jellyfin/translations/el.json @@ -1,12 +1,18 @@ { "config": { + "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } diff --git a/homeassistant/components/juicenet/translations/el.json b/homeassistant/components/juicenet/translations/el.json index 472765dfba5b85..2ca8a3c7cfa924 100644 --- a/homeassistant/components/juicenet/translations/el.json +++ b/homeassistant/components/juicenet/translations/el.json @@ -1,7 +1,18 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { + "data": { + "api_token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API" + }, "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API \u03b1\u03c0\u03cc https://home.juice.net/Manage.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf JuiceNet" } diff --git a/homeassistant/components/keenetic_ndms2/translations/el.json b/homeassistant/components/keenetic_ndms2/translations/el.json index 071014416e3175..1f8a5b838f3f78 100644 --- a/homeassistant/components/keenetic_ndms2/translations/el.json +++ b/homeassistant/components/keenetic_ndms2/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "no_udn": "\u039f\u03b9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7\u03c2 SSDP \u03b4\u03b5\u03bd \u03ad\u03c7\u03bf\u03c5\u03bd UDN", "not_keenetic_ndms2": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae\u03c2 Keenetic" }, diff --git a/homeassistant/components/kmtronic/translations/el.json b/homeassistant/components/kmtronic/translations/el.json index e17dece727749a..6f186c16e3bf4d 100644 --- a/homeassistant/components/kmtronic/translations/el.json +++ b/homeassistant/components/kmtronic/translations/el.json @@ -1,7 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/knx/translations/el.json b/homeassistant/components/knx/translations/el.json index 59687ef8ad6558..75be33560ca4bb 100644 --- a/homeassistant/components/knx/translations/el.json +++ b/homeassistant/components/knx/translations/el.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, diff --git a/homeassistant/components/kodi/translations/el.json b/homeassistant/components/kodi/translations/el.json index 0b9c8c36f08f95..e0a3118ee05fa2 100644 --- a/homeassistant/components/kodi/translations/el.json +++ b/homeassistant/components/kodi/translations/el.json @@ -1,5 +1,17 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "no_uuid": "\u03a4\u03bf Kodi \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03bc\u03bf\u03bd\u03b1\u03b4\u03b9\u03ba\u03cc \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc. \u0391\u03c5\u03c4\u03cc \u03c0\u03b9\u03b8\u03b1\u03bd\u03cc\u03c4\u03b1\u03c4\u03b1 \u03bf\u03c6\u03b5\u03af\u03bb\u03b5\u03c4\u03b1\u03b9 \u03c3\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03b1\u03bb\u03b9\u03ac \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 \u03c4\u03bf\u03c5 Kodi (17.x \u03ae \u03bc\u03b9\u03ba\u03c1\u03cc\u03c4\u03b5\u03c1\u03b7). \u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03bc\u03b5 \u03bc\u03b7 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03bf \u03c4\u03c1\u03cc\u03c0\u03bf \u03ae \u03bd\u03b1 \u03b1\u03bd\u03b1\u03b2\u03b1\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c3\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03b9\u03bf \u03c0\u03c1\u03cc\u03c3\u03c6\u03b1\u03c4\u03b7 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 Kodi.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "flow_title": "Kodi: {\u03cc\u03bd\u03bf\u03bc\u03b1}", "step": { "credentials": { @@ -16,6 +28,7 @@ "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1", "ssl": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03ad\u03c3\u03c9 SSL" }, "description": "\u03a0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 Kodi. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b2\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03ad\u03c7\u03b5\u03c4\u03b5 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9 \u03c4\u03bf \"\u039d\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03b5\u03c4\u03b1\u03b9 \u03bf \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03bf\u03c5 Kodi \u03bc\u03ad\u03c3\u03c9 HTTP\" \u03c3\u03c4\u03bf \u03a3\u03cd\u03c3\u03c4\u03b7\u03bc\u03b1/\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2/\u0394\u03af\u03ba\u03c4\u03c5\u03bf/\u03a5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b5\u03c2." diff --git a/homeassistant/components/konnected/translations/el.json b/homeassistant/components/konnected/translations/el.json index df3f20ee792e64..b75982d16fa6bc 100644 --- a/homeassistant/components/konnected/translations/el.json +++ b/homeassistant/components/konnected/translations/el.json @@ -1,8 +1,14 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", - "not_konn_panel": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Konnected.io" + "not_konn_panel": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Konnected.io", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "step": { "confirm": { @@ -15,6 +21,7 @@ }, "user": { "data": { + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", "port": "\u0398\u03cd\u03c1\u03b1" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03b3\u03b9\u03b1 \u03c4\u03bf Konnected Panel \u03c3\u03b1\u03c2." diff --git a/homeassistant/components/kostal_plenticore/translations/el.json b/homeassistant/components/kostal_plenticore/translations/el.json index 0dd1218ec1ae25..518070a76ef12b 100644 --- a/homeassistant/components/kostal_plenticore/translations/el.json +++ b/homeassistant/components/kostal_plenticore/translations/el.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { diff --git a/homeassistant/components/kraken/translations/el.json b/homeassistant/components/kraken/translations/el.json index c369993fa0dd08..252d1a5ebd2a8c 100644 --- a/homeassistant/components/kraken/translations/el.json +++ b/homeassistant/components/kraken/translations/el.json @@ -1,4 +1,14 @@ { + "config": { + "abort": { + "already_configured": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "step": { + "user": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;" + } + } + }, "options": { "step": { "init": { diff --git a/homeassistant/components/kulersky/translations/el.json b/homeassistant/components/kulersky/translations/el.json new file mode 100644 index 00000000000000..a13912159002b3 --- /dev/null +++ b/homeassistant/components/kulersky/translations/el.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "step": { + "confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/launch_library/translations/el.json b/homeassistant/components/launch_library/translations/el.json index 8a25e8b76a05e3..757cb3baee9d96 100644 --- a/homeassistant/components/launch_library/translations/el.json +++ b/homeassistant/components/launch_library/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, "step": { "user": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u0392\u03b9\u03b2\u03bb\u03b9\u03bf\u03b8\u03ae\u03ba\u03b7 \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7\u03c2;" diff --git a/homeassistant/components/lcn/translations/el.json b/homeassistant/components/lcn/translations/el.json index f5d8a4d53a0e6d..ae71f96d3617fe 100644 --- a/homeassistant/components/lcn/translations/el.json +++ b/homeassistant/components/lcn/translations/el.json @@ -1,7 +1,10 @@ { "device_automation": { "trigger_type": { - "fingerprint": "\u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b4\u03b1\u03ba\u03c4\u03c5\u03bb\u03b9\u03ba\u03bf\u03cd \u03b1\u03c0\u03bf\u03c4\u03c5\u03c0\u03ce\u03bc\u03b1\u03c4\u03bf\u03c2 \u03b5\u03bb\u03ae\u03c6\u03b8\u03b7" + "fingerprint": "\u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b4\u03b1\u03ba\u03c4\u03c5\u03bb\u03b9\u03ba\u03bf\u03cd \u03b1\u03c0\u03bf\u03c4\u03c5\u03c0\u03ce\u03bc\u03b1\u03c4\u03bf\u03c2 \u03b5\u03bb\u03ae\u03c6\u03b8\u03b7", + "send_keys": "\u03b1\u03c0\u03bf\u03c3\u03c4\u03bf\u03bb\u03ae \u03ba\u03bf\u03c5\u03bc\u03c0\u03b9\u03ce\u03bd \u03b5\u03bb\u03ae\u03c6\u03b8\u03b7", + "transmitter": "\u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03bf\u03bc\u03c0\u03bf\u03cd \u03b5\u03bb\u03ae\u03c6\u03b8\u03b7", + "transponder": "\u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b1\u03bd\u03b1\u03bc\u03b5\u03c4\u03b1\u03b4\u03cc\u03c4\u03b7 \u03b5\u03bb\u03ae\u03c6\u03b8\u03b7" } } } \ No newline at end of file diff --git a/homeassistant/components/life360/translations/el.json b/homeassistant/components/life360/translations/el.json index d49ed2e70d5b59..07106d89d63c4e 100644 --- a/homeassistant/components/life360/translations/el.json +++ b/homeassistant/components/life360/translations/el.json @@ -1,10 +1,17 @@ { "config": { + "abort": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "create_entry": { "default": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03bf\u03c1\u03af\u03c3\u03b5\u03c4\u03b5 \u03c0\u03c1\u03bf\u03b7\u03b3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2, \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7 Life360]({docs_url})." }, "error": { - "invalid_username": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "invalid_username": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/lifx/translations/el.json b/homeassistant/components/lifx/translations/el.json index f8853cd2d4716f..5a1d7707f5586d 100644 --- a/homeassistant/components/lifx/translations/el.json +++ b/homeassistant/components/lifx/translations/el.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, "step": { "confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf LIFX;" diff --git a/homeassistant/components/litejet/translations/el.json b/homeassistant/components/litejet/translations/el.json index 929bcbbc24841d..49112fc0c20cb1 100644 --- a/homeassistant/components/litejet/translations/el.json +++ b/homeassistant/components/litejet/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, "error": { "open_failed": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc \u03c4\u03bf \u03ac\u03bd\u03bf\u03b9\u03b3\u03bc\u03b1 \u03c4\u03b7\u03c2 \u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7\u03c2 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae\u03c2 \u03b8\u03cd\u03c1\u03b1\u03c2." }, diff --git a/homeassistant/components/litterrobot/translations/el.json b/homeassistant/components/litterrobot/translations/el.json index 118803f915bcd0..cdc7ae85736f17 100644 --- a/homeassistant/components/litterrobot/translations/el.json +++ b/homeassistant/components/litterrobot/translations/el.json @@ -1,7 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/local_ip/translations/el.json b/homeassistant/components/local_ip/translations/el.json index 8024f3e962d4d8..c33fa936300ca8 100644 --- a/homeassistant/components/local_ip/translations/el.json +++ b/homeassistant/components/local_ip/translations/el.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, "step": { "user": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;", "title": "\u03a4\u03bf\u03c0\u03b9\u03ba\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" } } diff --git a/homeassistant/components/locative/translations/el.json b/homeassistant/components/locative/translations/el.json index ee01e31d21c5a0..0bfb57d149f3a1 100644 --- a/homeassistant/components/locative/translations/el.json +++ b/homeassistant/components/locative/translations/el.json @@ -2,13 +2,15 @@ "config": { "abort": { "cloud_not_connected": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf Home Assistant Cloud.", - "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", + "webhook_not_internet_accessible": "\u0397 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1 \u03c4\u03bf\u03c5 Home Assistant \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c3\u03b2\u03ac\u03c3\u03b9\u03bc\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf \u03b4\u03b9\u03b1\u03b4\u03af\u03ba\u03c4\u03c5\u03bf \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03b5\u03b9 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03b1 webhook." }, "create_entry": { "default": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c4\u03b5\u03af\u03bb\u03b5\u03c4\u03b5 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b5\u03c2 \u03c3\u03c4\u03bf Home Assistant, \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 webhook \u03c3\u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae Locative.\n\n\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b1\u03ba\u03cc\u03bb\u03bf\u03c5\u03b8\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2:\n\n- URL: `{webhook_url}`\n- \u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2: \n\n\u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]({docs_url}) \u03b3\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2." }, "step": { "user": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Locative Webhook" } } diff --git a/homeassistant/components/logi_circle/translations/el.json b/homeassistant/components/logi_circle/translations/el.json index 3f9ee36865e476..ad26536d10b580 100644 --- a/homeassistant/components/logi_circle/translations/el.json +++ b/homeassistant/components/logi_circle/translations/el.json @@ -1,11 +1,15 @@ { "config": { "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "external_error": "\u03a0\u03c1\u03bf\u03ad\u03ba\u03c5\u03c8\u03b5 \u03b5\u03be\u03b1\u03af\u03c1\u03b5\u03c3\u03b7 \u03b1\u03c0\u03cc \u03ac\u03bb\u03bb\u03b7 \u03c1\u03bf\u03ae.", - "external_setup": "\u03a4\u03bf Logi Circle \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03b8\u03b7\u03ba\u03b5 \u03bc\u03b5 \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03af\u03b1 \u03b1\u03c0\u03cc \u03ac\u03bb\u03bb\u03b7 \u03c1\u03bf\u03ae." + "external_setup": "\u03a4\u03bf Logi Circle \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03b8\u03b7\u03ba\u03b5 \u03bc\u03b5 \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03af\u03b1 \u03b1\u03c0\u03cc \u03ac\u03bb\u03bb\u03b7 \u03c1\u03bf\u03ae.", + "missing_configuration": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7." }, "error": { - "follow_link": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf \u03ba\u03b1\u03b9 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af\u03c4\u03b5 \u03c0\u03c1\u03b9\u03bd \u03c0\u03b1\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03a5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae." + "authorize_url_timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2.", + "follow_link": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf \u03ba\u03b1\u03b9 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af\u03c4\u03b5 \u03c0\u03c1\u03b9\u03bd \u03c0\u03b1\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03a5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae.", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "step": { "auth": { diff --git a/homeassistant/components/lookin/translations/el.json b/homeassistant/components/lookin/translations/el.json index 9c4482a2e00991..1b348109b8d0ed 100644 --- a/homeassistant/components/lookin/translations/el.json +++ b/homeassistant/components/lookin/translations/el.json @@ -1,7 +1,15 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf" + }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "flow_title": "{name} ({host})", "step": { diff --git a/homeassistant/components/luftdaten/translations/el.json b/homeassistant/components/luftdaten/translations/el.json index e5fc7179f865fc..59519c251ef2ae 100644 --- a/homeassistant/components/luftdaten/translations/el.json +++ b/homeassistant/components/luftdaten/translations/el.json @@ -1,6 +1,8 @@ { "config": { "error": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "invalid_sensor": "\u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03bc\u03b7 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03bf\u03c2 \u03ae \u03ac\u03ba\u03c5\u03c1\u03bf\u03c2" }, "step": { diff --git a/homeassistant/components/lutron_caseta/translations/el.json b/homeassistant/components/lutron_caseta/translations/el.json index 63db1d8237c17a..f0c1ec35450b22 100644 --- a/homeassistant/components/lutron_caseta/translations/el.json +++ b/homeassistant/components/lutron_caseta/translations/el.json @@ -1,8 +1,13 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "not_lutron_device": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Lutron" }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "flow_title": "{name} ({host})", "step": { "import_failed": { diff --git a/homeassistant/components/lyric/translations/el.json b/homeassistant/components/lyric/translations/el.json index f238d2952cbfea..e9321950874078 100644 --- a/homeassistant/components/lyric/translations/el.json +++ b/homeassistant/components/lyric/translations/el.json @@ -1,8 +1,20 @@ { "config": { + "abort": { + "authorize_url_timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2.", + "missing_configuration": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7.", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "create_entry": { + "default": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "step": { + "pick_implementation": { + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03bc\u03b5\u03b8\u03cc\u03b4\u03bf\u03c5 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "reauth_confirm": { - "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Lyric \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2." + "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Lyric \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2.", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" } } } diff --git a/homeassistant/components/mailgun/translations/el.json b/homeassistant/components/mailgun/translations/el.json index 263ce28f293910..bc2821c0881999 100644 --- a/homeassistant/components/mailgun/translations/el.json +++ b/homeassistant/components/mailgun/translations/el.json @@ -2,7 +2,8 @@ "config": { "abort": { "cloud_not_connected": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf Home Assistant Cloud.", - "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", + "webhook_not_internet_accessible": "\u0397 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1 \u03c4\u03bf\u03c5 Home Assistant \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c3\u03b2\u03ac\u03c3\u03b9\u03bc\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf \u03b4\u03b9\u03b1\u03b4\u03af\u03ba\u03c4\u03c5\u03bf \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03b5\u03b9 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03b1 webhook." }, "create_entry": { "default": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c4\u03b5\u03af\u03bb\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03b1 \u03c3\u03c4\u03bf\u03bd Home Assistant, \u03b8\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf [Webhooks with Mailgun]({mailgun_url}). \n\n \u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2: \n\n - URL: `{webhook_url}`\n - \u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2: POST\n - \u03a4\u03cd\u03c0\u03bf\u03c2 \u03c0\u03b5\u03c1\u03b9\u03b5\u03c7\u03bf\u03bc\u03ad\u03bd\u03bf\u03c5: \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae/json \n\n \u0394\u03b5\u03af\u03c4\u03b5 [\u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]({docs_url}) \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03c4\u03bf\u03bd \u03c4\u03c1\u03cc\u03c0\u03bf \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b1\u03c5\u03c4\u03bf\u03bc\u03b1\u03c4\u03b9\u03c3\u03bc\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03c7\u03b5\u03af\u03c1\u03b9\u03c3\u03b7 \u03c4\u03c9\u03bd \u03b5\u03b9\u03c3\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03c9\u03bd \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd." diff --git a/homeassistant/components/mazda/translations/el.json b/homeassistant/components/mazda/translations/el.json index 80e09569cf52da..c95e8ad474779f 100644 --- a/homeassistant/components/mazda/translations/el.json +++ b/homeassistant/components/mazda/translations/el.json @@ -1,8 +1,13 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, "error": { "account_locked": "\u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ba\u03bb\u03b5\u03b9\u03b4\u03c9\u03bc\u03ad\u03bd\u03bf\u03c2. \u03a0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1.", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "unknown": "\u039c\u03b7 \u03b1\u03bd\u03b1\u03bc\u03b5\u03bd\u03cc\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { diff --git a/homeassistant/components/melcloud/translations/el.json b/homeassistant/components/melcloud/translations/el.json index d6b6c51e6f53c9..ddd40e10d825a3 100644 --- a/homeassistant/components/melcloud/translations/el.json +++ b/homeassistant/components/melcloud/translations/el.json @@ -3,6 +3,11 @@ "abort": { "already_configured": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 MELCloud \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf email. \u03a4\u03bf \u03ba\u03bf\u03c5\u03c0\u03cc\u03bd\u03b9 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03b1\u03bd\u03b1\u03bd\u03b5\u03c9\u03b8\u03b5\u03af." }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/met/translations/el.json b/homeassistant/components/met/translations/el.json index 6759159b1db517..9c0d532f298640 100644 --- a/homeassistant/components/met/translations/el.json +++ b/homeassistant/components/met/translations/el.json @@ -3,9 +3,19 @@ "abort": { "no_home": "\u0394\u03b5\u03bd \u03ad\u03c7\u03bf\u03c5\u03bd \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03c3\u03c5\u03bd\u03c4\u03b5\u03c4\u03b1\u03b3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03ba\u03b1\u03c4\u03bf\u03b9\u03ba\u03af\u03b1\u03c2 \u03c3\u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd \u03c4\u03bf\u03c5 Home Assistant" }, + "error": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, "step": { "user": { - "description": "Meteorologisk institutt" + "data": { + "elevation": "\u0391\u03bd\u03cd\u03c8\u03c9\u03c3\u03b7", + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + }, + "description": "Meteorologisk institutt", + "title": "\u03a4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1" } } } diff --git a/homeassistant/components/met_eireann/translations/el.json b/homeassistant/components/met_eireann/translations/el.json index 7e37a1c1f9d30c..7d30d65b9181f3 100644 --- a/homeassistant/components/met_eireann/translations/el.json +++ b/homeassistant/components/met_eireann/translations/el.json @@ -1,11 +1,18 @@ { "config": { + "error": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, "step": { "user": { "data": { + "elevation": "\u0391\u03bd\u03cd\u03c8\u03c9\u03c3\u03b7", + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1" }, - "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd \u03b1\u03c0\u03cc \u03c4\u03bf Met \u00c9ireann Public Weather Forecast API" + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd \u03b1\u03c0\u03cc \u03c4\u03bf Met \u00c9ireann Public Weather Forecast API", + "title": "\u03a4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1" } } } diff --git a/homeassistant/components/meteo_france/translations/el.json b/homeassistant/components/meteo_france/translations/el.json index 330cd15fb59212..3dfad5a493df0c 100644 --- a/homeassistant/components/meteo_france/translations/el.json +++ b/homeassistant/components/meteo_france/translations/el.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "error": { "empty": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03b1\u03c0\u03bf\u03c4\u03ad\u03bb\u03b5\u03c3\u03bc\u03b1 \u03c3\u03c4\u03b7\u03bd \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7 \u03c0\u03cc\u03bb\u03b7\u03c2: \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b5\u03b4\u03af\u03bf \u03c0\u03cc\u03bb\u03b7\u03c2" }, diff --git a/homeassistant/components/meteoclimatic/translations/el.json b/homeassistant/components/meteoclimatic/translations/el.json index cf201a68523344..085e6fe1643180 100644 --- a/homeassistant/components/meteoclimatic/translations/el.json +++ b/homeassistant/components/meteoclimatic/translations/el.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "error": { + "not_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/metoffice/translations/el.json b/homeassistant/components/metoffice/translations/el.json index 61ccae30de9b8c..9170cdf481f3d8 100644 --- a/homeassistant/components/metoffice/translations/el.json +++ b/homeassistant/components/metoffice/translations/el.json @@ -1,7 +1,19 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2" + }, "description": "\u03a4\u03bf \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2 \u03ba\u03b1\u03b9 \u03c4\u03bf \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2 \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03bf\u03cd\u03bd \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03bb\u03b7\u03c3\u03b9\u03ad\u03c3\u03c4\u03b5\u03c1\u03bf\u03c5 \u03bc\u03b5\u03c4\u03b5\u03c9\u03c1\u03bf\u03bb\u03bf\u03b3\u03b9\u03ba\u03bf\u03cd \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd.", "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03bc\u03b5 \u03c4\u03bf UK Met Office" } diff --git a/homeassistant/components/mikrotik/translations/el.json b/homeassistant/components/mikrotik/translations/el.json index 2a94c2fa87da6b..4faca6d756e2be 100644 --- a/homeassistant/components/mikrotik/translations/el.json +++ b/homeassistant/components/mikrotik/translations/el.json @@ -1,11 +1,18 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "name_exists": "\u03a4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9" }, "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "port": "\u0398\u03cd\u03c1\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", diff --git a/homeassistant/components/mill/translations/el.json b/homeassistant/components/mill/translations/el.json index 7b04113d7a2336..18743aada0a21a 100644 --- a/homeassistant/components/mill/translations/el.json +++ b/homeassistant/components/mill/translations/el.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "cloud": { "data": { @@ -16,7 +22,8 @@ "user": { "data": { "connection_type": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c4\u03cd\u03c0\u03bf\u03c5 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03cd\u03c0\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2. \u0397 \u03c4\u03bf\u03c0\u03b9\u03ba\u03ae \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03b8\u03b5\u03c1\u03bc\u03b1\u03bd\u03c4\u03ae\u03c1\u03b5\u03c2 3\u03b7\u03c2 \u03b3\u03b5\u03bd\u03b9\u03ac\u03c2" } diff --git a/homeassistant/components/minecraft_server/translations/el.json b/homeassistant/components/minecraft_server/translations/el.json index d6315fef3d1a65..26e755cad5bf45 100644 --- a/homeassistant/components/minecraft_server/translations/el.json +++ b/homeassistant/components/minecraft_server/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, "error": { "cannot_connect": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03c3\u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03b8\u03cd\u03c1\u03b1 \u03ba\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac. \u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03b5\u03c0\u03af\u03c3\u03b7\u03c2 \u03cc\u03c4\u03b9 \u03b5\u03ba\u03c4\u03b5\u03bb\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf\u03c5\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf\u03bd \u03c4\u03b7\u03bd \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 1.7 \u03c4\u03bf\u03c5 Minecraft \u03c3\u03c4\u03bf\u03bd \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae \u03c3\u03b1\u03c2.", "invalid_ip": "\u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03b5\u03af\u03bd\u03b1\u03b9 \u03ac\u03ba\u03c5\u03c1\u03b7 (\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 MAC \u03b4\u03b5\u03bd \u03bc\u03c0\u03cc\u03c1\u03b5\u03c3\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b4\u03b9\u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af). \u0394\u03b9\u03bf\u03c1\u03b8\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", @@ -8,7 +11,8 @@ "step": { "user": { "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae Minecraft \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03b5\u03c4\u03b1\u03b9 \u03b7 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae Minecraft" diff --git a/homeassistant/components/mjpeg/translations/el.json b/homeassistant/components/mjpeg/translations/el.json index 2dbff2ebe2cc96..5660d4fcf6c5e5 100644 --- a/homeassistant/components/mjpeg/translations/el.json +++ b/homeassistant/components/mjpeg/translations/el.json @@ -1,28 +1,40 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "step": { "user": { "data": { "mjpeg_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 MJPEG URL", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "still_image_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae\u03c2 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1\u03c2" + "still_image_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae\u03c2 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", + "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL" } } } }, "options": { "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "step": { "init": { "data": { "mjpeg_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 MJPEG URL", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "still_image_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae\u03c2 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1\u03c2" + "still_image_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae\u03c2 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", + "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL" } } } diff --git a/homeassistant/components/mjpeg/translations/nl.json b/homeassistant/components/mjpeg/translations/nl.json new file mode 100644 index 00000000000000..111756af3e337a --- /dev/null +++ b/homeassistant/components/mjpeg/translations/nl.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie" + }, + "step": { + "user": { + "data": { + "mjpeg_url": "MJPEG URL", + "name": "Naam", + "password": "Wachtwoord", + "still_image_url": "Stilstaand beeld URL", + "username": "Gebruikersnaam", + "verify_ssl": "SSL-certificaat verifi\u00ebren" + } + } + } + }, + "options": { + "error": { + "already_configured": "Apparaat is al geconfigureerd", + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie" + }, + "step": { + "init": { + "data": { + "mjpeg_url": "MJPEG URL", + "name": "Naam", + "password": "Wachtwoord", + "still_image_url": "Stilstaand beeld URL", + "username": "Gebruikersnaam", + "verify_ssl": "SSL-certificaat verifi\u00ebren" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/modem_callerid/translations/el.json b/homeassistant/components/modem_callerid/translations/el.json index 3abe8e270afeb1..6d3961cc258d5d 100644 --- a/homeassistant/components/modem_callerid/translations/el.json +++ b/homeassistant/components/modem_callerid/translations/el.json @@ -1,8 +1,13 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03ac\u03bb\u03bb\u03b5\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2" }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "usb_confirm": { "description": "\u03a0\u03c1\u03cc\u03ba\u03b5\u03b9\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03ba\u03bb\u03ae\u03c3\u03b5\u03b9\u03c2 \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae\u03c2 \u03c4\u03b7\u03bb\u03b5\u03c6\u03c9\u03bd\u03af\u03b1\u03c2 \u03bc\u03b5 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c6\u03c9\u03bd\u03b7\u03c4\u03b9\u03ba\u03bf\u03cd \u03bc\u03cc\u03bd\u03c4\u03b5\u03bc CX93001. \u0391\u03c5\u03c4\u03cc \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b1\u03bd\u03b1\u03ba\u03c4\u03ae\u03c3\u03b5\u03b9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ba\u03b1\u03bb\u03bf\u03cd\u03bd\u03c4\u03bf\u03c2 \u03bc\u03b5 \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b1\u03c0\u03cc\u03c1\u03c1\u03b9\u03c8\u03b7\u03c2 \u03bc\u03b9\u03b1\u03c2 \u03b5\u03b9\u03c3\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03b7\u03c2 \u03ba\u03bb\u03ae\u03c3\u03b7\u03c2.", diff --git a/homeassistant/components/modern_forms/translations/el.json b/homeassistant/components/modern_forms/translations/el.json index 44941e91084b53..b8cb77ffa99798 100644 --- a/homeassistant/components/modern_forms/translations/el.json +++ b/homeassistant/components/modern_forms/translations/el.json @@ -1,7 +1,17 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "flow_title": "{name}", "step": { + "confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;" + }, "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/el.json b/homeassistant/components/moehlenhoff_alpha2/translations/el.json index 7750317d34dc43..d1e9ebe7f198aa 100644 --- a/homeassistant/components/moehlenhoff_alpha2/translations/el.json +++ b/homeassistant/components/moehlenhoff_alpha2/translations/el.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/monoprice/translations/el.json b/homeassistant/components/monoprice/translations/el.json index 57a99233d13757..d1a0acdec26437 100644 --- a/homeassistant/components/monoprice/translations/el.json +++ b/homeassistant/components/monoprice/translations/el.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/moon/translations/sensor.el.json b/homeassistant/components/moon/translations/sensor.el.json index 7fc549d5a9a224..5f704f70850624 100644 --- a/homeassistant/components/moon/translations/sensor.el.json +++ b/homeassistant/components/moon/translations/sensor.el.json @@ -1,7 +1,9 @@ { "state": { "moon__phase": { + "first_quarter": "\u03a0\u03c1\u03ce\u03c4\u03bf \u03c4\u03ad\u03c4\u03b1\u03c1\u03c4\u03bf", "full_moon": "\u03a0\u03b1\u03bd\u03c3\u03ad\u03bb\u03b7\u03bd\u03bf\u03c2", + "last_quarter": "\u03a4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03bf \u03c4\u03ad\u03c4\u03b1\u03c1\u03c4\u03bf", "new_moon": "\u039d\u03ad\u03b1 \u03a3\u03b5\u03bb\u03ae\u03bd\u03b7" } } diff --git a/homeassistant/components/motion_blinds/translations/el.json b/homeassistant/components/motion_blinds/translations/el.json index 4271012e41ca36..2914382e820993 100644 --- a/homeassistant/components/motion_blinds/translations/el.json +++ b/homeassistant/components/motion_blinds/translations/el.json @@ -1,12 +1,19 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", + "connection_error": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "error": { "discovery_error": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03c0\u03cd\u03bb\u03b7 \u03ba\u03af\u03bd\u03b7\u03c3\u03b7\u03c2", "invalid_interface": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03c0\u03b1\u03c6\u03ae \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5" }, + "flow_title": "Motion Blinds", "step": { "connect": { "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", "interface": "\u0397 \u03b4\u03b9\u03b1\u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af" }, "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API 16 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03c9\u03bd, \u03b4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key \u03b3\u03b9\u03b1 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2.", @@ -21,6 +28,7 @@ }, "user": { "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" }, "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf Motion Gateway, \u03b5\u03ac\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b7 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7", diff --git a/homeassistant/components/motioneye/translations/el.json b/homeassistant/components/motioneye/translations/el.json index 5f47bf22e5e8db..d65df6be671fa0 100644 --- a/homeassistant/components/motioneye/translations/el.json +++ b/homeassistant/components/motioneye/translations/el.json @@ -1,7 +1,14 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, "error": { - "invalid_url": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "invalid_url": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "hassio_confirm": { @@ -13,7 +20,8 @@ "admin_password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b4\u03b9\u03b1\u03c7\u03b5\u03b9\u03c1\u03b9\u03c3\u03c4\u03ae", "admin_username": "\u0394\u03b9\u03b1\u03c7\u03b5\u03b9\u03c1\u03b9\u03c3\u03c4\u03ae\u03c2 \u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", "surveillance_password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03c0\u03b9\u03c4\u03ae\u03c1\u03b7\u03c3\u03b7\u03c2", - "surveillance_username": "\u0395\u03c0\u03b9\u03c4\u03ae\u03c1\u03b7\u03c3\u03b7 \u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + "surveillance_username": "\u0395\u03c0\u03b9\u03c4\u03ae\u03c1\u03b7\u03c3\u03b7 \u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", + "url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL" } } } diff --git a/homeassistant/components/mqtt/translations/el.json b/homeassistant/components/mqtt/translations/el.json index ffffd249c09420..233b6a995b3861 100644 --- a/homeassistant/components/mqtt/translations/el.json +++ b/homeassistant/components/mqtt/translations/el.json @@ -1,7 +1,11 @@ { "config": { "abort": { - "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03bc\u03ad\u03bd\u03b7" + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03bc\u03ad\u03bd\u03b7", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "step": { "broker": { @@ -46,11 +50,18 @@ } }, "options": { + "error": { + "bad_birth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b8\u03ad\u03bc\u03b1 birth.", + "bad_will": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b8\u03ad\u03bc\u03b1 will.", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "broker": { "data": { "broker": "\u039c\u03b5\u03c3\u03af\u03c4\u03b7\u03c2", - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03bc\u03b5\u03c3\u03af\u03c4\u03b7 MQTT.", "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 broker" @@ -58,7 +69,10 @@ "options": { "data": { "birth_enable": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03bf\u03c2 birth", - "will_enable": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03bf\u03c2 will" + "discovery": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7\u03c2", + "will_enable": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03bf\u03c2 will", + "will_retain": "\u0394\u03b9\u03b1\u03c4\u03ae\u03c1\u03b7\u03c3\u03b7 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03bf\u03c2 will", + "will_topic": "\u0398\u03ad\u03bc\u03b1 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03bf\u03c2 will" }, "description": "\u0391\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7 - \u0395\u03ac\u03bd \u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 (\u03c3\u03c5\u03bd\u03b9\u03c3\u03c4\u03ac\u03c4\u03b1\u03b9), \u03c4\u03bf Home Assistant \u03b8\u03b1 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c8\u03b5\u03b9 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03ba\u03b1\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03c0\u03bf\u03c5 \u03b4\u03b7\u03bc\u03bf\u03c3\u03b9\u03b5\u03cd\u03bf\u03c5\u03bd \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03ae \u03c4\u03bf\u03c5\u03c2 \u03c3\u03c4\u03bf\u03bd \u03bc\u03b5\u03c3\u03af\u03c4\u03b7 MQTT. \u0395\u03ac\u03bd \u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7, \u03cc\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b3\u03af\u03bd\u03bf\u03c5\u03bd \u03c7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b1.\nBirth message (\u039c\u03ae\u03bd\u03c5\u03bc\u03b1 \u03b3\u03ad\u03bd\u03bd\u03b7\u03c3\u03b7\u03c2) - \u03a4\u03bf \u03bc\u03ae\u03bd\u03c5\u03bc\u03b1 \u03b3\u03ad\u03bd\u03bd\u03b7\u03c3\u03b7\u03c2 \u03b8\u03b1 \u03b1\u03c0\u03bf\u03c3\u03c4\u03ad\u03bb\u03bb\u03b5\u03c4\u03b1\u03b9 \u03ba\u03ac\u03b8\u03b5 \u03c6\u03bf\u03c1\u03ac \u03c0\u03bf\u03c5 \u03c4\u03bf Home Assistant (\u03b5\u03c0\u03b1\u03bd\u03b1)\u03c3\u03c5\u03bd\u03b4\u03ad\u03b5\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03bc\u03b5\u03c3\u03af\u03c4\u03b7 MQTT.\nWill message - \u03a4\u03bf \u03bc\u03ae\u03bd\u03c5\u03bc\u03b1 will \u03b8\u03b1 \u03b1\u03c0\u03bf\u03c3\u03c4\u03ad\u03bb\u03bb\u03b5\u03c4\u03b1\u03b9 \u03ba\u03ac\u03b8\u03b5 \u03c6\u03bf\u03c1\u03ac \u03c0\u03bf\u03c5 \u03c4\u03bf Home Assistant \u03c7\u03ac\u03bd\u03b5\u03b9 \u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03ae \u03c4\u03bf\u03c5 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03bc\u03b5\u03c3\u03af\u03c4\u03b7, \u03c4\u03cc\u03c3\u03bf \u03c3\u03b5 \u03c0\u03b5\u03c1\u03af\u03c0\u03c4\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b8\u03b1\u03c1\u03ae\u03c2 (\u03c0.\u03c7. \u03c4\u03b5\u03c1\u03bc\u03b1\u03c4\u03b9\u03c3\u03bc\u03cc\u03c2 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03c4\u03bf\u03c5 Home Assistant) \u03cc\u03c3\u03bf \u03ba\u03b1\u03b9 \u03c3\u03b5 \u03c0\u03b5\u03c1\u03af\u03c0\u03c4\u03c9\u03c3\u03b7 \u03bc\u03b7 \u03ba\u03b1\u03b8\u03b1\u03c1\u03ae\u03c2 (\u03c0.\u03c7. \u03c3\u03c5\u03bd\u03c4\u03c1\u03b9\u03b2\u03ae \u03c4\u03bf\u03c5 Home Assistant \u03ae \u03b1\u03c0\u03ce\u03bb\u03b5\u03b9\u03b1 \u03c4\u03b7\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5) \u03b1\u03c0\u03bf\u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2.", "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 MQTT" diff --git a/homeassistant/components/mqtt/translations/it.json b/homeassistant/components/mqtt/translations/it.json index 0ed8311339f71f..6317fdf61d5c2f 100644 --- a/homeassistant/components/mqtt/translations/it.json +++ b/homeassistant/components/mqtt/translations/it.json @@ -77,8 +77,8 @@ "will_enable": "Abilita il messaggio testamento", "will_payload": "Payload del messaggio testamento", "will_qos": "QoS del messaggio testamento", - "will_retain": "Persistenza del messaggio testamento", - "will_topic": "Argomento del messaggio testamento" + "will_retain": "Persistenza del messaggio will", + "will_topic": "Argomento del messaggio will" }, "description": "Rilevamento: se il rilevamento \u00e8 abilitato (consigliato), Home Assistant rilever\u00e0 automaticamente i dispositivi e le entit\u00e0 che pubblicano la loro configurazione sul broker MQTT. Se il rilevamento \u00e8 disabilitato, tutta la configurazione deve essere eseguita manualmente.\nMessaggio di nascita: il messaggio di nascita verr\u00e0 inviato ogni volta che Home Assistant si (ri)collega al broker MQTT.\nMessaggio testamento: Il messaggio testamento verr\u00e0 inviato ogni volta che Home Assistant perde la connessione al broker, sia in caso di buona (es. arresto di Home Assistant) sia in caso di cattiva (es. Home Assistant in crash o perdita della connessione di rete) disconnessione.", "title": "Opzioni MQTT" diff --git a/homeassistant/components/mutesync/translations/el.json b/homeassistant/components/mutesync/translations/el.json index 1f731e6efbdc1e..8d54f81a6e4df0 100644 --- a/homeassistant/components/mutesync/translations/el.json +++ b/homeassistant/components/mutesync/translations/el.json @@ -1,7 +1,9 @@ { "config": { "error": { - "invalid_auth": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03c3\u03c4\u03bf m\u00fctesync \u03a0\u03c1\u03bf\u03c4\u03b9\u03bc\u03ae\u03c3\u03b5\u03b9\u03c2 > \u0388\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03c3\u03c4\u03bf m\u00fctesync \u03a0\u03c1\u03bf\u03c4\u03b9\u03bc\u03ae\u03c3\u03b5\u03b9\u03c2 > \u0388\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/myq/translations/el.json b/homeassistant/components/myq/translations/el.json index e0ad2d03cff68e..25805020ed85be 100644 --- a/homeassistant/components/myq/translations/el.json +++ b/homeassistant/components/myq/translations/el.json @@ -1,5 +1,14 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "reauth_confirm": { "data": { @@ -10,7 +19,8 @@ }, "user": { "data": { - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b7\u03bd \u03c0\u03cd\u03bb\u03b7 MyQ" } diff --git a/homeassistant/components/mysensors/translations/el.json b/homeassistant/components/mysensors/translations/el.json index d706ff53238ebc..17bf83158b7ac8 100644 --- a/homeassistant/components/mysensors/translations/el.json +++ b/homeassistant/components/mysensors/translations/el.json @@ -1,9 +1,11 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "duplicate_persistence_file": "\u03a4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf persistence \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7", "duplicate_topic": "\u03a4\u03bf \u03b8\u03ad\u03bc\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "invalid_device": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae", "invalid_ip": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", "invalid_persistence_file": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf persistance", @@ -14,12 +16,15 @@ "invalid_version": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 MySensors", "not_a_number": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc", "port_out_of_range": "\u039f \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03b8\u03cd\u03c1\u03b1\u03c2 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c4\u03bf\u03c5\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf\u03bd 1 \u03ba\u03b1\u03b9 \u03c4\u03bf \u03c0\u03bf\u03bb\u03cd 65535", - "same_topic": "\u03a4\u03b1 \u03b8\u03ad\u03bc\u03b1\u03c4\u03b1 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2 \u03ba\u03b1\u03b9 \u03b4\u03b7\u03bc\u03bf\u03c3\u03af\u03b5\u03c5\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c4\u03b1 \u03af\u03b4\u03b9\u03b1" + "same_topic": "\u03a4\u03b1 \u03b8\u03ad\u03bc\u03b1\u03c4\u03b1 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2 \u03ba\u03b1\u03b9 \u03b4\u03b7\u03bc\u03bf\u03c3\u03af\u03b5\u03c5\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c4\u03b1 \u03af\u03b4\u03b9\u03b1", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "error": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "duplicate_persistence_file": "\u03a4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf persistence \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7", "duplicate_topic": "\u03a4\u03bf \u03b8\u03ad\u03bc\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "invalid_device": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae", "invalid_ip": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", "invalid_persistence_file": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf persistance", @@ -31,12 +36,14 @@ "mqtt_required": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 MQTT \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", "not_a_number": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc", "port_out_of_range": "\u039f \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03b8\u03cd\u03c1\u03b1\u03c2 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c4\u03bf\u03c5\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf\u03bd 1 \u03ba\u03b1\u03b9 \u03c4\u03bf \u03c0\u03bf\u03bb\u03cd 65535", - "same_topic": "\u03a4\u03b1 \u03b8\u03ad\u03bc\u03b1\u03c4\u03b1 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2 \u03ba\u03b1\u03b9 \u03b4\u03b7\u03bc\u03bf\u03c3\u03af\u03b5\u03c5\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c4\u03b1 \u03af\u03b4\u03b9\u03b1" + "same_topic": "\u03a4\u03b1 \u03b8\u03ad\u03bc\u03b1\u03c4\u03b1 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2 \u03ba\u03b1\u03b9 \u03b4\u03b7\u03bc\u03bf\u03c3\u03af\u03b5\u03c5\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c4\u03b1 \u03af\u03b4\u03b9\u03b1", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "gw_mqtt": { "data": { "persistence_file": "\u03b1\u03c1\u03c7\u03b5\u03af\u03bf persistence (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1)", + "retain": "mqtt retain", "topic_in_prefix": "\u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03c4\u03b1 \u03b8\u03ad\u03bc\u03b1\u03c4\u03b1 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5 (topic_in_prefix)", "topic_out_prefix": "\u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03c4\u03b1 \u03b8\u03ad\u03bc\u03b1\u03c4\u03b1 \u03b5\u03be\u03cc\u03b4\u03bf\u03c5 (topic_out_prefix)", "version": "\u0388\u03ba\u03b4\u03bf\u03c3\u03b7 MySensors" diff --git a/homeassistant/components/mysensors/translations/it.json b/homeassistant/components/mysensors/translations/it.json index 65638dad15eaf1..95837352502403 100644 --- a/homeassistant/components/mysensors/translations/it.json +++ b/homeassistant/components/mysensors/translations/it.json @@ -43,7 +43,7 @@ "gw_mqtt": { "data": { "persistence_file": "file di persistenza (lascia vuoto per generare automaticamente)", - "retain": "mqtt conserva", + "retain": "Persistenza mqtt", "topic_in_prefix": "prefisso per argomenti di input (topic_in_prefix)", "topic_out_prefix": "prefisso per argomenti di output (topic_out_prefix)", "version": "Versione MySensors" diff --git a/homeassistant/components/nam/translations/el.json b/homeassistant/components/nam/translations/el.json index e1e7e8ff07900d..36896d9fa24435 100644 --- a/homeassistant/components/nam/translations/el.json +++ b/homeassistant/components/nam/translations/el.json @@ -1,9 +1,16 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "device_unsupported": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9.", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2", "reauth_unsuccessful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b1\u03bd\u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2. \u039a\u03b1\u03c4\u03b1\u03c1\u03b3\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03be\u03b1\u03bd\u03ac." }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "flow_title": "{host}", "step": { "confirm_discovery": { diff --git a/homeassistant/components/nanoleaf/translations/el.json b/homeassistant/components/nanoleaf/translations/el.json index 1829e51815dea8..bf3e406a86172e 100644 --- a/homeassistant/components/nanoleaf/translations/el.json +++ b/homeassistant/components/nanoleaf/translations/el.json @@ -4,6 +4,7 @@ "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03af\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03bc\u03ad\u03bd\u03b7", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "invalid_token": "\u0386\u03ba\u03c5\u03c1\u03bf \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2", "unknown": "\u0391\u03bd\u03b5\u03c0\u03ac\u03bd\u03c4\u03b5\u03c7\u03bf \u03bb\u03ac\u03b8\u03bf\u03c2" }, "error": { diff --git a/homeassistant/components/neato/translations/el.json b/homeassistant/components/neato/translations/el.json index a73e4283fb16db..5e57ddd8b8b5e4 100644 --- a/homeassistant/components/neato/translations/el.json +++ b/homeassistant/components/neato/translations/el.json @@ -1,3 +1,23 @@ { + "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "authorize_url_timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2.", + "missing_configuration": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7.", + "no_url_available": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL. \u0393\u03b9\u03b1 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1, [\u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b2\u03bf\u03ae\u03b8\u03b5\u03b9\u03b1\u03c2] ( {docs_url} )", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "create_entry": { + "default": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, + "step": { + "pick_implementation": { + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03bc\u03b5\u03b8\u03cc\u03b4\u03bf\u03c5 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, + "reauth_confirm": { + "title": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;" + } + } + }, "title": "Neato Botvac" } \ No newline at end of file diff --git a/homeassistant/components/nest/translations/el.json b/homeassistant/components/nest/translations/el.json index cdd1dc4b21569d..1628a72173341c 100644 --- a/homeassistant/components/nest/translations/el.json +++ b/homeassistant/components/nest/translations/el.json @@ -1,14 +1,31 @@ { "config": { + "abort": { + "authorize_url_timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2.", + "invalid_access_token": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "missing_configuration": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7.", + "no_url_available": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL. \u0393\u03b9\u03b1 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1, [\u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b2\u03bf\u03ae\u03b8\u03b5\u03b9\u03b1\u03c2] ( {docs_url} )", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", + "unknown_authorize_url_generation": "\u0386\u03b3\u03bd\u03c9\u03c3\u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2." + }, + "create_entry": { + "default": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "error": { + "bad_project_id": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03ad\u03c1\u03b3\u03bf\u03c5 Cloud (\u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03bf Cloud Console)", "internal_error": "\u0395\u03c3\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03cc \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03b5\u03c0\u03b9\u03ba\u03cd\u03c1\u03c9\u03c3\u03b7\u03c2 \u03ba\u03ce\u03b4\u03b9\u03ba\u03b1", "invalid_pin": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN", "subscriber_error": "\u0386\u03b3\u03bd\u03c9\u03c3\u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03c3\u03c5\u03bd\u03b4\u03c1\u03bf\u03bc\u03b7\u03c4\u03ae, \u03b4\u03b5\u03af\u03c4\u03b5 \u03c4\u03b1 \u03b1\u03c1\u03c7\u03b5\u03af\u03b1 \u03ba\u03b1\u03c4\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2", "timeout": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03c0\u03b9\u03ba\u03cd\u03c1\u03c9\u03c3\u03b7\u03c2 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1", "wrong_project_id": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03ad\u03c1\u03b3\u03bf\u03c5 Cloud (\u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03ad\u03c1\u03b3\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2)" }, "step": { "auth": { + "data": { + "code": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Google, [\u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2]({url}).\n\n\u039c\u03b5\u03c4\u03ac \u03c4\u03b7\u03bd \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7, \u03b1\u03bd\u03c4\u03b9\u03b3\u03c1\u03ac\u03c8\u03c4\u03b5-\u03b5\u03c0\u03b9\u03ba\u03bf\u03bb\u03bb\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9 \u03c0\u03b1\u03c1\u03b5\u03c7\u03cc\u03bc\u03b5\u03bd\u03bf \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc Auth Token.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd Google" }, @@ -16,12 +33,19 @@ "data": { "flow_impl": "\u03a0\u03ac\u03c1\u03bf\u03c7\u03bf\u03c2" }, + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;", "title": "\u03a0\u03ac\u03c1\u03bf\u03c7\u03bf\u03c2 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "link": { + "data": { + "code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN" + }, "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03c3\u03c4\u03b7 Nest, [\u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2]({url}).\n\n\u039c\u03b5\u03c4\u03ac \u03c4\u03b7\u03bd \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7, \u03b1\u03bd\u03c4\u03b9\u03b3\u03c1\u03ac\u03c8\u03c4\u03b5-\u03b5\u03c0\u03b9\u03ba\u03bf\u03bb\u03bb\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc PIN \u03c0\u03bf\u03c5 \u03c0\u03b1\u03c1\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd Nest" }, + "pick_implementation": { + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03bc\u03b5\u03b8\u03cc\u03b4\u03bf\u03c5 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "pubsub": { "data": { "cloud_project_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03ad\u03c1\u03b3\u03bf\u03c5 Google Cloud" @@ -30,7 +54,8 @@ "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Google Cloud" }, "reauth_confirm": { - "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 Nest \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03bb\u03ad\u03b3\u03be\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03b7\u03bd \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03c4\u03bf\u03c5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03c3\u03b1\u03c2" + "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 Nest \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03bb\u03ad\u03b3\u03be\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03b7\u03bd \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03c4\u03bf\u03c5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03c3\u03b1\u03c2", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" } } }, diff --git a/homeassistant/components/netatmo/translations/el.json b/homeassistant/components/netatmo/translations/el.json index fb93783d124961..1fcf961a3631b5 100644 --- a/homeassistant/components/netatmo/translations/el.json +++ b/homeassistant/components/netatmo/translations/el.json @@ -1,8 +1,22 @@ { "config": { + "abort": { + "authorize_url_timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2.", + "missing_configuration": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7.", + "no_url_available": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL. \u0393\u03b9\u03b1 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1, [\u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b2\u03bf\u03ae\u03b8\u03b5\u03b9\u03b1\u03c2] ( {docs_url} )", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "create_entry": { + "default": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "step": { + "pick_implementation": { + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03bc\u03b5\u03b8\u03cc\u03b4\u03bf\u03c5 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "reauth_confirm": { - "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 Netatmo \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2" + "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 Netatmo \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" } } }, diff --git a/homeassistant/components/netgear/translations/el.json b/homeassistant/components/netgear/translations/el.json index 3ad2637a5d2e3e..141f8f31ddd963 100644 --- a/homeassistant/components/netgear/translations/el.json +++ b/homeassistant/components/netgear/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { "config": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03ae \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2: \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03ae \u03c3\u03b1\u03c2" }, @@ -9,6 +12,7 @@ "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2 (\u03a0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "port": "\u0398\u03cd\u03c1\u03b1 (\u03a0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", + "ssl": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03ad\u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 (\u03a0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)" }, "description": "\u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf\u03c2 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2: {host}\n\u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03b8\u03cd\u03c1\u03b1: {port}\n\u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7: {username}", diff --git a/homeassistant/components/nexia/translations/el.json b/homeassistant/components/nexia/translations/el.json index 87e5fd2cd8d8b2..47d2a624cd3980 100644 --- a/homeassistant/components/nexia/translations/el.json +++ b/homeassistant/components/nexia/translations/el.json @@ -1,10 +1,19 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { "brand": "\u039c\u03ac\u03c1\u03ba\u03b1", - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 mynexia.com" } diff --git a/homeassistant/components/nfandroidtv/translations/el.json b/homeassistant/components/nfandroidtv/translations/el.json index 7effe92ee63c62..b95ec27a3bf83b 100644 --- a/homeassistant/components/nfandroidtv/translations/el.json +++ b/homeassistant/components/nfandroidtv/translations/el.json @@ -1,9 +1,17 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" }, "description": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u0395\u03b9\u03b4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9\u03c2 \u03b3\u03b9\u03b1 Android TV. \n\n \u0393\u03b9\u03b1 Android TV: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\n \u0393\u03b9\u03b1 Fire TV: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK \n\n \u0398\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03b5\u03af\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03c1\u03ac\u03c4\u03b7\u03c3\u03b7 DHCP \u03c3\u03c4\u03bf \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2 (\u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b5\u03b3\u03c7\u03b5\u03b9\u03c1\u03af\u03b4\u03b9\u03bf \u03c7\u03c1\u03ae\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2) \u03b5\u03af\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c4\u03b1\u03c4\u03b9\u03ba\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae. \u0395\u03ac\u03bd \u03cc\u03c7\u03b9, \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b8\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03b5\u03af \u03c4\u03b5\u03bb\u03b9\u03ba\u03ac \u03bc\u03b7 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7.", "title": "\u0395\u03b9\u03b4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9\u03c2 \u03b3\u03b9\u03b1 Android TV / Fire TV" diff --git a/homeassistant/components/nightscout/translations/el.json b/homeassistant/components/nightscout/translations/el.json index b5b12c25c03696..aaa8db2b70df84 100644 --- a/homeassistant/components/nightscout/translations/el.json +++ b/homeassistant/components/nightscout/translations/el.json @@ -1,10 +1,19 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "flow_title": "Nightscout", "step": { "user": { "data": { - "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API" + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", + "url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL" }, "description": "- \u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL: \u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1\u03c2 \u03c4\u03bf\u03c5 nightcout. \u0394\u03b7\u03bb\u03b1\u03b4\u03ae: https://myhomeassistant.duckdns.org:5423\n - \u039a\u03bb\u03b5\u03b9\u03b4\u03af API (\u03a0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc): \u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03bc\u03cc\u03bd\u03bf \u03b5\u03ac\u03bd \u03b7 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1 \u03c3\u03b1\u03c2 \u03c0\u03c1\u03bf\u03c3\u03c4\u03b1\u03c4\u03b5\u03cd\u03b5\u03c4\u03b1\u03b9 (auth_default_roles! = readable).", "title": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae Nightscout." diff --git a/homeassistant/components/nina/translations/el.json b/homeassistant/components/nina/translations/el.json index 181389950b999a..9faf567662ac37 100644 --- a/homeassistant/components/nina/translations/el.json +++ b/homeassistant/components/nina/translations/el.json @@ -1,7 +1,12 @@ { "config": { + "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, "error": { - "no_selection": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf\u03c5\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf\u03bd \u03bc\u03af\u03b1 \u03c0\u03cc\u03bb\u03b7/\u03bd\u03bf\u03bc\u03cc" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "no_selection": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf\u03c5\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf\u03bd \u03bc\u03af\u03b1 \u03c0\u03cc\u03bb\u03b7/\u03bd\u03bf\u03bc\u03cc", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/nmap_tracker/translations/el.json b/homeassistant/components/nmap_tracker/translations/el.json index 1ddcf47929517a..202bc8e07221d2 100644 --- a/homeassistant/components/nmap_tracker/translations/el.json +++ b/homeassistant/components/nmap_tracker/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { "invalid_hosts": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03b9 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03af \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ad\u03c2" }, @@ -16,13 +19,21 @@ } }, "options": { + "error": { + "invalid_hosts": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03b9 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03af \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ad\u03c2" + }, "step": { "init": { "data": { "consider_home": "\u0394\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1 \u03b1\u03bd\u03b1\u03bc\u03bf\u03bd\u03ae\u03c2 \u03bc\u03ad\u03c7\u03c1\u03b9 \u03bd\u03b1 \u03b5\u03c0\u03b9\u03c3\u03b7\u03bc\u03b1\u03bd\u03b8\u03b5\u03af \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03bc\u03bf\u03cd \u03c9\u03c2 \u03cc\u03c7\u03b9 \u03c3\u03c0\u03af\u03c4\u03b9, \u03b1\u03c6\u03bf\u03cd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b5\u03bc\u03c6\u03b1\u03bd\u03b9\u03c3\u03c4\u03b5\u03af.", + "exclude": "\u0394\u03b9\u03b5\u03c5\u03b8\u03cd\u03bd\u03c3\u03b5\u03b9\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 (\u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03bc\u03b5 \u03ba\u03cc\u03bc\u03bc\u03b1) \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03b1\u03c0\u03bf\u03ba\u03bb\u03b5\u03af\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7", + "home_interval": "\u0395\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03bb\u03b5\u03c0\u03c4\u03ce\u03bd \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03c4\u03c9\u03bd \u03c3\u03b1\u03c1\u03ce\u03c3\u03b5\u03c9\u03bd \u03b5\u03bd\u03b5\u03c1\u03b3\u03ce\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd (\u03b4\u03b9\u03b1\u03c4\u03ae\u03c1\u03b7\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03bc\u03c0\u03b1\u03c4\u03b1\u03c1\u03af\u03b1\u03c2)", + "hosts": "\u0394\u03b9\u03b5\u03c5\u03b8\u03cd\u03bd\u03c3\u03b5\u03b9\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 (\u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03bc\u03b5 \u03ba\u03cc\u03bc\u03bc\u03b1) \u03b3\u03b9\u03b1 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7", "interval_seconds": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7\u03c2", + "scan_options": "\u0391\u03ba\u03b1\u03c4\u03ad\u03c1\u03b3\u03b1\u03c3\u03c4\u03b5\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b9\u03bc\u03b5\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf Nmap", "track_new_devices": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03bd\u03ad\u03c9\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd" - } + }, + "description": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03ce\u03bd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf Nmap. \u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 \u03ba\u03b1\u03b9 \u03bf\u03b9 \u03b5\u03be\u03b1\u03b9\u03c1\u03ad\u03c3\u03b5\u03b9\u03c2 \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u0394\u03b9\u03b5\u03c5\u03b8\u03cd\u03bd\u03c3\u03b5\u03b9\u03c2 IP (192.168.1.1), \u0394\u03af\u03ba\u03c4\u03c5\u03b1 IP (192.168.0.0/24) \u03ae \u0395\u03cd\u03c1\u03bf\u03c2 IP (192.168.1.0-32)." } } }, diff --git a/homeassistant/components/notion/translations/el.json b/homeassistant/components/notion/translations/el.json index af900a992a648e..ee39e67ae73d11 100644 --- a/homeassistant/components/notion/translations/el.json +++ b/homeassistant/components/notion/translations/el.json @@ -1,14 +1,21 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, "error": { - "no_devices": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc" + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "no_devices": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "reauth_confirm": { "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, - "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username}." + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username}.", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, "user": { "data": { diff --git a/homeassistant/components/nuheat/translations/el.json b/homeassistant/components/nuheat/translations/el.json index 7723b5bbdb9237..9e6d0193fae4a5 100644 --- a/homeassistant/components/nuheat/translations/el.json +++ b/homeassistant/components/nuheat/translations/el.json @@ -1,13 +1,20 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { - "invalid_thermostat": "\u039f \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03c4\u03bf\u03c5 \u03b8\u03b5\u03c1\u03bc\u03bf\u03c3\u03c4\u03ac\u03c4\u03b7 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2." + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "invalid_thermostat": "\u039f \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03c4\u03bf\u03c5 \u03b8\u03b5\u03c1\u03bc\u03bf\u03c3\u03c4\u03ac\u03c4\u03b7 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "serial_number": "\u03a3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03c4\u03bf\u03c5 \u03b8\u03b5\u03c1\u03bc\u03bf\u03c3\u03c4\u03ac\u03c4\u03b7." + "serial_number": "\u03a3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03c4\u03bf\u03c5 \u03b8\u03b5\u03c1\u03bc\u03bf\u03c3\u03c4\u03ac\u03c4\u03b7.", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0398\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03b7\u03c4\u03b9\u03ba\u03cc \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03ae \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03bf\u03c5 \u03b8\u03b5\u03c1\u03bc\u03bf\u03c3\u03c4\u03ac\u03c4\u03b7 \u03c3\u03b1\u03c2, \u03c3\u03c5\u03bd\u03b4\u03b5\u03cc\u03bc\u03b5\u03bd\u03bf\u03b9 \u03c3\u03c4\u03bf https://MyNuHeat.com \u03ba\u03b1\u03b9 \u03b5\u03c0\u03b9\u03bb\u03ad\u03b3\u03bf\u03bd\u03c4\u03b1\u03c2 \u03c4\u03bf\u03bd/\u03c4\u03bf\u03c5\u03c2 \u03b8\u03b5\u03c1\u03bc\u03bf\u03c3\u03c4\u03ac\u03c4\u03b5\u03c2 \u03c3\u03b1\u03c2.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf NuHeat" diff --git a/homeassistant/components/nuki/translations/el.json b/homeassistant/components/nuki/translations/el.json index 237fb7e22a4ab5..1422144b0e6ac6 100644 --- a/homeassistant/components/nuki/translations/el.json +++ b/homeassistant/components/nuki/translations/el.json @@ -1,16 +1,26 @@ { "config": { + "abort": { + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "reauth_confirm": { - "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Nuki \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03bc\u03b5 \u03c4\u03b7 \u03b3\u03ad\u03c6\u03c5\u03c1\u03ac \u03c3\u03b1\u03c2." + "data": { + "token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, + "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Nuki \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03bc\u03b5 \u03c4\u03b7 \u03b3\u03ad\u03c6\u03c5\u03c1\u03ac \u03c3\u03b1\u03c2.", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", - "port": "\u0398\u03cd\u03c1\u03b1" + "port": "\u0398\u03cd\u03c1\u03b1", + "token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" } } } diff --git a/homeassistant/components/nut/translations/el.json b/homeassistant/components/nut/translations/el.json index 1e6a773c02d119..971d6476a23762 100644 --- a/homeassistant/components/nut/translations/el.json +++ b/homeassistant/components/nut/translations/el.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "resources": { "data": { @@ -18,7 +25,8 @@ "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "port": "\u0398\u03cd\u03c1\u03b1" + "port": "\u0398\u03cd\u03c1\u03b1", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae NUT" } @@ -26,7 +34,8 @@ }, "options": { "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "init": { diff --git a/homeassistant/components/nws/translations/el.json b/homeassistant/components/nws/translations/el.json index 414d71d2d52a41..14a404a8c661ff 100644 --- a/homeassistant/components/nws/translations/el.json +++ b/homeassistant/components/nws/translations/el.json @@ -1,8 +1,18 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2", "station": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd METAR" }, "description": "\u0395\u03ac\u03bd \u03b4\u03b5\u03bd \u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd METAR, \u03c4\u03bf \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2 \u03ba\u03b1\u03b9 \u03bc\u03ae\u03ba\u03bf\u03c2 \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03bf\u03cd\u03bd \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03bb\u03b7\u03c3\u03b9\u03ad\u03c3\u03c4\u03b5\u03c1\u03bf\u03c5 \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd. \u03a0\u03c1\u03bf\u03c2 \u03c4\u03bf \u03c0\u03b1\u03c1\u03cc\u03bd, \u03ad\u03bd\u03b1 \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03bf\u03c4\u03b9\u03b4\u03ae\u03c0\u03bf\u03c4\u03b5. \u03a3\u03c5\u03bd\u03b9\u03c3\u03c4\u03ac\u03c4\u03b1\u03b9 \u03b7 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03bc\u03b9\u03b1\u03c2 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 \u03b7\u03bb\u03b5\u03ba\u03c4\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03c4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b5\u03af\u03bf\u03c5.", diff --git a/homeassistant/components/nzbget/translations/el.json b/homeassistant/components/nzbget/translations/el.json index 9a00d825777429..c2d086e8be1e74 100644 --- a/homeassistant/components/nzbget/translations/el.json +++ b/homeassistant/components/nzbget/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", "unknown": "\u039c\u03b7 \u03b1\u03bd\u03b1\u03bc\u03b5\u03bd\u03cc\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "error": { @@ -13,7 +14,10 @@ "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + "port": "\u0398\u03cd\u03c1\u03b1", + "ssl": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03ad\u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", + "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL" }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf NZBGet" } diff --git a/homeassistant/components/octoprint/translations/el.json b/homeassistant/components/octoprint/translations/el.json index cf8474a3762c2e..60dc47229e2dfc 100644 --- a/homeassistant/components/octoprint/translations/el.json +++ b/homeassistant/components/octoprint/translations/el.json @@ -1,7 +1,14 @@ { "config": { "abort": { - "auth_failed": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b1\u03bd\u03ac\u03ba\u03c4\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd api \u03c4\u03b7\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2" + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "auth_failed": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b1\u03bd\u03ac\u03ba\u03c4\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd api \u03c4\u03b7\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "flow_title": "\u0395\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae\u03c2 OctoPrint: {host}", "progress": { @@ -14,7 +21,8 @@ "path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2", "port": "\u0391\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03b8\u03cd\u03c1\u03b1\u03c2", "ssl": "\u03a7\u03c1\u03ae\u03c3\u03b7 SSL", - "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", + "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL" } } } diff --git a/homeassistant/components/oncue/translations/el.json b/homeassistant/components/oncue/translations/el.json index bab52704f790ea..cdc7ae85736f17 100644 --- a/homeassistant/components/oncue/translations/el.json +++ b/homeassistant/components/oncue/translations/el.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/ondilo_ico/translations/el.json b/homeassistant/components/ondilo_ico/translations/el.json new file mode 100644 index 00000000000000..ef89a2c81d987b --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/el.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2.", + "missing_configuration": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7." + }, + "create_entry": { + "default": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, + "step": { + "pick_implementation": { + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03bc\u03b5\u03b8\u03cc\u03b4\u03bf\u03c5 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onewire/translations/el.json b/homeassistant/components/onewire/translations/el.json index 5b17c464742874..35b39838f5daf9 100644 --- a/homeassistant/components/onewire/translations/el.json +++ b/homeassistant/components/onewire/translations/el.json @@ -1,11 +1,16 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "invalid_path": "\u039f \u03ba\u03b1\u03c4\u03ac\u03bb\u03bf\u03b3\u03bf\u03c2 \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5." }, "step": { "owserver": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "port": "\u0398\u03cd\u03c1\u03b1" }, "title": "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03b5\u03c1\u03b5\u03b9\u03ce\u03bd owserver" diff --git a/homeassistant/components/opengarage/translations/el.json b/homeassistant/components/opengarage/translations/el.json index 99defca3e95eb8..d137f362508bbd 100644 --- a/homeassistant/components/opengarage/translations/el.json +++ b/homeassistant/components/opengarage/translations/el.json @@ -1,11 +1,20 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { "device_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", - "port": "\u0398\u03cd\u03c1\u03b1" + "port": "\u0398\u03cd\u03c1\u03b1", + "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL" } } } diff --git a/homeassistant/components/opentherm_gw/translations/el.json b/homeassistant/components/opentherm_gw/translations/el.json index 049ed9ce79e138..92dac2e9b3dfb9 100644 --- a/homeassistant/components/opentherm_gw/translations/el.json +++ b/homeassistant/components/opentherm_gw/translations/el.json @@ -1,13 +1,16 @@ { "config": { "error": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "id_exists": "\u03a4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03cd\u03bb\u03b7\u03c2 \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7" }, "step": { "init": { "data": { "device": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", - "id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc" + "id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" }, "title": "\u03a0\u03cd\u03bb\u03b7 OpenTherm" } diff --git a/homeassistant/components/openuv/translations/el.json b/homeassistant/components/openuv/translations/el.json index 3f2c11281fada9..c86a90e1f09cd9 100644 --- a/homeassistant/components/openuv/translations/el.json +++ b/homeassistant/components/openuv/translations/el.json @@ -1,7 +1,19 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "invalid_api_key": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API" + }, "step": { "user": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", + "elevation": "\u0391\u03bd\u03cd\u03c8\u03c9\u03c3\u03b7", + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2" + }, "title": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03b1\u03c2" } } diff --git a/homeassistant/components/openweathermap/translations/el.json b/homeassistant/components/openweathermap/translations/el.json index 9c703a704e899b..1455ed91a53917 100644 --- a/homeassistant/components/openweathermap/translations/el.json +++ b/homeassistant/components/openweathermap/translations/el.json @@ -3,6 +3,10 @@ "abort": { "already_configured": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 OpenWeatherMap \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03ad\u03c2 \u03c4\u03b9\u03c2 \u03c3\u03c5\u03bd\u03c4\u03b5\u03c4\u03b1\u03b3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af." }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_api_key": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/overkiz/translations/el.json b/homeassistant/components/overkiz/translations/el.json index 58a68ae1aa4367..2ba577de4c5feb 100644 --- a/homeassistant/components/overkiz/translations/el.json +++ b/homeassistant/components/overkiz/translations/el.json @@ -1,12 +1,16 @@ { "config": { "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2", "reauth_wrong_account": "\u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03bc\u03cc\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf\u03bd \u03af\u03b4\u03b9\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03af\u03b4\u03b9\u03bf \u03ba\u03cc\u03bc\u03b2\u03bf Overkiz." }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "server_in_maintenance": "\u039f \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03ba\u03c4\u03cc\u03c2 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c3\u03c5\u03bd\u03c4\u03ae\u03c1\u03b7\u03c3\u03b7", - "too_many_requests": "\u03a0\u03ac\u03c1\u03b1 \u03c0\u03bf\u03bb\u03bb\u03ac \u03b1\u03b9\u03c4\u03ae\u03bc\u03b1\u03c4\u03b1, \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1." + "too_many_requests": "\u03a0\u03ac\u03c1\u03b1 \u03c0\u03bf\u03bb\u03bb\u03ac \u03b1\u03b9\u03c4\u03ae\u03bc\u03b1\u03c4\u03b1, \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "flow_title": "\u03a0\u03cd\u03bb\u03b7: {gateway_id}", "step": { diff --git a/homeassistant/components/ovo_energy/translations/el.json b/homeassistant/components/ovo_energy/translations/el.json index 9d11ed632734e2..b8b9d35a23830d 100644 --- a/homeassistant/components/ovo_energy/translations/el.json +++ b/homeassistant/components/ovo_energy/translations/el.json @@ -1,12 +1,18 @@ { "config": { + "error": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "flow_title": "{username}", "step": { "reauth": { "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, - "description": "\u039f \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b3\u03b9\u03b1 \u03c4\u03bf OVO Energy. \u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c4\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2." + "description": "\u039f \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b3\u03b9\u03b1 \u03c4\u03bf OVO Energy. \u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c4\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2.", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "user": { "data": { diff --git a/homeassistant/components/ozw/translations/el.json b/homeassistant/components/ozw/translations/el.json index 10e5ee24ffd8c6..6708cec13583e3 100644 --- a/homeassistant/components/ozw/translations/el.json +++ b/homeassistant/components/ozw/translations/el.json @@ -4,6 +4,8 @@ "addon_info_failed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03bb\u03ae\u03c8\u03b7\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03b9\u03ce\u03bd \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 OpenZWave.", "addon_install_failed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 OpenZWave.", "addon_set_config_failed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd OpenZWave.", + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", "mqtt_required": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 MQTT \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, @@ -29,7 +31,8 @@ }, "start_addon": { "data": { - "network_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5" + "network_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5", + "usb_path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 USB" }, "title": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 OpenZWave" } diff --git a/homeassistant/components/panasonic_viera/translations/el.json b/homeassistant/components/panasonic_viera/translations/el.json index 27de1a7fb0d664..a55c760ec812fe 100644 --- a/homeassistant/components/panasonic_viera/translations/el.json +++ b/homeassistant/components/panasonic_viera/translations/el.json @@ -1,6 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "invalid_pin_code": "\u039f \u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN \u03c0\u03bf\u03c5 \u03b5\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b1\u03c4\u03b5 \u03b4\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf" }, "step": { diff --git a/homeassistant/components/philips_js/translations/el.json b/homeassistant/components/philips_js/translations/el.json index 9992a30caea431..ef134dc9064ab2 100644 --- a/homeassistant/components/philips_js/translations/el.json +++ b/homeassistant/components/philips_js/translations/el.json @@ -1,8 +1,13 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "invalid_pin": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf PIN", - "pairing_failure": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7: {error_id}" + "pairing_failure": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7: {error_id}", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "pair": { diff --git a/homeassistant/components/pi_hole/translations/el.json b/homeassistant/components/pi_hole/translations/el.json index 105be4965c4455..b6aa0fe536577e 100644 --- a/homeassistant/components/pi_hole/translations/el.json +++ b/homeassistant/components/pi_hole/translations/el.json @@ -1,11 +1,27 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { + "api_key": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API" + } + }, "user": { "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "location": "\u03a4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1", "name": "\u038c\u03bd\u03bf\u03bc\u03b1", - "statistics_only": "\u039c\u03cc\u03bd\u03bf \u03c3\u03c4\u03b1\u03c4\u03b9\u03c3\u03c4\u03b9\u03ba\u03ac \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1" + "port": "\u0398\u03cd\u03c1\u03b1", + "ssl": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03ad\u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL", + "statistics_only": "\u039c\u03cc\u03bd\u03bf \u03c3\u03c4\u03b1\u03c4\u03b9\u03c3\u03c4\u03b9\u03ba\u03ac \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1", + "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL" } } } diff --git a/homeassistant/components/picnic/translations/el.json b/homeassistant/components/picnic/translations/el.json index 32c00287a347d6..ba974da622582d 100644 --- a/homeassistant/components/picnic/translations/el.json +++ b/homeassistant/components/picnic/translations/el.json @@ -1,16 +1,21 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "reauth_successful": "\u0397 \u03b5\u03c0\u03b1\u03bd\u03b1\u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" }, "error": { - "different_account": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03af\u03b4\u03b9\u03bf\u03c2 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "different_account": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03af\u03b4\u03b9\u03bf\u03c2 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { "data": { "country_code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c7\u03ce\u03c1\u03b1\u03c2", - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } } diff --git a/homeassistant/components/picnic/translations/nl.json b/homeassistant/components/picnic/translations/nl.json index 210eebdf35717b..dc040fc03d0277 100644 --- a/homeassistant/components/picnic/translations/nl.json +++ b/homeassistant/components/picnic/translations/nl.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { "cannot_connect": "Kan geen verbinding maken", + "different_account": "Account moet dezelfde zijn als die gebruikt is voor het opzetten van de integratie", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/plaato/translations/el.json b/homeassistant/components/plaato/translations/el.json index bd5e517481fd4a..8813a4d71bf8ab 100644 --- a/homeassistant/components/plaato/translations/el.json +++ b/homeassistant/components/plaato/translations/el.json @@ -1,8 +1,10 @@ { "config": { "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "cloud_not_connected": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf Home Assistant Cloud.", - "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", + "webhook_not_internet_accessible": "\u0397 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1 \u03c4\u03bf\u03c5 Home Assistant \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c3\u03b2\u03ac\u03c3\u03b9\u03bc\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf \u03b4\u03b9\u03b1\u03b4\u03af\u03ba\u03c4\u03c5\u03bf \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03b5\u03b9 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03b1 webhook." }, "create_entry": { "default": "\u03a4\u03bf Plaato {device_type} \u03bc\u03b5 \u03cc\u03bd\u03bf\u03bc\u03b1 **{device_name}** \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03bc\u03b5 \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03af\u03b1!" @@ -26,6 +28,7 @@ "device_name": "\u039f\u03bd\u03bf\u03bc\u03ac\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c3\u03b1\u03c2", "device_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 Plaato" }, + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03c9\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd Plaato" }, "webhook": { diff --git a/homeassistant/components/plex/translations/el.json b/homeassistant/components/plex/translations/el.json index 9039fcf27bdd35..3f447b48d8633e 100644 --- a/homeassistant/components/plex/translations/el.json +++ b/homeassistant/components/plex/translations/el.json @@ -3,8 +3,10 @@ "abort": { "all_configured": "\u038c\u03bb\u03bf\u03b9 \u03bf\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf\u03b9 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ad\u03c2 \u03ad\u03c7\u03bf\u03c5\u03bd \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", "already_configured": "\u0391\u03c5\u03c4\u03cc\u03c2 \u03bf \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 Plex \u03b5\u03af\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", "reauth_successful": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c4\u03b7\u03ba\u03b5 \u03be\u03b1\u03bd\u03ac \u03bc\u03b5 \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03af\u03b1", - "token_request_timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03bb\u03ae\u03c8\u03b7\u03c2 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03bf\u03cd" + "token_request_timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03bb\u03ae\u03c8\u03b7\u03c2 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03bf\u03cd", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "error": { "faulty_credentials": "\u0397 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5, \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf Token", @@ -18,7 +20,10 @@ "manual_setup": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", - "token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)" + "port": "\u0398\u03cd\u03c1\u03b1", + "ssl": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03ad\u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL", + "token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", + "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL" }, "title": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Plex" }, diff --git a/homeassistant/components/plugwise/translations/el.json b/homeassistant/components/plugwise/translations/el.json index b52262e8602753..a891a74d3ad421 100644 --- a/homeassistant/components/plugwise/translations/el.json +++ b/homeassistant/components/plugwise/translations/el.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "flow_title": "{name}", "step": { "user": { diff --git a/homeassistant/components/plum_lightpad/translations/el.json b/homeassistant/components/plum_lightpad/translations/el.json index 5561ddd5d2a4a7..5f3d7c8b43532e 100644 --- a/homeassistant/components/plum_lightpad/translations/el.json +++ b/homeassistant/components/plum_lightpad/translations/el.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/point/translations/el.json b/homeassistant/components/point/translations/el.json index fd4186ecf038c5..1af8ebfcb4e12e 100644 --- a/homeassistant/components/point/translations/el.json +++ b/homeassistant/components/point/translations/el.json @@ -1,10 +1,18 @@ { "config": { "abort": { - "external_setup": "\u03a4\u03bf \u03c3\u03b7\u03bc\u03b5\u03af\u03bf \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03b8\u03b7\u03ba\u03b5 \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ce\u03c2 \u03b1\u03c0\u03cc \u03ac\u03bb\u03bb\u03b7 \u03c1\u03bf\u03ae." + "already_setup": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", + "authorize_url_timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2.", + "external_setup": "\u03a4\u03bf \u03c3\u03b7\u03bc\u03b5\u03af\u03bf \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03b8\u03b7\u03ba\u03b5 \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ce\u03c2 \u03b1\u03c0\u03cc \u03ac\u03bb\u03bb\u03b7 \u03c1\u03bf\u03ae.", + "no_flows": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7.", + "unknown_authorize_url_generation": "\u0386\u03b3\u03bd\u03c9\u03c3\u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2." + }, + "create_entry": { + "default": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "error": { - "follow_link": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf \u03ba\u03b1\u03b9 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af\u03c4\u03b5 \u03c0\u03c1\u03b9\u03bd \u03c0\u03b1\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03a5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae" + "follow_link": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf \u03ba\u03b1\u03b9 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af\u03c4\u03b5 \u03c0\u03c1\u03b9\u03bd \u03c0\u03b1\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03a5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae", + "no_token": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "step": { "auth": { @@ -14,7 +22,9 @@ "user": { "data": { "flow_impl": "\u03a0\u03ac\u03c1\u03bf\u03c7\u03bf\u03c2" - } + }, + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;", + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03bc\u03b5\u03b8\u03cc\u03b4\u03bf\u03c5 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" } } } diff --git a/homeassistant/components/poolsense/translations/el.json b/homeassistant/components/poolsense/translations/el.json index f6af3b500f22ec..bb70337158e1b6 100644 --- a/homeassistant/components/poolsense/translations/el.json +++ b/homeassistant/components/poolsense/translations/el.json @@ -1,11 +1,18 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "step": { "user": { "data": { "email": "Email", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;", "title": "PoolSense" } } diff --git a/homeassistant/components/powerwall/translations/el.json b/homeassistant/components/powerwall/translations/el.json index 59bd52f3b0e06b..d3649e44a8d3c0 100644 --- a/homeassistant/components/powerwall/translations/el.json +++ b/homeassistant/components/powerwall/translations/el.json @@ -1,24 +1,32 @@ { "config": { "abort": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1", "wrong_version": "\u03a4\u03bf powerwall \u03c3\u03b1\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b9\u03c3\u03bc\u03b9\u03ba\u03bf\u03cd \u03c0\u03bf\u03c5 \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9. \u03a3\u03ba\u03b5\u03c6\u03c4\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b1\u03b2\u03b1\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03ae \u03bd\u03b1 \u03b1\u03bd\u03b1\u03c6\u03ad\u03c1\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c0\u03c1\u03cc\u03b2\u03bb\u03b7\u03bc\u03b1 \u03ce\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03c0\u03b9\u03bb\u03c5\u03b8\u03b5\u03af." }, "flow_title": "{ip_address}", "step": { "confirm_discovery": { - "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ( {ip_address});" + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ( {ip_address});", + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf powerwall" }, "reauth_confim": { "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, + "description": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03ae\u03b8\u03c9\u03c2 \u03bf\u03b9 \u03c4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03bf\u03b9 5 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b5\u03c2 \u03c4\u03bf\u03c5 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03bf\u03cd \u03b1\u03c1\u03b9\u03b8\u03bc\u03bf\u03cd \u03b3\u03b9\u03b1 \u03c4\u03bf Backup Gateway \u03ba\u03b1\u03b9 \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b2\u03c1\u03b5\u03b8\u03b5\u03af \u03c3\u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae Tesla \u03ae \u03bf\u03b9 \u03c4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03bf\u03b9 5 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b5\u03c2 \u03c4\u03bf\u03c5 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c0\u03bf\u03c5 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03bf \u03b5\u03c3\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03cc \u03c4\u03b7\u03c2 \u03c0\u03cc\u03c1\u03c4\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf Backup Gateway 2.", "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 powerwall" }, "user": { "data": { + "ip_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "description": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03ae\u03b8\u03c9\u03c2 \u03bf\u03b9 \u03c4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03bf\u03b9 5 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b5\u03c2 \u03c4\u03bf\u03c5 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03bf\u03cd \u03b1\u03c1\u03b9\u03b8\u03bc\u03bf\u03cd \u03b3\u03b9\u03b1 \u03c4\u03bf Backup Gateway \u03ba\u03b1\u03b9 \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b2\u03c1\u03b5\u03b8\u03b5\u03af \u03c3\u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae Tesla \u03ae \u03bf\u03b9 \u03c4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03bf\u03b9 5 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b5\u03c2 \u03c4\u03bf\u03c5 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c0\u03bf\u03c5 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03bf \u03b5\u03c3\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03cc \u03c4\u03b7\u03c2 \u03c0\u03cc\u03c1\u03c4\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf Backup Gateway 2.", diff --git a/homeassistant/components/profiler/translations/el.json b/homeassistant/components/profiler/translations/el.json new file mode 100644 index 00000000000000..adba199dc48ac2 --- /dev/null +++ b/homeassistant/components/profiler/translations/el.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "step": { + "user": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/progettihwsw/translations/el.json b/homeassistant/components/progettihwsw/translations/el.json index e2d1540a3c103e..2e345a75b4463a 100644 --- a/homeassistant/components/progettihwsw/translations/el.json +++ b/homeassistant/components/progettihwsw/translations/el.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "relay_modes": { "data": { diff --git a/homeassistant/components/prosegur/translations/el.json b/homeassistant/components/prosegur/translations/el.json index cd3d44f62e432b..d76c733603dd0c 100644 --- a/homeassistant/components/prosegur/translations/el.json +++ b/homeassistant/components/prosegur/translations/el.json @@ -1,5 +1,14 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "reauth_confirm": { "data": { diff --git a/homeassistant/components/ps4/translations/el.json b/homeassistant/components/ps4/translations/el.json index e02eefac9db148..5bc6b4a7b0e4bf 100644 --- a/homeassistant/components/ps4/translations/el.json +++ b/homeassistant/components/ps4/translations/el.json @@ -1,11 +1,14 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "credential_error": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03b1\u03bd\u03ac\u03ba\u03c4\u03b7\u03c3\u03b7\u03c2 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03b7\u03c1\u03af\u03c9\u03bd.", + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", "port_987_bind_error": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03b7 \u03b8\u03cd\u03c1\u03b1 987. \u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7](https://www.home-assistant.io/components/ps4/) \u03b3\u03b9\u03b1 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2.", "port_997_bind_error": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03b7 \u03b8\u03cd\u03c1\u03b1 997. \u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7](https://www.home-assistant.io/components/ps4/) \u03b3\u03b9\u03b1 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2." }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "credential_timeout": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03b4\u03b9\u03b1\u03c0\u03af\u03c3\u03c4\u03b5\u03c5\u03c3\u03b7\u03c2 \u03c4\u03b5\u03c1\u03bc\u03ac\u03c4\u03b9\u03c3\u03b5 \u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c4\u03b7\u03c2. \u03a0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 submit \u03b3\u03b9\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7.", "login_failed": "\u0397 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7 \u03bc\u03b5 \u03c4\u03bf PlayStation 4 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5. \u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03bf \u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c9\u03c3\u03c4\u03cc\u03c2.", "no_ipaddress": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03bf\u03c5 PlayStation 4 \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03b5\u03c4\u03b5." @@ -17,6 +20,9 @@ }, "link": { "data": { + "code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN", + "ip_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "region": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c4\u03bf\u03c5 PlayStation 4. \u0393\u03b9\u03b1 \u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN, \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b9\u03c2 \"\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2\" \u03c3\u03c4\u03b7\u03bd \u03ba\u03bf\u03bd\u03c3\u03cc\u03bb\u03b1 PlayStation 4. \u03a3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c0\u03bb\u03bf\u03b7\u03b3\u03b7\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf 'Mobile App Connection Settings' (\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ce\u03bd \u03b3\u03b9\u03b1 \u03ba\u03b9\u03bd\u03b7\u03c4\u03ac) \u03ba\u03b1\u03b9 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 'Add Device' (\u03a0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2). \u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN \u03c0\u03bf\u03c5 \u03b5\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03c4\u03b1\u03b9. \u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7](https://www.home-assistant.io/components/ps4/) \u03b3\u03b9\u03b1 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2.", diff --git a/homeassistant/components/pure_energie/translations/el.json b/homeassistant/components/pure_energie/translations/el.json index 088aa6f754bdf5..a63ada73fa9d4e 100644 --- a/homeassistant/components/pure_energie/translations/el.json +++ b/homeassistant/components/pure_energie/translations/el.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "flow_title": "{model} ({host})", "step": { "user": { diff --git a/homeassistant/components/pure_energie/translations/he.json b/homeassistant/components/pure_energie/translations/he.json new file mode 100644 index 00000000000000..e9c083d9afc6d0 --- /dev/null +++ b/homeassistant/components/pure_energie/translations/he.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4", + "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4" + }, + "error": { + "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4" + }, + "flow_title": "{model} ({host})", + "step": { + "user": { + "data": { + "host": "\u05de\u05d0\u05e8\u05d7" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pure_energie/translations/nl.json b/homeassistant/components/pure_energie/translations/nl.json new file mode 100644 index 00000000000000..14e705836b0882 --- /dev/null +++ b/homeassistant/components/pure_energie/translations/nl.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd", + "cannot_connect": "Kan geen verbinding maken" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken" + }, + "flow_title": "{model} ({host})", + "step": { + "user": { + "data": { + "host": "Host" + } + }, + "zeroconf_confirm": { + "description": "Wilt u Pure Energie Meter (`{model}`) toevoegen aan Home Assistant?", + "title": "Ontdekt Pure Energie Meter apparaat" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pure_energie/translations/no.json b/homeassistant/components/pure_energie/translations/no.json new file mode 100644 index 00000000000000..6e02b8df2c05ff --- /dev/null +++ b/homeassistant/components/pure_energie/translations/no.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert", + "cannot_connect": "Tilkobling mislyktes" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes" + }, + "flow_title": "{modell} ({host})", + "step": { + "user": { + "data": { + "host": "Vert" + } + }, + "zeroconf_confirm": { + "description": "Vil du legge til Pure Energie Meter (` {model} `) til Home Assistant?", + "title": "Oppdaget Pure Energie Meter-enhet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pvoutput/translations/el.json b/homeassistant/components/pvoutput/translations/el.json index a884d21ce4af7a..2c025c4e6d46e5 100644 --- a/homeassistant/components/pvoutput/translations/el.json +++ b/homeassistant/components/pvoutput/translations/el.json @@ -1,14 +1,22 @@ { "config": { + "abort": { + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "step": { "reauth_confirm": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API" + }, "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03c1\u03b1\u03b3\u03bc\u03b1\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03c4\u03bf PVOutput, \u03b8\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af \u03bd\u03b1 \u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 {account_url}." }, "user": { "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", "system_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c3\u03c5\u03c3\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2" }, "description": "\u0393\u03b9\u03b1 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03c4\u03bf PVOutput, \u03b8\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af \u03bd\u03b1 \u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 {account_url}. \n\n \u03a4\u03b1 \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03ac \u03c3\u03c5\u03c3\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2 \u03c4\u03c9\u03bd \u03b5\u03b3\u03b3\u03b5\u03b3\u03c1\u03b1\u03bc\u03bc\u03ad\u03bd\u03c9\u03bd \u03c3\u03c5\u03c3\u03c4\u03b7\u03bc\u03ac\u03c4\u03c9\u03bd \u03c0\u03b1\u03c1\u03b1\u03c4\u03af\u03b8\u03b5\u03bd\u03c4\u03b1\u03b9 \u03c3\u03c4\u03b7\u03bd \u03af\u03b4\u03b9\u03b1 \u03c3\u03b5\u03bb\u03af\u03b4\u03b1." diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/el.json b/homeassistant/components/pvpc_hourly_pricing/translations/el.json index 4eac27f1b1a712..af128ba3bb31f5 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/el.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/rachio/translations/el.json b/homeassistant/components/rachio/translations/el.json index f8c2e32c5df73f..3e3a14f04075d4 100644 --- a/homeassistant/components/rachio/translations/el.json +++ b/homeassistant/components/rachio/translations/el.json @@ -1,7 +1,18 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API" + }, "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03b1\u03c0\u03cc \u03c4\u03bf https://app.rach.io/. \u039c\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b9\u03c2 \u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03ba\u03b1\u03b9 \u03ba\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf 'GET API KEY'.", "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c3\u03b1\u03c2 Rachio" } diff --git a/homeassistant/components/radio_browser/translations/ca.json b/homeassistant/components/radio_browser/translations/ca.json new file mode 100644 index 00000000000000..50b6e62d751a2d --- /dev/null +++ b/homeassistant/components/radio_browser/translations/ca.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." + }, + "step": { + "user": { + "description": "Vols afegir el navegador r\u00e0dio a Home Assistant?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/radio_browser/translations/el.json b/homeassistant/components/radio_browser/translations/el.json new file mode 100644 index 00000000000000..4c847a0fb6861b --- /dev/null +++ b/homeassistant/components/radio_browser/translations/el.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "step": { + "user": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Radio Browser \u03c3\u03c4\u03bf Home Assistant;" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/radio_browser/translations/et.json b/homeassistant/components/radio_browser/translations/et.json new file mode 100644 index 00000000000000..5c5d742654cebe --- /dev/null +++ b/homeassistant/components/radio_browser/translations/et.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + }, + "step": { + "user": { + "description": "Kas lisada Home Assistantile Radio Browser?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/radio_browser/translations/it.json b/homeassistant/components/radio_browser/translations/it.json new file mode 100644 index 00000000000000..761aa3467a5761 --- /dev/null +++ b/homeassistant/components/radio_browser/translations/it.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." + }, + "step": { + "user": { + "description": "Vuoi aggiungere Radio Browser a Home Assistant?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/radio_browser/translations/nl.json b/homeassistant/components/radio_browser/translations/nl.json new file mode 100644 index 00000000000000..d8f46a3130b1ca --- /dev/null +++ b/homeassistant/components/radio_browser/translations/nl.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, + "step": { + "user": { + "description": "Wilt u Radio Browser toevoegen aan Home Assistant?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/radio_browser/translations/pt-BR.json b/homeassistant/components/radio_browser/translations/pt-BR.json new file mode 100644 index 00000000000000..b25a8cbef92b76 --- /dev/null +++ b/homeassistant/components/radio_browser/translations/pt-BR.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 est\u00e1 configurado. Apenas uma \u00fanica configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "step": { + "user": { + "description": "Deseja adicionar o Radio Browser ao Home Assistant?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/radio_browser/translations/ru.json b/homeassistant/components/radio_browser/translations/ru.json new file mode 100644 index 00000000000000..f97f10c1efb1e5 --- /dev/null +++ b/homeassistant/components/radio_browser/translations/ru.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." + }, + "step": { + "user": { + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c Radio Browser?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rainforest_eagle/translations/el.json b/homeassistant/components/rainforest_eagle/translations/el.json index eab8417f7fac9c..fcf9a27cd1bb42 100644 --- a/homeassistant/components/rainforest_eagle/translations/el.json +++ b/homeassistant/components/rainforest_eagle/translations/el.json @@ -4,6 +4,8 @@ "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03af\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7" }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "unknown": "\u0391\u03bd\u03b5\u03c0\u03ac\u03bd\u03c4\u03b5\u03c7\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { diff --git a/homeassistant/components/rainmachine/translations/el.json b/homeassistant/components/rainmachine/translations/el.json index e7aea4fbd89aad..a244cc58ab3916 100644 --- a/homeassistant/components/rainmachine/translations/el.json +++ b/homeassistant/components/rainmachine/translations/el.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "flow_title": "{ip}", "step": { "user": { diff --git a/homeassistant/components/recollect_waste/translations/el.json b/homeassistant/components/recollect_waste/translations/el.json index 2f816f71c3c9a9..fa4af8002485ac 100644 --- a/homeassistant/components/recollect_waste/translations/el.json +++ b/homeassistant/components/recollect_waste/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { "invalid_place_or_service_id": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03b8\u03ad\u03c3\u03b7\u03c2 \u03ae \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1\u03c2" }, diff --git a/homeassistant/components/renault/translations/el.json b/homeassistant/components/renault/translations/el.json index e7e994b2a5607e..1d8c0e543fed69 100644 --- a/homeassistant/components/renault/translations/el.json +++ b/homeassistant/components/renault/translations/el.json @@ -1,7 +1,12 @@ { "config": { "abort": { - "kamereon_no_account": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7\u03c2 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd Kamereon" + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "kamereon_no_account": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7\u03c2 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd Kamereon", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "invalid_credentials": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "step": { "kamereon": { @@ -14,7 +19,8 @@ "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, - "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username}" + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username}", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, "user": { "data": { diff --git a/homeassistant/components/rfxtrx/translations/el.json b/homeassistant/components/rfxtrx/translations/el.json index cbc8f9fb3543c7..b04b6172969b0f 100644 --- a/homeassistant/components/rfxtrx/translations/el.json +++ b/homeassistant/components/rfxtrx/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "error": { @@ -9,6 +10,7 @@ "step": { "setup_network": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "port": "\u0398\u03cd\u03c1\u03b1" }, "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" @@ -20,6 +22,9 @@ "title": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" }, "setup_serial_manual_path": { + "data": { + "device": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 USB" + }, "title": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae" }, "user": { @@ -42,10 +47,12 @@ }, "options": { "error": { + "already_configured_device": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "invalid_event_code": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03bf\u03c2", "invalid_input_2262_off": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03b3\u03b9\u03b1 \u03b5\u03bd\u03c4\u03bf\u03bb\u03ae off", "invalid_input_2262_on": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03b3\u03b9\u03b1 \u03b5\u03bd\u03c4\u03bf\u03bb\u03ae on", - "invalid_input_off_delay": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03b3\u03b9\u03b1 \u03ba\u03b1\u03b8\u03c5\u03c3\u03c4\u03ad\u03c1\u03b7\u03c3\u03b7 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2" + "invalid_input_off_delay": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03b3\u03b9\u03b1 \u03ba\u03b1\u03b8\u03c5\u03c3\u03c4\u03ad\u03c1\u03b7\u03c3\u03b7 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "prompt_options": { @@ -67,7 +74,8 @@ "off_delay": "\u039a\u03b1\u03b8\u03c5\u03c3\u03c4\u03ad\u03c1\u03b7\u03c3\u03b7 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2", "off_delay_enabled": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03ba\u03b1\u03b8\u03c5\u03c3\u03c4\u03ad\u03c1\u03b7\u03c3\u03b7\u03c2 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2", "replace_device": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b3\u03b9\u03b1 \u03b1\u03bd\u03c4\u03b9\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7", - "signal_repetitions": "\u0391\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03b5\u03c0\u03b1\u03bd\u03b1\u03bb\u03ae\u03c8\u03b5\u03c9\u03bd \u03c3\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2" + "signal_repetitions": "\u0391\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03b5\u03c0\u03b1\u03bd\u03b1\u03bb\u03ae\u03c8\u03b5\u03c9\u03bd \u03c3\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2", + "venetian_blind_mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b2\u03b5\u03bd\u03b5\u03c4\u03c3\u03b9\u03ac\u03bd\u03b9\u03ba\u03b7\u03c2 \u03c0\u03b5\u03c1\u03c3\u03af\u03b4\u03b1\u03c2" }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" } diff --git a/homeassistant/components/ridwell/translations/el.json b/homeassistant/components/ridwell/translations/el.json index 54e03b32b8acb8..d8f4fa09850d41 100644 --- a/homeassistant/components/ridwell/translations/el.json +++ b/homeassistant/components/ridwell/translations/el.json @@ -1,11 +1,20 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "reauth_confirm": { "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, - "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username}:" + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username}:", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, "user": { "data": { diff --git a/homeassistant/components/ring/translations/el.json b/homeassistant/components/ring/translations/el.json index ed809f65e7c78b..5d50d06388de63 100644 --- a/homeassistant/components/ring/translations/el.json +++ b/homeassistant/components/ring/translations/el.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "2fa": { "data": { diff --git a/homeassistant/components/risco/translations/el.json b/homeassistant/components/risco/translations/el.json index dd977e968601a4..18799057793050 100644 --- a/homeassistant/components/risco/translations/el.json +++ b/homeassistant/components/risco/translations/el.json @@ -1,6 +1,10 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03c5\u03b8\u03b5\u03bd\u03c4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", "unknown": "\u039c\u03b7 \u03b1\u03bd\u03b1\u03bc\u03b5\u03bd\u03cc\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, @@ -19,6 +23,7 @@ "ha_to_risco": { "data": { "armed_away": "\u039f\u03c0\u03bb\u03b9\u03c3\u03bc\u03cc\u03c2 \u0395\u03ba\u03c4\u03cc\u03c2", + "armed_custom_bypass": "\u039f\u03c0\u03bb\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7 \u03a0\u03c1\u03bf\u03c3\u03b1\u03c1\u03bc\u03bf\u03c3\u03bc\u03ad\u03bd\u03b7 \u03a0\u03b1\u03c1\u03ac\u03ba\u03b1\u03bc\u03c8\u03b7", "armed_home": "\u039f\u03c0\u03bb\u03b9\u03c3\u03bc\u03cc\u03c2 \u0395\u03bd\u03c4\u03cc\u03c2", "armed_night": "\u039f\u03c0\u03bb\u03b9\u03c3\u03bc\u03cc\u03c2 \u03bd\u03cd\u03c7\u03c4\u03b1\u03c2" }, diff --git a/homeassistant/components/rituals_perfume_genie/translations/el.json b/homeassistant/components/rituals_perfume_genie/translations/el.json index ce96a2c2ab6bea..2bc22d498e13d5 100644 --- a/homeassistant/components/rituals_perfume_genie/translations/el.json +++ b/homeassistant/components/rituals_perfume_genie/translations/el.json @@ -1,7 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/roku/translations/el.json b/homeassistant/components/roku/translations/el.json index 02e2d48ac9c1bb..91087c73a58a86 100644 --- a/homeassistant/components/roku/translations/el.json +++ b/homeassistant/components/roku/translations/el.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "flow_title": "{name}", "step": { "discovery_confirm": { diff --git a/homeassistant/components/roomba/translations/el.json b/homeassistant/components/roomba/translations/el.json index 1c490c0ea473cc..e59ce426deb86a 100644 --- a/homeassistant/components/roomba/translations/el.json +++ b/homeassistant/components/roomba/translations/el.json @@ -1,10 +1,14 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "not_irobot_device": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae iRobot", "short_blid": "\u03a4\u03bf BLID \u03c0\u03b5\u03c1\u03b9\u03ba\u03cc\u03c0\u03b7\u03ba\u03b5" }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "flow_title": "{name} ({host})", "step": { "init": { diff --git a/homeassistant/components/roon/translations/el.json b/homeassistant/components/roon/translations/el.json index ab245da07eef7c..16fca72db26b5c 100644 --- a/homeassistant/components/roon/translations/el.json +++ b/homeassistant/components/roon/translations/el.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "link": { "description": "\u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Home Assistant \u03c3\u03c4\u03bf Roon. \u0391\u03c6\u03bf\u03cd \u03ba\u03ac\u03bd\u03b5\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03b7\u03bd \u03c5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae, \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae Roon Core, \u03b1\u03bd\u03bf\u03af\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03ba\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf HomeAssistant \u03c3\u03c4\u03b7\u03bd \u03ba\u03b1\u03c1\u03c4\u03ad\u03bb\u03b1 \u0395\u03c0\u03b5\u03ba\u03c4\u03ac\u03c3\u03b5\u03b9\u03c2.", diff --git a/homeassistant/components/ruckus_unleashed/translations/el.json b/homeassistant/components/ruckus_unleashed/translations/el.json index bab52704f790ea..877622243c8a8f 100644 --- a/homeassistant/components/ruckus_unleashed/translations/el.json +++ b/homeassistant/components/ruckus_unleashed/translations/el.json @@ -1,8 +1,17 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } diff --git a/homeassistant/components/samsungtv/translations/el.json b/homeassistant/components/samsungtv/translations/el.json index 5ede2b35303e8c..aa2ec61978bd5f 100644 --- a/homeassistant/components/samsungtv/translations/el.json +++ b/homeassistant/components/samsungtv/translations/el.json @@ -1,10 +1,18 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", "auth_missing": "\u03a4\u03bf Home Assistant \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03b7\u03bc\u03ad\u03bd\u03bf \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03c3\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7 Samsung. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c4\u03b7\u03c2 \u0394\u03b9\u03b1\u03c7\u03b5\u03af\u03c1\u03b9\u03c3\u03b7\u03c2 \u03b5\u03be\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03ce\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd \u03c4\u03b7\u03c2 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Home Assistant.", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "id_missing": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Samsung \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 SerialNumber.", "missing_config_entry": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Samsung \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2.", - "not_supported": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Samsung \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7 \u03c3\u03c4\u03b9\u03b3\u03bc\u03ae." + "not_supported": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Samsung \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7 \u03c3\u03c4\u03b9\u03b3\u03bc\u03ae.", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "error": { + "auth_missing": "\u03a4\u03bf Home Assistant \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03b7\u03bc\u03ad\u03bd\u03bf \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03c3\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7 Samsung. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c4\u03b7\u03c2 \u0394\u03b9\u03b1\u03c7\u03b5\u03af\u03c1\u03b9\u03c3\u03b7\u03c2 \u03b5\u03be\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03ce\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd \u03c4\u03b7\u03c2 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Home Assistant." }, "flow_title": "{device}", "step": { @@ -17,7 +25,8 @@ }, "user": { "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c4\u03b7\u03c2 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 Samsung. \u0395\u03ac\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03b9 \u03c0\u03bf\u03c4\u03ad \u03c0\u03c1\u03b9\u03bd \u03c4\u03bf Home Assistant, \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b4\u03b5\u03af\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03b1\u03bd\u03b1\u03b4\u03c5\u03cc\u03bc\u03b5\u03bd\u03bf \u03c0\u03b1\u03c1\u03ac\u03b8\u03c5\u03c1\u03bf \u03c3\u03c4\u03b7\u03bd \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae \u03c3\u03b1\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03c3\u03b1\u03c2 \u03b6\u03b7\u03c4\u03ac \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7." } diff --git a/homeassistant/components/sense/translations/ca.json b/homeassistant/components/sense/translations/ca.json index aff80de710d6a9..f852feefb0e73e 100644 --- a/homeassistant/components/sense/translations/ca.json +++ b/homeassistant/components/sense/translations/ca.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "El dispositiu ja est\u00e0 configurat" + "already_configured": "El dispositiu ja est\u00e0 configurat", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3", @@ -9,6 +10,13 @@ "unknown": "Error inesperat" }, "step": { + "reauth_validate": { + "data": { + "password": "Contrasenya" + }, + "description": "La integraci\u00f3 Sense ha de tornar a autenticar el compte {email}.", + "title": "Reautenticaci\u00f3 de la integraci\u00f3" + }, "user": { "data": { "email": "Correu electr\u00f2nic", @@ -16,6 +24,12 @@ "timeout": "Temps d'espera" }, "title": "Connexi\u00f3 amb Sense Energy Monitor" + }, + "validation": { + "data": { + "code": "Codi de verificaci\u00f3" + }, + "title": "Autenticaci\u00f3 multi-factor de Sense" } } } diff --git a/homeassistant/components/sense/translations/el.json b/homeassistant/components/sense/translations/el.json index e8d227cdedcb53..e735bf09f7dcaa 100644 --- a/homeassistant/components/sense/translations/el.json +++ b/homeassistant/components/sense/translations/el.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/sense/translations/pt-BR.json b/homeassistant/components/sense/translations/pt-BR.json index 3544bd22dc33aa..5944daf63caf3f 100644 --- a/homeassistant/components/sense/translations/pt-BR.json +++ b/homeassistant/components/sense/translations/pt-BR.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { "cannot_connect": "Falha ao conectar", @@ -9,6 +10,13 @@ "unknown": "Erro inesperado" }, "step": { + "reauth_validate": { + "data": { + "password": "Senha" + }, + "description": "A integra\u00e7\u00e3o do Sense precisa autenticar novamente sua conta {email} .", + "title": "Reautenticar Integra\u00e7\u00e3o" + }, "user": { "data": { "email": "Email", @@ -16,6 +24,12 @@ "timeout": "Tempo limite" }, "title": "Conecte-se ao seu monitor de Energia Sense" + }, + "validation": { + "data": { + "code": "C\u00f3digo de verifica\u00e7\u00e3o" + }, + "title": "Sense autentica\u00e7\u00e3o multifator" } } } diff --git a/homeassistant/components/senseme/translations/el.json b/homeassistant/components/senseme/translations/el.json index f1d52c417a8abc..f19dd73dde963d 100644 --- a/homeassistant/components/senseme/translations/el.json +++ b/homeassistant/components/senseme/translations/el.json @@ -1,8 +1,13 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_host": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + }, "flow_title": "{name} - {model} ({host})", "step": { "discovery_confirm": { diff --git a/homeassistant/components/sensibo/translations/el.json b/homeassistant/components/sensibo/translations/el.json index 34312fd03a1939..455c89deba4e4e 100644 --- a/homeassistant/components/sensibo/translations/el.json +++ b/homeassistant/components/sensibo/translations/el.json @@ -1,11 +1,15 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "step": { "user": { "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", "name": "\u038c\u03bd\u03bf\u03bc\u03b1" } } diff --git a/homeassistant/components/sensor/translations/el.json b/homeassistant/components/sensor/translations/el.json index 23d8a479b73eef..25b4e7bd72b2da 100644 --- a/homeassistant/components/sensor/translations/el.json +++ b/homeassistant/components/sensor/translations/el.json @@ -3,6 +3,8 @@ "condition_type": { "is_apparent_power": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03c6\u03b1\u03b9\u03bd\u03bf\u03bc\u03b5\u03bd\u03b9\u03ba\u03ae \u03b9\u03c3\u03c7\u03cd\u03c2 {entity_name}", "is_battery_level": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03bc\u03c0\u03b1\u03c4\u03b1\u03c1\u03af\u03b1\u03c2 {entity_name}", + "is_carbon_dioxide": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03b4\u03b9\u03bf\u03be\u03b5\u03b9\u03b4\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 \u03ac\u03bd\u03b8\u03c1\u03b1\u03ba\u03b1 \u03c4\u03bf\u03c5 {entity_name}", + "is_carbon_monoxide": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03bc\u03bf\u03bd\u03bf\u03be\u03b5\u03b9\u03b4\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 \u03ac\u03bd\u03b8\u03c1\u03b1\u03ba\u03b1 \u03c4\u03bf\u03c5 {entity_name}", "is_energy": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03b5\u03bd\u03ad\u03c1\u03b3\u03b5\u03b9\u03b1 {entity_name}", "is_frequency": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03c3\u03c5\u03c7\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 {entity_name}", "is_gas": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b1\u03ad\u03c1\u03b9\u03bf {entity_name}", @@ -22,13 +24,21 @@ "is_signal_strength": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03b9\u03c3\u03c7\u03cd\u03c2 \u03c3\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2 {entity_name}", "is_sulphur_dioxide": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03b4\u03b9\u03bf\u03be\u03b5\u03b9\u03b4\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 \u03b8\u03b5\u03af\u03bf\u03c5 {entity_name}", "is_temperature": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1 {entity_name}", + "is_value": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03c4\u03b9\u03bc\u03ae \u03c4\u03bf\u03c5 {entity_name}", "is_volatile_organic_compounds": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03c0\u03c4\u03b7\u03c4\u03b9\u03ba\u03ce\u03bd \u03bf\u03c1\u03b3\u03b1\u03bd\u03b9\u03ba\u03ce\u03bd \u03b5\u03bd\u03ce\u03c3\u03b5\u03c9\u03bd {entity_name}", "is_voltage": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03c4\u03ac\u03c3\u03b7 {entity_name}" }, "trigger_type": { + "apparent_power": "\u0395\u03bc\u03c6\u03b1\u03bd\u03b5\u03af\u03c2 \u03b1\u03bb\u03bb\u03b1\u03b3\u03ad\u03c2 \u03b9\u03c3\u03c7\u03cd\u03bf\u03c2 {entity_name}", "battery_level": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ae \u03b5\u03c0\u03b9\u03c0\u03ad\u03b4\u03bf\u03c5 \u03bc\u03c0\u03b1\u03c4\u03b1\u03c1\u03af\u03b1\u03c2 \u03b3\u03b9\u03b1 {entity_name}", + "carbon_dioxide": "\u0397 \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03bf\u03be\u03b5\u03b9\u03b4\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 \u03ac\u03bd\u03b8\u03c1\u03b1\u03ba\u03b1 \u03c4\u03bf\u03c5 {entity_name} \u03b1\u03bb\u03bb\u03ac\u03b6\u03b5\u03b9", + "carbon_monoxide": "\u0397 \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03bc\u03bf\u03bd\u03bf\u03be\u03b5\u03b9\u03b4\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 \u03ac\u03bd\u03b8\u03c1\u03b1\u03ba\u03b1 \u03c4\u03bf\u03c5 {entity_name} \u03b1\u03bb\u03bb\u03ac\u03b6\u03b5\u03b9", + "current": "{entity_name} \u03c4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b5\u03c2 \u03b1\u03bb\u03bb\u03b1\u03b3\u03ad\u03c2", + "energy": "\u0397 \u03b5\u03bd\u03ad\u03c1\u03b3\u03b5\u03b9\u03b1 \u03c4\u03bf\u03c5 {entity_name} \u03b1\u03bb\u03bb\u03ac\u03b6\u03b5\u03b9", "frequency": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ad\u03c2 \u03c3\u03c5\u03c7\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 {entity_name}", "gas": "{entity_name} \u03bc\u03b5\u03c4\u03b1\u03b2\u03bf\u03bb\u03ad\u03c2 \u03b1\u03b5\u03c1\u03af\u03bf\u03c5", + "humidity": "\u0397 \u03c5\u03b3\u03c1\u03b1\u03c3\u03af\u03b1 \u03c4\u03bf\u03c5 {entity_name} \u03b1\u03bb\u03bb\u03ac\u03b6\u03b5\u03b9", + "illuminance": "\u039f \u03c6\u03c9\u03c4\u03b9\u03c3\u03bc\u03cc\u03c2 \u03c4\u03bf\u03c5 {entity_name} \u03b1\u03bb\u03bb\u03ac\u03b6\u03b5\u03b9", "nitrogen_dioxide": "{entity_name} \u03bc\u03b5\u03c4\u03b1\u03b2\u03bf\u03bb\u03ad\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03b4\u03b9\u03bf\u03be\u03b5\u03b9\u03b4\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 \u03b1\u03b6\u03ce\u03c4\u03bf\u03c5", "nitrogen_monoxide": "{entity_name} \u03bc\u03b5\u03c4\u03b1\u03b2\u03bf\u03bb\u03ad\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03bc\u03bf\u03bd\u03bf\u03be\u03b5\u03b9\u03b4\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 \u03b1\u03b6\u03ce\u03c4\u03bf\u03c5", "nitrous_oxide": "{entity_name} \u03bc\u03b5\u03c4\u03b1\u03b2\u03bf\u03bb\u03ad\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03bf\u03be\u03b5\u03b9\u03b4\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 \u03b1\u03b6\u03ce\u03c4\u03bf\u03c5", @@ -36,8 +46,14 @@ "pm1": "{entity_name} \u03bc\u03b5\u03c4\u03b1\u03b2\u03bf\u03bb\u03ad\u03c2 \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 PM1", "pm10": "{entity_name} \u03bc\u03b5\u03c4\u03b1\u03b2\u03bf\u03bb\u03ad\u03c2 \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 PM10", "pm25": "{entity_name} \u03bc\u03b5\u03c4\u03b1\u03b2\u03bf\u03bb\u03ad\u03c2 \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 PM2.5", + "power": "\u0397 \u03b9\u03c3\u03c7\u03cd\u03c2 \u03c4\u03bf\u03c5 {entity_name} \u03b1\u03bb\u03bb\u03ac\u03b6\u03b5\u03b9", "power_factor": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ae \u03c3\u03c5\u03bd\u03c4\u03b5\u03bb\u03b5\u03c3\u03c4\u03ae \u03b9\u03c3\u03c7\u03cd\u03bf\u03c2 {entity_name}", + "pressure": "\u0397 \u03c0\u03af\u03b5\u03c3\u03b7 \u03c4\u03bf\u03c5 {entity_name} \u03b1\u03bb\u03bb\u03ac\u03b6\u03b5\u03b9", + "reactive_power": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ad\u03c2 \u03b1\u03ad\u03c1\u03b3\u03bf\u03c5 \u03b9\u03c3\u03c7\u03cd\u03bf\u03c2 {entity_name}", + "signal_strength": "\u0397 \u03b9\u03c3\u03c7\u03cd\u03c2 \u03c4\u03bf\u03c5 \u03c3\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2 \u03c4\u03bf\u03c5 {entity_name} \u03b1\u03bb\u03bb\u03ac\u03b6\u03b5\u03b9", "sulphur_dioxide": "{entity_name} \u03bc\u03b5\u03c4\u03b1\u03b2\u03bf\u03bb\u03ad\u03c2 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7\u03c2 \u03b4\u03b9\u03bf\u03be\u03b5\u03b9\u03b4\u03af\u03bf\u03c5 \u03c4\u03bf\u03c5 \u03b8\u03b5\u03af\u03bf\u03c5", + "temperature": "\u0397 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1 \u03c4\u03bf\u03c5 {entity_name} \u03b1\u03bb\u03bb\u03ac\u03b6\u03b5\u03b9", + "value": "\u0397 \u03c4\u03b9\u03bc\u03ae \u03c4\u03bf\u03c5 {entity_name} \u03b1\u03bb\u03bb\u03ac\u03b6\u03b5\u03b9", "volatile_organic_compounds": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ad\u03c2 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03b3\u03ba\u03ad\u03bd\u03c4\u03c1\u03c9\u03c3\u03b7 \u03c4\u03c9\u03bd \u03c0\u03c4\u03b7\u03c4\u03b9\u03ba\u03ce\u03bd \u03bf\u03c1\u03b3\u03b1\u03bd\u03b9\u03ba\u03ce\u03bd \u03b5\u03bd\u03ce\u03c3\u03b5\u03c9\u03bd {entity_name}", "voltage": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ae \u03c4\u03ac\u03c3\u03b7\u03c2 {entity_name}" } diff --git a/homeassistant/components/sensor/translations/it.json b/homeassistant/components/sensor/translations/it.json index 66aab37914d737..1e13fb79fd2a9d 100644 --- a/homeassistant/components/sensor/translations/it.json +++ b/homeassistant/components/sensor/translations/it.json @@ -34,12 +34,12 @@ "battery_level": "variazioni del livello di batteria di {entity_name} ", "carbon_dioxide": "Variazioni della concentrazione di anidride carbonica di {entity_name}", "carbon_monoxide": "Variazioni nella concentrazione di monossido di carbonio di {entity_name}", - "current": "variazioni di corrente di {entity_name}", - "energy": "variazioni di energia di {entity_name}", + "current": "Variazioni di corrente di {entity_name}", + "energy": "Variazioni di energia di {entity_name}", "frequency": "{entity_name} cambiamenti di frequenza", "gas": "Variazioni di gas di {entity_name}", - "humidity": "variazioni di umidit\u00e0 di {entity_name} ", - "illuminance": "variazioni dell'illuminazione di {entity_name}", + "humidity": "Variazioni di umidit\u00e0 di {entity_name} ", + "illuminance": "Variazioni dell'illuminazione di {entity_name}", "nitrogen_dioxide": "Variazioni della concentrazione di biossido di azoto di {entity_name}", "nitrogen_monoxide": "Variazioni della concentrazione di monossido di azoto di {entity_name}", "nitrous_oxide": "Variazioni della concentrazione di ossidi di azoto di {entity_name}", @@ -47,14 +47,14 @@ "pm1": "Variazioni della concentrazione di PM1 di {entity_name}", "pm10": "Variazioni della concentrazione di PM10 di {entity_name}", "pm25": "Variazioni della concentrazione di PM2.5 di {entity_name}", - "power": "variazioni di alimentazione di {entity_name}", + "power": "Variazioni di alimentazione di {entity_name}", "power_factor": "variazioni del fattore di potenza di {entity_name}", - "pressure": "variazioni della pressione di {entity_name}", + "pressure": "Variazioni della pressione di {entity_name}", "reactive_power": "variazioni di potenza reattiva di {entity_name}", - "signal_strength": "variazioni della potenza del segnale di {entity_name}", + "signal_strength": "Variazioni della potenza del segnale di {entity_name}", "sulphur_dioxide": "Variazioni della concentrazione di anidride solforosa di {entity_name}", - "temperature": "variazioni di temperatura di {entity_name}", - "value": "{entity_name} valori cambiati", + "temperature": "Variazioni di temperatura di {entity_name}", + "value": "Cambi di valore di {entity_name}", "volatile_organic_compounds": "Variazioni della concentrazione di composti organici volatili di {entity_name}", "voltage": "variazioni di tensione di {entity_name}" } diff --git a/homeassistant/components/sentry/translations/el.json b/homeassistant/components/sentry/translations/el.json index 4e39e2bdf6f59e..fd64b2140e261c 100644 --- a/homeassistant/components/sentry/translations/el.json +++ b/homeassistant/components/sentry/translations/el.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, "error": { - "bad_dsn": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf DSN" + "bad_dsn": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf DSN", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/sharkiq/translations/el.json b/homeassistant/components/sharkiq/translations/el.json index 3f7c4990cd012e..83187c3809074d 100644 --- a/homeassistant/components/sharkiq/translations/el.json +++ b/homeassistant/components/sharkiq/translations/el.json @@ -1,8 +1,16 @@ { "config": { "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2", "unknown": "\u039c\u03b7 \u03b1\u03bd\u03b1\u03bc\u03b5\u03bd\u03cc\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "reauth": { "data": { diff --git a/homeassistant/components/shelly/translations/el.json b/homeassistant/components/shelly/translations/el.json index 912cdfe64aa252..c4a52891406171 100644 --- a/homeassistant/components/shelly/translations/el.json +++ b/homeassistant/components/shelly/translations/el.json @@ -1,8 +1,14 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "unsupported_firmware": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03bc\u03b9\u03b1 \u03bc\u03b7 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03b7 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 \u03c5\u03bb\u03b9\u03ba\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03bc\u03b9\u03ba\u03bf\u03cd." }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "flow_title": "Shelly: {\u03cc\u03bd\u03bf\u03bc\u03b1}", "step": { "confirm_discovery": { @@ -31,6 +37,16 @@ "button4": "\u03a4\u03ad\u03c4\u03b1\u03c1\u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af" }, "trigger_type": { + "btn_down": "\u039a\u03bf\u03c5\u03bc\u03c0\u03af {subtype} \u03ba\u03ac\u03c4\u03c9", + "btn_up": "\u039a\u03bf\u03c5\u03bc\u03c0\u03af {subtype} \u03b5\u03c0\u03ac\u03bd\u03c9", + "double": "\u0394\u03b9\u03c0\u03bb\u03cc \u03ba\u03bb\u03b9\u03ba \u03c4\u03bf\u03c5 {subtype}", + "double_push": "{subtype} \u03b4\u03b9\u03c0\u03bb\u03ae \u03ce\u03b8\u03b7\u03c3\u03b7", + "long": "\u03a0\u03b1\u03c1\u03b1\u03c4\u03b5\u03c4\u03b1\u03bc\u03ad\u03bd\u03bf \u03ba\u03bb\u03b9\u03ba \u03c4\u03bf\u03c5 {subtype}", + "long_push": "{subtype} \u03bc\u03b1\u03ba\u03c1\u03ac \u03ce\u03b8\u03b7\u03c3\u03b7", + "long_single": "\u03a0\u03b1\u03c1\u03b1\u03c4\u03b5\u03c4\u03b1\u03bc\u03ad\u03bd\u03bf \u03ba\u03bb\u03b9\u03ba \u03ba\u03b1\u03b9 \u03bc\u03b5\u03c4\u03ac \u03ad\u03bd\u03b1 \u03bc\u03cc\u03bd\u03bf \u03ba\u03bb\u03b9\u03ba \u03c4\u03bf\u03c5 {subtype}", + "single": "\u039c\u03bf\u03bd\u03cc \u03ba\u03bb\u03b9\u03ba \u03c4\u03bf\u03c5 {subtype}", + "single_long": "\u039c\u03bf\u03bd\u03cc \u03ba\u03bb\u03b9\u03ba \u03ba\u03b1\u03b9 \u03bc\u03b5\u03c4\u03ac \u03c0\u03b1\u03c1\u03b1\u03c4\u03b5\u03c4\u03b1\u03bc\u03ad\u03bd\u03bf \u03ba\u03bb\u03b9\u03ba \u03c4\u03bf\u03c5 {subtype}", + "single_push": "{subtype} \u03bc\u03bf\u03bd\u03ae \u03ce\u03b8\u03b7\u03c3\u03b7", "triple": "\u03a4\u03c1\u03b9\u03c0\u03bb\u03cc \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf {subtype}" } } diff --git a/homeassistant/components/shelly/translations/it.json b/homeassistant/components/shelly/translations/it.json index ec20d3a7b26e4a..c004141cac47d9 100644 --- a/homeassistant/components/shelly/translations/it.json +++ b/homeassistant/components/shelly/translations/it.json @@ -41,7 +41,7 @@ "btn_up": "{subtype} pulsante in su", "double": "{subtype} premuto due volte", "double_push": "{subtype} doppia pressione", - "long": "{subtype} cliccato a lungo", + "long": "{subtype} premuto a lungo", "long_push": "{subtype} pressione prolungata", "long_single": "{subtype} premuto a lungo e poi singolarmente", "single": "{subtype} premuto singolarmente", diff --git a/homeassistant/components/shopping_list/translations/el.json b/homeassistant/components/shopping_list/translations/el.json index e4ad51b4d022c3..fe1c8f0f259a4c 100644 --- a/homeassistant/components/shopping_list/translations/el.json +++ b/homeassistant/components/shopping_list/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, "step": { "user": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03bb\u03af\u03c3\u03c4\u03b1 \u03b1\u03b3\u03bf\u03c1\u03ce\u03bd;", diff --git a/homeassistant/components/sia/translations/el.json b/homeassistant/components/sia/translations/el.json index be8651c1043107..fe565e503a14d4 100644 --- a/homeassistant/components/sia/translations/el.json +++ b/homeassistant/components/sia/translations/el.json @@ -6,10 +6,18 @@ "invalid_key_format": "\u03a4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b5\u03ba\u03b1\u03b5\u03be\u03b1\u03b4\u03b9\u03ba\u03ae \u03c4\u03b9\u03bc\u03ae, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03bc\u03cc\u03bd\u03bf 0-9 \u03ba\u03b1\u03b9 A-F.", "invalid_key_length": "\u03a4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c4\u03bf \u03c3\u03c9\u03c3\u03c4\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2, \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 16, 24 \u03ae 32 \u03b4\u03b5\u03ba\u03b1\u03b5\u03be\u03b1\u03b4\u03b9\u03ba\u03bf\u03af \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b5\u03c2.", "invalid_ping": "\u03a4\u03bf \u03b4\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 ping \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03bc\u03b5\u03c4\u03b1\u03be\u03cd 1 \u03ba\u03b1\u03b9 1440 \u03bb\u03b5\u03c0\u03c4\u03ce\u03bd.", - "invalid_zones": "\u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03c4\u03bf\u03c5\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf\u03bd 1 \u03b6\u03ce\u03bd\u03b7." + "invalid_zones": "\u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03c4\u03bf\u03c5\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf\u03bd 1 \u03b6\u03ce\u03bd\u03b7.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "additional_account": { + "data": { + "account": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd", + "additional_account": "\u03a0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03b9 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03af", + "encryption_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03ba\u03c1\u03c5\u03c0\u03c4\u03bf\u03b3\u03c1\u03ac\u03c6\u03b7\u03c3\u03b7\u03c2", + "ping_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 ping (\u03bb\u03b5\u03c0\u03c4\u03ac)", + "zones": "\u0391\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03b6\u03c9\u03bd\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc" + }, "title": "\u03a0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03ac\u03bb\u03bb\u03bf\u03c5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03c3\u03c4\u03b7\u03bd \u03c4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03b8\u03cd\u03c1\u03b1." }, "user": { @@ -30,7 +38,8 @@ "step": { "options": { "data": { - "ignore_timestamps": "\u0391\u03b3\u03bd\u03bf\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b7\u03c2 \u03c7\u03c1\u03bf\u03bd\u03bf\u03c3\u03c6\u03c1\u03b1\u03b3\u03af\u03b4\u03b1\u03c2 \u03c4\u03c9\u03bd \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03c9\u03bd SIA" + "ignore_timestamps": "\u0391\u03b3\u03bd\u03bf\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b7\u03c2 \u03c7\u03c1\u03bf\u03bd\u03bf\u03c3\u03c6\u03c1\u03b1\u03b3\u03af\u03b4\u03b1\u03c2 \u03c4\u03c9\u03bd \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03c9\u03bd SIA", + "zones": "\u0391\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03b6\u03c9\u03bd\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc" }, "description": "\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc: {account}", "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 SIA." diff --git a/homeassistant/components/simplisafe/translations/el.json b/homeassistant/components/simplisafe/translations/el.json index f6a55f9897c7ae..d35c59bcc409ee 100644 --- a/homeassistant/components/simplisafe/translations/el.json +++ b/homeassistant/components/simplisafe/translations/el.json @@ -2,11 +2,14 @@ "config": { "abort": { "already_configured": "\u0391\u03c5\u03c4\u03cc\u03c2 \u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 SimpliSafe \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7.", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2", "wrong_account": "\u03a4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03c0\u03bf\u03c5 \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b4\u03b5\u03bd \u03c4\u03b1\u03b9\u03c1\u03b9\u03ac\u03b6\u03bf\u03c5\u03bd \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc SimpliSafe." }, "error": { "identifier_exists": "\u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ae\u03b4\u03b7 \u03ba\u03b1\u03c4\u03b1\u03c7\u03c9\u03c1\u03b7\u03bc\u03ad\u03bd\u03bf\u03c2", - "still_awaiting_mfa": "\u0391\u03bd\u03b1\u03bc\u03ad\u03bd\u03b5\u03c4\u03b1\u03b9 \u03b1\u03ba\u03cc\u03bc\u03b7 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf email \u03c4\u03bf\u03c5 \u03a5\u03c0\u03bf\u03c5\u03c1\u03b3\u03b5\u03af\u03bf\u03c5 \u039f\u03b9\u03ba\u03bf\u03bd\u03bf\u03bc\u03b9\u03ba\u03ce\u03bd" + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "still_awaiting_mfa": "\u0391\u03bd\u03b1\u03bc\u03ad\u03bd\u03b5\u03c4\u03b1\u03b9 \u03b1\u03ba\u03cc\u03bc\u03b7 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf email \u03c4\u03bf\u03c5 \u03a5\u03c0\u03bf\u03c5\u03c1\u03b3\u03b5\u03af\u03bf\u03c5 \u039f\u03b9\u03ba\u03bf\u03bd\u03bf\u03bc\u03b9\u03ba\u03ce\u03bd", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "input_auth_code": { @@ -24,7 +27,8 @@ "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, - "description": "\u0397 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae \u03c3\u03b1\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03bb\u03ae\u03be\u03b5\u03b9 \u03ae \u03b1\u03bd\u03b1\u03ba\u03bb\u03b7\u03b8\u03b5\u03af. \u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2." + "description": "\u0397 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae \u03c3\u03b1\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03bb\u03ae\u03be\u03b5\u03b9 \u03ae \u03b1\u03bd\u03b1\u03ba\u03bb\u03b7\u03b8\u03b5\u03af. \u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2.", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, "user": { "data": { diff --git a/homeassistant/components/sleepiq/translations/el.json b/homeassistant/components/sleepiq/translations/el.json index 6a74424c88834e..45b5ef57ba5b55 100644 --- a/homeassistant/components/sleepiq/translations/el.json +++ b/homeassistant/components/sleepiq/translations/el.json @@ -1,12 +1,17 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "step": { "user": { "data": { - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } } diff --git a/homeassistant/components/sleepiq/translations/nl.json b/homeassistant/components/sleepiq/translations/nl.json new file mode 100644 index 00000000000000..3271c6bce45dc5 --- /dev/null +++ b/homeassistant/components/sleepiq/translations/nl.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Account is al geconfigureerd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie" + }, + "step": { + "user": { + "data": { + "password": "Wachtwoord", + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sma/translations/el.json b/homeassistant/components/sma/translations/el.json index 929c1cdde065ee..b90ad093a8de5a 100644 --- a/homeassistant/components/sma/translations/el.json +++ b/homeassistant/components/sma/translations/el.json @@ -1,14 +1,23 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7" + }, "error": { - "cannot_retrieve_device_info": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7, \u03b1\u03bb\u03bb\u03ac \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b1\u03bd\u03ac\u03ba\u03c4\u03b7\u03c3\u03b7 \u03c4\u03c9\u03bd \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03b9\u03ce\u03bd \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "cannot_retrieve_device_info": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7, \u03b1\u03bb\u03bb\u03ac \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b1\u03bd\u03ac\u03ba\u03c4\u03b7\u03c3\u03b7 \u03c4\u03c9\u03bd \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03b9\u03ce\u03bd \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { "data": { "group": "\u039f\u03bc\u03ac\u03b4\u03b1", "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "ssl": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03ad\u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL", + "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 SMA.", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 SMA Solar" diff --git a/homeassistant/components/smappee/translations/el.json b/homeassistant/components/smappee/translations/el.json index 67a4de8faf859a..06f797999860de 100644 --- a/homeassistant/components/smappee/translations/el.json +++ b/homeassistant/components/smappee/translations/el.json @@ -1,8 +1,13 @@ { "config": { "abort": { + "already_configured_device": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "already_configured_local_device": "\u0397 \u03c4\u03bf\u03c0\u03b9\u03ba\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae(\u03b5\u03c2) \u03b5\u03af\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7(\u03b5\u03c2). \u0391\u03c6\u03b1\u03b9\u03c1\u03ad\u03c3\u03c4\u03b5 \u03c0\u03c1\u03ce\u03c4\u03b1 \u03b1\u03c5\u03c4\u03ad\u03c2 \u03c0\u03c1\u03b9\u03bd \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03b5\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae cloud.", - "invalid_mdns": "\u039c\u03b7 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 Smappee." + "authorize_url_timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2.", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_mdns": "\u039c\u03b7 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 Smappee.", + "missing_configuration": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7.", + "no_url_available": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL. \u0393\u03b9\u03b1 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1, [\u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b2\u03bf\u03ae\u03b8\u03b5\u03b9\u03b1\u03c2] ( {docs_url} )" }, "flow_title": "{name}", "step": { @@ -13,8 +18,14 @@ "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf Smappee \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf Home Assistant." }, "local": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03b9 \u03b7 \u03c4\u03bf\u03c0\u03b9\u03ba\u03ae \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Smappee" }, + "pick_implementation": { + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03bc\u03b5\u03b8\u03cc\u03b4\u03bf\u03c5 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "zeroconf_confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Smappee \u03bc\u03b5 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c3\u03b5\u03b9\u03c1\u03ac\u03c2 `{serialnumber}` \u03c3\u03c4\u03bf Home Assistant;", "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Smappee" diff --git a/homeassistant/components/smart_meter_texas/translations/el.json b/homeassistant/components/smart_meter_texas/translations/el.json index bab52704f790ea..5b1861a0e4080f 100644 --- a/homeassistant/components/smart_meter_texas/translations/el.json +++ b/homeassistant/components/smart_meter_texas/translations/el.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/smarthab/translations/el.json b/homeassistant/components/smarthab/translations/el.json index ef7089357ab47e..43de6c1ca89021 100644 --- a/homeassistant/components/smarthab/translations/el.json +++ b/homeassistant/components/smarthab/translations/el.json @@ -1,5 +1,10 @@ { "config": { + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "service": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03c0\u03ac\u03b8\u03b5\u03b9\u03b1 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c3\u03c4\u03bf SmartHab. \u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03ba\u03c4\u03cc\u03c2 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03ae \u03c3\u03b1\u03c2.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/smartthings/translations/el.json b/homeassistant/components/smartthings/translations/el.json index ff993f6176ebfe..ea4c841cd13b7a 100644 --- a/homeassistant/components/smartthings/translations/el.json +++ b/homeassistant/components/smartthings/translations/el.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "invalid_webhook_url": "\u03a4\u03bf Home Assistant \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af \u03c3\u03c9\u03c3\u03c4\u03ac \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03b5\u03b9 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03b5\u03b9\u03c2 \u03b1\u03c0\u03cc \u03c4\u03bf SmartThings. \u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c4\u03bf\u03c5 webhook \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7:\n > {webhook_url} \n\n \u0395\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03ae \u03c3\u03b1\u03c2 \u03c3\u03cd\u03bc\u03c6\u03c9\u03bd\u03b1 \u03bc\u03b5 \u03c4\u03b9\u03c2 [\u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2]( {component_url} ), \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf Home Assistant \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac." + "invalid_webhook_url": "\u03a4\u03bf Home Assistant \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af \u03c3\u03c9\u03c3\u03c4\u03ac \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03b5\u03b9 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03b5\u03b9\u03c2 \u03b1\u03c0\u03cc \u03c4\u03bf SmartThings. \u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c4\u03bf\u03c5 webhook \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7:\n > {webhook_url} \n\n \u0395\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03ae \u03c3\u03b1\u03c2 \u03c3\u03cd\u03bc\u03c6\u03c9\u03bd\u03b1 \u03bc\u03b5 \u03c4\u03b9\u03c2 [\u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2]( {component_url} ), \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf Home Assistant \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", + "no_available_locations": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03bd \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b5\u03c2 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b5\u03c2 SmartThings \u03b3\u03b9\u03b1 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c3\u03c4\u03bf Home Assistant." }, "error": { "app_setup_error": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 SmartApp. \u03a0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", @@ -15,10 +16,16 @@ "title": "\u0395\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7 Home Assistant" }, "pat": { + "data": { + "access_token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 SmartThings [Personal Access Token]({token_url}) \u03c0\u03bf\u03c5 \u03ad\u03c7\u03b5\u03b9 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03b7\u03b8\u03b5\u03af \u03c3\u03cd\u03bc\u03c6\u03c9\u03bd\u03b1 \u03bc\u03b5 \u03c4\u03b9\u03c2 [\u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2]({component_url}). \u0391\u03c5\u03c4\u03cc \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 Home Assistant \u03c3\u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 SmartThings.", "title": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03bf\u03c3\u03c9\u03c0\u03b9\u03ba\u03ae\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, "select_location": { + "data": { + "location_id": "\u03a4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1" + }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 SmartThings \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c3\u03c4\u03bf Home Assistant. \u03a3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03b8\u03b1 \u03b1\u03bd\u03bf\u03af\u03be\u03b5\u03b9 \u03ad\u03bd\u03b1 \u03bd\u03ad\u03bf \u03c0\u03b1\u03c1\u03ac\u03b8\u03c5\u03c1\u03bf \u03ba\u03b1\u03b9 \u03b8\u03b1 \u03c3\u03b1\u03c2 \u03b6\u03b7\u03c4\u03b7\u03b8\u03b5\u03af \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03ba\u03b1\u03b9 \u03bd\u03b1 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 Home Assistant \u03c3\u03c4\u03b7\u03bd \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1.", "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1" }, diff --git a/homeassistant/components/smarttub/translations/el.json b/homeassistant/components/smarttub/translations/el.json index 667512e722b7ab..4f6e62a880c820 100644 --- a/homeassistant/components/smarttub/translations/el.json +++ b/homeassistant/components/smarttub/translations/el.json @@ -1,8 +1,16 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "step": { "reauth_confirm": { - "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 SmartTub \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2" + "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 SmartTub \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, "user": { "data": { diff --git a/homeassistant/components/smhi/translations/el.json b/homeassistant/components/smhi/translations/el.json index 65fe6161ed03a8..c852bd3a08e0d4 100644 --- a/homeassistant/components/smhi/translations/el.json +++ b/homeassistant/components/smhi/translations/el.json @@ -9,6 +9,11 @@ }, "step": { "user": { + "data": { + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + }, "title": "\u03a4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03c3\u03c4\u03b7 \u03a3\u03bf\u03c5\u03b7\u03b4\u03af\u03b1" } } diff --git a/homeassistant/components/sms/translations/el.json b/homeassistant/components/sms/translations/el.json index 73452c7fea7230..372cdfed20f386 100644 --- a/homeassistant/components/sms/translations/el.json +++ b/homeassistant/components/sms/translations/el.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/solaredge/translations/el.json b/homeassistant/components/solaredge/translations/el.json index 27721f76d80ed0..7b460c05717651 100644 --- a/homeassistant/components/solaredge/translations/el.json +++ b/homeassistant/components/solaredge/translations/el.json @@ -1,12 +1,18 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "could_not_connect": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf API \u03c4\u03bf\u03c5 solarage", + "invalid_api_key": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API", "site_not_active": "\u039f \u03b9\u03c3\u03c4\u03cc\u03c4\u03bf\u03c0\u03bf\u03c2 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03cc\u03c2" }, "step": { "user": { "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", "name": "\u03a4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03b1\u03c5\u03c4\u03ae\u03c2 \u03c4\u03b7\u03c2 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2", "site_id": "\u03a4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2 SolarEdge" }, diff --git a/homeassistant/components/solarlog/translations/el.json b/homeassistant/components/solarlog/translations/el.json index 53300521d3faa0..320ea67bf98191 100644 --- a/homeassistant/components/solarlog/translations/el.json +++ b/homeassistant/components/solarlog/translations/el.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/solax/translations/el.json b/homeassistant/components/solax/translations/el.json index 3b724d830f2900..1ed862e8f846c4 100644 --- a/homeassistant/components/solax/translations/el.json +++ b/homeassistant/components/solax/translations/el.json @@ -1,7 +1,8 @@ { "config": { "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/soma/translations/el.json b/homeassistant/components/soma/translations/el.json index 9f2e620391a4e8..e393d8ffb6568d 100644 --- a/homeassistant/components/soma/translations/el.json +++ b/homeassistant/components/soma/translations/el.json @@ -1,9 +1,15 @@ { "config": { "abort": { + "already_setup": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", + "authorize_url_timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2.", + "connection_error": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "missing_configuration": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf Soma \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7.", "result_error": "\u03a4\u03bf SOMA Connect \u03b1\u03c0\u03ac\u03bd\u03c4\u03b7\u03c3\u03b5 \u03bc\u03b5 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1\u03c4\u03bf\u03c2." }, + "create_entry": { + "default": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/somfy/translations/el.json b/homeassistant/components/somfy/translations/el.json index aecb2ee553fa42..8d1f457ae10faa 100644 --- a/homeassistant/components/somfy/translations/el.json +++ b/homeassistant/components/somfy/translations/el.json @@ -1,7 +1,18 @@ { "config": { "abort": { + "authorize_url_timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2.", + "missing_configuration": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7.", + "no_url_available": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL. \u0393\u03b9\u03b1 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1, [\u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b2\u03bf\u03ae\u03b8\u03b5\u03b9\u03b1\u03c2] ( {docs_url} )", "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "create_entry": { + "default": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, + "step": { + "pick_implementation": { + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03bc\u03b5\u03b8\u03cc\u03b4\u03bf\u03c5 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + } } } } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/el.json b/homeassistant/components/somfy_mylink/translations/el.json index 4b3af37a706698..59ffb563687037 100644 --- a/homeassistant/components/somfy_mylink/translations/el.json +++ b/homeassistant/components/somfy_mylink/translations/el.json @@ -1,7 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "flow_title": "{mac} ({ip})", "step": { diff --git a/homeassistant/components/sonarr/translations/el.json b/homeassistant/components/sonarr/translations/el.json index 1124fde09c4799..b8f1bab24bbdf5 100644 --- a/homeassistant/components/sonarr/translations/el.json +++ b/homeassistant/components/sonarr/translations/el.json @@ -1,7 +1,13 @@ { "config": { "abort": { - "reauth_successful": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c4\u03b7\u03ba\u03b5 \u03be\u03b1\u03bd\u03ac \u03bc\u03b5 \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03af\u03b1" + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", + "reauth_successful": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c4\u03b7\u03ba\u03b5 \u03be\u03b1\u03bd\u03ac \u03bc\u03b5 \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03af\u03b1", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "flow_title": "{name}", "step": { @@ -11,8 +17,12 @@ }, "user": { "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", "base_path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf API", - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1", + "ssl": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03ad\u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL", + "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL" } } } diff --git a/homeassistant/components/songpal/translations/el.json b/homeassistant/components/songpal/translations/el.json index 7f36d019843581..6e078b24305ad8 100644 --- a/homeassistant/components/songpal/translations/el.json +++ b/homeassistant/components/songpal/translations/el.json @@ -1,8 +1,12 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "not_songpal_device": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Songpal" }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "flow_title": "{name} ({host})", "step": { "init": { diff --git a/homeassistant/components/sonos/translations/el.json b/homeassistant/components/sonos/translations/el.json index 808556d2a30590..d538ac09c4b7fb 100644 --- a/homeassistant/components/sonos/translations/el.json +++ b/homeassistant/components/sonos/translations/el.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "not_sonos_device": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Sonos" + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", + "not_sonos_device": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Sonos", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, "step": { "confirm": { diff --git a/homeassistant/components/speedtestdotnet/translations/el.json b/homeassistant/components/speedtestdotnet/translations/el.json index 2ffe32a5feffe0..096e339732cbcc 100644 --- a/homeassistant/components/speedtestdotnet/translations/el.json +++ b/homeassistant/components/speedtestdotnet/translations/el.json @@ -3,6 +3,11 @@ "abort": { "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", "wrong_server_id": "\u03a4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf" + }, + "step": { + "user": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;" + } } }, "options": { diff --git a/homeassistant/components/spider/translations/el.json b/homeassistant/components/spider/translations/el.json index f9bbee6975761b..042f6c824ce96b 100644 --- a/homeassistant/components/spider/translations/el.json +++ b/homeassistant/components/spider/translations/el.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/spotify/translations/el.json b/homeassistant/components/spotify/translations/el.json index 7c97846844d791..099a7ebcd3b7ff 100644 --- a/homeassistant/components/spotify/translations/el.json +++ b/homeassistant/components/spotify/translations/el.json @@ -3,12 +3,16 @@ "abort": { "authorize_url_timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2.", "missing_configuration": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Spotify \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7.", + "no_url_available": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL. \u0393\u03b9\u03b1 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1, [\u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b2\u03bf\u03ae\u03b8\u03b5\u03b9\u03b1\u03c2] ( {docs_url} )", "reauth_account_mismatch": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 Spotify \u03bc\u03b5 \u03c4\u03bf\u03bd \u03bf\u03c0\u03bf\u03af\u03bf \u03ad\u03c7\u03b5\u03b9 \u03b3\u03af\u03bd\u03b5\u03b9 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2, \u03b4\u03b5\u03bd \u03c3\u03c5\u03bc\u03c6\u03c9\u03bd\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf\u03bd \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03bf \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03c4\u03bf\u03c5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd." }, "create_entry": { "default": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03c4\u03bf Spotify." }, "step": { + "pick_implementation": { + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03bc\u03b5\u03b8\u03cc\u03b4\u03bf\u03c5 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "reauth_confirm": { "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 Spotify \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03b5\u03b9 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03c4\u03bf Spotify \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc: {account}", "title": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03c4\u03bf Spotify" diff --git a/homeassistant/components/squeezebox/translations/el.json b/homeassistant/components/squeezebox/translations/el.json index 806a63c257a324..a8dcd409a81ea8 100644 --- a/homeassistant/components/squeezebox/translations/el.json +++ b/homeassistant/components/squeezebox/translations/el.json @@ -1,17 +1,23 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "no_server_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 LMS." }, "error": { - "no_server_found": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae." + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "no_server_found": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "flow_title": "{host}", "step": { "edit": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u0395\u03c0\u03b5\u03be\u03b5\u03c1\u03b3\u03b1\u03c3\u03af\u03b1 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03b9\u03ce\u03bd \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, diff --git a/homeassistant/components/srp_energy/translations/el.json b/homeassistant/components/srp_energy/translations/el.json index bee37751f76005..4583eb47823c14 100644 --- a/homeassistant/components/srp_energy/translations/el.json +++ b/homeassistant/components/srp_energy/translations/el.json @@ -1,7 +1,13 @@ { "config": { + "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, "error": { - "invalid_account": "\u03a4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03bd\u03b1\u03c2 9\u03c8\u03ae\u03c6\u03b9\u03bf\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_account": "\u03a4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03bd\u03b1\u03c2 9\u03c8\u03ae\u03c6\u03b9\u03bf\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/steamist/translations/el.json b/homeassistant/components/steamist/translations/el.json index 749746bd52a8e0..0d185423056124 100644 --- a/homeassistant/components/steamist/translations/el.json +++ b/homeassistant/components/steamist/translations/el.json @@ -1,7 +1,15 @@ { "config": { "abort": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", + "not_steamist_device": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae steamist" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "flow_title": "{name} ({ipaddress})", "step": { diff --git a/homeassistant/components/stookalert/translations/el.json b/homeassistant/components/stookalert/translations/el.json index 20cabc1bbe74cc..4070ff01357073 100644 --- a/homeassistant/components/stookalert/translations/el.json +++ b/homeassistant/components/stookalert/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/subaru/translations/el.json b/homeassistant/components/subaru/translations/el.json index 205f1ea320dd83..30ff37ccc1ee6e 100644 --- a/homeassistant/components/subaru/translations/el.json +++ b/homeassistant/components/subaru/translations/el.json @@ -1,12 +1,14 @@ { "config": { "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "error": { "bad_pin_format": "\u03a4\u03bf PIN \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 4 \u03c8\u03b7\u03c6\u03af\u03b1", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", - "incorrect_pin": "\u039b\u03b1\u03bd\u03b8\u03b1\u03c3\u03bc\u03ad\u03bd\u03bf PIN" + "incorrect_pin": "\u039b\u03b1\u03bd\u03b8\u03b1\u03c3\u03bc\u03ad\u03bd\u03bf PIN", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "step": { "pin": { diff --git a/homeassistant/components/surepetcare/translations/el.json b/homeassistant/components/surepetcare/translations/el.json index bab52704f790ea..cdc7ae85736f17 100644 --- a/homeassistant/components/surepetcare/translations/el.json +++ b/homeassistant/components/surepetcare/translations/el.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/switchbot/translations/el.json b/homeassistant/components/switchbot/translations/el.json index b24b965d00471a..fe3b7448ac86fa 100644 --- a/homeassistant/components/switchbot/translations/el.json +++ b/homeassistant/components/switchbot/translations/el.json @@ -1,8 +1,14 @@ { "config": { "abort": { + "already_configured_device": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "no_unconfigured_devices": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03bc\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2.", - "switchbot_unsupported_type": "\u039c\u03b7 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03bf\u03c2 \u03c4\u03cd\u03c0\u03bf\u03c2 Switchbot." + "switchbot_unsupported_type": "\u039c\u03b7 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03bf\u03c2 \u03c4\u03cd\u03c0\u03bf\u03c2 Switchbot.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "flow_title": "{name}", "step": { diff --git a/homeassistant/components/switcher_kis/translations/el.json b/homeassistant/components/switcher_kis/translations/el.json new file mode 100644 index 00000000000000..a13912159002b3 --- /dev/null +++ b/homeassistant/components/switcher_kis/translations/el.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "step": { + "confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/el.json b/homeassistant/components/syncthing/translations/el.json index 4d7c3f964d40ae..7d8c1e635dfb45 100644 --- a/homeassistant/components/syncthing/translations/el.json +++ b/homeassistant/components/syncthing/translations/el.json @@ -1,12 +1,22 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "step": { "user": { "data": { "title": "\u0395\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 Syncthing", - "token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc" + "token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc", + "url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", + "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL" } } } - } + }, + "title": "Syncthing" } \ No newline at end of file diff --git a/homeassistant/components/syncthru/translations/el.json b/homeassistant/components/syncthru/translations/el.json index f1c469f8e5f953..e20c18577e5aee 100644 --- a/homeassistant/components/syncthru/translations/el.json +++ b/homeassistant/components/syncthru/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { "invalid_url": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", "syncthru_not_supported": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03b9 SyncThru", @@ -9,13 +12,14 @@ "step": { "confirm": { "data": { - "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", + "url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03b4\u03b9\u03b5\u03c0\u03b1\u03c6\u03ae\u03c2 \u03b9\u03c3\u03c4\u03bf\u03cd" } }, "user": { "data": { "name": "\u038c\u03bd\u03bf\u03bc\u03b1", - "url": "URL \u03b4\u03b9\u03b5\u03c0\u03b1\u03c6\u03ae\u03c2 \u03b9\u03c3\u03c4\u03bf\u03cd" + "url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03b4\u03b9\u03b5\u03c0\u03b1\u03c6\u03ae\u03c2 \u03b9\u03c3\u03c4\u03bf\u03cd" } } } diff --git a/homeassistant/components/synology_dsm/translations/el.json b/homeassistant/components/synology_dsm/translations/el.json index abc00e150fbf1a..1cf10eb6175a1e 100644 --- a/homeassistant/components/synology_dsm/translations/el.json +++ b/homeassistant/components/synology_dsm/translations/el.json @@ -1,11 +1,16 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2", "reconfigure_successful": "\u0397 \u03b5\u03c0\u03b1\u03bd\u03b1\u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "missing_data": "\u039b\u03b5\u03af\u03c0\u03bf\u03c5\u03bd \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1: \u03b5\u03c0\u03b1\u03bd\u03b1\u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1 \u03ae \u03ac\u03bb\u03bb\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7", - "otp_failed": "\u039f \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b4\u03cd\u03bf \u03b2\u03b7\u03bc\u03ac\u03c4\u03c9\u03bd \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5, \u03b5\u03c0\u03b1\u03bd\u03b1\u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03bc\u03b5 \u03bd\u03ad\u03bf \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "otp_failed": "\u039f \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b4\u03cd\u03bf \u03b2\u03b7\u03bc\u03ac\u03c4\u03c9\u03bd \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5, \u03b5\u03c0\u03b1\u03bd\u03b1\u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03bc\u03b5 \u03bd\u03ad\u03bf \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "flow_title": "{name} ({host})", "step": { @@ -18,14 +23,18 @@ "link": { "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "port": "\u0398\u03cd\u03c1\u03b1" + "port": "\u0398\u03cd\u03c1\u03b1", + "ssl": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03ad\u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", + "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL" }, "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ({host});", "title": "Synology DSM" }, "reauth": { "data": { - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0391\u03b9\u03c4\u03af\u03b1: {details}", "title": "Synology DSM \u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" @@ -41,7 +50,10 @@ "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "port": "\u0398\u03cd\u03c1\u03b1" + "port": "\u0398\u03cd\u03c1\u03b1", + "ssl": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03ad\u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", + "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL" }, "title": "Synology DSM" } diff --git a/homeassistant/components/system_bridge/translations/el.json b/homeassistant/components/system_bridge/translations/el.json index 555cbf3406db4c..5576af325de9d6 100644 --- a/homeassistant/components/system_bridge/translations/el.json +++ b/homeassistant/components/system_bridge/translations/el.json @@ -1,12 +1,26 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "flow_title": "{name}", "step": { "authenticate": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API" + }, "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03c0\u03bf\u03c5 \u03ad\u03c7\u03b5\u03c4\u03b5 \u03bf\u03c1\u03af\u03c3\u03b5\u03b9 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03ae \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {name}." }, "user": { "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "port": "\u0398\u03cd\u03c1\u03b1" }, diff --git a/homeassistant/components/tado/translations/el.json b/homeassistant/components/tado/translations/el.json index 9d5c561fb7ac58..7fca0f12f444ee 100644 --- a/homeassistant/components/tado/translations/el.json +++ b/homeassistant/components/tado/translations/el.json @@ -1,12 +1,19 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { - "no_homes": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03bd \u03c3\u03c0\u03af\u03c4\u03b9\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03b1 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc tado." + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "no_homes": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03bd \u03c3\u03c0\u03af\u03c4\u03b9\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03b1 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc tado.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { "data": { - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 Tado" } diff --git a/homeassistant/components/tailscale/translations/el.json b/homeassistant/components/tailscale/translations/el.json index 1b4b0047b66ea2..0c08e11eee8cfe 100644 --- a/homeassistant/components/tailscale/translations/el.json +++ b/homeassistant/components/tailscale/translations/el.json @@ -1,11 +1,22 @@ { "config": { + "abort": { + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "step": { "reauth_confirm": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API" + }, "description": "\u03a4\u03b1 \u03ba\u03bf\u03c5\u03c0\u03cc\u03bd\u03b9\u03b1 API \u03c4\u03b7\u03c2 Tailscale \u03b9\u03c3\u03c7\u03cd\u03bf\u03c5\u03bd \u03b3\u03b9\u03b1 90 \u03b7\u03bc\u03ad\u03c1\u03b5\u03c2. \u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03bd\u03ad\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03c4\u03b7\u03c2 Tailscale \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://login.tailscale.com/admin/settings/authkeys." }, "user": { "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", "tailnet": "Tailnet" }, "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af\u03c4\u03b5 \u03bc\u03b5 \u03c4\u03b7\u03bd Tailscale \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03c3\u03c4\u03bf https://login.tailscale.com/admin/settings/authkeys.\n\n\u03a4\u03bf Tailnet \u03b5\u03af\u03bd\u03b1\u03b9 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 \u03c3\u03b1\u03c2 Tailscale. \u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c4\u03bf \u03b2\u03c1\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd \u03b5\u03c0\u03ac\u03bd\u03c9 \u03b1\u03c1\u03b9\u03c3\u03c4\u03b5\u03c1\u03ae \u03b3\u03c9\u03bd\u03af\u03b1 \u03c3\u03c4\u03bf\u03bd \u03c0\u03af\u03bd\u03b1\u03ba\u03b1 \u03b4\u03b9\u03b1\u03c7\u03b5\u03af\u03c1\u03b9\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 Tailscale (\u03b4\u03af\u03c0\u03bb\u03b1 \u03c3\u03c4\u03bf \u03bb\u03bf\u03b3\u03cc\u03c4\u03c5\u03c0\u03bf \u03c4\u03bf\u03c5 Tailscale)." diff --git a/homeassistant/components/tasmota/translations/el.json b/homeassistant/components/tasmota/translations/el.json index f66d14e030a402..cb9bc10730c8fb 100644 --- a/homeassistant/components/tasmota/translations/el.json +++ b/homeassistant/components/tasmota/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, "error": { "invalid_discovery_topic": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 \u03b8\u03ad\u03bc\u03b1\u03c4\u03bf\u03c2 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7\u03c2." }, diff --git a/homeassistant/components/tellduslive/translations/el.json b/homeassistant/components/tellduslive/translations/el.json index 068d8a80913306..c5948ef0f264a6 100644 --- a/homeassistant/components/tellduslive/translations/el.json +++ b/homeassistant/components/tellduslive/translations/el.json @@ -1,14 +1,25 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", + "authorize_url_timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1", + "unknown_authorize_url_generation": "\u0386\u03b3\u03bd\u03c9\u03c3\u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2." + }, + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "step": { "auth": { - "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 TelldusLive:\n 1. \u039a\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf\n 2. \u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf Telldus Live\n 3. \u0395\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7 **{\u03cc\u03bd\u03bf\u03bc\u03b1 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2}** (\u03ba\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf **\u039d\u03b1\u03b9**).\n 4. \u0395\u03c0\u03b9\u03c3\u03c4\u03c1\u03ad\u03c8\u03c4\u03b5 \u03b5\u03b4\u03ce \u03ba\u03b1\u03b9 \u03ba\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf **\u03a5\u03a0\u039f\u0392\u039f\u039b\u0397**.\n\n [\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd TelldusLive]({auth_url})" + "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 TelldusLive:\n 1. \u039a\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf\n 2. \u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf Telldus Live\n 3. \u0395\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7 **{\u03cc\u03bd\u03bf\u03bc\u03b1 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2}** (\u03ba\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf **\u039d\u03b1\u03b9**).\n 4. \u0395\u03c0\u03b9\u03c3\u03c4\u03c1\u03ad\u03c8\u03c4\u03b5 \u03b5\u03b4\u03ce \u03ba\u03b1\u03b9 \u03ba\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf **\u03a5\u03a0\u039f\u0392\u039f\u039b\u0397**.\n\n [\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd TelldusLive]({auth_url})", + "title": "\u0388\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ad\u03bd\u03b1\u03bd\u03c4\u03b9 \u03c4\u03bf\u03c5 TelldusLive" }, "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" }, - "description": "\u039a\u03b5\u03bd\u03cc" + "description": "\u039a\u03b5\u03bd\u03cc", + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf \u03c4\u03b5\u03bb\u03b9\u03ba\u03cc \u03c3\u03b7\u03bc\u03b5\u03af\u03bf." } } } diff --git a/homeassistant/components/tesla_wall_connector/translations/el.json b/homeassistant/components/tesla_wall_connector/translations/el.json index 3c5f96d7002a0e..3a835ab5d57372 100644 --- a/homeassistant/components/tesla_wall_connector/translations/el.json +++ b/homeassistant/components/tesla_wall_connector/translations/el.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "flow_title": "{serial_number} ({host})", "step": { diff --git a/homeassistant/components/tibber/translations/el.json b/homeassistant/components/tibber/translations/el.json index 6bf5e8416b0eef..0ad70ba6ac762b 100644 --- a/homeassistant/components/tibber/translations/el.json +++ b/homeassistant/components/tibber/translations/el.json @@ -4,10 +4,15 @@ "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_access_token": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03c4\u03bf Tibber" }, "step": { "user": { + "data": { + "access_token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b1\u03c0\u03cc \u03c4\u03bf https://developer.tibber.com/settings/accesstoken", "title": "Tibber" } diff --git a/homeassistant/components/tile/translations/el.json b/homeassistant/components/tile/translations/el.json index 1e427f465db8f5..57bc15272c9f8d 100644 --- a/homeassistant/components/tile/translations/el.json +++ b/homeassistant/components/tile/translations/el.json @@ -1,10 +1,18 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "step": { "reauth_confirm": { "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" - } + }, + "title": "\u0395\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 Tile" }, "user": { "data": { diff --git a/homeassistant/components/tolo/translations/el.json b/homeassistant/components/tolo/translations/el.json index f325bdfa640167..6b745e37a8ea74 100644 --- a/homeassistant/components/tolo/translations/el.json +++ b/homeassistant/components/tolo/translations/el.json @@ -1,10 +1,17 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf" + }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "flow_title": "{name}", "step": { + "confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;" + }, "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" diff --git a/homeassistant/components/toon/translations/el.json b/homeassistant/components/toon/translations/el.json index d67f873296b484..9ef53ffc9b7285 100644 --- a/homeassistant/components/toon/translations/el.json +++ b/homeassistant/components/toon/translations/el.json @@ -2,7 +2,11 @@ "config": { "abort": { "already_configured": "\u0397 \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03c3\u03c5\u03bc\u03c6\u03c9\u03bd\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af.", - "no_agreements": "\u0391\u03c5\u03c4\u03cc\u03c2 \u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03bf\u03b8\u03cc\u03bd\u03b5\u03c2 Toon." + "authorize_url_timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2.", + "missing_configuration": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7.", + "no_agreements": "\u0391\u03c5\u03c4\u03cc\u03c2 \u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03bf\u03b8\u03cc\u03bd\u03b5\u03c2 Toon.", + "no_url_available": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL. \u0393\u03b9\u03b1 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1, [\u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b2\u03bf\u03ae\u03b8\u03b5\u03b9\u03b1\u03c2] ( {docs_url} )", + "unknown_authorize_url_generation": "\u0386\u03b3\u03bd\u03c9\u03c3\u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2." }, "step": { "agreement": { diff --git a/homeassistant/components/totalconnect/translations/el.json b/homeassistant/components/totalconnect/translations/el.json index 9909277deed5c3..e2e19a56b0a99c 100644 --- a/homeassistant/components/totalconnect/translations/el.json +++ b/homeassistant/components/totalconnect/translations/el.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "no_locations": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03bd \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b5\u03c2 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7, \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 TotalConnect" + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "no_locations": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03bd \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b5\u03c2 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7, \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 TotalConnect", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" }, "error": { "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03c5\u03b8\u03b5\u03bd\u03c4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", @@ -10,17 +12,20 @@ "step": { "locations": { "data": { + "location": "\u03a4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1", "usercode": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03c3\u03c4\u03b7\u03bd \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 {location_id}", "title": "\u039a\u03c9\u03b4\u03b9\u03ba\u03bf\u03af \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2" }, "reauth_confirm": { - "description": "\u03a4\u03bf Total Connect \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2" + "description": "\u03a4\u03bf Total Connect \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, "user": { "data": { - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "Total Connect" } diff --git a/homeassistant/components/tplink/translations/el.json b/homeassistant/components/tplink/translations/el.json index 3e05e3e9e7a521..bea659e039a77f 100644 --- a/homeassistant/components/tplink/translations/el.json +++ b/homeassistant/components/tplink/translations/el.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "flow_title": "{name} {model} ({host})", "step": { "confirm": { diff --git a/homeassistant/components/traccar/translations/el.json b/homeassistant/components/traccar/translations/el.json index 4373918fcad45c..f4760538f239df 100644 --- a/homeassistant/components/traccar/translations/el.json +++ b/homeassistant/components/traccar/translations/el.json @@ -2,7 +2,8 @@ "config": { "abort": { "cloud_not_connected": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf Home Assistant Cloud.", - "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", + "webhook_not_internet_accessible": "\u0397 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1 \u03c4\u03bf\u03c5 Home Assistant \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c3\u03b2\u03ac\u03c3\u03b9\u03bc\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf \u03b4\u03b9\u03b1\u03b4\u03af\u03ba\u03c4\u03c5\u03bf \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03b5\u03b9 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03b1 webhook." }, "create_entry": { "default": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c4\u03b5\u03af\u03bb\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03b1 \u03c3\u03c4\u03bf Home Assistant, \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 webhook \u03c3\u03c4\u03bf Traccar.\n\n\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b1\u03ba\u03cc\u03bb\u03bf\u03c5\u03b8\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL: `{webhook_url}`\n\n\u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]({docs_url}) \u03b3\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2." diff --git a/homeassistant/components/tractive/translations/el.json b/homeassistant/components/tractive/translations/el.json index 0e2e754f90c225..b76849ed2953ee 100644 --- a/homeassistant/components/tractive/translations/el.json +++ b/homeassistant/components/tractive/translations/el.json @@ -1,7 +1,13 @@ { "config": { "abort": { - "reauth_failed_existing": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7\u03c2 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2, \u03b1\u03c6\u03b1\u03b9\u03c1\u03ad\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03be\u03b1\u03bd\u03ac." + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_failed_existing": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7\u03c2 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2, \u03b1\u03c6\u03b1\u03b9\u03c1\u03ad\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03be\u03b1\u03bd\u03ac.", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/tradfri/translations/el.json b/homeassistant/components/tradfri/translations/el.json index 5499c9ae10da67..8e37ecbf503d39 100644 --- a/homeassistant/components/tradfri/translations/el.json +++ b/homeassistant/components/tradfri/translations/el.json @@ -1,7 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7" + }, "error": { "cannot_authenticate": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2, \u03b5\u03af\u03bd\u03b1\u03b9 \u03b7 \u03c0\u03cd\u03bb\u03b7 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03b7 \u03bc\u03b5 \u03ad\u03bd\u03b1\u03bd \u03ac\u03bb\u03bb\u03bf \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae, \u03cc\u03c0\u03c9\u03c2 \u03c0.\u03c7. \u03c4\u03bf Homekit;", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "invalid_key": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae \u03bc\u03b5 \u03c4\u03bf \u03c0\u03b1\u03c1\u03b5\u03c7\u03cc\u03bc\u03b5\u03bd\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af. \u0391\u03bd \u03b1\u03c5\u03c4\u03cc \u03c3\u03c5\u03bc\u03b2\u03b1\u03af\u03bd\u03b5\u03b9 \u03c3\u03c5\u03bd\u03b5\u03c7\u03ce\u03c2, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03cd\u03bb\u03b7.", "timeout": "\u03a7\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03cc\u03c1\u03b9\u03bf \u03b5\u03c0\u03b9\u03ba\u03cd\u03c1\u03c9\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd." }, diff --git a/homeassistant/components/trafikverket_weatherstation/translations/el.json b/homeassistant/components/trafikverket_weatherstation/translations/el.json index 899af053888aa1..e790e3d3d9796d 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/el.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/el.json @@ -1,12 +1,18 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "invalid_station": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03bc\u03b5\u03c4\u03b5\u03c9\u03c1\u03bf\u03bb\u03bf\u03b3\u03b9\u03ba\u03cc\u03c2 \u03c3\u03c4\u03b1\u03b8\u03bc\u03cc\u03c2 \u03bc\u03b5 \u03c4\u03bf \u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1", "more_stations": "\u0392\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03bf\u03af \u03bc\u03b5\u03c4\u03b5\u03c9\u03c1\u03bf\u03bb\u03bf\u03b3\u03b9\u03ba\u03bf\u03af \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03af \u03bc\u03b5 \u03c4\u03bf \u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1" }, "step": { "user": { "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", "conditions": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03bf\u03cd\u03bc\u03b5\u03bd\u03b5\u03c2 \u03c3\u03c5\u03bd\u03b8\u03ae\u03ba\u03b5\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", "station": "\u03a3\u03c4\u03b1\u03b8\u03bc\u03cc\u03c2/\u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7" diff --git a/homeassistant/components/transmission/translations/el.json b/homeassistant/components/transmission/translations/el.json index 134104c26d61e3..4a0701cdaf92c8 100644 --- a/homeassistant/components/transmission/translations/el.json +++ b/homeassistant/components/transmission/translations/el.json @@ -1,12 +1,18 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "name_exists": "\u03a4\u03bf \u038c\u03bd\u03bf\u03bc\u03b1 \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7" }, "step": { "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "port": "\u0398\u03cd\u03c1\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" diff --git a/homeassistant/components/tuya/translations/el.json b/homeassistant/components/tuya/translations/el.json index c370d68d1264e5..63ba3b2696a13a 100644 --- a/homeassistant/components/tuya/translations/el.json +++ b/homeassistant/components/tuya/translations/el.json @@ -1,6 +1,12 @@ { "config": { + "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "login_error": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 ({code}): {msg}" }, "flow_title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Tuya", diff --git a/homeassistant/components/tuya/translations/select.el.json b/homeassistant/components/tuya/translations/select.el.json index 0b1641b60ec95b..45a09f368f7c9a 100644 --- a/homeassistant/components/tuya/translations/select.el.json +++ b/homeassistant/components/tuya/translations/select.el.json @@ -6,7 +6,9 @@ "2": "60 Hz" }, "tuya__basic_nightvision": { - "0": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03bf" + "0": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03bf", + "1": "\u0391\u03bd\u03b5\u03bd\u03b5\u03c1\u03b3\u03cc", + "2": "\u0395\u03bd\u03b5\u03c1\u03b3\u03cc" }, "tuya__countdown": { "1h": "1 \u03ce\u03c1\u03b1", @@ -74,6 +76,7 @@ "led": "LED" }, "tuya__light_mode": { + "none": "\u0391\u03bd\u03b5\u03bd\u03b5\u03c1\u03b3\u03cc", "pos": "\u03a5\u03c0\u03bf\u03b4\u03b5\u03af\u03be\u03c4\u03b5 \u03c4\u03b7 \u03b8\u03ad\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b7", "relay": "\u0388\u03bd\u03b4\u03b5\u03b9\u03be\u03b7 \u03c4\u03b7\u03c2 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2/\u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b7" }, @@ -88,7 +91,11 @@ }, "tuya__relay_status": { "last": "\u0398\u03c5\u03bc\u03b7\u03b8\u03b5\u03af\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03b1 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7", - "memory": "\u0398\u03c5\u03bc\u03b7\u03b8\u03b5\u03af\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03b1 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7" + "memory": "\u0398\u03c5\u03bc\u03b7\u03b8\u03b5\u03af\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03b1 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7", + "off": "\u0391\u03bd\u03b5\u03bd\u03b5\u03c1\u03b3\u03cc", + "on": "\u0395\u03bd\u03b5\u03c1\u03b3\u03cc", + "power_off": "\u0391\u03bd\u03b5\u03bd\u03b5\u03c1\u03b3\u03cc", + "power_on": "\u0395\u03bd\u03b5\u03c1\u03b3\u03cc" }, "tuya__vacuum_cistern": { "closed": "\u039a\u03bb\u03b5\u03b9\u03c3\u03c4\u03cc", @@ -97,15 +104,29 @@ "middle": "\u039c\u03b5\u03c3\u03b1\u03af\u03bf" }, "tuya__vacuum_collection": { - "large": "\u039c\u03b5\u03b3\u03ac\u03bb\u03bf" + "large": "\u039c\u03b5\u03b3\u03ac\u03bb\u03bf", + "middle": "\u039c\u03b5\u03c3\u03b1\u03af\u03bf", + "small": "\u039c\u03b9\u03ba\u03c1\u03cc" }, "tuya__vacuum_mode": { + "bow": "\u03a4\u03cc\u03be\u03bf", "chargego": "\u0395\u03c0\u03b9\u03c3\u03c4\u03c1\u03bf\u03c6\u03ae \u03c3\u03c4\u03b7 \u03b2\u03ac\u03c3\u03b7", + "left_bow": "\u03a4\u03cc\u03be\u03bf \u03b1\u03c1\u03b9\u03c3\u03c4\u03b5\u03c1\u03ac", + "left_spiral": "\u03a3\u03c0\u03b9\u03c1\u03ac\u03bb \u0391\u03c1\u03b9\u03c3\u03c4\u03b5\u03c1\u03ac", "mop": "\u03a3\u03c6\u03bf\u03c5\u03b3\u03b3\u03ac\u03c1\u03b9\u03c3\u03bc\u03b1", + "part": "\u039c\u03ad\u03c1\u03bf\u03c2", + "partial_bow": "\u03a4\u03cc\u03be\u03bf \u03b5\u03bd \u03bc\u03ad\u03c1\u03b5\u03b9", "pick_zone": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u0396\u03ce\u03bd\u03b7\u03c2", + "point": "\u03a3\u03b7\u03bc\u03b5\u03af\u03bf", + "pose": "\u03a3\u03c4\u03ac\u03c3\u03b7", "random": "\u03a4\u03c5\u03c7\u03b1\u03af\u03bf", + "right_bow": "\u03a4\u03cc\u03be\u03bf \u0394\u03b5\u03be\u03b9\u03ac", + "right_spiral": "\u03a3\u03c0\u03b9\u03c1\u03ac\u03bb \u0394\u03b5\u03be\u03b9\u03ac", + "single": "\u039c\u03bf\u03bd\u03cc", "smart": "\u0388\u03be\u03c5\u03c0\u03bd\u03bf", + "spiral": "\u03a3\u03c0\u03b9\u03c1\u03ac\u03bb", "standby": "\u039a\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03b1\u03bd\u03b1\u03bc\u03bf\u03bd\u03ae\u03c2", + "wall_follow": "\u0391\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b5 \u03c4\u03bf\u03bd \u03c4\u03bf\u03af\u03c7\u03bf", "zone": "\u0396\u03ce\u03bd\u03b7" } } diff --git a/homeassistant/components/tuya/translations/sensor.el.json b/homeassistant/components/tuya/translations/sensor.el.json index 0f034693986112..24f5e675ceb0e0 100644 --- a/homeassistant/components/tuya/translations/sensor.el.json +++ b/homeassistant/components/tuya/translations/sensor.el.json @@ -11,6 +11,9 @@ "cooling": "\u03a8\u03cd\u03be\u03b7", "heating": "\u0398\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7", "heating_temp": "\u0398\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1 \u03b8\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7\u03c2", + "reserve_1": "\u039a\u03c1\u03ac\u03c4\u03b7\u03c3\u03b7 1", + "reserve_2": "\u039a\u03c1\u03ac\u03c4\u03b7\u03c3\u03b7 2", + "reserve_3": "\u039a\u03c1\u03ac\u03c4\u03b7\u03c3\u03b7 3", "standby": "\u039a\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03b1\u03bd\u03b1\u03bc\u03bf\u03bd\u03ae\u03c2", "warm": "\u0394\u03b9\u03b1\u03c4\u03ae\u03c1\u03b7\u03c3\u03b7 \u03b8\u03b5\u03c1\u03bc\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" } diff --git a/homeassistant/components/twentemilieu/translations/el.json b/homeassistant/components/twentemilieu/translations/el.json index 27344f27bc865c..f4949d3832da09 100644 --- a/homeassistant/components/twentemilieu/translations/el.json +++ b/homeassistant/components/twentemilieu/translations/el.json @@ -1,11 +1,16 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "invalid_address": "\u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03c3\u03c4\u03b7\u03bd \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03b9\u03ce\u03bd Twente Milieu." }, "step": { "user": { "data": { + "house_letter": "\u0393\u03c1\u03ac\u03bc\u03bc\u03b1 \u03c3\u03c0\u03b9\u03c4\u03b9\u03bf\u03cd/\u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03b1", "house_number": "\u0391\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03c3\u03c0\u03b9\u03c4\u03b9\u03bf\u03cd", "post_code": "\u03a4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b9\u03ba\u03cc\u03c2 \u03ba\u03ce\u03b4\u03b9\u03ba\u03b1\u03c2" }, diff --git a/homeassistant/components/twilio/translations/el.json b/homeassistant/components/twilio/translations/el.json index df951b1e617c90..91b755c797beb6 100644 --- a/homeassistant/components/twilio/translations/el.json +++ b/homeassistant/components/twilio/translations/el.json @@ -2,13 +2,15 @@ "config": { "abort": { "cloud_not_connected": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf Home Assistant Cloud.", - "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", + "webhook_not_internet_accessible": "\u0397 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1 \u03c4\u03bf\u03c5 Home Assistant \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c3\u03b2\u03ac\u03c3\u03b9\u03bc\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf \u03b4\u03b9\u03b1\u03b4\u03af\u03ba\u03c4\u03c5\u03bf \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03b5\u03b9 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03b1 webhook." }, "create_entry": { "default": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c4\u03b5\u03af\u03bb\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03b1 \u03c3\u03c4\u03bf\u03bd Home Assistant, \u03b8\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf [Webhooks with Twilio]( {twilio_url} ). \n\n \u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2: \n\n - URL: ` {webhook_url} `\n - \u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2: POST\n - \u03a4\u03cd\u03c0\u03bf\u03c2 \u03c0\u03b5\u03c1\u03b9\u03b5\u03c7\u03bf\u03bc\u03ad\u03bd\u03bf\u03c5: \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae/x-www-form-urlencoded \n\n \u0394\u03b5\u03af\u03c4\u03b5 [\u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]( {docs_url} ) \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03c4\u03bf\u03bd \u03c4\u03c1\u03cc\u03c0\u03bf \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b1\u03c5\u03c4\u03bf\u03bc\u03b1\u03c4\u03b9\u03c3\u03bc\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03c7\u03b5\u03af\u03c1\u03b9\u03c3\u03b7 \u03c4\u03c9\u03bd \u03b5\u03b9\u03c3\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03c9\u03bd \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd." }, "step": { "user": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Twilio Webhook" } } diff --git a/homeassistant/components/twinkly/translations/el.json b/homeassistant/components/twinkly/translations/el.json index a73ad869c2c1de..8e0294b87b5d9b 100644 --- a/homeassistant/components/twinkly/translations/el.json +++ b/homeassistant/components/twinkly/translations/el.json @@ -1,10 +1,19 @@ { "config": { + "abort": { + "device_exists": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "discovery_confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} - {model} ({host});" }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf Twinkly led string \u03c3\u03b1\u03c2", "title": "Twinkly" } diff --git a/homeassistant/components/unifi/translations/el.json b/homeassistant/components/unifi/translations/el.json index 5087afff1bd717..b017910c2794be 100644 --- a/homeassistant/components/unifi/translations/el.json +++ b/homeassistant/components/unifi/translations/el.json @@ -2,9 +2,12 @@ "config": { "abort": { "already_configured": "\u039f \u03b9\u03c3\u03c4\u03cc\u03c4\u03bf\u03c0\u03bf\u03c2 \u03c4\u03bf\u03c5 UniFi Network \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", - "configuration_updated": "\u0397 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5." + "configuration_updated": "\u0397 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5.", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" }, "error": { + "faulty_credentials": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "service_unavailable": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "unknown_client_mac": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03bf\u03c2 \u03c0\u03b5\u03bb\u03ac\u03c4\u03b7\u03c2 \u03c3\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 MAC" }, "flow_title": "{site} ({host})", @@ -15,7 +18,8 @@ "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "port": "\u0398\u03cd\u03c1\u03b1", "site": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2", - "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", + "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL" }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 UniFi" } @@ -37,7 +41,7 @@ "detection_time": "\u03a7\u03c1\u03cc\u03bd\u03bf\u03c2 \u03c3\u03b5 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03c4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03b1 \u03c6\u03bf\u03c1\u03ac \u03c0\u03bf\u03c5 \u03b5\u03b8\u03b5\u03ac\u03b8\u03b7 \u03ad\u03c9\u03c2 \u03cc\u03c4\u03bf\u03c5 \u03b8\u03b5\u03c9\u03c1\u03b7\u03b8\u03b5\u03af \u03b1\u03c0\u03bf\u03bc\u03b1\u03ba\u03c1\u03c5\u03c3\u03bc\u03ad\u03bd\u03b7", "ignore_wired_bug": "\u0391\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03bb\u03bf\u03b3\u03b9\u03ba\u03ae\u03c2 \u03b5\u03bd\u03c3\u03cd\u03c1\u03bc\u03b1\u03c4\u03c9\u03bd \u03c3\u03c6\u03b1\u03bb\u03bc\u03ac\u03c4\u03c9\u03bd \u03c4\u03bf\u03c5 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 UniFi", "ssid_filter": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 SSID \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03b5\u03af\u03c4\u03b5 \u03b1\u03c3\u03cd\u03c1\u03bc\u03b1\u03c4\u03b1 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ad\u03c2-\u03c0\u03b5\u03bb\u03ac\u03c4\u03b5\u03c2", - "track_clients": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03c0\u03b5\u03bb\u03b1\u03c4\u03ce\u03bd \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5", + "track_clients": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03c7\u03c1\u03b7\u03c3\u03c4\u03ce\u03bd \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5", "track_devices": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 (\u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 Ubiquiti)", "track_wired_clients": "\u03a3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c0\u03b5\u03bb\u03ac\u03c4\u03b5\u03c2 \u03b5\u03bd\u03c3\u03cd\u03c1\u03bc\u03b1\u03c4\u03bf\u03c5 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5" }, @@ -45,6 +49,11 @@ "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 UniFi 1/3" }, "simple_options": { + "data": { + "block_client": "\u03a7\u03c1\u03ae\u03c3\u03c4\u03b5\u03c2 \u03bc\u03b5 \u03b5\u03bb\u03b5\u03b3\u03c7\u03cc\u03bc\u03b5\u03bd\u03b7 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", + "track_clients": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03c7\u03c1\u03b7\u03c3\u03c4\u03ce\u03bd \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5", + "track_devices": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 (\u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 Ubiquiti)" + }, "description": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 UniFi" }, "statistics_sensors": { diff --git a/homeassistant/components/unifiprotect/translations/el.json b/homeassistant/components/unifiprotect/translations/el.json index 32ac668aa29153..70c290590b1f3a 100644 --- a/homeassistant/components/unifiprotect/translations/el.json +++ b/homeassistant/components/unifiprotect/translations/el.json @@ -1,18 +1,22 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "discovery_started": "\u0397 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7 \u03be\u03b5\u03ba\u03af\u03bd\u03b7\u03c3\u03b5" }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", - "protect_version": "\u0397 \u03b5\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03b7 \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 v1.20.0. \u0391\u03bd\u03b1\u03b2\u03b1\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf UniFi Protect \u03ba\u03b1\u03b9 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac." + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "protect_version": "\u0397 \u03b5\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03b7 \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 v1.20.0. \u0391\u03bd\u03b1\u03b2\u03b1\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf UniFi Protect \u03ba\u03b1\u03b9 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "flow_title": "{name} ( {ip_address} )", "step": { "discovery_confirm": { "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", + "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL" }, "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ( {ip_address});", "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03c4\u03bf UniFi Protect" @@ -31,7 +35,8 @@ "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "port": "\u0398\u03cd\u03c1\u03b1", - "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", + "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL" }, "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03c4\u03bf\u03c0\u03b9\u03ba\u03cc \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03c0\u03bf\u03c5 \u03ad\u03c7\u03b5\u03b9 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03b7\u03b8\u03b5\u03af \u03c3\u03c4\u03b7\u03bd \u039a\u03bf\u03bd\u03c3\u03cc\u03bb\u03b1 UniFi OS \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5. \u039f\u03b9 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b5\u03c2 \u03c4\u03bf\u03c5 Ubiquiti Cloud \u03b4\u03b5\u03bd \u03b8\u03b1 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03bf\u03c5\u03bd. \u0393\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2: {local_user_documentation_url}", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 UniFi Protect" diff --git a/homeassistant/components/upb/translations/el.json b/homeassistant/components/upb/translations/el.json index 27cedabdefac6f..7f2ca3521301dd 100644 --- a/homeassistant/components/upb/translations/el.json +++ b/homeassistant/components/upb/translations/el.json @@ -1,7 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { - "invalid_upb_file": "\u039b\u03b5\u03af\u03c0\u03b5\u03b9 \u03ae \u03b5\u03af\u03bd\u03b1\u03b9 \u03ac\u03ba\u03c5\u03c1\u03bf \u03c4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03b5\u03be\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2 UPB UPStart, \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c4\u03bf\u03c5 \u03b1\u03c1\u03c7\u03b5\u03af\u03bf\u03c5." + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_upb_file": "\u039b\u03b5\u03af\u03c0\u03b5\u03b9 \u03ae \u03b5\u03af\u03bd\u03b1\u03b9 \u03ac\u03ba\u03c5\u03c1\u03bf \u03c4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03b5\u03be\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2 UPB UPStart, \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c4\u03bf\u03c5 \u03b1\u03c1\u03c7\u03b5\u03af\u03bf\u03c5.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/upcloud/translations/el.json b/homeassistant/components/upcloud/translations/el.json index c99e65e247cb87..ace40a192a3968 100644 --- a/homeassistant/components/upcloud/translations/el.json +++ b/homeassistant/components/upcloud/translations/el.json @@ -1,5 +1,9 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/upnp/translations/el.json b/homeassistant/components/upnp/translations/el.json index 6c58f72eef7719..9a84ba81ced346 100644 --- a/homeassistant/components/upnp/translations/el.json +++ b/homeassistant/components/upnp/translations/el.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "incomplete_discovery": "\u0395\u03bb\u03bb\u03b9\u03c0\u03ae\u03c2 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7" + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "incomplete_discovery": "\u0395\u03bb\u03bb\u03b9\u03c0\u03ae\u03c2 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7", + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf" }, "flow_title": "{name}", "step": { diff --git a/homeassistant/components/uptimerobot/translations/el.json b/homeassistant/components/uptimerobot/translations/el.json index 138602fb4721d4..7ade0ad11a5d73 100644 --- a/homeassistant/components/uptimerobot/translations/el.json +++ b/homeassistant/components/uptimerobot/translations/el.json @@ -1,16 +1,29 @@ { "config": { "abort": { - "reauth_failed_existing": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7\u03c2 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2, \u03b1\u03c6\u03b1\u03b9\u03c1\u03ad\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03be\u03b1\u03bd\u03ac." + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_failed_existing": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7\u03c2 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2, \u03b1\u03c6\u03b1\u03b9\u03c1\u03ad\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03be\u03b1\u03bd\u03ac.", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "error": { - "reauth_failed_matching_account": "\u03a4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03c0\u03bf\u03c5 \u03b4\u03ce\u03c3\u03b1\u03c4\u03b5 \u03b4\u03b5\u03bd \u03c4\u03b1\u03b9\u03c1\u03b9\u03ac\u03b6\u03b5\u03b9 \u03bc\u03b5 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03c3\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7." + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_api_key": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API", + "reauth_failed_matching_account": "\u03a4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03c0\u03bf\u03c5 \u03b4\u03ce\u03c3\u03b1\u03c4\u03b5 \u03b4\u03b5\u03bd \u03c4\u03b1\u03b9\u03c1\u03b9\u03ac\u03b6\u03b5\u03b9 \u03bc\u03b5 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03c3\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "reauth_confirm": { - "description": "\u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03ad\u03c7\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03bc\u03cc\u03bd\u03bf \u03b3\u03b9\u03b1 \u03b1\u03bd\u03ac\u03b3\u03bd\u03c9\u03c3\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf {intergration}." + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API" + }, + "description": "\u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03ad\u03c7\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03bc\u03cc\u03bd\u03bf \u03b3\u03b9\u03b1 \u03b1\u03bd\u03ac\u03b3\u03bd\u03c9\u03c3\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf {intergration}.", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, "user": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API" + }, "description": "\u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03ad\u03c7\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03bc\u03cc\u03bd\u03bf \u03b3\u03b9\u03b1 \u03b1\u03bd\u03ac\u03b3\u03bd\u03c9\u03c3\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf {intergration}." } } diff --git a/homeassistant/components/vallox/translations/el.json b/homeassistant/components/vallox/translations/el.json index d564f7d754f5c1..4b15c9262479f3 100644 --- a/homeassistant/components/vallox/translations/el.json +++ b/homeassistant/components/vallox/translations/el.json @@ -1,10 +1,15 @@ { "config": { "abort": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_host": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_host": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "user": { diff --git a/homeassistant/components/velbus/translations/el.json b/homeassistant/components/velbus/translations/el.json index a6b86e99d42cb9..b50784422b221c 100644 --- a/homeassistant/components/velbus/translations/el.json +++ b/homeassistant/components/velbus/translations/el.json @@ -1,6 +1,10 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "step": { diff --git a/homeassistant/components/venstar/translations/el.json b/homeassistant/components/venstar/translations/el.json index 594bd343ae1292..f80d57e35c8f46 100644 --- a/homeassistant/components/venstar/translations/el.json +++ b/homeassistant/components/venstar/translations/el.json @@ -1,10 +1,19 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "pin": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN", + "ssl": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03ad\u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03b8\u03b5\u03c1\u03bc\u03bf\u03c3\u03c4\u03ac\u03c4\u03b7 Venstar" diff --git a/homeassistant/components/verisure/translations/el.json b/homeassistant/components/verisure/translations/el.json index e8036c15205e5e..7d46b4ed96b0f8 100644 --- a/homeassistant/components/verisure/translations/el.json +++ b/homeassistant/components/verisure/translations/el.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "installation": { "data": { diff --git a/homeassistant/components/version/translations/el.json b/homeassistant/components/version/translations/el.json index 416c45829026ba..4a1a467a413661 100644 --- a/homeassistant/components/version/translations/el.json +++ b/homeassistant/components/version/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/vesync/translations/el.json b/homeassistant/components/vesync/translations/el.json index e055c1c1eb99ff..0fd6807133d0f2 100644 --- a/homeassistant/components/vesync/translations/el.json +++ b/homeassistant/components/vesync/translations/el.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/vicare/translations/el.json b/homeassistant/components/vicare/translations/el.json index a5facf43b79789..4c4385ec88b81f 100644 --- a/homeassistant/components/vicare/translations/el.json +++ b/homeassistant/components/vicare/translations/el.json @@ -1,9 +1,17 @@ { "config": { + "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "flow_title": "{name} ({host})", "step": { "user": { "data": { + "client_id": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", "heating_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03b8\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", diff --git a/homeassistant/components/vilfo/translations/el.json b/homeassistant/components/vilfo/translations/el.json index b7bc1745d7dc49..7af0b69e97e82c 100644 --- a/homeassistant/components/vilfo/translations/el.json +++ b/homeassistant/components/vilfo/translations/el.json @@ -1,8 +1,17 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { + "access_token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae Vilfo. \u03a7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c3\u03c4\u03b5 \u03c4\u03bf hostname/IP \u03c4\u03bf\u03c5 Vilfo Router \u03ba\u03b1\u03b9 \u03ad\u03bd\u03b1 \u03ba\u03bf\u03c5\u03c0\u03cc\u03bd\u03b9 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 API. \u0393\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03c0\u03ce\u03c2 \u03bd\u03b1 \u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ad\u03c2 \u03c4\u03b9\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2, \u03b5\u03c0\u03b9\u03c3\u03ba\u03b5\u03c6\u03b8\u03b5\u03af\u03c4\u03b5: https://www.home-assistant.io/integrations/vilfo", diff --git a/homeassistant/components/vizio/translations/el.json b/homeassistant/components/vizio/translations/el.json index a67e2504cf692e..128d16d9c4caee 100644 --- a/homeassistant/components/vizio/translations/el.json +++ b/homeassistant/components/vizio/translations/el.json @@ -1,14 +1,20 @@ { "config": { "abort": { + "already_configured_device": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "updated_entry": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af, \u03b1\u03bb\u03bb\u03ac \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1, \u03bf\u03b9 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ad\u03c2 \u03ae/\u03ba\u03b1\u03b9 \u03bf\u03b9 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03c0\u03bf\u03c5 \u03bf\u03c1\u03af\u03b6\u03bf\u03bd\u03c4\u03b1\u03b9 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b4\u03b5\u03bd \u03c4\u03b1\u03b9\u03c1\u03b9\u03ac\u03b6\u03bf\u03c5\u03bd \u03bc\u03b5 \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03b7\u03b3\u03bf\u03c5\u03bc\u03ad\u03bd\u03c9\u03c2 \u03b5\u03b9\u03c3\u03b1\u03c7\u03b8\u03b5\u03af\u03c3\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7, \u03bf\u03c0\u03cc\u03c4\u03b5 \u03b7 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03c9\u03b8\u03b5\u03af \u03b1\u03bd\u03b1\u03bb\u03cc\u03b3\u03c9\u03c2." }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "complete_pairing_failed": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03ae\u03c1\u03c9\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03b1\u03bd\u03c4\u03b9\u03c3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7\u03c2. \u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03bf PIN \u03c0\u03bf\u03c5 \u03b4\u03ce\u03c3\u03b1\u03c4\u03b5 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c9\u03c3\u03c4\u03cc\u03c2 \u03ba\u03b1\u03b9 \u03cc\u03c4\u03b9 \u03b7 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7 \u03b5\u03be\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03b5\u03af \u03bd\u03b1 \u03c4\u03c1\u03bf\u03c6\u03bf\u03b4\u03bf\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03c1\u03b5\u03cd\u03bc\u03b1 \u03ba\u03b1\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03b7 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf \u03c0\u03c1\u03b9\u03bd \u03c4\u03b7\u03bd \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae.", "existing_config_entry_found": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af \u03bc\u03b9\u03b1 \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03c3\u03b1 VIZIO SmartCast \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bc\u03b5 \u03c4\u03bf\u03bd \u03af\u03b4\u03b9\u03bf \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc. \u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c8\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03c3\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae\u03bd." }, "step": { "pair_tv": { + "data": { + "pin": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN" + }, "description": "\u0397 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae \u03c3\u03b1\u03c2 \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03b9 \u03ad\u03bd\u03b1\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc. \u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c3\u03c4\u03b7 \u03c6\u03cc\u03c1\u03bc\u03b1 \u03ba\u03b1\u03b9, \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03bf \u03b2\u03ae\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03ce\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7.", "title": "\u039f\u03bb\u03bf\u03ba\u03bb\u03ae\u03c1\u03c9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03b9\u03ba\u03b1\u03c3\u03af\u03b1\u03c2 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7\u03c2" }, @@ -22,8 +28,10 @@ }, "user": { "data": { + "access_token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "device_class": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" }, "description": "\u0388\u03bd\u03b1 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03bc\u03cc\u03bd\u03bf \u03b3\u03b9\u03b1 \u03c4\u03b9\u03c2 \u03c4\u03b7\u03bb\u03b5\u03bf\u03c1\u03ac\u03c3\u03b5\u03b9\u03c2. \u0395\u03ac\u03bd \u03c1\u03c5\u03b8\u03bc\u03af\u03b6\u03b5\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03b1\u03ba\u03cc\u03bc\u03b7 \u03ad\u03bd\u03b1 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2, \u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03b5\u03c1\u03ac\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c0\u03cc \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b1\u03b4\u03b9\u03ba\u03b1\u03c3\u03af\u03b1 \u03b1\u03bd\u03c4\u03b9\u03c3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7\u03c2.", "title": "VIZIO SmartCast \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" diff --git a/homeassistant/components/vlc_telnet/translations/el.json b/homeassistant/components/vlc_telnet/translations/el.json index b40afa3ffcdd3f..3b1aac109e0152 100644 --- a/homeassistant/components/vlc_telnet/translations/el.json +++ b/homeassistant/components/vlc_telnet/translations/el.json @@ -1,5 +1,17 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "flow_title": "{host}", "step": { "hassio_confirm": { diff --git a/homeassistant/components/volumio/translations/el.json b/homeassistant/components/volumio/translations/el.json index 8a6bf7c8ddd99c..4a2d8b6f54f353 100644 --- a/homeassistant/components/volumio/translations/el.json +++ b/homeassistant/components/volumio/translations/el.json @@ -1,8 +1,13 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "cannot_connect": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf Volumio \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5" }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "discovery_confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Volumio (`{name}`) \u03c3\u03c4\u03bf Home Assistant;", @@ -10,7 +15,8 @@ }, "user": { "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" } } } diff --git a/homeassistant/components/wallbox/translations/el.json b/homeassistant/components/wallbox/translations/el.json index 8680df5adc5108..dd95266866f804 100644 --- a/homeassistant/components/wallbox/translations/el.json +++ b/homeassistant/components/wallbox/translations/el.json @@ -1,7 +1,14 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, "error": { - "reauth_invalid": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5. \u039f \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03b4\u03b5\u03bd \u03c4\u03b1\u03b9\u03c1\u03b9\u03ac\u03b6\u03b5\u03b9 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03b1\u03c1\u03c7\u03b9\u03ba\u03cc" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "reauth_invalid": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5. \u039f \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03b4\u03b5\u03bd \u03c4\u03b1\u03b9\u03c1\u03b9\u03ac\u03b6\u03b5\u03b9 \u03bc\u03b5 \u03c4\u03bf\u03bd \u03b1\u03c1\u03c7\u03b9\u03ba\u03cc", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "reauth_confirm": { @@ -13,7 +20,8 @@ "user": { "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "station": "\u03a3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd" + "station": "\u03a3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } } diff --git a/homeassistant/components/watttime/translations/el.json b/homeassistant/components/watttime/translations/el.json index 85b0863bd7e74d..dd637072413251 100644 --- a/homeassistant/components/watttime/translations/el.json +++ b/homeassistant/components/watttime/translations/el.json @@ -1,6 +1,12 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1", "unknown_coordinates": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03bd \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 \u03b3\u03b9\u03b1 \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2/\u03bc\u03ae\u03ba\u03bf\u03c2" }, "step": { @@ -12,13 +18,17 @@ "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2 \u03ba\u03b1\u03b9 \u03bc\u03ae\u03ba\u03bf\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03b5\u03af\u03c4\u03b5:" }, "location": { + "data": { + "location_type": "\u03a4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1" + }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03b3\u03b9\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7:" }, "reauth_confirm": { "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, - "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username}:" + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username}:", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, "user": { "data": { diff --git a/homeassistant/components/waze_travel_time/translations/el.json b/homeassistant/components/waze_travel_time/translations/el.json index 87024302a4562f..e9a64002372f58 100644 --- a/homeassistant/components/waze_travel_time/translations/el.json +++ b/homeassistant/components/waze_travel_time/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, @@ -22,6 +25,7 @@ "avoid_ferries": "\u0391\u03c0\u03bf\u03c6\u03cd\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c0\u03bb\u03bf\u03af\u03b1;", "avoid_subscription_roads": "\u0391\u03c0\u03bf\u03c6\u03cd\u03b3\u03b5\u03c4\u03b5 \u03b4\u03c1\u03cc\u03bc\u03bf\u03c5\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b5\u03b9\u03ac\u03b6\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b2\u03b9\u03bd\u03b9\u03ad\u03c4\u03b1 / \u03c3\u03c5\u03bd\u03b4\u03c1\u03bf\u03bc\u03ae;", "avoid_toll_roads": "\u0391\u03c0\u03bf\u03c6\u03cd\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03cc\u03b4\u03b9\u03b1;", + "excl_filter": "\u03a5\u03c0\u03bf\u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u039f\u03a7\u0399 \u03c3\u03c4\u03b7\u03bd \u03a0\u03b5\u03c1\u03b9\u03b3\u03c1\u03b1\u03c6\u03ae \u03c4\u03b7\u03c2 \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae\u03c2", "incl_filter": "\u03a5\u03c0\u03bf\u03c3\u03c5\u03bc\u03b2\u03bf\u03bb\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac \u03c3\u03c4\u03b7\u03bd \u03a0\u03b5\u03c1\u03b9\u03b3\u03c1\u03b1\u03c6\u03ae \u03c4\u03b7\u03c2 \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae\u03c2", "realtime": "\u03a7\u03c1\u03cc\u03bd\u03bf\u03c2 \u03c4\u03b1\u03be\u03b9\u03b4\u03b9\u03bf\u03cd \u03c3\u03b5 \u03c0\u03c1\u03b1\u03b3\u03bc\u03b1\u03c4\u03b9\u03ba\u03cc \u03c7\u03c1\u03cc\u03bd\u03bf;", "units": "\u039c\u03bf\u03bd\u03ac\u03b4\u03b5\u03c2", diff --git a/homeassistant/components/webostv/translations/el.json b/homeassistant/components/webostv/translations/el.json index d03b04c6261a9a..9b5566b1ea4e9d 100644 --- a/homeassistant/components/webostv/translations/el.json +++ b/homeassistant/components/webostv/translations/el.json @@ -1,6 +1,8 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", "error_pairing": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 LG webOS TV \u03b1\u03bb\u03bb\u03ac \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c3\u03c5\u03b6\u03b5\u03c5\u03c7\u03b8\u03b5\u03af" }, "error": { diff --git a/homeassistant/components/wemo/translations/el.json b/homeassistant/components/wemo/translations/el.json index e31b6d6443cfef..b07fd2a3386277 100644 --- a/homeassistant/components/wemo/translations/el.json +++ b/homeassistant/components/wemo/translations/el.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, "step": { "confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Wemo;" diff --git a/homeassistant/components/whirlpool/translations/el.json b/homeassistant/components/whirlpool/translations/el.json index bab52704f790ea..9ffc0f96a831b2 100644 --- a/homeassistant/components/whirlpool/translations/el.json +++ b/homeassistant/components/whirlpool/translations/el.json @@ -1,5 +1,10 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/whois/translations/el.json b/homeassistant/components/whois/translations/el.json index b116200db57879..bcf941fe39bdef 100644 --- a/homeassistant/components/whois/translations/el.json +++ b/homeassistant/components/whois/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, "error": { "unexpected_response": "\u039c\u03b7 \u03b1\u03bd\u03b1\u03bc\u03b5\u03bd\u03cc\u03bc\u03b5\u03bd\u03b7 \u03b1\u03c0\u03ac\u03bd\u03c4\u03b7\u03c3\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae whois", "unknown_date_format": "\u0386\u03b3\u03bd\u03c9\u03c3\u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae \u03b7\u03bc\u03b5\u03c1\u03bf\u03bc\u03b7\u03bd\u03af\u03b1\u03c2 \u03c3\u03c4\u03b7\u03bd \u03b1\u03c0\u03cc\u03ba\u03c1\u03b9\u03c3\u03b7 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae whois", diff --git a/homeassistant/components/wiffi/translations/el.json b/homeassistant/components/wiffi/translations/el.json index 23e59bbc57f2d2..100dc55ecaee6b 100644 --- a/homeassistant/components/wiffi/translations/el.json +++ b/homeassistant/components/wiffi/translations/el.json @@ -7,6 +7,9 @@ }, "step": { "user": { + "data": { + "port": "\u0398\u03cd\u03c1\u03b1" + }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae TCP \u03b3\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 WIFFI" } } diff --git a/homeassistant/components/wilight/translations/el.json b/homeassistant/components/wilight/translations/el.json index 3f5afd4d729ce7..e83a8386341e68 100644 --- a/homeassistant/components/wilight/translations/el.json +++ b/homeassistant/components/wilight/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "not_supported_device": "\u0391\u03c5\u03c4\u03cc \u03c4\u03bf WiLight \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf \u03c0\u03b1\u03c1\u03cc\u03bd", "not_wilight_device": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 WiLight" }, diff --git a/homeassistant/components/withings/translations/el.json b/homeassistant/components/withings/translations/el.json index d983f0aa48d0b0..dc284985ee273f 100644 --- a/homeassistant/components/withings/translations/el.json +++ b/homeassistant/components/withings/translations/el.json @@ -1,13 +1,22 @@ { "config": { "abort": { - "already_configured": "\u0397 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03b3\u03b9\u03b1 \u03c4\u03bf \u03c0\u03c1\u03bf\u03c6\u03af\u03bb." + "already_configured": "\u0397 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03b3\u03b9\u03b1 \u03c4\u03bf \u03c0\u03c1\u03bf\u03c6\u03af\u03bb.", + "authorize_url_timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2.", + "missing_configuration": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7.", + "no_url_available": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL. \u0393\u03b9\u03b1 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1, [\u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b2\u03bf\u03ae\u03b8\u03b5\u03b9\u03b1\u03c2] ( {docs_url} )" }, "create_entry": { "default": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03c4\u03bf Withings." }, + "error": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, "flow_title": "{profile}", "step": { + "pick_implementation": { + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03bc\u03b5\u03b8\u03cc\u03b4\u03bf\u03c5 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, "profile": { "data": { "profile": "\u039f\u03bd\u03bf\u03bc\u03b1 \u03c0\u03c1\u03bf\u03c6\u03af\u03bb" @@ -16,7 +25,8 @@ "title": "\u03a0\u03c1\u03bf\u03c6\u03af\u03bb \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7." }, "reauth": { - "description": "\u03a4\u03bf \u03c0\u03c1\u03bf\u03c6\u03af\u03bb \"{profile}\" \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03b9 \u03bd\u03b1 \u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03b5\u03b9 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 Withings." + "description": "\u03a4\u03bf \u03c0\u03c1\u03bf\u03c6\u03af\u03bb \"{profile}\" \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03b9 \u03bd\u03b1 \u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03b5\u03b9 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 Withings.", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" } } } diff --git a/homeassistant/components/wiz/translations/el.json b/homeassistant/components/wiz/translations/el.json index 2665de72b156f7..8b0f37864c570f 100644 --- a/homeassistant/components/wiz/translations/el.json +++ b/homeassistant/components/wiz/translations/el.json @@ -1,16 +1,22 @@ { "config": { "abort": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf" }, "error": { "bulb_time_out": "\u0394\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf\u03bd \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b1. \u038a\u03c3\u03c9\u03c2 \u03bf \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03ba\u03c4\u03cc\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03ae \u03ad\u03c7\u03b5\u03b9 \u03b5\u03b9\u03c3\u03b1\u03c7\u03b8\u03b5\u03af \u03bb\u03ac\u03b8\u03bf\u03c2 IP/host. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03bd\u03ac\u03c8\u03c4\u03b5 \u03c4\u03bf \u03c6\u03c9\u03c2 \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac!", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "no_ip": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP.", - "no_wiz_light": "\u039f \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03bc\u03ad\u03c3\u03c9 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c0\u03bb\u03b1\u03c4\u03c6\u03cc\u03c1\u03bc\u03b1\u03c2 WiZ." + "no_wiz_light": "\u039f \u03bb\u03b1\u03bc\u03c0\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03bc\u03ad\u03c3\u03c9 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c0\u03bb\u03b1\u03c4\u03c6\u03cc\u03c1\u03bc\u03b1\u03c2 WiZ.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "flow_title": "{name} ({host})", "step": { + "confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;" + }, "discovery_confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ({host});" }, diff --git a/homeassistant/components/wiz/translations/nl.json b/homeassistant/components/wiz/translations/nl.json index 97ffaecd0f2499..79fc5c3db1f729 100644 --- a/homeassistant/components/wiz/translations/nl.json +++ b/homeassistant/components/wiz/translations/nl.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", + "cannot_connect": "Kan geen verbinding maken", "no_devices_found": "Geen apparaten gevonden op het netwerk" }, "error": { diff --git a/homeassistant/components/wled/translations/el.json b/homeassistant/components/wled/translations/el.json index 9bfe5cc02a010f..d6c3dbbc837980 100644 --- a/homeassistant/components/wled/translations/el.json +++ b/homeassistant/components/wled/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "cct_unsupported": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae WLED \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03ba\u03b1\u03bd\u03ac\u03bb\u03b9\u03b1 CCT, \u03c4\u03b1 \u03bf\u03c0\u03bf\u03af\u03b1 \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7" }, @@ -10,6 +11,9 @@ "flow_title": "{name}", "step": { "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf WLED \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf Home Assistant." }, "zeroconf_confirm": { diff --git a/homeassistant/components/wled/translations/select.el.json b/homeassistant/components/wled/translations/select.el.json index 3c3e1875f57ee7..ee65d6e5c8cc1f 100644 --- a/homeassistant/components/wled/translations/select.el.json +++ b/homeassistant/components/wled/translations/select.el.json @@ -1,6 +1,8 @@ { "state": { "wled__live_override": { + "0": "\u0391\u03bd\u03b5\u03bd\u03b5\u03c1\u03b3\u03cc", + "1": "\u0395\u03bd\u03b5\u03c1\u03b3\u03cc", "2": "\u039c\u03ad\u03c7\u03c1\u03b9 \u03bd\u03b1 \u03b3\u03af\u03bd\u03b5\u03b9 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" } } diff --git a/homeassistant/components/wolflink/translations/el.json b/homeassistant/components/wolflink/translations/el.json index fdad6c16b611fc..cd3c573a4dd821 100644 --- a/homeassistant/components/wolflink/translations/el.json +++ b/homeassistant/components/wolflink/translations/el.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, "step": { "device": { "data": { diff --git a/homeassistant/components/wolflink/translations/sensor.el.json b/homeassistant/components/wolflink/translations/sensor.el.json index 24c5868d0e9407..e05ea2942f2201 100644 --- a/homeassistant/components/wolflink/translations/sensor.el.json +++ b/homeassistant/components/wolflink/translations/sensor.el.json @@ -37,6 +37,8 @@ "initialisierung": "\u0391\u03c1\u03c7\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", "kalibration": "\u0392\u03b1\u03b8\u03bc\u03bf\u03bd\u03cc\u03bc\u03b7\u03c3\u03b7", "kalibration_heizbetrieb": "\u0392\u03b1\u03b8\u03bc\u03bf\u03bd\u03cc\u03bc\u03b7\u03c3\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b8\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7\u03c2", + "kalibration_kombibetrieb": "\u0392\u03b1\u03b8\u03bc\u03bf\u03bd\u03cc\u03bc\u03b7\u03c3\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 Combi", + "kalibration_warmwasserbetrieb": "\u0392\u03b1\u03b8\u03bc\u03bf\u03bd\u03cc\u03bc\u03b7\u03c3\u03b7 DHW", "kombibetrieb": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 Combi", "kombigerat": "\u039c\u03c0\u03cc\u03b9\u03bb\u03b5\u03c1 Combi", "kombigerat_mit_solareinbindung": "\u039c\u03c0\u03cc\u03b9\u03bb\u03b5\u03c1 Combi \u03bc\u03b5 \u03b7\u03bb\u03b9\u03b1\u03ba\u03ae \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7", diff --git a/homeassistant/components/xbox/translations/el.json b/homeassistant/components/xbox/translations/el.json new file mode 100644 index 00000000000000..93e620b6b5e0b0 --- /dev/null +++ b/homeassistant/components/xbox/translations/el.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2.", + "missing_configuration": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7.", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "create_entry": { + "default": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, + "step": { + "pick_implementation": { + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03bc\u03b5\u03b8\u03cc\u03b4\u03bf\u03c5 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_aqara/translations/el.json b/homeassistant/components/xiaomi_aqara/translations/el.json index 74252f1b493211..83923b660c2cd4 100644 --- a/homeassistant/components/xiaomi_aqara/translations/el.json +++ b/homeassistant/components/xiaomi_aqara/translations/el.json @@ -1,6 +1,8 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", "not_xiaomi_aqara": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03cd\u03bb\u03b7 Xiaomi Aqara, \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03b4\u03b5\u03bd \u03c4\u03b1\u03b9\u03c1\u03b9\u03ac\u03b6\u03b5\u03b9 \u03bc\u03b5 \u03c4\u03b9\u03c2 \u03b3\u03bd\u03c9\u03c3\u03c4\u03ad\u03c2 \u03c0\u03cd\u03bb\u03b5\u03c2" }, "error": { diff --git a/homeassistant/components/xiaomi_miio/translations/el.json b/homeassistant/components/xiaomi_miio/translations/el.json index d8802b2bfb8ae6..abd099a404b05d 100644 --- a/homeassistant/components/xiaomi_miio/translations/el.json +++ b/homeassistant/components/xiaomi_miio/translations/el.json @@ -1,10 +1,14 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", "incomplete_info": "\u0395\u03bb\u03bb\u03b9\u03c0\u03b5\u03af\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2, \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c0\u03b1\u03c1\u03b1\u03c3\u03c7\u03b5\u03b8\u03b5\u03af \u03c5\u03c0\u03bf\u03b4\u03bf\u03c7\u03ad\u03b1\u03c2 \u03ae \u03ba\u03bf\u03c5\u03c0\u03cc\u03bd\u03b9.", - "not_xiaomi_miio": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 (\u03b1\u03ba\u03cc\u03bc\u03b1) \u03b1\u03c0\u03cc \u03c4\u03bf Xiaomi Miio." + "not_xiaomi_miio": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 (\u03b1\u03ba\u03cc\u03bc\u03b1) \u03b1\u03c0\u03cc \u03c4\u03bf Xiaomi Miio.", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" }, "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "cloud_credentials_incomplete": "\u03a4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 Cloud \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bb\u03bb\u03b9\u03c0\u03ae, \u03c3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7, \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03c7\u03ce\u03c1\u03b1", "cloud_login_error": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf Xiaomi Miio Cloud, \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1.", "cloud_no_devices": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc Xiaomi Miio cloud.", @@ -33,8 +37,10 @@ }, "device": { "data": { + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", "model": "\u039c\u03bf\u03bd\u03c4\u03ad\u03bb\u03bf \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 (\u03a0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", - "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" + "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", + "token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API" }, "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf\u03bd 32 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b5\u03c2 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API, \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \u03b3\u03b9\u03b1 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2. \u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03b1\u03c0\u03cc \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Xiaomi Aqara.\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf\u03bd 32 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b5\u03c2 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API, \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \u03b3\u03b9\u03b1 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2. \u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03b1\u03c0\u03cc \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Xiaomi Aqara.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Xiaomi Miio \u03ae \u03c0\u03cd\u03bb\u03b7 Xiaomi" @@ -42,20 +48,23 @@ "gateway": { "data": { "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", - "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c4\u03b7\u03c2 \u03c0\u03cd\u03bb\u03b7\u03c2" + "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c4\u03b7\u03c2 \u03c0\u03cd\u03bb\u03b7\u03c2", + "token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API" }, "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf\u03c5\u03c2 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b5\u03c2 32 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API , \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u03b3\u03b9\u03b1 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2. \u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03b1\u03c0\u03cc \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Xiaomi Aqara.", "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03cd\u03bb\u03b7 Xiaomi" }, "manual": { "data": { - "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", + "token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API" }, "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf 32 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03c9\u03bd \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API, \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \u03b3\u03b9\u03b1 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2. \u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03b1\u03c0\u03cc \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Xiaomi Aqara.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Xiaomi Miio \u03ae \u03c0\u03cd\u03bb\u03b7 Xiaomi" }, "reauth_confirm": { - "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 xiaomi Miio \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03b5\u03b9 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03ac \u03ae \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03b9 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 cloud \u03c0\u03bf\u03c5 \u03bb\u03b5\u03af\u03c0\u03bf\u03c5\u03bd." + "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 xiaomi Miio \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03b5\u03b9 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03ac \u03ae \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03b9 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 cloud \u03c0\u03bf\u03c5 \u03bb\u03b5\u03af\u03c0\u03bf\u03c5\u03bd.", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, "select": { "data": { diff --git a/homeassistant/components/yale_smart_alarm/translations/el.json b/homeassistant/components/yale_smart_alarm/translations/el.json index 463745b6bbf44a..fb53e59f71fe0b 100644 --- a/homeassistant/components/yale_smart_alarm/translations/el.json +++ b/homeassistant/components/yale_smart_alarm/translations/el.json @@ -1,21 +1,28 @@ { "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "step": { "reauth_confirm": { "data": { "area_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1", - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } }, "user": { "data": { "area_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1", - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" } } } diff --git a/homeassistant/components/yamaha_musiccast/translations/el.json b/homeassistant/components/yamaha_musiccast/translations/el.json index d11e8bdc70993b..b164109231ed82 100644 --- a/homeassistant/components/yamaha_musiccast/translations/el.json +++ b/homeassistant/components/yamaha_musiccast/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "yxc_control_url_missing": "\u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03b4\u03b5\u03bd \u03b4\u03af\u03bd\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03b7\u03bd \u03c0\u03b5\u03c1\u03b9\u03b3\u03c1\u03b1\u03c6\u03ae \u03c4\u03bf\u03c5 ssdp." }, "error": { @@ -8,6 +9,9 @@ }, "flow_title": "MusicCast: {name}", "step": { + "confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;" + }, "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" diff --git a/homeassistant/components/yeelight/translations/el.json b/homeassistant/components/yeelight/translations/el.json index cfd4d626b5a670..b125dae0bc6063 100644 --- a/homeassistant/components/yeelight/translations/el.json +++ b/homeassistant/components/yeelight/translations/el.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf" + }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, @@ -14,6 +18,9 @@ } }, "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, "description": "\u0395\u03ac\u03bd \u03b1\u03c6\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ba\u03b5\u03bd\u03cc, \u03bf \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03bc\u03cc\u03c2 \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd." } } diff --git a/homeassistant/components/youless/translations/el.json b/homeassistant/components/youless/translations/el.json index 91ade7d132b8c3..beb582b14eacbe 100644 --- a/homeassistant/components/youless/translations/el.json +++ b/homeassistant/components/youless/translations/el.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/zerproc/translations/el.json b/homeassistant/components/zerproc/translations/el.json new file mode 100644 index 00000000000000..a13912159002b3 --- /dev/null +++ b/homeassistant/components/zerproc/translations/el.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "step": { + "confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zha/translations/el.json b/homeassistant/components/zha/translations/el.json index 4266ec99ffb92f..e0fb76cd6cb4b8 100644 --- a/homeassistant/components/zha/translations/el.json +++ b/homeassistant/components/zha/translations/el.json @@ -2,8 +2,12 @@ "config": { "abort": { "not_zha_device": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae zha", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", "usb_probe_failed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 usb" }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, "flow_title": "{name}", "step": { "confirm": { @@ -80,9 +84,13 @@ }, "trigger_type": { "device_dropped": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c0\u03b5\u03c3\u03b5", + "device_flipped": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b1\u03bd\u03b1\u03c0\u03bf\u03b4\u03bf\u03b3\u03cd\u03c1\u03b9\u03c3\u03b5 \"{subtype}\"", + "device_knocked": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c7\u03c4\u03cd\u03c0\u03b7\u03c3\u03b5 \"{subtype}\"", "device_offline": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03ba\u03c4\u03cc\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "device_rotated": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c0\u03b5\u03c1\u03b9\u03c3\u03c4\u03c1\u03ac\u03c6\u03b7\u03ba\u03b5 \"{subtype}\"", "device_shaken": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b1\u03bd\u03b1\u03ba\u03b9\u03bd\u03ae\u03b8\u03b7\u03ba\u03b5", + "device_slid": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b3\u03bb\u03af\u03c3\u03c4\u03c1\u03b7\u03c3\u03b5 \"{subtype}\"", + "device_tilted": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ba\u03bb\u03af\u03c3\u03b7", "remote_button_alt_double_press": "\u03a4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \"{subtype}\" \u03c0\u03b1\u03c4\u03ae\u03b8\u03b7\u03ba\u03b5 \u03b4\u03b9\u03c0\u03bb\u03ac (\u0395\u03bd\u03b1\u03bb\u03bb\u03b1\u03ba\u03c4\u03b9\u03ba\u03ae \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1)", "remote_button_alt_long_press": "\u03a4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \"{subtype}\" \u03c0\u03b1\u03c4\u03ae\u03b8\u03b7\u03ba\u03b5 \u03c3\u03c5\u03bd\u03b5\u03c7\u03ce\u03c2 (\u0395\u03bd\u03b1\u03bb\u03bb\u03b1\u03ba\u03c4\u03b9\u03ba\u03ae \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1)", "remote_button_alt_long_release": "\u03a4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \"{\u03c3\u03b8\u03b2\u03c4\u03c5\u03c0\u03b5}\" \u03b1\u03c0\u03b5\u03bb\u03b5\u03c5\u03b8\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03bc\u03b5\u03c4\u03ac \u03b1\u03c0\u03cc \u03c0\u03b1\u03c1\u03b1\u03c4\u03b5\u03c4\u03b1\u03bc\u03ad\u03bd\u03bf \u03c0\u03ac\u03c4\u03b7\u03bc\u03b1 (\u0395\u03bd\u03b1\u03bb\u03bb\u03b1\u03ba\u03c4\u03b9\u03ba\u03ae \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1)", diff --git a/homeassistant/components/zha/translations/it.json b/homeassistant/components/zha/translations/it.json index 884c6429cadc09..504ae7eadbaf42 100644 --- a/homeassistant/components/zha/translations/it.json +++ b/homeassistant/components/zha/translations/it.json @@ -84,8 +84,8 @@ }, "trigger_type": { "device_dropped": "Dispositivo caduto", - "device_flipped": "Dispositivo capovolto \" {subtype} \"", - "device_knocked": "Dispositivo bussato \" {subtype} \"", + "device_flipped": "Dispositivo capovolto \"{subtype}\"", + "device_knocked": "Dispositivo bussato \"{subtype}\"", "device_offline": "Dispositivo offline", "device_rotated": "Dispositivo ruotato \" {subtype} \"", "device_shaken": "Dispositivo in vibrazione", diff --git a/homeassistant/components/zoneminder/translations/el.json b/homeassistant/components/zoneminder/translations/el.json index 81511935386070..c370d342e1f3af 100644 --- a/homeassistant/components/zoneminder/translations/el.json +++ b/homeassistant/components/zoneminder/translations/el.json @@ -2,14 +2,18 @@ "config": { "abort": { "auth_fail": "\u03a4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ae \u03bf \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03c3\u03c6\u03b1\u03bb\u03bc\u03ad\u03bd\u03b1.", - "connection_error": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03b5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae ZoneMinder." + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "connection_error": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03b5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae ZoneMinder.", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "create_entry": { "default": "\u03a0\u03c1\u03bf\u03c3\u03c4\u03ad\u03b8\u03b7\u03ba\u03b5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 ZoneMinder." }, "error": { "auth_fail": "\u03a4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ae \u03bf \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03c3\u03c6\u03b1\u03bb\u03bc\u03ad\u03bd\u03b1.", - "connection_error": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03b5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae ZoneMinder." + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "connection_error": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03b5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae ZoneMinder.", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" }, "flow_title": "ZoneMinder", "step": { diff --git a/homeassistant/components/zwave/translations/el.json b/homeassistant/components/zwave/translations/el.json index 028a6302d0b827..ca7149b26b51aa 100644 --- a/homeassistant/components/zwave/translations/el.json +++ b/homeassistant/components/zwave/translations/el.json @@ -1,12 +1,17 @@ { "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, "error": { "option_error": "\u0397 \u03b5\u03c0\u03b9\u03ba\u03cd\u03c1\u03c9\u03c3\u03b7 Z-Wave \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5. \u0395\u03af\u03bd\u03b1\u03b9 \u03c3\u03c9\u03c3\u03c4\u03ae \u03b7 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf \u03c3\u03c4\u03b9\u03ba\u03ac\u03ba\u03b9 USB;" }, "step": { "user": { "data": { - "network_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1)" + "network_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1)", + "usb_path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 USB" }, "description": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03b5\u03bd\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b4\u03b5\u03bd \u03b4\u03b9\u03b1\u03c4\u03b7\u03c1\u03b5\u03af\u03c4\u03b1\u03b9 \u03c0\u03bb\u03ad\u03bf\u03bd. \u0393\u03b9\u03b1 \u03bd\u03ad\u03b5\u03c2 \u03b5\u03b3\u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ac\u03c3\u03b5\u03b9\u03c2, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf Z-Wave JS. \n\n \u0394\u03b5\u03af\u03c4\u03b5 https://www.home-assistant.io/docs/z-wave/installation/ \u03b3\u03b9\u03b1 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03c4\u03b9\u03c2 \u03bc\u03b5\u03c4\u03b1\u03b2\u03bb\u03b7\u03c4\u03ad\u03c2 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2" } diff --git a/homeassistant/components/zwave_js/translations/el.json b/homeassistant/components/zwave_js/translations/el.json index 4d431a31479377..2a4a519369a874 100644 --- a/homeassistant/components/zwave_js/translations/el.json +++ b/homeassistant/components/zwave_js/translations/el.json @@ -6,6 +6,8 @@ "addon_install_failed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS.", "addon_set_config_failed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd Z-Wave JS.", "addon_start_failed": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS.", + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "discovery_requires_supervisor": "\u0397 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03c4\u03bf\u03bd \u03b5\u03c0\u03cc\u03c0\u03c4\u03b7.", "not_zwave_device": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Z-Wave." @@ -13,7 +15,8 @@ "error": { "addon_start_failed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7.", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", - "invalid_ws_url": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL websocket" + "invalid_ws_url": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL websocket", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "flow_title": "{name}", "progress": { @@ -27,7 +30,8 @@ "s0_legacy_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af S0 (\u03c0\u03b1\u03bb\u03b1\u03b9\u03bf\u03cd \u03c4\u03cd\u03c0\u03bf\u03c5)", "s2_access_control_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 S2", "s2_authenticated_key": "\u03a0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af S2", - "s2_unauthenticated_key": "\u039c\u03b7 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af S2" + "s2_unauthenticated_key": "\u039c\u03b7 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af S2", + "usb_path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 USB" }, "description": "\u03a4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf \u03b8\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03b9 \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03ac \u03b1\u03c3\u03c6\u03b1\u03bb\u03b5\u03af\u03b1\u03c2 \u03b5\u03ac\u03bd \u03c4\u03b1 \u03c0\u03b5\u03b4\u03af\u03b1 \u03b1\u03c5\u03c4\u03ac \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03af\u03bd\u03bf\u03c5\u03bd \u03ba\u03b5\u03bd\u03ac.", "title": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS" @@ -38,6 +42,11 @@ "install_addon": { "title": "\u0397 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS \u03ad\u03c7\u03b5\u03b9 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03b9" }, + "manual": { + "data": { + "url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL" + } + }, "on_supervisor": { "data": { "use_addon": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf Z-Wave JS Supervisor" @@ -56,6 +65,7 @@ "device_automation": { "action_type": { "clear_lock_usercode": "\u039a\u03b1\u03b8\u03b1\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c7\u03c1\u03ae\u03c3\u03b7\u03c2 \u03c3\u03c4\u03bf {entity_name}", + "ping": "Ping \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae", "refresh_value": "\u0391\u03bd\u03b1\u03bd\u03b5\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c4\u03b9\u03bc\u03ad\u03c2 \u03b3\u03b9\u03b1 {entity_name}", "reset_meter": "\u0395\u03c0\u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac \u03bc\u03b5\u03c4\u03c1\u03b7\u03c4\u03ce\u03bd \u03c3\u03c4\u03bf {subtype}", "set_config_parameter": "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03c4\u03b9\u03bc\u03ae\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03bf\u03c5 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 {subtype}", @@ -85,10 +95,14 @@ "addon_install_failed": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS.", "addon_set_config_failed": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03bf \u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03c4\u03b7\u03c2 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd JS Z-Wave.", "addon_start_failed": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS.", + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "different_device": "\u0397 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae USB \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b7 \u03af\u03b4\u03b9\u03b1 \u03bc\u03b5 \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03b7\u03b3\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7\u03bd \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2. \u0394\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03bd\u03ad\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03bd\u03ad\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae." }, "error": { - "invalid_ws_url": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL websocket" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_ws_url": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL websocket", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "progress": { "install_addon": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03c0\u03b5\u03c1\u03b9\u03bc\u03ad\u03bd\u03b5\u03c4\u03b5 \u03bc\u03ad\u03c7\u03c1\u03b9 \u03bd\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03c9\u03b8\u03b5\u03af \u03b7 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS. \u0391\u03c5\u03c4\u03cc \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b4\u03b9\u03b1\u03c1\u03ba\u03ad\u03c3\u03b5\u03b9 \u03b1\u03c1\u03ba\u03b5\u03c4\u03ac \u03bb\u03b5\u03c0\u03c4\u03ac.", @@ -98,12 +112,24 @@ "configure_addon": { "data": { "emulate_hardware": "\u0395\u03be\u03bf\u03bc\u03bf\u03af\u03c9\u03c3\u03b7 \u03c5\u03bb\u03b9\u03ba\u03bf\u03cd", + "log_level": "\u0395\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03ba\u03b1\u03c4\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2", + "network_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5", "s0_legacy_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af S0 (\u03c0\u03b1\u03bb\u03b1\u03b9\u03bf\u03cd \u03c4\u03cd\u03c0\u03bf\u03c5)", "s2_access_control_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 S2", "s2_authenticated_key": "\u03a0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af S2", - "s2_unauthenticated_key": "\u039c\u03b7 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af S2" + "s2_unauthenticated_key": "\u039c\u03b7 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af S2", + "usb_path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 USB" }, - "description": "\u03a4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf \u03b8\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03b9 \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03ac \u03b1\u03c3\u03c6\u03b1\u03bb\u03b5\u03af\u03b1\u03c2 \u03b5\u03ac\u03bd \u03c4\u03b1 \u03c0\u03b5\u03b4\u03af\u03b1 \u03b1\u03c5\u03c4\u03ac \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03af\u03bd\u03bf\u03c5\u03bd \u03ba\u03b5\u03bd\u03ac." + "description": "\u03a4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf \u03b8\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03b9 \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03ac \u03b1\u03c3\u03c6\u03b1\u03bb\u03b5\u03af\u03b1\u03c2 \u03b5\u03ac\u03bd \u03c4\u03b1 \u03c0\u03b5\u03b4\u03af\u03b1 \u03b1\u03c5\u03c4\u03ac \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03af\u03bd\u03bf\u03c5\u03bd \u03ba\u03b5\u03bd\u03ac.", + "title": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS" + }, + "install_addon": { + "title": "\u0397 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Z-Wave JS \u03ad\u03c7\u03b5\u03b9 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03b9" + }, + "manual": { + "data": { + "url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL" + } }, "on_supervisor": { "data": { diff --git a/homeassistant/components/zwave_me/translations/el.json b/homeassistant/components/zwave_me/translations/el.json index b5512c539fef87..af8efe8ea86ee7 100644 --- a/homeassistant/components/zwave_me/translations/el.json +++ b/homeassistant/components/zwave_me/translations/el.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "no_valid_uuid_set": "\u0394\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf UUID" }, "error": { @@ -9,7 +10,8 @@ "step": { "user": { "data": { - "token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc" + "token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc", + "url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL" }, "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae Z-Way \u03ba\u03b1\u03b9 \u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 Z-Way. \u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03c4\u03bf \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 wss:// \u03b5\u03ac\u03bd \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af HTTPS \u03b1\u03bd\u03c4\u03af \u03b3\u03b9\u03b1 HTTP. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc, \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03c0\u03b1\u03c6\u03ae \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 Z-Way > \u039c\u03b5\u03bd\u03bf\u03cd > \u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 > \u03a7\u03c1\u03ae\u03c3\u03c4\u03b7\u03c2 > \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API. \u03a0\u03c1\u03bf\u03c4\u03b5\u03af\u03bd\u03b5\u03c4\u03b1\u03b9 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03bd\u03ad\u03bf \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03b3\u03b9\u03b1 \u03c4\u03bf Home Assistant \u03ba\u03b1\u03b9 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03c7\u03c9\u03c1\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7 \u03c3\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c0\u03bf\u03c5 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03b5\u03c4\u03b5 \u03b1\u03c0\u03cc \u03c4\u03bf Home Assistant. \u0395\u03af\u03bd\u03b1\u03b9 \u03b5\u03c0\u03af\u03c3\u03b7\u03c2 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03b1\u03c0\u03bf\u03bc\u03b1\u03ba\u03c1\u03c5\u03c3\u03bc\u03ad\u03bd\u03b7\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03bc\u03ad\u03c3\u03c9 \u03c4\u03bf\u03c5 find.z-wave.me \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03b5\u03bd\u03cc\u03c2 \u03b1\u03c0\u03bf\u03bc\u03b1\u03ba\u03c1\u03c5\u03c3\u03bc\u03ad\u03bd\u03bf\u03c5 Z-Way. \u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf wss://find.z-wave.me \u03c3\u03c4\u03bf \u03c0\u03b5\u03b4\u03af\u03bf IP \u03ba\u03b1\u03b9 \u03b1\u03bd\u03c4\u03b9\u03b3\u03c1\u03ac\u03c8\u03c4\u03b5 \u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03bc\u03b5 Global scope (\u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf Z-Way \u03bc\u03ad\u03c3\u03c9 \u03c4\u03bf\u03c5 find.z-wave.me \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc)." } From 23fdf9eef855b0f21d39f9476ed3507ba8dc891c Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 22 Feb 2022 01:29:58 +0100 Subject: [PATCH 0915/1098] Use selectors in Open-Meteo configuration flow (#67004) --- .../components/open_meteo/config_flow.py | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/open_meteo/config_flow.py b/homeassistant/components/open_meteo/config_flow.py index 2a7b292c363bf9..7f63b834bb9510 100644 --- a/homeassistant/components/open_meteo/config_flow.py +++ b/homeassistant/components/open_meteo/config_flow.py @@ -5,10 +5,11 @@ import voluptuous as vol -from homeassistant.components.zone import DOMAIN as ZONE_DOMAIN, ENTITY_ID_HOME +from homeassistant.components.zone import DOMAIN as ZONE_DOMAIN from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_ZONE from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers.selector import selector from .const import DOMAIN @@ -22,31 +23,22 @@ async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle a flow initialized by the user.""" - zones: dict[str, str] = { - entity_id: state.name - for entity_id in self.hass.states.async_entity_ids(ZONE_DOMAIN) - if (state := self.hass.states.get(entity_id)) is not None - } - if user_input is not None: await self.async_set_unique_id(user_input[CONF_ZONE]) self._abort_if_unique_id_configured() + + state = self.hass.states.get(user_input[CONF_ZONE]) return self.async_create_entry( - title=zones[user_input[CONF_ZONE]], + title=state.name if state else "Open-Meteo", data={CONF_ZONE: user_input[CONF_ZONE]}, ) - zones = dict(sorted(zones.items(), key=lambda x: x[1], reverse=True)) - return self.async_show_form( step_id="user", data_schema=vol.Schema( { - vol.Required(CONF_ZONE): vol.In( - { - ENTITY_ID_HOME: zones.pop(ENTITY_ID_HOME), - **zones, - } + vol.Required(CONF_ZONE): selector( + {"entity": {"domain": ZONE_DOMAIN}} ), } ), From a51d9012ad058d0feda51c6257a0c2fa14a4f9b6 Mon Sep 17 00:00:00 2001 From: Mike Degatano Date: Tue, 22 Feb 2022 01:52:31 -0500 Subject: [PATCH 0916/1098] Fix MQTT lights tests using `STATE_OFF` (#67011) --- tests/components/mqtt/test_light.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index d6125729191b61..ee59928c0c8790 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -3617,7 +3617,7 @@ async def test_sending_mqtt_brightness_command_with_template(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on(hass, "light.test", brightness=100) @@ -3655,7 +3655,7 @@ async def test_sending_mqtt_effect_command_with_template(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("light.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on(hass, "light.test", effect="colorloop") From d554a828756dabe468c4fd8ebf5483b473bc5346 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 21 Feb 2022 20:53:41 -1000 Subject: [PATCH 0917/1098] Add diagnostics support to flux_led (#67012) --- .../components/flux_led/diagnostics.py | 24 +++++++++++ .../components/flux_led/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/flux_led/__init__.py | 1 + tests/components/flux_led/test_diagnostics.py | 40 +++++++++++++++++++ 6 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/flux_led/diagnostics.py create mode 100644 tests/components/flux_led/test_diagnostics.py diff --git a/homeassistant/components/flux_led/diagnostics.py b/homeassistant/components/flux_led/diagnostics.py new file mode 100644 index 00000000000000..f0c95ffbe5623d --- /dev/null +++ b/homeassistant/components/flux_led/diagnostics.py @@ -0,0 +1,24 @@ +"""Diagnostics support for flux_led.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import DOMAIN +from .coordinator import FluxLedUpdateCoordinator + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + coordinator: FluxLedUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + return { + "entry": { + "title": entry.title, + "data": dict(entry.data), + }, + "data": coordinator.device.diagnostics, + } diff --git a/homeassistant/components/flux_led/manifest.json b/homeassistant/components/flux_led/manifest.json index 9f1d307c14a9d3..a1b541c177d715 100644 --- a/homeassistant/components/flux_led/manifest.json +++ b/homeassistant/components/flux_led/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "dependencies": ["network"], "documentation": "https://www.home-assistant.io/integrations/flux_led", - "requirements": ["flux_led==0.28.26"], + "requirements": ["flux_led==0.28.27"], "quality_scale": "platinum", "codeowners": ["@icemanch", "@bdraco"], "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index ef0210e3067f7a..57798e31ad9b15 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -679,7 +679,7 @@ fjaraskupan==1.0.2 flipr-api==1.4.1 # homeassistant.components.flux_led -flux_led==0.28.26 +flux_led==0.28.27 # homeassistant.components.homekit fnvhash==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fe0f3a638bf69a..93d1a4d060e48e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -434,7 +434,7 @@ fjaraskupan==1.0.2 flipr-api==1.4.1 # homeassistant.components.flux_led -flux_led==0.28.26 +flux_led==0.28.27 # homeassistant.components.homekit fnvhash==0.1.0 diff --git a/tests/components/flux_led/__init__.py b/tests/components/flux_led/__init__.py index e1abebd40f1fc0..65fb704e4c7861 100644 --- a/tests/components/flux_led/__init__.py +++ b/tests/components/flux_led/__init__.py @@ -110,6 +110,7 @@ async def _save_setup_callback(callback: Callable) -> None: bulb.paired_remotes = 2 bulb.pixels_per_segment = 300 bulb.segments = 2 + bulb.diagnostics = {"mock_diag": "mock_diag"} bulb.music_pixels_per_segment = 150 bulb.music_segments = 4 bulb.operating_mode = "RGB&W" diff --git a/tests/components/flux_led/test_diagnostics.py b/tests/components/flux_led/test_diagnostics.py new file mode 100644 index 00000000000000..c641d88c8e2439 --- /dev/null +++ b/tests/components/flux_led/test_diagnostics.py @@ -0,0 +1,40 @@ +"""Test flux_led diagnostics.""" +from homeassistant.components.flux_led.const import DOMAIN +from homeassistant.setup import async_setup_component + +from . import ( + _mock_config_entry_for_bulb, + _mocked_bulb, + _patch_discovery, + _patch_wifibulb, +) + +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_diagnostics(hass, hass_client): + """Test generating diagnostics for a config entry.""" + entry = _mock_config_entry_for_bulb(hass) + bulb = _mocked_bulb() + with _patch_discovery(), _patch_wifibulb(device=bulb): + await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await hass.async_block_till_done() + diag = await get_diagnostics_for_config_entry(hass, hass_client, entry) + assert diag == { + "data": {"mock_diag": "mock_diag"}, + "entry": { + "data": { + "host": "127.0.0.1", + "minor_version": 4, + "model": "AK001-ZJ2149", + "model_description": "Bulb RGBCW", + "model_info": "AK001-ZJ2149", + "model_num": 53, + "name": "Bulb RGBCW DDEEFF", + "remote_access_enabled": True, + "remote_access_host": "the.cloud", + "remote_access_port": 8816, + }, + "title": "Mock Title", + }, + } From 2cba9b3d7e91d22bd38cc5ef9f9ecdc380c905e1 Mon Sep 17 00:00:00 2001 From: Kevin Stillhammer Date: Tue, 22 Feb 2022 08:05:12 +0100 Subject: [PATCH 0918/1098] Cleanup_google_travel_time_tests (#66868) --- .../components/google_travel_time/conftest.py | 54 +++++++-------- .../google_travel_time/test_config_flow.py | 66 ++++++++++--------- .../google_travel_time/test_sensor.py | 14 ---- 3 files changed, 64 insertions(+), 70 deletions(-) diff --git a/tests/components/google_travel_time/conftest.py b/tests/components/google_travel_time/conftest.py index 7f668383c4b712..4ca7c5a91055fe 100644 --- a/tests/components/google_travel_time/conftest.py +++ b/tests/components/google_travel_time/conftest.py @@ -1,21 +1,27 @@ """Fixtures for Google Time Travel tests.""" -from unittest.mock import Mock, patch +from unittest.mock import patch from googlemaps.exceptions import ApiError import pytest +from homeassistant.components.google_travel_time.const import DOMAIN -@pytest.fixture(name="validate_config_entry") -def validate_config_entry_fixture(): - """Return valid config entry.""" - with patch( - "homeassistant.components.google_travel_time.helpers.Client", - return_value=Mock(), - ), patch( - "homeassistant.components.google_travel_time.helpers.distance_matrix", - return_value=None, - ): - yield +from tests.common import MockConfigEntry + + +@pytest.fixture(name="mock_config") +async def mock_config_fixture(hass, data, options): + """Mock a Google Travel Time config entry.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data=data, + options=options, + entry_id="test", + ) + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + yield config_entry @pytest.fixture(name="bypass_setup") @@ -38,21 +44,17 @@ def bypass_platform_setup_fixture(): yield -@pytest.fixture(name="bypass_update") -def bypass_update_fixture(): - """Bypass sensor update.""" - with patch("homeassistant.components.google_travel_time.sensor.distance_matrix"): - yield +@pytest.fixture(name="validate_config_entry") +def validate_config_entry_fixture(): + """Return valid config entry.""" + with patch("homeassistant.components.google_travel_time.helpers.Client"), patch( + "homeassistant.components.google_travel_time.helpers.distance_matrix" + ) as distance_matrix_mock: + distance_matrix_mock.return_value = None + yield distance_matrix_mock @pytest.fixture(name="invalidate_config_entry") -def invalidate_config_entry_fixture(): +def invalidate_config_entry_fixture(validate_config_entry): """Return invalid config entry.""" - with patch( - "homeassistant.components.google_travel_time.helpers.Client", - return_value=Mock(), - ), patch( - "homeassistant.components.google_travel_time.helpers.distance_matrix", - side_effect=ApiError("test"), - ): - yield + validate_config_entry.side_effect = ApiError("test") diff --git a/tests/components/google_travel_time/test_config_flow.py b/tests/components/google_travel_time/test_config_flow.py index d81d63e3af11ea..1426c749552f0f 100644 --- a/tests/components/google_travel_time/test_config_flow.py +++ b/tests/components/google_travel_time/test_config_flow.py @@ -1,4 +1,6 @@ """Test the Google Maps Travel Time config flow.""" +import pytest + from homeassistant import config_entries, data_entry_flow from homeassistant.components.google_travel_time.const import ( ARRIVAL_TIME, @@ -25,11 +27,11 @@ CONF_UNIT_SYSTEM_IMPERIAL, ) -from tests.common import MockConfigEntry from tests.components.google_travel_time.const import MOCK_CONFIG -async def test_minimum_fields(hass, validate_config_entry, bypass_setup): +@pytest.mark.usefixtures("validate_config_entry", "bypass_setup") +async def test_minimum_fields(hass): """Test we get the form.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -52,7 +54,8 @@ async def test_minimum_fields(hass, validate_config_entry, bypass_setup): } -async def test_invalid_config_entry(hass, invalidate_config_entry): +@pytest.mark.usefixtures("invalidate_config_entry") +async def test_invalid_config_entry(hass): """Test we get the form.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -68,22 +71,25 @@ async def test_invalid_config_entry(hass, invalidate_config_entry): assert result2["errors"] == {"base": "cannot_connect"} -async def test_options_flow(hass, validate_config_entry, bypass_update): +@pytest.mark.parametrize( + "data,options", + [ + ( + MOCK_CONFIG, + { + CONF_MODE: "driving", + CONF_ARRIVAL_TIME: "test", + CONF_UNITS: CONF_UNIT_SYSTEM_IMPERIAL, + }, + ) + ], +) +@pytest.mark.usefixtures("validate_config_entry") +async def test_options_flow(hass, mock_config): """Test options flow.""" - entry = MockConfigEntry( - domain=DOMAIN, - data=MOCK_CONFIG, - options={ - CONF_MODE: "driving", - CONF_ARRIVAL_TIME: "test", - CONF_UNITS: CONF_UNIT_SYSTEM_IMPERIAL, - }, + result = await hass.config_entries.options.async_init( + mock_config.entry_id, data=None ) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - result = await hass.config_entries.options.async_init(entry.entry_id, data=None) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "init" @@ -115,7 +121,7 @@ async def test_options_flow(hass, validate_config_entry, bypass_update): CONF_TRANSIT_ROUTING_PREFERENCE: "less_walking", } - assert entry.options == { + assert mock_config.options == { CONF_MODE: "driving", CONF_LANGUAGE: "en", CONF_AVOID: "tolls", @@ -127,17 +133,16 @@ async def test_options_flow(hass, validate_config_entry, bypass_update): } -async def test_options_flow_departure_time(hass, validate_config_entry, bypass_update): - """Test options flow wiith departure time.""" - entry = MockConfigEntry( - domain=DOMAIN, - data=MOCK_CONFIG, +@pytest.mark.parametrize( + "data,options", + [(MOCK_CONFIG, {})], +) +@pytest.mark.usefixtures("validate_config_entry") +async def test_options_flow_departure_time(hass, mock_config): + """Test options flow with departure time.""" + result = await hass.config_entries.options.async_init( + mock_config.entry_id, data=None ) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - result = await hass.config_entries.options.async_init(entry.entry_id, data=None) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "init" @@ -169,7 +174,7 @@ async def test_options_flow_departure_time(hass, validate_config_entry, bypass_u CONF_TRANSIT_ROUTING_PREFERENCE: "less_walking", } - assert entry.options == { + assert mock_config.options == { CONF_MODE: "driving", CONF_LANGUAGE: "en", CONF_AVOID: "tolls", @@ -181,7 +186,8 @@ async def test_options_flow_departure_time(hass, validate_config_entry, bypass_u } -async def test_dupe(hass, validate_config_entry, bypass_setup): +@pytest.mark.usefixtures("validate_config_entry", "bypass_setup") +async def test_dupe(hass): """Test setting up the same entry data twice is OK.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} diff --git a/tests/components/google_travel_time/test_sensor.py b/tests/components/google_travel_time/test_sensor.py index 6b203f51e98709..daedcfef4c18f4 100644 --- a/tests/components/google_travel_time/test_sensor.py +++ b/tests/components/google_travel_time/test_sensor.py @@ -16,20 +16,6 @@ from tests.common import MockConfigEntry -@pytest.fixture(name="mock_config") -async def mock_config_fixture(hass, data, options): - """Mock a Google Travel Time config entry.""" - config_entry = MockConfigEntry( - domain=DOMAIN, - data=data, - options=options, - entry_id="test", - ) - config_entry.add_to_hass(hass) - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - @pytest.fixture(name="mock_update") def mock_update_fixture(): """Mock an update to the sensor.""" From 6ec0e3811ac69e7378722e87df6a2c898e996ecc Mon Sep 17 00:00:00 2001 From: Kevin Stillhammer Date: Tue, 22 Feb 2022 08:15:35 +0100 Subject: [PATCH 0919/1098] Waze travel time sensor tests (#66558) --- .coveragerc | 3 - tests/components/waze_travel_time/conftest.py | 20 ++-- tests/components/waze_travel_time/const.py | 13 +++ .../waze_travel_time/test_config_flow.py | 32 ++----- .../waze_travel_time/test_sensor.py | 92 +++++++++++++++++++ 5 files changed, 122 insertions(+), 38 deletions(-) create mode 100644 tests/components/waze_travel_time/const.py create mode 100644 tests/components/waze_travel_time/test_sensor.py diff --git a/.coveragerc b/.coveragerc index 7bee7669e1670a..1a75262698844e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1395,9 +1395,6 @@ omit = homeassistant/components/watson_tts/tts.py homeassistant/components/watttime/__init__.py homeassistant/components/watttime/sensor.py - homeassistant/components/waze_travel_time/__init__.py - homeassistant/components/waze_travel_time/helpers.py - homeassistant/components/waze_travel_time/sensor.py homeassistant/components/wiffi/__init__.py homeassistant/components/wiffi/binary_sensor.py homeassistant/components/wiffi/sensor.py diff --git a/tests/components/waze_travel_time/conftest.py b/tests/components/waze_travel_time/conftest.py index 04675666356639..d2bb647b2cacaa 100644 --- a/tests/components/waze_travel_time/conftest.py +++ b/tests/components/waze_travel_time/conftest.py @@ -5,11 +5,13 @@ import pytest -@pytest.fixture(autouse=True) -def mock_wrc(): +@pytest.fixture(name="mock_wrc", autouse=True) +def mock_wrc_fixture(): """Mock out WazeRouteCalculator.""" - with patch("homeassistant.components.waze_travel_time.sensor.WazeRouteCalculator"): - yield + with patch( + "homeassistant.components.waze_travel_time.sensor.WazeRouteCalculator" + ) as mock_wrc: + yield mock_wrc @pytest.fixture(name="validate_config_entry") @@ -44,13 +46,11 @@ def bypass_platform_setup_fixture(): @pytest.fixture(name="mock_update") -def mock_update_fixture(): +def mock_update_fixture(mock_wrc): """Mock an update to the sensor.""" - with patch( - "homeassistant.components.waze_travel_time.sensor.WazeRouteCalculator.calc_all_routes_info", - return_value={"My route": (150, 300)}, - ): - yield + obj = mock_wrc.return_value + obj.calc_all_routes_info.return_value = {"My route": (150, 300)} + yield @pytest.fixture(name="invalidate_config_entry") diff --git a/tests/components/waze_travel_time/const.py b/tests/components/waze_travel_time/const.py new file mode 100644 index 00000000000000..f56e8c5892e214 --- /dev/null +++ b/tests/components/waze_travel_time/const.py @@ -0,0 +1,13 @@ +"""Constants for waze_travel_time tests.""" + +from homeassistant.components.waze_travel_time.const import ( + CONF_DESTINATION, + CONF_ORIGIN, +) +from homeassistant.const import CONF_REGION + +MOCK_CONFIG = { + CONF_ORIGIN: "location1", + CONF_DESTINATION: "location2", + CONF_REGION: "US", +} diff --git a/tests/components/waze_travel_time/test_config_flow.py b/tests/components/waze_travel_time/test_config_flow.py index 015850ba1b8171..8a57a34b739b90 100644 --- a/tests/components/waze_travel_time/test_config_flow.py +++ b/tests/components/waze_travel_time/test_config_flow.py @@ -16,6 +16,8 @@ ) from homeassistant.const import CONF_NAME, CONF_REGION, CONF_UNIT_SYSTEM_IMPERIAL +from .const import MOCK_CONFIG + from tests.common import MockConfigEntry @@ -29,11 +31,7 @@ async def test_minimum_fields(hass, validate_config_entry, bypass_setup): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - { - CONF_ORIGIN: "location1", - CONF_DESTINATION: "location2", - CONF_REGION: "US", - }, + MOCK_CONFIG, ) await hass.async_block_till_done() @@ -52,11 +50,7 @@ async def test_options(hass, validate_config_entry, mock_update): entry = MockConfigEntry( domain=DOMAIN, - data={ - CONF_ORIGIN: "location1", - CONF_DESTINATION: "location2", - CONF_REGION: "US", - }, + data=MOCK_CONFIG, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) @@ -178,11 +172,7 @@ async def test_dupe(hass, validate_config_entry, bypass_setup): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - { - CONF_ORIGIN: "location1", - CONF_DESTINATION: "location2", - CONF_REGION: "US", - }, + MOCK_CONFIG, ) await hass.async_block_till_done() @@ -197,11 +187,7 @@ async def test_dupe(hass, validate_config_entry, bypass_setup): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - { - CONF_ORIGIN: "location1", - CONF_DESTINATION: "location2", - CONF_REGION: "US", - }, + MOCK_CONFIG, ) await hass.async_block_till_done() @@ -217,11 +203,7 @@ async def test_invalid_config_entry(hass, invalidate_config_entry): assert result["errors"] == {} result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - { - CONF_ORIGIN: "location1", - CONF_DESTINATION: "location2", - CONF_REGION: "US", - }, + MOCK_CONFIG, ) assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM diff --git a/tests/components/waze_travel_time/test_sensor.py b/tests/components/waze_travel_time/test_sensor.py new file mode 100644 index 00000000000000..0409b037394165 --- /dev/null +++ b/tests/components/waze_travel_time/test_sensor.py @@ -0,0 +1,92 @@ +"""Test Waze Travel Time sensors.""" + +from WazeRouteCalculator import WRCError +import pytest + +from homeassistant.components.waze_travel_time.const import DOMAIN + +from .const import MOCK_CONFIG + +from tests.common import MockConfigEntry + + +@pytest.fixture(name="mock_config") +async def mock_config_fixture(hass, data): + """Mock a Waze Travel Time config entry.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data=data, + entry_id="test", + ) + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + +@pytest.fixture(name="mock_update_wrcerror") +def mock_update_wrcerror_fixture(mock_wrc): + """Mock an update to the sensor failed with WRCError.""" + obj = mock_wrc.return_value + obj.calc_all_routes_info.side_effect = WRCError("test") + yield + + +@pytest.fixture(name="mock_update_keyerror") +def mock_update_keyerror_fixture(mock_wrc): + """Mock an update to the sensor failed with KeyError.""" + obj = mock_wrc.return_value + obj.calc_all_routes_info.side_effect = KeyError("test") + yield + + +@pytest.mark.parametrize( + "data", + [MOCK_CONFIG], +) +@pytest.mark.usefixtures("mock_update", "mock_config") +async def test_sensor(hass): + """Test that sensor works.""" + assert hass.states.get("sensor.waze_travel_time").state == "150" + assert ( + hass.states.get("sensor.waze_travel_time").attributes["attribution"] + == "Powered by Waze" + ) + assert hass.states.get("sensor.waze_travel_time").attributes["duration"] == 150 + assert hass.states.get("sensor.waze_travel_time").attributes["distance"] == 300 + assert hass.states.get("sensor.waze_travel_time").attributes["route"] == "My route" + assert ( + hass.states.get("sensor.waze_travel_time").attributes["origin"] == "location1" + ) + assert ( + hass.states.get("sensor.waze_travel_time").attributes["destination"] + == "location2" + ) + assert ( + hass.states.get("sensor.waze_travel_time").attributes["unit_of_measurement"] + == "min" + ) + assert hass.states.get("sensor.waze_travel_time").attributes["icon"] == "mdi:car" + + +@pytest.mark.usefixtures("mock_update_wrcerror") +async def test_sensor_failed_wrcerror(hass, caplog): + """Test that sensor update fails with log message.""" + config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="test") + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert hass.states.get("sensor.waze_travel_time").state == "unknown" + assert "Error on retrieving data: " in caplog.text + + +@pytest.mark.usefixtures("mock_update_keyerror") +async def test_sensor_failed_keyerror(hass, caplog): + """Test that sensor update fails with log message.""" + config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="test") + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert hass.states.get("sensor.waze_travel_time").state == "unknown" + assert "Error retrieving data from server" in caplog.text From a17650550ff4034684dc727cb63a4069163117cd Mon Sep 17 00:00:00 2001 From: Kevin Stillhammer Date: Tue, 22 Feb 2022 08:17:18 +0100 Subject: [PATCH 0920/1098] google_travel_time: always resolve zones (#66165) --- .../components/google_travel_time/const.py | 2 - .../components/google_travel_time/sensor.py | 40 +++++++++---------- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/google_travel_time/const.py b/homeassistant/components/google_travel_time/const.py index 6b9b77242ba29d..893c0e48bd040a 100644 --- a/homeassistant/components/google_travel_time/const.py +++ b/homeassistant/components/google_travel_time/const.py @@ -26,8 +26,6 @@ DEFAULT_NAME = "Google Travel Time" -TRACKABLE_DOMAINS = ["device_tracker", "sensor", "zone", "person"] - ALL_LANGUAGES = [ "ar", "bg", diff --git a/homeassistant/components/google_travel_time/sensor.py b/homeassistant/components/google_travel_time/sensor.py index 6960361d9a1c80..3ee2a18455c17e 100644 --- a/homeassistant/components/google_travel_time/sensor.py +++ b/homeassistant/components/google_travel_time/sensor.py @@ -35,7 +35,6 @@ CONF_UNITS, DEFAULT_NAME, DOMAIN, - TRACKABLE_DOMAINS, ) _LOGGER = logging.getLogger(__name__) @@ -109,17 +108,10 @@ def __init__(self, config_entry, name, api_key, origin, destination, client): self._api_key = api_key self._unique_id = config_entry.entry_id self._client = client - - # Check if location is a trackable entity - if origin.split(".", 1)[0] in TRACKABLE_DOMAINS: - self._origin_entity_id = origin - else: - self._origin = origin - - if destination.split(".", 1)[0] in TRACKABLE_DOMAINS: - self._destination_entity_id = destination - else: - self._destination = destination + self._origin = origin + self._destination = destination + self._resolved_origin = None + self._resolved_destination = None async def async_added_to_hass(self) -> None: """Handle when entity is added.""" @@ -179,8 +171,8 @@ def extra_state_attributes(self): res["duration"] = _data["duration"]["text"] if "distance" in _data: res["distance"] = _data["distance"]["text"] - res["origin"] = self._origin - res["destination"] = self._destination + res["origin"] = self._resolved_origin + res["destination"] = self._resolved_destination res[ATTR_ATTRIBUTION] = ATTRIBUTION return res @@ -211,14 +203,18 @@ def update(self): elif atime is not None: options_copy[CONF_ARRIVAL_TIME] = atime - # Convert device_trackers to google friendly location - if hasattr(self, "_origin_entity_id"): - self._origin = find_coordinates(self.hass, self._origin_entity_id) - - if hasattr(self, "_destination_entity_id"): - self._destination = find_coordinates(self.hass, self._destination_entity_id) + self._resolved_origin = find_coordinates(self.hass, self._origin) + self._resolved_destination = find_coordinates(self.hass, self._destination) - if self._destination is not None and self._origin is not None: + _LOGGER.debug( + "Getting update for origin: %s destination: %s", + self._resolved_origin, + self._resolved_destination, + ) + if self._resolved_destination is not None and self._resolved_origin is not None: self._matrix = distance_matrix( - self._client, self._origin, self._destination, **options_copy + self._client, + self._resolved_origin, + self._resolved_destination, + **options_copy, ) From 010e6cb4ba2ccab9a4b02a7d30a6d090ea2253b4 Mon Sep 17 00:00:00 2001 From: Kevin Stillhammer Date: Tue, 22 Feb 2022 08:17:49 +0100 Subject: [PATCH 0921/1098] waze_travel_time: always resolve zones (#66162) --- .../components/waze_travel_time/const.py | 3 -- .../components/waze_travel_time/sensor.py | 36 +++++-------------- 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/waze_travel_time/const.py b/homeassistant/components/waze_travel_time/const.py index 554f3ecf6d8c63..37278543dfb6b1 100644 --- a/homeassistant/components/waze_travel_time/const.py +++ b/homeassistant/components/waze_travel_time/const.py @@ -25,6 +25,3 @@ REGIONS = ["US", "NA", "EU", "IL", "AU"] VEHICLE_TYPES = ["car", "taxi", "motorcycle"] - -# Attempt to find entity_id without finding address with period. -ENTITY_ID_PATTERN = "(? None: """Handle when entity is added.""" @@ -177,16 +162,8 @@ async def first_update(self, _=None): def update(self): """Fetch new state data for the sensor.""" _LOGGER.debug("Fetching Route for %s", self._attr_name) - # Get origin latitude and longitude from entity_id. - if self._origin_entity_id is not None: - self._waze_data.origin = find_coordinates(self.hass, self._origin_entity_id) - - # Get destination latitude and longitude from entity_id. - if self._destination_entity_id is not None: - self._waze_data.destination = find_coordinates( - self.hass, self._destination_entity_id - ) - + self._waze_data.origin = find_coordinates(self.hass, self._origin) + self._waze_data.destination = find_coordinates(self.hass, self._destination) self._waze_data.update() @@ -205,6 +182,11 @@ def __init__(self, origin, destination, region, config_entry): def update(self): """Update WazeRouteCalculator Sensor.""" + _LOGGER.debug( + "Getting update for origin: %s destination: %s", + self.origin, + self.destination, + ) if self.origin is not None and self.destination is not None: # Grab options on every update incl_filter = self.config_entry.options.get(CONF_INCL_FILTER) From 7c7a86242e138db4a5b5059872b5860725f21bca Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 21 Feb 2022 23:42:57 -0800 Subject: [PATCH 0922/1098] Allow supported brands in manifests (#67015) --- script/hassfest/manifest.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/script/hassfest/manifest.py b/script/hassfest/manifest.py index d146621b416912..64239a7fae1841 100644 --- a/script/hassfest/manifest.py +++ b/script/hassfest/manifest.py @@ -248,12 +248,14 @@ def verify_wildcard(value: str): vol.Optional("loggers"): [str], vol.Optional("disabled"): str, vol.Optional("iot_class"): vol.In(SUPPORTED_IOT_CLASSES), + vol.Optional("supported_brands"): vol.Schema({str: str}), } ) CUSTOM_INTEGRATION_MANIFEST_SCHEMA = MANIFEST_SCHEMA.extend( { vol.Optional("version"): vol.All(str, verify_version), + vol.Remove("supported_brands"): dict, } ) @@ -307,6 +309,13 @@ def validate_manifest(integration: Integration, core_components_dir: Path) -> No ): integration.add_error("manifest", "Domain is missing an IoT Class") + for domain, _name in integration.manifest.get("supported_brands", {}).items(): + if (core_components_dir / domain).exists(): + integration.add_warning( + "manifest", + f"Supported brand domain {domain} collides with built-in core integration", + ) + if not integration.core: validate_version(integration) From 98c00c0255c3bbbd8964752804d804c02976d0f8 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 22 Feb 2022 02:44:03 -0500 Subject: [PATCH 0923/1098] Bump zwave-js-server-python to 0.35.1 (#67014) --- homeassistant/components/zwave_js/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index e6975ddb47b0f7..6a892b2791d8aa 100644 --- a/homeassistant/components/zwave_js/manifest.json +++ b/homeassistant/components/zwave_js/manifest.json @@ -3,7 +3,7 @@ "name": "Z-Wave JS", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zwave_js", - "requirements": ["zwave-js-server-python==0.35.0"], + "requirements": ["zwave-js-server-python==0.35.1"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["usb", "http", "websocket_api"], "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index 57798e31ad9b15..55124fe8c41609 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2566,7 +2566,7 @@ zigpy==0.43.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.35.0 +zwave-js-server-python==0.35.1 # homeassistant.components.zwave_me zwave_me_ws==0.1.23 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 93d1a4d060e48e..bc0d7023804db1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1591,7 +1591,7 @@ zigpy-znp==0.7.0 zigpy==0.43.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.35.0 +zwave-js-server-python==0.35.1 # homeassistant.components.zwave_me zwave_me_ws==0.1.23 From e0ff7dc2e76eea0e281e29d986f8ffead4e6e9f2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 21 Feb 2022 23:51:41 -0800 Subject: [PATCH 0924/1098] Fix radio browser on Sonos (#67017) --- homeassistant/components/sonos/media_player.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 2763bd3fe427bf..7319139d3c2e33 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -524,7 +524,10 @@ def play_media(self, media_type: str, media_id: str, **kwargs: Any) -> None: media_type = spotify.resolve_spotify_media_type(media_type) media_id = spotify.spotify_uri_from_media_browser_url(media_id) + is_radio = False + if media_source.is_media_source_id(media_id): + is_radio = media_id.startswith("media-source://radio_browser/") media_type = MEDIA_TYPE_MUSIC media_id = ( run_coroutine_threadsafe( @@ -574,7 +577,7 @@ def play_media(self, media_type: str, media_id: str, **kwargs: Any) -> None: if kwargs.get(ATTR_MEDIA_ENQUEUE): soco.add_uri_to_queue(media_id) else: - soco.play_uri(media_id) + soco.play_uri(media_id, force_radio=is_radio) elif media_type == MEDIA_TYPE_PLAYLIST: if media_id.startswith("S:"): item = media_browser.get_media(self.media.library, media_id, media_type) # type: ignore[no-untyped-call] From 7f5304b6c28dd1d76be5367dbe3ae6c1a07d3ccf Mon Sep 17 00:00:00 2001 From: Keilin Bickar Date: Tue, 22 Feb 2022 02:53:04 -0500 Subject: [PATCH 0925/1098] Add Switch entity to SleepIQ (#66966) Co-authored-by: J. Nick Koston --- homeassistant/components/sleepiq/__init__.py | 16 ++++- .../components/sleepiq/binary_sensor.py | 8 +-- homeassistant/components/sleepiq/button.py | 6 +- .../components/sleepiq/coordinator.py | 42 ++++++++++- homeassistant/components/sleepiq/entity.py | 40 +++++++---- homeassistant/components/sleepiq/sensor.py | 8 +-- homeassistant/components/sleepiq/switch.py | 53 ++++++++++++++ tests/components/sleepiq/test_switch.py | 69 +++++++++++++++++++ 8 files changed, 214 insertions(+), 28 deletions(-) create mode 100644 homeassistant/components/sleepiq/switch.py create mode 100644 tests/components/sleepiq/test_switch.py diff --git a/homeassistant/components/sleepiq/__init__.py b/homeassistant/components/sleepiq/__init__.py index 26557ca6dafada..bac88880cdba09 100644 --- a/homeassistant/components/sleepiq/__init__.py +++ b/homeassistant/components/sleepiq/__init__.py @@ -18,11 +18,15 @@ from homeassistant.helpers.typing import ConfigType from .const import DOMAIN -from .coordinator import SleepIQDataUpdateCoordinator +from .coordinator import ( + SleepIQData, + SleepIQDataUpdateCoordinator, + SleepIQPauseUpdateCoordinator, +) _LOGGER = logging.getLogger(__name__) -PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.SENSOR] +PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.SENSOR, Platform.SWITCH] CONFIG_SCHEMA = vol.Schema( { @@ -77,11 +81,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: raise ConfigEntryNotReady(str(err) or "Error reading from SleepIQ API") from err coordinator = SleepIQDataUpdateCoordinator(hass, gateway, email) + pause_coordinator = SleepIQPauseUpdateCoordinator(hass, gateway, email) # Call the SleepIQ API to refresh data await coordinator.async_config_entry_first_refresh() + await pause_coordinator.async_config_entry_first_refresh() - hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = SleepIQData( + data_coordinator=coordinator, + pause_coordinator=pause_coordinator, + client=gateway, + ) hass.config_entries.async_setup_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/sleepiq/binary_sensor.py b/homeassistant/components/sleepiq/binary_sensor.py index d2aeae06e8a25b..53611edc66b93c 100644 --- a/homeassistant/components/sleepiq/binary_sensor.py +++ b/homeassistant/components/sleepiq/binary_sensor.py @@ -11,7 +11,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import DOMAIN, ICON_EMPTY, ICON_OCCUPIED, IS_IN_BED -from .coordinator import SleepIQDataUpdateCoordinator +from .coordinator import SleepIQData from .entity import SleepIQSensor @@ -21,10 +21,10 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the SleepIQ bed binary sensors.""" - coordinator: SleepIQDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + data: SleepIQData = hass.data[DOMAIN][entry.entry_id] async_add_entities( - IsInBedBinarySensor(coordinator, bed, sleeper) - for bed in coordinator.client.beds.values() + IsInBedBinarySensor(data.data_coordinator, bed, sleeper) + for bed in data.client.beds.values() for sleeper in bed.sleepers ) diff --git a/homeassistant/components/sleepiq/button.py b/homeassistant/components/sleepiq/button.py index 8cdc0398c2dd08..cca9253d589121 100644 --- a/homeassistant/components/sleepiq/button.py +++ b/homeassistant/components/sleepiq/button.py @@ -13,7 +13,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN -from .coordinator import SleepIQDataUpdateCoordinator +from .coordinator import SleepIQData from .entity import SleepIQEntity @@ -53,11 +53,11 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the sleep number buttons.""" - coordinator: SleepIQDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + data: SleepIQData = hass.data[DOMAIN][entry.entry_id] async_add_entities( SleepNumberButton(bed, ed) - for bed in coordinator.client.beds.values() + for bed in data.client.beds.values() for ed in ENTITY_DESCRIPTIONS ) diff --git a/homeassistant/components/sleepiq/coordinator.py b/homeassistant/components/sleepiq/coordinator.py index ca664f99426c42..a2394de20b190b 100644 --- a/homeassistant/components/sleepiq/coordinator.py +++ b/homeassistant/components/sleepiq/coordinator.py @@ -1,4 +1,6 @@ """Coordinator for SleepIQ.""" +import asyncio +from dataclasses import dataclass from datetime import timedelta import logging @@ -10,9 +12,10 @@ _LOGGER = logging.getLogger(__name__) UPDATE_INTERVAL = timedelta(seconds=60) +LONGER_UPDATE_INTERVAL = timedelta(minutes=5) -class SleepIQDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict]]): +class SleepIQDataUpdateCoordinator(DataUpdateCoordinator[None]): """SleepIQ data update coordinator.""" def __init__( @@ -26,7 +29,42 @@ def __init__( hass, _LOGGER, name=f"{username}@SleepIQ", - update_method=client.fetch_bed_statuses, update_interval=UPDATE_INTERVAL, ) self.client = client + + async def _async_update_data(self) -> None: + await self.client.fetch_bed_statuses() + + +class SleepIQPauseUpdateCoordinator(DataUpdateCoordinator[None]): + """SleepIQ data update coordinator.""" + + def __init__( + self, + hass: HomeAssistant, + client: AsyncSleepIQ, + username: str, + ) -> None: + """Initialize coordinator.""" + super().__init__( + hass, + _LOGGER, + name=f"{username}@SleepIQPause", + update_interval=LONGER_UPDATE_INTERVAL, + ) + self.client = client + + async def _async_update_data(self) -> None: + await asyncio.gather( + *[bed.fetch_pause_mode() for bed in self.client.beds.values()] + ) + + +@dataclass +class SleepIQData: + """Data for the sleepiq integration.""" + + data_coordinator: SleepIQDataUpdateCoordinator + pause_coordinator: SleepIQPauseUpdateCoordinator + client: AsyncSleepIQ diff --git a/homeassistant/components/sleepiq/entity.py b/homeassistant/components/sleepiq/entity.py index 141b94fa72db56..6d0c8784eecad7 100644 --- a/homeassistant/components/sleepiq/entity.py +++ b/homeassistant/components/sleepiq/entity.py @@ -14,18 +14,23 @@ from .const import ICON_OCCUPIED, SENSOR_TYPES +def device_from_bed(bed: SleepIQBed) -> DeviceInfo: + """Create a device given a bed.""" + return DeviceInfo( + connections={(device_registry.CONNECTION_NETWORK_MAC, bed.mac_addr)}, + manufacturer="SleepNumber", + name=bed.name, + model=bed.model, + ) + + class SleepIQEntity(Entity): """Implementation of a SleepIQ entity.""" def __init__(self, bed: SleepIQBed) -> None: """Initialize the SleepIQ entity.""" self.bed = bed - self._attr_device_info = DeviceInfo( - connections={(device_registry.CONNECTION_NETWORK_MAC, bed.mac_addr)}, - manufacturer="SleepNumber", - name=bed.name, - model=bed.model, - ) + self._attr_device_info = device_from_bed(bed) class SleepIQSensor(CoordinatorEntity): @@ -44,12 +49,7 @@ def __init__( super().__init__(coordinator) self.sleeper = sleeper self.bed = bed - self._attr_device_info = DeviceInfo( - connections={(device_registry.CONNECTION_NETWORK_MAC, bed.mac_addr)}, - manufacturer="SleepNumber", - name=bed.name, - model=bed.model, - ) + self._attr_device_info = device_from_bed(bed) self._attr_name = f"SleepNumber {bed.name} {sleeper.name} {SENSOR_TYPES[name]}" self._attr_unique_id = f"{bed.id}_{sleeper.name}_{name}" @@ -65,3 +65,19 @@ def _handle_coordinator_update(self) -> None: @abstractmethod def _async_update_attrs(self) -> None: """Update sensor attributes.""" + + +class SleepIQBedCoordinator(CoordinatorEntity): + """Implementation of a SleepIQ sensor.""" + + _attr_icon = ICON_OCCUPIED + + def __init__( + self, + coordinator: DataUpdateCoordinator, + bed: SleepIQBed, + ) -> None: + """Initialize the SleepIQ sensor entity.""" + super().__init__(coordinator) + self.bed = bed + self._attr_device_info = device_from_bed(bed) diff --git a/homeassistant/components/sleepiq/sensor.py b/homeassistant/components/sleepiq/sensor.py index dd7fdabcfb38bd..7d50876b1b2d16 100644 --- a/homeassistant/components/sleepiq/sensor.py +++ b/homeassistant/components/sleepiq/sensor.py @@ -10,7 +10,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import DOMAIN, SLEEP_NUMBER -from .coordinator import SleepIQDataUpdateCoordinator +from .coordinator import SleepIQData from .entity import SleepIQSensor @@ -20,10 +20,10 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the SleepIQ bed sensors.""" - coordinator: SleepIQDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + data: SleepIQData = hass.data[DOMAIN][entry.entry_id] async_add_entities( - SleepNumberSensorEntity(coordinator, bed, sleeper) - for bed in coordinator.client.beds.values() + SleepNumberSensorEntity(data.data_coordinator, bed, sleeper) + for bed in data.client.beds.values() for sleeper in bed.sleepers ) diff --git a/homeassistant/components/sleepiq/switch.py b/homeassistant/components/sleepiq/switch.py new file mode 100644 index 00000000000000..c8977f0ce730b4 --- /dev/null +++ b/homeassistant/components/sleepiq/switch.py @@ -0,0 +1,53 @@ +"""Support for SleepIQ switches.""" +from __future__ import annotations + +from typing import Any + +from asyncsleepiq import SleepIQBed + +from homeassistant.components.switch import SwitchEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .coordinator import SleepIQData, SleepIQPauseUpdateCoordinator +from .entity import SleepIQBedCoordinator + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the sleep number switches.""" + data: SleepIQData = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + SleepNumberPrivateSwitch(data.pause_coordinator, bed) + for bed in data.client.beds.values() + ) + + +class SleepNumberPrivateSwitch(SleepIQBedCoordinator, SwitchEntity): + """Representation of SleepIQ privacy mode.""" + + def __init__( + self, coordinator: SleepIQPauseUpdateCoordinator, bed: SleepIQBed + ) -> None: + """Initialize the switch.""" + super().__init__(coordinator, bed) + self._attr_name = f"SleepNumber {bed.name} Pause Mode" + self._attr_unique_id = f"{bed.id}-pause-mode" + + @property + def is_on(self) -> bool: + """Return whether the switch is on or off.""" + return bool(self.bed.paused) + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn on switch.""" + await self.bed.set_pause_mode(True) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn off switch.""" + await self.bed.set_pause_mode(False) diff --git a/tests/components/sleepiq/test_switch.py b/tests/components/sleepiq/test_switch.py new file mode 100644 index 00000000000000..38fc747c39d869 --- /dev/null +++ b/tests/components/sleepiq/test_switch.py @@ -0,0 +1,69 @@ +"""The tests for SleepIQ switch platform.""" +from homeassistant.components.sleepiq.coordinator import LONGER_UPDATE_INTERVAL +from homeassistant.components.switch import DOMAIN +from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON +from homeassistant.helpers import entity_registry as er +from homeassistant.util.dt import utcnow + +from tests.common import async_fire_time_changed +from tests.components.sleepiq.conftest import ( + BED_ID, + BED_NAME, + BED_NAME_LOWER, + setup_platform, +) + + +async def test_setup(hass, mock_asyncsleepiq): + """Test for successfully setting up the SleepIQ platform.""" + entry = await setup_platform(hass, DOMAIN) + entity_registry = er.async_get(hass) + + assert len(entity_registry.entities) == 1 + + entry = entity_registry.async_get(f"switch.sleepnumber_{BED_NAME_LOWER}_pause_mode") + assert entry + assert entry.original_name == f"SleepNumber {BED_NAME} Pause Mode" + assert entry.unique_id == f"{BED_ID}-pause-mode" + + +async def test_switch_set_states(hass, mock_asyncsleepiq): + """Test button press.""" + await setup_platform(hass, DOMAIN) + + await hass.services.async_call( + DOMAIN, + "turn_off", + {ATTR_ENTITY_ID: f"switch.sleepnumber_{BED_NAME_LOWER}_pause_mode"}, + blocking=True, + ) + await hass.async_block_till_done() + mock_asyncsleepiq.beds[BED_ID].set_pause_mode.assert_called_with(False) + + await hass.services.async_call( + DOMAIN, + "turn_on", + {ATTR_ENTITY_ID: f"switch.sleepnumber_{BED_NAME_LOWER}_pause_mode"}, + blocking=True, + ) + await hass.async_block_till_done() + mock_asyncsleepiq.beds[BED_ID].set_pause_mode.assert_called_with(True) + + +async def test_switch_get_states(hass, mock_asyncsleepiq): + """Test button press.""" + await setup_platform(hass, DOMAIN) + + assert ( + hass.states.get(f"switch.sleepnumber_{BED_NAME_LOWER}_pause_mode").state + == STATE_OFF + ) + mock_asyncsleepiq.beds[BED_ID].paused = True + + async_fire_time_changed(hass, utcnow() + LONGER_UPDATE_INTERVAL) + await hass.async_block_till_done() + + assert ( + hass.states.get(f"switch.sleepnumber_{BED_NAME_LOWER}_pause_mode").state + == STATE_ON + ) From 92b5bcffb6a3d4814d218a4559f301d4f6c611ba Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Tue, 22 Feb 2022 09:16:02 +0100 Subject: [PATCH 0926/1098] Bump renault-api to 0.1.9 (#67016) Co-authored-by: epenet --- homeassistant/components/renault/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/renault/manifest.json b/homeassistant/components/renault/manifest.json index 131e02ceba0b14..e5a9433fad8d73 100644 --- a/homeassistant/components/renault/manifest.json +++ b/homeassistant/components/renault/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/renault", "requirements": [ - "renault-api==0.1.8" + "renault-api==0.1.9" ], "codeowners": [ "@epenet" diff --git a/requirements_all.txt b/requirements_all.txt index 55124fe8c41609..a9f467da6ff81c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2097,7 +2097,7 @@ raspyrfm-client==1.2.8 regenmaschine==2022.01.0 # homeassistant.components.renault -renault-api==0.1.8 +renault-api==0.1.9 # homeassistant.components.python_script restrictedpython==5.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bc0d7023804db1..6d8f5b36e83e51 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1304,7 +1304,7 @@ radios==0.1.0 regenmaschine==2022.01.0 # homeassistant.components.renault -renault-api==0.1.8 +renault-api==0.1.9 # homeassistant.components.python_script restrictedpython==5.2 From 0b813f86000d7c9def779253ba29c4125523b78e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 21 Feb 2022 22:50:59 -1000 Subject: [PATCH 0927/1098] Add configuration_url to lookin (#67021) --- homeassistant/components/lookin/__init__.py | 1 + homeassistant/components/lookin/entity.py | 10 ++++++---- homeassistant/components/lookin/models.py | 1 + 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/lookin/__init__.py b/homeassistant/components/lookin/__init__.py index cedc4fc77fe87e..c15d46c5158d59 100644 --- a/homeassistant/components/lookin/__init__.py +++ b/homeassistant/components/lookin/__init__.py @@ -153,6 +153,7 @@ def _async_meteo_push_update(event: UDPEvent) -> None: ) hass.data[DOMAIN][entry.entry_id] = LookinData( + host=host, lookin_udp_subs=lookin_udp_subs, lookin_device=lookin_device, meteo_coordinator=meteo_coordinator, diff --git a/homeassistant/components/lookin/entity.py b/homeassistant/components/lookin/entity.py index 58eafd3843f9ea..6ff167d86fe4c4 100644 --- a/homeassistant/components/lookin/entity.py +++ b/homeassistant/components/lookin/entity.py @@ -18,7 +18,7 @@ LOGGER = logging.getLogger(__name__) -def _lookin_device_to_device_info(lookin_device: Device) -> DeviceInfo: +def _lookin_device_to_device_info(lookin_device: Device, host: str) -> DeviceInfo: """Convert a lookin device into DeviceInfo.""" return DeviceInfo( identifiers={(DOMAIN, lookin_device.id)}, @@ -26,17 +26,19 @@ def _lookin_device_to_device_info(lookin_device: Device) -> DeviceInfo: manufacturer="LOOKin", model=MODEL_NAMES[lookin_device.model], sw_version=lookin_device.firmware, + configuration_url=f"http://{host}/device", ) def _lookin_controlled_device_to_device_info( - lookin_device: Device, uuid: str, device: Climate | Remote + lookin_device: Device, uuid: str, device: Climate | Remote, host: str ) -> DeviceInfo: return DeviceInfo( identifiers={(DOMAIN, uuid)}, name=device.name, model=device.device_type, via_device=(DOMAIN, lookin_device.id), + configuration_url=f"http://{host}/data/{uuid}", ) @@ -62,7 +64,7 @@ def __init__(self, lookin_data: LookinData) -> None: super().__init__(lookin_data.meteo_coordinator) self._set_lookin_device_attrs(lookin_data) self._attr_device_info = _lookin_device_to_device_info( - lookin_data.lookin_device + lookin_data.lookin_device, lookin_data.host ) @@ -102,7 +104,7 @@ def __init__( self._set_lookin_device_attrs(lookin_data) self._set_lookin_entity_attrs(uuid, device, lookin_data) self._attr_device_info = _lookin_controlled_device_to_device_info( - self._lookin_device, uuid, device + self._lookin_device, uuid, device, lookin_data.host ) self._attr_unique_id = uuid self._attr_name = device.name diff --git a/homeassistant/components/lookin/models.py b/homeassistant/components/lookin/models.py index 3587136c4a235e..f0dffe66ec0708 100644 --- a/homeassistant/components/lookin/models.py +++ b/homeassistant/components/lookin/models.py @@ -13,6 +13,7 @@ class LookinData: """Data for the lookin integration.""" + host: str lookin_udp_subs: LookinUDPSubscriptions lookin_device: Device meteo_coordinator: LookinDataUpdateCoordinator From f69571f16476a7988f23fd9bf5d9461a1456c566 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 21 Feb 2022 22:58:31 -1000 Subject: [PATCH 0928/1098] Add support for climate fan and oscillate mode to HomeKit (#66463) --- .../components/homekit/type_thermostats.py | 221 +++++++++++- .../homekit/test_type_thermostats.py | 332 ++++++++++++++++++ 2 files changed, 541 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/homekit/type_thermostats.py b/homeassistant/components/homekit/type_thermostats.py index 5f925e2b01dc48..8c54896e85e48c 100644 --- a/homeassistant/components/homekit/type_thermostats.py +++ b/homeassistant/components/homekit/type_thermostats.py @@ -6,6 +6,8 @@ from homeassistant.components.climate.const import ( ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE, + ATTR_FAN_MODE, + ATTR_FAN_MODES, ATTR_HUMIDITY, ATTR_HVAC_ACTION, ATTR_HVAC_MODE, @@ -13,6 +15,8 @@ ATTR_MAX_TEMP, ATTR_MIN_HUMIDITY, ATTR_MIN_TEMP, + ATTR_SWING_MODE, + ATTR_SWING_MODES, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_COOL, @@ -25,6 +29,13 @@ DEFAULT_MIN_HUMIDITY, DEFAULT_MIN_TEMP, DOMAIN as DOMAIN_CLIMATE, + FAN_AUTO, + FAN_HIGH, + FAN_LOW, + FAN_MEDIUM, + FAN_MIDDLE, + FAN_OFF, + FAN_ON, HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_DRY, @@ -32,12 +43,21 @@ HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, + SERVICE_SET_FAN_MODE, SERVICE_SET_HUMIDITY, SERVICE_SET_HVAC_MODE as SERVICE_SET_HVAC_MODE_THERMOSTAT, + SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE as SERVICE_SET_TEMPERATURE_THERMOSTAT, + SUPPORT_FAN_MODE, + SUPPORT_SWING_MODE, SUPPORT_TARGET_HUMIDITY, SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, + SWING_BOTH, + SWING_HORIZONTAL, + SWING_OFF, + SWING_ON, + SWING_VERTICAL, ) from homeassistant.components.water_heater import ( DOMAIN as DOMAIN_WATER_HEATER, @@ -51,15 +71,24 @@ TEMP_CELSIUS, TEMP_FAHRENHEIT, ) -from homeassistant.core import callback +from homeassistant.core import State, callback +from homeassistant.util.percentage import ( + ordered_list_item_to_percentage, + percentage_to_ordered_list_item, +) from .accessories import TYPES, HomeAccessory from .const import ( + CHAR_ACTIVE, CHAR_COOLING_THRESHOLD_TEMPERATURE, + CHAR_CURRENT_FAN_STATE, CHAR_CURRENT_HEATING_COOLING, CHAR_CURRENT_HUMIDITY, CHAR_CURRENT_TEMPERATURE, CHAR_HEATING_THRESHOLD_TEMPERATURE, + CHAR_ROTATION_SPEED, + CHAR_SWING_MODE, + CHAR_TARGET_FAN_STATE, CHAR_TARGET_HEATING_COOLING, CHAR_TARGET_HUMIDITY, CHAR_TARGET_TEMPERATURE, @@ -67,7 +96,9 @@ DEFAULT_MAX_TEMP_WATER_HEATER, DEFAULT_MIN_TEMP_WATER_HEATER, PROP_MAX_VALUE, + PROP_MIN_STEP, PROP_MIN_VALUE, + SERV_FANV2, SERV_THERMOSTAT, ) from .util import temperature_to_homekit, temperature_to_states @@ -103,6 +134,11 @@ HC_HEAT_COOL_OFF, ] +ORDERED_FAN_SPEEDS = [FAN_LOW, FAN_MIDDLE, FAN_MEDIUM, FAN_HIGH] +PRE_DEFINED_FAN_MODES = set(ORDERED_FAN_SPEEDS) +SWING_MODE_PREFERRED_ORDER = [SWING_ON, SWING_BOTH, SWING_HORIZONTAL, SWING_VERTICAL] +PRE_DEFINED_SWING_MODES = set(SWING_MODE_PREFERRED_ORDER) + HC_MIN_TEMP = 10 HC_MAX_TEMP = 38 @@ -127,6 +163,19 @@ CURRENT_HVAC_FAN: HC_HEAT_COOL_COOL, } +FAN_STATE_INACTIVE = 0 +FAN_STATE_IDLE = 1 +FAN_STATE_ACTIVE = 2 + +HC_HASS_TO_HOMEKIT_FAN_STATE = { + CURRENT_HVAC_OFF: FAN_STATE_INACTIVE, + CURRENT_HVAC_IDLE: FAN_STATE_IDLE, + CURRENT_HVAC_HEAT: FAN_STATE_ACTIVE, + CURRENT_HVAC_COOL: FAN_STATE_ACTIVE, + CURRENT_HVAC_DRY: FAN_STATE_ACTIVE, + CURRENT_HVAC_FAN: FAN_STATE_ACTIVE, +} + HEAT_COOL_DEADBAND = 5 @@ -144,9 +193,11 @@ def __init__(self, *args): # Add additional characteristics if auto mode is supported self.chars = [] - state = self.hass.states.get(self.entity_id) - min_humidity = state.attributes.get(ATTR_MIN_HUMIDITY, DEFAULT_MIN_HUMIDITY) - features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) + self.fan_chars = [] + state: State = self.hass.states.get(self.entity_id) + attributes = state.attributes + min_humidity = attributes.get(ATTR_MIN_HUMIDITY, DEFAULT_MIN_HUMIDITY) + features = attributes.get(ATTR_SUPPORTED_FEATURES, 0) if features & SUPPORT_TARGET_TEMPERATURE_RANGE: self.chars.extend( @@ -157,6 +208,7 @@ def __init__(self, *args): self.chars.extend((CHAR_TARGET_HUMIDITY, CHAR_CURRENT_HUMIDITY)) serv_thermostat = self.add_preload_service(SERV_THERMOSTAT, self.chars) + self.set_primary_service(serv_thermostat) # Current mode characteristics self.char_current_heat_cool = serv_thermostat.configure_char( @@ -233,10 +285,116 @@ def __init__(self, *args): CHAR_CURRENT_HUMIDITY, value=50 ) + fan_modes = self.fan_modes = { + fan_mode.lower(): fan_mode + for fan_mode in attributes.get(ATTR_FAN_MODES, []) + } + self.ordered_fan_speeds = [] + if ( + features & SUPPORT_FAN_MODE + and fan_modes + and PRE_DEFINED_FAN_MODES.intersection(fan_modes) + ): + self.ordered_fan_speeds = [ + speed for speed in ORDERED_FAN_SPEEDS if speed in fan_modes + ] + self.fan_chars.append(CHAR_ROTATION_SPEED) + + if FAN_AUTO in fan_modes and (FAN_ON in fan_modes or self.ordered_fan_speeds): + self.fan_chars.append(CHAR_TARGET_FAN_STATE) + + self.fan_modes = fan_modes + if ( + features & SUPPORT_SWING_MODE + and (swing_modes := attributes.get(ATTR_SWING_MODES)) + and PRE_DEFINED_SWING_MODES.intersection(swing_modes) + ): + self.swing_on_mode = next( + iter( + swing_mode + for swing_mode in SWING_MODE_PREFERRED_ORDER + if swing_mode in swing_modes + ) + ) + self.fan_chars.append(CHAR_SWING_MODE) + + if self.fan_chars: + if attributes.get(ATTR_HVAC_ACTION) is not None: + self.fan_chars.append(CHAR_CURRENT_FAN_STATE) + serv_fan = self.add_preload_service(SERV_FANV2, self.fan_chars) + serv_thermostat.add_linked_service(serv_fan) + self.char_active = serv_fan.configure_char( + CHAR_ACTIVE, value=1, setter_callback=self._set_fan_active + ) + if CHAR_SWING_MODE in self.fan_chars: + self.char_swing = serv_fan.configure_char( + CHAR_SWING_MODE, + value=0, + setter_callback=self._set_fan_swing_mode, + ) + self.char_swing.display_name = "Swing Mode" + if CHAR_ROTATION_SPEED in self.fan_chars: + self.char_speed = serv_fan.configure_char( + CHAR_ROTATION_SPEED, + value=100, + properties={PROP_MIN_STEP: 100 / len(self.ordered_fan_speeds)}, + setter_callback=self._set_fan_speed, + ) + self.char_speed.display_name = "Fan Mode" + if CHAR_CURRENT_FAN_STATE in self.fan_chars: + self.char_current_fan_state = serv_fan.configure_char( + CHAR_CURRENT_FAN_STATE, + value=0, + ) + self.char_current_fan_state.display_name = "Fan State" + if CHAR_TARGET_FAN_STATE in self.fan_chars and FAN_AUTO in self.fan_modes: + self.char_target_fan_state = serv_fan.configure_char( + CHAR_TARGET_FAN_STATE, + value=0, + setter_callback=self._set_fan_auto, + ) + self.char_target_fan_state.display_name = "Fan Auto" + self._async_update_state(state) serv_thermostat.setter_callback = self._set_chars + def _set_fan_swing_mode(self, swing_on) -> None: + _LOGGER.debug("%s: Set swing mode to %s", self.entity_id, swing_on) + mode = self.swing_on_mode if swing_on else SWING_OFF + params = {ATTR_ENTITY_ID: self.entity_id, ATTR_SWING_MODE: mode} + self.async_call_service(DOMAIN_CLIMATE, SERVICE_SET_SWING_MODE, params) + + def _set_fan_speed(self, speed) -> None: + _LOGGER.debug("%s: Set fan speed to %s", self.entity_id, speed) + mode = percentage_to_ordered_list_item(self.ordered_fan_speeds, speed - 1) + params = {ATTR_ENTITY_ID: self.entity_id, ATTR_FAN_MODE: mode} + self.async_call_service(DOMAIN_CLIMATE, SERVICE_SET_FAN_MODE, params) + + def _get_on_mode(self) -> str: + if self.ordered_fan_speeds: + return percentage_to_ordered_list_item(self.ordered_fan_speeds, 50) + return self.fan_modes[FAN_ON] + + def _set_fan_active(self, active) -> None: + _LOGGER.debug("%s: Set fan active to %s", self.entity_id, active) + if FAN_OFF not in self.fan_modes: + _LOGGER.debug( + "%s: Fan does not support off, resetting to on", self.entity_id + ) + self.char_active.value = 1 + self.char_active.notify() + return + mode = self._get_on_mode() if active else self.fan_modes[FAN_OFF] + params = {ATTR_ENTITY_ID: self.entity_id, ATTR_FAN_MODE: mode} + self.async_call_service(DOMAIN_CLIMATE, SERVICE_SET_FAN_MODE, params) + + def _set_fan_auto(self, auto) -> None: + _LOGGER.debug("%s: Set fan auto to %s", self.entity_id, auto) + mode = self.fan_modes[FAN_AUTO] if auto else self._get_on_mode() + params = {ATTR_ENTITY_ID: self.entity_id, ATTR_FAN_MODE: mode} + self.async_call_service(DOMAIN_CLIMATE, SERVICE_SET_FAN_MODE, params) + def _temperature_to_homekit(self, temp): return temperature_to_homekit(temp, self._unit) @@ -446,7 +604,8 @@ def async_update_state(self, new_state): @callback def _async_update_state(self, new_state): """Update state without rechecking the device features.""" - features = new_state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) + attributes = new_state.attributes + features = attributes.get(ATTR_SUPPORTED_FEATURES, 0) # Update target operation mode FIRST hvac_mode = new_state.state @@ -462,7 +621,7 @@ def _async_update_state(self, new_state): ) # Set current operation mode for supported thermostats - if hvac_action := new_state.attributes.get(ATTR_HVAC_ACTION): + if hvac_action := attributes.get(ATTR_HVAC_ACTION): homekit_hvac_action = HC_HASS_TO_HOMEKIT_ACTION[hvac_action] self.char_current_heat_cool.set_value(homekit_hvac_action) @@ -473,26 +632,26 @@ def _async_update_state(self, new_state): # Update current humidity if CHAR_CURRENT_HUMIDITY in self.chars: - current_humdity = new_state.attributes.get(ATTR_CURRENT_HUMIDITY) + current_humdity = attributes.get(ATTR_CURRENT_HUMIDITY) if isinstance(current_humdity, (int, float)): self.char_current_humidity.set_value(current_humdity) # Update target humidity if CHAR_TARGET_HUMIDITY in self.chars: - target_humdity = new_state.attributes.get(ATTR_HUMIDITY) + target_humdity = attributes.get(ATTR_HUMIDITY) if isinstance(target_humdity, (int, float)): self.char_target_humidity.set_value(target_humdity) # Update cooling threshold temperature if characteristic exists if self.char_cooling_thresh_temp: - cooling_thresh = new_state.attributes.get(ATTR_TARGET_TEMP_HIGH) + cooling_thresh = attributes.get(ATTR_TARGET_TEMP_HIGH) if isinstance(cooling_thresh, (int, float)): cooling_thresh = self._temperature_to_homekit(cooling_thresh) self.char_cooling_thresh_temp.set_value(cooling_thresh) # Update heating threshold temperature if characteristic exists if self.char_heating_thresh_temp: - heating_thresh = new_state.attributes.get(ATTR_TARGET_TEMP_LOW) + heating_thresh = attributes.get(ATTR_TARGET_TEMP_LOW) if isinstance(heating_thresh, (int, float)): heating_thresh = self._temperature_to_homekit(heating_thresh) self.char_heating_thresh_temp.set_value(heating_thresh) @@ -504,11 +663,11 @@ def _async_update_state(self, new_state): # even if the device does not support it hc_hvac_mode = self.char_target_heat_cool.value if hc_hvac_mode == HC_HEAT_COOL_HEAT: - temp_low = new_state.attributes.get(ATTR_TARGET_TEMP_LOW) + temp_low = attributes.get(ATTR_TARGET_TEMP_LOW) if isinstance(temp_low, (int, float)): target_temp = self._temperature_to_homekit(temp_low) elif hc_hvac_mode == HC_HEAT_COOL_COOL: - temp_high = new_state.attributes.get(ATTR_TARGET_TEMP_HIGH) + temp_high = attributes.get(ATTR_TARGET_TEMP_HIGH) if isinstance(temp_high, (int, float)): target_temp = self._temperature_to_homekit(temp_high) if target_temp: @@ -519,6 +678,44 @@ def _async_update_state(self, new_state): unit = UNIT_HASS_TO_HOMEKIT[self._unit] self.char_display_units.set_value(unit) + if self.fan_chars: + self._async_update_fan_state(new_state) + + @callback + def _async_update_fan_state(self, new_state): + """Update state without rechecking the device features.""" + attributes = new_state.attributes + + if CHAR_SWING_MODE in self.fan_chars and ( + swing_mode := attributes.get(ATTR_SWING_MODE) + ): + swing = 1 if swing_mode in PRE_DEFINED_SWING_MODES else 0 + self.char_swing.set_value(swing) + + fan_mode = attributes.get(ATTR_FAN_MODE) + fan_mode_lower = fan_mode.lower() if isinstance(fan_mode, str) else None + if ( + CHAR_ROTATION_SPEED in self.fan_chars + and fan_mode_lower in self.ordered_fan_speeds + ): + self.char_speed.set_value( + ordered_list_item_to_percentage(self.ordered_fan_speeds, fan_mode_lower) + ) + + if CHAR_TARGET_FAN_STATE in self.fan_chars: + self.char_target_fan_state.set_value(1 if fan_mode_lower == FAN_AUTO else 0) + + if CHAR_CURRENT_FAN_STATE in self.fan_chars and ( + hvac_action := attributes.get(ATTR_HVAC_ACTION) + ): + self.char_current_fan_state.set_value( + HC_HASS_TO_HOMEKIT_FAN_STATE[hvac_action] + ) + + self.char_active.set_value( + int(new_state.state != HVAC_MODE_OFF and fan_mode_lower != FAN_OFF) + ) + @TYPES.register("WaterHeater") class WaterHeater(HomeAccessory): diff --git a/tests/components/homekit/test_type_thermostats.py b/tests/components/homekit/test_type_thermostats.py index a11aa9d6cb75a3..d1db618e7e4bc6 100644 --- a/tests/components/homekit/test_type_thermostats.py +++ b/tests/components/homekit/test_type_thermostats.py @@ -7,12 +7,16 @@ from homeassistant.components.climate.const import ( ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE, + ATTR_FAN_MODE, + ATTR_FAN_MODES, ATTR_HUMIDITY, ATTR_HVAC_ACTION, ATTR_HVAC_MODE, ATTR_HVAC_MODES, ATTR_MAX_TEMP, ATTR_MIN_TEMP, + ATTR_SWING_MODE, + ATTR_SWING_MODES, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_STEP, @@ -24,6 +28,12 @@ DEFAULT_MAX_TEMP, DEFAULT_MIN_HUMIDITY, DOMAIN as DOMAIN_CLIMATE, + FAN_AUTO, + FAN_HIGH, + FAN_LOW, + FAN_MEDIUM, + FAN_OFF, + FAN_ON, HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_DRY, @@ -31,11 +41,22 @@ HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, + SERVICE_SET_FAN_MODE, + SERVICE_SET_SWING_MODE, + SUPPORT_FAN_MODE, + SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, + SWING_BOTH, + SWING_HORIZONTAL, + SWING_OFF, ) from homeassistant.components.homekit.const import ( ATTR_VALUE, + CHAR_CURRENT_FAN_STATE, + CHAR_ROTATION_SPEED, + CHAR_SWING_MODE, + CHAR_TARGET_FAN_STATE, DEFAULT_MAX_TEMP_WATER_HEATER, DEFAULT_MIN_TEMP_WATER_HEATER, PROP_MAX_VALUE, @@ -2017,3 +2038,314 @@ async def test_thermostat_with_temp_clamps(hass, hk_driver, events): assert acc.char_target_heat_cool.value == 3 assert acc.char_current_temp.value == 1000 assert acc.char_display_units.value == 0 + + +async def test_thermostat_with_fan_modes_with_auto(hass, hk_driver, events): + """Test a thermostate with fan modes with an auto fan mode.""" + entity_id = "climate.test" + hass.states.async_set( + entity_id, + HVAC_MODE_OFF, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE + | SUPPORT_TARGET_TEMPERATURE_RANGE + | SUPPORT_FAN_MODE + | SUPPORT_SWING_MODE, + ATTR_FAN_MODES: [FAN_AUTO, FAN_LOW, FAN_MEDIUM, FAN_HIGH], + ATTR_SWING_MODES: [SWING_BOTH, SWING_OFF, SWING_HORIZONTAL], + ATTR_HVAC_ACTION: CURRENT_HVAC_IDLE, + ATTR_FAN_MODE: FAN_AUTO, + ATTR_SWING_MODE: SWING_BOTH, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], + }, + ) + await hass.async_block_till_done() + acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None) + hk_driver.add_accessory(acc) + + await acc.run() + await hass.async_block_till_done() + + assert acc.char_cooling_thresh_temp.value == 23.0 + assert acc.char_heating_thresh_temp.value == 19.0 + assert acc.ordered_fan_speeds == [FAN_LOW, FAN_MEDIUM, FAN_HIGH] + assert CHAR_ROTATION_SPEED in acc.fan_chars + assert CHAR_TARGET_FAN_STATE in acc.fan_chars + assert CHAR_SWING_MODE in acc.fan_chars + assert CHAR_CURRENT_FAN_STATE in acc.fan_chars + assert acc.char_speed.value == 100 + + hass.states.async_set( + entity_id, + HVAC_MODE_OFF, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE + | SUPPORT_TARGET_TEMPERATURE_RANGE + | SUPPORT_FAN_MODE + | SUPPORT_SWING_MODE, + ATTR_FAN_MODES: [FAN_AUTO, FAN_LOW, FAN_MEDIUM, FAN_HIGH], + ATTR_SWING_MODES: [SWING_BOTH, SWING_OFF, SWING_HORIZONTAL], + ATTR_HVAC_ACTION: CURRENT_HVAC_IDLE, + ATTR_FAN_MODE: FAN_LOW, + ATTR_SWING_MODE: SWING_BOTH, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], + }, + ) + await hass.async_block_till_done() + assert acc.char_speed.value == pytest.approx(100 / 3) + + call_set_swing_mode = async_mock_service( + hass, DOMAIN_CLIMATE, SERVICE_SET_SWING_MODE + ) + char_swing_iid = acc.char_swing.to_HAP()[HAP_REPR_IID] + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_swing_iid, + HAP_REPR_VALUE: 0, + } + ] + }, + "mock_addr", + ) + + await hass.async_block_till_done() + assert len(call_set_swing_mode) == 1 + assert call_set_swing_mode[-1].data[ATTR_ENTITY_ID] == entity_id + assert call_set_swing_mode[-1].data[ATTR_SWING_MODE] == SWING_OFF + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_swing_iid, + HAP_REPR_VALUE: 1, + } + ] + }, + "mock_addr", + ) + + await hass.async_block_till_done() + assert len(call_set_swing_mode) == 2 + assert call_set_swing_mode[-1].data[ATTR_ENTITY_ID] == entity_id + assert call_set_swing_mode[-1].data[ATTR_SWING_MODE] == SWING_BOTH + + call_set_fan_mode = async_mock_service(hass, DOMAIN_CLIMATE, SERVICE_SET_FAN_MODE) + char_rotation_speed_iid = acc.char_speed.to_HAP()[HAP_REPR_IID] + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_rotation_speed_iid, + HAP_REPR_VALUE: 100, + } + ] + }, + "mock_addr", + ) + + await hass.async_block_till_done() + assert len(call_set_fan_mode) == 1 + assert call_set_fan_mode[-1].data[ATTR_ENTITY_ID] == entity_id + assert call_set_fan_mode[-1].data[ATTR_FAN_MODE] == FAN_HIGH + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_rotation_speed_iid, + HAP_REPR_VALUE: 100 / 3, + } + ] + }, + "mock_addr", + ) + + await hass.async_block_till_done() + assert len(call_set_fan_mode) == 2 + assert call_set_fan_mode[-1].data[ATTR_ENTITY_ID] == entity_id + assert call_set_fan_mode[-1].data[ATTR_FAN_MODE] == FAN_LOW + + char_active_iid = acc.char_active.to_HAP()[HAP_REPR_IID] + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_active_iid, + HAP_REPR_VALUE: 0, + } + ] + }, + "mock_addr", + ) + + await hass.async_block_till_done() + assert acc.char_active.value == 1 + + char_target_fan_state_iid = acc.char_target_fan_state.to_HAP()[HAP_REPR_IID] + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_target_fan_state_iid, + HAP_REPR_VALUE: 1, + } + ] + }, + "mock_addr", + ) + + await hass.async_block_till_done() + assert len(call_set_fan_mode) == 3 + assert call_set_fan_mode[-1].data[ATTR_ENTITY_ID] == entity_id + assert call_set_fan_mode[-1].data[ATTR_FAN_MODE] == FAN_AUTO + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_target_fan_state_iid, + HAP_REPR_VALUE: 0, + } + ] + }, + "mock_addr", + ) + + await hass.async_block_till_done() + assert len(call_set_fan_mode) == 4 + assert call_set_fan_mode[-1].data[ATTR_ENTITY_ID] == entity_id + assert call_set_fan_mode[-1].data[ATTR_FAN_MODE] == FAN_MEDIUM + + +async def test_thermostat_with_fan_modes_with_off(hass, hk_driver, events): + """Test a thermostate with fan modes that can turn off.""" + entity_id = "climate.test" + hass.states.async_set( + entity_id, + HVAC_MODE_COOL, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE + | SUPPORT_TARGET_TEMPERATURE_RANGE + | SUPPORT_FAN_MODE + | SUPPORT_SWING_MODE, + ATTR_FAN_MODES: [FAN_ON, FAN_OFF], + ATTR_SWING_MODES: [SWING_BOTH, SWING_OFF, SWING_HORIZONTAL], + ATTR_HVAC_ACTION: CURRENT_HVAC_IDLE, + ATTR_FAN_MODE: FAN_ON, + ATTR_SWING_MODE: SWING_BOTH, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], + }, + ) + await hass.async_block_till_done() + acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None) + hk_driver.add_accessory(acc) + + await acc.run() + await hass.async_block_till_done() + + assert acc.char_cooling_thresh_temp.value == 23.0 + assert acc.char_heating_thresh_temp.value == 19.0 + assert acc.ordered_fan_speeds == [] + assert CHAR_ROTATION_SPEED not in acc.fan_chars + assert CHAR_TARGET_FAN_STATE not in acc.fan_chars + assert CHAR_SWING_MODE in acc.fan_chars + assert CHAR_CURRENT_FAN_STATE in acc.fan_chars + assert acc.char_active.value == 1 + + hass.states.async_set( + entity_id, + HVAC_MODE_COOL, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE + | SUPPORT_TARGET_TEMPERATURE_RANGE + | SUPPORT_FAN_MODE + | SUPPORT_SWING_MODE, + ATTR_FAN_MODES: [FAN_ON, FAN_OFF], + ATTR_SWING_MODES: [SWING_BOTH, SWING_OFF, SWING_HORIZONTAL], + ATTR_HVAC_ACTION: CURRENT_HVAC_IDLE, + ATTR_FAN_MODE: FAN_OFF, + ATTR_SWING_MODE: SWING_BOTH, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], + }, + ) + await hass.async_block_till_done() + assert acc.char_active.value == 0 + + call_set_fan_mode = async_mock_service(hass, DOMAIN_CLIMATE, SERVICE_SET_FAN_MODE) + char_active_iid = acc.char_active.to_HAP()[HAP_REPR_IID] + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_active_iid, + HAP_REPR_VALUE: 1, + } + ] + }, + "mock_addr", + ) + + await hass.async_block_till_done() + assert len(call_set_fan_mode) == 1 + assert call_set_fan_mode[-1].data[ATTR_ENTITY_ID] == entity_id + assert call_set_fan_mode[-1].data[ATTR_FAN_MODE] == FAN_ON + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_active_iid, + HAP_REPR_VALUE: 0, + } + ] + }, + "mock_addr", + ) + + await hass.async_block_till_done() + assert len(call_set_fan_mode) == 2 + assert call_set_fan_mode[-1].data[ATTR_ENTITY_ID] == entity_id + assert call_set_fan_mode[-1].data[ATTR_FAN_MODE] == FAN_OFF From 31867d54b6fb5b05eb292b460a6948f91d44c301 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 22 Feb 2022 09:26:41 +0000 Subject: [PATCH 0929/1098] Add Google Cast groups to device registry (#66805) --- homeassistant/components/cast/media_player.py | 13 ++++++------- tests/components/cast/test_media_player.py | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index bfbe4f84d600d2..091bde53ae0bbd 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -292,13 +292,12 @@ def __init__(self, hass: HomeAssistant, cast_info: ChromecastInfo) -> None: self._cast_view_remove_handler = None self._attr_unique_id = str(cast_info.uuid) self._attr_name = cast_info.friendly_name - if cast_info.cast_info.model_name != "Google Cast Group": - self._attr_device_info = DeviceInfo( - identifiers={(CAST_DOMAIN, str(cast_info.uuid).replace("-", ""))}, - manufacturer=str(cast_info.cast_info.manufacturer), - model=cast_info.cast_info.model_name, - name=str(cast_info.friendly_name), - ) + self._attr_device_info = DeviceInfo( + identifiers={(CAST_DOMAIN, str(cast_info.uuid).replace("-", ""))}, + manufacturer=str(cast_info.cast_info.manufacturer), + model=cast_info.cast_info.model_name, + name=str(cast_info.friendly_name), + ) async def async_added_to_hass(self): """Create chromecast object when added to hass.""" diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 2299389f12b51d..4f12a2b02bb355 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -632,7 +632,7 @@ async def test_entity_availability(hass: HomeAssistant): assert state.state == "unavailable" -@pytest.mark.parametrize("port,entry_type", ((8009, None),)) +@pytest.mark.parametrize("port,entry_type", ((8009, None), (12345, None))) async def test_device_registry(hass: HomeAssistant, port, entry_type): """Test device registry integration.""" entity_id = "media_player.speaker" From df9e92b4b8a6dcbc548116dd54c100d0f013bcea Mon Sep 17 00:00:00 2001 From: jjlawren Date: Tue, 22 Feb 2022 03:35:19 -0600 Subject: [PATCH 0930/1098] Add log message when Plex library section not found (#66820) --- homeassistant/components/plex/server.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index f1ac620f37906f..b4dc5755a73a7e 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -644,7 +644,10 @@ def lookup_media(self, media_type, **kwargs): _LOGGER.error("Must specify 'library_name' for this search") return None except NotFound: - _LOGGER.error("Library '%s' not found", library_name) + library_sections = [section.title for section in self.library.sections()] + _LOGGER.error( + "Library '%s' not found in %s", library_name, library_sections + ) return None return search_media(media_type, library_section, **kwargs) From 09e16fa3dc6fa6e9d8124c85be7415d135c11f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Tue, 22 Feb 2022 11:39:09 +0200 Subject: [PATCH 0931/1098] Add service info for upcloud entities (#61740) --- homeassistant/components/upcloud/__init__.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/upcloud/__init__.py b/homeassistant/components/upcloud/__init__.py index a4901dcf2d24b7..a824bc596d004a 100644 --- a/homeassistant/components/upcloud/__init__.py +++ b/homeassistant/components/upcloud/__init__.py @@ -21,16 +21,18 @@ ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, ) +from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, ) -from .const import CONFIG_ENTRY_UPDATE_SIGNAL_TEMPLATE, DEFAULT_SCAN_INTERVAL +from .const import CONFIG_ENTRY_UPDATE_SIGNAL_TEMPLATE, DEFAULT_SCAN_INTERVAL, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -177,7 +179,7 @@ class UpCloudServerEntity(CoordinatorEntity): def __init__( self, - coordinator: DataUpdateCoordinator[dict[str, upcloud_api.Server]], + coordinator: UpCloudDataUpdateCoordinator, uuid: str, ) -> None: """Initialize the UpCloud server entity.""" @@ -235,3 +237,17 @@ def extra_state_attributes(self) -> dict[str, Any]: ATTR_MEMORY_AMOUNT, ) } + + @property + def device_info(self) -> DeviceInfo: + """Return info for device registry.""" + assert self.coordinator.config_entry is not None + return DeviceInfo( + configuration_url="https://hub.upcloud.com", + default_model="Control Panel", + entry_type=DeviceEntryType.SERVICE, + identifiers={ + (DOMAIN, f"{self.coordinator.config_entry.data[CONF_USERNAME]}@hub") + }, + manufacturer="UpCloud Ltd", + ) From 0042fd5199cba7ef0bb0fccae09c14986477e8ed Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 22 Feb 2022 11:27:38 +0100 Subject: [PATCH 0932/1098] Fix nightly builder (#67022) --- .github/workflows/builder.yml | 3 +-- script/version_bump.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 126deba34943ca..ce545684da5367 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -114,8 +114,7 @@ jobs: run: | python3 -m pip install packaging python3 -m pip install --use-deprecated=legacy-resolver . - python3 script/version_bump.py nightly - version="$(python setup.py -V)" + version="$(python3 script/version_bump.py nightly)" - name: Write meta info file shell: bash diff --git a/script/version_bump.py b/script/version_bump.py index 7cc27c2e1e7458..d714c5183b7ed5 100755 --- a/script/version_bump.py +++ b/script/version_bump.py @@ -172,6 +172,7 @@ def main(): write_version(bumped) write_version_metadata(bumped) write_ci_workflow(bumped) + print(bumped) if not arguments.commit: return From 909de62bd4c6f279fe771dabfe8b865c0b822e89 Mon Sep 17 00:00:00 2001 From: Sjoerd Date: Tue, 22 Feb 2022 11:31:21 +0100 Subject: [PATCH 0933/1098] Add the ICAO 24-bit address to the OpenSky sensor events (#66114) --- homeassistant/components/opensky/sensor.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/opensky/sensor.py b/homeassistant/components/opensky/sensor.py index 1228bc87df1e89..b4278bcce3632c 100644 --- a/homeassistant/components/opensky/sensor.py +++ b/homeassistant/components/opensky/sensor.py @@ -26,6 +26,7 @@ CONF_ALTITUDE = "altitude" +ATTR_ICAO24 = "icao24" ATTR_CALLSIGN = "callsign" ATTR_ALTITUDE = "altitude" ATTR_ON_GROUND = "on_ground" @@ -45,7 +46,7 @@ ) OPENSKY_API_URL = "https://opensky-network.org/api/states/all" OPENSKY_API_FIELDS = [ - "icao24", + ATTR_ICAO24, ATTR_CALLSIGN, "origin_country", "time_position", @@ -128,11 +129,13 @@ def _handle_boundary(self, flights, event, metadata): altitude = metadata[flight].get(ATTR_ALTITUDE) longitude = metadata[flight].get(ATTR_LONGITUDE) latitude = metadata[flight].get(ATTR_LATITUDE) + icao24 = metadata[flight].get(ATTR_ICAO24) else: # Assume Flight has landed if missing. altitude = 0 longitude = None latitude = None + icao24 = None data = { ATTR_CALLSIGN: flight, @@ -140,6 +143,7 @@ def _handle_boundary(self, flights, event, metadata): ATTR_SENSOR: self._name, ATTR_LONGITUDE: longitude, ATTR_LATITUDE: latitude, + ATTR_ICAO24: icao24, } self._hass.bus.fire(event, data) From a12d6aa6ffc7129a7be6ce2426fcbf14f84b10a2 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 22 Feb 2022 06:03:01 -0500 Subject: [PATCH 0934/1098] Log error when using zwave_js 'refresh_value' on ping button/node status sensor (#66847) * Raise when using 'zwave_js.refresh_value' on ping button * Revert test change * block till done * Switch from raising an exception to logging an error for both the ping button and node status sensor * missed commit --- homeassistant/components/zwave_js/button.py | 18 +++++++++++++++++- homeassistant/components/zwave_js/sensor.py | 5 ++++- tests/components/zwave_js/test_button.py | 13 +++++++++++++ tests/components/zwave_js/test_sensor.py | 13 +++++++++++++ 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zwave_js/button.py b/homeassistant/components/zwave_js/button.py index ef8572fedc3b25..fb62bbdc3dc162 100644 --- a/homeassistant/components/zwave_js/button.py +++ b/homeassistant/components/zwave_js/button.py @@ -11,7 +11,7 @@ from homeassistant.helpers.entity import DeviceInfo, EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DATA_CLIENT, DOMAIN +from .const import DATA_CLIENT, DOMAIN, LOGGER from .helpers import get_device_id, get_valueless_base_unique_id @@ -58,8 +58,24 @@ def __init__(self, client: ZwaveClient, node: ZwaveNode) -> None: identifiers={get_device_id(client, node)}, ) + async def async_poll_value(self, _: bool) -> None: + """Poll a value.""" + # pylint: disable=no-self-use + LOGGER.error( + "There is no value to refresh for this entity so the zwave_js.refresh_value " + "service won't work for it" + ) + async def async_added_to_hass(self) -> None: """Call when entity is added.""" + self.async_on_remove( + async_dispatcher_connect( + self.hass, + f"{DOMAIN}_{self.unique_id}_poll_value", + self.async_poll_value, + ) + ) + self.async_on_remove( async_dispatcher_connect( self.hass, diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index 840c36b7fde65f..8ac909d76de9a4 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -488,7 +488,10 @@ def __init__( async def async_poll_value(self, _: bool) -> None: """Poll a value.""" # pylint: disable=no-self-use - raise ValueError("There is no value to poll for this entity") + LOGGER.error( + "There is no value to refresh for this entity so the zwave_js.refresh_value " + "service won't work for it" + ) @callback def _status_changed(self, _: dict) -> None: diff --git a/tests/components/zwave_js/test_button.py b/tests/components/zwave_js/test_button.py index deb95e5eef4dce..9b5ac66b06fa95 100644 --- a/tests/components/zwave_js/test_button.py +++ b/tests/components/zwave_js/test_button.py @@ -1,5 +1,6 @@ """Test the Z-Wave JS button entities.""" from homeassistant.components.button.const import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS +from homeassistant.components.zwave_js.const import DOMAIN, SERVICE_REFRESH_VALUE from homeassistant.const import ATTR_ENTITY_ID @@ -8,6 +9,7 @@ async def test_ping_entity( client, climate_radio_thermostat_ct100_plus_different_endpoints, integration, + caplog, ): """Test ping entity.""" client.async_send_command.return_value = {"responded": True} @@ -31,3 +33,14 @@ async def test_ping_entity( ) client.async_send_command.reset_mock() + + await hass.services.async_call( + DOMAIN, + SERVICE_REFRESH_VALUE, + { + ATTR_ENTITY_ID: "button.z_wave_thermostat_ping", + }, + blocking=True, + ) + + assert "There is no value to refresh for this entity" in caplog.text diff --git a/tests/components/zwave_js/test_sensor.py b/tests/components/zwave_js/test_sensor.py index c632f0fca177d8..891a417551eae6 100644 --- a/tests/components/zwave_js/test_sensor.py +++ b/tests/components/zwave_js/test_sensor.py @@ -15,6 +15,7 @@ ATTR_METER_TYPE_NAME, ATTR_VALUE, DOMAIN, + SERVICE_REFRESH_VALUE, SERVICE_RESET_METER, ) from homeassistant.const import ( @@ -207,6 +208,7 @@ async def test_node_status_sensor_not_ready( lock_id_lock_as_id150_not_ready, lock_id_lock_as_id150_state, integration, + caplog, ): """Test node status sensor is created and available if node is not ready.""" NODE_STATUS_ENTITY = "sensor.z_wave_module_for_id_lock_150_and_101_node_status" @@ -234,6 +236,17 @@ async def test_node_status_sensor_not_ready( assert hass.states.get(NODE_STATUS_ENTITY) assert hass.states.get(NODE_STATUS_ENTITY).state == "alive" + await hass.services.async_call( + DOMAIN, + SERVICE_REFRESH_VALUE, + { + ATTR_ENTITY_ID: NODE_STATUS_ENTITY, + }, + blocking=True, + ) + + assert "There is no value to refresh for this entity" in caplog.text + async def test_reset_meter( hass, From 3dd31acc33e8efd104268c5d6b343eac7404910d Mon Sep 17 00:00:00 2001 From: Pascal Winters Date: Tue, 22 Feb 2022 14:17:50 +0100 Subject: [PATCH 0935/1098] Bump PySwitchbot to 0.13.3 (#67025) --- homeassistant/components/switchbot/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/switchbot/manifest.json b/homeassistant/components/switchbot/manifest.json index 617698b8b022dd..7a16225dcbbf2c 100644 --- a/homeassistant/components/switchbot/manifest.json +++ b/homeassistant/components/switchbot/manifest.json @@ -2,7 +2,7 @@ "domain": "switchbot", "name": "SwitchBot", "documentation": "https://www.home-assistant.io/integrations/switchbot", - "requirements": ["PySwitchbot==0.13.2"], + "requirements": ["PySwitchbot==0.13.3"], "config_flow": true, "codeowners": ["@danielhiversen", "@RenierM26"], "iot_class": "local_polling", diff --git a/requirements_all.txt b/requirements_all.txt index a9f467da6ff81c..ae6697e9fcfbc2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -46,7 +46,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -# PySwitchbot==0.13.2 +# PySwitchbot==0.13.3 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6d8f5b36e83e51..ae9acafcee8f31 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -27,7 +27,7 @@ PyQRCode==1.2.1 PyRMVtransport==0.3.3 # homeassistant.components.switchbot -# PySwitchbot==0.13.2 +# PySwitchbot==0.13.3 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 From c3dc936b54451af0cf3c461ba1211dc4c3241343 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Tue, 22 Feb 2022 14:21:38 +0100 Subject: [PATCH 0936/1098] Cleanup Renault tests (#67030) Co-authored-by: epenet --- tests/components/renault/conftest.py | 6 ------ tests/components/renault/const.py | 31 ---------------------------- 2 files changed, 37 deletions(-) diff --git a/tests/components/renault/conftest.py b/tests/components/renault/conftest.py index da86b41e3b05f8..89c6c364860099 100644 --- a/tests/components/renault/conftest.py +++ b/tests/components/renault/conftest.py @@ -63,12 +63,6 @@ def patch_get_vehicles(vehicle_type: str): load_fixture(f"renault/vehicle_{vehicle_type}.json") ) ), - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.supports_endpoint", - side_effect=MOCK_VEHICLES[vehicle_type]["endpoints_available"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.has_contract_for_endpoint", - return_value=True, ): yield diff --git a/tests/components/renault/const.py b/tests/components/renault/const.py index d0fadc54030436..368f3b97fbdf8d 100644 --- a/tests/components/renault/const.py +++ b/tests/components/renault/const.py @@ -79,13 +79,6 @@ ATTR_NAME: "REG-NUMBER", ATTR_SW_VERSION: "X101VE", }, - "endpoints_available": [ - True, # cockpit - True, # hvac-status - False, # location - True, # battery-status - True, # charge-mode - ], "endpoints": { "battery_status": "battery_status_charging.json", "charge_mode": "charge_mode_always.json", @@ -246,14 +239,6 @@ ATTR_NAME: "REG-NUMBER", ATTR_SW_VERSION: "X102VE", }, - "endpoints_available": [ - True, # cockpit - True, # hvac-status - True, # location - True, # battery-status - True, # charge-mode - True, # lock-status - ], "endpoints": { "battery_status": "battery_status_not_charging.json", "charge_mode": "charge_mode_schedule.json", @@ -466,14 +451,6 @@ ATTR_NAME: "REG-NUMBER", ATTR_SW_VERSION: "XJB1SU", }, - "endpoints_available": [ - True, # cockpit - False, # hvac-status - True, # location - True, # battery-status - True, # charge-mode - True, # lock-status - ], "endpoints": { "battery_status": "battery_status_charging.json", "charge_mode": "charge_mode_always.json", @@ -674,14 +651,6 @@ ATTR_NAME: "REG-NUMBER", ATTR_SW_VERSION: "XJB1SU", }, - "endpoints_available": [ - True, # cockpit - False, # hvac-status - True, # location - # Ignore, # battery-status - # Ignore, # charge-mode - True, # lock-status - ], "endpoints": { "cockpit": "cockpit_fuel.json", "location": "location.json", From b57a7ce6a3f1717e97b06f15cf9bec56cd11edc2 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Tue, 22 Feb 2022 14:31:49 +0100 Subject: [PATCH 0937/1098] Bump pysensibo to v1.0.7 (#67032) --- homeassistant/components/sensibo/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensibo/manifest.json b/homeassistant/components/sensibo/manifest.json index 758dfca4b97313..35273bb3d6fdfe 100644 --- a/homeassistant/components/sensibo/manifest.json +++ b/homeassistant/components/sensibo/manifest.json @@ -2,7 +2,7 @@ "domain": "sensibo", "name": "Sensibo", "documentation": "https://www.home-assistant.io/integrations/sensibo", - "requirements": ["pysensibo==1.0.6"], + "requirements": ["pysensibo==1.0.7"], "config_flow": true, "codeowners": ["@andrey-git", "@gjohansson-ST"], "iot_class": "cloud_polling", diff --git a/requirements_all.txt b/requirements_all.txt index ae6697e9fcfbc2..8a1ea7d86d6880 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1819,7 +1819,7 @@ pysaj==0.0.16 pysdcp==1 # homeassistant.components.sensibo -pysensibo==1.0.6 +pysensibo==1.0.7 # homeassistant.components.serial # homeassistant.components.zha diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ae9acafcee8f31..aa29cdb4f0c91d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1161,7 +1161,7 @@ pyrituals==0.0.6 pyruckus==0.12 # homeassistant.components.sensibo -pysensibo==1.0.6 +pysensibo==1.0.7 # homeassistant.components.serial # homeassistant.components.zha From 8eb75074824def45d5029e65e192fe203b6a3e3f Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Tue, 22 Feb 2022 14:32:55 +0100 Subject: [PATCH 0938/1098] Cleanup after setup.py removal (#67036) --- .core_files.yaml | 2 +- .github/workflows/wheels.yml | 2 +- CODEOWNERS | 2 +- script/gen_requirements_all.py | 2 +- script/hassfest/codeowners.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.core_files.yaml b/.core_files.yaml index 2b9f1563d99b25..ebc3ff376f8ae0 100644 --- a/.core_files.yaml +++ b/.core_files.yaml @@ -128,7 +128,7 @@ requirements: &requirements - .github/workflows/* - homeassistant/package_constraints.txt - requirements*.txt - - setup.py + - setup.cfg any: - *base_platforms diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index a60ac651e31802..a0d6396ec3012c 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -44,7 +44,7 @@ jobs: echo "GRPC_PYTHON_BUILD_WITH_CYTHON=true" echo "GRPC_PYTHON_DISABLE_LIBC_COMPATIBILITY=true" # GRPC on armv7 needs -lexecinfo (issue #56669) since home assistant installs - # execinfo-dev when building wheels. The setup.py does not have an option for + # execinfo-dev when building wheels. The setuptools build setup does not have an option for # adding a single LDFLAG so copy all relevant linux flags here (as of 1.43.0) echo "GRPC_PYTHON_LDFLAGS=-lpthread -Wl,-wrap,memcpy -static-libgcc -lexecinfo" ) > .env_file diff --git a/CODEOWNERS b/CODEOWNERS index 075c8abbc659f2..283bd6442b1a87 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -4,7 +4,7 @@ # https://github.com/blog/2392-introducing-code-owners # Home Assistant Core -setup.py @home-assistant/core +setup.cfg @home-assistant/core homeassistant/*.py @home-assistant/core homeassistant/helpers/* @home-assistant/core homeassistant/util/* @home-assistant/core diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 95a5999aaf11ab..1e3f39a2f8900c 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -168,7 +168,7 @@ def explore_module(package, explore_children): def core_requirements(): - """Gather core requirements out of setup.py.""" + """Gather core requirements out of setup.cfg.""" parser = configparser.ConfigParser() parser.read("setup.cfg") return parser["options"]["install_requires"].strip().split("\n") diff --git a/script/hassfest/codeowners.py b/script/hassfest/codeowners.py index 91bd81efef5c87..cf8fb02b98900a 100644 --- a/script/hassfest/codeowners.py +++ b/script/hassfest/codeowners.py @@ -10,7 +10,7 @@ # https://github.com/blog/2392-introducing-code-owners # Home Assistant Core -setup.py @home-assistant/core +setup.cfg @home-assistant/core homeassistant/*.py @home-assistant/core homeassistant/helpers/* @home-assistant/core homeassistant/util/* @home-assistant/core From b6d8a82e7d2f2ef89afa92d09907768bdd42e32a Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Tue, 22 Feb 2022 14:43:02 +0100 Subject: [PATCH 0939/1098] Add Dacia as supported brand to Renault (#67029) Co-authored-by: epenet --- homeassistant/components/renault/manifest.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/renault/manifest.json b/homeassistant/components/renault/manifest.json index e5a9433fad8d73..71e2e7d64b8a53 100644 --- a/homeassistant/components/renault/manifest.json +++ b/homeassistant/components/renault/manifest.json @@ -10,5 +10,6 @@ "@epenet" ], "iot_class": "cloud_polling", - "loggers": ["renault_api"] + "loggers": ["renault_api"], + "supported_brands":{"dacia":"Dacia"} } From 995f4fbfda214616eaaaadbf87c5292c7254f14e Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Tue, 22 Feb 2022 14:45:05 +0100 Subject: [PATCH 0940/1098] Upgrade pwmled to 1.6.10 (#67034) --- homeassistant/components/rpi_gpio_pwm/manifest.json | 2 +- requirements_all.txt | 2 +- script/pip_check | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/rpi_gpio_pwm/manifest.json b/homeassistant/components/rpi_gpio_pwm/manifest.json index 96094e6f75ea6d..403f607e379372 100644 --- a/homeassistant/components/rpi_gpio_pwm/manifest.json +++ b/homeassistant/components/rpi_gpio_pwm/manifest.json @@ -2,7 +2,7 @@ "domain": "rpi_gpio_pwm", "name": "pigpio Daemon PWM LED", "documentation": "https://www.home-assistant.io/integrations/rpi_gpio_pwm", - "requirements": ["pwmled==1.6.9"], + "requirements": ["pwmled==1.6.10"], "codeowners": ["@soldag"], "iot_class": "local_push", "loggers": ["adafruit_blinka", "adafruit_circuitpython_pca9685", "pwmled"] diff --git a/requirements_all.txt b/requirements_all.txt index 8a1ea7d86d6880..2adac10bc6cf36 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1317,7 +1317,7 @@ pushover_complete==1.1.1 pvo==0.2.2 # homeassistant.components.rpi_gpio_pwm -pwmled==1.6.9 +pwmled==1.6.10 # homeassistant.components.canary py-canary==0.5.1 diff --git a/script/pip_check b/script/pip_check index af47f101fbba5b..c4ced71605a342 100755 --- a/script/pip_check +++ b/script/pip_check @@ -3,7 +3,7 @@ PIP_CACHE=$1 # Number of existing dependency conflicts # Update if a PR resolve one! -DEPENDENCY_CONFLICTS=9 +DEPENDENCY_CONFLICTS=8 PIP_CHECK=$(pip check --cache-dir=$PIP_CACHE) LINE_COUNT=$(echo "$PIP_CHECK" | wc -l) From 633e7e90ac0b1c55b91ed8831aad95e04fd2157b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Tue, 22 Feb 2022 14:46:46 +0100 Subject: [PATCH 0941/1098] Deprecate the updater integration (#67038) --- .../components/default_config/manifest.json | 3 +-- homeassistant/components/updater/__init__.py | 14 ++++---------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/default_config/manifest.json b/homeassistant/components/default_config/manifest.json index 88f86034aeaaea..9a65af96852a1d 100644 --- a/homeassistant/components/default_config/manifest.json +++ b/homeassistant/components/default_config/manifest.json @@ -31,11 +31,10 @@ "tag", "timer", "usb", - "updater", "webhook", "zeroconf", "zone" ], "codeowners": [], "quality_scale": "internal" -} +} \ No newline at end of file diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py index 6a6264304c9fc1..4f88b5d13696aa 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -58,16 +58,10 @@ def __init__( async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the updater component.""" - conf = config.get(DOMAIN, {}) - - for option in (CONF_COMPONENT_REPORTING, CONF_REPORTING): - if option in conf: - _LOGGER.warning( - "Analytics reporting with the option '%s' " - "is deprecated and you should remove that from your configuration. " - "The analytics part of this integration has moved to the new 'analytics' integration", - option, - ) + _LOGGER.warning( + "The updater integration has been deprecated and will be removed in 2022.5, " + "please remove it from your configuration" + ) async def check_new_version() -> Updater: """Check if a new version is available and report if one is.""" From a4a5057b0bdcd106fd122aa97adcfc1b3ed97764 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Tue, 22 Feb 2022 14:59:59 +0100 Subject: [PATCH 0942/1098] Improve code quality moon (#66461) * Code quality moon * Fix review comments --- homeassistant/components/moon/sensor.py | 71 +++++++++++-------------- tests/components/moon/test_sensor.py | 70 +++++++++++++----------- 2 files changed, 70 insertions(+), 71 deletions(-) diff --git a/homeassistant/components/moon/sensor.py b/homeassistant/components/moon/sensor.py index 97bc7ec4673ba5..cd10d9168d9c61 100644 --- a/homeassistant/components/moon/sensor.py +++ b/homeassistant/components/moon/sensor.py @@ -4,7 +4,10 @@ from astral import moon import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity +from homeassistant.components.sensor import ( + PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA, + SensorEntity, +) from homeassistant.const import CONF_NAME from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv @@ -34,7 +37,7 @@ STATE_WAXING_GIBBOUS: "mdi:moon-waxing-gibbous", } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( +PLATFORM_SCHEMA = PARENT_PLATFORM_SCHEMA.extend( {vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string} ) @@ -46,7 +49,7 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the Moon sensor.""" - name = config.get(CONF_NAME) + name: str = config[CONF_NAME] async_add_entities([MoonSensor(name)], True) @@ -54,46 +57,32 @@ async def async_setup_platform( class MoonSensor(SensorEntity): """Representation of a Moon sensor.""" - def __init__(self, name): + _attr_device_class = "moon__phase" + + def __init__(self, name: str) -> None: """Initialize the moon sensor.""" - self._name = name - self._state = None - - @property - def name(self): - """Return the name of the entity.""" - return self._name - - @property - def device_class(self): - """Return the device class of the entity.""" - return "moon__phase" - - @property - def native_value(self): - """Return the state of the device.""" - if self._state < 0.5 or self._state > 27.5: - return STATE_NEW_MOON - if self._state < 6.5: - return STATE_WAXING_CRESCENT - if self._state < 7.5: - return STATE_FIRST_QUARTER - if self._state < 13.5: - return STATE_WAXING_GIBBOUS - if self._state < 14.5: - return STATE_FULL_MOON - if self._state < 20.5: - return STATE_WANING_GIBBOUS - if self._state < 21.5: - return STATE_LAST_QUARTER - return STATE_WANING_CRESCENT - - @property - def icon(self): - """Icon to use in the frontend, if any.""" - return MOON_ICONS.get(self.state) + self._attr_name = name async def async_update(self): """Get the time and updates the states.""" today = dt_util.as_local(dt_util.utcnow()).date() - self._state = moon.phase(today) + state = moon.phase(today) + + if state < 0.5 or state > 27.5: + self._attr_native_value = STATE_NEW_MOON + elif state < 6.5: + self._attr_native_value = STATE_WAXING_CRESCENT + elif state < 7.5: + self._attr_native_value = STATE_FIRST_QUARTER + elif state < 13.5: + self._attr_native_value = STATE_WAXING_GIBBOUS + elif state < 14.5: + self._attr_native_value = STATE_FULL_MOON + elif state < 20.5: + self._attr_native_value = STATE_WANING_GIBBOUS + elif state < 21.5: + self._attr_native_value = STATE_LAST_QUARTER + else: + self._attr_native_value = STATE_WANING_CRESCENT + + self._attr_icon = MOON_ICONS.get(self._attr_native_value) diff --git a/tests/components/moon/test_sensor.py b/tests/components/moon/test_sensor.py index 8a0269ba9fecd4..066620b1051bb9 100644 --- a/tests/components/moon/test_sensor.py +++ b/tests/components/moon/test_sensor.py @@ -1,56 +1,66 @@ """The test for the moon sensor platform.""" -from datetime import datetime +from __future__ import annotations + from unittest.mock import patch +import pytest + from homeassistant.components.homeassistant import ( DOMAIN as HA_DOMAIN, SERVICE_UPDATE_ENTITY, ) +from homeassistant.components.moon.sensor import ( + MOON_ICONS, + STATE_FIRST_QUARTER, + STATE_FULL_MOON, + STATE_LAST_QUARTER, + STATE_NEW_MOON, + STATE_WANING_CRESCENT, + STATE_WANING_GIBBOUS, + STATE_WAXING_CRESCENT, + STATE_WAXING_GIBBOUS, +) from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component -import homeassistant.util.dt as dt_util -DAY1 = datetime(2017, 1, 1, 1, tzinfo=dt_util.UTC) -DAY2 = datetime(2017, 1, 18, 1, tzinfo=dt_util.UTC) - -async def test_moon_day1(hass): - """Test the Moon sensor.""" - config = {"sensor": {"platform": "moon", "name": "moon_day1"}} - - await async_setup_component(hass, HA_DOMAIN, {}) - assert await async_setup_component(hass, "sensor", config) - await hass.async_block_till_done() - - assert hass.states.get("sensor.moon_day1") - - with patch( - "homeassistant.components.moon.sensor.dt_util.utcnow", return_value=DAY1 - ): - await async_update_entity(hass, "sensor.moon_day1") - - assert hass.states.get("sensor.moon_day1").state == "waxing_crescent" - - -async def test_moon_day2(hass): +@pytest.mark.parametrize( + "moon_value,native_value,icon", + [ + (0, STATE_NEW_MOON, MOON_ICONS[STATE_NEW_MOON]), + (5, STATE_WAXING_CRESCENT, MOON_ICONS[STATE_WAXING_CRESCENT]), + (7, STATE_FIRST_QUARTER, MOON_ICONS[STATE_FIRST_QUARTER]), + (12, STATE_WAXING_GIBBOUS, MOON_ICONS[STATE_WAXING_GIBBOUS]), + (14.3, STATE_FULL_MOON, MOON_ICONS[STATE_FULL_MOON]), + (20.1, STATE_WANING_GIBBOUS, MOON_ICONS[STATE_WANING_GIBBOUS]), + (20.8, STATE_LAST_QUARTER, MOON_ICONS[STATE_LAST_QUARTER]), + (23, STATE_WANING_CRESCENT, MOON_ICONS[STATE_WANING_CRESCENT]), + ], +) +async def test_moon_day( + hass: HomeAssistant, moon_value: float, native_value: str, icon: str +) -> None: """Test the Moon sensor.""" - config = {"sensor": {"platform": "moon", "name": "moon_day2"}} + config = {"sensor": {"platform": "moon"}} await async_setup_component(hass, HA_DOMAIN, {}) assert await async_setup_component(hass, "sensor", config) await hass.async_block_till_done() - assert hass.states.get("sensor.moon_day2") + assert hass.states.get("sensor.moon") with patch( - "homeassistant.components.moon.sensor.dt_util.utcnow", return_value=DAY2 + "homeassistant.components.moon.sensor.moon.phase", return_value=moon_value ): - await async_update_entity(hass, "sensor.moon_day2") + await async_update_entity(hass, "sensor.moon") - assert hass.states.get("sensor.moon_day2").state == "waning_gibbous" + state = hass.states.get("sensor.moon") + assert state.state == native_value + assert state.attributes["icon"] == icon -async def async_update_entity(hass, entity_id): +async def async_update_entity(hass: HomeAssistant, entity_id: str) -> None: """Run an update action for an entity.""" await hass.services.async_call( HA_DOMAIN, From 2a2f245ae86533362b34c7a4be13a4167164dfca Mon Sep 17 00:00:00 2001 From: G Johansson Date: Tue, 22 Feb 2022 15:13:22 +0100 Subject: [PATCH 0943/1098] Add mac address as connection for Sensibo devices (#67035) --- homeassistant/components/sensibo/climate.py | 2 ++ homeassistant/components/sensibo/coordinator.py | 2 ++ homeassistant/components/sensibo/number.py | 2 ++ 3 files changed, 6 insertions(+) diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index 10927fc3a06c70..f829fd9ed3959b 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -35,6 +35,7 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv, entity_platform +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -146,6 +147,7 @@ def __init__( self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, coordinator.data[device_id]["id"])}, name=coordinator.data[device_id]["name"], + connections={(CONNECTION_NETWORK_MAC, coordinator.data[device_id]["mac"])}, manufacturer="Sensibo", configuration_url="https://home.sensibo.com/", model=coordinator.data[device_id]["model"], diff --git a/homeassistant/components/sensibo/coordinator.py b/homeassistant/components/sensibo/coordinator.py index bf2c1aca17f320..ef0475640b5aff 100644 --- a/homeassistant/components/sensibo/coordinator.py +++ b/homeassistant/components/sensibo/coordinator.py @@ -47,6 +47,7 @@ async def _async_update_data(self) -> dict[str, dict[str, Any]]: device_data: dict[str, dict[str, Any]] = {} for dev in devices: unique_id = dev["id"] + mac = dev["macAddress"] name = dev["room"]["name"] temperature = dev["measurements"].get("temperature", 0.0) humidity = dev["measurements"].get("humidity", 0) @@ -96,6 +97,7 @@ async def _async_update_data(self) -> dict[str, dict[str, Any]]: device_data[unique_id] = { "id": unique_id, + "mac": mac, "name": name, "ac_states": ac_states, "temp": temperature, diff --git a/homeassistant/components/sensibo/number.py b/homeassistant/components/sensibo/number.py index eff953592ac426..9e531249bf7e73 100644 --- a/homeassistant/components/sensibo/number.py +++ b/homeassistant/components/sensibo/number.py @@ -9,6 +9,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.entity import DeviceInfo, EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -96,6 +97,7 @@ def __init__( self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, coordinator.data[device_id]["id"])}, name=coordinator.data[device_id]["name"], + connections={(CONNECTION_NETWORK_MAC, coordinator.data[device_id]["mac"])}, manufacturer="Sensibo", configuration_url="https://home.sensibo.com/", model=coordinator.data[device_id]["model"], From d96c2df6a87f2a253b77a8a7647d722816bf2021 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 22 Feb 2022 16:25:46 +0100 Subject: [PATCH 0944/1098] Bump pyicloud to 1.0.0 (#67037) --- homeassistant/components/icloud/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- script/pip_check | 2 +- ...{disabled_test_config_flow.py => test_config_flow.py} | 9 +-------- 5 files changed, 5 insertions(+), 12 deletions(-) rename tests/components/icloud/{disabled_test_config_flow.py => test_config_flow.py} (98%) diff --git a/homeassistant/components/icloud/manifest.json b/homeassistant/components/icloud/manifest.json index 4b1d89e59b3cf2..168eafe70473b8 100644 --- a/homeassistant/components/icloud/manifest.json +++ b/homeassistant/components/icloud/manifest.json @@ -3,7 +3,7 @@ "name": "Apple iCloud", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/icloud", - "requirements": ["pyicloud==0.10.2"], + "requirements": ["pyicloud==1.0.0"], "codeowners": ["@Quentame", "@nzapponi"], "iot_class": "cloud_polling", "loggers": ["keyrings.alt", "pyicloud"] diff --git a/requirements_all.txt b/requirements_all.txt index 2adac10bc6cf36..935694fe56c2c3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1583,7 +1583,7 @@ pyhomeworks==0.0.6 pyialarm==1.9.0 # homeassistant.components.icloud -pyicloud==0.10.2 +pyicloud==1.0.0 # homeassistant.components.insteon pyinsteon==1.0.16 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index aa29cdb4f0c91d..3b54aff60a1b3c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -997,7 +997,7 @@ pyhomematic==0.1.77 pyialarm==1.9.0 # homeassistant.components.icloud -pyicloud==0.10.2 +pyicloud==1.0.0 # homeassistant.components.insteon pyinsteon==1.0.16 diff --git a/script/pip_check b/script/pip_check index c4ced71605a342..fa217e8986631a 100755 --- a/script/pip_check +++ b/script/pip_check @@ -3,7 +3,7 @@ PIP_CACHE=$1 # Number of existing dependency conflicts # Update if a PR resolve one! -DEPENDENCY_CONFLICTS=8 +DEPENDENCY_CONFLICTS=6 PIP_CHECK=$(pip check --cache-dir=$PIP_CACHE) LINE_COUNT=$(echo "$PIP_CHECK" | wc -l) diff --git a/tests/components/icloud/disabled_test_config_flow.py b/tests/components/icloud/test_config_flow.py similarity index 98% rename from tests/components/icloud/disabled_test_config_flow.py rename to tests/components/icloud/test_config_flow.py index 1f7e411003ad9f..59c5ebf24a9414 100644 --- a/tests/components/icloud/disabled_test_config_flow.py +++ b/tests/components/icloud/test_config_flow.py @@ -1,11 +1,4 @@ -"""Tests for the iCloud config flow. - -This integration is temporary disabled, as the library is incompatible -with the Python versions we currently support. - -This file has been renamed (instead of skipped), simply because its easier -to prevent library imports from happening that way. -""" +"""Tests for the iCloud config flow.""" from unittest.mock import MagicMock, Mock, patch from pyicloud.exceptions import PyiCloudFailedLoginException From dbb8806b31ebc218f8d5c5794004a361835c4f5e Mon Sep 17 00:00:00 2001 From: Kevin Stillhammer Date: Tue, 22 Feb 2022 18:06:23 +0100 Subject: [PATCH 0945/1098] Use length_util conversion (#67049) --- .../components/waze_travel_time/sensor.py | 4 +- .../waze_travel_time/test_sensor.py | 40 +++++++++++++++++-- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/waze_travel_time/sensor.py b/homeassistant/components/waze_travel_time/sensor.py index 62e103f9c1db5c..3471099b588d8a 100644 --- a/homeassistant/components/waze_travel_time/sensor.py +++ b/homeassistant/components/waze_travel_time/sensor.py @@ -14,6 +14,7 @@ CONF_REGION, CONF_UNIT_SYSTEM_IMPERIAL, EVENT_HOMEASSISTANT_STARTED, + LENGTH_KILOMETERS, TIME_MINUTES, ) from homeassistant.core import CoreState, HomeAssistant @@ -21,6 +22,7 @@ from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.location import find_coordinates +from homeassistant.util.unit_system import IMPERIAL_SYSTEM from .const import ( CONF_AVOID_FERRIES, @@ -237,7 +239,7 @@ def update(self): if units == CONF_UNIT_SYSTEM_IMPERIAL: # Convert to miles. - self.distance = distance / 1.609 + self.distance = IMPERIAL_SYSTEM.length(distance, LENGTH_KILOMETERS) else: self.distance = distance diff --git a/tests/components/waze_travel_time/test_sensor.py b/tests/components/waze_travel_time/test_sensor.py index 0409b037394165..2b28190e43075f 100644 --- a/tests/components/waze_travel_time/test_sensor.py +++ b/tests/components/waze_travel_time/test_sensor.py @@ -3,7 +3,16 @@ from WazeRouteCalculator import WRCError import pytest -from homeassistant.components.waze_travel_time.const import DOMAIN +from homeassistant.components.waze_travel_time.const import ( + CONF_AVOID_FERRIES, + CONF_AVOID_SUBSCRIPTION_ROADS, + CONF_AVOID_TOLL_ROADS, + CONF_REALTIME, + CONF_UNITS, + CONF_VEHICLE_TYPE, + DOMAIN, +) +from homeassistant.const import CONF_UNIT_SYSTEM_IMPERIAL from .const import MOCK_CONFIG @@ -11,11 +20,12 @@ @pytest.fixture(name="mock_config") -async def mock_config_fixture(hass, data): +async def mock_config_fixture(hass, data, options): """Mock a Waze Travel Time config entry.""" config_entry = MockConfigEntry( domain=DOMAIN, data=data, + options=options, entry_id="test", ) config_entry.add_to_hass(hass) @@ -40,8 +50,8 @@ def mock_update_keyerror_fixture(mock_wrc): @pytest.mark.parametrize( - "data", - [MOCK_CONFIG], + "data,options", + [(MOCK_CONFIG, {})], ) @pytest.mark.usefixtures("mock_update", "mock_config") async def test_sensor(hass): @@ -68,6 +78,28 @@ async def test_sensor(hass): assert hass.states.get("sensor.waze_travel_time").attributes["icon"] == "mdi:car" +@pytest.mark.parametrize( + "data,options", + [ + ( + MOCK_CONFIG, + { + CONF_UNITS: CONF_UNIT_SYSTEM_IMPERIAL, + CONF_REALTIME: True, + CONF_VEHICLE_TYPE: "car", + CONF_AVOID_TOLL_ROADS: True, + CONF_AVOID_SUBSCRIPTION_ROADS: True, + CONF_AVOID_FERRIES: True, + }, + ) + ], +) +@pytest.mark.usefixtures("mock_update", "mock_config") +async def test_imperial(hass): + """Test that the imperial option works.""" + assert hass.states.get("sensor.waze_travel_time").attributes["distance"] == 186.4113 + + @pytest.mark.usefixtures("mock_update_wrcerror") async def test_sensor_failed_wrcerror(hass, caplog): """Test that sensor update fails with log message.""" From c14912471d9570e16cbcafcb5a643c589fe7d384 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Tue, 22 Feb 2022 18:14:48 +0100 Subject: [PATCH 0946/1098] Bump pyuptimerobot to 22.2.0 (#67041) --- homeassistant/components/uptimerobot/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/uptimerobot/manifest.json b/homeassistant/components/uptimerobot/manifest.json index f52f751fc01a89..de6399c2e9f1af 100644 --- a/homeassistant/components/uptimerobot/manifest.json +++ b/homeassistant/components/uptimerobot/manifest.json @@ -3,7 +3,7 @@ "name": "UptimeRobot", "documentation": "https://www.home-assistant.io/integrations/uptimerobot", "requirements": [ - "pyuptimerobot==21.11.0" + "pyuptimerobot==22.2.0" ], "codeowners": [ "@ludeeus", "@chemelli74" diff --git a/requirements_all.txt b/requirements_all.txt index 935694fe56c2c3..5275123062587b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2025,7 +2025,7 @@ pyudev==0.22.0 pyunifiprotect==3.2.0 # homeassistant.components.uptimerobot -pyuptimerobot==21.11.0 +pyuptimerobot==22.2.0 # homeassistant.components.keyboard # pyuserinput==0.1.11 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3b54aff60a1b3c..b68b3b2b3b954d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1265,7 +1265,7 @@ pyudev==0.22.0 pyunifiprotect==3.2.0 # homeassistant.components.uptimerobot -pyuptimerobot==21.11.0 +pyuptimerobot==22.2.0 # homeassistant.components.vera pyvera==0.3.13 From f30681dae7efffd8980b3ee3ae7f355c603b842c Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Tue, 22 Feb 2022 11:33:10 -0600 Subject: [PATCH 0947/1098] Use aiopyarr for sonarr (#65349) --- homeassistant/components/sonarr/__init__.py | 57 +++- .../components/sonarr/config_flow.py | 55 ++-- homeassistant/components/sonarr/const.py | 7 +- homeassistant/components/sonarr/entity.py | 18 +- homeassistant/components/sonarr/manifest.json | 4 +- homeassistant/components/sonarr/sensor.py | 107 ++++--- homeassistant/components/sonarr/strings.json | 7 +- requirements_all.txt | 6 +- requirements_test_all.txt | 6 +- tests/components/sonarr/__init__.py | 8 +- tests/components/sonarr/conftest.py | 109 ++++---- tests/components/sonarr/fixtures/app.json | 28 -- tests/components/sonarr/fixtures/command.json | 3 +- tests/components/sonarr/fixtures/queue.json | 261 +++++++++--------- tests/components/sonarr/fixtures/series.json | 13 +- .../sonarr/fixtures/system-status.json | 29 ++ .../sonarr/fixtures/wanted-missing.json | 2 +- tests/components/sonarr/test_config_flow.py | 16 +- tests/components/sonarr/test_init.py | 49 +++- tests/components/sonarr/test_sensor.py | 22 +- 20 files changed, 463 insertions(+), 344 deletions(-) delete mode 100644 tests/components/sonarr/fixtures/app.json create mode 100644 tests/components/sonarr/fixtures/system-status.json diff --git a/homeassistant/components/sonarr/__init__.py b/homeassistant/components/sonarr/__init__.py index b574e68ae2fe0c..9934cc8f481dae 100644 --- a/homeassistant/components/sonarr/__init__.py +++ b/homeassistant/components/sonarr/__init__.py @@ -2,8 +2,11 @@ from __future__ import annotations from datetime import timedelta +import logging -from sonarr import Sonarr, SonarrAccessRestricted, SonarrError +from aiopyarr import ArrAuthenticationException, ArrException +from aiopyarr.models.host_configuration import PyArrHostConfiguration +from aiopyarr.sonarr_client import SonarrClient from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -11,6 +14,7 @@ CONF_HOST, CONF_PORT, CONF_SSL, + CONF_URL, CONF_VERIFY_SSL, Platform, ) @@ -22,7 +26,9 @@ CONF_BASE_PATH, CONF_UPCOMING_DAYS, CONF_WANTED_MAX_ITEMS, + DATA_HOST_CONFIG, DATA_SONARR, + DATA_SYSTEM_STATUS, DEFAULT_UPCOMING_DAYS, DEFAULT_WANTED_MAX_ITEMS, DOMAIN, @@ -30,6 +36,7 @@ PLATFORMS = [Platform.SENSOR] SCAN_INTERVAL = timedelta(seconds=30) +_LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -45,30 +52,33 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: } hass.config_entries.async_update_entry(entry, options=options) - sonarr = Sonarr( - host=entry.data[CONF_HOST], - port=entry.data[CONF_PORT], - api_key=entry.data[CONF_API_KEY], - base_path=entry.data[CONF_BASE_PATH], - session=async_get_clientsession(hass), - tls=entry.data[CONF_SSL], + host_configuration = PyArrHostConfiguration( + api_token=entry.data[CONF_API_KEY], + url=entry.data[CONF_URL], verify_ssl=entry.data[CONF_VERIFY_SSL], ) + sonarr = SonarrClient( + host_configuration=host_configuration, + session=async_get_clientsession(hass), + ) + try: - await sonarr.update() - except SonarrAccessRestricted as err: + system_status = await sonarr.async_get_system_status() + except ArrAuthenticationException as err: raise ConfigEntryAuthFailed( "API Key is no longer valid. Please reauthenticate" ) from err - except SonarrError as err: + except ArrException as err: raise ConfigEntryNotReady from err entry.async_on_unload(entry.add_update_listener(_async_update_listener)) hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = { + DATA_HOST_CONFIG: host_configuration, DATA_SONARR: sonarr, + DATA_SYSTEM_STATUS: system_status, } hass.config_entries.async_setup_platforms(entry, PLATFORMS) @@ -76,6 +86,31 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return True +async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Migrate old entry.""" + _LOGGER.debug("Migrating from version %s", entry.version) + + if entry.version == 1: + new_proto = "https" if entry.data[CONF_SSL] else "http" + new_host_port = f"{entry.data[CONF_HOST]}:{entry.data[CONF_PORT]}" + + new_path = "" + + if entry.data[CONF_BASE_PATH].rstrip("/") not in ("", "/", "/api"): + new_path = entry.data[CONF_BASE_PATH].rstrip("/") + + data = { + **entry.data, + CONF_URL: f"{new_proto}://{new_host_port}{new_path}", + } + hass.config_entries.async_update_entry(entry, data=data) + entry.version = 2 + + _LOGGER.info("Migration to version %s successful", entry.version) + + return True + + async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/sonarr/config_flow.py b/homeassistant/components/sonarr/config_flow.py index f226d1883a5ffd..c9ef2d3ecc8c83 100644 --- a/homeassistant/components/sonarr/config_flow.py +++ b/homeassistant/components/sonarr/config_flow.py @@ -4,28 +4,21 @@ import logging from typing import Any -from sonarr import Sonarr, SonarrAccessRestricted, SonarrError +from aiopyarr import ArrAuthenticationException, ArrException +from aiopyarr.models.host_configuration import PyArrHostConfiguration +from aiopyarr.sonarr_client import SonarrClient import voluptuous as vol +import yarl from homeassistant.config_entries import ConfigFlow, OptionsFlow -from homeassistant.const import ( - CONF_API_KEY, - CONF_HOST, - CONF_PORT, - CONF_SSL, - CONF_VERIFY_SSL, -) +from homeassistant.const import CONF_API_KEY, CONF_URL, CONF_VERIFY_SSL from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( - CONF_BASE_PATH, CONF_UPCOMING_DAYS, CONF_WANTED_MAX_ITEMS, - DEFAULT_BASE_PATH, - DEFAULT_PORT, - DEFAULT_SSL, DEFAULT_UPCOMING_DAYS, DEFAULT_VERIFY_SSL, DEFAULT_WANTED_MAX_ITEMS, @@ -40,25 +33,24 @@ async def validate_input(hass: HomeAssistant, data: dict) -> None: Data has the keys from DATA_SCHEMA with values provided by the user. """ - session = async_get_clientsession(hass) - - sonarr = Sonarr( - host=data[CONF_HOST], - port=data[CONF_PORT], - api_key=data[CONF_API_KEY], - base_path=data[CONF_BASE_PATH], - tls=data[CONF_SSL], + host_configuration = PyArrHostConfiguration( + api_token=data[CONF_API_KEY], + url=data[CONF_URL], verify_ssl=data[CONF_VERIFY_SSL], - session=session, ) - await sonarr.update() + sonarr = SonarrClient( + host_configuration=host_configuration, + session=async_get_clientsession(hass), + ) + + await sonarr.async_get_system_status() class SonarrConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Sonarr.""" - VERSION = 1 + VERSION = 2 def __init__(self): """Initialize the flow.""" @@ -83,7 +75,7 @@ async def async_step_reauth_confirm( if user_input is None: return self.async_show_form( step_id="reauth_confirm", - description_placeholders={"host": self.entry.data[CONF_HOST]}, + description_placeholders={"url": self.entry.data[CONF_URL]}, data_schema=vol.Schema({}), errors={}, ) @@ -105,9 +97,9 @@ async def async_step_user( try: await validate_input(self.hass, user_input) - except SonarrAccessRestricted: + except ArrAuthenticationException: errors = {"base": "invalid_auth"} - except SonarrError: + except ArrException: errors = {"base": "cannot_connect"} except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") @@ -116,8 +108,10 @@ async def async_step_user( if self.entry: return await self._async_reauth_update_entry(user_input) + parsed = yarl.URL(user_input[CONF_URL]) + return self.async_create_entry( - title=user_input[CONF_HOST], data=user_input + title=parsed.host or "Sonarr", data=user_input ) data_schema = self._get_user_data_schema() @@ -139,12 +133,9 @@ def _get_user_data_schema(self) -> dict[str, Any]: if self.entry: return {vol.Required(CONF_API_KEY): str} - data_schema = { - vol.Required(CONF_HOST): str, + data_schema: dict[str, Any] = { + vol.Required(CONF_URL): str, vol.Required(CONF_API_KEY): str, - vol.Optional(CONF_BASE_PATH, default=DEFAULT_BASE_PATH): str, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): int, - vol.Optional(CONF_SSL, default=DEFAULT_SSL): bool, } if self.show_advanced_options: diff --git a/homeassistant/components/sonarr/const.py b/homeassistant/components/sonarr/const.py index be0fa00d597dd2..58f5c4657168a6 100644 --- a/homeassistant/components/sonarr/const.py +++ b/homeassistant/components/sonarr/const.py @@ -7,17 +7,14 @@ CONF_INCLUDED = "include_paths" CONF_UNIT = "unit" CONF_UPCOMING_DAYS = "upcoming_days" -CONF_URLBASE = "urlbase" CONF_WANTED_MAX_ITEMS = "wanted_max_items" # Data +DATA_HOST_CONFIG = "host_config" DATA_SONARR = "sonarr" +DATA_SYSTEM_STATUS = "system_status" # Defaults -DEFAULT_BASE_PATH = "/api" -DEFAULT_HOST = "localhost" -DEFAULT_PORT = 8989 -DEFAULT_SSL = False DEFAULT_UPCOMING_DAYS = 1 DEFAULT_VERIFY_SSL = False DEFAULT_WANTED_MAX_ITEMS = 50 diff --git a/homeassistant/components/sonarr/entity.py b/homeassistant/components/sonarr/entity.py index 1d0cb2ce6f3bf1..41f6786503d17f 100644 --- a/homeassistant/components/sonarr/entity.py +++ b/homeassistant/components/sonarr/entity.py @@ -1,7 +1,9 @@ """Base Entity for Sonarr.""" from __future__ import annotations -from sonarr import Sonarr +from aiopyarr import SystemStatus +from aiopyarr.models.host_configuration import PyArrHostConfiguration +from aiopyarr.sonarr_client import SonarrClient from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo, Entity @@ -15,7 +17,9 @@ class SonarrEntity(Entity): def __init__( self, *, - sonarr: Sonarr, + sonarr: SonarrClient, + host_config: PyArrHostConfiguration, + system_status: SystemStatus, entry_id: str, device_id: str, ) -> None: @@ -23,6 +27,8 @@ def __init__( self._entry_id = entry_id self._device_id = device_id self.sonarr = sonarr + self.host_config = host_config + self.system_status = system_status @property def device_info(self) -> DeviceInfo | None: @@ -30,15 +36,11 @@ def device_info(self) -> DeviceInfo | None: if self._device_id is None: return None - configuration_url = "https://" if self.sonarr.tls else "http://" - configuration_url += f"{self.sonarr.host}:{self.sonarr.port}" - configuration_url += self.sonarr.base_path.replace("/api", "") - return DeviceInfo( identifiers={(DOMAIN, self._device_id)}, name="Activity Sensor", manufacturer="Sonarr", - sw_version=self.sonarr.app.info.version, + sw_version=self.system_status.version, entry_type=DeviceEntryType.SERVICE, - configuration_url=configuration_url, + configuration_url=self.host_config.base_url, ) diff --git a/homeassistant/components/sonarr/manifest.json b/homeassistant/components/sonarr/manifest.json index 4b1555fa3deb72..9c43bfed2822fa 100644 --- a/homeassistant/components/sonarr/manifest.json +++ b/homeassistant/components/sonarr/manifest.json @@ -3,9 +3,9 @@ "name": "Sonarr", "documentation": "https://www.home-assistant.io/integrations/sonarr", "codeowners": ["@ctalkington"], - "requirements": ["sonarr==0.3.0"], + "requirements": ["aiopyarr==22.2.1"], "config_flow": true, "quality_scale": "silver", "iot_class": "local_polling", - "loggers": ["sonarr"] + "loggers": ["aiopyarr"] } diff --git a/homeassistant/components/sonarr/sensor.py b/homeassistant/components/sonarr/sensor.py index 01046ded7c2ad6..c182bb2bbebde2 100644 --- a/homeassistant/components/sonarr/sensor.py +++ b/homeassistant/components/sonarr/sensor.py @@ -7,7 +7,9 @@ import logging from typing import Any, TypeVar -from sonarr import Sonarr, SonarrConnectionError, SonarrError +from aiopyarr import ArrConnectionException, ArrException, SystemStatus +from aiopyarr.models.host_configuration import PyArrHostConfiguration +from aiopyarr.sonarr_client import SonarrClient from typing_extensions import Concatenate, ParamSpec from homeassistant.components.sensor import SensorEntity, SensorEntityDescription @@ -18,7 +20,14 @@ from homeassistant.helpers.typing import StateType import homeassistant.util.dt as dt_util -from .const import CONF_UPCOMING_DAYS, CONF_WANTED_MAX_ITEMS, DATA_SONARR, DOMAIN +from .const import ( + CONF_UPCOMING_DAYS, + CONF_WANTED_MAX_ITEMS, + DATA_HOST_CONFIG, + DATA_SONARR, + DATA_SYSTEM_STATUS, + DOMAIN, +) from .entity import SonarrEntity _LOGGER = logging.getLogger(__name__) @@ -77,11 +86,22 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up Sonarr sensors based on a config entry.""" - sonarr: Sonarr = hass.data[DOMAIN][entry.entry_id][DATA_SONARR] + sonarr: SonarrClient = hass.data[DOMAIN][entry.entry_id][DATA_SONARR] + host_config: PyArrHostConfiguration = hass.data[DOMAIN][entry.entry_id][ + DATA_HOST_CONFIG + ] + system_status: SystemStatus = hass.data[DOMAIN][entry.entry_id][DATA_SYSTEM_STATUS] options: dict[str, Any] = dict(entry.options) entities = [ - SonarrSensor(sonarr, entry.entry_id, description, options) + SonarrSensor( + sonarr, + host_config, + system_status, + entry.entry_id, + description, + options, + ) for description in SENSOR_TYPES ] @@ -102,12 +122,12 @@ async def wrapper(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> None: try: await func(self, *args, **kwargs) self.last_update_success = True - except SonarrConnectionError as error: - if self.available: + except ArrConnectionException as error: + if self.last_update_success: _LOGGER.error("Error communicating with API: %s", error) self.last_update_success = False - except SonarrError as error: - if self.available: + except ArrException as error: + if self.last_update_success: _LOGGER.error("Invalid response from API: %s", error) self.last_update_success = False @@ -124,7 +144,9 @@ class SonarrSensor(SonarrEntity, SensorEntity): def __init__( self, - sonarr: Sonarr, + sonarr: SonarrClient, + host_config: PyArrHostConfiguration, + system_status: SystemStatus, entry_id: str, description: SensorEntityDescription, options: dict[str, Any], @@ -140,6 +162,8 @@ def __init__( super().__init__( sonarr=sonarr, + host_config=host_config, + system_status=system_status, entry_id=entry_id, device_id=entry_id, ) @@ -155,23 +179,30 @@ async def async_update(self) -> None: key = self.entity_description.key if key == "diskspace": - await self.sonarr.update() + self.data[key] = await self.sonarr.async_get_diskspace() elif key == "commands": - self.data[key] = await self.sonarr.commands() + self.data[key] = await self.sonarr.async_get_commands() elif key == "queue": - self.data[key] = await self.sonarr.queue() + self.data[key] = await self.sonarr.async_get_queue( + include_series=True, include_episode=True + ) elif key == "series": - self.data[key] = await self.sonarr.series() + self.data[key] = await self.sonarr.async_get_series() elif key == "upcoming": local = dt_util.start_of_local_day().replace(microsecond=0) start = dt_util.as_utc(local) end = start + timedelta(days=self.upcoming_days) - self.data[key] = await self.sonarr.calendar( - start=start.isoformat(), end=end.isoformat() + self.data[key] = await self.sonarr.async_get_calendar( + start_date=start, + end_date=end, + include_series=True, ) elif key == "wanted": - self.data[key] = await self.sonarr.wanted(page_size=self.wanted_max_items) + self.data[key] = await self.sonarr.async_get_wanted( + page_size=self.wanted_max_items, + include_series=True, + ) @property def extra_state_attributes(self) -> dict[str, str] | None: @@ -179,10 +210,10 @@ def extra_state_attributes(self) -> dict[str, str] | None: attrs = {} key = self.entity_description.key - if key == "diskspace": - for disk in self.sonarr.app.disks: - free = disk.free / 1024**3 - total = disk.total / 1024**3 + if key == "diskspace" and self.data.get(key) is not None: + for disk in self.data[key]: + free = disk.freeSpace / 1024**3 + total = disk.totalSpace / 1024**3 usage = free / total * 100 attrs[ @@ -190,23 +221,33 @@ def extra_state_attributes(self) -> dict[str, str] | None: ] = f"{free:.2f}/{total:.2f}{self.unit_of_measurement} ({usage:.2f}%)" elif key == "commands" and self.data.get(key) is not None: for command in self.data[key]: - attrs[command.name] = command.state + attrs[command.name] = command.status elif key == "queue" and self.data.get(key) is not None: - for item in self.data[key]: - remaining = 1 if item.size == 0 else item.size_remaining / item.size + for item in self.data[key].records: + remaining = 1 if item.size == 0 else item.sizeleft / item.size remaining_pct = 100 * (1 - remaining) - name = f"{item.episode.series.title} {item.episode.identifier}" + identifier = f"S{item.episode.seasonNumber:02d}E{item.episode. episodeNumber:02d}" + + name = f"{item.series.title} {identifier}" attrs[name] = f"{remaining_pct:.2f}%" elif key == "series" and self.data.get(key) is not None: for item in self.data[key]: - attrs[item.series.title] = f"{item.downloaded}/{item.episodes} Episodes" + stats = item.statistics + attrs[ + item.title + ] = f"{stats.episodeFileCount}/{stats.episodeCount} Episodes" elif key == "upcoming" and self.data.get(key) is not None: for episode in self.data[key]: - attrs[episode.series.title] = episode.identifier + identifier = f"S{episode.seasonNumber:02d}E{episode.episodeNumber:02d}" + attrs[episode.series.title] = identifier elif key == "wanted" and self.data.get(key) is not None: - for episode in self.data[key].episodes: - name = f"{episode.series.title} {episode.identifier}" - attrs[name] = episode.airdate + for item in self.data[key].records: + identifier = f"S{item.seasonNumber:02d}E{item.episodeNumber:02d}" + + name = f"{item.series.title} {identifier}" + attrs[name] = dt_util.as_local( + item.airDateUtc.replace(tzinfo=dt_util.UTC) + ).isoformat() return attrs @@ -215,8 +256,8 @@ def native_value(self) -> StateType: """Return the state of the sensor.""" key = self.entity_description.key - if key == "diskspace": - total_free = sum(disk.free for disk in self.sonarr.app.disks) + if key == "diskspace" and self.data.get(key) is not None: + total_free = sum(disk.freeSpace for disk in self.data[key]) free = total_free / 1024**3 return f"{free:.2f}" @@ -224,7 +265,7 @@ def native_value(self) -> StateType: return len(self.data[key]) if key == "queue" and self.data.get(key) is not None: - return len(self.data[key]) + return self.data[key].totalRecords if key == "series" and self.data.get(key) is not None: return len(self.data[key]) @@ -233,6 +274,6 @@ def native_value(self) -> StateType: return len(self.data[key]) if key == "wanted" and self.data.get(key) is not None: - return self.data[key].total + return self.data[key].totalRecords return None diff --git a/homeassistant/components/sonarr/strings.json b/homeassistant/components/sonarr/strings.json index 2281b6cec57a49..b8537e11442c77 100644 --- a/homeassistant/components/sonarr/strings.json +++ b/homeassistant/components/sonarr/strings.json @@ -4,17 +4,14 @@ "step": { "user": { "data": { - "host": "[%key:common::config_flow::data::host%]", + "url": "[%key:common::config_flow::data::url%]", "api_key": "[%key:common::config_flow::data::api_key%]", - "base_path": "Path to API", - "port": "[%key:common::config_flow::data::port%]", - "ssl": "[%key:common::config_flow::data::ssl%]", "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" } }, "reauth_confirm": { "title": "[%key:common::config_flow::title::reauth%]", - "description": "The Sonarr integration needs to be manually re-authenticated with the Sonarr API hosted at: {host}" + "description": "The Sonarr integration needs to be manually re-authenticated with the Sonarr API hosted at: {url}" } }, "error": { diff --git a/requirements_all.txt b/requirements_all.txt index 5275123062587b..5f12ee60725adc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -244,6 +244,9 @@ aiopvapi==1.6.19 # homeassistant.components.pvpc_hourly_pricing aiopvpc==3.0.0 +# homeassistant.components.sonarr +aiopyarr==22.2.1 + # homeassistant.components.recollect_waste aiorecollect==1.0.8 @@ -2252,9 +2255,6 @@ somecomfort==0.8.0 # homeassistant.components.somfy_mylink somfy-mylink-synergy==1.0.6 -# homeassistant.components.sonarr -sonarr==0.3.0 - # homeassistant.components.marytts speak2mary==1.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b68b3b2b3b954d..1832922baef93c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -179,6 +179,9 @@ aiopvapi==1.6.19 # homeassistant.components.pvpc_hourly_pricing aiopvpc==3.0.0 +# homeassistant.components.sonarr +aiopyarr==22.2.1 + # homeassistant.components.recollect_waste aiorecollect==1.0.8 @@ -1388,9 +1391,6 @@ somecomfort==0.8.0 # homeassistant.components.somfy_mylink somfy-mylink-synergy==1.0.6 -# homeassistant.components.sonarr -sonarr==0.3.0 - # homeassistant.components.marytts speak2mary==1.4.0 diff --git a/tests/components/sonarr/__init__.py b/tests/components/sonarr/__init__.py index cd3fb8f795a7d2..ca9fc91bd5e55d 100644 --- a/tests/components/sonarr/__init__.py +++ b/tests/components/sonarr/__init__.py @@ -1,13 +1,9 @@ """Tests for the Sonarr component.""" -from homeassistant.components.sonarr.const import CONF_BASE_PATH -from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT, CONF_SSL +from homeassistant.const import CONF_API_KEY, CONF_URL MOCK_REAUTH_INPUT = {CONF_API_KEY: "test-api-key-reauth"} MOCK_USER_INPUT = { - CONF_HOST: "192.168.1.189", - CONF_PORT: 8989, - CONF_BASE_PATH: "/api", - CONF_SSL: False, + CONF_URL: "http://192.168.1.189:8989", CONF_API_KEY: "MOCK_API_KEY", } diff --git a/tests/components/sonarr/conftest.py b/tests/components/sonarr/conftest.py index a03ae7532d43ad..da8ff75df0fc64 100644 --- a/tests/components/sonarr/conftest.py +++ b/tests/components/sonarr/conftest.py @@ -3,15 +3,16 @@ import json from unittest.mock import MagicMock, patch -import pytest -from sonarr.models import ( - Application, - CommandItem, - Episode, - QueueItem, - SeriesItem, - WantedResults, +from aiopyarr import ( + Command, + Diskspace, + SonarrCalendar, + SonarrQueue, + SonarrSeries, + SonarrWantedMissing, + SystemStatus, ) +import pytest from homeassistant.components.sonarr.const import ( CONF_BASE_PATH, @@ -33,34 +34,46 @@ from tests.common import MockConfigEntry, load_fixture -def sonarr_calendar(): +def sonarr_calendar() -> list[SonarrCalendar]: """Generate a response for the calendar method.""" results = json.loads(load_fixture("sonarr/calendar.json")) - return [Episode.from_dict(result) for result in results] + return [SonarrCalendar(result) for result in results] -def sonarr_commands(): +def sonarr_commands() -> list[Command]: """Generate a response for the commands method.""" results = json.loads(load_fixture("sonarr/command.json")) - return [CommandItem.from_dict(result) for result in results] + return [Command(result) for result in results] + +def sonarr_diskspace() -> list[Diskspace]: + """Generate a response for the diskspace method.""" + results = json.loads(load_fixture("sonarr/diskspace.json")) + return [Diskspace(result) for result in results] -def sonarr_queue(): + +def sonarr_queue() -> SonarrQueue: """Generate a response for the queue method.""" results = json.loads(load_fixture("sonarr/queue.json")) - return [QueueItem.from_dict(result) for result in results] + return SonarrQueue(results) -def sonarr_series(): +def sonarr_series() -> list[SonarrSeries]: """Generate a response for the series method.""" results = json.loads(load_fixture("sonarr/series.json")) - return [SeriesItem.from_dict(result) for result in results] + return [SonarrSeries(result) for result in results] + + +def sonarr_system_status() -> SystemStatus: + """Generate a response for the system status method.""" + result = json.loads(load_fixture("sonarr/system-status.json")) + return SystemStatus(result) -def sonarr_wanted(): +def sonarr_wanted() -> SonarrWantedMissing: """Generate a response for the wanted method.""" results = json.loads(load_fixture("sonarr/wanted-missing.json")) - return WantedResults.from_dict(results) + return SonarrWantedMissing(results) @pytest.fixture @@ -95,54 +108,38 @@ def mock_setup_entry() -> Generator[None, None, None]: @pytest.fixture -def mock_sonarr_config_flow( - request: pytest.FixtureRequest, -) -> Generator[None, MagicMock, None]: +def mock_sonarr_config_flow() -> Generator[None, MagicMock, None]: """Return a mocked Sonarr client.""" - fixture: str = "sonarr/app.json" - if hasattr(request, "param") and request.param: - fixture = request.param - - app = Application(json.loads(load_fixture(fixture))) with patch( - "homeassistant.components.sonarr.config_flow.Sonarr", autospec=True + "homeassistant.components.sonarr.config_flow.SonarrClient", autospec=True ) as sonarr_mock: client = sonarr_mock.return_value - client.host = "192.168.1.189" - client.port = 8989 - client.base_path = "/api" - client.tls = False - client.app = app - client.update.return_value = app - client.calendar.return_value = sonarr_calendar() - client.commands.return_value = sonarr_commands() - client.queue.return_value = sonarr_queue() - client.series.return_value = sonarr_series() - client.wanted.return_value = sonarr_wanted() + client.async_get_calendar.return_value = sonarr_calendar() + client.async_get_commands.return_value = sonarr_commands() + client.async_get_diskspace.return_value = sonarr_diskspace() + client.async_get_queue.return_value = sonarr_queue() + client.async_get_series.return_value = sonarr_series() + client.async_get_system_status.return_value = sonarr_system_status() + client.async_get_wanted.return_value = sonarr_wanted() + yield client @pytest.fixture -def mock_sonarr(request: pytest.FixtureRequest) -> Generator[None, MagicMock, None]: +def mock_sonarr() -> Generator[None, MagicMock, None]: """Return a mocked Sonarr client.""" - fixture: str = "sonarr/app.json" - if hasattr(request, "param") and request.param: - fixture = request.param - - app = Application(json.loads(load_fixture(fixture))) - with patch("homeassistant.components.sonarr.Sonarr", autospec=True) as sonarr_mock: + with patch( + "homeassistant.components.sonarr.SonarrClient", autospec=True + ) as sonarr_mock: client = sonarr_mock.return_value - client.host = "192.168.1.189" - client.port = 8989 - client.base_path = "/api" - client.tls = False - client.app = app - client.update.return_value = app - client.calendar.return_value = sonarr_calendar() - client.commands.return_value = sonarr_commands() - client.queue.return_value = sonarr_queue() - client.series.return_value = sonarr_series() - client.wanted.return_value = sonarr_wanted() + client.async_get_calendar.return_value = sonarr_calendar() + client.async_get_commands.return_value = sonarr_commands() + client.async_get_diskspace.return_value = sonarr_diskspace() + client.async_get_queue.return_value = sonarr_queue() + client.async_get_series.return_value = sonarr_series() + client.async_get_system_status.return_value = sonarr_system_status() + client.async_get_wanted.return_value = sonarr_wanted() + yield client diff --git a/tests/components/sonarr/fixtures/app.json b/tests/components/sonarr/fixtures/app.json deleted file mode 100644 index e9ce88b233e4ea..00000000000000 --- a/tests/components/sonarr/fixtures/app.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "info": { - "version": "2.0.0.1121", - "buildTime": "2014-02-08T20:49:36.5560392Z", - "isDebug": false, - "isProduction": true, - "isAdmin": true, - "isUserInteractive": false, - "startupPath": "C:\\ProgramData\\NzbDrone\\bin", - "appData": "C:\\ProgramData\\NzbDrone", - "osVersion": "6.2.9200.0", - "isMono": false, - "isLinux": false, - "isWindows": true, - "branch": "develop", - "authentication": false, - "startOfWeek": 0, - "urlBase": "" - }, - "diskspace": [ - { - "path": "C:\\", - "label": "", - "freeSpace": 282500067328, - "totalSpace": 499738734592 - } - ] -} diff --git a/tests/components/sonarr/fixtures/command.json b/tests/components/sonarr/fixtures/command.json index 97acc2f9f82e4a..943bed3308cddc 100644 --- a/tests/components/sonarr/fixtures/command.json +++ b/tests/components/sonarr/fixtures/command.json @@ -17,7 +17,6 @@ "queued": "2020-04-06T16:54:06.41945Z", "started": "2020-04-06T16:54:06.421322Z", "trigger": "manual", - "state": "started", "manual": true, "startedOn": "2020-04-06T16:54:06.41945Z", "stateChangeTime": "2020-04-06T16:54:06.421322Z", @@ -27,7 +26,7 @@ }, { "name": "RefreshSeries", - "state": "started", + "status": "started", "startedOn": "2020-04-06T16:57:51.406504Z", "stateChangeTime": "2020-04-06T16:57:51.417931Z", "sendUpdatesToClient": true, diff --git a/tests/components/sonarr/fixtures/queue.json b/tests/components/sonarr/fixtures/queue.json index 1a8eb0924c33e8..493353e2d88ebd 100644 --- a/tests/components/sonarr/fixtures/queue.json +++ b/tests/components/sonarr/fixtures/queue.json @@ -1,129 +1,140 @@ -[ - { - "series": { - "title": "The Andy Griffith Show", - "sortTitle": "andy griffith show", - "seasonCount": 8, - "status": "ended", - "overview": "Down-home humor and an endearing cast of characters helped make The Andy Griffith Show one of the most beloved comedies in the history of TV. The show centered around widower Andy Taylor, who divided his time between raising his young son Opie, and his job as sheriff of the sleepy North Carolina town, Mayberry. Andy and Opie live with Andy's Aunt Bee, who serves as a surrogate mother to both father and son. Andy's nervous cousin, Barney Fife, is his deputy sheriff whose incompetence is tolerated because Mayberry is virtually crime-free.", - "network": "CBS", - "airTime": "21:30", - "images": [ - { - "coverType": "fanart", - "url": "https://artworks.thetvdb.com/banners/fanart/original/77754-5.jpg" +{ + "page":1, + "pageSize":10, + "sortKey":"timeleft", + "sortDirection":"ascending", + "totalRecords":1, + "records":[ + { + "series":{ + "title":"The Andy Griffith Show", + "sortTitle":"andy griffith show", + "seasonCount":8, + "status":"ended", + "overview":"Down-home humor and an endearing cast of characters helped make The Andy Griffith Show one of the most beloved comedies in the history of TV. The show centered around widower Andy Taylor, who divided his time between raising his young son Opie, and his job as sheriff of the sleepy North Carolina town, Mayberry. Andy and Opie live with Andy's Aunt Bee, who serves as a surrogate mother to both father and son. Andy's nervous cousin, Barney Fife, is his deputy sheriff whose incompetence is tolerated because Mayberry is virtually crime-free.", + "network":"CBS", + "airTime":"21:30", + "images":[ + { + "coverType":"fanart", + "url":"https://artworks.thetvdb.com/banners/fanart/original/77754-5.jpg" + }, + { + "coverType":"banner", + "url":"https://artworks.thetvdb.com/banners/graphical/77754-g.jpg" + }, + { + "coverType":"poster", + "url":"https://artworks.thetvdb.com/banners/posters/77754-4.jpg" + } + ], + "seasons":[ + { + "seasonNumber":0, + "monitored":false + }, + { + "seasonNumber":1, + "monitored":false + }, + { + "seasonNumber":2, + "monitored":true + }, + { + "seasonNumber":3, + "monitored":false + }, + { + "seasonNumber":4, + "monitored":false + }, + { + "seasonNumber":5, + "monitored":true + }, + { + "seasonNumber":6, + "monitored":true + }, + { + "seasonNumber":7, + "monitored":true + }, + { + "seasonNumber":8, + "monitored":true + } + ], + "year":1960, + "path":"F:\\The Andy Griffith Show", + "profileId":5, + "seasonFolder":true, + "monitored":true, + "useSceneNumbering":false, + "runtime":25, + "tvdbId":77754, + "tvRageId":5574, + "tvMazeId":3853, + "firstAired":"1960-02-15T06:00:00Z", + "lastInfoSync":"2016-02-05T16:40:11.614176Z", + "seriesType":"standard", + "cleanTitle":"theandygriffithshow", + "imdbId":"", + "titleSlug":"the-andy-griffith-show", + "certification":"TV-G", + "genres":[ + "Comedy" + ], + "tags":[ + + ], + "added":"2008-02-04T13:44:24.204583Z", + "ratings":{ + "votes":547, + "value":8.6 }, - { - "coverType": "banner", - "url": "https://artworks.thetvdb.com/banners/graphical/77754-g.jpg" - }, - { - "coverType": "poster", - "url": "https://artworks.thetvdb.com/banners/posters/77754-4.jpg" - } - ], - "seasons": [ - { - "seasonNumber": 0, - "monitored": false - }, - { - "seasonNumber": 1, - "monitored": false - }, - { - "seasonNumber": 2, - "monitored": true - }, - { - "seasonNumber": 3, - "monitored": false - }, - { - "seasonNumber": 4, - "monitored": false - }, - { - "seasonNumber": 5, - "monitored": true - }, - { - "seasonNumber": 6, - "monitored": true - }, - { - "seasonNumber": 7, - "monitored": true + "qualityProfileId":5, + "id":17 + }, + "episode":{ + "seriesId":17, + "episodeFileId":0, + "seasonNumber":1, + "episodeNumber":1, + "title":"The New Housekeeper", + "airDate":"1960-10-03", + "airDateUtc":"1960-10-03T01:00:00Z", + "overview":"Sheriff Andy Taylor and his young son Opie are in need of a new housekeeper. Andy's Aunt Bee looks like the perfect candidate and moves in, but her presence causes friction with Opie.", + "hasFile":false, + "monitored":false, + "absoluteEpisodeNumber":1, + "unverifiedSceneNumbering":false, + "id":889 + }, + "quality":{ + "quality":{ + "id":7, + "name":"SD" }, - { - "seasonNumber": 8, - "monitored": true + "revision":{ + "version":1, + "real":0 } - ], - "year": 1960, - "path": "F:\\The Andy Griffith Show", - "profileId": 5, - "seasonFolder": true, - "monitored": true, - "useSceneNumbering": false, - "runtime": 25, - "tvdbId": 77754, - "tvRageId": 5574, - "tvMazeId": 3853, - "firstAired": "1960-02-15T06:00:00Z", - "lastInfoSync": "2016-02-05T16:40:11.614176Z", - "seriesType": "standard", - "cleanTitle": "theandygriffithshow", - "imdbId": "", - "titleSlug": "the-andy-griffith-show", - "certification": "TV-G", - "genres": [ - "Comedy" - ], - "tags": [], - "added": "2008-02-04T13:44:24.204583Z", - "ratings": { - "votes": 547, - "value": 8.6 }, - "qualityProfileId": 5, - "id": 17 - }, - "episode": { - "seriesId": 17, - "episodeFileId": 0, - "seasonNumber": 1, - "episodeNumber": 1, - "title": "The New Housekeeper", - "airDate": "1960-10-03", - "airDateUtc": "1960-10-03T01:00:00Z", - "overview": "Sheriff Andy Taylor and his young son Opie are in need of a new housekeeper. Andy's Aunt Bee looks like the perfect candidate and moves in, but her presence causes friction with Opie.", - "hasFile": false, - "monitored": false, - "absoluteEpisodeNumber": 1, - "unverifiedSceneNumbering": false, - "id": 889 - }, - "quality": { - "quality": { - "id": 7, - "name": "SD" - }, - "revision": { - "version": 1, - "real": 0 - } - }, - "size": 4472186820, - "title": "The.Andy.Griffith.Show.S01E01.x264-GROUP", - "sizeleft": 0, - "timeleft": "00:00:00", - "estimatedCompletionTime": "2016-02-05T22:46:52.440104Z", - "status": "Downloading", - "trackedDownloadStatus": "Ok", - "statusMessages": [], - "downloadId": "SABnzbd_nzo_Mq2f_b", - "protocol": "usenet", - "id": 1503378561 - } -] + "size":4472186820, + "title":"The.Andy.Griffith.Show.S01E01.x264-GROUP", + "sizeleft":0, + "timeleft":"00:00:00", + "estimatedCompletionTime":"2016-02-05T22:46:52.440104Z", + "status":"Downloading", + "trackedDownloadStatus":"Ok", + "statusMessages":[ + + ], + "downloadId":"SABnzbd_nzo_Mq2f_b", + "protocol":"usenet", + "id":1503378561 + } + ] +} diff --git a/tests/components/sonarr/fixtures/series.json b/tests/components/sonarr/fixtures/series.json index ea727c14a97656..154ab7eb75e21f 100644 --- a/tests/components/sonarr/fixtures/series.json +++ b/tests/components/sonarr/fixtures/series.json @@ -3,11 +3,6 @@ "title": "The Andy Griffith Show", "alternateTitles": [], "sortTitle": "andy griffith show", - "seasonCount": 8, - "totalEpisodeCount": 253, - "episodeCount": 0, - "episodeFileCount": 0, - "sizeOnDisk": 0, "status": "ended", "overview": "Down-home humor and an endearing cast of characters helped make The Andy Griffith Show one of the most beloved comedies in the history of TV. The show centered around widower Andy Taylor, who divided his time between raising his young son Opie, and his job as sheriff of the sleepy North Carolina town, Mayberry. Andy and Opie live with Andy's Aunt Bee, who serves as a surrogate mother to both father and son. Andy's nervous cousin, Barney Fife, is his deputy sheriff whose incompetence is tolerated because Mayberry is virtually crime-free.", "network": "CBS", @@ -158,6 +153,14 @@ "value": 8.6 }, "qualityProfileId": 2, + "statistics": { + "seasonCount": 8, + "episodeFileCount": 0, + "episodeCount": 0, + "totalEpisodeCount": 253, + "sizeOnDisk": 0, + "percentOfEpisodes": 0.0 + }, "id": 105 } ] diff --git a/tests/components/sonarr/fixtures/system-status.json b/tests/components/sonarr/fixtures/system-status.json new file mode 100644 index 00000000000000..fe6198a0444554 --- /dev/null +++ b/tests/components/sonarr/fixtures/system-status.json @@ -0,0 +1,29 @@ +{ + "appName": "Sonarr", + "version": "3.0.6.1451", + "buildTime": "2022-01-23T16:51:56Z", + "isDebug": false, + "isProduction": true, + "isAdmin": false, + "isUserInteractive": false, + "startupPath": "/app/sonarr/bin", + "appData": "/config", + "osName": "ubuntu", + "osVersion": "20.04", + "isMonoRuntime": true, + "isMono": true, + "isLinux": true, + "isOsx": false, + "isWindows": false, + "mode": "console", + "branch": "develop", + "authentication": "forms", + "sqliteVersion": "3.31.1", + "urlBase": "", + "runtimeVersion": "6.12.0.122", + "runtimeName": "mono", + "startTime": "2022-02-01T22:10:11.956137Z", + "packageVersion": "3.0.6.1451-ls247", + "packageAuthor": "[linuxserver.io](https://linuxserver.io)", + "packageUpdateMechanism": "docker" +} diff --git a/tests/components/sonarr/fixtures/wanted-missing.json b/tests/components/sonarr/fixtures/wanted-missing.json index 5db7c52f469954..df6212487fb380 100644 --- a/tests/components/sonarr/fixtures/wanted-missing.json +++ b/tests/components/sonarr/fixtures/wanted-missing.json @@ -1,6 +1,6 @@ { "page": 1, - "pageSize": 10, + "pageSize": 50, "sortKey": "airDateUtc", "sortDirection": "descending", "totalRecords": 2, diff --git a/tests/components/sonarr/test_config_flow.py b/tests/components/sonarr/test_config_flow.py index 52e7b9b61ca9f4..59783995d23eb5 100644 --- a/tests/components/sonarr/test_config_flow.py +++ b/tests/components/sonarr/test_config_flow.py @@ -1,7 +1,7 @@ """Test the Sonarr config flow.""" from unittest.mock import MagicMock, patch -from sonarr import SonarrAccessRestricted, SonarrError +from aiopyarr import ArrAuthenticationException, ArrException from homeassistant.components.sonarr.const import ( CONF_UPCOMING_DAYS, @@ -11,7 +11,7 @@ DOMAIN, ) from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER -from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_SOURCE, CONF_VERIFY_SSL +from homeassistant.const import CONF_API_KEY, CONF_SOURCE, CONF_URL, CONF_VERIFY_SSL from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import ( RESULT_TYPE_ABORT, @@ -38,7 +38,7 @@ async def test_cannot_connect( hass: HomeAssistant, mock_sonarr_config_flow: MagicMock ) -> None: """Test we show user form on connection error.""" - mock_sonarr_config_flow.update.side_effect = SonarrError + mock_sonarr_config_flow.async_get_system_status.side_effect = ArrException user_input = MOCK_USER_INPUT.copy() result = await hass.config_entries.flow.async_init( @@ -56,7 +56,9 @@ async def test_invalid_auth( hass: HomeAssistant, mock_sonarr_config_flow: MagicMock ) -> None: """Test we show user form on invalid auth.""" - mock_sonarr_config_flow.update.side_effect = SonarrAccessRestricted + mock_sonarr_config_flow.async_get_system_status.side_effect = ( + ArrAuthenticationException + ) user_input = MOCK_USER_INPUT.copy() result = await hass.config_entries.flow.async_init( @@ -74,7 +76,7 @@ async def test_unknown_error( hass: HomeAssistant, mock_sonarr_config_flow: MagicMock ) -> None: """Test we show user form on unknown error.""" - mock_sonarr_config_flow.update.side_effect = Exception + mock_sonarr_config_flow.async_get_system_status.side_effect = Exception user_input = MOCK_USER_INPUT.copy() result = await hass.config_entries.flow.async_init( @@ -153,7 +155,7 @@ async def test_full_user_flow_implementation( assert result["title"] == "192.168.1.189" assert result["data"] - assert result["data"][CONF_HOST] == "192.168.1.189" + assert result["data"][CONF_URL] == "http://192.168.1.189:8989" async def test_full_user_flow_advanced_options( @@ -183,7 +185,7 @@ async def test_full_user_flow_advanced_options( assert result["title"] == "192.168.1.189" assert result["data"] - assert result["data"][CONF_HOST] == "192.168.1.189" + assert result["data"][CONF_URL] == "http://192.168.1.189:8989" assert result["data"][CONF_VERIFY_SSL] diff --git a/tests/components/sonarr/test_init.py b/tests/components/sonarr/test_init.py index 3a59f5d7cca8c0..f4a317e3de0bf8 100644 --- a/tests/components/sonarr/test_init.py +++ b/tests/components/sonarr/test_init.py @@ -1,11 +1,19 @@ """Tests for the Sonsrr integration.""" from unittest.mock import MagicMock, patch -from sonarr import SonarrAccessRestricted, SonarrError +from aiopyarr import ArrAuthenticationException, ArrException -from homeassistant.components.sonarr.const import DOMAIN +from homeassistant.components.sonarr.const import CONF_BASE_PATH, DOMAIN from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState -from homeassistant.const import CONF_SOURCE +from homeassistant.const import ( + CONF_API_KEY, + CONF_HOST, + CONF_PORT, + CONF_SOURCE, + CONF_SSL, + CONF_URL, + CONF_VERIFY_SSL, +) from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry @@ -17,7 +25,7 @@ async def test_config_entry_not_ready( mock_sonarr: MagicMock, ) -> None: """Test the configuration entry not ready.""" - mock_sonarr.update.side_effect = SonarrError + mock_sonarr.async_get_system_status.side_effect = ArrException mock_config_entry.add_to_hass(hass) await hass.config_entries.async_setup(mock_config_entry.entry_id) @@ -32,7 +40,7 @@ async def test_config_entry_reauth( mock_sonarr: MagicMock, ) -> None: """Test the configuration entry needing to be re-authenticated.""" - mock_sonarr.update.side_effect = SonarrAccessRestricted + mock_sonarr.async_get_system_status.side_effect = ArrAuthenticationException with patch.object(hass.config_entries.flow, "async_init") as mock_flow_init: mock_config_entry.add_to_hass(hass) @@ -77,3 +85,34 @@ async def test_unload_config_entry( assert mock_config_entry.state is ConfigEntryState.NOT_LOADED assert mock_config_entry.entry_id not in hass.data[DOMAIN] + + +async def test_migrate_config_entry(hass: HomeAssistant): + """Test successful migration of entry data.""" + legacy_config = { + CONF_API_KEY: "MOCK_API_KEY", + CONF_HOST: "1.2.3.4", + CONF_PORT: 8989, + CONF_SSL: False, + CONF_VERIFY_SSL: False, + CONF_BASE_PATH: "/base/", + } + entry = MockConfigEntry(domain=DOMAIN, data=legacy_config) + + assert entry.data == legacy_config + assert entry.version == 1 + assert not entry.unique_id + + await entry.async_migrate(hass) + + assert entry.data == { + CONF_API_KEY: "MOCK_API_KEY", + CONF_HOST: "1.2.3.4", + CONF_PORT: 8989, + CONF_SSL: False, + CONF_VERIFY_SSL: False, + CONF_BASE_PATH: "/base/", + CONF_URL: "http://1.2.3.4:8989/base", + } + assert entry.version == 2 + assert not entry.unique_id diff --git a/tests/components/sonarr/test_sensor.py b/tests/components/sonarr/test_sensor.py index cc15376efb1e26..c499dc0112f173 100644 --- a/tests/components/sonarr/test_sensor.py +++ b/tests/components/sonarr/test_sensor.py @@ -2,8 +2,8 @@ from datetime import timedelta from unittest.mock import MagicMock, patch +from aiopyarr import ArrException import pytest -from sonarr import SonarrError from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.sonarr.const import DOMAIN @@ -96,8 +96,11 @@ async def test_sensors( assert state assert state.attributes.get(ATTR_ICON) == "mdi:television" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "Episodes" - assert state.attributes.get("Bob's Burgers S04E11") == "2014-01-26" - assert state.attributes.get("The Andy Griffith Show S01E01") == "1960-10-03" + assert state.attributes.get("Bob's Burgers S04E11") == "2014-01-27T01:30:00+00:00" + assert ( + state.attributes.get("The Andy Griffith Show S01E01") + == "1960-10-03T01:00:00+00:00" + ) assert state.state == "2" @@ -141,44 +144,49 @@ async def test_availability( await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() + assert hass.states.get(UPCOMING_ENTITY_ID) assert hass.states.get(UPCOMING_ENTITY_ID).state == "1" # state to unavailable - mock_sonarr.calendar.side_effect = SonarrError + mock_sonarr.async_get_calendar.side_effect = ArrException future = now + timedelta(minutes=1) with patch("homeassistant.util.dt.utcnow", return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() + assert hass.states.get(UPCOMING_ENTITY_ID) assert hass.states.get(UPCOMING_ENTITY_ID).state == STATE_UNAVAILABLE # state to available - mock_sonarr.calendar.side_effect = None + mock_sonarr.async_get_calendar.side_effect = None future += timedelta(minutes=1) with patch("homeassistant.util.dt.utcnow", return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() + assert hass.states.get(UPCOMING_ENTITY_ID) assert hass.states.get(UPCOMING_ENTITY_ID).state == "1" # state to unavailable - mock_sonarr.calendar.side_effect = SonarrError + mock_sonarr.async_get_calendar.side_effect = ArrException future += timedelta(minutes=1) with patch("homeassistant.util.dt.utcnow", return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() + assert hass.states.get(UPCOMING_ENTITY_ID) assert hass.states.get(UPCOMING_ENTITY_ID).state == STATE_UNAVAILABLE # state to available - mock_sonarr.calendar.side_effect = None + mock_sonarr.async_get_calendar.side_effect = None future += timedelta(minutes=1) with patch("homeassistant.util.dt.utcnow", return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() + assert hass.states.get(UPCOMING_ENTITY_ID) assert hass.states.get(UPCOMING_ENTITY_ID).state == "1" From d25a46d68d1a4f7355f650afdc27703f73430c6d Mon Sep 17 00:00:00 2001 From: Thibaut Date: Tue, 22 Feb 2022 19:01:19 +0100 Subject: [PATCH 0948/1098] Add low speed Overkiz cover (#66750) --- homeassistant/components/overkiz/cover.py | 10 ++++-- .../overkiz/cover_entities/vertical_cover.py | 35 +++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/overkiz/cover.py b/homeassistant/components/overkiz/cover.py index a87b3b5edd67a6..4e741aa68e6de0 100644 --- a/homeassistant/components/overkiz/cover.py +++ b/homeassistant/components/overkiz/cover.py @@ -1,5 +1,5 @@ """Support for Overkiz covers - shutters etc.""" -from pyoverkiz.enums import UIClass +from pyoverkiz.enums import OverkizCommand, UIClass from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform @@ -10,7 +10,7 @@ from .const import DOMAIN from .cover_entities.awning import Awning from .cover_entities.generic_cover import OverkizGenericCover -from .cover_entities.vertical_cover import VerticalCover +from .cover_entities.vertical_cover import LowSpeedCover, VerticalCover async def async_setup_entry( @@ -31,4 +31,10 @@ async def async_setup_entry( if device.ui_class != UIClass.AWNING ] + entities += [ + LowSpeedCover(device.device_url, data.coordinator) + for device in data.platforms[Platform.COVER] + if OverkizCommand.SET_CLOSURE_AND_LINEAR_SPEED in device.definition.commands + ] + async_add_entities(entities) diff --git a/homeassistant/components/overkiz/cover_entities/vertical_cover.py b/homeassistant/components/overkiz/cover_entities/vertical_cover.py index 12354f412417b4..4ad1c401d73a4a 100644 --- a/homeassistant/components/overkiz/cover_entities/vertical_cover.py +++ b/homeassistant/components/overkiz/cover_entities/vertical_cover.py @@ -13,6 +13,7 @@ SUPPORT_STOP, CoverDeviceClass, ) +from homeassistant.components.overkiz.coordinator import OverkizDataUpdateCoordinator from .generic_cover import COMMANDS_STOP, OverkizGenericCover @@ -99,3 +100,37 @@ async def async_close_cover(self, **kwargs: Any) -> None: """Close the cover.""" if command := self.executor.select_command(*COMMANDS_CLOSE): await self.executor.async_execute_command(command) + + +class LowSpeedCover(VerticalCover): + """Representation of an Overkiz Low Speed cover.""" + + def __init__( + self, + device_url: str, + coordinator: OverkizDataUpdateCoordinator, + ) -> None: + """Initialize the device.""" + super().__init__(device_url, coordinator) + self._attr_name = f"{self._attr_name} Low Speed" + self._attr_unique_id = f"{self._attr_unique_id}_low_speed" + + async def async_set_cover_position(self, **kwargs: Any) -> None: + """Move the cover to a specific position.""" + await self.async_set_cover_position_low_speed(**kwargs) + + async def async_open_cover(self, **kwargs: Any) -> None: + """Open the cover.""" + await self.async_set_cover_position_low_speed(**{ATTR_POSITION: 100}) + + async def async_close_cover(self, **kwargs: Any) -> None: + """Close the cover.""" + await self.async_set_cover_position_low_speed(**{ATTR_POSITION: 0}) + + async def async_set_cover_position_low_speed(self, **kwargs: Any) -> None: + """Move the cover to a specific position with a low speed.""" + position = 100 - kwargs.get(ATTR_POSITION, 0) + + await self.executor.async_execute_command( + OverkizCommand.SET_CLOSURE_AND_LINEAR_SPEED, position, "lowspeed" + ) From a60c37cdb8cc9d0b9bad1dedb92b6068cd9d1244 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Tue, 22 Feb 2022 19:31:16 +0100 Subject: [PATCH 0949/1098] Expose Samsung wrapper as async (#67042) Co-authored-by: epenet --- .../components/samsungtv/__init__.py | 13 +- homeassistant/components/samsungtv/bridge.py | 137 +++++++++++------- .../components/samsungtv/config_flow.py | 16 +- .../components/samsungtv/diagnostics.py | 2 +- .../components/samsungtv/media_player.py | 68 ++++----- .../components/samsungtv/test_config_flow.py | 16 +- tests/components/samsungtv/test_init.py | 2 +- 7 files changed, 147 insertions(+), 107 deletions(-) diff --git a/homeassistant/components/samsungtv/__init__.py b/homeassistant/components/samsungtv/__init__.py index 45be07585d7a9b..508ed2f876ffc9 100644 --- a/homeassistant/components/samsungtv/__init__.py +++ b/homeassistant/components/samsungtv/__init__.py @@ -100,10 +100,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: @callback def _async_get_device_bridge( - data: dict[str, Any] + hass: HomeAssistant, data: dict[str, Any] ) -> SamsungTVLegacyBridge | SamsungTVWSBridge: """Get device bridge.""" return SamsungTVBridge.get_bridge( + hass, data[CONF_METHOD], data[CONF_HOST], data[CONF_PORT], @@ -131,9 +132,9 @@ def new_token_callback() -> None: bridge.register_new_token_callback(new_token_callback) - def stop_bridge(event: Event) -> None: + async def stop_bridge(event: Event) -> None: """Stop SamsungTV bridge connection.""" - bridge.stop() + await bridge.async_stop() entry.async_on_unload( hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_bridge) @@ -169,14 +170,14 @@ async def _async_create_bridge_with_updated_data( updated_data[CONF_PORT] = port updated_data[CONF_METHOD] = method - bridge = _async_get_device_bridge({**entry.data, **updated_data}) + bridge = _async_get_device_bridge(hass, {**entry.data, **updated_data}) mac = entry.data.get(CONF_MAC) if not mac and bridge.method == METHOD_WEBSOCKET: if info: mac = mac_from_device_info(info) else: - mac = await hass.async_add_executor_job(bridge.mac_from_device) + mac = await bridge.async_mac_from_device() if not mac: mac = await hass.async_add_executor_job( @@ -196,7 +197,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: - hass.data[DOMAIN][entry.entry_id].stop() + await hass.data[DOMAIN][entry.entry_id].async_stop() return unload_ok diff --git a/homeassistant/components/samsungtv/bridge.py b/homeassistant/components/samsungtv/bridge.py index 83df8952278b89..74daf1d34e04eb 100644 --- a/homeassistant/components/samsungtv/bridge.py +++ b/homeassistant/components/samsungtv/bridge.py @@ -54,25 +54,18 @@ async def async_get_device_info( hass: HomeAssistant, bridge: SamsungTVWSBridge | SamsungTVLegacyBridge | None, host: str, -) -> tuple[int | None, str | None, dict[str, Any] | None]: - """Fetch the port, method, and device info.""" - return await hass.async_add_executor_job(_get_device_info, bridge, host) - - -def _get_device_info( - bridge: SamsungTVWSBridge | SamsungTVLegacyBridge, host: str ) -> tuple[int | None, str | None, dict[str, Any] | None]: """Fetch the port, method, and device info.""" if bridge and bridge.port: - return bridge.port, bridge.method, bridge.device_info() + return bridge.port, bridge.method, await bridge.async_device_info() for port in WEBSOCKET_PORTS: - bridge = SamsungTVBridge.get_bridge(METHOD_WEBSOCKET, host, port) - if info := bridge.device_info(): + bridge = SamsungTVBridge.get_bridge(hass, METHOD_WEBSOCKET, host, port) + if info := await bridge.async_device_info(): return port, METHOD_WEBSOCKET, info - bridge = SamsungTVBridge.get_bridge(METHOD_LEGACY, host, LEGACY_PORT) - result = bridge.try_connect() + bridge = SamsungTVBridge.get_bridge(hass, METHOD_LEGACY, host, LEGACY_PORT) + result = await bridge.async_try_connect() if result in (RESULT_SUCCESS, RESULT_AUTH_MISSING): return LEGACY_PORT, METHOD_LEGACY, None @@ -84,15 +77,22 @@ class SamsungTVBridge(ABC): @staticmethod def get_bridge( - method: str, host: str, port: int | None = None, token: str | None = None + hass: HomeAssistant, + method: str, + host: str, + port: int | None = None, + token: str | None = None, ) -> SamsungTVLegacyBridge | SamsungTVWSBridge: """Get Bridge instance.""" if method == METHOD_LEGACY or port == LEGACY_PORT: - return SamsungTVLegacyBridge(method, host, port) - return SamsungTVWSBridge(method, host, port, token) + return SamsungTVLegacyBridge(hass, method, host, port) + return SamsungTVWSBridge(hass, method, host, port, token) - def __init__(self, method: str, host: str, port: int | None = None) -> None: + def __init__( + self, hass: HomeAssistant, method: str, host: str, port: int | None = None + ) -> None: """Initialize Bridge.""" + self.hass = hass self.port = port self.method = method self.host = host @@ -110,28 +110,29 @@ def register_new_token_callback(self, func: CALLBACK_TYPE) -> None: self._new_token_callback = func @abstractmethod - def try_connect(self) -> str | None: + async def async_try_connect(self) -> str | None: """Try to connect to the TV.""" @abstractmethod - def device_info(self) -> dict[str, Any] | None: + async def async_device_info(self) -> dict[str, Any] | None: """Try to gather infos of this TV.""" @abstractmethod - def mac_from_device(self) -> str | None: + async def async_mac_from_device(self) -> str | None: """Try to fetch the mac address of the TV.""" @abstractmethod - def get_app_list(self) -> dict[str, str] | None: + async def async_get_app_list(self) -> dict[str, str] | None: """Get installed app list.""" - def is_on(self) -> bool: + async def async_is_on(self) -> bool: """Tells if the TV is on.""" if self._remote is not None: - self.close_remote() + await self.async_close_remote() try: - return self._get_remote() is not None + remote = await self.hass.async_add_executor_job(self._get_remote) + return remote is not None except ( UnhandledResponse, AccessDenied, @@ -143,14 +144,14 @@ def is_on(self) -> bool: # Different reasons, e.g. hostname not resolveable return False - def send_key(self, key: str, key_type: str | None = None) -> None: + async def async_send_key(self, key: str, key_type: str | None = None) -> None: """Send a key to the tv and handles exceptions.""" try: # recreate connection if connection was dead retry_count = 1 for _ in range(retry_count + 1): try: - self._send_key(key, key_type) + await self._async_send_key(key, key_type) break except ( ConnectionClosed, @@ -168,19 +169,19 @@ def send_key(self, key: str, key_type: str | None = None) -> None: pass @abstractmethod - def _send_key(self, key: str, key_type: str | None = None) -> None: + async def _async_send_key(self, key: str, key_type: str | None = None) -> None: """Send the key.""" @abstractmethod - def _get_remote(self, avoid_open: bool = False) -> Remote: + def _get_remote(self, avoid_open: bool = False) -> Remote | SamsungTVWS: """Get Remote object.""" - def close_remote(self) -> None: + async def async_close_remote(self) -> None: """Close remote object.""" try: if self._remote is not None: # Close the current remote connection - self._remote.close() + await self.hass.async_add_executor_job(self._remote.close) self._remote = None except OSError: LOGGER.debug("Could not establish connection") @@ -199,9 +200,11 @@ def _notify_new_token_callback(self) -> None: class SamsungTVLegacyBridge(SamsungTVBridge): """The Bridge for Legacy TVs.""" - def __init__(self, method: str, host: str, port: int | None) -> None: + def __init__( + self, hass: HomeAssistant, method: str, host: str, port: int | None + ) -> None: """Initialize Bridge.""" - super().__init__(method, host, LEGACY_PORT) + super().__init__(hass, method, host, LEGACY_PORT) self.config = { CONF_NAME: VALUE_CONF_NAME, CONF_DESCRIPTION: VALUE_CONF_NAME, @@ -212,15 +215,19 @@ def __init__(self, method: str, host: str, port: int | None) -> None: CONF_TIMEOUT: 1, } - def mac_from_device(self) -> None: + async def async_mac_from_device(self) -> None: """Try to fetch the mac address of the TV.""" return None - def get_app_list(self) -> dict[str, str]: + async def async_get_app_list(self) -> dict[str, str]: """Get installed app list.""" return {} - def try_connect(self) -> str: + async def async_try_connect(self) -> str: + """Try to connect to the Legacy TV.""" + return await self.hass.async_add_executor_job(self._try_connect) + + def _try_connect(self) -> str: """Try to connect to the Legacy TV.""" config = { CONF_NAME: VALUE_CONF_NAME, @@ -247,7 +254,7 @@ def try_connect(self) -> str: LOGGER.debug("Failing config: %s, error: %s", config, err) return RESULT_CANNOT_CONNECT - def device_info(self) -> None: + async def async_device_info(self) -> None: """Try to gather infos of this device.""" return None @@ -269,34 +276,47 @@ def _get_remote(self, avoid_open: bool = False) -> Remote: pass return self._remote - def _send_key(self, key: str, key_type: str | None = None) -> None: + async def _async_send_key(self, key: str, key_type: str | None = None) -> None: + """Send the key using legacy protocol.""" + return await self.hass.async_add_executor_job(self._send_key, key) + + def _send_key(self, key: str) -> None: """Send the key using legacy protocol.""" if remote := self._get_remote(): remote.control(key) - def stop(self) -> None: + async def async_stop(self) -> None: """Stop Bridge.""" LOGGER.debug("Stopping SamsungTVLegacyBridge") - self.close_remote() + await self.async_close_remote() class SamsungTVWSBridge(SamsungTVBridge): """The Bridge for WebSocket TVs.""" def __init__( - self, method: str, host: str, port: int | None = None, token: str | None = None + self, + hass: HomeAssistant, + method: str, + host: str, + port: int | None = None, + token: str | None = None, ) -> None: """Initialize Bridge.""" - super().__init__(method, host, port) + super().__init__(hass, method, host, port) self.token = token self._app_list: dict[str, str] | None = None - def mac_from_device(self) -> str | None: + async def async_mac_from_device(self) -> str | None: """Try to fetch the mac address of the TV.""" - info = self.device_info() + info = await self.async_device_info() return mac_from_device_info(info) if info else None - def get_app_list(self) -> dict[str, str] | None: + async def async_get_app_list(self) -> dict[str, str] | None: + """Get installed app list.""" + return await self.hass.async_add_executor_job(self._get_app_list) + + def _get_app_list(self) -> dict[str, str] | None: """Get installed app list.""" if self._app_list is None: if remote := self._get_remote(): @@ -308,7 +328,11 @@ def get_app_list(self) -> dict[str, str] | None: return self._app_list - def try_connect(self) -> str: + async def async_try_connect(self) -> str: + """Try to connect to the Websocket TV.""" + return await self.hass.async_add_executor_job(self._try_connect) + + def _try_connect(self) -> str: """Try to connect to the Websocket TV.""" for self.port in WEBSOCKET_PORTS: config = { @@ -350,15 +374,21 @@ def try_connect(self) -> str: return RESULT_CANNOT_CONNECT - def device_info(self) -> dict[str, Any] | None: + async def async_device_info(self) -> dict[str, Any] | None: """Try to gather infos of this TV.""" if remote := self._get_remote(avoid_open=True): with contextlib.suppress(HttpApiError, RequestsTimeout): - device_info: dict[str, Any] = remote.rest_device_info() + device_info: dict[str, Any] = await self.hass.async_add_executor_job( + remote.rest_device_info + ) return device_info return None + async def _async_send_key(self, key: str, key_type: str | None = None) -> None: + """Send the key using websocket protocol.""" + return await self.hass.async_add_executor_job(self._send_key, key, key_type) + def _send_key(self, key: str, key_type: str | None = None) -> None: """Send the key using websocket protocol.""" if key == "KEY_POWEROFF": @@ -369,7 +399,7 @@ def _send_key(self, key: str, key_type: str | None = None) -> None: else: remote.send_key(key) - def _get_remote(self, avoid_open: bool = False) -> Remote: + def _get_remote(self, avoid_open: bool = False) -> SamsungTVWS: """Create or return a remote control instance.""" if self._remote is None: # We need to create a new instance to reconnect. @@ -388,11 +418,16 @@ def _get_remote(self, avoid_open: bool = False) -> Remote: self._remote.open("samsung.remote.control") # This is only happening when the auth was switched to DENY # A removed auth will lead to socket timeout because waiting for auth popup is just an open socket - except ConnectionFailure: + except ConnectionFailure as err: + LOGGER.debug("ConnectionFailure %s", err.__repr__()) self._notify_reauth_callback() - except (WebSocketException, OSError): + except (WebSocketException, OSError) as err: + LOGGER.debug("WebSocketException, OSError %s", err.__repr__()) self._remote = None else: + LOGGER.debug( + "Created SamsungTVWSBridge for %s (%s)", CONF_NAME, self.host + ) if self.token != self._remote.token: LOGGER.debug( "SamsungTVWSBridge has provided a new token %s", @@ -402,7 +437,7 @@ def _get_remote(self, avoid_open: bool = False) -> Remote: self._notify_new_token_callback() return self._remote - def stop(self) -> None: + async def async_stop(self) -> None: """Stop Bridge.""" LOGGER.debug("Stopping SamsungTVWSBridge") - self.close_remote() + await self.async_close_remote() diff --git a/homeassistant/components/samsungtv/config_flow.py b/homeassistant/components/samsungtv/config_flow.py index fe9e43848846fa..8b7e0f83a492d0 100644 --- a/homeassistant/components/samsungtv/config_flow.py +++ b/homeassistant/components/samsungtv/config_flow.py @@ -124,11 +124,11 @@ def _async_update_and_abort_for_matching_unique_id(self) -> None: updates[CONF_MAC] = self._mac self._abort_if_unique_id_configured(updates=updates) - def _try_connect(self) -> None: + async def _try_connect(self) -> None: """Try to connect and check auth.""" for method in SUPPORTED_METHODS: - self._bridge = SamsungTVBridge.get_bridge(method, self._host) - result = self._bridge.try_connect() + self._bridge = SamsungTVBridge.get_bridge(self.hass, method, self._host) + result = await self._bridge.async_try_connect() if result == RESULT_SUCCESS: return if result != RESULT_CANNOT_CONNECT: @@ -203,7 +203,7 @@ async def async_step_user( """Handle a flow initialized by the user.""" if user_input is not None: await self._async_set_name_host_from_input(user_input) - await self.hass.async_add_executor_job(self._try_connect) + await self._try_connect() assert self._bridge self._async_abort_entries_match({CONF_HOST: self._host}) if self._bridge.method != METHOD_LEGACY: @@ -309,7 +309,7 @@ async def async_step_confirm( """Handle user-confirmation of discovered node.""" if user_input is not None: - await self.hass.async_add_executor_job(self._try_connect) + await self._try_connect() assert self._bridge return self._get_entry_from_bridge() @@ -341,9 +341,11 @@ async def async_step_reauth_confirm( assert self._reauth_entry if user_input is not None: bridge = SamsungTVBridge.get_bridge( - self._reauth_entry.data[CONF_METHOD], self._reauth_entry.data[CONF_HOST] + self.hass, + self._reauth_entry.data[CONF_METHOD], + self._reauth_entry.data[CONF_HOST], ) - result = await self.hass.async_add_executor_job(bridge.try_connect) + result = await bridge.async_try_connect() if result == RESULT_SUCCESS: new_data = dict(self._reauth_entry.data) new_data[CONF_TOKEN] = bridge.token diff --git a/homeassistant/components/samsungtv/diagnostics.py b/homeassistant/components/samsungtv/diagnostics.py index 324a3d1b32fb40..007ab283cfde8d 100644 --- a/homeassistant/components/samsungtv/diagnostics.py +++ b/homeassistant/components/samsungtv/diagnostics.py @@ -23,5 +23,5 @@ async def async_get_config_entry_diagnostics( ] return { "entry": async_redact_data(entry.as_dict(), TO_REDACT), - "device_info": await hass.async_add_executor_job(bridge.device_info), + "device_info": await bridge.async_device_info(), } diff --git a/homeassistant/components/samsungtv/media_player.py b/homeassistant/components/samsungtv/media_player.py index db99726f20ca8e..cb857a96afbe67 100644 --- a/homeassistant/components/samsungtv/media_player.py +++ b/homeassistant/components/samsungtv/media_player.py @@ -153,30 +153,32 @@ def access_denied(self) -> None: ) ) - def update(self) -> None: + async def async_update(self) -> None: """Update state of device.""" if self._auth_failed or self.hass.is_stopping: return if self._power_off_in_progress(): self._attr_state = STATE_OFF else: - self._attr_state = STATE_ON if self._bridge.is_on() else STATE_OFF + self._attr_state = ( + STATE_ON if await self._bridge.async_is_on() else STATE_OFF + ) if self._attr_state == STATE_ON and self._app_list is None: self._app_list = {} # Ensure that we don't update it twice in parallel - self._update_app_list() + await self._async_update_app_list() - def _update_app_list(self) -> None: - self._app_list = self._bridge.get_app_list() + async def _async_update_app_list(self) -> None: + self._app_list = await self._bridge.async_get_app_list() if self._app_list is not None: self._attr_source_list.extend(self._app_list) - def send_key(self, key: str, key_type: str | None = None) -> None: + async def _async_send_key(self, key: str, key_type: str | None = None) -> None: """Send a key to the tv and handles exceptions.""" if self._power_off_in_progress() and key != "KEY_POWEROFF": LOGGER.info("TV is powering off, not sending command: %s", key) return - self._bridge.send_key(key, key_type) + await self._bridge.async_send_key(key, key_type) def _power_off_in_progress(self) -> bool: return ( @@ -196,57 +198,57 @@ def available(self) -> bool: or self._power_off_in_progress() ) - def turn_off(self) -> None: + async def async_turn_off(self) -> None: """Turn off media player.""" self._end_of_power_off = dt_util.utcnow() + SCAN_INTERVAL_PLUS_OFF_TIME - self.send_key("KEY_POWEROFF") + await self._async_send_key("KEY_POWEROFF") # Force closing of remote session to provide instant UI feedback - self._bridge.close_remote() + await self._bridge.async_close_remote() - def volume_up(self) -> None: + async def async_volume_up(self) -> None: """Volume up the media player.""" - self.send_key("KEY_VOLUP") + await self._async_send_key("KEY_VOLUP") - def volume_down(self) -> None: + async def async_volume_down(self) -> None: """Volume down media player.""" - self.send_key("KEY_VOLDOWN") + await self._async_send_key("KEY_VOLDOWN") - def mute_volume(self, mute: bool) -> None: + async def async_mute_volume(self, mute: bool) -> None: """Send mute command.""" - self.send_key("KEY_MUTE") + await self._async_send_key("KEY_MUTE") - def media_play_pause(self) -> None: + async def async_media_play_pause(self) -> None: """Simulate play pause media player.""" if self._playing: - self.media_pause() + await self.async_media_pause() else: - self.media_play() + await self.async_media_play() - def media_play(self) -> None: + async def async_media_play(self) -> None: """Send play command.""" self._playing = True - self.send_key("KEY_PLAY") + await self._async_send_key("KEY_PLAY") - def media_pause(self) -> None: + async def async_media_pause(self) -> None: """Send media pause command to media player.""" self._playing = False - self.send_key("KEY_PAUSE") + await self._async_send_key("KEY_PAUSE") - def media_next_track(self) -> None: + async def async_media_next_track(self) -> None: """Send next track command.""" - self.send_key("KEY_CHUP") + await self._async_send_key("KEY_CHUP") - def media_previous_track(self) -> None: + async def async_media_previous_track(self) -> None: """Send the previous track command.""" - self.send_key("KEY_CHDOWN") + await self._async_send_key("KEY_CHDOWN") async def async_play_media( self, media_type: str, media_id: str, **kwargs: Any ) -> None: """Support changing a channel.""" if media_type == MEDIA_TYPE_APP: - await self.hass.async_add_executor_job(self.send_key, media_id, "run_app") + await self._async_send_key(media_id, "run_app") return if media_type != MEDIA_TYPE_CHANNEL: @@ -261,9 +263,9 @@ async def async_play_media( return for digit in media_id: - await self.hass.async_add_executor_job(self.send_key, f"KEY_{digit}") + await self._async_send_key(f"KEY_{digit}") await asyncio.sleep(KEY_PRESS_TIMEOUT) - await self.hass.async_add_executor_job(self.send_key, "KEY_ENTER") + await self._async_send_key("KEY_ENTER") def _wake_on_lan(self) -> None: """Wake the device via wake on lan.""" @@ -279,14 +281,14 @@ async def async_turn_on(self) -> None: elif self._mac: await self.hass.async_add_executor_job(self._wake_on_lan) - def select_source(self, source: str) -> None: + async def async_select_source(self, source: str) -> None: """Select input source.""" if self._app_list and source in self._app_list: - self.send_key(self._app_list[source], "run_app") + await self._async_send_key(self._app_list[source], "run_app") return if source in SOURCES: - self.send_key(SOURCES[source]) + await self._async_send_key(SOURCES[source]) return LOGGER.error("Unsupported source") diff --git a/tests/components/samsungtv/test_config_flow.py b/tests/components/samsungtv/test_config_flow.py index 2aea4b22595b0d..eb8ca745819be6 100644 --- a/tests/components/samsungtv/test_config_flow.py +++ b/tests/components/samsungtv/test_config_flow.py @@ -322,7 +322,7 @@ async def test_ssdp(hass: HomeAssistant, no_mac_address: Mock) -> None: no_mac_address.return_value = "aa:bb:cc:dd:ee:ff" with patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.device_info", + "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.async_device_info", return_value=MOCK_DEVICE_INFO, ): # confirm to add the entry @@ -351,7 +351,7 @@ async def test_ssdp_noprefix(hass: HomeAssistant, no_mac_address: Mock) -> None: no_mac_address.return_value = "aa:bb:cc:dd:ee:ff" with patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.device_info", + "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.async_device_info", return_value=MOCK_DEVICE_INFO_2, ): # confirm to add the entry @@ -399,7 +399,7 @@ async def test_ssdp_legacy_missing_auth(hass: HomeAssistant) -> None: # missing authentication with patch( - "homeassistant.components.samsungtv.bridge.SamsungTVLegacyBridge.try_connect", + "homeassistant.components.samsungtv.bridge.SamsungTVLegacyBridge.async_try_connect", return_value=RESULT_AUTH_MISSING, ): result = await hass.config_entries.flow.async_configure( @@ -421,7 +421,7 @@ async def test_ssdp_legacy_not_supported(hass: HomeAssistant) -> None: assert result["step_id"] == "confirm" with patch( - "homeassistant.components.samsungtv.bridge.SamsungTVLegacyBridge.try_connect", + "homeassistant.components.samsungtv.bridge.SamsungTVLegacyBridge.async_try_connect", return_value=RESULT_NOT_SUPPORTED, ): # device not supported @@ -498,7 +498,7 @@ async def test_ssdp_not_successful(hass: HomeAssistant) -> None: "homeassistant.components.samsungtv.bridge.SamsungTVWS.open", side_effect=OSError("Boom"), ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.device_info", + "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.async_device_info", return_value=MOCK_DEVICE_INFO, ): @@ -527,7 +527,7 @@ async def test_ssdp_not_successful_2(hass: HomeAssistant) -> None: "homeassistant.components.samsungtv.bridge.SamsungTVWS.open", side_effect=ConnectionFailure("Boom"), ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.device_info", + "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.async_device_info", return_value=MOCK_DEVICE_INFO, ): @@ -554,7 +554,7 @@ async def test_ssdp_already_in_progress( no_mac_address.return_value = "aa:bb:cc:dd:ee:ff" with patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.device_info", + "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.async_device_info", return_value=MOCK_DEVICE_INFO, ): @@ -581,7 +581,7 @@ async def test_ssdp_already_configured( no_mac_address.return_value = "aa:bb:cc:dd:ee:ff" with patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.device_info", + "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.async_device_info", return_value=MOCK_DEVICE_INFO, ): diff --git a/tests/components/samsungtv/test_init.py b/tests/components/samsungtv/test_init.py index 12419b21fe8fdc..6b6aa4292430d1 100644 --- a/tests/components/samsungtv/test_init.py +++ b/tests/components/samsungtv/test_init.py @@ -83,7 +83,7 @@ async def test_setup_from_yaml_without_port_device_offline(hass: HomeAssistant) "homeassistant.components.samsungtv.bridge.SamsungTVWS.open", side_effect=OSError, ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.device_info", + "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.async_device_info", return_value=None, ): await async_setup_component(hass, SAMSUNGTV_DOMAIN, MOCK_CONFIG) From 30c9b8ee562f7c118126cc29237a6cb665034485 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Tue, 22 Feb 2022 10:32:23 -0800 Subject: [PATCH 0950/1098] Improve code quality for Overkiz integration (#67060) --- homeassistant/components/overkiz/coordinator.py | 6 ++---- homeassistant/components/overkiz/executor.py | 15 +++++---------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/overkiz/coordinator.py b/homeassistant/components/overkiz/coordinator.py index cbf83a90963cfd..98031926cfd759 100644 --- a/homeassistant/components/overkiz/coordinator.py +++ b/homeassistant/components/overkiz/coordinator.py @@ -6,7 +6,7 @@ from aiohttp import ServerDisconnectedError from pyoverkiz.client import OverkizClient -from pyoverkiz.enums import EventName, ExecutionState +from pyoverkiz.enums import EventName, ExecutionState, Protocol from pyoverkiz.exceptions import ( BadCredentialsException, InvalidEventListenerIdException, @@ -55,9 +55,7 @@ def __init__( self.client = client self.devices: dict[str, Device] = {d.device_url: d for d in devices} self.is_stateless = all( - device.device_url.startswith("rts://") - or device.device_url.startswith("internal://") - for device in devices + device.protocol in (Protocol.RTS, Protocol.INTERNAL) for device in devices ) self.executions: dict[str, dict[str, str]] = {} self.areas = self._places_to_area(places) diff --git a/homeassistant/components/overkiz/executor.py b/homeassistant/components/overkiz/executor.py index c362969abf20c1..cd39afd96961ba 100644 --- a/homeassistant/components/overkiz/executor.py +++ b/homeassistant/components/overkiz/executor.py @@ -8,7 +8,6 @@ from pyoverkiz.models import Command, Device from pyoverkiz.types import StateType as OverkizStateType -from .const import LOGGER from .coordinator import OverkizDataUpdateCoordinator @@ -59,15 +58,11 @@ def select_attribute(self, *attributes: str) -> OverkizStateType: async def async_execute_command(self, command_name: str, *args: Any) -> None: """Execute device command in async context.""" - try: - exec_id = await self.coordinator.client.execute_command( - self.device.device_url, - Command(command_name, list(args)), - "Home Assistant", - ) - except Exception as exception: # pylint: disable=broad-except - LOGGER.error(exception) - return + exec_id = await self.coordinator.client.execute_command( + self.device.device_url, + Command(command_name, list(args)), + "Home Assistant", + ) # ExecutionRegisteredEvent doesn't contain the device_url, thus we need to register it here self.coordinator.executions[exec_id] = { From db8620dac6e253a8bb139fde5161f31fe4180788 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Tue, 22 Feb 2022 20:32:39 +0200 Subject: [PATCH 0951/1098] Bump aioshelly to 1.0.10 (#67056) --- homeassistant/components/shelly/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json index 085b3ad733d715..1e9f34608eb485 100644 --- a/homeassistant/components/shelly/manifest.json +++ b/homeassistant/components/shelly/manifest.json @@ -3,7 +3,7 @@ "name": "Shelly", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/shelly", - "requirements": ["aioshelly==1.0.9"], + "requirements": ["aioshelly==1.0.10"], "zeroconf": [ { "type": "_http._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index 5f12ee60725adc..b6966ffc6a1b86 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -257,7 +257,7 @@ aioridwell==2021.12.2 aiosenseme==0.6.1 # homeassistant.components.shelly -aioshelly==1.0.9 +aioshelly==1.0.10 # homeassistant.components.steamist aiosteamist==0.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1832922baef93c..ef3ce287d0bed1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -192,7 +192,7 @@ aioridwell==2021.12.2 aiosenseme==0.6.1 # homeassistant.components.shelly -aioshelly==1.0.9 +aioshelly==1.0.10 # homeassistant.components.steamist aiosteamist==0.3.1 From 9950e543dfc59ea7a97747d975fe2eabeba72eaf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 22 Feb 2022 09:15:35 -1000 Subject: [PATCH 0952/1098] Add newly discovered samsungtv OUI (#67059) --- homeassistant/components/samsungtv/manifest.json | 3 ++- homeassistant/generated/dhcp.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/samsungtv/manifest.json b/homeassistant/components/samsungtv/manifest.json index ef0e99001c91b8..21e23c74eb13f0 100644 --- a/homeassistant/components/samsungtv/manifest.json +++ b/homeassistant/components/samsungtv/manifest.json @@ -24,7 +24,8 @@ {"macaddress": "8CC8CD*"}, {"macaddress": "606BBD*"}, {"macaddress": "F47B5E*"}, - {"macaddress": "4844F7*"} + {"macaddress": "4844F7*"}, + {"macaddress": "8CEA48*"} ], "codeowners": [ "@escoand", diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 60a8e50c523c07..8633534e976603 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -79,6 +79,7 @@ {'domain': 'samsungtv', 'macaddress': '606BBD*'}, {'domain': 'samsungtv', 'macaddress': 'F47B5E*'}, {'domain': 'samsungtv', 'macaddress': '4844F7*'}, + {'domain': 'samsungtv', 'macaddress': '8CEA48*'}, {'domain': 'screenlogic', 'registered_devices': True}, {'domain': 'screenlogic', 'hostname': 'pentair: *', 'macaddress': '00C033*'}, {'domain': 'sense', 'hostname': 'sense-*', 'macaddress': '009D6B*'}, From c2e62e4d9f888ee56a02fd78ee98bbd192ed1074 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 22 Feb 2022 13:15:16 -0800 Subject: [PATCH 0953/1098] Re-org device automations (#67064) Co-authored-by: Franck Nijhof --- .../components/device_automation/__init__.py | 102 +++--------------- .../components/device_automation/action.py | 68 ++++++++++++ .../components/device_automation/condition.py | 64 +++++++++++ .../components/device_automation/trigger.py | 38 +++++-- homeassistant/helpers/condition.py | 21 +--- homeassistant/helpers/script.py | 18 +--- tests/helpers/test_condition.py | 2 +- tests/helpers/test_script.py | 2 +- 8 files changed, 188 insertions(+), 127 deletions(-) create mode 100644 homeassistant/components/device_automation/action.py create mode 100644 homeassistant/components/device_automation/condition.py diff --git a/homeassistant/components/device_automation/__init__.py b/homeassistant/components/device_automation/__init__.py index 5cbc8a1e678c12..75613b3d118fd8 100644 --- a/homeassistant/components/device_automation/__init__.py +++ b/homeassistant/components/device_automation/__init__.py @@ -7,14 +7,14 @@ from functools import wraps import logging from types import ModuleType -from typing import TYPE_CHECKING, Any, Literal, NamedTuple, Protocol, Union, overload +from typing import TYPE_CHECKING, Any, Literal, NamedTuple, Union, overload import voluptuous as vol import voluptuous_serialize from homeassistant.components import websocket_api from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM -from homeassistant.core import CALLBACK_TYPE, Context, HomeAssistant +from homeassistant.core import HomeAssistant from homeassistant.helpers import ( config_validation as cv, device_registry as dr, @@ -28,11 +28,16 @@ from .exceptions import DeviceNotFound, InvalidDeviceAutomationConfig if TYPE_CHECKING: - from homeassistant.components.automation import ( - AutomationActionType, - AutomationTriggerInfo, - ) - from homeassistant.helpers import condition + from .action import DeviceAutomationActionProtocol + from .condition import DeviceAutomationConditionProtocol + from .trigger import DeviceAutomationTriggerProtocol + + DeviceAutomationPlatformType = Union[ + ModuleType, + DeviceAutomationTriggerProtocol, + DeviceAutomationConditionProtocol, + DeviceAutomationActionProtocol, + ] # mypy: allow-untyped-calls, allow-untyped-defs @@ -83,77 +88,6 @@ class DeviceAutomationType(Enum): } -class DeviceAutomationTriggerProtocol(Protocol): - """Define the format of device_trigger modules. - - Each module must define either TRIGGER_SCHEMA or async_validate_trigger_config. - """ - - TRIGGER_SCHEMA: vol.Schema - - async def async_validate_trigger_config( - self, hass: HomeAssistant, config: ConfigType - ) -> ConfigType: - """Validate config.""" - raise NotImplementedError - - async def async_attach_trigger( - self, - hass: HomeAssistant, - config: ConfigType, - action: AutomationActionType, - automation_info: AutomationTriggerInfo, - ) -> CALLBACK_TYPE: - """Attach a trigger.""" - raise NotImplementedError - - -class DeviceAutomationConditionProtocol(Protocol): - """Define the format of device_condition modules. - - Each module must define either CONDITION_SCHEMA or async_validate_condition_config. - """ - - CONDITION_SCHEMA: vol.Schema - - async def async_validate_condition_config( - self, hass: HomeAssistant, config: ConfigType - ) -> ConfigType: - """Validate config.""" - raise NotImplementedError - - def async_condition_from_config( - self, hass: HomeAssistant, config: ConfigType - ) -> condition.ConditionCheckerType: - """Evaluate state based on configuration.""" - raise NotImplementedError - - -class DeviceAutomationActionProtocol(Protocol): - """Define the format of device_action modules. - - Each module must define either ACTION_SCHEMA or async_validate_action_config. - """ - - ACTION_SCHEMA: vol.Schema - - async def async_validate_action_config( - self, hass: HomeAssistant, config: ConfigType - ) -> ConfigType: - """Validate config.""" - raise NotImplementedError - - async def async_call_action_from_config( - self, - hass: HomeAssistant, - config: ConfigType, - variables: dict[str, Any], - context: Context | None, - ) -> None: - """Execute a device action.""" - raise NotImplementedError - - @bind_hass async def async_get_device_automations( hass: HomeAssistant, @@ -193,14 +127,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: return True -DeviceAutomationPlatformType = Union[ - ModuleType, - DeviceAutomationTriggerProtocol, - DeviceAutomationConditionProtocol, - DeviceAutomationActionProtocol, -] - - @overload async def async_get_device_automation_platform( # noqa: D103 hass: HomeAssistant, @@ -231,13 +157,13 @@ async def async_get_device_automation_platform( # noqa: D103 @overload async def async_get_device_automation_platform( # noqa: D103 hass: HomeAssistant, domain: str, automation_type: DeviceAutomationType | str -) -> DeviceAutomationPlatformType: +) -> "DeviceAutomationPlatformType": ... async def async_get_device_automation_platform( hass: HomeAssistant, domain: str, automation_type: DeviceAutomationType | str -) -> DeviceAutomationPlatformType: +) -> "DeviceAutomationPlatformType": """Load device automation platform for integration. Throws InvalidDeviceAutomationConfig if the integration is not found or does not support device automation. diff --git a/homeassistant/components/device_automation/action.py b/homeassistant/components/device_automation/action.py new file mode 100644 index 00000000000000..5261757c645cf1 --- /dev/null +++ b/homeassistant/components/device_automation/action.py @@ -0,0 +1,68 @@ +"""Device action validator.""" +from __future__ import annotations + +from typing import Any, Protocol, cast + +import voluptuous as vol + +from homeassistant.const import CONF_DOMAIN +from homeassistant.core import Context, HomeAssistant +from homeassistant.helpers.typing import ConfigType + +from . import DeviceAutomationType, async_get_device_automation_platform +from .exceptions import InvalidDeviceAutomationConfig + + +class DeviceAutomationActionProtocol(Protocol): + """Define the format of device_action modules. + + Each module must define either ACTION_SCHEMA or async_validate_action_config. + """ + + ACTION_SCHEMA: vol.Schema + + async def async_validate_action_config( + self, hass: HomeAssistant, config: ConfigType + ) -> ConfigType: + """Validate config.""" + raise NotImplementedError + + async def async_call_action_from_config( + self, + hass: HomeAssistant, + config: ConfigType, + variables: dict[str, Any], + context: Context | None, + ) -> None: + """Execute a device action.""" + raise NotImplementedError + + +async def async_validate_action_config( + hass: HomeAssistant, config: ConfigType +) -> ConfigType: + """Validate config.""" + try: + platform = await async_get_device_automation_platform( + hass, config[CONF_DOMAIN], DeviceAutomationType.ACTION + ) + if hasattr(platform, "async_validate_action_config"): + return await platform.async_validate_action_config(hass, config) + return cast(ConfigType, platform.ACTION_SCHEMA(config)) + except InvalidDeviceAutomationConfig as err: + raise vol.Invalid(str(err) or "Invalid action configuration") from err + + +async def async_call_action_from_config( + hass: HomeAssistant, + config: ConfigType, + variables: dict[str, Any], + context: Context | None, +) -> None: + """Execute a device action.""" + platform = await async_get_device_automation_platform( + hass, + config[CONF_DOMAIN], + DeviceAutomationType.ACTION, + ) + await platform.async_call_action_from_config(hass, config, variables, context) diff --git a/homeassistant/components/device_automation/condition.py b/homeassistant/components/device_automation/condition.py new file mode 100644 index 00000000000000..1c226ee8c29296 --- /dev/null +++ b/homeassistant/components/device_automation/condition.py @@ -0,0 +1,64 @@ +"""Validate device conditions.""" +from __future__ import annotations + +from typing import TYPE_CHECKING, Protocol, cast + +import voluptuous as vol + +from homeassistant.const import CONF_DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.typing import ConfigType + +from . import DeviceAutomationType, async_get_device_automation_platform +from .exceptions import InvalidDeviceAutomationConfig + +if TYPE_CHECKING: + from homeassistant.helpers import condition + + +class DeviceAutomationConditionProtocol(Protocol): + """Define the format of device_condition modules. + + Each module must define either CONDITION_SCHEMA or async_validate_condition_config. + """ + + CONDITION_SCHEMA: vol.Schema + + async def async_validate_condition_config( + self, hass: HomeAssistant, config: ConfigType + ) -> ConfigType: + """Validate config.""" + raise NotImplementedError + + def async_condition_from_config( + self, hass: HomeAssistant, config: ConfigType + ) -> condition.ConditionCheckerType: + """Evaluate state based on configuration.""" + raise NotImplementedError + + +async def async_validate_condition_config( + hass: HomeAssistant, config: ConfigType +) -> ConfigType: + """Validate device condition config.""" + try: + config = cv.DEVICE_CONDITION_SCHEMA(config) + platform = await async_get_device_automation_platform( + hass, config[CONF_DOMAIN], DeviceAutomationType.CONDITION + ) + if hasattr(platform, "async_validate_condition_config"): + return await platform.async_validate_condition_config(hass, config) + return cast(ConfigType, platform.CONDITION_SCHEMA(config)) + except InvalidDeviceAutomationConfig as err: + raise vol.Invalid(str(err) or "Invalid condition configuration") from err + + +async def async_condition_from_config( + hass: HomeAssistant, config: ConfigType +) -> condition.ConditionCheckerType: + """Test a device condition.""" + platform = await async_get_device_automation_platform( + hass, config[CONF_DOMAIN], DeviceAutomationType.CONDITION + ) + return platform.async_condition_from_config(hass, config) diff --git a/homeassistant/components/device_automation/trigger.py b/homeassistant/components/device_automation/trigger.py index f2962d6544e5df..933c5c4c60a750 100644 --- a/homeassistant/components/device_automation/trigger.py +++ b/homeassistant/components/device_automation/trigger.py @@ -1,5 +1,5 @@ """Offer device oriented automation.""" -from typing import cast +from typing import Protocol, cast import voluptuous as vol @@ -21,17 +21,41 @@ TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA) +class DeviceAutomationTriggerProtocol(Protocol): + """Define the format of device_trigger modules. + + Each module must define either TRIGGER_SCHEMA or async_validate_trigger_config. + """ + + TRIGGER_SCHEMA: vol.Schema + + async def async_validate_trigger_config( + self, hass: HomeAssistant, config: ConfigType + ) -> ConfigType: + """Validate config.""" + raise NotImplementedError + + async def async_attach_trigger( + self, + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, + ) -> CALLBACK_TYPE: + """Attach a trigger.""" + raise NotImplementedError + + async def async_validate_trigger_config( hass: HomeAssistant, config: ConfigType ) -> ConfigType: """Validate config.""" - platform = await async_get_device_automation_platform( - hass, config[CONF_DOMAIN], DeviceAutomationType.TRIGGER - ) - if not hasattr(platform, "async_validate_trigger_config"): - return cast(ConfigType, platform.TRIGGER_SCHEMA(config)) - try: + platform = await async_get_device_automation_platform( + hass, config[CONF_DOMAIN], DeviceAutomationType.TRIGGER + ) + if not hasattr(platform, "async_validate_trigger_config"): + return cast(ConfigType, platform.TRIGGER_SCHEMA(config)) return await platform.async_validate_trigger_config(hass, config) except InvalidDeviceAutomationConfig as err: raise vol.Invalid(str(err) or "Invalid trigger configuration") from err diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 06853dd945064f..3355424b710412 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -13,10 +13,7 @@ from typing import Any, cast from homeassistant.components import zone as zone_cmp -from homeassistant.components.device_automation import ( - DeviceAutomationType, - async_get_device_automation_platform, -) +from homeassistant.components.device_automation import condition as device_condition from homeassistant.components.sensor import SensorDeviceClass from homeassistant.const import ( ATTR_DEVICE_CLASS, @@ -30,7 +27,6 @@ CONF_BELOW, CONF_CONDITION, CONF_DEVICE_ID, - CONF_DOMAIN, CONF_ENTITY_ID, CONF_ID, CONF_STATE, @@ -872,10 +868,8 @@ async def async_device_from_config( hass: HomeAssistant, config: ConfigType ) -> ConditionCheckerType: """Test a device condition.""" - platform = await async_get_device_automation_platform( - hass, config[CONF_DOMAIN], DeviceAutomationType.CONDITION - ) - return trace_condition_function(platform.async_condition_from_config(hass, config)) + checker = await device_condition.async_condition_from_config(hass, config) + return trace_condition_function(checker) async def async_trigger_from_config( @@ -931,15 +925,10 @@ async def async_validate_condition_config( sub_cond = await async_validate_condition_config(hass, sub_cond) conditions.append(sub_cond) config["conditions"] = conditions + return config if condition == "device": - config = cv.DEVICE_CONDITION_SCHEMA(config) - platform = await async_get_device_automation_platform( - hass, config[CONF_DOMAIN], DeviceAutomationType.CONDITION - ) - if hasattr(platform, "async_validate_condition_config"): - return await platform.async_validate_condition_config(hass, config) - return cast(ConfigType, platform.CONDITION_SCHEMA(config)) + return await device_condition.async_validate_condition_config(hass, config) if condition in ("numeric_state", "state"): validator = cast( diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 5a80691fa46152..1eabc33b89d20f 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -15,7 +15,8 @@ import voluptuous as vol from homeassistant import exceptions -from homeassistant.components import device_automation, scene +from homeassistant.components import scene +from homeassistant.components.device_automation import action as device_action from homeassistant.components.logger import LOGSEVERITY from homeassistant.const import ( ATTR_AREA_ID, @@ -244,13 +245,7 @@ async def async_validate_action_config( pass elif action_type == cv.SCRIPT_ACTION_DEVICE_AUTOMATION: - platform = await device_automation.async_get_device_automation_platform( - hass, config[CONF_DOMAIN], device_automation.DeviceAutomationType.ACTION - ) - if hasattr(platform, "async_validate_action_config"): - config = await platform.async_validate_action_config(hass, config) - else: - config = platform.ACTION_SCHEMA(config) + config = await device_action.async_validate_action_config(hass, config) elif action_type == cv.SCRIPT_ACTION_CHECK_CONDITION: config = await condition.async_validate_condition_config(hass, config) @@ -580,12 +575,7 @@ async def _async_call_service_step(self): async def _async_device_step(self): """Perform the device automation specified in the action.""" self._step_log("device automation") - platform = await device_automation.async_get_device_automation_platform( - self._hass, - self._action[CONF_DOMAIN], - device_automation.DeviceAutomationType.ACTION, - ) - await platform.async_call_action_from_config( + await device_action.async_call_action_from_config( self._hass, self._action, self._variables, self._context ) diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index b365c114b03360..ffbc2130f3b30c 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -2977,7 +2977,7 @@ async def test_platform_async_validate_condition_config(hass): config = {CONF_DEVICE_ID: "test", CONF_DOMAIN: "test", CONF_CONDITION: "device"} platform = AsyncMock() with patch( - "homeassistant.helpers.condition.async_get_device_automation_platform", + "homeassistant.components.device_automation.condition.async_get_device_automation_platform", return_value=platform, ): platform.async_validate_condition_config.return_value = config diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 5bb4833a796e26..11ba9810b9dffd 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -3721,7 +3721,7 @@ async def test_platform_async_validate_action_config(hass): config = {CONF_DEVICE_ID: "test", CONF_DOMAIN: "test"} platform = AsyncMock() with patch( - "homeassistant.helpers.script.device_automation.async_get_device_automation_platform", + "homeassistant.components.device_automation.action.async_get_device_automation_platform", return_value=platform, ): platform.async_validate_action_config.return_value = config From 756e7118507388ae6a53c3c5eb588cf8aabf168e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 22 Feb 2022 13:28:37 -0800 Subject: [PATCH 0954/1098] Add a new validate config WS command (#67057) --- .../components/websocket_api/commands.py | 88 +++++++++++-------- homeassistant/helpers/config_validation.py | 12 ++- .../components/websocket_api/test_commands.py | 57 ++++++++++++ 3 files changed, 120 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index 0b7e355ef24a21..650013dda7f13f 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -63,6 +63,7 @@ def async_register_commands( async_reg(hass, handle_subscribe_trigger) async_reg(hass, handle_test_condition) async_reg(hass, handle_unsubscribe_events) + async_reg(hass, handle_validate_config) def pong_message(iden: int) -> dict[str, Any]: @@ -116,7 +117,7 @@ def forward_events(event: Event) -> None: event_type, forward_events ) - connection.send_message(messages.result_message(msg["id"])) + connection.send_result(msg["id"]) @callback @@ -139,7 +140,7 @@ def forward_bootstrap_integrations(message: dict[str, Any]) -> None: hass, SIGNAL_BOOTSTRAP_INTEGRATONS, forward_bootstrap_integrations ) - connection.send_message(messages.result_message(msg["id"])) + connection.send_result(msg["id"]) @callback @@ -157,13 +158,9 @@ def handle_unsubscribe_events( if subscription in connection.subscriptions: connection.subscriptions.pop(subscription)() - connection.send_message(messages.result_message(msg["id"])) + connection.send_result(msg["id"]) else: - connection.send_message( - messages.error_message( - msg["id"], const.ERR_NOT_FOUND, "Subscription not found." - ) - ) + connection.send_error(msg["id"], const.ERR_NOT_FOUND, "Subscription not found.") @decorators.websocket_command( @@ -196,36 +193,20 @@ async def handle_call_service( context, target=target, ) - connection.send_message( - messages.result_message(msg["id"], {"context": context}) - ) + connection.send_result(msg["id"], {"context": context}) except ServiceNotFound as err: if err.domain == msg["domain"] and err.service == msg["service"]: - connection.send_message( - messages.error_message( - msg["id"], const.ERR_NOT_FOUND, "Service not found." - ) - ) + connection.send_error(msg["id"], const.ERR_NOT_FOUND, "Service not found.") else: - connection.send_message( - messages.error_message( - msg["id"], const.ERR_HOME_ASSISTANT_ERROR, str(err) - ) - ) + connection.send_error(msg["id"], const.ERR_HOME_ASSISTANT_ERROR, str(err)) except vol.Invalid as err: - connection.send_message( - messages.error_message(msg["id"], const.ERR_INVALID_FORMAT, str(err)) - ) + connection.send_error(msg["id"], const.ERR_INVALID_FORMAT, str(err)) except HomeAssistantError as err: connection.logger.exception(err) - connection.send_message( - messages.error_message(msg["id"], const.ERR_HOME_ASSISTANT_ERROR, str(err)) - ) + connection.send_error(msg["id"], const.ERR_HOME_ASSISTANT_ERROR, str(err)) except Exception as err: # pylint: disable=broad-except connection.logger.exception(err) - connection.send_message( - messages.error_message(msg["id"], const.ERR_UNKNOWN_ERROR, str(err)) - ) + connection.send_error(msg["id"], const.ERR_UNKNOWN_ERROR, str(err)) @callback @@ -244,7 +225,7 @@ def handle_get_states( if entity_perm(state.entity_id, "read") ] - connection.send_message(messages.result_message(msg["id"], states)) + connection.send_result(msg["id"], states) @decorators.websocket_command({vol.Required("type"): "get_services"}) @@ -254,7 +235,7 @@ async def handle_get_services( ) -> None: """Handle get services command.""" descriptions = await async_get_all_descriptions(hass) - connection.send_message(messages.result_message(msg["id"], descriptions)) + connection.send_result(msg["id"], descriptions) @callback @@ -263,7 +244,7 @@ def handle_get_config( hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] ) -> None: """Handle get config command.""" - connection.send_message(messages.result_message(msg["id"], hass.config.as_dict())) + connection.send_result(msg["id"], hass.config.as_dict()) @decorators.websocket_command({vol.Required("type"): "manifest/list"}) @@ -417,7 +398,7 @@ def handle_entity_source( if entity_perm(entity_id, "read") } - connection.send_message(messages.result_message(msg["id"], sources)) + connection.send_result(msg["id"], sources) return sources = {} @@ -535,7 +516,7 @@ async def handle_execute_script( context = connection.context(msg) script_obj = Script(hass, msg["sequence"], f"{const.DOMAIN} script", const.DOMAIN) await script_obj.async_run(msg.get("variables"), context=context) - connection.send_message(messages.result_message(msg["id"], {"context": context})) + connection.send_result(msg["id"], {"context": context}) @decorators.websocket_command( @@ -555,3 +536,40 @@ async def handle_fire_event( hass.bus.async_fire(msg["event_type"], msg.get("event_data"), context=context) connection.send_result(msg["id"], {"context": context}) + + +@decorators.websocket_command( + { + vol.Required("type"): "validate_config", + vol.Optional("trigger"): cv.match_all, + vol.Optional("condition"): cv.match_all, + vol.Optional("action"): cv.match_all, + } +) +@decorators.async_response +async def handle_validate_config( + hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] +) -> None: + """Handle validate config command.""" + # Circular dep + # pylint: disable=import-outside-toplevel + from homeassistant.helpers import condition, script, trigger + + result = {} + + for key, schema, validator in ( + ("trigger", cv.TRIGGER_SCHEMA, trigger.async_validate_trigger_config), + ("condition", cv.CONDITION_SCHEMA, condition.async_validate_condition_config), + ("action", cv.SCRIPT_SCHEMA, script.async_validate_actions_config), + ): + if key not in msg: + continue + + try: + await validator(hass, schema(msg[key])) # type: ignore + except vol.Invalid as err: + result[key] = {"valid": False, "error": str(err)} + else: + result[key] = {"valid": True, "error": None} + + connection.send_result(msg["id"], result) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 30ce647132e2a7..7f33ec1f1ec9ab 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1033,7 +1033,12 @@ def script_action(value: Any) -> dict: if not isinstance(value, dict): raise vol.Invalid("expected dictionary") - return ACTION_TYPE_SCHEMAS[determine_script_action(value)](value) + try: + action = determine_script_action(value) + except ValueError as err: + raise vol.Invalid(str(err)) + + return ACTION_TYPE_SCHEMAS[action](value) SCRIPT_SCHEMA = vol.All(ensure_list, [script_action]) @@ -1444,7 +1449,10 @@ def determine_script_action(action: dict[str, Any]) -> str: if CONF_VARIABLES in action: return SCRIPT_ACTION_VARIABLES - return SCRIPT_ACTION_CALL_SERVICE + if CONF_SERVICE in action or CONF_SERVICE_TEMPLATE in action: + return SCRIPT_ACTION_CALL_SERVICE + + raise ValueError("Unable to determine action") ACTION_TYPE_SCHEMAS: dict[str, Callable[[Any], dict]] = { diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index 8304b093a148ba..130870f73f03a3 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -1286,3 +1286,60 @@ async def test_integration_setup_info(hass, websocket_client, hass_admin_user): {"domain": "august", "seconds": 12.5}, {"domain": "isy994", "seconds": 12.8}, ] + + +@pytest.mark.parametrize( + "key,config", + ( + ("trigger", {"platform": "event", "event_type": "hello"}), + ( + "condition", + {"condition": "state", "entity_id": "hello.world", "state": "paulus"}, + ), + ("action", {"service": "domain_test.test_service"}), + ), +) +async def test_validate_config_works(websocket_client, key, config): + """Test config validation.""" + await websocket_client.send_json({"id": 7, "type": "validate_config", key: config}) + + msg = await websocket_client.receive_json() + assert msg["id"] == 7 + assert msg["type"] == const.TYPE_RESULT + assert msg["success"] + assert msg["result"] == {key: {"valid": True, "error": None}} + + +@pytest.mark.parametrize( + "key,config,error", + ( + ( + "trigger", + {"platform": "non_existing", "event_type": "hello"}, + "Invalid platform 'non_existing' specified", + ), + ( + "condition", + { + "condition": "non_existing", + "entity_id": "hello.world", + "state": "paulus", + }, + "Unexpected value for condition: 'non_existing'. Expected and, device, not, numeric_state, or, state, sun, template, time, trigger, zone", + ), + ( + "action", + {"non_existing": "domain_test.test_service"}, + "Unable to determine action @ data[0]", + ), + ), +) +async def test_validate_config_invalid(websocket_client, key, config, error): + """Test config validation.""" + await websocket_client.send_json({"id": 7, "type": "validate_config", key: config}) + + msg = await websocket_client.receive_json() + assert msg["id"] == 7 + assert msg["type"] == const.TYPE_RESULT + assert msg["success"] + assert msg["result"] == {key: {"valid": False, "error": error}} From 938b64081b0cbc21d1a9c1141c1e575824ce31ae Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 22 Feb 2022 13:59:40 -0800 Subject: [PATCH 0955/1098] Block peer certs on supervisor (#66837) Co-authored-by: Pascal Vizeli Co-authored-by: Mike Degatano --- homeassistant/bootstrap.py | 4 +-- .../components/analytics/analytics.py | 2 +- homeassistant/components/hassio/__init__.py | 22 ++++++++---- homeassistant/components/http/__init__.py | 9 +++-- homeassistant/components/onboarding/views.py | 2 +- homeassistant/components/ozw/config_flow.py | 2 +- homeassistant/components/updater/__init__.py | 2 +- .../components/zwave_js/config_flow.py | 6 ++-- homeassistant/helpers/supervisor.py | 11 ++++++ tests/components/hassio/conftest.py | 2 ++ tests/components/hassio/test_binary_sensor.py | 6 +++- tests/components/hassio/test_init.py | 8 +++-- tests/components/hassio/test_sensor.py | 6 +++- tests/components/http/test_ban.py | 4 ++- tests/components/http/test_init.py | 36 +++++++++++++++++++ tests/components/onboarding/test_views.py | 2 ++ tests/components/updater/test_init.py | 10 +++--- tests/helpers/test_supervisor.py | 16 +++++++++ tests/test_bootstrap.py | 2 +- 19 files changed, 121 insertions(+), 31 deletions(-) create mode 100644 homeassistant/helpers/supervisor.py create mode 100644 tests/helpers/test_supervisor.py diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 986171cbee79e9..40feae117a496b 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -23,7 +23,7 @@ SIGNAL_BOOTSTRAP_INTEGRATONS, ) from .exceptions import HomeAssistantError -from .helpers import area_registry, device_registry, entity_registry +from .helpers import area_registry, device_registry, entity_registry, supervisor from .helpers.dispatcher import async_dispatcher_send from .helpers.typing import ConfigType from .setup import ( @@ -398,7 +398,7 @@ def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]: domains.update(hass.config_entries.async_domains()) # Make sure the Hass.io component is loaded - if "HASSIO" in os.environ: + if supervisor.has_supervisor(): domains.add("hassio") return domains diff --git a/homeassistant/components/analytics/analytics.py b/homeassistant/components/analytics/analytics.py index d1b8879bf7c11a..a7c664091c153a 100644 --- a/homeassistant/components/analytics/analytics.py +++ b/homeassistant/components/analytics/analytics.py @@ -104,7 +104,7 @@ def endpoint(self) -> str: @property def supervisor(self) -> bool: """Return bool if a supervisor is present.""" - return hassio.is_hassio(self.hass) + return hassio.is_hassio() async def load(self) -> None: """Load preferences.""" diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 434c95b03b2ddd..1ade17e452e384 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -41,6 +41,8 @@ async_get_registry, ) from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.frame import report +from homeassistant.helpers.supervisor import has_supervisor from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.loader import bind_hass @@ -394,12 +396,21 @@ def get_core_info(hass): @callback @bind_hass -def is_hassio(hass: HomeAssistant) -> bool: +def is_hassio( + hass: HomeAssistant | None = None, # pylint: disable=unused-argument +) -> bool: """Return true if Hass.io is loaded. Async friendly. """ - return DOMAIN in hass.config.components + if hass is not None: + report( + "hass param deprecated for is_hassio", + exclude_integrations={DOMAIN}, + error_if_core=False, + ) + + return has_supervisor() @callback @@ -412,11 +423,8 @@ def get_supervisor_ip() -> str: async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: C901 """Set up the Hass.io component.""" - # Check local setup - for env in ("HASSIO", "HASSIO_TOKEN"): - if os.environ.get(env): - continue - _LOGGER.error("Missing %s environment variable", env) + if not has_supervisor(): + _LOGGER.error("Supervisor not available") if config_entries := hass.config_entries.async_entries(DOMAIN): hass.async_create_task( hass.config_entries.async_remove(config_entries[0].entry_id) diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index a41329a1548917..46b08ee69c3134 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -23,8 +23,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP, SERVER_PORT from homeassistant.core import Event, HomeAssistant from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import storage -import homeassistant.helpers.config_validation as cv +from homeassistant.helpers import config_validation as cv, storage, supervisor from homeassistant.helpers.network import NoURLAvailableError, get_url from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass @@ -167,6 +166,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: login_threshold = conf[CONF_LOGIN_ATTEMPTS_THRESHOLD] ssl_profile = conf[CONF_SSL_PROFILE] + if ssl_peer_certificate is not None and supervisor.has_supervisor(): + _LOGGER.warning( + "Peer certificates are not supported when running the supervisor" + ) + ssl_peer_certificate = None + server = HomeAssistantHTTP( hass, server_host=server_host, diff --git a/homeassistant/components/onboarding/views.py b/homeassistant/components/onboarding/views.py index b277bd97edf877..d7dde43b4e056e 100644 --- a/homeassistant/components/onboarding/views.py +++ b/homeassistant/components/onboarding/views.py @@ -195,7 +195,7 @@ async def post(self, request): from homeassistant.components import hassio if ( - hassio.is_hassio(hass) + hassio.is_hassio() and "raspberrypi" in hassio.get_core_info(hass)["machine"] ): onboard_integrations.append("rpi_power") diff --git a/homeassistant/components/ozw/config_flow.py b/homeassistant/components/ozw/config_flow.py index 5e745a123f4b88..9a3f0dcb8b8388 100644 --- a/homeassistant/components/ozw/config_flow.py +++ b/homeassistant/components/ozw/config_flow.py @@ -45,7 +45,7 @@ async def async_step_user(self, user_input=None): # Set a unique_id to make sure discovery flow is aborted on progress. await self.async_set_unique_id(DOMAIN, raise_on_progress=False) - if not hassio.is_hassio(self.hass): + if not hassio.is_hassio(): return self._async_use_mqtt_integration() return await self.async_step_on_supervisor() diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py index 4f88b5d13696aa..e1dc2fca4e4b6f 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -74,7 +74,7 @@ async def check_new_version() -> Updater: _LOGGER.debug("Fetched version %s: %s", newest, release_notes) # Load data from Supervisor - if hassio.is_hassio(hass): + if hassio.is_hassio(): core_info = hassio.get_core_info(hass) newest = core_info["version_latest"] diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index 32f406d7476347..e58e9a80ccfd57 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -332,14 +332,14 @@ async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle the initial step.""" - if is_hassio(self.hass): + if is_hassio(): return await self.async_step_on_supervisor() return await self.async_step_manual() async def async_step_usb(self, discovery_info: usb.UsbServiceInfo) -> FlowResult: """Handle USB Discovery.""" - if not is_hassio(self.hass): + if not is_hassio(): return self.async_abort(reason="discovery_requires_supervisor") if self._async_current_entries(): return self.async_abort(reason="already_configured") @@ -641,7 +641,7 @@ async def async_step_init( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Manage the options.""" - if is_hassio(self.hass): + if is_hassio(): return await self.async_step_on_supervisor() return await self.async_step_manual() diff --git a/homeassistant/helpers/supervisor.py b/homeassistant/helpers/supervisor.py new file mode 100644 index 00000000000000..7e7cfadeadc219 --- /dev/null +++ b/homeassistant/helpers/supervisor.py @@ -0,0 +1,11 @@ +"""Supervisor helper.""" + +import os + +from homeassistant.core import callback + + +@callback +def has_supervisor() -> bool: + """Return true if supervisor is available.""" + return "SUPERVISOR" in os.environ diff --git a/tests/components/hassio/conftest.py b/tests/components/hassio/conftest.py index 89a8c6f5c5106c..c5ec209500df5f 100644 --- a/tests/components/hassio/conftest.py +++ b/tests/components/hassio/conftest.py @@ -21,6 +21,8 @@ def hassio_env(): ), patch.dict(os.environ, {"HASSIO_TOKEN": HASSIO_TOKEN}), patch( "homeassistant.components.hassio.HassIO.get_info", Mock(side_effect=HassioAPIError()), + ), patch.dict( + os.environ, {"SUPERVISOR": "127.0.0.1"} ): yield diff --git a/tests/components/hassio/test_binary_sensor.py b/tests/components/hassio/test_binary_sensor.py index e4263eb5529528..6008653e7a6686 100644 --- a/tests/components/hassio/test_binary_sensor.py +++ b/tests/components/hassio/test_binary_sensor.py @@ -11,7 +11,11 @@ from tests.common import MockConfigEntry -MOCK_ENVIRON = {"HASSIO": "127.0.0.1", "HASSIO_TOKEN": "abcdefgh"} +MOCK_ENVIRON = { + "HASSIO": "127.0.0.1", + "HASSIO_TOKEN": "abcdefgh", + "SUPERVISOR": "127.0.0.1", +} @pytest.fixture(autouse=True) diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index e006cf9d829137..d901432b6e354e 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -16,7 +16,11 @@ from tests.common import MockConfigEntry, async_fire_time_changed -MOCK_ENVIRON = {"HASSIO": "127.0.0.1", "HASSIO_TOKEN": "abcdefgh"} +MOCK_ENVIRON = { + "HASSIO": "127.0.0.1", + "HASSIO_TOKEN": "abcdefgh", + "SUPERVISOR": "127.0.0.1", +} @pytest.fixture(autouse=True) @@ -151,7 +155,6 @@ async def test_setup_api_ping(hass, aioclient_mock): assert aioclient_mock.call_count == 10 assert hass.components.hassio.get_core_info()["version_latest"] == "1.0.0" - assert hass.components.hassio.is_hassio() async def test_setup_api_panel(hass, aioclient_mock): @@ -334,7 +337,6 @@ async def test_warn_when_cannot_connect(hass, caplog): result = await async_setup_component(hass, "hassio", {}) assert result - assert hass.components.hassio.is_hassio() assert "Not connected with the supervisor / system too busy!" in caplog.text diff --git a/tests/components/hassio/test_sensor.py b/tests/components/hassio/test_sensor.py index 481ba1b578fdb1..4969cac1d67fb1 100644 --- a/tests/components/hassio/test_sensor.py +++ b/tests/components/hassio/test_sensor.py @@ -11,7 +11,11 @@ from tests.common import MockConfigEntry -MOCK_ENVIRON = {"HASSIO": "127.0.0.1", "HASSIO_TOKEN": "abcdefgh"} +MOCK_ENVIRON = { + "HASSIO": "127.0.0.1", + "HASSIO_TOKEN": "abcdefgh", + "SUPERVISOR": "127.0.0.1", +} @pytest.fixture(autouse=True) diff --git a/tests/components/http/test_ban.py b/tests/components/http/test_ban.py index fbd545e0506197..54e2f0495cd538 100644 --- a/tests/components/http/test_ban.py +++ b/tests/components/http/test_ban.py @@ -36,7 +36,9 @@ def hassio_env_fixture(): with patch.dict(os.environ, {"HASSIO": "127.0.0.1"}), patch( "homeassistant.components.hassio.HassIO.is_connected", return_value={"result": "ok", "data": {}}, - ), patch.dict(os.environ, {"HASSIO_TOKEN": "123456"}): + ), patch.dict(os.environ, {"HASSIO_TOKEN": "123456"}), patch.dict( + os.environ, {"SUPERVISOR": "127.0.0.1"} + ): yield diff --git a/tests/components/http/test_init.py b/tests/components/http/test_init.py index 79d0a6c47916ec..97b705f5b6d7ea 100644 --- a/tests/components/http/test_init.py +++ b/tests/components/http/test_init.py @@ -261,6 +261,42 @@ async def test_peer_cert(hass, tmpdir): assert len(mock_load_verify_locations.mock_calls) == 1 +async def test_peer_cert_ignored_with_supervisor(hass, tmpdir): + """Test peer certiicate requirement ignored in supervised deployments.""" + cert_path, key_path, peer_cert_path = await hass.async_add_executor_job( + _setup_empty_ssl_pem_files, tmpdir + ) + + with patch("ssl.SSLContext.load_cert_chain"), patch( + "homeassistant.components.http.supervisor.has_supervisor", return_value=True + ), patch( + "ssl.SSLContext.load_verify_locations" + ) as mock_load_verify_locations, patch( + "homeassistant.util.ssl.server_context_modern", + side_effect=server_context_modern, + ) as mock_context: + assert ( + await async_setup_component( + hass, + "http", + { + "http": { + "ssl_peer_certificate": peer_cert_path, + "ssl_profile": "modern", + "ssl_certificate": cert_path, + "ssl_key": key_path, + } + }, + ) + is True + ) + await hass.async_start() + await hass.async_block_till_done() + + assert len(mock_context.mock_calls) == 1 + mock_load_verify_locations.assert_not_called() + + async def test_emergency_ssl_certificate_when_invalid(hass, tmpdir, caplog): """Test http can startup with an emergency self signed cert when the current one is broken.""" diff --git a/tests/components/onboarding/test_views.py b/tests/components/onboarding/test_views.py index 976e2b84c68eb0..2b4db4f68a2eac 100644 --- a/tests/components/onboarding/test_views.py +++ b/tests/components/onboarding/test_views.py @@ -86,6 +86,8 @@ async def mock_supervisor_fixture(hass, aioclient_mock): return_value={"panels": {}}, ), patch.dict( os.environ, {"HASSIO_TOKEN": "123456"} + ), patch.dict( + os.environ, {"SUPERVISOR": "127.0.0.1"} ): yield diff --git a/tests/components/updater/test_init.py b/tests/components/updater/test_init.py index 2b0f494f5f541a..e2cc0ee41b07e3 100644 --- a/tests/components/updater/test_init.py +++ b/tests/components/updater/test_init.py @@ -7,8 +7,6 @@ from homeassistant.helpers.update_coordinator import UpdateFailed from homeassistant.setup import async_setup_component -from tests.common import mock_component - NEW_VERSION = "10000.0" MOCK_VERSION = "10.0" MOCK_DEV_VERSION = "10.0.dev0" @@ -113,12 +111,12 @@ async def test_new_version_shows_entity_after_hour_hassio( hass, mock_get_newest_version ): """Test if binary sensor gets updated if new version is available / Hass.io.""" - mock_component(hass, "hassio") - hass.data["hassio_core_info"] = {"version_latest": "999.0"} + with patch("homeassistant.components.updater.hassio.is_hassio", return_value=True): + hass.data["hassio_core_info"] = {"version_latest": "999.0"} - assert await async_setup_component(hass, updater.DOMAIN, {updater.DOMAIN: {}}) + assert await async_setup_component(hass, updater.DOMAIN, {updater.DOMAIN: {}}) - await hass.async_block_till_done() + await hass.async_block_till_done() assert hass.states.is_state("binary_sensor.updater", "on") assert ( diff --git a/tests/helpers/test_supervisor.py b/tests/helpers/test_supervisor.py new file mode 100644 index 00000000000000..cfefe7a9ec471a --- /dev/null +++ b/tests/helpers/test_supervisor.py @@ -0,0 +1,16 @@ +"""Test the Hassio helper.""" +from unittest.mock import patch + +from homeassistant.helpers.supervisor import has_supervisor + + +async def test_has_supervisor_yes(): + """Test has_supervisor when supervisor available.""" + with patch("homeassistant.helpers.supervisor.os.environ", {"SUPERVISOR": True}): + assert has_supervisor() + + +async def test_has_supervisor_no(): + """Test has_supervisor when supervisor not available.""" + with patch("homeassistant.helpers.supervisor.os.environ"): + assert not has_supervisor() diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 87d93c1a1ac0b4..b34039cc2c9cbe 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -86,7 +86,7 @@ async def test_load_hassio(hass): with patch.dict(os.environ, {}, clear=True): assert bootstrap._get_domains(hass, {}) == set() - with patch.dict(os.environ, {"HASSIO": "1"}): + with patch.dict(os.environ, {"SUPERVISOR": "1"}): assert bootstrap._get_domains(hass, {}) == {"hassio"} From be09ca3a7159ad0d67573f9e8b221f178485d1ce Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 22 Feb 2022 15:11:02 -0800 Subject: [PATCH 0956/1098] Add source name to radio browser media source (#67077) --- homeassistant/components/radio_browser/media_source.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/radio_browser/media_source.py b/homeassistant/components/radio_browser/media_source.py index 952b0e67e25c56..8240691b2477bb 100644 --- a/homeassistant/components/radio_browser/media_source.py +++ b/homeassistant/components/radio_browser/media_source.py @@ -43,6 +43,8 @@ async def async_get_media_source(hass: HomeAssistant) -> RadioMediaSource: class RadioMediaSource(MediaSource): """Provide Radio stations as media sources.""" + name = "Radio Browser" + def __init__( self, hass: HomeAssistant, radios: RadioBrowser, entry: ConfigEntry ) -> None: From 7d4f5a68d64bc9ff3ca6f7042fc34d28b8dfb9dc Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 22 Feb 2022 15:33:38 -0800 Subject: [PATCH 0957/1098] Bump frontend to 20220222.0 (#67078) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index f596a64e5b1a29..9a3db7f8294d8e 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20220220.0" + "home-assistant-frontend==20220222.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 1b065833adcfdb..44ff7d46453dad 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ ciso8601==2.2.0 cryptography==35.0.0 emoji==1.6.3 hass-nabucasa==0.53.1 -home-assistant-frontend==20220220.0 +home-assistant-frontend==20220222.0 httpx==0.21.3 ifaddr==0.1.7 jinja2==3.0.3 diff --git a/requirements_all.txt b/requirements_all.txt index b6966ffc6a1b86..5fea13dd6e62fc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -843,7 +843,7 @@ hole==0.7.0 holidays==0.13 # homeassistant.components.frontend -home-assistant-frontend==20220220.0 +home-assistant-frontend==20220222.0 # homeassistant.components.zwave # homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ef3ce287d0bed1..676464c76cb87d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -556,7 +556,7 @@ hole==0.7.0 holidays==0.13 # homeassistant.components.frontend -home-assistant-frontend==20220220.0 +home-assistant-frontend==20220222.0 # homeassistant.components.zwave # homeassistant-pyozw==0.1.10 From 1274078f1b875804833120e7039e00b2d94ddc20 Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Wed, 23 Feb 2022 00:39:19 +0100 Subject: [PATCH 0958/1098] Fix naming of device entities created by Fritz!Tools (#67076) --- homeassistant/components/fritz/switch.py | 26 ++++++++++-------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/fritz/switch.py b/homeassistant/components/fritz/switch.py index 3b01ce618b863f..730ffb7fc0d763 100644 --- a/homeassistant/components/fritz/switch.py +++ b/homeassistant/components/fritz/switch.py @@ -479,6 +479,17 @@ def __init__(self, avm_wrapper: AvmWrapper, device: FritzDevice) -> None: self._name = f"{device.hostname} Internet Access" self._attr_unique_id = f"{self._mac}_internet_access" self._attr_entity_category = EntityCategory.CONFIG + self._attr_device_info = DeviceInfo( + connections={(CONNECTION_NETWORK_MAC, self._mac)}, + default_manufacturer="AVM", + default_model="FRITZ!Box Tracked device", + default_name=device.hostname, + identifiers={(DOMAIN, self._mac)}, + via_device=( + DOMAIN, + avm_wrapper.unique_id, + ), + ) @property def is_on(self) -> bool | None: @@ -492,21 +503,6 @@ def available(self) -> bool: return False return super().available - @property - def device_info(self) -> DeviceInfo: - """Return the device information.""" - return DeviceInfo( - connections={(CONNECTION_NETWORK_MAC, self._mac)}, - default_manufacturer="AVM", - default_model="FRITZ!Box Tracked device", - default_name=self.name, - identifiers={(DOMAIN, self._mac)}, - via_device=( - DOMAIN, - self._avm_wrapper.unique_id, - ), - ) - async def async_turn_on(self, **kwargs: Any) -> None: """Turn on switch.""" await self._async_handle_turn_on_off(turn_on=True) From cb190a7b171694063e688707fd004fa3957946a6 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Wed, 23 Feb 2022 00:44:02 +0100 Subject: [PATCH 0959/1098] Add (basic) diagnostics support for Hue integration (#67074) Co-authored-by: Paulus Schoutsen --- homeassistant/components/hue/diagnostics.py | 22 +++++++++++++++++++++ tests/components/hue/test_diagnostics.py | 22 +++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 homeassistant/components/hue/diagnostics.py create mode 100644 tests/components/hue/test_diagnostics.py diff --git a/homeassistant/components/hue/diagnostics.py b/homeassistant/components/hue/diagnostics.py new file mode 100644 index 00000000000000..17f00a50bbe135 --- /dev/null +++ b/homeassistant/components/hue/diagnostics.py @@ -0,0 +1,22 @@ +"""Diagnostics support for Hue.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .bridge import HueBridge +from .const import DOMAIN + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + bridge: HueBridge = hass.data[DOMAIN][entry.entry_id] + if bridge.api_version == 1: + # diagnostics is only implemented for V2 bridges. + return {} + # Hue diagnostics are already redacted + return await bridge.api.get_diagnostics() diff --git a/tests/components/hue/test_diagnostics.py b/tests/components/hue/test_diagnostics.py new file mode 100644 index 00000000000000..8ccc91a5d195a6 --- /dev/null +++ b/tests/components/hue/test_diagnostics.py @@ -0,0 +1,22 @@ +"""Test Hue diagnostics.""" + +from .conftest import setup_platform + +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_diagnostics_v1(hass, hass_client, mock_bridge_v1): + """Test diagnostics v1.""" + await setup_platform(hass, mock_bridge_v1, []) + config_entry = hass.config_entries.async_entries("hue")[0] + result = await get_diagnostics_for_config_entry(hass, hass_client, config_entry) + assert result == {} + + +async def test_diagnostics_v2(hass, hass_client, mock_bridge_v2): + """Test diagnostics v2.""" + mock_bridge_v2.api.get_diagnostics.return_value = {"hello": "world"} + await setup_platform(hass, mock_bridge_v2, []) + config_entry = hass.config_entries.async_entries("hue")[0] + result = await get_diagnostics_for_config_entry(hass, hass_client, config_entry) + assert result == {"hello": "world"} From 5e938ea61b86887121f9a789043c20cf259cff45 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Tue, 22 Feb 2022 16:00:14 -0800 Subject: [PATCH 0960/1098] Bump PyOverkiz and improve code quality (late review) (#67075) --- .../overkiz/cover_entities/vertical_cover.py | 12 ++++++++++-- homeassistant/components/overkiz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/overkiz/cover_entities/vertical_cover.py b/homeassistant/components/overkiz/cover_entities/vertical_cover.py index 4ad1c401d73a4a..ec502a403ad94f 100644 --- a/homeassistant/components/overkiz/cover_entities/vertical_cover.py +++ b/homeassistant/components/overkiz/cover_entities/vertical_cover.py @@ -3,7 +3,13 @@ from typing import Any, cast -from pyoverkiz.enums import OverkizCommand, OverkizState, UIClass, UIWidget +from pyoverkiz.enums import ( + OverkizCommand, + OverkizCommandParam, + OverkizState, + UIClass, + UIWidget, +) from homeassistant.components.cover import ( ATTR_POSITION, @@ -132,5 +138,7 @@ async def async_set_cover_position_low_speed(self, **kwargs: Any) -> None: position = 100 - kwargs.get(ATTR_POSITION, 0) await self.executor.async_execute_command( - OverkizCommand.SET_CLOSURE_AND_LINEAR_SPEED, position, "lowspeed" + OverkizCommand.SET_CLOSURE_AND_LINEAR_SPEED, + position, + OverkizCommandParam.LOWSPEED, ) diff --git a/homeassistant/components/overkiz/manifest.json b/homeassistant/components/overkiz/manifest.json index 833441442b2f67..1c5d6b1b685e1f 100644 --- a/homeassistant/components/overkiz/manifest.json +++ b/homeassistant/components/overkiz/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/overkiz", "requirements": [ - "pyoverkiz==1.3.6" + "pyoverkiz==1.3.8" ], "zeroconf": [ { diff --git a/requirements_all.txt b/requirements_all.txt index 5fea13dd6e62fc..925df830952b2a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1753,7 +1753,7 @@ pyotgw==1.1b1 pyotp==2.6.0 # homeassistant.components.overkiz -pyoverkiz==1.3.6 +pyoverkiz==1.3.8 # homeassistant.components.openweathermap pyowm==3.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 676464c76cb87d..2a44bfa63619c2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1125,7 +1125,7 @@ pyotgw==1.1b1 pyotp==2.6.0 # homeassistant.components.overkiz -pyoverkiz==1.3.6 +pyoverkiz==1.3.8 # homeassistant.components.openweathermap pyowm==3.2.0 From 0c9be633f5c4a7fe0525ff112486b3c62147a911 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Wed, 23 Feb 2022 01:02:12 +0100 Subject: [PATCH 0961/1098] Fix missing uptime sensor in some Fritz scenarios (#67073) * Fix missing uptime sensor in some Fritz scenarios * apply review comment --- homeassistant/components/fritz/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/fritz/sensor.py b/homeassistant/components/fritz/sensor.py index 7874fb87b004ee..f01966d7114d88 100644 --- a/homeassistant/components/fritz/sensor.py +++ b/homeassistant/components/fritz/sensor.py @@ -170,7 +170,7 @@ class FritzSensorEntityDescription(SensorEntityDescription, FritzRequireKeysMixi device_class=SensorDeviceClass.TIMESTAMP, entity_category=EntityCategory.DIAGNOSTIC, value_fn=_retrieve_device_uptime_state, - is_suitable=lambda info: info.mesh_role != MeshRoles.NONE, + is_suitable=lambda info: True, ), FritzSensorEntityDescription( key="connection_uptime", From bdcdf5222549710aeb70266b35d27cd7dbfef6ce Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 23 Feb 2022 00:20:00 +0000 Subject: [PATCH 0962/1098] [ci skip] Translation update --- .../binary_sensor/translations/de.json | 4 ++++ .../binary_sensor/translations/et.json | 4 ++++ .../binary_sensor/translations/hu.json | 4 ++++ .../binary_sensor/translations/ja.json | 4 ++++ .../binary_sensor/translations/no.json | 4 ++++ .../binary_sensor/translations/pl.json | 4 ++++ .../binary_sensor/translations/ru.json | 4 ++++ .../binary_sensor/translations/zh-Hant.json | 4 ++++ .../diagnostics/translations/fr.json | 2 +- .../components/dlna_dms/translations/ca.json | 24 +++++++++++++++++++ .../components/dlna_dms/translations/de.json | 24 +++++++++++++++++++ .../components/dlna_dms/translations/el.json | 24 +++++++++++++++++++ .../components/dlna_dms/translations/et.json | 24 +++++++++++++++++++ .../components/dlna_dms/translations/hu.json | 24 +++++++++++++++++++ .../components/dlna_dms/translations/ja.json | 24 +++++++++++++++++++ .../components/dlna_dms/translations/no.json | 24 +++++++++++++++++++ .../components/dlna_dms/translations/pl.json | 24 +++++++++++++++++++ .../components/dlna_dms/translations/ru.json | 24 +++++++++++++++++++ .../dlna_dms/translations/zh-Hant.json | 24 +++++++++++++++++++ .../components/iss/translations/fr.json | 9 +++++++ .../moon/translations/sensor.el.json | 6 ++++- .../components/mqtt/translations/el.json | 6 +++++ .../pure_energie/translations/de.json | 23 ++++++++++++++++++ .../radio_browser/translations/de.json | 12 ++++++++++ .../radio_browser/translations/hu.json | 12 ++++++++++ .../radio_browser/translations/ja.json | 12 ++++++++++ .../radio_browser/translations/no.json | 12 ++++++++++ .../radio_browser/translations/pl.json | 12 ++++++++++ .../radio_browser/translations/zh-Hant.json | 12 ++++++++++ .../components/sense/translations/de.json | 16 ++++++++++++- .../components/sense/translations/el.json | 16 ++++++++++++- .../components/sense/translations/et.json | 16 ++++++++++++- .../components/sense/translations/hu.json | 16 ++++++++++++- .../components/sense/translations/ja.json | 16 ++++++++++++- .../components/sense/translations/no.json | 16 ++++++++++++- .../components/sense/translations/pl.json | 16 ++++++++++++- .../components/sense/translations/ru.json | 16 ++++++++++++- .../sense/translations/zh-Hant.json | 16 ++++++++++++- .../components/sonarr/translations/ca.json | 3 ++- .../components/sonarr/translations/de.json | 3 ++- .../components/sonarr/translations/el.json | 1 + .../components/sonarr/translations/en.json | 3 ++- .../components/sonarr/translations/pt-BR.json | 3 ++- .../wolflink/translations/sensor.el.json | 3 +++ 44 files changed, 535 insertions(+), 15 deletions(-) create mode 100644 homeassistant/components/dlna_dms/translations/ca.json create mode 100644 homeassistant/components/dlna_dms/translations/de.json create mode 100644 homeassistant/components/dlna_dms/translations/el.json create mode 100644 homeassistant/components/dlna_dms/translations/et.json create mode 100644 homeassistant/components/dlna_dms/translations/hu.json create mode 100644 homeassistant/components/dlna_dms/translations/ja.json create mode 100644 homeassistant/components/dlna_dms/translations/no.json create mode 100644 homeassistant/components/dlna_dms/translations/pl.json create mode 100644 homeassistant/components/dlna_dms/translations/ru.json create mode 100644 homeassistant/components/dlna_dms/translations/zh-Hant.json create mode 100644 homeassistant/components/pure_energie/translations/de.json create mode 100644 homeassistant/components/radio_browser/translations/de.json create mode 100644 homeassistant/components/radio_browser/translations/hu.json create mode 100644 homeassistant/components/radio_browser/translations/ja.json create mode 100644 homeassistant/components/radio_browser/translations/no.json create mode 100644 homeassistant/components/radio_browser/translations/pl.json create mode 100644 homeassistant/components/radio_browser/translations/zh-Hant.json diff --git a/homeassistant/components/binary_sensor/translations/de.json b/homeassistant/components/binary_sensor/translations/de.json index d3f2bd7fb7fc52..1d6257c48c6a0e 100644 --- a/homeassistant/components/binary_sensor/translations/de.json +++ b/homeassistant/components/binary_sensor/translations/de.json @@ -134,6 +134,10 @@ "off": "L\u00e4dt nicht", "on": "L\u00e4dt" }, + "carbon_monoxide": { + "off": "Normal", + "on": "Erkannt" + }, "co": { "off": "Normal", "on": "Erkannt" diff --git a/homeassistant/components/binary_sensor/translations/et.json b/homeassistant/components/binary_sensor/translations/et.json index 241784aeea00ec..92b4e52952d8a4 100644 --- a/homeassistant/components/binary_sensor/translations/et.json +++ b/homeassistant/components/binary_sensor/translations/et.json @@ -134,6 +134,10 @@ "off": "Ei lae", "on": "Laeb" }, + "carbon_monoxide": { + "off": "Korras", + "on": "Tuvastatud" + }, "co": { "off": "Puudub", "on": "Tuvastatud" diff --git a/homeassistant/components/binary_sensor/translations/hu.json b/homeassistant/components/binary_sensor/translations/hu.json index d95b02d3c8b01e..3690c1def1effb 100644 --- a/homeassistant/components/binary_sensor/translations/hu.json +++ b/homeassistant/components/binary_sensor/translations/hu.json @@ -134,6 +134,10 @@ "off": "Nem t\u00f6lt\u0151dik", "on": "T\u00f6lt\u0151dik" }, + "carbon_monoxide": { + "off": "Norm\u00e1l", + "on": "\u00c9szlelve" + }, "co": { "off": "Tiszta", "on": "\u00c9rz\u00e9kelve" diff --git a/homeassistant/components/binary_sensor/translations/ja.json b/homeassistant/components/binary_sensor/translations/ja.json index 79c2222a6da0da..541c507396136d 100644 --- a/homeassistant/components/binary_sensor/translations/ja.json +++ b/homeassistant/components/binary_sensor/translations/ja.json @@ -134,6 +134,10 @@ "off": "\u5145\u96fb\u3057\u3066\u3044\u306a\u3044", "on": "\u5145\u96fb" }, + "carbon_monoxide": { + "off": "\u30af\u30ea\u30a2", + "on": "\u691c\u51fa" + }, "co": { "off": "\u30af\u30ea\u30a2", "on": "\u691c\u51fa\u3055\u308c\u307e\u3057\u305f" diff --git a/homeassistant/components/binary_sensor/translations/no.json b/homeassistant/components/binary_sensor/translations/no.json index a014b252144bee..62cf2d7cc1b508 100644 --- a/homeassistant/components/binary_sensor/translations/no.json +++ b/homeassistant/components/binary_sensor/translations/no.json @@ -134,6 +134,10 @@ "off": "Lader ikke", "on": "Lader" }, + "carbon_monoxide": { + "off": "Klart", + "on": "Oppdaget" + }, "co": { "off": "Klart", "on": "Oppdaget" diff --git a/homeassistant/components/binary_sensor/translations/pl.json b/homeassistant/components/binary_sensor/translations/pl.json index 55e2dee35cd9ff..d318968da56638 100644 --- a/homeassistant/components/binary_sensor/translations/pl.json +++ b/homeassistant/components/binary_sensor/translations/pl.json @@ -134,6 +134,10 @@ "off": "roz\u0142adowywanie", "on": "\u0142adowanie" }, + "carbon_monoxide": { + "off": "brak", + "on": "wykryto" + }, "co": { "off": "brak", "on": "wykryto" diff --git a/homeassistant/components/binary_sensor/translations/ru.json b/homeassistant/components/binary_sensor/translations/ru.json index 45c73269a720f4..cc9573e2b14f93 100644 --- a/homeassistant/components/binary_sensor/translations/ru.json +++ b/homeassistant/components/binary_sensor/translations/ru.json @@ -134,6 +134,10 @@ "off": "\u041d\u0435 \u0437\u0430\u0440\u044f\u0436\u0430\u0435\u0442\u0441\u044f", "on": "\u0417\u0430\u0440\u044f\u0436\u0430\u0435\u0442\u0441\u044f" }, + "carbon_monoxide": { + "off": "\u041d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d", + "on": "\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d" + }, "co": { "off": "\u041d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d", "on": "\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d" diff --git a/homeassistant/components/binary_sensor/translations/zh-Hant.json b/homeassistant/components/binary_sensor/translations/zh-Hant.json index 4c14fb93fe7376..a0adaaab083f95 100644 --- a/homeassistant/components/binary_sensor/translations/zh-Hant.json +++ b/homeassistant/components/binary_sensor/translations/zh-Hant.json @@ -134,6 +134,10 @@ "off": "\u672a\u5728\u5145\u96fb", "on": "\u5145\u96fb\u4e2d" }, + "carbon_monoxide": { + "off": "\u672a\u89f8\u767c", + "on": "\u5df2\u89f8\u767c" + }, "co": { "off": "\u672a\u5075\u6e2c", "on": "\u5075\u6e2c" diff --git a/homeassistant/components/diagnostics/translations/fr.json b/homeassistant/components/diagnostics/translations/fr.json index cfa7ba1e7553c8..f5936aced5b624 100644 --- a/homeassistant/components/diagnostics/translations/fr.json +++ b/homeassistant/components/diagnostics/translations/fr.json @@ -1,3 +1,3 @@ { - "title": "Diagnostiques" + "title": "Diagnostics" } \ No newline at end of file diff --git a/homeassistant/components/dlna_dms/translations/ca.json b/homeassistant/components/dlna_dms/translations/ca.json new file mode 100644 index 00000000000000..f5cdd4cc4415cc --- /dev/null +++ b/homeassistant/components/dlna_dms/translations/ca.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat", + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", + "bad_ssdp": "Falta un valor necessari a les dades SSDP", + "no_devices_found": "No s'han trobat dispositius a la xarxa", + "not_dms": "El dispositiu no \u00e9s un servidor multim\u00e8dia compatible" + }, + "flow_title": "{name}", + "step": { + "confirm": { + "description": "Vols comen\u00e7ar la configuraci\u00f3?" + }, + "user": { + "data": { + "host": "Amfitri\u00f3" + }, + "description": "Tria un dispositiu a configurar", + "title": "Dispositius DLNA DMA descoberts" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dlna_dms/translations/de.json b/homeassistant/components/dlna_dms/translations/de.json new file mode 100644 index 00000000000000..ebe1946707ef9f --- /dev/null +++ b/homeassistant/components/dlna_dms/translations/de.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "bad_ssdp": "In den SSDP-Daten fehlt ein erforderlicher Wert", + "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden", + "not_dms": "Das Ger\u00e4t ist kein unterst\u00fctzter Medienserver" + }, + "flow_title": "{name}", + "step": { + "confirm": { + "description": "M\u00f6chtest Du mit der Einrichtung beginnen?" + }, + "user": { + "data": { + "host": "Host" + }, + "description": "W\u00e4hle ein zu konfigurierendes Ger\u00e4t aus", + "title": "Erkannte DLNA DMA-Ger\u00e4te" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dlna_dms/translations/el.json b/homeassistant/components/dlna_dms/translations/el.json new file mode 100644 index 00000000000000..94d14f7536061d --- /dev/null +++ b/homeassistant/components/dlna_dms/translations/el.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", + "bad_ssdp": "\u0391\u03c0\u03cc \u03c4\u03b1 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 SSDP \u03bb\u03b5\u03af\u03c0\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7 \u03c4\u03b9\u03bc\u03ae", + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", + "not_dms": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03bf\u03c2 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 \u03c0\u03bf\u03bb\u03c5\u03bc\u03ad\u03c3\u03c9\u03bd" + }, + "flow_title": "{name}", + "step": { + "confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;" + }, + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b3\u03b9\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7", + "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 DLNA DMA" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dlna_dms/translations/et.json b/homeassistant/components/dlna_dms/translations/et.json new file mode 100644 index 00000000000000..744d4ad54a71be --- /dev/null +++ b/homeassistant/components/dlna_dms/translations/et.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "already_in_progress": "Seadistamine juba k\u00e4ib", + "bad_ssdp": "SSDP andmetes puudub n\u00f5utav v\u00e4\u00e4rtus", + "no_devices_found": "V\u00f5rgust ei leitud \u00fchtegi seadet", + "not_dms": "Seade ei ole toetatud meediaserver" + }, + "flow_title": "{name}", + "step": { + "confirm": { + "description": "Kas soovid alustada seadistamist?" + }, + "user": { + "data": { + "host": "Host" + }, + "description": "Vali h\u00e4\u00e4lestatav seade", + "title": "Avastatud DLNA DMA-seadmed" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dlna_dms/translations/hu.json b/homeassistant/components/dlna_dms/translations/hu.json new file mode 100644 index 00000000000000..8c645d42aa8765 --- /dev/null +++ b/homeassistant/components/dlna_dms/translations/hu.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "bad_ssdp": "Az SSDP-adatok hi\u00e1nyosak", + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "not_dms": "A m\u00e9diaszerver eszk\u00f6z nem t\u00e1mogatott" + }, + "flow_title": "{name}", + "step": { + "confirm": { + "description": "El szeretn\u00e9 kezdeni a be\u00e1ll\u00edt\u00e1st?" + }, + "user": { + "data": { + "host": "C\u00edm" + }, + "description": "V\u00e1lasszon egy konfigur\u00e1land\u00f3 eszk\u00f6zt", + "title": "Felfedezett DLNA DMA eszk\u00f6z\u00f6k" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dlna_dms/translations/ja.json b/homeassistant/components/dlna_dms/translations/ja.json new file mode 100644 index 00000000000000..c7f8f5c1587ca2 --- /dev/null +++ b/homeassistant/components/dlna_dms/translations/ja.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", + "already_in_progress": "\u69cb\u6210\u30d5\u30ed\u30fc\u306f\u3059\u3067\u306b\u9032\u884c\u4e2d\u3067\u3059", + "bad_ssdp": "SSDP\u30c7\u30fc\u30bf\u306b\u5fc5\u8981\u306a\u5024\u304c\u3042\u308a\u307e\u305b\u3093", + "no_devices_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u4e0a\u306b\u30c7\u30d0\u30a4\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093", + "not_dms": "\u30c7\u30d0\u30a4\u30b9\u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u308b\u30e1\u30c7\u30a3\u30a2\u30b5\u30fc\u30d0\u30fc\u3067\u306f\u3042\u308a\u307e\u305b\u3093" + }, + "flow_title": "{name}", + "step": { + "confirm": { + "description": "\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3092\u958b\u59cb\u3057\u307e\u3059\u304b\uff1f" + }, + "user": { + "data": { + "host": "\u30db\u30b9\u30c8" + }, + "description": "\u8a2d\u5b9a\u3059\u308b\u30c7\u30d0\u30a4\u30b9\u3092\u9078\u629e", + "title": "\u691c\u51fa\u3055\u308c\u305fDLNA DMA\u30c7\u30d0\u30a4\u30b9" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dlna_dms/translations/no.json b/homeassistant/components/dlna_dms/translations/no.json new file mode 100644 index 00000000000000..3b36e3c8b3aba1 --- /dev/null +++ b/homeassistant/components/dlna_dms/translations/no.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", + "bad_ssdp": "SSDP-data mangler en n\u00f8dvendig verdi", + "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket", + "not_dms": "Enheten er ikke en st\u00f8ttet medieserver" + }, + "flow_title": "{name}", + "step": { + "confirm": { + "description": "Vil du starte oppsettet?" + }, + "user": { + "data": { + "host": "Vert" + }, + "description": "Velg en enhet \u00e5 konfigurere", + "title": "Oppdaget DLNA DMA-enheter" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dlna_dms/translations/pl.json b/homeassistant/components/dlna_dms/translations/pl.json new file mode 100644 index 00000000000000..bd7407f80b689d --- /dev/null +++ b/homeassistant/components/dlna_dms/translations/pl.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "already_in_progress": "Konfiguracja jest ju\u017c w toku", + "bad_ssdp": "W danych SSDP brakuje wymaganej warto\u015bci", + "no_devices_found": "Nie znaleziono urz\u0105dze\u0144 w sieci", + "not_dms": "Urz\u0105dzenie nie jest obs\u0142ugiwanym serwerem multimedi\u00f3w" + }, + "flow_title": "{name}", + "step": { + "confirm": { + "description": "Czy chcesz rozpocz\u0105\u0107 konfiguracj\u0119?" + }, + "user": { + "data": { + "host": "Nazwa hosta lub adres IP" + }, + "description": "Wybierz urz\u0105dzenie do skonfigurowania", + "title": "Wykryto urz\u0105dzenia DLNA DMA" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dlna_dms/translations/ru.json b/homeassistant/components/dlna_dms/translations/ru.json new file mode 100644 index 00000000000000..52a8ad0ee14376 --- /dev/null +++ b/homeassistant/components/dlna_dms/translations/ru.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.", + "already_in_progress": "\u041f\u0440\u043e\u0446\u0435\u0441\u0441 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "bad_ssdp": "\u0412 \u0434\u0430\u043d\u043d\u044b\u0445 SSDP \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435.", + "no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0441\u0435\u0442\u0438.", + "not_dms": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 Media Server." + }, + "flow_title": "{name}", + "step": { + "confirm": { + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443?" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438.", + "title": "\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 DLNA DMA" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dlna_dms/translations/zh-Hant.json b/homeassistant/components/dlna_dms/translations/zh-Hant.json new file mode 100644 index 00000000000000..2f06619c006020 --- /dev/null +++ b/homeassistant/components/dlna_dms/translations/zh-Hant.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", + "bad_ssdp": "\u6240\u7f3a\u5c11\u7684 SSDP \u8cc7\u6599\u70ba\u5fc5\u9808\u6578\u503c", + "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e", + "not_dms": "\u88dd\u7f6e\u4e26\u975e\u652f\u63f4\u5a92\u9ad4\u4f3a\u670d\u5668" + }, + "flow_title": "{name}", + "step": { + "confirm": { + "description": "\u662f\u5426\u8981\u958b\u59cb\u8a2d\u5b9a\uff1f" + }, + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef" + }, + "description": "\u9078\u64c7\u88dd\u7f6e\u4ee5\u8a2d\u5b9a", + "title": "\u5df2\u63a2\u7d22\u5230\u7684 DLNA DMA \u88dd\u7f6e" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iss/translations/fr.json b/homeassistant/components/iss/translations/fr.json index 4dee8082dbfd84..fd0b2adba422f9 100644 --- a/homeassistant/components/iss/translations/fr.json +++ b/homeassistant/components/iss/translations/fr.json @@ -12,5 +12,14 @@ "description": "Voulez-vous configurer la Station spatiale internationale?" } } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "Montrer sur la carte" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/moon/translations/sensor.el.json b/homeassistant/components/moon/translations/sensor.el.json index 5f704f70850624..59b00e329afb39 100644 --- a/homeassistant/components/moon/translations/sensor.el.json +++ b/homeassistant/components/moon/translations/sensor.el.json @@ -4,7 +4,11 @@ "first_quarter": "\u03a0\u03c1\u03ce\u03c4\u03bf \u03c4\u03ad\u03c4\u03b1\u03c1\u03c4\u03bf", "full_moon": "\u03a0\u03b1\u03bd\u03c3\u03ad\u03bb\u03b7\u03bd\u03bf\u03c2", "last_quarter": "\u03a4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03bf \u03c4\u03ad\u03c4\u03b1\u03c1\u03c4\u03bf", - "new_moon": "\u039d\u03ad\u03b1 \u03a3\u03b5\u03bb\u03ae\u03bd\u03b7" + "new_moon": "\u039d\u03ad\u03b1 \u03a3\u03b5\u03bb\u03ae\u03bd\u03b7", + "waning_crescent": "\u03a6\u03b8\u03af\u03bd\u03c9\u03bd \u039c\u03b7\u03bd\u03af\u03c3\u03ba\u03bf\u03c2", + "waning_gibbous": "\u03a6\u03b8\u03af\u03bd\u03c9\u03bd \u0391\u03bc\u03c6\u03af\u03ba\u03c5\u03c1\u03c4\u03bf\u03c2", + "waxing_crescent": "\u0391\u03cd\u03be\u03c9\u03bd \u039c\u03b7\u03bd\u03af\u03c3\u03ba\u03bf\u03c2", + "waxing_gibbous": "\u0391\u03cd\u03be\u03c9\u03bd \u0391\u03bc\u03c6\u03af\u03ba\u03c5\u03c1\u03c4\u03bf\u03c2" } } } \ No newline at end of file diff --git a/homeassistant/components/mqtt/translations/el.json b/homeassistant/components/mqtt/translations/el.json index 233b6a995b3861..6921604bdee00f 100644 --- a/homeassistant/components/mqtt/translations/el.json +++ b/homeassistant/components/mqtt/translations/el.json @@ -69,8 +69,14 @@ "options": { "data": { "birth_enable": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03bf\u03c2 birth", + "birth_payload": "\u03a9\u03c6\u03ad\u03bb\u03b9\u03bc\u03bf \u03c6\u03bf\u03c1\u03c4\u03af\u03bf \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03bf\u03c2 birth", + "birth_qos": "QoS \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03bf\u03c2 birth", + "birth_retain": "\u0394\u03b9\u03b1\u03c4\u03ae\u03c1\u03b7\u03c3\u03b7 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03bf\u03c2 birth", + "birth_topic": "\u0398\u03ad\u03bc\u03b1 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03bf\u03c2 birth", "discovery": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7\u03c2", "will_enable": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03bf\u03c2 will", + "will_payload": "\u03a9\u03c6\u03ad\u03bb\u03b9\u03bc\u03bf \u03c6\u03bf\u03c1\u03c4\u03af\u03bf \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03bf\u03c2 will", + "will_qos": "QoS \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03bf\u03c2 will", "will_retain": "\u0394\u03b9\u03b1\u03c4\u03ae\u03c1\u03b7\u03c3\u03b7 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03bf\u03c2 will", "will_topic": "\u0398\u03ad\u03bc\u03b1 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03bf\u03c2 will" }, diff --git a/homeassistant/components/pure_energie/translations/de.json b/homeassistant/components/pure_energie/translations/de.json new file mode 100644 index 00000000000000..6aafb35d5f92c8 --- /dev/null +++ b/homeassistant/components/pure_energie/translations/de.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen" + }, + "flow_title": "{model} ({host})", + "step": { + "user": { + "data": { + "host": "Host" + } + }, + "zeroconf_confirm": { + "description": "M\u00f6chtest du Pure Energie Meter (` {model} `) zu Home Assistant hinzuf\u00fcgen?", + "title": "Pure Energie Meter-Ger\u00e4t entdeckt" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/radio_browser/translations/de.json b/homeassistant/components/radio_browser/translations/de.json new file mode 100644 index 00000000000000..094e66dd3f5a1c --- /dev/null +++ b/homeassistant/components/radio_browser/translations/de.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "step": { + "user": { + "description": "M\u00f6chtest du den Radio-Browser zu Home Assistant hinzuf\u00fcgen?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/radio_browser/translations/hu.json b/homeassistant/components/radio_browser/translations/hu.json new file mode 100644 index 00000000000000..fbc52f3b1de791 --- /dev/null +++ b/homeassistant/components/radio_browser/translations/hu.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "step": { + "user": { + "description": "Szeretn\u00e9 hozz\u00e1adni Home Assistanthoz: Radio Browser?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/radio_browser/translations/ja.json b/homeassistant/components/radio_browser/translations/ja.json new file mode 100644 index 00000000000000..24b32e6e30a9f2 --- /dev/null +++ b/homeassistant/components/radio_browser/translations/ja.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002" + }, + "step": { + "user": { + "description": "Home Assistant\u306b\u3001Radio Browser\u3092\u8ffd\u52a0\u3057\u307e\u3059\u304b\uff1f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/radio_browser/translations/no.json b/homeassistant/components/radio_browser/translations/no.json new file mode 100644 index 00000000000000..8646b43508eb7d --- /dev/null +++ b/homeassistant/components/radio_browser/translations/no.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." + }, + "step": { + "user": { + "description": "Vil du legge til Radio Browser til Home Assistant?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/radio_browser/translations/pl.json b/homeassistant/components/radio_browser/translations/pl.json new file mode 100644 index 00000000000000..903848b73eadba --- /dev/null +++ b/homeassistant/components/radio_browser/translations/pl.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." + }, + "step": { + "user": { + "description": "Czy chcesz doda\u0107 radia internetowe do Home Assistanta?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/radio_browser/translations/zh-Hant.json b/homeassistant/components/radio_browser/translations/zh-Hant.json new file mode 100644 index 00000000000000..a826b3311938d6 --- /dev/null +++ b/homeassistant/components/radio_browser/translations/zh-Hant.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + }, + "step": { + "user": { + "description": "\u662f\u5426\u8981\u5ee3\u64ad\u700f\u89bd\u5668\u65b0\u589e\u81f3 Home Assistant\uff1f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sense/translations/de.json b/homeassistant/components/sense/translations/de.json index d0290abdf981ed..5c7002aaa2282e 100644 --- a/homeassistant/components/sense/translations/de.json +++ b/homeassistant/components/sense/translations/de.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Ger\u00e4t ist bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", @@ -9,6 +10,13 @@ "unknown": "Unerwarteter Fehler" }, "step": { + "reauth_validate": { + "data": { + "password": "Passwort" + }, + "description": "Die Sense-Integration muss dein Konto {email} erneut authentifizieren.", + "title": "Integration erneut authentifizieren" + }, "user": { "data": { "email": "E-Mail", @@ -16,6 +24,12 @@ "timeout": "Zeit\u00fcberschreitung" }, "title": "Stelle eine Verbindung zu deinem Sense Energy Monitor her" + }, + "validation": { + "data": { + "code": "Verifizierungs-Code" + }, + "title": "Sense Multi-Faktor-Authentifizierung" } } } diff --git a/homeassistant/components/sense/translations/el.json b/homeassistant/components/sense/translations/el.json index e735bf09f7dcaa..b70057311722a9 100644 --- a/homeassistant/components/sense/translations/el.json +++ b/homeassistant/components/sense/translations/el.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", @@ -9,6 +10,13 @@ "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { + "reauth_validate": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, + "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 Sense \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03bb\u03ad\u03b3\u03be\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03b7\u03bd \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03c4\u03bf\u03c5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03c3\u03b1\u03c2 {email} .", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" + }, "user": { "data": { "email": "Email", @@ -16,6 +24,12 @@ "timeout": "\u03a7\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03cc\u03c1\u03b9\u03bf" }, "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf Sense Energy Monitor" + }, + "validation": { + "data": { + "code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03c0\u03b1\u03bb\u03ae\u03b8\u03b5\u03c5\u03c3\u03b7\u03c2" + }, + "title": "\u0388\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ce\u03bd \u03c0\u03b1\u03c1\u03b1\u03b3\u03cc\u03bd\u03c4\u03c9\u03bd Sense" } } } diff --git a/homeassistant/components/sense/translations/et.json b/homeassistant/components/sense/translations/et.json index 8438be5c677f18..1d0b64b5054eda 100644 --- a/homeassistant/components/sense/translations/et.json +++ b/homeassistant/components/sense/translations/et.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "reauth_successful": "Taastuvastamine \u00f5nnestus" }, "error": { "cannot_connect": "\u00dchendamine nurjus", @@ -9,6 +10,13 @@ "unknown": "Tundmatu viga" }, "step": { + "reauth_validate": { + "data": { + "password": "Salas\u00f5na" + }, + "description": "Sense'i sidumine peab konto {email} uuesti autentima.", + "title": "Taastuvasta sidumine" + }, "user": { "data": { "email": "E-post", @@ -16,6 +24,12 @@ "timeout": "Ajal\u00f5pp" }, "title": "\u00dchendu oma Sense Energy Monitor'iga" + }, + "validation": { + "data": { + "code": "Kinnituskood" + }, + "title": "Sense mitmeastmeline autentimine" } } } diff --git a/homeassistant/components/sense/translations/hu.json b/homeassistant/components/sense/translations/hu.json index 9defa2971bb509..28dc6dbb00c291 100644 --- a/homeassistant/components/sense/translations/hu.json +++ b/homeassistant/components/sense/translations/hu.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt." }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", @@ -9,6 +10,13 @@ "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { + "reauth_validate": { + "data": { + "password": "Jelsz\u00f3" + }, + "description": "A Sense integr\u00e1ci\u00f3nak \u00fajra kell hiteles\u00edtenie fi\u00f3kj\u00e1t {email} .", + "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" + }, "user": { "data": { "email": "E-mail", @@ -16,6 +24,12 @@ "timeout": "Id\u0151t\u00fall\u00e9p\u00e9s" }, "title": "Csatlakoztassa a Sense Energy Monitort" + }, + "validation": { + "data": { + "code": "Ellen\u0151rz\u0151 k\u00f3d" + }, + "title": "Sense t\u00f6bbfaktoros hiteles\u00edt\u00e9s" } } } diff --git a/homeassistant/components/sense/translations/ja.json b/homeassistant/components/sense/translations/ja.json index 60fe4c88e20067..437ce96d9f19ec 100644 --- a/homeassistant/components/sense/translations/ja.json +++ b/homeassistant/components/sense/translations/ja.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" + "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", + "reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f" }, "error": { "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", @@ -9,6 +10,13 @@ "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, "step": { + "reauth_validate": { + "data": { + "password": "\u30d1\u30b9\u30ef\u30fc\u30c9" + }, + "description": "Sense\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3067\u306f\u3001\u30a2\u30ab\u30a6\u30f3\u30c8 {email} \u3092\u518d\u8a8d\u8a3c\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002", + "title": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u518d\u8a8d\u8a3c" + }, "user": { "data": { "email": "E\u30e1\u30fc\u30eb", @@ -16,6 +24,12 @@ "timeout": "\u30bf\u30a4\u30e0\u30a2\u30a6\u30c8" }, "title": "Sense Energy Monitor\u306b\u63a5\u7d9a\u3059\u308b" + }, + "validation": { + "data": { + "code": "\u8a8d\u8a3c\u30b3\u30fc\u30c9" + }, + "title": "Sense\u591a\u8981\u7d20\u8a8d\u8a3c" } } } diff --git a/homeassistant/components/sense/translations/no.json b/homeassistant/components/sense/translations/no.json index 11f92bfccb4628..004580b51926eb 100644 --- a/homeassistant/components/sense/translations/no.json +++ b/homeassistant/components/sense/translations/no.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Enheten er allerede konfigurert" + "already_configured": "Enheten er allerede konfigurert", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" }, "error": { "cannot_connect": "Tilkobling mislyktes", @@ -9,6 +10,13 @@ "unknown": "Uventet feil" }, "step": { + "reauth_validate": { + "data": { + "password": "Passord" + }, + "description": "Sense-integrasjonen m\u00e5 autentisere kontoen din {email} p\u00e5 nytt.", + "title": "Godkjenne integrering p\u00e5 nytt" + }, "user": { "data": { "email": "E-post", @@ -16,6 +24,12 @@ "timeout": "Tidsavbrudd" }, "title": "Koble til din Sense Energy Monitor" + }, + "validation": { + "data": { + "code": "Bekreftelseskode" + }, + "title": "Sense multi-faktor autentisering" } } } diff --git a/homeassistant/components/sense/translations/pl.json b/homeassistant/components/sense/translations/pl.json index 8bc58118a232b1..86dcb69f4c669d 100644 --- a/homeassistant/components/sense/translations/pl.json +++ b/homeassistant/components/sense/translations/pl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", @@ -9,6 +10,13 @@ "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { + "reauth_validate": { + "data": { + "password": "Has\u0142o" + }, + "description": "Integracja Sense wymaga ponownego uwierzytelnienia Twojego konta {email}.", + "title": "Ponownie uwierzytelnij integracj\u0119" + }, "user": { "data": { "email": "Adres e-mail", @@ -16,6 +24,12 @@ "timeout": "Limit czasu" }, "title": "Po\u0142\u0105czenie z monitorem energii Sense" + }, + "validation": { + "data": { + "code": "Kod weryfikacyjny" + }, + "title": "Uwierzytelnianie wielosk\u0142adnikowe Sense" } } } diff --git a/homeassistant/components/sense/translations/ru.json b/homeassistant/components/sense/translations/ru.json index c113c06a0218b5..9b14769b839715 100644 --- a/homeassistant/components/sense/translations/ru.json +++ b/homeassistant/components/sense/translations/ru.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant." + "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.", + "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." }, "error": { "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", @@ -9,6 +10,13 @@ "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "step": { + "reauth_validate": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Sense {email}.", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f" + }, "user": { "data": { "email": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b", @@ -16,6 +24,12 @@ "timeout": "\u0422\u0430\u0439\u043c-\u0430\u0443\u0442" }, "title": "Sense Energy Monitor" + }, + "validation": { + "data": { + "code": "\u041a\u043e\u0434 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f" + }, + "title": "\u041c\u043d\u043e\u0433\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f Sense" } } } diff --git a/homeassistant/components/sense/translations/zh-Hant.json b/homeassistant/components/sense/translations/zh-Hant.json index 5ca9a9f847d91a..720db0d1bd8d9d 100644 --- a/homeassistant/components/sense/translations/zh-Hant.json +++ b/homeassistant/components/sense/translations/zh-Hant.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", @@ -9,6 +10,13 @@ "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { + "reauth_validate": { + "data": { + "password": "\u5bc6\u78bc" + }, + "description": "Sense \u6574\u5408\u9700\u8981\u91cd\u65b0\u8a8d\u8b49\u60a8\u7684\u5e33\u865f {email}\u3002", + "title": "\u91cd\u65b0\u8a8d\u8b49\u6574\u5408" + }, "user": { "data": { "email": "\u96fb\u5b50\u90f5\u4ef6", @@ -16,6 +24,12 @@ "timeout": "\u903e\u6642" }, "title": "\u9023\u7dda\u81f3 Sense \u80fd\u6e90\u76e3\u63a7" + }, + "validation": { + "data": { + "code": "\u9a57\u8b49\u78bc" + }, + "title": "Sense \u591a\u6b65\u9a5f\u9a57\u8b49" } } } diff --git a/homeassistant/components/sonarr/translations/ca.json b/homeassistant/components/sonarr/translations/ca.json index 10930df8525385..d3cb57208752e6 100644 --- a/homeassistant/components/sonarr/translations/ca.json +++ b/homeassistant/components/sonarr/translations/ca.json @@ -12,7 +12,7 @@ "flow_title": "{name}", "step": { "reauth_confirm": { - "description": "La integraci\u00f3 de Sonarr ha de tornar a autenticar-se manualment amb l'API de Sonarr allotjada a: {host}", + "description": "La integraci\u00f3 de Sonarr ha de tornar a autenticar-se manualment amb l'API de Sonarr allotjada a: {url}", "title": "Reautenticaci\u00f3 de la integraci\u00f3" }, "user": { @@ -22,6 +22,7 @@ "host": "Amfitri\u00f3", "port": "Port", "ssl": "Utilitza un certificat SSL", + "url": "URL", "verify_ssl": "Verifica el certificat SSL" } } diff --git a/homeassistant/components/sonarr/translations/de.json b/homeassistant/components/sonarr/translations/de.json index 9779a9850344a1..eb521c1c2375f6 100644 --- a/homeassistant/components/sonarr/translations/de.json +++ b/homeassistant/components/sonarr/translations/de.json @@ -12,7 +12,7 @@ "flow_title": "{name}", "step": { "reauth_confirm": { - "description": "Die Sonarr-Integration muss manuell mit der Sonarr-API, die unter {host} gehostet wird, neu authentifiziert werden", + "description": "Die Sonarr-Integration muss manuell erneut mit der Sonarr-API authentifiziert werden, die unter folgender Adresse gehostet wird: {url}", "title": "Integration erneut authentifizieren" }, "user": { @@ -22,6 +22,7 @@ "host": "Host", "port": "Port", "ssl": "Verwendet ein SSL-Zertifikat", + "url": "URL", "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" } } diff --git a/homeassistant/components/sonarr/translations/el.json b/homeassistant/components/sonarr/translations/el.json index b8f1bab24bbdf5..a348d76f798950 100644 --- a/homeassistant/components/sonarr/translations/el.json +++ b/homeassistant/components/sonarr/translations/el.json @@ -22,6 +22,7 @@ "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "port": "\u0398\u03cd\u03c1\u03b1", "ssl": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03ad\u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL", + "url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL" } } diff --git a/homeassistant/components/sonarr/translations/en.json b/homeassistant/components/sonarr/translations/en.json index 74232e9f8b793f..f676005dcfe428 100644 --- a/homeassistant/components/sonarr/translations/en.json +++ b/homeassistant/components/sonarr/translations/en.json @@ -12,7 +12,7 @@ "flow_title": "{name}", "step": { "reauth_confirm": { - "description": "The Sonarr integration needs to be manually re-authenticated with the Sonarr API hosted at: {host}", + "description": "The Sonarr integration needs to be manually re-authenticated with the Sonarr API hosted at: {url}", "title": "Reauthenticate Integration" }, "user": { @@ -22,6 +22,7 @@ "host": "Host", "port": "Port", "ssl": "Uses an SSL certificate", + "url": "URL", "verify_ssl": "Verify SSL certificate" } } diff --git a/homeassistant/components/sonarr/translations/pt-BR.json b/homeassistant/components/sonarr/translations/pt-BR.json index f6b8b63781a3d0..4c474ef2349d13 100644 --- a/homeassistant/components/sonarr/translations/pt-BR.json +++ b/homeassistant/components/sonarr/translations/pt-BR.json @@ -12,7 +12,7 @@ "flow_title": "{name}", "step": { "reauth_confirm": { - "description": "A integra\u00e7\u00e3o do Sonarr precisa ser autenticada manualmente novamente com a API do Sonarr hospedada em: {host}", + "description": "A integra\u00e7\u00e3o do Sonarr precisa ser autenticada manualmente novamente com a API do Sonarr hospedada em: {url}", "title": "Reautenticar Integra\u00e7\u00e3o" }, "user": { @@ -22,6 +22,7 @@ "host": "Nome do host", "port": "Porta", "ssl": "Usar um certificado SSL", + "url": "URL", "verify_ssl": "Verifique o certificado SSL" } } diff --git a/homeassistant/components/wolflink/translations/sensor.el.json b/homeassistant/components/wolflink/translations/sensor.el.json index e05ea2942f2201..4b7813b0c7405e 100644 --- a/homeassistant/components/wolflink/translations/sensor.el.json +++ b/homeassistant/components/wolflink/translations/sensor.el.json @@ -39,11 +39,13 @@ "kalibration_heizbetrieb": "\u0392\u03b1\u03b8\u03bc\u03bf\u03bd\u03cc\u03bc\u03b7\u03c3\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b8\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7\u03c2", "kalibration_kombibetrieb": "\u0392\u03b1\u03b8\u03bc\u03bf\u03bd\u03cc\u03bc\u03b7\u03c3\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 Combi", "kalibration_warmwasserbetrieb": "\u0392\u03b1\u03b8\u03bc\u03bf\u03bd\u03cc\u03bc\u03b7\u03c3\u03b7 DHW", + "kaskadenbetrieb": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c1\u03c1\u03ac\u03ba\u03c4\u03b7", "kombibetrieb": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 Combi", "kombigerat": "\u039c\u03c0\u03cc\u03b9\u03bb\u03b5\u03c1 Combi", "kombigerat_mit_solareinbindung": "\u039c\u03c0\u03cc\u03b9\u03bb\u03b5\u03c1 Combi \u03bc\u03b5 \u03b7\u03bb\u03b9\u03b1\u03ba\u03ae \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7", "mindest_kombizeit": "\u0395\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf\u03c2 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c2 \u03c3\u03c5\u03bd\u03b4\u03c5\u03b1\u03c3\u03bc\u03bf\u03cd", "nachlauf_heizkreispumpe": "\u0391\u03bd\u03c4\u03bb\u03af\u03b1 \u03ba\u03c5\u03ba\u03bb\u03ce\u03bc\u03b1\u03c4\u03bf\u03c2 \u03b8\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7\u03c2 \u03c3\u03b5 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1", + "nachspulen": "\u039c\u03b5\u03c4\u03ac \u03c4\u03bf \u03be\u03ad\u03c0\u03bb\u03c5\u03bc\u03b1", "nur_heizgerat": "\u039c\u03cc\u03bd\u03bf \u03bb\u03ad\u03b2\u03b7\u03c4\u03b1\u03c2", "parallelbetrieb": "\u03a0\u03b1\u03c1\u03ac\u03bb\u03bb\u03b7\u03bb\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1", "partymodus": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c0\u03ac\u03c1\u03c4\u03b9", @@ -67,6 +69,7 @@ "standby": "\u039a\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03b1\u03bd\u03b1\u03bc\u03bf\u03bd\u03ae\u03c2", "start": "\u0388\u03bd\u03b1\u03c1\u03be\u03b7", "storung": "\u0392\u03bb\u03ac\u03b2\u03b7", + "taktsperre": "\u0391\u03bd\u03c4\u03b9-\u03ba\u03cd\u03ba\u03bb\u03bf\u03c2", "telefonfernschalter": "\u03a4\u03b7\u03bb\u03b5\u03c6\u03c9\u03bd\u03b9\u03ba\u03cc\u03c2 \u03b1\u03c0\u03bf\u03bc\u03b1\u03ba\u03c1\u03c5\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2 \u03b4\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b7\u03c2", "test": "\u0394\u03bf\u03ba\u03b9\u03bc\u03ae", "tpw": "TPW", From d5a2381f07b2b8174256500a9799901832f377dd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 22 Feb 2022 14:31:41 -1000 Subject: [PATCH 0963/1098] Add diagnostics support to lutron_caseta (#67079) --- .../components/lutron_caseta/diagnostics.py | 31 ++++++++++ tests/components/lutron_caseta/__init__.py | 41 +++++++++++++ .../lutron_caseta/test_config_flow.py | 28 +-------- .../lutron_caseta/test_diagnostics.py | 60 +++++++++++++++++++ 4 files changed, 134 insertions(+), 26 deletions(-) create mode 100644 homeassistant/components/lutron_caseta/diagnostics.py create mode 100644 tests/components/lutron_caseta/test_diagnostics.py diff --git a/homeassistant/components/lutron_caseta/diagnostics.py b/homeassistant/components/lutron_caseta/diagnostics.py new file mode 100644 index 00000000000000..7ae0b5c40a9f63 --- /dev/null +++ b/homeassistant/components/lutron_caseta/diagnostics.py @@ -0,0 +1,31 @@ +"""Diagnostics support for lutron_caseta.""" +from __future__ import annotations + +from typing import Any + +from pylutron_caseta.smartbridge import Smartbridge + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import BRIDGE_LEAP, DOMAIN + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + bridge: Smartbridge = hass.data[DOMAIN][entry.entry_id][BRIDGE_LEAP] + return { + "entry": { + "title": entry.title, + "data": dict(entry.data), + }, + "data": { + "devices": bridge.devices, + "buttons": bridge.buttons, + "scenes": bridge.scenes, + "occupancy_groups": bridge.occupancy_groups, + "areas": bridge.areas, + }, + } diff --git a/tests/components/lutron_caseta/__init__.py b/tests/components/lutron_caseta/__init__.py index 0e0ca8686efeb7..ace4066ae3bb32 100644 --- a/tests/components/lutron_caseta/__init__.py +++ b/tests/components/lutron_caseta/__init__.py @@ -1 +1,42 @@ """Tests for the Lutron Caseta integration.""" + + +class MockBridge: + """Mock Lutron bridge that emulates configured connected status.""" + + def __init__(self, can_connect=True): + """Initialize MockBridge instance with configured mock connectivity.""" + self.can_connect = can_connect + self.is_currently_connected = False + self.buttons = {} + self.areas = {} + self.occupancy_groups = {} + self.scenes = self.get_scenes() + self.devices = self.get_devices() + + async def connect(self): + """Connect the mock bridge.""" + if self.can_connect: + self.is_currently_connected = True + + def is_connected(self): + """Return whether the mock bridge is connected.""" + return self.is_currently_connected + + def get_devices(self): + """Return devices on the bridge.""" + return { + "1": {"serial": 1234, "name": "bridge", "model": "model", "type": "type"} + } + + def get_devices_by_domain(self, domain): + """Return devices on the bridge.""" + return {} + + def get_scenes(self): + """Return scenes on the bridge.""" + return {} + + async def close(self): + """Close the mock bridge connection.""" + self.is_currently_connected = False diff --git a/tests/components/lutron_caseta/test_config_flow.py b/tests/components/lutron_caseta/test_config_flow.py index 9dbedeacf5bf8e..821bf07cf08825 100644 --- a/tests/components/lutron_caseta/test_config_flow.py +++ b/tests/components/lutron_caseta/test_config_flow.py @@ -20,6 +20,8 @@ ) from homeassistant.const import CONF_HOST +from . import MockBridge + from tests.common import MockConfigEntry ATTR_HOSTNAME = "hostname" @@ -39,32 +41,6 @@ } -class MockBridge: - """Mock Lutron bridge that emulates configured connected status.""" - - def __init__(self, can_connect=True): - """Initialize MockBridge instance with configured mock connectivity.""" - self.can_connect = can_connect - self.is_currently_connected = False - - async def connect(self): - """Connect the mock bridge.""" - if self.can_connect: - self.is_currently_connected = True - - def is_connected(self): - """Return whether the mock bridge is connected.""" - return self.is_currently_connected - - def get_devices(self): - """Return devices on the bridge.""" - return {"1": {"serial": 1234}} - - async def close(self): - """Close the mock bridge connection.""" - self.is_currently_connected = False - - async def test_bridge_import_flow(hass): """Test a bridge entry gets created and set up during the import flow.""" diff --git a/tests/components/lutron_caseta/test_diagnostics.py b/tests/components/lutron_caseta/test_diagnostics.py new file mode 100644 index 00000000000000..89fcb65df9d706 --- /dev/null +++ b/tests/components/lutron_caseta/test_diagnostics.py @@ -0,0 +1,60 @@ +"""Test the Lutron Caseta diagnostics.""" + +from unittest.mock import patch + +from homeassistant.components.lutron_caseta import DOMAIN +from homeassistant.components.lutron_caseta.const import ( + CONF_CA_CERTS, + CONF_CERTFILE, + CONF_KEYFILE, +) +from homeassistant.const import CONF_HOST + +from . import MockBridge + +from tests.common import MockConfigEntry +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_diagnostics(hass, hass_client) -> None: + """Test generating diagnostics for lutron_caseta.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_HOST: "1.1.1.1", + CONF_KEYFILE: "", + CONF_CERTFILE: "", + CONF_CA_CERTS: "", + }, + unique_id="abc", + ) + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.lutron_caseta.Smartbridge.create_tls", + return_value=MockBridge(can_connect=True), + ): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + diag = await get_diagnostics_for_config_entry(hass, hass_client, config_entry) + assert diag == { + "data": { + "areas": {}, + "buttons": {}, + "devices": { + "1": { + "model": "model", + "name": "bridge", + "serial": 1234, + "type": "type", + } + }, + "occupancy_groups": {}, + "scenes": {}, + }, + "entry": { + "data": {"ca_certs": "", "certfile": "", "host": "1.1.1.1", "keyfile": ""}, + "title": "Mock Title", + }, + } From 1658d530e107331c0fdb0596f6714ad5f592f45a Mon Sep 17 00:00:00 2001 From: jjlawren Date: Tue, 22 Feb 2022 18:34:48 -0600 Subject: [PATCH 0964/1098] Add Plex scan_clients button, enable autoscan (#67055) Co-authored-by: Robert Svensson --- homeassistant/components/plex/__init__.py | 15 +++++++ homeassistant/components/plex/button.py | 53 +++++++++++++++++++++++ homeassistant/components/plex/const.py | 5 ++- homeassistant/components/plex/services.py | 5 ++- tests/components/plex/test_button.py | 36 +++++++++++++++ tests/components/plex/test_init.py | 17 ++++++++ 6 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/plex/button.py create mode 100644 tests/components/plex/test_button.py diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index 44bca818333d47..df3a4b8cd11a1e 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -25,10 +25,12 @@ async_dispatcher_connect, async_dispatcher_send, ) +from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.network import is_internal_request from homeassistant.helpers.typing import ConfigType from .const import ( + CLIENT_SCAN_INTERVAL, CONF_SERVER, CONF_SERVER_IDENTIFIER, DISPATCHERS, @@ -247,6 +249,19 @@ def get_plex_account(plex_server): await hass.async_add_executor_job(get_plex_account, plex_server) + @callback + def scheduled_client_scan(_): + _LOGGER.debug("Scheduled scan for new clients on %s", plex_server.friendly_name) + async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) + + entry.async_on_unload( + async_track_time_interval( + hass, + scheduled_client_scan, + CLIENT_SCAN_INTERVAL, + ) + ) + return True diff --git a/homeassistant/components/plex/button.py b/homeassistant/components/plex/button.py new file mode 100644 index 00000000000000..23e8ec103e923a --- /dev/null +++ b/homeassistant/components/plex/button.py @@ -0,0 +1,53 @@ +"""Representation of Plex buttons.""" +from __future__ import annotations + +from homeassistant.components.button import ButtonEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.entity import DeviceInfo, EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import ( + CONF_SERVER, + CONF_SERVER_IDENTIFIER, + DOMAIN, + PLEX_UPDATE_PLATFORMS_SIGNAL, +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Plex button from config entry.""" + server_id: str = config_entry.data[CONF_SERVER_IDENTIFIER] + server_name: str = config_entry.data[CONF_SERVER] + async_add_entities([PlexScanClientsButton(server_id, server_name)]) + + +class PlexScanClientsButton(ButtonEntity): + """Representation of a scan_clients button entity.""" + + _attr_entity_category = EntityCategory.CONFIG + + def __init__(self, server_id: str, server_name: str) -> None: + """Initialize a scan_clients Plex button entity.""" + self.server_id = server_id + self._attr_name = f"Scan Clients ({server_name})" + self._attr_unique_id = f"plex-scan_clients-{self.server_id}" + + async def async_press(self) -> None: + """Press the button.""" + async_dispatcher_send( + self.hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(self.server_id) + ) + + @property + def device_info(self) -> DeviceInfo: + """Return a device description for device registry.""" + return DeviceInfo( + identifiers={(DOMAIN, self.server_id)}, + manufacturer="Plex", + ) diff --git a/homeassistant/components/plex/const.py b/homeassistant/components/plex/const.py index ce28357a4b1927..dea976f46dd567 100644 --- a/homeassistant/components/plex/const.py +++ b/homeassistant/components/plex/const.py @@ -1,4 +1,6 @@ """Constants for the Plex component.""" +from datetime import timedelta + from homeassistant.const import Platform, __version__ DOMAIN = "plex" @@ -12,11 +14,12 @@ PLEXTV_THROTTLE = 60 +CLIENT_SCAN_INTERVAL = timedelta(minutes=10) DEBOUNCE_TIMEOUT = 1 DISPATCHERS = "dispatchers" GDM_DEBOUNCER = "gdm_debouncer" GDM_SCANNER = "gdm_scanner" -PLATFORMS = frozenset([Platform.MEDIA_PLAYER, Platform.SENSOR]) +PLATFORMS = frozenset([Platform.BUTTON, Platform.MEDIA_PLAYER, Platform.SENSOR]) PLATFORMS_COMPLETED = "platforms_completed" PLAYER_SOURCE = "player_source" SERVERS = "servers" diff --git a/homeassistant/components/plex/services.py b/homeassistant/components/plex/services.py index 11d40dbab75a23..0433ba836cd8a8 100644 --- a/homeassistant/components/plex/services.py +++ b/homeassistant/components/plex/services.py @@ -31,7 +31,10 @@ async def async_refresh_library_service(service_call: ServiceCall) -> None: await hass.async_add_executor_job(refresh_library, hass, service_call) async def async_scan_clients_service(_: ServiceCall) -> None: - _LOGGER.debug("Scanning for new Plex clients") + _LOGGER.warning( + "This service is deprecated in favor of the scan_clients button entity. " + "Service calls will still work for now but the service will be removed in a future release" + ) for server_id in hass.data[DOMAIN][SERVERS]: async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) diff --git a/tests/components/plex/test_button.py b/tests/components/plex/test_button.py new file mode 100644 index 00000000000000..b540ba2d031656 --- /dev/null +++ b/tests/components/plex/test_button.py @@ -0,0 +1,36 @@ +"""Tests for Plex buttons.""" +from datetime import timedelta +from unittest.mock import patch + +from homeassistant.components.button.const import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS +from homeassistant.components.plex.const import DEBOUNCE_TIMEOUT +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.util import dt + +from tests.common import async_fire_time_changed + + +async def test_scan_clients_button_schedule(hass, setup_plex_server): + """Test scan_clients button scheduled update.""" + with patch( + "homeassistant.components.plex.server.PlexServer._async_update_platforms" + ) as mock_scan_clients: + await setup_plex_server() + mock_scan_clients.reset_mock() + + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(seconds=DEBOUNCE_TIMEOUT), + ) + + assert await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + { + ATTR_ENTITY_ID: "button.scan_clients_plex_server_1", + }, + True, + ) + await hass.async_block_till_done() + + assert mock_scan_clients.called diff --git a/tests/components/plex/test_init.py b/tests/components/plex/test_init.py index fced4bae58ab31..8e09f7023862e4 100644 --- a/tests/components/plex/test_init.py +++ b/tests/components/plex/test_init.py @@ -276,3 +276,20 @@ async def test_bad_token_with_tokenless_server( # Ensure updates that rely on account return nothing trigger_plex_update(mock_websocket) await hass.async_block_till_done() + + +async def test_scan_clients_schedule(hass, setup_plex_server): + """Test scan_clients scheduled update.""" + with patch( + "homeassistant.components.plex.server.PlexServer._async_update_platforms" + ) as mock_scan_clients: + await setup_plex_server() + mock_scan_clients.reset_mock() + + async_fire_time_changed( + hass, + dt_util.utcnow() + const.CLIENT_SCAN_INTERVAL, + ) + await hass.async_block_till_done() + + assert mock_scan_clients.called From b8590fde40621aa4ab050455f3ed433030c684ec Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Wed, 23 Feb 2022 01:35:22 +0100 Subject: [PATCH 0965/1098] Improve tests of Fritz!Tools (part1) (#66972) --- .coveragerc | 3 - tests/components/fritz/conftest.py | 165 ++--- tests/components/fritz/const.py | 753 ++++++++++++++++++++- tests/components/fritz/test_button.py | 75 ++ tests/components/fritz/test_config_flow.py | 14 +- tests/components/fritz/test_diagnostics.py | 80 +++ tests/components/fritz/test_init.py | 96 +++ 7 files changed, 1077 insertions(+), 109 deletions(-) create mode 100644 tests/components/fritz/test_button.py create mode 100644 tests/components/fritz/test_diagnostics.py create mode 100644 tests/components/fritz/test_init.py diff --git a/.coveragerc b/.coveragerc index 1a75262698844e..1784bdad2f6b9f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -394,13 +394,10 @@ omit = homeassistant/components/freebox/router.py homeassistant/components/freebox/sensor.py homeassistant/components/freebox/switch.py - homeassistant/components/fritz/__init__.py homeassistant/components/fritz/binary_sensor.py - homeassistant/components/fritz/button.py homeassistant/components/fritz/common.py homeassistant/components/fritz/const.py homeassistant/components/fritz/device_tracker.py - homeassistant/components/fritz/diagnostics.py homeassistant/components/fritz/sensor.py homeassistant/components/fritz/services.py homeassistant/components/fritz/switch.py diff --git a/tests/components/fritz/conftest.py b/tests/components/fritz/conftest.py index 1dc60f4a59ee0f..139a8448b08f8f 100644 --- a/tests/components/fritz/conftest.py +++ b/tests/components/fritz/conftest.py @@ -1,115 +1,86 @@ """Common stuff for AVM Fritz!Box tests.""" -from unittest import mock +import logging from unittest.mock import patch +from fritzconnection.core.processor import Service +from fritzconnection.lib.fritzhosts import FritzHosts import pytest +from .const import MOCK_FB_SERVICES, MOCK_MESH_DATA, MOCK_MODELNAME -@pytest.fixture() -def fc_class_mock(): - """Fixture that sets up a mocked FritzConnection class.""" - with patch("fritzconnection.FritzConnection", autospec=True) as result: - result.return_value = FritzConnectionMock() - yield result +LOGGER = logging.getLogger(__name__) + + +class FritzServiceMock(Service): + """Service mocking.""" + + def __init__(self, serviceId: str, actions: dict) -> None: + """Init Service mock.""" + super().__init__() + self._actions = actions + self.serviceId = serviceId class FritzConnectionMock: # pylint: disable=too-few-public-methods """FritzConnection mocking.""" - FRITZBOX_DATA = { - ("WANIPConn:1", "GetStatusInfo"): { - "NewConnectionStatus": "Connected", - "NewUptime": 35307, - }, - ("WANIPConnection:1", "GetStatusInfo"): {}, - ("WANCommonIFC:1", "GetCommonLinkProperties"): { - "NewLayer1DownstreamMaxBitRate": 10087000, - "NewLayer1UpstreamMaxBitRate": 2105000, - "NewPhysicalLinkStatus": "Up", - }, - ("WANCommonIFC:1", "GetAddonInfos"): { - "NewByteSendRate": 3438, - "NewByteReceiveRate": 67649, - "NewTotalBytesSent": 1712232562, - "NewTotalBytesReceived": 5221019883, - }, - ("LANEthernetInterfaceConfig:1", "GetStatistics"): { - "NewBytesSent": 23004321, - "NewBytesReceived": 12045, - }, - ("DeviceInfo:1", "GetInfo"): { - "NewSerialNumber": "abcdefgh", - "NewName": "TheName", - "NewModelName": "FRITZ!Box 7490", - }, - } - - FRITZBOX_DATA_INDEXED = { - ("X_AVM-DE_Homeauto:1", "GetGenericDeviceInfos"): [ - { - "NewSwitchIsValid": "VALID", - "NewMultimeterIsValid": "VALID", - "NewTemperatureIsValid": "VALID", - "NewDeviceId": 16, - "NewAIN": "08761 0114116", - "NewDeviceName": "FRITZ!DECT 200 #1", - "NewTemperatureOffset": "0", - "NewSwitchLock": "0", - "NewProductName": "FRITZ!DECT 200", - "NewPresent": "CONNECTED", - "NewMultimeterPower": 1673, - "NewHkrComfortTemperature": "0", - "NewSwitchMode": "AUTO", - "NewManufacturer": "AVM", - "NewMultimeterIsEnabled": "ENABLED", - "NewHkrIsTemperature": "0", - "NewFunctionBitMask": 2944, - "NewTemperatureIsEnabled": "ENABLED", - "NewSwitchState": "ON", - "NewSwitchIsEnabled": "ENABLED", - "NewFirmwareVersion": "03.87", - "NewHkrSetVentilStatus": "CLOSED", - "NewMultimeterEnergy": 5182, - "NewHkrComfortVentilStatus": "CLOSED", - "NewHkrReduceTemperature": "0", - "NewHkrReduceVentilStatus": "CLOSED", - "NewHkrIsEnabled": "DISABLED", - "NewHkrSetTemperature": "0", - "NewTemperatureCelsius": "225", - "NewHkrIsValid": "INVALID", - }, - {}, - ], - ("Hosts1", "GetGenericHostEntry"): [ - { - "NewSerialNumber": 1234, - "NewName": "TheName", - "NewModelName": "FRITZ!Box 7490", - }, - {}, - ], - } - - MODELNAME = "FRITZ!Box 7490" - - def __init__(self): + def __init__(self, services): """Inint Mocking class.""" - self.modelname = self.MODELNAME - self.call_action = mock.Mock(side_effect=self._side_effect_call_action) - type(self).action_names = mock.PropertyMock( - side_effect=self._side_effect_action_names - ) + self.modelname = MOCK_MODELNAME + self.call_action = self._call_action + self._services = services self.services = { - srv: None - for srv, _ in list(self.FRITZBOX_DATA) + list(self.FRITZBOX_DATA_INDEXED) + srv: FritzServiceMock(serviceId=srv, actions=actions) + for srv, actions in services.items() } + LOGGER.debug("-" * 80) + LOGGER.debug("FritzConnectionMock - services: %s", self.services) + + def _call_action(self, service: str, action: str, **kwargs): + LOGGER.debug( + "_call_action service: %s, action: %s, **kwargs: %s", + service, + action, + {**kwargs}, + ) + if ":" in service: + service, number = service.split(":", 1) + service = service + number + elif not service[-1].isnumeric(): + service = service + "1" - def _side_effect_call_action(self, service, action, **kwargs): if kwargs: - index = next(iter(kwargs.values())) - return self.FRITZBOX_DATA_INDEXED[(service, action)][index] - return self.FRITZBOX_DATA[(service, action)] + if (index := kwargs.get("NewIndex")) is None: + index = next(iter(kwargs.values())) + + return self._services[service][action][index] + return self._services[service][action] + - def _side_effect_action_names(self): - return list(self.FRITZBOX_DATA) + list(self.FRITZBOX_DATA_INDEXED) +class FritzHostMock(FritzHosts): + """FritzHosts mocking.""" + + def get_mesh_topology(self, raw=False): + """Retrurn mocked mesh data.""" + return MOCK_MESH_DATA + + +@pytest.fixture() +def fc_class_mock(): + """Fixture that sets up a mocked FritzConnection class.""" + with patch( + "homeassistant.components.fritz.common.FritzConnection", autospec=True + ) as result: + result.return_value = FritzConnectionMock(MOCK_FB_SERVICES) + yield result + + +@pytest.fixture() +def fh_class_mock(): + """Fixture that sets up a mocked FritzHosts class.""" + with patch( + "homeassistant.components.fritz.common.FritzHosts", + new=FritzHostMock, + ) as result: + yield result diff --git a/tests/components/fritz/const.py b/tests/components/fritz/const.py index 3212794fc85ec9..79d92a1e22de2f 100644 --- a/tests/components/fritz/const.py +++ b/tests/components/fritz/const.py @@ -26,9 +26,758 @@ } } MOCK_HOST = "fake_host" -MOCK_IP = "192.168.178.1" +MOCK_IPS = {"fritz.box": "192.168.178.1", "printer": "192.168.178.2"} +MOCK_MODELNAME = "FRITZ!Box 7530 AX" +MOCK_FIRMWARE = "256.07.29" MOCK_SERIAL_NUMBER = "fake_serial_number" MOCK_FIRMWARE_INFO = [True, "1.1.1"] +MOCK_MESH_SSID = "TestSSID" +MOCK_MESH_MASTER_MAC = "1C:ED:6F:12:34:11" +MOCK_MESH_MASTER_WIFI1_MAC = "1C:ED:6F:12:34:12" +MOCK_MESH_SLAVE_MAC = "1C:ED:6F:12:34:21" +MOCK_MESH_SLAVE_WIFI1_MAC = "1C:ED:6F:12:34:22" + +MOCK_FB_SERVICES: dict[str, dict] = { + "DeviceInfo1": { + "GetInfo": { + "NewSerialNumber": MOCK_MESH_MASTER_MAC, + "NewName": "TheName", + "NewModelName": MOCK_MODELNAME, + "NewSoftwareVersion": MOCK_FIRMWARE, + "NewUpTime": 2518179, + }, + }, + "Hosts1": { + "GetGenericHostEntry": [ + { + "NewIPAddress": MOCK_IPS["fritz.box"], + "NewAddressSource": "Static", + "NewLeaseTimeRemaining": 0, + "NewMACAddress": MOCK_MESH_MASTER_MAC, + "NewInterfaceType": "", + "NewActive": True, + "NewHostName": "fritz.box", + }, + { + "NewIPAddress": MOCK_IPS["printer"], + "NewAddressSource": "DHCP", + "NewLeaseTimeRemaining": 0, + "NewMACAddress": "AA:BB:CC:00:11:22", + "NewInterfaceType": "Ethernet", + "NewActive": True, + "NewHostName": "printer", + }, + ], + "X_AVM-DE_GetMeshListPath": {}, + }, + "LANEthernetInterfaceConfig1": { + "GetStatistics": { + "NewBytesSent": 23004321, + "NewBytesReceived": 12045, + }, + }, + "Layer3Forwarding1": { + "GetDefaultConnectionService": { + "NewDefaultConnectionService": "1.WANPPPConnection.1" + } + }, + "UserInterface1": { + "GetInfo": {}, + }, + "WANCommonIFC1": { + "GetCommonLinkProperties": { + "NewLayer1DownstreamMaxBitRate": 10087000, + "NewLayer1UpstreamMaxBitRate": 2105000, + "NewPhysicalLinkStatus": "Up", + }, + "GetAddonInfos": { + "NewByteSendRate": 3438, + "NewByteReceiveRate": 67649, + "NewTotalBytesSent": 1712232562, + "NewTotalBytesReceived": 5221019883, + "NewX_AVM_DE_TotalBytesSent64": 1712232562, + "NeWX_AVM_DE_TotalBytesReceived64": 5221019883, + }, + "GetTotalBytesSent": {"NewTotalBytesSent": 1712232562}, + "GetTotalBytesReceived": {"NewTotalBytesReceived": 5221019883}, + }, + "WANCommonInterfaceConfig1": { + "GetCommonLinkProperties": { + "NewWANAccessType": "DSL", + "NewLayer1UpstreamMaxBitRate": 51805000, + "NewLayer1DownstreamMaxBitRate": 318557000, + "NewPhysicalLinkStatus": "Up", + } + }, + "WANDSLInterfaceConfig1": { + "GetInfo": { + "NewEnable": True, + "NewStatus": "Up", + "NewDataPath": "Interleaved", + "NewUpstreamCurrRate": 46720, + "NewDownstreamCurrRate": 292030, + "NewUpstreamMaxRate": 51348, + "NewDownstreamMaxRate": 315978, + "NewUpstreamNoiseMargin": 90, + "NewDownstreamNoiseMargin": 80, + "NewUpstreamAttenuation": 70, + "NewDownstreamAttenuation": 120, + "NewATURVendor": "41564d00", + "NewATURCountry": "0400", + "NewUpstreamPower": 500, + "NewDownstreamPower": 500, + } + }, + "WANIPConn1": { + "GetStatusInfo": { + "NewConnectionStatus": "Connected", + "NewUptime": 35307, + }, + "GetExternalIPAddress": {"NewExternalIPAddress": "1.2.3.4"}, + }, + "WANPPPConnection1": { + "GetInfo": { + "NewEnable": True, + "NewConnectionStatus": "Connected", + "NewUptime": 57199, + "NewUpstreamMaxBitRate": 46531924, + "NewDownstreamMaxBitRate": 43430530, + "NewExternalIPAddress": "1.2.3.4", + }, + "GetPortMappingNumberOfEntries": {}, + }, + "X_AVM-DE_Homeauto1": { + "GetGenericDeviceInfos": [ + { + "NewSwitchIsValid": "VALID", + "NewMultimeterIsValid": "VALID", + "NewTemperatureIsValid": "VALID", + "NewDeviceId": 16, + "NewAIN": "08761 0114116", + "NewDeviceName": "FRITZ!DECT 200 #1", + "NewTemperatureOffset": "0", + "NewSwitchLock": "0", + "NewProductName": "FRITZ!DECT 200", + "NewPresent": "CONNECTED", + "NewMultimeterPower": 1673, + "NewHkrComfortTemperature": "0", + "NewSwitchMode": "AUTO", + "NewManufacturer": "AVM", + "NewMultimeterIsEnabled": "ENABLED", + "NewHkrIsTemperature": "0", + "NewFunctionBitMask": 2944, + "NewTemperatureIsEnabled": "ENABLED", + "NewSwitchState": "ON", + "NewSwitchIsEnabled": "ENABLED", + "NewFirmwareVersion": "03.87", + "NewHkrSetVentilStatus": "CLOSED", + "NewMultimeterEnergy": 5182, + "NewHkrComfortVentilStatus": "CLOSED", + "NewHkrReduceTemperature": "0", + "NewHkrReduceVentilStatus": "CLOSED", + "NewHkrIsEnabled": "DISABLED", + "NewHkrSetTemperature": "0", + "NewTemperatureCelsius": "225", + "NewHkrIsValid": "INVALID", + }, + {}, + ], + }, + "X_AVM-DE_HostFilter1": { + "GetWANAccessByIP": { + MOCK_IPS["printer"]: {"NewDisallow": False, "NewWANAccess": "granted"} + } + }, +} + +MOCK_MESH_DATA = { + "schema_version": "1.9", + "nodes": [ + { + "uid": "n-1", + "device_name": "fritz.box", + "device_model": "FRITZ!Box 7530 AX", + "device_manufacturer": "AVM", + "device_firmware_version": "256.07.29", + "device_mac_address": MOCK_MESH_MASTER_MAC, + "is_meshed": True, + "mesh_role": "master", + "meshd_version": "3.13", + "node_interfaces": [ + { + "uid": "ni-5", + "name": "LANBridge", + "type": "LAN", + "mac_address": MOCK_MESH_MASTER_MAC, + "blocking_state": "NOT_BLOCKED", + "node_links": [], + }, + { + "uid": "ni-30", + "name": "LAN:2", + "type": "LAN", + "mac_address": MOCK_MESH_MASTER_MAC, + "blocking_state": "NOT_BLOCKED", + "node_links": [], + }, + { + "uid": "ni-32", + "name": "LAN:3", + "type": "LAN", + "mac_address": MOCK_MESH_MASTER_MAC, + "blocking_state": "NOT_BLOCKED", + "node_links": [], + }, + { + "uid": "ni-31", + "name": "LAN:1", + "type": "LAN", + "mac_address": MOCK_MESH_MASTER_MAC, + "blocking_state": "NOT_BLOCKED", + "node_links": [ + { + "uid": "nl-78", + "type": "LAN", + "state": "CONNECTED", + "last_connected": 1642872967, + "node_1_uid": "n-1", + "node_2_uid": "n-76", + "node_interface_1_uid": "ni-31", + "node_interface_2_uid": "ni-77", + "max_data_rate_rx": 1000000, + "max_data_rate_tx": 1000000, + "cur_data_rate_rx": 0, + "cur_data_rate_tx": 0, + "cur_availability_rx": 99, + "cur_availability_tx": 99, + } + ], + }, + { + "uid": "ni-33", + "name": "LAN:4", + "type": "LAN", + "mac_address": MOCK_MESH_MASTER_MAC, + "blocking_state": "NOT_BLOCKED", + "node_links": [], + }, + { + "uid": "ni-230", + "name": "AP:2G:0", + "type": "WLAN", + "mac_address": MOCK_MESH_MASTER_WIFI1_MAC, + "blocking_state": "UNKNOWN", + "node_links": [ + { + "uid": "nl-219", + "type": "WLAN", + "state": "CONNECTED", + "last_connected": 1644618820, + "node_1_uid": "n-1", + "node_2_uid": "n-89", + "node_interface_1_uid": "ni-230", + "node_interface_2_uid": "ni-90", + "max_data_rate_rx": 72200, + "max_data_rate_tx": 72200, + "cur_data_rate_rx": 54000, + "cur_data_rate_tx": 65000, + "cur_availability_rx": 100, + "cur_availability_tx": 100, + "rx_rsni": 51, + "tx_rsni": 255, + "rx_rcpi": -38, + "tx_rcpi": 255, + }, + { + "uid": "nl-168", + "type": "WLAN", + "state": "CONNECTED", + "last_connected": 1645162418, + "node_1_uid": "n-1", + "node_2_uid": "n-118", + "node_interface_1_uid": "ni-230", + "node_interface_2_uid": "ni-119", + "max_data_rate_rx": 144400, + "max_data_rate_tx": 144400, + "cur_data_rate_rx": 144400, + "cur_data_rate_tx": 130000, + "cur_availability_rx": 100, + "cur_availability_tx": 100, + "rx_rsni": 37, + "tx_rsni": 255, + "rx_rcpi": -52, + "tx_rcpi": 255, + }, + { + "uid": "nl-185", + "type": "WLAN", + "state": "CONNECTED", + "last_connected": 1645273363, + "node_1_uid": "n-1", + "node_2_uid": "n-100", + "node_interface_1_uid": "ni-230", + "node_interface_2_uid": "ni-99", + "max_data_rate_rx": 72200, + "max_data_rate_tx": 72200, + "cur_data_rate_rx": 1000, + "cur_data_rate_tx": 1000, + "cur_availability_rx": 100, + "cur_availability_tx": 100, + "rx_rsni": 35, + "tx_rsni": 255, + "rx_rcpi": -54, + "tx_rcpi": 255, + }, + { + "uid": "nl-166", + "type": "WLAN", + "state": "CONNECTED", + "last_connected": 1644618912, + "node_1_uid": "n-1", + "node_2_uid": "n-16", + "node_interface_1_uid": "ni-230", + "node_interface_2_uid": "ni-15", + "max_data_rate_rx": 72200, + "max_data_rate_tx": 72200, + "cur_data_rate_rx": 54000, + "cur_data_rate_tx": 65000, + "cur_availability_rx": 100, + "cur_availability_tx": 100, + "rx_rsni": 41, + "tx_rsni": 255, + "rx_rcpi": -48, + "tx_rcpi": 255, + }, + { + "uid": "nl-239", + "type": "WLAN", + "state": "CONNECTED", + "last_connected": 1644618828, + "node_1_uid": "n-1", + "node_2_uid": "n-59", + "node_interface_1_uid": "ni-230", + "node_interface_2_uid": "ni-58", + "max_data_rate_rx": 72200, + "max_data_rate_tx": 72200, + "cur_data_rate_rx": 54000, + "cur_data_rate_tx": 65000, + "cur_availability_rx": 100, + "cur_availability_tx": 100, + "rx_rsni": 43, + "tx_rsni": 255, + "rx_rcpi": -46, + "tx_rcpi": 255, + }, + { + "uid": "nl-173", + "type": "WLAN", + "state": "CONNECTED", + "last_connected": 1645331764, + "node_1_uid": "n-1", + "node_2_uid": "n-137", + "node_interface_1_uid": "ni-230", + "node_interface_2_uid": "ni-138", + "max_data_rate_rx": 72200, + "max_data_rate_tx": 72200, + "cur_data_rate_rx": 72200, + "cur_data_rate_tx": 65000, + "cur_availability_rx": 100, + "cur_availability_tx": 100, + "rx_rsni": 38, + "tx_rsni": 255, + "rx_rcpi": -51, + "tx_rcpi": 255, + }, + { + "uid": "nl-217", + "type": "WLAN", + "state": "CONNECTED", + "last_connected": 1644618833, + "node_1_uid": "n-1", + "node_2_uid": "n-128", + "node_interface_1_uid": "ni-230", + "node_interface_2_uid": "ni-127", + "max_data_rate_rx": 72200, + "max_data_rate_tx": 72200, + "cur_data_rate_rx": 54000, + "cur_data_rate_tx": 72200, + "cur_availability_rx": 100, + "cur_availability_tx": 100, + "rx_rsni": 41, + "tx_rsni": 255, + "rx_rcpi": -48, + "tx_rcpi": 255, + }, + { + "uid": "nl-198", + "type": "WLAN", + "state": "CONNECTED", + "last_connected": 1644618820, + "node_1_uid": "n-1", + "node_2_uid": "n-105", + "node_interface_1_uid": "ni-230", + "node_interface_2_uid": "ni-106", + "max_data_rate_rx": 72200, + "max_data_rate_tx": 72200, + "cur_data_rate_rx": 48000, + "cur_data_rate_tx": 58500, + "cur_availability_rx": 100, + "cur_availability_tx": 100, + "rx_rsni": 28, + "tx_rsni": 255, + "rx_rcpi": -61, + "tx_rcpi": 255, + }, + { + "uid": "nl-213", + "type": "WLAN", + "state": "CONNECTED", + "last_connected": 1644618820, + "node_1_uid": "n-1", + "node_2_uid": "n-111", + "node_interface_1_uid": "ni-230", + "node_interface_2_uid": "ni-112", + "max_data_rate_rx": 72200, + "max_data_rate_tx": 72200, + "cur_data_rate_rx": 48000, + "cur_data_rate_tx": 1000, + "cur_availability_rx": 100, + "cur_availability_tx": 100, + "rx_rsni": 44, + "tx_rsni": 255, + "rx_rcpi": -45, + "tx_rcpi": 255, + }, + { + "uid": "nl-224", + "type": "WLAN", + "state": "CONNECTED", + "last_connected": 1644618831, + "node_1_uid": "n-1", + "node_2_uid": "n-197", + "node_interface_1_uid": "ni-230", + "node_interface_2_uid": "ni-196", + "max_data_rate_rx": 72200, + "max_data_rate_tx": 72200, + "cur_data_rate_rx": 48000, + "cur_data_rate_tx": 1000, + "cur_availability_rx": 100, + "cur_availability_tx": 100, + "rx_rsni": 51, + "tx_rsni": 255, + "rx_rcpi": -38, + "tx_rcpi": 255, + }, + { + "uid": "nl-182", + "type": "WLAN", + "state": "CONNECTED", + "last_connected": 1644618822, + "node_1_uid": "n-1", + "node_2_uid": "n-56", + "node_interface_1_uid": "ni-230", + "node_interface_2_uid": "ni-55", + "max_data_rate_rx": 72200, + "max_data_rate_tx": 72200, + "cur_data_rate_rx": 54000, + "cur_data_rate_tx": 72200, + "cur_availability_rx": 100, + "cur_availability_tx": 100, + "rx_rsni": 34, + "tx_rsni": 255, + "rx_rcpi": -55, + "tx_rcpi": 255, + }, + { + "uid": "nl-205", + "type": "WLAN", + "state": "CONNECTED", + "last_connected": 1644618820, + "node_1_uid": "n-1", + "node_2_uid": "n-109", + "node_interface_1_uid": "ni-230", + "node_interface_2_uid": "ni-108", + "max_data_rate_rx": 72200, + "max_data_rate_tx": 72200, + "cur_data_rate_rx": 54000, + "cur_data_rate_tx": 1000, + "cur_availability_rx": 100, + "cur_availability_tx": 100, + "rx_rsni": 43, + "tx_rsni": 255, + "rx_rcpi": -46, + "tx_rcpi": 255, + }, + { + "uid": "nl-240", + "type": "WLAN", + "state": "CONNECTED", + "last_connected": 1644618827, + "node_1_uid": "n-1", + "node_2_uid": "n-95", + "node_interface_1_uid": "ni-230", + "node_interface_2_uid": "ni-96", + "max_data_rate_rx": 72200, + "max_data_rate_tx": 72200, + "cur_data_rate_rx": 48000, + "cur_data_rate_tx": 58500, + "cur_availability_rx": 100, + "cur_availability_tx": 100, + "rx_rsni": 25, + "tx_rsni": 255, + "rx_rcpi": -64, + "tx_rcpi": 255, + }, + { + "uid": "nl-146", + "type": "WLAN", + "state": "CONNECTED", + "last_connected": 1642872967, + "node_1_uid": "n-1", + "node_2_uid": "n-167", + "node_interface_1_uid": "ni-230", + "node_interface_2_uid": "ni-134", + "max_data_rate_rx": 144400, + "max_data_rate_tx": 144400, + "cur_data_rate_rx": 144400, + "cur_data_rate_tx": 130000, + "cur_availability_rx": 100, + "cur_availability_tx": 100, + "rx_rsni": 48, + "tx_rsni": 255, + "rx_rcpi": -41, + "tx_rcpi": 255, + }, + { + "uid": "nl-232", + "type": "WLAN", + "state": "CONNECTED", + "last_connected": 1644618829, + "node_1_uid": "n-1", + "node_2_uid": "n-18", + "node_interface_1_uid": "ni-230", + "node_interface_2_uid": "ni-17", + "max_data_rate_rx": 72200, + "max_data_rate_tx": 72200, + "cur_data_rate_rx": 48000, + "cur_data_rate_tx": 21700, + "cur_availability_rx": 100, + "cur_availability_tx": 100, + "rx_rsni": 22, + "tx_rsni": 255, + "rx_rcpi": -67, + "tx_rcpi": 255, + }, + ], + "ssid": MOCK_MESH_SSID, + "opmode": "AP", + "security": "WPA2_WPA3_MIXED", + "supported_streams_tx": [ + ["20 MHz", 2], + ["40 MHz", 0], + ["80 MHz", 0], + ["160 MHz", 0], + ["80+80 MHz", 0], + ], + "supported_streams_rx": [ + ["20 MHz", 2], + ["40 MHz", 0], + ["80 MHz", 0], + ["160 MHz", 0], + ["80+80 MHz", 0], + ], + "current_channel": 13, + "phymodes": ["g", "n", "ax"], + "channel_utilization": 0, + "anpi": -91, + "steering_enabled": True, + "11k_friendly": True, + "11v_friendly": True, + "legacy_friendly": True, + "rrm_compliant": False, + "channel_list": [ + {"channel": 1}, + {"channel": 2}, + {"channel": 3}, + {"channel": 4}, + {"channel": 5}, + {"channel": 6}, + {"channel": 7}, + {"channel": 8}, + {"channel": 9}, + {"channel": 10}, + {"channel": 11}, + {"channel": 12}, + {"channel": 13}, + ], + }, + ], + }, + { + "uid": "n-76", + "device_name": "printer", + "device_model": "", + "device_manufacturer": "", + "device_firmware_version": "", + "device_mac_address": "AA:BB:CC:00:11:22", + "is_meshed": False, + "mesh_role": "unknown", + "meshd_version": "0.0", + "node_interfaces": [ + { + "uid": "ni-77", + "name": "eth0", + "type": "LAN", + "mac_address": "AA:BB:CC:00:11:22", + "blocking_state": "UNKNOWN", + "node_links": [ + { + "uid": "nl-78", + "type": "LAN", + "state": "CONNECTED", + "last_connected": 1642872967, + "node_1_uid": "n-1", + "node_2_uid": "n-76", + "node_interface_1_uid": "ni-31", + "node_interface_2_uid": "ni-77", + "max_data_rate_rx": 1000000, + "max_data_rate_tx": 1000000, + "cur_data_rate_rx": 0, + "cur_data_rate_tx": 0, + "cur_availability_rx": 99, + "cur_availability_tx": 99, + } + ], + } + ], + }, + { + "uid": "n-167", + "device_name": "fritz-repeater", + "device_model": "FRITZ!Box 7490", + "device_manufacturer": "AVM", + "device_firmware_version": "113.07.29", + "device_mac_address": MOCK_MESH_SLAVE_MAC, + "is_meshed": True, + "mesh_role": "slave", + "meshd_version": "3.13", + "node_interfaces": [ + { + "uid": "ni-140", + "name": "LAN:3", + "type": "LAN", + "mac_address": MOCK_MESH_SLAVE_MAC, + "blocking_state": "UNKNOWN", + "node_links": [], + }, + { + "uid": "ni-139", + "name": "LAN:4", + "type": "LAN", + "mac_address": MOCK_MESH_SLAVE_MAC, + "blocking_state": "UNKNOWN", + "node_links": [], + }, + { + "uid": "ni-141", + "name": "LAN:2", + "type": "LAN", + "mac_address": MOCK_MESH_SLAVE_MAC, + "blocking_state": "UNKNOWN", + "node_links": [], + }, + { + "uid": "ni-134", + "name": "UPLINK:2G:0", + "type": "WLAN", + "mac_address": MOCK_MESH_SLAVE_WIFI1_MAC, + "blocking_state": "UNKNOWN", + "node_links": [ + { + "uid": "nl-146", + "type": "WLAN", + "state": "CONNECTED", + "last_connected": 1642872967, + "node_1_uid": "n-1", + "node_2_uid": "n-167", + "node_interface_1_uid": "ni-230", + "node_interface_2_uid": "ni-134", + "max_data_rate_rx": 144400, + "max_data_rate_tx": 144400, + "cur_data_rate_rx": 144400, + "cur_data_rate_tx": 130000, + "cur_availability_rx": 100, + "cur_availability_tx": 100, + "rx_rsni": 48, + "tx_rsni": 255, + "rx_rcpi": -41, + "tx_rcpi": 255, + } + ], + "ssid": "", + "opmode": "WDS_REPEATER", + "security": "WPA3PSK", + "supported_streams_tx": [ + ["20 MHz", 3], + ["40 MHz", 3], + ["80 MHz", 0], + ["160 MHz", 0], + ["80+80 MHz", 0], + ], + "supported_streams_rx": [ + ["20 MHz", 3], + ["40 MHz", 3], + ["80 MHz", 0], + ["160 MHz", 0], + ["80+80 MHz", 0], + ], + "current_channel": 13, + "phymodes": ["b", "g", "n"], + "channel_utilization": 0, + "anpi": 255, + "steering_enabled": True, + "11k_friendly": False, + "11v_friendly": True, + "legacy_friendly": True, + "rrm_compliant": False, + "channel_list": [ + {"channel": 1}, + {"channel": 2}, + {"channel": 3}, + {"channel": 4}, + {"channel": 5}, + {"channel": 6}, + {"channel": 7}, + {"channel": 8}, + {"channel": 9}, + {"channel": 10}, + {"channel": 11}, + {"channel": 12}, + {"channel": 13}, + ], + "client_position": "unknown", + }, + { + "uid": "ni-143", + "name": "LANBridge", + "type": "LAN", + "mac_address": MOCK_MESH_SLAVE_MAC, + "blocking_state": "UNKNOWN", + "node_links": [], + }, + { + "uid": "ni-142", + "name": "LAN:1", + "type": "LAN", + "mac_address": MOCK_MESH_SLAVE_MAC, + "blocking_state": "UNKNOWN", + "node_links": [], + }, + ], + }, + ], +} + MOCK_USER_DATA = MOCK_CONFIG[DOMAIN][CONF_DEVICES][0] MOCK_DEVICE_INFO = { @@ -38,7 +787,7 @@ MOCK_SSDP_DATA = ssdp.SsdpServiceInfo( ssdp_usn="mock_usn", ssdp_st="mock_st", - ssdp_location=f"https://{MOCK_IP}:12345/test", + ssdp_location=f"https://{MOCK_IPS['fritz.box']}:12345/test", upnp={ ATTR_UPNP_FRIENDLY_NAME: "fake_name", ATTR_UPNP_UDN: "uuid:only-a-test", diff --git a/tests/components/fritz/test_button.py b/tests/components/fritz/test_button.py new file mode 100644 index 00000000000000..a6ff579958a59f --- /dev/null +++ b/tests/components/fritz/test_button.py @@ -0,0 +1,75 @@ +"""Tests for Shelly button platform.""" +from unittest.mock import patch + +import pytest + +from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS +from homeassistant.components.fritz.const import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from .const import MOCK_USER_DATA + +from tests.common import MockConfigEntry + + +async def test_button_setup(hass: HomeAssistant, fc_class_mock, fh_class_mock): + """Test setup of Fritz!Tools buttons.""" + + entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA) + entry.add_to_hass(hass) + + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + assert entry.state == ConfigEntryState.LOADED + + buttons = hass.states.async_all(BUTTON_DOMAIN) + assert len(buttons) == 4 + + for button in buttons: + assert button.state == STATE_UNKNOWN + + +@pytest.mark.parametrize( + "entity_id, wrapper_method", + [ + ("button.mock_title_firmware_update", "async_trigger_firmware_update"), + ("button.mock_title_reboot", "async_trigger_reboot"), + ("button.mock_title_reconnect", "async_trigger_reconnect"), + ("button.mock_title_cleanup", "async_trigger_cleanup"), + ], +) +async def test_buttons( + hass: HomeAssistant, + entity_id: str, + wrapper_method: str, + fc_class_mock, + fh_class_mock, +): + """Test Fritz!Tools buttons.""" + entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA) + entry.add_to_hass(hass) + + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + assert entry.state == ConfigEntryState.LOADED + + button = hass.states.get(entity_id) + assert button + assert button.state == STATE_UNKNOWN + with patch( + f"homeassistant.components.fritz.common.AvmWrapper.{wrapper_method}" + ) as mock_press_action: + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) + await hass.async_block_till_done() + mock_press_action.assert_called_once() + + button = hass.states.get(entity_id) + assert button.state != STATE_UNKNOWN diff --git a/tests/components/fritz/test_config_flow.py b/tests/components/fritz/test_config_flow.py index 6505ee2bcaaf72..502757c2c67e22 100644 --- a/tests/components/fritz/test_config_flow.py +++ b/tests/components/fritz/test_config_flow.py @@ -26,7 +26,7 @@ from .const import ( MOCK_FIRMWARE_INFO, - MOCK_IP, + MOCK_IPS, MOCK_REQUEST, MOCK_SSDP_DATA, MOCK_USER_DATA, @@ -51,7 +51,7 @@ async def test_user(hass: HomeAssistant, fc_class_mock, mock_get_source_ip): "requests.post" ) as mock_request_post, patch( "homeassistant.components.fritz.config_flow.socket.gethostbyname", - return_value=MOCK_IP, + return_value=MOCK_IPS["fritz.box"], ): mock_request_get.return_value.status_code = 200 @@ -102,7 +102,7 @@ async def test_user_already_configured( "requests.post" ) as mock_request_post, patch( "homeassistant.components.fritz.config_flow.socket.gethostbyname", - return_value=MOCK_IP, + return_value=MOCK_IPS["fritz.box"], ): mock_request_get.return_value.status_code = 200 @@ -295,7 +295,7 @@ async def test_ssdp_already_configured( side_effect=fc_class_mock, ), patch("homeassistant.components.fritz.common.FritzStatus"), patch( "homeassistant.components.fritz.config_flow.socket.gethostbyname", - return_value=MOCK_IP, + return_value=MOCK_IPS["fritz.box"], ): result = await hass.config_entries.flow.async_init( @@ -322,7 +322,7 @@ async def test_ssdp_already_configured_host( side_effect=fc_class_mock, ), patch("homeassistant.components.fritz.common.FritzStatus"), patch( "homeassistant.components.fritz.config_flow.socket.gethostbyname", - return_value=MOCK_IP, + return_value=MOCK_IPS["fritz.box"], ): result = await hass.config_entries.flow.async_init( @@ -349,7 +349,7 @@ async def test_ssdp_already_configured_host_uuid( side_effect=fc_class_mock, ), patch("homeassistant.components.fritz.common.FritzStatus"), patch( "homeassistant.components.fritz.config_flow.socket.gethostbyname", - return_value=MOCK_IP, + return_value=MOCK_IPS["fritz.box"], ): result = await hass.config_entries.flow.async_init( @@ -420,7 +420,7 @@ async def test_ssdp(hass: HomeAssistant, fc_class_mock, mock_get_source_ip): ) assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["data"][CONF_HOST] == MOCK_IP + assert result["data"][CONF_HOST] == MOCK_IPS["fritz.box"] assert result["data"][CONF_PASSWORD] == "fake_pass" assert result["data"][CONF_USERNAME] == "fake_user" diff --git a/tests/components/fritz/test_diagnostics.py b/tests/components/fritz/test_diagnostics.py new file mode 100644 index 00000000000000..892210d08442b2 --- /dev/null +++ b/tests/components/fritz/test_diagnostics.py @@ -0,0 +1,80 @@ +"""Tests for the AVM Fritz!Box integration.""" +from __future__ import annotations + +from aiohttp import ClientSession + +from homeassistant.components.diagnostics import REDACTED +from homeassistant.components.fritz.common import AvmWrapper +from homeassistant.components.fritz.const import DOMAIN +from homeassistant.components.fritz.diagnostics import TO_REDACT +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from .const import MOCK_USER_DATA + +from tests.common import MockConfigEntry +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_entry_diagnostics( + hass: HomeAssistant, hass_client: ClientSession, fc_class_mock, fh_class_mock +): + """Test config entry diagnostics.""" + entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA) + entry.add_to_hass(hass) + + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + assert entry.state == ConfigEntryState.LOADED + + entry_dict = entry.as_dict() + for key in TO_REDACT: + entry_dict["data"][key] = REDACTED + result = await get_diagnostics_for_config_entry(hass, hass_client, entry) + avm_wrapper: AvmWrapper = hass.data[DOMAIN][entry.entry_id] + assert result == { + "entry": entry_dict, + "device_info": { + "client_devices": [ + { + "connected_to": device.connected_to, + "connection_type": device.connection_type, + "hostname": device.hostname, + "is_connected": device.is_connected, + "last_activity": device.last_activity.isoformat(), + "wan_access": device.wan_access, + } + for _, device in avm_wrapper.devices.items() + ], + "connection_type": "WANPPPConnection", + "current_firmware": "256.07.29", + "discovered_services": [ + "DeviceInfo1", + "Hosts1", + "LANEthernetInterfaceConfig1", + "Layer3Forwarding1", + "UserInterface1", + "WANCommonIFC1", + "WANCommonInterfaceConfig1", + "WANDSLInterfaceConfig1", + "WANIPConn1", + "WANPPPConnection1", + "X_AVM-DE_Homeauto1", + "X_AVM-DE_HostFilter1", + ], + "is_router": True, + "last_exception": None, + "last_update success": True, + "latest_firmware": None, + "mesh_role": "master", + "model": "FRITZ!Box 7530 AX", + "update_available": False, + "wan_link_properties": { + "NewLayer1DownstreamMaxBitRate": 318557000, + "NewLayer1UpstreamMaxBitRate": 51805000, + "NewPhysicalLinkStatus": "Up", + "NewWANAccessType": "DSL", + }, + }, + } diff --git a/tests/components/fritz/test_init.py b/tests/components/fritz/test_init.py new file mode 100644 index 00000000000000..fd67321d235cb7 --- /dev/null +++ b/tests/components/fritz/test_init.py @@ -0,0 +1,96 @@ +"""Tests for AVM Fritz!Box.""" +from unittest.mock import patch + +from fritzconnection.core.exceptions import FritzSecurityError +import pytest + +from homeassistant.components.device_tracker.const import ( + CONF_CONSIDER_HOME, + DEFAULT_CONSIDER_HOME, +) +from homeassistant.components.fritz.const import DOMAIN, FRITZ_EXCEPTIONS +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from .const import MOCK_USER_DATA + +from tests.common import MockConfigEntry + + +async def test_setup(hass: HomeAssistant, fc_class_mock, fh_class_mock): + """Test setup and unload of Fritz!Tools.""" + + entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA) + entry.add_to_hass(hass) + + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + assert entry.state == ConfigEntryState.LOADED + + await hass.config_entries.async_unload(entry.entry_id) + assert entry.state == ConfigEntryState.NOT_LOADED + + +async def test_options_reload(hass: HomeAssistant, fc_class_mock, fh_class_mock): + """Test reload of Fritz!Tools, when options changed.""" + + entry = MockConfigEntry( + domain=DOMAIN, + data=MOCK_USER_DATA, + options={CONF_CONSIDER_HOME: DEFAULT_CONSIDER_HOME.total_seconds()}, + ) + entry.add_to_hass(hass) + + with patch( + "homeassistant.config_entries.ConfigEntries.async_reload", + return_value=None, + ) as mock_reload: + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + assert entry.state == ConfigEntryState.LOADED + + result = await hass.config_entries.options.async_init(entry.entry_id) + await hass.async_block_till_done() + await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={CONF_CONSIDER_HOME: 60}, + ) + await hass.async_block_till_done() + mock_reload.assert_called_once() + + +async def test_setup_auth_fail(hass: HomeAssistant): + """Test starting a flow by user with an already configured device.""" + + entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA) + entry.add_to_hass(hass) + + with patch( + "homeassistant.components.fritz.common.FritzConnection", + side_effect=FritzSecurityError, + ): + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + + assert entry.state == ConfigEntryState.SETUP_ERROR + + +@pytest.mark.parametrize( + "error", + FRITZ_EXCEPTIONS, +) +async def test_setup_fail(hass: HomeAssistant, error): + """Test starting a flow by user with an already configured device.""" + + entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA) + entry.add_to_hass(hass) + + with patch( + "homeassistant.components.fritz.common.FritzConnection", + side_effect=error, + ): + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + + assert entry.state == ConfigEntryState.SETUP_RETRY From c76d2c4283234f6b434bea8348d1cc8cd17ab4b3 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Wed, 23 Feb 2022 01:35:48 +0100 Subject: [PATCH 0966/1098] Fritz device_trackers for non mesh devices (#67006) --- homeassistant/components/fritz/common.py | 78 ++++++++++++------- homeassistant/components/fritz/config_flow.py | 9 +++ homeassistant/components/fritz/const.py | 3 + homeassistant/components/fritz/strings.json | 3 +- .../components/fritz/translations/en.json | 14 +--- 5 files changed, 68 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index 5706ca1948699b..b2a429bfa3c119 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -39,6 +39,8 @@ from homeassistant.util import dt as dt_util from .const import ( + CONF_OLD_DISCOVERY, + DEFAULT_CONF_OLD_DISCOVERY, DEFAULT_DEVICE_NAME, DEFAULT_HOST, DEFAULT_PORT, @@ -325,27 +327,33 @@ async def async_scan_devices(self, now: datetime | None = None) -> None: """Wrap up FritzboxTools class scan.""" await self.hass.async_add_executor_job(self.scan_devices, now) + def manage_device_info( + self, dev_info: Device, dev_mac: str, consider_home: bool + ) -> bool: + """Update device lists.""" + _LOGGER.debug("Client dev_info: %s", dev_info) + + if dev_mac in self._devices: + self._devices[dev_mac].update(dev_info, consider_home) + return False + + device = FritzDevice(dev_mac, dev_info.name) + device.update(dev_info, consider_home) + self._devices[dev_mac] = device + return True + + def send_signal_device_update(self, new_device: bool) -> None: + """Signal device data updated.""" + dispatcher_send(self.hass, self.signal_device_update) + if new_device: + dispatcher_send(self.hass, self.signal_device_new) + def scan_devices(self, now: datetime | None = None) -> None: """Scan for new devices and return a list of found device ids.""" _LOGGER.debug("Checking host info for FRITZ!Box device %s", self.host) self._update_available, self._latest_firmware = self._update_device_info() - topology: dict = {} - if ( - "Hosts1" not in self.connection.services - or "X_AVM-DE_GetMeshListPath" - not in self.connection.services["Hosts1"].actions - ): - self.mesh_role = MeshRoles.NONE - else: - try: - topology = self.fritz_hosts.get_mesh_topology() - except FritzActionError: - self.mesh_role = MeshRoles.SLAVE - # Avoid duplicating device trackers - return - _LOGGER.debug("Checking devices for FRITZ!Box device %s", self.host) _default_consider_home = DEFAULT_CONSIDER_HOME.total_seconds() if self._options: @@ -371,6 +379,32 @@ def scan_devices(self, now: datetime | None = None) -> None: wan_access=None, ) + if ( + "Hosts1" not in self.connection.services + or "X_AVM-DE_GetMeshListPath" + not in self.connection.services["Hosts1"].actions + ) or ( + self._options + and self._options.get(CONF_OLD_DISCOVERY, DEFAULT_CONF_OLD_DISCOVERY) + ): + _LOGGER.debug( + "Using old hosts discovery method. (Mesh not supported or user option)" + ) + self.mesh_role = MeshRoles.NONE + for mac, info in hosts.items(): + if self.manage_device_info(info, mac, consider_home): + new_device = True + self.send_signal_device_update(new_device) + return + + try: + if not (topology := self.fritz_hosts.get_mesh_topology()): + raise Exception("Mesh supported but empty topology reported") + except FritzActionError: + self.mesh_role = MeshRoles.SLAVE + # Avoid duplicating device trackers + return + mesh_intf = {} # first get all meshed devices for node in topology.get("nodes", []): @@ -414,19 +448,11 @@ def scan_devices(self, now: datetime | None = None) -> None: dev_info.connected_to = intf["device"] dev_info.connection_type = intf["type"] dev_info.ssid = intf.get("ssid") - _LOGGER.debug("Client dev_info: %s", dev_info) - - if dev_mac in self._devices: - self._devices[dev_mac].update(dev_info, consider_home) - else: - device = FritzDevice(dev_mac, dev_info.name) - device.update(dev_info, consider_home) - self._devices[dev_mac] = device + + if self.manage_device_info(dev_info, dev_mac, consider_home): new_device = True - dispatcher_send(self.hass, self.signal_device_update) - if new_device: - dispatcher_send(self.hass, self.signal_device_new) + self.send_signal_device_update(new_device) async def async_trigger_firmware_update(self) -> bool: """Trigger firmware update.""" diff --git a/homeassistant/components/fritz/config_flow.py b/homeassistant/components/fritz/config_flow.py index 180a6239d9fb84..0844d725522a08 100644 --- a/homeassistant/components/fritz/config_flow.py +++ b/homeassistant/components/fritz/config_flow.py @@ -21,6 +21,8 @@ from .common import AvmWrapper from .const import ( + CONF_OLD_DISCOVERY, + DEFAULT_CONF_OLD_DISCOVERY, DEFAULT_HOST, DEFAULT_PORT, DOMAIN, @@ -107,6 +109,7 @@ def _async_create_entry(self) -> FlowResult: }, options={ CONF_CONSIDER_HOME: DEFAULT_CONSIDER_HOME.total_seconds(), + CONF_OLD_DISCOVERY: DEFAULT_CONF_OLD_DISCOVERY, }, ) @@ -296,6 +299,12 @@ async def async_step_init( CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME.total_seconds() ), ): vol.All(vol.Coerce(int), vol.Clamp(min=0, max=900)), + vol.Optional( + CONF_OLD_DISCOVERY, + default=self.config_entry.options.get( + CONF_OLD_DISCOVERY, DEFAULT_CONF_OLD_DISCOVERY + ), + ): bool, } ) return self.async_show_form(step_id="init", data_schema=data_schema) diff --git a/homeassistant/components/fritz/const.py b/homeassistant/components/fritz/const.py index 635460db15fb0a..f33cf4639964ef 100644 --- a/homeassistant/components/fritz/const.py +++ b/homeassistant/components/fritz/const.py @@ -32,6 +32,9 @@ class MeshRoles(StrEnum): Platform.SWITCH, ] +CONF_OLD_DISCOVERY = "old_discovery" +DEFAULT_CONF_OLD_DISCOVERY = False + DATA_FRITZ = "fritz_data" DSL_CONNECTION: Literal["dsl"] = "dsl" diff --git a/homeassistant/components/fritz/strings.json b/homeassistant/components/fritz/strings.json index f1cdb719741336..450566f101b488 100644 --- a/homeassistant/components/fritz/strings.json +++ b/homeassistant/components/fritz/strings.json @@ -45,7 +45,8 @@ "step": { "init": { "data": { - "consider_home": "Seconds to consider a device at 'home'" + "consider_home": "Seconds to consider a device at 'home'", + "old_discovery": "Enable old discovery method" } } } diff --git a/homeassistant/components/fritz/translations/en.json b/homeassistant/components/fritz/translations/en.json index 0fa47bd8328faf..0a58ee686f3ff1 100644 --- a/homeassistant/components/fritz/translations/en.json +++ b/homeassistant/components/fritz/translations/en.json @@ -9,7 +9,6 @@ "already_configured": "Device is already configured", "already_in_progress": "Configuration flow is already in progress", "cannot_connect": "Failed to connect", - "connection_error": "Failed to connect", "invalid_auth": "Invalid authentication" }, "flow_title": "{name}", @@ -30,16 +29,6 @@ "description": "Update FRITZ!Box Tools credentials for: {host}.\n\nFRITZ!Box Tools is unable to log in to your FRITZ!Box.", "title": "Updating FRITZ!Box Tools - credentials" }, - "start_config": { - "data": { - "host": "Host", - "password": "Password", - "port": "Port", - "username": "Username" - }, - "description": "Setup FRITZ!Box Tools to control your FRITZ!Box.\nMinimum needed: username, password.", - "title": "Setup FRITZ!Box Tools - mandatory" - }, "user": { "data": { "host": "Host", @@ -56,7 +45,8 @@ "step": { "init": { "data": { - "consider_home": "Seconds to consider a device at 'home'" + "consider_home": "Seconds to consider a device at 'home'", + "old_discovery": "Enable old discovery method" } } } From fda3877852f1f0e223887ba0bd0226947007772b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 22 Feb 2022 23:39:54 -0800 Subject: [PATCH 0967/1098] Improved local media ID handling (#67083) --- .../components/media_source/__init__.py | 23 +++++++------ .../components/media_source/local_source.py | 33 +++++++++---------- tests/components/media_source/test_init.py | 13 ++++++-- 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/media_source/__init__.py b/homeassistant/components/media_source/__init__.py index 3be5bf040d7964..d990b0b1f3d779 100644 --- a/homeassistant/components/media_source/__init__.py +++ b/homeassistant/components/media_source/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations from collections.abc import Callable +import dataclasses from datetime import timedelta from typing import Any from urllib.parse import quote @@ -165,15 +166,17 @@ async def websocket_resolve_media( """Resolve media.""" try: media = await async_resolve_media(hass, msg["media_content_id"]) - url = media.url except Unresolvable as err: connection.send_error(msg["id"], "resolve_media_failed", str(err)) - else: - if url[0] == "/": - url = async_sign_path( - hass, - quote(url), - timedelta(seconds=msg["expires"]), - ) - - connection.send_result(msg["id"], {"url": url, "mime_type": media.mime_type}) + return + + data = dataclasses.asdict(media) + + if data["url"][0] == "/": + data["url"] = async_sign_path( + hass, + quote(data["url"]), + timedelta(seconds=msg["expires"]), + ) + + connection.send_result(msg["id"], data) diff --git a/homeassistant/components/media_source/local_source.py b/homeassistant/components/media_source/local_source.py index 76598eac963d65..66baa0eaa8c58d 100644 --- a/homeassistant/components/media_source/local_source.py +++ b/homeassistant/components/media_source/local_source.py @@ -56,10 +56,6 @@ def async_parse_identifier(self, item: MediaSourceItem) -> tuple[str, str]: if item.domain != DOMAIN: raise Unresolvable("Unknown domain.") - if not item.identifier: - # Empty source_dir_id and location - return "", "" - source_dir_id, _, location = item.identifier.partition("/") if source_dir_id not in self.hass.config.media_dirs: raise Unresolvable("Unknown source directory.") @@ -74,36 +70,39 @@ def async_parse_identifier(self, item: MediaSourceItem) -> tuple[str, str]: async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia: """Resolve media to a url.""" source_dir_id, location = self.async_parse_identifier(item) - if source_dir_id == "" or source_dir_id not in self.hass.config.media_dirs: - raise Unresolvable("Unknown source directory.") - - mime_type, _ = mimetypes.guess_type( - str(self.async_full_path(source_dir_id, location)) - ) + path = self.async_full_path(source_dir_id, location) + mime_type, _ = mimetypes.guess_type(str(path)) assert isinstance(mime_type, str) return PlayMedia(f"/media/{item.identifier}", mime_type) async def async_browse_media(self, item: MediaSourceItem) -> BrowseMediaSource: """Return media.""" - try: - source_dir_id, location = self.async_parse_identifier(item) - except Unresolvable as err: - raise BrowseError(str(err)) from err + if item.identifier: + try: + source_dir_id, location = self.async_parse_identifier(item) + except Unresolvable as err: + raise BrowseError(str(err)) from err + + else: + source_dir_id, location = None, "" result = await self.hass.async_add_executor_job( self._browse_media, source_dir_id, location ) + return result - def _browse_media(self, source_dir_id: str, location: str) -> BrowseMediaSource: + def _browse_media( + self, source_dir_id: str | None, location: str + ) -> BrowseMediaSource: """Browse media.""" # If only one media dir is configured, use that as the local media root - if source_dir_id == "" and len(self.hass.config.media_dirs) == 1: + if source_dir_id is None and len(self.hass.config.media_dirs) == 1: source_dir_id = list(self.hass.config.media_dirs)[0] # Multiple folder, root is requested - if source_dir_id == "": + if source_dir_id is None: if location: raise BrowseError("Folder not found.") diff --git a/tests/components/media_source/test_init.py b/tests/components/media_source/test_init.py index 7aae72475cb3f5..a32535a5657e13 100644 --- a/tests/components/media_source/test_init.py +++ b/tests/components/media_source/test_init.py @@ -1,8 +1,8 @@ """Test Media Source initialization.""" from unittest.mock import Mock, patch -from urllib.parse import quote import pytest +import yarl from homeassistant.components import media_source from homeassistant.components.media_player import MEDIA_CLASS_DIRECTORY, BrowseError @@ -159,7 +159,10 @@ async def test_websocket_resolve_media(hass, hass_ws_client, filename): client = await hass_ws_client(hass) - media = media_source.models.PlayMedia(f"/media/local/{filename}", "audio/mpeg") + media = media_source.models.PlayMedia( + f"/media/local/{filename}", + "audio/mpeg", + ) with patch( "homeassistant.components.media_source.async_resolve_media", @@ -177,9 +180,13 @@ async def test_websocket_resolve_media(hass, hass_ws_client, filename): assert msg["success"] assert msg["id"] == 1 - assert msg["result"]["url"].startswith(quote(media.url)) assert msg["result"]["mime_type"] == media.mime_type + # Validate url is signed. + parsed = yarl.URL(msg["result"]["url"]) + assert parsed.path == getattr(media, "url") + assert "authSig" in parsed.query + with patch( "homeassistant.components.media_source.async_resolve_media", side_effect=media_source.Unresolvable("test"), From 636d791b37f96633c22cf4c487681a9e94483ca9 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 23 Feb 2022 08:44:35 +0100 Subject: [PATCH 0968/1098] Fix type issues [litterrobot] (#67092) --- .../components/litterrobot/entity.py | 11 +++++---- homeassistant/components/litterrobot/hub.py | 12 ++++++---- mypy.ini | 24 ------------------- script/hassfest/mypy_config.py | 8 ------- 4 files changed, 14 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/litterrobot/entity.py b/homeassistant/components/litterrobot/entity.py index 2c4ed37f955d72..51b88bb4f79245 100644 --- a/homeassistant/components/litterrobot/entity.py +++ b/homeassistant/components/litterrobot/entity.py @@ -9,7 +9,7 @@ from pylitterbot import Robot from pylitterbot.exceptions import InvalidCommandException -from homeassistant.core import callback +from homeassistant.core import CALLBACK_TYPE, callback from homeassistant.helpers.entity import DeviceInfo, EntityCategory from homeassistant.helpers.event import async_call_later from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -60,7 +60,7 @@ class LitterRobotControlEntity(LitterRobotEntity): def __init__(self, robot: Robot, entity_type: str, hub: LitterRobotHub) -> None: """Init a Litter-Robot control entity.""" super().__init__(robot=robot, entity_type=entity_type, hub=hub) - self._refresh_callback = None + self._refresh_callback: CALLBACK_TYPE | None = None async def perform_action_and_refresh( self, action: MethodType, *args: Any, **kwargs: Any @@ -99,8 +99,11 @@ def async_cancel_refresh_callback(self): self._refresh_callback = None @staticmethod - def parse_time_at_default_timezone(time_str: str) -> time | None: + def parse_time_at_default_timezone(time_str: str | None) -> time | None: """Parse a time string and add default timezone.""" + if time_str is None: + return None + if (parsed_time := dt_util.parse_time(time_str)) is None: return None @@ -127,7 +130,7 @@ def __init__(self, robot: Robot, entity_type: str, hub: LitterRobotHub) -> None: async def perform_action_and_assume_state( self, action: MethodType, assumed_state: Any - ) -> bool: + ) -> None: """Perform an action and assume the state passed in if call is successful.""" if await self.perform_action_and_refresh(action, assumed_state): self._assumed_state = assumed_state diff --git a/homeassistant/components/litterrobot/hub.py b/homeassistant/components/litterrobot/hub.py index 3aa86dcc93ac13..49fba822a6f369 100644 --- a/homeassistant/components/litterrobot/hub.py +++ b/homeassistant/components/litterrobot/hub.py @@ -1,4 +1,7 @@ """A wrapper 'hub' for the Litter-Robot API.""" +from __future__ import annotations + +from collections.abc import Mapping from datetime import timedelta import logging @@ -19,10 +22,11 @@ class LitterRobotHub: """A Litter-Robot hub wrapper class.""" - def __init__(self, hass: HomeAssistant, data: dict) -> None: + account: Account + + def __init__(self, hass: HomeAssistant, data: Mapping) -> None: """Initialize the Litter-Robot hub.""" self._data = data - self.account = None self.logged_in = False async def _async_update_data() -> bool: @@ -40,7 +44,6 @@ async def _async_update_data() -> bool: async def login(self, load_robots: bool = False) -> None: """Login to Litter-Robot.""" - self.logged_in = False self.account = Account() try: await self.account.connect( @@ -48,8 +51,7 @@ async def login(self, load_robots: bool = False) -> None: password=self._data[CONF_PASSWORD], load_robots=load_robots, ) - self.logged_in = True - return self.logged_in + return except LitterRobotLoginException as ex: _LOGGER.error("Invalid credentials") raise ex diff --git a/mypy.ini b/mypy.ini index 95e2090f0616c2..c72dfbee1bdf3f 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2420,30 +2420,6 @@ ignore_errors = true [mypy-homeassistant.components.kostal_plenticore.switch] ignore_errors = true -[mypy-homeassistant.components.litterrobot] -ignore_errors = true - -[mypy-homeassistant.components.litterrobot.button] -ignore_errors = true - -[mypy-homeassistant.components.litterrobot.entity] -ignore_errors = true - -[mypy-homeassistant.components.litterrobot.hub] -ignore_errors = true - -[mypy-homeassistant.components.litterrobot.select] -ignore_errors = true - -[mypy-homeassistant.components.litterrobot.sensor] -ignore_errors = true - -[mypy-homeassistant.components.litterrobot.switch] -ignore_errors = true - -[mypy-homeassistant.components.litterrobot.vacuum] -ignore_errors = true - [mypy-homeassistant.components.lovelace] ignore_errors = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index 99104702f26c13..53468caa29d71e 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -97,14 +97,6 @@ "homeassistant.components.kostal_plenticore.select", "homeassistant.components.kostal_plenticore.sensor", "homeassistant.components.kostal_plenticore.switch", - "homeassistant.components.litterrobot", - "homeassistant.components.litterrobot.button", - "homeassistant.components.litterrobot.entity", - "homeassistant.components.litterrobot.hub", - "homeassistant.components.litterrobot.select", - "homeassistant.components.litterrobot.sensor", - "homeassistant.components.litterrobot.switch", - "homeassistant.components.litterrobot.vacuum", "homeassistant.components.lovelace", "homeassistant.components.lovelace.dashboard", "homeassistant.components.lovelace.resources", From c11663344d7385ca36c2a19b748a4a2bfccf6ccc Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 23 Feb 2022 08:57:06 +0100 Subject: [PATCH 0969/1098] Fix type issues [firmata] (#67093) --- homeassistant/components/firmata/__init__.py | 2 +- homeassistant/components/firmata/board.py | 19 ++++++++++------ homeassistant/components/firmata/const.py | 6 +++-- homeassistant/components/firmata/entity.py | 4 ++-- homeassistant/components/firmata/light.py | 2 +- homeassistant/components/firmata/pin.py | 19 ++++++++++++---- mypy.ini | 24 -------------------- script/hassfest/mypy_config.py | 8 ------- 8 files changed, 35 insertions(+), 49 deletions(-) diff --git a/homeassistant/components/firmata/__init__.py b/homeassistant/components/firmata/__init__.py index cef9db620b87be..ffc4d39a89b0f5 100644 --- a/homeassistant/components/firmata/__init__.py +++ b/homeassistant/components/firmata/__init__.py @@ -190,7 +190,7 @@ async def handle_shutdown(event) -> None: device_registry = dr.async_get(hass) device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, - connections={}, + connections=set(), identifiers={(DOMAIN, board.name)}, manufacturer=FIRMATA_MANUFACTURER, name=board.name, diff --git a/homeassistant/components/firmata/board.py b/homeassistant/components/firmata/board.py index 1f0052732ea68a..002775edb0ce10 100644 --- a/homeassistant/components/firmata/board.py +++ b/homeassistant/components/firmata/board.py @@ -1,6 +1,9 @@ """Code to handle a Firmata board.""" +from __future__ import annotations + +from collections.abc import Mapping import logging -from typing import Union +from typing import Literal, Union from pymata_express.pymata_express import PymataExpress from pymata_express.pymata_express_serial import serial @@ -32,18 +35,18 @@ class FirmataBoard: """Manages a single Firmata board.""" - def __init__(self, config: dict) -> None: + def __init__(self, config: Mapping) -> None: """Initialize the board.""" self.config = config - self.api = None - self.firmware_version = None + self.api: PymataExpress = None + self.firmware_version: str | None = None self.protocol_version = None self.name = self.config[CONF_NAME] self.switches = [] self.lights = [] self.binary_sensors = [] self.sensors = [] - self.used_pins = [] + self.used_pins: list[FirmataPinType] = [] if CONF_SWITCHES in self.config: self.switches = self.config[CONF_SWITCHES] @@ -118,8 +121,10 @@ def mark_pin_used(self, pin: FirmataPinType) -> bool: self.used_pins.append(pin) return True - def get_pin_type(self, pin: FirmataPinType) -> tuple: + def get_pin_type(self, pin: FirmataPinType) -> tuple[Literal[0, 1], int]: """Return the type and Firmata location of a pin on the board.""" + pin_type: Literal[0, 1] + firmata_pin: int if isinstance(pin, str): pin_type = PIN_TYPE_ANALOG firmata_pin = int(pin[1:]) @@ -130,7 +135,7 @@ def get_pin_type(self, pin: FirmataPinType) -> tuple: return (pin_type, firmata_pin) -async def get_board(data: dict) -> PymataExpress: +async def get_board(data: Mapping) -> PymataExpress: """Create a Pymata board object.""" board_data = {} diff --git a/homeassistant/components/firmata/const.py b/homeassistant/components/firmata/const.py index 091d724229c74e..da722b51897ab6 100644 --- a/homeassistant/components/firmata/const.py +++ b/homeassistant/components/firmata/const.py @@ -1,4 +1,6 @@ """Constants for the Firmata component.""" +from typing import Final + from homeassistant.const import ( CONF_BINARY_SENSORS, CONF_LIGHTS, @@ -19,8 +21,8 @@ PIN_MODE_PWM = "PWM" PIN_MODE_INPUT = "INPUT" PIN_MODE_PULLUP = "PULLUP" -PIN_TYPE_ANALOG = 1 -PIN_TYPE_DIGITAL = 0 +PIN_TYPE_ANALOG: Final = 1 +PIN_TYPE_DIGITAL: Final = 0 CONF_SAMPLING_INTERVAL = "sampling_interval" CONF_SERIAL_BAUD_RATE = "serial_baud_rate" CONF_SERIAL_PORT = "serial_port" diff --git a/homeassistant/components/firmata/entity.py b/homeassistant/components/firmata/entity.py index 0f248e0b9d77ba..0e66656421b82c 100644 --- a/homeassistant/components/firmata/entity.py +++ b/homeassistant/components/firmata/entity.py @@ -20,7 +20,7 @@ def __init__(self, api): def device_info(self) -> DeviceInfo: """Return device info.""" return DeviceInfo( - connections={}, + connections=set(), identifiers={(DOMAIN, self._api.board.name)}, manufacturer=FIRMATA_MANUFACTURER, name=self._api.board.name, @@ -33,7 +33,7 @@ class FirmataPinEntity(FirmataEntity): def __init__( self, - api: type[FirmataBoardPin], + api: FirmataBoardPin, config_entry: ConfigEntry, name: str, pin: FirmataPinType, diff --git a/homeassistant/components/firmata/light.py b/homeassistant/components/firmata/light.py index d42e72f992aaa6..de51105811465c 100644 --- a/homeassistant/components/firmata/light.py +++ b/homeassistant/components/firmata/light.py @@ -58,7 +58,7 @@ class FirmataLight(FirmataPinEntity, LightEntity): def __init__( self, - api: type[FirmataBoardPin], + api: FirmataBoardPin, config_entry: ConfigEntry, name: str, pin: FirmataPinType, diff --git a/homeassistant/components/firmata/pin.py b/homeassistant/components/firmata/pin.py index 6dadb07fd6313f..190889914b3b22 100644 --- a/homeassistant/components/firmata/pin.py +++ b/homeassistant/components/firmata/pin.py @@ -3,6 +3,7 @@ from collections.abc import Callable import logging +from typing import cast from .board import FirmataBoard, FirmataPinType from .const import PIN_MODE_INPUT, PIN_MODE_PULLUP, PIN_TYPE_ANALOG @@ -23,11 +24,12 @@ def __init__(self, board: FirmataBoard, pin: FirmataPinType, pin_mode: str) -> N self._pin = pin self._pin_mode = pin_mode self._pin_type, self._firmata_pin = self.board.get_pin_type(self._pin) - self._state = None + self._state: bool | int | None = None + self._analog_pin: int | None = None if self._pin_type == PIN_TYPE_ANALOG: # Pymata wants the analog pin formatted as the # from "A#" - self._analog_pin = int(self._pin[1:]) + self._analog_pin = int(cast(str, self._pin)[1:]) def setup(self): """Set up a pin and make sure it is valid.""" @@ -38,6 +40,8 @@ def setup(self): class FirmataBinaryDigitalOutput(FirmataBoardPin): """Representation of a Firmata Digital Output Pin.""" + _state: bool + def __init__( self, board: FirmataBoard, @@ -92,6 +96,8 @@ async def turn_off(self) -> None: class FirmataPWMOutput(FirmataBoardPin): """Representation of a Firmata PWM/analog Output Pin.""" + _state: int + def __init__( self, board: FirmataBoard, @@ -139,12 +145,14 @@ async def set_level(self, level: int) -> None: class FirmataBinaryDigitalInput(FirmataBoardPin): """Representation of a Firmata Digital Input Pin.""" + _state: bool + def __init__( self, board: FirmataBoard, pin: FirmataPinType, pin_mode: str, negate: bool ) -> None: """Initialize the digital input pin.""" self._negate = negate - self._forward_callback = None + self._forward_callback: Callable[[], None] super().__init__(board, pin, pin_mode) async def start_pin(self, forward_callback: Callable[[], None]) -> None: @@ -206,12 +214,15 @@ async def latch_callback(self, data: list) -> None: class FirmataAnalogInput(FirmataBoardPin): """Representation of a Firmata Analog Input Pin.""" + _analog_pin: int + _state: int + def __init__( self, board: FirmataBoard, pin: FirmataPinType, pin_mode: str, differential: int ) -> None: """Initialize the analog input pin.""" self._differential = differential - self._forward_callback = None + self._forward_callback: Callable[[], None] super().__init__(board, pin, pin_mode) async def start_pin(self, forward_callback: Callable[[], None]) -> None: diff --git a/mypy.ini b/mypy.ini index c72dfbee1bdf3f..887a65394b7206 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2273,30 +2273,6 @@ ignore_errors = true [mypy-homeassistant.components.fireservicerota.switch] ignore_errors = true -[mypy-homeassistant.components.firmata] -ignore_errors = true - -[mypy-homeassistant.components.firmata.binary_sensor] -ignore_errors = true - -[mypy-homeassistant.components.firmata.board] -ignore_errors = true - -[mypy-homeassistant.components.firmata.entity] -ignore_errors = true - -[mypy-homeassistant.components.firmata.light] -ignore_errors = true - -[mypy-homeassistant.components.firmata.pin] -ignore_errors = true - -[mypy-homeassistant.components.firmata.sensor] -ignore_errors = true - -[mypy-homeassistant.components.firmata.switch] -ignore_errors = true - [mypy-homeassistant.components.geniushub] ignore_errors = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index 53468caa29d71e..a78e07e057b6e3 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -48,14 +48,6 @@ "homeassistant.components.fireservicerota.binary_sensor", "homeassistant.components.fireservicerota.sensor", "homeassistant.components.fireservicerota.switch", - "homeassistant.components.firmata", - "homeassistant.components.firmata.binary_sensor", - "homeassistant.components.firmata.board", - "homeassistant.components.firmata.entity", - "homeassistant.components.firmata.light", - "homeassistant.components.firmata.pin", - "homeassistant.components.firmata.sensor", - "homeassistant.components.firmata.switch", "homeassistant.components.geniushub", "homeassistant.components.geniushub.binary_sensor", "homeassistant.components.geniushub.climate", From 93247d7933a49ebe2b4592c71138f189b201fe32 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Wed, 23 Feb 2022 09:34:32 +0100 Subject: [PATCH 0970/1098] Use RequestError in tradfri (#67101) --- homeassistant/components/tradfri/__init__.py | 4 ++-- homeassistant/components/tradfri/base_class.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/tradfri/__init__.py b/homeassistant/components/tradfri/__init__.py index c11b9874bae9dc..1971b14b2beb8c 100644 --- a/homeassistant/components/tradfri/__init__.py +++ b/homeassistant/components/tradfri/__init__.py @@ -5,7 +5,7 @@ import logging from typing import Any -from pytradfri import Gateway, PytradfriError, RequestError +from pytradfri import Gateway, RequestError from pytradfri.api.aiocoap_api import APIFactory from pytradfri.command import Command from pytradfri.device import Device @@ -149,7 +149,7 @@ async def on_hass_stop(event: Event) -> None: ) groups = await api(groups_commands, timeout=TIMEOUT_API) - except PytradfriError as exc: + except RequestError as exc: await factory.shutdown() raise ConfigEntryNotReady from exc diff --git a/homeassistant/components/tradfri/base_class.py b/homeassistant/components/tradfri/base_class.py index 8c4f483b9a83c6..a2bd28e386840c 100644 --- a/homeassistant/components/tradfri/base_class.py +++ b/homeassistant/components/tradfri/base_class.py @@ -9,7 +9,7 @@ from pytradfri.command import Command from pytradfri.device import Device -from pytradfri.error import PytradfriError +from pytradfri.error import RequestError from homeassistant.core import callback from homeassistant.helpers.entity import DeviceInfo @@ -31,7 +31,7 @@ async def wrapper(command: Command | list[Command]) -> None: """Decorate api call.""" try: await func(command) - except PytradfriError as err: + except RequestError as err: _LOGGER.error("Unable to execute command %s: %s", command, err) return wrapper From c879bf295bcb4ab0bc4b0bbf602f715ae0f8c4f5 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 23 Feb 2022 08:41:44 +0000 Subject: [PATCH 0971/1098] Deprecate manual MQTT configuration available in config flow (#66247) --- homeassistant/components/mqtt/__init__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 197cf26b41fdcf..c7652fe97b9311 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -183,7 +183,14 @@ CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.All( - cv.deprecated(CONF_TLS_VERSION), + cv.deprecated(CONF_BIRTH_MESSAGE), # Deprecated in HA Core 2022.3 + cv.deprecated(CONF_BROKER), # Deprecated in HA Core 2022.3 + cv.deprecated(CONF_DISCOVERY), # Deprecated in HA Core 2022.3 + cv.deprecated(CONF_PASSWORD), # Deprecated in HA Core 2022.3 + cv.deprecated(CONF_PORT), # Deprecated in HA Core 2022.3 + cv.deprecated(CONF_TLS_VERSION), # Deprecated June 2020 + cv.deprecated(CONF_USERNAME), # Deprecated in HA Core 2022.3 + cv.deprecated(CONF_WILL_MESSAGE), # Deprecated in HA Core 2022.3 vol.Schema( { vol.Optional(CONF_CLIENT_ID): cv.string, From 459e6c273bbec9ed3c6040d78536036ce509eb63 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 23 Feb 2022 00:51:01 -0800 Subject: [PATCH 0972/1098] Track hidden items in media source (#67096) --- .../components/media_player/browse_media.py | 12 ++++++---- .../components/media_source/__init__.py | 2 ++ tests/components/cast/test_media_player.py | 6 +---- .../components/dlna_dmr/test_media_player.py | 2 -- tests/components/media_player/test_init.py | 23 +++++++++++++++++-- tests/components/media_source/test_init.py | 1 + .../components/motioneye/test_media_source.py | 16 ++++++------- 7 files changed, 40 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/media_player/browse_media.py b/homeassistant/components/media_player/browse_media.py index 829c96671a9f44..6fe4683c1fcc8e 100644 --- a/homeassistant/components/media_player/browse_media.py +++ b/homeassistant/components/media_player/browse_media.py @@ -56,6 +56,7 @@ def __init__( children: list[BrowseMedia] | None = None, children_media_class: str | None = None, thumbnail: str | None = None, + not_shown: int = 0, ) -> None: """Initialize browse media item.""" self.media_class = media_class @@ -67,12 +68,10 @@ def __init__( self.children = children self.children_media_class = children_media_class self.thumbnail = thumbnail + self.not_shown = not_shown def as_dict(self, *, parent: bool = True) -> dict: """Convert Media class to browse media dictionary.""" - if self.children_media_class is None: - self.calculate_children_class() - response = { "title": self.title, "media_class": self.media_class, @@ -80,13 +79,18 @@ def as_dict(self, *, parent: bool = True) -> dict: "media_content_id": self.media_content_id, "can_play": self.can_play, "can_expand": self.can_expand, - "children_media_class": self.children_media_class, "thumbnail": self.thumbnail, } if not parent: return response + if self.children_media_class is None: + self.calculate_children_class() + + response["not_shown"] = self.not_shown + response["children_media_class"] = self.children_media_class + if self.children: response["children"] = [ child.as_dict(parent=False) for child in self.children diff --git a/homeassistant/components/media_source/__init__.py b/homeassistant/components/media_source/__init__.py index d990b0b1f3d779..e2bd1b4903b0ad 100644 --- a/homeassistant/components/media_source/__init__.py +++ b/homeassistant/components/media_source/__init__.py @@ -111,9 +111,11 @@ async def async_browse_media( if content_filter is None or item.children is None: return item + old_count = len(item.children) item.children = [ child for child in item.children if child.can_expand or content_filter(child) ] + item.not_shown = old_count - len(item.children) return item diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 4f12a2b02bb355..663941de77aa65 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -856,7 +856,6 @@ async def test_entity_browse_media(hass: HomeAssistant, hass_ws_client): "media_content_id": "media-source://media_source/local/Epic Sax Guy 10 Hours.mp4", "can_play": True, "can_expand": False, - "children_media_class": None, "thumbnail": None, } assert expected_child_1 in response["result"]["children"] @@ -868,7 +867,6 @@ async def test_entity_browse_media(hass: HomeAssistant, hass_ws_client): "media_content_id": "media-source://media_source/local/test.mp3", "can_play": True, "can_expand": False, - "children_media_class": None, "thumbnail": None, } assert expected_child_2 in response["result"]["children"] @@ -912,7 +910,6 @@ async def test_entity_browse_media_audio_only( "media_content_id": "media-source://media_source/local/Epic Sax Guy 10 Hours.mp4", "can_play": True, "can_expand": False, - "children_media_class": None, "thumbnail": None, } assert expected_child_1 not in response["result"]["children"] @@ -924,7 +921,6 @@ async def test_entity_browse_media_audio_only( "media_content_id": "media-source://media_source/local/test.mp3", "can_play": True, "can_expand": False, - "children_media_class": None, "thumbnail": None, } assert expected_child_2 in response["result"]["children"] @@ -1861,7 +1857,6 @@ async def test_cast_platform_browse_media(hass: HomeAssistant, hass_ws_client): "media_content_id": "", "can_play": False, "can_expand": True, - "children_media_class": None, "thumbnail": "https://brands.home-assistant.io/_/spotify/logo.png", } assert expected_child in response["result"]["children"] @@ -1888,6 +1883,7 @@ async def test_cast_platform_browse_media(hass: HomeAssistant, hass_ws_client): "children_media_class": None, "thumbnail": None, "children": [], + "not_shown": 0, } assert response["result"] == expected_response diff --git a/tests/components/dlna_dmr/test_media_player.py b/tests/components/dlna_dmr/test_media_player.py index c68a311d2b677e..896968557c1c60 100644 --- a/tests/components/dlna_dmr/test_media_player.py +++ b/tests/components/dlna_dmr/test_media_player.py @@ -960,7 +960,6 @@ async def test_browse_media( "media_content_id": "media-source://media_source/local/Epic Sax Guy 10 Hours.mp4", "can_play": True, "can_expand": False, - "children_media_class": None, "thumbnail": None, } assert expected_child_video in response["result"]["children"] @@ -972,7 +971,6 @@ async def test_browse_media( "media_content_id": "media-source://media_source/local/test.mp3", "can_play": True, "can_expand": False, - "children_media_class": None, "thumbnail": None, } assert expected_child_audio in response["result"]["children"] diff --git a/tests/components/media_player/test_init.py b/tests/components/media_player/test_init.py index c1fcd510c23a2e..a725129bed614c 100644 --- a/tests/components/media_player/test_init.py +++ b/tests/components/media_player/test_init.py @@ -5,6 +5,7 @@ from unittest.mock import patch from homeassistant.components import media_player +from homeassistant.components.media_player.browse_media import BrowseMedia from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF from homeassistant.setup import async_setup_component @@ -166,7 +167,14 @@ async def test_media_browse(hass, hass_ws_client): media_player.SUPPORT_BROWSE_MEDIA, ), patch( "homeassistant.components.media_player.MediaPlayerEntity.async_browse_media", - return_value={"bla": "yo"}, + return_value=BrowseMedia( + media_class=media_player.MEDIA_CLASS_DIRECTORY, + media_content_id="mock-id", + media_content_type="mock-type", + title="Mock Title", + can_play=False, + can_expand=True, + ), ) as mock_browse_media: await client.send_json( { @@ -183,7 +191,18 @@ async def test_media_browse(hass, hass_ws_client): assert msg["id"] == 5 assert msg["type"] == TYPE_RESULT assert msg["success"] - assert msg["result"] == {"bla": "yo"} + assert msg["result"] == { + "title": "Mock Title", + "media_class": "directory", + "media_content_type": "mock-type", + "media_content_id": "mock-id", + "can_play": False, + "can_expand": True, + "children_media_class": None, + "thumbnail": None, + "not_shown": 0, + "children": [], + } assert mock_browse_media.mock_calls[0][1] == ("album", "abcd") with patch( diff --git a/tests/components/media_source/test_init.py b/tests/components/media_source/test_init.py index a32535a5657e13..e36ccdac931eb2 100644 --- a/tests/components/media_source/test_init.py +++ b/tests/components/media_source/test_init.py @@ -58,6 +58,7 @@ async def test_async_browse_media(hass): assert media.title == "media" assert len(media.children) == 1, media.children media.children[0].title = "Epic Sax Guy 10 Hours" + assert media.not_shown == 1 # Test invalid media content with pytest.raises(BrowseError): diff --git a/tests/components/motioneye/test_media_source.py b/tests/components/motioneye/test_media_source.py index f2c8db3879b038..ddc50fb77029f9 100644 --- a/tests/components/motioneye/test_media_source.py +++ b/tests/components/motioneye/test_media_source.py @@ -103,10 +103,10 @@ async def test_async_browse_media_success(hass: HomeAssistant) -> None: ), "can_play": False, "can_expand": True, - "children_media_class": "directory", "thumbnail": None, } ], + "not_shown": 0, } media = await media_source.async_browse_media( @@ -135,10 +135,10 @@ async def test_async_browse_media_success(hass: HomeAssistant) -> None: ), "can_play": False, "can_expand": True, - "children_media_class": "directory", "thumbnail": None, } ], + "not_shown": 0, } media = await media_source.async_browse_media( @@ -166,7 +166,6 @@ async def test_async_browse_media_success(hass: HomeAssistant) -> None: ), "can_play": False, "can_expand": True, - "children_media_class": "video", "thumbnail": None, }, { @@ -179,10 +178,10 @@ async def test_async_browse_media_success(hass: HomeAssistant) -> None: ), "can_play": False, "can_expand": True, - "children_media_class": "image", "thumbnail": None, }, ], + "not_shown": 0, } client.async_get_movies = AsyncMock(return_value=TEST_MOVIES) @@ -213,10 +212,10 @@ async def test_async_browse_media_success(hass: HomeAssistant) -> None: ), "can_play": False, "can_expand": True, - "children_media_class": "directory", "thumbnail": None, } ], + "not_shown": 0, } client.get_movie_url = Mock(return_value="http://movie") @@ -248,7 +247,6 @@ async def test_async_browse_media_success(hass: HomeAssistant) -> None: ), "can_play": True, "can_expand": False, - "children_media_class": None, "thumbnail": "http://movie", }, { @@ -262,7 +260,6 @@ async def test_async_browse_media_success(hass: HomeAssistant) -> None: ), "can_play": True, "can_expand": False, - "children_media_class": None, "thumbnail": "http://movie", }, { @@ -276,10 +273,10 @@ async def test_async_browse_media_success(hass: HomeAssistant) -> None: ), "can_play": True, "can_expand": False, - "children_media_class": None, "thumbnail": "http://movie", }, ], + "not_shown": 0, } @@ -326,10 +323,10 @@ async def test_async_browse_media_images_success(hass: HomeAssistant) -> None: ), "can_play": False, "can_expand": False, - "children_media_class": None, "thumbnail": "http://image", } ], + "not_shown": 0, } @@ -479,4 +476,5 @@ async def test_async_resolve_media_failure(hass: HomeAssistant) -> None: "children_media_class": "video", "thumbnail": None, "children": [], + "not_shown": 0, } From b6572d1cabf92d9b5e2f2480dcd942a4615712b4 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 23 Feb 2022 09:55:26 +0100 Subject: [PATCH 0973/1098] Fix type issues [geniushub] (#67095) --- .../components/geniushub/__init__.py | 20 +++++++------------ .../components/geniushub/binary_sensor.py | 4 ++-- homeassistant/components/geniushub/sensor.py | 19 ++++++++++-------- .../components/geniushub/water_heater.py | 2 +- mypy.ini | 15 -------------- script/hassfest/mypy_config.py | 5 ----- 6 files changed, 21 insertions(+), 44 deletions(-) diff --git a/homeassistant/components/geniushub/__init__.py b/homeassistant/components/geniushub/__init__.py index 38a0ff15af3506..83e358acc34a59 100644 --- a/homeassistant/components/geniushub/__init__.py +++ b/homeassistant/components/geniushub/__init__.py @@ -1,7 +1,7 @@ """Support for a Genius Hub system.""" from __future__ import annotations -from datetime import timedelta +from datetime import datetime, timedelta import logging from typing import Any @@ -223,7 +223,7 @@ class GeniusEntity(Entity): def __init__(self) -> None: """Initialize the entity.""" - self._unique_id = self._name = None + self._unique_id: str | None = None async def async_added_to_hass(self) -> None: """Set up a listener when this entity is added to HA.""" @@ -238,11 +238,6 @@ def unique_id(self) -> str | None: """Return a unique ID.""" return self._unique_id - @property - def name(self) -> str: - """Return the name of the geniushub entity.""" - return self._name - @property def should_poll(self) -> bool: """Return False as geniushub entities should not be polled.""" @@ -258,7 +253,8 @@ def __init__(self, broker, device) -> None: self._device = device self._unique_id = f"{broker.hub_uid}_device_{device.id}" - self._last_comms = self._state_attr = None + self._last_comms: datetime | None = None + self._state_attr = None @property def extra_state_attributes(self) -> dict[str, Any]: @@ -337,11 +333,9 @@ def extra_state_attributes(self) -> dict[str, Any]: class GeniusHeatingZone(GeniusZone): """Base for Genius Heating Zones.""" - def __init__(self, broker, zone) -> None: - """Initialize the Zone.""" - super().__init__(broker, zone) - - self._max_temp = self._min_temp = self._supported_features = None + _max_temp: float + _min_temp: float + _supported_features: int @property def current_temperature(self) -> float | None: diff --git a/homeassistant/components/geniushub/binary_sensor.py b/homeassistant/components/geniushub/binary_sensor.py index 1e54df528204ee..f00019361e527e 100644 --- a/homeassistant/components/geniushub/binary_sensor.py +++ b/homeassistant/components/geniushub/binary_sensor.py @@ -42,9 +42,9 @@ def __init__(self, broker, device, state_attr) -> None: self._state_attr = state_attr if device.type[:21] == "Dual Channel Receiver": - self._name = f"{device.type[:21]} {device.id}" + self._attr_name = f"{device.type[:21]} {device.id}" else: - self._name = f"{device.type} {device.id}" + self._attr_name = f"{device.type} {device.id}" @property def is_on(self) -> bool: diff --git a/homeassistant/components/geniushub/sensor.py b/homeassistant/components/geniushub/sensor.py index fad66c493129cc..20acb533a2c7a0 100644 --- a/homeassistant/components/geniushub/sensor.py +++ b/homeassistant/components/geniushub/sensor.py @@ -34,14 +34,14 @@ async def async_setup_platform( broker = hass.data[DOMAIN]["broker"] - sensors = [ + entities: list[GeniusBattery | GeniusIssue] = [ GeniusBattery(broker, d, GH_STATE_ATTR) for d in broker.client.device_objs if GH_STATE_ATTR in d.data["state"] ] - issues = [GeniusIssue(broker, i) for i in list(GH_LEVEL_MAPPING)] + entities.extend([GeniusIssue(broker, i) for i in list(GH_LEVEL_MAPPING)]) - async_add_entities(sensors + issues, update_before_add=True) + async_add_entities(entities, update_before_add=True) class GeniusBattery(GeniusDevice, SensorEntity): @@ -53,7 +53,7 @@ def __init__(self, broker, device, state_attr) -> None: self._state_attr = state_attr - self._name = f"{device.type} {device.id}" + self._attr_name = f"{device.type} {device.id}" @property def icon(self) -> str: @@ -62,7 +62,10 @@ def icon(self) -> str: interval = timedelta( seconds=self._device.data["_state"].get("wakeupInterval", 30 * 60) ) - if self._last_comms < dt_util.utcnow() - interval * 3: + if ( + not self._last_comms + or self._last_comms < dt_util.utcnow() - interval * 3 + ): return "mdi:battery-unknown" battery_level = self._device.data["state"][self._state_attr] @@ -104,12 +107,12 @@ def __init__(self, broker, level) -> None: self._hub = broker.client self._unique_id = f"{broker.hub_uid}_{GH_LEVEL_MAPPING[level]}" - self._name = f"GeniusHub {GH_LEVEL_MAPPING[level]}" + self._attr_name = f"GeniusHub {GH_LEVEL_MAPPING[level]}" self._level = level - self._issues = [] + self._issues: list = [] @property - def native_value(self) -> str: + def native_value(self) -> int: """Return the number of issues.""" return len(self._issues) diff --git a/homeassistant/components/geniushub/water_heater.py b/homeassistant/components/geniushub/water_heater.py index f1ff8444d28d3f..a64aa1345e1051 100644 --- a/homeassistant/components/geniushub/water_heater.py +++ b/homeassistant/components/geniushub/water_heater.py @@ -73,7 +73,7 @@ def operation_list(self) -> list[str]: @property def current_operation(self) -> str: """Return the current operation mode.""" - return GH_STATE_TO_HA[self._zone.data["mode"]] + return GH_STATE_TO_HA[self._zone.data["mode"]] # type: ignore[return-value] async def async_set_operation_mode(self, operation_mode) -> None: """Set a new operation mode for this boiler.""" diff --git a/mypy.ini b/mypy.ini index 887a65394b7206..95c83dd1a5fa78 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2273,21 +2273,6 @@ ignore_errors = true [mypy-homeassistant.components.fireservicerota.switch] ignore_errors = true -[mypy-homeassistant.components.geniushub] -ignore_errors = true - -[mypy-homeassistant.components.geniushub.binary_sensor] -ignore_errors = true - -[mypy-homeassistant.components.geniushub.climate] -ignore_errors = true - -[mypy-homeassistant.components.geniushub.sensor] -ignore_errors = true - -[mypy-homeassistant.components.geniushub.water_heater] -ignore_errors = true - [mypy-homeassistant.components.google_assistant.helpers] ignore_errors = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index a78e07e057b6e3..ae435c16ef0372 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -48,11 +48,6 @@ "homeassistant.components.fireservicerota.binary_sensor", "homeassistant.components.fireservicerota.sensor", "homeassistant.components.fireservicerota.switch", - "homeassistant.components.geniushub", - "homeassistant.components.geniushub.binary_sensor", - "homeassistant.components.geniushub.climate", - "homeassistant.components.geniushub.sensor", - "homeassistant.components.geniushub.water_heater", "homeassistant.components.google_assistant.helpers", "homeassistant.components.google_assistant.http", "homeassistant.components.google_assistant.report_state", From c59115bb4f571f1f618693f9f060d63fcd2b3302 Mon Sep 17 00:00:00 2001 From: Poltorak Serguei Date: Wed, 23 Feb 2022 12:18:33 +0300 Subject: [PATCH 0974/1098] Add suggested area to the Z-Wave.Me integration (#66986) * Add Z-Wave.Me Device Info * fix * fix * small fix Co-authored-by: Dmitry Vlasov --- homeassistant/components/zwave_me/__init__.py | 17 +++++++++++++++-- homeassistant/components/zwave_me/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/zwave_me/__init__.py b/homeassistant/components/zwave_me/__init__.py index 42b510f9417f5d..6e2ee0fd58c42e 100644 --- a/homeassistant/components/zwave_me/__init__.py +++ b/homeassistant/components/zwave_me/__init__.py @@ -9,7 +9,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send -from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity import DeviceInfo, Entity from .const import DOMAIN, PLATFORMS, ZWaveMePlatform @@ -104,9 +104,22 @@ def __init__(self, controller, device): self.controller = controller self.device = device self._attr_name = device.title - self._attr_unique_id = f"{self.controller.config.unique_id}-{self.device.id}" + self._attr_unique_id: str = ( + f"{self.controller.config.unique_id}-{self.device.id}" + ) self._attr_should_poll = False + @property + def device_info(self) -> DeviceInfo: + """Return device specific attributes.""" + return DeviceInfo( + identifiers={(DOMAIN, self._attr_unique_id)}, + name=self._attr_name, + manufacturer=self.device.manufacturer, + sw_version=self.device.firmware, + suggested_area=self.device.locationName, + ) + async def async_added_to_hass(self) -> None: """Connect to an updater.""" self.async_on_remove( diff --git a/homeassistant/components/zwave_me/manifest.json b/homeassistant/components/zwave_me/manifest.json index 1a7177ca470111..8863cd6ebf7ec3 100644 --- a/homeassistant/components/zwave_me/manifest.json +++ b/homeassistant/components/zwave_me/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/zwave_me", "iot_class": "local_push", "requirements": [ - "zwave_me_ws==0.1.23", + "zwave_me_ws==0.2.1", "url-normalize==1.4.1" ], "after_dependencies": ["zeroconf"], diff --git a/requirements_all.txt b/requirements_all.txt index 925df830952b2a..66c31d0b25d42b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2569,4 +2569,4 @@ zm-py==0.5.2 zwave-js-server-python==0.35.1 # homeassistant.components.zwave_me -zwave_me_ws==0.1.23 +zwave_me_ws==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2a44bfa63619c2..3ebda80093bb71 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1594,4 +1594,4 @@ zigpy==0.43.0 zwave-js-server-python==0.35.1 # homeassistant.components.zwave_me -zwave_me_ws==0.1.23 +zwave_me_ws==0.2.1 From 0e54bd448a90dc048f11c3e54f9aca4b742d9b5c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 23 Feb 2022 11:31:31 +0100 Subject: [PATCH 0975/1098] Remove unused attribute [litterrobot] (#67106) --- homeassistant/components/litterrobot/hub.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/litterrobot/hub.py b/homeassistant/components/litterrobot/hub.py index 49fba822a6f369..43d60e534eae92 100644 --- a/homeassistant/components/litterrobot/hub.py +++ b/homeassistant/components/litterrobot/hub.py @@ -27,7 +27,6 @@ class LitterRobotHub: def __init__(self, hass: HomeAssistant, data: Mapping) -> None: """Initialize the Litter-Robot hub.""" self._data = data - self.logged_in = False async def _async_update_data() -> bool: """Update all device states from the Litter-Robot API.""" From 4fecd5d8af0337fa8fb1bdd646f9fbf19d831e7b Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 23 Feb 2022 11:53:02 +0100 Subject: [PATCH 0976/1098] Fix type issues [fireservicerota] (#67094) Co-authored-by: Franck Nijhof --- .../components/fireservicerota/__init__.py | 10 ++++---- .../fireservicerota/binary_sensor.py | 24 ++++++++++++++----- .../components/fireservicerota/sensor.py | 5 ++-- .../components/fireservicerota/switch.py | 8 ++++--- mypy.ini | 12 ---------- script/hassfest/mypy_config.py | 4 ---- 6 files changed, 32 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/fireservicerota/__init__.py b/homeassistant/components/fireservicerota/__init__.py index 4cdb7ec0c42fd0..ffd8230794065d 100644 --- a/homeassistant/components/fireservicerota/__init__.py +++ b/homeassistant/components/fireservicerota/__init__.py @@ -1,4 +1,6 @@ """The FireServiceRota integration.""" +from __future__ import annotations + from datetime import timedelta import logging @@ -195,25 +197,25 @@ async def update_call(self, func, *args): return await self._hass.async_add_executor_job(func, *args) - async def async_update(self) -> object: + async def async_update(self) -> dict | None: """Get the latest availability data.""" data = await self.update_call( self.fsr.get_availability, str(self._hass.config.time_zone) ) if not data: - return + return None self.on_duty = bool(data.get("available")) _LOGGER.debug("Updated availability data: %s", data) return data - async def async_response_update(self) -> object: + async def async_response_update(self) -> dict | None: """Get the latest incident response data.""" if not self.incident_id: - return + return None _LOGGER.debug("Updating response data for incident id %s", self.incident_id) diff --git a/homeassistant/components/fireservicerota/binary_sensor.py b/homeassistant/components/fireservicerota/binary_sensor.py index c175e58cae8765..9e4d5b123f5a61 100644 --- a/homeassistant/components/fireservicerota/binary_sensor.py +++ b/homeassistant/components/fireservicerota/binary_sensor.py @@ -1,4 +1,8 @@ """Binary Sensor platform for FireServiceRota integration.""" +from __future__ import annotations + +from typing import Any + from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -8,6 +12,7 @@ DataUpdateCoordinator, ) +from . import FireServiceRotaClient from .const import DATA_CLIENT, DATA_COORDINATOR, DOMAIN as FIRESERVICEROTA_DOMAIN @@ -16,7 +21,9 @@ async def async_setup_entry( ) -> None: """Set up FireServiceRota binary sensor based on a config entry.""" - client = hass.data[FIRESERVICEROTA_DOMAIN][entry.entry_id][DATA_CLIENT] + client: FireServiceRotaClient = hass.data[FIRESERVICEROTA_DOMAIN][entry.entry_id][ + DATA_CLIENT + ] coordinator: DataUpdateCoordinator = hass.data[FIRESERVICEROTA_DOMAIN][ entry.entry_id @@ -28,13 +35,18 @@ async def async_setup_entry( class ResponseBinarySensor(CoordinatorEntity, BinarySensorEntity): """Representation of an FireServiceRota sensor.""" - def __init__(self, coordinator: DataUpdateCoordinator, client, entry): + def __init__( + self, + coordinator: DataUpdateCoordinator, + client: FireServiceRotaClient, + entry: ConfigEntry, + ) -> None: """Initialize.""" super().__init__(coordinator) self._client = client self._unique_id = f"{entry.unique_id}_Duty" - self._state = None + self._state: bool | None = None @property def name(self) -> str: @@ -55,7 +67,7 @@ def unique_id(self) -> str: return self._unique_id @property - def is_on(self) -> bool: + def is_on(self) -> bool | None: """Return the state of the binary sensor.""" self._state = self._client.on_duty @@ -63,9 +75,9 @@ def is_on(self) -> bool: return self._state @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any]: """Return available attributes for binary sensor.""" - attr = {} + attr: dict[str, Any] = {} if not self.coordinator.data: return attr diff --git a/homeassistant/components/fireservicerota/sensor.py b/homeassistant/components/fireservicerota/sensor.py index a17892a8591813..66878b731452ec 100644 --- a/homeassistant/components/fireservicerota/sensor.py +++ b/homeassistant/components/fireservicerota/sensor.py @@ -1,5 +1,6 @@ """Sensor platform for FireServiceRota integration.""" import logging +from typing import Any from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry @@ -65,9 +66,9 @@ def should_poll(self) -> bool: return False @property - def extra_state_attributes(self) -> object: + def extra_state_attributes(self) -> dict[str, Any]: """Return available attributes for sensor.""" - attr = {} + attr: dict[str, Any] = {} if not (data := self._state_attributes): return attr diff --git a/homeassistant/components/fireservicerota/switch.py b/homeassistant/components/fireservicerota/switch.py index 7202420e571a41..e625ac5deb55b8 100644 --- a/homeassistant/components/fireservicerota/switch.py +++ b/homeassistant/components/fireservicerota/switch.py @@ -1,5 +1,6 @@ """Switch platform for FireServiceRota integration.""" import logging +from typing import Any from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry @@ -73,9 +74,9 @@ def available(self): return self._client.on_duty @property - def extra_state_attributes(self) -> object: + def extra_state_attributes(self) -> dict[str, Any]: """Return available attributes for switch.""" - attr = {} + attr: dict[str, Any] = {} if not self._state_attributes: return attr @@ -140,10 +141,11 @@ async def async_update(self) -> bool: data = await self._client.async_response_update() if not data or "status" not in data: - return + return False self._state = data["status"] == "acknowledged" self._state_attributes = data self._state_icon = data["status"] _LOGGER.debug("Set state of entity 'Response Switch' to '%s'", self._state) + return True diff --git a/mypy.ini b/mypy.ini index 95c83dd1a5fa78..dbbbe8245407a0 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2261,18 +2261,6 @@ ignore_errors = true [mypy-homeassistant.components.evohome.water_heater] ignore_errors = true -[mypy-homeassistant.components.fireservicerota] -ignore_errors = true - -[mypy-homeassistant.components.fireservicerota.binary_sensor] -ignore_errors = true - -[mypy-homeassistant.components.fireservicerota.sensor] -ignore_errors = true - -[mypy-homeassistant.components.fireservicerota.switch] -ignore_errors = true - [mypy-homeassistant.components.google_assistant.helpers] ignore_errors = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index ae435c16ef0372..8842b42fbd3d94 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -44,10 +44,6 @@ "homeassistant.components.evohome", "homeassistant.components.evohome.climate", "homeassistant.components.evohome.water_heater", - "homeassistant.components.fireservicerota", - "homeassistant.components.fireservicerota.binary_sensor", - "homeassistant.components.fireservicerota.sensor", - "homeassistant.components.fireservicerota.switch", "homeassistant.components.google_assistant.helpers", "homeassistant.components.google_assistant.http", "homeassistant.components.google_assistant.report_state", From 9db2d8768bd1cbeaaad73ce99f331bf8523174f7 Mon Sep 17 00:00:00 2001 From: Ryan Fleming Date: Wed, 23 Feb 2022 05:59:28 -0500 Subject: [PATCH 0977/1098] Add tools to octoprint when the printer comes back online (#59666) Co-authored-by: Martin Hjelmare --- .../components/octoprint/__init__.py | 7 ++- homeassistant/components/octoprint/sensor.py | 45 +++++++++++++------ 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/octoprint/__init__.py b/homeassistant/components/octoprint/__init__.py index f92cf0c8d30b7f..eded3bec16a4a8 100644 --- a/homeassistant/components/octoprint/__init__.py +++ b/homeassistant/components/octoprint/__init__.py @@ -1,4 +1,6 @@ """Support for monitoring OctoPrint 3D printers.""" +from __future__ import annotations + from datetime import timedelta import logging from typing import cast @@ -175,7 +177,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await coordinator.async_config_entry_first_refresh() - hass.data[DOMAIN][entry.entry_id] = {"coordinator": coordinator, "client": client} + hass.data[DOMAIN][entry.entry_id] = { + "coordinator": coordinator, + "client": client, + } hass.config_entries.async_setup_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/octoprint/sensor.py b/homeassistant/components/octoprint/sensor.py index 8057de5596668d..5a094c10987c55 100644 --- a/homeassistant/components/octoprint/sensor.py +++ b/homeassistant/components/octoprint/sensor.py @@ -13,7 +13,7 @@ ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE, TEMP_CELSIUS -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -47,13 +47,23 @@ async def async_setup_entry( assert device_id is not None - entities: list[SensorEntity] = [] - if coordinator.data["printer"]: - printer_info = coordinator.data["printer"] - types = ["actual", "target"] - for tool in printer_info.temperatures: - for temp_type in types: - entities.append( + known_tools = set() + + @callback + def async_add_tool_sensors() -> None: + if not coordinator.data["printer"]: + return + + new_tools = [] + for tool in [ + tool + for tool in coordinator.data["printer"].temperatures + if tool.name not in known_tools + ]: + assert device_id is not None + known_tools.add(tool.name) + for temp_type in ("actual", "target"): + new_tools.append( OctoPrintTemperatureSensor( coordinator, tool.name, @@ -61,13 +71,20 @@ async def async_setup_entry( device_id, ) ) - else: - _LOGGER.debug("Printer appears to be offline, skipping temperature sensors") + if new_tools: + async_add_entities(new_tools) + + config_entry.async_on_unload(coordinator.async_add_listener(async_add_tool_sensors)) - entities.append(OctoPrintStatusSensor(coordinator, device_id)) - entities.append(OctoPrintJobPercentageSensor(coordinator, device_id)) - entities.append(OctoPrintEstimatedFinishTimeSensor(coordinator, device_id)) - entities.append(OctoPrintStartTimeSensor(coordinator, device_id)) + if coordinator.data["printer"]: + async_add_tool_sensors() + + entities: list[SensorEntity] = [ + OctoPrintStatusSensor(coordinator, device_id), + OctoPrintJobPercentageSensor(coordinator, device_id), + OctoPrintEstimatedFinishTimeSensor(coordinator, device_id), + OctoPrintStartTimeSensor(coordinator, device_id), + ] async_add_entities(entities) From 6c922e1fdb9c405a7f337075be16e1cf2becaaf9 Mon Sep 17 00:00:00 2001 From: Matthias Lohr Date: Wed, 23 Feb 2022 12:08:28 +0100 Subject: [PATCH 0978/1098] Add number platform to tolo integration (#66799) --- .coveragerc | 1 + homeassistant/components/tolo/__init__.py | 1 + homeassistant/components/tolo/const.py | 4 + homeassistant/components/tolo/number.py | 111 ++++++++++++++++++++++ 4 files changed, 117 insertions(+) create mode 100644 homeassistant/components/tolo/number.py diff --git a/.coveragerc b/.coveragerc index 1784bdad2f6b9f..0d79bb02a4a70a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1246,6 +1246,7 @@ omit = homeassistant/components/tolo/climate.py homeassistant/components/tolo/fan.py homeassistant/components/tolo/light.py + homeassistant/components/tolo/number.py homeassistant/components/tolo/select.py homeassistant/components/tolo/sensor.py homeassistant/components/tomato/device_tracker.py diff --git a/homeassistant/components/tolo/__init__.py b/homeassistant/components/tolo/__init__.py index 6e95089ccbea20..6f17379dfbf94f 100644 --- a/homeassistant/components/tolo/__init__.py +++ b/homeassistant/components/tolo/__init__.py @@ -28,6 +28,7 @@ Platform.CLIMATE, Platform.FAN, Platform.LIGHT, + Platform.NUMBER, Platform.SELECT, Platform.SENSOR, ] diff --git a/homeassistant/components/tolo/const.py b/homeassistant/components/tolo/const.py index bfd700bb955294..77bee92d521ac8 100644 --- a/homeassistant/components/tolo/const.py +++ b/homeassistant/components/tolo/const.py @@ -11,3 +11,7 @@ DEFAULT_MAX_HUMIDITY = 99 DEFAULT_MIN_HUMIDITY = 60 + +POWER_TIMER_MAX = 60 +SALT_BATH_TIMER_MAX = 60 +FAN_TIMER_MAX = 60 diff --git a/homeassistant/components/tolo/number.py b/homeassistant/components/tolo/number.py new file mode 100644 index 00000000000000..85d80756020d83 --- /dev/null +++ b/homeassistant/components/tolo/number.py @@ -0,0 +1,111 @@ +"""TOLO Sauna number controls.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any + +from tololib import ToloClient +from tololib.message_info import SettingsInfo + +from homeassistant.components.number import NumberEntity, NumberEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import TIME_MINUTES +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import ToloSaunaCoordinatorEntity, ToloSaunaUpdateCoordinator +from .const import DOMAIN, FAN_TIMER_MAX, POWER_TIMER_MAX, SALT_BATH_TIMER_MAX + + +@dataclass +class ToloNumberEntityDescriptionBase: + """Required values when describing TOLO Number entities.""" + + getter: Callable[[SettingsInfo], int | None] + setter: Callable[[ToloClient, int | None], Any] + + +@dataclass +class ToloNumberEntityDescription( + NumberEntityDescription, ToloNumberEntityDescriptionBase +): + """Class describing TOLO Number entities.""" + + entity_category = EntityCategory.CONFIG + min_value = 0 + step = 1 + + +NUMBERS = ( + ToloNumberEntityDescription( + key="power_timer", + icon="mdi:power-settings", + name="Power Timer", + unit_of_measurement=TIME_MINUTES, + max_value=POWER_TIMER_MAX, + getter=lambda settings: settings.power_timer, + setter=lambda client, value: client.set_power_timer(value), + ), + ToloNumberEntityDescription( + key="salt_bath_timer", + icon="mdi:shaker-outline", + name="Salt Bath Timer", + unit_of_measurement=TIME_MINUTES, + max_value=SALT_BATH_TIMER_MAX, + getter=lambda settings: settings.salt_bath_timer, + setter=lambda client, value: client.set_salt_bath_timer(value), + ), + ToloNumberEntityDescription( + key="fan_timer", + icon="mdi:fan-auto", + name="Fan Timer", + unit_of_measurement=TIME_MINUTES, + max_value=FAN_TIMER_MAX, + getter=lambda settings: settings.fan_timer, + setter=lambda client, value: client.set_fan_timer(value), + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up number controls for TOLO Sauna.""" + coordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + ToloNumberEntity(coordinator, entry, description) for description in NUMBERS + ) + + +class ToloNumberEntity(ToloSaunaCoordinatorEntity, NumberEntity): + """TOLO Number entity.""" + + entity_description: ToloNumberEntityDescription + + def __init__( + self, + coordinator: ToloSaunaUpdateCoordinator, + entry: ConfigEntry, + entity_description: ToloNumberEntityDescription, + ) -> None: + """Initialize TOLO Number entity.""" + super().__init__(coordinator, entry) + self.entity_description = entity_description + self._attr_unique_id = f"{entry.entry_id}_{entity_description.key}" + + @property + def value(self) -> float: + """Return the value of this TOLO Number entity.""" + return self.entity_description.getter(self.coordinator.data.settings) or 0 + + def set_value(self, value: float) -> None: + """Set the value of this TOLO Number entity.""" + int_value = int(value) + if int_value == 0: + self.entity_description.setter(self.coordinator.client, None) + return + self.entity_description.setter(self.coordinator.client, int_value) From 34bae4dcd45b3d70d8a131ae683c584948aea63f Mon Sep 17 00:00:00 2001 From: Matthias Lohr Date: Wed, 23 Feb 2022 12:08:47 +0100 Subject: [PATCH 0979/1098] Add timer sensors for TOLO (#66938) --- homeassistant/components/tolo/sensor.py | 138 +++++++++++++++++------- 1 file changed, 98 insertions(+), 40 deletions(-) diff --git a/homeassistant/components/tolo/sensor.py b/homeassistant/components/tolo/sensor.py index bcdb7db016535b..714667cc1f83b8 100644 --- a/homeassistant/components/tolo/sensor.py +++ b/homeassistant/components/tolo/sensor.py @@ -1,12 +1,19 @@ """TOLO Sauna (non-binary, general) sensors.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass + +from tololib.message_info import SettingsInfo, StatusInfo from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, + SensorEntityDescription, SensorStateClass, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import PERCENTAGE, TEMP_CELSIUS +from homeassistant.const import PERCENTAGE, TEMP_CELSIUS, TIME_MINUTES from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -15,6 +22,75 @@ from .const import DOMAIN +@dataclass +class ToloSensorEntityDescriptionBase: + """Required values when describing TOLO Sensor entities.""" + + getter: Callable[[StatusInfo], int | None] + availability_checker: Callable[[SettingsInfo, StatusInfo], bool] | None + + +@dataclass +class ToloSensorEntityDescription( + SensorEntityDescription, ToloSensorEntityDescriptionBase +): + """Class describing TOLO Sensor entities.""" + + state_class = SensorStateClass.MEASUREMENT + + +SENSORS = ( + ToloSensorEntityDescription( + key="water_level", + entity_category=EntityCategory.DIAGNOSTIC, + icon="mdi:waves-arrow-up", + name="Water Level", + native_unit_of_measurement=PERCENTAGE, + getter=lambda status: status.water_level_percent, + availability_checker=None, + ), + ToloSensorEntityDescription( + key="tank_temperature", + device_class=SensorDeviceClass.TEMPERATURE, + entity_category=EntityCategory.DIAGNOSTIC, + name="Tank Temperature", + native_unit_of_measurement=TEMP_CELSIUS, + getter=lambda status: status.tank_temperature, + availability_checker=None, + ), + ToloSensorEntityDescription( + key="power_timer_remaining", + entity_category=EntityCategory.DIAGNOSTIC, + icon="mdi:power-settings", + name="Power Timer", + native_unit_of_measurement=TIME_MINUTES, + getter=lambda status: status.power_timer, + availability_checker=lambda settings, status: status.power_on + and settings.power_timer is not None, + ), + ToloSensorEntityDescription( + key="salt_bath_timer_remaining", + entity_category=EntityCategory.DIAGNOSTIC, + icon="mdi:shaker-outline", + name="Salt Bath Timer", + native_unit_of_measurement=TIME_MINUTES, + getter=lambda status: status.salt_bath_timer, + availability_checker=lambda settings, status: status.salt_bath_on + and settings.salt_bath_timer is not None, + ), + ToloSensorEntityDescription( + key="fan_timer_remaining", + entity_category=EntityCategory.DIAGNOSTIC, + icon="mdi:fan-auto", + name="Fan Timer", + native_unit_of_measurement=TIME_MINUTES, + getter=lambda status: status.fan_timer, + availability_checker=lambda settings, status: status.fan_on + and settings.fan_timer is not None, + ), +) + + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, @@ -23,54 +99,36 @@ async def async_setup_entry( """Set up (non-binary, general) sensors for TOLO Sauna.""" coordinator = hass.data[DOMAIN][entry.entry_id] async_add_entities( - [ - ToloWaterLevelSensor(coordinator, entry), - ToloTankTemperatureSensor(coordinator, entry), - ] + ToloSensorEntity(coordinator, entry, description) for description in SENSORS ) -class ToloWaterLevelSensor(ToloSaunaCoordinatorEntity, SensorEntity): - """Sensor for tank water level.""" +class ToloSensorEntity(ToloSaunaCoordinatorEntity, SensorEntity): + """TOLO Number entity.""" - _attr_entity_category = EntityCategory.DIAGNOSTIC - _attr_name = "Water Level" - _attr_icon = "mdi:waves-arrow-up" - _attr_state_class = SensorStateClass.MEASUREMENT - _attr_native_unit_of_measurement = PERCENTAGE + entity_description: ToloSensorEntityDescription def __init__( - self, coordinator: ToloSaunaUpdateCoordinator, entry: ConfigEntry + self, + coordinator: ToloSaunaUpdateCoordinator, + entry: ConfigEntry, + entity_description: ToloSensorEntityDescription, ) -> None: - """Initialize TOLO Sauna tank water level sensor entity.""" + """Initialize TOLO Number entity.""" super().__init__(coordinator, entry) - - self._attr_unique_id = f"{entry.entry_id}_water_level" + self.entity_description = entity_description + self._attr_unique_id = f"{entry.entry_id}_{entity_description.key}" @property - def native_value(self) -> int: - """Return current tank water level.""" - return self.coordinator.data.status.water_level_percent - - -class ToloTankTemperatureSensor(ToloSaunaCoordinatorEntity, SensorEntity): - """Sensor for tank temperature.""" - - _attr_entity_category = EntityCategory.DIAGNOSTIC - _attr_name = "Tank Temperature" - _attr_device_class = SensorDeviceClass.TEMPERATURE - _attr_state_class = SensorStateClass.MEASUREMENT - _attr_native_unit_of_measurement = TEMP_CELSIUS - - def __init__( - self, coordinator: ToloSaunaUpdateCoordinator, entry: ConfigEntry - ) -> None: - """Initialize TOLO Sauna tank temperature sensor entity.""" - super().__init__(coordinator, entry) - - self._attr_unique_id = f"{entry.entry_id}_tank_temperature" + def available(self) -> bool: + """Return availability of the TOLO sensor.""" + if self.entity_description.availability_checker is None: + return super().available + return self.entity_description.availability_checker( + self.coordinator.data.settings, self.coordinator.data.status + ) @property - def native_value(self) -> int: - """Return current tank temperature.""" - return self.coordinator.data.status.tank_temperature + def native_value(self) -> int | None: + """Return native value of the TOLO sensor.""" + return self.entity_description.getter(self.coordinator.data.status) From e1989e285896e07fb6f4a5f09dcf5039c722a16e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 23 Feb 2022 01:15:31 -1000 Subject: [PATCH 0980/1098] Enable strict typing for powerwall (#65577) --- .strict-typing | 1 + .../components/powerwall/__init__.py | 305 ++++++++---------- .../components/powerwall/binary_sensor.py | 140 +++----- .../components/powerwall/config_flow.py | 29 +- homeassistant/components/powerwall/const.py | 28 +- homeassistant/components/powerwall/entity.py | 43 +-- .../components/powerwall/manifest.json | 2 +- homeassistant/components/powerwall/models.py | 50 +++ homeassistant/components/powerwall/sensor.py | 109 ++----- mypy.ini | 11 + requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 12 files changed, 327 insertions(+), 395 deletions(-) create mode 100644 homeassistant/components/powerwall/models.py diff --git a/.strict-typing b/.strict-typing index cd148430340e5d..be25c50ae38b8f 100644 --- a/.strict-typing +++ b/.strict-typing @@ -143,6 +143,7 @@ homeassistant.components.openuv.* homeassistant.components.overkiz.* homeassistant.components.persistent_notification.* homeassistant.components.pi_hole.* +homeassistant.components.powerwall.* homeassistant.components.proximity.* homeassistant.components.pvoutput.* homeassistant.components.pure_energie.* diff --git a/homeassistant/components/powerwall/__init__.py b/homeassistant/components/powerwall/__init__.py index 8d91b984d46724..10504e2aa0662c 100644 --- a/homeassistant/components/powerwall/__init__.py +++ b/homeassistant/components/powerwall/__init__.py @@ -1,4 +1,6 @@ """The Tesla Powerwall integration.""" +from __future__ import annotations + import contextlib from datetime import timedelta import logging @@ -16,9 +18,8 @@ from homeassistant.components import persistent_notification from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, Platform -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady -from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.util.network import is_ip_address @@ -26,21 +27,12 @@ from .const import ( DOMAIN, POWERWALL_API_CHANGED, - POWERWALL_API_CHARGE, - POWERWALL_API_DEVICE_TYPE, - POWERWALL_API_GATEWAY_DIN, - POWERWALL_API_GRID_SERVICES_ACTIVE, - POWERWALL_API_GRID_STATUS, - POWERWALL_API_METERS, - POWERWALL_API_SERIAL_NUMBERS, - POWERWALL_API_SITE_INFO, - POWERWALL_API_SITEMASTER, - POWERWALL_API_STATUS, POWERWALL_COORDINATOR, POWERWALL_HTTP_SESSION, - POWERWALL_OBJECT, + POWERWALL_LOGIN_FAILED_COUNT, UPDATE_INTERVAL, ) +from .models import PowerwallBaseInfo, PowerwallData, PowerwallRuntimeData CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False) @@ -50,211 +42,194 @@ MAX_LOGIN_FAILURES = 5 +API_CHANGED_ERROR_BODY = ( + "It seems like your powerwall uses an unsupported version. " + "Please update the software of your powerwall or if it is " + "already the newest consider reporting this issue.\nSee logs for more information" +) +API_CHANGED_TITLE = "Unknown powerwall software version" + + +class PowerwallDataManager: + """Class to manager powerwall data and relogin on failure.""" + + def __init__( + self, + hass: HomeAssistant, + power_wall: Powerwall, + ip_address: str, + password: str | None, + runtime_data: PowerwallRuntimeData, + ) -> None: + """Init the data manager.""" + self.hass = hass + self.ip_address = ip_address + self.password = password + self.runtime_data = runtime_data + self.power_wall = power_wall + + @property + def login_failed_count(self) -> int: + """Return the current number of failed logins.""" + return self.runtime_data[POWERWALL_LOGIN_FAILED_COUNT] + + @property + def api_changed(self) -> int: + """Return true if the api has changed out from under us.""" + return self.runtime_data[POWERWALL_API_CHANGED] + + def _increment_failed_logins(self) -> None: + self.runtime_data[POWERWALL_LOGIN_FAILED_COUNT] += 1 + + def _clear_failed_logins(self) -> None: + self.runtime_data[POWERWALL_LOGIN_FAILED_COUNT] = 0 + + def _recreate_powerwall_login(self) -> None: + """Recreate the login on auth failure.""" + http_session = self.runtime_data[POWERWALL_HTTP_SESSION] + http_session.close() + http_session = requests.Session() + self.runtime_data[POWERWALL_HTTP_SESSION] = http_session + self.power_wall = Powerwall(self.ip_address, http_session=http_session) + self.power_wall.login(self.password or "") -async def _migrate_old_unique_ids(hass, entry_id, powerwall_data): - serial_numbers = powerwall_data[POWERWALL_API_SERIAL_NUMBERS] - site_info = powerwall_data[POWERWALL_API_SITE_INFO] - - @callback - def _async_migrator(entity_entry: entity_registry.RegistryEntry): - parts = entity_entry.unique_id.split("_") - # Check if the unique_id starts with the serial_numbers of the powerwalls - if parts[0 : len(serial_numbers)] != serial_numbers: - # The old unique_id ended with the nomianal_system_engery_kWh so we can use that - # to find the old base unique_id and extract the device_suffix. - normalized_energy_index = ( - len(parts) - 1 - parts[::-1].index(str(site_info.nominal_system_energy)) - ) - device_suffix = parts[normalized_energy_index + 1 :] - - new_unique_id = "_".join([*serial_numbers, *device_suffix]) - _LOGGER.info( - "Migrating unique_id from [%s] to [%s]", - entity_entry.unique_id, - new_unique_id, - ) - return {"new_unique_id": new_unique_id} - return None - - await entity_registry.async_migrate_entries(hass, entry_id, _async_migrator) - - -async def _async_handle_api_changed_error( - hass: HomeAssistant, error: MissingAttributeError -): - # The error might include some important information about what exactly changed. - _LOGGER.error(str(error)) - persistent_notification.async_create( - hass, - "It seems like your powerwall uses an unsupported version. " - "Please update the software of your powerwall or if it is " - "already the newest consider reporting this issue.\nSee logs for more information", - title="Unknown powerwall software version", - ) + async def async_update_data(self) -> PowerwallData: + """Fetch data from API endpoint.""" + # Check if we had an error before + _LOGGER.debug("Checking if update failed") + if self.api_changed: + raise UpdateFailed("The powerwall api has changed") + return await self.hass.async_add_executor_job(self._update_data) + + def _update_data(self) -> PowerwallData: + """Fetch data from API endpoint.""" + _LOGGER.debug("Updating data") + for attempt in range(2): + try: + if attempt == 1: + self._recreate_powerwall_login() + data = _fetch_powerwall_data(self.power_wall) + except PowerwallUnreachableError as err: + raise UpdateFailed("Unable to fetch data from powerwall") from err + except MissingAttributeError as err: + _LOGGER.error("The powerwall api has changed: %s", str(err)) + # The error might include some important information about what exactly changed. + persistent_notification.create( + self.hass, API_CHANGED_ERROR_BODY, API_CHANGED_TITLE + ) + self.runtime_data[POWERWALL_API_CHANGED] = True + raise UpdateFailed("The powerwall api has changed") from err + except AccessDeniedError as err: + if attempt == 1: + self._increment_failed_logins() + raise ConfigEntryAuthFailed from err + if self.password is None: + raise ConfigEntryAuthFailed from err + raise UpdateFailed( + f"Login attempt {self.login_failed_count}/{MAX_LOGIN_FAILURES} failed, will retry: {err}" + ) from err + except APIError as err: + raise UpdateFailed(f"Updated failed due to {err}, will retry") from err + else: + self._clear_failed_logins() + return data + raise RuntimeError("unreachable") async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Tesla Powerwall from a config entry.""" - - entry_id = entry.entry_id - - hass.data.setdefault(DOMAIN, {}) http_session = requests.Session() ip_address = entry.data[CONF_IP_ADDRESS] password = entry.data.get(CONF_PASSWORD) power_wall = Powerwall(ip_address, http_session=http_session) try: - powerwall_data = await hass.async_add_executor_job( - _login_and_fetch_base_info, power_wall, password + base_info = await hass.async_add_executor_job( + _login_and_fetch_base_info, power_wall, ip_address, password ) except PowerwallUnreachableError as err: http_session.close() raise ConfigEntryNotReady from err except MissingAttributeError as err: http_session.close() - await _async_handle_api_changed_error(hass, err) + # The error might include some important information about what exactly changed. + _LOGGER.error("The powerwall api has changed: %s", str(err)) + persistent_notification.async_create( + hass, API_CHANGED_ERROR_BODY, API_CHANGED_TITLE + ) return False except AccessDeniedError as err: _LOGGER.debug("Authentication failed", exc_info=err) http_session.close() raise ConfigEntryAuthFailed from err - await _migrate_old_unique_ids(hass, entry_id, powerwall_data) - - gateway_din = powerwall_data[POWERWALL_API_GATEWAY_DIN] + gateway_din = base_info.gateway_din if gateway_din and entry.unique_id is not None and is_ip_address(entry.unique_id): hass.config_entries.async_update_entry(entry, unique_id=gateway_din) - login_failed_count = 0 - - runtime_data = hass.data[DOMAIN][entry.entry_id] = { - POWERWALL_API_CHANGED: False, - POWERWALL_HTTP_SESSION: http_session, - } - - def _recreate_powerwall_login(): - nonlocal http_session - nonlocal power_wall - http_session.close() - http_session = requests.Session() - power_wall = Powerwall(ip_address, http_session=http_session) - runtime_data[POWERWALL_OBJECT] = power_wall - runtime_data[POWERWALL_HTTP_SESSION] = http_session - power_wall.login(password) - - async def _async_login_and_retry_update_data(): - """Retry the update after a failed login.""" - nonlocal login_failed_count - # If the session expired, recreate, relogin, and try again - _LOGGER.debug("Retrying login and updating data") - try: - await hass.async_add_executor_job(_recreate_powerwall_login) - data = await _async_update_powerwall_data(hass, entry, power_wall) - except AccessDeniedError as err: - login_failed_count += 1 - if login_failed_count == MAX_LOGIN_FAILURES: - raise ConfigEntryAuthFailed from err - raise UpdateFailed( - f"Login attempt {login_failed_count}/{MAX_LOGIN_FAILURES} failed, will retry: {err}" - ) from err - except APIError as err: - raise UpdateFailed(f"Updated failed due to {err}, will retry") from err - else: - login_failed_count = 0 - return data - - async def async_update_data(): - """Fetch data from API endpoint.""" - # Check if we had an error before - nonlocal login_failed_count - _LOGGER.debug("Checking if update failed") - if runtime_data[POWERWALL_API_CHANGED]: - return runtime_data[POWERWALL_COORDINATOR].data + runtime_data = PowerwallRuntimeData( + api_changed=False, + base_info=base_info, + http_session=http_session, + login_failed_count=0, + coordinator=None, + ) - _LOGGER.debug("Updating data") - try: - data = await _async_update_powerwall_data(hass, entry, power_wall) - except AccessDeniedError as err: - if password is None: - raise ConfigEntryAuthFailed from err - return await _async_login_and_retry_update_data() - except APIError as err: - raise UpdateFailed(f"Updated failed due to {err}, will retry") from err - else: - login_failed_count = 0 - return data + manager = PowerwallDataManager(hass, power_wall, ip_address, password, runtime_data) coordinator = DataUpdateCoordinator( hass, _LOGGER, name="Powerwall site", - update_method=async_update_data, + update_method=manager.async_update_data, update_interval=timedelta(seconds=UPDATE_INTERVAL), ) - runtime_data.update( - { - **powerwall_data, - POWERWALL_OBJECT: power_wall, - POWERWALL_COORDINATOR: coordinator, - } - ) - await coordinator.async_config_entry_first_refresh() - hass.config_entries.async_setup_platforms(entry, PLATFORMS) + runtime_data[POWERWALL_COORDINATOR] = coordinator - return True + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = runtime_data + hass.config_entries.async_setup_platforms(entry, PLATFORMS) -async def _async_update_powerwall_data( - hass: HomeAssistant, entry: ConfigEntry, power_wall: Powerwall -): - """Fetch updated powerwall data.""" - try: - return await hass.async_add_executor_job(_fetch_powerwall_data, power_wall) - except PowerwallUnreachableError as err: - raise UpdateFailed("Unable to fetch data from powerwall") from err - except MissingAttributeError as err: - await _async_handle_api_changed_error(hass, err) - hass.data[DOMAIN][entry.entry_id][POWERWALL_API_CHANGED] = True - # Returns the cached data. This data can also be None - return hass.data[DOMAIN][entry.entry_id][POWERWALL_COORDINATOR].data + return True -def _login_and_fetch_base_info(power_wall: Powerwall, password: str): +def _login_and_fetch_base_info( + power_wall: Powerwall, host: str, password: str +) -> PowerwallBaseInfo: """Login to the powerwall and fetch the base info.""" if password is not None: power_wall.login(password) - power_wall.detect_and_pin_version() - return call_base_info(power_wall) + return call_base_info(power_wall, host) -def call_base_info(power_wall): - """Wrap powerwall properties to be a callable.""" +def call_base_info(power_wall: Powerwall, host: str) -> PowerwallBaseInfo: + """Return PowerwallBaseInfo for the device.""" # Make sure the serial numbers always have the same order gateway_din = None - with contextlib.suppress((AssertionError, PowerwallError)): + with contextlib.suppress(AssertionError, PowerwallError): gateway_din = power_wall.get_gateway_din().upper() - return { - POWERWALL_API_SITE_INFO: power_wall.get_site_info(), - POWERWALL_API_STATUS: power_wall.get_status(), - POWERWALL_API_DEVICE_TYPE: power_wall.get_device_type(), - POWERWALL_API_SERIAL_NUMBERS: sorted(power_wall.get_serial_numbers()), - POWERWALL_API_GATEWAY_DIN: gateway_din, - } + return PowerwallBaseInfo( + gateway_din=gateway_din, + site_info=power_wall.get_site_info(), + status=power_wall.get_status(), + device_type=power_wall.get_device_type(), + serial_numbers=sorted(power_wall.get_serial_numbers()), + url=f"https://{host}", + ) -def _fetch_powerwall_data(power_wall): +def _fetch_powerwall_data(power_wall: Powerwall) -> PowerwallData: """Process and update powerwall data.""" - return { - POWERWALL_API_CHARGE: power_wall.get_charge(), - POWERWALL_API_SITEMASTER: power_wall.get_sitemaster(), - POWERWALL_API_METERS: power_wall.get_meters(), - POWERWALL_API_GRID_SERVICES_ACTIVE: power_wall.is_grid_services_active(), - POWERWALL_API_GRID_STATUS: power_wall.get_grid_status(), - } + return PowerwallData( + charge=power_wall.get_charge(), + site_master=power_wall.get_sitemaster(), + meters=power_wall.get_meters(), + grid_services_active=power_wall.is_grid_services_active(), + grid_status=power_wall.get_grid_status(), + ) async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/powerwall/binary_sensor.py b/homeassistant/components/powerwall/binary_sensor.py index 5c8ebbdcf1926e..868d9e3076dbc9 100644 --- a/homeassistant/components/powerwall/binary_sensor.py +++ b/homeassistant/components/powerwall/binary_sensor.py @@ -1,4 +1,5 @@ """Support for powerwall binary sensors.""" + from tesla_powerwall import GridStatus, MeterType from homeassistant.components.binary_sensor import ( @@ -9,19 +10,9 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import ( - DOMAIN, - POWERWALL_API_DEVICE_TYPE, - POWERWALL_API_GRID_SERVICES_ACTIVE, - POWERWALL_API_GRID_STATUS, - POWERWALL_API_METERS, - POWERWALL_API_SERIAL_NUMBERS, - POWERWALL_API_SITE_INFO, - POWERWALL_API_SITEMASTER, - POWERWALL_API_STATUS, - POWERWALL_COORDINATOR, -) +from .const import DOMAIN from .entity import PowerWallEntity +from .models import PowerwallRuntimeData async def async_setup_entry( @@ -29,152 +20,103 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up the August sensors.""" - powerwall_data = hass.data[DOMAIN][config_entry.entry_id] - - coordinator = powerwall_data[POWERWALL_COORDINATOR] - site_info = powerwall_data[POWERWALL_API_SITE_INFO] - device_type = powerwall_data[POWERWALL_API_DEVICE_TYPE] - status = powerwall_data[POWERWALL_API_STATUS] - powerwalls_serial_numbers = powerwall_data[POWERWALL_API_SERIAL_NUMBERS] - - entities = [] - for sensor_class in ( - PowerWallRunningSensor, - PowerWallGridServicesActiveSensor, - PowerWallGridStatusSensor, - PowerWallConnectedSensor, - PowerWallChargingStatusSensor, - ): - entities.append( - sensor_class( - coordinator, site_info, status, device_type, powerwalls_serial_numbers + """Set up the powerwall sensors.""" + powerwall_data: PowerwallRuntimeData = hass.data[DOMAIN][config_entry.entry_id] + async_add_entities( + [ + sensor_class(powerwall_data) + for sensor_class in ( + PowerWallRunningSensor, + PowerWallGridServicesActiveSensor, + PowerWallGridStatusSensor, + PowerWallConnectedSensor, + PowerWallChargingStatusSensor, ) - ) - - async_add_entities(entities, True) + ] + ) class PowerWallRunningSensor(PowerWallEntity, BinarySensorEntity): """Representation of an Powerwall running sensor.""" - @property - def name(self): - """Device Name.""" - return "Powerwall Status" + _attr_name = "Powerwall Status" + _attr_device_class = BinarySensorDeviceClass.POWER @property - def device_class(self): - """Device Class.""" - return BinarySensorDeviceClass.POWER - - @property - def unique_id(self): + def unique_id(self) -> str: """Device Uniqueid.""" return f"{self.base_unique_id}_running" @property - def is_on(self): + def is_on(self) -> bool: """Get the powerwall running state.""" - return self.coordinator.data[POWERWALL_API_SITEMASTER].is_running + return self.data.site_master.is_running class PowerWallConnectedSensor(PowerWallEntity, BinarySensorEntity): """Representation of an Powerwall connected sensor.""" - @property - def name(self): - """Device Name.""" - return "Powerwall Connected to Tesla" - - @property - def device_class(self): - """Device Class.""" - return BinarySensorDeviceClass.CONNECTIVITY + _attr_name = "Powerwall Connected to Tesla" + _attr_device_class = BinarySensorDeviceClass.CONNECTIVITY @property - def unique_id(self): + def unique_id(self) -> str: """Device Uniqueid.""" return f"{self.base_unique_id}_connected_to_tesla" @property - def is_on(self): + def is_on(self) -> bool: """Get the powerwall connected to tesla state.""" - return self.coordinator.data[POWERWALL_API_SITEMASTER].is_connected_to_tesla + return self.data.site_master.is_connected_to_tesla class PowerWallGridServicesActiveSensor(PowerWallEntity, BinarySensorEntity): """Representation of a Powerwall grid services active sensor.""" - @property - def name(self): - """Device Name.""" - return "Grid Services Active" - - @property - def device_class(self): - """Device Class.""" - return BinarySensorDeviceClass.POWER + _attr_name = "Grid Services Active" + _attr_device_class = BinarySensorDeviceClass.POWER @property - def unique_id(self): + def unique_id(self) -> str: """Device Uniqueid.""" return f"{self.base_unique_id}_grid_services_active" @property - def is_on(self): + def is_on(self) -> bool: """Grid services is active.""" - return self.coordinator.data[POWERWALL_API_GRID_SERVICES_ACTIVE] + return self.data.grid_services_active class PowerWallGridStatusSensor(PowerWallEntity, BinarySensorEntity): """Representation of an Powerwall grid status sensor.""" - @property - def name(self): - """Device Name.""" - return "Grid Status" + _attr_name = "Grid Status" + _attr_device_class = BinarySensorDeviceClass.POWER @property - def device_class(self): - """Device Class.""" - return BinarySensorDeviceClass.POWER - - @property - def unique_id(self): + def unique_id(self) -> str: """Device Uniqueid.""" return f"{self.base_unique_id}_grid_status" @property - def is_on(self): + def is_on(self) -> bool: """Grid is online.""" - return self.coordinator.data[POWERWALL_API_GRID_STATUS] == GridStatus.CONNECTED + return self.data.grid_status == GridStatus.CONNECTED class PowerWallChargingStatusSensor(PowerWallEntity, BinarySensorEntity): """Representation of an Powerwall charging status sensor.""" - @property - def name(self): - """Device Name.""" - return "Powerwall Charging" - - @property - def device_class(self): - """Device Class.""" - return BinarySensorDeviceClass.BATTERY_CHARGING + _attr_name = "Powerwall Charging" + _attr_device_class = BinarySensorDeviceClass.BATTERY_CHARGING @property - def unique_id(self): + def unique_id(self) -> str: """Device Uniqueid.""" return f"{self.base_unique_id}_powerwall_charging" @property - def is_on(self): + def is_on(self) -> bool: """Powerwall is charging.""" # is_sending_to returns true for values greater than 100 watts - return ( - self.coordinator.data[POWERWALL_API_METERS] - .get_meter(MeterType.BATTERY) - .is_sending_to() - ) + return self.data.meters.get_meter(MeterType.BATTERY).is_sending_to() diff --git a/homeassistant/components/powerwall/config_flow.py b/homeassistant/components/powerwall/config_flow.py index 9814a52d8a02d5..08e9f90df1b82b 100644 --- a/homeassistant/components/powerwall/config_flow.py +++ b/homeassistant/components/powerwall/config_flow.py @@ -9,6 +9,7 @@ MissingAttributeError, Powerwall, PowerwallUnreachableError, + SiteInfo, ) import voluptuous as vol @@ -23,11 +24,12 @@ _LOGGER = logging.getLogger(__name__) -def _login_and_fetch_site_info(power_wall: Powerwall, password: str): +def _login_and_fetch_site_info( + power_wall: Powerwall, password: str +) -> tuple[SiteInfo, str]: """Login to the powerwall and fetch the base info.""" if password is not None: power_wall.login(password) - power_wall.detect_and_pin_version() return power_wall.get_site_info(), power_wall.get_gateway_din() @@ -60,7 +62,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 - def __init__(self): + def __init__(self) -> None: """Initialize the powerwall flow.""" self.ip_address: str | None = None self.title: str | None = None @@ -101,7 +103,7 @@ async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowRes return await self.async_step_confirm_discovery() async def _async_try_connect( - self, user_input + self, user_input: dict[str, Any] ) -> tuple[dict[str, Any] | None, dict[str, str] | None]: """Try to connect to the powerwall.""" info = None @@ -120,7 +122,9 @@ async def _async_try_connect( return errors, info - async def async_step_confirm_discovery(self, user_input=None) -> FlowResult: + async def async_step_confirm_discovery( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Confirm a discovered powerwall.""" assert self.ip_address is not None assert self.unique_id is not None @@ -148,9 +152,11 @@ async def async_step_confirm_discovery(self, user_input=None) -> FlowResult: }, ) - async def async_step_user(self, user_input=None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle the initial step.""" - errors = {} + errors: dict[str, str] | None = {} if user_input is not None: errors, info = await self._async_try_connect(user_input) if not errors: @@ -176,9 +182,12 @@ async def async_step_user(self, user_input=None): errors=errors, ) - async def async_step_reauth_confirm(self, user_input=None): + async def async_step_reauth_confirm( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle reauth confirmation.""" - errors = {} + assert self.reauth_entry is not None + errors: dict[str, str] | None = {} if user_input is not None: entry_data = self.reauth_entry.data errors, _ = await self._async_try_connect( @@ -197,7 +206,7 @@ async def async_step_reauth_confirm(self, user_input=None): errors=errors, ) - async def async_step_reauth(self, data): + async def async_step_reauth(self, data: dict[str, str]) -> FlowResult: """Handle configuration by re-auth.""" self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] diff --git a/homeassistant/components/powerwall/const.py b/homeassistant/components/powerwall/const.py index b2f0e3afe80c23..b2738bce4ac449 100644 --- a/homeassistant/components/powerwall/const.py +++ b/homeassistant/components/powerwall/const.py @@ -1,34 +1,20 @@ """Constants for the Tesla Powerwall integration.""" +from typing import Final DOMAIN = "powerwall" -POWERWALL_OBJECT = "powerwall" -POWERWALL_COORDINATOR = "coordinator" -POWERWALL_API_CHANGED = "api_changed" +POWERWALL_BASE_INFO: Final = "base_info" +POWERWALL_COORDINATOR: Final = "coordinator" +POWERWALL_API_CHANGED: Final = "api_changed" +POWERWALL_HTTP_SESSION: Final = "http_session" +POWERWALL_LOGIN_FAILED_COUNT: Final = "login_failed_count" -UPDATE_INTERVAL = 30 +UPDATE_INTERVAL = 5 ATTR_FREQUENCY = "frequency" ATTR_INSTANT_AVERAGE_VOLTAGE = "instant_average_voltage" ATTR_INSTANT_TOTAL_CURRENT = "instant_total_current" ATTR_IS_ACTIVE = "is_active" -STATUS_VERSION = "version" - -POWERWALL_SITE_NAME = "site_name" - -POWERWALL_API_METERS = "meters" -POWERWALL_API_CHARGE = "charge" -POWERWALL_API_GRID_SERVICES_ACTIVE = "grid_services_active" -POWERWALL_API_GRID_STATUS = "grid_status" -POWERWALL_API_SITEMASTER = "sitemaster" -POWERWALL_API_STATUS = "status" -POWERWALL_API_DEVICE_TYPE = "device_type" -POWERWALL_API_SITE_INFO = "site_info" -POWERWALL_API_SERIAL_NUMBERS = "serial_numbers" -POWERWALL_API_GATEWAY_DIN = "gateway_din" - -POWERWALL_HTTP_SESSION = "http_session" - MODEL = "PowerWall 2" MANUFACTURER = "Tesla" diff --git a/homeassistant/components/powerwall/entity.py b/homeassistant/components/powerwall/entity.py index ae647a080c0a80..20871944663c27 100644 --- a/homeassistant/components/powerwall/entity.py +++ b/homeassistant/components/powerwall/entity.py @@ -3,30 +3,37 @@ from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN, MANUFACTURER, MODEL +from .const import ( + DOMAIN, + MANUFACTURER, + MODEL, + POWERWALL_BASE_INFO, + POWERWALL_COORDINATOR, +) +from .models import PowerwallData, PowerwallRuntimeData -class PowerWallEntity(CoordinatorEntity): +class PowerWallEntity(CoordinatorEntity[PowerwallData]): """Base class for powerwall entities.""" - def __init__( - self, coordinator, site_info, status, device_type, powerwalls_serial_numbers - ): - """Initialize the sensor.""" + def __init__(self, powerwall_data: PowerwallRuntimeData) -> None: + """Initialize the entity.""" + base_info = powerwall_data[POWERWALL_BASE_INFO] + coordinator = powerwall_data[POWERWALL_COORDINATOR] + assert coordinator is not None super().__init__(coordinator) - self._site_info = site_info - self._device_type = device_type - self._version = status.version # The serial numbers of the powerwalls are unique to every site - self.base_unique_id = "_".join(powerwalls_serial_numbers) - - @property - def device_info(self) -> DeviceInfo: - """Powerwall device info.""" - return DeviceInfo( + self.base_unique_id = "_".join(base_info.serial_numbers) + self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, self.base_unique_id)}, manufacturer=MANUFACTURER, - model=f"{MODEL} ({self._device_type.name})", - name=self._site_info.site_name, - sw_version=self._version, + model=f"{MODEL} ({base_info.device_type.name})", + name=base_info.site_info.site_name, + sw_version=base_info.status.version, + configuration_url=base_info.url, ) + + @property + def data(self) -> PowerwallData: + """Return the coordinator data.""" + return self.coordinator.data diff --git a/homeassistant/components/powerwall/manifest.json b/homeassistant/components/powerwall/manifest.json index 55c7ab41e64bf3..be5d4678e27b46 100644 --- a/homeassistant/components/powerwall/manifest.json +++ b/homeassistant/components/powerwall/manifest.json @@ -3,7 +3,7 @@ "name": "Tesla Powerwall", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/powerwall", - "requirements": ["tesla-powerwall==0.3.15"], + "requirements": ["tesla-powerwall==0.3.17"], "codeowners": ["@bdraco", "@jrester"], "dhcp": [ { diff --git a/homeassistant/components/powerwall/models.py b/homeassistant/components/powerwall/models.py new file mode 100644 index 00000000000000..472d9e593045cf --- /dev/null +++ b/homeassistant/components/powerwall/models.py @@ -0,0 +1,50 @@ +"""The powerwall integration models.""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import TypedDict + +from requests import Session +from tesla_powerwall import ( + DeviceType, + GridStatus, + MetersAggregates, + PowerwallStatus, + SiteInfo, + SiteMaster, +) + +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + + +@dataclass +class PowerwallBaseInfo: + """Base information for the powerwall integration.""" + + gateway_din: None | str + site_info: SiteInfo + status: PowerwallStatus + device_type: DeviceType + serial_numbers: list[str] + url: str + + +@dataclass +class PowerwallData: + """Point in time data for the powerwall integration.""" + + charge: float + site_master: SiteMaster + meters: MetersAggregates + grid_services_active: bool + grid_status: GridStatus + + +class PowerwallRuntimeData(TypedDict): + """Run time data for the powerwall.""" + + coordinator: DataUpdateCoordinator | None + login_failed_count: int + base_info: PowerwallBaseInfo + api_changed: bool + http_session: Session diff --git a/homeassistant/components/powerwall/sensor.py b/homeassistant/components/powerwall/sensor.py index da0e7e7f599789..a48726211b2a7e 100644 --- a/homeassistant/components/powerwall/sensor.py +++ b/homeassistant/components/powerwall/sensor.py @@ -1,7 +1,7 @@ -"""Support for August sensors.""" +"""Support for powerwall sensors.""" from __future__ import annotations -import logging +from typing import Any from tesla_powerwall import MeterType @@ -21,72 +21,43 @@ ATTR_INSTANT_TOTAL_CURRENT, ATTR_IS_ACTIVE, DOMAIN, - POWERWALL_API_CHARGE, - POWERWALL_API_DEVICE_TYPE, - POWERWALL_API_METERS, - POWERWALL_API_SERIAL_NUMBERS, - POWERWALL_API_SITE_INFO, - POWERWALL_API_STATUS, POWERWALL_COORDINATOR, ) from .entity import PowerWallEntity +from .models import PowerwallData, PowerwallRuntimeData _METER_DIRECTION_EXPORT = "export" _METER_DIRECTION_IMPORT = "import" _METER_DIRECTIONS = [_METER_DIRECTION_EXPORT, _METER_DIRECTION_IMPORT] -_LOGGER = logging.getLogger(__name__) - - async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up the August sensors.""" - powerwall_data = hass.data[DOMAIN][config_entry.entry_id] - _LOGGER.debug("Powerwall_data: %s", powerwall_data) - + """Set up the powerwall sensors.""" + powerwall_data: PowerwallRuntimeData = hass.data[DOMAIN][config_entry.entry_id] coordinator = powerwall_data[POWERWALL_COORDINATOR] - site_info = powerwall_data[POWERWALL_API_SITE_INFO] - device_type = powerwall_data[POWERWALL_API_DEVICE_TYPE] - status = powerwall_data[POWERWALL_API_STATUS] - powerwalls_serial_numbers = powerwall_data[POWERWALL_API_SERIAL_NUMBERS] - - entities: list[SensorEntity] = [] - # coordinator.data[POWERWALL_API_METERS].meters holds all meters that are available - for meter in coordinator.data[POWERWALL_API_METERS].meters: - entities.append( - PowerWallEnergySensor( - meter, - coordinator, - site_info, - status, - device_type, - powerwalls_serial_numbers, - ) - ) + assert coordinator is not None + data: PowerwallData = coordinator.data + entities: list[ + PowerWallEnergySensor | PowerWallEnergyDirectionSensor | PowerWallChargeSensor + ] = [] + for meter in data.meters.meters: + entities.append(PowerWallEnergySensor(powerwall_data, meter)) for meter_direction in _METER_DIRECTIONS: entities.append( PowerWallEnergyDirectionSensor( + powerwall_data, meter, - coordinator, - site_info, - status, - device_type, - powerwalls_serial_numbers, meter_direction, ) ) - entities.append( - PowerWallChargeSensor( - coordinator, site_info, status, device_type, powerwalls_serial_numbers - ) - ) + entities.append(PowerWallChargeSensor(powerwall_data)) - async_add_entities(entities, True) + async_add_entities(entities) class PowerWallChargeSensor(PowerWallEntity, SensorEntity): @@ -98,14 +69,14 @@ class PowerWallChargeSensor(PowerWallEntity, SensorEntity): _attr_device_class = SensorDeviceClass.BATTERY @property - def unique_id(self): + def unique_id(self) -> str: """Device Uniqueid.""" return f"{self.base_unique_id}_charge" @property - def native_value(self): + def native_value(self) -> int: """Get the current value in percentage.""" - return round(self.coordinator.data[POWERWALL_API_CHARGE]) + return round(self.data.charge) class PowerWallEnergySensor(PowerWallEntity, SensorEntity): @@ -115,19 +86,9 @@ class PowerWallEnergySensor(PowerWallEntity, SensorEntity): _attr_native_unit_of_measurement = POWER_KILO_WATT _attr_device_class = SensorDeviceClass.POWER - def __init__( - self, - meter: MeterType, - coordinator, - site_info, - status, - device_type, - powerwalls_serial_numbers, - ): + def __init__(self, powerwall_data: PowerwallRuntimeData, meter: MeterType) -> None: """Initialize the sensor.""" - super().__init__( - coordinator, site_info, status, device_type, powerwalls_serial_numbers - ) + super().__init__(powerwall_data) self._meter = meter self._attr_name = f"Powerwall {self._meter.value.title()} Now" self._attr_unique_id = ( @@ -135,18 +96,14 @@ def __init__( ) @property - def native_value(self): + def native_value(self) -> float: """Get the current value in kW.""" - return ( - self.coordinator.data[POWERWALL_API_METERS] - .get_meter(self._meter) - .get_power(precision=3) - ) + return self.data.meters.get_meter(self._meter).get_power(precision=3) @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any]: """Return the device specific state attributes.""" - meter = self.coordinator.data[POWERWALL_API_METERS].get_meter(self._meter) + meter = self.data.meters.get_meter(self._meter) return { ATTR_FREQUENCY: round(meter.frequency, 1), ATTR_INSTANT_AVERAGE_VOLTAGE: round(meter.average_voltage, 1), @@ -164,18 +121,12 @@ class PowerWallEnergyDirectionSensor(PowerWallEntity, SensorEntity): def __init__( self, + powerwall_data: PowerwallRuntimeData, meter: MeterType, - coordinator, - site_info, - status, - device_type, - powerwalls_serial_numbers, - meter_direction, - ): + meter_direction: str, + ) -> None: """Initialize the sensor.""" - super().__init__( - coordinator, site_info, status, device_type, powerwalls_serial_numbers - ) + super().__init__(powerwall_data) self._meter = meter self._meter_direction = meter_direction self._attr_name = ( @@ -186,9 +137,9 @@ def __init__( ) @property - def native_value(self): + def native_value(self) -> float: """Get the current value in kWh.""" - meter = self.coordinator.data[POWERWALL_API_METERS].get_meter(self._meter) + meter = self.data.meters.get_meter(self._meter) if self._meter_direction == _METER_DIRECTION_EXPORT: return meter.get_energy_exported() return meter.get_energy_imported() diff --git a/mypy.ini b/mypy.ini index dbbbe8245407a0..b508045d623b5b 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1382,6 +1382,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.powerwall.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.proximity.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/requirements_all.txt b/requirements_all.txt index 66c31d0b25d42b..75ea65b43c475c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2348,7 +2348,7 @@ temperusb==1.5.3 # tensorflow==2.5.0 # homeassistant.components.powerwall -tesla-powerwall==0.3.15 +tesla-powerwall==0.3.17 # homeassistant.components.tesla_wall_connector tesla-wall-connector==1.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3ebda80093bb71..f14ef8acb71aac 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1445,7 +1445,7 @@ tailscale==0.2.0 tellduslive==0.10.11 # homeassistant.components.powerwall -tesla-powerwall==0.3.15 +tesla-powerwall==0.3.17 # homeassistant.components.tesla_wall_connector tesla-wall-connector==1.0.1 From 6a5215dc0e0c4014dcaf51fcefafcdf7fee9db0a Mon Sep 17 00:00:00 2001 From: Timothy Kist Date: Wed, 23 Feb 2022 11:25:54 +0000 Subject: [PATCH 0981/1098] Allow multidict 6.0.2+ to fix ZHA, gTTS and other integrations (#67046) --- homeassistant/package_constraints.txt | 4 ++-- script/gen_requirements_all.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 44ff7d46453dad..89631a4fc1eadd 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -94,5 +94,5 @@ python-engineio>=3.13.1,<4.0 python-socketio>=4.6.0,<5.0 # Constrain multidict to avoid typing issues -# https://github.com/home-assistant/core/pull/64792 -multidict<6.0.0 +# https://github.com/home-assistant/core/pull/67046 +multidict>=6.0.2 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 1e3f39a2f8900c..fe8962e4f1e4d5 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -122,8 +122,8 @@ python-socketio>=4.6.0,<5.0 # Constrain multidict to avoid typing issues -# https://github.com/home-assistant/core/pull/64792 -multidict<6.0.0 +# https://github.com/home-assistant/core/pull/67046 +multidict>=6.0.2 """ IGNORE_PRE_COMMIT_HOOK_ID = ( From 845bf80e725af8c921915906b0f796c7a8164d11 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Wed, 23 Feb 2022 12:29:32 +0100 Subject: [PATCH 0982/1098] Mqtt improve test coverage (#66279) Co-authored-by: Martin Hjelmare --- homeassistant/components/mqtt/config_flow.py | 4 +- tests/components/mqtt/test_config_flow.py | 59 ++-- tests/components/mqtt/test_init.py | 284 +++++++++++++++++-- 3 files changed, 309 insertions(+), 38 deletions(-) diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index 23e2a0d1e81cfe..3f93e50829a8a8 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -33,6 +33,8 @@ ) from .util import MQTT_WILL_BIRTH_SCHEMA +MQTT_TIMEOUT = 5 + class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow.""" @@ -337,7 +339,7 @@ def on_connect(client_, userdata, flags, result_code): client.loop_start() try: - return result.get(timeout=5) + return result.get(timeout=MQTT_TIMEOUT) except queue.Empty: return False finally: diff --git a/tests/components/mqtt/test_config_flow.py b/tests/components/mqtt/test_config_flow.py index f16a0e5e83a4a9..d9aab02e821fe1 100644 --- a/tests/components/mqtt/test_config_flow.py +++ b/tests/components/mqtt/test_config_flow.py @@ -1,5 +1,4 @@ """Test config flow.""" - from unittest.mock import patch import pytest @@ -30,6 +29,31 @@ def mock_try_connection(): yield mock_try +@pytest.fixture +def mock_try_connection_success(): + """Mock the try connection method with success.""" + + def loop_start(): + """Simulate connect on loop start.""" + mock_client().on_connect(mock_client, None, None, 0) + + with patch("paho.mqtt.client.Client") as mock_client: + mock_client().loop_start = loop_start + yield mock_client() + + +@pytest.fixture +def mock_try_connection_time_out(): + """Mock the try connection method with a time out.""" + + # Patch prevent waiting 5 sec for a timeout + with patch("paho.mqtt.client.Client") as mock_client, patch( + "homeassistant.components.mqtt.config_flow.MQTT_TIMEOUT", 0 + ): + mock_client().loop_start = lambda *args: 1 + yield mock_client() + + async def test_user_connection_works( hass, mock_try_connection, mock_finish_setup, mqtt_client_mock ): @@ -57,10 +81,10 @@ async def test_user_connection_works( assert len(mock_finish_setup.mock_calls) == 1 -async def test_user_connection_fails(hass, mock_try_connection, mock_finish_setup): +async def test_user_connection_fails( + hass, mock_try_connection_time_out, mock_finish_setup +): """Test if connection cannot be made.""" - mock_try_connection.return_value = False - result = await hass.config_entries.flow.async_init( "mqtt", context={"source": config_entries.SOURCE_USER} ) @@ -74,7 +98,7 @@ async def test_user_connection_fails(hass, mock_try_connection, mock_finish_setu assert result["errors"]["base"] == "cannot_connect" # Check we tried the connection - assert len(mock_try_connection.mock_calls) == 1 + assert len(mock_try_connection_time_out.mock_calls) # Check config entry did not setup assert len(mock_finish_setup.mock_calls) == 0 @@ -163,7 +187,12 @@ async def test_hassio_ignored(hass: HomeAssistant) -> None: result = await hass.config_entries.flow.async_init( mqtt.DOMAIN, data=HassioServiceInfo( - config={"addon": "Mosquitto", "host": "mock-mosquitto", "port": "1883"} + config={ + "addon": "Mosquitto", + "host": "mock-mosquitto", + "port": "1883", + "protocol": "3.1.1", + } ), context={"source": config_entries.SOURCE_HASSIO}, ) @@ -172,9 +201,7 @@ async def test_hassio_ignored(hass: HomeAssistant) -> None: assert result.get("reason") == "already_configured" -async def test_hassio_confirm( - hass, mock_try_connection, mock_finish_setup, mqtt_client_mock -): +async def test_hassio_confirm(hass, mock_try_connection_success, mock_finish_setup): """Test we can finish a config flow.""" mock_try_connection.return_value = True @@ -196,6 +223,7 @@ async def test_hassio_confirm( assert result["step_id"] == "hassio_confirm" assert result["description_placeholders"] == {"addon": "Mock Addon"} + mock_try_connection_success.reset_mock() result = await hass.config_entries.flow.async_configure( result["flow_id"], {"discovery": True} ) @@ -210,7 +238,7 @@ async def test_hassio_confirm( "discovery": True, } # Check we tried the connection - assert len(mock_try_connection.mock_calls) == 1 + assert len(mock_try_connection_success.mock_calls) # Check config entry got setup assert len(mock_finish_setup.mock_calls) == 1 @@ -368,10 +396,9 @@ def get_suggested(schema, key): async def test_option_flow_default_suggested_values( - hass, mqtt_mock, mock_try_connection + hass, mqtt_mock, mock_try_connection_success ): """Test config flow options has default/suggested values.""" - mock_try_connection.return_value = True config_entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0] config_entry.data = { mqtt.CONF_BROKER: "test-broker", @@ -516,7 +543,7 @@ async def test_option_flow_default_suggested_values( await hass.async_block_till_done() -async def test_options_user_connection_fails(hass, mock_try_connection): +async def test_options_user_connection_fails(hass, mock_try_connection_time_out): """Test if connection cannot be made.""" config_entry = MockConfigEntry(domain=mqtt.DOMAIN) config_entry.add_to_hass(hass) @@ -524,12 +551,10 @@ async def test_options_user_connection_fails(hass, mock_try_connection): mqtt.CONF_BROKER: "test-broker", mqtt.CONF_PORT: 1234, } - - mock_try_connection.return_value = False - result = await hass.config_entries.options.async_init(config_entry.entry_id) assert result["type"] == "form" + mock_try_connection_time_out.reset_mock() result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={mqtt.CONF_BROKER: "bad-broker", mqtt.CONF_PORT: 2345}, @@ -539,7 +564,7 @@ async def test_options_user_connection_fails(hass, mock_try_connection): assert result["errors"]["base"] == "cannot_connect" # Check we tried the connection - assert len(mock_try_connection.mock_calls) == 1 + assert len(mock_try_connection_time_out.mock_calls) # Check config entry did not update assert config_entry.data == { mqtt.CONF_BROKER: "test-broker", diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index a9a96df4f8fdff..68ba2a040b8bfc 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -1,16 +1,21 @@ """The tests for the MQTT component.""" import asyncio from datetime import datetime, timedelta +from functools import partial import json +import logging import ssl from unittest.mock import ANY, AsyncMock, MagicMock, call, mock_open, patch import pytest import voluptuous as vol +import yaml +from homeassistant import config as hass_config from homeassistant.components import mqtt, websocket_api from homeassistant.components.mqtt import debug_info from homeassistant.components.mqtt.mixins import MQTT_ENTITY_DEVICE_INFO_SCHEMA +from homeassistant.components.mqtt.models import ReceiveMessage from homeassistant.const import ( ATTR_ASSUMED_STATE, EVENT_HOMEASSISTANT_STARTED, @@ -34,6 +39,14 @@ ) from tests.testing_config.custom_components.test.sensor import DEVICE_CLASSES +_LOGGER = logging.getLogger(__name__) + + +class RecordCallsPartial(partial): + """Wrapper class for partial.""" + + __name__ = "RecordCallPartialTest" + @pytest.fixture(autouse=True) def mock_storage(hass_storage): @@ -675,6 +688,10 @@ async def test_subscribe_topic(hass, mqtt_mock, calls, record_calls): await hass.async_block_till_done() assert len(calls) == 1 + # Cannot unsubscribe twice + with pytest.raises(HomeAssistantError): + unsub() + async def test_subscribe_topic_non_async(hass, mqtt_mock, calls, record_calls): """Test the subscription of a topic using the non-async function.""" @@ -706,13 +723,13 @@ async def test_subscribe_bad_topic(hass, mqtt_mock, calls, record_calls): async def test_subscribe_deprecated(hass, mqtt_mock): """Test the subscription of a topic using deprecated callback signature.""" - calls = [] @callback def record_calls(topic, payload, qos): """Record calls.""" calls.append((topic, payload, qos)) + calls = [] unsub = await mqtt.async_subscribe(hass, "test-topic", record_calls) async_fire_mqtt_message(hass, "test-topic", "test-payload") @@ -728,17 +745,59 @@ def record_calls(topic, payload, qos): await hass.async_block_till_done() assert len(calls) == 1 + mqtt_mock.async_publish.reset_mock() + + # Test with partial wrapper + calls = [] + unsub = await mqtt.async_subscribe( + hass, "test-topic", RecordCallsPartial(record_calls) + ) + + async_fire_mqtt_message(hass, "test-topic", "test-payload") + + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0][0] == "test-topic" + assert calls[0][1] == "test-payload" + + unsub() + + async_fire_mqtt_message(hass, "test-topic", "test-payload") + + await hass.async_block_till_done() + assert len(calls) == 1 async def test_subscribe_deprecated_async(hass, mqtt_mock): - """Test the subscription of a topic using deprecated callback signature.""" - calls = [] + """Test the subscription of a topic using deprecated coroutine signature.""" - async def record_calls(topic, payload, qos): + def async_record_calls(topic, payload, qos): """Record calls.""" calls.append((topic, payload, qos)) - unsub = await mqtt.async_subscribe(hass, "test-topic", record_calls) + calls = [] + unsub = await mqtt.async_subscribe(hass, "test-topic", async_record_calls) + + async_fire_mqtt_message(hass, "test-topic", "test-payload") + + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0][0] == "test-topic" + assert calls[0][1] == "test-payload" + + unsub() + + async_fire_mqtt_message(hass, "test-topic", "test-payload") + + await hass.async_block_till_done() + assert len(calls) == 1 + mqtt_mock.async_publish.reset_mock() + + # Test with partial wrapper + calls = [] + unsub = await mqtt.async_subscribe( + hass, "test-topic", RecordCallsPartial(async_record_calls) + ) async_fire_mqtt_message(hass, "test-topic", "test-payload") @@ -1010,9 +1069,9 @@ async def test_restore_subscriptions_on_reconnect(hass, mqtt_client_mock, mqtt_m await hass.async_block_till_done() assert mqtt_client_mock.subscribe.call_count == 1 - mqtt_mock._mqtt_on_disconnect(None, None, 0) + mqtt_client_mock.on_disconnect(None, None, 0) with patch("homeassistant.components.mqtt.DISCOVERY_COOLDOWN", 0): - mqtt_mock._mqtt_on_connect(None, None, None, 0) + mqtt_client_mock.on_connect(None, None, None, 0) await hass.async_block_till_done() assert mqtt_client_mock.subscribe.call_count == 2 @@ -1044,23 +1103,143 @@ async def test_restore_all_active_subscriptions_on_reconnect( await hass.async_block_till_done() assert mqtt_client_mock.unsubscribe.call_count == 0 - mqtt_mock._mqtt_on_disconnect(None, None, 0) + mqtt_client_mock.on_disconnect(None, None, 0) with patch("homeassistant.components.mqtt.DISCOVERY_COOLDOWN", 0): - mqtt_mock._mqtt_on_connect(None, None, None, 0) + mqtt_client_mock.on_connect(None, None, None, 0) await hass.async_block_till_done() expected.append(call("test/state", 1)) assert mqtt_client_mock.subscribe.mock_calls == expected -async def test_setup_logs_error_if_no_connect_broker(hass, caplog): +async def test_initial_setup_logs_error(hass, caplog, mqtt_client_mock): + """Test for setup failure if initial client connection fails.""" + entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"}) + + mqtt_client_mock.connect.return_value = 1 + assert await mqtt.async_setup_entry(hass, entry) + await hass.async_block_till_done() + assert "Failed to connect to MQTT server:" in caplog.text + + +async def test_logs_error_if_no_connect_broker( + hass, caplog, mqtt_mock, mqtt_client_mock +): """Test for setup failure if connection to broker is missing.""" + # test with rc = 3 -> broker unavailable + mqtt_client_mock.on_connect(mqtt_client_mock, None, None, 3) + await hass.async_block_till_done() + assert ( + "Unable to connect to the MQTT broker: Connection Refused: broker unavailable." + in caplog.text + ) + + +@patch("homeassistant.components.mqtt.TIMEOUT_ACK", 0.3) +async def test_handle_mqtt_on_callback(hass, caplog, mqtt_mock, mqtt_client_mock): + """Test receiving an ACK callback before waiting for it.""" + # Simulate an ACK for mid == 1, this will call mqtt_mock._mqtt_handle_mid(mid) + mqtt_client_mock.on_publish(mqtt_client_mock, None, 1) + await hass.async_block_till_done() + # Make sure the ACK has been received + await hass.async_block_till_done() + # Now call publish without call back, this will call _wait_for_mid(msg_info.mid) + await mqtt.async_publish(hass, "no_callback/test-topic", "test-payload") + # Since the mid event was already set, we should not see any timeout + await hass.async_block_till_done() + assert ( + "Transmitting message on no_callback/test-topic: 'test-payload', mid: 1" + in caplog.text + ) + assert "No ACK from MQTT server" not in caplog.text + + +async def test_publish_error(hass, caplog): + """Test publish error.""" entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"}) + # simulate an Out of memory error with patch("paho.mqtt.client.Client") as mock_client: mock_client().connect = lambda *args: 1 + mock_client().publish().rc = 1 + assert await mqtt.async_setup_entry(hass, entry) + await hass.async_block_till_done() + with pytest.raises(HomeAssistantError): + await mqtt.async_publish( + hass, "some-topic", b"test-payload", qos=0, retain=False, encoding=None + ) + assert "Failed to connect to MQTT server: Out of memory." in caplog.text + + +async def test_handle_message_callback(hass, caplog, mqtt_mock, mqtt_client_mock): + """Test for handling an incoming message callback.""" + msg = ReceiveMessage("some-topic", b"test-payload", 0, False) + mqtt_client_mock.on_connect(mqtt_client_mock, None, None, 0) + await mqtt.async_subscribe(hass, "some-topic", lambda *args: 0) + mqtt_client_mock.on_message(mock_mqtt, None, msg) + + await hass.async_block_till_done() + await hass.async_block_till_done() + assert "Received message on some-topic: b'test-payload'" in caplog.text + + +async def test_setup_override_configuration(hass, caplog, tmp_path): + """Test override setup from configuration entry.""" + calls_username_password_set = [] + + def mock_usename_password_set(username, password): + calls_username_password_set.append((username, password)) + + # Mock password setup from config + config = { + "username": "someuser", + "password": "someyamlconfiguredpassword", + "protocol": "3.1", + } + new_yaml_config_file = tmp_path / "configuration.yaml" + new_yaml_config = yaml.dump({mqtt.DOMAIN: config}) + new_yaml_config_file.write_text(new_yaml_config) + assert new_yaml_config_file.read_text() == new_yaml_config + + with patch.object(hass_config, "YAML_CONFIG_FILE", new_yaml_config_file): + # Mock config entry + entry = MockConfigEntry( + domain=mqtt.DOMAIN, + data={mqtt.CONF_BROKER: "test-broker", "password": "somepassword"}, + ) + + with patch("paho.mqtt.client.Client") as mock_client: + mock_client().username_pw_set = mock_usename_password_set + mock_client.on_connect(return_value=0) + await async_setup_component(hass, mqtt.DOMAIN, {mqtt.DOMAIN: config}) + await entry.async_setup(hass) + await hass.async_block_till_done() + + assert ( + "Data in your configuration entry is going to override your configuration.yaml:" + in caplog.text + ) + + # Check if the protocol was set to 3.1 from configuration.yaml + assert mock_client.call_args[1]["protocol"] == 3 + + # Check if the password override worked + assert calls_username_password_set[0][0] == "someuser" + assert calls_username_password_set[0][1] == "somepassword" + + +async def test_setup_mqtt_client_protocol(hass): + """Test MQTT client protocol setup.""" + entry = MockConfigEntry( + domain=mqtt.DOMAIN, + data={mqtt.CONF_BROKER: "test-broker", mqtt.CONF_PROTOCOL: "3.1"}, + ) + with patch("paho.mqtt.client.Client") as mock_client: + mock_client.on_connect(return_value=0) assert await mqtt.async_setup_entry(hass, entry) - assert "Failed to connect to MQTT server:" in caplog.text + + # check if protocol setup was correctly + assert mock_client.call_args[1]["protocol"] == 3 async def test_setup_raises_ConfigEntryNotReady_if_no_connect_broker(hass, caplog): @@ -1073,18 +1252,29 @@ async def test_setup_raises_ConfigEntryNotReady_if_no_connect_broker(hass, caplo assert "Failed to connect to MQTT server due to exception:" in caplog.text -async def test_setup_uses_certificate_on_certificate_set_to_auto(hass): - """Test setup uses bundled certs when certificate is set to auto.""" +@pytest.mark.parametrize("insecure", [None, False, True]) +async def test_setup_uses_certificate_on_certificate_set_to_auto_and_insecure( + hass, insecure +): + """Test setup uses bundled certs when certificate is set to auto and insecure.""" calls = [] + insecure_check = {"insecure": "not set"} def mock_tls_set(certificate, certfile=None, keyfile=None, tls_version=None): calls.append((certificate, certfile, keyfile, tls_version)) + def mock_tls_insecure_set(insecure_param): + insecure_check["insecure"] = insecure_param + + config_item_data = {mqtt.CONF_BROKER: "test-broker", "certificate": "auto"} + if insecure is not None: + config_item_data["tls_insecure"] = insecure with patch("paho.mqtt.client.Client") as mock_client: mock_client().tls_set = mock_tls_set + mock_client().tls_insecure_set = mock_tls_insecure_set entry = MockConfigEntry( domain=mqtt.DOMAIN, - data={mqtt.CONF_BROKER: "test-broker", "certificate": "auto"}, + data=config_item_data, ) assert await mqtt.async_setup_entry(hass, entry) @@ -1097,6 +1287,13 @@ def mock_tls_set(certificate, certfile=None, keyfile=None, tls_version=None): # assert mock_mqtt.mock_calls[0][1][2]["certificate"] == expectedCertificate assert calls[0][0] == expectedCertificate + # test if insecure is set + assert ( + insecure_check["insecure"] == insecure + if insecure is not None + else insecure_check["insecure"] == "not set" + ) + async def test_setup_without_tls_config_uses_tlsv1_under_python36(hass): """Test setup defaults to TLSv1 under python3.6.""" @@ -1150,7 +1347,7 @@ async def wait_birth(topic, payload, qos): with patch("homeassistant.components.mqtt.DISCOVERY_COOLDOWN", 0.1): await mqtt.async_subscribe(hass, "birth", wait_birth) - mqtt_mock._mqtt_on_connect(None, None, 0, 0) + mqtt_client_mock.on_connect(None, None, 0, 0) await hass.async_block_till_done() await birth.wait() mqtt_client_mock.publish.assert_called_with("birth", "birth", 0, False) @@ -1180,7 +1377,7 @@ async def wait_birth(topic, payload, qos): with patch("homeassistant.components.mqtt.DISCOVERY_COOLDOWN", 0.1): await mqtt.async_subscribe(hass, "homeassistant/status", wait_birth) - mqtt_mock._mqtt_on_connect(None, None, 0, 0) + mqtt_client_mock.on_connect(None, None, 0, 0) await hass.async_block_till_done() await birth.wait() mqtt_client_mock.publish.assert_called_with( @@ -1195,7 +1392,7 @@ async def wait_birth(topic, payload, qos): async def test_no_birth_message(hass, mqtt_client_mock, mqtt_mock): """Test disabling birth message.""" with patch("homeassistant.components.mqtt.DISCOVERY_COOLDOWN", 0.1): - mqtt_mock._mqtt_on_connect(None, None, 0, 0) + mqtt_client_mock.on_connect(None, None, 0, 0) await hass.async_block_till_done() await asyncio.sleep(0.2) mqtt_client_mock.publish.assert_not_called() @@ -1215,7 +1412,7 @@ async def test_no_birth_message(hass, mqtt_client_mock, mqtt_mock): } ], ) -async def test_delayed_birth_message(hass, mqtt_client_mock, mqtt_config): +async def test_delayed_birth_message(hass, mqtt_client_mock, mqtt_config, mqtt_mock): """Test sending birth message does not happen until Home Assistant starts.""" hass.state = CoreState.starting birth = asyncio.Event() @@ -1244,7 +1441,7 @@ async def wait_birth(topic, payload, qos): with patch("homeassistant.components.mqtt.DISCOVERY_COOLDOWN", 0.1): await mqtt.async_subscribe(hass, "homeassistant/status", wait_birth) - mqtt_mock._mqtt_on_connect(None, None, 0, 0) + mqtt_client_mock.on_connect(None, None, 0, 0) await hass.async_block_till_done() with pytest.raises(asyncio.TimeoutError): await asyncio.wait_for(birth.wait(), 0.2) @@ -1313,7 +1510,7 @@ async def test_mqtt_subscribes_topics_on_connect(hass, mqtt_client_mock, mqtt_mo await mqtt.async_subscribe(hass, "still/pending", None, 1) hass.add_job = MagicMock() - mqtt_mock._mqtt_on_connect(None, None, 0, 0) + mqtt_client_mock.on_connect(None, None, 0, 0) await hass.async_block_till_done() @@ -1391,6 +1588,18 @@ async def test_mqtt_ws_subscription(hass, hass_ws_client, mqtt_mock): assert response["success"] +async def test_mqtt_ws_subscription_not_admin( + hass, hass_ws_client, mqtt_mock, hass_read_only_access_token +): + """Test MQTT websocket user is not admin.""" + client = await hass_ws_client(hass, access_token=hass_read_only_access_token) + await client.send_json({"id": 5, "type": "mqtt/subscribe", "topic": "test-topic"}) + response = await client.receive_json() + assert response["success"] is False + assert response["error"]["code"] == "unauthorized" + assert response["error"]["message"] == "Unauthorized" + + async def test_dump_service(hass, mqtt_mock): """Test that we can dump a topic.""" mopen = mock_open() @@ -2117,3 +2326,38 @@ async def test_service_info_compatibility(hass, caplog): with patch("homeassistant.helpers.frame._REPORTED_INTEGRATIONS", set()): assert discovery_info["topic"] == "tasmota/discovery/DC4F220848A2/config" assert "Detected integration that accessed discovery_info['topic']" in caplog.text + + +async def test_subscribe_connection_status(hass, mqtt_mock, mqtt_client_mock): + """Test connextion status subscription.""" + mqtt_connected_calls = [] + + @callback + async def async_mqtt_connected(status): + """Update state on connection/disconnection to MQTT broker.""" + mqtt_connected_calls.append(status) + + mqtt_mock.connected = True + + unsub = mqtt.async_subscribe_connection_status(hass, async_mqtt_connected) + await hass.async_block_till_done() + + # Mock connection status + mqtt_client_mock.on_connect(None, None, 0, 0) + await hass.async_block_till_done() + assert mqtt.is_connected(hass) is True + + # Mock disconnect status + mqtt_client_mock.on_disconnect(None, None, 0) + await hass.async_block_till_done() + + # Unsubscribe + unsub() + + mqtt_client_mock.on_connect(None, None, 0, 0) + await hass.async_block_till_done() + + # Check calls + assert len(mqtt_connected_calls) == 2 + assert mqtt_connected_calls[0] is True + assert mqtt_connected_calls[1] is False From 8dbb184ed5cec52dda1cbd98966e72f9b0713791 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Wed, 23 Feb 2022 12:30:13 +0100 Subject: [PATCH 0983/1098] Add MQTT publish ACK timeout test (#67062) --- tests/components/mqtt/test_init.py | 42 ++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 68ba2a040b8bfc..e589e447a01827 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -1242,6 +1242,48 @@ async def test_setup_mqtt_client_protocol(hass): assert mock_client.call_args[1]["protocol"] == 3 +@patch("homeassistant.components.mqtt.TIMEOUT_ACK", 0.2) +async def test_handle_mqtt_timeout_on_callback(hass, caplog): + """Test publish without receiving an ACK callback.""" + mid = 0 + + class FakeInfo: + """Returns a simulated client publish response.""" + + mid = 100 + rc = 0 + + with patch("paho.mqtt.client.Client") as mock_client: + + def _mock_ack(topic, qos=0): + # Handle ACK for subscribe normally + nonlocal mid + mid += 1 + mock_client.on_subscribe(0, 0, mid) + return (0, mid) + + # We want to simulate the publish behaviour MQTT client + mock_client = mock_client.return_value + mock_client.publish.return_value = FakeInfo() + mock_client.subscribe.side_effect = _mock_ack + mock_client.connect.return_value = 0 + + entry = MockConfigEntry( + domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"} + ) + # Set up the integration + assert await mqtt.async_setup_entry(hass, entry) + # Make sure we are connected correctly + mock_client.on_connect(mock_client, None, None, 0) + + # Now call we publish without simulating and ACK callback + await mqtt.async_publish(hass, "no_callback/test-topic", "test-payload") + await hass.async_block_till_done() + # The is no ACK so we should see a timeout in the log after publishing + assert len(mock_client.publish.mock_calls) == 1 + assert "No ACK from MQTT server" in caplog.text + + async def test_setup_raises_ConfigEntryNotReady_if_no_connect_broker(hass, caplog): """Test for setup failure if connection to broker is missing.""" entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"}) From 3afadf8adb3f075bc9a223e9d5269fdde8a9d615 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 23 Feb 2022 12:32:07 +0100 Subject: [PATCH 0984/1098] Revert "Block peer certs on supervisor" (#67104) --- homeassistant/bootstrap.py | 4 +-- .../components/analytics/analytics.py | 2 +- homeassistant/components/hassio/__init__.py | 22 ++++-------- homeassistant/components/http/__init__.py | 9 ++--- homeassistant/components/onboarding/views.py | 2 +- homeassistant/components/ozw/config_flow.py | 2 +- homeassistant/components/updater/__init__.py | 2 +- .../components/zwave_js/config_flow.py | 6 ++-- homeassistant/helpers/supervisor.py | 11 ------ tests/components/hassio/conftest.py | 2 -- tests/components/hassio/test_binary_sensor.py | 6 +--- tests/components/hassio/test_init.py | 8 ++--- tests/components/hassio/test_sensor.py | 6 +--- tests/components/http/test_ban.py | 4 +-- tests/components/http/test_init.py | 36 ------------------- tests/components/onboarding/test_views.py | 2 -- tests/components/updater/test_init.py | 10 +++--- tests/helpers/test_supervisor.py | 16 --------- tests/test_bootstrap.py | 2 +- 19 files changed, 31 insertions(+), 121 deletions(-) delete mode 100644 homeassistant/helpers/supervisor.py delete mode 100644 tests/helpers/test_supervisor.py diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 40feae117a496b..986171cbee79e9 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -23,7 +23,7 @@ SIGNAL_BOOTSTRAP_INTEGRATONS, ) from .exceptions import HomeAssistantError -from .helpers import area_registry, device_registry, entity_registry, supervisor +from .helpers import area_registry, device_registry, entity_registry from .helpers.dispatcher import async_dispatcher_send from .helpers.typing import ConfigType from .setup import ( @@ -398,7 +398,7 @@ def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]: domains.update(hass.config_entries.async_domains()) # Make sure the Hass.io component is loaded - if supervisor.has_supervisor(): + if "HASSIO" in os.environ: domains.add("hassio") return domains diff --git a/homeassistant/components/analytics/analytics.py b/homeassistant/components/analytics/analytics.py index a7c664091c153a..d1b8879bf7c11a 100644 --- a/homeassistant/components/analytics/analytics.py +++ b/homeassistant/components/analytics/analytics.py @@ -104,7 +104,7 @@ def endpoint(self) -> str: @property def supervisor(self) -> bool: """Return bool if a supervisor is present.""" - return hassio.is_hassio() + return hassio.is_hassio(self.hass) async def load(self) -> None: """Load preferences.""" diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 1ade17e452e384..434c95b03b2ddd 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -41,8 +41,6 @@ async_get_registry, ) from homeassistant.helpers.entity import DeviceInfo -from homeassistant.helpers.frame import report -from homeassistant.helpers.supervisor import has_supervisor from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.loader import bind_hass @@ -396,21 +394,12 @@ def get_core_info(hass): @callback @bind_hass -def is_hassio( - hass: HomeAssistant | None = None, # pylint: disable=unused-argument -) -> bool: +def is_hassio(hass: HomeAssistant) -> bool: """Return true if Hass.io is loaded. Async friendly. """ - if hass is not None: - report( - "hass param deprecated for is_hassio", - exclude_integrations={DOMAIN}, - error_if_core=False, - ) - - return has_supervisor() + return DOMAIN in hass.config.components @callback @@ -423,8 +412,11 @@ def get_supervisor_ip() -> str: async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: C901 """Set up the Hass.io component.""" - if not has_supervisor(): - _LOGGER.error("Supervisor not available") + # Check local setup + for env in ("HASSIO", "HASSIO_TOKEN"): + if os.environ.get(env): + continue + _LOGGER.error("Missing %s environment variable", env) if config_entries := hass.config_entries.async_entries(DOMAIN): hass.async_create_task( hass.config_entries.async_remove(config_entries[0].entry_id) diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 46b08ee69c3134..a41329a1548917 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -23,7 +23,8 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP, SERVER_PORT from homeassistant.core import Event, HomeAssistant from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import config_validation as cv, storage, supervisor +from homeassistant.helpers import storage +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.network import NoURLAvailableError, get_url from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass @@ -166,12 +167,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: login_threshold = conf[CONF_LOGIN_ATTEMPTS_THRESHOLD] ssl_profile = conf[CONF_SSL_PROFILE] - if ssl_peer_certificate is not None and supervisor.has_supervisor(): - _LOGGER.warning( - "Peer certificates are not supported when running the supervisor" - ) - ssl_peer_certificate = None - server = HomeAssistantHTTP( hass, server_host=server_host, diff --git a/homeassistant/components/onboarding/views.py b/homeassistant/components/onboarding/views.py index d7dde43b4e056e..b277bd97edf877 100644 --- a/homeassistant/components/onboarding/views.py +++ b/homeassistant/components/onboarding/views.py @@ -195,7 +195,7 @@ async def post(self, request): from homeassistant.components import hassio if ( - hassio.is_hassio() + hassio.is_hassio(hass) and "raspberrypi" in hassio.get_core_info(hass)["machine"] ): onboard_integrations.append("rpi_power") diff --git a/homeassistant/components/ozw/config_flow.py b/homeassistant/components/ozw/config_flow.py index 9a3f0dcb8b8388..5e745a123f4b88 100644 --- a/homeassistant/components/ozw/config_flow.py +++ b/homeassistant/components/ozw/config_flow.py @@ -45,7 +45,7 @@ async def async_step_user(self, user_input=None): # Set a unique_id to make sure discovery flow is aborted on progress. await self.async_set_unique_id(DOMAIN, raise_on_progress=False) - if not hassio.is_hassio(): + if not hassio.is_hassio(self.hass): return self._async_use_mqtt_integration() return await self.async_step_on_supervisor() diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py index e1dc2fca4e4b6f..4f88b5d13696aa 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -74,7 +74,7 @@ async def check_new_version() -> Updater: _LOGGER.debug("Fetched version %s: %s", newest, release_notes) # Load data from Supervisor - if hassio.is_hassio(): + if hassio.is_hassio(hass): core_info = hassio.get_core_info(hass) newest = core_info["version_latest"] diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index e58e9a80ccfd57..32f406d7476347 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -332,14 +332,14 @@ async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle the initial step.""" - if is_hassio(): + if is_hassio(self.hass): return await self.async_step_on_supervisor() return await self.async_step_manual() async def async_step_usb(self, discovery_info: usb.UsbServiceInfo) -> FlowResult: """Handle USB Discovery.""" - if not is_hassio(): + if not is_hassio(self.hass): return self.async_abort(reason="discovery_requires_supervisor") if self._async_current_entries(): return self.async_abort(reason="already_configured") @@ -641,7 +641,7 @@ async def async_step_init( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Manage the options.""" - if is_hassio(): + if is_hassio(self.hass): return await self.async_step_on_supervisor() return await self.async_step_manual() diff --git a/homeassistant/helpers/supervisor.py b/homeassistant/helpers/supervisor.py deleted file mode 100644 index 7e7cfadeadc219..00000000000000 --- a/homeassistant/helpers/supervisor.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Supervisor helper.""" - -import os - -from homeassistant.core import callback - - -@callback -def has_supervisor() -> bool: - """Return true if supervisor is available.""" - return "SUPERVISOR" in os.environ diff --git a/tests/components/hassio/conftest.py b/tests/components/hassio/conftest.py index c5ec209500df5f..89a8c6f5c5106c 100644 --- a/tests/components/hassio/conftest.py +++ b/tests/components/hassio/conftest.py @@ -21,8 +21,6 @@ def hassio_env(): ), patch.dict(os.environ, {"HASSIO_TOKEN": HASSIO_TOKEN}), patch( "homeassistant.components.hassio.HassIO.get_info", Mock(side_effect=HassioAPIError()), - ), patch.dict( - os.environ, {"SUPERVISOR": "127.0.0.1"} ): yield diff --git a/tests/components/hassio/test_binary_sensor.py b/tests/components/hassio/test_binary_sensor.py index 6008653e7a6686..e4263eb5529528 100644 --- a/tests/components/hassio/test_binary_sensor.py +++ b/tests/components/hassio/test_binary_sensor.py @@ -11,11 +11,7 @@ from tests.common import MockConfigEntry -MOCK_ENVIRON = { - "HASSIO": "127.0.0.1", - "HASSIO_TOKEN": "abcdefgh", - "SUPERVISOR": "127.0.0.1", -} +MOCK_ENVIRON = {"HASSIO": "127.0.0.1", "HASSIO_TOKEN": "abcdefgh"} @pytest.fixture(autouse=True) diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index d901432b6e354e..e006cf9d829137 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -16,11 +16,7 @@ from tests.common import MockConfigEntry, async_fire_time_changed -MOCK_ENVIRON = { - "HASSIO": "127.0.0.1", - "HASSIO_TOKEN": "abcdefgh", - "SUPERVISOR": "127.0.0.1", -} +MOCK_ENVIRON = {"HASSIO": "127.0.0.1", "HASSIO_TOKEN": "abcdefgh"} @pytest.fixture(autouse=True) @@ -155,6 +151,7 @@ async def test_setup_api_ping(hass, aioclient_mock): assert aioclient_mock.call_count == 10 assert hass.components.hassio.get_core_info()["version_latest"] == "1.0.0" + assert hass.components.hassio.is_hassio() async def test_setup_api_panel(hass, aioclient_mock): @@ -337,6 +334,7 @@ async def test_warn_when_cannot_connect(hass, caplog): result = await async_setup_component(hass, "hassio", {}) assert result + assert hass.components.hassio.is_hassio() assert "Not connected with the supervisor / system too busy!" in caplog.text diff --git a/tests/components/hassio/test_sensor.py b/tests/components/hassio/test_sensor.py index 4969cac1d67fb1..481ba1b578fdb1 100644 --- a/tests/components/hassio/test_sensor.py +++ b/tests/components/hassio/test_sensor.py @@ -11,11 +11,7 @@ from tests.common import MockConfigEntry -MOCK_ENVIRON = { - "HASSIO": "127.0.0.1", - "HASSIO_TOKEN": "abcdefgh", - "SUPERVISOR": "127.0.0.1", -} +MOCK_ENVIRON = {"HASSIO": "127.0.0.1", "HASSIO_TOKEN": "abcdefgh"} @pytest.fixture(autouse=True) diff --git a/tests/components/http/test_ban.py b/tests/components/http/test_ban.py index 54e2f0495cd538..fbd545e0506197 100644 --- a/tests/components/http/test_ban.py +++ b/tests/components/http/test_ban.py @@ -36,9 +36,7 @@ def hassio_env_fixture(): with patch.dict(os.environ, {"HASSIO": "127.0.0.1"}), patch( "homeassistant.components.hassio.HassIO.is_connected", return_value={"result": "ok", "data": {}}, - ), patch.dict(os.environ, {"HASSIO_TOKEN": "123456"}), patch.dict( - os.environ, {"SUPERVISOR": "127.0.0.1"} - ): + ), patch.dict(os.environ, {"HASSIO_TOKEN": "123456"}): yield diff --git a/tests/components/http/test_init.py b/tests/components/http/test_init.py index 97b705f5b6d7ea..79d0a6c47916ec 100644 --- a/tests/components/http/test_init.py +++ b/tests/components/http/test_init.py @@ -261,42 +261,6 @@ async def test_peer_cert(hass, tmpdir): assert len(mock_load_verify_locations.mock_calls) == 1 -async def test_peer_cert_ignored_with_supervisor(hass, tmpdir): - """Test peer certiicate requirement ignored in supervised deployments.""" - cert_path, key_path, peer_cert_path = await hass.async_add_executor_job( - _setup_empty_ssl_pem_files, tmpdir - ) - - with patch("ssl.SSLContext.load_cert_chain"), patch( - "homeassistant.components.http.supervisor.has_supervisor", return_value=True - ), patch( - "ssl.SSLContext.load_verify_locations" - ) as mock_load_verify_locations, patch( - "homeassistant.util.ssl.server_context_modern", - side_effect=server_context_modern, - ) as mock_context: - assert ( - await async_setup_component( - hass, - "http", - { - "http": { - "ssl_peer_certificate": peer_cert_path, - "ssl_profile": "modern", - "ssl_certificate": cert_path, - "ssl_key": key_path, - } - }, - ) - is True - ) - await hass.async_start() - await hass.async_block_till_done() - - assert len(mock_context.mock_calls) == 1 - mock_load_verify_locations.assert_not_called() - - async def test_emergency_ssl_certificate_when_invalid(hass, tmpdir, caplog): """Test http can startup with an emergency self signed cert when the current one is broken.""" diff --git a/tests/components/onboarding/test_views.py b/tests/components/onboarding/test_views.py index 2b4db4f68a2eac..976e2b84c68eb0 100644 --- a/tests/components/onboarding/test_views.py +++ b/tests/components/onboarding/test_views.py @@ -86,8 +86,6 @@ async def mock_supervisor_fixture(hass, aioclient_mock): return_value={"panels": {}}, ), patch.dict( os.environ, {"HASSIO_TOKEN": "123456"} - ), patch.dict( - os.environ, {"SUPERVISOR": "127.0.0.1"} ): yield diff --git a/tests/components/updater/test_init.py b/tests/components/updater/test_init.py index e2cc0ee41b07e3..2b0f494f5f541a 100644 --- a/tests/components/updater/test_init.py +++ b/tests/components/updater/test_init.py @@ -7,6 +7,8 @@ from homeassistant.helpers.update_coordinator import UpdateFailed from homeassistant.setup import async_setup_component +from tests.common import mock_component + NEW_VERSION = "10000.0" MOCK_VERSION = "10.0" MOCK_DEV_VERSION = "10.0.dev0" @@ -111,12 +113,12 @@ async def test_new_version_shows_entity_after_hour_hassio( hass, mock_get_newest_version ): """Test if binary sensor gets updated if new version is available / Hass.io.""" - with patch("homeassistant.components.updater.hassio.is_hassio", return_value=True): - hass.data["hassio_core_info"] = {"version_latest": "999.0"} + mock_component(hass, "hassio") + hass.data["hassio_core_info"] = {"version_latest": "999.0"} - assert await async_setup_component(hass, updater.DOMAIN, {updater.DOMAIN: {}}) + assert await async_setup_component(hass, updater.DOMAIN, {updater.DOMAIN: {}}) - await hass.async_block_till_done() + await hass.async_block_till_done() assert hass.states.is_state("binary_sensor.updater", "on") assert ( diff --git a/tests/helpers/test_supervisor.py b/tests/helpers/test_supervisor.py deleted file mode 100644 index cfefe7a9ec471a..00000000000000 --- a/tests/helpers/test_supervisor.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Test the Hassio helper.""" -from unittest.mock import patch - -from homeassistant.helpers.supervisor import has_supervisor - - -async def test_has_supervisor_yes(): - """Test has_supervisor when supervisor available.""" - with patch("homeassistant.helpers.supervisor.os.environ", {"SUPERVISOR": True}): - assert has_supervisor() - - -async def test_has_supervisor_no(): - """Test has_supervisor when supervisor not available.""" - with patch("homeassistant.helpers.supervisor.os.environ"): - assert not has_supervisor() diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index b34039cc2c9cbe..87d93c1a1ac0b4 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -86,7 +86,7 @@ async def test_load_hassio(hass): with patch.dict(os.environ, {}, clear=True): assert bootstrap._get_domains(hass, {}) == set() - with patch.dict(os.environ, {"SUPERVISOR": "1"}): + with patch.dict(os.environ, {"HASSIO": "1"}): assert bootstrap._get_domains(hass, {}) == {"hassio"} From dd88a05cb400d416a68a1be16fee8ee2ab48a70f Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Wed, 23 Feb 2022 13:10:35 +0100 Subject: [PATCH 0985/1098] Make type checking pass for deCONZ init, gateway and services (#66054) * Type and enable type checking for init, config_flow, diagnostics, gateway and services * Fix import * Fix review comment --- .strict-typing | 5 + homeassistant/components/deconz/__init__.py | 38 +++++-- homeassistant/components/deconz/gateway.py | 107 +++++++++----------- homeassistant/components/deconz/services.py | 7 +- mypy.ini | 64 ++++++++++-- script/hassfest/mypy_config.py | 3 - tests/components/deconz/test_gateway.py | 65 ++++-------- tests/components/deconz/test_init.py | 39 ++++--- 8 files changed, 179 insertions(+), 149 deletions(-) diff --git a/.strict-typing b/.strict-typing index be25c50ae38b8f..74a255a7f96b21 100644 --- a/.strict-typing +++ b/.strict-typing @@ -58,6 +58,11 @@ homeassistant.components.canary.* homeassistant.components.cover.* homeassistant.components.crownstone.* homeassistant.components.cpuspeed.* +homeassistant.components.deconz +homeassistant.components.deconz.config_flow +homeassistant.components.deconz.diagnostics +homeassistant.components.deconz.gateway +homeassistant.components.deconz.services homeassistant.components.device_automation.* homeassistant.components.device_tracker.* homeassistant.components.devolo_home_control.* diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index f069605d43865c..112f29db33324b 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -12,11 +12,14 @@ EVENT_HOMEASSISTANT_STOP, ) from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady import homeassistant.helpers.entity_registry as er from .config_flow import get_master_gateway -from .const import CONF_GROUP_ID_BASE, CONF_MASTER_GATEWAY, DOMAIN -from .gateway import DeconzGateway +from .const import CONF_GROUP_ID_BASE, CONF_MASTER_GATEWAY, DOMAIN, PLATFORMS +from .deconz_event import async_setup_events, async_unload_events +from .errors import AuthenticationRequired, CannotConnect +from .gateway import DeconzGateway, get_deconz_session from .services import async_setup_services, async_unload_services @@ -33,17 +36,28 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b if not config_entry.options: await async_update_master_gateway(hass, config_entry) - gateway = DeconzGateway(hass, config_entry) - if not await gateway.async_setup(): - return False - - if not hass.data[DOMAIN]: - async_setup_services(hass) + try: + api = await get_deconz_session(hass, config_entry.data) + except CannotConnect as err: + raise ConfigEntryNotReady from err + except AuthenticationRequired as err: + raise ConfigEntryAuthFailed from err + + gateway = hass.data[DOMAIN][config_entry.entry_id] = DeconzGateway( + hass, config_entry, api + ) - hass.data[DOMAIN][config_entry.entry_id] = gateway + config_entry.add_update_listener(gateway.async_config_entry_updated) + hass.config_entries.async_setup_platforms(config_entry, PLATFORMS) + await async_setup_events(gateway) await gateway.async_update_device_registry() + if len(hass.data[DOMAIN]) == 1: + async_setup_services(hass) + + api.start() + config_entry.async_on_unload( hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, gateway.shutdown) ) @@ -53,7 +67,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Unload deCONZ config entry.""" - gateway = hass.data[DOMAIN].pop(config_entry.entry_id) + gateway: DeconzGateway = hass.data[DOMAIN].pop(config_entry.entry_id) + async_unload_events(gateway) if not hass.data[DOMAIN]: async_unload_services(hass) @@ -89,9 +104,10 @@ async def async_update_group_unique_id( hass: HomeAssistant, config_entry: ConfigEntry ) -> None: """Update unique ID entities based on deCONZ groups.""" - if not isinstance(old_unique_id := config_entry.data.get(CONF_GROUP_ID_BASE), str): + if not (group_id_base := config_entry.data.get(CONF_GROUP_ID_BASE)): return + old_unique_id = cast(str, group_id_base) new_unique_id = cast(str, config_entry.unique_id) @callback diff --git a/homeassistant/components/deconz/gateway.py b/homeassistant/components/deconz/gateway.py index d197f27910b572..3bd0278a27a1bd 100644 --- a/homeassistant/components/deconz/gateway.py +++ b/homeassistant/components/deconz/gateway.py @@ -1,13 +1,21 @@ """Representation of a deCONZ gateway.""" + +from __future__ import annotations + import asyncio +from types import MappingProxyType +from typing import Any, cast import async_timeout from pydeconz import DeconzSession, errors, group, light, sensor +from pydeconz.alarm_system import AlarmSystem as DeconzAlarmSystem +from pydeconz.group import Group as DeconzGroup +from pydeconz.light import DeconzLight +from pydeconz.sensor import DeconzSensor from homeassistant.config_entries import SOURCE_HASSIO, ConfigEntry from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT -from homeassistant.core import HomeAssistant, callback -from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import ( aiohttp_client, device_registry as dr, @@ -29,25 +37,23 @@ LOGGER, PLATFORMS, ) -from .deconz_event import async_setup_events, async_unload_events +from .deconz_event import DeconzAlarmEvent, DeconzEvent from .errors import AuthenticationRequired, CannotConnect -@callback -def get_gateway_from_config_entry(hass, config_entry): - """Return gateway with a matching config entry ID.""" - return hass.data[DECONZ_DOMAIN][config_entry.entry_id] - - class DeconzGateway: """Manages a single deCONZ gateway.""" - def __init__(self, hass, config_entry) -> None: + def __init__( + self, hass: HomeAssistant, config_entry: ConfigEntry, api: DeconzSession + ) -> None: """Initialize the system.""" self.hass = hass self.config_entry = config_entry + self.api = api - self.api = None + api.add_device_callback = self.async_add_device_callback + api.connection_status_callback = self.async_connection_status_callback self.available = True self.ignore_state_updates = False @@ -66,24 +72,24 @@ def __init__(self, hass, config_entry) -> None: sensor.RESOURCE_TYPE: self.signal_new_sensor, } - self.deconz_ids = {} - self.entities = {} - self.events = [] + self.deconz_ids: dict[str, str] = {} + self.entities: dict[str, set[str]] = {} + self.events: list[DeconzAlarmEvent | DeconzEvent] = [] @property def bridgeid(self) -> str: """Return the unique identifier of the gateway.""" - return self.config_entry.unique_id + return cast(str, self.config_entry.unique_id) @property def host(self) -> str: """Return the host of the gateway.""" - return self.config_entry.data[CONF_HOST] + return cast(str, self.config_entry.data[CONF_HOST]) @property def master(self) -> bool: """Gateway which is used with deCONZ services without defining id.""" - return self.config_entry.options[CONF_MASTER_GATEWAY] + return cast(bool, self.config_entry.options[CONF_MASTER_GATEWAY]) # Options @@ -111,7 +117,7 @@ def option_allow_new_devices(self) -> bool: # Callbacks @callback - def async_connection_status_callback(self, available) -> None: + def async_connection_status_callback(self, available: bool) -> None: """Handle signals of gateway connection status.""" self.available = available self.ignore_state_updates = False @@ -119,7 +125,15 @@ def async_connection_status_callback(self, available) -> None: @callback def async_add_device_callback( - self, resource_type, device=None, force: bool = False + self, + resource_type: str, + device: DeconzAlarmSystem + | DeconzGroup + | DeconzLight + | DeconzSensor + | list[DeconzAlarmSystem | DeconzGroup | DeconzLight | DeconzSensor] + | None = None, + force: bool = False, ) -> None: """Handle event of new device creation in deCONZ.""" if ( @@ -166,32 +180,6 @@ async def async_update_device_registry(self) -> None: via_device=(CONNECTION_NETWORK_MAC, self.api.config.mac), ) - async def async_setup(self) -> bool: - """Set up a deCONZ gateway.""" - try: - self.api = await get_gateway( - self.hass, - self.config_entry.data, - self.async_add_device_callback, - self.async_connection_status_callback, - ) - - except CannotConnect as err: - raise ConfigEntryNotReady from err - - except AuthenticationRequired as err: - raise ConfigEntryAuthFailed from err - - self.hass.config_entries.async_setup_platforms(self.config_entry, PLATFORMS) - - await async_setup_events(self) - - self.api.start() - - self.config_entry.add_update_listener(self.async_config_entry_updated) - - return True - @staticmethod async def async_config_entry_updated( hass: HomeAssistant, entry: ConfigEntry @@ -211,7 +199,7 @@ async def async_config_entry_updated( await gateway.options_updated() - async def options_updated(self): + async def options_updated(self) -> None: """Manage entities affected by config entry options.""" deconz_ids = [] @@ -242,14 +230,14 @@ async def options_updated(self): entity_registry.async_remove(entity_id) @callback - def shutdown(self, event) -> None: + def shutdown(self, event: Event) -> None: """Wrap the call to deconz.close. Used as an argument to EventBus.async_listen_once. """ self.api.close() - async def async_reset(self): + async def async_reset(self) -> bool: """Reset this gateway to default state.""" self.api.async_connection_status_callback = None self.api.close() @@ -258,30 +246,35 @@ async def async_reset(self): self.config_entry, PLATFORMS ) - async_unload_events(self) - self.deconz_ids = {} return True -async def get_gateway( - hass, config, async_add_device_callback, async_connection_status_callback +@callback +def get_gateway_from_config_entry( + hass: HomeAssistant, config_entry: ConfigEntry +) -> DeconzGateway: + """Return gateway with a matching config entry ID.""" + return cast(DeconzGateway, hass.data[DECONZ_DOMAIN][config_entry.entry_id]) + + +async def get_deconz_session( + hass: HomeAssistant, + config: MappingProxyType[str, Any], ) -> DeconzSession: """Create a gateway object and verify configuration.""" session = aiohttp_client.async_get_clientsession(hass) - deconz = DeconzSession( + deconz_session = DeconzSession( session, config[CONF_HOST], config[CONF_PORT], config[CONF_API_KEY], - add_device=async_add_device_callback, - connection_status=async_connection_status_callback, ) try: async with async_timeout.timeout(10): - await deconz.refresh_state() - return deconz + await deconz_session.refresh_state() + return deconz_session except errors.Unauthorized as err: LOGGER.warning("Invalid key for deCONZ at %s", config[CONF_HOST]) diff --git a/homeassistant/components/deconz/services.py b/homeassistant/components/deconz/services.py index aeb528c0ac9004..4b840532fa6c6c 100644 --- a/homeassistant/components/deconz/services.py +++ b/homeassistant/components/deconz/services.py @@ -1,7 +1,5 @@ """deCONZ services.""" -from types import MappingProxyType - from pydeconz.utils import normalize_bridge_id import voluptuous as vol @@ -16,6 +14,7 @@ async_entries_for_config_entry, async_entries_for_device, ) +from homeassistant.util.read_only_dict import ReadOnlyDict from .config_flow import get_master_gateway from .const import CONF_BRIDGE_ID, DOMAIN, LOGGER @@ -111,9 +110,7 @@ def async_unload_services(hass: HomeAssistant) -> None: hass.services.async_remove(DOMAIN, service) -async def async_configure_service( - gateway: DeconzGateway, data: MappingProxyType -) -> None: +async def async_configure_service(gateway: DeconzGateway, data: ReadOnlyDict) -> None: """Set attribute of device in deCONZ. Entity is used to resolve to a device path (e.g. '/lights/1'). diff --git a/mypy.ini b/mypy.ini index b508045d623b5b..adb0cb2529711e 100644 --- a/mypy.ini +++ b/mypy.ini @@ -447,6 +447,61 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.deconz] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + +[mypy-homeassistant.components.deconz.config_flow] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + +[mypy-homeassistant.components.deconz.diagnostics] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + +[mypy-homeassistant.components.deconz.gateway] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + +[mypy-homeassistant.components.deconz.services] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.device_automation.*] check_untyped_defs = true disallow_incomplete_defs = true @@ -2209,9 +2264,6 @@ ignore_errors = true [mypy-homeassistant.components.conversation.default_agent] ignore_errors = true -[mypy-homeassistant.components.deconz] -ignore_errors = true - [mypy-homeassistant.components.deconz.alarm_control_panel] ignore_errors = true @@ -2227,9 +2279,6 @@ ignore_errors = true [mypy-homeassistant.components.deconz.fan] ignore_errors = true -[mypy-homeassistant.components.deconz.gateway] -ignore_errors = true - [mypy-homeassistant.components.deconz.light] ignore_errors = true @@ -2245,9 +2294,6 @@ ignore_errors = true [mypy-homeassistant.components.deconz.sensor] ignore_errors = true -[mypy-homeassistant.components.deconz.services] -ignore_errors = true - [mypy-homeassistant.components.deconz.siren] ignore_errors = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index 8842b42fbd3d94..8d542290458841 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -23,19 +23,16 @@ "homeassistant.components.cloud.http_api", "homeassistant.components.conversation", "homeassistant.components.conversation.default_agent", - "homeassistant.components.deconz", "homeassistant.components.deconz.alarm_control_panel", "homeassistant.components.deconz.binary_sensor", "homeassistant.components.deconz.climate", "homeassistant.components.deconz.cover", "homeassistant.components.deconz.fan", - "homeassistant.components.deconz.gateway", "homeassistant.components.deconz.light", "homeassistant.components.deconz.lock", "homeassistant.components.deconz.logbook", "homeassistant.components.deconz.number", "homeassistant.components.deconz.sensor", - "homeassistant.components.deconz.services", "homeassistant.components.deconz.siren", "homeassistant.components.deconz.switch", "homeassistant.components.denonavr.config_flow", diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index 8a449456fde205..3a4f6b907af219 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -1,7 +1,8 @@ """Test deCONZ gateway.""" +import asyncio from copy import deepcopy -from unittest.mock import Mock, patch +from unittest.mock import patch import pydeconz from pydeconz.websocket import STATE_RETRYING, STATE_RUNNING @@ -19,7 +20,7 @@ from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.errors import AuthenticationRequired, CannotConnect from homeassistant.components.deconz.gateway import ( - get_gateway, + get_deconz_session, get_gateway_from_config_entry, ) from homeassistant.components.fan import DOMAIN as FAN_DOMAIN @@ -202,25 +203,6 @@ async def test_gateway_device_configuration_url_when_addon(hass, aioclient_mock) ) -async def test_gateway_retry(hass): - """Retry setup.""" - with patch( - "homeassistant.components.deconz.gateway.get_gateway", - side_effect=CannotConnect, - ): - await setup_deconz_integration(hass) - assert not hass.data[DECONZ_DOMAIN] - - -async def test_gateway_setup_fails(hass): - """Retry setup.""" - with patch( - "homeassistant.components.deconz.gateway.get_gateway", side_effect=Exception - ): - await setup_deconz_integration(hass) - assert not hass.data[DECONZ_DOMAIN] - - async def test_connection_status_signalling( hass, aioclient_mock, mock_deconz_websocket ): @@ -282,18 +264,6 @@ async def test_update_address(hass, aioclient_mock): assert len(mock_setup_entry.mock_calls) == 1 -async def test_gateway_trigger_reauth_flow(hass): - """Failed authentication trigger a reauthentication flow.""" - with patch( - "homeassistant.components.deconz.gateway.get_gateway", - side_effect=AuthenticationRequired, - ), patch.object(hass.config_entries.flow, "async_init") as mock_flow_init: - await setup_deconz_integration(hass) - mock_flow_init.assert_called_once() - - assert hass.data[DECONZ_DOMAIN] == {} - - async def test_reset_after_successful_setup(hass, aioclient_mock): """Make sure that connection status triggers a dispatcher send.""" config_entry = await setup_deconz_integration(hass, aioclient_mock) @@ -305,25 +275,24 @@ async def test_reset_after_successful_setup(hass, aioclient_mock): assert result is True -async def test_get_gateway(hass): +async def test_get_deconz_session(hass): """Successful call.""" with patch("pydeconz.DeconzSession.refresh_state", return_value=True): - assert await get_gateway(hass, ENTRY_CONFIG, Mock(), Mock()) + assert await get_deconz_session(hass, ENTRY_CONFIG) -async def test_get_gateway_fails_unauthorized(hass): - """Failed call.""" - with patch( - "pydeconz.DeconzSession.refresh_state", - side_effect=pydeconz.errors.Unauthorized, - ), pytest.raises(AuthenticationRequired): - assert await get_gateway(hass, ENTRY_CONFIG, Mock(), Mock()) is False - - -async def test_get_gateway_fails_cannot_connect(hass): +@pytest.mark.parametrize( + "side_effect, raised_exception", + [ + (asyncio.TimeoutError, CannotConnect), + (pydeconz.RequestError, CannotConnect), + (pydeconz.Unauthorized, AuthenticationRequired), + ], +) +async def test_get_deconz_session_fails(hass, side_effect, raised_exception): """Failed call.""" with patch( "pydeconz.DeconzSession.refresh_state", - side_effect=pydeconz.errors.RequestError, - ), pytest.raises(CannotConnect): - assert await get_gateway(hass, ENTRY_CONFIG, Mock(), Mock()) is False + side_effect=side_effect, + ), pytest.raises(raised_exception): + assert await get_deconz_session(hass, ENTRY_CONFIG) diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index e50ac41d63de85..05fb708b75f412 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -1,6 +1,5 @@ """Test deCONZ component setup process.""" -import asyncio from unittest.mock import patch from homeassistant.components.deconz import ( @@ -13,6 +12,7 @@ CONF_GROUP_ID_BASE, DOMAIN as DECONZ_DOMAIN, ) +from homeassistant.components.deconz.errors import AuthenticationRequired, CannotConnect from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT from homeassistant.helpers import entity_registry as er @@ -42,29 +42,36 @@ async def setup_entry(hass, entry): assert await async_setup_entry(hass, entry) is True -async def test_setup_entry_fails(hass): - """Test setup entry fails if deCONZ is not available.""" - with patch("pydeconz.DeconzSession.refresh_state", side_effect=Exception): - await setup_deconz_integration(hass) - assert not hass.data[DECONZ_DOMAIN] +async def test_setup_entry_successful(hass, aioclient_mock): + """Test setup entry is successful.""" + config_entry = await setup_deconz_integration(hass, aioclient_mock) + assert hass.data[DECONZ_DOMAIN] + assert config_entry.entry_id in hass.data[DECONZ_DOMAIN] + assert hass.data[DECONZ_DOMAIN][config_entry.entry_id].master -async def test_setup_entry_no_available_bridge(hass): - """Test setup entry fails if deCONZ is not available.""" + +async def test_setup_entry_fails_config_entry_not_ready(hass): + """Failed authentication trigger a reauthentication flow.""" with patch( - "pydeconz.DeconzSession.refresh_state", side_effect=asyncio.TimeoutError + "homeassistant.components.deconz.get_deconz_session", + side_effect=CannotConnect, ): await setup_deconz_integration(hass) - assert not hass.data[DECONZ_DOMAIN] + assert hass.data[DECONZ_DOMAIN] == {} -async def test_setup_entry_successful(hass, aioclient_mock): - """Test setup entry is successful.""" - config_entry = await setup_deconz_integration(hass, aioclient_mock) - assert hass.data[DECONZ_DOMAIN] - assert config_entry.entry_id in hass.data[DECONZ_DOMAIN] - assert hass.data[DECONZ_DOMAIN][config_entry.entry_id].master +async def test_setup_entry_fails_trigger_reauth_flow(hass): + """Failed authentication trigger a reauthentication flow.""" + with patch( + "homeassistant.components.deconz.get_deconz_session", + side_effect=AuthenticationRequired, + ), patch.object(hass.config_entries.flow, "async_init") as mock_flow_init: + await setup_deconz_integration(hass) + mock_flow_init.assert_called_once() + + assert hass.data[DECONZ_DOMAIN] == {} async def test_setup_entry_multiple_gateways(hass, aioclient_mock): From d97da2fd498eda8a2d7c21acc71191719b2b9c64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 23 Feb 2022 13:37:07 +0100 Subject: [PATCH 0986/1098] Bump awesomeversion from 22.1.0 to 22.2.0 (#67107) --- homeassistant/package_constraints.txt | 2 +- requirements.txt | 2 +- setup.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 89631a4fc1eadd..472202859e2e96 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -8,7 +8,7 @@ async-upnp-client==0.23.5 async_timeout==4.0.2 atomicwrites==1.4.0 attrs==21.2.0 -awesomeversion==22.1.0 +awesomeversion==22.2.0 bcrypt==3.1.7 certifi>=2021.5.30 ciso8601==2.2.0 diff --git a/requirements.txt b/requirements.txt index b907cc50cbb33d..4885e717b30fbc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ astral==2.2 async_timeout==4.0.2 attrs==21.2.0 atomicwrites==1.4.0 -awesomeversion==22.1.0 +awesomeversion==22.2.0 bcrypt==3.1.7 certifi>=2021.5.30 ciso8601==2.2.0 diff --git a/setup.cfg b/setup.cfg index 75d7905ff0eebb..74ea6e296b59e2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -36,7 +36,7 @@ install_requires = async_timeout==4.0.2 attrs==21.2.0 atomicwrites==1.4.0 - awesomeversion==22.1.0 + awesomeversion==22.2.0 bcrypt==3.1.7 certifi>=2021.5.30 ciso8601==2.2.0 From 834f1403c654983305e56abea7ef4408bf7082d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 23 Feb 2022 13:37:27 +0100 Subject: [PATCH 0987/1098] Bump pyhaversion from 21.11.1 to 22.02.0 (#67108) --- homeassistant/components/version/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/version/manifest.json b/homeassistant/components/version/manifest.json index 803076e44dc840..fa1410521ae006 100644 --- a/homeassistant/components/version/manifest.json +++ b/homeassistant/components/version/manifest.json @@ -3,7 +3,7 @@ "name": "Version", "documentation": "https://www.home-assistant.io/integrations/version", "requirements": [ - "pyhaversion==21.11.1" + "pyhaversion==22.02.0" ], "codeowners": [ "@fabaff", diff --git a/requirements_all.txt b/requirements_all.txt index 75ea65b43c475c..bc33d5b8012043 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1565,7 +1565,7 @@ pygtfs==0.1.6 pygti==0.9.2 # homeassistant.components.version -pyhaversion==21.11.1 +pyhaversion==22.02.0 # homeassistant.components.heos pyheos==0.7.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f14ef8acb71aac..69ea303f3f368a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -985,7 +985,7 @@ pygatt[GATTTOOL]==4.0.5 pygti==0.9.2 # homeassistant.components.version -pyhaversion==21.11.1 +pyhaversion==22.02.0 # homeassistant.components.heos pyheos==0.7.2 From f82d1a88e88f6c09c02f758d760ff225d868ee3d Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Wed, 23 Feb 2022 08:24:46 -0500 Subject: [PATCH 0988/1098] Bump ZHA quirks to 0.0.67 (#67109) --- homeassistant/components/zha/manifest.json | 15 +++++++++++++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index 009b8e5c775837..e542c77516e370 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -7,7 +7,7 @@ "bellows==0.29.0", "pyserial==3.5", "pyserial-asyncio==0.6", - "zha-quirks==0.0.66", + "zha-quirks==0.0.67", "zigpy-deconz==0.14.0", "zigpy==0.43.0", "zigpy-xbee==0.14.0", @@ -73,5 +73,16 @@ ], "after_dependencies": ["usb", "zeroconf"], "iot_class": "local_polling", - "loggers": ["aiosqlite", "bellows", "crccheck", "pure_pcapy3", "zhaquirks", "zigpy", "zigpy_deconz", "zigpy_xbee", "zigpy_zigate", "zigpy_znp"] + "loggers": [ + "aiosqlite", + "bellows", + "crccheck", + "pure_pcapy3", + "zhaquirks", + "zigpy", + "zigpy_deconz", + "zigpy_xbee", + "zigpy_zigate", + "zigpy_znp" + ] } diff --git a/requirements_all.txt b/requirements_all.txt index bc33d5b8012043..d3044a7bf88f88 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2539,7 +2539,7 @@ zengge==0.2 zeroconf==0.38.3 # homeassistant.components.zha -zha-quirks==0.0.66 +zha-quirks==0.0.67 # homeassistant.components.zhong_hong zhong_hong_hvac==1.0.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 69ea303f3f368a..f85a5a6f779008 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1573,7 +1573,7 @@ youless-api==0.16 zeroconf==0.38.3 # homeassistant.components.zha -zha-quirks==0.0.66 +zha-quirks==0.0.67 # homeassistant.components.zha zigpy-deconz==0.14.0 From 0a6e30e4b9738bfcbc221e982505737cdb23723c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 23 Feb 2022 15:21:46 +0100 Subject: [PATCH 0989/1098] Improve sonos ConfigFlow registration (#67110) --- homeassistant/components/sonos/config_flow.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/homeassistant/components/sonos/config_flow.py b/homeassistant/components/sonos/config_flow.py index 30778edc493bfb..cc453f146911b5 100644 --- a/homeassistant/components/sonos/config_flow.py +++ b/homeassistant/components/sonos/config_flow.py @@ -2,7 +2,6 @@ from collections.abc import Awaitable import dataclasses -from homeassistant import config_entries from homeassistant.components import ssdp, zeroconf from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResult @@ -17,7 +16,7 @@ async def _async_has_devices(hass: HomeAssistant) -> bool: return bool(await ssdp.async_get_discovery_info_by_st(hass, UPNP_ST)) -class SonosDiscoveryFlowHandler(DiscoveryFlowHandler[Awaitable[bool]]): +class SonosDiscoveryFlowHandler(DiscoveryFlowHandler[Awaitable[bool]], domain=DOMAIN): """Sonos discovery flow that callsback zeroconf updates.""" def __init__(self) -> None: @@ -43,6 +42,3 @@ async def async_step_zeroconf( "Zeroconf", properties, host, uid, boot_seqnum, model, mdns_name ) return await self.async_step_discovery(dataclasses.asdict(discovery_info)) - - -config_entries.HANDLERS.register(DOMAIN)(SonosDiscoveryFlowHandler) From 8befb3b905ecdf18719412ef589387676195197f Mon Sep 17 00:00:00 2001 From: Robert Hillis Date: Wed, 23 Feb 2022 09:22:50 -0500 Subject: [PATCH 0990/1098] Deprecate yaml config for fritzbox callmonitor (#61762) Co-authored-by: Franck Nijhof --- homeassistant/components/fritzbox_callmonitor/sensor.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homeassistant/components/fritzbox_callmonitor/sensor.py b/homeassistant/components/fritzbox_callmonitor/sensor.py index 490b67c03bd8f4..5efb0776c418f9 100644 --- a/homeassistant/components/fritzbox_callmonitor/sensor.py +++ b/homeassistant/components/fritzbox_callmonitor/sensor.py @@ -55,6 +55,7 @@ SCAN_INTERVAL = timedelta(hours=3) +# Deprecated in Home Assistant 2022.3 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, @@ -75,6 +76,12 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Import the platform into a config entry.""" + _LOGGER.warning( + "Configuration of the AVM FRITZ!Box Call Monitor sensor platform in YAML " + "is deprecated and will be removed in Home Assistant 2022.5; " + "Your existing configuration has been imported into the UI automatically " + "and can be safely removed from your configuration.yaml file" + ) hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=config From eb80abf89ecebcd3c9610e031127589ea8ae237f Mon Sep 17 00:00:00 2001 From: Robert Hillis Date: Wed, 23 Feb 2022 11:05:21 -0500 Subject: [PATCH 0991/1098] Add Phone Modem call reject button (#66742) Co-authored-by: Franck Nijhof --- .coveragerc | 1 + .../components/modem_callerid/__init__.py | 2 +- .../components/modem_callerid/button.py | 47 +++++++++++++++++++ .../components/modem_callerid/sensor.py | 9 ++++ 4 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/modem_callerid/button.py diff --git a/.coveragerc b/.coveragerc index 0d79bb02a4a70a..9a3a52895c3e5c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -720,6 +720,7 @@ omit = homeassistant/components/mochad/* homeassistant/components/modbus/climate.py homeassistant/components/modbus/binary_sensor.py + homeassistant/components/modem_callerid/button.py homeassistant/components/modem_callerid/sensor.py homeassistant/components/moehlenhoff_alpha2/__init__.py homeassistant/components/moehlenhoff_alpha2/climate.py diff --git a/homeassistant/components/modem_callerid/__init__.py b/homeassistant/components/modem_callerid/__init__.py index d66be29f8b72ec..8f62cf4beb5abf 100644 --- a/homeassistant/components/modem_callerid/__init__.py +++ b/homeassistant/components/modem_callerid/__init__.py @@ -8,7 +8,7 @@ from .const import DATA_KEY_API, DOMAIN, EXCEPTIONS -PLATFORMS = [Platform.SENSOR] +PLATFORMS = [Platform.BUTTON, Platform.SENSOR] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/modem_callerid/button.py b/homeassistant/components/modem_callerid/button.py new file mode 100644 index 00000000000000..63a88a8a4e5f63 --- /dev/null +++ b/homeassistant/components/modem_callerid/button.py @@ -0,0 +1,47 @@ +"""Support for Phone Modem button.""" +from __future__ import annotations + +from phone_modem import PhoneModem + +from homeassistant.components.button import ButtonEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_DEVICE +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_platform + +from .const import DATA_KEY_API, DOMAIN + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: entity_platform.AddEntitiesCallback, +) -> None: + """Set up the Modem Caller ID sensor.""" + api = hass.data[DOMAIN][entry.entry_id][DATA_KEY_API] + async_add_entities( + [ + PhoneModemButton( + api, + entry.data[CONF_DEVICE], + entry.entry_id, + ) + ] + ) + + +class PhoneModemButton(ButtonEntity): + """Implementation of USB modem caller ID button.""" + + _attr_icon = "mdi:phone-hangup" + _attr_name = "Phone Modem Reject" + + def __init__(self, api: PhoneModem, device: str, server_unique_id: str) -> None: + """Initialize the button.""" + self.device = device + self.api = api + self._attr_unique_id = server_unique_id + + async def async_press(self) -> None: + """Press the button.""" + await self.api.reject_call(self.device) diff --git a/homeassistant/components/modem_callerid/sensor.py b/homeassistant/components/modem_callerid/sensor.py index 94c7c51ee80512..3a1af4aa0a8ce1 100644 --- a/homeassistant/components/modem_callerid/sensor.py +++ b/homeassistant/components/modem_callerid/sensor.py @@ -1,6 +1,8 @@ """A sensor for incoming calls using a USB modem that supports caller ID.""" from __future__ import annotations +import logging + from phone_modem import PhoneModem from homeassistant.components.sensor import SensorEntity @@ -11,6 +13,8 @@ from .const import CID, DATA_KEY_API, DOMAIN, ICON, SERVICE_REJECT_CALL +_LOGGER = logging.getLogger(__name__) + async def async_setup_entry( hass: HomeAssistant, @@ -42,6 +46,11 @@ async def _async_on_hass_stop(event: Event) -> None: platform = entity_platform.async_get_current_platform() platform.async_register_entity_service(SERVICE_REJECT_CALL, {}, "async_reject_call") + _LOGGER.warning( + "Calling reject_call service is deprecated and will be removed after 2022.4; " + "A new button entity is now available with the same function " + "and replaces the existing service" + ) class ModemCalleridSensor(SensorEntity): From 419e68352697c850056971462e7d614e9e1918cd Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Wed, 23 Feb 2022 17:14:58 +0100 Subject: [PATCH 0992/1098] Add Remote Engine Start status to Renault integration (#67028) Co-authored-by: epenet --- .../components/renault/renault_vehicle.py | 5 ++ homeassistant/components/renault/sensor.py | 16 +++++++ tests/components/renault/conftest.py | 14 ++++++ tests/components/renault/const.py | 47 +++++++++++++++++++ .../renault/fixtures/res_state.1.json | 10 ++++ tests/components/renault/test_diagnostics.py | 1 + 6 files changed, 93 insertions(+) create mode 100644 tests/components/renault/fixtures/res_state.1.json diff --git a/homeassistant/components/renault/renault_vehicle.py b/homeassistant/components/renault/renault_vehicle.py index c4e42a7be5becc..12860bc6b9aebe 100644 --- a/homeassistant/components/renault/renault_vehicle.py +++ b/homeassistant/components/renault/renault_vehicle.py @@ -153,4 +153,9 @@ async def async_initialise(self) -> None: key="lock_status", update_method=lambda x: x.get_lock_status, ), + RenaultCoordinatorDescription( + endpoint="res-state", + key="res_state", + update_method=lambda x: x.get_res_state, + ), ) diff --git a/homeassistant/components/renault/sensor.py b/homeassistant/components/renault/sensor.py index 8e63d09b5c762c..c6621b16bbc4dc 100644 --- a/homeassistant/components/renault/sensor.py +++ b/homeassistant/components/renault/sensor.py @@ -12,6 +12,7 @@ KamereonVehicleCockpitData, KamereonVehicleHvacStatusData, KamereonVehicleLocationData, + KamereonVehicleResStateData, ) from homeassistant.components.sensor import ( @@ -333,4 +334,19 @@ def _get_utc_value(entity: RenaultSensor[T]) -> datetime: name="Location Last Activity", value_lambda=_get_utc_value, ), + RenaultSensorEntityDescription( + key="res_state", + coordinator="res_state", + data_key="details", + entity_class=RenaultSensor[KamereonVehicleResStateData], + name="Remote Engine Start", + ), + RenaultSensorEntityDescription( + key="res_state_code", + coordinator="res_state", + data_key="code", + entity_class=RenaultSensor[KamereonVehicleResStateData], + entity_registry_enabled_default=False, + name="Remote Engine Start Code", + ), ) diff --git a/tests/components/renault/conftest.py b/tests/components/renault/conftest.py index 89c6c364860099..6c62e5d22e2ee4 100644 --- a/tests/components/renault/conftest.py +++ b/tests/components/renault/conftest.py @@ -101,6 +101,11 @@ def _get_fixtures(vehicle_type: str) -> MappingProxyType: if "lock_status" in mock_vehicle["endpoints"] else load_fixture("renault/no_data.json") ).get_attributes(schemas.KamereonVehicleLockStatusDataSchema), + "res_state": schemas.KamereonVehicleDataResponseSchema.loads( + load_fixture(f"renault/{mock_vehicle['endpoints']['res_state']}") + if "res_state" in mock_vehicle["endpoints"] + else load_fixture("renault/no_data.json") + ).get_attributes(schemas.KamereonVehicleResStateDataSchema), } @@ -127,6 +132,9 @@ def patch_fixtures_with_data(vehicle_type: str): ), patch( "renault_api.renault_vehicle.RenaultVehicle.get_lock_status", return_value=mock_fixtures["lock_status"], + ), patch( + "renault_api.renault_vehicle.RenaultVehicle.get_res_state", + return_value=mock_fixtures["res_state"], ): yield @@ -154,6 +162,9 @@ def patch_fixtures_with_no_data(): ), patch( "renault_api.renault_vehicle.RenaultVehicle.get_lock_status", return_value=mock_fixtures["lock_status"], + ), patch( + "renault_api.renault_vehicle.RenaultVehicle.get_res_state", + return_value=mock_fixtures["res_state"], ): yield @@ -179,6 +190,9 @@ def _patch_fixtures_with_side_effect(side_effect: Any): ), patch( "renault_api.renault_vehicle.RenaultVehicle.get_lock_status", side_effect=side_effect, + ), patch( + "renault_api.renault_vehicle.RenaultVehicle.get_res_state", + side_effect=side_effect, ): yield diff --git a/tests/components/renault/const.py b/tests/components/renault/const.py index 368f3b97fbdf8d..41a72c6b7ab375 100644 --- a/tests/components/renault/const.py +++ b/tests/components/renault/const.py @@ -229,6 +229,17 @@ ATTR_STATE: "plugged", ATTR_UNIQUE_ID: "vf1aaaaa555777999_plug_state", }, + { + ATTR_ENTITY_ID: "sensor.reg_number_remote_engine_start", + ATTR_STATE: STATE_UNKNOWN, + ATTR_UNIQUE_ID: "vf1aaaaa555777999_res_state", + }, + { + ATTR_DEFAULT_DISABLED: True, + ATTR_ENTITY_ID: "sensor.reg_number_remote_engine_start_code", + ATTR_STATE: STATE_UNKNOWN, + ATTR_UNIQUE_ID: "vf1aaaaa555777999_res_state_code", + }, ], }, "zoe_50": { @@ -246,6 +257,7 @@ "hvac_status": "hvac_status.2.json", "location": "location.json", "lock_status": "lock_status.1.json", + "res_state": "res_state.1.json", }, Platform.BINARY_SENSOR: [ { @@ -441,6 +453,17 @@ ATTR_STATE: "2020-02-18T16:58:38+00:00", ATTR_UNIQUE_ID: "vf1aaaaa555777999_location_last_activity", }, + { + ATTR_ENTITY_ID: "sensor.reg_number_remote_engine_start", + ATTR_STATE: "Stopped, ready for RES", + ATTR_UNIQUE_ID: "vf1aaaaa555777999_res_state", + }, + { + ATTR_DEFAULT_DISABLED: True, + ATTR_ENTITY_ID: "sensor.reg_number_remote_engine_start_code", + ATTR_STATE: "10", + ATTR_UNIQUE_ID: "vf1aaaaa555777999_res_state_code", + }, ], }, "captur_phev": { @@ -457,6 +480,7 @@ "cockpit": "cockpit_fuel.json", "location": "location.json", "lock_status": "lock_status.1.json", + "res_state": "res_state.1.json", }, Platform.BINARY_SENSOR: [ { @@ -641,6 +665,17 @@ ATTR_STATE: "2020-02-18T16:58:38+00:00", ATTR_UNIQUE_ID: "vf1aaaaa555777123_location_last_activity", }, + { + ATTR_ENTITY_ID: "sensor.reg_number_remote_engine_start", + ATTR_STATE: "Stopped, ready for RES", + ATTR_UNIQUE_ID: "vf1aaaaa555777123_res_state", + }, + { + ATTR_DEFAULT_DISABLED: True, + ATTR_ENTITY_ID: "sensor.reg_number_remote_engine_start_code", + ATTR_STATE: "10", + ATTR_UNIQUE_ID: "vf1aaaaa555777123_res_state_code", + }, ], }, "captur_fuel": { @@ -655,6 +690,7 @@ "cockpit": "cockpit_fuel.json", "location": "location.json", "lock_status": "lock_status.1.json", + "res_state": "res_state.1.json", }, Platform.BINARY_SENSOR: [ { @@ -743,6 +779,17 @@ ATTR_STATE: "2020-02-18T16:58:38+00:00", ATTR_UNIQUE_ID: "vf1aaaaa555777123_location_last_activity", }, + { + ATTR_ENTITY_ID: "sensor.reg_number_remote_engine_start", + ATTR_STATE: "Stopped, ready for RES", + ATTR_UNIQUE_ID: "vf1aaaaa555777123_res_state", + }, + { + ATTR_DEFAULT_DISABLED: True, + ATTR_ENTITY_ID: "sensor.reg_number_remote_engine_start_code", + ATTR_STATE: "10", + ATTR_UNIQUE_ID: "vf1aaaaa555777123_res_state_code", + }, ], }, } diff --git a/tests/components/renault/fixtures/res_state.1.json b/tests/components/renault/fixtures/res_state.1.json new file mode 100644 index 00000000000000..e6973ed091af86 --- /dev/null +++ b/tests/components/renault/fixtures/res_state.1.json @@ -0,0 +1,10 @@ +{ + "data": { + "type": "ResState", + "id": "VF1AAAAA555777999", + "attributes": { + "details": "Stopped, ready for RES", + "code": "10" + } + } + } \ No newline at end of file diff --git a/tests/components/renault/test_diagnostics.py b/tests/components/renault/test_diagnostics.py index 1a61859ac93a53..fccf5a757b4002 100644 --- a/tests/components/renault/test_diagnostics.py +++ b/tests/components/renault/test_diagnostics.py @@ -157,6 +157,7 @@ "externalTemperature": 8.0, "hvacStatus": "off", }, + "res_state": {}, } From 49aabcb2ac4e37bd9563acf27f4dcf1cd009420e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 23 Feb 2022 17:38:52 +0100 Subject: [PATCH 0993/1098] Add homeassistant to partial backup service (#67117) --- homeassistant/components/hassio/__init__.py | 1 + homeassistant/components/hassio/services.yaml | 5 +++++ tests/components/hassio/test_init.py | 8 +++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 434c95b03b2ddd..b5549c1b5e4f23 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -125,6 +125,7 @@ SCHEMA_BACKUP_PARTIAL = SCHEMA_BACKUP_FULL.extend( { + vol.Optional(ATTR_HOMEASSISTANT): cv.boolean, vol.Optional(ATTR_FOLDERS): vol.All(cv.ensure_list, [cv.string]), vol.Optional(ATTR_ADDONS): vol.All(cv.ensure_list, [cv.string]), } diff --git a/homeassistant/components/hassio/services.yaml b/homeassistant/components/hassio/services.yaml index 6b77a180c09726..6186f222183c81 100644 --- a/homeassistant/components/hassio/services.yaml +++ b/homeassistant/components/hassio/services.yaml @@ -87,6 +87,11 @@ backup_partial: name: Create a partial backup. description: Create a partial backup. fields: + homeassistant: + name: Home Assistant settings + description: Backup Home Assistant settings + selector: + boolean: addons: name: Add-ons description: Optional list of add-on slugs. diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index e006cf9d829137..689ec13804361d 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -397,12 +397,18 @@ async def test_service_calls(hassio_env, hass, aioclient_mock, caplog): await hass.services.async_call( "hassio", "backup_partial", - {"addons": ["test"], "folders": ["ssl"], "password": "123456"}, + { + "homeassistant": True, + "addons": ["test"], + "folders": ["ssl"], + "password": "123456", + }, ) await hass.async_block_till_done() assert aioclient_mock.call_count == 12 assert aioclient_mock.mock_calls[-1][2] == { + "homeassistant": True, "addons": ["test"], "folders": ["ssl"], "password": "123456", From 8b7639940eeafdd922a9fe37de271155868833e4 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 23 Feb 2022 17:47:54 +0100 Subject: [PATCH 0994/1098] Fix type issues [mobile_app] (#67091) --- .../components/mobile_app/binary_sensor.py | 11 +++++----- .../components/mobile_app/device_action.py | 3 ++- .../components/mobile_app/device_tracker.py | 1 - homeassistant/components/mobile_app/entity.py | 6 +++--- .../components/mobile_app/helpers.py | 12 +++++------ .../components/mobile_app/http_api.py | 3 ++- .../mobile_app/push_notification.py | 2 +- homeassistant/components/mobile_app/sensor.py | 11 +++++----- mypy.ini | 21 ------------------- script/hassfest/mypy_config.py | 7 ------- 10 files changed, 24 insertions(+), 53 deletions(-) diff --git a/homeassistant/components/mobile_app/binary_sensor.py b/homeassistant/components/mobile_app/binary_sensor.py index d9d766974fdbe1..4d40e42a47e23b 100644 --- a/homeassistant/components/mobile_app/binary_sensor.py +++ b/homeassistant/components/mobile_app/binary_sensor.py @@ -1,4 +1,6 @@ """Binary sensor platform for mobile_app.""" +from typing import Any + from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME, CONF_UNIQUE_ID, CONF_WEBHOOK_ID, STATE_ON @@ -18,7 +20,6 @@ ATTR_SENSOR_TYPE, ATTR_SENSOR_TYPE_BINARY_SENSOR as ENTITY_TYPE, ATTR_SENSOR_UNIQUE_ID, - DATA_DEVICES, DOMAIN, ) from .entity import MobileAppEntity, unique_id @@ -39,7 +40,7 @@ async def async_setup_entry( for entry in entries: if entry.domain != ENTITY_TYPE or entry.disabled_by: continue - config = { + config: dict[str, Any] = { ATTR_SENSOR_ATTRIBUTES: {}, ATTR_SENSOR_DEVICE_CLASS: entry.device_class or entry.original_device_class, ATTR_SENSOR_ICON: entry.original_icon, @@ -49,7 +50,7 @@ async def async_setup_entry( ATTR_SENSOR_UNIQUE_ID: entry.unique_id, ATTR_SENSOR_ENTITY_CATEGORY: entry.entity_category, } - entities.append(MobileAppBinarySensor(config, entry.device_id, config_entry)) + entities.append(MobileAppBinarySensor(config, config_entry)) async_add_entities(entities) @@ -65,9 +66,7 @@ def handle_sensor_registration(data): CONF_NAME ] = f"{config_entry.data[ATTR_DEVICE_NAME]} {data[ATTR_SENSOR_NAME]}" - device = hass.data[DOMAIN][DATA_DEVICES][data[CONF_WEBHOOK_ID]] - - async_add_entities([MobileAppBinarySensor(data, device, config_entry)]) + async_add_entities([MobileAppBinarySensor(data, config_entry)]) async_dispatcher_connect( hass, diff --git a/homeassistant/components/mobile_app/device_action.py b/homeassistant/components/mobile_app/device_action.py index 3ad43098225416..d17702ec24fb70 100644 --- a/homeassistant/components/mobile_app/device_action.py +++ b/homeassistant/components/mobile_app/device_action.py @@ -7,6 +7,7 @@ from homeassistant.components.device_automation import InvalidDeviceAutomationConfig from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_TYPE from homeassistant.core import Context, HomeAssistant +from homeassistant.exceptions import TemplateError from homeassistant.helpers import config_validation as cv, template from .const import DOMAIN @@ -62,7 +63,7 @@ async def async_call_action_from_config( try: service_data[key] = template.render_complex(value_template, variables) - except template.TemplateError as err: + except TemplateError as err: raise InvalidDeviceAutomationConfig( f"Error rendering {key}: {err}" ) from err diff --git a/homeassistant/components/mobile_app/device_tracker.py b/homeassistant/components/mobile_app/device_tracker.py index 67f3672ef8da15..5e2ae23af16554 100644 --- a/homeassistant/components/mobile_app/device_tracker.py +++ b/homeassistant/components/mobile_app/device_tracker.py @@ -37,7 +37,6 @@ async def async_setup_entry( """Set up OwnTracks based off an entry.""" entity = MobileAppEntity(entry) async_add_entities([entity]) - return True class MobileAppEntity(TrackerEntity, RestoreEntity): diff --git a/homeassistant/components/mobile_app/entity.py b/homeassistant/components/mobile_app/entity.py index 0c26533b7cb509..0cb6cfc6fcc5d1 100644 --- a/homeassistant/components/mobile_app/entity.py +++ b/homeassistant/components/mobile_app/entity.py @@ -1,4 +1,6 @@ """A entity class for mobile_app.""" +from __future__ import annotations + from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_ICON, @@ -8,7 +10,6 @@ STATE_UNAVAILABLE, ) from homeassistant.core import callback -from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.restore_state import RestoreEntity @@ -33,10 +34,9 @@ def unique_id(webhook_id, sensor_unique_id): class MobileAppEntity(RestoreEntity): """Representation of an mobile app entity.""" - def __init__(self, config: dict, device: DeviceEntry, entry: ConfigEntry) -> None: + def __init__(self, config: dict, entry: ConfigEntry) -> None: """Initialize the entity.""" self._config = config - self._device = device self._entry = entry self._registration = entry.data self._unique_id = config[CONF_UNIQUE_ID] diff --git a/homeassistant/components/mobile_app/helpers.py b/homeassistant/components/mobile_app/helpers.py index c4e0a81560b0fb..b7d38357a786d2 100644 --- a/homeassistant/components/mobile_app/helpers.py +++ b/homeassistant/components/mobile_app/helpers.py @@ -60,7 +60,7 @@ def encrypt(ciphertext, key): return (SecretBox.KEY_SIZE, encrypt) -def _decrypt_payload(key: str, ciphertext: str) -> dict[str, str]: +def _decrypt_payload(key: str | None, ciphertext: str) -> dict[str, str] | None: """Decrypt encrypted payload.""" try: keylen, decrypt = setup_decrypt() @@ -72,13 +72,13 @@ def _decrypt_payload(key: str, ciphertext: str) -> dict[str, str]: _LOGGER.warning("Ignoring encrypted payload because no decryption key known") return None - key = key.encode("utf-8") - key = key[:keylen] - key = key.ljust(keylen, b"\0") + key_bytes = key.encode("utf-8") + key_bytes = key_bytes[:keylen] + key_bytes = key_bytes.ljust(keylen, b"\0") try: - message = decrypt(ciphertext, key) - message = json.loads(message.decode("utf-8")) + msg_bytes = decrypt(ciphertext, key_bytes) + message = json.loads(msg_bytes.decode("utf-8")) _LOGGER.debug("Successfully decrypted mobile_app payload") return message except ValueError: diff --git a/homeassistant/components/mobile_app/http_api.py b/homeassistant/components/mobile_app/http_api.py index a80e7db204ea74..20fa25ec21f886 100644 --- a/homeassistant/components/mobile_app/http_api.py +++ b/homeassistant/components/mobile_app/http_api.py @@ -4,6 +4,7 @@ from contextlib import suppress from http import HTTPStatus import secrets +from typing import cast from aiohttp.web import Request, Response import emoji @@ -89,7 +90,7 @@ async def post(self, request: Request, data: dict) -> Response: # If otherwise empty string contains emoji # use descriptive name of the first emoji data[ATTR_DEVICE_NAME] = emoji.demojize( - emoji.emoji_lis(data[ATTR_DEVICE_NAME])[0]["emoji"] + cast(str, emoji.emoji_lis(data[ATTR_DEVICE_NAME])[0]["emoji"]) ).replace(":", "") else: # Fallback to DEVICE_ID diff --git a/homeassistant/components/mobile_app/push_notification.py b/homeassistant/components/mobile_app/push_notification.py index f3852895d32ac6..0ef9739c8bde36 100644 --- a/homeassistant/components/mobile_app/push_notification.py +++ b/homeassistant/components/mobile_app/push_notification.py @@ -28,7 +28,7 @@ def __init__( self.support_confirm = support_confirm self._send_message = send_message self.on_teardown = on_teardown - self.pending_confirms = {} + self.pending_confirms: dict[str, dict] = {} @callback def async_send_notification(self, data, fallback_send): diff --git a/homeassistant/components/mobile_app/sensor.py b/homeassistant/components/mobile_app/sensor.py index 32477ccb50a7e3..45bc4acd6a2d1d 100644 --- a/homeassistant/components/mobile_app/sensor.py +++ b/homeassistant/components/mobile_app/sensor.py @@ -1,6 +1,8 @@ """Sensor platform for mobile_app.""" from __future__ import annotations +from typing import Any + from homeassistant.components.sensor import SensorDeviceClass, SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -28,7 +30,6 @@ ATTR_SENSOR_TYPE_SENSOR as ENTITY_TYPE, ATTR_SENSOR_UNIQUE_ID, ATTR_SENSOR_UOM, - DATA_DEVICES, DOMAIN, ) from .entity import MobileAppEntity, unique_id @@ -49,7 +50,7 @@ async def async_setup_entry( for entry in entries: if entry.domain != ENTITY_TYPE or entry.disabled_by: continue - config = { + config: dict[str, Any] = { ATTR_SENSOR_ATTRIBUTES: {}, ATTR_SENSOR_DEVICE_CLASS: entry.device_class or entry.original_device_class, ATTR_SENSOR_ICON: entry.original_icon, @@ -60,7 +61,7 @@ async def async_setup_entry( ATTR_SENSOR_UOM: entry.unit_of_measurement, ATTR_SENSOR_ENTITY_CATEGORY: entry.entity_category, } - entities.append(MobileAppSensor(config, entry.device_id, config_entry)) + entities.append(MobileAppSensor(config, config_entry)) async_add_entities(entities) @@ -76,9 +77,7 @@ def handle_sensor_registration(data): CONF_NAME ] = f"{config_entry.data[ATTR_DEVICE_NAME]} {data[ATTR_SENSOR_NAME]}" - device = hass.data[DOMAIN][DATA_DEVICES][data[CONF_WEBHOOK_ID]] - - async_add_entities([MobileAppSensor(data, device, config_entry)]) + async_add_entities([MobileAppSensor(data, config_entry)]) async_dispatcher_connect( hass, diff --git a/mypy.ini b/mypy.ini index adb0cb2529711e..3f8386a2a27d4d 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2477,27 +2477,6 @@ ignore_errors = true [mypy-homeassistant.components.minecraft_server.sensor] ignore_errors = true -[mypy-homeassistant.components.mobile_app.binary_sensor] -ignore_errors = true - -[mypy-homeassistant.components.mobile_app.device_action] -ignore_errors = true - -[mypy-homeassistant.components.mobile_app.device_tracker] -ignore_errors = true - -[mypy-homeassistant.components.mobile_app.helpers] -ignore_errors = true - -[mypy-homeassistant.components.mobile_app.http_api] -ignore_errors = true - -[mypy-homeassistant.components.mobile_app.push_notification] -ignore_errors = true - -[mypy-homeassistant.components.mobile_app.sensor] -ignore_errors = true - [mypy-homeassistant.components.netgear] ignore_errors = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index 8d542290458841..ef29562578e735 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -94,13 +94,6 @@ "homeassistant.components.minecraft_server", "homeassistant.components.minecraft_server.helpers", "homeassistant.components.minecraft_server.sensor", - "homeassistant.components.mobile_app.binary_sensor", - "homeassistant.components.mobile_app.device_action", - "homeassistant.components.mobile_app.device_tracker", - "homeassistant.components.mobile_app.helpers", - "homeassistant.components.mobile_app.http_api", - "homeassistant.components.mobile_app.push_notification", - "homeassistant.components.mobile_app.sensor", "homeassistant.components.netgear", "homeassistant.components.netgear.config_flow", "homeassistant.components.netgear.device_tracker", From cfd763db40544c31077b46631bbdd9655581dfe9 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Wed, 23 Feb 2022 10:58:00 -0600 Subject: [PATCH 0995/1098] Refactor Sonos media metadata handling (#66840) Co-authored-by: Paulus Schoutsen --- .coveragerc | 1 + homeassistant/components/sonos/const.py | 3 + homeassistant/components/sonos/media.py | 234 +++++++++++++++++ .../components/sonos/media_player.py | 27 +- homeassistant/components/sonos/speaker.py | 248 ++---------------- tests/components/sonos/conftest.py | 2 + tests/components/sonos/test_speaker.py | 4 +- 7 files changed, 282 insertions(+), 237 deletions(-) create mode 100644 homeassistant/components/sonos/media.py diff --git a/.coveragerc b/.coveragerc index 9a3a52895c3e5c..5ce91028102ddf 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1122,6 +1122,7 @@ omit = homeassistant/components/sonos/favorites.py homeassistant/components/sonos/helpers.py homeassistant/components/sonos/household_coordinator.py + homeassistant/components/sonos/media.py homeassistant/components/sonos/media_browser.py homeassistant/components/sonos/media_player.py homeassistant/components/sonos/speaker.py diff --git a/homeassistant/components/sonos/const.py b/homeassistant/components/sonos/const.py index 2f6ea3c20cbb3d..6c4bdd07b31418 100644 --- a/homeassistant/components/sonos/const.py +++ b/homeassistant/components/sonos/const.py @@ -159,13 +159,16 @@ SONOS_FALLBACK_POLL = "sonos_fallback_poll" SONOS_ALARMS_UPDATED = "sonos_alarms_updated" SONOS_FAVORITES_UPDATED = "sonos_favorites_updated" +SONOS_MEDIA_UPDATED = "sonos_media_updated" SONOS_SPEAKER_ACTIVITY = "sonos_speaker_activity" SONOS_SPEAKER_ADDED = "sonos_speaker_added" SONOS_STATE_UPDATED = "sonos_state_updated" SONOS_REBOOTED = "sonos_rebooted" SONOS_VANISHED = "sonos_vanished" +SOURCE_AIRPLAY = "AirPlay" SOURCE_LINEIN = "Line-in" +SOURCE_SPOTIFY_CONNECT = "Spotify Connect" SOURCE_TV = "TV" AVAILABILITY_CHECK_INTERVAL = datetime.timedelta(minutes=1) diff --git a/homeassistant/components/sonos/media.py b/homeassistant/components/sonos/media.py new file mode 100644 index 00000000000000..85d15680a971ff --- /dev/null +++ b/homeassistant/components/sonos/media.py @@ -0,0 +1,234 @@ +"""Support for media metadata handling.""" +from __future__ import annotations + +import datetime +import logging +from typing import Any + +from soco.core import ( + MUSIC_SRC_AIRPLAY, + MUSIC_SRC_LINE_IN, + MUSIC_SRC_RADIO, + MUSIC_SRC_SPOTIFY_CONNECT, + MUSIC_SRC_TV, + SoCo, +) +from soco.data_structures import DidlAudioBroadcast, DidlPlaylistContainer +from soco.music_library import MusicLibrary + +from homeassistant.core import HomeAssistant +from homeassistant.helpers.config_validation import time_period_str +from homeassistant.helpers.dispatcher import dispatcher_send +from homeassistant.util import dt as dt_util + +from .const import ( + SONOS_MEDIA_UPDATED, + SONOS_STATE_PLAYING, + SONOS_STATE_TRANSITIONING, + SOURCE_AIRPLAY, + SOURCE_LINEIN, + SOURCE_SPOTIFY_CONNECT, + SOURCE_TV, +) +from .helpers import soco_error + +LINEIN_SOURCES = (MUSIC_SRC_TV, MUSIC_SRC_LINE_IN) +SOURCE_MAPPING = { + MUSIC_SRC_AIRPLAY: SOURCE_AIRPLAY, + MUSIC_SRC_TV: SOURCE_TV, + MUSIC_SRC_LINE_IN: SOURCE_LINEIN, + MUSIC_SRC_SPOTIFY_CONNECT: SOURCE_SPOTIFY_CONNECT, +} +UNAVAILABLE_VALUES = {"", "NOT_IMPLEMENTED", None} +DURATION_SECONDS = "duration_in_s" +POSITION_SECONDS = "position_in_s" + +_LOGGER = logging.getLogger(__name__) + + +def _timespan_secs(timespan: str | None) -> None | float: + """Parse a time-span into number of seconds.""" + if timespan in UNAVAILABLE_VALUES: + return None + return time_period_str(timespan).total_seconds() # type: ignore[arg-type] + + +class SonosMedia: + """Representation of the current Sonos media.""" + + def __init__(self, hass: HomeAssistant, soco: SoCo) -> None: + """Initialize a SonosMedia.""" + self.hass = hass + self.soco = soco + self.play_mode: str | None = None + self.playback_status: str | None = None + + # This block is reset with clear() + self.album_name: str | None = None + self.artist: str | None = None + self.channel: str | None = None + self.duration: float | None = None + self.image_url: str | None = None + self.queue_position: int | None = None + self.queue_size: int | None = None + self.playlist_name: str | None = None + self.source_name: str | None = None + self.title: str | None = None + self.uri: str | None = None + + self.position: float | None = None + self.position_updated_at: datetime.datetime | None = None + + def clear(self) -> None: + """Clear basic media info.""" + self.album_name = None + self.artist = None + self.channel = None + self.duration = None + self.image_url = None + self.playlist_name = None + self.queue_position = None + self.queue_size = None + self.source_name = None + self.title = None + self.uri = None + + def clear_position(self) -> None: + """Clear the position attributes.""" + self.position = None + self.position_updated_at = None + + @property + def library(self) -> MusicLibrary: + """Return the soco MusicLibrary instance.""" + return self.soco.music_library + + @soco_error() + def poll_track_info(self) -> dict[str, Any]: + """Poll the speaker for current track info, add converted position values, and return.""" + track_info = self.soco.get_current_track_info() + track_info[DURATION_SECONDS] = _timespan_secs(track_info.get("duration")) + track_info[POSITION_SECONDS] = _timespan_secs(track_info.get("position")) + return track_info + + def write_media_player_states(self) -> None: + """Send a signal to media player(s) to write new states.""" + dispatcher_send(self.hass, SONOS_MEDIA_UPDATED, self.soco.uid) + + def set_basic_track_info(self, update_position: bool = False) -> None: + """Query the speaker to update media metadata and position info.""" + self.clear() + + track_info = self.poll_track_info() + self.uri = track_info["uri"] + + audio_source = self.soco.music_source_from_uri(self.uri) + if source := SOURCE_MAPPING.get(audio_source): + self.source_name = source + if audio_source in LINEIN_SOURCES: + self.clear_position() + self.title = source + return + + self.artist = track_info.get("artist") + self.album_name = track_info.get("album") + self.title = track_info.get("title") + self.image_url = track_info.get("album_art") + + playlist_position = int(track_info.get("playlist_position")) + if playlist_position > 0: + self.queue_position = playlist_position + + self.update_media_position(track_info, force_update=update_position) + + def update_media_from_event(self, evars: dict[str, Any]) -> None: + """Update information about currently playing media using an event payload.""" + new_status = evars["transport_state"] + state_changed = new_status != self.playback_status + + self.play_mode = evars["current_play_mode"] + self.playback_status = new_status + + track_uri = evars["enqueued_transport_uri"] or evars["current_track_uri"] + audio_source = self.soco.music_source_from_uri(track_uri) + + self.set_basic_track_info(update_position=state_changed) + + if ct_md := evars["current_track_meta_data"]: + if not self.image_url: + if album_art_uri := getattr(ct_md, "album_art_uri", None): + self.image_url = self.library.build_album_art_full_uri( + album_art_uri + ) + + et_uri_md = evars["enqueued_transport_uri_meta_data"] + if isinstance(et_uri_md, DidlPlaylistContainer): + self.playlist_name = et_uri_md.title + + if queue_size := evars.get("number_of_tracks", 0): + self.queue_size = int(queue_size) + + if audio_source == MUSIC_SRC_RADIO: + self.channel = et_uri_md.title + + if ct_md and ct_md.radio_show: + radio_show = ct_md.radio_show.split(",")[0] + self.channel = " • ".join(filter(None, [self.channel, radio_show])) + + if isinstance(et_uri_md, DidlAudioBroadcast): + self.title = self.title or self.channel + + self.write_media_player_states() + + @soco_error() + def poll_media(self) -> None: + """Poll information about currently playing media.""" + transport_info = self.soco.get_current_transport_info() + new_status = transport_info["current_transport_state"] + + if new_status == SONOS_STATE_TRANSITIONING: + return + + update_position = new_status != self.playback_status + self.playback_status = new_status + self.play_mode = self.soco.play_mode + + self.set_basic_track_info(update_position=update_position) + + self.write_media_player_states() + + def update_media_position( + self, position_info: dict[str, int], force_update: bool = False + ) -> None: + """Update state when playing music tracks.""" + if (duration := position_info.get(DURATION_SECONDS)) == 0: + self.clear_position() + return + + should_update = force_update + self.duration = duration + current_position = position_info.get(POSITION_SECONDS) + + # player started reporting position? + if current_position is not None and self.position is None: + should_update = True + + # position jumped? + if current_position is not None and self.position is not None: + if self.playback_status == SONOS_STATE_PLAYING: + assert self.position_updated_at is not None + time_delta = dt_util.utcnow() - self.position_updated_at + time_diff = time_delta.total_seconds() + else: + time_diff = 0 + + calculated_position = self.position + time_diff + + if abs(calculated_position - current_position) > 1.5: + should_update = True + + if current_position is None: + self.clear_position() + elif should_update: + self.position = current_position + self.position_updated_at = dt_util.utcnow() diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 7319139d3c2e33..65e8c4111ae045 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -23,6 +23,7 @@ async_process_play_media_url, ) from homeassistant.components.media_player.const import ( + ATTR_INPUT_SOURCE, ATTR_MEDIA_ENQUEUE, MEDIA_TYPE_ALBUM, MEDIA_TYPE_ARTIST, @@ -65,6 +66,7 @@ MEDIA_TYPES_TO_SONOS, PLAYABLE_MEDIA_TYPES, SONOS_CREATE_MEDIA_PLAYER, + SONOS_MEDIA_UPDATED, SONOS_STATE_PLAYING, SONOS_STATE_TRANSITIONING, SOURCE_LINEIN, @@ -255,6 +257,23 @@ def __init__(self, speaker: SonosSpeaker) -> None: self._attr_unique_id = self.soco.uid self._attr_name = self.speaker.zone_name + async def async_added_to_hass(self) -> None: + """Handle common setup when added to hass.""" + await super().async_added_to_hass() + self.async_on_remove( + async_dispatcher_connect( + self.hass, + SONOS_MEDIA_UPDATED, + self.async_write_media_state, + ) + ) + + @callback + def async_write_media_state(self, uid: str) -> None: + """Write media state if the provided UID is coordinator of this speaker.""" + if self.coordinator.uid == uid: + self.async_write_ha_state() + @property def coordinator(self) -> SonosSpeaker: """Return the current coordinator SonosSpeaker.""" @@ -295,7 +314,7 @@ def _update(self) -> None: self.speaker.update_groups() self.speaker.update_volume() if self.speaker.is_coordinator: - self.speaker.update_media() + self.media.poll_media() @property def volume_level(self) -> float | None: @@ -660,6 +679,12 @@ def extra_state_attributes(self) -> dict[str, Any]: if self.media.queue_position is not None: attributes[ATTR_QUEUE_POSITION] = self.media.queue_position + if self.media.queue_size: + attributes["queue_size"] = self.media.queue_size + + if self.source: + attributes[ATTR_INPUT_SOURCE] = self.source + return attributes async def async_get_browse_image( diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index 018eab4ca04bbe..3a2bac516841cb 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -9,15 +9,12 @@ import logging import time from typing import Any -import urllib.parse import async_timeout import defusedxml.ElementTree as ET -from soco.core import MUSIC_SRC_LINE_IN, MUSIC_SRC_RADIO, MUSIC_SRC_TV, SoCo -from soco.data_structures import DidlAudioBroadcast, DidlPlaylistContainer +from soco.core import SoCo from soco.events_base import Event as SonosEvent, SubscriptionBase from soco.exceptions import SoCoException, SoCoUPnPException -from soco.music_library import MusicLibrary from soco.plugins.plex import PlexPlugin from soco.plugins.sharelink import ShareLinkPlugin from soco.snapshot import Snapshot @@ -58,12 +55,11 @@ SONOS_STATE_TRANSITIONING, SONOS_STATE_UPDATED, SONOS_VANISHED, - SOURCE_LINEIN, - SOURCE_TV, SUBSCRIPTION_TIMEOUT, ) from .favorites import SonosFavorites from .helpers import soco_error +from .media import SonosMedia from .statistics import ActivityStatistics, EventStatistics NEVER_TIME = -1200.0 @@ -80,7 +76,6 @@ "zoneGroupTopology", ] SUPPORTED_VANISH_REASONS = ("sleeping", "upgrade") -UNAVAILABLE_VALUES = {"", "NOT_IMPLEMENTED", None} UNUSED_DEVICE_KEYS = ["SPID", "TargetRoomName"] @@ -97,57 +92,6 @@ def fetch_battery_info_or_none(soco: SoCo) -> dict[str, Any] | None: return soco.get_battery_info() -def _timespan_secs(timespan: str | None) -> None | float: - """Parse a time-span into number of seconds.""" - if timespan in UNAVAILABLE_VALUES: - return None - - assert timespan is not None - return sum(60 ** x[0] * int(x[1]) for x in enumerate(reversed(timespan.split(":")))) - - -class SonosMedia: - """Representation of the current Sonos media.""" - - def __init__(self, soco: SoCo) -> None: - """Initialize a SonosMedia.""" - self.library = MusicLibrary(soco) - self.play_mode: str | None = None - self.playback_status: str | None = None - - self.album_name: str | None = None - self.artist: str | None = None - self.channel: str | None = None - self.duration: float | None = None - self.image_url: str | None = None - self.queue_position: int | None = None - self.playlist_name: str | None = None - self.source_name: str | None = None - self.title: str | None = None - self.uri: str | None = None - - self.position: float | None = None - self.position_updated_at: datetime.datetime | None = None - - def clear(self) -> None: - """Clear basic media info.""" - self.album_name = None - self.artist = None - self.channel = None - self.duration = None - self.image_url = None - self.playlist_name = None - self.queue_position = None - self.source_name = None - self.title = None - self.uri = None - - def clear_position(self) -> None: - """Clear the position attributes.""" - self.position = None - self.position_updated_at = None - - class SonosSpeaker: """Representation of a Sonos speaker.""" @@ -158,7 +102,7 @@ def __init__( self.hass = hass self.soco = soco self.household_id: str = soco.household_id - self.media = SonosMedia(soco) + self.media = SonosMedia(hass, soco) self._plex_plugin: PlexPlugin | None = None self._share_link_plugin: ShareLinkPlugin | None = None self.available = True @@ -512,7 +456,18 @@ def async_dispatch_media_update(self, event: SonosEvent) -> None: if crossfade := event.variables.get("current_crossfade_mode"): self.cross_fade = bool(int(crossfade)) - self.hass.async_add_executor_job(self.update_media, event) + # Missing transport_state indicates a transient error + if (new_status := event.variables.get("transport_state")) is None: + return + + # Ignore transitions, we should get the target state soon + if new_status == SONOS_STATE_TRANSITIONING: + return + + self.event_stats.process(event) + self.hass.async_add_executor_job( + self.media.update_media_from_event, event.variables + ) @callback def async_update_volume(self, event: SonosEvent) -> None: @@ -1064,176 +1019,3 @@ def update_volume(self) -> None: """Update information about current volume settings.""" self.volume = self.soco.volume self.muted = self.soco.mute - - @soco_error() - def update_media(self, event: SonosEvent | None = None) -> None: - """Update information about currently playing media.""" - variables = event.variables if event else {} - - if "transport_state" in variables: - # If the transport has an error then transport_state will - # not be set - new_status = variables["transport_state"] - else: - transport_info = self.soco.get_current_transport_info() - new_status = transport_info["current_transport_state"] - - # Ignore transitions, we should get the target state soon - if new_status == SONOS_STATE_TRANSITIONING: - return - - if event: - self.event_stats.process(event) - - self.media.clear() - update_position = new_status != self.media.playback_status - self.media.playback_status = new_status - - if "transport_state" in variables: - self.media.play_mode = variables["current_play_mode"] - track_uri = ( - variables["enqueued_transport_uri"] or variables["current_track_uri"] - ) - music_source = self.soco.music_source_from_uri(track_uri) - if uri_meta_data := variables.get("enqueued_transport_uri_meta_data"): - if isinstance(uri_meta_data, DidlPlaylistContainer): - self.media.playlist_name = uri_meta_data.title - else: - self.media.play_mode = self.soco.play_mode - music_source = self.soco.music_source - - if music_source == MUSIC_SRC_TV: - self.update_media_linein(SOURCE_TV) - elif music_source == MUSIC_SRC_LINE_IN: - self.update_media_linein(SOURCE_LINEIN) - else: - track_info = self.soco.get_current_track_info() - if not track_info["uri"]: - self.media.clear_position() - else: - self.media.uri = track_info["uri"] - self.media.artist = track_info.get("artist") - self.media.album_name = track_info.get("album") - self.media.title = track_info.get("title") - - if music_source == MUSIC_SRC_RADIO: - self.update_media_radio(variables) - else: - self.update_media_music(track_info) - self.update_media_position(update_position, track_info) - - self.write_entity_states() - - # Also update slaves - speakers = self.hass.data[DATA_SONOS].discovered.values() - for speaker in speakers: - if speaker.coordinator == self: - speaker.write_entity_states() - - def update_media_linein(self, source: str) -> None: - """Update state when playing from line-in/tv.""" - self.media.clear_position() - - self.media.title = source - self.media.source_name = source - - def update_media_radio(self, variables: dict) -> None: - """Update state when streaming radio.""" - self.media.clear_position() - radio_title = None - - if current_track_metadata := variables.get("current_track_meta_data"): - if album_art_uri := getattr(current_track_metadata, "album_art_uri", None): - self.media.image_url = self.media.library.build_album_art_full_uri( - album_art_uri - ) - if not self.media.artist: - self.media.artist = getattr(current_track_metadata, "creator", None) - - # A missing artist implies metadata is incomplete, try a different method - if not self.media.artist: - radio_show = None - stream_content = None - if current_track_metadata.radio_show: - radio_show = current_track_metadata.radio_show.split(",")[0] - if not current_track_metadata.stream_content.startswith( - ("ZPSTR_", "TYPE=") - ): - stream_content = current_track_metadata.stream_content - radio_title = " • ".join(filter(None, [radio_show, stream_content])) - - if radio_title: - # Prefer the radio title created above - self.media.title = radio_title - elif uri_meta_data := variables.get("enqueued_transport_uri_meta_data"): - if isinstance(uri_meta_data, DidlAudioBroadcast) and ( - self.soco.music_source_from_uri(self.media.title) == MUSIC_SRC_RADIO - or ( - isinstance(self.media.title, str) - and isinstance(self.media.uri, str) - and ( - self.media.title in self.media.uri - or self.media.title in urllib.parse.unquote(self.media.uri) - ) - ) - ): - # Fall back to the radio channel name as a last resort - self.media.title = uri_meta_data.title - - media_info = self.soco.get_current_media_info() - self.media.channel = media_info["channel"] - - # Check if currently playing radio station is in favorites - fav = next( - ( - fav - for fav in self.favorites - if fav.reference.get_uri() == media_info["uri"] - ), - None, - ) - if fav: - self.media.source_name = fav.title - - def update_media_music(self, track_info: dict) -> None: - """Update state when playing music tracks.""" - self.media.image_url = track_info.get("album_art") - - playlist_position = int(track_info.get("playlist_position")) # type: ignore - if playlist_position > 0: - self.media.queue_position = playlist_position - 1 - - def update_media_position( - self, update_media_position: bool, track_info: dict - ) -> None: - """Update state when playing music tracks.""" - self.media.duration = _timespan_secs(track_info.get("duration")) - current_position = _timespan_secs(track_info.get("position")) - - if self.media.duration == 0: - self.media.clear_position() - return - - # player started reporting position? - if current_position is not None and self.media.position is None: - update_media_position = True - - # position jumped? - if current_position is not None and self.media.position is not None: - if self.media.playback_status == SONOS_STATE_PLAYING: - assert self.media.position_updated_at is not None - time_delta = dt_util.utcnow() - self.media.position_updated_at - time_diff = time_delta.total_seconds() - else: - time_diff = 0 - - calculated_position = self.media.position + time_diff - - if abs(calculated_position - current_position) > 1.5: - update_media_position = True - - if current_position is None: - self.media.clear_position() - elif update_media_position: - self.media.position = current_position - self.media.position_updated_at = dt_util.utcnow() diff --git a/tests/components/sonos/conftest.py b/tests/components/sonos/conftest.py index 70061e88692564..8e133f76ac1c3f 100644 --- a/tests/components/sonos/conftest.py +++ b/tests/components/sonos/conftest.py @@ -284,9 +284,11 @@ def no_media_event_fixture(soco): "current_crossfade_mode": "0", "current_play_mode": "NORMAL", "current_section": "0", + "current_track_meta_data": "", "current_track_uri": "", "enqueued_transport_uri": "", "enqueued_transport_uri_meta_data": "", + "number_of_tracks": "0", "transport_state": "STOPPED", } return SonosMockEvent(soco, soco.avTransport, variables) diff --git a/tests/components/sonos/test_speaker.py b/tests/components/sonos/test_speaker.py index cb53fb43ed2524..96b3d222dc61ec 100644 --- a/tests/components/sonos/test_speaker.py +++ b/tests/components/sonos/test_speaker.py @@ -19,9 +19,7 @@ async def test_fallback_to_polling( caplog.clear() # Ensure subscriptions are cancelled and polling methods are called when subscriptions time out - with patch( - "homeassistant.components.sonos.speaker.SonosSpeaker.update_media" - ), patch( + with patch("homeassistant.components.sonos.media.SonosMedia.poll_media"), patch( "homeassistant.components.sonos.speaker.SonosSpeaker.subscription_address" ): async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) From 87593fa3ec4edd1fb467ed0709ef57c3c41e0fc4 Mon Sep 17 00:00:00 2001 From: Francois Chagnon Date: Wed, 23 Feb 2022 12:01:45 -0500 Subject: [PATCH 0996/1098] Add Humidifier support to zwave_js (#65847) --- .../components/zwave_js/discovery.py | 13 + .../components/zwave_js/humidifier.py | 217 + tests/components/zwave_js/common.py | 2 + tests/components/zwave_js/conftest.py | 46 + .../fixtures/climate_adc_t3000_state.json | 4120 +++++++++++++++++ tests/components/zwave_js/test_humidifier.py | 1056 +++++ 6 files changed, 5454 insertions(+) create mode 100644 homeassistant/components/zwave_js/humidifier.py create mode 100644 tests/components/zwave_js/fixtures/climate_adc_t3000_state.json create mode 100644 tests/components/zwave_js/test_humidifier.py diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 3692e50d595a12..32c54c2b2903a0 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -16,6 +16,9 @@ from zwave_js_server.const.command_class.barrier_operator import ( SIGNALING_STATE_PROPERTY, ) +from zwave_js_server.const.command_class.humidity_control import ( + HUMIDITY_CONTROL_MODE_PROPERTY, +) from zwave_js_server.const.command_class.lock import ( CURRENT_MODE_PROPERTY, DOOR_STATUS_PROPERTY, @@ -492,6 +495,16 @@ def get_config_parameter_discovery_schema( type={"any"}, ), ), + # humidifier + # hygrostats supporting mode (and optional setpoint) + ZWaveDiscoverySchema( + platform="humidifier", + primary_value=ZWaveValueDiscoverySchema( + command_class={CommandClass.HUMIDITY_CONTROL_MODE}, + property={HUMIDITY_CONTROL_MODE_PROPERTY}, + type={"number"}, + ), + ), # climate # thermostats supporting mode (and optional setpoint) ZWaveDiscoverySchema( diff --git a/homeassistant/components/zwave_js/humidifier.py b/homeassistant/components/zwave_js/humidifier.py new file mode 100644 index 00000000000000..b94c7a8e2a3622 --- /dev/null +++ b/homeassistant/components/zwave_js/humidifier.py @@ -0,0 +1,217 @@ +"""Representation of Z-Wave humidifiers.""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from zwave_js_server.client import Client as ZwaveClient +from zwave_js_server.const import CommandClass +from zwave_js_server.const.command_class.humidity_control import ( + HUMIDITY_CONTROL_SETPOINT_PROPERTY, + HumidityControlMode, + HumidityControlSetpointType, +) +from zwave_js_server.model.value import Value as ZwaveValue + +from homeassistant.components.humidifier import ( + HumidifierDeviceClass, + HumidifierEntity, + HumidifierEntityDescription, +) +from homeassistant.components.humidifier.const import ( + DEFAULT_MAX_HUMIDITY, + DEFAULT_MIN_HUMIDITY, + DOMAIN as HUMIDIFIER_DOMAIN, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DATA_CLIENT, DOMAIN +from .discovery import ZwaveDiscoveryInfo +from .entity import ZWaveBaseEntity + + +@dataclass +class ZwaveHumidifierEntityDescriptionRequiredKeys: + """A class for humidifier entity description required keys.""" + + # The "on" control mode for this entity, e.g. HUMIDIFY for humidifier + on_mode: HumidityControlMode + + # The "on" control mode for the inverse entity, e.g. DEHUMIDIFY for humidifier + inverse_mode: HumidityControlMode + + # The setpoint type controlled by this entity + setpoint_type: HumidityControlSetpointType + + +@dataclass +class ZwaveHumidifierEntityDescription( + HumidifierEntityDescription, ZwaveHumidifierEntityDescriptionRequiredKeys +): + """A class that describes the humidifier or dehumidifier entity.""" + + +HUMIDIFIER_ENTITY_DESCRIPTION = ZwaveHumidifierEntityDescription( + key="humidifier", + device_class=HumidifierDeviceClass.HUMIDIFIER, + on_mode=HumidityControlMode.HUMIDIFY, + inverse_mode=HumidityControlMode.DEHUMIDIFY, + setpoint_type=HumidityControlSetpointType.HUMIDIFIER, +) + + +DEHUMIDIFIER_ENTITY_DESCRIPTION = ZwaveHumidifierEntityDescription( + key="dehumidifier", + device_class=HumidifierDeviceClass.DEHUMIDIFIER, + on_mode=HumidityControlMode.DEHUMIDIFY, + inverse_mode=HumidityControlMode.HUMIDIFY, + setpoint_type=HumidityControlSetpointType.DEHUMIDIFIER, +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Z-Wave humidifier from config entry.""" + client: ZwaveClient = hass.data[DOMAIN][config_entry.entry_id][DATA_CLIENT] + + @callback + def async_add_humidifier(info: ZwaveDiscoveryInfo) -> None: + """Add Z-Wave Humidifier.""" + entities: list[ZWaveBaseEntity] = [] + + if ( + str(HumidityControlMode.HUMIDIFY.value) + in info.primary_value.metadata.states + ): + entities.append( + ZWaveHumidifier( + config_entry, client, info, HUMIDIFIER_ENTITY_DESCRIPTION + ) + ) + + if ( + str(HumidityControlMode.DEHUMIDIFY.value) + in info.primary_value.metadata.states + ): + entities.append( + ZWaveHumidifier( + config_entry, client, info, DEHUMIDIFIER_ENTITY_DESCRIPTION + ) + ) + + async_add_entities(entities) + + config_entry.async_on_unload( + async_dispatcher_connect( + hass, + f"{DOMAIN}_{config_entry.entry_id}_add_{HUMIDIFIER_DOMAIN}", + async_add_humidifier, + ) + ) + + +class ZWaveHumidifier(ZWaveBaseEntity, HumidifierEntity): + """Representation of a Z-Wave Humidifier or Dehumidifier.""" + + entity_description: ZwaveHumidifierEntityDescription + _current_mode: ZwaveValue + _setpoint: ZwaveValue | None = None + + def __init__( + self, + config_entry: ConfigEntry, + client: ZwaveClient, + info: ZwaveDiscoveryInfo, + description: ZwaveHumidifierEntityDescription, + ) -> None: + """Initialize humidifier.""" + super().__init__(config_entry, client, info) + + self.entity_description = description + + self._attr_name = f"{self._attr_name} {description.key}" + self._attr_unique_id = f"{self._attr_unique_id}_{description.key}" + + self._current_mode = self.info.primary_value + + self._setpoint = self.get_zwave_value( + HUMIDITY_CONTROL_SETPOINT_PROPERTY, + command_class=CommandClass.HUMIDITY_CONTROL_SETPOINT, + value_property_key=description.setpoint_type, + add_to_watched_value_ids=True, + ) + + @property + def is_on(self) -> bool | None: + """Return True if entity is on.""" + return int(self._current_mode.value) in [ + self.entity_description.on_mode, + HumidityControlMode.AUTO, + ] + + def _supports_inverse_mode(self) -> bool: + return ( + str(self.entity_description.inverse_mode.value) + in self._current_mode.metadata.states + ) + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn on device.""" + mode = int(self._current_mode.value) + if mode == HumidityControlMode.OFF: + new_mode = self.entity_description.on_mode + elif mode == self.entity_description.inverse_mode: + new_mode = HumidityControlMode.AUTO + else: + return + + await self.info.node.async_set_value(self._current_mode, new_mode) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn off device.""" + mode = int(self._current_mode.value) + if mode == HumidityControlMode.AUTO: + if self._supports_inverse_mode(): + new_mode = self.entity_description.inverse_mode + else: + new_mode = HumidityControlMode.OFF + elif mode == self.entity_description.on_mode: + new_mode = HumidityControlMode.OFF + else: + return + + await self.info.node.async_set_value(self._current_mode, new_mode) + + @property + def target_humidity(self) -> int | None: + """Return the humidity we try to reach.""" + if not self._setpoint: + return None + return int(self._setpoint.value) + + async def async_set_humidity(self, humidity: int) -> None: + """Set new target humidity.""" + if self._setpoint: + await self.info.node.async_set_value(self._setpoint, humidity) + + @property + def min_humidity(self) -> int: + """Return the minimum humidity.""" + min_value = DEFAULT_MIN_HUMIDITY + if self._setpoint and self._setpoint.metadata.min: + min_value = self._setpoint.metadata.min + return min_value + + @property + def max_humidity(self) -> int: + """Return the maximum humidity.""" + max_value = DEFAULT_MAX_HUMIDITY + if self._setpoint and self._setpoint.metadata.max: + max_value = self._setpoint.metadata.max + return max_value diff --git a/tests/components/zwave_js/common.py b/tests/components/zwave_js/common.py index d73daacdc75851..aea895d03bb139 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -37,5 +37,7 @@ ZEN_31_ENTITY = "light.kitchen_under_cabinet_lights" METER_ENERGY_SENSOR = "sensor.smart_switch_6_electric_consumed_kwh" METER_VOLTAGE_SENSOR = "sensor.smart_switch_6_electric_consumed_v" +HUMIDIFIER_ADC_T3000_ENTITY = "humidifier.adc_t3000_humidifier" +DEHUMIDIFIER_ADC_T3000_ENTITY = "humidifier.adc_t3000_dehumidifier" PROPERTY_ULTRAVIOLET = "Ultraviolet" diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index d8fef11269ccc5..318dc99a1df6e9 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -276,6 +276,12 @@ def climate_radio_thermostat_ct100_plus_different_endpoints_state_fixture(): ) +@pytest.fixture(name="climate_adc_t3000_state", scope="session") +def climate_adc_t3000_state_fixture(): + """Load the climate ADC-T3000 node state fixture data.""" + return json.loads(load_fixture("zwave_js/climate_adc_t3000_state.json")) + + @pytest.fixture(name="climate_danfoss_lc_13_state", scope="session") def climate_danfoss_lc_13_state_fixture(): """Load the climate Danfoss (LC-13) electronic radiator thermostat node state fixture data.""" @@ -610,6 +616,46 @@ def climate_radio_thermostat_ct100_plus_different_endpoints_fixture( return node +@pytest.fixture(name="climate_adc_t3000") +def climate_adc_t3000_fixture(client, climate_adc_t3000_state): + """Mock a climate ADC-T3000 node.""" + node = Node(client, copy.deepcopy(climate_adc_t3000_state)) + client.driver.controller.nodes[node.node_id] = node + return node + + +@pytest.fixture(name="climate_adc_t3000_missing_setpoint") +def climate_adc_t3000_missing_setpoint_fixture(client, climate_adc_t3000_state): + """Mock a climate ADC-T3000 node with missing de-humidify setpoint.""" + data = copy.deepcopy(climate_adc_t3000_state) + data["name"] = f"{data['name']} missing setpoint" + for value in data["values"][:]: + if ( + value["commandClassName"] == "Humidity Control Setpoint" + and value["propertyKeyName"] == "De-humidifier" + ): + data["values"].remove(value) + node = Node(client, data) + client.driver.controller.nodes[node.node_id] = node + return node + + +@pytest.fixture(name="climate_adc_t3000_missing_mode") +def climate_adc_t3000_missing_mode_fixture(client, climate_adc_t3000_state): + """Mock a climate ADC-T3000 node with missing mode setpoint.""" + data = copy.deepcopy(climate_adc_t3000_state) + data["name"] = f"{data['name']} missing mode" + for value in data["values"]: + if value["commandClassName"] == "Humidity Control Mode": + states = value["metadata"]["states"] + for key in list(states.keys()): + if states[key] == "De-humidify": + del states[key] + node = Node(client, data) + client.driver.controller.nodes[node.node_id] = node + return node + + @pytest.fixture(name="climate_danfoss_lc_13") def climate_danfoss_lc_13_fixture(client, climate_danfoss_lc_13_state): """Mock a climate radio danfoss LC-13 node.""" diff --git a/tests/components/zwave_js/fixtures/climate_adc_t3000_state.json b/tests/components/zwave_js/fixtures/climate_adc_t3000_state.json new file mode 100644 index 00000000000000..ba55aadd98c8dc --- /dev/null +++ b/tests/components/zwave_js/fixtures/climate_adc_t3000_state.json @@ -0,0 +1,4120 @@ +{ + "nodeId": 68, + "index": 0, + "installerIcon": 4608, + "userIcon": 4608, + "status": 4, + "ready": true, + "isListening": false, + "isRouting": true, + "isSecure": true, + "manufacturerId": 400, + "productId": 1, + "productType": 6, + "firmwareVersion": "1.44", + "zwavePlusVersion": 1, + "name": "ADC-T3000", + "deviceConfig": { + "filename": "/data/store/config/adc-t3000.json", + "isEmbedded": false, + "manufacturer": "Building 36 Technologies", + "manufacturerId": 400, + "label": "ADC-T 3000", + "description": "Alarm.com Smart Thermostat", + "devices": [ + { + "productType": 6, + "productId": 1 + } + ], + "firmwareVersion": { + "min": "0.0", + "max": "255.255" + }, + "paramInformation": { + "_map": {} + } + }, + "label": "ADC-T 3000", + "interviewAttempts": 0, + "endpoints": [ + { + "nodeId": 68, + "index": 0, + "installerIcon": 4608, + "userIcon": 4608, + "deviceClass": { + "basic": { + "key": 4, + "label": "Routing Slave" + }, + "generic": { + "key": 8, + "label": "Thermostat" + }, + "specific": { + "key": 6, + "label": "General Thermostat V2" + }, + "mandatorySupportedCCs": [ + 32, + 114, + 64, + 67, + 134 + ], + "mandatoryControlledCCs": [] + } + } + ], + "values": [ + { + "endpoint": 0, + "commandClass": 49, + "commandClassName": "Multilevel Sensor", + "property": "Air temperature", + "propertyName": "Air temperature", + "ccVersion": 11, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Air temperature", + "ccSpecific": { + "sensorType": 1, + "scale": 1 + }, + "unit": "\u00b0F" + }, + "value": 72, + "nodeId": 68, + "newValue": 73, + "prevValue": 72.5 + }, + { + "endpoint": 0, + "commandClass": 49, + "commandClassName": "Multilevel Sensor", + "property": "Humidity", + "propertyName": "Humidity", + "ccVersion": 11, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Humidity", + "ccSpecific": { + "sensorType": 5, + "scale": 0 + }, + "unit": "%" + }, + "value": 34, + "nodeId": 68, + "newValue": 34, + "prevValue": 34 + }, + { + "endpoint": 0, + "commandClass": 49, + "commandClassName": "Multilevel Sensor", + "property": "Voltage", + "propertyName": "Voltage", + "ccVersion": 11, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Voltage", + "ccSpecific": { + "sensorType": 15, + "scale": 0 + }, + "unit": "V" + }, + "value": 3.034 + }, + { + "endpoint": 0, + "commandClass": 64, + "commandClassName": "Thermostat Mode", + "property": "mode", + "propertyName": "mode", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Thermostat mode", + "min": 0, + "max": 255, + "states": { + "0": "Off", + "1": "Heat", + "2": "Cool", + "3": "Auto" + } + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 64, + "commandClassName": "Thermostat Mode", + "property": "manufacturerData", + "propertyName": "manufacturerData", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + } + }, + { + "endpoint": 0, + "commandClass": 66, + "commandClassName": "Thermostat Operating State", + "property": "state", + "propertyName": "state", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Operating state", + "min": 0, + "max": 255, + "states": { + "0": "Idle", + "1": "Heating", + "2": "Cooling", + "3": "Fan Only", + "4": "Pending Heat", + "5": "Pending Cool", + "6": "Vent/Economizer", + "7": "Aux Heating", + "8": "2nd Stage Heating", + "9": "2nd Stage Cooling", + "10": "2nd Stage Aux Heat", + "11": "3rd Stage Aux Heat" + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 67, + "commandClassName": "Thermostat Setpoint", + "property": "setpoint", + "propertyKey": 1, + "propertyName": "setpoint", + "propertyKeyName": "Heating", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "ccSpecific": { + "setpointType": 1 + }, + "min": 35, + "max": 95, + "unit": "\u00b0F" + }, + "value": 60.8 + }, + { + "endpoint": 0, + "commandClass": 67, + "commandClassName": "Thermostat Setpoint", + "property": "setpoint", + "propertyKey": 2, + "propertyName": "setpoint", + "propertyKeyName": "Cooling", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "ccSpecific": { + "setpointType": 2 + }, + "min": 50, + "max": 95, + "unit": "\u00b0F" + }, + "value": 80 + }, + { + "endpoint": 0, + "commandClass": 68, + "commandClassName": "Thermostat Fan Mode", + "property": "mode", + "propertyName": "mode", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Thermostat fan mode", + "min": 0, + "max": 255, + "states": { + "0": "Auto low", + "1": "Low", + "6": "Circulation" + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 68, + "commandClassName": "Thermostat Fan Mode", + "property": "off", + "propertyName": "off", + "ccVersion": 3, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Thermostat fan turned off" + }, + "value": false + }, + { + "endpoint": 0, + "commandClass": 69, + "commandClassName": "Thermostat Fan State", + "property": "state", + "propertyName": "state", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Thermostat fan state", + "min": 0, + "max": 255, + "states": { + "0": "Idle / off", + "1": "Running / running low", + "2": "Running high", + "3": "Running medium", + "4": "Circulation mode", + "5": "Humidity circulation mode", + "6": "Right - left circulation mode", + "7": "Up - down circulation mode", + "8": "Quiet circulation mode" + } + }, + "value": 0, + "newValue": 1, + "prevValue": 0 + }, + { + "endpoint": 0, + "commandClass": 100, + "commandClassName": "Humidity Control Setpoint", + "property": "setpoint", + "propertyKey": 1, + "propertyName": "setpoint", + "propertyKeyName": "Humidifier", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "ccSpecific": { + "setpointType": 1 + }, + "min": 10, + "max": 70, + "unit": "%" + }, + "value": 35 + }, + { + "endpoint": 0, + "commandClass": 100, + "commandClassName": "Humidity Control Setpoint", + "property": "setpointScale", + "propertyKey": 1, + "propertyName": "setpointScale", + "propertyKeyName": "1", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "states": { + "0": "%" + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 100, + "commandClassName": "Humidity Control Setpoint", + "property": "setpoint", + "propertyKey": 2, + "propertyName": "setpoint", + "propertyKeyName": "De-humidifier", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "ccSpecific": { + "setpointType": 2 + }, + "min": 30, + "max": 90, + "unit": "%" + }, + "value": 60 + }, + { + "endpoint": 0, + "commandClass": 100, + "commandClassName": "Humidity Control Setpoint", + "property": "setpointScale", + "propertyKey": 2, + "propertyName": "setpointScale", + "propertyKeyName": "2", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "states": { + "0": "%" + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 109, + "commandClassName": "Humidity Control Mode", + "property": "mode", + "propertyName": "mode", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Humidity control mode", + "min": 0, + "max": 255, + "states": { + "0": "Off", + "1": "Humidify", + "2": "De-humidify", + "3": "Auto" + } + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 110, + "commandClassName": "Humidity Control Operating State", + "property": "state", + "propertyName": "state", + "ccVersion": 0, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Humidity control operating state", + "min": 0, + "max": 255, + "states": { + "0": "Idle", + "1": "Humidifying", + "2": "De-humidifying" + } + }, + "value": 0, + "newValue": 1, + "prevValue": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 1, + "propertyName": "HVAC System Type", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Configures the type of heating system used.", + "label": "HVAC System Type", + "default": 0, + "min": 0, + "max": 1, + "states": { + "0": "Normal", + "1": "Heat Pump" + }, + "valueSize": 1, + "format": 0, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 2, + "propertyName": "Number of Heat Stages", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Heat Stages 0-3 Default is 2.", + "label": "Number of Heat Stages", + "default": 2, + "min": 0, + "max": 3, + "valueSize": 1, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 3, + "propertyName": "Number of Cool Stages", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Cool Stages 0-2 Default is 2.", + "label": "Number of Cool Stages", + "default": 2, + "min": 0, + "max": 2, + "valueSize": 1, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 4, + "propertyName": "Heat Fuel Type", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Choose type of fuel. Reality - whether unit is boiler vs forced air.", + "label": "Heat Fuel Type", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Fossil Fuel", + "1": "Electric" + }, + "valueSize": 1, + "format": 0, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 5, + "propertyKey": 16776960, + "propertyName": "Calibration Temperature", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Allowable range: -10 to 10 in 1 \u00b0F increments.", + "label": "Calibration Temperature", + "default": 0, + "min": -32768, + "max": 32767, + "states": { + "-1": "Disabled" + }, + "unit": "0.1\u00b0F", + "valueSize": 4, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 6, + "propertyKey": 16776960, + "propertyName": "Swing", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Allowable range: 0 to 3 in 0.5 \u00b0F increments.", + "label": "Swing", + "default": 50, + "min": -32768, + "max": 32767, + "states": { + "-1": "Disabled" + }, + "unit": "0.1\u00b0F", + "valueSize": 4, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 5 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 7, + "propertyKey": 16776960, + "propertyName": "Overshoot", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Allowable range: 0 to 3 in 0.5 \u00b0F increments.", + "label": "Overshoot", + "default": 0, + "min": -32768, + "max": 32767, + "states": { + "-1": "Disabled" + }, + "unit": "0.1\u00b0F", + "valueSize": 4, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 8, + "propertyName": "Heat Staging Delay", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Heat Staging Delay", + "default": 30, + "min": 1, + "max": 60, + "unit": "minutes", + "valueSize": 1, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 30 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 9, + "propertyName": "Cool Staging Delay", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Cool Staging Delay", + "default": 30, + "min": 1, + "max": 60, + "unit": "minutes", + "valueSize": 1, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 30 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 10, + "propertyKey": 16776960, + "propertyName": "Balance Setpoint", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Allowable range: 0 to 95 in 1 \u00b0F increments.", + "label": "Balance Setpoint", + "default": 300, + "min": -32768, + "max": 32767, + "states": { + "-1": "Disabled" + }, + "unit": "0.1\u00b0F", + "valueSize": 4, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 300 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 12, + "propertyName": "Fan Circulation Period", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Fan Circulation Period", + "default": 60, + "min": 10, + "max": 1440, + "unit": "minutes", + "valueSize": 2, + "format": 1, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 60 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 13, + "propertyName": "Fan Circulation Duty Cycle", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Fan Circulation Duty Cycle", + "default": 25, + "min": 0, + "max": 100, + "unit": "%", + "valueSize": 1, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 25 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 14, + "propertyName": "Fan Purge Time", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Fan Purge Time", + "default": 60, + "min": 1, + "max": 3600, + "unit": "seconds", + "valueSize": 2, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 60 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 15, + "propertyKey": 16776960, + "propertyName": "Maximum Heat Setpoint", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Allowable range: 35 to 95 in 1 \u00b0F increments.", + "label": "Maximum Heat Setpoint", + "default": 950, + "min": -32768, + "max": 32767, + "states": { + "-1": "Disabled" + }, + "unit": "0.1\u00b0F", + "valueSize": 4, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 950 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 16, + "propertyKey": 16776960, + "propertyName": "Minimum Heat Setpoint", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Allowable range: 35 to 95 in 1 \u00b0F increments.", + "label": "Minimum Heat Setpoint", + "default": 350, + "min": -32768, + "max": 32767, + "states": { + "-1": "Disabled" + }, + "unit": "0.1\u00b0F", + "valueSize": 4, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 350 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 17, + "propertyKey": 16776960, + "propertyName": "Maximum Cool Setpoint", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Allowable range: 50 to 95 in 1 \u00b0F increments.", + "label": "Maximum Cool Setpoint", + "default": 950, + "min": -32768, + "max": 32767, + "states": { + "-1": "Disabled" + }, + "unit": "0.1\u00b0F", + "valueSize": 4, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 950 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 18, + "propertyKey": 16776960, + "propertyName": "Minimum Cool Setpoint", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Allowable range: 50 to 95 in 1 \u00b0F increments.", + "label": "Minimum Cool Setpoint", + "default": 500, + "min": -32768, + "max": 32767, + "states": { + "-1": "Disabled" + }, + "unit": "0.1\u00b0F", + "valueSize": 4, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 500 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 19, + "propertyName": "Thermostat Lock", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Lock out physical thermostat controls.", + "label": "Thermostat Lock", + "default": 0, + "min": 0, + "max": 2, + "states": { + "0": "Disabled", + "1": "Full Lock", + "2": "Partial Lock" + }, + "valueSize": 1, + "format": 0, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 20, + "propertyName": "Compressor Delay", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Compressor Delay", + "default": 5, + "min": 0, + "max": 60, + "unit": "minutes", + "valueSize": 1, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 5 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 23, + "propertyName": "Temperature Display Units", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Celsius or Farenheit for temperature display.", + "label": "Temperature Display Units", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Celsius", + "1": "Farenheit" + }, + "valueSize": 1, + "format": 0, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 24, + "propertyName": "HVAC Modes Enabled", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Which heating/cooling modes are available.", + "label": "HVAC Modes Enabled", + "default": 15, + "min": 3, + "max": 31, + "states": { + "3": "Off, Heat", + "5": "Off, Cool", + "7": "Off, Heat, Cool", + "15": "Off, Heat, Cool, Auto", + "19": "Off, Heat, Emergency Heat", + "23": "Off, Heat, Cool, Emergency Heat", + "31": "Off, Heat, Cool, Auto, Emergency Heat" + }, + "valueSize": 1, + "format": 0, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 15 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 25, + "propertyKey": 255, + "propertyName": "Configurable Terminal Setting Z2", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Changes control of configurable terminal", + "label": "Configurable Terminal Setting Z2", + "default": 0, + "min": 0, + "max": 4, + "states": { + "0": "None", + "1": "W3, 3rd Stage Auxiliary Heat", + "2": "H, Humidifier", + "3": "DH, Dehumidifier", + "4": "External Air Baffle or Vent" + }, + "valueSize": 2, + "format": 0, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 25, + "propertyKey": 65280, + "propertyName": "Configurable Terminal Setting Z1", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Changes control of configurable terminal", + "label": "Configurable Terminal Setting Z1", + "default": 0, + "min": 0, + "max": 4, + "states": { + "0": "None", + "1": "W3, 3rd Stage Auxiliary Heat", + "2": "H, Humidifier", + "3": "DH, Dehumidifier", + "4": "External Air Baffle or Vent" + }, + "valueSize": 2, + "format": 0, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 2 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 26, + "propertyName": "Power Source", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "description": "Which source of power is utilized.", + "label": "Power Source", + "default": 0, + "min": 0, + "max": 1, + "states": { + "0": "Battery", + "1": "C-Wire" + }, + "valueSize": 1, + "format": 0, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 27, + "propertyName": "Battery Alert Threshold Low", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Battery Alert Range", + "label": "Battery Alert Threshold Low", + "default": 30, + "min": 0, + "max": 100, + "unit": "%", + "valueSize": 1, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 30 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 28, + "propertyName": "Battery Alert Threshold Very Low", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Very Low Battery Alert Range (percentage)", + "label": "Battery Alert Threshold Very Low", + "default": 15, + "min": 0, + "max": 100, + "unit": "%", + "valueSize": 1, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 15 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 2147483648, + "propertyName": "Current Relay State: Z1 Terminal Load", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: Z1 Terminal Load", + "min": 0, + "max": 1, + "states": { + "0": "No Load", + "1": "Load" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1, + "newValue": 1, + "prevValue": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 1073741824, + "propertyName": "Current Relay State: Y2 Terminal Load", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: Y2 Terminal Load", + "min": 0, + "max": 1, + "states": { + "0": "No Load", + "1": "Load" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0, + "newValue": 0, + "prevValue": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 536870912, + "propertyName": "Current Relay State: Y Terminal Load", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: Y Terminal Load", + "min": 0, + "max": 1, + "states": { + "0": "No Load", + "1": "Load" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1, + "newValue": 1, + "prevValue": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 268435456, + "propertyName": "Current Relay State: W2 Terminal Load", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: W2 Terminal Load", + "min": 0, + "max": 1, + "states": { + "0": "No Load", + "1": "Load" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0, + "newValue": 0, + "prevValue": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 134217728, + "propertyName": "Current Relay State: W Terminal Load", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: W Terminal Load", + "min": 0, + "max": 1, + "states": { + "0": "No Load", + "1": "Load" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1, + "newValue": 1, + "prevValue": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 67108864, + "propertyName": "Current Relay State: G Terminal Load", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: G Terminal Load", + "min": 0, + "max": 1, + "states": { + "0": "No Load", + "1": "Load" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1, + "newValue": 1, + "prevValue": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 33554432, + "propertyName": "Current Relay State: O Terminal Load", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: O Terminal Load", + "min": 0, + "max": 1, + "states": { + "0": "No Load", + "1": "Load" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0, + "newValue": 0, + "prevValue": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 8388608, + "propertyName": "Current Relay State: Override Terminal Load", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: Override Terminal Load", + "min": 0, + "max": 1, + "states": { + "0": "No Load", + "1": "Load" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0, + "newValue": 0, + "prevValue": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 1048576, + "propertyName": "Current Relay State: C Terminal Load", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: C Terminal Load", + "min": 0, + "max": 1, + "states": { + "0": "No Load", + "1": "Load" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1, + "newValue": 1, + "prevValue": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 524288, + "propertyName": "Current Relay State: RC Terminal Load", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: RC Terminal Load", + "min": 0, + "max": 1, + "states": { + "0": "No Load", + "1": "Load" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0, + "newValue": 0, + "prevValue": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 262144, + "propertyName": "Current Relay State: RH Terminal Load", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: RH Terminal Load", + "min": 0, + "max": 1, + "states": { + "0": "No Load", + "1": "Load" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1, + "newValue": 1, + "prevValue": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 131072, + "propertyName": "Current Relay State: Z2 Terminal Load", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: Z2 Terminal Load", + "min": 0, + "max": 1, + "states": { + "0": "No Load", + "1": "Load" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0, + "newValue": 0, + "prevValue": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 65536, + "propertyName": "Current Relay State: B Terminal Load", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: B Terminal Load", + "min": 0, + "max": 1, + "states": { + "0": "No Load", + "1": "Load" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0, + "newValue": 0, + "prevValue": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 32768, + "propertyName": "Current Relay State: Z1 Relay State", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: Z1 Relay State", + "min": 0, + "max": 1, + "states": { + "0": "Not closed", + "1": "Closed" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0, + "newValue": 0, + "prevValue": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 16384, + "propertyName": "Current Relay State: Y2 Relay State", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: Y2 Relay State", + "min": 0, + "max": 1, + "states": { + "0": "Not closed", + "1": "Closed" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0, + "newValue": 0, + "prevValue": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 8192, + "propertyName": "Current Relay State: Y Relay State", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: Y Relay State", + "min": 0, + "max": 1, + "states": { + "0": "Not closed", + "1": "Closed" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0, + "newValue": 0, + "prevValue": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 4096, + "propertyName": "Current Relay State: W2 Relay State", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: W2 Relay State", + "min": 0, + "max": 1, + "states": { + "0": "Not closed", + "1": "Closed" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0, + "newValue": 0, + "prevValue": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 2048, + "propertyName": "Current Relay State: W Relay State", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: W Relay State", + "min": 0, + "max": 1, + "states": { + "0": "Not closed", + "1": "Closed" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0, + "newValue": 0, + "prevValue": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 1024, + "propertyName": "Current Relay State: G Relay State", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: G Relay State", + "min": 0, + "max": 1, + "states": { + "0": "Not closed", + "1": "Closed" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0, + "newValue": 0, + "prevValue": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 512, + "propertyName": "Current Relay State: O Relay State", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: O Relay State", + "min": 0, + "max": 1, + "states": { + "0": "Not closed", + "1": "Closed" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0, + "newValue": 0, + "prevValue": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 8, + "propertyName": "Current Relay State: RC Relay State", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: RC Relay State", + "min": 0, + "max": 1, + "states": { + "0": "Not closed", + "1": "Closed" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1, + "newValue": 1, + "prevValue": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 4, + "propertyName": "Current Relay State: RH Relay State", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: RH Relay State", + "min": 0, + "max": 1, + "states": { + "0": "Not closed", + "1": "Closed" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1, + "newValue": 1, + "prevValue": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 2, + "propertyName": "Current Relay State: Z2 Relay State", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: Z2 Relay State", + "min": 0, + "max": 1, + "states": { + "0": "Not closed", + "1": "Closed" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0, + "newValue": 0, + "prevValue": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyKey": 1, + "propertyName": "Current Relay State: B Relay State", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current Relay State: B Relay State", + "min": 0, + "max": 1, + "states": { + "0": "Not closed", + "1": "Closed" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0, + "newValue": 0, + "prevValue": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 30, + "propertyKey": 255, + "propertyName": "Remote Temperature Enable", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Enables remote temperature sensor instead of built-in.", + "label": "Remote Temperature Enable", + "default": 0, + "min": 0, + "max": 4, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 2, + "format": 0, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 30, + "propertyKey": 65280, + "propertyName": "Remote Temperature Status", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "description": "Status of the remote temperature sensor.", + "label": "Remote Temperature Status", + "default": 0, + "min": 0, + "max": 4, + "states": { + "0": "Remote temperature disabled", + "1": "Active and functioning properly", + "2": "Inactive, timeout reached (see parameter 39)", + "3": "Inactive, temperature differential reached (see parameter 40)", + "4": "Inactive, 3 successive communication attempts failed" + }, + "valueSize": 2, + "format": 0, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 31, + "propertyKey": 16776960, + "propertyName": "Heat Differential", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Allowable range: 1 to 10 in 0.5 \u00b0F increments.", + "label": "Heat Differential", + "default": 30, + "min": -32768, + "max": 32767, + "states": { + "-1": "Disabled" + }, + "unit": "0.1\u00b0F", + "valueSize": 4, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 30 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 32, + "propertyKey": 16776960, + "propertyName": "Cool Differential", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Allowable range: 1 to 10 in 0.5 \u00b0F increments.", + "label": "Cool Differential", + "default": 30, + "min": -32768, + "max": 32767, + "states": { + "-1": "Disabled" + }, + "unit": "0.1\u00b0F", + "valueSize": 4, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 30 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 33, + "propertyKey": 16776960, + "propertyName": "Temperature Reporting Threshold", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Allowable range: 0.5 to 2 in 0.5 \u00b0F increments.", + "label": "Temperature Reporting Threshold", + "default": 10, + "min": -32768, + "max": 32767, + "states": { + "-1": "Disabled" + }, + "unit": "0.1\u00b0F", + "valueSize": 4, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 5 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 35, + "propertyName": "Z-Wave Echo Association Reports", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Enable/Disabled Echo Assoc. Reports.", + "label": "Z-Wave Echo Association Reports", + "default": 0, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 1, + "format": 0, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 36, + "propertyKey": 16776960, + "propertyName": "C-Wire Power Thermistor Offset", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Allowable range: -10 to 10 in 0.1 \u00b0F increments.", + "label": "C-Wire Power Thermistor Offset", + "default": -20, + "min": -32768, + "max": 32767, + "states": { + "-1": "Disabled" + }, + "unit": "0.1\u00b0F", + "valueSize": 4, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": -10 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 37, + "propertyName": "Run Fan With Auxiliary Heat", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Run Fan With Auxiliary Heat", + "default": 0, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 1, + "format": 0, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 1, + "propertyName": "Z-Wave Association Report: Thermostat Mode", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Thermostat Mode", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 8, + "propertyName": "Z-Wave Association Report: Thermostat Operating State", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Thermostat Operating State", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 16, + "propertyName": "Z-Wave Association Report: Thermostat Fan Mode", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Thermostat Fan Mode", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 32, + "propertyName": "Z-Wave Association Report: Thermostat Fan State", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Thermostat Fan State", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 64, + "propertyName": "Z-Wave Association Report: Ambiant Temperature", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Ambiant Temperature", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 128, + "propertyName": "Z-Wave Association Report: Relative Humidity", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Relative Humidity", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 512, + "propertyName": "Z-Wave Association Report: Battery Low Notification", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Battery Low Notification", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 1024, + "propertyName": "Z-Wave Association Report: Battery Very Low Notification", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Battery Very Low Notification", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 2048, + "propertyName": "Z-Wave Association Report: Thermostat Supported Modes", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Thermostat Supported Modes", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 4096, + "propertyName": "Z-Wave Association Report: Remote Enable Report", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Remote Enable Report", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 8192, + "propertyName": "Z-Wave Association Report: Humidity Control Operating State Report", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Humidity Control Operating State Report", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 16384, + "propertyName": "Z-Wave Association Report: HVAC Type", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: HVAC Type", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 32768, + "propertyName": "Z-Wave Association Report: Number of Cool/Pump Stages", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Number of Cool/Pump Stages", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 65536, + "propertyName": "Z-Wave Association Report: Number of Heat/Aux Stages", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Number of Heat/Aux Stages", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 131072, + "propertyName": "Z-Wave Association Report: Relay Status", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Relay Status", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 262144, + "propertyName": "Z-Wave Association Report: Power Source", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Power Source", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 524288, + "propertyName": "Z-Wave Association Report: Notification Report Power Applied", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Notification Report Power Applied", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 1048576, + "propertyName": "Z-Wave Association Report: Notification Report Mains Disconnected", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Notification Report Mains Disconnected", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 2097152, + "propertyName": "Z-Wave Association Report: Notification Report Mains Reconnected", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Notification Report Mains Reconnected", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 4194304, + "propertyName": "Z-Wave Association Report: Notification Report Replace Battery Soon", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Notification Report Replace Battery Soon", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 8388608, + "propertyName": "Z-Wave Association Report: Notification Report Replace Battery Now", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Notification Report Replace Battery Now", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 16777216, + "propertyName": "Z-Wave Association Report: Notification Report System Hardware Failure", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Notification Report System Hardware Failure", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 33554432, + "propertyName": "Z-Wave Association Report: Notification Report System Software Failure", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Notification Report System Software Failure", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 67108864, + "propertyName": "Z-Wave Association Report: Notification Report System Hardware Failure with Code", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Notification Report System Hardware Failure with Code", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 134217728, + "propertyName": "Z-Wave Association Report: Notification Report System Software Failure with Code", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Notification Report System Software Failure with Code", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 268435456, + "propertyName": "Z-Wave Association Report: Display Units", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Display Units", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 536870912, + "propertyName": "Z-Wave Association Report: Heat Fuel Type", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Heat Fuel Type", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 1073741824, + "propertyName": "Z-Wave Association Report: Humidity Control Mode", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Humidity Control Mode", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 2147483648, + "propertyName": "Z-Wave Association Report: Humidity Control Setpoints", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Bitmask to selectively enable non-required Z-wave association reports.", + "label": "Z-Wave Association Report: Humidity Control Setpoints", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 39, + "propertyName": "Remote Temperature Timeout", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Remote Temperature Timeout", + "default": 130, + "min": 0, + "max": 32767, + "unit": "minutes", + "valueSize": 2, + "format": 1, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 130 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 40, + "propertyKey": 16776960, + "propertyName": "Remote Temperature Differential", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Allowable range: 0 to 99 in 1 \u00b0F increments.", + "label": "Remote Temperature Differential", + "default": 250, + "min": -32768, + "max": 32767, + "states": { + "-1": "Disabled" + }, + "unit": "0.1\u00b0F", + "valueSize": 4, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 250 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 41, + "propertyName": "Remote Temperature ACK Failure Limit", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Remote Temperature ACK Failure Limit", + "default": 3, + "min": 0, + "max": 127, + "valueSize": 1, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 42, + "propertyName": "Remote Temperature Display Enable", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Remote Temperature Display Enable", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 1, + "format": 0, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 43, + "propertyName": "Outdoor Temperature Timeout", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Outdoor Temperature Timeout", + "default": 1440, + "min": 0, + "max": 32767, + "unit": "minutes", + "valueSize": 2, + "format": 1, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 1440 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 45, + "propertyName": "Heat Pump Expire", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Heat Pump Expire", + "default": 0, + "min": 0, + "max": 2880, + "unit": "minutes", + "valueSize": 2, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 46, + "propertyKey": 16776960, + "propertyName": "Dehumidify by AC Offset", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Allowable range: 0 to 10 in 1 \u00b0F increments.", + "label": "Dehumidify by AC Offset", + "default": 30, + "min": -32768, + "max": 32767, + "states": { + "-1": "Disabled" + }, + "unit": "0.1\u00b0F", + "valueSize": 4, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 30 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 48, + "propertyName": "PIR Enable", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "PIR Enable", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 1, + "format": 0, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 49, + "propertyName": "Humidity Display", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Humidity Display", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 1, + "format": 0, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 50, + "propertyKey": 2147483648, + "propertyName": "System configuration: Aux Fan", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "description": "Summarized report of system configuration", + "label": "System configuration: Aux Fan", + "min": 0, + "max": 1, + "states": { + "0": "Disabled", + "1": "Enabled" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 50, + "propertyKey": 1610612736, + "propertyName": "System configuration: Cool Stages", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "description": "Summarized report of system configuration", + "label": "System configuration: Cool Stages", + "min": 0, + "max": 3, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 50, + "propertyKey": 402653184, + "propertyName": "System configuration: Heat Stages", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "description": "Summarized report of system configuration", + "label": "System configuration: Heat Stages", + "min": 0, + "max": 3, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 50, + "propertyKey": 67108864, + "propertyName": "System configuration: Fuel", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "description": "Summarized report of system configuration", + "label": "System configuration: Fuel", + "min": 0, + "max": 1, + "states": { + "0": "Fuel", + "1": "Electric" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 50, + "propertyKey": 50331648, + "propertyName": "System configuration: HVAC Type", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "description": "Summarized report of system configuration", + "label": "System configuration: HVAC Type", + "min": 0, + "max": 3, + "states": { + "0": "Normal", + "1": "Heat Pump" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 50, + "propertyKey": 15728640, + "propertyName": "System configuration: Z2 Configuration", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "description": "Summarized report of system configuration", + "label": "System configuration: Z2 Configuration", + "min": 0, + "max": 15, + "states": { + "0": "None", + "1": "W3, 3rd Stage Auxiliary Heat", + "2": "H, Humidifier", + "3": "DH, Dehumidifier", + "4": "External Air Baffle or Vent" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 50, + "propertyKey": 983040, + "propertyName": "System configuration: Z1 Configuration", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "description": "Summarized report of system configuration", + "label": "System configuration: Z1 Configuration", + "min": 0, + "max": 15, + "states": { + "0": "None", + "1": "W3, 3rd Stage Auxiliary Heat", + "2": "H, Humidifier", + "3": "DH, Dehumidifier", + "4": "External Air Baffle or Vent" + }, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 2 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 50, + "propertyKey": 256, + "propertyName": "System configuration: Override", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "description": "Summarized report of system configuration", + "label": "System configuration: Override", + "min": 0, + "max": 1, + "valueSize": 4, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 52, + "propertyName": "Vent Options", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Vent Options", + "default": 4, + "min": 0, + "max": 4, + "states": { + "0": "Disabled", + "1": "Always activate regardless of thermostat operating state", + "2": "Only activate when heating", + "3": "Only activate when cooling", + "4": "Only activate when heating or cooling" + }, + "valueSize": 1, + "format": 0, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 4 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 53, + "propertyName": "Vent Circulation Period", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Vent Circulation Period", + "default": 60, + "min": 10, + "max": 1440, + "unit": "minutes", + "valueSize": 2, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 60 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 54, + "propertyName": "Vent Circulation Duty Cycle", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Vent Circulation Duty Cycle", + "default": 25, + "min": 0, + "max": 100, + "unit": "%", + "valueSize": 1, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 25 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 55, + "propertyKey": 16776960, + "propertyName": "Vent Maximum Outdoor Temperature", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Allowable range: 0 to 99 in 1 \u00b0F increments.", + "label": "Vent Maximum Outdoor Temperature", + "default": -32768, + "min": -32768, + "max": 32767, + "states": { + "-1": "Disabled" + }, + "unit": "0.1\u00b0F", + "valueSize": 4, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": -1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 56, + "propertyKey": 16776960, + "propertyName": "Vent Minimum Outdoor Temperature", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Allowable range: 0 to 99 in 1 \u00b0F increments.", + "label": "Vent Minimum Outdoor Temperature", + "default": -32768, + "min": -32768, + "max": 32767, + "states": { + "-1": "Disabled" + }, + "unit": "0.1\u00b0F", + "valueSize": 4, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": -1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 57, + "propertyName": "Relay Harvest Level", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Relay Harvest Level", + "default": 12, + "min": 0, + "max": 12, + "states": { + "0": "Off", + "9": "8 pulses", + "10": "16 pulses", + "11": "32 pulses", + "12": "64 pulses" + }, + "valueSize": 1, + "format": 0, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 11 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 58, + "propertyName": "Relay Harvest Interval", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Relay Harvest Interval", + "default": 4, + "min": 0, + "max": 5, + "states": { + "0": "Off", + "2": "4 Milliseconds", + "3": "8 Milliseconds", + "4": "16 Milliseconds", + "5": "32 Milliseconds" + }, + "valueSize": 1, + "format": 0, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 4 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 59, + "propertyName": "Minimum Battery Reporting Interval", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Minimum number of hours between battery reports", + "label": "Minimum Battery Reporting Interval", + "default": 60, + "min": 0, + "max": 127, + "unit": "hours", + "valueSize": 1, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 6 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 60, + "propertyName": "Humidity Control Swing", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Percent value the thermostat will add (for de-humidify) to or remove (for humidify) from the relevant humidity control setpoint.", + "label": "Humidity Control Swing", + "default": 5, + "min": 1, + "max": 100, + "unit": "%", + "valueSize": 1, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 61, + "propertyName": "Humidity Reporting Threshold", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "The minimum percent the relative humidity must change between reported humidity values.", + "label": "Humidity Reporting Threshold", + "default": 5, + "min": 0, + "max": 100, + "unit": "%", + "valueSize": 1, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 62, + "propertyName": "Z-Wave Send Fail Limit", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Z-Wave Send Fail Limit", + "default": 10, + "min": 0, + "max": 255, + "valueSize": 1, + "format": 1, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 10 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 64, + "propertyName": "Vent Override Lockout", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Activate the vent if it has not been active in the specified period.", + "label": "Vent Override Lockout", + "default": 12, + "min": 0, + "max": 127, + "unit": "hours", + "valueSize": 1, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 12 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 65, + "propertyName": "Humidify Options", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Humidify Options", + "default": 1, + "min": 0, + "max": 1, + "states": { + "0": "Always humidify regardless of thermostat operating state", + "1": "Only humidify when the thermostat operating state is heating, when in heat mode or when heating in auto mode. When in any other thermostat mode, the thermostat will humidify whenever it is necessary." + }, + "valueSize": 1, + "format": 0, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 51, + "propertyName": "Thermostat Reset", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": false, + "writeable": true, + "description": "Must write the magic value 2870 to take effect.", + "label": "Thermostat Reset", + "default": 0, + "min": 0, + "max": 2870, + "valueSize": 2, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + } + }, + { + "endpoint": 0, + "commandClass": 113, + "commandClassName": "Notification", + "property": "Power Management", + "propertyKey": "Power status", + "propertyName": "Power Management", + "propertyKeyName": "Power status", + "ccVersion": 7, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Power status", + "ccSpecific": { + "notificationType": 8 + }, + "min": 0, + "max": 255, + "states": { + "0": "idle", + "1": "Power has been applied" + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 113, + "commandClassName": "Notification", + "property": "Power Management", + "propertyKey": "Battery maintenance status", + "propertyName": "Power Management", + "propertyKeyName": "Battery maintenance status", + "ccVersion": 7, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Battery maintenance status", + "ccSpecific": { + "notificationType": 8 + }, + "min": 0, + "max": 255, + "states": { + "0": "idle", + "10": "Replace battery soon", + "11": "Replace battery now" + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 113, + "commandClassName": "Notification", + "property": "System", + "propertyKey": "Hardware status", + "propertyName": "System", + "propertyKeyName": "Hardware status", + "ccVersion": 7, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Hardware status", + "ccSpecific": { + "notificationType": 9 + }, + "min": 0, + "max": 255, + "states": { + "0": "idle", + "1": "System hardware failure", + "3": "System hardware failure (with failure code)" + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 113, + "commandClassName": "Notification", + "property": "System", + "propertyKey": "Software status", + "propertyName": "System", + "propertyKeyName": "Software status", + "ccVersion": 7, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Software status", + "ccSpecific": { + "notificationType": 9 + }, + "min": 0, + "max": 255, + "states": { + "0": "idle", + "2": "System software failure", + "4": "System software failure (with failure code)" + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 113, + "commandClassName": "Notification", + "property": "Power Management", + "propertyKey": "Mains status", + "propertyName": "Power Management", + "propertyKeyName": "Mains status", + "ccVersion": 7, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Mains status", + "ccSpecific": { + "notificationType": 8 + }, + "min": 0, + "max": 255, + "states": { + "2": "AC mains disconnected", + "3": "AC mains re-connected" + } + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "manufacturerId", + "propertyName": "manufacturerId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Manufacturer ID", + "min": 0, + "max": 65535 + }, + "value": 400 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productType", + "propertyName": "productType", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Product type", + "min": 0, + "max": 65535 + }, + "value": 6 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productId", + "propertyName": "productId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Product ID", + "min": 0, + "max": 65535 + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 128, + "commandClassName": "Battery", + "property": "level", + "propertyName": "level", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Battery level", + "min": 0, + "max": 100, + "unit": "%" + }, + "value": 100 + }, + { + "endpoint": 0, + "commandClass": 128, + "commandClassName": "Battery", + "property": "isLow", + "propertyName": "isLow", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Low battery level" + }, + "value": false + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "libraryType", + "propertyName": "libraryType", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Library type", + "states": { + "0": "Unknown", + "1": "Static Controller", + "2": "Controller", + "3": "Enhanced Slave", + "4": "Slave", + "5": "Installer", + "6": "Routing Slave", + "7": "Bridge Controller", + "8": "Device under Test", + "9": "N/A", + "10": "AV Remote", + "11": "AV Device" + } + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "protocolVersion", + "propertyName": "protocolVersion", + "ccVersion": 2, + "metadata": { + "type": "string", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "6.4" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "ccVersion": 2, + "metadata": { + "type": "string[]", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": [ + "1.44", + "1.40", + "1.30" + ] + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hardwareVersion", + "propertyName": "hardwareVersion", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Z-Wave chip hardware version" + }, + "value": 8 + } + ], + "isFrequentListening": "1000ms", + "maxDataRate": 100000, + "supportedDataRates": [ + 40000, + 100000 + ], + "protocolVersion": 3, + "supportsBeaming": true, + "supportsSecurity": false, + "nodeType": 1, + "zwavePlusNodeType": 0, + "zwavePlusRoleType": 7, + "deviceClass": { + "basic": { + "key": 4, + "label": "Routing Slave" + }, + "generic": { + "key": 8, + "label": "Thermostat" + }, + "specific": { + "key": 6, + "label": "General Thermostat V2" + }, + "mandatorySupportedCCs": [ + 32, + 114, + 64, + 67, + 134 + ], + "mandatoryControlledCCs": [] + }, + "commandClasses": [ + { + "id": 49, + "name": "Multilevel Sensor", + "version": 11, + "isSecure": true + }, + { + "id": 64, + "name": "Thermostat Mode", + "version": 2, + "isSecure": true + }, + { + "id": 66, + "name": "Thermostat Operating State", + "version": 2, + "isSecure": true + }, + { + "id": 67, + "name": "Thermostat Setpoint", + "version": 3, + "isSecure": true + }, + { + "id": 68, + "name": "Thermostat Fan Mode", + "version": 3, + "isSecure": true + }, + { + "id": 69, + "name": "Thermostat Fan State", + "version": 1, + "isSecure": true + }, + { + "id": 85, + "name": "Transport Service", + "version": 2, + "isSecure": false + }, + { + "id": 89, + "name": "Association Group Information", + "version": 1, + "isSecure": true + }, + { + "id": 90, + "name": "Device Reset Locally", + "version": 1, + "isSecure": true + }, + { + "id": 94, + "name": "Z-Wave Plus Info", + "version": 2, + "isSecure": false + }, + { + "id": 100, + "name": "Humidity Control Setpoint", + "version": 1, + "isSecure": true + }, + { + "id": 108, + "name": "Supervision", + "version": 1, + "isSecure": false + }, + { + "id": 109, + "name": "Humidity Control Mode", + "version": 2, + "isSecure": true + }, + { + "id": 110, + "name": "Humidity Control Operating State", + "version": 1, + "isSecure": true + }, + { + "id": 112, + "name": "Configuration", + "version": 1, + "isSecure": true + }, + { + "id": 113, + "name": "Notification", + "version": 7, + "isSecure": true + }, + { + "id": 114, + "name": "Manufacturer Specific", + "version": 2, + "isSecure": true + }, + { + "id": 115, + "name": "Powerlevel", + "version": 1, + "isSecure": true + }, + { + "id": 122, + "name": "Firmware Update Meta Data", + "version": 3, + "isSecure": true + }, + { + "id": 128, + "name": "Battery", + "version": 1, + "isSecure": true + }, + { + "id": 129, + "name": "Clock", + "version": 1, + "isSecure": true + }, + { + "id": 133, + "name": "Association", + "version": 2, + "isSecure": true + }, + { + "id": 134, + "name": "Version", + "version": 2, + "isSecure": true + }, + { + "id": 159, + "name": "Security 2", + "version": 1, + "isSecure": true + } + ], + "interviewStage": "Complete", + "deviceDatabaseUrl": "https://devices.zwave-js.io/?jumpTo=0x0190:0x0006:0x0001:1.44", + "statistics": { + "commandsTX": 6, + "commandsRX": 6124, + "commandsDroppedRX": 40, + "commandsDroppedTX": 0, + "timeoutResponse": 0 + }, + "highestSecurityClass": 1, + "isControllerNode": false, + "keepAwake": false +} \ No newline at end of file diff --git a/tests/components/zwave_js/test_humidifier.py b/tests/components/zwave_js/test_humidifier.py new file mode 100644 index 00000000000000..6e76fe8d1643ea --- /dev/null +++ b/tests/components/zwave_js/test_humidifier.py @@ -0,0 +1,1056 @@ +"""Test the Z-Wave JS humidifier platform.""" +from zwave_js_server.const import CommandClass +from zwave_js_server.const.command_class.humidity_control import HumidityControlMode +from zwave_js_server.event import Event + +from homeassistant.components.humidifier import HumidifierDeviceClass +from homeassistant.components.humidifier.const import ( + ATTR_HUMIDITY, + ATTR_MAX_HUMIDITY, + ATTR_MIN_HUMIDITY, + DEFAULT_MAX_HUMIDITY, + DEFAULT_MIN_HUMIDITY, + DOMAIN as HUMIDIFIER_DOMAIN, + SERVICE_SET_HUMIDITY, +) +from homeassistant.const import ( + ATTR_DEVICE_CLASS, + ATTR_ENTITY_ID, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, +) + +from .common import DEHUMIDIFIER_ADC_T3000_ENTITY, HUMIDIFIER_ADC_T3000_ENTITY + + +async def test_humidifier(hass, client, climate_adc_t3000, integration): + """Test a humidity control command class entity.""" + + node = climate_adc_t3000 + state = hass.states.get(HUMIDIFIER_ADC_T3000_ENTITY) + + assert state + assert state.state == STATE_ON + assert state.attributes[ATTR_DEVICE_CLASS] == HumidifierDeviceClass.HUMIDIFIER + assert state.attributes[ATTR_HUMIDITY] == 35 + assert state.attributes[ATTR_MIN_HUMIDITY] == 10 + assert state.attributes[ATTR_MAX_HUMIDITY] == 70 + + client.async_send_command.reset_mock() + + # Test setting humidity + await hass.services.async_call( + HUMIDIFIER_DOMAIN, + SERVICE_SET_HUMIDITY, + { + ATTR_ENTITY_ID: HUMIDIFIER_ADC_T3000_ENTITY, + ATTR_HUMIDITY: 41, + }, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args_list[0][0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 68 + assert args["valueId"] == { + "ccVersion": 1, + "commandClassName": "Humidity Control Setpoint", + "commandClass": CommandClass.HUMIDITY_CONTROL_SETPOINT, + "endpoint": 0, + "property": "setpoint", + "propertyKey": 1, + "propertyName": "setpoint", + "propertyKeyName": "Humidifier", + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "unit": "%", + "min": 10, + "max": 70, + "ccSpecific": {"setpointType": 1}, + }, + "value": 35, + } + assert args["value"] == 41 + + client.async_send_command.reset_mock() + + # Test de-humidify mode update from value updated event + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.DEHUMIDIFY), + "prevValue": int(HumidityControlMode.HUMIDIFY), + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(HUMIDIFIER_ADC_T3000_ENTITY) + assert state.state == STATE_OFF + + client.async_send_command.reset_mock() + + # Test auto mode update from value updated event + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.AUTO), + "prevValue": int(HumidityControlMode.HUMIDIFY), + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(HUMIDIFIER_ADC_T3000_ENTITY) + assert state.state == STATE_ON + + client.async_send_command.reset_mock() + + # Test off mode update from value updated event + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.OFF), + "prevValue": int(HumidityControlMode.HUMIDIFY), + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(HUMIDIFIER_ADC_T3000_ENTITY) + assert state.state == STATE_OFF + + client.async_send_command.reset_mock() + + # Test turning off when device is previously humidifying + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.HUMIDIFY), + "prevValue": int(HumidityControlMode.OFF), + }, + }, + ) + node.receive_event(event) + + await hass.services.async_call( + HUMIDIFIER_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: HUMIDIFIER_ADC_T3000_ENTITY}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args_list[0][0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 68 + assert args["valueId"] == { + "ccVersion": 2, + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "min": 0, + "max": 255, + "label": "Humidity control mode", + "states": {"0": "Off", "1": "Humidify", "2": "De-humidify", "3": "Auto"}, + }, + "value": int(HumidityControlMode.HUMIDIFY), + } + assert args["value"] == int(HumidityControlMode.OFF) + + client.async_send_command.reset_mock() + + # Test turning off when device is previously auto + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.AUTO), + "prevValue": int(HumidityControlMode.OFF), + }, + }, + ) + node.receive_event(event) + + await hass.services.async_call( + HUMIDIFIER_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: HUMIDIFIER_ADC_T3000_ENTITY}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args_list[0][0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 68 + assert args["valueId"] == { + "ccVersion": 2, + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "min": 0, + "max": 255, + "label": "Humidity control mode", + "states": {"0": "Off", "1": "Humidify", "2": "De-humidify", "3": "Auto"}, + }, + "value": int(HumidityControlMode.AUTO), + } + assert args["value"] == int(HumidityControlMode.DEHUMIDIFY) + + client.async_send_command.reset_mock() + + # Test turning off when device is previously de-humidifying + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.DEHUMIDIFY), + "prevValue": int(HumidityControlMode.OFF), + }, + }, + ) + node.receive_event(event) + + await hass.services.async_call( + HUMIDIFIER_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: HUMIDIFIER_ADC_T3000_ENTITY}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 0 + + client.async_send_command.reset_mock() + + # Test turning off when device is previously off + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.OFF), + "prevValue": int(HumidityControlMode.AUTO), + }, + }, + ) + node.receive_event(event) + + await hass.services.async_call( + HUMIDIFIER_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: HUMIDIFIER_ADC_T3000_ENTITY}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 0 + + client.async_send_command.reset_mock() + + # Test turning on when device is previously humidifying + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.HUMIDIFY), + "prevValue": int(HumidityControlMode.OFF), + }, + }, + ) + node.receive_event(event) + + await hass.services.async_call( + HUMIDIFIER_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: HUMIDIFIER_ADC_T3000_ENTITY}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 0 + + client.async_send_command.reset_mock() + + # Test turning on when device is previously auto + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.AUTO), + "prevValue": int(HumidityControlMode.OFF), + }, + }, + ) + node.receive_event(event) + + await hass.services.async_call( + HUMIDIFIER_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: HUMIDIFIER_ADC_T3000_ENTITY}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 0 + + client.async_send_command.reset_mock() + + # Test turning on when device is previously de-humidifying + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.DEHUMIDIFY), + "prevValue": int(HumidityControlMode.OFF), + }, + }, + ) + node.receive_event(event) + + await hass.services.async_call( + HUMIDIFIER_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: HUMIDIFIER_ADC_T3000_ENTITY}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args_list[0][0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 68 + assert args["valueId"] == { + "ccVersion": 2, + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "min": 0, + "max": 255, + "label": "Humidity control mode", + "states": {"0": "Off", "1": "Humidify", "2": "De-humidify", "3": "Auto"}, + }, + "value": int(HumidityControlMode.DEHUMIDIFY), + } + assert args["value"] == int(HumidityControlMode.AUTO) + + client.async_send_command.reset_mock() + + # Test turning on when device is previously off + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.OFF), + "prevValue": int(HumidityControlMode.AUTO), + }, + }, + ) + node.receive_event(event) + + await hass.services.async_call( + HUMIDIFIER_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: HUMIDIFIER_ADC_T3000_ENTITY}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args_list[0][0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 68 + assert args["valueId"] == { + "ccVersion": 2, + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "min": 0, + "max": 255, + "label": "Humidity control mode", + "states": {"0": "Off", "1": "Humidify", "2": "De-humidify", "3": "Auto"}, + }, + "value": int(HumidityControlMode.OFF), + } + assert args["value"] == int(HumidityControlMode.HUMIDIFY) + + +async def test_dehumidifier_missing_setpoint( + hass, client, climate_adc_t3000_missing_setpoint, integration +): + """Test a humidity control command class entity.""" + + entity_id = "humidifier.adc_t3000_missing_setpoint_dehumidifier" + state = hass.states.get(entity_id) + + assert state + assert ATTR_HUMIDITY not in state.attributes + assert state.attributes[ATTR_MIN_HUMIDITY] == DEFAULT_MIN_HUMIDITY + assert state.attributes[ATTR_MAX_HUMIDITY] == DEFAULT_MAX_HUMIDITY + + client.async_send_command.reset_mock() + + # Test setting humidity + await hass.services.async_call( + HUMIDIFIER_DOMAIN, + SERVICE_SET_HUMIDITY, + { + ATTR_ENTITY_ID: entity_id, + ATTR_HUMIDITY: 41, + }, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 0 + + client.async_send_command.reset_mock() + + +async def test_humidifier_missing_mode( + hass, client, climate_adc_t3000_missing_mode, integration +): + """Test a humidity control command class entity.""" + + node = climate_adc_t3000_missing_mode + + # Test that de-humidifer entity does not exist but humidifier entity does + entity_id = "humidifier.adc_t3000_missing_mode_dehumidifier" + state = hass.states.get(entity_id) + assert not state + + entity_id = "humidifier.adc_t3000_missing_mode_humidifier" + state = hass.states.get(entity_id) + assert state + + client.async_send_command.reset_mock() + + # Test turning off when device is previously auto for a device which does not have de-humidify mode + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.AUTO), + "prevValue": int(HumidityControlMode.OFF), + }, + }, + ) + node.receive_event(event) + + await hass.services.async_call( + HUMIDIFIER_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args_list[0][0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 68 + assert args["valueId"] == { + "ccVersion": 2, + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "min": 0, + "max": 255, + "label": "Humidity control mode", + "states": {"0": "Off", "1": "Humidify", "3": "Auto"}, + }, + "value": int(HumidityControlMode.AUTO), + } + assert args["value"] == int(HumidityControlMode.OFF) + + client.async_send_command.reset_mock() + + +async def test_dehumidifier(hass, client, climate_adc_t3000, integration): + """Test a humidity control command class entity.""" + + node = climate_adc_t3000 + state = hass.states.get(DEHUMIDIFIER_ADC_T3000_ENTITY) + + assert state + assert state.state == STATE_ON + assert state.attributes[ATTR_DEVICE_CLASS] == HumidifierDeviceClass.DEHUMIDIFIER + assert state.attributes[ATTR_HUMIDITY] == 60 + assert state.attributes[ATTR_MIN_HUMIDITY] == 30 + assert state.attributes[ATTR_MAX_HUMIDITY] == 90 + + client.async_send_command.reset_mock() + + # Test setting humidity + await hass.services.async_call( + HUMIDIFIER_DOMAIN, + SERVICE_SET_HUMIDITY, + { + ATTR_ENTITY_ID: DEHUMIDIFIER_ADC_T3000_ENTITY, + ATTR_HUMIDITY: 41, + }, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args_list[0][0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 68 + assert args["valueId"] == { + "ccVersion": 1, + "commandClassName": "Humidity Control Setpoint", + "commandClass": CommandClass.HUMIDITY_CONTROL_SETPOINT, + "endpoint": 0, + "property": "setpoint", + "propertyKey": 2, + "propertyName": "setpoint", + "propertyKeyName": "De-humidifier", + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "unit": "%", + "min": 30, + "max": 90, + "ccSpecific": {"setpointType": 2}, + }, + "value": 60, + } + assert args["value"] == 41 + + client.async_send_command.reset_mock() + + # Test humidify mode update from value updated event + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.HUMIDIFY), + "prevValue": int(HumidityControlMode.DEHUMIDIFY), + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(DEHUMIDIFIER_ADC_T3000_ENTITY) + assert state.state == STATE_OFF + + client.async_send_command.reset_mock() + + # Test auto mode update from value updated event + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.AUTO), + "prevValue": int(HumidityControlMode.DEHUMIDIFY), + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(DEHUMIDIFIER_ADC_T3000_ENTITY) + assert state.state == STATE_ON + + client.async_send_command.reset_mock() + + # Test off mode update from value updated event + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.OFF), + "prevValue": int(HumidityControlMode.DEHUMIDIFY), + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(DEHUMIDIFIER_ADC_T3000_ENTITY) + assert state.state == STATE_OFF + + client.async_send_command.reset_mock() + + # Test turning off when device is previously de-humidifying + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.DEHUMIDIFY), + "prevValue": int(HumidityControlMode.OFF), + }, + }, + ) + node.receive_event(event) + + await hass.services.async_call( + HUMIDIFIER_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: DEHUMIDIFIER_ADC_T3000_ENTITY}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args_list[0][0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 68 + assert args["valueId"] == { + "ccVersion": 2, + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "min": 0, + "max": 255, + "label": "Humidity control mode", + "states": {"0": "Off", "1": "Humidify", "2": "De-humidify", "3": "Auto"}, + }, + "value": int(HumidityControlMode.DEHUMIDIFY), + } + assert args["value"] == int(HumidityControlMode.OFF) + + client.async_send_command.reset_mock() + + # Test turning off when device is previously auto + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.AUTO), + "prevValue": int(HumidityControlMode.OFF), + }, + }, + ) + node.receive_event(event) + + await hass.services.async_call( + HUMIDIFIER_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: DEHUMIDIFIER_ADC_T3000_ENTITY}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args_list[0][0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 68 + assert args["valueId"] == { + "ccVersion": 2, + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "min": 0, + "max": 255, + "label": "Humidity control mode", + "states": {"0": "Off", "1": "Humidify", "2": "De-humidify", "3": "Auto"}, + }, + "value": int(HumidityControlMode.AUTO), + } + assert args["value"] == int(HumidityControlMode.HUMIDIFY) + + client.async_send_command.reset_mock() + + # Test turning off when device is previously humidifying + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.HUMIDIFY), + "prevValue": int(HumidityControlMode.OFF), + }, + }, + ) + node.receive_event(event) + + await hass.services.async_call( + HUMIDIFIER_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: DEHUMIDIFIER_ADC_T3000_ENTITY}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 0 + + client.async_send_command.reset_mock() + + # Test turning off when device is previously off + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.OFF), + "prevValue": int(HumidityControlMode.AUTO), + }, + }, + ) + node.receive_event(event) + + await hass.services.async_call( + HUMIDIFIER_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: DEHUMIDIFIER_ADC_T3000_ENTITY}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 0 + + client.async_send_command.reset_mock() + + # Test turning on when device is previously de-humidifying + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.DEHUMIDIFY), + "prevValue": int(HumidityControlMode.OFF), + }, + }, + ) + node.receive_event(event) + + await hass.services.async_call( + HUMIDIFIER_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: DEHUMIDIFIER_ADC_T3000_ENTITY}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 0 + + client.async_send_command.reset_mock() + + # Test turning on when device is previously auto + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.AUTO), + "prevValue": int(HumidityControlMode.OFF), + }, + }, + ) + node.receive_event(event) + + await hass.services.async_call( + HUMIDIFIER_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: DEHUMIDIFIER_ADC_T3000_ENTITY}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 0 + + client.async_send_command.reset_mock() + + # Test turning on when device is previously humidifying + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.HUMIDIFY), + "prevValue": int(HumidityControlMode.OFF), + }, + }, + ) + node.receive_event(event) + + await hass.services.async_call( + HUMIDIFIER_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: DEHUMIDIFIER_ADC_T3000_ENTITY}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args_list[0][0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 68 + assert args["valueId"] == { + "ccVersion": 2, + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "min": 0, + "max": 255, + "label": "Humidity control mode", + "states": {"0": "Off", "1": "Humidify", "2": "De-humidify", "3": "Auto"}, + }, + "value": int(HumidityControlMode.HUMIDIFY), + } + assert args["value"] == int(HumidityControlMode.AUTO) + + client.async_send_command.reset_mock() + + # Test turning on when device is previously off + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 68, + "args": { + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": int(HumidityControlMode.OFF), + "prevValue": int(HumidityControlMode.AUTO), + }, + }, + ) + node.receive_event(event) + + await hass.services.async_call( + HUMIDIFIER_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: DEHUMIDIFIER_ADC_T3000_ENTITY}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args_list[0][0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 68 + assert args["valueId"] == { + "ccVersion": 2, + "commandClassName": "Humidity Control Mode", + "commandClass": CommandClass.HUMIDITY_CONTROL_MODE, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "min": 0, + "max": 255, + "label": "Humidity control mode", + "states": {"0": "Off", "1": "Humidify", "2": "De-humidify", "3": "Auto"}, + }, + "value": int(HumidityControlMode.OFF), + } + assert args["value"] == int(HumidityControlMode.DEHUMIDIFY) From facf22c2ddccbf9b205a2d8b26da457330b53ba6 Mon Sep 17 00:00:00 2001 From: patagona Date: Wed, 23 Feb 2022 18:21:20 +0100 Subject: [PATCH 0997/1098] Correctly handle missing mpd albumart (#66771) Co-authored-by: Martin Hjelmare --- homeassistant/components/mpd/media_player.py | 51 ++++++++++++++++---- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/mpd/media_player.py b/homeassistant/components/mpd/media_player.py index d2c6b7eeaf3350..4d3df8f00ca21d 100644 --- a/homeassistant/components/mpd/media_player.py +++ b/homeassistant/components/mpd/media_player.py @@ -119,6 +119,9 @@ def __init__(self, server, port, password, name): self._muted_volume = None self._media_position_updated_at = None self._media_position = None + self._media_image_hash = None + # Track if the song changed so image doesn't have to be loaded every update. + self._media_image_file = None self._commands = None # set up MPD client @@ -149,6 +152,7 @@ async def _fetch_status(self): """Fetch status from MPD.""" self._status = await self._client.status() self._currentsong = await self._client.currentsong() + await self._async_update_media_image_hash() if (position := self._status.get("elapsed")) is None: position = self._status.get("time") @@ -265,16 +269,46 @@ def media_album_name(self): @property def media_image_hash(self): """Hash value for media image.""" - if file := self._currentsong.get("file"): - return hashlib.sha256(file.encode("utf-8")).hexdigest()[:16] - - return None + return self._media_image_hash async def async_get_media_image(self): """Fetch media image of current playing track.""" if not (file := self._currentsong.get("file")): return None, None + response = await self._async_get_file_image_response(file) + if response is None: + return None, None + + image = bytes(response["binary"]) + mime = response.get( + "type", "image/png" + ) # readpicture has type, albumart does not + return (image, mime) + async def _async_update_media_image_hash(self): + """Update the hash value for the media image.""" + file = self._currentsong.get("file") + + if file == self._media_image_file: + return + + if ( + file is not None + and (response := await self._async_get_file_image_response(file)) + is not None + ): + self._media_image_hash = hashlib.sha256( + bytes(response["binary"]) + ).hexdigest()[:16] + else: + # If there is no image, this hash has to be None, else the media player component + # assumes there is an image and returns an error trying to load it and the + # frontend media control card breaks. + self._media_image_hash = None + + self._media_image_file = file + + async def _async_get_file_image_response(self, file): # not all MPD implementations and versions support the `albumart` and `fetchpicture` commands can_albumart = "albumart" in self._commands can_readpicture = "readpicture" in self._commands @@ -303,14 +337,11 @@ async def async_get_media_image(self): error, ) + # response can be an empty object if there is no image if not response: - return None, None + return None - image = bytes(response.get("binary")) - mime = response.get( - "type", "image/png" - ) # readpicture has type, albumart does not - return (image, mime) + return response @property def volume_level(self): From c243247e35a016d3583b3db510987b6d855d8379 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 23 Feb 2022 07:38:18 -1000 Subject: [PATCH 0998/1098] Remove effects from WiZ wall dimmer switches (#67097) --- homeassistant/components/wiz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/wiz/manifest.json b/homeassistant/components/wiz/manifest.json index df780d7b39c61d..26d32589c94b92 100644 --- a/homeassistant/components/wiz/manifest.json +++ b/homeassistant/components/wiz/manifest.json @@ -13,7 +13,7 @@ "dependencies": ["network"], "quality_scale": "platinum", "documentation": "https://www.home-assistant.io/integrations/wiz", - "requirements": ["pywizlight==0.5.12"], + "requirements": ["pywizlight==0.5.13"], "iot_class": "local_push", "codeowners": ["@sbidy"] } diff --git a/requirements_all.txt b/requirements_all.txt index d3044a7bf88f88..42ef52313ae69f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2061,7 +2061,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.5.12 +pywizlight==0.5.13 # homeassistant.components.xeoma pyxeoma==1.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f85a5a6f779008..17ca84b70fb3ca 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1292,7 +1292,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.5.12 +pywizlight==0.5.13 # homeassistant.components.zerproc pyzerproc==0.4.8 From 3380a15bbb10c34ba79647f5501c6fea60b0f0ff Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 23 Feb 2022 09:54:27 -0800 Subject: [PATCH 0999/1098] Mobile app: Drop descriptive emoji name support (#67120) --- homeassistant/components/mobile_app/http_api.py | 16 ++-------------- .../components/mobile_app/manifest.json | 4 ++-- homeassistant/package_constraints.txt | 1 - requirements_all.txt | 3 --- requirements_test_all.txt | 3 --- 5 files changed, 4 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/mobile_app/http_api.py b/homeassistant/components/mobile_app/http_api.py index 20fa25ec21f886..ea8c56d1a7c42a 100644 --- a/homeassistant/components/mobile_app/http_api.py +++ b/homeassistant/components/mobile_app/http_api.py @@ -4,10 +4,8 @@ from contextlib import suppress from http import HTTPStatus import secrets -from typing import cast from aiohttp.web import Request, Response -import emoji from nacl.secret import SecretBox import voluptuous as vol @@ -82,18 +80,8 @@ async def post(self, request: Request, data: dict) -> Response: data[CONF_USER_ID] = request["hass_user"].id - if slugify(data[ATTR_DEVICE_NAME], separator=""): - # if slug is not empty and would not only be underscores - # use DEVICE_NAME - pass - elif emoji.emoji_count(data[ATTR_DEVICE_NAME]): - # If otherwise empty string contains emoji - # use descriptive name of the first emoji - data[ATTR_DEVICE_NAME] = emoji.demojize( - cast(str, emoji.emoji_lis(data[ATTR_DEVICE_NAME])[0]["emoji"]) - ).replace(":", "") - else: - # Fallback to DEVICE_ID + # Fallback to DEVICE_ID if slug is empty. + if not slugify(data[ATTR_DEVICE_NAME], separator=""): data[ATTR_DEVICE_NAME] = data[ATTR_DEVICE_ID] await hass.async_create_task( diff --git a/homeassistant/components/mobile_app/manifest.json b/homeassistant/components/mobile_app/manifest.json index 4723a2a6fb93d6..6cb4e964c9b5b1 100644 --- a/homeassistant/components/mobile_app/manifest.json +++ b/homeassistant/components/mobile_app/manifest.json @@ -3,11 +3,11 @@ "name": "Mobile App", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/mobile_app", - "requirements": ["PyNaCl==1.4.0", "emoji==1.6.3"], + "requirements": ["PyNaCl==1.4.0"], "dependencies": ["http", "webhook", "person", "tag", "websocket_api"], "after_dependencies": ["cloud", "camera", "notify"], "codeowners": ["@home-assistant/core"], "quality_scale": "internal", "iot_class": "local_push", - "loggers": ["emoji", "nacl"] + "loggers": ["nacl"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 472202859e2e96..366d26ac0a708d 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -13,7 +13,6 @@ bcrypt==3.1.7 certifi>=2021.5.30 ciso8601==2.2.0 cryptography==35.0.0 -emoji==1.6.3 hass-nabucasa==0.53.1 home-assistant-frontend==20220222.0 httpx==0.21.3 diff --git a/requirements_all.txt b/requirements_all.txt index 42ef52313ae69f..c6fa19cea2d272 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -608,9 +608,6 @@ elkm1-lib==1.2.0 # homeassistant.components.elmax elmax_api==0.0.2 -# homeassistant.components.mobile_app -emoji==1.6.3 - # homeassistant.components.emulated_roku emulated_roku==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 17ca84b70fb3ca..b4225458e8ca11 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -400,9 +400,6 @@ elkm1-lib==1.2.0 # homeassistant.components.elmax elmax_api==0.0.2 -# homeassistant.components.mobile_app -emoji==1.6.3 - # homeassistant.components.emulated_roku emulated_roku==0.2.1 From e37402e1d5540f9053e81a34258fe20cdad72d84 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Wed, 23 Feb 2022 18:55:31 +0100 Subject: [PATCH 1000/1098] Import tag (#64539) Co-authored-by: epenet --- homeassistant/components/esphome/__init__.py | 11 ++++++----- homeassistant/components/mqtt/tag.py | 5 ++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index 7d5736a2e689d2..0154e2eba28c22 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -117,7 +117,7 @@ async def async_setup_entry( # noqa: C901 port = entry.data[CONF_PORT] password = entry.data[CONF_PASSWORD] noise_psk = entry.data.get(CONF_NOISE_PSK) - device_id = None + device_id: str | None = None zeroconf_instance = await zeroconf.async_get_instance(hass) @@ -184,11 +184,12 @@ def async_on_service_call(service: HomeassistantServiceCall) -> None: return # Call native tag scan - if service_name == "tag_scanned": + if service_name == "tag_scanned" and device_id is not None: + # Importing tag via hass.components in case it is overridden + # in a custom_components (custom_components.tag) + tag = hass.components.tag tag_id = service_data["tag_id"] - hass.async_create_task( - hass.components.tag.async_scan_tag(tag_id, device_id) - ) + hass.async_create_task(tag.async_scan_tag(tag_id, device_id)) return hass.bus.async_fire(service.service, service_data) diff --git a/homeassistant/components/mqtt/tag.py b/homeassistant/components/mqtt/tag.py index 4f6f380e47db9e..a2541c064c0238 100644 --- a/homeassistant/components/mqtt/tag.py +++ b/homeassistant/components/mqtt/tag.py @@ -177,7 +177,10 @@ async def tag_scanned(msg): if not tag_id: # No output from template, ignore return - await self.hass.components.tag.async_scan_tag(tag_id, self.device_id) + # Importing tag via hass.components in case it is overridden + # in a custom_components (custom_components.tag) + tag = self.hass.components.tag + await tag.async_scan_tag(tag_id, self.device_id) self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, From 93fab1f99659ab1cb16de215832cbe37fe5a27f3 Mon Sep 17 00:00:00 2001 From: Kevin Stillhammer Date: Wed, 23 Feb 2022 18:59:42 +0100 Subject: [PATCH 1001/1098] Cleanup Waze_travel_time_sensor_tests (#67047) --- tests/components/waze_travel_time/conftest.py | 45 +++++++++---------- .../waze_travel_time/test_config_flow.py | 40 +++++------------ 2 files changed, 31 insertions(+), 54 deletions(-) diff --git a/tests/components/waze_travel_time/conftest.py b/tests/components/waze_travel_time/conftest.py index d2bb647b2cacaa..4d964d9f08ada8 100644 --- a/tests/components/waze_travel_time/conftest.py +++ b/tests/components/waze_travel_time/conftest.py @@ -14,6 +14,13 @@ def mock_wrc_fixture(): yield mock_wrc +@pytest.fixture(name="mock_update") +def mock_update_fixture(mock_wrc): + """Mock an update to the sensor.""" + obj = mock_wrc.return_value + obj.calc_all_routes_info.return_value = {"My route": (150, 300)} + + @pytest.fixture(name="validate_config_entry") def validate_config_entry_fixture(): """Return valid config entry.""" @@ -22,17 +29,15 @@ def validate_config_entry_fixture(): ) as mock_wrc: obj = mock_wrc.return_value obj.calc_all_routes_info.return_value = None - yield + yield mock_wrc -@pytest.fixture(name="bypass_setup") -def bypass_setup_fixture(): - """Bypass entry setup.""" - with patch( - "homeassistant.components.waze_travel_time.async_setup_entry", - return_value=True, - ): - yield +@pytest.fixture(name="invalidate_config_entry") +def invalidate_config_entry_fixture(validate_config_entry): + """Return invalid config entry.""" + obj = validate_config_entry.return_value + obj.calc_all_routes_info.return_value = {} + obj.calc_all_routes_info.side_effect = WRCError("test") @pytest.fixture(name="bypass_platform_setup") @@ -45,21 +50,11 @@ def bypass_platform_setup_fixture(): yield -@pytest.fixture(name="mock_update") -def mock_update_fixture(mock_wrc): - """Mock an update to the sensor.""" - obj = mock_wrc.return_value - obj.calc_all_routes_info.return_value = {"My route": (150, 300)} - yield - - -@pytest.fixture(name="invalidate_config_entry") -def invalidate_config_entry_fixture(): - """Return invalid config entry.""" +@pytest.fixture(name="bypass_setup") +def bypass_setup_fixture(): + """Bypass entry setup.""" with patch( - "homeassistant.components.waze_travel_time.helpers.WazeRouteCalculator" - ) as mock_wrc: - obj = mock_wrc.return_value - obj.calc_all_routes_info.return_value = {} - obj.calc_all_routes_info.side_effect = WRCError("test") + "homeassistant.components.waze_travel_time.async_setup_entry", + return_value=True, + ): yield diff --git a/tests/components/waze_travel_time/test_config_flow.py b/tests/components/waze_travel_time/test_config_flow.py index 8a57a34b739b90..c4414cdbefd5df 100644 --- a/tests/components/waze_travel_time/test_config_flow.py +++ b/tests/components/waze_travel_time/test_config_flow.py @@ -1,4 +1,6 @@ """Test the Waze Travel Time config flow.""" +import pytest + from homeassistant import config_entries, data_entry_flow from homeassistant.components.waze_travel_time.const import ( CONF_AVOID_FERRIES, @@ -21,7 +23,8 @@ from tests.common import MockConfigEntry -async def test_minimum_fields(hass, validate_config_entry, bypass_setup): +@pytest.mark.usefixtures("validate_config_entry") +async def test_minimum_fields(hass): """Test we get the form.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -45,9 +48,8 @@ async def test_minimum_fields(hass, validate_config_entry, bypass_setup): } -async def test_options(hass, validate_config_entry, mock_update): +async def test_options(hass): """Test options flow.""" - entry = MockConfigEntry( domain=DOMAIN, data=MOCK_CONFIG, @@ -99,7 +101,8 @@ async def test_options(hass, validate_config_entry, mock_update): } -async def test_import(hass, validate_config_entry, mock_update): +@pytest.mark.usefixtures("validate_config_entry") +async def test_import(hass): """Test import for config flow.""" result = await hass.config_entries.flow.async_init( DOMAIN, @@ -139,30 +142,8 @@ async def test_import(hass, validate_config_entry, mock_update): } -async def _setup_dupe_import(hass, mock_update): - """Set up dupe import.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data={ - CONF_ORIGIN: "location1", - CONF_DESTINATION: "location2", - CONF_REGION: "US", - CONF_AVOID_FERRIES: True, - CONF_AVOID_SUBSCRIPTION_ROADS: True, - CONF_AVOID_TOLL_ROADS: True, - CONF_EXCL_FILTER: "exclude", - CONF_INCL_FILTER: "include", - CONF_REALTIME: False, - CONF_UNITS: CONF_UNIT_SYSTEM_IMPERIAL, - CONF_VEHICLE_TYPE: "taxi", - }, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - await hass.async_block_till_done() - - -async def test_dupe(hass, validate_config_entry, bypass_setup): +@pytest.mark.usefixtures("validate_config_entry") +async def test_dupe(hass): """Test setting up the same entry data twice is OK.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -194,7 +175,8 @@ async def test_dupe(hass, validate_config_entry, bypass_setup): assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY -async def test_invalid_config_entry(hass, invalidate_config_entry): +@pytest.mark.usefixtures("invalidate_config_entry") +async def test_invalid_config_entry(hass): """Test we get the form.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} From a08165a8d70f484ef28ef01a1ac7679f21f3264a Mon Sep 17 00:00:00 2001 From: Jonathan Keljo Date: Wed, 23 Feb 2022 10:09:12 -0800 Subject: [PATCH 1002/1098] Create greeneye_monitor entities when monitor connects (#66710) --- .../components/greeneye_monitor/sensor.py | 214 ++++++++---------- tests/components/greeneye_monitor/common.py | 10 + .../components/greeneye_monitor/test_init.py | 13 +- .../greeneye_monitor/test_sensor.py | 82 ++++--- 4 files changed, 168 insertions(+), 151 deletions(-) diff --git a/homeassistant/components/greeneye_monitor/sensor.py b/homeassistant/components/greeneye_monitor/sensor.py index dee2dbe75d9da2..d6b7185a7cfb5f 100644 --- a/homeassistant/components/greeneye_monitor/sensor.py +++ b/homeassistant/components/greeneye_monitor/sensor.py @@ -53,56 +53,71 @@ async def async_setup_platform( if not discovery_info: return - entities: list[GEMSensor] = [] - for monitor_config in discovery_info[CONF_MONITORS]: - monitor_serial_number = monitor_config[CONF_SERIAL_NUMBER] - - channel_configs = monitor_config[CONF_CHANNELS] - for sensor in channel_configs: - entities.append( - CurrentSensor( - monitor_serial_number, - sensor[CONF_NUMBER], - sensor[CONF_NAME], - sensor[CONF_NET_METERING], + monitor_configs = discovery_info[CONF_MONITORS] + + def on_new_monitor(monitor: greeneye.monitor.Monitor) -> None: + monitor_config = next( + filter( + lambda monitor_config: monitor_config[CONF_SERIAL_NUMBER] + == monitor.serial_number, + monitor_configs, + ), + None, + ) + if monitor_config: + entities: list[GEMSensor] = [] + + channel_configs = monitor_config[CONF_CHANNELS] + for sensor in channel_configs: + entities.append( + CurrentSensor( + monitor, + sensor[CONF_NUMBER], + sensor[CONF_NAME], + sensor[CONF_NET_METERING], + ) ) - ) - - pulse_counter_configs = monitor_config[CONF_PULSE_COUNTERS] - for sensor in pulse_counter_configs: - entities.append( - PulseCounter( - monitor_serial_number, - sensor[CONF_NUMBER], - sensor[CONF_NAME], - sensor[CONF_COUNTED_QUANTITY], - sensor[CONF_TIME_UNIT], - sensor[CONF_COUNTED_QUANTITY_PER_PULSE], + + pulse_counter_configs = monitor_config[CONF_PULSE_COUNTERS] + for sensor in pulse_counter_configs: + entities.append( + PulseCounter( + monitor, + sensor[CONF_NUMBER], + sensor[CONF_NAME], + sensor[CONF_COUNTED_QUANTITY], + sensor[CONF_TIME_UNIT], + sensor[CONF_COUNTED_QUANTITY_PER_PULSE], + ) ) - ) - - temperature_sensor_configs = monitor_config[CONF_TEMPERATURE_SENSORS] - for sensor in temperature_sensor_configs[CONF_SENSORS]: - entities.append( - TemperatureSensor( - monitor_serial_number, - sensor[CONF_NUMBER], - sensor[CONF_NAME], - temperature_sensor_configs[CONF_TEMPERATURE_UNIT], + + temperature_sensor_configs = monitor_config[CONF_TEMPERATURE_SENSORS] + for sensor in temperature_sensor_configs[CONF_SENSORS]: + entities.append( + TemperatureSensor( + monitor, + sensor[CONF_NUMBER], + sensor[CONF_NAME], + temperature_sensor_configs[CONF_TEMPERATURE_UNIT], + ) ) - ) - - voltage_sensor_configs = monitor_config[CONF_VOLTAGE_SENSORS] - for sensor in voltage_sensor_configs: - entities.append( - VoltageSensor( - monitor_serial_number, - sensor[CONF_NUMBER], - sensor[CONF_NAME], + + voltage_sensor_configs = monitor_config[CONF_VOLTAGE_SENSORS] + for sensor in voltage_sensor_configs: + entities.append( + VoltageSensor(monitor, sensor[CONF_NUMBER], sensor[CONF_NAME]) ) - ) - async_add_entities(entities) + async_add_entities(entities) + monitor_configs.remove(monitor_config) + + if len(monitor_configs) == 0: + monitors.remove_listener(on_new_monitor) + + monitors: greeneye.Monitors = hass.data[DATA_GREENEYE_MONITOR] + monitors.add_listener(on_new_monitor) + for monitor in monitors.monitors.values(): + on_new_monitor(monitor) UnderlyingSensorType = Union[ @@ -119,13 +134,19 @@ class GEMSensor(SensorEntity): _attr_should_poll = False def __init__( - self, monitor_serial_number: int, name: str, sensor_type: str, number: int + self, + monitor: greeneye.monitor.Monitor, + name: str, + sensor_type: str, + sensor: UnderlyingSensorType, + number: int, ) -> None: """Construct the entity.""" - self._monitor_serial_number = monitor_serial_number + self._monitor = monitor + self._monitor_serial_number = self._monitor.serial_number self._attr_name = name - self._monitor: greeneye.monitor.Monitor | None = None self._sensor_type = sensor_type + self._sensor: UnderlyingSensorType = sensor self._number = number self._attr_unique_id = ( f"{self._monitor_serial_number}-{self._sensor_type}-{self._number}" @@ -133,37 +154,12 @@ def __init__( async def async_added_to_hass(self) -> None: """Wait for and connect to the sensor.""" - monitors = self.hass.data[DATA_GREENEYE_MONITOR] - - if not self._try_connect_to_monitor(monitors): - monitors.add_listener(self._on_new_monitor) - - def _on_new_monitor(self, monitor: greeneye.monitor.Monitor) -> None: - monitors = self.hass.data[DATA_GREENEYE_MONITOR] - if self._try_connect_to_monitor(monitors): - monitors.remove_listener(self._on_new_monitor) + self._sensor.add_listener(self.async_write_ha_state) async def async_will_remove_from_hass(self) -> None: """Remove listener from the sensor.""" if self._sensor: self._sensor.remove_listener(self.async_write_ha_state) - else: - monitors = self.hass.data[DATA_GREENEYE_MONITOR] - monitors.remove_listener(self._on_new_monitor) - - def _try_connect_to_monitor(self, monitors: greeneye.Monitors) -> bool: - self._monitor = monitors.monitors.get(self._monitor_serial_number) - if not self._sensor: - return False - - self._sensor.add_listener(self.async_write_ha_state) - self.async_write_ha_state() - - return True - - @property - def _sensor(self) -> UnderlyingSensorType | None: - raise NotImplementedError() class CurrentSensor(GEMSensor): @@ -173,30 +169,25 @@ class CurrentSensor(GEMSensor): _attr_device_class = SensorDeviceClass.POWER def __init__( - self, monitor_serial_number: int, number: int, name: str, net_metering: bool + self, + monitor: greeneye.monitor.Monitor, + number: int, + name: str, + net_metering: bool, ) -> None: """Construct the entity.""" - super().__init__(monitor_serial_number, name, "current", number) + super().__init__(monitor, name, "current", monitor.channels[number - 1], number) + self._sensor: greeneye.monitor.Channel = self._sensor self._net_metering = net_metering - @property - def _sensor(self) -> greeneye.monitor.Channel | None: - return self._monitor.channels[self._number - 1] if self._monitor else None - @property def native_value(self) -> float | None: """Return the current number of watts being used by the channel.""" - if not self._sensor: - return None - return self._sensor.watts @property def extra_state_attributes(self) -> dict[str, Any] | None: """Return total wattseconds in the state dictionary.""" - if not self._sensor: - return None - if self._net_metering: watt_seconds = self._sensor.polarized_watt_seconds else: @@ -212,7 +203,7 @@ class PulseCounter(GEMSensor): def __init__( self, - monitor_serial_number: int, + monitor: greeneye.monitor.Monitor, number: int, name: str, counted_quantity: str, @@ -220,19 +211,18 @@ def __init__( counted_quantity_per_pulse: float, ) -> None: """Construct the entity.""" - super().__init__(monitor_serial_number, name, "pulse", number) + super().__init__( + monitor, name, "pulse", monitor.pulse_counters[number - 1], number + ) + self._sensor: greeneye.monitor.PulseCounter = self._sensor self._counted_quantity_per_pulse = counted_quantity_per_pulse self._time_unit = time_unit self._attr_native_unit_of_measurement = f"{counted_quantity}/{self._time_unit}" - @property - def _sensor(self) -> greeneye.monitor.PulseCounter | None: - return self._monitor.pulse_counters[self._number - 1] if self._monitor else None - @property def native_value(self) -> float | None: """Return the current rate of change for the given pulse counter.""" - if not self._sensor or self._sensor.pulses_per_second is None: + if self._sensor.pulses_per_second is None: return None result = ( @@ -258,11 +248,8 @@ def _seconds_per_time_unit(self) -> int: ) @property - def extra_state_attributes(self) -> dict[str, Any] | None: + def extra_state_attributes(self) -> dict[str, Any]: """Return total pulses in the data dictionary.""" - if not self._sensor: - return None - return {DATA_PULSES: self._sensor.pulses} @@ -272,26 +259,18 @@ class TemperatureSensor(GEMSensor): _attr_device_class = SensorDeviceClass.TEMPERATURE def __init__( - self, monitor_serial_number: int, number: int, name: str, unit: str + self, monitor: greeneye.monitor.Monitor, number: int, name: str, unit: str ) -> None: """Construct the entity.""" - super().__init__(monitor_serial_number, name, "temp", number) - self._attr_native_unit_of_measurement = unit - - @property - def _sensor(self) -> greeneye.monitor.TemperatureSensor | None: - return ( - self._monitor.temperature_sensors[self._number - 1] - if self._monitor - else None + super().__init__( + monitor, name, "temp", monitor.temperature_sensors[number - 1], number ) + self._sensor: greeneye.monitor.TemperatureSensor = self._sensor + self._attr_native_unit_of_measurement = unit @property def native_value(self) -> float | None: """Return the current temperature being reported by this sensor.""" - if not self._sensor: - return None - return self._sensor.temperature @@ -301,19 +280,14 @@ class VoltageSensor(GEMSensor): _attr_native_unit_of_measurement = ELECTRIC_POTENTIAL_VOLT _attr_device_class = SensorDeviceClass.VOLTAGE - def __init__(self, monitor_serial_number: int, number: int, name: str) -> None: + def __init__( + self, monitor: greeneye.monitor.Monitor, number: int, name: str + ) -> None: """Construct the entity.""" - super().__init__(monitor_serial_number, name, "volts", number) - - @property - def _sensor(self) -> greeneye.monitor.VoltageSensor | None: - """Wire the updates to the monitor itself, since there is no voltage element in the API.""" - return self._monitor.voltage_sensor if self._monitor else None + super().__init__(monitor, name, "volts", monitor.voltage_sensor, number) + self._sensor: greeneye.monitor.VoltageSensor = self._sensor @property def native_value(self) -> float | None: """Return the current voltage being reported by this sensor.""" - if not self._sensor: - return None - return self._sensor.voltage diff --git a/tests/components/greeneye_monitor/common.py b/tests/components/greeneye_monitor/common.py index 0a19b79795f6b0..e9285647f4de21 100644 --- a/tests/components/greeneye_monitor/common.py +++ b/tests/components/greeneye_monitor/common.py @@ -239,3 +239,13 @@ def mock_monitor(serial_number: int) -> MagicMock: monitor.temperature_sensors = [mock_temperature_sensor() for i in range(0, 8)] monitor.channels = [mock_channel() for i in range(0, 32)] return monitor + + +async def connect_monitor( + hass: HomeAssistant, monitors: AsyncMock, serial_number: int +) -> MagicMock: + """Simulate a monitor connecting to Home Assistant. Returns the mock monitor API object.""" + monitor = mock_monitor(serial_number) + monitors.add_monitor(monitor) + await hass.async_block_till_done() + return monitor diff --git a/tests/components/greeneye_monitor/test_init.py b/tests/components/greeneye_monitor/test_init.py index c8e13714939587..df8c67e7eed97f 100644 --- a/tests/components/greeneye_monitor/test_init.py +++ b/tests/components/greeneye_monitor/test_init.py @@ -18,6 +18,7 @@ SINGLE_MONITOR_CONFIG_TEMPERATURE_SENSORS, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS, SINGLE_MONITOR_SERIAL_NUMBER, + connect_monitor, setup_greeneye_monitor_component_with_config, ) from .conftest import ( @@ -53,7 +54,7 @@ async def test_setup_creates_temperature_entities( assert await setup_greeneye_monitor_component_with_config( hass, SINGLE_MONITOR_CONFIG_TEMPERATURE_SENSORS ) - + await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER) assert_temperature_sensor_registered( hass, SINGLE_MONITOR_SERIAL_NUMBER, 1, "temp_a" ) @@ -87,7 +88,7 @@ async def test_setup_creates_pulse_counter_entities( assert await setup_greeneye_monitor_component_with_config( hass, SINGLE_MONITOR_CONFIG_PULSE_COUNTERS ) - + await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER) assert_pulse_counter_registered( hass, SINGLE_MONITOR_SERIAL_NUMBER, @@ -124,7 +125,7 @@ async def test_setup_creates_power_sensor_entities( assert await setup_greeneye_monitor_component_with_config( hass, SINGLE_MONITOR_CONFIG_POWER_SENSORS ) - + await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER) assert_power_sensor_registered(hass, SINGLE_MONITOR_SERIAL_NUMBER, 1, "channel 1") assert_power_sensor_registered(hass, SINGLE_MONITOR_SERIAL_NUMBER, 2, "channel two") @@ -136,7 +137,7 @@ async def test_setup_creates_voltage_sensor_entities( assert await setup_greeneye_monitor_component_with_config( hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS ) - + await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER) assert_voltage_sensor_registered(hass, SINGLE_MONITOR_SERIAL_NUMBER, 1, "voltage 1") @@ -147,6 +148,10 @@ async def test_multi_monitor_config(hass: HomeAssistant, monitors: AsyncMock) -> MULTI_MONITOR_CONFIG, ) + await connect_monitor(hass, monitors, 1) + await connect_monitor(hass, monitors, 2) + await connect_monitor(hass, monitors, 3) + assert_temperature_sensor_registered(hass, 1, 1, "unit_1_temp_1") assert_temperature_sensor_registered(hass, 2, 1, "unit_2_temp_1") assert_temperature_sensor_registered(hass, 3, 1, "unit_3_temp_1") diff --git a/tests/components/greeneye_monitor/test_sensor.py b/tests/components/greeneye_monitor/test_sensor.py index ac1fe92873a58b..f739b8a64cad03 100644 --- a/tests/components/greeneye_monitor/test_sensor.py +++ b/tests/components/greeneye_monitor/test_sensor.py @@ -1,5 +1,5 @@ """Tests for greeneye_monitor sensors.""" -from unittest.mock import AsyncMock, MagicMock +from unittest.mock import AsyncMock from homeassistant.components.greeneye_monitor.sensor import ( DATA_PULSES, @@ -19,38 +19,50 @@ SINGLE_MONITOR_CONFIG_TEMPERATURE_SENSORS, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS, SINGLE_MONITOR_SERIAL_NUMBER, - mock_monitor, + connect_monitor, setup_greeneye_monitor_component_with_config, ) from .conftest import assert_sensor_state -async def test_disable_sensor_before_monitor_connected( +async def test_sensor_does_not_exist_before_monitor_connected( hass: HomeAssistant, monitors: AsyncMock ) -> None: - """Test that a sensor disabled before its monitor connected stops listening for new monitors.""" + """Test that a sensor does not exist before its monitor is connected.""" # The sensor base class handles connecting the monitor, so we test this with a single voltage sensor for ease await setup_greeneye_monitor_component_with_config( hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS ) + entity_registry = get_entity_registry(hass) + assert entity_registry.async_get("sensor.voltage_1") is None + + +async def test_sensors_created_when_monitor_connected( + hass: HomeAssistant, monitors: AsyncMock +) -> None: + """Test that sensors get created when the monitor first connects.""" + # The sensor base class handles updating the state on connection, so we test this with a single voltage sensor for ease + await setup_greeneye_monitor_component_with_config( + hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS + ) + assert len(monitors.listeners) == 1 - await disable_entity(hass, "sensor.voltage_1") + await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER) assert len(monitors.listeners) == 0 # Make sure we cleaned up the listener + assert_sensor_state(hass, "sensor.voltage_1", "120.0") -async def test_updates_state_when_monitor_connected( +async def test_sensors_created_during_setup_if_monitor_already_connected( hass: HomeAssistant, monitors: AsyncMock ) -> None: - """Test that a sensor updates its state when its monitor first connects.""" + """Test that sensors get created during setup if the monitor happens to connect really quickly.""" # The sensor base class handles updating the state on connection, so we test this with a single voltage sensor for ease + await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER) await setup_greeneye_monitor_component_with_config( hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS ) - assert_sensor_state(hass, "sensor.voltage_1", STATE_UNKNOWN) - assert len(monitors.listeners) == 1 - connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER) assert len(monitors.listeners) == 0 # Make sure we cleaned up the listener assert_sensor_state(hass, "sensor.voltage_1", "120.0") @@ -63,7 +75,7 @@ async def test_disable_sensor_after_monitor_connected( await setup_greeneye_monitor_component_with_config( hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS ) - monitor = connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER) + monitor = await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER) assert len(monitor.voltage_sensor.listeners) == 1 await disable_entity(hass, "sensor.voltage_1") @@ -78,7 +90,7 @@ async def test_updates_state_when_sensor_pushes( await setup_greeneye_monitor_component_with_config( hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS ) - monitor = connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER) + monitor = await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER) assert_sensor_state(hass, "sensor.voltage_1", "120.0") monitor.voltage_sensor.voltage = 119.8 @@ -93,7 +105,7 @@ async def test_power_sensor_initially_unknown( await setup_greeneye_monitor_component_with_config( hass, SINGLE_MONITOR_CONFIG_POWER_SENSORS ) - connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER) + await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER) assert_sensor_state( hass, "sensor.channel_1", STATE_UNKNOWN, {DATA_WATT_SECONDS: 1000} ) @@ -109,7 +121,7 @@ async def test_power_sensor(hass: HomeAssistant, monitors: AsyncMock) -> None: await setup_greeneye_monitor_component_with_config( hass, SINGLE_MONITOR_CONFIG_POWER_SENSORS ) - monitor = connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER) + monitor = await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER) monitor.channels[0].watts = 120.0 monitor.channels[1].watts = 120.0 monitor.channels[0].notify_all_listeners() @@ -120,12 +132,35 @@ async def test_power_sensor(hass: HomeAssistant, monitors: AsyncMock) -> None: assert_sensor_state(hass, "sensor.channel_two", "120.0", {DATA_WATT_SECONDS: -400}) +async def test_pulse_counter_initially_unknown( + hass: HomeAssistant, monitors: AsyncMock +) -> None: + """Test that the pulse counter sensor can handle its initial state being unknown (since the GEM API needs at least two packets to arrive before it can compute pulses per time).""" + await setup_greeneye_monitor_component_with_config( + hass, SINGLE_MONITOR_CONFIG_PULSE_COUNTERS + ) + monitor = await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER) + monitor.pulse_counters[0].pulses_per_second = None + monitor.pulse_counters[1].pulses_per_second = None + monitor.pulse_counters[2].pulses_per_second = None + monitor.pulse_counters[0].notify_all_listeners() + monitor.pulse_counters[1].notify_all_listeners() + monitor.pulse_counters[2].notify_all_listeners() + assert_sensor_state(hass, "sensor.pulse_a", STATE_UNKNOWN, {DATA_PULSES: 1000}) + # This counter was configured with each pulse meaning 0.5 gallons and + # wanting to show gallons per minute, so 10 pulses per second -> 300 gal/min + assert_sensor_state(hass, "sensor.pulse_2", STATE_UNKNOWN, {DATA_PULSES: 1000}) + # This counter was configured with each pulse meaning 0.5 gallons and + # wanting to show gallons per hour, so 10 pulses per second -> 18000 gal/hr + assert_sensor_state(hass, "sensor.pulse_3", STATE_UNKNOWN, {DATA_PULSES: 1000}) + + async def test_pulse_counter(hass: HomeAssistant, monitors: AsyncMock) -> None: """Test that a pulse counter sensor reports its values properly, including calculating different units.""" await setup_greeneye_monitor_component_with_config( hass, SINGLE_MONITOR_CONFIG_PULSE_COUNTERS ) - connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER) + await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER) assert_sensor_state(hass, "sensor.pulse_a", "10.0", {DATA_PULSES: 1000}) # This counter was configured with each pulse meaning 0.5 gallons and # wanting to show gallons per minute, so 10 pulses per second -> 300 gal/min @@ -140,7 +175,7 @@ async def test_temperature_sensor(hass: HomeAssistant, monitors: AsyncMock) -> N await setup_greeneye_monitor_component_with_config( hass, SINGLE_MONITOR_CONFIG_TEMPERATURE_SENSORS ) - connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER) + await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER) # The config says that the sensor is reporting in Fahrenheit; if we set that up # properly, HA will have converted that to Celsius by default. assert_sensor_state(hass, "sensor.temp_a", "0.0") @@ -151,28 +186,21 @@ async def test_voltage_sensor(hass: HomeAssistant, monitors: AsyncMock) -> None: await setup_greeneye_monitor_component_with_config( hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS ) - connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER) + await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER) assert_sensor_state(hass, "sensor.voltage_1", "120.0") async def test_multi_monitor_sensors(hass: HomeAssistant, monitors: AsyncMock) -> None: """Test that sensors still work when multiple monitors are registered.""" await setup_greeneye_monitor_component_with_config(hass, MULTI_MONITOR_CONFIG) - connect_monitor(monitors, 1) - connect_monitor(monitors, 2) - connect_monitor(monitors, 3) + await connect_monitor(hass, monitors, 1) + await connect_monitor(hass, monitors, 2) + await connect_monitor(hass, monitors, 3) assert_sensor_state(hass, "sensor.unit_1_temp_1", "32.0") assert_sensor_state(hass, "sensor.unit_2_temp_1", "0.0") assert_sensor_state(hass, "sensor.unit_3_temp_1", "32.0") -def connect_monitor(monitors: AsyncMock, serial_number: int) -> MagicMock: - """Simulate a monitor connecting to Home Assistant. Returns the mock monitor API object.""" - monitor = mock_monitor(serial_number) - monitors.add_monitor(monitor) - return monitor - - async def disable_entity(hass: HomeAssistant, entity_id: str) -> None: """Disable the given entity.""" entity_registry = get_entity_registry(hass) From a54e3ca1f8876ecc36a29c8362aac17d29049669 Mon Sep 17 00:00:00 2001 From: Milan Meulemans Date: Wed, 23 Feb 2022 19:10:30 +0100 Subject: [PATCH 1003/1098] Add Nanoleaf Swipe Device Trigger (#66195) --- .coveragerc | 1 + homeassistant/components/nanoleaf/__init__.py | 50 ++++++++++-- homeassistant/components/nanoleaf/const.py | 11 +++ .../components/nanoleaf/device_trigger.py | 79 +++++++++++++++++++ .../components/nanoleaf/strings.json | 8 ++ .../components/nanoleaf/translations/en.json | 8 ++ 6 files changed, 150 insertions(+), 7 deletions(-) create mode 100644 homeassistant/components/nanoleaf/device_trigger.py diff --git a/.coveragerc b/.coveragerc index 5ce91028102ddf..696cd5ce3b0bb7 100644 --- a/.coveragerc +++ b/.coveragerc @@ -762,6 +762,7 @@ omit = homeassistant/components/nad/media_player.py homeassistant/components/nanoleaf/__init__.py homeassistant/components/nanoleaf/button.py + homeassistant/components/nanoleaf/device_trigger.py homeassistant/components/nanoleaf/diagnostics.py homeassistant/components/nanoleaf/entity.py homeassistant/components/nanoleaf/light.py diff --git a/homeassistant/components/nanoleaf/__init__.py b/homeassistant/components/nanoleaf/__init__.py index 677c0d4cc2a081..9e9cf1d6ca4b07 100644 --- a/homeassistant/components/nanoleaf/__init__.py +++ b/homeassistant/components/nanoleaf/__init__.py @@ -6,16 +6,32 @@ from datetime import timedelta import logging -from aionanoleaf import EffectsEvent, InvalidToken, Nanoleaf, StateEvent, Unavailable +from aionanoleaf import ( + EffectsEvent, + InvalidToken, + Nanoleaf, + StateEvent, + TouchEvent, + Unavailable, +) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, CONF_TOKEN, Platform +from homeassistant.const import ( + CONF_DEVICE_ID, + CONF_HOST, + CONF_TOKEN, + CONF_TYPE, + Platform, +) from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import DOMAIN +from .const import DOMAIN, NANOLEAF_EVENT, TOUCH_GESTURE_TRIGGER_MAP, TOUCH_MODELS + +_LOGGER = logging.getLogger(__name__) PLATFORMS = [Platform.BUTTON, Platform.LIGHT] @@ -46,7 +62,7 @@ async def async_get_state() -> None: coordinator = DataUpdateCoordinator( hass, - logging.getLogger(__name__), + _LOGGER, name=entry.title, update_interval=timedelta(minutes=1), update_method=async_get_state, @@ -54,14 +70,34 @@ async def async_get_state() -> None: await coordinator.async_config_entry_first_refresh() - async def update_light_state_callback(event: StateEvent | EffectsEvent) -> None: + async def light_event_callback(event: StateEvent | EffectsEvent) -> None: """Receive state and effect event.""" coordinator.async_set_updated_data(None) + if supports_touch := nanoleaf.model in TOUCH_MODELS: + device_registry = dr.async_get(hass) + device_entry = device_registry.async_get_or_create( + config_entry_id=entry.entry_id, + identifiers={(DOMAIN, nanoleaf.serial_no)}, + ) + + async def touch_event_callback(event: TouchEvent) -> None: + """Receive touch event.""" + gesture_type = TOUCH_GESTURE_TRIGGER_MAP.get(event.gesture_id) + if gesture_type is None: + _LOGGER.debug("Received unknown touch gesture ID %s", event.gesture_id) + return + _LOGGER.warning("Received touch gesture %s", gesture_type) + hass.bus.async_fire( + NANOLEAF_EVENT, + {CONF_DEVICE_ID: device_entry.id, CONF_TYPE: gesture_type}, + ) + event_listener = asyncio.create_task( nanoleaf.listen_events( - state_callback=update_light_state_callback, - effects_callback=update_light_state_callback, + state_callback=light_event_callback, + effects_callback=light_event_callback, + touch_callback=touch_event_callback if supports_touch else None, ) ) diff --git a/homeassistant/components/nanoleaf/const.py b/homeassistant/components/nanoleaf/const.py index 505af8ce69d1d0..7e246209733ab7 100644 --- a/homeassistant/components/nanoleaf/const.py +++ b/homeassistant/components/nanoleaf/const.py @@ -1,3 +1,14 @@ """Constants for Nanoleaf integration.""" DOMAIN = "nanoleaf" + +NANOLEAF_EVENT = f"{DOMAIN}_event" + +TOUCH_MODELS = {"NL29", "NL42", "NL52"} + +TOUCH_GESTURE_TRIGGER_MAP = { + 2: "swipe_up", + 3: "swipe_down", + 4: "swipe_left", + 5: "swipe_right", +} diff --git a/homeassistant/components/nanoleaf/device_trigger.py b/homeassistant/components/nanoleaf/device_trigger.py new file mode 100644 index 00000000000000..311d12506ba7d4 --- /dev/null +++ b/homeassistant/components/nanoleaf/device_trigger.py @@ -0,0 +1,79 @@ +"""Provides device triggers for Nanoleaf.""" +from __future__ import annotations + +from typing import Any + +import voluptuous as vol + +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) +from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA +from homeassistant.components.device_automation.exceptions import DeviceNotFound +from homeassistant.components.homeassistant.triggers import event as event_trigger +from homeassistant.const import ( + CONF_DEVICE_ID, + CONF_DOMAIN, + CONF_EVENT, + CONF_PLATFORM, + CONF_TYPE, +) +from homeassistant.core import CALLBACK_TYPE, HomeAssistant +from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.typing import ConfigType + +from .const import DOMAIN, NANOLEAF_EVENT, TOUCH_GESTURE_TRIGGER_MAP, TOUCH_MODELS + +TRIGGER_TYPES = TOUCH_GESTURE_TRIGGER_MAP.values() + +TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend( + { + vol.Required(CONF_DOMAIN): DOMAIN, + vol.Required(CONF_DEVICE_ID): str, + vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES), + } +) + + +async def async_get_triggers( + hass: HomeAssistant, device_id: str +) -> list[dict[str, Any]]: + """List device triggers for Nanoleaf devices.""" + device_registry = dr.async_get(hass) + device_entry = device_registry.async_get(device_id) + if device_entry is None: + raise DeviceNotFound(f"Device ID {device_id} is not valid") + if device_entry.model not in TOUCH_MODELS: + return [] + return [ + { + CONF_PLATFORM: "device", + CONF_DOMAIN: DOMAIN, + CONF_DEVICE_ID: device_id, + CONF_TYPE: trigger_type, + } + for trigger_type in TRIGGER_TYPES + ] + + +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, +) -> CALLBACK_TYPE: + """Attach a trigger.""" + event_config = event_trigger.TRIGGER_SCHEMA( + { + event_trigger.CONF_PLATFORM: CONF_EVENT, + event_trigger.CONF_EVENT_TYPE: NANOLEAF_EVENT, + event_trigger.CONF_EVENT_DATA: { + CONF_TYPE: config[CONF_TYPE], + CONF_DEVICE_ID: config[CONF_DEVICE_ID], + }, + } + ) + return await event_trigger.async_attach_trigger( + hass, event_config, action, automation_info, platform_type="device" + ) diff --git a/homeassistant/components/nanoleaf/strings.json b/homeassistant/components/nanoleaf/strings.json index 96fcfd2622adeb..80eb2ded7d0e89 100644 --- a/homeassistant/components/nanoleaf/strings.json +++ b/homeassistant/components/nanoleaf/strings.json @@ -24,5 +24,13 @@ "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", "unknown": "[%key:common::config_flow::error::unknown%]" } + }, + "device_automation": { + "trigger_type": { + "swipe_up": "Swipe Up", + "swipe_down": "Swipe Down", + "swipe_left": "Swipe Left", + "swipe_right": "Swipe Right" + } } } diff --git a/homeassistant/components/nanoleaf/translations/en.json b/homeassistant/components/nanoleaf/translations/en.json index 7696f056aa3818..7064c617b0ec38 100644 --- a/homeassistant/components/nanoleaf/translations/en.json +++ b/homeassistant/components/nanoleaf/translations/en.json @@ -24,5 +24,13 @@ } } } + }, + "device_automation": { + "trigger_type": { + "swipe_down": "Swipe Down", + "swipe_left": "Swipe Left", + "swipe_right": "Swipe Right", + "swipe_up": "Swipe Up" + } } } \ No newline at end of file From 2a697bdf414745bd83d0df6b81c5477f594d5959 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Wed, 23 Feb 2022 10:15:04 -0800 Subject: [PATCH 1004/1098] Add support for Atlantic Electrical Heater in Overkiz integration (#67045) Co-authored-by: J. Nick Koston --- .coveragerc | 2 + homeassistant/components/overkiz/climate.py | 26 +++++++ .../overkiz/climate_entities/__init__.py | 8 ++ .../atlantic_electrical_heater.py | 73 +++++++++++++++++++ homeassistant/components/overkiz/const.py | 2 + 5 files changed, 111 insertions(+) create mode 100644 homeassistant/components/overkiz/climate.py create mode 100644 homeassistant/components/overkiz/climate_entities/__init__.py create mode 100644 homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater.py diff --git a/.coveragerc b/.coveragerc index 696cd5ce3b0bb7..360bd5f6911b31 100644 --- a/.coveragerc +++ b/.coveragerc @@ -870,6 +870,8 @@ omit = homeassistant/components/overkiz/__init__.py homeassistant/components/overkiz/binary_sensor.py homeassistant/components/overkiz/button.py + homeassistant/components/overkiz/climate.py + homeassistant/components/overkiz/climate_entities/* homeassistant/components/overkiz/cover.py homeassistant/components/overkiz/cover_entities/* homeassistant/components/overkiz/coordinator.py diff --git a/homeassistant/components/overkiz/climate.py b/homeassistant/components/overkiz/climate.py new file mode 100644 index 00000000000000..a94c731ec8f9f1 --- /dev/null +++ b/homeassistant/components/overkiz/climate.py @@ -0,0 +1,26 @@ +"""Support for Overkiz climate devices.""" +from __future__ import annotations + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import HomeAssistantOverkizData +from .climate_entities import WIDGET_TO_CLIMATE_ENTITY +from .const import DOMAIN + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the Overkiz climate from a config entry.""" + data: HomeAssistantOverkizData = hass.data[DOMAIN][entry.entry_id] + + async_add_entities( + WIDGET_TO_CLIMATE_ENTITY[device.widget](device.device_url, data.coordinator) + for device in data.platforms[Platform.CLIMATE] + if device.widget in WIDGET_TO_CLIMATE_ENTITY + ) diff --git a/homeassistant/components/overkiz/climate_entities/__init__.py b/homeassistant/components/overkiz/climate_entities/__init__.py new file mode 100644 index 00000000000000..0e98b7c7e21d71 --- /dev/null +++ b/homeassistant/components/overkiz/climate_entities/__init__.py @@ -0,0 +1,8 @@ +"""Climate entities for the Overkiz (by Somfy) integration.""" +from pyoverkiz.enums.ui import UIWidget + +from .atlantic_electrical_heater import AtlanticElectricalHeater + +WIDGET_TO_CLIMATE_ENTITY = { + UIWidget.ATLANTIC_ELECTRICAL_HEATER: AtlanticElectricalHeater, +} diff --git a/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater.py b/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater.py new file mode 100644 index 00000000000000..8756b768e52209 --- /dev/null +++ b/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater.py @@ -0,0 +1,73 @@ +"""Support for Atlantic Electrical Heater.""" +from __future__ import annotations + +from typing import cast + +from pyoverkiz.enums import OverkizCommand, OverkizCommandParam, OverkizState + +from homeassistant.components.climate import ( + HVAC_MODE_OFF, + SUPPORT_PRESET_MODE, + ClimateEntity, +) +from homeassistant.components.climate.const import ( + HVAC_MODE_HEAT, + PRESET_COMFORT, + PRESET_ECO, + PRESET_NONE, +) +from homeassistant.components.overkiz.entity import OverkizEntity +from homeassistant.const import TEMP_CELSIUS + +PRESET_FROST_PROTECTION = "frost_protection" + +OVERKIZ_TO_HVAC_MODES: dict[str, str] = { + OverkizCommandParam.ON: HVAC_MODE_HEAT, + OverkizCommandParam.COMFORT: HVAC_MODE_HEAT, + OverkizCommandParam.OFF: HVAC_MODE_OFF, +} +HVAC_MODES_TO_OVERKIZ = {v: k for k, v in OVERKIZ_TO_HVAC_MODES.items()} + +OVERKIZ_TO_PRESET_MODES: dict[str, str] = { + OverkizCommandParam.OFF: PRESET_NONE, + OverkizCommandParam.FROSTPROTECTION: PRESET_FROST_PROTECTION, + OverkizCommandParam.ECO: PRESET_ECO, + OverkizCommandParam.COMFORT: PRESET_COMFORT, +} + +PRESET_MODES_TO_OVERKIZ = {v: k for k, v in OVERKIZ_TO_PRESET_MODES.items()} + + +class AtlanticElectricalHeater(OverkizEntity, ClimateEntity): + """Representation of Atlantic Electrical Heater.""" + + _attr_hvac_modes = [*HVAC_MODES_TO_OVERKIZ] + _attr_preset_modes = [*PRESET_MODES_TO_OVERKIZ] + _attr_supported_features = SUPPORT_PRESET_MODE + _attr_temperature_unit = TEMP_CELSIUS + + @property + def hvac_mode(self) -> str: + """Return hvac operation ie. heat, cool mode.""" + return OVERKIZ_TO_HVAC_MODES[ + cast(str, self.executor.select_state(OverkizState.CORE_ON_OFF)) + ] + + async def async_set_hvac_mode(self, hvac_mode: str) -> None: + """Set new target hvac mode.""" + await self.executor.async_execute_command( + OverkizCommand.SET_HEATING_LEVEL, HVAC_MODES_TO_OVERKIZ[hvac_mode] + ) + + @property + def preset_mode(self) -> str | None: + """Return the current preset mode, e.g., home, away, temp.""" + return OVERKIZ_TO_PRESET_MODES[ + cast(str, self.executor.select_state(OverkizState.IO_TARGET_HEATING_LEVEL)) + ] + + async def async_set_preset_mode(self, preset_mode: str) -> None: + """Set new preset mode.""" + await self.executor.async_execute_command( + OverkizCommand.SET_HEATING_LEVEL, PRESET_MODES_TO_OVERKIZ[preset_mode] + ) diff --git a/homeassistant/components/overkiz/const.py b/homeassistant/components/overkiz/const.py index 60a9163df5f485..7f031ef3b6a27b 100644 --- a/homeassistant/components/overkiz/const.py +++ b/homeassistant/components/overkiz/const.py @@ -21,6 +21,7 @@ PLATFORMS: list[Platform] = [ Platform.BINARY_SENSOR, Platform.BUTTON, + Platform.CLIMATE, Platform.COVER, Platform.LIGHT, Platform.LOCK, @@ -57,6 +58,7 @@ UIClass.SWINGING_SHUTTER: Platform.COVER, UIClass.VENETIAN_BLIND: Platform.COVER, UIClass.WINDOW: Platform.COVER, + UIWidget.ATLANTIC_ELECTRICAL_HEATER: Platform.CLIMATE, # widgetName, uiClass is HeatingSystem (not supported) UIWidget.DOMESTIC_HOT_WATER_TANK: Platform.SWITCH, # widgetName, uiClass is WaterHeatingSystem (not supported) UIWidget.MY_FOX_SECURITY_CAMERA: Platform.SWITCH, # widgetName, uiClass is Camera (not supported) UIWidget.RTD_INDOOR_SIREN: Platform.SWITCH, # widgetName, uiClass is Siren (not supported) From 79d267f8d739c2eaab15b36f727dd155f5528798 Mon Sep 17 00:00:00 2001 From: sophof Date: Wed, 23 Feb 2022 19:16:12 +0100 Subject: [PATCH 1005/1098] Fix derivative integration showing unexpected spikes (#65528) --- homeassistant/components/derivative/sensor.py | 67 +++++++++------ tests/components/derivative/test_sensor.py | 86 +++++++++++++++++++ 2 files changed, 129 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/derivative/sensor.py b/homeassistant/components/derivative/sensor.py index 65af74981cfb73..9be01f27e4d0fe 100644 --- a/homeassistant/components/derivative/sensor.py +++ b/homeassistant/components/derivative/sensor.py @@ -1,6 +1,7 @@ """Numeric derivative of data coming from a source sensor over time.""" from __future__ import annotations +from datetime import timedelta from decimal import Decimal, DecimalException import logging @@ -112,7 +113,9 @@ def __init__( self._sensor_source_id = source_entity self._round_digits = round_digits self._state = 0 - self._state_list = [] # List of tuples with (timestamp, sensor_value) + self._state_list = ( + [] + ) # List of tuples with (timestamp_start, timestamp_end, derivative) self._name = name if name is not None else f"{source_entity} derivative" @@ -149,39 +152,32 @@ def calc_derivative(event): ): return - now = new_state.last_updated - # Filter out the tuples that are older than (and outside of the) `time_window` - self._state_list = [ - (timestamp, state) - for timestamp, state in self._state_list - if (now - timestamp).total_seconds() < self._time_window - ] - # It can happen that the list is now empty, in that case - # we use the old_state, because we cannot do anything better. - if len(self._state_list) == 0: - self._state_list.append((old_state.last_updated, old_state.state)) - self._state_list.append((new_state.last_updated, new_state.state)) - if self._unit_of_measurement is None: unit = new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) self._unit_of_measurement = self._unit_template.format( "" if unit is None else unit ) - try: - # derivative of previous measures. - last_time, last_value = self._state_list[-1] - first_time, first_value = self._state_list[0] + # filter out all derivatives older than `time_window` from our window list + self._state_list = [ + (time_start, time_end, state) + for time_start, time_end, state in self._state_list + if (new_state.last_updated - time_end).total_seconds() + < self._time_window + ] - elapsed_time = (last_time - first_time).total_seconds() - delta_value = Decimal(last_value) - Decimal(first_value) - derivative = ( + try: + elapsed_time = ( + new_state.last_updated - old_state.last_updated + ).total_seconds() + delta_value = Decimal(new_state.state) - Decimal(old_state.state) + new_derivative = ( delta_value / Decimal(elapsed_time) / Decimal(self._unit_prefix) * Decimal(self._unit_time) ) - assert isinstance(derivative, Decimal) + except ValueError as err: _LOGGER.warning("While calculating derivative: %s", err) except DecimalException as err: @@ -190,9 +186,32 @@ def calc_derivative(event): ) except AssertionError as err: _LOGGER.error("Could not calculate derivative: %s", err) + + # add latest derivative to the window list + self._state_list.append( + (old_state.last_updated, new_state.last_updated, new_derivative) + ) + + def calculate_weight(start, end, now): + window_start = now - timedelta(seconds=self._time_window) + if start < window_start: + weight = (end - window_start).total_seconds() / self._time_window + else: + weight = (end - start).total_seconds() / self._time_window + return weight + + # If outside of time window just report derivative (is the same as modeling it in the window), + # otherwise take the weighted average with the previous derivatives + if elapsed_time > self._time_window: + derivative = new_derivative else: - self._state = derivative - self.async_write_ha_state() + derivative = 0 + for (start, end, value) in self._state_list: + weight = calculate_weight(start, end, new_state.last_updated) + derivative = derivative + (value * Decimal(weight)) + + self._state = derivative + self.async_write_ha_state() async_track_state_change_event( self.hass, [self._sensor_source_id], calc_derivative diff --git a/tests/components/derivative/test_sensor.py b/tests/components/derivative/test_sensor.py index 03861a30c47b72..b93aef50774f67 100644 --- a/tests/components/derivative/test_sensor.py +++ b/tests/components/derivative/test_sensor.py @@ -1,5 +1,7 @@ """The tests for the derivative sensor platform.""" from datetime import timedelta +from math import sin +import random from unittest.mock import patch from homeassistant.const import POWER_WATT, TIME_HOURS, TIME_MINUTES, TIME_SECONDS @@ -180,6 +182,90 @@ async def test_data_moving_average_for_discrete_sensor(hass): assert abs(1 - derivative) <= 0.1 + 1e-6 +async def test_data_moving_average_for_irregular_times(hass): + """Test derivative sensor state.""" + # We simulate the following situation: + # The temperature rises 1 °C per minute for 30 minutes long. + # There is 60 random datapoints (and the start and end) and the signal is normally distributed + # around the expected value with ±0.1°C + # We use a time window of 1 minute and expect an error of less than the standard deviation. (0.01) + + time_window = 60 + random.seed(0) + times = sorted(random.sample(range(1800), 60)) + + def temp_function(time): + random.seed(0) + temp = time / (600) + return random.gauss(temp, 0.1) + + temperature_values = list(map(temp_function, times)) + + config, entity_id = await _setup_sensor( + hass, + { + "time_window": {"seconds": time_window}, + "unit_time": TIME_MINUTES, + "round": 3, + }, + ) + + base = dt_util.utcnow() + for time, value in zip(times, temperature_values): + now = base + timedelta(seconds=time) + with patch("homeassistant.util.dt.utcnow", return_value=now): + hass.states.async_set(entity_id, value, {}, force_update=True) + await hass.async_block_till_done() + + if time_window < time and time > times[3]: + state = hass.states.get("sensor.power") + derivative = round(float(state.state), config["sensor"]["round"]) + # Test that the error is never more than + # (time_window_in_minutes / true_derivative * 100) = 10% + ε + assert abs(0.1 - derivative) <= 0.01 + 1e-6 + + +async def test_double_signal_after_delay(hass): + """Test derivative sensor state.""" + # The old algorithm would produce extreme values if, after a delay longer than the time window + # there would be two signals, a large spike would be produced. Check explicitly for this situation + time_window = 60 + times = [*range(time_window * 10)] + times = times + [ + time_window * 20, + time_window * 20 + 0.01, + ] + + # just apply sine as some sort of temperature change and make sure the change after the delay is very small + temperature_values = [sin(x) for x in times] + temperature_values[-2] = temperature_values[-3] + 0.01 + temperature_values[-1] = temperature_values[-2] + 0.01 + + config, entity_id = await _setup_sensor( + hass, + { + "time_window": {"seconds": time_window}, + "unit_time": TIME_MINUTES, + "round": 3, + }, + ) + + base = dt_util.utcnow() + previous = 0 + for time, value in zip(times, temperature_values): + now = base + timedelta(seconds=time) + with patch("homeassistant.util.dt.utcnow", return_value=now): + hass.states.async_set(entity_id, value, {}, force_update=True) + await hass.async_block_till_done() + state = hass.states.get("sensor.power") + derivative = round(float(state.state), config["sensor"]["round"]) + if time == times[-1]: + # Test that the error is never more than + # (time_window_in_minutes / true_derivative * 100) = 10% + ε + assert abs(previous - derivative) <= 0.01 + 1e-6 + previous = derivative + + async def test_prefix(hass): """Test derivative sensor state using a power source.""" config = { From 8c69194695c5f1feec6385ec64cb605f177e14f5 Mon Sep 17 00:00:00 2001 From: zvldz <45265234+zvldz@users.noreply.github.com> Date: Wed, 23 Feb 2022 20:19:01 +0200 Subject: [PATCH 1006/1098] Add telegram message_tag, disable_notification, parse_mode (#63604) Co-authored-by: root --- homeassistant/components/telegram/notify.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/homeassistant/components/telegram/notify.py b/homeassistant/components/telegram/notify.py index fd7e731194c99e..b87ddc670c359d 100644 --- a/homeassistant/components/telegram/notify.py +++ b/homeassistant/components/telegram/notify.py @@ -11,6 +11,11 @@ PLATFORM_SCHEMA, BaseNotificationService, ) +from homeassistant.components.telegram_bot import ( + ATTR_DISABLE_NOTIF, + ATTR_MESSAGE_TAG, + ATTR_PARSER, +) from homeassistant.const import ATTR_LOCATION from homeassistant.helpers.reload import setup_reload_service @@ -56,6 +61,21 @@ def send_message(self, message="", **kwargs): service_data.update({ATTR_MESSAGE: message}) data = kwargs.get(ATTR_DATA) + # Set message tag + if data is not None and ATTR_MESSAGE_TAG in data: + message_tag = data.get(ATTR_MESSAGE_TAG) + service_data.update({ATTR_MESSAGE_TAG: message_tag}) + + # Set disable_notification + if data is not None and ATTR_DISABLE_NOTIF in data: + disable_notification = data.get(ATTR_DISABLE_NOTIF) + service_data.update({ATTR_DISABLE_NOTIF: disable_notification}) + + # Set parse_mode + if data is not None and ATTR_PARSER in data: + parse_mode = data.get(ATTR_PARSER) + service_data.update({ATTR_PARSER: parse_mode}) + # Get keyboard info if data is not None and ATTR_KEYBOARD in data: keys = data.get(ATTR_KEYBOARD) From f4aefdbf0b560c6dd76b54d58ca74146a71a95e2 Mon Sep 17 00:00:00 2001 From: R0nd Date: Wed, 23 Feb 2022 21:20:52 +0300 Subject: [PATCH 1007/1098] Support setting volume in lg_netcast media_player (#58126) Co-authored-by: Artem Draft --- .../components/lg_netcast/media_player.py | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/lg_netcast/media_player.py b/homeassistant/components/lg_netcast/media_player.py index 64ef4f0939fe1b..5faf6941aeb4da 100644 --- a/homeassistant/components/lg_netcast/media_player.py +++ b/homeassistant/components/lg_netcast/media_player.py @@ -20,6 +20,7 @@ SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, ) from homeassistant.const import ( @@ -48,6 +49,7 @@ SUPPORT_LGTV = ( SUPPORT_PAUSE | SUPPORT_VOLUME_STEP + | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK @@ -121,11 +123,8 @@ def update(self): try: with self._client as client: self._state = STATE_PLAYING - volume_info = client.query_data("volume_info") - if volume_info: - volume_info = volume_info[0] - self._volume = float(volume_info.find("level").text) - self._muted = volume_info.find("mute").text == "true" + + self.__update_volume() channel_info = client.query_data("cur_channel") if channel_info: @@ -160,6 +159,13 @@ def update(self): except (LgNetCastError, RequestException): self._state = STATE_OFF + def __update_volume(self): + volume_info = self._client.get_volume() + if volume_info: + (volume, muted) = volume_info + self._volume = volume + self._muted = muted + @property def name(self): """Return the name of the device.""" @@ -241,6 +247,10 @@ def volume_down(self): """Volume down media player.""" self.send_command(25) + def set_volume_level(self, volume): + """Set volume level, range 0..1.""" + self._client.set_volume(float(volume * 100)) + def mute_volume(self, mute): """Send mute command.""" self.send_command(26) From e22f8496c01d352b05015e4516715a7dc55a083a Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 23 Feb 2022 19:42:17 +0100 Subject: [PATCH 1008/1098] Allow sending telegram stickers from sticker packs (#57482) --- .../components/telegram_bot/__init__.py | 31 +++++++++++++++++-- .../components/telegram_bot/services.yaml | 6 ++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index 7c03ecab271b53..cebdd4f4573d66 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -63,6 +63,7 @@ ATTR_REPLY_TO_MSGID = "reply_to_message_id" ATTR_REPLYMARKUP = "reply_markup" ATTR_SHOW_ALERT = "show_alert" +ATTR_STICKER_ID = "sticker_id" ATTR_TARGET = "target" ATTR_TEXT = "text" ATTR_URL = "url" @@ -165,6 +166,10 @@ } ) +SERVICE_SCHEMA_SEND_STICKER = SERVICE_SCHEMA_SEND_FILE.extend( + {vol.Optional(ATTR_STICKER_ID): cv.string} +) + SERVICE_SCHEMA_SEND_LOCATION = BASE_SERVICE_SCHEMA.extend( { vol.Required(ATTR_LONGITUDE): cv.template, @@ -228,7 +233,7 @@ SERVICE_MAP = { SERVICE_SEND_MESSAGE: SERVICE_SCHEMA_SEND_MESSAGE, SERVICE_SEND_PHOTO: SERVICE_SCHEMA_SEND_FILE, - SERVICE_SEND_STICKER: SERVICE_SCHEMA_SEND_FILE, + SERVICE_SEND_STICKER: SERVICE_SCHEMA_SEND_STICKER, SERVICE_SEND_ANIMATION: SERVICE_SCHEMA_SEND_FILE, SERVICE_SEND_VIDEO: SERVICE_SCHEMA_SEND_FILE, SERVICE_SEND_VOICE: SERVICE_SCHEMA_SEND_FILE, @@ -371,7 +376,6 @@ def _render_template_attr(data, attribute): ) elif msgtype in [ SERVICE_SEND_PHOTO, - SERVICE_SEND_STICKER, SERVICE_SEND_ANIMATION, SERVICE_SEND_VIDEO, SERVICE_SEND_VOICE, @@ -380,6 +384,10 @@ def _render_template_attr(data, attribute): await hass.async_add_executor_job( partial(notify_service.send_file, msgtype, **kwargs) ) + elif msgtype == SERVICE_SEND_STICKER: + await hass.async_add_executor_job( + partial(notify_service.send_sticker, **kwargs) + ) elif msgtype == SERVICE_SEND_LOCATION: await hass.async_add_executor_job( partial(notify_service.send_location, **kwargs) @@ -797,6 +805,25 @@ def send_file(self, file_type=SERVICE_SEND_PHOTO, target=None, **kwargs): else: _LOGGER.error("Can't send file with kwargs: %s", kwargs) + def send_sticker(self, target=None, **kwargs): + """Send a sticker from a telegram sticker pack.""" + params = self._get_msg_kwargs(kwargs) + stickerid = kwargs.get(ATTR_STICKER_ID) + if stickerid: + for chat_id in self._get_target_chat_ids(target): + self._send_msg( + self.bot.send_sticker, + "Error sending sticker", + params[ATTR_MESSAGE_TAG], + chat_id=chat_id, + sticker=stickerid, + disable_notification=params[ATTR_DISABLE_NOTIF], + reply_markup=params[ATTR_REPLYMARKUP], + timeout=params[ATTR_TIMEOUT], + ) + else: + self.send_file(SERVICE_SEND_STICKER, target, **kwargs) + def send_location(self, latitude, longitude, target=None, **kwargs): """Send a location.""" latitude = float(latitude) diff --git a/homeassistant/components/telegram_bot/services.yaml b/homeassistant/components/telegram_bot/services.yaml index ea406cfdf96358..6afd42dffb81c9 100644 --- a/homeassistant/components/telegram_bot/services.yaml +++ b/homeassistant/components/telegram_bot/services.yaml @@ -181,6 +181,12 @@ send_sticker: example: "/path/to/the/sticker.webp" selector: text: + sticker_id: + name: Sticker ID + description: ID of a sticker that exists on telegram servers + example: CAACAgIAAxkBAAEDDldhZD-hqWclr6krLq-FWSfCrGNmOQAC9gAD9HsZAAFeYY-ltPYnrCEE + selector: + text: username: name: Username description: Username for a URL which require HTTP authentication. From 731f9ca7e0fabcd5cf8c37e08ce840d1a5222275 Mon Sep 17 00:00:00 2001 From: Maximilian <43999966+DeerMaximum@users.noreply.github.com> Date: Wed, 23 Feb 2022 19:45:10 +0100 Subject: [PATCH 1009/1098] Fix missing nina start value (#66869) --- homeassistant/components/nina/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/nina/__init__.py | 24 +++ .../nina/fixtures/sample_warning_details.json | 167 ++++++++++++++++++ .../nina/fixtures/sample_warnings.json | 2 +- tests/components/nina/test_binary_sensor.py | 17 +- tests/components/nina/test_init.py | 11 +- 8 files changed, 204 insertions(+), 23 deletions(-) create mode 100644 tests/components/nina/fixtures/sample_warning_details.json diff --git a/homeassistant/components/nina/manifest.json b/homeassistant/components/nina/manifest.json index 3f40668fdd548d..7c45d193bbccfb 100644 --- a/homeassistant/components/nina/manifest.json +++ b/homeassistant/components/nina/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/nina", "requirements": [ - "pynina==0.1.5" + "pynina==0.1.7" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index c6fa19cea2d272..100e1c7bcc95f8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1706,7 +1706,7 @@ pynetgear==0.9.1 pynetio==0.1.9.1 # homeassistant.components.nina -pynina==0.1.5 +pynina==0.1.7 # homeassistant.components.nuki pynuki==1.5.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b4225458e8ca11..876d70eb1a6992 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1087,7 +1087,7 @@ pymysensors==0.22.1 pynetgear==0.9.1 # homeassistant.components.nina -pynina==0.1.5 +pynina==0.1.7 # homeassistant.components.nuki pynuki==1.5.2 diff --git a/tests/components/nina/__init__.py b/tests/components/nina/__init__.py index 92697378293bd5..d6c9fffdfa7867 100644 --- a/tests/components/nina/__init__.py +++ b/tests/components/nina/__init__.py @@ -1 +1,25 @@ """Tests for the Nina integration.""" +import json +from typing import Any + +from tests.common import load_fixture + + +def mocked_request_function(url: str) -> dict[str, Any]: + """Mock of the request function.""" + dummy_response: dict[str, Any] = json.loads( + load_fixture("sample_warnings.json", "nina") + ) + + dummy_response_details: dict[str, Any] = json.loads( + load_fixture("sample_warning_details.json", "nina") + ) + + if url == "https://warnung.bund.de/api31/dashboard/083350000000.json": + return dummy_response + + warning_id = url.replace("https://warnung.bund.de/api31/warnings/", "").replace( + ".json", "" + ) + + return dummy_response_details[warning_id] diff --git a/tests/components/nina/fixtures/sample_warning_details.json b/tests/components/nina/fixtures/sample_warning_details.json new file mode 100644 index 00000000000000..61c28dc9992a3c --- /dev/null +++ b/tests/components/nina/fixtures/sample_warning_details.json @@ -0,0 +1,167 @@ +{ + "mow.DE-BW-S-SE018-20211102-18-001": { + "identifier": "mow.DE-BW-S-SE018-20211102-18-001", + "sender": "DE-NW-BN-SE030", + "sent": "2021-11-02T20:07:16+01:00", + "status": "Actual", + "msgType": "Update", + "scope": "Public", + "code": [ + "DVN:1", + "medien_ueberregional", + "nina", + "Materna:noPush", + "Materna:noMirror" + ], + "references": "DE-NW-BN-SE030-20200506-30-001 DE-NW-BN-SE030-20200422-30-000 DE-NW-BN-SE030-20200420-30-001 DE-NW-BN-SE030-20200416-30-001 DE-NW-BN-SE030-20200403-30-000 DE-NW-BN-W003,mow.DE-NW-BN-SE030-20200506-30-001 mow.DE-NW-BN-SE030-20200422-30-000 mow.DE-NW-BN-SE030-20200420-30-001 mow.DE-NW-BN-SE030-20200416-30-001 mow.DE-NW-BN-SE030-20200403-30-000 mow.DE-NW-BN-W003-20200403-000,2020-04-03T00:00:00+00:00", + "info": [ + { + "language": "DE", + "category": [ + "Health" + ], + "event": "Gefahreninformation", + "urgency": "Immediate", + "severity": "Minor", + "certainty": "Observed", + "eventCode": [ + { + "valueName": "profile:DE-BBK-EVENTCODE", + "value": "BBK-EVC-040" + } + ], + "headline": "Corona-Verordnung des Landes: Warnstufe durch Landesgesundheitsamt ausgerufen", + "description": "Die Zahl der mit dem Corona-Virus infizierten Menschen steigt gegenwärtig stark an. Es wächst daher die Gefahr einer weiteren Verbreitung der Infektion und - je nach Einzelfall - auch von schweren Erkrankungen.", + "instruction": "Waschen Sie sich regelmäßig und gründlich die Hände.
- Beachten Sie die AHA + A + L - Regeln:
Abstand halten - 1,5 m Mindestabstand beachten, Körperkontakt vermeiden!
Hygiene - regelmäßiges Händewaschen, Husten- und Nieshygiene beachten!
Alltagsmaske (Mund-Nase-Bedeckung) tragen!
App - installieren und nutzen Sie die Corona-Warn-App!
Lüften: Sorgen Sie für eine regelmäßige und gründliche Lüftung von Räumen - auch und gerade in der kommenden kalten Jahreszeit!
- Bitte folgen Sie den behördlichen Anordnungen.
- Husten und niesen Sie in ein Taschentuch oder in die Armbeuge.
- Bleiben Sie bei Erkältungssymptomen nach Möglichkeit zu Hause. Kontaktieren Sie Ihre Hausarztpraxis per Telefon oder wenden sich an die Telefonnummer 116117 des Ärztlichen Bereitschaftsdienstes und besprechen Sie das weitere Vorgehen. Gehen Sie nicht unaufgefordert in eine Arztpraxis oder ins Krankenhaus.
- Seien Sie kritisch: Informieren Sie sich nur aus gesicherten Quellen.", + "contact": "Weitere Informationen und Empfehlungen finden Sie im Corona-Informations-Bereich der Warn-App NINA. Beachten Sie auch die Internetseiten der örtlichen Gesundheitsbehörde (Stadt- bzw. Kreisverwaltung) Ihres Aufenthaltsortes", + "parameter": [ + { + "valueName": "instructionText", + "value": "- Beachten Sie die AHA + A + L - Regeln:\nAbstand halten - 1,5 m Mindestabstand beachten, Körperkontakt vermeiden! \nHygiene - regelmäßiges Händewaschen, Husten- und Nieshygiene beachten! \nAlltagsmaske (Mund-Nase-Bedeckung) tragen! \nApp - installieren und nutzen Sie die Corona-Warn-App! \nLüften: Sorgen Sie für eine regelmäßige und gründliche Lüftung von Räumen - auch und gerade in der kommenden kalten Jahreszeit! \n- Bitte folgen Sie den behördlichen Anordnungen. \n- Husten und niesen Sie in ein Taschentuch oder in die Armbeuge. \n- Bleiben Sie bei Erkältungssymptomen nach Möglichkeit zu Hause. Kontaktieren Sie Ihre Hausarztpraxis per Telefon oder wenden sich an die Telefonnummer 116117 des Ärztlichen Bereitschaftsdienstes und besprechen Sie das weitere Vorgehen. Gehen Sie nicht unaufgefordert in eine Arztpraxis oder ins Krankenhaus. \n- Seien Sie kritisch: Informieren Sie sich nur aus gesicherten Quellen." + }, + { + "valueName": "warnVerwaltungsbereiche", + "value": "130000000000,140000000000,160000000000,110000000000,020000000000,070000000000,030000000000,050000000000,080000000000,120000000000,010000000000,150000000000,040000000000,060000000000,090000000000,100000000000" + }, + { + "valueName": "instructionCode", + "value": "BBK-ISC-132" + }, + { + "valueName": "sender_langname", + "value": "BBK, Nationale Warnzentrale Bonn" + }, + { + "valueName": "sender_signature", + "value": "Bundesamt für Bevölkerungsschutz und Katastrophenhilfe\nNationale Warnzentrale Bonn\nhttps://warnung.bund.de" + }, + { + "valueName": "PHGEM", + "value": "1+11057,100001" + }, + { + "valueName": "ZGEM", + "value": "1+11057,100001" + } + ], + "area": [ + { + "areaDesc": "Bundesland: Freie Hansestadt Bremen, Land Berlin, Land Hessen, Land Nordrhein-Westfalen, Land Brandenburg, Freistaat Bayern, Land Mecklenburg-Vorpommern, Land Rheinland-Pfalz, Freistaat Sachsen, Land Schleswig-Holstein, Freie und Hansestadt Hamburg, Freistaat Thüringen, Land Niedersachsen, Land Saarland, Land Sachsen-Anhalt, Land Baden-Württemberg", + "geocode": [ + { + "valueName": "AreaId", + "value": "0" + } + ] + } + ] + } + ] + }, + "mow.DE-NW-BN-SE030-20201014-30-000" : { + "identifier": "mow.DE-NW-BN-SE030-20201014-30-000", + "sender": "opendata@dwd.de", + "sent": "2021-10-11T05:20:00+01:00", + "status": "Actual", + "msgType": "Alert", + "source": "PVW", + "scope": "Public", + "code": [ + "DVN:2", + "id:2.49.0.0.276.0.DWD.PVW.1645004040000.5a168da8-ac20-4b6d-86be-d616526a7914" + ], + "info": [ + { + "language": "de-DE", + "category": [ + "Met" + ], + "event": "STURMBÖEN", + "responseType": [ + "Prepare" + ], + "urgency": "Immediate", + "severity": "Moderate", + "certainty": "Likely", + "eventCode": [ + { + "valueName": "PROFILE_VERSION", + "value": "2.1.11" + }, + { + "valueName": "LICENSE", + "value": "© GeoBasis-DE / BKG 2019 (Daten modifiziert)" + }, + { + "valueName": "II", + "value": "52" + }, + { + "valueName": "GROUP", + "value": "WIND" + }, + { + "valueName": "AREA_COLOR", + "value": "251 140 0" + } + ], + "effective": "2021-11-01T03:20:00+01:00", + "onset": "2021-11-01T05:20:00+01:00", + "expires": "3021-11-22T05:19:00+01:00", + "senderName": "Deutscher Wetterdienst", + "headline": "Ausfall Notruf 112", + "description": "Es treten Sturmböen mit Geschwindigkeiten zwischen 70 km/h (20m/s, 38kn, Bft 8) und 85 km/h (24m/s, 47kn, Bft 9) aus westlicher Richtung auf. In Schauernähe sowie in exponierten Lagen muss mit schweren Sturmböen bis 90 km/h (25m/s, 48kn, Bft 10) gerechnet werden.", + "instruction": "ACHTUNG! Hinweis auf mögliche Gefahren: Es können zum Beispiel einzelne Äste herabstürzen. Achten Sie besonders auf herabfallende Gegenstände.", + "web": "https://www.wettergefahren.de", + "contact": "Deutscher Wetterdienst", + "parameter": [ + { + "valueName": "gusts", + "value": "70-85 [km/h]" + }, + { + "valueName": "exposed gusts", + "value": "<90 [km/h]" + }, + { + "valueName": "wind direction", + "value": "west" + }, + { + "valueName": "PHGEM", + "value": "3243+168,3413+1,3424+52,3478+1,3495+2,3499,3639+2527,6168+1,6175+22,6199+36,6238,6241+7,6256,9956+184,10142,10154,10164+7,10173,10176+6,10186+1,10195+2,10199,10201+6,10214+4,10220,10249+117,10368,10373+2,10425+9,10436+1,10440+8,10450+1,10453+7,10462+1,10467+5,10474+2,10484+5,10773+68,10843+2,10847+9,10858,10867+8,10878+1,10882+68,10952+7,10961+2,11046,11056+1" + }, + { + "valueName": "ZGEM", + "value": "3243+168,3413+1,3424+52,3478+1,3495+2,3499,3639+2527,6168+1,6175+22,6199+36,6238,6241+7,6256,9956+184,10142,10154,10164+7,10173,10176+6,10186+1,10195+2,10199,10201+6,10214+4,10220,10249+117,10368,10373+2,10425+9,10436+1,10440+8,10450+1,10453+7,10462+1,10467+5,10474+2,10484+5,10773+68,10843+2,10847+9,10858,10867+8,10878+1,10882+68,10952+7,10961+2,11046,11056+1" + } + ], + "area": [ + { + "areaDesc": "Gemeinde Oberreichenbach, Gemeinde Neuweiler, Stadt Nagold, Stadt Neubulach, Gemeinde Schömberg, Gemeinde Simmersfeld, Gemeinde Simmozheim, Gemeinde Rohrdorf, Gemeinde Ostelsheim, Gemeinde Ebhausen, Gemeinde Egenhausen, Gemeinde Dobel, Stadt Bad Liebenzell, Stadt Solingen, Stadt Haiterbach, Stadt Bad Herrenalb, Gemeinde Höfen an der Enz, Gemeinde Gechingen, Gemeinde Enzklösterle, Gemeinde Gutach (Schwarzwaldbahn) und 3392 weitere." + } + ] + } + ] + } +} \ No newline at end of file diff --git a/tests/components/nina/fixtures/sample_warnings.json b/tests/components/nina/fixtures/sample_warnings.json index d53fecffa63208..b49e436ef8b56a 100644 --- a/tests/components/nina/fixtures/sample_warnings.json +++ b/tests/components/nina/fixtures/sample_warnings.json @@ -37,7 +37,7 @@ } }, "i18nTitle": {"de": "Ausfall Notruf 112"}, - "start": "2021-11-01T05:20:00+01:00", + "onset": "2021-11-01T05:20:00+01:00", "sent": "2021-10-11T05:20:00+01:00", "expires": "3021-11-22T05:19:00+01:00" } diff --git a/tests/components/nina/test_binary_sensor.py b/tests/components/nina/test_binary_sensor.py index ebdd7ed4105ea4..d0a65e190f0f13 100644 --- a/tests/components/nina/test_binary_sensor.py +++ b/tests/components/nina/test_binary_sensor.py @@ -1,5 +1,4 @@ """Test the Nina binary sensor.""" -import json from typing import Any from unittest.mock import patch @@ -17,7 +16,9 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er -from tests.common import MockConfigEntry, load_fixture +from . import mocked_request_function + +from tests.common import MockConfigEntry ENTRY_DATA: dict[str, Any] = { "slots": 5, @@ -35,13 +36,9 @@ async def test_sensors(hass: HomeAssistant) -> None: """Test the creation and values of the NINA sensors.""" - dummy_response: dict[str, Any] = json.loads( - load_fixture("sample_warnings.json", "nina") - ) - with patch( "pynina.baseApi.BaseAPI._makeRequest", - return_value=dummy_response, + wraps=mocked_request_function, ): conf_entry: MockConfigEntry = MockConfigEntry( @@ -125,13 +122,9 @@ async def test_sensors(hass: HomeAssistant) -> None: async def test_sensors_without_corona_filter(hass: HomeAssistant) -> None: """Test the creation and values of the NINA sensors without the corona filter.""" - dummy_response: dict[str, Any] = json.loads( - load_fixture("nina/sample_warnings.json") - ) - with patch( "pynina.baseApi.BaseAPI._makeRequest", - return_value=dummy_response, + wraps=mocked_request_function, ): conf_entry: MockConfigEntry = MockConfigEntry( diff --git a/tests/components/nina/test_init.py b/tests/components/nina/test_init.py index 455d7465a8776e..66e8edb2806752 100644 --- a/tests/components/nina/test_init.py +++ b/tests/components/nina/test_init.py @@ -1,5 +1,4 @@ """Test the Nina init file.""" -import json from typing import Any from unittest.mock import patch @@ -10,7 +9,9 @@ from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry, load_fixture +from . import mocked_request_function + +from tests.common import MockConfigEntry ENTRY_DATA: dict[str, Any] = { "slots": 5, @@ -22,13 +23,9 @@ async def init_integration(hass) -> MockConfigEntry: """Set up the NINA integration in Home Assistant.""" - dummy_response: dict[str, Any] = json.loads( - load_fixture("sample_warnings.json", "nina") - ) - with patch( "pynina.baseApi.BaseAPI._makeRequest", - return_value=dummy_response, + wraps=mocked_request_function, ): entry: MockConfigEntry = MockConfigEntry( From cb070f3138916fa37d99bf972c86389cf59d07e7 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Wed, 23 Feb 2022 10:46:08 -0800 Subject: [PATCH 1010/1098] Fix RTS device delays in Overkiz integration (#67124) --- homeassistant/components/overkiz/executor.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/overkiz/executor.py b/homeassistant/components/overkiz/executor.py index cd39afd96961ba..2e9b0080815480 100644 --- a/homeassistant/components/overkiz/executor.py +++ b/homeassistant/components/overkiz/executor.py @@ -4,12 +4,22 @@ from typing import Any from urllib.parse import urlparse -from pyoverkiz.enums.command import OverkizCommand +from pyoverkiz.enums import OverkizCommand, Protocol from pyoverkiz.models import Command, Device from pyoverkiz.types import StateType as OverkizStateType from .coordinator import OverkizDataUpdateCoordinator +# Commands that don't support setting +# the delay to another value +COMMANDS_WITHOUT_DELAY = [ + OverkizCommand.IDENTIFY, + OverkizCommand.OFF, + OverkizCommand.ON, + OverkizCommand.ON_WITH_TIMER, + OverkizCommand.TEST, +] + class OverkizExecutor: """Representation of an Overkiz device with execution handler.""" @@ -58,6 +68,14 @@ def select_attribute(self, *attributes: str) -> OverkizStateType: async def async_execute_command(self, command_name: str, *args: Any) -> None: """Execute device command in async context.""" + # Set the execution duration to 0 seconds for RTS devices on supported commands + # Default execution duration is 30 seconds and will block consecutive commands + if ( + self.device.protocol == Protocol.RTS + and command_name not in COMMANDS_WITHOUT_DELAY + ): + args = args + (0,) + exec_id = await self.coordinator.client.execute_command( self.device.device_url, Command(command_name, list(args)), From 2dd14f8e94ac826e5a7d1257e2a275c82d846449 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Wed, 23 Feb 2022 19:59:12 +0100 Subject: [PATCH 1011/1098] Add mysensors remove device support (#67128) --- .../components/mysensors/__init__.py | 18 ++++++ homeassistant/components/mysensors/device.py | 8 +-- tests/components/mysensors/conftest.py | 7 +++ tests/components/mysensors/test_init.py | 57 ++++++++++++++++++- 4 files changed, 82 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index 0bf4bb80a18ef8..4d3c3046a89ae4 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -15,6 +15,7 @@ from homeassistant.const import CONF_OPTIMISTIC, Platform from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import ConfigType @@ -264,6 +265,23 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return True +async def async_remove_config_entry_device( + hass: HomeAssistant, config_entry: ConfigEntry, device_entry: DeviceEntry +) -> bool: + """Remove a MySensors config entry from a device.""" + gateway: BaseAsyncGateway = hass.data[DOMAIN][MYSENSORS_GATEWAYS][ + config_entry.entry_id + ] + device_id = next( + device_id for domain, device_id in device_entry.identifiers if domain == DOMAIN + ) + node_id = int(device_id.partition("-")[2]) + gateway.sensors.pop(node_id, None) + gateway.tasks.persistence.need_save = True + + return True + + @callback def setup_mysensors_platform( hass: HomeAssistant, diff --git a/homeassistant/components/mysensors/device.py b/homeassistant/components/mysensors/device.py index 0ced1520758071..b1e562e878c96f 100644 --- a/homeassistant/components/mysensors/device.py +++ b/homeassistant/components/mysensors/device.py @@ -65,10 +65,6 @@ def dev_id(self) -> DevId: """ return self.gateway_id, self.node_id, self.child_id, self.value_type - @property - def _logger(self) -> logging.Logger: - return logging.getLogger(f"{__name__}.{self.name}") - async def async_will_remove_from_hass(self) -> None: """Remove this entity from home assistant.""" for platform in PLATFORM_TYPES: @@ -77,9 +73,7 @@ async def async_will_remove_from_hass(self) -> None: platform_dict = self.hass.data[DOMAIN][platform_str] if self.dev_id in platform_dict: del platform_dict[self.dev_id] - self._logger.debug( - "deleted %s from platform %s", self.dev_id, platform - ) + _LOGGER.debug("Deleted %s from platform %s", self.dev_id, platform) @property def _node(self) -> Sensor: diff --git a/tests/components/mysensors/conftest.py b/tests/components/mysensors/conftest.py index 6dd7add37e6c9a..fe98b3f7e0a651 100644 --- a/tests/components/mysensors/conftest.py +++ b/tests/components/mysensors/conftest.py @@ -6,6 +6,7 @@ from typing import Any from unittest.mock import MagicMock, patch +from mysensors import BaseSyncGateway from mysensors.persistence import MySensorsJSONDecoder from mysensors.sensor import Sensor import pytest @@ -142,6 +143,12 @@ def receive_message(message_string: str) -> None: yield config_entry, receive_message +@pytest.fixture(name="gateway") +def gateway_fixture(transport, integration) -> BaseSyncGateway: + """Return a setup gateway.""" + return transport.call_args[0][0] + + def load_nodes_state(fixture_path: str) -> dict: """Load mysensors nodes fixture.""" return json.loads(load_fixture(fixture_path), cls=MySensorsJSONDecoder) diff --git a/tests/components/mysensors/test_init.py b/tests/components/mysensors/test_init.py index 7c83334d8f3b9c..6f97c312ec0cd6 100644 --- a/tests/components/mysensors/test_init.py +++ b/tests/components/mysensors/test_init.py @@ -1,9 +1,13 @@ """Test function in __init__.py.""" from __future__ import annotations -from typing import Any +from collections.abc import Callable +from typing import Any, Awaitable from unittest.mock import patch +from aiohttp import ClientWebSocketResponse +from mysensors import BaseSyncGateway +from mysensors.sensor import Sensor import pytest from homeassistant.components.mysensors import ( @@ -27,9 +31,12 @@ CONF_TOPIC_OUT_PREFIX, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.typing import ConfigType from homeassistant.setup import async_setup_component +from tests.common import MockConfigEntry + @pytest.mark.parametrize( "config, expected_calls, expected_to_succeed, expected_config_entry_data", @@ -347,3 +354,51 @@ async def test_import( persistence_path = config_entry_data.pop(CONF_PERSISTENCE_FILE) assert persistence_path == expected_persistence_path assert config_entry_data == expected_config_entry_data[idx] + + +async def test_remove_config_entry_device( + hass: HomeAssistant, + gps_sensor: Sensor, + integration: tuple[MockConfigEntry, Callable[[str], None]], + gateway: BaseSyncGateway, + hass_ws_client: Callable[[HomeAssistant], Awaitable[ClientWebSocketResponse]], +) -> None: + """Test that a device can be removed ok.""" + entity_id = "sensor.gps_sensor_1_1" + node_id = 1 + config_entry, _ = integration + assert await async_setup_component(hass, "config", {}) + await hass.async_block_till_done() + + device_registry = dr.async_get(hass) + device_entry = device_registry.async_get_device( + identifiers={(DOMAIN, f"{config_entry.entry_id}-{node_id}")} + ) + entity_registry = er.async_get(hass) + state = hass.states.get(entity_id) + + assert gateway.sensors + assert gateway.sensors[node_id] + assert device_entry + assert state + + client = await hass_ws_client(hass) + await client.send_json( + { + "id": 5, + "type": "config/device_registry/remove_config_entry", + "config_entry_id": config_entry.entry_id, + "device_id": device_entry.id, + } + ) + response = await client.receive_json() + assert response["success"] + await hass.async_block_till_done() + + assert node_id not in gateway.sensors + assert gateway.tasks.persistence.need_save is True + assert not device_registry.async_get_device( + identifiers={(DOMAIN, f"{config_entry.entry_id}-1")} + ) + assert not entity_registry.async_get(entity_id) + assert not hass.states.get(entity_id) From 9906717e33ce007249910e4d00854d343a9c05a1 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Wed, 23 Feb 2022 20:17:48 +0100 Subject: [PATCH 1012/1098] Use opt in device removal for rfxtrx (#58252) --- homeassistant/components/rfxtrx/__init__.py | 52 ++++-- .../components/rfxtrx/config_flow.py | 28 ---- homeassistant/components/rfxtrx/const.py | 1 - homeassistant/components/rfxtrx/strings.json | 3 +- tests/components/rfxtrx/conftest.py | 28 +++- tests/components/rfxtrx/test_config_flow.py | 152 ------------------ tests/components/rfxtrx/test_init.py | 61 ++++--- 7 files changed, 104 insertions(+), 221 deletions(-) diff --git a/homeassistant/components/rfxtrx/__init__.py b/homeassistant/components/rfxtrx/__init__.py index 6700e66a94124c..2dcfe639a64159 100644 --- a/homeassistant/components/rfxtrx/__init__.py +++ b/homeassistant/components/rfxtrx/__init__.py @@ -5,7 +5,6 @@ import binascii from collections.abc import Callable import copy -import functools import logging from typing import NamedTuple @@ -25,9 +24,13 @@ EVENT_HOMEASSISTANT_STOP, Platform, ) -from homeassistant.core import HomeAssistant, ServiceCall, callback +from homeassistant.core import Event, HomeAssistant, ServiceCall, callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.device_registry import DeviceRegistry +from homeassistant.helpers.device_registry import ( + EVENT_DEVICE_REGISTRY_UPDATED, + DeviceEntry, + DeviceRegistry, +) from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.restore_state import RestoreEntity @@ -37,7 +40,6 @@ COMMAND_GROUP_LIST, CONF_AUTOMATIC_ADD, CONF_DATA_BITS, - CONF_REMOVE_DEVICE, DATA_RFXOBJECT, DEVICE_PACKET_TYPE_LIGHTING4, EVENT_RFXTRX_EVENT, @@ -82,7 +84,9 @@ def _bytearray_string(data): ] -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: +async def async_setup_entry( + hass: HomeAssistant, entry: config_entries.ConfigEntry +) -> bool: """Set up the RFXtrx component.""" hass.data.setdefault(DOMAIN, {}) @@ -224,6 +228,27 @@ def _add_device(event, device_id): hass.config_entries.async_update_entry(entry=entry, data=data) devices[device_id] = config + @callback + def _remove_device(event: Event): + if event.data["action"] != "remove": + return + device_entry = device_registry.deleted_devices[event.data["device_id"]] + device_id = next(iter(device_entry.identifiers))[1:] + data = { + **entry.data, + CONF_DEVICES: { + packet_id: entity_info + for packet_id, entity_info in entry.data[CONF_DEVICES].items() + if tuple(entity_info.get(CONF_DEVICE_ID)) != device_id + }, + } + hass.config_entries.async_update_entry(entry=entry, data=data) + devices.pop(device_id) + + entry.async_on_unload( + hass.bus.async_listen(EVENT_DEVICE_REGISTRY_UPDATED, _remove_device) + ) + def _shutdown_rfxtrx(event): """Close connection with RFXtrx.""" rfx_object.close_connection() @@ -388,6 +413,16 @@ def get_device_id( return DeviceTuple(f"{device.packettype:x}", f"{device.subtype:x}", id_string) +async def async_remove_config_entry_device( + hass: HomeAssistant, config_entry: ConfigEntry, device_entry: DeviceEntry +) -> bool: + """Remove config entry from a device. + + The actual cleanup is done in the device registry event + """ + return True + + class RfxtrxEntity(RestoreEntity): """Represents a Rfxtrx device. @@ -424,13 +459,6 @@ async def async_added_to_hass(self): ) ) - self.async_on_remove( - self.hass.helpers.dispatcher.async_dispatcher_connect( - f"{DOMAIN}_{CONF_REMOVE_DEVICE}_{self._device_id}", - functools.partial(self.async_remove, force_remove=True), - ) - ) - @property def should_poll(self): """No polling needed for a RFXtrx switch.""" diff --git a/homeassistant/components/rfxtrx/config_flow.py b/homeassistant/components/rfxtrx/config_flow.py index 1ec74c2415a117..7a842ad470ce76 100644 --- a/homeassistant/components/rfxtrx/config_flow.py +++ b/homeassistant/components/rfxtrx/config_flow.py @@ -23,7 +23,6 @@ CONF_TYPE, ) from homeassistant.core import callback -from homeassistant.helpers import config_validation as cv from homeassistant.helpers.device_registry import ( DeviceEntry, DeviceRegistry, @@ -41,7 +40,6 @@ CONF_AUTOMATIC_ADD, CONF_DATA_BITS, CONF_OFF_DELAY, - CONF_REMOVE_DEVICE, CONF_REPLACE_DEVICE, CONF_SIGNAL_REPETITIONS, CONF_VENETIAN_BLIND_MODE, @@ -110,26 +108,6 @@ async def async_step_prompt_options(self, user_input=None): ] self._selected_device_object = get_rfx_object(event_code) return await self.async_step_set_device_options() - if CONF_REMOVE_DEVICE in user_input: - remove_devices = user_input[CONF_REMOVE_DEVICE] - devices = {} - for entry_id in remove_devices: - device_data = self._get_device_data(entry_id) - - event_code = device_data[CONF_EVENT_CODE] - device_id = device_data[CONF_DEVICE_ID] - self.hass.helpers.dispatcher.async_dispatcher_send( - f"{DOMAIN}_{CONF_REMOVE_DEVICE}_{device_id}" - ) - self._device_registry.async_remove_device(entry_id) - if event_code is not None: - devices[event_code] = None - - self.update_config_data( - global_options=self._global_options, devices=devices - ) - - return self.async_create_entry(title="", data={}) if CONF_EVENT_CODE in user_input: self._selected_device_event_code = user_input[CONF_EVENT_CODE] self._selected_device = {} @@ -156,11 +134,6 @@ async def async_step_prompt_options(self, user_input=None): self._device_registry = device_registry self._device_entries = device_entries - remove_devices = { - entry.id: entry.name_by_user if entry.name_by_user else entry.name - for entry in device_entries - } - configure_devices = { entry.id: entry.name_by_user if entry.name_by_user else entry.name for entry in device_entries @@ -174,7 +147,6 @@ async def async_step_prompt_options(self, user_input=None): ): bool, vol.Optional(CONF_EVENT_CODE): str, vol.Optional(CONF_DEVICE): vol.In(configure_devices), - vol.Optional(CONF_REMOVE_DEVICE): cv.multi_select(remove_devices), } return self.async_show_form( diff --git a/homeassistant/components/rfxtrx/const.py b/homeassistant/components/rfxtrx/const.py index b7cb52df98428d..50cd355c457996 100644 --- a/homeassistant/components/rfxtrx/const.py +++ b/homeassistant/components/rfxtrx/const.py @@ -6,7 +6,6 @@ CONF_OFF_DELAY = "off_delay" CONF_VENETIAN_BLIND_MODE = "venetian_blind_mode" -CONF_REMOVE_DEVICE = "remove_device" CONF_REPLACE_DEVICE = "replace_device" CONST_VENETIAN_BLIND_MODE_DEFAULT = "Unknown" diff --git a/homeassistant/components/rfxtrx/strings.json b/homeassistant/components/rfxtrx/strings.json index eb3a9ba699c5a0..542ff9a45cdcb6 100644 --- a/homeassistant/components/rfxtrx/strings.json +++ b/homeassistant/components/rfxtrx/strings.json @@ -42,8 +42,7 @@ "debug": "Enable debugging", "automatic_add": "Enable automatic add", "event_code": "Enter event code to add", - "device": "Select device to configure", - "remove_device": "Select device to delete" + "device": "Select device to configure" }, "title": "Rfxtrx Options" }, diff --git a/tests/components/rfxtrx/conftest.py b/tests/components/rfxtrx/conftest.py index 06e37545d252b9..70de0be5937c07 100644 --- a/tests/components/rfxtrx/conftest.py +++ b/tests/components/rfxtrx/conftest.py @@ -1,4 +1,6 @@ """Common test tools.""" +from __future__ import annotations + from datetime import timedelta from unittest.mock import patch @@ -24,6 +26,23 @@ def create_rfx_test_cfg(device="abcd", automatic_add=False, devices=None): } +async def setup_rfx_test_cfg( + hass, device="abcd", automatic_add=False, devices: dict[str, dict] | None = None +): + """Construct a rfxtrx config entry.""" + entry_data = create_rfx_test_cfg( + device=device, automatic_add=automatic_add, devices=devices + ) + mock_entry = MockConfigEntry(domain="rfxtrx", unique_id=DOMAIN, data=entry_data) + mock_entry.supports_remove_device = True + mock_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + await hass.async_start() + return mock_entry + + @pytest.fixture(autouse=True, name="rfxtrx") async def rfxtrx_fixture(hass): """Fixture that cleans up threads from integration.""" @@ -50,14 +69,7 @@ async def _signal_event(packet_id): @pytest.fixture(name="rfxtrx_automatic") async def rfxtrx_automatic_fixture(hass, rfxtrx): """Fixture that starts up with automatic additions.""" - entry_data = create_rfx_test_cfg(automatic_add=True, devices={}) - mock_entry = MockConfigEntry(domain="rfxtrx", unique_id=DOMAIN, data=entry_data) - - mock_entry.add_to_hass(hass) - - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() - await hass.async_start() + await setup_rfx_test_cfg(hass, automatic_add=True, devices={}) yield rfxtrx diff --git a/tests/components/rfxtrx/test_config_flow.py b/tests/components/rfxtrx/test_config_flow.py index cd87f02c89399b..bee9ea4880abd7 100644 --- a/tests/components/rfxtrx/test_config_flow.py +++ b/tests/components/rfxtrx/test_config_flow.py @@ -408,88 +408,6 @@ async def test_options_add_duplicate_device(hass): assert result["errors"]["event_code"] == "already_configured_device" -async def test_options_add_remove_device(hass): - """Test we can add a device.""" - - entry = MockConfigEntry( - domain=DOMAIN, - data={ - "host": None, - "port": None, - "device": "/dev/tty123", - "automatic_add": False, - "devices": {}, - }, - unique_id=DOMAIN, - ) - entry.add_to_hass(hass) - - result = await hass.config_entries.options.async_init(entry.entry_id) - - assert result["type"] == "form" - assert result["step_id"] == "prompt_options" - - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={ - "automatic_add": True, - "event_code": "0b1100cd0213c7f230010f71", - }, - ) - - assert result["type"] == "form" - assert result["step_id"] == "set_device_options" - - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={"signal_repetitions": 5, "off_delay": "4"}, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - - await hass.async_block_till_done() - - assert entry.data["automatic_add"] - - assert entry.data["devices"]["0b1100cd0213c7f230010f71"] - assert entry.data["devices"]["0b1100cd0213c7f230010f71"]["signal_repetitions"] == 5 - assert entry.data["devices"]["0b1100cd0213c7f230010f71"]["off_delay"] == 4 - - state = hass.states.get("binary_sensor.ac_213c7f2_48") - assert state - assert state.state == STATE_UNKNOWN - assert state.attributes.get("friendly_name") == "AC 213c7f2:48" - - device_registry = dr.async_get(hass) - device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id) - - assert device_entries[0].id - - result = await hass.config_entries.options.async_init(entry.entry_id) - - assert result["type"] == "form" - assert result["step_id"] == "prompt_options" - - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={ - "automatic_add": False, - "remove_device": [device_entries[0].id], - }, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - - await hass.async_block_till_done() - - assert not entry.data["automatic_add"] - - assert "0b1100cd0213c7f230010f71" not in entry.data["devices"] - - state = hass.states.get("binary_sensor.ac_213c7f2_48") - assert not state - - async def test_options_replace_sensor_device(hass): """Test we can replace a sensor device.""" @@ -758,76 +676,6 @@ async def test_options_replace_control_device(hass): assert not state -async def test_options_remove_multiple_devices(hass): - """Test we can add a device.""" - - entry = MockConfigEntry( - domain=DOMAIN, - data={ - "host": None, - "port": None, - "device": "/dev/tty123", - "automatic_add": False, - "devices": { - "0b1100cd0213c7f230010f71": {"device_id": ["11", "0", "213c7f2:48"]}, - "0b1100100118cdea02010f70": {"device_id": ["11", "0", "118cdea:2"]}, - "0b1100101118cdea02010f70": {"device_id": ["11", "0", "1118cdea:2"]}, - }, - }, - unique_id=DOMAIN, - ) - entry.add_to_hass(hass) - - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - state = hass.states.get("binary_sensor.ac_213c7f2_48") - assert state - state = hass.states.get("binary_sensor.ac_118cdea_2") - assert state - state = hass.states.get("binary_sensor.ac_1118cdea_2") - assert state - - device_registry = dr.async_get(hass) - device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id) - - assert len(device_entries) == 3 - - def match_device_id(entry): - device_id = next(iter(entry.identifiers))[1:] - if device_id == ("11", "0", "213c7f2:48"): - return True - if device_id == ("11", "0", "118cdea:2"): - return True - return False - - remove_devices = [elem.id for elem in device_entries if match_device_id(elem)] - - result = await hass.config_entries.options.async_init(entry.entry_id) - - assert result["type"] == "form" - assert result["step_id"] == "prompt_options" - - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={ - "automatic_add": False, - "remove_device": remove_devices, - }, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - - await hass.async_block_till_done() - - state = hass.states.get("binary_sensor.ac_213c7f2_48") - assert not state - state = hass.states.get("binary_sensor.ac_118cdea_2") - assert not state - state = hass.states.get("binary_sensor.ac_1118cdea_2") - assert state - - async def test_options_add_and_configure_device(hass): """Test we can add a device.""" diff --git a/tests/components/rfxtrx/test_init.py b/tests/components/rfxtrx/test_init.py index 4ad5f9a342af6a..d5562fa51496cf 100644 --- a/tests/components/rfxtrx/test_init.py +++ b/tests/components/rfxtrx/test_init.py @@ -1,19 +1,20 @@ """The tests for the Rfxtrx component.""" +from __future__ import annotations from unittest.mock import call -from homeassistant.components.rfxtrx import DOMAIN from homeassistant.components.rfxtrx.const import EVENT_RFXTRX_EVENT from homeassistant.core import callback from homeassistant.helpers import device_registry as dr +from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry -from tests.components.rfxtrx.conftest import create_rfx_test_cfg +from tests.components.rfxtrx.conftest import setup_rfx_test_cfg async def test_fire_event(hass, rfxtrx): """Test fire event.""" - entry_data = create_rfx_test_cfg( + await setup_rfx_test_cfg( + hass, device="/dev/serial/by-id/usb-RFXCOM_RFXtrx433_A1Y0NJGR-if00-port0", automatic_add=True, devices={ @@ -21,13 +22,6 @@ async def test_fire_event(hass, rfxtrx): "0716000100900970": {}, }, ) - mock_entry = MockConfigEntry(domain="rfxtrx", unique_id=DOMAIN, data=entry_data) - - mock_entry.add_to_hass(hass) - - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() - await hass.async_start() device_registry: dr.DeviceRegistry = dr.async_get(hass) @@ -78,13 +72,7 @@ def record_event(event): async def test_send(hass, rfxtrx): """Test configuration.""" - entry_data = create_rfx_test_cfg(device="/dev/null", devices={}) - mock_entry = MockConfigEntry(domain="rfxtrx", unique_id=DOMAIN, data=entry_data) - - mock_entry.add_to_hass(hass) - - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() + await setup_rfx_test_cfg(hass, device="/dev/null", devices={}) await hass.services.async_call( "rfxtrx", "send", {"event": "0a520802060101ff0f0269"}, blocking=True @@ -93,3 +81,40 @@ async def test_send(hass, rfxtrx): assert rfxtrx.transport.send.mock_calls == [ call(bytearray(b"\x0a\x52\x08\x02\x06\x01\x01\xff\x0f\x02\x69")) ] + + +async def test_ws_device_remove(hass, hass_ws_client): + """Test removing a device through device registry.""" + assert await async_setup_component(hass, "config", {}) + + device_id = ["11", "0", "213c7f2:16"] + mock_entry = await setup_rfx_test_cfg( + hass, + devices={ + "0b1100cd0213c7f210010f51": {"fire_event": True, "device_id": device_id}, + }, + ) + + device_reg = dr.async_get(hass) + + device_entry = device_reg.async_get_device(identifiers={("rfxtrx", *device_id)}) + assert device_entry + + # Ask to remove existing device + client = await hass_ws_client(hass) + await client.send_json( + { + "id": 5, + "type": "config/device_registry/remove_config_entry", + "config_entry_id": mock_entry.entry_id, + "device_id": device_entry.id, + } + ) + response = await client.receive_json() + assert response["success"] + + # Verify device entry is removed + assert device_reg.async_get_device(identifiers={("rfxtrx", *device_id)}) is None + + # Verify that the config entry has removed the device + assert mock_entry.data["devices"] == {} From 9fe61f9e7f745171fe74fdcce5ad4198225d7973 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Wed, 23 Feb 2022 14:18:42 -0500 Subject: [PATCH 1013/1098] Add zwave_js light support for HSM-200 V1 (#67089) Co-authored-by: Martin Hjelmare --- .../components/zwave_js/discovery.py | 23 +- homeassistant/components/zwave_js/light.py | 102 ++- tests/components/zwave_js/conftest.py | 14 + .../express_controls_ezmultipli_state.json | 673 ++++++++++++++++++ tests/components/zwave_js/test_light.py | 177 ++++- 5 files changed, 968 insertions(+), 21 deletions(-) create mode 100644 tests/components/zwave_js/fixtures/express_controls_ezmultipli_state.json diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 32c54c2b2903a0..69a3d05539b7c0 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -16,6 +16,7 @@ from zwave_js_server.const.command_class.barrier_operator import ( SIGNALING_STATE_PROPERTY, ) +from zwave_js_server.const.command_class.color_switch import CURRENT_COLOR_PROPERTY from zwave_js_server.const.command_class.humidity_control import ( HUMIDITY_CONTROL_MODE_PROPERTY, ) @@ -125,9 +126,9 @@ class ZWaveValueDiscoverySchema(DataclassMustHaveAtLeastOne): # [optional] the value's property name must match ANY of these values property_name: set[str] | None = None # [optional] the value's property key must match ANY of these values - property_key: set[str | int] | None = None + property_key: set[str | int | None] | None = None # [optional] the value's property key name must match ANY of these values - property_key_name: set[str] | None = None + property_key_name: set[str | None] | None = None # [optional] the value's metadata_type must match ANY of these values type: set[str] | None = None @@ -180,8 +181,8 @@ class ZWaveDiscoverySchema: def get_config_parameter_discovery_schema( property_: set[str | int] | None = None, property_name: set[str] | None = None, - property_key: set[str | int] | None = None, - property_key_name: set[str] | None = None, + property_key: set[str | int | None] | None = None, + property_key_name: set[str | None] | None = None, **kwargs: Any, ) -> ZWaveDiscoverySchema: """ @@ -462,6 +463,20 @@ def get_config_parameter_discovery_schema( }, ), ), + # HomeSeer HSM-200 v1 + ZWaveDiscoverySchema( + platform="light", + hint="black_is_off", + manufacturer_id={0x001E}, + product_id={0x0001}, + product_type={0x0004}, + primary_value=ZWaveValueDiscoverySchema( + command_class={CommandClass.SWITCH_COLOR}, + property={CURRENT_COLOR_PROPERTY}, + property_key={None}, + ), + absent_values=[SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA], + ), # ====== START OF CONFIG PARAMETER SPECIFIC MAPPING SCHEMAS ======= # Door lock mode config parameter. Functionality equivalent to Notification CC # list sensors. diff --git a/homeassistant/components/zwave_js/light.py b/homeassistant/components/zwave_js/light.py index caba0f5de36d6e..0f3f17df41a477 100644 --- a/homeassistant/components/zwave_js/light.py +++ b/homeassistant/components/zwave_js/light.py @@ -74,8 +74,10 @@ async def async_setup_entry( def async_add_light(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave Light.""" - light = ZwaveLight(config_entry, client, info) - async_add_entities([light]) + if info.platform_hint == "black_is_off": + async_add_entities([ZwaveBlackIsOffLight(config_entry, client, info)]) + else: + async_add_entities([ZwaveLight(config_entry, client, info)]) config_entry.async_on_unload( async_dispatcher_connect( @@ -127,7 +129,9 @@ def __init__( # get additional (optional) values and set features self._target_brightness = self.get_zwave_value( - TARGET_VALUE_PROPERTY, add_to_watched_value_ids=False + TARGET_VALUE_PROPERTY, + CommandClass.SWITCH_MULTILEVEL, + add_to_watched_value_ids=False, ) self._target_color = self.get_zwave_value( TARGET_COLOR_PROPERTY, @@ -167,14 +171,14 @@ def on_value_update(self) -> None: self._calculate_color_values() @property - def brightness(self) -> int: + def brightness(self) -> int | None: """Return the brightness of this light between 0..255. Z-Wave multilevel switches use a range of [0, 99] to control brightness. """ - if self.info.primary_value.value is not None: - return round((self.info.primary_value.value / 99) * 255) - return 0 + if self.info.primary_value.value is None: + return None + return round((self.info.primary_value.value / 99) * 255) @property def color_mode(self) -> str | None: @@ -182,9 +186,12 @@ def color_mode(self) -> str | None: return self._color_mode @property - def is_on(self) -> bool: + def is_on(self) -> bool | None: """Return true if device is on (brightness above 0).""" - return self.brightness > 0 + brightness = self.brightness + if brightness is None: + return None + return brightness > 0 @property def hs_color(self) -> tuple[float, float] | None: @@ -318,6 +325,9 @@ async def _async_set_brightness( self, brightness: int | None, transition: float | None = None ) -> None: """Set new brightness to light.""" + # If we have no target brightness value, there is nothing to do + if not self._target_brightness: + return if brightness is None: # Level 255 means to set it to previous value. zwave_brightness = 255 @@ -426,3 +436,77 @@ def _calculate_color_values(self) -> None: self._rgbw_color = (red, green, blue, white) # Light supports rgbw, set color mode to rgbw self._color_mode = COLOR_MODE_RGBW + + +class ZwaveBlackIsOffLight(ZwaveLight): + """ + Representation of a Z-Wave light where setting the color to black turns it off. + + Currently only supports lights with RGB, no color temperature, and no white channels. + """ + + def __init__( + self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo + ) -> None: + """Initialize the light.""" + super().__init__(config_entry, client, info) + + self._last_color: dict[str, int] | None = None + self._supported_color_modes.discard(COLOR_MODE_BRIGHTNESS) + + @property + def brightness(self) -> int: + """Return the brightness of this light between 0..255.""" + return 255 + + @property + def is_on(self) -> bool | None: + """Return true if device is on (brightness above 0).""" + if self.info.primary_value.value is None: + return None + return any(value != 0 for value in self.info.primary_value.value.values()) + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn the device on.""" + await super().async_turn_on(**kwargs) + + if ( + kwargs.get(ATTR_RGBW_COLOR) is not None + or kwargs.get(ATTR_COLOR_TEMP) is not None + or kwargs.get(ATTR_HS_COLOR) is not None + ): + return + + transition = kwargs.get(ATTR_TRANSITION) + # turn on light to last color if known, otherwise set to white + if self._last_color is not None: + await self._async_set_colors( + { + ColorComponent.RED: self._last_color["red"], + ColorComponent.GREEN: self._last_color["green"], + ColorComponent.BLUE: self._last_color["blue"], + }, + transition, + ) + else: + await self._async_set_colors( + { + ColorComponent.RED: 255, + ColorComponent.GREEN: 255, + ColorComponent.BLUE: 255, + }, + transition, + ) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn the light off.""" + self._last_color = self.info.primary_value.value + await self._async_set_colors( + { + ColorComponent.RED: 0, + ColorComponent.GREEN: 0, + ColorComponent.BLUE: 0, + }, + kwargs.get(ATTR_TRANSITION), + ) + await self._async_set_brightness(0, kwargs.get(ATTR_TRANSITION)) diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 318dc99a1df6e9..2535daaf1141af 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -497,6 +497,12 @@ def zp3111_state_fixture(): return json.loads(load_fixture("zwave_js/zp3111-5_state.json")) +@pytest.fixture(name="express_controls_ezmultipli_state", scope="session") +def light_express_controls_ezmultipli_state_fixture(): + """Load the Express Controls EZMultiPli node state fixture data.""" + return json.loads(load_fixture("zwave_js/express_controls_ezmultipli_state.json")) + + @pytest.fixture(name="client") def mock_client_fixture(controller_state, version_state, log_config_state): """Mock a client.""" @@ -981,3 +987,11 @@ def zp3111_fixture(client, zp3111_state): node = Node(client, copy.deepcopy(zp3111_state)) client.driver.controller.nodes[node.node_id] = node return node + + +@pytest.fixture(name="express_controls_ezmultipli") +def express_controls_ezmultipli_fixture(client, express_controls_ezmultipli_state): + """Mock a Express Controls EZMultiPli node.""" + node = Node(client, copy.deepcopy(express_controls_ezmultipli_state)) + client.driver.controller.nodes[node.node_id] = node + return node diff --git a/tests/components/zwave_js/fixtures/express_controls_ezmultipli_state.json b/tests/components/zwave_js/fixtures/express_controls_ezmultipli_state.json new file mode 100644 index 00000000000000..ea267d86b8ce3f --- /dev/null +++ b/tests/components/zwave_js/fixtures/express_controls_ezmultipli_state.json @@ -0,0 +1,673 @@ +{ + "nodeId": 96, + "index": 0, + "installerIcon": 3079, + "userIcon": 3079, + "status": 4, + "ready": true, + "isListening": true, + "isRouting": true, + "isSecure": false, + "manufacturerId": 30, + "productId": 1, + "productType": 4, + "firmwareVersion": "1.8", + "zwavePlusVersion": 1, + "name": "HSM200", + "location": "Basement", + "deviceConfig": { + "filename": "/data/db/devices/0x001e/ezmultipli.json", + "isEmbedded": true, + "manufacturer": "Express Controls", + "manufacturerId": 30, + "label": "EZMultiPli", + "description": "Multi Sensor", + "devices": [ + { + "productType": 4, + "productId": 1 + } + ], + "firmwareVersion": { + "min": "0.0", + "max": "255.255" + }, + "associations": {}, + "paramInformation": { + "_map": {} + } + }, + "label": "EZMultiPli", + "interviewAttempts": 1, + "endpoints": [ + { + "nodeId": 96, + "index": 0, + "installerIcon": 3079, + "userIcon": 3079, + "deviceClass": { + "basic": { + "key": 4, + "label": "Routing Slave" + }, + "generic": { + "key": 7, + "label": "Notification Sensor" + }, + "specific": { + "key": 1, + "label": "Notification Sensor" + }, + "mandatorySupportedCCs": [], + "mandatoryControlledCCs": [] + } + } + ], + "values": [ + { + "endpoint": 0, + "commandClass": 113, + "commandClassName": "Notification", + "property": "Home Security", + "propertyKey": "Motion sensor status", + "propertyName": "Home Security", + "propertyKeyName": "Motion sensor status", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Motion sensor status", + "ccSpecific": { + "notificationType": 7 + }, + "min": 0, + "max": 255, + "states": { + "0": "idle", + "7": "Motion detection (location provided)" + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 49, + "commandClassName": "Multilevel Sensor", + "property": "Air temperature", + "propertyName": "Air temperature", + "ccVersion": 6, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Air temperature", + "ccSpecific": { + "sensorType": 1, + "scale": 0 + }, + "unit": "\u00b0C" + }, + "value": 16.8 + }, + { + "endpoint": 0, + "commandClass": 49, + "commandClassName": "Multilevel Sensor", + "property": "Illuminance", + "propertyName": "Illuminance", + "ccVersion": 6, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Illuminance", + "ccSpecific": { + "sensorType": 3, + "scale": 0 + }, + "unit": "%" + }, + "value": 61 + }, + { + "endpoint": 0, + "commandClass": 51, + "commandClassName": "Color Switch", + "property": "duration", + "propertyName": "duration", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Remaining duration" + } + }, + { + "endpoint": 0, + "commandClass": 51, + "commandClassName": "Color Switch", + "property": "currentColor", + "propertyKey": 2, + "propertyName": "currentColor", + "propertyKeyName": "Red", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "description": "The current value of the Red color.", + "label": "Current value (Red)", + "min": 0, + "max": 255 + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 51, + "commandClassName": "Color Switch", + "property": "currentColor", + "propertyKey": 3, + "propertyName": "currentColor", + "propertyKeyName": "Green", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "description": "The current value of the Green color.", + "label": "Current value (Green)", + "min": 0, + "max": 255 + }, + "value": 255 + }, + { + "endpoint": 0, + "commandClass": 51, + "commandClassName": "Color Switch", + "property": "currentColor", + "propertyKey": 4, + "propertyName": "currentColor", + "propertyKeyName": "Blue", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "description": "The current value of the Blue color.", + "label": "Current value (Blue)", + "min": 0, + "max": 255 + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 51, + "commandClassName": "Color Switch", + "property": "currentColor", + "propertyName": "currentColor", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Current Color" + }, + "value": { + "red": 0, + "green": 255, + "blue": 0 + } + }, + { + "endpoint": 0, + "commandClass": 51, + "commandClassName": "Color Switch", + "property": "hexColor", + "propertyName": "hexColor", + "ccVersion": 1, + "metadata": { + "type": "color", + "readable": true, + "writeable": true, + "label": "RGB Color", + "minLength": 6, + "maxLength": 7 + }, + "value": "00ff00" + }, + { + "endpoint": 0, + "commandClass": 51, + "commandClassName": "Color Switch", + "property": "targetColor", + "propertyKey": 2, + "propertyName": "targetColor", + "propertyKeyName": "Red", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "The target value of the Red color.", + "label": "Target value (Red)", + "min": 0, + "max": 255 + } + }, + { + "endpoint": 0, + "commandClass": 51, + "commandClassName": "Color Switch", + "property": "targetColor", + "propertyKey": 3, + "propertyName": "targetColor", + "propertyKeyName": "Green", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "The target value of the Green color.", + "label": "Target value (Green)", + "min": 0, + "max": 255 + } + }, + { + "endpoint": 0, + "commandClass": 51, + "commandClassName": "Color Switch", + "property": "targetColor", + "propertyKey": 4, + "propertyName": "targetColor", + "propertyKeyName": "Blue", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "The target value of the Blue color.", + "label": "Target value (Blue)", + "min": 0, + "max": 255 + } + }, + { + "endpoint": 0, + "commandClass": 51, + "commandClassName": "Color Switch", + "property": "targetColor", + "propertyName": "targetColor", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": true, + "label": "Target Color", + "valueChangeOptions": ["transitionDuration"] + } + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "manufacturerId", + "propertyName": "manufacturerId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Manufacturer ID", + "min": 0, + "max": 65535 + }, + "value": 30 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productType", + "propertyName": "productType", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Product type", + "min": 0, + "max": 65535 + }, + "value": 4 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productId", + "propertyName": "productId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Product ID", + "min": 0, + "max": 65535 + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "libraryType", + "propertyName": "libraryType", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Library type", + "states": { + "0": "Unknown", + "1": "Static Controller", + "2": "Controller", + "3": "Enhanced Slave", + "4": "Slave", + "5": "Installer", + "6": "Routing Slave", + "7": "Bridge Controller", + "8": "Device under Test", + "9": "N/A", + "10": "AV Remote", + "11": "AV Device" + } + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "protocolVersion", + "propertyName": "protocolVersion", + "ccVersion": 2, + "metadata": { + "type": "string", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "4.5" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "ccVersion": 2, + "metadata": { + "type": "string[]", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": ["1.8"] + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hardwareVersion", + "propertyName": "hardwareVersion", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Z-Wave chip hardware version" + }, + "value": 2 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 1, + "propertyName": "OnTime", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "OnTime", + "default": 10, + "min": 0, + "max": 127, + "unit": "Minutes", + "valueSize": 1, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 10 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 2, + "propertyName": "OnLevel", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Allowable range: 0-99, 255", + "label": "OnLevel", + "default": 255, + "min": 0, + "max": 255, + "valueSize": 1, + "format": 1, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 255 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 3, + "propertyName": "LiteMin", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "LiteMin", + "default": 60, + "min": 0, + "max": 127, + "unit": "Minutes", + "valueSize": 1, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 60 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 4, + "propertyName": "TempMin", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "A Temperature report is sent to the controller every TempMin minutes.", + "label": "TempMin", + "default": 60, + "min": 0, + "max": 127, + "unit": "Minutes", + "valueSize": 1, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 60 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 5, + "propertyName": "TempAdj", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "TempAdj", + "default": 0, + "min": -128, + "max": 127, + "valueSize": 1, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": -40 + } + ], + "isFrequentListening": false, + "maxDataRate": 100000, + "supportedDataRates": [40000, 100000], + "protocolVersion": 3, + "supportsBeaming": true, + "supportsSecurity": false, + "nodeType": 1, + "zwavePlusNodeType": 0, + "zwavePlusRoleType": 5, + "deviceClass": { + "basic": { + "key": 4, + "label": "Routing Slave" + }, + "generic": { + "key": 7, + "label": "Notification Sensor" + }, + "specific": { + "key": 1, + "label": "Notification Sensor" + }, + "mandatorySupportedCCs": [], + "mandatoryControlledCCs": [] + }, + "commandClasses": [ + { + "id": 94, + "name": "Z-Wave Plus Info", + "version": 2, + "isSecure": false + }, + { + "id": 113, + "name": "Notification", + "version": 3, + "isSecure": false + }, + { + "id": 49, + "name": "Multilevel Sensor", + "version": 6, + "isSecure": false + }, + { + "id": 51, + "name": "Color Switch", + "version": 1, + "isSecure": false + }, + { + "id": 114, + "name": "Manufacturer Specific", + "version": 2, + "isSecure": false + }, + { + "id": 134, + "name": "Version", + "version": 2, + "isSecure": false + }, + { + "id": 89, + "name": "Association Group Information", + "version": 1, + "isSecure": false + }, + { + "id": 133, + "name": "Association", + "version": 2, + "isSecure": false + }, + { + "id": 112, + "name": "Configuration", + "version": 2, + "isSecure": false + }, + { + "id": 119, + "name": "Node Naming and Location", + "version": 1, + "isSecure": false + }, + { + "id": 90, + "name": "Device Reset Locally", + "version": 1, + "isSecure": false + }, + { + "id": 122, + "name": "Firmware Update Meta Data", + "version": 2, + "isSecure": false + }, + { + "id": 115, + "name": "Powerlevel", + "version": 1, + "isSecure": false + } + ], + "interviewStage": "Complete", + "deviceDatabaseUrl": "https://devices.zwave-js.io/?jumpTo=0x001e:0x0004:0x0001:1.8", + "statistics": { + "commandsTX": 147, + "commandsRX": 322, + "commandsDroppedRX": 0, + "commandsDroppedTX": 3, + "timeoutResponse": 0 + }, + "highestSecurityClass": -1, + "isControllerNode": false, + "keepAwake": false +} diff --git a/tests/components/zwave_js/test_light.py b/tests/components/zwave_js/test_light.py index 373ca2525acccc..01de5a70692ac4 100644 --- a/tests/components/zwave_js/test_light.py +++ b/tests/components/zwave_js/test_light.py @@ -13,9 +13,17 @@ ATTR_RGBW_COLOR, ATTR_SUPPORTED_COLOR_MODES, ATTR_TRANSITION, + DOMAIN as LIGHT_DOMAIN, SUPPORT_TRANSITION, ) -from homeassistant.const import ATTR_SUPPORTED_FEATURES, STATE_OFF, STATE_ON +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, +) from .common import ( AEON_SMART_SWITCH_LIGHT_ENTITY, @@ -24,6 +32,8 @@ ZEN_31_ENTITY, ) +HSM200_V1_ENTITY = "light.hsm200" + async def test_light(hass, client, bulb_6_multi_color, integration): """Test the light entity.""" @@ -62,7 +72,6 @@ async def test_light(hass, client, bulb_6_multi_color, integration): "type": "number", "readable": True, "writeable": True, - "label": "Target value", "valueChangeOptions": ["transitionDuration"], }, } @@ -95,7 +104,6 @@ async def test_light(hass, client, bulb_6_multi_color, integration): "type": "number", "readable": True, "writeable": True, - "label": "Target value", "valueChangeOptions": ["transitionDuration"], }, } @@ -168,7 +176,6 @@ async def test_light(hass, client, bulb_6_multi_color, integration): "type": "number", "readable": True, "writeable": True, - "label": "Target value", "valueChangeOptions": ["transitionDuration"], }, } @@ -206,7 +213,6 @@ async def test_light(hass, client, bulb_6_multi_color, integration): "type": "number", "readable": True, "writeable": True, - "label": "Target value", "valueChangeOptions": ["transitionDuration"], }, } @@ -444,7 +450,6 @@ async def test_light(hass, client, bulb_6_multi_color, integration): "type": "number", "readable": True, "writeable": True, - "label": "Target value", "valueChangeOptions": ["transitionDuration"], }, } @@ -469,7 +474,6 @@ async def test_optional_light(hass, client, aeon_smart_switch_6, integration): async def test_rgbw_light(hass, client, zen_31, integration): """Test the light entity.""" - zen_31 state = hass.states.get(ZEN_31_ENTITY) assert state @@ -523,7 +527,6 @@ async def test_rgbw_light(hass, client, zen_31, integration): "type": "number", "readable": True, "writeable": True, - "label": "Target value", "valueChangeOptions": ["transitionDuration"], }, "value": 59, @@ -542,3 +545,161 @@ async def test_light_none_color_value(hass, light_color_null_values, integration assert state.state == STATE_ON assert state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_TRANSITION assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["hs"] + + +async def test_black_is_off(hass, client, express_controls_ezmultipli, integration): + """Test the black is off light entity.""" + node = express_controls_ezmultipli + state = hass.states.get(HSM200_V1_ENTITY) + assert state.state == STATE_ON + + # Attempt to turn on the light and ensure it defaults to white + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: HSM200_V1_ENTITY}, + blocking=True, + ) + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args_list[0][0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == node.node_id + assert args["valueId"] == { + "commandClassName": "Color Switch", + "commandClass": 51, + "endpoint": 0, + "property": "targetColor", + "propertyName": "targetColor", + "ccVersion": 1, + "metadata": { + "label": "Target Color", + "type": "any", + "readable": True, + "writeable": True, + "valueChangeOptions": ["transitionDuration"], + }, + } + assert args["value"] == {"red": 255, "green": 255, "blue": 255} + + client.async_send_command.reset_mock() + + # Force the light to turn off + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": node.node_id, + "args": { + "commandClassName": "Color Switch", + "commandClass": 51, + "endpoint": 0, + "property": "currentColor", + "newValue": { + "red": 0, + "green": 0, + "blue": 0, + }, + "prevValue": { + "red": 0, + "green": 255, + "blue": 0, + }, + "propertyName": "currentColor", + }, + }, + ) + node.receive_event(event) + await hass.async_block_till_done() + state = hass.states.get(HSM200_V1_ENTITY) + assert state.state == STATE_OFF + + # Force the light to turn on + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": node.node_id, + "args": { + "commandClassName": "Color Switch", + "commandClass": 51, + "endpoint": 0, + "property": "currentColor", + "newValue": { + "red": 0, + "green": 255, + "blue": 0, + }, + "prevValue": { + "red": 0, + "green": 0, + "blue": 0, + }, + "propertyName": "currentColor", + }, + }, + ) + node.receive_event(event) + await hass.async_block_till_done() + state = hass.states.get(HSM200_V1_ENTITY) + assert state.state == STATE_ON + + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: HSM200_V1_ENTITY}, + blocking=True, + ) + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args_list[0][0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == node.node_id + assert args["valueId"] == { + "commandClassName": "Color Switch", + "commandClass": 51, + "endpoint": 0, + "property": "targetColor", + "propertyName": "targetColor", + "ccVersion": 1, + "metadata": { + "label": "Target Color", + "type": "any", + "readable": True, + "writeable": True, + "valueChangeOptions": ["transitionDuration"], + }, + } + assert args["value"] == {"red": 0, "green": 0, "blue": 0} + + client.async_send_command.reset_mock() + + # Assert that the last color is restored + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: HSM200_V1_ENTITY}, + blocking=True, + ) + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args_list[0][0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == node.node_id + assert args["valueId"] == { + "commandClassName": "Color Switch", + "commandClass": 51, + "endpoint": 0, + "property": "targetColor", + "propertyName": "targetColor", + "ccVersion": 1, + "metadata": { + "label": "Target Color", + "type": "any", + "readable": True, + "writeable": True, + "valueChangeOptions": ["transitionDuration"], + }, + } + assert args["value"] == {"red": 0, "green": 255, "blue": 0} + + client.async_send_command.reset_mock() From eb4bc273af9f8690766e43565572cad5ebb344cc Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 23 Feb 2022 19:21:28 +0000 Subject: [PATCH 1014/1098] Improve Tasmota device removal (#66811) --- homeassistant/components/tasmota/__init__.py | 67 +++++++---- .../components/tasmota/device_trigger.py | 4 +- homeassistant/components/tasmota/discovery.py | 16 +-- tests/components/tasmota/test_discovery.py | 107 +++++++++++++++++- 4 files changed, 163 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/tasmota/__init__.py b/homeassistant/components/tasmota/__init__.py index 2d664bb46ee232..44dd248917780f 100644 --- a/homeassistant/components/tasmota/__init__.py +++ b/homeassistant/components/tasmota/__init__.py @@ -26,6 +26,7 @@ from homeassistant.components.websocket_api.connection import ActiveConnection from homeassistant.config_entries import ConfigEntry from homeassistant.core import Event, HomeAssistant, callback +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.device_registry import ( CONNECTION_NETWORK_MAC, EVENT_DEVICE_REGISTRY_UPDATED, @@ -72,7 +73,7 @@ async def _unsubscribe_topics(sub_state: dict | None) -> dict: tasmota_mqtt = TasmotaMQTTClient(_publish, _subscribe_topics, _unsubscribe_topics) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) async def async_discover_device(config: TasmotaDeviceConfig, mac: str) -> None: """Discover and add a Tasmota device.""" @@ -80,25 +81,40 @@ async def async_discover_device(config: TasmotaDeviceConfig, mac: str) -> None: hass, mac, config, entry, tasmota_mqtt, device_registry ) - async def async_device_removed(event: Event) -> None: + async def async_device_updated(event: Event) -> None: """Handle the removal of a device.""" - device_registry = await hass.helpers.device_registry.async_get_registry() - if event.data["action"] != "remove": + device_registry = dr.async_get(hass) + device_id = event.data["device_id"] + if event.data["action"] not in ("remove", "update"): return - device = device_registry.deleted_devices[event.data["device_id"]] - - if entry.entry_id not in device.config_entries: - return - - macs = [c[1] for c in device.connections if c[0] == CONNECTION_NETWORK_MAC] + connections: set[tuple[str, str]] + if event.data["action"] == "update": + if "config_entries" not in event.data["changes"]: + return + + device = device_registry.async_get(device_id) + if not device: + # The device is already removed, do cleanup when we get "remove" event + return + if entry.entry_id in device.config_entries: + # Not removed from device + return + connections = device.connections + else: + deleted_device = device_registry.deleted_devices[event.data["device_id"]] + connections = deleted_device.connections + if entry.entry_id not in deleted_device.config_entries: + return + + macs = [c[1] for c in connections if c[0] == CONNECTION_NETWORK_MAC] for mac in macs: await clear_discovery_topic( mac, entry.data[CONF_DISCOVERY_PREFIX], tasmota_mqtt ) hass.data[DATA_UNSUB].append( - hass.bus.async_listen(EVENT_DEVICE_REGISTRY_UPDATED, async_device_removed) + hass.bus.async_listen(EVENT_DEVICE_REGISTRY_UPDATED, async_device_updated) ) async def start_platforms() -> None: @@ -138,7 +154,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data.pop(DATA_REMOVE_DISCOVER_COMPONENT.format(platform))() # deattach device triggers - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) devices = async_entries_for_config_entry(device_registry, entry.entry_id) for device in devices: await device_automation.async_remove_automations(hass, device.id) @@ -156,11 +172,13 @@ async def _remove_device( """Remove device from device registry.""" device = device_registry.async_get_device(set(), {(CONNECTION_NETWORK_MAC, mac)}) - if device is None: + if device is None or config_entry.entry_id not in device.config_entries: return - _LOGGER.debug("Removing tasmota device %s", mac) - device_registry.async_remove_device(device.id) + _LOGGER.debug("Removing tasmota from device %s", mac) + device_registry.async_update_device( + device.id, remove_config_entry_id=config_entry.entry_id + ) await clear_discovery_topic( mac, config_entry.data[CONF_DISCOVERY_PREFIX], tasmota_mqtt ) @@ -203,13 +221,13 @@ async def async_setup_device( @websocket_api.websocket_command( {vol.Required("type"): "tasmota/device/remove", vol.Required("device_id"): str} ) -@websocket_api.async_response -async def websocket_remove_device( +@callback +def websocket_remove_device( hass: HomeAssistant, connection: ActiveConnection, msg: dict ) -> None: """Delete device.""" device_id = msg["device_id"] - dev_registry = await hass.helpers.device_registry.async_get_registry() + dev_registry = dr.async_get(hass) if not (device := dev_registry.async_get(device_id)): connection.send_error( @@ -217,8 +235,9 @@ async def websocket_remove_device( ) return - for config_entry in device.config_entries: - config_entry = hass.config_entries.async_get_entry(config_entry) + for config_entry_id in device.config_entries: + config_entry = hass.config_entries.async_get_entry(config_entry_id) + assert config_entry # Only delete the device if it belongs to a Tasmota device entry if config_entry.domain == DOMAIN: dev_registry.async_remove_device(device_id) @@ -228,3 +247,11 @@ async def websocket_remove_device( connection.send_error( msg["id"], websocket_api.const.ERR_NOT_FOUND, "Non Tasmota device" ) + + +async def async_remove_config_entry_device( + hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry +) -> bool: + """Remove Tasmota config entry from a device.""" + # Just return True, cleanup is done on when handling device registry events + return True diff --git a/homeassistant/components/tasmota/device_trigger.py b/homeassistant/components/tasmota/device_trigger.py index 61efbb76e23bb8..aca5a2848e3211 100644 --- a/homeassistant/components/tasmota/device_trigger.py +++ b/homeassistant/components/tasmota/device_trigger.py @@ -20,7 +20,7 @@ from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import ConfigType @@ -220,7 +220,7 @@ async def discovery_update(trigger_config: TasmotaTriggerConfig) -> None: hass, TASMOTA_DISCOVERY_ENTITY_UPDATED.format(*discovery_hash), discovery_update ) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get_device( set(), {(CONNECTION_NETWORK_MAC, tasmota_trigger.cfg.mac)}, diff --git a/homeassistant/components/tasmota/discovery.py b/homeassistant/components/tasmota/discovery.py index 67aea199fe4caa..da9e809bd8b788 100644 --- a/homeassistant/components/tasmota/discovery.py +++ b/homeassistant/components/tasmota/discovery.py @@ -21,7 +21,7 @@ from homeassistant.components import sensor from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from homeassistant.helpers import device_registry as dev_reg +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.entity_registry import async_entries_for_device @@ -61,7 +61,7 @@ async def async_start( ) -> None: """Start Tasmota device discovery.""" - async def _discover_entity( + def _discover_entity( tasmota_entity_config: TasmotaEntityConfig | None, discovery_hash: DiscoveryHashType, platform: str, @@ -69,7 +69,7 @@ async def _discover_entity( """Handle adding or updating a discovered entity.""" if not tasmota_entity_config: # Entity disabled, clean up entity registry - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) unique_id = unique_id_from_hash(discovery_hash) entity_id = entity_registry.async_get_entity_id(platform, DOMAIN, unique_id) if entity_id: @@ -158,7 +158,7 @@ async def async_device_discovered(payload: dict, mac: str) -> None: for platform in PLATFORMS: tasmota_entities = tasmota_get_entities_for_platform(payload, platform) for (tasmota_entity_config, discovery_hash) in tasmota_entities: - await _discover_entity(tasmota_entity_config, discovery_hash, platform) + _discover_entity(tasmota_entity_config, discovery_hash, platform) async def async_sensors_discovered( sensors: list[tuple[TasmotaBaseSensorConfig, DiscoveryHashType]], mac: str @@ -166,10 +166,10 @@ async def async_sensors_discovered( """Handle discovery of (additional) sensors.""" platform = sensor.DOMAIN - device_registry = await hass.helpers.device_registry.async_get_registry() - entity_registry = await hass.helpers.entity_registry.async_get_registry() + device_registry = dr.async_get(hass) + entity_registry = er.async_get(hass) device = device_registry.async_get_device( - set(), {(dev_reg.CONNECTION_NETWORK_MAC, mac)} + set(), {(dr.CONNECTION_NETWORK_MAC, mac)} ) if device is None: @@ -186,7 +186,7 @@ async def async_sensors_discovered( for (tasmota_sensor_config, discovery_hash) in sensors: if tasmota_sensor_config: orphaned_entities.discard(tasmota_sensor_config.unique_id) - await _discover_entity(tasmota_sensor_config, discovery_hash, platform) + _discover_entity(tasmota_sensor_config, discovery_hash, platform) for unique_id in orphaned_entities: entity_id = entity_registry.async_get_entity_id(platform, DOMAIN, unique_id) if entity_id: diff --git a/tests/components/tasmota/test_discovery.py b/tests/components/tasmota/test_discovery.py index 713d0f5ae67e37..90ca5d918fd2ef 100644 --- a/tests/components/tasmota/test_discovery.py +++ b/tests/components/tasmota/test_discovery.py @@ -10,7 +10,7 @@ from .conftest import setup_tasmota_helper from .test_common import DEFAULT_CONFIG, DEFAULT_CONFIG_9_0_0_3 -from tests.common import async_fire_mqtt_message +from tests.common import MockConfigEntry, async_fire_mqtt_message async def test_subscribing_config_topic(hass, mqtt_mock, setup_tasmota): @@ -261,6 +261,111 @@ async def test_device_remove( assert device_entry is None +async def test_device_remove_multiple_config_entries_1( + hass, mqtt_mock, caplog, device_reg, entity_reg, setup_tasmota +): + """Test removing a discovered device.""" + config = copy.deepcopy(DEFAULT_CONFIG) + mac = config["mac"] + + mock_entry = MockConfigEntry(domain="test") + mock_entry.add_to_hass(hass) + + device_reg.async_get_or_create( + config_entry_id=mock_entry.entry_id, + connections={(dr.CONNECTION_NETWORK_MAC, mac)}, + ) + + tasmota_entry = hass.config_entries.async_entries("tasmota")[0] + + async_fire_mqtt_message( + hass, + f"{DEFAULT_PREFIX}/{mac}/config", + json.dumps(config), + ) + await hass.async_block_till_done() + + # Verify device entry is created + device_entry = device_reg.async_get_device( + set(), {(dr.CONNECTION_NETWORK_MAC, mac)} + ) + assert device_entry is not None + assert device_entry.config_entries == {tasmota_entry.entry_id, mock_entry.entry_id} + + async_fire_mqtt_message( + hass, + f"{DEFAULT_PREFIX}/{mac}/config", + "", + ) + await hass.async_block_till_done() + + # Verify device entry is not removed + device_entry = device_reg.async_get_device( + set(), {(dr.CONNECTION_NETWORK_MAC, mac)} + ) + assert device_entry is not None + assert device_entry.config_entries == {mock_entry.entry_id} + + +async def test_device_remove_multiple_config_entries_2( + hass, mqtt_mock, caplog, device_reg, entity_reg, setup_tasmota +): + """Test removing a discovered device.""" + config = copy.deepcopy(DEFAULT_CONFIG) + mac = config["mac"] + + mock_entry = MockConfigEntry(domain="test") + mock_entry.add_to_hass(hass) + + device_reg.async_get_or_create( + config_entry_id=mock_entry.entry_id, + connections={(dr.CONNECTION_NETWORK_MAC, mac)}, + ) + + other_device_entry = device_reg.async_get_or_create( + config_entry_id=mock_entry.entry_id, + connections={(dr.CONNECTION_NETWORK_MAC, "other_device")}, + ) + + tasmota_entry = hass.config_entries.async_entries("tasmota")[0] + + async_fire_mqtt_message( + hass, + f"{DEFAULT_PREFIX}/{mac}/config", + json.dumps(config), + ) + await hass.async_block_till_done() + + # Verify device entry is created + device_entry = device_reg.async_get_device( + set(), {(dr.CONNECTION_NETWORK_MAC, mac)} + ) + assert device_entry is not None + assert device_entry.config_entries == {tasmota_entry.entry_id, mock_entry.entry_id} + assert other_device_entry.id != device_entry.id + + # Remove other config entry from the device + device_reg.async_update_device( + device_entry.id, remove_config_entry_id=mock_entry.entry_id + ) + await hass.async_block_till_done() + + # Verify device entry is not removed + device_entry = device_reg.async_get_device( + set(), {(dr.CONNECTION_NETWORK_MAC, mac)} + ) + assert device_entry is not None + assert device_entry.config_entries == {tasmota_entry.entry_id} + mqtt_mock.async_publish.assert_not_called() + + # Remove other config entry from the other device - Tasmota should not do any cleanup + device_reg.async_update_device( + other_device_entry.id, remove_config_entry_id=mock_entry.entry_id + ) + await hass.async_block_till_done() + mqtt_mock.async_publish.assert_not_called() + + async def test_device_remove_stale(hass, mqtt_mock, caplog, device_reg, setup_tasmota): """Test removing a stale (undiscovered) device does not throw.""" mac = "00000049A3BC" From 46c2bd0eb072c8898ec22626aabfafb8c0e976fb Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Wed, 23 Feb 2022 20:26:46 +0100 Subject: [PATCH 1015/1098] Tweak UniFi client tracker (#67129) --- .../components/unifi/device_tracker.py | 1 - tests/components/unifi/test_device_tracker.py | 21 ++----------------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index b241e07fc89458..60ea4b3284be63 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -198,7 +198,6 @@ def async_update_callback(self) -> None: elif ( self.client.last_updated == SOURCE_DATA - and self._last_seen != self.client.last_seen and self.is_wired == self.client.is_wired ): self._last_seen = self.client.last_seen diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index b490d43fffdf5f..532f19c35ae1d2 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -54,23 +54,6 @@ async def test_tracked_wireless_clients( assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME - # State change signalling works without events - - mock_unifi_websocket( - data={ - "meta": {"message": MESSAGE_CLIENT}, - "data": [client], - } - ) - await hass.async_block_till_done() - - client_state = hass.states.get("device_tracker.client") - assert client_state.state == STATE_NOT_HOME - assert client_state.attributes["ip"] == "10.0.0.1" - assert client_state.attributes["mac"] == "00:00:00:00:00:01" - assert client_state.attributes["hostname"] == "client" - assert client_state.attributes["host_name"] == "client" - # Updated timestamp marks client as home client["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) @@ -93,7 +76,7 @@ async def test_tracked_wireless_clients( assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME - # Same timestamp again means client is away + # Same timestamp doesn't explicitly mark client as away mock_unifi_websocket( data={ @@ -103,7 +86,7 @@ async def test_tracked_wireless_clients( ) await hass.async_block_till_done() - assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME + assert hass.states.get("device_tracker.client").state == STATE_HOME async def test_tracked_clients( From ec980a574b95ad159555a771ee9ae61a003e427b Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 23 Feb 2022 20:58:42 +0100 Subject: [PATCH 1016/1098] Improve typing [util.decorator] (#67087) --- .strict-typing | 1 + homeassistant/auth/mfa_modules/__init__.py | 6 +++--- homeassistant/auth/providers/__init__.py | 6 +++--- homeassistant/components/alexa/entities.py | 2 +- homeassistant/components/alexa/handlers.py | 2 +- homeassistant/components/alexa/intent.py | 2 +- homeassistant/components/filter/sensor.py | 2 +- .../components/google_assistant/smart_home.py | 2 +- homeassistant/components/homekit/accessories.py | 4 +++- homeassistant/components/konnected/handlers.py | 2 +- homeassistant/components/mobile_app/webhook.py | 2 +- homeassistant/components/mysensors/gateway.py | 6 ++---- homeassistant/components/mysensors/handler.py | 7 ++++++- homeassistant/components/mysensors/helpers.py | 4 +++- homeassistant/components/onvif/parsers.py | 5 ++++- homeassistant/components/overkiz/coordinator.py | 6 +++++- homeassistant/components/owntracks/messages.py | 2 +- homeassistant/components/stream/__init__.py | 4 ++-- homeassistant/components/stream/core.py | 2 +- homeassistant/config_entries.py | 10 ++++++---- homeassistant/helpers/selector.py | 6 +++--- homeassistant/util/decorator.py | 11 ++++++----- mypy.ini | 3 +++ 23 files changed, 59 insertions(+), 38 deletions(-) diff --git a/.strict-typing b/.strict-typing index 74a255a7f96b21..621c6c315fca2d 100644 --- a/.strict-typing +++ b/.strict-typing @@ -22,6 +22,7 @@ homeassistant.helpers.script_variables homeassistant.helpers.translation homeassistant.util.async_ homeassistant.util.color +homeassistant.util.decorator homeassistant.util.process homeassistant.util.unit_system diff --git a/homeassistant/auth/mfa_modules/__init__.py b/homeassistant/auth/mfa_modules/__init__.py index bb81d1fb04f94f..61c36da6e90320 100644 --- a/homeassistant/auth/mfa_modules/__init__.py +++ b/homeassistant/auth/mfa_modules/__init__.py @@ -16,7 +16,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.util.decorator import Registry -MULTI_FACTOR_AUTH_MODULES = Registry() +MULTI_FACTOR_AUTH_MODULES: Registry[str, type[MultiFactorAuthModule]] = Registry() MULTI_FACTOR_AUTH_MODULE_SCHEMA = vol.Schema( { @@ -129,7 +129,7 @@ async def auth_mfa_module_from_config( hass: HomeAssistant, config: dict[str, Any] ) -> MultiFactorAuthModule: """Initialize an auth module from a config.""" - module_name = config[CONF_TYPE] + module_name: str = config[CONF_TYPE] module = await _load_mfa_module(hass, module_name) try: @@ -142,7 +142,7 @@ async def auth_mfa_module_from_config( ) raise - return MULTI_FACTOR_AUTH_MODULES[module_name](hass, config) # type: ignore[no-any-return] + return MULTI_FACTOR_AUTH_MODULES[module_name](hass, config) async def _load_mfa_module(hass: HomeAssistant, module_name: str) -> types.ModuleType: diff --git a/homeassistant/auth/providers/__init__.py b/homeassistant/auth/providers/__init__.py index d80d7a5273b430..63389059051aed 100644 --- a/homeassistant/auth/providers/__init__.py +++ b/homeassistant/auth/providers/__init__.py @@ -25,7 +25,7 @@ _LOGGER = logging.getLogger(__name__) DATA_REQS = "auth_prov_reqs_processed" -AUTH_PROVIDERS = Registry() +AUTH_PROVIDERS: Registry[str, type[AuthProvider]] = Registry() AUTH_PROVIDER_SCHEMA = vol.Schema( { @@ -136,7 +136,7 @@ async def auth_provider_from_config( hass: HomeAssistant, store: AuthStore, config: dict[str, Any] ) -> AuthProvider: """Initialize an auth provider from a config.""" - provider_name = config[CONF_TYPE] + provider_name: str = config[CONF_TYPE] module = await load_auth_provider_module(hass, provider_name) try: @@ -149,7 +149,7 @@ async def auth_provider_from_config( ) raise - return AUTH_PROVIDERS[provider_name](hass, store, config) # type: ignore[no-any-return] + return AUTH_PROVIDERS[provider_name](hass, store, config) async def load_auth_provider_module( diff --git a/homeassistant/components/alexa/entities.py b/homeassistant/components/alexa/entities.py index 1ab24927bcb2cb..5ecd326afb677b 100644 --- a/homeassistant/components/alexa/entities.py +++ b/homeassistant/components/alexa/entities.py @@ -83,7 +83,7 @@ _LOGGER = logging.getLogger(__name__) -ENTITY_ADAPTERS = Registry() +ENTITY_ADAPTERS: Registry[str, type[AlexaEntity]] = Registry() TRANSLATION_TABLE = dict.fromkeys(map(ord, r"}{\/|\"()[]+~!><*%"), None) diff --git a/homeassistant/components/alexa/handlers.py b/homeassistant/components/alexa/handlers.py index f3f669de3b39cc..a27bc432b4f343 100644 --- a/homeassistant/components/alexa/handlers.py +++ b/homeassistant/components/alexa/handlers.py @@ -73,7 +73,7 @@ from .state_report import async_enable_proactive_mode _LOGGER = logging.getLogger(__name__) -HANDLERS = Registry() +HANDLERS = Registry() # type: ignore[var-annotated] @HANDLERS.register(("Alexa.Discovery", "Discover")) diff --git a/homeassistant/components/alexa/intent.py b/homeassistant/components/alexa/intent.py index 0b8bf55fcdad2a..7352bbd995ade4 100644 --- a/homeassistant/components/alexa/intent.py +++ b/homeassistant/components/alexa/intent.py @@ -12,7 +12,7 @@ _LOGGER = logging.getLogger(__name__) -HANDLERS = Registry() +HANDLERS = Registry() # type: ignore[var-annotated] INTENTS_API_ENDPOINT = "/api/alexa" diff --git a/homeassistant/components/filter/sensor.py b/homeassistant/components/filter/sensor.py index d2ad3ec313cb31..a5b54a621a7713 100644 --- a/homeassistant/components/filter/sensor.py +++ b/homeassistant/components/filter/sensor.py @@ -52,7 +52,7 @@ FILTER_NAME_THROTTLE = "throttle" FILTER_NAME_TIME_THROTTLE = "time_throttle" FILTER_NAME_TIME_SMA = "time_simple_moving_average" -FILTERS = Registry() +FILTERS: Registry[str, type[Filter]] = Registry() CONF_FILTERS = "filters" CONF_FILTER_NAME = "filter" diff --git a/homeassistant/components/google_assistant/smart_home.py b/homeassistant/components/google_assistant/smart_home.py index 80bc61cc61dba9..5f38194e3e3b8e 100644 --- a/homeassistant/components/google_assistant/smart_home.py +++ b/homeassistant/components/google_assistant/smart_home.py @@ -19,7 +19,7 @@ EXECUTE_LIMIT = 2 # Wait 2 seconds for execute to finish -HANDLERS = Registry() +HANDLERS = Registry() # type: ignore[var-annotated] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index 922c4c52568be9..d348b4c1f42f80 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -1,4 +1,6 @@ """Extend the basic Accessory and Bridge functions.""" +from __future__ import annotations + import logging from pyhap.accessory import Accessory, Bridge @@ -90,7 +92,7 @@ TYPE_SWITCH: "Switch", TYPE_VALVE: "Valve", } -TYPES = Registry() +TYPES: Registry[str, type[HomeAccessory]] = Registry() def get_accessory(hass, driver, state, aid, config): # noqa: C901 diff --git a/homeassistant/components/konnected/handlers.py b/homeassistant/components/konnected/handlers.py index ef878fc6f2bd59..af784750627a78 100644 --- a/homeassistant/components/konnected/handlers.py +++ b/homeassistant/components/konnected/handlers.py @@ -9,7 +9,7 @@ from .const import CONF_INVERSE, SIGNAL_DS18B20_NEW _LOGGER = logging.getLogger(__name__) -HANDLERS = decorator.Registry() +HANDLERS = decorator.Registry() # type: ignore[var-annotated] @HANDLERS.register("state") diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index d659d7625c17ca..221c4eef733dc6 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -109,7 +109,7 @@ DELAY_SAVE = 10 -WEBHOOK_COMMANDS = Registry() +WEBHOOK_COMMANDS = Registry() # type: ignore[var-annotated] COMBINED_CLASSES = set(BINARY_SENSOR_CLASSES + SENSOR_CLASSES) SENSOR_TYPES = [ATTR_SENSOR_TYPE_BINARY_SENSOR, ATTR_SENSOR_TYPE_SENSOR] diff --git a/homeassistant/components/mysensors/gateway.py b/homeassistant/components/mysensors/gateway.py index b167c8c58defce..be0381ab74ed43 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -3,7 +3,7 @@ import asyncio from collections import defaultdict -from collections.abc import Callable, Coroutine +from collections.abc import Callable import logging import socket import sys @@ -337,9 +337,7 @@ def mysensors_callback(msg: Message) -> None: _LOGGER.debug("Node update: node %s child %s", msg.node_id, msg.child_id) msg_type = msg.gateway.const.MessageType(msg.type) - msg_handler: Callable[ - [HomeAssistant, GatewayId, Message], Coroutine[Any, Any, None] - ] | None = HANDLERS.get(msg_type.name) + msg_handler = HANDLERS.get(msg_type.name) if msg_handler is None: return diff --git a/homeassistant/components/mysensors/handler.py b/homeassistant/components/mysensors/handler.py index 4d61a2812aecb0..57ff12fc6f019c 100644 --- a/homeassistant/components/mysensors/handler.py +++ b/homeassistant/components/mysensors/handler.py @@ -1,6 +1,9 @@ """Handle MySensors messages.""" from __future__ import annotations +from collections.abc import Callable, Coroutine +from typing import Any + from mysensors import Message from homeassistant.const import Platform @@ -12,7 +15,9 @@ from .device import get_mysensors_devices from .helpers import discover_mysensors_platform, validate_set_msg -HANDLERS = decorator.Registry() +HANDLERS: decorator.Registry[ + str, Callable[[HomeAssistant, GatewayId, Message], Coroutine[Any, Any, None]] +] = decorator.Registry() @HANDLERS.register("set") diff --git a/homeassistant/components/mysensors/helpers.py b/homeassistant/components/mysensors/helpers.py index 5b6682393b15fd..a5f67111738934 100644 --- a/homeassistant/components/mysensors/helpers.py +++ b/homeassistant/components/mysensors/helpers.py @@ -31,7 +31,9 @@ ) _LOGGER = logging.getLogger(__name__) -SCHEMAS = Registry() +SCHEMAS: Registry[ + tuple[str, str], Callable[[BaseAsyncGateway, ChildSensor, ValueType], vol.Schema] +] = Registry() @callback diff --git a/homeassistant/components/onvif/parsers.py b/homeassistant/components/onvif/parsers.py index 9574d44edeae2a..b518dbbb45179b 100644 --- a/homeassistant/components/onvif/parsers.py +++ b/homeassistant/components/onvif/parsers.py @@ -1,10 +1,13 @@ """ONVIF event parsers.""" +from collections.abc import Callable, Coroutine +from typing import Any + from homeassistant.util import dt as dt_util from homeassistant.util.decorator import Registry from .models import Event -PARSERS = Registry() +PARSERS: Registry[str, Callable[[str, Any], Coroutine[Any, Any, Event]]] = Registry() @PARSERS.register("tns1:VideoSource/MotionAlarm") diff --git a/homeassistant/components/overkiz/coordinator.py b/homeassistant/components/overkiz/coordinator.py index 98031926cfd759..d90a52ae409523 100644 --- a/homeassistant/components/overkiz/coordinator.py +++ b/homeassistant/components/overkiz/coordinator.py @@ -1,8 +1,10 @@ """Helpers to help coordinate updates.""" from __future__ import annotations +from collections.abc import Callable, Coroutine from datetime import timedelta import logging +from typing import Any from aiohttp import ServerDisconnectedError from pyoverkiz.client import OverkizClient @@ -25,7 +27,9 @@ from .const import DOMAIN, LOGGER, UPDATE_INTERVAL -EVENT_HANDLERS = Registry() +EVENT_HANDLERS: Registry[ + str, Callable[[OverkizDataUpdateCoordinator, Event], Coroutine[Any, Any, None]] +] = Registry() class OverkizDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Device]]): diff --git a/homeassistant/components/owntracks/messages.py b/homeassistant/components/owntracks/messages.py index bd01284329b9ed..b85a37dadf9c72 100644 --- a/homeassistant/components/owntracks/messages.py +++ b/homeassistant/components/owntracks/messages.py @@ -17,7 +17,7 @@ _LOGGER = logging.getLogger(__name__) -HANDLERS = decorator.Registry() +HANDLERS = decorator.Registry() # type: ignore[var-annotated] def get_cipher(): diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index e22e06df7e29c5..157f20b5b37f70 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -245,7 +245,7 @@ def add_provider( self, fmt: str, timeout: int = OUTPUT_IDLE_TIMEOUT ) -> StreamOutput: """Add provider output stream.""" - if not self._outputs.get(fmt): + if not (provider := self._outputs.get(fmt)): @callback def idle_callback() -> None: @@ -259,7 +259,7 @@ def idle_callback() -> None: self.hass, IdleTimer(self.hass, timeout, idle_callback) ) self._outputs[fmt] = provider - return self._outputs[fmt] + return provider def remove_provider(self, provider: StreamOutput) -> None: """Remove provider output stream.""" diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index 91414dd96d9513..8db6a239818093 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -23,7 +23,7 @@ from . import Stream -PROVIDERS = Registry() +PROVIDERS: Registry[str, type[StreamOutput]] = Registry() @attr.s(slots=True) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index af04ee032dc0e8..7bc37bcd3057af 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -62,7 +62,7 @@ # This is used to signal that re-authentication is required by the user. SOURCE_REAUTH = "reauth" -HANDLERS = Registry() +HANDLERS: Registry[str, type[ConfigFlow]] = Registry() STORAGE_KEY = "core.config_entries" STORAGE_VERSION = 1 @@ -530,8 +530,10 @@ async def async_migrate(self, hass: HomeAssistant) -> bool: ) return False # Handler may be a partial + # Keep for backwards compatibility + # https://github.com/home-assistant/core/pull/67087#discussion_r812559950 while isinstance(handler, functools.partial): - handler = handler.func + handler = handler.func # type: ignore[unreachable] if self.version == handler.VERSION: return True @@ -753,7 +755,7 @@ async def async_create_flow( if not context or "source" not in context: raise KeyError("Context not set or doesn't have a source set") - flow = cast(ConfigFlow, handler()) + flow = handler() flow.init_step = context["source"] return flow @@ -1496,7 +1498,7 @@ async def async_create_flow( if entry.domain not in HANDLERS: raise data_entry_flow.UnknownHandler - return cast(OptionsFlow, HANDLERS[entry.domain].async_get_options_flow(entry)) + return HANDLERS[entry.domain].async_get_options_flow(entry) async def async_finish_flow( self, flow: data_entry_flow.FlowHandler, result: data_entry_flow.FlowResult diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index 38fe621f96c9d1..f280feb83b283f 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -13,7 +13,7 @@ from . import config_validation as cv -SELECTORS = decorator.Registry() +SELECTORS: decorator.Registry[str, type[Selector]] = decorator.Registry() def _get_selector_class(config: Any) -> type[Selector]: @@ -24,12 +24,12 @@ def _get_selector_class(config: Any) -> type[Selector]: if len(config) != 1: raise vol.Invalid(f"Only one type can be specified. Found {', '.join(config)}") - selector_type = list(config)[0] + selector_type: str = list(config)[0] if (selector_class := SELECTORS.get(selector_type)) is None: raise vol.Invalid(f"Unknown selector type {selector_type} found") - return cast(type[Selector], selector_class) + return selector_class def selector(config: Any) -> Selector: diff --git a/homeassistant/util/decorator.py b/homeassistant/util/decorator.py index 602cdba5598304..c648f6f1caba4b 100644 --- a/homeassistant/util/decorator.py +++ b/homeassistant/util/decorator.py @@ -2,18 +2,19 @@ from __future__ import annotations from collections.abc import Callable, Hashable -from typing import TypeVar +from typing import Any, TypeVar -CALLABLE_T = TypeVar("CALLABLE_T", bound=Callable) # pylint: disable=invalid-name +_KT = TypeVar("_KT", bound=Hashable) +_VT = TypeVar("_VT", bound=Callable[..., Any]) -class Registry(dict): +class Registry(dict[_KT, _VT]): """Registry of items.""" - def register(self, name: Hashable) -> Callable[[CALLABLE_T], CALLABLE_T]: + def register(self, name: _KT) -> Callable[[_VT], _VT]: """Return decorator to register item with a specific name.""" - def decorator(func: CALLABLE_T) -> CALLABLE_T: + def decorator(func: _VT) -> _VT: """Register decorated function.""" self[name] = func return func diff --git a/mypy.ini b/mypy.ini index 3f8386a2a27d4d..781bc5c199e1fa 100644 --- a/mypy.ini +++ b/mypy.ini @@ -76,6 +76,9 @@ disallow_any_generics = true [mypy-homeassistant.util.color] disallow_any_generics = true +[mypy-homeassistant.util.decorator] +disallow_any_generics = true + [mypy-homeassistant.util.process] disallow_any_generics = true From 3ca918d454231330270397203856f99e47d68166 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 23 Feb 2022 21:14:01 +0100 Subject: [PATCH 1017/1098] Update frontend to 20220223.0 (#67130) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 9a3db7f8294d8e..ad728fd36046f5 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20220222.0" + "home-assistant-frontend==20220223.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 366d26ac0a708d..176e25c414fcf7 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -14,7 +14,7 @@ certifi>=2021.5.30 ciso8601==2.2.0 cryptography==35.0.0 hass-nabucasa==0.53.1 -home-assistant-frontend==20220222.0 +home-assistant-frontend==20220223.0 httpx==0.21.3 ifaddr==0.1.7 jinja2==3.0.3 diff --git a/requirements_all.txt b/requirements_all.txt index 100e1c7bcc95f8..395b221ea6115d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -840,7 +840,7 @@ hole==0.7.0 holidays==0.13 # homeassistant.components.frontend -home-assistant-frontend==20220222.0 +home-assistant-frontend==20220223.0 # homeassistant.components.zwave # homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 876d70eb1a6992..bf79d2d991c37b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -553,7 +553,7 @@ hole==0.7.0 holidays==0.13 # homeassistant.components.frontend -home-assistant-frontend==20220222.0 +home-assistant-frontend==20220223.0 # homeassistant.components.zwave # homeassistant-pyozw==0.1.10 From 75eed17c0be494c7d5aca8c5fc076e393825fe02 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 23 Feb 2022 21:15:32 +0100 Subject: [PATCH 1018/1098] Bumped version to 2022.3.0b0 --- homeassistant/const.py | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 49f7ed18490dc5..af372ebd8e80f2 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 3 -PATCH_VERSION: Final = "0.dev0" +PATCH_VERSION: Final = "0b0" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/setup.cfg b/setup.cfg index 74ea6e296b59e2..1943703d33dc31 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = homeassistant -version = 2022.3.0.dev0 +version = 2022.3.0b0 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 From b21d954e5025422d10a04fba862fa5ca5bc6ebd1 Mon Sep 17 00:00:00 2001 From: soluga <33458264+soluga@users.noreply.github.com> Date: Thu, 24 Feb 2022 01:29:26 +0100 Subject: [PATCH 1019/1098] Don't try to resolve state if native_value is Null (#67134) --- homeassistant/components/wolflink/sensor.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/wolflink/sensor.py b/homeassistant/components/wolflink/sensor.py index f1a94cbbe20e92..a39b03fbd9f9a7 100644 --- a/homeassistant/components/wolflink/sensor.py +++ b/homeassistant/components/wolflink/sensor.py @@ -152,10 +152,11 @@ def device_class(self): def native_value(self): """Return the state converting with supported values.""" state = super().native_value - resolved_state = [ - item for item in self.wolf_object.items if item.value == int(state) - ] - if resolved_state: - resolved_name = resolved_state[0].name - return STATES.get(resolved_name, resolved_name) + if state is not None: + resolved_state = [ + item for item in self.wolf_object.items if item.value == int(state) + ] + if resolved_state: + resolved_name = resolved_state[0].name + return STATES.get(resolved_name, resolved_name) return state From b0d043c55bbb7b2e59cf5eb372cc3990ec7edb97 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 23 Feb 2022 16:22:39 -0800 Subject: [PATCH 1020/1098] Media source to verify domain to avoid KeyError (#67137) --- .../components/media_source/__init__.py | 17 +++++++++++------ tests/components/media_source/test_init.py | 4 ++++ tests/components/netatmo/test_media_source.py | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/media_source/__init__.py b/homeassistant/components/media_source/__init__.py index e2bd1b4903b0ad..77b254dcf9de98 100644 --- a/homeassistant/components/media_source/__init__.py +++ b/homeassistant/components/media_source/__init__.py @@ -85,11 +85,16 @@ def _get_media_item( ) -> MediaSourceItem: """Return media item.""" if media_content_id: - return MediaSourceItem.from_uri(hass, media_content_id) + item = MediaSourceItem.from_uri(hass, media_content_id) + else: + # We default to our own domain if its only one registered + domain = None if len(hass.data[DOMAIN]) > 1 else DOMAIN + return MediaSourceItem(hass, domain, "") - # We default to our own domain if its only one registered - domain = None if len(hass.data[DOMAIN]) > 1 else DOMAIN - return MediaSourceItem(hass, domain, "") + if item.domain is not None and item.domain not in hass.data[DOMAIN]: + raise ValueError("Unknown media source") + + return item @bind_hass @@ -106,7 +111,7 @@ async def async_browse_media( try: item = await _get_media_item(hass, media_content_id).async_browse() except ValueError as err: - raise BrowseError("Not a media source item") from err + raise BrowseError(str(err)) from err if content_filter is None or item.children is None: return item @@ -128,7 +133,7 @@ async def async_resolve_media(hass: HomeAssistant, media_content_id: str) -> Pla try: item = _get_media_item(hass, media_content_id) except ValueError as err: - raise Unresolvable("Not a media source item") from err + raise Unresolvable(str(err)) from err return await item.async_resolve() diff --git a/tests/components/media_source/test_init.py b/tests/components/media_source/test_init.py index e36ccdac931eb2..319ef295be393b 100644 --- a/tests/components/media_source/test_init.py +++ b/tests/components/media_source/test_init.py @@ -98,6 +98,10 @@ async def test_async_unresolve_media(hass): with pytest.raises(media_source.Unresolvable): await media_source.async_resolve_media(hass, "invalid") + # Test invalid media source + with pytest.raises(media_source.Unresolvable): + await media_source.async_resolve_media(hass, "media-source://media_source2") + async def test_websocket_browse_media(hass, hass_ws_client): """Test browse media websocket.""" diff --git a/tests/components/netatmo/test_media_source.py b/tests/components/netatmo/test_media_source.py index c47416721869aa..db1a79145b4bc8 100644 --- a/tests/components/netatmo/test_media_source.py +++ b/tests/components/netatmo/test_media_source.py @@ -54,7 +54,7 @@ async def test_async_browse_media(hass): # Test invalid base with pytest.raises(media_source.BrowseError) as excinfo: await media_source.async_browse_media(hass, f"{const.URI_SCHEME}{DOMAIN}/") - assert str(excinfo.value) == "Not a media source item" + assert str(excinfo.value) == "Invalid media source URI" # Test successful listing media = await media_source.async_browse_media( From 3550a926295be7901893a354662ec1ab965e3926 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Wed, 23 Feb 2022 17:42:18 -0600 Subject: [PATCH 1021/1098] Fix Sonos radio metadata processing with missing data (#67141) --- homeassistant/components/sonos/media.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sonos/media.py b/homeassistant/components/sonos/media.py index 85d15680a971ff..f4108b8531763f 100644 --- a/homeassistant/components/sonos/media.py +++ b/homeassistant/components/sonos/media.py @@ -169,7 +169,8 @@ def update_media_from_event(self, evars: dict[str, Any]) -> None: self.queue_size = int(queue_size) if audio_source == MUSIC_SRC_RADIO: - self.channel = et_uri_md.title + if et_uri_md: + self.channel = et_uri_md.title if ct_md and ct_md.radio_show: radio_show = ct_md.radio_show.split(",")[0] From 6a31cd92795b659f192305fd40ae3ad3c70b4dae Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 23 Feb 2022 16:21:24 -0800 Subject: [PATCH 1022/1098] Fix SQL sensor (#67144) --- homeassistant/components/sql/sensor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sql/sensor.py b/homeassistant/components/sql/sensor.py index 1c8e87051beabb..1c8514d0d26493 100644 --- a/homeassistant/components/sql/sensor.py +++ b/homeassistant/components/sql/sensor.py @@ -2,6 +2,7 @@ from __future__ import annotations from datetime import date +import decimal import logging import re @@ -158,7 +159,7 @@ def update(self) -> None: _LOGGER.debug("result = %s", res.items()) data = res[self._column_name] for key, value in res.items(): - if isinstance(value, float): + if isinstance(value, decimal.Decimal): value = float(value) if isinstance(value, date): value = value.isoformat() From 0cd4f74d739105d950c6b100e4b864464277ec44 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 23 Feb 2022 21:15:48 -0800 Subject: [PATCH 1023/1098] Allow get_states to recover (#67146) --- .../components/websocket_api/commands.py | 34 ++++++++++++++++++- .../components/websocket_api/test_commands.py | 13 +++++-- tests/components/websocket_api/test_http.py | 3 +- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index 650013dda7f13f..4ed0a9ada96e09 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -34,6 +34,10 @@ from homeassistant.helpers.service import async_get_all_descriptions from homeassistant.loader import IntegrationNotFound, async_get_integration from homeassistant.setup import DATA_SETUP_TIME, async_get_loaded_integrations +from homeassistant.util.json import ( + find_paths_unserializable_data, + format_unserializable_data, +) from . import const, decorators, messages from .connection import ActiveConnection @@ -225,7 +229,35 @@ def handle_get_states( if entity_perm(state.entity_id, "read") ] - connection.send_result(msg["id"], states) + # JSON serialize here so we can recover if it blows up due to the + # state machine containing unserializable data. This command is required + # to succeed for the UI to show. + response = messages.result_message(msg["id"], states) + try: + connection.send_message(const.JSON_DUMP(response)) + return + except (ValueError, TypeError): + connection.logger.error( + "Unable to serialize to JSON. Bad data found at %s", + format_unserializable_data( + find_paths_unserializable_data(response, dump=const.JSON_DUMP) + ), + ) + del response + + # If we can't serialize, we'll filter out unserializable states + serialized = [] + for state in states: + try: + serialized.append(const.JSON_DUMP(state)) + except (ValueError, TypeError): + # Error is already logged above + pass + + # We now have partially serialized states. Craft some JSON. + response2 = const.JSON_DUMP(messages.result_message(msg["id"], ["TO_REPLACE"])) + response2 = response2.replace('"TO_REPLACE"', ", ".join(serialized)) + connection.send_message(response2) @decorators.websocket_command({vol.Required("type"): "get_services"}) diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index 130870f73f03a3..742d9bddd3884d 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -587,13 +587,20 @@ async def test_states_filters_visible(hass, hass_admin_user, websocket_client): async def test_get_states_not_allows_nan(hass, websocket_client): """Test get_states command not allows NaN floats.""" - hass.states.async_set("greeting.hello", "world", {"hello": float("NaN")}) + hass.states.async_set("greeting.hello", "world") + hass.states.async_set("greeting.bad", "data", {"hello": float("NaN")}) + hass.states.async_set("greeting.bye", "universe") await websocket_client.send_json({"id": 5, "type": "get_states"}) msg = await websocket_client.receive_json() - assert not msg["success"] - assert msg["error"]["code"] == const.ERR_UNKNOWN_ERROR + assert msg["id"] == 5 + assert msg["type"] == const.TYPE_RESULT + assert msg["success"] + assert msg["result"] == [ + hass.states.get("greeting.hello").as_dict(), + hass.states.get("greeting.bye").as_dict(), + ] async def test_subscribe_unsubscribe_events_whitelist( diff --git a/tests/components/websocket_api/test_http.py b/tests/components/websocket_api/test_http.py index 336c79d22b8c20..c3564d2b21b3fb 100644 --- a/tests/components/websocket_api/test_http.py +++ b/tests/components/websocket_api/test_http.py @@ -76,7 +76,8 @@ async def test_non_json_message(hass, websocket_client, caplog): msg = await websocket_client.receive_json() assert msg["id"] == 5 assert msg["type"] == const.TYPE_RESULT - assert not msg["success"] + assert msg["success"] + assert msg["result"] == [] assert ( f"Unable to serialize to JSON. Bad data found at $.result[0](State: test_domain.entity).attributes.bad={bad_data}(" in caplog.text From f0383782f92eef3f41ffaddb070a55f77526d1f8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 23 Feb 2022 20:15:20 -1000 Subject: [PATCH 1024/1098] Use compact encoding for JSON websocket messages (#67148) Co-authored-by: Paulus Schoutsen --- homeassistant/components/websocket_api/commands.py | 4 +++- homeassistant/components/websocket_api/const.py | 4 +++- tests/components/websocket_api/test_messages.py | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index 4ed0a9ada96e09..abc37dd2a0a64e 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -480,7 +480,9 @@ def forward_triggers( msg["id"], {"variables": variables, "context": context} ) connection.send_message( - json.dumps(message, cls=ExtendedJSONEncoder, allow_nan=False) + json.dumps( + message, cls=ExtendedJSONEncoder, allow_nan=False, separators=(",", ":") + ) ) connection.subscriptions[msg["id"]] = ( diff --git a/homeassistant/components/websocket_api/const.py b/homeassistant/components/websocket_api/const.py index 9428d6fd87d3e7..6c5615ad253b83 100644 --- a/homeassistant/components/websocket_api/const.py +++ b/homeassistant/components/websocket_api/const.py @@ -53,4 +53,6 @@ # Data used to store the current connection list DATA_CONNECTIONS: Final = f"{DOMAIN}.connections" -JSON_DUMP: Final = partial(json.dumps, cls=JSONEncoder, allow_nan=False) +JSON_DUMP: Final = partial( + json.dumps, cls=JSONEncoder, allow_nan=False, separators=(",", ":") +) diff --git a/tests/components/websocket_api/test_messages.py b/tests/components/websocket_api/test_messages.py index 3ec156e69493b0..618879f4b7fec8 100644 --- a/tests/components/websocket_api/test_messages.py +++ b/tests/components/websocket_api/test_messages.py @@ -83,13 +83,13 @@ async def test_message_to_json(caplog): json_str = message_to_json({"id": 1, "message": "xyz"}) - assert json_str == '{"id": 1, "message": "xyz"}' + assert json_str == '{"id":1,"message":"xyz"}' json_str2 = message_to_json({"id": 1, "message": _Unserializeable()}) assert ( json_str2 - == '{"id": 1, "type": "result", "success": false, "error": {"code": "unknown_error", "message": "Invalid JSON in response"}}' + == '{"id":1,"type":"result","success":false,"error":{"code":"unknown_error","message":"Invalid JSON in response"}}' ) assert "Unable to serialize to JSON" in caplog.text From f40f25473ca39d942cf9b3500c918a335420a7da Mon Sep 17 00:00:00 2001 From: Robert Hillis Date: Thu, 24 Feb 2022 00:18:14 -0500 Subject: [PATCH 1025/1098] Bump aiopyarr to 22.2.2 (#67149) --- homeassistant/components/sonarr/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonarr/manifest.json b/homeassistant/components/sonarr/manifest.json index 9c43bfed2822fa..6a9b00d204174a 100644 --- a/homeassistant/components/sonarr/manifest.json +++ b/homeassistant/components/sonarr/manifest.json @@ -3,7 +3,7 @@ "name": "Sonarr", "documentation": "https://www.home-assistant.io/integrations/sonarr", "codeowners": ["@ctalkington"], - "requirements": ["aiopyarr==22.2.1"], + "requirements": ["aiopyarr==22.2.2"], "config_flow": true, "quality_scale": "silver", "iot_class": "local_polling", diff --git a/requirements_all.txt b/requirements_all.txt index 395b221ea6115d..9cf25c8e2f527e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -245,7 +245,7 @@ aiopvapi==1.6.19 aiopvpc==3.0.0 # homeassistant.components.sonarr -aiopyarr==22.2.1 +aiopyarr==22.2.2 # homeassistant.components.recollect_waste aiorecollect==1.0.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bf79d2d991c37b..2d577e3d8a24f6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -180,7 +180,7 @@ aiopvapi==1.6.19 aiopvpc==3.0.0 # homeassistant.components.sonarr -aiopyarr==22.2.1 +aiopyarr==22.2.2 # homeassistant.components.recollect_waste aiorecollect==1.0.8 From 25933e1186d72dd1b05bc30eddb3fdc75cbf344a Mon Sep 17 00:00:00 2001 From: Gage Benne Date: Thu, 24 Feb 2022 01:05:45 -0500 Subject: [PATCH 1026/1098] Bump pydexcom to 0.2.3 (#67152) --- homeassistant/components/dexcom/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/dexcom/manifest.json b/homeassistant/components/dexcom/manifest.json index b60ea3a576cdf1..25193019f7ddbe 100644 --- a/homeassistant/components/dexcom/manifest.json +++ b/homeassistant/components/dexcom/manifest.json @@ -3,7 +3,7 @@ "name": "Dexcom", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/dexcom", - "requirements": ["pydexcom==0.2.2"], + "requirements": ["pydexcom==0.2.3"], "codeowners": ["@gagebenne"], "iot_class": "cloud_polling", "loggers": ["pydexcom"] diff --git a/requirements_all.txt b/requirements_all.txt index 9cf25c8e2f527e..7dfc9c19b9eeac 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1474,7 +1474,7 @@ pydeconz==87 pydelijn==1.0.0 # homeassistant.components.dexcom -pydexcom==0.2.2 +pydexcom==0.2.3 # homeassistant.components.zwave pydispatcher==2.0.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2d577e3d8a24f6..cc71923136d472 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -924,7 +924,7 @@ pydaikin==2.7.0 pydeconz==87 # homeassistant.components.dexcom -pydexcom==0.2.2 +pydexcom==0.2.3 # homeassistant.components.zwave pydispatcher==2.0.5 From 70f9196e8fca1312ed25163ee8082258fa27d774 Mon Sep 17 00:00:00 2001 From: Keilin Bickar Date: Thu, 24 Feb 2022 00:27:31 -0500 Subject: [PATCH 1027/1098] SleepIQ Dependency update (#67154) --- homeassistant/components/sleepiq/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sleepiq/manifest.json b/homeassistant/components/sleepiq/manifest.json index 48ada7b14a2d6e..93cd1be3204fd5 100644 --- a/homeassistant/components/sleepiq/manifest.json +++ b/homeassistant/components/sleepiq/manifest.json @@ -3,7 +3,7 @@ "name": "SleepIQ", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sleepiq", - "requirements": ["asyncsleepiq==1.0.0"], + "requirements": ["asyncsleepiq==1.1.0"], "codeowners": ["@mfugate1", "@kbickar"], "dhcp": [ { diff --git a/requirements_all.txt b/requirements_all.txt index 7dfc9c19b9eeac..9b21741348fcf4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -354,7 +354,7 @@ async-upnp-client==0.23.5 asyncpysupla==0.0.5 # homeassistant.components.sleepiq -asyncsleepiq==1.0.0 +asyncsleepiq==1.1.0 # homeassistant.components.aten_pe atenpdu==0.3.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cc71923136d472..93dacff8619423 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -259,7 +259,7 @@ arcam-fmj==0.12.0 async-upnp-client==0.23.5 # homeassistant.components.sleepiq -asyncsleepiq==1.0.0 +asyncsleepiq==1.1.0 # homeassistant.components.aurora auroranoaa==0.0.2 From 37ebeae83bba6a2a5fa8415a30aca9bdc080d527 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 23 Feb 2022 22:16:27 -0800 Subject: [PATCH 1028/1098] Bumped version to 2022.3.0b1 --- homeassistant/const.py | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index af372ebd8e80f2..72968b5021dffe 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 3 -PATCH_VERSION: Final = "0b0" +PATCH_VERSION: Final = "0b1" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/setup.cfg b/setup.cfg index 1943703d33dc31..3f032e34de391e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = homeassistant -version = 2022.3.0b0 +version = 2022.3.0b1 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 From 596f3110ba7cafd4def1c30a824fb88e26e016a5 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 24 Feb 2022 18:14:38 +0100 Subject: [PATCH 1029/1098] Fix MQTT config entry deprecation warnings (#67174) --- homeassistant/components/mqtt/__init__.py | 77 +++++++++++------------ 1 file changed, 37 insertions(+), 40 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index c7652fe97b9311..1982d1f3df58ac 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -179,6 +179,40 @@ required=True, ) +CONFIG_SCHEMA_BASE = vol.Schema( + { + vol.Optional(CONF_CLIENT_ID): cv.string, + vol.Optional(CONF_KEEPALIVE, default=DEFAULT_KEEPALIVE): vol.All( + vol.Coerce(int), vol.Range(min=15) + ), + vol.Optional(CONF_BROKER): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_USERNAME): cv.string, + vol.Optional(CONF_PASSWORD): cv.string, + vol.Optional(CONF_CERTIFICATE): vol.Any("auto", cv.isfile), + vol.Inclusive( + CONF_CLIENT_KEY, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG + ): cv.isfile, + vol.Inclusive( + CONF_CLIENT_CERT, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG + ): cv.isfile, + vol.Optional(CONF_TLS_INSECURE): cv.boolean, + vol.Optional(CONF_TLS_VERSION, default=DEFAULT_TLS_PROTOCOL): vol.Any( + "auto", "1.0", "1.1", "1.2" + ), + vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): vol.All( + cv.string, vol.In([PROTOCOL_31, PROTOCOL_311]) + ), + vol.Optional(CONF_WILL_MESSAGE, default=DEFAULT_WILL): MQTT_WILL_BIRTH_SCHEMA, + vol.Optional(CONF_BIRTH_MESSAGE, default=DEFAULT_BIRTH): MQTT_WILL_BIRTH_SCHEMA, + vol.Optional(CONF_DISCOVERY, default=DEFAULT_DISCOVERY): cv.boolean, + # discovery_prefix must be a valid publish topic because if no + # state topic is specified, it will be created with the given prefix. + vol.Optional( + CONF_DISCOVERY_PREFIX, default=DEFAULT_PREFIX + ): valid_publish_topic, + } +) CONFIG_SCHEMA = vol.Schema( { @@ -191,44 +225,7 @@ cv.deprecated(CONF_TLS_VERSION), # Deprecated June 2020 cv.deprecated(CONF_USERNAME), # Deprecated in HA Core 2022.3 cv.deprecated(CONF_WILL_MESSAGE), # Deprecated in HA Core 2022.3 - vol.Schema( - { - vol.Optional(CONF_CLIENT_ID): cv.string, - vol.Optional(CONF_KEEPALIVE, default=DEFAULT_KEEPALIVE): vol.All( - vol.Coerce(int), vol.Range(min=15) - ), - vol.Optional(CONF_BROKER): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD): cv.string, - vol.Optional(CONF_CERTIFICATE): vol.Any("auto", cv.isfile), - vol.Inclusive( - CONF_CLIENT_KEY, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG - ): cv.isfile, - vol.Inclusive( - CONF_CLIENT_CERT, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG - ): cv.isfile, - vol.Optional(CONF_TLS_INSECURE): cv.boolean, - vol.Optional( - CONF_TLS_VERSION, default=DEFAULT_TLS_PROTOCOL - ): vol.Any("auto", "1.0", "1.1", "1.2"), - vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): vol.All( - cv.string, vol.In([PROTOCOL_31, PROTOCOL_311]) - ), - vol.Optional( - CONF_WILL_MESSAGE, default=DEFAULT_WILL - ): MQTT_WILL_BIRTH_SCHEMA, - vol.Optional( - CONF_BIRTH_MESSAGE, default=DEFAULT_BIRTH - ): MQTT_WILL_BIRTH_SCHEMA, - vol.Optional(CONF_DISCOVERY, default=DEFAULT_DISCOVERY): cv.boolean, - # discovery_prefix must be a valid publish topic because if no - # state topic is specified, it will be created with the given prefix. - vol.Optional( - CONF_DISCOVERY_PREFIX, default=DEFAULT_PREFIX - ): valid_publish_topic, - } - ), + CONFIG_SCHEMA_BASE, ) }, extra=vol.ALLOW_EXTRA, @@ -619,7 +616,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Load a config entry.""" # If user didn't have configuration.yaml config, generate defaults if (conf := hass.data.get(DATA_MQTT_CONFIG)) is None: - conf = CONFIG_SCHEMA({DOMAIN: dict(entry.data)})[DOMAIN] + conf = CONFIG_SCHEMA_BASE(dict(entry.data)) elif any(key in conf for key in entry.data): shared_keys = conf.keys() & entry.data.keys() override = {k: entry.data[k] for k in shared_keys} @@ -811,7 +808,7 @@ async def async_config_entry_updated( self = hass.data[DATA_MQTT] if (conf := hass.data.get(DATA_MQTT_CONFIG)) is None: - conf = CONFIG_SCHEMA({DOMAIN: dict(entry.data)})[DOMAIN] + conf = CONFIG_SCHEMA_BASE(dict(entry.data)) self.conf = _merge_config(entry, conf) await self.async_disconnect() From 3c0cd126dd0536c6de66ad96aa6247bd0d2529b9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 24 Feb 2022 09:03:59 -1000 Subject: [PATCH 1030/1098] Move camera to after deps for HomeKit (#67190) --- homeassistant/components/homekit/manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homekit/manifest.json b/homeassistant/components/homekit/manifest.json index 9981b3a1109e20..bde540d6372253 100644 --- a/homeassistant/components/homekit/manifest.json +++ b/homeassistant/components/homekit/manifest.json @@ -8,8 +8,8 @@ "PyQRCode==1.2.1", "base36==0.1.1" ], - "dependencies": ["http", "camera", "ffmpeg", "network"], - "after_dependencies": ["zeroconf"], + "dependencies": ["ffmpeg", "http", "network"], + "after_dependencies": ["camera", "zeroconf"], "codeowners": ["@bdraco"], "zeroconf": ["_homekit._tcp.local."], "config_flow": true, From 32566085c818e8cc95c18fd62a9973cab4a11468 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 24 Feb 2022 09:07:17 -1000 Subject: [PATCH 1031/1098] Fix ElkM1 systems that do not use password authentication (#67194) --- homeassistant/components/elkm1/__init__.py | 23 +++++++--- homeassistant/components/elkm1/config_flow.py | 24 ++++++++-- homeassistant/components/elkm1/const.py | 2 +- homeassistant/components/elkm1/discovery.py | 2 + tests/components/elkm1/test_config_flow.py | 44 +++++++++++++++++++ 5 files changed, 86 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/elkm1/__init__.py b/homeassistant/components/elkm1/__init__.py index 8b0dd26fc32684..04a26f2822b066 100644 --- a/homeassistant/components/elkm1/__init__.py +++ b/homeassistant/components/elkm1/__init__.py @@ -228,7 +228,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: _LOGGER.debug("Setting up elkm1 %s", conf["host"]) - if not entry.unique_id or ":" not in entry.unique_id and is_ip_address(host): + if (not entry.unique_id or ":" not in entry.unique_id) and is_ip_address(host): + _LOGGER.debug( + "Unique id for %s is missing during setup, trying to fill from discovery", + host, + ) if device := await async_discover_device(hass, host): async_update_entry_from_discovery(hass, entry, device) @@ -276,7 +280,7 @@ def _element_changed(element, changeset): try: if not await async_wait_for_elk_to_sync( - elk, LOGIN_TIMEOUT, SYNC_TIMEOUT, conf[CONF_HOST] + elk, LOGIN_TIMEOUT, SYNC_TIMEOUT, bool(conf[CONF_USERNAME]) ): return False except asyncio.TimeoutError as exc: @@ -327,7 +331,10 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_wait_for_elk_to_sync( - elk: elkm1.Elk, login_timeout: int, sync_timeout: int, conf_host: str + elk: elkm1.Elk, + login_timeout: int, + sync_timeout: int, + password_auth: bool, ) -> bool: """Wait until the elk has finished sync. Can fail login or timeout.""" @@ -353,15 +360,21 @@ def sync_complete(): success = True elk.add_handler("login", login_status) elk.add_handler("sync_complete", sync_complete) - events = ((login_event, login_timeout), (sync_event, sync_timeout)) + events = [] + if password_auth: + events.append(("login", login_event, login_timeout)) + events.append(("sync_complete", sync_event, sync_timeout)) - for event, timeout in events: + for name, event, timeout in events: + _LOGGER.debug("Waiting for %s event for %s seconds", name, timeout) try: async with async_timeout.timeout(timeout): await event.wait() except asyncio.TimeoutError: + _LOGGER.debug("Timed out waiting for %s event", name) elk.disconnect() raise + _LOGGER.debug("Received %s event", name) return success diff --git a/homeassistant/components/elkm1/config_flow.py b/homeassistant/components/elkm1/config_flow.py index 2453958b3deff1..a21cf186005c5a 100644 --- a/homeassistant/components/elkm1/config_flow.py +++ b/homeassistant/components/elkm1/config_flow.py @@ -24,6 +24,7 @@ from homeassistant.helpers import device_registry as dr from homeassistant.helpers.typing import DiscoveryInfoType from homeassistant.util import slugify +from homeassistant.util.network import is_ip_address from . import async_wait_for_elk_to_sync from .const import CONF_AUTO_CONFIGURE, DISCOVER_SCAN_TIMEOUT, DOMAIN, LOGIN_TIMEOUT @@ -80,7 +81,9 @@ async def validate_input(data: dict[str, str], mac: str | None) -> dict[str, str ) elk.connect() - if not await async_wait_for_elk_to_sync(elk, LOGIN_TIMEOUT, VALIDATE_TIMEOUT, url): + if not await async_wait_for_elk_to_sync( + elk, LOGIN_TIMEOUT, VALIDATE_TIMEOUT, bool(userid) + ): raise InvalidAuth short_mac = _short_mac(mac) if mac else None @@ -124,6 +127,7 @@ async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowRes self._discovered_device = ElkSystem( discovery_info.macaddress, discovery_info.ip, 0 ) + _LOGGER.debug("Elk discovered from dhcp: %s", self._discovered_device) return await self._async_handle_discovery() async def async_step_integration_discovery( @@ -135,6 +139,9 @@ async def async_step_integration_discovery( discovery_info["ip_address"], discovery_info["port"], ) + _LOGGER.debug( + "Elk discovered from integration discovery: %s", self._discovered_device + ) return await self._async_handle_discovery() async def _async_handle_discovery(self) -> FlowResult: @@ -304,11 +311,22 @@ async def async_step_manual_connection(self, user_input=None): async def async_step_import(self, user_input): """Handle import.""" - if device := await async_discover_device( - self.hass, urlparse(user_input[CONF_HOST]).hostname + _LOGGER.debug("Elk is importing from yaml") + url = _make_url_from_data(user_input) + + if self._url_already_configured(url): + return self.async_abort(reason="address_already_configured") + + host = urlparse(url).hostname + _LOGGER.debug( + "Importing is trying to fill unique id from discovery for %s", host + ) + if is_ip_address(host) and ( + device := await async_discover_device(self.hass, host) ): await self.async_set_unique_id(dr.format_mac(device.mac_address)) self._abort_if_unique_id_configured() + return (await self._async_create_or_error(user_input, True))[1] def _url_already_configured(self, url): diff --git a/homeassistant/components/elkm1/const.py b/homeassistant/components/elkm1/const.py index 80d594fce0a41f..fd4856bd5d5d27 100644 --- a/homeassistant/components/elkm1/const.py +++ b/homeassistant/components/elkm1/const.py @@ -9,7 +9,7 @@ DOMAIN = "elkm1" -LOGIN_TIMEOUT = 15 +LOGIN_TIMEOUT = 20 CONF_AUTO_CONFIGURE = "auto_configure" CONF_AREA = "area" diff --git a/homeassistant/components/elkm1/discovery.py b/homeassistant/components/elkm1/discovery.py index 7055f3958e9d6f..326698c36869d3 100644 --- a/homeassistant/components/elkm1/discovery.py +++ b/homeassistant/components/elkm1/discovery.py @@ -29,9 +29,11 @@ def async_update_entry_from_discovery( ) -> bool: """Update a config entry from a discovery.""" if not entry.unique_id or ":" not in entry.unique_id: + _LOGGER.debug("Adding unique id from discovery: %s", device) return hass.config_entries.async_update_entry( entry, unique_id=dr.format_mac(device.mac_address) ) + _LOGGER.debug("Unique id is already present from discovery: %s", device) return False diff --git a/tests/components/elkm1/test_config_flow.py b/tests/components/elkm1/test_config_flow.py index d8a0feea670a34..49402d7b4d51d0 100644 --- a/tests/components/elkm1/test_config_flow.py +++ b/tests/components/elkm1/test_config_flow.py @@ -652,6 +652,50 @@ async def test_form_import_device_discovered(hass): assert len(mock_setup_entry.mock_calls) == 1 +async def test_form_import_existing(hass): + """Test we abort on existing import.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: f"elks://{MOCK_IP_ADDRESS}"}, + unique_id="cc:cc:cc:cc:cc:cc", + ) + config_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + "host": f"elks://{MOCK_IP_ADDRESS}", + "username": "friend", + "password": "love", + "temperature_unit": "C", + "auto_configure": False, + "keypad": { + "enabled": True, + "exclude": [], + "include": [[1, 1], [2, 2], [3, 3]], + }, + "output": {"enabled": False, "exclude": [], "include": []}, + "counter": {"enabled": False, "exclude": [], "include": []}, + "plc": {"enabled": False, "exclude": [], "include": []}, + "prefix": "ohana", + "setting": {"enabled": False, "exclude": [], "include": []}, + "area": {"enabled": False, "exclude": [], "include": []}, + "task": {"enabled": False, "exclude": [], "include": []}, + "thermostat": {"enabled": False, "exclude": [], "include": []}, + "zone": { + "enabled": True, + "exclude": [[15, 15], [28, 208]], + "include": [], + }, + }, + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "address_already_configured" + + @pytest.mark.parametrize( "source, data", [ From 694fb2dddec3c0f128cc06f363b62285a5e36ba4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 24 Feb 2022 14:54:46 -0800 Subject: [PATCH 1032/1098] Move media_source to after_deps (#67195) --- homeassistant/components/dlna_dms/manifest.json | 5 +++-- homeassistant/components/motioneye/manifest.json | 15 ++++----------- homeassistant/components/nest/manifest.json | 3 ++- tests/components/motioneye/test_media_source.py | 7 +++++++ tests/components/nest/test_media_source.py | 6 ++++++ 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/dlna_dms/manifest.json b/homeassistant/components/dlna_dms/manifest.json index feee4b6e9031a7..84cfc2e69fdc22 100644 --- a/homeassistant/components/dlna_dms/manifest.json +++ b/homeassistant/components/dlna_dms/manifest.json @@ -4,7 +4,8 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/dlna_dms", "requirements": ["async-upnp-client==0.23.5"], - "dependencies": ["media_source", "ssdp"], + "dependencies": ["ssdp"], + "after_dependencies": ["media_source"], "ssdp": [ { "deviceType": "urn:schemas-upnp-org:device:MediaServer:1", @@ -26,4 +27,4 @@ "codeowners": ["@chishm"], "iot_class": "local_polling", "quality_scale": "platinum" -} \ No newline at end of file +} diff --git a/homeassistant/components/motioneye/manifest.json b/homeassistant/components/motioneye/manifest.json index 0eb4dc57d9dfe0..5c1dbb376a03c1 100644 --- a/homeassistant/components/motioneye/manifest.json +++ b/homeassistant/components/motioneye/manifest.json @@ -3,17 +3,10 @@ "name": "motionEye", "documentation": "https://www.home-assistant.io/integrations/motioneye", "config_flow": true, - "dependencies": [ - "http", - "media_source", - "webhook" - ], - "requirements": [ - "motioneye-client==0.3.12" - ], - "codeowners": [ - "@dermotduffy" - ], + "dependencies": ["http", "webhook"], + "after_dependencies": ["media_source"], + "requirements": ["motioneye-client==0.3.12"], + "codeowners": ["@dermotduffy"], "iot_class": "local_polling", "loggers": ["motioneye_client"] } diff --git a/homeassistant/components/nest/manifest.json b/homeassistant/components/nest/manifest.json index 6e7ac1257fa116..6968b401561839 100644 --- a/homeassistant/components/nest/manifest.json +++ b/homeassistant/components/nest/manifest.json @@ -2,7 +2,8 @@ "domain": "nest", "name": "Nest", "config_flow": true, - "dependencies": ["ffmpeg", "http", "media_source"], + "dependencies": ["ffmpeg", "http"], + "after_dependencies": ["media_source"], "documentation": "https://www.home-assistant.io/integrations/nest", "requirements": ["python-nest==4.2.0", "google-nest-sdm==1.7.1"], "codeowners": ["@allenporter"], diff --git a/tests/components/motioneye/test_media_source.py b/tests/components/motioneye/test_media_source.py index ddc50fb77029f9..6c0e46b34c6973 100644 --- a/tests/components/motioneye/test_media_source.py +++ b/tests/components/motioneye/test_media_source.py @@ -12,6 +12,7 @@ from homeassistant.components.motioneye.const import DOMAIN from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr +from homeassistant.setup import async_setup_component from . import ( TEST_CAMERA_DEVICE_IDENTIFIER, @@ -67,6 +68,12 @@ _LOGGER = logging.getLogger(__name__) +@pytest.fixture(autouse=True) +async def setup_media_source(hass) -> None: + """Set up media source.""" + assert await async_setup_component(hass, "media_source", {}) + + async def test_async_browse_media_success(hass: HomeAssistant) -> None: """Test successful browse media.""" diff --git a/tests/components/nest/test_media_source.py b/tests/components/nest/test_media_source.py index 015a14fb92c451..2361049ecc1c6e 100644 --- a/tests/components/nest/test_media_source.py +++ b/tests/components/nest/test_media_source.py @@ -90,6 +90,12 @@ def frame_image_data(frame_i, total_frames): return img +@pytest.fixture(autouse=True) +async def setup_media_source(hass) -> None: + """Set up media source.""" + assert await async_setup_component(hass, "media_source", {}) + + @pytest.fixture def mp4() -> io.BytesIO: """Generate test mp4 clip.""" From 9e7cbb011c196793da1d8407242e6c188bea83ed Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 24 Feb 2022 16:30:53 -0800 Subject: [PATCH 1033/1098] Bump aiohue to 4.3.0 (#67202) --- homeassistant/components/hue/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/hue/manifest.json b/homeassistant/components/hue/manifest.json index 193d2b7fa81591..bf6e7f06abdc05 100644 --- a/homeassistant/components/hue/manifest.json +++ b/homeassistant/components/hue/manifest.json @@ -3,7 +3,7 @@ "name": "Philips Hue", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/hue", - "requirements": ["aiohue==4.2.1"], + "requirements": ["aiohue==4.3.0"], "ssdp": [ { "manufacturer": "Royal Philips Electronics", diff --git a/requirements_all.txt b/requirements_all.txt index 9b21741348fcf4..9541447aaf062e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -191,7 +191,7 @@ aiohomekit==0.7.14 aiohttp_cors==0.7.0 # homeassistant.components.hue -aiohue==4.2.1 +aiohue==4.3.0 # homeassistant.components.homewizard aiohwenergy==0.8.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 93dacff8619423..e244a67ffe1e34 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -141,7 +141,7 @@ aiohomekit==0.7.14 aiohttp_cors==0.7.0 # homeassistant.components.hue -aiohue==4.2.1 +aiohue==4.3.0 # homeassistant.components.homewizard aiohwenergy==0.8.0 From 18087caf85e0be9d05c09ee5297fda1df59a2d9f Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Thu, 24 Feb 2022 21:28:36 -0600 Subject: [PATCH 1034/1098] 20220224.0 (#67204) --- homeassistant/components/frontend/manifest.json | 5 ++--- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index ad728fd36046f5..f9ad2bd428db60 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20220223.0" + "home-assistant-frontend==20220224.0" ], "dependencies": [ "api", @@ -13,8 +13,7 @@ "diagnostics", "http", "lovelace", - "onboarding", - "search", + "onboarding", "search", "system_log", "websocket_api" ], diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 176e25c414fcf7..1bd905e6abd550 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -14,7 +14,7 @@ certifi>=2021.5.30 ciso8601==2.2.0 cryptography==35.0.0 hass-nabucasa==0.53.1 -home-assistant-frontend==20220223.0 +home-assistant-frontend==20220224.0 httpx==0.21.3 ifaddr==0.1.7 jinja2==3.0.3 diff --git a/requirements_all.txt b/requirements_all.txt index 9541447aaf062e..bf43d648d5dfd1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -840,7 +840,7 @@ hole==0.7.0 holidays==0.13 # homeassistant.components.frontend -home-assistant-frontend==20220223.0 +home-assistant-frontend==20220224.0 # homeassistant.components.zwave # homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e244a67ffe1e34..8484a2c12d3a12 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -553,7 +553,7 @@ hole==0.7.0 holidays==0.13 # homeassistant.components.frontend -home-assistant-frontend==20220223.0 +home-assistant-frontend==20220224.0 # homeassistant.components.zwave # homeassistant-pyozw==0.1.10 From 3372bdfc0f9accf6bc5bef471cf46aaf745f012d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 24 Feb 2022 20:57:23 -0800 Subject: [PATCH 1035/1098] Bumped version to 2022.3.0b2 --- homeassistant/const.py | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 72968b5021dffe..925c08e24fde05 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 3 -PATCH_VERSION: Final = "0b1" +PATCH_VERSION: Final = "0b2" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/setup.cfg b/setup.cfg index 3f032e34de391e..295206ed0031f5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = homeassistant -version = 2022.3.0b1 +version = 2022.3.0b2 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 From 6fcdd3b411948fefaab1d614915815d223085640 Mon Sep 17 00:00:00 2001 From: kevdliu <1766838+kevdliu@users.noreply.github.com> Date: Fri, 25 Feb 2022 03:44:17 -0500 Subject: [PATCH 1036/1098] Take Abode camera snapshot before fetching latest image (#67150) Co-authored-by: Franck Nijhof Co-authored-by: Franck Nijhof --- homeassistant/components/abode/camera.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/abode/camera.py b/homeassistant/components/abode/camera.py index 9885ccb54ef2ec..1eb6f859d0c80e 100644 --- a/homeassistant/components/abode/camera.py +++ b/homeassistant/components/abode/camera.py @@ -88,6 +88,8 @@ def camera_image( self, width: int | None = None, height: int | None = None ) -> bytes | None: """Get a camera image.""" + if not self.capture(): + return None self.refresh_image() if self._response: From b572d10e4284ab91e92dd2c33d9a09aa7c9e557f Mon Sep 17 00:00:00 2001 From: Mark Dietzer Date: Fri, 25 Feb 2022 09:15:49 -0800 Subject: [PATCH 1037/1098] Fix Twitch component to use new API (#67153) Co-authored-by: Paulus Schoutsen --- homeassistant/components/twitch/__init__.py | 2 +- homeassistant/components/twitch/manifest.json | 2 +- homeassistant/components/twitch/sensor.py | 112 +++++++++----- requirements_all.txt | 6 +- requirements_test_all.txt | 6 +- tests/components/twitch/test_twitch.py | 138 +++++++++++------- 6 files changed, 164 insertions(+), 102 deletions(-) diff --git a/homeassistant/components/twitch/__init__.py b/homeassistant/components/twitch/__init__.py index 0cdeb8139450bd..64feb17d6b5c97 100644 --- a/homeassistant/components/twitch/__init__.py +++ b/homeassistant/components/twitch/__init__.py @@ -1 +1 @@ -"""The twitch component.""" +"""The Twitch component.""" diff --git a/homeassistant/components/twitch/manifest.json b/homeassistant/components/twitch/manifest.json index 17f1c8586c012d..ef68ba945187f6 100644 --- a/homeassistant/components/twitch/manifest.json +++ b/homeassistant/components/twitch/manifest.json @@ -2,7 +2,7 @@ "domain": "twitch", "name": "Twitch", "documentation": "https://www.home-assistant.io/integrations/twitch", - "requirements": ["python-twitch-client==0.6.0"], + "requirements": ["twitchAPI==2.5.2"], "codeowners": [], "iot_class": "cloud_polling", "loggers": ["twitch"] diff --git a/homeassistant/components/twitch/sensor.py b/homeassistant/components/twitch/sensor.py index b3357d331bdefd..771f88f0ef11b6 100644 --- a/homeassistant/components/twitch/sensor.py +++ b/homeassistant/components/twitch/sensor.py @@ -3,12 +3,18 @@ import logging -from requests.exceptions import HTTPError -from twitch import TwitchClient +from twitchAPI.twitch import ( + AuthScope, + AuthType, + InvalidTokenException, + MissingScopeException, + Twitch, + TwitchAuthorizationException, +) import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity -from homeassistant.const import CONF_CLIENT_ID, CONF_TOKEN +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_TOKEN from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -33,9 +39,12 @@ STATE_OFFLINE = "offline" STATE_STREAMING = "streaming" +OAUTH_SCOPES = [AuthScope.USER_READ_SUBSCRIPTIONS] + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_CLIENT_ID): cv.string, + vol.Required(CONF_CLIENT_SECRET): cv.string, vol.Required(CONF_CHANNELS): vol.All(cv.ensure_list, [cv.string]), vol.Optional(CONF_TOKEN): cv.string, } @@ -51,28 +60,45 @@ def setup_platform( """Set up the Twitch platform.""" channels = config[CONF_CHANNELS] client_id = config[CONF_CLIENT_ID] + client_secret = config[CONF_CLIENT_SECRET] oauth_token = config.get(CONF_TOKEN) - client = TwitchClient(client_id, oauth_token) + client = Twitch(app_id=client_id, app_secret=client_secret) + client.auto_refresh_auth = False try: - client.ingests.get_server_list() - except HTTPError: - _LOGGER.error("Client ID or OAuth token is not valid") + client.authenticate_app(scope=OAUTH_SCOPES) + except TwitchAuthorizationException: + _LOGGER.error("INvalid client ID or client secret") return - channel_ids = client.users.translate_usernames_to_ids(channels) + if oauth_token: + try: + client.set_user_authentication( + token=oauth_token, scope=OAUTH_SCOPES, validate=True + ) + except MissingScopeException: + _LOGGER.error("OAuth token is missing required scope") + return + except InvalidTokenException: + _LOGGER.error("OAuth token is invalid") + return + + channels = client.get_users(logins=channels) - add_entities([TwitchSensor(channel_id, client) for channel_id in channel_ids], True) + add_entities( + [TwitchSensor(channel=channel, client=client) for channel in channels["data"]], + True, + ) class TwitchSensor(SensorEntity): """Representation of an Twitch channel.""" - def __init__(self, channel, client): + def __init__(self, channel, client: Twitch): """Initialize the sensor.""" self._client = client self._channel = channel - self._oauth_enabled = client._oauth_token is not None + self._enable_user_auth = client.has_required_auth(AuthType.USER, OAUTH_SCOPES) self._state = None self._preview = None self._game = None @@ -84,7 +110,7 @@ def __init__(self, channel, client): @property def name(self): """Return the name of the sensor.""" - return self._channel.display_name + return self._channel["display_name"] @property def native_value(self): @@ -101,7 +127,7 @@ def extra_state_attributes(self): """Return the state attributes.""" attr = dict(self._statistics) - if self._oauth_enabled: + if self._enable_user_auth: attr.update(self._subscription) attr.update(self._follow) @@ -112,7 +138,7 @@ def extra_state_attributes(self): @property def unique_id(self): """Return unique ID for this sensor.""" - return self._channel.id + return self._channel["id"] @property def icon(self): @@ -122,41 +148,51 @@ def icon(self): def update(self): """Update device state.""" - channel = self._client.channels.get_by_id(self._channel.id) + followers = self._client.get_users_follows(to_id=self._channel["id"])["total"] + channel = self._client.get_users(user_ids=[self._channel["id"]])["data"][0] self._statistics = { - ATTR_FOLLOWING: channel.followers, - ATTR_VIEWS: channel.views, + ATTR_FOLLOWING: followers, + ATTR_VIEWS: channel["view_count"], } - if self._oauth_enabled: - user = self._client.users.get() + if self._enable_user_auth: + user = self._client.get_users()["data"][0] - try: - sub = self._client.users.check_subscribed_to_channel( - user.id, self._channel.id - ) + subs = self._client.check_user_subscription( + user_id=user["id"], broadcaster_id=self._channel["id"] + ) + if "data" in subs: self._subscription = { ATTR_SUBSCRIPTION: True, - ATTR_SUBSCRIPTION_SINCE: sub.created_at, - ATTR_SUBSCRIPTION_GIFTED: sub.is_gift, + ATTR_SUBSCRIPTION_GIFTED: subs["data"][0]["is_gift"], } - except HTTPError: + elif "status" in subs and subs["status"] == 404: self._subscription = {ATTR_SUBSCRIPTION: False} - - try: - follow = self._client.users.check_follows_channel( - user.id, self._channel.id + elif "error" in subs: + raise Exception( + f"Error response on check_user_subscription: {subs['error']}" ) - self._follow = {ATTR_FOLLOW: True, ATTR_FOLLOW_SINCE: follow.created_at} - except HTTPError: + else: + raise Exception("Unknown error response on check_user_subscription") + + follows = self._client.get_users_follows( + from_id=user["id"], to_id=self._channel["id"] + )["data"] + if len(follows) > 0: + self._follow = { + ATTR_FOLLOW: True, + ATTR_FOLLOW_SINCE: follows[0]["followed_at"], + } + else: self._follow = {ATTR_FOLLOW: False} - stream = self._client.streams.get_stream_by_user(self._channel.id) - if stream: - self._game = stream.channel.get("game") - self._title = stream.channel.get("status") - self._preview = stream.preview.get("medium") + streams = self._client.get_streams(user_id=[self._channel["id"]])["data"] + if len(streams) > 0: + stream = streams[0] + self._game = stream["game_name"] + self._title = stream["title"] + self._preview = stream["thumbnail_url"] self._state = STATE_STREAMING else: - self._preview = self._channel.logo + self._preview = channel["offline_image_url"] self._state = STATE_OFFLINE diff --git a/requirements_all.txt b/requirements_all.txt index bf43d648d5dfd1..82f12c9c57eef1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1987,9 +1987,6 @@ python-tado==0.12.0 # homeassistant.components.telegram_bot python-telegram-bot==13.1 -# homeassistant.components.twitch -python-twitch-client==0.6.0 - # homeassistant.components.vlc python-vlc==1.1.2 @@ -2395,6 +2392,9 @@ twentemilieu==0.5.0 # homeassistant.components.twilio twilio==6.32.0 +# homeassistant.components.twitch +twitchAPI==2.5.2 + # homeassistant.components.rainforest_eagle uEagle==0.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8484a2c12d3a12..aa09fe716239ce 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1239,9 +1239,6 @@ python-songpal==0.14 # homeassistant.components.tado python-tado==0.12.0 -# homeassistant.components.twitch -python-twitch-client==0.6.0 - # homeassistant.components.awair python_awair==0.2.1 @@ -1471,6 +1468,9 @@ twentemilieu==0.5.0 # homeassistant.components.twilio twilio==6.32.0 +# homeassistant.components.twitch +twitchAPI==2.5.2 + # homeassistant.components.rainforest_eagle uEagle==0.0.2 diff --git a/tests/components/twitch/test_twitch.py b/tests/components/twitch/test_twitch.py index 310be91c7961c0..bfffeb4ae7f1a5 100644 --- a/tests/components/twitch/test_twitch.py +++ b/tests/components/twitch/test_twitch.py @@ -1,11 +1,8 @@ """The tests for an update of the Twitch component.""" from unittest.mock import MagicMock, patch -from requests import HTTPError -from twitch.resources import Channel, Follow, Stream, Subscription, User - from homeassistant.components import sensor -from homeassistant.const import CONF_CLIENT_ID +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.setup import async_setup_component ENTITY_ID = "sensor.channel123" @@ -13,6 +10,7 @@ sensor.DOMAIN: { "platform": "twitch", CONF_CLIENT_ID: "1234", + CONF_CLIENT_SECRET: " abcd", "channels": ["channel123"], } } @@ -20,39 +18,46 @@ sensor.DOMAIN: { "platform": "twitch", CONF_CLIENT_ID: "1234", + CONF_CLIENT_SECRET: "abcd", "channels": ["channel123"], "token": "9876", } } -USER_ID = User({"id": 123, "display_name": "channel123", "logo": "logo.png"}) -STREAM_OBJECT_ONLINE = Stream( - { - "channel": {"game": "Good Game", "status": "Title"}, - "preview": {"medium": "stream-medium.png"}, - } -) -CHANNEL_OBJECT = Channel({"followers": 42, "views": 24}) -OAUTH_USER_ID = User({"id": 987}) -SUB_ACTIVE = Subscription({"created_at": "2020-01-20T21:22:42", "is_gift": False}) -FOLLOW_ACTIVE = Follow({"created_at": "2020-01-20T21:22:42"}) +USER_OBJECT = { + "id": 123, + "display_name": "channel123", + "offline_image_url": "logo.png", + "view_count": 42, +} +STREAM_OBJECT_ONLINE = { + "game_name": "Good Game", + "title": "Title", + "thumbnail_url": "stream-medium.png", +} + +FOLLOWERS_OBJECT = [{"followed_at": "2020-01-20T21:22:42"}] * 24 +OAUTH_USER_ID = {"id": 987} +SUB_ACTIVE = {"is_gift": False} +FOLLOW_ACTIVE = {"followed_at": "2020-01-20T21:22:42"} + + +def make_data(data): + """Create a data object.""" + return {"data": data, "total": len(data)} async def test_init(hass): """Test initial config.""" - channels = MagicMock() - channels.get_by_id.return_value = CHANNEL_OBJECT - streams = MagicMock() - streams.get_stream_by_user.return_value = None - twitch_mock = MagicMock() - twitch_mock.users.translate_usernames_to_ids.return_value = [USER_ID] - twitch_mock.channels = channels - twitch_mock.streams = streams + twitch_mock.get_streams.return_value = make_data([]) + twitch_mock.get_users.return_value = make_data([USER_OBJECT]) + twitch_mock.get_users_follows.return_value = make_data(FOLLOWERS_OBJECT) + twitch_mock.has_required_auth.return_value = False with patch( - "homeassistant.components.twitch.sensor.TwitchClient", return_value=twitch_mock + "homeassistant.components.twitch.sensor.Twitch", return_value=twitch_mock ): assert await async_setup_component(hass, sensor.DOMAIN, CONFIG) is True await hass.async_block_till_done() @@ -62,20 +67,21 @@ async def test_init(hass): assert sensor_state.name == "channel123" assert sensor_state.attributes["icon"] == "mdi:twitch" assert sensor_state.attributes["friendly_name"] == "channel123" - assert sensor_state.attributes["views"] == 24 - assert sensor_state.attributes["followers"] == 42 + assert sensor_state.attributes["views"] == 42 + assert sensor_state.attributes["followers"] == 24 async def test_offline(hass): """Test offline state.""" twitch_mock = MagicMock() - twitch_mock.users.translate_usernames_to_ids.return_value = [USER_ID] - twitch_mock.channels.get_by_id.return_value = CHANNEL_OBJECT - twitch_mock.streams.get_stream_by_user.return_value = None + twitch_mock.get_streams.return_value = make_data([]) + twitch_mock.get_users.return_value = make_data([USER_OBJECT]) + twitch_mock.get_users_follows.return_value = make_data(FOLLOWERS_OBJECT) + twitch_mock.has_required_auth.return_value = False with patch( - "homeassistant.components.twitch.sensor.TwitchClient", + "homeassistant.components.twitch.sensor.Twitch", return_value=twitch_mock, ): assert await async_setup_component(hass, sensor.DOMAIN, CONFIG) is True @@ -90,12 +96,13 @@ async def test_streaming(hass): """Test streaming state.""" twitch_mock = MagicMock() - twitch_mock.users.translate_usernames_to_ids.return_value = [USER_ID] - twitch_mock.channels.get_by_id.return_value = CHANNEL_OBJECT - twitch_mock.streams.get_stream_by_user.return_value = STREAM_OBJECT_ONLINE + twitch_mock.get_users.return_value = make_data([USER_OBJECT]) + twitch_mock.get_users_follows.return_value = make_data(FOLLOWERS_OBJECT) + twitch_mock.get_streams.return_value = make_data([STREAM_OBJECT_ONLINE]) + twitch_mock.has_required_auth.return_value = False with patch( - "homeassistant.components.twitch.sensor.TwitchClient", + "homeassistant.components.twitch.sensor.Twitch", return_value=twitch_mock, ): assert await async_setup_component(hass, sensor.DOMAIN, CONFIG) is True @@ -112,15 +119,21 @@ async def test_oauth_without_sub_and_follow(hass): """Test state with oauth.""" twitch_mock = MagicMock() - twitch_mock.users.translate_usernames_to_ids.return_value = [USER_ID] - twitch_mock.channels.get_by_id.return_value = CHANNEL_OBJECT - twitch_mock._oauth_token = True # A replacement for the token - twitch_mock.users.get.return_value = OAUTH_USER_ID - twitch_mock.users.check_subscribed_to_channel.side_effect = HTTPError() - twitch_mock.users.check_follows_channel.side_effect = HTTPError() + twitch_mock.get_streams.return_value = make_data([]) + twitch_mock.get_users.side_effect = [ + make_data([USER_OBJECT]), + make_data([USER_OBJECT]), + make_data([OAUTH_USER_ID]), + ] + twitch_mock.get_users_follows.side_effect = [ + make_data(FOLLOWERS_OBJECT), + make_data([]), + ] + twitch_mock.has_required_auth.return_value = True + twitch_mock.check_user_subscription.return_value = {"status": 404} with patch( - "homeassistant.components.twitch.sensor.TwitchClient", + "homeassistant.components.twitch.sensor.Twitch", return_value=twitch_mock, ): assert await async_setup_component(hass, sensor.DOMAIN, CONFIG_WITH_OAUTH) @@ -135,15 +148,23 @@ async def test_oauth_with_sub(hass): """Test state with oauth and sub.""" twitch_mock = MagicMock() - twitch_mock.users.translate_usernames_to_ids.return_value = [USER_ID] - twitch_mock.channels.get_by_id.return_value = CHANNEL_OBJECT - twitch_mock._oauth_token = True # A replacement for the token - twitch_mock.users.get.return_value = OAUTH_USER_ID - twitch_mock.users.check_subscribed_to_channel.return_value = SUB_ACTIVE - twitch_mock.users.check_follows_channel.side_effect = HTTPError() + twitch_mock.get_streams.return_value = make_data([]) + twitch_mock.get_users.side_effect = [ + make_data([USER_OBJECT]), + make_data([USER_OBJECT]), + make_data([OAUTH_USER_ID]), + ] + twitch_mock.get_users_follows.side_effect = [ + make_data(FOLLOWERS_OBJECT), + make_data([]), + ] + twitch_mock.has_required_auth.return_value = True + + # This function does not return an array so use make_data + twitch_mock.check_user_subscription.return_value = make_data([SUB_ACTIVE]) with patch( - "homeassistant.components.twitch.sensor.TwitchClient", + "homeassistant.components.twitch.sensor.Twitch", return_value=twitch_mock, ): assert await async_setup_component(hass, sensor.DOMAIN, CONFIG_WITH_OAUTH) @@ -151,7 +172,6 @@ async def test_oauth_with_sub(hass): sensor_state = hass.states.get(ENTITY_ID) assert sensor_state.attributes["subscribed"] is True - assert sensor_state.attributes["subscribed_since"] == "2020-01-20T21:22:42" assert sensor_state.attributes["subscription_is_gifted"] is False assert sensor_state.attributes["following"] is False @@ -160,15 +180,21 @@ async def test_oauth_with_follow(hass): """Test state with oauth and follow.""" twitch_mock = MagicMock() - twitch_mock.users.translate_usernames_to_ids.return_value = [USER_ID] - twitch_mock.channels.get_by_id.return_value = CHANNEL_OBJECT - twitch_mock._oauth_token = True # A replacement for the token - twitch_mock.users.get.return_value = OAUTH_USER_ID - twitch_mock.users.check_subscribed_to_channel.side_effect = HTTPError() - twitch_mock.users.check_follows_channel.return_value = FOLLOW_ACTIVE + twitch_mock.get_streams.return_value = make_data([]) + twitch_mock.get_users.side_effect = [ + make_data([USER_OBJECT]), + make_data([USER_OBJECT]), + make_data([OAUTH_USER_ID]), + ] + twitch_mock.get_users_follows.side_effect = [ + make_data(FOLLOWERS_OBJECT), + make_data([FOLLOW_ACTIVE]), + ] + twitch_mock.has_required_auth.return_value = True + twitch_mock.check_user_subscription.return_value = {"status": 404} with patch( - "homeassistant.components.twitch.sensor.TwitchClient", + "homeassistant.components.twitch.sensor.Twitch", return_value=twitch_mock, ): assert await async_setup_component(hass, sensor.DOMAIN, CONFIG_WITH_OAUTH) From 53632cc9e5a55c568813d88337f333abc81edeb7 Mon Sep 17 00:00:00 2001 From: martijnvanduijneveldt Date: Fri, 25 Feb 2022 17:25:13 +0100 Subject: [PATCH 1038/1098] Fix nanoleaf white flashing when using scenes (#67168) --- homeassistant/components/nanoleaf/light.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/nanoleaf/light.py b/homeassistant/components/nanoleaf/light.py index 29c7cb786e6e58..ed3476c4576acb 100644 --- a/homeassistant/components/nanoleaf/light.py +++ b/homeassistant/components/nanoleaf/light.py @@ -153,11 +153,17 @@ async def async_turn_on(self, **kwargs: Any) -> None: effect = kwargs.get(ATTR_EFFECT) transition = kwargs.get(ATTR_TRANSITION) - if hs_color: + if effect: + if effect not in self.effect_list: + raise ValueError( + f"Attempting to apply effect not in the effect list: '{effect}'" + ) + await self._nanoleaf.set_effect(effect) + elif hs_color: hue, saturation = hs_color await self._nanoleaf.set_hue(int(hue)) await self._nanoleaf.set_saturation(int(saturation)) - if color_temp_mired: + elif color_temp_mired: await self._nanoleaf.set_color_temperature( mired_to_kelvin(color_temp_mired) ) @@ -172,12 +178,6 @@ async def async_turn_on(self, **kwargs: Any) -> None: await self._nanoleaf.turn_on() if brightness: await self._nanoleaf.set_brightness(int(brightness / 2.55)) - if effect: - if effect not in self.effect_list: - raise ValueError( - f"Attempting to apply effect not in the effect list: '{effect}'" - ) - await self._nanoleaf.set_effect(effect) async def async_turn_off(self, **kwargs: Any) -> None: """Instruct the light to turn off.""" From 73eff0dde42b8e9307576460c7a1237b53884621 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Fri, 25 Feb 2022 10:27:06 -0600 Subject: [PATCH 1039/1098] Adjust Sonos visibility checks (#67196) --- homeassistant/components/sonos/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py index 71068479fe46be..27d51d8f3e63a2 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -182,6 +182,9 @@ def _create_soco(self, ip_address: str, source: SoCoCreationSource) -> SoCo | No soco = SoCo(ip_address) # Ensure that the player is available and UID is cached uid = soco.uid + # Abort early if the device is not visible + if not soco.is_visible: + return None _ = soco.volume return soco except NotSupportedException as exc: @@ -240,8 +243,7 @@ def _manual_hosts(self, now: datetime.datetime | None = None) -> None: None, ) if not known_uid: - soco = self._create_soco(ip_addr, SoCoCreationSource.CONFIGURED) - if soco and soco.is_visible: + if soco := self._create_soco(ip_addr, SoCoCreationSource.CONFIGURED): self._discovered_player(soco) self.data.hosts_heartbeat = call_later( @@ -249,8 +251,7 @@ def _manual_hosts(self, now: datetime.datetime | None = None) -> None: ) def _discovered_ip(self, ip_address): - soco = self._create_soco(ip_address, SoCoCreationSource.DISCOVERED) - if soco and soco.is_visible: + if soco := self._create_soco(ip_address, SoCoCreationSource.DISCOVERED): self._discovered_player(soco) async def _async_create_discovered_player(self, uid, discovered_ip, boot_seqnum): From 51771707fb31122382dfd26fc11a0c730092d722 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 24 Feb 2022 23:40:28 -0800 Subject: [PATCH 1040/1098] Add media source support to Kodi (#67203) --- homeassistant/components/kodi/browse_media.py | 12 ++++++++- homeassistant/components/kodi/manifest.json | 1 + homeassistant/components/kodi/media_player.py | 26 ++++++++++++++++--- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/kodi/browse_media.py b/homeassistant/components/kodi/browse_media.py index 1b0c5d521c9f56..e0fdb0f73fdc20 100644 --- a/homeassistant/components/kodi/browse_media.py +++ b/homeassistant/components/kodi/browse_media.py @@ -1,7 +1,9 @@ """Support for media browsing.""" import asyncio +import contextlib import logging +from homeassistant.components import media_source from homeassistant.components.media_player import BrowseError, BrowseMedia from homeassistant.components.media_player.const import ( MEDIA_CLASS_ALBUM, @@ -184,7 +186,7 @@ async def item_payload(item, get_thumbnail_url=None): ) -async def library_payload(): +async def library_payload(hass): """ Create response payload to describe contents of a specific library. @@ -222,6 +224,14 @@ async def library_payload(): ) ) + with contextlib.suppress(media_source.BrowseError): + item = await media_source.async_browse_media(hass, None) + # If domain is None, it's overview of available sources + if item.domain is None: + library_info.children.extend(item.children) + else: + library_info.children.append(item) + return library_info diff --git a/homeassistant/components/kodi/manifest.json b/homeassistant/components/kodi/manifest.json index 3a39a7870a3276..86034ea9cfc615 100644 --- a/homeassistant/components/kodi/manifest.json +++ b/homeassistant/components/kodi/manifest.json @@ -2,6 +2,7 @@ "domain": "kodi", "name": "Kodi", "documentation": "https://www.home-assistant.io/integrations/kodi", + "after_dependencies": ["media_source"], "requirements": ["pykodi==0.2.7"], "codeowners": ["@OnFreund", "@cgtobi"], "zeroconf": ["_xbmc-jsonrpc-h._tcp.local."], diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py index 5067ee84826e85..56b0abb6a1539b 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -5,6 +5,7 @@ from functools import wraps import logging import re +from typing import Any import urllib.parse import jsonrpc_base @@ -12,7 +13,11 @@ from pykodi import CannotConnectError import voluptuous as vol +from homeassistant.components import media_source from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity +from homeassistant.components.media_player.browse_media import ( + async_process_play_media_url, +) from homeassistant.components.media_player.const import ( MEDIA_TYPE_ALBUM, MEDIA_TYPE_ARTIST, @@ -24,6 +29,7 @@ MEDIA_TYPE_SEASON, MEDIA_TYPE_TRACK, MEDIA_TYPE_TVSHOW, + MEDIA_TYPE_URL, MEDIA_TYPE_VIDEO, SUPPORT_BROWSE_MEDIA, SUPPORT_NEXT_TRACK, @@ -691,8 +697,15 @@ async def async_media_seek(self, position): await self._kodi.media_seek(position) @cmd - async def async_play_media(self, media_type, media_id, **kwargs): + async def async_play_media( + self, media_type: str, media_id: str, **kwargs: Any + ) -> None: """Send the play_media command to the media player.""" + if media_source.is_media_source_id(media_id): + media_type = MEDIA_TYPE_URL + play_item = await media_source.async_resolve_media(self.hass, media_id) + media_id = play_item.url + media_type_lower = media_type.lower() if media_type_lower == MEDIA_TYPE_CHANNEL: @@ -700,7 +713,7 @@ async def async_play_media(self, media_type, media_id, **kwargs): elif media_type_lower == MEDIA_TYPE_PLAYLIST: await self._kodi.play_playlist(int(media_id)) elif media_type_lower == "directory": - await self._kodi.play_directory(str(media_id)) + await self._kodi.play_directory(media_id) elif media_type_lower in [ MEDIA_TYPE_ARTIST, MEDIA_TYPE_ALBUM, @@ -719,7 +732,9 @@ async def async_play_media(self, media_type, media_id, **kwargs): {MAP_KODI_MEDIA_TYPES[media_type_lower]: int(media_id)} ) else: - await self._kodi.play_file(str(media_id)) + media_id = async_process_play_media_url(self.hass, media_id) + + await self._kodi.play_file(media_id) @cmd async def async_set_shuffle(self, shuffle): @@ -898,7 +913,10 @@ async def _get_thumbnail_url( ) if media_content_type in [None, "library"]: - return await library_payload() + return await library_payload(self.hass) + + if media_source.is_media_source_id(media_content_id): + return await media_source.async_browse_media(self.hass, media_content_id) payload = { "search_type": media_content_type, From 921a011391a4df667f546006f89647fe094cb663 Mon Sep 17 00:00:00 2001 From: Avi Miller Date: Fri, 25 Feb 2022 19:59:16 +1100 Subject: [PATCH 1041/1098] Bump the Twinkly dependency to fix the excessive debug output (#67207) --- homeassistant/components/twinkly/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/twinkly/manifest.json b/homeassistant/components/twinkly/manifest.json index 871cd27166dbac..e3b97e9385b59e 100644 --- a/homeassistant/components/twinkly/manifest.json +++ b/homeassistant/components/twinkly/manifest.json @@ -2,7 +2,7 @@ "domain": "twinkly", "name": "Twinkly", "documentation": "https://www.home-assistant.io/integrations/twinkly", - "requirements": ["ttls==1.4.2"], + "requirements": ["ttls==1.4.3"], "codeowners": ["@dr1rrb", "@Robbie1221"], "config_flow": true, "dhcp": [{ "hostname": "twinkly_*" }], diff --git a/requirements_all.txt b/requirements_all.txt index 82f12c9c57eef1..713cd7dd33e959 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2381,7 +2381,7 @@ tp-connected==0.0.4 transmissionrpc==0.11 # homeassistant.components.twinkly -ttls==1.4.2 +ttls==1.4.3 # homeassistant.components.tuya tuya-iot-py-sdk==0.6.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index aa09fe716239ce..3f94c277f31585 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1457,7 +1457,7 @@ total_connect_client==2022.2.1 transmissionrpc==0.11 # homeassistant.components.twinkly -ttls==1.4.2 +ttls==1.4.3 # homeassistant.components.tuya tuya-iot-py-sdk==0.6.6 From 549756218bfe83fa626d2881417c915b1e91e202 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 25 Feb 2022 01:39:30 -0500 Subject: [PATCH 1042/1098] Don't add extra entities for zwave_js controller (#67209) * Don't add extra entities for zwave_js controller * Revert reformat of controller_state * fix indentation issues * fix indentation issues --- homeassistant/components/zwave_js/__init__.py | 22 ++-- tests/components/zwave_js/conftest.py | 14 +++ .../fixtures/aeon_smart_switch_6_state.json | 3 +- .../aeotec_radiator_thermostat_state.json | 3 +- .../fixtures/aeotec_zw164_siren_state.json | 3 +- .../fixtures/bulb_6_multi_color_state.json | 3 +- .../fixtures/chain_actuator_zws12_state.json | 3 +- .../fixtures/climate_danfoss_lc_13_state.json | 3 +- .../climate_eurotronic_spirit_z_state.json | 3 +- .../climate_heatit_z_trm2fx_state.json | 3 +- .../climate_heatit_z_trm3_no_value_state.json | 3 +- .../fixtures/climate_heatit_z_trm3_state.json | 3 +- ...setpoint_on_different_endpoints_state.json | 3 +- ..._ct100_plus_different_endpoints_state.json | 3 +- ...ate_radio_thermostat_ct100_plus_state.json | 3 +- ...ostat_ct101_multiple_temp_units_state.json | 3 +- .../fixtures/controller_node_state.json | 104 ++++++++++++++++++ .../cover_aeotec_nano_shutter_state.json | 3 +- .../fixtures/cover_fibaro_fgr222_state.json | 3 +- .../fixtures/cover_iblinds_v2_state.json | 3 +- .../fixtures/cover_qubino_shutter_state.json | 3 +- .../zwave_js/fixtures/cover_zw062_state.json | 3 +- .../fixtures/eaton_rf9640_dimmer_state.json | 3 +- .../fixtures/ecolink_door_sensor_state.json | 3 +- .../zwave_js/fixtures/fan_ge_12730_state.json | 3 +- .../zwave_js/fixtures/fan_generic_state.json | 3 +- .../zwave_js/fixtures/fan_hs_fc200_state.json | 3 +- .../fixtures/fortrezz_ssa1_siren_state.json | 3 +- .../fixtures/fortrezz_ssa3_siren_state.json | 3 +- .../ge_in_wall_dimmer_switch_state.json | 3 +- .../fixtures/hank_binary_switch_state.json | 3 +- .../fixtures/inovelli_lzw36_state.json | 3 +- .../light_color_null_values_state.json | 3 +- .../fixtures/lock_august_asl03_state.json | 3 +- .../fixtures/lock_id_lock_as_id150_state.json | 3 +- ...pp_electric_strike_lock_control_state.json | 3 +- .../fixtures/lock_schlage_be469_state.json | 3 +- .../nortek_thermostat_added_event.json | 3 +- .../fixtures/nortek_thermostat_state.json | 3 +- .../fixtures/null_name_check_state.json | 3 +- .../fixtures/srt321_hrt4_zw_state.json | 3 +- .../vision_security_zl7432_state.json | 3 +- .../wallmote_central_scene_state.json | 3 +- .../zwave_js/fixtures/zen_31_state.json | 3 +- .../fixtures/zp3111-5_not_ready_state.json | 3 +- .../zwave_js/fixtures/zp3111-5_state.json | 3 +- tests/components/zwave_js/test_button.py | 13 +++ tests/components/zwave_js/test_init.py | 2 + tests/components/zwave_js/test_sensor.py | 17 ++- 49 files changed, 247 insertions(+), 54 deletions(-) create mode 100644 tests/components/zwave_js/fixtures/controller_node_state.json diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 5a0a7bbf29ff71..5d294931e6626a 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -293,17 +293,19 @@ async def async_on_node_ready(node: ZwaveNode) -> None: async def async_on_node_added(node: ZwaveNode) -> None: """Handle node added event.""" - # Create a node status sensor for each device - await async_setup_platform(SENSOR_DOMAIN) - async_dispatcher_send( - hass, f"{DOMAIN}_{entry.entry_id}_add_node_status_sensor", node - ) + # No need for a ping button or node status sensor for controller nodes + if not node.is_controller_node: + # Create a node status sensor for each device + await async_setup_platform(SENSOR_DOMAIN) + async_dispatcher_send( + hass, f"{DOMAIN}_{entry.entry_id}_add_node_status_sensor", node + ) - # Create a ping button for each device - await async_setup_platform(BUTTON_DOMAIN) - async_dispatcher_send( - hass, f"{DOMAIN}_{entry.entry_id}_add_ping_button_entity", node - ) + # Create a ping button for each device + await async_setup_platform(BUTTON_DOMAIN) + async_dispatcher_send( + hass, f"{DOMAIN}_{entry.entry_id}_add_ping_button_entity", node + ) # we only want to run discovery when the node has reached ready state, # otherwise we'll have all kinds of missing info issues. diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 2535daaf1141af..9696c922fb3c6c 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -181,6 +181,12 @@ def controller_state_fixture(): return json.loads(load_fixture("zwave_js/controller_state.json")) +@pytest.fixture(name="controller_node_state", scope="session") +def controller_node_state_fixture(): + """Load the controller node state fixture data.""" + return json.loads(load_fixture("zwave_js/controller_node_state.json")) + + @pytest.fixture(name="version_state", scope="session") def version_state_fixture(): """Load the version state fixture data.""" @@ -535,6 +541,14 @@ async def disconnect(): yield client +@pytest.fixture(name="controller_node") +def controller_node_fixture(client, controller_node_state): + """Mock a controller node.""" + node = Node(client, copy.deepcopy(controller_node_state)) + client.driver.controller.nodes[node.node_id] = node + return node + + @pytest.fixture(name="multisensor_6") def multisensor_6_fixture(client, multisensor_6_state): """Mock a multisensor 6 node.""" diff --git a/tests/components/zwave_js/fixtures/aeon_smart_switch_6_state.json b/tests/components/zwave_js/fixtures/aeon_smart_switch_6_state.json index 1eb2baf3a0252a..bf547556ac84ea 100644 --- a/tests/components/zwave_js/fixtures/aeon_smart_switch_6_state.json +++ b/tests/components/zwave_js/fixtures/aeon_smart_switch_6_state.json @@ -1245,5 +1245,6 @@ "label": "Z-Wave chip hardware version" } } - ] + ], + "isControllerNode": false } \ No newline at end of file diff --git a/tests/components/zwave_js/fixtures/aeotec_radiator_thermostat_state.json b/tests/components/zwave_js/fixtures/aeotec_radiator_thermostat_state.json index 646363e331336e..cbd11c668701f3 100644 --- a/tests/components/zwave_js/fixtures/aeotec_radiator_thermostat_state.json +++ b/tests/components/zwave_js/fixtures/aeotec_radiator_thermostat_state.json @@ -616,5 +616,6 @@ "label": "Z-Wave chip hardware version" } } - ] + ], + "isControllerNode": false } \ No newline at end of file diff --git a/tests/components/zwave_js/fixtures/aeotec_zw164_siren_state.json b/tests/components/zwave_js/fixtures/aeotec_zw164_siren_state.json index 5616abd6e0f7f7..59e4fdfc9fb6e5 100644 --- a/tests/components/zwave_js/fixtures/aeotec_zw164_siren_state.json +++ b/tests/components/zwave_js/fixtures/aeotec_zw164_siren_state.json @@ -3752,5 +3752,6 @@ } ], "interviewStage": "Complete", - "deviceDatabaseUrl": "https://devices.zwave-js.io/?jumpTo=0x0371:0x0103:0x00a4:1.3" + "deviceDatabaseUrl": "https://devices.zwave-js.io/?jumpTo=0x0371:0x0103:0x00a4:1.3", + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/bulb_6_multi_color_state.json b/tests/components/zwave_js/fixtures/bulb_6_multi_color_state.json index 668d1b9dce9980..b0dba3c6d0523f 100644 --- a/tests/components/zwave_js/fixtures/bulb_6_multi_color_state.json +++ b/tests/components/zwave_js/fixtures/bulb_6_multi_color_state.json @@ -645,5 +645,6 @@ "label": "Z-Wave chip hardware version" } } - ] + ], + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/chain_actuator_zws12_state.json b/tests/components/zwave_js/fixtures/chain_actuator_zws12_state.json index a726175fa38e1a..2b8477b597fbee 100644 --- a/tests/components/zwave_js/fixtures/chain_actuator_zws12_state.json +++ b/tests/components/zwave_js/fixtures/chain_actuator_zws12_state.json @@ -397,5 +397,6 @@ }, "value": 0 } - ] + ], + "isControllerNode": false } \ No newline at end of file diff --git a/tests/components/zwave_js/fixtures/climate_danfoss_lc_13_state.json b/tests/components/zwave_js/fixtures/climate_danfoss_lc_13_state.json index 8574674714f073..206a32df6640f5 100644 --- a/tests/components/zwave_js/fixtures/climate_danfoss_lc_13_state.json +++ b/tests/components/zwave_js/fixtures/climate_danfoss_lc_13_state.json @@ -432,5 +432,6 @@ "1.1" ] } - ] + ], + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/climate_eurotronic_spirit_z_state.json b/tests/components/zwave_js/fixtures/climate_eurotronic_spirit_z_state.json index afa216cac32acc..1241e0b35d745c 100644 --- a/tests/components/zwave_js/fixtures/climate_eurotronic_spirit_z_state.json +++ b/tests/components/zwave_js/fixtures/climate_eurotronic_spirit_z_state.json @@ -712,5 +712,6 @@ "label": "Z-Wave chip hardware version" } } - ] + ], + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/climate_heatit_z_trm2fx_state.json b/tests/components/zwave_js/fixtures/climate_heatit_z_trm2fx_state.json index 2526e346a5362f..8c655d503ed796 100644 --- a/tests/components/zwave_js/fixtures/climate_heatit_z_trm2fx_state.json +++ b/tests/components/zwave_js/fixtures/climate_heatit_z_trm2fx_state.json @@ -1440,5 +1440,6 @@ "isSecure": false } ], - "interviewStage": "Complete" + "interviewStage": "Complete", + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/climate_heatit_z_trm3_no_value_state.json b/tests/components/zwave_js/fixtures/climate_heatit_z_trm3_no_value_state.json index 50886b504a7133..75d8bb99e5551c 100644 --- a/tests/components/zwave_js/fixtures/climate_heatit_z_trm3_no_value_state.json +++ b/tests/components/zwave_js/fixtures/climate_heatit_z_trm3_no_value_state.json @@ -1246,5 +1246,6 @@ "isSecure": true } ], - "interviewStage": "Complete" + "interviewStage": "Complete", + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/climate_heatit_z_trm3_state.json b/tests/components/zwave_js/fixtures/climate_heatit_z_trm3_state.json index 98c185fd8d5b9d..0ac4c6ab696ed6 100644 --- a/tests/components/zwave_js/fixtures/climate_heatit_z_trm3_state.json +++ b/tests/components/zwave_js/fixtures/climate_heatit_z_trm3_state.json @@ -1173,5 +1173,6 @@ }, "value": 25.5 } - ] + ], + "isControllerNode": false } \ No newline at end of file diff --git a/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct100_mode_and_setpoint_on_different_endpoints_state.json b/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct100_mode_and_setpoint_on_different_endpoints_state.json index 52f2168fd83c47..8bfe3a3f7af9da 100644 --- a/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct100_mode_and_setpoint_on_different_endpoints_state.json +++ b/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct100_mode_and_setpoint_on_different_endpoints_state.json @@ -826,5 +826,6 @@ "version": 1, "isSecure": false } - ] + ], + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct100_plus_different_endpoints_state.json b/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct100_plus_different_endpoints_state.json index f940dd210aa085..398371a7445429 100644 --- a/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct100_plus_different_endpoints_state.json +++ b/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct100_plus_different_endpoints_state.json @@ -1083,5 +1083,6 @@ "version": 3, "isSecure": false } - ] + ], + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct100_plus_state.json b/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct100_plus_state.json index 3805394dbce9b3..b81acf66b803d5 100644 --- a/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct100_plus_state.json +++ b/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct100_plus_state.json @@ -851,5 +851,6 @@ }, "value": false } - ] + ], + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct101_multiple_temp_units_state.json b/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct101_multiple_temp_units_state.json index 5feaa247f2ef97..5c8a12a683268a 100644 --- a/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct101_multiple_temp_units_state.json +++ b/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct101_multiple_temp_units_state.json @@ -958,5 +958,6 @@ "version": 1, "isSecure": false } - ] + ], + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/controller_node_state.json b/tests/components/zwave_js/fixtures/controller_node_state.json new file mode 100644 index 00000000000000..1f3c71971bc655 --- /dev/null +++ b/tests/components/zwave_js/fixtures/controller_node_state.json @@ -0,0 +1,104 @@ +{ + "nodeId": 1, + "index": 0, + "status": 4, + "ready": true, + "isListening": true, + "isRouting": false, + "isSecure": "unknown", + "manufacturerId": 134, + "productId": 90, + "productType": 1, + "firmwareVersion": "1.2", + "deviceConfig": { + "filename": "/data/db/devices/0x0086/zw090.json", + "isEmbedded": true, + "manufacturer": "AEON Labs", + "manufacturerId": 134, + "label": "ZW090", + "description": "Z\u2010Stick Gen5 USB Controller", + "devices": [ + { + "productType": 1, + "productId": 90 + }, + { + "productType": 257, + "productId": 90 + }, + { + "productType": 513, + "productId": 90 + } + ], + "firmwareVersion": { + "min": "0.0", + "max": "255.255" + }, + "associations": {}, + "paramInformation": { + "_map": {} + }, + "metadata": { + "reset": "Use this procedure only in the event that the primary controller is missing or otherwise inoperable.\n\nPress and hold the Action Button on Z-Stick for 20 seconds and then release", + "manual": "https://products.z-wavealliance.org/ProductManual/File?folder=&filename=MarketCertificationFiles/1345/Z%20Stick%20Gen5%20manual%201.pdf" + } + }, + "label": "ZW090", + "interviewAttempts": 0, + "endpoints": [ + { + "nodeId": 1, + "index": 0, + "deviceClass": { + "basic": { + "key": 2, + "label": "Static Controller" + }, + "generic": { + "key": 2, + "label": "Static Controller" + }, + "specific": { + "key": 1, + "label": "PC Controller" + }, + "mandatorySupportedCCs": [], + "mandatoryControlledCCs": [32] + }, + "commandClasses": [] + } + ], + "values": [], + "isFrequentListening": false, + "maxDataRate": 40000, + "supportedDataRates": [40000], + "protocolVersion": 3, + "deviceClass": { + "basic": { + "key": 2, + "label": "Static Controller" + }, + "generic": { + "key": 2, + "label": "Static Controller" + }, + "specific": { + "key": 1, + "label": "PC Controller" + }, + "mandatorySupportedCCs": [], + "mandatoryControlledCCs": [32] + }, + "interviewStage": "Complete", + "deviceDatabaseUrl": "https://devices.zwave-js.io/?jumpTo=0x0086:0x0001:0x005a:1.2", + "statistics": { + "commandsTX": 0, + "commandsRX": 0, + "commandsDroppedRX": 0, + "commandsDroppedTX": 0, + "timeoutResponse": 0 + }, + "isControllerNode": true, + "keepAwake": false +} diff --git a/tests/components/zwave_js/fixtures/cover_aeotec_nano_shutter_state.json b/tests/components/zwave_js/fixtures/cover_aeotec_nano_shutter_state.json index b5373f38ec447b..7959378a7ad463 100644 --- a/tests/components/zwave_js/fixtures/cover_aeotec_nano_shutter_state.json +++ b/tests/components/zwave_js/fixtures/cover_aeotec_nano_shutter_state.json @@ -494,5 +494,6 @@ } ], "interviewStage": "Complete", - "deviceDatabaseUrl": "https://devices.zwave-js.io/?jumpTo=0x0371:0x0003:0x008d:3.1" + "deviceDatabaseUrl": "https://devices.zwave-js.io/?jumpTo=0x0371:0x0003:0x008d:3.1", + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/cover_fibaro_fgr222_state.json b/tests/components/zwave_js/fixtures/cover_fibaro_fgr222_state.json index 59dff9458464ee..6d4defbd42c438 100644 --- a/tests/components/zwave_js/fixtures/cover_fibaro_fgr222_state.json +++ b/tests/components/zwave_js/fixtures/cover_fibaro_fgr222_state.json @@ -1129,5 +1129,6 @@ "commandsDroppedRX": 1, "commandsDroppedTX": 0, "timeoutResponse": 0 - } + }, + "isControllerNode": false } \ No newline at end of file diff --git a/tests/components/zwave_js/fixtures/cover_iblinds_v2_state.json b/tests/components/zwave_js/fixtures/cover_iblinds_v2_state.json index 42200c2f1d64d7..4d10577a2d1d24 100644 --- a/tests/components/zwave_js/fixtures/cover_iblinds_v2_state.json +++ b/tests/components/zwave_js/fixtures/cover_iblinds_v2_state.json @@ -353,5 +353,6 @@ "label": "Z-Wave chip hardware version" } } - ] + ], + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/cover_qubino_shutter_state.json b/tests/components/zwave_js/fixtures/cover_qubino_shutter_state.json index bde7c90e1e4ce8..913f24d41aeff8 100644 --- a/tests/components/zwave_js/fixtures/cover_qubino_shutter_state.json +++ b/tests/components/zwave_js/fixtures/cover_qubino_shutter_state.json @@ -896,5 +896,6 @@ "commandsDroppedRX": 0, "commandsDroppedTX": 0, "timeoutResponse": 0 - } + }, + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/cover_zw062_state.json b/tests/components/zwave_js/fixtures/cover_zw062_state.json index a0ccd4de9c5da0..47aafdfd0a4246 100644 --- a/tests/components/zwave_js/fixtures/cover_zw062_state.json +++ b/tests/components/zwave_js/fixtures/cover_zw062_state.json @@ -917,5 +917,6 @@ "label": "Z-Wave chip hardware version" } } - ] + ], + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/eaton_rf9640_dimmer_state.json b/tests/components/zwave_js/fixtures/eaton_rf9640_dimmer_state.json index 3edb0494c37fc4..a1806a99ce0f7b 100644 --- a/tests/components/zwave_js/fixtures/eaton_rf9640_dimmer_state.json +++ b/tests/components/zwave_js/fixtures/eaton_rf9640_dimmer_state.json @@ -775,5 +775,6 @@ "value": 0, "ccVersion": 3 } - ] + ], + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/ecolink_door_sensor_state.json b/tests/components/zwave_js/fixtures/ecolink_door_sensor_state.json index ac32a9f99bb8e2..444b7eafc679c1 100644 --- a/tests/components/zwave_js/fixtures/ecolink_door_sensor_state.json +++ b/tests/components/zwave_js/fixtures/ecolink_door_sensor_state.json @@ -325,6 +325,7 @@ "2.0" ] } - ] + ], + "isControllerNode": false } \ No newline at end of file diff --git a/tests/components/zwave_js/fixtures/fan_ge_12730_state.json b/tests/components/zwave_js/fixtures/fan_ge_12730_state.json index ddbff0f3ffa518..fa4c96d439ab7c 100644 --- a/tests/components/zwave_js/fixtures/fan_ge_12730_state.json +++ b/tests/components/zwave_js/fixtures/fan_ge_12730_state.json @@ -427,5 +427,6 @@ "3.10" ] } - ] + ], + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/fan_generic_state.json b/tests/components/zwave_js/fixtures/fan_generic_state.json index d09848fb759f7b..fc89976d14a927 100644 --- a/tests/components/zwave_js/fixtures/fan_generic_state.json +++ b/tests/components/zwave_js/fixtures/fan_generic_state.json @@ -348,5 +348,6 @@ "label": "Z-Wave chip hardware version" } } - ] + ], + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/fan_hs_fc200_state.json b/tests/components/zwave_js/fixtures/fan_hs_fc200_state.json index f83a1193c22954..edab052af5b3b1 100644 --- a/tests/components/zwave_js/fixtures/fan_hs_fc200_state.json +++ b/tests/components/zwave_js/fixtures/fan_hs_fc200_state.json @@ -10502,5 +10502,6 @@ "commandsDroppedRX": 0, "commandsDroppedTX": 0, "timeoutResponse": 2 - } + }, + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/fortrezz_ssa1_siren_state.json b/tests/components/zwave_js/fixtures/fortrezz_ssa1_siren_state.json index d8973f2688e37a..8c88082718c542 100644 --- a/tests/components/zwave_js/fixtures/fortrezz_ssa1_siren_state.json +++ b/tests/components/zwave_js/fixtures/fortrezz_ssa1_siren_state.json @@ -346,5 +346,6 @@ "commandsDroppedRX": 0, "commandsDroppedTX": 0, "timeoutResponse": 2 - } + }, + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/fortrezz_ssa3_siren_state.json b/tests/components/zwave_js/fixtures/fortrezz_ssa3_siren_state.json index fb31f838667128..aa0e05dd47fdde 100644 --- a/tests/components/zwave_js/fixtures/fortrezz_ssa3_siren_state.json +++ b/tests/components/zwave_js/fixtures/fortrezz_ssa3_siren_state.json @@ -351,5 +351,6 @@ "commandsDroppedTX": 0, "timeoutResponse": 1 }, - "highestSecurityClass": -1 + "highestSecurityClass": -1, + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/ge_in_wall_dimmer_switch_state.json b/tests/components/zwave_js/fixtures/ge_in_wall_dimmer_switch_state.json index d47896a980aaa0..4cbf9ef1ce43af 100644 --- a/tests/components/zwave_js/fixtures/ge_in_wall_dimmer_switch_state.json +++ b/tests/components/zwave_js/fixtures/ge_in_wall_dimmer_switch_state.json @@ -638,5 +638,6 @@ "mandatoryControlledCCs": [] }, "interviewStage": "Complete", - "deviceDatabaseUrl": "https://devices.zwave-js.io/?jumpTo=0x0063:0x4944:0x3038:5.26" + "deviceDatabaseUrl": "https://devices.zwave-js.io/?jumpTo=0x0063:0x4944:0x3038:5.26", + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/hank_binary_switch_state.json b/tests/components/zwave_js/fixtures/hank_binary_switch_state.json index d27338db5a9222..926285e5359012 100644 --- a/tests/components/zwave_js/fixtures/hank_binary_switch_state.json +++ b/tests/components/zwave_js/fixtures/hank_binary_switch_state.json @@ -720,5 +720,6 @@ "label": "Z-Wave chip hardware version" } } - ] + ], + "isControllerNode": false } \ No newline at end of file diff --git a/tests/components/zwave_js/fixtures/inovelli_lzw36_state.json b/tests/components/zwave_js/fixtures/inovelli_lzw36_state.json index bfa56891413373..11e88eff8be642 100644 --- a/tests/components/zwave_js/fixtures/inovelli_lzw36_state.json +++ b/tests/components/zwave_js/fixtures/inovelli_lzw36_state.json @@ -1952,5 +1952,6 @@ } } } - ] + ], + "isControllerNode": false } \ No newline at end of file diff --git a/tests/components/zwave_js/fixtures/light_color_null_values_state.json b/tests/components/zwave_js/fixtures/light_color_null_values_state.json index 213b873f85ca8f..b244913070c1d9 100644 --- a/tests/components/zwave_js/fixtures/light_color_null_values_state.json +++ b/tests/components/zwave_js/fixtures/light_color_null_values_state.json @@ -685,5 +685,6 @@ "version": 1, "isSecure": false } - ] + ], + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/lock_august_asl03_state.json b/tests/components/zwave_js/fixtures/lock_august_asl03_state.json index b22c21e4777219..642682766df15c 100644 --- a/tests/components/zwave_js/fixtures/lock_august_asl03_state.json +++ b/tests/components/zwave_js/fixtures/lock_august_asl03_state.json @@ -446,5 +446,6 @@ "label": "Z-Wave chip hardware version" } } - ] + ], + "isControllerNode": false } \ No newline at end of file diff --git a/tests/components/zwave_js/fixtures/lock_id_lock_as_id150_state.json b/tests/components/zwave_js/fixtures/lock_id_lock_as_id150_state.json index f5e66b7e7a6675..5bd4cfc80806bd 100644 --- a/tests/components/zwave_js/fixtures/lock_id_lock_as_id150_state.json +++ b/tests/components/zwave_js/fixtures/lock_id_lock_as_id150_state.json @@ -2915,5 +2915,6 @@ "version": 1, "isSecure": true } - ] + ], + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/lock_popp_electric_strike_lock_control_state.json b/tests/components/zwave_js/fixtures/lock_popp_electric_strike_lock_control_state.json index 2b4a3a8898401e..dc6e9e40d7ca8b 100644 --- a/tests/components/zwave_js/fixtures/lock_popp_electric_strike_lock_control_state.json +++ b/tests/components/zwave_js/fixtures/lock_popp_electric_strike_lock_control_state.json @@ -564,5 +564,6 @@ "commandsDroppedRX": 0, "commandsDroppedTX": 0, "timeoutResponse": 0 - } + }, + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/lock_schlage_be469_state.json b/tests/components/zwave_js/fixtures/lock_schlage_be469_state.json index fedee0f9cf1db8..64f83a43e0d8fd 100644 --- a/tests/components/zwave_js/fixtures/lock_schlage_be469_state.json +++ b/tests/components/zwave_js/fixtures/lock_schlage_be469_state.json @@ -2103,5 +2103,6 @@ }, "value": 0 } - ] + ], + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/nortek_thermostat_added_event.json b/tests/components/zwave_js/fixtures/nortek_thermostat_added_event.json index 598650b863c610..39c04216a04413 100644 --- a/tests/components/zwave_js/fixtures/nortek_thermostat_added_event.json +++ b/tests/components/zwave_js/fixtures/nortek_thermostat_added_event.json @@ -250,7 +250,8 @@ "label": "Dimming duration" } } - ] + ], + "isControllerNode": false }, "result": {} } diff --git a/tests/components/zwave_js/fixtures/nortek_thermostat_state.json b/tests/components/zwave_js/fixtures/nortek_thermostat_state.json index a3b34aeedf0c7a..a99303af259c54 100644 --- a/tests/components/zwave_js/fixtures/nortek_thermostat_state.json +++ b/tests/components/zwave_js/fixtures/nortek_thermostat_state.json @@ -1275,5 +1275,6 @@ "label": "Dimming duration" } } - ] + ], + "isControllerNode": false } \ No newline at end of file diff --git a/tests/components/zwave_js/fixtures/null_name_check_state.json b/tests/components/zwave_js/fixtures/null_name_check_state.json index fe63eaee20787d..8905e47b155dd8 100644 --- a/tests/components/zwave_js/fixtures/null_name_check_state.json +++ b/tests/components/zwave_js/fixtures/null_name_check_state.json @@ -410,5 +410,6 @@ "version": 3, "isSecure": false } - ] + ], + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/srt321_hrt4_zw_state.json b/tests/components/zwave_js/fixtures/srt321_hrt4_zw_state.json index a2fdaa995614bd..d1db5664f76f9e 100644 --- a/tests/components/zwave_js/fixtures/srt321_hrt4_zw_state.json +++ b/tests/components/zwave_js/fixtures/srt321_hrt4_zw_state.json @@ -258,5 +258,6 @@ "2.0" ] } - ] + ], + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/vision_security_zl7432_state.json b/tests/components/zwave_js/fixtures/vision_security_zl7432_state.json index d37e82ea3af836..f7abbffb590c2e 100644 --- a/tests/components/zwave_js/fixtures/vision_security_zl7432_state.json +++ b/tests/components/zwave_js/fixtures/vision_security_zl7432_state.json @@ -429,5 +429,6 @@ "version": 1, "isSecure": false } - ] + ], + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/wallmote_central_scene_state.json b/tests/components/zwave_js/fixtures/wallmote_central_scene_state.json index f5560dd7e78d89..af5314002fa3a6 100644 --- a/tests/components/zwave_js/fixtures/wallmote_central_scene_state.json +++ b/tests/components/zwave_js/fixtures/wallmote_central_scene_state.json @@ -694,5 +694,6 @@ "label": "Z-Wave chip hardware version" } } - ] + ], + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/zen_31_state.json b/tests/components/zwave_js/fixtures/zen_31_state.json index 7407607e086e68..3b1278da0b9985 100644 --- a/tests/components/zwave_js/fixtures/zen_31_state.json +++ b/tests/components/zwave_js/fixtures/zen_31_state.json @@ -2803,5 +2803,6 @@ "version": 1, "isSecure": true } - ] + ], + "isControllerNode": false } \ No newline at end of file diff --git a/tests/components/zwave_js/fixtures/zp3111-5_not_ready_state.json b/tests/components/zwave_js/fixtures/zp3111-5_not_ready_state.json index f892eb5570eeec..272f6118830e5c 100644 --- a/tests/components/zwave_js/fixtures/zp3111-5_not_ready_state.json +++ b/tests/components/zwave_js/fixtures/zp3111-5_not_ready_state.json @@ -64,5 +64,6 @@ "commandsDroppedRX": 0, "commandsDroppedTX": 0, "timeoutResponse": 0 - } + }, + "isControllerNode": false } diff --git a/tests/components/zwave_js/fixtures/zp3111-5_state.json b/tests/components/zwave_js/fixtures/zp3111-5_state.json index 8de7dd2b713e4e..e652653d9461b8 100644 --- a/tests/components/zwave_js/fixtures/zp3111-5_state.json +++ b/tests/components/zwave_js/fixtures/zp3111-5_state.json @@ -702,5 +702,6 @@ "commandsDroppedTX": 0, "timeoutResponse": 0 }, - "highestSecurityClass": -1 + "highestSecurityClass": -1, + "isControllerNode": false } diff --git a/tests/components/zwave_js/test_button.py b/tests/components/zwave_js/test_button.py index 9b5ac66b06fa95..29858e0eb9761d 100644 --- a/tests/components/zwave_js/test_button.py +++ b/tests/components/zwave_js/test_button.py @@ -1,13 +1,16 @@ """Test the Z-Wave JS button entities.""" from homeassistant.components.button.const import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS from homeassistant.components.zwave_js.const import DOMAIN, SERVICE_REFRESH_VALUE +from homeassistant.components.zwave_js.helpers import get_valueless_base_unique_id from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.helpers.entity_registry import async_get async def test_ping_entity( hass, client, climate_radio_thermostat_ct100_plus_different_endpoints, + controller_node, integration, caplog, ): @@ -44,3 +47,13 @@ async def test_ping_entity( ) assert "There is no value to refresh for this entity" in caplog.text + + # Assert a node ping button entity is not created for the controller + node = client.driver.controller.nodes[1] + assert node.is_controller_node + assert ( + async_get(hass).async_get_entity_id( + DOMAIN, "sensor", f"{get_valueless_base_unique_id(client, node)}.ping" + ) + is None + ) diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 1003316f1e50da..1b3a2d1204f418 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -934,6 +934,7 @@ async def test_replace_same_node( "commandsDroppedTX": 0, "timeoutResponse": 0, }, + "isControllerNode": False, }, "result": {}, }, @@ -1052,6 +1053,7 @@ async def test_replace_different_node( "commandsDroppedTX": 0, "timeoutResponse": 0, }, + "isControllerNode": False, }, "result": {}, }, diff --git a/tests/components/zwave_js/test_sensor.py b/tests/components/zwave_js/test_sensor.py index 891a417551eae6..1d41e145a95dc3 100644 --- a/tests/components/zwave_js/test_sensor.py +++ b/tests/components/zwave_js/test_sensor.py @@ -18,6 +18,7 @@ SERVICE_REFRESH_VALUE, SERVICE_RESET_METER, ) +from homeassistant.components.zwave_js.helpers import get_valueless_base_unique_id from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, @@ -155,7 +156,9 @@ async def test_config_parameter_sensor(hass, lock_id_lock_as_id150, integration) assert entity_entry.disabled -async def test_node_status_sensor(hass, client, lock_id_lock_as_id150, integration): +async def test_node_status_sensor( + hass, client, controller_node, lock_id_lock_as_id150, integration +): """Test node status sensor is created and gets updated on node state changes.""" NODE_STATUS_ENTITY = "sensor.z_wave_module_for_id_lock_150_and_101_node_status" node = lock_id_lock_as_id150 @@ -201,6 +204,18 @@ async def test_node_status_sensor(hass, client, lock_id_lock_as_id150, integrati await client.disconnect() assert hass.states.get(NODE_STATUS_ENTITY).state != STATE_UNAVAILABLE + # Assert a node status sensor entity is not created for the controller + node = client.driver.controller.nodes[1] + assert node.is_controller_node + assert ( + ent_reg.async_get_entity_id( + DOMAIN, + "sensor", + f"{get_valueless_base_unique_id(client, node)}.node_status", + ) + is None + ) + async def test_node_status_sensor_not_ready( hass, From 2c075a00c7a9cfefbab8a47079fef85348a9d54d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 25 Feb 2022 17:11:22 +0100 Subject: [PATCH 1043/1098] Add support for 8-gang switches to Tuya (#67218) --- homeassistant/components/tuya/const.py | 2 ++ homeassistant/components/tuya/switch.py | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/homeassistant/components/tuya/const.py b/homeassistant/components/tuya/const.py index ee653534fc8acd..e734004065837a 100644 --- a/homeassistant/components/tuya/const.py +++ b/homeassistant/components/tuya/const.py @@ -329,6 +329,8 @@ class DPCode(StrEnum): SWITCH_4 = "switch_4" # Switch 4 SWITCH_5 = "switch_5" # Switch 5 SWITCH_6 = "switch_6" # Switch 6 + SWITCH_7 = "switch_7" # Switch 7 + SWITCH_8 = "switch_8" # Switch 8 SWITCH_BACKLIGHT = "switch_backlight" # Backlight switch SWITCH_CHARGE = "switch_charge" SWITCH_CONTROLLER = "switch_controller" diff --git a/homeassistant/components/tuya/switch.py b/homeassistant/components/tuya/switch.py index 6be35e0102b1de..d978b377cc5f8f 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -137,6 +137,16 @@ name="Switch 6", device_class=SwitchDeviceClass.OUTLET, ), + SwitchEntityDescription( + key=DPCode.SWITCH_7, + name="Switch 7", + device_class=SwitchDeviceClass.OUTLET, + ), + SwitchEntityDescription( + key=DPCode.SWITCH_8, + name="Switch 8", + device_class=SwitchDeviceClass.OUTLET, + ), SwitchEntityDescription( key=DPCode.SWITCH_USB1, name="USB 1", From d9195434dea394502553e90447d614536b6b4041 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 25 Feb 2022 10:57:29 +0100 Subject: [PATCH 1044/1098] Move Phone Modem reject call deprecation warning (#67223) --- homeassistant/components/modem_callerid/sensor.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/modem_callerid/sensor.py b/homeassistant/components/modem_callerid/sensor.py index 3a1af4aa0a8ce1..f4b2f3c3e44fe7 100644 --- a/homeassistant/components/modem_callerid/sensor.py +++ b/homeassistant/components/modem_callerid/sensor.py @@ -44,13 +44,7 @@ async def _async_on_hass_stop(event: Event) -> None: ) platform = entity_platform.async_get_current_platform() - platform.async_register_entity_service(SERVICE_REJECT_CALL, {}, "async_reject_call") - _LOGGER.warning( - "Calling reject_call service is deprecated and will be removed after 2022.4; " - "A new button entity is now available with the same function " - "and replaces the existing service" - ) class ModemCalleridSensor(SensorEntity): @@ -94,4 +88,9 @@ def _async_incoming_call(self, new_state: str) -> None: async def async_reject_call(self) -> None: """Reject Incoming Call.""" + _LOGGER.warning( + "Calling reject_call service is deprecated and will be removed after 2022.4; " + "A new button entity is now available with the same function " + "and replaces the existing service" + ) await self.api.reject_call(self.device) From b3db4133c8037290825b8f5e8ab25e34f9d38079 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 25 Feb 2022 17:05:56 +0100 Subject: [PATCH 1045/1098] Fix zwave_js migration luminance sensor (#67234) --- homeassistant/components/zwave_js/migrate.py | 7 ++-- tests/components/zwave_js/test_migrate.py | 36 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zwave_js/migrate.py b/homeassistant/components/zwave_js/migrate.py index 73a094fd95a823..204b5d0aebd5f3 100644 --- a/homeassistant/components/zwave_js/migrate.py +++ b/homeassistant/components/zwave_js/migrate.py @@ -9,7 +9,7 @@ from zwave_js_server.model.value import Value as ZwaveValue from homeassistant.config_entries import ConfigEntry -from homeassistant.const import STATE_UNAVAILABLE +from homeassistant.const import LIGHT_LUX, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.device_registry import ( DeviceEntry, @@ -91,6 +91,8 @@ 113: NOTIFICATION_CC_LABEL_TO_PROPERTY_NAME, } +UNIT_LEGACY_MIGRATION_MAP = {LIGHT_LUX: "Lux"} + class ZWaveMigrationData(TypedDict): """Represent the Z-Wave migration data dict.""" @@ -209,7 +211,8 @@ def add_entity_value( # Normalize unit of measurement. if unit := entity_entry.unit_of_measurement: - unit = unit.lower() + _unit = UNIT_LEGACY_MIGRATION_MAP.get(unit, unit) + unit = _unit.lower() if unit == "": unit = None diff --git a/tests/components/zwave_js/test_migrate.py b/tests/components/zwave_js/test_migrate.py index 3479638b387149..95f969a9586f26 100644 --- a/tests/components/zwave_js/test_migrate.py +++ b/tests/components/zwave_js/test_migrate.py @@ -8,6 +8,7 @@ from homeassistant.components.zwave_js.api import ENTRY_ID, ID, TYPE from homeassistant.components.zwave_js.const import DOMAIN from homeassistant.components.zwave_js.helpers import get_device_id +from homeassistant.const import LIGHT_LUX from homeassistant.helpers import device_registry as dr, entity_registry as er from .common import AIR_TEMPERATURE_SENSOR, NOTIFICATION_MOTION_BINARY_SENSOR @@ -33,6 +34,10 @@ ZWAVE_MULTISENSOR_DEVICE_AREA = "Z-Wave Multisensor Area" ZWAVE_SOURCE_NODE_ENTITY = "sensor.zwave_source_node" ZWAVE_SOURCE_NODE_UNIQUE_ID = "52-4321" +ZWAVE_LUMINANCE_ENTITY = "sensor.zwave_luminance" +ZWAVE_LUMINANCE_UNIQUE_ID = "52-6543" +ZWAVE_LUMINANCE_NAME = "Z-Wave Luminance" +ZWAVE_LUMINANCE_ICON = "mdi:zwave-test-luminance" ZWAVE_BATTERY_ENTITY = "sensor.zwave_battery_level" ZWAVE_BATTERY_UNIQUE_ID = "52-1234" ZWAVE_BATTERY_NAME = "Z-Wave Battery Level" @@ -69,6 +74,14 @@ def zwave_migration_data_fixture(hass): platform="zwave", name="Z-Wave Source Node", ) + zwave_luminance_entry = er.RegistryEntry( + entity_id=ZWAVE_LUMINANCE_ENTITY, + unique_id=ZWAVE_LUMINANCE_UNIQUE_ID, + platform="zwave", + name=ZWAVE_LUMINANCE_NAME, + icon=ZWAVE_LUMINANCE_ICON, + unit_of_measurement="lux", + ) zwave_battery_entry = er.RegistryEntry( entity_id=ZWAVE_BATTERY_ENTITY, unique_id=ZWAVE_BATTERY_UNIQUE_ID, @@ -131,6 +144,18 @@ def zwave_migration_data_fixture(hass): "unique_id": ZWAVE_SOURCE_NODE_UNIQUE_ID, "unit_of_measurement": zwave_source_node_entry.unit_of_measurement, }, + ZWAVE_LUMINANCE_ENTITY: { + "node_id": 52, + "node_instance": 1, + "command_class": 49, + "command_class_label": "Luminance", + "value_index": 3, + "device_id": zwave_multisensor_device.id, + "domain": zwave_luminance_entry.domain, + "entity_id": zwave_luminance_entry.entity_id, + "unique_id": ZWAVE_LUMINANCE_UNIQUE_ID, + "unit_of_measurement": zwave_luminance_entry.unit_of_measurement, + }, ZWAVE_BATTERY_ENTITY: { "node_id": 52, "node_instance": 1, @@ -169,6 +194,7 @@ def zwave_migration_data_fixture(hass): { ZWAVE_SWITCH_ENTITY: zwave_switch_entry, ZWAVE_SOURCE_NODE_ENTITY: zwave_source_node_entry, + ZWAVE_LUMINANCE_ENTITY: zwave_luminance_entry, ZWAVE_BATTERY_ENTITY: zwave_battery_entry, ZWAVE_POWER_ENTITY: zwave_power_entry, ZWAVE_TAMPERING_ENTITY: zwave_tampering_entry, @@ -218,6 +244,7 @@ async def test_migrate_zwave( migration_entity_map = { ZWAVE_SWITCH_ENTITY: "switch.smart_switch_6", + ZWAVE_LUMINANCE_ENTITY: "sensor.multisensor_6_illuminance", ZWAVE_BATTERY_ENTITY: "sensor.multisensor_6_battery_level", } @@ -225,6 +252,7 @@ async def test_migrate_zwave( ZWAVE_SWITCH_ENTITY, ZWAVE_POWER_ENTITY, ZWAVE_SOURCE_NODE_ENTITY, + ZWAVE_LUMINANCE_ENTITY, ZWAVE_BATTERY_ENTITY, ZWAVE_TAMPERING_ENTITY, ] @@ -279,6 +307,7 @@ async def test_migrate_zwave( # this should have been migrated and no longer present under that id assert not ent_reg.async_is_registered("sensor.multisensor_6_battery_level") + assert not ent_reg.async_is_registered("sensor.multisensor_6_illuminance") # these should not have been migrated and is still in the registry assert ent_reg.async_is_registered(ZWAVE_SOURCE_NODE_ENTITY) @@ -295,6 +324,7 @@ async def test_migrate_zwave( # this is the new entity_ids of the zwave_js entities assert ent_reg.async_is_registered(ZWAVE_SWITCH_ENTITY) assert ent_reg.async_is_registered(ZWAVE_BATTERY_ENTITY) + assert ent_reg.async_is_registered(ZWAVE_LUMINANCE_ENTITY) # check that the migrated entries have correct attributes switch_entry = ent_reg.async_get(ZWAVE_SWITCH_ENTITY) @@ -307,6 +337,12 @@ async def test_migrate_zwave( assert battery_entry.unique_id == "3245146787.52-128-0-level" assert battery_entry.name == ZWAVE_BATTERY_NAME assert battery_entry.icon == ZWAVE_BATTERY_ICON + luminance_entry = ent_reg.async_get(ZWAVE_LUMINANCE_ENTITY) + assert luminance_entry + assert luminance_entry.unique_id == "3245146787.52-49-0-Illuminance" + assert luminance_entry.name == ZWAVE_LUMINANCE_NAME + assert luminance_entry.icon == ZWAVE_LUMINANCE_ICON + assert luminance_entry.unit_of_measurement == LIGHT_LUX # check that the zwave config entry has been removed assert not hass.config_entries.async_entries("zwave") From b767f83dc6fafa19ffc3fd722f8fdcbec5f892e5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 25 Feb 2022 10:00:03 -0800 Subject: [PATCH 1046/1098] Adjust serializing resolved media (#67240) --- .../components/media_player/browse_media.py | 8 +++--- .../components/media_source/__init__.py | 26 +++++++++---------- tests/components/media_source/test_init.py | 3 ++- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/media_player/browse_media.py b/homeassistant/components/media_player/browse_media.py index 6fe4683c1fcc8e..26494e4c8a7849 100644 --- a/homeassistant/components/media_player/browse_media.py +++ b/homeassistant/components/media_player/browse_media.py @@ -15,7 +15,9 @@ @callback -def async_process_play_media_url(hass: HomeAssistant, media_content_id: str) -> str: +def async_process_play_media_url( + hass: HomeAssistant, media_content_id: str, *, allow_relative_url: bool = False +) -> str: """Update a media URL with authentication if it points at Home Assistant.""" if media_content_id[0] != "/" and not is_hass_url(hass, media_content_id): return media_content_id @@ -34,8 +36,8 @@ def async_process_play_media_url(hass: HomeAssistant, media_content_id: str) -> ) media_content_id = str(parsed.join(yarl.URL(signed_path))) - # prepend external URL - if media_content_id[0] == "/": + # convert relative URL to absolute URL + if media_content_id[0] == "/" and not allow_relative_url: media_content_id = f"{get_url(hass)}{media_content_id}" return media_content_id diff --git a/homeassistant/components/media_source/__init__.py b/homeassistant/components/media_source/__init__.py index 77b254dcf9de98..2bcd80a39ab665 100644 --- a/homeassistant/components/media_source/__init__.py +++ b/homeassistant/components/media_source/__init__.py @@ -2,21 +2,20 @@ from __future__ import annotations from collections.abc import Callable -import dataclasses -from datetime import timedelta from typing import Any -from urllib.parse import quote import voluptuous as vol from homeassistant.components import frontend, websocket_api -from homeassistant.components.http.auth import async_sign_path from homeassistant.components.media_player import ( ATTR_MEDIA_CONTENT_ID, CONTENT_AUTH_EXPIRY_TIME, BrowseError, BrowseMedia, ) +from homeassistant.components.media_player.browse_media import ( + async_process_play_media_url, +) from homeassistant.components.websocket_api import ActiveConnection from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.integration_platform import ( @@ -177,13 +176,12 @@ async def websocket_resolve_media( connection.send_error(msg["id"], "resolve_media_failed", str(err)) return - data = dataclasses.asdict(media) - - if data["url"][0] == "/": - data["url"] = async_sign_path( - hass, - quote(data["url"]), - timedelta(seconds=msg["expires"]), - ) - - connection.send_result(msg["id"], data) + connection.send_result( + msg["id"], + { + "url": async_process_play_media_url( + hass, media.url, allow_relative_url=True + ), + "mime_type": media.mime_type, + }, + ) diff --git a/tests/components/media_source/test_init.py b/tests/components/media_source/test_init.py index 319ef295be393b..2655000efc91ea 100644 --- a/tests/components/media_source/test_init.py +++ b/tests/components/media_source/test_init.py @@ -187,7 +187,8 @@ async def test_websocket_resolve_media(hass, hass_ws_client, filename): assert msg["id"] == 1 assert msg["result"]["mime_type"] == media.mime_type - # Validate url is signed. + # Validate url is relative and signed. + assert msg["result"]["url"][0] == "/" parsed = yarl.URL(msg["result"]["url"]) assert parsed.path == getattr(media, "url") assert "authSig" in parsed.query From a7c67e6cde7433cb6afd764044adb709acc4a008 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 25 Feb 2022 10:01:16 -0800 Subject: [PATCH 1047/1098] Bumped version to 2022.3.0b3 --- homeassistant/const.py | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 925c08e24fde05..95f2b3d9f5781e 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 3 -PATCH_VERSION: Final = "0b2" +PATCH_VERSION: Final = "0b3" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/setup.cfg b/setup.cfg index 295206ed0031f5..00a0d4a695f179 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = homeassistant -version = 2022.3.0b2 +version = 2022.3.0b3 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 From 33969fd4c1dc3866a8130dc7d6ab746a8952cbf1 Mon Sep 17 00:00:00 2001 From: stegm Date: Sat, 26 Feb 2022 22:32:38 +0100 Subject: [PATCH 1048/1098] Add diagnostics to Kostal Plenticore (#66435) --- .../kostal_plenticore/diagnostics.py | 42 ++++++++ .../components/kostal_plenticore/conftest.py | 96 +++++++++++++++++++ .../kostal_plenticore/test_diagnostics.py | 49 ++++++++++ 3 files changed, 187 insertions(+) create mode 100644 homeassistant/components/kostal_plenticore/diagnostics.py create mode 100644 tests/components/kostal_plenticore/conftest.py create mode 100644 tests/components/kostal_plenticore/test_diagnostics.py diff --git a/homeassistant/components/kostal_plenticore/diagnostics.py b/homeassistant/components/kostal_plenticore/diagnostics.py new file mode 100644 index 00000000000000..2e061d35528a4b --- /dev/null +++ b/homeassistant/components/kostal_plenticore/diagnostics.py @@ -0,0 +1,42 @@ +"""Diagnostics support for Kostal Plenticore.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.diagnostics import REDACTED, async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_PASSWORD +from homeassistant.core import HomeAssistant + +from .const import DOMAIN +from .helper import Plenticore + +TO_REDACT = {CONF_PASSWORD} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry +) -> dict[str, dict[str, Any]]: + """Return diagnostics for a config entry.""" + data = {"config_entry": async_redact_data(config_entry.as_dict(), TO_REDACT)} + + plenticore: Plenticore = hass.data[DOMAIN][config_entry.entry_id] + + # Get information from Kostal Plenticore library + available_process_data = await plenticore.client.get_process_data() + available_settings_data = await plenticore.client.get_settings() + data["client"] = { + "version": str(await plenticore.client.get_version()), + "me": str(await plenticore.client.get_me()), + "available_process_data": available_process_data, + "available_settings_data": { + module_id: [str(setting) for setting in settings] + for module_id, settings in available_settings_data.items() + }, + } + + device_info = {**plenticore.device_info} + device_info["identifiers"] = REDACTED # contains serial number + data["device"] = device_info + + return data diff --git a/tests/components/kostal_plenticore/conftest.py b/tests/components/kostal_plenticore/conftest.py new file mode 100644 index 00000000000000..c3ed1b45592607 --- /dev/null +++ b/tests/components/kostal_plenticore/conftest.py @@ -0,0 +1,96 @@ +"""Fixtures for Kostal Plenticore tests.""" +from __future__ import annotations + +from collections.abc import Generator +from unittest.mock import AsyncMock, MagicMock, patch + +from kostal.plenticore import MeData, SettingsData, VersionData +import pytest + +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import DeviceInfo + +from tests.common import MockConfigEntry + + +@pytest.fixture +async def init_integration( + hass: HomeAssistant, +) -> Generator[None, MockConfigEntry, None]: + """Set up Kostal Plenticore integration for testing.""" + with patch( + "homeassistant.components.kostal_plenticore.Plenticore", autospec=True + ) as mock_api_class: + # setup + plenticore = mock_api_class.return_value + plenticore.async_setup = AsyncMock() + plenticore.async_setup.return_value = True + + plenticore.device_info = DeviceInfo( + configuration_url="http://192.168.1.2", + identifiers={("kostal_plenticore", "12345")}, + manufacturer="Kostal", + model="PLENTICORE plus 10", + name="scb", + sw_version="IOC: 01.45 MC: 01.46", + ) + + plenticore.client = MagicMock() + + plenticore.client.get_version = AsyncMock() + plenticore.client.get_version.return_value = VersionData( + { + "api_version": "0.2.0", + "hostname": "scb", + "name": "PUCK RESTful API", + "sw_version": "01.16.05025", + } + ) + + plenticore.client.get_me = AsyncMock() + plenticore.client.get_me.return_value = MeData( + { + "locked": False, + "active": True, + "authenticated": True, + "permissions": [], + "anonymous": False, + "role": "USER", + } + ) + + plenticore.client.get_process_data = AsyncMock() + plenticore.client.get_process_data.return_value = { + "devices:local": ["HomeGrid_P", "HomePv_P"] + } + + plenticore.client.get_settings = AsyncMock() + plenticore.client.get_settings.return_value = { + "devices:local": [ + SettingsData( + { + "id": "Battery:MinSoc", + "unit": "%", + "default": "None", + "min": 5, + "max": 100, + "type": "byte", + "access": "readwrite", + } + ) + ] + } + + mock_config_entry = MockConfigEntry( + entry_id="2ab8dd92a62787ddfe213a67e09406bd", + title="scb", + domain="kostal_plenticore", + data={"host": "192.168.1.2", "password": "SecretPassword"}, + ) + + mock_config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + yield mock_config_entry diff --git a/tests/components/kostal_plenticore/test_diagnostics.py b/tests/components/kostal_plenticore/test_diagnostics.py new file mode 100644 index 00000000000000..56af8bafe06567 --- /dev/null +++ b/tests/components/kostal_plenticore/test_diagnostics.py @@ -0,0 +1,49 @@ +"""Test Kostal Plenticore diagnostics.""" +from aiohttp import ClientSession + +from homeassistant.components.diagnostics import REDACTED +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_entry_diagnostics( + hass: HomeAssistant, hass_client: ClientSession, init_integration: MockConfigEntry +): + """Test config entry diagnostics.""" + assert await get_diagnostics_for_config_entry( + hass, hass_client, init_integration + ) == { + "config_entry": { + "entry_id": "2ab8dd92a62787ddfe213a67e09406bd", + "version": 1, + "domain": "kostal_plenticore", + "title": "scb", + "data": {"host": "192.168.1.2", "password": REDACTED}, + "options": {}, + "pref_disable_new_entities": False, + "pref_disable_polling": False, + "source": "user", + "unique_id": None, + "disabled_by": None, + }, + "client": { + "version": "Version(api_version=0.2.0, hostname=scb, name=PUCK RESTful API, sw_version=01.16.05025)", + "me": "Me(locked=False, active=True, authenticated=True, permissions=[] anonymous=False role=USER)", + "available_process_data": {"devices:local": ["HomeGrid_P", "HomePv_P"]}, + "available_settings_data": { + "devices:local": [ + "SettingsData(id=Battery:MinSoc, unit=%, default=None, min=5, max=100,type=byte, access=readwrite)" + ] + }, + }, + "device": { + "configuration_url": "http://192.168.1.2", + "identifiers": "**REDACTED**", + "manufacturer": "Kostal", + "model": "PLENTICORE plus 10", + "name": "scb", + "sw_version": "IOC: 01.45 MC: 01.46", + }, + } From fb82013c3992d5011986cae4e3a717108434e41a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 25 Feb 2022 09:20:56 -1000 Subject: [PATCH 1049/1098] Fix powerwall data incompatibility with energy integration (#67245) --- homeassistant/components/powerwall/sensor.py | 87 ++++++++++++++------ 1 file changed, 61 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/powerwall/sensor.py b/homeassistant/components/powerwall/sensor.py index a48726211b2a7e..bc8ab1c021554e 100644 --- a/homeassistant/components/powerwall/sensor.py +++ b/homeassistant/components/powerwall/sensor.py @@ -3,7 +3,7 @@ from typing import Any -from tesla_powerwall import MeterType +from tesla_powerwall import Meter, MeterType from homeassistant.components.sensor import ( SensorDeviceClass, @@ -28,7 +28,6 @@ _METER_DIRECTION_EXPORT = "export" _METER_DIRECTION_IMPORT = "import" -_METER_DIRECTIONS = [_METER_DIRECTION_EXPORT, _METER_DIRECTION_IMPORT] async def async_setup_entry( @@ -42,20 +41,20 @@ async def async_setup_entry( assert coordinator is not None data: PowerwallData = coordinator.data entities: list[ - PowerWallEnergySensor | PowerWallEnergyDirectionSensor | PowerWallChargeSensor - ] = [] + PowerWallEnergySensor + | PowerWallImportSensor + | PowerWallExportSensor + | PowerWallChargeSensor + ] = [PowerWallChargeSensor(powerwall_data)] + for meter in data.meters.meters: - entities.append(PowerWallEnergySensor(powerwall_data, meter)) - for meter_direction in _METER_DIRECTIONS: - entities.append( - PowerWallEnergyDirectionSensor( - powerwall_data, - meter, - meter_direction, - ) - ) - - entities.append(PowerWallChargeSensor(powerwall_data)) + entities.extend( + [ + PowerWallEnergySensor(powerwall_data, meter), + PowerWallExportSensor(powerwall_data, meter), + PowerWallImportSensor(powerwall_data, meter), + ] + ) async_add_entities(entities) @@ -128,18 +127,54 @@ def __init__( """Initialize the sensor.""" super().__init__(powerwall_data) self._meter = meter - self._meter_direction = meter_direction - self._attr_name = ( - f"Powerwall {self._meter.value.title()} {self._meter_direction.title()}" - ) - self._attr_unique_id = ( - f"{self.base_unique_id}_{self._meter.value}_{self._meter_direction}" - ) + self._attr_name = f"Powerwall {meter.value.title()} {meter_direction.title()}" + self._attr_unique_id = f"{self.base_unique_id}_{meter.value}_{meter_direction}" + + @property + def available(self) -> bool: + """Check if the reading is actually available. + + The device reports 0 when something goes wrong which + we do not want to include in statistics and its a + transient data error. + """ + return super().available and self.native_value != 0 + + @property + def meter(self) -> Meter: + """Get the meter for the sensor.""" + return self.data.meters.get_meter(self._meter) + + +class PowerWallExportSensor(PowerWallEnergyDirectionSensor): + """Representation of an Powerwall Export sensor.""" + + def __init__( + self, + powerwall_data: PowerwallRuntimeData, + meter: MeterType, + ) -> None: + """Initialize the sensor.""" + super().__init__(powerwall_data, meter, _METER_DIRECTION_EXPORT) @property def native_value(self) -> float: """Get the current value in kWh.""" - meter = self.data.meters.get_meter(self._meter) - if self._meter_direction == _METER_DIRECTION_EXPORT: - return meter.get_energy_exported() - return meter.get_energy_imported() + return abs(self.meter.get_energy_exported()) + + +class PowerWallImportSensor(PowerWallEnergyDirectionSensor): + """Representation of an Powerwall Import sensor.""" + + def __init__( + self, + powerwall_data: PowerwallRuntimeData, + meter: MeterType, + ) -> None: + """Initialize the sensor.""" + super().__init__(powerwall_data, meter, _METER_DIRECTION_IMPORT) + + @property + def native_value(self) -> float: + """Get the current value in kWh.""" + return abs(self.meter.get_energy_imported()) From 2d53e222ffb5c1fc717ada0f37eb273fbaab9483 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 25 Feb 2022 11:52:14 -0800 Subject: [PATCH 1050/1098] Improve not shown handling (#67247) --- .../components/camera/media_source.py | 3 +++ .../components/media_source/__init__.py | 2 +- tests/components/camera/test_media_source.py | 1 + tests/components/media_source/test_init.py | 26 ++++++++++++++++++- 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/camera/media_source.py b/homeassistant/components/camera/media_source.py index c61cbef146afaf..ab7661fefe2765 100644 --- a/homeassistant/components/camera/media_source.py +++ b/homeassistant/components/camera/media_source.py @@ -81,11 +81,13 @@ async def async_browse_media( # Root. List cameras. component: EntityComponent = self.hass.data[DOMAIN] children = [] + not_shown = 0 for camera in component.entities: camera = cast(Camera, camera) stream_type = camera.frontend_stream_type if stream_type not in supported_stream_types: + not_shown += 1 continue children.append( @@ -111,4 +113,5 @@ async def async_browse_media( can_expand=True, children_media_class=MEDIA_CLASS_VIDEO, children=children, + not_shown=not_shown, ) diff --git a/homeassistant/components/media_source/__init__.py b/homeassistant/components/media_source/__init__.py index 2bcd80a39ab665..3c42016f8f7c0a 100644 --- a/homeassistant/components/media_source/__init__.py +++ b/homeassistant/components/media_source/__init__.py @@ -119,7 +119,7 @@ async def async_browse_media( item.children = [ child for child in item.children if child.can_expand or content_filter(child) ] - item.not_shown = old_count - len(item.children) + item.not_shown += old_count - len(item.children) return item diff --git a/tests/components/camera/test_media_source.py b/tests/components/camera/test_media_source.py index 3a3558419e5c76..54d6ef6279e078 100644 --- a/tests/components/camera/test_media_source.py +++ b/tests/components/camera/test_media_source.py @@ -35,6 +35,7 @@ async def test_browsing_filter_non_hls(hass, mock_camera_web_rtc): assert item is not None assert item.title == "Camera" assert len(item.children) == 0 + assert item.not_shown == 2 async def test_resolving(hass, mock_camera_hls): diff --git a/tests/components/media_source/test_init.py b/tests/components/media_source/test_init.py index 2655000efc91ea..491b1972cb680e 100644 --- a/tests/components/media_source/test_init.py +++ b/tests/components/media_source/test_init.py @@ -6,7 +6,7 @@ from homeassistant.components import media_source from homeassistant.components.media_player import MEDIA_CLASS_DIRECTORY, BrowseError -from homeassistant.components.media_source import const +from homeassistant.components.media_source import const, models from homeassistant.setup import async_setup_component @@ -60,6 +60,30 @@ async def test_async_browse_media(hass): media.children[0].title = "Epic Sax Guy 10 Hours" assert media.not_shown == 1 + # Test content filter adds to original not_shown + orig_browse = models.MediaSourceItem.async_browse + + async def not_shown_browse(self): + """Patch browsed item to set not_shown base value.""" + item = await orig_browse(self) + item.not_shown = 10 + return item + + with patch( + "homeassistant.components.media_source.models.MediaSourceItem.async_browse", + not_shown_browse, + ): + media = await media_source.async_browse_media( + hass, + "", + content_filter=lambda item: item.media_content_type.startswith("video/"), + ) + assert isinstance(media, media_source.models.BrowseMediaSource) + assert media.title == "media" + assert len(media.children) == 1, media.children + media.children[0].title = "Epic Sax Guy 10 Hours" + assert media.not_shown == 11 + # Test invalid media content with pytest.raises(BrowseError): await media_source.async_browse_media(hass, "invalid") From f39aea70e60afa6146849353bab1a860d78c0f13 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 25 Feb 2022 11:35:39 -0800 Subject: [PATCH 1051/1098] =?UTF-8?q?Give=20Sonos=20media=20browse=20folde?= =?UTF-8?q?rs=20Sonos=20logos=20to=20distinguish=20from=20media=E2=80=A6?= =?UTF-8?q?=20(#67248)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homeassistant/components/sonos/media_browser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/sonos/media_browser.py b/homeassistant/components/sonos/media_browser.py index 2272ceb183fd7a..b2d881e8bf2790 100644 --- a/homeassistant/components/sonos/media_browser.py +++ b/homeassistant/components/sonos/media_browser.py @@ -267,6 +267,7 @@ async def root_payload( media_class=MEDIA_CLASS_DIRECTORY, media_content_id="", media_content_type="favorites", + thumbnail="https://brands.home-assistant.io/_/sonos/logo.png", can_play=False, can_expand=True, ) @@ -281,6 +282,7 @@ async def root_payload( media_class=MEDIA_CLASS_DIRECTORY, media_content_id="", media_content_type="library", + thumbnail="https://brands.home-assistant.io/_/sonos/logo.png", can_play=False, can_expand=True, ) From d16f0ba32b0598df4c2c72e3cffe4e2928b91d8e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 25 Feb 2022 09:37:19 -1000 Subject: [PATCH 1052/1098] Prevent the wrong WiZ device from being used when the IP is a different device (#67250) --- homeassistant/components/wiz/__init__.py | 9 +++++++++ tests/components/wiz/test_init.py | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/homeassistant/components/wiz/__init__.py b/homeassistant/components/wiz/__init__.py index 7bea86d323ceac..d739c571c8b56c 100644 --- a/homeassistant/components/wiz/__init__.py +++ b/homeassistant/components/wiz/__init__.py @@ -60,6 +60,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await bulb.async_close() raise ConfigEntryNotReady(f"{ip_address}: {err}") from err + if bulb.mac != entry.unique_id: + # The ip address of the bulb has changed and its likely offline + # and another WiZ device has taken the IP. Avoid setting up + # since its the wrong device. As soon as the device comes back + # online the ip will get updated and setup will proceed. + raise ConfigEntryNotReady( + "Found bulb {bulb.mac} at {ip_address}, expected {entry.unique_id}" + ) + async def _async_update() -> None: """Update the WiZ device.""" try: diff --git a/tests/components/wiz/test_init.py b/tests/components/wiz/test_init.py index fb21e930efdd8f..58afb5c944a8fc 100644 --- a/tests/components/wiz/test_init.py +++ b/tests/components/wiz/test_init.py @@ -50,3 +50,11 @@ async def test_cleanup_on_failed_first_update(hass: HomeAssistant) -> None: _, entry = await async_setup_integration(hass, wizlight=bulb) assert entry.state == config_entries.ConfigEntryState.SETUP_RETRY bulb.async_close.assert_called_once() + + +async def test_wrong_device_now_has_our_ip(hass: HomeAssistant) -> None: + """Test setup is retried when the wrong device is found.""" + bulb = _mocked_wizlight(None, None, FAKE_SOCKET) + bulb.mac = "dddddddddddd" + _, entry = await async_setup_integration(hass, wizlight=bulb) + assert entry.state == config_entries.ConfigEntryState.SETUP_RETRY From 241611ff0578be7e0c70f6972057a2a1b00443b6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 25 Feb 2022 12:19:56 -0800 Subject: [PATCH 1053/1098] Kodi/Roku: Add brand logos to brand folders at root level (#67251) --- homeassistant/components/kodi/browse_media.py | 3 +++ homeassistant/components/roku/browse_media.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/homeassistant/components/kodi/browse_media.py b/homeassistant/components/kodi/browse_media.py index e0fdb0f73fdc20..519c4dc7c1bbf6 100644 --- a/homeassistant/components/kodi/browse_media.py +++ b/homeassistant/components/kodi/browse_media.py @@ -224,6 +224,9 @@ async def library_payload(hass): ) ) + for child in library_info.children: + child.thumbnail = "https://brands.home-assistant.io/_/kodi/logo.png" + with contextlib.suppress(media_source.BrowseError): item = await media_source.async_browse_media(hass, None) # If domain is None, it's overview of available sources diff --git a/homeassistant/components/roku/browse_media.py b/homeassistant/components/roku/browse_media.py index d8cd540e613b52..72b572e8d3e694 100644 --- a/homeassistant/components/roku/browse_media.py +++ b/homeassistant/components/roku/browse_media.py @@ -135,6 +135,9 @@ async def root_payload( ) ) + for child in children: + child.thumbnail = "https://brands.home-assistant.io/_/roku/logo.png" + try: browse_item = await media_source.async_browse_media(hass, None) From 86f511ac6a8072d4c1b6fce183b5aba56cc1c917 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 25 Feb 2022 14:01:20 -0800 Subject: [PATCH 1054/1098] Bump hass-nabucasa to 0.54.0 (#67252) --- homeassistant/components/cloud/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index e9548c03ba6e4b..d5d0c2c03705e3 100644 --- a/homeassistant/components/cloud/manifest.json +++ b/homeassistant/components/cloud/manifest.json @@ -2,7 +2,7 @@ "domain": "cloud", "name": "Home Assistant Cloud", "documentation": "https://www.home-assistant.io/integrations/cloud", - "requirements": ["hass-nabucasa==0.53.1"], + "requirements": ["hass-nabucasa==0.54.0"], "dependencies": ["http", "webhook"], "after_dependencies": ["google_assistant", "alexa"], "codeowners": ["@home-assistant/cloud"], diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 1bd905e6abd550..fd8fe9c06817d5 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -13,7 +13,7 @@ bcrypt==3.1.7 certifi>=2021.5.30 ciso8601==2.2.0 cryptography==35.0.0 -hass-nabucasa==0.53.1 +hass-nabucasa==0.54.0 home-assistant-frontend==20220224.0 httpx==0.21.3 ifaddr==0.1.7 diff --git a/requirements_all.txt b/requirements_all.txt index 713cd7dd33e959..a1ed674536c384 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -807,7 +807,7 @@ habitipy==0.2.0 hangups==0.4.17 # homeassistant.components.cloud -hass-nabucasa==0.53.1 +hass-nabucasa==0.54.0 # homeassistant.components.splunk hass_splunk==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3f94c277f31585..5f9291dc2e1914 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -532,7 +532,7 @@ habitipy==0.2.0 hangups==0.4.17 # homeassistant.components.cloud -hass-nabucasa==0.53.1 +hass-nabucasa==0.54.0 # homeassistant.components.tasmota hatasmota==0.3.1 From f21ee7a748f0fe9ed4976d34d4b38849bab5d3ad Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 26 Feb 2022 01:02:13 -0800 Subject: [PATCH 1055/1098] Fix camera content type while browsing (#67256) --- .../components/camera/media_source.py | 15 +++++++++------ tests/components/camera/test_media_source.py | 18 ++++++++++++++++-- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/camera/media_source.py b/homeassistant/components/camera/media_source.py index ab7661fefe2765..e65aabe459de69 100644 --- a/homeassistant/components/camera/media_source.py +++ b/homeassistant/components/camera/media_source.py @@ -73,10 +73,7 @@ async def async_browse_media( if item.identifier: raise BrowseError("Unknown item") - supported_stream_types: list[str | None] = [None] - - if "stream" in self.hass.config.components: - supported_stream_types.append(STREAM_TYPE_HLS) + can_stream_hls = "stream" in self.hass.config.components # Root. List cameras. component: EntityComponent = self.hass.data[DOMAIN] @@ -86,7 +83,13 @@ async def async_browse_media( camera = cast(Camera, camera) stream_type = camera.frontend_stream_type - if stream_type not in supported_stream_types: + if stream_type is None: + content_type = camera.content_type + + elif can_stream_hls and stream_type == STREAM_TYPE_HLS: + content_type = FORMAT_CONTENT_TYPE[HLS_PROVIDER] + + else: not_shown += 1 continue @@ -95,7 +98,7 @@ async def async_browse_media( domain=DOMAIN, identifier=camera.entity_id, media_class=MEDIA_CLASS_VIDEO, - media_content_type=FORMAT_CONTENT_TYPE[HLS_PROVIDER], + media_content_type=content_type, title=camera.name, thumbnail=f"/api/camera_proxy/{camera.entity_id}", can_play=True, diff --git a/tests/components/camera/test_media_source.py b/tests/components/camera/test_media_source.py index 54d6ef6279e078..b9fb22c9ed850c 100644 --- a/tests/components/camera/test_media_source.py +++ b/tests/components/camera/test_media_source.py @@ -15,21 +15,35 @@ async def setup_media_source(hass): assert await async_setup_component(hass, "media_source", {}) -async def test_browsing(hass, mock_camera_hls): +async def test_browsing_hls(hass, mock_camera_hls): """Test browsing camera media source.""" item = await media_source.async_browse_media(hass, "media-source://camera") assert item is not None assert item.title == "Camera" assert len(item.children) == 0 + assert item.not_shown == 2 # Adding stream enables HLS camera hass.config.components.add("stream") item = await media_source.async_browse_media(hass, "media-source://camera") + assert item.not_shown == 0 + assert len(item.children) == 2 + assert item.children[0].media_content_type == FORMAT_CONTENT_TYPE["hls"] + + +async def test_browsing_mjpeg(hass, mock_camera): + """Test browsing camera media source.""" + item = await media_source.async_browse_media(hass, "media-source://camera") + assert item is not None + assert item.title == "Camera" assert len(item.children) == 2 + assert item.not_shown == 0 + assert item.children[0].media_content_type == "image/jpg" + assert item.children[1].media_content_type == "image/png" -async def test_browsing_filter_non_hls(hass, mock_camera_web_rtc): +async def test_browsing_filter_web_rtc(hass, mock_camera_web_rtc): """Test browsing camera media source hides non-HLS cameras.""" item = await media_source.async_browse_media(hass, "media-source://camera") assert item is not None From 5b5aa3d604c4eb75eeb7f3db8ca72425f8864122 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 26 Feb 2022 00:58:45 -0800 Subject: [PATCH 1056/1098] Kodi: Mark MJPEG cameras using PNGs as incompatible (#67257) --- homeassistant/components/kodi/browse_media.py | 13 ++++++++++++- homeassistant/components/kodi/media_player.py | 11 +++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/kodi/browse_media.py b/homeassistant/components/kodi/browse_media.py index 519c4dc7c1bbf6..73247d23a9df58 100644 --- a/homeassistant/components/kodi/browse_media.py +++ b/homeassistant/components/kodi/browse_media.py @@ -186,6 +186,15 @@ async def item_payload(item, get_thumbnail_url=None): ) +def media_source_content_filter(item: BrowseMedia) -> bool: + """Content filter for media sources.""" + # Filter out cameras using PNG over MJPEG. They don't work in Kodi. + return not ( + item.media_content_id.startswith("media-source://camera/") + and item.media_content_type == "image/png" + ) + + async def library_payload(hass): """ Create response payload to describe contents of a specific library. @@ -228,7 +237,9 @@ async def library_payload(hass): child.thumbnail = "https://brands.home-assistant.io/_/kodi/logo.png" with contextlib.suppress(media_source.BrowseError): - item = await media_source.async_browse_media(hass, None) + item = await media_source.async_browse_media( + hass, None, content_filter=media_source_content_filter + ) # If domain is None, it's overview of available sources if item.domain is None: library_info.children.extend(item.children) diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py index 56b0abb6a1539b..53798a7ccd9f69 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -77,7 +77,12 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType import homeassistant.util.dt as dt_util -from .browse_media import build_item_response, get_media_info, library_payload +from .browse_media import ( + build_item_response, + get_media_info, + library_payload, + media_source_content_filter, +) from .const import ( CONF_WS_PORT, DATA_CONNECTION, @@ -916,7 +921,9 @@ async def _get_thumbnail_url( return await library_payload(self.hass) if media_source.is_media_source_id(media_content_id): - return await media_source.async_browse_media(self.hass, media_content_id) + return await media_source.async_browse_media( + self.hass, media_content_id, content_filter=media_source_content_filter + ) payload = { "search_type": media_content_type, From 5cffec8b23daa4f5245a6025a6f81c957cda08c0 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Sat, 26 Feb 2022 00:56:07 -0800 Subject: [PATCH 1057/1098] Fix Doorbird warning if registering favorites fail (#67262) --- homeassistant/components/doorbird/__init__.py | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/doorbird/__init__.py b/homeassistant/components/doorbird/__init__.py index 06264153af2acc..502ff453a27448 100644 --- a/homeassistant/components/doorbird/__init__.py +++ b/homeassistant/components/doorbird/__init__.py @@ -3,6 +3,7 @@ from http import HTTPStatus import logging +from typing import Any from aiohttp import web from doorbirdpy import DoorBird @@ -166,7 +167,9 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -async def _async_register_events(hass, doorstation): +async def _async_register_events( + hass: HomeAssistant, doorstation: ConfiguredDoorBird +) -> bool: try: await hass.async_add_executor_job(doorstation.register_events, hass) except requests.exceptions.HTTPError: @@ -243,7 +246,7 @@ def token(self): """Get token for device.""" return self._token - def register_events(self, hass): + def register_events(self, hass: HomeAssistant) -> None: """Register events on device.""" # Get the URL of this server hass_url = get_url(hass) @@ -258,9 +261,10 @@ def register_events(self, hass): favorites = self.device.favorites() for event in self.doorstation_events: - self._register_event(hass_url, event, favs=favorites) - - _LOGGER.info("Successfully registered URL for %s on %s", event, self.name) + if self._register_event(hass_url, event, favs=favorites): + _LOGGER.info( + "Successfully registered URL for %s on %s", event, self.name + ) @property def slug(self): @@ -270,21 +274,25 @@ def slug(self): def _get_event_name(self, event): return f"{self.slug}_{event}" - def _register_event(self, hass_url, event, favs=None): + def _register_event( + self, hass_url: str, event: str, favs: dict[str, Any] | None = None + ) -> bool: """Add a schedule entry in the device for a sensor.""" url = f"{hass_url}{API_URL}/{event}?token={self._token}" # Register HA URL as webhook if not already, then get the ID if self.webhook_is_registered(url, favs=favs): - return + return True self.device.change_favorite("http", f"Home Assistant ({event})", url) if not self.webhook_is_registered(url): _LOGGER.warning( - 'Could not find favorite for URL "%s". ' 'Skipping sensor "%s"', + 'Unable to set favorite URL "%s". ' 'Event "%s" will not fire', url, event, ) + return False + return True def webhook_is_registered(self, url, favs=None) -> bool: """Return whether the given URL is registered as a device favorite.""" From a3cdc2facb504ed69a160159a6c0d3cd7d467654 Mon Sep 17 00:00:00 2001 From: pailloM <56462552+pailloM@users.noreply.github.com> Date: Sat, 26 Feb 2022 03:46:16 -0500 Subject: [PATCH 1058/1098] Re-enable apcupsd (#67264) --- homeassistant/components/apcupsd/manifest.json | 1 - requirements_all.txt | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/apcupsd/manifest.json b/homeassistant/components/apcupsd/manifest.json index 18d5549ef9a594..13a08685c68af3 100644 --- a/homeassistant/components/apcupsd/manifest.json +++ b/homeassistant/components/apcupsd/manifest.json @@ -1,5 +1,4 @@ { - "disabled": "Integration library not compatible with Python 3.10", "domain": "apcupsd", "name": "apcupsd", "documentation": "https://www.home-assistant.io/integrations/apcupsd", diff --git a/requirements_all.txt b/requirements_all.txt index a1ed674536c384..9984b08e920687 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -322,6 +322,9 @@ anel_pwrctrl-homeassistant==0.0.1.dev2 # homeassistant.components.anthemav anthemav==1.2.0 +# homeassistant.components.apcupsd +apcaccess==0.0.13 + # homeassistant.components.apprise apprise==0.9.7 From 61b43860537995f13bae9783bf26ad261d9679f8 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 26 Feb 2022 19:37:24 +0100 Subject: [PATCH 1059/1098] Fix dhcp None hostname (#67289) * Fix dhcp None hostname * Test handle None hostname --- homeassistant/components/dhcp/__init__.py | 16 +++++++++------- tests/components/dhcp/test_init.py | 22 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/dhcp/__init__.py b/homeassistant/components/dhcp/__init__.py index a3de0e5170839b..0b5f8a49a34761 100644 --- a/homeassistant/components/dhcp/__init__.py +++ b/homeassistant/components/dhcp/__init__.py @@ -159,7 +159,7 @@ async def async_stop(self): async def async_start(self): """Start the watcher.""" - def process_client(self, ip_address, hostname, mac_address): + def process_client(self, ip_address: str, hostname: str, mac_address: str) -> None: """Process a client.""" return run_callback_threadsafe( self.hass.loop, @@ -170,7 +170,9 @@ def process_client(self, ip_address, hostname, mac_address): ).result() @callback - def async_process_client(self, ip_address, hostname, mac_address): + def async_process_client( + self, ip_address: str, hostname: str, mac_address: str + ) -> None: """Process a client.""" made_ip_address = make_ip_address(ip_address) @@ -355,15 +357,15 @@ async def async_stop(self): async def async_start(self): """Stop watching for device tracker registrations.""" self._unsub = async_dispatcher_connect( - self.hass, CONNECTED_DEVICE_REGISTERED, self._async_process_device_state + self.hass, CONNECTED_DEVICE_REGISTERED, self._async_process_device_data ) @callback - def _async_process_device_state(self, data: dict[str, Any]) -> None: + def _async_process_device_data(self, data: dict[str, str | None]) -> None: """Process a device tracker state.""" - ip_address = data.get(ATTR_IP) - hostname = data.get(ATTR_HOST_NAME, "") - mac_address = data.get(ATTR_MAC) + ip_address = data[ATTR_IP] + hostname = data[ATTR_HOST_NAME] or "" + mac_address = data[ATTR_MAC] if ip_address is None or mac_address is None: return diff --git a/tests/components/dhcp/test_init.py b/tests/components/dhcp/test_init.py index d1b8d72be67702..a809d6eb5aba2f 100644 --- a/tests/components/dhcp/test_init.py +++ b/tests/components/dhcp/test_init.py @@ -663,6 +663,28 @@ async def test_device_tracker_registered(hass): await hass.async_block_till_done() +async def test_device_tracker_registered_hostname_none(hass): + """Test handle None hostname.""" + with patch.object(hass.config_entries.flow, "async_init") as mock_init: + device_tracker_watcher = dhcp.DeviceTrackerRegisteredWatcher( + hass, + {}, + [{"domain": "mock-domain", "hostname": "connect", "macaddress": "B8B7F1*"}], + ) + await device_tracker_watcher.async_start() + await hass.async_block_till_done() + async_dispatcher_send( + hass, + CONNECTED_DEVICE_REGISTERED, + {"ip": "192.168.210.56", "mac": "b8b7f16db533", "host_name": None}, + ) + await hass.async_block_till_done() + + assert len(mock_init.mock_calls) == 0 + await device_tracker_watcher.async_stop() + await hass.async_block_till_done() + + async def test_device_tracker_hostname_and_macaddress_after_start(hass): """Test matching based on hostname and macaddress after start.""" From 23846eb1209be49bf4085396f76093279a6ab0a2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 26 Feb 2022 14:12:33 -0800 Subject: [PATCH 1060/1098] Bump frontend to 20220226.0 (#67313) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index f9ad2bd428db60..4b28744d0e3504 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20220224.0" + "home-assistant-frontend==20220226.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index fd8fe9c06817d5..a36f21efd6b75e 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -14,7 +14,7 @@ certifi>=2021.5.30 ciso8601==2.2.0 cryptography==35.0.0 hass-nabucasa==0.54.0 -home-assistant-frontend==20220224.0 +home-assistant-frontend==20220226.0 httpx==0.21.3 ifaddr==0.1.7 jinja2==3.0.3 diff --git a/requirements_all.txt b/requirements_all.txt index 9984b08e920687..626406a0bff9d9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -843,7 +843,7 @@ hole==0.7.0 holidays==0.13 # homeassistant.components.frontend -home-assistant-frontend==20220224.0 +home-assistant-frontend==20220226.0 # homeassistant.components.zwave # homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5f9291dc2e1914..964a793b9c1f08 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -553,7 +553,7 @@ hole==0.7.0 holidays==0.13 # homeassistant.components.frontend -home-assistant-frontend==20220224.0 +home-assistant-frontend==20220226.0 # homeassistant.components.zwave # homeassistant-pyozw==0.1.10 From 8c3c8ff1d4918fc707b6568d3a6f3d76e16dec93 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 26 Feb 2022 14:13:19 -0800 Subject: [PATCH 1061/1098] Bumped version to 2022.3.0b4 --- homeassistant/const.py | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 95f2b3d9f5781e..b3f86ff9d6a179 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 3 -PATCH_VERSION: Final = "0b3" +PATCH_VERSION: Final = "0b4" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/setup.cfg b/setup.cfg index 00a0d4a695f179..7cacc2260325cc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = homeassistant -version = 2022.3.0b3 +version = 2022.3.0b4 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 From b468cc8c9e90d6d1f482f16d2a203ceaa23d478d Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 27 Feb 2022 00:23:56 +0100 Subject: [PATCH 1062/1098] Remove redundant type cast (#67317) --- homeassistant/components/frontend/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index c6812f4d9dec29..803b093fd406f6 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -7,7 +7,7 @@ import logging import os import pathlib -from typing import Any, TypedDict, cast +from typing import Any, TypedDict from aiohttp import hdrs, web, web_urldispatcher import jinja2 @@ -313,7 +313,7 @@ def _frontend_root(dev_repo_path: str | None) -> pathlib.Path: # pylint: disable=import-outside-toplevel import hass_frontend - return cast(pathlib.Path, hass_frontend.where()) + return hass_frontend.where() async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: From 2639965b2419c4862eec111022c340f2752e107b Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Sun, 27 Feb 2022 11:19:20 -0800 Subject: [PATCH 1063/1098] Bump pyoverkiz to 1.3.9 in Overkiz integration (#67339) --- homeassistant/components/overkiz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/overkiz/manifest.json b/homeassistant/components/overkiz/manifest.json index 1c5d6b1b685e1f..5e8fe27e21ec57 100644 --- a/homeassistant/components/overkiz/manifest.json +++ b/homeassistant/components/overkiz/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/overkiz", "requirements": [ - "pyoverkiz==1.3.8" + "pyoverkiz==1.3.9" ], "zeroconf": [ { diff --git a/requirements_all.txt b/requirements_all.txt index 626406a0bff9d9..f04552f55d10b2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1753,7 +1753,7 @@ pyotgw==1.1b1 pyotp==2.6.0 # homeassistant.components.overkiz -pyoverkiz==1.3.8 +pyoverkiz==1.3.9 # homeassistant.components.openweathermap pyowm==3.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 964a793b9c1f08..49995b5de6ade3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1122,7 +1122,7 @@ pyotgw==1.1b1 pyotp==2.6.0 # homeassistant.components.overkiz -pyoverkiz==1.3.8 +pyoverkiz==1.3.9 # homeassistant.components.openweathermap pyowm==3.2.0 From 6d5be0167733903f0e461942dec9a30c15611553 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 27 Feb 2022 12:04:22 -0800 Subject: [PATCH 1064/1098] Guard for index error in picnic (#67345) --- homeassistant/components/picnic/coordinator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/picnic/coordinator.py b/homeassistant/components/picnic/coordinator.py index 773142a01093e9..9f387858e5fdb5 100644 --- a/homeassistant/components/picnic/coordinator.py +++ b/homeassistant/components/picnic/coordinator.py @@ -112,7 +112,7 @@ def _get_order_data(self) -> tuple[dict, dict]: next_delivery = ( copy.deepcopy(next_deliveries[-1]) if next_deliveries else {} ) - last_order = copy.deepcopy(deliveries[0]) + last_order = copy.deepcopy(deliveries[0]) if deliveries else {} except (KeyError, TypeError): # A KeyError or TypeError indicate that the response contains unexpected data return {}, {} From aee2a8bc511427c1e0859a25d02d7de2f9ece115 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 27 Feb 2022 12:59:05 -0800 Subject: [PATCH 1065/1098] Guard for non-string inputs in Alexa (#67348) --- homeassistant/components/alexa/capabilities.py | 2 ++ tests/components/alexa/test_capabilities.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index 133ad4f2bda366..327d5973892f10 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -822,6 +822,8 @@ def get_valid_inputs(source_list): """Return list of supported inputs.""" input_list = [] for source in source_list: + if not isinstance(source, str): + continue formatted_source = ( source.lower().replace("-", "").replace("_", "").replace(" ", "") ) diff --git a/tests/components/alexa/test_capabilities.py b/tests/components/alexa/test_capabilities.py index 8a9a40e3217180..d24849e100601c 100644 --- a/tests/components/alexa/test_capabilities.py +++ b/tests/components/alexa/test_capabilities.py @@ -182,7 +182,7 @@ async def test_api_increase_color_temp(hass, result, initial): @pytest.mark.parametrize( "domain,payload,source_list,idx", [ - ("media_player", "GAME CONSOLE", ["tv", "game console"], 1), + ("media_player", "GAME CONSOLE", ["tv", "game console", 10000], 1), ("media_player", "SATELLITE TV", ["satellite-tv", "game console"], 0), ("media_player", "SATELLITE TV", ["satellite_tv", "game console"], 0), ("media_player", "BAD DEVICE", ["satellite_tv", "game console"], None), From e4c8ac64a41511ea90d9a48fe9590f27107b92b2 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Mon, 28 Feb 2022 00:50:42 -0600 Subject: [PATCH 1066/1098] Bump plexapi to 4.10.0 (#67364) --- homeassistant/components/plex/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/plex/manifest.json b/homeassistant/components/plex/manifest.json index 238c25ad917c97..85a060ae7cd8c6 100644 --- a/homeassistant/components/plex/manifest.json +++ b/homeassistant/components/plex/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/plex", "requirements": [ - "plexapi==4.9.2", + "plexapi==4.10.0", "plexauth==0.0.6", "plexwebsocket==0.0.13" ], diff --git a/requirements_all.txt b/requirements_all.txt index f04552f55d10b2..e8ace669520bd2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1262,7 +1262,7 @@ pillow==9.0.1 pizzapi==0.0.3 # homeassistant.components.plex -plexapi==4.9.2 +plexapi==4.10.0 # homeassistant.components.plex plexauth==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 49995b5de6ade3..770acbd7d61eb9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -787,7 +787,7 @@ pilight==0.1.1 pillow==9.0.1 # homeassistant.components.plex -plexapi==4.9.2 +plexapi==4.10.0 # homeassistant.components.plex plexauth==0.0.6 From 06791d42f2c0f0714a0013ed0f916390abaa47a0 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 28 Feb 2022 13:19:50 +0000 Subject: [PATCH 1067/1098] Fix race when unsubscribing from MQTT topics (#67376) * Fix race when unsubscribing from MQTT topics * Improve test --- homeassistant/components/mqtt/__init__.py | 8 +++--- tests/components/mqtt/test_init.py | 32 +++++++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 1982d1f3df58ac..107bc4660c2a1e 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -967,10 +967,6 @@ def async_remove() -> None: self.subscriptions.remove(subscription) self._matching_subscriptions.cache_clear() - if any(other.topic == topic for other in self.subscriptions): - # Other subscriptions on topic remaining - don't unsubscribe. - return - # Only unsubscribe if currently connected. if self.connected: self.hass.async_create_task(self._async_unsubscribe(topic)) @@ -982,6 +978,10 @@ async def _async_unsubscribe(self, topic: str) -> None: This method is a coroutine. """ + if any(other.topic == topic for other in self.subscriptions): + # Other subscriptions on topic remaining - don't unsubscribe. + return + async with self._paho_lock: result: int | None = None result, mid = await self.hass.async_add_executor_job( diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index e589e447a01827..7296d4e81012f0 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -1056,6 +1056,38 @@ async def test_not_calling_unsubscribe_with_active_subscribers( assert not mqtt_client_mock.unsubscribe.called +async def test_unsubscribe_race(hass, mqtt_client_mock, mqtt_mock): + """Test not calling unsubscribe() when other subscribers are active.""" + # Fake that the client is connected + mqtt_mock().connected = True + + calls_a = MagicMock() + calls_b = MagicMock() + + mqtt_client_mock.reset_mock() + unsub = await mqtt.async_subscribe(hass, "test/state", calls_a) + unsub() + await mqtt.async_subscribe(hass, "test/state", calls_b) + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, "test/state", "online") + await hass.async_block_till_done() + assert not calls_a.called + assert calls_b.called + + # We allow either calls [subscribe, unsubscribe, subscribe] or [subscribe, subscribe] + expected_calls_1 = [ + call.subscribe("test/state", 0), + call.unsubscribe("test/state"), + call.subscribe("test/state", 0), + ] + expected_calls_2 = [ + call.subscribe("test/state", 0), + call.subscribe("test/state", 0), + ] + assert mqtt_client_mock.mock_calls in (expected_calls_1, expected_calls_2) + + @pytest.mark.parametrize( "mqtt_config", [{mqtt.CONF_BROKER: "mock-broker", mqtt.CONF_DISCOVERY: False}], From 4423ecbe1c6ef0ef121c73aab2c4cc6bdc666406 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Mon, 28 Feb 2022 18:38:08 -0600 Subject: [PATCH 1068/1098] Reduce magic in Sonos error handling fixture (#67401) --- homeassistant/components/sonos/helpers.py | 32 ++++++++++++++++------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/sonos/helpers.py b/homeassistant/components/sonos/helpers.py index fbc1d2642eabb1..a11847d2b0ca78 100644 --- a/homeassistant/components/sonos/helpers.py +++ b/homeassistant/components/sonos/helpers.py @@ -3,7 +3,7 @@ from collections.abc import Callable import logging -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar from soco import SoCo from soco.exceptions import SoCoException, SoCoUPnPException @@ -55,15 +55,9 @@ def wrapper(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> _R | None: ) return None - # In order of preference: - # * SonosSpeaker instance - # * SoCo instance passed as an arg - # * SoCo instance (as self) - speaker_or_soco = getattr(self, "speaker", args_soco or self) - zone_name = speaker_or_soco.zone_name - # Prefer the entity_id if available, zone name as a fallback - # Needed as SonosSpeaker instances are not entities - target = getattr(self, "entity_id", zone_name) + if (target := _find_target_identifier(self, args_soco)) is None: + raise RuntimeError("Unexpected use of soco_error") from err + message = f"Error calling {function} on {target}: {err}" raise SonosUpdateError(message) from err @@ -80,6 +74,24 @@ def wrapper(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> _R | None: return decorator +def _find_target_identifier(instance: Any, fallback_soco: SoCo | None) -> str | None: + """Extract the the best available target identifier from the provided instance object.""" + if entity_id := getattr(instance, "entity_id", None): + # SonosEntity instance + return entity_id + if zone_name := getattr(instance, "zone_name", None): + # SonosSpeaker instance + return zone_name + if speaker := getattr(instance, "speaker", None): + # Holds a SonosSpeaker instance attribute + return speaker.zone_name + if soco := getattr(instance, "soco", fallback_soco): + # Holds a SoCo instance attribute + # Only use attributes with no I/O + return soco._player_name or soco.ip_address # pylint: disable=protected-access + return None + + def hostname_to_uid(hostname: str) -> str: """Convert a Sonos hostname to a uid.""" if hostname.startswith("Sonos-"): From cd5056fdab72c0a2f3e834777b00ce8d14546dd4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 28 Feb 2022 14:39:13 -1000 Subject: [PATCH 1069/1098] Bump zeroconf to 0.38.4 (#67406) --- homeassistant/components/zeroconf/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json index fa3b8688c47e0b..dc4c7c001aeabe 100644 --- a/homeassistant/components/zeroconf/manifest.json +++ b/homeassistant/components/zeroconf/manifest.json @@ -2,7 +2,7 @@ "domain": "zeroconf", "name": "Zero-configuration networking (zeroconf)", "documentation": "https://www.home-assistant.io/integrations/zeroconf", - "requirements": ["zeroconf==0.38.3"], + "requirements": ["zeroconf==0.38.4"], "dependencies": ["network", "api"], "codeowners": ["@bdraco"], "quality_scale": "internal", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index a36f21efd6b75e..c944b6c141c68b 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -32,7 +32,7 @@ typing-extensions>=3.10.0.2,<5.0 voluptuous-serialize==2.5.0 voluptuous==0.12.2 yarl==1.7.2 -zeroconf==0.38.3 +zeroconf==0.38.4 # Constrain pycryptodome to avoid vulnerability # see https://github.com/home-assistant/core/pull/16238 diff --git a/requirements_all.txt b/requirements_all.txt index e8ace669520bd2..99c1a711d14980 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2536,7 +2536,7 @@ youtube_dl==2021.12.17 zengge==0.2 # homeassistant.components.zeroconf -zeroconf==0.38.3 +zeroconf==0.38.4 # homeassistant.components.zha zha-quirks==0.0.67 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 770acbd7d61eb9..1201b07682a465 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1567,7 +1567,7 @@ yeelight==0.7.9 youless-api==0.16 # homeassistant.components.zeroconf -zeroconf==0.38.3 +zeroconf==0.38.4 # homeassistant.components.zha zha-quirks==0.0.67 From ee3be011a543b23ecf2b077ec51c4573b2f13ba6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 28 Feb 2022 17:02:34 -0800 Subject: [PATCH 1070/1098] Bumped version to 2022.3.0b5 --- homeassistant/const.py | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index b3f86ff9d6a179..e46509d2b84d0b 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 3 -PATCH_VERSION: Final = "0b4" +PATCH_VERSION: Final = "0b5" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/setup.cfg b/setup.cfg index 7cacc2260325cc..58a3918d0ad34f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = homeassistant -version = 2022.3.0b4 +version = 2022.3.0b5 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 From d766b1732352fbf5a10be2bb56fe7d710d87592a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 1 Mar 2022 14:00:48 -1000 Subject: [PATCH 1071/1098] Partially revert powerwall abs change from #67245 (#67300) --- homeassistant/components/powerwall/sensor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/powerwall/sensor.py b/homeassistant/components/powerwall/sensor.py index bc8ab1c021554e..93b3c64d18cc37 100644 --- a/homeassistant/components/powerwall/sensor.py +++ b/homeassistant/components/powerwall/sensor.py @@ -114,7 +114,7 @@ def extra_state_attributes(self) -> dict[str, Any]: class PowerWallEnergyDirectionSensor(PowerWallEntity, SensorEntity): """Representation of an Powerwall Direction Energy sensor.""" - _attr_state_class = SensorStateClass.TOTAL_INCREASING + _attr_state_class = SensorStateClass.TOTAL _attr_native_unit_of_measurement = ENERGY_KILO_WATT_HOUR _attr_device_class = SensorDeviceClass.ENERGY @@ -160,7 +160,7 @@ def __init__( @property def native_value(self) -> float: """Get the current value in kWh.""" - return abs(self.meter.get_energy_exported()) + return self.meter.get_energy_exported() class PowerWallImportSensor(PowerWallEnergyDirectionSensor): @@ -177,4 +177,4 @@ def __init__( @property def native_value(self) -> float: """Get the current value in kWh.""" - return abs(self.meter.get_energy_imported()) + return self.meter.get_energy_imported() From 26203e99246bb75f53aff6261073e32f55a39632 Mon Sep 17 00:00:00 2001 From: Jeff <34590663+jumbledbytes@users.noreply.github.com> Date: Mon, 28 Feb 2022 11:37:11 -0800 Subject: [PATCH 1072/1098] Support disconnected Powerwall configuration (#67325) Co-authored-by: J. Nick Koston --- homeassistant/components/powerwall/binary_sensor.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/homeassistant/components/powerwall/binary_sensor.py b/homeassistant/components/powerwall/binary_sensor.py index 868d9e3076dbc9..fed47823c7f6a7 100644 --- a/homeassistant/components/powerwall/binary_sensor.py +++ b/homeassistant/components/powerwall/binary_sensor.py @@ -110,6 +110,15 @@ class PowerWallChargingStatusSensor(PowerWallEntity, BinarySensorEntity): _attr_name = "Powerwall Charging" _attr_device_class = BinarySensorDeviceClass.BATTERY_CHARGING + @property + def available(self) -> bool: + """Powerwall is available.""" + # Return False if no battery is installed + return ( + super().available + and self.data.meters.get_meter(MeterType.BATTERY) is not None + ) + @property def unique_id(self) -> str: """Device Uniqueid.""" From aeac31c926636507f0d41ccfd398762015d1555c Mon Sep 17 00:00:00 2001 From: cnico Date: Wed, 2 Mar 2022 01:21:47 +0100 Subject: [PATCH 1073/1098] Add flipr API error detection and catch it correctly. (#67405) --- homeassistant/components/flipr/__init__.py | 14 +++++++-- homeassistant/components/flipr/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/flipr/test_sensor.py | 30 ++++++++++++++++++++ 5 files changed, 44 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/flipr/__init__.py b/homeassistant/components/flipr/__init__.py index 8379845982a8b2..1a9f3dc03140b8 100644 --- a/homeassistant/components/flipr/__init__.py +++ b/homeassistant/components/flipr/__init__.py @@ -3,6 +3,7 @@ import logging from flipr_api import FliprAPIRestClient +from flipr_api.exceptions import FliprError from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, Platform @@ -11,6 +12,7 @@ from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, + UpdateFailed, ) from .const import ATTRIBUTION, CONF_FLIPR_ID, DOMAIN, MANUFACTURER, NAME @@ -68,9 +70,15 @@ def __init__(self, hass, entry): async def _async_update_data(self): """Fetch data from API endpoint.""" - return await self.hass.async_add_executor_job( - self.client.get_pool_measure_latest, self.flipr_id - ) + try: + data = await self.hass.async_add_executor_job( + self.client.get_pool_measure_latest, self.flipr_id + ) + except (FliprError) as error: + _LOGGER.error(error) + raise UpdateFailed from error + + return data class FliprEntity(CoordinatorEntity): diff --git a/homeassistant/components/flipr/manifest.json b/homeassistant/components/flipr/manifest.json index 357b5aeb160ca8..77388393d3f408 100644 --- a/homeassistant/components/flipr/manifest.json +++ b/homeassistant/components/flipr/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/flipr", "requirements": [ - "flipr-api==1.4.1"], + "flipr-api==1.4.2"], "codeowners": [ "@cnico" ], diff --git a/requirements_all.txt b/requirements_all.txt index 99c1a711d14980..ef6c59a53efe68 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -679,7 +679,7 @@ fixerio==1.0.0a0 fjaraskupan==1.0.2 # homeassistant.components.flipr -flipr-api==1.4.1 +flipr-api==1.4.2 # homeassistant.components.flux_led flux_led==0.28.27 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1201b07682a465..85b53a3408ca45 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -431,7 +431,7 @@ fivem-api==0.1.2 fjaraskupan==1.0.2 # homeassistant.components.flipr -flipr-api==1.4.1 +flipr-api==1.4.2 # homeassistant.components.flux_led flux_led==0.28.27 diff --git a/tests/components/flipr/test_sensor.py b/tests/components/flipr/test_sensor.py index 7fd04fbc99244e..45816801472153 100644 --- a/tests/components/flipr/test_sensor.py +++ b/tests/components/flipr/test_sensor.py @@ -2,6 +2,8 @@ from datetime import datetime from unittest.mock import patch +from flipr_api.exceptions import FliprError + from homeassistant.components.flipr.const import CONF_FLIPR_ID, DOMAIN from homeassistant.const import ( ATTR_ICON, @@ -84,3 +86,31 @@ async def test_sensors(hass: HomeAssistant) -> None: assert state.attributes.get(ATTR_ICON) == "mdi:pool" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "mV" assert state.state == "0.23654886" + + +async def test_error_flipr_api_sensors(hass: HomeAssistant) -> None: + """Test the Flipr sensors error.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="test_entry_unique_id", + data={ + CONF_EMAIL: "toto@toto.com", + CONF_PASSWORD: "myPassword", + CONF_FLIPR_ID: "myfliprid", + }, + ) + + entry.add_to_hass(hass) + + registry = await hass.helpers.entity_registry.async_get_registry() + + with patch( + "flipr_api.FliprAPIRestClient.get_pool_measure_latest", + side_effect=FliprError("Error during flipr data retrieval..."), + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + # Check entity is not generated because of the FliprError raised. + entity = registry.async_get("sensor.flipr_myfliprid_red_ox") + assert entity is None From f1620cbb2e00849c21f6fa39a26e89576a0fba83 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 1 Mar 2022 16:56:05 -0800 Subject: [PATCH 1074/1098] Add support for detecting hostname based addresses as internal (#67407) --- homeassistant/helpers/network.py | 4 +- tests/components/roku/test_media_player.py | 13 ++++-- tests/helpers/test_network.py | 51 ++++++++++++++++------ 3 files changed, 51 insertions(+), 17 deletions(-) diff --git a/homeassistant/helpers/network.py b/homeassistant/helpers/network.py index 9d7780ab9005df..a8c4b3cf45875e 100644 --- a/homeassistant/helpers/network.py +++ b/homeassistant/helpers/network.py @@ -25,7 +25,9 @@ class NoURLAvailableError(HomeAssistantError): def is_internal_request(hass: HomeAssistant) -> bool: """Test if the current request is internal.""" try: - _get_internal_url(hass, require_current_request=True) + get_url( + hass, allow_external=False, allow_cloud=False, require_current_request=True + ) return True except NoURLAvailableError: return False diff --git a/tests/components/roku/test_media_player.py b/tests/components/roku/test_media_player.py index 79b996530e308f..050814e381710a 100644 --- a/tests/components/roku/test_media_player.py +++ b/tests/components/roku/test_media_player.py @@ -759,7 +759,10 @@ async def test_media_browse( assert msg["result"]["children"][0]["title"] == "Roku Channel Store" assert msg["result"]["children"][0]["media_content_type"] == MEDIA_TYPE_APP assert msg["result"]["children"][0]["media_content_id"] == "11" - assert "/browse_media/app/11" in msg["result"]["children"][0]["thumbnail"] + assert ( + msg["result"]["children"][0]["thumbnail"] + == "http://192.168.1.160:8060/query/icon/11" + ) assert msg["result"]["children"][0]["can_play"] # test invalid media type @@ -1016,14 +1019,18 @@ async def test_tv_media_browse( assert msg["result"]["children"][0]["media_content_type"] == MEDIA_TYPE_APP assert msg["result"]["children"][0]["media_content_id"] == "tvinput.hdmi2" assert ( - "/browse_media/app/tvinput.hdmi2" in msg["result"]["children"][0]["thumbnail"] + msg["result"]["children"][0]["thumbnail"] + == "http://192.168.1.160:8060/query/icon/tvinput.hdmi2" ) assert msg["result"]["children"][0]["can_play"] assert msg["result"]["children"][3]["title"] == "Roku Channel Store" assert msg["result"]["children"][3]["media_content_type"] == MEDIA_TYPE_APP assert msg["result"]["children"][3]["media_content_id"] == "11" - assert "/browse_media/app/11" in msg["result"]["children"][3]["thumbnail"] + assert ( + msg["result"]["children"][3]["thumbnail"] + == "http://192.168.1.160:8060/query/icon/11" + ) assert msg["result"]["children"][3]["can_play"] # test channels diff --git a/tests/helpers/test_network.py b/tests/helpers/test_network.py index 15a9b8d1ff8a75..0838375fd1f7e2 100644 --- a/tests/helpers/test_network.py +++ b/tests/helpers/test_network.py @@ -20,6 +20,17 @@ from tests.common import mock_component +@pytest.fixture(name="mock_current_request") +def mock_current_request_mock(): + """Mock the current request.""" + mock_current_request = Mock(name="mock_request") + with patch( + "homeassistant.helpers.network.http.current_request", + Mock(get=mock_current_request), + ): + yield mock_current_request + + async def test_get_url_internal(hass: HomeAssistant): """Test getting an instance URL when the user has set an internal URL.""" assert hass.config.internal_url is None @@ -611,7 +622,7 @@ async def test_get_current_request_url_with_known_host( get_url(hass, require_current_request=True) -async def test_is_internal_request(hass: HomeAssistant): +async def test_is_internal_request(hass: HomeAssistant, mock_current_request): """Test if accessing an instance on its internal URL.""" # Test with internal URL: http://example.local:8123 await async_process_ha_core_config( @@ -620,18 +631,16 @@ async def test_is_internal_request(hass: HomeAssistant): ) assert hass.config.internal_url == "http://example.local:8123" + + # No request active + mock_current_request.return_value = None assert not is_internal_request(hass) - with patch( - "homeassistant.helpers.network._get_request_host", return_value="example.local" - ): - assert is_internal_request(hass) + mock_current_request.return_value = Mock(url="http://example.local:8123") + assert is_internal_request(hass) - with patch( - "homeassistant.helpers.network._get_request_host", - return_value="no_match.example.local", - ): - assert not is_internal_request(hass) + mock_current_request.return_value = Mock(url="http://no_match.example.local:8123") + assert not is_internal_request(hass) # Test with internal URL: http://192.168.0.1:8123 await async_process_ha_core_config( @@ -642,10 +651,26 @@ async def test_is_internal_request(hass: HomeAssistant): assert hass.config.internal_url == "http://192.168.0.1:8123" assert not is_internal_request(hass) - with patch( - "homeassistant.helpers.network._get_request_host", return_value="192.168.0.1" + mock_current_request.return_value = Mock(url="http://192.168.0.1:8123") + assert is_internal_request(hass) + + # Test for matching against local IP + hass.config.api = Mock(use_ssl=False, local_ip="192.168.123.123", port=8123) + for allowed in ("127.0.0.1", "192.168.123.123"): + mock_current_request.return_value = Mock(url=f"http://{allowed}:8123") + assert is_internal_request(hass), mock_current_request.return_value.url + + # Test for matching against HassOS hostname + with patch.object( + hass.components.hassio, "is_hassio", return_value=True + ), patch.object( + hass.components.hassio, + "get_host_info", + return_value={"hostname": "hellohost"}, ): - assert is_internal_request(hass) + for allowed in ("hellohost", "hellohost.local"): + mock_current_request.return_value = Mock(url=f"http://{allowed}:8123") + assert is_internal_request(hass), mock_current_request.return_value.url async def test_is_hass_url(hass): From 768a0311287f8af1a47b5b42f2233ed6280dbbd8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 1 Mar 2022 15:14:14 -0800 Subject: [PATCH 1075/1098] Restore children media class (#67409) --- homeassistant/components/dlna_dms/dms.py | 6 ++++-- .../components/media_player/browse_media.py | 16 +++++++--------- tests/components/cast/test_media_player.py | 5 +++++ tests/components/dlna_dmr/test_media_player.py | 2 ++ tests/components/motioneye/test_media_source.py | 9 +++++++++ 5 files changed, 27 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/dlna_dms/dms.py b/homeassistant/components/dlna_dms/dms.py index d3a65448f84fb4..1166d1db6db09b 100644 --- a/homeassistant/components/dlna_dms/dms.py +++ b/homeassistant/components/dlna_dms/dms.py @@ -575,7 +575,8 @@ async def async_browse_search(self, query: str) -> BrowseMediaSource: children=children, ) - media_source.calculate_children_class() + if media_source.children: + media_source.calculate_children_class() return media_source @@ -645,7 +646,8 @@ def _didl_to_media_source( thumbnail=self._didl_thumbnail_url(item), ) - media_source.calculate_children_class() + if media_source.children: + media_source.calculate_children_class() return media_source diff --git a/homeassistant/components/media_player/browse_media.py b/homeassistant/components/media_player/browse_media.py index 26494e4c8a7849..fa825042817137 100644 --- a/homeassistant/components/media_player/browse_media.py +++ b/homeassistant/components/media_player/browse_media.py @@ -3,6 +3,7 @@ from datetime import timedelta import logging +from typing import Any from urllib.parse import quote import yarl @@ -74,11 +75,15 @@ def __init__( def as_dict(self, *, parent: bool = True) -> dict: """Convert Media class to browse media dictionary.""" - response = { + if self.children_media_class is None and self.children: + self.calculate_children_class() + + response: dict[str, Any] = { "title": self.title, "media_class": self.media_class, "media_content_type": self.media_content_type, "media_content_id": self.media_content_id, + "children_media_class": self.children_media_class, "can_play": self.can_play, "can_expand": self.can_expand, "thumbnail": self.thumbnail, @@ -87,11 +92,7 @@ def as_dict(self, *, parent: bool = True) -> dict: if not parent: return response - if self.children_media_class is None: - self.calculate_children_class() - response["not_shown"] = self.not_shown - response["children_media_class"] = self.children_media_class if self.children: response["children"] = [ @@ -104,11 +105,8 @@ def as_dict(self, *, parent: bool = True) -> dict: def calculate_children_class(self) -> None: """Count the children media classes and calculate the correct class.""" - if self.children is None or len(self.children) == 0: - return - self.children_media_class = MEDIA_CLASS_DIRECTORY - + assert self.children is not None proposed_class = self.children[0].media_class if all(child.media_class == proposed_class for child in self.children): self.children_media_class = proposed_class diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 663941de77aa65..40a1269557d368 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -857,6 +857,7 @@ async def test_entity_browse_media(hass: HomeAssistant, hass_ws_client): "can_play": True, "can_expand": False, "thumbnail": None, + "children_media_class": None, } assert expected_child_1 in response["result"]["children"] @@ -868,6 +869,7 @@ async def test_entity_browse_media(hass: HomeAssistant, hass_ws_client): "can_play": True, "can_expand": False, "thumbnail": None, + "children_media_class": None, } assert expected_child_2 in response["result"]["children"] @@ -911,6 +913,7 @@ async def test_entity_browse_media_audio_only( "can_play": True, "can_expand": False, "thumbnail": None, + "children_media_class": None, } assert expected_child_1 not in response["result"]["children"] @@ -922,6 +925,7 @@ async def test_entity_browse_media_audio_only( "can_play": True, "can_expand": False, "thumbnail": None, + "children_media_class": None, } assert expected_child_2 in response["result"]["children"] @@ -1858,6 +1862,7 @@ async def test_cast_platform_browse_media(hass: HomeAssistant, hass_ws_client): "can_play": False, "can_expand": True, "thumbnail": "https://brands.home-assistant.io/_/spotify/logo.png", + "children_media_class": None, } assert expected_child in response["result"]["children"] diff --git a/tests/components/dlna_dmr/test_media_player.py b/tests/components/dlna_dmr/test_media_player.py index 896968557c1c60..a9ac5946f307fa 100644 --- a/tests/components/dlna_dmr/test_media_player.py +++ b/tests/components/dlna_dmr/test_media_player.py @@ -961,6 +961,7 @@ async def test_browse_media( "can_play": True, "can_expand": False, "thumbnail": None, + "children_media_class": None, } assert expected_child_video in response["result"]["children"] @@ -972,6 +973,7 @@ async def test_browse_media( "can_play": True, "can_expand": False, "thumbnail": None, + "children_media_class": None, } assert expected_child_audio in response["result"]["children"] diff --git a/tests/components/motioneye/test_media_source.py b/tests/components/motioneye/test_media_source.py index 6c0e46b34c6973..6979d5c645dde0 100644 --- a/tests/components/motioneye/test_media_source.py +++ b/tests/components/motioneye/test_media_source.py @@ -111,6 +111,7 @@ async def test_async_browse_media_success(hass: HomeAssistant) -> None: "can_play": False, "can_expand": True, "thumbnail": None, + "children_media_class": "directory", } ], "not_shown": 0, @@ -143,6 +144,7 @@ async def test_async_browse_media_success(hass: HomeAssistant) -> None: "can_play": False, "can_expand": True, "thumbnail": None, + "children_media_class": "directory", } ], "not_shown": 0, @@ -174,6 +176,7 @@ async def test_async_browse_media_success(hass: HomeAssistant) -> None: "can_play": False, "can_expand": True, "thumbnail": None, + "children_media_class": "video", }, { "title": "Images", @@ -186,6 +189,7 @@ async def test_async_browse_media_success(hass: HomeAssistant) -> None: "can_play": False, "can_expand": True, "thumbnail": None, + "children_media_class": "image", }, ], "not_shown": 0, @@ -220,6 +224,7 @@ async def test_async_browse_media_success(hass: HomeAssistant) -> None: "can_play": False, "can_expand": True, "thumbnail": None, + "children_media_class": "directory", } ], "not_shown": 0, @@ -255,6 +260,7 @@ async def test_async_browse_media_success(hass: HomeAssistant) -> None: "can_play": True, "can_expand": False, "thumbnail": "http://movie", + "children_media_class": None, }, { "title": "00-36-49.mp4", @@ -268,6 +274,7 @@ async def test_async_browse_media_success(hass: HomeAssistant) -> None: "can_play": True, "can_expand": False, "thumbnail": "http://movie", + "children_media_class": None, }, { "title": "00-02-27.mp4", @@ -281,6 +288,7 @@ async def test_async_browse_media_success(hass: HomeAssistant) -> None: "can_play": True, "can_expand": False, "thumbnail": "http://movie", + "children_media_class": None, }, ], "not_shown": 0, @@ -331,6 +339,7 @@ async def test_async_browse_media_images_success(hass: HomeAssistant) -> None: "can_play": False, "can_expand": False, "thumbnail": "http://image", + "children_media_class": None, } ], "not_shown": 0, From b31e570ec73fa2dcdf641e7169cde285d698b5bc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 1 Mar 2022 09:18:09 -1000 Subject: [PATCH 1076/1098] Avoid creating wiring select for Magic Home if its not supported (#67417) --- homeassistant/components/flux_led/select.py | 2 +- tests/components/flux_led/test_select.py | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/flux_led/select.py b/homeassistant/components/flux_led/select.py index 4067110e336b71..63929740020f93 100644 --- a/homeassistant/components/flux_led/select.py +++ b/homeassistant/components/flux_led/select.py @@ -64,7 +64,7 @@ async def async_setup_entry( coordinator, base_unique_id, f"{name} Operating Mode", "operating_mode" ) ) - if device.wirings: + if device.wirings and device.wiring is not None: entities.append( FluxWiringsSelect(coordinator, base_unique_id, f"{name} Wiring", "wiring") ) diff --git a/tests/components/flux_led/test_select.py b/tests/components/flux_led/test_select.py index b2a88b00fe06b6..91be62e5ab7d67 100644 --- a/tests/components/flux_led/test_select.py +++ b/tests/components/flux_led/test_select.py @@ -299,3 +299,23 @@ async def test_select_white_channel_type(hass: HomeAssistant) -> None: == WhiteChannelType.NATURAL.name.lower() ) assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_select_device_no_wiring(hass: HomeAssistant) -> None: + """Test select is not created if the device does not support wiring.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: IP_ADDRESS, CONF_NAME: DEFAULT_ENTRY_TITLE}, + unique_id=MAC_ADDRESS, + ) + config_entry.add_to_hass(hass) + bulb = _mocked_bulb() + bulb.wiring = None + bulb.wirings = ["RGB", "GRB"] + bulb.raw_state = bulb.raw_state._replace(model_num=0x25) + with _patch_discovery(), _patch_wifibulb(device=bulb): + await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) + await hass.async_block_till_done() + + wiring_entity_id = "select.bulb_rgbcw_ddeeff_wiring" + assert hass.states.get(wiring_entity_id) is None From 40d72b31884f91a7e70e7071f2529f07fd573647 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Tue, 1 Mar 2022 16:40:00 +0100 Subject: [PATCH 1077/1098] CONF_SLAVE do not have default 0 in a validator (#67418) --- homeassistant/components/modbus/validators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/modbus/validators.py b/homeassistant/components/modbus/validators.py index 4f910043d12c0f..347e8d3fc7284f 100644 --- a/homeassistant/components/modbus/validators.py +++ b/homeassistant/components/modbus/validators.py @@ -209,7 +209,7 @@ def duplicate_entity_validator(config: dict) -> dict: addr += "_" + str(entry[CONF_COMMAND_ON]) if CONF_COMMAND_OFF in entry: addr += "_" + str(entry[CONF_COMMAND_OFF]) - addr += "_" + str(entry[CONF_SLAVE]) + addr += "_" + str(entry.get(CONF_SLAVE, 0)) if addr in addresses: err = f"Modbus {component}/{name} address {addr} is duplicate, second entry not loaded!" _LOGGER.warning(err) From 47812c6b911964abc211a2b888f761a4fdbca08d Mon Sep 17 00:00:00 2001 From: JeroenTuinstra <47460053+JeroenTuinstra@users.noreply.github.com> Date: Wed, 2 Mar 2022 00:12:54 +0100 Subject: [PATCH 1078/1098] Correct selector for remote integration line 50 (#67432) --- homeassistant/components/remote/services.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/remote/services.yaml b/homeassistant/components/remote/services.yaml index 3130484d10b57e..bdeef15971ec1b 100644 --- a/homeassistant/components/remote/services.yaml +++ b/homeassistant/components/remote/services.yaml @@ -47,7 +47,7 @@ send_command: required: true example: "Play" selector: - text: + object: num_repeats: name: Repeats description: The number of times you want to repeat the command(s). From 9a306e2a89cdac0cb6ba40b986b742e29d44c509 Mon Sep 17 00:00:00 2001 From: Teemu R Date: Tue, 1 Mar 2022 18:19:34 +0100 Subject: [PATCH 1079/1098] Bump python-songpal to 0.14.1 (#67435) Changelog https://github.com/rytilahti/python-songpal/releases/tag/0.14.1 --- homeassistant/components/songpal/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/songpal/manifest.json b/homeassistant/components/songpal/manifest.json index 80a26a56b2281a..8825de877e5008 100644 --- a/homeassistant/components/songpal/manifest.json +++ b/homeassistant/components/songpal/manifest.json @@ -3,7 +3,7 @@ "name": "Sony Songpal", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/songpal", - "requirements": ["python-songpal==0.14"], + "requirements": ["python-songpal==0.14.1"], "codeowners": ["@rytilahti", "@shenxn"], "ssdp": [ { diff --git a/requirements_all.txt b/requirements_all.txt index ef6c59a53efe68..672cdec1707095 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1982,7 +1982,7 @@ python-smarttub==0.0.29 python-sochain-api==0.0.2 # homeassistant.components.songpal -python-songpal==0.14 +python-songpal==0.14.1 # homeassistant.components.tado python-tado==0.12.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 85b53a3408ca45..3c76d58854c809 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1234,7 +1234,7 @@ python-picnic-api==1.1.0 python-smarttub==0.0.29 # homeassistant.components.songpal -python-songpal==0.14 +python-songpal==0.14.1 # homeassistant.components.tado python-tado==0.12.0 From 99322e2658335629219e20c7d300d57f14948608 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 2 Mar 2022 01:06:36 +0100 Subject: [PATCH 1080/1098] Fix CO2Signal having unknown data (#67453) --- homeassistant/components/co2signal/sensor.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/co2signal/sensor.py b/homeassistant/components/co2signal/sensor.py index 8d691b0e5c9ebe..b10cd054ff9f4f 100644 --- a/homeassistant/components/co2signal/sensor.py +++ b/homeassistant/components/co2signal/sensor.py @@ -92,14 +92,15 @@ def __init__( def available(self) -> bool: """Return True if entity is available.""" return ( - super().available - and self.coordinator.data["data"].get(self._description.key) is not None + super().available and self._description.key in self.coordinator.data["data"] ) @property def native_value(self) -> StateType: """Return sensor state.""" - return round(self.coordinator.data["data"][self._description.key], 2) # type: ignore[misc] + if (value := self.coordinator.data["data"][self._description.key]) is None: # type: ignore[misc] + return None + return round(value, 2) @property def native_unit_of_measurement(self) -> str | None: From fa01715bbbe59c3060a96e49e4a116da4918a4cd Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 1 Mar 2022 16:05:23 -0800 Subject: [PATCH 1081/1098] Bump frontend to 20220301.0 (#67457) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 4b28744d0e3504..cc118e23dc91fe 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20220226.0" + "home-assistant-frontend==20220301.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index c944b6c141c68b..bd9e1da6a69e32 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -14,7 +14,7 @@ certifi>=2021.5.30 ciso8601==2.2.0 cryptography==35.0.0 hass-nabucasa==0.54.0 -home-assistant-frontend==20220226.0 +home-assistant-frontend==20220301.0 httpx==0.21.3 ifaddr==0.1.7 jinja2==3.0.3 diff --git a/requirements_all.txt b/requirements_all.txt index 672cdec1707095..4d6dcefb10ff91 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -843,7 +843,7 @@ hole==0.7.0 holidays==0.13 # homeassistant.components.frontend -home-assistant-frontend==20220226.0 +home-assistant-frontend==20220301.0 # homeassistant.components.zwave # homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3c76d58854c809..c7828110fc26ec 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -553,7 +553,7 @@ hole==0.7.0 holidays==0.13 # homeassistant.components.frontend -home-assistant-frontend==20220226.0 +home-assistant-frontend==20220301.0 # homeassistant.components.zwave # homeassistant-pyozw==0.1.10 From 17bc8c64f8e65efb46d8ba98951ccfecbe7b4e15 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Wed, 2 Mar 2022 01:22:15 +0100 Subject: [PATCH 1082/1098] Add missing temperature sensor for Shelly Motion2 (#67458) --- homeassistant/components/shelly/sensor.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index ce9c57f588966a..21a7447e2b2854 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -215,6 +215,15 @@ class RestSensorDescription(RestEntityDescription, SensorEntityDescription): icon="mdi:gauge", state_class=SensorStateClass.MEASUREMENT, ), + ("sensor", "temp"): BlockSensorDescription( + key="sensor|temp", + name="Temperature", + unit_fn=temperature_unit, + value=lambda value: round(value, 1), + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + ), ("sensor", "extTemp"): BlockSensorDescription( key="sensor|extTemp", name="Temperature", From 1ebb4cf395a99724dbe544a01894f0a36486672d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 1 Mar 2022 17:21:51 -0800 Subject: [PATCH 1083/1098] Bumped version to 2022.3.0b6 --- homeassistant/const.py | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index e46509d2b84d0b..51af5c06d22a82 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 3 -PATCH_VERSION: Final = "0b5" +PATCH_VERSION: Final = "0b6" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/setup.cfg b/setup.cfg index 58a3918d0ad34f..4d686e63c5b518 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = homeassistant -version = 2022.3.0b5 +version = 2022.3.0b6 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 From 4c0ba7cd77204fa5025c89a84c83dea427d0af93 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 2 Mar 2022 16:49:48 +0100 Subject: [PATCH 1084/1098] Improve mobile_app key handling (#67429) --- homeassistant/components/mobile_app/const.py | 1 + .../components/mobile_app/helpers.py | 76 ++++-- .../components/mobile_app/webhook.py | 25 +- tests/components/mobile_app/test_http_api.py | 44 ++++ tests/components/mobile_app/test_webhook.py | 225 +++++++++++++++++- 5 files changed, 345 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/mobile_app/const.py b/homeassistant/components/mobile_app/const.py index a2a4e15ee72729..ba81a0484cf042 100644 --- a/homeassistant/components/mobile_app/const.py +++ b/homeassistant/components/mobile_app/const.py @@ -28,6 +28,7 @@ ATTR_DEVICE_NAME = "device_name" ATTR_MANUFACTURER = "manufacturer" ATTR_MODEL = "model" +ATTR_NO_LEGACY_ENCRYPTION = "no_legacy_encryption" ATTR_OS_NAME = "os_name" ATTR_OS_VERSION = "os_version" ATTR_PUSH_WEBSOCKET_CHANNEL = "push_websocket_channel" diff --git a/homeassistant/components/mobile_app/helpers.py b/homeassistant/components/mobile_app/helpers.py index b7d38357a786d2..545c3511fc9394 100644 --- a/homeassistant/components/mobile_app/helpers.py +++ b/homeassistant/components/mobile_app/helpers.py @@ -7,7 +7,7 @@ import logging from aiohttp.web import Response, json_response -from nacl.encoding import Base64Encoder +from nacl.encoding import Base64Encoder, HexEncoder, RawEncoder from nacl.secret import SecretBox from homeassistant.const import ATTR_DEVICE_ID, CONTENT_TYPE_JSON @@ -23,6 +23,7 @@ ATTR_DEVICE_NAME, ATTR_MANUFACTURER, ATTR_MODEL, + ATTR_NO_LEGACY_ENCRYPTION, ATTR_OS_VERSION, ATTR_SUPPORTS_ENCRYPTION, CONF_SECRET, @@ -34,7 +35,7 @@ _LOGGER = logging.getLogger(__name__) -def setup_decrypt() -> tuple[int, Callable]: +def setup_decrypt(key_encoder) -> tuple[int, Callable]: """Return decryption function and length of key. Async friendly. @@ -42,12 +43,14 @@ def setup_decrypt() -> tuple[int, Callable]: def decrypt(ciphertext, key): """Decrypt ciphertext using key.""" - return SecretBox(key).decrypt(ciphertext, encoder=Base64Encoder) + return SecretBox(key, encoder=key_encoder).decrypt( + ciphertext, encoder=Base64Encoder + ) return (SecretBox.KEY_SIZE, decrypt) -def setup_encrypt() -> tuple[int, Callable]: +def setup_encrypt(key_encoder) -> tuple[int, Callable]: """Return encryption function and length of key. Async friendly. @@ -55,15 +58,22 @@ def setup_encrypt() -> tuple[int, Callable]: def encrypt(ciphertext, key): """Encrypt ciphertext using key.""" - return SecretBox(key).encrypt(ciphertext, encoder=Base64Encoder) + return SecretBox(key, encoder=key_encoder).encrypt( + ciphertext, encoder=Base64Encoder + ) return (SecretBox.KEY_SIZE, encrypt) -def _decrypt_payload(key: str | None, ciphertext: str) -> dict[str, str] | None: +def _decrypt_payload_helper( + key: str | None, + ciphertext: str, + get_key_bytes: Callable[[str, int], str | bytes], + key_encoder, +) -> dict[str, str] | None: """Decrypt encrypted payload.""" try: - keylen, decrypt = setup_decrypt() + keylen, decrypt = setup_decrypt(key_encoder) except OSError: _LOGGER.warning("Ignoring encrypted payload because libsodium not installed") return None @@ -72,18 +82,33 @@ def _decrypt_payload(key: str | None, ciphertext: str) -> dict[str, str] | None: _LOGGER.warning("Ignoring encrypted payload because no decryption key known") return None - key_bytes = key.encode("utf-8") - key_bytes = key_bytes[:keylen] - key_bytes = key_bytes.ljust(keylen, b"\0") + key_bytes = get_key_bytes(key, keylen) - try: - msg_bytes = decrypt(ciphertext, key_bytes) - message = json.loads(msg_bytes.decode("utf-8")) - _LOGGER.debug("Successfully decrypted mobile_app payload") - return message - except ValueError: - _LOGGER.warning("Ignoring encrypted payload because unable to decrypt") - return None + msg_bytes = decrypt(ciphertext, key_bytes) + message = json.loads(msg_bytes.decode("utf-8")) + _LOGGER.debug("Successfully decrypted mobile_app payload") + return message + + +def _decrypt_payload(key: str | None, ciphertext: str) -> dict[str, str] | None: + """Decrypt encrypted payload.""" + + def get_key_bytes(key: str, keylen: int) -> str: + return key + + return _decrypt_payload_helper(key, ciphertext, get_key_bytes, HexEncoder) + + +def _decrypt_payload_legacy(key: str | None, ciphertext: str) -> dict[str, str] | None: + """Decrypt encrypted payload.""" + + def get_key_bytes(key: str, keylen: int) -> bytes: + key_bytes = key.encode("utf-8") + key_bytes = key_bytes[:keylen] + key_bytes = key_bytes.ljust(keylen, b"\0") + return key_bytes + + return _decrypt_payload_helper(key, ciphertext, get_key_bytes, RawEncoder) def registration_context(registration: dict) -> Context: @@ -158,11 +183,16 @@ def webhook_response( data = json.dumps(data, cls=JSONEncoder) if registration[ATTR_SUPPORTS_ENCRYPTION]: - keylen, encrypt = setup_encrypt() - - key = registration[CONF_SECRET].encode("utf-8") - key = key[:keylen] - key = key.ljust(keylen, b"\0") + keylen, encrypt = setup_encrypt( + HexEncoder if ATTR_NO_LEGACY_ENCRYPTION in registration else RawEncoder + ) + + if ATTR_NO_LEGACY_ENCRYPTION in registration: + key = registration[CONF_SECRET] + else: + key = registration[CONF_SECRET].encode("utf-8") + key = key[:keylen] + key = key.ljust(keylen, b"\0") enc_data = encrypt(data.encode("utf-8"), key).decode("utf-8") data = json.dumps({"encrypted": True, "encrypted_data": enc_data}) diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index 221c4eef733dc6..860b8ef7b53dba 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -7,6 +7,7 @@ import secrets from aiohttp.web import HTTPBadRequest, Request, Response, json_response +from nacl.exceptions import CryptoError from nacl.secret import SecretBox import voluptuous as vol @@ -58,6 +59,7 @@ ATTR_EVENT_TYPE, ATTR_MANUFACTURER, ATTR_MODEL, + ATTR_NO_LEGACY_ENCRYPTION, ATTR_OS_VERSION, ATTR_SENSOR_ATTRIBUTES, ATTR_SENSOR_DEVICE_CLASS, @@ -97,6 +99,7 @@ ) from .helpers import ( _decrypt_payload, + _decrypt_payload_legacy, empty_okay_response, error_response, registration_context, @@ -191,7 +194,27 @@ async def handle_webhook( if req_data[ATTR_WEBHOOK_ENCRYPTED]: enc_data = req_data[ATTR_WEBHOOK_ENCRYPTED_DATA] - webhook_payload = _decrypt_payload(config_entry.data[CONF_SECRET], enc_data) + try: + webhook_payload = _decrypt_payload(config_entry.data[CONF_SECRET], enc_data) + if ATTR_NO_LEGACY_ENCRYPTION not in config_entry.data: + data = {**config_entry.data, ATTR_NO_LEGACY_ENCRYPTION: True} + hass.config_entries.async_update_entry(config_entry, data=data) + except CryptoError: + if ATTR_NO_LEGACY_ENCRYPTION not in config_entry.data: + try: + webhook_payload = _decrypt_payload_legacy( + config_entry.data[CONF_SECRET], enc_data + ) + except CryptoError: + _LOGGER.warning( + "Ignoring encrypted payload because unable to decrypt" + ) + except ValueError: + _LOGGER.warning("Ignoring invalid encrypted payload") + else: + _LOGGER.warning("Ignoring encrypted payload because unable to decrypt") + except ValueError: + _LOGGER.warning("Ignoring invalid encrypted payload") if webhook_type not in WEBHOOK_COMMANDS: _LOGGER.error( diff --git a/tests/components/mobile_app/test_http_api.py b/tests/components/mobile_app/test_http_api.py index 5d92418bba2c24..4c4e9b54ccfdc5 100644 --- a/tests/components/mobile_app/test_http_api.py +++ b/tests/components/mobile_app/test_http_api.py @@ -1,4 +1,5 @@ """Tests for the mobile_app HTTP API.""" +from binascii import unhexlify from http import HTTPStatus import json from unittest.mock import patch @@ -75,6 +76,49 @@ async def test_registration_encryption(hass, hass_client): assert resp.status == HTTPStatus.CREATED register_json = await resp.json() + key = unhexlify(register_json[CONF_SECRET]) + + payload = json.dumps(RENDER_TEMPLATE["data"]).encode("utf-8") + + data = SecretBox(key).encrypt(payload, encoder=Base64Encoder).decode("utf-8") + + container = {"type": "render_template", "encrypted": True, "encrypted_data": data} + + resp = await api_client.post( + f"/api/webhook/{register_json[CONF_WEBHOOK_ID]}", json=container + ) + + assert resp.status == HTTPStatus.OK + + webhook_json = await resp.json() + assert "encrypted_data" in webhook_json + + decrypted_data = SecretBox(key).decrypt( + webhook_json["encrypted_data"], encoder=Base64Encoder + ) + decrypted_data = decrypted_data.decode("utf-8") + + assert json.loads(decrypted_data) == {"one": "Hello world"} + + +async def test_registration_encryption_legacy(hass, hass_client): + """Test that registrations happen.""" + try: + from nacl.encoding import Base64Encoder + from nacl.secret import SecretBox + except (ImportError, OSError): + pytest.skip("libnacl/libsodium is not installed") + return + + await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + + api_client = await hass_client() + + resp = await api_client.post("/api/mobile_app/registrations", json=REGISTER) + + assert resp.status == HTTPStatus.CREATED + register_json = await resp.json() + keylen = SecretBox.KEY_SIZE key = register_json[CONF_SECRET].encode("utf-8") key = key[:keylen] diff --git a/tests/components/mobile_app/test_webhook.py b/tests/components/mobile_app/test_webhook.py index 48b61988de20c6..5f220cf0ebe820 100644 --- a/tests/components/mobile_app/test_webhook.py +++ b/tests/components/mobile_app/test_webhook.py @@ -1,4 +1,5 @@ """Webhook tests for mobile_app.""" +from binascii import unhexlify from http import HTTPStatus from unittest.mock import patch @@ -22,7 +23,29 @@ from tests.common import async_mock_service -def encrypt_payload(secret_key, payload): +def encrypt_payload(secret_key, payload, encode_json=True): + """Return a encrypted payload given a key and dictionary of data.""" + try: + from nacl.encoding import Base64Encoder + from nacl.secret import SecretBox + except (ImportError, OSError): + pytest.skip("libnacl/libsodium is not installed") + return + + import json + + prepped_key = unhexlify(secret_key) + + if encode_json: + payload = json.dumps(payload) + payload = payload.encode("utf-8") + + return ( + SecretBox(prepped_key).encrypt(payload, encoder=Base64Encoder).decode("utf-8") + ) + + +def encrypt_payload_legacy(secret_key, payload, encode_json=True): """Return a encrypted payload given a key and dictionary of data.""" try: from nacl.encoding import Base64Encoder @@ -38,7 +61,9 @@ def encrypt_payload(secret_key, payload): prepped_key = prepped_key[:keylen] prepped_key = prepped_key.ljust(keylen, b"\0") - payload = json.dumps(payload).encode("utf-8") + if encode_json: + payload = json.dumps(payload) + payload = payload.encode("utf-8") return ( SecretBox(prepped_key).encrypt(payload, encoder=Base64Encoder).decode("utf-8") @@ -56,6 +81,27 @@ def decrypt_payload(secret_key, encrypted_data): import json + prepped_key = unhexlify(secret_key) + + decrypted_data = SecretBox(prepped_key).decrypt( + encrypted_data, encoder=Base64Encoder + ) + decrypted_data = decrypted_data.decode("utf-8") + + return json.loads(decrypted_data) + + +def decrypt_payload_legacy(secret_key, encrypted_data): + """Return a decrypted payload given a key and a string of encrypted data.""" + try: + from nacl.encoding import Base64Encoder + from nacl.secret import SecretBox + except (ImportError, OSError): + pytest.skip("libnacl/libsodium is not installed") + return + + import json + keylen = SecretBox.KEY_SIZE prepped_key = secret_key.encode("utf-8") prepped_key = prepped_key[:keylen] @@ -273,6 +319,181 @@ async def test_webhook_handle_decryption(webhook_client, create_registrations): assert decrypted_data == {"one": "Hello world"} +async def test_webhook_handle_decryption_legacy(webhook_client, create_registrations): + """Test that we can encrypt/decrypt properly.""" + key = create_registrations[0]["secret"] + data = encrypt_payload_legacy(key, RENDER_TEMPLATE["data"]) + + container = {"type": "render_template", "encrypted": True, "encrypted_data": data} + + resp = await webhook_client.post( + "/api/webhook/{}".format(create_registrations[0]["webhook_id"]), json=container + ) + + assert resp.status == HTTPStatus.OK + + webhook_json = await resp.json() + assert "encrypted_data" in webhook_json + + decrypted_data = decrypt_payload_legacy(key, webhook_json["encrypted_data"]) + + assert decrypted_data == {"one": "Hello world"} + + +async def test_webhook_handle_decryption_fail( + webhook_client, create_registrations, caplog +): + """Test that we can encrypt/decrypt properly.""" + key = create_registrations[0]["secret"] + + # Send valid data + data = encrypt_payload(key, RENDER_TEMPLATE["data"]) + container = {"type": "render_template", "encrypted": True, "encrypted_data": data} + resp = await webhook_client.post( + "/api/webhook/{}".format(create_registrations[0]["webhook_id"]), json=container + ) + + assert resp.status == HTTPStatus.OK + webhook_json = await resp.json() + decrypted_data = decrypt_payload(key, webhook_json["encrypted_data"]) + assert decrypted_data == {"one": "Hello world"} + caplog.clear() + + # Send invalid JSON data + data = encrypt_payload(key, "{not_valid", encode_json=False) + container = {"type": "render_template", "encrypted": True, "encrypted_data": data} + resp = await webhook_client.post( + "/api/webhook/{}".format(create_registrations[0]["webhook_id"]), json=container + ) + + assert resp.status == HTTPStatus.OK + webhook_json = await resp.json() + assert decrypt_payload(key, webhook_json["encrypted_data"]) == {} + assert "Ignoring invalid encrypted payload" in caplog.text + caplog.clear() + + # Break the key, and send JSON data + data = encrypt_payload(key[::-1], RENDER_TEMPLATE["data"]) + container = {"type": "render_template", "encrypted": True, "encrypted_data": data} + resp = await webhook_client.post( + "/api/webhook/{}".format(create_registrations[0]["webhook_id"]), json=container + ) + + assert resp.status == HTTPStatus.OK + webhook_json = await resp.json() + assert decrypt_payload(key, webhook_json["encrypted_data"]) == {} + assert "Ignoring encrypted payload because unable to decrypt" in caplog.text + + +async def test_webhook_handle_decryption_legacy_fail( + webhook_client, create_registrations, caplog +): + """Test that we can encrypt/decrypt properly.""" + key = create_registrations[0]["secret"] + + # Send valid data using legacy method + data = encrypt_payload_legacy(key, RENDER_TEMPLATE["data"]) + container = {"type": "render_template", "encrypted": True, "encrypted_data": data} + resp = await webhook_client.post( + "/api/webhook/{}".format(create_registrations[0]["webhook_id"]), json=container + ) + + assert resp.status == HTTPStatus.OK + webhook_json = await resp.json() + decrypted_data = decrypt_payload_legacy(key, webhook_json["encrypted_data"]) + assert decrypted_data == {"one": "Hello world"} + caplog.clear() + + # Send invalid JSON data + data = encrypt_payload_legacy(key, "{not_valid", encode_json=False) + container = {"type": "render_template", "encrypted": True, "encrypted_data": data} + resp = await webhook_client.post( + "/api/webhook/{}".format(create_registrations[0]["webhook_id"]), json=container + ) + + assert resp.status == HTTPStatus.OK + webhook_json = await resp.json() + assert decrypt_payload_legacy(key, webhook_json["encrypted_data"]) == {} + assert "Ignoring invalid encrypted payload" in caplog.text + caplog.clear() + + # Break the key, and send JSON data + data = encrypt_payload_legacy(key[::-1], RENDER_TEMPLATE["data"]) + container = {"type": "render_template", "encrypted": True, "encrypted_data": data} + resp = await webhook_client.post( + "/api/webhook/{}".format(create_registrations[0]["webhook_id"]), json=container + ) + + assert resp.status == HTTPStatus.OK + webhook_json = await resp.json() + assert decrypt_payload_legacy(key, webhook_json["encrypted_data"]) == {} + assert "Ignoring encrypted payload because unable to decrypt" in caplog.text + + +async def test_webhook_handle_decryption_legacy_upgrade( + webhook_client, create_registrations +): + """Test that we can encrypt/decrypt properly.""" + key = create_registrations[0]["secret"] + + # Send using legacy method + data = encrypt_payload_legacy(key, RENDER_TEMPLATE["data"]) + + container = {"type": "render_template", "encrypted": True, "encrypted_data": data} + + resp = await webhook_client.post( + "/api/webhook/{}".format(create_registrations[0]["webhook_id"]), json=container + ) + + assert resp.status == HTTPStatus.OK + + webhook_json = await resp.json() + assert "encrypted_data" in webhook_json + + decrypted_data = decrypt_payload_legacy(key, webhook_json["encrypted_data"]) + + assert decrypted_data == {"one": "Hello world"} + + # Send using new method + data = encrypt_payload(key, RENDER_TEMPLATE["data"]) + + container = {"type": "render_template", "encrypted": True, "encrypted_data": data} + + resp = await webhook_client.post( + "/api/webhook/{}".format(create_registrations[0]["webhook_id"]), json=container + ) + + assert resp.status == HTTPStatus.OK + + webhook_json = await resp.json() + assert "encrypted_data" in webhook_json + + decrypted_data = decrypt_payload(key, webhook_json["encrypted_data"]) + + assert decrypted_data == {"one": "Hello world"} + + # Send using legacy method - no longer possible + data = encrypt_payload_legacy(key, RENDER_TEMPLATE["data"]) + + container = {"type": "render_template", "encrypted": True, "encrypted_data": data} + + resp = await webhook_client.post( + "/api/webhook/{}".format(create_registrations[0]["webhook_id"]), json=container + ) + + assert resp.status == HTTPStatus.OK + + webhook_json = await resp.json() + assert "encrypted_data" in webhook_json + + # The response should be empty, encrypted with the new method + with pytest.raises(Exception): + decrypt_payload_legacy(key, webhook_json["encrypted_data"]) + decrypted_data = decrypt_payload(key, webhook_json["encrypted_data"]) + + assert decrypted_data == {} + + async def test_webhook_requires_encryption(webhook_client, create_registrations): """Test that encrypted registrations only accept encrypted data.""" resp = await webhook_client.post( From c81ccaebd34ec13160f8a786325ae3181ab6d0b7 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Wed, 2 Mar 2022 10:36:23 +0100 Subject: [PATCH 1085/1098] Rfxtrx correct overzealous type checking (#67437) --- homeassistant/components/rfxtrx/config_flow.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/rfxtrx/config_flow.py b/homeassistant/components/rfxtrx/config_flow.py index 7a842ad470ce76..549a5c3ccbf408 100644 --- a/homeassistant/components/rfxtrx/config_flow.py +++ b/homeassistant/components/rfxtrx/config_flow.py @@ -34,7 +34,13 @@ async_get_registry as async_get_entity_registry, ) -from . import DOMAIN, DeviceTuple, get_device_id, get_rfx_object +from . import ( + DOMAIN, + DeviceTuple, + get_device_id, + get_device_tuple_from_identifiers, + get_rfx_object, +) from .binary_sensor import supported as binary_supported from .const import ( CONF_AUTOMATIC_ADD, @@ -59,7 +65,7 @@ class DeviceData(TypedDict): """Dict data representing a device entry.""" - event_code: str + event_code: str | None device_id: DeviceTuple @@ -388,15 +394,15 @@ def _get_device_event_code(self, entry_id): def _get_device_data(self, entry_id) -> DeviceData: """Get event code based on device identifier.""" - event_code: str + event_code: str | None = None entry = self._device_registry.async_get(entry_id) assert entry - device_id = cast(DeviceTuple, next(iter(entry.identifiers))[1:]) + device_id = get_device_tuple_from_identifiers(entry.identifiers) + assert device_id for packet_id, entity_info in self._config_entry.data[CONF_DEVICES].items(): if tuple(entity_info.get(CONF_DEVICE_ID)) == device_id: event_code = cast(str, packet_id) break - assert event_code return DeviceData(event_code=event_code, device_id=device_id) @callback From 94fd7ec028e31095609a56f2a4f100e79a4b9107 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 2 Mar 2022 16:53:13 +0100 Subject: [PATCH 1086/1098] Improve binary sensor group when member is unknown or unavailable (#67468) --- .../components/group/binary_sensor.py | 14 +++++++++-- tests/components/group/test_binary_sensor.py | 24 ++++++++++++++++--- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/group/binary_sensor.py b/homeassistant/components/group/binary_sensor.py index 9c0301d97e676c..de0c3d393cafeb 100644 --- a/homeassistant/components/group/binary_sensor.py +++ b/homeassistant/components/group/binary_sensor.py @@ -17,6 +17,7 @@ CONF_UNIQUE_ID, STATE_ON, STATE_UNAVAILABLE, + STATE_UNKNOWN, ) from homeassistant.core import Event, HomeAssistant, callback import homeassistant.helpers.config_validation as cv @@ -80,7 +81,6 @@ def __init__( self._attr_extra_state_attributes = {ATTR_ENTITY_ID: entity_ids} self._attr_unique_id = unique_id self._device_class = device_class - self._state: str | None = None self.mode = any if mode: self.mode = all @@ -106,13 +106,23 @@ def async_state_changed_listener(event: Event) -> None: def async_update_group_state(self) -> None: """Query all members and determine the binary sensor group state.""" all_states = [self.hass.states.get(x) for x in self._entity_ids] + + # filtered_states are members currently in the state machine filtered_states: list[str] = [x.state for x in all_states if x is not None] + + # Set group as unavailable if all members are unavailable self._attr_available = any( state != STATE_UNAVAILABLE for state in filtered_states ) - if STATE_UNAVAILABLE in filtered_states: + + valid_state = self.mode( + state not in (STATE_UNKNOWN, STATE_UNAVAILABLE) for state in filtered_states + ) + if not valid_state: + # Set as unknown if any / all member is not unknown or unavailable self._attr_is_on = None else: + # Set as ON if any / all member is ON states = list(map(lambda x: x == STATE_ON, filtered_states)) state = self.mode(states) self._attr_is_on = state diff --git a/tests/components/group/test_binary_sensor.py b/tests/components/group/test_binary_sensor.py index 0a85c793aaa334..a0872b11f16a19 100644 --- a/tests/components/group/test_binary_sensor.py +++ b/tests/components/group/test_binary_sensor.py @@ -95,6 +95,16 @@ async def test_state_reporting_all(hass): hass.states.get("binary_sensor.binary_sensor_group").state == STATE_UNAVAILABLE ) + hass.states.async_set("binary_sensor.test1", STATE_ON) + hass.states.async_set("binary_sensor.test2", STATE_UNKNOWN) + await hass.async_block_till_done() + assert hass.states.get("binary_sensor.binary_sensor_group").state == STATE_UNKNOWN + + hass.states.async_set("binary_sensor.test1", STATE_UNKNOWN) + hass.states.async_set("binary_sensor.test2", STATE_UNKNOWN) + await hass.async_block_till_done() + assert hass.states.get("binary_sensor.binary_sensor_group").state == STATE_UNKNOWN + async def test_state_reporting_any(hass): """Test the state reporting.""" @@ -116,11 +126,10 @@ async def test_state_reporting_any(hass): await hass.async_start() await hass.async_block_till_done() - # binary sensors have state off if unavailable hass.states.async_set("binary_sensor.test1", STATE_ON) hass.states.async_set("binary_sensor.test2", STATE_UNAVAILABLE) await hass.async_block_till_done() - assert hass.states.get("binary_sensor.binary_sensor_group").state == STATE_UNKNOWN + assert hass.states.get("binary_sensor.binary_sensor_group").state == STATE_ON hass.states.async_set("binary_sensor.test1", STATE_ON) hass.states.async_set("binary_sensor.test2", STATE_OFF) @@ -137,7 +146,6 @@ async def test_state_reporting_any(hass): await hass.async_block_till_done() assert hass.states.get("binary_sensor.binary_sensor_group").state == STATE_ON - # binary sensors have state off if unavailable hass.states.async_set("binary_sensor.test1", STATE_UNAVAILABLE) hass.states.async_set("binary_sensor.test2", STATE_UNAVAILABLE) await hass.async_block_till_done() @@ -149,3 +157,13 @@ async def test_state_reporting_any(hass): entry = entity_registry.async_get("binary_sensor.binary_sensor_group") assert entry assert entry.unique_id == "unique_identifier" + + hass.states.async_set("binary_sensor.test1", STATE_ON) + hass.states.async_set("binary_sensor.test2", STATE_UNKNOWN) + await hass.async_block_till_done() + assert hass.states.get("binary_sensor.binary_sensor_group").state == STATE_ON + + hass.states.async_set("binary_sensor.test1", STATE_UNKNOWN) + hass.states.async_set("binary_sensor.test2", STATE_UNKNOWN) + await hass.async_block_till_done() + assert hass.states.get("binary_sensor.binary_sensor_group").state == STATE_UNKNOWN From 274e4d5558cb9873b597049133c334777d239d2e Mon Sep 17 00:00:00 2001 From: Jc2k Date: Wed, 2 Mar 2022 12:58:39 +0000 Subject: [PATCH 1087/1098] Bump to aiohomekit 0.7.15 (#67470) --- homeassistant/components/homekit_controller/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index 44de321db4ae32..dfd45991b3ffb8 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit Controller", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homekit_controller", - "requirements": ["aiohomekit==0.7.14"], + "requirements": ["aiohomekit==0.7.15"], "zeroconf": ["_hap._tcp.local."], "after_dependencies": ["zeroconf"], "codeowners": ["@Jc2k", "@bdraco"], diff --git a/requirements_all.txt b/requirements_all.txt index 4d6dcefb10ff91..91afd8a8e557b6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -184,7 +184,7 @@ aioguardian==2021.11.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==0.7.14 +aiohomekit==0.7.15 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c7828110fc26ec..0d228b65601834 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -134,7 +134,7 @@ aioguardian==2021.11.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==0.7.14 +aiohomekit==0.7.15 # homeassistant.components.emulated_hue # homeassistant.components.http From 4668720f0265339a6ca026dde52f6471f0930fa6 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Wed, 2 Mar 2022 14:00:48 +0000 Subject: [PATCH 1088/1098] Remove Ecobee homekit vendor extensions that just don't work (#67474) --- .../components/homekit_controller/number.py | 36 --------- .../specific_devices/test_ecobee3.py | 80 ------------------- 2 files changed, 116 deletions(-) diff --git a/homeassistant/components/homekit_controller/number.py b/homeassistant/components/homekit_controller/number.py index 5fcb5027640992..b994bc80f4a21d 100644 --- a/homeassistant/components/homekit_controller/number.py +++ b/homeassistant/components/homekit_controller/number.py @@ -48,42 +48,6 @@ icon="mdi:volume-high", entity_category=EntityCategory.CONFIG, ), - CharacteristicsTypes.VENDOR_ECOBEE_HOME_TARGET_COOL: NumberEntityDescription( - key=CharacteristicsTypes.VENDOR_ECOBEE_HOME_TARGET_COOL, - name="Home Cool Target", - icon="mdi:thermometer-minus", - entity_category=EntityCategory.CONFIG, - ), - CharacteristicsTypes.VENDOR_ECOBEE_HOME_TARGET_HEAT: NumberEntityDescription( - key=CharacteristicsTypes.VENDOR_ECOBEE_HOME_TARGET_HEAT, - name="Home Heat Target", - icon="mdi:thermometer-plus", - entity_category=EntityCategory.CONFIG, - ), - CharacteristicsTypes.VENDOR_ECOBEE_SLEEP_TARGET_COOL: NumberEntityDescription( - key=CharacteristicsTypes.VENDOR_ECOBEE_SLEEP_TARGET_COOL, - name="Sleep Cool Target", - icon="mdi:thermometer-minus", - entity_category=EntityCategory.CONFIG, - ), - CharacteristicsTypes.VENDOR_ECOBEE_SLEEP_TARGET_HEAT: NumberEntityDescription( - key=CharacteristicsTypes.VENDOR_ECOBEE_SLEEP_TARGET_HEAT, - name="Sleep Heat Target", - icon="mdi:thermometer-plus", - entity_category=EntityCategory.CONFIG, - ), - CharacteristicsTypes.VENDOR_ECOBEE_AWAY_TARGET_COOL: NumberEntityDescription( - key=CharacteristicsTypes.VENDOR_ECOBEE_AWAY_TARGET_COOL, - name="Away Cool Target", - icon="mdi:thermometer-minus", - entity_category=EntityCategory.CONFIG, - ), - CharacteristicsTypes.VENDOR_ECOBEE_AWAY_TARGET_HEAT: NumberEntityDescription( - key=CharacteristicsTypes.VENDOR_ECOBEE_AWAY_TARGET_HEAT, - name="Away Heat Target", - icon="mdi:thermometer-plus", - entity_category=EntityCategory.CONFIG, - ), } diff --git a/tests/components/homekit_controller/specific_devices/test_ecobee3.py b/tests/components/homekit_controller/specific_devices/test_ecobee3.py index 83378650b97b67..3c47195b44221e 100644 --- a/tests/components/homekit_controller/specific_devices/test_ecobee3.py +++ b/tests/components/homekit_controller/specific_devices/test_ecobee3.py @@ -14,12 +14,10 @@ SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, ) -from homeassistant.components.number import NumberMode from homeassistant.components.sensor import SensorStateClass from homeassistant.config_entries import ConfigEntryState from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers import entity_registry as er -from homeassistant.helpers.entity import EntityCategory from tests.components.homekit_controller.common import ( HUB_TEST_ACCESSORY_ID, @@ -123,84 +121,6 @@ async def test_ecobee3_setup(hass): }, state="heat", ), - EntityTestInfo( - entity_id="number.homew_home_cool_target", - friendly_name="HomeW Home Cool Target", - unique_id="homekit-123456789012-aid:1-sid:16-cid:35", - entity_category=EntityCategory.CONFIG, - capabilities={ - "max": 33.3, - "min": 18.3, - "mode": NumberMode.AUTO, - "step": 0.1, - }, - state="24.4", - ), - EntityTestInfo( - entity_id="number.homew_home_heat_target", - friendly_name="HomeW Home Heat Target", - unique_id="homekit-123456789012-aid:1-sid:16-cid:34", - entity_category=EntityCategory.CONFIG, - capabilities={ - "max": 26.1, - "min": 7.2, - "mode": NumberMode.AUTO, - "step": 0.1, - }, - state="22.2", - ), - EntityTestInfo( - entity_id="number.homew_sleep_cool_target", - friendly_name="HomeW Sleep Cool Target", - unique_id="homekit-123456789012-aid:1-sid:16-cid:37", - entity_category=EntityCategory.CONFIG, - capabilities={ - "max": 33.3, - "min": 18.3, - "mode": NumberMode.AUTO, - "step": 0.1, - }, - state="27.8", - ), - EntityTestInfo( - entity_id="number.homew_sleep_heat_target", - friendly_name="HomeW Sleep Heat Target", - unique_id="homekit-123456789012-aid:1-sid:16-cid:36", - entity_category=EntityCategory.CONFIG, - capabilities={ - "max": 26.1, - "min": 7.2, - "mode": NumberMode.AUTO, - "step": 0.1, - }, - state="17.8", - ), - EntityTestInfo( - entity_id="number.homew_away_cool_target", - friendly_name="HomeW Away Cool Target", - unique_id="homekit-123456789012-aid:1-sid:16-cid:39", - entity_category=EntityCategory.CONFIG, - capabilities={ - "max": 33.3, - "min": 18.3, - "mode": NumberMode.AUTO, - "step": 0.1, - }, - state="26.7", - ), - EntityTestInfo( - entity_id="number.homew_away_heat_target", - friendly_name="HomeW Away Heat Target", - unique_id="homekit-123456789012-aid:1-sid:16-cid:38", - entity_category=EntityCategory.CONFIG, - capabilities={ - "max": 26.1, - "min": 7.2, - "mode": NumberMode.AUTO, - "step": 0.1, - }, - state="18.9", - ), EntityTestInfo( entity_id="sensor.homew_current_temperature", friendly_name="HomeW Current Temperature", From 9aba0ba990be6c1a8bbc609f8fe8b98848241202 Mon Sep 17 00:00:00 2001 From: Michael Chisholm Date: Thu, 3 Mar 2022 02:54:47 +1100 Subject: [PATCH 1089/1098] Sort DMS results using only criteria supported by the device (#67475) --- homeassistant/components/dlna_dms/dms.py | 23 +++++- .../dlna_dms/test_dms_device_source.py | 77 ++++++++++++++++++- 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/dlna_dms/dms.py b/homeassistant/components/dlna_dms/dms.py index 1166d1db6db09b..d7ee08f85f8034 100644 --- a/homeassistant/components/dlna_dms/dms.py +++ b/homeassistant/components/dlna_dms/dms.py @@ -542,7 +542,7 @@ async def async_browse_object(self, object_id: str) -> BrowseMediaSource: children = await self._device.async_browse_direct_children( object_id, metadata_filter=DLNA_BROWSE_FILTER, - sort_criteria=DLNA_SORT_CRITERIA, + sort_criteria=self._sort_criteria, ) return self._didl_to_media_source(base_object, children) @@ -675,6 +675,27 @@ def _make_identifier(self, action: Action, object_id: str) -> str: """Make an identifier for BrowseMediaSource.""" return f"{self.source_id}/{action}{object_id}" + @property # type: ignore + @functools.cache + def _sort_criteria(self) -> list[str]: + """Return criteria to be used for sorting results. + + The device must be connected before reading this property. + """ + assert self._device + + if self._device.sort_capabilities == ["*"]: + return DLNA_SORT_CRITERIA + + # Filter criteria based on what the device supports. Strings in + # DLNA_SORT_CRITERIA are prefixed with a sign, while those in + # the device's sort_capabilities are not. + return [ + criterion + for criterion in DLNA_SORT_CRITERIA + if criterion[1:] in self._device.sort_capabilities + ] + class Action(StrEnum): """Actions that can be specified in a DMS media-source identifier.""" diff --git a/tests/components/dlna_dms/test_dms_device_source.py b/tests/components/dlna_dms/test_dms_device_source.py index 4ee9cce91ba90d..d6fcdb267d6c50 100644 --- a/tests/components/dlna_dms/test_dms_device_source.py +++ b/tests/components/dlna_dms/test_dms_device_source.py @@ -8,7 +8,7 @@ from didl_lite import didl_lite import pytest -from homeassistant.components.dlna_dms.const import DOMAIN +from homeassistant.components.dlna_dms.const import DLNA_SORT_CRITERIA, DOMAIN from homeassistant.components.dlna_dms.dms import ( ActionError, DeviceConnectionError, @@ -686,6 +686,81 @@ async def test_browse_media_object( assert not child.children +async def test_browse_object_sort_anything( + device_source_mock: DmsDeviceSource, dms_device_mock: Mock +) -> None: + """Test sort criteria for children where device allows anything.""" + dms_device_mock.sort_capabilities = ["*"] + + object_id = "0" + dms_device_mock.async_browse_metadata.return_value = didl_lite.Container( + id="0", restricted="false", title="root" + ) + dms_device_mock.async_browse_direct_children.return_value = DmsDevice.BrowseResult( + [], 0, 0, 0 + ) + await device_source_mock.async_browse_object("0") + + # Sort criteria should be dlna_dms's default + dms_device_mock.async_browse_direct_children.assert_awaited_once_with( + object_id, metadata_filter=ANY, sort_criteria=DLNA_SORT_CRITERIA + ) + + +async def test_browse_object_sort_superset( + device_source_mock: DmsDeviceSource, dms_device_mock: Mock +) -> None: + """Test sorting where device allows superset of integration's criteria.""" + dms_device_mock.sort_capabilities = [ + "dc:title", + "upnp:originalTrackNumber", + "upnp:class", + "upnp:artist", + "dc:creator", + "upnp:genre", + ] + + object_id = "0" + dms_device_mock.async_browse_metadata.return_value = didl_lite.Container( + id="0", restricted="false", title="root" + ) + dms_device_mock.async_browse_direct_children.return_value = DmsDevice.BrowseResult( + [], 0, 0, 0 + ) + await device_source_mock.async_browse_object("0") + + # Sort criteria should be dlna_dms's default + dms_device_mock.async_browse_direct_children.assert_awaited_once_with( + object_id, metadata_filter=ANY, sort_criteria=DLNA_SORT_CRITERIA + ) + + +async def test_browse_object_sort_subset( + device_source_mock: DmsDeviceSource, dms_device_mock: Mock +) -> None: + """Test sorting where device allows subset of integration's criteria.""" + dms_device_mock.sort_capabilities = [ + "dc:title", + "upnp:class", + ] + + object_id = "0" + dms_device_mock.async_browse_metadata.return_value = didl_lite.Container( + id="0", restricted="false", title="root" + ) + dms_device_mock.async_browse_direct_children.return_value = DmsDevice.BrowseResult( + [], 0, 0, 0 + ) + await device_source_mock.async_browse_object("0") + + # Sort criteria should be reduced to only those allowed, + # and in the order specified by DLNA_SORT_CRITERIA + expected_criteria = ["+upnp:class", "+dc:title"] + dms_device_mock.async_browse_direct_children.assert_awaited_once_with( + object_id, metadata_filter=ANY, sort_criteria=expected_criteria + ) + + async def test_browse_media_path( device_source_mock: DmsDeviceSource, dms_device_mock: Mock ) -> None: From 092b9730679051c35920242cdd5fd1f21c8abeaf Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Wed, 2 Mar 2022 14:30:54 +0200 Subject: [PATCH 1090/1098] Bump aioshelly to 1.0.11 (#67476) --- homeassistant/components/shelly/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json index 1e9f34608eb485..1d4d47748be860 100644 --- a/homeassistant/components/shelly/manifest.json +++ b/homeassistant/components/shelly/manifest.json @@ -3,7 +3,7 @@ "name": "Shelly", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/shelly", - "requirements": ["aioshelly==1.0.10"], + "requirements": ["aioshelly==1.0.11"], "zeroconf": [ { "type": "_http._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index 91afd8a8e557b6..9714786a873a02 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -257,7 +257,7 @@ aioridwell==2021.12.2 aiosenseme==0.6.1 # homeassistant.components.shelly -aioshelly==1.0.10 +aioshelly==1.0.11 # homeassistant.components.steamist aiosteamist==0.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0d228b65601834..8f53952071330e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -192,7 +192,7 @@ aioridwell==2021.12.2 aiosenseme==0.6.1 # homeassistant.components.shelly -aioshelly==1.0.10 +aioshelly==1.0.11 # homeassistant.components.steamist aiosteamist==0.3.1 From 288270ac0879a2ca745f7c167489714c7284b308 Mon Sep 17 00:00:00 2001 From: cnico Date: Wed, 2 Mar 2022 13:00:33 +0100 Subject: [PATCH 1091/1098] Address late review of flipr (#67477) --- homeassistant/components/flipr/__init__.py | 3 +-- tests/components/flipr/test_binary_sensor.py | 3 ++- tests/components/flipr/test_sensor.py | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/flipr/__init__.py b/homeassistant/components/flipr/__init__.py index 1a9f3dc03140b8..3281410ec2da3a 100644 --- a/homeassistant/components/flipr/__init__.py +++ b/homeassistant/components/flipr/__init__.py @@ -75,8 +75,7 @@ async def _async_update_data(self): self.client.get_pool_measure_latest, self.flipr_id ) except (FliprError) as error: - _LOGGER.error(error) - raise UpdateFailed from error + raise UpdateFailed(error) from error return data diff --git a/tests/components/flipr/test_binary_sensor.py b/tests/components/flipr/test_binary_sensor.py index 48f9361723c4f9..fc24ddee3405d2 100644 --- a/tests/components/flipr/test_binary_sensor.py +++ b/tests/components/flipr/test_binary_sensor.py @@ -5,6 +5,7 @@ from homeassistant.components.flipr.const import CONF_FLIPR_ID, DOMAIN from homeassistant.const import CONF_EMAIL, CONF_PASSWORD from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as entity_reg from homeassistant.util import dt as dt_util from tests.common import MockConfigEntry @@ -36,7 +37,7 @@ async def test_sensors(hass: HomeAssistant) -> None: entry.add_to_hass(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = entity_reg.async_get(hass) with patch( "flipr_api.FliprAPIRestClient.get_pool_measure_latest", diff --git a/tests/components/flipr/test_sensor.py b/tests/components/flipr/test_sensor.py index 45816801472153..c5ab3dc1541a04 100644 --- a/tests/components/flipr/test_sensor.py +++ b/tests/components/flipr/test_sensor.py @@ -13,6 +13,7 @@ TEMP_CELSIUS, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as entity_reg from homeassistant.util import dt as dt_util from tests.common import MockConfigEntry @@ -44,7 +45,7 @@ async def test_sensors(hass: HomeAssistant) -> None: entry.add_to_hass(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = entity_reg.async_get(hass) with patch( "flipr_api.FliprAPIRestClient.get_pool_measure_latest", @@ -102,7 +103,7 @@ async def test_error_flipr_api_sensors(hass: HomeAssistant) -> None: entry.add_to_hass(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = entity_reg.async_get(hass) with patch( "flipr_api.FliprAPIRestClient.get_pool_measure_latest", From da4f4f641d4fc9dc3db2a46943925a2d5e3bfd91 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 2 Mar 2022 08:22:34 -0800 Subject: [PATCH 1092/1098] Add guard radio browser media source (#67486) --- .../components/radio_browser/media_source.py | 74 +++++++++++-------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/radio_browser/media_source.py b/homeassistant/components/radio_browser/media_source.py index 8240691b2477bb..6ba1b7b2b9aef9 100644 --- a/homeassistant/components/radio_browser/media_source.py +++ b/homeassistant/components/radio_browser/media_source.py @@ -12,6 +12,7 @@ MEDIA_TYPE_MUSIC, ) from homeassistant.components.media_player.errors import BrowseError +from homeassistant.components.media_source.error import Unresolvable from homeassistant.components.media_source.models import ( BrowseMediaSource, MediaSource, @@ -35,9 +36,8 @@ async def async_get_media_source(hass: HomeAssistant) -> RadioMediaSource: """Set up Radio Browser media source.""" # Radio browser support only a single config entry entry = hass.config_entries.async_entries(DOMAIN)[0] - radios = hass.data[DOMAIN] - return RadioMediaSource(hass, radios, entry) + return RadioMediaSource(hass, entry) class RadioMediaSource(MediaSource): @@ -45,26 +45,33 @@ class RadioMediaSource(MediaSource): name = "Radio Browser" - def __init__( - self, hass: HomeAssistant, radios: RadioBrowser, entry: ConfigEntry - ) -> None: + def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: """Initialize CameraMediaSource.""" super().__init__(DOMAIN) self.hass = hass self.entry = entry - self.radios = radios + + @property + def radios(self) -> RadioBrowser | None: + """Return the radio browser.""" + return self.hass.data.get(DOMAIN) async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia: """Resolve selected Radio station to a streaming URL.""" - station = await self.radios.station(uuid=item.identifier) + radios = self.radios + + if radios is None: + raise Unresolvable("Radio Browser not initialized") + + station = await radios.station(uuid=item.identifier) if not station: - raise BrowseError("Radio station is no longer available") + raise Unresolvable("Radio station is no longer available") if not (mime_type := self._async_get_station_mime_type(station)): - raise BrowseError("Could not determine stream type of radio station") + raise Unresolvable("Could not determine stream type of radio station") # Register "click" with Radio Browser - await self.radios.station_click(uuid=station.uuid) + await radios.station_click(uuid=station.uuid) return PlayMedia(station.url, mime_type) @@ -73,6 +80,11 @@ async def async_browse_media( item: MediaSourceItem, ) -> BrowseMediaSource: """Return media.""" + radios = self.radios + + if radios is None: + raise BrowseError("Radio Browser not initialized") + return BrowseMediaSource( domain=DOMAIN, identifier=None, @@ -83,10 +95,10 @@ async def async_browse_media( can_expand=True, children_media_class=MEDIA_CLASS_DIRECTORY, children=[ - *await self._async_build_popular(item), - *await self._async_build_by_tag(item), - *await self._async_build_by_language(item), - *await self._async_build_by_country(item), + *await self._async_build_popular(radios, item), + *await self._async_build_by_tag(radios, item), + *await self._async_build_by_language(radios, item), + *await self._async_build_by_country(radios, item), ], ) @@ -100,7 +112,9 @@ def _async_get_station_mime_type(station: Station) -> str | None: return mime_type @callback - def _async_build_stations(self, stations: list[Station]) -> list[BrowseMediaSource]: + def _async_build_stations( + self, radios: RadioBrowser, stations: list[Station] + ) -> list[BrowseMediaSource]: """Build list of media sources from radio stations.""" items: list[BrowseMediaSource] = [] @@ -126,23 +140,23 @@ def _async_build_stations(self, stations: list[Station]) -> list[BrowseMediaSour return items async def _async_build_by_country( - self, item: MediaSourceItem + self, radios: RadioBrowser, item: MediaSourceItem ) -> list[BrowseMediaSource]: """Handle browsing radio stations by country.""" category, _, country_code = (item.identifier or "").partition("/") if country_code: - stations = await self.radios.stations( + stations = await radios.stations( filter_by=FilterBy.COUNTRY_CODE_EXACT, filter_term=country_code, hide_broken=True, order=Order.NAME, reverse=False, ) - return self._async_build_stations(stations) + return self._async_build_stations(radios, stations) # We show country in the root additionally, when there is no item if not item.identifier or category == "country": - countries = await self.radios.countries(order=Order.NAME) + countries = await radios.countries(order=Order.NAME) return [ BrowseMediaSource( domain=DOMAIN, @@ -160,22 +174,22 @@ async def _async_build_by_country( return [] async def _async_build_by_language( - self, item: MediaSourceItem + self, radios: RadioBrowser, item: MediaSourceItem ) -> list[BrowseMediaSource]: """Handle browsing radio stations by language.""" category, _, language = (item.identifier or "").partition("/") if category == "language" and language: - stations = await self.radios.stations( + stations = await radios.stations( filter_by=FilterBy.LANGUAGE_EXACT, filter_term=language, hide_broken=True, order=Order.NAME, reverse=False, ) - return self._async_build_stations(stations) + return self._async_build_stations(radios, stations) if category == "language": - languages = await self.radios.languages(order=Order.NAME, hide_broken=True) + languages = await radios.languages(order=Order.NAME, hide_broken=True) return [ BrowseMediaSource( domain=DOMAIN, @@ -206,17 +220,17 @@ async def _async_build_by_language( return [] async def _async_build_popular( - self, item: MediaSourceItem + self, radios: RadioBrowser, item: MediaSourceItem ) -> list[BrowseMediaSource]: """Handle browsing popular radio stations.""" if item.identifier == "popular": - stations = await self.radios.stations( + stations = await radios.stations( hide_broken=True, limit=250, order=Order.CLICK_COUNT, reverse=True, ) - return self._async_build_stations(stations) + return self._async_build_stations(radios, stations) if not item.identifier: return [ @@ -234,22 +248,22 @@ async def _async_build_popular( return [] async def _async_build_by_tag( - self, item: MediaSourceItem + self, radios: RadioBrowser, item: MediaSourceItem ) -> list[BrowseMediaSource]: """Handle browsing radio stations by tags.""" category, _, tag = (item.identifier or "").partition("/") if category == "tag" and tag: - stations = await self.radios.stations( + stations = await radios.stations( filter_by=FilterBy.TAG_EXACT, filter_term=tag, hide_broken=True, order=Order.NAME, reverse=False, ) - return self._async_build_stations(stations) + return self._async_build_stations(radios, stations) if category == "tag": - tags = await self.radios.tags( + tags = await radios.tags( hide_broken=True, limit=100, order=Order.STATION_COUNT, From ddf7efd93759fb70426dc08bf3e111ee340ee61a Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 2 Mar 2022 18:18:58 +0100 Subject: [PATCH 1093/1098] Bumped version to 2022.3.0 --- homeassistant/const.py | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 51af5c06d22a82..c0ff111fc89936 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 3 -PATCH_VERSION: Final = "0b6" +PATCH_VERSION: Final = "0" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/setup.cfg b/setup.cfg index 4d686e63c5b518..d72829b574e5af 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = homeassistant -version = 2022.3.0b6 +version = 2022.3.0 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 From 9db56a8119c82cda49ceabbeb63f93a7e335e148 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Sun, 27 Feb 2022 00:14:12 +0100 Subject: [PATCH 1094/1098] Don't trigger device removal for non rfxtrx devices (#67315) --- homeassistant/components/rfxtrx/__init__.py | 33 ++++++++++++++++----- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/rfxtrx/__init__.py b/homeassistant/components/rfxtrx/__init__.py index 2dcfe639a64159..8dda4d32644f01 100644 --- a/homeassistant/components/rfxtrx/__init__.py +++ b/homeassistant/components/rfxtrx/__init__.py @@ -6,7 +6,7 @@ from collections.abc import Callable import copy import logging -from typing import NamedTuple +from typing import NamedTuple, cast import RFXtrx as rfxtrxmod import async_timeout @@ -229,11 +229,7 @@ def _add_device(event, device_id): devices[device_id] = config @callback - def _remove_device(event: Event): - if event.data["action"] != "remove": - return - device_entry = device_registry.deleted_devices[event.data["device_id"]] - device_id = next(iter(device_entry.identifiers))[1:] + def _remove_device(device_id: DeviceTuple): data = { **entry.data, CONF_DEVICES: { @@ -245,8 +241,19 @@ def _remove_device(event: Event): hass.config_entries.async_update_entry(entry=entry, data=data) devices.pop(device_id) + @callback + def _updated_device(event: Event): + if event.data["action"] != "remove": + return + device_entry = device_registry.deleted_devices[event.data["device_id"]] + if entry.entry_id not in device_entry.config_entries: + return + device_id = get_device_tuple_from_identifiers(device_entry.identifiers) + if device_id: + _remove_device(device_id) + entry.async_on_unload( - hass.bus.async_listen(EVENT_DEVICE_REGISTRY_UPDATED, _remove_device) + hass.bus.async_listen(EVENT_DEVICE_REGISTRY_UPDATED, _updated_device) ) def _shutdown_rfxtrx(event): @@ -413,6 +420,18 @@ def get_device_id( return DeviceTuple(f"{device.packettype:x}", f"{device.subtype:x}", id_string) +def get_device_tuple_from_identifiers( + identifiers: set[tuple[str, str]] +) -> DeviceTuple | None: + """Calculate the device tuple from a device entry.""" + identifier = next((x for x in identifiers if x[0] == DOMAIN), None) + if not identifier: + return None + # work around legacy identifier, being a multi tuple value + identifier2 = cast(tuple[str, str, str, str], identifier) + return DeviceTuple(identifier2[1], identifier2[2], identifier2[3]) + + async def async_remove_config_entry_device( hass: HomeAssistant, config_entry: ConfigEntry, device_entry: DeviceEntry ) -> bool: From b9f44eec0a006adde40fb773799858ca72e0a9a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Mar 2022 13:56:48 +0100 Subject: [PATCH 1095/1098] Bump docker/login-action from 1.13.0 to 1.14.0 (#67416) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/builder.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index ce545684da5367..a62ef8fb2d7515 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -122,13 +122,13 @@ jobs: echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE - name: Login to DockerHub - uses: docker/login-action@v1.13.0 + uses: docker/login-action@v1.14.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@v1.13.0 + uses: docker/login-action@v1.14.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -187,13 +187,13 @@ jobs: fi - name: Login to DockerHub - uses: docker/login-action@v1.13.0 + uses: docker/login-action@v1.14.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@v1.13.0 + uses: docker/login-action@v1.14.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -252,13 +252,13 @@ jobs: uses: actions/checkout@v2.4.0 - name: Login to DockerHub - uses: docker/login-action@v1.13.0 + uses: docker/login-action@v1.14.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@v1.13.0 + uses: docker/login-action@v1.14.0 with: registry: ghcr.io username: ${{ github.repository_owner }} From be19a2e2ab439ce38766a1a0a875be3f7a9d835d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Mar 2022 08:48:40 +0100 Subject: [PATCH 1096/1098] Bump docker/login-action from 1.14.0 to 1.14.1 (#67462) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/builder.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index a62ef8fb2d7515..26811dae73e8b8 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -122,13 +122,13 @@ jobs: echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE - name: Login to DockerHub - uses: docker/login-action@v1.14.0 + uses: docker/login-action@v1.14.1 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@v1.14.0 + uses: docker/login-action@v1.14.1 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -187,13 +187,13 @@ jobs: fi - name: Login to DockerHub - uses: docker/login-action@v1.14.0 + uses: docker/login-action@v1.14.1 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@v1.14.0 + uses: docker/login-action@v1.14.1 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -252,13 +252,13 @@ jobs: uses: actions/checkout@v2.4.0 - name: Login to DockerHub - uses: docker/login-action@v1.14.0 + uses: docker/login-action@v1.14.1 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@v1.14.0 + uses: docker/login-action@v1.14.1 with: registry: ghcr.io username: ${{ github.repository_owner }} From 0349d7d09d93bed2641decf60ee8a44b1c20e1d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 2 Mar 2022 13:55:05 +0100 Subject: [PATCH 1097/1098] Split meta image creation (#67480) --- .github/workflows/builder.yml | 116 ++++++++++++++++++---------------- 1 file changed, 60 insertions(+), 56 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 26811dae73e8b8..a857ef77edbb1b 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -243,21 +243,28 @@ jobs: channel: beta publish_container: - name: Publish meta container + name: Publish meta container for ${{ matrix.registry }} if: github.repository_owner == 'home-assistant' needs: ["init", "build_base"] runs-on: ubuntu-latest + strategy: + matrix: + registry: + - "ghcr.io/home-assistant" + - "homeassistant" steps: - name: Checkout the repository uses: actions/checkout@v2.4.0 - name: Login to DockerHub + if: matrix.registry == 'homeassistant' uses: docker/login-action@v1.14.1 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry + if: matrix.registry == 'ghcr.io/home-assistant' uses: docker/login-action@v1.14.1 with: registry: ghcr.io @@ -273,38 +280,37 @@ jobs: export DOCKER_CLI_EXPERIMENTAL=enabled function create_manifest() { - local docker_reg=${1} - local tag_l=${2} - local tag_r=${3} - - docker manifest create "${docker_reg}/home-assistant:${tag_l}" \ - "${docker_reg}/amd64-homeassistant:${tag_r}" \ - "${docker_reg}/i386-homeassistant:${tag_r}" \ - "${docker_reg}/armhf-homeassistant:${tag_r}" \ - "${docker_reg}/armv7-homeassistant:${tag_r}" \ - "${docker_reg}/aarch64-homeassistant:${tag_r}" - - docker manifest annotate "${docker_reg}/home-assistant:${tag_l}" \ - "${docker_reg}/amd64-homeassistant:${tag_r}" \ + local tag_l=${1} + local tag_r=${2} + + docker manifest create "${{ matrix.registry }}/home-assistant:${tag_l}" \ + "${{ matrix.registry }}/amd64-homeassistant:${tag_r}" \ + "${{ matrix.registry }}/i386-homeassistant:${tag_r}" \ + "${{ matrix.registry }}/armhf-homeassistant:${tag_r}" \ + "${{ matrix.registry }}/armv7-homeassistant:${tag_r}" \ + "${{ matrix.registry }}/aarch64-homeassistant:${tag_r}" + + docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \ + "${{ matrix.registry }}/amd64-homeassistant:${tag_r}" \ --os linux --arch amd64 - docker manifest annotate "${docker_reg}/home-assistant:${tag_l}" \ - "${docker_reg}/i386-homeassistant:${tag_r}" \ + docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \ + "${{ matrix.registry }}/i386-homeassistant:${tag_r}" \ --os linux --arch 386 - docker manifest annotate "${docker_reg}/home-assistant:${tag_l}" \ - "${docker_reg}/armhf-homeassistant:${tag_r}" \ + docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \ + "${{ matrix.registry }}/armhf-homeassistant:${tag_r}" \ --os linux --arch arm --variant=v6 - docker manifest annotate "${docker_reg}/home-assistant:${tag_l}" \ - "${docker_reg}/armv7-homeassistant:${tag_r}" \ + docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \ + "${{ matrix.registry }}/armv7-homeassistant:${tag_r}" \ --os linux --arch arm --variant=v7 - docker manifest annotate "${docker_reg}/home-assistant:${tag_l}" \ - "${docker_reg}/aarch64-homeassistant:${tag_r}" \ + docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \ + "${{ matrix.registry }}/aarch64-homeassistant:${tag_r}" \ --os linux --arch arm64 --variant=v8 - docker manifest push --purge "${docker_reg}/home-assistant:${tag_l}" + docker manifest push --purge "${{ matrix.registry }}/home-assistant:${tag_l}" } function validate_image() { @@ -315,36 +321,34 @@ jobs: fi } - for docker_reg in "homeassistant" "ghcr.io/home-assistant"; do - docker pull "${docker_reg}/amd64-homeassistant:${{ needs.init.outputs.version }}" - docker pull "${docker_reg}/i386-homeassistant:${{ needs.init.outputs.version }}" - docker pull "${docker_reg}/armhf-homeassistant:${{ needs.init.outputs.version }}" - docker pull "${docker_reg}/armv7-homeassistant:${{ needs.init.outputs.version }}" - docker pull "${docker_reg}/aarch64-homeassistant:${{ needs.init.outputs.version }}" - - validate_image "${docker_reg}/amd64-homeassistant:${{ needs.init.outputs.version }}" - validate_image "${docker_reg}/i386-homeassistant:${{ needs.init.outputs.version }}" - validate_image "${docker_reg}/armhf-homeassistant:${{ needs.init.outputs.version }}" - validate_image "${docker_reg}/armv7-homeassistant:${{ needs.init.outputs.version }}" - validate_image "${docker_reg}/aarch64-homeassistant:${{ needs.init.outputs.version }}" - - # Create version tag - create_manifest "${docker_reg}" "${{ needs.init.outputs.version }}" "${{ needs.init.outputs.version }}" - - # Create general tags - if [[ "${{ needs.init.outputs.version }}" =~ d ]]; then - create_manifest "${docker_reg}" "dev" "${{ needs.init.outputs.version }}" - elif [[ "${{ needs.init.outputs.version }}" =~ b ]]; then - create_manifest "${docker_reg}" "beta" "${{ needs.init.outputs.version }}" - create_manifest "${docker_reg}" "rc" "${{ needs.init.outputs.version }}" - else - create_manifest "${docker_reg}" "stable" "${{ needs.init.outputs.version }}" - create_manifest "${docker_reg}" "latest" "${{ needs.init.outputs.version }}" - create_manifest "${docker_reg}" "beta" "${{ needs.init.outputs.version }}" - create_manifest "${docker_reg}" "rc" "${{ needs.init.outputs.version }}" - - # Create series version tag (e.g. 2021.6) - v="${{ needs.init.outputs.version }}" - create_manifest "${docker_reg}" "${v%.*}" "${{ needs.init.outputs.version }}" - fi - done + docker pull "${{ matrix.registry }}/amd64-homeassistant:${{ needs.init.outputs.version }}" + docker pull "${{ matrix.registry }}/i386-homeassistant:${{ needs.init.outputs.version }}" + docker pull "${{ matrix.registry }}/armhf-homeassistant:${{ needs.init.outputs.version }}" + docker pull "${{ matrix.registry }}/armv7-homeassistant:${{ needs.init.outputs.version }}" + docker pull "${{ matrix.registry }}/aarch64-homeassistant:${{ needs.init.outputs.version }}" + + validate_image "${{ matrix.registry }}/amd64-homeassistant:${{ needs.init.outputs.version }}" + validate_image "${{ matrix.registry }}/i386-homeassistant:${{ needs.init.outputs.version }}" + validate_image "${{ matrix.registry }}/armhf-homeassistant:${{ needs.init.outputs.version }}" + validate_image "${{ matrix.registry }}/armv7-homeassistant:${{ needs.init.outputs.version }}" + validate_image "${{ matrix.registry }}/aarch64-homeassistant:${{ needs.init.outputs.version }}" + + # Create version tag + create_manifest "${{ needs.init.outputs.version }}" "${{ needs.init.outputs.version }}" + + # Create general tags + if [[ "${{ needs.init.outputs.version }}" =~ d ]]; then + create_manifest"dev" "${{ needs.init.outputs.version }}" + elif [[ "${{ needs.init.outputs.version }}" =~ b ]]; then + create_manifest "beta" "${{ needs.init.outputs.version }}" + create_manifest "rc" "${{ needs.init.outputs.version }}" + else + create_manifest "stable" "${{ needs.init.outputs.version }}" + create_manifest "latest" "${{ needs.init.outputs.version }}" + create_manifest "beta" "${{ needs.init.outputs.version }}" + create_manifest "rc" "${{ needs.init.outputs.version }}" + + # Create series version tag (e.g. 2021.6) + v="${{ needs.init.outputs.version }}" + create_manifest "${v%.*}" "${{ needs.init.outputs.version }}" + fi From d7c480f2d8bd15efca17b04a37428c046b1e2b76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 2 Mar 2022 16:55:33 +0100 Subject: [PATCH 1098/1098] Set fail-fast to false for meta container (#67484) --- .github/workflows/builder.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index a857ef77edbb1b..2ca9b754a3a5fa 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -248,6 +248,7 @@ jobs: needs: ["init", "build_base"] runs-on: ubuntu-latest strategy: + fail-fast: false matrix: registry: - "ghcr.io/home-assistant"